使用pnpm搭建你的monorepo三步走

背景

24年差不多一整年都在跟pnpm的多包打交道,在公司的两款ai产品中都使用的是pnpm来搭建,其中第三版重构的小程序ai技能也是踩了不少坑(有机会的话后面会写篇文章来探讨下为适应公司ai产品发展方向所设计的前端架构),现在在做的前端工程化从pnpm8升级到pnpm9又是踩了不少坑

三个阶段

创建多包流程(大家都知道的)

在项目中创建pnpm-workspace.yaml,声明你的多包目录

yaml 复制代码
packages:
  - "cli/*"

然后在根目录下创建一个cli文件夹,在里面创建你的多包

tree 复制代码
cli
├───📁 core/
│   ├───📁 bin/
│   │   └───...
│   ├───📁 dist/
│   │   └───...
│   ├───📁 node_modules/
│   │   └───...
│   ├───📁 src/
│   │   └───...
│   ├───📄 CHANGELOG.md
│   ├───📄 package.json
│   ├───📄 rollup.config.js
│   └───📄 tsconfig.json
├───📁 plugin-create/
│   ├───📁 dist/
│   │   └───...
│   ├───📁 node_modules/
│   │   └───...
│   ├───📁 scripts/
│   │   └───...
│   ├───📁 src/
│   │   └───...
│   ├───📁 templates/
│   │   └───...
│   ├───📄 .gitignore
│   ├───📄 CHANGELOG.md
│   ├───📄 package.json
│   ├───📄 rollup.config.js
│   └───📄 tsconfig.json
├───📁 utils/
│   ├───📁 dist/
│   │   └───...
│   ├───📁 src/
│   │   └───...
│   ├───📄 CHANGELOG.md
│   ├───📄 package.json
│   ├───📄 rollup.config.js
│   └───📄 tsconfig.json
└───📄 README.md

对多包创建关联关系

bash 复制代码
cd cli/core
pnpm i @xxx/cli-utils -S

这样你就能得到这样的一个依赖关系(注:pnpm9需要在.npmrc中配置link-workspace-packages=true)

在使用pnpm publish的时候会自动将workspace: ^转成@xxx/cli-utils相对应的版本,不用担心发布npm包之后的版本问题

需要注意的是,在调试@xxx/cli-utils的时候,需要时刻保持utils的dist文件是最新的,也就是你的dev命令是运行的

相较于传统的npm link的模式,pnpm的多包调试省去了对每个包都进行npm link命令,极大提升了调试体验

发包流程(可能也许大概估计差不多大家都知道的)

官方推荐使用changesets来管理变更集,所以我也是遵循官方建议

初始化

bash 复制代码
# 安装@changesets/cli
pnpm add -Dw @changesets/cli
​
# 初始化
pnpm changeset init

这时候就会自动创建出changeset的配置文件

记录变更

pnpm changeset,会出现一系列Prompt问题,会列出改动和未改动的npm包方便开发者选择

消费变更

pnpm changeset version,会将临时生成的变更信息回填到上一步选择的npm包的CHANGELOG.md文件中,并更新版本

发布

pnpm changeset publish,本质上就是对npm publish做了一次封装,同时会检查对应的registry上有没有对应包的版本,如果已经存在了,就不会再发包了,如果不存在会对对应的包版本执行一次npm publish

更多细节内容可以参考这两篇文章:

juejin.cn/post/702482...

juejin.cn/post/709860...

细节控制(细,不是细狗的细)

公共依赖的版本控制

在根路径下的package.json中安装的依赖,我们可以在任意子包中使用而不需要再次安装,比如:根路径安装了lodash,这时候可以在子包A/B/C中直接使用lodash。但问题在于发布子包后,子包的package.json中没有lodash依赖,这时候在业务中安装子包的时候就会出现实际依赖跟package.json不一致的情况,可以算是另类的"幽灵依赖"了

解决方案有两种:

  1. 在子包中再安装一次lodash,但可能会多个包使用lodash的情况下出现版本不一致的问题
  2. 使用pnpm9.5.0新出的catalog特性管理公共依赖

我现在采用的就是第二种方案,在pnpm-workspace.yaml中添加catalog

yaml 复制代码
## pnpm-workspace.yaml
# 单个依赖的catalog声明
catalog:
  rollup: ^4.28.0
​
# 系列依赖的catalog声明
catalogs:
  eslint8:
    eslint: ^8.57.1
    eslint-config-alloy: ^5.1.2
    eslint-plugin-import: ^2.31.0

依赖声明

json 复制代码
// utils/package.json
"devDependencies": {
    "rollup": "catalog:",
    "eslint": "catalog:eslint8",
    "eslint-config-alloy": "catalog:eslint8",
    "eslint-plugin-import": "catalog:eslint8",
}

跟workspace一样,在发布阶段会自动改写成相对应的版本

但该方案其实也有弊端,详情可以看我另外一篇文章:使用pnpm搭建monorepo的踩坑日志

构建命令配置

背景 :目前手上的这几个pnpm项目,子包都在十个左右,且都有持续增加的可能。CI/CD使用的是coding的云原生构建,构建命令只能有一个。

如何管理这个唯一的构建命令是一个问题,解决方案有:

  1. 在根路径下使用npm-run-all,每新增一个子包,就加一个构建命令
  2. 使用pnpm --filter <package_selector> <command>对子包进行依赖构建

目前选用的是方案2,我主要使用了pnpm --filter <package_selector>...和pnpm --filter ...<package_selector>这两个命令,简单说明下这两个命令的区别:

  1. pnpm --filter foo... run build:运行 foo 及其所有依赖的build命令,向下递归
  2. pnpm --filter ...foo run build:运行 foo 以及依赖它的所有包的build命令,向上递归

这时候我们只需要选择一个底层的基础包或者最上层的应用包运行build命令就可以,无论新增了多少依赖包,都不需要调整命令

特例 :有一种情况比较特殊,我在工程化架构的设计中,对每个基础功能都提炼为单独的npm包,这些包都是相对独立的,也就是没有共同依赖,这时候就不好写filter命令了

既然没有共同依赖,那就创建一个好了。按照filter的机制,可以创建一个基础包或者总包。

如果是基础包的话,每新增一个功能包,都需要在新包中添加基础包依赖,功能包的依赖就变"脏"了

如果是总包的话,每新增一个功能包,只需要在总包中添加功能包依赖,总包没有实际用处,不影响其他

因为是多人开发,所以这个机制可能会因为人工的关系而被遗漏掉,所以我写了一个自动脚本,在pre-commit的时候自动检测是否有新的功能包,如果有,自动添加到总包的依赖中

这样,在根路径的构建命令中,我只需要添加pnpm --filter all... run build即可

总结

总的来说,使用pnpm来管理多包还是挺简单的,不过对于首次接触pnpm的开发者来说,也有不少坑在里头。

最后:与君共勉

相关推荐
RobinDevNotes24 分钟前
从 Electron 到 GPUI,9 个跨平台桌面方案该怎么选
前端·javascript·rust·electron·openresty·前端开发工具
jay神1 小时前
基于 YOLOv8 的轴承缺陷检测(Web 版)
前端·人工智能·yolo
jay神2 小时前
基于 YOLOv8 的森林火灾检测系统(Web 版)
前端·yolo
code2cat2 小时前
HTML基础篇之第一张网页:从index.html到浏览器里的任务清单
前端·html
miss2 小时前
AI 改完不敢上线?OpenSpec 规约驱动开发实战
前端·代码规范·前端工程化
AiMan3 小时前
腾讯ima还是不可以导出左侧所有对话,只能单条导出,方法不对吗
前端
去伪存真3 小时前
并发量一高就装死?Agent 全链路“防猝死”手册
前端·agent
Moment3 小时前
AI 编程时代,聊聊我的 Mac mini 双屏桌面
前端·后端
AiMan3 小时前
先确认能不能一次把Microsofat Copilot所有内容打包一个文件导出
前端
总要学点什么3 小时前
第97篇:三维数据加载-城市建筑白模自定义着色器——让一条光带沿高度扫上去,快慢却由帧率说了算
前端·gis·webgl·cesium·着色器·空间分析·白模