Skip to content

Monorepo - pnpm Workspace ​

项目结构 ​

my-monorepo/
├── pnpm-workspace.yaml      # 工作区定义
├── package.json
├── packages/                 # 公共包
│   ├── ui/                   # UI 组件库
│   ├── utils/                # 工具函数
│   └── eslint-config/        # ESLint 共享配置
├── apps/                     # 应用
│   ├── web/                  # 前端应用
│   └── docs/                 # 文档站点
└── .npmrc
my-monorepo/
├── pnpm-workspace.yaml      # 工作区定义
├── package.json
├── packages/                 # 公共包
│   ├── ui/                   # UI 组件库
│   ├── utils/                # 工具函数
│   └── eslint-config/        # ESLint 共享配置
├── apps/                     # 应用
│   ├── web/                  # 前端应用
│   └── docs/                 # 文档站点
└── .npmrc

配置文件 ​

pnpm-workspace.yaml ​

yaml
packages:
  - 'apps/*'
  - 'packages/*'
packages:
  - 'apps/*'
  - 'packages/*'

.npmrc ​

shamefully-hoist=true   # 将依赖提升到 root node_modules(兼容某些工具)
strict-peer-dependencies=false
shamefully-hoist=true   # 将依赖提升到 root node_modules(兼容某些工具)
strict-peer-dependencies=false

常用命令 ​

bash
# 在所有包中执行命令
pnpm -r run build

# 过滤特定包
pnpm --filter @my/ui run build
pnpm --filter "@my/*" run test

# 将 utils 安装到 web 应用的依赖中
pnpm --filter @my/web add @my/utils

# 所有包安装依赖
pnpm install
# 在所有包中执行命令
pnpm -r run build

# 过滤特定包
pnpm --filter @my/ui run build
pnpm --filter "@my/*" run test

# 将 utils 安装到 web 应用的依赖中
pnpm --filter @my/web add @my/utils

# 所有包安装依赖
pnpm install

workspace 协议 ​

json
// packages/ui/package.json
{
  "name": "@my/ui",
  "version": "1.0.0",
  "exports": {
    ".": "./src/index.ts",
    "./button": "./src/button/index.ts"
  }
}

// apps/web/package.json(引用)
{
  "dependencies": {
    "@my/ui": "workspace:*",       // 始终使用本地最新版本
    "@my/utils": "workspace:^1.0.0" // 范围匹配
  }
}
// packages/ui/package.json
{
  "name": "@my/ui",
  "version": "1.0.0",
  "exports": {
    ".": "./src/index.ts",
    "./button": "./src/button/index.ts"
  }
}

// apps/web/package.json(引用)
{
  "dependencies": {
    "@my/ui": "workspace:*",       // 始终使用本地最新版本
    "@my/utils": "workspace:^1.0.0" // 范围匹配
  }
}

发布时会自动将 workspace: 协议替换为实际版本号。

依赖管理策略 ​

bash
# 公共依赖安装在 root(提升)
pnpm add -w typescript -D

# 特定包安装
pnpm --filter @my/web add react

# 更新所有包中的公共依赖
pnpm up -r typescript
# 公共依赖安装在 root(提升)
pnpm add -w typescript -D

# 特定包安装
pnpm --filter @my/web add react

# 更新所有包中的公共依赖
pnpm up -r typescript

node_modules 结构 ​

node_modules/
├── .pnpm/          # 内容寻址存储,所有依赖实际存放在这里
├── react -> .pnpm/react@18.2.0/node_modules/react
├── vue -> .pnpm/vue@3.4.0/node_modules/vue
├── @my/
│   ├── ui -> ../../packages/ui          # 软链接到本地包
│   └── utils -> ../../packages/utils
node_modules/
├── .pnpm/          # 内容寻址存储,所有依赖实际存放在这里
├── react -> .pnpm/react@18.2.0/node_modules/react
├── vue -> .pnpm/vue@3.4.0/node_modules/vue
├── @my/
│   ├── ui -> ../../packages/ui          # 软链接到本地包
│   └── utils -> ../../packages/utils

pnpm 通过硬链接 + 软链接实现:

  • 相同版本依赖硬盘只存一份(硬链接)
  • workspace: 协议包通过软链链接
  • 避免了 npm/yarn 的幽灵依赖问题

幽灵依赖 (Phantom Dependency) ​

js
// ❌ npm/yarn 中可以直接引用未声明的依赖
// web 的 package.json 没有声明 lodash,但 web 代码可以 import
// 因为 lodash 被某个间接依赖安装并提升了

// ✅ pnpm 严格模式,未声明的包不可访问
// 必须在 web/package.json 中显式声明
{
  "dependencies": {
    "lodash": "^4.0.0"  // 必须显式添加
  }
}
// ❌ npm/yarn 中可以直接引用未声明的依赖
// web 的 package.json 没有声明 lodash,但 web 代码可以 import
// 因为 lodash 被某个间接依赖安装并提升了

// ✅ pnpm 严格模式,未声明的包不可访问
// 必须在 web/package.json 中显式声明
{
  "dependencies": {
    "lodash": "^4.0.0"  // 必须显式添加
  }
}