news 2026/9/16 14:49:43

Gutenberg 包管理实践:npm Workspaces 单体仓库、Lerna 独立版本与自动化 npm 发布

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Gutenberg 包管理实践:npm Workspaces 单体仓库、Lerna 独立版本与自动化 npm 发布

Gutenberg 包管理实践:npm Workspaces 单体仓库、Lerna 独立版本与自动化 npm 发布

【免费下载链接】gutenbergThe Block Editor project for WordPress and beyond. Plugin is available from the official repository.项目地址: https://gitcode.com/GitHub_Trending/gu/gutenberg

Gutenberg 仓库用 npm workspaces 统一管理 100+ 个@wordpress/*包,用 Lerna 以独立版本(independent versioning)模式将它们发布到 npm,并通过每个包强制维护的CHANGELOG.md驱动半自动化发布。本文基于仓库中的 包管理文档 展开,结合根目录 package.json、lerna.json 与 tools/release 下的发布 CLI 源码,讲清包目录约定、Changelog 规则、依赖管理方式、内部 workspace 模式,以及 npm 发布的实际执行链路。

总体架构:npm workspaces 管包,Lerna 管发布

Gutenberg 的包管理策略可以概括为一句话:npm workspaces 负责仓库内的依赖组织,Lerna 负责把包发布到 npm。两者的分工在仓库根部的两个配置文件中体现得非常清楚。

根 package.json 中的 workspace 声明

根 package.json 的workspaces字段(L216-L224)声明了所有被纳入 workspace 体系的目录:

"workspaces": [ "packages/*", "routes/*", "storybook", "test/*", "tools/*", "widgets/*", "!test/ai-development" ]

几点值得注意:

  • packages/*是对外发布到 npm 的@wordpress/*包所在的目录;
  • tools/*test/*内部 workspace,用于开发工具和测试基础设施,不发布到 npm;
  • routes/*widgets/*是构建产物侧的 bundle 入口,storybook是 Storybook 宿主;
  • 末尾的!test/ai-development表示从 workspace 集合中显式排除该目录。

此外,根package.jsondevDependencies(L84-L103)大量使用file:协议引用本仓库内部的 workspace,例如:

"@wordpress/eslint-tools": "file:./tools/eslint", "@wordpress/monorepo-tools": "file:./tools/monorepo", "@wordpress/release-tools": "file:./tools/release", "@wordpress/scripts": "file:./packages/scripts"

这正是内部 workspace 模式的直接体现:工具以file:引用挂载到根,而不是把依赖散落进根目录。同时,环境要求被声明在engines字段中(Node.js>=24.18.0、npm>=11.16.0,L17-L20),并通过devEngines强制校验。

lerna.json 的配置含义

发布侧的配置集中在根目录 lerna.json,全部内容只有 16 行:

{ "command": { "publish": { "message": "chore(release): publish" } }, "ignoreChanges": [ "**/benchmark/*.js", "**/CHANGELOG.md", "**/{__mocks__,__tests__,test}/**", "**/{storybook,stories}/**" ], "packages": [ "packages/*" ], "version": "independent", "$schema": "node_modules/lerna/schemas/lerna-schema.json" }

逐字段解读:

配置项取值含义
packages["packages/*"]Lerna 只管理packages/下的目录,tools/test/等内部 workspace 不参与版本计算
versionindependent各包独立版本,一个包发版不会带动其他包升版
ignoreChanges测试、mock、storybook、benchmark、CHANGELOG.md文件修改这些文件不会触发 Lerna 判定"该包需要发版"
command.publish.messagechore(release): publish自动生成的发布 commit 信息

ignoreChanges中把**/CHANGELOG.md排除掉是有意为之:Changelog 由发布流程统一改写(见后文),其变更本身不应被视为"代码变更"。

每个包的 CHANGELOG.md:发布流程的驱动数据

Gutenberg 文档明确指出:维护几十个 npm 包时很难跟踪变化,因此每个包都必须维护CHANGELOG.md,贡献者在提交生产代码时必须向该文件追加条目。这不是单纯的文档习惯——发布 CLI 会直接解析 Changelog 文本来决定版本号如何提升,是发布流水线的输入数据。

写入规范:Unreleased 区块

每个 PR 若包含用户可见变更,都应在对应包的CHANGELOG.md顶部## Unreleased标题下追加条目(若标题不存在则需创建)。规范格式见 packages/README.md 的 Maintaining Changelogs 一节,例如:

<!-- Learn how to maintain this file at https://github.com/WordPress/gutenberg/tree/HEAD/packages#maintaining-changelogs. --> ## Unreleased ### Bug Fixes - Fixed an off-by-one error with the `sum` function.

子标题与语义化版本映射

Changelog 子标题与 semver 版本提升的对应关系是发布流程的核心约定(完整列表来自 packages/README.md):

子标题含义版本提升要求
Breaking Changes向后不兼容变更,需要使用者特别注意稳定包(1.0.0+)提升 major
New Features新增向后兼容的公共 API 或功能提升 minor
Enhancements对现有功能的向后兼容改进提升 minor
Deprecations弃用通知,不影响公共接口或行为提升 minor
Bug Fixes修复既有错误行为提升 patch
Internal不影响公共接口或行为的内部变更提升 patch
Stable Release标记 0.x 预发布包为稳定可用提升到 1.0.0

文档特别强调:描述必须准确,因为发布流程会用它来判定下一个版本号;当拿不准时以 semver 规范为准。

Stable Release:从 0.x 晋升 1.0.0

自动化发布流程对预发布包(0.x.x最多只提升 minor 版本,即使包含破坏性变更——这符合 semver 中0.x阶段 API 可能频繁变动的约定。当某个包的 API 被认为足够稳定、可以投入生产时,通过在CHANGELOG.md中写入### Stable Release小节触发晋升:

## Unreleased ### Stable Release This package is now considered stable and production-ready. The API will follow semantic versioning from this point forward. ### Breaking Changes - Final API adjustments before 1.0.0 release.

Stable Release小节只用于 1.0 之前的包;晋升之后,破坏性变更将按 semver 惯例触发 major 提升。

源码印证:版本提升是怎么算出来的

上述规则不是停留在文档层面的约定。发布 CLI 的核心函数 calculateVersionBumpFromChangelog 逐行扫描 Changelog,逻辑与文档描述一一对应:

  • ## Unreleased开始扫描,遇到下一个##已发布版本标题即停止(common.js L46-L59);
  • 当前版本小于1.0.0时遇到### Stable Release→ 提升 major(即跳升到 1.0.0);
  • 遇到### Breaking Changes→ 稳定包提升 major,0.x包只提升 minor(common.js L70-L79);
  • 遇到### Deprecations/### Enhancements/### New API/### New Features→ 提升 minor;
  • 其他任何新小节或变更条目 → 提升minimumVersionBump(CLI 中默认为patch)。

也就是说,Changelog 中写的每一个子标题都会被机器读取,标题拼写直接决定版本号

依赖管理:生产依赖进包,开发依赖进根或工具包

文档与 packages/README.md 将依赖分为两类,处理方式截然不同:

生产依赖:用-w安装到具体包

生产依赖写在包的package.jsondependencies中,从仓库根目录执行:

# 添加:把 change-case 安装为 packages/a11y 的依赖 npm install change-case -w packages/a11y # 锁定 lockfile 中已有版本时复用;强制换版本用 @ 后缀 npm install change-case@latest -w packages/a11y # 移除 npm uninstall change-case -w packages/a11y

更新依赖的推荐策略(文档称之为 monorepo 中最容易困惑的部分)是:先npm uninstall,再npm install回同名的包——除非显式指定版本,否则装回来的是最新版本。

开发依赖:不进包,进根或内部工具包

开发依赖不应写进各个@wordpress/*包,而应安装在根package.json(或内部工具包)中,使所有包共享同一套工具配置。例如:

# 在仓库根目录执行 npm install glob --save-dev

新包创建后的必要步骤

新建包目录并写好package.json后,需要运行npm install更新package-lock.json,包才会被 npm workspaces 识别。packages/README.md给出了完整的新包模板(含wpScript/wpScriptModuleExports/types/sideEffects/publishConfig字段),并规定每个包必须附带 README 与CHANGELOG.md,新包的 CHANGELOG 初始内容如下:

<!-- Learn how to maintain this file at https://github.com/WordPress/gutenberg/tree/HEAD/packages#maintaining-changelogs. --> ## Unreleased Initial release.

内部 workspaces:tools/ 与 test/

关联文档的最后一节专门讲内部 workspace:仓库在tools/test/下维护开发工具与测试基础设施的 workspace。**当需要为仓库级工作新增工具、脚本或依赖时,应在tools/下创建(或复用已有的)workspace,而不是往根package.json里加依赖。**详细规范见 Workspace Development 指南,要点如下:

为什么依赖不该进根 package.json

  • 职责分离:每个 workspace 显式声明自己需要的依赖,代码与依赖的关系可读;
  • 根目录干净:根package.json只保留仓库级工具(lint、format、typecheck、git hooks、monorepo 编排),评审依赖变更更容易;
  • 减少合并冲突:更新某个 workspace 的依赖不需要触碰根文件;
  • 防止幻影依赖:npm 的 hoist 策略会把依赖提升到根node_modules,workspace 可能"偷看"到未声明的依赖。保持根目录精简也是未来迁移到 pnpm(隔离依赖模型)的前提。

各目录的分工

位置用途是否发布 npm
packages/*发布的@wordpress/*
tools/*内部开发工具(ESLint 配置、发布 CLI、API 文档生成器、校验工具等)
test/*测试基础设施(unit、integration、e2e、performance 等)
storybookStorybook 宿主
routes/*编辑器路由入口
widgets/*Widget bundle

凡是被根package.jsonworkspacesglob 匹配到的目录(如tools/*),只要其中放了一个package.json就会被自动纳入 workspace,无需逐一手动注册。

新建内部 workspace 的标准步骤

转换模式参考 Storybook 迁移实践,共六步:

  1. 在 workspace 目录添加package.json,内部工具设"private": true,只列该 workspace 真正 import/执行的依赖;依赖同仓库其他 workspace 时用file:引用(如"@wordpress/scripts": "file:../../packages/scripts");
  2. 含 TypeScript 时添加tsconfig.jsonextends共享基配置@wordpress/monorepo-tools/tsconfig/base.json,并在根tsconfig.json的 project references 中登记;
  3. 若路径已被现有 glob 覆盖则自动注册,否则在根workspaces数组中补一条;
  4. 在根package.jsonnpm run --workspace <name> <script> --暴露转发脚本,末尾的--会把额外 CLI 参数透传给 workspace;
  5. 补一份 README 说明用途、脚本与非常规配置;
  6. 更新 CI:统一通过根目录的npm run包装脚本调用 workspace,而不是cd进目录,保证本地与 CI 跑的是同一条命令。

日常命令模式

# 在特定 workspace 中增删依赖(只改该包 package.json 与根 lockfile) npm install <pkg> --workspace @wordpress/<name> npm uninstall <pkg> --workspace @wordpress/<name> # 运行某个 workspace 的脚本 npm run <script> --workspace @wordpress/<name> # 在定义了该脚本的所有 workspace 中运行(如根 prelint:js 的用法) npm run --if-present --workspaces <script>

根 package.json 中可见这些模式的真实用例,例如"prelint:js": "npm run --if-present --workspaces prelint:js""test:unit": "npm run --workspace @wordpress/unit-tests test:unit --"

npm 发布流程:与插件 RC1 同步的自动化链路

文档说明:WordPress 包发布到 npm 是自动化的,与每两周一次的 Gutenberg 插件 RC1 发布同步;其他发布方式记录在 Gutenberg Release Process 文档。仓库内的实现位于 tools/release 这个内部 workspace(在根package.json中以@wordpress/release-tools: file:./tools/release挂载),入口是 tools/release/cli.js,基于 commander 注册了四条包发布命令:

命令(别名)用途关键选项
publish-npm-packages-latestnpm-latest从 Gutenberg 插件同步发布生产版本,latestdist-tag--semver <patch\|minor\|major>(默认 patch)、-c, --ci--repository-path
publish-npm-packages-bugfix-latestnpm-bugfix面向latestdist-tag 的补丁发布-c, --ci--repository-path
publish-npm-packages-wordpress-corenpm-wp面向 WordPress core 的补丁发布,wp-X.Ydist-tag必填--wp-version <wpVersion>
publish-npm-packages-nextnpm-next发布开发版本,nextdist-tag,预发布版本号--semver-c, --ci

其中--semver指定本次发布的最小版本提升等级(见 cli.js L19-L60);与 Changelog 推导出的提升取两者中较大的一个,这与 packages/README.md 中"选 semver 与 Gutenberg 发布最低提升中更大者"的规则一致。

发布时 Changelog 如何被改写

updatePackages 函数展示了 Changelog 从"数据"到"版本记录"的落地过程:

  1. glob 扫描packages/*/CHANGELOG.md,并跳过private: true的包(packages.js L195-L202),私有包不发布、不改写;
  2. 对每个包调用calculateVersionBumpFromChangelog计算提升等级,再用semver.inc算出下一个版本;
  3. ## Unreleased改写为## Unreleased+## <新版本> (<发布日期>)next类型则写成<版本>-next.0),同时把package.json的 version 更新为<新版本>-prerelease
  4. 打印包名: 旧版本 -> 新版本清单,提交Update changelog files并推送到 npm 发布分支。

这也解释了为什么贡献者只写## Unreleased:版本标题和日期是发布时由工具自动生成的。

工程化细节:重试、校验与恢复

tools/release/commands/packages.js 顶部的一组常量揭示了大规模发布的健壮性设计:

  • NPM_RELEASE_PHASE_ATTEMPTS = 3:每个发布阶段最多重试 3 次,退避采用指数策略(2^(n-1) * 5s,上限 120s);
  • NPM_RELEASE_VERIFICATION_ATTEMPTS = 18:注册表传播校验重试 18 次——注释说明一次 121 个包的发布中,最后一个包可见曾耗时约 25 分钟,预算按"尾部"而非"常态"设置;
  • NPM_RELEASE_TAG_PUSH_BATCH_SIZE = 25:Git tag 按每批 25 个推送,避免单批过大;
  • refs/npm-release/*预备态 ref:lerna version --no-push生成的发布 commit 会先持久化到 scratch ref(L35-L43 的注释),使 CI runner 中途失败后可以重跑恢复,且不会提前对外暴露尚未上 npm 的版本。

发布前还有一个 npm 预检步骤 runNpmPublishPreflight:先用npm whoami验证凭据,再逐个npm view <name>@<version>核对注册表中已有版本的gitHead与 dist-tag 是否与本次发布一致,防止重复发布或 dist-tag 被其他发布移动后的不安全恢复。

小结与延伸阅读

Gutenberg 的包管理把"人写 Changelog、机器算版本、Lerna 打 tag 发包、发布 CLI 做同步与校验"串成了一条可审计的流水线,核心文件对照:

  • 包管理总览:docs/contributors/code/managing-packages.md
  • 包创建/Changelog/依赖/TypeScript 细则:packages/README.md
  • 内部 workspace 规范:docs/contributors/code/workspace-development.md
  • 发布流程入口:docs/contributors/code/release/README.md
  • Lerna 配置:lerna.json;workspace 声明:package.json
  • 发布 CLI 与版本计算:tools/release/cli.js、tools/release/commands/common.js、tools/release/commands/packages.js

需要说明的适用前提:本文所有命令与配置均以当前仓库状态为准(Node.js ≥24.18.0、npm ≥11.16.0、Lerna 10);发布命令的执行还需要 Gutenberg 开发团队的仓库权限,普通贡献者的日常工作面是"改代码 + 写 Changelog + 用-w管理依赖"这三件事。

【免费下载链接】gutenbergThe Block Editor project for WordPress and beyond. Plugin is available from the official repository.项目地址: https://gitcode.com/GitHub_Trending/gu/gutenberg

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 14:48:00

OpenWhispr原生Helper编译指南:13个平台专用二进制的构建全流程

OpenWhispr原生Helper编译指南&#xff1a;13个平台专用二进制的构建全流程 【免费下载链接】openwhispr Voice-to-text dictation app with local (Nvidia Parakeet/Whisper) and cloud models (BYOK). Privacy-first and available cross-platform. 项目地址: https://gitc…

作者头像 李华
网站建设 2026/9/16 14:46:59

2026职场趋势:AI与新能源成黄金赛道

1. 职场趋势的周期性波动解析每年三四月份的职场招聘高峰被称为"金三银四"&#xff0c;这已经成为职场人士的普遍认知。但2026年的就业市场却呈现出与往年截然不同的景象——传统热门行业的招聘需求明显降温&#xff0c;而一些新兴领域却逆势爆发。这种结构性变化背后…

作者头像 李华
网站建设 2026/9/16 14:41:00

STM32电子血压计实现:示波法、脉搏波提取与ADC采样全解析

简介&#xff1a;基于STM32设计的电子血压计完整项目包&#xff0c;面向嵌入式方向的学生与开发者&#xff0c;适用于课程设计、毕业设计、工程实训及学科竞赛等场景。资源已经过严格测试可直接运行&#xff0c;包含完整源码、工程文件及使用说明&#xff0c;可帮助快速复刻血压…

作者头像 李华
网站建设 2026/9/16 14:39:58

SpringBoot+MyBatis打造新生报到系统:从数据库设计到并发兜底

简介&#xff1a;SpringBoot大学新生报到系统是一份面向计算机专业毕业设计及Spring Boot实战学习的完整工程源码包&#xff0c;覆盖学生信息管理、报到流程管理和宿舍分配等核心模块。压缩包共561个文件&#xff0c;约16.02MB&#xff0c;其中包含123个Java源码、93个Vue页面、…

作者头像 李华