编程

Vite 4.3 正式发布

971 2023-04-22 20:16:00

2023年4月20号Vite 官方团队宣布 Vite 4.3 正式发布。距离上一次发布 Vite 4.2 版本(2023年03月16号),相隔35天。可见 Vite 研发团队成员多么给力,小编也是一路看着Vite 小不点,一步一步茁壮成长的,发展到现在真心不容易。Vite 做到可以跟 React 老大哥扳手腕了。详细请见这篇 :React团队建议:Create React App 替换成 Vite。该文章当时在react 社区轰动圈内人士。Vite 现在的成就和地位,那当然也离不开 Vite 研发团队每一位成员都付出,另外也离不开尤雨溪 的全方位指导。

在这个Vite 4.3 版本中,Vite 团队专注于改进 devServer 的性能。简化了解析逻辑,优化了热路径,并对查找 package.json、TS 配置文件和一般解析 URL 实现了更智能的缓存。与 Vite 4.2 相比,速度得到了全面提升!

而且在当时Best of JS正式公布 前端构建工具中排名榜首。

全文大纲

  1. Vite 介绍
  2. Vite 4.3 性能测试概述
  3. Vite 4.3 测试结果
  4. Vite 4.3 性能提升总结
  5. 为什么 Vite 4.3 这么快?
  6. 下一步计划

Vite 介绍

官网:https://vitejs.dev/

Github:https://github.com/vitejs/vite

借用官方的原话:vite是下一代前端开发与构建工具。 是一种新型前端构建工具,能够显著提升前端开发体验。它主要由两部分组成:

  1. dev server:利用浏览器的ESM能力来提供源文件,具有丰富的内置功能并具有高效的HMR
  2. 生产构建:生产环境利用Rollup来构建代码,提供指令用来优化构建过程

Vite作为一个基于浏览器原生ESM的构建工具,它省略了开发环境的打包过程,利用浏览器去解析imports,在服务端按需编译返回。同时,在开发环境拥有速度快到惊人的模块热更新,且热更新的速度不会随着模块增多而变慢。因此,使用Vite进行开发,至少会比Webpack快10倍左右。

Vite的主要特性

  • Instant Server Start —— 即时服务启动
  • Lightning Fast HMR —— 闪电般快速的热更新
  • Rich Features —— 丰富的功能
  • Optimized Build —— 经过优化的构建
  • Universal Plugin Interface —— 通用的Plugin接口
  • Fully Typed APIs —— 类型齐全的API

Vite 4.3 性能测试概述

最近,Vite 团队核心成员“”在社交平台表示,他在 Windows 机器上对 Vite 4.3 进行了基准测试,其初始加载速度有了大幅提升!其中,Vite 4.3 的冷启动时间快了 3 倍,热启动时间也快了 1.3 倍。另外,在使用 SWC 插件时,Vite 4.3 的冷启动时间比 turbopack 快 1.5 秒。

在开始之前,先来介绍三个相关概念:

  • 启动时间:从“执行命令”到“在浏览器中触发 load 事件”的时间。
  • 根 HMR 时间:从“根文件被更改”到“该文件在浏览器中执行”的时间。
  • 叶子 HMR 时间:从“叶子文件被改变”到“该文件在浏览器中执行”的时间。

接下来就看看 Vite 4.3 相较于上一个版本在速度上都有哪些提升!

测试概述

最终得到的结论如下:

  • Vite(plugin-react) 的启动时间只比 turbopack 慢 0.2s(冷启动)
  • Vite(plugin-react) 的启动时间比 turbopack 快 0.2s(热启动)
  • Vite(plugin-react-swc) 的启动时间比 turbopack 快 1.5s(冷启动)
  • Vite(plugin-react-swc) 的启动时间比 turbopack 快 1.8s(热启动)
  • Vite 的叶 HMR 时间比 turbopack 快
  • Vite 的根 HMR 时间比 turbopack 慢 5-10ms
  • rspack 的启动时间是 Vite 的一半,但 HMR 的启动时间是 Vite 的 10 倍

进行本测试的机器规格和其他信息

  • 运行时:node.js 18.15.0
  • 中央处理器:锐龙 9 5900X
  • 内存:DDR4-3600 32GB
  • 固态硬盘:WD Black SN750 NVME SSD

Vite 4.3 测试结果

对 Vite 4.3 和 Vite 4.2.1 的启动时间分别进行测试:

  • Vite 4.2.1 + plugin-react 3.1.0 / plugin-react-swc 3.2.0
  • Vite 4.3.0 + plugin-react 4.0.0-beta.0 / plugin-react-swc main

根据上表中的数据得到图表如下所示:

  • 启动时间
  • 热更新时间

除此之外,测试前后的 FCP(First Contentful Paint,即首次有内容渲染的时间) 对比如下:

  • Vite 4.2.1 + plugin-react 3.1.0
    • FCP 最大值:70334
    • FCP 最小值:33901
    • FCP 平均值:38031
  • Vite 4.3.0+ plugin-react 4.0.0-beta.0
    • FCP最大值:3348
    • FCP 最低:2970
    • FCP 平均值:3083

可以看到,Vite 4.3 比 Vite 4.2.1 的 FCP 平均快了 12 倍!

半年前,Vercel 推出了下一代打包工具:Turbopack,并宣称其比 Vite 快 10 倍。如今,Vite 在某些方面已经超越了 Turbopack,期待未来 Vite 会有更好的表现!

Vite 4.3 性能提升总结

以下是性能改进的具体数据,由 sapphi-red/performance-compare 测试得出,该测试会以 1000 个 React 组件测试应用的冷启动和热启动时间以及根节点和叶节点组件的 HMR 时间:

冷启动和热启动时间报表

启动时间 vite 4.2.1 对比 vite 4.3.0

热更新时间 vite 4.2.1 对比 vite 4.3.0

此性能运行的规格和版本:

  • CPU:Ryzen 9 5900X,内存:DDR4-3600 32GB,SSD:WD Blue SN550 NVME SSD
  • Windows 10 专业版 21H2 19044.2846
  • Node.js 18.16.0
  • Vite 和 React 插件版本
    • Vite 4.2 (babel): Vite 4.2.1 + plugin-react 3.1.0
    • Vite 4.3 (babel): Vite 4.3.0 + plugin-react 4.0.0-beta.1
    • Vite 4.2 (swc): Vite 4.2.1 + plugin-react-swc 3.2.0
    • Vite 4.3 (swc): Vite 4.3.0 + plugin-react-swc 3.3.0

Vite 团队将继续致力于提升 Vite 的性能,正在为 Vite 开发一个官方基准测试工具,以获得每个 Pull Request 的性能指标。vite-plugin-inspect 现在有更多与性能相关的功能,可以帮助开发者确定哪些插件或中间件是应用性能的瓶颈。页面加载后使用 vite --profile(然后按 p)将保存 devServer 启动的 CPU 配置文件。可以在应用中将它们作为 speedscope 打开以识别性能问题。

为什么 Vite 4.3 这么快?

更智能的解析策略

Vite会将所有接收到的URL和路径解析为目标模块。在 Vite 4.2 中,存在很多冗余的解析逻辑和不必要的模块搜索。为了减少计算和文件系统调用,Vite 4.3 使解析逻辑更简单、更严格和更准确。

更简单的解析

Vite 4.2严重依赖 resolve 包来解析依赖的 package.json,查看 resolve 的源码发现解析 package.json 时有很多无用的逻辑。Vite 4.3 摒弃了 resolve,遵循更简单的 resolve 逻辑:直接检查嵌套父目录中是否存在 package.json

更严格的解析

Vite 必须调用 Nodejs fs API 来查找模块。但是 IO 很昂贵。Vite 4.3 缩小了文件搜索范围,并跳过搜索一些特殊路径,以尽可能减少 fs 调用。例如:

  1. 由于 # 符号不会出现在 URL 中,用户可以控制源文件路径中没有 # 符号,因此 Vite 4.3 不再检查用户源文件中带有 # 符号的路径,而是仅在 node_modules 中搜索它们。
  2. 在Unix系统中,Vite 4.2 会先检查根目录下的每一个绝对路径,对大多数路径都可以,但是如果绝对路径以根开头就很容易失败。为了在 /root/root 不存在的情况下跳过搜索 /root/root/path-to-file,Vite 4.3 会在开头判断 /root/root 作为目录是否存在,并预先缓存结果。
  3. 当 Vite 服务器收到 @fs/xxx 和 @vite/xxx 时,就不需要再解析这些 URL。Vite 4.3 直接返回之前缓存的结果,不再重新解析。

更准确的解析

Vite 4.2 在文件路径为目录时递归解析模块,会导致不必要的重复计算。Vite 4.3 将递归解析扁平化,并对不同类型的路径应用适当的解析,展平后缓存一些 fs 调用也更容易。

包解析

  • Vite 4.3 打破了解析 node_modules 包数据的性能瓶颈。Vite 4.2 使用绝对文件路径作为包数据缓存键。这还不够,因为 Vite 必须遍历 pkg/foo/bar 和 pkg/foo/baz 中的同一个目录。
  • Vite 4.3 不仅使用了绝对路径(/root/node_modules/pkg/foo/bar.js & /root/node_modules/pkg/foo/baz.js),还使用了遍历目录(/root/node_modules/pkg/foo & /root/node_modules/pkg) 作为 pkg 缓存的键。
  • 另一种情况是,Vite 4.2 在单个函数中查找深层导入路径的 package.json,例如 Vite 4.2 解析 a/b/c/d 等文件路径时,首先检查根 a/package.json 是否存在, 如果没有,则按照a/b/c/package.json -> a/b/package.json的顺序查找最近的package.json,但事实是查找根package.json和最近的package.json应该分开处理 ,因为在不同的解析上下文中需要它们。Vite 4.3 将根 package.json 和最近的 package.json 解析分成两部分,这样它们就不会混在一起。

fs.realpathSync 问题

Nodejs 中有一个有趣的 realpathSync 问题,它指出 fs.realpathSync 比 fs.realpathSync.native 慢 70 倍。但 Vite 4.2 仅在非 Windows 系统上使用 fs.realpathSync.native,因为它在 Windows 上的行为不同。为了解决这个问题,Vite 4.3 在 Windows 上调用 fs.realpathSync.native 时添加了网络驱动器验证。

非阻塞任务

作为一个按需服务,Vite dev server 可以在没有准备好所有东西的情况下启动。

非阻塞 tsconfig 解析

Vite 服务器在预绑定 ts 或 tsx 时需要 tsconfig 数据。

Vite 4.2 在服务端启动之前,在插件钩子 configResolved 中等待 tsconfig 数据解析完成。一旦服务器启动而没有准备好 tsconfig 数据,页面请求就可以访问服务器,即使请求可能需要稍后等待 tsconfig 解析。

Vite 4.3 会在服务器启动前初始化 tsconfig 解析,但服务器不会等待。解析过程在后台运行。一旦有ts相关的请求进来,就得等tsconfig解析完了。

非阻塞文件处理

Vite 中有大量的 fs 调用,其中一些是同步的。这些同步 fs 调用可能会阻塞主线程。Vite 4.3 将它们改为异步。此外,并行化异步函数也更容易。关于异步函数,可能有许多 Promise 对象在解析后要释放。由于更智能的解析策略,释放 fs-Promise 对象的成本要低得多。

HMR 防抖

考虑两个简单的依赖链 C <- B <- A & D <- B <- A,当 A 被编辑时,HMR 将从 A 传播到 C 和 A 传播到 D。这导致 A 和 B 在 Vite 4.2 中被更新两次。

Vite 4.3 缓存了这些遍历的模块,以避免多次搜索它们,它适用于由 git checkout 触发的 HMR。

并行化

并行化始终是获得更好性能的好选择。在 Vite 4.3 中,我们并行化了一些核心功能,包括导入分析、提取 deps 的导出、解析模块 url 和运行批量优化器。

Javascript 优化

用回调替换 *yield

Vite 使用 tsconfck 来查找和解析 tsconfig 文件。tsconfck 曾经通过 *yield 遍历目标目录,生成器的一个缺点是它需要更多的内存空间来存储它的生成器对象,并且在运行时会有大量的生成器上下文切换。所以从 v2.1.1 开始在核心中用回调替换 *yield。

使用 === 代替 startsWith 和 endsWith

Vite 4.2 使用 startsWith 和 endsWith 检查热更新 URL 中的头部和尾部 '/'。比较 str.startsWith('x') 和 str[0] === 'x' 的执行基准发现,=== 比 startsWith 快约 20%,endsWith 比 === 慢约 60%。

避免重复创建正则表达式

Vite 需要很多正则表达式来匹配字符串,其中大部分都是静态的,因此只使用它们的单例会更好。Vite 4.3 将正则表达式提升,以便可以重复使用它们。

放弃生成自定义错误

在 Vite 4.2 中,有一些自定义错误以改进开发体验。这些错误可能会导致额外的计算和垃圾回收,从而降低 Vite 的速度。在 Vite 4.3 中,放弃了生成某些热更新自定义错误(例如 package.json NOT_FOUND 错误),直接抛出原始错误以获得更好的性能。

下一步计划

接下来,Vite 团队决定今年做一个 Vite 主版本,以配合 9 月 Node.js 16 的 EOL,放弃对 Node.js 14 和 16 的支持。另外 Node.js 20 正式发布 也讲放弃对 Node.js 14 版本进行更新维护了,所以建议node.js 升级最新版。