使用 webpack 打包模块化后的应用程序,webpack 会生成一个可部署的 /dist 目录,然后把打包后的内容放置在此目录中。只要 /dist 目录中的内容部署到 server 上,client(通常是浏览器)就能够访问此 server 的网站及其资源。而最后一步获取资源是比较耗费时间的,所以浏览器使用 缓存 的技术来降低网络流量,使网站加载速度更快。然而,如果我们在部署新版本时不更改资源的文件名,浏览器可能会认为它没有被更新,就会使用它的缓存版本。由于缓存的存在,当你需要获取新的代码时,就会显得很棘手。
在这里介绍通过必要的配置,以确保 webpack 编译生成的文件能够被客户端缓存,而在文件内容变化后,能够请求到新的文件。
输出文件的文件名(output filename)
我们可以通过替换 output.filename 中的 substitutions 设置,来定义输出文件的名称。webpack 提供了一种使用称为 substitution(可替换模板字符串) 的方式,通过带括号字符串来模板化文件名。其中,[contenthash] substitution 将根据资源内容创建出唯一 hash。当资源内容发生变化时,[contenthash] 也会发生变化。
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist')
},
webpack.config.js:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
mode: 'development',
devtool: 'inline-source-map',
entry: {
index: './src/index.js',
},
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist')
},
plugins: [
// new CleanWebpackPlugin(), // 构建前清理 /dist 文件夹
new CleanWebpackPlugin({ cleanStaleWebpackAssets: false }),
new HtmlWebpackPlugin({ template: './src/index.html' })
// new HtmlWebpackPlugin({ title: '管理输出' })
]
};
运行npm run build
结果:
可以看到,bundle 的名称是它内容(通过 hash)的映射。如果文件不做修改,然后再次运行构建,我们以为文件名会保持不变。然而,真的运行,可能会发现情况并非如此,注意是可能,这个看webpack的版本。(本人当前demo下没有出现这个问题)
输出可能会因当前的 webpack 版本而稍有差异。与旧版本相比,新版本不一定有完全相同的问题,但我们仍然推荐的以下步骤,确保结果可靠。
提取引导模板(extracting boilerplate)
正如我们在 代码分离 中所学到的,SplitChunksPlugin
可以用于将模块分离到单独的 bundle 中。webpack 还提供了一个优化功能,可使用 optimization.runtimeChunk
选项将 runtime 代码拆分为一个单独的 chunk。将其设置为 single
来为所有 chunk 创建一个 runtime bundle。
optimization: {
runtimeChunk: 'single',
},
webpack.config.js:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
mode: 'development',
devtool: 'inline-source-map',
entry: {
index: './src/index.js',
},
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist')
},
optimization: {
runtimeChunk: 'single',
},
plugins: [
// new CleanWebpackPlugin(), // 构建前清理 /dist 文件夹
new CleanWebpackPlugin({ cleanStaleWebpackAssets: false }),
new HtmlWebpackPlugin({ template: './src/index.html' })
// new HtmlWebpackPlugin({ title: '管理输出' })
]
};
将第三方库(library)(例如 lodash 或 react)提取到单独的 vendor chunk 文件中,是比较推荐的做法,这是因为,它们很少像本地的源代码那样频繁修改。这样可以利用 client 的长效缓存机制,减少向 server 请求获取资源,同时还能保证 client 代码和 server 代码版本一致。 这可以通过 SplitChunksPlugin
插件的 cacheGroups
选项来实现。我们在 optimization.splitChunks
添加如下 cacheGroups 参数并构建:
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
},
},
webpack.config.js:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
mode: 'development',
devtool: 'inline-source-map',
entry: {
index: './src/index.js',
},
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist')
},
optimization: {
runtimeChunk: 'single',
splitChunks: {
// 将第三方库(library) (例如 lodash 或 react)提取到单独的 vendor chunk 文件中
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
},
},
},
plugins: [
// new CleanWebpackPlugin(), // 构建前清理 /dist 文件夹
new CleanWebpackPlugin({ cleanStaleWebpackAssets: false }),
new HtmlWebpackPlugin({ template: './src/index.html' })
// new HtmlWebpackPlugin({ title: '管理输出' })
]
};
运行npm run build
结果:
我们可以看到 index 不再含有来自 node_modules 目录的 vendor 代码,并且体积减少到1.64kib!
以下是配置代码对比结果
optimization不做配置:
asset index.90d8de6a91772c9eed3d.js 1.38 MiB [emitted] [immutable] (name: index)
asset index.html 262 bytes [emitted]
runtime modules 1.25 KiB 6 modules
cacheable modules 530 KiB 184ad84822250661af.js 1.61 KiB
./src/index.js 228 bytes [built] [code generated]
./node_modules/lodash/lodash.js 530 KiB [built] [code generated]
webpack 5.16.0 compiled successfully in 667 ms
增加optimization配置:
optimization: {
runtimeChunk: 'single',
},
asset index.71620420237e492d60bb.js 1.37 MiB [emitted] [immutable] (name: index)
asset runtime.bcea988bda22c3d406e4.js 15.9 KiB [emitted] [immutable] (name: runtime)
asset index.html 317 bytes [emitted]
Entrypoint index 1.38 MiB = runtime.bcea988bda22c3d406e4.js 15.9 KiB index.71620420237e492d60bb.js 1.37 MiB
runtime modules 3.75 KiB 7 modules
cacheable modules 530 KiB
./src/index.js 228 bytes [built] [code generated]
./node_modules/lodash/lodash.js 530 KiB [built] [code generated]
webpack 5.16.0 compiled successfully in 660 ms
接着增加optimization配置:
optimization: {
runtimeChunk: 'single',
splitChunks: {
// 将第三方库(library) (例如 lodash 或 react)提取到单独的 vendor chunk 文件中
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
},
},
},
asset vendors.e7928d05f3918ae6d79c.js 1.37 MiB [emitted] [immutable] (name: vendors) (id hint: vendor)
asset runtime.bcea988bda22c3d406e4.js 15.9 KiB [emitted] [immutable] (name: runtime)
asset index.a7184ad84822250661af.js 1.61 KiB [emitted] [immutable] (name: index)
asset index.html 372 bytes [emitted]
Entrypoint index 1.39 MiB = runtime.bcea988bda22c3d406e4.js 15.9 KiB vendors.e7928d05f3918ae6d79c.js 1.37 MiB index.a7184ad84822250661af.js 1.61 KiB
runtime modules 3.75 KiB 7 modules
cacheable modules 530 KiB
./src/index.js 228 bytes [built] [code generated]
./node_modules/lodash/lodash.js 530 KiB [built] [code generated]
webpack 5.16.0 compiled successfully in 590 ms
模块标识符(module identifier)
以下demo结果在本人当前webpack版本下不会出现,但是需要了解到有这种情况出现。
在项目中再添加一个模块 print.js,再次运行构建,然后我们期望的是,只有 main bundle 的 hash 发生变化,然而……
可以看到这三个文件的 hash 都变化了。这是因为每个 module.id 会默认地基于解析顺序(resolve order)进行增量。也就是说,当解析顺序发生变化,ID 也会随之改变。因此,简要概括:
- main bundle 会随着自身的新增内容的修改,而发生变化。
- vendor bundle 会随着自身的 module.id 的变化,而发生变化。
- runtime bundle 会因为现在包含一个新模块的引用,而发生变化。
第一个和最后一个都是符合预期的行为,vendor hash 发生变化是我们要修复的。将 optimization.moduleIds
设置为 'deterministic'
:
moduleIds: 'deterministic',
webpack.config.js文件:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
mode: 'development',
devtool: 'inline-source-map',
entry: {
index: './src/index.js',
},
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist')
},
optimization: {
moduleIds: 'deterministic',
runtimeChunk: 'single',
splitChunks: {
// 将第三方库(library) (例如 lodash 或 react)提取到单独的 vendor chunk 文件中
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
},
},
},
plugins: [
// new CleanWebpackPlugin(), // 构建前清理 /dist 文件夹
new CleanWebpackPlugin({ cleanStaleWebpackAssets: false }),
new HtmlWebpackPlugin({ template: './src/index.html' })
// new HtmlWebpackPlugin({ title: '管理输出' })
]
};
现在,不论是否添加任何新的本地依赖,对于前后两次构建,vendor hash 都应该保持一致。
然后,修改 src/index.js,临时移除额外的依赖:
可以看到,这两次构建中,vendor bundle 文件名称没有发生变化。
相关代码链接