Browse Source

handle multiple exports of a single binding

contingency-plan
Rich Harris 9 years ago
parent
commit
975528d034
  1. 60
      src/Bundle.js
  2. 32
      src/Module.js
  3. 13
      src/Statement.js
  4. 16
      src/ast/Scope.js
  5. 10
      test/function/export-two-ways/_config.js
  6. 2
      test/function/export-two-ways/foo.js
  7. 5
      test/function/export-two-ways/main.js

60
src/Bundle.js

@ -290,35 +290,34 @@ export default class Bundle {
//
// This doesn't apply if the bundle is exported as ES6!
let allBundleExports = blank();
let varDeclarations = blank();
let varExports = blank();
let getterExports = [];
if ( format !== 'es6' && exportMode === 'named' ) {
keys( this.entryModule.exports ).forEach( key => {
const exportDeclaration = this.entryModule.exports[ key ];
const originalDeclaration = this.entryModule.findDeclaration( exportDeclaration.localName );
if ( originalDeclaration && originalDeclaration.type === 'VariableDeclaration' ) {
const canonicalName = this.trace( this.entryModule, exportDeclaration.localName, false );
allBundleExports[ canonicalName ] = `exports.${key}`;
varExports[ key ] = true;
}
this.orderedModules.forEach( module => {
keys( module.varDeclarations ).forEach( name => {
varDeclarations[ module.replacements[ name ] || name ] = true;
});
});
keys( this.entryModule.reexports ).forEach( key => {
const reexportDeclaration = this.entryModule.reexports[ key ];
if ( reexportDeclaration.module.isExternal ) return;
const originalDeclaration = reexportDeclaration.module.findDeclaration( reexportDeclaration.localName );
if ( originalDeclaration && originalDeclaration.type === 'VariableDeclaration' ) {
const canonicalName = this.trace( reexportDeclaration.module, reexportDeclaration.localName, false );
allBundleExports[ canonicalName ] = `exports.${key}`;
varExports[ key ] = true;
}
});
if ( format !== 'es6' && exportMode === 'named' ) {
keys( this.entryModule.exports )
.concat( keys( this.entryModule.reexports ) )
.forEach( name => {
const canonicalName = this.traceExport( this.entryModule, name );
if ( varDeclarations[ canonicalName ] ) {
varExports[ name ] = true;
// if the same binding is exported multiple ways, we need to
// use getters to keep all exports in sync
if ( allBundleExports[ canonicalName ] ) {
getterExports.push({ key: name, value: allBundleExports[ canonicalName ] });
} else {
allBundleExports[ canonicalName ] = `exports.${name}`;
}
}
});
}
// since we're rewriting variable exports, we want to
@ -327,7 +326,6 @@ export default class Bundle {
.concat( keys( this.entryModule.reexports ) )
.filter( key => !varExports[ key ] );
let magicString = new MagicString.Bundle({ separator: '\n\n' });
this.orderedModules.forEach( module => {
@ -370,6 +368,16 @@ export default class Bundle {
magicString.prepend( namespaceBlock );
if ( getterExports.length ) {
// TODO offer ES3-safe (but not spec-compliant) alternative?
const indent = magicString.getIndentString();
const getterExportsBlock = `Object.defineProperties(exports, {\n` +
getterExports.map( ({ key, value }) => indent + `${key}: { get: function () { return ${value}; } }` ).join( ',\n' ) +
`\n});`;
magicString.append( '\n\n' + getterExportsBlock );
}
const finalise = finalisers[ format ];
if ( !finalise ) {

32
src/Module.js

@ -65,6 +65,7 @@ export default class Module {
this.replacements = blank();
this.definitions = blank();
this.varDeclarations = blank();
this.definitionPromises = blank();
this.modifications = blank();
@ -200,6 +201,10 @@ export default class Module {
this.definitions[ name ] = statement;
});
keys( statement.declaresVar ).forEach( name => {
this.varDeclarations[ name ] = statement;
});
keys( statement.modifies ).forEach( name => {
( this.modifications[ name ] || ( this.modifications[ name ] = [] ) ).push( statement );
});
@ -303,33 +308,6 @@ export default class Module {
});
}
findDeclaration ( localName ) {
const importDeclaration = this.imports[ localName ];
// name was defined by another module
if ( importDeclaration ) {
const module = importDeclaration.module;
if ( module.isExternal ) return null;
if ( importDeclaration.name === '*' ) return null;
if ( importDeclaration.name === 'default' ) return null;
const exportDeclaration = module.exports[ importDeclaration.name ];
return module.findDeclaration( exportDeclaration.localName );
}
// name was defined by this module, if any
let i = this.statements.length;
while ( i-- ) {
const declaration = this.statements[i].scope.declarations[ localName ];
if ( declaration ) {
return declaration;
}
}
return null;
}
mark ( name ) {
// shortcut cycles
if ( this.definitionPromises[ name ] ) {

13
src/Statement.js

@ -18,6 +18,7 @@ export default class Statement {
this.scope = new Scope();
this.defines = blank();
this.declaresVar = blank();
this.modifies = blank();
this.dependsOn = blank();
this.stronglyDependsOn = blank();
@ -43,7 +44,7 @@ export default class Statement {
case 'FunctionDeclaration':
case 'ArrowFunctionExpression':
if ( node.type === 'FunctionDeclaration' ) {
scope.addDeclaration( node.id.name, node );
scope.addDeclaration( node.id.name, node, false );
}
newScope = new Scope({
@ -55,7 +56,7 @@ export default class Statement {
// named function expressions - the name is considered
// part of the function's scope
if ( node.type === 'FunctionExpression' && node.id ) {
newScope.addDeclaration( node.id.name, node );
newScope.addDeclaration( node.id.name, node, false );
}
break;
@ -81,12 +82,12 @@ export default class Statement {
case 'VariableDeclaration':
node.declarations.forEach( declarator => {
scope.addDeclaration( declarator.id.name, node );
scope.addDeclaration( declarator.id.name, node, true );
});
break;
case 'ClassDeclaration':
scope.addDeclaration( node.id.name, node );
scope.addDeclaration( node.id.name, node, false );
break;
}
@ -142,6 +143,10 @@ export default class Statement {
keys( scope.declarations ).forEach( name => {
this.defines[ name ] = true;
});
keys( scope.varDeclarations ).forEach( name => {
this.declaresVar[ name ] = true;
});
}
checkForReads ( scope, node, parent, strong ) {

16
src/ast/Scope.js

@ -12,6 +12,7 @@ export default class Scope {
this.parent = options.parent;
this.depth = this.parent ? this.parent.depth + 1 : 0;
this.declarations = blank();
this.varDeclarations = blank();
this.isBlockScope = !!options.block;
if ( options.params ) {
@ -21,25 +22,16 @@ export default class Scope {
}
}
// add ( name, isBlockDeclaration ) {
// if ( !isBlockDeclaration && this.isBlockScope ) {
// // it's a `var` or function declaration, and this
// // is a block scope, so we need to go up
// this.parent.add( name, isBlockDeclaration );
// } else {
// this.names.push( name );
// }
// }
addDeclaration ( name, declaration ) {
addDeclaration ( name, declaration, isVar ) {
const isBlockDeclaration = declaration.type === 'VariableDeclaration' && blockDeclarations[ declaration.kind ];
if ( !isBlockDeclaration && this.isBlockScope ) {
// it's a `var` or function declaration, and this
// is a block scope, so we need to go up
this.parent.addDeclaration( name, declaration );
this.parent.addDeclaration( name, declaration, isVar );
} else {
this.declarations[ name ] = declaration;
if ( isVar ) this.varDeclarations[ name ] = true;
}
}

10
test/function/export-two-ways/_config.js

@ -0,0 +1,10 @@
var assert = require( 'assert' );
module.exports = {
description: 'exports the same binding more than one way',
exports: function ( exports ) {
assert.equal( exports.a, 2 );
assert.equal( exports.b, 2 );
assert.equal( exports.c, 2 );
}
};

2
test/function/export-two-ways/foo.js

@ -0,0 +1,2 @@
export var foo = 1;
foo = 2;

5
test/function/export-two-ways/main.js

@ -0,0 +1,5 @@
import { foo } from './foo';
export { foo as a };
export { foo as b };
export { foo as c };
Loading…
Cancel
Save