最近使用 Vue3 技术把公司的一个大的 Web 前端项目进行了一个升级。升级过程坎坷不断,但最终是成功了,具体升级相关的另开篇讲,这里主要讲下使用 Vite 作为打包工具之后,每次打包上线完,用户客户端页面路由跳转报错,必须要主动刷新页面才可获取最新资源的问题。
刚刚说了,导致这个问题的具体原因,是因为在 vue-router 里使用了页面模块懒加载,通过 Vite 打包之后,不同于 Webpack ,生成的 dist/assite 文件夹中,会有很多个小的 js 文件,而 Webpack 打包会生成一个大的 app.js 文件。这样的好处是,大大提升了项目首页开屏的速度!但是缺点上完线之后就暴露出来了,由于上线打包,使用自动化工具,会把之前的项目文件清空,然后把 build 之后的文件移动过来,相当于整个做了一个替换!所以如果客户端不主动刷新浏览器,用户的本地项目还是上一个版本的,当它需要进行跳转页面或者引入其他的包时,自然在服务器上找不到之前的包了 -_-!
针对这个问题,我想了以下几种解决的办法:
这种场景就好像是 App 版本更新似的,每次上线都要定义一个版本,这个版本通过后端接口进行修改和获取,但是需要前端和后端一起配合,以及一整套流程控制,数据表维护等。实现起来相对来说较麻烦,需要一定时间。
这种相当于第一种方案的简化版(不需要后端配合)。只需要在打包的时候,自己生成一个 json 文件来记录本次打包的版本号就可以了,这个版本号不一定要按真实的版本来,只需要是唯一的就行了,换句话说,可以生成一个随机码,uaaId 、随机数、时间戳都可以。这种可以完全自动化,弄好之后,不需要人工去改什么了。
简化版,简单粗暴,直接通过记录页面跳转的时间,来判断是否刷新,因为上线一般是晚上进行,所以第二天,基本也都能执行刷新动作了,但是不够及时。万一不够时间,还是会有这个问题。
作为一个补充。
这个 Vue3 中我没有实现过,只是作为一个思路,如果能在路由 push 报错的时候,捕捉错误并且刷新页面,貌似也是可行。
最终,我选择了第二种方法,解决了这个问题,相对来说不需要后端配合也减少了工程量。以下是解决的具体实现:
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 > 文件执行结束!')
通过 node 在打包完成以后生成一个 version.json 文件。同时生成一个随机码作为区分版本的 ID。
"postbuild": "node ./build/build.js",
此指令会在 build 之后自动被调用执行。具体不明白可参看这篇文章:
package.json中的scripts命令解析
import axios from 'axios'
// 路由拦截
router.beforeEach((to, from, next) => {
// ... 其他逻辑
// 检查版本更新
if (from.path !== '/') {
checkAppNewVersion()
}
})
// 检查服务端是否已经更新,如果更新刷新页面
async function checkAppNewVersion() {
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 = Local.get(localKeys.APP_VERSION)
if (localVersion && localVersion !== version) {
Local.set(localKeys.APP_VERSION, version)
window.location.reload()
}
Local.set(localKeys.APP_VERSION, version)
}
这里之所以是在 beforeEach 钩子里处理,是有原因的,afterEach 和 beforeResolve 是在 beforeEach 执行之后执行,当服务器文件已经更新了,然后本地还没更新的情况下,是加载不到即将要跳转的页面文件的。
beforeResolve 执行的时候,说明所需要加载的组件已经加载好了,如果加载不到文件的话,这里已经开始抛错了。
以上基本已经处理完了,服务端上线了新的文件之后,本地页面跳转的时候根据拿到的 version 和本地的进行对比,如果不同就先刷新页面,重新获取最新资源文件。
但是这样做还是有个小问题:
当用户在登录页时,服务端更新了版本,所以用户在输入用户名和密码之后,点击登录,显示登录成功,正常是应该直接跳到首页,但是此时跳转前检测到版本更新,所以会原地刷新一下获取最新资源,导致页面又恢复到了登录之前的状态,用户需要重新登录。这点体验很不好。所以后期加了以下处理:
// 监听页面打开显示
document.addEventListener('visibilitychange', function () {
// console.log('show ===>', document.visibilityState, !document.hidden)
if (!document.hidden) {
checkAppNewVersion()
}
})
增加了页面显示的监听,在用户打开页面时,先获取版本更新一下,这样就可以避免这个问题。
感谢阅读,如果这篇文章对你有帮助的话,帮忙点个赞吧,谢谢!