code-branchmonorepo

pnpm 在管理 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) 的位置。

pnpm-workspace.yaml
packages:
  # 匹配 'packages' 目录下的所有子目录
  - "packages/*"
  # 匹配 'apps' 目录下的所有子目录
  - "apps/*"
  # 也可以排除特定目录
  # - '!packages/legacy-app'
  • 作用: 告诉 pnpm 哪些目录是工作空间中的独立包。

  • 位置: 必须放在 monorepo 的根目录。

  1. 包之间的依赖(内部引用):在 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 严格依赖管理带来的优势,重新引入幽灵依赖的风险。

常用命令实例

安装

移除

更新

运行脚本

最后更新于