Emotion 基于 Changesets 的版本发布指南:从提交变更到 npm 发布的全流程
Emotion 基于 Changesets 的版本发布指南:从提交变更到 npm 发布的全流程
Emotion(CSS-in-JS 高性能样式组合库)以 monorepo 形式在 package.json 中管理 packages/*、site、scripts/*、playgrounds/* 等众多工作区,其版本管理与 npm 发布完全交给 Changesets 工具链驱动:贡献者提交 changeset,维护者统一执行版本号更新与发布命令,Changelog 自动生成。本文将结合仓库源码级配置,完整讲解从 yarn changeset 到 NPM_CONFIG_OTP=xxx yarn release 的发布流程、底层命令原理与 2FA 超时处理方案,读完即可独立完成一次 Emotion 的正式发布。
一、为什么是 Changesets:版本管理的核心思路
Emotion 在 RELEASING.md 中明确说明:使用 Changesets 做版本管理,让发布"非常容易",且 Changelog 是自动生成的。
其核心思路是变更驱动(change-driven)的版本管理:
- 每次代码改动不直接提交版本号变更,而是先产生一个描述性的
changeset(一段 Markdown + 受影响包列表 + 各自的 semver bump 类型); - 发布前统一由 CLI 聚合所有 changeset,自动计算每个包应升级到的版本号,并同步重写各包的
package.json与CHANGELOG.md; - 随后执行构建与
npm publish将包真正推送到 npm registry。
在 package.json 的 dependencies 中可以看到完整的工具链支撑:@changesets/cli@^2.27.7 与 @changesets/changelog-github@^0.5.0(第 184-185 行);yarn.lock 中 @changesets/cli@npm:2.27.7 的依赖树包含 @changesets/apply-release-plan、@changesets/assemble-release-plan、@changesets/read、@changesets/write、@changesets/git 等模块,分别负责"汇总变更计划、计算版本、读取/写入 changeset 文件、操作 git"。
这种设计对 Emotion 这种多包仓库的价值在于:一个 PR 往往同时影响 @emotion/css、@emotion/styled、@emotion/react 等多个包,人工维护版本号极易出错,而 changesets 能根据依赖关系自动推导每个包是否需要连带 bump。
二、贡献者视角:先写 changeset,再谈发布
发布流程的起点在贡献者提交 PR 时。按照 CONTRIBUTING.md 中 "Changesets" 一节的说明,贡献者需要在根目录运行:
yarn changeset
该命令对应 package.json 第 25 行的 script:"changeset": "changeset"。运行后会以交互方式引导你完成三件事:
- 选择受影响的包(Emotion 的
packages/*下每个目录都是一个独立包); - 为每个包选择 semver bump 类型:
major(破坏性变更)、minor(新增功能)、patch(缺陷修复); - 用 Markdown 编写变更说明,这段文字最终会被自动插入对应包的
CHANGELOG.md。
生成的 changeset 文件会存放在仓库的 .changeset 目录中。这是"贡献者写描述、维护者做发布"的协作分界线:任何人在合并 PR 前都应确保对应变更已经带有 changeset,否则发布时该变更不会触发版本升级。
从仓库现状可以印证 changelog 的组织方式:根目录 CHANGELOG.md 第一段明确写着"所有新的变更现在都记录在各包目录下的 CHANGELOG.md 文件中"。例如 packages/css/CHANGELOG.md、packages/babel-plugin/CHANGELOG.md、packages/react/CHANGELOG.md 等,每个包的发布历史独立成册;依赖 @changesets/changelog-github 则负责把 changeset 中的 Markdown 与 GitHub PR/作者信息关联,生成更丰富的 changelog 条目。
三、完整发布流程:三步走
正式发布由维护者执行,完整流程记录在 RELEASING.md,共三步:
第 1 步:确保依赖与工作区最新
yarn
发布前先运行一次 yarn,确保所有工作区依赖与 lockfile 一致。仓库使用 Yarn Modern(package.json 中 "packageManager": "yarn@3.2.3",且配置了 preinstall 脚本 node ./scripts/ensure-yarn.js 强制校验 Yarn 版本),这一步同时也会触发 postinstall(preconstruct dev && manypkg check),即用 Preconstruct 建立开发态链接,并用 manypkg 校验 monorepo 依赖结构是否符合规范,为后续构建与发布做好准备。
第 2 步:聚合 changeset 并更新版本
yarn version-packages
该命令对应 package.json 第 26 行的脚本:
"version-packages": "changeset version && yarn --mode=\"update-lockfile\""
它由两段组成,分两层作用:
changeset version:读取.changeset目录下所有未消费的 changeset,聚合出完整的发布计划(release plan),据此更新各包的package.json版本号、重写各包的CHANGELOG.md(插入本次变更说明与版本标题),并删除已消费的 changeset 文件;yarn --mode="update-lockfile":仅更新yarn.lock中记录的版本信息,不重新解析依赖树,保证 lockfile 与刚更新的包版本保持一致。
这一步通常在本地执行后作为一个"版本发布提交"(version commit)合入仓库。注意:此时只是本地版本号变更,还没有任何东西发布到 npm。
第 3 步:构建并发布到 npm
NPM_CONFIG_OTP=PUTANOTPCODEHERE yarn release
release 脚本定义在 package.json 第 27 行:
"release": "yarn build && changeset publish"
同样分两层:
yarn build:调用 Preconstruct 构建所有包(preconstruct build)。这是发布前的必要前置,CONTRIBUTING.md 的 "Building" 一节明确标注"发布前必须构建"(Required before publishing)。Emotion 各包通过 package.json 的preconstruct配置生成dist产物(如packages/styled/base/dist/styled.umd.min.js),changeset publish发布的正是这些构建产物;changeset publish:读取上一步更新后的版本号,对每个版本变化的包执行npm publish,自动按依赖拓扑顺序发布,避免出现"被依赖的包尚未发布"的情况。
NPM_CONFIG_OTP 环境变量为 npm 发布提供一次性密码(One-Time Password),即 npm 账户开启 2FA(双因素认证)时的动态验证码。将 PUTANOTPCODEHERE 替换为当前有效的验证码即可。
四、2FA 验证码超时怎么办
npm 的 2FA 验证码通常有约 30 秒的有效期,而 Emotion 有十几个包,整个构建加发布过程可能超过一个验证码的生命周期,因此 RELEASING.md 专门给出了处理方案:
如果 2FA 验证码在发布过程中超时,使用新的验证码重新运行命令即可,只有尚未发布的包会被发布。
NPM_CONFIG_OTP=新验证码 yarn release
这一行为的保障来自 changeset publish 的设计:它会先读取每个包的当前已发布版本并与本地版本比对,只发布本地版本号高于 registry 中已存在版本的包。因此重新运行时,之前已经成功发布的包会被自动跳过,幂等性由工具链保证,无需担心重复发布或版本冲突。
五、发布前后的质量保障
一次可靠的发布不仅依赖上述三步,仓库还提供了一整套配套脚本用于发布前的自检(均定义在 package.json 的 scripts 中):
| 命令 | 作用 | 适用时机 |
|---|---|---|
yarn test |
运行 jest 测试套件 | 发布前必跑 |
yarn test:typescript |
对所有工作区运行 TypeScript 类型测试 | 发布前建议跑 |
yarn lint:check |
eslint 全量检查(含 @emotion/pkg-renaming 等自定义规则) |
发布前建议跑 |
yarn test:dist |
构建后针对 dist 产物运行测试,验证发布物本身可用 | 模拟发布产物验证 |
yarn test:prod |
使用生产环境配置(jest.prod.js)运行测试 | 验证生产构建 |
yarn size |
通过 bundlesize 检查关键包体积阈值(见 package.json 的 bundlesize 字段) |
发布前可选 |
其中 test:dist 与发布关系最直接:它先执行 yarn build 再用 jest.dist.js 对构建产物跑一遍测试,能从发布物角度提前发现构建问题,避免"测试通过、发布出来的包却是坏的"。仓库根目录还配有 jest.config.js、jest-react18.config.js 等测试配置,覆盖 React 16 / React 18 双环境。
六、一次发布的关键时间线小结
把上述内容串起来,一次完整的 Emotion 发布过程如下:
- 贡献者:开发功能/修复 bug 后运行
yarn changeset,为影响的包声明 bump 类型并撰写变更说明; - 维护者合并含 changeset 的 PR 后:
yarn同步依赖;yarn test、yarn lint:check等确认质量;yarn version-packages聚合 changeset、更新版本号与各包CHANGELOG.md、同步 lockfile,并提交版本提交;NPM_CONFIG_OTP=<当前验证码> yarn release构建全部包并按依赖顺序发布;- 若发布途中 2FA 超时,换新验证码重跑
yarn release,已发布的包自动跳过。
这套以 Changesets 为核心的流程把"版本号维护"从人工计算中彻底解放出来,contributors 只管提交变更描述,维护者只管执行两条命令,Changelog 与版本联动全部自动化,这也是 RELEASING.md 所说"releasing really easy and changelogs are automatically generated"的完整实现。