PNPM Workspace 管理 Monorepo 实践

2025/7/16 pnpmmonorepo

🌙 1. 项目结构设计

推荐的项目目录结构:

my-monorepo/
├── .npmrc
├── package.json
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── apps/
│   ├── web/                # 前端应用
│   ├── mobile/             # 移动端应用
│   └── desktop/            # 桌面应用
├── packages/
│   ├── ui/                 # 共享UI组件
│   ├── utils/              # 工具函数库
│   ├── config/             # 共享配置
│   └── api/                # API客户端
├── services/
│   ├── api-service/        # API服务
│   └── auth-service/       # 认证服务
└── scripts/                # 项目级脚本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

🌙 2. 核心配置文件

🌙 pnpm-workspace.yaml

packages:
  - 'apps/*'
  - 'packages/*'
  - 'services/*'
1
2
3
4

🌙 .npmrc

# PNPM 默认不会自动安装 peer dependencies,需要手动声明,开启自动安装 peer dependencies
auto-install-peers=true
# 允许使用相近版本的 peer dependencies, 不严格检查peer dependencies版本
strict-peer-dependencies=false
# 默认行为:PNPM 使用符号链接保持严格的依赖隔离, 开启后将依赖提升到 node_modules 根目录
shamefully-hoist=true
# 优先使用工作区本地包而非外部
prefer-workspace-packages=true
1
2
3
4
5
6
7
8

🌙 3. 依赖管理策略

🌙 3.1 共享依赖

根目录 package.json 中安装公共依赖:

pnpm add -w typescript eslint prettier @types/node
1

🌙 3.2 子包依赖

为特定子包添加依赖:

  • 从外部注册表安装:会从 npm registry 或其他配置的源下载包
  • 版本锁定:会在目标包的 package.json 中记录具体版本号
  • 存储位置:安装在目标包的 node_modules 中
pnpm add <package-name> --filter <target-package>

# 例如
pnpm add lodash@4.17.21 --filter web
1
2
3
4

会在 apps/web/package.json 中生成:

{
  "dependencies": {
    "lodash": "4.17.21"
  }
}
1
2
3
4
5

🌙 3.3 工作区内部依赖

  • 从本地工作区链接:会链接到工作区内的其他包
  • 版本通配:使用 workspace:* 或 workspace:^ 等特殊版本标识
  • 存储位置:通过符号链接指向工作区内的包
pnpm add <workspace-package> --filter <target-package> --workspace

# 例如
pnpm add @project/ui --filter web --workspace
1
2
3
4

会在 apps/web/package.json 中生成:

{
  "dependencies": {
    "@project/ui": "workspace:*"
  }
}
1
2
3
4
5

🌙 4. 脚本管理方案

🌙 4.1 根目录 package.json

{
  "scripts": {
    "prepare": "pnpm -r run prepare",
    "build": "pnpm -r run build",
    "test": "pnpm -r run test",
    "lint": "pnpm -r run lint",
    "dev": "pnpm run --parallel dev",
    "changeset": "changeset",
    "version-packages": "changeset version",
    "release": "changeset publish"
  }
}
1
2
3
4
5
6
7
8
9
10
11
12

🌙 4.2 子包脚本示例

{
  "scripts": {
    "build": "tsc",
    "test": "vitest run",
    "lint": "eslint src",
    "dev": "vite dev",
    "prepare": "husky install"
  }
}
1
2
3
4
5
6
7
8
9

🌙 5. 版本管理与发布

🌙 5.1 安装 Changesets

pnpm add -Dw @changesets/cli
pnpm changeset init
1
2

🌙 5.2 版本发布流程

  1. 创建变更集:
    pnpm changeset
    
    1
  2. 更新版本:
    pnpm version-packages
    
    1
  3. 发布:
    pnpm release
    
    1

当使用 changeset 发布时,workspace:* 会被自动转换为具体的版本号:

// 开发时
"dependencies": {
  "@project/ui": "workspace:*"
}
// 发布后
"dependencies": {
  "@project/ui": "1.2.3"
}
1
2
3
4
5
6
7
8

🌙 6. 开发环境配置

🌙 6.1 跨包调试

apps/web/package.json 中:

{
  "dependencies": {
    "@project/ui": "workspace:*"
  }
}
1
2
3
4
5

🌙 6.2 TS 路径映射

tsconfig.base.json:

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@project/*": ["packages/*/src"],
      "@apps/*": ["apps/*/src"]
    }
  }
}
1
2
3
4
5
6
7
8
9

🌙 7. 代码质量保障

🌙 7.1 统一代码风格

根目录 .eslintrc.js:

module.exports = {
  extends: ['eslint-config-project'],
  parserOptions: {
    project: './tsconfig.json',
    tsconfigRootDir: __dirname,
  },
};
1
2
3
4
5
6
7

🌙 7.2 共享配置

packages/config/eslint-preset:

module.exports = {
  extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
  rules: {
    // 共享规则
  }
};
1
2
3
4
5
6

🌙 8. 性能优化技巧

🌙 8.1 过滤命令执行

pnpm --filter "web" run build
pnpm --filter "@project/*" run test
1
2

🌙 8.2 并行执行

pnpm run --parallel dev
pnpm run --stream --filter "!api-service" build
1
2

🌙 9. CI/CD 集成

🌙 9.1 GitHub Actions 示例

.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
        with:
          version: 7
      - run: pnpm install
      - run: pnpm run test --filter "[affected]"

  build:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: pnpm/action-setup@v2
      - run: pnpm install
      - run: pnpm run build --filter "[affected]"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

🌙 10. 高级技巧

🌙 10.1 任务管道

pnpm --filter "web...utils" run build
1

🌙 10.2 依赖图可视化

pnpm m ls --graph
1

🌙 10.3 依赖检查

pnpm why react --filter web
1

🌙 11. 常见问题解决

🌙 11.1 幽灵依赖问题

  • 现象:使用了未在 package.json 声明的依赖

  • 解决:使用 shamefully-hoist=true,显式声明所有依赖

解决方案:

  1. 确保所有依赖都显式声明
  2. 使用 pnpm.overrides 统一版本

🌙 11.2 循环依赖

  • 现象:A 包依赖 B 包,同时 B 包又依赖 A 包

  • 解决:重构代码结构,提取公共逻辑到第三个包

检测工具:

pnpm m ls --cycle
1

🌙 11.3 版本冲突

  • 现象:不同包要求不同版本的同一依赖

  • 解决:在根目录使用 pnpm.overrides 统一版本

使用 resolutions 字段强制版本:

{
  "pnpm": {
    "overrides": {
      "react": "18.2.0",
      "react-dom": "18.2.0"
    }
  }
}
1
2
3
4
5
6
7
8

🌙 11.4 实践建议

  • 明确区分:第三方依赖用常规安装,内部共享包用 --workspace
  • 版本控制:内部依赖始终使用 workspace:* 保持同步
  • 依赖隔离:每个包的依赖应尽可能独立,减少共享依赖
  • 定期检查:使用 pnpm why 检查依赖关系
  • CI验证:在 CI 中测试不带 shamefully-hoist 的构建

🌙 12. 推荐插件

  1. @changesets/cli - 版本管理 (opens new window)
  2. @preconstruct/cli - 包构建 (opens new window)
  3. Turbo - 任务缓存和优化 (opens new window)

Changeset Github Action (opens new window)

next.js - 使用 trubo + pnpm workspace 管理的monorepo (opens new window)

mantine - 优秀的monorepo管理的react ui组件库 (opens new window)