Browse Source

Merge pull request #864 from rollup/gh-860

Prevent `export {foo as default}` from creating a live binding
legacy-quote-reserved-properties
Rich Harris 8 years ago
committed by GitHub
parent
commit
cfbf78795e
  1. 6
      src/Module.js
  2. 67
      src/ast/nodes/ExportNamedDeclaration.js
  3. 5
      test/function/export-as-default/_config.js
  4. 3
      test/function/export-as-default/foo.js
  5. 3
      test/function/export-as-default/main.js

6
src/Module.js

@ -170,7 +170,13 @@ export default class Module {
throw new Error( `A module cannot have multiple exports with the same name ('${exportedName}')` );
}
// `export { default as foo }` – special case. We want importers
// to use the UnboundDefaultExport proxy, not the original declaration
if ( exportedName === 'default' ) {
this.exports[ exportedName ] = { localName: 'default' };
} else {
this.exports[ exportedName ] = { localName };
}
});
} else {
this.bundle.onwarn( `Module ${this.id} has an empty export declaration` );

67
src/ast/nodes/ExportNamedDeclaration.js

@ -1,17 +1,59 @@
import { find } from '../../utils/array.js';
import Node from '../Node.js';
class UnboundDefaultExport {
constructor ( original ) {
this.original = original;
this.name = original.name;
}
activate () {
if ( this.activated ) return;
this.activated = true;
this.original.activate();
}
addReference ( reference ) {
this.name = reference.name;
this.original.addReference( reference );
}
bind ( scope ) {
this.original.bind( scope );
}
gatherPossibleValues ( values ) {
this.original.gatherPossibleValues( values );
}
getName ( es ) {
if ( this.original && !this.original.isReassigned ) {
return this.original.getName( es );
}
return this.name;
}
}
export default class ExportNamedDeclaration extends Node {
initialise ( scope ) {
this.scope = scope;
this.isExportDeclaration = true;
if ( this.declaration ) {
this.declaration.initialise( scope );
// special case – `export { foo as default }` should not create a live binding
const defaultExport = find( this.specifiers, specifier => specifier.exported.name === 'default' );
if ( defaultExport ) {
const declaration = this.scope.findDeclaration( defaultExport.local.name );
this.defaultExport = new UnboundDefaultExport( declaration );
scope.declarations.default = this.defaultExport;
}
if ( this.declaration ) this.declaration.initialise( scope );
}
bind ( scope ) {
if ( this.declaration ) {
this.declaration.bind( scope );
}
if ( this.declaration ) this.declaration.bind( scope );
}
render ( code, es ) {
@ -19,7 +61,20 @@ export default class ExportNamedDeclaration extends Node {
code.remove( this.start, this.declaration.start );
this.declaration.render( code, es );
} else {
code.remove( this.leadingCommentStart || this.start, this.next || this.end );
const start = this.leadingCommentStart || this.start;
const end = this.next || this.end;
if ( this.defaultExport ) {
const name = this.defaultExport.getName( es );
const originalName = this.defaultExport.original.getName( es );
if ( name !== originalName ) {
code.overwrite( start, end, `var ${name} = ${originalName};` );
return;
}
}
code.remove( start, end );
}
}
}

5
test/function/export-as-default/_config.js

@ -0,0 +1,5 @@
var assert = require( 'assert' );
module.exports = {
description: 'export { foo as default } does not create a live binding'
};

3
test/function/export-as-default/foo.js

@ -0,0 +1,3 @@
var foo = 1;
export { foo as default };
foo = 2;

3
test/function/export-as-default/main.js

@ -0,0 +1,3 @@
import foo from './foo.js';
assert.equal( foo, 1 );
Loading…
Cancel
Save