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