纯前端实现web项目版本发布后用户侧自动刷新

背景

在 Web 系统进行版本迭代更新后,对于处于旧版本的客户端(即在版本发布时用户在线但未刷新页面的情况),无法自动感知到有新版本,需要用户手动刷新页面方可加载最新的版本资源。

技术方案设计

实现目标

仅针对旧版本的客户端:

  • 当版本发布时,主动展示版本更新提示,用户点击提示能够自动刷新页面。
  • 在 SPA 路由跳转时,自动刷新页面。

实现方案设计

解决方案为在前端项目本次版本发布时,将一个最新的版本号 JSON 描述文件存储至服务端(放置于腾讯云 Cos 中)。同时,在前端项目打包构建过程中,将版本号注入到代码里。客户端通过定时轮询的方式,获取服务端的 JSON 文件并与本地的版本号进行对比。若版本号不一致,则提示用户主动刷新。此外,在路由跳转时,判断若需要版本更新,则自动刷新页面

### gen-app-version-vite-plugin 插件实现

通过vite插件的transformIndexHtml钩子,在html中,注入版本信息

js 复制代码
import fs from 'fs'
import path from 'path'

const currWorkingDir = process.cwd()

function generateVersionPlugin() {

  const getVersionData = () => {
    const packageJsonPath = path.join(currWorkingDir, 'package.json')
    const packageJsonData = JSON.parse(fs.readFileSync(packageJsonPath))
    const version = packageJsonData.version
    const versionData = {
      version: version,
      name: packageJsonData.name,
    }
    return versionData
  }

  return {
    name: 'generate-app-version-plugin',
    transformIndexHtml(htmlContent) {
      const versionData = getVersionData()
      const headEndIndex = htmlContent.indexOf('</head>')
      // 在<head>标签结束位置插入全局变量
      const newHtmlContent = htmlContent.slice(0, headEndIndex) + `<script> 
       Object.defineProperty(window, '__yt_build_info__', { value: ${JSON.stringify(versionData)}, writable: false })
       </script>` + htmlContent.slice(headEndIndex)
      return newHtmlContent
    }
  }
}

export default generateVersionPlugin

定时拉取version文件任务实现

项目入口文件中,引入checkAppVersion函数执行即可

js 复制代码
import { Alert } from 'antd'
import ReloadOutlined from '@ant-design/icons/ReloadOutlined'
import ReactDOM from 'react-dom'
const key = '__yt_build_info__'

function compareVersions(version1: string, version2: string) {
  const v1Parts = version1.split('.').map(Number)
  const v2Parts = version2.split('.').map(Number)
  for (let i = 0; i < Math.max(v1Parts.length, v2Parts.length); i++) {
    if (v1Parts[i] === undefined) {
      v1Parts[i] = 0
    }
    if (v2Parts[i] === undefined) {
      v2Parts[i] = 0
    }
    if (v1Parts[i] > v2Parts[i]) {
      return 1
    } else if (v1Parts[i] < v2Parts[i]) {
      return -1
    }
  }
  return 0
}

interface RespData {
  name: 'client' | 'admin'
  version: string
}

const getLocalVersionInfo = () => {
  const win = window as any
  const localBuildInfo = win?.[key] || {}
  return localBuildInfo
}

const containerkey = 'checkVersion'
function renderAlert() {
  const div = document.createElement('div')
  div.id = containerkey
  div.style.display = 'inline-flex'
  div.style.position = 'fixed'
  div.style.left = '50%'
  div.style.top = '-50px'
  div.style.zIndex = '0'
  div.style.transform = 'translate(-50%, 0)'
  div.style.transition = 'top 1s'  // 使用过渡效果

  const existingDiv = document.getElementById(containerkey)
  if (!existingDiv) {
    document.body.appendChild(div)
    setTimeout(() => {
      div.style.top = '20px'
    }, 0)

    ReactDOM.render(
      <Alert
        // type='warn'
        message={<div
          onClick={() => {
            location.reload()
          }}
          style={{
            cursor: 'pointer',
            padding: '0 20px',
            color: 'green'
          }}
        > 当前系统功能有更新迭代,点击此处将刷新页面加载到最新版本  <ReloadOutlined /></div>}
        banner
        // closable
        onClose={() => {
          setTimeout(() => {
            unmountAlert()
          }, 200)
        }}
      />,
      div
    )
  }
}

function unmountAlert() {
  const div = document.getElementById(containerkey)
  if (div) {
    ReactDOM.unmountComponentAtNode(div)
    div.remove()
  }
}

function generateRandomNumber(min: number, max: number) {
  return Math.floor(Math.random() * (max - min + 1)) + min
}

export const checkAppVersion = (opt: { viteModeEnv: string }) => {
  const { viteModeEnv } = opt

  function checkAppVersion() {
    let url = ''
    if (viteModeEnv === 'prod' || viteModeEnv === 'test') {
      url = location.origin + '/version.json'
    }
    if (viteModeEnv === 'dev') {
      url = 'https://you-domain.com' + '/version.json'
    }

    const localBuildInfo = getLocalVersionInfo()
    if (!localBuildInfo.version) {
      return
    }

    fetch(url)
      .then(response => response.json())
      .then((serverVersionInfo: RespData) => {
        if (!serverVersionInfo) return
        const diffVal = compareVersions(serverVersionInfo.version, localBuildInfo.version)
        if (diffVal == 0) {
          setTimeout(() => {
            checkAppVersion()
          }, generateRandomNumber(3, 6) * 1000)
          return
        }
        if (diffVal > 0 || diffVal < 0) {
          renderAlert()
        }
      })
      .catch(error => {
        console.error('获取数据时出错:', error)
      })
  }
  checkAppVersion()
}

实现效果

相关推荐
程序员爱技术1 小时前
Vue 2 + JavaScript + vue-count-to 集成案例
前端·javascript·vue.js
并不会2 小时前
常见 CSS 选择器用法
前端·css·学习·html·前端开发·css选择器
衣乌安、2 小时前
【CSS】居中样式
前端·css·css3
兔老大的胡萝卜2 小时前
ppk谈JavaScript,悟透JavaScript,精通CSS高级Web,JavaScript DOM编程艺术,高性能JavaScript pdf
前端·javascript
低代码布道师2 小时前
CSS的三个重点
前端·css
耶啵奶膘4 小时前
uniapp-是否删除
linux·前端·uni-app
王哈哈^_^5 小时前
【数据集】【YOLO】【目标检测】交通事故识别数据集 8939 张,YOLO道路事故目标检测实战训练教程!
前端·人工智能·深度学习·yolo·目标检测·计算机视觉·pyqt
cs_dn_Jie6 小时前
钉钉 H5 微应用 手机端调试
前端·javascript·vue.js·vue·钉钉
开心工作室_kaic6 小时前
ssm068海鲜自助餐厅系统+vue(论文+源码)_kaic
前端·javascript·vue.js
有梦想的刺儿7 小时前
webWorker基本用法
前端·javascript·vue.js