123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736 |
- /**
- * 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);
- }
|