目前,生产环境下的打包结果是单个 JavaScript 文件。如果更改了代码,则客户端也必须重新下载整个包,包括一些外部依赖包。
最好的结果是只下载更改的部分。如果外部依赖包发生更改,则客户端应仅获取依赖包。对于应用本身的代码也是如此。我们可以使用optimization.splitChunks.cacheGroups
来进行分割打包。在生产模式下运行时,webpack 4可以开箱即可执行一系列拆分。但在这里,我们会手动执行某些操作。
要正确使打包结果无效,必须将哈希附加到生成的 bundle 中,如添加哈希值到文件名一章所描述的。
分割打包的思想
通过拆分打包,您可以将外包依赖项单独打包,并从客户端级别缓存中受益。执行了该过程,应用程序的整个大小依然保持不变。尽管需要执行的请求越多,会产生轻微的开销,但缓存的好处弥补了这一成本。
打一个简单的比方,你可以得到 main.js(10 kB)和 vendor.js(90 kB),而不是 main.js(100 kB)。现在,对于已经使用过该应用程序的客户端,如果仅仅对应用代码做更改,将只加载 main.js 部分。
缓存也有相应的问题,其中之一就是缓存失效。与此相关的潜在方法将在“ 添加哈希值到文件名”一章中讨论。
打包分割并不是唯一的出路。代码分割一章讨论了另一种更精细的方式。
添加要拆分的东西
鉴于还没有可以拆分的外部依赖包,我们首先将 React 添加到项目中:
npm install react react-dom --save
然后在项目中导入它:
src/index.js
import "react";
import "react-dom";
...
执行 npm run build
以获取基本构建,你应该得到如下的东西:
Hash: 80f9bb6fc04c54949644
Version: webpack 4.1.1
Time: 3276ms
Built at: 3/16/2018 4:59:25 PM
Asset Size Chunks Chunk Names
如您所见,main.js 很大。这是接下来要解决的问题。
设置 vendor 包
在 webpack 4 之前,过去常常要使用 CommonsChunkPlugin
来管理打包分裂,现在该插件已被自动化和配置所取代。要从 node_modules 目录中提取供应商(外部依赖)包,请按如下方式调整配置:
webpack.config.js
const productionConfig = merge([
...
{
optimization: {
splitChunks: {
chunks: "initial",
},
},
},
]);
如果您再次打包(npm run build
),您应该看到以下内容:
Hash: 6c499f10237fdbb07378
Version: webpack 4.1.1
Time: 3172ms
Built at: 3/16/2018 5:00:03 PM
Asset Size Chunks Chunk Names
vendors~main.js 96.8 KiB 0 [emitted] vendors~main
main.js 1.35 KiB 1 [emitted] main
main.css 1.27 KiB 1 [emitted] main
vendors~main.css 2.27 KiB 0 [emitted] vendors~main
vendors~main.js.map 235 KiB 0 [emitted] vendors~main
vendors~main.css.map 93 bytes 0 [emitted] vendors~main
main.js.map 7.11 KiB 1 [emitted] main
main.css.map 85 bytes 1 [emitted] main
index.html 329 bytes [emitted]
Entrypoint main = vendors~main.js vendors~main.css ...
...
现在,我们看到打包结果被拆分了。下图说明了当前的情况。
控制分割打包
我们可以重写上面的配置,并将外部依赖包的匹配规则针对于 node_modules 文件夹,如下所示:
webpack.config.js
const productionConfig = merge([
...
{
optimization: {
splitChunks: {
cacheGroups: {
commons: {
test: /[\\/]node_modules[\\/]/,
name: "vendor",
chunks: "initial",
},
},
},
},
},
]);
如果你不希望自动分割打包,按照此格式可以更好地控制拆分过程。
拆分和合并块
Webpack 通过两个插件提供对生成的块的更多控制:AggressiveSplittingPlugin
和AggressiveMergingPlugin
。前者允许您发出更多更小的包。由于 HTTP/2 新标准的工作方式,这种处理是非常方便的。
以下是一种更激进的分割打包方式:
{
plugins: [
new webpack.optimize.AggressiveSplittingPlugin({
minSize: 10000,
maxSize: 30000,
}),
],
},
如果你分成多个小的块,对于客户端缓存来说是比较有利的;但是,在 HTTP/1 环境中还会有额外的请求开销。目前,由于 HtmlWebpackPlugin
中的 bug ,如果启用该插件,这个方法不会起作用。
这个激进的插件还能够以相反的方式工作,允许您将小的块组合成更大的块:
{
plugins: [
new AggressiveMergingPlugin({
minSizeReduce: 2,
moveToParents: true,
}),
],
},
如果使用 Webpack 对于已经打包过的块进行记录,那就可以更好的利用缓存。“添加哈希到文件名”一章中详细讨论了这个想法。
webpack.optimize
包含 LimitChunkCountPlugin
和 MinChunkSizePlugin
,可以进一步控制块的大小。
Tobias Koppers 在webpack的官方博客中详细讨论了激进合并。
Webpack 中的块类型
在上面的示例中,您使用了不同类型的 Webpack 块。Webpack 处理三种类型的块:
- 入口块 - 入口块包含 Webpack 运行时和它随后加载的模块。
- 正常块 - 正常块不包含 Webpack 运行时。相反,这些可以在应用程序运行时动态加载,Webpack 为这些块生成合适的包装器(例如 JSONP)。您将在下一章中,设置代码拆分时生成正常块。
- 初始块 - 初始块是正常的块,计入应用程序的初始加载时间。作为用户,您不必关心这些。这是入口块和正常块之间的重要的分割点。
总结
与之前相比,现在情况好转了。注意比较 main
块与 vendor
块之间的大小。要从此拆分中受益,你可以设置缓存。请参考本书的下一部分“ 添加哈希值到文件名”。
回顾一下:
- Webpack 允许您通过
optimization.splitChunks.cacheGroups
字段从配置入口中分割打包。它在生产模式下默认执行分割打包。 vendor 包
包含项目的第三方代码。可以通过检查模块的导入位置来检测外部依赖。- Webpack 通过特定的插件提供对块的分割进行更多的控制,例如
AggressiveSplittingPlugin
和AggressiveMergingPlugin
。这些拆分插件在面向 HTTP/2 的设置时非常方便。 - 内部 Webpack 依赖于三种块类型:入口块,正常块和初始块。
在下一章中,您将了解代码拆分和按需加载代码。