Browse Source

first stab at handling reexports separately

contingency-plan
Rich Harris 10 years ago
parent
commit
ae084c0424
  1. 50
      src/Bundle.js
  2. 165
      src/Module.js
  3. 16
      src/Statement.js
  4. 2
      src/utils/getExportMode.js

50
src/Bundle.js

@ -224,7 +224,7 @@ export default class Bundle {
keys( statement.modifies ).forEach( name => {
const definingStatement = module.definitions[ name ];
const exportDeclaration = module.exports[ name ] || (
const exportDeclaration = module.exports[ name ] || module.reexports[ name ] || (
module.exports.default && module.exports.default.identifier === name && module.exports.default
);
@ -305,11 +305,25 @@ export default class Bundle {
varExports[ key ] = true;
}
});
keys( this.entryModule.reexports ).forEach( key => {
const reexportDeclaration = this.entryModule.reexports[ key ];
const originalDeclaration = reexportDeclaration.module.findDeclaration( reexportDeclaration.importedName );
if ( originalDeclaration && originalDeclaration.type === 'VariableDeclaration' ) {
const canonicalName = this.trace( reexportDeclaration.module, reexportDeclaration.importedName, false );
allBundleExports[ canonicalName ] = `exports.${key}`;
varExports[ key ] = true;
}
});
}
// since we're rewriting variable exports, we want to
// ensure we don't try and export them again at the bottom
this.toExport = keys( this.entryModule.exports )
.concat( keys( this.entryModule.reexports ) )
.filter( key => !varExports[ key ] );
@ -476,40 +490,50 @@ export default class Bundle {
// defined elsewhere
const otherModule = importDeclaration.module;
const name = importDeclaration.name;
if ( otherModule.isExternal ) {
if ( importDeclaration.name === 'default' ) {
if ( name === 'default' ) {
return otherModule.needsNamed && !es6 ?
`${otherModule.name}__default` :
otherModule.name;
}
if ( importDeclaration.name === '*' ) {
if ( name === '*' ) {
return otherModule.name;
}
return es6 ?
importDeclaration.name :
`${otherModule.name}.${importDeclaration.name}`;
name :
`${otherModule.name}.${name}`;
}
if ( importDeclaration.name === '*' ) {
if ( name === '*' ) {
return otherModule.replacements[ '*' ];
}
if ( importDeclaration.name === 'default' ) {
if ( name === 'default' ) {
return otherModule.defaultName();
}
const exportDeclaration = otherModule.exports[ importDeclaration.name ];
if ( exportDeclaration ) return this.trace( otherModule, exportDeclaration.localName );
return this.traceExport( otherModule, name, es6 );
}
traceExport ( module, name, es6 ) {
const reexportDeclaration = module.reexports[ name ];
if ( reexportDeclaration ) {
return this.traceExport( reexportDeclaration.module, reexportDeclaration.importedName );
}
const exportDeclaration = module.exports[ name ];
if ( exportDeclaration ) return this.trace( module, exportDeclaration.localName );
for ( let i = 0; i < otherModule.exportDelegates.length; i += 1 ) {
const delegate = otherModule.exportDelegates[i];
const delegateExportDeclaration = delegate.module.exports[ importDeclaration.name ];
for ( let i = 0; i < module.exportDelegates.length; i += 1 ) {
const delegate = module.exportDelegates[i];
const delegateExportDeclaration = delegate.module.exports[ name ];
if ( delegateExportDeclaration ) {
return this.trace( delegate.module, delegateExportDeclaration.localName );
return this.trace( delegate.module, delegateExportDeclaration.localName, es6 );
}
}

165
src/Module.js

@ -55,6 +55,7 @@ export default class Module {
// imports and exports, indexed by ID
this.imports = blank();
this.exports = blank();
this.reexports = blank();
this.exportAlls = blank();
@ -74,10 +75,32 @@ export default class Module {
const node = statement.node;
const source = node.source && node.source.value;
// export { name } from './other'
if ( source ) {
if ( node.type === 'ExportAllDeclaration' ) {
// Store `export * from '...'` statements in an array of delegates.
// When an unknown import is encountered, we see if one of them can satisfy it.
this.exportDelegates.push({
statement,
source
});
}
else {
node.specifiers.forEach( specifier => {
this.reexports[ specifier.exported.name ] = {
source,
importedName: specifier.local.name,
module: null // filled in later
};
});
}
}
// export default function foo () {}
// export default foo;
// export default 42;
if ( node.type === 'ExportDefaultDeclaration' ) {
else if ( node.type === 'ExportDefaultDeclaration' ) {
const isDeclaration = /Declaration$/.test( node.declaration.type );
const isAnonymous = /(?:Class|Function)Expression$/.test( node.declaration.type );
@ -108,20 +131,10 @@ export default class Module {
const localName = specifier.local.name;
const exportedName = specifier.exported.name;
// export { foo } from './foo';
if ( source ) {
this.imports[ localName ] = {
source,
localName: exportedName,
name: localName
};
}
this.exports[ exportedName ] = {
statement,
localName,
exportedName,
linkedImport: source ? this.imports[ localName ] : null
exportedName
};
});
}
@ -146,15 +159,6 @@ export default class Module {
};
}
}
// Store `export * from '...'` statements in an array of delegates.
// When an unknown import is encountered, we see if one of them can satisfy it.
else {
this.exportDelegates.push({
statement,
source
});
}
}
addImport ( statement ) {
@ -215,6 +219,13 @@ export default class Module {
consolidateDependencies () {
let strongDependencies = blank();
function addDependency ( dependencies, declaration ) {
if ( declaration && declaration.module && !declaration.module.isExternal ) {
dependencies[ declaration.module.id ] = declaration.module;
return true;
}
}
this.statements.forEach( statement => {
if ( statement.isImportDeclaration && !statement.node.specifiers.length && !statement.module.isExternal ) {
// include module for its side-effects
@ -224,18 +235,17 @@ export default class Module {
keys( statement.stronglyDependsOn ).forEach( name => {
if ( statement.defines[ name ] ) return;
const exportAllDeclaration = this.exportAlls[ name ];
if ( exportAllDeclaration && exportAllDeclaration.module && !exportAllDeclaration.module.isExternal ) {
strongDependencies[ exportAllDeclaration.module.id ] = exportAllDeclaration.module;
return;
let module = this;
let reexportDeclaration;
while ( module.reexports[ name ] ) {
reexportDeclaration = module.reexports[ name ];
module = reexportDeclaration.module;
name = reexportDeclaration.importedName;
}
const importDeclaration = this.imports[ name ];
if ( importDeclaration && importDeclaration.module && !importDeclaration.module.isExternal ) {
strongDependencies[ importDeclaration.module.id ] = importDeclaration.module;
}
addDependency( strongDependencies, reexportDeclaration ) ||
addDependency( strongDependencies, this.exportAlls[ name ] ) ||
addDependency( strongDependencies, this.imports[ name ] );
});
});
@ -245,11 +255,17 @@ export default class Module {
keys( statement.dependsOn ).forEach( name => {
if ( statement.defines[ name ] ) return;
const importDeclaration = this.imports[ name ];
if ( importDeclaration && importDeclaration.module && !importDeclaration.module.isExternal ) {
weakDependencies[ importDeclaration.module.id ] = importDeclaration.module;
let module = this;
let reexportDeclaration;
while ( module.reexports[ name ] ) {
reexportDeclaration = module.reexports[ name ];
module = reexportDeclaration.module;
name = reexportDeclaration.importedName;
}
addDependency( weakDependencies, reexportDeclaration ) ||
addDependency( weakDependencies, this.exportAlls[ name ] ) ||
addDependency( weakDependencies, this.imports[ name ] );
});
});
@ -368,39 +384,7 @@ export default class Module {
return module.markAllExportStatements();
}
const exportDeclaration = module.exports[ importDeclaration.name ];
if ( !exportDeclaration ) {
const noExport = new Error( `Module ${module.id} does not export ${importDeclaration.name} (imported by ${this.id})` );
// See if there exists an export delegate that defines `name`.
return first( module.exportDelegates, noExport, declaration => {
return module.bundle.fetchModule( declaration.source, module.id ).then( submodule => {
declaration.module = submodule;
return submodule.mark( name ).then( result => {
if ( !result.length ) throw noExport;
// It's found! This module exports `name` through declaration.
// It is however not imported into this scope.
module.exportAlls[ name ] = declaration;
declaration.statement.dependsOn[ name ] =
declaration.statement.stronglyDependsOn[ name ] = result;
return result;
});
});
});
}
exportDeclaration.isUsed = true;
if ( importDeclaration.name === 'default' ) {
return exportDeclaration.statement.mark();
}
return module.mark( exportDeclaration.localName );
return module.markExport( importDeclaration.name, this );
});
}
@ -458,6 +442,51 @@ export default class Module {
});
}
markExport ( name, 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.importedName );
});
}
const exportDeclaration = this.exports[ name ];
if ( exportDeclaration ) {
exportDeclaration.isUsed = true;
if ( name === 'default' ) {
return exportDeclaration.statement.mark();
}
return this.mark( exportDeclaration.localName );
}
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;
return submodule.mark( name ).then( result => {
if ( !result.length ) throw noExport;
// It's found! This module exports `name` through declaration.
// It is however not imported into this scope.
this.exportAlls[ name ] = declaration;
declaration.statement.dependsOn[ name ] =
declaration.statement.stronglyDependsOn[ name ] = result;
return result;
});
});
});
}
parse ( ast ) {
// The ast can be supplied programmatically (but usually won't be)
if ( !ast ) {

16
src/Statement.js

@ -26,6 +26,7 @@ export default class Statement {
this.isImportDeclaration = node.type === 'ImportDeclaration';
this.isExportDeclaration = /^Export/.test( node.type );
this.isReexportDeclaration = this.isExportDeclaration && !!node.source;
}
analyse () {
@ -235,6 +236,17 @@ export default class Statement {
if ( this.isIncluded ) return; // prevent infinite loops
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 => {
this.module.reexports[ specifier.exported.name ].module = otherModule;
return otherModule.markExport( specifier.local.name );
});
});
}
const dependencies = Object.keys( this.dependsOn );
return sequence( dependencies, name => {
@ -371,6 +383,10 @@ export default class Statement {
return magicString;
}
source () {
return this.module.source.slice( this.start, this.end );
}
toString () {
return this.module.magicString.slice( this.start, this.end );
}

2
src/utils/getExportMode.js

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

Loading…
Cancel
Save