Nub 是什么?

Nub 是一个由 Node.js 驱动的全能工具包,现代化 Node.js 生态系统中的开发体验。用它来运行文件、package.json 脚本和本地安装的 CLI。用它替代 nodenpm runnpx(或你偏好的包管理器中的等价物)。

$ npm install -g --ignore-scripts=false @nubjs/nub
$ nub index.ts             # 运行一个 TypeScript 文件
$ nub run dev              # 运行一个 package.json 脚本
$ nubx prisma generate     # 运行 node_modules/.bin 中的 CLI

它是一个运行在原生 Node 上的 Rust CLI——提供你项目锁定的版本,或当没有锁定时使用 PATH 上的版本——并通过 Node 自身的公共扩展接口增强它——module.registerHooks()--import 预加载、NODE_OPTIONS、N-API 插件。它不会替换你的运行时、包管理器或编辑器。

它是一个新的运行时吗?

不是。Nub 运行原生 Node 之上。没有 Nub 运行时、没有 Nub JavaScript 引擎、没有单独的兼容性层。当 nub index.ts 运行时,实际执行的是原生 node 二进制文件——你项目锁定的版本,如果缺失 Nub 会从 nodejs.org 提供;当没有锁定时使用 PATH 上的版本;Nub 启动它、注册 TypeScript / JSX / YAML / TOML 的加载钩子、在 Node 落后时预加载 polyfill,然后退到一边。参见底层是如何工作的?了解机制层面的答案。

它是 Node 的分支吗?

不是。Nub 不修改 Node 源码、不发布自定义构建的 Node 二进制文件、也不嵌入 libnode。每一项增强都基于 Node 的公共扩展接口。

Nub 是 Node 的替代品吗?

不是——Nub 就是 Node。你的代码由原生 node 二进制文件转译和执行;没有需要发现的 Nub 运行时,也没有重新实现。Nub 是 Node 之上的一层开发体验默认设置,完全构建在 Node 自身的扩展接口上。

它和原生 node 有什么不同?

运行 nub <file>node <file> 逐旗兼容——相同的 argv 形状、相同的标志集、相同的行为——在此基础上还添加了:

  • TypeScript 优先 — 完整的 TS 支持,不仅仅是类型擦除(枚举、命名空间、参数属性、装饰器、import = / export =、无扩展名 .ts 导入、内联 source map)
  • 尊重 tsconfig.jsonpathsbaseUrlextends 链、jsx 在运行时应用
  • JSX 支持.jsx / .tsx 文件直接执行,运行时来源为 compilerOptions.jsx / jsxImportSource
  • 环境文件.env.env.[mode](模式来自 APP_ENV)以 Vite 兼容的优先级预加载
  • YAML / TOML / JSON5 / JSONC / 文本加载器import config from "./config.yaml" 的工作方式与 import data from "./data.json" 相同
  • 自动 polyfillTemporalURLPattern、浏览器形态 Worker、WinterTC 差距全局变量,按版本检测和功能门控
  • 取消实验性标志EventSourceWebSocketlocalStorage / sessionStoragevm.Modulenode:sqlite — Node 已内置但仍保持门控的标志,在每个版本中仍需 --experimental-* 的精确版本带中自动注入

当你输入 nub 而非 node 时,所有这些都免费获得。要获得原生 Node 行为,输入 node——PATH 垫片是每次调用独立的,所以 shell 中的 node 始终是用户的真实 Node。对于不需要运行时增强的编排,nub runnubx 上的 --node 标志会剥离钩子/预加载/取消标志,同时保留 Nub 的 CLI 工作。

电梯演讲是什么?

如果你过去几年一直在用 tsxdotenv-clinodemonnpxpnpm run 加上一堆 tsconfig 加载器垫片来组装个人工具链——Nub 用一个毫秒级启动的二进制文件取代了那堆东西,而且不要求你更换运行时。保留 Node,替换工具链。

它比 Node.js 更快吗?

不是——你的代码在 Node 本身中运行,所以一旦开始执行,V8 就是 V8。显著更快的是 CLI 层面,因为原生 Rust 二进制文件跳过了那些工具为了做脚本查找而支付的完整 Node 启动:

  • nub run 在冷启动时比 pnpm run 快 24 倍。
  • nubx 在冷启动时比 npx 快 19 倍。

转译输出落入一个以 (源码内容, Nub 版本, 解析的 tsconfig) 为键的内容寻址磁盘缓存,所以第一次运行会转译,之后的每次运行几乎不支付任何开销。

我现有的 Node 代码能在 Nub 上运行吗?

能。兼容性层面就是 Node 的。如果一个包能在 Node 上运行,它就能在 Nub 上运行——因为底下实际运行的正是 Node。

契约

面向 Node 的代码在 Nub 上逐字节运行。如果你遇到不一致的情况,那是一个 bug。

CommonJS / require() 呢?

可以。CommonJS、require()、动态 require()require.cachemodule.exports、ESM 和 CJS 形式的 node: 核心模块——全部可以,因为底下运行的就是 Node。Nub 的加载钩子在 ESM 和 CJS 入口点等价地覆盖了 TypeScript / JSX / YAML / TOML。

原生(N-API)插件呢?

可以。原生插件针对 Node 的 N-API ABI 编译,而 Nub 运行在原生 Node 二进制文件上,所以插件的加载方式与原生 Node 完全相同。(Nub 自身将 oxc 转译器作为 N-API 插件提供;该路径与你 better-sqlite3@napi-rs/canvas 使用的路径相同。)

它支持所有 Node 核心模块吗?

是的。每个 node: 核心模块——node:fsnode:httpnode:testnode:worker_threadsnode:sqlitenode:crypto,所有这些——都可以,因为它们由原生 Node 二进制文件提供,而不是由 Nub 提供。

它支持哪些 Node 版本?

  • 增强模式需要 Node 18.19+(Node 18 LTS)——Nub 用于转译导入路径的加载器钩子 API 的最低版本。
  • 兼容模式--node 标志)在任何 Node 版本上尽力而为。这里 Nub 是一个纯编排器(脚本解析、工作区 bin PATH、环境变量注入),启动原生 Node 而不进行运行时增强。对于临时的原生 Node 行为,直接输入 node

低于最低版本

在低于 18.19 的 Node 上,增强命令会发出标记错误,告诉你升级 Node 或使用兼容模式。

它能在 Linux / macOS / Windows 上运行吗?

是的——Linux(x64、arm64)、macOS(x64、arm64)、Windows(x64)。与底层 Node + napi-rs 生态系统已目标的平台相同。Nub 通过 npm install -g --ignore-scripts=false @nubjs/nub 以预构建的 Rust 二进制文件分发,安装时解析平台特定的 N-API 插件。

如何在 CI / GitHub Actions 中使用?

actions/setup-node 替换为 nubjs/setup-nub

- uses: actions/setup-node@v4
- uses: nubjs/setup-nub@v0

该 action 将 Nub CLI 安装到 runner 上,然后 Nub 自身提供项目锁定的 Node。它还预提供一个预热 Node 版本并跨运行缓存 Nub 的存储。参见 GitHub Action页面了解完整的输入接口。

它会替换我的包管理器吗?

可以。nub install 命令是一个完整的包管理器——pnpm 的 CLI 接口、你项目的锁文件——由内嵌的 aube 引擎驱动。它检测你项目已有的锁文件并以相同格式写回,所以不强制迁移:pnpm、npm 和 Bun 可以并行工作,nub pm use 在其确切锁定版本上提供原始工具(corepack 的工作,不需要 PATH 垫片)。

锁文件行为
pnpm-lock.yaml原地往返
package-lock.json原地往返
bun.lock原地往返
yarn.lock只读——会重写它的安装会被拒绝,并给出确切要运行的 yarn 命令

它能与 pnpm / npm / yarn / bun 一起工作吗?

是的。所有四个都像没有 Nub 一样继续工作。你的锁文件保持你包管理器生成的格式,工作区拓扑(pnpm-workspace.yamlnpm 工作区、yarn 工作区)由 nub runnubx 直接识别。事实来源:

  • nub run 读取 package.json 及其 "scripts" 字段。
  • nubx 读取 node_modules/.bin

Yarn Berry Plug'n'Play

PnP 没有 node_modules/.bin/——只有一个由 Yarn 加载器管理的运行时解析表。Nub 仍然处理它:运行时检测到 .pnp.cjs 并通过它运行项目,nubx 通过 pnpapi 解析 PnP bin(与 yarn exec 相同的方式)。参见模块解析

它对 monorepo 友好吗?

是的。工作区拓扑来自 package.json"workspaces" 字段(npmyarn)和 pnpm-workspace.yamlpnpm),使用每个工作区用户从 pnpm 已知的 -r / --filter 语义:

nub run -r build                        # 所有工作区包
nub run --filter @org/api dev           # 按名称选择一个包
nub run --filter "./packages/*" test    # glob 匹配
nub run --filter "@org/web..." build    # @org/web 及其依赖

拓扑排序、并行和 --workspace-root 被识别。关键在于什么被识别:每个包约 150ms 的 pnpm 启动开销。在一个 30 包的 monorepo 中,pnpm -r run build 在任何代码运行之前支付 30 次启动开销;nub run -r build 支付零次。

它会替换 npx 吗?

是的——nubx(也可用作 nub exec)是等价物,遵循与 npxpnpm dlx 相同的本地优先然后 DLX 模型,没有交互式提示:

  • 本地 binnubx 向上遍历目录树检查每个层级的 node_modules/.bin/(标准解析算法),用 Rust,在单位数毫秒内返回。
  • 未安装nubx 从注册表获取二进制文件并即时运行。Nub 是一个完整的包管理器,所以它自己做获取而不是 shell 出到外部 PM。

它会替换 tsx / ts-node 吗?

是的——nub script.ts 直接运行 TypeScript,具有完整的 TS 支持(不仅仅是类型擦除),尊重你的 tsconfig.json,带有内联 source map 使堆栈跟踪指向你的 .ts 源码。完整详情:运行时 → TypeScript

为什么不直接用 Node 内置的 TypeScript 支持?

Node 22.18+ 默认运行 .ts 文件,但它擦除类型而非转译——所以任何超出可擦除注解的内容都自力更生:

  • enumnamespace 和参数属性 → 语法错误(Node 26 甚至移除了 --experimental-transform-types,曾经处理它们的标志)
  • JSX 和 emitDecoratorMetadata → 不支持
  • tsconfig.jsonpathsbaseUrljsxexperimentalDecoratorsextends → 被忽略("故意不支持",根据 Node 26 文档)
  • 无扩展名 .ts 导入 → 无法解析,且没有 source map

Nub 通过其原生插件转译每个文件,所以这些都正常工作。如果你的代码库是 erasableSyntaxOnly 干净的,Node 内置支持就够了;如果不是,你需要一个真正的转译管道——这正是 Nub 提供而 Node 刻意不提供的。

它会替换 nodemon / tsx watch 吗?

对于变更时重启,是的:nub watch script.ts(或 nub --watch script.ts)在解析的依赖图(加上你的 .env* 文件和 tsconfig.json)中的任何内容发生变化时重启进程——无需维护 glob 列表。(脚本名之后--watch——nub run dev --watch——像其他参数一样转发给脚本本身。)完整详情:监听模式

dotenv-cli 呢?

不需要。Nub 自动加载你的环境文件——使用 Vite/Bun 风格的优先级规则——并在 Node 启动之前将它们注入进程环境:

.env.[mode].local
.env.local
.env.[mode]
.env

[mode] 来自 APP_ENV——不是 NODE_ENV。要直接加载特定文件,传递 --env-file(可重复)。支持 ${VAR}$VAR 展开,包括嵌套引用和循环检测。

测试模式

在测试模式下(APP_ENV=test),.env.local 被跳过,遵循 Next.js 约定——本地密钥不应泄漏到你的测试套件中。

完整详情:运行时 → 环境变量

底层是如何工作的?

Nub 是一个 Rust CLI。当你输入 nub script.ts 时,Rust 二进制文件会:

  1. 解析你项目锁定的 Node 版本——devEngines.runtime.node-version.nvmrcengines.node——如果缺失则从 nodejs.org 提供,当没有锁定时回退到 PATH 上的 node
  2. 读取你最近的 package.jsontsconfig.json.env* 文件。
  3. 启动该 Node,设置指向 Nub 预加载包的 --import 和该 Node 版本所需的 NODE_OPTIONS 实验性标志。
  4. 预加载包调用 module.registerHooks() 安装 Nub 的加载钩子(TS / JSX / YAML / TOML / JSON5 / JSONC 转译和解析路径,由 oxc 的 N-API 绑定支持),在 Node 落后时安装 polyfill(TemporalURLPatternWebSocketWorker、WinterTC 差距全局变量),然后将控制权交给你的脚本。
  5. 子进程递归继承相同的增强——从脚本内部的 node hello.js,到 Vite 或 Next.js 为开发服务器启动的 Node 进程,到你测试运行器启动的工作线程池,到你自己的代码中的 child_process.spawn("node", …),全部增强。

每一步都基于 Node 的公共扩展接口——参见它依赖什么 Node 功能?了解完整清单。

它依赖什么 Node 功能?

Nub 应用的每一项增强都是耐心好的用户在原生 Node 上手动能做到的。Node 为此工作暴露的扩展接口在过去三年中大幅增长,而 Nub 组合的工具集很小且完全公开:

  • module.registerHooks()(Node 22.15,2025 年 4 月)— 用于转译导入路径的同步加载器钩子。
  • module.register()(Node 20.6,2023 年 8 月)— 编程式加载器注册,无需 --loader CLI 操作。
  • --import 预加载(Node 19.0,2022 年 10 月,自 20.6 起稳定)— 在用户 main 之前运行设置代码,ESM 感知。用于安装 polyfill 和设置加载钩子。
  • --require 预加载(Node 1.6,2012 年)— 较旧的 CJS 风格预加载,在 pre-ESM 角落仍然有用。
  • NODE_OPTIONS 和 V8 标志注入 — 用于开启 Node 已实现但保持门控的 --experimental-* 标志。
  • N-API 插件(自 Node 8.6 起稳定,2017 年 10 月)— ABI 稳定的原生插件合约。oxc 转译器作为 Node 插件提供,从加载钩子内部调用。

这就是全部。没有修补的 Node 二进制文件、没有 vendored libnode、没有需要发现的单独运行时。

为什么之前没有人这样做?

因为使之可能的扩展接口是新的——Nub 组合的公共 Node 机制在过去三年才落地(module.registerHooks() 在 2025 年 4 月,module.register() 在 2023 年)。Deno 和 Bun 早于该接口的大部分——那时候,提供集成 TypeScript 意味着围绕它编写运行时;今天同样的层可以组合在 Node 之上,完整兼容性层面免费保留。

为什么 Node 不原生提供这些默认设置?

Node 刻意保持可预测——它是数千个生产部署依赖的基础。将意见性默认设置、集成 TypeScript、预加载 .env 或工作区感知的脚本运行器发布到核心会改变"运行在 Node 上"的含义。

Node 在过去三年中做的是,暴露了足够的公共扩展接口——module.registerHooks()module.register()--import、稳定的 N-API——使现代默认设置层不再需要存在于 Node 内部。它可以存在于其上。这就是 Nub。

它提供了哪些 polyfill?

每个 polyfill 都是建立在成熟社区实现之上的薄垫片,经过功能检测,并在 Node 提供时延迟到原生实现。

  • Temporal — TC39 日期/时间 API。Node 26+ 原生;Nub 在更旧的 Node 上通过 @js-temporal/polyfill 填充
  • URLPattern — WHATWG URL 模式匹配。Node 24+ 原生;Nub 在更旧的 Node 上通过 urlpattern-polyfill 填充
  • WebSocket — 浏览器标准 WebSocket 客户端。Node 20.10+ 可用;Nub 在 Node 22.0 以下注入 --experimental-websocket
  • Worker — 基于 node:worker_threads 的浏览器形态 worker 构造函数。Node 没有原生计划(nodejs/node#43583 自 2022 年开放);Nub 提供了一个约 150 行的包装器
  • WinterTC 差距全局变量reportErrorselfPromiseRejectionEvent。Node TSC 不反对这些;PR 因不活跃停滞。约 50 行预加载包

这些不是 Nub 特定的。它们是 WinterTC 发布的跨运行时合约——最小公共 Web API——针对它编写的代码在每个合规运行时上不变运行,包括原生 Node。

安装 Nub 后可以停止使用哪些 npm 包?

两类——第一类不限于 npm 包。Nub 直接取代这些:

dotenv
cross-env
tsx, ts-node
nodemon
tsconfig-paths
npx                 # → nubx / nub exec
nvm, fnm            # 锁定文件本身就提供正确的 Node
corepack            # → nub pm
PM CLI 本身          # nub install / nub run,对照你已有的锁文件

现代 Node 已经淘汰了第二类,Nub 让你通过在现代 Node 最低版本上运行来继承它们:

node-fetch, cross-fetch, whatwg-fetch   # 18 起原生 fetch
abort-controller                        # 15 起原生
uuid (v4 情况)                          # 14.17 起 crypto.randomUUID
rimraf                                  # 14.14 起 fs.rmSync recursive+force
mkdirp                                  # 10.12 起 fs.mkdirSync recursive
glob, globby (基本情况)              # 22 起 fs.glob
form-data                               # 18 起原生 FormData
# 22.0 起原生 WebSocket,20.10 以下取消标志
ws client

第二类的功劳归于 Node——在现代 Node 最低版本上运行才是使继承实用的关键,无需逐项目验证 Node 版本。

Nub 如何解析模块?

Nub 的解析器是 Node ESM 解析算法的 Rust 实现,在原生 Node 行为之上有层次地加入了 tsconfig.json paths 和无扩展名 .ts 导入探测。它基于 oxc_resolver 提供原语,通过从 Node 自身 ESM 解析器规范移植的一致性套件验证,也是 nubxnub run 中解析器的动力来源。

热路径的其余部分委托给 Rust JavaScript 工具生态系统已经趋同的 crate——oxc 用于解析、转换和 source map;napi-rs 用于 Rust→Node 绑定。Nub 自身的贡献是编排:检测用户的 Node 版本、连接 module.registerHooks()、前置 PATH 垫片、管理转译缓存、版本门控 polyfill 和解析器本身。

它和 Bun 有什么不同?

Bun 是一个从零开始的 JavaScript 运行时,构建在 JavaScriptCore 之上,有自己的模块加载器、包管理器、测试运行器、打包器和 Bun.* API 接口。使用这些接口的代码是 Bun 特定的:

Bun.serve()
bun:sqlite
bun:test
the Bun global
@types/bun

Nub 保持应用代码在 Node 的公共接口上:

no globalThis.nub
no nub:* module namespace
no @nub/* npm scope
no "nub" field in package.json

Nub 应用的所有增强在带有匹配 module.register() / --import / npm 插件的原生 Node 上也能工作。Nub 关注 Node 现在作为扩展接口暴露的部分:TypeScript 加载、脚本和包编排、包管理和版本提供。

它和 Deno 有什么不同?

Deno 是一个从零开始的运行时,构建在 V8 之上,有自己的权限模型、标准库、模块解析(URL 导入、jsr: 说明符)和 Deno.* API 接口。使用这些接口的代码是 Deno 特定的:

Deno.serve()
Deno.env
Deno.readTextFile

Deno 在 Deno 内部实现 Node 兼容性。Nub 运行在你安装的实际 Node 二进制文件上;如果一个包能在 Node 上运行,它就能在 Nub 上运行,这是由构造决定的。

它和 node --run 有什么不同?

Node 在 22.0 中发布了内置脚本运行器——node --run——而且它确实很快。但它刻意保持最小化,这些差距就是项目仍然选择 pnpm run 的原因:

  • 没有 pre / post 生命周期钩子。
  • 没有工作区——没有跨 monorepo 的递归或过滤运行。
  • 没有无数脚本读取的 npm_* 环境变量(npm_package_*npm_config_*npm_lifecycle_event)。

nub run 命令在脚本运行器基准测试中同样快,并弥补了所有三个差距,所以为 npmpnpm 编写的脚本保持不变运行。

如果我想停止使用 Nub 怎么办?

你的代码库在原生 Node 上保持不变运行。Nub 不会向你的代码添加任何 API——没有 nub 全局变量、没有 nub:* 导入命名空间、没有 @nub/* 作用域、没有 package.json 中的 "nub" 字段。

如果 Nub 明天消失,最坏的情况是回到你之前的工具链:

  • TypeScript 文件需要单独的编译步骤或 tsx 等价的加载器。
  • .env 文件需要 dotenv-cliimport "dotenv/config"

代码本身不需要改变。

什么是 --node 标志?

nub runnubx 上的兼容标志。它禁用该编排调用的 Nub 运行时增强,同时保留 CLI 工作(工作区、脚本、npm_* 环境、生命周期钩子)。

# 原生 Node,完全停止——shell 中的 `node` 始终是你的真实 Node
node script.js
nub script.js                # Nub 增强激活
nub run --node test          # Nub 的 CLI 编排,运行时增强关闭
nubx --node prisma generate  # Nub 的 bin 解析,运行时增强关闭

它剥离了生成进程的每一项运行时增强——转译钩子、.env 加载、polyfill、取消标志和 PATH 垫片——留下逐字节精确的 Node 运行时行为;完整契约见运行时概览

在脚本的 #!/usr/bin/env node shebang 链期望真实 Node、二分查找 Nub bug、或 CI 想要逐字节精确的 Node 运行时行为配合 Nub 的 --filter / 工作区选择时很有用。对于临时的原生 Node 运行,输入 node

Nub 会向我的 package.json 添加什么吗?

不会。Nub 读取的字段是现有的标准字段,与 Node、npmpnpmyarn 读取的完全相同:

"scripts"
"workspaces"
"bin"
"type"
"exports"
"imports"
"engines.node"

Nub 会向我的 tsconfig.json 添加什么吗?

不会。Nub 像 tsc 一样读取 tsconfig.json——pathsbaseUrlextends 链、jsxexperimentalDecorators——并在运行时应用它们。

如何安装 Nub?

npm install -g --ignore-scripts=false @nubjs/nub

这会安装 Rust 二进制文件(通过 swcesbuildoxc 和基于 napi-rs 的工具使用的通常 @<scope>/<platform-arch> npm 分发模式)以及你平台的 N-API 插件。生成的 nubnubx 命令在你的 PATH 上。

如何升级?

npm install -g --ignore-scripts=false @nubjs/nub@latest

与安装相同——匹配你的安装方式:Homebrew 安装用 brew upgrade nub,否则重新运行你的安装器。Nub 和 Node 独立版本化:Nub 解析你项目锁定的 Node(.node-version / .nvmrc / engines.node)并在缺失时提供,仅在没有锁定时回退到 PATH 上的 node。所以升级 Nub 不会改变你项目的 Node 版本,升级 Node 也不会改变你的 Nub 版本。

有 curl 安装脚本吗?

有。在 macOS 和 Linux 上,curl -fsSL https://nubjs.com/install.sh | bash;在 Windows 上,powershell -c "irm https://nubjs.com/install.ps1 | iex"。两者都将原生二进制文件放入 ~/.nub 并放到你的 PATH 上。如果你更愿意通过包管理器安装,npm install -g --ignore-scripts=false @nubjs/nub(或 pnpm add -g / yarn global add)做同样的事,在 macOS 和 Linux 上 brew install nubjs/tap/nub 也可以。

它有开发服务器吗?

没有。Vite 已经拥有这个领域;nub run dev 运行你项目的 dev 脚本——更快,因为 nub run 包装器比 pnpm run 包装器开销更小,但开发服务器本身仍然是 Vite(或 Next.js,或你正在运行的任何东西)。

它有测试运行器吗?

没有。Node 自身的 node:test 已经拥有这个领域——以及 vitestjest(如果你更喜欢这些)。Nub 运行你选择的任何一个。你的测试运行器启动的工作线程继承 Nub 的增强,所以 TypeScript 测试文件直接可用。

它有打包器吗?

没有。你项目已经使用的打包器(Vite / Rollup / Rolldown / esbuild / webpack / tsup)继续工作。Nub 的转译路径用于执行,而非发布生产制品。

热重载的情况如何?

监听模式在文件变化时重启进程——监听集是解析的依赖图加上你的 .env* 文件和 tsconfig.json,所以对于 TypeScript 项目无需 glob 维护就能直接工作。它不保留内存中的状态或就地热替换模块;重启就是重启。参见监听模式