资讯详情

Changesets 实战:Monorepo 版本管理与自动化发布流程

发布时间:2026/9/26 11:49:09

500+
企业客户服务经验
120+
行业领域内容覆盖
3000+
原创页面设计沉淀
98%
客户满意度

Changesets 实战:Monorepo 版本管理与自动化发布流程

如果你正在维护一个 monorepo或者在版本管理这件事上经常被“这个 PR 影响的包到底要不要发新版本、版本号该升 minor 还是 patch”这类问题折磨到半夜那我建议你认真了解一下 Changesets。它是一套基于变更记录文件的版本管理方案在 npm 生态里已经成为大量 monorepo 项目的默认选择。核心用法不复杂代码改动时顺手写一份变更记录最后执行一条命令所有包的新版本号、CHANGELOG、内部依赖引用都会被自动整理好。我会从它要解决的问题讲起把初始化、日常操作、自动化发布完整串一遍最后附上我实际踩过的一些坑。1. Changesets 解决的核心问题版本管理的无序性1.1 手动版本管理容易踩的三类坑手动维护版本号在一个单包里其实没那么痛苦但一旦走进 monorepo事情就变得不可控。最常见的一类坑是“改了代码忘了升版本”某个基础库被改出 bug使用者却还在引用旧版本排查问题时看了一眼 package.json版本号没变于是绕了一大圈才发现问题出在依赖没升级。这种事我经历过不止一次尤其在多人协作时格外明显——你无法要求每个人都记得“我动了 shared/utils要把它的 version 从 1.3.1 升到 1.3.2”。第二类坑是版本号“拍脑袋”有人觉得改动不大随手升个 patch 就完事实际上接口签名变了按 SemVer 应该是 minor 甚至 major。版本号一旦失去语义后面所有依赖范围的判断都会失真这也直接导致第三类坑——CHANGELOG 无从写起。很多团队会在发版前临时问“这次有哪些变动”回答往往是翻 commit history 或者靠记忆拼凑写出来的 changelog 极其敷衍。大家也清楚这不对但 GitHub 的 commit message 又不总是规范靠提交历史生成 changelog 的质量其实很随机。这三类坑的本质是版本发布的信息散落在代码、commit、大脑记忆三个地方没有任何一个环节强制你把它们汇聚起来。Changesets 做的事情就是把“这次改动影响了哪些包、应该升什么级别、具体改了什么”这些信息在你写代码的那个时间点就固化下来。1.2 变更集文件把“升级决定”提前写下来Changesets 的核心机制说穿了并不复杂每个版本相关的改动都对应一个 Markdown 文件放在项目的 .changeset 目录下注意目录名不带 s我一开始拼错过好几次。这个文件不直接修改任何版本号它只写三件事影响哪些包、这些包分别应该升 patch / minor / major、以及一段写给用户看的变更描述。当你把这段描述想象成“一个 mini CHANGELOG 条目”一切就顺理成章了。这样的设计有几个明显优势。第一变更信息跟代码一起合入PR 合并后信息不会丢第二发版时不需要重新回忆工具会汇总所有变更集并统一处理第三因为只是文件所以完全可以用 Git 管理一个变更集就是一个 diff代码评审时顺便就能看到“这个改动会影响谁、影响多大是否合理”。我特别喜欢第三点——版本升级的决策不再藏在某个人的脑子里而是变成团队可 review 的显式信息。1.3 与语义化版本结合让版本号有据可依语义化版本SemVer的规则大家都懂patch 修 bug、minor 加功能、major 有破坏性变更。但真正难的是执行尤其是“依赖 A 修了一个 bug依赖它的包 B 要不要跟着发版”这类链式判断。Changesets 不替你决定级别它强迫你在提交时选一个级别然后按 SemVer 规则推导最终版本号同时处理包与包之间的依赖引用。这种“人做语义判断、工具做数值计算”的分工既保留了人的判断力又杜绝了手算版本号时的低级错误。如果你的项目有多个变更集作用于同一个包Changesets 会合并处理取其中最高的版本升级级别。比如一个变更集标记为 patch另一个标记为 minor那最终版本会按 minor 升。我第一次遇到这种情况时还有点意外后来想想是对的——发版本来就是一个“催收”过程所有积累的改动一起出一次性抬升版本比发两个版本合理得多。2. Changesets 核心机制拆解从变更集到版本号2.1 一个变更集文件到底长什么样进入项目后在 .changeset 目录下看到的文件长这样文件名是随机生成的字符串比如 rapid-koalas-punch.md--- yourscope/core: minor yourscope/utils: patch --- 新增了配置缓存能力顺手修了 utils 里一个因边界值导致的错误。开头那段 YAML frontmatter 里key 是包名value 是这次想要申请的升级级别。注意这里写的是“目标级别”不是最终版本号。比如 core 当前是 1.2.0frontmatter 写 minor那最终会变成 1.3.0。底下正文就是给用户的变更说明会在执行版本升级时合并进 CHANGELOG.md。文件名是随机生成的这件事有人可能觉得丑但我的体会是它很有用作为唯一标识符它避免了人为命名带来的冲突和语义重复。你在两个分支上各写了一个变更集合到 main 时会发现它们是两个不同文件这比两个人同时改了同一个 CHANGELOG.md 再合并要省心得多。2.2 新版本号是怎么被推导出来的如果你以为 Changesets 只是把 frontmatter 里的 minor 翻译成 0.1那就太小看它了。真正有价值的是它对内部依赖关系的处理。假设 yourscope/core 从 1.2.0 升到 1.3.0而 yourscope/utils 这次也升到了 1.4.1。同时工作区里还有一个包 yourscope/app 依赖 core但这次没有任何改动。默认配置下updateInternalDependencies 为 patchchangeset version 会把 app 的依赖声明从yourscope/core: 1.2.0更新到1.3.0并给 app 也安排一个 patch 版本确保它发布以后引用的不是不存在的版本组合。计算流程大致是先读出所有变更集 → 对每个涉及的包计算目标版本 → 收集那些依赖了被升级包的内部工作区包 → 按 updateInternalDependencies 规则决定它们要不要跟着发版 → 更新 package.json 里的依赖声明 → 生成 CHANGELOG → 删除变更集文件。你会发现这一步做的其实是“把版本图和依赖图对齐”这正是 monorepo 发布里最繁琐、最容易错的部分。2.3 版本升级与发布是两个独立过程Changesets 把“计算版本”和“真正发布”明确拆成了两条命令changeset version负责在本地改版本号、写 changelogchangeset publish负责把包推到 npm registry并且会自动跳过已经存在过的版本。这两步分离是我认为它设计上最高明的一点。分离带来两个实际好处。一是发布前可以完整预览 diff执行 changeset version 后所有 package.json 和 CHANGELOG.md 的改动都进入 git代码评审能看到“本次发版会变成什么版本、会影响哪些包”。二是在 CI 里可以做成“先合并发布 PR再触发实际 publish”的稳妥流程而不是把版本计算和发布硬绑在一个命令里出错了很难回退。很多工具把这两件事拧巴到一起看起来省事真出事时才知道麻烦。2.4 为什么在 monorepo 里这套机制特别省心我见过不少 monorepo 项目发布逻辑都是自己脚本翻遍 packages 目录、逐个判断 git 提交时间、再倒推版本号脚本写得越来越长边界情况越来越多。这些脚本最大的问题是没有“意图”——它们只能靠时间、commit 做推断永远无法知道某个提交到底是有意升级还是顺手改了文件。Changesets 在 monorepo 里之所以好用是因为它把“意图”显式化。每个包在被改动时提交人就亮明态度这个改动对包来说是 patch / minor / major。发布时汇总这些态度再结合依赖关系自动补齐受连带影响的包。这样就算一个 monorepo 里十几个包发布顺序、版本号、changelog 也都是可预测的结果而不是靠巧合。对我这种没有专职 DevOps 支持的小团队来说省掉的自动化脚本维护成本是很可观的。3. 从零接入 Changesets安装与初始化配置3.1 安装 changesets/cli 的正确姿势先说结论在 monorepo 根目录安装不要装到某个子包里。因为 changeset 命令是从根目录读取配置和整个 workspace 的包清单装错地方你会发现它根本找不全包。以 pnpm 为例pnpm add -Dw changesets/cli-D是作为 devDependency-w是 --workspace-root表示安装到 monorepo 根目录。npm 和 yarn 对应的命令也类似只要保证它出现在根 package.json 的 devDependencies 里就行。我这里默认你用的是 pnpm workspace因为这是目前 monorepo 组合里我试下来最顺的但 Changesets 本身和 npm / yarn / pnpm 都能配合。装完先验证一下版本pnpm changeset --version能输出版本信息说明 PATH 已经生效。不要跳过这一步我见过装完却提示找不到命令的情况大多是 pnpm 自身的脚本环境没刷新重开终端或者用pnpm exec changeset试试通常能解决。3.2 初始化生成 config.json安装完成后执行pnpm changeset init它会帮你创建 .changeset 目录里面生成一个 config.json 和一个 README.md。README 是用来给贡献者看的说明config.json 才是核心。初始化生成的默认配置大概长这样{ $schema: https://unpkg.com/changesets/config2.3.1/schema.json, changelog: changesets/cli/changelog, commit: false, fixed: [], linked: [], access: restricted, baseBranch: main, updateInternalDependencies: patch, ignore: [] }不要觉得这些配置都无所谓里面有几个字段会直接决定你发布的行为。我一向建议初始化之后先花三分钟逐项过一遍而不是直接开写变更集。下面把我认为最重要的几个字段单独拿出来解释。3.3 关键配置项逐个说清楚先看access。它决定发布到 npm 时是公开还是私有的。如果你的包名带 scope比如 yourscope/corenpm 默认不允许直接发布公开包这里必须显式设置成public否则 publish 时会收到类似“must either provide an npm token or configure access”的报错。私有包则保持restricted但要注意私有包发布需要登录 npm 账号且 registry 支持。大部分个人或小团队项目都是公开包所以第一个要改的通常就是这个字段。再看baseBranch。它指定主分支名称默认是 main。很多项目主分支还是 master如果不对齐changeset 的某些基于分支对比的功能会计算错误。我改过的最常见配置就是把 baseBranch 改成 master。然后是fixed和linked这两个字段经常被混在一起聊但它俩的语义完全不同。fixed是把一组包绑死在同一版本号上其中一个升到 1.2.0其他包也被迫升到 1.2.0典型场景是 react 和 react-dom 这种必须绝对同步的兄弟包。linked则只保证“同一批发布”版本号各自独立——比如你的设计系统里 button 和 icon 两个包button 升到 2.1.0icon 可能升到 0.4.2但它们会在同一次发版里一起被处理这样引用它们的配套组件不会出现版本的交叉断裂。用之前先想清楚你要的是绝对版本一致还是发布节奏一致这俩别写反了。最后是updateInternalDependencies它控制内部依赖变化时依赖方要不要跟着发版。默认patch是“最保守也最安全”的选择任何内部包版本变化都会触发依赖它的包发一个 patch 版本从而把 package.json 里的依赖声明同步到新版本。如果觉得这种连带发版太频繁可以改成minor意思是只有被依赖的包升到 minor 以上依赖方才跟着发版。我的建议是初期保持默认 patch等发布节奏稳定后再收紧。4. 完整实操流程创建变更集、升级版本、发布包4.1 日常开发中如何创建一个变更集最正规的做法是在功能分支里跟着代码一起写变更集。当你改完一个包后在终端执行pnpm changeset命令会进入交互式问答先让你选影响哪些包可以多选再让你选每个包是 patch、minor 还是 major最后让你输入一段变更描述。回答完它就在 .changeset 目录下生成一个文件。我的习惯是描述尽量具体不要写“修复了若干问题”这种废话因为这段文字最终会原样进到 CHANGELOG.md用户真的会读。如果你更习惯手动直接创建 Markdown 文件也行。但交互式命令有个好处是在选择包时它会列出 workspace 里所有可发布的包避免你漏选或者选错包名。对于改动特别多、一个 PR 涉及多个包的场景我会分几次执行为每个包生成独立变更集这样发版时 CHANGELOG 里的归类更清晰。这里有一个实操心得把“创建变更集”写进 PR 的规范里。我在项目 README 的贡献指南中加了一条——“任何修改了 packages 下代码的 PR必须附带一个 changeset 文件”。没有这条硬性要求你会发现团队里总有人因为忘记而被发布流程卡住。4.2 执行 changeset version 时会发生什么当 .changeset 目录里积累了几个变更集准备正式发版时执行pnpm changeset version这条命令会做一堆事情我建议你执行前先确认本地 git 是干净的因为它的输出会直接改动一堆文件。具体包括按 frontmatter 里的规格计算并更新所有涉及包的版本号为每个有新版本的包生成或追加 CHANGELOG.md更新内部依赖的引用关系最后删除已经处理过的变更集文件。执行完你会看到 package.json 被改了一轮changelog 文件也丰富了起来。这时候不要急着 commit先git diff看一遍。重点看三类内容版本号是否符合预期、changelog 是否生成了正确的分组、依赖声明是否被同步更新。我踩过的坑是没看直接提交结果发到 npm 才发现某个包版本升错了只能急急忙忙再发一个补丁版本非常被动。如果你在 config.json 里设置了commit: truechangeset version 会自动帮你把改动提交到 git但生成 tag 这一步默认不自动做需要你手动处理或者交由后面的 publish 命令完成。4.3 发布到 npm 的完整步骤执行pnpm changeset publish之前确保你已经执行过 build。Changesets 本身不管编译它只负责把 package.json 声明好的文件发出去如果你的 dist 目录没有生成发布出去就是一个坏包。所以在 monorepo 里我的标准顺序是pnpm changeset version pnpm build pnpm changeset publish先通过 version 把版本号定下来再用这个新版本号跑一次构建最后才让 publish 推出去。这样产物文件里嵌入的版本信息和 package.json 始终保持一致不会出现源码和编译产物版本对不上的尴尬。publish 命令执行时会先检查 registry 上是否已经存在当前版本如果存在就跳过。这对 monorepo 尤其重要——你不必担心重复发布也不必手动维护“哪些包已发布”的列表。发布完成Changesets 还会生成 git tag 并 push具体行为取决于配置和版本方便回溯版本历史。关于 npm 认证确保你的 npm token 已配置好或者已执行npm login。CI 场景下则把 token 放到环境变量里。对于公开 scoped 包access 要设成 public。我第一次发布 scoped 包时在这里卡了很久报错信息里的提示其实已经很明确但就是容易忽略。4.4 接入 GitHub Actions 自动发版如果每次发版都要人肉执行命令Changesets 的优势至少减半。最常见的接入方式是利用官方提供的 changesets/action让它在 push 到 main 分支时自动做两件事开一个“版本发布 PR”以及在 PR 合入后立即执行 publish。下面这个 workflow 示例是我在实践中使用的一个简化版本name: Release on: push: branches: - main jobs: release: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - uses: pnpm/action-setupv2 - uses: actions/setup-nodev4 with: node-version: 20 cache: pnpm - run: pnpm install - run: pnpm build - name: Create Release Pull Request or Publish uses: changesets/actionv1 with: version: pnpm changeset version publish: pnpm changeset publish env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }}这个流程的工作方式很有意思如果有变更集文件action 会创建一个名为 “Release Packages” 的 PR把版本号和 changelog 都整理好如果 PR 被合并后没有新的变更集它就直接走 publish。团队里其他人不用安装任何本地工具只要在 PR 里附带变更集文件一切都自动化了。第一次配置时记得把 NPM_TOKEN 加到仓库 secrets 里否则 publish 那步会因缺少认证直接失败。5. 常见问题与排查技巧实录5.1 变更集文件在合并时冲突了怎么办多人并行开发两个分支都改了 .changeset 下的文件合并时冲突很正常。但变更集是 Markdown 文件冲突处理起来比 package.json 简单多了直接打开冲突文件把你需要的标题块和内容合并保留即可。如果是两个人分别改了不同包冲突时就把两个 frontmatter 块按格式合并进一个文件如果影响同一个包判断一下级别取较高的那个级别保留描述部分手动合并成一段完整的 changelog 内容。这里有个建议一个变更集文件尽量只描述一个核心改动别把它当成大杂烩。这样两个分支同时修改同一个变更集的概率会小很多就算真冲突了你合并时也比较容易看出哪些内容该留、哪些是另一方的改动。5.2 内部依赖版本没有跟着更新多半是这几种情况表现是A 依赖 BB 发布了新版本但 A 的 package.json 里对 B 的引用还是旧版或者 A 根本没有产生新的版本号。最常见的原因有这几个。第一如果你的 workspace 里用的是workspace:*协议发布时 pnpm 会自动把协议替换成真实版本号你在本地 package.json 里看不到变化是正常的不用慌。第二如果你手动写了具体版本号比如B: 1.3.0但updateInternalDependencies设置成了minor而 B 只是 patch 升级那 A 确实不会跟着变。第三A 在ignore列表里。这几个场景我都遇到过排查时先确认 A 是否在配置的 ignore/fixed 中再检查协议类型基本就能定位。5.3 只想发布部分包--only 用法一个大的 monorepo 经常只改了一小撮包但 changeset publish 默认会把所有有新版本的包都发出去。如果某些包因为版本计算存在依赖关系你并不想真的发布怎么办较新版本的 CLI 支持在 publish 时加--only参数限定范围pnpm changeset publish --only yourscope/core这条命令只会发布你指定的包其他新版本会留在本地。我一般用在“主版本发版但某个新包还想留在仓库内部再验证几轮”的场景。注意 --only 只影响发布不改变版本号计算所以如果还有其他包因依赖关系被 version 升级了它们的 package.json 已经改了只是暂时没发布到 registry 而已。5.4 预发布版本alpha/beta的完整流程想发 alpha 或 beta 测试包Changesets 提供了 pre 模式。进入 pre 模式前建议先把当前已积累的变更集处理掉避免 pre 版本和正式版本混在一起导致发版混乱。然后执行pnpm changeset pre enter alpha进入 pre 模式后后续执行pnpm changeset创建的变更集会带上 pre 标记再执行pnpm changeset version时版本号会被计算成1.1.0-alpha.0这种形式。发布后测试工作流跑完最后退出 pre 模式pnpm changeset pre exit退出后之前那些打上 pre 标记的变更集就会按正式版本规则重新计算最终发成正式版。这里有两个易踩的坑一是 pre 模式下的版本号计算和正式版的计数方式不同不要拿 pre 的 count 去类比正式发布二是 exit 前记得把所有该提交的文件都提交pre 模式的配置文件也放在 .changeset 下用 git 管理不要手工删掉再重新 init。5.5 几个值得收藏的调试命令最后整理几个我常用的命令都是线上排查时帮我省过时间的。命令用途pnpm changeset status查看待发布的包和变更集信息确认发布范围pnpm changeset status --since main只看基于 main 分支之后新增的变更集适合 PR 检查pnpm changeset status --outputstatus.json把状态导出成 JSON供 CI 脚本判断是否允许发布pnpm changeset version --help查看 version 的可用参数偶尔用到了看看pnpm changeset pre enter beta进入 beta 预发布模式alpha/beta 可替换其中status --output我比较推荐如果你在 CI 里自定义过发布条件可以直接解析 JSON判断有没有必要的包待发布从而决定要不要触发后续流程。不过要注意 status 命令在没有任何变更集会返回非零退出码如果你正好在做“无变更集则跳过发布”的检查这反而是你需要的信号。我实际用下来最大的体会是Changesets 不是一个帮你写版本号的脚本而是把团队的发布习惯变好的一种约束。它在代码合入的那一刻就问清楚“这次动了谁、想怎么升”这些答案沉淀成文件后发版就变成了一件几乎没有脑力负担的流程。我见过太多团队在发布脚本上维护越来越复杂的逻辑到最后连写脚本的人都说不清边界情况Changesets 用一套非常朴素的文件约定把版本决策从提交人开始就固定下来反而让整个流程回到了简单。如果你还没有引入任何版本管理工具遇到 monorepo 或多包发布需求的时候我建议先别急着写发布脚本试试把变更集文件当成唯一的事实来源如果已经有脚本也可以慢慢把脚本里的版本计算逻辑迁移到 changeset version 上只保留 build 和发布的前置步骤。小团队没有专职 DevOps这套方案带给我的安全感说不太清楚原因就是发版那天终于不用盯着电脑等出错了。
热门专题

继续阅读更多专题内容

围绕企业服务、数字化转型与官网运营的常青话题,持续输出深度内容

企业官网建设指南 企业托管服务模式 财税政策与解读 企业数字化转型 官网SEO与获客 网站安全与运维
配套服务

读完这篇文章,了解更多服务

从整站搭建到SEO布局,17项核心服务助您打造高转化的企业官网

01

企业托管整站搭建

从信息架构到栏目预留,搭建可生长的企业站点骨架,每个页面独立原创设计。...

了解详情
02

规整可信网页设计

雪地靴温暖风原创设计,金属铜线条贯穿全页,拒绝通用模板与AI流水线。...

了解详情
03

企业服务SEO布局

关键词体系与语义化结构,从建站源头为搜索排名而生。...

了解详情
04

业务预约咨询表单

多场景表单与线索收集体系,把访问流量转化为可追踪的销售线索。...

了解详情
05

企业服务站点运维

安全巡检、数据备份与内容更新支持,全年守护网站稳定运行。...

了解详情
06

全终端商务适配

电脑、平板、手机一致呈现,移动端体验与转化同样出色。...

了解详情
需要专业建议?

让专业顾问为您解读行业趋势

关于企业官网建设、SEO获客与数字化转型的任何疑问,欢迎一对一咨询我们的专业顾问。