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>,
)

根据script src的hash值去判断页面是否更新