在前端工程化中,文件名哈希(Hash)的核心目的是解决浏览器缓存问题(Cache Busting)。通过给文件名加上基于内容计算的唯一标识,可以确保当代码更新时,文件名会随之改变,从而强迫浏览器下载最新资源,而未改变的文件则继续沿用缓存。
1. Webpack 中的实现
Webpack 提供了多种哈希占位符,但在生产环境中,[contenthash] 是最推荐的选择,因为它仅在文件内容发生变化时才会改变。
配置示例
在 webpack.config.js 中,通过 output 属性进行配置:
JavaScript
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
// [name] 是入口名称,[contenthash] 是根据内容生成的哈希值,8位长度
filename: '[name].[contenthash:8].js',
path: path.resolve(__dirname, 'dist'),
clean: true, // 每次构建前清理 dist 目录
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html', // 自动将带哈希的 js 注入到 HTML 中
}),
],
};
三种哈希的区别
理解这三者的差异对于优化缓存命中率至关重要:
| 哈希类型 | 作用范围 | 建议场景 |
|---|---|---|
[fullhash] |
整个项目。只要项目中有一个文件改了,所有产物的哈希都会变。 | 开发环境(Webpack 5 之前叫 hash)。 |
[chunkhash] |
基于 Webpack 的 Entry(入口)。同一个入口下的所有文件共享一个哈希。 | 简单的 JS 打包。 |
[contenthash] |
基于单个文件内容。JS 改了,CSS 没改,则 CSS 的哈希不变。 | 生产环境最佳实践。 |
2. Vite 中的实现
Vite 在生产环境(vite build)下使用 Rollup 进行构建,默认就已经开启了文件名哈希功能。你通常不需要手动配置就能看到 main.a1b2c3.js 这样的文件。
如果你需要自定义哈希的格式,可以在 vite.config.js 中通过 build.rollupOptions 进行细粒度控制:
配置示例
JavaScript
import { defineConfig } from 'vite';
export default defineConfig({
build: {
rollupOptions: {
output: {
// 用于入口文件(如 index.js)
entryFileNames: 'assets/[name].[hash].js',
// 用于动态导入的 chunk 文件
chunkFileNames: 'assets/[name].[hash].js',
// 用于资源文件(如 css, image, font)
assetFileNames: 'assets/[name].[hash].[ext]',
},
},
},
});
为什么 Vite 默认就很强?
Vite 的底层构建工具 Rollup 对内容哈希的处理非常成熟。由于 Vite 鼓励使用原生 ESM,它在构建时能更精准地分析依赖树。即便你没有配置,Vite 也会生成如下结构的产物:
-
index.css->index.d4e5f6.css -
main.js->main.b1c2d3.js
3. 实现逻辑的底层原理
无论是 Webpack 还是 Vite,实现内容哈希的逻辑都可以解构为以下几个步骤:
-
内容读取:读取最终生成的代码字符串或二进制流。
-
摘要计算:使用加密哈希算法(通常是 MD4 或 MD5,Webpack 5 默认使用更快的算法)对内容生成摘要。
-
截取字符:为了文件名美观和长度限制,通常截取前 8 到 16 位。
-
映射替换:将计算出的哈希值替换掉配置文件中的
[hash]或[contenthash]占位符。 -
HTML 注入:由于文件名变了,构建工具会自动更新 HTML 文件中的
<script src="...">和<link href="...">标签,确保引用正确。
注意: 在配置哈希时,一定要配合
HtmlWebpackPlugin(Webpack)或 Vite 默认的 HTML 处理,否则你手动写的 HTML 路径将无法找到带哈希的新文件。
你想了解如何针对第三方库(如 React 或 Vue)进行长效缓存优化(即代码分割 Runtime 和 Vendor)吗?