diff --git a/.flowconfig b/.flowconfig index ba6ee95f4f4..cdc1d1cf0cd 100644 --- a/.flowconfig +++ b/.flowconfig @@ -36,4 +36,4 @@ untyped-import untyped-type-import [version] -0.161.0 +0.164.0 diff --git a/package.json b/package.json index 925359a20f6..9b26a5856b5 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "@types/node": "^15.12.4", "cross-env": "^7.0.0", "eslint": "^7.20.0", - "flow-bin": "0.161.0", + "flow-bin": "0.164.0", "glob": "^7.1.6", "gulp": "^4.0.2", "gulp-babel": "^8.0.0", diff --git a/packages/core/core/src/BundleGraph.js b/packages/core/core/src/BundleGraph.js index 1c762f07be3..f17ef135267 100644 --- a/packages/core/core/src/BundleGraph.js +++ b/packages/core/core/src/BundleGraph.js @@ -1491,7 +1491,9 @@ export default class BundleGraph { if (!resolved) continue; let exported = this.getExportedSymbols(resolved, boundary) .filter(s => s.exportSymbol !== 'default') - .map(s => ({...s, exportAs: s.exportSymbol})); + .map(s => + s.exportSymbol !== '*' ? {...s, exportAs: s.exportSymbol} : s, + ); symbols.push(...exported); } } diff --git a/packages/core/core/src/Parcel.js b/packages/core/core/src/Parcel.js index 60d7a2cbb2d..bc748d31b6c 100644 --- a/packages/core/core/src/Parcel.js +++ b/packages/core/core/src/Parcel.js @@ -399,7 +399,7 @@ export default class Parcel { _getResolvedParcelOptions(): ParcelOptions { return nullthrows( this.#resolvedOptions, - 'Resolved options is null, please let parcel initialise before accessing this.', + 'Resolved options is null, please let parcel initialize before accessing this.', ); } diff --git a/packages/core/core/src/serializer.js b/packages/core/core/src/serializer.js index ab078a82e4f..57b770c533d 100644 --- a/packages/core/core/src/serializer.js +++ b/packages/core/core/src/serializer.js @@ -7,10 +7,10 @@ export let serializeRaw = v8.serialize; // $FlowFixMe - Flow doesn't know about this method yet export let deserializeRaw = v8.deserialize; -const nameToCtor: Map> = new Map(); -const ctorToName: Map, string> = new Map(); +const nameToCtor: Map> = new Map(); +const ctorToName: Map, string> = new Map(); -export function registerSerializableClass(name: string, ctor: Class<*>) { +export function registerSerializableClass(name: string, ctor: Class) { if (ctorToName.has(ctor)) { throw new Error('Class already registered with serializer'); } @@ -19,7 +19,7 @@ export function registerSerializableClass(name: string, ctor: Class<*>) { ctorToName.set(ctor, name); } -export function unregisterSerializableClass(name: string, ctor: Class<*>) { +export function unregisterSerializableClass(name: string, ctor: Class) { if (nameToCtor.get(name) === ctor) { nameToCtor.delete(name); } diff --git a/packages/core/core/src/utils.js b/packages/core/core/src/utils.js index ecd5b479a83..8de07de3b64 100644 --- a/packages/core/core/src/utils.js +++ b/packages/core/core/src/utils.js @@ -67,7 +67,8 @@ export function registerCoreWithSerializer() { Graph, ParcelConfig, RequestGraph, - }): Array<[string, Class<*>]>)) { + // $FlowFixMe[unclear-type] + }): Array<[string, Class]>)) { registerSerializableClass(packageVersion + ':' + name, ctor); } diff --git a/packages/core/integration-tests/test/integration/side-effects-false/import-require.js b/packages/core/integration-tests/test/integration/side-effects-false/import-require.js new file mode 100644 index 00000000000..0bf82ad6b76 --- /dev/null +++ b/packages/core/integration-tests/test/integration/side-effects-false/import-require.js @@ -0,0 +1,4 @@ +import {foo} from 'bar'; +const { bar } = require("bar"); + +export default foo(2) + bar(); diff --git a/packages/core/integration-tests/test/integration/side-effects-false/import.js b/packages/core/integration-tests/test/integration/side-effects-false/import.js new file mode 100644 index 00000000000..fe948bb713e --- /dev/null +++ b/packages/core/integration-tests/test/integration/side-effects-false/import.js @@ -0,0 +1,3 @@ +import {foo} from 'bar'; + +export default foo(2); diff --git a/packages/core/integration-tests/test/integration/side-effects-false/node_modules/bar/bar.js b/packages/core/integration-tests/test/integration/side-effects-false/node_modules/bar/bar.js new file mode 100644 index 00000000000..4ce39dd3c76 --- /dev/null +++ b/packages/core/integration-tests/test/integration/side-effects-false/node_modules/bar/bar.js @@ -0,0 +1,5 @@ +sideEffect(); + +export default function bar() { + return "returned from bar"; +} diff --git a/packages/core/integration-tests/test/integration/side-effects-false/node_modules/bar/foo.js b/packages/core/integration-tests/test/integration/side-effects-false/node_modules/bar/foo.js new file mode 100644 index 00000000000..36b0b344468 --- /dev/null +++ b/packages/core/integration-tests/test/integration/side-effects-false/node_modules/bar/foo.js @@ -0,0 +1,3 @@ +export default function foo(a) { + return a * a; +} diff --git a/packages/core/integration-tests/test/integration/side-effects-false/node_modules/bar/index.js b/packages/core/integration-tests/test/integration/side-effects-false/node_modules/bar/index.js new file mode 100644 index 00000000000..cf61931c936 --- /dev/null +++ b/packages/core/integration-tests/test/integration/side-effects-false/node_modules/bar/index.js @@ -0,0 +1,2 @@ +export {default as foo} from './foo'; +export {default as bar} from './bar'; diff --git a/packages/core/integration-tests/test/integration/side-effects-false/node_modules/bar/package.json b/packages/core/integration-tests/test/integration/side-effects-false/node_modules/bar/package.json new file mode 100644 index 00000000000..1ea9bea7dfa --- /dev/null +++ b/packages/core/integration-tests/test/integration/side-effects-false/node_modules/bar/package.json @@ -0,0 +1,4 @@ +{ + "name": "bar", + "sideEffects": false +} diff --git a/packages/core/integration-tests/test/javascript.js b/packages/core/integration-tests/test/javascript.js index 5ed13c7b3e1..45fa45996c6 100644 --- a/packages/core/integration-tests/test/javascript.js +++ b/packages/core/integration-tests/test/javascript.js @@ -2,8 +2,12 @@ import assert from 'assert'; import path from 'path'; import url from 'url'; import { + assertDependencyWasExcluded, bundle, bundler, + findAsset, + findDependency, + getNextBuild, run, runBundle, runBundles, @@ -18,6 +22,7 @@ import { import {makeDeferredWithPromise, normalizePath} from '@parcel/utils'; import vm from 'vm'; import Logger from '@parcel/logger'; +import nullthrows from 'nullthrows'; describe('javascript', function () { beforeEach(async () => { @@ -5735,4 +5740,1105 @@ describe('javascript', function () { assert.deepEqual(calls, ['common', 'deep']); }); + + it('supports deferring unused ESM imports with sideEffects: false', async function () { + let b = await bundle( + path.join(__dirname, '/integration/side-effects-false/import.js'), + ); + + let content = await outputFS.readFile(b.getBundles()[0].filePath, 'utf8'); + + assert(!content.includes('returned from bar')); + + let called = false; + let output = await run(b, { + sideEffect() { + called = true; + }, + }); + + assert(!called, 'side effect called'); + assert.strictEqual(output.default, 4); + }); + + it('supports ESM imports and requires with sideEffects: false', async function () { + let b = await bundle( + path.join(__dirname, '/integration/side-effects-false/import-require.js'), + ); + + let output = await run(b, { + sideEffect() {}, + }); + + assert.strictEqual(output.default, '4returned from bar'); + }); + + for (let shouldScopeHoist of [false, true]) { + let options = { + defaultTargetOptions: { + shouldScopeHoist, + }, + }; + let usesSymbolPropagation = shouldScopeHoist; + describe(`sideEffects: false with${ + shouldScopeHoist ? '' : 'out' + } scope-hoisting`, function () { + if (usesSymbolPropagation) { + it('supports excluding unused CSS imports', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-css/index.html', + ), + options, + ); + + assertBundles(b, [ + { + name: 'index.html', + assets: ['index.html'], + }, + { + type: 'js', + assets: ['index.js', 'a.js', 'b1.js'], + }, + { + type: 'css', + assets: ['b1.css'], + }, + ]); + + let calls = []; + let res = await run( + b, + { + output: null, + sideEffect: caller => { + calls.push(caller); + }, + }, + {require: false}, + ); + assert.deepEqual(calls, ['b1']); + assert.deepEqual(res.output, 2); + + let css = await outputFS.readFile( + b.getBundles().find(bundle => bundle.type === 'css').filePath, + 'utf8', + ); + assert(!css.includes('.b2')); + }); + + it("doesn't create new bundles for dynamic imports in excluded assets", async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-no-new-bundle/index.html', + ), + options, + ); + + assertBundles(b, [ + { + name: 'index.html', + assets: ['index.html'], + }, + { + type: 'js', + assets: ['index.js', 'a.js', 'b1.js'], + }, + ]); + + let calls = []; + let res = await run( + b, + { + output: null, + sideEffect: caller => { + calls.push(caller); + }, + }, + {require: false}, + ); + assert.deepEqual(calls, ['b1']); + assert.deepEqual(res.output, 2); + }); + } + + it('supports deferring unused ES6 re-exports (namespace used)', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-re-exports/a.js', + ), + options, + ); + + assertDependencyWasExcluded(b, 'index.js', './message2.js'); + if (usesSymbolPropagation) { + // TODO this only excluded, but should be deferred. + assert(!findAsset(b, 'message3.js')); + } + + let calls = []; + let res = await run( + b, + { + sideEffect: caller => { + calls.push(caller); + }, + }, + {require: false}, + ); + + assert.deepEqual( + calls, + shouldScopeHoist ? ['message1'] : ['message1', 'message3', 'index'], + ); + assert.deepEqual(res.output, 'Message 1'); + }); + + it('supports deferring an unused ES6 re-export (wildcard, empty, unused)', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-re-exports-all-empty/a.js', + ), + options, + ); + + if (usesSymbolPropagation) { + assertDependencyWasExcluded(b, 'index.js', './empty.js'); + } + + assert.deepEqual((await run(b, null, {require: false})).output, 123); + }); + + it('supports deferring unused ES6 re-exports (reexport named used)', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-re-exports/b.js', + ), + options, + ); + + if (usesSymbolPropagation) { + assert(!findAsset(b, 'message1.js')); + assert(!findAsset(b, 'message3.js')); + } + + let calls = []; + let res = await run( + b, + { + sideEffect: caller => { + calls.push(caller); + }, + }, + {require: false}, + ); + + assert.deepEqual( + calls, + shouldScopeHoist + ? ['message2'] + : ['message1', 'message2', 'message3', 'index'], + ); + assert.deepEqual(res.output, 'Message 2'); + }); + + it('supports deferring unused ES6 re-exports (namespace rename used)', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-re-exports/c.js', + ), + options, + ); + + if (usesSymbolPropagation) { + assert(!findAsset(b, 'message1.js')); + } + assertDependencyWasExcluded(b, 'index.js', './message2.js'); + + let calls = []; + let res = await run( + b, + { + sideEffect: caller => { + calls.push(caller); + }, + }, + {require: false}, + ); + + assert.deepEqual( + calls, + shouldScopeHoist ? ['message3'] : ['message1', 'message3', 'index'], + ); + assert.deepEqual(res.output, {default: 'Message 3'}); + }); + + it('supports deferring unused ES6 re-exports (direct export used)', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-re-exports/d.js', + ), + options, + ); + + assertDependencyWasExcluded(b, 'index.js', './message2.js'); + if (usesSymbolPropagation) { + assert(!findAsset(b, 'message1.js')); + assert(!findAsset(b, 'message3.js')); + } + + let calls = []; + let res = await run( + b, + { + sideEffect: caller => { + calls.push(caller); + }, + }, + {require: false}, + ); + + assert.deepEqual( + calls, + shouldScopeHoist ? ['index'] : ['message1', 'message3', 'index'], + ); + assert.deepEqual(res.output, 'Message 4'); + }); + + it('supports chained ES6 re-exports', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-re-exports-chained/index.js', + ), + options, + ); + + if (usesSymbolPropagation) { + assert(!findAsset(b, 'bar.js')); + } + + let calls = []; + let res = await run( + b, + { + sideEffect: caller => { + calls.push(caller); + }, + }, + {require: false}, + ); + + assert.deepEqual( + calls, + shouldScopeHoist + ? ['key', 'foo', 'index'] + : ['key', 'foo', 'bar', 'types', 'index'], + ); + assert.deepEqual(res.output, ['key', 'foo']); + }); + + it('should not optimize away an unused ES6 re-export and an used import', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-re-exports-import/a.js', + ), + options, + ); + + let res = await run(b, null, {require: false}); + assert.deepEqual(res.output, 123); + }); + + it('should not optimize away an unused ES6 re-export and an used import (different symbols)', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-re-exports-import-different/a.js', + ), + options, + ); + + let res = await run(b, null, {require: false}); + assert.deepEqual(res.output, 123); + }); + + it('correctly handles ES6 re-exports in library mode entries', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-re-exports-library/a.js', + ), + options, + ); + + let contents = await outputFS.readFile( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-re-exports-library/build.js', + ), + 'utf8', + ); + assert(!contents.includes('console.log')); + + let res = await run(b); + assert.deepEqual(res, {c1: 'foo'}); + }); + + if (shouldScopeHoist) { + it('correctly updates deferred assets that are reexported', async function () { + let testDir = path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-update-deferred-reexported', + ); + + let b = bundler(path.join(testDir, 'index.js'), { + inputFS: overlayFS, + outputFS: overlayFS, + ...options, + }); + + let subscription = await b.watch(); + + let bundleEvent = await getNextBuild(b); + assert(bundleEvent.type === 'buildSuccess'); + let output = await run(bundleEvent.bundleGraph); + assert.deepEqual(output, '12345hello'); + + await overlayFS.mkdirp(path.join(testDir, 'node_modules', 'foo')); + await overlayFS.copyFile( + path.join(testDir, 'node_modules', 'foo', 'foo_updated.js'), + path.join(testDir, 'node_modules', 'foo', 'foo.js'), + ); + + bundleEvent = await getNextBuild(b); + assert(bundleEvent.type === 'buildSuccess'); + output = await run(bundleEvent.bundleGraph); + assert.deepEqual(output, '1234556789'); + + await subscription.unsubscribe(); + }); + + it('correctly updates deferred assets that are reexported and imported directly', async function () { + let testDir = path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-update-deferred-direct', + ); + + let b = bundler(path.join(testDir, 'index.js'), { + inputFS: overlayFS, + outputFS: overlayFS, + ...options, + }); + + let subscription = await b.watch(); + + let bundleEvent = await getNextBuild(b); + assert(bundleEvent.type === 'buildSuccess'); + let output = await run(bundleEvent.bundleGraph); + assert.deepEqual(output, '12345hello'); + + await overlayFS.mkdirp(path.join(testDir, 'node_modules', 'foo')); + await overlayFS.copyFile( + path.join(testDir, 'node_modules', 'foo', 'foo_updated.js'), + path.join(testDir, 'node_modules', 'foo', 'foo.js'), + ); + + bundleEvent = await getNextBuild(b); + assert(bundleEvent.type === 'buildSuccess'); + output = await run(bundleEvent.bundleGraph); + assert.deepEqual(output, '1234556789'); + + await subscription.unsubscribe(); + }); + + it('removes deferred reexports when imported from multiple asssets', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-re-exports-multiple-dynamic/a.js', + ), + options, + ); + + let contents = await outputFS.readFile( + b.getBundles()[0].filePath, + 'utf8', + ); + + assert(!contents.includes('$import$')); + assert(contents.includes('= 1234;')); + assert(!contents.includes('= 5678;')); + + let output = await run(b); + assert.deepEqual(output, [1234, {default: 1234}]); + }); + } + + it('keeps side effects by default', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects/a.js', + ), + options, + ); + + let called = false; + let res = await run( + b, + { + sideEffect: () => { + called = true; + }, + }, + {require: false}, + ); + + assert(called, 'side effect not called'); + assert.deepEqual(res.output, 4); + }); + + it('supports the package.json sideEffects: false flag', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-false/a.js', + ), + options, + ); + + let called = false; + let res = await run( + b, + { + sideEffect: () => { + called = true; + }, + }, + {require: false}, + ); + + assert(!called, 'side effect called'); + assert.deepEqual(res.output, 4); + }); + + it('supports removing a deferred dependency', async function () { + let testDir = path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-false', + ); + + let b = bundler(path.join(testDir, 'a.js'), { + inputFS: overlayFS, + outputFS: overlayFS, + ...options, + }); + + let subscription = await b.watch(); + + try { + let bundleEvent = await getNextBuild(b); + assert.strictEqual(bundleEvent.type, 'buildSuccess'); + let called = false; + let res = await run( + bundleEvent.bundleGraph, + { + sideEffect: () => { + called = true; + }, + }, + {require: false}, + ); + assert(!called, 'side effect called'); + assert.deepEqual(res.output, 4); + assertDependencyWasExcluded( + bundleEvent.bundleGraph, + 'index.js', + './bar', + ); + + await overlayFS.mkdirp(path.join(testDir, 'node_modules/bar')); + await overlayFS.copyFile( + path.join(testDir, 'node_modules/bar/index.1.js'), + path.join(testDir, 'node_modules/bar/index.js'), + ); + + bundleEvent = await getNextBuild(b); + assert.strictEqual(bundleEvent.type, 'buildSuccess'); + called = false; + res = await run( + bundleEvent.bundleGraph, + { + sideEffect: () => { + called = true; + }, + }, + {require: false}, + ); + assert(!called, 'side effect called'); + assert.deepEqual(res.output, 4); + } finally { + await subscription.unsubscribe(); + } + }); + + it('supports wildcards', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-false-wildcards/a.js', + ), + options, + ); + let called = false; + let res = await run( + b, + { + sideEffect: () => { + called = true; + }, + }, + {require: false}, + ); + + if (usesSymbolPropagation) { + assert(!called, 'side effect called'); + } + assert.deepEqual(res.output, 'bar'); + }); + + it('correctly handles excluded and wrapped reexport assets', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-false-wrap-excluded/a.js', + ), + options, + ); + + let res = await run(b, null, {require: false}); + assert.deepEqual(res.output, 4); + }); + + it('supports the package.json sideEffects flag with an array', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-array/a.js', + ), + options, + ); + + let calls = []; + let res = await run( + b, + { + sideEffect: caller => { + calls.push(caller); + }, + }, + {require: false}, + ); + + assert(calls.toString() == 'foo', "side effect called for 'foo'"); + assert.deepEqual(res.output, 4); + }); + + it('supports the package.json sideEffects: false flag with shared dependencies', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-false-duplicate/a.js', + ), + options, + ); + + let called = false; + let res = await run( + b, + { + sideEffect: () => { + called = true; + }, + }, + {require: false}, + ); + + assert(!called, 'side effect called'); + assert.deepEqual(res.output, 6); + }); + + it('supports the package.json sideEffects: false flag with shared dependencies and code splitting', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-split/a.js', + ), + options, + ); + + let res = await run(b, null, {require: false}); + assert.deepEqual(await res.output, 581); + }); + + it('supports the package.json sideEffects: false flag with shared dependencies and code splitting II', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-split2/a.js', + ), + options, + ); + + let res = await run(b, null, {require: false}); + assert.deepEqual(await res.output, [{default: 123, foo: 2}, 581]); + }); + + it('missing exports should be replaced with an empty object', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/empty-module/a.js', + ), + options, + ); + + let res = await run(b, null, {require: false}); + assert.deepEqual(res.output, {b: {}}); + }); + + it('supports namespace imports of theoretically excluded reexporting assets', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/import-namespace-sideEffects/index.js', + ), + options, + ); + + let res = await run(b, null, {require: false}); + assert.deepEqual(res.output, {Main: 'main', a: 'foo', b: 'bar'}); + }); + + it('can import from a different bundle via a re-export', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/re-export-bundle-boundary-side-effects/index.js', + ), + options, + ); + + let res = await run(b, null, {require: false}); + assert.deepEqual(await res.output, ['operational', 'ui']); + }); + + it('supports excluding multiple chained namespace reexports', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-chained-re-exports-multiple/a.js', + ), + options, + ); + + if (usesSymbolPropagation) { + assert(!findAsset(b, 'symbol1.js')); + } + + let calls = []; + let res = await run( + b, + { + sideEffect: caller => { + calls.push(caller); + }, + }, + {require: false}, + ); + + assert.deepEqual( + calls, + shouldScopeHoist + ? ['message1'] + : [ + 'message1', + 'message2', + 'message', + 'symbol1', + 'symbol2', + 'symbol', + ], + ); + assert.deepEqual(res.output, 'Message 1'); + }); + + it('supports excluding when doing both exports and reexports', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-export-reexport/a.js', + ), + options, + ); + + if (usesSymbolPropagation) { + assert(!findAsset(b, 'other.js')); + } + + let calls = []; + let res = await run( + b, + { + sideEffect: caller => { + calls.push(caller); + }, + }, + {require: false}, + ); + + assert.deepEqual( + calls, + usesSymbolPropagation ? ['index'] : ['other', 'index'], + ); + assert.deepEqual(res.output, 'Message 1'); + }); + + it('supports deferring with chained renaming reexports', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-re-exports-rename-chained/a.js', + ), + options, + ); + + // assertDependencyWasExcluded(b, 'message.js', './message2'); + + let calls = []; + let res = await run( + b, + { + sideEffect: caller => { + calls.push(caller); + }, + }, + {require: false}, + ); + + assert.deepEqual( + calls, + shouldScopeHoist + ? ['message1'] + : ['message1', 'message2', 'message', 'index2', 'index'], + ); + assert.deepEqual(res.output, 'Message 1'); + }); + + it('supports named and renamed reexports of the same asset (default used)', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-re-exports-rename-same2/a.js', + ), + options, + ); + + if (usesSymbolPropagation) { + assert.deepStrictEqual( + new Set(b.getUsedSymbols(nullthrows(findAsset(b, 'other.js')))), + new Set(['bar']), + ); + } + + let calls = []; + let res = await run( + b, + { + sideEffect: caller => { + calls.push(caller); + }, + }, + {require: false}, + ); + + assert.deepEqual( + calls, + shouldScopeHoist ? ['other'] : ['other', 'index'], + ); + assert.deepEqual(res.output, 'bar'); + }); + + it('supports named and renamed reexports of the same asset (named used)', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-re-exports-rename-same2/b.js', + ), + options, + ); + + if (usesSymbolPropagation) { + assert.deepStrictEqual( + new Set(b.getUsedSymbols(nullthrows(findAsset(b, 'other.js')))), + new Set(['bar']), + ); + } + + let calls = []; + let res = await run( + b, + { + sideEffect: caller => { + calls.push(caller); + }, + }, + {require: false}, + ); + + assert.deepEqual( + calls, + shouldScopeHoist ? ['other'] : ['other', 'index'], + ); + assert.deepEqual(res.output, 'bar'); + }); + + it('supports named and namespace exports of the same asset (named used)', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-re-exports-namespace-same/a.js', + ), + options, + ); + + if (usesSymbolPropagation) { + assert.deepStrictEqual( + new Set(b.getUsedSymbols(nullthrows(findAsset(b, 'index.js')))), + new Set([]), + ); + assert.deepStrictEqual( + new Set(b.getUsedSymbols(nullthrows(findAsset(b, 'other.js')))), + new Set(['default']), + ); + } + + let calls = []; + let res = await run( + b, + { + sideEffect: caller => { + calls.push(caller); + }, + }, + {require: false}, + ); + + assert.deepEqual( + calls, + shouldScopeHoist ? ['other'] : ['other', 'index'], + ); + assert.deepEqual(res.output, ['foo']); + }); + + it('supports named and namespace exports of the same asset (namespace used)', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-re-exports-namespace-same/b.js', + ), + options, + ); + + if (usesSymbolPropagation) { + assert.deepStrictEqual( + new Set(b.getUsedSymbols(nullthrows(findAsset(b, 'index.js')))), + new Set([]), + ); + assert.deepStrictEqual( + new Set(b.getUsedSymbols(nullthrows(findAsset(b, 'other.js')))), + new Set(['bar']), + ); + } + + let calls = []; + let res = await run( + b, + { + sideEffect: caller => { + calls.push(caller); + }, + }, + {require: false}, + ); + + assert.deepEqual( + calls, + shouldScopeHoist ? ['other'] : ['other', 'index'], + ); + assert.deepEqual(res.output, ['bar']); + }); + + it('supports named and namespace exports of the same asset (both used)', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-re-exports-namespace-same/c.js', + ), + options, + ); + + if (usesSymbolPropagation) { + assert.deepStrictEqual( + new Set(b.getUsedSymbols(nullthrows(findAsset(b, 'index.js')))), + new Set([]), + ); + assert.deepStrictEqual( + new Set(b.getUsedSymbols(nullthrows(findAsset(b, 'other.js')))), + new Set(['default', 'bar']), + ); + } + + let calls = []; + let res = await run( + b, + { + sideEffect: caller => { + calls.push(caller); + }, + }, + {require: false}, + ); + + assert.deepEqual( + calls, + shouldScopeHoist ? ['other'] : ['other', 'index'], + ); + assert.deepEqual(res.output, ['foo', 'bar']); + }); + + it('supports deferring non-weak dependencies that are not used', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-semi-weak/a.js', + ), + options, + ); + + // assertDependencyWasExcluded(b, 'esm2.js', './other.js'); + + let calls = []; + let res = await run( + b, + { + sideEffect: caller => { + calls.push(caller); + }, + }, + {require: false}, + ); + + assert.deepEqual( + calls, + shouldScopeHoist ? ['esm1'] : ['esm1', 'other', 'esm2', 'index'], + ); + assert.deepEqual(res.output, 'Message 1'); + }); + + it('supports excluding CommonJS (CommonJS unused)', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-commonjs/a.js', + ), + options, + ); + + if (usesSymbolPropagation) { + assert.deepStrictEqual( + new Set(b.getUsedSymbols(nullthrows(findAsset(b, 'esm.js')))), + new Set(['message1']), + ); + // We can't statically analyze commonjs.js, so message1 appears to be used + assert.deepStrictEqual( + new Set(b.getUsedSymbols(nullthrows(findAsset(b, 'commonjs.js')))), + // the exports object is used freely + new Set(['*', 'message1']), + ); + assert.deepStrictEqual( + new Set( + b.getUsedSymbols(findDependency(b, 'index.js', './commonjs.js')), + ), + new Set(['message1']), + ); + } + + let calls = []; + let res = await run( + b, + { + sideEffect: caller => { + calls.push(caller); + }, + }, + {require: false}, + ); + + assert.deepEqual(calls, ['esm', 'commonjs', 'index']); + assert.deepEqual(res.output, 'Message 1'); + }); + + it('supports excluding CommonJS (CommonJS used)', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/side-effects-commonjs/b.js', + ), + options, + ); + + if (usesSymbolPropagation) { + assert(!findAsset(b, 'esm.js')); + assert.deepStrictEqual( + new Set(b.getUsedSymbols(nullthrows(findAsset(b, 'commonjs.js')))), + // the exports object is used freely + new Set(['*', 'message2']), + ); + assert.deepEqual( + new Set( + b.getUsedSymbols(findDependency(b, 'index.js', './commonjs.js')), + ), + new Set(['message2']), + ); + } + + let calls = []; + let res = await run( + b, + { + sideEffect: caller => { + calls.push(caller); + }, + }, + {require: false}, + ); + assert.deepEqual( + calls, + shouldScopeHoist ? ['commonjs'] : ['esm', 'commonjs', 'index'], + ); + assert.deepEqual(res.output, 'Message 2'); + }); + }); + } }); diff --git a/packages/core/integration-tests/test/scope-hoisting.js b/packages/core/integration-tests/test/scope-hoisting.js index b871856e0f1..159adcdc37b 100644 --- a/packages/core/integration-tests/test/scope-hoisting.js +++ b/packages/core/integration-tests/test/scope-hoisting.js @@ -5,7 +5,7 @@ import {normalizePath} from '@parcel/utils'; import {md} from '@parcel/diagnostic'; import { assertBundles, - assertDependencyWasDeferred, + assertDependencyWasExcluded, bundle as _bundle, bundler as _bundler, distDir, @@ -2222,7 +2222,7 @@ describe('scope hoisting', function () { let output = await run(bundleEvent.bundleGraph); assert.deepEqual(output, [123]); - assertDependencyWasDeferred( + assertDependencyWasExcluded( bundleEvent.bundleGraph, 'a.js', './c.js', @@ -2547,857 +2547,23 @@ describe('scope hoisting', function () { }); }); - describe('sideEffects: false', function () { - it('supports excluding unused CSS imports', async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-css/index.html', - ), - ); - - assertBundles(b, [ - { - name: 'index.html', - assets: ['index.html'], - }, - { - type: 'js', - assets: ['index.js', 'a.js', 'b1.js'], - }, - { - type: 'css', - assets: ['b1.css'], - }, - ]); - - let calls = []; - let res = await run( - b, - { - output: null, - sideEffect: caller => { - calls.push(caller); - }, - }, - {require: false}, - ); - assert.deepEqual(calls, ['b1']); - assert.deepEqual(res.output, 2); - - let css = await outputFS.readFile( - b.getBundles().find(bundle => bundle.type === 'css').filePath, - 'utf8', - ); - assert(!css.includes('.b2')); - }); - - it("doesn't create new bundles for dynamic imports in excluded assets", async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-no-new-bundle/index.html', - ), - ); - - assertBundles(b, [ - { - name: 'index.html', - assets: ['index.html'], - }, - { - type: 'js', - assets: ['index.js', 'a.js', 'b1.js'], - }, - ]); - - let calls = []; - let res = await run( - b, - { - output: null, - sideEffect: caller => { - calls.push(caller); - }, - }, - {require: false}, - ); - assert.deepEqual(calls, ['b1']); - assert.deepEqual(res.output, 2); - }); - - it('supports deferring an unused ES6 re-exports (namespace used)', async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-re-exports/a.js', - ), - ); - - assertDependencyWasDeferred(b, 'index.js', './message2.js'); - assert(!findAsset(b, 'message3.js')); - - let calls = []; - let output = await run(b, { - sideEffect: caller => { - calls.push(caller); - }, - }); - - assert.deepEqual(calls, ['message1']); - assert.deepEqual(output, 'Message 1'); - }); - - it('supports deferring an unused ES6 re-export (wildcard, empty, unused)', async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-re-exports-all-empty/a.js', - ), - ); - - assertDependencyWasDeferred(b, 'index.js', './empty.js'); - - assert.deepEqual(await run(b), 123); - }); - - it('supports deferring an unused ES6 re-exports (reexport named used)', async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-re-exports/b.js', - ), - ); - - assert(!findAsset(b, 'message1.js')); - assert(!findAsset(b, 'message3.js')); - - let calls = []; - let output = await run(b, { - sideEffect: caller => { - calls.push(caller); - }, - }); - - assert.deepEqual(calls, ['message2']); - assert.deepEqual(output, 'Message 2'); - }); - - it('supports deferring an unused ES6 re-exports (namespace rename used)', async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-re-exports/c.js', - ), - ); - - assert(!findAsset(b, 'message1.js')); - assertDependencyWasDeferred(b, 'index.js', './message2.js'); - - let calls = []; - let output = await run(b, { - sideEffect: caller => { - calls.push(caller); - }, - }); - - assert.deepEqual(calls, ['message3']); - assert.deepEqual(output, {default: 'Message 3'}); - }); - - it('supports deferring an unused ES6 re-exports (direct export used)', async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-re-exports/d.js', - ), - ); - - assert(!findAsset(b, 'message1.js')); - assertDependencyWasDeferred(b, 'index.js', './message2.js'); - assert(!findAsset(b, 'message13js')); - - let calls = []; - let output = await run(b, { - sideEffect: caller => { - calls.push(caller); - }, - }); - - assert.deepEqual(calls, ['index']); - assert.deepEqual(output, 'Message 4'); - }); - - it('supports chained ES6 re-exports', async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-re-exports-chained/index.js', - ), - ); - - assert(!findAsset(b, 'bar.js')); - - let calls = []; - let output = await run(b, { - sideEffect: caller => { - calls.push(caller); - }, - }); - - assert.deepEqual(calls, ['key', 'foo', 'index']); - assert.deepEqual(output, ['key', 'foo']); - }); - - it('should not optimize away an unused ES6 re-export and an used import', async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-re-exports-import/a.js', - ), - ); - - let output = await run(b); - assert.deepEqual(output, 123); - }); - - it('should not optimize away an unused ES6 re-export and an used import (different symbols)', async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-re-exports-import-different/a.js', - ), - ); - - let output = await run(b); - assert.deepEqual(output, 123); - }); - - it('correctly handles ES6 re-exports in library mode entries', async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-re-exports-library/a.js', - ), - ); - - let contents = await outputFS.readFile( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-re-exports-library/build.js', - ), - 'utf8', - ); - assert(!contents.includes('console.log')); - - let output = await run(b); - assert.deepEqual(output, {c1: 'foo'}); - }); - - it('correctly updates deferred assets that are reexported', async function () { - let testDir = path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-update-deferred-reexported', - ); - - let b = bundler(path.join(testDir, 'index.js'), { - inputFS: overlayFS, - outputFS: overlayFS, - }); - - let subscription = await b.watch(); - - let bundleEvent = await getNextBuild(b); - assert(bundleEvent.type === 'buildSuccess'); - let output = await run(bundleEvent.bundleGraph); - assert.deepEqual(output, '12345hello'); - - await overlayFS.mkdirp(path.join(testDir, 'node_modules', 'foo')); - await overlayFS.copyFile( - path.join(testDir, 'node_modules', 'foo', 'foo_updated.js'), - path.join(testDir, 'node_modules', 'foo', 'foo.js'), - ); - - bundleEvent = await getNextBuild(b); - assert(bundleEvent.type === 'buildSuccess'); - output = await run(bundleEvent.bundleGraph); - assert.deepEqual(output, '1234556789'); - - await subscription.unsubscribe(); - }); - - it('correctly updates deferred assets that are reexported and imported directly', async function () { - let testDir = path.join( + it('removes functions that increment variables in object properties', async function () { + let b = await bundle( + path.join( __dirname, - '/integration/scope-hoisting/es6/side-effects-update-deferred-direct', - ); - - let b = bundler(path.join(testDir, 'index.js'), { - inputFS: overlayFS, - outputFS: overlayFS, - }); - - let subscription = await b.watch(); - - let bundleEvent = await getNextBuild(b); - assert(bundleEvent.type === 'buildSuccess'); - let output = await run(bundleEvent.bundleGraph); - assert.deepEqual(output, '12345hello'); - - await overlayFS.mkdirp(path.join(testDir, 'node_modules', 'foo')); - await overlayFS.copyFile( - path.join(testDir, 'node_modules', 'foo', 'foo_updated.js'), - path.join(testDir, 'node_modules', 'foo', 'foo.js'), - ); - - bundleEvent = await getNextBuild(b); - assert(bundleEvent.type === 'buildSuccess'); - output = await run(bundleEvent.bundleGraph); - assert.deepEqual(output, '1234556789'); - - await subscription.unsubscribe(); - }); - - it('removes deferred reexports when imported from multiple asssets', async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-re-exports-multiple-dynamic/a.js', - ), - ); - - let contents = await outputFS.readFile( - b.getBundles()[0].filePath, - 'utf8', - ); - - assert(!contents.includes('$import$')); - assert(contents.includes('= 1234;')); - assert(!contents.includes('= 5678;')); - - let output = await run(b); - assert.deepEqual(output, [1234, {default: 1234}]); - }); - - it('keeps side effects by default', async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects/a.js', - ), - ); - - let called = false; - let output = await run(b, { - sideEffect: () => { - called = true; - }, - }); - - assert(called, 'side effect not called'); - assert.deepEqual(output, 4); - }); - - it('supports the package.json sideEffects: false flag', async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-false/a.js', - ), - ); - - let called = false; - let output = await run(b, { - sideEffect: () => { - called = true; + '/integration/scope-hoisting/es6/tree-shaking-increment-object/a.js', + ), + { + defaultTargetOptions: { + shouldOptimize: true, }, - }); - - assert(!called, 'side effect called'); - assert.deepEqual(output, 4); - }); - - it('supports removing a deferred dependency', async function () { - let testDir = path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-false', - ); - - let b = bundler(path.join(testDir, 'a.js'), { - inputFS: overlayFS, - outputFS: overlayFS, - }); - - let subscription = await b.watch(); - - try { - let bundleEvent = await getNextBuild(b); - assert.strictEqual(bundleEvent.type, 'buildSuccess'); - let called = false; - let output = await run(bundleEvent.bundleGraph, { - sideEffect: () => { - called = true; - }, - }); - assert(!called, 'side effect called'); - assert.deepEqual(output, 4); - assertDependencyWasDeferred( - bundleEvent.bundleGraph, - 'index.js', - './bar', - ); - - await overlayFS.mkdirp(path.join(testDir, 'node_modules/bar')); - await overlayFS.copyFile( - path.join(testDir, 'node_modules/bar/index.1.js'), - path.join(testDir, 'node_modules/bar/index.js'), - ); + }, + ); - bundleEvent = await getNextBuild(b); - assert.strictEqual(bundleEvent.type, 'buildSuccess'); - called = false; - output = await run(bundleEvent.bundleGraph, { - sideEffect: () => { - called = true; - }, - }); - assert(!called, 'side effect called'); - assert.deepEqual(output, 4); - } finally { - await subscription.unsubscribe(); - } - }); + let content = await outputFS.readFile(b.getBundles()[0].filePath, 'utf8'); + assert(!content.includes('++')); - it('supports wildcards', async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-false-wildcards/a.js', - ), - ); - let called = false; - let output = await run(b, { - sideEffect: () => { - called = true; - }, - }); - - assert(!called, 'side effect called'); - assert.deepEqual(output, 'bar'); - }); - - it('correctly handles excluded and wrapped reexport assets', async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-false-wrap-excluded/a.js', - ), - ); - - let output = await run(b); - assert.deepEqual(output, 4); - }); - - it('supports the package.json sideEffects flag with an array', async function () { - let b = await bundle( - path.join( - __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('supports the package.json sideEffects: false flag with shared dependencies', async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-false-duplicate/a.js', - ), - ); - - let called = false; - let output = await run(b, { - sideEffect: () => { - called = true; - }, - }); - - assert(!called, 'side effect called'); - assert.deepEqual(output, 6); - }); - - it('supports the package.json sideEffects: false flag with shared dependencies and code splitting', async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-split/a.js', - ), - ); - - assert.deepEqual(await run(b), 581); - }); - - it('supports the package.json sideEffects: false flag with shared dependencies and code splitting II', async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-split2/a.js', - ), - ); - - assert.deepEqual(await run(b), [{default: 123, foo: 2}, 581]); - }); - - it('missing exports should be replaced with an empty object', async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/empty-module/a.js', - ), - ); - - let output = await run(b); - assert.deepEqual(output, {b: {}}); - }); - - it('supports namespace imports of theoretically excluded reexporting assets', async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/import-namespace-sideEffects/index.js', - ), - ); - - let output = await run(b); - assert.deepEqual(output, {Main: 'main', a: 'foo', b: 'bar'}); - }); - - it('can import from a different bundle via a re-export', async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/re-export-bundle-boundary-side-effects/index.js', - ), - ); - let output = await run(b); - assert.deepEqual(output, ['operational', 'ui']); - }); - - it('supports excluding multiple chained namespace reexports', async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-chained-re-exports-multiple/a.js', - ), - ); - - assert(!findAsset(b, 'symbol1.js')); - - let calls = []; - let output = await run(b, { - sideEffect: caller => { - calls.push(caller); - }, - }); - - assert.deepEqual(calls, ['message1']); - assert.deepEqual(output, 'Message 1'); - }); - - it('supports excluding when doing both exports and reexports', async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-export-reexport/a.js', - ), - ); - - assert(!findAsset(b, 'other.js')); - - let calls = []; - let output = await run(b, { - sideEffect: caller => { - calls.push(caller); - }, - }); - - assert.deepEqual(calls, ['index']); - assert.deepEqual(output, 'Message 1'); - }); - - it('supports deferring with chained renaming reexports', async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-re-exports-rename-chained/a.js', - ), - ); - - // assertDependencyWasDeferred(b, 'message.js', './message2'); - - let calls = []; - let output = await run(b, { - sideEffect: caller => { - calls.push(caller); - }, - }); - - assert.deepEqual(calls, ['message1']); - assert.deepEqual(output, 'Message 1'); - }); - - it('supports named and renamed reexports of the same asset (default used)', async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-re-exports-rename-same2/a.js', - ), - ); - - assert.deepStrictEqual( - new Set(b.getUsedSymbols(nullthrows(findAsset(b, 'other.js')))), - new Set(['bar']), - ); - - let calls = []; - let output = await run(b, { - sideEffect: caller => { - calls.push(caller); - }, - }); - - assert.deepEqual(calls, ['other']); - assert.deepEqual(output, 'bar'); - }); - - it('supports named and renamed reexports of the same asset (named used)', async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-re-exports-rename-same2/b.js', - ), - ); - - assert.deepStrictEqual( - new Set(b.getUsedSymbols(nullthrows(findAsset(b, 'other.js')))), - new Set(['bar']), - ); - - let calls = []; - let output = await run(b, { - sideEffect: caller => { - calls.push(caller); - }, - }); - - assert.deepEqual(calls, ['other']); - assert.deepEqual(output, 'bar'); - }); - - it('removes functions that increment variables in object properties', async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/tree-shaking-increment-object/a.js', - ), - { - defaultTargetOptions: { - shouldOptimize: true, - }, - }, - ); - - let content = await outputFS.readFile( - b.getBundles()[0].filePath, - 'utf8', - ); - assert(!content.includes('++')); - - await run(b); - }); - - it('supports named and namespace exports of the same asset (named used)', async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-re-exports-namespace-same/a.js', - ), - ); - - assert.deepStrictEqual( - new Set(b.getUsedSymbols(nullthrows(findAsset(b, 'index.js')))), - new Set([]), - ); - assert.deepStrictEqual( - new Set(b.getUsedSymbols(nullthrows(findAsset(b, 'other.js')))), - new Set(['default']), - ); - - let calls = []; - let output = await run(b, { - sideEffect: caller => { - calls.push(caller); - }, - }); - - assert.deepEqual(calls, ['other']); - assert.deepEqual(output, ['foo']); - }); - - it('supports named and namespace exports of the same asset (namespace used)', async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-re-exports-namespace-same/b.js', - ), - ); - - assert.deepStrictEqual( - new Set(b.getUsedSymbols(nullthrows(findAsset(b, 'index.js')))), - new Set([]), - ); - assert.deepStrictEqual( - new Set(b.getUsedSymbols(nullthrows(findAsset(b, 'other.js')))), - new Set(['bar']), - ); - - let calls = []; - let output = await run(b, { - sideEffect: caller => { - calls.push(caller); - }, - }); - - assert.deepEqual(calls, ['other']); - assert.deepEqual(output, ['bar']); - }); - - it('supports named and namespace exports of the same asset (both used)', async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-re-exports-namespace-same/c.js', - ), - ); - - assert.deepStrictEqual( - new Set(b.getUsedSymbols(nullthrows(findAsset(b, 'index.js')))), - new Set([]), - ); - assert.deepStrictEqual( - new Set(b.getUsedSymbols(nullthrows(findAsset(b, 'other.js')))), - new Set(['default', 'bar']), - ); - - let calls = []; - let output = await run(b, { - sideEffect: caller => { - calls.push(caller); - }, - }); - - assert.deepEqual(calls, ['other']); - assert.deepEqual(output, ['foo', 'bar']); - }); - - it('supports deferring non-weak dependencies that are not used', async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-semi-weak/a.js', - ), - ); - - // assertDependencyWasDeferred(b, 'esm2.js', './other.js'); - - let calls = []; - let output = await run(b, { - sideEffect: caller => { - calls.push(caller); - }, - }); - - assert.deepEqual(calls, ['esm1']); - assert.deepEqual(output, 'Message 1'); - }); - - it('supports excluding CommonJS (CommonJS unused)', async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-commonjs/a.js', - ), - ); - - assert.deepStrictEqual( - new Set(b.getUsedSymbols(nullthrows(findAsset(b, 'esm.js')))), - new Set(['message1']), - ); - // We can't statically analyze commonjs.js, so message1 appears to be used - assert.deepStrictEqual( - new Set(b.getUsedSymbols(nullthrows(findAsset(b, 'commonjs.js')))), - // the exports object is used freely - new Set(['*', 'message1']), - ); - assert.deepStrictEqual( - new Set( - b.getUsedSymbols(findDependency(b, 'index.js', './commonjs.js')), - ), - new Set(['message1']), - ); - - let calls = []; - let output = await run(b, { - sideEffect: caller => { - calls.push(caller); - }, - }); - - assert.deepEqual(calls, ['esm', 'commonjs', 'index']); - assert.deepEqual(output, 'Message 1'); - }); - - it('supports excluding CommonJS (CommonJS used)', async function () { - let b = await bundle( - path.join( - __dirname, - '/integration/scope-hoisting/es6/side-effects-commonjs/b.js', - ), - ); - - assert(!findAsset(b, 'esm.js')); - assert.deepStrictEqual( - new Set(b.getUsedSymbols(nullthrows(findAsset(b, 'commonjs.js')))), - // the exports object is used freely - new Set(['*', 'message2']), - ); - assert.deepEqual( - new Set( - b.getUsedSymbols(findDependency(b, 'index.js', './commonjs.js')), - ), - new Set(['message2']), - ); - - let calls = []; - let output = await run(b, { - sideEffect: caller => { - calls.push(caller); - }, - }); - - assert.deepEqual(calls, ['commonjs']); - assert.deepEqual(output, 'Message 2'); - }); + await run(b); }); it('ignores missing import specifiers in source assets', async function () { diff --git a/packages/core/test-utils/src/utils.js b/packages/core/test-utils/src/utils.js index 1d988e85b8f..47e1fcca383 100644 --- a/packages/core/test-utils/src/utils.js +++ b/packages/core/test-utils/src/utils.js @@ -193,7 +193,7 @@ export function mergeParcelOptions( }; } -export function assertDependencyWasDeferred( +export function assertDependencyWasExcluded( bundleGraph: BundleGraph, assetFileName: string, specifier: string, diff --git a/packages/packagers/js/src/DevPackager.js b/packages/packagers/js/src/DevPackager.js index 9151e459ffc..1d5857220ba 100644 --- a/packages/packagers/js/src/DevPackager.js +++ b/packages/packagers/js/src/DevPackager.js @@ -98,7 +98,9 @@ export class DevPackager { let dependencies = this.bundleGraph.getDependencies(asset); for (let dep of dependencies) { let resolved = this.bundleGraph.getResolvedAsset(dep, this.bundle); - if (resolved) { + if (this.bundleGraph.isDependencySkipped(dep)) { + deps[getSpecifier(dep)] = false; + } else if (resolved) { deps[getSpecifier(dep)] = this.bundleGraph.getAssetPublicId(resolved); } diff --git a/packages/packagers/js/src/dev-prelude.js b/packages/packagers/js/src/dev-prelude.js index a8c68b36189..5636da413bd 100644 --- a/packages/packagers/js/src/dev-prelude.js +++ b/packages/packagers/js/src/dev-prelude.js @@ -80,11 +80,13 @@ return cache[name].exports; function localRequire(x) { - return newRequire(localRequire.resolve(x)); + var res = localRequire.resolve(x); + return res === false ? {} : newRequire(res); } function resolve(x) { - return modules[name][1][x] || x; + var id = modules[name][1][x]; + return id != null ? id : x; } } diff --git a/packages/transformers/js/core/src/hoist.rs b/packages/transformers/js/core/src/hoist.rs index d2a4abed5b1..8cd2f85e90b 100644 --- a/packages/transformers/js/core/src/hoist.rs +++ b/packages/transformers/js/core/src/hoist.rs @@ -7,18 +7,12 @@ use swc_common::{sync::Lrc, Mark, Span, SyntaxContext, DUMMY_SP}; use swc_ecmascript::ast::*; use swc_ecmascript::visit::{Fold, FoldWith, Node, Visit, VisitWith}; +use crate::id; use crate::utils::{ match_import, match_member_expr, match_require, Bailout, BailoutReason, CodeHighlight, - Diagnostic, DiagnosticSeverity, SourceLocation, + Diagnostic, DiagnosticSeverity, IdentId, SourceLocation, }; -type IdentId = (JsWord, SyntaxContext); -macro_rules! id { - ($ident: expr) => { - ($ident.sym.clone(), $ident.span.ctxt) - }; -} - macro_rules! hash { ($str:expr) => {{ let mut hasher = DefaultHasher::new(); @@ -29,28 +23,16 @@ macro_rules! hash { pub fn hoist( module: Module, - source_map: Lrc, module_id: &str, - decls: HashSet, - ignore_mark: Mark, - global_mark: Mark, - trace_bailouts: bool, + collect: &Collect, ) -> Result<(Module, HoistResult, Vec), Vec> { - let mut collect = Collect::new(source_map, decls, ignore_mark, global_mark, trace_bailouts); - module.visit_with(&Invalid { span: DUMMY_SP } as _, &mut collect); - - let mut hoist = Hoist::new(module_id, &collect); + let mut hoist = Hoist::new(module_id, collect); let module = module.fold_with(&mut hoist); + if !hoist.diagnostics.is_empty() { return Err(hoist.diagnostics); } - if let Some(bailouts) = &collect.bailouts { - hoist - .diagnostics - .extend(bailouts.iter().map(|bailout| bailout.to_diagnostic())); - } - let diagnostics = std::mem::take(&mut hoist.diagnostics); Ok((module, hoist.get_result(), diagnostics)) } @@ -277,7 +259,10 @@ impl<'a> Fold for Hoist<'a> { id.0 } else { self - .get_export_ident(DUMMY_SP, self.collect.exports.get(&id).unwrap()) + .get_export_ident( + DUMMY_SP, + self.collect.exports_locals.get(&id.0).unwrap(), + ) .sym }; self.exported_symbols.push(ExportedSymbol { @@ -823,7 +808,7 @@ impl<'a> Fold for Hoist<'a> { } } - if let Some(exported) = self.collect.exports.get(&id!(node)) { + if let Some(exported) = self.collect.exports_locals.get(&node.sym) { // If wrapped, mark the original symbol as exported. // Otherwise replace with an export identifier. if self.collect.should_wrap { @@ -1126,13 +1111,14 @@ macro_rules! collect_visit_fn { }; } -#[derive(PartialEq, Clone, Copy)] +#[derive(Debug, PartialEq, Clone, Copy, Serialize)] pub enum ImportKind { Require, Import, DynamicImport, } +#[derive(Debug)] pub struct Import { pub source: JsWord, pub specifier: JsWord, @@ -1140,27 +1126,69 @@ pub struct Import { pub loc: SourceLocation, } +#[derive(Debug)] +pub struct Export { + pub source: Option, + pub specifier: JsWord, + pub loc: SourceLocation, +} + pub struct Collect { pub source_map: Lrc, pub decls: HashSet, - ignore_mark: Mark, - global_ctxt: SyntaxContext, - static_cjs_exports: bool, - has_cjs_exports: bool, - is_esm: bool, - should_wrap: bool, + pub ignore_mark: Mark, + pub global_ctxt: SyntaxContext, + pub static_cjs_exports: bool, + pub has_cjs_exports: bool, + pub is_esm: bool, + pub should_wrap: bool, + // local name -> descriptor pub imports: HashMap, - exports: HashMap, - non_static_access: HashMap>, - non_const_bindings: HashMap>, - non_static_requires: HashSet, - wrapped_requires: HashSet, + // exported name -> descriptor + pub exports: HashMap, + // local name -> exported name + pub exports_locals: HashMap, + pub exports_all: HashMap, + pub non_static_access: HashMap>, + pub non_const_bindings: HashMap>, + pub non_static_requires: HashSet, + pub wrapped_requires: HashSet, + pub bailouts: Option>, in_module_this: bool, in_top_level: bool, in_export_decl: bool, in_function: bool, in_assign: bool, - bailouts: Option>, +} + +#[derive(Debug, Serialize)] +struct CollectImportedSymbol { + source: JsWord, + local: JsWord, + imported: JsWord, + loc: SourceLocation, + kind: ImportKind, +} + +#[derive(Debug, Serialize)] +struct CollectExportedSymbol { + source: Option, + local: JsWord, + exported: JsWord, + loc: SourceLocation, +} + +#[derive(Debug, Serialize)] +struct CollectExportedAll { + source: JsWord, + loc: SourceLocation, +} + +#[derive(Serialize, Debug)] +pub struct CollectResult { + imports: Vec, + exports: Vec, + exports_all: Vec, } impl Collect { @@ -1182,6 +1210,8 @@ impl Collect { should_wrap: false, imports: HashMap::new(), exports: HashMap::new(), + exports_locals: HashMap::new(), + exports_all: HashMap::new(), non_static_access: HashMap::new(), non_const_bindings: HashMap::new(), non_static_requires: HashSet::new(), @@ -1196,6 +1226,58 @@ impl Collect { } } +impl From for CollectResult { + fn from(collect: Collect) -> CollectResult { + CollectResult { + imports: collect + .imports + .into_iter() + .map( + |( + local, + Import { + source, + specifier, + loc, + kind, + }, + )| CollectImportedSymbol { + source, + local: local.0, + imported: specifier, + loc, + kind, + }, + ) + .collect(), + exports: collect + .exports + .into_iter() + .map( + |( + exported, + Export { + source, + specifier, + loc, + }, + )| CollectExportedSymbol { + source, + local: specifier, + exported, + loc, + }, + ) + .collect(), + exports_all: collect + .exports_all + .into_iter() + .map(|(source, loc)| CollectExportedAll { source, loc }) + .collect(), + } + } +} + impl Visit for Collect { fn visit_module(&mut self, node: &Module, _parent: &dyn Node) { self.in_module_this = true; @@ -1308,30 +1390,52 @@ impl Visit for Collect { } fn visit_named_export(&mut self, node: &NamedExport, _parent: &dyn Node) { - if node.src.is_some() { - return; - } - for specifier in &node.specifiers { + let source = node.src.as_ref().map(|s| s.value.clone()); match specifier { ExportSpecifier::Named(named) => { let exported = match &named.exported { - Some(exported) => exported.sym.clone(), - None => named.orig.sym.clone(), + Some(exported) => exported.clone(), + None => named.orig.clone(), }; - self.exports.entry(id!(named.orig)).or_insert(exported); + self.exports.insert( + exported.sym.clone(), + Export { + specifier: named.orig.sym.clone(), + loc: SourceLocation::from(&self.source_map, exported.span), + source, + }, + ); + self + .exports_locals + .entry(named.orig.sym.clone()) + .or_insert_with(|| exported.sym.clone()); } ExportSpecifier::Default(default) => { + self.exports.insert( + js_word!("default"), + Export { + specifier: default.exported.sym.clone(), + loc: SourceLocation::from(&self.source_map, default.exported.span), + source, + }, + ); self - .exports - .entry(id!(default.exported)) - .or_insert(js_word!("default")); + .exports_locals + .entry(default.exported.sym.clone()) + .or_insert_with(|| js_word!("default")); } ExportSpecifier::Namespace(namespace) => { - self - .exports - .entry(id!(namespace.name)) - .or_insert_with(|| "*".into()); + self.exports.insert( + namespace.name.sym.clone(), + Export { + specifier: "*".into(), + loc: SourceLocation::from(&self.source_map, namespace.span), + source, + }, + ); + // Populating exports_locals with * doesn't make any sense at all + // and hoist doesn't use this anyway. } } } @@ -1340,12 +1444,32 @@ impl Visit for Collect { fn visit_export_decl(&mut self, node: &ExportDecl, _parent: &dyn Node) { match &node.decl { Decl::Class(class) => { + self.exports.insert( + class.ident.sym.clone(), + Export { + specifier: class.ident.sym.clone(), + loc: SourceLocation::from(&self.source_map, class.ident.span), + source: None, + }, + ); self - .exports - .insert(id!(class.ident), class.ident.sym.clone()); + .exports_locals + .entry(class.ident.sym.clone()) + .or_insert_with(|| class.ident.sym.clone()); } Decl::Fn(func) => { - self.exports.insert(id!(func.ident), func.ident.sym.clone()); + self.exports.insert( + func.ident.sym.clone(), + Export { + specifier: func.ident.sym.clone(), + loc: SourceLocation::from(&self.source_map, func.ident.span), + source: None, + }, + ); + self + .exports_locals + .entry(func.ident.sym.clone()) + .or_insert_with(|| func.ident.sym.clone()); } Decl::Var(var) => { for decl in &var.decls { @@ -1366,12 +1490,34 @@ impl Visit for Collect { match &node.decl { DefaultDecl::Class(class) => { if let Some(ident) = &class.ident { - self.exports.insert(id!(ident), "default".into()); + self.exports.insert( + "default".into(), + Export { + specifier: ident.sym.clone(), + loc: SourceLocation::from(&self.source_map, node.span), + source: None, + }, + ); + self + .exports_locals + .entry(ident.sym.clone()) + .or_insert_with(|| "default".into()); } } DefaultDecl::Fn(func) => { if let Some(ident) = &func.ident { - self.exports.insert(id!(ident), "default".into()); + self.exports.insert( + "default".into(), + Export { + specifier: ident.sym.clone(), + loc: SourceLocation::from(&self.source_map, node.span), + source: None, + }, + ); + self + .exports_locals + .entry(ident.sym.clone()) + .or_insert_with(|| "default".into()); } } _ => { @@ -1382,6 +1528,13 @@ impl Visit for Collect { node.visit_children_with(self); } + fn visit_export_all(&mut self, node: &ExportAll, _parent: &dyn Node) { + self.exports_all.insert( + node.src.value.clone(), + SourceLocation::from(&self.source_map, node.span), + ); + } + fn visit_return_stmt(&mut self, node: &ReturnStmt, _parent: &dyn Node) { if !self.in_function { self.should_wrap = true; @@ -1393,7 +1546,18 @@ impl Visit for Collect { fn visit_binding_ident(&mut self, node: &BindingIdent, _parent: &dyn Node) { if self.in_export_decl { - self.exports.insert(id!(node.id), node.id.sym.clone()); + self.exports.insert( + node.id.sym.clone(), + Export { + specifier: node.id.sym.clone(), + loc: SourceLocation::from(&self.source_map, node.id.span), + source: None, + }, + ); + self + .exports_locals + .entry(node.id.sym.clone()) + .or_insert_with(|| node.id.sym.clone()); } if self.in_assign && node.id.span.ctxt() == self.global_ctxt { @@ -1407,7 +1571,18 @@ impl Visit for Collect { fn visit_assign_pat_prop(&mut self, node: &AssignPatProp, _parent: &dyn Node) { if self.in_export_decl { - self.exports.insert(id!(node.key), node.key.sym.clone()); + self.exports.insert( + node.key.sym.clone(), + Export { + specifier: node.key.sym.clone(), + loc: SourceLocation::from(&self.source_map, node.key.span), + source: None, + }, + ); + self + .exports_locals + .entry(node.key.sym.clone()) + .or_insert_with(|| node.key.sym.clone()); } if self.in_assign && node.key.span.ctxt() == self.global_ctxt { diff --git a/packages/transformers/js/core/src/lib.rs b/packages/transformers/js/core/src/lib.rs index 09a0a472650..b9a3f51e9a1 100644 --- a/packages/transformers/js/core/src/lib.rs +++ b/packages/transformers/js/core/src/lib.rs @@ -29,9 +29,10 @@ use path_slash::PathExt; use serde::{Deserialize, Serialize}; use swc_common::comments::SingleThreadedComments; use swc_common::errors::{DiagnosticBuilder, Emitter, Handler}; +use swc_common::DUMMY_SP; use swc_common::{chain, sync::Lrc, FileName, Globals, Mark, SourceMap}; use swc_ecma_preset_env::{preset_env, Mode::Entry, Targets, Version, Versions}; -use swc_ecmascript::ast::Module; +use swc_ecmascript::ast::{Invalid, Module}; use swc_ecmascript::codegen::text_writer::JsWriter; use swc_ecmascript::parser::lexer::Lexer; use swc_ecmascript::parser::{EsConfig, PResult, Parser, StringInput, Syntax, TsConfig}; @@ -41,17 +42,19 @@ use swc_ecmascript::transforms::{ optimization::simplify::dead_branch_remover, optimization::simplify::expr_simplifier, pass::Optional, proposals::decorators, react, typescript, }; -use swc_ecmascript::visit::FoldWith; +use swc_ecmascript::visit::{FoldWith, VisitWith}; use decl_collector::*; use dependency_collector::*; use env_replacer::*; use fs::inline_fs; use global_replacer::GlobalReplacer; -use hoist::hoist; +use hoist::{hoist, CollectResult, HoistResult}; use modules::esm2cjs; use utils::{CodeHighlight, Diagnostic, DiagnosticSeverity, SourceLocation, SourceType}; +use crate::hoist::Collect; + type SourceMapBuffer = Vec<(swc_common::BytePos, swc_common::LineCol)>; #[derive(Serialize, Debug, Deserialize)] @@ -86,14 +89,15 @@ pub struct Config { trace_bailouts: bool, } -#[derive(Serialize, Debug, Deserialize, Default)] +#[derive(Serialize, Debug, Default)] pub struct TransformResult { #[serde(with = "serde_bytes")] code: Vec, map: Option, shebang: Option, dependencies: Vec, - hoist_result: Option, + hoist_result: Option, + symbol_result: Option, diagnostics: Option>, needs_esm_helpers: bool, used_env: HashSet, @@ -392,16 +396,20 @@ pub fn transform(config: Config) -> Result { return Ok(result); } + let mut collect = Collect::new( + source_map.clone(), + decls, + ignore_mark, + global_mark, + config.trace_bailouts, + ); + module.visit_with(&Invalid { span: DUMMY_SP } as _, &mut collect); + if let Some(bailouts) = &collect.bailouts { + diagnostics.extend(bailouts.iter().map(|bailout| bailout.to_diagnostic())); + } + let module = if config.scope_hoist { - let res = hoist( - module, - source_map.clone(), - config.module_id.as_str(), - decls, - ignore_mark, - global_mark, - config.trace_bailouts, - ); + let res = hoist(module, config.module_id.as_str(), &collect); match res { Ok((module, hoist_result, hoist_diagnostics)) => { result.hoist_result = Some(hoist_result); @@ -414,6 +422,8 @@ pub fn transform(config: Config) -> Result { } } } else { + result.symbol_result = Some(collect.into()); + let (module, needs_helpers) = esm2cjs(module, versions); result.needs_esm_helpers = needs_helpers; module diff --git a/packages/transformers/js/core/src/utils.rs b/packages/transformers/js/core/src/utils.rs index c2ec6dac2ae..83e2758c9ce 100644 --- a/packages/transformers/js/core/src/utils.rs +++ b/packages/transformers/js/core/src/utils.rs @@ -327,3 +327,12 @@ macro_rules! fold_member_expr_skip_prop { } }; } + +#[macro_export] +macro_rules! id { + ($ident: expr) => { + ($ident.sym.clone(), $ident.span.ctxt) + }; +} + +pub type IdentId = (JsWord, SyntaxContext); diff --git a/packages/transformers/js/src/JSTransformer.js b/packages/transformers/js/src/JSTransformer.js index cd16019b0e2..08a758971c0 100644 --- a/packages/transformers/js/src/JSTransformer.js +++ b/packages/transformers/js/src/JSTransformer.js @@ -367,6 +367,7 @@ export default (new Transformer({ map, shebang, hoist_result, + symbol_result, needs_esm_helpers, diagnostics, used_env, @@ -768,17 +769,60 @@ export default (new Transformer({ asset.meta.hasCJSExports = hoist_result.has_cjs_exports; asset.meta.staticExports = hoist_result.static_cjs_exports; asset.meta.shouldWrap = hoist_result.should_wrap; - } else if (needs_esm_helpers) { - asset.addDependency({ - specifier: '@parcel/transformer-js/src/esmodule-helpers.js', - specifierType: 'esm', - resolveFrom: __filename, - env: { - includeNodeModules: { - '@parcel/transformer-js': true, + } else { + if (symbol_result) { + let deps = new Map( + asset + .getDependencies() + .map(dep => [dep.meta.placeholder ?? dep.specifier, dep]), + ); + asset.symbols.ensure(); + + for (let {exported, local, loc, source} of symbol_result.exports) { + let dep = source ? deps.get(source) : undefined; + asset.symbols.set( + exported, + `${dep?.id ?? ''}$${local}`, + convertLoc(loc), + ); + if (dep != null) { + dep.symbols.ensure(); + dep.symbols.set( + local, + `${dep?.id ?? ''}$${local}`, + convertLoc(loc), + true, + ); + } + } + + for (let {source, local, imported, loc} of symbol_result.imports) { + let dep = deps.get(source); + if (!dep) continue; + dep.symbols.ensure(); + dep.symbols.set(imported, local, convertLoc(loc)); + } + + for (let {source, loc} of symbol_result.exports_all) { + let dep = deps.get(source); + if (!dep) continue; + dep.symbols.ensure(); + dep.symbols.set('*', '*', convertLoc(loc), true); + } + } + + if (needs_esm_helpers) { + asset.addDependency({ + specifier: '@parcel/transformer-js/src/esmodule-helpers.js', + specifierType: 'esm', + resolveFrom: __filename, + env: { + includeNodeModules: { + '@parcel/transformer-js': true, + }, }, - }, - }); + }); + } } asset.type = 'js'; diff --git a/yarn.lock b/yarn.lock index 2014826e0bd..aed541c005d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6134,10 +6134,10 @@ flatted@^3.1.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.1.1.tgz#c4b489e80096d9df1dfc97c79871aea7c617c469" integrity sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA== -flow-bin@0.161.0: - version "0.161.0" - resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.161.0.tgz#1c03ea4a9e3036a8bc639f050bd8dc6f5288e8bb" - integrity sha512-5BKQi+sjOXz67Kbc1teiBwd5e0Da6suW7S5oUCm9VclnzSZ3nlfNZUdrkvgJ5S4w5KDBDToEL7rREpn8rKF1zg== +flow-bin@0.164.0: + version "0.164.0" + resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.164.0.tgz#dc2a1f1cd59670e7d95320232a673c6e31b11ff3" + integrity sha512-cSAA0LLa1SlQ1YmNCYpJ19ES39WYrqjfxX8Oqhbvn6+DET8Cs+EnVkJWHVSkfK8zUupbxvSt7pDoFXePYbcJRA== flush-write-stream@^1.0.0, flush-write-stream@^1.0.2: version "1.1.1"