@@ -40,18 +40,41 @@ const isOverlap = (a, b) => {
4040 return false ;
4141} ;
4242
43+ const getCombinations = ( array , maxDepth ) => {
44+ const results = [ ] ;
45+ getCombinationsInteral ( array , maxDepth , results ) ;
46+ return results ;
47+ } ;
48+
49+ const getCombinationsInteral = ( array , maxDepth , results ) => {
50+ results . push ( array ) ;
51+ if ( maxDepth === 0 || array . length === 1 ) {
52+ return ;
53+ }
54+ for ( let i = 0 ; i < array . length ; i ++ ) {
55+ const newArray = array . slice ( 0 , i ) . concat ( array . slice ( i + 1 , array . length ) ) ;
56+ getCombinationsInteral ( newArray , maxDepth - 1 , results ) ;
57+ }
58+ } ;
59+
4360const compareEntries = ( a , b ) => {
4461 // 1. by priority
4562 const diffPriority = a . cacheGroup . priority - b . cacheGroup . priority ;
4663 if ( diffPriority ) return diffPriority ;
47- // 2. by total modules size
48- const diffSize = b . size - a . size ;
49- if ( diffSize ) return diffSize ;
64+ // 2. by number of chunks
65+ const diffCount = a . chunks . size - b . chunks . size ;
66+ if ( diffCount ) return diffCount ;
67+ // 3. by size reduction
68+ const aSizeReduce = a . size * ( a . chunks . size - 1 ) ;
69+ const bSizeReduce = b . size * ( b . chunks . size - 1 ) ;
70+ const diffSizeReduce = aSizeReduce - bSizeReduce ;
71+ if ( diffSizeReduce ) return diffSizeReduce ;
72+ // 4. by number of modules (to be able to compare by identifier)
5073 const modulesA = a . modules ;
5174 const modulesB = b . modules ;
52- // 3. by module identifiers
5375 const diff = modulesA . size - modulesB . size ;
5476 if ( diff ) return diff ;
77+ // 5. by module identifiers
5578 modulesA . sort ( ) ;
5679 modulesB . sort ( ) ;
5780 const aI = modulesA [ Symbol . iterator ] ( ) ;
@@ -74,6 +97,7 @@ module.exports = class SplitChunksPlugin {
7497
7598 static normalizeOptions ( options ) {
7699 return {
100+ maxComplexity : options . maxComplexity || 0 ,
77101 chunks : options . chunks || "all" ,
78102 minSize : options . minSize || 0 ,
79103 minChunks : options . minChunks || 1 ,
@@ -226,47 +250,53 @@ module.exports = class SplitChunksPlugin {
226250 reuseExistingChunk : cacheGroupSource . reuseExistingChunk
227251 } ;
228252 // Select chunks by configuration
229- const selectedChunks = cacheGroup . chunks === "initial" ? chunks . filter ( chunk => chunk . canBeInitial ( ) ) :
253+ const maxSelectedChunks = cacheGroup . chunks === "initial" ? chunks . filter ( chunk => chunk . canBeInitial ( ) ) :
230254 cacheGroup . chunks === "async" ? chunks . filter ( chunk => ! chunk . canBeInitial ( ) ) :
231255 chunks ;
232- // Get indices of chunks in which this module occurs
233- const chunkIndices = selectedChunks . map ( chunk => indexMap . get ( chunk ) ) ;
234- // Break if minimum number of chunks is not reached
235- if ( chunkIndices . length < cacheGroup . minChunks )
236- continue ;
237- // Determine name for split chunk
238- const name = cacheGroup . getName ( module , selectedChunks , cacheGroup . key ) ;
239- // Create key for maps
240- // When it has a name we use the name as key
241- // Elsewise we create the key from chunks and cache group key
242- // This automatically merges equal names
243- const chunksKey = chunkIndices . sort ( ) . join ( ) ;
244- const key = name && `name:${ name } ` ||
245- `chunks:${ chunksKey } key:${ cacheGroup . key } ` ;
246- // Add module to maps
247- let info = chunksInfoMap . get ( key ) ;
248- if ( info === undefined ) {
249- chunksInfoMap . set ( key , info = {
250- modules : new SortableSet ( undefined , sortByIdentifier ) ,
251- cacheGroup,
252- name,
253- chunks : new Map ( ) ,
254- reusedableChunks : new Set ( ) ,
255- chunksKeys : new Set ( )
256- } ) ;
257- }
258- info . modules . add ( module ) ;
259- if ( ! info . chunksKeys . has ( chunksKey ) ) {
260- info . chunksKeys . add ( chunksKey ) ;
261- for ( const chunk of selectedChunks ) {
262- info . chunks . set ( chunk , chunk . getNumberOfModules ( ) ) ;
256+ // For all (nearly all) combination of chunk selection
257+ for ( const selectedChunks of getCombinations ( maxSelectedChunks , this . options . maxComplexity ) ) {
258+ // Get indices of chunks in which this module occurs
259+ const chunkIndices = selectedChunks . map ( chunk => indexMap . get ( chunk ) ) ;
260+ // Break if minimum number of chunks is not reached
261+ if ( chunkIndices . length < cacheGroup . minChunks )
262+ continue ;
263+ // Determine name for split chunk
264+ const name = cacheGroup . getName ( module , selectedChunks , cacheGroup . key ) ;
265+ // Create key for maps
266+ // When it has a name we use the name as key
267+ // Elsewise we create the key from chunks and cache group key
268+ // This automatically merges equal names
269+ const chunksKey = chunkIndices . sort ( ) . join ( ) ;
270+ const key = name && `name:${ name } ` ||
271+ `chunks:${ chunksKey } key:${ cacheGroup . key } ` ;
272+ // Add module to maps
273+ let info = chunksInfoMap . get ( key ) ;
274+ if ( info === undefined ) {
275+ chunksInfoMap . set ( key , info = {
276+ modules : new SortableSet ( undefined , sortByIdentifier ) ,
277+ cacheGroup,
278+ name,
279+ chunks : new Map ( ) ,
280+ reusedableChunks : new Set ( ) ,
281+ chunksKeys : new Set ( )
282+ } ) ;
283+ }
284+ info . modules . add ( module ) ;
285+ if ( ! info . chunksKeys . has ( chunksKey ) ) {
286+ info . chunksKeys . add ( chunksKey ) ;
287+ for ( const chunk of selectedChunks ) {
288+ info . chunks . set ( chunk , chunk . getNumberOfModules ( ) ) ;
289+ }
263290 }
264291 }
265292 }
266293 }
267- for ( const info of chunksInfoMap . values ( ) ) {
294+ for ( const [ key , info ] of chunksInfoMap ) {
268295 // Get size of module lists
269296 info . size = getModulesSize ( info . modules ) ;
297+ if ( info . size < info . cacheGroup . minSize ) {
298+ chunksInfoMap . delete ( key ) ;
299+ }
270300 }
271301 let changed = false ;
272302 while ( chunksInfoMap . size > 0 ) {
@@ -286,10 +316,7 @@ module.exports = class SplitChunksPlugin {
286316 }
287317
288318 const item = bestEntry ;
289- if ( item . size < item . cacheGroup . minSize ) {
290- chunksInfoMap . delete ( bestEntryKey ) ;
291- continue ;
292- }
319+ chunksInfoMap . delete ( bestEntryKey ) ;
293320
294321 let chunkName = item . name ;
295322 // Variable for the new chunk (lazy created)
@@ -357,21 +384,25 @@ module.exports = class SplitChunksPlugin {
357384 }
358385 }
359386 // remove all modules from other entries and update size
360- for ( const info of chunksInfoMap . values ( ) ) {
387+ for ( const [ key , info ] of chunksInfoMap ) {
361388 if ( isOverlap ( info . chunks , item . chunks ) ) {
362389 const oldSize = info . modules . size ;
363390 for ( const module of item . modules ) {
364391 info . modules . delete ( module ) ;
365392 }
393+ if ( info . modules . size === 0 ) {
394+ chunksInfoMap . delete ( key ) ;
395+ continue ;
396+ }
366397 if ( info . modules . size !== oldSize ) {
367398 info . size = getModulesSize ( info . modules ) ;
399+ if ( info . size < info . cacheGroup . minSize )
400+ chunksInfoMap . delete ( key ) ;
368401 }
369402 }
370403 }
371404 changed = true ;
372405 }
373-
374- chunksInfoMap . delete ( bestEntryKey ) ;
375406 }
376407 if ( changed ) return true ;
377408 } ) ;
0 commit comments