pnpm install 全流程解读(Monorepo + 子包级处理)

6 阅读5分钟

在现代前端 monorepo 中,pnpm install 看似一句命令,背后却有一套精密的 全局视图 + 子包依赖管理 + 扁平化安装策略。以下从根目录到每个子包的完整流程拆解。

假设项目结构如下:

pnpm-workspace.yaml
packages/
  ui/
apps/
  web/
  api/

执行命令:

pnpm install

整个流程可拆解为 八大阶段


阶段 0:初始化 & Workspace 扫描

目标:建立 monorepo 全局视图,识别所有子包。

根目录处理:

  1. 读取 pnpm-workspace.yaml 配置:

    packages:
      - packages/*
      - apps/*
    
    • 标识哪些目录属于 workspace。
  2. 使用 Glob 展开匹配目录:

    • packages/*packages/ui
    • apps/*apps/web, apps/api
  3. 遍历每个目录:

    • 检查是否存在 package.json
  4. 构建 Package Map(全局内存视图):

    {
      "@my-org/ui": { dir: "packages/ui", version: "1.0.0" },
      "@my-org/web": { dir: "apps/web", version: "1.0.0" },
      "@my-org/api": { dir: "apps/api", version: "1.0.0" }
    }
    

子包处理

  • 此阶段子包仅提供 package.json 信息
  • 不做实际安装,只被 pnpm 作为依赖源解析

比喻:Package Map 就像 monorepo 的楼层导览图,每个子包办公室的位置和编号都在上面标清楚。


阶段 1:解析子包依赖

目标:明确每个依赖的来源与版本,生成解析计划。

根目录处理:

  • 遍历所有子包 package.json

  • 识别依赖协议:

    • workspace: → 本地 workspace 包
    • catalog: → catalog 管理的统一版本
    • 普通 semver → registry 第三方依赖

子包处理(以 apps/web 为例):

{
  "dependencies": {
    "@my-org/ui": "workspace:*",
    "react": "catalog:",
    "axios": "^1.6.0"
  }
}
  1. workspace 依赖:

    • 查 Package Map 找本地路径
    • 标记为 link 本地
  2. catalog 依赖:

    • 查 catalog 表确定版本,如 "^18.2.0"
  3. registry 依赖:

    • 标记为需要缓存或下载

输出:每个子包生成 依赖解析计划

apps/web:
[
  { name: "@my-org/ui", type: "workspace", dir: "packages/ui", version: "1.0.0" },
  { name: "react", type: "catalog", version: "^18.2.0" },
  { name: "axios", type: "registry", version: "^1.6.0" }
]

比喻:每个子包做自己的采购清单,明确“本地拿 / catalog 拿 / registry 拿”。


阶段 2:版本冲突解决 & 扁平化优化

目标:保证依赖版本一致,尽量共享,减少重复安装。

根目录处理:

  1. 构建 全局依赖图

    • 整合所有子包解析计划
    • 记录依赖来源、版本、子包依赖关系
  2. 版本冲突解决:

    • 相同依赖不同版本 → 尝试单一兼容版本
    • 不兼容版本 → 子包隔离安装
  3. 扁平化优化:

    • 相同版本依赖只存一份在 .pnpm
    • 子包 node_modules 通过 symlink 指向共享位置
  4. 生成最终安装计划

子包处理

  • 每个子包根据依赖解析计划和全局优化结果生成自己的 node_modules 结构:

    • workspace → symlink 本地包
    • catalog / registry → symlink 全局缓存
  • 子包内部仍未写物理文件,只是确定了“依赖最终放哪、版本是多少”

比喻:像仓库协调采购:

  • 阶段 1 → 每个子包列清单
  • 阶段 2 → 决定哪些物品共享、哪些单独存放,并画好指向箭头(symlink)

阶段 3:下载 & 缓存管理

目标:把 registry / catalog 依赖放到本地缓存。

根目录处理

  • 遍历全局依赖图
  • 检查全局缓存 ~/.pnpm-store
  • 缓存没有 → 从 npm registry 下载
  • 下载完成 → 写入缓存

子包处理

  • 子包 node_modules 不直接存文件
  • symlink 指向全局缓存 / workspace 本地包

比喻:仓库先查库存,没货再买,子包只挂指向箭头。


阶段 4:构建子包 node_modules

目标:把依赖落地到每个子包。

操作

  • 遍历每个子包安装计划:

    • workspace → link 本地包
    • catalog / registry → link 缓存目录
  • 确保扁平化结构、依赖树完整

子包内部示例

apps/web/node_modules/
  @my-org/ui -> ../../packages/ui (symlink)
  react -> ../../.pnpm/react@18.2.0/node_modules/react
  axios -> ../../.pnpm/axios@1.6.0/node_modules/axios

比喻:像把共享仓库的物品放到办公室桌上,通过 symlink 指向真实物品。


阶段 5:生命周期脚本执行

目标:执行子包初始化与构建脚本。

  • preinstall → 安装前准备
  • install → 内部安装行为
  • postinstall → 构建/生成文件/链接工具

子包处理

  • 每个子包可执行自己的 lifecycle scripts,确保安装完成即可运行

阶段 6:lockfile 更新

目标:记录依赖最终版本,保证一致性。

  • 根目录生成/更新 pnpm-lock.yaml

  • 记录:

    • 包名
    • 版本
    • 来源
    • 校验和
  • 子包无需单独操作,但 lockfile 决定未来安装一致性


阶段 7:最终检查 & 完成安装

  • 校验依赖树完整性
  • 确保 symlink、缓存、node_modules 正确
  • 每个子包可直接 import / require 所有依赖

全流程总结

pnpm install (根目录)
 ├─> 初始化 & workspace 扫描 → 构建 Package Map
 │     └─> 子包提供 package.json
 ├─> 遍历子包 → 解析依赖 (workspace / catalog / registry)
 │     └─> 每个子包生成依赖解析计划
 ├─> 版本冲突解决 & 扁平化优化 → 构建全局依赖图
 │     └─> 子包生成最终 node_modules 安装计划
 ├─> 下载缺失依赖 → 写入全局缓存
 │     └─> 子包 symlink 指向缓存 / workspace
 ├─> 构建子包 node_modules → symlink workspace + catalog/registry
 ├─> 执行生命周期脚本 (preinstall / install / postinstall)
 │     └─> 子包完成初始化和构建
 ├─> 更新 lockfile → 记录最终依赖版本与来源
 └─> 最终检查 → 子包依赖完整,安装完成

核心理解

  1. Package Map → workspace 全局视图,子包位置 + 版本
  2. workspace 协议 → 强制本地依赖,保证 link
  3. catalog 协议 → 统一第三方依赖版本
  4. node_modules 构建 → 扁平化 + symlink,每个子包看到完整依赖
  5. 缓存管理 → 下载一次,全局复用
  6. lockfile & lifecycle scripts → 保证安装一致性 + 初始化完成

一句话总结
pnpm install = 根目录扫描 → 协议解析 → 版本冲突优化 → 缓存下载 → 子包 node_modules 构建 → 生命周期脚本执行 → lockfile 更新 → 子包可直接使用。