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}')` ); 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 { } else {
this.bundle.onwarn( `Module ${this.id} has an empty export declaration` ); 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'; 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 { export default class ExportNamedDeclaration extends Node {
initialise ( scope ) { initialise ( scope ) {
this.scope = scope;
this.isExportDeclaration = true; 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 ) { bind ( scope ) {
if ( this.declaration ) { if ( this.declaration ) this.declaration.bind( scope );
this.declaration.bind( scope );
}
} }
render ( code, es ) { render ( code, es ) {
@ -19,7 +61,20 @@ export default class ExportNamedDeclaration extends Node {
code.remove( this.start, this.declaration.start ); code.remove( this.start, this.declaration.start );
this.declaration.render( code, es ); this.declaration.render( code, es );
} else { } 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' ); var assert = require( 'assert' );
module.exports = { module.exports = {
solo: true,
description: 'export { foo as default } does not create a live binding' description: 'export { foo as default } does not create a live binding'
}; };

Loading…
Cancel
Save