|
| 1 | +import path from 'path'; |
| 2 | + |
1 | 3 | import webpack from 'webpack';
|
2 | 4 | import sources from 'webpack-sources';
|
3 | 5 |
|
@@ -110,6 +112,7 @@ class CssModuleFactory {
|
110 | 112 |
|
111 | 113 | class MiniCssExtractPlugin {
|
112 | 114 | constructor(options) {
|
| 115 | + this.disconnectedGroups = {}; |
113 | 116 | this.options = Object.assign(
|
114 | 117 | {
|
115 | 118 | filename: '[name].css',
|
@@ -188,8 +191,81 @@ class MiniCssExtractPlugin {
|
188 | 191 | hash: chunk.contentHash[MODULE_TYPE],
|
189 | 192 | });
|
190 | 193 | }
|
| 194 | + |
| 195 | + const splitChunks = compilation.chunks.filter( |
| 196 | + (thisChunk) => |
| 197 | + thisChunk.chunkReason && |
| 198 | + thisChunk.chunkReason.includes('split chunk') |
| 199 | + ); |
| 200 | + |
| 201 | + splitChunks.forEach((splitChunk) => { |
| 202 | + const modulesWeCouldRemove = []; |
| 203 | + const nonCssModules = Array.from(splitChunk.modulesIterable).filter( |
| 204 | + (mod) => mod.type !== MODULE_TYPE |
| 205 | + ); |
| 206 | + |
| 207 | + nonCssModules.forEach((nonCssMod) => { |
| 208 | + if ( |
| 209 | + nonCssMod._source && // eslint-disable-line no-underscore-dangle |
| 210 | + nonCssMod._source._value === `// extracted by ${pluginName}` // eslint-disable-line no-underscore-dangle |
| 211 | + ) { |
| 212 | + modulesWeCouldRemove.push(nonCssMod); |
| 213 | + } |
| 214 | + }); |
| 215 | + |
| 216 | + // If there's nothing but CSS modules left in this split chunk, remove the whole thing |
| 217 | + // This will mean that the main template manifest (ie. webpack's boilerplate code) won't |
| 218 | + // contain any references to these empty modules |
| 219 | + if (modulesWeCouldRemove.length === nonCssModules.length) { |
| 220 | + modulesWeCouldRemove.forEach((nonCssMod) => { |
| 221 | + // Trace all the "reasons" for this module (ie. other modules that depend on it) |
| 222 | + // then add this module into their respective chunks. This effectively reverses |
| 223 | + // the work SplitChunksPlugin did to break out the logic into a separate chunk. |
| 224 | + // Without this step there will be script errors owing to missing dependencies |
| 225 | + // and adding them back to their origin chunks is harmless as they're empty. |
| 226 | + nonCssMod.reasons.forEach((reason) => { |
| 227 | + reason.module.chunksIterable.forEach( |
| 228 | + (previouslyConnectedChunk) => { |
| 229 | + splitChunk.moveModule( |
| 230 | + nonCssMod, |
| 231 | + previouslyConnectedChunk |
| 232 | + ); |
| 233 | + } |
| 234 | + ); |
| 235 | + }); |
| 236 | + }); |
| 237 | + |
| 238 | + splitChunk.groupsIterable.forEach((group) => { |
| 239 | + group.removeChunk(splitChunk); |
| 240 | + |
| 241 | + // Book-keeping |
| 242 | + this.disconnectedGroups[splitChunk.id] = |
| 243 | + this.disconnectedGroups[splitChunk.id] || []; |
| 244 | + this.disconnectedGroups[splitChunk.id].push(group); |
| 245 | + }); |
| 246 | + } |
| 247 | + }); |
191 | 248 | }
|
192 | 249 | );
|
| 250 | + compilation.hooks.chunkAsset.tap(pluginName, (chunk, file) => { |
| 251 | + // If this was a split chunk we disconnected previously |
| 252 | + if (this.disconnectedGroups[chunk.id]) { |
| 253 | + if (path.extname(file) === '.css') { |
| 254 | + this.disconnectedGroups[chunk.id].forEach((group) => { |
| 255 | + // Add the stylesheet as a file on every chunk that requires it |
| 256 | + // This ensures plugins like html-webpack-plugin will find and include |
| 257 | + // split CSS chunks |
| 258 | + group.chunks.forEach((parentChunk) => { |
| 259 | + parentChunk.files.push(file); |
| 260 | + }); |
| 261 | + }); |
| 262 | + } else if (path.extname(file) === '.js') { |
| 263 | + // Discard empty JS file |
| 264 | + chunk.files.pop(); |
| 265 | + delete compilation.assets[file]; // eslint-disable-line no-param-reassign |
| 266 | + } |
| 267 | + } |
| 268 | + }); |
193 | 269 | compilation.chunkTemplate.hooks.renderManifest.tap(
|
194 | 270 | pluginName,
|
195 | 271 | (result, { chunk }) => {
|
|
0 commit comments