diff --git a/src/Bundle.js b/src/Bundle.js index b0ef4ac..bdb7da8 100644 --- a/src/Bundle.js +++ b/src/Bundle.js @@ -1,4 +1,3 @@ -import Promise from 'es6-promise/lib/es6-promise/promise.js'; import MagicString from 'magic-string'; import first from './utils/first.js'; import { blank, forOwn, keys } from './utils/object.js'; @@ -10,6 +9,7 @@ import { load, makeOnwarn, resolveId } from './utils/defaults.js'; import getExportMode from './utils/getExportMode.js'; import getIndentString from './utils/getIndentString.js'; import { unixizePath } from './utils/normalizePlatform.js'; +import { mapSequence } from './utils/promise.js'; import transform from './utils/transform.js'; import transformBundle from './utils/transformBundle.js'; import collapseSourcemaps from './utils/collapseSourcemaps.js'; @@ -175,7 +175,7 @@ export default class Bundle { } fetchAllDependencies ( module ) { - const promises = module.sources.map( source => { + return mapSequence( module.sources, source => { return this.resolveId( source, module.id ) .then( resolvedId => { // If the `resolvedId` is supposed to be external, make it so. @@ -205,8 +205,6 @@ export default class Bundle { } }); }); - - return Promise.all( promises ); } render ( options = {} ) { diff --git a/src/Module.js b/src/Module.js index 7764a08..4b2e7d4 100644 --- a/src/Module.js +++ b/src/Module.js @@ -102,37 +102,37 @@ export default class Module { this.declarations.default = new SyntheticDefaultDeclaration( node, statement, identifier || this.basename() ); } - // export { foo, bar, baz } // export var { foo, bar } = ... // export var foo = 42; // export var a = 1, b = 2, c = 3; // export function foo () {} - else if ( node.type === 'ExportNamedDeclaration' ) { + else if ( node.declaration ) { + let declaration = node.declaration; + + if ( declaration.type === 'VariableDeclaration' ) { + declaration.declarations.forEach( decl => { + extractNames( decl.id ).forEach( localName => { + this.exports[ localName ] = { localName }; + }); + }); + } else { + // export function foo () {} + const localName = declaration.id.name; + this.exports[ localName ] = { localName }; + } + } + + // export { foo, bar, baz } + else { if ( node.specifiers.length ) { - // export { foo, bar, baz } node.specifiers.forEach( specifier => { const localName = specifier.local.name; const exportedName = specifier.exported.name; this.exports[ exportedName ] = { localName }; }); - } - - else { - let declaration = node.declaration; - - if ( declaration.type === 'VariableDeclaration' ) { - declaration.declarations.forEach( decl => { - extractNames( decl.id ).forEach( localName => { - this.exports[ localName ] = { localName }; - }); - }); - } - else { - // export function foo () {} - const localName = declaration.id.name; - this.exports[ localName ] = { localName }; - } + } else { + this.bundle.onwarn( `Module ${this.id} has an empty export declaration` ); } } } diff --git a/src/ast/isReference.js b/src/ast/isReference.js index 0c9a9d7..c2e6c89 100644 --- a/src/ast/isReference.js +++ b/src/ast/isReference.js @@ -10,7 +10,9 @@ export default function isReference ( node, parent ) { if ( !parent ) return true; // TODO is this right? - if ( parent.type === 'MemberExpression' ) return parent.computed || node === parent.object; + if ( parent.type === 'MemberExpression' || parent.type === 'MethodDefinition' ) { + return parent.computed || node === parent.object; + } // disregard the `bar` in `{ bar: foo }`, but keep it in `{ [bar]: foo }` if ( parent.type === 'Property' ) return parent.computed || node === parent.value; diff --git a/src/finalisers/iife.js b/src/finalisers/iife.js index 95c6d65..1d4dfb2 100644 --- a/src/finalisers/iife.js +++ b/src/finalisers/iife.js @@ -31,7 +31,7 @@ export default function iife ( bundle, magicString, { exportMode, indentString } } if ( exportMode === 'named' ) { - dependencies.unshift( `(this.${name} = {})` ); + dependencies.unshift( `(this.${name} = this.${name} || {})` ); args.unshift( 'exports' ); } diff --git a/src/finalisers/umd.js b/src/finalisers/umd.js index 3e8e813..e0d18e1 100644 --- a/src/finalisers/umd.js +++ b/src/finalisers/umd.js @@ -31,7 +31,7 @@ export default function umd ( bundle, magicString, { exportMode, indentString }, if ( exportMode === 'named' ) { amdDeps.unshift( `'exports'` ); cjsDeps.unshift( `exports` ); - globalDeps.unshift( `(${setupNamespace(options.moduleName)} = {})` ); + globalDeps.unshift( `(${setupNamespace(options.moduleName)} = global.${options.moduleName} || {})` ); args.unshift( 'exports' ); } diff --git a/src/utils/promise.js b/src/utils/promise.js new file mode 100644 index 0000000..1730de8 --- /dev/null +++ b/src/utils/promise.js @@ -0,0 +1,16 @@ +import Promise from 'es6-promise/lib/es6-promise/promise.js'; + +export function mapSequence ( array, fn ) { + let results = []; + let promise = Promise.resolve(); + + function next ( member, i ) { + return fn( member ).then( value => results[i] = value ); + } + + for ( let i = 0; i < array.length; i += 1 ) { + promise = promise.then( () => next( array[i], i ) ); + } + + return promise.then( () => results ); +} diff --git a/src/utils/run.js b/src/utils/run.js index 4470305..b57149d 100644 --- a/src/utils/run.js +++ b/src/utils/run.js @@ -95,6 +95,8 @@ export default function run ( node, scope, statement, strongDependencies, force if ( declaration ) { if ( declaration.isParam ) hasSideEffect = true; + } else if ( !scope.isTopLevel ) { + hasSideEffect = true; } else { declaration = statement.module.trace( subject.name ); diff --git a/test/form/computed-properties/_expected/amd.js b/test/form/computed-properties/_expected/amd.js index 4fd933f..0ccd4d8 100644 --- a/test/form/computed-properties/_expected/amd.js +++ b/test/form/computed-properties/_expected/amd.js @@ -1,9 +1,19 @@ define(['exports'], function (exports) { 'use strict'; var foo = 'foo'; + var bar = 'bar'; + var baz = 'baz'; + var bam = 'bam'; - var x = {[foo]: 'bar'}; + var x = { [foo]: 'bar' }; + + class X { + [bar] () {} + get [baz] () {} + set [bam] ( value ) {} + } exports.x = x; + exports.X = X; }); \ No newline at end of file diff --git a/test/form/computed-properties/_expected/cjs.js b/test/form/computed-properties/_expected/cjs.js index 22126d8..443bab3 100644 --- a/test/form/computed-properties/_expected/cjs.js +++ b/test/form/computed-properties/_expected/cjs.js @@ -1,7 +1,17 @@ 'use strict'; var foo = 'foo'; +var bar = 'bar'; +var baz = 'baz'; +var bam = 'bam'; -var x = {[foo]: 'bar'}; +var x = { [foo]: 'bar' }; -exports.x = x; \ No newline at end of file +class X { + [bar] () {} + get [baz] () {} + set [bam] ( value ) {} +} + +exports.x = x; +exports.X = X; \ No newline at end of file diff --git a/test/form/computed-properties/_expected/es6.js b/test/form/computed-properties/_expected/es6.js index 6dc09ca..d818b11 100644 --- a/test/form/computed-properties/_expected/es6.js +++ b/test/form/computed-properties/_expected/es6.js @@ -1,5 +1,14 @@ var foo = 'foo'; +var bar = 'bar'; +var baz = 'baz'; +var bam = 'bam'; -var x = {[foo]: 'bar'}; +var x = { [foo]: 'bar' }; -export { x }; \ No newline at end of file +class X { + [bar] () {} + get [baz] () {} + set [bam] ( value ) {} +} + +export { x, X }; \ No newline at end of file diff --git a/test/form/computed-properties/_expected/iife.js b/test/form/computed-properties/_expected/iife.js index 35b80af..031368a 100644 --- a/test/form/computed-properties/_expected/iife.js +++ b/test/form/computed-properties/_expected/iife.js @@ -2,9 +2,19 @@ 'use strict'; var foo = 'foo'; + var bar = 'bar'; + var baz = 'baz'; + var bam = 'bam'; - var x = {[foo]: 'bar'}; + var x = { [foo]: 'bar' }; + + class X { + [bar] () {} + get [baz] () {} + set [bam] ( value ) {} + } exports.x = x; + exports.X = X; -}((this.computedProperties = {}))); \ No newline at end of file +}((this.computedProperties = this.computedProperties || {}))); \ No newline at end of file diff --git a/test/form/computed-properties/_expected/umd.js b/test/form/computed-properties/_expected/umd.js index 5f7d146..ae57683 100644 --- a/test/form/computed-properties/_expected/umd.js +++ b/test/form/computed-properties/_expected/umd.js @@ -1,13 +1,23 @@ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : - (factory((global.computedProperties = {}))); + (factory((global.computedProperties = global.computedProperties || {}))); }(this, function (exports) { 'use strict'; var foo = 'foo'; + var bar = 'bar'; + var baz = 'baz'; + var bam = 'bam'; - var x = {[foo]: 'bar'}; + var x = { [foo]: 'bar' }; + + class X { + [bar] () {} + get [baz] () {} + set [bam] ( value ) {} + } exports.x = x; + exports.X = X; })); \ No newline at end of file diff --git a/test/form/computed-properties/main.js b/test/form/computed-properties/main.js index 65bb97c..6cfc64a 100644 --- a/test/form/computed-properties/main.js +++ b/test/form/computed-properties/main.js @@ -1,3 +1,12 @@ var foo = 'foo'; +var bar = 'bar'; +var baz = 'baz'; +var bam = 'bam'; -export var x = {[foo]: 'bar'}; +export var x = { [foo]: 'bar' }; + +export class X { + [bar] () {} + get [baz] () {} + set [bam] ( value ) {} +} diff --git a/test/form/dedupes-external-imports/_expected/iife.js b/test/form/dedupes-external-imports/_expected/iife.js index 17ed0b7..57e6649 100644 --- a/test/form/dedupes-external-imports/_expected/iife.js +++ b/test/form/dedupes-external-imports/_expected/iife.js @@ -30,4 +30,4 @@ exports.bar = bar; exports.baz = baz; -}((this.myBundle = {}),external)); +}((this.myBundle = this.myBundle || {}),external)); diff --git a/test/form/dedupes-external-imports/_expected/umd.js b/test/form/dedupes-external-imports/_expected/umd.js index 70bc301..06be0e7 100644 --- a/test/form/dedupes-external-imports/_expected/umd.js +++ b/test/form/dedupes-external-imports/_expected/umd.js @@ -1,7 +1,7 @@ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('external')) : typeof define === 'function' && define.amd ? define(['exports', 'external'], factory) : - (factory((global.myBundle = {}),global.external)); + (factory((global.myBundle = global.myBundle || {}),global.external)); }(this, function (exports,external) { 'use strict'; class Foo extends external.Component { diff --git a/test/form/export-all-from-internal/_expected/iife.js b/test/form/export-all-from-internal/_expected/iife.js index b5dad56..8a3cd17 100644 --- a/test/form/export-all-from-internal/_expected/iife.js +++ b/test/form/export-all-from-internal/_expected/iife.js @@ -7,4 +7,4 @@ exports.a = a; exports.b = b; -}((this.exposedInternals = {}))); +}((this.exposedInternals = this.exposedInternals || {}))); diff --git a/test/form/export-all-from-internal/_expected/umd.js b/test/form/export-all-from-internal/_expected/umd.js index 74613b4..b249cc6 100644 --- a/test/form/export-all-from-internal/_expected/umd.js +++ b/test/form/export-all-from-internal/_expected/umd.js @@ -1,7 +1,7 @@ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : - (factory((global.exposedInternals = {}))); + (factory((global.exposedInternals = global.exposedInternals || {}))); }(this, function (exports) { 'use strict'; const a = 1; diff --git a/test/form/exports-at-end-if-possible/_expected/iife.js b/test/form/exports-at-end-if-possible/_expected/iife.js index a0ce6a9..1c5c67b 100644 --- a/test/form/exports-at-end-if-possible/_expected/iife.js +++ b/test/form/exports-at-end-if-possible/_expected/iife.js @@ -9,4 +9,4 @@ exports.FOO = FOO; -}((this.myBundle = {}))); +}((this.myBundle = this.myBundle || {}))); diff --git a/test/form/exports-at-end-if-possible/_expected/umd.js b/test/form/exports-at-end-if-possible/_expected/umd.js index 20d0d2a..e9cbc8d 100644 --- a/test/form/exports-at-end-if-possible/_expected/umd.js +++ b/test/form/exports-at-end-if-possible/_expected/umd.js @@ -1,7 +1,7 @@ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : - (factory((global.myBundle = {}))); + (factory((global.myBundle = global.myBundle || {}))); }(this, function (exports) { 'use strict'; var FOO = 'foo'; diff --git a/test/form/multiple-exports/_expected/iife.js b/test/form/multiple-exports/_expected/iife.js index 3a3975d..d2da90f 100644 --- a/test/form/multiple-exports/_expected/iife.js +++ b/test/form/multiple-exports/_expected/iife.js @@ -7,4 +7,4 @@ exports.foo = foo; exports.bar = bar; -}((this.myBundle = {}))); +}((this.myBundle = this.myBundle || {}))); diff --git a/test/form/multiple-exports/_expected/umd.js b/test/form/multiple-exports/_expected/umd.js index 485e381..30a54d6 100644 --- a/test/form/multiple-exports/_expected/umd.js +++ b/test/form/multiple-exports/_expected/umd.js @@ -1,7 +1,7 @@ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : - (factory((global.myBundle = {}))); + (factory((global.myBundle = global.myBundle || {}))); }(this, function (exports) { 'use strict'; var foo = 1; diff --git a/test/form/namespaced-named-exports/_expected/iife.js b/test/form/namespaced-named-exports/_expected/iife.js index 2fc75df..69637e9 100644 --- a/test/form/namespaced-named-exports/_expected/iife.js +++ b/test/form/namespaced-named-exports/_expected/iife.js @@ -7,4 +7,4 @@ this.foo.bar = this.foo.bar || {}; exports.answer = answer; -}((this.foo.bar.baz = {}))); +}((this.foo.bar.baz = this.foo.bar.baz || {}))); diff --git a/test/form/namespaced-named-exports/_expected/umd.js b/test/form/namespaced-named-exports/_expected/umd.js index aeb7775..9a0f47d 100644 --- a/test/form/namespaced-named-exports/_expected/umd.js +++ b/test/form/namespaced-named-exports/_expected/umd.js @@ -1,7 +1,7 @@ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : - (factory((global.foo = global.foo || {}, global.foo.bar = global.foo.bar || {}, global.foo.bar.baz = {}))); + (factory((global.foo = global.foo || {}, global.foo.bar = global.foo.bar || {}, global.foo.bar.baz = global.foo.bar.baz || {}))); }(this, function (exports) { 'use strict'; var answer = 42; diff --git a/test/form/preserves-comments-after-imports/_expected/iife.js b/test/form/preserves-comments-after-imports/_expected/iife.js index e9338dc..ee4c000 100644 --- a/test/form/preserves-comments-after-imports/_expected/iife.js +++ b/test/form/preserves-comments-after-imports/_expected/iife.js @@ -9,4 +9,4 @@ exports.obj = obj; -}((this.myBundle = {}))); +}((this.myBundle = this.myBundle || {}))); diff --git a/test/form/preserves-comments-after-imports/_expected/umd.js b/test/form/preserves-comments-after-imports/_expected/umd.js index ac58856..13a54bf 100644 --- a/test/form/preserves-comments-after-imports/_expected/umd.js +++ b/test/form/preserves-comments-after-imports/_expected/umd.js @@ -1,7 +1,7 @@ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : - (factory((global.myBundle = {}))); + (factory((global.myBundle = global.myBundle || {}))); }(this, function (exports) { 'use strict'; /** A comment for a number */ diff --git a/test/function/empty-exports/_config.js b/test/function/empty-exports/_config.js new file mode 100644 index 0000000..65b2185 --- /dev/null +++ b/test/function/empty-exports/_config.js @@ -0,0 +1,17 @@ +var assert = require( 'assert' ); + +var warned = false; + +module.exports = { + description: 'warns on export {}, but does not fail', + options: { + onwarn: function ( msg ) { + warned = true; + assert.ok( /main\.js has an empty export declaration/.test( msg ) ); + } + }, + exports: function ( exports ) { + assert.equal( Object.keys( exports ).length, 0 ); + assert.ok( warned, 'did not warn' ); + } +}; diff --git a/test/function/empty-exports/main.js b/test/function/empty-exports/main.js new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/test/function/empty-exports/main.js @@ -0,0 +1 @@ +export {}; diff --git a/test/function/member-expression-assignment-in-function/_config.js b/test/function/member-expression-assignment-in-function/_config.js new file mode 100644 index 0000000..bf7e6fc --- /dev/null +++ b/test/function/member-expression-assignment-in-function/_config.js @@ -0,0 +1,10 @@ +var assert = require( 'assert' ); + +module.exports = { + description: 'detect side effect in member expression assignment when not top level', + code: function ( code ) { + assert.equal( code.indexOf( 'function set(key, value) { foo[key] = value; }' ) >= 0, true, code ); + assert.equal( code.indexOf( 'set("bar", 2);' ) >= 0, true, code ); + assert.equal( code.indexOf( 'set("qux", 3);' ) >= 0, true, code ); + } +} diff --git a/test/function/member-expression-assignment-in-function/main.js b/test/function/member-expression-assignment-in-function/main.js new file mode 100644 index 0000000..f627721 --- /dev/null +++ b/test/function/member-expression-assignment-in-function/main.js @@ -0,0 +1,9 @@ +var foo = {}; + +function set(key, value) { foo[key] = value; } + +set("bar", 2); +set("qux", 3); + +console.log(foo); +