Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 615
Joe Lencioni Avatar asked Mar 08 '18 00:03

Joe Lencioni


People also ask

Can you have multiple entry points in a single webpack configuration file?

webpack.config.jsWe can also pass an array of file paths to the entry property which creates what is known as a "multi-main entry". This is useful when you would like to inject multiple dependent files together and graph their dependencies into one "chunk".

How are webpack chunks loaded?

Webpack injects some code into main. js which takes care of lazy loading async chunks and stops from loading same chunks again and again. When a route changes, React router calls a Webpack function to load a chunk file and Webpack after done loading runs it, which chunk then would internally ask React to do something.

Can webpack split code into separate files?

Code splitting is one of the most compelling features of webpack. This feature allows you to split your code into various bundles which can then be loaded on demand or in parallel.

How does webpack code splitting work?

In React, code splitting involves applying the dynamic import() syntax to your components so that webpack automatically split the imported component as a separate bundle.


2 Answers

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 
like image 109
Joe Lencioni Avatar answered Oct 09 '22 13:10

Joe Lencioni


Your current config (using Webpack 3) uses CommonsChunkPlugin for an Explicit vendor chunk:

Split your code into vendor and application.

Checking the Webpack output for your repo I found that a.bundle.js contains the following code:

// `react`, `react-dom` plus console.log('core module');     // from core-module.js console.log('core module b');   // from core-module-b.js console.log('non-core module'); // from non-core-module.js 

Similar code is inside b.bundle.js (the difference in this script is the last console.log which is referenced from non-core-module-b.js: console.log('non-core module b');).

Updating the webpack.config.js optimization option to:

optimization: {     runtimeChunk: 'single',      splitChunks: {         chunks: 'all',          cacheGroups: {             default: {                 enforce: true,                 priority: 1             },             vendors: {                 test: /[\\/]node_modules[\\/]/,                 priority: 2,                 name: 'vendors',                 enforce: true,                 chunks: 'all'             }         }     } } 

Produce a non duplication code between the bundles.


You can check the working code here. I also created a pull request to your sample project.

More info about code splitting and the splitChunks optimization

like image 45
Carloluis Avatar answered Oct 09 '22 12:10

Carloluis