从 Lerna 转向 Changesets:大型 Monorepo 迁移中那些“查无此文”的坑
在前端工程化领域,Lerna 曾是 Monorepo 的代名词。但随着时间的推移,Lerna 笨重的体积、复杂的版本联动逻辑以及一度停滞的维护,让很多团队开始转向更轻量、更符合现代 CI/CD 流程的工具——Changesets。
最近我刚完成了公司内部一个拥有 30+ 子包的 Monorepo 从 Lerna 到 Changesets 的全量迁移。官网上面的“Getting Started”只需要三行命令,但在实际生产环境的迁移中,我遇到了一堆文档里根本没提到的细节。
为什么我们要抛弃 Lerna?
核心原因是 “心智负担”。Lerna 的 version 和 publish 命令高度耦合了 Git Tag 和 NPM Registry 的状态。如果发布过程中网络抖动,或者 Git Tag 没推上去,修复起来简直是噩梦。而 Changesets 的逻辑是:“版本变更意图即文件”。你只需要写一个 .changeset 文件,剩下的交给自动化流程。
迁移第一步:清理战场
在安装 @changesets/cli 之前,你需要彻底移除 Lerna 的痕迹。
- 移除
lerna.json。 - 删除
package.json中的lerna依赖。 - 检查
scripts:把所有的lerna publish或lerna version替换掉。
坑一:如何模拟 Lerna 的 "Fixed" 模式?
Lerna 最常用的模式是 Fixed 模式,即所有包共用一个版本号。但 Changesets 默认是趋向于 Independent(独立版本)的。
解决方案:
在 .changeset/config.json 中使用 linked 配置项。
{
"linked": [
["@my-scope/*"]
]
}
细节注意: 这里的通配符虽然好用,但如果你的 Monorepo 里混杂了“私有工具包”和“对外发布包”,记得手动拆分数组。如果不配置 linked,Changesets 每次只会更新被修改的包,这会导致依赖该包的其他子包出现版本不一致的警告。
坑二:peerDependencies 的自动关联更新
这是官方文档中最模糊的地方。在 Lerna 中,如果 A 依赖 B,B 升级了,Lerna 会自动处理。但在 Changesets 中,你需要明确配置 updateInternalDependencies。
实战配置:
{
"updateInternalDependencies": "patch"
}
如果设为 patch,当依赖包版本变动时,消费者包的版本也会被动提升。但坑点在于: 如果你使用了 peerDependencies 且版本范围写得比较死(比如 ^1.0.0),当主版本号变动时,Changesets 有时不会自动帮你更新这个 Range。你可能需要结合 pnpm 的 link 特性或者手动写一个 preversion 脚本来二次检查。
坑三:CI 环境下的“幽灵”发布
在 GitHub Actions 中使用 changesets/action 时,很多人会遇到:明明没有新 Changeset,CI 却依然尝试运行发布逻辑。
正确姿势:
你需要在 CI 脚本中利用 changeset status 输出的结果做条件判断。
- name: Create Release Pull Request or Publish to npm
id: changesets
uses: changesets/action@v1
with:
publish: pnpm release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
注意: 如果你的项目必须先构建(build)才能发布,请务必确保 pnpm release 脚本内部包含了 pnpm build。Changesets 本身不负责构建,它只负责改 package.json 和打 Tag。
坑四:Pre-release 状态的“退出”难题
Changesets 的预发布模式(Alpha/Beta)是通过 changeset pre enter <tag> 进入的。
进入后,.changeset 文件夹下会多出一个 pre.json。
坑在哪里?
如果你在预发布期间删除了某个 .changeset 文件,或者手动改了 pre.json,可能会导致 changeset publish 找不到包。
经验: 永远不要手动修改 pre.json。如果发布失败,先执行 changeset pre exit 退出模式,清理环境后再重新进入。
坑五:Changelog 的自定义
Changesets 默认生成的 CHANGELOG.md 非常简陋,通常只有一行 commit 描述。
如果你的团队需要像 Lerna 那样自动带上作者、PR 链接,你需要安装插件:npm install @changesets/changelog-github
然后在配置文件里启用:
"changelog": ["@changesets/changelog-github", { "repo": "org/repo" }]
隐蔽细节: 这个插件依赖 GITHUB_TOKEN 环境变量。如果在本地运行 changeset version,它会因为拿不到 Token 而生成失败或者回退到默认模式。
总结:迁移后的收益
- 版本回溯极其简单:所有的版本变更意图都提交到了 Git,而不是存在某个人的电脑里。
- 发布流程解耦:开发者只负责写
.changeset,发布权交给 CI。 - 依赖处理更现代:配合
pnpm workspace,Changesets 的表现远比 Lerna 稳定。
如果你的 Monorepo 还在被 Lerna 的 lerna bootstrap 或发布失败后的 Tag 清理折磨,真的建议花半天时间切到 Changesets。虽然迁移过程中有几个“隐形坑”,但一旦路走通了,那种自动化发布的顺滑感是真的香。