前端发布新版本浏览器自动更新

308 阅读4分钟

本人vue3+vite项目

背景

  • 前端项目版本投产后如果用户没有及时的拿到最新投产的资源包,可能会存在以下问题:
  1. 缺少新功能或修复:如果最新的资源包含新功能或修复了现有的问题,但用户没有及时获取到它们,那么用户将无法及时的体验到这些新功能或修复的好处。
  2. 安全问题:新版本的资源可能包含了安全性修复,修复了已知的漏洞或安全风险。如果用户没有及时的获取到这些修复,那么网站可能存在潜在的安全风险,容易受到攻击。
  3. 程序报错:如果最新投产的版本包含了一些配合后端接口数据改造,比如后端接口数据结构或者字段发生了改变,但是前端静态资源JS没有获取到最新的,可能会导致程序报错,会严重影响到用户体验。

解决方案

  1. 轮询检测版本更新
  2. 使用服务端推送技术(Server-Sent Events,SSE)
  3. 使用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版本信息的脚本

/* useNodeGetGitInfo.js */
/** 定义模块和变量* */
// const exec = require('child_process').exec //异步子进程
const moment = require('moment');
const { execSync } = require('child_process'); // 同步子进程
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版本信息

// 如果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: moment(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()}/.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",
},

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

// app.vue
...
import { useDocumentVisibility } from '@vueuse/core';

const visibility = useDocumentVisibility();
watch(visibility, (current, previous) => {
  // 获取当前版本git信息
  const gitInfo = import.meta.env.VITE_GIT_INFO;
  const gitInfoObj = gitInfo && JSON.parse(gitInfo);
  if (current === 'visible' && previous === 'hidden' && import.meta.env.MODE !== 'development') {
    fetch(`/${import.meta.env.MODE}/gitInfo.json`)
      .then((res) => res.json())
      .then((data) => {
        if (data.commitId !== gitInfoObj.commitId) {
          ElMessageBox({
            title: '更新提示',
            message: '有新的版本发布,请及时更新',
            confirmButtonText: '确认',
            callback: () => {
              window.location.reload();
            },
          });
        }
      });
  }
});

完成以上步骤,可以在自己项目中使用 npm run build && npm run preview进行测试并查看预览效果。