如何解决前端上线之后用户页面不刷新的问题

前言

最近使用 Vue3 技术把公司的一个大的 Web 前端项目进行了一个升级。升级过程坎坷不断,但最终是成功了,具体升级相关的另开篇讲,这里主要讲下使用 Vite 作为打包工具之后,每次打包上线完,用户客户端页面路由跳转报错,必须要主动刷新页面才可获取最新资源的问题。

起因

刚刚说了,导致这个问题的具体原因,是因为在 vue-router 里使用了页面模块懒加载,通过 Vite 打包之后,不同于 Webpack ,生成的 dist/assite 文件夹中,会有很多个小的 js 文件,而 Webpack 打包会生成一个大的 app.js 文件。这样的好处是,大大提升了项目首页开屏的速度!但是缺点上完线之后就暴露出来了,由于上线打包,使用自动化工具,会把之前的项目文件清空,然后把 build 之后的文件移动过来,相当于整个做了一个替换!所以如果客户端不主动刷新浏览器,用户的本地项目还是上一个版本的,当它需要进行跳转页面或者引入其他的包时,自然在服务器上找不到之前的包了 -_-!

方案

针对这个问题,我想了以下几种解决的办法:

1. 通过前后端配合定义一个版本接口

这种场景就好像是 App 版本更新似的,每次上线都要定义一个版本,这个版本通过后端接口进行修改和获取,但是需要前端和后端一起配合,以及一整套流程控制,数据表维护等。实现起来相对来说较麻烦,需要一定时间。

2. 前端打包生成一个json文件记录版本,路由跳转的时候,通过请求服务端版本号与本地版本号是否一致,决定是否刷新

这种相当于第一种方案的简化版(不需要后端配合)。只需要在打包的时候,自己生成一个 json 文件来记录本次打包的版本号就可以了,这个版本号不一定要按真实的版本来,只需要是唯一的就行了,换句话说,可以生成一个随机码,uaaId 、随机数、时间戳都可以。这种可以完全自动化,弄好之后,不需要人工去改什么了。

3. 记录路由跳转时间,每隔6个小时刷新路由

简化版,简单粗暴,直接通过记录页面跳转的时间,来判断是否刷新,因为上线一般是晚上进行,所以第二天,基本也都能执行刷新动作了,但是不够及时。万一不够时间,还是会有这个问题。

4. 退出登录的时候刷新页面

作为一个补充。

5. 重写push方法,捕捉错误

这个 Vue3 中我没有实现过,只是作为一个思路,如果能在路由 push 报错的时候,捕捉错误并且刷新页面,貌似也是可行。

解决

最终,我选择了第二种方法,解决了这个问题,相对来说不需要后端配合也减少了工程量。以下是解决的具体实现:

1. 项目根目录下新建 build/build.js

如何解决前端上线之后用户页面不刷新的问题_第1张图片

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 > 文件执行结束!')

通过 node 在打包完成以后生成一个 version.json 文件。同时生成一个随机码作为区分版本的 ID。

2. 在 package.json 中增加指令

如何解决前端上线之后用户页面不刷新的问题_第2张图片

package.json

"postbuild": "node ./build/build.js",

此指令会在 build 之后自动被调用执行。具体不明白可参看这篇文章:
package.json中的scripts命令解析

3. 在 router/index.js 中处理版本更新

router/index.js

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 执行之后执行,当服务器文件已经更新了,然后本地还没更新的情况下,是加载不到即将要跳转的页面文件的。
如何解决前端上线之后用户页面不刷新的问题_第3张图片
beforeResolve 执行的时候,说明所需要加载的组件已经加载好了,如果加载不到文件的话,这里已经开始抛错了。

以上基本已经处理完了,服务端上线了新的文件之后,本地页面跳转的时候根据拿到的 version 和本地的进行对比,如果不同就先刷新页面,重新获取最新资源文件。

但是这样做还是有个小问题:
当用户在登录页时,服务端更新了版本,所以用户在输入用户名和密码之后,点击登录,显示登录成功,正常是应该直接跳到首页,但是此时跳转前检测到版本更新,所以会原地刷新一下获取最新资源,导致页面又恢复到了登录之前的状态,用户需要重新登录。这点体验很不好。所以后期加了以下处理:

// 监听页面打开显示
document.addEventListener('visibilitychange', function () {
  // console.log('show ===>', document.visibilityState, !document.hidden)
  if (!document.hidden) {
    checkAppNewVersion()
  }
})

增加了页面显示的监听,在用户打开页面时,先获取版本更新一下,这样就可以避免这个问题。

感谢阅读,如果这篇文章对你有帮助的话,帮忙点个赞吧,谢谢!

你可能感兴趣的:(Vue,前端,javascript,vue.js)