产生原因:
使用vite对项目进行打包,对 js 和 css 文件使用了 chunkhash 进行了文件缓存控制,但是项目的index.html文件在版本频繁迭代更新时,会存在被浏览器缓存的情况。
在发版后,如果用户不强制刷新页面,浏览器会使用旧的index.html文件,在跳转页面时会向服务器端请求了上个版本 chunkhash 的 js 和 css 文件,但此时的文件已经在版本更新时已替换删除了,最终表现为页面卡顿,控制台报错 404。
解决方案:
方案1
服务器发版时,上一个版本的asset文件包不删除
缺点:随着频繁发版,服务器端前端项目文件会越来越多,浪费空间;若旧页面的接口涉及到参数改动等,会引起报错;流水线使用 docker 打包部署会变得非常麻烦。
方案2:
在每一次打包时,前端生成一个version版本号,路由跳转时请求服务端的version.json数据,与本地缓存的版本号做对比,从而监控版本迭代变化,实现自动更新。
缺点:在实现自动更新的过程,没办法跳到指定的页面,需要刷新后再重新点击。但这个体验感可以忽略不计。
具体操作步骤如下:
- 在项目目录下创建build文件
// build\build.js
console.log("build > 文件开始执行!")
const fs = require("fs")
const path = require("path")
function getRootPath(...dir) {
return path.resolve(process.cwd(), ...dir)
}
const runBuild = async () => {
try {
const OUTPUT_DIR = "dist"
const VERSION = "version.json"
const versionJson = {
version: "V_" + Math.floor(Math.random() * 10000) + Date.now()
}
fs.writeFileSync(getRootPath(`${OUTPUT_DIR}/${VERSION}`), JSON.stringify(versionJson))
console.log(`version file is build successfully!`)
} catch (error) {
console.error("version build error:\n" + error)
process.exit(1)
}
}
runBuild()
console.log("build > 文件执行结束!")
- 在
package.json
中配置"postbuild": "node ./build/build.js"
,不同环境的构建都要配置
"postbuild": "node ./build/build.js",
"postbuild:test": "node ./build/build.js",
"postbuild:pre": "node ./build/build.js",
- 封装本地缓存版本号的读写方法
export const getAppVersion = () => {
return localStorage.getItem(CacheKey.APP_VERSION) as string
}
export const setAppVersion = (version: string) => {
localStorage.setItem(CacheKey.APP_VERSION, version)
}
- 写一个公共方法,用于检验服务端版本号是否更新,如有更新则自动刷新
// 检查服务端是否已经更新,如果更新刷新页面
export async function checkAppNewVersion() {
if (process.env.NODE_ENV === "development") {
return
}
// 带参数是为了拿到最新的数据
const url = `/version.json?t=${Date.now()}`
let res = null
try {
res = await axios.get(url)
} catch (err) {
console.error("checkAppNewVersion error: ", err)
}
if (!res) return
const version = res.data.version
const localVersion = getAppVersion()
if (localVersion && localVersion == version) {
return
}
if (localVersion && localVersion !== version) {
ElMessage({
message: "发现新内容,自动更新中...",
type: "success",
showClose: true,
duration: 1500,
onClose: () => {
setAppVersion(version)
window.location.reload()
}
})
}
setAppVersion(version)
}
- 在
router.beforeEach
钩子中,请求服务器version版本号
router.beforeEach(async (to, _from, next) => {
await checkAppNewVersion()
...
})
- 为了防止当用户在登录页时,服务端更新了版本,所以用户在输入用户名和密码之后,点击登录,显示登录成功,正常是应该直接跳到首页,但是此时跳转前检测到版本更新,所以会原地刷新一下获取最新资源,导致页面又恢复到了登录之前的状态,用户需要重新登录。可以在
App.vue
中加入这个监听
// 监听页面打开显示
document.addEventListener('visibilitychange', function () {
// console.log('show ===>', document.visibilityState, !document.hidden)
if (!document.hidden) {
checkAppNewVersion()
}
})
注意:
如果是采用pnpm打包,需要在.npmrc
中配置:
enable-pre-post-scripts=true
网友评论