From 00c70e35cb177b3856d1521f96dba7ed8274f2c9 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 17 Aug 2016 13:57:43 -0400 Subject: [PATCH 1/3] failing test for #860 --- test/function/export-as-default/_config.js | 6 ++++++ test/function/export-as-default/foo.js | 3 +++ test/function/export-as-default/main.js | 3 +++ 3 files changed, 12 insertions(+) create mode 100644 test/function/export-as-default/_config.js create mode 100644 test/function/export-as-default/foo.js create mode 100644 test/function/export-as-default/main.js diff --git a/test/function/export-as-default/_config.js b/test/function/export-as-default/_config.js new file mode 100644 index 0000000..0f4773a --- /dev/null +++ b/test/function/export-as-default/_config.js @@ -0,0 +1,6 @@ +var assert = require( 'assert' ); + +module.exports = { + solo: true, + description: 'export { foo as default } does not create a live binding' +}; diff --git a/test/function/export-as-default/foo.js b/test/function/export-as-default/foo.js new file mode 100644 index 0000000..2e5b275 --- /dev/null +++ b/test/function/export-as-default/foo.js @@ -0,0 +1,3 @@ +let foo = 1; +export { foo as default }; +foo = 2; diff --git a/test/function/export-as-default/main.js b/test/function/export-as-default/main.js new file mode 100644 index 0000000..e2b1718 --- /dev/null +++ b/test/function/export-as-default/main.js @@ -0,0 +1,3 @@ +import foo from './foo.js'; + +assert.equal( foo, 1 ); From e36a57527b490a748a6e13e7952c8a8d382fa774 Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Sun, 18 Sep 2016 17:45:09 -0400 Subject: [PATCH 2/3] prevent {export foo as default} from creating a live binding (#860) --- src/Module.js | 8 ++- src/ast/nodes/ExportNamedDeclaration.js | 67 ++++++++++++++++++++-- test/function/export-as-default/_config.js | 1 - 3 files changed, 68 insertions(+), 8 deletions(-) diff --git a/src/Module.js b/src/Module.js index 6e58131..3abdd4c 100644 --- a/src/Module.js +++ b/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` ); diff --git a/src/ast/nodes/ExportNamedDeclaration.js b/src/ast/nodes/ExportNamedDeclaration.js index b815dd6..3f9ba4e 100644 --- a/src/ast/nodes/ExportNamedDeclaration.js +++ b/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 ); } } } diff --git a/test/function/export-as-default/_config.js b/test/function/export-as-default/_config.js index 0f4773a..d5de425 100644 --- a/test/function/export-as-default/_config.js +++ b/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' }; From ef3c8d54522f477d729e383987a8d811e26471e8 Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Sun, 18 Sep 2016 17:47:58 -0400 Subject: [PATCH 3/3] let -> var --- test/function/export-as-default/foo.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/function/export-as-default/foo.js b/test/function/export-as-default/foo.js index 2e5b275..3c54230 100644 --- a/test/function/export-as-default/foo.js +++ b/test/function/export-as-default/foo.js @@ -1,3 +1,3 @@ -let foo = 1; +var foo = 1; export { foo as default }; foo = 2;