Browse Source

start moving naming logic into bundle, rather than modules

contingency-plan
Rich-Harris 10 years ago
parent
commit
e36bf8dbd5
  1. 73
      src/Bundle.js
  2. 12
      src/ExternalModule.js
  3. 106
      src/Module.js
  4. 2
      src/finalisers/es6.js
  5. 5
      src/finalisers/shared/getExportBlock.js

73
src/Bundle.js

@ -87,12 +87,18 @@ export default class Bundle {
});
}
// TODO would be better to deconflict once, rather than per-render
deconflict ( es6 ) {
let definers = blank();
let conflicts = blank();
let allReplacements = blank();
// Assign names to external modules
this.externalModules.forEach( module => {
// while we're here...
allReplacements[ module.id ] = blank();
let name = makeLegalIdentifier( module.suggestedNames['*'] || module.suggestedNames.default || module.id );
while ( definers[ name ] ) {
@ -107,6 +113,9 @@ export default class Bundle {
// Discover conflicts (i.e. two statements in separate modules both define `foo`)
this.orderedModules.forEach( module => {
// while we're here...
allReplacements[ module.id ] = blank();
module.statements.forEach( statement => {
if ( !statement.isIncluded ) return;
@ -145,9 +154,61 @@ export default class Bundle {
modules.forEach( module => {
const replacement = getSafeName( name );
module.rename( name, replacement );
allReplacements[ module.id ][ name ] = replacement;
});
});
// Assign non-conflicting names to internal default/namespace export
this.orderedModules.forEach( module => {
if ( !module.needsDefault && !module.needsAll ) return;
if ( module.needsDefault ) {
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.declaredName && !defaultExport.isModified ) return; // TODO encapsulate check for whether we need synthetic default name
const defaultName = getSafeName( module.suggestedNames.default );
module.replacements.default = defaultName;
}
// TODO namespace
});
this.orderedModules.forEach( module => {
keys( module.imports ).forEach( localName => {
if ( !module.imports[ localName ].isUsed ) return;
const bundleName = trace( module, localName );
if ( bundleName !== localName ) {
allReplacements[ module.id ][ localName ] = bundleName;
}
});
});
function trace ( module, localName ) {
const importDeclaration = module.imports[ localName ];
// defined in this module
if ( !importDeclaration ) {
return module.replacements[ localName ] || localName;
}
// defined elsewhere
const otherModule = importDeclaration.module;
if ( otherModule.isExternal ) {
// TODO ES6
return `${otherModule.name}.${importDeclaration.name}`;
}
const exportDeclaration = otherModule.exports[ importDeclaration.name ];
return trace( otherModule, exportDeclaration.localName );
}
// TODO assign names to default/namespace exports, based on suggestions
// TODO trace bindings and rename within modules here (rather than later
// with getCanonicalName)
@ -160,6 +221,8 @@ export default class Bundle {
conflicts[ name ] = true;
return name;
}
return allReplacements;
}
fetchModule ( importee, importer ) {
@ -262,7 +325,7 @@ export default class Bundle {
render ( options = {} ) {
const format = options.format || 'es6';
this.deconflict( format === 'es6' );
const allReplacements = this.deconflict( format === 'es6' );
// If we have named exports from the bundle, and those exports
// are assigned to *within* the bundle, we may need to rewrite e.g.
@ -288,7 +351,7 @@ export default class Bundle {
const originalDeclaration = this.entryModule.findDeclaration( exportDeclaration.localName );
if ( originalDeclaration && originalDeclaration.type === 'VariableDeclaration' ) {
const canonicalName = this.entryModule.getCanonicalName( exportDeclaration.localName, false );
const canonicalName = this.entryModule.replacements[ exportDeclaration.localName ] || exportDeclaration.localName;
allBundleExports[ canonicalName ] = `exports.${key}`;
this.varExports[ key ] = true;
@ -305,7 +368,7 @@ export default class Bundle {
let magicString = new MagicString.Bundle({ separator: '\n\n' });
this.orderedModules.forEach( module => {
const source = module.render( allBundleExports, format );
const source = module.render( allBundleExports, allReplacements[ module.id ], format );
if ( source.toString().length ) {
magicString.addSource( source );
}
@ -316,10 +379,10 @@ export default class Bundle {
const namespaceBlock = this.internalNamespaceModules.map( module => {
const exportKeys = keys( module.exports );
return `var ${module.getCanonicalName('*', format === 'es6')} = {\n` +
return `var ${module.namespaceName} = {\n` +
exportKeys.map( key => {
const localName = module.exports[ key ].localName;
return `${indentString}get ${key} () { return ${module.getCanonicalName(localName, format === 'es6')}; }`;
return `${indentString}get ${key} () { return ${localName}; }`; // TODO...
}).join( ',\n' ) +
`\n};\n\n`;
}).join( '' );

12
src/ExternalModule.js

@ -26,18 +26,6 @@ export default class ExternalModule {
return null;
}
getCanonicalName ( name, es6 ) {
if ( name === 'default' ) {
return this.needsNamed && !es6 ? `${this.name}__default` : this.name;
}
if ( name === '*' ) {
return this.name; // TODO is this correct in ES6?
}
return es6 ? ( this.canonicalNames[ name ] || name ) : `${this.name}.${name}`;
}
rename ( name, replacement ) {
this.canonicalNames[ name ] = replacement;
}

106
src/Module.js

@ -19,11 +19,11 @@ function deconflict ( name, names ) {
return name;
}
function isEmptyExportedVarDeclaration ( node, module, allBundleExports, es6 ) {
function isEmptyExportedVarDeclaration ( node, module, allBundleExports, moduleReplacements, es6 ) {
if ( node.type !== 'VariableDeclaration' || node.declarations[0].init ) return false;
const name = node.declarations[0].id.name;
const canonicalName = module.getCanonicalName( name, es6 );
const canonicalName = moduleReplacements[ name ];
return canonicalName in allBundleExports;
}
@ -62,7 +62,8 @@ export default class Module {
// array of all-export sources
this.exportDelegates = [];
this.canonicalNames = blank();
this.canonicalNames = blank(); // TODO still necessary?
this.replacements = blank();
this.definitions = blank();
this.definitionPromises = blank();
@ -293,58 +294,6 @@ export default class Module {
return null;
}
getCanonicalName ( localName, es6 ) {
// Special case
if ( localName === 'default' && ( this.exports.default.isModified || !this.suggestedNames.default ) ) {
let canonicalName = makeLegalIdentifier( this.id.replace( dirname( this.bundle.entryModule.id ) + '/', '' ).replace( /\.js$/, '' ) );
return deconflict( canonicalName, this.definitions );
}
if ( this.suggestedNames[ localName ] ) {
localName = this.suggestedNames[ localName ];
}
const id = localName + ( es6 ? '-es6' : '' ); // TODO ugh this seems like a terrible hack
if ( !this.canonicalNames[ id ] ) {
let canonicalName;
if ( this.imports[ localName ] ) {
const importDeclaration = this.imports[ localName ];
const module = importDeclaration.module;
if ( importDeclaration.name === '*' ) {
canonicalName = module.suggestedNames[ '*' ];
} else {
let exporterLocalName;
if ( module.isExternal ) {
exporterLocalName = importDeclaration.name;
} else {
const exportDeclaration = module.exports[ importDeclaration.name ];
// The export declaration of the particular name is known.
if (exportDeclaration) {
exporterLocalName = exportDeclaration.localName;
} else { // export * from '...'
exporterLocalName = importDeclaration.name;
}
}
canonicalName = module.getCanonicalName( exporterLocalName, es6 );
}
}
else {
canonicalName = localName;
}
this.canonicalNames[ id ] = canonicalName;
}
return this.canonicalNames[ id ];
}
mark ( name ) {
// shortcut cycles. TODO this won't work everywhere...
if ( this.definitionPromises[ name ] ) {
@ -356,6 +305,7 @@ export default class Module {
// 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 => {
@ -380,15 +330,15 @@ export default class Module {
module.suggestName( 'default', `${suggestion}__default` );
}
if ( module.isExternal ) {
if ( importDeclaration.name === 'default' ) {
module.needsDefault = true;
} else if ( importDeclaration.name === '*' ) {
module.needsAll = true;
} else {
module.needsNamed = true;
}
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 );
return emptyArrayPromise;
}
@ -601,11 +551,10 @@ export default class Module {
}
rename ( name, replacement ) {
// TODO again, hacky...
this.canonicalNames[ name ] = this.canonicalNames[ name + '-es6' ] = replacement;
this.replacements[ name ] = replacement;
}
render ( allBundleExports, format ) {
render ( allBundleExports, moduleReplacements, format ) {
let magicString = this.magicString.clone();
this.statements.forEach( statement => {
@ -623,7 +572,7 @@ export default class Module {
}
// skip `export var foo;` if foo is exported
if ( isEmptyExportedVarDeclaration( statement.node.declaration, this, allBundleExports, format === 'es6' ) ) {
if ( isEmptyExportedVarDeclaration( statement.node.declaration, this, allBundleExports, moduleReplacements, format === 'es6' ) ) {
magicString.remove( statement.start, statement.next );
return;
}
@ -631,7 +580,7 @@ export default class Module {
// skip empty var declarations for exported bindings
// (otherwise we're left with `exports.foo;`, which is useless)
if ( isEmptyExportedVarDeclaration( statement.node, this, allBundleExports, format === 'es6' ) ) {
if ( isEmptyExportedVarDeclaration( statement.node, this, allBundleExports, moduleReplacements, format === 'es6' ) ) {
magicString.remove( statement.start, statement.next );
return;
}
@ -652,12 +601,12 @@ export default class Module {
keys( statement.dependsOn )
.concat( keys( statement.defines ) )
.forEach( name => {
const canonicalName = this.getCanonicalName( name, format === 'es6' );
const bundleName = moduleReplacements[ name ] || name;
if ( allBundleExports[ canonicalName ] ) {
bundleExports[ name ] = replacements[ name ] = allBundleExports[ canonicalName ];
} else if ( name !== canonicalName ) {
replacements[ name ] = canonicalName;
if ( allBundleExports[ bundleName ] ) {
bundleExports[ name ] = replacements[ name ] = allBundleExports[ bundleName ];
} else if ( bundleName !== name ) { // TODO weird structure
replacements[ name ] = bundleName;
}
});
@ -677,9 +626,16 @@ export default class Module {
}
else if ( statement.node.type === 'ExportDefaultDeclaration' ) {
const canonicalName = this.getCanonicalName( 'default', format === 'es6' );
//const canonicalName = this.getCanonicalName( 'default', format === 'es6' );
let canonicalName;
if ( this.replacements.default ) {
canonicalName = this.replacements.default;
} else {
canonicalName = statement.node.declaration.name; // TODO declaredName?
canonicalName = this.replacements[ canonicalName ] || canonicalName;
}
if ( statement.node.declaration.type === 'Identifier' && canonicalName === this.getCanonicalName( statement.node.declaration.name, format === 'es6' ) ) {
if ( statement.node.declaration.type === 'Identifier' && canonicalName === ( moduleReplacements[ statement.node.declaration.name ] || statement.node.declaration.name ) ) {
magicString.remove( statement.start, statement.next );
return;
}

2
src/finalisers/es6.js

@ -37,7 +37,7 @@ export default function es6 ( bundle, magicString ) {
const exportBlock = keys( exports ).map( exportedName => {
const specifier = exports[ exportedName ];
const canonicalName = bundle.entryModule.getCanonicalName( specifier.localName );
const canonicalName = bundle.entryModule.replacements[ specifier.localName ] || specifier.localName;
if ( exportedName === 'default' ) {
return `export default ${canonicalName};`;

5
src/finalisers/shared/getExportBlock.js

@ -1,12 +1,13 @@
export default function getExportBlock ( bundle, exportMode, mechanism = 'return' ) {
if ( exportMode === 'default' ) {
return `${mechanism} ${bundle.entryModule.getCanonicalName('default')};`;
return `${mechanism} ${bundle.entryModule.replacements.default};`;
}
return bundle.toExport
.map( name => {
const prop = name === 'default' ? `['default']` : `.${name}`;
return `exports${prop} = ${bundle.entryModule.getCanonicalName(name)};`;
name = bundle.entryModule.replacements[ name ] || name;
return `exports${prop} = ${name};`;
})
.join( '\n' );
}

Loading…
Cancel
Save