如何解决前端更新部署后通知用户刷新

解决场景:当前端部署更新后,用户一直停留在页面上,拿不到最新资源,会造成无响应的情况。

解决方案:
1.在public文件夹中加入manifest.json文件,记录版本信息。
2.在前端打包的时候向配置文件manifest.json文件写入当前时间戳。
3.在入口JS引入检查更新的逻辑,有更新则提示更新:
路由守卫router.beforeResolve,检查更新,对比manifest.json文件的响应头Etag判断是否有更新。

通过Worker轮询,检查更新。对比manifest.json文件的响应头Etag是否有更新。

一、Public下的加入manifest.json文件

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

如果是不向用户提示更新内容,可不填,前段开发者也无需维护manifest.json的msg内容,这里主要考虑到如果用户在填长表单的时候,填了一大半,你这时候给用户弹个更新提示,用户无法判断是否影响当前表单填写提交,如果将更新信息展示出来,用户感知更新内容,可判断是否需要立即刷新,还是提交完表单再刷新。

二、webpack向manifest.json写入当前时间戳信息

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
        }
      })
    })

	//如果你无需维护更新内容的话,可直接写入timestamp
	const filePath = path.resolve(`./public`, 'manifest.json')
	writeFileSync(filePath, `${JSON.stringify({ timestamp: new Date().getTime() })}`)

三、检查更新的逻辑

入口文件main.js处引入
我这里检查更新的文件是放在utils/checkUpdate
// 检查版本更新
import ‘@/utils/checkUpdate’
checkUpdate.js:

import router from '@/router'
import { Modal } from 'ant-design-vue'
if (process.env.NODE_ENV === 'production') {
  let lastEtag = ''
  let hasUpdate = false
  let worker = null

  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)
    }
  }

  async function confirmReload(msg = '', lastEtag) {
    worker &&
      worker.postMessage({
        type: 'pause'
      })
    try {
      Modal.confirm({
        title: '温馨提示',
        content: '系统后台有更新,请点击“立即刷新”刷新页面\n' + msg,
        okText: '立即刷新',
        cancelText: '5分钟后提示我',
        onOk() {
          worker.postMessage({
            type: 'destroy'
          })
          location.reload()
        },
        onCancel() {
          worker &&
            worker.postMessage({
              type: 'recheck',
              lastEtag: lastEtag
            })
        }
      })
    } catch (e) {}
  }

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

  // 利用worker轮询
  worker = new Worker(
    /* webpackChunkName: "checkUpdate.worker" */ new URL('../worker/checkUpdate.worker.js', import.meta.url)
  )

  worker.postMessage({
    type: 'check'
  })
  worker.onmessage = ({ data }) => {
    if (data.type === 'hasUpdate') {
      hasUpdate = true
      confirmReload(data.msg, data.lastEtag)
    }
  }
}

这里因为缺换路由本来就要刷新页面,用户可无需感知系统更新信息,直接通过请求头的Etag即可,这里的Fetch方法就用head获取相应头就好了。

checkUpdate.worker.js:

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 }) => {
  if (data.type === 'check') {
    // 每5分钟执行一次
    // 立即执行一次,获取最新的etag,避免在setInterval等待中系统更新,第一次获取的etag是新的,但是lastEtag还是undefined,不满足条件,错失刷新时机
    checkUpdate()
    intervalId = setInterval(checkUpdate,5 * 60 * 1000)
  }
  if (data.type === 'recheck') {
    // 每5分钟执行一次
    hasUpdate = false
    lastEtag = data.lastEtag
    intervalId = setInterval(checkUpdate,  5 * 60 * 1000)
  }
  if (data.type === 'pause') {
    clearInterval(intervalId)
  }
  if (data.type === 'destroy') {
    clearInterval(intervalId)
    close()
  }
})

如果不使用worker直接讲轮询逻辑放在checkUpdate即可

四、 Worker引入

从 webpack 5 开始,你可以使用 Web Workers 代替 worker-loader。

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

也可以逻辑写成字符串,然后通过ToURL给new Worker,如下:

function createWorker(f) {
  const blob = new Blob(['(' + f.toString() +')()'], {type: "application/javascript"});
  const blobUrl = window.URL.createObjectURL(blob);
  const worker = new Worker(blobUrl);
  return worker;
}

createWorker(function () {
  self.addEventListener('message', function (event) {
    // 消费信息
      self.postMessage('send message')
  }, false);
})

五、Worker通信

// 主线程
var uInt8Array = new Uint8Array(new ArrayBuffer(10));
for (var i = 0; i < uInt8Array.length; ++i) {
  uInt8Array[i] = i * 2; // [0, 2, 4, 6, 8,...]
}
worker.postMessage(uInt8Array);
// Worker 线程
self.onmessage = function (e) {
  var uInt8Array = e.data;
  postMessage('Inside worker.js: uInt8Array.toString() = ' + uInt8Array.toString());
  postMessage('Inside worker.js: uInt8Array.byteLength = ' + uInt8Array.byteLength);
};

但是,拷贝方式发送二进制数据,会造成性能问题。比如,主线程向 Worker 发送一个 500MB 文件,默认情况下浏览器会生成一个原文件的拷贝。为了解决这个问题,JavaScript 允许主线程把二进制数据直接转移给子线程,但是一旦转移,主线程就无法再使用这些二进制数据了,这是为了防止出现多个线程同时修改数据的麻烦局面。这种转移数据的方法,叫做Transferable Objects。这使得主线程可以快速把数据交给 Worker,对于影像处理、声音处理、3D 运算等就非常方便了,不会产生性能负担。

如果要直接转移数据的控制权,就要使用下面的写法。

然而,并不是所有的对象都可以被转移。只有那些被设计为可转移的对象(用[ Transferable ] IDL 扩展属性修饰),比如ArrayBuffer、MessagePort,ImageBitmap,OffscreenCanvas,才能通过这种方式来传递。转移操作是不可逆的,一旦对象被转移,原始上下文中的引用将不再有效。转移对象可以显著减少复制数据所需的时间和内存。

// Transferable Objects 格式
worker.postMessage(arrayBuffer, [arrayBuffer]);

// 例子
var ab = new ArrayBuffer(1);
worker.postMessage(ab, [ab]);
  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
前端线上部署是指将前端代码部署到生产环境,使用户可以访问和使用最新的前端功能和界面。通知用户有新版本可以通过以下几种方式实现: 1. 弹窗通知:在用户访问网站或应用时,弹出一个通知窗口告知用户有新版本可用。这可以通过在前端代码中添加一个弹窗组件,并在后端部署新版本时触发弹窗通知。 2. 横幅通知:在网站或应用的顶部或底部添加一个横幅,显示有新版本可用的提示信息。这可以通过在前端代码中添加一个固定位置的横幅组件,并在后端部署新版本时触发横幅通知。 3. 电子邮件通知:将新版本的信息发送给用户的注册邮箱,通知他们有新版本可用。这可以通过在后端代码中添加一个发送邮件的功能,并在部署新版本时触发邮件通知。 4. 短信通知:将新版本的信息发送给用户的手机号码,通知他们有新版本可用。这可以通过在后端代码中添加一个发送短信的功能,并在部署新版本时触发短信通知。 5. APP推送通知:对于移动应用,可以使用推送通知的方式通知用户有新版本可用。这可以通过在后端代码中添加一个推送通知的功能,并在部署新版本时触发推送通知。 需要注意的是,以上通知方式需要在前端和后端代码中进行相应的开发和配置。具体的实现方式可以根据项目需求和技术栈来选择和调整。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值