diff --git a/src/ExternalModule.js b/src/ExternalModule.js index 45af687..d6c003d 100644 --- a/src/ExternalModule.js +++ b/src/ExternalModule.js @@ -39,7 +39,10 @@ export default class ExternalModule { originalName: name, name, - module: this + module: this, + mark: () => { + this.importedByBundle[ name ] = true; + } }); } @@ -47,5 +50,9 @@ export default class ExternalModule { }; } - markExport () {} + // External modules are always marked for inclusion in the bundle. + // Marking an external module signals its use as a namespace. + mark () { + this.needsAll = true; + } } diff --git a/src/Module.js b/src/Module.js index f606c6a..eb1501d 100644 --- a/src/Module.js +++ b/src/Module.js @@ -144,10 +144,14 @@ export default class Module { // Always define a new `Identifier` for the default export. this.exports.define( 'default', { - originalName: name, + originalName: 'default', name, module: this, + mark () { + this.isUsed = true; + this.statement.mark(); + }, statement, // Keep the identifier name, if one exists. @@ -194,6 +198,10 @@ export default class Module { name, module: this, + mark () { + this.isUsed = true; + this.statement.mark(); + }, statement, localName: name, expression: declaration @@ -259,7 +267,11 @@ export default class Module { name, statement, - module: this + module: this, + mark () { + this.isUsed = true; + this.statement.mark(); + } }); }); @@ -296,7 +308,11 @@ export default class Module { // For each name we depend on that isn't in scope, // add a new global and bind the local name to it. if ( !this.locals.inScope( name ) ) { - this.bundle.globals.define( name ); + this.bundle.globals.define( name, { + originalName: name, + name, + mark () {} + }); this.locals.bind( name, this.bundle.globals.reference( name ) ); } }); @@ -383,6 +399,8 @@ export default class Module { // Enforce dynamic access of the module's properties. dynamicAccess () { + if ( this.needsDynamicAccess ) return; + this.needsDynamicAccess = true; this.markAllExportStatements(); @@ -395,20 +413,8 @@ export default class Module { return this.bundle.moduleById[ this.resolvedIds[ source ] ]; } - mark ( name ) { - const id = this.locals.lookup( name ); - - if ( id && id.module ) { - if ( id.module.isExternal ) { - id.module.importedByBundle[ id.originalName ] = true; - } - - if ( id.statement ) { - // Assert that statement is defined. It isn't for external modules. - id.isUsed = true; - id.statement.mark(); - } - } + mark () { + this.dynamicAccess(); } markAllStatements ( isEntryModule ) { @@ -436,7 +442,7 @@ export default class Module { else { // Be sure to mark the default export for the entry module. if ( isEntryModule && statement.node.type === 'ExportDefaultDeclaration' ) { - this.markExport( 'default', this ); + this.exports.lookup( 'default' ).mark(); } statement.mark(); @@ -450,34 +456,6 @@ export default class Module { }); } - markExport ( name, importer ) { - const id = this.exports.lookup( name ); - - if ( id ) { - id.isUsed = true; - - // Assert that statement is defined. It isn't for external modules. - if ( id.statement ) id.statement.mark(); - - return; - } - - for ( const module of this.exportAlls ) { - const id = module.exports.lookup( name ); - - if ( id ) { - id.isUsed = true; - - // Assert that statement is defined. It isn't for external modules. - if ( id.statement ) id.statement.mark(); - - return; - } - } - - throw new Error( `Module ${this.id} does not export ${name} (imported by ${importer.id})` ); - } - parse ( ast ) { // The ast can be supplied programmatically (but usually won't be) if ( !ast ) { diff --git a/src/Statement.js b/src/Statement.js index 818b837..1b746ea 100644 --- a/src/Statement.js +++ b/src/Statement.js @@ -151,6 +151,7 @@ export default class Statement { if ( node._scope ) scope = scope.parent; + // Optimize namespace lookups, which manifests as MemberExpressions. if ( node.type === 'MemberExpression' && ( !currentMemberExpression || node.object === currentMemberExpression ) ) { currentMemberExpression = node; @@ -162,9 +163,20 @@ export default class Statement { namespace = id; } - const name = node.property.name || ( node.property.type === 'Literal' ? node.property.value : null ); - - if ( !name ) { + // Extract the name of the accessed property, from and Identifier or Literal. + // Any eventual Literal value is converted to a string. + const name = node.property.name || + ( node.property.type === 'Literal' ? String( node.property.value ) : null ); + + // If we can't resolve the name being accessed, + // we require the namespace to be dynamically accessible. + // + // // resolvable + // console.log( javascript.keywords[ 6 ] ) + // + // // unresolvable + // console.log( javascript.keywords[ 1 + 5 ] ) + if ( name === null ) { namespace.dynamicAccess(); namespace = null; @@ -312,31 +324,21 @@ export default class Statement { // `export { name } from './other'` is a special case if ( this.isReexportDeclaration ) { - const otherModule = this.module.getModule( this.node.source.value ); - + // TODO: If we add the specifiers to `dependantIds`, + // we can remove this special case. this.node.specifiers.forEach( specifier => { - otherModule.markExport( specifier.local.name, this.module ); + this.module.exports.lookup( specifier.exported.name ).mark(); }); return; } - this.dependantIds.forEach( id => { - // FIXME: what should be done about modules? - if ( id.isModule ) { - if ( id.isExternal ) { - - } else { - - } - } - - id.module && id.module.markExport( id.originalName, this.module ); - }); + this.dependantIds.forEach( id => id.mark() ); + // TODO: perhaps these could also be added? keys( this.dependsOn ).forEach( name => { if ( this.defines[ name ] ) return; // TODO maybe exclude from `this.dependsOn` in the first place? - this.module.mark( name ); + this.module.locals.lookup( name ).mark(); }); }