# monorepo

## 一、 pnpm 的优势

* 高效的依赖安装:
  * 内容寻址存储 (Content-Addressable Store): pnpm 会在全局存储依赖项的硬链接 (hard links)，这意味着如果多个项目使用相同版本的依赖，它们只会占用磁盘上的一份空间。这大大节省了磁盘空间。
  * 非扁平化的 `node_modules`: pnpm 创建的 `node_modules` 结构是符号链接 (symlinks) 的嵌套结构。这解决了幽灵依赖 (Phantom Dependencies) 和提升问题 (Dependency Hoisting) ^1。
  * 幽灵依赖 (Phantom Dependencies): 子依赖不会被提升到项目根目录，因此你无法意外地使用未显式声明的依赖。
* 性能卓越:
  * 由于依赖项的去重和硬链接机制，安装速度通常比 npm 或 yarn 更快。
* 严格性:
  * `node_modules` 结构更严格，确保项目只能访问其直接声明的依赖，提高了代码库的可维护性和可靠性。

## 二、 Monorepo 的关键配置

要将一个代码库配置为 pnpm monorepo，最核心的文件是 `pnpm-workspace.yaml`。

1. `pnpm-workspace.yaml`：这个文件定义了工作空间 (Workspace) 的根目录和所有子包 (Packages) 的位置。

{% code title="pnpm-workspace.yaml" fullWidth="false" %}

```yaml
packages:
  # 匹配 'packages' 目录下的所有子目录
  - "packages/*"
  # 匹配 'apps' 目录下的所有子目录
  - "apps/*"
  # 也可以排除特定目录
  # - '!packages/legacy-app'
```

{% endcode %}

* 作用: 告诉 pnpm 哪些目录是工作空间中的独立包。
* 位置: 必须放在 monorepo 的根目录。

2. 包之间的依赖（内部引用）：在 monorepo 中，不同包之间可以相互依赖。

* 声明方式: 在依赖包的 `package.json` 文件中，像引用外部依赖一样，使用包名进行引用，但版本号通常使用特殊的工作空间协议 `workspace:`。
* 版本号协议:
  * `"my-ui-library": "workspace:^1.0.0"`: 使用 `^` 遵循 SemVer 规则。
  * `"my-utils": "workspace:*"`: 使用 `*` 匹配当前最新版本。
  * `"my-app": "workspace:^"` (最常用): pnpm 会自动解析到该包的当前版本。

## 三、 Monorepo 常用命令

pnpm 提供了强大的命令来管理工作空间中的所有包。

| 命令                     | 描述                                     | 示例                                       |
| ---------------------- | -------------------------------------- | ---------------------------------------- |
| `pnpm install`         | 在根目录执行，安装所有子包的依赖。                      | `pnpm install`                           |
| `pnpm add`             | 添加依赖到指定的包。                             | `pnpm add lodash --filter <pkg-name>`    |
| `pnpm remove`          | 从指定的包中移除依赖。                            | `pnpm remove lodash --filter <pkg-name>` |
| `pnpm run`             | 运行一个脚本命令。                              | `pnpm run build --filter <pkg-name>`     |
| `pnpm run <script> -r` | 递归地在所有子包中运行相同的脚本。                      | `pnpm run test -r`                       |
| `pnpm link`            | 在工作空间内手动创建符号链接（通常不需要，`install` 会自动处理）。 |                                          |
| `pnpm publish -r`      | 递归地发布所有有更新的包。                          | `pnpm publish -r`                        |

## 四、 筛选器 (`--filter`)

`--filter` 是 pnpm monorepo 中最核心且最强大的特性之一。它允许您将命令的作用范围限制在一个或多个特定的包上。

| 筛选器语法                    | 描述                       | 示例                          |
| ------------------------ | ------------------------ | --------------------------- |
| `--filter <pkg-name>`    | 针对指定的包。                  | `--filter my-app`           |
| `--filter <scope>/*`     | 针对某个范围内的所有包。             | `--filter @scope/*`         |
| `--filter ...<pkg-name>` | 包括指定包及其所有依赖项。            | `--filter ...my-app`        |
| `--filter <pkg-name>...` | 包括指定包及其所有被依赖项（依赖于该包的项目）。 | `--filter my-ui-lib...`     |
| `--filter ./<path>`      | 针对指定路径下的包。               | `--filter ./packages/admin` |
| `--filter '{<glob>}'`    | 使用 glob 模式筛选。            | `--filter '{apps/*}'`       |

例如，要只构建 `my-app` 及其所有依赖的内部库： `pnpm run build --filter ...my-app`

## 五、 运行拓扑排序（构建流程）

当使用 `-r` 或 `--filter` 运行脚本时，pnpm 会自动进行拓扑排序 (topological sorting)。

* 作用: pnpm 会根据包之间的依赖关系，确定一个安全的执行顺序。例如，如果 `app` 依赖于 `ui-lib`，pnpm 会确保先构建 `ui-lib`，然后才构建 `app`。
* 关键: 这使得构建流程非常可靠，不需要额外的工具来管理构建顺序。

## 六、 限制（`shamefully-hoist`）

为了与某些依赖于扁平化 `node_modules` 结构的工具（如某些旧版 Webpack 加载器或 Jest）兼容，pnpm 提供了选项来放松其严格的依赖结构。

* `shamefully-hoist = true`: 在 `.npmrc` 文件中设置此选项，可以强制 pnpm 像 npm/yarn 那样，将所有依赖项提升到根 `node_modules` 中。
* ⚠️ 建议: 尽量避免使用此选项，因为它会失去 pnpm 严格依赖管理带来的优势，重新引入幽灵依赖的风险。

## 常用命令实例

### 安装

```zsh
# 给出 apps/web 添加 react
pnpm add react --filter web # pnpm add react -F 

# 给某个子包添加本地 workspace 依赖，必须添加引号（避免 zsh/bash glob 错误）
pnpm add '@workspace/common@workspace:*' -filter web

# 或者使用相对协议（效果同上）
pnpm add 'common@workspace:*' --filter web
```

### 移除

```zsh
pnpm remove react --filter web
# 或
pnpm rm react --filter web

# 移除本地 workspace 依赖
pnpm remove @@workspace/common --filter web
```

### 更新

```shell
# 更新某个子包的依赖
pnpm update react --filter web

# 更新所有依赖
pnpm update --filter we

# 更新 workspace 本地依赖(重新链接)，pnpm 会自动同步本地包的最新代码    
pnpm install
```

### 运行脚本

```shell
# 在制指定子包中运行脚本
pnpm --filter web run dev
pnpm -F web dev
```

```shell
# 并行运行多个包的脚本
pnpm --filter "./apps/*" run build
```

```shell
## 安依赖顺序运行（拓扑排序）
pnpm --filter web... run build # 先构建 common，再构建 web
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://coderlzw.gitbook.io/web/npm/pnpm/monorepo.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
