From ff602ff4e450abfd76fe8a1e6ca82eafa8db5a70 Mon Sep 17 00:00:00 2001 From: Izaak Schroeder Date: Tue, 6 Feb 2018 20:11:59 -0800 Subject: [PATCH 01/10] Add `type` property to `ExtractedModule`. Newer versions of `webpack` use this to determine certain behaviours when embedding a module into the bundle. While there is no native support for CSS currently this won't break anything. --- ExtractedModule.js | 1 + 1 file changed, 1 insertion(+) diff --git a/ExtractedModule.js b/ExtractedModule.js index 38207a8d..1b44faf0 100644 --- a/ExtractedModule.js +++ b/ExtractedModule.js @@ -12,6 +12,7 @@ function ExtractedModule(identifier, originalModule, source, sourceMap, addtitio this._sourceMap = sourceMap; this._prevModules = prevModules; this.addtitionalInformation = addtitionalInformation; + this.type = "css"; this.chunks = []; } module.exports = ExtractedModule; From 7e227f497f9376ecdc22654f64ab728dabcfc47a Mon Sep 17 00:00:00 2001 From: Izaak Schroeder Date: Tue, 6 Feb 2018 20:13:21 -0800 Subject: [PATCH 02/10] Remove useless type checking functions. Most of these were never used, and the one that was (`isString`) isn't really worth the effort to maintain. This is especially true if one wishes to use `flow` later on to do type refinement. --- index.js | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/index.js b/index.js index 713a8ed6..b29f4e52 100644 --- a/index.js +++ b/index.js @@ -105,7 +105,7 @@ function ExtractTextPlugin(options) { " allChunks: boolean\n" + " disable: boolean\n"); } - if(isString(options)) { + if(typeof options === "string") { options = { filename: options }; } else { schemaTester(pluginSchema, options); @@ -120,7 +120,7 @@ function ExtractTextPlugin(options) { module.exports = ExtractTextPlugin; function getLoaderObject(loader) { - if (isString(loader)) { + if (typeof loader === "string") { return {loader: loader}; } return loader; @@ -134,18 +134,6 @@ function mergeOptions(a, b) { return a; } -function isString(a) { - return typeof a === "string"; -} - -function isFunction(a) { - return isType('Function', a); -} - -function isType(type, obj) { - return Object.prototype.toString.call(obj) === '[object ' + type + ']'; -} - ExtractTextPlugin.loader = function(options) { return { loader: require.resolve("./loader"), options: options }; }; @@ -184,17 +172,17 @@ ExtractTextPlugin.prototype.extract = function(options) { if(options.loader) { console.warn('loader option has been deprecated - replace with "use"'); } - if(Array.isArray(options) || isString(options) || typeof options.options === "object" || typeof options.query === 'object') { + if(Array.isArray(options) || (typeof options === "string") || typeof options.options === "object" || typeof options.query === 'object') { options = { loader: options }; } else { schemaTester(loaderSchema, options); } var loader = options.use ||  options.loader; var before = options.fallback || options.fallbackLoader || []; - if(isString(loader)) { + if(typeof loader === "string") { loader = loader.split("!"); } - if(isString(before)) { + if(typeof before === "string") { before = before.split("!"); } else if(!Array.isArray(before)) { before = [before]; From 2e2bcc3bf62e97681d2ae77194b6a56cdf8c2953 Mon Sep 17 00:00:00 2001 From: Izaak Schroeder Date: Tue, 6 Feb 2018 20:14:23 -0800 Subject: [PATCH 03/10] Set default filename correctly if `NODE_ENV` is not set. It happens. Support the plebs. --- index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index b29f4e52..eab0affa 100644 --- a/index.js +++ b/index.js @@ -110,7 +110,9 @@ function ExtractTextPlugin(options) { } else { schemaTester(pluginSchema, options); } - this.filename = options.filename || (process.env.NODE_ENV === 'development' ? '[name].css' : '[name].[contenthash].css'); + this.filename = options.filename || ( + !process.env.NODE_ENV || (process.env.NODE_ENV === 'development') ? '[name].css' : '[name].[contenthash].css' + ); this.id = options.id != null ? options.id : ++nextId; this.options = {}; mergeOptions(this.options, options); From 4b3b0a55e2cf70bd9a4f756eaa3e27f6e0021509 Mon Sep 17 00:00:00 2001 From: Izaak Schroeder Date: Tue, 6 Feb 2018 20:19:05 -0800 Subject: [PATCH 04/10] Support `webpack@4`. Several of webpack's internal functions have changed. This changed functionality has been incorporated into the plugin. --- index.js | 58 +++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/index.js b/index.js index eab0affa..1fd6d976 100644 --- a/index.js +++ b/index.js @@ -222,21 +222,14 @@ ExtractTextPlugin.prototype.apply = function(compiler) { var id = this.id; var extractedChunks, entryChunks, initialChunks; compilation.plugin("optimize-tree", function(chunks, modules, callback) { - extractedChunks = chunks.map(function() { - return new Chunk(); + extractedChunks = chunks.map(function(chunk) { + return new Chunk(chunk.name); }); chunks.forEach(function(chunk, i) { var extractedChunk = extractedChunks[i]; - extractedChunk.index = i; - extractedChunk.originalChunk = chunk; extractedChunk.name = chunk.name; - extractedChunk.entrypoints = chunk.entrypoints; - chunk.chunks.forEach(function(c) { - extractedChunk.addChunk(extractedChunks[chunks.indexOf(c)]); - }); - chunk.parents.forEach(function(c) { - extractedChunk.addParent(extractedChunks[chunks.indexOf(c)]); - }); + extractedChunk.originalChunk = chunk; + splitChunk(chunk, extractedChunk); }); async.forEach(chunks, function(chunk, callback) { var extractedChunk = extractedChunks[chunks.indexOf(chunk)]; @@ -355,32 +348,53 @@ function isChunk(chunk, error) { function forEachChunkModule(chunk, cb) { isChunk(chunk); + // webpack >= 4.x.x + if (chunk.modulesIterable) { + Array.from(chunk.modulesIterable, (x) => { + cb(x); + return x; + }) + } // webpack >= 3.x.x - if (typeof chunk.forEachModule === 'function') { + else if (typeof chunk.forEachModule === 'function') { chunk.forEachModule(cb); } + // webpack < 3.x.x else { - // webpack < 3.x.x chunk.modules.forEach(cb); } - - // Nothing better to return... - return chunk; } function getChunkModulesArray(chunk) { isChunk(chunk); - var arr = []; - + // webpack >= 4.x.x + if (chunk.modulesIterable) { + return Array.from(chunk.modulesIterable); + } // webpack >= 3.x.x - if ( typeof chunk.mapModules === 'function' ) { - arr = chunk.mapModules(); + else if ( typeof chunk.mapModules === 'function' ) { + return chunk.mapModules(); } else { // webpack < 3.x.x - arr = chunk.modules.slice(); + return chunk.modules.slice(); } +} - return arr; +function splitChunk(chunk, extractedChunk) { + // webpack >= 4.x.x + if (typeof chunk.split === 'function') { + chunk.split(extractedChunk); + } + // webpack < 4.x.x + else { + extractedChunk.entrypoints = chunk.entrypoints; + chunk.chunks.forEach(function(c) { + extractedChunk.addChunk(extractedChunks[chunks.indexOf(c)]); + }); + chunk.parents.forEach(function(c) { + extractedChunk.addParent(extractedChunks[chunks.indexOf(c)]); + }); + } } From 2322e97de9aa7a0daa049f709b8e174a5c847c65 Mon Sep 17 00:00:00 2001 From: Izaak Schroeder Date: Tue, 6 Feb 2018 20:22:30 -0800 Subject: [PATCH 05/10] Support native `__webpack_public_path__`. This means you don't need to set the `publicPath` option during setup and the loader will use webpack's native one. Resolves https://github.com/faceyspacey/extract-css-chunks-webpack-plugin/issues/51. --- loader.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/loader.js b/loader.js index 31a1af32..11d699e4 100644 --- a/loader.js +++ b/loader.js @@ -54,10 +54,10 @@ module.exports.pitch = function(request) { } var childFilename = "extract-text-webpack-plugin-output-filename"; // eslint-disable-line no-path-concat - var publicPath = typeof query.publicPath === "string" ? query.publicPath : this._compilation.outputOptions.publicPath; + var publicPath = typeof query.publicPath === "string" ? query.publicPath : null; var outputOptions = { filename: childFilename, - publicPath: publicPath + publicPath: publicPath || this._compilation.outputOptions.publicPath, }; var childCompiler = this._compilation.createChildCompiler("extract-text-webpack-plugin", outputOptions); childCompiler.apply(new NodeTemplatePlugin(outputOptions)); @@ -140,12 +140,14 @@ module.exports.pitch = function(request) { // All we need is a date that changes during dev, to trigger a reload since // hashes generated based on the file contents are what trigger HMR. if (process.env.NODE_ENV === 'development') { + const pathVar = publicPath ? + `"${publicPath}"` : "__wepback_public_path__"; resultSource += ` if (module.hot) { module.hot.accept(); if (module.hot.data) { var neverUsed = ${+new Date()} - require(${loaderUtils.stringifyRequest(this, path.join(__dirname, "hotModuleReplacement.js"))})("${publicPath}", "%%extracted-file%%"); + require(${loaderUtils.stringifyRequest(this, path.join(__dirname, "hotModuleReplacement.js"))})(${pathVar}, "%%extracted-file%%"); } }`; } From ada40f5a6dc674634aacb5bb42023d0dafe7fd6a Mon Sep 17 00:00:00 2001 From: Izaak Schroeder Date: Tue, 6 Feb 2018 20:23:29 -0800 Subject: [PATCH 06/10] Remove useless arguments to `NodeTemplatePlugin`. This `options` parameter was set in `webpack-1` and even then it was set to be deprecated. I don't think this is used for anything anymore. --- loader.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loader.js b/loader.js index 11d699e4..c1f9b0e3 100644 --- a/loader.js +++ b/loader.js @@ -60,7 +60,7 @@ module.exports.pitch = function(request) { publicPath: publicPath || this._compilation.outputOptions.publicPath, }; var childCompiler = this._compilation.createChildCompiler("extract-text-webpack-plugin", outputOptions); - childCompiler.apply(new NodeTemplatePlugin(outputOptions)); + childCompiler.apply(new NodeTemplatePlugin()); childCompiler.apply(new LibraryTemplatePlugin(null, "commonjs2")); childCompiler.apply(new NodeTargetPlugin()); childCompiler.apply(new SingleEntryPlugin(this.context, "!!" + request)); From 91ad1e422e70abe4116c541d3aab666af9ff48da Mon Sep 17 00:00:00 2001 From: Izaak Schroeder Date: Tue, 6 Feb 2018 20:28:52 -0800 Subject: [PATCH 07/10] Support `hot` as first class option. Still falls back to `process.env.NODE_ENV` check if no `hot` option is given. --- index.js | 8 ++++++-- loader.js | 6 ++++-- schema/loader-schema.js | 1 + schema/plugin-schema.json | 4 ++++ 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index 1fd6d976..c6aca072 100644 --- a/index.js +++ b/index.js @@ -189,7 +189,11 @@ ExtractTextPlugin.prototype.extract = function(options) { } else if(!Array.isArray(before)) { before = [before]; } - options = mergeOptions({omit: before.length, remove: true}, options); + options = mergeOptions({ + omit: before.length, + remove: true, + hot: !process.env.NODE_ENV || (process.env.NODE_ENV === "development") + }, options); delete options.loader; delete options.use; delete options.fallback; @@ -278,7 +282,7 @@ ExtractTextPlugin.prototype.apply = function(compiler) { // HMR: inject file name into corresponding javascript modules in order to trigger // appropriate hot module reloading of CSS - if (process.env.NODE_ENV === 'development') { + if (options.hot) { compilation.plugin("before-chunk-assets", function() { extractedChunks.forEach(function(extractedChunk) { forEachChunkModule(extractedChunk, function(module) { diff --git a/loader.js b/loader.js index c1f9b0e3..4b44c80c 100644 --- a/loader.js +++ b/loader.js @@ -15,7 +15,8 @@ var LimitChunkCountPlugin = require("webpack/lib/optimize/LimitChunkCountPlugin" var NS = fs.realpathSync(__dirname); module.exports = function(source) { - if (process.env.NODE_ENV !== 'development') return source + var hot = "hot" in query ? query.hot : (!process.env.NODE_ENV || process.env.NODE_ENV === "development"); + if (!hot) return source // We need to always require hotModuleReplacement.js for HMR to work in a wierd scenario // where only one css file is imported. Otherwise HMR breaks when modules are disposed. @@ -59,6 +60,7 @@ module.exports.pitch = function(request) { filename: childFilename, publicPath: publicPath || this._compilation.outputOptions.publicPath, }; + var hot = "hot" in query ? query.hot : (!process.env.NODE_ENV || process.env.NODE_ENV === "development"); var childCompiler = this._compilation.createChildCompiler("extract-text-webpack-plugin", outputOptions); childCompiler.apply(new NodeTemplatePlugin()); childCompiler.apply(new LibraryTemplatePlugin(null, "commonjs2")); @@ -139,7 +141,7 @@ module.exports.pitch = function(request) { // // All we need is a date that changes during dev, to trigger a reload since // hashes generated based on the file contents are what trigger HMR. - if (process.env.NODE_ENV === 'development') { + if (hot) { const pathVar = publicPath ? `"${publicPath}"` : "__wepback_public_path__"; resultSource += ` diff --git a/schema/loader-schema.js b/schema/loader-schema.js index 80266761..2559df4d 100644 --- a/schema/loader-schema.js +++ b/schema/loader-schema.js @@ -11,6 +11,7 @@ module.exports = { "filename": { "type": "string" }, "use": { "type": ["string", "array", "object"] }, "publicPath": { "type": "string" }, + "hot": { "type": "boolean" }, // deprecated "fallbackLoader": { "type": ["string", "array", "object"] }, diff --git a/schema/plugin-schema.json b/schema/plugin-schema.json index 7b507d17..3b4ac2f9 100644 --- a/schema/plugin-schema.json +++ b/schema/plugin-schema.json @@ -41,6 +41,10 @@ "publicPath": { "description": "", "type": "string" + }, + "hot": { + "description": "Enable HMR", + "type": "boolean" } } } From 74025c7d1bbe9f2bb1918a3ab6d0b62598ac2c9e Mon Sep 17 00:00:00 2001 From: Izaak Schroeder Date: Tue, 6 Feb 2018 20:31:38 -0800 Subject: [PATCH 08/10] Loosen `isChunk` check. This check fails when doing `npm link` or when there's any `webpack` instance mismatch. This isn't necessarily the same as a version mismatch, just that the `Chunk` class came from two different source files. This obviously happens during `npm link` when both folders contain copies of the `webpack` module. This looser check should still be ok and catch most cases where something bad is going to happen. --- index.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index c6aca072..c0a1568c 100644 --- a/index.js +++ b/index.js @@ -342,7 +342,11 @@ function getPath(compilation, source, chunk) { } function isChunk(chunk, error) { - if (!(chunk instanceof Chunk)) { + if (!chunk || ( + !chunk.modulesIterable && + !chunk.forEachModule && + !chunk.modules + )) { throw new Error(typeof error === 'string' ? error : 'chunk is not an instance of Chunk'); } From 0586399f68adbb3bfa0b243344aaa6d64933f284 Mon Sep 17 00:00:00 2001 From: Izaak Schroeder Date: Tue, 6 Feb 2018 21:37:48 -0800 Subject: [PATCH 09/10] [wip] fix query missing. --- loader.js | 1 + 1 file changed, 1 insertion(+) diff --git a/loader.js b/loader.js index 4b44c80c..6bb89e9d 100644 --- a/loader.js +++ b/loader.js @@ -15,6 +15,7 @@ var LimitChunkCountPlugin = require("webpack/lib/optimize/LimitChunkCountPlugin" var NS = fs.realpathSync(__dirname); module.exports = function(source) { + var query = loaderUtils.getOptions(this) || {}; var hot = "hot" in query ? query.hot : (!process.env.NODE_ENV || process.env.NODE_ENV === "development"); if (!hot) return source From 0b4e184395b39a59c35e5cd01e3de3ec6a3f4961 Mon Sep 17 00:00:00 2001 From: Izaak Schroeder Date: Mon, 26 Feb 2018 22:59:28 -0800 Subject: [PATCH 10/10] Fix missing `extractedChunks` parameter. --- index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index c0a1568c..abece09d 100644 --- a/index.js +++ b/index.js @@ -233,7 +233,7 @@ ExtractTextPlugin.prototype.apply = function(compiler) { var extractedChunk = extractedChunks[i]; extractedChunk.name = chunk.name; extractedChunk.originalChunk = chunk; - splitChunk(chunk, extractedChunk); + splitChunk(chunk, extractedChunk, extractedChunks); }); async.forEach(chunks, function(chunk, callback) { var extractedChunk = extractedChunks[chunks.indexOf(chunk)]; @@ -390,7 +390,7 @@ function getChunkModulesArray(chunk) { } } -function splitChunk(chunk, extractedChunk) { +function splitChunk(chunk, extractedChunk, extractedChunks) { // webpack >= 4.x.x if (typeof chunk.split === 'function') { chunk.split(extractedChunk);