本文是《从零到一:构建现代化企业级 Monorepo 项目实战》系列的第五篇。前面我们搭建了项目和代码质量工具,这篇文章将讲解如何优雅地管理多包版本和发布流程。
🎯 本文目标
读完这篇文章,你将学会:
- Changeset 的工作原理和优势
- 完整的版本发布流程
- 语义化版本控制实践
- CHANGELOG 自动生成技巧
- Changeset 中英文交互对照
📖 Monorepo 版本管理的挑战
传统方式的问题
bash
# 场景:需要同时发布 3 个相关的包
# 😫 方式1:手动修改版本号
vim packages/shared/package.json # 1.0.0 → 1.0.1
vim packages/utils/package.json # 1.0.0 → 1.0.1
vim packages/ui/package.json # 1.0.0 → 1.0.1
# 😫 方式2:使用 lerna
lerna version patch
# 所有包都升级,即使有些包没有改动
# 😫 方式3:使用 standard-version
pnpm exec standard-version
# 只能基于 commit 判断,不够灵活
理想的版本管理
bash
# ✅ 期望的效果:
1. 灵活选择要更新的包
2. 自动处理依赖关系
3. 生成清晰的 CHANGELOG
4. 一键发布所有变更
🚀 Changeset 工作流
核心概念
Changeset = 变更集合
markdown
一个 Changeset 包含:
1. 哪些包需要更新
2. 每个包的版本类型(major/minor/patch)
3. 变更描述(会出现在 CHANGELOG 中)
工作流程图
graph LR
A[开发代码] --> B[添加 Changeset]
B --> C[提交代码]
C --> D[准备发布]
D --> E[执行 version]
E --> F[更新版本号]
F --> G[生成 CHANGELOG]
G --> H[构建项目]
H --> I[发布到 NPM]
I --> J[推送到远程]
📦 安装和配置
1. 安装 Changeset
bash
pnpm add -Dw @changesets/cli
pnpm changeset init
2. 配置文件
json
// .changeset/config.json
{
"$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"access": "public",
"baseBranch": "master",
"updateInternalDependencies": "patch",
"ignore": ["@gdu-common/docs", "@gdu-common/build-config"]
}
配置说明:
字段 | 说明 | 推荐值 |
---|---|---|
changelog |
CHANGELOG 生成器 | @changesets/cli/changelog |
commit |
是否自动提交 | false (手动控制更好) |
access |
NPM 发布权限 | public 或 restricted |
baseBranch |
主分支名称 | master 或 main |
ignore |
不需要发布的包 | 文档、构建配置等 |
3. 添加脚本
json
// package.json
{
"scripts": {
"changeset:add": "pnpm changeset add",
"changeset:version": "pnpm changeset version",
"changeset:publish": "pnpm build && pnpm changeset publish",
"changeset:status": "pnpm changeset status",
"uv": "pnpm changeset && pnpm changeset version"
}
}
🎬 完整发布流程实战
场景:修复了一个 Bug
bash
# 1. 修改代码
vim packages/ui/src/components/Button/button.vue
# 2. 添加 Changeset
pnpm changeset add
Changeset 交互流程(中英文对照)
步骤1:选择包
bash
🦋 Which packages would you like to include?
选择要更新的包
( ) @gdu-common/shared
( ) @gdu-common/utils
(●) @gdu-common/ui # 空格选中
( ) @gdu-common/controls-sdk
→ 操作:空格键选择,回车确认
步骤2:选择 Major(主版本)
bash
🦋 Which packages should have a major bump?
选择需要主版本更新的包(不兼容的 API 变更)
( ) all packages
( ) @gdu-common/ui@1.0.0
→ 操作:如果不是破坏性变更,直接回车跳过
步骤3:选择 Minor(次版本)
bash
🦋 Which packages should have a minor bump?
选择需要次版本更新的包(新功能)
( ) all packages
( ) @gdu-common/ui@1.0.0
→ 操作:如果不是新功能,直接回车跳过
步骤4:Patch(补丁版本)
bash
🦋 The following packages will be patch bumped:
以下包将进行补丁更新(Bug 修复)
@gdu-common/ui@1.0.0
→ 说明:未选 major/minor 的包会自动归到 patch
步骤5:输入变更描述
bash
🦋 Please enter a summary for this change
请输入变更说明(会出现在 CHANGELOG 中)
Summary >> 修复 Button 组件点击事件bug
→ 操作:输入中文描述,回车确认
步骤6:确认
bash
🦋 === Summary of changesets ===
🦋 patch: @gdu-common/ui
🦋 Is this your desired changeset? (Y/n)
这是你期望的变更吗?
→ 操作:输入 Y 或直接回车
生成的 Changeset 文件
markdown
## <!-- .changeset/funny-elephants-dance.md -->
## "@gdu-common/ui": patch
修复 Button 组件点击事件bug
提交 Changeset
bash
git add .changeset/funny-elephants-dance.md
git commit -m "docs: 添加变更记录"
git push
🔄 更新版本号
执行 version 命令
bash
pnpm changeset version
自动执行的操作:
bash
🦋 Running version command...
# 1. 读取所有 changeset 文件
📝 Found 1 changeset:
- funny-elephants-dance.md
# 2. 更新 package.json
📦 @gdu-common/ui: 1.0.0 → 1.0.1
# 3. 更新依赖此包的其他包
📦 @gdu-common/docs (dev): @gdu-common/ui@1.0.0 → 1.0.1
# 4. 生成 CHANGELOG
📝 Generating CHANGELOG.md for @gdu-common/ui
# 5. 删除已处理的 changeset 文件
🗑️ Removing .changeset/funny-elephants-dance.md
✅ Version bump complete!
生成的 CHANGELOG
markdown
<!-- packages/ui/CHANGELOG.md -->
# @gdu-common/ui
## 1.0.1
### Patch Changes
- 修复 Button 组件点击事件bug
提交版本更新
bash
git add .
git commit -m "chore: 更新版本到 1.0.1"
git push
📦 发布到 NPM
构建并发布
bash
# 1. 构建所有包
pnpm build
# 2. 发布到 NPM
pnpm changeset publish
自动执行:
bash
🦋 Publishing packages to npm...
📦 @gdu-common/ui@1.0.1
✅ Published successfully!
🏷️ Creating git tag...
✅ Tag v1.0.1 created
🚀 Pushing tag to remote...
✅ Tag pushed
🎉 All packages published!
推送代码
bash
git push --follow-tags
🎯 版本类型详解
Major - 主版本(破坏性变更)
示例:修改组件 API
typescript
// ❌ 旧版本 1.0.0
<Button type="primary">点击</Button>
// ✅ 新版本 2.0.0
<Button variant="primary">点击</Button> // type 改成 variant
// 版本:1.0.0 → 2.0.0
Minor - 次版本(新功能)
示例:添加新属性
typescript
// 旧版本 1.0.0
<Button>点击</Button>
// 新版本 1.1.0
<Button loading>点击</Button> // ✨ 新增 loading 属性
// 版本:1.0.0 → 1.1.0
// 向后兼容,不影响旧代码
Patch - 补丁版本(Bug 修复)
示例:修复 Bug
typescript
// 旧版本 1.0.0 - 有 Bug
const handleClick = () => {
emit('click') // 😱 忘记传递 event
}
// 新版本 1.0.1 - 修复
const handleClick = event => {
emit('click', event) // ✅ 修复
}
// 版本:1.0.0 → 1.0.1
💡 高级用法
1. 预发布版本(Alpha/Beta)
bash
# 进入预发布模式
pnpm changeset pre enter alpha
# 添加变更
pnpm changeset add
# 更新版本
pnpm changeset version
# 生成:1.0.0 → 1.0.1-alpha.0
# 继续添加变更
pnpm changeset add
pnpm changeset version
# 生成:1.0.1-alpha.0 → 1.0.1-alpha.1
# 退出预发布模式
pnpm changeset pre exit
# 最终发布
pnpm changeset version
# 生成:1.0.1-alpha.1 → 1.0.1
2. 批量更新多个包
bash
pnpm changeset add
# 选择多个包
🦋 Which packages would you like to include?
(●) @gdu-common/shared # 空格选中
(●) @gdu-common/utils # 空格选中
(●) @gdu-common/ui # 空格选中
3. 依赖自动更新
bash
# ui 依赖 utils
{
"dependencies": {
"@gdu-common/utils": "workspace:^"
}
}
# 当 utils 更新到 1.1.0 时
# ui 的 CHANGELOG 会自动记录:
## 1.0.5
### Patch Changes
- Updated dependencies
- @gdu-common/utils@1.1.0
4. 查看即将发布的内容
bash
# 查看状态
pnpm changeset status
# 输出
🦋 Changeset status
This branch has:
- 2 changesets that will bump packages
Packages to be bumped:
@gdu-common/ui: patch
@gdu-common/utils: minor
# 预览版本更新(不实际执行)
pnpm changeset version --dry-run
🔧 实际项目配置
package.json 配置
json
{
"name": "@gdu-common/ui",
"version": "1.2.3",
"publishConfig": {
"registry": "http://jfrog.gdu-tech.com/artifactory/api/npm/gdu-npm-front/",
"access": "public"
},
"repository": {
"type": "git",
"url": "https://gitlab.gdu-tech.com/front-group/template/gdu-common.git",
"directory": "packages/ui"
}
}
发布脚本优化
json
// 根 package.json
{
"scripts": {
"uv": "pnpm changeset && pnpm changeset version",
"publish:all": "pnpm build && pnpm changeset publish",
"push": "git push --follow-tags"
}
}
简化流程:
bash
# 一键添加变更并更新版本
pnpm uv
# 一键构建并发布
pnpm publish:all
# 推送
pnpm push
📝 CHANGELOG 管理
自动生成的 CHANGELOG
markdown
# @gdu-common/ui
## 1.2.0
### Minor Changes
- 添加 Button 组件的 loading 状态
- 支持自定义图标
### Patch Changes
- 修复 Input 组件的清除按钮样式问题
- Updated dependencies
- @gdu-common/utils@1.1.0
## 1.1.0
### Minor Changes
- 新增 Input 组件
- 新增 Modal 组件
### Patch Changes
- 修复 Button 组件在 Safari 下的样式问题
CHANGELOG 最佳实践
markdown
# ✅ 好的变更描述
- 修复 Button 组件在暗色模式下的对比度问题
- 添加 Input 组件的键盘导航支持
- 优化 Modal 组件的动画性能
# ❌ 不好的变更描述
- 修复 bug
- 更新组件
- 改进代码
🎯 实战演练:完整发布流程
场景:开发了一个新功能
bash
# ===== 第1步:开发完成 =====
# 添加了 Button 组件的 loading 状态
# ===== 第2步:添加 Changeset =====
pnpm changeset add
🦋 Which packages would you like to include?
(●) @gdu-common/ui
🦋 Which packages should have a minor bump?
(●) @gdu-common/ui@1.1.0 # 新功能选 minor
🦋 Summary >> 添加 Button 组件的 loading 状态
🦋 Is this your desired changeset? (Y/n) >> Y
✅ Changeset added!
# ===== 第3步:提交 Changeset =====
git add .changeset/
git commit -m "docs: 添加变更记录"
git push
# ===== 第4步:准备发布 =====
# 所有功能开发完成,准备发布
# ===== 第5步:更新版本号 =====
pnpm changeset version
🦋 All files have been updated and committed
# 查看变更
git diff HEAD~1
# packages/ui/package.json
- "version": "1.1.0"
+ "version": "1.2.0"
# packages/ui/CHANGELOG.md 新增内容
+ ## 1.2.0
+ ### Minor Changes
+ - 添加 Button 组件的 loading 状态
# ===== 第6步:提交版本更新 =====
git add .
git commit -m "chore: 发布 v1.2.0"
git push
# ===== 第7步:构建 =====
pnpm build
Tasks: 4 successful, 4 total
Cached: 3 cached, 4 total
Time: 2.1s
# ===== 第8步:发布到 NPM =====
pnpm changeset publish
🦋 Publishing packages to npm...
📦 @gdu-common/ui@1.2.0
✅ Published
# ===== 第9步:推送标签 =====
pnpm push
✅ 发布完成!
🎨 高级技巧
1. 一次更新多个包
bash
# 场景:utils 添加了新函数,ui 使用了这个函数
pnpm changeset add
🦋 Which packages would you like to include?
(●) @gdu-common/utils # utils 新增功能
(●) @gdu-common/ui # ui 使用新功能
🦋 Which packages should have a minor bump?
(●) @gdu-common/utils@1.0.0 # utils minor
🦋 Which packages should have a patch bump?
(●) @gdu-common/ui@1.1.0 # ui patch(只是使用,不是新功能)
🦋 Summary >>
工具库新增 formatCurrency 函数
UI 组件使用新的货币格式化工具
2. 依赖自动升级
json
// .changeset/config.json
{
"updateInternalDependencies": "patch"
}
效果:
bash
# utils 升级到 1.1.0
# ui 依赖 utils,自动升级一个 patch 版本
@gdu-common/utils: 1.0.0 → 1.1.0 (minor)
@gdu-common/ui: 1.0.0 → 1.0.1 (patch, 因为依赖更新)
3. 快捷命令
json
{
"scripts": {
"uv": "pnpm changeset && pnpm changeset version"
}
}
bash
# 一个命令完成添加和更新
pnpm uv
# 交互完成后,自动执行 version
# 减少操作步骤!
📊 版本管理最佳实践
1. 语义化版本控制
严格遵循 SemVer:
版本格式:主版本.次版本.补丁版本
1.2.3
│ │ │
│ │ └─ Patch:Bug 修复,向后兼容
│ └─── Minor:新功能,向后兼容
└───── Major:破坏性变更,不向后兼容
判断标准:
变更类型 | 版本类型 | 示例 |
---|---|---|
修改 API 接口 | Major | 删除/重命名参数 |
添加新组件 | Minor | 新增 Button 组件 |
修复样式bug | Patch | 修复按钮颜色 |
优化性能 | Patch | 优化渲染性能 |
添加新属性 | Minor | Button 新增 loading |
修改内部实现 | Patch | 重构内部逻辑 |
2. 变更描述规范
✅ 好的描述:
markdown
- 添加 Button 组件的 loading 状态支持
- 修复 Input 组件在 IE11 下的兼容性问题
- 优化 Modal 组件的打开动画性能,减少 30% 的渲染时间
❌ 不好的描述:
markdown
- 更新
- 修复bug
- 改进
3. 发布前检查
bash
# 创建 pre-publish 脚本
{
"scripts": {
"prepublishOnly": "pnpm lint:all && pnpm build"
}
}
# 发布前自动执行检查和构建
🤖 自动化发布
CI/CD 集成
yaml
# .gitlab-ci.yml
version-check:
stage: check
script:
- |
if [ -d ".changeset" ] && [ "$(ls -A .changeset/*.md 2>/dev/null)" ]; then
echo "✅ Found changeset files"
else
echo "⚠️ No changeset files found"
echo "💡 Run: pnpm changeset add"
exit 1
fi
publish:
stage: deploy
only:
- master
script:
- pnpm install
- pnpm build
- pnpm changeset publish
📈 实际效果
版本发布效率对比
方式 | 手动操作 | Changeset | 提升 |
---|---|---|---|
修改版本号 | 5分钟 | 30秒 | 10x |
生成 CHANGELOG | 15分钟 | 自动 | ∞ |
发布到 NPM | 10分钟 | 2分钟 | 5x |
总耗时 | 30分钟 | 3分钟 | 10x |
版本管理准确性
bash
# 使用 Changeset 前
- 忘记更新版本号:20% 的发布
- 版本号错误:10% 的发布
- CHANGELOG 缺失:50% 的发布
# 使用 Changeset 后
- 自动更新,100% 准确 ✅
- 自动生成 CHANGELOG ✅
- 依赖关系自动处理 ✅
🤔 常见问题
Q1: 多人协作时如何处理冲突?
场景: 两个人同时添加了 changeset
bash
# 开发者 A
.changeset/cool-cats-sing.md
→ 更新 ui 组件
# 开发者 B
.changeset/brave-dogs-jump.md
→ 更新 utils 工具
# 合并后
pnpm changeset version
# ✅ 自动合并两个变更,生成统一的版本
Q2: 如何回滚发布?
bash
# 1. 删除 NPM 上的版本(24小时内)
npm unpublish @gdu-common/ui@1.2.0
# 2. 删除 Git tag
git tag -d v1.2.0
git push origin :refs/tags/v1.2.0
# 3. 回滚代码
git reset --hard HEAD~1
Q3: 如何发布特定的包?
bash
# 只发布 ui 包
pnpm changeset publish --filter @gdu-common/ui
# 或者在添加 changeset 时只选择 ui
Q4: Changeset 文件可以手动编辑吗?
markdown
## <!-- .changeset/cool-update.md -->
"@gdu-common/ui": minor
"@gdu-common/utils": patch
---
添加新功能并修复相关bug
详细说明:
- UI 组件新增 loading 状态(minor)
- 工具函数修复边界情况(patch)
可以! 手动编辑可以更精确地控制版本和描述。
🎁 快速上手清单
初始化
bash
# 1. 安装
pnpm add -Dw @changesets/cli
# 2. 初始化
pnpm changeset init
# 3. 配置 .changeset/config.json
# 4. 添加脚本到 package.json
日常使用
bash
# 开发功能 → 添加变更
pnpm changeset add
# 准备发布 → 更新版本
pnpm changeset version
# 发布 → 构建并发布
pnpm build && pnpm changeset publish
# 推送
git push --follow-tags
检查清单
-
.changeset/config.json
配置正确 -
baseBranch
设置为你的主分支 -
ignore
包含不需要发布的包 - 各包的
publishConfig
配置正确 - Git 远程仓库配置正确
🎉 总结
Changeset 提供了:
核心价值
- ✅ 灵活的版本控制 - 精确控制每个包的版本
- ✅ 自动的依赖更新 - 依赖包自动同步版本
- ✅ 清晰的 CHANGELOG - 自动生成,格式统一
- ✅ 简单的工作流 - 3个命令完成发布
实际收益
- 📈 版本管理效率提升 10 倍
- 🎯 版本准确性 100%
- 📝 CHANGELOG 覆盖率 100%
- ⏱️ 发布时间从 30分钟 → 3分钟
关键命令
bash
pnpm changeset add # 添加变更
pnpm changeset version # 更新版本
pnpm changeset publish # 发布
pnpm changeset status # 查看状态
在下一篇文章中,我将分享 VitePress 文档站点的搭建和优化,包括:
- 自定义主题配置
- 组件示例自动导入
- 交互式文档
- SEO 和性能优化
🔗 系列文章
- 📖 上一篇: 代码质量保障:ESLint + Prettier + Stylelint 完美配置
- 📖 下一篇: 《VitePress 文档站点:打造专业级组件文档》
- 🏠 专栏首页: 从零到一:构建现代化企业级 Monorepo 项目实战
Changeset 真的很好用!如果你也在用或者准备用,点个赞让我知道! 👍
你的版本管理用的什么方案?有什么坑要分享?评论区见! 💬