From 5a4eb5efbb1f2417a01fa59d2d7986d190e53191 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Sun, 26 Jan 2020 13:27:18 +0100 Subject: [PATCH 1/2] Add option to avoid hoisting transitive imports --- cli/help.md | 3 ++ docs/01-command-line-reference.md | 2 + docs/02-javascript-api.md | 1 + docs/06-faqs.md | 43 ++++++++++++++++++- docs/999-big-list-of-options.md | 7 +++ src/Chunk.ts | 6 ++- src/rollup/types.d.ts | 1 + src/utils/mergeOptions.ts | 1 + .../avoid-chunk-import-hoisting/_config.js | 10 +++++ .../_expected/amd/generated-dep.js | 9 ++++ .../_expected/amd/main1.js | 5 +++ .../_expected/amd/main2.js | 5 +++ .../_expected/cjs/generated-dep.js | 9 ++++ .../_expected/cjs/main1.js | 5 +++ .../_expected/cjs/main2.js | 5 +++ .../_expected/es/generated-dep.js | 5 +++ .../_expected/es/main1.js | 3 ++ .../_expected/es/main2.js | 3 ++ .../_expected/system/generated-dep.js | 14 ++++++ .../_expected/system/main1.js | 14 ++++++ .../_expected/system/main2.js | 14 ++++++ .../avoid-chunk-import-hoisting/dep.js | 3 ++ .../avoid-chunk-import-hoisting/main1.js | 3 ++ .../avoid-chunk-import-hoisting/main2.js | 3 ++ test/misc/optionList.js | 4 +- 25 files changed, 173 insertions(+), 5 deletions(-) create mode 100644 test/chunking-form/samples/avoid-chunk-import-hoisting/_config.js create mode 100644 test/chunking-form/samples/avoid-chunk-import-hoisting/_expected/amd/generated-dep.js create mode 100644 test/chunking-form/samples/avoid-chunk-import-hoisting/_expected/amd/main1.js create mode 100644 test/chunking-form/samples/avoid-chunk-import-hoisting/_expected/amd/main2.js create mode 100644 test/chunking-form/samples/avoid-chunk-import-hoisting/_expected/cjs/generated-dep.js create mode 100644 test/chunking-form/samples/avoid-chunk-import-hoisting/_expected/cjs/main1.js create mode 100644 test/chunking-form/samples/avoid-chunk-import-hoisting/_expected/cjs/main2.js create mode 100644 test/chunking-form/samples/avoid-chunk-import-hoisting/_expected/es/generated-dep.js create mode 100644 test/chunking-form/samples/avoid-chunk-import-hoisting/_expected/es/main1.js create mode 100644 test/chunking-form/samples/avoid-chunk-import-hoisting/_expected/es/main2.js create mode 100644 test/chunking-form/samples/avoid-chunk-import-hoisting/_expected/system/generated-dep.js create mode 100644 test/chunking-form/samples/avoid-chunk-import-hoisting/_expected/system/main1.js create mode 100644 test/chunking-form/samples/avoid-chunk-import-hoisting/_expected/system/main2.js create mode 100644 test/chunking-form/samples/avoid-chunk-import-hoisting/dep.js create mode 100644 test/chunking-form/samples/avoid-chunk-import-hoisting/main1.js create mode 100644 test/chunking-form/samples/avoid-chunk-import-hoisting/main2.js diff --git a/cli/help.md b/cli/help.md index 44045eb9743..0e7520d1ae2 100644 --- a/cli/help.md +++ b/cli/help.md @@ -31,8 +31,10 @@ Basic options: --no-esModule Do not add __esModule property --exports Specify export mode (auto, default, named, none) --extend Extend global variable defined by --name +--no-externalLiveBindings Do not generate code to support live bindings --footer Code to insert at end of bundle (outside wrapper) --no-freeze Do not freeze namespace objects +--no-hoistTransitiveImports Do not hoist transitive imports into entry chunks --no-indent Don't indent result --no-interop Do not include interop block --inlineDynamicImports Create single bundle when using dynamic imports @@ -48,6 +50,7 @@ Basic options: --silent Don't print warnings --sourcemapExcludeSources Do not include source code in source maps --sourcemapFile Specify bundle position for source maps +--no-stdin do not read "-" from stdin --strictDeprecations Throw errors for deprecated features --no-treeshake Disable tree-shaking optimisations --no-treeshake.annotations Ignore pure call annotations diff --git a/docs/01-command-line-reference.md b/docs/01-command-line-reference.md index 7975b964731..1cb74a4a9ac 100755 --- a/docs/01-command-line-reference.md +++ b/docs/01-command-line-reference.md @@ -75,6 +75,7 @@ export default { // can be an array (for multiple inputs) entryFileNames, extend, footer, + hoistTransitiveImports, interop, intro, outro, @@ -237,6 +238,7 @@ Many options have command line equivalents. In those cases, any arguments passed --no-externalLiveBindings Do not generate code to support live bindings --footer Code to insert at end of bundle (outside wrapper) --no-freeze Do not freeze namespace objects +--no-hoistTransitiveImports Do not hoist transitive imports into entry chunks --no-indent Don't indent result --no-interop Do not include interop block --inlineDynamicImports Create single bundle when using dynamic imports diff --git a/docs/02-javascript-api.md b/docs/02-javascript-api.md index ab5d3db7caa..7f207c67c56 100755 --- a/docs/02-javascript-api.md +++ b/docs/02-javascript-api.md @@ -126,6 +126,7 @@ const outputOptions = { extend, externalLiveBindings, footer, + hoistTransitiveImports, interop, intro, outro, diff --git a/docs/06-faqs.md b/docs/06-faqs.md index d582ce2e4df..97f9dd9a012 100755 --- a/docs/06-faqs.md +++ b/docs/06-faqs.md @@ -4,11 +4,11 @@ title: Frequently Asked Questions #### Why are ES modules better than CommonJS Modules? -ES modules are an official standard and the clear path forward for JavaScript code structure, whereas CommonJS modules are an idiosyncratic legacy format that served as a stopgap solution before ES modules had been proposed. ES modules allow static analysis that helps with optimizations like tree-shaking, and provide advanced features like circular references and live bindings. +ES modules are an official standard and the clear path forward for JavaScript code structure, whereas CommonJS modules are an idiosyncratic legacy format that served as a stopgap solution before ES modules had been proposed. ES modules allow static analysis that helps with optimizations like tree-shaking and scope-hoisting, and provide advanced features like circular references and live bindings. #### What Is "tree-shaking?" -Tree-shaking, also known as "live code inclusion," is the process of eliminating code that is not actually used in a given project. It is [similar to dead code elimination](https://medium.com/@Rich_Harris/tree-shaking-versus-dead-code-elimination-d3765df85c80#.jnypozs9n) but can be much more efficient. +Tree-shaking, also known as "live code inclusion", is Rollup's process of eliminating code that is not actually used in a given project. It is a [form of dead code elimination](https://medium.com/@Rich_Harris/tree-shaking-versus-dead-code-elimination-d3765df85c80#.jnypozs9n) but can be much more efficient than other approaches with regard to output size. The name is derived from the [abstract syntax tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree) of the modules (not the module graph). The algorithm first marks all relevant statements and then "shakes the syntax tree" to remove all dead code. It is similar in idea to the [mark-and-sweep garbage collection algorithm](https://en.wikipedia.org/wiki/Tracing_garbage_collection). Even though this algorithm is not restricted to ES modules, they make it much more efficient as they allow Rollup to treat all modules together as a big abstract syntax tree with shared bindings. #### How do I use Rollup in Node.js with CommonJS modules? @@ -24,6 +24,45 @@ There are two primary reasons: Please see [this issue](https://github.com/rollup/rollup/issues/1555#issuecomment-322862209) for a more verbose explanation. +#### Why do additional imports turn up in my entry chunks when code-splitting? + +By default when creating multiple chunks, imports of dependencies of entry chunks will be added as empty imports to the entry chunks themselves. [Example](https://rollupjs.org/repl/?shareable=JTdCJTIybW9kdWxlcyUyMiUzQSU1QiU3QiUyMm5hbWUlMjIlM0ElMjJtYWluLmpzJTIyJTJDJTIyY29kZSUyMiUzQSUyMmltcG9ydCUyMHZhbHVlJTIwZnJvbSUyMCcuJTJGb3RoZXItZW50cnkuanMnJTNCJTVDbmNvbnNvbGUubG9nKHZhbHVlKSUzQiUyMiUyQyUyMmlzRW50cnklMjIlM0F0cnVlJTdEJTJDJTdCJTIybmFtZSUyMiUzQSUyMm90aGVyLWVudHJ5LmpzJTIyJTJDJTIyY29kZSUyMiUzQSUyMmltcG9ydCUyMGV4dGVybmFsVmFsdWUlMjBmcm9tJTIwJ2V4dGVybmFsJyUzQiU1Q25leHBvcnQlMjBkZWZhdWx0JTIwMiUyMColMjBleHRlcm5hbFZhbHVlJTNCJTIyJTJDJTIyaXNFbnRyeSUyMiUzQXRydWUlN0QlNUQlMkMlMjJvcHRpb25zJTIyJTNBJTdCJTIyZm9ybWF0JTIyJTNBJTIyZXNtJTIyJTJDJTIybmFtZSUyMiUzQSUyMm15QnVuZGxlJTIyJTJDJTIyYW1kJTIyJTNBJTdCJTIyaWQlMjIlM0ElMjIlMjIlN0QlMkMlMjJnbG9iYWxzJTIyJTNBJTdCJTdEJTdEJTJDJTIyZXhhbXBsZSUyMiUzQW51bGwlN0Q=): + +```js +// input +// main.js +import value from './other-entry.js'; +console.log(value); + +// other-entry.js +import externalValue from 'external'; +export default 2 * externalValue; + +// output +// main.js +import 'external'; // this import has been hoisted from other-entry.js +import value from './other-entry.js'; +console.log(value); + +// other-entry.js +import externalValue from 'external'; +var value = 2 * externalValue; +export default value; +``` + +This does not affect code execution order or behaviour, but it will speed up how your code is loaded and parsed. Without this optimization, a JavaScript engine needs to perform the following steps to run `main.js`: +1. Load and parse `main.js`. At the end, an import to `other-entry.js` will be discovered. +2. Load and parse `other-entry.js`. At the end, an import to `external` will be discovered. +3. Load and parse `external`. +4. Execute `main.js`. + +With this optimization, a JavaScript engine will discover all transitive dependencies after parsing an entry module, avoiding the waterfall: +1. Load and parse `main.js`. At the end, imports to `other-entry.js` and `external` will be discovered. +2. Load and parse `other-entry.js` and `external`. The import of `other-entry.js` is already loaded and parsed. +3. Execute `main.js`. + +There may be situations where this optimization is not desired, in which case you can turn it off via the [`output.hoistTransitiveImports`](guide/en/#outputhoisttransitiveimports) option. This optimization is also never applied when using the [`preserveModules`](guide/en/#preservemodules) option. + #### Is Rollup meant for building libraries or applications? Rollup is already used by many major JavaScript libraries, and can also be used to build the vast majority of applications. However if you want to use code-splitting or dynamic imports with older browsers, you will need an additional runtime to handle loading missing chunks. We recommend using the [SystemJS Production Build](https://github.com/systemjs/systemjs#browser-production) as it integrates nicely with Rollup's system format output and is capable of properly handling all the ES module live bindings and re-export edge cases. Alternatively, an AMD loader can be used as well. diff --git a/docs/999-big-list-of-options.md b/docs/999-big-list-of-options.md index d584349b690..c0ef48a5853 100755 --- a/docs/999-big-list-of-options.md +++ b/docs/999-big-list-of-options.md @@ -466,6 +466,13 @@ Default: `false` Whether or not to extend the global variable defined by the `name` option in `umd` or `iife` formats. When `true`, the global variable will be defined as `(global.name = global.name || {})`. When false, the global defined by `name` will be overwritten like `(global.name = {})`. +#### output.hoistTransitiveImports +Type: `boolean`
+CLI: `--hoistTransitiveImports`/`--no-hoistTransitiveImports`
+Default: `true` + +By default when creating multiple chunks, transitive imports of entry chunks will be added as empty imports to the entry chunks. See ["Why do additional imports turn up in my entry chunks when code-splitting?"](guide/en/#why-do-additional-imports-turn-up-in-my-entry-chunks-when-code-splitting) for details and background. Setting this option to `false` will disable this behaviour. This option is ignored when using the [`preserveModules`](guide/en/#preservemodules) option as here, imports will never be hoisted. + #### output.interop Type: `boolean`
CLI: `--interop`/`--no-interop`
diff --git a/src/Chunk.ts b/src/Chunk.ts index 542747dd375..59fc3a0f8d3 100644 --- a/src/Chunk.ts +++ b/src/Chunk.ts @@ -554,7 +554,11 @@ export default class Chunk { } } // for static and dynamic entry points, inline the execution list to avoid loading latency - if (!this.graph.preserveModules && this.facadeModule !== null) { + if ( + options.hoistTransitiveImports !== false && + !this.graph.preserveModules && + this.facadeModule !== null + ) { for (const dep of this.dependencies) { if (dep instanceof Chunk) this.inlineChunkDependencies(dep, true); } diff --git a/src/rollup/types.d.ts b/src/rollup/types.d.ts index 064fa10709e..c84992232e1 100644 --- a/src/rollup/types.d.ts +++ b/src/rollup/types.d.ts @@ -484,6 +484,7 @@ export interface OutputOptions { format?: ModuleFormat; freeze?: boolean; globals?: GlobalsOption; + hoistTransitiveImports?: boolean; importMetaUrl?: (chunkId: string, moduleId: string) => string; indent?: boolean; interop?: boolean; diff --git a/src/utils/mergeOptions.ts b/src/utils/mergeOptions.ts index ca62fc73dcd..46f71f43d23 100644 --- a/src/utils/mergeOptions.ts +++ b/src/utils/mergeOptions.ts @@ -286,6 +286,7 @@ function getOutputOptions( format, freeze: getOption('freeze', true), globals: getOption('globals'), + hoistTransitiveImports: getOption('hoistTransitiveImports', true), indent: getOption('indent', true), interop: getOption('interop', true), intro: getOption('intro'), diff --git a/test/chunking-form/samples/avoid-chunk-import-hoisting/_config.js b/test/chunking-form/samples/avoid-chunk-import-hoisting/_config.js new file mode 100644 index 00000000000..e46fa0fd615 --- /dev/null +++ b/test/chunking-form/samples/avoid-chunk-import-hoisting/_config.js @@ -0,0 +1,10 @@ +module.exports = { + description: 'avoids hoisting transitive dependencies via flag', + options: { + input: ['main1.js', 'main2.js'], + external: ['lib'], + output: { + hoistTransitiveImports: false + } + } +}; diff --git a/test/chunking-form/samples/avoid-chunk-import-hoisting/_expected/amd/generated-dep.js b/test/chunking-form/samples/avoid-chunk-import-hoisting/_expected/amd/generated-dep.js new file mode 100644 index 00000000000..4fc2383a2a2 --- /dev/null +++ b/test/chunking-form/samples/avoid-chunk-import-hoisting/_expected/amd/generated-dep.js @@ -0,0 +1,9 @@ +define(['exports', 'lib'], function (exports, value) { 'use strict'; + + value = value && value.hasOwnProperty('default') ? value['default'] : value; + + var dep = 2 * value; + + exports.dep = dep; + +}); diff --git a/test/chunking-form/samples/avoid-chunk-import-hoisting/_expected/amd/main1.js b/test/chunking-form/samples/avoid-chunk-import-hoisting/_expected/amd/main1.js new file mode 100644 index 00000000000..4e7c5c84f74 --- /dev/null +++ b/test/chunking-form/samples/avoid-chunk-import-hoisting/_expected/amd/main1.js @@ -0,0 +1,5 @@ +define(['./generated-dep'], function (dep) { 'use strict'; + + console.log('main1', dep.dep); + +}); diff --git a/test/chunking-form/samples/avoid-chunk-import-hoisting/_expected/amd/main2.js b/test/chunking-form/samples/avoid-chunk-import-hoisting/_expected/amd/main2.js new file mode 100644 index 00000000000..e5f5685b5a8 --- /dev/null +++ b/test/chunking-form/samples/avoid-chunk-import-hoisting/_expected/amd/main2.js @@ -0,0 +1,5 @@ +define(['./generated-dep'], function (dep) { 'use strict'; + + console.log('main2', dep.dep); + +}); diff --git a/test/chunking-form/samples/avoid-chunk-import-hoisting/_expected/cjs/generated-dep.js b/test/chunking-form/samples/avoid-chunk-import-hoisting/_expected/cjs/generated-dep.js new file mode 100644 index 00000000000..cea89a71035 --- /dev/null +++ b/test/chunking-form/samples/avoid-chunk-import-hoisting/_expected/cjs/generated-dep.js @@ -0,0 +1,9 @@ +'use strict'; + +function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } + +var value = _interopDefault(require('lib')); + +var dep = 2 * value; + +exports.dep = dep; diff --git a/test/chunking-form/samples/avoid-chunk-import-hoisting/_expected/cjs/main1.js b/test/chunking-form/samples/avoid-chunk-import-hoisting/_expected/cjs/main1.js new file mode 100644 index 00000000000..39589dfb1d7 --- /dev/null +++ b/test/chunking-form/samples/avoid-chunk-import-hoisting/_expected/cjs/main1.js @@ -0,0 +1,5 @@ +'use strict'; + +var dep = require('./generated-dep.js'); + +console.log('main1', dep.dep); diff --git a/test/chunking-form/samples/avoid-chunk-import-hoisting/_expected/cjs/main2.js b/test/chunking-form/samples/avoid-chunk-import-hoisting/_expected/cjs/main2.js new file mode 100644 index 00000000000..8dc8e8f3922 --- /dev/null +++ b/test/chunking-form/samples/avoid-chunk-import-hoisting/_expected/cjs/main2.js @@ -0,0 +1,5 @@ +'use strict'; + +var dep = require('./generated-dep.js'); + +console.log('main2', dep.dep); diff --git a/test/chunking-form/samples/avoid-chunk-import-hoisting/_expected/es/generated-dep.js b/test/chunking-form/samples/avoid-chunk-import-hoisting/_expected/es/generated-dep.js new file mode 100644 index 00000000000..d59a457c4ea --- /dev/null +++ b/test/chunking-form/samples/avoid-chunk-import-hoisting/_expected/es/generated-dep.js @@ -0,0 +1,5 @@ +import value from 'lib'; + +var dep = 2 * value; + +export { dep as d }; diff --git a/test/chunking-form/samples/avoid-chunk-import-hoisting/_expected/es/main1.js b/test/chunking-form/samples/avoid-chunk-import-hoisting/_expected/es/main1.js new file mode 100644 index 00000000000..06223528d3f --- /dev/null +++ b/test/chunking-form/samples/avoid-chunk-import-hoisting/_expected/es/main1.js @@ -0,0 +1,3 @@ +import { d as dep } from './generated-dep.js'; + +console.log('main1', dep); diff --git a/test/chunking-form/samples/avoid-chunk-import-hoisting/_expected/es/main2.js b/test/chunking-form/samples/avoid-chunk-import-hoisting/_expected/es/main2.js new file mode 100644 index 00000000000..8364d7997b5 --- /dev/null +++ b/test/chunking-form/samples/avoid-chunk-import-hoisting/_expected/es/main2.js @@ -0,0 +1,3 @@ +import { d as dep } from './generated-dep.js'; + +console.log('main2', dep); diff --git a/test/chunking-form/samples/avoid-chunk-import-hoisting/_expected/system/generated-dep.js b/test/chunking-form/samples/avoid-chunk-import-hoisting/_expected/system/generated-dep.js new file mode 100644 index 00000000000..f570c8632a9 --- /dev/null +++ b/test/chunking-form/samples/avoid-chunk-import-hoisting/_expected/system/generated-dep.js @@ -0,0 +1,14 @@ +System.register(['lib'], function (exports) { + 'use strict'; + var value; + return { + setters: [function (module) { + value = module.default; + }], + execute: function () { + + var dep = exports('d', 2 * value); + + } + }; +}); diff --git a/test/chunking-form/samples/avoid-chunk-import-hoisting/_expected/system/main1.js b/test/chunking-form/samples/avoid-chunk-import-hoisting/_expected/system/main1.js new file mode 100644 index 00000000000..d13859ddc6d --- /dev/null +++ b/test/chunking-form/samples/avoid-chunk-import-hoisting/_expected/system/main1.js @@ -0,0 +1,14 @@ +System.register(['./generated-dep.js'], function () { + 'use strict'; + var dep; + return { + setters: [function (module) { + dep = module.d; + }], + execute: function () { + + console.log('main1', dep); + + } + }; +}); diff --git a/test/chunking-form/samples/avoid-chunk-import-hoisting/_expected/system/main2.js b/test/chunking-form/samples/avoid-chunk-import-hoisting/_expected/system/main2.js new file mode 100644 index 00000000000..ab882c0f261 --- /dev/null +++ b/test/chunking-form/samples/avoid-chunk-import-hoisting/_expected/system/main2.js @@ -0,0 +1,14 @@ +System.register(['./generated-dep.js'], function () { + 'use strict'; + var dep; + return { + setters: [function (module) { + dep = module.d; + }], + execute: function () { + + console.log('main2', dep); + + } + }; +}); diff --git a/test/chunking-form/samples/avoid-chunk-import-hoisting/dep.js b/test/chunking-form/samples/avoid-chunk-import-hoisting/dep.js new file mode 100644 index 00000000000..d668bd82ef2 --- /dev/null +++ b/test/chunking-form/samples/avoid-chunk-import-hoisting/dep.js @@ -0,0 +1,3 @@ +import value from 'lib'; + +export default 2 * value; diff --git a/test/chunking-form/samples/avoid-chunk-import-hoisting/main1.js b/test/chunking-form/samples/avoid-chunk-import-hoisting/main1.js new file mode 100644 index 00000000000..d2077c27cac --- /dev/null +++ b/test/chunking-form/samples/avoid-chunk-import-hoisting/main1.js @@ -0,0 +1,3 @@ +import dep from './dep.js'; + +console.log('main1', dep); diff --git a/test/chunking-form/samples/avoid-chunk-import-hoisting/main2.js b/test/chunking-form/samples/avoid-chunk-import-hoisting/main2.js new file mode 100644 index 00000000000..8a213ce9bb9 --- /dev/null +++ b/test/chunking-form/samples/avoid-chunk-import-hoisting/main2.js @@ -0,0 +1,3 @@ +import dep from './dep.js'; + +console.log('main2', dep); diff --git a/test/misc/optionList.js b/test/misc/optionList.js index 256b2c2dbba..9332c129202 100644 --- a/test/misc/optionList.js +++ b/test/misc/optionList.js @@ -1,3 +1,3 @@ exports.input = 'acorn, acornInjectPlugins, cache, chunkGroupingSize, context, experimentalCacheExpiry, experimentalOptimizeChunks, external, inlineDynamicImports, input, manualChunks, moduleContext, onwarn, perf, plugins, preserveModules, preserveSymlinks, shimMissingExports, strictDeprecations, treeshake, watch'; -exports.flags = 'acorn, acornInjectPlugins, amd, assetFileNames, banner, c, cache, chunkFileNames, chunkGroupingSize, compact, config, context, d, dir, dynamicImportFunction, e, entryFileNames, environment, esModule, experimentalCacheExpiry, experimentalOptimizeChunks, exports, extend, external, externalLiveBindings, f, file, footer, format, freeze, g, globals, h, i, indent, inlineDynamicImports, input, interop, intro, m, manualChunks, moduleContext, n, name, namespaceToStringTag, noConflict, o, onwarn, outro, paths, perf, plugins, preferConst, preserveModules, preserveSymlinks, shimMissingExports, silent, sourcemap, sourcemapExcludeSources, sourcemapFile, stdin, strict, strictDeprecations, treeshake, v, w, watch'; -exports.output = 'amd, assetFileNames, banner, chunkFileNames, compact, dir, dynamicImportFunction, entryFileNames, esModule, exports, extend, externalLiveBindings, file, footer, format, freeze, globals, indent, interop, intro, name, namespaceToStringTag, noConflict, outro, paths, plugins, preferConst, sourcemap, sourcemapExcludeSources, sourcemapFile, sourcemapPathTransform, strict'; +exports.flags = 'acorn, acornInjectPlugins, amd, assetFileNames, banner, c, cache, chunkFileNames, chunkGroupingSize, compact, config, context, d, dir, dynamicImportFunction, e, entryFileNames, environment, esModule, experimentalCacheExpiry, experimentalOptimizeChunks, exports, extend, external, externalLiveBindings, f, file, footer, format, freeze, g, globals, h, hoistTransitiveImports, i, indent, inlineDynamicImports, input, interop, intro, m, manualChunks, moduleContext, n, name, namespaceToStringTag, noConflict, o, onwarn, outro, paths, perf, plugins, preferConst, preserveModules, preserveSymlinks, shimMissingExports, silent, sourcemap, sourcemapExcludeSources, sourcemapFile, stdin, strict, strictDeprecations, treeshake, v, w, watch'; +exports.output = 'amd, assetFileNames, banner, chunkFileNames, compact, dir, dynamicImportFunction, entryFileNames, esModule, exports, extend, externalLiveBindings, file, footer, format, freeze, globals, hoistTransitiveImports, indent, interop, intro, name, namespaceToStringTag, noConflict, outro, paths, plugins, preferConst, sourcemap, sourcemapExcludeSources, sourcemapFile, sourcemapPathTransform, strict'; From 5f72e5f9d8007e07346a5e4ac24832404f2ae373 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Sun, 26 Jan 2020 16:42:30 +0100 Subject: [PATCH 2/2] Add FAQ how to add polyfills --- docs/06-faqs.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/docs/06-faqs.md b/docs/06-faqs.md index 97f9dd9a012..3fcb532f70a 100755 --- a/docs/06-faqs.md +++ b/docs/06-faqs.md @@ -63,6 +63,39 @@ With this optimization, a JavaScript engine will discover all transitive depende There may be situations where this optimization is not desired, in which case you can turn it off via the [`output.hoistTransitiveImports`](guide/en/#outputhoisttransitiveimports) option. This optimization is also never applied when using the [`preserveModules`](guide/en/#preservemodules) option. +#### How do I add polyfills to a Rollup bundle? + +Even though Rollup will usually try to maintain exact module execution order when bundling, there are two situations when this is not always the case: code-splitting and external dependencies. The problem is most obvious with external dependencies, see the following [example](https://rollupjs.org/repl/?shareable=JTdCJTIybW9kdWxlcyUyMiUzQSU1QiU3QiUyMm5hbWUlMjIlM0ElMjJtYWluLmpzJTIyJTJDJTIyY29kZSUyMiUzQSUyMmltcG9ydCUyMCcuJTJGcG9seWZpbGwuanMnJTNCJTVDbmltcG9ydCUyMCdleHRlcm5hbCclM0IlNUNuY29uc29sZS5sb2coJ21haW4nKSUzQiUyMiUyQyUyMmlzRW50cnklMjIlM0F0cnVlJTdEJTJDJTdCJTIybmFtZSUyMiUzQSUyMnBvbHlmaWxsLmpzJTIyJTJDJTIyY29kZSUyMiUzQSUyMmNvbnNvbGUubG9nKCdwb2x5ZmlsbCcpJTNCJTIyJTJDJTIyaXNFbnRyeSUyMiUzQWZhbHNlJTdEJTVEJTJDJTIyb3B0aW9ucyUyMiUzQSU3QiUyMmZvcm1hdCUyMiUzQSUyMmVzbSUyMiUyQyUyMm5hbWUlMjIlM0ElMjJteUJ1bmRsZSUyMiUyQyUyMmFtZCUyMiUzQSU3QiUyMmlkJTIyJTNBJTIyJTIyJTdEJTJDJTIyZ2xvYmFscyUyMiUzQSU3QiU3RCU3RCUyQyUyMmV4YW1wbGUlMjIlM0FudWxsJTdE): + +```js +// main.js +import './polyfill.js'; +import 'external'; +console.log('main'); + +// polyfill.js +console.log('polyfill'); +``` + +Here the execution order is `polyfill.js` → `external` → `main.js`. Now when you bundle the code, you will get + +```js +import 'external'; +console.log('polyfill'); +console.log('main'); +``` + +with the execution order `external` → `polyfill.js` → `main.js`. This is not a problem caused by Rollup putting the `import` at the top of the bundle—imports are always executed first, no matter where they are located in the file. This problem can be solved by creating more chunks: If `dep.js` ends up in a different chunk than `main.js`, [correct execution order will be preserved](https://rollupjs.org/repl/?shareable=JTdCJTIybW9kdWxlcyUyMiUzQSU1QiU3QiUyMm5hbWUlMjIlM0ElMjJtYWluLmpzJTIyJTJDJTIyY29kZSUyMiUzQSUyMmltcG9ydCUyMCcuJTJGcG9seWZpbGwuanMnJTNCJTVDbmltcG9ydCUyMCdleHRlcm5hbCclM0IlNUNuY29uc29sZS5sb2coJ21haW4nKSUzQiUyMiUyQyUyMmlzRW50cnklMjIlM0F0cnVlJTdEJTJDJTdCJTIybmFtZSUyMiUzQSUyMnBvbHlmaWxsLmpzJTIyJTJDJTIyY29kZSUyMiUzQSUyMmNvbnNvbGUubG9nKCdwb2x5ZmlsbCcpJTNCJTIyJTJDJTIyaXNFbnRyeSUyMiUzQXRydWUlN0QlNUQlMkMlMjJvcHRpb25zJTIyJTNBJTdCJTIyZm9ybWF0JTIyJTNBJTIyZXNtJTIyJTJDJTIybmFtZSUyMiUzQSUyMm15QnVuZGxlJTIyJTJDJTIyYW1kJTIyJTNBJTdCJTIyaWQlMjIlM0ElMjIlMjIlN0QlMkMlMjJnbG9iYWxzJTIyJTNBJTdCJTdEJTdEJTJDJTIyZXhhbXBsZSUyMiUzQW51bGwlN0Q=). However there is not yet an automatic way to do this in Rollup. For code-splitting, the situation is similar as Rollup is trying to create as few chunks as possible while making sure no code is executed that is not needed. + +For most code this is not a problem, because Rollup can guarantee: + +> If module A imports module B and there are no circular imports, then B will always be executed before A. + +This is however a problem for polyfills, as those usually need to be executed first but it is usually not desired to place an import of the polyfill in every single module. Luckily, this is not needed: + +1. If there are no external dependencies that depend on the polyfill, it is enough to add an import of the polyfill as first statement to each static entry point. +2. Otherwise, additionally making the polyfill a separate entry or [manual chunk](guide/en/#manualchunks) will always make sure it is executed first. + #### Is Rollup meant for building libraries or applications? Rollup is already used by many major JavaScript libraries, and can also be used to build the vast majority of applications. However if you want to use code-splitting or dynamic imports with older browsers, you will need an additional runtime to handle loading missing chunks. We recommend using the [SystemJS Production Build](https://github.com/systemjs/systemjs#browser-production) as it integrates nicely with Rollup's system format output and is capable of properly handling all the ES module live bindings and re-export edge cases. Alternatively, an AMD loader can be used as well.