前言 在传统的多页 Web 应用中,每次用户访问页面时,都会从服务器获取最新的页面和资源,因此版本更新相对简单,用户总是能获取到最新的版本。然而,SPA 在首次加载后,前端的静态资源会缓存在浏览器内存中,且在整个使用过程中通常不会自动重新加载。这种特性意味着如果应用有新的版本发布,用户可能仍在使用旧版本,无法立即获得最新的功能、修复或安全更新。
那么,在我们部署之后,如何提醒用户版本更新,并引导用户刷新页面呢?
手搓 完整代码地址:Github
比较构建文件的 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 (); }); }, });
看啥呢?(我没尝试,上面的够用了 (懒…))