vue生产环境报错 ChunkLoadError
这周在测试环境中监控到一个前端异常ChunkLoadError: Loading chunk * failed
出现的频率并不是很高,一开始也没有在意。
后来看到一个bug,在业务中点击下一步的时候,无法正确的跳转页面,且后续点击上一步也有问题
现场还原
第一时间看了业务代码后,发现这个业务的路由配置大概如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26export const routes = [
// ....
{
name:'业务办理',
path:'/service',
component: () => import('./service.vue')
children:[
{
name:'步骤1',
path:'/service/step1',
component: () => import('./service/step1.vue')
},
{
name:'步骤2',
path:'/service/step2',
component: () => import('./service/step2.vue')
},
{
name:'步骤3',
path:'/service/step3',
component: () => import('./service/step3.vue')
}
]
}
// ...
]
这时候我才意识到,是 Loading chunk * failed
这个错误导致的这个错误。
当我们点击下一步跳转路由的时候,如果懒加载这个路由文件失败,就会出现页面停留在当前页面的错误,但其实我们路由的hash
或者history
其实已经是下一个路由了。
寻找解决方法
百度一下
在搜索引擎一番搜索这个问题的解决方法后,看到很多阶段方案都是在
vue-router
中添加onError
监听:1
2
3
4
5
6
7
8
9
10
11
12
13// 在router.js 添加此段代码解决问题
// 解决Loading chunk (\d)+ failed问题
router.onError((error) => {
const pattern = /Loading chunk (\d)+ failed/g;
const isChunkLoadFailed = error.message.match(pattern);
if (isChunkLoadFailed) {
// 用路由的replace方法,并没有相当于F5刷新页面,失败的js文件并没有从新请求,会导致一直尝试replace页面导致死循环,而用 location.reload 方法,相当于触发F5刷新页面,虽然用户体验上来说会有刷新加载察觉,但不会导致页面卡死及死循环,从而曲线救国解决该问题
location.reload();
// const targetPath = $router.history.pending.fullPath;
// $router.replace(targetPath);
}
});
export default router;这个解决方法对于我们的场景是很契合,我们的业务是一个
流程
式的场景,如果在失败后暴力的刷新页面,对用户的体验是机器糟糕的。分析下我们的代码
既然刷新页面这个场景不能满足我们的场景,那从我们的场景出发,就会发现,如果我们在这个业务场景下所有的路由是在一个
chunk
下,那是不是就不会出现办理中资源加载失败的问题啦?
这个其实很好弄,直接加上/* webpackChunkName: "service" */
就行啦1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26export const routes = [
// ....
{
name:'业务办理',
path:'/service',
component: () => import(/* webpackChunkName: "service" */ './service.vue')
children:[
{
name:'步骤1',
path:'/service/step1',
component: () => import(/* webpackChunkName: "service" */ './service/step1.vue')
},
{
name:'步骤2',
path:'/service/step2',
component: () => import(/* webpackChunkName: "service" */ './service/step2.vue')
},
{
name:'步骤3',
path:'/service/step3',
component: () => import(/* webpackChunkName: "service" */ './service/step3.vue')
}
]
}
// ...
]本地打包一下,发现确实多了一个名为
service
的chunk
Ok , 上测试 , 问题修复新问题
上完测试后,确实在流程中,不会出现这个问题了,但是后台监控发现,出现了
Loading chunk service failed
这就尴尬了,说明我们只是解决了这个bug,并没有从根本上解决这个问题。
好的,那就先仔细分析下可能出现这个问题的原因吧:
网络异常
不考虑代码部署、环境和极限临界情况下,如果网络出现异常,那必然会出现Loading chunk * failed
html缓存
如果用户访问的版本是0.0.1
的版本的应用,此时我们的应用版本是0.0.2
,如果我们在部署新版本0.0.2
的时候,删除或者覆盖了0.0.1
的静态资源,那这个时候0.0.1
版本就加载不到对应的资源了。
如何解决
- 失败后
重试
在搜索引擎搜索这个问题后,很快就发现,有一个webpack pluginwebpack-retry-chunk-load-plugin
可以帮我们解决问题
根据文档配置,配置后就可以实现失败后重试
啦 - 禁止app入口文件
index.html
等html使用缓存
这个也简单,直接在html
中加入缓存控制1
2
3
4<!-- HTTP 1.1 -->
<meta http-equiv="pragma" content="no-cache">
<!-- HTTP 1.0 -->
<meta http-equiv="cache-control" content="no-cache"> - 确保不同版本的app的资源唯一,且可以并存在服务器上
这一点其实就是两个问题,- 确保不同版本的app的资源唯一
webpack在打包的时候可以在chunk
后面添加上文件的hash
,通过这个可以实现这个问题 - 并存在服务器上
这一点其实只要我们在部署的时候不删除原有文件,只是添加新文件到服务器上就可以啦
- 确保不同版本的app的资源唯一
总结思路
对于这个问题,我们可以整理出如下顺序的解决步骤
html
禁止缓存- 指定懒加载组件的
webpackChunkName
,合理合并chunk
- 使用
webpack-retry-chunk-load-plugin
,在失败后重试几次
额外思考
- 异步加载路由的时候,在
vue-router
异步加载过程中,如果加上一个loading
的交互,是不是可以提升用户体检? - 我们如何判断这个
chunk
是因为网络情况加载失败,还是请求404
,这其实是两个问题?