Vite 是什么? 为什么要用 Vite? 它解决了哪些问题? 又是如何解决?
2023年11月19日
什么是 Vite?
Vite 是近几年前端业界热门的建构工具 (build tool),它大幅地简化了前端建构的流程与时间。Vite 的核心功能有几点:
- 作为开发服务器 (dev server):让开发者可以在本地 (localhost) 进行开发。Vite 的热模组更新 (hot module replacement) 提供开发者非常好的开发体验。以开发 React 来说,当有任何的改动后,Vite 的热模块更新会以非常快的速度重新渲染本地的页面,同时会保留当下的任何状态 (state)。
- 打包代码:Vite 背后是透过 Rollup 进行打包,高度优化部署到生产环境 (production) 的打包结果。
目前前端社群中的绝大部分框架,都采用了 Vite,包含 Astro, Nuxt, SvelteKit, Solid Start, Qwik City 都是;基本上除了 Next 与 Angular 之外,热门的框架都是选 Vite。
为什么要用 Vite? 它解决了哪些问题?
Vite 的核心功能跟业界很常使用的 Webpack 很相似,那为什么要选 Vite 呢? 它解决了哪些过去遇到的问题?
解决开发服务器过慢
假如有用过 Webpack 在大型专案的开发者,可能会有类似的经验,就是在改动某部分代码后,要等数十秒才能看到画面渲染新的版本,特别是专案中有用 TypeScript 时,每次的等待时间都让开发体验非常的差。类似的问题可能不只在使用 Webpack 的专案出现,其他工具也有 (例如 Vue CLI)。
会有这个问题,是因为当专案越大时,不仅专案本身的代码越多,依赖的套件也会变多,导致整个专案的代码代码量大幅度上升,而过去的工具并没办法很有效的处理这么多代码,才会导致,改一个东西,要等很久才会在画面上看到改变。
而 Vite 的出现就是要解决这类问题。Vite 不管在本地专案的冷启动,或是专案改动时的热模组更新,速度都非常的快。每次修改后不用等个数十秒才能看到新的画面。这让开发者的体验更好,试想假如做某个专案,每次改动完都要等半分钟,很可能让开发者失去耐心。除此之外,这也让回馈循环 (feedback loop speed) 更加快速,对于专案开发也是助益良多。
生产环境的代码打包
除了说能解决开发时的开发服务器过慢问题,Vite 也透过 Rollup 协助进行生产环境的打包。虽然现在原生的 ES Module 已经获得广泛的支持,但如果直接在生产环境使用没打包的 ES Module 仍是没效率的。在打包的过程中,透过 tree-shaking、lazy-loading、common chunk splitting 等方式,可以让打包出的结果能够获得优化。
你可能会问,为什么我们要为生产环境打包代码? 毕竟现在原生的 ESM 已经得到广泛支持。原因是在生产环境中直接使用未打包的 ESM 仍然不是太有效率,这是因为由于嵌套引入 (nested import) 会需要额外的网路传输往返,即使开启 HTTP/2 也是,而额外的网路传输就需要额外的时间。为了在生产环境中,能有最佳的加载性能,透过 tree-shaking、懒加载(lazy-loading)和常见块分割(common chunk splitting)来打包代码,可以更好地缓存,也能提升效率。
Vite 如何解决服务器过慢的问题?
上面提到,Vite 解决开发服务器过慢的问题,其实我们可以更进一步拆解成「开发服务器启动过慢」与「开发服务器更新过慢」这两个问题来看。
Vite 如何解决开发服务器「启动过慢」的问题
当启动开发服务器时,如果用基于打包工具 (bundler-based) 的设置,会需要先把你整个应用程式都走过一遍,然后才能启动服务器。Vite 通过把应用程式中的模组分为两类来加快开发服务器的启动时间,一个是依赖 (dependencies),另一个是源码 (source code)。
依赖大多是普通的 JavaScript,开发过程中不会经常改变。一些大型的依赖(例如拥有数百个模组的组件库)处理起来也相当耗时。依赖还可能以不同的模组格式(例如 ESM 或 CommonJS)提供。Vite 使用 esbuild 预先打包这些依赖。esbuild 是用 Go 语言写的,比基于 JavaScript 的打包工具快 10 到 100 倍,这让 Vite 加快速度不少。
而源码则通常需要转换 JSX、CSS 或 Vue/Svelte 组件等非纯 JavaScript 的东西,并且会经常被更动。然而,并不是所有的源码都需要同时加载(例如,基于路由的分割的代码,只有在到某个路由时才需要加载)。Vite 通过原生 ESM 来提供源码。实际上这是让浏览器接管了打包工具的部分工作,Vite 只需要根据浏览器的请求,按需转换和提供源码。只有在当前某个元件被实际使用时,才会动态载入背后的代码,这也样让最开始要启动服务器的代码减少,自然让启动变快。
Vite 如何解决开发服务器「更新过慢」的问题
在基于打包工具 (bundler-based)的设置中,当一个文件被更新后,重新构建整个包效率会很低。原因显而易见,因为更新速度会随着应用程式大小的增加而下降 (白话来讲就是,越大包,打包起来越慢)。
在某些打包工具中,开发服务器会在记忆体中运行打包,但是当文件改变时,它仍然需要重新构建打包,并重新加载网页。重新构建打包可能很耗时,并且重新加载页面会消除应用程式当前的状态 (state)。这就是为什么一些打包工具支持热模组替换(HMR):允许模组在不影响页面其余部分的情况下「热替换」自己。这大大提高了开发体验(DX),但实际上,即使是 HMR,随着应用程式大小的增长,更新速度也会显著下降。
在 Vite 中,HMR 是通过原生 ESM 进行的。当一段代码被更新时,Vite 只需要准确地使更新的模组与其最近的 HMR 边界之间的链路失效 (大多数时候只是模组本身),这使得 HMR 更新不管应用程式的大小如何都能维持高速。
Vite 还利用 HTTP 标头来加速整页重载 (这也是利用浏览器来帮忙):原码的模组请求是通过 304 Not Modified
请求,而依赖模组则通过 Cache-Control: max-age=31536000,immutable
来缓存,这样缓存确保这些依赖不会再次触及开发服务器,更新时速度也就大幅提升。
Vite 的未来发展蓝图
在社群中,经常有人会问说,为什么 Vite 不用 esbuild 作为打包工具,而是选择 Rollup? 毕竟 esbuild 速度更快。主要原因是 Rollup 的插件 API 设计比较灵活,Vite 团队认为 Rollup 在性能和灵活性之间提供了更好的平衡。
虽然,Rollup 目前效能不如 esbuild,但这件事正在被改善中。目前最新版的 Rollup 已经把解析器换成了 SWC。此外官方团队也正在进行用 Rust 语言重写 Rollup (很有趣地,这个改写的版本被叫做 Rolldown)。先前 Vite 创作者尤雨溪,在 2023 年 ViteConf 有宣布改写的 Rolldown 计划,等 Rolldown 完成后,将会在 Vite 中取代 Rollup,来大幅提升构建性能,并消除开发和构建之间的不一致性。