From 98cf636e2ec28961132b1cc2efb6e350060e2030 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Segersv=C3=A4rd?= Date: Mon, 14 Sep 2015 17:31:24 +0200 Subject: [PATCH] Implemented `export * from "internal";` --- src/Bundle.js | 18 +++++------ src/Module.js | 81 ++++++++++++++++++++++++++++++++---------------- src/Scope.js | 4 +-- src/Statement.js | 4 +-- 4 files changed, 66 insertions(+), 41 deletions(-) diff --git a/src/Bundle.js b/src/Bundle.js index 6f0857b..b985c14 100644 --- a/src/Bundle.js +++ b/src/Bundle.js @@ -49,7 +49,6 @@ export default class Bundle { this.statements = null; this.externalModules = []; - this.internalNamespaceModules = []; } build () { @@ -60,13 +59,10 @@ export default class Bundle { this.exports = entryModule.exports; entryModule.markAllStatements( true ); - this.orderedModules = this.sort(); + entryModule.markAllExports(); - this.exports.localIds().forEach( ([ , id ]) => { - // If the export is a module (namespace), we need - // all its exports dynamically accessible. - if ( id.module === id ) id.dynamicAccess(); - }); + // Sort the modules. + this.orderedModules = this.sort(); // As a last step, deconflict all identifier names, once. this.scope.deconflict(); @@ -213,9 +209,11 @@ export default class Bundle { // prepend bundle with internal namespaces const indentString = getIndentString( magicString, options ); - const namespaceBlock = this.internalNamespaceModules.map( module => { - const exports = module.exports.localIds().map( ( [ name, id ] ) => - `${indentString}get ${name} () { return ${id.name}; }`); + const namespaceBlock = this.modules.filter( module => module.needsDynamicAccess ).map( module => { + const exports = module.exports.getNames().map( name => { + const id = module.exports.lookup( name ); + return `${indentString}get ${name} () { return ${id.name}; }`; + }); return `var ${module.name} = {\n` + exports.join( ',\n' ) + diff --git a/src/Module.js b/src/Module.js index 36607ec..18f9628 100644 --- a/src/Module.js +++ b/src/Module.js @@ -26,7 +26,7 @@ function removeSourceMappingURLComments ( source, magicString ) { } function assign ( target, source ) { - for ( var key in source ) target[ key ] = source[ key ]; + for ( let key in source ) target[ key ] = source[ key ]; } class Id { @@ -92,9 +92,9 @@ export default class Module { return reference.call( this.exports, name ); } - // ... otherwise search exportAlls - for ( let i = 0; i < this.exportAlls.length; i += 1 ) { - const module = this.exportAlls[i]; + // ... otherwise search allExportsFrom + for ( let i = 0; i < this.allExportsFrom.length; i += 1 ) { + const module = this.allExportsFrom[i]; if ( module.exports.inScope( name ) ) { return module.exports.reference( name ); } @@ -107,7 +107,7 @@ export default class Module { this.exports.inScope = name => { if ( inScope.call( this.exports, name ) ) return true; - return this.exportAlls.some( module => module.exports.inScope( name ) ); + return this.allExportsFrom.some( module => module.exports.inScope( name ) ); }; // Create a unique virtual scope for references to the module. @@ -115,7 +115,9 @@ export default class Module { // unique.define( this.name, this ); // this.reference = unique.reference( this.name ); - this.exportAlls = []; + // As far as we know, all our exported bindings have been resolved. + this.allExportsResolved = true; + this.allExportsFrom = []; this.reassignments = []; @@ -138,10 +140,18 @@ export default class Module { // When an unknown import is encountered, we see if one of them can satisfy it. if ( module.isExternal ) { - throw new Error( `Cannot trace 'export *' references through external modules.` ); + let err = new Error( `Cannot trace 'export *' references through external modules.` ); + err.file = this.id; + err.loc = getLocation( this.source, node.start ); + throw err; } - this.exportAlls.push( module ); + // It seems like we must re-export all exports from another module... + this.allExportsResolved = false; + + if ( !~this.allExportsFrom.indexOf( module ) ) { + this.allExportsFrom.push( module ); + } } else { @@ -266,6 +276,23 @@ export default class Module { }); }); + // If all exports aren't resolved, but all our delegate modules are... + if ( !this.allExportsResolved && this.allExportsFrom.every( module => module.allExportsResolved )) { + // .. then all our exports should be as well. + this.allExportsResolved = true; + + // For all modules we export all from, iterate through its exported names. + // If we don't already define the binding 'name', + // bind the name to the other module's reference. + this.allExportsFrom.forEach( module => { + module.exports.getNames().forEach( name => { + if ( !this.exports.defines( name ) ) { + this.exports.bind( name, module.exports.reference( name ) ); + } + }); + }); + } + // discover variables that are reassigned inside function // bodies, so we can keep bindings live, e.g. // @@ -375,8 +402,12 @@ export default class Module { }); }); - this.locals.getNames().forEach( name => { - const id = this.locals.lookup( name ); + // Go through all our local and exported ids and make us depend on + // the defining modules as well as + this.exports.getIds().concat(this.locals.getIds()).forEach( id => { + if ( id.module && !id.module.isExternal ) { + weakDependencies[ id.module.id ] = id.module; + } if ( !id.modifierStatements ) return; @@ -394,24 +425,16 @@ export default class Module { return { strongDependencies, weakDependencies }; } - // Enforce dynamic access of the module's properties. - dynamicAccess () { - if ( this.needsDynamicAccess ) return; - - this.needsDynamicAccess = true; - this.markAllExportStatements(); - - if ( !~this.bundle.internalNamespaceModules.indexOf( this ) ) { - this.bundle.internalNamespaceModules.push( this ); - } - } - getModule ( source ) { return this.bundle.moduleById[ this.resolvedIds[ source ] ]; } + // If a module is marked, enforce dynamic access of its properties. mark () { - this.dynamicAccess(); + if ( this.needsDynamicAccess ) return; + this.needsDynamicAccess = true; + + this.markAllExports(); } markAllStatements ( isEntryModule ) { @@ -447,10 +470,9 @@ export default class Module { }); } - markAllExportStatements () { - this.statements.forEach( statement => { - if ( statement.isExportDeclaration ) statement.mark(); - }); + // Marks all exported identifiers. + markAllExports () { + this.exports.getIds().forEach( id => id.mark() ); } parse ( ast ) { @@ -621,6 +643,11 @@ export default class Module { magicString.remove( statement.node.start, statement.node.declaration.start ); } + else if ( statement.node.type === 'ExportAllDeclaration' ) { + // TODO: remove once `export * from 'external'` is supported. + magicString.remove( statement.start, statement.next ); + } + // remove `export` from `export class Foo {...}` or `export default Foo` // TODO default exports need different treatment else if ( statement.node.declaration.id ) { diff --git a/src/Scope.js b/src/Scope.js index 1c04fae..9d644ef 100644 --- a/src/Scope.js +++ b/src/Scope.js @@ -112,8 +112,8 @@ export default class Scope { } // Returns a list of `[ name, identifier ]` tuples. - localIds () { - return keys( this.names ).map( name => [ name, this.lookup( name ) ] ); + getIds () { + return keys( this.names ).map( name => this.lookup( name ) ); } // Lookup the identifier referred to by `name`. diff --git a/src/Statement.js b/src/Statement.js index 790c3a1..260d4f5 100644 --- a/src/Statement.js +++ b/src/Statement.js @@ -200,7 +200,7 @@ export default class Statement { ( node.property.type === 'Literal' ? String( node.property.value ) : null ); // If we can't resolve the name being accessed statically, - // we require the namespace to be dynamically accessible. + // we mark the whole namespace for inclusion in the bundle. // // // resolvable // console.log( javascript.keywords.for ) @@ -211,7 +211,7 @@ export default class Statement { // console.log( javascript.keywords[ index ] ) // console.log( javascript.keywords[ 1 + 5 ] ) if ( name === null ) { - namespace.dynamicAccess(); + namespace.mark(); namespace = null; currentMemberExpression = null;