I am working on a large project and trying to land webpack 3 -> 4 update. This app has somewhere around 1,000 entry points, and about 10 of those are considered "global" or "core" and guaranteed to be on every page. These core bundles contain a mix of vendor and non-vendor code. I need to configure webpack to build all of these assets so that any chunks appearing in any of these bundles will not appear in any other bundle regardless of the size of the chunk, without creating new assets that need to be added to the page.
With webpack 3, we have been using CommonsChunkPlugin to accomplish this. Here's a simple example:
new webpack.optimize.CommonsChunkPlugin({
name: 'a-global-bundle',
minChunks: Infinity,
}),
Now with webpack 4 and the removal of CommonsChunkPlugin, it isn't clear to me how to accomplish this sort of optimization.
I'd like to be able to give webpack a list of entry points and any chunks that appear in any of those will not appear in any other bundle, but I'm not sure how to do this. I've read through some forthcoming documentation on splitChunks
but I haven't been able to piece together a solution.
I've set up a small repo as a starting point to tinker with: https://github.com/lencioni/webpack-splitchunks-playground
One interesting direction I'm trying out is configuring cacheGroups
with a group for each of these entry points and implementing the test
option with a function that does this check. However, the documentation is pretty sparse on this, so I'm not really sure what the right way to write this test function would be or even if this will work at all.
Ok, so I think I have figured out how to do this. But first, here's what the build looks like with the default splitChunks configuration (note FOO.bundle.js is an async bundle created by a dynamic import):
Asset Size Chunks Chunk Names
core.bundle.js 605 KiB 0 [emitted] [big] core
coreB.bundle.js 791 KiB 1 [emitted] [big] coreB
coreC.bundle.js 791 KiB 2 [emitted] [big] coreC
a.bundle.js 748 KiB 3 [emitted] [big] a
b.bundle.js 792 KiB 4 [emitted] [big] b
c.bundle.js 674 KiB 5 [emitted] [big] c
FOO.bundle.js 709 bytes 6 [emitted] FOO
runtime.bundle.js 7.49 KiB 7 [emitted] runtime
If the goal is to make it so any modules appearing in core, coreB, and coreC won't appear in any other bundle, this can be done with the following configuration:
function coreBundleCacheGroups(coreBundles) {
const cacheGroups = {};
const coreChunkNames = Object.keys(coreBundles);
const coreChunkNamesSet = new Set(coreChunkNames);
coreChunkNames.forEach((name) => {
cacheGroups[name] = {
name,
chunks: 'all',
minSize: 0,
minChunks: 1,
reuseExistingChunk: true,
priority: 10000,
enforce: true,
test(module, chunks) {
if (module.depth === 0) {
return false;
}
// Find first core chunk name that matches
const partOfGlobalChunks = chunks.filter(chunk => coreChunkNamesSet.has(chunk.name));
if (!partOfGlobalChunks.length) {
return false;
}
const partOfGlobalChunksSet = new Set(partOfGlobalChunks.map(chunk => chunk.name));
const firstCoreChunkName = coreChunkNames.find(name => partOfGlobalChunksSet.has(name));
return firstCoreChunkName === name;
},
};
});
return cacheGroups;
}
const coreBundles = {
core: './src/bundles/core.js',
coreB: './src/bundles/core-b.js',
coreC: './src/bundles/core-c.js',
};
module.exports = {
mode: 'none',
entry: {
...coreBundles,
a: './src/bundles/a.js',
b: './src/bundles/b.js',
c: './src/bundles/c.js',
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
},
optimization: {
runtimeChunk: 'single',
splitChunks: {
cacheGroups: {
...coreBundleCacheGroups(coreBundles),
},
},
},
};
which produces the following output:
Asset Size Chunks Chunk Names
core.bundle.js 605 KiB 0 [emitted] [big] core
coreB.bundle.js 188 KiB 1 [emitted] coreB
coreC.bundle.js 1.5 KiB 2 [emitted] coreC
a.bundle.js 76.4 KiB 3 [emitted] a
b.bundle.js 2.28 KiB 4 [emitted] b
c.bundle.js 1.91 KiB 5 [emitted] c
FOO.bundle.js 622 bytes 6 [emitted] FOO
runtime.bundle.js 7.49 KiB 7 [emitted] runtime