问题描述:
vite项目中,尽管每次只是部分文件更新,但构建之后,由于组件依赖关系,只要其中一个文件内容发生变化并且文件使用了[hash]
配置(相当于内容hash),其他文件的导入路径都会更新,导致内容变化,文件指纹也会变化。
解决思路:
vite 仓库相关 issue 和讨论 github.com/vitejs/vite...
主要是入口文件(entryFileNames
)的路径名变化,影响绝大部分模块。 所以想要实现增量更新,这个入口文件名最好固定。
- 固定入口文件会导致浏览器缓存,整个应用都不再更新。
- 虽然可以在加载入口文件时加一个 query 时间戳。但是动态的组件中,对这个入口文件引用是不带query的,控制台会报错,页面会白屏。
- 要解决上面问题,就需要所有引用入口文件的地方,全部带上时间戳。可以借助 importmap 进行映射。实现一个 plugin,自动在入口文件后面拼接时间戳,同时在 head 头中注入 importmap。
ts
entryFileNames: "assets/[name].main.js"
chunkFileNames: "assets/[name].[hash].js"
ts
import type { Plugin } from 'vite'
export default function viteImportMapPlugin(): Plugin {
return {
name: 'html-entry-version-with-importmap',
transformIndexHtml(html) {
const version = Date.now()
// 1. 给入口文件加 query
const updatedHtml = html.replace(
/(<script[^>]+src=")([^"]*main)(.js)"/,
`$1$2.js?v=${version}"`
)
// 2. 在 HTML 中插入 importmap
// 提取 main.js 路径(不带 query)
const match = html.match(/<script[^>]+src="([^"]*main).js"/)
if (!match) return updatedHtml
const entryPath = match[1] + '.js'
const entryWithVersion = `${entryPath}?v=${version}`
const importmap = `\n <script type="importmap">\n {\n "imports": {\n "${entryPath}": "${entryWithVersion}"\n }\n }\n </script>\n`
// 3. 把 importmap 插入到 </head> 前
return updatedHtml.replace('</head>', `${importmap}\n</head>`)
}
}
}
可能存在的问题:
- importmap 兼容性,如果浏览器不支持,需要使用 es-module-shims 进行 polyfill。并且这个文件需要写在头部,使用
async
属性,因为 importmap 是静态映射表,加载完成后,无法热更新。
ts
<script async src="/es-module-shims.min.js"></script>
- 不适合多入口文件(应该可搞),不支持 SSR等。
- 观察一段时间看看是否有副作用。
其他优化
- 利用
manualChunks
进行三方包分割