跳至内容

为何选择 Vite

问题

在浏览器原生支持 ES 模块之前,开发者们并没有一个原生的机制来模块化地编写 JavaScript 代码。这就是为什么我们都熟悉“打包”的概念:使用工具来抓取、处理、拼接我们的源码模块到可以在浏览器中运行的文件里。

随着时间的推移,我们已经看到了像 webpackRollupParcel 这样的工具,它们极大地改善了前端开发者的开发体验。

然而,随着我们构建的应用程序越来越雄心勃勃,我们处理的 JavaScript 代码量也在急剧增加。对于大型项目来说,包含数千个模块是很常见的。我们开始遇到基于 JavaScript 的工具的性能瓶颈:启动一个开发服务器通常需要不合理的长时间等待(有时甚至长达几分钟!),即使使用热模块替换(HMR),文件编辑也可能需要几秒钟才能在浏览器中反映出来。缓慢的反馈循环会极大地影响开发人员的生产力和幸福感。

Vite 旨在通过利用生态系统中的新进展来解决这些问题:浏览器中原生 ES 模块的可用性,以及用编译到原生语言编写的 JavaScript 工具的兴起。

缓慢的服务启动

当冷启动开发服务器时,基于打包器的构建设置必须急切地抓取和构建你的整个应用程序,然后才能提供服务。

Vite 通过首先将应用程序中的模块分为两类来提高开发服务器的启动时间:依赖源码

  • 依赖主要是纯 JavaScript,在开发过程中不会经常更改。一些大型依赖项(例如,具有数百个模块的组件库)的处理成本也很高。依赖项也可以以各种模块格式(例如,ESM 或 CommonJS)提供。

    Vite 使用 esbuild 预构建依赖。 esbuild是用 Go 编写的,预构建依赖的速度比基于 JavaScript 的打包器快 10-100 倍。

  • 源码通常包含需要转换的非纯 JavaScript(例如,JSX、CSS 或 Vue/Svelte 组件),并且会经常被编辑。此外,并非所有源码都需要同时加载(例如,使用基于路由的代码拆分)。

    Vite 通过 原生 ESM 提供源码。这实际上是让浏览器接管打包器的一部分工作:Vite 只需要按需转换和提供源码,因为浏览器会请求它。条件动态导入后面的代码只有在当前屏幕上实际使用时才会被处理。

Bundle based dev server entry ··· route route module module module module ··· Bundle Server ready
Native ESM based dev server entry ··· route route module module module module ··· Server ready Dynamic import (code split point) HTTP request

缓慢的更新

当在基于打包器的构建设置中编辑文件时,重建整个包是低效的,原因很明显:更新速度会随着应用程序的大小线性下降。

在某些打包器中,开发服务器在内存中运行打包,因此当文件更改时,它只需要使模块图的一部分无效,但它仍然需要重新构建整个包并重新加载网页。重新构建包的成本可能很高,重新加载页面会清除应用程序的当前状态。这就是为什么一些打包器支持热模块替换(HMR):允许模块“热替换”自身,而不会影响页面的其余部分。这大大提高了 DX - 然而,在实践中,我们发现即使 HMR 更新速度也会随着应用程序大小的增长而显着恶化。

在 Vite 中,HMR 是通过原生 ESM 执行的。当编辑文件时,Vite 只需要精确地使编辑的模块及其最近的 HMR 边界之间的链无效(大多数情况下只有模块本身),从而使 HMR 更新始终快速,而不管你的应用程序的大小。

Vite 还利用 HTTP 标头来加速整页重新加载(再次,让浏览器为我们做更多的工作):源码模块请求通过 304 Not Modified 进行条件化,依赖模块请求通过 Cache-Control: max-age=31536000,immutable 强缓存,因此它们在缓存后不会再次访问服务器。

一旦你体验到 Vite 的速度,我们非常怀疑你是否愿意再次忍受打包开发。

为什么要在生产环境中打包

即使现在原生 ESM 得到了广泛的支持,但在生产环境中提供未打包的 ESM 仍然是低效的(即使使用 HTTP/2),因为嵌套导入会导致额外的网络往返。为了在生产环境中获得最佳的加载性能,最好还是使用 tree-shaking、懒加载和公共块拆分(为了更好的缓存)来打包你的代码。

确保开发服务器和生产构建之间的最佳输出和行为一致性并非易事。这就是为什么 Vite 提供了一个预配置的 build 命令,该命令内置了许多开箱即用的 性能优化

为什么不使用 esbuild 打包?

虽然 Vite 利用 esbuild 在 开发中预构建一些依赖,但 Vite 不使用 esbuild 作为生产构建的打包器。

Vite 当前的插件 API 与使用 esbuild 作为打包器不兼容。尽管 esbuild 速度更快,但 Vite 对 Rollup 灵活的插件 API 和基础设施的采用极大地促进了其在生态系统中的成功。目前,我们认为 Rollup 提供了更好的性能与灵活性之间的权衡。

Rollup 也在努力提高性能,在 v4 中将其解析器切换到 SWC。并且正在努力构建一个 Rollup 的 Rust 端口,名为 Rolldown。一旦 Rolldown 准备就绪,它可能会取代 Vite 中的 Rollup 和 esbuild,从而显着提高构建性能并消除开发和构建之间的不一致。你可以观看 Evan You 的 ViteConf 2023 主题演讲了解更多详情

Vite 与其他未打包的构建工具有何关系?

Preact 团队的 WMR 旨在提供类似的功能集。Vite 用于开发和构建的通用 Rollup 插件 API 受其启发。WMR 不再维护。Preact 团队现在推荐将 Vite 与 @preactjs/preset-vite 一起使用。

Snowpack 也是一个无打包的原生 ESM 开发服务器,在范围上与 Vite 非常相似。Vite 的依赖预构建也受到 Snowpack v1(现在是 esinstall)的启发。Snowpack 不再维护。Snowpack 团队现在正在开发 Astro,这是一个由 Vite 驱动的静态站点构建器。

@web/dev-server (以前是 es-dev-server)是一个很棒的项目,Vite 1.0 基于 Koa 的服务器设置受到了它的启发。@web 总括项目得到了积极维护,并且包含许多其他优秀的工具,这些工具也可能使 Vite 用户受益。

在 MIT 许可下发布。(083ff36d)