前端项目发布后,如何使正在使用的用户更新为最新的版本?

1.背景

  • 每次项目上线后,异常监控总是零零散散报一些资源加载或者解析失败的告警
    在这里插入图片描述
  • 仔细对比chunk的hash值会发现已经是上一版本的js文件
  • 为什么会出现这个问题呢?也不难想到,项目是单页应用,页面使用懒加载分多个chunk打包,首次只加载首页需要的js文件。如果在项目发布前,用户已经在程序中,并且还有未访问的页面,此时我们重新发布上线了,老的文件已经被覆盖,用户再访问未访问过的页面时就会找不到资源,导致白屏。
  • 如果用户在发布前已经进入过其他页面,被缓存在本地,这样虽然不会导致白屏了,但是也无法看到最新的效果。
  • 在测试环境模拟一下,点击一个未访问过的页面时,确实白屏了,报错信息也吻合
    前端项目发布后,如何使正在使用的用户更新为最新的版本?_第1张图片

2.解决思路

  • 导致这些问题的根本原因就是,用户和程序都无法感知项目已上线,无法做到立即刷新,重新加载,那我们就想办法在项目上线后通知页面进行刷新。

2.1运行中的项目,如何感知到项目更新了

  • 最容易想到的就是有一个存储版本号的地方,我们去轮询是否和上一次请求到的版本一致,如果不一致去做一些刷新的操作
  • 既然是前端的项目,最好还是我们自己去维护这个数据,所以我选择在每次打包的时候生成一个存储当前时间戳的json文件,存在dist目录下一起放到我们前端服务器上。
  • 写一个最最简单的webpack插件帮我们实现生成json文件的功能
const pluginName = 'GenerateTimeJsonWebpackPlugin';
const fs = require('fs');
const path = require('path');

class GenerateTimeJsonWebpackPlugin {
  apply(compiler) {
    compiler.hooks.run.tap(pluginName, (compilation) => {
      const filePath = path.resolve('build', 'timeStamp.json');
      fs.writeFile(filePath, `${JSON.stringify({time: new Date().getTime()})}`, (err) => {
        console.log(err);
      });
    });
  }
}

module.exports = GenerateTimeJsonWebpackPlugin;
  • 这样每次构建时就会多一个文件
    前端项目发布后,如何使正在使用的用户更新为最新的版本?_第2张图片
    在这里插入图片描述

2.2什么时机判断是否更新?

  • 轮询肯定是最耗性能的,间隔太小浪费,间隔太大又有白屏的风险
  • 由于我们出现请求页面的时机总是在页面加载的时候,所以选择加一个路由守卫,在页面跳转前判断是否更新,如果更新了就reload页面。
  • reload就可以更新是因为,每次平时index.html时是带有时间戳的,会请求最新文件,新的文件中内联引入的main.js,和其他chunk, hash值都是最新的,所以映射的其他懒加载页面的文件也是最新的。
  • 项目是用react进行开发的,所以写个高阶组件来实现守卫的功能
import { Route } from 'react-router-dom';
import { useRef, useState } from 'react';
import axios from "axios";
const basePath = '/mp/138519745866498048/368331042878263296/credit-shop/';

// 路由守卫
const BeforeRouteEnter = (props) => {
  const {path, exact, component} = props;

  // 用来控制在时间戳未请求到时,不加载路由对应的页面,避免请求到老的资源
  const [loading, setLoading] = useState(process.env.NODE_ENV === 'production');

  // 用来存储当前项目的时间戳
  const timesTamp = useRef('');
  
  // 生产环境需要判断 当前访问的chunk是否已经更新
  if (process.env.NODE_ENV === 'production') {

    // 由于项目中统一封装过的axios做了很多错误的处理 为了不影响正常页面的功能 选择单独使用axios请求
    axios
      .get(basePath + `timeStamp.json?t=${new Date().getTime()}`)
      .then(res => {

        // 判断是否已经更新了代码 更新了就重新加载页面 请求新的资源
        if (timesTamp.current && (timesTamp.current !== res.data?.time)) {
          window.location.reload();
        }
        timesTamp.current = res.data?.time;
        setLoading(false);
      }).catch(err => {
        setLoading(false);
      });
  }
  return !loading ? <Route path={path} exact={exact} component={component}></Route> : ''

}
export default BeforeRouteEnter;

然后将Route组件改成我们自己封装的BeforeRouteEnter,这样在每次跳转前都请求一次当前打包的时间戳,判断是否和正在运行中的一致,不一致就重载,这样一来就解决了我们的问题~

有问题欢迎大家及时指出,避免误导其他同学~

你可能感兴趣的:(js,前端,javascript,webpack,react)