Skip to content

Vite+ 第二章:拆解 Vite+ 内部到底装了什么 ​

译注:本文翻译自 dev.to 前端专栏作者 Othmane Nemli 的系列文章《Vite+ — Chapter 2: What's Inside Vite+?》,原文发布于 2026 年 9 月 25 日。原文链接见文末。本文为系列第二章,第一章讨论了「为什么还需要另一个 JavaScript 工具」。

在第一章中,我们探讨了 Vite+ 存在的理由。简单来说:现代 JavaScript 项目会用到许多优秀的工具,但把这些工具连接起来并长期维护,往往会变得相当复杂。Vite+ 试图将其中若干工具整合到同一套工作流之下。

但这又引出了另一个问题:Vite+ 内部究竟装了什么?

当你看到下面这些命令时:

shell
vp dev
vp test
vp check
vp build

它们背后到底发生了什么?让我们打开这个工具箱看看。

Vite+ 不是一个大而全的工具 ​

首先要理解的是,Vite+ 并不是要取代一切。它把多个各司其职的工具聚合在一起。简化后的结构大致如下:

plaintext
                    Vite+
                      |
       +--------------+--------------+
       |              |              |
   Development      Quality        Testing
       |              |              |
      Vite       Oxlint / Oxfmt   Vitest
       |
    Building
       |
   Rolldown

       +-----------------------------+
       |
    Libraries
       |
    tsdown

       +-----------------------------+
       |
     Tasks
       |
   Vite Task

每个工具解决不同的问题,关键在于 Vite+ 提供了一致的方式来使用它们。下面逐一介绍。

1. Vite —— 开发 ​

先从大多数开发者已经熟悉的工具说起。Vite 主要是一个开发服务器和构建工具。

假设你在构建一个 React 应用,目录结构可能是:

plaintext
src/
├── App.tsx
├── main.tsx
└── components/
    └── Button.tsx

你希望写完代码后立刻在浏览器中看到结果,这正是 Vite 的用武之地。你可以用 vp dev 启动开发服务器,底层实际工作的是 Vite。

Vite 为什么有用? ​

想象你把一个 Button 组件从返回 Hello 改成返回 Hello World。你显然不希望每改一行就停掉服务器、重新构建整个应用、再重启浏览器。Vite 通过 热模块替换(HMR) 等特性提供快速的开发体验。简单来说:

plaintext
你修改代码
      ↓
Vite 感知到变化
      ↓
浏览器更新
      ↓
你继续工作

这就是 Vite 成为现代前端开发常见组成部分的原因。在 Vite+ 中,你依然得到 Vite,它并没有被替换。

2. Vitest —— 测试 ​

写代码只是一半工作,我们还需要确认它能正常运行,这就是 Vitest 的职责。

假设有这样一个函数:

typescript
function add(a: number, b: number) {
  return a + b;
}

你可以写一个测试:

typescript
import { expect, test } from "vitest";

test("adds two numbers", () => {
  expect(add(2, 3)).toBe(5);
});

然后运行 vp test。Vite+ 使用 Vitest 作为测试工具。

为什么选 Vitest? ​

你可能已经知道 Jest 之类的工具。这里的关键并不是评判哪个测试运行器「最好」,而在于 Vitest 与 Vite 生态配合得非常自然。这意味着你的开发环境和测试环境可以共享大量配置和行为。概念上:

plaintext
Development
     ↓
   Vite

Testing
     ↓
  Vitest

        ↓
Shared ecosystem

这种集成正是 Vitest 能自然融入 Vite+ 的原因之一。

3. Rolldown —— 打包 ​

接下来是一个听起来有点吓人的词:打包器(bundler)。别担心,概念其实很简单。

你的应用可能包含成百上千个文件:

plaintext
src/
├── main.ts
├── App.tsx
├── components/
│   ├── Button.tsx
│   ├── Modal.tsx
│   └── Header.tsx
├── utils/
│   ├── date.ts
│   └── format.ts
└── ...

浏览器并不一定需要原封不动地接收所有这些文件。打包器会分析文件之间的关系,并为生产环境生成优化后的产物。概念上:

plaintext
你的源代码
       ↓
    打包器
       ↓
  生产环境文件
       ↓
     浏览器

传统上,Vite 使用 Rollup 进行生产构建。Vite+ 则引入了 Rolldown——一个用 Rust 编写的新一代打包器,旨在为 Vite 生态提供高性能的打包基础。你未必需要直接与 Rolldown 打交道,只需运行 vp build,让工具链处理即可。

打包器为什么重要? ​

想象你的应用有 10000 行源代码。你不希望手动去判断「哪些文件应该被包含」「哪些模块相互依赖」「这些文件能否合并」「无用代码能否移除」。打包器会构建依赖图:

plaintext
App
├── Header
├── Dashboard
│   ├── Chart
│   └── Table
└── Utils

然后生成优化后的输出。这正是构建性能变得重要的领域之一,对大型项目尤其如此。

4. tsdown —— 构建库 ​

开发者构建的不只是应用,有时也在创建库,比如 my-ui-library、my-auth-library、my-api-client、my-utils。

假设你导出了一个函数:

typescript
export function formatDate(date: Date) {
  // ...
}

你希望其他开发者能通过 npm install my-utils 安装它。此时构建流程的需求就不同了,你可能需要:JavaScript 产物、TypeScript 类型声明、不同的模块格式、包元数据、优化输出。

这正是 tsdown 进入 Vite+ 生态的位置。你可以用 vp pack 来打包一个库。关键区别在于:

plaintext
Application
    ↓
vp build

Library
    ↓
vp pack

这两种工作流的目标并不相同。

5. Oxlint —— 代码检查 ​

现在谈谈代码质量。想象有人写下这样的代码:

javascript
const user = getUser();

console.log(user);

if (user) {
  // ...
}

代码也许能跑,但可能存在问题:未使用的变量、可疑的模式、意外的 bug、不一致的代码、团队不允许的做法。Linter 就是用来查找这类问题的。

Vite+ 使用 Oxlint 进行代码检查。可以把它理解为:

plaintext
你的代码
   ↓
Oxlint
   ↓
潜在问题

例如 vp check 可以把代码检查纳入整体项目检查之中。

为什么还要另一个 linter? ​

你可能会想:「可我们已经有 ESLint 了。」没错,ESLint 仍被广泛使用,生态庞大。Oxlint 则采取了不同的思路,重点放在性能上。它属于更广泛的 Oxc 工具链,同样用 Rust 编写。

对 Vite+ 而言,重要的理念不是「ESLint 不好」,而是:「如果常见的开发工具能极其快速,并整合进同一条工具链,会怎样?」这正是选择 Oxlint 这类工具背后的哲学。

6. Oxfmt —— 格式化 ​

代码检查和格式化相关,但不是一回事。Linter 问的是:「这段代码有没有潜在问题?」格式化工具问的是:「能否让代码遵循一致的风格?」

例如下面两段在功能上相似:

javascript
const user={name:"John"};

和:

javascript
const user = {
  name: "John",
};

但大多数团队希望所有人使用相同的格式规则,这就是 Oxfmt 的职责。可以把它看作工作流中的格式化环节:

plaintext
源代码
    ↓
Oxfmt
    ↓
一致的格式

这同样类似于开发者传统上使用 Prettier 的场景。

代码检查 vs 格式化 ​

如果你刚接触前端开发,这个区别值得记住。

格式化:「让代码看起来一致。」例如 const name="John" 会变成 const name = "John";。

代码检查:「查找可能有问题的模式。」例如 const unusedVariable = 123;,linter 会提示 unusedVariable is never used。

所以:

plaintext
Oxfmt  → 代码长什么样

Oxlint → 代码中的潜在问题

两者都可以纳入 vp check。

7. Vite Task —— 运行任务 ​

随着项目增长,这部分会变得更有意思。大多数项目都有任务,例如 build、test、lint、typecheck。你可能会在 package.json 中定义它们:

json
{
  "scripts": {
    "build": "...",
    "test": "...",
    "lint": "..."
  }
}

对小型项目来说这足够了。但想象一个 monorepo:

plaintext
apps/
  web/
  admin/

packages/
  ui/
  auth/
  utils/

此时任务之间产生了关系。例如:

plaintext
ui
 ↓
web

如果 UI 包发生变化,Web 应用可能需要重新构建。这正是任务运行器发挥作用的地方。Vite+ 包含 Vite Task,用于任务执行与缓存。

任务依赖 ​

具体来说,假设 packages/ui 被 apps/web 使用。你修改了 packages/ui/Button.tsx,依赖图是:

plaintext
ui
 ↓
web

一个聪明的任务运行器能理解:「Web 应用依赖 UI,所以 Web 构建可能需要运行。」但假设另一个包 packages/utils 没有变化,如果那里没有相关改动,重建所有内容可能就是多余的。这正是任务图和缓存发挥作用的地方。

缓存 ​

缓存听起来复杂,但基本思路很简单。假设你运行 vp run build,构建耗时 30 秒。你在没有任何改动的情况下再次运行,为什么要再花 30 秒做完全相同的工作?缓存可以记住上一次的结果。概念上:

plaintext
第一次运行:

Source
  ↓
Build
  ↓
Result
  ↓
Cache

然后:

plaintext
第二次运行:

Source
  ↓
相关内容有变化吗?
  ↓
没有
  ↓
复用结果

这在大型仓库和 CI 中尤其有价值。我们会在第四章更深入地讨论这一点。

8. 运行时与包管理 ​

还有一部分开发体验容易被忽视:环境本身。

一个项目可能期望 Node.js 22 + pnpm,而另一个项目期望 Node.js 20 + npm。Vite+ 也提供了管理运行时和包管理器环境的命令与工作流。例如 vp env 可以作为该工作流的一部分使用。

目标是让项目所使用的环境更加明确、可复现。这很重要,因为「在我机器上能跑」是软件开发中最古老的问题之一。

把各部分拼在一起 ​

现在我们可以看清简单的 vp 命令背后是什么了:

plaintext
                         Vite+
                           |
        +------------------+------------------+
        |                  |                  |
    Development         Quality            Testing
        |                  |                  |
       Vite         Oxlint + Oxfmt         Vitest
        |
     Building
        |
    Rolldown

        |
    Libraries
        |
     tsdown

        |
      Tasks
        |
   Vite Task

你不必在第一天就分别学习每一个工具,而是可以通过一套通用工作流与它们交互。例如:

  • vp dev → 开发
  • vp test → 测试
  • vp check → 代码质量
  • vp build → 构建

原文链接 ​

https://dev.to/othmane_nemli/vite-chapter-2-whats-inside-vite-1n2k