SplitChunksPlugin
通常,在webpack
的内部图谱里面,chunks
是以父子
关系关联在一起的。CommonsChunkPlugin
曾被用来避免他们之间的重复依赖,但是在未来它将起不到优化作用了。
从webpack4
开始,CommonsChunkPlugin
已经被移除了,取而代之的是optimization.splitChunks
默认场景
对于大部分用户来说,SplitChunkPlugin
是开箱即用的,而且会用的很好。
默认情况下,它只会影响到那些按需加载
的chunks
,因为修改initial chunks
会影响到项目的HTML
文件中的脚本标签。
webpack
在以下场景下会去自动分割chunks
:
- 新的
chunk
可以被多个chunk
分享,或者它来自于node_modules
文件夹 - 新的
chunk
体积大于30kb
(在进行min+gz
之前的体积) - 在按需加载
chunk
时,其最大并发请求的数量小于等于5 - 在加载初始化页面过程中,其最大并发请求的数量小于等于3
在尽量满足后两个场景的情况下,更大体积的chunk
将更受到SplitChunkPlugin
的关注(去优先判断体积)
配置
SplitChunksPlugin
默认情况下有一些初始配置,如果你不了解这些默认的初始配置,那么你可能会有一定的困扰。所以,在使用webpack
的各种插件之前,先了解其默认配置是一个不错的主意。
optimization.splitChunks
下面这个配置对象就可以解释出SplitChunksPlugin
的一些默认行为:
webpack.config.js
module.exports = {
//...
optimization: {
splitChunks: {
chunks: 'async',
minSize: 30000,
maxSize: 0,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: '~',
automaticNameMaxLength: 30,
name: true,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
}
};
splitChunks.automaticNameDelimiter
string
默认情况下,webpack
会使用chunk
的源和名称来为其生成其相应的文件名(例如:vendors~main.js
)。这个选项可以让你定制一个连字符,用来生成chunk
的文件名。
splitChunks.automaticNameMaxLength
number: 109
这个选项是用来设置SplitChunksPlugin
在生成chunk
的文件名时可以生成的文件名称的最大字符个数。
splitChunks.chunks
function (chunk) | string
该配置项决定了哪些chunk
会被选取以进行优化。当提供了一个字符串,只有all
、async
、initial
是合法的关键词。
all
具有更强大的力量,因为它可以选取那些共享的chunk
,即使它们是在同步和非同步chunk
之间共享的。
webpack.config.js
module.exports = {
//...
optimization: {
splitChunks: {
// include all types of chunks
chunks: 'all'
}
}
};
或者,你也可以提供一个函数去做更多的控制。这个函数的返回值将决定是否包含每一个chunk
module.exports = {
//...
optimization: {
splitChunks: {
chunks (chunk) {
// exclude `my-excluded-chunk`
return chunk.name !== 'my-excluded-chunk';
}
}
}
};
splitChunks.maxAsyncRequests
number
当按需加载时可并发请求的最大请求数量
splitChunks.maxInitialRequests
number
在入口文件中可并发请求的最大请求数量
splitChunks.minChunks
number
在分割前,可被多少个chunk
分享的最小值
splitChunks.minSize
number
单位byte
,生成chunk
的最小体积约束。
splitChunks.maxSize
number
我们通过设置maxSize
来告诉webpack
去把那些体积大于maxSize
的chunk
分割变成更小的部分(无论是全局的optimization.splitChunks.maxSize
还是每一个cache group
的optimization.splitChunks.cacheGroups[x].maxSize
,甚至那些fallback
的optimization.splitChunks.fallbackCacheGroup.maxSize
都遵循这个规则)。这些更小的部分在体积上最少要大于minSize
(且接近于maxSize
)。这个算法是不可逆转的,且它只会对模块造成局部影响。因此,这在使用长期缓存和不需要记录的场景下非常有用。
maxSize
只是一个提示,当所有模块都大于maxSize
的时候它是可以被违背的,或者分割的时候可能会违背minSize
在做分割时,如果chunk
已经有了名称,那么这个chunk
的每个部分都会基于那个名称重新生成新的名称。具体的生成策略依赖于optimization.splitChunks.hidePathInfo
配置的值,它将会基于第一个模块的名称或hash
给新名称加上key
。
maxSize
选项的本意是和HTTP/2
和长效缓存一起使用。这样它将减少请求数量已达到更换的缓存效果。它也可以被用来减小文件的体积以求更快的构建速度。
maxSize
相比于maxInitialRequest/maxAsyncRequests
具有更高的权重。实际上,它们的权重排序是这样的:maxInitialRequest < maxAsyncRequests < maxSize < minSize
splitChunks.name
boolean = true function (module, chunks, cacheGroupKey) => string
string
改规则同样适用于每一个cacheGroup
的splitChunks.cacheGroup.{cacheGroup}.name
。
如果设置为true
,它将基于chunk
和cache group key
自动生成一个名字。
如果设置为一个字符串或者一个函数的话,它将允许你生成一个自定义的名字。尤其是如果你设置的是同一个字符串或者一个函数总是返回相同的字符串,它将自动把所有的通用模块和vendor
都合并到一个chunk
里面去。这将导致一个比较大的初始化下载,并且加载页面数据会比较慢。
如果你设置了一个函数,那么你将从入参中得到chunk.name
和chunk.hash
属性,这对你去自定义生成chunk
的名称尤其有用。
如果splitChunks.name
匹配到了一个entry point
名称,那么这个entry point
将会被移除掉。
在线上环境,
splitChunks.name
推荐被设置为false
,因为它在没必要的情况下不会变更名称。
main.js
import _ from 'lodash'
console.log(_.join(['Hello', 'webpack'], ''))
webpack.config.js
module.exports = {
//...
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'
}
}
}
}
};
运行以上配置,它将产出一个common group
的chunk
,名称为commons-main-lodash.js.e7519d2bb8777058fa27.js
当给不同的
split chunk
设置一个相同的名称,那么所有的vendor
模块都会被打包替换到单一的共享chunk
中去。尽管它是不被推荐的。
splitChunks.cacheGroups
cacheGroup
可以继承或者重写任何splitChunks.*
中设置的任何配置项,但是test
、priority
和reuseExistingChunk
只能在cacheGroup
下配置。如果想去禁用cacheGroup
的所有默认行为,将它们设置为false
即可。
webpack.config.js
module.exports = {
//...
optimization: {
splitChunks: {
cacheGroups: {
default: false
}
}
}
};
splitChunks.cacheGroups.{cacheGroup}.priority
number
在进项分割时,一个模块可能同时属于不同的cache group
,优化器将会选择那个拥有更高priority
的cache group
。默认的分组拥有一个priority
为负数的权重,以利于自定义分组可以设置一个更高的权重(自定义分组的默认值是0
)。
splitChunks.cacheGroups.{cacheGroup}.reuseExistingChunk
boolean
如果当前chunk
中包含的模块已经从main bundle
中分割出来了,那么它将会直接使用那个模块而不是重新生成一个。这个行为可能会影响当前chunk
的名称。
webpack.config.js
module.exports = {
//...
optimization: {
splitChunks: {
cacheGroups: {
vendors: {
reuseExistingChunk: true
}
}
}
}
};
splitCHunks.cacheGroups.{cacheGroup}.test
function (module, chunk) => boolean
RegExp
string
该选项控制着哪些模块将被当前cache group
选中。如果忽略这个选项,那么它将默认选择所有模块。它可以匹配模块资源的绝对路径也可以是chunk
的名称。当一个chunk
的名称匹配上了,那么这个chunk
里的所有模块也都被选中了。
webpack.config.js
module.exports = {
//...
optimization: {
splitChunks: {
cacheGroups: {
vendors: {
test(module, chunks) {
//...
return module.type === 'javascript/auto';
}
}
}
}
}
};
splitChunks.cacheGroups.{cacheGroup}.filename
string
当且仅当当前这个chunk
是一个initial chunk
的时候,该选项会允许你去重写其文件名称。所有的占位符都可以在output.filename
中找到。
该选项虽说也可以在全局中设置
splitChunks.filename
,但是它是不推荐的,因为在splitChunks.chunks
没有被设置为initial
的情况下它会导致一些错误。避免在全局中设置它。
webpack.config.js
module.exports = {
//...
optimization: {
splitChunks: {
cacheGroups: {
vendors: {
filename: '[name].bundle.js'
}
}
}
}
};
splitChunks.cacheGroups.{cacheGroup}.enforce
boolean = false
该选项在设置为true
的情况下将导致webpack
会忽略splitChunks.minSize
,splitChunks.minChunks
,splitChunks.maxAsyncRequests
和splitChunks.maxInitialRequests
选项的值,并且总是为当前这个cache group
创建一个chunk
。
webpack.config.js
module.exports = {
//...
optimization: {
splitChunks: {
cacheGroups: {
vendors: {
enforce: true
}
}
}
}
};
示例
默认值:示例1
// index.js
import('./a'); // dynamic import
// a.js
import 'react';
//...
结果: 一个分割的chunk
将会被创建,且它内部包含着react
模块。在import call
中,该chunk
将会和那个包含着./a
的原始chunk
一起并行加载。
为什么?
- 场景1: 当前这个
chunk
包含着来自于node_modules
的模块; - 场景2:
react
的体积大于30kb
- 场景3:
import call
的并行请求的数量是2 - 场景4: 不影响初始化页面也在的请求。
那么它背后的原因是什么呢? react
通常不会随着你的应用代码经常变动。通过把它分割到一个独立的chunk
中去可以让它脱离你的应用代码独立缓存(假设你正在使用chunkhash
, records
, Cache-Control
或者长效缓存)。
默认值:示例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
//...
结果:一个独立的chunk
将会被创建,它里面包含着./helpers
以及所有它的依赖模块。在import call
的时候,该chunk
会和原始的chunk
一起并行加载。
为什么呢?
- 场景1: 该
chunk
被两个import call
所共享 - 场景2:
helpers
的体积大于30kb
- 场景3:
import call
的并行请求个数等于2 - 场景4: 不影响初始化页面的加载请求
如果把helpers
的内容放在每一个chunk
中将会导致它的代码被下载两次。但是我们通过一个独立的chunk
可以让它只下载一次。我们虽然付出了增加一次请求的代价,但是这是权衡利弊后的结果。 这也是为什么要设置最小尺寸是30kb
的原因。
分割Chunk:示例1
创建一个common chunk
,它将包含在entry point
中所共享的所有代码
webpack.config.js
module.exports = {
//...
optimization: {
splitChunks: {
cacheGroups: {
commons: {
name: 'commons',
chunks: 'initial',
minChunks: 2
}
}
}
}
};
此配置会增大你的初始化
bundles
,我们推荐使用动态引入的方式去加载模块,当它不是被立即需要的时候。
分割Chunks:示例2
创建一个vendors chunk
,它将包含整个应用中所有来自于node_modules
中的所有代码。
webpack.config.js
module.exports = {
//...
optimization: {
splitChunks: {
cacheGroups: {
commons: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
}
};
这将导致一个很大的
chunk
,它包含了所有的扩展包。我们推荐只把你的框架和工具模块分割在一起,而其他的依赖模块通过动态引入。
分割Chunks:示例3
创建一个custom chunk
,它将包含所以匹配上了正则表达式的来自于node_modules
的模块。
webpack.config.js
module.exports = {
//...
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
name: 'vendor',
chunks: 'all',
}
}
}
}
};
这将导致
react
和react-dom
模块都分割到同一个独立的chunk
中去。