构建拆分
目前,应用程序的生产版本是单个JavaScript文件。 如果应用程序已更改,则客户端也必须下载供应商(vendor)依赖内容。
最好只下载更改的部分。如果供应商依赖项发生更改,则客户端应仅获取供应商依赖项。实际的应用程序代码也是如此。构建拆分可以使用optimization.splitChunks.cacheGroups
来实现。在生产模式下运行时,webpack4可以开箱即可执行一系列拆分但在这种情况下,我们会手动执行某些操作。
要要使bundle无效,必须将哈希附加到生成的bundle中,如:向文件名中注入hash章节中所述。只有这样客户端才能识别请求内容是否改变,进而决定是否下载新的内容。
构建拆分的原理
利用构建拆分,你可以将供应商依赖项推送到独立的构建包中,并从客户端级缓存中受益。该过程可以让应用程序在保持整个大小不变的情况下完成。虽然增加了请求的次数,产生了轻微的开销,但是客户端的缓存的弥补了这个成本。
举个例子。你可以得到 main.js(10 kB)和 vendor.js(90 kB),而不是 main.js(100 kB)。现在,对于已经使用过该应用程序的客户端,对应用程序所做的更改,维护更为廉价。
使用这种方式,就会面因临缓存而产生的问题。其中之一是缓存失效。解决方法会在 在文件名中添加hash 章节中讨论。
构建拆分不是唯一的出路。代码分割 章节讨论了另一种更细粒度的方法。
为了实现分离,引入其它依赖
鉴于暂时没有什么东西可以拆分供应商(vendor)包,你应该在源文件中引入一些东西。
在项目中添加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 97.5 KiB 0 [emitted] main
main.css 3.49 KiB 0 [emitted] main
main.js.map 240 KiB 0 [emitted] main
main.css.map 85 bytes 0 [emitted] main
index.html 220 bytes [emitted]
Entrypoint main = main.js main.css main.js.map main.css.map
...
正如你所看到的那样, main.js 非常大。这就是接下来要解决的问题。
vendor
输出
配置 在webpack4.x之前,我们使用 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",
},
},
},
},
},
]);
如果你不希望依赖自动化,则使用这种配置可以更好地控制拆分过程。
拆分和合并模块(Chunks)
Webpack通过两个插件实现对生成的块的更多控制:AggressiveSplittingPlugin
和AggressiveMergingPlugin
。
前者允许你分离出更多和更小的包。由于HTTP/2新标准的工作方式,让这种方式很容易实现。
以下是 Aggressive
分离的基本思路:
{
plugins: [
new webpack.optimize.AggressiveSplittingPlugin({
minSize: 10000,
maxSize: 30000,
}),
],
},
如果你分成多个小构建包,那么缓存会失效,这是需要做一个权衡。此外,你还会在HTTP/1环境中增加请求开销。目前,由于插件中的一个错误,启用HtmlWebpackPlugin
该插件不起作用。
而 AggressiveMergingPlugin
插件以相反的方式工作,并允许你将小包组合成更大的:
{
plugins: [
new AggressiveMergingPlugin({
minSizeReduce: 2,
moveToParents: true,
}),
],
},
如果使用webpack 记录,则可以使用这个插件获得良好的缓存行为。这个想法在添加哈希到文件名章节中详细介绍。
webpack.optimize
还包含 LimitChunkCountPlugin
和 MinChunkSizePlugin
,它们可以进一步控制构建包大小。
Tobias Koppers 在他的官方博客中,详细的介绍了 aggressive merging。
webpack中的模块(Chunk)类型
在上面的示例中,你使用了不同类型的webpack块。Webpack处理三种类型的块:
- Entry chunks - 它包含webpack运行时和它随后加载的模块。
- Normal chunks - 它不包含webpack运行时的模块。相反,是这些可以在应用程序运行时动态加载的模块。一个包装器(例如JSONP)生成这些模块。在下一章代码分割这个主题中,你将生成一个
Normal chunks
。 - Initial chunks - 它是
Normal chunks
,但它用来计算进入应用程序的初始加载时间。作为使用者,你不用关心这些。这是Initial chunks
和Normal chunks
之间的区别,这一点很重要。
总结
与之前相比,现在情况好转。要知道小 main
包与 vendor
包相比如何?如何从此拆分中受益?你需要在设置缓存,下一部分的 在文件名中添加hash 一章将详细介绍。
内容回顾:
- Webpack allows you to split bundles from configuration entries through the
optimization.splitChunks.cacheGroups
field. It performs 构建拆分 by default in production mode as well. - A vendor bundle contains the third party code of your project. The vendor dependencies can be detected by inspecting where the modules are imported.
- Webpack offers more control over chunking through specific plugins, such as
AggressiveSplittingPlugin
andAggressiveMergingPlugin
. Mainly the splitting plugin can be handy in HTTP/2 oriented setups. - Internally webpack relies on three chunk types: entry, normal, and initial chunks.
在下一章中,你将学习到代码分割和按需加载代码。
← Source Maps 代码分割 →