version-check-js 1.1.0
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.
- package/LICENSE +21 -0
- package/README.en.md +366 -0
- package/README.md +341 -0
- package/dist/index.esm.js +2 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/package.json +42 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 ybchen292
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.en.md
ADDED
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
# VersionCheck.js
|
|
2
|
+
|
|
3
|
+
> A lightweight front-end version detection tool that supports automatic polling detection, manual detection, ETag mode and version file mode, with built-in native prompt dialogs. Supports custom prompts and callback functions. Suitable for any HTML-based front-end projects.
|
|
4
|
+
|
|
5
|
+
[中文版本](./README.md) | [Gitee](https://gitee.com/ybchen292/version-check) | [GitHub](https://github.com/ybchen292/version-check)
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 🌟 Key Features
|
|
10
|
+
|
|
11
|
+
- **Intelligent Dual-mode Detection**:
|
|
12
|
+
- **ETag Mode** (Default): Detect new versions by checking the `ETag` response header of the entry HTML file
|
|
13
|
+
- **Version File Mode**: Detect new versions by checking the `version` field in a specified JSON file
|
|
14
|
+
- **Automated Detection**: Automatically detects every 5 minutes by default, customizable interval
|
|
15
|
+
- **Flexible Manual Detection**: Support for actively calling detection methods
|
|
16
|
+
- **Smart Page Management**: Automatically pauses detection when page is hidden, resumes when page is visible
|
|
17
|
+
- **Highly Configurable**: Support for custom prompt messages, update callbacks, error handling, log processing, etc.
|
|
18
|
+
- **Multi-environment Compatibility**: Supports UMD module specification, can be used in CommonJS, AMD and browser global environments
|
|
19
|
+
- **Robust Storage Mechanism**: Automatic downgrade from localStorage → memory storage
|
|
20
|
+
- **Complete Error Handling**: Distinguishes between operation logs and error logs, provides detailed error information
|
|
21
|
+
|
|
22
|
+
## 📦 Installation Methods
|
|
23
|
+
|
|
24
|
+
### 1. Via `<script>` Tag (Browser Environment)
|
|
25
|
+
|
|
26
|
+
```html
|
|
27
|
+
<!-- Production environment -->
|
|
28
|
+
<script src="dist/index.js"></script>
|
|
29
|
+
|
|
30
|
+
<!-- Or using CDN -->
|
|
31
|
+
<script src="https://cdn.jsdelivr.net/npm/version-check-js@latest/dist/index.js"></script>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### 2. Via NPM (Node.js Environment)
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
# Install latest version
|
|
38
|
+
npm install version-check-js
|
|
39
|
+
|
|
40
|
+
# Or using yarn
|
|
41
|
+
yarn add version-check-js
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Then import in your code:
|
|
45
|
+
|
|
46
|
+
```javascript
|
|
47
|
+
// CommonJS way
|
|
48
|
+
const VersionCheck = require('version-check-js');
|
|
49
|
+
|
|
50
|
+
// ES6 module way
|
|
51
|
+
import VersionCheck from 'version-check-js';
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 3. Via unpkg CDN
|
|
55
|
+
|
|
56
|
+
```html
|
|
57
|
+
<script src="https://unpkg.com/version-check-js@latest/dist/index.js"></script>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## 🚀 Quick Start
|
|
63
|
+
|
|
64
|
+
### Basic Usage
|
|
65
|
+
|
|
66
|
+
```javascript
|
|
67
|
+
// Instantiate VersionCheck
|
|
68
|
+
const versionCheck = new VersionCheck({
|
|
69
|
+
url: '/version.json', // Specify version file path (or default to '/' for ETag mode)
|
|
70
|
+
interval: 60 * 1000, // Set detection interval to 1 minute
|
|
71
|
+
message: 'New version found, refresh now?', // Custom prompt message
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Start automatic detection
|
|
75
|
+
versionCheck.start();
|
|
76
|
+
|
|
77
|
+
// Stop automatic detection
|
|
78
|
+
// versionCheck.stop();
|
|
79
|
+
|
|
80
|
+
// Destroy instance
|
|
81
|
+
// versionCheck.destroy();
|
|
82
|
+
|
|
83
|
+
// Manually trigger one detection
|
|
84
|
+
versionCheck.check().then(hasUpdate => {
|
|
85
|
+
console.log('Has update:', hasUpdate);
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## ⚙️ Configuration Options Detailed
|
|
92
|
+
|
|
93
|
+
| Parameter | Type | Default Value | Description |
|
|
94
|
+
| ---------- | ---------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------ |
|
|
95
|
+
| `url` | `string` | `'/'` | Detection address:<br>- Default `'/'`: Enable ETag mode<br>- File path (e.g., `/version.json`): Enable version file mode |
|
|
96
|
+
| `interval` | `number` | `10 * 60 * 1000` (10 minutes) | Polling detection interval (milliseconds), recommended not less than 30 seconds |
|
|
97
|
+
| `message` | `string` | `'New version detected, refresh now?'` | Update prompt message, only effective when `onUpdate` is not set |
|
|
98
|
+
| `onUpdate` | `Function` | `null` | Custom update callback function (higher priority than default confirm dialog) |
|
|
99
|
+
| `onError` | `Function` | `(err) => console.error('Version check failed:', err)` | Error callback function, receives error object as parameter |
|
|
100
|
+
| `onLog` | `Function` | `(msg) => console.log('VersionCheck:', msg)` | Operation log callback function, used to record normal operation information |
|
|
101
|
+
| `storage` | `Object` | `null` | Custom storage configuration (requires `get`, `set`, `remove` methods), defaults to localStorage |
|
|
102
|
+
|
|
103
|
+
### Configuration Best Practices
|
|
104
|
+
|
|
105
|
+
```javascript
|
|
106
|
+
const versionCheck = new VersionCheck({
|
|
107
|
+
// Basic configuration
|
|
108
|
+
url: '/api/version', // Recommended to use specific API interface
|
|
109
|
+
interval: 5 * 60 * 1000, // 5-minute detection (recommended for production)
|
|
110
|
+
|
|
111
|
+
// User experience optimization
|
|
112
|
+
message: 'New version available, update now?',
|
|
113
|
+
|
|
114
|
+
// Custom callbacks
|
|
115
|
+
onUpdate: () => {
|
|
116
|
+
// Custom update logic
|
|
117
|
+
console.log('Executing update...');
|
|
118
|
+
// Can add animations, notifications, etc.
|
|
119
|
+
window.location.reload();
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
// Error handling
|
|
123
|
+
onError: error => {
|
|
124
|
+
// Production environment can send error logs to monitoring system
|
|
125
|
+
console.error('Version check exception:', error);
|
|
126
|
+
// Sentry.captureException(error);
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
// Operation logs
|
|
130
|
+
onLog: message => {
|
|
131
|
+
// Record operation logs for debugging
|
|
132
|
+
console.log('VersionCheck:', message);
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## 🔧 Complete API Documentation
|
|
138
|
+
|
|
139
|
+
### Instance Methods
|
|
140
|
+
|
|
141
|
+
#### `start()`
|
|
142
|
+
|
|
143
|
+
Start automatic polling detection.
|
|
144
|
+
|
|
145
|
+
```javascript
|
|
146
|
+
versionCheck.start(); // Returns undefined
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
#### `stop([isInternal])`
|
|
150
|
+
|
|
151
|
+
Stop automatic polling detection.
|
|
152
|
+
|
|
153
|
+
```javascript
|
|
154
|
+
versionCheck.stop(); // External call, triggers onLog
|
|
155
|
+
versionCheck.stop(true); // Internal call, doesn't trigger onLog
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
#### `check()`
|
|
159
|
+
|
|
160
|
+
Manually trigger one detection, returns Promise.
|
|
161
|
+
|
|
162
|
+
```javascript
|
|
163
|
+
try {
|
|
164
|
+
const hasUpdate = await versionCheck.check();
|
|
165
|
+
if (hasUpdate) {
|
|
166
|
+
console.log('New version detected!');
|
|
167
|
+
}
|
|
168
|
+
} catch (error) {
|
|
169
|
+
console.error('Detection failed:', error);
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
#### `reload()`
|
|
174
|
+
|
|
175
|
+
Force refresh page, automatically handles URL parameter deduplication.
|
|
176
|
+
|
|
177
|
+
```javascript
|
|
178
|
+
versionCheck.reload(); // Will add timestamp parameter to avoid cache
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
#### `destroy()`
|
|
182
|
+
|
|
183
|
+
Destroy instance, clean up all resources (timers, event listeners, storage references, etc.).
|
|
184
|
+
|
|
185
|
+
```javascript
|
|
186
|
+
versionCheck.destroy(); // Instance cannot be used again after destruction
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Static Properties
|
|
190
|
+
|
|
191
|
+
#### `checkMode`
|
|
192
|
+
|
|
193
|
+
Get current detection mode.
|
|
194
|
+
|
|
195
|
+
```javascript
|
|
196
|
+
console.log(versionCheck.checkMode); // 'etag' or 'file'
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
#### `isRunning`
|
|
200
|
+
|
|
201
|
+
Get current detection status.
|
|
202
|
+
|
|
203
|
+
```javascript
|
|
204
|
+
console.log(versionCheck.isRunning); // boolean
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## 📝 Usage Scenarios and Examples
|
|
208
|
+
|
|
209
|
+
### Integration with Axios Interceptors
|
|
210
|
+
|
|
211
|
+
```javascript
|
|
212
|
+
// axios configuration
|
|
213
|
+
import axios from 'axios';
|
|
214
|
+
import VersionCheck from 'version-check-js';
|
|
215
|
+
|
|
216
|
+
const versionCheck = new VersionCheck({
|
|
217
|
+
url: '/api/version',
|
|
218
|
+
interval: 300000,
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// Perform version check in request interceptor
|
|
222
|
+
axios.interceptors.request.use(
|
|
223
|
+
async config => {
|
|
224
|
+
if (process.env.NODE_ENV === 'production') {
|
|
225
|
+
try {
|
|
226
|
+
await versionCheck.check();
|
|
227
|
+
} catch (error) {
|
|
228
|
+
console.warn('Version check failed:', error);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return config;
|
|
232
|
+
},
|
|
233
|
+
error => {
|
|
234
|
+
return Promise.reject(error);
|
|
235
|
+
}
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
versionCheck.start();
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## 📝 Usage Scenarios and Examples
|
|
242
|
+
|
|
243
|
+
### Integration with Axios Interceptors
|
|
244
|
+
|
|
245
|
+
```javascript
|
|
246
|
+
// axios configuration
|
|
247
|
+
import axios from 'axios';
|
|
248
|
+
import VersionCheck from 'version-check-js';
|
|
249
|
+
|
|
250
|
+
const versionCheck = new VersionCheck({
|
|
251
|
+
url: '/api/version',
|
|
252
|
+
interval: 300000,
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// Perform version check in request interceptor
|
|
256
|
+
axios.interceptors.request.use(
|
|
257
|
+
async config => {
|
|
258
|
+
if (process.env.NODE_ENV === 'production') {
|
|
259
|
+
try {
|
|
260
|
+
await versionCheck.check().then(flag => {
|
|
261
|
+
console.log(flag);
|
|
262
|
+
});
|
|
263
|
+
} catch (error) {
|
|
264
|
+
console.warn('Version check failed:', error);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return config;
|
|
268
|
+
},
|
|
269
|
+
error => {
|
|
270
|
+
return Promise.reject(error);
|
|
271
|
+
}
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
versionCheck.start();
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
### Storage Strategy Configuration
|
|
280
|
+
|
|
281
|
+
```javascript
|
|
282
|
+
// Custom storage adapter
|
|
283
|
+
const customStorage = {
|
|
284
|
+
get: function (key) {
|
|
285
|
+
try {
|
|
286
|
+
return localStorage.getItem(key);
|
|
287
|
+
} catch (e) {
|
|
288
|
+
// Fallback to cookie
|
|
289
|
+
return this._getFromCookie(key);
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
|
|
293
|
+
set: function (key, value) {
|
|
294
|
+
try {
|
|
295
|
+
localStorage.setItem(key, value);
|
|
296
|
+
return true;
|
|
297
|
+
} catch (e) {
|
|
298
|
+
// Fallback to cookie
|
|
299
|
+
return this._setToCookie(key, value);
|
|
300
|
+
}
|
|
301
|
+
},
|
|
302
|
+
// Cookie operation methods
|
|
303
|
+
_getFromCookie: function (key) {
|
|
304
|
+
/* ... */
|
|
305
|
+
},
|
|
306
|
+
_setToCookie: function (key, value) {
|
|
307
|
+
/* ... */
|
|
308
|
+
},
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
const versionCheck = new VersionCheck({
|
|
312
|
+
storage: customStorage,
|
|
313
|
+
});
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### Error Monitoring Integration
|
|
317
|
+
|
|
318
|
+
```javascript
|
|
319
|
+
const versionCheck = new VersionCheck({
|
|
320
|
+
onError: error => {
|
|
321
|
+
// Send to custom monitoring API
|
|
322
|
+
fetch('/api/error-report', {
|
|
323
|
+
method: 'POST',
|
|
324
|
+
body: JSON.stringify({
|
|
325
|
+
error: error.message,
|
|
326
|
+
stack: error.stack,
|
|
327
|
+
timestamp: Date.now(),
|
|
328
|
+
}),
|
|
329
|
+
});
|
|
330
|
+
},
|
|
331
|
+
|
|
332
|
+
onLog: message => {
|
|
333
|
+
// Record operation logs
|
|
334
|
+
},
|
|
335
|
+
});
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Performance Optimization Recommendations
|
|
339
|
+
|
|
340
|
+
```javascript
|
|
341
|
+
// Production environment configuration
|
|
342
|
+
const prodConfig = {
|
|
343
|
+
url: '/api/version',
|
|
344
|
+
interval: 5 * 60 * 1000, // 5 minutes (avoid too frequent)
|
|
345
|
+
onError: error => {
|
|
346
|
+
// Production environment silent handling, avoid affecting user experience
|
|
347
|
+
console.debug('Version check error:', error.message);
|
|
348
|
+
},
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
// Development environment configuration
|
|
352
|
+
const devConfig = {
|
|
353
|
+
url: '/api/version',
|
|
354
|
+
interval: 30 * 1000, // 30 seconds (for debugging)
|
|
355
|
+
onLog: message => {
|
|
356
|
+
// Development environment detailed logs
|
|
357
|
+
console.log('🔧 VersionCheck:', message);
|
|
358
|
+
},
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
const versionCheck = new VersionCheck(process.env.NODE_ENV === 'production' ? prodConfig : devConfig);
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
## 📄 License
|
|
365
|
+
|
|
366
|
+
MIT License
|
package/README.md
ADDED
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
# VersionCheck.js
|
|
2
|
+
|
|
3
|
+
> 一个轻量级的前端版本检测工具,支持自动轮询检测、手动检测、ETag 模式和版本文件模式,内置原生提示弹窗。支持自定义提示和回调函数。适用于任何基于 HTML 的前端项目。
|
|
4
|
+
|
|
5
|
+
[English](./README.en.md) | [Gitee](https://gitee.com/ybchen292/version-check) | [GitHub](https://github.com/ybchen292/version-check)
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 🌟 功能特性
|
|
10
|
+
|
|
11
|
+
- **智能双模式检测**:
|
|
12
|
+
- **ETag 模式**(默认):通过检测入口 HTML 文件的 `ETag` 响应头判断是否有新版本
|
|
13
|
+
- **版本文件模式**:通过检测指定 JSON 文件中的 `version` 字段判断是否有新版本
|
|
14
|
+
- **自动化检测**:默认每 10 分钟自动检测一次,可自定义间隔时间
|
|
15
|
+
- **灵活的手动检测**:支持主动调用检测方法
|
|
16
|
+
- **智能页面管理**:页面隐藏时自动暂停检测,页面显示时恢复检测
|
|
17
|
+
- **高度可配置**:支持自定义提示文案、更新回调、错误处理、日志处理等
|
|
18
|
+
- **多环境兼容**:支持 UMD 模块规范,可在 CommonJS、AMD 和浏览器全局环境中使用
|
|
19
|
+
- **健壮的存储机制**:自动降级 localStorage → 内存存储
|
|
20
|
+
- **完善的错误处理**:区分操作日志和错误日志,提供详细的错误信息
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 📦 安装方式
|
|
25
|
+
|
|
26
|
+
### 1. 通过 `<script>` 标签引入(浏览器环境)
|
|
27
|
+
|
|
28
|
+
```html
|
|
29
|
+
<!-- 生产环境 -->
|
|
30
|
+
<script src="dist/index.js"></script>
|
|
31
|
+
|
|
32
|
+
<!-- 或使用 CDN -->
|
|
33
|
+
<script src="https://cdn.jsdelivr.net/npm/version-check-js@latest/dist/index.js"></script>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### 2. 通过 NPM 安装(Node.js 环境)
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# 安装最新版本
|
|
40
|
+
npm install version-check-js
|
|
41
|
+
|
|
42
|
+
# 或使用 yarn
|
|
43
|
+
yarn add version-check-js
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
然后在代码中导入:
|
|
47
|
+
|
|
48
|
+
```javascript
|
|
49
|
+
// CommonJS 方式
|
|
50
|
+
const VersionCheck = require('version-check-js');
|
|
51
|
+
|
|
52
|
+
// ES6 模块方式
|
|
53
|
+
import VersionCheck from 'version-check-js';
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 3. 通过 unpkg CDN 引入
|
|
57
|
+
|
|
58
|
+
```html
|
|
59
|
+
<script src="https://unpkg.com/version-check-js@latest/dist/index.js"></script>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 🚀 快速开始
|
|
65
|
+
|
|
66
|
+
### 基础用法
|
|
67
|
+
|
|
68
|
+
```javascript
|
|
69
|
+
// 实例化 VersionCheck
|
|
70
|
+
const versionCheck = new VersionCheck({
|
|
71
|
+
url: '/version.json', // 指定版本文件路径(或默认使用 '/' 进入 ETag 模式)
|
|
72
|
+
interval: 60 * 1000, // 设置检测间隔为 1 分钟
|
|
73
|
+
message: '发现新版本,是否立即刷新?', // 自定义提示文案
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// 启动自动检测
|
|
77
|
+
versionCheck.start();
|
|
78
|
+
|
|
79
|
+
// 停止自动检测
|
|
80
|
+
// versionCheck.stop();
|
|
81
|
+
|
|
82
|
+
// 销毁实例
|
|
83
|
+
// versionCheck.destroy();
|
|
84
|
+
|
|
85
|
+
// 手动触发一次检测
|
|
86
|
+
versionCheck.check().then(hasUpdate => {
|
|
87
|
+
console.log('是否有更新:', hasUpdate);
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## ⚙️ 配置项详解
|
|
94
|
+
|
|
95
|
+
| 参数名 | 类型 | 默认值 | 描述 |
|
|
96
|
+
| ---------- | ---------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------ |
|
|
97
|
+
| `url` | `string` | `'/'` | 检测地址:<br>- 默认 `'/'`:启用 ETag 模式<br>- 文件路径(如 `/version.json`):启用版本文件模式 |
|
|
98
|
+
| `interval` | `number` | `10 * 60 * 1000`(10 分钟) | 轮询检测间隔时间(毫秒),建议不小于 30 秒 |
|
|
99
|
+
| `message` | `string` | `'检测到新版本,是否立即刷新?'` | 更新提示文案,仅在未设置 `onUpdate` 时生效 |
|
|
100
|
+
| `onUpdate` | `Function` | `null` | 自定义更新回调函数(优先级高于默认 confirm 弹窗) |
|
|
101
|
+
| `onError` | `Function` | `(err) => console.error('版本检测失败:', err)` | 错误回调函数,接收错误对象作为参数 |
|
|
102
|
+
| `onLog` | `Function` | `(msg) => console.log('版本检测:', msg)` | 操作日志回调函数,用于记录正常操作信息 |
|
|
103
|
+
| `storage` | `Object` | `null` | 自定义存储配置(需提供 `get`、`set`、`remove` 方法),默认使用 localStorage |
|
|
104
|
+
|
|
105
|
+
### 配置项最佳实践
|
|
106
|
+
|
|
107
|
+
```javascript
|
|
108
|
+
const versionCheck = new VersionCheck({
|
|
109
|
+
// 基础配置
|
|
110
|
+
url: '/api/version', // 推荐使用具体的 API 接口
|
|
111
|
+
interval: 5 * 60 * 1000, // 5分钟检测一次(生产环境推荐)
|
|
112
|
+
|
|
113
|
+
// 用户体验优化
|
|
114
|
+
message: '发现新版本可用,是否立即更新?',
|
|
115
|
+
|
|
116
|
+
// 自定义回调
|
|
117
|
+
onUpdate: () => {
|
|
118
|
+
// 自定义更新逻辑
|
|
119
|
+
console.log('执行更新...');
|
|
120
|
+
// 可以在这里添加动画、提示等
|
|
121
|
+
window.location.reload();
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
// 错误处理
|
|
125
|
+
onError: error => {
|
|
126
|
+
// 生产环境可以发送错误日志到监控系统
|
|
127
|
+
console.error('版本检测异常:', error);
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
// 操作日志
|
|
131
|
+
onLog: message => {
|
|
132
|
+
// 记录操作日志,便于调试
|
|
133
|
+
console.log('VersionCheck:', message);
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## 🔧 完整 API 文档
|
|
141
|
+
|
|
142
|
+
### 实例方法
|
|
143
|
+
|
|
144
|
+
#### `start()`
|
|
145
|
+
|
|
146
|
+
启动自动轮询检测。
|
|
147
|
+
|
|
148
|
+
```javascript
|
|
149
|
+
versionCheck.start(); // 返回 undefined
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
#### `stop([isInternal])`
|
|
153
|
+
|
|
154
|
+
停止自动轮询检测。
|
|
155
|
+
|
|
156
|
+
```javascript
|
|
157
|
+
versionCheck.stop(); // 外部调用,会触发 onLog
|
|
158
|
+
versionCheck.stop(true); // 内部调用,不会触发 onLog
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
#### `check()`
|
|
162
|
+
|
|
163
|
+
手动触发一次检测,返回 Promise。
|
|
164
|
+
|
|
165
|
+
```javascript
|
|
166
|
+
try {
|
|
167
|
+
const hasUpdate = await versionCheck.check();
|
|
168
|
+
if (hasUpdate) {
|
|
169
|
+
console.log('检测到新版本!');
|
|
170
|
+
}
|
|
171
|
+
} catch (error) {
|
|
172
|
+
console.error('检测失败:', error);
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
#### `reload()`
|
|
177
|
+
|
|
178
|
+
强制刷新页面,自动处理 URL 参数去重。
|
|
179
|
+
|
|
180
|
+
```javascript
|
|
181
|
+
versionCheck.reload(); // 会添加时间戳参数避免缓存
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
#### `destroy()`
|
|
185
|
+
|
|
186
|
+
销毁实例,清理所有资源(定时器、事件监听器、存储引用等)。
|
|
187
|
+
|
|
188
|
+
```javascript
|
|
189
|
+
versionCheck.destroy(); // 实例销毁后不可再次使用
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### 静态属性
|
|
193
|
+
|
|
194
|
+
#### `checkMode`
|
|
195
|
+
|
|
196
|
+
获取当前检测模式。
|
|
197
|
+
|
|
198
|
+
```javascript
|
|
199
|
+
console.log(versionCheck.checkMode); // 'etag' 或 'file'
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
#### `isRunning`
|
|
203
|
+
|
|
204
|
+
获取当前检测状态。
|
|
205
|
+
|
|
206
|
+
```javascript
|
|
207
|
+
console.log(versionCheck.isRunning); // boolean
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## 📝 使用场景和示例
|
|
213
|
+
|
|
214
|
+
### 配合 Axios 拦截器使用
|
|
215
|
+
|
|
216
|
+
```javascript
|
|
217
|
+
// axios 配置
|
|
218
|
+
import axios from 'axios';
|
|
219
|
+
import VersionCheck from 'version-check-js';
|
|
220
|
+
|
|
221
|
+
const versionCheck = new VersionCheck({
|
|
222
|
+
url: '/api/version',
|
|
223
|
+
interval: 300000,
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// 请求拦截器中进行版本检测
|
|
227
|
+
axios.interceptors.request.use(
|
|
228
|
+
async config => {
|
|
229
|
+
if (process.env.NODE_ENV === 'production') {
|
|
230
|
+
try {
|
|
231
|
+
await versionCheck.check().then(flag => {
|
|
232
|
+
console.log(flag);
|
|
233
|
+
});
|
|
234
|
+
} catch (error) {
|
|
235
|
+
console.warn('版本检测失败:', error);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return config;
|
|
239
|
+
},
|
|
240
|
+
error => {
|
|
241
|
+
return Promise.reject(error);
|
|
242
|
+
}
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
versionCheck.start();
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## 🛠️ 高级配置和最佳实践
|
|
251
|
+
|
|
252
|
+
### 存储策略配置
|
|
253
|
+
|
|
254
|
+
```javascript
|
|
255
|
+
// 自定义存储适配器
|
|
256
|
+
const customStorage = {
|
|
257
|
+
get: function (key) {
|
|
258
|
+
try {
|
|
259
|
+
return localStorage.getItem(key);
|
|
260
|
+
} catch (e) {
|
|
261
|
+
// 降级到 cookie
|
|
262
|
+
return this._getFromCookie(key);
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
|
|
266
|
+
set: function (key, value) {
|
|
267
|
+
try {
|
|
268
|
+
localStorage.setItem(key, value);
|
|
269
|
+
return true;
|
|
270
|
+
} catch (e) {
|
|
271
|
+
// 降级到 cookie
|
|
272
|
+
return this._setToCookie(key, value);
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
// Cookie 操作方法
|
|
276
|
+
_getFromCookie: function (key) {
|
|
277
|
+
/* ... */
|
|
278
|
+
},
|
|
279
|
+
_setToCookie: function (key, value) {
|
|
280
|
+
/* ... */
|
|
281
|
+
},
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
const versionCheck = new VersionCheck({
|
|
285
|
+
storage: customStorage,
|
|
286
|
+
});
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### 错误监控集成
|
|
290
|
+
|
|
291
|
+
```javascript
|
|
292
|
+
const versionCheck = new VersionCheck({
|
|
293
|
+
onError: error => {
|
|
294
|
+
// 发送到自定义监控接口
|
|
295
|
+
fetch('/api/error-report', {
|
|
296
|
+
method: 'POST',
|
|
297
|
+
body: JSON.stringify({
|
|
298
|
+
error: error.message,
|
|
299
|
+
stack: error.stack,
|
|
300
|
+
timestamp: Date.now(),
|
|
301
|
+
}),
|
|
302
|
+
});
|
|
303
|
+
},
|
|
304
|
+
|
|
305
|
+
onLog: message => {
|
|
306
|
+
// 记录操作日志
|
|
307
|
+
},
|
|
308
|
+
});
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### 性能优化建议
|
|
312
|
+
|
|
313
|
+
```javascript
|
|
314
|
+
// 生产环境配置
|
|
315
|
+
const prodConfig = {
|
|
316
|
+
url: '/api/version',
|
|
317
|
+
interval: 5 * 60 * 1000, // 5分钟(避免过于频繁)
|
|
318
|
+
onError: error => {
|
|
319
|
+
// 生产环境静默处理,避免影响用户体验
|
|
320
|
+
console.debug('Version check error:', error.message);
|
|
321
|
+
},
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
// 开发环境配置
|
|
325
|
+
const devConfig = {
|
|
326
|
+
url: '/api/version',
|
|
327
|
+
interval: 30 * 1000, // 30秒(便于调试)
|
|
328
|
+
onLog: message => {
|
|
329
|
+
// 开发环境详细日志
|
|
330
|
+
console.log('🔧 VersionCheck:', message);
|
|
331
|
+
},
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
const versionCheck = new VersionCheck(process.env.NODE_ENV === 'production' ? prodConfig : devConfig);
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
## 📄 许可证
|
|
340
|
+
|
|
341
|
+
MIT License
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
class t{constructor(t={}){if("object"!=typeof t||null===t)throw new TypeError("options \u5fc5\u987b\u662f\u5bf9\u8c61\u7c7b\u578b");this.config=this.t({url:"/",interval:6e5,message:"\u68c0\u6d4b\u5230\u65b0\u7248\u672c\uff0c\u662f\u5426\u7acb\u5373\u5237\u65b0\uff1f",onUpdate:null,onError:t=>{},onLog:t=>{},storage:null},t),this.timer=null,this.memoryStorage=null,this.storageApi=this.i(),this.versionKey="version_identifier",this.checkMode=this.h(),this.isRunning=!1,this.o()}t(t,i){const s={...t,...i};if("string"!=typeof s.url||""===s.url.trim())throw new TypeError("url \u5fc5\u987b\u662f\u975e\u7a7a\u5b57\u7b26\u4e32");return("number"!=typeof s.interval||s.interval<=0)&&(this.config?.onLog("interval \u914d\u7f6e\u65e0\u6548\uff0c\u4f7f\u7528\u9ed8\u8ba4\u503c 5 \u5206\u949f"),s.interval=t.interval),"string"==typeof s.message&&""!==s.message.trim()||(this.config?.onLog("message \u914d\u7f6e\u65e0\u6548\uff0c\u4f7f\u7528\u9ed8\u8ba4\u503c"),s.message=t.message),["onUpdate","onError","onLog"].forEach(i=>{null!==s[i]&&"function"!=typeof s[i]&&(this.config?.onLog(`${i} \u5fc5\u987b\u662f\u51fd\u6570\uff0c\u4f7f\u7528\u9ed8\u8ba4\u5904\u7406`),s[i]=t[i])}),null!==s.storage&&("object"==typeof s.storage&&"function"==typeof s.storage.get&&"function"==typeof s.storage.set||(this.config?.onLog("storage \u914d\u7f6e\u65e0\u6548\uff0c\u4f7f\u7528\u9ed8\u8ba4\u5b58\u50a8"),s.storage=null)),s}o(){this.l(),this.u=this.p.bind(this),document.addEventListener("visibilitychange",this.u)}l(){this.u&&(document.removeEventListener("visibilitychange",this.u),this.u=null)}p(){this.isRunning&&(document.hidden?this.m():this.$())}m(){this.stop(!0),this.config.onLog("\u9875\u9762\u9690\u85cf\uff0c\u6682\u505c\u7248\u672c\u68c0\u6d4b")}$(){this.start(),this.config.onLog("\u9875\u9762\u663e\u793a\uff0c\u6062\u590d\u7248\u672c\u68c0\u6d4b")}i(){const t={get:t=>{if("string"!=typeof t)return this.config.onError(new Error("\u5b58\u50a8\u952e\u5fc5\u987b\u662f\u5b57\u7b26\u4e32")),null;try{if(window.localStorage)return localStorage.getItem(t)}catch(t){this.config.onError(new Error(`localStorage get \u5931\u8d25: ${t.message}`))}return this.memoryStorage},set:(t,i)=>{if("string"!=typeof t)return this.config.onError(new Error("\u5b58\u50a8\u952e\u5fc5\u987b\u662f\u5b57\u7b26\u4e32")),!1;try{if(window.localStorage)return localStorage.setItem(t,String(i)),!0}catch(t){this.config.onError(new Error(`localStorage set \u5931\u8d25: ${t.message}`))}return this.memoryStorage=String(i),!0}};return this.config.storage||t}h(){const{url:t}=this.config;return"/"!==t&&/\.\w+$/.test(t)?"file":"etag"}async _(t,i){try{const s=await fetch(t,i);if(s.status>=400&&s.status<500)return this.config.onError(new Error(`\u5ba2\u6237\u7aef\u9519\u8bef HTTP ${s.status}: ${s.statusText}`)),s;if(!s.ok)throw new Error(`HTTP ${s.status}: ${s.statusText}`);return s}catch(t){throw this.config.onError(new Error(`\u7f51\u7edc\u8bf7\u6c42\u5931\u8d25: ${t.message}`)),t}}async T(){const{url:t}=this.config;try{const i=await this._(t,{method:"HEAD",cache:"no-cache",credentials:"same-origin"});if(!i.ok)throw new Error(`ETag \u8bf7\u6c42\u5931\u8d25\uff0c\u72b6\u6001\u7801\uff1a${i.status}`);const s=i.headers.get("ETag");if(!s)throw new Error("\u670d\u52a1\u5668\u672a\u8fd4\u56de ETag\uff0c\u68c0\u6d4b\u5931\u8d25");return this.v(s)}catch(t){return this.config.onError(t),!1}}async S(){const{url:t}=this.config;try{const i=await this._(t,{method:"GET",cache:"no-cache",credentials:"same-origin",headers:{Accept:"application/json"}});if(!i.ok)throw new Error(`\u7248\u672c\u6587\u4ef6\u8bf7\u6c42\u5931\u8d25\uff0c\u72b6\u6001\u7801\uff1a${i.status}`);const s=await i.json();if(!s||"object"!=typeof s)throw new Error("\u7248\u672c\u6587\u4ef6\u683c\u5f0f\u9519\u8bef\uff0c\u5fc5\u987b\u8fd4\u56de\u5bf9\u8c61");if(!s.version)throw new Error("\u7248\u672c\u6587\u4ef6\u683c\u5f0f\u9519\u8bef\uff0c\u7f3a\u5c11 version \u5b57\u6bb5");return this.v(s.version)}catch(t){return this.config.onError(t),!1}}v(t){const i=this.storageApi.get(this.versionKey);return i?t!=i&&(this.V(t),!0):(this.storageApi.set(this.versionKey,t),!1)}V(t){const{onUpdate:i,message:s}=this.config;if("function"==typeof i)return this.storageApi.set(this.versionKey,t),void i();this.stop(!0),window.confirm(s)?(this.storageApi.set(this.versionKey,t),this.reload()):this.start()}reload(){let t=window.location.href;t=t.replace(/[?&]t=\d+/g,"");const i=t.includes("?")?"&":"?",s=`${t}${i}t=${Date.now()}`;window.location.replace(s)}async check(){return"etag"===this.checkMode?await this.T():await this.S()}start(){if(this.isRunning&&this.timer)return void this.config.onLog("\u68c0\u6d4b\u5df2\u5728\u8fd0\u884c\u4e2d");this.isRunning=!0,this.j();const t=async()=>{try{await this.check()}catch(t){this.config.onError(new Error(`\u8f6e\u8be2\u68c0\u6d4b\u5931\u8d25: ${t.message}`))}this.isRunning&&(this.timer=setTimeout(t,this.config.interval))};this.timer=setTimeout(t,this.config.interval),this.config.onLog("\u7248\u672c\u68c0\u6d4b\u5df2\u542f\u52a8")}stop(t=!1){this.j(),t||(this.isRunning=!1,this.config.onLog("\u7248\u672c\u68c0\u6d4b\u5df2\u505c\u6b62"))}j(){this.timer&&(clearTimeout(this.timer),this.timer=null)}destroy(){this.stop(),this.l(),this.memoryStorage=null,this.config=null,this.timer=null,this.storageApi=null,this.checkMode=null,this.isRunning=!1}}export{t as default};
|
|
2
|
+
//# sourceMappingURL=index.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.esm.js","sources":["../index.js"],"sourcesContent":["/**\r\n * 通用前端版本检测工具\r\n * 自动判断检测模式(ETag/版本文件)| 默认自动轮询 | 保留手动检测 | 内置原生confirm提示\r\n * @module version-check-js\r\n * @version 1.1.0\r\n */\r\nclass VersionCheck {\r\n /**\r\n * 构造函数:初始化配置和存储(极简配置)\r\n * @param {Object} options 配置项\r\n * @param {string} [options.url='/'] 检测地址(默认/:ETag模式;传文件路径如/version.json:版本文件模式)\r\n * @param {number} [options.interval=10 * 60 * 1000] 轮询间隔(毫秒),默认10分钟\r\n * @param {string} [options.message='检测到新版本,是否立即刷新?'] 更新提示文案\r\n * @param {Function} [options.onUpdate=null] 自定义更新回调(优先级高于默认confirm)\r\n * @param {Function} [options.onError=(err)=>console.error(err)] 错误回调\r\n * @param {Function} [options.onLog=(msg)=>console.log(msg)] 日志回调\r\n * @param {Object} [options.storage=null] 自定义存储配置(get/set方法)\r\n */\r\n constructor(options = {}) {\r\n // 验证输入参数类型\r\n if (typeof options !== 'object' || options === null) {\r\n throw new TypeError('options 必须是对象类型');\r\n }\r\n\r\n // 定义默认配置\r\n const defaultConfig = {\r\n url: '/',\r\n interval: 10 * 60 * 1000,\r\n message: '检测到新版本,是否立即刷新?',\r\n onUpdate: null,\r\n onError: err => console.error('版本检测失败:', err),\r\n onLog: msg => console.log('版本检测:', msg),\r\n storage: null,\r\n };\r\n\r\n // 合并配置并验证\r\n this.config = this._mergeAndValidateConfig(defaultConfig, options);\r\n\r\n // 初始化核心状态\r\n this.timer = null; // 轮询定时器\r\n this.memoryStorage = null; // 内存存储降级\r\n this.storageApi = this._initStorage(); // 存储适配器\r\n this.versionKey = 'version_identifier'; // 存储版本标识的key\r\n this.checkMode = this._getCheckMode(); // 自动判断检测模式\r\n this.isRunning = false; // 检测状态\r\n\r\n this._bindVisibilityListener(); // 绑定可见性变化监听器\r\n }\r\n\r\n /**\r\n * 合并并验证配置项\r\n * @private\r\n * @param {Object} defaults 默认配置\r\n * @param {Object} options 用户配置\r\n * @returns {Object} 合并后的配置\r\n */\r\n _mergeAndValidateConfig(defaults, options) {\r\n const config = { ...defaults, ...options };\r\n\r\n // 验证 url\r\n if (typeof config.url !== 'string' || config.url.trim() === '') {\r\n throw new TypeError('url 必须是非空字符串');\r\n }\r\n\r\n // 验证 interval\r\n if (typeof config.interval !== 'number' || config.interval <= 0) {\r\n this.config?.onLog('interval 配置无效,使用默认值 5 分钟');\r\n config.interval = defaults.interval;\r\n }\r\n\r\n // 验证 message\r\n if (typeof config.message !== 'string' || config.message.trim() === '') {\r\n this.config?.onLog('message 配置无效,使用默认值');\r\n config.message = defaults.message;\r\n }\r\n\r\n // 验证回调函数\r\n ['onUpdate', 'onError', 'onLog'].forEach(method => {\r\n if (config[method] !== null && typeof config[method] !== 'function') {\r\n this.config?.onLog(`${method} 必须是函数,使用默认处理`);\r\n config[method] = defaults[method];\r\n }\r\n });\r\n\r\n // 验证 storage\r\n if (config.storage !== null) {\r\n if (\r\n typeof config.storage !== 'object' ||\r\n typeof config.storage.get !== 'function' ||\r\n typeof config.storage.set !== 'function'\r\n ) {\r\n this.config?.onLog('storage 配置无效,使用默认存储');\r\n config.storage = null;\r\n }\r\n }\r\n\r\n return config;\r\n }\r\n\r\n /**\r\n * 绑定页面可见性变化监听器\r\n * @private\r\n */\r\n _bindVisibilityListener() {\r\n this._unbindVisibilityListener();\r\n this._visibilityHandler = this._handleVisibilityChange.bind(this);\r\n document.addEventListener('visibilitychange', this._visibilityHandler);\r\n }\r\n\r\n /**\r\n * 解绑页面可见性变化监听器\r\n * @private\r\n */\r\n _unbindVisibilityListener() {\r\n if (this._visibilityHandler) {\r\n document.removeEventListener('visibilitychange', this._visibilityHandler);\r\n this._visibilityHandler = null;\r\n }\r\n }\r\n\r\n /**\r\n * 处理页面可见性变化\r\n * @private\r\n */\r\n _handleVisibilityChange() {\r\n if (!this.isRunning) return;\r\n\r\n if (document.hidden) {\r\n this._pauseDetection();\r\n } else {\r\n this._resumeDetection();\r\n }\r\n }\r\n\r\n /**\r\n * 暂停检测(页面隐藏时)\r\n * @private\r\n */\r\n _pauseDetection() {\r\n this.stop(true);\r\n this.config.onLog('页面隐藏,暂停版本检测');\r\n }\r\n\r\n /**\r\n * 恢复检测(页面显示时)\r\n * @private\r\n */\r\n _resumeDetection() {\r\n this.start();\r\n this.config.onLog('页面显示,恢复版本检测');\r\n }\r\n\r\n /**\r\n * 初始化存储适配器(优先自定义 → localStorage → 内存)\r\n * @private\r\n * @returns {Object} 存储接口(get/set)\r\n */\r\n _initStorage() {\r\n const defaultStorage = {\r\n get: key => {\r\n if (typeof key !== 'string') {\r\n this.config.onError(new Error('存储键必须是字符串'));\r\n return null;\r\n }\r\n\r\n try {\r\n if (window.localStorage) {\r\n return localStorage.getItem(key);\r\n }\r\n } catch (e) {\r\n this.config.onError(new Error(`localStorage get 失败: ${e.message}`));\r\n }\r\n\r\n return this.memoryStorage;\r\n },\r\n\r\n set: (key, value) => {\r\n if (typeof key !== 'string') {\r\n this.config.onError(new Error('存储键必须是字符串'));\r\n return false;\r\n }\r\n\r\n try {\r\n if (window.localStorage) {\r\n localStorage.setItem(key, String(value));\r\n return true;\r\n }\r\n } catch (e) {\r\n this.config.onError(new Error(`localStorage set 失败: ${e.message}`));\r\n }\r\n\r\n this.memoryStorage = String(value);\r\n return true;\r\n },\r\n };\r\n\r\n return this.config.storage || defaultStorage;\r\n }\r\n\r\n /**\r\n * 自动判断检测模式\r\n * @private\r\n * @returns {string} 'etag' | 'file'\r\n */\r\n _getCheckMode() {\r\n const { url } = this.config;\r\n const isVersionFile = url !== '/' && /\\.\\w+$/.test(url);\r\n return isVersionFile ? 'file' : 'etag';\r\n }\r\n\r\n /**\r\n * 简化的网络请求(依赖循环调用机制)\r\n * @private\r\n * @param {string} url 请求地址\r\n * @param {Object} options 请求选项\r\n * @returns {Promise<Response>} fetch 响应\r\n */\r\n async _fetchRequest(url, options) {\r\n try {\r\n const response = await fetch(url, options);\r\n\r\n // 4xx 错误记录但不重试(客户端错误)\r\n if (response.status >= 400 && response.status < 500) {\r\n this.config.onError(new Error(`客户端错误 HTTP ${response.status}: ${response.statusText}`));\r\n return response;\r\n }\r\n\r\n if (!response.ok) {\r\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\r\n }\r\n\r\n return response;\r\n } catch (error) {\r\n this.config.onError(new Error(`网络请求失败: ${error.message}`));\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * ETag模式检测逻辑\r\n * @private\r\n * @returns {Promise<boolean>} 是否检测到更新\r\n */\r\n async _checkByEtag() {\r\n const { url } = this.config;\r\n\r\n try {\r\n const response = await this._fetchRequest(url, {\r\n method: 'HEAD',\r\n cache: 'no-cache',\r\n credentials: 'same-origin',\r\n });\r\n\r\n if (!response.ok) {\r\n throw new Error(`ETag 请求失败,状态码:${response.status}`);\r\n }\r\n\r\n const newVersion = response.headers.get('ETag');\r\n if (!newVersion) {\r\n throw new Error('服务器未返回 ETag,检测失败');\r\n }\r\n\r\n return this._compareVersion(newVersion);\r\n } catch (error) {\r\n this.config.onError(error);\r\n return false;\r\n }\r\n }\r\n\r\n /**\r\n * 版本文件模式检测逻辑\r\n * @private\r\n * @returns {Promise<boolean>} 是否检测到更新\r\n */\r\n async _checkByVersionFile() {\r\n const { url } = this.config;\r\n\r\n try {\r\n const response = await this._fetchRequest(url, {\r\n method: 'GET',\r\n cache: 'no-cache',\r\n credentials: 'same-origin',\r\n headers: {\r\n Accept: 'application/json',\r\n },\r\n });\r\n\r\n if (!response.ok) {\r\n throw new Error(`版本文件请求失败,状态码:${response.status}`);\r\n }\r\n\r\n const result = await response.json();\r\n\r\n if (!result || typeof result !== 'object') {\r\n throw new Error('版本文件格式错误,必须返回对象');\r\n }\r\n\r\n if (!result.version) {\r\n throw new Error('版本文件格式错误,缺少 version 字段');\r\n }\r\n\r\n return this._compareVersion(result.version);\r\n } catch (error) {\r\n this.config.onError(error);\r\n return false;\r\n }\r\n }\r\n\r\n /**\r\n * 对比版本标识\r\n * @private\r\n * @param {string} newVersion 最新版本标识\r\n * @returns {boolean} 是否检测到更新\r\n */\r\n _compareVersion(newVersion) {\r\n const oldVersion = this.storageApi.get(this.versionKey);\r\n\r\n if (!oldVersion) {\r\n this.storageApi.set(this.versionKey, newVersion);\r\n return false;\r\n }\r\n\r\n if (newVersion == oldVersion) return false;\r\n\r\n this._triggerUpdate(newVersion);\r\n return true;\r\n }\r\n\r\n /**\r\n * 触发更新回调\r\n * @private\r\n */\r\n _triggerUpdate(newVersion) {\r\n const { onUpdate, message } = this.config;\r\n\r\n if (typeof onUpdate === 'function') {\r\n this.storageApi.set(this.versionKey, newVersion);\r\n onUpdate();\r\n return;\r\n }\r\n\r\n this.stop(true);\r\n const isConfirm = window.confirm(message);\r\n if (isConfirm) {\r\n this.storageApi.set(this.versionKey, newVersion);\r\n this.reload();\r\n } else {\r\n this.start();\r\n }\r\n }\r\n\r\n /**\r\n * 重新加载页面(避免缓存)\r\n */\r\n reload() {\r\n let currentUrl = window.location.href;\r\n currentUrl = currentUrl.replace(/[?&]t=\\d+/g, '');\r\n const separator = currentUrl.includes('?') ? '&' : '?';\r\n const newUrl = `${currentUrl}${separator}t=${Date.now()}`;\r\n window.location.replace(newUrl);\r\n }\r\n\r\n /**\r\n * 手动触发单次检测\r\n * @returns {Promise<boolean>} 是否检测到更新\r\n */\r\n async check() {\r\n return this.checkMode === 'etag' ? await this._checkByEtag() : await this._checkByVersionFile();\r\n }\r\n\r\n /**\r\n * 启动自动轮询检测\r\n */\r\n start() {\r\n if (this.isRunning && this.timer) {\r\n this.config.onLog('检测已在运行中');\r\n return;\r\n }\r\n\r\n this.isRunning = true;\r\n this._clearTimer();\r\n\r\n // 立即执行一次检测\r\n // this.check().catch(error => this.config.onError(new Error(`首次检测失败: ${error.message}`)));\r\n\r\n // 启动轮询\r\n const poll = async () => {\r\n try {\r\n await this.check();\r\n } catch (error) {\r\n this.config.onError(new Error(`轮询检测失败: ${error.message}`));\r\n }\r\n if (this.isRunning) {\r\n this.timer = setTimeout(poll, this.config.interval);\r\n }\r\n };\r\n this.timer = setTimeout(poll, this.config.interval);\r\n\r\n this.config.onLog('版本检测已启动');\r\n }\r\n\r\n /**\r\n * 停止自动轮询检测\r\n * @param {boolean} isInternal 是否为内部调用\r\n */\r\n stop(isInternal = false) {\r\n this._clearTimer();\r\n\r\n if (!isInternal) {\r\n this.isRunning = false;\r\n this.config.onLog('版本检测已停止');\r\n }\r\n }\r\n\r\n /**\r\n * 清理定时器\r\n * @private\r\n */\r\n _clearTimer() {\r\n if (this.timer) {\r\n clearTimeout(this.timer);\r\n this.timer = null;\r\n }\r\n }\r\n\r\n /**\r\n * 销毁实例\r\n */\r\n destroy() {\r\n this.stop();\r\n this._unbindVisibilityListener();\r\n this.memoryStorage = null;\r\n this.config = null;\r\n this.timer = null;\r\n this.storageApi = null;\r\n this.checkMode = null;\r\n this.isRunning = false;\r\n\r\n console.log('VersionCheck 实例已销毁');\r\n }\r\n}\r\n\r\nexport default VersionCheck;\r\n"],"names":["VersionCheck","constructor","options","TypeError","this","config","_mergeAndValidateConfig","url","interval","message","onUpdate","onError","err","onLog","msg","storage","timer","memoryStorage","storageApi","_initStorage","versionKey","checkMode","_getCheckMode","isRunning","_bindVisibilityListener","defaults","trim","forEach","method","get","set","_unbindVisibilityListener","_visibilityHandler","_handleVisibilityChange","bind","document","addEventListener","removeEventListener","hidden","_pauseDetection","_resumeDetection","stop","start","defaultStorage","key","Error","window","localStorage","getItem","e","value","setItem","String","test","_fetchRequest","response","fetch","status","statusText","ok","error","_checkByEtag","cache","credentials","newVersion","headers","_compareVersion","_checkByVersionFile","Accept","result","json","version","oldVersion","_triggerUpdate","confirm","reload","currentUrl","location","href","replace","separator","includes","newUrl","Date","now","check","_clearTimer","poll","async","setTimeout","isInternal","clearTimeout","destroy"],"mappings":"AAMA,MAAMA,EAYJ,WAAAC,CAAYC,EAAU,IAEpB,GAAuB,iBAAZA,GAAoC,OAAZA,EACjC,MAAM,IAAIC,UAAU,sDAetBC,KAAKC,OAASD,KAAKE,EAXG,CACpBC,IAAK,IACLC,SAAU,IACVC,QAAS,uFACTC,SAAU,KACVC,QAASC,MACTC,MAAOC,MACPC,QAAS,MAI+Cb,GAG1DE,KAAKY,MAAQ,KACbZ,KAAKa,cAAgB,KACrBb,KAAKc,WAAad,KAAKe,IACvBf,KAAKgB,WAAa,qBAClBhB,KAAKiB,UAAYjB,KAAKkB,IACtBlB,KAAKmB,WAAY,EAEjBnB,KAAKoB,GACN,CASD,CAAAlB,CAAwBmB,EAAUvB,GAChC,MAAMG,EAAS,IAAKoB,KAAavB,GAGjC,GAA0B,iBAAfG,EAAOE,KAA0C,KAAtBF,EAAOE,IAAImB,OAC/C,MAAM,IAAIvB,UAAU,wDAmCtB,OA/B+B,iBAApBE,EAAOG,UAAyBH,EAAOG,UAAY,KAC5DJ,KAAKC,QAAQQ,MAAM,wFACnBR,EAAOG,SAAWiB,EAASjB,UAIC,iBAAnBH,EAAOI,SAAkD,KAA1BJ,EAAOI,QAAQiB,SACvDtB,KAAKC,QAAQQ,MAAM,wEACnBR,EAAOI,QAAUgB,EAAShB,SAI5B,CAAC,WAAY,UAAW,SAASkB,QAAQC,IAChB,OAAnBvB,EAAOuB,IAA8C,mBAAnBvB,EAAOuB,KAC3CxB,KAAKC,QAAQQ,MAAM,GAAGe,8EACtBvB,EAAOuB,GAAUH,EAASG,MAKP,OAAnBvB,EAAOU,UAEmB,iBAAnBV,EAAOU,SACgB,mBAAvBV,EAAOU,QAAQc,KACQ,mBAAvBxB,EAAOU,QAAQe,MAEtB1B,KAAKC,QAAQQ,MAAM,8EACnBR,EAAOU,QAAU,OAIdV,CACR,CAMD,CAAAmB,GACEpB,KAAK2B,IACL3B,KAAK4B,EAAqB5B,KAAK6B,EAAwBC,KAAK9B,MAC5D+B,SAASC,iBAAiB,mBAAoBhC,KAAK4B,EACpD,CAMD,CAAAD,GACM3B,KAAK4B,IACPG,SAASE,oBAAoB,mBAAoBjC,KAAK4B,GACtD5B,KAAK4B,EAAqB,KAE7B,CAMD,CAAAC,GACO7B,KAAKmB,YAENY,SAASG,OACXlC,KAAKmC,IAELnC,KAAKoC,IAER,CAMD,CAAAD,GACEnC,KAAKqC,MAAK,GACVrC,KAAKC,OAAOQ,MAAM,qEACnB,CAMD,CAAA2B,GACEpC,KAAKsC,QACLtC,KAAKC,OAAOQ,MAAM,qEACnB,CAOD,CAAAM,GACE,MAAMwB,EAAiB,CACrBd,IAAKe,IACH,GAAmB,iBAARA,EAET,OADAxC,KAAKC,OAAOM,QAAQ,IAAIkC,MAAM,2DACvB,KAGT,IACE,GAAIC,OAAOC,aACT,OAAOA,aAAaC,QAAQJ,EAE/B,CAAC,MAAOK,GACP7C,KAAKC,OAAOM,QAAQ,IAAIkC,MAAM,kCAAwBI,EAAExC,WACzD,CAED,OAAOL,KAAKa,eAGda,IAAK,CAACc,EAAKM,KACT,GAAmB,iBAARN,EAET,OADAxC,KAAKC,OAAOM,QAAQ,IAAIkC,MAAM,4DACvB,EAGT,IACE,GAAIC,OAAOC,aAET,OADAA,aAAaI,QAAQP,EAAKQ,OAAOF,KAC1B,CAEV,CAAC,MAAOD,GACP7C,KAAKC,OAAOM,QAAQ,IAAIkC,MAAM,kCAAwBI,EAAExC,WACzD,CAGD,OADAL,KAAKa,cAAgBmC,OAAOF,IACrB,IAIX,OAAO9C,KAAKC,OAAOU,SAAW4B,CAC/B,CAOD,CAAArB,GACE,MAAMf,IAAEA,GAAQH,KAAKC,OAErB,MAD8B,MAARE,GAAe,SAAS8C,KAAK9C,GAC5B,OAAS,MACjC,CASD,OAAM+C,CAAc/C,EAAKL,GACvB,IACE,MAAMqD,QAAiBC,MAAMjD,EAAKL,GAGlC,GAAIqD,EAASE,QAAU,KAAOF,EAASE,OAAS,IAE9C,OADArD,KAAKC,OAAOM,QAAQ,IAAIkC,MAAM,uCAAcU,EAASE,WAAWF,EAASG,eAClEH,EAGT,IAAKA,EAASI,GACZ,MAAM,IAAId,MAAM,QAAQU,EAASE,WAAWF,EAASG,cAGvD,OAAOH,CACR,CAAC,MAAOK,GAEP,MADAxD,KAAKC,OAAOM,QAAQ,IAAIkC,MAAM,yCAAWe,EAAMnD,YACzCmD,CACP,CACF,CAOD,OAAMC,GACJ,MAAMtD,IAAEA,GAAQH,KAAKC,OAErB,IACE,MAAMkD,QAAiBnD,KAAKkD,EAAc/C,EAAK,CAC7CqB,OAAQ,OACRkC,MAAO,WACPC,YAAa,gBAGf,IAAKR,EAASI,GACZ,MAAM,IAAId,MAAM,8DAAiBU,EAASE,UAG5C,MAAMO,EAAaT,EAASU,QAAQpC,IAAI,QACxC,IAAKmC,EACH,MAAM,IAAInB,MAAM,2EAGlB,OAAOzC,KAAK8D,EAAgBF,EAC7B,CAAC,MAAOJ,GAEP,OADAxD,KAAKC,OAAOM,QAAQiD,IACb,CACR,CACF,CAOD,OAAMO,GACJ,MAAM5D,IAAEA,GAAQH,KAAKC,OAErB,IACE,MAAMkD,QAAiBnD,KAAKkD,EAAc/C,EAAK,CAC7CqB,OAAQ,MACRkC,MAAO,WACPC,YAAa,cACbE,QAAS,CACPG,OAAQ,sBAIZ,IAAKb,EAASI,GACZ,MAAM,IAAId,MAAM,iFAAgBU,EAASE,UAG3C,MAAMY,QAAed,EAASe,OAE9B,IAAKD,GAA4B,iBAAXA,EACpB,MAAM,IAAIxB,MAAM,8FAGlB,IAAKwB,EAAOE,QACV,MAAM,IAAI1B,MAAM,2FAGlB,OAAOzC,KAAK8D,EAAgBG,EAAOE,QACpC,CAAC,MAAOX,GAEP,OADAxD,KAAKC,OAAOM,QAAQiD,IACb,CACR,CACF,CAQD,CAAAM,CAAgBF,GACd,MAAMQ,EAAapE,KAAKc,WAAWW,IAAIzB,KAAKgB,YAE5C,OAAKoD,EAKDR,GAAcQ,IAElBpE,KAAKqE,EAAeT,IACb,IAPL5D,KAAKc,WAAWY,IAAI1B,KAAKgB,WAAY4C,IAC9B,EAOV,CAMD,CAAAS,CAAeT,GACb,MAAMtD,SAAEA,EAAQD,QAAEA,GAAYL,KAAKC,OAEnC,GAAwB,mBAAbK,EAGT,OAFAN,KAAKc,WAAWY,IAAI1B,KAAKgB,WAAY4C,QACrCtD,IAIFN,KAAKqC,MAAK,GACQK,OAAO4B,QAAQjE,IAE/BL,KAAKc,WAAWY,IAAI1B,KAAKgB,WAAY4C,GACrC5D,KAAKuE,UAELvE,KAAKsC,OAER,CAKD,MAAAiC,GACE,IAAIC,EAAa9B,OAAO+B,SAASC,KACjCF,EAAaA,EAAWG,QAAQ,aAAc,IAC9C,MAAMC,EAAYJ,EAAWK,SAAS,KAAO,IAAM,IAC7CC,EAAS,GAAGN,IAAaI,MAAcG,KAAKC,QAClDtC,OAAO+B,SAASE,QAAQG,EACzB,CAMD,WAAMG,GACJ,MAA0B,SAAnBjF,KAAKiB,gBAA6BjB,KAAKyD,UAAuBzD,KAAK+D,GAC3E,CAKD,KAAAzB,GACE,GAAItC,KAAKmB,WAAanB,KAAKY,MAEzB,YADAZ,KAAKC,OAAOQ,MAAM,8CAIpBT,KAAKmB,WAAY,EACjBnB,KAAKkF,IAML,MAAMC,EAAOC,UACX,UACQpF,KAAKiF,OACZ,CAAC,MAAOzB,GACPxD,KAAKC,OAAOM,QAAQ,IAAIkC,MAAM,yCAAWe,EAAMnD,WAChD,CACGL,KAAKmB,YACPnB,KAAKY,MAAQyE,WAAWF,EAAMnF,KAAKC,OAAOG,YAG9CJ,KAAKY,MAAQyE,WAAWF,EAAMnF,KAAKC,OAAOG,UAE1CJ,KAAKC,OAAOQ,MAAM,6CACnB,CAMD,IAAA4B,CAAKiD,GAAa,GAChBtF,KAAKkF,IAEAI,IACHtF,KAAKmB,WAAY,EACjBnB,KAAKC,OAAOQ,MAAM,8CAErB,CAMD,CAAAyE,GACMlF,KAAKY,QACP2E,aAAavF,KAAKY,OAClBZ,KAAKY,MAAQ,KAEhB,CAKD,OAAA4E,GACExF,KAAKqC,OACLrC,KAAK2B,IACL3B,KAAKa,cAAgB,KACrBb,KAAKC,OAAS,KACdD,KAAKY,MAAQ,KACbZ,KAAKc,WAAa,KAClBd,KAAKiB,UAAY,KACjBjB,KAAKmB,WAAY,CAGlB"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var global,factory;global=this,factory=function(){return class{constructor(t={}){if("object"!=typeof t||null===t)throw new TypeError("options \u5fc5\u987b\u662f\u5bf9\u8c61\u7c7b\u578b");this.config=this.t({url:"/",interval:6e5,message:"\u68c0\u6d4b\u5230\u65b0\u7248\u672c\uff0c\u662f\u5426\u7acb\u5373\u5237\u65b0\uff1f",onUpdate:null,onError:t=>{},onLog:t=>{},storage:null},t),this.timer=null,this.memoryStorage=null,this.storageApi=this.i(),this.versionKey="version_identifier",this.checkMode=this.o(),this.isRunning=!1,this.h()}t(t,i){const e={...t,...i};if("string"!=typeof e.url||""===e.url.trim())throw new TypeError("url \u5fc5\u987b\u662f\u975e\u7a7a\u5b57\u7b26\u4e32");return("number"!=typeof e.interval||e.interval<=0)&&(this.config?.onLog("interval \u914d\u7f6e\u65e0\u6548\uff0c\u4f7f\u7528\u9ed8\u8ba4\u503c 5 \u5206\u949f"),e.interval=t.interval),"string"==typeof e.message&&""!==e.message.trim()||(this.config?.onLog("message \u914d\u7f6e\u65e0\u6548\uff0c\u4f7f\u7528\u9ed8\u8ba4\u503c"),e.message=t.message),["onUpdate","onError","onLog"].forEach(i=>{null!==e[i]&&"function"!=typeof e[i]&&(this.config?.onLog(`${i} \u5fc5\u987b\u662f\u51fd\u6570\uff0c\u4f7f\u7528\u9ed8\u8ba4\u5904\u7406`),e[i]=t[i])}),null!==e.storage&&("object"==typeof e.storage&&"function"==typeof e.storage.get&&"function"==typeof e.storage.set||(this.config?.onLog("storage \u914d\u7f6e\u65e0\u6548\uff0c\u4f7f\u7528\u9ed8\u8ba4\u5b58\u50a8"),e.storage=null)),e}h(){this.l(),this.u=this.p.bind(this),document.addEventListener("visibilitychange",this.u)}l(){this.u&&(document.removeEventListener("visibilitychange",this.u),this.u=null)}p(){this.isRunning&&(document.hidden?this.m():this.T())}m(){this.stop(!0),this.config.onLog("\u9875\u9762\u9690\u85cf\uff0c\u6682\u505c\u7248\u672c\u68c0\u6d4b")}T(){this.start(),this.config.onLog("\u9875\u9762\u663e\u793a\uff0c\u6062\u590d\u7248\u672c\u68c0\u6d4b")}i(){const t={get:t=>{if("string"!=typeof t)return this.config.onError(new Error("\u5b58\u50a8\u952e\u5fc5\u987b\u662f\u5b57\u7b26\u4e32")),null;try{if(window.localStorage)return localStorage.getItem(t)}catch(t){this.config.onError(new Error(`localStorage get \u5931\u8d25: ${t.message}`))}return this.memoryStorage},set:(t,i)=>{if("string"!=typeof t)return this.config.onError(new Error("\u5b58\u50a8\u952e\u5fc5\u987b\u662f\u5b57\u7b26\u4e32")),!1;try{if(window.localStorage)return localStorage.setItem(t,String(i)),!0}catch(t){this.config.onError(new Error(`localStorage set \u5931\u8d25: ${t.message}`))}return this.memoryStorage=String(i),!0}};return this.config.storage||t}o(){const{url:t}=this.config;return"/"!==t&&/\.\w+$/.test(t)?"file":"etag"}async $(t,i){try{const e=await fetch(t,i);if(e.status>=400&&e.status<500)return this.config.onError(new Error(`\u5ba2\u6237\u7aef\u9519\u8bef HTTP ${e.status}: ${e.statusText}`)),e;if(!e.ok)throw new Error(`HTTP ${e.status}: ${e.statusText}`);return e}catch(t){throw this.config.onError(new Error(`\u7f51\u7edc\u8bf7\u6c42\u5931\u8d25: ${t.message}`)),t}}async _(){const{url:t}=this.config;try{const i=await this.$(t,{method:"HEAD",cache:"no-cache",credentials:"same-origin"});if(!i.ok)throw new Error(`ETag \u8bf7\u6c42\u5931\u8d25\uff0c\u72b6\u6001\u7801\uff1a${i.status}`);const e=i.headers.get("ETag");if(!e)throw new Error("\u670d\u52a1\u5668\u672a\u8fd4\u56de ETag\uff0c\u68c0\u6d4b\u5931\u8d25");return this.v(e)}catch(t){return this.config.onError(t),!1}}async S(){const{url:t}=this.config;try{const i=await this.$(t,{method:"GET",cache:"no-cache",credentials:"same-origin",headers:{Accept:"application/json"}});if(!i.ok)throw new Error(`\u7248\u672c\u6587\u4ef6\u8bf7\u6c42\u5931\u8d25\uff0c\u72b6\u6001\u7801\uff1a${i.status}`);const e=await i.json();if(!e||"object"!=typeof e)throw new Error("\u7248\u672c\u6587\u4ef6\u683c\u5f0f\u9519\u8bef\uff0c\u5fc5\u987b\u8fd4\u56de\u5bf9\u8c61");if(!e.version)throw new Error("\u7248\u672c\u6587\u4ef6\u683c\u5f0f\u9519\u8bef\uff0c\u7f3a\u5c11 version \u5b57\u6bb5");return this.v(e.version)}catch(t){return this.config.onError(t),!1}}v(t){const i=this.storageApi.get(this.versionKey);return i?t!=i&&(this.V(t),!0):(this.storageApi.set(this.versionKey,t),!1)}V(t){const{onUpdate:i,message:e}=this.config;if("function"==typeof i)return this.storageApi.set(this.versionKey,t),void i();this.stop(!0),window.confirm(e)?(this.storageApi.set(this.versionKey,t),this.reload()):this.start()}reload(){let t=window.location.href;t=t.replace(/[?&]t=\d+/g,"");const i=t.includes("?")?"&":"?",e=`${t}${i}t=${Date.now()}`;window.location.replace(e)}async check(){return"etag"===this.checkMode?await this._():await this.S()}start(){if(this.isRunning&&this.timer)return void this.config.onLog("\u68c0\u6d4b\u5df2\u5728\u8fd0\u884c\u4e2d");this.isRunning=!0,this.j();const t=async()=>{try{await this.check()}catch(t){this.config.onError(new Error(`\u8f6e\u8be2\u68c0\u6d4b\u5931\u8d25: ${t.message}`))}this.isRunning&&(this.timer=setTimeout(t,this.config.interval))};this.timer=setTimeout(t,this.config.interval),this.config.onLog("\u7248\u672c\u68c0\u6d4b\u5df2\u542f\u52a8")}stop(t=!1){this.j(),t||(this.isRunning=!1,this.config.onLog("\u7248\u672c\u68c0\u6d4b\u5df2\u505c\u6b62"))}j(){this.timer&&(clearTimeout(this.timer),this.timer=null)}destroy(){this.stop(),this.l(),this.memoryStorage=null,this.config=null,this.timer=null,this.storageApi=null,this.checkMode=null,this.isRunning=!1}}},"object"==typeof exports&&"undefined"!=typeof module?module.exports=factory():"function"==typeof define&&define.amd?define(factory):(global="undefined"!=typeof globalThis?globalThis:global||self).VersionCheck=factory();
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../index.js"],"sourcesContent":["/**\r\n * 通用前端版本检测工具\r\n * 自动判断检测模式(ETag/版本文件)| 默认自动轮询 | 保留手动检测 | 内置原生confirm提示\r\n * @module version-check-js\r\n * @version 1.1.0\r\n */\r\nclass VersionCheck {\r\n /**\r\n * 构造函数:初始化配置和存储(极简配置)\r\n * @param {Object} options 配置项\r\n * @param {string} [options.url='/'] 检测地址(默认/:ETag模式;传文件路径如/version.json:版本文件模式)\r\n * @param {number} [options.interval=10 * 60 * 1000] 轮询间隔(毫秒),默认10分钟\r\n * @param {string} [options.message='检测到新版本,是否立即刷新?'] 更新提示文案\r\n * @param {Function} [options.onUpdate=null] 自定义更新回调(优先级高于默认confirm)\r\n * @param {Function} [options.onError=(err)=>console.error(err)] 错误回调\r\n * @param {Function} [options.onLog=(msg)=>console.log(msg)] 日志回调\r\n * @param {Object} [options.storage=null] 自定义存储配置(get/set方法)\r\n */\r\n constructor(options = {}) {\r\n // 验证输入参数类型\r\n if (typeof options !== 'object' || options === null) {\r\n throw new TypeError('options 必须是对象类型');\r\n }\r\n\r\n // 定义默认配置\r\n const defaultConfig = {\r\n url: '/',\r\n interval: 10 * 60 * 1000,\r\n message: '检测到新版本,是否立即刷新?',\r\n onUpdate: null,\r\n onError: err => console.error('版本检测失败:', err),\r\n onLog: msg => console.log('版本检测:', msg),\r\n storage: null,\r\n };\r\n\r\n // 合并配置并验证\r\n this.config = this._mergeAndValidateConfig(defaultConfig, options);\r\n\r\n // 初始化核心状态\r\n this.timer = null; // 轮询定时器\r\n this.memoryStorage = null; // 内存存储降级\r\n this.storageApi = this._initStorage(); // 存储适配器\r\n this.versionKey = 'version_identifier'; // 存储版本标识的key\r\n this.checkMode = this._getCheckMode(); // 自动判断检测模式\r\n this.isRunning = false; // 检测状态\r\n\r\n this._bindVisibilityListener(); // 绑定可见性变化监听器\r\n }\r\n\r\n /**\r\n * 合并并验证配置项\r\n * @private\r\n * @param {Object} defaults 默认配置\r\n * @param {Object} options 用户配置\r\n * @returns {Object} 合并后的配置\r\n */\r\n _mergeAndValidateConfig(defaults, options) {\r\n const config = { ...defaults, ...options };\r\n\r\n // 验证 url\r\n if (typeof config.url !== 'string' || config.url.trim() === '') {\r\n throw new TypeError('url 必须是非空字符串');\r\n }\r\n\r\n // 验证 interval\r\n if (typeof config.interval !== 'number' || config.interval <= 0) {\r\n this.config?.onLog('interval 配置无效,使用默认值 5 分钟');\r\n config.interval = defaults.interval;\r\n }\r\n\r\n // 验证 message\r\n if (typeof config.message !== 'string' || config.message.trim() === '') {\r\n this.config?.onLog('message 配置无效,使用默认值');\r\n config.message = defaults.message;\r\n }\r\n\r\n // 验证回调函数\r\n ['onUpdate', 'onError', 'onLog'].forEach(method => {\r\n if (config[method] !== null && typeof config[method] !== 'function') {\r\n this.config?.onLog(`${method} 必须是函数,使用默认处理`);\r\n config[method] = defaults[method];\r\n }\r\n });\r\n\r\n // 验证 storage\r\n if (config.storage !== null) {\r\n if (\r\n typeof config.storage !== 'object' ||\r\n typeof config.storage.get !== 'function' ||\r\n typeof config.storage.set !== 'function'\r\n ) {\r\n this.config?.onLog('storage 配置无效,使用默认存储');\r\n config.storage = null;\r\n }\r\n }\r\n\r\n return config;\r\n }\r\n\r\n /**\r\n * 绑定页面可见性变化监听器\r\n * @private\r\n */\r\n _bindVisibilityListener() {\r\n this._unbindVisibilityListener();\r\n this._visibilityHandler = this._handleVisibilityChange.bind(this);\r\n document.addEventListener('visibilitychange', this._visibilityHandler);\r\n }\r\n\r\n /**\r\n * 解绑页面可见性变化监听器\r\n * @private\r\n */\r\n _unbindVisibilityListener() {\r\n if (this._visibilityHandler) {\r\n document.removeEventListener('visibilitychange', this._visibilityHandler);\r\n this._visibilityHandler = null;\r\n }\r\n }\r\n\r\n /**\r\n * 处理页面可见性变化\r\n * @private\r\n */\r\n _handleVisibilityChange() {\r\n if (!this.isRunning) return;\r\n\r\n if (document.hidden) {\r\n this._pauseDetection();\r\n } else {\r\n this._resumeDetection();\r\n }\r\n }\r\n\r\n /**\r\n * 暂停检测(页面隐藏时)\r\n * @private\r\n */\r\n _pauseDetection() {\r\n this.stop(true);\r\n this.config.onLog('页面隐藏,暂停版本检测');\r\n }\r\n\r\n /**\r\n * 恢复检测(页面显示时)\r\n * @private\r\n */\r\n _resumeDetection() {\r\n this.start();\r\n this.config.onLog('页面显示,恢复版本检测');\r\n }\r\n\r\n /**\r\n * 初始化存储适配器(优先自定义 → localStorage → 内存)\r\n * @private\r\n * @returns {Object} 存储接口(get/set)\r\n */\r\n _initStorage() {\r\n const defaultStorage = {\r\n get: key => {\r\n if (typeof key !== 'string') {\r\n this.config.onError(new Error('存储键必须是字符串'));\r\n return null;\r\n }\r\n\r\n try {\r\n if (window.localStorage) {\r\n return localStorage.getItem(key);\r\n }\r\n } catch (e) {\r\n this.config.onError(new Error(`localStorage get 失败: ${e.message}`));\r\n }\r\n\r\n return this.memoryStorage;\r\n },\r\n\r\n set: (key, value) => {\r\n if (typeof key !== 'string') {\r\n this.config.onError(new Error('存储键必须是字符串'));\r\n return false;\r\n }\r\n\r\n try {\r\n if (window.localStorage) {\r\n localStorage.setItem(key, String(value));\r\n return true;\r\n }\r\n } catch (e) {\r\n this.config.onError(new Error(`localStorage set 失败: ${e.message}`));\r\n }\r\n\r\n this.memoryStorage = String(value);\r\n return true;\r\n },\r\n };\r\n\r\n return this.config.storage || defaultStorage;\r\n }\r\n\r\n /**\r\n * 自动判断检测模式\r\n * @private\r\n * @returns {string} 'etag' | 'file'\r\n */\r\n _getCheckMode() {\r\n const { url } = this.config;\r\n const isVersionFile = url !== '/' && /\\.\\w+$/.test(url);\r\n return isVersionFile ? 'file' : 'etag';\r\n }\r\n\r\n /**\r\n * 简化的网络请求(依赖循环调用机制)\r\n * @private\r\n * @param {string} url 请求地址\r\n * @param {Object} options 请求选项\r\n * @returns {Promise<Response>} fetch 响应\r\n */\r\n async _fetchRequest(url, options) {\r\n try {\r\n const response = await fetch(url, options);\r\n\r\n // 4xx 错误记录但不重试(客户端错误)\r\n if (response.status >= 400 && response.status < 500) {\r\n this.config.onError(new Error(`客户端错误 HTTP ${response.status}: ${response.statusText}`));\r\n return response;\r\n }\r\n\r\n if (!response.ok) {\r\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\r\n }\r\n\r\n return response;\r\n } catch (error) {\r\n this.config.onError(new Error(`网络请求失败: ${error.message}`));\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * ETag模式检测逻辑\r\n * @private\r\n * @returns {Promise<boolean>} 是否检测到更新\r\n */\r\n async _checkByEtag() {\r\n const { url } = this.config;\r\n\r\n try {\r\n const response = await this._fetchRequest(url, {\r\n method: 'HEAD',\r\n cache: 'no-cache',\r\n credentials: 'same-origin',\r\n });\r\n\r\n if (!response.ok) {\r\n throw new Error(`ETag 请求失败,状态码:${response.status}`);\r\n }\r\n\r\n const newVersion = response.headers.get('ETag');\r\n if (!newVersion) {\r\n throw new Error('服务器未返回 ETag,检测失败');\r\n }\r\n\r\n return this._compareVersion(newVersion);\r\n } catch (error) {\r\n this.config.onError(error);\r\n return false;\r\n }\r\n }\r\n\r\n /**\r\n * 版本文件模式检测逻辑\r\n * @private\r\n * @returns {Promise<boolean>} 是否检测到更新\r\n */\r\n async _checkByVersionFile() {\r\n const { url } = this.config;\r\n\r\n try {\r\n const response = await this._fetchRequest(url, {\r\n method: 'GET',\r\n cache: 'no-cache',\r\n credentials: 'same-origin',\r\n headers: {\r\n Accept: 'application/json',\r\n },\r\n });\r\n\r\n if (!response.ok) {\r\n throw new Error(`版本文件请求失败,状态码:${response.status}`);\r\n }\r\n\r\n const result = await response.json();\r\n\r\n if (!result || typeof result !== 'object') {\r\n throw new Error('版本文件格式错误,必须返回对象');\r\n }\r\n\r\n if (!result.version) {\r\n throw new Error('版本文件格式错误,缺少 version 字段');\r\n }\r\n\r\n return this._compareVersion(result.version);\r\n } catch (error) {\r\n this.config.onError(error);\r\n return false;\r\n }\r\n }\r\n\r\n /**\r\n * 对比版本标识\r\n * @private\r\n * @param {string} newVersion 最新版本标识\r\n * @returns {boolean} 是否检测到更新\r\n */\r\n _compareVersion(newVersion) {\r\n const oldVersion = this.storageApi.get(this.versionKey);\r\n\r\n if (!oldVersion) {\r\n this.storageApi.set(this.versionKey, newVersion);\r\n return false;\r\n }\r\n\r\n if (newVersion == oldVersion) return false;\r\n\r\n this._triggerUpdate(newVersion);\r\n return true;\r\n }\r\n\r\n /**\r\n * 触发更新回调\r\n * @private\r\n */\r\n _triggerUpdate(newVersion) {\r\n const { onUpdate, message } = this.config;\r\n\r\n if (typeof onUpdate === 'function') {\r\n this.storageApi.set(this.versionKey, newVersion);\r\n onUpdate();\r\n return;\r\n }\r\n\r\n this.stop(true);\r\n const isConfirm = window.confirm(message);\r\n if (isConfirm) {\r\n this.storageApi.set(this.versionKey, newVersion);\r\n this.reload();\r\n } else {\r\n this.start();\r\n }\r\n }\r\n\r\n /**\r\n * 重新加载页面(避免缓存)\r\n */\r\n reload() {\r\n let currentUrl = window.location.href;\r\n currentUrl = currentUrl.replace(/[?&]t=\\d+/g, '');\r\n const separator = currentUrl.includes('?') ? '&' : '?';\r\n const newUrl = `${currentUrl}${separator}t=${Date.now()}`;\r\n window.location.replace(newUrl);\r\n }\r\n\r\n /**\r\n * 手动触发单次检测\r\n * @returns {Promise<boolean>} 是否检测到更新\r\n */\r\n async check() {\r\n return this.checkMode === 'etag' ? await this._checkByEtag() : await this._checkByVersionFile();\r\n }\r\n\r\n /**\r\n * 启动自动轮询检测\r\n */\r\n start() {\r\n if (this.isRunning && this.timer) {\r\n this.config.onLog('检测已在运行中');\r\n return;\r\n }\r\n\r\n this.isRunning = true;\r\n this._clearTimer();\r\n\r\n // 立即执行一次检测\r\n // this.check().catch(error => this.config.onError(new Error(`首次检测失败: ${error.message}`)));\r\n\r\n // 启动轮询\r\n const poll = async () => {\r\n try {\r\n await this.check();\r\n } catch (error) {\r\n this.config.onError(new Error(`轮询检测失败: ${error.message}`));\r\n }\r\n if (this.isRunning) {\r\n this.timer = setTimeout(poll, this.config.interval);\r\n }\r\n };\r\n this.timer = setTimeout(poll, this.config.interval);\r\n\r\n this.config.onLog('版本检测已启动');\r\n }\r\n\r\n /**\r\n * 停止自动轮询检测\r\n * @param {boolean} isInternal 是否为内部调用\r\n */\r\n stop(isInternal = false) {\r\n this._clearTimer();\r\n\r\n if (!isInternal) {\r\n this.isRunning = false;\r\n this.config.onLog('版本检测已停止');\r\n }\r\n }\r\n\r\n /**\r\n * 清理定时器\r\n * @private\r\n */\r\n _clearTimer() {\r\n if (this.timer) {\r\n clearTimeout(this.timer);\r\n this.timer = null;\r\n }\r\n }\r\n\r\n /**\r\n * 销毁实例\r\n */\r\n destroy() {\r\n this.stop();\r\n this._unbindVisibilityListener();\r\n this.memoryStorage = null;\r\n this.config = null;\r\n this.timer = null;\r\n this.storageApi = null;\r\n this.checkMode = null;\r\n this.isRunning = false;\r\n\r\n console.log('VersionCheck 实例已销毁');\r\n }\r\n}\r\n\r\nexport default VersionCheck;\r\n"],"names":["constructor","options","TypeError","this","config","_mergeAndValidateConfig","url","interval","message","onUpdate","onError","err","onLog","msg","storage","timer","memoryStorage","storageApi","_initStorage","versionKey","checkMode","_getCheckMode","isRunning","_bindVisibilityListener","defaults","trim","forEach","method","get","set","_unbindVisibilityListener","_visibilityHandler","_handleVisibilityChange","bind","document","addEventListener","removeEventListener","hidden","_pauseDetection","_resumeDetection","stop","start","defaultStorage","key","Error","window","localStorage","getItem","e","value","setItem","String","test","_fetchRequest","response","fetch","status","statusText","ok","error","_checkByEtag","cache","credentials","newVersion","headers","_compareVersion","_checkByVersionFile","Accept","result","json","version","oldVersion","_triggerUpdate","confirm","reload","currentUrl","location","href","replace","separator","includes","newUrl","Date","now","check","_clearTimer","poll","async","setTimeout","isInternal","clearTimeout","destroy"],"mappings":"yDAMA,MAYE,WAAAA,CAAYC,EAAU,IAEpB,GAAuB,iBAAZA,GAAoC,OAAZA,EACjC,MAAM,IAAIC,UAAU,sDAetBC,KAAKC,OAASD,KAAKE,EAXG,CACpBC,IAAK,IACLC,SAAU,IACVC,QAAS,uFACTC,SAAU,KACVC,QAASC,MACTC,MAAOC,MACPC,QAAS,MAI+Cb,GAG1DE,KAAKY,MAAQ,KACbZ,KAAKa,cAAgB,KACrBb,KAAKc,WAAad,KAAKe,IACvBf,KAAKgB,WAAa,qBAClBhB,KAAKiB,UAAYjB,KAAKkB,IACtBlB,KAAKmB,WAAY,EAEjBnB,KAAKoB,GACN,CASD,CAAAlB,CAAwBmB,EAAUvB,GAChC,MAAMG,EAAS,IAAKoB,KAAavB,GAGjC,GAA0B,iBAAfG,EAAOE,KAA0C,KAAtBF,EAAOE,IAAImB,OAC/C,MAAM,IAAIvB,UAAU,wDAmCtB,OA/B+B,iBAApBE,EAAOG,UAAyBH,EAAOG,UAAY,KAC5DJ,KAAKC,QAAQQ,MAAM,wFACnBR,EAAOG,SAAWiB,EAASjB,UAIC,iBAAnBH,EAAOI,SAAkD,KAA1BJ,EAAOI,QAAQiB,SACvDtB,KAAKC,QAAQQ,MAAM,wEACnBR,EAAOI,QAAUgB,EAAShB,SAI5B,CAAC,WAAY,UAAW,SAASkB,QAAQC,IAChB,OAAnBvB,EAAOuB,IAA8C,mBAAnBvB,EAAOuB,KAC3CxB,KAAKC,QAAQQ,MAAM,GAAGe,8EACtBvB,EAAOuB,GAAUH,EAASG,MAKP,OAAnBvB,EAAOU,UAEmB,iBAAnBV,EAAOU,SACgB,mBAAvBV,EAAOU,QAAQc,KACQ,mBAAvBxB,EAAOU,QAAQe,MAEtB1B,KAAKC,QAAQQ,MAAM,8EACnBR,EAAOU,QAAU,OAIdV,CACR,CAMD,CAAAmB,GACEpB,KAAK2B,IACL3B,KAAK4B,EAAqB5B,KAAK6B,EAAwBC,KAAK9B,MAC5D+B,SAASC,iBAAiB,mBAAoBhC,KAAK4B,EACpD,CAMD,CAAAD,GACM3B,KAAK4B,IACPG,SAASE,oBAAoB,mBAAoBjC,KAAK4B,GACtD5B,KAAK4B,EAAqB,KAE7B,CAMD,CAAAC,GACO7B,KAAKmB,YAENY,SAASG,OACXlC,KAAKmC,IAELnC,KAAKoC,IAER,CAMD,CAAAD,GACEnC,KAAKqC,MAAK,GACVrC,KAAKC,OAAOQ,MAAM,qEACnB,CAMD,CAAA2B,GACEpC,KAAKsC,QACLtC,KAAKC,OAAOQ,MAAM,qEACnB,CAOD,CAAAM,GACE,MAAMwB,EAAiB,CACrBd,IAAKe,IACH,GAAmB,iBAARA,EAET,OADAxC,KAAKC,OAAOM,QAAQ,IAAIkC,MAAM,2DACvB,KAGT,IACE,GAAIC,OAAOC,aACT,OAAOA,aAAaC,QAAQJ,EAE/B,CAAC,MAAOK,GACP7C,KAAKC,OAAOM,QAAQ,IAAIkC,MAAM,kCAAwBI,EAAExC,WACzD,CAED,OAAOL,KAAKa,eAGda,IAAK,CAACc,EAAKM,KACT,GAAmB,iBAARN,EAET,OADAxC,KAAKC,OAAOM,QAAQ,IAAIkC,MAAM,4DACvB,EAGT,IACE,GAAIC,OAAOC,aAET,OADAA,aAAaI,QAAQP,EAAKQ,OAAOF,KAC1B,CAEV,CAAC,MAAOD,GACP7C,KAAKC,OAAOM,QAAQ,IAAIkC,MAAM,kCAAwBI,EAAExC,WACzD,CAGD,OADAL,KAAKa,cAAgBmC,OAAOF,IACrB,IAIX,OAAO9C,KAAKC,OAAOU,SAAW4B,CAC/B,CAOD,CAAArB,GACE,MAAMf,IAAEA,GAAQH,KAAKC,OAErB,MAD8B,MAARE,GAAe,SAAS8C,KAAK9C,GAC5B,OAAS,MACjC,CASD,OAAM+C,CAAc/C,EAAKL,GACvB,IACE,MAAMqD,QAAiBC,MAAMjD,EAAKL,GAGlC,GAAIqD,EAASE,QAAU,KAAOF,EAASE,OAAS,IAE9C,OADArD,KAAKC,OAAOM,QAAQ,IAAIkC,MAAM,uCAAcU,EAASE,WAAWF,EAASG,eAClEH,EAGT,IAAKA,EAASI,GACZ,MAAM,IAAId,MAAM,QAAQU,EAASE,WAAWF,EAASG,cAGvD,OAAOH,CACR,CAAC,MAAOK,GAEP,MADAxD,KAAKC,OAAOM,QAAQ,IAAIkC,MAAM,yCAAWe,EAAMnD,YACzCmD,CACP,CACF,CAOD,OAAMC,GACJ,MAAMtD,IAAEA,GAAQH,KAAKC,OAErB,IACE,MAAMkD,QAAiBnD,KAAKkD,EAAc/C,EAAK,CAC7CqB,OAAQ,OACRkC,MAAO,WACPC,YAAa,gBAGf,IAAKR,EAASI,GACZ,MAAM,IAAId,MAAM,8DAAiBU,EAASE,UAG5C,MAAMO,EAAaT,EAASU,QAAQpC,IAAI,QACxC,IAAKmC,EACH,MAAM,IAAInB,MAAM,2EAGlB,OAAOzC,KAAK8D,EAAgBF,EAC7B,CAAC,MAAOJ,GAEP,OADAxD,KAAKC,OAAOM,QAAQiD,IACb,CACR,CACF,CAOD,OAAMO,GACJ,MAAM5D,IAAEA,GAAQH,KAAKC,OAErB,IACE,MAAMkD,QAAiBnD,KAAKkD,EAAc/C,EAAK,CAC7CqB,OAAQ,MACRkC,MAAO,WACPC,YAAa,cACbE,QAAS,CACPG,OAAQ,sBAIZ,IAAKb,EAASI,GACZ,MAAM,IAAId,MAAM,iFAAgBU,EAASE,UAG3C,MAAMY,QAAed,EAASe,OAE9B,IAAKD,GAA4B,iBAAXA,EACpB,MAAM,IAAIxB,MAAM,8FAGlB,IAAKwB,EAAOE,QACV,MAAM,IAAI1B,MAAM,2FAGlB,OAAOzC,KAAK8D,EAAgBG,EAAOE,QACpC,CAAC,MAAOX,GAEP,OADAxD,KAAKC,OAAOM,QAAQiD,IACb,CACR,CACF,CAQD,CAAAM,CAAgBF,GACd,MAAMQ,EAAapE,KAAKc,WAAWW,IAAIzB,KAAKgB,YAE5C,OAAKoD,EAKDR,GAAcQ,IAElBpE,KAAKqE,EAAeT,IACb,IAPL5D,KAAKc,WAAWY,IAAI1B,KAAKgB,WAAY4C,IAC9B,EAOV,CAMD,CAAAS,CAAeT,GACb,MAAMtD,SAAEA,EAAQD,QAAEA,GAAYL,KAAKC,OAEnC,GAAwB,mBAAbK,EAGT,OAFAN,KAAKc,WAAWY,IAAI1B,KAAKgB,WAAY4C,QACrCtD,IAIFN,KAAKqC,MAAK,GACQK,OAAO4B,QAAQjE,IAE/BL,KAAKc,WAAWY,IAAI1B,KAAKgB,WAAY4C,GACrC5D,KAAKuE,UAELvE,KAAKsC,OAER,CAKD,MAAAiC,GACE,IAAIC,EAAa9B,OAAO+B,SAASC,KACjCF,EAAaA,EAAWG,QAAQ,aAAc,IAC9C,MAAMC,EAAYJ,EAAWK,SAAS,KAAO,IAAM,IAC7CC,EAAS,GAAGN,IAAaI,MAAcG,KAAKC,QAClDtC,OAAO+B,SAASE,QAAQG,EACzB,CAMD,WAAMG,GACJ,MAA0B,SAAnBjF,KAAKiB,gBAA6BjB,KAAKyD,UAAuBzD,KAAK+D,GAC3E,CAKD,KAAAzB,GACE,GAAItC,KAAKmB,WAAanB,KAAKY,MAEzB,YADAZ,KAAKC,OAAOQ,MAAM,8CAIpBT,KAAKmB,WAAY,EACjBnB,KAAKkF,IAML,MAAMC,EAAOC,UACX,UACQpF,KAAKiF,OACZ,CAAC,MAAOzB,GACPxD,KAAKC,OAAOM,QAAQ,IAAIkC,MAAM,yCAAWe,EAAMnD,WAChD,CACGL,KAAKmB,YACPnB,KAAKY,MAAQyE,WAAWF,EAAMnF,KAAKC,OAAOG,YAG9CJ,KAAKY,MAAQyE,WAAWF,EAAMnF,KAAKC,OAAOG,UAE1CJ,KAAKC,OAAOQ,MAAM,6CACnB,CAMD,IAAA4B,CAAKiD,GAAa,GAChBtF,KAAKkF,IAEAI,IACHtF,KAAKmB,WAAY,EACjBnB,KAAKC,OAAOQ,MAAM,8CAErB,CAMD,CAAAyE,GACMlF,KAAKY,QACP2E,aAAavF,KAAKY,OAClBZ,KAAKY,MAAQ,KAEhB,CAKD,OAAA4E,GACExF,KAAKqC,OACLrC,KAAK2B,IACL3B,KAAKa,cAAgB,KACrBb,KAAKC,OAAS,KACdD,KAAKY,MAAQ,KACbZ,KAAKc,WAAa,KAClBd,KAAKiB,UAAY,KACjBjB,KAAKmB,WAAY,CAGlB"}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "version-check-js",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "version-check-js极简配置的前端版本检测工具,自动判断ETag/版本文件模式,默认自动轮询,保留手动检测 。原生javascript实现。不局限于框架使用",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "rollup -c",
|
|
12
|
+
"dev": "rollup -c -w",
|
|
13
|
+
"prepublishOnly": "npm run build",
|
|
14
|
+
"test": "echo \"No tests specified\" && exit 0"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"version-checker",
|
|
18
|
+
"version-check",
|
|
19
|
+
"version",
|
|
20
|
+
"checker",
|
|
21
|
+
"etag",
|
|
22
|
+
"版本检测",
|
|
23
|
+
"前端更新",
|
|
24
|
+
"自动轮询"
|
|
25
|
+
],
|
|
26
|
+
"author": "446354153@qq.com",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://github.com/ybchen292/version-check"
|
|
31
|
+
},
|
|
32
|
+
"homepage": "https://github.com/ybchen292/version-check",
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@rollup/plugin-commonjs": "^25.0.0",
|
|
35
|
+
"@rollup/plugin-node-resolve": "^15.0.0",
|
|
36
|
+
"@rollup/plugin-terser": "^0.4.0",
|
|
37
|
+
"rollup": "^3.0.0"
|
|
38
|
+
},
|
|
39
|
+
"volta": {
|
|
40
|
+
"node": "20.16.0"
|
|
41
|
+
}
|
|
42
|
+
}
|