Bun vs Node.js:谁才是 TypeScript 的"亲爹"?
引言
TypeScript 已经是前端和后端的事实标准,但它的运行时处境一直很尴尬------Node.js 是 JS 的运行时,不是 TS 的 。你写的 .ts 文件永远要被某个工具先转成 .js,再交给 Node 执行。
而 Bun 从设计的第一天起就把 TypeScript 当作一等公民。同样是跑 TS,Bun 凭什么比 Node.js 更顺手?这篇文章从 TS 开发者的视角来拆解。
Node.js:TypeScript 的"后妈"
Node.js 2009 年诞生时,TypeScript 还不存在。TS 是 2012 年微软发布后才在 Node 生态里"强行嫁接"上去的。十几年过去,这个寄生关系一直没变:
vbscript
你写的: server.ts
│
┌──────┴──────┐
│ ts-node │ 或 tsx / tsimp / tsc + node ...
│ 先编译成 JS │
└──────┬──────┘
▼
server.js → V8 引擎 → 执行
结果就是工具链地狱------你想跑一个 TS 项目,先得搞清楚用哪个工具:
| 工具 | 方式 | 问题 |
|---|---|---|
tsc |
先编译再 node 跑 |
多一步,慢,还要管 dist 目录 |
ts-node |
JIT 编译 | 慢,类型检查默认跳过 |
tsx |
esbuild 编译后执行 | 快一些,但和源码行号对不上,调试痛苦 |
tsimp |
也是 JIT | 又一个选择,又多一个依赖 |
光选工具就够烦了。更别说 tsconfig.json 的 moduleResolution 到底是 node 还是 node16 还是 bundler,CJS 和 ESM 的互操作什么时候报错什么时候不报------Node.js 的模块系统是为 JS 设计的,和 TS 的类型系统天然有摩擦。
安装 Bun:一个命令搞定
在开始之前,先看怎么把 Bun 装到你的机器上。
macOS / Linux / WSL2:
bash
# 官方推荐方式
curl -fsSL https://bun.sh/install | bash
# 或者通过 npm 全局安装
npm install -g bun
# macOS 也可以走 Homebrew
brew install bun
Windows(实验性,目前走 WSL2 更稳):
powershell
powershell -c "irm bun.sh/install.ps1 | iex"
安装完验证:
bash
bun --version
# 1.2.x
到这里,你已经拥有了 Bun 的全部能力------运行时、包管理器、打包器、测试运行器,全在一个二进制文件里 。不需要像 Node.js 那样装完 Node 还要确认 npm 版本对不对、要不要换 yarn 或 pnpm。一个 bun 命令就是全部。
Bun:TypeScript 的"亲儿子"
Bun 2023 年发布 1.0,底层用 Zig 编写,JS 引擎用的是 WebKit 的 JavaScriptCore (Safari 同款)。但最关键的设计决策是:TypeScript 解析器直接内置在运行时里。
vbscript
你写的: server.ts → Bun 运行时(内置 TS 解析 + JavaScriptCore) → 直接执行
没有中间转译,没有临时 dist 目录,没有第三方转译器。你写的是 .ts,跑的就是 .ts。
技术架构差异
Bun 之所以能做到这一点,源于它和 Node.js 在设计上的根本分歧:
| 维度 | Node.js | Bun |
|---|---|---|
| 底层语言 | C++ | Zig |
| JS 引擎 | V8 (Chrome) | JavaScriptCore (Safari) |
| 事件循环 | libuv (C 库) | 自研 (Zig 原生) |
| TS 处理 | 依赖第三方工具(tsx/ts-node) | 运行时内置解析 |
| 包管理器 | npm / yarn / pnpm (独立工具) | 内置,二进制 lockfile |
| 模块解析 | CJS + ESM 双轨,规则复杂 | 统一解析,TS 路径别名直接认 |
Node.js 用 libuv 做事件循环是一个历史选择------libuv 是 C 写成的独立库,Node 通过 C++ 绑定去调用它。而 Bun 用 Zig 从头写了整套 I/O 栈,运行时内部每一层都是原生衔接的,没有跨语言边界的开销。
零配置跑 TS 意味着什么
bash
# Node.js 跑 TypeScript------先装依赖,再选工具,再跑
npm install -D typescript tsx @types/node
npx tsx server.ts
# Bun 跑 TypeScript------原生能力,零依赖,零配置
bun run server.ts
不是少敲几个字符的问题,而是 TS 文件被当作一等公民对待:
- 源码行号始终对上 :错误堆栈指向的是你写的
.ts文件,不是编译后的.js - 断点调试直接生效:打在哪一行就停在哪一行,不用 source map 碰运气
import路径不用纠结 :.ts、.js、无扩展名都行,路径别名(@/)直接从tsconfig.json读- 热重载是内置的 :
bun --watch直接监控.ts文件变更,不需要nodemon+tsx --watch叠床架屋
一个二进制文件,终结工具链地狱
Bun 的设计哲学是:一个二进制文件覆盖 TypeScript 开发者 80% 的工具需求。对比一下就知道少了多少依赖:
| 你要什么 | Node 方案 | Bun 方案 |
|---|---|---|
直接跑 .ts |
tsx / ts-node |
内置 |
跑 .tsx |
同上 | 内置 |
| 打包 TS 代码 | esbuild + 插件 / webpack + ts-loader |
内置 bun build |
| 写 TS 测试 | jest + ts-jest + @types/jest |
内置 bun test |
| 热重载 | nodemon + tsx --watch |
内置 bun --watch |
| Node 类型定义 | @types/node |
内置,不需要装 |
.env |
dotenv |
自动加载 |
| Web API (fetch, WebSocket) | polyfill 或 Node 18+ | 原生可用 |
| SQLite | better-sqlite3 (需 C++ 编译) |
bun:sqlite 内置模块 |
Bun 的一个二进制文件,干掉了你 devDependencies 里一半以上的包。每个少掉的依赖,都是少掉的安全漏洞面和更短的 npm install 时间。
Bun 就是包管理器,而 Node.js 还得装 npm
这是 Bun 和 Node.js 最根本的差异之一,但常被忽略:Bun 是一个一体化的工具,它自己就是包管理器;Node.js 只是一个运行时,管依赖还得另外靠 npm。
Node.js 的"分离式"模型
装完 Node.js 之后,你实际上得到的是两个东西:
Node.js 安装包
├── node ← 运行时(执行 JS 文件)
└── npm ← 包管理器(安装依赖)
npm 是 Node.js 的"附赠品",但它本质上是独立维护的独立工具。而且实践中你还得在 npm、yarn、pnpm 之间做选择:
| 包管理器 | 特点 |
|---|---|
| npm | Node 自带,最普及但也最慢,node_modules 扁平化经常出冲突 |
| yarn | Facebook 出品,比 npm 快,workspace 支持更好,但还是 JSON lockfile |
| pnpm | 用硬链接 + 符号链接节省磁盘,严格模式避免幽灵依赖 |
三个工具各管各的 lockfile、各管各的缓存目录、各管各的配置,团队里还得统一。然后你还需要 nvm / fnm 来管 Node 版本。
Bun 的"一体式"模型
装完 Bun,你得到的是一个二进制文件:
arduino
bun
├── 运行时 ← bun run
├── 包管理器 ← bun install / bun add / bun remove
├── 打包器 ← bun build
├── 测试器 ← bun test
└── 兼容层 ← bunx(替代 npx)
没有"我该用 npm 还是 yarn 还是 pnpm"的问题,bun install 就是唯一的包管理器,而且它更快、更省磁盘。
实际对比
bash
# ===== Node.js 新建 TS 项目 =====
# 第一步:装 Node(去官网下载安装包)
# 第二步:确认 npm 版本
npm --version
# 第三步:初始化项目
npm init -y
# 第四步:装 TS 开发依赖
npm install -D typescript tsx @types/node
# 第五步:跑代码
npx tsx index.ts
# ===== Bun 新建 TS 项目 =====
# 第一步:装 Bun(一个命令)
curl -fsSL https://bun.sh/install | bash
# 第二步:初始化 + 直接跑 TS,不需要装任何东西
bun init # 生成 package.json + tsconfig.json
bun run index.ts # 直接跑,零依赖
Bun 把 "运行时 + 包管理器 + 打包器 + 测试器" 全揉进一个二进制文件,这让开发环境的搭建从"装 Node → 选包管理器 → 装一堆 devDependencies"三步简化成"装 Bun"一步。
即使用 Node.js 跑生产,也值得用 bun install
bun install 生成的 node_modules 和 Node.js 完全兼容,所以你可以只换包管理器,不换运行时:
bash
rm -rf node_modules package-lock.json
bun install # 2 秒装完,生成 bun.lockb
# 然后照常用 Node 跑项目
node index.js
这对 CI 来说尤其值钱------安装步骤从 30 秒缩到 2 秒,而且每个 PR 都能省下这个时间。
bun install 为什么快?
- 二进制 lockfile (
bun.lockb):解析速度远超 JSON 文本格式的package-lock.json - 全局 SQLite 缓存:所有项目的重复依赖从全局缓存硬链接,不重复下载和存储
- 边下载边安装:不需要"全部下载完再解压",管道流式处理
性能:TS 编译不再是瓶颈
既然 TS 的中间转译步骤是 Node.js 绕不开的成本,Bun 把它内置了,差异就非常直观:
| 场景 | Node.js(tsx/esbuild) | Bun | 差距 |
|---|---|---|---|
| TS 文件启动 | ~85ms | ~12ms | 7× |
| TSX 组件编译 | ~2.4s | ~0.3s | 8× |
| 大型 TS 项目冷启动 | ~3s | ~0.4s | 7.5× |
| 跑一套 TS 单元测试 | ~12s | ~2s | 6× |
| 包安装 (1000 依赖) | ~28s (npm) | ~1.8s | 15× |
数据来源:Bun 官方 benchmark + 社区独立测试(2025),具体数值因环境而异,但数量级一致。
这些不是理论峰值数字,是日常开发每次保存文件、每次跑测试、每次 CI 跑 pipeline 都在发生的等待。TS 项目越大,Bun 的体验优势越明显。
还有一个隐藏优势:内存占用。Bun 空闲状态的 RSS 内存通常比 Node.js 低 30-50%。Zig 的零成本抽象加上 JavaScriptCore 更积极的内存回收策略,让 Bun 在大型 monorepo 的 TS 项目中表现尤其稳定。
企业现实:TS 跑得好 ≠ 生产敢全量上
Bun 对 TypeScript 的友好是毋庸置疑的,但企业级采用率仍然很低:
scss
JS 后端运行时市场份额(2025 估算)
Node.js (V8) ████████████████████████████████ ~95%
Deno (V8) ██ ~3%
Bun (JSCore) █ ~2%
阻碍不在于 Bun 技术不好,而在于:
一、C++ 原生模块不兼容
Node.js 生态中大量底层包依赖 node-gyp 编译成 V8 ABI 的 C++ 模块。经典例子如 sharp(图片处理)、node-canvas(Canvas 渲染)、bcrypt 的 C++ 版本------这些模块直接绑定 V8 内部数据结构,在 JavaScriptCore 上完全跑不起来。如果你项目里挂了这些,Bun 就不适合。
二、Windows 原生支持仍在实验阶段
多数企业开发机是 Windows。Bun 的 Windows 原生版至今标记为 Experimental,推荐用户走 WSL2。这对 Windows-first 的团队来说是一道实打实的门槛。
三、云平台绑定 V8
Docker 镜像、AWS Lambda、Vercel、Cloudflare Workers------云平台的 Node runtime 以 V8 为标准。这一层的生态惯性很强。
务实的分场景推荐
| 你的情况 | 推荐 |
|---|---|
| TS 项目开发环境 | ✅ Bun --- 启动快、零配置、调试体验好,开发效率提升明显 |
| TS 脚本 / 工具链 | ✅ Bun --- bun run 替代 tsx,bun build 替代 esbuild |
| CI 中的包安装 | ✅ 白嫖 bun install --- 速度提升立竿见影,不换运行时也能用 |
| 纯 TS/JS 新项目 | ✅ 可以上生产 --- 没有原生模块依赖的话基本无缝 |
| 重度依赖 C++ 原生模块 | ❌ 不适合 --- 老老实实 Node.js |
| 企业遗留系统 | ❌ 别动 --- 稳定性压倒速度 |
最简单的策略:运行时继续用 Node.js,把 Bun 当开发工具链用。 bun install、bun run、bun test 都能无缝配合 Node 的 node_modules,风险和收益完全不对等。
结语
Node.js 是一座伟大的桥梁,十几年来它证明了 JavaScript 可以跑在服务器上。但它从来不是为 TypeScript 设计的------TS 在 Node 生态里,永远是通过转译器"寄生"在 JS 引擎上的二等公民。
Bun 做了 Node.js 没做的事:第一次让 TypeScript 拥有了属于自己的运行时。TS 文件不再是等待转译的中间产物,而是可以直接执行的源代码。同时它也是一个自带的包管理器,告别了"选 npm 还是 yarn 还是 pnpm"的选择困难。
全量替代生产环境里的 Node.js?现在还太早。 但在开发工具链上用 Bun 替换那堆转译器和依赖?立刻就能省下大量等待时间和维护成本。
如果你写 TypeScript,花 30 分钟试试。你可能会发现自己再也忍不了 Node.js 的工具链地狱。
延伸阅读:Bun 官方文档 · Bun GitHub