Bun vs Node.js:谁才是 TypeScript 的"亲爹"?

15 阅读9分钟

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 生态里"强行嫁接"上去的。十几年过去,这个寄生关系一直没变:

你写的:   server.ts
            │
     ┌──────┴──────┐
     │  ts-node    │  或  tsx / tsimp / tsc + node ...
     │  先编译成 JS  │
     └──────┬──────┘
            ▼
          server.js  →  V8 引擎  →  执行

结果就是工具链地狱——你想跑一个 TS 项目,先得搞清楚用哪个工具:

工具方式问题
tsc先编译再 node多一步,慢,还要管 dist 目录
ts-nodeJIT 编译慢,类型检查默认跳过
tsxesbuild 编译后执行快一些,但和源码行号对不上,调试痛苦
tsimp也是 JIT又一个选择,又多一个依赖

光选工具就够烦了。更别说 tsconfig.jsonmoduleResolution 到底是 node 还是 node16 还是 bundler,CJS 和 ESM 的互操作什么时候报错什么时候不报——Node.js 的模块系统是为 JS 设计的,和 TS 的类型系统天然有摩擦


安装 Bun:一个命令搞定

在开始之前,先看怎么把 Bun 装到你的机器上。

macOS / Linux / WSL2:

# 官方推荐方式
curl -fsSL https://bun.sh/install | bash

# 或者通过 npm 全局安装
npm install -g bun

# macOS 也可以走 Homebrew
brew install bun

Windows(实验性,目前走 WSL2 更稳):

powershell -c "irm bun.sh/install.ps1 | iex"

安装完验证:

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 解析器直接内置在运行时里

你写的:   server.ts  →  Bun 运行时(内置 TS 解析 + JavaScriptCore) →  直接执行

没有中间转译,没有临时 dist 目录,没有第三方转译器。你写的是 .ts,跑的就是 .ts

技术架构差异

Bun 之所以能做到这一点,源于它和 Node.js 在设计上的根本分歧:

维度Node.jsBun
底层语言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 意味着什么

# 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 方案
直接跑 .tstsx / 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内置,不需要装
.envdotenv自动加载
Web API (fetch, WebSocket)polyfill 或 Node 18+原生可用
SQLitebetter-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 之间做选择:

包管理器特点
npmNode 自带,最普及但也最慢,node_modules 扁平化经常出冲突
yarnFacebook 出品,比 npm 快,workspace 支持更好,但还是 JSON lockfile
pnpm用硬链接 + 符号链接节省磁盘,严格模式避免幽灵依赖

三个工具各管各的 lockfile、各管各的缓存目录、各管各的配置,团队里还得统一。然后你还需要 nvm / fnm 来管 Node 版本。

Bun 的"一体式"模型

装完 Bun,你得到的是一个二进制文件:

bun
├── 运行时     ← bun run
├── 包管理器   ← bun install / bun add / bun remove
├── 打包器     ← bun build
├── 测试器     ← bun test
└── 兼容层     ← bunx(替代 npx)

没有"我该用 npm 还是 yarn 还是 pnpm"的问题,bun install 就是唯一的包管理器,而且它更快、更省磁盘。

实际对比

# ===== 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 完全兼容,所以你可以只换包管理器,不换运行时

rm -rf node_modules package-lock.json
bun install    # 2 秒装完,生成 bun.lockb

# 然后照常用 Node 跑项目
node index.js

这对 CI 来说尤其值钱——安装步骤从 30 秒缩到 2 秒,而且每个 PR 都能省下这个时间。

bun install 为什么快?

  1. 二进制 lockfilebun.lockb):解析速度远超 JSON 文本格式的 package-lock.json
  2. 全局 SQLite 缓存:所有项目的重复依赖从全局缓存硬链接,不重复下载和存储
  3. 边下载边安装:不需要"全部下载完再解压",管道流式处理

性能:TS 编译不再是瓶颈

既然 TS 的中间转译步骤是 Node.js 绕不开的成本,Bun 把它内置了,差异就非常直观:

场景Node.js(tsx/esbuild)Bun差距
TS 文件启动~85ms~12ms
TSX 组件编译~2.4s~0.3s
大型 TS 项目冷启动~3s~0.4s7.5×
跑一套 TS 单元测试~12s~2s
包安装 (1000 依赖)~28s (npm)~1.8s15×

数据来源:Bun 官方 benchmark + 社区独立测试(2025),具体数值因环境而异,但数量级一致。

这些不是理论峰值数字,是日常开发每次保存文件、每次跑测试、每次 CI 跑 pipeline 都在发生的等待。TS 项目越大,Bun 的体验优势越明显。

还有一个隐藏优势:内存占用。Bun 空闲状态的 RSS 内存通常比 Node.js 低 30-50%。Zig 的零成本抽象加上 JavaScriptCore 更积极的内存回收策略,让 Bun 在大型 monorepo 的 TS 项目中表现尤其稳定。


企业现实:TS 跑得好 ≠ 生产敢全量上

Bun 对 TypeScript 的友好是毋庸置疑的,但企业级采用率仍然很低:

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 替代 tsxbun build 替代 esbuild
CI 中的包安装✅ 白嫖 bun install — 速度提升立竿见影,不换运行时也能用
纯 TS/JS 新项目✅ 可以上生产 — 没有原生模块依赖的话基本无缝
重度依赖 C++ 原生模块❌ 不适合 — 老老实实 Node.js
企业遗留系统❌ 别动 — 稳定性压倒速度

最简单的策略:运行时继续用 Node.js,把 Bun 当开发工具链用。 bun installbun runbun 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