Turborepo Monorepo 指南:管理多应用程序项目
包含多个应用程序和共享包的 Monorepos 现在是构建互连软件产品的公司的标准架构。 Vercel 收购的 Turborepo 提供了构建编排层,使 monorepos 变得实用——处理任务依赖性、缓存和跨数十个包的并行执行。
要点
- Turborepo 远程缓存可以将增量更改的 CI 构建时间缩短 80-90%
- 任务管道确保包自动按照正确的依赖顺序构建
- 共享包(类型、验证器、实用程序)消除了应用程序之间的代码重复
- pnpm 工作空间为 monorepos 提供磁盘高效的依赖管理
Monorepo 架构
目录结构
典型的 Turborepo monorepo 将应用程序与共享包分开:
my-monorepo/
apps/
api/ # NestJS backend
web/ # Next.js frontend
docs/ # Documentation site
mobile/ # React Native app
packages/
db/ # Drizzle ORM schemas and migrations
types/ # Shared TypeScript types
validators/ # Zod validation schemas
utils/ # Shared utility functions
ui/ # Shared UI components
config/ # Shared configuration (ESLint, TypeScript)
turbo.json # Turborepo configuration
pnpm-workspace.yaml
package.json # Root package.json
工作区配置
在 pnpm-workspace.yaml 中定义工作区:
packages:
- "apps/*"
- "packages/*"
每个工作区都有自己的 package.json 以及依赖项和脚本。使用工作区协议引用共享包:
{
"dependencies": {
"@myorg/types": "workspace:*",
"@myorg/validators": "workspace:*",
"@myorg/utils": "workspace:*"
}
}
Turborepo 配置
任务管道
Turbo.json 定义任务如何相互关联:
{
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**"]
},
"dev": {
"dependsOn": ["^build"],
"cache": false,
"persistent": true
},
"lint": {
"dependsOn": ["^build"]
},
"test": {
"dependsOn": ["build"]
}
}
}
关键概念:
- dependsOn: ["^build"]:插入符号 (^) 表示“首先构建所有依赖项”。构建 Web 应用程序时,Turborepo 首先构建包/类型、包/验证器和包/实用程序。
- 输出:Turborepo 缓存的文件。在后续运行中,如果输入未更改,Turborepo 将重播缓存的输出而不是重建。
- persistent: true:对于持续运行的开发服务器。
- cache: false:禁用必须始终运行的任务(如开发服务器)的缓存。
共享包
类型包
集中跨应用程序使用的 TypeScript 类型:
// packages/types/src/user.ts
export interface User {
id: string;
email: string;
name: string;
role: "admin" | "user" | "support";
createdAt: Date;
}
export type CreateUserDTO = Omit<User, "id" | "createdAt">;
export type UpdateUserDTO = Partial<CreateUserDTO>;
验证器包
Zod 模式在前端和后端之间共享:
// packages/validators/src/user.ts
import { z } from "zod";
export const createUserSchema = z.object({
email: z.string().email(),
name: z.string().min(2).max(255),
role: z.enum(["admin", "user", "support"]),
});
构建配置
共享包必须先预先构建,然后应用程序才能使用它们。使用将 TypeScript 编译为 JavaScript 的构建脚本配置每个包:
{
"scripts": {
"build": "tsc --project tsconfig.json",
"dev": "tsc --watch"
}
}
缓存策略
本地缓存
Turborepo 默认将任务输出缓存在 node_modules/.cache/turbo 中。当您运行turbo build并且没有任何变化时,Turborepo会在几毫秒内重播缓存的输出。
远程缓存
对于 CI/CD,启用远程缓存以在团队成员和 CI 运行之间共享构建工件:
npx turbo login
npx turbo link
远程缓存意味着在一台开发人员机器上完成的构建或 CI 运行可供每个人使用。影响是巨大的:增量 CI 构建时间从 10 分钟缩短到不到 1 分钟。
开发工作流程
运行所有应用程序
pnpm dev # Starts all apps and watches all packages
Turborepo 首先构建共享包(因为 dev 取决于 ^build),然后并行启动应用程序开发服务器。对共享包的更改会触发传播到使用应用程序的重建。
运行特定应用程序
pnpm dev --filter=web # Only the web app and its dependencies
pnpm dev --filter=api # Only the API and its dependencies
过滤对于大型单一存储库至关重要,因为不需要启动所有内容。
CI/CD 优化
受影响的包检测
Turborepo 确定自上次提交以来哪些包发生了更改,并且只为受影响的包运行任务:
turbo build --filter=...[HEAD^1]
这仅针对在最新提交及其依赖项中更改的包运行构建。
并行执行
Turborepo 并行运行独立任务。如果packages/types和packages/utils没有依赖关系,它们会同时构建。并发级别适应可用的 CPU 内核。
常见问题
问:Turborepo monorepo 可以处理多少个包裹?
Turborepo 处理包含数百个包的 monorepos。性能可以很好地扩展,因为任务图是在启动时进行分析的,而缓存意味着大多数包会跳过增量更改的构建。
问:我们应该在工作区中使用 pnpm 还是 npm?
强烈建议将 pnpm 用于 monorepos。其内容可寻址存储可消除软件包之间的重复依赖关系,从而节省大量磁盘空间和安装时间。 pnpm 工作区对于依赖项解析也更加严格,可以捕获丢失的依赖项声明。
问:我们如何跨应用程序处理不同的 Node.js 版本?
使用每个package.json中的engines字段来指定Node.js版本要求。对于运行时差异,请配置 CI 以使用每个应用程序的适当版本。
问:我们可以逐步采用 Turborepo 吗?
是的。将turbo.json 添加到现有的pnpm 工作区项目并配置任务管道。现有脚本继续有效。您无需重组即可立即获得缓存和任务编排。
下一步是什么
Turborepo 将 monorepo 开发从维护负担转变为生产力倍增器。从简单的结构开始,然后根据项目需求进行扩展。
联系 ECOSIRE 获取单一存储库架构咨询,或探索我们的 Odoo 集成服务 获取多应用 ERP 架构。
由 ECOSIRE 发布——帮助企业利用企业软件解决方案进行扩展。
作者
ECOSIRE Research and Development Team
在 ECOSIRE 构建企业级数字产品。分享关于 Odoo 集成、电商自动化和 AI 驱动商业解决方案的洞见。
相关文章
Docker Odoo 部署:生产就绪容器设置
在 Docker 容器中部署 Odoo 以进行生产。涵盖 Docker Compose 配置、PostgreSQL 设置、Nginx 反向代理、SSL、备份和扩展的完整指南。
使用 Drizzle ORM 和 NestJS 构建 API:完整指南
使用 Drizzle ORM 和 NestJS 构建类型安全的 API。了解生产应用程序的模式定义、迁移、查询构建、关系、事务和测试模式。
Playwright 测试指南:Next.js 应用程序的 E2E 测试
使用 Playwright 为 Next.js 应用程序编写可靠的端到端测试。完整的指南涵盖设置、页面对象、身份验证测试、视觉回归和 CI/CD 集成。