monorepo-structure

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Monorepo Structure

Monorepo 结构

One repo, multiple packages, shared types, parallel builds.
单仓库多包结构,支持共享类型、并行构建。

When to Use This Skill

何时采用该方案

  • Sharing code between frontend and backend
  • Multiple apps need common types/utilities
  • Want atomic commits across packages
  • Tired of version hell with separate repos
  • Need parallel builds with caching
  • 前端与后端之间共享代码
  • 多个应用需要通用类型/工具函数
  • 希望跨包进行原子提交
  • 受够了多仓库的版本混乱问题
  • 需要带缓存的并行构建能力

Core Concepts

核心概念

  1. Workspaces - pnpm manages multiple packages in one repo
  2. Turborepo - Orchestrates builds with caching and parallelization
  3. Shared types - Single source of truth for TypeScript types
  4. Build order - Dependencies build before dependents
  1. 工作区(Workspaces) - pnpm 管理单仓库中的多个包
  2. Turborepo - 负责编排构建流程,支持缓存与并行化
  3. 共享类型 - TypeScript 类型的单一可信来源
  4. 构建顺序 - 依赖包先于依赖它的模块构建

Project Structure

项目结构

project-root/
├── apps/
│   ├── web/                    # Next.js frontend
│   │   ├── app/
│   │   ├── components/
│   │   └── package.json
│   ├── api/                    # Backend API
│   │   ├── src/
│   │   └── package.json
│   └── worker/                 # Background worker
│       ├── src/
│       └── package.json
├── packages/
│   ├── types/                  # Shared TypeScript types
│   │   ├── src/
│   │   │   ├── index.ts
│   │   │   ├── user.ts
│   │   │   └── schemas.ts
│   │   └── package.json
│   ├── utils/                  # Shared utilities
│   │   └── package.json
│   └── config/                 # Shared configs (eslint, tsconfig)
│       └── package.json
├── package.json                # Root package.json
├── pnpm-workspace.yaml
├── turbo.json
└── tsconfig.base.json
project-root/
├── apps/
│   ├── web/                    # Next.js 前端应用
│   │   ├── app/
│   │   ├── components/
│   │   └── package.json
│   ├── api/                    # 后端API服务
│   │   ├── src/
│   │   └── package.json
│   └── worker/                 # 后台工作进程
│       ├── src/
│       └── package.json
├── packages/
│   ├── types/                  # 共享TypeScript类型定义
│   │   ├── src/
│   │   │   ├── index.ts
│   │   │   ├── user.ts
│   │   │   └── schemas.ts
│   │   └── package.json
│   ├── utils/                  # 共享工具函数包
│   │   └── package.json
│   └── config/                 # 共享配置(eslint、tsconfig等)
│       └── package.json
├── package.json                # 根目录package.json
├── pnpm-workspace.yaml
├── turbo.json
└── tsconfig.base.json

TypeScript Implementation

TypeScript 实现细节

pnpm-workspace.yaml

pnpm-workspace.yaml

yaml
packages:
  - "apps/*"
  - "packages/*"
yaml
packages:
  - "apps/*"
  - "packages/*"

Root package.json

根目录 package.json

json
{
  "name": "my-saas",
  "private": true,
  "scripts": {
    "dev": "turbo dev",
    "build": "turbo build",
    "test": "turbo test",
    "lint": "turbo lint",
    "typecheck": "turbo typecheck",
    "clean": "turbo clean && rm -rf node_modules"
  },
  "devDependencies": {
    "turbo": "^2.0.0",
    "typescript": "^5.4.0"
  },
  "packageManager": "pnpm@9.0.0"
}
json
{
  "name": "my-saas",
  "private": true,
  "scripts": {
    "dev": "turbo dev",
    "build": "turbo build",
    "test": "turbo test",
    "lint": "turbo lint",
    "typecheck": "turbo typecheck",
    "clean": "turbo clean && rm -rf node_modules"
  },
  "devDependencies": {
    "turbo": "^2.0.0",
    "typescript": "^5.4.0"
  },
  "packageManager": "pnpm@9.0.0"
}

turbo.json

turbo.json

json
{
  "$schema": "https://turbo.build/schema.json",
  "globalDependencies": ["**/.env.*local"],
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", ".next/**", "!.next/cache/**"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    },
    "test": {
      "dependsOn": ["^build"]
    },
    "typecheck": {
      "dependsOn": ["^build"]
    },
    "lint": {
      "dependsOn": ["^build"]
    },
    "clean": {
      "cache": false
    }
  }
}
json
{
  "$schema": "https://turbo.build/schema.json",
  "globalDependencies": ["**/.env.*local"],
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", ".next/**", "!.next/cache/**"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    },
    "test": {
      "dependsOn": ["^build"]
    },
    "typecheck": {
      "dependsOn": ["^build"]
    },
    "lint": {
      "dependsOn": ["^build"]
    },
    "clean": {
      "cache": false
    }
  }
}

tsconfig.base.json

tsconfig.base.json

json
{
  "compilerOptions": {
    "target": "ES2022",
    "lib": ["ES2022"],
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitOverride": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "resolveJsonModule": true,
    "isolatedModules": true
  }
}
json
{
  "compilerOptions": {
    "target": "ES2022",
    "lib": ["ES2022"],
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitOverride": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "resolveJsonModule": true,
    "isolatedModules": true
  }
}

Shared Types Package

共享类型包

json
// packages/types/package.json
{
  "name": "@myapp/types",
  "version": "0.0.1",
  "private": true,
  "main": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.js"
    }
  },
  "scripts": {
    "build": "tsc",
    "dev": "tsc --watch",
    "typecheck": "tsc --noEmit"
  },
  "devDependencies": {
    "typescript": "^5.4.0"
  },
  "dependencies": {
    "zod": "^3.23.0"
  }
}
json
// packages/types/tsconfig.json
{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "include": ["src/**/*"]
}
typescript
// packages/types/src/index.ts
export * from './user';
export * from './schemas';
typescript
// packages/types/src/user.ts
export interface User {
  id: string;
  email: string;
  name: string;
  role: 'admin' | 'user' | 'guest';
  createdAt: Date;
}

export interface CreateUserInput {
  email: string;
  name: string;
  role?: 'admin' | 'user' | 'guest';
}
typescript
// packages/types/src/schemas.ts
import { z } from 'zod';

export const UserSchema = z.object({
  id: z.string().uuid(),
  email: z.string().email(),
  name: z.string().min(1),
  role: z.enum(['admin', 'user', 'guest']),
  createdAt: z.coerce.date(),
});

export const CreateUserSchema = z.object({
  email: z.string().email(),
  name: z.string().min(1),
  role: z.enum(['admin', 'user', 'guest']).default('user'),
});
json
// packages/types/package.json
{
  "name": "@myapp/types",
  "version": "0.0.1",
  "private": true,
  "main": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.js"
    }
  },
  "scripts": {
    "build": "tsc",
    "dev": "tsc --watch",
    "typecheck": "tsc --noEmit"
  },
  "devDependencies": {
    "typescript": "^5.4.0"
  },
  "dependencies": {
    "zod": "^3.23.0"
  }
}
json
// packages/types/tsconfig.json
{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "include": ["src/**/*"]
}
typescript
// packages/types/src/index.ts
export * from './user';
export * from './schemas';
typescript
// packages/types/src/user.ts
export interface User {
  id: string;
  email: string;
  name: string;
  role: 'admin' | 'user' | 'guest';
  createdAt: Date;
}

export interface CreateUserInput {
  email: string;
  name: string;
  role?: 'admin' | 'user' | 'guest';
}
typescript
// packages/types/src/schemas.ts
import { z } from 'zod';

export const UserSchema = z.object({
  id: z.string().uuid(),
  email: z.string().email(),
  name: z.string().min(1),
  role: z.enum(['admin', 'user', 'guest']),
  createdAt: z.coerce.date(),
});

export const CreateUserSchema = z.object({
  email: z.string().email(),
  name: z.string().min(1),
  role: z.enum(['admin', 'user', 'guest']).default('user'),
});

App Package Using Shared Types

引用共享类型的应用包

json
// apps/web/package.json
{
  "name": "@myapp/web",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"
  },
  "dependencies": {
    "@myapp/types": "workspace:*",
    "next": "^14.0.0",
    "react": "^18.0.0"
  }
}
typescript
// apps/web/app/api/users/route.ts
import type { User, CreateUserInput } from '@myapp/types';
import { CreateUserSchema } from '@myapp/types';

export async function POST(request: Request) {
  const body = await request.json();
  
  // Validate with shared schema
  const input = CreateUserSchema.parse(body);
  
  // Create user...
  const user: User = await createUser(input);
  
  return Response.json(user);
}
json
// apps/web/package.json
{
  "name": "@myapp/web",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"
  },
  "dependencies": {
    "@myapp/types": "workspace:*",
    "next": "^14.0.0",
    "react": "^18.0.0"
  }
}
typescript
// apps/web/app/api/users/route.ts
import type { User, CreateUserInput } from '@myapp/types';
import { CreateUserSchema } from '@myapp/types';

export async function POST(request: Request) {
  const body = await request.json();
  
  // 使用共享Schema验证
  const input = CreateUserSchema.parse(body);
  
  // 创建用户...
  const user: User = await createUser(input);
  
  return Response.json(user);
}

Shared Utils Package

共享工具函数包

json
// packages/utils/package.json
{
  "name": "@myapp/utils",
  "version": "0.0.1",
  "private": true,
  "main": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "scripts": {
    "build": "tsc",
    "dev": "tsc --watch"
  },
  "devDependencies": {
    "typescript": "^5.4.0"
  }
}
typescript
// packages/utils/src/index.ts
export function formatDate(date: Date): string {
  return date.toISOString().split('T')[0];
}

export function slugify(text: string): string {
  return text
    .toLowerCase()
    .replace(/[^\w\s-]/g, '')
    .replace(/\s+/g, '-');
}

export function sleep(ms: number): Promise<void> {
  return new Promise(resolve => setTimeout(resolve, ms));
}
json
// packages/utils/package.json
{
  "name": "@myapp/utils",
  "version": "0.0.1",
  "private": true,
  "main": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "scripts": {
    "build": "tsc",
    "dev": "tsc --watch"
  },
  "devDependencies": {
    "typescript": "^5.4.0"
  }
}
typescript
// packages/utils/src/index.ts
export function formatDate(date: Date): string {
  return date.toISOString().split('T')[0];
}

export function slugify(text: string): string {
  return text
    .toLowerCase()
    .replace(/[^\w\s-]/g, '')
    .replace(/\s+/g, '-');
}

export function sleep(ms: number): Promise<void> {
  return new Promise(resolve => setTimeout(resolve, ms));
}

Common Commands

常用命令

bash
undefined
bash
undefined

Install all dependencies

安装所有依赖

pnpm install
pnpm install

Run all dev servers in parallel

并行启动所有开发服务

pnpm dev
pnpm dev

Build everything (respects dependency order)

构建所有模块(遵循依赖顺序)

pnpm build
pnpm build

Run tests across all packages

跨所有包运行测试

pnpm test
pnpm test

Add dependency to specific package

为指定包添加依赖

pnpm add zod --filter @myapp/types
pnpm add zod --filter @myapp/types

Add dev dependency to root

为根目录添加开发依赖

pnpm add -D prettier -w
pnpm add -D prettier -w

Run command in specific package

在指定包中运行命令

pnpm --filter @myapp/web dev
pnpm --filter @myapp/web dev

Run command in all packages matching pattern

在所有匹配规则的包中运行命令

pnpm --filter "@myapp/*" build
undefined
pnpm --filter "@myapp/*" build
undefined

Dependency Flow

依赖流向

packages/types (source of truth)
packages/utils (may import types)
apps/web, apps/api, apps/worker (import both)
Turborepo handles build order via
dependsOn: ["^build"]
- packages always build before apps that depend on them.
packages/types(类型可信来源)
packages/utils(可导入类型)
apps/web, apps/api, apps/worker(同时导入类型与工具)
Turborepo 通过
dependsOn: ["^build"]
处理构建顺序 - 包总是先于依赖它的应用构建。

.gitignore

.gitignore

gitignore
undefined
gitignore
undefined

Dependencies

依赖目录

node_modules/
node_modules/

Build outputs

构建产物

dist/ .next/ .turbo/
dist/ .next/ .turbo/

Environment

环境变量文件

.env .env.local .env.*.local
.env .env.local .env.*.local

IDE

IDE 配置

.idea/ .vscode/
.idea/ .vscode/

OS

系统文件

.DS_Store
undefined
.DS_Store
undefined

Best Practices

最佳实践

  1. Use
    workspace:*
    - Always for internal dependencies
  2. Types flow down - Shared types package is the source of truth
  3. One tsconfig.base - Extend from root, override only what's needed
  4. Atomic commits - Change types and consumers in same commit
  5. Cache builds - Turborepo caches unchanged packages
  1. 使用
    workspace:*
    - 内部依赖始终使用该标识
  2. 类型向下传递 - 共享类型包是唯一的类型可信来源
  3. 单一基础TS配置 - 所有包继承根目录配置,仅覆盖必要项
  4. 原子提交 - 在同一提交中修改类型及其所有消费者
  5. 缓存构建结果 - Turborepo 会缓存未修改的包构建产物

Common Mistakes

常见错误

  • Using
    ^1.0.0
    instead of
    workspace:*
    for internal deps
  • Building packages individually instead of
    turbo build
  • Circular dependencies between packages
  • Not including
    dist/
    in
    .gitignore
  • Forgetting
    dependsOn: ["^build"]
    in turbo.json
  • 内部依赖使用
    ^1.0.0
    而非
    workspace:*
  • 单独构建包而非使用
    turbo build
  • 包之间存在循环依赖
  • 未将
    dist/
    添加到
    .gitignore
  • 在 turbo.json 中遗漏
    dependsOn: ["^build"]

Related Skills

相关方案

  • TypeScript Strict
  • Environment Config
  • TypeScript 严格模式
  • 环境变量配置