|
| 1 | +--- |
| 2 | +title: SplitChunksPlugin |
| 3 | +contributors: |
| 4 | + - sokra |
| 5 | + - jeremenichelli |
| 6 | +related: |
| 7 | + - title: "webpack 4: Code Splitting, chunk graph and the splitChunks optimization" |
| 8 | + url: https://medium.com/webpack/webpack-4-code-splitting-chunk-graph-and-the-splitchunks-optimization-be739a861366 |
| 9 | +--- |
| 10 | + |
| 11 | +Originally, chunks (and modules imported inside them) were connected by a parent-child relationship in the internal webpack graph. The `CommonsChunkPlugin` was used to avoid duplicated dependencies across them, but further optimizations where not possible |
| 12 | + |
| 13 | +Since version 4 the `CommonsChunkPlugin` was removed in favor of `optimization.splitChunks` and `optimize.runtimeChunk` options. Here is how the new flow works. |
| 14 | + |
| 15 | + |
| 16 | +## Defaults |
| 17 | + |
| 18 | +Out of the box `SplitChunksPlugin` should work great for most users. |
| 19 | + |
| 20 | +By default it only affects on-demand chunks because changing initial chunks would affect the script tags the HTML file should include to run the project. |
| 21 | + |
| 22 | +webpack will automatically split chunks based on these conditions: |
| 23 | + |
| 24 | +* New chunk can be shared OR modules are from the `node_modules` folder |
| 25 | +* New chunk would be bigger than 30kb (before min+gz) |
| 26 | +* Maximum number of parallel requests when loading chunks on demand would be lower or equal to 5 |
| 27 | +* Maximum number of parallel requests at initial page load would be lower or equal to 3 |
| 28 | + |
| 29 | +When trying to fulfill the last two conditions, bigger chunks are preferred. |
| 30 | + |
| 31 | +Let's take a look at some examples. |
| 32 | + |
| 33 | +### Defaults: Example 1 |
| 34 | + |
| 35 | +``` js |
| 36 | +// index.js |
| 37 | + |
| 38 | +// dynamically import a.js |
| 39 | +import("./a"); |
| 40 | +``` |
| 41 | + |
| 42 | +``` js |
| 43 | +// a.js |
| 44 | +import "react"; |
| 45 | + |
| 46 | +// ... |
| 47 | +``` |
| 48 | + |
| 49 | +**Result:** A separate chunk would be created containing `react`. At the import call this chunk is loaded in parallel to the original chunk containing `./a`. |
| 50 | + |
| 51 | +Why: |
| 52 | + |
| 53 | +* Condition 1: The chunk contains modules from `node_modules` |
| 54 | +* Condition 2: `react` is bigger than 30kb |
| 55 | +* Condition 3: Number of parallel requests at the import call is 2 |
| 56 | +* Condition 4: Doesn't affect request at initial page load |
| 57 | + |
| 58 | +What's the reasoning behind this? `react` probably won't change as often as your application code. By moving it into a separate chunk this chunk can be cached separately from your app code (assuming you are using chunkhash, records, Cache-Control or other long term cache approach). |
| 59 | + |
| 60 | +### Defaults: Example 2 |
| 61 | + |
| 62 | +``` js |
| 63 | +// entry.js |
| 64 | + |
| 65 | +// dynamically import a.js and b.js |
| 66 | +import("./a"); |
| 67 | +import("./b"); |
| 68 | +``` |
| 69 | + |
| 70 | +``` js |
| 71 | +// a.js |
| 72 | +import "./helpers"; // helpers is 40kb in size |
| 73 | + |
| 74 | +// ... |
| 75 | +``` |
| 76 | + |
| 77 | +``` js |
| 78 | +// b.js |
| 79 | +import "./helpers"; |
| 80 | +import "./more-helpers"; // more-helpers is also 40kb in size |
| 81 | + |
| 82 | +// ... |
| 83 | +``` |
| 84 | + |
| 85 | +**Result:** A separate chunk would be created containing `./helpers` and all dependencies of it. At the import calls this chunk is loaded in parallel to the original chunks. |
| 86 | + |
| 87 | +Why: |
| 88 | + |
| 89 | +* Condition 1: The chunk is shared between both import calls |
| 90 | +* Condition 2: `helpers` is bigger than 30kb |
| 91 | +* Condition 3: Number of parallel requests at the import calls is 2 |
| 92 | +* Condition 4: Doesn't affect request at initial page load |
| 93 | + |
| 94 | +Putting the content of `helpers` into each chunk will result into its code being downloaded twice. By using a separate chunk this will only happen once. We pay the cost of an additional request, which could be considered a tradeoff. That's why there is a minimum size of 30kb. |
| 95 | + |
| 96 | + |
| 97 | +## Configuration |
| 98 | + |
| 99 | +For developers that want to have more control over this functionality, webpack provides a set of options to better fit your needs. |
| 100 | + |
| 101 | +If you are manually changing the split configuration, measure the impact of the changes to see and make sure there's a real benefit. |
| 102 | + |
| 103 | +W> Default configuration was chosen to fit web performance best practices but the optimum strategy for your project might defer depending on the nature of it. |
| 104 | + |
| 105 | +### Configuring cache groups |
| 106 | + |
| 107 | +The defaults assign all modules from `node_modules` to a cache group called `vendors` and all modules duplicated in at least 2 chunks to a cache group `default`. |
| 108 | + |
| 109 | +A module can be assigned to multiple cache groups. The optimization then prefers the cache group with the higher `priority` (`priority` option) or that one that forms bigger chunks. |
| 110 | + |
| 111 | +### Conditions |
| 112 | + |
| 113 | +Modules from the same chunks and cache group will form a new chunk when all conditions are fulfilled. |
| 114 | + |
| 115 | +There are 4 options to configure the conditions: |
| 116 | + |
| 117 | +* `minSize` (default: 30000) Minimum size for a chunk. |
| 118 | +* `minChunks` (default: 1) Minimum number of chunks that share a module before splitting |
| 119 | +* `maxInitialRequests` (default 3) Maximum number of parallel requests at an entrypoint |
| 120 | +* `maxAsyncRequests` (default 5) Maximum number of parallel requests at on-demand loading |
| 121 | + |
| 122 | +### Naming |
| 123 | + |
| 124 | +To control the chunk name of the split chunk the `name` option can be used. |
| 125 | + |
| 126 | +W> When assigning equal names to different split chunks, all vendor modules are placed into a single shared chunk, though it's not recommend since it can result in more code downloaded. |
| 127 | + |
| 128 | +The magic value `true` automatically chooses a name based on chunks and cache group key, otherwise a string or function can be passed. |
| 129 | + |
| 130 | +When the name matches an entry point name, the entry point is removed. |
| 131 | + |
| 132 | +#### `optimization.splitChunks.automaticNameDelimiter` |
| 133 | + |
| 134 | +By default webpack will generate names using origin and name of the chunk, like `vendors~main.js`. |
| 135 | + |
| 136 | +If your project has a conflict with the `~` character, it can be changed by setting this option to any other value that works for your project: `automaticNameDelimiter: "-"`. |
| 137 | + |
| 138 | +Then the resulting names will look like `vendors-main.js`. |
| 139 | + |
| 140 | +### Select modules |
| 141 | + |
| 142 | +The `test` option controls which modules are selected by this cache group. Omitting it selects all modules. It can be a RegExp, string or function. |
| 143 | + |
| 144 | +It can match the absolute module resource path or chunk names. When a chunk name is matched, all modules in this chunk are selected. |
| 145 | + |
| 146 | +### Select chunks |
| 147 | + |
| 148 | +With the `chunks` option the selected chunks can be configured. |
| 149 | + |
| 150 | +There are 3 values possible `"initial"`, `"async"` and `"all"`. When configured the optimization only selects initial chunks, on-demand chunks or all chunks. |
| 151 | + |
| 152 | +The option `reuseExistingChunk` allows to reuse existing chunks instead of creating a new one when modules match exactly. |
| 153 | + |
| 154 | +This can be controlled per cache group. |
| 155 | + |
| 156 | + |
| 157 | +### `optimization.splitChunks.chunks: all` |
| 158 | + |
| 159 | +As it was mentioned before this plugin will affect dynamic imported modules. Setting the `optimization.splitChunks.chunks` option to `"all"` initial chunks will get affected by it (even the ones not imported dynamically). This way chunks can even be shared between entry points and on-demand loading. |
| 160 | + |
| 161 | +This is the recommended configuration. |
| 162 | + |
| 163 | +T> You can combine this configuration with the [HtmlWebpackPlugin](/plugins/html-webpack-plugin/), it will inject all the generated vendor chunks for you. |
| 164 | + |
| 165 | + |
| 166 | +## `optimization.splitChunks` |
| 167 | + |
| 168 | +This configuration object represents the default behavior of the `SplitChunksPlugin`. |
| 169 | + |
| 170 | +```js |
| 171 | +splitChunks: { |
| 172 | + chunks: "async", |
| 173 | + minSize: 30000, |
| 174 | + minChunks: 1, |
| 175 | + maxAsyncRequests: 5, |
| 176 | + maxInitialRequests: 3, |
| 177 | + automaticNameDelimiter: '~', |
| 178 | + name: true, |
| 179 | + cacheGroups: { |
| 180 | + default: { |
| 181 | + minChunks: 2, |
| 182 | + priority: -20, |
| 183 | + reuseExistingChunk: true |
| 184 | + }, |
| 185 | + vendors: { |
| 186 | + test: /[\\/]node_modules[\\/]/, |
| 187 | + priority: -10 |
| 188 | + } |
| 189 | + } |
| 190 | +} |
| 191 | +``` |
| 192 | + |
| 193 | +By default cache groups inherit options from `splitChunks.*`, but `test`, `priority` and `reuseExistingChunk` can only be configured on cache group level. |
| 194 | + |
| 195 | +`cacheGroups` is an object where keys are the cache group names. All options from the ones listed above are possible: `chunks`, `minSize`, `minChunks`, `maxAsyncRequests`, `maxInitialRequests`, `name`. |
| 196 | + |
| 197 | +You can set `optimization.splitChunks.cacheGroups.default` to `false` to disable the default cache group, same for `vendors` cache group. |
| 198 | + |
| 199 | +The priority of the default groups are negative to allow any custom cache group to take higher priority (the default value is `0`). |
| 200 | + |
| 201 | +Here are some examples and their effect: |
| 202 | + |
| 203 | +### Split Chunks: Example 1 |
| 204 | + |
| 205 | +Create a `commons` chunk, which includes all code shared between entry points. |
| 206 | + |
| 207 | +```js |
| 208 | +splitChunks: { |
| 209 | + cacheGroups: { |
| 210 | + commons: { |
| 211 | + name: "commons", |
| 212 | + chunks: "initial", |
| 213 | + minChunks: 2 |
| 214 | + } |
| 215 | + } |
| 216 | +} |
| 217 | +``` |
| 218 | + |
| 219 | +W> This configuration can enlarge your initial bundles, it is recommended to use dynamic imports when a module is not immediately needed. |
| 220 | + |
| 221 | +### Split Chunks: Example 2 |
| 222 | + |
| 223 | +Create a `vendors` chunk, which includes all code from `node_modules` in the whole application. |
| 224 | + |
| 225 | +``` js |
| 226 | +splitChunks: { |
| 227 | + cacheGroups: { |
| 228 | + commons: { |
| 229 | + test: /[\\/]node_modules[\\/]/, |
| 230 | + name: "vendors", |
| 231 | + chunks: "all" |
| 232 | + } |
| 233 | + } |
| 234 | +} |
| 235 | +``` |
| 236 | + |
| 237 | +W> This might result in a large chunk containing all external packages. It is recommended to only include your core frameworks and utilities and dynamically load the rest of the dependencies. |
| 238 | + |
| 239 | + |
| 240 | +## `optimize.runtimeChunk` |
| 241 | + |
| 242 | +Setting `optimize.runtimeChunk` to `true` adds an additonal chunk to each entrypoint containing only the runtime. |
| 243 | + |
| 244 | +The value `single` instead creates a runtime file to be shared for all generated chunks. |
0 commit comments