pnpm workspace 与 monorepo 实战教程 2026

💡 什么是 Monorepo? Monorepo(单一仓库)是一种项目管理策略,将多个相关项目放在同一个仓库中管理。pnpm workspace 是实现 Monorepo 的最佳工具之一。
本文将带你从 0 到 1 掌握:
- ✅ Monorepo 架构设计原则
- ✅ pnpm workspace 配置详解
- ✅ 依赖共享与版本管理
- ✅ 性能优化策略
- ✅ CI/CD 集成方案
- ✅ 实际项目案例
一、为什么选择 pnpm workspace?
1.1 Monorepo vs Multi-repo
| 维度 | Monorepo | Multi-repo |
|---|---|---|
| 代码共享 | ✅ 模块间轻松共享 | ❌ 需要 npm 发布 |
| 依赖管理 | ✅ 统一管理,版本一致 | ❌ 重复安装,版本可能不一致 |
| 代码复用 | ✅ 共享组件、工具函数 | ❌ 重复造轮子 |
| CI/CD | ✅ 增量构建,效率高 | ❌ 各自构建,重复工作 |
| 学习成本 | ✅ 一处学习,处处可用 | ❌ 多个仓库,各自为政 |
| 仓库大小 | ⚠️ 可能很大 | ✅ 每个仓库较小 |
| 权限管理 | ⚠️ 权限较粗粒度 | ✅ 精细权限控制 |
1.2 pnpm vs npm/yarn
| 特性 | pnpm | npm | yarn |
|---|---|---|---|
| 磁盘空间 | ⭐⭐⭐⭐⭐(硬链接共享) | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| 安装速度 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| workspace 支持 | ✅ 原生支持 | ⚠️ 有限支持 | ✅ 支持 |
| 安全性 | ⭐⭐⭐⭐⭐(严格隔离) | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 缓存机制 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| 社区支持 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
1.3 pnpm 的核心优势
# pnpm 使用硬链接和符号链接来共享依赖
# 所有版本的相同包只存储一次
pnpm install
# 传统 npm/yarn 每个项目独立安装
npm install
# 或
yarn install空间对比示例:
# 10 个项目使用相同的 100 个依赖
# npm/yarn: 10 * 100 = 1000 份依赖副本
# pnpm: 100 份依赖(通过硬链接共享)
# 实际空间节省可达 80-90%二、项目结构设计
2.1 推荐的 monorepo 结构
my-monorepo/
├── .git/ # Git 仓库
├── .github/ # GitHub Actions 配置
├── .vscode/ # VS Code 配置
├── packages/ # 工作区包目录
│ ├── app/ # 主应用(可执行)
│ │ ├── src/
│ │ ├── package.json
│ │ └── vite.config.ts
│ ├── components/ # 共享组件库
│ │ ├── src/
│ │ └── package.json
│ ├── utils/ # 工具函数库
│ │ ├── src/
│ │ └── package.json
│ └── ui/ # UI 组件库
│ ├── src/
│ └── package.json
├── apps/ # 应用目录(可选)
│ └── admin/ # 管理后台
│ ├── src/
│ └── package.json
├── tools/ # 工具脚本
│ ├── scripts/
│ └── configs/
├── .gitignore
├── package.json # 根 package.json
├── pnpm-workspace.yaml # pnpm workspace 配置
├── tsconfig.json # 根 TypeScript 配置
├── tsconfig.base.json # TypeScript 基础配置
└── README.md2.2 各目录职责说明
| 目录 | 职责 | 是否必须 |
|---|---|---|
| packages/ | 可复用的包(组件、工具、UI) | ✅ |
| apps/ | 独立运行的应用 | ⚠️ 可选 |
| tools/ | 项目工具脚本和配置 | ⚠️ 可选 |
| pnpm-workspace.yaml | workspace 配置 | ✅ |
| tsconfig.base.json | 共享的 TypeScript 配置 | ✅ |
三、pnpm workspace 配置详解
3.1 初始化 workspace
# 1. 创建项目目录
mkdir my-monorepo && cd my-monorepo
# 2. 初始化 pnpm workspace
pnpm init
# 3. 创建 pnpm-workspace.yaml
touch pnpm-workspace.yaml
# 4. 创建 packages 目录
mkdir packages apps3.2 pnpm-workspace.yaml 配置
# pnpm-workspace.yaml
packages:
# 所有包目录
- 'packages/**'
# 应用目录
- 'apps/**'
# 可选:排除某些目录
- '!**/node_modules'
- '!**/.git'
# 配置 workspace 根目录的依赖安装
# 这些依赖会安装在根 node_modules
# 可被所有子包共享
# 注意:建议使用 workspace 协议代替
# 启用严格模式(推荐)
# strict: true
# 配置 peer dependencies 自动安装
# auto-install-peers: true
# 配置依赖注入(高级特性)
# inject:
# - package: eslint
# version: ^8.0.03.3 根 package.json 配置
{
"name": "@my-monorepo/root",
"private": true,
"version": "1.0.0",
"description": "My Monorepo",
"scripts": {
"build": "pnpm --filter \"./packages/**\" build",
"dev": "pnpm --filter @my-monorepo/app dev",
"test": "pnpm --filter \"./packages/**\" test",
"lint": "pnpm --filter \"./packages/**\" lint",
"clean": "pnpm --filter \"./packages/**\" clean",
"publish": "pnpm --filter \"./packages/**\" publish --access public"
},
"devDependencies": {
"@types/node": "^20.0.0",
"typescript": "^5.0.0",
"eslint": "^8.0.0",
"prettier": "^3.0.0"
},
"pnpm": {
"overrides": {
"lodash": "^4.17.21",
"react": "^18.0.0"
}
}
}3.4 子包 package.json 配置
// packages/components/package.json
{
"name": "@my-monorepo/components",
"version": "1.0.0",
"description": "共享组件库",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"scripts": {
"build": "tsc",
"dev": "tsc --watch",
"test": "vitest run",
"lint": "eslint ."
},
"dependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
"devDependencies": {
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"typescript": "^5.0.0"
}
}四、依赖管理策略
4.1 workspace 协议(推荐)
// packages/app/package.json
{
"dependencies": {
"@my-monorepo/components": "workspace:^",
"@my-monorepo/utils": "workspace:^",
"@my-monorepo/ui": "workspace:1.0.0"
}
}workspace 协议版本说明:
| 版本格式 | 含义 |
|---|---|
workspace:* | 任意版本 |
workspace:^ | 兼容版本(^1.0.0) |
workspace:~ | 补丁版本(~1.0.0) |
workspace:1.0.0 | 精确版本 |
workspace:../utils | 相对路径 |
4.2 安装依赖
# 安装所有依赖(根目录执行)
pnpm install
# 为特定包安装依赖
pnpm --filter @my-monorepo/app install
# 在特定包目录下安装
cd packages/app
pnpm install
# 添加新依赖到特定包
pnpm --filter @my-monorepo/app add lodash
# 添加开发依赖到根目录
pnpm add -Dw typescript eslint
# 添加共享依赖(根目录)
pnpm add -w react react-dom
# 从特定包移除依赖
pnpm --filter @my-monorepo/app remove lodash
# 更新所有依赖
pnpm update
# 更新特定包的依赖
pnpm --filter @my-monorepo/components update
# 检查过时依赖
pnpm outdated
# 清理未使用的依赖
pnpm prune4.3 依赖安装策略
# 策略 1:根目录安装共享依赖
pnpm add -w react react-dom typescript
# 策略 2:特定包安装专属依赖
pnpm --filter @my-monorepo/app add lodash
# 策略 3:使用 workspace 协议引用内部包
pnpm --filter @my-monorepo/app add @my-monorepo/components@workspace
# 策略 4:安装所有子包的依赖
pnpm install
# 策略 5:只安装生产依赖
pnpm install --prod
# 策略 6:使用 lockfile 精确安装
pnpm install --frozen-lockfile4.4 依赖缓存优化
# 查看 pnpm 存储路径
pnpm config get store-dir
# 输出: /Users/username/Library/pnpm/store/v3
# 设置自定义存储路径
pnpm config set store-dir /path/to/custom/store
# 清理缓存
pnpm store prune
# 查看缓存大小
pnpm store status
# 验证缓存完整性
pnpm store verify
# 从缓存中删除特定包
pnpm store rm lodash五、TypeScript 配置
5.1 根 tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
"lib": ["ES2020", "DOM"],
"baseUrl": ".",
"paths": {
"@my-monorepo/*": ["packages/*/src"]
},
"types": ["node"]
},
"files": [],
"references": [
{ "path": "./packages/components" },
{ "path": "./packages/utils" },
{ "path": "./packages/ui" }
]
}5.2 子包 tsconfig.json
// packages/components/tsconfig.json
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"composite": true,
"declaration": true,
"declarationMap": true,
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}5.3 路径别名配置
// tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@my-monorepo/components": ["packages/components/src"],
"@my-monorepo/utils": ["packages/utils/src"],
"@my-monorepo/ui": ["packages/ui/src"],
"@my-monorepo/app/*": ["packages/app/src/*"]
}
}
}使用示例:
// packages/app/src/App.tsx
import { Button } from '@my-monorepo/components';
import { formatDate } from '@my-monorepo/utils';
import { Card } from '@my-monorepo/ui';
function App() {
return (
<Card>
<Button onClick={() => console.log(formatDate(new Date()))}>
Click Me
</Button>
</Card>
);
}六、脚本管理与执行
6.1 根目录脚本
{
"scripts": {
// 构建所有包
"build": "pnpm --filter \"./packages/**\" build",
// 构建特定包及其依赖
"build:app": "pnpm --filter @my-monorepo/app... build",
// 并行构建
"build:parallel": "pnpm --parallel --filter \"./packages/**\" build",
// 开发模式
"dev": "pnpm --filter @my-monorepo/app dev",
// 并行开发多个包
"dev:all": "pnpm --parallel --filter \"./packages/**\" dev",
// 测试
"test": "pnpm --filter \"./packages/**\" test",
// 测试特定包
"test:components": "pnpm --filter @my-monorepo/components test",
// 代码检查
"lint": "pnpm --filter \"./packages/**\" lint",
// 格式化
"format": "prettier --write \"**/*.{ts,tsx,js,jsx,json}\"",
// 清理构建产物
"clean": "pnpm --filter \"./packages/**\" clean",
// 发布所有包
"publish": "pnpm --filter \"./packages/**\" publish --access public",
// 版本管理
"version:major": "pnpm --filter \"./packages/**\" version major",
"version:minor": "pnpm --filter \"./packages/**\" version minor",
"version:patch": "pnpm --filter \"./packages/**\" version patch"
}
}6.2 执行脚本的方式
# 执行根目录脚本
pnpm run build
# 执行特定包的脚本
pnpm --filter @my-monorepo/app run dev
# 在包目录下直接执行
cd packages/app
pnpm run dev
# 并行执行多个脚本
pnpm --parallel --filter @my-monorepo/components --filter @my-monorepo/utils run dev
# 执行依赖包的脚本(会先执行依赖构建)
pnpm --filter @my-monorepo/app... run build
# 跳过缓存执行
pnpm --filter @my-monorepo/app run build --no-cache6.3 脚本执行顺序控制
# 使用 --filter 控制执行顺序
pnpm --filter @my-monorepo/utils build
pnpm --filter @my-monorepo/components build
pnpm --filter @my-monorepo/ui build
pnpm --filter @my-monorepo/app build
# 使用 ... 自动解析依赖顺序
pnpm --filter @my-monorepo/app... build
# 使用 workspace 协议时,pnpm 会自动处理依赖顺序七、性能优化策略
7.1 缓存策略
# 启用构建缓存(vite/webpack 等)
# vite.config.ts
export default {
build: {
cacheDir: '../../node_modules/.vite'
}
}
# 设置 pnpm 缓存大小限制
pnpm config set store-max-size 10GB
# 清理旧缓存
pnpm store prune
# 使用 faster 模式(跳过某些验证)
pnpm install --faster
# 启用预构建(pnpm 8+)
pnpm install --prefer-offline7.2 增量构建
# 使用 turbo 进行增量构建
npm install turbo --save-dev
# turbo.json 配置
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"test": {
"dependsOn": ["build"],
"outputs": []
},
"lint": {
"outputs": []
}
}
}
# 运行 turbo 构建
npx turbo build
# 运行 turbo 测试
npx turbo test
# 强制重新构建
npx turbo build --force7.3 内存优化
# 设置 Node.js 内存限制
export NODE_OPTIONS="--max-old-space-size=4096"
# 优化 tsconfig 以减少内存使用
{
"compilerOptions": {
"skipLibCheck": true,
"noEmit": true,
"incremental": true
}
}
# 使用 swc 替代 tsc 进行更快的编译
pnpm add -D @swc/cli @swc/core7.4 依赖分析
# 分析依赖树
pnpm ls
# 分析特定包的依赖
pnpm ls --filter @my-monorepo/app
# 查看重复依赖
pnpm dedupe --list
# 执行依赖去重
pnpm dedupe
# 检查未使用的依赖
pnpm install --check
# 查看依赖大小
pnpm why lodash
# 查看依赖统计
pnpm stats八、CI/CD 集成
8.1 GitHub Actions 配置
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install pnpm
run: npm install -g pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build packages
run: pnpm build
- name: Run tests
run: pnpm test
- name: Run lint
run: pnpm lint
deploy:
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install pnpm
run: npm install -g pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build and deploy
run: pnpm deploy8.2 缓存策略优化
- name: Cache node_modules
uses: actions/cache@v4
with:
path: |
node_modules
**/node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-node-
- name: Cache build outputs
uses: actions/cache@v4
with:
path: |
packages/**/dist
apps/**/dist
key: ${{ runner.os }}-build-${{ github.sha }}
restore-keys: |
${{ runner.os }}-build-8.3 增量 CI 配置
- name: Run turbo build
run: npx turbo build --cache-dir=.turbo
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
- name: Upload turbo cache
uses: actions/upload-artifact@v4
with:
name: turbo-cache
path: .turbo九、常见问题与解决方案
Q1:workspace 协议找不到包?
# 检查包名是否正确
pnpm --filter @my-monorepo/app ls
# 检查 workspace 配置
cat pnpm-workspace.yaml
# 确保包已在 workspace 中
pnpm list --filter "@my-monorepo/*"
# 重新安装依赖
pnpm install
# 检查 package.json 中的 workspace 协议
# 确保格式正确:workspace:^ 或 workspace:*Q2:TypeScript 路径别名不生效?
// 确保配置了正确的路径别名
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@my-monorepo/*": ["packages/*/src"]
}
}
}
// 如果使用 Vite,需要配置 vite.config.ts
import { defineConfig } from 'vite';
import path from 'path';
export default defineConfig({
resolve: {
alias: {
'@my-monorepo': path.resolve(__dirname, './packages')
}
}
});Q3:依赖版本冲突?
# 使用 pnpm overrides 强制统一版本
{
"pnpm": {
"overrides": {
"react": "^18.0.0",
"react-dom": "^18.0.0",
"lodash": "^4.17.21"
}
}
}
# 查看依赖树
pnpm ls --filter @my-monorepo/app
# 分析重复依赖
pnpm dedupe --list
# 执行去重
pnpm dedupeQ4:构建速度慢?
# 使用 turbo 增量构建
npx turbo build
# 启用缓存
pnpm install --prefer-offline
# 使用 swc 替代 tsc
pnpm add -D @swc/cli
# 减少内存使用
export NODE_OPTIONS="--max-old-space-size=8192"
# 并行构建
pnpm --parallel buildQ5:如何发布包?
# 发布所有包
pnpm --filter "./packages/**" publish --access public
# 发布特定包
pnpm --filter @my-monorepo/components publish --access public
# 发布到 npm 私有仓库
pnpm --filter @my-monorepo/components publish --registry https://npm.pkg.github.com
# 使用 np 进行版本管理
pnpm add -D np
npx np --filter @my-monorepo/components十、完整项目示例
10.1 项目初始化脚本
#!/bin/bash
# init-monorepo.sh
# 创建目录结构
mkdir -p my-monorepo/{packages,apps,tools/scripts}
cd my-monorepo
# 初始化 pnpm
pnpm init -y
# 创建 pnpm-workspace.yaml
cat > pnpm-workspace.yaml << 'EOF'
packages:
- 'packages/**'
- 'apps/**'
EOF
# 创建根 package.json
cat > package.json << 'EOF'
{
"name": "@my-monorepo/root",
"private": true,
"version": "1.0.0",
"scripts": {
"build": "pnpm --filter \"./packages/**\" build",
"dev": "pnpm --filter @my-monorepo/app dev",
"test": "pnpm --filter \"./packages/**\" test",
"lint": "pnpm --filter \"./packages/**\" lint"
},
"devDependencies": {
"typescript": "^5.0.0",
"eslint": "^8.0.0",
"prettier": "^3.0.0"
}
}
EOF
# 创建 tsconfig.json
cat > tsconfig.json << 'EOF'
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"baseUrl": ".",
"paths": {
"@my-monorepo/*": ["packages/*/src"]
}
}
}
EOF
# 创建示例包
mkdir -p packages/components/src
cat > packages/components/package.json << 'EOF'
{
"name": "@my-monorepo/components",
"version": "1.0.0",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"scripts": {
"build": "tsc",
"dev": "tsc --watch",
"test": "vitest run"
},
"dependencies": {
"react": "^18.0.0"
},
"devDependencies": {
"@types/react": "^18.0.0",
"typescript": "^5.0.0",
"vitest": "^1.0.0"
}
}
EOF
cat > packages/components/tsconfig.json << 'EOF'
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"composite": true,
"declaration": true,
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"]
}
EOF
cat > packages/components/src/index.ts << 'EOF'
export const Button = ({ children }: { children: React.ReactNode }) => {
return <button>{children}</button>;
};
EOF
echo "Monorepo 项目初始化完成!"10.2 使用脚本
# 运行初始化脚本
bash init-monorepo.sh
# 安装依赖
pnpm install
# 构建所有包
pnpm build
# 启动开发服务器
pnpm dev结语
pnpm workspace 是构建大规模前端项目的利器,它不仅解决了依赖管理的痛点,还提供了优秀的性能和缓存机制。通过合理的架构设计和脚本管理,你可以轻松管理包含数十个包的复杂项目。
推荐阅读:
🚀 提示: 开始你的 monorepo 之旅吧!从一个小型项目开始,逐步扩展,你会发现管理多个包变得前所未有的轻松。
延伸阅读
免责声明
本文仅供技术交流和学习参考。涉及第三方服务的链接可能包含 sponsored 标记,请自行核实服务条款、价格和可用性,并遵守当地法律法规。