303 lines
8.4 KiB
JavaScript
303 lines
8.4 KiB
JavaScript
|
'use strict';
|
||
|
|
||
|
var fs = require('fs');
|
||
|
var path = require('path');
|
||
|
var babylon = require('babylon');
|
||
|
var t = require('babel-types');
|
||
|
var generate = require('babel-generator').default;
|
||
|
var traverse = require('babel-traverse').default;
|
||
|
var resolve = require('resolve');
|
||
|
|
||
|
var camelToDashed = require('../lib/parsers').camelToDashed;
|
||
|
|
||
|
var basename = path.basename;
|
||
|
var dirname = path.dirname;
|
||
|
|
||
|
var uniqueIndex = 0;
|
||
|
function getUniqueIndex() {
|
||
|
return uniqueIndex++;
|
||
|
}
|
||
|
|
||
|
var property_files = fs.readdirSync(path.resolve(__dirname, '../lib/properties')).filter(function (property) {
|
||
|
return property.substr(-3) === '.js';
|
||
|
});
|
||
|
var out_file = fs.createWriteStream(path.resolve(__dirname, '../lib/properties.js'), {encoding: 'utf-8'});
|
||
|
|
||
|
out_file.write('\'use strict\';\n\n// autogenerated\n\n');
|
||
|
out_file.write('/*\n *\n * http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSS2Properties\n */\n\n');
|
||
|
|
||
|
function isModuleDotExports(node) {
|
||
|
return (
|
||
|
t.isMemberExpression(node, {computed: false}) &&
|
||
|
t.isIdentifier(node.object, {name: 'module'}) &&
|
||
|
t.isIdentifier(node.property, {name: 'exports'})
|
||
|
);
|
||
|
}
|
||
|
function isRequire(node, filename) {
|
||
|
if (
|
||
|
t.isCallExpression(node) &&
|
||
|
t.isIdentifier(node.callee, {name: 'require'}) &&
|
||
|
node.arguments.length === 1 &&
|
||
|
t.isStringLiteral(node.arguments[0])
|
||
|
) {
|
||
|
var relative = node.arguments[0].value;
|
||
|
var fullPath = resolve.sync(relative, {basedir: dirname(filename)});
|
||
|
return {relative: relative, fullPath: fullPath};
|
||
|
} else {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// step 1: parse all files and figure out their dependencies
|
||
|
var parsedFilesByPath = {};
|
||
|
property_files.map(function (property) {
|
||
|
var filename = path.resolve(__dirname, '../lib/properties/' + property);
|
||
|
var src = fs.readFileSync(filename, 'utf8');
|
||
|
property = basename(property, '.js');
|
||
|
var ast = babylon.parse(src);
|
||
|
var dependencies = [];
|
||
|
traverse(ast, {
|
||
|
enter(path) {
|
||
|
var r;
|
||
|
if (r = isRequire(path.node, filename)) {
|
||
|
dependencies.push(r.fullPath);
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
parsedFilesByPath[filename] = {
|
||
|
filename: filename,
|
||
|
property: property,
|
||
|
ast: ast,
|
||
|
dependencies: dependencies,
|
||
|
};
|
||
|
});
|
||
|
|
||
|
// step 2: serialize the files in an order where dependencies are always above
|
||
|
// the files they depend on
|
||
|
var externalDependencies = [];
|
||
|
var parsedFiles = [];
|
||
|
var addedFiles = {};
|
||
|
function addFile(filename, dependencyPath) {
|
||
|
if (dependencyPath.indexOf(filename) !== -1) {
|
||
|
throw new Error(
|
||
|
'Circular dependency: ' +
|
||
|
dependencyPath.slice(dependencyPath.indexOf(filename)).concat([filename]).join(' -> ')
|
||
|
);
|
||
|
}
|
||
|
var file = parsedFilesByPath[filename];
|
||
|
if (addedFiles[filename]) {
|
||
|
return;
|
||
|
}
|
||
|
if (!file) {
|
||
|
externalDependencies.push(filename);
|
||
|
} else {
|
||
|
file.dependencies.forEach(function (dependency) {
|
||
|
addFile(dependency, dependencyPath.concat([filename]));
|
||
|
});
|
||
|
parsedFiles.push(parsedFilesByPath[filename]);
|
||
|
}
|
||
|
addedFiles[filename] = true;
|
||
|
}
|
||
|
Object.keys(parsedFilesByPath).forEach(function (filename) {
|
||
|
addFile(filename, []);
|
||
|
});
|
||
|
// Step 3: add files to output
|
||
|
// renaming exports to local variables `moduleName_export_exportName`
|
||
|
// and updating require calls as appropriate
|
||
|
var moduleExportsByPath = {};
|
||
|
var statements = [];
|
||
|
externalDependencies.forEach(function (filename, i) {
|
||
|
var id = t.identifier(
|
||
|
'external_dependency_' +
|
||
|
basename(filename, '.js').replace(/[^A-Za-z]/g, '') +
|
||
|
'_' + i
|
||
|
);
|
||
|
moduleExportsByPath[filename] = {defaultExports: id};
|
||
|
var relativePath = path.relative(path.resolve(__dirname + '/../lib'), filename);
|
||
|
if (relativePath[0] !== '.') {
|
||
|
relativePath = './' + relativePath;
|
||
|
}
|
||
|
statements.push(t.variableDeclaration(
|
||
|
'var',
|
||
|
[
|
||
|
t.variableDeclarator(
|
||
|
id,
|
||
|
t.callExpression(
|
||
|
t.identifier('require'),
|
||
|
[
|
||
|
t.stringLiteral(
|
||
|
relativePath
|
||
|
)
|
||
|
]
|
||
|
)
|
||
|
)
|
||
|
]
|
||
|
));
|
||
|
});
|
||
|
function getRequireValue(node, file) {
|
||
|
var r;
|
||
|
// replace require("./foo").bar with the named export from foo
|
||
|
if (
|
||
|
t.isMemberExpression(node, {computed: false}) &&
|
||
|
(r = isRequire(node.object, file.filename))
|
||
|
) {
|
||
|
var e = moduleExportsByPath[r.fullPath];
|
||
|
if (!e) {
|
||
|
return;
|
||
|
}
|
||
|
if (!e.namedExports) {
|
||
|
return t.memberExpression(
|
||
|
e.defaultExports,
|
||
|
node.property
|
||
|
);
|
||
|
}
|
||
|
if (!e.namedExports[node.property.name]) {
|
||
|
throw new Error(r.relative + ' does not export ' + node.property.name);
|
||
|
}
|
||
|
return e.namedExports[node.property.name];
|
||
|
|
||
|
// replace require("./foo") with the default export of foo
|
||
|
} else if (r = isRequire(node, file.filename)) {
|
||
|
var e = moduleExportsByPath[r.fullPath];
|
||
|
if (!e) {
|
||
|
if (/^\.\.\//.test(r.relative)) {
|
||
|
node.arguments[0].value = r.relative.substr(1);
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
return e.defaultExports;
|
||
|
}
|
||
|
}
|
||
|
parsedFiles.forEach(function (file) {
|
||
|
var namedExports = {};
|
||
|
var localVariableMap = {};
|
||
|
|
||
|
traverse(file.ast, {
|
||
|
enter(path) {
|
||
|
// replace require calls with the corresponding value
|
||
|
var r;
|
||
|
if (r = getRequireValue(path.node, file)) {
|
||
|
path.replaceWith(r);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// if we see `var foo = require('bar')` we can just inline the variable
|
||
|
// representing `require('bar')` wherever `foo` was used.
|
||
|
if (
|
||
|
t.isVariableDeclaration(path.node) &&
|
||
|
path.node.declarations.length === 1 &&
|
||
|
t.isIdentifier(path.node.declarations[0].id) &&
|
||
|
(r = getRequireValue(path.node.declarations[0].init, file))
|
||
|
) {
|
||
|
var newName = 'compiled_local_variable_reference_' + getUniqueIndex();
|
||
|
path.scope.rename(
|
||
|
path.node.declarations[0].id.name,
|
||
|
newName
|
||
|
);
|
||
|
localVariableMap[newName] = r;
|
||
|
path.remove();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// rename all top level variables to keep them local to the module
|
||
|
if (
|
||
|
t.isVariableDeclaration(path.node) &&
|
||
|
t.isProgram(path.parent)
|
||
|
) {
|
||
|
path.node.declarations.forEach(function (declaration) {
|
||
|
path.scope.rename(
|
||
|
declaration.id.name,
|
||
|
file.property + '_local_var_' + declaration.id.name
|
||
|
);
|
||
|
});
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// replace module.exports.bar with a variable for the named export
|
||
|
if (
|
||
|
t.isMemberExpression(path.node, {computed: false}) &&
|
||
|
isModuleDotExports(path.node.object)
|
||
|
) {
|
||
|
var name = path.node.property.name;
|
||
|
var identifier = t.identifier(file.property + '_export_' + name);
|
||
|
path.replaceWith(identifier);
|
||
|
namedExports[name] = identifier;
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
traverse(file.ast, {
|
||
|
enter(path) {
|
||
|
if (
|
||
|
t.isIdentifier(path.node) &&
|
||
|
Object.prototype.hasOwnProperty.call(localVariableMap, path.node.name)
|
||
|
) {
|
||
|
path.replaceWith(localVariableMap[path.node.name]);
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
var defaultExports = t.objectExpression(Object.keys(namedExports).map(function (name) {
|
||
|
return t.objectProperty(t.identifier(name), namedExports[name]);
|
||
|
}));
|
||
|
moduleExportsByPath[file.filename] = {
|
||
|
namedExports: namedExports,
|
||
|
defaultExports: defaultExports
|
||
|
};
|
||
|
statements.push(t.variableDeclaration(
|
||
|
'var',
|
||
|
Object.keys(namedExports).map(function (name) {
|
||
|
return t.variableDeclarator(namedExports[name]);
|
||
|
})
|
||
|
))
|
||
|
statements.push.apply(statements, file.ast.program.body);
|
||
|
});
|
||
|
var propertyDefinitions = [];
|
||
|
parsedFiles.forEach(function (file) {
|
||
|
var dashed = camelToDashed(file.property);
|
||
|
propertyDefinitions.push(
|
||
|
t.objectProperty(
|
||
|
t.identifier(file.property),
|
||
|
t.identifier(file.property + '_export_definition')
|
||
|
)
|
||
|
);
|
||
|
if (file.property !== dashed) {
|
||
|
propertyDefinitions.push(
|
||
|
t.objectProperty(
|
||
|
t.stringLiteral(dashed),
|
||
|
t.identifier(file.property + '_export_definition')
|
||
|
)
|
||
|
);
|
||
|
}
|
||
|
});
|
||
|
var definePropertiesCall = t.callExpression(
|
||
|
t.memberExpression(
|
||
|
t.identifier('Object'),
|
||
|
t.identifier('defineProperties')
|
||
|
),
|
||
|
[
|
||
|
t.identifier('prototype'),
|
||
|
t.objectExpression(
|
||
|
propertyDefinitions
|
||
|
)
|
||
|
]
|
||
|
);
|
||
|
statements.push(t.expressionStatement(
|
||
|
t.assignmentExpression(
|
||
|
'=',
|
||
|
t.memberExpression(
|
||
|
t.identifier('module'),
|
||
|
t.identifier('exports')
|
||
|
),
|
||
|
t.functionExpression(
|
||
|
null,
|
||
|
[t.identifier('prototype')],
|
||
|
t.blockStatement([t.expressionStatement(definePropertiesCall)])
|
||
|
)
|
||
|
)
|
||
|
));
|
||
|
out_file.write(generate(t.program(statements)).code + '\n')
|
||
|
out_file.end(function (err) {
|
||
|
if (err) {
|
||
|
throw err;
|
||
|
}
|
||
|
});
|