记录一次老平台改造通知用户刷新页面,纯前端实现
方案概述
背景
前端构建完上线,用户还停留还在老页面,用户不知道网页重新部署了,跳转页面的时候有时候js连接hash变了导致报错跳不过去,并且用户体验不到新功能。
客户打网页后,长时间不关闭对应标签页,也不刷新页面(在中后台管理项目挺常见的),且期间服务器页面有更新,经常出现
现状
目前我们或者合作商情况有如下几种情况:
- 目前路由hash模式,也就是锚点的形式,造成缓存问题;
- 在用户无感知的情况下升级平台,而用户因历史访问,无法获取最新的资源,造成用户体验差;
- 升级平台后,可能出现资源访问不到情况;
在现有的平台体系中,由于现在平台架构趋于一个成熟的阶段,基于改动最小、影响最小的原则的情况下,来设计适合目前平台的一套合理的方案解决这些问题;
问题本质
目前平台采用的hash的模式处理,也就是我们常说的锚点的形式,改方式的和history的不同是,histroy 的每次url变化都会像后端服务器请求,会重载页面index.html,这就是单页面history 需要在服务器上配置重定向到index.html的原因;而我们平台使用hash的形式,这种形式,只有在首次加载或者刷新页面的时候会重新请求服务器的index.html,相应的资源在这种情况下才会加载新的资源,url 地址变换并不会向history一样请求服务器,而是前端自主完成,所以在第一次访问完成之后,主要的js已经加载啦,后面操作都是根据主js的相关解析,请求相应的资源!所以在这种情况更新系统 造成资源是旧的或者资源请求不到的情况!
方案设计
前提
基于目前的平台框架,一切改动不能脱离现有的框架,对现有的入侵最小,其他的项目直接复用。
设计
-
打包生成的目录生成相应的项目指纹信息,也就是表示,可以是版本号、时间戳、哈希值等等;
-
路由钩子触发检测当前版本或者资源是否最新状态,来处理相应的逻辑,流程图如下
tips:
上面流程有点改动,改成点击菜单触发,这个在生产环境上大量测试上的改动。
实现
-
编写rollup(vite)插件
-
vite打包改造,下面可以根据项目需要是否考虑加入,viite本身的打包模式,文件发生变动,打出来的文件名hash 会发生变化,未改动的文件还是之前的文件名格式
tips
: 上面为了好看,当然也可以不改,默认输出的就是带hash的js文件; -
路由钩子处理 菜单上点击菜单处理
tips
: 为啥没有放到钩子里面处理,是因为点击菜单会有请求资源直接报错啦,不会走钩子,所以无法触发更新的接口,当然可以放到离开的钩子上处理,目前用的保守做法,点击前置触发。
- 服务器Nginx上配置,红色部分是为了重载index.html禁止缓存这个html信息
通过 nginx ,禁用缓存:
bash
# nginx.conf
location / {
root html;root /Volumes/wanglaibin/work/vite-project/dist
index index.html index.htm;
if ( $uri = '/index.html' ) { # disabled index.html cache
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
try_files $uri $uri/ /index.html;
}
直接通过 html meta 标签禁用缓存:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
</head>
</html>
其他补充
浏览器放大缩小,tab页激活触发
javascript
document.addEventListener('visibilitychange', async () => {
if (document.visibilityState === 'visible') {
console.log('当前会话被激活')
await versionCheck()
console.log('当前会话被激活-end')
}
})
浏览器聚焦触发
javascript
window.addEventListener('focus', async() => {
console.log('当前会话成为焦点')
await versionCheck()
console.log('当前会话成为焦点-end')
})
浏览器js资源加载失败
javascript
window.addEventListener('error', async (err) => {
const errTagName = (err?.target as any)?.tagName
if (errTagName === 'SCRIPT'){
console.log('js 资源加载失败')
await versionCheck()
console.log('js 资源加载失败-end')
}
},
true)
写在最后的话
其实具体到项目场景更为复杂,所以我们都是从问题入手、排查、方案等处理方式,不同的项目不同的处理方式,当然目前的对于当前来说更好的!当然可能过几个月就不一定啦!
抛出一个问题
其他我们平台还有比较棘手,目前已经处理,在测试过程中。我可以跟你们描述下问题就是可以一个浏览器多个tab可以登陆不同的账号,也可以登陆相同的账号,不同的账号不同的token,相同的账号也是不同的token 但是这些token写到session里面,页面内部也可以新开tab,内部新开的tab是相同的token,系统也要有一个token续期的功能,系统内部新开的tab 保证token同步不能失效,系统内部两个token ,一个接口token,一个刷新token,要用刷新token在失效前换取新的token之后同步到系统内部新开的tab中,但是但不能影响其他的账号登陆以及相同账号登陆的token。
或许为啥这样设计,没办法历史原因,现在就是后端不动,前端做token续期。