# 文件指纹chunkhash 与 持久化缓存caching

# 文件指纹chunkhash

文件指纹:打包后输出的文件名的后缀。

# 指纹类别

  • Hash:和整个项目的构建相关,build-specific,即每次编译都不同。
    • 可以在测试环境打包的JS文件中使用'[name].[hash]'
  • Chunkhash:和 Webpack 打包的 chunk 有关,chunk-specific,是根据每个 chunk 的内容计算出的 hash,不同的 entry 会生出不同的 chunkhash。
    • 适用于生产环境打包后的JS文件'[name].[chunkhash]',最大限度利用浏览器缓存。
  • Contenthash:根据文件内容来定义 hash,文件内容不变,则 contenthash 不变。

[chunkhash]不能和 HMR 一起使用

注意:开发环境应该直接用[name],不要在开发环境使用[chunkhash]、[hash]、[contenthash],因为不需要在开发环境持久缓存,而且这样会增加编译时间。

这也是为什么常说”[chunkhash]不能和 HMR 一起使用“。

占位符指定长度

注意:hash都是比较长的,可以在占位符上指定我们要的长度,来生成我们想要的位数,如:[hash:8]、[chunkhash:8]、[contenthash:8]

# 各类别适用文件

  • JS文件的指纹设置[chunkhash]
    • 设置 output 的 filename。
output: {
    filename: '[name][chunkhash:8].js',
    path: __dirname + '/dist'
}
  • CSS文件的指纹设置[contenthash]
    • 设置 MiniCssExtractPlugin 的 filename。
// MiniCssExtractPlugin:将css资源提取到一个独立的文件
plugins: [
    new MiniCssExtractPlugin({
        filename: `[name][contenthash:8].css`
    }),
]
  • Images/Fonts的指纹设置[hash]
    • 注意:图片,字体文件的hash和css/js资源的hash概念不一样,图片,字体文件的hash是由内容决定的。
    • 设置 file-loader 的 name。
module: {
    rules: [
        {
            test: /\.(png|svg|jpg|gif)$/,
            use: [{
                loader: 'file-loader',
                options: {
                    name: 'img/[name][hash:8].[ext] '
                }
            }]
        }
    ]
}

各类别适用文件

  • JS文件的指纹设置'[name][chunkhash:8].js'
    • js文件为什么不用contenthash呢?)
    • 因为js引入了css模块,如果css改变,css使用的contenthash,css的指纹变了,但对于引入它的js模块来说,如果使用contenthash,则js模块指纹不变。这样就会出错了,因为js无法引入更新后的css文件。
  • CSS文件的指纹设置'[name][contenthash:8].css'
    • css文件为什么不用chunkhash呢?)
    • 因为js引入了css模块,如果js改变,js使用的是chunkhash,则chunkhash会改变,那么其引入的css模块也会跟着改变指纹,但这是不合理的,因为css自身内容根本没变。
    • 所以css要使用contenthash,只与自身内容有关,无视被哪个js模块引用。
  • Images/Fonts的指纹设置'[name][hash:8].[ext]', 注意,图片字体的hash与和css或js的hash概念不一样,是按内容生成的,不是按编译生成的。

# 持久化缓存caching(注意id问题)

  • 本质上就是通过指定:output.filename: '[name].[chunkhash].js',因为[chunkhash]是内容相关的,只要内容发生了改变,构建后文件名的 hash 就会发生改变。
  • 再配合代码分割:将vendor.js单独打包。因为它们是不太可能频繁发生改变的,所以无需多次加载这些模块。
  • 最后注意模块id变化问题:
    • 默认情况下,异步模块id是计数器递增的,但这样有问题,如果在中间增加了新模块,那么之后所有模块的 ID 都会被改变,但其实它们的内容没变。
    • 可以使用 HashedModuleIdsPlugin 插件来改变模块 ID 的计算方式。这个插件用模块路径的哈希值代替了基于计数器的 ID:
// webpack.config.js
module.exports = {
  plugins: [
    new webpack.HashedModuleIdsPlugin(),
  ],
};

编译输出:

[3IRH] ./index.js 29 kB {1} [built]
[DuR2] (webpack)/buildin/global.js 488 bytes {2} [built]
[JkW7] (webpack)/buildin/module.js 495 bytes {2} [built]
[LbCc] ./webPlayer.js 24 kB {1} [built]
[lebJ] ./comments.js 58 kB {0} [built]
[02Tr] ./ads.js 74 kB {1} [built]
    + 1 hidden module

# 如何将文件名发送到浏览器

可以使用 HtmlWebpackPlugin 或者 WebpackManifestPlugin

  • HtmlWebpackPlugin:是一个简单但扩展性不强的插件。在编译期间,它会生成一个 HTML 文件,文件包含了所有已经被编译的资源。
<!-- index.html -->
<!doctype html>
<!-- ... -->
<script src="bundle.8e0d62a03.js"></script>
  • WebpackManifestPlugin:是一个扩展性更佳的插件。在打包时,它会生成一个 JSON 文件,里面包含了原文件名和带哈希文件名的映射。在服务端,通过这个 JSON 就能方便的找到我们真正要执行的文件。
// manifest.json
{
  "bundle.js": "bundle.8e0d62a03.js"
}

# 参考链接

Last Updated: 9/3/2020, 11:41:44 AM