如何优雅的实现前端版本投产主动触发浏览器刷新更新版本内容

需要背景

前端我的项目版本投产后如果用户没有及时的拿到最新投产的资源包,可能会存在以下问题:

  1. 短少新性能或修复:如果最新的资源蕴含新性能或修复了现有的问题,但用户没有及时获取到它们,那么用户将无奈及时的体验到这些新性能或修复的益处。
  2. 平安问题:新版本的资源可能蕴含了安全性修复,修复了已知的破绽或平安危险。如果用户没有及时的获取到这些修复,那么网站可能存在潜在的平安危险,容易受到攻打。
  3. 程序报错:如果最新投产的版本蕴含了一些配合后端接口数据革新,比方后端接口数据结构或者字段产生了扭转,然而前端动态资源JS没有获取到最新的,可能会导致程序报错,会重大影响到用户体验。

为了防止以上问题,确保在前端版本投产后,能及时获取到最新的前端动态资源,比方js、css和图片等,咱们须要应用一种比拟好的计划来实现该需要,那么,目前有哪些比拟常见的解决方案呢?

后期调研

其实,检测前端我的项目版本内容变动主动刷新浏览器以更新版本内容的实现计划有不少,上面是目前常见的一些实现计划:

  1. 轮询检测版本更新:
  • 在前端代码中增加一个定时器,定期向服务器发送申请,查看是否有新的版本可用。
  • 服务器端能够提供一个接口用于查看版本更新,比拟以后客户端的版本号与服务器端最新版本号是否统一。
  • 如果有新版本可用,前端代码能够主动触发页面刷新更新版本内容。

优缺点:实现起来简略粗犷,然而,定时轮询检测版本更新可能会对服务器端和客户端造成肯定的累赘,而且轮询工夫距离不好管制,多久轮询一次适合?

  1. 应用服务端推送技术(Server-Sent Events,SSE):
  • Server-Sent Events 是一种基于HTTP的单向通信机制,容许服务器实时向客户端发送事件音讯。
  • 在前端代码中,通过创立 EventSource 对象来与服务器建设 SSE 连贯,并监听服务器发送的音讯。
  • 当服务器检测到新版本时,能够向客户端发送一个 SSE 事件音讯,客户端收到音讯后触发浏览器主动刷新操作。

优缺点:SSE 的长处是它是一个简略而轻量级的协定,不须要额定的库或框架,实用于一些简略的实时通信场景。

  1. 应用WebSocket实时通信:
  • 建设WebSocket连贯,使服务器可能实时向客户端发送音讯。
  • 当有新的版本可用时,服务器能够被动向客户端发送告诉。
  • 客户端收到告诉后,能够主动触发浏览器刷新更新版本内容。

优缺点:通过WebSocket实时通信,服务器能够间接向客户端推送音讯,无需进行轮询。当服务器检测到新版本时,它能够立刻发送音讯给客户端,客户端收到音讯后触发浏览器主动刷新操作,实现版本内容自动更新。

这三种实现计划都须要后端配合,那有没有不须要后端配合,纯前端就能实现的计划呢?那必须有呀

纯前端实现版本投产主动刷新浏览器更新版本内容

应用nodejs脚本生成版本信息json文件 + 监听页面显示和暗藏会触发的visibilitychange事件,纯前端实现版本投产主动刷新浏览器更新版本内容

大抵实现原理:

  1. 应用nodejs编写脚本,获取git版本相干信息(必须蕴含git commitId,用于版本比照),并保留为json文件,寄存在构建打包的目录下(比方,public目录)。
  2. 应用页面显示和暗藏会触发的visibilitychange事件,监听页面的显示和暗藏操作,如果页面显示,则申请打包放在dist根目录下的版本信息json文件,比照以后打包版本的commitId与历史版本信息json文件中commitId是否统一,如果不统一,则触发浏览器刷新。
  3. vite打包我的项目应用.env文件 + import.meta.env保留以后打包变量(webpack打包我的项目能够应用definePlugin插件 + process.env 保留变量)

应用nodejs编写获取git版本信息的脚本

前置常识:

  • vite 我的项目打包,以及理解我的项目架构和目录构造
  • nodejs 命令执行、文件读写操作相干api
  • dotenv 装置 dotenv npm依赖,用于批改 .env 文件

话不多说,间接上代码:

// useNodeGetGitInfo.js /** 定义模块和变量**/// const exec = require('child_process').exec //异步子过程const execSync = require('child_process').execSync // 同步子过程const fs = require('fs') // 文件读取模块const path = require('path') // 文件门路解决模块const gitInfoPath = 'gitInfo.json' // gitInfo门路const publicPath = 'public' // 不能放到dist目录(该目录打包文件会被清空),要放到public目录,const autoPush = false // 写入版本信息之后是否主动提交git上const isVite = true // 是否是vite构建打包const commitId = execSync('git show -s --format=%H').toString().trim() // 以后提交的版本号// 不借用chalk库,原生Node打印色彩// console.log('\x1b[32m%s\x1b[0m', '这是绿色文本') // 绿色// console.log('\x1b[33m%s\x1b[0m', '这是黄色文本') // 黄色// console.log('\x1b[31m%s\x1b[0m', '这是红色文本') // 红色/** 程序开始**/let gitInfoObj = {} // 保留git版本信息Date.prototype.format ||  (Date.prototype.format = function (fmt) {    const opt = {      'Y+': this.getFullYear().toString(), // 年      'm+': (this.getMonth() + 1).toString(), // 月      'd+': this.getDate().toString(), // 日      'H+': this.getHours().toString(), // 时      'M+': this.getMinutes().toString(), // 分      'S+': this.getSeconds().toString(), // 秒      // 有其余格式化字符需要能够持续增加,必须转化成字符串    }    for (let k in opt) {      if (new RegExp('(' + k + ')', 'i').test(fmt)) {        fmt = fmt.replace(          RegExp.$1,          RegExp.$1.length == 1            ? opt[k]            : opt[k].padStart(RegExp.$1.length, '0'),        )      }    }    return fmt  })// 如果gitInfoPath存在,将先读取里边的版本信息if (fs.existsSync(gitInfoPath)) {  gitInfoObj = JSON.parse(fs.readFileSync(gitInfoPath).toString())}// 判断以后版本是否曾经存在,存在则不再次生成if (gitInfoObj.commitId === commitId) {  console.warn('\x1B[33m%s\x1b[0m', 'warning: 以后的git版本数据曾经存在了!\n')} else {  const currentGitBranch = execSync('git rev-parse --abbrev-ref HEAD')    .toString()    .trim() // 以后git分支  const name = execSync('git show -s --format=%cn').toString().trim() // 姓名  const email = execSync('git show -s --format=%ce').toString().trim() // 邮箱  const date = new Date(execSync('git show -s --format=%cd').toString()) // 日期  const message = execSync('git show -s --format=%s').toString().trim() // 阐明  gitInfoObj = {    currentGitBranch,    name,    email,    date: date.format('yyyy-mm-dd hh:mm:ss'),    commitId,    message,  }  const saveInfoStr = JSON.stringify(gitInfoObj, null, 2)  fs.writeFileSync(gitInfoPath, saveInfoStr)  // 写入版本信息之后,主动将版本信息提交到以后分支的git上  if (autoPush) {    execSync(`git add .`)    execSync(`git commit ${gitInfoPath} -m 主动提交版本信息`)    execSync(      `git pull origin ${execSync('git rev-parse --abbrev-ref HEAD')        .toString()        .trim()}`,    )    execSync(      `git push origin ${execSync('git rev-parse --abbrev-ref HEAD')        .toString()        .trim()}`,    )  }  // 程序执行完结  console.log(    '\x1b[32m%s\x1b[0m',    `execute success: file address is ${process.cwd()}/${gitInfoPath}\n`,  )}// 将gitInfo文件移植到public文件中,以便构建工具可能失常打包到我的项目根目录if (fs.existsSync(publicPath)) {  fs.writeFileSync(    `${process.cwd()}/${publicPath}/${gitInfoPath}`,    fs.readFileSync(gitInfoPath),  )}// 如果是vite构建打包,把git信息追加写入.env文件中if (isVite) {  const dotenv = require('dotenv')  const envPath = `${process.cwd()}/dotenv/.env`  // 读取 .env 文件内容  const envContent = fs.readFileSync(envPath, {    encoding: 'utf-8',  })  // 解析内容为键值对对象  const envVariables = dotenv.parse(envContent)  const gitInfoStr = JSON.stringify(gitInfoObj)  // 批改特定的环境变量  envVariables.VITE_GIT_INFO = gitInfoStr  // 将批改后的键值对转换为字符串  const updatedEnvContent = Object.entries(envVariables)    .map(([key, value]) => `${key}=${value}`)    .join('\n')  // 将批改后的内容写入 .env 文件  console.log(updatedEnvContent)  fs.writeFileSync(envPath, updatedEnvContent, { encoding: 'utf-8' })  console.log('\x1b[32m%s\x1b[0m', '.env 文件已更新')}

配置执行获取git版本信息脚本命令

// package.json"scripts": {  "build": "npm run get-git-info && vite build",  "preview": "vite preview",  "get-git-info": "node scripts/git/useNodeGetGitInfo.js",},

留神:scripts/git/useNodeGetGitInfo.js这个是笔者我的项目寄存脚本的门路,可依据本人我的项目适当调整

我的项目入口JS文件,监听visibilitychange事件

前置常识:

  • vite + vue3 生命周期
  • visibilitychange 事件用法
  • import.meta.env 获取 vite 打包相干变量
// app.jsconst updateVersion = () => {  // 检测新版本主动刷新浏览器更新版本内容  if (import.meta.env.MODE !== 'development') {    // 获取以后版本git信息    const gitInfo = import.meta.env.VITE_GIT_INFO    const gitInfoObj = gitInfo && JSON.parse(gitInfo)    // 通过监听visibilitychange事件,取获取git版本信息    document.addEventListener('visibilitychange', () => {      // 只有在页面显示的时候才触发版本检测      if (document.visibilityState === 'hidden') return      // 应用工夫戳避免申请到缓存的数据      fetch(`/gitInfo.json?v=${Date.now()}`)        .then((res) => {          return res.json()        })        .then((data) => {          if (data.commitId !== gitInfoObj.commitId) {            location.reload()          }        })    })  }}onMounted(() => {  updateVersion()})

实现以上步骤,能够在本人我的项目中应用 npm run build && npm run preview进行测试并查看预览成果。

node脚本生成的文件

生成的git版本信息json文件

// gitInfo.json{  "currentGitBranch": "master",  "name": "Better",  "email": "924902324@qq.com",  "date": "2023-06-12 21:34:07",  "commitId": "51007620dcea797659336606631f331082fad0c2",  "message": "feat: 打包版本发生变化主动触发浏览器刷新"}

生成 .env 文件

# .env 文件VITE_GIT_INFO={"currentGitBranch":"master","name":"Better","email":"924902324@qq.com","date":"2023-06-12 21:34:07","commitId":"51007620dcea797659336606631f331082fad0c2","message":"feat: 打包版本发生变化主动触发浏览器刷新"}

总结

通过应用nodejs脚本生成git版本信息json文件 + 监听页面显示和暗藏会触发的visibilitychange事件,纯前端实现版本投产主动刷新浏览器更新版本内容,相比定时轮询等实现形式更加优雅,也不须要后端配合,当然也不会减少后端服务器的压力,惟一的毛病可能就是,频繁切换标签关上页面会屡次申请git版本信息json文件,不过因为该文件很小只有500字节左右,还是能够承受的。

有小伙伴可能会问,间接通过在nginx服务器上设置html文件禁止应用缓存(no-store)就好了,因为只有index.html禁止应用缓存,html加载的js和css都是通过vite或者webpack构建工具打包的,文件变动会自动更新文件哈希串,为啥还要用这种形式呢?这种形式的确能够在肯定水平上解决浏览器缓存问题,然而有个前提是须要用户手动刷新浏览器或者从新关上页面,能力加载到最新的资源,如果用户关上页面当前停留在该页面始终不被动刷新,那么用户可能还是拿到的旧的资源,而不是投产后的最新资源,而通过监听页面显示和暗藏会触发的 visibilitychange 事件就能够解决这个问题,即便用户不手动刷新浏览器,只有用户切屏或者切换标签(包含锁屏、睡眠)等都会触发接口申请比照版本,版本更新主动触发浏览器刷新,从而拿到最新的资源包。

如果大家有更好的实现计划,欢送评论区留言,一起学习,一起提高!

本文由mdnice多平台公布