一、什么是文件指纹?
核心思路(一句话)
文件指纹就是将资源内容映射为 Hash,并将 Hash 写入资源 URL,使内容变化产生新 URL,从而实现静态资源长期缓存。
资源内容 → Hash → 文件名 → 长期缓存 │ ┌──────────┴──────────┐ 内容不变 内容变化 ↓ ↓ URL不变 URL变化 ↓ ↓ 命中缓存 下载新资源底层原理
浏览器缓存最终是围绕URL 对应的资源。
因此:
/app.js即使服务器内容已经更新,只要浏览器仍然使用旧缓存,就存在版本不一致问题。
而:
/app.a81f3c.js /app.72bc91.js是两个不同 URL。
因此可以采用:
Cache-Control: public, max-age=31536000, immutable让带指纹的静态资源长期缓存。
真正解决问题的是“URL 随内容变化”,Hash 只是实现这个机制的手段。
二、Webpack 三种 Hash 的准确区别
注意:这里说的
hash、chunkhash、contenthash是 Webpack 输出文件名模板中的三种 Hash 占位符。不同 Webpack 版本的内部实现细节有所演进,因此面试时应抓住“依赖范围”和“缓存粒度”,不要简单说成三个 Hash 算法。
| 类型 | 技术含义 | 影响范围 | 缓存粒度 |
|---|---|---|---|
hash | 基于整个 Compilation 的构建结果计算的 Hash | 整个构建 | 最粗 |
chunkhash | 基于 Chunk 的内容及其相关依赖计算的 Hash | Chunk | 中等 |
contenthash | 基于最终输出资源内容计算的 Hash | 单个资源 | 最细 |
1.hash
整个 Compilation ↓ hash ↓ 多个输出资源可能使用同一个 Hash例如:
main.a81f3c.js vendor.a81f3c.js只要整个构建发生变化,使用该hash的资源文件名就可能一起变化。
问题:缓存粒度太粗。
2.chunkhash
Chunk A → chunkhash A Chunk B → chunkhash B例如:
main.a81f3c.js vendor.72bc91.js修改main所属 Chunk:
main.a81f3c.js → main.91def2.js vendor.72bc91.js → 通常保持不变它比hash更适合缓存,因为变化被限制在 Chunk 范围。
但它仍然是Chunk 粒度,不是最终输出文件粒度。
另外,
chunkhash在现代 Webpack 中已被标记为过时方向,官方推荐使用contenthash。
3.contenthash
最终输出资源 ↓ 资源内容 ↓ contenthash例如:
main.a81f3c.js main.72bc91.css如果只修改 CSS:
CSS内容变化 → CSS contenthash变化 JS最终内容没变 → JS contenthash保持不变因此 JavaScript 和 CSS 可以拥有独立的缓存生命周期。
这正是contenthash适合长期缓存的核心原因。
三、三种 Hash 的本质区别
不要死记“全局、Chunk、文件”三个词,面试直接抓住:
hash → 依赖整个构建 chunkhash → 依赖 Chunk contenthash → 依赖最终资源内容也就是:
Hash 的关键区别不是 Hash 算法不同,而是“
参与计算的依赖范围不同”。
最终目的都是:
依赖范围越精确 ↓ 无关资源越不容易被连带修改文件名 ↓ 缓存复用率越高四、为什么生产环境还需要关注 Runtime?
这是文件指纹题非常容易被忽略的高级问题。
Webpack 会生成一部分Runtime 代码,负责模块加载、模块映射、动态 Chunk 加载等运行时信息。
例如:
main.js vendor.js runtime.jsRuntime 中可能保存:
模块ID Chunk映射 异步Chunk文件名 加载逻辑因此当 Chunk 文件名发生变化时:
业务代码变化 ↓ Chunk Hash变化 ↓ Chunk文件名变化 ↓ Runtime中的Chunk映射也可能变化 ↓ Runtime内容变化 ↓ runtime.[contenthash].js变化于是可能出现:
vendor.abc.js ← 内容没变 runtime.123.js ← 变化 main.456.js ← 变化这样 Runtime 自己的缓存也会频繁失效。
五、如何降低 Runtime 对长期缓存的影响?
核心方案
optimization:{runtimeChunk:"single",splitChunks:{chunks:"all",},}把 Runtime 独立出来:
应用 │ ┌─────────┼─────────┐ ↓ ↓ ↓ runtime vendor business │ │ │ ↓ ↓ ↓ runtime.x vendor.x main.x这样不同代码的变化被隔离。
例如:
修改业务代码 ↓ business Hash变化 vendor ↓ 保持缓存 runtime ↓ 只有运行时映射确实发生变化时才变化但要注意
runtimeChunk: "single"不是保证 Runtime 永远不变。
如果 Chunk 文件名、模块关系、Chunk 映射发生变化,Runtime 仍然可能变化。
因此正确理解是:
独立 Runtime 是为了缩小变化传播范围,而不是让 Runtime 永久稳定。
六、生产环境完整缓存策略
HTML ↓ 短缓存 / 协商缓存 ↓ 引用带 Content Hash 的静态资源 ↓ contenthash + 代码分割 + splitChunks + runtimeChunk ↓ JS / CSS / 图片 / 字体 ↓ 长期强缓存其中:
HTML → 获取最新资源清单 带 Hash 的静态资源 → 内容不变就一直缓存 → 内容变化就通过新 URL 获取七、容易被问到的两个边界
Hash 是否绝对唯一?
不是。Hash 存在碰撞可能,只是实际工程中通过足够合理的 Hash 长度和算法,将碰撞概率控制在可接受范围。
文件指纹是否用于安全校验?
主要不是。文件指纹解决:
缓存失效 + 资源版本管理安全完整性校验属于其他机制,例如Subresource Integrity。
八、最终记忆模型
文件指纹 │ ├── 目的 │ └── 内容变化 → URL变化 → 长期缓存 │ ├── Hash粒度 │ ├── hash → Compilation │ ├── chunkhash → Chunk │ └── contenthash → 最终资源 │ └── 生产优化 ├── contenthash ├── splitChunks └── runtimeChunk满分答案
Webpack 文件指纹的核心,是让资源内容决定 URL,从而解决长期缓存和资源更新之间的矛盾。
三种 Hash 的区别在于参与计算的依赖范围:
hash → 整个 Compilation chunkhash → Chunk contenthash → 最终输出资源内容hash粒度最大,容易导致无关资源一起失效;chunkhash缩小到 Chunk;contenthash进一步缩小到具体资源,因此现代 Webpack 的长期缓存通常优先使用contenthash。
同时要注意 Webpack Runtime:它保存模块和 Chunk 的运行时映射,Chunk 文件名变化可能导致 Runtime 内容变化。因此生产环境通常结合:
contenthash + splitChunks + runtimeChunk将变化频率不同的资源隔离。
最终目标只有一个:资源没变就复用缓存,资源变了才生成新的 URL。