webpack中css哈希必须用contenthash:hash每次构建都变,chunkhash受js影响,仅contenthash按内容生成;vite需配置assetfilenames而非entryfilenames;html中手写link会绕过哈希,须移入js导入或用插件动态替换;?v=参数在cdn忽略查询字符串时失效,推荐文件名含contenthash。

Webpack 中 CSS 文件名哈希必须用 contenthash,不是 hash 或 chunkhash
用 hash 会导致每次构建都变,哪怕 CSS 没改,用户也会重复下载;chunkhash 在 JS 和 CSS 同属一个 chunk 时会相互影响——JS 改了,CSS 的 hash 也变,缓存失效。只有 contenthash 是按文件内容计算的,改哪一版 CSS,就只变那一版的文件名。
关键配置在 mini-css-extract-plugin 的 filename 和 chunkFilename 选项里:
filename: 'css/[name].[contenthash:8].css'chunkFilename: 'css/[name].[contenthash:8].css'
同时确保 output.filename 和 output.chunkFilename 不干扰 CSS 输出(它们管 JS);HtmlWebpackPlugin 必须开启 inject: true,否则生成的带哈希的 href 不会写进 HTML。
Vite 配置 assetFileNames 才能给 CSS 加哈希,entryFileNames 不够
Vite 默认对 JS 用 [name].[hash].js,但 CSS 属于 asset,得单独配 build.rollupOptions.output.assetFileNames。只改 entryFileNames 对 CSS 无效,打包后仍是 style.css。
正确写法(vite.config.ts):
build: {
rollupOptions: {
output: {
assetFileNames: 'assets/[name].[hash].css',
entryFileNames: 'assets/[name].[hash].js',
chunkFileNames: 'assets/[name].[hash].js'
}
}
}
如果开了 cssCodeSplit: true(默认开),还要确保 chunkFileNames 也覆盖 CSS 分包逻辑,否则异步加载的 CSS 可能没哈希。
HTML 里手写的 <link href="xxx.css"> 会彻底绕过哈希机制
Webpack/Vite 只处理 JS 中 import './style.css' 的路径,不会扫描 HTML 里的硬编码 href。你配再全的哈希,只要 HTML 里还留着 href="/static/style.css",浏览器就永远请求旧文件。
解决方式只有两个:
- 删掉所有手写
<link>,把样式引入移到 JS 入口里:import './styles/main.css' - 如果必须保留 HTML 引入(比如第三方主题、CDN CSS),就得用构建插件读取产出的
manifest.json或dist/index.html,动态替换href值——别手动维护
检查最终生成的 dist/index.html,确认 <link href=".../main.a1b2c3d4.css"> 这种才是生效的,本地开发时看到的 style.css 不作数。
服务端或 CDN 忽略查询参数时,?v=xxx 会完全失效
加版本号参数是妥协方案,但 Cloudflare、阿里云 CDN 等默认开启 “Ignore query string”,style.css?v=2.3.0 和 style.css?v=2.3.1 被当成同一个 URL 缓存。这时候浏览器发了新请求,CDN 却直接返回旧响应。
真正可靠的破缓存方式只有两种:
- 文件名含
contenthash(推荐,URL 天然不同) - 服务端返回正确
ETag+Cache-Control: must-revalidate,让浏览器发条件请求并收到 304
用 ?v= 的唯一合理场景是:没有构建工具的小项目,且你能控制 CDN 关掉 “Ignore query string” —— 否则不如不加。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











