纯前端实现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()
}

实现效果

相关推荐
迷雾漫步者29 分钟前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-1 小时前
验证码机制
前端·后端
燃先生._.2 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖3 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
m0_748235243 小时前
前端实现获取后端返回的文件流并下载
前端·状态模式
m0_748240254 小时前
前端如何检测用户登录状态是否过期
前端
black^sugar4 小时前
纯前端实现更新检测
开发语言·前端·javascript
寻找沙漠的人5 小时前
前端知识补充—CSS
前端·css
GISer_Jing5 小时前
2025前端面试热门题目——计算机网络篇
前端·计算机网络·面试