目录一、先建立认知持久化缓存是什么核心目标实现前提二、Webpack 持久化缓存的核心Hash 策略Webpack 提供三种 Hash1. hash2. chunkhash3. contenthash推荐面试亮点说法三、只有 Hash 还不够还需要解决这几个问题问题一runtime 变化污染业务代码 hash什么是 runtime问题解决方案单独抽离 runtime问题二模块 ID 不稳定导致 hash 变化问题解决方案Webpack 4 的对应方案问题三第三方库和业务代码混在一起问题解决方案SplitChunksPlugin 分离问题四CSS hash 随 JS 变动问题解决方案抽离 CSS 文件四、服务器端配合HTTP 缓存头带 hash 的静态资源index.html 不能缓存面试亮点说法五、完整配置示意六、面试回答精彩模板版本一标准版1 分钟版本二进阶版更有深度七、整体知识结构梳理八、高分总结这道题很多人会和离线缓存混淆。要先分清楚离线缓存Service Worker让用户断网也能访问持久化缓存浏览器 HTTP 缓存 文件 hash 命名让用户二次访问时不重复下载没变化的资源这道题考察的是后者。一、先建立认知持久化缓存是什么核心目标文件内容没变浏览器就不重新下载直接用本地缓存。实现前提浏览器的 HTTP 缓存策略强缓存可以让资源长期缓存在本地。但有一个问题如果文件名不变浏览器永远用缓存即使服务器文件已经更新如果每次都改文件名缓存就完全失效解决方案把文件内容的 hash 值加入文件名。内容变了 → 文件名变了 → 浏览器识别为新资源 → 重新下载。内容没变 → 文件名不变 → 浏览器命中缓存 → 不下载。这就是 Webpack 持久化缓存的核心思路。二、Webpack 持久化缓存的核心Hash 策略Webpack 提供三种 Hash1.hashoutput: { filename: [name].[hash].js }整个项目共用一个 hash任何一个文件变动所有文件的 hash 全部改变缓存利用率极低不推荐2.chunkhashoutput: { filename: [name].[chunkhash].js }同一个 chunk 内的文件共用 hash某个 chunk 变了只有它对应的文件 hash 改变但 CSS 和 JS 同属一个 chunk改了 JSCSS 的 hash 也会变中等方案但不够精细3.contenthash推荐output: { filename: [name].[contenthash].js }基于文件内容本身计算 hash只有这个文件内容真正变了hash 才变CSS 和 JS 互不影响最佳方案面试亮点说法三种 hash 的核心区别在于 hash 的计算维度不同。contenthash最精准因为它基于文件内容本身一个文件改了不影响其他文件的 hash最大化缓存命中率所以在生产环境中应该优先使用contenthash。三、只有 Hash 还不够还需要解决这几个问题仅靠 contenthash 还不能完全实现持久化缓存还有几个问题需要解决。问题一runtime 变化污染业务代码 hash什么是 runtimeWebpack 的 runtime 是一段负责模块加载、依赖解析的引导代码它内部记录了所有模块的 ID 和路径映射。问题每次新增一个模块runtime 里的映射关系就会改变导致 runtime 的 hash 变化。如果 runtime 和业务代码打包在一起业务代码的 hash 也会跟着变缓存就失效了。解决方案单独抽离 runtimeoptimization: { runtimeChunk: single }这样 runtime 单独打成一个小文件业务代码和第三方库的 hash 就不会被它污染。问题二模块 ID 不稳定导致 hash 变化问题Webpack 默认用数字自增作为模块 ID。新增或删除一个模块所有模块的 ID 会重新排列导致大量文件 hash 变化缓存失效。解决方案// Webpack 5 optimization: { moduleIds: deterministic }deterministic模式会根据模块路径生成稳定的 hash ID新增/删除模块不会影响其他模块的 ID。Webpack 4 的对应方案const { HashedModuleIdsPlugin } require(webpack) plugins: [ new HashedModuleIdsPlugin() ]问题三第三方库和业务代码混在一起问题业务代码频繁改动第三方库React、Vue、Lodash几乎不变。如果打包在一起业务代码一变第三方库的 hash 也变用户每次都要重新下载第三方库。解决方案SplitChunksPlugin 分离optimization: { splitChunks: { cacheGroups: { vendors: { test: /[\\/]node_modules[\\/]/, name: vendors, chunks: all, priority: 10 } } } }把第三方库单独打成vendors.contenthash.js只要依赖没变这个文件的 hash 就不变浏览器永远命中缓存。问题四CSS hash 随 JS 变动问题如果用style-loader把 CSS 内联到 JSCSS 的缓存就跟着 JS 走完全没有独立的缓存控制。解决方案抽离 CSS 文件const MiniCssExtractPlugin require(mini-css-extract-plugin) plugins: [ new MiniCssExtractPlugin({ filename: [name].[contenthash].css }) ]CSS 独立成文件独立计算 contenthashJS 变了不影响 CSS 缓存CSS 变了也不影响 JS 缓存。四、服务器端配合HTTP 缓存头光有 Webpack 的 hash 还不够还需要服务器配置正确的缓存策略。带 hash 的静态资源文件名本身就包含了版本信息可以设置超长强缓存Cache-Control: max-age31536000, immutableimmutable表示文件内容不会变浏览器可以放心使用缓存不需要发 304 请求验证。index.html不能缓存HTML 是入口文件引用了带 hash 的 JS/CSS 地址。如果 HTML 也被强缓存用户永远拿不到最新的资源地址。Cache-Control: no-cacheno-cache表示每次都要向服务器确认是否有更新但实际文件没变时服务器返回 304几乎没有额外消耗。面试亮点说法持久化缓存不只是 Webpack 的事还需要服务器配合。带 hash 的静态资源可以设置超长强缓存因为内容变了文件名就变了但 HTML 一定不能强缓存因为它是所有资源的入口必须保证用户拿到最新版本。五、完整配置示意把上面所有方案整合在一起const MiniCssExtractPlugin require(mini-css-extract-plugin) module.exports { mode: production, output: { filename: [name].[contenthash:8].js, // JS 用 contenthash chunkFilename: [name].[contenthash:8].js // 异步 chunk 也用 contenthash }, optimization: { moduleIds: deterministic, // 稳定模块 ID runtimeChunk: single, // 抽离 runtime splitChunks: { cacheGroups: { vendors: { test: /[\\/]node_modules[\\/]/, name: vendors, chunks: all } } } }, plugins: [ new MiniCssExtractPlugin({ filename: [name].[contenthash:8].css // CSS 独立 contenthash }) ] }六、面试回答精彩模板版本一标准版1 分钟Webpack 实现持久化缓存核心是用contenthash给文件命名。内容不变文件名不变浏览器就命中缓存内容变了文件名自然变浏览器识别为新资源。但只有 hash 还不够还需要做几件事用runtimeChunk: single把 runtime 单独抽出去避免它的变化污染业务代码的 hash用moduleIds: deterministic让模块 ID 稳定防止新增模块导致无关文件 hash 变化用SplitChunks把第三方库单独打包因为它们不常变动分离后缓存命中率更高同时用MiniCssExtractPlugin抽离 CSS让 JS 和 CSS 各自独立计算 hash。服务端配合上带 hash 的静态资源设置长期强缓存HTML 文件设置 no-cache保证入口始终是最新的。版本二进阶版更有深度持久化缓存的核心逻辑是用文件内容的 hash 作为文件名的一部分让浏览器自动区分新旧资源。Webpack 提供了三种 hash但只有contenthash是基于文件内容本身计算的最精准生产环境应该优先用它。但光有 contenthash 还不够还有几个容易被忽视的点第一Webpack 的 runtime 代码记录了模块 ID 映射每次构建都可能变要用runtimeChunk: single单独抽出来防止它污染其他文件的 hash。第二默认的模块 ID 是数字自增新增一个模块会导致后续所有模块 ID 重排hash 大面积失效要配置moduleIds: deterministic让 ID 稳定。第三第三方库应该用 SplitChunks 分离出来因为它们变动频率极低单独打包后用户几乎永远能命中缓存。CSS 也要用 MiniCssExtractPlugin 抽离成独立文件让它和 JS 各自独立的 hash互不影响。最后服务端配合同样关键带 hash 的资源设置max-age31536000, immutableHTML 设置no-cache因为 HTML 是入口必须保证用户拿到最新的资源地址。七、整体知识结构梳理持久化缓存实现路径 │ ├── 核心机制contenthash 文件命名 │ ├── hash不推荐项目级任何变动全部失效 │ ├── chunkhash中等chunk 级同 chunk 共享 │ └── contenthash推荐文件内容级最精准 │ ├── 配套优化 │ ├── runtimeChunk: single → 抽离 runtime 防污染 │ ├── moduleIds: deterministic→ 稳定模块 ID │ ├── SplitChunks → 第三方库独立缓存 │ └── MiniCssExtractPlugin → CSS 独立 hash │ └── 服务端配合 ├── 静态资源Cache-Control: max-age31536000, immutable └── HTMLCache-Control: no-cache八、高分总结Webpack 持久化缓存的本质是通过contenthash让文件名和内容绑定配合浏览器 HTTP 强缓存实现内容不变不下载的目标。面试里答得精彩的关键不是只说用 contenthash而是能进一步讲清楚为什么 contenthash 比 hash、chunkhash 更好runtime 和模块 ID 不稳定会怎么破坏 hash第三方库为什么要单独分包服务端 HTML 为什么不能强缓存把这条链路讲通就从会配置升级到了理解机制。