前端项目部署后如何提示用户版本更新详解

作者:望舒时间:2026-11-01 02:17:48浏览:148

本文详解前端项目部署后如何提示用户版本更新。通过生成manifest.json记录时间戳,利用fetch HEAD请求对比ETag判断资源变更。结合Vue路由守卫与Web Worker轮询机制,实现后台检测并弹窗提示用户刷新页面,确保用户获取最新功能与Bug修复,同时避免阻塞主线程。

前言

项目部署上线后,尤其是网页应用,向当前操作系统的用户提示版本更新至关重要。通常采用“刷新大法”清除缓存,但对于正在浏览网页的用户,若系统已更新,请求的仍可能是旧版资源。

为确保用户及时获取最新功能及修复的bug,需通知用户刷新页面以加载最新代码。

解决方案

每次打包时生成时间戳作为系统伪版本,存入JSON文件,通过对比文件响应头Etag判断是否有更新。具体步骤如下:

  • 在public文件夹下添加manifest.json文件,包含两个字段:更新内容、更新时间戳
  • 前端打包时向manifest.json写入当前时间戳信息
  • 在入口文件main.js中引入检查版本更新的逻辑,有更新则提示更新。有两种方式提示提示用户:
    • 路由守卫router.beforeResolve(Vue-Router为例),检查更新,通过对比manifest.json文件的响应头Etag判断是否有更新
    • 通过Worker轮询检查更新,对比manifest.json文件的响应头Etag判断是否有更新。Worker线程不影响其他线程逻辑。

整体逻辑如下所示:

前言

1、在public目录下新建manifest.json

{
   "timestamp":21312321311,
   "msg":"更新内容如下:\n--1.添加系统更新提示机制"
}

2、将当前时间戳写入manifest.json

在vue.config.js文件中


const { readFile, writeFile } = require('fs')
// 获取路径
const filePath = path.resolve(`./public`, 'manifest.json')
// 读取文件内容
readFile(filePath, 'utf8', (err, data) => {
  if (err) {
    console.error('读取文件时出错:', err)
    return
  }
  // 将文件内容转换JSON
  const dataObj = JSON.parse(data)
  //修改时间戳
  dataObj.timestamp = new Date().getTime()
  // 将修改后的内容写回文件
  writeFile(filePath, JSON.stringify(dataObj), 'utf8', err => {
    if (err) {
      console.error('写入文件时出错:', err)
      return
    }
  })
})

3、检查版本更新

新建checkUpdate.js文件

(1)初始化变量

import Worker from "./checkUpdate.worker.js";
import router from '../router'
//上次的Etag
let lastEtag = '';
//是否更新
let hasUpdate = false
//创建worker线程
const worker = new Worker();

(2)检查版本更新

//检查版本更新
async function checkUpdate() {
  try {
    // 检测前端资源是否有更新
    let response = await fetch(`/manifest.json?v=${Date.now()}`, {
      method: 'head'
    })
    // 获取最新的etag  
    let etag = response.headers.get('etag')
    hasUpdate = lastEtag && etag !== lastEtag
    lastEtag = etag
  } catch (e) {
    return Promise.reject(e)
  }
}

其中let response = await fetch(/manifest.json?v=${Date.now()}, { method: 'head' }),使用fetch函数发起HTTP请求,获取指定资源的头信息(HTTP头部)。其中manifest.json为请求资源,Date.now()生成当前时间戳作为查询参数v的值,以避免浏览器缓存,强制获取最新资源。请求方式为HEAD,即仅请求资源头部信息而不获取具体内容。

let etag = response.headers.get('etag'),从HTTP响应中获取ETag头部信息,ETag是服务器生成的资源唯一标识,用于检查资源是否发生变化。具体比较逻辑如下:

1、客户端发起请求,请求中包含上次获取的资源ETag。 2、服务器收到请求后,比较客户端提供的ETag与当前资源的ETag是否一致。 3、如果一致,则返回HTTP 304 Not Modified响应,表示资源未发生变化,客户端可使用缓存版本。 4、如果不一致,服务器返回最新资源内容,同时更新ETag。 5、客户端收到响应后,更新本地缓存的资源内容和ETag。

(3)路由跳转检测版本更新

 // 路由拦截
 router.beforeEach(async (to, from, next) => {
  next()
  try {
    await checkUpdate()
    if (hasUpdate) {
      worker.postMessage({
        type: 'destroy'
      })
      location.reload()
    }
  } catch (e) {}
})

(4)向worker线程发送检查版本更新逻辑

worker.postMessage({
  type: 'check'
})

(5)接收worker线程数据更新

worker.onmessage = ({ data }) => {
  console.log(data,'data')
  if (data.type === 'hasUpdate') {
    hasUpdate = true
    confirmReload(data.msg, data.lastEtag)
  }
}

(6)收到版本更新信息,进行弹框提示

收到版本更新信息后,先暂停轮询检查版本更新,点击确定按钮,则发送destory消息,点击取消按钮则发送recheck消息

async function confirmReload(msg = '', lastEtag) {
  worker &&
    worker.postMessage({
      type: 'pause'
    })
  try {
  //弹框提示逻辑
  } catch (e) { }
}

checkUpdate.js全部代码实现

import Worker from "./checkUpdate.worker.js";
import router from '../router'
//上次的Etag
let lastEtag = '';
//是否更新
let hasUpdate = false
//创建worker线程
const worker = new Worker();

//检查版本更新
async function checkUpdate() {
  try {
    // 检测前端资源是否有更新
    let response = await fetch(`/manifest.json?v=${Date.now()}`, {
      method: 'head'
    })
    // 获取最新的etag  
    let etag = response.headers.get('etag')
    hasUpdate = lastEtag && etag !== lastEtag
    lastEtag = etag
    console.log(lastEtag = etag,'lastEtag = etag')
  } catch (e) {
    return Promise.reject(e)
  }
}

async function confirmReload(msg = '', lastEtag) {
  worker &&
    worker.postMessage({
      type: 'pause'
    })
  try {
    console.log('版本更新了')
  } catch (e) { }
}
 // 路由拦截
 router.beforeEach(async (to, from, next) => {
  next()
  try {
    await checkUpdate()
    if (hasUpdate) {
      worker.postMessage({
        type: 'destroy'
      })
      location.reload()
    }
  } catch (e) {}
})

worker.postMessage({
  type: 'check'
})


worker.onmessage = ({ data }) => {
  console.log(data,'data')
  if (data.type === 'hasUpdate') {
    hasUpdate = true
    confirmReload(data.msg, data.lastEtag)
  }
}

4、woker线程

新建checkUpdate.worker.js

(1)初始化变量

let lastEtag;//上次的Etag
let hasUpdate = false;//是否更新
let intervalId = '';

(2)检查版本更新

逻辑与第三步大致相同,唯一区别是检测到更新后,发送hasUpdate消息,给出弹框提示是否需要更新

async function checkUpdate() {
  try {
    // 检测前端资源是否有更新
    let response = await fetch(`/manifest.json?v=${Date.now()}`, {
      method: 'get'
    })
    // 获取最新的etag和data
    let etag = response.headers.get('etag')
    let data = await response.json()
    hasUpdate = lastEtag !== undefined && etag !== lastEtag
  
    if (hasUpdate) {
      postMessage({
        type: 'hasUpdate', 
        msg: data.msg,
        lastEtag: lastEtag,
        etag: etag
      })
    }
    lastEtag = etag
  } catch (e) {
    return Promise.reject(e)
  }
}

(3)监听主线程发送过来的数据

// 监听主线程发送过来的数据
addEventListener('message', ({ data }) => {
  console.log(data,'消息')
  if (data.type === 'check') {  
    // 每5分钟执行一次
    // 立即执行一次,获取最新的etag,避免在setInterval等待中系统更新,第一次获取的etag是新的,但是lastEtag还是undefined,不满足条件,错失刷新时机
    // checkUpdate()
    intervalId = setInterval(()=>{
      checkUpdate()
      //这里3s方便测试
    },  3 * 1000)
  }
  if (data.type === 'recheck') {
    // 每5分钟执行一次
    hasUpdate = false
    lastEtag = data.lastEtag
    intervalId = setInterval(()=>{
      checkUpdate()
    },  3 * 1000)
  }
  if (data.type === 'pause') {
    clearInterval(intervalId)
  }
  if (data.type === 'destroy') {
    clearInterval(intervalId)
    close()
  }
})

完整代码逻辑如下:

let lastEtag
let hasUpdate = false
let intervalId = ''
async function checkUpdate() {
 
  try {
    // 检测前端资源是否有更新
    let response = await fetch(`/manifest.json?v=${Date.now()}`, {
      method: 'get'
    })
    // 获取最新的etag和data
    let etag = response.headers.get('etag')
    let data = await response.json()
    hasUpdate = lastEtag !== undefined && etag !== lastEtag
  
    if (hasUpdate) {
      postMessage({
        type: 'hasUpdate', 
        msg: data.msg,
        lastEtag: lastEtag,
        etag: etag
      })
    }
    lastEtag = etag
  } catch (e) {
    return Promise.reject(e)
  }
}

// 监听主线程发送过来的数据
addEventListener('message', ({ data }) => {
  console.log(data,'消息')
  if (data.type === 'check') {  
     console.log('checkcheckcheck')
    // 每5分钟执行一次
    // 立即执行一次,获取最新的etag,避免在setInterval等待中系统更新,第一次获取的etag是新的,但是lastEtag还是undefined,不满足条件,错失刷新时机
    // checkUpdate()
    intervalId = setInterval(()=>{
      checkUpdate()
      console.log('检查版本更新')
    },  3 * 1000)
  }
  if (data.type === 'recheck') {
    // 每5分钟执行一次
    hasUpdate = false
    lastEtag = data.lastEtag
    intervalId = setInterval(()=>{
      checkUpdate()
      console.log('检查版本更新')
    },  3 * 1000)
    console.log('recheckrecheckrecheck')
  }
  if (data.type === 'pause') {
    clearInterval(intervalId)
  }
  if (data.type === 'destroy') {
    clearInterval(intervalId)
    close()
  }
})

5、入口文件引入

import "@/utils/checkUpdate.js"

可能出现的问题

1、worker

webpack 5可以使用Web Workers

new Worker(new URL('./checkUpdate.worker.js', import.meta.url));

webpack 5以下,使用worker-loader,先安装worker-loader

yarn add worker-loader -D

然后在vue.config.js配置

  chainWebpack(config) {
    config.module
    .rule('worker')
    .test(/\.worker\.js$/)
    .use('worker')
    .loader('worker-loader')
    .end()
  }

如果配置有问题,会一直出现下面这个问题,worker-loader的配置要放在其他loader前。

前言

总结

到此这篇关于前端项目部署后如何提示用户版本更新的文章就介绍到这了,更多相关前端提示用户版本更新内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!

作者最新文章
相关文章
更多