WEBKT

从 Lerna 转向 Changesets:大型 Monorepo 迁移中那些“查无此文”的坑

2 0 0 0

在前端工程化领域,Lerna 曾是 Monorepo 的代名词。但随着时间的推移,Lerna 笨重的体积、复杂的版本联动逻辑以及一度停滞的维护,让很多团队开始转向更轻量、更符合现代 CI/CD 流程的工具——Changesets

最近我刚完成了公司内部一个拥有 30+ 子包的 Monorepo 从 Lerna 到 Changesets 的全量迁移。官网上面的“Getting Started”只需要三行命令,但在实际生产环境的迁移中,我遇到了一堆文档里根本没提到的细节。

为什么我们要抛弃 Lerna?

核心原因是 “心智负担”。Lerna 的 versionpublish 命令高度耦合了 Git Tag 和 NPM Registry 的状态。如果发布过程中网络抖动,或者 Git Tag 没推上去,修复起来简直是噩梦。而 Changesets 的逻辑是:“版本变更意图即文件”。你只需要写一个 .changeset 文件,剩下的交给自动化流程。


迁移第一步:清理战场

在安装 @changesets/cli 之前,你需要彻底移除 Lerna 的痕迹。

  1. 移除 lerna.json
  2. 删除 package.json 中的 lerna 依赖。
  3. 检查 scripts:把所有的 lerna publishlerna 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。你可能需要结合 pnpmlink 特性或者手动写一个 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 而生成失败或者回退到默认模式。


总结:迁移后的收益

  1. 版本回溯极其简单:所有的版本变更意图都提交到了 Git,而不是存在某个人的电脑里。
  2. 发布流程解耦:开发者只负责写 .changeset,发布权交给 CI。
  3. 依赖处理更现代:配合 pnpm workspace,Changesets 的表现远比 Lerna 稳定。

如果你的 Monorepo 还在被 Lerna 的 lerna bootstrap 或发布失败后的 Tag 清理折磨,真的建议花半天时间切到 Changesets。虽然迁移过程中有几个“隐形坑”,但一旦路走通了,那种自动化发布的顺滑感是真的香。

架构师老王 MonorepoChangesets前端工程化

评论点评