前言
在传统的多页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();       });   }, });
 
  | 
 
看啥呢?(我没尝试,上面的够用了 (懒…))