前端项目部署后如何提示用户版本更新详解
作者:不叫猫先生
前言
项目部署上线后,特别是网页项目,提示正在操作系统的用户去更新版本非常 important。一般我们都会用“刷新大法”来清理缓存,但是对于正在操作网页的用户,不造系统更新了,请求的还是老版本的资源。
为了确保用户能够及时获得最新的功能和修复的 bug,我们需要通知用户刷新页面获取最新的代码。
解决方案
每次打包时,都生成一个时间戳,作为系统的伪版本,放到JSON文件中,通过对比文件的响应头Etag判断是否有更新。具体步骤如下:
- 在public文件夹下加入manifest.json文件,里面存放两个字段:更新内容、更新时间戳
- 前端打包的时候向manifest.json写入当前时间戳信息
- 在入口文件main.js中引入检查版本更新的逻辑,有更新则提示更新。有两种方式提示提示用户:
路由守卫router.beforeResolve(Vue-Router为例)
,检查更新,对比manifest.json文件的响应头Etag判断是否有更新- 通过Worker轮询,检查更新,对比manifest.json文件的响应头Etag判断是否有更新。Worker线程并不影响其他线程的逻辑。
整体逻辑如下所示:
1、public目录下新建manifest.json
{ "timestamp":21312321311, "msg":"更新内容如下:\n--1.添加系统更新提示机制" }
2、写入当前时间戳到manifest.json
vue.config.js文件中
const { readFile, writeFile } = require('fs') // 获取路径 const filePath = path.resolve(`./public`, 'manifest.json') // 读取文件内容 readFile(filePath, 'utf8', (err, data) => { if (err) { console.error('读取文件时出错:', err) return } // 将文件内容转换JSON const dataObj = JSON.parse(data) //修改时间戳 dataObj.timestamp = new Date().getTime() // 将修改后的内容写回文件 writeFile(filePath, JSON.stringify(dataObj), 'utf8', err => { if (err) { console.error('写入文件时出错:', err) return } }) })
3、检查版本更新
新建 checkUpdate.js 文件
(1)初始化变量
import Worker from "./checkUpdate.worker.js"; import router from '../router' //上次的Etag let lastEtag = ''; //是否更新 let hasUpdate = false //创建worker线程 const worker = new Worker();
(2)检查版本更新
//检查版本更新 async function checkUpdate() { try { // 检测前端资源是否有更新 let response = await fetch(`/manifest.json?v=${Date.now()}`, { method: 'head' }) // 获取最新的etag let etag = response.headers.get('etag') hasUpdate = lastEtag && etag !== lastEtag lastEtag = etag } catch (e) { return Promise.reject(e) } }
其中let response = await fetch(/manifest.json?v=${Date.now()}, { method: 'head' })
,
使用 fetch 函数发起了一个 HTTP 请求,获取了指定资源的头信息(HTTP 头部)。其中 manifest.json 是要请求的资源,Date.now() 会生成当前时间的时间戳,作为查询参数 v 的值,这样可以避免浏览器缓存,强制获取最新的资源。请求方式为 HEAD,这意味着只请求资源的头部信息而不获取具体的内容。
let etag = response.headers.get('etag')
,从 HTTP 响应中获取了 ETag 头部信息,ETag 是服务器生成的资源唯一标识,用于检查资源是否发生了变化。具体比较逻辑如下:
1、客户端发起请求,请求中包含上次获取的资源的ETag。
2、服务器收到请求后,比较客户端提供的ETag与当前资源的ETag是否一致。
3、如果一致,则返回HTTP 304 Not Modified响应,表示资源未发生变化,客户端可以使用缓存的版本。
4、如果不一致,服务器返回最新的资源内容,同时更新ETag。
5、客户端收到响应后,更新本地缓存的资源内容和ETag。
(3)路由跳转检测版本更新
// 路由拦截 router.beforeEach(async (to, from, next) => { next() try { await checkUpdate() if (hasUpdate) { worker.postMessage({ type: 'destroy' }) location.reload() } } catch (e) {} })
(4)向worker线程发送检查版本更新逻辑
worker.postMessage({ type: 'check' })
(5)接收到 worker 线程数据更新
worker.onmessage = ({ data }) => { console.log(data,'data') if (data.type === 'hasUpdate') { hasUpdate = true confirmReload(data.msg, data.lastEtag) } }
(6)收到版本更新信息,进行弹框提示
收到版本更新信息后,先暂停轮询检查版本更新,点击确定按钮,则发送destory消息,点击取消按钮则发送recheck消息
async function confirmReload(msg = '', lastEtag) { worker && worker.postMessage({ type: 'pause' }) try { //弹框提示逻辑 } catch (e) { } }
checkUpdate.js 全部代码实现
import Worker from "./checkUpdate.worker.js"; import router from '../router' //上次的Etag let lastEtag = ''; //是否更新 let hasUpdate = false //创建worker线程 const worker = new Worker(); //检查版本更新 async function checkUpdate() { try { // 检测前端资源是否有更新 let response = await fetch(`/manifest.json?v=${Date.now()}`, { method: 'head' }) // 获取最新的etag let etag = response.headers.get('etag') hasUpdate = lastEtag && etag !== lastEtag lastEtag = etag console.log(lastEtag = etag,'lastEtag = etag') } catch (e) { return Promise.reject(e) } } async function confirmReload(msg = '', lastEtag) { worker && worker.postMessage({ type: 'pause' }) try { console.log('版本更新了') } catch (e) { } } // 路由拦截 router.beforeEach(async (to, from, next) => { next() try { await checkUpdate() if (hasUpdate) { worker.postMessage({ type: 'destroy' }) location.reload() } } catch (e) {} }) worker.postMessage({ type: 'check' }) worker.onmessage = ({ data }) => { console.log(data,'data') if (data.type === 'hasUpdate') { hasUpdate = true confirmReload(data.msg, data.lastEtag) } }
4、woker线程
新建 checkUpdate.worker.js
(1)初始化变量
let lastEtag;//上次的Etag let hasUpdate = false;//是否更新 let intervalId = '';
(2)检查版本更新
逻辑更第三步差不多,唯一一点就是检测到更新后,发送hasUpdate消息,给出弹框提示是否需要更新
async function checkUpdate() { try { // 检测前端资源是否有更新 let response = await fetch(`/manifest.json?v=${Date.now()}`, { method: 'get' }) // 获取最新的etag和data let etag = response.headers.get('etag') let data = await response.json() hasUpdate = lastEtag !== undefined && etag !== lastEtag if (hasUpdate) { postMessage({ type: 'hasUpdate', msg: data.msg, lastEtag: lastEtag, etag: etag }) } lastEtag = etag } catch (e) { return Promise.reject(e) } }
(3)监听主线程发送过来的数据
// 监听主线程发送过来的数据 addEventListener('message', ({ data }) => { console.log(data,'消息') if (data.type === 'check') { // 每5分钟执行一次 // 立即执行一次,获取最新的etag,避免在setInterval等待中系统更新,第一次获取的etag是新的,但是lastEtag还是undefined,不满足条件,错失刷新时机 // checkUpdate() intervalId = setInterval(()=>{ checkUpdate() //这里3s方便测试 }, 3 * 1000) } if (data.type === 'recheck') { // 每5分钟执行一次 hasUpdate = false lastEtag = data.lastEtag intervalId = setInterval(()=>{ checkUpdate() }, 3 * 1000) } if (data.type === 'pause') { clearInterval(intervalId) } if (data.type === 'destroy') { clearInterval(intervalId) close() } })
完整代码逻辑如下:
let lastEtag let hasUpdate = false let intervalId = '' async function checkUpdate() { try { // 检测前端资源是否有更新 let response = await fetch(`/manifest.json?v=${Date.now()}`, { method: 'get' }) // 获取最新的etag和data let etag = response.headers.get('etag') let data = await response.json() hasUpdate = lastEtag !== undefined && etag !== lastEtag if (hasUpdate) { postMessage({ type: 'hasUpdate', msg: data.msg, lastEtag: lastEtag, etag: etag }) } lastEtag = etag } catch (e) { return Promise.reject(e) } } // 监听主线程发送过来的数据 addEventListener('message', ({ data }) => { console.log(data,'消息') if (data.type === 'check') { console.log('checkcheckcheck') // 每5分钟执行一次 // 立即执行一次,获取最新的etag,避免在setInterval等待中系统更新,第一次获取的etag是新的,但是lastEtag还是undefined,不满足条件,错失刷新时机 // checkUpdate() intervalId = setInterval(()=>{ checkUpdate() console.log('检查版本更新') }, 3 * 1000) } if (data.type === 'recheck') { // 每5分钟执行一次 hasUpdate = false lastEtag = data.lastEtag intervalId = setInterval(()=>{ checkUpdate() console.log('检查版本更新') }, 3 * 1000) console.log('recheckrecheckrecheck') } if (data.type === 'pause') { clearInterval(intervalId) } if (data.type === 'destroy') { clearInterval(intervalId) close() } })
5、入口文件引入
import "@/utils/checkUpdate.js"
可能出现的问题
1、worker
webpack 5 可以使用 Web Workers
new Worker(new URL('./checkUpdate.worker.js', import.meta.url));
webpack 5 以下,使用 worker-loader,先安装worker-loader
yarn add worker-loader -D
然后在vue.config.js 配置
chainWebpack(config) { config.module .rule('worker') .test(/\.worker\.js$/) .use('worker') .loader('worker-loader') .end() }
如果配置有问题,会一直出现下面这个问题,worker-loader 的配置要放在其他 loader 前。
总结
到此这篇关于前端项目部署后如何提示用户版本更新的文章就介绍到这了,更多相关前端提示用户版本更新内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!