vue路由懒加载import()会造成性能影响吗?
背景
通常我们都会用 ES6的方式来导入和导出模块,而其中有个叫 import() 的方法,是在运行时动态加载模块用的。
vue 中建议在路由配置中用此方法来进行代码拆分,达到根据路由变化,动态加载依赖组件的目的。这种方式被官方成为 vue路由懒加载
以上看起来并没有什么问题。 但是,如果你的项目足够复杂,路由层级肯定也很复杂。 相应的,需要动态加载的组件会非常之多。 这个时候你会惊奇的发现,天,怎么暴增了那么多资源请求。 全是各种以 chunk 命名的 js,这个数量可能会多达上千个。

原因分析
通过分析,发现这些请求均来自打包之后的入口文件 index.html 在其 head 标签中,包含了大量的如下形式的 link 标签。
<link href=/js/chunk-0006203a.9e52c3e3.js rel=prefetch>
<link href=/js/chunk-0006203a.9e52c3e4.js rel=preload>
...
与集中出现的大量请求刚好对应上了。
找 google 大神请求了下终于知道是怎么回事了。
原来从 webpack 4.6.0 开始加入了 <link rel="prefetch"> 和 <link rel="prefetch"> 的支持。
这两个东西都是用来预加载的,浏览器会在空闲时间段内对这些 link 资源进行加载和缓存。 这样可以实现页面切换时的快速响应,打的就是一个时间差。
而这些 link 的来源,就是 vue 组件编写时各个地方 import() 调用时所拆分出来的。
在编译打包之后,被一次性的插入到了 index.html 入口页面中。
关于 prefetch 与 reload
主流浏览器对 prefetch 的兼容性,绿色表示兼容,红色表示不兼容。
柱状图高度表示浏览器市场份额。结果如下

主流浏览器对 preload 的兼容性,绿色表示兼容,红色表示不兼容。
柱状图高度表示浏览器市场份额。结果如下

可以看到,如果以浏览器品牌来看, preload 的兼容性比较差,而 prefetch 的兼容性要好一些。 当然,如果仅从市场份额来看,目前(2019/03/28)82.14% 的浏览器都支持 prefetch,而 preload 的支持率也达到了 80.45%
性能影响的猜测
浏览器不是会在空闲时间加载这些标记为预加载的资源嘛。 那这个所谓的空闲时间,到底是怎么定义的呢?我觉得这个就很值得思考了。
如果说,某些预加载已经在进行中。 但是临时插入的 ajax 请求明显处于更高的优先级,应该需要马上处理。 这个时候,可能就需要暂停某些 prefetch,先处理优先级更高的事情。 然后,等待再次空闲时,再继续处理预加载。
设想, 如果以上过程,反反复复的发生呢。自然,对于当前页面的加载性能影响就会越来越大。 比如,往往首页的复杂度都比较高,但是预加载又恰恰发生在首页加载过程中。。。
实际测试验证
关闭 prefetch 前,总计1217个请求,加载耗时5.88秒。
关闭 prefetch 后,请求减少到 114个,加载耗时2.33秒。
如图,就我个人的粗糙测试来说,关闭所有 prefetch 之后。 减少1000个左右的预加载,平均加载时间缩短了3~4秒左右。 当然,prefetch 会影响 Onload Event,所以上面的数据并不能说明问题。
那么对于 DOMContentLoaded (DCL), First Contentful Paint (FCP), First Meaningful Paint (FMP) 这些阶段状态的影响呢?
| 状态 | 开启状态下 | 关闭状态下 | 差值 |
|---|---|---|---|
| DCL | 1944.46ms | 1289.78ms | 654.68ms |
| FCP | 2671.70ms | 1575.36ms | 1096.34ms |
| FMP | 3652.74ms | 2185.48ms | 1467.26ms |
| 请求 | 1217个/次 | 114个/次 | 1103个/次 |
根据多次粗略测试求得的平均值,得出以上数据。可以看出,确实对于性能还是有一定影响的。 这个是本地开发环境所测得的数据,估计线上环境由于网络原因的影响,这个差值有可能会放大。
本人对此也十分疑惑,希望有其他童鞋能提供相应的数据。 看看你们是否有相同的情况。对此,我不敢贸然下结论,但是还是会有这方面的顾虑。
如何关闭 prefetch 与 preload
1. 在 webpack 的文档中有详细的说明,具体来说就是在使用 import() 方法的时候,可以通过注释来控制其行为。如下所示:
// Single target
import(
/* webpackChunkName: "my-chunk-name" */
/* webpackMode: "lazy" */
'module'
);
// Multiple possible targets
import(
/* webpackInclude: /\.json$/ */
/* webpackExclude: /\.noimport\.json$/ */
/* webpackChunkName: "my-chunk-name" */
/* webpackMode: "lazy" */
/* webpackPrefetch: true */
/* webpackPreload: true */
`./locale/${language}`
);
2. 在 vue 项目中,可以通过配置来关闭 prefetch。在 vue.config.js 中,加入以下配置:
module.exports = {
// ... 你的其他配置,注意 chainWebpack 只能放到配置的第一层中
chainWebpack: (config) => {
// A. 直接干掉插件 prefetch
config.plugins.delete('prefetch');
// B. 正则白名单进行过滤, 符合白名单的才会预处理 (可能无效,请自行实验)
// config.plugin('prefetch').tap(options => {
// options.fileWhitelist = [/Prefetch\.[a-zA-Z]+$/];
// return options;
// });
},
};
一些参考
GoogleChromeLabs/preload-webpack-plugin
Lazy Loading in Vue using Webpack's Code Splitting
link rel=”prefetch/preload” in webpack
其他
欢迎内行的童鞋,提供更翔实和精确的测试数据。 本人对浏览器性能没什么研究,所以测试不具备太大的科学性,欢迎拍砖。