在 Turborepo 中使用 Nub
Nub 和 Turborepo 今天如何在 monorepo 中共存——Turborepo 拥有任务图和缓存,Nub 是下面的运行时和包管理器。你保持现有的 packageManager 字段和锁文件,在 Nub 下运行 Turborepo,TypeScript 和 .env 文件在每个任务中直接工作。
Turborepo 和 Nub 解决不同问题,它们在不知道彼此的情况下组合。Turborepo 跨你的包调度任务并缓存其结果。Nub 是下面运行的东西:它安装你的依赖、提供 Node 版本,并增强每个任务生成的 Node,使 TypeScript 和 .env 文件无需构建步骤即可工作。你不需要重新配置 Turborepo 来采用 Nub——你保持现有设置并通过 Nub 运行 Turborepo。
以下所有内容基于两个工具今天实际如何一起工作。有注意事项的地方,本指南会命名它。
简短版本
- 保持你的
package.jsonpackageManager字段和现有锁文件不变。 - 用
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 入口将无法解析。
你不需要重写包脚本在任何地方说 nub。node ./build.ts 的脚本就够了——Nub 的增强是将其变为 TypeScript 感知运行的东西。
选择 Node 版本
Turborepo 运行你的包管理器,后者运行你的 Node。Nub 提供并使用你项目固定的 Node 版本,所以 monorepo 中每个任务在相同版本上运行,无需 nvm 或 Corepack。按 Node 生态系统其余部分的方式固定——.nvmrc、packageManager 或 devEngines 条目,或 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 工作。