别再让发包折磨你了:Monorepo 发布工具选型与实操避坑指南
6
0
0
0
在 Monorepo 的世界里,构建速度(缓存)固然重要,但最让维护者头秃的往往是发布工作流(Publish Workflow)。
当你的仓库里躺着几十个互相依赖的 Package 时,手动改版本号简直是自杀行为。你不仅要考虑哪些包变了,还要处理 A 依赖 B,B 更新了 A 要不要跟着升级 这种复杂的拓扑关系。
今天我们不聊虚的,直接硬碰硬对比三套主流方案:Lerna (Legacy/Pro)、Changesets 以及 pnpm 原生发布,看看这些方案在实战中到底有多少坑。
一、 Lerna:曾经的王者,现在的“包袱”?
即便 Nx 接手了 Lerna,大家对它的印象大多还停留在那个“大而全”的 Legacy 时代。
实操坑点:
- Fixed Mode 的“全家桶”焦虑:Lerna 默认偏向所有包共用一个版本号。如果 Package A 改了一行无关痛痒的代码,Package B 到 Z 的版本号全得跟着跳。这对于强强耦合的项目还行,但在追求微模块化的今天,这会导致版本号严重通胀。
- Changelog 的“玄学”生成:Lerna 的
conventional-commits插件极其挑剔。如果你的 Commit Message 稍微不规范,或者在 CI 环境下 Git Fetch 的深度不够(Shallow Clone),它就死活扫不出变更,导致 Changelog 漏记。 - 魔法行为过多:
lerna publish会一口气完成修改版本、打 Tag、推 Git、发 NPM。一旦中途因为网络波动导致 NPM 发布失败,你会发现本地 Tag 已经打上了,Git 也推了,状态卡在半山腰,回滚极其痛苦。
适用场景:老项目维护,或者你依然迷恋“一个命令搞定一切”的省心感。
二、 Changesets:目前最推崇的“声明式”方案
Changesets 的核心逻辑是:“开发者在提交代码时,顺便写一份遗嘱(Changeset 文件)”。
核心优势:
- 意图驱动:变更记录不再依赖于不靠谱的 Git Commit Message,而是由开发者通过
npx changeset手动选择变更类型(patch/minor/major)并填写描述。 - 异步发布:它把“记录变更”和“执行版本更新”彻底解耦。你可以积累十个 Changesets 后,再统一在 CI 中执行一次
version操作。
实操坑点:
- 心智负担:开发者必须养成“提交代码前先跑一遍 changeset”的习惯。虽然可以通过 Git Hook 强制执行,但对于新人来说,多了一步流程。
- CI 集成的复杂性:Changesets 的官方 GitHub Action 逻辑非常精妙(自动开 PR 收集变更),但如果你用的是 GitLab CI 或 Jenkins,你需要自己写脚本来处理
version->commit->tag的闭环。 - 依赖提升(Internal Dependency):如果 A 依赖 B,B 升级了,Changesets 默认会给 A 增加一个 patch 更新。在大型项目中,这会产生连锁反应,建议配置
linked选项来规避不必要的升级。
三、 pnpm publish:极致的极简主义
如果你追求原生,不想引入额外的工具栈,pnpm -r publish 配合 pnpm version 其实能打。
实操技巧:
- workspace: 协议*:这是 pnpm 的杀手锏。发布时,它会自动把
workspace:*替换成真实的 Semver 版本号。 - --filter 机制:利用
pnpm publish --filter "...[origin/main]"这种命令,理论上可以只发布自上次提交以来变更过的包。
实操坑点:
- 缺乏 Changelog 自动管理:pnpm 本身不帮你写 Changelog。你得配合
standard-version或者自己手写。 - 版本同步逻辑薄弱:它很难像 Changesets 那样优雅地处理“一组包必须同步升级”的复杂逻辑。
- 状态管理:没有 Changesets 那种暂存变更文件的机制,你必须在发布的那一刻决定版本号,容错率较低。
方案对比:我该选哪个?
| 维度 | Lerna (Fixed) | Changesets | pnpm publish |
|---|---|---|---|
| 版本控制 | 统一管理,简单粗暴 | 细粒度,意图驱动 | 比较原始,依赖手动/脚本 |
| Changelog | 自动(依赖 Commit) | 手动描述 + 自动汇总 | 无(需自行扩展) |
| 心智负担 | 低(如果不报错的话) | 中(需要多写文件) | 高(需要关注细节) |
| CI 友好度 | 一般(容易卡状态) | 极高(Action 流程完善) | 高(命令简单) |
避坑指南:总结出来的几条铁律
- 永远不要在本地发布:
不管用什么工具,本地环境的npm token、网络环境、Node 版本都是不确定的。必须走 CI 自动化流。 - 重视
access: 'public':
在 Monorepo 里,如果你的包是 Scoped(如@my-org/pkg),默认是私有的。发布时记得加--access public,否则会报 402 错误。 - 处理好 Peer Dependencies:
Monorepo 内部包互相依赖时,尽量使用peerDependencies。否则当用户安装你的包时,可能会因为版本不一致被安装多份冗余副本。 - 预发布测试(Dry Run):
在正式发布前,务必执行pnpm publish --dry-run或者changeset version --snapshot。看看生成的package.json里的版本号对不对,文件打包进去了没。
我的建议:
- 如果是开源项目(如 Vue/Vite 插件、工具库):首选 Changesets。它对贡献者非常友好,生成的 Changelog 也最清晰。
- 如果是公司内部项目(如中后台业务组件库):如果追求快,Lerna + Nx 插件 依然是目前最成熟、文档最多的方案。
- 如果你是极简主义者且项目依赖关系简单:直接 pnpm 原生 + 自建脚本 即可。