lepu-test-platform-web/node_modules/zrender/build/babel-plugin-transform-modu...

737 lines
23 KiB
JavaScript
Raw Normal View History

/**
* Both used by zrender and echarts.
*/
const assert = require('assert');
const nodePath = require('path');
const basename = nodePath.basename;
const extname = nodePath.extname;
const babelTypes = require('@babel/types');
const babelTemplate = require('@babel/template');
const helperModuleTransforms = require('@babel/helper-module-transforms');
const isModule = helperModuleTransforms.isModule;
const isSideEffectImport = helperModuleTransforms.isSideEffectImport;
const ensureStatementsHoisted = helperModuleTransforms.ensureStatementsHoisted;
module.exports = function ({types, template}, options) {
return {
visitor: {
Program: {
exit(path) {
// For now this requires unambiguous rather that just sourceType
// because Babel currently parses all files as sourceType:module.
if (!isModule(path, true /* requireUnambiguous */)) {
return;
}
// Rename the bindings auto-injected into the scope so there is no
// risk of conflict between the bindings.
path.scope.rename('exports');
path.scope.rename('module');
path.scope.rename('require');
path.scope.rename('__filename');
path.scope.rename('__dirname');
const meta = rewriteModuleStatementsAndPrepare(path);
let headers = [];
let tails = [];
const checkExport = createExportChecker();
for (const [source, metadata] of meta.source) {
headers.push(...buildRequireStatements(types, source, metadata));
headers.push(...buildNamespaceInitStatements(meta, metadata, checkExport));
}
tails.push(...buildLocalExportStatements(meta, checkExport));
ensureStatementsHoisted(headers);
// FIXME ensure tail?
path.unshiftContainer('body', headers);
path.pushContainer('body', tails);
checkAssignOrUpdateExport(path, meta);
}
}
}
};
};
/**
* Remove all imports and exports from the file, and return all metadata
* needed to reconstruct the module's behavior.
* @return {ModuleMetadata}
*/
function normalizeModuleAndLoadMetadata(programPath) {
nameAnonymousExports(programPath);
const {local, source} = getModuleMetadata(programPath);
removeModuleDeclarations(programPath);
// Reuse the imported namespace name if there is one.
for (const [, metadata] of source) {
if (metadata.importsNamespace.size > 0) {
// This is kind of gross. If we stop using `loose: true` we should
// just make this destructuring assignment.
metadata.name = metadata.importsNamespace.values().next().value;
}
}
return {
exportName: 'exports',
exportNameListName: null,
local,
source
};
}
/**
* Get metadata about the imports and exports present in this module.
*/
function getModuleMetadata(programPath) {
const localData = getLocalExportMetadata(programPath);
const sourceData = new Map();
const getData = sourceNode => {
const source = sourceNode.value;
let data = sourceData.get(source);
if (!data) {
data = {
name: programPath.scope.generateUidIdentifier(
basename(source, extname(source))
).name,
interop: 'none',
loc: null,
// Data about the requested sources and names.
imports: new Map(),
// importsNamespace: import * as util from './a/b/util';
importsNamespace: new Set(),
// Metadata about data that is passed directly from source to export.
reexports: new Map(),
reexportNamespace: new Set(),
reexportAll: null,
};
sourceData.set(source, data);
}
return data;
};
programPath.get('body').forEach(child => {
if (child.isImportDeclaration()) {
const data = getData(child.node.source);
if (!data.loc) {
data.loc = child.node.loc;
}
child.get('specifiers').forEach(spec => {
if (spec.isImportDefaultSpecifier()) {
const localName = spec.get('local').node.name;
data.imports.set(localName, 'default');
const reexport = localData.get(localName);
if (reexport) {
localData.delete(localName);
reexport.names.forEach(name => {
data.reexports.set(name, 'default');
});
}
}
else if (spec.isImportNamespaceSpecifier()) {
const localName = spec.get('local').node.name;
assert(
data.importsNamespace.size === 0,
`Duplicate import namespace: ${localName}`
);
data.importsNamespace.add(localName);
const reexport = localData.get(localName);
if (reexport) {
localData.delete(localName);
reexport.names.forEach(name => {
data.reexportNamespace.add(name);
});
}
}
else if (spec.isImportSpecifier()) {
const importName = spec.get('imported').node.name;
const localName = spec.get('local').node.name;
data.imports.set(localName, importName);
const reexport = localData.get(localName);
if (reexport) {
localData.delete(localName);
reexport.names.forEach(name => {
data.reexports.set(name, importName);
});
}
}
});
}
else if (child.isExportAllDeclaration()) {
const data = getData(child.node.source);
if (!data.loc) {
data.loc = child.node.loc;
}
data.reexportAll = {
loc: child.node.loc,
};
}
else if (child.isExportNamedDeclaration() && child.node.source) {
const data = getData(child.node.source);
if (!data.loc) {
data.loc = child.node.loc;
}
child.get('specifiers').forEach(spec => {
if (!spec.isExportSpecifier()) {
throw spec.buildCodeFrameError('Unexpected export specifier type');
}
const importName = spec.get('local').node.name;
const exportName = spec.get('exported').node.name;
data.reexports.set(exportName, importName);
if (exportName === '__esModule') {
throw exportName.buildCodeFrameError('Illegal export "__esModule".');
}
});
}
});
for (const metadata of sourceData.values()) {
if (metadata.importsNamespace.size > 0) {
metadata.interop = 'namespace';
continue;
}
let needsDefault = false;
let needsNamed = false;
for (const importName of metadata.imports.values()) {
if (importName === 'default') {
needsDefault = true;
}
else {
needsNamed = true;
}
}
for (const importName of metadata.reexports.values()) {
if (importName === 'default') {
needsDefault = true;
}
else {
needsNamed = true;
}
}
if (needsDefault && needsNamed) {
// TODO(logan): Using the namespace interop here is unfortunate. Revisit.
metadata.interop = 'namespace';
}
else if (needsDefault) {
metadata.interop = 'default';
}
}
return {
local: localData,
source: sourceData,
};
}
/**
* Get metadata about local variables that are exported.
* @return {Map<string, LocalExportMetadata>}
*/
function getLocalExportMetadata(programPath){
const bindingKindLookup = new Map();
programPath.get('body').forEach(child => {
let kind;
if (child.isImportDeclaration()) {
kind = 'import';
}
else {
if (child.isExportDefaultDeclaration()) {
child = child.get('declaration');
}
if (child.isExportNamedDeclaration() && child.node.declaration) {
child = child.get('declaration');
}
if (child.isFunctionDeclaration()) {
kind = 'hoisted';
}
else if (child.isClassDeclaration()) {
kind = 'block';
}
else if (child.isVariableDeclaration({ kind: 'var' })) {
kind = 'var';
}
else if (child.isVariableDeclaration()) {
kind = 'block';
}
else {
return;
}
}
Object.keys(child.getOuterBindingIdentifiers()).forEach(name => {
bindingKindLookup.set(name, kind);
});
});
const localMetadata = new Map();
const getLocalMetadata = idPath => {
const localName = idPath.node.name;
let metadata = localMetadata.get(localName);
if (!metadata) {
const kind = bindingKindLookup.get(localName);
if (kind === undefined) {
throw idPath.buildCodeFrameError(`Exporting local "${localName}", which is not declared.`);
}
metadata = {
names: [],
kind,
};
localMetadata.set(localName, metadata);
}
return metadata;
};
programPath.get('body').forEach(child => {
if (child.isExportNamedDeclaration() && !child.node.source) {
if (child.node.declaration) {
const declaration = child.get('declaration');
const ids = declaration.getOuterBindingIdentifierPaths();
Object.keys(ids).forEach(name => {
if (name === '__esModule') {
throw declaration.buildCodeFrameError('Illegal export "__esModule".');
}
getLocalMetadata(ids[name]).names.push(name);
});
}
else {
child.get('specifiers').forEach(spec => {
const local = spec.get('local');
const exported = spec.get('exported');
if (exported.node.name === '__esModule') {
throw exported.buildCodeFrameError('Illegal export "__esModule".');
}
getLocalMetadata(local).names.push(exported.node.name);
});
}
}
else if (child.isExportDefaultDeclaration()) {
const declaration = child.get('declaration');
if (
declaration.isFunctionDeclaration() ||
declaration.isClassDeclaration()
) {
getLocalMetadata(declaration.get('id')).names.push('default');
}
else {
// These should have been removed by the nameAnonymousExports() call.
throw declaration.buildCodeFrameError('Unexpected default expression export.');
}
}
});
return localMetadata;
}
/**
* Ensure that all exported values have local binding names.
*/
function nameAnonymousExports(programPath) {
// Name anonymous exported locals.
programPath.get('body').forEach(child => {
if (!child.isExportDefaultDeclaration()) {
return;
}
// export default foo;
const declaration = child.get('declaration');
if (declaration.isFunctionDeclaration()) {
if (!declaration.node.id) {
declaration.node.id = declaration.scope.generateUidIdentifier('default');
}
}
else if (declaration.isClassDeclaration()) {
if (!declaration.node.id) {
declaration.node.id = declaration.scope.generateUidIdentifier('default');
}
}
else {
const id = declaration.scope.generateUidIdentifier('default');
const namedDecl = babelTypes.exportNamedDeclaration(null, [
babelTypes.exportSpecifier(babelTypes.identifier(id.name), babelTypes.identifier('default')),
]);
namedDecl._blockHoist = child.node._blockHoist;
const varDecl = babelTypes.variableDeclaration('var', [
babelTypes.variableDeclarator(id, declaration.node),
]);
varDecl._blockHoist = child.node._blockHoist;
child.replaceWithMultiple([namedDecl, varDecl]);
}
});
}
function removeModuleDeclarations(programPath) {
programPath.get('body').forEach(child => {
if (child.isImportDeclaration()) {
child.remove();
}
else if (child.isExportNamedDeclaration()) {
if (child.node.declaration) {
child.node.declaration._blockHoist = child.node._blockHoist;
child.replaceWith(child.node.declaration);
}
else {
child.remove();
}
}
else if (child.isExportDefaultDeclaration()) {
// export default foo;
const declaration = child.get('declaration');
if (
declaration.isFunctionDeclaration() ||
declaration.isClassDeclaration()
) {
declaration._blockHoist = child.node._blockHoist;
child.replaceWith(declaration);
}
else {
// These should have been removed by the nameAnonymousExports() call.
throw declaration.buildCodeFrameError('Unexpected default expression export.');
}
}
else if (child.isExportAllDeclaration()) {
child.remove();
}
});
}
/**
* Perform all of the generic ES6 module rewriting needed to handle initial
* module processing. This function will rewrite the majority of the given
* program to reference the modules described by the returned metadata,
* and returns a list of statements for use when initializing the module.
*/
function rewriteModuleStatementsAndPrepare(path) {
path.node.sourceType = 'script';
const meta = normalizeModuleAndLoadMetadata(path);
return meta;
}
/**
* Create the runtime initialization statements for a given requested source.
* These will initialize all of the runtime import/export logic that
* can't be handled statically by the statements created by
* buildExportInitializationStatements().
*/
function buildNamespaceInitStatements(meta, metadata, checkExport) {
const statements = [];
const {localImportName, localImportDefaultName} = getLocalImportName(metadata);
for (const exportName of metadata.reexportNamespace) {
// Assign export to namespace object.
checkExport(exportName);
statements.push(buildExport({exportName, localName: localImportName}));
}
// Source code:
// import {color2 as color2Alias, color3, color4, color5} from 'xxx';
// export {default as b} from 'xxx';
// export {color2Alias};
// export {color3};
// let color5Renamed = color5
// export {color5Renamed};
// Only two entries in metadata.reexports:
// 'color2Alias' => 'color2'
// 'color3' => 'color3',
// 'b' => 'default'
//
// And consider:
// export {default as defaultAsBB} from './xx/yy';
// export {exportSingle} from './xx/yy';
// No entries in metadata.imports, and 'default' exists in metadata.reexports.
for (const entry of metadata.reexports.entries()) {
const exportName = entry[0];
checkExport(exportName);
statements.push(
(localImportDefaultName || entry[1] === 'default')
? buildExport({exportName, localName: localImportName})
: buildExport({exportName, namespace: localImportName, propName: entry[1]})
);
}
if (metadata.reexportAll) {
const statement = buildNamespaceReexport(
meta,
metadata.name,
checkExport
);
statement.loc = metadata.reexportAll.loc;
// Iterate props creating getter for each prop.
statements.push(statement);
}
return statements;
}
/**
* Create a re-export initialization loop for a specific imported namespace.
*/
function buildNamespaceReexport(meta, namespace, checkExport) {
checkExport();
return babelTemplate.statement(`
(function() {
for (var key in NAMESPACE) {
if (NAMESPACE == null || !NAMESPACE.hasOwnProperty(key) || key === 'default' || key === '__esModule') return;
VERIFY_NAME_LIST;
exports[key] = NAMESPACE[key];
}
})();
`)({
NAMESPACE: namespace,
VERIFY_NAME_LIST: meta.exportNameListName
? babelTemplate.statement(`
if (Object.prototype.hasOwnProperty.call(EXPORTS_LIST, key)) return;
`)({EXPORTS_LIST: meta.exportNameListName})
: null
});
}
function buildRequireStatements(types, source, metadata) {
let headers = [];
const loadExpr = types.callExpression(
types.identifier('require'),
// replace `require('./src/xxx')` to `require('./lib/xxx')`
// for echarts and zrender in old npm or webpack.
[types.stringLiteral(source.replace('/src/', '/lib/'))]
);
// side effect import: import 'xxx';
if (isSideEffectImport(metadata)) {
let header = types.expressionStatement(loadExpr);
header.loc = metadata.loc;
headers.push(header);
}
else {
const {localImportName, localImportDefaultName} = getLocalImportName(metadata);
let reqHeader = types.variableDeclaration('var', [
types.variableDeclarator(
types.identifier(localImportName),
loadExpr
)
]);
reqHeader.loc = metadata.loc;
headers.push(reqHeader);
if (!localImportDefaultName) {
// src:
// import {someInZrUtil1 as someInZrUtil1Alias, zz} from 'zrender/core/util';
// metadata.imports:
// Map { 'someInZrUtil1Alias' => 'someInZrUtil1', 'zz' => 'zz' }
for (const importEntry of metadata.imports) {
headers.push(
babelTemplate.statement(`var IMPORTNAME = NAMESPACE.PROPNAME;`)({
NAMESPACE: localImportName,
IMPORTNAME: importEntry[0],
PROPNAME: importEntry[1]
})
);
}
}
}
return headers;
}
function getLocalImportName(metadata) {
const localImportDefaultName = getDefaultName(metadata.imports);
assert(
!localImportDefaultName || metadata.imports.size === 1,
'Forbiden that both import default and others.'
);
return {
localImportName: localImportDefaultName || metadata.name,
localImportDefaultName
};
}
function getDefaultName(map) {
for (const entry of map) {
if (entry[1] === 'default') {
return entry[0];
}
}
}
function buildLocalExportStatements(meta, checkExport) {
let tails = [];
// All local export, for example:
// Map {
// 'localVarMame' => {
// names: [ 'exportName1', 'exportName2' ],
// kind: 'var'
// },
for (const localEntry of meta.local) {
for (const exportName of localEntry[1].names) {
checkExport(exportName);
tails.push(buildExport({exportName, localName: localEntry[0]}));
}
}
return tails;
}
function createExportChecker() {
let someHasBeenExported;
return function checkExport(exportName) {
assert(
!someHasBeenExported || exportName !== 'default',
`Forbiden that both export default and others.`
);
someHasBeenExported = true;
};
}
function buildExport({exportName, namespace, propName, localName}) {
const exportDefault = exportName === 'default';
const head = exportDefault ? 'module.exports' : `exports.${exportName}`;
let opt = {};
// FIXME
// Does `PRIORITY`, `LOCATION_PARAMS` recognised as babel-template placeholder?
// We have to do this for workaround temporarily.
if (/^[A-Z0-9_]+$/.test(localName)) {
opt[localName] = localName;
}
return babelTemplate.statement(
localName
? `${head} = ${localName};`
: `${head} = ${namespace}.${propName};`
)(opt);
}
/**
* Consider this case:
* export var a;
* function inject(b) {
* a = b;
* }
* It will be transpiled to:
* var a;
* exports.a = 1;
* function inject(b) {
* a = b;
* }
* That is a wrong transpilation, because the `export.a` will not
* be assigned as `b` when `inject` called.
* Of course, it can be transpiled correctly as:
* var _locals = {};
* var a;
* Object.defineProperty(exports, 'a', {
* get: function () { return _locals[a]; }
* };
* exports.a = a;
* function inject(b) {
* _locals[a] = b;
* }
* But it is not ES3 compatible.
* So we just forbiden this usage here.
*/
function checkAssignOrUpdateExport(programPath, meta) {
let visitor = {
// Include:
// `a++;` (no `path.get('left')`)
// `x += 1212`;
UpdateExpression: {
exit: function exit(path, scope) {
// console.log(arguments);
let left = path.get('left');
if (left && left.isIdentifier()) {
asertNotAssign(path, left.node.name);
}
}
},
// Include:
// `x = 5;` (`x` is an identifier.)
// `c.d = 3;` (but `c.d` is not an identifier.)
// `y = function () {}`
// Exclude:
// `var x = 121;`
// `export var x = 121;`
AssignmentExpression: {
exit: function exit(path) {
let left = path.get('left');
if (left.isIdentifier()) {
asertNotAssign(path, left.node.name);
}
}
}
};
function asertNotAssign(path, localName) {
// Ignore variables that is not in global scope.
if (programPath.scope.getBinding(localName) !== path.scope.getBinding(localName)) {
return;
}
for (const localEntry of meta.local) {
assert(
localName !== localEntry[0],
`An exported variable \`${localEntry[0]}\` is forbiden to be assigned.`
);
}
}
programPath.traverse(visitor);
}