实践总结 3 种前端部署后页面检测版本的方法
領導:為什么每次項目部署后,有的用戶要清緩存才能看到最新的頁面
我:瀏覽器有默認的緩存策略,如果服務器在響應頭中沒有禁用緩存,那么瀏覽器每次請求頁面會先看看緩存里面有沒有,有的話從緩存取,造成還是取的舊頁面。正常來說,用戶只需要點擊刷新按鈕,刷新一下頁面就好了,不必清除瀏覽器緩存刷新。
領導:為什么緩存這么嚴重,有的用戶清除緩存刷新還是不行,關掉瀏覽器重新進來還是不行,要重啟電腦才有效。
我:要重啟電腦?這 。。。。。。用戶都這樣么,還是只有一小部分用戶。
領導:不是所有的用戶,有個別用戶會出現這種情況
我:那可能得到用戶電腦上看看了
每次需求投產后,因為有緩存問題導致用戶看到的還是舊版內容,使用過程中出現了問題,聯系我們才知道項目更新了,用戶體驗不好;
于是查找資料,尋找合適的方案,根據 評論區 的討論,實踐總結了下面 3 種前端部署后頁面檢測版本更新的方法
當檢測到版本更新則及時通知用戶,用戶可以選擇是否立即更新,并不會影響用戶當前進行的業務;
下面以 vue 項目為例
1、輪詢打包后的 index.html,比較生成的 js 文件的 hash
項目打包后,index.html 會包含打包后的 js 文件,這些文件的文件名包含的 hash 將會和上一次打包的不同,比較 hash 也就能判斷是否有版本更新;
let firstV = [] //記錄初始獲得的 script 文件字符串
let currentv = [] //記錄當前獲得的 script 文件字符串
// 獲得的文件字符串類似這樣 `<script src="/js/chunk-vendors.1234fff.js"></script>`
async function getHtml() {
let res = await axios.get('/index.html?date=' + Date.now())
if (res.status == '200') {
let text = res.data
if (text) {
// 解析 html 內容,匹配 script 字符串
let reg = /<script([^>]+)><\/script>/ig
return text.match(reg)
}
}
return []
}
function isEqual(a, b) {
return a.length = Array.from(new Set(a.concat(b))).length
}
export async function checkIfNewVersion() {
firstV = await getHtml()
window.checkVersionInterval && clearInterval(window.checkVersionInterval)
window.checkVersionInterval = setInterval(async () =>{
currentV = await getHtml()
console.log(firstV,currentv)
// 當前 script hash 和初始的不同時,說明已經更新
if(!isEqual(firstV, currentv)) {
console.log('已更新')
}
},3000)
}
// 文檔可見時檢測版本是否更新
document.addEventListener("visibilitychange", () => {
if (document.visibilityState === "visible") {
checkIfNewVersion();
} else {
window.checkVersionInterval && clearInterval(window.checkVersionInterval)
}
});
getHtml() 得到的結果示例如下:
[
'<script src="/js/chunk-vendors.1234fff.js"></script>',
'<script src="/js/app.1234fff.js"></script>',
]
改動了一點業務代碼后,再次打包,上面 app.js 的 hash 就會發生變化
[
'<script src="/js/chunk-vendors.1234fff.js"></script>',
'<script src="/js/app.12ed5ca.js"></script>',
]
比較兩個的結果,如果結果不一樣,則代表有版本更新。
2、HEAD 方*詢響應頭中的 etag
ETag 是資源的特定版本的標識符。當資源內容發生變化時,會生成新的 ETag;HEAD?方法請求資源的響應頭信息,服務器不會返回響應體,可以節省帶寬資源;
這里可以輪詢打包后的 index.html,取兩次響應頭中的 eTag 比較,如果不同,說明版本更新了;前提是服務器沒有禁用緩存。
let firstEtag = `` //記錄第一次進來請求獲得的 etag
let currentEtag = `` //記錄當前的 etag,會不斷的刷新
async function getEtag(){
let res = await axios.head('/index.html')
if(res.status == '200'){
if(res.headers && res.headers.etag){
return res.headers.etag
}
}
return ''
}
export async function checkEtag() {
firstEtag = await getEtag()
window.checkEtagInterval && clearInterval(window.checkEtagInterval)
window.checkEtagInterval = setInterval(async() =>{
// 每隔一定時間請求最新的 etag
currentEtag = await getEtag()
// 當前最新的 currentEtag 和初始 firstEtag 進行比較,不同則說明資源更新了;
if(firstEtag && currentEtag && firstEtag!==currentEtag){
console.log('已更新')
}
},3000)
}
// 文檔可見時檢測版本是否更新
document.addEventListener("visibilitychange", () => {
if (document.visibilityState === "visible") {
checkEtag();
} else {
window.checkEtagInterval && clearInterval(window.checkEtagInterval)
}
});
3、監聽 git commit hash 變化
項目改動提交 git 時會生成唯一的 hash 字符串,將最近提交的 commit hash 作為版本號保存在一個 json 文件中;通過輪詢 json 文件,檢測里面的版本號是否和上次不同,不同則表示有版本更新;
監聽 git commit hash 變化的好處是只要投產的版本有 git 提交記錄,而不管靜態文件變化還是代碼變化,都能檢測到版本更新;
在 vue.config.js 中引入 git-revision-webpack-plugin,該插件可獲取到項目本地 git 的最新提交 commit hash
const GitRevisionPlugin = require('git-revision-webpack-plugin')
const gitRevision = new GitRevisionPlugin()
const { writeFile , existsSync } = require('fs')
if(existsSync('./public')){
fs.writeFile(
'./public/version.json',
`{"commitHash":${JSON.stringify(gitRevision.commithash())}`,
(error) =>{}
)
}
上面代碼使用 gitRevision.commithash() 獲取 commit hash,將其存入到 public/versionHash.json 文件中;
項目打包會執行上面的代碼,生成后的 'versionHash.json' 文件類似這樣
// 示例
{ "commitHash" : "234fjsdr322f32f322f32f3g32g23jglk32gjkl32lg3" }
項目改動后,提交改動的地方后,再次打包,會將最新的 commit hash 存入到 public/versionHash.json
// 示例
{ "commitHash" : "234fjsdr322f3eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" }
然后在頁面中輪詢 '/versionHash.json',比較 commit hash ,檢測是否有更新
let firstCommitHash = ``
let currentCommitHash = ``
async function getCommitHash() {
// 避免瀏覽器緩存加上時間戳參數
let res = await axios.get('/versionHash.json?date=' + Date.now())
if (res.status == '200') {
if (res.data && res.data.commitHash) {
return res.data.commitHash
}
}
return ''
}
export async function checkCommitHash() {
firstCommitHash = await getCommitHash()
window.checkCommitHash && clearInterval(window.checkCommitHash)
window.checkCommitHash = setInterval(async () => {
// 輪詢 versionHash.json 文件
currentCommitHash = await getCommitHash()
if (firstCommitHash && currentCommitHash && firstCommitHash !== currentCommitHash) {
console.log('已更新')
// 作相應處理
}
}, 3000)
}
關于檢測版本更新的時機
檢測時機,我覺得有三種比較合適,可以靈活搭配上面的方法使用
- 資源加載錯誤時(常常發生在切換菜單時),檢測版本更新
- 路由切換發生錯誤時(也發生在切換菜單時或者當前頁面引用其他路由時),檢測版本更新
- 監聽
visibilitychange + focus事件
1、資源加載錯誤時
前端部署后,某些資源已經更新,當切換菜單時,可能會出現資源加載失敗的錯誤(404)。此時可以使用 addEventListener('error') 捕獲資源加載錯誤
window.addEventListener('error',(event) =>{
// 檢測版本更新
// window.location.reload()
},true)
2、路由切換發生錯誤時
和上面的 addEventListener('error') 捕獲資源加載錯誤類似, vue-router 的 router.onError() 方法可以捕獲到路由加載的錯誤。
路由切換時某些資源加載失敗,會拋出 Loading chunk chunk-xxxx failed,可以用正則匹配它并作相應處理;
router.onError((error) =>{
let reg = /Loading.*?failed/g
if(reg.test(error)){
// 檢測版本更新
// window.location.reload()
}
})
3、監聽 visibilitychange + focus 事件
visibilitychange:當其選項卡的內容變得可見或被隱藏時,會在 document 上觸發?visibilitychange?事件。
當用戶導航到新頁面、切換標簽頁、關閉標簽頁、最小化或關閉瀏覽器,或者在移動設備上從瀏覽器切換到不同的應用程序時,該事件就會觸發,其?
visibilityState?為?hidden
在 pc 端,從瀏覽器切換到其他應用程序并不會觸發 visibilitychange 事件,所以加以 focus 輔佐;當鼠標點擊過當前頁面(必須 focus 過),此時切換到其他應用會觸發頁面的 blur 實踐;再次切回到瀏覽器則會觸發 focus 事件;
document.addEventListener("visibilitychange", () => {
if (document.visibilityState === "visible") {
// 開始檢測更新
} else {
// 結束檢測更新
}
});
document.addEventListener('focus',() =>{
// 開始檢測更新
})
關于禁用緩存
禁用 html 緩存
<!-- HTTP/1.1 -->
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<!-- HTTP/1.0; 與 Cache-Control: no-cache 效果一致 -->
<meta http-equiv="Pragma" content="no-cache">
<!-- 如果在 Cache-Control 設置了 "max-age" 或者 "s-max-age" 指令,那么?`Expires`?頭會被忽略。-->
<meta http-equiv="Expires" content="0">
如果只在 html 中設置這個的話,只在 IE 中有效;若要在其他瀏覽器中生效,則需要對服務器設置禁用緩存;
nginx 設置禁用緩存
// 配置 html 和 htm 文件不緩存
location / {
root html;
index index.html index.htm;
add_header Cache-Control "no-cache,no-store,must-revalidate";
}
總結
本文總結了 3 種前端部署后頁面檢測版本更新的方法;
- 輪詢打包后的 index.html,比較生成的 js 文件的 hash
- HEAD 方*詢響應頭中的
etag - 監聽
git commit hash變化
3 種都有用武之地,看具體場景和需求;
監聽 git commit hash 變化優勢是可以檢測到靜態資源的變化;
HEAD 方*詢響應頭中的 etag,優勢是只需要取響應頭中的字段,服務器不需要返回響應體,節約資源;
總結
以上是生活随笔為你收集整理的实践总结 3 种前端部署后页面检测版本的方法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【技术推荐】我愿称之为开源界最好用的行为
- 下一篇: 01-前世今生:Kubernete 是如