Monorepo 实战
约 703 字大约 2 分钟
MonorepopnpmTurborepo
2024-08-13
概述
Monorepo 是一种将多个项目存储在同一个代码仓库中的开发策略。
优势
| 优势 | 说明 |
|---|---|
| 代码共享 | 多个项目共享同一套代码 |
| 统一构建 | 一次构建所有项目 |
| 依赖管理 | 避免重复安装相同依赖 |
| 原子提交 | 跨项目更改一次性提交 |
| 统一工具链 | ESLint、Prettier、TypeScript 统一配置 |
挑战
- 仓库体积增长
- CI/CD 复杂性增加
- 权限管理困难
- 学习成本
pnpm Workspace
项目结构
/
├── pnpm-workspace.yaml
├── package.json
├── packages/
│ ├── shared/
│ │ ├── package.json
│ │ └── src/
│ ├── web/
│ │ ├── package.json
│ │ └── src/
│ └── api/
│ ├── package.json
│ └── src/
└── ...配置
# pnpm-workspace.yaml
packages:
- 'packages/*'
- 'apps/*'// package.json (根目录)
{
"name": "my-monorepo",
"private": true,
"scripts": {
"dev": "pnpm -r dev",
"build": "pnpm -r build",
"test": "pnpm -r test",
"lint": "pnpm -r lint"
}
}依赖引用
// packages/web/package.json
{
"dependencies": {
"@my-monorepo/shared": "workspace:*",
"lodash": "workspace:*"
}
}Turborepo
安装
pnpm add -D turbo配置
// turbo.json
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": ["**/.env.*local"],
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**"]
},
"dev": {
"cache": false,
"persistent": true
},
"lint": {
"outputs": []
},
"test": {
"dependsOn": ["build"],
"outputs": ["coverage/**"]
}
}
}任务调度
┌─────────────────────────────────────┐
│ build │
│ dependsOn: ^build │
└───────────────┬─────────────────────┘
│
┌───────────┼───────────┐
▼ ▼ ▼
┌───────┐ ┌───────┐ ┌───────┐
│ web │ │ api │ │ shared│
│build │ │build │ │build │
└───┬───┘ └───┬───┘ └───────┘
│ │
▼ ▼
┌───────┐ ┌───────┐
│ web │ │ api │
│ test │ │ test │
└───────┘ └───────┘nx
配置
// nx.json
{
"namedInputs": {
"default": ["{projectRoot}/**/*"],
"prod": ["!{projectRoot}/**/*.spec.ts"]
},
"targetDefaults": {
"build": {
"dependsOn": ["^build"],
"outputs": ["{workspaceRoot}/dist/{projectRoot}"]
}
}
}// project.json (单个项目)
{
"name": "web",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/web/src",
"targets": {
"build": {
"executor": "@nx/vite:build"
},
"dev": {
"executor": "@nx/vite:dev"
}
}
}依赖管理
外部依赖
# 安装到根目录 (所有项目共享)
pnpm add -w express
# 安装到特定项目
pnpm --filter web add axios
pnpm --filter api add express内部依赖
// packages/shared/src/index.ts
export const formatDate = (date: Date): string => {
return date.toISOString().split('T')[0];
};
// packages/web/src/App.tsx
import { formatDate } from '@my-monorepo/shared';代码共享策略
共享配置
/
├── eslint.base.js
├── prettier.config.js
├── tsconfig.base.json
└── packages/
└── shared/
└── ...// eslint.base.js
module.exports = {
extends: ['eslint:recommended'],
rules: { /* ... */ },
};
// packages/web/.eslintrc.js
module.exports = {
extends: ['../../eslint.base.js'],
};UI 组件库
packages/
└── ui/
├── src/
│ ├── Button/
│ ├── Input/
│ └── index.ts
├── package.json
└── tsconfig.jsonGit 工作流
提交规范
# 提交示例
git commit -m "feat(web): add login page"
git commit -m "fix(api): resolve auth issue"
git commit -m "chore shared: update types"Commitizen
pnpm add -D commitizen cz-conventional-changelog// package.json
{
"scripts": {
"commit": "cz"
},
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
}
}CI/CD
GitHub Actions
name: CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
with:
version: 8
- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm turbo build
- run: pnpm turbo test增量构建
# 只构建受影响的包
- name: Build affected
run: pnpm turbo build --filter="...[HEAD^1]"最佳实践
- 明确的结构: 目录结构清晰,命名规范
- 版本同步: 使用 workspace 协议统一管理版本
- 构建缓存: 配置 turbo/nx 缓存加速 CI
- 依赖优化: 合理拆分共享代码
- 类型检查: 在根目录运行一次 TypeScript 检查
- 代码规范: 统一的 ESLint、Prettier 配置
- 文档: 维护 CONTRIBUTING.md 说明开发流程
工具对比
| 工具 | 特点 | 学习曲线 |
|---|---|---|
| pnpm workspace | 轻量,基于 pnpm | 低 |
| Turborepo | Vercel 出品,任务调度强 | 中 |
| nx | 功能全面,视觉化 | 高 |
| Lerna | 老牌方案 | 低 |
