UNPKG

version-polling

Version:

A JavaScript library for web application updates

298 lines (223 loc) 11.6 kB
# version-polling > 一个用于实时检测 web 应用更新的 JavaScript 库 ## 前言 > 以下内容是通过 GitHub Copilot 生成的 😊 在现代 web 应用开发中,前端代码的更新频率较高,尤其是单页应用(SPA)。当应用发布新版本时,如何及时通知用户并引导其刷新页面以加载最新资源,成为了一个亟待解决的问题。`version-polling` 库应运而生,旨在提供一种简单高效的方式来检测前端应用的版本更新,并提示用户进行页面刷新。 ## 适用场景 用户在浏览器中打开某 web 应用(通常是后台管理系统)很长时间且未刷新页面时,如果应用有新功能添加或问题修复,用户可能无法及时知道有新版发布。这样会导致用户继续使用旧版,影响用户体验和数据准确性,甚至出现程序报错。 ## 功能特性 - 针对前端 web 单页应用(SPA)而设计 - 纯前端技术实现,使用简单无需后端支持 - 提供三种版本控制方式 1.使用`HTTP ETag`作为版本标识符 2.使用`chunkHash`作为版本标识符 `v1.3.0` 3.使用`version.json` 文件管理版本号 `v1.3.0` - 支持 TypeScript ## 实现原理 ### 使用`HTTP ETag`作为版本标识符 > 使用`HTTP ETag`作为版本标识符来判断应用是否有更新。 > `HTTP ETag`说明:每次请求`index.html`文件时,HTTP 响应头上会有一个 [ETag](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/ETag) 字段, > 格式类似`ETag: W/"0815"`该字段的值是服务器资源的唯一标识符,通过比较前后两次请求的 Etag 字段值,可以判断资源是否发生变化,以这个为依据判断是否有更新。 > 缺点是`HTTP ETag`是由服务器生成的,前端不可控。 1. 使用`Web Worker`API 在浏览器后台**轮询**请求`index.html`文件,不会影响主线程运行。 2. 请求`index.html`文件,对比本地和请求响应头的 ETag 的字段值。 3. 如果 ETag 字段值不一致,说明有更新,则弹出更新提示,并引导用户手动**刷新页面**(例如弹窗提示),完成应用更新。 4. 当页面不可见时(例如切换标签页或最小化窗口),停止实时检测任务;再次可见时(例如切换回标签页或还原窗口),恢复实时检测任务。 5. 支持添加其他前端事件(例如页面导航或自定义事件)触发检测,由开发者自行决定检测时机。 ### 使用`chunkHash`作为版本标识符 > 使用`chunkHash`作为版本标识符来判断应用是否有更新。 > `chunkHash`说明:因为前端 spa 项目都是打包后再部署,这里以 vite 为例,打包产物 index.html 文件内容中会存在一个 script 标签,格式类似`<script type="module" crossorigin src="/assets/index.065a65a6.js"></script>`,其中`index`是 chunk 名称,后面的`065a65a6`是 chunk 哈希值,每次项目代码有改动再打包这里的`chunkHash`哈希值都会发生变化,以这个为依据判断是否有更新。 1. 使用`Web Worker`API 在浏览器后台**轮询**请求`index.html`文件,不会影响主线程运行。 2. 请求`index.html`文件,对比当前文件和最新文件中的`chunkHash`的哈希值。 3. 如果`chunkHash`哈希值不一致,说明有更新,则弹出更新提示,并引导用户手动**刷新页面**(例如弹窗提示),完成应用更新。 4. 其他逻辑和方式一保持一致。 ### 使用`version.json` 文件管理版本号 > 使用 `version.json` 文件管理版本内容,由开发者手动控制应用版本更新。 > 缺点是需要开发者手动维护 `version.json` 文件 1. 使用`Web Worker`API 在浏览器后台**轮询**请求`version.json`文件,不会影响主线程运行。 2. 请求`version.json`文件,对比当前文件和最新文件中的 version 字段值。 3. 版本号比较遵循 [Semver](https://semver.org/lang/zh-CN/) 语义化版本规范,如果有高版本则弹出更新提示,并引导用户手动**刷新页面**(例如弹窗提示),完成应用更新。 4. 其他逻辑和方式一保持一致。 ## 浏览器兼容性 适用于支持原生 ES Modules 的浏览器 ```yaml title=".browserslistrc" chrome >= 87 edge >= 88 firefox >= 78 safari >= 14 ``` ## 安装 - 通过 npm 引入,并通过构建工具进行打包 ```bash # 本地项目安装 npm install version-polling --save ``` - 通过 CDN 方式引入,直接插入到 HTML > 无侵入用法,接入成本最低 ```html <script src="https://unpkg.com/version-polling/dist/version-polling.min.js"></script> ``` 使用参考[前端静态 HTML 页面自动检测更新示例](https://github.com/JoeshuTT/version-polling/blob/main/examples/static-html-app/global.html) ## 使用示例 ### 基础用法 当检测到有新版本时,会触发`onUpdate`回调函数,弹出提示用户有更新,点击确定刷新页面。 ```js // 在应用入口文件中使用: 如 main.js, app.jsx import { createVersionPolling } from 'version-polling'; createVersionPolling({ silent: process.env.NODE_ENV === 'development', // 开发环境下不检测 onUpdate: (self) => { const result = confirm('页面有更新,点击确定刷新页面!'); if (result) { self.onRefresh(); } else { self.onCancel(); } }, }); ``` ### 使用第三方组件提示更新 ```js import { createVersionPolling } from 'version-polling'; import { MessageBox } from 'element-ui'; createVersionPolling({ silent: process.env.NODE_ENV === 'development', // 开发环境下不检测 onUpdate: (self) => { MessageBox.confirm('检测到网页有更新, 是否刷新页面加载最新版本?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', }) .then(() => { self.onRefresh(); }) .catch(() => { self.onCancel(); }); }, }); ``` ### 使用轮询,并且设置轮询间隔 如果觉得前端轮询请求太频繁,可以设置轮询间隔为半小时。 ```js import { createVersionPolling } from 'version-polling'; createVersionPolling({ pollingInterval: 30 * 60 * 1000, // 每 30 分钟检测一次 silent: process.env.NODE_ENV === 'development', // 开发环境下不检测 onUpdate: (self) => { const result = confirm('页面有更新,点击确定刷新页面!'); if (result) { self.onRefresh(); } else { self.onCancel(); } }, }); ``` ### 不使用轮询,仅通过前端事件触发检测 在浏览器页面导航跳转时、页面显示切换时,触发检测任务。 在[Window](https://developer.mozilla.org/zh-CN/docs/Web/API/Window#%E6%B8%85%E5%8D%95%E4%BA%8B%E4%BB%B6)上触发的事件。 ```js import { createVersionPolling } from 'version-polling'; createVersionPolling({ eventTriggerList: ['popstate'], silent: process.env.NODE_ENV === 'development', // 开发环境下不检测 silentPollingInterval: true, onUpdate: (self) => { const result = confirm('页面有更新,点击确定刷新页面!'); if (result) { self.onRefresh(); } else { self.onCancel(); } }, }); ``` ### 通过前端自定义事件触发检测 由开发者自行决定触发检测的时机。 ```js import { createVersionPolling } from 'version-polling'; createVersionPolling({ eventTriggerList: ['myEvent'], silent: process.env.NODE_ENV === 'development', // 开发环境下不检测 onUpdate: (self) => { const result = confirm('页面有更新,点击确定刷新页面!'); if (result) { self.onRefresh(); } else { self.onCancel(); } }, }); ``` 通过`dispatchEvent`触发版本检测。 ```js dispatchEvent(new CustomEvent('myEvent')); ``` 还可以在路由跳转时触发,以`Vue Router`为例,借助导航守卫来触发版本检测。 ```js router.afterEach((to, from) => { dispatchEvent(new CustomEvent('myEvent')); }); ``` ### 设置版本控制方式为`chunkHash` 使用`chunkHash`作为版本标识符,控制应用版本更新。以打包构建工具 vite 为例。 ```js import { createVersionPolling } from 'version-polling'; createVersionPolling({ vcType: 'chunkHash', chunkName: 'index', silent: import.meta.env.MODE === 'development', // 开发环境下不检测 onUpdate: (self) => { const result = confirm('页面有更新,点击确定刷新页面!'); if (result) { self.onRefresh(); } else { self.onCancel(); } }, }); ``` chunkName 也可能是其他值,例如 Vue CLI 是`app`,查看打包产物里`index.html`文件内容来确定。 ### 设置版本控制方式为`versionJson` 使用`version.json` 文件管理版本号,控制应用版本更新。 ```js import { createVersionPolling } from 'version-polling'; createVersionPolling({ vcType: 'versionJson', silent: import.meta.env.MODE === 'development', // 开发环境下不检测 onUpdate: (self) => { const result = confirm('页面有更新,点击确定刷新页面!'); if (result) { self.onRefresh(); } else { self.onCancel(); } }, }); ``` 修改`version.json`文件,配置`version`字段值 ```json { "version": "2.1.0", "versionContent": "更新内容: 修复已知存在的问题" } ``` `version.json`文件跟`index.html`文件放在同一个服务器目录下。 ## API ### options | 参数 | 说明 | 类型 | 默认值 | | --------------------- | -------------------------------------------------------- | ---------------- | ---------------------------------------------------- | | vcType | 版本控制方式,可选值有`etag``chunkHash``versionJson` | `string` | `etag` | | htmlFileUrl | `index.html`文件地址 | `string` | `${location.origin}${location.pathname}` | | chunkName | chunk 名称 | `string` | `index` | | versionFileUrl | `version.json`文件地址 | `string` | `${location.origin}${location.pathname}version.json` | | eventTriggerList | 触发版本检测的事件名称列表 | `string[]` | `-` | | pollingInterval | 轮询间隔,单位为毫秒,默认为 5 分钟 | `number` | `5 * 60 * 1000` | | silent | 为`true`时,不进行版本检测 | `boolean` | `false` | | silentPollingInterval | 为`true`时,不做轮询版本检测 | `boolean` | `false` | | silentPageVisibility | 为`true`时,`visibilitychange`事件不会触发版本检测 | `boolean` | `false` | | onUpdate | 检测到版本更新触发的回调函数 | `(self) => void` | `-` | ### deprecated - `v1.3.0`移除`appETagKey``forceUpdate`配置项,移除原因使用鸡肋 ## 开源协议 [MIT](LICENSE)