Skip to content

Monorepo 实践

一、Monorepo 概述

1.1 什么是 Monorepo

Monorepo 是一种将多个项目放在同一个代码仓库中管理的策略。

# Polyrepo (多仓库)
├── repo-app/
├── repo-ui-lib/
├── repo-utils/
└── repo-server/

# Monorepo (单仓库)
└── monorepo/
    ├── apps/
    │   └── web/
    ├── packages/
    │   ├── ui/
    │   └── utils/
    └── package.json

1.2 优缺点

优点缺点
代码共享方便仓库体积大
统一依赖版本权限管理复杂
原子提交CI 构建时间长
重构更容易需要专门的工具支持

二、pnpm Workspace

2.1 配置

yaml
# pnpm-workspace.yaml
packages:
  - 'apps/*'
  - 'packages/*'
json
// package.json
{
  "name": "my-monorepo",
  "private": true,
  "scripts": {
    "dev": "pnpm -r run dev",
    "build": "pnpm -r run build",
    "lint": "pnpm -r run lint"
  }
}

2.2 目录结构

my-monorepo/
├── pnpm-workspace.yaml
├── package.json
├── apps/
│   ├── web/
│   │   ├── package.json
│   │   └── src/
│   └── admin/
│       ├── package.json
│       └── src/
└── packages/
    ├── ui/
    │   ├── package.json
    │   └── src/
    └── utils/
        ├── package.json
        └── src/

2.3 包配置

json
// packages/ui/package.json
{
  "name": "@myorg/ui",
  "version": "1.0.0",
  "main": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "import": "./dist/index.mjs",
      "require": "./dist/index.js"
    }
  },
  "scripts": {
    "build": "tsup src/index.ts --format cjs,esm --dts",
    "dev": "tsup src/index.ts --format cjs,esm --dts --watch"
  }
}

2.4 内部依赖

json
// apps/web/package.json
{
  "name": "@myorg/web",
  "dependencies": {
    "@myorg/ui": "workspace:*",
    "@myorg/utils": "workspace:^1.0.0"
  }
}
协议说明
workspace:*任意版本
workspace:^兼容版本
workspace:~补丁版本

2.5 常用命令

bash
# 安装依赖
pnpm install

# 为指定包添加依赖
pnpm add lodash --filter @myorg/ui

# 运行所有包的脚本
pnpm -r run build

# 运行指定包的脚本
pnpm --filter @myorg/web dev

# 并行运行
pnpm -r --parallel run dev

# 按依赖顺序运行
pnpm -r run build  # 自动拓扑排序

三、Turborepo

3.1 安装与配置

bash
npx create-turbo@latest
json
// turbo.json
{
  "$schema": "https://turbo.build/schema.json",
  "globalDependencies": ["**/.env.*local"],
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", ".next/**"]
    },
    "lint": {},
    "dev": {
      "cache": false,
      "persistent": true
    },
    "test": {
      "dependsOn": ["build"]
    }
  }
}

3.2 核心特性

依赖感知构建

json
{
  "pipeline": {
    "build": {
      "dependsOn": ["^build"]  // 先构建依赖包
    }
  }
}

增量构建 & 远程缓存

bash
# 启用远程缓存
npx turbo login
npx turbo link

# 构建 (命中缓存时跳过)
turbo run build

并行执行

bash
# 并行构建所有包
turbo run build --parallel

# 指定并发数
turbo run build --concurrency=10

3.3 过滤执行

bash
# 运行指定包
turbo run build --filter=@myorg/web

# 运行变更的包
turbo run build --filter=[HEAD^1]

# 运行依赖某个包的所有包
turbo run build --filter=...@myorg/ui

# 排除某个包
turbo run build --filter=!@myorg/docs

四、依赖管理策略

4.1 统一版本

json
// 根目录 package.json
{
  "pnpm": {
    "overrides": {
      "react": "^18.2.0",
      "react-dom": "^18.2.0"
    }
  }
}

4.2 共享配置

packages/
└── config/
    ├── eslint-config/
    │   └── package.json  # @myorg/eslint-config
    ├── tsconfig/
    │   └── package.json  # @myorg/tsconfig
    └── prettier-config/
        └── package.json  # @myorg/prettier-config
json
// apps/web/.eslintrc.js
module.exports = {
  extends: ['@myorg/eslint-config']
};

// apps/web/tsconfig.json
{
  "extends": "@myorg/tsconfig/react.json"
}

4.3 版本发布

bash
# 使用 Changesets
pnpm add -Dw @changesets/cli
pnpm changeset init

# 创建变更记录
pnpm changeset

# 更新版本
pnpm changeset version

# 发布
pnpm changeset publish

五、实战配置

5.1 完整目录结构

monorepo/
├── .github/
│   └── workflows/
│       └── ci.yml
├── apps/
│   ├── web/
│   │   ├── package.json
│   │   ├── tsconfig.json
│   │   └── src/
│   └── docs/
├── packages/
│   ├── ui/
│   ├── utils/
│   └── config/
│       ├── eslint-config/
│       ├── tsconfig/
│       └── prettier-config/
├── .changeset/
├── .eslintrc.js
├── .prettierrc
├── package.json
├── pnpm-workspace.yaml
├── turbo.json
└── tsconfig.json

5.2 CI 配置

yaml
# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - uses: pnpm/action-setup@v2
        with:
          version: 8

      - uses: actions/setup-node@v3
        with:
          node-version: 18
          cache: 'pnpm'

      - run: pnpm install

      - run: pnpm turbo run lint build test

六、高频面试题

Q1: Monorepo 的优势?

  1. 代码共享和复用容易
  2. 统一依赖版本,避免版本冲突
  3. 原子化提交,跨包变更一次提交
  4. 统一的开发规范和工具链

Q2: pnpm 相比 npm/yarn 的优势?

  1. 硬链接节省磁盘空间
  2. 非扁平 node_modules,依赖结构清晰
  3. 原生支持 Workspace
  4. 安装速度快

Q3: Turborepo 的核心优势?

  1. 智能缓存 (本地 + 远程)
  2. 增量构建
  3. 依赖感知的任务编排
  4. 并行执行

前端面试知识库