用 Changesets 管理 Monorepo 版本?一篇朴实又详细的实战指南
写在前面:如果你正在维护一个包含多个 npm 包的 Monorepo 项目(比如使用 pnpm/yarn workspace),你一定经历过这些痛苦:
- 每次发版都要手动改
package.json的版本号- 忘记更新 CHANGELOG,用户不知道新版本改了啥
- A 包依赖 B 包,B 升级了但 A 没同步,导致线上出问题
- 团队成员对“这次要不要发版”争论不休
别慌,今天我要介绍的 Changesets,就是专治这些“版本管理综合症”的良药。它轻量、自动化、语义化,还能和 GitHub Actions 完美配合。本文将手把手带你从零配置到上线,没有花里胡哨的概念,只有真实可用的代码和配置。
一、Changesets 是什么?能解决什么问题?
Changesets 是一个开源工具(由 Atlassian 开源),核心目标就两个:
- 自动计算并更新符合 SemVer(语义化版本)的版本号
- 自动生成清晰、规范的 CHANGELOG.md
但它最厉害的地方在于——专为 Monorepo 设计。
它解决了哪些痛点?
| 痛点 | Changesets 的解法 |
|---|---|
| 手动改版本号容易出错 | 自动生成,基于变更内容决定是 major/minor/patch |
| CHANGELOG 写着写着就懒得写了 | 每次提交代码时顺便写一句描述,CHANGELOG 自动聚合生成 |
| Monorepo 中包依赖关系复杂 | 自动检测依赖,升级 A 包时,依赖 A 的 B 包也会自动 patch 升级 |
| 发布流程不透明 | 通过 PR 预览所有变更,团队 review 后再发布 |
| CI/CD 集成麻烦 | 官方提供 GitHub Action,几行 YAML 就搞定自动化 |
✅ 一句话总结:Changesets 把“版本管理”这件事,从人工操作变成了可追溯、可协作、可自动化的流程。
二、快速上手:5 分钟跑通 Changesets
我们以一个典型的 pnpm Monorepo 为例,演示如何快速集成 Changesets。
步骤 1:安装 & 初始化
# 在项目根目录执行
pnpm add -Dw @changesets/cli
# 初始化 Changesets
npx changeset init
执行后,会生成以下文件:
.changeset/目录(存放变更集).changeset/config.json(配置文件).changeset/README.md(说明文档)
步骤 2:添加脚本(方便后续使用)
在根目录 package.json 中添加:
{
"scripts": {
"changeset": "changeset",
"version": "changeset version",
"release": "changeset publish"
}
}
步骤 3:创建你的第一个 Changeset
pnpm changeset
你会看到交互式界面:
-
选择受影响的包(用空格选中,回车确认)
◯ packages/core ◉ packages/ui -
选择版本类型
◯ major (breaking change) ◉ minor (new feature) ◯ patch (bug fix) -
输入变更描述
Added Button component with theme support
完成后,会在 .changeset/ 下生成一个类似 funny-dogs-write.md 的文件:
---
"packages/ui": minor
---
Added Button component with theme support
💡 这个文件就是“变更集”,它会被 Git 提交,成为版本变更的唯一事实来源。
步骤 4:准备发布(本地测试)
# 计算新版本号 + 更新 package.json + 生成 CHANGELOG
pnpm version
# 查看效果(别急着 push)
git diff
你会看到:
packages/ui/package.json的version从1.0.0→1.1.0packages/ui/CHANGELOG.md新增了一条记录.changeset/funny-dogs-write.md被自动删除(已处理)
步骤 5:发布到 npm
# 先确保你已登录 npm
npm login
# 发布
pnpm release
# 推送代码和 tag
git push --follow-tags
🎉 搞定!整个过程无需手动改任何版本号。
三、Monorepo 场景下的高级用法
场景 1:A 包依赖 B 包,B 升级后 A 也要跟着升
假设:
packages/ui依赖packages/core- 你在
core中修复了一个 bug(patch 变更)
Changesets 会自动:
- 将
core从1.0.0→1.0.1 - 将
ui的依赖更新为"packages/core": "workspace:^1.0.1" - 同时将
ui的版本也 patch 升级(比如1.2.0→1.2.1)
这是因为默认配置
"updateInternalDependencies": "patch",表示内部依赖更新时,当前包也做 patch 升级。
场景 2:多个包需要固定相同版本(如 React 生态)
如果你希望 @myapp/core、@myapp/ui、@myapp/utils 永远保持相同版本号(类似 Babel 的做法),只需在 .changeset/config.json 中配置:
{
"fixed": [
["@myapp/core", "@myapp/ui", "@myapp/utils"]
]
}
这样,只要其中一个包有变更,所有包都会一起升级到同一个版本。
场景 3:预发布(alpha/beta/rc)
想先发个测试版给用户试用?
# 进入 beta 预发布模式
npx changeset pre enter beta
# 创建 changeset(和平时一样)
pnpm changeset
# 生成版本(会变成 1.0.0-beta.0)
pnpm version
# 发布到 npm 的 beta 标签
pnpm release --tag beta
# 退出预发布模式
npx changeset pre exit
四、自动化发布:GitHub Actions 配置详解
手动发布适合小项目,但团队协作必须自动化。以下是经过生产验证的 GitHub Actions 配置。
文件:.github/workflows/release.yml
name: Release
on:
push:
branches: [main]
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0 # 获取完整历史,保证 CHANGELOG 正确
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 18
registry-url: 'https://registry.npmjs.org'
- name: Install pnpm
uses: pnpm/action-setup@v2
- name: Install deps
run: pnpm install --frozen-lockfile
- name: Create Release PR or Publish
uses: changesets/action@v1
with:
version: pnpm version
publish: pnpm release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
效果:
- 当你 push 到 main 分支且包含
.changeset/*.md文件时
→ 自动创建一个名为 "Version Packages" 的 PR,里面预览所有版本变更和 CHANGELOG。 - 当你合并这个 PR 时
→ 自动运行pnpm release,把包发布到 npm,并打上 Git tag。
🔑 关键点:
- 所有发布操作都通过 PR 审核,避免误发
- 团队成员可以在 PR 里看到“这次到底改了啥”
- 无需任何人手动执行
pnpm release
如何设置 NPM_TOKEN?
-
去 npm 官网 → Account → Access Tokens → Generate New Token(选 Automation 类型)
-
在 GitHub 仓库 Settings → Secrets → New repository secret
- Name:
NPM_TOKEN - Value: 粘贴你刚生成的 token
- Name:
五、最佳实践 & 常见坑
✅ 什么时候该写 Changeset?
| 场景 | 是否需要 |
|---|---|
| 新增功能 / API | ✅ minor |
| 修复 Bug | ✅ patch |
| 破坏性变更(删 API、改参数) | ✅ major |
| 仅修改文档、注释、格式化 | ❌ 不需要 |
| 更新 devDependencies | ❌ 不需要 |
✍️ Changeset 描述怎么写才专业?
差:
updated code
好:
Added `useFetch` hook for data fetching
- Supports automatic retry and loading states
- Returns `{ data, error, loading }`
- Example:
```ts
const { data } = useFetch('/api/users');
💡 原则:站在使用者角度写,而不是开发者角度。
⚠️ 常见问题排查
Q:发布后发现版本号不对,怎么办?
A:立刻回滚 Git commit 和 npm 版本(npm unpublish 有时间限制),然后检查 .changeset/ 下是否有多余的变更集。
Q:CHANGELOG 格式不符合团队规范?
A:可以自定义 changelog 生成器,支持完全自定义格式。
Q:为什么依赖包没自动升级?
A:检查 package.json 中的依赖是否用了 workspace:^ 协议(pnpm)或 workspace:*(yarn)。例如:
{
"dependencies": {
"@myapp/core": "workspace:^"
}
}
六、总结:为什么我推荐 Changesets?
| 对比项 | 传统方式 | Changesets |
|---|---|---|
| 版本号管理 | 手动改,易出错 | 自动计算,符合 SemVer |
| CHANGELOG | 经常忘记写 | 强制关联每次提交 |
| Monorepo 支持 | 需要自己写脚本 | 内置依赖分析 |
| 发布流程 | 黑盒,只有 maintainer 知道 | 通过 PR 透明化 |
| CI/CD 集成 | 复杂 | 官方 Action,开箱即用 |
Changesets 的哲学很简单:把“版本变更”当作代码的一部分来管理。
每个 Changeset 文件都是一个小型 RFC,记录了“为什么这次要发版”。这不仅提升了工程效率,更让团队协作更加清晰。
附:完整配置参考
.changeset/config.json
{
"$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": []
}
根目录 package.json 脚本
{
"scripts": {
"changeset": "changeset",
"version": "changeset version",
"release": "changeset publish",
"ci:version": "changeset version && pnpm install --lockfile-only",
"ci:publish": "pnpm build && changeset publish"
}
}
最后:如果你的项目是 Monorepo,还在手动管理版本,真的该试试 Changesets 了。它不会让你的项目变得“高大上”,但一定能让你的发布流程少踩 80% 的坑。
📚 官方文档:github.com/changesets/…