Skip to content

Commit 43b9d21

Browse files
committed
WIP13
1 parent ba8c0cd commit 43b9d21

File tree

23 files changed

+349
-274
lines changed

23 files changed

+349
-274
lines changed

lib/WebpackOptionsApply.js

Lines changed: 2 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -287,98 +287,8 @@ class WebpackOptionsApply extends OptionsApply {
287287
new FlagDependencyUsagePlugin().apply(compiler);
288288
if(options.optimization.concatenateModules)
289289
new ModuleConcatenationPlugin().apply(compiler);
290-
const nameOptionToName = value => {
291-
if(value === true) {
292-
return (module, chunks) => {
293-
const names = chunks.map(c => c.name);
294-
if(!names.every(Boolean)) return;
295-
names.sort();
296-
return names.join("~");
297-
};
298-
} else {
299-
return value;
300-
}
301-
};
302-
const initialVendorsOptionToEnforce = value => {
303-
if(value === true) {
304-
value = /[\\/]node_modules[\\/]/;
305-
}
306-
if(typeof value === "string") {
307-
value = {
308-
[value]: /[\\/]node_modules[\\/]/
309-
};
310-
}
311-
return vendorsOptionToEnforce(value);
312-
};
313-
const vendorsOptionToEnforce = value => {
314-
if(value === true) {
315-
value = /[\\/]node_modules[\\/]/;
316-
}
317-
if(typeof value === "string") {
318-
value = {
319-
[value]: /[\\/]node_modules[\\/]/
320-
};
321-
}
322-
if(value instanceof RegExp) {
323-
return (module, chunks) => {
324-
if(!module.nameForCondition) return;
325-
const name = module.nameForCondition();
326-
return value.test(name);
327-
};
328-
}
329-
if(typeof value === "string") {
330-
return (module, chunks) => {
331-
if(!module.nameForCondition) return;
332-
const name = module.nameForCondition();
333-
return name.startsWith(value);
334-
};
335-
}
336-
if(value && typeof value === "object") {
337-
return (module, chunks) => {
338-
if(!module.nameForCondition) return;
339-
const name = module.nameForCondition();
340-
for(const chunkName of Object.keys(value)) {
341-
const regExp = value[chunkName];
342-
if(typeof regExp === "string") {
343-
if(name.startsWith(regExp))
344-
return chunkName;
345-
} else if(typeof regExp === "function") {
346-
const result = regExp(name);
347-
if(typeof result === "string")
348-
return result;
349-
else if(result)
350-
return chunkName;
351-
} else {
352-
if(regExp.test(name))
353-
return chunkName;
354-
}
355-
}
356-
};
357-
}
358-
return value;
359-
};
360-
if(options.optimization.asyncCommonsChunks || options.optimization.asyncVendorsChunks) {
361-
const accpOptions = Object.assign({}, options.optimization.asyncCommonsChunks, {
362-
initialChunks: false,
363-
name: nameOptionToName(typeof options.optimization.asyncCommonsChunks === "object" ? options.optimization.asyncCommonsChunks.name : undefined),
364-
enforce: vendorsOptionToEnforce(options.optimization.asyncVendorsChunks)
365-
});
366-
if(!options.optimization.asyncCommonsChunks) {
367-
accpOptions.minChunks = Infinity;
368-
}
369-
new AutomaticCommonsChunksPlugin(accpOptions).apply(compiler);
370-
}
371-
if(options.optimization.initialCommonsChunks || options.optimization.initialVendorsChunks) {
372-
const accpOptions = Object.assign({}, options.optimization.initialCommonsChunks, {
373-
initialChunks: true,
374-
name: nameOptionToName(typeof options.optimization.initialCommonsChunks === "object" ? options.optimization.initialCommonsChunks.name : undefined),
375-
enforce: initialVendorsOptionToEnforce(options.optimization.initialVendorsChunks)
376-
});
377-
if(!options.optimization.initialCommonsChunks) {
378-
accpOptions.minChunks = Infinity;
379-
}
380-
new AutomaticCommonsChunksPlugin(accpOptions).apply(compiler);
381-
}
290+
if(options.optimization.splitChunks)
291+
new AutomaticCommonsChunksPlugin(options.optimization.splitChunks).apply(compiler);
382292
if(options.optimization.noEmitOnErrors)
383293
new NoEmitOnErrorsPlugin().apply(compiler);
384294
if(options.optimization.namedModules)

lib/WebpackOptionsDefaulter.js

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -164,10 +164,16 @@ class WebpackOptionsDefaulter extends OptionsDefaulter {
164164
this.set("optimization.providedExports", true);
165165
this.set("optimization.usedExports", "make", options => isProductionLikeMode(options));
166166
this.set("optimization.concatenateModules", "make", options => isProductionLikeMode(options));
167-
this.set("optimization.asyncCommonsChunks", true);
168-
this.set("optimization.asyncVendorsChunks", true);
169-
this.set("optimization.initialCommonsChunks", false);
170-
this.set("optimization.initialVendorsChunks", false);
167+
this.set("optimization.splitChunks", {});
168+
this.set("optimization.splitChunks.minSize", 30000);
169+
this.set("optimization.splitChunks.includeInitialChunks", false);
170+
this.set("optimization.splitChunks.minChunks", 2);
171+
this.set("optimization.splitChunks.maxAsyncRequests", 5);
172+
this.set("optimization.splitChunks.maxInitialRequests", 3);
173+
this.set("optimization.splitChunks.name", true);
174+
this.set("optimization.splitChunks.cacheGroups", {
175+
"node_modules": /[\\/]node_modules[\\/]/
176+
});
171177
this.set("optimization.noEmitOnErrors", "make", options => isProductionLikeMode(options));
172178
this.set("optimization.namedModules", "make", options => options.mode === "development");
173179
this.set("optimization.namedChunks", "make", options => options.mode === "development");

lib/optimize/AutomaticCommonsChunksPlugin.js

Lines changed: 104 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,93 @@ const getRequests = chunk => {
2323

2424
module.exports = class AutomaticCommonsChunksPlugin {
2525
constructor(options) {
26-
this.options = Object.assign({}, {
27-
initialChunks: false,
28-
minSize: 30000,
29-
minChunks: 2,
30-
maxRequests: 4,
31-
name: undefined, // function(module, chunks) => string | undefined
32-
enforce: undefined // function(module, module) => true | false
33-
}, options);
26+
this.options = AutomaticCommonsChunksPlugin.normalizeOptions(options);
3427
this.alreadyOptimized = new WeakSet();
3528
}
3629

30+
static normalizeOptions(options) {
31+
return {
32+
minSize: options.minSize || 0,
33+
includeInitialChunks: options.includeInitialChunks || false,
34+
minChunks: options.minChunks || 2,
35+
maxAsyncRequests: options.maxAsyncRequests || 1,
36+
maxInitialRequests: options.maxInitialRequests || 1,
37+
getName: AutomaticCommonsChunksPlugin.normalizeName(options.name),
38+
getCacheGroup: AutomaticCommonsChunksPlugin.normalizeCacheGroups(options.cacheGroups),
39+
};
40+
}
41+
42+
static normalizeName(option) {
43+
if(option === true) {
44+
return (module, chunks) => {
45+
const names = chunks.map(c => c.name);
46+
if(!names.every(Boolean)) return;
47+
names.sort();
48+
return names.join("~");
49+
};
50+
}
51+
if(typeof option === "string") {
52+
return () => {
53+
return option;
54+
};
55+
}
56+
if(typeof option === "function")
57+
return option;
58+
return () => {};
59+
}
60+
61+
static normalizeCacheGroups(cacheGroups) {
62+
if(typeof cacheGroups === "function") {
63+
return cacheGroups;
64+
}
65+
if(typeof cacheGroups === "string" || cacheGroups instanceof RegExp) {
66+
cacheGroups = {
67+
"vendors": cacheGroups
68+
};
69+
}
70+
if(cacheGroups && typeof cacheGroups === "object") {
71+
return (module, chunks) => {
72+
for(const key of Object.keys(cacheGroups)) {
73+
let option = cacheGroups[key];
74+
if(option instanceof RegExp || typeof option === "string") {
75+
option = {
76+
test: option
77+
};
78+
}
79+
if(typeof option === "function") {
80+
const result = option(module, chunks);
81+
if(result) {
82+
return Object.assign({
83+
key
84+
}, result);
85+
}
86+
}
87+
if(AutomaticCommonsChunksPlugin.checkTest(option.test, module)) {
88+
return {
89+
key: key,
90+
name: AutomaticCommonsChunksPlugin.normalizeName(option.name)(module, chunks),
91+
enforce: option.enforce
92+
};
93+
}
94+
}
95+
};
96+
}
97+
return () => {};
98+
}
99+
100+
static checkTest(test, module) {
101+
if(typeof test === "function")
102+
return test(module);
103+
if(!module.nameForCondition)
104+
return false;
105+
const name = module.nameForCondition();
106+
if(typeof test === "string")
107+
return name.startsWith(test);
108+
if(test instanceof RegExp)
109+
return test.test(name);
110+
return !!test;
111+
}
112+
37113
apply(compiler) {
38114
compiler.hooks.compilation.tap("AutomaticCommonsChunksPlugin", compilation => {
39115
compilation.hooks.unseal.tap("AutomaticCommonsChunksPlugin", () => {
@@ -46,42 +122,42 @@ module.exports = class AutomaticCommonsChunksPlugin {
46122
const indexMap = new Map();
47123
let index = 1;
48124
for(const chunk of chunks) {
49-
if(chunk.isInitial() === this.options.initialChunks)
125+
if(this.options.includeInitialChunks || !chunk.isInitial())
50126
indexMap.set(chunk, index++);
51127
}
52128
// Map a list of chunks to a list of modules
53129
// For the key the chunk "index" is used, the value is a SortableSet of modules
54130
const chunksInfoMap = new Map();
55131
// Walk through all modules
56132
for(const module of compilation.modules) {
133+
// Ignore entry modules
134+
if(module.isEntryModule()) continue;
57135
// Get indices of chunks in which this module occurs
58136
const chunkIndices = Array.from(module.chunksIterable, chunk => indexMap.get(chunk)).filter(Boolean);
59137
// Get array of chunks
60138
const chunks = Array.from(module.chunksIterable).filter(chunk => indexMap.get(chunk) !== undefined);
61-
// Get enforce from "enforce" option
62-
let enforce = this.options.enforce;
63-
if(typeof enforce === "function")
64-
enforce = enforce(module, chunks);
139+
// 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);
65144
// Break if minimum number of chunks is not reached
66145
if(!enforce && chunkIndices.length < this.options.minChunks)
67146
continue;
68-
// Get name from "name" option
69-
let name = typeof enforce === "string" ? enforce : this.options.name;
70-
if(typeof name === "function")
71-
name = name(module, chunks);
72147
// Create key for maps
73148
// When it has a name we use the name as key
74149
// Elsewise we create the key from chunks
75150
// This automatically merges equal names
76151
const chunksKey = chunkIndices.sort().join();
77-
let key = name ? name : chunksKey;
78-
if(enforce) key += ",enforced";
152+
let key = name || groupKey || chunksKey;
153+
key += !!enforce;
79154
// Add module to maps
80155
let info = chunksInfoMap.get(key);
81156
if(info === undefined) {
82157
chunksInfoMap.set(key, info = {
83158
modules: new SortableSet(undefined, sortByIdentifier),
84159
enforce,
160+
groupKey,
85161
name,
86162
chunks: new Map(),
87163
reusedableChunks: new Set(),
@@ -166,8 +242,13 @@ module.exports = class AutomaticCommonsChunksPlugin {
166242
for(const chunk of item.chunks.keys()) {
167243
// skip if we address ourself
168244
if(chunk.name === chunkName || chunk === newChunk) continue;
169-
// respect max requests when not a named chunk
170-
if(!enforced && getRequests(chunk) >= this.options.maxRequests) continue;
245+
// 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+
}
171252
if(newChunk === undefined) {
172253
// Create the new chunk
173254
newChunk = compilation.addChunk(chunkName);
@@ -183,14 +264,14 @@ module.exports = class AutomaticCommonsChunksPlugin {
183264
// If we successfully creates a new chunk
184265
if(isReused) {
185266
// Add a note to the chunk
186-
newChunk.chunkReason = "reused as commons chunk";
267+
newChunk.chunkReason = item.groupKey + ": reused as commons chunk";
187268
if(chunkName) {
188269
newChunk.chunkReason += " " + chunkName;
189270
}
190271
changed = true;
191272
} else if(newChunk) {
192273
// Add a note to the chunk
193-
newChunk.chunkReason = enforced ? "vendors chunk" : "commons chunk";
274+
newChunk.chunkReason = item.groupKey + ": " + (enforced ? "vendors" : "commons") + " chunk";
194275
// If the choosen name is already an entry point we remove the entry point
195276
if(chunkName) {
196277
const entrypoint = compilation.entrypoints.get(chunkName);

0 commit comments

Comments
 (0)