@@ -9,6 +9,8 @@ const SortableSet = require("../util/SortableSet");
99const GraphHelpers = require ( "../GraphHelpers" ) ;
1010const { isSubset } = require ( "../util/SetHelpers" ) ;
1111
12+ /** @typedef {import("../Chunk") } Chunk */
13+
1214const hashFilename = name => {
1315 return crypto
1416 . createHash ( "md4" )
@@ -78,6 +80,10 @@ const compareEntries = (a, b) => {
7880 }
7981} ;
8082
83+ const INITIAL_CHUNK_FILTER = chunk => chunk . canBeInitial ( ) ;
84+ const ASYNC_CHUNK_FILTER = chunk => ! chunk . canBeInitial ( ) ;
85+ const ALL_CHUNK_FILTER = chunk => true ;
86+
8187module . exports = class SplitChunksPlugin {
8288 constructor ( options ) {
8389 this . options = SplitChunksPlugin . normalizeOptions ( options ) ;
@@ -107,9 +113,20 @@ module.exports = class SplitChunksPlugin {
107113
108114 static normalizeName ( { name, automaticNameDelimiter } ) {
109115 if ( name === true ) {
116+ const cache = new Map ( ) ;
110117 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+ }
111125 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+ }
113130 names . sort ( ) ;
114131 let name =
115132 ( cacheGroup && cacheGroup !== "default"
@@ -124,6 +141,7 @@ module.exports = class SplitChunksPlugin {
124141 name =
125142 name . slice ( 0 , 100 ) + automaticNameDelimiter + hashFilename ( name ) ;
126143 }
144+ cacheEntry [ cacheGroup ] = name ;
127145 return name ;
128146 } ;
129147 return fn ;
@@ -139,23 +157,26 @@ module.exports = class SplitChunksPlugin {
139157
140158 static normalizeChunksFilter ( chunks ) {
141159 if ( chunks === "initial" ) {
142- return chunk => chunk . canBeInitial ( ) ;
160+ return INITIAL_CHUNK_FILTER ;
143161 }
144162 if ( chunks === "async" ) {
145- return chunk => ! chunk . canBeInitial ( ) ;
163+ return ASYNC_CHUNK_FILTER ;
146164 }
147165 if ( chunks === "all" ) {
148- return ( ) => true ;
166+ return ALL_CHUNK_FILTER ;
149167 }
150168 if ( typeof chunks === "function" ) return chunks ;
151169 }
152170
153171 static normalizeCacheGroups ( { cacheGroups, automaticNameDelimiter } ) {
154172 if ( typeof cacheGroups === "function" ) {
173+ // TODO webpack 5 remove this
174+ if ( cacheGroups . length !== 1 )
175+ return module => cacheGroups ( module , module . getChunks ( ) ) ;
155176 return cacheGroups ;
156177 }
157178 if ( cacheGroups && typeof cacheGroups === "object" ) {
158- const fn = ( module , chunks ) => {
179+ const fn = module => {
159180 let results ;
160181 for ( const key of Object . keys ( cacheGroups ) ) {
161182 let option = cacheGroups [ key ] ;
@@ -185,7 +206,7 @@ module.exports = class SplitChunksPlugin {
185206 results . push ( result ) ;
186207 }
187208 }
188- } else if ( SplitChunksPlugin . checkTest ( option . test , module , chunks ) ) {
209+ } else if ( SplitChunksPlugin . checkTest ( option . test , module ) ) {
189210 if ( results === undefined ) results = [ ] ;
190211 results . push ( {
191212 key : key ,
@@ -215,20 +236,27 @@ module.exports = class SplitChunksPlugin {
215236 return fn ;
216237 }
217238
218- static checkTest ( test , module , chunks ) {
239+ static checkTest ( test , module ) {
219240 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+ }
221245 if ( typeof test === "boolean" ) return test ;
222- const names = chunks
223- . map ( c => c . name )
224- . concat ( module . nameForCondition ? [ module . nameForCondition ( ) ] : [ ] )
225- . filter ( Boolean ) ;
226246 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+ }
228252 return false ;
229253 }
230254 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+ }
232260 return false ;
233261 }
234262 return false ;
@@ -256,32 +284,92 @@ module.exports = class SplitChunksPlugin {
256284 . sort ( )
257285 . join ( ) ;
258286 } ;
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 ( ) ;
261289 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+ }
264294 }
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 ) ;
272307 }
273- combinations . set ( key , array ) ;
308+ array . push ( chunksSet ) ;
274309 }
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+
275354 // Map a list of chunks to a list of modules
276355 // For the key the chunk "index" is used, the value is a SortableSet of modules
277356 const chunksInfoMap = new Map ( ) ;
357+
278358 // Walk through all modules
279359 for ( const module of compilation . modules ) {
280- // Get array of chunks
281- const chunks = module . getChunks ( ) ;
282360 // 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+
285373 for ( const cacheGroupSource of cacheGroups ) {
286374 const cacheGroup = {
287375 key : cacheGroupSource . key ,
@@ -323,15 +411,15 @@ module.exports = class SplitChunksPlugin {
323411 reuseExistingChunk : cacheGroupSource . reuseExistingChunk
324412 } ;
325413 // 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 ) {
331415 // Break if minimum number of chunks is not reached
332- if ( chunkIndices . length < cacheGroup . minChunks ) continue ;
416+ if ( chunkCombination . size < cacheGroup . minChunks ) continue ;
333417 // Select chunks by configuration
334- const selectedChunks = Array . from ( chunkCombination ) . filter (
418+ const {
419+ chunks : selectedChunks ,
420+ key : selectedChunksKey
421+ } = getSelectedChunks (
422+ chunkCombination ,
335423 cacheGroup . chunksFilter
336424 ) ;
337425 // Break if minimum number of chunks is not reached
@@ -346,10 +434,9 @@ module.exports = class SplitChunksPlugin {
346434 // When it has a name we use the name as key
347435 // Elsewise we create the key from chunks and cache group key
348436 // This automatically merges equal names
349- const chunksKey = getKey ( selectedChunks ) ;
350437 const key =
351438 ( name && `name:${ name } ` ) ||
352- `chunks:${ chunksKey } key:${ cacheGroup . key } ` ;
439+ `chunks:${ selectedChunksKey } key:${ cacheGroup . key } ` ;
353440 // Add module to maps
354441 let info = chunksInfoMap . get ( key ) ;
355442 if ( info === undefined ) {
@@ -359,30 +446,31 @@ module.exports = class SplitChunksPlugin {
359446 modules : new SortableSet ( undefined , sortByIdentifier ) ,
360447 cacheGroup,
361448 name,
449+ size : 0 ,
362450 chunks : new Map ( ) ,
363451 reusedableChunks : new Set ( ) ,
364452 chunksKeys : new Set ( )
365453 } )
366454 ) ;
367455 }
368456 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 ) ;
371460 for ( const chunk of selectedChunks ) {
372461 info . chunks . set ( chunk , chunk . getNumberOfModules ( ) ) ;
373462 }
374463 }
375464 }
376465 }
377466 }
467+
378468 for ( const [ key , info ] of chunksInfoMap ) {
379469 // Get size of module lists
380- info . size = getModulesSize ( info . modules ) ;
381470 if ( info . size < info . cacheGroup . minSize ) {
382471 chunksInfoMap . delete ( key ) ;
383472 }
384473 }
385- let changed = false ;
386474 while ( chunksInfoMap . size > 0 ) {
387475 // Find best matching entry
388476 let bestEntryKey ;
@@ -429,6 +517,7 @@ module.exports = class SplitChunksPlugin {
429517 }
430518 }
431519 }
520+ const usedChunks = [ ] ;
432521 // Walk through all chunks
433522 for ( const chunk of item . chunks . keys ( ) ) {
434523 // skip if we address ourself
@@ -450,12 +539,9 @@ module.exports = class SplitChunksPlugin {
450539 }
451540 // Add graph connections for splitted chunk
452541 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 ) ;
458543 }
544+
459545 // If we successfully created a new chunk or reused one
460546 if ( newChunk ) {
461547 // Add a note to the chunk
@@ -491,7 +577,24 @@ module.exports = class SplitChunksPlugin {
491577 if ( ! isReused ) {
492578 // Add all modules to the new chunk
493579 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
494584 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+ }
495598 }
496599 }
497600 // remove all modules from other entries and update size
@@ -512,10 +615,8 @@ module.exports = class SplitChunksPlugin {
512615 }
513616 }
514617 }
515- changed = true ;
516618 }
517619 }
518- if ( changed ) return true ;
519620 }
520621 ) ;
521622 } ) ;
0 commit comments