Skip to content

Commit 9023d21

Browse files
authored
Merge pull request webpack#8477 from webpack/feature/better-chunk-ids
improve ids for Long Term Caching
2 parents 180c58d + 3c3ec62 commit 9023d21

File tree

119 files changed

+2898
-2755
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

119 files changed

+2898
-2755
lines changed

declarations/WebpackOptions.d.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -805,9 +805,15 @@ export interface OptimizationOptions {
805805
*/
806806
checkWasmTypes?: boolean;
807807
/**
808-
* Define the algorithm to choose chunk ids (named: readable ids for better debugging, size: numeric ids focused on minimal initial download size, total-size: numeric ids focused on minimal total download size, false: no algorithm used, as custom one can be provided via plugin)
808+
* Define the algorithm to choose chunk ids (named: readable ids for better debugging, deterministic: numeric hash ids for better long term caching, size: numeric ids focused on minimal initial download size, total-size: numeric ids focused on minimal total download size, false: no algorithm used, as custom one can be provided via plugin)
809809
*/
810-
chunkIds?: "natural" | "named" | "size" | "total-size" | false;
810+
chunkIds?:
811+
| "natural"
812+
| "named"
813+
| "deterministic"
814+
| "size"
815+
| "total-size"
816+
| false;
811817
/**
812818
* Concatenate modules when possible to generate less modules, more efficient code and enable more optimizations by the minimizer
813819
*/
@@ -911,10 +917,6 @@ export interface OptimizationSplitChunksOptions {
911917
* Sets the name delimiter for created chunks
912918
*/
913919
automaticNameDelimiter?: string;
914-
/**
915-
* Sets the name prefix for created chunks
916-
*/
917-
automaticNamePrefix?: string;
918920
/**
919921
* Select chunks for determining cache group content (defaults to "initial", "initial" and "all" requires adding these chunks to the HTML)
920922
*/
@@ -927,6 +929,10 @@ export interface OptimizationSplitChunksOptions {
927929
* Sets the template for the filename for created chunks (Only works for initial chunks)
928930
*/
929931
filename?: string;
932+
/**
933+
* Sets the hint for chunk id
934+
*/
935+
idHint?: string;
930936
/**
931937
* Maximum number of requests which are accepted for on-demand loading
932938
*/
@@ -950,7 +956,7 @@ export interface OptimizationSplitChunksOptions {
950956
/**
951957
* Give chunks for this cache group a name (chunks with equal name are merged)
952958
*/
953-
name?: boolean | Function | string;
959+
name?: false | Function | string;
954960
/**
955961
* Priority of this cache group
956962
*/
@@ -1021,7 +1027,7 @@ export interface OptimizationSplitChunksOptions {
10211027
/**
10221028
* Give chunks created a name (chunks with equal name are merged)
10231029
*/
1024-
name?: boolean | Function | string;
1030+
name?: false | Function | string;
10251031
}
10261032
/**
10271033
* This interface was referenced by `WebpackOptions`'s JSON-Schema

lib/Chunk.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ class Chunk {
6666
this.debugId = debugId++;
6767
/** @type {string} */
6868
this.name = name;
69+
/** @type {SortableSet<string>} */
70+
this.idNameHints = new SortableSet();
6971
/** @type {boolean} */
7072
this.preventIntegration = false;
7173
/** @type {string?} */
@@ -413,6 +415,9 @@ class Chunk {
413415
chunkGroup.insertChunk(newChunk, this);
414416
newChunk.addGroup(chunkGroup);
415417
}
418+
for (const idHint of this.idNameHints) {
419+
newChunk.idNameHints.add(idHint);
420+
}
416421
}
417422

418423
/**

lib/ChunkGraph.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -690,6 +690,11 @@ class ChunkGraph {
690690
chunkA.name = chunkB.name;
691691
}
692692

693+
// Merge id name hints
694+
for (const hint of chunkB.idNameHints) {
695+
chunkA.idNameHints.add(hint);
696+
}
697+
693698
// getChunkModules is used here to create a clone, because disconnectChunkAndModule modifies
694699
for (const module of this.getChunkModules(chunkB)) {
695700
this.disconnectChunkAndModule(chunkB, module);

lib/ContextModule.js

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -125,35 +125,35 @@ class ContextModule extends Module {
125125
_createIdentifier() {
126126
let identifier = this.context;
127127
if (this.options.resourceQuery) {
128-
identifier += ` ${this.options.resourceQuery}`;
128+
identifier += `|${this.options.resourceQuery}`;
129129
}
130130
if (this.options.mode) {
131-
identifier += ` ${this.options.mode}`;
131+
identifier += `|${this.options.mode}`;
132132
}
133133
if (!this.options.recursive) {
134-
identifier += " nonrecursive";
134+
identifier += "|nonrecursive";
135135
}
136136
if (this.options.addon) {
137-
identifier += ` ${this.options.addon}`;
137+
identifier += `|${this.options.addon}`;
138138
}
139139
if (this.options.regExp) {
140-
identifier += ` ${this.options.regExp}`;
140+
identifier += `|${this.options.regExp}`;
141141
}
142142
if (this.options.include) {
143-
identifier += ` include: ${this.options.include}`;
143+
identifier += `|include: ${this.options.include}`;
144144
}
145145
if (this.options.exclude) {
146-
identifier += ` exclude: ${this.options.exclude}`;
146+
identifier += `|exclude: ${this.options.exclude}`;
147147
}
148148
if (this.options.groupOptions) {
149-
identifier += ` groupOptions: ${JSON.stringify(
149+
identifier += `|groupOptions: ${JSON.stringify(
150150
this.options.groupOptions
151151
)}`;
152152
}
153153
if (this.options.namespaceObject === "strict") {
154-
identifier += " strict namespace object";
154+
identifier += "|strict namespace object";
155155
} else if (this.options.namespaceObject) {
156-
identifier += " namespace object";
156+
identifier += "|namespace object";
157157
}
158158

159159
return identifier;

lib/NormalModuleFactory.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ class NormalModuleFactory {
211211
null,
212212
new RawModule(
213213
"/* (ignored) */",
214-
`ignored ${context} ${request}`,
214+
`ignored|${request}`,
215215
`${request} (ignored)`
216216
)
217217
);

lib/Stats.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1552,7 +1552,7 @@ class Stats {
15521552
if (childString) {
15531553
if (child.name) {
15541554
colors.normal("Child ");
1555-
colors.bold(child.name);
1555+
colors.bold(child.name.replace(/\|/g, " "));
15561556
colors.normal(":");
15571557
} else {
15581558
colors.normal("Child");

lib/WebpackOptionsApply.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ const SystemPlugin = require("./dependencies/SystemPlugin");
4747
const WarnDeprecatedOptionPlugin = require("./WarnDeprecatedOptionPlugin");
4848
const WarnNoModeSetPlugin = require("./WarnNoModeSetPlugin");
4949

50+
const DeterministicChunkIdsPlugin = require("./ids/DeterministicChunkIdsPlugin");
5051
const DeterministicModuleIdsPlugin = require("./ids/DeterministicModuleIdsPlugin");
5152
const HashedModuleIdsPlugin = require("./ids/HashedModuleIdsPlugin");
5253
const NamedChunkIdsPlugin = require("./ids/NamedChunkIdsPlugin");
@@ -395,9 +396,7 @@ class WebpackOptionsApply extends OptionsApply {
395396
new HashedModuleIdsPlugin().apply(compiler);
396397
break;
397398
case "deterministic":
398-
new DeterministicModuleIdsPlugin({
399-
maxLength: 3
400-
}).apply(compiler);
399+
new DeterministicModuleIdsPlugin().apply(compiler);
401400
break;
402401
case "size":
403402
new OccurrenceModuleIdsPlugin({
@@ -419,6 +418,9 @@ class WebpackOptionsApply extends OptionsApply {
419418
case "named":
420419
new NamedChunkIdsPlugin().apply(compiler);
421420
break;
421+
case "deterministic":
422+
new DeterministicChunkIdsPlugin().apply(compiler);
423+
break;
422424
case "size":
423425
new OccurrenceChunkIdsPlugin({
424426
prioritiseInitial: true

lib/WebpackOptionsDefaulter.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,9 @@ class WebpackOptionsDefaulter extends OptionsDefaulter {
134134
const hasName = filename.includes("[name]");
135135
const hasId = filename.includes("[id]");
136136
const hasChunkHash = filename.includes("[chunkhash]");
137+
const hasContentHash = filename.includes("[contenthash]");
137138
// Anything changing depending on chunk is fine
138-
if (hasChunkHash || hasName || hasId) return filename;
139+
if (hasChunkHash || hasContentHash || hasName || hasId) return filename;
139140
// Elsewise prefix "[id]." in front of the basename to make it changing
140141
return filename.replace(/(^|\/)([^/]*(?:\?|$))/, "$1[id].$2");
141142
}
@@ -240,7 +241,7 @@ class WebpackOptionsDefaulter extends OptionsDefaulter {
240241
return "natural";
241242
});
242243
this.set("optimization.chunkIds", "make", options => {
243-
if (isProductionLikeMode(options)) return "total-size";
244+
if (isProductionLikeMode(options)) return "deterministic";
244245
if (options.mode === "development") return "named";
245246
return "natural";
246247
});
@@ -266,20 +267,19 @@ class WebpackOptionsDefaulter extends OptionsDefaulter {
266267
this.set("optimization.splitChunks.maxAsyncRequests", "make", options => {
267268
return isProductionLikeMode(options) ? 6 : Infinity;
268269
});
269-
this.set("optimization.splitChunks.automaticNameDelimiter", "~");
270+
this.set("optimization.splitChunks.automaticNameDelimiter", "-");
270271
this.set("optimization.splitChunks.maxInitialRequests", "make", options => {
271272
return isProductionLikeMode(options) ? 4 : Infinity;
272273
});
273-
this.set("optimization.splitChunks.name", true);
274274
this.set("optimization.splitChunks.cacheGroups", {});
275275
this.set("optimization.splitChunks.cacheGroups.default", {
276-
automaticNamePrefix: "",
276+
idHint: "",
277277
reuseExistingChunk: true,
278278
minChunks: 2,
279279
priority: -20
280280
});
281281
this.set("optimization.splitChunks.cacheGroups.defaultVendors", {
282-
automaticNamePrefix: "vendors",
282+
idHint: "vendors",
283283
test: NODE_MODULES_REGEXP,
284284
priority: -10
285285
});
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
MIT License http://www.opensource.org/licenses/mit-license.php
3+
Author Florent Cailhol @ooflorent
4+
*/
5+
6+
"use strict";
7+
8+
const { compareChunksNatural } = require("../util/comparators");
9+
const {
10+
getFullChunkName,
11+
getUsedChunkIds,
12+
assignDeterministicIds
13+
} = require("./IdHelpers");
14+
15+
/** @typedef {import("../Compiler")} Compiler */
16+
/** @typedef {import("../Module")} Module */
17+
18+
class DeterministicChunkIdsPlugin {
19+
constructor(options) {
20+
this.options = options || {};
21+
}
22+
23+
/**
24+
* @param {Compiler} compiler the compiler instance
25+
* @returns {void}
26+
*/
27+
apply(compiler) {
28+
compiler.hooks.compilation.tap(
29+
"DeterministicChunkIdsPlugin",
30+
compilation => {
31+
compilation.hooks.chunkIds.tap(
32+
"DeterministicChunkIdsPlugin",
33+
chunks => {
34+
const chunkGraph = compilation.chunkGraph;
35+
const context = this.options.context
36+
? this.options.context
37+
: compiler.context;
38+
39+
const compareNatural = compareChunksNatural(chunkGraph);
40+
41+
assignDeterministicIds(
42+
Array.from(chunks).filter(chunk => {
43+
return chunk.id === null;
44+
}),
45+
chunk =>
46+
getFullChunkName(chunk, chunkGraph, context, compiler.root),
47+
compareNatural,
48+
this.options.maxLength || 3,
49+
getUsedChunkIds(compilation),
50+
(chunk, id) => {
51+
chunk.id = id;
52+
chunk.ids = [id];
53+
}
54+
);
55+
}
56+
);
57+
}
58+
);
59+
}
60+
}
61+
62+
module.exports = DeterministicChunkIdsPlugin;

lib/ids/DeterministicModuleIdsPlugin.js

Lines changed: 18 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -8,29 +8,15 @@
88
const {
99
compareModulesByPreOrderIndexOrIdentifier
1010
} = require("../util/comparators");
11-
const createHash = require("../util/createHash");
11+
const {
12+
getUsedModuleIds,
13+
getFullModuleName,
14+
assignDeterministicIds
15+
} = require("./IdHelpers");
1216

1317
/** @typedef {import("../Compiler")} Compiler */
1418
/** @typedef {import("../Module")} Module */
1519

16-
/**
17-
* @param {string} str string to hash
18-
* @param {number} len max length of the number in decimal digests
19-
* @returns {number} hash as number in the range from i. e. for len = 3: 0 - 999
20-
*/
21-
const getHashNumber = (str, len) => {
22-
const hash = createHash("md4");
23-
hash.update(str);
24-
const digest = hash.digest("hex") + "10000000000";
25-
if (len === 1) {
26-
return +digest.match(/\d/)[0];
27-
}
28-
return +digest
29-
.match(/\d/g)
30-
.slice(0, len)
31-
.join("");
32-
};
33-
3420
class DeterministicModuleIdsPlugin {
3521
constructor(options) {
3622
this.options = options || {};
@@ -52,57 +38,22 @@ class DeterministicModuleIdsPlugin {
5238
? this.options.context
5339
: compiler.context;
5440

55-
const modulesInNaturalOrder = Array.from(modules)
56-
.filter(m => chunkGraph.getNumberOfModuleChunks(m) > 0)
57-
.sort(
58-
compareModulesByPreOrderIndexOrIdentifier(
59-
compilation.moduleGraph
60-
)
61-
);
62-
63-
// 80% fill rate
64-
const optimalLength = Math.ceil(
65-
Math.log10(modulesInNaturalOrder.length * 1.25 + 1)
66-
);
67-
68-
// use the provided length or default to the optimal length
69-
const maxLength = Math.max(
70-
Math.min(
71-
typeof this.options.maxLength === "number"
72-
? this.options.maxLength
73-
: 0,
74-
15 // higher values will give numerical problems
41+
assignDeterministicIds(
42+
Array.from(modules).filter(module => {
43+
if (chunkGraph.getNumberOfModuleChunks(module) === 0)
44+
return false;
45+
return chunkGraph.getModuleId(module) === null;
46+
}),
47+
module => getFullModuleName(module, context, compiler.root),
48+
compareModulesByPreOrderIndexOrIdentifier(
49+
compilation.moduleGraph
7550
),
76-
optimalLength
77-
);
78-
79-
const usedIds = new Set();
80-
81-
if (compilation.usedModuleIds) {
82-
for (const id of compilation.usedModuleIds) {
83-
usedIds.add(id);
84-
}
85-
}
86-
87-
for (const module of modulesInNaturalOrder) {
88-
const moduleId = chunkGraph.getModuleId(module);
89-
if (moduleId !== null) {
90-
usedIds.add(moduleId);
91-
}
92-
}
93-
94-
for (const module of modulesInNaturalOrder) {
95-
if (chunkGraph.getModuleId(module) === null) {
96-
const ident = module.libIdent({ context }) || "";
97-
let id;
98-
let i = 0;
99-
do {
100-
id = getHashNumber(ident + i++, maxLength);
101-
} while (usedIds.has(id));
51+
this.options.maxLength || 3,
52+
getUsedModuleIds(compilation),
53+
(module, id) => {
10254
chunkGraph.setModuleId(module, id);
103-
usedIds.add(id);
10455
}
105-
}
56+
);
10657
}
10758
);
10859
}

0 commit comments

Comments
 (0)