正在加载,请稍候…

Monorepo 搭建指南:Turborepo、Nx 与 pnpm Workspaces 对比

完整的 monorepo 指南:何时使用 monorepo,如何使用 pnpm workspaces 搭建 Turborepo 或 Nx,管理共享包,以及优化

Monorepo 搭建指南:Turborepo、Nx 与 pnpm Workspaces 对比

Monorepo 与 Polyrepo:如何做出正确选择

Monorepo 将多个项目存储在一个仓库中。Google、Facebook 和 Airbnb 等公司都使用 monorepo 来管理代码库。但这并不意味着它们适合每个团队。

选择 monorepo 的场景:

  • 有多个应用共享的库
  • 团队经常进行跨项目变更
  • 希望统一工具、lint 和测试
  • 正在构建设计系统或组件库

坚持使用 polyrepo 的场景:

  • 项目使用完全不同的技术栈
  • 团队完全独立工作
  • 安全/合规要求严格分离

Monorepo 搭建指南:Turborepo、Nx 与 pnpm Workspaces 对比 插图

pnpm Workspaces:基础

大多数现代 monorepo 工具都基于包管理器工作区构建:

# pnpm-workspace.yaml(monorepo 根目录)
packages:
  - 'apps/*'       # app-web, app-mobile, app-api
  - 'packages/*'   # ui, utils, config, types
// 根 package.json
{
  "name": "my-monorepo",
  "private": true,
  "scripts": {
    "dev": "turbo dev",
    "build": "turbo build",
    "lint": "turbo lint",
    "test": "turbo test"
  },
  "devDependencies": {
    "turbo": "^2.0.0",
    "@types/node": "^20.0.0",
    "typescript": "^5.0.0"
  }
}
# 为所有工作区安装依赖
pnpm install

# 向特定包添加依赖
pnpm add react --filter @myapp/web

# 添加共享内部包
pnpm add @myapp/ui --filter @myapp/web

Turborepo:快速缓存构建

Turborepo 是最简单、最流行的 monorepo 构建系统:

// turbo.json(根目录)
{
  "$schema": "https://turbo.build/schema.json",
  "tasks": {
    "build": {
      "dependsOn": ["^build"],  // 先构建依赖
      "outputs": ["dist/**", ".next/**"]
    },
    "test": {
      "dependsOn": ["^build"],
      "outputs": ["coverage/**"]
    },
    "lint": {
      "outputs": []
    },
    "dev": {
      "cache": false,          // 从不缓存开发服务器
      "persistent": true       // 长时间运行的进程
    }
  }
}
# 为所有包运行构建(首次运行后缓存)
turbo build

# 仅对更改的包运行构建
turbo build --filter=[HEAD^1]

# 为特定应用及其依赖运行构建
turbo build --filter=@myapp/web...

# 清除缓存
turbo build --force

Monorepo 搭建指南:Turborepo、Nx 与 pnpm Workspaces 对比 插图

Turborepo 远程缓存

# 连接到 Vercel 远程缓存
npx turbo login
npx turbo link

# 现在 CI 机器共享同一缓存——未更改代码的构建时间为 0 秒!
# .github/workflows/ci.yml
- name: Build
  run: turbo build
  env:
    TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
    TURBO_TEAM: ${{ vars.TURBO_TEAM }}

Nx:更强大,更复杂

Nx 更适合具有复杂依赖关系图的大型代码库:

# 创建新的 Nx monorepo
npx create-nx-workspace@latest myorg

# 生成应用和库
nx generate @nx/react:app web
nx generate @nx/node:app api
nx generate @nx/react:library ui
nx generate @nx/js:library utils

# 运行受影响的 task(仅自 main 分支以来更改的内容)
nx affected --target=build
nx affected --target=test --base=origin/main

# 依赖关系图可视化
nx graph
// nx.json
{
  "targetDefaults": {
    "build": {
      "dependsOn": ["^build"],
      "cache": true
    },
    "test": {
      "cache": true,
      "inputs": ["default", "^production"]
    }
  },
  "namedInputs": {
    "production": [
      "default",
      "!{projectRoot}/**/*.spec.ts",
      "!{projectRoot}/jest.config.ts"
    ]
  }
}

Monorepo 搭建指南:Turborepo、Nx 与 pnpm Workspaces 对比 插图

共享包结构

monorepo/
├── apps/
│   ├── web/                    # Next.js 应用
│   │   └── package.json        # 依赖 @myapp/ui, @myapp/utils
│   └── api/                    # Express API  
│       └── package.json        # 依赖 @myapp/utils, @myapp/types
└── packages/
    ├── ui/                     # 共享 React 组件
    │   ├── src/
    │   │   ├── Button.tsx
    │   │   └── index.ts
    │   ├── package.json
    │   └── tsconfig.json
    ├── utils/                  # 共享工具函数
    ├── types/                  # 共享 TypeScript 类型
    └── config/                 # 共享 tsconfig, eslint 配置
// packages/ui/package.json
{
  "name": "@myapp/ui",
  "version": "0.0.0",
  "main": "./src/index.ts",   // 直接引用源码——开发时无需构建步骤
  "types": "./src/index.ts",
  "exports": {
    ".": {
      "import": "./dist/index.js",
      "types": "./dist/index.d.ts"
    }
  },
  "scripts": {
    "build": "tsc --build",
    "dev": "tsc --watch"
  },
  "peerDependencies": {
    "react": "^18.0.0"
  }
}
// apps/web/package.json
{
  "name": "@myapp/web",
  "dependencies": {
    "@myapp/ui": "workspace:*",    // pnpm 工作区引用
    "@myapp/utils": "workspace:*"
  }
}

共享 TypeScript 配置

// packages/config/tsconfig.base.json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "strict": true,
    "skipLibCheck": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true
  }
}

// apps/web/tsconfig.json
{
  "extends": "@myapp/config/tsconfig.base.json",
  "compilerOptions": {
    "lib": ["dom", "ES2022"],
    "jsx": "react-jsx"
  },
  "include": ["src"]
}

共享 ESLint 配置

// packages/config/eslint.base.js
module.exports = {
  parser: '@typescript-eslint/parser',
  plugins: ['@typescript-eslint'],
  extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
  rules: {
    '@typescript-eslint/no-unused-vars': 'error',
    '@typescript-eslint/explicit-function-return-type': 'off',
  },
};

// apps/web/.eslintrc.js
const base = require('@myapp/config/eslint.base.js');
module.exports = {
  ...base,
  extends: [...base.extends, 'plugin:react/recommended'],
};

Turborepo 与 Nx 对比

特性 Turborepo Nx
搭建复杂度 简单 复杂
学习曲线
缓存 ✅ 优秀 ✅ 优秀
增量构建
代码生成 ❌ 基础 ✅ 丰富
插件生态 增长中 成熟
远程缓存 Vercel(免费) Nx Cloud(付费/自托管)
最适合 中小型项目 大型企业

→ 使用 JSON 转 YAML 转换器 在 JSON 和 YAML 之间转换包配置。