Gutenberg 仓库 Workspace 开发指南:基于 npm workspaces 的内部工作区新建、注册与日常依赖管理
【免费下载链接】gutenbergThe Block Editor project for WordPress and beyond. Plugin is available from the official repository.项目地址: https://gitcode.com/GitHub_Trending/gu/gutenberg
导读
Gutenberg 仓库不仅发布packages/*下的@wordpress/*npm 包,还通过 npm workspaces 为主体,结合根 package.json、tsconfig.json 与各工作区真实的package.json源码,完整讲解:为什么内部依赖要落在工作区而非根依赖、工作区分布在哪里、如何从零新建一个工作区、以及日常如何增删依赖与运行脚本。读完你就能按仓库规范安全地扩展这套 monorepo,并理解 CI 与本地命令为何要保持一致。
为什么依赖应放在工作区,而不是根 package.json
monorepo 根目录的package.json是仓库的"总指挥",但任何添加进根devDependencies的依赖,都会隐式地对每一个工作区可见。这会让"某个工作区到底依赖什么"变得模糊不清。当前仓库刻意保持根依赖精简——根 package.json 只保留仓库级工具链(husky、lerna、lint-staged、syncpack、patch-package、concurrently、cross-env、wait-on以及@wordpress/scripts、@wordpress/env、@wordpress/eslint-tools、@wordpress/monorepo-tools、@wordpress/release-tools、@wordpress/stylelint-tools等内部工具),页面级代码与测试所需的依赖全部下沉到各自工作区。
把依赖放进工作区而非根依赖,主要有四个好处:
- 关注点分离(Separation of concerns):每个工作区在自己的
package.json中声明其真正需要的依赖,代码与依赖的对应关系一目了然。 - 根目录更干净(Cleaner root):根
package.json只承载 lint、format、类型检查、git hooks 与 monorepo 编排,依赖变更的 review 变得更轻松。 - 更少的合并冲突(Fewer merge conflicts):贡献者更新某个工作区的依赖时无需触碰根
package.json。 - 防止"幽灵依赖"(Phantom dependency prevention):npm 采用提升(hoisting)安装策略,依赖会被提升到根
node_modules,一个工作区可能"碰巧" import 到它从未声明的包。保持根目录精简,能让这种依赖关系保持诚实,也是未来迁移到隔离依赖方案(仓库讨论中的 pnpm 迁移)的前提——届时工作区只能看到自己声明的依赖。
因此,"这个依赖该放哪?"的默认答案永远是工作区,而不是根。如果下意识想往根package.json里加依赖,先思考它能否放进:
- 一个已存在的工作区(
tools/或test/下已覆盖该场景的),例如@wordpress/eslint-tools、@wordpress/release-tools、@wordpress/validation-tools、@wordpress/unit-tests; - 或一个新建的工作区(
tools/下放开发工具,test/下放测试基础设施),如果没有合适归宿。
工作区都分布在哪里
当前仓库的工作区分布可以归纳为下表:
| 位置 | 用途 |
|---|---|
packages/* | 对外发布的@wordpress/*npm 包,管理规范见 管理包(Managing Packages) |
tools/* | 内部开发工具(ESLint 配置、发布 CLI、API 文档生成器、校验脚本等),不发布到 npm |
test/* | 测试基础设施(unit、integration、e2e、performance、storybook-playwright 等) |
storybook | Gutenberg Storybook 宿主 |
routes/* | 编辑器路由入口点 |
widgets/* | 组件面板(widget)打包产物 |
以仓库实际内容核对:tools/下现有agents、docs、eslint、monorepo、pr-meta、react-18、react-19、release、stylelint、validation等工作区;test/下现有ai-development、e2e、integration、performance、php、storybook-playwright、unit等。
这些 glob 的注册集合就写在根 package.json 的"workspaces"数组中:
"workspaces": [ "packages/*", "routes/*", "storybook", "test/*", "tools/*", "widgets/*", "!test/ai-development" ]两点值得注意:
- 任何被现有 glob 匹配到的目录(例如
tools/*),只要其内部出现了package.json,就会被 npm自动识别为工作区,无需额外注册; - 数组中的
!test/ai-development是一条排除规则,用于把该目录从test/*的匹配中剔除,说明test/ai-development虽然放在test/下,却是独立管理依赖的(根脚本中test:agent-evals使用npm --prefix test/ai-development run eval --单独调用)。
创建一个新工作区的完整步骤
该模式在 #74640(Storybook 转换)中确立,并成为后续迁移的模板。一共六步。
第 1 步:在工作区目录中添加package.json
对于不发布到 npm 的内部工具,模板如下:
{ "name": "@wordpress/<workspace-name>", "version": "0.0.0", "description": "<short description>", "private": true, "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", "homepage": "https://github.com/WordPress/gutenberg/tree/HEAD/tools/<workspace-name>", "repository": { "type": "git", "url": "git+https://github.com/WordPress/gutenberg.git", "directory": "tools/<workspace-name>" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" }, "devDependencies": {}, "scripts": {} }要点:
- 永不该发布的包务必设置
"private": true。仓库中的 tools/monorepo/package.json、tools/validation/package.json、test/unit/package.json 均是此写法,尽管它们同时声明了publishConfig.access,private字段仍从根上禁止发布; - 只声明该工作区实际 import 或执行的依赖。例如 tools/validation/package.json 只列出校验脚本真正用到的
chalk、glob、jsonc-parser、simple-git等; - 要依赖 monorepo 内的另一个工作区,使用
file:引用。例如 tools/monorepo/package.json 中"@wordpress/data": "file:../../packages/data",根 package.json 中"@wordpress/scripts": "file:./packages/scripts"、"@wordpress/monorepo-tools": "file:./tools/monorepo"也是同一机制——file:让 npm 直接链接本地目录而不是去 registry 拉取。
第 2 步:(可选)添加tsconfig.json
如果工作区包含 TypeScript,应继承共享基础配置:
{ "extends": "@wordpress/monorepo-tools/tsconfig/base.json", "compilerOptions": { "rootDir": "./", "outDir": "./build" }, "include": [ "./**/*.ts" ] }这里的@wordpress/monorepo-tools/tsconfig/base.json是真实存在的共享基座,见 tools/monorepo/tsconfig/base.json:它开启strict、noImplicitReturns、composite、declaration、declarationMap、isolatedModules等严格选项,并约定rootDir与declarationDir,其可被引用的路径正是通过 tools/monorepo/package.json 的exports字段("./tsconfig/*": "./tsconfig/*")暴露出来的。
随后,需要在根 tsconfig.json 的references数组中为新工作区添加一条项目引用(project reference),例如{ "path": "tools/pr-meta" }。从仓库现状看,根 tsconfig 的 references 覆盖了routes/*、storybook、test/e2e、test/performance、test/storybook-playwright、widgets以及tools/pr-meta等目录,可以推断并非每个工作区都要求有 TypeScript 项目引用,只有含 TS 源码且需要被类型检查覆盖的工作区才需要。
第 3 步:注册工作区(如需要)
如果新工作区位于根package.jsonworkspaces已有 glob 覆盖的路径下(例如tools/*),它会被自动注册。否则,需要在根package.json的workspaces数组中新增一条记录。当前仓库对tools/*、test/*、routes/*、widgets/*均已配置 glob,因此绝大多数新建工作区无需改数组;只有在路径超出既有模式(如test/ai-development这类需要隔离的情况)时才会动它。
第 4 步:从仓库根暴露脚本
将贡献者应从根目录运行的脚本用npm run --workspace转发:
"scripts": { "my-task": "npm run --workspace @wordpress/<workspace-name> my-task --" }结尾的--用于把额外的 CLI 参数透传给工作区脚本,例如npm run my-task -- --watch。这在根 package.json 中是贯穿全篇的惯例:
"test:unit": "npm run --workspace @wordpress/unit-tests test:unit --""lint:lockfile": "npm run --workspace @wordpress/validation-tools validate-package-lock --""docs:api-ref": "npm run --workspace @wordpress/docs-tools docs:api-ref --""agents:setup": "npm run --workspace @wordpress/agent-tools setup --""storybook:build": "npm run --workspace @wordpress/storybook storybook:build"
第 5 步:添加 README
在工作区目录中放置一个README.md,说明该工作区的职责、暴露的脚本以及任何不显而易见的设置。例如test/unit工作区的 homepage 字段就指向其 README(见 test/unit/package.json)。
第 6 步:更新 CI 工作流
.github/workflows/下的 CI 工作流应通过第 4 步建立的根npm run包装调用工作区,而不是cd进工作区目录再执行。这样贡献者在本地运行的命令与 CI 完全一致:
- run: npm ci - run: npm run my-task仓库实况可以印证这一点:在 .github/workflows/unit-test.yml 中,Jest 与 Vitest 分区分别通过npm run test:unit -- ...和npm run test:unit:vitest:shuffled -- ...调用,最终落到@wordpress/unit-tests工作区的脚本(见 test/unit/package.json 的test:unit、test:unit:vitest等定义),而 CI 从不直接进入工作区目录执行。
日常开发中的工作区操作
增删某个工作区的依赖
始终把依赖变更限定在使用它的工作区:
npm install <package> --workspace @wordpress/<workspace-name> npm uninstall <package> --workspace @wordpress/<workspace-name>这两条命令只会更新该工作区的package.json和根目录的package-lock.json,不会触碰根package.json——这正是前文"少合并冲突、根目录保持精简"的落地体现。
运行某个工作区的脚本
在仓库根目录执行:
npm run <script> --workspace @wordpress/<workspace-name>或者,如果第 4 步已经建立了根级转发脚本:
npm run <root-script>在所有工作区中运行同一脚本
如果要在每个定义了某脚本的工作区中批量执行它:
npm run --if-present --workspaces <script>--if-present保证没有定义该脚本的工作区被安静跳过。根 package.json 的prelint:js就是这一模式的范例:
"prelint:js": "npm run --if-present --workspaces prelint:js"深入验证:一个真实工作区的解剖
以tools/monorepo为例,把整套规范串起来看:
- 它叫
@wordpress/monorepo-tools,"private": true(见 tools/monorepo/package.json),符合"内部工具不发布"的定位; - 它通过
file:引用同仓库的@wordpress/data包(tools/monorepo/package.json),是"工作区间依赖"的标准写法; - 它通过
exports暴露./tsconfig/*子路径(tools/monorepo/package.json),使得其他工作区能用@wordpress/monorepo-tools/tsconfig/base.json继承共享 TS 配置; - 根
package.json用"@wordpress/monorepo-tools": "file:./tools/monorepo"(根 package.json)把它引入根工具链,同时多个根脚本(如lint:tsconfig、lint:lockfile)通过npm run --workspace @wordpress/validation-tools之类的方式调用工作区脚本。
这恰好覆盖了"新建工作区六步"中的每一步:package.json、tsconfig基座、file:依赖、根脚本转发,以及 CI 中经根脚本统一调用的约定。当你在 Gutenberg monorepo 中新增或修改内部工作区时,这套模式就是唯一需要遵循的规范。
结语
Gutenberg 的 workspace 体系本质上是"用 npm workspaces 把一个巨大的 JS/TS monorepo 拆成职责清晰的小单元":根package.json只做编排,所有功能代码与测试基础设施各自声明依赖、各自暴露脚本。掌握 docs/contributors/code/workspace-development.md 中这套"默认放工作区、六步新建、根脚本转发、CI 对齐"的约定后,你既能安全地为仓库添加新工具或测试工作区,也能在提交依赖变更时避免无谓的合并冲突与幽灵依赖问题。
【免费下载链接】gutenbergThe Block Editor project for WordPress and beyond. Plugin is available from the official repository.项目地址: https://gitcode.com/GitHub_Trending/gu/gutenberg
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考