Skip to content

Commit ddcfa37

Browse files
authored
Merge pull request webpack#6503 from webpack/bugfix/split-chunk-combinations
improve chunk splitting
2 parents 4b30be4 + 1e73752 commit ddcfa37

23 files changed

Lines changed: 359 additions & 202 deletions

File tree

lib/WebpackOptionsDefaulter.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ class WebpackOptionsDefaulter extends OptionsDefaulter {
168168
this.set("optimization.usedExports", "make", options => isProductionLikeMode(options));
169169
this.set("optimization.concatenateModules", "make", options => isProductionLikeMode(options));
170170
this.set("optimization.splitChunks", {});
171+
this.set("optimization.splitChunks.maxComplexity", "make", options => isProductionLikeMode(options) ? 3 : 1);
171172
this.set("optimization.splitChunks.chunks", "async");
172173
this.set("optimization.splitChunks.minSize", 30000);
173174
this.set("optimization.splitChunks.minChunks", 1);

lib/optimize/SplitChunksPlugin.js

Lines changed: 75 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -40,18 +40,41 @@ const isOverlap = (a, b) => {
4040
return false;
4141
};
4242

43+
const getCombinations = (array, maxDepth) => {
44+
const results = [];
45+
getCombinationsInteral(array, maxDepth, results);
46+
return results;
47+
};
48+
49+
const getCombinationsInteral = (array, maxDepth, results) => {
50+
results.push(array);
51+
if(maxDepth === 0 || array.length === 1) {
52+
return;
53+
}
54+
for(let i = 0; i < array.length; i++) {
55+
const newArray = array.slice(0, i).concat(array.slice(i + 1, array.length));
56+
getCombinationsInteral(newArray, maxDepth - 1, results);
57+
}
58+
};
59+
4360
const compareEntries = (a, b) => {
4461
// 1. by priority
4562
const diffPriority = a.cacheGroup.priority - b.cacheGroup.priority;
4663
if(diffPriority) return diffPriority;
47-
// 2. by total modules size
48-
const diffSize = b.size - a.size;
49-
if(diffSize) return diffSize;
64+
// 2. by number of chunks
65+
const diffCount = a.chunks.size - b.chunks.size;
66+
if(diffCount) return diffCount;
67+
// 3. by size reduction
68+
const aSizeReduce = a.size * (a.chunks.size - 1);
69+
const bSizeReduce = b.size * (b.chunks.size - 1);
70+
const diffSizeReduce = aSizeReduce - bSizeReduce;
71+
if(diffSizeReduce) return diffSizeReduce;
72+
// 4. by number of modules (to be able to compare by identifier)
5073
const modulesA = a.modules;
5174
const modulesB = b.modules;
52-
// 3. by module identifiers
5375
const diff = modulesA.size - modulesB.size;
5476
if(diff) return diff;
77+
// 5. by module identifiers
5578
modulesA.sort();
5679
modulesB.sort();
5780
const aI = modulesA[Symbol.iterator]();
@@ -74,6 +97,7 @@ module.exports = class SplitChunksPlugin {
7497

7598
static normalizeOptions(options) {
7699
return {
100+
maxComplexity: options.maxComplexity || 0,
77101
chunks: options.chunks || "all",
78102
minSize: options.minSize || 0,
79103
minChunks: options.minChunks || 1,
@@ -226,47 +250,53 @@ module.exports = class SplitChunksPlugin {
226250
reuseExistingChunk: cacheGroupSource.reuseExistingChunk
227251
};
228252
// Select chunks by configuration
229-
const selectedChunks = cacheGroup.chunks === "initial" ? chunks.filter(chunk => chunk.canBeInitial()) :
253+
const maxSelectedChunks = cacheGroup.chunks === "initial" ? chunks.filter(chunk => chunk.canBeInitial()) :
230254
cacheGroup.chunks === "async" ? chunks.filter(chunk => !chunk.canBeInitial()) :
231255
chunks;
232-
// Get indices of chunks in which this module occurs
233-
const chunkIndices = selectedChunks.map(chunk => indexMap.get(chunk));
234-
// Break if minimum number of chunks is not reached
235-
if(chunkIndices.length < cacheGroup.minChunks)
236-
continue;
237-
// Determine name for split chunk
238-
const name = cacheGroup.getName(module, selectedChunks, cacheGroup.key);
239-
// Create key for maps
240-
// When it has a name we use the name as key
241-
// Elsewise we create the key from chunks and cache group key
242-
// This automatically merges equal names
243-
const chunksKey = chunkIndices.sort().join();
244-
const key = name && `name:${name}` ||
245-
`chunks:${chunksKey} key:${cacheGroup.key}`;
246-
// Add module to maps
247-
let info = chunksInfoMap.get(key);
248-
if(info === undefined) {
249-
chunksInfoMap.set(key, info = {
250-
modules: new SortableSet(undefined, sortByIdentifier),
251-
cacheGroup,
252-
name,
253-
chunks: new Map(),
254-
reusedableChunks: new Set(),
255-
chunksKeys: new Set()
256-
});
257-
}
258-
info.modules.add(module);
259-
if(!info.chunksKeys.has(chunksKey)) {
260-
info.chunksKeys.add(chunksKey);
261-
for(const chunk of selectedChunks) {
262-
info.chunks.set(chunk, chunk.getNumberOfModules());
256+
// For all (nearly all) combination of chunk selection
257+
for(const selectedChunks of getCombinations(maxSelectedChunks, this.options.maxComplexity)) {
258+
// Get indices of chunks in which this module occurs
259+
const chunkIndices = selectedChunks.map(chunk => indexMap.get(chunk));
260+
// Break if minimum number of chunks is not reached
261+
if(chunkIndices.length < cacheGroup.minChunks)
262+
continue;
263+
// Determine name for split chunk
264+
const name = cacheGroup.getName(module, selectedChunks, cacheGroup.key);
265+
// Create key for maps
266+
// When it has a name we use the name as key
267+
// Elsewise we create the key from chunks and cache group key
268+
// This automatically merges equal names
269+
const chunksKey = chunkIndices.sort().join();
270+
const key = name && `name:${name}` ||
271+
`chunks:${chunksKey} key:${cacheGroup.key}`;
272+
// Add module to maps
273+
let info = chunksInfoMap.get(key);
274+
if(info === undefined) {
275+
chunksInfoMap.set(key, info = {
276+
modules: new SortableSet(undefined, sortByIdentifier),
277+
cacheGroup,
278+
name,
279+
chunks: new Map(),
280+
reusedableChunks: new Set(),
281+
chunksKeys: new Set()
282+
});
283+
}
284+
info.modules.add(module);
285+
if(!info.chunksKeys.has(chunksKey)) {
286+
info.chunksKeys.add(chunksKey);
287+
for(const chunk of selectedChunks) {
288+
info.chunks.set(chunk, chunk.getNumberOfModules());
289+
}
263290
}
264291
}
265292
}
266293
}
267-
for(const info of chunksInfoMap.values()) {
294+
for(const [key, info] of chunksInfoMap) {
268295
// Get size of module lists
269296
info.size = getModulesSize(info.modules);
297+
if(info.size < info.cacheGroup.minSize) {
298+
chunksInfoMap.delete(key);
299+
}
270300
}
271301
let changed = false;
272302
while(chunksInfoMap.size > 0) {
@@ -286,10 +316,7 @@ module.exports = class SplitChunksPlugin {
286316
}
287317

288318
const item = bestEntry;
289-
if(item.size < item.cacheGroup.minSize) {
290-
chunksInfoMap.delete(bestEntryKey);
291-
continue;
292-
}
319+
chunksInfoMap.delete(bestEntryKey);
293320

294321
let chunkName = item.name;
295322
// Variable for the new chunk (lazy created)
@@ -357,21 +384,25 @@ module.exports = class SplitChunksPlugin {
357384
}
358385
}
359386
// remove all modules from other entries and update size
360-
for(const info of chunksInfoMap.values()) {
387+
for(const [key, info] of chunksInfoMap) {
361388
if(isOverlap(info.chunks, item.chunks)) {
362389
const oldSize = info.modules.size;
363390
for(const module of item.modules) {
364391
info.modules.delete(module);
365392
}
393+
if(info.modules.size === 0) {
394+
chunksInfoMap.delete(key);
395+
continue;
396+
}
366397
if(info.modules.size !== oldSize) {
367398
info.size = getModulesSize(info.modules);
399+
if(info.size < info.cacheGroup.minSize)
400+
chunksInfoMap.delete(key);
368401
}
369402
}
370403
}
371404
changed = true;
372405
}
373-
374-
chunksInfoMap.delete(bestEntryKey);
375406
}
376407
if(changed) return true;
377408
});

schemas/WebpackOptions.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1335,6 +1335,11 @@
13351335
"type": "object",
13361336
"additionalProperties": false,
13371337
"properties": {
1338+
"maxComplexity": {
1339+
"description": "Limits the algorithmic complexity",
1340+
"type": "number",
1341+
"minimum": 0
1342+
},
13381343
"chunks": {
13391344
"description": "Select chunks for determining shared modules (defaults to \"async\", \"initial\" and \"all\" requires adding these chunks to the HTML)",
13401345
"enum": [

0 commit comments

Comments
 (0)