之前一直有个问题没有解决,就是测试突然就发现页面点不动了,点击什么都没反应。通过排查发现是重新部署前端包之后,测试点击路由跳转找不到新的 js 文件导致的。一直没有解决这个问题。最近项目忙的差不多了,通过测试发现这种情况下页面会有一个 ChunkLoadError 的异常,应该是 webpack 自定义的异常。控制台报错如下:
可以看到有 Uncaught(in promise) 这样的错误。这样的话,就可以对全局 window 添加一个异常捕获,来实现对用户的提示。让用户知道是项目更新导致的,然后自动强制刷新当前页面,用户就可以使用最新版的前端包了。大概思路如下:
window.addEventListener('unhandledrejection', evt => {
if (evt?.reason?.name === 'ChunkLoadError') { // 发现找不到新的 js 文件
const alertContent = (
<Fragment>
<p>
<Icon className="mr-10" id={alertError} size={16} fill={errorColor} />
平台可能已更新,关闭提示将自动刷新页面使用最新版
</p>
<p>如刷新失败请手动刷新</p>
</Fragment>
);
const config = {
width: 400,
handleCancel: () => window.location.reload(),
handleEnsure: () => window.location.reload()
};
Modal.alert(alertContent, config); // 弹框提示用户
}
});
注意,这里的 uncaught(in promise) 的错误就是 promise 没有添加 catch 冒泡上来的错误,这种错误是通过 unhandledrejection 来捕获。
最终效果如下图
后续
最近把项目中的 react-router 升级了一下,从 3.x 升级到了 5.x,然后发现上面的这个提示没有了。看了下控制台,发现上面的 uncaught(in promise) 的错误没有了,只剩下了一个错误。如下图
貌似是因为升级 react-router 之后,把原来 webpack 中通过 require.ensure 的方式动态获取组件的方式替换成了 React.lazy(import()) 导致的。然后这个错误可能不会冒泡抛出了,所以上面的捕获失效了。导致这个小功能也就失效了。
查看 webpack 的文档,文档中说的也是 require.ensure 是依赖 promise 的,所以应该是这个原因。
// 原来的动态引入方式
import {injectReducer} from '../../store/reducers';
export default store => ({
path: 'manage-intel',
/* Async getComponent is only invoked when route matches */
getComponent(nextState, cb) {
/* Webpack - use 'require.ensure' to create a split point
and embed an async module loader (jsonp) when bundling */
require.ensure(
[],
require => {
/* Webpack - use require callback to define
dependencies for bundling */
const component = require('./components').default;
const reducer = require('./reducers').default;
/* Add the reducer to the store on key 'counter' */
injectReducer(store, {key: 'manageIntelData', reducer});
/* Return getComponent */
cb(null, component);
/* Webpack named bundle */
},
'manageIntel'
);
}
});
// 现在的动态引入方式
import React, {lazy, Suspense} from 'react';
import PropTypes from 'prop-types';
import {Loading} from 'tdp-ui';
import {injectReducer} from '../../store/reducers';
import reducer from './reducers/index';
const ManageIntel = lazy(() => import(/* webpackChunkName: "manageIntel" */ './components'));
export default function ManageIntelPage({store, title}) {
injectReducer(store, {key: 'manageIntelData', reducer});
return (
<Suspense fallback={<Loading />}>
<ManageIntel store={store} title={title} />
</Suspense>
);
}
ManageIntelPage.propTypes = {
store: PropTypes.object,
title: PropTypes.oneOfType([PropTypes.array, PropTypes.string])
};
查了一些文章,有些是通过 window.addEventListener('error', fn) 来进行处理的,但是我这里也捕获不到。所以只能通过 ErrorBoundary 来进行提示了。当然也可以在 import() 后面添加 catch 来进行捕获,但是这样的话,每一个 import 都要进行添加,感觉有点费劲,所以目前还是决定用 ErrorBoundary 来进行添加。
后续的后续
升级 react-router 之后,把动态引用的方式也顺便改了一下,从 require.ensure 改成了 React.lazy 的方式,因为 require.ensure 使用的是 promise,所以会报 promise 的错误。然后通过捕获 promise 的错误可以实现最开始想要的效果。现在的错误是直接显示 error,通过 window.addEventListener('error', callback) 发现捕获不到,原因是在最外层的 container 加了 errorDec,导致错误无法上升到 container,所以捕获不到。然后把原来在 layout 中的异常捕获提升到了最外层的 container 组件中。通过把对 promise 的异常捕获换成 error 捕获就可以了。
新的错误:
捕获错误效果和最后的展示效果如下图
代码如下:
// src/container/AppContainer.js
// Uncaught ChunkLoadError 对应 js 加载错误,也就是对应的 js 发生了更新
// Loading CSS chunk .* failed 对应 css 加载错误,也就是对应的 css 发生了更新
componentDidMount() {
window.addEventListener('error', evt => {
if (/Uncaught ChunkLoadError|Loading CSS chunk .* failed/.test(evt?.message)) {
Modal.alert(alertContent, alertConfig);
}
});
}
参考
rollbar.com/blog/javasc… medium.com/@botfather/… gist.github.com/raphael-leg… raphael-leger.medium.com/react-webpa…