缓存指南
缓存指南
我们使用 webpack 来打包模块化应用程序,从而生成一个可部署的 /dist 目录。一旦 /dist 目录中的内容被部署到服务器,客户端(通常是浏览器)就会访问该服务器以获取站点及其资源。最后一步可能非常耗时,因此浏览器采用了一种称为缓存的技术。这种技术可以让网站加载更快,并减少不必要的网络流量。但是,当您需要更新代码时,它也可能带来麻烦。
本指南重点介绍确保 webpack 编译生成的文件能够保持缓存(除非内容发生更改)所需的配置。
输出文件名
我们可以使用 output.filename 的替换规则来定义输出文件的名称。webpack 提供了一种使用带括号的字符串(称为替换规则)来模板化文件名的方法。[contenthash] 替换规则会根据资源的内容添加一个唯一哈希。当资源内容发生变化时,[contenthash] 也会随之改变。
让我们使用入门指南中的示例,并结合输出管理中的 plugins 来配置项目,这样我们就无需手动维护 index.html 文件:
项目
diff
webpack-demo
├── package.json
├── package-lock.json
├── webpack.config.js
├── /dist
├── /src
│ └── index.js
└── /node_modules
webpack.config.js
diff
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import HtmlWebpackPlugin from 'html-webpack-plugin';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default {
entry: './src/index.js',
plugins: [
new HtmlWebpackPlugin({
- title: 'Output Management',
+ title: 'Caching',
}),
],
output: {
- filename: 'bundle.js',
+ filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist'),
clean: true,
},
};
使用此配置运行构建脚本 npm run build,应产生以下输出:
bash
...
Asset Size Chunks Chunk Names
main.7e2c49a622975ebd9b7e.js 544 kB 0 [emitted] [big] main
index.html 197 bytes [emitted]
...
正如您所看到的,bundle 的名称现在会反映其内容(通过哈希)。如果我们再次运行构建而不做任何更改,我们预计该文件名会保持不变。然而,如果我们再次运行它,可能会发现情况并非如此:
bash
...
Asset Size Chunks Chunk Names
main.205199ab45963f6a62ec.js 544 kB 0 [emitted] [big] main
index.html 197 bytes [emitted]
...
这是因为 webpack 在入口 chunk 中包含了一些样板代码,特别是 runtime 和 manifest。
警告: 输出可能因你当前的 webpack 版本不同而有所差异。较新版本可能不会遇到与较旧版本相同的所有哈希问题,但我们仍然建议采取以下步骤以确保安全。
提取样板代码
正如我们在代码分割中学到的,SplitChunksPlugin 可用于将模块拆分到单独的 bundle 中。webpack 提供了一个优化功能,可以使用 optimization.runtimeChunk 选项将运行时代码拆分为单独的 chunk。将其设置为 single,可以为所有 chunk 创建一个单一的 runtime bundle:
webpack.config.js
diff
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import HtmlWebpackPlugin from 'html-webpack-plugin';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default {
entry: './src/index.js',
plugins: [
new HtmlWebpackPlugin({
title: 'Caching',
}),
],
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist'),
clean: true,
},
+ optimization: {
+ runtimeChunk: 'single',
+ },
};
让我们再次运行构建,查看提取出的 runtime bundle:
bash
Hash: 82c9c385607b2150fab2
Version: webpack 4.12.0
Time: 3027ms
Asset Size Chunks Chunk Names
runtime.cc17ae2a94ec771e9221.js 1.42 KiB 0 [emitted] runtime
main.e81de2cf758ada72f306.js 69.5 KiB 1 [emitted] main
index.html 275 bytes [emitted]
[1] (webpack)/buildin/module.js 497 bytes {1} [built]
[2] (webpack)/buildin/global.js 489 bytes {1} [built]
[3] ./src/index.js 309 bytes {1} [built]
+ 1 hidden module
将第三方库(如 lodash 或 react)提取到单独的 vendor chunk 也是一种良好的实践,因为它们相对于我们的本地源代码不太可能发生变化。这一步可以让客户端从服务器请求更少的内容以保持最新状态。
这可以通过使用 SplitChunksPlugin 的 cacheGroups 选项来完成,如 SplitChunksPlugin 示例 2 所示。让我们添加 optimization.splitChunks 并配置 cacheGroups,然后构建:
webpack.config.js
diff
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import HtmlWebpackPlugin from 'html-webpack-plugin';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default {
entry: './src/index.js',
plugins: [
new HtmlWebpackPlugin({
title: 'Caching',
}),
],
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist'),
clean: true,
},
optimization: {
runtimeChunk: 'single',
+ splitChunks: {
+ cacheGroups: {
+ vendor: {
+ test: /[\\/]node_modules[\\/]/,
+ name: 'vendors',
+ chunks: 'all',
+ },
+ },
+ },
},
};
让我们再次运行构建,查看新的 vendor bundle:
bash
...
Asset Size Chunks Chunk Names
runtime.cc17ae2a94ec771e9221.js 1.42 KiB 0 [emitted] runtime
vendors.a42c3ca0d742766d7a28.js 69.4 KiB 1 [emitted] vendors
main.abf44fedb7d11d4312d7.js 240 bytes 2 [emitted] main
index.html 353 bytes [emitted]
...
现在我们可以看到,main bundle 不再包含来自 node_modules 目录的 vendor 代码,并且体积已缩小到 240 bytes!
模块标识符
让我们向项目添加另一个模块 print.js:
项目
diff
webpack-demo
├── package.json
├── package-lock.json
├── webpack.config.js
├── /dist
├── /src
│ ├── index.js
+│ └── print.js
└── /node_modules
print.js
diff
+ export default function print(text) {
+ console.log(text);
+ };
src/index.js
diff
import _ from 'lodash';
+ import Print from './print';
function component() {
const element = document.createElement('div');
// Lodash, now imported by this script
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
+ element.onclick = Print.bind(null, 'Hello webpack!');
return element;
}
document.body.appendChild(component());
再次运行构建,我们预计只有 main bundle 的哈希会改变,然而......
bash
...
Asset Size Chunks Chunk Names
runtime.1400d5af64fc1b7b3a45.js 5.85 kB 0 [emitted] runtime
vendor.a7561fb0e9a071baadb9.js 541 kB 1 [emitted] [big] vendor
main.b746e3eb72875af2caa9.js 1.22 kB 2 [emitted] main
index.html 352 bytes [emitted]
...
...我们可以看到三个都改变了。这是因为默认情况下,每个 module.id 会根据解析顺序递增。这意味着当解析顺序发生变化时,这些 ID 也会随之改变。总结如下:
mainbundle 因其新内容而改变。vendorbundle 因其module.id被改变而改变。runtimebundle 因现在包含对新模块的引用而改变。
第一个和最后一个改变是符合预期的,我们需要修复的是 vendor 的哈希。让我们使用 optimization.moduleIds 并设置 'deterministic' 选项:
webpack.config.js
diff
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import HtmlWebpackPlugin from 'html-webpack-plugin';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default {
entry: './src/index.js',
plugins: [
new HtmlWebpackPlugin({
title: 'Caching',
}),
],
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist'),
clean: true,
},
optimization: {
+ moduleIds: 'deterministic',
runtimeChunk: 'single',
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
},
},
},
};
现在,即使有新的本地依赖,我们的 vendor 哈希也应在多次构建之间保持一致:
bash
...
Asset Size Chunks Chunk Names
main.216e852f60c8829c2289.js 340 bytes 0 [emitted] main
vendors.55e79e5927a639d21a1b.js 69.5 KiB 1 [emitted] vendors
runtime.725a1a51ede5ae0cfde0.js 1.42 KiB 2 [emitted] runtime
index.html 353 bytes [emitted]
Entrypoint main = runtime.725a1a51ede5ae0cfde0.js vendors.55e79e5927a639d21a1b.js main.216e852f60c8829c2289.js
...
让我们修改 src/index.js,临时移除那个额外的依赖:
src/index.js
diff
import _ from 'lodash';
- import Print from './print';
+ // import Print from './print';
function component() {
const element = document.createElement('div');
// Lodash, now imported by this script
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
- element.onclick = Print.bind(null, 'Hello webpack!');
+ // element.onclick = Print.bind(null, 'Hello webpack!');
return element;
}
document.body.appendChild(component());
最后再次运行构建:
bash
...
Asset Size Chunks Chunk Names
main.ad717f2466ce655fff5c.js 274 bytes 0 [emitted] main
vendors.55e79e5927a639d21a1b.js 69.5 KiB 1 [emitted] vendors
runtime.725a1a51ede5ae0cfde0.js 1.42 KiB 2 [emitted] runtime
index.html 353 bytes [emitted]
Entrypoint main = runtime.725a1a51ede5ae0cfde0.js vendors.55e79e5927a639d21a1b.js main.ad717f2466ce655fff5c.js
...
我们可以看到两次构建在 vendor bundle 的文件名中都产生了 55e79e5927a639d21a1b。
结论
缓存可能很复杂,但为应用程序或网站用户带来的好处使这些努力值得。请参阅下面的_延伸阅读_部分以了解更多信息。
帮助我们改进文档
发现翻译问题或内容错误?请告诉我们。
