2301 字
12 分钟
pnpm + Workspace + Turborepo:现代 Monorepo 完整指南(2024-2025)
pnpm + Workspace + Turborepo:现代 Monorepo 完整指南(2024-2025)
基于 2024-2025 年的最新最佳实践,涵盖 pnpm + Workspace 的完整配置, 以及 pnpm + Workspace + Turborepo 的高级方案,适合生产环境使用。
第一章:为什么选择 pnpm?
1.1 pnpm 相比 npm/yarn 的优势
pnpm 使用内容寻址存储(content-addressable store)来管理依赖,相比传统包管理器有以下优势:
- 磁盘空间效率:通过硬链接而不是复制来管理 node_modules,节省大量磁盘空间
- 更快的安装速度:并行安装和更好的缓存机制
- 严格的依赖隔离:防止幽灵依赖(phantom dependencies)
- 工作区支持:原生支持 monorepo,无需额外工具
1.2 pnpm vs Yarn Workspaces
pnpm 的优势:
✅ 更快的性能(硬链接 vs 软链接)
✅ 更小的磁盘占用
✅ 严格的依赖隔离
✅ 内置 workspace 支持
✅ 更活跃的维护
Yarn 的现状:
❌ 相对较慢
❌ 磁盘占用大
❌ 社区活跃度下降
❌ 生产环境不再推荐用于新项目
1.3 pnpm 的核心概念
// pnpm 的存储模式
~/.pnpm-store/
├── v3/
│ └── node_modules/
│ └── {hash}/
│ └── node_modules/
│ └── 实际包文件
// workspace 中的 node_modules
project/
└── node_modules/
└── package-name (symlink to ~/.pnpm-store)
第二章:pnpm + Workspace 基础配置
2.1 快速开始
使用以下命令初始化 pnpm monorepo:
# 创建项目目录
mkdir awesome-monorepo
cd awesome-monorepo
# 初始化 pnpm
pnpm init
# 设置 Node.js 版本要求
npm pkg set engines.node=">=16.13.0"
# 指定包管理器
npm pkg set packageManager="pnpm@latest"
# 支持 ES modules
npm pkg set type="module"
2.2 创建 pnpm-workspace.yaml
创建工作区配置文件:
# pnpm-workspace.yaml
packages:
- 'packages/*'
- 'apps/*'
这告诉 pnpm 将 packages 和 apps 目录下的所有文件夹视为工作区包。
2.3 根项目的 package.json
根项目应该有以下结构:
{
"name": "awesome-monorepo",
"version": "1.0.0",
"private": true,
"scripts": {
"preinstall": "npx only-allow pnpm",
"build": "pnpm --filter=@awesome/* run build",
"test": "pnpm --filter=@awesome/* run test",
"lint": "pnpm --filter=@awesome/* run lint",
"clean": "rimraf 'packages/*/{dist,node_modules}' && rimraf node_modules",
"changeset": "changeset",
"version-packages": "changeset version",
"release": "pnpm build && pnpm changeset publish"
},
"devDependencies": {
"@changesets/cli": "^2.26.0",
"rimraf": "^4.1.2",
"typescript": "^4.9.4",
"only-allow": "^1.1.1"
}
}
2.4 .npmrc 配置
# .npmrc
# 启用工作区包链接
auto-install-peers=true
# 严格的 peer 依赖检查
strict-peer-dependencies=true
# 处理 peer 依赖警告(如果需要)
# hoist-pattern[]=*eslint*
# hoist-pattern[]=*prettier*
2.5 项目目录结构
推荐的项目结构:
awesome-monorepo/
├── .changeset/
│ └── config.json
├── apps/
│ └── web-app/
│ ├── src/
│ ├── package.json
│ └── tsconfig.json
├── packages/
│ ├── ui-components/
│ │ ├── src/
│ │ ├── package.json
│ │ └── tsconfig.json
│ ├── utils/
│ │ ├── src/
│ │ ├── package.json
│ │ └── tsconfig.json
│ └── api-client/
│ ├── src/
│ ├── package.json
│ └── tsconfig.json
├── tools/
│ └── build-scripts/
├── package.json
├── pnpm-workspace.yaml
├── pnpm-lock.yaml
└── README.md
第三章:pnpm Workspace 依赖管理
3.1 workspace: 协议
在 package.json 中使用 workspace:* 协议来引用本地包:
{
"name": "@awesome/api-client",
"dependencies": {
"@awesome/utils": "workspace:*"
}
}
这意味着在开发时链接本地包,发布到 npm 时自动转换为实际版本。
3.2 添加依赖到特定包
使用 —filter 标志来管理特定包的依赖:
# 添加到单个包
pnpm add react react-dom --filter @awesome/ui-components
# 添加到多个包
pnpm add dayjs --filter "@awesome/*"
# 添加开发依赖
pnpm add -D @types/jest --filter @awesome/utils
# 添加本地包依赖
pnpm add @awesome/utils@workspace:* --filter @awesome/api-client
3.3 root 级别的依赖管理
在根项目安装共享的开发依赖:
# 在根项目安装
pnpm add -Dw typescript @types/node eslint prettier jest
# 在根项目安装通用依赖
pnpm add -w lodash axios
第四章:高级:pnpm + Workspace + Turborepo
4.1 为什么添加 Turborepo?
Turborepo 提供了 pnpm workspaces 之上的构建优化:
- 智能缓存:避免重复构建相同的代码
- 并行执行:同时运行多个任务
- 任务编排:定义任务之间的依赖关系
- 远程缓存:与团队共享构建工件
性能提升:初始构建 30 秒,缓存后 0.2 秒(150 倍加速)
4.2 安装 Turborepo
# 安装 Turborepo 作为开发依赖
pnpm add -Dw turbo
4.3 创建 turbo.json 配置
在项目根目录创建 turbo.json:
{
"$schema": "https://turborepo.org/schema.json",
"baseBranch": "origin/main",
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", "build/**", ".next/**", "!.next/cache/**"],
"env": [
"NODE_ENV",
"NEXT_PUBLIC_*"
]
},
"test": {
"dependsOn": ["^build"],
"outputs": ["coverage/**"]
},
"lint": {
"outputs": []
},
"dev": {
"cache": false,
"persistent": true
}
}
}
关键配置说明:
dependsOn: ["^build"]- 先构建依赖的包outputs- 要缓存的文件输出env- 影响缓存的环境变量cache: false- dev 任务不缓存(因为它一直在运行)
4.4 更新根项目 package.json
更新脚本使用 turbo:
{
"private": true,
"scripts": {
"build": "turbo run build",
"dev": "turbo run dev",
"test": "turbo run test",
"lint": "turbo run lint",
"clean": "turbo run clean && rm -rf node_modules pnpm-lock.yaml"
},
"devDependencies": {
"turbo": "latest"
},
"packageManager": "[email protected]"
}
4.5 在各个包中定义构建脚本
{
"name": "@awesome/ui-components",
"scripts": {
"build": "tsc && vite build",
"dev": "vite",
"test": "jest",
"lint": "eslint src/**/*.ts"
}
}
4.6 Turborepo 运行任务
# 构建所有包(智能缓存)
pnpm run build
# 开发模式(监听所有包)
pnpm run dev
# 构建特定包
turbo run build --filter=@awesome/ui-components
# 构建及其依赖
turbo run build --filter=@awesome/web-app...
# 并行运行(最多 4 个并发)
turbo run build --concurrency 4
# 显示构建图
turbo run build --graph
第五章:实战案例:完整的 Monorepo 设置
5.1 项目目录结构
awesome-monorepo/
├── .github/
│ └── workflows/
│ └── ci.yaml
├── apps/
│ ├── web/ # Next.js 应用
│ │ ├── src/
│ │ ├── package.json
│ │ └── tsconfig.json
│ └── api/ # NestJS API
│ ├── src/
│ ├── package.json
│ └── tsconfig.json
├── packages/
│ ├── ui/ # React 组件库
│ │ ├── src/
│ │ ├── package.json
│ │ └── tsconfig.json
│ ├── utils/ # 工具函数
│ │ ├── src/
│ │ ├── package.json
│ │ └── tsconfig.json
│ ├── types/ # 共享类型
│ │ ├── src/
│ │ ├── package.json
│ │ └── tsconfig.json
│ └── config/ # 共享配置
│ ├── eslint-config/
│ ├── typescript-config/
│ └── prettier-config/
├── .npmrc
├── pnpm-workspace.yaml
├── turbo.json
├── tsconfig.json
├── package.json
├── pnpm-lock.yaml
└── README.md
5.2 根 tsconfig.json
配置 TypeScript 路径映射:
{
"compilerOptions": {
"target": "ES2020",
"lib": ["DOM", "DOM.Iterable", "ES2020"],
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"baseUrl": ".",
"paths": {
"@awesome/ui": ["packages/ui/src"],
"@awesome/utils": ["packages/utils/src"],
"@awesome/types": ["packages/types/src"],
"@awesome/config/*": ["packages/config/*/src"]
}
}
}
5.3 各包的 package.json 示例
packages/utils/package.json
{
"name": "@awesome/utils",
"version": "1.0.0",
"description": "Shared utilities",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.es.js",
"require": "./dist/index.js",
"types": "./dist/index.d.ts"
}
},
"scripts": {
"build": "tsc && vite build",
"dev": "tsc --watch",
"test": "jest",
"lint": "eslint src"
}
}
apps/web/package.json
{
"name": "@awesome/web",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "eslint src"
},
"dependencies": {
"@awesome/ui": "workspace:*",
"@awesome/utils": "workspace:*",
"@awesome/types": "workspace:*",
"next": "^14.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/node": "^20.0.0",
"@types/react": "^18.0.0",
"typescript": "^5.0.0"
}
}
5.4 CI/CD 配置(GitHub Actions)
# .github/workflows/ci.yaml
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
with:
version: '8.15.0'
- uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm lint
- run: pnpm test
- run: pnpm build
第六章:版本管理与发布
6.1 使用 Changesets
Changesets 是管理 monorepo 版本和发布的最佳实践:
# 安装 changesets
pnpm add -Dw @changesets/cli
# 初始化
pnpm changeset init
# 创建 changeset
pnpm changeset
# 版本更新
pnpm changeset version
# 发布
pnpm changeset publish
6.2 .changeset/config.json
{
"$schema": "https://unpkg.com/@changesets/[email protected]/schema.json",
"changelog": ["@changesets/changelog-github", { "repo": "your-org/awesome-monorepo" }],
"commit": false,
"fixed": [],
"linked": [["@awesome/*"]],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": ["@awesome/build-tools"],
"snapshot": {
"useCalculatedVersion": true,
"prereleaseTemplate": "{tag}-{datetime}"
}
}
第七章:最佳实践与常见问题
7.1 依赖管理最佳实践
# 使用 workspace:* 链接本地包
pnpm add @awesome/utils@workspace:* --filter @awesome/api-client
# 提升常见的开发依赖到根
pnpm add -Dw typescript eslint prettier jest
# 为共享库指定 peer dependencies
# 在 ui 库的 package.json 中
{
"peerDependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
}
}
# 对多个包执行命令
pnpm --filter="@awesome/*" run build
# 只对改变的包执行命令(需要 git)
turbo run build --since origin/main
7.2 常见问题解决
问题 1:TypeScript 找不到路径
// 确保 tsconfig.json 中有路径映射
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@awesome/*": ["packages/*/src"]
}
}
}
问题 2:循环依赖
解决方案:
1. 创建 @awesome/types 包
2. 把共享类型放在 types 包中
3. 其他包引用 @awesome/types
4. 避免交叉引用
问题 3:构建缓存失效
# 清除 pnpm 缓存
pnpm store prune
# 清除 turbo 缓存
turbo run clean
# 重新安装
rm -rf node_modules pnpm-lock.yaml
pnpm install
第八章:性能指标
8.1 预期性能指标
初始构建:
- 小型项目(3-5 个包):5-15 秒
- 中型项目(10-20 个包):15-45 秒
- 大型项目(50+ 个包):1-5 分钟
缓存后:
- 所有项目:< 1 秒(通常 0.2-0.5 秒)
增量构建(改变 1 个文件):
- 受影响包及其依赖:2-10 秒
- 其他包:从缓存加载(< 0.5 秒)
8.2 监控构建性能
# 显示任务图
turbo run build --graph
# 显示构建详情
turbo run build --verbose
# 显示任务依赖
turbo run build --graph > graph.mmd
# 分析性能
turbo run build --profile
第九章:总结与建议
核心技术栈对比
pnpm alone:
✅ 快速、高效
✅ 原生 workspace 支持
❌ 缺乏高级构建优化
❌ 无缓存机制
pnpm + Turborepo:
✅ 快速、高效
✅ 智能缓存(150 倍加速)
✅ 任务编排和并行执行
✅ 远程缓存支持
✅ 生产环境推荐
其他方案:
- Nx:功能完整但学习曲线陡峭,配置复杂
- Lerna:已过时,性能较差,维护不活跃
- npm/yarn workspaces:功能基础,缺乏优化
2024-2025 推荐方案
强烈推荐:pnpm + Workspace + Turborepo
原因:
- 性能最优:pnpm 的高效依赖管理 + Turborepo 的智能缓存
- 生态活跃:两个工具都在积极维护和改进
- 易于使用:Turborepo 配置简单,学习成本低
- 灵活性:可以随时降级到原生 pnpm workspaces
- 社区支持:越来越多的大型项目采用这个方案
快速决策树
你需要 Monorepo 吗?
├─ 不需要 → 使用单一 git 仓库
└─ 需要 → 选择包管理器
├─ 有 CI/CD 需求?
│ ├─ 有 → 使用 pnpm + Turborepo
│ └─ 没有 → 使用 pnpm workspace
└─ 需要简单高效?→ 使用 pnpm + Turborepo
---🚀
pnpm + Workspace + Turborepo:现代 Monorepo 完整指南(2024-2025)
https://fuwari.vercel.app/posts/pnpm--workspace--turborepo现代-monorepo-完整指南2024-2025/
