How to configure webpack 4 to prevent chunks from list of entry points appearing in any other bundle?

Joe Lencioni picture Joe Lencioni · Mar 8, 2018 · Viewed 10.2k times · Source

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.

Answer

Joe Lencioni picture Joe Lencioni · Mar 14, 2018

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