Polaris 多包仓库的 Changesets 变更日志与版本发布指南

原创2026-10-06 18:18:471,986 阅读
文章标签:前端UI组件

Polaris 多包仓库的 Changesets 变更日志与版本发布指南

导读

本文以 Polaris 设计系统仓库根目录下 .changeset/README.md 为骨架,完整讲解这个由 pnpm workspace + Turborepo 组织、横跨 @shopify/polaris-react、@shopify/polaris-tokens、@shopify/polaris-icons、@shopify/polaris-migrator、@shopify/stylelint-polaris 等多个 npm 包的多包仓库,如何使用 Changesets 统一管理 CHANGELOG 与版本发布。读者学完后,将能在自己的 feature 分支上熟练运行 pnpm changeset,正确回答 CLI 的三个版本决策问题(包含哪些包、哪些 major、哪些 minor),并理解 .changeset/config.json 的配置语义、changeset 文件的 front matter 结构,以及它们如何与 changeset version、changeset publish 和 Turbo 构建流水线协同工作,最终在 CI 中自动生成 "Version Packages" PR 并发布到 npm。

一、为什么用 Changesets:多包仓库的统一版本控制

1.1 仓库背景:一个仓库、多个 npm 包

当前仓库以 pnpm workspace 管理多个独立发布的包。根据 pnpm-workspace.yaml 的 packages 列表,共包含以下子包:

子包目录 npm 包名 用途
polaris-react @shopify/polaris Shopify 后台核心 React 组件库(当前版本 13.10.1)
polaris-tokens @shopify/polaris-tokens 设计令牌(9.4.2)
polaris-icons @shopify/polaris-icons 图标库(9.3.1)
polaris-migrator @shopify/polaris-migrator 升级 codemod 工具(1.0.7)
stylelint-polaris @shopify/stylelint-polaris Stylelint 校验工具(16.0.7)
polaris-for-vscode VSCode 扩展 编辑器中提供 Polaris 辅助能力
polaris.shopify.com 站点 设计系统文档站

这些包之间的依赖关系也很清晰:@shopify/polaris 依赖 @shopify/polaris-icons 与 @shopify/polaris-tokens;@shopify/polaris-migrator 又依赖 @shopify/polaris-tokens 和 @shopify/stylelint-polaris。当一次 PR 同时改动多个包时,如果靠人工维护各包的 CHANGELOG 与版本号,很容易漏改、错改,或出现内部依赖版本不匹配。Changesets 正是为这种"monorepo 多包发布"场景设计的方案:开发者只需在 PR 里描述这次改动影响哪些包、属于什么语义化版本级别,版本号与 CHANGELOG 的落地交给工具自动完成。

1.2 Changesets 在本仓库中的落地位置

本仓库的 Changesets 配置集中在 .changeset 目录,当前包含三个文件:

根目录 package.json 中还暴露了与版本发布相关的脚本(均通过根目录的 pnpm 调用):

  • "changeset": "changeset":新建 changeset 的入口;
  • "version-packages": "changeset version && pnpm install --lockfile-only":落地版本号与 CHANGELOG,并同步锁文件;
  • "release-packages": "pnpm build:release && changeset publish":构建全部非站点包后发布到 npm;
  • "build:release": "turbo run build --filter='!polaris.shopify.com'":用 Turborepo 构建除站点外的所有包。

也就是说,Changesets 并不是孤立的一个目录,而是与仓库的构建、发布流水线深度绑定。

二、核心操作:运行 pnpm changeset 添加变更条目

2.1 前置约定

根据 .changeset/README.md 的说明,为你的 PR 添加 CHANGELOG 条目的标准姿势是:在你的 feature 分支上运行:

pnpm changeset

然后使用方向键(arrow)、空格键(spacebar) 和 回车键(return) 回答 Changesets CLI 提出的三个问题。整个交互过程是 TUI 式的多选界面,不需要手写任何 YAML。

2.2 问题一:🦋 Which packages would you like to include?

  • 按 Space 键勾选本次改动涉及的包(可多选);
  • 按 Enter 键进入下一个问题。

这一步是"改动范围声明":本次 PR 改了哪些包,就把哪些包勾进来。勾选本身不决定版本级别,只决定"哪些包的 CHANGELOG 会获得条目"。

2.3 问题二:🦋 Which packages should have a major bump?

  • 直接按 Enter 表示本轮没有破坏性变更,进入下一问;
  • 若本次改动包含破坏性变更(如移除组件 API、修改公共接口),则用方向键定位到对应包,按 Space 勾选,使其在发布时获得 major 级别升级(对应 SemVer 的 X.0.0)。

2.4 问题三:🦋 Which packages should have a minor bump?

  • 直接按 Enter 表示不选择任何 minor 包,此时你的改动会自动被归为 patch 级别;
  • 若本次改动包含向后兼容的新功能(新增组件、新增 props、新增 API),用上下方向键定位并配合空格键勾选,使其获得 minor 升级(对应 SemVer 的 X.Y.0)。

注意这里的关键设计:"默认 patch"。如果不主动声明 major 或 minor,Changesets 会保守地按 patch 处理,避免开发者遗漏标记导致意外的大版本提升。CLI 完成后,终端会输出类似如下的确认信息:

🦋  The following packages will be patch bumped:
🦋  {PACKAGE NAME}
🦋  {PACKAGE NAME}
🦋  Please enter a summary for this change (this will be in the changelogs).
🦋    (submit empty line to open external editor)
🦋  Summary › {CHANGELOG ENTRY}

接下来输入一段面向读者的摘要(Summary),它会直接进入该包未来的 CHANGELOG。如果直接提交空行,Changesets 会打开外部编辑器($EDITOR)供你撰写更长的说明。

2.5 提交与收尾

  • 按 Change Log 内容规范撰写摘要(本仓库 README 指向团队维护的 changelog 编写规范,核心要求是:写清楚"对使用者意味着什么",而不是复述代码 diff);
  • 随后 commit 并 push 这个生成的 changeset 文件即可。

2.6 一个真实示例:changeset 文件长什么样

运行 pnpm changeset 后会在 .changeset 目录生成一个随机命名(通常是"形容词-名词-动词"风格)的 Markdown 文件。仓库里现成的 .changeset/loud-rivers-wear.md 就是一个标准示例:

---
'polaris.shopify.com': patch
---

Updating dux package to 5.0.1

其结构非常清晰:

  • 开头的 --- 包裹部分是 front matter,每行形如 '包名': 版本级别,声明"哪个包要升到什么级别";
  • --- 之后是 CHANGELOG 正文,即开发者输入的 summary;
  • 该文件还有未使用的元数据部分(如 '@shopify/polaris': minor 这种行会被同时保留用于说明内部依赖是否需要跟随升级)。

正因如此,changeset 文件本身就是一个可评审、可回溯的"发布意图声明",PR reviewer 可以在合并前就确认版本级别是否合理。

三、配置解读:.changeset/config.json 的每一项语义

仓库的 .changeset/config.json 是 Changesets 的全局配置,内容如下:

{
  "$schema": "https://unpkg.com/@changesets/config@2.0.0/schema.json",
  "changelog": ["@changesets/changelog-github", {"repo": "Shopify/polaris"}],
  "commit": false,
  "fixed": [],
  "linked": [],
  "access": "public",
  "baseBranch": "main",
  "updateInternalDependencies": "patch",
  "ignore": [],
  "___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
    "updateInternalDependents": "always"
  }
}

各字段含义如下:

配置项 值 含义与影响
$schema @changesets/config@2.0.0/schema.json 编辑器校验与智能提示用,声明配置 schema 版本
changelog ["@changesets/changelog-github", {"repo": "Shopify/polaris"}] 指定 CHANGELOG 生成器为 GitHub 风格(会生成"感谢贡献者 + 关联 PR 链接"的条目),并指向目标仓库 Shopify/polaris,对应根目录 devDependencies 中的 @changesets/changelog-github@^0.5.1
commit false 执行 changeset version 时不自动创建 git commit,将版本落地与提交动作解耦,便于人工审查 diff 后再提交
fixed [] 不启用"固定版本组";若启用,组内包必须一起发布到同一版本
linked [] 不启用"联动版本";若启用,组内包版本号保持同步递增
access public 发布到 npm 时使用 public 访问级别,适用于公开发布的开源包
baseBranch main 以 main 作为版本计算的基准分支
updateInternalDependencies patch 当仓库内包 A 依赖包 B,且 B 本次有 minor/major 升级时,A 对 B 的版本依赖至少以 patch 级别跟随更新,保证安装后内部依赖版本一致
ignore [] 不忽略任何包
___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH.updateInternalDependents always 实验性选项:内部依赖方(如 @shopify/polaris 之于 @shopify/polaris-tokens)只要依赖被升级,就无条件更新其依赖声明

这些配置与子包 package.json 中的 publishConfig 是配套的。例如 polaris-react/package.json、polaris-tokens/package.json、polaris-icons/package.json、polaris-migrator/package.json 都声明了 "publishConfig": {"access": "public"},与 access: "public" 一致,确保发布时无需交互确认即可公开发布。

四、从 changeset 到发布:完整的发布工作流

4.1 消费 changeset 的两条命令

changeset 文件并不会自己生效,版本落地与发布由以下命令完成(对应根目录 package.json 的脚本):

pnpm version-packages   # 等价于: changeset version && pnpm install --lockfile-only
pnpm release-packages   # 等价于: pnpm build:release && changeset publish
  • changeset version 会读取 .changeset 下所有待处理 changeset,按 front matter 的声明更新各包 package.json 的 version 字段、追加/合并 CHANGELOG.md,并消费掉这些 changeset 文件(通常移动到 .changeset 的历史归档中);
  • 之后用 pnpm install --lockfile-only 同步 pnpm-lock.yaml,保证锁文件与更新后的版本一致;
  • changeset publish 则根据新版本号逐个 npm publish 各包。

4.2 版本计算与内部依赖联动

从配置可以看出本仓库的发布策略:

  • 每个 PR 独立产生 changeset,互不阻塞,版本号在发布前统一计算;
  • fixed 与 linked 均为空,说明各包版本独立演进,互不强制同版本;
  • 但 updateInternalDependencies: "patch" 与 updateInternalDependents: "always" 保证内部依赖链(@shopify/polaris → @shopify/polaris-tokens、@shopify/polaris-icons;@shopify/polaris-migrator → @shopify/polaris-tokens、@shopify/stylelint-polaris)的版本声明始终跟随上游升级,避免出现"发布后内部依赖还指向旧版本"的经典 monorepo 问题。

4.3 CI 中的自动发布:Version Packages PR

仓库的发布文档 documentation/Releasing.md 描述了 Changesets 官方 GitHub Action 在 CI 中的行为:

  1. 每当 PR 合入 main,CI 自动创建/更新 changeset-release/main 分支,并打开标题为 "[Version Packages]" 的 PR,该 PR 始终包含一次最新执行的 changeset version 结果;
  2. 合入 changeset-release/main 到 main 后触发 release.yml 工作流执行发布;
  3. 发布完成后重建 changeset-release/main 分支,准备下一轮版本。

此外,documentation/Releasing.md 还介绍了两种补充发布手段:

  • Snapshot releases(快照发布):在 CI 通过的 feature 分支 PR 上评论 /snapit,即可把当前改动打成带前缀的预发布版本供消费项目临时测试,无需真正发布正式版本;
  • Prerelease(beta):在 next 分支上执行 pnpm changeset pre enter beta 进入预发布模式,所有 changeset 会被汇总到 "[Version Packages (beta)]" PR,合并后以 beta dist-tag 发布,供大版本(含破坏性变更)的候选验证。

五、写给团队成员的实操清单

把上面的内容收敛成一份可执行的清单(适用于任何在本仓库提交 PR 的开发者):

  1. 确保当前在 feature 分支,且改动已完成;
  2. 运行 pnpm changeset;
  3. 问题一:Space 勾选本次改动的包,Enter 进入下一问;
  4. 问题二:若无破坏性变更直接 Enter;若有,用方向键 + Space 勾选对应包;
  5. 问题三:若无新功能直接 Enter(自动归为 patch);若有,勾选对应包;
  6. 在 Summary › 处按 CHANGELOG 规范撰写面向使用者的摘要(空行可唤起外部编辑器);
  7. 检查生成的 changeset 文件(front matter 中的包名与级别、正文摘要),commit 并 push;
  8. 后续由 CI 自动生成 Version Packages PR,合并即发布;若想提前验证,可用 /snapit 触发 snapshot release。

六、常见疑问与注意事项

  • 为什么默认是 patch? 因为 Changesets 的哲学是"保守升级":只有显式声明 major/minor 才会提升版本级别,避免无意的破坏性版本。
  • 可以直接手写 changeset 文件吗? 可以。参考 .changeset/loud-rivers-wear.md 的格式即可,但 CLI 能保证包名拼写正确、级别选择正确,仍建议用 pnpm changeset 生成。
  • 版本落地与提交是分离的。 commit: false 意味着 changeset version 只改文件不自动 commit,团队可以在提交前人工审查版本 diff。
  • Node 版本要求。 根据根目录 package.json 的 engines,本仓库要求 Node >=20.10.0,且使用 pnpm@8.15.5 作为包管理器,运行 changeset 相关命令前请先满足环境要求。
  • 发布前必须构建。 仓库的 release-packages 先执行 pnpm build:release(Turborepo 构建除 polaris.shopify.com 外的所有包)再 changeset publish,因为发布的是构建产物(如 @shopify/polaris 的 build/ 目录,见 polaris-react/package.json 的 files 字段)。

结语

Changesets 为 Polaris 这样的多包 monorepo 提供了一套"声明式 + 自动化"的版本治理方案:开发者只需在 PR 中回答三个问题、写一段面向使用者的摘要,剩下的版本号计算、CHANGELOG 生成、内部依赖联动、npm 发布与 CI 自动化,都由 .changeset/config.json 与根目录 package.json 中的脚本协同完成。理解这一套机制,不仅能让你在 Polaris 仓库中顺畅提交变更,也能迁移到任何使用 Changesets 的 pnpm monorepo 项目中。

登录后查看全文
polaris-react-archive