WEBKT

别再让发包折磨你了:Monorepo 发布工具选型与实操避坑指南

6 0 0 0

在 Monorepo 的世界里,构建速度(缓存)固然重要,但最让维护者头秃的往往是发布工作流(Publish Workflow)

当你的仓库里躺着几十个互相依赖的 Package 时,手动改版本号简直是自杀行为。你不仅要考虑哪些包变了,还要处理 A 依赖 B,B 更新了 A 要不要跟着升级 这种复杂的拓扑关系。

今天我们不聊虚的,直接硬碰硬对比三套主流方案:Lerna (Legacy/Pro)、Changesets 以及 pnpm 原生发布,看看这些方案在实战中到底有多少坑。


一、 Lerna:曾经的王者,现在的“包袱”?

即便 Nx 接手了 Lerna,大家对它的印象大多还停留在那个“大而全”的 Legacy 时代。

实操坑点:

  1. Fixed Mode 的“全家桶”焦虑:Lerna 默认偏向所有包共用一个版本号。如果 Package A 改了一行无关痛痒的代码,Package B 到 Z 的版本号全得跟着跳。这对于强强耦合的项目还行,但在追求微模块化的今天,这会导致版本号严重通胀。
  2. Changelog 的“玄学”生成:Lerna 的 conventional-commits 插件极其挑剔。如果你的 Commit Message 稍微不规范,或者在 CI 环境下 Git Fetch 的深度不够(Shallow Clone),它就死活扫不出变更,导致 Changelog 漏记。
  3. 魔法行为过多lerna publish 会一口气完成修改版本、打 Tag、推 Git、发 NPM。一旦中途因为网络波动导致 NPM 发布失败,你会发现本地 Tag 已经打上了,Git 也推了,状态卡在半山腰,回滚极其痛苦。

适用场景:老项目维护,或者你依然迷恋“一个命令搞定一切”的省心感。


二、 Changesets:目前最推崇的“声明式”方案

Changesets 的核心逻辑是:“开发者在提交代码时,顺便写一份遗嘱(Changeset 文件)”

核心优势:

  • 意图驱动:变更记录不再依赖于不靠谱的 Git Commit Message,而是由开发者通过 npx changeset 手动选择变更类型(patch/minor/major)并填写描述。
  • 异步发布:它把“记录变更”和“执行版本更新”彻底解耦。你可以积累十个 Changesets 后,再统一在 CI 中执行一次 version 操作。

实操坑点:

  1. 心智负担:开发者必须养成“提交代码前先跑一遍 changeset”的习惯。虽然可以通过 Git Hook 强制执行,但对于新人来说,多了一步流程。
  2. CI 集成的复杂性:Changesets 的官方 GitHub Action 逻辑非常精妙(自动开 PR 收集变更),但如果你用的是 GitLab CI 或 Jenkins,你需要自己写脚本来处理 version -> commit -> tag 的闭环。
  3. 依赖提升(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]" 这种命令,理论上可以只发布自上次提交以来变更过的包。

实操坑点:

  1. 缺乏 Changelog 自动管理:pnpm 本身不帮你写 Changelog。你得配合 standard-version 或者自己手写。
  2. 版本同步逻辑薄弱:它很难像 Changesets 那样优雅地处理“一组包必须同步升级”的复杂逻辑。
  3. 状态管理:没有 Changesets 那种暂存变更文件的机制,你必须在发布的那一刻决定版本号,容错率较低。

方案对比:我该选哪个?

维度 Lerna (Fixed) Changesets pnpm publish
版本控制 统一管理,简单粗暴 细粒度,意图驱动 比较原始,依赖手动/脚本
Changelog 自动(依赖 Commit) 手动描述 + 自动汇总 无(需自行扩展)
心智负担 低(如果不报错的话) 中(需要多写文件) 高(需要关注细节)
CI 友好度 一般(容易卡状态) 极高(Action 流程完善) 高(命令简单)

避坑指南:总结出来的几条铁律

  1. 永远不要在本地发布
    不管用什么工具,本地环境的 npm token、网络环境、Node 版本都是不确定的。必须走 CI 自动化流。
  2. 重视 access: 'public'
    在 Monorepo 里,如果你的包是 Scoped(如 @my-org/pkg),默认是私有的。发布时记得加 --access public,否则会报 402 错误。
  3. 处理好 Peer Dependencies
    Monorepo 内部包互相依赖时,尽量使用 peerDependencies。否则当用户安装你的包时,可能会因为版本不一致被安装多份冗余副本。
  4. 预发布测试(Dry Run)
    在正式发布前,务必执行 pnpm publish --dry-run 或者 changeset version --snapshot。看看生成的 package.json 里的版本号对不对,文件打包进去了没。

我的建议:

  • 如果是开源项目(如 Vue/Vite 插件、工具库):首选 Changesets。它对贡献者非常友好,生成的 Changelog 也最清晰。
  • 如果是公司内部项目(如中后台业务组件库):如果追求快,Lerna + Nx 插件 依然是目前最成熟、文档最多的方案。
  • 如果你是极简主义者且项目依赖关系简单:直接 pnpm 原生 + 自建脚本 即可。
码农老王 MonorepoChangesetsLerna

评论点评