diff --git a/src/Asset.js b/src/Asset.js index 79812e3eab9..e08758baee0 100644 --- a/src/Asset.js +++ b/src/Asset.js @@ -125,7 +125,7 @@ class Asset { async getPackage() { if (!this._package) { - this._package = await this.getConfig(['package.json']); + this._package = await this.resolver.findPackage(path.dirname(this.name)); } return this._package; diff --git a/src/packagers/JSConcatPackager.js b/src/packagers/JSConcatPackager.js index 89fe42d0889..1e060f5ac2b 100644 --- a/src/packagers/JSConcatPackager.js +++ b/src/packagers/JSConcatPackager.js @@ -141,8 +141,8 @@ class JSConcatPackager extends Packager { this.addedAssets.add(asset); let {js} = asset.generated; - // If the asset's package has the sideEffects: false flag set, and there are no used - // exports marked, exclude the asset from the bundle. + // If the asset has no side effects according to the its package's sideEffects flag, + // and there are no used exports marked, exclude the asset from the bundle. if ( asset.cacheData.sideEffects === false && (!asset.usedExports || asset.usedExports.size === 0) diff --git a/src/scope-hoisting/hoist.js b/src/scope-hoisting/hoist.js index b1e34ddcef3..d9ebfca19cc 100644 --- a/src/scope-hoisting/hoist.js +++ b/src/scope-hoisting/hoist.js @@ -1,4 +1,6 @@ +const path = require('path'); const matchesPattern = require('../visitors/matches-pattern'); +const mm = require('micromatch'); const t = require('babel-types'); const template = require('babel-template'); const rename = require('./renamer'); @@ -28,14 +30,32 @@ const TYPEOF = { require: 'function' }; +function hasSideEffects(asset, {sideEffects} = asset._package) { + switch (typeof sideEffects) { + case 'undefined': + return true; + case 'boolean': + return sideEffects; + case 'string': + return mm.isMatch( + path.relative(asset._package.pkgdir, asset.name), + sideEffects, + {matchBase: true} + ); + case 'object': + return sideEffects.some(sideEffects => + hasSideEffects(asset, {sideEffects}) + ); + } +} + module.exports = { Program: { enter(path, asset) { asset.cacheData.imports = asset.cacheData.imports || Object.create(null); asset.cacheData.exports = asset.cacheData.exports || Object.create(null); asset.cacheData.wildcards = asset.cacheData.wildcards || []; - asset.cacheData.sideEffects = - asset._package && asset._package.sideEffects; + asset.cacheData.sideEffects = asset._package && hasSideEffects(asset); let shouldWrap = false; path.traverse({ diff --git a/test/graphql.js b/test/graphql.js index ed6eacc93af..b532006994a 100644 --- a/test/graphql.js +++ b/test/graphql.js @@ -31,7 +31,6 @@ describe('graphql', function() { firstName lastName } - `.definitions ); }); diff --git a/test/integration/scope-hoisting/es6/side-effects-array/a.js b/test/integration/scope-hoisting/es6/side-effects-array/a.js new file mode 100644 index 00000000000..d07f8414fae --- /dev/null +++ b/test/integration/scope-hoisting/es6/side-effects-array/a.js @@ -0,0 +1,3 @@ +import {foo} from 'bar'; + +output = foo(2); diff --git a/test/integration/scope-hoisting/es6/side-effects-array/node_modules/bar/bar.js b/test/integration/scope-hoisting/es6/side-effects-array/node_modules/bar/bar.js new file mode 100644 index 00000000000..0e5ae1cab45 --- /dev/null +++ b/test/integration/scope-hoisting/es6/side-effects-array/node_modules/bar/bar.js @@ -0,0 +1,5 @@ +sideEffect('bar'); + +export default function bar() { + return 2; +} diff --git a/test/integration/scope-hoisting/es6/side-effects-array/node_modules/bar/foo.js b/test/integration/scope-hoisting/es6/side-effects-array/node_modules/bar/foo.js new file mode 100644 index 00000000000..ed9bdb2fa3f --- /dev/null +++ b/test/integration/scope-hoisting/es6/side-effects-array/node_modules/bar/foo.js @@ -0,0 +1,5 @@ +sideEffect('foo'); + +export default function foo(a) { + return a * a; +} diff --git a/test/integration/scope-hoisting/es6/side-effects-array/node_modules/bar/index.js b/test/integration/scope-hoisting/es6/side-effects-array/node_modules/bar/index.js new file mode 100644 index 00000000000..f9e940c9f6a --- /dev/null +++ b/test/integration/scope-hoisting/es6/side-effects-array/node_modules/bar/index.js @@ -0,0 +1,2 @@ +export foo from './foo'; +export bar from './bar'; diff --git a/test/integration/scope-hoisting/es6/side-effects-array/node_modules/bar/package.json b/test/integration/scope-hoisting/es6/side-effects-array/node_modules/bar/package.json new file mode 100644 index 00000000000..10a6720860d --- /dev/null +++ b/test/integration/scope-hoisting/es6/side-effects-array/node_modules/bar/package.json @@ -0,0 +1,4 @@ +{ + "name": "bar", + "sideEffects": ["./foo.js"] +} diff --git a/test/scope-hoisting.js b/test/scope-hoisting.js index e88c70ab191..1815b333eaa 100644 --- a/test/scope-hoisting.js +++ b/test/scope-hoisting.js @@ -281,6 +281,22 @@ describe('scope hoisting', function() { assert.deepEqual(output, 'bar'); }); + it('supports the package.json sideEffects flag with an array', async function() { + let b = await bundle( + __dirname + '/integration/scope-hoisting/es6/side-effects-array/a.js' + ); + + let calls = []; + let output = await run(b, { + sideEffect: caller => { + calls.push(caller); + } + }); + + assert(calls.toString() == 'foo', "side effect called for 'foo'"); + assert.deepEqual(output, 4); + }); + it('missing exports should be replaced with an empty object', async function() { let b = await bundle( __dirname + '/integration/scope-hoisting/es6/empty-module/a.js'