diff --git a/docs/999-big-list-of-options.md b/docs/999-big-list-of-options.md index acd4735a55b..ca20a3acff9 100755 --- a/docs/999-big-list-of-options.md +++ b/docs/999-big-list-of-options.md @@ -145,7 +145,7 @@ Alternatively, supply a function that will turn an external module ID into a glo When given as a command line argument, it should be a comma-separated list of `id:variableName` pairs: -```bash +``` rollup -i src/main.js ... -g jquery:$,underscore:_ ``` @@ -263,11 +263,31 @@ Default: `false` This will inline dynamic imports instead of creating new chunks to create a single bundle. Only possible if a single input is provided. #### manualChunks -Type: `{ [chunkAlias: string]: string[] }` +Type: `{ [chunkAlias: string]: string[] } | ((id: string) => string | void)` + +Allows the creation of custom shared common chunks. When using the object form, each property represents a chunk that contains the listed modules and all their dependencies if they are part of the module graph unless they are already in another manual chunk. The name of the chunk will be determined by the property key. + +Note that it is not necessary for the listed modules themselves to be be part of the module graph, which is useful if you are working with `rollup-plugin-node-resolve` and use deep imports from packages. For instance + +``` +manualChunks: { + lodash: ['lodash'] +} +``` + +will put all lodash modules into a manual chunk even if you are only using imports of the form `import get from 'lodash/get'`. -Allows the creation of custom shared common chunks. Provides an alias for the chunk and the list of modules to include in that chunk. Modules are bundled into the chunk along with their dependencies. If a module is already in a previous chunk, then the chunk will reference it there. Modules defined into chunks this way are considered to be entry points that can execute independently to any parent importers. +When using the function form, each resolved module id will be passed to the function. If a string is returned, the module and all its dependency will be added to the manual chunk with the given name. For instance this will create a `vendor` chunk containing all dependencies inside `node_modules`: + +```javascript +manualChunks(id) { + if (id.includes('node_modules')) { + return 'vendor'; + } +} +``` -Note that manual chunks can change the behaviour of the application if side-effects are triggered before the corresponding modules are actually used. +Be aware that manual chunks can change the behaviour of the application if side-effects are triggered before the corresponding modules are actually used. #### onwarn Type: `(warning: RollupWarning, defaultHandler: (warning: string | RollupWarning) => void) => void;` diff --git a/src/Graph.ts b/src/Graph.ts index 5a20e022243..dd40cb55caa 100644 --- a/src/Graph.ts +++ b/src/Graph.ts @@ -12,6 +12,7 @@ import { ModuleLoader, UnresolvedModuleWithAlias } from './ModuleLoader'; import { Asset, InputOptions, + ManualChunksOption, ModuleJSON, OutputBundle, RollupCache, @@ -191,13 +192,14 @@ export default class Graph { this, this.moduleById, this.pluginDriver, - options.external + options.external, + typeof options.manualChunks === 'function' && options.manualChunks ); } build( entryModules: string | string[] | Record, - manualChunks: Record | void, + manualChunks: ManualChunksOption | void, inlineDynamicImports: boolean ): Promise { // Phase 1 – discovery. We load the entry module and find which @@ -208,7 +210,9 @@ export default class Graph { return Promise.all([ this.moduleLoader.addEntryModules(normalizeEntryModules(entryModules), true), - manualChunks && this.moduleLoader.addManualChunks(manualChunks) + manualChunks && + typeof manualChunks === 'object' && + this.moduleLoader.addManualChunks(manualChunks) ]).then(([{ entryModules, manualChunkModulesByAlias }]) => { if (entryModules.length === 0) { throw new Error('You must supply options.input to rollup'); diff --git a/src/ModuleLoader.ts b/src/ModuleLoader.ts index 419b33ec6e2..140d83c0049 100644 --- a/src/ModuleLoader.ts +++ b/src/ModuleLoader.ts @@ -4,6 +4,7 @@ import Graph from './Graph'; import Module from './Module'; import { ExternalOption, + GetManualChunk, IsExternal, ModuleJSON, ResolvedId, @@ -36,7 +37,7 @@ export interface UnresolvedModuleWithAlias { } interface UnresolvedEntryModuleWithAlias extends UnresolvedModuleWithAlias { - isManualChunkEntry?: boolean; + manualChunkAlias?: string; } function normalizeRelativeExternalId(importer: string, source: string) { @@ -50,6 +51,7 @@ export class ModuleLoader { { module: Module | null; name: string } >(); private readonly entryModules: Module[] = []; + private readonly getManualChunk: GetManualChunk; private readonly graph: Graph; private latestLoadModulesPromise: Promise = Promise.resolve(); private readonly manualChunkModules: Record = {}; @@ -60,7 +62,8 @@ export class ModuleLoader { graph: Graph, modulesById: Map, pluginDriver: PluginDriver, - external: ExternalOption + external: ExternalOption, + getManualChunk: GetManualChunk | null ) { this.graph = graph; this.modulesById = modulesById; @@ -72,6 +75,7 @@ export class ModuleLoader { const ids = new Set(Array.isArray(external) ? external : external ? [external] : []); this.isExternal = id => ids.has(id); } + this.getManualChunk = typeof getManualChunk === 'function' ? getManualChunk : () => null; } addEntryModuleAndGetReferenceId(unresolvedEntryModule: UnresolvedModuleWithAlias): string { @@ -127,17 +131,17 @@ export class ModuleLoader { for (const alias of Object.keys(manualChunks)) { const manualChunkIds = manualChunks[alias]; for (const unresolvedId of manualChunkIds) { - unresolvedManualChunks.push({ alias, unresolvedId, isManualChunkEntry: true }); + unresolvedManualChunks.push({ alias: null, unresolvedId, manualChunkAlias: alias }); } } const loadNewManualChunkModulesPromise = Promise.all( unresolvedManualChunks.map(this.loadEntryModule) ).then(manualChunkModules => { - for (const module of manualChunkModules) { - if (!this.manualChunkModules[module.manualChunkAlias]) { - this.manualChunkModules[module.manualChunkAlias] = []; - } - this.manualChunkModules[module.manualChunkAlias].push(module); + for (let index = 0; index < manualChunkModules.length; index++) { + this.addToManualChunk( + unresolvedManualChunks[index].manualChunkAlias, + manualChunkModules[index] + ); } }); @@ -164,6 +168,17 @@ export class ModuleLoader { ).then((result: ResolveIdResult) => this.normalizeResolveIdResult(result, importer, source)); } + private addToManualChunk(alias: string, module: Module) { + if (module.manualChunkAlias !== null && module.manualChunkAlias !== alias) { + error(errCannotAssignModuleToChunk(module.id, alias, module.manualChunkAlias)); + } + module.manualChunkAlias = alias; + if (!this.manualChunkModules[alias]) { + this.manualChunkModules[alias] = []; + } + this.manualChunkModules[alias].push(module); + } + private awaitLoadModulesPromise(loadNewModulesPromise: Promise): Promise { this.latestLoadModulesPromise = Promise.all([ loadNewModulesPromise, @@ -218,6 +233,10 @@ export class ModuleLoader { const module: Module = new Module(this.graph, id); this.modulesById.set(id, module); + const manualChunkAlias = this.getManualChunk(id); + if (typeof manualChunkAlias === 'string') { + this.addToManualChunk(manualChunkAlias, module); + } timeStart('load modules', 3); return Promise.resolve( @@ -329,11 +348,7 @@ export class ModuleLoader { return resolvedId; } - private loadEntryModule = ({ - alias, - unresolvedId, - isManualChunkEntry - }: UnresolvedEntryModuleWithAlias): Promise => + private loadEntryModule = ({ alias, unresolvedId }: UnresolvedModuleWithAlias): Promise => this.pluginDriver .hookFirst('resolveId', [unresolvedId, undefined]) .then((resolveIdResult: ResolveIdResult) => { @@ -351,13 +366,6 @@ export class ModuleLoader { if (typeof id === 'string') { return this.fetchModule(id, undefined).then(module => { if (alias !== null) { - if (isManualChunkEntry) { - if (module.manualChunkAlias !== null && module.manualChunkAlias !== alias) { - error(errCannotAssignModuleToChunk(module.id, alias, module.manualChunkAlias)); - } - module.manualChunkAlias = alias; - return module; - } if (module.chunkAlias !== null && module.chunkAlias !== alias) { error(errCannotAssignModuleToChunk(module.id, alias, module.chunkAlias)); } diff --git a/src/rollup/types.d.ts b/src/rollup/types.d.ts index 1f1b37736cf..d2a83d7bff2 100644 --- a/src/rollup/types.d.ts +++ b/src/rollup/types.d.ts @@ -195,7 +195,6 @@ export type RenderChunkHook = ( | string | null; -// TODO this should probably return ResolveIdResult export type ResolveDynamicImportHook = ( this: PluginContext, specifier: string | ESTree.Node, @@ -315,9 +314,12 @@ export interface TreeshakingOptions { pureExternalModules?: boolean; } +export type GetManualChunk = (id: string) => string | void; + export type ExternalOption = string[] | IsExternal; export type GlobalsOption = { [name: string]: string } | ((name: string) => string); export type InputOption = string | string[] | { [entryAlias: string]: string }; +export type ManualChunksOption = { [chunkAlias: string]: string[] } | GetManualChunk; export interface InputOptions { acorn?: any; @@ -331,7 +333,7 @@ export interface InputOptions { external?: ExternalOption; inlineDynamicImports?: boolean; input?: InputOption; - manualChunks?: { [chunkAlias: string]: string[] }; + manualChunks?: ManualChunksOption; moduleContext?: ((id: string) => string) | { [id: string]: string }; onwarn?: WarningHandler; perf?: boolean; diff --git a/src/utils/mergeOptions.ts b/src/utils/mergeOptions.ts index 37016d94b5f..63d2a2fcb14 100644 --- a/src/utils/mergeOptions.ts +++ b/src/utils/mergeOptions.ts @@ -56,6 +56,7 @@ const getOnWarn = ( ? warning => config.onwarn(warning, defaultOnWarnHandler) : defaultOnWarnHandler; +// TODO Lukas manual chunks should receive the same treatment const getExternal = (config: GenericConfigObject, command: GenericConfigObject) => { const configExternal = config.external; return typeof configExternal === 'function' diff --git a/test/chunking-form/samples/manual-chunks-function/_config.js b/test/chunking-form/samples/manual-chunks-function/_config.js new file mode 100644 index 00000000000..0ea976037f8 --- /dev/null +++ b/test/chunking-form/samples/manual-chunks-function/_config.js @@ -0,0 +1,12 @@ +module.exports = { + description: 'allows to define manual chunks via a function', + options: { + input: ['main-a'], + manualChunks(id) { + if (id[id.length - 5] === '-') { + console.log(id, id[id.length - 4]); + return `chunk-${id[id.length - 4]}`; + } + } + } +}; diff --git a/test/chunking-form/samples/manual-chunks-function/_expected/amd/chunk-a.js b/test/chunking-form/samples/manual-chunks-function/_expected/amd/chunk-a.js new file mode 100644 index 00000000000..6f202bfb172 --- /dev/null +++ b/test/chunking-form/samples/manual-chunks-function/_expected/amd/chunk-a.js @@ -0,0 +1,9 @@ +define(['./generated-chunk-b', './generated-chunk-c'], function (__chunk_1, __chunk_2) { 'use strict'; + + console.log('dep1'); + + console.log('dep-a'); + + console.log('main-a'); + +}); diff --git a/test/chunking-form/samples/manual-chunks-function/_expected/amd/generated-chunk-b.js b/test/chunking-form/samples/manual-chunks-function/_expected/amd/generated-chunk-b.js new file mode 100644 index 00000000000..234e6a42b0c --- /dev/null +++ b/test/chunking-form/samples/manual-chunks-function/_expected/amd/generated-chunk-b.js @@ -0,0 +1,7 @@ +define(function () { 'use strict'; + + console.log('dep2'); + + console.log('dep-b'); + +}); diff --git a/test/chunking-form/samples/manual-chunks-function/_expected/amd/generated-chunk-c.js b/test/chunking-form/samples/manual-chunks-function/_expected/amd/generated-chunk-c.js new file mode 100644 index 00000000000..ac30fde69d7 --- /dev/null +++ b/test/chunking-form/samples/manual-chunks-function/_expected/amd/generated-chunk-c.js @@ -0,0 +1,5 @@ +define(['./generated-chunk-b'], function (__chunk_1) { 'use strict'; + + console.log('dep-c'); + +}); diff --git a/test/chunking-form/samples/manual-chunks-function/_expected/cjs/chunk-a.js b/test/chunking-form/samples/manual-chunks-function/_expected/cjs/chunk-a.js new file mode 100644 index 00000000000..d65da8d78b5 --- /dev/null +++ b/test/chunking-form/samples/manual-chunks-function/_expected/cjs/chunk-a.js @@ -0,0 +1,10 @@ +'use strict'; + +require('./generated-chunk-b.js'); +require('./generated-chunk-c.js'); + +console.log('dep1'); + +console.log('dep-a'); + +console.log('main-a'); diff --git a/test/chunking-form/samples/manual-chunks-function/_expected/cjs/generated-chunk-b.js b/test/chunking-form/samples/manual-chunks-function/_expected/cjs/generated-chunk-b.js new file mode 100644 index 00000000000..51b3d3e3d98 --- /dev/null +++ b/test/chunking-form/samples/manual-chunks-function/_expected/cjs/generated-chunk-b.js @@ -0,0 +1,5 @@ +'use strict'; + +console.log('dep2'); + +console.log('dep-b'); diff --git a/test/chunking-form/samples/manual-chunks-function/_expected/cjs/generated-chunk-c.js b/test/chunking-form/samples/manual-chunks-function/_expected/cjs/generated-chunk-c.js new file mode 100644 index 00000000000..d8349602bd6 --- /dev/null +++ b/test/chunking-form/samples/manual-chunks-function/_expected/cjs/generated-chunk-c.js @@ -0,0 +1,5 @@ +'use strict'; + +require('./generated-chunk-b.js'); + +console.log('dep-c'); diff --git a/test/chunking-form/samples/manual-chunks-function/_expected/es/chunk-a.js b/test/chunking-form/samples/manual-chunks-function/_expected/es/chunk-a.js new file mode 100644 index 00000000000..ae2fdbf85e5 --- /dev/null +++ b/test/chunking-form/samples/manual-chunks-function/_expected/es/chunk-a.js @@ -0,0 +1,8 @@ +import './generated-chunk-b.js'; +import './generated-chunk-c.js'; + +console.log('dep1'); + +console.log('dep-a'); + +console.log('main-a'); diff --git a/test/chunking-form/samples/manual-chunks-function/_expected/es/generated-chunk-b.js b/test/chunking-form/samples/manual-chunks-function/_expected/es/generated-chunk-b.js new file mode 100644 index 00000000000..16fb2a90267 --- /dev/null +++ b/test/chunking-form/samples/manual-chunks-function/_expected/es/generated-chunk-b.js @@ -0,0 +1,3 @@ +console.log('dep2'); + +console.log('dep-b'); diff --git a/test/chunking-form/samples/manual-chunks-function/_expected/es/generated-chunk-c.js b/test/chunking-form/samples/manual-chunks-function/_expected/es/generated-chunk-c.js new file mode 100644 index 00000000000..78ee7d95de5 --- /dev/null +++ b/test/chunking-form/samples/manual-chunks-function/_expected/es/generated-chunk-c.js @@ -0,0 +1,3 @@ +import './generated-chunk-b.js'; + +console.log('dep-c'); diff --git a/test/chunking-form/samples/manual-chunks-function/_expected/system/chunk-a.js b/test/chunking-form/samples/manual-chunks-function/_expected/system/chunk-a.js new file mode 100644 index 00000000000..791b503f984 --- /dev/null +++ b/test/chunking-form/samples/manual-chunks-function/_expected/system/chunk-a.js @@ -0,0 +1,15 @@ +System.register(['./generated-chunk-b.js', './generated-chunk-c.js'], function (exports, module) { + 'use strict'; + return { + setters: [function () {}, function () {}], + execute: function () { + + console.log('dep1'); + + console.log('dep-a'); + + console.log('main-a'); + + } + }; +}); diff --git a/test/chunking-form/samples/manual-chunks-function/_expected/system/generated-chunk-b.js b/test/chunking-form/samples/manual-chunks-function/_expected/system/generated-chunk-b.js new file mode 100644 index 00000000000..5fe714bfe08 --- /dev/null +++ b/test/chunking-form/samples/manual-chunks-function/_expected/system/generated-chunk-b.js @@ -0,0 +1,12 @@ +System.register([], function (exports, module) { + 'use strict'; + return { + execute: function () { + + console.log('dep2'); + + console.log('dep-b'); + + } + }; +}); diff --git a/test/chunking-form/samples/manual-chunks-function/_expected/system/generated-chunk-c.js b/test/chunking-form/samples/manual-chunks-function/_expected/system/generated-chunk-c.js new file mode 100644 index 00000000000..6486e19b0b8 --- /dev/null +++ b/test/chunking-form/samples/manual-chunks-function/_expected/system/generated-chunk-c.js @@ -0,0 +1,11 @@ +System.register(['./generated-chunk-b.js'], function (exports, module) { + 'use strict'; + return { + setters: [function () {}], + execute: function () { + + console.log('dep-c'); + + } + }; +}); diff --git a/test/chunking-form/samples/manual-chunks-function/dep-a.js b/test/chunking-form/samples/manual-chunks-function/dep-a.js new file mode 100644 index 00000000000..5cf180c1404 --- /dev/null +++ b/test/chunking-form/samples/manual-chunks-function/dep-a.js @@ -0,0 +1,4 @@ +import './dep-c'; +import './dep1'; + +console.log('dep-a'); diff --git a/test/chunking-form/samples/manual-chunks-function/dep-b.js b/test/chunking-form/samples/manual-chunks-function/dep-b.js new file mode 100644 index 00000000000..67c590f583e --- /dev/null +++ b/test/chunking-form/samples/manual-chunks-function/dep-b.js @@ -0,0 +1,3 @@ +import './dep2'; + +console.log('dep-b'); diff --git a/test/chunking-form/samples/manual-chunks-function/dep-c.js b/test/chunking-form/samples/manual-chunks-function/dep-c.js new file mode 100644 index 00000000000..3934b0ad90e --- /dev/null +++ b/test/chunking-form/samples/manual-chunks-function/dep-c.js @@ -0,0 +1,3 @@ +import './dep2'; + +console.log('dep-c'); diff --git a/test/chunking-form/samples/manual-chunks-function/dep1.js b/test/chunking-form/samples/manual-chunks-function/dep1.js new file mode 100644 index 00000000000..6aa6066cec9 --- /dev/null +++ b/test/chunking-form/samples/manual-chunks-function/dep1.js @@ -0,0 +1 @@ +console.log('dep1'); diff --git a/test/chunking-form/samples/manual-chunks-function/dep2.js b/test/chunking-form/samples/manual-chunks-function/dep2.js new file mode 100644 index 00000000000..f5325d80e8a --- /dev/null +++ b/test/chunking-form/samples/manual-chunks-function/dep2.js @@ -0,0 +1 @@ +console.log('dep2'); diff --git a/test/chunking-form/samples/manual-chunks-function/main-a.js b/test/chunking-form/samples/manual-chunks-function/main-a.js new file mode 100644 index 00000000000..cf97f330a79 --- /dev/null +++ b/test/chunking-form/samples/manual-chunks-function/main-a.js @@ -0,0 +1,4 @@ +import './dep-a'; +import './dep-b'; + +console.log('main-a'); diff --git a/test/function/samples/manual-chunks-conflict/_config.js b/test/function/samples/manual-chunks-conflict/_config.js index e07a6cf5cbd..75d353007fd 100644 --- a/test/function/samples/manual-chunks-conflict/_config.js +++ b/test/function/samples/manual-chunks-conflict/_config.js @@ -9,6 +9,6 @@ module.exports = { }, error: { code: 'INVALID_CHUNK', - message: `Cannot assign dep.js to the "dep1" chunk as it is already in the "dep2" chunk.` + message: `Cannot assign dep.js to the "dep2" chunk as it is already in the "dep1" chunk.` } };