Redon

一心的小屋

vite 项目更新后客户端更新提示

发布于 # Vite

原理

Vite 部分配置

在项目根目录添加一个 build 文件夹,存放 vite 相关的 .ts 文件。

build 文件夹添加 html.ts,编写一个 vite 插件,作用是打包时候在 index.html 文件的 head 内插入当前打包的时间。

// 路径 /build/html.ts
import type { Plugin } from 'vite'

export function setupHtmlPlugin(buildTime: string) {
  const plugin: Plugin = {
    name: 'html-plugin',
    apply: 'build',
    transformIndexHtml(html) {
      return html.replace('<head>', `<head>\n    <meta name="buildTime" content="${buildTime}">`)
    }
  }

  return plugin
}

build 文件夹添加 time.ts,并且安装 dayjs 库,编写一个获取时间的函数。

// 路径 /build/time.ts
import dayjs from 'dayjs'
import timezone from 'dayjs/plugin/timezone'
import utc from 'dayjs/plugin/utc'

export function getBuildTime() {
  dayjs.extend(utc)
  dayjs.extend(timezone)

  const buildTime = dayjs.tz(Date.now(), 'Asia/Shanghai').format('YYYY-MM-DD HH:mm:ss')

  return buildTime
}

vite.config.ts 文件中引用

import process from 'node:process'
import { fileURLToPath, URL } from 'node:url'
import { defineConfig, loadEnv } from 'vite'
import { setupHtmlPlugin } from './build/html'
import { getBuildTime } from './build/time'

export default defineConfig(() => {
  // 当前打包时间
  const buildTime = getBuildTime()

  return {
    base: '/',
    plugins: [
      vue(),
      // 使用插件
      setupHtmlPlugin(buildTime)
    ],
    define: {
      // 注入全局的 BUILD_TIME 变量
      BUILD_TIME: JSON.stringify(buildTime)
    },
  }
})

项目配置部分

添加 src/plugins 目录,添加 app.ts 文件,编写相关逻辑,这里使用 Vue3Naive UI 作为参考

// 路径 src/plugin/app.ts
import type { App } from 'vue'
import { NButton } from 'naive-ui'
import { h } from 'vue'

const UPDATE_CHECK_INTERVAL = 3 * 60 * 1000

export function setupAppVersionNotification() {
  let isShow = false
  let updateInterval: ReturnType<typeof setInterval> | undefined

  const shouldCheckForUpdates = [!isShow, document.visibilityState === 'visible', !import.meta.env.DEV].every(Boolean)

  const checkForUpdates = async () => {
    if (!shouldCheckForUpdates)
      return

    const buildTime = await getHtmlBuildTime()

    if (buildTime === BUILD_TIME) {
      return
    }

    isShow = true

    const n = window.$notification?.create({
      title: '系统版本更新通知',
      content: '检测到系统有新版本发布,是否立即刷新页面?',
      action() {
        return h('div', { style: { display: 'flex', justifyContent: 'end', gap: '12px', width: '325px' } }, [
          h(
            NButton,
            {
              onClick() {
                n?.destroy()
              }
            },
            () => '稍后再说'
          ),
          h(
            NButton,
            {
              type: 'primary',
              onClick() {
                location.reload()
              }
            },
            () => '立即刷新'
          )
        ])
      },
      onClose() {
        isShow = false
      }
    })
  }

  const startUpdateInterval = () => {
    if (updateInterval) {
      clearInterval(updateInterval)
    }
    updateInterval = setInterval(checkForUpdates, UPDATE_CHECK_INTERVAL)
  }

  if (shouldCheckForUpdates) {
    document.addEventListener('visibilitychange', () => {
      if (document.visibilityState === 'visible') {
        checkForUpdates()
        startUpdateInterval()
      }
    })

    startUpdateInterval()
  }
}

async function getHtmlBuildTime() {
  const baseUrl = '/'

  const res = await fetch(`${baseUrl}index.html?time=${Date.now()}`)

  const html = await res.text()

  const match = html.match(/<meta name="buildTime" content="(.*)">/)

  const buildTime = match?.[1] || ''

  return buildTime
}

src/main.ts 文件引入刚刚文件

import { createApp } from 'vue'
import App from './App.vue'
import { setupAppVersionNotification } from './plugins/app'

async function bootstrap() {
  const app = createApp(App)
  // 如果是 react,同理也是在 dom 加载前
  setupAppVersionNotification()
  app.mount('#app')
}

bootstrap()

其它方案

使用 vite-plugin-version 插件在打包时读取 package.json 内的版本号并生成 version.json 文件,比对文件版本进行提示更新,原理其实一样。

备注

代码部份来自 SoybeanAdmin,有改动