前端項(xiàng)目部署后如何提示用戶版本更新詳解
前言
項(xiàng)目部署上線后,特別是網(wǎng)頁(yè)項(xiàng)目,提示正在操作系統(tǒng)的用戶去更新版本非常 important。一般我們都會(huì)用“刷新大法”來(lái)清理緩存,但是對(duì)于正在操作網(wǎng)頁(yè)的用戶,不造系統(tǒng)更新了,請(qǐng)求的還是老版本的資源。
為了確保用戶能夠及時(shí)獲得最新的功能和修復(fù)的 bug,我們需要通知用戶刷新頁(yè)面獲取最新的代碼。
解決方案
每次打包時(shí),都生成一個(gè)時(shí)間戳,作為系統(tǒng)的偽版本,放到JSON文件中,通過(guò)對(duì)比文件的響應(yīng)頭Etag判斷是否有更新。具體步驟如下:
- 在public文件夾下加入manifest.json文件,里面存放兩個(gè)字段:更新內(nèi)容、更新時(shí)間戳
- 前端打包的時(shí)候向manifest.json寫(xiě)入當(dāng)前時(shí)間戳信息
- 在入口文件main.js中引入檢查版本更新的邏輯,有更新則提示更新。有兩種方式提示提示用戶:
路由守衛(wèi)router.beforeResolve(Vue-Router為例)
,檢查更新,對(duì)比manifest.json文件的響應(yīng)頭Etag判斷是否有更新- 通過(guò)Worker輪詢,檢查更新,對(duì)比manifest.json文件的響應(yīng)頭Etag判斷是否有更新。Worker線程并不影響其他線程的邏輯。
整體邏輯如下所示:
1、public目錄下新建manifest.json
{ "timestamp":21312321311, "msg":"更新內(nèi)容如下:\n--1.添加系統(tǒng)更新提示機(jī)制" }
2、寫(xiě)入當(dāng)前時(shí)間戳到manifest.json
vue.config.js文件中
const { readFile, writeFile } = require('fs') // 獲取路徑 const filePath = path.resolve(`./public`, 'manifest.json') // 讀取文件內(nèi)容 readFile(filePath, 'utf8', (err, data) => { if (err) { console.error('讀取文件時(shí)出錯(cuò):', err) return } // 將文件內(nèi)容轉(zhuǎn)換JSON const dataObj = JSON.parse(data) //修改時(shí)間戳 dataObj.timestamp = new Date().getTime() // 將修改后的內(nèi)容寫(xiě)回文件 writeFile(filePath, JSON.stringify(dataObj), 'utf8', err => { if (err) { console.error('寫(xiě)入文件時(shí)出錯(cuò):', err) return } }) })
3、檢查版本更新
新建 checkUpdate.js 文件
(1)初始化變量
import Worker from "./checkUpdate.worker.js"; import router from '../router' //上次的Etag let lastEtag = ''; //是否更新 let hasUpdate = false //創(chuàng)建worker線程 const worker = new Worker();
(2)檢查版本更新
//檢查版本更新 async function checkUpdate() { try { // 檢測(cè)前端資源是否有更新 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 函數(shù)發(fā)起了一個(gè) HTTP 請(qǐng)求,獲取了指定資源的頭信息(HTTP 頭部)。其中 manifest.json 是要請(qǐng)求的資源,Date.now() 會(huì)生成當(dāng)前時(shí)間的時(shí)間戳,作為查詢參數(shù) v 的值,這樣可以避免瀏覽器緩存,強(qiáng)制獲取最新的資源。請(qǐng)求方式為 HEAD,這意味著只請(qǐng)求資源的頭部信息而不獲取具體的內(nèi)容。
let etag = response.headers.get('etag')
,從 HTTP 響應(yīng)中獲取了 ETag 頭部信息,ETag 是服務(wù)器生成的資源唯一標(biāo)識(shí),用于檢查資源是否發(fā)生了變化。具體比較邏輯如下:
1、客戶端發(fā)起請(qǐng)求,請(qǐng)求中包含上次獲取的資源的ETag。
2、服務(wù)器收到請(qǐng)求后,比較客戶端提供的ETag與當(dāng)前資源的ETag是否一致。
3、如果一致,則返回HTTP 304 Not Modified響應(yīng),表示資源未發(fā)生變化,客戶端可以使用緩存的版本。
4、如果不一致,服務(wù)器返回最新的資源內(nèi)容,同時(shí)更新ETag。
5、客戶端收到響應(yīng)后,更新本地緩存的資源內(nèi)容和ETag。
(3)路由跳轉(zhuǎn)檢測(cè)版本更新
// 路由攔截 router.beforeEach(async (to, from, next) => { next() try { await checkUpdate() if (hasUpdate) { worker.postMessage({ type: 'destroy' }) location.reload() } } catch (e) {} })
(4)向worker線程發(fā)送檢查版本更新邏輯
worker.postMessage({ type: 'check' })
(5)接收到 worker 線程數(shù)據(jù)更新
worker.onmessage = ({ data }) => { console.log(data,'data') if (data.type === 'hasUpdate') { hasUpdate = true confirmReload(data.msg, data.lastEtag) } }
(6)收到版本更新信息,進(jìn)行彈框提示
收到版本更新信息后,先暫停輪詢檢查版本更新,點(diǎn)擊確定按鈕,則發(fā)送destory消息,點(diǎn)擊取消按鈕則發(fā)送recheck消息
async function confirmReload(msg = '', lastEtag) { worker && worker.postMessage({ type: 'pause' }) try { //彈框提示邏輯 } catch (e) { } }
checkUpdate.js 全部代碼實(shí)現(xiàn)
import Worker from "./checkUpdate.worker.js"; import router from '../router' //上次的Etag let lastEtag = ''; //是否更新 let hasUpdate = false //創(chuàng)建worker線程 const worker = new Worker(); //檢查版本更新 async function checkUpdate() { try { // 檢測(cè)前端資源是否有更新 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)檢查版本更新
邏輯更第三步差不多,唯一一點(diǎn)就是檢測(cè)到更新后,發(fā)送hasUpdate消息,給出彈框提示是否需要更新
async function checkUpdate() { try { // 檢測(cè)前端資源是否有更新 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)監(jiān)聽(tīng)主線程發(fā)送過(guò)來(lái)的數(shù)據(jù)
// 監(jiān)聽(tīng)主線程發(fā)送過(guò)來(lái)的數(shù)據(jù) addEventListener('message', ({ data }) => { console.log(data,'消息') if (data.type === 'check') { // 每5分鐘執(zhí)行一次 // 立即執(zhí)行一次,獲取最新的etag,避免在setInterval等待中系統(tǒng)更新,第一次獲取的etag是新的,但是lastEtag還是undefined,不滿足條件,錯(cuò)失刷新時(shí)機(jī) // checkUpdate() intervalId = setInterval(()=>{ checkUpdate() //這里3s方便測(cè)試 }, 3 * 1000) } if (data.type === 'recheck') { // 每5分鐘執(zhí)行一次 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 { // 檢測(cè)前端資源是否有更新 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) } } // 監(jiān)聽(tīng)主線程發(fā)送過(guò)來(lái)的數(shù)據(jù) addEventListener('message', ({ data }) => { console.log(data,'消息') if (data.type === 'check') { console.log('checkcheckcheck') // 每5分鐘執(zhí)行一次 // 立即執(zhí)行一次,獲取最新的etag,避免在setInterval等待中系統(tǒng)更新,第一次獲取的etag是新的,但是lastEtag還是undefined,不滿足條件,錯(cuò)失刷新時(shí)機(jī) // checkUpdate() intervalId = setInterval(()=>{ checkUpdate() console.log('檢查版本更新') }, 3 * 1000) } if (data.type === 'recheck') { // 每5分鐘執(zhí)行一次 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"
可能出現(xiàn)的問(wèn)題
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() }
如果配置有問(wèn)題,會(huì)一直出現(xiàn)下面這個(gè)問(wèn)題,worker-loader 的配置要放在其他 loader 前。
總結(jié)
到此這篇關(guān)于前端項(xiàng)目部署后如何提示用戶版本更新的文章就介紹到這了,更多相關(guān)前端提示用戶版本更新內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JS利用ES6和ES5分別實(shí)現(xiàn)長(zhǎng)整數(shù)和字節(jié)數(shù)組互轉(zhuǎn)
這篇文章主要為大家詳細(xì)介紹了長(zhǎng)整數(shù)與字節(jié)數(shù)組互轉(zhuǎn)的技術(shù)原理,文中提供了ES6(現(xiàn)代瀏覽器/Node.js)與ES5(兼容舊環(huán)境)兩套實(shí)現(xiàn)方案,需要的可以參考下2025-04-04js中document.getElementById(id)的具體用法
本文主要介紹了js中document.getElementById(id)的具體用法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04JavaScript 十六進(jìn)制RGB色碼轉(zhuǎn)換器
JavaScript 十六進(jìn)制RGB色碼轉(zhuǎn)換器,大家可以學(xué)習(xí)下思路。2009-08-08使用json來(lái)定義函數(shù),在里面可以定義多個(gè)函數(shù)的實(shí)現(xiàn)方法
下面小編就為大家?guī)?lái)一篇使用json來(lái)定義函數(shù),在里面可以定義多個(gè)函數(shù)的實(shí)現(xiàn)方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-10-10JavaScript canvas復(fù)刻蘋果發(fā)布會(huì)環(huán)形進(jìn)度條
canvas 真是一個(gè)好東西,它給前端插上了想象的翅膀,伴隨著 h5 而來(lái),將 web 代入了新的領(lǐng)域。本文將利用anvas復(fù)刻蘋果發(fā)布會(huì)環(huán)形進(jìn)度條,感興趣的可以嘗試一下2022-07-07微信小程序云開(kāi)發(fā)之使用云數(shù)據(jù)庫(kù)
這篇文章主要為大家詳細(xì)介紹了微信小程序云開(kāi)發(fā)之使用云數(shù)據(jù)庫(kù),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-05-05js采用concat和sort將N個(gè)數(shù)組拼接起來(lái)的方法
這篇文章主要介紹了js采用concat和sort將N個(gè)數(shù)組拼接起來(lái)的方法,涉及JavaScript針對(duì)數(shù)組的合并與排序操作相關(guān)技巧,需要的朋友可以參考下2016-01-01擁有一個(gè)屬于自己的javascript表單驗(yàn)證插件
這篇文章主要幫助大家擁有一個(gè)屬于自己的javascript表單驗(yàn)證插件,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-03-03