Monorepo 实战:使用 pnpm + Turborepo 管理大型项目

随着项目规模的增长,你是否在多个仓库间疲于奔命?是否厌倦了重复的依赖安装和复杂的版本管理?是时候拥抱 Monorepo 了!

为什么我们需要 Monorepo?

在传统的 MultiRepo(多仓库)架构中,每个项目都有自己的独立仓库。这种模式在小型项目中工作良好,但随着业务复杂度增加,问题逐渐暴露:

  • 代码共享困难:公共组件、工具函数需要在多个仓库间复制粘贴

  • 依赖管理复杂:不同仓库的依赖版本不一致,导致诡异 bug

  • 跨项目变更繁琐:一个需求需要修改多个仓库,提交、测试、发布流程复杂

  • 开发环境配置重复:每个项目都要配置相似的构建工具、代码检查等

Monorepo(单一仓库) 将多个相关项目放在同一个代码仓库中,让它们可以共享代码、配置和工具链。

技术选型:为什么是 pnpm + Turborepo?

pnpm:下一代包管理器

与 npm/yarn 相比,pnpm 在 Monorepo 场景下具有显著优势:

  • 磁盘空间高效:使用硬链接和符号链接,相同依赖只安装一次

  • 安装速度快:依赖安装速度通常比 npm/yarn 快 2-3 倍

  • 严格的依赖管理:避免幽灵依赖和非法依赖访问

  • 内置 Workspace 支持:原生支持 Monorepo 工作空间

Turborepo:高性能的构建系统

Turborepo 专为 Monorepo 设计,解决了构建和任务执行的痛点:

  • 增量构建:只构建发生变化的部分

  • 并行执行:最大化利用多核 CPU

  • 依赖图感知:智能识别任务依赖关系

  • 远程缓存:团队共享构建缓存,避免重复工作

实战:从零搭建 Monorepo 项目

第一步:项目初始化

bash

复制代码
mkdir my-monorepo && cd my-monorepo
pnpm init

创建 pnpm-workspace.yaml 文件,定义工作空间:

yaml

复制代码
packages:
  - 'apps/*'
  - 'packages/*'
  - 'tools/*'

项目结构规划:

text

复制代码
my-monorepo/
├── apps/
│   ├── web-app/          # 前端应用
│   └── admin-app/        # 后台应用
├── packages/
│   ├── ui/               # UI 组件库
│   ├── utils/            # 工具函数
│   └── types/            # 类型定义
├── tools/
│   └── eslint-config/    # ESLint 配置
└── package.json

第二步:配置 Turborepo

安装 Turborepo:

bash

复制代码
pnpm add -Dw turbo

创建 turbo.json 配置文件:

json

复制代码
{
  "$schema": "https://turbo.build/schema.json",
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", "build/**"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    },
    "test": {
      "dependsOn": ["build"]
    },
    "lint": {
      "dependsOn": ["^build"]
    },
    "type-check": {
      "dependsOn": ["^build"]
    }
  }
}

第三步:创建共享包

1. 共享类型包 (packages/types)

bash

复制代码
mkdir -p packages/types
cd packages/types
pnpm init

json

复制代码
// packages/types/package.json
{
  "name": "@my-project/types",
  "version": "1.0.0",
  "types": "index.ts",
  "scripts": {
    "build": "tsc --build",
    "clean": "rm -rf dist"
  }
}

typescript

复制代码
// packages/types/index.ts
export interface User {
  id: string;
  name: string;
  email: string;
}

export interface ApiResponse<T> {
  data: T;
  success: boolean;
}

2. UI 组件库 (packages/ui)

bash

复制代码
mkdir -p packages/ui
cd packages/ui
pnpm init

json

复制代码
// packages/ui/package.json
{
  "name": "@my-project/ui",
  "version": "1.0.0",
  "main": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "scripts": {
    "build": "tsup src/index.tsx --format esm,cjs --dts --external react",
    "dev": "tsup src/index.tsx --format esm,cjs --dts --external react --watch"
  },
  "peerDependencies": {
    "react": "^18.0.0"
  },
  "devDependencies": {
    "@my-project/types": "workspace:*",
    "tsup": "^6.0.0"
  }
}

tsx

复制代码
// packages/ui/src/Button.tsx
import React from 'react';
import { ButtonHTMLAttributes } from '@my-project/types';

export const Button: React.FC<ButtonHTMLAttributes<HTMLButtonElement>> = ({
  children,
  ...props
}) => {
  return <button {...props}>{children}</button>;
};

第四步:创建应用

Web 应用 (apps/web-app)

bash

复制代码
mkdir -p apps/web-app
cd apps/web-app
pnpm init

json

复制代码
// apps/web-app/package.json
{
  "name": "@my-project/web-app",
  "version": "1.0.0",
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
    "type-check": "tsc --noEmit"
  },
  "dependencies": {
    "@my-project/ui": "workspace:*",
    "@my-project/types": "workspace:*"
  }
}

第五步:配置根目录脚本

json

复制代码
// package.json (根目录)
{
  "name": "my-monorepo",
  "private": true,
  "scripts": {
    "build": "turbo run build",
    "dev": "turbo run dev --parallel",
    "lint": "turbo run lint",
    "type-check": "turbo run type-check",
    "test": "turbo run test",
    "clean": "turbo run clean"
  },
  "devDependencies": {
    "turbo": "^1.10.0"
  }
}

高级特性与最佳实践

1. 依赖管理技巧

使用 pnpm 的过滤命令操作特定包:

bash

复制代码
# 为所有包添加依赖
pnpm add -r lodash

# 为特定包添加依赖
pnpm add vite --filter @my-project/web-app

# 使用工作空间协议,确保总是使用本地版本
"dependencies": {
  "@my-project/ui": "workspace:*"
}

2. 缓存配置优化

配置 Turborepo 远程缓存,实现团队构建共享:

bash

复制代码
# 在 CI/CD 环境中
turbo run build --api="http://my-turborepo.com" --token="$TURBO_TOKEN"

3. 环境变量管理

创建共享环境配置:

typescript

复制代码
// packages/config/src/env.ts
export const getEnvVars = () => ({
  apiUrl: process.env.API_URL || 'http://localhost:3000',
  environment: process.env.NODE_ENV || 'development',
});

4. CI/CD 流水线优化

yaml

复制代码
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: pnpm/action-setup@v2
      - uses: actions/setup-node@v3
        with:
          node-version: 18
          cache: 'pnpm'
      
      - run: pnpm install
      - run: pnpm type-check
      - run: pnpm lint
      - run: pnpm test
      - run: pnpm build
      
      # 缓存构建输出
      - uses: actions/cache@v3
        with:
          path: |
            apps/*/dist
            packages/*/dist
          key: ${{ runner.os }}-build-${{ hashFiles('**/package.json', '**/tsconfig.json') }}

性能对比:传统方式 vs pnpm + Turborepo

场景 传统 MultiRepo pnpm + Turborepo 提升
初始安装 每个项目独立安装 依赖共享,硬链接 减少 60% 磁盘空间
构建时间 全量构建 增量构建 + 缓存 减少 70% 构建时间
开发启动 分别启动 并行启动 减少 50% 启动时间
跨项目变更 手动同步 自动依赖追踪 减少 80% 协调成本

常见问题与解决方案

Q: 如何处理不同项目的不同 Node.js 版本?

A: 使用 .nvmrc 文件 + volta 或 nvm 管理:

bash

复制代码
# 在每个项目根目录创建 .nvmrc
echo "18" > apps/web-app/.nvmrc
echo "16" > apps/legacy-app/.nvmrc

Q: 如何管理私有包发布?

A: 使用 changesets 进行版本管理和发布:

bash

复制代码
pnpm add -Dw @changesets/cli
pnpm changeset init

总结

通过 pnpm + Turborepo 的组合,我们成功构建了一个高效、可扩展的 Monorepo 架构:

  • 🚀 极速体验:依赖安装和构建速度大幅提升

  • 🔗 代码共享:轻松实现组件和工具函数的复用

  • 🧩 模块化架构:清晰的包边界和依赖关系

  • ⚡ 开发效率:热重载、并行执行提升开发体验

  • 🛠 统一工具链:一致的代码质量和构建流程

Monorepo 不是银弹,但在中大型项目中,它能够显著提升团队的协作效率和开发体验。现在就开始重构你的项目架构,享受现代化开发工作流带来的便利吧!

相关推荐
崔庆才丨静觅11 分钟前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60611 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了1 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅1 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅1 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅2 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment2 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅2 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊2 小时前
jwt介绍
前端
爱敲代码的小鱼2 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax