diff --git a/src/Bundle.js b/src/Bundle.js index b3b2d06..82b19dd 100644 --- a/src/Bundle.js +++ b/src/Bundle.js @@ -31,7 +31,8 @@ export default class Bundle { transform: ensureArray( options.transform ) }; - this.scope = new Scope(); + this.globals = new Scope(); + this.scope = new Scope( this.globals ); this.toExport = null; @@ -120,6 +121,8 @@ export default class Bundle { // Analyze the module once all its dependencies have been resolved. // This means that any dependencies of a module has already been // analysed when it's time for the module itself. + // + // FIXME: Except for cyclic dependencies... module.analyse(); return module; }); @@ -198,7 +201,6 @@ export default class Bundle { render ( options = {} ) { const format = options.format || 'es6'; - const allReplacements = blank(); // Determine export mode - 'default', 'named', 'none' const exportMode = getExportMode( this, options.exports ); @@ -225,7 +227,7 @@ export default class Bundle { this.orderedModules.forEach( module => { module.reassignments.forEach( name => { - isReassignedVarDeclaration[ module.replacements[ name ] || name ] = true; + isReassignedVarDeclaration[ module.exports.lookup( name ) ] = true; }); }); diff --git a/src/ExternalModule.js b/src/ExternalModule.js index 9503595..0d8a492 100644 --- a/src/ExternalModule.js +++ b/src/ExternalModule.js @@ -18,6 +18,22 @@ export default class ExternalModule { this.needsAll = false; this.exports = bundle.scope.virtual(); + + const ref = this.exports.reference; + + // Override reference. + this.exports.reference = name => { + if ( !this.exports.defines( name ) ) { + this.exports.define({ + originalName: name, + name, + + module: this + }); + } + + return ref.call( this.exports, name ); + }; } findDefiningStatement () { diff --git a/src/Module.js b/src/Module.js index f54d04c..9b5469d 100644 --- a/src/Module.js +++ b/src/Module.js @@ -4,6 +4,7 @@ import Statement from './Statement'; import walk from './ast/walk'; import { blank, keys } from './utils/object'; import getLocation from './utils/getLocation'; +import makeLegalIdentifier from './utils/makeLegalIdentifier'; function isEmptyExportedVarDeclaration ( node, exports, toExport ) { if ( node.type !== 'VariableDeclaration' || node.declarations[0].init ) return false; @@ -15,6 +16,14 @@ function isEmptyExportedVarDeclaration ( node, exports, toExport ) { return !~toExport.indexOf( id.name ); } +function removeSourceMappingURLComments ( source, magicString ) { + const pattern = /\/\/#\s+sourceMappingURL=.+\n?/g; + let match; + while ( match = pattern.exec( source ) ) { + magicString.remove( match.index, match.index + match[0].length ); + } +} + export default class Module { constructor ({ id, source, ast, bundle }) { this.source = source; @@ -23,7 +32,7 @@ export default class Module { this.id = id; // Implement Identifier interface. - this.name = id; + this.name = makeLegalIdentifier( id ); // By default, `id` is the filename. Custom resolvers and loaders // can change that, but it makes sense to use it for the source filename @@ -31,32 +40,21 @@ export default class Module { filename: id }); - // remove existing sourceMappingURL comments - const pattern = /\/\/#\s+sourceMappingURL=.+\n?/g; - let match; - while ( match = pattern.exec( source ) ) { - this.magicString.remove( match.index, match.index + match[0].length ); - } + removeSourceMappingURLComments( source, this.magicString ); - this.suggestedNames = blank(); this.comments = []; this.statements = this.parse( ast ); // all dependencies this.resolvedIds = blank(); - this.boundImportSpecifiers = false; - - // imports and exports, indexed by local name - this.imports = blank(); + // Virtual scopes for the local and exported names. this.locals = bundle.scope.virtual(); this.exports = bundle.scope.virtual(); this.exportAlls = []; - this.replacements = blank(); - this.reassignments = []; this.marked = blank(); @@ -107,7 +105,8 @@ export default class Module { // If the default export has an identifier, bind to it. this.exports.bind( 'default', this.locals.reference( identifier ) ); } else { - this.exports.define({ + // Define the default identifier. + const id = { originalName: 'default', name: 'default', @@ -117,7 +116,13 @@ export default class Module { isDeclaration, isAnonymous, isModified: false // in case of `export default foo; foo = somethingElse` - }); + }; + + this.exports.define( id ); + + // Rename it to avoid generating the `default` idenntifier, + // which is invalid. + id.name = this.name; } } @@ -149,7 +154,7 @@ export default class Module { name = declaration.id.name; } - this.exports.bind({ + this.locals.define({ originalName: name, name, @@ -157,6 +162,8 @@ export default class Module { localName: name, expression: declaration }); + + this.exports.bind( name, this.locals.reference( name ) ); } } } @@ -242,8 +249,8 @@ export default class Module { }); keys( statement.dependsOn ).forEach( name => { - if ( !this.definitions[ name ] && !this.imports[ name ] ) { - this.bundle.assumedGlobals[ name ] = true; + if ( !this.locals.inScope( name ) ) { + this.bundle.globals.define( name ); } }); }); @@ -315,26 +322,13 @@ export default class Module { } defaultName () { - const defaultExport = this.exports.default; - - if ( !defaultExport ) return null; - - const name = defaultExport.identifier && !defaultExport.isModified ? - defaultExport.identifier : - this.replacements.default; - - return this.replacements[ name ] || name; + return this.name; } findDefiningStatement ( name ) { if ( this.definitions[ name ] ) return this.definitions[ name ]; - // TODO what about `default`/`*`? - - const importDeclaration = this.imports[ name ]; - if ( !importDeclaration ) return null; - - return importDeclaration.module.findDefiningStatement( name ); + return null; } getModule ( source ) { @@ -344,9 +338,6 @@ export default class Module { mark ( name ) { const id = this.locals.lookup( name ); - if ( id && !id.statement) - console.log(id) - if ( id && id.statement ) { // Assert that statement is defined. It isn't for external modules. id.statement.mark(); @@ -499,7 +490,6 @@ export default class Module { this.statements.forEach( statement => { if ( !statement.isIncluded ) { - console.log( 'removing definer of', keys( statement.defines ) ); magicString.remove( statement.start, statement.next ); return; } @@ -543,11 +533,9 @@ export default class Module { keys( statement.dependsOn ) .concat( keys( statement.defines ) ) .forEach( name => { - // console.log ( name, statement.node ); - // console.log( this.locals ); const bundleName = this.locals.lookup( name ).name; - if ( !~toExport.indexOf( bundleName ) ) { + if ( ~toExport.indexOf( bundleName ) ) { bundleExports[ name ] = replacements[ name ] = bundleName; } else if ( bundleName !== name ) { // TODO weird structure replacements[ name ] = bundleName; diff --git a/src/Statement.js b/src/Statement.js index 127f3e8..b89fb50 100644 --- a/src/Statement.js +++ b/src/Statement.js @@ -262,8 +262,6 @@ export default class Statement { if ( this.isIncluded ) return; // prevent infinite loops this.isIncluded = true; - console.log( 'marking definer of', keys( this.defines ) ); - // `export { name } from './other'` is a special case if ( this.isReexportDeclaration ) { const otherModule = this.module.getModule( this.node.source.value ); @@ -282,7 +280,6 @@ export default class Statement { } replaceIdentifiers ( magicString, names, bundleExports ) { - console.log( magicString.slice(this.start, this.end) ); const replacementStack = [ names ]; const nameList = keys( names ); @@ -312,10 +309,12 @@ export default class Statement { if ( node.type === 'VariableDeclaration' ) { // if this contains a single declarator, and it's one that // needs to be rewritten, we replace the whole lot - const name = node.declarations[0].id.name; + const id = node.declarations[0].id; + const name = id.name; + if ( node.declarations.length === 1 && bundleExports[ name ] ) { - magicString.overwrite( node.start, node.declarations[0].id.end, bundleExports[ name ], true ); - node.declarations[0].id._skip = true; + magicString.overwrite( node.start, id.end, bundleExports[ name ], true ); + id._skip = true; } // otherwise, we insert the `exports.foo = foo` after the declaration