webpack4 打包“块”的分离

背景

当不使用外部插件时,webpack 会把 webpack.config.js 配置文件里每个入口打包一个 [entry].js 文件,这意味着单页面应用(单入口)最终只会打包生成一个 js 文件。而很多情况下,这种处理并不是最佳的处理方式。比如会造成单个 js 文件体积过大,并且每次项目迭代都会造成全量 js 的更新,不能充分利用缓存。所以我们需要按照一些规则,对打包输出的“块”进行拆分,已达到优化性能的目的。

webpack4 中 SplitChunksPlugin 插件提供了对打包文件进行拆分的功能。

为了对打包后生成的所有文件有全面的了解,本文第一小节先讲解了 runtimeChunk 配置的作用。

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

以上为 Webpack官方文档 对为什么在 webpack4 后移除了 CommonsChunkPlugin 而使用全新的 splitChunksPlugin 插件的解释。

本文以 webpack v4.16.2 为例,一些配置选项在低版本中可能无效。

runtimeChunk

设置 optimization.runtimeChunktrue 能为每一个入口添加一个额外的“块”,该块是应用“运行时”(runtime)依赖块。

该配置有两个配置选项:

  • single:为所有的块创建一个共享的“运行时”(runtime)文件。
  • multiple 为通用的块创建多个运行时文件。

默认值为 false,即每个入口块嵌入 runtime 依赖,而不是单独将它分离成一个独立的块。

SplitChunksPlugin

默认配置

若不加以配置,webpack4 会自动使用 splitChunks 对资源块进行拆分,默认拆分规则如下(来源 Webpack 官方文档):

  • 块被多处引用或者来自 node_modules 目录

  • 块大小大于 30kb(压缩前)

  • 按需加载的块并行请求数不能大于 5 个

  • 初始化加载的块并行请求数不能大于 3 个

也就是说 splitChunks 只会拆分被多次引用的块,或者该块来自于 node_modules 目录;并且该块在压缩前的大小应大于 30kb,否则不拆离。同时,最后两个条件对并行请求数做出了限制,这就意味着为了满足后两个条件,可能会产生体积较大的块。

默认配置的例子

当不对 optimization.splitChunks 进行任何自定义配置,将optimization.runtimeChunk 设置为 true 时,对一个单页面多路由应用进行打包时,产生了如下 js 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
total 941
24 chunk.0.dec7f9a819944483d79d.js
4 chunk.1.eae64cbc2aa0f9e4e783.js
4 chunk.10.f8750d707c27b95bae13.js
24 chunk.11.2aef84c3ff12c183b072.js
4 chunk.2.21418087238bcf98e494.js
4 chunk.3.545ceaa63e410a4b2d59.js
4 chunk.4.8ca9fe6ed85d546daba4.js
8 chunk.5.1fa9e53fb286828383a7.js
4 chunk.6.50e9186b6024c98d9f30.js
1 chunk.7.a737ccfe76e044e4e6e7.js
8 chunk.8.af950ea9507f49c156e4.js
4 chunk.9.238ddb8be097a6200372.js
800 chunk.app.7c6ddec2161a37416ecb.js
44 chunk.main.c54944c0d51e726f5c37.js
4 runtime.26028abb8f1fadc57c34.js

css文件:

1
2
3
4
5
6
7
8
9
10
11
  4 chunk.1.b853c7844b6e82e97c6b.css
4 chunk.10.ece96fde52d6f0024d0a.css
8 chunk.11.3917cb3b5334910b2ca9.css
1 chunk.2.e9364a9f3d011dd5312b.css
4 chunk.5.d93fa41defa698bc9435.css
4 chunk.6.e52a594a36b4ef3fe490.css
1 chunk.7.52ac776fd2423a0a7c72.css
8 chunk.8.04f1836d396f81ffec4c.css
4 chunk.9.52d5fcfb3d22a54ccf0b.css
320 chunk.app.eea3b0fe4cd81c07d6d8.css
20 chunk.main.f451462a7e2cced31c33.css

入口文件 index.html 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html lang=zh-CN>

<head>
<meta charset=UTF-8>
<title>惠税-助手</title>
<meta name=viewport content="width=device-width,initial-scale=1">
<script>window.SYART_TIME = new Date().getTime();</script>
<link href=./static/css/chunk.app.eea3b0fe4cd81c07d6d8.css rel=stylesheet>
</head>

<body>
<div id=app></div>
<script type=text/javascript src=./static/js/runtime.26028abb8f1fadc57c34.js></script>
<script type=text/javascript src=./static/js/chunk.app.7c6ddec2161a37416ecb.js></script>
</body>
<script>window.LOAD_TIME = Date.now() - window.startTime;</script>

</html>

其中,chunk.[number].[hash] 文件是各个路由页面的块,他们将在需要时被按需加载。chunk.app.[hash] 块则是入口文件初始化加载所依赖的块,chunk.main.[hash] 是 splitChunks 将整个应用中被多次引用的公共内容分离出来,这样能避免相同的文件被多次请求。

runtime.[hash].js 文件即是第一节中提到的“运行时”依赖块,当把 optimization.runtimeChunk 设置为 false 时则不会生成该块。

小结

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.

官方文档中建议使用默认配置,认为默认配置是最佳实践。然而有些时候默认配置并不能完全满足项目需求,可能需要一些自定义的配置。

自定义配置

splitChunksPlugin 插件提供了配置参数,使得开发者能够对包进行自定义配置和拆分块。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
module.exports = {
//...
optimization: {
splitChunks: {
chunks: 'async', // all async initial 选择对哪些块进行优化
minSize: 30000, // 被拆分的最小大小(压缩前)
minChunks: 1, // 被共享的最小次数
maxAsyncRequests: 5, // 最大按需求并行请次数
maxInitialRequests: 3, // 最大初始化并行请求数
automaticNameDelimiter: '~', // 自动命名分隔符
name: true, // 自动为块命名
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
}
};

其中 cacheGroups 选项支持对块进行进一步的自定义分割,其中的配置默认继承于 splitChunksPlugin.* 配置,在内部进行配置将覆盖继承配置。其中 test priorityreuseExistingChunkcacheGroups 特有的配置。

test 选项对要打包的文件进行选择,一般是一个正则表达式。类似 loaders 配置里的 test

priority 设置该配置组的优先级。一个模块可能属于多个 cacheGroups,这时优先将其打包入高优先级的块中。

reuseExistingChunk 如果该块已经从主块中分离出来了,则将直接重用该块,而不是重新生成,不过可能会影响块的命名。

总结

以上就是 Webpack4 中打包分离“块”的配置方法。之所以要进行“块”的分离,是因为一些代码可以被公用,将他们分离出来可以避免重复加载;同时,一些基本不会改动的基础支持被分离出来时,当项目迭代时,这些基础块可以从缓存中获取,减少流量费用。另外,适当的将较大的“块”拆成多个较小的“块”有利于发挥现代浏览器并行下载资源的优势,提高资源加载速度。

除了适当的对“块”进行拆分,充分利用缓存、模块按需加载、开启 Web 服务器的 Gzip 压缩等方法也是提高 Web 应用加载和响应速度的常用方法。