仅记录,参考juejin.cn/post/718545…
注意
由于是通过监测打包后的index.html地址中的hash值(每次打包后都会并不一样)一致性来判断是否更新,因此index.html这个文件是需要nginx配置不缓存的,否则用户就算手动刷新了也不会更新。
import { Notification, Button } from '@arco-design/web-react'
class Updater {
constructor (options) {
this.oldScript = []
this.newScript = []
this.dispatch = {
'no-update': [],
update: []
}
this.init() //初始化
this.timing(options?.timer) //轮询
}
async init () {
const html = await this.getHtml()
this.oldScript = this.parserScript(html)
}
async getHtml () {
const html = await fetch('/').then(res => res.text()) //读取index html
return html
}
parserScript (html) {
const reg = new RegExp(/<script(?:\s+[^>]*)?>(.*?)<\/script\s*>/gi) //script正则
return html.match(reg) //匹配script标签
}
//发布订阅通知
on (key = 'no-update' | 'update', fn) {
this.dispatch[key].push(fn)
return this
}
compare (oldArr, newArr) {
const base = oldArr.length
const arr = Array.from(new Set(oldArr.concat(newArr)))
//如果新旧length 一样无更新
if (arr.length === base) {
this.dispatch['no-update'].forEach(fn => {
fn(this.newScript)
})
} else {
//否则通知更新
this.dispatch['update'].forEach(fn => {
fn(this.newScript)
})
}
}
timing (time = 10000) {
//轮询
setInterval(async () => {
const newHtml = await this.getHtml()
this.newScript = this.parserScript(newHtml)
this.compare(this.oldScript, this.newScript)
}, time)
}
}
const updateNotification = () => {
const updater = new Updater({
timer: 5000
})
// updater.on('no-update', val => {
// const id = val
// Notification.success({
// id,
// closable: false,
// title: 'Update notification',
// content: 'Please refresh for the latest version!',
// showIcon: true,
// position: 'bottomRight',
// duration: 0,
// btn: (
// <span>
// <Button
// type="primary"
// size="small"
// onClick={() => {
// window.location.reload()
// }}
// >
// Refresh now
// </Button>
// </span>
// )
// })
// })
//更新通知
updater.on('update', val => {
console.log('update')
const id = val
Notification.info({
id,
closable: false,
title: 'Update notification',
content: 'Please refresh for the latest version!',
showIcon: true,
position: 'bottomRight',
duration: 0,
btn: (
<span>
<Button
type="primary"
size="small"
onClick={() => {
window.location.reload()
}}
>
Refresh now
</Button>
</span>
)
})
})
}
export default updateNotification
优化轮询操作
利用 web worker 多线程,优化轮询操作带来的性能消耗
注意
react 使用worker 直接引用worker.js文件是失效的,原因Webpack 或其他构建工具不能正确处理 Web Worker 文件。默认情况下,Webpack 会将 JavaScript 文件视为模块并进行相应的处理。
解决方法:
- 下载webpack worker-loader 插件
// webpack.config
module.exports = {
// ...
module: {
rules: [
// ...
{
test: /\.worker\.js$/,
use: {
loader: 'worker-loader',
options: {
inline: true, // 将 Worker 作为 Blob 内联到打包后的文件中
fallback: false // 不使用额外的 Worker 文件
}
}
},
// ...
]
},
// ...
};
上述配置告诉 Webpack 对以 .worker.js 结尾的文件使用 worker-loader 进行处理,并将 Web Worker 内联到打包后的文件中。
// App.js
import MyWorker from './worker.js';
function MyComponent() {
useEffect(() => {
const worker = new MyWorker();
worker.onmessage = function (event) {
// 处理从 Web Worker 返回的结果
const result = event.data;
// 更新组件状态或执行其他操作
};
worker.postMessage('message');
return () => {
worker.terminate();
};
}, []);
// ...
}
注意,我们使用 import MyWorker from './worker.js' 来导入 Web Worker,而不是直接使用文件路径。
- 直接在public目录下创建workerjs文件下即可
// worker.js
class Updater {
....同上
}
onmessage = function (event) {
// const data = event.data
const updater = new Updater()
//更新通知
updater.on('update', val => {
// 将结果发送回主线程
postMessage(val)
})
}
// App.js
useEffect(() => {
var worker = new Worker('worker.js')
// 向 Web Worker 发送消息
worker.postMessage(null)
// 接收 Web Worker 的消息
worker.onmessage = function (event) {
const result = event.data
if (result) {
// 提示弹窗
Notification.info({
id: result,
closable: false,
title: 'Update notification',
content: 'Please refresh for the latest version!',
showIcon: true,
position: 'bottomRight',
duration: 0,
btn: (
<span>
<Button
type="primary"
size="small"
onClick={() => {
window.location.reload()
}}
>
Refresh now
</Button>
</span>
)
})
}
}
return () => {
worker.terminate()
}
}, [])
因为这个轮询逻辑比较简单,所以直接选择第二种方法即可