vue-sw-updater 0.0.3 → 0.0.5

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/README.md CHANGED
@@ -1,106 +1,142 @@
1
- # Vue Service Worker Updater
2
-
3
- 一个基于 Vue 2 和 Ant Design Vue 的项目更新通知与缓存清理方案。
4
-
5
- ## 功能特性
6
-
7
- * **自动检测更新**: 基于 Service Worker API,自动感知应用版本更新。
8
- * **零侵入集成**: Service Worker 逻辑由本库维护,无需手动编写复杂的 SW 代码。
9
- * **多环境隔离**: 支持 `appName` `env` 参数,生成独立的缓存空间,避免多应用或多环境缓存冲突。
10
- * **全局提示**: 使用 Ant Design Vue Modal 全局弹窗提示用户,样式醒目(警告图标+加粗文字)。
11
- * **强制刷新**: 用户点击“立即更新”后,自动清理当前应用缓存并重载页面,确保加载最新资源。
12
- * **环境检测**: 自动检测浏览器版本,如果不支持 Service Worker 或版本过低,会显示阻断性提示。
13
-
14
- ## 安装
15
-
16
- ```bash
17
- npm install vue-sw-updater --save
18
- # 确保项目中已安装 ant-design-vue 和 vue
19
- npm install ant-design-vue vue --save
20
- ```
21
-
22
- ## 快速使用
23
-
24
- ### 1. 引入插件
25
-
26
- 在你的 `main.js` 或入口文件中:
27
-
28
- ```javascript
29
- import Vue from 'vue';
30
- import VueSWUpdater from 'vue-sw-updater';
31
- import 'ant-design-vue/dist/antd.css';
32
-
33
- // 从你的配置或环境变量中获取信息
34
- const version = process.env.VUE_APP_VERSION || '1.0.0';
35
- const appName = process.env.VUE_APP_NAME || 'my-app';
36
- const env = process.env.NODE_ENV || 'production';
37
-
38
- Vue.use(VueSWUpdater, {
39
- // 必须:应用版本号
40
- version: version,
41
-
42
- // 必须:应用名称(用于隔离缓存)
43
- appName: appName,
44
-
45
- // 必须:部署环境(用于隔离缓存,如 production, staging)
46
- env: env,
47
-
48
- // 可选:Service Worker 文件路径,默认为 /service-worker.js
49
- swUrl: '/service-worker.js',
50
-
51
- // 可选:调试模式
52
- debug: false
53
- });
54
- ```
55
-
56
- ### 2. 部署 Service Worker 文件
57
-
58
- 本库提供了一个通用的 `service-worker.js`,你需要将其包含在你的项目根目录(`public` 目录)中。
59
-
60
- **方式 A:构建时复制 (推荐)**
61
-
62
- 如果你使用 Webpack (Vue CLI),可以使用 `copy-webpack-plugin` 将本库的 SW 文件复制到你的构建目录。
63
-
64
- ```javascript
65
- // vue.config.js
66
- const CopyWebpackPlugin = require('copy-webpack-plugin');
67
- const path = require('path');
68
-
69
- module.exports = {
70
- configureWebpack: {
71
- plugins: [
72
- new CopyWebpackPlugin({
73
- patterns: [
74
- {
75
- from: path.resolve(__dirname, 'node_modules/vue-sw-updater/dist/service-worker.js'),
76
- to: 'service-worker.js' // 输出到 dist 根目录
77
- }
78
- ]
79
- })
80
- ]
81
- }
82
- }
83
- ```
84
-
85
- ## 更新机制说明
86
-
87
- 1. 当传入的 `version` 发生变化时,插件会注册带有新参数的 Service Worker(例如 `/service-worker.js?v=1.0.1&app=my-app&env=production`)。
88
- 2. 浏览器检测到 URL 变化,重新下载并安装 Service Worker。
89
- 3. 新 SW 初始化时,会根据 `appName` + `env` + `version` 生成全新的 Cache Key。
90
- 4. 当检测到新版本等待激活(Waiting)时,弹出**警告样式**的更新提示框。
91
- 5. 用户点击“立即更新”:
92
- * 发送 `SKIP_WAITING` 消息给 SW。
93
- * SW 收到消息后,**立即清理当前应用环境下的旧缓存**。
94
- * SW 执行 `self.skipWaiting()` 激活新版本。
95
- * 页面监听到 `controllerchange` 事件,执行 `window.location.reload()` 刷新页面。
96
-
97
- ## 浏览器支持
98
-
99
- 插件会自动检测以下浏览器版本要求:
100
-
101
- * Chrome: 45+
102
- * Firefox: 44+
103
- * Safari: 11+
104
- * Edge: 17+
105
-
106
- 如果用户浏览器版本低于上述要求,将自动显示全屏提示,建议用户升级。
1
+ # Vue Service Worker Updater
2
+
3
+ 一个基于 Vue 2 和 Ant Design Vue 的项目更新通知与缓存清理方案。支持 Service Worker 自动更新和 HTTP 轮询检测更新双重机制,确保在 HTTPS 和 HTTP 环境下均能可靠运行。
4
+
5
+ ## 功能特性
6
+
7
+ * **双模更新检测**:
8
+ * **Service Worker 模式**: 在 HTTPS 环境下,利用 SW 自动感知应用更新,支持离线缓存和增量更新。
9
+ * **HTTP 轮询模式**: HTTP 环境或不支持 SW 的浏览器中,自动降级为 ETag / Last-Modified 轮询检测。
10
+ * **零侵入集成**: 核心逻辑封装在库内,无需手动编写复杂的 SW 或轮询代码。
11
+ * **多环境隔离**: 支持 `appName` 和 `env` 参数,生成独立的缓存空间,避免多应用或多环境缓存冲突。
12
+ * **全局提示**: 使用 Ant Design Vue Modal 全局弹窗提示用户,样式醒目(警告图标+加粗文字)。
13
+ * **强制刷新**: 用户点击“立即更新”后,自动清理当前应用缓存并重载页面,确保加载最新资源。
14
+ * **环境自适应**: 自动检测环境安全性(HTTPS/localhost),如果环境不安全且配置了轮询参数,自动切换至轮询模式;否则显示环境不支持提示。
15
+
16
+ ## 安装
17
+
18
+ ```bash
19
+ npm install vue-sw-updater --save
20
+ # 确保项目中已安装 ant-design-vue 和 vue
21
+ npm install ant-design-vue vue --save
22
+ ```
23
+
24
+ ## 快速使用
25
+
26
+ ### 1. 引入插件
27
+
28
+ 在你的 `main.js` 或入口文件中:
29
+
30
+ ```javascript
31
+ import Vue from 'vue';
32
+ import VueSWUpdater from 'vue-sw-updater';
33
+ import 'ant-design-vue/dist/antd.css';
34
+
35
+ // 从你的配置或环境变量中获取信息
36
+ const version = process.env.VUE_APP_VERSION || '1.0.0';
37
+ const appName = process.env.VUE_APP_NAME || 'my-app';
38
+ const env = process.env.NODE_ENV || 'production';
39
+
40
+ Vue.use(VueSWUpdater, {
41
+ // [必填] 应用版本号 (当前运行的版本)
42
+ version: version,
43
+
44
+ // [必填] 应用名称(用于 SW 缓存隔离)
45
+ appName: appName,
46
+
47
+ // [必填] 部署环境(用于 SW 缓存隔离)
48
+ env: env,
49
+
50
+ // [可选] Service Worker 文件路径,默认为 /service-worker.js
51
+ swUrl: '/service-worker.js',
52
+
53
+ // [可选] 轮询检测间隔(分钟)。
54
+ // 如果设置为 > 0,当 SW 不可用(如 HTTP 环境)时,将启用轮询模式。
55
+ // 建议设置为 10 或 30。
56
+ pollingInterval: 10,
57
+
58
+ // [可选] 检测更新的目标 URL (仅轮询模式使用)
59
+ // 默认为空字符串 '',即检测当前页面 (index.html) 的 Header 变化
60
+ checkUrl: '',
61
+
62
+ // [可选] 调试模式,开启后控制台会输出更多日志
63
+ debug: false
64
+ });
65
+ ```
66
+
67
+ ### 2. 构建配置 (针对不同模式)
68
+
69
+ #### A. 准备 Service Worker (HTTPS 模式推荐)
70
+
71
+ 本库提供了一个通用的 `service-worker.js`,你需要将其包含在你的项目根目录(`public` 目录)中。
72
+
73
+ **Webpack / Vue CLI 配置:**
74
+
75
+ ```javascript
76
+ // vue.config.js
77
+ const CopyWebpackPlugin = require('copy-webpack-plugin');
78
+ const path = require('path');
79
+
80
+ module.exports = {
81
+ configureWebpack: {
82
+ plugins: [
83
+ new CopyWebpackPlugin({
84
+ patterns: [
85
+ {
86
+ from: path.resolve(__dirname, 'node_modules/vue-sw-updater/dist/service-worker.js'),
87
+ to: 'service-worker.js' // 输出到 dist 根目录
88
+ }
89
+ ]
90
+ })
91
+ ]
92
+ }
93
+ }
94
+ ```
95
+
96
+ #### B. HTTP 轮询模式配置
97
+
98
+ 无需额外文件。插件会自动轮询当前页面(或指定的 `checkUrl`)的 HTTP 响应头:
99
+ * **ETag**: 资源唯一标识
100
+ * **Last-Modified**: 资源最后修改时间
101
+
102
+ **注意**: 请确保你的 Web 服务器(Nginx/Apache/IIS)已配置为返回这些 Header。对于 SPA 应用的入口文件 (`index.html`),通常默认都会返回。
103
+
104
+ ## 运行机制
105
+
106
+ ### 自动模式选择
107
+
108
+ 插件初始化时会执行以下判断:
109
+
110
+ 1. **检查 Service Worker 支持**:
111
+ * 如果是 HTTPS 或 localhost,且浏览器支持 SW -> **启用 SW 模式**。
112
+ * 如果是 HTTP (非 localhost) 或浏览器不支持 SW -> **进入降级判断**。
113
+
114
+ 2. **降级判断**:
115
+ * 如果配置了 `pollingInterval > 0` -> **启用 HTTP 轮询模式**。
116
+ * 如果没有配置轮询 -> **显示“环境不支持”错误提示** (BrowserError Modal)。
117
+
118
+ ### SW 模式工作流
119
+
120
+ 1. 插件注册 `service-worker.js?v=1.0.0&app=my-app...`。
121
+ 2. 浏览器发现 URL 变化,下载新 SW。
122
+ 3. 新 SW 安装并等待激活。
123
+ 4. 插件检测到 `waiting` 状态,弹出更新提示框。
124
+ 5. 用户点击更新 -> 清理缓存 -> 强制刷新。
125
+
126
+ ### HTTP 轮询模式工作流
127
+
128
+ 1. 插件启动定时器 (例如每 10 分钟)。
129
+ 2. 定时发送 `HEAD` 请求检测目标 URL (默认当前页面)。
130
+ 3. 对比响应头中的 `ETag` 或 `Last-Modified` 与页面加载时是否一致。
131
+ 4. 如果发现 Header 变更,视为有新版本发布,弹出更新提示框。
132
+ 5. 用户点击更新 -> 强制刷新 (`window.location.reload(true)`).
133
+
134
+ ## 浏览器支持
135
+
136
+ 插件会自动处理兼容性,最低要求:
137
+ * **Edge**: 17+
138
+ * **Chrome**: 45+
139
+ * **Firefox**: 44+
140
+ * **Safari**: 11.1+
141
+
142
+ 如果浏览器版本过低,无论哪种模式都会显示阻断性提示。
@@ -22,7 +22,7 @@ const BLACKLIST = [
22
22
  console.log(`[VueSWUpdater] Service Worker 初始化. App: ${APP_NAME}, Env: ${ENV}, Version: ${VERSION}, Cache: ${CACHE_NAME}`);
23
23
 
24
24
  self.addEventListener('install', (event) => {
25
- console.log('[VueSWUpdater] Installing...');
25
+ console.log('[VueSWUpdater] 正在安装...');
26
26
  event.waitUntil(
27
27
  caches.open(CACHE_NAME).then((cache) => {
28
28
  // 仅缓存核心入口文件,其他资源依赖运行时缓存
@@ -30,7 +30,7 @@ self.addEventListener('install', (event) => {
30
30
  './',
31
31
  './index.html'
32
32
  ]).catch(err => {
33
- console.warn('[VueSWUpdater] Pre-caching failed:', err);
33
+ console.warn('[VueSWUpdater] 预缓存失败:', err);
34
34
  });
35
35
  })
36
36
  );
@@ -38,14 +38,14 @@ self.addEventListener('install', (event) => {
38
38
  });
39
39
 
40
40
  self.addEventListener('activate', (event) => {
41
- console.log('[VueSWUpdater] Activating...');
41
+ console.log('[VueSWUpdater] 正在激活...');
42
42
  event.waitUntil(
43
43
  caches.keys().then((keyList) => {
44
44
  return Promise.all(keyList.map((key) => {
45
45
  // 清理旧版本缓存 (只清理本插件前缀的缓存)
46
46
  // 逻辑:如果是同一个 App 和 Env,但是版本不同,则清理
47
47
  if (key.startsWith(CACHE_PREFIX) && key !== CACHE_NAME) {
48
- console.log('[VueSWUpdater] Removing old cache:', key);
48
+ console.log('[VueSWUpdater] 移除旧缓存:', key);
49
49
  return caches.delete(key);
50
50
  }
51
51
  }));
@@ -57,7 +57,7 @@ self.addEventListener('activate', (event) => {
57
57
  // 监听 skipWaiting 消息(由 UI 组件触发)
58
58
  self.addEventListener('message', (event) => {
59
59
  if (event.data && event.data.type === 'SKIP_WAITING') {
60
- console.log('[VueSWUpdater] Skip Waiting received');
60
+ console.log('[VueSWUpdater] 收到 Skip Waiting 指令');
61
61
 
62
62
  // 强制清理所有缓存(不仅仅是旧版本,确保彻底)
63
63
  // 注意:这里只清理当前 APP_NAME 和 ENV 下的缓存,防止误删其他应用缓存
@@ -65,7 +65,7 @@ self.addEventListener('message', (event) => {
65
65
  caches.keys().then(keys => {
66
66
  keys.forEach(key => {
67
67
  if (key.startsWith(CACHE_PREFIX)) {
68
- console.log('[VueSWUpdater] Force clearing cache before update:', key);
68
+ console.log('[VueSWUpdater] 更新前强制清理缓存:', key);
69
69
  caches.delete(key);
70
70
  }
71
71
  });
@@ -84,7 +84,8 @@ self.addEventListener('fetch', (event) => {
84
84
  if (BLACKLIST.some(path => url.pathname.startsWith(path))) return;
85
85
 
86
86
  // HTML 页面:网络优先,失败回退到缓存 (Network First)
87
- if (event.request.mode === 'navigate' || event.request.headers.get('accept').includes('text/html')) {
87
+ const accept = event.request.headers.get('accept') || '';
88
+ if (event.request.mode === 'navigate' || accept.includes('text/html')) {
88
89
  event.respondWith(
89
90
  fetch(event.request)
90
91
  .then(response => {
@@ -103,25 +104,41 @@ self.addEventListener('fetch', (event) => {
103
104
  return;
104
105
  }
105
106
 
106
- // 静态资源:缓存优先,同时更新缓存 (Stale While Revalidate)
107
- // 或者简单的 Cache First,这里为了稳健使用 Cache First + Network Fallback
107
+ const destination = event.request.destination;
108
+ const pathname = url.pathname || '';
109
+ const isScriptOrStyle =
110
+ destination === 'script' ||
111
+ destination === 'style' ||
112
+ pathname.endsWith('.js') ||
113
+ pathname.endsWith('.css');
114
+
115
+ if (isScriptOrStyle) {
116
+ event.respondWith(
117
+ fetch(event.request)
118
+ .catch(() => caches.match(event.request))
119
+ );
120
+ return;
121
+ }
122
+
108
123
  event.respondWith(
109
- caches.match(event.request).then((response) => {
110
- if (response) {
111
- return response;
112
- }
113
- return fetch(event.request).then(networkResponse => {
114
- // 动态缓存请求成功的资源
115
- if (!networkResponse || networkResponse.status !== 200 || networkResponse.type !== 'basic') {
124
+ caches.match(event.request).then((cachedResponse) => {
125
+ const fetchAndUpdate = fetch(event.request)
126
+ .then(networkResponse => {
127
+ if (networkResponse && networkResponse.status === 200 && networkResponse.type === 'basic') {
128
+ const responseToCache = networkResponse.clone();
129
+ caches.open(CACHE_NAME).then(cache => {
130
+ cache.put(event.request, responseToCache);
131
+ });
132
+ }
116
133
  return networkResponse;
117
- }
118
- const responseToCache = networkResponse.clone();
119
- caches.open(CACHE_NAME).then(cache => {
120
- // 避免缓存过大文件或不必要文件,这里可以加过滤逻辑
121
- cache.put(event.request, responseToCache);
122
134
  });
123
- return networkResponse;
124
- });
135
+
136
+ if (cachedResponse) {
137
+ event.waitUntil(fetchAndUpdate.catch(() => {}));
138
+ return cachedResponse;
139
+ }
140
+
141
+ return fetchAndUpdate;
125
142
  })
126
143
  );
127
144
  });