Monorepo系列:Pnpm Workspace 搭建 Monorepo

近期,团队在做技术升级,新的模式下我们选择 pnpm workspace 搭建 monorepo 的方式来管理前端业务代码,一个业务方向的多个项目使用统一 git 仓库,具备依赖共享、构建统一等优势。结构如下:

复制代码
my-business/
├── apps/
│   ├── web/          # 前端应用
│   └── mobile/       # 移动端应用
├── packages/
│   ├── ui/           # 组件库
│   └── utils/        # 工具库
|── pnpm-workspace.yaml
└── package.json

使用 Monorepo 管理的好处:

  • 一致的工具链:整个仓库使用统一的工具和配置,如构建工具、代码风格、测试框架等;一次修改,所有相关项目都能受益。
  • 统一的版本管理:所有项目使用同一套依赖版本,便于管理,减少因版本不一致导致的问题【借助 pnpm Catalog 特性】。
  • 简化依赖管理:内部依赖可以直接通过工作空间链接,而不需要发布到包管理器【借助 pnpm workspace 工作空间协议】。

项目选型使用了 pnpm workspace:

  • 节省磁盘空间:依赖会被存储在内容可寻址的存储中,而不是每个项目单独存储,从而节省磁盘空间。
  • 提升构建速度:安装过的依赖项都会直接从存储区中获取并链接到 node_modules。
  • 非扁平的 node_modules 目录:禁止幽灵依赖,未声明依赖无法被偷偷使用。

Workspace 工作空间

工作空间根目录必须有一个 pnpm-workspace.yaml 文件

yaml 复制代码
# pnpm-workspace.yaml
packages:
  - "packages/*"
  - "apps/*"

在工作空间内,一个包可以依赖另一个包,使用 workspace:* 协议(如 web 项目引用了 ui 包)。

json 复制代码
{
  "name": "web",
  "version": "0.1.0",
  "dependencies": {
    "ui": "workspace:*"
  }
}
  • 在根目录运行 pnpm install,它会安装所有包的依赖,并链接工作空间内的包;
  • 可以使用 pnpm run --filter <package_name> <script> 来在特定包中运行脚本或者使用 pnpm -r run <script> 在所有包中运行脚本。

Catalogs 将依赖项版本定义为可复用常量

在根目录声明 catalog:,即可为团队钉住一组"可被采用"的依赖版本。子包写 "react": "catalog:" 即自动对齐,不再在每个 package.json 里手动同步版本号。

有两种定义方式:

  1. 使用 (单数) catalog 字段创建名为 default 的目录。
  2. 使用 (复数) catalogs 字段创建任意命名的目录。
yaml 复制代码
# pnpm-workspace.yaml
catalogs:
  # 可以通过 "catalog:react18" 引用
  react18:
    react: 18.2.0
    react-dom: 18.2.0
  # 可以通过 "catalog:umi" 引用
  umi:
    "@umijs/max": "4.4.12"
    "@umijs/utils": "4.4.12"
json 复制代码
// apps/web/package.json
{
  "dependencies": {
    "@umijs/max": "catalog:umi" # 等价于 4.4.12
  }
}

优势:

  • 维护唯一版本:通常希望在工作空间中共同的依赖项版本一致。 Catalog 让工作区内共同依赖项的版本更容易维护。 减少重复的依赖关系可能的冲突及重复依赖项打包体积增大。
  • 易于更新:升级或者更新依赖项版本时,只需编辑 pnpm-workspace.yaml 中的目录,而不需要更改所有用到该依赖项的 package.json 文件。 这样可以节省时间 --- 只需更改一行,而不是多行。
  • 减少合并冲突:由于在升级依赖项时不需要编辑 package.json 文件,所以这些依赖项版本更新时就不会发生 git 冲突。

需要注意的是:要更新 pnpm-workspace.yaml 中定义的依赖项,需要手动选择较新的版本范围(pnpm update 暂不支持)。

peerDependencies 共享宿主环境的依赖

json 复制代码
// packages/ui/package.json
{
  "peerDependencies": {
    "react": "^18.0.0",
    "react-dom": "^18.0.0"
  }
}

对等依赖(Peer Dependencies) 是一种特殊的依赖关系,这里用于组件库&工具库之间共享相同的核心依赖(如 React、ReactDOM 等)。

filter 指定包运行脚本

  1. 可以使用 pnpm run --filter <package_name> <script> 来在特定包中运行脚本
shell 复制代码
# 运行 web 项目的 dev 脚本
pnpm run --filter web dev
  1. 执行某包及依赖包的脚本(执行 web 及 ui、utils 项目的 dev 脚本)
shell 复制代码
# 运行 web 及其依赖项的 build 脚本
pnpm --filter web... build

注意:pnpm --filter 的默认行为是:串行执行,前一个任务不退出,下一个不会开始。

shell 复制代码
pnpm --filter web... dev

并不会按顺序执行完成,原因是:前一个 dev 不退出,导致下一个不会开始。

PS:其他匹配方式,详见官方文档:https://www.pnpm.cn/filtering

上述 dev 怎么并行执行?

方式一:使用 package.json 的 pre 和 post 钩子

json 复制代码
{
  "scripts": {
    "dev": "pnpm --filter web dev",
    "predev": "pnpm --filter ui dev"
  }
}

方式二:使用第三方包 concurrently

json 复制代码
{
  "scripts": {
    "dev": "concurrently \"pnpm --filter web dev\" \"pnpm --filter ui dev\""
  }
}

[推荐]方式三:使用 turbo 管理

json 复制代码
{
  "scripts": {
    "dev": "turbo run dev --filter web --filter ui"
  }
}

PS:后续文章专门介绍 turbo 管理 monorepo 项目。

相关推荐
前端架构师-老李1 天前
npm、yarn、pnpm的对比和优略
前端·npm·node.js·pnpm·yarn
skywalk81637 天前
pnpm(‌P‌erformance ‌N‌ode ‌P‌ackage ‌Manager‌)包管理工具在FreeBSD系统下的安装使用
人工智能·pnpm·freebsd·iflow
梵得儿SHI7 天前
Vue 开发环境搭建全指南:从工具准备到项目启动
前端·javascript·vue.js·node.js·pnpm·vue开发环境·nvm版本管理
oscar9991 个月前
Monorepo 全面解析:优势、挑战与适用场景
git·monorepo
williamdsy2 个月前
实战复盘:pnpm Monorepo 中的 Nuxt 依赖地狱——Unhead 升级引发的连锁血案
vue.js·pnpm
流氓也是种气质 _Cookie3 个月前
从依赖地狱到依赖天堂PNPM
pnpm·monorepo
止观止4 个月前
深入探索 pnpm:高效磁盘利用与灵活的包管理解决方案
前端·pnpm·前端工程化·包管理器
就爱瞎逛4 个月前
pnpm link如何不踩坑
pnpm·打包
努力了吗梁同学4 个月前
Nuxt3 中使用 pnpm 安装的 NuxtImg 使用会提示找不到图片
前端·vue·pnpm·nuxt·nuxtimg