pnpm workspace 的工作原理

1. 基本概念

pnpm workspace 是一种用于管理多个包(packages)的特性,特别适合单体仓库(monorepo)的场景。它允许在一个仓库中管理多个项目,共享依赖关系,并简化开发流程。

pnpm 通过 硬链接(hard links)符号链接(symlinks) 实现依赖的高效管理:

  • 硬链接:所有子包共享同一份依赖文件,通过硬链接指向全局存储(.pnpm-store),极大节省磁盘空间。

  • 符号链接:子包的 node_modules 仅包含直接依赖,通过符号链接引用全局存储中的实际文件,避免依赖冗余。

  • 严格隔离:默认启用严格模式,防止跨包访问未声明的“幽灵依赖”

2. 配置文件

  • pnpm-workspace.yaml:这是 pnpm workspace 的核心配置文件,必须放在项目根目录下。它定义了哪些目录下的包属于 workspace。例如:

    yaml 复制代码
    packages:
      - 'packages/*'
      - 'apps/*'

    这表示 packages/apps/ 目录下的所有直接子目录都被视为 workspace 的一部分。

  • package.json:根目录下的 package.json 可以设置为 "private": true,以避免误将整个 workspace 发布到 npm 仓库。

3. 依赖管理

  • 本地链接:当 linkWorkspacePackages 设置为 true 时,pnpm 会优先从 workspace 中链接满足依赖范围的包。例如,如果 bar 依赖 "foo": "^1.0.0",且 foo@1.0.0 存在于 workspace 中,则会直接链接 foo@1.0.0bar
  • workspace: 协议:使用 workspace: 协议可以强制 pnpm 只从本地 workspace 中解析依赖。例如:
    json 复制代码
    {
      "dependencies": {
        "foo": "workspace:*"
      }
    }
    这表示 foo 的版本必须来自 workspace,且版本号由 workspace 中的包决定。

4. 依赖提升(Hoisting)

pnpm 会将公共依赖提升到根目录的 node_modules 中,避免重复安装,节省磁盘空间并提高安装速度。

5. 脚本运行

可以使用 pnpm -r run <script> 命令递归运行所有 workspace 中的脚本。例如:

bash 复制代码
pnpm -r run build

这将运行所有 workspace 中的 build 脚本。

6. 循环依赖

如果 workspace 中存在循环依赖,pnpm 无法保证脚本按拓扑顺序运行,并会发出警告。

实例说明

1. 项目结构

假设我们有一个项目结构如下:

复制代码
my-monorepo/
├── package.json
├── pnpm-workspace.yaml
├── apps/
│   └── web/
├── packages/
│   ├── ui/
│   └── shared-utils/

2. 配置文件

  • pnpm-workspace.yaml
    yaml 复制代码
    packages:
      - 'apps/*'
      - 'packages/*'
  • 根目录 package.json
    json 复制代码
    {
      "name": "my-monorepo",
      "private": true,
      "scripts": {
        "build": "pnpm -r run build"
      }
    }

3. 安装依赖

在根目录运行以下命令安装所有依赖:

bash 复制代码
pnpm install

4. 添加依赖

  • 为整个 workspace 添加依赖
    bash 复制代码
    pnpm add <package-name> -w
  • 为特定 workspace 添加依赖
    bash 复制代码
    pnpm add <package-name> --filter <workspace-name>

5. 运行脚本

  • 运行特定 workspace 的脚本
    bash 复制代码
    pnpm run <script-name> --filter <workspace-name>
  • 运行所有 workspace 的脚本
    bash 复制代码
    pnpm -r run <script-name>

6. 子包互相引用

假设 web 依赖于 uishared-utils,可以在 web/package.json 中这样声明:

json 复制代码
{
  "name": "web",
  "dependencies": {
    "ui": "workspace:*",
    "shared-utils": "workspace:*"
  }
}

然后运行以下命令安装依赖:

bash 复制代码
pnpm install --filter web

通过以上配置和操作,pnpm workspace 可以高效地管理多个包的依赖关系,简化开发流程。