Skip to content

Commit 24f184f

Browse files
committed
WIP13
1 parent 5798e41 commit 24f184f

File tree

31 files changed

+456
-771
lines changed

31 files changed

+456
-771
lines changed

lib/WebpackOptionsDefaulter.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,14 +165,18 @@ class WebpackOptionsDefaulter extends OptionsDefaulter {
165165
this.set("optimization.usedExports", "make", options => isProductionLikeMode(options));
166166
this.set("optimization.concatenateModules", "make", options => isProductionLikeMode(options));
167167
this.set("optimization.splitChunks", {});
168+
this.set("optimization.splitChunks.chunks", "async");
168169
this.set("optimization.splitChunks.minSize", 30000);
169-
this.set("optimization.splitChunks.includeInitialChunks", false);
170-
this.set("optimization.splitChunks.minChunks", 2);
170+
this.set("optimization.splitChunks.minChunks", 1);
171171
this.set("optimization.splitChunks.maxAsyncRequests", 5);
172172
this.set("optimization.splitChunks.maxInitialRequests", 3);
173173
this.set("optimization.splitChunks.name", true);
174174
this.set("optimization.splitChunks.cacheGroups", {
175-
"node_modules": /[\\/]node_modules[\\/]/
175+
"vendors": {
176+
test: /[\\/]node_modules[\\/]/,
177+
minChunks: 1,
178+
chunks: "async"
179+
}
176180
});
177181
this.set("optimization.noEmitOnErrors", "make", options => isProductionLikeMode(options));
178182
this.set("optimization.namedModules", "make", options => options.mode === "development");

lib/optimize/AutomaticCommonsChunksPlugin.js

Lines changed: 79 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -29,23 +29,23 @@ module.exports = class AutomaticCommonsChunksPlugin {
2929

3030
static normalizeOptions(options) {
3131
return {
32+
chunks: options.chunks || "all",
3233
minSize: options.minSize || 0,
33-
includeInitialChunks: options.includeInitialChunks || false,
34-
minChunks: options.minChunks || 2,
34+
minChunks: options.minChunks || 1,
3535
maxAsyncRequests: options.maxAsyncRequests || 1,
3636
maxInitialRequests: options.maxInitialRequests || 1,
37-
getName: AutomaticCommonsChunksPlugin.normalizeName(options.name),
37+
getName: AutomaticCommonsChunksPlugin.normalizeName(options.name) || (() => {}),
3838
getCacheGroup: AutomaticCommonsChunksPlugin.normalizeCacheGroups(options.cacheGroups),
3939
};
4040
}
4141

4242
static normalizeName(option) {
4343
if(option === true) {
44-
return (module, chunks) => {
44+
return (module, chunks, cacheGroup) => {
4545
const names = chunks.map(c => c.name);
4646
if(!names.every(Boolean)) return;
4747
names.sort();
48-
return names.join("~");
48+
return (cacheGroup ? cacheGroup + "~" : "") + names.join("~");
4949
};
5050
}
5151
if(typeof option === "string") {
@@ -55,7 +55,6 @@ module.exports = class AutomaticCommonsChunksPlugin {
5555
}
5656
if(typeof option === "function")
5757
return option;
58-
return () => {};
5958
}
6059

6160
static normalizeCacheGroups(cacheGroups) {
@@ -68,7 +67,7 @@ module.exports = class AutomaticCommonsChunksPlugin {
6867
};
6968
}
7069
if(cacheGroups && typeof cacheGroups === "object") {
71-
return (module, chunks) => {
70+
return (module) => {
7271
for(const key of Object.keys(cacheGroups)) {
7372
let option = cacheGroups[key];
7473
if(option instanceof RegExp || typeof option === "string") {
@@ -77,18 +76,25 @@ module.exports = class AutomaticCommonsChunksPlugin {
7776
};
7877
}
7978
if(typeof option === "function") {
80-
const result = option(module, chunks);
79+
let result = option(module);
8180
if(result) {
82-
return Object.assign({
83-
key
81+
result = Object.assign({
82+
key,
8483
}, result);
84+
if(result.name) result.getName = () => result.name;
85+
return result;
8586
}
8687
}
8788
if(AutomaticCommonsChunksPlugin.checkTest(option.test, module)) {
8889
return {
8990
key: key,
90-
name: AutomaticCommonsChunksPlugin.normalizeName(option.name)(module, chunks),
91-
enforce: option.enforce
91+
getName: AutomaticCommonsChunksPlugin.normalizeName(option.name),
92+
chunks: option.chunks,
93+
enforce: option.enforce,
94+
minSize: option.minSize,
95+
minChunks: option.minChunks,
96+
maxAsyncRequests: option.maxAsyncRequests,
97+
maxInitialRequests: option.maxInitialRequests,
9298
};
9399
}
94100
}
@@ -122,42 +128,67 @@ module.exports = class AutomaticCommonsChunksPlugin {
122128
const indexMap = new Map();
123129
let index = 1;
124130
for(const chunk of chunks) {
125-
if(this.options.includeInitialChunks || !chunk.isInitial())
126-
indexMap.set(chunk, index++);
131+
indexMap.set(chunk, index++);
127132
}
128133
// Map a list of chunks to a list of modules
129134
// For the key the chunk "index" is used, the value is a SortableSet of modules
130135
const chunksInfoMap = new Map();
131136
// Walk through all modules
132137
for(const module of compilation.modules) {
133-
// Ignore entry modules
134-
if(module.isEntryModule()) continue;
135-
// Get indices of chunks in which this module occurs
136-
const chunkIndices = Array.from(module.chunksIterable, chunk => indexMap.get(chunk)).filter(Boolean);
137138
// Get array of chunks
138-
const chunks = Array.from(module.chunksIterable).filter(chunk => indexMap.get(chunk) !== undefined);
139+
const chunks = module.getChunks();
139140
// Get cache group
140-
const cacheGroup = this.options.getCacheGroup(module, chunks);
141-
const groupKey = cacheGroup === undefined ? undefined : cacheGroup.key;
142-
const enforce = cacheGroup === undefined ? false : cacheGroup.enforce;
143-
const name = cacheGroup !== undefined ? cacheGroup.name : this.options.getName(module, chunks);
141+
let cacheGroup = this.options.getCacheGroup(module);
142+
if(cacheGroup) {
143+
cacheGroup = {
144+
key: cacheGroup.key,
145+
chunks: cacheGroup.chunks || this.options.chunks,
146+
minSize: cacheGroup.minSize !== undefined ? cacheGroup.minSize : cacheGroup.enforce ? 0 : this.options.minSize,
147+
minChunks: cacheGroup.minChunks !== undefined ? cacheGroup.minChunks : cacheGroup.enforce ? 1 : this.options.minChunks,
148+
maxAsyncRequests: cacheGroup.maxAsyncRequests !== undefined ? cacheGroup.maxAsyncRequests : cacheGroup.enforce ? Infinity : this.options.maxAsyncRequests,
149+
maxInitialRequests: cacheGroup.maxInitialRequests !== undefined ? cacheGroup.maxInitialRequests : cacheGroup.enforce ? Infinity : this.options.maxInitialRequests,
150+
getName: cacheGroup.getName !== undefined ? cacheGroup.getName : this.options.getName,
151+
};
152+
} else {
153+
cacheGroup = {
154+
key: undefined,
155+
chunks: this.options.chunks,
156+
minSize: this.options.minSize,
157+
minChunks: this.options.minChunks,
158+
maxAsyncRequests: this.options.maxAsyncRequests,
159+
maxInitialRequests: this.options.maxInitialRequests,
160+
getName: this.options.getName
161+
};
162+
}
163+
// Select chunks by configuration
164+
const selectedChunks = cacheGroup.chunks === "initial" ? chunks.filter(chunk => chunk.isInitial()) :
165+
cacheGroup.chunks === "async" ? chunks.filter(chunk => !chunk.isInitial()) :
166+
chunks;
167+
// Get indices of chunks in which this module occurs
168+
const chunkIndices = selectedChunks.map(chunk => indexMap.get(chunk));
144169
// Break if minimum number of chunks is not reached
145-
if(!enforce && chunkIndices.length < this.options.minChunks)
170+
if(chunkIndices.length < cacheGroup.minChunks)
171+
continue;
172+
// Break if we selected only one chunk but no cache group
173+
if(chunkIndices.length === 1 && !cacheGroup.key)
146174
continue;
175+
// Determine name for split chunk
176+
const name = cacheGroup.getName(module, selectedChunks, cacheGroup.key);
147177
// Create key for maps
148178
// When it has a name we use the name as key
179+
// When it has a cache group we use the cache group key
149180
// Elsewise we create the key from chunks
150181
// This automatically merges equal names
151182
const chunksKey = chunkIndices.sort().join();
152-
let key = name || groupKey || chunksKey;
153-
key += !!enforce;
183+
const key = name && `name:${name}` ||
184+
cacheGroup.key && `key:${cacheGroup.key}` ||
185+
`chunks:${chunksKey}`;
154186
// Add module to maps
155187
let info = chunksInfoMap.get(key);
156188
if(info === undefined) {
157189
chunksInfoMap.set(key, info = {
158190
modules: new SortableSet(undefined, sortByIdentifier),
159-
enforce,
160-
groupKey,
191+
cacheGroup,
161192
name,
162193
chunks: new Map(),
163194
reusedableChunks: new Set(),
@@ -167,21 +198,20 @@ module.exports = class AutomaticCommonsChunksPlugin {
167198
info.modules.add(module);
168199
if(!info.chunksKeys.has(chunksKey)) {
169200
info.chunksKeys.add(chunksKey);
170-
for(const chunk of chunks) {
201+
for(const chunk of selectedChunks) {
171202
info.chunks.set(chunk, chunk.getNumberOfModules());
172203
}
173204
}
174205
}
175206
// Get size of module lists and sort them by name and size
176207
const entries = Array.from(chunksInfoMap.entries(), pair => {
177208
const info = pair[1];
178-
info.key = pair[0];
179209
info.size = Array.from(info.modules, m => m.size()).reduce((a, b) => a + b, 0);
180210
return info;
181211
}).filter(item => {
182212
if(item.enforce) return true;
183213
// Filter by size limit
184-
if(item.size < this.options.minSize) return false;
214+
if(item.size < item.cacheGroup.minSize) return false;
185215
return true;
186216
}).sort((a, b) => {
187217
// Sort
@@ -217,12 +247,11 @@ module.exports = class AutomaticCommonsChunksPlugin {
217247
// Walk though all entries
218248
for(const item of entries) {
219249
let chunkName = item.name;
220-
const enforced = item.enforce;
221250
// Variable for the new chunk (lazy created)
222251
let newChunk;
223-
// When not enforced, check if we can reuse a chunk instead of creating a new one
252+
// When no chunk name, check if we can reuse a chunk instead of creating a new one
224253
let isReused = false;
225-
if(!enforced) {
254+
if(!chunkName) {
226255
for(const pair of item.chunks) {
227256
if(pair[1] === item.modules.size) {
228257
const chunk = pair[0];
@@ -243,12 +272,10 @@ module.exports = class AutomaticCommonsChunksPlugin {
243272
// skip if we address ourself
244273
if(chunk.name === chunkName || chunk === newChunk) continue;
245274
// respect max requests when not enforced
246-
if(!enforced) {
247-
const maxRequests = chunk.isInitial() ?
248-
this.options.maxInitialRequests :
249-
this.options.maxAsyncRequests;
250-
if(getRequests(chunk) >= maxRequests) continue;
251-
}
275+
const maxRequests = chunk.isInitial() ?
276+
item.cacheGroup.maxInitialRequests :
277+
item.cacheGroup.maxAsyncRequests;
278+
if(isFinite(maxRequests) && getRequests(chunk) >= maxRequests) continue;
252279
if(newChunk === undefined) {
253280
// Create the new chunk
254281
newChunk = compilation.addChunk(chunkName);
@@ -261,30 +288,28 @@ module.exports = class AutomaticCommonsChunksPlugin {
261288
module.rewriteChunkInReasons(chunk, [newChunk]);
262289
}
263290
}
264-
// If we successfully creates a new chunk
265-
if(isReused) {
291+
// If we successfully created a new chunk or reused one
292+
if(newChunk) {
266293
// Add a note to the chunk
267-
newChunk.chunkReason = item.groupKey + ": reused as commons chunk";
268-
if(chunkName) {
269-
newChunk.chunkReason += " " + chunkName;
294+
newChunk.chunkReason = isReused ? "reused as split chunk" : "split chunk";
295+
if(item.cacheGroup.key) {
296+
newChunk.chunkReason += ` (cache group: ${item.cacheGroup.key})`;
270297
}
271-
changed = true;
272-
} else if(newChunk) {
273-
// Add a note to the chunk
274-
newChunk.chunkReason = item.groupKey + ": " + (enforced ? "vendors" : "commons") + " chunk";
275-
// If the choosen name is already an entry point we remove the entry point
276298
if(chunkName) {
299+
newChunk.chunkReason += ` (name: ${chunkName})`;
300+
// If the choosen name is already an entry point we remove the entry point
277301
const entrypoint = compilation.entrypoints.get(chunkName);
278302
if(entrypoint) {
279303
compilation.entrypoints.delete(chunkName);
280304
entrypoint.remove();
281305
newChunk.entryModule = undefined;
282306
}
283-
newChunk.chunkReason += " " + chunkName;
284307
}
285-
// Add all modules to the new chunk
286-
for(const module of item.modules) {
287-
GraphHelpers.connectChunkAndModule(newChunk, module);
308+
if(!isReused) {
309+
// Add all modules to the new chunk
310+
for(const module of item.modules) {
311+
GraphHelpers.connectChunkAndModule(newChunk, module);
312+
}
288313
}
289314
changed = true;
290315
}

schemas/WebpackOptions.json

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1335,19 +1335,23 @@
13351335
"type": "object",
13361336
"additionalProperties": false,
13371337
"properties": {
1338+
"chunks": {
1339+
"description": "Select chunks for determining shared modules (defaults to \"async\", \"initial\" and \"all\" requires adding these chunks to the HTML)",
1340+
"enum": [
1341+
"initial",
1342+
"async",
1343+
"all"
1344+
]
1345+
},
13381346
"minSize": {
13391347
"description": "Minimal size for created chunk",
13401348
"type": "number",
13411349
"minimum": 0
13421350
},
1343-
"includeInitialChunks": {
1344-
"description": "Include initial chunks (This requires adding these chunks to the HTML)",
1345-
"type": "boolean"
1346-
},
13471351
"minChunks": {
13481352
"description": "Minimum number of times a module has to be duplicated until it's considered for splitting",
13491353
"type": "number",
1350-
"minimum": 2
1354+
"minimum": 1
13511355
},
13521356
"maxAsyncRequests": {
13531357
"description": "Maximum number of requests which are accepted for on-demand loading",
@@ -1391,7 +1395,7 @@
13911395
"type": "object",
13921396
"additionalProperties": {
13931397
"description": "Configuration for a cache group",
1394-
"oneOf": [
1398+
"anyOf": [
13951399
{
13961400
"instanceof": "Function"
13971401
},
@@ -1419,13 +1423,46 @@
14191423
}
14201424
]
14211425
},
1426+
"chunks": {
1427+
"description": "Select chunks for determining cache group content (defaults to \"initial\", \"initial\" and \"all\" requires adding these chunks to the HTML)",
1428+
"enum": [
1429+
"initial",
1430+
"async",
1431+
"all"
1432+
]
1433+
},
14221434
"enforce": {
1423-
"description": "Include minimum size and maximum requests and always create chunks for this cache group",
1435+
"description": "Ignore minimum size, minimum chunks and maximum requests and always create chunks for this cache group",
14241436
"type": "boolean"
14251437
},
1438+
"minSize": {
1439+
"description": "Minimal size for created chunk",
1440+
"type": "number",
1441+
"minimum": 0
1442+
},
1443+
"minChunks": {
1444+
"description": "Minimum number of times a module has to be duplicated until it's considered for splitting",
1445+
"type": "number",
1446+
"minimum": 1
1447+
},
1448+
"maxAsyncRequests": {
1449+
"description": "Maximum number of requests which are accepted for on-demand loading",
1450+
"type": "number",
1451+
"minimum": 1
1452+
},
1453+
"maxInitialRequests": {
1454+
"description": "Maximum number of initial chunks which are accepted for an entry point",
1455+
"type": "number",
1456+
"minimum": 1
1457+
},
14261458
"name": {
14271459
"description": "Give chunks for this cache group a name (chunks with equal name are merged)",
14281460
"oneOf": [
1461+
{
1462+
"enum": [
1463+
true
1464+
]
1465+
},
14291466
{
14301467
"instanceof": "Function"
14311468
},

test/statsCases/async-commons-chunk-auto/a.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ import "./e";
33
import "x";
44
import "y";
55
export default "a";
6-
import("./g");
6+
import(/* webpackChunkName: "async-g" */ "./g");

0 commit comments

Comments
 (0)