From e4fd1ef172afac994c0a7b20344d9f5a1989bfb1 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 17 Aug 2015 10:33:02 -0400 Subject: [PATCH 01/18] failing tests for #16 --- .../export-from-with-definition-conflict/_config.js | 12 ++++++++++++ .../export-from-with-definition-conflict/a.js | 3 +++ .../export-from-with-definition-conflict/main.js | 9 +++++++++ .../export-from-with-import-conflict/_config.js | 12 ++++++++++++ test/function/export-from-with-import-conflict/a.js | 3 +++ test/function/export-from-with-import-conflict/b.js | 2 ++ .../export-from-with-import-conflict/main.js | 8 ++++++++ 7 files changed, 49 insertions(+) create mode 100644 test/function/export-from-with-definition-conflict/_config.js create mode 100644 test/function/export-from-with-definition-conflict/a.js create mode 100644 test/function/export-from-with-definition-conflict/main.js create mode 100644 test/function/export-from-with-import-conflict/_config.js create mode 100644 test/function/export-from-with-import-conflict/a.js create mode 100644 test/function/export-from-with-import-conflict/b.js create mode 100644 test/function/export-from-with-import-conflict/main.js diff --git a/test/function/export-from-with-definition-conflict/_config.js b/test/function/export-from-with-definition-conflict/_config.js new file mode 100644 index 0000000..b9bc78a --- /dev/null +++ b/test/function/export-from-with-definition-conflict/_config.js @@ -0,0 +1,12 @@ +var assert = require( 'assert' ); + +module.exports = { + description: 'ignores conflict between local definitions and export from declaration', + exports: function ( exports ) { + assert.equal( exports.foo, 'a-bar' ); + assert.equal( exports.bar, 'a-foo' ); + assert.equal( exports.baz, 'a-baz' ); + } +}; + +// https://github.com/rollup/rollup/issues/16 diff --git a/test/function/export-from-with-definition-conflict/a.js b/test/function/export-from-with-definition-conflict/a.js new file mode 100644 index 0000000..c24adbf --- /dev/null +++ b/test/function/export-from-with-definition-conflict/a.js @@ -0,0 +1,3 @@ +export var foo = 'a-foo'; +export var bar = 'a-bar'; +export var baz = 'a-baz'; diff --git a/test/function/export-from-with-definition-conflict/main.js b/test/function/export-from-with-definition-conflict/main.js new file mode 100644 index 0000000..91eed71 --- /dev/null +++ b/test/function/export-from-with-definition-conflict/main.js @@ -0,0 +1,9 @@ +var foo = 'local-foo'; +var baz = 'local-baz'; + +export { foo as bar } from './a'; +export { bar as foo } from './a'; +export { baz } from './a'; + +assert.equal( foo, 'b-foo' ); +assert.equal( baz, 'b-baz' ); diff --git a/test/function/export-from-with-import-conflict/_config.js b/test/function/export-from-with-import-conflict/_config.js new file mode 100644 index 0000000..2b61c4a --- /dev/null +++ b/test/function/export-from-with-import-conflict/_config.js @@ -0,0 +1,12 @@ +var assert = require( 'assert' ); + +module.exports = { + description: 'ignores conflict between import declaration and export from declaration', + exports: function ( exports ) { + assert.equal( exports.foo, 'a-bar' ); + assert.equal( exports.bar, 'a-foo' ); + assert.equal( exports.baz, 'a-baz' ); + } +}; + +// https://github.com/rollup/rollup/issues/16 diff --git a/test/function/export-from-with-import-conflict/a.js b/test/function/export-from-with-import-conflict/a.js new file mode 100644 index 0000000..c24adbf --- /dev/null +++ b/test/function/export-from-with-import-conflict/a.js @@ -0,0 +1,3 @@ +export var foo = 'a-foo'; +export var bar = 'a-bar'; +export var baz = 'a-baz'; diff --git a/test/function/export-from-with-import-conflict/b.js b/test/function/export-from-with-import-conflict/b.js new file mode 100644 index 0000000..a1c82e5 --- /dev/null +++ b/test/function/export-from-with-import-conflict/b.js @@ -0,0 +1,2 @@ +export var foo = 'b-foo'; +export var baz = 'b-baz'; diff --git a/test/function/export-from-with-import-conflict/main.js b/test/function/export-from-with-import-conflict/main.js new file mode 100644 index 0000000..edc705e --- /dev/null +++ b/test/function/export-from-with-import-conflict/main.js @@ -0,0 +1,8 @@ +import { foo, baz } from './b'; + +export { foo as bar } from './a'; +export { bar as foo } from './a'; +export { baz } from './a'; + +assert.equal( foo, 'b-foo' ); +assert.equal( baz, 'b-baz' ); From ae084c04245c6fc5935a9fcaa7bb43789b1f9d66 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 17 Aug 2015 17:23:43 -0400 Subject: [PATCH 02/18] first stab at handling reexports separately --- src/Bundle.js | 50 ++++++++--- src/Module.js | 165 ++++++++++++++++++++++--------------- src/Statement.js | 16 ++++ src/utils/getExportMode.js | 2 +- 4 files changed, 151 insertions(+), 82 deletions(-) diff --git a/src/Bundle.js b/src/Bundle.js index 563c58b..d8e4741 100644 --- a/src/Bundle.js +++ b/src/Bundle.js @@ -224,7 +224,7 @@ export default class Bundle { keys( statement.modifies ).forEach( name => { const definingStatement = module.definitions[ name ]; - const exportDeclaration = module.exports[ name ] || ( + const exportDeclaration = module.exports[ name ] || module.reexports[ name ] || ( module.exports.default && module.exports.default.identifier === name && module.exports.default ); @@ -305,11 +305,25 @@ export default class Bundle { varExports[ key ] = true; } }); + + keys( this.entryModule.reexports ).forEach( key => { + const reexportDeclaration = this.entryModule.reexports[ key ]; + + const originalDeclaration = reexportDeclaration.module.findDeclaration( reexportDeclaration.importedName ); + + if ( originalDeclaration && originalDeclaration.type === 'VariableDeclaration' ) { + const canonicalName = this.trace( reexportDeclaration.module, reexportDeclaration.importedName, false ); + + allBundleExports[ canonicalName ] = `exports.${key}`; + varExports[ key ] = true; + } + }); } // since we're rewriting variable exports, we want to // ensure we don't try and export them again at the bottom this.toExport = keys( this.entryModule.exports ) + .concat( keys( this.entryModule.reexports ) ) .filter( key => !varExports[ key ] ); @@ -476,40 +490,50 @@ export default class Bundle { // defined elsewhere const otherModule = importDeclaration.module; + const name = importDeclaration.name; if ( otherModule.isExternal ) { - if ( importDeclaration.name === 'default' ) { + if ( name === 'default' ) { return otherModule.needsNamed && !es6 ? `${otherModule.name}__default` : otherModule.name; } - if ( importDeclaration.name === '*' ) { + if ( name === '*' ) { return otherModule.name; } return es6 ? - importDeclaration.name : - `${otherModule.name}.${importDeclaration.name}`; + name : + `${otherModule.name}.${name}`; } - if ( importDeclaration.name === '*' ) { + if ( name === '*' ) { return otherModule.replacements[ '*' ]; } - if ( importDeclaration.name === 'default' ) { + if ( name === 'default' ) { return otherModule.defaultName(); } - const exportDeclaration = otherModule.exports[ importDeclaration.name ]; - if ( exportDeclaration ) return this.trace( otherModule, exportDeclaration.localName ); + return this.traceExport( otherModule, name, es6 ); + } + + traceExport ( module, name, es6 ) { + const reexportDeclaration = module.reexports[ name ]; + if ( reexportDeclaration ) { + return this.traceExport( reexportDeclaration.module, reexportDeclaration.importedName ); + } + + const exportDeclaration = module.exports[ name ]; + if ( exportDeclaration ) return this.trace( module, exportDeclaration.localName ); - for ( let i = 0; i < otherModule.exportDelegates.length; i += 1 ) { - const delegate = otherModule.exportDelegates[i]; - const delegateExportDeclaration = delegate.module.exports[ importDeclaration.name ]; + for ( let i = 0; i < module.exportDelegates.length; i += 1 ) { + const delegate = module.exportDelegates[i]; + const delegateExportDeclaration = delegate.module.exports[ name ]; if ( delegateExportDeclaration ) { - return this.trace( delegate.module, delegateExportDeclaration.localName ); + return this.trace( delegate.module, delegateExportDeclaration.localName, es6 ); } } diff --git a/src/Module.js b/src/Module.js index f272a74..b69ee64 100644 --- a/src/Module.js +++ b/src/Module.js @@ -55,6 +55,7 @@ export default class Module { // imports and exports, indexed by ID this.imports = blank(); this.exports = blank(); + this.reexports = blank(); this.exportAlls = blank(); @@ -74,10 +75,32 @@ export default class Module { const node = statement.node; const source = node.source && node.source.value; + // export { name } from './other' + if ( source ) { + if ( node.type === 'ExportAllDeclaration' ) { + // Store `export * from '...'` statements in an array of delegates. + // When an unknown import is encountered, we see if one of them can satisfy it. + this.exportDelegates.push({ + statement, + source + }); + } + + else { + node.specifiers.forEach( specifier => { + this.reexports[ specifier.exported.name ] = { + source, + importedName: specifier.local.name, + module: null // filled in later + }; + }); + } + } + // export default function foo () {} // export default foo; // export default 42; - if ( node.type === 'ExportDefaultDeclaration' ) { + else if ( node.type === 'ExportDefaultDeclaration' ) { const isDeclaration = /Declaration$/.test( node.declaration.type ); const isAnonymous = /(?:Class|Function)Expression$/.test( node.declaration.type ); @@ -108,20 +131,10 @@ export default class Module { const localName = specifier.local.name; const exportedName = specifier.exported.name; - // export { foo } from './foo'; - if ( source ) { - this.imports[ localName ] = { - source, - localName: exportedName, - name: localName - }; - } - this.exports[ exportedName ] = { statement, localName, - exportedName, - linkedImport: source ? this.imports[ localName ] : null + exportedName }; }); } @@ -146,15 +159,6 @@ export default class Module { }; } } - - // Store `export * from '...'` statements in an array of delegates. - // When an unknown import is encountered, we see if one of them can satisfy it. - else { - this.exportDelegates.push({ - statement, - source - }); - } } addImport ( statement ) { @@ -215,6 +219,13 @@ export default class Module { consolidateDependencies () { let strongDependencies = blank(); + function addDependency ( dependencies, declaration ) { + if ( declaration && declaration.module && !declaration.module.isExternal ) { + dependencies[ declaration.module.id ] = declaration.module; + return true; + } + } + this.statements.forEach( statement => { if ( statement.isImportDeclaration && !statement.node.specifiers.length && !statement.module.isExternal ) { // include module for its side-effects @@ -224,18 +235,17 @@ export default class Module { keys( statement.stronglyDependsOn ).forEach( name => { if ( statement.defines[ name ] ) return; - const exportAllDeclaration = this.exportAlls[ name ]; - - if ( exportAllDeclaration && exportAllDeclaration.module && !exportAllDeclaration.module.isExternal ) { - strongDependencies[ exportAllDeclaration.module.id ] = exportAllDeclaration.module; - return; + let module = this; + let reexportDeclaration; + while ( module.reexports[ name ] ) { + reexportDeclaration = module.reexports[ name ]; + module = reexportDeclaration.module; + name = reexportDeclaration.importedName; } - const importDeclaration = this.imports[ name ]; - - if ( importDeclaration && importDeclaration.module && !importDeclaration.module.isExternal ) { - strongDependencies[ importDeclaration.module.id ] = importDeclaration.module; - } + addDependency( strongDependencies, reexportDeclaration ) || + addDependency( strongDependencies, this.exportAlls[ name ] ) || + addDependency( strongDependencies, this.imports[ name ] ); }); }); @@ -245,11 +255,17 @@ export default class Module { keys( statement.dependsOn ).forEach( name => { if ( statement.defines[ name ] ) return; - const importDeclaration = this.imports[ name ]; - - if ( importDeclaration && importDeclaration.module && !importDeclaration.module.isExternal ) { - weakDependencies[ importDeclaration.module.id ] = importDeclaration.module; + let module = this; + let reexportDeclaration; + while ( module.reexports[ name ] ) { + reexportDeclaration = module.reexports[ name ]; + module = reexportDeclaration.module; + name = reexportDeclaration.importedName; } + + addDependency( weakDependencies, reexportDeclaration ) || + addDependency( weakDependencies, this.exportAlls[ name ] ) || + addDependency( weakDependencies, this.imports[ name ] ); }); }); @@ -368,39 +384,7 @@ export default class Module { return module.markAllExportStatements(); } - const exportDeclaration = module.exports[ importDeclaration.name ]; - - if ( !exportDeclaration ) { - const noExport = new Error( `Module ${module.id} does not export ${importDeclaration.name} (imported by ${this.id})` ); - - // See if there exists an export delegate that defines `name`. - return first( module.exportDelegates, noExport, declaration => { - return module.bundle.fetchModule( declaration.source, module.id ).then( submodule => { - declaration.module = submodule; - - return submodule.mark( name ).then( result => { - if ( !result.length ) throw noExport; - - // It's found! This module exports `name` through declaration. - // It is however not imported into this scope. - module.exportAlls[ name ] = declaration; - - declaration.statement.dependsOn[ name ] = - declaration.statement.stronglyDependsOn[ name ] = result; - - return result; - }); - }); - }); - } - - exportDeclaration.isUsed = true; - - if ( importDeclaration.name === 'default' ) { - return exportDeclaration.statement.mark(); - } - - return module.mark( exportDeclaration.localName ); + return module.markExport( importDeclaration.name, this ); }); } @@ -458,6 +442,51 @@ export default class Module { }); } + markExport ( name, importer ) { + const reexportDeclaration = this.reexports[ name ]; + if ( reexportDeclaration ) { + reexportDeclaration.isUsed = true; + + return this.bundle.fetchModule( reexportDeclaration.source, this.id ) + .then( otherModule => { + reexportDeclaration.module = otherModule; + return otherModule.markExport( reexportDeclaration.importedName ); + }); + } + + const exportDeclaration = this.exports[ name ]; + if ( exportDeclaration ) { + exportDeclaration.isUsed = true; + if ( name === 'default' ) { + return exportDeclaration.statement.mark(); + } + + return this.mark( exportDeclaration.localName ); + } + + const noExport = new Error( `Module ${this.id} does not export ${name} (imported by ${importer.id})` ); + + // See if there exists an export delegate that defines `name`. + return first( this.exportDelegates, noExport, declaration => { + return this.bundle.fetchModule( declaration.source, this.id ).then( submodule => { + declaration.module = submodule; + + return submodule.mark( name ).then( result => { + if ( !result.length ) throw noExport; + + // It's found! This module exports `name` through declaration. + // It is however not imported into this scope. + this.exportAlls[ name ] = declaration; + + declaration.statement.dependsOn[ name ] = + declaration.statement.stronglyDependsOn[ name ] = result; + + return result; + }); + }); + }); + } + parse ( ast ) { // The ast can be supplied programmatically (but usually won't be) if ( !ast ) { diff --git a/src/Statement.js b/src/Statement.js index f8d612b..3259d2a 100644 --- a/src/Statement.js +++ b/src/Statement.js @@ -26,6 +26,7 @@ export default class Statement { this.isImportDeclaration = node.type === 'ImportDeclaration'; this.isExportDeclaration = /^Export/.test( node.type ); + this.isReexportDeclaration = this.isExportDeclaration && !!node.source; } analyse () { @@ -235,6 +236,17 @@ export default class Statement { if ( this.isIncluded ) return; // prevent infinite loops this.isIncluded = true; + // `export { name } from './other'` is a special case + if ( this.isReexportDeclaration ) { + return this.module.bundle.fetchModule( this.node.source.value, this.module.id ) + .then( otherModule => { + return sequence( this.node.specifiers, specifier => { + this.module.reexports[ specifier.exported.name ].module = otherModule; + return otherModule.markExport( specifier.local.name ); + }); + }); + } + const dependencies = Object.keys( this.dependsOn ); return sequence( dependencies, name => { @@ -371,6 +383,10 @@ export default class Statement { return magicString; } + source () { + return this.module.source.slice( this.start, this.end ); + } + toString () { return this.module.magicString.slice( this.start, this.end ); } diff --git a/src/utils/getExportMode.js b/src/utils/getExportMode.js index 0dbf07d..86ed4d4 100644 --- a/src/utils/getExportMode.js +++ b/src/utils/getExportMode.js @@ -5,7 +5,7 @@ function badExports ( option, keys ) { } export default function getExportMode ( bundle, exportMode ) { - const exportKeys = keys( bundle.entryModule.exports ); + const exportKeys = keys( bundle.entryModule.exports ).concat( keys( bundle.entryModule.reexports ) ); if ( exportMode === 'default' ) { if ( exportKeys.length !== 1 || exportKeys[0] !== 'default' ) { From e53bfa618095cba1b3896079cf8a211b6336cc66 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 17 Aug 2015 18:00:16 -0400 Subject: [PATCH 03/18] fix a few tests --- src/Module.js | 43 +++++++++++++++++++++++++++---------------- src/Statement.js | 8 ++++++-- 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/src/Module.js b/src/Module.js index b69ee64..ae10569 100644 --- a/src/Module.js +++ b/src/Module.js @@ -232,21 +232,30 @@ export default class Module { strongDependencies[ statement.module.id ] = statement.module; // TODO is this right? `statement.module` should be `this`, surely? } - keys( statement.stronglyDependsOn ).forEach( name => { - if ( statement.defines[ name ] ) return; + else if ( statement.isReexportDeclaration ) { + statement.node.specifiers.forEach( specifier => { + let reexport; + + let module = this; + let name = specifier.exported.name; + while ( module.reexports[ name ] && module.reexports[ name ].isUsed ) { + reexport = module.reexports[ name ]; + module = reexport.module; + name = reexport.importedName; + } - let module = this; - let reexportDeclaration; - while ( module.reexports[ name ] ) { - reexportDeclaration = module.reexports[ name ]; - module = reexportDeclaration.module; - name = reexportDeclaration.importedName; - } + addDependency( strongDependencies, reexport ); + }); + } - addDependency( strongDependencies, reexportDeclaration ) || - addDependency( strongDependencies, this.exportAlls[ name ] ) || - addDependency( strongDependencies, this.imports[ name ] ); - }); + else { + keys( statement.stronglyDependsOn ).forEach( name => { + if ( statement.defines[ name ] ) return; + + addDependency( strongDependencies, this.exportAlls[ name ] ) || + addDependency( strongDependencies, this.imports[ name ] ); + }); + } }); let weakDependencies = blank(); @@ -384,7 +393,7 @@ export default class Module { return module.markAllExportStatements(); } - return module.markExport( importDeclaration.name, this ); + return module.markExport( importDeclaration.name, 'TODO_suggestedName', this ); }); } @@ -442,7 +451,7 @@ export default class Module { }); } - markExport ( name, importer ) { + markExport ( name, suggestedName, importer ) { const reexportDeclaration = this.reexports[ name ]; if ( reexportDeclaration ) { reexportDeclaration.isUsed = true; @@ -450,7 +459,7 @@ export default class Module { return this.bundle.fetchModule( reexportDeclaration.source, this.id ) .then( otherModule => { reexportDeclaration.module = otherModule; - return otherModule.markExport( reexportDeclaration.importedName ); + return otherModule.markExport( reexportDeclaration.importedName, 'TODO_suggestedName', this ); }); } @@ -458,6 +467,8 @@ export default class Module { if ( exportDeclaration ) { exportDeclaration.isUsed = true; if ( name === 'default' ) { + this.needsDefault = true; + this.suggestName( 'default', suggestedName ); return exportDeclaration.statement.mark(); } diff --git a/src/Statement.js b/src/Statement.js index 3259d2a..a710d36 100644 --- a/src/Statement.js +++ b/src/Statement.js @@ -241,8 +241,12 @@ export default class Statement { return this.module.bundle.fetchModule( this.node.source.value, this.module.id ) .then( otherModule => { return sequence( this.node.specifiers, specifier => { - this.module.reexports[ specifier.exported.name ].module = otherModule; - return otherModule.markExport( specifier.local.name ); + const reexport = this.module.reexports[ specifier.exported.name ]; + + reexport.isUsed = true; + reexport.module = otherModule; + + return otherModule.markExport( specifier.local.name, specifier.exported.name, this.module ); }); }); } From be73b3279239151b4acbd640cd2fc881b40ccdfd Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 17 Aug 2015 18:01:32 -0400 Subject: [PATCH 04/18] fix export * case --- src/Module.js | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/Module.js b/src/Module.js index ae10569..82b49d7 100644 --- a/src/Module.js +++ b/src/Module.js @@ -233,19 +233,21 @@ export default class Module { } else if ( statement.isReexportDeclaration ) { - statement.node.specifiers.forEach( specifier => { - let reexport; - - let module = this; - let name = specifier.exported.name; - while ( module.reexports[ name ] && module.reexports[ name ].isUsed ) { - reexport = module.reexports[ name ]; - module = reexport.module; - name = reexport.importedName; - } + if ( statement.node.specifiers ) { + statement.node.specifiers.forEach( specifier => { + let reexport; + + let module = this; + let name = specifier.exported.name; + while ( module.reexports[ name ] && module.reexports[ name ].isUsed ) { + reexport = module.reexports[ name ]; + module = reexport.module; + name = reexport.importedName; + } - addDependency( strongDependencies, reexport ); - }); + addDependency( strongDependencies, reexport ); + }); + } } else { From 7521fe7e25d8e8b4143df9af8434d14b5cbb8268 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 17 Aug 2015 18:10:08 -0400 Subject: [PATCH 05/18] fix namespace reexports --- src/Bundle.js | 38 +++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/src/Bundle.js b/src/Bundle.js index d8e4741..7659ce0 100644 --- a/src/Bundle.js +++ b/src/Bundle.js @@ -339,23 +339,31 @@ export default class Bundle { // prepend bundle with internal namespaces const indentString = magicString.getIndentString(); const namespaceBlock = this.internalNamespaceModules.map( module => { - const exportKeys = keys( module.exports ); + const exports = keys( module.exports ).map( key => { + const exportDeclaration = module.exports[ key ]; - return `var ${module.replacements['*']} = {\n` + - exportKeys.map( key => { - let actualModule = module; - let exportDeclaration = module.exports[ key ]; - - // special case - `export { default as foo } from './foo'` - while ( exportDeclaration.linkedImport ) { - actualModule = exportDeclaration.linkedImport.module; - exportDeclaration = actualModule.exports[ exportDeclaration.linkedImport.name ]; - } + let localName = exportDeclaration.localName; + localName = module.replacements[ localName ] || localName; + return `${indentString}get ${key} () { return ${localName}; }`; + }); - let localName = exportDeclaration.localName; - localName = actualModule.replacements[ localName ] || localName; - return `${indentString}get ${key} () { return ${localName}; }`; // TODO... - }).join( ',\n' ) + + const reexports = keys( module.reexports ).map( key => { + let exportingModule = module; + let actualKey = key; + let reexport; + + while ( exportingModule.reexports[ key ] ) { + reexport = exportingModule.reexports[ key ]; + exportingModule = reexport.module; + key = reexport.importedName; + } + + key = exportingModule.replacements[ key ] || key; + return `${indentString}get ${actualKey} () { return ${key}; }`; + }); + + return `var ${module.replacements['*']} = {\n` + + exports.concat( reexports ).join( ',\n' ) + `\n};\n\n`; }).join( '' ); From 6ed5b7f78cf1bfadcf6605ce7f409412ac235af4 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 17 Aug 2015 18:11:51 -0400 Subject: [PATCH 06/18] remove reference to linkedImport --- src/Module.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Module.js b/src/Module.js index 82b49d7..dc30291 100644 --- a/src/Module.js +++ b/src/Module.js @@ -373,7 +373,7 @@ export default class Module { module.suggestName( 'default', `${suggestion}__default` ); } - if ( importDeclaration.name === 'default' && ( module.isExternal || !module.exports.default.linkedImport ) ) { // special case - exclude `export { default } from ...` + if ( importDeclaration.name === 'default' ) { module.needsDefault = true; } else if ( importDeclaration.name === '*' ) { module.needsAll = true; From 419a37f145cdc072f6ea6658dd8a45898fc8ae6f Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 17 Aug 2015 18:30:12 -0400 Subject: [PATCH 07/18] fix more tests, messily --- src/Bundle.js | 17 +++++++++++++++++ src/Module.js | 15 +++------------ src/Statement.js | 4 +++- src/finalisers/shared/getExportBlock.js | 5 ++++- 4 files changed, 27 insertions(+), 14 deletions(-) diff --git a/src/Bundle.js b/src/Bundle.js index 7659ce0..46c8fff 100644 --- a/src/Bundle.js +++ b/src/Bundle.js @@ -309,6 +309,7 @@ export default class Bundle { keys( this.entryModule.reexports ).forEach( key => { const reexportDeclaration = this.entryModule.reexports[ key ]; + if ( reexportDeclaration.module.isExternal ) return; const originalDeclaration = reexportDeclaration.module.findDeclaration( reexportDeclaration.importedName ); if ( originalDeclaration && originalDeclaration.type === 'VariableDeclaration' ) { @@ -530,6 +531,22 @@ export default class Bundle { traceExport ( module, name, es6 ) { const reexportDeclaration = module.reexports[ name ]; if ( reexportDeclaration ) { + if ( reexportDeclaration.module.isExternal ) { + if ( name === 'default' ) { + return reexportDeclaration.module.needsNamed && !es6 ? + `${reexportDeclaration.module.name}__default` : + reexportDeclaration.module.name; + } + + if ( name === '*' ) { + return reexportDeclaration.module.name; + } + + return es6 ? + name : + `${reexportDeclaration.module.name}.${name}`; + } + return this.traceExport( reexportDeclaration.module, reexportDeclaration.importedName ); } diff --git a/src/Module.js b/src/Module.js index dc30291..a004182 100644 --- a/src/Module.js +++ b/src/Module.js @@ -239,7 +239,7 @@ export default class Module { let module = this; let name = specifier.exported.name; - while ( module.reexports[ name ] && module.reexports[ name ].isUsed ) { + while ( !module.isExternal && module.reexports[ name ] && module.reexports[ name ].isUsed ) { reexport = module.reexports[ name ]; module = reexport.module; name = reexport.importedName; @@ -266,15 +266,6 @@ export default class Module { keys( statement.dependsOn ).forEach( name => { if ( statement.defines[ name ] ) return; - let module = this; - let reexportDeclaration; - while ( module.reexports[ name ] ) { - reexportDeclaration = module.reexports[ name ]; - module = reexportDeclaration.module; - name = reexportDeclaration.importedName; - } - - addDependency( weakDependencies, reexportDeclaration ) || addDependency( weakDependencies, this.exportAlls[ name ] ) || addDependency( weakDependencies, this.imports[ name ] ); }); @@ -395,7 +386,7 @@ export default class Module { return module.markAllExportStatements(); } - return module.markExport( importDeclaration.name, 'TODO_suggestedName', this ); + return module.markExport( importDeclaration.name, name, this ); }); } @@ -461,7 +452,7 @@ export default class Module { return this.bundle.fetchModule( reexportDeclaration.source, this.id ) .then( otherModule => { reexportDeclaration.module = otherModule; - return otherModule.markExport( reexportDeclaration.importedName, 'TODO_suggestedName', this ); + return otherModule.markExport( reexportDeclaration.importedName, suggestedName, this ); }); } diff --git a/src/Statement.js b/src/Statement.js index a710d36..00d4c4e 100644 --- a/src/Statement.js +++ b/src/Statement.js @@ -246,7 +246,9 @@ export default class Statement { reexport.isUsed = true; reexport.module = otherModule; - return otherModule.markExport( specifier.local.name, specifier.exported.name, this.module ); + return otherModule.isExternal ? + null : + otherModule.markExport( specifier.local.name, specifier.exported.name, this.module ); }); }); } diff --git a/src/finalisers/shared/getExportBlock.js b/src/finalisers/shared/getExportBlock.js index c789669..72aee4a 100644 --- a/src/finalisers/shared/getExportBlock.js +++ b/src/finalisers/shared/getExportBlock.js @@ -11,7 +11,10 @@ export default function getExportBlock ( bundle, exportMode, mechanism = 'return return bundle.toExport .map( name => { const prop = name === 'default' ? `['default']` : `.${name}`; - name = bundle.trace( bundle.entryModule, name ); + const reexport = bundle.entryModule.reexports[ name ]; + name = reexport ? + bundle.traceExport( bundle.entryModule, name ) : + bundle.trace( bundle.entryModule, name ); return `exports${prop} = ${name};`; }) .join( '\n' ); From b09bce28b57cdd4a7d26a8fe95c84e0415d51e60 Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Mon, 17 Aug 2015 20:00:46 -0400 Subject: [PATCH 08/18] fix typo in test --- test/function/export-from-with-definition-conflict/main.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/function/export-from-with-definition-conflict/main.js b/test/function/export-from-with-definition-conflict/main.js index 91eed71..dca99f9 100644 --- a/test/function/export-from-with-definition-conflict/main.js +++ b/test/function/export-from-with-definition-conflict/main.js @@ -5,5 +5,5 @@ export { foo as bar } from './a'; export { bar as foo } from './a'; export { baz } from './a'; -assert.equal( foo, 'b-foo' ); -assert.equal( baz, 'b-baz' ); +assert.equal( foo, 'local-foo' ); +assert.equal( baz, 'local-baz' ); From 13521adec8e592c140f3691781adc2dc47273e6c Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Tue, 18 Aug 2015 08:29:15 -0400 Subject: [PATCH 09/18] fix es6 export --- src/Bundle.js | 8 ++-- src/Module.js | 8 ++-- src/finalisers/es6.js | 37 +++++++++++++------ .../form/exported-empty-vars/_expected/es6.js | 4 +- test/form/multiple-exports/_expected/es6.js | 3 +- 5 files changed, 36 insertions(+), 24 deletions(-) diff --git a/src/Bundle.js b/src/Bundle.js index 46c8fff..070eabe 100644 --- a/src/Bundle.js +++ b/src/Bundle.js @@ -310,10 +310,10 @@ export default class Bundle { const reexportDeclaration = this.entryModule.reexports[ key ]; if ( reexportDeclaration.module.isExternal ) return; - const originalDeclaration = reexportDeclaration.module.findDeclaration( reexportDeclaration.importedName ); + const originalDeclaration = reexportDeclaration.module.findDeclaration( reexportDeclaration.localName ); if ( originalDeclaration && originalDeclaration.type === 'VariableDeclaration' ) { - const canonicalName = this.trace( reexportDeclaration.module, reexportDeclaration.importedName, false ); + const canonicalName = this.trace( reexportDeclaration.module, reexportDeclaration.localName, false ); allBundleExports[ canonicalName ] = `exports.${key}`; varExports[ key ] = true; @@ -356,7 +356,7 @@ export default class Bundle { while ( exportingModule.reexports[ key ] ) { reexport = exportingModule.reexports[ key ]; exportingModule = reexport.module; - key = reexport.importedName; + key = reexport.localName; } key = exportingModule.replacements[ key ] || key; @@ -547,7 +547,7 @@ export default class Bundle { `${reexportDeclaration.module.name}.${name}`; } - return this.traceExport( reexportDeclaration.module, reexportDeclaration.importedName ); + return this.traceExport( reexportDeclaration.module, reexportDeclaration.localName ); } const exportDeclaration = module.exports[ name ]; diff --git a/src/Module.js b/src/Module.js index a004182..909acf3 100644 --- a/src/Module.js +++ b/src/Module.js @@ -90,7 +90,7 @@ export default class Module { node.specifiers.forEach( specifier => { this.reexports[ specifier.exported.name ] = { source, - importedName: specifier.local.name, + localName: specifier.local.name, module: null // filled in later }; }); @@ -208,6 +208,8 @@ export default class Module { // if names are referenced that are neither defined nor imported // in this module, we assume that they're globals this.statements.forEach( statement => { + if ( statement.isExportDeclaration ) return; + keys( statement.dependsOn ).forEach( name => { if ( !this.definitions[ name ] && !this.imports[ name ] ) { this.bundle.assumedGlobals[ name ] = true; @@ -242,7 +244,7 @@ export default class Module { while ( !module.isExternal && module.reexports[ name ] && module.reexports[ name ].isUsed ) { reexport = module.reexports[ name ]; module = reexport.module; - name = reexport.importedName; + name = reexport.localName; } addDependency( strongDependencies, reexport ); @@ -452,7 +454,7 @@ export default class Module { return this.bundle.fetchModule( reexportDeclaration.source, this.id ) .then( otherModule => { reexportDeclaration.module = otherModule; - return otherModule.markExport( reexportDeclaration.importedName, suggestedName, this ); + return otherModule.markExport( reexportDeclaration.localName, suggestedName, this ); }); } diff --git a/src/finalisers/es6.js b/src/finalisers/es6.js index ba6a528..c612d87 100644 --- a/src/finalisers/es6.js +++ b/src/finalisers/es6.js @@ -10,6 +10,22 @@ function uniqueNames ( declarations ) { return keys( uniques ); } +function notDefault ( name ) { + return name !== 'default'; +} + +function getSpecifiers ( exports, replacements ) { + return keys( exports ).filter( notDefault ).map( name => { + const specifier = exports[ name ]; + console.log( 'specifier', specifier ) + const canonicalName = replacements[ specifier.localName ] || specifier.localName; + + return canonicalName === name ? + name : + `${canonicalName} as ${name}`; + }); +} + export default function es6 ( bundle, magicString ) { const importBlock = bundle.externalModules .map( module => { @@ -40,23 +56,20 @@ export default function es6 ( bundle, magicString ) { magicString.prepend( importBlock + '\n\n' ); } - const exports = bundle.entryModule.exports; - const exportBlock = keys( exports ).map( exportedName => { - const specifier = exports[ exportedName ]; + const module = bundle.entryModule; - const canonicalName = bundle.entryModule.replacements[ specifier.localName ] || specifier.localName; + const specifiers = getSpecifiers( module.exports, module.replacements ) + .concat( getSpecifiers( module.reexports, module.replacements ) ); - if ( exportedName === 'default' ) { - return `export default ${canonicalName};`; - } + let exportBlock = specifiers.length ? `export { ${specifiers.join(', ')} };` : ''; - return exportedName === canonicalName ? - `export { ${exportedName} };` : - `export { ${canonicalName} as ${exportedName} };`; - }).join( '\n' ); + const defaultExport = module.exports.default || module.reexports.default; + if ( defaultExport ) { + exportBlock += `export default ${bundle.traceExport(module,'default')};`; + } if ( exportBlock ) { - magicString.append( '\n\n' + exportBlock ); + magicString.append( '\n\n' + exportBlock.trim() ); } return magicString.trim(); diff --git a/test/form/exported-empty-vars/_expected/es6.js b/test/form/exported-empty-vars/_expected/es6.js index 3be564f..4b76753 100644 --- a/test/form/exported-empty-vars/_expected/es6.js +++ b/test/form/exported-empty-vars/_expected/es6.js @@ -6,6 +6,4 @@ var baz; bar = 43; baz = 44; -export { foo }; -export { bar }; -export { baz }; +export { foo, bar, baz }; diff --git a/test/form/multiple-exports/_expected/es6.js b/test/form/multiple-exports/_expected/es6.js index a46872c..2822603 100644 --- a/test/form/multiple-exports/_expected/es6.js +++ b/test/form/multiple-exports/_expected/es6.js @@ -1,5 +1,4 @@ var foo = 1; var bar = 2; -export { foo }; -export { bar }; +export { foo, bar }; From fc5b89dacdac17f70e543f9e9812130084918b6e Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Tue, 18 Aug 2015 08:43:47 -0400 Subject: [PATCH 10/18] fix global detection --- src/Module.js | 2 +- src/Statement.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Module.js b/src/Module.js index 909acf3..7e8a87c 100644 --- a/src/Module.js +++ b/src/Module.js @@ -208,7 +208,7 @@ export default class Module { // if names are referenced that are neither defined nor imported // in this module, we assume that they're globals this.statements.forEach( statement => { - if ( statement.isExportDeclaration ) return; + if ( statement.isReexportDeclaration ) return; keys( statement.dependsOn ).forEach( name => { if ( !this.definitions[ name ] && !this.imports[ name ] ) { diff --git a/src/Statement.js b/src/Statement.js index 00d4c4e..e11567c 100644 --- a/src/Statement.js +++ b/src/Statement.js @@ -164,7 +164,7 @@ export default class Statement { const definingScope = scope.findDefiningScope( node.name ); - if ( ( !definingScope || definingScope.depth === 0 ) && !this.defines[ node.name ] ) { + if ( !definingScope || definingScope.depth === 0 ) { this.dependsOn[ node.name ] = true; if ( strong ) this.stronglyDependsOn[ node.name ] = true; } From dd3bf0c3b0138ee1a4115138b3bd79432f043676 Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Tue, 18 Aug 2015 08:43:54 -0400 Subject: [PATCH 11/18] typos --- test/function/deconflicts-globals/bar.js | 2 +- test/function/deconflicts-globals/foo.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/function/deconflicts-globals/bar.js b/test/function/deconflicts-globals/bar.js index 5451a72..0944105 100644 --- a/test/function/deconflicts-globals/bar.js +++ b/test/function/deconflicts-globals/bar.js @@ -3,4 +3,4 @@ import foo from './foo'; export default function() { assert.equal( foo(), 'foo' ); return Number; -}; +} diff --git a/test/function/deconflicts-globals/foo.js b/test/function/deconflicts-globals/foo.js index a5b033a..61e56b6 100644 --- a/test/function/deconflicts-globals/foo.js +++ b/test/function/deconflicts-globals/foo.js @@ -2,4 +2,4 @@ export var Number = 42; export default function() { return 'foo'; -}; +} From 98494b2b5fb90ad397353f493b117db8bed5afde Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 19 Aug 2015 09:11:44 -0400 Subject: [PATCH 12/18] simplify es6 exports --- src/finalisers/es6.js | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/finalisers/es6.js b/src/finalisers/es6.js index c612d87..0ed804c 100644 --- a/src/finalisers/es6.js +++ b/src/finalisers/es6.js @@ -14,18 +14,6 @@ function notDefault ( name ) { return name !== 'default'; } -function getSpecifiers ( exports, replacements ) { - return keys( exports ).filter( notDefault ).map( name => { - const specifier = exports[ name ]; - console.log( 'specifier', specifier ) - const canonicalName = replacements[ specifier.localName ] || specifier.localName; - - return canonicalName === name ? - name : - `${canonicalName} as ${name}`; - }); -} - export default function es6 ( bundle, magicString ) { const importBlock = bundle.externalModules .map( module => { @@ -58,8 +46,13 @@ export default function es6 ( bundle, magicString ) { const module = bundle.entryModule; - const specifiers = getSpecifiers( module.exports, module.replacements ) - .concat( getSpecifiers( module.reexports, module.replacements ) ); + const specifiers = bundle.toExport.filter( notDefault ).map( name => { + const canonicalName = bundle.traceExport( module, name ); + + return canonicalName === name ? + name : + `${canonicalName} as ${name}`; + }); let exportBlock = specifiers.length ? `export { ${specifiers.join(', ')} };` : ''; From 199d3ad4e429927cbc84f06e566f51d91c389b23 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 19 Aug 2015 09:13:05 -0400 Subject: [PATCH 13/18] simplify export block logic --- src/finalisers/shared/getExportBlock.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/finalisers/shared/getExportBlock.js b/src/finalisers/shared/getExportBlock.js index 72aee4a..24eeb61 100644 --- a/src/finalisers/shared/getExportBlock.js +++ b/src/finalisers/shared/getExportBlock.js @@ -11,10 +11,7 @@ export default function getExportBlock ( bundle, exportMode, mechanism = 'return return bundle.toExport .map( name => { const prop = name === 'default' ? `['default']` : `.${name}`; - const reexport = bundle.entryModule.reexports[ name ]; - name = reexport ? - bundle.traceExport( bundle.entryModule, name ) : - bundle.trace( bundle.entryModule, name ); + name = bundle.traceExport( bundle.entryModule, name ); return `exports${prop} = ${name};`; }) .join( '\n' ); From 975528d034465f2a7d17de42198b60b4f57069a7 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 19 Aug 2015 10:08:31 -0400 Subject: [PATCH 14/18] handle multiple exports of a single binding --- src/Bundle.js | 60 ++++++++++++++---------- src/Module.js | 32 ++----------- src/Statement.js | 13 +++-- src/ast/Scope.js | 16 ++----- test/function/export-two-ways/_config.js | 10 ++++ test/function/export-two-ways/foo.js | 2 + test/function/export-two-ways/main.js | 5 ++ 7 files changed, 69 insertions(+), 69 deletions(-) create mode 100644 test/function/export-two-ways/_config.js create mode 100644 test/function/export-two-ways/foo.js create mode 100644 test/function/export-two-ways/main.js diff --git a/src/Bundle.js b/src/Bundle.js index 070eabe..0c95c0c 100644 --- a/src/Bundle.js +++ b/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 ) { diff --git a/src/Module.js b/src/Module.js index 7e8a87c..a3ad6c8 100644 --- a/src/Module.js +++ b/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 ] ) { diff --git a/src/Statement.js b/src/Statement.js index e11567c..228e5f1 100644 --- a/src/Statement.js +++ b/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 ) { diff --git a/src/ast/Scope.js b/src/ast/Scope.js index d9bfae8..4c88967 100644 --- a/src/ast/Scope.js +++ b/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; } } diff --git a/test/function/export-two-ways/_config.js b/test/function/export-two-ways/_config.js new file mode 100644 index 0000000..9dc72d3 --- /dev/null +++ b/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 ); + } +}; diff --git a/test/function/export-two-ways/foo.js b/test/function/export-two-ways/foo.js new file mode 100644 index 0000000..2ce0ac6 --- /dev/null +++ b/test/function/export-two-ways/foo.js @@ -0,0 +1,2 @@ +export var foo = 1; +foo = 2; diff --git a/test/function/export-two-ways/main.js b/test/function/export-two-ways/main.js new file mode 100644 index 0000000..589038f --- /dev/null +++ b/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 }; From c343b67e2aee1751259039a846b65dbe7b5c9dc1 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 19 Aug 2015 10:15:10 -0400 Subject: [PATCH 15/18] DRY out namespace exports --- src/Bundle.js | 30 +++++++----------------------- 1 file changed, 7 insertions(+), 23 deletions(-) diff --git a/src/Bundle.js b/src/Bundle.js index 0c95c0c..5688d20 100644 --- a/src/Bundle.js +++ b/src/Bundle.js @@ -338,31 +338,15 @@ export default class Bundle { // prepend bundle with internal namespaces const indentString = magicString.getIndentString(); const namespaceBlock = this.internalNamespaceModules.map( module => { - const exports = keys( module.exports ).map( key => { - const exportDeclaration = module.exports[ key ]; - - let localName = exportDeclaration.localName; - localName = module.replacements[ localName ] || localName; - return `${indentString}get ${key} () { return ${localName}; }`; - }); - - const reexports = keys( module.reexports ).map( key => { - let exportingModule = module; - let actualKey = key; - let reexport; - - while ( exportingModule.reexports[ key ] ) { - reexport = exportingModule.reexports[ key ]; - exportingModule = reexport.module; - key = reexport.localName; - } - - key = exportingModule.replacements[ key ] || key; - return `${indentString}get ${actualKey} () { return ${key}; }`; - }); + const exports = keys( module.exports ) + .concat( keys( module.reexports ) ) + .map( name => { + const canonicalName = this.traceExport( module, name ); + return `${indentString}get ${name} () { return ${canonicalName}; }`; + }); return `var ${module.replacements['*']} = {\n` + - exports.concat( reexports ).join( ',\n' ) + + exports.join( ',\n' ) + `\n};\n\n`; }).join( '' ); From f1d35aad7e9b0567cf54cb55e339a097bfa3ac07 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 19 Aug 2015 10:28:37 -0400 Subject: [PATCH 16/18] use arrays, not objects, to track variable declarations --- src/Bundle.js | 2 +- src/Module.js | 7 ++++--- src/Statement.js | 5 ----- src/ast/Scope.js | 5 +++-- 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/Bundle.js b/src/Bundle.js index 5688d20..c70cfc1 100644 --- a/src/Bundle.js +++ b/src/Bundle.js @@ -295,7 +295,7 @@ export default class Bundle { let getterExports = []; this.orderedModules.forEach( module => { - keys( module.varDeclarations ).forEach( name => { + module.varDeclarations.forEach( name => { varDeclarations[ module.replacements[ name ] || name ] = true; }); }); diff --git a/src/Module.js b/src/Module.js index a3ad6c8..9964ae2 100644 --- a/src/Module.js +++ b/src/Module.js @@ -64,8 +64,9 @@ export default class Module { this.replacements = blank(); + this.varDeclarations = []; + this.definitions = blank(); - this.varDeclarations = blank(); this.definitionPromises = blank(); this.modifications = blank(); @@ -201,8 +202,8 @@ export default class Module { this.definitions[ name ] = statement; }); - keys( statement.declaresVar ).forEach( name => { - this.varDeclarations[ name ] = statement; + statement.scope.varDeclarations.forEach( name => { + this.varDeclarations.push( name ); }); keys( statement.modifies ).forEach( name => { diff --git a/src/Statement.js b/src/Statement.js index 228e5f1..d4117e1 100644 --- a/src/Statement.js +++ b/src/Statement.js @@ -18,7 +18,6 @@ export default class Statement { this.scope = new Scope(); this.defines = blank(); - this.declaresVar = blank(); this.modifies = blank(); this.dependsOn = blank(); this.stronglyDependsOn = blank(); @@ -143,10 +142,6 @@ 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 ) { diff --git a/src/ast/Scope.js b/src/ast/Scope.js index 4c88967..6cbc6ce 100644 --- a/src/ast/Scope.js +++ b/src/ast/Scope.js @@ -12,9 +12,10 @@ 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; + this.varDeclarations = []; + if ( options.params ) { options.params.forEach( param => { this.declarations[ param.name ] = param; @@ -31,7 +32,7 @@ export default class Scope { this.parent.addDeclaration( name, declaration, isVar ); } else { this.declarations[ name ] = declaration; - if ( isVar ) this.varDeclarations[ name ] = true; + if ( isVar ) this.varDeclarations.push( name ) } } From b5bb5602170468aa09ca4a3a85546c00f043adee Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 19 Aug 2015 10:32:13 -0400 Subject: [PATCH 17/18] DRY --- src/Bundle.js | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/src/Bundle.js b/src/Bundle.js index c70cfc1..48f0c88 100644 --- a/src/Bundle.js +++ b/src/Bundle.js @@ -290,13 +290,13 @@ export default class Bundle { // // This doesn't apply if the bundle is exported as ES6! let allBundleExports = blank(); - let varDeclarations = blank(); + let isVarDeclaration = blank(); let varExports = blank(); let getterExports = []; this.orderedModules.forEach( module => { module.varDeclarations.forEach( name => { - varDeclarations[ module.replacements[ name ] || name ] = true; + isVarDeclaration[ module.replacements[ name ] || name ] = true; }); }); @@ -306,7 +306,7 @@ export default class Bundle { .forEach( name => { const canonicalName = this.traceExport( this.entryModule, name ); - if ( varDeclarations[ canonicalName ] ) { + if ( isVarDeclaration[ canonicalName ] ) { varExports[ name ] = true; // if the same binding is exported multiple ways, we need to @@ -354,9 +354,8 @@ export default class Bundle { 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' ) + + getterExports.map( ({ key, value }) => indentString + `${key}: { get: function () { return ${value}; } }` ).join( ',\n' ) + `\n});`; magicString.append( '\n\n' + getterExportsBlock ); @@ -368,13 +367,7 @@ export default class Bundle { throw new Error( `You must specify an output type - valid options are ${keys( finalisers ).join( ', ' )}` ); } - magicString = finalise( this, magicString.trim(), { - // Determine export mode - 'default', 'named', 'none' - exportMode, - - // Determine indentation - indentString: getIndentString( magicString, options ) - }, options ); + magicString = finalise( this, magicString.trim(), { exportMode, indentString }, options ); if ( options.banner ) magicString.prepend( options.banner + '\n' ); if ( options.footer ) magicString.append( '\n' + options.footer ); From bfad828248c8aee8155030b05e947d3548b31c14 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 19 Aug 2015 10:42:11 -0400 Subject: [PATCH 18/18] more DRY --- src/Bundle.js | 63 +++++++++++---------------------------------------- 1 file changed, 13 insertions(+), 50 deletions(-) diff --git a/src/Bundle.js b/src/Bundle.js index 48f0c88..0fd7656 100644 --- a/src/Bundle.js +++ b/src/Bundle.js @@ -336,7 +336,7 @@ export default class Bundle { }); // prepend bundle with internal namespaces - const indentString = magicString.getIndentString(); + const indentString = getIndentString( magicString, options ); const namespaceBlock = this.internalNamespaceModules.map( module => { const exports = keys( module.exports ) .concat( keys( module.reexports ) ) @@ -477,64 +477,27 @@ export default class Bundle { const importDeclaration = module.imports[ localName ]; // defined in this module - if ( !importDeclaration ) { - if ( localName === 'default' ) return module.defaultName(); - return module.replacements[ localName ] || localName; - } + if ( !importDeclaration ) return module.replacements[ localName ] || localName; // defined elsewhere - const otherModule = importDeclaration.module; - const name = importDeclaration.name; - - if ( otherModule.isExternal ) { - if ( name === 'default' ) { - return otherModule.needsNamed && !es6 ? - `${otherModule.name}__default` : - otherModule.name; - } - - if ( name === '*' ) { - return otherModule.name; - } - - return es6 ? - name : - `${otherModule.name}.${name}`; - } - - if ( name === '*' ) { - return otherModule.replacements[ '*' ]; - } - - if ( name === 'default' ) { - return otherModule.defaultName(); - } - - return this.traceExport( otherModule, name, es6 ); + return this.traceExport( importDeclaration.module, importDeclaration.name, es6 ); } traceExport ( module, name, es6 ) { + if ( module.isExternal ) { + if ( name === 'default' ) return module.needsNamed && !es6 ? `${module.name}__default` : module.name; + if ( name === '*' ) return module.name; + return es6 ? name : `${module.name}.${name}`; + } + const reexportDeclaration = module.reexports[ name ]; if ( reexportDeclaration ) { - if ( reexportDeclaration.module.isExternal ) { - if ( name === 'default' ) { - return reexportDeclaration.module.needsNamed && !es6 ? - `${reexportDeclaration.module.name}__default` : - reexportDeclaration.module.name; - } - - if ( name === '*' ) { - return reexportDeclaration.module.name; - } - - return es6 ? - name : - `${reexportDeclaration.module.name}.${name}`; - } - return this.traceExport( reexportDeclaration.module, reexportDeclaration.localName ); } + if ( name === '*' ) return module.replacements[ '*' ]; + if ( name === 'default' ) return module.defaultName(); + const exportDeclaration = module.exports[ name ]; if ( exportDeclaration ) return this.trace( module, exportDeclaration.localName ); @@ -547,6 +510,6 @@ export default class Bundle { } } - throw new Error( 'Could not trace binding' ); + throw new Error( `Could not trace binding '${name}' from ${module.id}` ); } }