Skip to content

Commit 2962f42

Browse files
committed
fix: prune empty js bundles when code splitting
Fixes webpack-contrib#147
1 parent 7d1e0ca commit 2962f42

File tree

1 file changed

+76
-0
lines changed

1 file changed

+76
-0
lines changed

src/index.js

+76
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import path from 'path';
2+
13
import webpack from 'webpack';
24
import sources from 'webpack-sources';
35

@@ -110,6 +112,7 @@ class CssModuleFactory {
110112

111113
class MiniCssExtractPlugin {
112114
constructor(options) {
115+
this.disconnectedGroups = {};
113116
this.options = Object.assign(
114117
{
115118
filename: '[name].css',
@@ -188,8 +191,81 @@ class MiniCssExtractPlugin {
188191
hash: chunk.contentHash[MODULE_TYPE],
189192
});
190193
}
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+
});
191248
}
192249
);
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+
});
193269
compilation.chunkTemplate.hooks.renderManifest.tap(
194270
pluginName,
195271
(result, { chunk }) => {

0 commit comments

Comments
 (0)