Browse Source

Integrating Scope into Module. Removed ~200 lines.

gh-109
Oskar Segersvärd 10 years ago
parent
commit
f8da4fdb65
  1. 114
      src/Bundle.js
  2. 19
      src/ExternalModule.js
  3. 307
      src/Module.js
  4. 20
      src/Scope.js
  5. 21
      src/Statement.js
  6. 2
      src/utils/getExportMode.js

114
src/Bundle.js

@ -12,6 +12,7 @@ import { defaultLoader } from './utils/load';
import getExportMode from './utils/getExportMode'; import getExportMode from './utils/getExportMode';
import getIndentString from './utils/getIndentString'; import getIndentString from './utils/getIndentString';
import { unixizePath } from './utils/normalizePlatform.js'; import { unixizePath } from './utils/normalizePlatform.js';
import Scope from './Scope';
export default class Bundle { export default class Bundle {
constructor ( options ) { constructor ( options ) {
@ -30,6 +31,8 @@ export default class Bundle {
transform: ensureArray( options.transform ) transform: ensureArray( options.transform )
}; };
this.scope = new Scope();
this.toExport = null; this.toExport = null;
this.pending = blank(); this.pending = blank();
@ -48,8 +51,6 @@ export default class Bundle {
return Promise.resolve( this.resolveId( this.entry, undefined, this.resolveOptions ) ) return Promise.resolve( this.resolveId( this.entry, undefined, this.resolveOptions ) )
.then( id => this.fetchModule( id ) ) .then( id => this.fetchModule( id ) )
.then( entryModule => { .then( entryModule => {
entryModule.bindImportSpecifiers();
const defaultExport = entryModule.exports.default; const defaultExport = entryModule.exports.default;
this.entryModule = entryModule; this.entryModule = entryModule;
@ -85,88 +86,10 @@ export default class Bundle {
entryModule.markAllStatements( true ); entryModule.markAllStatements( true );
this.markAllModifierStatements(); this.markAllModifierStatements();
this.orderedModules = this.sort(); this.orderedModules = this.sort();
});
}
// TODO would be better to deconflict once, rather than per-render
deconflict ( es6 ) {
let usedNames = blank();
// ensure no conflicts with globals
keys( this.assumedGlobals ).forEach( name => usedNames[ name ] = true );
let allReplacements = blank();
// Assign names to external modules
this.externalModules.forEach( module => {
// while we're here...
allReplacements[ module.id ] = blank();
// TODO is this necessary in the ES6 case?
let name = makeLegalIdentifier( module.suggestedNames['*'] || module.suggestedNames.default || module.id );
module.name = getSafeName( name );
});
// Discover conflicts (i.e. two statements in separate modules both define `foo`)
let i = this.orderedModules.length;
while ( i-- ) {
const module = this.orderedModules[i];
// while we're here...
allReplacements[ module.id ] = blank();
keys( module.definitions ).forEach( name => {
const safeName = getSafeName( name );
if ( safeName !== name ) {
module.rename( name, safeName );
allReplacements[ module.id ][ name ] = safeName;
}
});
}
// Assign non-conflicting names to internal default/namespace export
this.orderedModules.forEach( module => {
if ( !module.needsDefault && !module.needsAll ) return;
if ( module.needsAll ) {
const namespaceName = getSafeName( module.suggestedNames[ '*' ] );
module.replacements[ '*' ] = namespaceName;
}
if ( module.needsDefault || module.needsAll && module.exports.default ) {
const defaultExport = module.exports.default;
// only create a new name if either
// a) it's an expression (`export default 42`) or
// b) it's a name that is reassigned to (`export var a = 1; a = 2`)
if ( defaultExport && defaultExport.identifier && !defaultExport.isModified ) return; // TODO encapsulate check for whether we need synthetic default name
const defaultName = getSafeName( module.suggestedNames.default ); // As a last step, deconflict all identifier names, once.
module.replacements.default = defaultName; this.scope.deconflict();
}
}); });
this.orderedModules.forEach( module => {
keys( module.imports ).forEach( localName => {
if ( !module.imports[ localName ].isUsed ) return;
const bundleName = this.trace( module, localName, es6 );
if ( bundleName !== localName ) {
allReplacements[ module.id ][ localName ] = bundleName;
}
});
});
function getSafeName ( name ) {
while ( usedNames[ name ] ) {
name = `_${name}`;
}
usedNames[ name ] = true;
return name;
}
return allReplacements;
} }
fetchModule ( id ) { fetchModule ( id ) {
@ -193,7 +116,13 @@ export default class Bundle {
this.modules.push( module ); this.modules.push( module );
this.moduleById[ id ] = module; this.moduleById[ id ] = module;
return this.fetchAllDependencies( module ).then( () => module ); return this.fetchAllDependencies( module ).then( () => {
// 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.
module.analyse();
return module;
});
}); });
} }
@ -206,7 +135,7 @@ export default class Bundle {
// external module // external module
if ( !resolvedId ) { if ( !resolvedId ) {
if ( !this.moduleById[ source ] ) { if ( !this.moduleById[ source ] ) {
const module = new ExternalModule( source ); const module = new ExternalModule( { id: source, bundle: this } );
this.externalModules.push( module ); this.externalModules.push( module );
this.moduleById[ source ] = module; this.moduleById[ source ] = module;
} }
@ -269,7 +198,7 @@ export default class Bundle {
render ( options = {} ) { render ( options = {} ) {
const format = options.format || 'es6'; const format = options.format || 'es6';
const allReplacements = this.deconflict( format === 'es6' ); const allReplacements = blank();
// Determine export mode - 'default', 'named', 'none' // Determine export mode - 'default', 'named', 'none'
const exportMode = getExportMode( this, options.exports ); const exportMode = getExportMode( this, options.exports );
@ -322,14 +251,12 @@ export default class Bundle {
// since we're rewriting variable exports, we want to // since we're rewriting variable exports, we want to
// ensure we don't try and export them again at the bottom // ensure we don't try and export them again at the bottom
this.toExport = keys( this.entryModule.exports ) this.toExport = this.entryModule.exports.usedNames();
.concat( keys( this.entryModule.reexports ) )
.filter( key => !varExports[ key ] );
let magicString = new MagicString.Bundle({ separator: '\n\n' }); let magicString = new MagicString.Bundle({ separator: '\n\n' });
this.orderedModules.forEach( module => { this.orderedModules.forEach( module => {
const source = module.render( allBundleExports, allReplacements[ module.id ], format ); const source = module.render( this.toExport, format );
if ( source.toString().length ) { if ( source.toString().length ) {
magicString.addSource( source ); magicString.addSource( source );
} }
@ -337,13 +264,10 @@ export default class Bundle {
// prepend bundle with internal namespaces // prepend bundle with internal namespaces
const indentString = getIndentString( magicString, options ); const indentString = getIndentString( magicString, options );
const namespaceBlock = this.internalNamespaceModules.map( module => { const namespaceBlock = this.internalNamespaceModules.map( module => {
const exports = keys( module.exports ) const exports = module.exports.localIds().map( ( [ name, id ] ) =>
.concat( keys( module.reexports ) ) `${indentString}get ${name} () { return ${id.name}; }`);
.map( name => {
const canonicalName = this.traceExport( module, name );
return `${indentString}get ${name} () { return ${canonicalName}; }`;
});
return `var ${module.replacements['*']} = {\n` + return `var ${module.replacements['*']} = {\n` +
exports.join( ',\n' ) + exports.join( ',\n' ) +

19
src/ExternalModule.js

@ -1,15 +1,12 @@
import { blank } from './utils/object';
export default class ExternalModule { export default class ExternalModule {
constructor ( id ) { constructor ( { id, bundle } ) {
this.id = id; this.id = id;
this.name = null; this.name = id;
this.isExternal = true; this.isExternal = true;
this.importedByBundle = []; this.importedByBundle = [];
this.suggestedNames = blank();
this.needsDefault = false; this.needsDefault = false;
// Invariant: needsNamed and needsAll are never both true at once. // Invariant: needsNamed and needsAll are never both true at once.
@ -19,19 +16,19 @@ export default class ExternalModule {
// //
this.needsNamed = false; this.needsNamed = false;
this.needsAll = false; this.needsAll = false;
this.exports = bundle.scope.virtual();
} }
findDefiningStatement () { findDefiningStatement () {
return null; return null;
} }
rename () {
// noop
}
suggestName ( exportName, suggestion ) { suggestName ( exportName, suggestion ) {
if ( !this.suggestedNames[ exportName ] ) { const id = this.exports.lookup( exportName );
this.suggestedNames[ exportName ] = suggestion;
if ( id.name === id.originalName ) {
id.name = suggestion;
} }
} }
} }

307
src/Module.js

@ -4,23 +4,15 @@ import Statement from './Statement';
import walk from './ast/walk'; import walk from './ast/walk';
import { blank, keys } from './utils/object'; import { blank, keys } from './utils/object';
import getLocation from './utils/getLocation'; import getLocation from './utils/getLocation';
import makeLegalIdentifier from './utils/makeLegalIdentifier';
function deconflict ( name, names ) { function isEmptyExportedVarDeclaration ( node, exports, toExport ) {
while ( name in names ) {
name = `_${name}`;
}
return name;
}
function isEmptyExportedVarDeclaration ( node, allBundleExports, moduleReplacements ) {
if ( node.type !== 'VariableDeclaration' || node.declarations[0].init ) return false; if ( node.type !== 'VariableDeclaration' || node.declarations[0].init ) return false;
const name = node.declarations[0].id.name; const name = node.declarations[0].id.name;
const canonicalName = moduleReplacements[ name ] || name;
return canonicalName in allBundleExports; const id = exports.lookup( name );
return !~toExport.indexOf( id.name );
} }
export default class Module { export default class Module {
@ -30,6 +22,9 @@ export default class Module {
this.bundle = bundle; this.bundle = bundle;
this.id = id; this.id = id;
// Implement Identifier interface.
this.name = id;
// By default, `id` is the filename. Custom resolvers and loaders // By default, `id` is the filename. Custom resolvers and loaders
// can change that, but it makes sense to use it for the source filename // can change that, but it makes sense to use it for the source filename
this.magicString = new MagicString( source, { this.magicString = new MagicString( source, {
@ -49,15 +44,14 @@ export default class Module {
this.statements = this.parse( ast ); this.statements = this.parse( ast );
// all dependencies // all dependencies
this.dependencies = [];
this.resolvedIds = blank(); this.resolvedIds = blank();
this.boundImportSpecifiers = false; this.boundImportSpecifiers = false;
// imports and exports, indexed by local name // imports and exports, indexed by local name
this.imports = blank(); this.imports = blank();
this.exports = blank();
this.reexports = blank(); this.locals = bundle.scope.virtual();
this.exportDelegates = blank(); this.exports = bundle.scope.virtual();
this.exportAlls = []; this.exportAlls = [];
@ -70,7 +64,7 @@ export default class Module {
this.definitionPromises = blank(); this.definitionPromises = blank();
this.modifications = blank(); this.modifications = blank();
this.analyse(); this.dependencies = this.collectDependencies();
} }
addExport ( statement ) { addExport ( statement ) {
@ -79,24 +73,19 @@ export default class Module {
// export { name } from './other' // export { name } from './other'
if ( source ) { if ( source ) {
if ( !~this.dependencies.indexOf( source ) ) this.dependencies.push( source ); const module = this.getModule( source );
if ( node.type === 'ExportAllDeclaration' ) { if ( node.type === 'ExportAllDeclaration' ) {
// Store `export * from '...'` statements in an array of delegates. // Store `export * from '...'` statements in an array of delegates.
// When an unknown import is encountered, we see if one of them can satisfy it. // When an unknown import is encountered, we see if one of them can satisfy it.
this.exportAlls.push({ this.exportAlls.push( module );
statement,
source
});
} }
else { else {
node.specifiers.forEach( specifier => { node.specifiers.forEach( specifier => {
this.reexports[ specifier.exported.name ] = { // Bind the export of this module, to the export of the other.
source, this.exports.bind( specifier.exported.name,
localName: specifier.local.name, module.exports.reference( specifier.local.name ) );
module: null // filled in later
};
}); });
} }
} }
@ -114,15 +103,23 @@ export default class Module {
node.declaration.name : node.declaration.name :
null; null;
this.exports.default = { if ( identifier ) {
statement, // If the default export has an identifier, bind to it.
this.exports.bind( 'default', this.locals.reference( identifier ) );
} else {
this.exports.define({
originalName: 'default',
name: 'default', name: 'default',
localName: identifier || 'default',
statement,
localName: 'default',
identifier, identifier,
isDeclaration, isDeclaration,
isAnonymous, isAnonymous,
isModified: false // in case of `export default foo; foo = somethingElse` isModified: false // in case of `export default foo; foo = somethingElse`
}; });
}
} }
// export { foo, bar, baz } // export { foo, bar, baz }
@ -135,11 +132,7 @@ export default class Module {
const localName = specifier.local.name; const localName = specifier.local.name;
const exportedName = specifier.exported.name; const exportedName = specifier.exported.name;
this.exports[ exportedName ] = { this.exports.bind( exportedName, this.locals.reference( localName ) );
statement,
localName,
exportedName
};
}); });
} }
@ -156,40 +149,44 @@ export default class Module {
name = declaration.id.name; name = declaration.id.name;
} }
this.exports[ name ] = { this.exports.bind({
originalName: name,
name,
statement, statement,
localName: name, localName: name,
expression: declaration expression: declaration
}; });
} }
} }
} }
addImport ( statement ) { addImport ( statement ) {
const node = statement.node; const node = statement.node;
const source = node.source.value; const module = this.getModule( node.source.value );
if ( !~this.dependencies.indexOf( source ) ) this.dependencies.push( source );
node.specifiers.forEach( specifier => { node.specifiers.forEach( specifier => {
const isDefault = specifier.type === 'ImportDefaultSpecifier'; const isDefault = specifier.type === 'ImportDefaultSpecifier';
const isNamespace = specifier.type === 'ImportNamespaceSpecifier'; const isNamespace = specifier.type === 'ImportNamespaceSpecifier';
const name = isDefault ? 'default' : specifier.imported.name;
const localName = specifier.local.name; const localName = specifier.local.name;
const name = isDefault ? 'default' : isNamespace ? '*' : specifier.imported.name;
if ( this.imports[ localName ] ) { // console.log( `import ${localName} as ${name} ...`);
if ( this.locals.defines( localName ) ) {
const err = new Error( `Duplicated import '${localName}'` ); const err = new Error( `Duplicated import '${localName}'` );
err.file = this.id; err.file = this.id;
err.loc = getLocation( this.source, specifier.start ); err.loc = getLocation( this.source, specifier.start );
throw err; throw err;
} }
this.imports[ localName ] = { if ( isNamespace ) {
source, // If it's a namespace import, we bind the localName to the module itself.
name, this.locals.bind( localName, module );
localName } else {
}; this.locals.bind( localName, module.exports.reference( name ) );
}
}); });
} }
@ -203,6 +200,15 @@ export default class Module {
// consolidate names that are defined/modified in this module // consolidate names that are defined/modified in this module
keys( statement.defines ).forEach( name => { keys( statement.defines ).forEach( name => {
this.locals.define({
originalName: name,
name,
statement,
module: this
});
// FIXME: remove?
this.definitions[ name ] = statement; this.definitions[ name ] = statement;
}); });
@ -243,32 +249,17 @@ export default class Module {
}); });
} }
bindImportSpecifiers () { // Returns the set of imported module ids by going through all import/exports statements.
if ( this.boundImportSpecifiers ) return; collectDependencies () {
this.boundImportSpecifiers = true; const importedModules = blank();
[ this.imports, this.reexports ].forEach( specifiers => {
keys( specifiers ).forEach( name => {
const specifier = specifiers[ name ];
if ( specifier.module ) return;
const id = this.resolvedIds[ specifier.source ];
specifier.module = this.bundle.moduleById[ id ];
});
});
this.exportAlls.forEach( delegate => { this.statements.forEach( statement => {
const id = this.resolvedIds[ delegate.source ]; if ( statement.isImportDeclaration || ( statement.isExportDeclaration && statement.node.source ) ) {
delegate.module = this.bundle.moduleById[ id ]; importedModules[ statement.node.source.value ] = true;
}
}); });
this.dependencies.forEach( source => { return keys( importedModules );
const id = this.resolvedIds[ source ];
const module = this.bundle.moduleById[ id ];
if ( !module.isExternal ) module.bindImportSpecifiers();
});
} }
consolidateDependencies () { consolidateDependencies () {
@ -284,8 +275,7 @@ export default class Module {
this.statements.forEach( statement => { this.statements.forEach( statement => {
if ( statement.isImportDeclaration && !statement.node.specifiers.length ) { if ( statement.isImportDeclaration && !statement.node.specifiers.length ) {
// include module for its side-effects // include module for its side-effects
const id = this.resolvedIds[ statement.node.source.value ]; const module = this.getModule( statement.node.source.value );
const module = this.bundle.moduleById[ id ];
if ( !module.isExternal ) strongDependencies[ module.id ] = module; if ( !module.isExternal ) strongDependencies[ module.id ] = module;
} }
@ -293,17 +283,11 @@ export default class Module {
else if ( statement.isReexportDeclaration ) { else if ( statement.isReexportDeclaration ) {
if ( statement.node.specifiers ) { if ( statement.node.specifiers ) {
statement.node.specifiers.forEach( specifier => { statement.node.specifiers.forEach( specifier => {
let reexport;
let module = this;
let name = specifier.exported.name; let name = specifier.exported.name;
while ( !module.isExternal && module.reexports[ name ] && module.reexports[ name ].isUsed ) {
reexport = module.reexports[ name ];
module = reexport.module;
name = reexport.localName;
}
addDependency( strongDependencies, reexport ); let id = this.locals.lookup( name );
addDependency( strongDependencies, id );
}); });
} }
} }
@ -312,8 +296,7 @@ export default class Module {
keys( statement.stronglyDependsOn ).forEach( name => { keys( statement.stronglyDependsOn ).forEach( name => {
if ( statement.defines[ name ] ) return; if ( statement.defines[ name ] ) return;
addDependency( strongDependencies, this.exportDelegates[ name ] ) || addDependency( strongDependencies, this.locals.lookup( name ) );
addDependency( strongDependencies, this.imports[ name ] );
}); });
} }
}); });
@ -324,8 +307,7 @@ export default class Module {
keys( statement.dependsOn ).forEach( name => { keys( statement.dependsOn ).forEach( name => {
if ( statement.defines[ name ] ) return; if ( statement.defines[ name ] ) return;
addDependency( weakDependencies, this.exportDelegates[ name ] ) || addDependency( weakDependencies, this.locals.lookup( name ) );
addDependency( weakDependencies, this.imports[ name ] );
}); });
}); });
@ -355,66 +337,19 @@ export default class Module {
return importDeclaration.module.findDefiningStatement( name ); return importDeclaration.module.findDefiningStatement( name );
} }
mark ( name ) { getModule ( source ) {
// shortcut cycles return this.bundle.moduleById[ this.resolvedIds[ source ] ];
if ( this.marked[ name ] ) return;
this.marked[ name ] = true;
// The definition for this name is in a different module
if ( this.imports[ name ] ) {
const importDeclaration = this.imports[ name ];
importDeclaration.isUsed = true;
const module = importDeclaration.module;
// suggest names. TODO should this apply to non default/* imports?
if ( importDeclaration.name === 'default' ) {
// TODO this seems ropey
const localName = importDeclaration.localName;
let suggestion = this.suggestedNames[ localName ] || localName;
// special case - the module has its own import by this name
while ( !module.isExternal && module.imports[ suggestion ] ) {
suggestion = `_${suggestion}`;
}
module.suggestName( 'default', suggestion );
} else if ( importDeclaration.name === '*' ) {
const localName = importDeclaration.localName;
const suggestion = this.suggestedNames[ localName ] || localName;
module.suggestName( '*', suggestion );
module.suggestName( 'default', `${suggestion}__default` );
}
if ( importDeclaration.name === 'default' ) {
module.needsDefault = true;
} else if ( importDeclaration.name === '*' ) {
module.needsAll = true;
} else {
module.needsNamed = true;
}
if ( module.isExternal ) {
module.importedByBundle.push( importDeclaration );
} }
else if ( importDeclaration.name === '*' ) { mark ( name ) {
// we need to create an internal namespace const id = this.locals.lookup( name );
if ( !~this.bundle.internalNamespaceModules.indexOf( module ) ) {
this.bundle.internalNamespaceModules.push( module );
}
module.markAllExportStatements(); if ( id && !id.statement)
} console.log(id)
else { if ( id && id.statement ) {
module.markExport( importDeclaration.name, name, this ); // Assert that statement is defined. It isn't for external modules.
} id.statement.mark();
}
else {
const statement = name === 'default' ? this.exports.default.statement : this.definitions[ name ];
if ( statement ) statement.mark();
} }
} }
@ -427,8 +362,7 @@ export default class Module {
// ...unless they're empty, in which case assume we're importing them for the side-effects // ...unless they're empty, in which case assume we're importing them for the side-effects
// THIS IS NOT FOOLPROOF. Probably need /*rollup: include */ or similar // THIS IS NOT FOOLPROOF. Probably need /*rollup: include */ or similar
if ( !statement.node.specifiers.length ) { if ( !statement.node.specifiers.length ) {
const id = this.resolvedIds[ statement.node.source.value ]; const otherModule = this.getModule( statement.node.source.value );
const otherModule = this.bundle.moduleById[ id ];
if ( !otherModule.isExternal ) otherModule.markAllStatements(); if ( !otherModule.isExternal ) otherModule.markAllStatements();
} }
@ -454,39 +388,21 @@ export default class Module {
} }
markExport ( name, suggestedName, importer ) { markExport ( name, suggestedName, importer ) {
const reexport = this.reexports[ name ]; const id = this.exports.lookup( name );
const exportDeclaration = this.exports[ name ];
if ( reexport ) { if ( id ) {
reexport.isUsed = true; // Assert that statement is defined. It isn't for external modules.
reexport.module.markExport( reexport.localName, suggestedName, this ); if ( id.statement ) id.statement.mark();
}
else if ( exportDeclaration ) {
exportDeclaration.isUsed = true;
if ( name === 'default' ) {
this.needsDefault = true;
this.suggestName( 'default', suggestedName );
return exportDeclaration.statement.mark();
}
this.mark( exportDeclaration.localName ); return;
} }
else { for ( const module of this.exportAlls ) {
// See if there exists an export delegate that defines `name`. const id = module.exports.lookup( name );
let i;
for ( i = 0; i < this.exportAlls.length; i += 1 ) {
const declaration = this.exportAlls[i];
if ( declaration.module.exports[ name ] ) {
// It's found! This module exports `name` through declaration.
// It is however not imported into this scope.
this.exportDelegates[ name ] = declaration;
declaration.module.markExport( name );
declaration.statement.dependsOn[ name ] = if ( id ) {
declaration.statement.stronglyDependsOn[ name ] = true; // Assert that statement is defined. It isn't for external modules.
if ( id.statement ) id.statement.mark();
return; return;
} }
@ -494,7 +410,6 @@ export default class Module {
throw new Error( `Module ${this.id} does not export ${name} (imported by ${importer.id})` ); throw new Error( `Module ${this.id} does not export ${name} (imported by ${importer.id})` );
} }
}
parse ( ast ) { parse ( ast ) {
// The ast can be supplied programmatically (but usually won't be) // The ast can be supplied programmatically (but usually won't be)
@ -579,15 +494,12 @@ export default class Module {
return statements; return statements;
} }
rename ( name, replacement ) { render ( toExport ) {
this.replacements[ name ] = replacement;
}
render ( allBundleExports, moduleReplacements ) {
let magicString = this.magicString.clone(); let magicString = this.magicString.clone();
this.statements.forEach( statement => { this.statements.forEach( statement => {
if ( !statement.isIncluded ) { if ( !statement.isIncluded ) {
console.log( 'removing definer of', keys( statement.defines ) );
magicString.remove( statement.start, statement.next ); magicString.remove( statement.start, statement.next );
return; return;
} }
@ -601,7 +513,7 @@ export default class Module {
} }
// skip `export var foo;` if foo is exported // skip `export var foo;` if foo is exported
if ( isEmptyExportedVarDeclaration( statement.node.declaration, allBundleExports, moduleReplacements ) ) { if ( isEmptyExportedVarDeclaration( statement.node.declaration, this.exports, toExport ) ) {
magicString.remove( statement.start, statement.next ); magicString.remove( statement.start, statement.next );
return; return;
} }
@ -609,7 +521,7 @@ export default class Module {
// skip empty var declarations for exported bindings // skip empty var declarations for exported bindings
// (otherwise we're left with `exports.foo;`, which is useless) // (otherwise we're left with `exports.foo;`, which is useless)
if ( isEmptyExportedVarDeclaration( statement.node, allBundleExports, moduleReplacements ) ) { if ( isEmptyExportedVarDeclaration( statement.node, this.exports, toExport ) ) {
magicString.remove( statement.start, statement.next ); magicString.remove( statement.start, statement.next );
return; return;
} }
@ -617,9 +529,10 @@ export default class Module {
// split up/remove var declarations as necessary // split up/remove var declarations as necessary
if ( statement.node.isSynthetic ) { if ( statement.node.isSynthetic ) {
// insert `var/let/const` if necessary // insert `var/let/const` if necessary
if ( !allBundleExports[ statement.node.declarations[0].id.name ] ) { // FIXME: !!
magicString.insert( statement.start, `${statement.node.kind} ` ); // if ( !allBundleExports[ statement.node.declarations[0].id.name ] ) {
} // magicString.insert( statement.start, `${statement.node.kind} ` );
// }
magicString.overwrite( statement.end, statement.next, ';\n' ); // TODO account for trailing newlines magicString.overwrite( statement.end, statement.next, ';\n' ); // TODO account for trailing newlines
} }
@ -630,10 +543,12 @@ export default class Module {
keys( statement.dependsOn ) keys( statement.dependsOn )
.concat( keys( statement.defines ) ) .concat( keys( statement.defines ) )
.forEach( name => { .forEach( name => {
const bundleName = moduleReplacements[ name ] || name; // console.log ( name, statement.node );
// console.log( this.locals );
const bundleName = this.locals.lookup( name ).name;
if ( allBundleExports[ bundleName ] ) { if ( !~toExport.indexOf( bundleName ) ) {
bundleExports[ name ] = replacements[ name ] = allBundleExports[ bundleName ]; bundleExports[ name ] = replacements[ name ] = bundleName;
} else if ( bundleName !== name ) { // TODO weird structure } else if ( bundleName !== name ) { // TODO weird structure
replacements[ name ] = bundleName; replacements[ name ] = bundleName;
} }
@ -657,10 +572,11 @@ export default class Module {
else if ( statement.node.type === 'ExportDefaultDeclaration' ) { else if ( statement.node.type === 'ExportDefaultDeclaration' ) {
const canonicalName = this.defaultName(); const canonicalName = this.defaultName();
if ( statement.node.declaration.type === 'Identifier' && canonicalName === ( moduleReplacements[ statement.node.declaration.name ] || statement.node.declaration.name ) ) { // FIXME: dunno what to do here yet.
magicString.remove( statement.start, statement.next ); // if ( statement.node.declaration.type === 'Identifier' && canonicalName === ( moduleReplacements[ statement.node.declaration.name ] || statement.node.declaration.name ) ) {
return; // magicString.remove( statement.start, statement.next );
} // return;
// }
// prevent `var undefined = sideEffectyDefault(foo)` // prevent `var undefined = sideEffectyDefault(foo)`
if ( canonicalName === undefined ) { if ( canonicalName === undefined ) {
@ -684,15 +600,4 @@ export default class Module {
return magicString.trim(); return magicString.trim();
} }
suggestName ( defaultOrBatch, suggestion ) {
// deconflict anonymous default exports with this module's definitions
const shouldDeconflict = this.exports.default && this.exports.default.isAnonymous;
if ( shouldDeconflict ) suggestion = deconflict( suggestion, this.definitions );
if ( !this.suggestedNames[ defaultOrBatch ] ) {
this.suggestedNames[ defaultOrBatch ] = makeLegalIdentifier( suggestion );
}
}
} }

20
src/Scope.js

@ -37,10 +37,6 @@ export default class Scope {
// Binds the `name` to the given reference `ref`. // Binds the `name` to the given reference `ref`.
bind ( name, ref ) { bind ( name, ref ) {
if ( isntReference( ref ) ) {
throw new TypeError( `` );
}
this.ids[ this.index( name ) ] = ref; this.ids[ this.index( name ) ] = ref;
} }
@ -71,6 +67,11 @@ export default class Scope {
} }
} }
// TODO: rename! Too similar to `define`.
defines ( name ) {
return name in this.names;
}
// !! private, don't use !! // !! private, don't use !!
// //
// Lookup the `ids` index of `name`. // Lookup the `ids` index of `name`.
@ -84,6 +85,11 @@ export default class Scope {
return this.names[ name ]; return this.names[ name ];
} }
// Returns a list of [ localName, identifier ] tuples.
localIds () {
return keys( this.names ).map( name => [ name, this.lookup( name ) ] );
}
// Lookup the identifier referred to by `name`. // Lookup the identifier referred to by `name`.
lookup ( name ) { lookup ( name ) {
let id = this.ids[ this.names[ name ] ]; let id = this.ids[ this.names[ name ] ];
@ -97,7 +103,11 @@ export default class Scope {
// Get a reference to the identifier `name` in this scope. // Get a reference to the identifier `name` in this scope.
reference ( name ) { reference ( name ) {
return new Reference( this, this.names[ name ] ); if ( !this.defines( name ) ) {
this.define( name );
}
return new Reference( this, this.index( name ) );
} }
// Return the names currently in use in the scope. // Return the names currently in use in the scope.

21
src/Statement.js

@ -189,6 +189,7 @@ export default class Statement {
// disallow assignments/updates to imported bindings and namespaces // disallow assignments/updates to imported bindings and namespaces
if ( isAssignment ) { if ( isAssignment ) {
// FIXME: imports is no longer used.
const importSpecifier = this.module.imports[ node.name ]; const importSpecifier = this.module.imports[ node.name ];
if ( importSpecifier && !scope.contains( node.name ) ) { if ( importSpecifier && !scope.contains( node.name ) ) {
@ -207,11 +208,12 @@ export default class Statement {
// special case = `export default foo; foo += 1;` - we'll // special case = `export default foo; foo += 1;` - we'll
// need to assign a new variable so that the exported // need to assign a new variable so that the exported
// value is not updated by the second statement // value is not updated by the second statement
if ( this.module.exports.default && depth === 0 && this.module.exports.default.identifier === node.name ) { const def = this.module.exports.lookup( 'default' );
if ( def && depth === 0 && def.identifier === node.name ) {
// but only if this is a) inside a function body or // but only if this is a) inside a function body or
// b) after the export declaration // b) after the export declaration
if ( !!scope.parent || node.start > this.module.exports.default.statement.node.start ) { if ( !!scope.parent || node.start > def.statement.node.start ) {
this.module.exports.default.isModified = true; def.isModified = true;
} }
} }
@ -260,18 +262,14 @@ export default class Statement {
if ( this.isIncluded ) return; // prevent infinite loops if ( this.isIncluded ) return; // prevent infinite loops
this.isIncluded = true; this.isIncluded = true;
console.log( 'marking definer of', keys( this.defines ) );
// `export { name } from './other'` is a special case // `export { name } from './other'` is a special case
if ( this.isReexportDeclaration ) { if ( this.isReexportDeclaration ) {
const id = this.module.resolvedIds[ this.node.source.value ]; const otherModule = this.module.getModule( this.node.source.value );
const otherModule = this.module.bundle.moduleById[ id ];
this.node.specifiers.forEach( specifier => { this.node.specifiers.forEach( specifier => {
const reexport = this.module.reexports[ specifier.exported.name ]; otherModule.exports.lookup( specifier.local.name ).statement.mark();
reexport.isUsed = true;
reexport.module = otherModule; // TODO still necessary?
if ( !otherModule.isExternal ) otherModule.markExport( specifier.local.name, specifier.exported.name, this.module );
}); });
return; return;
@ -284,6 +282,7 @@ export default class Statement {
} }
replaceIdentifiers ( magicString, names, bundleExports ) { replaceIdentifiers ( magicString, names, bundleExports ) {
console.log( magicString.slice(this.start, this.end) );
const replacementStack = [ names ]; const replacementStack = [ names ];
const nameList = keys( names ); const nameList = keys( names );

2
src/utils/getExportMode.js

@ -5,7 +5,7 @@ function badExports ( option, keys ) {
} }
export default function getExportMode ( bundle, exportMode ) { export default function getExportMode ( bundle, exportMode ) {
const exportKeys = keys( bundle.entryModule.exports ).concat( keys( bundle.entryModule.reexports ) ); const exportKeys = bundle.entryModule.exports.usedNames();
if ( exportMode === 'default' ) { if ( exportMode === 'default' ) {
if ( exportKeys.length !== 1 || exportKeys[0] !== 'default' ) { if ( exportKeys.length !== 1 || exportKeys[0] !== 'default' ) {

Loading…
Cancel
Save