前言
当我们重新部署前端项目的时候,如果用户一直停留在页面上并未刷新使用,会存在功能使用差异性的问题,因此,当前端部署项目后,需要提醒用户有去重新加载页面。
技术框架
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 setTimeoutId
let 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>[^"']+)/gm
const 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 ***pareScript = async (oldArr, newArr) => {
console.log('******************pareScript**************')
console.log("🚀 ~ file: auto-update.js:37 ~ ***pareScript ~ oldArr, newArr:", oldArr, newArr)
const base = oldArr.length
console.log("🚀 ~ file: auto-update.js:36 ~ ***pareScript ~ base:", base)
// 去重
const arr = Array.from(new Set(oldArr.concat(newArr)))
console.log("🚀 ~ file: auto-update.js:39 ~ ***pareScript ~ 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 ***pareScript(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:编写模板 ***Notify.vue 文件
<template>
<div class="***_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>
.***_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 使用组件***Notify
<template>
<div id="app">
<router-view />
</div>
</template>
<script>
// 引入***Notify组件
import ***Notify from "@/***ponents/***mon/***Notify/index.vue"
export default {
name: 'App',
***ponents: {
***Notify, // 注册组件
},
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("***Notify", {
// 使用自定义组件
ref: "***Notify",
props: {
msg: msg,
version: version
},
}),
customClass:'versionNotifyStyle', //自定义类名
})
})
},
},
}
</script>
Step4:在 main.js 内使用
// 引入自动更新提醒
import autoUpdate from './auto-update'
// 非生产环境使用
process.env.VUE_APP_ENV !== 'production' && autoUpdate.start()