Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Manual chunks function #2831

Merged
merged 62 commits into from May 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
dd1d6a9
Merge compact and non-compact import.meta.url mechanisms
lukastaegert Mar 28, 2019
9c53d24
Extract more common code
lukastaegert Mar 29, 2019
98d5188
Add option to configure import.meta.url resolution
lukastaegert Apr 1, 2019
df1c642
Switch to using a plugin hook
lukastaegert Apr 5, 2019
7276c9b
Extract more common code
lukastaegert Mar 29, 2019
6127570
Move functionality into default plugin
lukastaegert Apr 8, 2019
a3c40fd
Improve SystemJS handling
lukastaegert Apr 9, 2019
660e880
Refactor import.meta.url handling
lukastaegert Mar 31, 2019
e053537
Fix, refactor and test asset emission
lukastaegert Apr 7, 2019
eadfdce
Extract ModuleLoader
lukastaegert Mar 10, 2019
6e46702
Use sets for colouring hashes
lukastaegert Mar 10, 2019
6f3f139
Simplify alias generation
lukastaegert Mar 10, 2019
fb789a6
Attach aliases to modules
lukastaegert Mar 11, 2019
9d54f71
Transform loading into a process that dynamically accepts new entry p…
lukastaegert Mar 11, 2019
4c0c660
Pass alias objects through the module loader
lukastaegert Mar 14, 2019
34a2f43
Implement basic chunk emission
lukastaegert Apr 12, 2019
e24df1f
Allow duplicate entry points again
lukastaegert Apr 14, 2019
148661c
Rename addEntry -> emitEntryChunk
lukastaegert Apr 14, 2019
9a5a98c
Simplify alias handling by immediately assigning a chunkAlias to entry
lukastaegert Apr 14, 2019
98296c3
* Allow manual chunks to contain nested entry points
lukastaegert Apr 15, 2019
70ef241
Manual chunks never conflict with entry points:
lukastaegert Apr 15, 2019
a306a92
Return correct file name if a facade is created for an emitted chunk
lukastaegert Apr 15, 2019
c90f7d7
Start using central error handlers
lukastaegert Apr 15, 2019
27cdcda
Improve plugin driver type, add generic resolveFileUrl hook
lukastaegert Apr 16, 2019
32d3e53
Test new resolveFileUrl hook, make meta properties tree-shakeable
lukastaegert Apr 17, 2019
b7f229f
Move setAssetSource failure tests to function
lukastaegert Apr 17, 2019
b5819c8
Test and extract all errors thrown when emitting assets
lukastaegert Apr 17, 2019
69fb789
Extract and refine error when chunk id cannot be found
lukastaegert Apr 17, 2019
994a89d
Fail if filename is not yet available
lukastaegert Apr 17, 2019
5853e05
Fail when adding a chunk after loading has finished
lukastaegert Apr 18, 2019
8537553
Do not access process.cwd() unchecked
lukastaegert Apr 18, 2019
b445491
Move isExternal to module loader
lukastaegert Apr 18, 2019
746c2b9
Fix typing of resolveId context hook
lukastaegert Apr 18, 2019
2fd3e6d
Refine worker test
lukastaegert Apr 19, 2019
1b810a3
Revert to "wrong" resolveId type as this will probably be fixed in a …
lukastaegert Apr 19, 2019
70197b4
Suppress .js extensions for AMD, fix issue with empty dynamically imp…
lukastaegert Apr 20, 2019
95438f8
Use generated chunk naming scheme for emitted chunks and rename context
lukastaegert Apr 21, 2019
12e7589
Allow emitted chunks to be named
lukastaegert Apr 21, 2019
ccceb93
Add paint worklet example
lukastaegert Apr 22, 2019
48bc0c1
Update documentation
lukastaegert Apr 22, 2019
2b8570d
Add reference ids to resolveFileUrl and replace type
lukastaegert Apr 23, 2019
738d6ca
Do not require `input` to be set if a dynamic entry is emitted
lukastaegert Apr 23, 2019
08b69d2
Use facade module id as [name] for dynamic imports
lukastaegert Apr 24, 2019
6675abb
Update documentation
lukastaegert Apr 24, 2019
35b76f3
Refine pluginDriver types (ported from add-entry branch)
lukastaegert Apr 25, 2019
e7f79dc
Make sure resolveId is always passed either string or null
lukastaegert Apr 26, 2019
1b29f8c
Merge branch 'improve-resolve-id-handling' into improve-id-resolution
lukastaegert Apr 27, 2019
e4157ab
Unify parameter names
lukastaegert Apr 27, 2019
ab0f13f
Add new this.resolve context function
lukastaegert Apr 28, 2019
35944c0
Get rid of dynamic import alias
lukastaegert Apr 29, 2019
125547e
Make sure resolveDynamicImport behaves the same as resolveId if an ob…
lukastaegert May 1, 2019
996d7ed
Test new warnings and errors
lukastaegert May 1, 2019
18d5d38
Mark this.resolveId and this.isExternal as deprecated
lukastaegert May 1, 2019
293cada
Extract more errors
lukastaegert May 1, 2019
f5c27bd
Use error message generators instead of error generators
lukastaegert May 1, 2019
3552fdc
Fix documentation ordering
lukastaegert May 1, 2019
76f65ed
Mark utility functions deprecated in types
lukastaegert May 1, 2019
4ea1620
Use relative ids in error messages
lukastaegert May 2, 2019
efe30cc
Allow manualChunks to be a function
lukastaegert May 2, 2019
3201c39
Only add manual chunks in a single location
lukastaegert May 2, 2019
261d6ca
Add vendor chunk example
lukastaegert May 2, 2019
4421de0
Merge branch 'master' into manual-chunks-function
lukastaegert May 3, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
28 changes: 24 additions & 4 deletions docs/999-big-list-of-options.md
Expand Up @@ -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:_
```

Expand Down Expand Up @@ -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;`
Expand Down
10 changes: 7 additions & 3 deletions src/Graph.ts
Expand Up @@ -12,6 +12,7 @@ import { ModuleLoader, UnresolvedModuleWithAlias } from './ModuleLoader';
import {
Asset,
InputOptions,
ManualChunksOption,
ModuleJSON,
OutputBundle,
RollupCache,
Expand Down Expand Up @@ -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<string, string>,
manualChunks: Record<string, string[]> | void,
manualChunks: ManualChunksOption | void,
inlineDynamicImports: boolean
): Promise<Chunk[]> {
// Phase 1 – discovery. We load the entry module and find which
Expand All @@ -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');
Expand Down
48 changes: 28 additions & 20 deletions src/ModuleLoader.ts
Expand Up @@ -4,6 +4,7 @@ import Graph from './Graph';
import Module from './Module';
import {
ExternalOption,
GetManualChunk,
IsExternal,
ModuleJSON,
ResolvedId,
Expand Down Expand Up @@ -36,7 +37,7 @@ export interface UnresolvedModuleWithAlias {
}

interface UnresolvedEntryModuleWithAlias extends UnresolvedModuleWithAlias {
isManualChunkEntry?: boolean;
manualChunkAlias?: string;
}

function normalizeRelativeExternalId(importer: string, source: string) {
Expand All @@ -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<any> = Promise.resolve();
private readonly manualChunkModules: Record<string, Module[]> = {};
Expand All @@ -60,7 +62,8 @@ export class ModuleLoader {
graph: Graph,
modulesById: Map<string, Module | ExternalModule>,
pluginDriver: PluginDriver,
external: ExternalOption
external: ExternalOption,
getManualChunk: GetManualChunk | null
) {
this.graph = graph;
this.modulesById = modulesById;
Expand All @@ -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 {
Expand Down Expand Up @@ -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]
);
}
});

Expand All @@ -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<T>(loadNewModulesPromise: Promise<T>): Promise<T> {
this.latestLoadModulesPromise = Promise.all([
loadNewModulesPromise,
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -329,11 +348,7 @@ export class ModuleLoader {
return resolvedId;
}

private loadEntryModule = ({
alias,
unresolvedId,
isManualChunkEntry
}: UnresolvedEntryModuleWithAlias): Promise<Module> =>
private loadEntryModule = ({ alias, unresolvedId }: UnresolvedModuleWithAlias): Promise<Module> =>
this.pluginDriver
.hookFirst('resolveId', [unresolvedId, undefined])
.then((resolveIdResult: ResolveIdResult) => {
Expand All @@ -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));
}
Expand Down
6 changes: 4 additions & 2 deletions src/rollup/types.d.ts
Expand Up @@ -195,7 +195,6 @@ export type RenderChunkHook = (
| string
| null;

// TODO this should probably return ResolveIdResult
export type ResolveDynamicImportHook = (
this: PluginContext,
specifier: string | ESTree.Node,
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/utils/mergeOptions.ts
Expand Up @@ -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'
Expand Down
12 changes: 12 additions & 0 deletions 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]}`;
}
}
}
};
@@ -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');

});
@@ -0,0 +1,7 @@
define(function () { 'use strict';

console.log('dep2');

console.log('dep-b');

});
@@ -0,0 +1,5 @@
define(['./generated-chunk-b'], function (__chunk_1) { 'use strict';

console.log('dep-c');

});
@@ -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');
@@ -0,0 +1,5 @@
'use strict';

console.log('dep2');

console.log('dep-b');
@@ -0,0 +1,5 @@
'use strict';

require('./generated-chunk-b.js');

console.log('dep-c');
@@ -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');
@@ -0,0 +1,3 @@
console.log('dep2');

console.log('dep-b');
@@ -0,0 +1,3 @@
import './generated-chunk-b.js';

console.log('dep-c');
@@ -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');

}
};
});
@@ -0,0 +1,12 @@
System.register([], function (exports, module) {
'use strict';
return {
execute: function () {

console.log('dep2');

console.log('dep-b');

}
};
});
@@ -0,0 +1,11 @@
System.register(['./generated-chunk-b.js'], function (exports, module) {
'use strict';
return {
setters: [function () {}],
execute: function () {

console.log('dep-c');

}
};
});
4 changes: 4 additions & 0 deletions test/chunking-form/samples/manual-chunks-function/dep-a.js
@@ -0,0 +1,4 @@
import './dep-c';
import './dep1';

console.log('dep-a');
3 changes: 3 additions & 0 deletions test/chunking-form/samples/manual-chunks-function/dep-b.js
@@ -0,0 +1,3 @@
import './dep2';

console.log('dep-b');
3 changes: 3 additions & 0 deletions test/chunking-form/samples/manual-chunks-function/dep-c.js
@@ -0,0 +1,3 @@
import './dep2';

console.log('dep-c');
1 change: 1 addition & 0 deletions test/chunking-form/samples/manual-chunks-function/dep1.js
@@ -0,0 +1 @@
console.log('dep1');
1 change: 1 addition & 0 deletions test/chunking-form/samples/manual-chunks-function/dep2.js
@@ -0,0 +1 @@
console.log('dep2');
4 changes: 4 additions & 0 deletions test/chunking-form/samples/manual-chunks-function/main-a.js
@@ -0,0 +1,4 @@
import './dep-a';
import './dep-b';

console.log('main-a');
2 changes: 1 addition & 1 deletion test/function/samples/manual-chunks-conflict/_config.js
Expand Up @@ -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.`
}
};