使用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的时候,需要时刻保持utilsdist文件是最新的,也就是你的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的开发者来说,也有不少坑在里头。

最后:与君共勉

相关推荐
AwesomeDevin44 分钟前
一种前端硬编码图片扩写方案
前端
放逐者-保持本心,方可放逐1 小时前
css 布局及动画应用(flex+transform+transition+animation)
前端·css·transform·animation·flex·transition·transgorm
卿言卿语1 小时前
第三章:HTML的字符实体,meta标签以及全局属性
前端·html·visual studio code
marshalVS1 小时前
前端学习-事件对象与典型案例(二十六)
前端·javascript·学习
mit6.8241 小时前
[Qt] 窗口 | 菜单栏MenuBar
前端·c++·qt·ubuntu
PieroPc1 小时前
特制一个自己的UI库,只用CSS、图标、emoji图 第二版
前端·css·ui
PieroPc2 小时前
两个关于 li bottom 的CSS 问题 笔记
前端·css·笔记
72degrees2 小时前
vue2迁移至rsbuild
前端·javascript·vue.js
葡萄架子2 小时前
全面了解 Web 前端技术:从基础到实践
前端
ss2732 小时前
免费获取2025新年跨年春节春晚烟花祝福html+js源码
前端·javascript·html