一个网站该如何以最佳的方式向用户发送资源文件?有很多不同的场景,不同的技术和不同的术语。在这篇文章里,我希望能够让你明白:哪种文件分割策略最适合你的网站和用户,以及如何实现。
根据 Webpack 术语表,有两种不同的文件分割类型。它们看起来似乎可以互换,但显然不行:
- 捆绑拆分:创建更多、更小的文件(但每个请求都需要加载它们)以获得更好的缓存效果。
- 代码拆分:动态加载代码,用户只下载他们正在查看的内容所需的代码。
第二种方法看起来更有吸引力,不是吗?事实上,有很多文章似乎都假设这是拆分 JavaScript 文件唯一有价值的方案。但我想要告诉你的是,对于很多网站来说,第一种方法更有价值,而且它应该是你首先要考虑的。
捆绑拆分
捆绑拆分背后的想法非常简单。如果你有一个巨大的文件,哪怕只是修改了一行代码,用户也必须再次下载整个文件。但是,如果你将它分成两个文件,那么用户只需要下载被修改的那个文件,浏览器会从缓存中获取另一个文件。
捆绑拆分与缓存有关,因此对于首次访问网站的用户来说,有没有拆分其实并没有什么不同。
对于频繁访问网站的用户来说,要衡量捆绑拆分所带来的性能提升可能也很棘手,但我们必须这样做!
我需要一个表格来记录性能数据。下面是上述提到的场景:
- Alice 每周访问我们的网站一次,为期 10 周;
- 我们每周更新一次网站;
- 我们每周都会更新“产品列表”页面;
- 我们还有一个“产品详细信息”页面,目前还未开发出来;
- 在第 5 周,我们添加了一个新的 npm 包;
- 在第 8 周,我们更新了一个现有的 npm 包。
基 线
假设我们的 JavaScript 包大小是 400 KB,只包含 main.js 单个文件。
我们的 Webpack 配置如下(我省略了不相关的配置):
const path = require('path');
module.exports = {
entry: path.resolve(__dirname, 'src/index.js'),
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash].js',
},
};
每个礼拜,当我们做出一些变更时,这个包的 contenthash 就会发生变化。因此,每周 Alice 访问我们的网站时必须下载新的 400 KB 文件。
我们把这些数字记录在表格中,它看起来就像这样。
下载量总共是 4.12 MB,为期 10 周。
但我们可以做得更好。
拆分 vendor 包
现在,我们将包拆分为 main.js 和 vendor.js 文件。
这很简单:
const path = require('path');
module.exports = {
entry: path.resolve(__dirname, 'src/index.js'),
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash].js',
},
optimization: {
splitChunks: {
chunks: 'all',
},
},
};
Webpack 4 努力为你做最好的事情,甚至都不需要告诉它你想要如何拆分捆绑包。
有人说,“这样看起来很整洁,不错,Webpack!”
也有人说,“你都对我的包做了什么?”
设置 optimization.splitChunks.chunks ='all’意味着“将 node_modules 所有内容都放入名为 vendors~main.js 的文件中”。
经过这个基本的捆绑拆分,Alice 每次访问网站时仍然需要下载 200 KB 的 main.js 新文件,然后分别在第 1 周,第 8 周和第 5 周下载 200 KB 的 vendor.js 文件。