前端重新部署后,领导跟我说页面崩溃了...

12,539 阅读2分钟

背景

每次前端更新,重新部署后,用户还停留在更新之前的页面,当请求页面数据时,会导致页面白屏,报错信息如下:

Uncaught ChunkLoadError: Loading chunk {n} failed.

原因

每次更新后,使用 react.lazy 导致路由缓存,然而版本发布后,js脚本更新导致出现错误。

解决方案

1.对error事件进行监听,检测到错误之后重新刷新

      window.addEventListener(
        'error',
        function (event) {
          if (
            event.message &&
            String(event.message).includes('Loading chunk') &&
            String(event.message).includes('failed')
          ) {
             window.location.reload();
          }
        },
        true,
      );

2.对window.console.error事件监听,效果同上

      window.console.error = function () {
        console.log(JSON.stringify(arguments), 'window.console.error'); // 自定义处理
      };

3.其他方案

如:HTTP2.0推送机制 / fis3 构建 /webSocket通知等,未尝试

注:有好的方案可以在下面评论讨论哈

本篇收录在个人工作记录专栏中,专门记录一些比较有意思的场景和问题。

后记

在之后的某一天,该问题再次暴露出来。源于一位同事在使用过程中,会不定页面的出现报错情况,报错如下:

image.png

很明显,还是资源加载问题,按道理讲应该可以走入我们逻辑进行刷新。但是当时用户反馈:刷新也不能解决问题,强制刷新才可以解决。

分析:刷新为什么不能解决问题?其实还是因为当用户在发版后第一次进入页面,因为网络原因等未成功加载完资源就退出了,后续刷新均走的缓存中的错误资源,因此让用户手动去刷新解决不了问题,恰巧这个资源又是css资源。

解决方案:

不知道大家发现没有,上面对error事件进行监听代码中,并没有包括css失败的情况,因此匹配上css加载失败的情况即可。

更新代码如下:

      window.addEventListener(
        'error',
        function (event) {
          if (
            event.message &&
            String(event.message).includes('chunk') &&
            String(event.message).includes('failed')
          ) {
            window.location.replace(window.location.href);
          }
        },
        true,
      );

后续优化方案:

使用errorBoundary解决更加友好,通过cookie防止刷新失败后不停的刷新,指定两小时内刷新一次:

import React from 'react';
import Cookies from 'js-cookie';
import { Result, Button } from 'antd';

const CK_RESOURCE_LOAD_FAILED = 'resource-load-failed';

// 1. Loading chunk
const reg1 = /Loading chunk/i;
// 2. Failed to load resource
const reg2 = /Failed to load resource/i;
// 3. load failed with status 404
const reg3 = /load failed with status 404/;
// 4. Failed to load module script
const reg4 = /load module script/;

const hasJsError = (error: Error) =>
  reg1.test(String(error)) || reg2.test(String(error)) || reg3.test(String(error)) || reg4.test(String(error));

// react.lazy导致路由缓存,然而版本发布后,js脚本更新导致出现js加载出现404
export default class StarshipErrorBoundary extends React.PureComponent {
  state = {
    hasError: false,
  };

  componentDidCatch(error: Error) {
    const jsLoadError = hasJsError(error);
    // 是否已经刷新的标记
    const hasReload = Cookies.get(CK_RESOURCE_LOAD_FAILED) ?? false;

    //如果是缓存错误且两小时内没有触发过则刷新
    if (jsLoadError && !hasReload) {
      window.location.replace(window.location.href);
      setTimeout(() =>
        Cookies.set(CK_RESOURCE_LOAD_FAILED, 'true', {
          expires: 2 / 24,
        })
      );
    }
  }

  static getDerivedStateFromError(error: Error) {
    console.log(error, 'getDerivedStateFromError');
    return {
      hasError: true,
    };
  }

  render() {
    return this.state.hasError ? (
      <Result
        style={{ width: '100%' }}
        status="404"
        title="404"
        subTitle="对不起,访问的页面存在错误或已经丢失."
        extra={
          <Button type="primary" onClick={() => window.location.replace(window.location.href)}>
            刷新页面
          </Button>
        }
      />
    ) : (
      this.props.children
    );
  }
}