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 => { keys( statement.modifies ).forEach( name => {
const definingStatement = module.definitions[ 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 module.exports.default && module.exports.default.identifier === name && module.exports.default
); );
@ -305,11 +305,25 @@ export default class Bundle {
varExports[ key ] = true; 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 // 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 = keys( this.entryModule.exports )
.concat( keys( this.entryModule.reexports ) )
.filter( key => !varExports[ key ] ); .filter( key => !varExports[ key ] );
@ -476,40 +490,50 @@ export default class Bundle {
// defined elsewhere // defined elsewhere
const otherModule = importDeclaration.module; const otherModule = importDeclaration.module;
const name = importDeclaration.name;
if ( otherModule.isExternal ) { if ( otherModule.isExternal ) {
if ( importDeclaration.name === 'default' ) { if ( name === 'default' ) {
return otherModule.needsNamed && !es6 ? return otherModule.needsNamed && !es6 ?
`${otherModule.name}__default` : `${otherModule.name}__default` :
otherModule.name; otherModule.name;
} }
if ( importDeclaration.name === '*' ) { if ( name === '*' ) {
return otherModule.name; return otherModule.name;
} }
return es6 ? return es6 ?
importDeclaration.name : name :
`${otherModule.name}.${importDeclaration.name}`; `${otherModule.name}.${name}`;
} }
if ( importDeclaration.name === '*' ) { if ( name === '*' ) {
return otherModule.replacements[ '*' ]; return otherModule.replacements[ '*' ];
} }
if ( importDeclaration.name === 'default' ) { if ( name === 'default' ) {
return otherModule.defaultName(); return otherModule.defaultName();
} }
const exportDeclaration = otherModule.exports[ importDeclaration.name ]; return this.traceExport( otherModule, name, es6 );
if ( exportDeclaration ) return this.trace( otherModule, exportDeclaration.localName ); }
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 ) { for ( let i = 0; i < module.exportDelegates.length; i += 1 ) {
const delegate = otherModule.exportDelegates[i]; const delegate = module.exportDelegates[i];
const delegateExportDeclaration = delegate.module.exports[ importDeclaration.name ]; const delegateExportDeclaration = delegate.module.exports[ name ];
if ( delegateExportDeclaration ) { 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 // imports and exports, indexed by ID
this.imports = blank(); this.imports = blank();
this.exports = blank(); this.exports = blank();
this.reexports = blank();
this.exportAlls = blank(); this.exportAlls = blank();
@ -74,10 +75,32 @@ export default class Module {
const node = statement.node; const node = statement.node;
const source = node.source && node.source.value; 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 function foo () {}
// export default foo; // export default foo;
// export default 42; // export default 42;
if ( node.type === 'ExportDefaultDeclaration' ) { else if ( node.type === 'ExportDefaultDeclaration' ) {
const isDeclaration = /Declaration$/.test( node.declaration.type ); const isDeclaration = /Declaration$/.test( node.declaration.type );
const isAnonymous = /(?:Class|Function)Expression$/.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 localName = specifier.local.name;
const exportedName = specifier.exported.name; const exportedName = specifier.exported.name;
// export { foo } from './foo';
if ( source ) {
this.imports[ localName ] = {
source,
localName: exportedName,
name: localName
};
}
this.exports[ exportedName ] = { this.exports[ exportedName ] = {
statement, statement,
localName, localName,
exportedName, exportedName
linkedImport: source ? this.imports[ localName ] : null
}; };
}); });
} }
@ -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 ) { addImport ( statement ) {
@ -215,6 +219,13 @@ export default class Module {
consolidateDependencies () { consolidateDependencies () {
let strongDependencies = blank(); 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 => { this.statements.forEach( statement => {
if ( statement.isImportDeclaration && !statement.node.specifiers.length && !statement.module.isExternal ) { if ( statement.isImportDeclaration && !statement.node.specifiers.length && !statement.module.isExternal ) {
// include module for its side-effects // include module for its side-effects
@ -224,18 +235,17 @@ export default class Module {
keys( statement.stronglyDependsOn ).forEach( name => { keys( statement.stronglyDependsOn ).forEach( name => {
if ( statement.defines[ name ] ) return; if ( statement.defines[ name ] ) return;
const exportAllDeclaration = this.exportAlls[ name ]; let module = this;
let reexportDeclaration;
if ( exportAllDeclaration && exportAllDeclaration.module && !exportAllDeclaration.module.isExternal ) { while ( module.reexports[ name ] ) {
strongDependencies[ exportAllDeclaration.module.id ] = exportAllDeclaration.module; reexportDeclaration = module.reexports[ name ];
return; module = reexportDeclaration.module;
name = reexportDeclaration.importedName;
} }
const importDeclaration = this.imports[ name ]; addDependency( strongDependencies, reexportDeclaration ) ||
addDependency( strongDependencies, this.exportAlls[ name ] ) ||
if ( importDeclaration && importDeclaration.module && !importDeclaration.module.isExternal ) { addDependency( strongDependencies, this.imports[ name ] );
strongDependencies[ importDeclaration.module.id ] = importDeclaration.module;
}
}); });
}); });
@ -245,11 +255,17 @@ export default class Module {
keys( statement.dependsOn ).forEach( name => { keys( statement.dependsOn ).forEach( name => {
if ( statement.defines[ name ] ) return; if ( statement.defines[ name ] ) return;
const importDeclaration = this.imports[ name ]; let module = this;
let reexportDeclaration;
if ( importDeclaration && importDeclaration.module && !importDeclaration.module.isExternal ) { while ( module.reexports[ name ] ) {
weakDependencies[ importDeclaration.module.id ] = importDeclaration.module; 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(); return module.markAllExportStatements();
} }
const exportDeclaration = module.exports[ importDeclaration.name ]; return module.markExport( importDeclaration.name, this );
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 );
}); });
} }
@ -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 ) { parse ( ast ) {
// The ast can be supplied programmatically (but usually won't be) // The ast can be supplied programmatically (but usually won't be)
if ( !ast ) { if ( !ast ) {

16
src/Statement.js

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

Loading…
Cancel
Save