1
- import fs from 'fs' ;
2
1
import path from 'path' ;
3
2
4
3
import webpack from 'webpack' ;
@@ -7,9 +6,12 @@ import sources from 'webpack-sources';
7
6
const hotLoader = path . resolve ( __dirname , './hotLoader.js' ) ;
8
7
9
8
const { ConcatSource, SourceMapSource, OriginalSource } = sources ;
10
- const { Template, util : { createHash } } = webpack ;
9
+ const {
10
+ Template,
11
+ util : { createHash } ,
12
+ } = webpack ;
11
13
12
- const NS = path . dirname ( fs . realpathSync ( __filename ) ) ;
14
+ const MODULE_TYPE = 'css/extract-chunks' ;
13
15
14
16
const pluginName = 'extract-css-chunks-webpack-plugin' ;
15
17
@@ -58,7 +60,7 @@ class CssDependencyTemplate {
58
60
59
61
class CssModule extends webpack . Module {
60
62
constructor ( dependency ) {
61
- super ( NS , dependency . context ) ;
63
+ super ( MODULE_TYPE , dependency . context ) ;
62
64
this . _identifier = dependency . identifier ;
63
65
this . _identifierIndex = dependency . identifierIndex ;
64
66
this . content = dependency . content ;
@@ -114,35 +116,48 @@ class CssModule extends webpack.Module {
114
116
}
115
117
116
118
class CssModuleFactory {
117
- create ( { dependencies : [ dependency ] } , callback ) {
119
+ create (
120
+ {
121
+ dependencies : [ dependency ] ,
122
+ } ,
123
+ callback ,
124
+ ) {
118
125
callback ( null , new CssModule ( dependency ) ) ;
119
126
}
120
127
}
121
128
122
129
class ExtractCssChunks {
123
130
constructor ( options ) {
124
- this . options = Object . assign (
125
- {
126
- filename : '[name].css' ,
127
- } ,
128
- options ,
129
- ) ;
131
+ this . options = Object . assign ( { filename : '[name].css' } , options ) ;
132
+ const { cssModules, reloadAll } = this . options ;
133
+
130
134
if ( ! this . options . chunkFilename ) {
131
135
const { filename } = this . options ;
132
136
const hasName = filename . includes ( '[name]' ) ;
133
137
const hasId = filename . includes ( '[id]' ) ;
134
138
const hasChunkHash = filename . includes ( '[chunkhash]' ) ;
135
- // Anything changing depending on chunk is fine
139
+
140
+ // Anything changing depending on chunk is fine
136
141
if ( hasChunkHash || hasName || hasId ) {
137
142
this . options . chunkFilename = filename ;
138
143
} else {
139
- // Elsewise prefix '[id].' in front of the basename to make it changing
140
- this . options . chunkFilename = filename . replace (
141
- / ( ^ | \/ ) ( [ ^ / ] * (?: \? | $ ) ) / ,
142
- '$1[id].$2' ,
143
- ) ;
144
+ // Elsewise prefix '[id].' in front of the basename to make it changing
145
+ this . options . chunkFilename = filename . replace ( / ( ^ | \/ ) ( [ ^ / ] * (?: \? | $ ) ) / , '$1[id].$2' ) ;
144
146
}
145
147
}
148
+
149
+ this . hotLoaderObject = Object . assign ( {
150
+ loader : hotLoader ,
151
+ options : {
152
+ cssModules : false ,
153
+ reloadAll : false ,
154
+ } ,
155
+ } , {
156
+ options : {
157
+ cssModules,
158
+ reloadAll,
159
+ } ,
160
+ } ) ;
146
161
}
147
162
148
163
apply ( compiler ) {
@@ -153,20 +168,16 @@ class ExtractCssChunks {
153
168
compiler . options . module . rules = this . updateWebpackConfig ( compiler . options . module . rules ) ;
154
169
}
155
170
} catch ( e ) {
156
- console . error ( ' Something went wrong: contact the author' , JSON . stringify ( e ) ) ; // eslint-disable-line no-console
171
+ throw new Error ( ` Something went wrong: contact the author: ${ JSON . stringify ( e ) } ` ) ;
157
172
}
158
173
159
174
compiler . hooks . thisCompilation . tap ( pluginName , ( compilation ) => {
160
175
compilation . hooks . normalModuleLoader . tap ( pluginName , ( lc , m ) => {
161
176
const loaderContext = lc ;
162
177
const module = m ;
163
- loaderContext [ NS ] = ( content ) => {
178
+ loaderContext [ MODULE_TYPE ] = ( content ) => {
164
179
if ( ! Array . isArray ( content ) && content != null ) {
165
- throw new Error (
166
- `Exported value was not extracted as an array: ${ JSON . stringify (
167
- content ,
168
- ) } `,
169
- ) ;
180
+ throw new Error ( `Exported value was not extracted as an array: ${ JSON . stringify ( content ) } ` ) ;
170
181
}
171
182
const identifierCountMap = new Map ( ) ;
172
183
for ( const line of content ) {
@@ -188,7 +199,7 @@ class ExtractCssChunks {
188
199
pluginName ,
189
200
( result , { chunk } ) => {
190
201
const renderedModules = Array . from ( chunk . modulesIterable ) . filter (
191
- module => module . type === NS ,
202
+ module => module . type === MODULE_TYPE ,
192
203
) ;
193
204
if ( renderedModules . length > 0 ) {
194
205
result . push ( {
@@ -202,10 +213,10 @@ class ExtractCssChunks {
202
213
filenameTemplate : this . options . filename ,
203
214
pathOptions : {
204
215
chunk,
205
- contentHashType : NS ,
216
+ contentHashType : MODULE_TYPE ,
206
217
} ,
207
218
identifier : `${ pluginName } .${ chunk . id } ` ,
208
- hash : chunk . contentHash [ NS ] ,
219
+ hash : chunk . contentHash [ MODULE_TYPE ] ,
209
220
} ) ;
210
221
}
211
222
} ,
@@ -214,7 +225,7 @@ class ExtractCssChunks {
214
225
pluginName ,
215
226
( result , { chunk } ) => {
216
227
const renderedModules = Array . from ( chunk . modulesIterable ) . filter (
217
- module => module . type === NS ,
228
+ module => module . type === MODULE_TYPE ,
218
229
) ;
219
230
if ( renderedModules . length > 0 ) {
220
231
result . push ( {
@@ -228,10 +239,10 @@ class ExtractCssChunks {
228
239
filenameTemplate : this . options . chunkFilename ,
229
240
pathOptions : {
230
241
chunk,
231
- contentHashType : NS ,
242
+ contentHashType : MODULE_TYPE ,
232
243
} ,
233
244
identifier : `${ pluginName } .${ chunk . id } ` ,
234
- hash : chunk . contentHash [ NS ] ,
245
+ hash : chunk . contentHash [ MODULE_TYPE ] ,
235
246
} ) ;
236
247
}
237
248
} ,
@@ -245,7 +256,7 @@ class ExtractCssChunks {
245
256
}
246
257
if ( REGEXP_CONTENTHASH . test ( chunkFilename ) ) {
247
258
hash . update (
248
- JSON . stringify ( chunk . getChunkMaps ( true ) . contentHash [ NS ] || { } ) ,
259
+ JSON . stringify ( chunk . getChunkMaps ( true ) . contentHash [ MODULE_TYPE ] || { } ) ,
249
260
) ;
250
261
}
251
262
if ( REGEXP_NAME . test ( chunkFilename ) ) {
@@ -258,12 +269,12 @@ class ExtractCssChunks {
258
269
const { hashFunction, hashDigest, hashDigestLength } = outputOptions ;
259
270
const hash = createHash ( hashFunction ) ;
260
271
for ( const m of chunk . modulesIterable ) {
261
- if ( m . type === NS ) {
272
+ if ( m . type === MODULE_TYPE ) {
262
273
m . updateHash ( hash ) ;
263
274
}
264
275
}
265
276
const { contentHash } = chunk ;
266
- contentHash [ NS ] = hash
277
+ contentHash [ MODULE_TYPE ] = hash
267
278
. digest ( hashDigest )
268
279
. substring ( 0 , hashDigestLength ) ;
269
280
} ) ;
@@ -276,9 +287,7 @@ class ExtractCssChunks {
276
287
'' ,
277
288
'// object to store loaded CSS chunks' ,
278
289
'var installedCssChunks = {' ,
279
- Template . indent (
280
- chunk . ids . map ( id => `${ JSON . stringify ( id ) } : 0` ) . join ( ',\n' ) ,
281
- ) ,
290
+ Template . indent ( chunk . ids . map ( id => `${ JSON . stringify ( id ) } : 0` ) . join ( ',\n' ) ) ,
282
291
'}' ,
283
292
] ) ;
284
293
}
@@ -313,14 +322,14 @@ class ExtractCssChunks {
313
322
) } [chunkId] + "`;
314
323
} ,
315
324
contentHash : {
316
- [ NS ] : `" + ${ JSON . stringify (
317
- chunkMaps . contentHash [ NS ] ,
325
+ [ MODULE_TYPE ] : `" + ${ JSON . stringify (
326
+ chunkMaps . contentHash [ MODULE_TYPE ] ,
318
327
) } [chunkId] + "`,
319
328
} ,
320
329
contentHashWithLength : {
321
- [ NS ] : ( length ) => {
330
+ [ MODULE_TYPE ] : ( length ) => {
322
331
const shortContentHashMap = { } ;
323
- const contentHash = chunkMaps . contentHash [ NS ] ;
332
+ const contentHash = chunkMaps . contentHash [ MODULE_TYPE ] ;
324
333
for ( const chunkId of Object . keys ( contentHash ) ) {
325
334
if ( typeof contentHash [ chunkId ] === 'string' ) {
326
335
shortContentHashMap [ chunkId ] = contentHash [
@@ -337,7 +346,7 @@ class ExtractCssChunks {
337
346
chunkMaps . name ,
338
347
) } [chunkId]||chunkId) + "`,
339
348
} ,
340
- contentHashType : NS ,
349
+ contentHashType : MODULE_TYPE ,
341
350
} ,
342
351
) ;
343
352
return Template . asString ( [
@@ -397,24 +406,34 @@ class ExtractCssChunks {
397
406
} ) ;
398
407
}
399
408
400
- updateWebpackConfig ( rulez ) {
401
- let isExtract = null ;
402
- return rulez . reduce ( ( rules , rule ) => {
403
- if ( rule . oneOf ) {
404
- rule . oneOf = this . updateWebpackConfig ( rule . oneOf ) ;
405
- }
409
+ traverseDepthFirst ( root , visit ) {
410
+ let nodesToVisit = [ root ] ;
406
411
407
- if ( rule . use && Array . isArray ( rule . use ) ) {
408
- isExtract = rule . use . some ( ( l ) => {
409
- const needle = l . loader || l ;
410
- return needle . includes ( pluginName ) ;
411
- } ) ;
412
+ while ( nodesToVisit . length > 0 ) {
413
+ const currentNode = nodesToVisit . shift ( ) ;
412
414
413
- if ( isExtract ) {
414
- rule . use . unshift ( hotLoader ) ;
415
- }
415
+ if ( currentNode !== null && typeof currentNode === 'object' ) {
416
+ const children = Object . values ( currentNode ) ;
417
+ nodesToVisit = [ ... children , ... nodesToVisit ] ;
416
418
}
417
419
420
+ visit ( currentNode ) ;
421
+ }
422
+ }
423
+
424
+ updateWebpackConfig ( rulez ) {
425
+ return rulez . reduce ( ( rules , rule ) => {
426
+ this . traverseDepthFirst ( rule , ( node ) => {
427
+ if ( node && node . use && Array . isArray ( node . use ) ) {
428
+ const isMiniCss = node . use . some ( ( l ) => {
429
+ const needle = l . loader || l ;
430
+ return needle . includes ( pluginName ) ;
431
+ } ) ;
432
+ if ( isMiniCss ) {
433
+ node . use . unshift ( this . hotLoaderObject ) ;
434
+ }
435
+ }
436
+ } ) ;
418
437
rules . push ( rule ) ;
419
438
420
439
return rules ;
@@ -425,7 +444,7 @@ class ExtractCssChunks {
425
444
const obj = { } ;
426
445
for ( const chunk of mainChunk . getAllAsyncChunks ( ) ) {
427
446
for ( const module of chunk . modulesIterable ) {
428
- if ( module . type === NS ) {
447
+ if ( module . type === MODULE_TYPE ) {
429
448
obj [ chunk . id ] = 1 ;
430
449
break ;
431
450
}
@@ -476,17 +495,14 @@ class ExtractCssChunks {
476
495
// get first module where dependencies are fulfilled
477
496
for ( const list of modulesByChunkGroup ) {
478
497
// skip and remove already added modules
479
- while ( list . length > 0 && usedModules . has ( list [ list . length - 1 ] ) ) {
480
- list . pop ( ) ;
481
- }
498
+ while ( list . length > 0 && usedModules . has ( list [ list . length - 1 ] ) ) { list . pop ( ) ; }
482
499
483
500
// skip empty lists
484
501
if ( list . length !== 0 ) {
485
502
const module = list [ list . length - 1 ] ;
486
503
const deps = moduleDependencies . get ( module ) ;
487
504
// determine dependencies that are not yet included
488
- const failedDeps = Array . from ( deps )
489
- . filter ( unusedModulesFilter ) ;
505
+ const failedDeps = Array . from ( deps ) . filter ( unusedModulesFilter ) ;
490
506
491
507
// store best match for fallback behavior
492
508
if ( ! bestMatchDeps || bestMatchDeps . length > failedDeps . length ) {
0 commit comments