前端检测版本更新-Worker 项目实践

942 阅读4分钟

前言

单页应用(Single Page Application,简称SPA)项目中,通过动态重载页面的部分内容来提高项目流畅性。然而该应用也存在弊端,当服务端发生更新,接口请求体和响应体结构发生变化,停留在客户端的项目依然访问的旧资源,未能同步更新,就可能导致项目报错。对于浏览器长时间保持打开的用户来说,当版本更新提示客户版本升级就很有必要。

实现方式

1. WebSocket实时通信

  • 建立WebSocket连接,有新版本时服务器向客户发送通知
    需要后端配合,后端回复说就不能前端自己搞嘛

2. 前端检测版本更新

  • a) 比较构建文件的hash值
  • b) 利用HTTP协议的Etag 或 Last-modified
const projectHome = '/'
// a) 比较构建文件的hash值
const getScriptHash = () => {
    fetch(projectHome).then((res) => {  
    const html = res.text();  
    return html.match(/<script(?:\s+[^>]*)?>(.*?)<\/script\s*>/i)[0]  
    })
}
// '\x3Cscript type="module" crossorigin src="/Lance-Element-Admin/assets/index-56a14e29.js">\x3C/script>'

// b: 利用HTTP协议的Etag 或 Last-modified
const getVersionTag = async () => {  
    return fetch(projectHome, { method: 'HEAD', cache: 'no-cache' })  
    .then(res => res.headers.get('etag') || res.headers.get('last-modified'))  
    .catch(e => {  
    console.error('Failed to fetch version tag', e)  
    return null  
    })  
}
// etag: 'W/"67caae6b-578"'  last-modified: 'Fri, 07 Mar 2025 08:29:31 GMT'

3. 前端检测触发时机/方式

  1. 通过 setInterval, setTimeout 模拟 setInterval 轮询
  2. 监听 visibilitychange tab切换显示 触发
以下使用的是我们的 getVersionTag方法 来做校验为例
let timer = null
const doFetch = async () => {
    const versionTag = await getVersionTag()  
    if (!versionTag) return  
    // 首次运行时不提示更新  
    if (!window.lastVersionTag) {  
        window.lastVersionTag = versionTag  
        return  
    }  
    if (window.lastVersionTag !== versionTag) {
        stop()
        console.log('项目更新了, 是时候刷新页面了!!!!')
        // todo 提示用户项目更新操作 +++
    }
}
const start = async (immediate = false) => {  
    if (immediate) {  
     await doFetch()  
    }
    if (timer) stop()
    timer = setInterval(doFetch, 5000)
}
const stop = () => {  
    clearInterval(timer)  
}

function handleVisibilitychange() {  
    if (document.hidden) {  
        stop()  
    } else {
        start(true)  
    }  
}
document.addEventListener('visibilitychange', handleVisibilitychange)
start()

以上我们的 前端版本更新校验主要逻辑已经初步完成了, 但是作为一个优秀的你想到了避免阻塞主线程是不是可以用 Worker 起一个新的线程处理,来提升页面性能

Web Worker

1. 作用

  1. 避免主线程阻塞:将耗时任务(如复杂计算、大数据处理)移至后台线程,防止页面卡顿或无响应。
  2. 提升计算性能:利用多核 CPU 并行处理任务(如加密解密、图像/视频处理)。
  3. 异步处理 I/O 操作:后台处理文件读写(通过 File API)、网络请求(如大文件分片上传)。
  4. 实时数据处理:持续处理 WebSocket 推送的实时数据流(如金融行情、聊天消息)。
  5. 离线任务支持:结合 Service Worker 实现后台同步或离线缓存更新

2. Web Workder 创建方式

  1. 常规创建 Worker
// worker.js
self.onmessage = (e) => {
  // 模拟处理任务
  const result = e.data * 100
  // 返回结果
  self.postMessage(result)
}
// 主线程
const worker = new Worker('worker.js')  // 指定 Worker 脚本路径
worker.postMessage({ data: 1 })  // 发送消息
// 接收消息
worker.onmessage = (e) => {
  console.log('结果:', e.data)
}
// 手动终止 Worker
worker.terminate()
  1. 动态创建 Worker(通过 Blob/URL)
// 主线程
const code = `
  self.onmessage = (e) => {
    const result = e.data * 100
    self.postMessage(result)
  };
`
const blob = new Blob([code], { type: 'application/javascript' })
const url = URL.createObjectURL(blob)
const worker = new Worker(url)
worker.postMessage({ data: 1 })  // 发送消息
// 接收消息
worker.onmessage = (e) => {
  console.log('结果:', e.data)
}
// 手动终止 Worker
worker.terminate()

通过Web Worker 实现版本检测

// CheckUpdates/utils.ts

// 创建worker
export const createWorker = (func: () => void, deps?: Array<() => void>) => {  
// work 依赖 fns  
const depsFncStr = `${deps?.map(dep => dep.toString()).join(';\n\n') || ''}`  
const blob = new Blob(  
[`  
${depsFncStr};  
(${func.toString()})();  
`],  
{
    type: 'application/javascript'  
})  
return new Worker(window.URL.createObjectURL(blob))  
}

// 动态创建的worker
export const createWorkFn = () => {  
const opts = {  
    immediate: true,  
    intervalTime: 5000, // 毫秒  
    fetchUrl: '/'  
}
let timer: any = null  
const stop = () => {  
    clearInterval(timer)  
}  
const temp: Worker = self as any  
let lastVersionTag = ''  

const getVersionTag = async () => {  
    return fetch(opts.fetchUrl, { method: 'HEAD', cache: 'no-cache' })  
    .then(res => res.headers.get('etag') || res.headers.get('last-modified'))  
    .catch(e => {  
    console.error('Failed to fetch version tag', e)  
    return null  
    })  
}
const doFetch = async () => {  
    const versionTag = await getVersionTag()  
    if (!versionTag) return  
    // 首次运行时不提示更新  
    if (!lastVersionTag) {  
        lastVersionTag = versionTag  
        return  
    }
    if (lastVersionTag !== versionTag) {  
        stop()  
        temp.postMessage({ type: 'showNotice', data: versionTag })  
    }
}

temp.addEventListener('message', async (event: any) => {  
    // { type: 'start' | 'stop' }  
    switch (event.data.type) {  
    case 'start':  
    {  
        const data = event.data.data  
        if (data.intervalTime) opts.intervalTime = data.intervalTime  
        if (data.fetchUrl) opts.fetchUrl = data.fetchUrl  
        if (data.immediate) {  
        await doFetch()  
        }  
        if (timer) stop()  
        timer = setInterval(doFetch, opts.intervalTime)  
    }
    break
    case 'stop': {  
        stop()    
        }
    break
    }  
})
return temp  
}
// CheckUpdates
import { createWorker, createWorkFn } from './utils'
const opts = {  
intervalTime: 5 * 60 * 1000, // 5min  
fetchUrl: entrance  
// immediate: false  
}
const worker = createWorker(createWorkFn, [])  
worker.addEventListener('message', (e: any) => {  
 // e.data: {type: 'showNotice', data: 'version'}
 handleNotice()
})  
const start = (immediate = false) => {  
    worker.postMessage({ type: 'start', data: { ...opts, immediate } })  
}  
const stop = () => {  
    worker.postMessage({ type: 'stop' })  
}
const handleNotice = () => {
 console.log('项目更新了, 是时候刷新页面了!!!!')
 // todo 提示用户项目更新操作 +++
}

function handleVisibilitychange() {  
    if (document.hidden) {  
        stop()  
    } else {  
        start(true)  
    }  
}

start()  
document.addEventListener('visibilitychange', handleVisibilitychange)

以上就是完整的检测版本更新的逻辑,完整的项目为 项目链接
文件地址: src/layout/components/CheckUpdates
该项目是 Vue3 + Ts + Element-plus & Vite 搭建,兼容了手机端 开箱即用的后台管理项目 如果觉得不错希望能得到各位大佬的star

访问链接

检测更新图片

PC访问

手机端访问