Turborepo 和 Nub 解决不同问题,它们在不知道彼此的情况下组合。Turborepo 跨你的包调度任务并缓存其结果。Nub 是下面运行的东西:它安装你的依赖、提供 Node 版本,并增强每个任务生成的 Node,使 TypeScript 和 .env 文件无需构建步骤即可工作。你不需要重新配置 Turborepo 来采用 Nub——你保持现有设置并通过 Nub 运行 Turborepo。

以下所有内容基于两个工具今天实际如何一起工作。有注意事项的地方,本指南会命名它。

简短版本

  • 保持你的 package.json packageManager 字段和现有锁文件不变。
  • nub install 代替你旧的包管理器安装。
  • 在 Nub 下运行 Turborepo——nub run <script>,其中脚本是 turbo run …,或 nubx turbo run …

这就是全部集成。本页的其余部分解释为什么它有效以及需要注意什么。

你不需要告诉 Turborepo 使用 Nub

Turborepo 从根 package.json 中的 packageManager 字段(Corepack 格式,例如 pnpm@9.12.0)加上存在的锁文件推断仓库的包管理器。它识别 npm、pnpm、yarn 和 bun。没有 Turborepo 用于包管理器的设置,也没有 Nub 标识符供它识别。所以没有东西可指向 Turborepo,你也不应该尝试。

这没问题,因为 Nub 与你项目已有的工具锁文件兼容——它往返 npm、pnpm 和 bun 锁文件并读取 yarn 的。Turborepo 通过 packageManager 字段和锁文件继续看到现任包管理器,保持满意。Nub 在下面处理安装。你的 Turborepo 配置完全不变。

pnpm install          # 之前
nub install           # 之后 — 相同锁文件,相同 node_modules 布局

Turborepo 如何运行任务,以及 Nub 如何介入

当你运行 turbo run build 时,Turborepo 不自行执行每个包的脚本主体。对于定义任务的每个包,它用 run <task> 生成仓库的包管理器——对于 pnpm 仓库,那是包目录中的 pnpm run build。包管理器是执行脚本并将 node_modules/.bin 前置到路径的东西。

该子进程从 Turborepo 继承其环境,后者从启动 Turborepo 的任何东西继承。这就是 Nub 使用的接缝。当你在 Nub 下运行 Turborepo 时,Nub 已设置增强环境——NODE_OPTIONS 预加载和 path 上的 node 垫片——该环境向下流经整个树:Nub 启动 Turborepo,Turborepo 生成 pnpm run build,pnpm 运行你的脚本,而你脚本调用的 node 是 Nub 增强的 Node。

实际结果:构建脚本是纯 node ./build.ts 的包直接运行 TypeScript,无需单独编译步骤,并加载该包的 .env——在 Turborepo 任务内部,无需每包配置。

// packages/lib/package.json
{
  "name": "@acme/lib",
  "scripts": { "build": "node ./build.ts" }
}
// turbo.json
{
  "tasks": { "build": {} }
}
nub run build         # 根脚本:"turbo run build"
@acme/lib:build: cache miss, executing
@acme/lib:build: [lib] built ok
 Tasks:    1 successful, 1 total

重要的是 Turborepo 在 Nub 下运行。用 nub run <script>(其中脚本命令是 turbo run …)或 nubx turbo run … 启动它。如果你从普通 shell 直接调用 turbo 二进制文件,任务在 path 上的任何 Node 上运行,无增强,.ts 入口将无法解析。

你不需要重写包脚本在任何地方说 nubnode ./build.ts 的脚本就够了——Nub 的增强是将其变为 TypeScript 感知运行的东西。

选择 Node 版本

Turborepo 运行你的包管理器,后者运行你的 Node。Nub 提供并使用你项目固定的 Node 版本,所以 monorepo 中每个任务在相同版本上运行,无需 nvm 或 Corepack。按 Node 生态系统其余部分的方式固定——.nvmrcpackageManagerdevEngines 条目,或 nub node pin——Nub 负责提供它。

这也回避了一个常见 monorepo 失败:包管理器拒绝在它在 path 上找到的旧 Node 下运行。因为 Nub 驱动版本,Turborepo 生成的包管理器在固定 Node 上运行而非过时的系统 Node。

什么干净组合,以及注意什么

干净的部分是劳动分工。Turborepo 拥有任务图、调度和缓存。Nub 拥有运行时、包管理器,以及每个任务下面的 TypeScript 和 .env 处理。两者都不进入对方的领域,增强自行跨越进程边界。

需要注意几点:

  • Turborepo 的缓存是环境感知的。 Turborepo 哈希任务的输入,包括声明的环境变量,来决定缓存命中。Nub 的增强骑在 NODE_OPTIONS 中,Turborepo 传递给任务但不折叠到缓存哈希中,除非你声明它,所以采用 Nub 自身不使你的缓存失效。如果你依赖任务中的特定变量,像你已有的一样在 turbo.json 中声明它们——该行为不变。
  • .env 加载是 Nub 的,在任务级别。 每个任务获得它运行所在包的 .env,因为 Nub 增强运行脚本的 Node。这独立于 Turborepo 自身的 env/globalEnv 声明,后者控制缓存哈希而非加载。如果值必须影响缓存,它仍属于 turbo.json
  • 每次都在 Nub 下运行 Turborepo。 这是唯一真正的要求。从未通过 Nub 的 shell 中的裸 turbo run 不会携带增强。保持入口点为 nub run 脚本(或 nubx turbo)使项目中的每个人和 CI 都自动如此。

关于未来

今天本指南严格关于共存:Turborepo 驱动,Nub 在下面运行。Nub 可能随时间增长更多任务感知能力,但这里没有任何东西依赖于此——上面的集成以现在发布的 Turborepo 和现在发布的 Nub 工作。