vue2server7 7.0.18 → 7.0.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,277 +0,0 @@
1
- # 前端版本热更新检测
2
-
3
- ## 概述
4
-
5
- 本项目实现了一套前端版本热更新检测机制,用于在系统发布新版本后,**自动通知用户刷新页面**以获取最新代码。
6
-
7
- 核心思路:每次构建生成唯一版本号,运行时通过 Web Worker 后台轮询版本文件,发现版本不一致即弹窗提示。
8
-
9
- ---
10
-
11
- ## 涉及文件
12
-
13
- | 文件 | 层级 | 职责 |
14
- |------|------|------|
15
- | `build/vite-plugin-version.ts` | 构建层 | Vite 插件,构建时生成版本号并写入 HTML 和 JSON |
16
- | `src/utils/version-update.ts` | 运行层(主线程) | 初始化检测、管理 Worker 生命周期、弹窗交互 |
17
- | `src/utils/version-check.worker.ts` | 运行层(Worker 线程) | 后台定时轮询 `version.json` 并比对版本号 |
18
- | `src/main.ts` | 入口层 | 应用启动后调用 `initVersionCheck()` |
19
-
20
- ---
21
-
22
- ## 架构总览
23
-
24
- ```
25
- ┌─────────────────────────────────────────────────────────────────┐
26
- │ 构建阶段 (Vite Build) │
27
- │ │
28
- │ vite-plugin-version.ts │
29
- │ ├─ 生成版本号: "1742000000000.a3f2k1" │
30
- │ ├─ 注入 <meta name="app-version" content="..."> 到 index.html │
31
- │ └─ 写入 dist/version.json { "version": "..." } │
32
- └─────────────────────────────────────────────────────────────────┘
33
-
34
-
35
- ┌─────────────────────────────────────────────────────────────────┐
36
- │ 运行阶段 (Browser Runtime) │
37
- │ │
38
- │ main.ts │
39
- │ └─ initVersionCheck() │
40
- │ │ │
41
- │ ▼ │
42
- │ version-update.ts (主线程) │
43
- │ ├─ 读取 <meta> 中的当前版本号 │
44
- │ ├─ 创建 Web Worker │
45
- │ ├─ 监听 Worker 消息 │
46
- │ ├─ 监听 visibilitychange 事件 │
47
- │ │ │
48
- │ │ postMessage onmessage │
49
- │ │ ─────────────► ┌──────────────────────┐ │
50
- │ │ │ version-check │ │
51
- │ │ ◄───────────── │ .worker.ts │ │
52
- │ │ postMessage │ (Worker 线程) │ │
53
- │ │ │ │ │
54
- │ │ │ 每 60s fetch │ │
55
- │ │ │ version.json │ │
56
- │ │ └──────────────────────┘ │
57
- │ │ │ │
58
- │ │ ▼ │
59
- │ │ 版本不一致? │
60
- │ │ ├─ 是 → postMessage('update-available') │
61
- │ │ └─ 否 → 等待下一轮 │
62
- │ │ │
63
- │ ▼ │
64
- │ 弹窗提示用户 │
65
- │ ├─ "立即刷新" → window.location.reload() │
66
- │ └─ "稍后提醒" → 5 分钟后重新轮询 │
67
- └─────────────────────────────────────────────────────────────────┘
68
- ```
69
-
70
- ---
71
-
72
- ## 详细流程
73
-
74
- ### 1. 构建阶段
75
-
76
- **文件:`build/vite-plugin-version.ts`**
77
-
78
- 构建时,Vite 插件自动执行以下操作:
79
-
80
- 1. **生成版本号**:使用 `Date.now()` + 6 位随机字符串,确保每次构建唯一
81
- ```
82
- 例如:"1742000000000.a3f2k1"
83
- ```
84
-
85
- 2. **注入 HTML meta 标签**:通过 `transformIndexHtml` 钩子,在 `<head>` 中注入:
86
- ```html
87
- <meta name="app-version" content="1742000000000.a3f2k1">
88
- ```
89
- 这个标签会随 `index.html` 一起被浏览器缓存,代表用户当前加载的版本。
90
-
91
- 3. **生成 version.json**:通过 `closeBundle` 钩子,在产物目录写入:
92
- ```json
93
- { "version": "1742000000000.a3f2k1" }
94
- ```
95
- 这个文件部署后可被远程访问,代表服务器上的最新版本。
96
-
97
- > 插件设置了 `apply: 'build'`,仅在生产构建时生效,开发模式不产生任何输出。
98
-
99
- ---
100
-
101
- ### 2. 应用启动
102
-
103
- **文件:`src/main.ts`**
104
-
105
- 应用挂载到 DOM 后调用 `initVersionCheck()`:
106
-
107
- ```typescript
108
- app.mount('#app');
109
-
110
- import { initVersionCheck } from './utils/version-update';
111
- initVersionCheck();
112
- ```
113
-
114
- 放在 `mount()` 之后是因为需要确保 DOM 中的 `<meta>` 标签已经可以被 `querySelector` 读取到。
115
-
116
- ---
117
-
118
- ### 3. 初始化检测
119
-
120
- **文件:`src/utils/version-update.ts` → `initVersionCheck()`**
121
-
122
- ```
123
- 开发环境?─── 是 ──→ 直接返回(不检测)
124
-
125
-
126
-
127
- 读取 <meta name="app-version"> 的 content
128
-
129
- 空?─── 是 ──→ 直接返回
130
-
131
-
132
-
133
- 创建 Web Worker(version-check.worker.ts)
134
-
135
- 失败?── 是 ──→ 静默降级,不检测
136
-
137
-
138
-
139
- 监听 Worker 的 onmessage 事件
140
-
141
-
142
- 发送 start 指令给 Worker(携带版本号、URL、轮询间隔)
143
-
144
-
145
- 注册 visibilitychange 事件监听
146
- ```
147
-
148
- ---
149
-
150
- ### 4. 后台轮询
151
-
152
- **文件:`src/utils/version-check.worker.ts`**
153
-
154
- Worker 收到 `start` 指令后:
155
-
156
- 1. **立即执行一次检测**(不等第一个间隔周期)
157
- 2. **启动定时器**,默认每 **60 秒** 执行一次检测
158
- 3. 每次检测:
159
- - `fetch` 请求 `version.json?t={timestamp}`(时间戳参数绕过 CDN/浏览器缓存)
160
- - 解析返回的 JSON,提取 `version` 字段
161
- - 与启动时传入的 `currentVersion` 比较
162
- - **不一致** → `postMessage({ type: 'update-available' })` 通知主线程,并停止轮询
163
- - **一致** → 什么都不做,等待下一轮
164
- - **网络错误** → 静默忽略,下一轮自动重试
165
-
166
- ---
167
-
168
- ### 5. 页面可见性优化
169
-
170
- **文件:`src/utils/version-update.ts` → `handleVisibilityChange()`**
171
-
172
- 监听 `document.visibilitychange` 事件:
173
-
174
- | 场景 | 动作 | 目的 |
175
- |------|------|------|
176
- | 用户切到其他标签页 / 最小化窗口 | 发送 `pause` → Worker 清除定时器 | 避免后台无意义的网络请求 |
177
- | 用户切回页面 | 发送 `resume` → Worker 重新启动轮询 | 回到前台立即检测一次并恢复定时 |
178
-
179
- ---
180
-
181
- ### 6. 弹窗交互
182
-
183
- **文件:`src/utils/version-update.ts` → `showUpdateDialog()`**
184
-
185
- 主线程收到 `update-available` 消息后,使用 TDesign 的 `DialogPlugin.confirm` 弹出确认框:
186
-
187
- ```
188
- ┌─────────────────────────────────┐
189
- │ 发现新版本 │
190
- │ │
191
- │ 系统已更新,请刷新页面 │
192
- │ 以获取最新功能和修复。 │
193
- │ │
194
- │ [稍后提醒] [立即刷新] │
195
- └─────────────────────────────────┘
196
- ```
197
-
198
- 用户操作对应的行为:
199
-
200
- | 操作 | 行为 |
201
- |------|------|
202
- | **立即刷新** | `window.location.reload()` 强制刷新,加载新版本 |
203
- | **稍后提醒** | 进入 5 分钟静默期,之后重新开始轮询 |
204
- | **关闭弹窗**(点 X) | 同"稍后提醒" |
205
-
206
- ---
207
-
208
- ### 7. 稍后提醒机制
209
-
210
- **文件:`src/utils/version-update.ts` → `scheduleRecheck()`**
211
-
212
- 用户选择"稍后提醒"后:
213
-
214
- 1. 重置 `hasNotified = false`(允许再次弹窗)
215
- 2. 设置 **5 分钟**(`SNOOZE_INTERVAL`)的 `setTimeout`
216
- 3. 超时后重新向 Worker 发送 `start` 指令,开始新一轮轮询
217
- 4. 检测到新版本后再次弹窗,如此循环直到用户刷新
218
-
219
- ---
220
-
221
- ### 8. 销毁清理
222
-
223
- **文件:`src/utils/version-update.ts` → `destroyVersionCheck()`**
224
-
225
- 应用卸载时调用,执行以下清理:
226
-
227
- 1. 移除 `visibilitychange` 事件监听
228
- 2. 向 Worker 发送 `stop` 指令(清除 Worker 内部定时器)
229
- 3. `worker.terminate()` 终止 Worker 线程
230
- 4. 置空 Worker 引用
231
-
232
- ---
233
-
234
- ## 时间参数
235
-
236
- | 参数 | 值 | 说明 |
237
- |------|------|------|
238
- | `POLL_INTERVAL` | 60 秒 | Worker 正常轮询间隔 |
239
- | `SNOOZE_INTERVAL` | 5 分钟 | 用户点击"稍后提醒"后的静默期 |
240
-
241
- ---
242
-
243
- ## 为什么使用 Web Worker?
244
-
245
- 1. **不阻塞主线程**:`setInterval` + `fetch` 在 Worker 中执行,不影响 UI 渲染和交互响应
246
- 2. **独立生命周期**:Worker 有自己的事件循环,即使主线程繁忙(如大量 DOM 操作),轮询也不会被延迟
247
- 3. **干净的通信模型**:通过 `postMessage` 解耦,主线程只关心"有没有新版本",不关心轮询细节
248
-
249
- ---
250
-
251
- ## 开发环境行为
252
-
253
- 开发环境下,版本检测**完全不生效**,双重保障:
254
-
255
- 1. **构建层**:`vite-plugin-version` 设置了 `apply: 'build'`,`dev` 模式不注入 meta、不生成 version.json
256
- 2. **运行层**:`initVersionCheck()` 第一行 `if (import.meta.env.DEV) return` 直接跳过
257
-
258
- ---
259
-
260
- ## 缓存策略
261
-
262
- - **`version.json`**:请求时附带 `?t={timestamp}` 查询参数,绕过浏览器缓存和 CDN 缓存,确保每次拿到最新内容
263
- - **`index.html`**:服务器应配置为 `Cache-Control: no-cache`,确保用户刷新后能拿到最新的 HTML(包含新的 meta 标签)
264
- - **JS/CSS 资源**:Vite 构建产物自带 hash 文件名,天然支持长期缓存,新版本会生成新的文件名
265
-
266
- ---
267
-
268
- ## 边界场景处理
269
-
270
- | 场景 | 处理方式 |
271
- |------|------|
272
- | 浏览器不支持 Web Worker | `try/catch` 捕获异常,静默降级,不影响正常使用 |
273
- | `<meta>` 标签缺失 | `initVersionCheck()` 检查到空值后直接返回 |
274
- | 网络请求失败 | Worker 中 `catch` 静默忽略,等下一轮自动重试 |
275
- | `version.json` 返回非 200 | `res.ok` 检查不通过,跳过本次比对 |
276
- | 用户反复点击"稍后提醒" | 每次都重新进入 5 分钟静默期,不会叠加弹窗 |
277
- | 同一检测周期内重复触发 | `hasNotified` 标记防止重复弹窗 |
@@ -1,35 +0,0 @@
1
- import { writeFileSync } from 'node:fs';
2
- import { resolve } from 'node:path';
3
-
4
- import type { Plugin } from 'vite';
5
-
6
- export function vitePluginVersion(): Plugin {
7
- const version = `${Date.now()}.${Math.random().toString(36).slice(2, 8)}`;
8
- let outDir: string;
9
-
10
- return {
11
- name: 'vite-plugin-version',
12
- apply: 'build',
13
-
14
- configResolved(config) {
15
- outDir = resolve(config.root, config.build.outDir);
16
- },
17
-
18
- transformIndexHtml() {
19
- return [
20
- {
21
- tag: 'meta',
22
- attrs: { name: 'app-version', content: version },
23
- injectTo: 'head' as const,
24
- },
25
- ];
26
- },
27
-
28
- closeBundle() {
29
- const filePath = resolve(outDir, 'version.json');
30
- writeFileSync(filePath, JSON.stringify({ version }, null, 2));
31
-
32
- console.log(`\n✅ Version file generated: ${version}\n`);
33
- },
34
- };
35
- }
@@ -1,46 +0,0 @@
1
- import path from 'node:path';
2
-
3
- import vue from '@vitejs/plugin-vue';
4
- import vueJsx from '@vitejs/plugin-vue-jsx';
5
- import type { ConfigEnv, UserConfig } from 'vite';
6
- import { loadEnv } from 'vite';
7
- import svgLoader from 'vite-svg-loader';
8
-
9
- import { vitePluginVersion } from './build/vite-plugin-version';
10
-
11
- const CWD = process.cwd();
12
-
13
- // https://vitejs.dev/config/
14
- export default ({ mode }: ConfigEnv): UserConfig => {
15
- const { VITE_BASE_URL, VITE_API_URL_PREFIX } = loadEnv(mode, CWD);
16
- return {
17
- base: VITE_BASE_URL,
18
- resolve: {
19
- alias: {
20
- '@': path.resolve(__dirname, './src'),
21
- },
22
- },
23
-
24
- css: {
25
- preprocessorOptions: {
26
- less: {
27
- modifyVars: {
28
- hack: `true; @import (reference) "${path.resolve('src/style/variables.less')}";`,
29
- },
30
- math: 'strict',
31
- javascriptEnabled: true,
32
- },
33
- },
34
- },
35
-
36
- plugins: [vue(), vueJsx(), svgLoader(), vitePluginVersion()],
37
-
38
- server: {
39
- port: 3002,
40
- host: '0.0.0.0',
41
- proxy: {
42
- [VITE_API_URL_PREFIX]: 'http://127.0.0.1:3000/',
43
- },
44
- },
45
- };
46
- };