@@ -9,6 +9,8 @@ const SortableSet = require("../util/SortableSet");
9
9
const GraphHelpers = require ( "../GraphHelpers" ) ;
10
10
const { isSubset } = require ( "../util/SetHelpers" ) ;
11
11
12
+ /** @typedef {import("../Chunk") } Chunk */
13
+
12
14
const hashFilename = name => {
13
15
return crypto
14
16
. createHash ( "md4" )
@@ -78,6 +80,10 @@ const compareEntries = (a, b) => {
78
80
}
79
81
} ;
80
82
83
+ const INITIAL_CHUNK_FILTER = chunk => chunk . canBeInitial ( ) ;
84
+ const ASYNC_CHUNK_FILTER = chunk => ! chunk . canBeInitial ( ) ;
85
+ const ALL_CHUNK_FILTER = chunk => true ;
86
+
81
87
module . exports = class SplitChunksPlugin {
82
88
constructor ( options ) {
83
89
this . options = SplitChunksPlugin . normalizeOptions ( options ) ;
@@ -107,9 +113,20 @@ module.exports = class SplitChunksPlugin {
107
113
108
114
static normalizeName ( { name, automaticNameDelimiter } ) {
109
115
if ( name === true ) {
116
+ const cache = new Map ( ) ;
110
117
const fn = ( module , chunks , cacheGroup ) => {
118
+ let cacheEntry = cache . get ( chunks ) ;
119
+ if ( cacheEntry === undefined ) {
120
+ cacheEntry = { } ;
121
+ cache . set ( chunks , cacheEntry ) ;
122
+ } else if ( cacheGroup in cacheEntry ) {
123
+ return cacheEntry [ cacheGroup ] ;
124
+ }
111
125
const names = chunks . map ( c => c . name ) ;
112
- if ( ! names . every ( Boolean ) ) return ;
126
+ if ( ! names . every ( Boolean ) ) {
127
+ cacheEntry [ cacheGroup ] = undefined ;
128
+ return ;
129
+ }
113
130
names . sort ( ) ;
114
131
let name =
115
132
( cacheGroup && cacheGroup !== "default"
@@ -124,6 +141,7 @@ module.exports = class SplitChunksPlugin {
124
141
name =
125
142
name . slice ( 0 , 100 ) + automaticNameDelimiter + hashFilename ( name ) ;
126
143
}
144
+ cacheEntry [ cacheGroup ] = name ;
127
145
return name ;
128
146
} ;
129
147
return fn ;
@@ -139,23 +157,26 @@ module.exports = class SplitChunksPlugin {
139
157
140
158
static normalizeChunksFilter ( chunks ) {
141
159
if ( chunks === "initial" ) {
142
- return chunk => chunk . canBeInitial ( ) ;
160
+ return INITIAL_CHUNK_FILTER ;
143
161
}
144
162
if ( chunks === "async" ) {
145
- return chunk => ! chunk . canBeInitial ( ) ;
163
+ return ASYNC_CHUNK_FILTER ;
146
164
}
147
165
if ( chunks === "all" ) {
148
- return ( ) => true ;
166
+ return ALL_CHUNK_FILTER ;
149
167
}
150
168
if ( typeof chunks === "function" ) return chunks ;
151
169
}
152
170
153
171
static normalizeCacheGroups ( { cacheGroups, automaticNameDelimiter } ) {
154
172
if ( typeof cacheGroups === "function" ) {
173
+ // TODO webpack 5 remove this
174
+ if ( cacheGroups . length !== 1 )
175
+ return module => cacheGroups ( module , module . getChunks ( ) ) ;
155
176
return cacheGroups ;
156
177
}
157
178
if ( cacheGroups && typeof cacheGroups === "object" ) {
158
- const fn = ( module , chunks ) => {
179
+ const fn = module => {
159
180
let results ;
160
181
for ( const key of Object . keys ( cacheGroups ) ) {
161
182
let option = cacheGroups [ key ] ;
@@ -185,7 +206,7 @@ module.exports = class SplitChunksPlugin {
185
206
results . push ( result ) ;
186
207
}
187
208
}
188
- } else if ( SplitChunksPlugin . checkTest ( option . test , module , chunks ) ) {
209
+ } else if ( SplitChunksPlugin . checkTest ( option . test , module ) ) {
189
210
if ( results === undefined ) results = [ ] ;
190
211
results . push ( {
191
212
key : key ,
@@ -215,20 +236,27 @@ module.exports = class SplitChunksPlugin {
215
236
return fn ;
216
237
}
217
238
218
- static checkTest ( test , module , chunks ) {
239
+ static checkTest ( test , module ) {
219
240
if ( test === undefined ) return true ;
220
- if ( typeof test === "function" ) return test ( module , chunks ) ;
241
+ if ( typeof test === "function" ) {
242
+ if ( test . length !== 1 ) return test ( module , module . getChunks ( ) ) ;
243
+ return test ( module ) ;
244
+ }
221
245
if ( typeof test === "boolean" ) return test ;
222
- const names = chunks
223
- . map ( c => c . name )
224
- . concat ( module . nameForCondition ? [ module . nameForCondition ( ) ] : [ ] )
225
- . filter ( Boolean ) ;
226
246
if ( typeof test === "string" ) {
227
- for ( const name of names ) if ( name . startsWith ( test ) ) return true ;
247
+ if ( module . nameForCondition && module . nameForCondition ( ) . startsWith ( test ) )
248
+ return true ;
249
+ for ( const chunk of module . chunksIterable ) {
250
+ if ( chunk . name && chunk . name . startsWith ( test ) ) return true ;
251
+ }
228
252
return false ;
229
253
}
230
254
if ( test instanceof RegExp ) {
231
- for ( const name of names ) if ( test . test ( name ) ) return true ;
255
+ if ( module . nameForCondition && test . test ( module . nameForCondition ( ) ) )
256
+ return true ;
257
+ for ( const chunk of module . chunksIterable ) {
258
+ if ( chunk . name && test . test ( chunk . name ) ) return true ;
259
+ }
232
260
return false ;
233
261
}
234
262
return false ;
@@ -256,32 +284,92 @@ module.exports = class SplitChunksPlugin {
256
284
. sort ( )
257
285
. join ( ) ;
258
286
} ;
259
- // Create a list of possible combinations
260
- const chunkSetsInGraph = new Map ( ) ; // Map<string, Set<Chunk>>
287
+ /** @type { Map<string, Set<Chunk>> } */
288
+ const chunkSetsInGraph = new Map ( ) ;
261
289
for ( const module of compilation . modules ) {
262
- const chunkIndices = getKey ( module . chunksIterable ) ;
263
- chunkSetsInGraph . set ( chunkIndices , new Set ( module . chunksIterable ) ) ;
290
+ const chunksKey = getKey ( module . chunksIterable ) ;
291
+ if ( ! chunkSetsInGraph . has ( chunksKey ) ) {
292
+ chunkSetsInGraph . set ( chunksKey , new Set ( module . chunksIterable ) ) ;
293
+ }
264
294
}
265
- const combinations = new Map ( ) ; // Map<string, Set<Chunk>[]>
266
- for ( const [ key , chunksSet ] of chunkSetsInGraph ) {
267
- var array = [ ] ;
268
- for ( const set of chunkSetsInGraph . values ( ) ) {
269
- if ( isSubset ( chunksSet , set ) ) {
270
- array . push ( set ) ;
271
- }
295
+
296
+ // group these set of chunks by count
297
+ // to allow to check less sets via isSubset
298
+ // (only smaller sets can be subset)
299
+ /** @type {Map<number, Array<Set<Chunk>>> } */
300
+ const chunkSetsByCount = new Map ( ) ;
301
+ for ( const chunksSet of chunkSetsInGraph . values ( ) ) {
302
+ const count = chunksSet . size ;
303
+ let array = chunkSetsByCount . get ( count ) ;
304
+ if ( array === undefined ) {
305
+ array = [ ] ;
306
+ chunkSetsByCount . set ( count , array ) ;
272
307
}
273
- combinations . set ( key , array ) ;
308
+ array . push ( chunksSet ) ;
274
309
}
310
+
311
+ // Create a list of possible combinations
312
+ const combinationsCache = new Map ( ) ; // Map<string, Set<Chunk>[]>
313
+ const selectedChunksCacheByChunksSet = new WeakMap ( ) ; // WeakMap<Set<Chunk>, WeakMap<Function, {chunks: Chunk[], key: string}>>
314
+
315
+ const getCombinations = key => {
316
+ const chunksSet = chunkSetsInGraph . get ( key ) ;
317
+ var array = [ chunksSet ] ;
318
+ if ( chunksSet . size > 1 ) {
319
+ for ( const [ count , setArray ] of chunkSetsByCount ) {
320
+ // "equal" is not needed because they would have been merge in the first step
321
+ if ( count < chunksSet . size ) {
322
+ for ( const set of setArray ) {
323
+ if ( isSubset ( chunksSet , set ) ) {
324
+ array . push ( set ) ;
325
+ }
326
+ }
327
+ }
328
+ }
329
+ }
330
+ return array ;
331
+ } ;
332
+
333
+ const getSelectedChunks = ( chunks , chunkFilter ) => {
334
+ let entry = selectedChunksCacheByChunksSet . get ( chunks ) ;
335
+ if ( entry === undefined ) {
336
+ entry = new Map ( ) ;
337
+ selectedChunksCacheByChunksSet . set ( chunks , entry ) ;
338
+ }
339
+ let entry2 = entry . get ( chunkFilter ) ;
340
+ if ( entry2 === undefined ) {
341
+ const selectedChunks = [ ] ;
342
+ for ( const chunk of chunks ) {
343
+ if ( chunkFilter ( chunk ) ) selectedChunks . push ( chunk ) ;
344
+ }
345
+ entry2 = {
346
+ chunks : selectedChunks ,
347
+ key : getKey ( selectedChunks )
348
+ } ;
349
+ entry . set ( chunkFilter , entry2 ) ;
350
+ }
351
+ return entry2 ;
352
+ } ;
353
+
275
354
// Map a list of chunks to a list of modules
276
355
// For the key the chunk "index" is used, the value is a SortableSet of modules
277
356
const chunksInfoMap = new Map ( ) ;
357
+
278
358
// Walk through all modules
279
359
for ( const module of compilation . modules ) {
280
- // Get array of chunks
281
- const chunks = module . getChunks ( ) ;
282
360
// Get cache group
283
- let cacheGroups = this . options . getCacheGroups ( module , chunks ) ;
284
- if ( ! Array . isArray ( cacheGroups ) ) continue ;
361
+ let cacheGroups = this . options . getCacheGroups ( module ) ;
362
+ if ( ! Array . isArray ( cacheGroups ) || cacheGroups . length === 0 )
363
+ continue ;
364
+
365
+ // Prepare some values
366
+ const chunksKey = getKey ( module . chunksIterable ) ;
367
+ let combs = combinationsCache . get ( chunksKey ) ;
368
+ if ( combs === undefined ) {
369
+ combs = getCombinations ( chunksKey ) ;
370
+ combinationsCache . set ( chunksKey , combs ) ;
371
+ }
372
+
285
373
for ( const cacheGroupSource of cacheGroups ) {
286
374
const cacheGroup = {
287
375
key : cacheGroupSource . key ,
@@ -323,15 +411,15 @@ module.exports = class SplitChunksPlugin {
323
411
reuseExistingChunk : cacheGroupSource . reuseExistingChunk
324
412
} ;
325
413
// For all combination of chunk selection
326
- for ( const chunkCombination of combinations . get ( getKey ( chunks ) ) ) {
327
- // Get indices of chunks in which this module occurs
328
- const chunkIndices = Array . from ( chunkCombination , chunk =>
329
- indexMap . get ( chunk )
330
- ) ;
414
+ for ( const chunkCombination of combs ) {
331
415
// Break if minimum number of chunks is not reached
332
- if ( chunkIndices . length < cacheGroup . minChunks ) continue ;
416
+ if ( chunkCombination . size < cacheGroup . minChunks ) continue ;
333
417
// Select chunks by configuration
334
- const selectedChunks = Array . from ( chunkCombination ) . filter (
418
+ const {
419
+ chunks : selectedChunks ,
420
+ key : selectedChunksKey
421
+ } = getSelectedChunks (
422
+ chunkCombination ,
335
423
cacheGroup . chunksFilter
336
424
) ;
337
425
// Break if minimum number of chunks is not reached
@@ -346,10 +434,9 @@ module.exports = class SplitChunksPlugin {
346
434
// When it has a name we use the name as key
347
435
// Elsewise we create the key from chunks and cache group key
348
436
// This automatically merges equal names
349
- const chunksKey = getKey ( selectedChunks ) ;
350
437
const key =
351
438
( name && `name:${ name } ` ) ||
352
- `chunks:${ chunksKey } key:${ cacheGroup . key } ` ;
439
+ `chunks:${ selectedChunksKey } key:${ cacheGroup . key } ` ;
353
440
// Add module to maps
354
441
let info = chunksInfoMap . get ( key ) ;
355
442
if ( info === undefined ) {
@@ -359,30 +446,31 @@ module.exports = class SplitChunksPlugin {
359
446
modules : new SortableSet ( undefined , sortByIdentifier ) ,
360
447
cacheGroup,
361
448
name,
449
+ size : 0 ,
362
450
chunks : new Map ( ) ,
363
451
reusedableChunks : new Set ( ) ,
364
452
chunksKeys : new Set ( )
365
453
} )
366
454
) ;
367
455
}
368
456
info . modules . add ( module ) ;
369
- if ( ! info . chunksKeys . has ( chunksKey ) ) {
370
- info . chunksKeys . add ( chunksKey ) ;
457
+ info . size += module . size ( ) ;
458
+ if ( ! info . chunksKeys . has ( selectedChunksKey ) ) {
459
+ info . chunksKeys . add ( selectedChunksKey ) ;
371
460
for ( const chunk of selectedChunks ) {
372
461
info . chunks . set ( chunk , chunk . getNumberOfModules ( ) ) ;
373
462
}
374
463
}
375
464
}
376
465
}
377
466
}
467
+
378
468
for ( const [ key , info ] of chunksInfoMap ) {
379
469
// Get size of module lists
380
- info . size = getModulesSize ( info . modules ) ;
381
470
if ( info . size < info . cacheGroup . minSize ) {
382
471
chunksInfoMap . delete ( key ) ;
383
472
}
384
473
}
385
- let changed = false ;
386
474
while ( chunksInfoMap . size > 0 ) {
387
475
// Find best matching entry
388
476
let bestEntryKey ;
@@ -429,6 +517,7 @@ module.exports = class SplitChunksPlugin {
429
517
}
430
518
}
431
519
}
520
+ const usedChunks = [ ] ;
432
521
// Walk through all chunks
433
522
for ( const chunk of item . chunks . keys ( ) ) {
434
523
// skip if we address ourself
@@ -450,12 +539,9 @@ module.exports = class SplitChunksPlugin {
450
539
}
451
540
// Add graph connections for splitted chunk
452
541
chunk . split ( newChunk ) ;
453
- // Remove all selected modules from the chunk
454
- for ( const module of item . modules ) {
455
- chunk . removeModule ( module ) ;
456
- module . rewriteChunkInReasons ( chunk , [ newChunk ] ) ;
457
- }
542
+ usedChunks . push ( chunk ) ;
458
543
}
544
+
459
545
// If we successfully created a new chunk or reused one
460
546
if ( newChunk ) {
461
547
// Add a note to the chunk
@@ -491,7 +577,24 @@ module.exports = class SplitChunksPlugin {
491
577
if ( ! isReused ) {
492
578
// Add all modules to the new chunk
493
579
for ( const module of item . modules ) {
580
+ if ( typeof module . chunkCondition === "function" ) {
581
+ if ( ! module . chunkCondition ( newChunk ) ) continue ;
582
+ }
583
+ // Add module to new chunk
494
584
GraphHelpers . connectChunkAndModule ( newChunk , module ) ;
585
+ // Remove module from used chunks
586
+ for ( const chunk of usedChunks ) {
587
+ chunk . removeModule ( module ) ;
588
+ module . rewriteChunkInReasons ( chunk , [ newChunk ] ) ;
589
+ }
590
+ }
591
+ } else {
592
+ // Remove all modules from used chunks
593
+ for ( const module of item . modules ) {
594
+ for ( const chunk of usedChunks ) {
595
+ chunk . removeModule ( module ) ;
596
+ module . rewriteChunkInReasons ( chunk , [ newChunk ] ) ;
597
+ }
495
598
}
496
599
}
497
600
// remove all modules from other entries and update size
@@ -512,10 +615,8 @@ module.exports = class SplitChunksPlugin {
512
615
}
513
616
}
514
617
}
515
- changed = true ;
516
618
}
517
619
}
518
- if ( changed ) return true ;
519
620
}
520
621
) ;
521
622
} ) ;
0 commit comments