Vite 的首屏性能为什么不好?

本文正在参加「金石计划 . 瓜分6万现金大奖」

Vite 给人一种又快又慢的感觉,快主要体现在 DevServer 的启动和热更新上,但随之带来的问题是,首屏性能不好以及页面加载时间长的问题。

那为什么 Vite 的首屏性能不是怎么好呢?

Vite 的运行流程

就像研究浏览器页面首屏性能,我们会先想到一个问题:从输入 url 到页面显示,这个过程发生了什么?

当我们研究 Vite 首屏性能,就不得不考虑一个问题:从输入 url 到页面显示,Vite 做了什么?

以下是 Vite3.x 的运行流程图:

image-20221121214041647

  1. 运行 Vite 命令,启动 DevServer,输出端口

  2. 访问页面,页面会访问静态资源和依赖

    • 请求静态资源时,Vite 会对资源进行转换,然后响应请求
    • 请求依赖时,需要等待依赖预构建完成后,DevServer 才会响应请求。
  3. 当页面资源全部加载并执行完成后,显示首屏。

可以粗略的得出一个结论:

  • 总时间 = Vite Server 启动时间 + 首屏时间

  • 首屏时间 = Max(所有静态资源请求处理时间, 依赖预构建时间)

Vite Server 启动时间

严格来说,Server 启动时间,不算在首屏性能中,不过也值得我们拿出来研究一下。

DevServer 启动前,只做了少量的操作:

  • 标准化用户传入的 config
  • 初始化 DevServer 对象
  • 热更新相关的逻辑

可以看出 Server 的启动时间,其实是不随项目的规模变大而显著增长的

注意这里说的是 Vite3.x

在 Vite2.x,Vite 会等待依赖预构建完成,才打印出 Server 端口,这时候才认为 Server 完全启动。

Vite3.x 则直接启动 Server,依赖预构建则异步执行。相当于将构建阶段往后挪了,这就会给人这样一种感觉:启动速度快了,但首屏反而更慢

静态资源处理时间

在浏览器打开页面,会请求 HTML 文件及其需要的静态资源文件

每当请求一个资源文件时,Vite 会对它们进行转换处理,例如:

  • 给 HTML 注入热更新脚本
  • 将 Vue 文件转换成 JS 代码,让浏览器能够正确运行
  • 将 less 文件,转换成 CSS

Vite 并没有在 Server 启动期间进行代码转换,而是在浏览器请求模块时进行编译转换,这就能做到用到哪个模块就编译哪个模块。这也是 Vite Server 启动快的原因,但这同时也会带来更长的首屏时间

image-20221121225206624

项目规模的变大,对首屏时间的影响?

单个页面需要转换的资源越多,静态资源的转换时间就越长

一般来说,项目规模增大,往往是新增了更多的页面,单个页面使用的模块往往不会随项目规模增长

如果是这种情况,其实对静态资源处理时间影响不会非常大,因为 Vite 只对使用到的模块进行转换,而其他页面的模块由于没有被使用到,因此也不会被转换。

如何减少获取静态资源的总时间?

  • 使用 HTTP2。因为浏览器对 HTTP 1 的请求,会有并发上限,达到上限的请求,需要排队。而 HTTP 2 有多路复用的特性,则不会用并发上限的问题
  • 使用缓存。Vite 其实已经内置了,第二次访问相同的资源时,Vite 会返回 304 状态码,使用浏览器缓存。但这个对首屏没有帮助,第一次仍然需要进行模块代码转换。如果需要提升首屏性能,则需要持久化缓存,但这个 Vite 只是有规划,但是仍然没有实现。

依赖预构建时间

依赖预构建的目的有两个:

  • CommonJS 和 UMD 兼容性
  • 提升性能

它的过程主要分为:

  • 依赖扫描/依赖发现阶段

    需要扫描所有项目文件,找到所有项目中使用到的依赖。更多细节可以查看《五千字深度解读 Vite 的依赖扫描》

  • 预构建阶段

    本质就是对项目中用到的依赖,提前进行构建打包,是一次使用 esbuild 的多入口构建,入口为所有项目依赖的入口脚本(各依赖 package.json 中定义的入口文件)。更多细节可以查看《快速理解 Vite 的依赖预构建》

image-20221128200521718

从预构建过程可以看出:

  • 依赖扫描的性能,与项目的文件数量有关,项目文件越多,需要扫描的文件就越多,时间就越长
  • 预构建的性能,与项目使用的依赖的数量有关,使用的模块越多,需要构建的模块就越多,时间就越长

因此,项目规模越大,首屏时间就会越慢。当然这也是所有打包工具都会遇到的问题。

Vite 会优先使用本地的预构建产物。只有第一次启动 Vite 的时候,才会进行依赖预构建,第二次启动则会使用上次构建好的依赖。除非项目使用到的依赖 、配置文件发生了变化 ,则需要重新进行预构建。

于是可以分为以下两种情况:

  • 首次执行 Vite,依赖预构建时间 > 所有静态资源请求处理时间。因此我们在开发者工具 Network 中,常常能看到,页面的所有静态资源请求都已经全部响应完成,但是部分依赖仍然在请求状态中。此时性能瓶颈在依赖预构建,直到依赖的请求全部响应,页面才展示。

  • 非首次启动 Vite 时,由于有缓存,不需要依赖预构建,这时候首屏的主要耗时在静态资源的文件转换

image-20221128201736465

总结

从整个 Vite 的运行流程可以看出,Vite 的启动速度,仅仅是把构建的过程,放到 DevServer 启动之后,构建的时间并没有减少,因此 Vite 也导致了 Vite 的首屏性能不好。

但 Vite 其实已经做了很多的努力了,使用了预构建缓存,运行时的模块转换的缓存,这一些列的措施,是我们在后续开发中的页面性能有了较大的提升。

如果这篇文章对您有所帮助,可以点赞加收藏👍,您的鼓励是我创作路上的最大的动力。也可以关注我的公众号订阅后续的文章:《五千字深度解读 Vite 的依赖扫描》

  • 《深度解读 Vite 的依赖扫描》

  • 更多内容可以查看我的专栏:《Vite 设计与实现》

    © 版权声明
    THE END
    喜欢就支持一下吧
    点赞6 分享
    评论 抢沙发
    头像
    欢迎您留下宝贵的见解!
    提交
    头像

    昵称

    取消
    昵称表情代码图片

      暂无评论内容