Bun 和 ts-node 怎么选?TypeScript 执行方案对比
ts-node 是老牌 TypeScript 执行器,Bun 是 Zig 编写的新一代运行时。本文从执行速度、watch 模式、ESM 兼容、REPL、测试与配置复杂度全面对比,给出 2026 年的明确建议。
2026 年的新项目没有理由再选 ts-node——Bun 或 tsx 的速度快几个数量级,且 ESM 兼容性问题更少。ts-node 只剩一个合理场景:维护历史项目,且其 "依赖官方 tsc 编译、类型错误即拒绝执行" 的严格模式被团队视为特性。
ts-node:曾经的唯一答案
ts-node 的价值是在"Node 不认 TypeScript"的年代提供直接执行能力:内部调用官方 TypeScript 编译器转译后执行,默认连带类型检查——类型有错拒绝运行。这在当年是安全保障,在今天看则是性能枷锁:每次启动都要过一遍 tsc,大项目冷启动以秒计甚至十秒计。
Bun:把 TS 执行变成零成本
Bun 用 Zig 内置转译器,TS 文件的执行与 JS 完全同速:bun app.ts 即开即跑,watch 模式(bun --hot)响应毫秒级。代价是不做类型检查——类型安全交给编辑器和 CI 里的 tsc --noEmit(这也是现代工具链 tsx、esbuild、swc 的共同选择)。
关键维度对比
| 维度 | ts-node | Bun |
|---|---|---|
| 启动速度 | 慢(tsc 编译) | 极快(Zig 转译) |
| 类型检查 | 内置(可选 transpileOnly 关闭) | 无(交给 tsc/编辑器) |
| ESM 支持 | 历史包袱重(loader 配置繁琐) | 原生一等支持 |
| 运行时 | 仍是 Node.js | Bun 独立运行时 |
| 附加能力 | 无 | 包管理/打包/测试全家桶 |
ESM 兼容性:ts-node 的最大痛点
ts-node 在 CommonJS 项目里工作良好,但 ESM 项目("type": "module")的配置堪称社区著名难题:--loader ts-node/esm、路径扩展名、tsconfig 的 module 设置三者必须精确配合,报错信息晦涩。Bun 对 ESM/CJS 混用无感,import 和 require 随意混写——这是迁移到 Bun 后最直观的解脱。
测试与 REPL
ts-node 的测试依赖外部框架(Jest/Mocha + 各自 TS 配置);Bun 内置 bun test(Jest 兼容 API),TS 测试文件直接跑,速度提升显著。REPL 方面 bun repl 原生支持 TS;ts-node 的 REPL 在 ESM 模式下问题多发。
迁移建议
ts-node 项目的低风险迁移路径:先把 ts-node 换成 tsx(Node 运行时不变,ESM 问题消失,速度立升),稳定后再评估是否迁往 Bun 运行时。直接跳到 Bun 也可行,但要完整回归测试——运行时从 Node 换成 Bun 是质变。
常见问题(FAQ)
Q:ts-node 的 transpileOnly 模式和 Bun 有何不同?
--transpileOnly 跳过类型检查后,ts-node 的瓶颈仍在 tsc 转译本身(慢);Bun 用 Zig 转译器,同样的"只转译不检查"快几个数量级。模式相同,实现代差。
Q:生产环境能用 Bun 跑 TypeScript 吗?
可以且是 Bun 的设计场景(开发生产同一运行时)。注意评估:Node 特有 API 的兼容性、监控探针对 Bun 的支持度、团队运维熟悉度。保守方案是构建期用 Bun、运行期用 Node。
Q:ts-node 项目迁 Bun 的主要工作量?
三处:package.json 脚本改为 bun 命令;tsconfig 面向 Bun 微调(module 解析);验证依赖中 Node 内部 API 的使用(require('node:_http_...') 这类内部模块在 Bun 下不可用)。
参考文档
本文依据公开文档整理,示例需按项目依赖版本验证;不将文档中的性能描述视为本项目实测结果。