前端项目发布后,如何使正在使用的用户更新为最新的版本?
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
1.背景
- 每次项目上线后异常监控总是零零散散报一些资源加载或者解析失败的告警
- 仔细对比chunk的hash值会发现已经是上一版本的js文件
- 为什么会出现这个问题呢也不难想到项目是单页应用页面使用懒加载分多个chunk打包首次只加载首页需要的js文件。如果在项目发布前用户已经在程序中并且还有未访问的页面此时我们重新发布上线了老的文件已经被覆盖用户再访问未访问过的页面时就会找不到资源导致白屏。
- 如果用户在发布前已经进入过其他页面被缓存在本地这样虽然不会导致白屏了但是也无法看到最新的效果。
- 在测试环境模拟一下点击一个未访问过的页面时确实白屏了报错信息也吻合
2.解决思路
- 导致这些问题的根本原因就是用户和程序都无法感知项目已上线无法做到立即刷新重新加载那我们就想办法在项目上线后通知页面进行刷新。
2.1运行中的项目如何感知到项目更新了
- 最容易想到的就是有一个存储版本号的地方我们去轮询是否和上一次请求到的版本一致如果不一致去做一些刷新的操作
- 既然是前端的项目最好还是我们自己去维护这个数据所以我选择在每次打包的时候生成一个存储当前时间戳的json文件存在dist目录下一起放到我们前端服务器上。
- 写一个最最简单的webpack插件帮我们实现生成json文件的功能
const pluginName = 'GenerateTimeJsonWebpackPlugin';
const fs = require('fs');
const path = require('path');
class GenerateTimeJsonWebpackPlugin {
apply(compiler) {
compiler.hooks.run.tap(pluginName, (compilation) => {
const filePath = path.resolve('build', 'timeStamp.json');
fs.writeFile(filePath, `${JSON.stringify({time: new Date().getTime()})}`, (err) => {
console.log(err);
});
});
}
}
module.exports = GenerateTimeJsonWebpackPlugin;
- 这样每次构建时就会多一个文件
2.2什么时机判断是否更新
- 轮询肯定是最耗性能的间隔太小浪费间隔太大又有白屏的风险
- 由于我们出现请求页面的时机总是在页面加载的时候所以选择加一个路由守卫在页面跳转前判断是否更新如果更新了就reload页面。
- 项目是用react进行开发的所以写个高阶组件来实现守卫的功能
import { Route } from 'react-router-dom';
import { useRef, useState } from 'react';
import axios from "axios";
const basePath = '/mp/138519745866498048/368331042878263296/credit-shop/';
// 路由守卫
const BeforeRouteEnter = (props) => {
const {path, exact, component} = props;
// 用来控制在时间戳未请求到时不加载路由对应的页面避免请求到老的资源
const [loading, setLoading] = useState(process.env.NODE_ENV === 'production');
// 用来存储当前项目的时间戳
const timesTamp = useRef('');
// 生产环境需要判断 当前访问的chunk是否已经更新
if (process.env.NODE_ENV === 'production') {
// 由于项目中统一封装过的axios做了很多错误的处理 为了不影响正常页面的功能 选择单独使用axios请求
axios
.get(basePath + `timeStamp.json?t=${new Date().getTime()}`)
.then(res => {
// 判断是否已经更新了代码 更新了就重新加载页面 请求新的资源
if (timesTamp.current && (timesTamp.current !== res.data?.time)) {
window.location.reload();
}
timesTamp.current = res.data?.time;
setLoading(false);
}).catch(err => {
setLoading(false);
});
}
return !loading ? <Route path={path} exact={exact} component={component}></Route> : ''
}
export default BeforeRouteEnter;
然后将Route组件改成我们自己封装的BeforeRouteEnter这样在每次跳转前都请求一次当前打包的时间戳判断是否和正在运行中的一致不一致就重载这样一来就解决了我们的问题~
有问题欢迎大家及时指出避免误导其他同学~