项目场景
最近着手的react项目上有个需求,关于版本更新后,因为项目是按需加载的,前端页面点击菜单时会去拿对应菜单名称+哈希值的js文件,然后页面就会报Error:Loading chunk 10 failed.
这一类的错误,会直接影响到正在使用平台的用户的体验。
原因分析
代码有更改后,webpack打包后的文件会生成新的哈希值后缀,但是项目入口页缓存的还是老哈希值,这就会导致已加载的项目依然会去加载老哈希值对应的文件,与此同时在服务器上,你的老哈希值文件已经被替换了,于是就会产生你拿不到对应文件的报错。
类似于这种
解决方案
react
react我尝试了很多种方案,有些方案更改成本会很大,这边大家可以避坑
1.譬如有个兄弟说的,定位到webpack中报错这一行的源码,在源码下添加window.location.reload()
即可,这种方式导致你需要fork一下你使用的webpack版本,自己修改源码后上传一个新的npm包,再修改一系列的依赖,确实可以实现重载需求,但是会踩很多坑(不推荐);
2. 想通过react自带的错误处理机制:v15版本使用unstable_handleError
,16版本使用EerrorBoundary
,
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
v15版本---后续版本已废弃
unstable_handleError(e) {
console.log(e)
}
v16版本---最新使用方法
componentDidCatch(error, info) {
// Display fallback UI
this.setState({ hasError: true });
// You can also log the error to an error reporting service
logErrorToMyService(error, info);
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
<ErrorBoundary>
<MyWidget />
</ErrorBoundary>
很抱歉的告诉你,这一类的方法都监听不到,因为人家是资源加载失败,压根还没有进入到组件内,所以这连个方法也是不可行;还有一些提出的在路由中去监听的,我也尝试过,同样的理由,均以失败告终;
3. 最后就是只能妥协,使用浏览器的监听模式,通过webpack源码我发现它使用的是promise的方式抛出异常,那我们就去捕获未处理的promise异常,在入口页index.html
使用unhandledrejection
,
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="keywords" content="xxxx" />
<meta name="description" content="xxxx" />
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport"
content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1,user-scalable=no,viewport-fit=cover" />
<link rel="shortcut icon" href="./favicon.ico">
<title>xxxx</title>
<!--[if lte IE 9]>
<script>location.href = 'https://www.baidu.com'</script>
<![endif]-->
</head>
<body>
<script>
//监听资源加载错误
window.addEventListener("unhandledrejection", function (e) {
if (e.reason.message.includes('Loading chunk')) window.location.reload();
}, true);
</script>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
</body>
</html>
可以看到主流浏览器都是支持的==
当检测到报错信息中含有Loading chunk
,就代表着资源请求上的错误,这时你已经检测到了版本的升级,你可以做个有好的弹窗处理,提醒他点击重新加载,也可以像我一样暴力重载,记得让运维配置一下前端入口页的缓存设置为不缓存,防止入口页index.html
也是缓存数据,类似于这样
server {
listen 80 default_server;
server_name xxx.xxx.com;
root /app/xxx/html/;
location ~ .*\.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm)$
{
expires 7d;
}
location ~ .*\.(?:js|css)$
{
expires 7d;
}
location ~ .*\.(?:htm|html)$
{
add_header Cache-Control "private, no-store, no-cache, must-revalidate, proxy-revalidate";
}
}
Vue
对于vue来说,在vue-router中有一个 router.onError(callback)
的api,这就简化了我们处理的方式
router.onError((error) => {
const pattern = /Loading chunk (\d)+ failed/g;
const isChunkLoadFailed = error.message.match(pattern);
const targetPath = router.history.pending.fullPath;
if (isChunkLoadFailed) {
router.replace(targetPath);
}
});
我们需要做的就是捕获错误,重新渲染页面。