前端版本检测

WebUpdateNotificationPlugin 版本更新检测说明

本文档说明 pay 项目中 @plugin-web-update-notification/webpack 的工作原理、构建注入方式、运行时检测逻辑,以及排查与验证方法。

1. 背景与目的

SPA / MPA 前端发版后,用户若长期不关闭页面,浏览器仍运行旧版 JS。常见后果:

  • 请求已不存在的 chunk 文件(404)
  • 页面白屏或功能异常
  • 用户看到的是旧 UI / 旧逻辑

WebUpdateNotificationPlugin 在检测到新版本后,弹出「发现新版本,请刷新」提示,引导用户 location.reload() 加载最新资源。

2. 项目中的配置

配置位置:vue.config.js

js 复制代码
const { WebUpdateNotificationPlugin } = require('@plugin-web-update-notification/webpack')

module.exports = defineConfig({
  configureWebpack: {
    plugins: [
      new WebUpdateNotificationPlugin({
        logVersion: true,
      }),
    ],
  },
})
配置项 当前值 说明
logVersion true 构建时在控制台打印版本号
versionType 未配置(默认) Git 仓库下默认使用 git_commit_hash
checkInterval 未配置(默认 10 分钟) 轮询间隔

重要 :插件未按 VUE_APP_ENV(sit/uat/prod)做条件判断,所有环境的 production build 都会启用,包括:

bash 复制代码
npm run build:skyee:wealth:sit
npm run build:skyee:wealth:uat
npm run build:skyee:wealth:prod

本地 serve:* 开发模式虽也会加载插件,但因 HMR 热更新,实际意义不大。

3. 版本号从哪里来

3.1 构建时(Node / webpack 进程)

插件在 webpack 构建阶段 读取版本号,不是在浏览器里读 git

默认逻辑(@plugin-web-update-notification/core):

  1. 检测仓库类型:存在 .git 目录 → 判定为 Git 仓库
  2. 执行系统命令获取短 commit hash:
js 复制代码
import { execSync } from 'child_process'

function getGitCommitHash() {
  return execSync('git rev-parse --short HEAD')
    .toString()
    .replace('\n', '')
    .trim()
}
  1. 版本号 = 执行 npm run build 时,当前工作区 HEAD 对应的短 hash

例如构建时 HEAD 为 084cb6697194d8e77d880061545fb347933c09c2,则版本号为 084cb6697

3.2 其他可选版本类型

versionType 来源
git_commit_hash git rev-parse --short HEAD(Git 仓库默认)
svn_revision_number svnversion(SVN 仓库默认)
pkg_version process.env.npm_package_version
build_timestamp 构建时间戳(非 Git/SVN 时降级)
custom 手动指定 customVersion

4. 构建时注入了哪些内容

webpack 插件在两个钩子中工作:

4.1 emit 阶段

向构建产物写入:

文件 说明
pluginWebUpdateNotice/web_version_by_plugin.json 远端版本文件,供运行时轮询
pluginWebUpdateNotice/webUpdateNoticeInjectScript.*.js 检测与弹窗逻辑脚本
pluginWebUpdateNotice/webUpdateNoticeInjectStyle.*.css 通知栏样式(非隐藏模式)

web_version_by_plugin.json 示例:

json 复制代码
{
  "version": "084cb6697",
  "silence": false
}

4.2 afterEmit 阶段

修改 dist/.../index.html,在 <head> 中注入:

html 复制代码
<link rel="stylesheet" href="/skyee/wealth/pluginWebUpdateNotice/webUpdateNoticeInjectStyle.efdbdf2b.css">
<script src="/skyee/wealth/pluginWebUpdateNotice/webUpdateNoticeInjectScript.global.ffdddd40.js"></script>
<script>window.pluginWebUpdateNotice_version = '084cb6697';</script>

</body> 前注入通知容器:

html 复制代码
<div class="plugin-web-update-notice-anchor"></div>

4.3 路径说明

本仓库为 MPA 多入口,publicPath 按平台/项目区分,例如 Skyee wealth:

复制代码
/skyee/wealth/index.html
/skyee/wealth/pluginWebUpdateNotice/web_version_by_plugin.json

插件会自动读取 webpack 的 publicPath 拼出正确 URL。

5. 运行时如何检测新版本

页面加载后,注入脚本的核心逻辑(简化):

js 复制代码
// 本地版本:打开页面时 index.html 里写死的,之后不会变
const localVersion = window.pluginWebUpdateNotice_version  // 例:'084cb6697'

// 从服务器拉最新 version.json(?t= 防缓存)
fetch('/skyee/wealth/pluginWebUpdateNotice/web_version_by_plugin.json?t=' + Date.now())
  .then(res => res.json())
  .then(({ version, silence }) => {
    if (silence) return
    if (version !== localVersion) {
      showNotification()  // 弹「发现新版本,请刷新」
    }
  })

判断依据 :本地版本 ≠ 远端 version.json 中的版本 → 认为有新版本。

5.1 触发检测的时机

时机 默认行为
页面首次加载 立即检测
定时轮询 每 10 分钟
标签页切回前台 visibilitychange
窗口重新聚焦 focus
JS 文件加载失败 监听 <script>error 事件(chunk 404 时触发)

5.2 典型时间线

复制代码
T1  用户打开页面
    → 加载构建 A 的 index.html
    → window.pluginWebUpdateNotice_version = '084cb6697'

T2  团队重新 build 并部署构建 B(新 commit)
    → 服务器 version.json 变为 '5bee76407'
    → 用户浏览器仍运行旧页面,本地仍是 '084cb6697'

T3  轮询 / 切回标签页 / chunk 404
    → fetch version.json → '5bee76407'
    → '084cb6697' !== '5bee76407'
    → 弹出更新提示

用户点击「刷新」后,会加载新的 index.html,本地版本同步更新,不再弹窗(除非 again 部署了更新的版本)。

6. 版本号与 Git Commit 的对应关系

页面源码中的版本号可直接用 git 反查:

bash 复制代码
git show 084cb6697 --oneline
# 084cb6697 Merge branch 'feature-timeline' into sit

示例(SIT 某次部署):

页面中的版本号 对应 commit 提交信息 时间
084cb6697 084cb6697194d8e77d880061545fb347933c09c2 Merge branch 'feature-timeline' into sit 2026-07-30 10:29:32

构建日志中若 logVersion: true,也会打印:

复制代码
version: 084cb6697

7. 部署与缓存注意事项

7.1 index.html 禁止强缓存

index.html 被 CDN/Nginx 长期缓存,可能出现:

  • 用户刷新后仍拿到旧 index.html,本地版本号不更新
  • 更新提示反复出现或行为异常

推荐 :对 index.html 设置 Cache-Control: no-cache, no-store, must-revalidate

Nginx 示例:

nginx 复制代码
location / {
  if ( $uri = '/index.html' ) {
    add_header Cache-Control "no-cache, no-store, must-revalidate";
  }
  try_files $uri $uri/ /index.html;
}

带 hash 的 JS/CSS(app.[hash].js)可长期缓存;index.htmlversion.json 需能及时更新。

7.2 version.json 请求

插件请求时带 ?t=Date.now() 避免浏览器缓存。若网关层对 query 有异常缓存策略,需一并排查。

8. 如何验证插件已生效

8.1 查看页面源码

在浏览器中「查看网页源代码」,在 <head> 内应能看到:

html 复制代码
<script>window.pluginWebUpdateNotice_version = 'xxxxxxxx';</script>

8.2 直接访问 version.json

复制代码
https://<域名>/skyee/wealth/pluginWebUpdateNotice/web_version_by_plugin.json

返回的 version 应与当前最新部署的 commit hash 一致。

8.3 控制台

非 prod 环境可手动触发检测(若页面已挂载全局方法):

js 复制代码
window.pluginWebUpdateNotice_?.checkUpdate()

8.4 模拟发版检测

  1. 打开页面,记下 window.pluginWebUpdateNotice_version
  2. 重新 build 并部署(commit 变化)
  3. 不刷新页面,等待轮询或切走再切回标签页
  4. 应出现「发现新版本」提示

9. 与 chunk 懒加载 / 代码拆分的兼容性

本仓库 feature-optmiz 等分支对 antd/element、首页 Tab 做了按需懒加载。插件通过 window 捕获阶段 监听 <script> 加载失败:

js 复制代码
window.addEventListener('error', (e) => {
  if (e.target?.tagName === 'SCRIPT') checkUpdate()
}, true)

该监听与业务代码中 import()try/catch 互不冲突:即使 Promise 被 catch,浏览器仍会派发 script error 事件,从而触发版本检测。懒加载反而更容易在发版后触发 chunk 404 检测路径。

10. 常见问题

Q1:本地 localhost 开发会看到更新提示吗?

一般不明显。serve 模式有 HMR,且每次编译版本可能变化,不适合作为线上行为参考。以 build 后部署到 sit/uat/prod 为准。

Q2:同一 commit 重复 build,版本号会变吗?

不会。只要 git rev-parse --short HEAD 结果相同,版本号就相同。未提交的新改动不影响 hash(除非 HEAD 本身变化)。

Q3:CI 浅克隆(git clone --depth 1)有影响吗?

无影响。浅克隆仍包含当前 HEAD 的 commit hash,插件能正常读取。

Q4:如何临时关闭某次部署的更新提示?

构建时设置 silence: true(需改插件配置或发版流程),该次构建的 version.jsonsilence 为 true 时不会弹窗。

Q5:多页面(MPA)每个入口都有独立版本吗?

同一次 build 共用同一 git hash,各入口 index.html 注入的版本号相同;各入口目录下各有自己的 pluginWebUpdateNotice/ 路径(由 publicPath 决定)。

11. 相关文件与依赖

路径 / 包 说明
vue.config.js 插件注册
package.json@plugin-web-update-notification/webpack 依赖版本 ^1.6.4
config/build.js 统一 build 入口,透传 --mode=sit
config/tools.js MPA pages / publicPath 配置
构建产物 pluginWebUpdateNotice/web_version_by_plugin.json 运行时轮询的版本文件

12. 流程总览

用户 用户浏览器(旧页面) 静态服务器 webpack 构建 开发者/CI 用户 用户浏览器(旧页面) 静态服务器 webpack 构建 开发者/CI #mermaid-svg-w2hvhP0U1lbhbb3I{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-w2hvhP0U1lbhbb3I .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-w2hvhP0U1lbhbb3I .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-w2hvhP0U1lbhbb3I .error-icon{fill:#552222;}#mermaid-svg-w2hvhP0U1lbhbb3I .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-w2hvhP0U1lbhbb3I .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-w2hvhP0U1lbhbb3I .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-w2hvhP0U1lbhbb3I .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-w2hvhP0U1lbhbb3I .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-w2hvhP0U1lbhbb3I .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-w2hvhP0U1lbhbb3I .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-w2hvhP0U1lbhbb3I .marker{fill:#333333;stroke:#333333;}#mermaid-svg-w2hvhP0U1lbhbb3I .marker.cross{stroke:#333333;}#mermaid-svg-w2hvhP0U1lbhbb3I svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-w2hvhP0U1lbhbb3I p{margin:0;}#mermaid-svg-w2hvhP0U1lbhbb3I .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-w2hvhP0U1lbhbb3I text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-w2hvhP0U1lbhbb3I .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-w2hvhP0U1lbhbb3I .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-w2hvhP0U1lbhbb3I .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-w2hvhP0U1lbhbb3I .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-w2hvhP0U1lbhbb3I #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-w2hvhP0U1lbhbb3I .sequenceNumber{fill:white;}#mermaid-svg-w2hvhP0U1lbhbb3I #sequencenumber{fill:#333;}#mermaid-svg-w2hvhP0U1lbhbb3I #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-w2hvhP0U1lbhbb3I .messageText{fill:#333;stroke:none;}#mermaid-svg-w2hvhP0U1lbhbb3I .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-w2hvhP0U1lbhbb3I .labelText,#mermaid-svg-w2hvhP0U1lbhbb3I .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-w2hvhP0U1lbhbb3I .loopText,#mermaid-svg-w2hvhP0U1lbhbb3I .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-w2hvhP0U1lbhbb3I .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-w2hvhP0U1lbhbb3I .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-w2hvhP0U1lbhbb3I .noteText,#mermaid-svg-w2hvhP0U1lbhbb3I .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-w2hvhP0U1lbhbb3I .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-w2hvhP0U1lbhbb3I .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-w2hvhP0U1lbhbb3I .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-w2hvhP0U1lbhbb3I .actorPopupMenu{position:absolute;}#mermaid-svg-w2hvhP0U1lbhbb3I .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-w2hvhP0U1lbhbb3I .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-w2hvhP0U1lbhbb3I .actor-man circle,#mermaid-svg-w2hvhP0U1lbhbb3I line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-w2hvhP0U1lbhbb3I :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 用户此前已打开页面 localVersion = 084cb6697 localVersion 更新为 5bee76407 npm run build:skyee:wealth:sit execSync('git rev-parse --short HEAD') 部署 index.html + version.json + 静态资源 新 commit 再次 build 部署新版本 version.json = 5bee76407 fetch version.json?t=... { version: 5bee76407 } 084cb6697 !== 5bee76407 弹窗「发现新版本,请刷新」 点击刷新 加载新 index.html


文档维护 :若调整 vue.config.js 中插件配置或更换版本策略,请同步更新本文档。

相关推荐
写不来代码的草莓熊1 小时前
Cesium 地图交互绘制圆形、多边形
前端·javascript·地图
禅思院2 小时前
流式 Markdown 渲染完全指南【引子】
前端·架构·前端框架
JackSparrow4142 小时前
前端安全之JS混淆+请求加密+请求签名以提升爬虫难度
前端·javascript·后端·爬虫·python·安全
IMPYLH2 小时前
HTML 的 <em> 元素
java·前端·html
名为沙丁鱼的猫7294 小时前
【审计日志组件实践复盘】前端工程化 + 后端DDD架构(AOP切面拦截日志) + MBG
前端·架构
xqqxqxxq5 小时前
AI智能旅游规划系统 - 前端技术笔记
前端·笔记·旅游
陈随易11 小时前
bm2,MoonBit实现的pm2替代品
前端·后端·程序员
a11177613 小时前
农业数字孪生大屏网页 html开源
前端·html
圣光SG13 小时前
web操作题练习(六)
前端