前言
当我们重新部署前端项目的时候,如果用户一直停留在页面上并未刷新使用,会存在功能使用差异性的问题,因此,当前端部署项目后,需要提醒用户有去重新加载页面。
技术框架
vue、js、webpack
解决方案
根据打完包之后生成的script src 的hash值去判断
,每次打包都会生成唯一的hash值,只要轮询去判断不一样了,那一定是重新部署了轮询(20s、自己设定时间)服务器的index.html 文件,将新的script数组与旧script数组比较数组内容是否一致(可以将新旧数组拼接后去重,若去重后的数组长度,与旧数组长度不一样,则说明重新部署了),若新旧数组不一致则通知用户刷新页面通过监听visibilitychange事件,在页面隐藏时停止轮询,页面显示立马检测一次更新检测到更新后,停止轮询
(感兴趣的还可去看方案一:编译项目时动态生成一个记录版本号的文件,轮询请求该文件。)
效果
页面右下角提示更新:
代码实现
Step1:在src目录下封装 auto-update.js
/* * @Description: 自动更新 */// const timeData = 60 * 1000 // 检查间隔时间const timeData = 20 * 1000 // 检查间隔时间let hidden = false // 页面是否隐藏let setTimeoutIdlet needTip = true // 默认开启提示let oldScript = []let newScript = []const getHtml = async () => { const html = await fetch('/').then(res => res.text()) //读取index html return html}// const scriptReg = /<script.*src=["'](?<src>[^"']+)/gmconst parserScript = (html) => { const reg = new RegExp(/<script(?:\s+[^>]*)?>(.*?)<\/script\s*>/ig) //script正则 return html.match(reg) //匹配script标签}const init = async () =>{ const html = await getHtml() // console.log("? ~ file: auto-update.js:31 ~ init ~ html:", html) oldScript = parserScript(html) console.log("? ~ file: auto-update.js:30 ~ init ~ oldScript:", oldScript)}const compareScript = async (oldArr, newArr) => { console.log('***************compareScript**************') console.log("? ~ file: auto-update.js:37 ~ compareScript ~ oldArr, newArr:", oldArr, newArr) const base = oldArr.length console.log("? ~ file: auto-update.js:36 ~ compareScript ~ base:", base) // 去重 const arr = Array.from(new Set(oldArr.concat(newArr))) console.log("? ~ file: auto-update.js:39 ~ compareScript ~ arr:", arr, arr.length) let needRefresh = false // 如果新旧length 一样无更新 // 否则通知更新 if (arr.length !== base) { console.warn('更新了!!!!!!, arr.length !== base', arr.length !== base) needRefresh = true } // for (let i = 0; i < oldArr.length; i++) { // if (oldArr[i] !== arr[i]) { // needRefresh = true // console.warn('更新了!!!!!!, 值不等') // break // } // } return needRefresh}// 自动更新const autoUpdate = async () => { setTimeoutId = setTimeout(async () => { const newHtml = await getHtml() // console.log("? ~ file: auto-update.js:89 ~ newHtml:", newHtml) newScript = parserScript(newHtml) console.log("? ~ file: auto-update.js:79 ~ newScript:", newScript) // 页面隐藏了就不检查更新 if (!hidden) { const willRefresh = await compareScript(oldScript, newScript) console.log("? ~ file: auto-update.js:85 ~ setTimeoutId=setTimeout ~ willRefresh:", willRefresh) if (willRefresh && needTip) { // 延时更新,防止部署未完成用户就刷新空白 setTimeout(()=>{ // ----弹框确认---先简单点弹框确认,可以用注释内的,跳过右下角通知的内容(Step2、3) // const result = confirm('发现新版本,点击确定更新') // if (result) { // sessionStorage.setItem('version', version) // location.reload() // 刷新当前页 // } // -------------- //*****右下角通知提示 */ window.dispatchEvent( new CustomEvent("onmessageUpdate", { detail: { msg: "发现系统版本更新,请刷新页面~", version: version }, }) ) //******************* */ }, 10000) needTip = false // 关闭更新提示,防止重复提醒 } } console.log("? ~ file: auto-update.js:90 ~ autoUpdate ~ needTip: ", needTip) if (needTip) { console.warn('needTip autoUpdate'); autoUpdate() } }, timeData)}// 停止检测更新const stop = () => { if (setTimeoutId) { clearTimeout(setTimeoutId) setTimeoutId = '' }}// 开始检查更新const start = async () => { init() console.log('start0000000000') autoUpdate() console.log('start1111111111') // 监听页面是否隐藏 document.addEventListener('visibilitychange', () => { hidden = document.hidden console.log("? ~ file: auto-update.js:64 ~ document.addEventListener ~ hidden, needTip:", hidden, needTip) // 页面隐藏了就不检查更新。或者已经有一个提示框了,防止重复提示。 if (!hidden && needTip) { console.log('!!!checkupdate', '222222222'); autoUpdate() } else { stop() } })}export default { start }
Step2:编写模板 CnNotify.vue 文件
<template> <div class="cn_notify"> <div class="content"> <i class="el-icon-message-solid"></i> {{ msg }} </div> <div class="footer"> <el-row class="btnBox"> <el-button type="primary" @click="onSubmit">确认刷新</el-button> <el-button @click="cancle">我知道了</el-button> </el-row> </div> </div></template><script>export default { props: { msg: { type: String, default: '', }, }, data() { return {}; }, created() {}, methods: { // 点击确定更新 onSubmit() { location.reload() // 刷新 }, // 关闭 cancle() { this.$parent.close(); }, },};</script><style lang='scss' scoped>.cn_notify { .content { padding: 20px 0; } .footer { display: flex; justify-content: center; }}</style><style lang='scss'>.versionNotifyStyle { .el-notification__content { width: 280px !important; }}</style>
Step3:app.vue 使用组件CnNotify
<template> <div id="app"> <router-view /> </div></template><script>// 引入CnNotify组件import CnNotify from "@/components/common/CnNotify/index.vue"export default { name: 'App', components: { CnNotify, // 注册组件 }, mounted() { this.watchUpdate() }, methods: { watchUpdate() { window.addEventListener("onmessageUpdate", (res) => { console.log("? ~ file: App.vue:20 ~ window.addEventListener ~ res:", res) let msg = res.detail.msg, version = res.detail.version this.$notify({ title: "版本更新提示", duration: 0, position: "bottom-right", dangerouslyUseHTMLString: true, message: this.$createElement("CnNotify", { // 使用自定义组件 ref: "CnNotify", props: { msg: msg, version: version }, }), customClass:'versionNotifyStyle', //自定义类名 }) }) }, },}</script>
Step4:在 main.js 内使用
// 引入自动更新提醒import autoUpdate from './auto-update'// 非生产环境使用process.env.VUE_APP_ENV !== 'production' && autoUpdate.start()