使用Webpack、Vite等構(gòu)建工具打包時,會為靜態(tài)資源添加hash,確保每次更新都能生成唯一的文件名。然而,當新版本部署后,用戶瀏覽器中可能仍然緩存著舊版本的文件。雖然可以設(shè)置較短的緩存時間,但用戶仍然需要手動刷新頁面才能獲取最新版本。那么如何讓用戶在頁面打開期間,自動感知到新版本的發(fā)布并完成更新?
下面是一個簡易的輪詢檢測script資源變化來實現(xiàn)自動更新提醒:
<script setup name="">
// 常量定義
const POLL_INTERVAL = 2000
let cachedScriptSources = null // 緩存的script地址
const SCRIPT_SRC_REGEX = /<script[^>]*src=["']([^"']+)/g
// 獲取頁面中所有script的src地址
async function fetchScriptSources() {
try {
// Date.now() 用于防止瀏覽器緩存
const html = await fetch(`/?timestamp=${Date.now()}`).then((res) => res.text())
const sources = []
let match
SCRIPT_SRC_REGEX.lastIndex = 0 // 重置正則表達式狀態(tài)
while((match = SCRIPT_SRC_REGEX.exec(html))) {
sources.push(match[1])
}
return sources
} catch (error) {
console.error('獲取頁面資源失敗:', error)
return cachedScriptSources || []
}
}
// 檢查頁面是否需要更新
async function checkForUpdates() {
const newScriptSources = await fetchScriptSources()
if (!cachedScriptSources) {
cachedScriptSources = newScriptSources
return false
}
// 比較兩個數(shù)組是否相同
if (cachedScriptSources.length !== newScriptSources.length) {
cachedScriptSources = newScriptSources
return true
}
const hasChanges = cachedScriptSources.some((src, index) => src !== newScriptSources[index])
cachedScriptSources = newScriptSources
return hasChanges
}
// 輪詢檢查頁面更新
function startUpdatePolling() {
const poll = async () => {
const shouldUpdate = await checkForUpdates()
if (shouldUpdate) {
const userConfirmed = confirm('檢測到頁面有更新,是否刷新頁面?')
if (userConfirmed) {
location.reload()
return // 刷新頁面,不需要繼續(xù)輪詢
}
}
setTimeout(poll, POLL_INTERVAL)
}
setTimeout(poll, POLL_INTERVAL)
}
startUpdatePolling()
</script>
原理詳解
1、監(jiān)控資源文件變化
現(xiàn)代前端應用構(gòu)建后,通常會給靜態(tài)資源(如JavaScript、CSS文件)添加hash值作為版本標識:
app.js → app.abc123.js (版本1)
app.js → app.def456.js (版本2,內(nèi)容更新后hash變化)
通過檢測這些資源文件名的變化來判斷是否有新版本發(fā)布。
2、繞過瀏覽器緩存
fetch(`/?timestamp=${Date.now()}`)
這里使用時間戳作為查詢參數(shù),確保每次請求都能獲取最新的HTML文件,而不是瀏覽器緩存的舊版本。
3、提取Script資源
const SCRIPT_SRC_REGEX = /<script[^>]*src=["']([^"']+)/g
使用正則表達式從HTML中提取所有<script>標簽的src屬性。注意正則表達式末尾的g標志表示全局匹配,因此需要手動重置lastIndex屬性。
4、比較
const hasChanges = cachedScriptSources.some((src, index) => src !== newScriptSources[index])
使用Array.some()方法比較兩個數(shù)組,只要發(fā)現(xiàn)任何一個位置的資源路徑不同,就判定為有更新。
拓展:uniapp更新檢測
// utils/updateManager.js
class UpdateManager {
constructor() {
this.updateManager = null
this.hasUpdate = false
this.init()
}
init() {
// #ifdef MP-WEIXIN
if (uni.getUpdateManager) {
this.updateManager = uni.getUpdateManager()
this.setupListeners()
}
// #endif
}
setupListeners() {
if (!this.updateManager) return
// 檢查更新結(jié)果回調(diào)
this.updateManager.onCheckForUpdate((res) => {
console.log('檢查更新結(jié)果:', res)
if (res.hasUpdate) {
this.hasUpdate = true
// 可以在這里記錄有更新,但先不提示,等下載完成再提示
}
})
// 更新下載完成回調(diào)
this.updateManager.onUpdateReady(() => {
this.showUpdateConfirm()
})
// 更新下載失敗回調(diào)
this.updateManager.onUpdateFailed(() => {
uni.showToast({
title: '更新失敗,請檢查網(wǎng)絡',
icon: 'none'
})
})
}
showUpdateConfirm() {
uni.showModal({
title: '更新提示',
content: '新版本已經(jīng)準備好,是否重啟應用?',
success: (res) => {
if (res.confirm) {
// 強制重啟小程序
this.updateManager.applyUpdate()
} else {
// 用戶拒絕,可以記錄狀態(tài),稍后提醒
this.scheduleReminder()
}
}
})
}
scheduleReminder() {
// 5分鐘后再次提醒
setTimeout(() => {
if (this.hasUpdate) {
this.showUpdateConfirm()
}
}, 5 * 60 * 1000)
}
checkUpdate() {
if (this.updateManager) {
this.updateManager.onCheckForUpdate(() => {})
}
}
}
export default new UpdateManager()
在App.vue中初始化
<script>
import updateManager from '@/utils/updateManager.js'
export default {
onLaunch() {
// 小程序更新檢測
updateManager.checkUpdate()
},
onShow() {
// 每次進入前臺檢查
updateManager.checkUpdate()
}
}
</script>