# SplitChunkPlugin
# 准备
webpack 解析文件时,会生成一张依赖图,里面有许多 chunk 互相依赖 ,webpack 可以将这些 chunk 分割、整合成一个或多个 bundle。这一过程我们可以通过 SplitChunkPlugin 进行调整。
chunk 有两种形式:
- initial: 是入口起点的 main chunk。此 chunk 包含为入口起点指定的所有模块及其依赖项。
- non-initial: 是可以延迟加载的块。可能会出现在使用 动态导入 或者 SplitChunksPlugin 时。
举例:
module.exports = {
entry: "./src/index.js",
};
2
3
// ./index.js
import _ from "lodash";
import("./util.js").then((u) => {
const blog = _.join(["www", "hxin", "link"], ".");
u.log(`博客地址:${blog}`);
});
// ./util.js
import $ from "jquery";
export function log(str) {
console.log($.trim(str));
}
2
3
4
5
6
7
8
9
10
11
12
13
14
- initial chunk:
./src/index.js
、lodash
- non-initial chunk:
./src/util.js
、jquery
(除 initial chunk 的所有依赖)
提示
以上内容,有助于你理解以下分割过程和配置信息。
# 默认配置
module.exports = {
// mode: "production",
//...
optimization: {
splitChunks: {
chunks: "async",
minSize: 30000,
maxSize: 0,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: "~",
name: true,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
},
},
},
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
以上为 webpack 的默认配置 (mode:production),可翻译如下:
- 定义 chunk 分割规则 vendors,需要同时满足以下 6 个条件即可分割:
- 该 chunk 属于 non-initial chunk
- 该 chunk 来自 node_modules
- 该 chunk 在打包之前的文件大于 30000 字节
- 该 chunk 至少被 1 个文件引入
- 该 chunk 分割之后,按需加载最大请求数量不能超过 5
- 该 chunk 分割之后,入口文件最大请求数量不能超过 3
- 定义 chunk 分割规则 default,需要同时满足以下 5 个条件即可分割:
- 该 chunk 属于 non-initial chunk
- 该 chunk 在打包之前的文件大于 30000 字节
- 该 chunk 至少被 2 个文件引入
- 该 chunk 分割之后,按需加载最大请求数量不能超过 5
- 该 chunk 分割之后,入口文件最大请求数量不能超过 3
- 注意:如果该 chunk 内部依赖的其他 chunk 被分割出来过且已存在,那么就复用,而不是重新分割出去或打包进来。
优先执行规则 vendors、后执行规则 default。因为规则 vendors 的优先级 (priority) 大于规则 default。打包出来的 bundle 名称自动根据规则名称和入口名称加上~
自动组合生成。
# 例子和解析
提示
index.js
、util.js
代码和以上准备小节示例一致。
// webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
module.exports = {
mode: "production",
entry: {
index: "./src/index.js",
},
output: {
filename: "[name].bundle.js",
path: path.resolve(__dirname, "dist"),
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: "Webpack example",
template: "./index.html",
}),
],
optimization: {
// 默认配置
splitChunks: {
chunks: "async",
minSize: 30000,
maxSize: 0,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: "~",
name: true,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
},
},
},
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
提示
jqurey 打包前大约是 310kb,lodash 打包前大约是 550kb,这可能并不准确。
我们可以预先分析打包后的结果,首先分析 initial chunk 和 non-initial chunk,(准备小节)即:
+ initial chunk: ./src/index.js、lodash
+ non-initial chunk: ./src/util.js、jquery
2
根据上方可以推断当前可以生成一个 initial bundle 和一个 non-initial bundle。
# 如果 SplitChunkPlugin 不起作用
- initial chunk: ./src/index.js、lodash
- non-initial chunk: ./src/util.js、jquery
+ initial bundle
+ non-initial bundle
2
3
4
5
6
再根据分割规则(默认配置小节)推断:
- initial bundle 忽略分割,因为里面的两 chunk 不符合分割规则 vendors、default 的第一条规则:该 chunk 属于 non-initial chunk,所以就直接将
./src/index.js
、lodash
整合成index.bundle.js
。 - non-initial bundle 中的
./src/util.js
不符合分割规则 vendors 的第二条规则:该 chunk 来自 node_modules。也不符合分割规则 default 的第三条规则:该 chunk 至少被 2 个文件引入。当前只被index.js
引入。分割规则 vendors、default 都不符合,所以无法分割。 - non-initial bundle 中的
jquery
符合分割规则 vendors,并且分割规则 vendors 优于 default,所以jquery
可以按照规则 vendors 分割出来。
类似下方:
# SplitChunkPlugin 起作用
- initial chunk: ./src/index.js、lodash
- non-initial chunk: ./src/util.js、jquery
- initial chunk: ./src/index.js、lodash
- non-initial chunk: ./src/util.js
- chunk: jquery
+ initial bundle
+ non-initial bundle
+ bundle
2
3
4
5
6
7
8
9
10
11
那么打包的结果就是三个 bundle,名字也可以进行分析:
- initial bundle 名字肯定在入口定义了
entry: { index: ... }
,名称为 index。 - bundle 分离出来的模块没有名字,因为我们没定义,所以默认是 number 类型 (递增)
- non-initial 异步加载模块我们也没有定义,所以也是是 number 类型 (递增)
...
- initial chunk: ./src/index.js、lodash
- non-initial chunk: ./src/util.js
- chunk: jquery
+ initial bundle -> index.bundle.js
+ bundle -> id.bundle.js
+ non-initial bundle -> id.bundle.js
2
3
4
5
6
7
8
···
Asset Size Chunks Chunk Names
1.bundle.js 87.9 KiB 1 [emitted]
2.bundle.js 195 bytes 2 [emitted]
index.bundle.js 73.4 KiB 0 [emitted] index
index.html 238 bytes [emitted]
···
2
3
4
5
6
7
举一反三: 如果想让lodash
分离出单独的一个 chunk,可以进行如下设置:
module.exports = {
// ···
optimization: {
splitChunks: {
chunks: "initial",
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
},
},
},
},
};
2
3
4
5
6
7
8
9
10
11
12
13
14
这么一来lodash
就符合规则 vendors 了,
...
- initial chunk: ./src/index.js、lodash
- non-initial chunk: ./src/util.js、jquery
- initial chunk: ./src/index.js
- chunk: lodash
- non-initial chunk: ./src/util.js jquery
+ initial bundle -> index.bundle.js
+ bundle -> vendors~index.bundle.js
+ non-initial bundle -> id.bundle.js
2
3
4
5
6
7
8
9
10
11
···
Asset Size Chunks Chunk Names
2.bundle.js 88.1 KiB 2 [emitted]
index.bundle.js 2.39 KiB 0 [emitted] index
index.html 285 bytes [emitted]
vendors~index.bundle.js 71.3 KiB 1 [emitted] vendors~index
···
2
3
4
5
6
7
# splitChunks.chunks
值:"async"
、"initial"
、"all"
、function(chunk)
,默认:"async"
决定选取哪些 chunk 进行优化。
"async"
:对按需加载的模块进行优化"initial"
:对入口模块进行优化"all"
:所有(按需加载、入口)模块进行优化,同步与非同步之前还可共享模块function(chunk)
:传入一个函数,自行控制模块的优化
# splitChunks.automaticNameDelimiter
值:string
,默认:~
默认情况下,webpack 将使用 chunk 的来源和名称生成名称(例如:vendors~main.js)。此选项允许您指定用于生成名称的分隔符。
# splitChunks.maxAsyncRequests
值:number
,默认:5 (mode:production)、Infinity (mode:development)
按需加载时并行请求的最大数量。
# splitChunks.maxInitialRequests
值:number
,默认:3 (mode:production)、Infinity (mode:development)
一个入口最大的并行请求数,入口点处并行请求的最大数量。
# splitChunks.minChunks
值:number
,默认:1
模块进行分割前必须共享的块的最小数量。
# splitChunks.minSize
值:number
,默认:30000 (mode:production)、10000 (mode:development)
生成一个新 chunk 最小的体积(以字节为单位)。
# splitChunks.maxSize
值:number
,默认:0
使用 maxSize
(全局:optimization.splitChunks.maxSize
、每个缓存组:optimization.splitChunks.cacheGroups[x].maxSize
、每个回退缓存组:optimization.splitChunks.fallbackCacheGroup.maxSize
)告诉 webpack 尝试将大于 maxSize
的块分割成更小的部分。分割出来的部分的尺寸至少为 minSize
(仅次于 maxSize
)。该算法是确定性的,对模块的更改只会产生局部影响。因此,当使用长期缓存时,它是可用的,并且不需要记录。maxSize
只是一个提示,当拆分后模块大于 maxSize
或拆分会违反 minSize
时,可以不遵循 maxSize
。当块已经有名称时,每个部分将从该名称派生出一个新名称。取决于 optimization.splitChunks.hidePathInfo
,它将添加从第一个模块名或它的散列派生的键。maxSize
选项的目是用于 HTTP/2 和长期缓存。它增加了请求数,以便更好地缓存。它还可以用来减小文件大小,以便更快地重新构建。
优先级问题
maxSize
相比于 maxInitialRequest/maxAsyncRequests
具有更高的权重。实际上,它们的权重排序是这样的:maxInitialRequest
< maxAsyncRequests
< maxSize
< minSize
。
# splitChunks.name
值:true
、string
、function (module, chunks, cacheGroupKey)
,默认:true
分割块的名称。提供 true 将根据 chunk 和 cacheGroup key 自动生成名称。提供字符串或函数将允许您使用自定义名称。如果名称与入口点名称匹配,则将删除入口点。
提示
在生产环境,splitChunks.name
推荐被设置为 false,因为它在没必要的情况下不会变更名称。
# splitChunks.cacheGroups
cacheGroup 可以继承或者重写任何 splitChunks.*
中设置的任何配置项,但是 test
、priority
和 reuseExistingChunk
只能在 cacheGroup
下配置。如果想去禁用 cacheGroup
的所有默认行为,将它们设置为 false 即可。
# splitChunks.cacheGroups.priority
值:number
cacheGroup 打包的先后优先级。在进项分割时,一个模块可能同时属于不同的 cacheGroup,优化器将会选择那个拥有更高 priority 的 cacheGroup。默认的分组拥有一个 priority 为负数的权重,以利于自定义分组可以设置一个更高的权重(自定义分组的默认值是 0)。
# splitChunks.cacheGroups.{cacheGroup}.reuseExistingChunk
值: boolean
如果当前 chunk 中包含的模块已经从 main bundle 中分割出来了,那么它将会直接使用那个模块而不是重新生成一个。这个行为可能会影响当前 chunk 的名称。
# splitChunks.cacheGroups.{cacheGroup}.test
值:string
、RegExp
、function(module,chunk)
该选项控制着哪些模块将被当前 cacheGroup 选中。如果忽略这个选项,那么它将默认选择所有模块。它可以匹配模块资源的绝对路径也可以是 chunk 的名称。当一个 chunk 的名称匹配上了,那么这个 chunk 里的所有模块也都被选中了。
# splitChunks.cacheGroups.{cacheGroup}.filename
值:string
当且仅当当前这个 chunk 是一个 initial chunk 的时候,该选项会允许你去重写其文件名称。所有的占位符都可以在 output.filename
中找到。
注意
该选项虽说也可以在全局中设置 splitChunks.filename
,但是它是不推荐的,因为在 splitChunks.chunks
没有被设置为 initial 的情况下它会导致一些错误。避免在全局中设置它。
# splitChunks.cacheGroups.{cacheGroup}.enforce
值:boolean
,默认:false
该选项在设置为 true 的情况下将导致 webpack 会忽略 splitChunks.minSize
、splitChunks.minChunks
、splitChunks.maxAsyncRequests
和splitChunks.maxInitialRequests
选项的值,并且总是为当前这个 cacheGroup 创建一个 chunk。
← 缓存 使用 Typescript →