Browse Source

prevent {export foo as default} from creating a live binding (#860)

legacy-quote-reserved-properties
Rich-Harris 8 years ago
parent
commit
e36a57527b
  1. 8
      src/Module.js
  2. 67
      src/ast/nodes/ExportNamedDeclaration.js
  3. 1
      test/function/export-as-default/_config.js

8
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}')` );
}
this.exports[ exportedName ] = { localName };
// `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 );
}
}
}

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

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

Loading…
Cancel
Save