1.方案一:需要后端配合的方案
如果后端可以配合我们的话,可以使用websocket与后端进行实时通讯,前端部署完成后,后端发出一个通知。前端检测到通知消息后,进行提示。还可以再进一步优化下,使用EventSource,EventSource是HTML5中的一种新的API,用来实现服务器端向客户端推送事件。相比于常规的轮询方式,EventSource可以实现更加高效、低延迟的数据传输。
2.方案二:纯前端实现,json文件
在项目根目录放一个json文件,写入一个固定的key值然后打包的时候变一下,然后在代码中轮询去判断看有没有变化,有就提示。
具体做法是:public里放一个json文件,然后每次打包的时候 改变这个json,第一次拿到了json的数据存起来,然后轮询请求,直到json 数据上这个时间变化了,提示用户。
具体解决方案:
①.在在public文件夹下加入manifest.json文件,记录版本信息
②.前端打包的时候向manifest.json写入当前时间戳信息
③.在入口JS引入检查更新的逻辑,有更新则提示更新
这个做法有个问题就是:需要手动配置json文件,还需要打包的时候修改
3.方案三:纯前端实现,根据script src的hash值去判断
今天要详细说的就是这第三个方案。在工作当中都遇到了这么一个需求,我们的用户在站点上停留的时间可能比较长,而我们的系统每天都要进行很多次的更新。这就造成了一个问题,当我们的系统更新了之后,用户还停留在老的页面。我们则更希望用户能够收到一个提示,提示他应该刷新页面了。
实现的原理:
1.每隔一小段时间去请求一次服务器的首页,把它解析为纯文本
2.前端工程化会自动的给这个首页js和css文件,带上一个文件指纹,只要这些文件有变动,该文件指纹是一个哈希值,它也会跟着变,每隔一小段时间去分析页面中该hash值是否变化
3.只要发现JS有变动,说明这个系统的功能有所改动,那么我们就在页面上给他弹一个框,告诉他系统有更新。
适用场景:单页面应用且更新频率较高的场景,涉及到轮询(通过websocket实现也可以)
本地demo代码
yarn create vite@4.0.0
create-vite
# ···
# 选择 react,typescript 即可
cd vite-project-react
yarn
# 本地启动
yarn dev
# 本地产出
yarn build
# 预览生产环境
yarn preview
vite-project-react/dist/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
<script type="module" crossorigin src="/assets/index-fbe6c06b.js"></script>
<link rel="stylesheet" href="/assets/index-3fce1f81.css">
</head>
<body>
<div id="root"></div>
</body>
</html>
vite-project-react/src/auto-update.js
// 根据script src的hash值 实现前端更新后,单页面的地址部署刷新
let prevSrcs; // 上一次获取到的script链接地址
// 匹配过滤script标签
const scriptReg = /\<script.*src=["'](?<src>[^"']+)/gm;
/**
* 获取最新页面中的script链接地址
*/
async function extractNewScript() {
// 请求服务器
const html = await fetch('/?_timestamp=' + Date.now()).then((resp) =>
resp.text(),
);
let result = [];
let match;
while ((match = scriptReg.exec(html))) {
result.push(match.groups.src);
}
// 记录这一次请求首页当中,所有的JS的地址
return result;
}
/**
* 检查是否需要更新
* @returns boolean
*/
async function needUpdate() {
const nextScrs = await extractNewScript();
if (!prevSrcs) {
// 如果之前没有保存这个地址,把当前地址保存进去,第一次就不需要更新了
prevSrcs = nextScrs;
return false;
}
let result = false;
if (prevSrcs.length !== nextScrs.length) {
// 比较之前的地址和新的地址是否完全一样,只要有差异就需要更新
result = true;
}
for (let i = 0; i < prevSrcs.length; i++) {
if (prevSrcs[i] !== nextScrs[i]) {
result = true;
break;
}
}
prevSrcs = nextScrs;
return result;
}
const DURATION = 60 * 1000;
const PRODUCTION = process.env.NODE_ENV === 'production';
function autoRefresh() {
setTimeout(async () => {
const willUpdate = await needUpdate();
if (willUpdate) {
const result = confirm('页面有更新,点击确定刷新页面');
if (result) {
location.reload();
}
}
autoRefresh();
}, DURATION);
}
PRODUCTION && autoRefresh();
在main.ts文件中引入auto-update.js
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'
import './auto-update'
ReactDOM.render(<App />, document.getElementById('root') as HTMLElement).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)