Vite:下一代前端构建工具的崛起

Published on

引入

随着前端项目规模的扩大和现代浏览器对 ES 模块的支持,传统构建工具的启动速度和开发效率逐渐成为瓶颈,Vite 的出现正是为了解决这些问题。

Vite是一个法语词,意为“快”,就像vite官网自我介绍的那样,vite是一个blazing fast 速度极快的前端构建工具

这篇文章我们将从构建工具的历史演进、Vite的核心优势、以及未来发展等方面来了解Vite这个构建工具。

构建工具的历史背景

为什么我们需要构建工具?

在早期的前端开发中,JavaScript还没有模块化的能力,开发者通过 <script> 标签手动加载多个 JS 文件:

<script src="utils.js"></script>
<script src="app.js"></script>

这会造成许多问题:

  • 依赖管理困难:加载顺序很重要,比如 app.js 依赖于 utils.js,但如果加载顺序错误,程序就会崩溃。
  • 性能问题:如果有几十个或上百个文件,浏览器会频繁发送网络请求,导致页面加载缓慢;图片等静态资源也需要优化
  • 兼容性问题:ES6+等新语法需要转移成兼容版本、CSS前缀要自动补全等
  • 开发效率问题:除了需要手动管理大量的模块依赖关系,代码改动后还需要手动刷新浏览器,且调试不便,难以定位问题

构建工具的演进

有问题,就有人想解决方案。

于是社区相继出现了Grunt(2012年)、Gulp(2013年)、Browserify(2013年),但它们都存在各自的问题,Grunt 和 Gulp主要用于静态资源处理,但不支持模块化;Browserify 引入模块化能力,但功能有限。

Webpack

2014年,Webpack出现,标志着前端构建工具的一个里程碑。它首次将模块化开发、代码分割、插件系统和 HMR 集成到一个统一的工具中,为开发者提供了完整的解决方案。

Webpack 基于 "Bundle" 的设计,将一个或多个模块打包成一个或多个 bundle,然后在浏览器中加载这些 bundle。

webpack-bundler

在开发时,Webpack的构建流程如下:

  • 首先对源代码进行 AST(抽象语法树)解析,通过 AST 收集所有模块依赖
  • 使用各种 loader 对模块进行转换(如 babel-loader 转换 ES6+语法)
  • 将所有模块打包成 bundle
  • 启动开发服务器提供访问
  • 当文件发生变更时,需要重新进行模块转换和打包

生产构建时,Webpack的流程与开发模式类似,但更关注代码优化和兼容性:

  • 完整的代码分析和依赖收集
  • 模块转换和打包
  • 进行 Tree Shaking 删除无用代码
  • 代码压缩和优化
  • 生成最终的静态资源

虽然 Webpack表现出色,但随着项目规模的增大,它也浮现出诸多问题:

  1. 复杂的依赖图和模块解析

Webpack 在构建时需要解析整个依赖图,尤其是对于大型项目,模块依赖的复杂性会导致Webpack必须分析和重新构建大量模块。 增量构建虽然可以提高小范围变更的构建速度,但面对复杂的依赖关系和代码拆分时,Webpack仍然需要重新计算整个依赖图,尤其是在动态导入的场景下,无法避免复杂依赖的重新构建。

  1. 构建过程中的高开销

Webpack 的构建过程包括 AST 解析、代码转换、模块打包、优化(如 Tree Shaking、代码压缩等),这些步骤都需要消耗大量的时间和计算资源。

  1. HMR的性能瓶颈

Webpack 的热模块替换(HMR)功能虽然可以在文件修改时更新浏览器上的部分内容,而无需刷新整个页面,但 HMR 仍然需要在文件变更时重新构建依赖图,找到依赖项,并且更新相应的模块。

而Vite走了一条截然不同的道路,极大改善了这些问题。

Vite!Blazing Fast!

Vite抓住了两大技术发展的机遇,一是浏览器自 2017 年起支持原生 ES 模块,二是编译型语言(如 Go和Rust)推动工具链性能大幅提升。它的核心优势和特点主要体现在以下几个方面:

1. 加速开发服务器启动

Vite 采用 "Native ESM" 的按需编译方式,利用浏览器的原生模块支持,仅在浏览器请求某模块时即时编译,大幅缩短启动时间。

Vite在开发模式下的工作流程如下:

  • 启动开发服务器,不需要预先打包
  • 浏览器请求模式时,才会使用esbuild进行即时编译
  • 编译后的代码以原生ES Module的形式返回给浏览器
  • 文件变更时,只需要使模块缓存失效,不需要重新打包整个应用

Vite巧妙地将代码分为两类处理,依赖源码

(1)依赖

大多在开发时不会变动的纯JavaScript,一些较大的依赖(例如有上百个模块的组件库)处理 代价很高。Vite使用esbuild(2020年首次公开发布)构建依赖,而esbuild使用Go编写,编译速度比传统的JavaScript打包器快10-100倍。如下图:

esbuild

(2)源码

通常包含一些并非直接是JS,需要转换的文件,例如JSX,CSS或者Vue文件),且这些文件常常被编辑。

而且,不是所有的源码都需要同时被加载(例如基于路由拆分的代码模块)。

Vite以原生ESM方式提供源码,让浏览器接管了打包程序的部分工作,Vite 只需要在浏览器请求源码时进行转换并按需提供源码。如下图:

esm-based

而我们上文讲到的webpack bundle based dev server是这样的:

esbuild

2. 改进热更新(HMR)

基于打包启动时,当源文件被修改后,重新构建整个包是低效的。一些打包器的开发服务器将构建内容存入内存,这样它们只需要在文件更改时使模块图的一部分失活,但它也仍需要整个重新构建并重载页面。

这样代价很高,并且重新加载页面会消除应用的当前状态,所以一些打包器支持了动态模块热替换(HMR):允许一个模块 “热替换” 它自己,而不会影响页面其余部分。

传统打包工具需要维护一个完整的模块依赖图,任何更改都需要部分重新打包

举个例子,假设我们有这样一个应用结构:

src/
  ├── main.js
  ├── App.vue
  ├── components/
  │   ├── Header.vue
  │   ├── Sidebar.vue
  │   └── Content.vue
  └── utils/
      └── helper.js

现在我们修改Content.vue的一些代码,传统打包工具如webpack的HMR流程

  • 发现 Content.vue 改变
  • 查找整个依赖图,找出所有与 Content.vue 相关的模块
  • 重新构建这些相关模块,生成新的包含这些模块的 bundle
  • 通过 WebSocket 通知浏览器有更新
  • 浏览器加载新的 bundle
  • 执行 HMR runtime 代码,替换旧模块

而Vite由于利用浏览器的原生ESM能力,每个模块都是独立的,可以精准地重新编译,Vite的HMR流程:

  • 发现Content.vue改变
  • 直接重新编译这个文件
  • 通过 WebSocket 通知浏览器这个文件更新了
  • 浏览器通过 ESM import 直接请求新的模块
  • Vue HMR runtime 处理组件更新

Webpack 在热更新时需重新构建部分依赖图和模块,并生成新的 bundle。相比之下,Vite 利用浏览器原生 ESM,精准更新受影响模块,省去额外的打包步骤。这就是为什么在大型应用中,Vite 的 HMR 性能表现会明显优于传统打包工具。

3. 依赖Rollup进行生产模式下的打包构建

在生产环境下,Vite使用Rollup构建以实现更强大的优化,流程如下:

  • 扫描入口文件
  • 对依赖进行预构建优化
  • 使用Rollup进行打包
  • 进行代码分割和按需加载优化
  • 优化静态资源
  • 生成生产环境的静态文件

我们前面说过Vite在开发模式时可以立即启动开发服务器而无需打包,那为什么在生产模式不沿用这个原理,而是使用了Rollup工具进行打包呢?

我认为主要有以下考虑:

(1)代码优化需求

生产环境对代码的性能和体积要求更高,需要进行Tree Shaking、代码压缩、代码合并、动态加载优化等,这是打包工具的强项。

(2)兼容性考虑

虽然现代浏览器支持ES模块,但仍有部分旧浏览器不支持ES模块,打包工具可以通过插件生成兼容的代码格式。

(3)加载效率问题

开发环境的HTTP请求是由本地服务器处理的,延迟几乎可以忽略。但是在生产环境下,如果为每个模块发起一个HTTP请求,会造成网络瀑布流,显著增加首屏加载时间。

此外,Vite选择Rollup作为生产环境的打包工具,主要是因为Rollup在代码优化和Tree Shaking方面表现优秀。它能够精准地移除无用代码,并对模块进行高效的压缩和分割。 相比于esbuild,Rollup有更成熟且丰富的插件生态系统,提供更多的优化选项。

Vite的未来

Vite 的发展得益于浏览器对 ESM 的支持以及编译工具的进步。

根据vite的官方文档介绍,Rollup一直在进行进行性能优化,Rollup V4 已经使用 SWC 替代传统的 JavaScript 解析器。

对于Vite未来的更详细的信息,可以看尤雨溪这两年关于Vite的演讲,以下是视频链接和简要总结:

  1. Evan You | Keynote: The State of Vite | ViteConf 2023

版本更新回顾:

  • Vite4.0升级到Rollup3;采用SWC驱动的React插件
  • Vite4.1-4.4 启动速度提升4倍,HMR速度提升2倍; 改进源码映射调试体验;引入Lightening CSS的实验性支持
  • Vite5.0(测试版):弃用CommonJS API,移除对过期Node.js版本的支持

存在的问题:

  • 生产构建速度相对较慢
  • 开发和生产环境行为存在不一致
  • 代码分割控制能力有限
  • SSR external配置难以理解和处理

解决方案

  • 短期:Rolldown项目,用Rust重写的Rollup,替换Vite中的esbuild和Rollup
  • 长期:用Rust重写Vite核心部分,统一开发和生产环境的构建工具
  1. Vite and the Future of JavaScript Tooling | ViteConf 2024

  • 周下载量翻倍,成为大多数框架的默认构建工具,在State of JS调查中多个类别位列第一
  • 尤雨溪创立VoidZero公司,专注于两个核心项目:
    • Oxc项目:新一代JavaScript工具链基础设施,包含解析器、语义分析、转换器等组件,比现有Rust方案快3倍
    • Rolldown项目进展:已完成90%的Rollup插件兼容,比esbuild快2倍
  • 未来规划:近期基于Rolldown的Vite6
  • 长期愿景:打造统一的JavaScript工具链,实现高性能、可组合的工具体系
Table of Contents