Skip to content

Commit 0c3c554

Browse files
committed
WUIP15
1 parent 78c61a0 commit 0c3c554

File tree

40 files changed

+370
-316
lines changed

40 files changed

+370
-316
lines changed

lib/optimize/AutomaticCommonsChunksPlugin.js

Lines changed: 151 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,44 @@ const getRequests = chunk => {
2121
return requests;
2222
};
2323

24+
const getModulesSize = modules => {
25+
let sum = 0;
26+
for(const m of modules)
27+
sum += m.size();
28+
return sum;
29+
};
30+
31+
const isOverlap = (a, b) => {
32+
for(const item of a) {
33+
if(b.has(item)) return true;
34+
}
35+
return false;
36+
}
37+
38+
const compareEntries = (a, b) => {
39+
// 1. by total modules size
40+
const diffSize = b.size - a.size;
41+
if(diffSize) return diffSize;
42+
const modulesA = a.modules;
43+
const modulesB = b.modules;
44+
// 3. by module identifiers
45+
const diff = modulesA.size - modulesB.size;
46+
if(diff) return diff;
47+
modulesA.sort();
48+
modulesB.sort();
49+
const aI = modulesA[Symbol.iterator]();
50+
const bI = modulesB[Symbol.iterator]();
51+
while(true) { // eslint-disable-line
52+
const aItem = aI.next();
53+
const bItem = bI.next();
54+
if(aItem.done) return 0;
55+
const aModuleIdentifier = aItem.value.identifier();
56+
const bModuleIdentifier = bItem.value.identifier();
57+
if(aModuleIdentifier > bModuleIdentifier) return -1;
58+
if(aModuleIdentifier < bModuleIdentifier) return 1;
59+
}
60+
};
61+
2462
module.exports = class AutomaticCommonsChunksPlugin {
2563
constructor(options) {
2664
this.options = AutomaticCommonsChunksPlugin.normalizeOptions(options);
@@ -35,7 +73,7 @@ module.exports = class AutomaticCommonsChunksPlugin {
3573
maxAsyncRequests: options.maxAsyncRequests || 1,
3674
maxInitialRequests: options.maxInitialRequests || 1,
3775
getName: AutomaticCommonsChunksPlugin.normalizeName(options.name) || (() => {}),
38-
getCacheGroup: AutomaticCommonsChunksPlugin.normalizeCacheGroups(options.cacheGroups),
76+
getCacheGroups: AutomaticCommonsChunksPlugin.normalizeCacheGroups(options.cacheGroups),
3977
};
4078
}
4179

@@ -68,6 +106,7 @@ module.exports = class AutomaticCommonsChunksPlugin {
68106
}
69107
if(cacheGroups && typeof cacheGroups === "object") {
70108
return (module, chunks) => {
109+
let results;
71110
for(const key of Object.keys(cacheGroups)) {
72111
let option = cacheGroups[key];
73112
if(option instanceof RegExp || typeof option === "string") {
@@ -78,15 +117,18 @@ module.exports = class AutomaticCommonsChunksPlugin {
78117
if(typeof option === "function") {
79118
let result = option(module);
80119
if(result) {
81-
result = Object.assign({
82-
key,
83-
}, result);
84-
if(result.name) result.getName = () => result.name;
85-
return result;
120+
if(results === undefined) results = [];
121+
for(const r of (Array.isArray(result) ? result : [result])) {
122+
const result = Object.assign({
123+
key,
124+
}, r);
125+
if(result.name) result.getName = () => result.name;
126+
results.push(result);
127+
}
86128
}
87-
}
88-
if(AutomaticCommonsChunksPlugin.checkTest(option.test, module, chunks)) {
89-
return {
129+
} else if(AutomaticCommonsChunksPlugin.checkTest(option.test, module, chunks)) {
130+
if(results === undefined) results = [];
131+
results.push({
90132
key: key,
91133
getName: AutomaticCommonsChunksPlugin.normalizeName(option.name),
92134
chunks: option.chunks,
@@ -96,15 +138,18 @@ module.exports = class AutomaticCommonsChunksPlugin {
96138
maxAsyncRequests: option.maxAsyncRequests,
97139
maxInitialRequests: option.maxInitialRequests,
98140
reuseExistingChunk: option.reuseExistingChunk
99-
};
141+
});
100142
}
101143
}
144+
return results;
102145
};
103146
}
104147
return () => {};
105148
}
106149

107150
static checkTest(test, module, chunks) {
151+
if(test === undefined)
152+
return true;
108153
if(typeof test === "function")
109154
return test(module, chunks);
110155
if(typeof test === "boolean")
@@ -147,9 +192,9 @@ module.exports = class AutomaticCommonsChunksPlugin {
147192
// Get array of chunks
148193
const chunks = module.getChunks();
149194
// Get cache group
150-
let cacheGroup = this.options.getCacheGroup(module, chunks);
151-
if(cacheGroup) {
152-
cacheGroup = {
195+
let cacheGroups = this.options.getCacheGroups(module, chunks);
196+
if(cacheGroups) {
197+
cacheGroups = (Array.isArray(cacheGroups) ? cacheGroups : [cacheGroups]).map(cacheGroup => ({
153198
key: cacheGroup.key,
154199
chunks: cacheGroup.chunks || this.options.chunks,
155200
minSize: cacheGroup.minSize !== undefined ? cacheGroup.minSize : cacheGroup.enforce ? 0 : this.options.minSize,
@@ -158,105 +203,92 @@ module.exports = class AutomaticCommonsChunksPlugin {
158203
maxInitialRequests: cacheGroup.maxInitialRequests !== undefined ? cacheGroup.maxInitialRequests : cacheGroup.enforce ? Infinity : this.options.maxInitialRequests,
159204
getName: cacheGroup.getName !== undefined ? cacheGroup.getName : this.options.getName,
160205
reuseExistingChunk: cacheGroup.reuseExistingChunk
161-
};
206+
}));
162207
} else {
163-
cacheGroup = {
164-
key: undefined,
165-
chunks: this.options.chunks,
166-
minSize: this.options.minSize,
167-
minChunks: this.options.minChunks,
168-
maxAsyncRequests: this.options.maxAsyncRequests,
169-
maxInitialRequests: this.options.maxInitialRequests,
170-
getName: this.options.getName,
171-
reuseExistingChunk: true
172-
};
173-
}
174-
// Select chunks by configuration
175-
const selectedChunks = cacheGroup.chunks === "initial" ? chunks.filter(chunk => chunk.isInitial()) :
176-
cacheGroup.chunks === "async" ? chunks.filter(chunk => !chunk.isInitial()) :
177-
chunks;
178-
// Get indices of chunks in which this module occurs
179-
const chunkIndices = selectedChunks.map(chunk => indexMap.get(chunk));
180-
// Break if minimum number of chunks is not reached
181-
if(chunkIndices.length < cacheGroup.minChunks)
182-
continue;
183-
// Break if we selected only one chunk but no cache group
184-
if(chunkIndices.length === 1 && !cacheGroup.key)
185-
continue;
186-
// Determine name for split chunk
187-
const name = cacheGroup.getName(module, selectedChunks, cacheGroup.key);
188-
// Create key for maps
189-
// When it has a name we use the name as key
190-
// When it has a cache group we use the cache group key
191-
// Elsewise we create the key from chunks
192-
// This automatically merges equal names
193-
const chunksKey = chunkIndices.sort().join();
194-
const key = name && `name:${name}` ||
195-
cacheGroup.key && `chunks:${chunksKey} key:${cacheGroup.key}` ||
196-
`chunks:${chunksKey}`;
197-
// Add module to maps
198-
let info = chunksInfoMap.get(key);
199-
if(info === undefined) {
200-
chunksInfoMap.set(key, info = {
201-
modules: new SortableSet(undefined, sortByIdentifier),
202-
cacheGroup,
203-
name,
204-
chunks: new Map(),
205-
reusedableChunks: new Set(),
206-
chunksKeys: new Set()
207-
});
208+
cacheGroups = [];
208209
}
209-
info.modules.add(module);
210-
if(!info.chunksKeys.has(chunksKey)) {
211-
info.chunksKeys.add(chunksKey);
212-
for(const chunk of selectedChunks) {
213-
info.chunks.set(chunk, chunk.getNumberOfModules());
210+
cacheGroups.push({
211+
key: undefined,
212+
chunks: this.options.chunks,
213+
minSize: this.options.minSize,
214+
minChunks: this.options.minChunks,
215+
maxAsyncRequests: this.options.maxAsyncRequests,
216+
maxInitialRequests: this.options.maxInitialRequests,
217+
getName: this.options.getName,
218+
reuseExistingChunk: true
219+
});
220+
for(const cacheGroup of cacheGroups) {
221+
// Select chunks by configuration
222+
const selectedChunks = cacheGroup.chunks === "initial" ? chunks.filter(chunk => chunk.isInitial()) :
223+
cacheGroup.chunks === "async" ? chunks.filter(chunk => !chunk.isInitial()) :
224+
chunks;
225+
// Get indices of chunks in which this module occurs
226+
const chunkIndices = selectedChunks.map(chunk => indexMap.get(chunk));
227+
// Break if minimum number of chunks is not reached
228+
if(chunkIndices.length < cacheGroup.minChunks)
229+
continue;
230+
// Break if we selected only one chunk but no cache group
231+
if(chunkIndices.length === 1 && !cacheGroup.key)
232+
continue;
233+
// Determine name for split chunk
234+
const name = cacheGroup.getName(module, selectedChunks, cacheGroup.key);
235+
// Create key for maps
236+
// When it has a name we use the name as key
237+
// When it has a cache group we use the cache group key
238+
// Elsewise we create the key from chunks
239+
// This automatically merges equal names
240+
const chunksKey = chunkIndices.sort().join();
241+
const key = name && `name:${name}` ||
242+
cacheGroup.key && `chunks:${chunksKey} key:${cacheGroup.key}` ||
243+
`chunks:${chunksKey}`;
244+
// Add module to maps
245+
let info = chunksInfoMap.get(key);
246+
if(info === undefined) {
247+
chunksInfoMap.set(key, info = {
248+
modules: new SortableSet(undefined, sortByIdentifier),
249+
cacheGroup,
250+
name,
251+
chunks: new Map(),
252+
reusedableChunks: new Set(),
253+
chunksKeys: new Set()
254+
});
255+
}
256+
info.modules.add(module);
257+
if(!info.chunksKeys.has(chunksKey)) {
258+
info.chunksKeys.add(chunksKey);
259+
for(const chunk of selectedChunks) {
260+
info.chunks.set(chunk, chunk.getNumberOfModules());
261+
}
214262
}
215263
}
216264
}
217-
// Get size of module lists and sort them by name and size
218-
const entries = Array.from(chunksInfoMap.entries(), pair => {
219-
const info = pair[1];
220-
info.size = Array.from(info.modules, m => m.size()).reduce((a, b) => a + b, 0);
221-
return info;
222-
}).filter(item => {
223-
if(item.enforce) return true;
224-
// Filter by size limit
225-
if(item.size < item.cacheGroup.minSize) return false;
226-
return true;
227-
}).sort((a, b) => {
228-
// Sort
229-
// 1. by enforced (enforce first)
230-
const enforcedA = a.enforce;
231-
const enforcedB = b.enforce;
232-
if(enforcedA && !enforcedB) return -1;
233-
if(!enforcedA && enforcedB) return 1;
234-
// 2. by total modules size
235-
const diffSize = b.size - a.size;
236-
if(diffSize) return diffSize;
237-
const modulesA = a.modules;
238-
const modulesB = b.modules;
239-
// 3. by module identifiers
240-
const diff = modulesA.size - modulesB.size;
241-
if(diff) return diff;
242-
modulesA.sort();
243-
modulesB.sort();
244-
const aI = modulesA[Symbol.iterator]();
245-
const bI = modulesB[Symbol.iterator]();
246-
while(true) { // eslint-disable-line
247-
const aItem = aI.next();
248-
const bItem = bI.next();
249-
if(aItem.done) return 0;
250-
const aModuleIdentifier = aItem.value.identifier();
251-
const bModuleIdentifier = bItem.value.identifier();
252-
if(aModuleIdentifier > bModuleIdentifier) return -1;
253-
if(aModuleIdentifier < bModuleIdentifier) return 1;
265+
// Get size of module lists
266+
for(const info of chunksInfoMap.values()) {
267+
info.size = getModulesSize(info.modules);
268+
}
269+
let changed = false;
270+
while(chunksInfoMap.size > 0) {
271+
// Find best matching entry
272+
let bestEntryKey;
273+
let bestEntry;
274+
for(const pair of chunksInfoMap) {
275+
const key = pair[0];
276+
const info = pair[1];
277+
if(bestEntry === undefined) {
278+
bestEntry = info;
279+
bestEntryKey = key;
280+
} else if(compareEntries(bestEntry, info) < 0) {
281+
bestEntry = info;
282+
bestEntryKey = key;
283+
}
284+
}
285+
286+
const item = bestEntry;
287+
if(item.size < item.cacheGroup.minSize) {
288+
chunksInfoMap.delete(bestEntryKey);
289+
continue;
254290
}
255-
});
256291

257-
let changed = false;
258-
// Walk though all entries
259-
for(const item of entries) {
260292
let chunkName = item.name;
261293
// Variable for the new chunk (lazy created)
262294
let newChunk;
@@ -322,8 +354,22 @@ module.exports = class AutomaticCommonsChunksPlugin {
322354
GraphHelpers.connectChunkAndModule(newChunk, module);
323355
}
324356
}
357+
// remove all modules from other entries and update size
358+
for(const info of chunksInfoMap.values()) {
359+
if(isOverlap(info.chunks, item.chunks)) {
360+
const oldSize = info.modules.size;
361+
for(const module of item.modules) {
362+
info.modules.delete(module);
363+
}
364+
if(info.modules.size !== oldSize) {
365+
info.size = getModulesSize(info.modules);
366+
}
367+
}
368+
}
325369
changed = true;
326370
}
371+
372+
chunksInfoMap.delete(bestEntryKey);
327373
}
328374
if(changed) return true;
329375
});

test/statsCases/aggressive-splitting-entry/expected.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Child fitting:
44
Time: Xms
55
Asset Size Chunks Chunk Names
66
706f97eb6a2b8fb1a17e.js 1.05 KiB 0 [emitted]
7-
3e99eb0cb296524693dd.js 9.86 KiB 1 [emitted]
7+
3e99eb0cb296524693dd.js 9.92 KiB 1 [emitted]
88
4c485a9e1dc58eedfbd1.js 1.94 KiB 2 [emitted]
99
337c857e3e34cc5ef72d.js 1.94 KiB 3 [emitted]
1010
Entrypoint main = 4c485a9e1dc58eedfbd1.js 337c857e3e34cc5ef72d.js 3e99eb0cb296524693dd.js
@@ -29,7 +29,7 @@ Child content-change:
2929
Time: Xms
3030
Asset Size Chunks Chunk Names
3131
706f97eb6a2b8fb1a17e.js 1.05 KiB 0 [emitted]
32-
3e99eb0cb296524693dd.js 9.86 KiB 1 [emitted]
32+
3e99eb0cb296524693dd.js 9.92 KiB 1 [emitted]
3333
4c485a9e1dc58eedfbd1.js 1.94 KiB 2 [emitted]
3434
337c857e3e34cc5ef72d.js 1.94 KiB 3 [emitted]
3535
Entrypoint main = 4c485a9e1dc58eedfbd1.js 337c857e3e34cc5ef72d.js 3e99eb0cb296524693dd.js

test/statsCases/aggressive-splitting-on-demand/expected.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ f4818881f9e5138ad8ac.js 1 KiB 1 [emitted]
1111
a28f1d7d7367daaaf00a.js 1 KiB 7 [emitted]
1212
1c507089c2c8ceb9757d.js 1.94 KiB 8 [emitted]
1313
9ac0014114101e5f7d2c.js 1.94 KiB 9, 1 [emitted]
14-
f507739d37522e79688a.js 8.33 KiB 10 [emitted] main
14+
f507739d37522e79688a.js 8.38 KiB 10 [emitted] main
1515
Entrypoint main = f507739d37522e79688a.js
1616
chunk {0} ba1a8dd27d611254d495.js 1.76 KiB <{10}> ={2}= ={3}= ={9}= ={7}= ={1}= [recorded] aggressive splitted
1717
> ./b ./d ./e ./f ./g ./h ./i ./j ./k [11] ./index.js 6:0-72

0 commit comments

Comments
 (0)