@@ -21,6 +21,44 @@ const getRequests = chunk => {
21
21
return requests ;
22
22
} ;
23
23
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
+
24
62
module . exports = class AutomaticCommonsChunksPlugin {
25
63
constructor ( options ) {
26
64
this . options = AutomaticCommonsChunksPlugin . normalizeOptions ( options ) ;
@@ -35,7 +73,7 @@ module.exports = class AutomaticCommonsChunksPlugin {
35
73
maxAsyncRequests : options . maxAsyncRequests || 1 ,
36
74
maxInitialRequests : options . maxInitialRequests || 1 ,
37
75
getName : AutomaticCommonsChunksPlugin . normalizeName ( options . name ) || ( ( ) => { } ) ,
38
- getCacheGroup : AutomaticCommonsChunksPlugin . normalizeCacheGroups ( options . cacheGroups ) ,
76
+ getCacheGroups : AutomaticCommonsChunksPlugin . normalizeCacheGroups ( options . cacheGroups ) ,
39
77
} ;
40
78
}
41
79
@@ -68,6 +106,7 @@ module.exports = class AutomaticCommonsChunksPlugin {
68
106
}
69
107
if ( cacheGroups && typeof cacheGroups === "object" ) {
70
108
return ( module , chunks ) => {
109
+ let results ;
71
110
for ( const key of Object . keys ( cacheGroups ) ) {
72
111
let option = cacheGroups [ key ] ;
73
112
if ( option instanceof RegExp || typeof option === "string" ) {
@@ -78,15 +117,18 @@ module.exports = class AutomaticCommonsChunksPlugin {
78
117
if ( typeof option === "function" ) {
79
118
let result = option ( module ) ;
80
119
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
+ }
86
128
}
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 ( {
90
132
key : key ,
91
133
getName : AutomaticCommonsChunksPlugin . normalizeName ( option . name ) ,
92
134
chunks : option . chunks ,
@@ -96,15 +138,18 @@ module.exports = class AutomaticCommonsChunksPlugin {
96
138
maxAsyncRequests : option . maxAsyncRequests ,
97
139
maxInitialRequests : option . maxInitialRequests ,
98
140
reuseExistingChunk : option . reuseExistingChunk
99
- } ;
141
+ } ) ;
100
142
}
101
143
}
144
+ return results ;
102
145
} ;
103
146
}
104
147
return ( ) => { } ;
105
148
}
106
149
107
150
static checkTest ( test , module , chunks ) {
151
+ if ( test === undefined )
152
+ return true ;
108
153
if ( typeof test === "function" )
109
154
return test ( module , chunks ) ;
110
155
if ( typeof test === "boolean" )
@@ -147,9 +192,9 @@ module.exports = class AutomaticCommonsChunksPlugin {
147
192
// Get array of chunks
148
193
const chunks = module . getChunks ( ) ;
149
194
// 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 => ( {
153
198
key : cacheGroup . key ,
154
199
chunks : cacheGroup . chunks || this . options . chunks ,
155
200
minSize : cacheGroup . minSize !== undefined ? cacheGroup . minSize : cacheGroup . enforce ? 0 : this . options . minSize ,
@@ -158,105 +203,92 @@ module.exports = class AutomaticCommonsChunksPlugin {
158
203
maxInitialRequests : cacheGroup . maxInitialRequests !== undefined ? cacheGroup . maxInitialRequests : cacheGroup . enforce ? Infinity : this . options . maxInitialRequests ,
159
204
getName : cacheGroup . getName !== undefined ? cacheGroup . getName : this . options . getName ,
160
205
reuseExistingChunk : cacheGroup . reuseExistingChunk
161
- } ;
206
+ } ) ) ;
162
207
} 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 = [ ] ;
208
209
}
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
+ }
214
262
}
215
263
}
216
264
}
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 ;
254
290
}
255
- } ) ;
256
291
257
- let changed = false ;
258
- // Walk though all entries
259
- for ( const item of entries ) {
260
292
let chunkName = item . name ;
261
293
// Variable for the new chunk (lazy created)
262
294
let newChunk ;
@@ -322,8 +354,22 @@ module.exports = class AutomaticCommonsChunksPlugin {
322
354
GraphHelpers . connectChunkAndModule ( newChunk , module ) ;
323
355
}
324
356
}
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
+ }
325
369
changed = true ;
326
370
}
371
+
372
+ chunksInfoMap . delete ( bestEntryKey ) ;
327
373
}
328
374
if ( changed ) return true ;
329
375
} ) ;
0 commit comments