常见问题
关于 Nub 的常见问题简明解答——它是什么、如何工作,以及它刻意不是什么。
Nub 是什么?
Nub 是一个由 Node.js 驱动的全能工具包,现代化 Node.js 生态系统中的开发体验。用它来运行文件、package.json 脚本和本地安装的 CLI。用它替代 node、npm run 和 npx(或你偏好的包管理器中的等价物)。
$ 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.json—paths、baseUrl、extends链、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"相同 - 自动 polyfill —
Temporal、URLPattern、浏览器形态Worker、WinterTC 差距全局变量,按版本检测和功能门控 - 取消实验性标志 —
EventSource、WebSocket、localStorage/sessionStorage、vm.Module、node:sqlite— Node 已内置但仍保持门控的标志,在每个版本中仍需--experimental-*的精确版本带中自动注入
当你输入 nub 而非 node 时,所有这些都免费获得。要获得原生 Node 行为,输入 node——PATH 垫片是每次调用独立的,所以 shell 中的 node 始终是用户的真实 Node。对于不需要运行时增强的编排,nub run 和 nubx 上的 --node 标志会剥离钩子/预加载/取消标志,同时保留 Nub 的 CLI 工作。
电梯演讲是什么?
如果你过去几年一直在用 tsx、dotenv-cli、nodemon、npx、pnpm 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.cache、module.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:fs、node:http、node:test、node:worker_threads、node:sqlite、node: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.yaml、npm 工作区、yarn 工作区)由 nub run 和 nubx 直接识别。事实来源:
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" 字段(npm、yarn)和 pnpm-workspace.yaml(pnpm),使用每个工作区用户从 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)是等价物,遵循与 npx 和 pnpm dlx 相同的本地优先然后 DLX 模型,没有交互式提示:
- 本地 bin —
nubx向上遍历目录树检查每个层级的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 文件,但它擦除类型而非转译——所以任何超出可擦除注解的内容都自力更生:
enum、namespace和参数属性 → 语法错误(Node 26 甚至移除了--experimental-transform-types,曾经处理它们的标志)- JSX 和
emitDecoratorMetadata→ 不支持 tsconfig.json的paths、baseUrl、jsx、experimentalDecorators、extends→ 被忽略("故意不支持",根据 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 二进制文件会:
- 解析你项目锁定的 Node 版本——
devEngines.runtime、.node-version、.nvmrc或engines.node——如果缺失则从 nodejs.org 提供,当没有锁定时回退到PATH上的node。 - 读取你最近的
package.json、tsconfig.json和.env*文件。 - 启动该 Node,设置指向 Nub 预加载包的
--import和该 Node 版本所需的NODE_OPTIONS实验性标志。 - 预加载包调用
module.registerHooks()安装 Nub 的加载钩子(TS / JSX / YAML / TOML / JSON5 / JSONC 转译和解析路径,由oxc的 N-API 绑定支持),在 Node 落后时安装 polyfill(Temporal、URLPattern、WebSocket、Worker、WinterTC 差距全局变量),然后将控制权交给你的脚本。 - 子进程递归继承相同的增强——从脚本内部的
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 月)— 编程式加载器注册,无需--loaderCLI 操作。--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-websocketWorker— 基于node:worker_threads的浏览器形态 worker 构造函数。Node 没有原生计划(nodejs/node#43583 自 2022 年开放);Nub 提供了一个约 150 行的包装器- WinterTC 差距全局变量 —
reportError、self、PromiseRejectionEvent。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 解析器规范移植的一致性套件验证,也是 nubx 和 nub 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/bunNub 保持应用代码在 Node 的公共接口上:
no globalThis.nub
no nub:* module namespace
no @nub/* npm scope
no "nub" field in package.jsonNub 应用的所有增强在带有匹配 module.register() / --import / npm 插件的原生 Node 上也能工作。Nub 关注 Node 现在作为扩展接口暴露的部分:TypeScript 加载、脚本和包编排、包管理和版本提供。
它和 Deno 有什么不同?
Deno 是一个从零开始的运行时,构建在 V8 之上,有自己的权限模型、标准库、模块解析(URL 导入、jsr: 说明符)和 Deno.* API 接口。使用这些接口的代码是 Deno 特定的:
Deno.serve()
Deno.env
Deno.readTextFileDeno 在 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 命令在脚本运行器基准测试中同样快,并弥补了所有三个差距,所以为 npm 或 pnpm 编写的脚本保持不变运行。
如果我想停止使用 Nub 怎么办?
你的代码库在原生 Node 上保持不变运行。Nub 不会向你的代码添加任何 API——没有 nub 全局变量、没有 nub:* 导入命名空间、没有 @nub/* 作用域、没有 package.json 中的 "nub" 字段。
如果 Nub 明天消失,最坏的情况是回到你之前的工具链:
- TypeScript 文件需要单独的编译步骤或 tsx 等价的加载器。
.env文件需要dotenv-cli或import "dotenv/config"。
代码本身不需要改变。
什么是 --node 标志?
nub run 和 nubx 上的兼容标志。它禁用该编排调用的 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、npm、pnpm 和 yarn 读取的完全相同:
"scripts"
"workspaces"
"bin"
"type"
"exports"
"imports"
"engines.node"Nub 会向我的 tsconfig.json 添加什么吗?
不会。Nub 像 tsc 一样读取 tsconfig.json——paths、baseUrl、extends 链、jsx、experimentalDecorators——并在运行时应用它们。
如何安装 Nub?
npm install -g --ignore-scripts=false @nubjs/nub这会安装 Rust 二进制文件(通过 swc、esbuild、oxc 和基于 napi-rs 的工具使用的通常 @<scope>/<platform-arch> npm 分发模式)以及你平台的 N-API 插件。生成的 nub 和 nubx 命令在你的 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 已经拥有这个领域——以及 vitest 和 jest(如果你更喜欢这些)。Nub 运行你选择的任何一个。你的测试运行器启动的工作线程继承 Nub 的增强,所以 TypeScript 测试文件直接可用。
它有打包器吗?
没有。你项目已经使用的打包器(Vite / Rollup / Rolldown / esbuild / webpack / tsup)继续工作。Nub 的转译路径用于执行,而非发布生产制品。
热重载的情况如何?
监听模式在文件变化时重启进程——监听集是解析的依赖图加上你的 .env* 文件和 tsconfig.json,所以对于 TypeScript 项目无需 glob 维护就能直接工作。它不保留内存中的状态或就地热替换模块;重启就是重启。参见监听模式。