版本发布与工程实践:Changesets、CI 与避坑

本节目标

多包各自维护版本号、各自发版,会乱套。Changesets 是社区主流方案——它把「要发版的变更」记成一个个 changeset 文件,发版时统一算版本、统一打 tag。

# 1) 初始化 changesets
pnpm add -Dw @changesets/cli
npx changeset init

# 2) 你改了 @my/ui 的 Button,登记一次变更
npx changeset
# 交互里选 @my/ui,选版本 bump 类型(patch/minor/major),写一句描述

# 3) CI 里「版本确定」+「发布」两步
npx changeset version    # 根据所有 changeset 算出新版本号,更新各包 package.json,删掉 changeset 文件
npx changeset publish    # 把版本变更的包发到 npm(自动跳过没变的)

changeset version 的妙处是原子化:它一次性把所有相关包的版本号、依赖关系算对(比如 ui 升了 minor,依赖它的 web 也会跟着 patch),你不用手动对齐。通常把这两步放进 CI:有人合并变更就自动算版本,打 tag 后自动 publish。

# .github/workflows/release.yml 片段(概念示意)
on:
  push:
    branches: [main]
jobs:
  release:
    steps:
      - run: pnpm install
      - run: npx changeset version
      - run: npx changeset publish
        env:
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

最后,盘点 Monorepo 最常见的坑:

坑 现象 对策
幽灵依赖 没声明却能 import 到别人的包 用 pnpm(严格隔离);CI 跑 pnpm dedupe --check
构建慢 全量 tsc 好几分钟 tsc --build 增量 + Turbo 缓存 + include 收窄
循环依赖 导入到 undefined madge 检测 + 抽共享层
权限/secret 泄露 CI 误发私有包 changeset publish 配 private: true 包不发布;npm token 最小权限
# 增量编译:只重编「变更及下游」,大型 Monorepo 提速必备
pnpm --filter @my/web exec tsc --build

名词解释

Changesets:多包版本管理工具。开发者用「changeset 文件」登记「哪个包、bump 什么版本、为什么」,发版时统一算版本号并 publish,避免手动对齐版本。

原子变更(Atomic Change):一次提交/发版整体生效或整体回滚,不会出现「A 升了 B 没跟上」的半成品状态。Changesets 的版本计算就是为保证原子性。

CI/CD:持续集成(自动跑测试/构建)与持续交付(自动发布)的流水线,是 Monorepo 自动发版的承载。

增量编译(tsc --build):基于 Project References,只重新编译「改动及其下游包」,大型 Monorepo 把几分钟的 tsc 压到秒级的关键。

课后练习

练习 1:changeset version 和 changeset publish 分别干什么?顺序能反过来吗?

答案:version 根据 changeset 算出新版本号、更新 package.json、删除 changeset 文件;publish 才真正把包发到 npm。必须先 version 再 publish——没算版本就发,发的还是旧版本号,且依赖关系对不齐。

练习 2:为什么用 pnpm 能减少「幽灵依赖」?

答案:pnpm 用「严格 node_modules 结构 + 软链接」,只有当包在自身 dependencies/devDependencies 显式声明时才能 import 到;没声明却能用的是幽灵依赖,pnpm 默认直接报「找不到模块」,从机制上堵死。

总结

Monorepo 的终点不是「能跑」,而是「能稳稳地规模化协作与发版」。Changesets 用「登记变更 → 原子算版本 → 自动 publish」把多包发版从手工地狱变成流水线;tsc --build + Turbo 把构建压进秒级;而幽灵依赖、循环依赖、secret 泄露这些坑,靠 pnpm 隔离、madge 检测、最小权限 token 来兜底。我的整体判断:Monorepo 的复杂度不在「搭」,而在「治理」——工具链(pnpm + Turbo + Changesets)到位,它就是你团队协作的倍增器;反之则是另一座债务山。