Skip to content

如何在用户不刷新网页的情况下自动更新部署后的代码

Published:

前言

在开发环境中,Vite 能够实时推送代码更新到浏览器,这样我们不需要手动刷新页面,能更高效地开发。但在生产环境中,如何实现类似的自动更新功能呢?当我们更新了代码并重新部署时,浏览器如何知道这些更新并提醒用户刷新页面?

在生产环境中,我们通常会将打包后的 HTML 和 JS 文件部署到静态资源服务器上。用户在访问网页时,浏览器会加载这些文件,并在页面中执行 JS 代码。每次我们更新代码并重新打包后,生成的 JS 文件通常会有不同的指纹(比如 chunk-ae17d.js),但浏览器并不会自动感知这些变化。

解决方案

为了让浏览器知道有新版本的代码可用,我们可以在页面中设置一个定时器,定期检查页面的 HTML 内容是否发生变化。如果检测到变化,我们就可以提醒用户刷新页面。

实现步骤

  1. 定期检查页面:通过定时请求当前页面,并获取页面中的 JS 文件列表。
  2. 比较文件列表:与上次请求的文件列表进行比较,如果有变化,则认为代码已更新。
  3. 通知用户:如果检测到更新,提醒用户刷新页面以获取最新内容。

示例代码

let lastSrcs: string[]; // 存储上一次获取的 JS 文件地址

// 正则表达式用于从 HTML 中提取 script 标签的 src 属性
const scriptRegex = /<script.*src=["'](?<src>[^"']+)/gm;

// 提取当前页面中所有的 JS 文件地址
async function extractNewScripts(): Promise<string[]> {
  // 添加时间戳以防止缓存
  const html = await fetch("/?_timestamp=" + Date.now()).then(res =>
    res.text()
  );

  // 重置正则表达式
  scriptRegex.lastIndex = 0;
  let result: string[] = [];
  let match;
  // 从 HTML 中提取 JS 文件地址
  while ((match = scriptRegex.exec(html))) {
    result.push(match.groups.src);
  }

  return result;
}

// 检查页面是否需要更新
async function needUpdate(): Promise<boolean> {
  const newScripts = await extractNewScripts();

  if (!lastSrcs) {
    lastSrcs = newScripts;
    return false;
  }

  if (lastSrcs.length !== newScripts.length) {
    return true;
  }

  for (let i = 0; i < lastSrcs.length; i++) {
    if (lastSrcs[i] !== newScripts[i]) {
      return true;
    }
  }

  lastSrcs = newScripts;
  return false;
}

// 每隔 2 秒检查一次是否需要更新
const duration = 2000;
function autoRefresh() {
  setTimeout(async () => {
    const willUpdate = await needUpdate();
    if (willUpdate) {
      alert("页面已更新,请刷新");
      location.reload();
    }

    autoRefresh();
  }, duration);
}

// 启动自动检查和刷新功能
autoRefresh();