From 0ac4e297adf95cff78de361fc4867fd412ec3b60 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Thu, 14 Jun 2018 10:12:48 +0200 Subject: [PATCH] Scope hoisting for ES6 and CommonJS modules (#1135) --- package.json | 3 +- src/Bundler.js | 33 +- src/FSCache.js | 2 +- src/Parser.js | 4 +- src/Pipeline.js | 15 +- src/SourceMap.js | 79 +- src/assets/CSSAsset.js | 2 +- src/assets/GLSLAsset.js | 4 +- src/assets/GlobAsset.js | 9 +- src/assets/GraphqlAsset.js | 4 +- src/assets/JSAsset.js | 36 +- src/assets/JSONAsset.js | 6 +- src/assets/RawAsset.js | 9 +- src/assets/TOMLAsset.js | 7 +- src/assets/VueAsset.js | 33 +- src/assets/YAMLAsset.js | 7 +- src/builtins/.eslintrc.json | 5 +- src/builtins/bundle-loader.js | 16 +- src/builtins/helpers.js | 30 + src/builtins/prelude.js | 5 + src/builtins/prelude2.js | 45 ++ src/cli.js | 5 + src/packagers/JSConcatPackager.js | 526 ++++++++++++ src/packagers/JSPackager.js | 5 +- src/packagers/SourceMapPackager.js | 1 + src/packagers/index.js | 5 +- src/scope-hoisting/concat.js | 394 +++++++++ src/scope-hoisting/hoist.js | 570 +++++++++++++ src/scope-hoisting/mangler.js | 69 ++ src/scope-hoisting/renamer.js | 33 + src/scope-hoisting/shake.js | 126 +++ src/transforms/terser.js | 2 +- src/visitors/globals.js | 3 +- src/worker.js | 4 +- test/fs.js | 6 +- test/integration/circular/about.js | 4 + test/integration/circular/index.js | 4 + .../commonjs/default-import/a.js | 3 + .../commonjs/default-import/b.js | 1 + .../commonjs/define-exports/a.js | 7 + .../commonjs/eliminate-exports/a.js | 4 + .../commonjs/eliminate-exports/b.js | 6 + .../scope-hoisting/commonjs/empty-module/a.js | 3 + .../scope-hoisting/commonjs/empty-module/b.js | 0 .../commonjs/es6-commonjs-hybrid/a.js | 3 + .../commonjs/es6-commonjs-hybrid/b.js | 2 + .../scope-hoisting/commonjs/export-order/a.js | 3 + .../scope-hoisting/commonjs/export-order/b.js | 2 + .../commonjs/import-namespace/a.js | 3 + .../commonjs/import-namespace/b.js | 1 + .../commonjs/live-bindings/a.js | 5 + .../commonjs/live-bindings/b.js | 7 + .../commonjs/module-object/a.js | 8 + .../commonjs/module-object/package.json | 3 + .../scope-hoisting/commonjs/multi-assign/a.js | 2 + .../scope-hoisting/commonjs/named-import/a.js | 3 + .../scope-hoisting/commonjs/named-import/b.js | 1 + .../commonjs/object-pattern/a.js | 2 + .../commonjs/object-pattern/b.js | 2 + .../commonjs/object-pattern/package.json | 4 + .../commonjs/re-export-var/a.js | 3 + .../commonjs/re-export-var/b.js | 3 + .../commonjs/re-export-var/c.js | 7 + .../commonjs/require-circular/a.js | 2 + .../commonjs/require-circular/b.js | 1 + .../commonjs/require-conditional/a.js | 7 + .../commonjs/require-conditional/b.js | 2 + .../commonjs/require-conditional/c.js | 1 + .../require-default-export-declaration/a.js | 3 + .../require-default-export-declaration/b.js | 3 + .../require-default-export-expression/a.js | 3 + .../require-default-export-expression/b.js | 1 + .../require-default-export-variable/a.js | 3 + .../require-default-export-variable/b.js | 2 + .../commonjs/require-execution-order/a.js | 3 + .../commonjs/require-execution-order/b.js | 2 + .../commonjs/require-execution-order/c.js | 1 + .../require-in-function-import-hoist/a.js | 8 + .../require-in-function-import-hoist/b.js | 2 + .../require-in-function-import-hoist/c.js | 1 + .../commonjs/require-in-function-import/a.js | 8 + .../commonjs/require-in-function-import/b.js | 2 + .../commonjs/require-in-function-import/c.js | 1 + .../require-in-function-reexport/a.js | 8 + .../require-in-function-reexport/b.js | 2 + .../require-in-function-reexport/c.js | 2 + .../commonjs/require-in-function/a.js | 8 + .../commonjs/require-in-function/b.js | 2 + .../require-named-export-declaration/a.js | 3 + .../require-named-export-declaration/b.js | 1 + .../require-named-export-variable/a.js | 3 + .../require-named-export-variable/b.js | 2 + .../commonjs/require-re-export-all/a.js | 3 + .../commonjs/require-re-export-all/b.js | 2 + .../commonjs/require-re-export-all/c.js | 1 + .../commonjs/require-re-export-default/a.js | 3 + .../commonjs/require-re-export-default/b.js | 2 + .../commonjs/require-re-export-default/c.js | 1 + .../require-re-export-exclude-default/a.js | 1 + .../require-re-export-exclude-default/b.js | 1 + .../require-re-export-exclude-default/c.js | 2 + .../commonjs/require-re-export-multiple/a.js | 3 + .../commonjs/require-re-export-multiple/b.js | 2 + .../commonjs/require-re-export-multiple/c.js | 2 + .../commonjs/require-re-export-multiple/d.js | 1 + .../commonjs/require-re-export-named/a.js | 3 + .../commonjs/require-re-export-named/b.js | 2 + .../commonjs/require-re-export-named/c.js | 1 + .../commonjs/require-re-export-namespace/a.js | 3 + .../commonjs/require-re-export-namespace/b.js | 4 + .../commonjs/require-re-export-namespace/c.js | 1 + .../commonjs/require-renamed-export/a.js | 3 + .../commonjs/require-renamed-export/b.js | 2 + .../commonjs/require-resolve/a.js | 3 + .../commonjs/require-resolve/b.js | 1 + .../scope-hoisting/commonjs/require/a.js | 3 + .../scope-hoisting/commonjs/require/b.js | 1 + .../commonjs/stream-module/a.js | 1 + .../commonjs/this-reference-wrapped/a.js | 3 + .../commonjs/this-reference-wrapped/b.js | 2 + .../commonjs/this-reference/a.js | 3 + .../commonjs/this-reference/b.js | 1 + .../scope-hoisting/commonjs/tree-shaking/a.js | 1 + .../scope-hoisting/commonjs/tree-shaking/b.js | 2 + .../scope-hoisting/commonjs/wrap-eval/a.js | 3 + .../scope-hoisting/commonjs/wrap-eval/b.js | 2 + .../commonjs/wrap-module-computed/a.js | 2 + .../scope-hoisting/commonjs/wrap-module/a.js | 2 + .../scope-hoisting/commonjs/wrap-return/a.js | 3 + .../scope-hoisting/commonjs/wrap-return/b.js | 3 + .../es6/default-export-anonymous/a.js | 3 + .../es6/default-export-anonymous/b.js | 3 + .../es6/default-export-declaration/a.js | 3 + .../es6/default-export-declaration/b.js | 3 + .../es6/default-export-expression/a.js | 3 + .../es6/default-export-expression/b.js | 1 + .../es6/default-export-variable/a.js | 3 + .../es6/default-export-variable/b.js | 2 + .../es6/dynamic-default-interop/a.js | 3 + .../es6/dynamic-default-interop/b.js | 3 + .../es6/dynamic-default-interop/shared.js | 1 + .../scope-hoisting/es6/dynamic-import/a.js | 7 + .../scope-hoisting/es6/dynamic-import/b.js | 4 + .../scope-hoisting/es6/dynamic-import/c.js | 3 + .../scope-hoisting/es6/empty-module/a.js | 3 + .../scope-hoisting/es6/empty-module/b.js | 0 .../es6/export-binding-identifiers/a.js | 3 + .../es6/export-binding-identifiers/b.js | 3 + .../es6/import-commonjs-default/a.js | 4 + .../es6/import-commonjs-default/exports.js | 1 + .../es6/import-commonjs-default/wrapped.js | 3 + .../es6/import-namespace-commonjs/a.js | 6 + .../es6/import-namespace-commonjs/b.js | 3 + .../es6/import-namespace-commonjs/c.js | 1 + .../scope-hoisting/es6/import-namespace/a.js | 3 + .../scope-hoisting/es6/import-namespace/b.js | 1 + .../scope-hoisting/es6/live-bindings/a.js | 5 + .../scope-hoisting/es6/live-bindings/b.js | 7 + .../scope-hoisting/es6/multi-export/a.js | 3 + .../scope-hoisting/es6/multi-export/b.js | 3 + .../es6/named-export-declaration/a.js | 3 + .../es6/named-export-declaration/b.js | 1 + .../es6/named-export-variable/a.js | 3 + .../es6/named-export-variable/b.js | 2 + .../es6/re-export-all-multiple/a.js | 3 + .../es6/re-export-all-multiple/b.js | 3 + .../es6/re-export-all-multiple/c.js | 1 + .../es6/re-export-all-multiple/d.js | 1 + .../scope-hoisting/es6/re-export-all/a.js | 3 + .../scope-hoisting/es6/re-export-all/b.js | 2 + .../scope-hoisting/es6/re-export-all/c.js | 1 + .../scope-hoisting/es6/re-export-default/a.js | 3 + .../scope-hoisting/es6/re-export-default/b.js | 2 + .../scope-hoisting/es6/re-export-default/c.js | 1 + .../es6/re-export-exclude-default/a.js | 3 + .../es6/re-export-exclude-default/b.js | 1 + .../es6/re-export-exclude-default/c.js | 2 + .../es6/re-export-multiple/a.js | 3 + .../es6/re-export-multiple/b.js | 2 + .../es6/re-export-multiple/c.js | 2 + .../es6/re-export-multiple/d.js | 1 + .../scope-hoisting/es6/re-export-named/a.js | 3 + .../scope-hoisting/es6/re-export-named/b.js | 2 + .../scope-hoisting/es6/re-export-named/c.js | 1 + .../es6/re-export-namespace/a.js | 3 + .../es6/re-export-namespace/b.js | 2 + .../es6/re-export-namespace/c.js | 1 + .../scope-hoisting/es6/re-export-var/a.js | 3 + .../scope-hoisting/es6/re-export-var/b.js | 3 + .../scope-hoisting/es6/re-export-var/c.js | 7 + .../scope-hoisting/es6/renamed-export/a.js | 3 + .../scope-hoisting/es6/renamed-export/b.js | 2 + .../scope-hoisting/es6/renamed-import/a.js | 3 + .../scope-hoisting/es6/renamed-import/b.js | 1 + .../es6/side-effects-false/a.js | 3 + .../node_modules/bar/bar.js | 5 + .../node_modules/bar/foo.js | 3 + .../node_modules/bar/index.js | 2 + .../node_modules/bar/package.json | 4 + .../scope-hoisting/es6/side-effects/a.js | 3 + .../es6/side-effects/node_modules/bar/bar.js | 5 + .../es6/side-effects/node_modules/bar/foo.js | 3 + .../side-effects/node_modules/bar/index.js | 2 + .../node_modules/bar/package.json | 3 + .../scope-hoisting/es6/tree-shaking/a.js | 2 + .../scope-hoisting/es6/tree-shaking/b.js | 2 + test/integration/sourcemap-reference/data.js | 6 + .../integration/sourcemap-reference/data.json | 6 - test/integration/sourcemap-reference/index.js | 3 +- test/javascript.js | 59 +- test/scope-hoisting.js | 761 ++++++++++++++++++ test/sourcemaps.js | 2 +- test/utils.js | 13 +- test/watcher.js | 8 +- 214 files changed, 3299 insertions(+), 129 deletions(-) create mode 100644 src/builtins/helpers.js create mode 100644 src/builtins/prelude2.js create mode 100644 src/packagers/JSConcatPackager.js create mode 100644 src/scope-hoisting/concat.js create mode 100644 src/scope-hoisting/hoist.js create mode 100644 src/scope-hoisting/mangler.js create mode 100644 src/scope-hoisting/renamer.js create mode 100644 src/scope-hoisting/shake.js create mode 100644 test/integration/scope-hoisting/commonjs/default-import/a.js create mode 100644 test/integration/scope-hoisting/commonjs/default-import/b.js create mode 100644 test/integration/scope-hoisting/commonjs/define-exports/a.js create mode 100644 test/integration/scope-hoisting/commonjs/eliminate-exports/a.js create mode 100644 test/integration/scope-hoisting/commonjs/eliminate-exports/b.js create mode 100644 test/integration/scope-hoisting/commonjs/empty-module/a.js create mode 100644 test/integration/scope-hoisting/commonjs/empty-module/b.js create mode 100644 test/integration/scope-hoisting/commonjs/es6-commonjs-hybrid/a.js create mode 100644 test/integration/scope-hoisting/commonjs/es6-commonjs-hybrid/b.js create mode 100644 test/integration/scope-hoisting/commonjs/export-order/a.js create mode 100644 test/integration/scope-hoisting/commonjs/export-order/b.js create mode 100644 test/integration/scope-hoisting/commonjs/import-namespace/a.js create mode 100644 test/integration/scope-hoisting/commonjs/import-namespace/b.js create mode 100644 test/integration/scope-hoisting/commonjs/live-bindings/a.js create mode 100644 test/integration/scope-hoisting/commonjs/live-bindings/b.js create mode 100644 test/integration/scope-hoisting/commonjs/module-object/a.js create mode 100644 test/integration/scope-hoisting/commonjs/module-object/package.json create mode 100644 test/integration/scope-hoisting/commonjs/multi-assign/a.js create mode 100644 test/integration/scope-hoisting/commonjs/named-import/a.js create mode 100644 test/integration/scope-hoisting/commonjs/named-import/b.js create mode 100644 test/integration/scope-hoisting/commonjs/object-pattern/a.js create mode 100644 test/integration/scope-hoisting/commonjs/object-pattern/b.js create mode 100644 test/integration/scope-hoisting/commonjs/object-pattern/package.json create mode 100644 test/integration/scope-hoisting/commonjs/re-export-var/a.js create mode 100644 test/integration/scope-hoisting/commonjs/re-export-var/b.js create mode 100644 test/integration/scope-hoisting/commonjs/re-export-var/c.js create mode 100644 test/integration/scope-hoisting/commonjs/require-circular/a.js create mode 100644 test/integration/scope-hoisting/commonjs/require-circular/b.js create mode 100644 test/integration/scope-hoisting/commonjs/require-conditional/a.js create mode 100644 test/integration/scope-hoisting/commonjs/require-conditional/b.js create mode 100644 test/integration/scope-hoisting/commonjs/require-conditional/c.js create mode 100644 test/integration/scope-hoisting/commonjs/require-default-export-declaration/a.js create mode 100644 test/integration/scope-hoisting/commonjs/require-default-export-declaration/b.js create mode 100644 test/integration/scope-hoisting/commonjs/require-default-export-expression/a.js create mode 100644 test/integration/scope-hoisting/commonjs/require-default-export-expression/b.js create mode 100644 test/integration/scope-hoisting/commonjs/require-default-export-variable/a.js create mode 100644 test/integration/scope-hoisting/commonjs/require-default-export-variable/b.js create mode 100644 test/integration/scope-hoisting/commonjs/require-execution-order/a.js create mode 100644 test/integration/scope-hoisting/commonjs/require-execution-order/b.js create mode 100644 test/integration/scope-hoisting/commonjs/require-execution-order/c.js create mode 100644 test/integration/scope-hoisting/commonjs/require-in-function-import-hoist/a.js create mode 100644 test/integration/scope-hoisting/commonjs/require-in-function-import-hoist/b.js create mode 100644 test/integration/scope-hoisting/commonjs/require-in-function-import-hoist/c.js create mode 100644 test/integration/scope-hoisting/commonjs/require-in-function-import/a.js create mode 100644 test/integration/scope-hoisting/commonjs/require-in-function-import/b.js create mode 100644 test/integration/scope-hoisting/commonjs/require-in-function-import/c.js create mode 100644 test/integration/scope-hoisting/commonjs/require-in-function-reexport/a.js create mode 100644 test/integration/scope-hoisting/commonjs/require-in-function-reexport/b.js create mode 100644 test/integration/scope-hoisting/commonjs/require-in-function-reexport/c.js create mode 100644 test/integration/scope-hoisting/commonjs/require-in-function/a.js create mode 100644 test/integration/scope-hoisting/commonjs/require-in-function/b.js create mode 100644 test/integration/scope-hoisting/commonjs/require-named-export-declaration/a.js create mode 100644 test/integration/scope-hoisting/commonjs/require-named-export-declaration/b.js create mode 100644 test/integration/scope-hoisting/commonjs/require-named-export-variable/a.js create mode 100644 test/integration/scope-hoisting/commonjs/require-named-export-variable/b.js create mode 100644 test/integration/scope-hoisting/commonjs/require-re-export-all/a.js create mode 100644 test/integration/scope-hoisting/commonjs/require-re-export-all/b.js create mode 100644 test/integration/scope-hoisting/commonjs/require-re-export-all/c.js create mode 100644 test/integration/scope-hoisting/commonjs/require-re-export-default/a.js create mode 100644 test/integration/scope-hoisting/commonjs/require-re-export-default/b.js create mode 100644 test/integration/scope-hoisting/commonjs/require-re-export-default/c.js create mode 100644 test/integration/scope-hoisting/commonjs/require-re-export-exclude-default/a.js create mode 100644 test/integration/scope-hoisting/commonjs/require-re-export-exclude-default/b.js create mode 100644 test/integration/scope-hoisting/commonjs/require-re-export-exclude-default/c.js create mode 100644 test/integration/scope-hoisting/commonjs/require-re-export-multiple/a.js create mode 100644 test/integration/scope-hoisting/commonjs/require-re-export-multiple/b.js create mode 100644 test/integration/scope-hoisting/commonjs/require-re-export-multiple/c.js create mode 100644 test/integration/scope-hoisting/commonjs/require-re-export-multiple/d.js create mode 100644 test/integration/scope-hoisting/commonjs/require-re-export-named/a.js create mode 100644 test/integration/scope-hoisting/commonjs/require-re-export-named/b.js create mode 100644 test/integration/scope-hoisting/commonjs/require-re-export-named/c.js create mode 100644 test/integration/scope-hoisting/commonjs/require-re-export-namespace/a.js create mode 100644 test/integration/scope-hoisting/commonjs/require-re-export-namespace/b.js create mode 100644 test/integration/scope-hoisting/commonjs/require-re-export-namespace/c.js create mode 100644 test/integration/scope-hoisting/commonjs/require-renamed-export/a.js create mode 100644 test/integration/scope-hoisting/commonjs/require-renamed-export/b.js create mode 100644 test/integration/scope-hoisting/commonjs/require-resolve/a.js create mode 100644 test/integration/scope-hoisting/commonjs/require-resolve/b.js create mode 100644 test/integration/scope-hoisting/commonjs/require/a.js create mode 100644 test/integration/scope-hoisting/commonjs/require/b.js create mode 100644 test/integration/scope-hoisting/commonjs/stream-module/a.js create mode 100644 test/integration/scope-hoisting/commonjs/this-reference-wrapped/a.js create mode 100644 test/integration/scope-hoisting/commonjs/this-reference-wrapped/b.js create mode 100644 test/integration/scope-hoisting/commonjs/this-reference/a.js create mode 100644 test/integration/scope-hoisting/commonjs/this-reference/b.js create mode 100644 test/integration/scope-hoisting/commonjs/tree-shaking/a.js create mode 100644 test/integration/scope-hoisting/commonjs/tree-shaking/b.js create mode 100644 test/integration/scope-hoisting/commonjs/wrap-eval/a.js create mode 100644 test/integration/scope-hoisting/commonjs/wrap-eval/b.js create mode 100644 test/integration/scope-hoisting/commonjs/wrap-module-computed/a.js create mode 100644 test/integration/scope-hoisting/commonjs/wrap-module/a.js create mode 100644 test/integration/scope-hoisting/commonjs/wrap-return/a.js create mode 100644 test/integration/scope-hoisting/commonjs/wrap-return/b.js create mode 100644 test/integration/scope-hoisting/es6/default-export-anonymous/a.js create mode 100644 test/integration/scope-hoisting/es6/default-export-anonymous/b.js create mode 100644 test/integration/scope-hoisting/es6/default-export-declaration/a.js create mode 100644 test/integration/scope-hoisting/es6/default-export-declaration/b.js create mode 100644 test/integration/scope-hoisting/es6/default-export-expression/a.js create mode 100644 test/integration/scope-hoisting/es6/default-export-expression/b.js create mode 100644 test/integration/scope-hoisting/es6/default-export-variable/a.js create mode 100644 test/integration/scope-hoisting/es6/default-export-variable/b.js create mode 100644 test/integration/scope-hoisting/es6/dynamic-default-interop/a.js create mode 100644 test/integration/scope-hoisting/es6/dynamic-default-interop/b.js create mode 100644 test/integration/scope-hoisting/es6/dynamic-default-interop/shared.js create mode 100644 test/integration/scope-hoisting/es6/dynamic-import/a.js create mode 100644 test/integration/scope-hoisting/es6/dynamic-import/b.js create mode 100644 test/integration/scope-hoisting/es6/dynamic-import/c.js create mode 100644 test/integration/scope-hoisting/es6/empty-module/a.js create mode 100644 test/integration/scope-hoisting/es6/empty-module/b.js create mode 100644 test/integration/scope-hoisting/es6/export-binding-identifiers/a.js create mode 100644 test/integration/scope-hoisting/es6/export-binding-identifiers/b.js create mode 100644 test/integration/scope-hoisting/es6/import-commonjs-default/a.js create mode 100644 test/integration/scope-hoisting/es6/import-commonjs-default/exports.js create mode 100644 test/integration/scope-hoisting/es6/import-commonjs-default/wrapped.js create mode 100644 test/integration/scope-hoisting/es6/import-namespace-commonjs/a.js create mode 100644 test/integration/scope-hoisting/es6/import-namespace-commonjs/b.js create mode 100644 test/integration/scope-hoisting/es6/import-namespace-commonjs/c.js create mode 100644 test/integration/scope-hoisting/es6/import-namespace/a.js create mode 100644 test/integration/scope-hoisting/es6/import-namespace/b.js create mode 100644 test/integration/scope-hoisting/es6/live-bindings/a.js create mode 100644 test/integration/scope-hoisting/es6/live-bindings/b.js create mode 100644 test/integration/scope-hoisting/es6/multi-export/a.js create mode 100644 test/integration/scope-hoisting/es6/multi-export/b.js create mode 100644 test/integration/scope-hoisting/es6/named-export-declaration/a.js create mode 100644 test/integration/scope-hoisting/es6/named-export-declaration/b.js create mode 100644 test/integration/scope-hoisting/es6/named-export-variable/a.js create mode 100644 test/integration/scope-hoisting/es6/named-export-variable/b.js create mode 100644 test/integration/scope-hoisting/es6/re-export-all-multiple/a.js create mode 100644 test/integration/scope-hoisting/es6/re-export-all-multiple/b.js create mode 100644 test/integration/scope-hoisting/es6/re-export-all-multiple/c.js create mode 100644 test/integration/scope-hoisting/es6/re-export-all-multiple/d.js create mode 100644 test/integration/scope-hoisting/es6/re-export-all/a.js create mode 100644 test/integration/scope-hoisting/es6/re-export-all/b.js create mode 100644 test/integration/scope-hoisting/es6/re-export-all/c.js create mode 100644 test/integration/scope-hoisting/es6/re-export-default/a.js create mode 100644 test/integration/scope-hoisting/es6/re-export-default/b.js create mode 100644 test/integration/scope-hoisting/es6/re-export-default/c.js create mode 100644 test/integration/scope-hoisting/es6/re-export-exclude-default/a.js create mode 100644 test/integration/scope-hoisting/es6/re-export-exclude-default/b.js create mode 100644 test/integration/scope-hoisting/es6/re-export-exclude-default/c.js create mode 100644 test/integration/scope-hoisting/es6/re-export-multiple/a.js create mode 100644 test/integration/scope-hoisting/es6/re-export-multiple/b.js create mode 100644 test/integration/scope-hoisting/es6/re-export-multiple/c.js create mode 100644 test/integration/scope-hoisting/es6/re-export-multiple/d.js create mode 100644 test/integration/scope-hoisting/es6/re-export-named/a.js create mode 100644 test/integration/scope-hoisting/es6/re-export-named/b.js create mode 100644 test/integration/scope-hoisting/es6/re-export-named/c.js create mode 100644 test/integration/scope-hoisting/es6/re-export-namespace/a.js create mode 100644 test/integration/scope-hoisting/es6/re-export-namespace/b.js create mode 100644 test/integration/scope-hoisting/es6/re-export-namespace/c.js create mode 100644 test/integration/scope-hoisting/es6/re-export-var/a.js create mode 100644 test/integration/scope-hoisting/es6/re-export-var/b.js create mode 100644 test/integration/scope-hoisting/es6/re-export-var/c.js create mode 100644 test/integration/scope-hoisting/es6/renamed-export/a.js create mode 100644 test/integration/scope-hoisting/es6/renamed-export/b.js create mode 100644 test/integration/scope-hoisting/es6/renamed-import/a.js create mode 100644 test/integration/scope-hoisting/es6/renamed-import/b.js create mode 100644 test/integration/scope-hoisting/es6/side-effects-false/a.js create mode 100644 test/integration/scope-hoisting/es6/side-effects-false/node_modules/bar/bar.js create mode 100644 test/integration/scope-hoisting/es6/side-effects-false/node_modules/bar/foo.js create mode 100644 test/integration/scope-hoisting/es6/side-effects-false/node_modules/bar/index.js create mode 100644 test/integration/scope-hoisting/es6/side-effects-false/node_modules/bar/package.json create mode 100644 test/integration/scope-hoisting/es6/side-effects/a.js create mode 100644 test/integration/scope-hoisting/es6/side-effects/node_modules/bar/bar.js create mode 100644 test/integration/scope-hoisting/es6/side-effects/node_modules/bar/foo.js create mode 100644 test/integration/scope-hoisting/es6/side-effects/node_modules/bar/index.js create mode 100644 test/integration/scope-hoisting/es6/side-effects/node_modules/bar/package.json create mode 100644 test/integration/scope-hoisting/es6/tree-shaking/a.js create mode 100644 test/integration/scope-hoisting/es6/tree-shaking/b.js create mode 100644 test/integration/sourcemap-reference/data.js delete mode 100644 test/integration/sourcemap-reference/data.json create mode 100644 test/scope-hoisting.js diff --git a/package.json b/package.json index 7d90846c7bc..2fbd8ab6786 100644 --- a/package.json +++ b/package.json @@ -106,8 +106,7 @@ "format": "prettier --write \"./{src,bin,test}/**/*.{js,json,md}\"", "build": "yarn minify && babel src -d lib && ncp src/builtins lib/builtins", "prepublish": "yarn build", - "minify": - "terser -c -m -o src/builtins/prelude.min.js src/builtins/prelude.js", + "minify": "terser -c -m -o src/builtins/prelude.min.js src/builtins/prelude.js && terser -c -m -o src/builtins/prelude2.min.js src/builtins/prelude2.js", "precommit": "npm run lint && lint-staged", "lint": "eslint . && prettier \"./{src,bin,test}/**/*.{js,json,md}\" --list-different", diff --git a/src/Bundler.js b/src/Bundler.js index 9c84793729f..5698f5efc52 100644 --- a/src/Bundler.js +++ b/src/Bundler.js @@ -35,7 +35,7 @@ class Bundler extends EventEmitter { this.resolver = new Resolver(this.options); this.parser = new Parser(this.options); - this.packagers = new PackagerRegistry(); + this.packagers = new PackagerRegistry(this.options); this.cache = this.options.cache ? new FSCache(this.options) : null; this.delegate = options.delegate || {}; this.bundleLoaders = {}; @@ -91,6 +91,14 @@ class Bundler extends EventEmitter { const watch = typeof options.watch === 'boolean' ? options.watch : !isProduction; const target = options.target || 'browser'; + const hmr = + target === 'node' + ? false + : typeof options.hmr === 'boolean' + ? options.hmr + : watch; + const scopeHoist = + options.scopeHoist !== undefined ? options.scopeHoist : false; return { production: isProduction, outDir: Path.resolve(options.outDir || 'dist'), @@ -104,19 +112,15 @@ class Bundler extends EventEmitter { minify: typeof options.minify === 'boolean' ? options.minify : isProduction, target: target, - hmr: - target === 'node' - ? false - : typeof options.hmr === 'boolean' - ? options.hmr - : watch, + hmr: hmr, https: options.https || false, logLevel: isNaN(options.logLevel) ? 3 : options.logLevel, entryFiles: this.entryFiles, hmrPort: options.hmrPort || 0, rootDir: getRootDir(this.entryFiles), sourceMaps: - typeof options.sourceMaps === 'boolean' ? options.sourceMaps : true, + (typeof options.sourceMaps === 'boolean' ? options.sourceMaps : true) && + !scopeHoist, hmrHostname: options.hmrHostname || (options.target === 'electron' ? 'localhost' : ''), @@ -126,6 +130,7 @@ class Bundler extends EventEmitter { typeof options.autoinstall === 'boolean' ? options.autoinstall : !isProduction, + scopeHoist: scopeHoist, contentHash: typeof options.contentHash === 'boolean' ? options.contentHash @@ -246,6 +251,8 @@ class Bundler extends EventEmitter { asset.invalidateBundle(); } + logger.status(emoji.progress, `Producing bundles...`); + // Create a root bundle to hold all of the entry assets, and add them to the tree. this.mainBundle = new Bundle(); for (let asset of this.entryAssets) { @@ -271,6 +278,8 @@ class Bundler extends EventEmitter { this.hmr.emitUpdate(changedAssets); } + logger.status(emoji.progress, `Packaging...`); + // Package everything up this.bundleHashes = await this.mainBundle.package( this, @@ -318,7 +327,7 @@ class Bundler extends EventEmitter { } await this.loadPlugins(); - + if (!this.options.env) { await loadEnv(Path.join(this.options.rootDir, 'index')); this.options.env = process.env; @@ -508,14 +517,17 @@ class Bundler extends EventEmitter { let processed = this.cache && (await this.cache.read(asset.name)); let cacheMiss = false; if (!processed || asset.shouldInvalidate(processed.cacheData)) { - processed = await this.farm.run(asset.name); + processed = await this.farm.run(asset.name, asset.id); + processed.id = asset.id; cacheMiss = true; } asset.endTime = Date.now(); asset.buildTime = asset.endTime - asset.startTime; + asset.id = processed.id; asset.generated = processed.generated; asset.hash = processed.hash; + asset.cacheData = processed.cacheData; // Call the delegate to get implicit dependencies let dependencies = processed.dependencies; @@ -535,6 +547,7 @@ class Bundler extends EventEmitter { // that changing it triggers a recompile of the parent. this.watch(dep.name, asset); } else { + dep.parent = asset.name; let assetDep = await this.resolveDep(asset, dep); if (assetDep) { await this.loadAsset(assetDep); diff --git a/src/FSCache.js b/src/FSCache.js index 38c842f0db4..fdfd98166e1 100644 --- a/src/FSCache.js +++ b/src/FSCache.js @@ -6,7 +6,7 @@ const pkg = require('../package.json'); const logger = require('./Logger'); // These keys can affect the output, so if they differ, the cache should not match -const OPTION_KEYS = ['publicURL', 'minify', 'hmr', 'target']; +const OPTION_KEYS = ['publicURL', 'minify', 'hmr', 'target', 'scopeHoist']; class FSCache { constructor(options) { diff --git a/src/Parser.js b/src/Parser.js index 22ac505ce8e..8c6fc23b50e 100644 --- a/src/Parser.js +++ b/src/Parser.js @@ -61,8 +61,8 @@ class Parser { this.extensions[ext.toLowerCase()] = parser; } - findParser(filename) { - if (/[*+{}]/.test(filename) && glob.hasMagic(filename)) { + findParser(filename, fromPipeline) { + if (!fromPipeline && /[*+{}]/.test(filename) && glob.hasMagic(filename)) { return GlobAsset; } diff --git a/src/Pipeline.js b/src/Pipeline.js index 40f588ced8a..6483bd9ed1a 100644 --- a/src/Pipeline.js +++ b/src/Pipeline.js @@ -11,13 +11,15 @@ class Pipeline { this.parser = new Parser(options); } - async process(path, isWarmUp) { + async process(path, id, isWarmUp) { let options = this.options; if (isWarmUp) { options = Object.assign({isWarmUp}, options); } let asset = this.parser.getAsset(path, options); + asset.id = id; + let generated = await this.processAsset(asset); let generatedMap = {}; for (let rendition of generated) { @@ -52,17 +54,19 @@ class Pipeline { // Find an asset type for the rendition type. // If the asset is not already an instance of this asset type, process it. let AssetType = this.parser.findParser( - asset.name.slice(0, -inputType.length) + type + asset.name.slice(0, -inputType.length) + type, + true ); if (!(asset instanceof AssetType)) { - let opts = Object.assign({rendition}, asset.options); + let opts = Object.assign({}, asset.options, {rendition}); let subAsset = new AssetType(asset.name, opts); + subAsset.id = asset.id; subAsset.contents = value; subAsset.dependencies = asset.dependencies; + subAsset.cacheData = Object.assign(asset.cacheData, subAsset.cacheData); let processed = await this.processAsset(subAsset); generated = generated.concat(processed); - Object.assign(asset.cacheData, subAsset.cacheData); asset.hash = md5(asset.hash + subAsset.hash); } else { generated.push(rendition); @@ -98,7 +102,8 @@ class Pipeline { yield { type, value: asset.generated[type], - final: true + // for scope hoisting, we need to post process all JS + final: !(type === 'js' && this.options.scopeHoist) }; } } diff --git a/src/SourceMap.js b/src/SourceMap.js index eee70281efe..6b97a2fa1f7 100644 --- a/src/SourceMap.js +++ b/src/SourceMap.js @@ -149,8 +149,8 @@ class SourceMap { column: mapping.original.column }); - if (!originalMapping.line) { - return false; + if (!originalMapping || !originalMapping.line) { + return; } this.addMapping({ @@ -182,7 +182,60 @@ class SourceMap { return this; } - findClosest(line, column, key = 'original') { + findClosestGenerated(line, column) { + if (line < 1) { + throw new Error('Line numbers must be >= 1'); + } + + if (column < 0) { + throw new Error('Column numbers must be >= 0'); + } + + if (this.mappings.length < 1) { + return undefined; + } + + let startIndex = 0; + let stopIndex = this.mappings.length - 1; + let middleIndex = (stopIndex + startIndex) >>> 1; + + while ( + startIndex < stopIndex && + this.mappings[middleIndex].generated.line !== line + ) { + let mid = this.mappings[middleIndex].generated.line; + if (line < mid) { + stopIndex = middleIndex - 1; + } else if (line > mid) { + startIndex = middleIndex + 1; + } + middleIndex = (stopIndex + startIndex) >>> 1; + } + + let mapping = this.mappings[middleIndex]; + if (!mapping || mapping.generated.line !== line) { + return this.mappings.length - 1; + } + + while ( + middleIndex >= 1 && + this.mappings[middleIndex - 1].generated.line === line + ) { + middleIndex--; + } + + while ( + middleIndex < this.mappings.length - 1 && + this.mappings[middleIndex + 1].generated.line === line && + column > this.mappings[middleIndex].generated.column + ) { + middleIndex++; + } + + return middleIndex; + } + + findClosest(line, column, key) { if (line < 1) { throw new Error('Line numbers must be >= 1'); } @@ -211,7 +264,7 @@ class SourceMap { middleIndex = Math.floor((stopIndex + startIndex) / 2); } - let mapping = this.mappings[middleIndex]; + var mapping = this.mappings[middleIndex]; if (!mapping || mapping[key].line !== line) { return this.mappings.length - 1; } @@ -235,16 +288,20 @@ class SourceMap { } originalPositionFor(generatedPosition) { - let index = this.findClosest( + let index = this.findClosestGenerated( generatedPosition.line, - generatedPosition.column, - 'generated' + generatedPosition.column ); + let mapping = this.mappings[index]; + if (!mapping) { + return null; + } + return { - source: this.mappings[index].source, - name: this.mappings[index].name, - line: this.mappings[index].original.line, - column: this.mappings[index].original.column + source: mapping.source, + name: mapping.name, + line: mapping.original.line, + column: mapping.original.column }; } diff --git a/src/assets/CSSAsset.js b/src/assets/CSSAsset.js index c79bb355364..4cf066ac1f0 100644 --- a/src/assets/CSSAsset.js +++ b/src/assets/CSSAsset.js @@ -125,7 +125,7 @@ class CSSAsset extends Asset { { type: 'js', value: js, - final: true + hasDependencies: false } ]; } diff --git a/src/assets/GLSLAsset.js b/src/assets/GLSLAsset.js index 50019952cce..fb67e73242c 100644 --- a/src/assets/GLSLAsset.js +++ b/src/assets/GLSLAsset.js @@ -53,9 +53,7 @@ class GLSLAsset extends Asset { const glslifyBundle = await localRequire('glslify-bundle', this.name); let glsl = glslifyBundle(this.ast); - return { - js: `module.exports=${JSON.stringify(glsl)};` - }; + return `module.exports=${JSON.stringify(glsl)};`; } } diff --git a/src/assets/GlobAsset.js b/src/assets/GlobAsset.js index bb567d00e1d..63f151f5bde 100644 --- a/src/assets/GlobAsset.js +++ b/src/assets/GlobAsset.js @@ -38,9 +38,12 @@ class GlobAsset extends Asset { } generate() { - return { - js: 'module.exports = ' + generate(this.contents) + ';' - }; + return [ + { + type: 'js', + value: 'module.exports = ' + generate(this.contents) + ';' + } + ]; } } diff --git a/src/assets/GraphqlAsset.js b/src/assets/GraphqlAsset.js index af15efe6edf..62e7a3563d9 100644 --- a/src/assets/GraphqlAsset.js +++ b/src/assets/GraphqlAsset.js @@ -13,9 +13,7 @@ class GraphqlAsset extends Asset { } generate() { - return { - js: `module.exports=${JSON.stringify(this.ast, false, 2)};` - }; + return `module.exports=${JSON.stringify(this.ast, false, 2)};`; } } diff --git a/src/assets/JSAsset.js b/src/assets/JSAsset.js index cfdc370fe0e..57301b8956d 100644 --- a/src/assets/JSAsset.js +++ b/src/assets/JSAsset.js @@ -12,6 +12,7 @@ const babel = require('../transforms/babel'); const generate = require('babel-generator').default; const terser = require('../transforms/terser'); const SourceMap = require('../SourceMap'); +const hoist = require('../scope-hoisting/hoist'); const IMPORT_RE = /\b(?:import\b|export\b|require\s*\()/; const ENV_RE = /\b(?:process\.env)\b/; @@ -29,7 +30,8 @@ class JSAsset extends Asset { this.isES6Module = false; this.outputCode = null; this.cacheData.env = {}; - this.sourceMap = options.rendition ? options.rendition.sourceMap : null; + this.rendition = options.rendition; + this.sourceMap = this.rendition ? this.rendition.sourceMap : null; } shouldInvalidate(cacheData) { @@ -82,6 +84,15 @@ class JSAsset extends Asset { } traverse(visitor) { + // Create a babel File object if one hasn't been created yet. + // This is needed so that cached NodePath objects get a `hub` object on them. + // Plugins like babel-minify depend on this to get the original source code string. + if (!this.babelFile) { + this.babelFile = new BabelFile(this.babelConfig || {}); + this.babelFile.addCode(this.contents); + this.babelFile.addAst(this.ast); + } + return traverse(this.ast, visitor, null, this); } @@ -123,8 +134,16 @@ class JSAsset extends Asset { } } - if (this.isES6Module) { - await babel(this); + if (this.options.scopeHoist) { + await this.parseIfNeeded(); + await this.getPackage(); + + this.traverse(hoist); + this.isAstDirty = true; + } else { + if (this.isES6Module) { + await babel(this); + } } if (this.options.minify) { @@ -133,6 +152,9 @@ class JSAsset extends Asset { } async generate() { + let enableSourceMaps = + this.options.sourceMaps && + (!this.rendition || !!this.rendition.sourceMap); let code; if (this.isAstDirty) { let opts = { @@ -142,7 +164,7 @@ class JSAsset extends Asset { let generated = generate(this.ast, opts, this.contents); - if (this.options.sourceMaps && generated.rawMappings) { + if (enableSourceMaps && generated.rawMappings) { let rawMap = new SourceMap(generated.rawMappings, { [this.relativeName]: this.contents }); @@ -161,10 +183,10 @@ class JSAsset extends Asset { code = generated.code; } else { - code = this.outputCode || this.contents; + code = this.outputCode != null ? this.outputCode : this.contents; } - if (this.options.sourceMaps && !this.sourceMap) { + if (enableSourceMaps && !this.sourceMap) { this.sourceMap = new SourceMap().generateEmptyMap( this.relativeName, this.contents @@ -173,7 +195,7 @@ class JSAsset extends Asset { if (this.globals.size > 0) { code = Array.from(this.globals.values()).join('\n') + '\n' + code; - if (this.options.sourceMaps) { + if (enableSourceMaps) { if (!(this.sourceMap instanceof SourceMap)) { this.sourceMap = await new SourceMap().addMap(this.sourceMap); } diff --git a/src/assets/JSONAsset.js b/src/assets/JSONAsset.js index 2979b7e1d31..203223beb5b 100644 --- a/src/assets/JSONAsset.js +++ b/src/assets/JSONAsset.js @@ -18,7 +18,7 @@ class JSONAsset extends Asset { this.ast ? JSON.stringify(this.ast, null, 2) : this.contents };`; - if (this.options.minify) { + if (this.options.minify && !this.options.scopeHoist) { let minified = minify(code); if (minified.error) { throw minified.error; @@ -27,9 +27,7 @@ class JSONAsset extends Asset { code = minified.code; } - return { - js: code - }; + return code; } } diff --git a/src/assets/RawAsset.js b/src/assets/RawAsset.js index 4a1be0cdda5..d44abf2e306 100644 --- a/src/assets/RawAsset.js +++ b/src/assets/RawAsset.js @@ -18,9 +18,12 @@ class RawAsset extends Asset { this.generateBundleName() ); - return { - js: `module.exports=${JSON.stringify(pathToAsset)};` - }; + return [ + { + type: 'js', + value: `module.exports=${JSON.stringify(pathToAsset)};` + } + ]; } async generateHash() { diff --git a/src/assets/TOMLAsset.js b/src/assets/TOMLAsset.js index 10c86dfed6b..ce1d77e8058 100644 --- a/src/assets/TOMLAsset.js +++ b/src/assets/TOMLAsset.js @@ -13,9 +13,10 @@ class TOMLAsset extends Asset { } generate() { - return { - js: serializeObject(this.ast, this.options.minify) - }; + return serializeObject( + this.ast, + this.options.minify && !this.options.scopeHoist + ); } } diff --git a/src/assets/VueAsset.js b/src/assets/VueAsset.js index a5705dcebde..e68b715f22a 100644 --- a/src/assets/VueAsset.js +++ b/src/assets/VueAsset.js @@ -68,8 +68,27 @@ class VueAsset extends Asset { // Generate JS output. let js = this.ast.script ? generated[0].value : ''; - let supplemental = ` - var ${optsVar} = exports.default || module.exports; + let supplemental = ''; + + // TODO: make it possible to process this code with the normal scope hoister + if (this.options.scopeHoist) { + optsVar = `$${this.id}$export$default`; + + if (!js.includes(optsVar)) { + optsVar = `$${this.id}$exports`; + if (!js.includes(optsVar)) { + supplemental += ` + var ${optsVar} = {}; + `; + + this.cacheData.isCommonJS = true; + } + } + } else { + supplemental += `var ${optsVar} = exports.default || module.exports;`; + } + + supplemental += ` if (typeof ${optsVar} === 'function') { ${optsVar} = ${optsVar}.options; } @@ -79,19 +98,17 @@ class VueAsset extends Asset { supplemental += this.compileCSSModules(generated, optsVar); supplemental += this.compileHMR(generated, optsVar); - if (this.options.minify && supplemental) { + if (this.options.minify && !this.options.scopeHoist && supplemental) { let {code, error} = minify(supplemental, {toplevel: true}); if (error) { throw error; } supplemental = code; + if (supplemental) { + supplemental = `\n(function(){${supplemental}})();`; + } } - - if (supplemental) { - supplemental = `\n(function(){${supplemental}})();`; - } - js += supplemental; if (js) { diff --git a/src/assets/YAMLAsset.js b/src/assets/YAMLAsset.js index 40c904ff6f8..327d7407180 100644 --- a/src/assets/YAMLAsset.js +++ b/src/assets/YAMLAsset.js @@ -13,9 +13,10 @@ class YAMLAsset extends Asset { } generate() { - return { - js: serializeObject(this.ast, this.options.minify) - }; + return serializeObject( + this.ast, + this.options.minify && !this.options.scopeHoist + ); } } diff --git a/src/builtins/.eslintrc.json b/src/builtins/.eslintrc.json index 3361ed605a0..bbe2667e56c 100644 --- a/src/builtins/.eslintrc.json +++ b/src/builtins/.eslintrc.json @@ -7,6 +7,7 @@ "browser": true }, "rules": { - "no-global-assign": 1 + "no-global-assign": 1, + "no-unused-vars": 0 } -} \ No newline at end of file +} diff --git a/src/builtins/bundle-loader.js b/src/builtins/bundle-loader.js index e60c0ae3119..f36878875f7 100644 --- a/src/builtins/bundle-loader.js +++ b/src/builtins/bundle-loader.js @@ -12,7 +12,10 @@ function loadBundlesLazy(bundles) { } catch (err) { if (err.code === 'MODULE_NOT_FOUND') { return new LazyPromise(function (resolve, reject) { - loadBundles(bundles) + loadBundles(bundles.slice(0, -1)) + .then(function () { + return require(id); + }) .then(resolve, reject); }); } @@ -22,12 +25,7 @@ function loadBundlesLazy(bundles) { } function loadBundles(bundles) { - var id = bundles[bundles.length - 1]; - - return Promise.all(bundles.slice(0, -1).map(loadBundle)) - .then(function () { - return require(id); - }); + return Promise.all(bundles.map(loadBundle)); } var bundleLoaders = {}; @@ -57,9 +55,7 @@ function loadBundle(bundle) { return bundles[bundle] = bundleLoader(getBundleURL() + bundle) .then(function (resolved) { if (resolved) { - module.bundle.modules[id] = [function (require, module) { - module.exports = resolved; - }, {}]; + module.bundle.register(id, resolved); } return resolved; diff --git a/src/builtins/helpers.js b/src/builtins/helpers.js new file mode 100644 index 00000000000..e40bff949d3 --- /dev/null +++ b/src/builtins/helpers.js @@ -0,0 +1,30 @@ +function $parcel$interopDefault(a) { + return a && a.__esModule + ? {d: a.default} + : {d: a}; +} + +function $parcel$exportWildcard(dest, source) { + Object.keys(source).forEach(function(key) { + if(key === "default" || key === "__esModule") { + return; + } + + Object.defineProperty(dest, key, { + enumerable: true, + get: function get() { + return source[key]; + } + }); + }); + + return dest; +} + +function $parcel$missingModule(name) { + var err = new Error("Cannot find module '" + name + "'"); + err.code = 'MODULE_NOT_FOUND'; + throw err; +} + +var $parcel$global = this; diff --git a/src/builtins/prelude.js b/src/builtins/prelude.js index 8463e1c60b2..189d8cba73d 100644 --- a/src/builtins/prelude.js +++ b/src/builtins/prelude.js @@ -70,6 +70,11 @@ parcelRequire = (function (modules, cache, entry, globalName) { newRequire.modules = modules; newRequire.cache = cache; newRequire.parent = previousRequire; + newRequire.register = function (id, exports) { + modules[id] = [function (require, module) { + module.exports = exports; + }, {}]; + }; for (var i = 0; i < entry.length; i++) { newRequire(entry[i]); diff --git a/src/builtins/prelude2.js b/src/builtins/prelude2.js new file mode 100644 index 00000000000..6dcd6d49f10 --- /dev/null +++ b/src/builtins/prelude2.js @@ -0,0 +1,45 @@ +parcelRequire = (function (init) { + // Save the require from previous bundle to this closure if any + var previousRequire = typeof parcelRequire === 'function' && parcelRequire; + var nodeRequire = typeof require === 'function' && require; + var modules = {}; + + function localRequire(name, jumped) { + if (name in modules) { + return modules[name]; + } + + // if we cannot find the module within our internal map or + // cache jump to the current global require ie. the last bundle + // that was added to the page. + var currentRequire = typeof parcelRequire === 'function' && parcelRequire; + if (!jumped && currentRequire) { + return currentRequire(name, true); + } + + // If there are other bundles on this page the require from the + // previous one is saved to 'previousRequire'. Repeat this as + // many times as there are bundles until the module is found or + // we exhaust the require chain. + if (previousRequire) { + return previousRequire(name, true); + } + + // Try the node require function if it exists. + if (nodeRequire && typeof name === 'string') { + return nodeRequire(name); + } + + var err = new Error('Cannot find module \'' + name + '\''); + err.code = 'MODULE_NOT_FOUND'; + throw err; + } + + localRequire.register = function register(id, exports) { + modules[id] = exports; + }; + + modules = init(localRequire); + localRequire.modules = modules; + return localRequire; +}) diff --git a/src/cli.js b/src/cli.js index bfa36a1345b..6bde994d052 100755 --- a/src/cli.js +++ b/src/cli.js @@ -122,6 +122,10 @@ program .option('--no-minify', 'disable minification') .option('--no-cache', 'disable the filesystem cache') .option('--no-source-maps', 'disable sourcemaps') + .option( + '--experimental-scope-hoisting', + 'enable experimental scope hoisting/tree shaking support' + ) .option( '-t, --target ', 'set the runtime environment, either "node", "browser" or "electron". defaults to "browser"', @@ -182,6 +186,7 @@ async function bundle(main, command) { }; } + command.scopeHoist = command.experimentalScopeHoisting || false; const bundler = new Bundler(main, command); command.target = command.target || 'browser'; diff --git a/src/packagers/JSConcatPackager.js b/src/packagers/JSConcatPackager.js new file mode 100644 index 00000000000..39f27b69750 --- /dev/null +++ b/src/packagers/JSConcatPackager.js @@ -0,0 +1,526 @@ +const Packager = require('./Packager'); +const path = require('path'); +const fs = require('fs'); +const concat = require('../scope-hoisting/concat'); +const urlJoin = require('../utils/urlJoin'); +const walk = require('babylon-walk'); +const babylon = require('babylon'); +const t = require('babel-types'); + +const prelude = fs + .readFileSync(path.join(__dirname, '../builtins/prelude2.min.js'), 'utf8') + .trim() + .replace(/;$/, ''); +const helpers = + fs + .readFileSync(path.join(__dirname, '../builtins/helpers.js'), 'utf8') + .trim() + '\n'; + +class JSConcatPackager extends Packager { + async start() { + this.addedAssets = new Set(); + this.exposedModules = new Set(); + this.externalModules = new Set(); + this.size = 0; + this.needsPrelude = false; + this.statements = []; + this.assetPostludes = new Map(); + + for (let asset of this.bundle.assets) { + // If this module is referenced by another JS bundle, it needs to be exposed externally. + let isExposed = !Array.from(asset.parentDeps).every(dep => { + let depAsset = this.bundler.loadedAssets.get(dep.parent); + return this.bundle.assets.has(depAsset) || depAsset.type !== 'js'; + }); + + if ( + isExposed || + (this.bundle.entryAsset === asset && + this.bundle.parentBundle && + this.bundle.parentBundle.childBundles.size !== 1) + ) { + this.exposedModules.add(asset); + this.needsPrelude = true; + } + + for (let mod of asset.depAssets.values()) { + if ( + !this.bundle.assets.has(mod) && + this.options.bundleLoaders[asset.type] + ) { + this.needsPrelude = true; + break; + } + } + } + + if (this.bundle.entryAsset) { + this.markUsedExports(this.bundle.entryAsset); + } + + if (this.needsPrelude) { + if ( + this.bundle.entryAsset && + this.options.bundleLoaders[this.bundle.entryAsset.type] + ) { + this.exposedModules.add(this.bundle.entryAsset); + } + } + + this.write(helpers); + } + + write(string) { + this.statements.push(...this.parse(string)); + } + + getSize() { + return this.size; + } + + markUsedExports(asset) { + if (asset.usedExports) { + return; + } + + asset.usedExports = new Set(); + + for (let identifier in asset.cacheData.imports) { + let [source, name] = asset.cacheData.imports[identifier]; + let dep = asset.depAssets.get(asset.dependencies.get(source)); + this.markUsed(dep, name); + } + } + + markUsed(mod, id) { + let exp = mod.cacheData.exports[id]; + if (Array.isArray(exp)) { + let depMod = mod.depAssets.get(mod.dependencies.get(exp[0])); + return this.markUsed(depMod, exp[1]); + } + + this.markUsedExports(mod); + mod.usedExports.add(id); + } + + getExportIdentifier(asset) { + let id = '$' + asset.id + '$exports'; + if (this.shouldWrap(asset)) { + return `($${asset.id}$init(), ${id})`; + } + + return id; + } + + async addAsset(asset) { + if (this.addedAssets.has(asset)) { + return; + } + 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 ( + asset.cacheData.sideEffects === false && + (!asset.usedExports || asset.usedExports.size === 0) + ) { + return; + } + + for (let [dep, mod] of asset.depAssets) { + if (dep.dynamic && this.bundle.childBundles.has(mod.parentBundle)) { + for (let child of mod.parentBundle.siblingBundles) { + if (!child.isEmpty) { + await this.addBundleLoader(child.type, asset); + } + } + + await this.addBundleLoader(mod.type, asset, true); + } else { + // If the dep isn't in this bundle, add it to the list of external modules to preload. + // Only do this if this is the root JS bundle, otherwise they will have already been + // loaded in parallel with this bundle as part of a dynamic import. + if ( + !this.bundle.assets.has(mod) && + (!this.bundle.parentBundle || + this.bundle.parentBundle.type !== 'js') && + this.options.bundleLoaders[mod.type] + ) { + this.externalModules.add(mod); + await this.addBundleLoader(mod.type, asset); + } + } + } + + // if (this.bundle.entryAsset === asset && this.externalModules.size > 0) { + // js = ` + // function $parcel$entry() { + // ${js.trim()} + // } + // `; + // } + + // js = js.trim() + '\n'; + this.size += js.length; + } + + shouldWrap(asset) { + if (!asset) { + return false; + } + + if (asset.cacheData.shouldWrap != null) { + return asset.cacheData.shouldWrap; + } + + // Set to false initially so circular deps work + asset.cacheData.shouldWrap = false; + + // We need to wrap if any of the deps are marked by the hoister, e.g. + // when the dep is required inside a function or conditional. + // We also need to wrap if any of the parents are wrapped - transitive requires + // shouldn't be evaluated until their parents are. + let shouldWrap = [...asset.parentDeps].some( + dep => + dep.shouldWrap || + this.shouldWrap(this.bundler.loadedAssets.get(dep.parent)) + ); + + asset.cacheData.shouldWrap = shouldWrap; + return shouldWrap; + } + + addDeps(asset, included) { + if (!this.bundle.assets.has(asset) || included.has(asset)) { + return []; + } + + included.add(asset); + + let depAsts = new Map(); + for (let depAsset of asset.depAssets.values()) { + let depAst = this.addDeps(depAsset, included); + depAsts.set(depAsset, depAst); + } + + let statements; + if ( + asset.cacheData.sideEffects === false && + (!asset.usedExports || asset.usedExports.size === 0) + ) { + statements = []; + } else { + statements = this.parse(asset.generated.js, asset.name); + } + + if (this.shouldWrap(asset)) { + statements = this.wrapModule(asset, statements); + } + + if (statements[0]) { + if (!statements[0].leadingComments) { + statements[0].leadingComments = []; + } + statements[0].leadingComments.push({ + type: 'CommentLine', + value: ` ASSET: ${path.relative(this.options.rootDir, asset.name)}` + }); + } + + let statementIndices = new Map(); + for (let i = 0; i < statements.length; i++) { + let statement = statements[i]; + if (t.isExpressionStatement(statement)) { + for (let depAsset of this.findRequires(asset, statement)) { + if (!statementIndices.has(depAsset)) { + statementIndices.set(depAsset, i); + } + } + } + } + + let reverseDeps = [...asset.depAssets.values()].reverse(); + for (let dep of reverseDeps) { + let index = statementIndices.has(dep) ? statementIndices.get(dep) : 0; + statements.splice(index, 0, ...depAsts.get(dep)); + } + + if (this.assetPostludes.has(asset)) { + statements.push(...this.parse(this.assetPostludes.get(asset))); + } + + return statements; + } + + wrapModule(asset, statements) { + let body = []; + let decls = []; + let fns = []; + for (let node of statements) { + // Hoist all declarations out of the function wrapper + // so that they can be referenced by other modules directly. + if (t.isVariableDeclaration(node)) { + for (let decl of node.declarations) { + decls.push(t.variableDeclarator(decl.id)); + if (decl.init) { + body.push( + t.expressionStatement( + t.assignmentExpression( + '=', + t.identifier(decl.id.name), + decl.init + ) + ) + ); + } + } + } else if (t.isFunctionDeclaration(node)) { + // Function declarations can be hoisted out of the module initialization function + fns.push(node); + } else if (t.isClassDeclaration(node)) { + // Class declarations are not hoisted. We declare a variable outside the + // function convert to a class expression assignment. + decls.push(t.variableDeclarator(t.identifier(node.id.name))); + body.push( + t.expressionStatement( + t.assignmentExpression( + '=', + t.identifier(node.id.name), + t.toExpression(node) + ) + ) + ); + } else { + body.push(node); + } + } + + let executed = `$${asset.id}$executed`; + decls.push( + t.variableDeclarator(t.identifier(executed), t.booleanLiteral(false)) + ); + + let init = t.functionDeclaration( + t.identifier(`$${asset.id}$init`), + [], + t.blockStatement([ + t.ifStatement(t.identifier(executed), t.returnStatement()), + t.expressionStatement( + t.assignmentExpression( + '=', + t.identifier(executed), + t.booleanLiteral(true) + ) + ), + ...body + ]) + ); + + return [t.variableDeclaration('var', decls), ...fns, init]; + } + + parse(code, filename) { + let ast = babylon.parse(code, { + sourceFilename: filename, + allowReturnOutsideFunction: true + }); + + return ast.program.body; + } + + findRequires(asset, ast) { + let result = []; + walk.simple(ast, { + CallExpression(node) { + let {arguments: args, callee} = node; + + if (!t.isIdentifier(callee)) { + return; + } + + if (callee.name === '$parcel$require') { + result.push( + asset.depAssets.get(asset.dependencies.get(args[1].value)) + ); + } + } + }); + + return result; + } + + getBundleSpecifier(bundle) { + let name = path.basename(bundle.name); + if (bundle.entryAsset) { + return [name, bundle.entryAsset.id]; + } + + return name; + } + + async addAssetToBundle(asset) { + if (this.bundle.assets.has(asset)) { + return; + } + this.bundle.addAsset(asset); + if (!asset.parentBundle) { + asset.parentBundle = this.bundle; + } + + // Add all dependencies as well + for (let child of asset.depAssets.values()) { + await this.addAssetToBundle(child, this.bundle); + } + + await this.addAsset(asset); + } + + async addBundleLoader(bundleType, parentAsset, dynamic) { + let loader = this.options.bundleLoaders[bundleType]; + if (!loader) { + return; + } + + let bundleLoader = this.bundler.loadedAssets.get( + require.resolve('../builtins/bundle-loader') + ); + if (!bundleLoader && !dynamic) { + bundleLoader = await this.bundler.getAsset('_bundle_loader'); + } + + if (bundleLoader) { + // parentAsset.depAssets.set({name: '_bundle_loader'}, bundleLoader); + await this.addAssetToBundle(bundleLoader); + } else { + return; + } + + let target = this.options.target === 'node' ? 'node' : 'browser'; + let asset = await this.bundler.getAsset(loader[target]); + if (!this.bundle.assets.has(asset)) { + let dep = {name: asset.name}; + asset.parentDeps.add(dep); + parentAsset.dependencies.set(dep.name, dep); + parentAsset.depAssets.set(dep, asset); + this.assetPostludes.set( + asset, + `${this.getExportIdentifier(bundleLoader)}.register(${JSON.stringify( + bundleType + )},${this.getExportIdentifier(asset)});\n` + ); + + await this.addAssetToBundle(asset); + } + } + + async end() { + let included = new Set(); + for (let asset of this.bundle.assets) { + this.statements.push(...this.addDeps(asset, included)); + } + + // Preload external modules before running entry point if needed + if (this.externalModules.size > 0) { + let bundleLoader = this.bundler.loadedAssets.get( + require.resolve('../builtins/bundle-loader') + ); + + let preload = []; + for (let mod of this.externalModules) { + // Find the bundle that has the module as its entry point + let bundle = Array.from(mod.bundles).find(b => b.entryAsset === mod); + if (bundle) { + preload.push([path.basename(bundle.name), mod.id]); + } + } + + let loads = `${this.getExportIdentifier( + bundleLoader + )}.load(${JSON.stringify(preload)})`; + if (this.bundle.entryAsset) { + loads += '.then($parcel$entry)'; + } + + loads += ';'; + this.write(loads); + } + + let entryExports = + this.bundle.entryAsset && + this.getExportIdentifier(this.bundle.entryAsset); + if ( + entryExports && + this.bundle.entryAsset.generated.js.includes(entryExports) + ) { + this.write(` + if (typeof exports === "object" && typeof module !== "undefined") { + // CommonJS + module.exports = ${entryExports}; + } else if (typeof define === "function" && define.amd) { + // RequireJS + define(function () { + return ${entryExports}; + }); + } ${ + this.options.global + ? `else { + //