Bakkac's Blog

Vite 的构建流程

本文目录

上级:Vite

image.png Vite 的工作流(或称之为现代前端工具链)旨在提供卓越的开发体验和高效的生产构建。Vite 在底层为您做了很多工作,主要通过两种命令支持两种不同的环境:开发环境和生产环境。

以下是 Vite 核心工作流的详细分解:

1. 核心目标和起始点

前端应用之所以需要 Vite 这样的工具,是因为我们希望编写能够直接在浏览器中运行的 JavaScript。手动编写这些代码既不切实际,也无法提供良好的开发者体验。

  • 输入 (Input): 流程通常始于源文件,例如一个使用了 TypeScript 和 JSX 的文件(如 foo.tsx)。
  • 浏览器兼容性问题: 浏览器无法直接运行源文件中的许多内容。例如,类型定义 需要被移除,JSX 需要被编译,并且 ESM 模块在浏览器中需要相对导入且必须包含 文件扩展名 。
  • Vite 的角色: Vite 负责处理所有的模块解析(Module Resolution),重写所有路径,并确保一切在开发和生产环境中都能正常工作。

2. 开发环境工作流 (npm run dev)

开发环境的重点是速度和良好的开发者体验(Developer Experience, DX)。

步骤目的使用工具/技术详细说明
1. 类型剥离/转译快速将 TSX 转换为 JavaScript。esbuild在开发环境中,我们不怎么关心类型检查,因为它会拖慢速度。Vite 使用esbuild来剥离所有类型定义,这是一种非常快速的打包器(Bundler),但在这里主要用于*\转译\* (transpilation)。
2. 转换 (JSX -> JS)将 JSX 转换为有效的 JavaScript。esbuild 或其他插件继剥离类型之后,下一步是将 JSX 转换为 JavaScript (例如 react.createElement)。
3. 增强 DX 功能提升开发效率和调试能力。N/A主要通过以下方式实现: 热模块重载 (HMR) 和 注入 Source Maps。
4. 模块注入启用 HMR 功能。框架插件HMR 是一个重要特性,它允许您在保存文件时自动更新界面,而无需刷新整个页面并保持状态。这依赖于框架(如 React 或 Vue)的插件,并通过注入一些 JavaScript 到模块中来实现 HMR。
5. 调试映射确保错误可读性。Source MapsSource Maps 被注入到编译后的代码中,以便在发生错误时,将错误指向原始的未编译代码,否则错误信息将很难阅读。
6. 迭代当文件更改时。Vite/esbuild一旦文件发生更改(例如您保存了 foo 文件),Vite 将重复整个过程:编译、剥离类型、执行模块解析。

3. 生产环境工作流 (npm run build)

生产环境的重点是生产体验(Production Experience),这意味着生成一个小体积的打包文件(small bundle)、移除未使用的代码,并进行类型检查。

步骤目的使用工具/技术详细说明
1. 类型检查/转译确保代码质量并处理类型。TSC (TypeScript Compiler)与开发环境不同,生产环境需要进行类型检查。因此,Vite 使用 TSC 来进行编译。TSC 会移除类型定义并处理 JSX 转换。
2. 附加转换实现兼容性或处理特定语法。Babel 或其他编译器可能会使用 Babel 来添加 Polyfills(例如为了支持 IE 10)。如果使用 Vue 或 Svelte,可能需要使用不同的编译器。此外,还会进行其他转换,例如注入环境变量。
3. 模块打包将代码整合成最终的浏览器可用模块。Rollup一旦代码转换成纯 JavaScript,Vite 会使用 Rollup 进行打包 (Bundle)。Vite 之所以使用 Rollup,是因为它比 esbuild *\更成熟\,且具有*\更大的生态系统\(ecosystem)来处理复杂的打包需求。
4. 优化减少最终文件大小和提升加载性能。Rollup 插件Rollup 会执行多项优化:进行 *\Minify/Uglify\(删除空白、将变量名转换为更小的名称),并处理不同的*\代码块大小\(chunk sizes)和*\多个入口点\*(multiple entry points)。

简而言之,Vite 的工作流就像一条定制的装配线:

在开发这条线上,它使用esbuild 这台极速机器,专注于快速去除外壳(类型和 JSX),并立即安装实时反馈系统(HMR 和 Source Maps),确保工程师可以高效调试。

在生产这条线上,它使用TSC进行严格的质量检验(类型检查),然后将半成品交给经验更丰富的Rollup打包大师,Rollup 负责精细化压缩、优化和封装,以确保最终产品以最小、最快的形式交付给用户。

Future

Vite 使用的是双打包器架构 (two-bundler architecture)。

  • 在开发阶段,它严重依赖 esbuild,因为 esbuild 速度极快。这要归功于它在开发过程中使用了 ESM (ES Modules),并避免了预先打包整个应用
  • 对于生产环境构建,它使用 Rollup,因为 Rollup 具有更好的输出控制和成熟的插件生态系统。因其 Tree-shaking(摇树优化)、输出质量和插件模型而备受推崇。

但随着时间的推移,这种架构也带来了问题。在开发中使用 esbuild,在生产中使用 Rollup,这意味着 Vite 有两条代码转换流水线,两种不同的打包模型,并且必须桥接两个不同的插件系统。每次生态系统变得更加复杂时,Vite 都需要编写更多的内部“胶水代码”来保持体验的一致性。最重要的是,当一个工具在开发环境和生产环境使用两套不同系统时,就会出现微妙的差异。

Rolldown 就是为了简化这种架构而诞生的。他们的目标是为 Vite 提供一个单一的打包工具,最终能同时满足开发和生产的需求。它需要足够快以应对 esbuild 的竞争,具有足够的兼容性以保留基于 Rollup 的庞大插件生态系统,并且足够灵活以支持 Vite 在大型实际应用中所需的功能。

image.png 为什么打包速度很重要呢?

  1. 打包是个繁重的过程,如果不 care,真的会很慢,打包工具必须解析源文件、解析导入关系、应用代码转换、运行插件、分析导出、移除未使用的代码、生成代码块,最后写入输出文件。在大型项目中,这些步骤耗费的时间很快就会累加起来。
  2. 众所周知,当构建速度很慢时,我们通常会懒得去运行它。另一方面,当构建非常快时,开发反馈循环就会紧密得多。

Rolldown 是用 Rust 编写的,并使用 Oxc 来处理解析和代码压缩 (minification) 等与语言相关的工作。Oxc 是 VoidZero 更大宏图的一部分,旨在用 Rust 构建更快的 JavaScript 工具链基础设施。

在底层,Rolldown 是新的 VoidZero “三位一体”架构的中间层。它位于作为协调者的 Vite 和 Oxc 编译器之间。Rolldown 负责处理链接和打包,而 Oxc 处理解析和转换等繁重的工作。这种架构允许原始 AST(抽象语法树)传输,这意味着这些工具可以在内存中共享代码的结构,而无需不断地重新解析代码,这正是我们看到 30 倍性能飞跃的根本原因。

即将推出的“全打包模式” (full bundle mode)。自 Vite 诞生以来,其“不打包” (unbundled) 的开发方法一直是黄金标准,但随着项目增长到数千个模块,浏览器中的网络请求开销开始成为一个切实的问题。由于 Rolldown 速度够快,Vite 8 现在可以尝试在开发阶段,仅用几毫秒的时间就将你的整个应用打包完毕。