1
1
/*
2
- MIT License http://www.opensource.org/licenses/mit-license.php
3
- Author Tobias Koppers @sokra
4
- */
2
+ MIT License http://www.opensource.org/licenses/mit-license.php
3
+ Author Tobias Koppers @sokra
4
+ */
5
5
var fs = require ( 'fs' ) ;
6
6
var ConcatSource = require ( "webpack-sources" ) . ConcatSource ;
7
- var CachedSource = require ( "webpack-sources" ) . CachedSource ;
8
7
var async = require ( "async" ) ;
9
8
var ExtractedModule = require ( "./ExtractedModule" ) ;
10
9
var Chunk = require ( "webpack/lib/Chunk" ) ;
10
+ var NormalModule = require ( "webpack/lib/NormalModule" ) ;
11
11
var OrderUndefinedError = require ( "./OrderUndefinedError" ) ;
12
12
var loaderUtils = require ( "loader-utils" ) ;
13
- var schemaTester = require ( './schema/validator' ) ;
14
- var loaderSchema = require ( './schema/loader-schema' ) ;
15
- var pluginSchema = require ( './schema/plugin-schema.json' ) ;
13
+ var validateOptions = require ( 'schema-utils' ) ;
14
+ var path = require ( 'path' ) ;
16
15
17
16
var NS = fs . realpathSync ( __dirname ) ;
18
- var DEV = process . env . NODE_ENV === 'development' ;
17
+
19
18
var nextId = 0 ;
20
19
21
20
function ExtractTextPluginCompilation ( ) {
22
21
this . modulesByIdentifier = { } ;
23
22
}
24
23
25
- // ExtractTextPlugin.prototype.mergeNonInitialChunks = function(chunk, intoChunk, checkedChunks) {
26
- // if (chunk.chunks) {
27
- // // Fix error when hot module replacement used with CommonsChunkPlugin
28
- // chunk.chunks = chunk.chunks.filter(function(c) {
29
- // return typeof c !== 'undefined';
30
- // })
31
- // }
24
+ function isInitialOrHasNoParents ( chunk ) {
25
+ return chunk . isInitial ( ) || chunk . parents . length === 0 ;
26
+ }
32
27
33
- // if(!intoChunk) {
34
- // checkedChunks = [];
35
- // chunk.chunks.forEach(function(c) {
36
- // if(c.isInitial()) return;
37
- // this.mergeNonInitialChunks(c, chunk, checkedChunks);
38
- // }, this);
39
- // } else if(checkedChunks.indexOf(chunk) < 0) {
40
- // checkedChunks.push(chunk);
41
- // chunk.modules.slice().forEach(function(module) {
42
- // intoChunk.addModule(module);
43
- // module.addChunk(intoChunk);
44
- // });
45
- // chunk.chunks.forEach(function(c) {
46
- // if(c.isInitial()) return;
47
- // this.mergeNonInitialChunks(c, intoChunk, checkedChunks);
48
- // }, this);
49
- // }
50
- // };
28
+ ExtractTextPlugin . prototype . mergeNonInitialChunks = function ( chunk , intoChunk , checkedChunks ) {
29
+ if ( ! intoChunk ) {
30
+ checkedChunks = [ ] ;
31
+ chunk . chunks . forEach ( function ( c ) {
32
+ if ( isInitialOrHasNoParents ( c ) ) return ;
33
+ this . mergeNonInitialChunks ( c , chunk , checkedChunks ) ;
34
+ } , this ) ;
35
+ } else if ( checkedChunks . indexOf ( chunk ) < 0 ) {
36
+ checkedChunks . push ( chunk ) ;
37
+ chunk . modules . slice ( ) . forEach ( function ( module ) {
38
+ intoChunk . addModule ( module ) ;
39
+ module . addChunk ( intoChunk ) ;
40
+ } ) ;
41
+ chunk . chunks . forEach ( function ( c ) {
42
+ if ( isInitialOrHasNoParents ( c ) ) return ;
43
+ this . mergeNonInitialChunks ( c , intoChunk , checkedChunks ) ;
44
+ } , this ) ;
45
+ }
46
+ } ;
51
47
52
48
ExtractTextPluginCompilation . prototype . addModule = function ( identifier , originalModule , source , additionalInformation , sourceMap , prevModules ) {
53
49
var m ;
@@ -115,26 +111,25 @@ function getOrder(a, b) {
115
111
}
116
112
117
113
function ExtractTextPlugin ( options ) {
118
- options = options || { }
119
-
120
114
if ( arguments . length > 1 ) {
121
115
throw new Error ( "Breaking change: ExtractTextPlugin now only takes a single argument. Either an options " +
122
- "object *or* the name of the result file.\n" +
123
- "Example: if your old code looked like this:\n" +
124
- " new ExtractTextPlugin('css/[name].css', { disable: false, allChunks: true })\n\n" +
125
- "You would change it to:\n" +
126
- " new ExtractTextPlugin({ filename: 'css/[name].css', disable: false, allChunks: true })\n\n" +
127
- "The available options are:\n" +
128
- " filename: string\n" +
129
- " allChunks: boolean\n" +
130
- " disable: boolean\n" ) ;
116
+ "object *or* the name of the result file.\n" +
117
+ "Example: if your old code looked like this:\n" +
118
+ " new ExtractTextPlugin('css/[name].css', { disable: false, allChunks: true })\n\n" +
119
+ "You would change it to:\n" +
120
+ " new ExtractTextPlugin({ filename: 'css/[name].css', disable: false, allChunks: true })\n\n" +
121
+ "The available options are:\n" +
122
+ " filename: string\n" +
123
+ " allChunks: boolean\n" +
124
+ " disable: boolean\n" +
125
+ " ignoreOrder: boolean\n" ) ;
131
126
}
132
127
if ( isString ( options ) ) {
133
128
options = { filename : options } ;
134
129
} else {
135
- schemaTester ( pluginSchema , options ) ;
130
+ validateOptions ( path . resolve ( __dirname , './schema/plugin.json' ) , options , 'Extract Text Plugin' ) ;
136
131
}
137
- this . filename = options . filename || ( DEV ? '[name].css' : '[name].[contenthash].css' ) ;
132
+ this . filename = options . filename ;
138
133
this . id = options . id != null ? options . id : ++ nextId ;
139
134
this . options = { } ;
140
135
mergeOptions ( this . options , options ) ;
@@ -192,15 +187,15 @@ ExtractTextPlugin.prototype.loader = function(options) {
192
187
ExtractTextPlugin . prototype . extract = function ( options ) {
193
188
if ( arguments . length > 1 ) {
194
189
throw new Error ( "Breaking change: extract now only takes a single argument. Either an options " +
195
- "object *or* the loader(s).\n" +
196
- "Example: if your old code looked like this:\n" +
197
- " ExtractTextPlugin.extract('style-loader', 'css-loader')\n\n" +
198
- "You would change it to:\n" +
199
- " ExtractTextPlugin.extract({ fallback: 'style-loader', use: 'css-loader' })\n\n" +
200
- "The available options are:\n" +
201
- " use: string | object | loader[]\n" +
202
- " fallback: string | object | loader[]\n" +
203
- " publicPath: string\n" ) ;
190
+ "object *or* the loader(s).\n" +
191
+ "Example: if your old code looked like this:\n" +
192
+ " ExtractTextPlugin.extract('style-loader', 'css-loader')\n\n" +
193
+ "You would change it to:\n" +
194
+ " ExtractTextPlugin.extract({ fallback: 'style-loader', use: 'css-loader' })\n\n" +
195
+ "The available options are:\n" +
196
+ " use: string | object | loader[]\n" +
197
+ " fallback: string | object | loader[]\n" +
198
+ " publicPath: string\n" ) ;
204
199
}
205
200
if ( options . fallbackLoader ) {
206
201
console . warn ( 'fallbackLoader option has been deprecated - replace with "fallback"' ) ;
@@ -211,7 +206,7 @@ ExtractTextPlugin.prototype.extract = function(options) {
211
206
if ( Array . isArray ( options ) || isString ( options ) || typeof options . options === "object" || typeof options . query === 'object' ) {
212
207
options = { loader : options } ;
213
208
} else {
214
- schemaTester ( loaderSchema , options ) ;
209
+ validateOptions ( path . resolve ( __dirname , './schema/loader.json' ) , options , 'Extract Text Plugin (Loader)' ) ;
215
210
}
216
211
var loader = options . use || options . loader ;
217
212
var before = options . fallback || options . fallbackLoader || [ ] ;
@@ -231,14 +226,15 @@ ExtractTextPlugin.prototype.extract = function(options) {
231
226
return [ this . loader ( options ) ]
232
227
. concat ( before , loader )
233
228
. map ( getLoaderObject ) ;
234
- }
229
+ } ;
235
230
236
231
ExtractTextPlugin . extract = ExtractTextPlugin . prototype . extract . bind ( ExtractTextPlugin ) ;
237
232
238
233
ExtractTextPlugin . prototype . apply = function ( compiler ) {
239
234
var options = this . options ;
240
235
compiler . plugin ( "this-compilation" , function ( compilation ) {
241
236
var extractCompilation = new ExtractTextPluginCompilation ( ) ;
237
+ var toRemoveModules = { } ;
242
238
compilation . plugin ( "normal-module-loader" , function ( loaderContext , module ) {
243
239
loaderContext [ NS ] = function ( content , opt ) {
244
240
if ( options . disable )
@@ -274,29 +270,49 @@ ExtractTextPlugin.prototype.apply = function(compiler) {
274
270
} ) ;
275
271
async . forEach ( chunks , function ( chunk , callback ) {
276
272
var extractedChunk = extractedChunks [ chunks . indexOf ( chunk ) ] ;
277
-
278
- // SETTING THIS TO TRUE INSURES ALL CHUNKS ARE HANDLED:
279
- var shouldExtract = true ; //!!(options.allChunks || chunk.isInitial());
280
-
273
+ var shouldExtract = ! ! ( options . allChunks || isInitialOrHasNoParents ( chunk ) ) ;
281
274
async . forEach ( chunk . modules . slice ( ) , function ( module , callback ) {
282
275
var meta = module [ NS ] ;
283
276
if ( meta && ( ! meta . options . id || meta . options . id === id ) ) {
284
277
var wasExtracted = Array . isArray ( meta . content ) ;
285
278
if ( shouldExtract !== wasExtracted ) {
286
- module [ NS + "/extract" ] = shouldExtract ; // eslint-disable-line no-path-concat
287
- compilation . rebuildModule ( module , function ( err ) {
279
+ var newModule = new NormalModule (
280
+ module . request ,
281
+ module . userRequest ,
282
+ module . rawRequest ,
283
+ module . loaders ,
284
+ module . resource ,
285
+ module . parser
286
+ ) ;
287
+ newModule [ NS + "/extract" ] = shouldExtract ; // eslint-disable-line no-path-concat
288
+ // build a new module and save result to extracted compilations
289
+ compilation . buildModule ( newModule , false , newModule , null , function ( err ) {
288
290
if ( err ) {
289
291
compilation . errors . push ( err ) ;
290
292
return callback ( ) ;
291
293
}
292
- meta = module [ NS ] ;
294
+ meta = newModule [ NS ] ;
295
+ // Error out if content is not an array and is not null
293
296
if ( ! Array . isArray ( meta . content ) && meta . content != null ) {
294
- err = new Error ( module . identifier ( ) + " doesn't export content" ) ;
297
+ err = new Error ( newModule . identifier ( ) + " doesn't export content" ) ;
295
298
compilation . errors . push ( err ) ;
296
299
return callback ( ) ;
297
300
}
298
- if ( meta . content )
299
- extractCompilation . addResultToChunk ( module . identifier ( ) , meta . content , module , extractedChunk ) ;
301
+ if ( meta . content ) {
302
+ var ident = module . identifier ( ) ;
303
+ extractCompilation . addResultToChunk ( ident , meta . content , newModule , extractedChunk ) ;
304
+ // remove generated result from chunk
305
+ if ( toRemoveModules [ ident ] ) {
306
+ toRemoveModules [ ident ] . chunks . push ( chunk )
307
+ } else {
308
+ toRemoveModules [ ident ] = {
309
+ module : newModule ,
310
+ moduleToRemove : module ,
311
+ chunks : [ chunk ]
312
+ } ;
313
+ }
314
+
315
+ }
300
316
callback ( ) ;
301
317
} ) ;
302
318
} else {
@@ -311,23 +327,47 @@ ExtractTextPlugin.prototype.apply = function(compiler) {
311
327
} ) ;
312
328
} , function ( err ) {
313
329
if ( err ) return callback ( err ) ;
314
- // REMOVING THIS CODE IS ALL THAT'S NEEDED TO CREATE CSS FILES PER CHUNK:
315
- // extractedChunks.forEach(function(extractedChunk) {
316
- // if(extractedChunk.isInitial())
317
- // this.mergeNonInitialChunks(extractedChunk);
318
- // }, this);
319
- // extractedChunks.forEach(function(extractedChunk) {
320
- // if(!extractedChunk.isInitial()) {
321
- // extractedChunk.modules.slice().forEach(function(module) {
322
- // extractedChunk.removeModule(module);
323
- // });
324
- // }
325
- // });
330
+ extractedChunks . forEach ( function ( extractedChunk ) {
331
+ if ( isInitialOrHasNoParents ( extractedChunk ) )
332
+ this . mergeNonInitialChunks ( extractedChunk ) ;
333
+ } , this ) ;
334
+ extractedChunks . forEach ( function ( extractedChunk ) {
335
+ if ( ! isInitialOrHasNoParents ( extractedChunk ) ) {
336
+ extractedChunk . modules . slice ( ) . forEach ( function ( module ) {
337
+ extractedChunk . removeModule ( module ) ;
338
+ } ) ;
339
+ }
340
+ } ) ;
326
341
compilation . applyPlugins ( "optimize-extracted-chunks" , extractedChunks ) ;
327
342
callback ( ) ;
328
343
} . bind ( this ) ) ;
329
344
} . bind ( this ) ) ;
330
-
345
+ compilation . plugin ( "optimize-module-ids" , function ( modules ) {
346
+ modules . forEach ( function ( module ) {
347
+ var data = toRemoveModules [ module . identifier ( ) ] ;
348
+ if ( data ) {
349
+ var id = module . id ;
350
+ var newModule = new NormalModule (
351
+ module . request ,
352
+ module . userRequest ,
353
+ module . rawRequest ,
354
+ module . loaders ,
355
+ module . resource ,
356
+ module . parser
357
+ ) ;
358
+ newModule . id = id ;
359
+ newModule . _source = data . module . _source ;
360
+ data . chunks . forEach ( function ( chunk ) {
361
+ chunk . removeModule ( data . moduleToRemove ) ;
362
+ var deps = data . moduleToRemove . dependencies . slice ( ) ;
363
+ deps . forEach ( d => {
364
+ chunk . removeModule ( d . module ) ;
365
+ } ) ;
366
+ chunk . addModule ( newModule ) ;
367
+ } ) ;
368
+ }
369
+ } ) ;
370
+ } ) ;
331
371
compilation . plugin ( "additional-assets" , function ( callback ) {
332
372
extractedChunks . forEach ( function ( extractedChunk ) {
333
373
if ( extractedChunk . modules . length ) {
@@ -361,38 +401,7 @@ ExtractTextPlugin.prototype.apply = function(compiler) {
361
401
} ) ;
362
402
}
363
403
} , this ) ;
364
-
365
- // duplicate js chunks into secondary files that don't have css injection,
366
- // giving the additional js files the extension: `.no_css.js`
367
- Object . keys ( compilation . assets ) . forEach ( function ( name ) {
368
- var asset = compilation . assets [ name ] ;
369
-
370
- if ( / \. j s $ / . test ( name ) && asset . _source ) {
371
- var regex = / \/ \* _ _ S T A R T _ C S S _ _ \* \/ [ \s \S ] * ?\/ \* _ _ E N D _ C S S _ _ \* \/ / g
372
- var source = asset . source ( ) ;
373
-
374
- if ( ! source . match ( regex ) ) {
375
- return ;
376
- }
377
- var newName = name . replace ( / \. j s / , '.no_css.js' ) ;
378
- var newAsset = new CachedSource ( asset . _source ) ;
379
- // remove js that adds css to DOM via style-loader, so that React Loadable
380
- // can serve smaller files (without css) in initial request.
381
- newAsset . _cachedSource = source . replace ( regex , '' ) ;
382
-
383
- compilation . assets [ newName ] = newAsset ;
384
-
385
- // add no_css file to files associated with chunk so that they are minified,
386
- // and receive source maps, and can be found by React Loadable
387
- extractedChunks . forEach ( function ( extractedChunk ) {
388
- var chunk = extractedChunk . originalChunk ;
389
- if ( chunk . files . indexOf ( name ) > - 1 ) {
390
- chunk . files . push ( newName ) ;
391
- }
392
- } )
393
- }
394
- } )
395
- callback ( )
404
+ callback ( ) ;
396
405
} . bind ( this ) ) ;
397
406
} . bind ( this ) ) ;
398
407
} ;
0 commit comments