notes icon indicating copy to clipboard operation
notes copied to clipboard

vue路由懒加载import()会造成性能影响吗?

Open lanlin opened this issue 6 years ago • 0 comments

背景

通常我们都会用 ES6的方式来导入和导出模块,而其中有个叫 import() 的方法,是在运行时动态加载模块用的。

vue 中建议在路由配置中用此方法来进行代码拆分,达到根据路由变化,动态加载依赖组件的目的。这种方式被官方成为 vue路由懒加载

以上看起来并没有什么问题。 但是,如果你的项目足够复杂,路由层级肯定也很复杂。 相应的,需要动态加载的组件会非常之多。 这个时候你会惊奇的发现,天,怎么暴增了那么多资源请求。 全是各种以 chunk 命名的 js,这个数量可能会多达上千个。

image

原因分析

通过分析,发现这些请求均来自打包之后的入口文件 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 的兼容性,绿色表示兼容,红色表示不兼容。 柱状图高度表示浏览器市场份额。结果如下 image

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

可以看到,如果以浏览器品牌来看, preload 的兼容性比较差,而 prefetch 的兼容性要好一些。 当然,如果仅从市场份额来看,目前(2019/03/28)82.14% 的浏览器都支持 prefetch,而 preload 的支持率也达到了 80.45%

性能影响的猜测

浏览器不是会在空闲时间加载这些标记为预加载的资源嘛。 那这个所谓的空闲时间,到底是怎么定义的呢?我觉得这个就很值得思考了。

如果说,某些预加载已经在进行中。 但是临时插入的 ajax 请求明显处于更高的优先级,应该需要马上处理。 这个时候,可能就需要暂停某些 prefetch,先处理优先级更高的事情。 然后,等待再次空闲时,再继续处理预加载。

设想, 如果以上过程,反反复复的发生呢。自然,对于当前页面的加载性能影响就会越来越大。 比如,往往首页的复杂度都比较高,但是预加载又恰恰发生在首页加载过程中。。。

实际测试验证

image 关闭 prefetch 前,总计1217个请求,加载耗时5.88秒。

image 关闭 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}`
);

详见 Webpack Module Methods

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

其他

欢迎内行的童鞋,提供更翔实和精确的测试数据。 本人对浏览器性能没什么研究,所以测试不具备太大的科学性,欢迎拍砖。

lanlin avatar Mar 28 '19 04:03 lanlin