前言
在传统的多页Web应用中,每次用户访问页面时,都会从服务器获取最新的页面和资源,因此版本更新相对简单,用户总是能获取到最新的版本。然而,SPA在首次加载后,前端的静态资源会缓存在浏览器内存中,且在整个使用过程中通常不会自动重新加载。这种特性意味着如果应用有新的版本发布,用户可能仍在使用旧版本,无法立即获得最新的功能、修复或安全更新。
那么,在我们部署之后,如何提醒用户版本更新,并引导用户刷新页面呢?
手搓
比较构建文件的hash值
这里用轮询的方式请求index.html文件,从中解析里面所有的js文件,由于vue打包后每个js文件都有指纹标识,因此对比每次打包后的指纹,分析文件是否存在变动,如果有变动则提示用户更新!(该方案需要webpack/vite开启打包文件带上hash值)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| import { MessageBox } from 'element-ui';
const modelConfirm = (title, message, type='warning') => { return new Promise((resolve, reject) => { MessageBox.confirm(message, title, { type }).then(() => { refreshPage(); }).catch(() => { reject(); }); }); };
function refreshPage() { const timestamp = new Date().getTime(); window.location.href = window.location.pathname + '?t=' + timestamp; location.reload(); localStorage.clear(); sessionStorage.clear(); }
let scriptHashes = new Set(); let timer = undefined;
async function fetchScriptHashes() { const html = await fetch('/?_timestamp=' + Date.now()).then((resp) => resp.text()); const scriptRegex = /<script(?:\s+[^>]*)?>(.*?)<\/script\s*>/gi; const scripts = html.match(scriptRegex) ?? []; return new Set(scripts); }
async function compareScriptHashes() { const newScriptHashes = await fetchScriptHashes();
if (scriptHashes.size === 0) { scriptHashes = newScriptHashes; } else if ( scriptHashes.size !== newScriptHashes.size || ![...scriptHashes].every((hash) => newScriptHashes.has(hash)) ) { console.info('更新了', { oldScript: [...scriptHashes], newScript: [...newScriptHashes], }); clearInterval(timer); modelConfirm('更新提示','检测到页面有内容更新,为了功能的正常使用,是否立即刷新?'); } else { console.info(`没更新${new Date().toLocaleString()}`, { oldScript: [...scriptHashes], }); } }
export function autoRefresh() { timer = setInterval(compareScriptHashes, 10000); }
|
1 2 3 4 5
| import { autoRefresh } from "@/utils/index.js"; if(process.env.NODE_ENV === 'production'){ autoRefresh(); }
|
效果:

比较Etag或last-modified
利用HTTP协议的缓存机制,比较Etag或last-modified前后是否一致。(经测试,即使代码内容不做任何修改, ETag 和 Last-Modified 的值每次打包完都不一样;而js的hash值如果内容不做任何修改,hash值也不会修改)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| import { MessageBox } from 'element-ui';
const modelConfirm = (title, message, type = 'warning') => { return new Promise((resolve, reject) => { MessageBox.confirm(message, title, { type, }) .then(() => { refreshPage(); }) .catch(() => { reject(); }); }); };
function refreshPage() { const timestamp = new Date().getTime(); window.location.href = window.location.pathname + '?t=' + timestamp; location.reload(); localStorage.clear(); sessionStorage.clear(); }
let versionTag = null; let timer = undefined;
async function fetchVersionTag() { const response = await fetch('/?_timestamp=' + Date.now(), { cache: 'no-cache', }); return response.headers.get('etag') || response.headers.get('last-modified'); }
async function compareVersionTag() { const newVersionTag = await fetchVersionTag();
if (versionTag === null) { versionTag = newVersionTag; } else if (versionTag !== newVersionTag) { console.info('更新了', { oldVersionTag: versionTag, newVersionTag: newVersionTag, }); clearInterval(timer); modelConfirm( '更新提示', '检测到页面有内容更新,为了功能的正常使用,是否立即刷新?', ); } else { console.info(`没更新${new Date().toLocaleString()}`, { oldVersionTag: versionTag, }); } }
export function autoRefresh() { timer = setInterval(compareVersionTag, 10000); }
|
1 2 3 4 5
| import { autoRefresh } from "@/utils/index.js"; if(process.env.NODE_ENV === 'production'){ autoRefresh(); }
|
参考文献
第三方库
1 2
| npm install version-polling --save
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import { createVersionPolling } from 'version-polling'; import { MessageBox } from 'element-ui';
createVersionPolling({ silent: process.env.NODE_ENV === 'development', onUpdate: (self) => { MessageBox.confirm('检测到网页有更新, 是否刷新页面加载最新版本?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', }) .then(() => { self.onRefresh(); }) .catch(() => { self.onCancel(); }); }, });
|
看啥呢?(我没尝试,上面的够用了 (懒…))