写在前面:这个问题我们在茶水间讨论了至少三年。从最初全员死守
tsc配tsconfig.json,到后来有人偷偷引入swc、esbuild、vite,团队里曾经因为“构建慢”吵得不可开交。今天我不讲虚的,只讲我们团队亲测过的真实体感,以及每个工具到底适合什么人。
一、先别急着选工具,先问问自己这三个问题
在深入技术细节之前,我建议你先停下来想想:
- 你的项目是什么类型? 是大型 Monorepo?还是小型内部工具?或者是面向用户的 Web 应用?
- 你最痛的点是什么? 是构建速度慢?还是类型检查太严?还是打包体积大?
- 你的团队有多少人?技术水平如何? 一个人写的玩具项目和 50 人团队的生产系统,需求完全不同。
如果这些问题你还没想清楚,直接跳工具选型,大概率会踩坑。
二、tsc:那个你熟悉的“老朋友”
2.1 它到底是什么
tsc 是 TypeScript 官方的编译器。严格来说,它不是构建工具,而是一个类型检查器 + 代码转换器。它的工作是把 .ts/.tsx 文件翻译成 .js 文件,同时验证类型是否正确。
2.2 为什么很多人还在用它
- 官方血统:TypeScript 团队维护,社区支持最完善
- 配置直观:
tsconfig.json是行业标准,几乎每个开发者都见过 - 类型检查权威:如果你想保证类型安全,
tsc --noEmit依然是黄金标准 - 生态兼容:几乎所有工具链都能和它配合
2.3 它的致命弱点:慢
这是 most developers 的痛点。
假设你有一个 500 个 TypeScript 文件的项目,每次改动一个文件,tsc 需要重新类型检查所有相关文件。随着项目增长,构建时间从几秒变成几十秒,甚至几分钟。
真实数据参考(基于我们团队的测试):
- 小型项目(< 100 个 TS 文件):tsc 约 2-5 秒
- 中型项目(100-500 个 TS 文件):tsc 约 10-30 秒
- 大型项目(> 500 个 TS 文件):tsc 可能超过 1 分钟
这还只是增量构建。如果是全量构建,时间会翻倍。
2.4 代码示例:tsc 的典型用法
// tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "node",
"strict": true,
"jsx": "react-jsx",
"outDir": "./dist",
"sourceMap": true,
"declaration": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
# 开发时
tsc --watch --noEmit # 只检查类型,不输出文件
# 生产构建
tsc # 输出到 dist/
2.5 什么时候该用 tsc
✅ 推荐场景:
- 项目较小,构建速度不是瓶颈
- 团队对 TypeScript 类型系统依赖极高
- 需要输出
.d.ts声明文件给外部消费者 - 作为其他构建工具的类型检查环节(而不是编译环节)
❌ 不推荐场景:
- 大型 Monorepo
- 需要热更新(HMR)的开发体验
- 对构建速度有严苛要求
三、esbuild:那个让所有人尖叫的“速度怪兽”
3.1 它凭什么这么快
esbuild 用 Go 语言编写,核心设计哲学就一个:快。
它不像 tsc 那样做完整的 AST 遍历和类型检查,而是采用更激进的策略:
- 跳过类型检查(类型检查交给
tsc或typescript单独做) - 批量处理文件,减少 I/O 开销
- 利用并发和缓存
速度对比(同一项目):
| 工具 | 冷启动 | 增量构建 |
|---|---|---|
| tsc | 35s | 12s |
| esbuild | 0.8s | 0.3s |
你没看错,快 40-50 倍。
3.2 它的工作原理
esbuild 本质上是一个打包器 + 转换器,它的流程是:
TypeScript 文件 → esbuild 解析 → 转换 JS → 打包 → 输出
↑
类型检查(可选,通常单独用 tsc 做)
关键点:esbuild 不检查类型。它假设你的 TypeScript 代码是正确的,直接翻译。
3.3 代码示例:esbuild 的实际配置
// esbuild.config.js
const esbuild = require('esbuild');
esbuild.build({
entryPoints: ['src/index.ts'],
bundle: true,
sourcemap: true,
outdir: 'dist',
platform: 'node',
target: 'node18',
// 跳过类型检查,速度更快
tsconfigRaw: {
compilerOptions: {
noEmit: false,
// 可以覆盖 tsconfig.json 中的一些选项
}
},
// 关键:不运行类型检查,只做转换
}).catch(() => process.exit(1));
// package.json
{
"scripts": {
"build": "esbuild src/index.ts --bundle --outfile=dist/index.js --platform=node",
"type-check": "tsc --noEmit"
}
}
3.4 它的局限性
- 不支持完整的 TypeScript 功能:某些高级类型特性(如复杂的条件类型)可能编译出错
- 没有类型检查:你必须额外运行
tsc来做类型验证 - 插件生态不如 Rollup/Webpack:虽然插件系统越来越完善,但相比 Webpack 的十年积累,还是小众
3.5 什么时候该用 esbuild
✅ 推荐场景:
- 构建速度是核心需求(CI/CD、开发服务器)
- 项目规模大,tsc 已经慢到无法忍受
- 团队接受“类型检查与构建分离”的架构
- 使用 Vite、esbuild 原生支持的工具链
❌ 不推荐场景:
- 项目较小,tsc 完全够用
- 需要复杂的打包策略(代码分割、动态导入优化等)
- 团队对 TypeScript 高级类型特性依赖极深
四、中间派:swc、Vite、Turbopack
4.1 SWC:Rust 写的“另一个选择”
SWC(Speedy Web Compiler)是用 Rust 编写的编译工具,和 esbuild 类似,追求极致速度。
// 使用 @swc/core 进行编译
const swc = require('@swc/core');
const fs = require('fs');
async function compile(input, output) {
const result = await swc.compile(
fs.readFileSync(input, 'utf8'),
{
jsc: {
parser: {
syntax: 'typescript',
tsx: true,
},
target: 'es2020',
},
module: {
type: 'commonjs',
},
}
);
fs.writeFileSync(output, result.code);
}
SWC vs esbuild:
- SWC 支持更多 TypeScript 特性
- esbuild 通常更快一点
- SWC 有完整的类型检查支持(通过
@swc/core的transformAPI) - 社区生态:esbuild > SWC
4.2 Vite:面向开发体验的“现代构建工具”
Vite 不是单纯的编译器,而是一个开发服务器 + 构建工具。它利用了浏览器对 ES Module 的原生支持,在开发时直接提供服务,无需打包。
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
build: {
sourcemap: true,
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
},
},
},
},
});
为什么 Vite 快?
- 开发时:浏览器直接请求
.ts文件,Vite 用 esbuild 做预编译,速度极快 - 构建时:默认使用 Rollup(可以切换到 esbuild)
开发流程:
浏览器 → 请求 src/main.ts → Vite 服务器 → esbuild 预编译 → 返回 JS
↑
缓存命中则直接返回
4.3 Turbopack:Next.js 的“下一代”
Turbopack 是 Vercel 开发的增量打包引擎,专门针对 Next.js 优化。它用 Rust 编写,目标是替代 Webpack。
目前 Turbopack 还在 Beta 阶段,但已经展示出了比 Webpack 快 700 倍的性能。如果你使用 Next.js,可以考虑开启 turbopack: true。
五、真实案例:我们团队的选择过程
5.1 背景
我们是一个中等规模的 SaaS 产品团队,项目特点:
- Monorepo,包含 3 个前端应用、2 个后端服务
- 总计约 800 个 TypeScript 文件
- 前端使用 React,后端使用 Node.js
- 团队成员 15 人,技术水平参差不齐
5.2 问题诊断
最初我们只用 tsc + webpack,遇到的痛点:
- 开发服务器启动慢:每次改动,HMR 需要 5-10 秒
- CI/CD 构建时间长:全量构建平均 3 分钟,拖慢了发布节奏
- 类型检查与构建耦合:无法单独优化
5.3 选型决策
我们做了为期两周的 POC(概念验证),测试了以下方案:
| 方案 | 构建时间 | 开发体验 | 类型检查 | 复杂度 |
|---|---|---|---|---|
| tsc + webpack | 180s | 慢 | 内置 | 高 |
| esbuild + tsc | 8s | 快 | 分离 | 中 |
| Vite (esbuild) | 3s | 极快 | 分离 | 低 |
| SWC + tsc | 6s | 快 | 分离 | 中 |
最终选择:Vite + tsc 分离
5.4 配置细节
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
build: {
target: 'esnext',
minify: 'esbuild',
sourcemap: true,
},
optimizeDeps: {
esbuildOptions: {
// 开发时预编译,加速 HMR
target: 'es2020',
},
},
});
// tsconfig.json(只用于类型检查)
{
"compilerOptions": {
"noEmit": true,
"strict": true,
"jsx": "react-jsx"
},
"include": ["src"]
}
// package.json scripts
{
"scripts": {
"dev": "vite",
"build": "vite build",
"type-check": "tsc"
}
}
5.5 效果
- 开发服务器启动时间:从 45 秒降到 3 秒
- HMR 响应时间:从 5-10 秒降到 < 500ms
- CI/CD 构建时间:从 3 分钟降到 20 秒
- 类型检查独立运行,可以在 CI 中单独触发
六、如何为你们的团队做决策
6.1 决策树
你的项目是大型 Monorepo 吗?
├── 是 → 考虑 Turbopack / Vite / esbuild
└── 否 → 项目规模多大?
├── < 100 TS 文件 → tsc 完全够用
├── 100-500 文件 → esbuild / SWC
└── > 500 文件 → Vite / Turbopack
6.2 关键考量因素
- 团队熟练度:如果团队只熟悉 Webpack,强行切换到 esbuild 可能需要学习成本
- 生态兼容性:某些旧库可能只支持 CommonJS,需要额外配置
- 类型检查策略:是否接受“构建与类型检查分离”?
- 未来扩展性:项目是否会快速增长?
6.3 渐进式迁移建议
如果你正在使用 tsc + webpack,不要一次性切换。可以分步进行:
第一步:将类型检查分离
# 在 CI 中单独运行类型检查
npm run type-check # tsc --noEmit
第二步:用 esbuild 替换 webpack 的编译环节
// webpack.config.js
const esbuild = require('esbuild');
module.exports = {
module: {
rules: [
{
test: /\.[jt]sx?$/,
exclude: /node_modules/,
use: 'esbuild-loader', // 用 esbuild 代替 babel-loader
},
],
},
};
第三步:完全迁移到 Vite
# 使用 create-vite 初始化新项目
npm create vite@latest my-app -- --template react-ts
七、常见误区澄清
误区 1:“esbuild 不能检查类型,所以不安全”
事实:类型检查可以独立运行。大多数团队使用 tsc --noEmit 在 CI 中检查类型,esbuild 只负责编译。这是业界最佳实践。
误区 2:“Vite 只适合前端项目”
事实:Vite 也可以用于后端 Node.js 项目(使用 vite-node)。虽然不如 esbuild 直接,但完全可以工作。
误区 3:“切换构建工具会破坏现有代码”
事实:只要你的 TypeScript 代码是标准的,切换工具通常不会有问题。建议先在本地跑通,再迁移到 CI/CD。
八、总结:没有银弹,只有最适合的
| 工具 | 速度 | 类型检查 | 易用性 | 适合场景 |
|---|---|---|---|---|
| tsc | 慢 | ✅ 完整 | 高 | 小型项目、类型敏感 |
| esbuild | 极快 | ❌ 需分离 | 中 | 大型项目、速度优先 |
| Vite | 极快 | ❌ 需分离 | 高 | 现代前端项目 |
| SWC | 快 | ⚠️ 部分 | 中 | 需要更多 TS 特性 |
| Turbopack | 极快 | ❌ 需分离 | 中 | Next.js 生态 |
我的建议:
- 如果你是新手或小项目,继续用
tsc,它够用且简单 - 如果你追求速度且项目较大,选择
esbuild或Vite - 如果你用 Next.js,尝试
Turbopack - 无论选什么,把类型检查独立出来,这是提升体验的关键
九、最后的话
构建工具的选择从来不是技术问题,而是团队工程文化的体现。我们团队在选型时,花了一周时间讨论,最终达成共识:“快”是我们的核心需求,但“类型安全”不能妥协。
所以我们的答案很明确:esbuild(或 Vite)负责构建,tsc 负责类型检查。两者分离,各司其职。
希望这篇文章能帮你们团队做出更明智的选择。如果你们有什么具体的场景或问题,欢迎在评论区交流——我们团队踩过不少坑,有很多血泪教训可以分享。
