vue项目检测版本变化并提示用户刷新页面

思路

  • 前端项目自动化部署发版本时,使用webpack构建命令生成json文件,json中写一个随机生成的字符串(比如打包版本时的时间戳),每次打包自动更新这个文件。
  • 在项目中,通过定时任务或者切换页面路由,请求json文件。使用本地保存的上一次生成的字符串和json文件中的字符串进行比较,如果两个字符串不一样,则说明前端重新部署了,提醒用户进行更新操作。

实现

1. version.js

项目根目录下创建version.js

js 复制代码
const fs = require('fs')
const Timestamp = new Date().getTime()
fs.writeFile('public/version.json', '{"version":' + Timestamp + '}\n', function(err) {
  if (err) {
    return console.log(err)
  }
})

2. package.json

修改构建命令,打包时执行version.js的命令

js 复制代码
 "scripts": {
    "build:prod": "node version.js && vue-cli-service build --mode production && rm -rf dist/static/js/*.map",
    "build:staging": "node version.js && vue-cli-service build --mode staging",
  },

3. vue.config.js

添加配置,通过 html-webpack-plugin 插件自定义html注入变量

js 复制代码
const HtmlWebpackPlugin = require('html-webpack-plugin')
const bpmVersion = require('./public/version.json')

...

configureWebpack: {
    // provide the app's title in webpack's name field, so that
    // it can be accessed in index.html to inject the correct title.
    name: name,
    resolve: {
      alias: {
        '@': resolve('src')
      }
    },
    plugins: [
      new HtmlWebpackPlugin({
        template: './public/index.html',
        inject: true,
        bpmVersion: bpmVersion?.version
      })
    ]
  },

npm run build 打包 runtime.js 404问题

注释这一段代码: inline: /runtime..*.js$/

js 复制代码
config.plugin('ScriptExtHtmlWebpackPlugin').after('html').use('script-ext-html-webpack-plugin', [{
  // `runtime` must same as runtimeChunk name. default is `runtime`
  // inline: /runtime\..*\.js$/
}]).end()

4. index.html

利用meta标签,存储版本号字段

npm run build 打包报错: Template execution failed: ReferenceError: BASE_URL is not defined

方法:把 BASE_URL 改成 htmlWebpackPlugin 注入字段就可以了

js 复制代码
<meta name="version" content="<%= htmlWebpackPlugin.options.bpmVersion %>" />

// <link rel="icon" href="<%= BASE_URL %>favicon.ico" />
<link rel="icon" href="<%= htmlWebpackPlugin.options.url %>favicon.ico" />

5. 新建src/utils/watchUpdate.js文件

js 复制代码
import Modal from '@/components/UpdateModal'
import Vue from 'vue'

let time = 0
let timer = null
let isMove = false
let isMoveTimer = null
const timerFunction = async() => {
  // Stop polling when the number of times exceeds
  if (isMove || time >= 1) {
    clearInterval(timer)
    return (timer = null)
  }

  const res = await fetch(`/version.json?v=${new Date().getTime()}`, {
    headers: { 'Cache-Control': 'no-cache' }
  })
    .then(res => {
      return res?.json()
    })
    .catch(err => {
      console.log(err)
      return clearTimer()
    })

  const latestVersion = res?.version
  const currentPageVersion = Number(
    document.querySelector('meta[name="version"]').content || ''
  )
  console.log(latestVersion, '--------', currentPageVersion)
  if (latestVersion && latestVersion !== currentPageVersion) {
    const MessageConstructor = Vue.extend(Modal)
    const instance = new MessageConstructor({
      data: {}
    })
    instance.id = new Date().getTime().toString()
    instance.$mount()
    document.body.appendChild(instance.$el)
    return clearTimer()
  }

  time++
}

const moveFunction = () => {
  time = 0
  isMove = true
  clearTimeout(isMoveTimer)
  isMoveTimer = setTimeout(function() {
    isMove = false
    console.log(isMove)
    if (!timer && !isMove) {
      timer = setInterval(timerFunction, 1000)
    }
  }, 10000)
}

if (process.env.NODE_ENV !== 'development') {
  timer = setInterval(timerFunction, 5000)
  window.addEventListener('mousemove', moveFunction)
}

const clearTimer = () => {
  clearInterval(timer)
  window.removeEventListener('mousemove', moveFunction)
  timer = null
}

6. 新建 updateModal.vue文件

js 复制代码
<template>
  <div class="update-modal-container">
    <div class="update-modal">
      <div class="update-modal_title">System Update 🚀</div>
      <div class="update-modal_body">
        The system has been updated, please refresh the page (please save the
        current page data before refreshing).
      </div>
      <div class="update-modal_footer">
        <el-button
          type="primary"
          size="small"
          plain
          @click="handleAfterLeave"
        >Ignore</el-button>
        <el-button
          type="primary"
          size="small"
          @click="refresh"
        >Refresh</el-button>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'UpdateModal',
  data() {
    return {}
  },
  methods: {
    handleAfterLeave() {
      this.$destroy(true)
      this.$el.parentNode.removeChild(this.$el)
    },
    refresh() {
      this.handleAfterLeave()
      location.reload(true)
    }
  }
}
</script>

<style lang="scss" scoped>
.update-modal-container {
  position: fixed;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.5);
  z-index: 99999;
  display: flex;
  justify-content: center;
  align-items: center;
  .update-modal {
    user-select: none;
    max-width: 450px;
    background-color: #fff;
    box-shadow: 0px 0px 6px 0px rgba(0, 0, 0, 0.1);
    border-radius: 10px;
    animation: shakeY 1.5s linear;
    z-index: 99998;
    &_title {
      padding: 16px 24px;
      border-bottom: 1px solid #f0f0f2;
      color: #171725;
      font-size: 20px;
      font-weight: 600;
      line-height: 150%;
      letter-spacing: 0.1px;
    }
    &_body {
      padding: 32px 24px;
      color: #171725;
      font-size: 16px;
      font-weight: 400;
      line-height: 150%;
      letter-spacing: 0.1px;
    }
    &_footer {
      padding: 16px 24px;
      border-top: 1px solid #efefef;
      display: flex;
      justify-content: flex-end;
    }
  }
}
@keyframes shakeY {
  from,
  to {
    transform: translate3d(0, 0, 0);
  }

  10%,
  30%,
  50%,
  70%,
  90% {
    transform: translate3d(0, -10px, 0);
  }

  20%,
  40%,
  60%,
  80% {
    transform: translate3d(0, 10px, 0);
  }
}

.shakeY {
  animation-name: shakeY;
}
</style>

7. 在main.js中导入watchUpdate文件

js 复制代码
import '@/utils/watchUpdate'
相关推荐
昨天;明天。今天。5 分钟前
案例-任务清单
前端·javascript·css
zqx_71 小时前
随记 前端框架React的初步认识
前端·react.js·前端框架
惜.己2 小时前
javaScript基础(8个案例+代码+效果图)
开发语言·前端·javascript·vscode·css3·html5
什么鬼昵称2 小时前
Pikachu-csrf-CSRF(get)
前端·csrf
长天一色2 小时前
【ECMAScript 从入门到进阶教程】第三部分:高级主题(高级函数与范式,元编程,正则表达式,性能优化)
服务器·开发语言·前端·javascript·性能优化·ecmascript
NiNg_1_2343 小时前
npm、yarn、pnpm之间的区别
前端·npm·node.js
秋殇与星河3 小时前
CSS总结
前端·css
BigYe程普3 小时前
我开发了一个出海全栈SaaS工具,还写了一套全栈开发教程
开发语言·前端·chrome·chatgpt·reactjs·个人开发
余生H3 小时前
前端的全栈混合之路Meteor篇:关于前后端分离及与各框架的对比
前端·javascript·node.js·全栈
程序员-珍3 小时前
使用openapi生成前端请求文件报错 ‘Token “Integer“ does not exist.‘
java·前端·spring boot·后端·restful·个人开发