Monorepo 实践
一、Monorepo 概述
1.1 什么是 Monorepo
Monorepo 是一种将多个项目放在同一个代码仓库中管理的策略。
# Polyrepo (多仓库)
├── repo-app/
├── repo-ui-lib/
├── repo-utils/
└── repo-server/
# Monorepo (单仓库)
└── monorepo/
├── apps/
│ └── web/
├── packages/
│ ├── ui/
│ └── utils/
└── package.json1.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@latestjson
// 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=103.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-configjson
// 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.json5.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 的优势?
- 代码共享和复用容易
- 统一依赖版本,避免版本冲突
- 原子化提交,跨包变更一次提交
- 统一的开发规范和工具链
Q2: pnpm 相比 npm/yarn 的优势?
- 硬链接节省磁盘空间
- 非扁平 node_modules,依赖结构清晰
- 原生支持 Workspace
- 安装速度快
Q3: Turborepo 的核心优势?
- 智能缓存 (本地 + 远程)
- 增量构建
- 依赖感知的任务编排
- 并行执行