正在加载,请稍候…

使用 Nx 和 Turborepo 的 Monorepo 架构:设置与最佳实践

学习如何使用 Nx 或 Turborepo 构建 JavaScript/TypeScript monorepo,涵盖工作区设置、任务管道、缓存、代码生成和 CI

使用 Nx 和 Turborepo 的 Monorepo 架构:设置与最佳实践

使用 Nx 和 Turborepo 的 Monorepo 架构

Monorepo 支持跨多个包共享代码和统一工具。本指南比较 Nx 和 Turborepo,并涵盖生产模式。

为什么使用 Monorepo?

优点:

  • 代码共享:共享工具函数、类型和组件
  • 原子提交:一次提交更改多个包
  • 统一工具:一个 linter、格式化器、TypeScript 配置
  • 简化依赖管理:无版本冲突

使用 Nx 和 Turborepo 的 Monorepo 架构:设置与最佳实践 插图

Turborepo 设置

# 使用 Turborepo 创建新的 monorepo
npx create-turbo@latest my-monorepo
cd my-monorepo
// turbo.json
{
  "$schema": "https://turbo.build/schema.json",
  "globalDependencies": ["**/.env.*local"],
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],  // ^ 表示先运行依赖
      "outputs": ["dist/**", ".next/**"],
      "cache": true
    },
    "test": {
      "dependsOn": ["build"],
      "outputs": ["coverage/**"],
      "cache": true
    },
    "lint": {
      "outputs": []
    },
    "dev": {
      "cache": false,
      "persistent": true  // 长时间运行进程
    },
    "typecheck": {
      "dependsOn": ["^build"],
      "outputs": []
    }
  }
}

工作区结构

my-monorepo/
  apps/
    web/          # Next.js 前端
    api/          # Express 后端
    docs/         # 文档站点
  packages/
    ui/           # 共享 React 组件
    types/        # 共享 TypeScript 类型
    utils/        # 共享工具函数
    config/       # 共享配置(eslint, tsconfig)
  package.json    # 根工作区配置
  turbo.json
// 根 package.json
{
  "name": "my-monorepo",
  "workspaces": ["apps/*", "packages/*"],
  "scripts": {
    "build": "turbo run build",
    "dev": "turbo run dev",
    "test": "turbo run test",
    "lint": "turbo run lint",
    "typecheck": "turbo run typecheck"
  },
  "devDependencies": {
    "turbo": "latest"
  }
}

使用 Nx 和 Turborepo 的 Monorepo 架构:设置与最佳实践 插图

共享包设置

// packages/ui/package.json
{
  "name": "@myapp/ui",
  "main": "./src/index.ts",
  "types": "./src/index.ts",
  "exports": {
    ".": "./src/index.ts"
  },
  "scripts": {
    "build": "tsup src/index.ts --format cjs,esm --dts",
    "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
    "lint": "eslint src/",
    "typecheck": "tsc --noEmit"
  },
  "peerDependencies": {
    "react": ">=18"
  }
}
// packages/ui/src/Button.tsx
export interface ButtonProps {
  children: React.ReactNode;
  variant?: 'primary' | 'secondary';
  onClick?: () => void;
}

export function Button({ children, variant = 'primary', onClick }: ButtonProps) {
  return (
    <button
      className={`btn btn-${variant}`}
      onClick={onClick}
    >
      {children}
    </button>
  );
}

// packages/ui/src/index.ts
export { Button } from './Button';
export { Input } from './Input';
export { Modal } from './Modal';
// apps/web/src/page.tsx - 使用共享包
import { Button, Input } from '@myapp/ui';

export default function HomePage() {
  return (
    <div>
      <Input placeholder="搜索..." />
      <Button variant="primary">提交</Button>
    </div>
  );
}

共享 TypeScript 配置

// packages/config/tsconfig.base.json
{
  "compilerOptions": {
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "moduleResolution": "bundler",
    "target": "ES2022",
    "lib": ["ES2022"],
    "paths": {
      "@myapp/*": ["../../packages/*/src"]
    }
  }
}

// apps/web/tsconfig.json
{
  "extends": "@myapp/config/tsconfig.base.json",
  "compilerOptions": {
    "lib": ["ES2022", "DOM"],
    "plugins": [{ "name": "next" }]
  },
  "include": ["src/**/*", "next-env.d.ts"]
}

使用 Nx 和 Turborepo 的 Monorepo 架构:设置与最佳实践 插图

Nx Monorepo 替代方案

# 创建 Nx 工作区
npx create-nx-workspace@latest my-workspace
cd my-workspace

# 生成应用和库
nx generate @nx/react:application web
nx generate @nx/express:application api
nx generate @nx/react:library ui --directory=shared
// nx.json
{
  "targetDefaults": {
    "build": {
      "dependsOn": ["^build"],
      "cache": true
    },
    "test": {
      "cache": true
    }
  },
  "affected": {
    "defaultBase": "main"
  }
}

使用远程缓存的 CI 优化

# Turborepo 远程缓存(Vercel)
npx turbo login
npx turbo link  # 链接到 Vercel 团队

# GitHub Actions
- name: Build
  run: turbo run build
  env:
    TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
    TURBO_TEAM: ${{ vars.TURBO_TEAM }}

# 仅运行受影响的包
turbo run test --filter="...[HEAD^1]"
# 仅自上次提交以来更改的包

内部包版本管理

// packages/ui/package.json
{
  "version": "0.0.0",  // 内部使用 - 不发布
  // 或者使用 changesets 管理可发布的包
}
# 使用 Changesets 进行版本管理
npx changeset init
npx changeset  # 为修改的包创建 changeset
npx changeset version  # 根据 changesets 更新版本
npx changeset publish  # 发布到 npm

总结

Monorepo 最佳实践:

  • 对于以 JavaScript 为中心的 monorepo,使用 Turborepo(更简单)
  • 对于大型企业代码库,使用 Nx(功能更丰富)
  • 通过显式导出定义清晰的包边界
  • 共享 tsconfig、eslint 和 prettier 配置
  • 使用远程缓存实现快速 CI 构建
  • 使用 Changesets 将包发布到 npm