
这几年,JavaScript 运行时的叙事一直很热。
Bun 把“快”和“一体化工具链”讲得很漂亮:运行 TypeScript、装包、跑脚本、打包、测试,一个 bun 命令就能接住。Deno 则从另一条路重做运行时:安全默认值、TypeScript-first、内置工具链、现代模块系统。
看起来,Node.js 像是那个应该被替换的旧世界。
但真实项目里,很多团队并没有那么自由。服务跑在 Node 上,CI/CD 是 Node,历史脚本是 Node,npm 生态是 Node,线上排障经验也是 Node。你当然可以在新项目里尝试 Bun 或 Deno,但一个几年沉淀下来的业务系统,往往不能只靠“新工具更快”就迁过去。
所以真正的问题不是:
要不要干掉 Node?
更现实的问题是:
能不能继续用 Node,但把现代开发体验补上来?
最近开源的 Nub,切的正是这个口子。
Nub 是什么:一个站在 Node 上面的工具包
Nub 的作者是 Colin McDonnell,也就是很多人熟悉的 colinhacks,他还是 Zod、tRPC 等项目背后的核心作者之一。
从 GitHub README 的定位看,Nub 不是一个新的 JavaScript 运行时,而是一个 “all-in-one Node.js toolkit”。截至 2026-06-19,我通过 GitHub API 查到 nubjs/nub 仓库创建于 2026-06-03,主语言是 Rust,最新 release 已到 v0.1.4,仓库描述就是:The all-in-one Node.js toolkit。
这几个信息放在一起,其实很能说明它的野心:
它想做的不是“另一个 Node 替代品”,而是“Node.js Plus”。
以前一个普通 Node 项目想要舒服地开发 TypeScript,往往会叠一堆工具:
| 你想要的能力 | 常见工具 |
|---|---|
| 直接跑 TypeScript / TSX | tsx、ts-node |
| 自动加载环境变量 | dotenv |
| 文件变更自动重启 | nodemon、node --watch |
支持 tsconfig paths | tsconfig-paths |
| 临时执行 CLI | npx、pnpm dlx |
| 管理 Node 版本 | nvm、fnm、volta |
| 管理包管理器版本 | corepack |
这些工具都能用,但它们散落在不同层:有的挂在运行时前面,有的挂在脚本里,有的靠环境变量,有的靠编辑器和打包器理解。项目越久,这些小工具越容易变成一层层“隐形胶水”。
Nub 的思路很直接:把这些胶水收进一个入口。
nub index.ts
nub run dev
nubx prisma generate
nub install
nub watch src/server.ts
nub node install 22
这就是 Nub 最容易被误解的地方。
它看起来像 Bun,因为它也是一个命令接住很多事;但它底下跑的仍然是你项目里的 Node。
它不是魔法,而是把 Node 的扩展面用满
很多新工具一说“更快、更现代”,开发者第一反应会是:是不是又造了一个运行时?是不是又要迁移一堆兼容问题?
Nub 比较聪明的地方在于,它没有换掉 Node。
你执行:
nub app.ts
Nub 这个 Rust CLI 会先做几件事:找到当前项目该用哪个 Node,读取附近的 package.json、tsconfig.json、.env 等配置,然后启动真实的 Node 进程。
关键在启动方式上。
Nub 会通过 --import 注入自己的 preload 脚本,再利用 Node 的模块自定义能力,把 resolve 和 load 这类环节接进来。Node 官方文档里,module.registerHooks() 就是用来注册模块解析和加载钩子的 API;在 Node 22.15+ 之后,它走的是同步、同线程的 fast path。
换句话说,Nub 不是篡改 Node,而是在 Node 开放出来的入口上做增强。
当你的代码里 import 一个 TypeScript、JSX、YAML、TOML、JSONC 文件时,请求会先经过 Nub 的 load hook。Nub 负责把它转成 Node 能执行的东西,再交还给 Node。
TypeScript 这块尤其关键。
Node 自己已经内置了 TypeScript type stripping,但官方文档也写得很清楚:它的目标是轻量,不支持需要生成 JavaScript 的语法,比如 enum、带运行时代码的 namespace、参数属性、decorators,也不读取 tsconfig.json,不处理 paths。
Nub 要补的正是这段空白。
它通过 N-API addon 调用 oxc 做转译,所以能覆盖更完整的 TypeScript 表面:enum、namespace、参数属性、legacy decorators、TSX 等都可以被处理。对真实业务项目来说,这比“能不能把类型擦掉”更重要,因为很多老项目恰恰用到了这些不那么轻量的 TypeScript 特性。
它增强的是整条开发链路
如果 Nub 只是一个新的 tsx,那它就没那么值得写。
它真正有意思的地方,是把 Node 项目开发时的多个断点串了起来。
第一个是路径解析。
很多项目会在 tsconfig.json 里写:
{
"compilerOptions": {
"paths": {
"@/*": ["src/*"]
}
}
}
编辑器认识,TypeScript 认识,打包器认识,但原生 Node 不一定认识。于是你又要靠额外 loader、运行时补丁,或者在构建阶段重写路径。
Nub 的做法是把 tsconfig paths 接到 Node resolver 上。这样它不只是“编译 TypeScript”,而是在运行时解析模块时就理解项目规则。
第二个是 .env。
很多项目启动前都要先接 dotenv/config,或者在命令里写一堆环境变量加载逻辑。Nub 会按类似 Vite / Bun 的习惯,在启动前自动加载 .env*,并支持变量展开。对开发体验来说,这属于小事,但小事多了就是摩擦。
第三个是 watch。
很多 TypeScript runner 最大的问题不是能不能跑,而是 watch 到底准不准。文件实际是内存转译的,Node 看到的可能不是原始 .ts 文件,变更触发就容易不完整。
Nub 的设计是借用 Node 原生 --watch,同时通过 preload 把 load hook 实际碰过的依赖文件报告给 watcher。这样 watch 不是靠你维护一组 glob,也不是凭空猜目录,而是跟着真实依赖图走。
第四个是子进程。
Node 项目里的脚本经常会再启动 node,比如某个 CLI 内部又 spawn 一个 Node 进程。如果外层增强,内层回到原生 Node,就会出现“主命令能跑,子命令又坏了”的体验断层。
Nub 用 PATH shim 处理这件事,让脚本里再次启动的 node 也能继承同一套增强能力。这个点很工程化,听起来不炫,但它决定了一个工具能不能在复杂项目里站住。
快的是工具链,不是你的业务代码
这里一定要把预期说清楚。
Nub 不会让你的业务接口突然快 10 倍。
因为你的业务代码最后还是跑在 Node 上,V8 还是那个 V8,Node 的 I/O 模型也还是那个模型。Nub 不替换 JavaScript 引擎,也不把你的服务变成 Bun。
它快的地方主要在工具链外层。
官方 README 里给了几组很醒目的数据:
| 命令 | Nub 对标对象 | 官方描述 |
|---|---|---|
nub run | npm run / pnpm run | 冷路径可比 pnpm run 快约 24 倍 |
nubx | npx / pnpm dlx | 可比 npx 轻约 19 倍 |
nub install | 包管理安装 | README 提到使用 pnpm-shaped install engine |
为什么这里能快?
原因并不神秘。npm、pnpm、npx 本身也是 Node 程序。你执行一个脚本,外面先起一层 JavaScript CLI,CLI 再解析参数、读配置、找 bin、改 PATH、再启动你的脚本。
Nub 把很多调度逻辑放进 Rust CLI:找脚本、找二进制、找 Node、找包管理器、准备环境。这样少掉了不少 wrapper 启动开销。对大型服务的请求性能来说,这不算什么;但对本地开发里高频执行的 run、x、watch、install 来说,体感会很明显。
这也是 Nub 和 Bun 的一个重要差别。
Bun 的快,包含运行时、包管理器、打包器、测试器等一整套新栈。Nub 的快,更像是把 Node 项目前面的“启动和调度层”换成更轻的实现。
它对安全和兼容也有自己的判断
Nub 还有一个容易被忽略的点:它默认收紧了安装阶段的安全策略。
README 里提到,依赖的 build scripts 是 deny-by-default。也就是说,包安装时不是所有 postinstall 都可以默认执行,只有显式允许、可信依赖配置或满足某些信任条件时才会运行。
这件事放在今天的 npm 生态里很有意义。
过去几年,供应链攻击已经不是什么理论风险。安装依赖时自动执行脚本,体验上很方便,但安全边界很松。Nub 把这件事默认收紧,说明它不是只在卷性能,也在重新设计 Node 项目的默认开发边界。
兼容上,Nub 也没有强迫你进入一套全新格式。
它仍然围绕 package.json、现有 lockfile、项目已有 Node 版本约定工作。包安装部分是 pnpm-shaped,README 也强调 npm、pnpm、Bun lockfile 可以 round-trip,Yarn 目前是 read-only。
这对老项目很重要。真正能落地的工具,往往不是让你立刻迁移所有东西,而是允许你一点点接入。
为什么现在会有 Nub 这样的工具?
把 Nub 放到更大的趋势里看,它其实回答了一个过去两年越来越明显的问题:
Node 生态已经足够大,但 Node 的默认开发体验仍然偏“基础设施裸露”。
你想写 TypeScript,要选 runner;想跑脚本,要靠 npm/pnpm/yarn;想管理 Node,要装版本管理器;想统一包管理器,要靠 Corepack;想 watch,要补工具;想加载 .env,又是一层配置。
这些东西单看都能解决,但组合起来就很碎。
Bun 和 Deno 的流行,某种程度上就是开发者在表达一个需求:
我不想再为每个项目重新拼一遍工具链。
但 Bun / Deno 的答案是“换一个更现代的运行时入口”。Nub 的答案是“运行时继续用 Node,我把入口现代化”。
这就是它值得关注的原因。
它不是在和 Node 对着干,而是在承认 Node 的现实统治力之后,做了一层增强。对很多团队来说,这比“彻底迁移”更现实。
什么时候适合试 Nub?
我会把适用场景分成三类。
| 场景 | 是否适合 |
|---|---|
| 新 Node 项目,想少装一堆开发工具 | 很适合试 |
| 老项目 TS 配置复杂,但不想切 Bun/Deno | 值得小范围试 |
| CI 里大量跑 npm/pnpm scripts,冷启动开销明显 | 值得压测 |
| 对 install scripts 有供应链安全顾虑 | 值得关注 |
| 生产运行时要求极度稳定,不能接受新工具风险 | 先别急着全量切 |
| 依赖 Yarn 复杂能力或私有包策略很重 | 需要先验证兼容 |
最稳妥的试法,不是马上把项目全迁到 Nub,而是从低风险命令开始:
nub run test
nub run lint
nubx eslint .
nub watch src/server.ts
先看脚本行为是否一致,再看本地开发体验是否真的更顺。如果只是为了追热点,把核心 CI 和生产启动方式一次性切过去,反而不符合 Nub “增强层”的定位。
最后:Node 不一定要被替代,它也可以被增强
过去我们讨论 JavaScript 运行时,常常习惯用“谁替代谁”的视角。
Bun 会不会替代 Node?Deno 会不会替代 Node?Node 会不会落后?
但 Nub 给了一个更务实的答案:
有些系统确实适合换新运行时;有些系统更适合继续站在 Node 上,把开发链路一点点补齐。
对已经离不开 Node 的团队来说,这可能是一条很有吸引力的中间路线。你不用放弃 npm 生态,不用重写部署环境,不用把所有历史脚本迁到一个新世界;你只是把 node 前面的那层开发入口换掉,让 TypeScript、脚本、watch、env、paths、Node 版本和包管理变得更像一个整体。
所以我觉得 Nub 真正有意思的地方,不是“它比 Node 更快”,而是它重新定义了一个问题:
既然 Node 还会长期存在,我们能不能把 Node 用得更像 2026 年的工具?
如果这个方向跑通,JavaScript 工具链接下来的竞争就不只是“谁有新的 runtime”,还会是“谁能把已有 runtime 的开发体验补得更完整”。
这对 Node 来说,不是坏消息。
这反而说明:Node 生态太重了,重到没人能轻易绕开;也太重要了,重要到值得有人专门给它做一个 Plus 版本。
参考资料
- Nub GitHub 仓库:nubjs/nub
- Nub 文档:nubjs.com/docs
- Node.js 文档:
module.registerHooks() - Node.js 文档:Modules: TypeScript
- Bun 文档:Welcome to Bun
- Deno 文档:Get started with Deno