Skip to content

Commit 1e73752

Browse files
committed
improve chunk splitting
by also trying to select combinations of module chunks (limited by complexity) fix size ordering (was reversed) add chunk cound ordering
1 parent 4b30be4 commit 1e73752

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)