You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							736 lines
						
					
					
						
							23 KiB
						
					
					
				
			
		
		
	
	
							736 lines
						
					
					
						
							23 KiB
						
					
					
				/** | 
						|
 * 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); | 
						|
} | 
						|
 | 
						|
 | 
						|
 |