---
title: "pnpm Catalog 与 Monorepo 落地"
canonical_url: "https://wwlight.vercel.app/guides/notes/pnpm-catalog-monorepo"
---
# pnpm Catalog 与 Monorepo 落地

<note>

[Catalog](https://pnpm.io/catalogs) 将依赖版本范围定义为可复用常量；[Workspace](https://pnpm.io/workspaces) 将多个 package 纳入同一仓库。二者可组合使用：单包仓库可仅启用 Catalog；多包仓库使用 Workspace，并以 Catalog 统一第三方依赖版本。

</note>

## 适用场景

<table>
<thead>
  <tr>
    <th>
      场景
    </th>
    
    <th>
      方案
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      单应用，需集中维护依赖版本
    </td>
    
    <td>
      Catalog
    </td>
  </tr>
  
  <tr>
    <td>
      多应用 / 多库，需共享代码与统一版本
    </td>
    
    <td>
      Workspace + Catalog
    </td>
  </tr>
  
  <tr>
    <td>
      包之间无共享，发版节奏独立
    </td>
    
    <td>
      保持多仓库
    </td>
  </tr>
</tbody>
</table>

本站为单包 Nuxt 站点，根目录已存在 `pnpm-workspace.yaml`（含 `overrides`、`allowBuilds`）。下文按「Catalog → Workspace」顺序说明落地步骤；凡可由 CLI 生成的配置，均给出对应命令。

## Catalog 协议

在 `pnpm-workspace.yaml` 定义版本后，`package.json` 使用 `catalog:`（或 `catalog:<name>`）引用，等价于直接书写版本范围。

适用字段：

- `package.json`：`dependencies` / `devDependencies` / `peerDependencies` / `optionalDependencies`
- `pnpm-workspace.yaml`：`overrides`
- `pnx` 说明符：如 `pnx shx@catalog:`

## 默认 Catalog 与具名 Catalog

### 默认 Catalog

顶层 `catalog` 字段定义名为 `default` 的目录。引用时可写 `catalog:`（简写）或 `catalog:default`。

亦可用 `catalogs.default` 书写默认目录。**不可**同时定义 `catalog` 与 `catalogs.default`，否则报错：`The 'default' catalog was defined multiple times`。

```yaml
# pnpm-workspace.yaml
packages:
  - '.'

catalog:
  nuxt: ^4.5.2
  vue: ^3.5.43
  '@vueuse/core': ^15.0.0
```

```json
{
  "dependencies": {
    "nuxt": "catalog:",
    "vue": "catalog:default",
    "@vueuse/core": "catalog:"
  }
}
```

### 具名 Catalog（分组）

`catalogs` 下可定义任意名称的目录，用于：

1. **版本线分组**：同一依赖并存多套主版本（如 `react17` / `react18`），按包迁移进度切换引用。
2. **领域分组**：按技术栈拆分（如 `frontend` / `build` / `test`），降低默认目录体积、明确归属。

```yaml
# pnpm-workspace.yaml
catalog:
  typescript: ^5.9.0

catalogs:
  # 版本线
  react17:
    react: ^17.0.2
    react-dom: ^17.0.2
  react18:
    react: ^18.3.1
    react-dom: ^18.3.1

  # 领域
  frontend:
    vue: ^3.5.43
    '@vueuse/core': ^15.0.0
  build:
    vite: ^7.0.0
    typescript: ^5.9.0
  test:
    vitest: ^3.0.0
    '@vue/test-utils': ^2.4.0
```

```json
{
  "dependencies": {
    "react": "catalog:react18",
    "vue": "catalog:frontend"
  },
  "devDependencies": {
    "vitest": "catalog:test",
    "vite": "catalog:build"
  }
}
```

<table>
<thead>
  <tr>
    <th>
      写法
    </th>
    
    <th>
      含义
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        catalog:
      </code>
    </td>
    
    <td>
      默认目录（<code>
        default
      </code>
      
      ）
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        catalog:default
      </code>
    </td>
    
    <td>
      默认目录（显式）
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        catalog:react18
      </code>
    </td>
    
    <td>
      具名目录 <code>
        react18
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        catalog:frontend
      </code>
    </td>
    
    <td>
      具名目录 <code>
        frontend
      </code>
    </td>
  </tr>
</tbody>
</table>

默认 Catalog 与具名 Catalog 可并存，便于存量包保留旧版本线、新包使用新版本线。

## 用命令写入 Catalog

优先使用 CLI，避免手写 yaml / json 不同步。

### 写入默认 Catalog

```bash
# 写入 default catalog，package.json 记为 catalog:
pnpm add lodash --save-catalog

# 开发依赖
pnpm add -D typescript --save-catalog

# 精确版本
pnpm add lodash --save-catalog --save-exact
```

效果：

- `pnpm-workspace.yaml` 的 `catalog`（或 `catalogs.default`）增加条目
- 当前包的 `package.json` 对应字段写为 `"lodash": "catalog:"`

### 写入具名 Catalog

```bash
pnpm add react --save-catalog-name=react18
pnpm add react-dom --save-catalog-name=react18
pnpm add -D vitest --save-catalog-name=test
```

效果：

- 写入 `catalogs.<name>`
- `package.json` 写为 `"react": "catalog:react18"`

### catalogMode

控制 `pnpm add`（未带 `--save-catalog*`）是否自动进入默认 Catalog：

<table>
<thead>
  <tr>
    <th>
      值
    </th>
    
    <th>
      行为
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        manual
      </code>
      
      （默认）
    </td>
    
    <td>
      不自动写入 Catalog，需显式 <code>
        --save-catalog
      </code>
      
       / <code>
        --save-catalog-name
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        prefer
      </code>
    </td>
    
    <td>
      优先使用 Catalog 中兼容版本；无匹配时回退为直接依赖
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        strict
      </code>
    </td>
    
    <td>
      仅允许 Catalog 中的版本，超出范围则失败
    </td>
  </tr>
</tbody>
</table>

```yaml
# pnpm-workspace.yaml
catalogMode: prefer
catalogPrune: true   # install 时移除未被引用的 catalog 条目
```

配置 `prefer` / `strict` 后，日常 `pnpm add <pkg>` 即可参与默认 Catalog 维护；具名目录仍通过 `--save-catalog-name` 指定。

### 存量仓库批量迁移

将多包中**相同版本范围**的依赖收进默认 Catalog，并改写引用：

```bash
# 官方 codemod（在仓库根目录执行）
pnx codemod pnpm/catalog
# 或
pnpm dlx codemod run pnpm/catalog
```

行为概要：

- 扫描 workspace 内全部 `package.json`
- 被 **两个及以上** 包以相同 range 使用的依赖 → 写入 `catalog`，引用改为 `catalog:`
- 多版本并存的依赖 → 不迁移（可随后用有名 Catalog 手工分组）
- 结束时执行 `pnpm install` 刷新 lockfile

迁移后检查 diff，确认版本范围与预期一致。

## 单包落地步骤

<steps level="4">

#### 确认 pnpm 版本

Catalog 需较新的 pnpm（本站 `devEngines.packageManager` 为 pnpm 11+）。查看：

```bash
pnpm -v
```

#### 用命令写入依赖（推荐）

```bash
pnpm add nuxt --save-catalog
pnpm add @vueuse/core --save-catalog
pnpm add -D typescript --save-catalog-name=build
```

或先手写最小 `pnpm-workspace.yaml`，再执行上述命令补齐条目。

#### 安装

```bash
pnpm install
```

#### （可选）存量依赖一键收编

若 `package.json` 中已有大量显式版本：

```bash
pnx codemod pnpm/catalog
```

</steps>

保留原有 `overrides`、`allowBuilds` 等字段即可，与 `catalog` / `catalogs` 并列。

## Monorepo（Workspace + Catalog）

### 目录结构

```text
.
├── pnpm-workspace.yaml
├── package.json
├── apps/
│   ├── web/
│   └── admin/
└── packages/
    ├── ui/
    └── utils/
```

### 声明成员与 Catalog 分组

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

catalog:
  typescript: ^5.9.0

catalogs:
  frontend:
    vue: ^3.5.43
    '@vueuse/core': ^15.0.0
  build:
    vite: ^7.0.0
  test:
    vitest: ^3.0.0
```

### 内部包：`workspace:`

```json
{
  "name": "@acme/web",
  "dependencies": {
    "@acme/utils": "workspace:^",
    "vue": "catalog:frontend"
  }
}
```

<table>
<thead>
  <tr>
    <th>
      协议
    </th>
    
    <th>
      用途
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        workspace:
      </code>
      
       / <code>
        workspace:^
      </code>
      
       / <code>
        workspace:*
      </code>
    </td>
    
    <td>
      仅解析本仓库 package
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        catalog:
      </code>
      
       / <code>
        catalog:<name>
      </code>
    </td>
    
    <td>
      解析 Catalog 中的第三方（或已收入 Catalog 的 <code>
        workspace:
      </code>
      
       条目）
    </td>
  </tr>
</tbody>
</table>

发布 / pack 时，`workspace:` 与 `catalog:` 均会被替换为具体 semver。

自 pnpm v12.2.0 起，Catalog 条目可直接写 `workspace:^`，供多包装成 `catalog:` 引用内部包。

### 落地顺序

<steps level="4">

#### 划分 apps / packages

明确可独立发布的库与应用边界，再创建目录并迁移代码。

#### 配置 workspace

编辑 `pnpm-workspace.yaml` 的 `packages` glob；用命令写入 Catalog：

```bash
pnpm add vue --filter @acme/web --save-catalog-name=frontend
pnpm add -D vitest --filter @acme/web --save-catalog-name=test
```

#### 声明内部依赖

```bash
# 在 apps/web 下将 workspace 包加入依赖（默认写入 workspace: 协议）
pnpm add @acme/utils --filter @acme/web
```

#### 安装与校验

```bash
pnpm install
pnpm why vue
pnpm -r run build
```

#### 根目录脚本

```json
{
  "scripts": {
    "dev": "pnpm --filter @acme/web dev",
    "build": "pnpm -r run build",
    "test": "pnpm -r run test"
  }
}
```

常用过滤：

```bash
pnpm --filter @acme/web dev
pnpm --filter "./packages/*" build
pnpm -r --filter "...@acme/web" test
```

</steps>

### 维护约定

<table>
<thead>
  <tr>
    <th>
      操作
    </th>
    
    <th>
      命令
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      第三方依赖进默认 Catalog
    </td>
    
    <td>
      <code>
        pnpm add <pkg> --save-catalog
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      第三方依赖进具名分组
    </td>
    
    <td>
      <code>
        pnpm add <pkg> --save-catalog-name=<group>
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      升级某分组内版本
    </td>
    
    <td>
      修改 <code>
        pnpm-workspace.yaml
      </code>
      
       对应行后执行 <code>
        pnpm install
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      清理未使用条目
    </td>
    
    <td>
      设置 <code>
        catalogPrune: true
      </code>
      
       后 <code>
        pnpm install
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      存量多包收编
    </td>
    
    <td>
      <code>
        pnx codemod pnpm/catalog
      </code>
    </td>
  </tr>
</tbody>
</table>

## 错误与限制

<table>
<thead>
  <tr>
    <th>
      错误 / 情况
    </th>
    
    <th>
      处理
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        ERR_PNPM_CATALOG_ENTRY_NOT_FOUND_FOR_SPEC
      </code>
    </td>
    
    <td>
      Catalog 中无该包名；补条目或改为显式版本
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ERR_PNPM_CATALOG_ENTRY_INVALID_RECURSIVE_DEFINITION
      </code>
    </td>
    
    <td>
      Catalog 条目不可再写 <code>
        catalog:
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        default
      </code>
      
       重复定义
    </td>
    
    <td>
      删除 <code>
        catalog
      </code>
      
       与 <code>
        catalogs.default
      </code>
      
       之一
    </td>
  </tr>
  
  <tr>
    <td>
      本地路径 / <code>
        file:
      </code>
      
       / <code>
        link:
      </code>
      
       / 部分 <code>
        workspace:
      </code>
    </td>
    
    <td>
      默认不自动进 Catalog；手工收入时需满足对应 pnpm 版本要求
    </td>
  </tr>
</tbody>
</table>

## 最小模板

```yaml
# pnpm-workspace.yaml
packages:
  - 'apps/*'
  - 'packages/*'

catalog:
  typescript: ^5.9.0

catalogs:
  frontend:
    vue: ^3.5.43
  build:
    vite: ^7.0.0
  test:
    vitest: ^3.0.0

catalogMode: prefer
catalogPrune: true
```

```bash
# 生成依赖条目示例
pnpm add vue --filter @acme/web --save-catalog-name=frontend
pnpm add -D vite vitest --filter @acme/web --save-catalog-name=build
pnpm add @acme/utils --filter @acme/web
pnpm install
```

## 参考

<div className="grid,gap-4,sm:grid-cols-2,[&>*]:my-0!">
<tip to="https://pnpm.io/catalogs">

pnpm Catalogs

</tip>

<tip to="https://pnpm.io/workspaces">

pnpm Workspaces

</tip>

<tip to="https://pnpm.io/cli/add">

pnpm add（含 --save-catalog）

</tip>

<tip to="https://github.com/pnpm/codemod/blob/main/catalog/README.md">

pnpm/catalog codemod

</tip>
</div>


## Sitemap

See the full [sitemap](https://wwlight.vercel.app/sitemap.md) for all pages.
