Browse Source

load all dependencies, regardless of actual usage

contingency-plan
Rich-Harris 9 years ago
parent
commit
ff27523d44
  1. 66
      src/Bundle.js
  2. 115
      src/Module.js
  3. 36
      src/Statement.js
  4. 2
      test/function/tracks-alias-mutations/_config.js

66
src/Bundle.js

@ -32,7 +32,8 @@ export default class Bundle {
this.toExport = null;
this.modulePromises = blank();
this.pending = blank();
this.moduleById = blank();
this.modules = [];
this.statements = null;
@ -44,8 +45,11 @@ export default class Bundle {
}
build () {
return this.fetchModule( this.entry, undefined )
return Promise.resolve( this.resolveId( this.entry, undefined, this.resolveOptions ) )
.then( id => this.fetchModule( id ) )
.then( entryModule => {
entryModule.bindImportSpecifiers();
const defaultExport = entryModule.exports.default;
this.entryModule = entryModule;
@ -169,26 +173,12 @@ export default class Bundle {
return allReplacements;
}
fetchModule ( importee, importer ) {
return Promise.resolve( this.resolveId( importee, importer, this.resolveOptions ) )
.then( id => {
if ( !id ) {
// external module
if ( !this.modulePromises[ importee ] ) {
const module = new ExternalModule( importee );
this.externalModules.push( module );
this.modulePromises[ importee ] = Promise.resolve( module );
}
return this.modulePromises[ importee ];
}
fetchModule ( id ) {
// short-circuit cycles
if ( this.pending[ id ] ) return null;
this.pending[ id ] = true;
if ( id === importer ) {
throw new Error( `A module cannot import itself (${id})` );
}
if ( !this.modulePromises[ id ] ) {
this.modulePromises[ id ] = Promise.resolve( this.load( id, this.loadOptions ) )
return Promise.resolve( this.load( id, this.loadOptions ) )
.then( source => {
let ast;
@ -205,12 +195,42 @@ export default class Bundle {
});
this.modules.push( module );
this.moduleById[ id ] = module;
const promises = this.fetchAllModules( module, module.imports )
.concat( this.fetchAllModules( module, module.reexports ) );
return module;
return Promise.all( promises ).then( () => module );
});
}
return this.modulePromises[ id ];
fetchAllModules ( module, specifiers ) {
return keys( specifiers )
.map( name => {
const specifier = specifiers[ name ];
return Promise.resolve( this.resolveId( specifier.source, module.id, this.resolveOptions ) )
.then( resolvedId => {
// external module
if ( !resolvedId ) {
if ( !this.moduleById[ specifier.source ] ) {
const module = new ExternalModule( specifier.source );
this.externalModules.push( module );
this.moduleById[ specifier.source ] = module;
}
specifier.module = this.moduleById[ specifier.source ];
}
else if ( resolvedId === module.id ) {
throw new Error( `A module cannot import itself (${resolvedId})` );
}
else {
specifier.id = resolvedId;
return this.fetchModule( resolvedId );
}
});
});
}

115
src/Module.js

@ -66,6 +66,7 @@ export default class Module {
this.varDeclarations = [];
this.marked = blank();
this.definitions = blank();
this.definitionPromises = blank();
this.modifications = blank();
@ -224,6 +225,19 @@ export default class Module {
});
}
bindImportSpecifiers () {
[ this.imports, this.reexports ].forEach( specifiers => {
keys( specifiers ).forEach( name => {
const specifier = specifiers[ name ];
if ( specifier.module ) return;
specifier.module = this.bundle.moduleById[ specifier.id ];
specifier.module.bindImportSpecifiers();
});
});
}
consolidateDependencies () {
let strongDependencies = blank();
@ -302,29 +316,20 @@ export default class Module {
const importDeclaration = this.imports[ name ];
if ( !importDeclaration ) return null;
return Promise.resolve( importDeclaration.module || this.bundle.fetchModule( importDeclaration.source, this.id ) )
.then( module => {
importDeclaration.module = module;
return module.findDefiningStatement( name );
});
return importDeclaration.module.findDefiningStatement( name );
}
mark ( name ) {
// shortcut cycles
if ( this.definitionPromises[ name ] ) {
return emptyPromise;
}
let promise;
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;
promise = this.bundle.fetchModule( importDeclaration.source, this.id )
.then( module => {
importDeclaration.module = module;
const module = importDeclaration.module;
// suggest names. TODO should this apply to non default/* imports?
if ( importDeclaration.name === 'default' ) {
@ -355,86 +360,76 @@ export default class Module {
if ( module.isExternal ) {
module.importedByBundle.push( importDeclaration );
return emptyPromise;
}
if ( importDeclaration.name === '*' ) {
else if ( importDeclaration.name === '*' ) {
// we need to create an internal namespace
if ( !~this.bundle.internalNamespaceModules.indexOf( module ) ) {
this.bundle.internalNamespaceModules.push( module );
}
return module.markAllExportStatements();
module.markAllExportStatements();
}
return module.markExport( importDeclaration.name, name, this );
});
else {
module.markExport( importDeclaration.name, name, this );
}
}
else {
const statement = name === 'default' ? this.exports.default.statement : this.definitions[ name ];
promise = statement && statement.mark();
if ( statement ) statement.mark();
}
this.definitionPromises[ name ] = promise || emptyPromise;
return this.definitionPromises[ name ];
}
markAllStatements ( isEntryModule ) {
return sequence( this.statements, statement => {
this.statements.forEach( statement => {
if ( statement.isIncluded ) return; // TODO can this happen? probably not...
// skip import declarations...
if ( statement.isImportDeclaration ) {
// ...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
if ( !statement.node.specifiers.length ) {
return this.bundle.fetchModule( statement.node.source.value, this.id )
.then( module => {
statement.module = module;
if ( module.isExternal ) {
return;
}
return module.markAllStatements();
});
}
return;
// TODO...
// if ( !statement.node.specifiers.length ) {
// return this.bundle.fetchModule( statement.node.source.value, this.id )
// .then( module => {
// statement.module = module;
// if ( module.isExternal ) {
// return;
// }
// return module.markAllStatements();
// });
// }
//
// return;
}
// skip `export { foo, bar, baz }`...
if ( statement.node.type === 'ExportNamedDeclaration' && statement.node.specifiers.length ) {
else if ( statement.node.type === 'ExportNamedDeclaration' && statement.node.specifiers.length ) {
// ...but ensure they are defined, if this is the entry module
if ( isEntryModule ) {
return statement.mark();
}
return;
if ( isEntryModule ) statement.mark();
}
// include everything else
return statement.mark();
else {
statement.mark();
}
});
}
markAllExportStatements () {
return sequence( this.statements, statement => {
return statement.isExportDeclaration ?
statement.mark() :
null;
this.statements.forEach( statement => {
if ( statement.isExportDeclaration ) statement.mark();
});
}
markExport ( name, suggestedName, importer ) {
const reexportDeclaration = this.reexports[ name ];
if ( reexportDeclaration ) {
reexportDeclaration.isUsed = true;
return this.bundle.fetchModule( reexportDeclaration.source, this.id )
.then( otherModule => {
reexportDeclaration.module = otherModule;
return otherModule.markExport( reexportDeclaration.localName, suggestedName, this );
});
const reexport = this.reexports[ name ];
if ( reexport ) {
reexport.isUsed = true;
reexport.module.markExport( reexport.localName, suggestedName, this );
}
const exportDeclaration = this.exports[ name ];
@ -452,11 +447,11 @@ export default class Module {
const noExport = new Error( `Module ${this.id} does not export ${name} (imported by ${importer.id})` );
// See if there exists an export delegate that defines `name`.
return first( this.exportDelegates, noExport, declaration => {
return this.bundle.fetchModule( declaration.source, this.id ).then( submodule => {
declaration.module = submodule;
for ( i = 0; i < this.exportDelegates.length; i += 1 ) {
const declaration = this.exportDelegates[i];
const result = declaration.module.mark( name );
return submodule.mark( name ).then( result => {
if ( !result.length ) throw noExport;
// It's found! This module exports `name` through declaration.
@ -467,9 +462,7 @@ export default class Module {
declaration.statement.stronglyDependsOn[ name ] = result;
return result;
});
});
});
}
}
parse ( ast ) {

36
src/Statement.js

@ -263,27 +263,25 @@ export default class Statement {
this.isIncluded = true;
// `export { name } from './other'` is a special case
if ( this.isReexportDeclaration ) {
return this.module.bundle.fetchModule( this.node.source.value, this.module.id )
.then( otherModule => {
return sequence( this.node.specifiers, specifier => {
const reexport = this.module.reexports[ specifier.exported.name ];
reexport.isUsed = true;
reexport.module = otherModule;
return otherModule.isExternal ?
null :
otherModule.markExport( specifier.local.name, specifier.exported.name, this.module );
});
});
}
const dependencies = Object.keys( this.dependsOn );
// if ( this.isReexportDeclaration ) {
// return this.module.bundle.fetchModule( this.node.source.value, this.module.id )
// .then( otherModule => {
// return sequence( this.node.specifiers, specifier => {
// const reexport = this.module.reexports[ specifier.exported.name ];
//
// reexport.isUsed = true;
// reexport.module = otherModule;
//
// return otherModule.isExternal ?
// null :
// otherModule.markExport( specifier.local.name, specifier.exported.name, this.module );
// });
// });
// }
return sequence( dependencies, name => {
Object.keys( this.dependsOn ).forEach( name => {
if ( this.defines[ name ] ) return; // TODO maybe exclude from `this.dependsOn` in the first place?
return this.module.mark( name );
this.module.mark( name );
});
}

2
test/function/tracks-alias-mutations/_config.js

@ -1,4 +1,4 @@
module.exports = {
description: 'tracks mutations of aliased objects',
solo: true
// solo: true
};

Loading…
Cancel
Save