SplitChunksPlugin
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 were not possible.
Since webpack v4, the CommonsChunkPlugin was removed in favor of optimization.splitChunks.
Defaults
Out of the box SplitChunksPlugin should work well for most users.
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.
Webpack will automatically split chunks based on these conditions:
- New chunk can be shared OR modules are from the
node_modulesfolder - New chunk would be bigger than 20kb (before min+gz; 10kb outside
productionmode) - Maximum number of parallel requests when loading chunks on demand would be lower or equal to 30 (unlimited outside
productionmode) - Maximum number of parallel requests at initial page load would be lower or equal to 30 (unlimited outside
productionmode)
When trying to fulfill the last two conditions, bigger chunks are preferred.
Configuration
Webpack provides a set of options for developers that want more control over this functionality.
optimization.splitChunks
This configuration object represents the default behavior of the SplitChunksPlugin in production mode; the comments give the values used in the other modes.
webpack.config.js
export default {
// ...
optimization: {
splitChunks: {
chunks: "async",
minSize: 20000, // 10000 in other modes
minRemainingSize: 20000, // equals minSize; 0 in development mode
minChunks: 1,
maxAsyncRequests: 30, // Infinity in other modes
maxInitialRequests: 30, // Infinity in other modes
enforceSizeThreshold: 50000, // 30000 in other modes
cacheGroups: {
defaultVendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
reuseExistingChunk: true,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
},
},
},
};splitChunks.automaticNameDelimiter
string = '-'
By default webpack will generate names using origin and name of the chunk (e.g. vendors-main.js). This option lets you specify the delimiter to use for the generated names.
splitChunks.chunks
string = 'async' function (chunk) RegExp
This indicates which chunks will be selected for optimization. When a string is provided, valid values are all, async, and initial. Providing all can be particularly powerful, because it means that chunks can be shared even between async and non-async chunks.
Note that it is applied to the fallback cache group as well (splitChunks.fallbackCacheGroup.chunks).
webpack.config.js
export default {
// ...
optimization: {
splitChunks: {
// include all types of chunks
chunks: "all",
},
},
};Alternatively, you may provide a function for more control. The return value will indicate whether to include each chunk.
export default {
// ...
optimization: {
splitChunks: {
chunks(chunk) {
// exclude `my-excluded-chunk`
return chunk.name !== "my-excluded-chunk";
},
},
},
};If you are using webpack version 5.86.0 or later, you can also pass a regular expression:
export default {
// ...
optimization: {
splitChunks: {
chunks: /foo/,
},
},
};splitChunks.maxAsyncRequests
number
Maximum number of parallel requests when on-demand loading.
The default value of splitChunks.maxAsyncRequests depends on the mode:
| Mode | Default |
|---|---|
"production" | 30 |
"development" | Infinity |
"none" | Infinity |
splitChunks.maxInitialRequests
number
Maximum number of parallel requests at an entry point.
The default value of splitChunks.maxInitialRequests depends on the mode:
| Mode | Default |
|---|---|
"production" | 30 |
"development" | Infinity |
"none" | Infinity |
splitChunks.defaultSizeTypes
[string] = ['javascript', 'css', 'unknown']
Sets the size types which are used when a number is used for sizes. When the built-in CSS support (experiments.css) is disabled, the default is ['javascript', 'unknown'].
splitChunks.minChunks
number = 1
The minimum times must a module be shared among chunks before splitting.
splitChunks.hidePathInfo
boolean
Prevents exposing path info when creating names for parts splitted by maxSize.
splitChunks.minSize
number { [index: string]: number }
Minimum size, in bytes, for a chunk to be generated.
The default value of splitChunks.minSize depends on the mode:
| Mode | Default |
|---|---|
"production" | 20000 |
"development" | 10000 |
"none" | 10000 |
splitChunks.minSizeReduction
number { [index: string]: number }
Minimum size reduction to the main chunk (bundle), in bytes, needed for a chunk to be generated. Meaning if splitting into a chunk does not reduce the size of the main chunk (bundle) by the given amount of bytes, it won't be split, even if it meets the splitChunks.minSize value.
splitChunks.enforceSizeThreshold
splitChunks.cacheGroups.{cacheGroup}.enforceSizeThreshold
number
Size threshold at which splitting is enforced and other restrictions (minRemainingSize, maxAsyncRequests, maxInitialRequests) are ignored.
The default value of splitChunks.enforceSizeThreshold depends on the mode:
| Mode | Default |
|---|---|
"production" | 50000 |
"development" | 30000 |
"none" | 30000 |
splitChunks.minRemainingSize
splitChunks.cacheGroups.{cacheGroup}.minRemainingSize
number
splitChunks.minRemainingSize option was introduced in webpack 5 to avoid zero sized modules by ensuring that the minimum size of the chunk which remains after splitting is above a limit.
The default value of splitChunks.minRemainingSize depends on the mode:
| Mode | Default |
|---|---|
"production" | the value of splitChunks.minSize |
"development" | 0 |
"none" | the value of splitChunks.minSize |
It doesn't need to be specified manually except for the rare cases where deep control is required.
splitChunks.maxSize
number = 0
Using maxSize (either globally optimization.splitChunks.maxSize per cache group optimization.splitChunks.cacheGroups[x].maxSize or for the fallback cache group optimization.splitChunks.fallbackCacheGroup.maxSize) tells webpack to try to split chunks bigger than maxSize bytes into smaller parts. Parts will be at least minSize (next to maxSize) in size.
The algorithm is deterministic and changes to the modules will only have local effects. So that it is usable when using long term caching and doesn't require records. maxSize is only a hint and could be violated when modules are bigger than maxSize or splitting would violate minSize.
When the chunk has a name already, each part will get a new name derived from that name. Depending on the value of optimization.splitChunks.hidePathInfo it will add a key derived from the first module name or a hash of it.
maxSize option is intended to be used with HTTP/2 and long term caching. It increases the request count for better caching. It could also be used to decrease the file size for faster rebuilding.
splitChunks.maxAsyncSize
number
Like maxSize, maxAsyncSize can be applied globally (splitChunks.maxAsyncSize), to cacheGroups (splitChunks.cacheGroups.{cacheGroup}.maxAsyncSize), or to the fallback cache group (splitChunks.fallbackCacheGroup.maxAsyncSize).
The difference between maxAsyncSize and maxSize is that maxAsyncSize will only affect on-demand loading chunks.
splitChunks.maxInitialSize
number
Like maxSize, maxInitialSize can be applied globally (splitChunks.maxInitialSize), to cacheGroups (splitChunks.cacheGroups.{cacheGroup}.maxInitialSize), or to the fallback cache group (splitChunks.fallbackCacheGroup.maxInitialSize).
The difference between maxInitialSize and maxSize is that maxInitialSize will only affect initial load chunks.
splitChunks.name
boolean = false function (module, chunks, cacheGroupKey) => string string
Also available for each cacheGroup: splitChunks.cacheGroups.{cacheGroup}.name.
The name of the split chunk. Providing false will keep the same name of the chunks so it doesn't change names unnecessarily. It is the recommended value for production builds.
Providing a string or a function allows you to use a custom name. Specifying either a string or a function that always returns the same string will merge all common modules and vendors into a single chunk. This might lead to bigger initial downloads and slow down page loads.
If you choose to specify a function, you may find the chunk.name property (where chunk is an element of the chunks array) particularly useful in choosing a name for your chunk.
If splitChunks.name matches the name of an existing chunk (e.g. an entry point), the selected modules are moved into that chunk only when it is a parent of all selected chunks (e.g. via dependOn); otherwise webpack emits an error.
main.js
import _ from "lodash";
console.log(_.join(["Hello", "webpack"], " "));webpack.config.js
export default {
// ...
optimization: {
splitChunks: {
cacheGroups: {
commons: {
test: /[\\/]node_modules[\\/]/,
// cacheGroupKey here is `commons` as the key of the cacheGroup
name(module, chunks, cacheGroupKey) {
const moduleFileName = module
.identifier()
.split("/")
.reduceRight((item) => item);
const allChunksNames = chunks.map((item) => item.name).join("~");
return `${cacheGroupKey}-${allChunksNames}-${moduleFileName}`;
},
chunks: "all",
},
},
},
},
};Running webpack with following splitChunks configuration would also output a chunk of the group common with next name: commons-main-lodash.js.e7519d2bb8777058fa27.js (hash given as an example of real world output).
splitChunks.usedExports
splitChunks.cacheGroups{cacheGroup}.usedExports
boolean
Figure out which exports are used by modules to mangle export names, omit unused exports and generate more efficient code.
Defaults to true when optimization.usedExports is true (the case in production mode) and to false otherwise.
splitChunks.cacheGroups
Cache groups can inherit and/or override any options from splitChunks.*; but test, priority and reuseExistingChunk can only be configured on cache group level. To disable any of the default cache groups, set them to false.
webpack.config.js
export default {
// ...
optimization: {
splitChunks: {
cacheGroups: {
default: false,
},
},
},
};splitChunks.cacheGroups.{cacheGroup}.priority
number = -20
A module can belong to multiple cache groups. The optimization will prefer the cache group with a higher priority. The default groups have a negative priority to allow custom groups to take higher priority (default value is 0 for custom groups).
splitChunks.cacheGroups.{cacheGroup}.reuseExistingChunk
boolean = false
If the current chunk contains modules already split out from the main bundle, it will be reused instead of a new one being generated. This can affect the resulting file name of the chunk. The built-in default and defaultVendors cache groups set it to true.
webpack.config.js
export default {
// ...
optimization: {
splitChunks: {
cacheGroups: {
defaultVendors: {
reuseExistingChunk: true,
},
},
},
},
};splitChunks.cacheGroups.{cacheGroup}.type
function RegExp string
Allows to assign modules to a cache group by module type.
webpack.config.js
export default {
// ...
optimization: {
splitChunks: {
cacheGroups: {
json: {
type: "json",
},
},
},
},
};splitChunks.cacheGroups.{cacheGroup}.layer
RegExp string function
Assign modules to a cache group by module layer.
splitChunks.cacheGroups.test
splitChunks.cacheGroups.{cacheGroup}.test
function (module, { chunkGraph, moduleGraph }) => boolean RegExp string
Controls which modules are selected by this cache group. Omitting it selects all modules. It is matched against the module's resource path (module.nameForCondition()): a string matches by prefix, a RegExp by test.
Providing a function to{cacheGroup}.test:
webpack.config.js
import path from "node:path";
export default {
// ...
optimization: {
splitChunks: {
cacheGroups: {
svgGroup: {
test(module) {
// `module.resource` contains the absolute path of the file on disk.
// Note the usage of `path.sep` instead of / or \, for cross-platform compatibility.
return (
module.resource &&
module.resource.endsWith(".svg") &&
module.resource.includes(`${path.sep}cacheable_svgs${path.sep}`)
);
},
},
byModuleTypeGroup: {
test(module) {
return module.type === "javascript/auto";
},
},
},
},
},
};In order to see what information is available in module and chunks objects, you can put debugger; statement in the callback. Then run your webpack build in debug mode to inspect the parameters in Chromium DevTools.
Providing a RegExp to {cacheGroup}.test:
webpack.config.js
export default {
// ...
optimization: {
splitChunks: {
cacheGroups: {
defaultVendors: {
// Note the usage of `[\\/]` as a path separator for cross-platform compatibility.
test: /[\\/]node_modules[\\/]|vendor[\\/]analytics_provider|vendor[\\/]other_lib/,
},
},
},
},
};splitChunks.cacheGroups.{cacheGroup}.filename
string function (pathData, assetInfo) => string
Overrides the filename template for the chunks created by this cache group (initial and on-demand). It can also be set globally as splitChunks.filename.
All placeholders available in output.filename are also available here.
webpack.config.js
export default {
// ...
optimization: {
splitChunks: {
cacheGroups: {
defaultVendors: {
filename: "[name].bundle.js",
},
},
},
},
};And as a function:
webpack.config.js
export default {
// ...
optimization: {
splitChunks: {
cacheGroups: {
defaultVendors: {
filename: (pathData) =>
// Use pathData object for generating filename string based on your requirements
`${pathData.chunk.name}-bundle.js`,
},
},
},
},
};It is possible to create a folder structure by providing path prefixing the filename: 'js/vendor/bundle.js'.
webpack.config.js
export default {
// ...
optimization: {
splitChunks: {
cacheGroups: {
defaultVendors: {
filename: "js/[name]/bundle.js",
},
},
},
},
};splitChunks.cacheGroups.{cacheGroup}.enforce
boolean = false
Tells webpack to ignore splitChunks.minSize, splitChunks.minChunks, splitChunks.maxAsyncRequests and splitChunks.maxInitialRequests options and always create chunks for this cache group.
webpack.config.js
export default {
// ...
optimization: {
splitChunks: {
cacheGroups: {
defaultVendors: {
enforce: true,
},
},
},
},
};splitChunks.cacheGroups.{cacheGroup}.idHint
string
Sets the hint for chunk id. It will be added to chunk's filename.
webpack.config.js
export default {
// ...
optimization: {
splitChunks: {
cacheGroups: {
defaultVendors: {
idHint: "vendors",
},
},
},
},
};Examples
Defaults: Example 1
// index.js
import("./a"); // dynamic import// a.js
import "react";
// ...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.
Why:
- Condition 1: The chunk contains modules from
node_modules - Condition 2:
reactis bigger than 20kb - Condition 3: Number of parallel requests at the import call is 2
- Condition 4: Doesn't affect request at initial page load
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).
Defaults: Example 2
// entry.js
// dynamic imports
import("./a");
import("./b");// a.js
import "./helpers"; // helpers is 40kb in size
// ...// b.js
import "./helpers";
import "./more-helpers"; // more-helpers is also 40kb in size
// ...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.
Why:
- Condition 1: The chunk is shared between both import calls
- Condition 2:
helpersis bigger than 20kb - Condition 3: Number of parallel requests at the import calls is 2
- Condition 4: Doesn't affect request at initial page load
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 20kb.
Split Chunks: Example 1
Create a commons chunk, which includes all code shared between entry points.
webpack.config.js
export default {
// ...
optimization: {
splitChunks: {
cacheGroups: {
commons: {
name: "commons",
chunks: "initial",
minChunks: 2,
},
},
},
},
};Split Chunks: Example 2
Create a vendors chunk, which includes all code from node_modules in the whole application.
webpack.config.js
export default {
// ...
optimization: {
splitChunks: {
cacheGroups: {
commons: {
test: /[\\/]node_modules[\\/]/,
name: "vendors",
chunks: "all",
},
},
},
},
};Split Chunks: Example 3
Create a custom vendor chunk, which contains certain node_modules packages matched by RegExp.
webpack.config.js
export default {
// ...
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
name: "vendor",
chunks: "all",
},
},
},
},
};


