用于 WordPress 插件的构建工具。
描述
@wordpress/build 是一个为 WordPress 插件设计的约定式构建系统。它提供:
- 转译:使用 esbuild 将 TypeScript/JSX 源代码转换为 CommonJS(
build/)和 ESM(build-module/)两种格式 - 样式编译:处理 SCSS 文件和 CSS 模块,生成 LTR 和 RTL 版本
- 打包:为 WordPress 脚本和模块创建浏览器可用的包
- PHP 生成:自动为脚本、模块和样式生成 PHP 注册文件
- 监听模式:在开发期间进行增量重建
安装
npm install @wordpress/build --save-dev
用法
生产构建
wp-build
或通过 npm 脚本:
{
"scripts": {
"build": "wp-build"
}
}
开发模式(监听)
wp-build --watch
或通过 npm 脚本:
{
"scripts": {
"dev": "wp-build --watch"
}
}
测试生成的样式
当 NODE_ENV 为 test 且 process 已定义时,生成的 CSS 输出会在基于 Node 的环境中跳过自动样式注入。
浏览器没有 process,
因此始终会接收样式。基于 Node 的 DOM 实现(例如 jsdom)
无法可靠支持现代 CSS 特性,因此需要 DOM 中存在实际样式的测试应在浏览器环境中运行。
浏览器支持
JavaScript 和 CSS 构建在指定构建目标时使用 Browserslist。
你可以 自行提供配置,
或者使用默认的 @wordpress/browserslist-config
来遵循 WordPress 浏览器支持。
包配置
使用以下可选字段配置你的 package.json:
wpScript
控制该包是否作为打包后的 WordPress 脚本/模块公开,并可通过配置的全局变量访问。
true:该包将被打包并作为 WordPress 脚本公开。它将作为配置的全局变量的一部分在 WordPress 中可用(例如wp.blockEditor、wp.data,或在配置不同时使用自定义全局名称)。省略或
false(默认):该包不会作为 WordPress 脚本公开。仅作为其他包依赖项而设计的包应使用此项。该包仍可由其他包通过 npm 导入作为依赖项使用。
{
"wpScript": true
}
有关何时省略或将此项设置为 false 的更多详情,请参阅 包指南。
wpScriptModuleExports
定义脚本模块入口点:
{
"wpScriptModuleExports": {
"./interactivity": "./build-module/interactivity/index.js"
}
}
wpScriptDefaultExport
处理默认导出包装:
{
"wpScriptDefaultExport": true
}
wpScriptExtraDependencies
额外的脚本依赖项:
{
"wpScriptExtraDependencies": [ "wp-polyfill" ]
}
wpStyleEntryPoints
自定义 SCSS 入口点模式:
{
"wpStyleEntryPoints": {
"style": "src/style.scss"
}
}
wpCopyFiles
要复制的文件,可选择进行 PHP 转换:
{
"wpCopyFiles": [
{
"from": "src/index.php",
"to": "build/index.php",
"transform": "php"
}
]
}
wpWorkers
为需要自包含 Web Worker 文件的包提供 Worker 打包定义。
Worker 会包含所有依赖项进行打包,并可通过 Blob URL 加载。
字符串简写 —— 仅入口路径:
{
"wpWorkers": {
"./worker": "./src/worker.ts"
}
}
对象格式 —— 带模块解析重定向的入口路径:
{
"wpWorkers": {
"./worker": {
"entry": "./src/worker.ts",
"resolve": {
"vips-es6.js": "vips.js"
}
}
}
}
resolve 映射会在打包期间重定向模块加载。键是要匹配的文件名模式;
值是同目录中的替换文件名。
当依赖项的 ES 模块入口使用 import.meta.url 时,这很有用,
该用法在 Blob URL Worker 环境中会失败。通过重定向到替代入口(例如 CommonJS 版本),可以避免此问题。
根配置
使用 wpPlugin 对象配置你的根 package.json,以控制全局命名空间和外部化行为:
wpPlugin.name
用于为生成的 PHP 函数添加前缀的名称。必须遵循 PHP 中的函数名规则,即有效名称以字母或下划线开头,后跟任意数量的字母、数字或下划线。
{
"wpPlugin": {
"name": "myPlugin"
}
}
wpPlugin.scriptGlobal
你的包使用的全局变量名(例如 "wp"、"myPlugin")。设置为 false 可禁用全局暴露:
{
"wpPlugin": {
"scriptGlobal": "myPlugin"
}
}
wpPlugin.packageNamespace
用于匹配全局暴露的包作用域(不带 @ 前缀)。只有匹配 @{packageNamespace}/* 的包会暴露全局变量:
{
"wpPlugin": {
"scriptGlobal": "myPlugin",
"packageNamespace": "my-plugin"
}
}
wpPlugin.handlePrefix
用于 .asset.php 文件中 WordPress 脚本句柄的前缀(例如 wp-data、my-plugin-editor)。默认值为 packageNamespace:
{
"wpPlugin": {
"scriptGlobal": "myPlugin",
"packageNamespace": "my-plugin",
"handlePrefix": "mp"
}
}
使用此配置:
@my-plugin/editor→window.myPlugin.editor,句柄为mp-editor@my-plugin/data→window.myPlugin.data,句柄为mp-data
wpPlugin.externalNamespaces
要外部化的其他包命名空间(作为外部依赖消费,而不暴露)。每个命名空间必须是一个对象,包含 global 和可选的 handlePrefix:
{
"wpPlugin": {
"externalNamespaces": {
"woo": {
"global": "woo",
"handlePrefix": "woocommerce"
},
"acme": {
"global": "acme",
"handlePrefix": "acme-plugin"
}
}
}
}
这允许你的包将第三方依赖项作为外部依赖消费:
import { Cart } from '@woo/cart'→window.woo.cart,句柄为woocommerce-cartimport { Button } from '@acme/ui'→window.acme.ui,句柄为acme-plugin-ui- 依赖项会在
.asset.php文件中被跟踪
如果省略 handlePrefix,则默认使用命名空间键(例如 "woo" → woo-cart)。
wpPlugin.pages(实验性)
定义支持路由的管理页面。会为每个页面生成用于路由注册的 PHP 函数,并且可由其他插件扩展。
页面可以定义为简单字符串,也可以定义为带初始化模块的对象:
{
"wpPlugin": {
"pages": [
"my-admin-page",
{
"id": "my-other-page",
"init": [ "@my-plugin/my-page-init" ]
}
]
}
}
页面配置:
- 字符串格式:
"my-admin-page"—— 不带初始化模块的简单页面 - 对象格式:
{ "id": "page-slug", "init": ["@scope/package"], "capability": "edit_theme_options", "experimental": true }—— 带可选初始化模块的页面id(必填):WordPress 后台 URL 中使用的页面别名init(可选):页面初始化期间要执行的脚本模块 ID 数组capability(可选,默认manage_options):用户查看该页面所需的能力。全页模式会自行强制执行此设置,因为它在菜单页面回调流程之外渲染;请将其与注册该页面菜单条目时使用的能力保持一致。experimental(可选,默认false):当为true时,该页面将从 WordPress Core 构建中排除(IS_WORDPRESS_CORE=true),任何仅属于实验性页面的路由也会一并排除。
生成的文件:
这会生成两种页面模式:
build/pages/my-admin-page/page.php– 整页模式(使用自定义侧边栏接管整个管理屏幕)build/pages/my-admin-page/page-wp-admin.php– WP-Admin 模式(集成在标准 wp-admin 界面中)build/pages.php– 所有页面的加载器
每种模式都提供路由/菜单注册函数和渲染回调。路由会自动为两种模式注册。
启动模块:
页面通过 @wordpress/boot 脚本模块启动,该模块随 WordPress Core 7.0+ 和 Gutenberg 插件一起提供。插件不需要自己的 packages/boot。
生成的页面文件会优先使用插件本地的 build/modules/boot/index.min.asset.php(如果存在),否则使用 Core 中位于 wp-includes/js/dist/script-modules/boot/ 的副本。如果两者都不可用,页面将不会加载任何脚本,并只显示“此屏幕需要 JavaScript”提示。
为 WP-Admin 模式注册菜单项:
WP-Admin 模式集成在标准 WordPress 管理界面中(保留侧边栏和顶部栏)。菜单项应使用简单别名和回调注册:
add_submenu_page(
'themes.php', // 父级菜单
__( 'My Page', 'my-plugin' ), // 页面标题
__( 'My Page', 'my-plugin' ), // 菜单标题
'edit_theme_options', // 权限
'my-admin-page-wp-admin', // 菜单别名(简单)
'my_plugin_my_admin_page_wp_admin_render_page' // 来自生成的 PHP 的回调(带前缀)
);
注意:回调函数名称会带有你的插件名前缀(来自根目录 package.json 中的 wpPlugin.name)。例如,如果你的插件名是 my-plugin,该函数将是 my_plugin_my_admin_page_wp_admin_render_page。
页面别名是 my-admin-page-wp-admin(你的页面 ID + -wp-admin)。WordPress 会将所有请求路由到该回调,JavaScript 路由器负责处理内部导航。
使用 p 查询参数进行深度链接:
用户和扩展可以使用 p 查询参数直接链接到特定路由:
// 链接到特定路由
$url = admin_url( 'admin.php?page=my-admin-page-wp-admin&p=' . urlencode( '/settings' ) );
页面加载时,JavaScript 启动系统会读取 p 参数,并自动导航到该路由。
为整页模式注册菜单项:
整页模式会使用自定义侧边栏接管整个管理屏幕:
add_menu_page( 'Title', 'Menu', 'capability', 'my-admin-page', 'my_plugin_my_admin_page_render_page', 'icon', 20 );
Init 模块:
Init 模块是在页面初始化期间执行的 JavaScript 包,会在菜单项和路由注册之后、应用渲染之前运行。它们非常适合用于:
- 为菜单项添加图标(图标无法从 PHP 传递)
- 注册命令面板条目
创建 Init 模块:
在 packages/my-page-init/package.json 中:
{
"name": "@my-plugin/my-page-init",
"wpScriptModuleExports": "./build-module/index.js",
"dependencies": {
"@wordpress/boot": "file:../boot",
"@wordpress/data": "file:../data",
"@wordpress/icons": "file:../icons"
}
}
在 packages/my-page-init/src/index.ts 中:
import { home, styles } from '@wordpress/icons';
import { dispatch } from '@wordpress/data';
import { store as bootStore } from '@wordpress/boot';
/**
* 初始化页面 - 此函数是必需的。
* 所有 init 模块都必须导出一个 'init' 函数。
*/
export async function init() {
// 为菜单项添加图标
dispatch( bootStore ).updateMenuItem( 'home', { icon: home } );
dispatch( bootStore ).updateMenuItem( 'styles', { icon: styles } );
}
init() 函数是必需的——所有 init 模块都必须导出这个命名函数。Init 模块会作为静态依赖加载,并在启动系统注册菜单项和路由之后、应用渲染之前按顺序执行。
示例:WordPress Core(Gutenberg)
{
"wpPlugin": {
"scriptGlobal": "wp",
"packageNamespace": "wordpress"
}
}
此配置:
- 像
@wordpress/data这样的包会暴露window.wp.data - 像
@wordpress/block-editor这样的包会暴露window.wp.blockEditor - 所有包都可以将
@wordpress/*作为外部依赖使用
示例:第三方插件
{
"wpPlugin": {
"name": "acme",
"scriptGlobal": "acme",
"packageNamespace": "acme"
}
}
此配置:
- 像
@acme/editor这样的包会暴露window.acme.editor - 像
@acme/data这样的包会暴露window.acme.data - 所有包仍然可以使用
@wordpress/*→window.wp.* - 所有包仍然可以使用第三方依赖(react、lodash)→
window.React、window.lodash
行为
- 带有
wpScript: true且匹配命名空间的包:打包并暴露到全局 - 带有
wpScript: true但不匹配命名空间的包:打包但不暴露到全局 - 依赖:
@wordpress/*包始终被外部化为wp.*全局变量 - 第三方依赖:React、lodash、jQuery、moment 始终被外部化为它们的标准全局变量
- 资产文件:始终会生成
.asset.php文件,用于 WordPress 依赖管理
输出结构
构建工具会在 build/ 目录中生成多个文件,但主要输出是 PHP 注册文件。
请确保在你的插件文件中引入生成的 PHP 文件。
require_once plugin_dir_path( __FILE__ ) . 'build/build.php';
路由(实验性)
路由为 WordPress 管理页面提供基于文件的路由系统。每个路由都必须关联到在 wpPlugin.pages 中定义的页面(见上文)。请在仓库根目录创建 routes/ 目录,并为每个路由创建子目录。
路由结构
routes/
home/
package.json # 路由配置
stage.tsx # 主内容组件
inspector.tsx # 可选侧边栏组件
canvas.tsx # 可选自定义画布组件
route.tsx # 可选生命周期钩子(beforeLoad、loader、canvas)
路由配置
在 routes/{route-name}/package.json 中:
{
"route": {
"path": "/",
"page": "my-admin-page"
}
}
对于应出现在多个页面上的路由:
{
"route": {
"path": "/settings",
"page": [ "my-admin-page", "other-page" ]
}
}
page 字段可以是:
- 字符串:路由属于单个页面
- 数组:路由出现在多个页面上(构建系统会为每个页面注册该路由)
每个页面 ID 都必须匹配根目录 package.json 中 wpPlugin.pages 里定义的页面之一。这会告诉构建系统该路由属于哪个(些)页面。它也可以映射到其他插件已注册的现有页面。
多页面路由适用于在不同管理页面之间共享的功能,例如可从主页面和专用设置页面访问的设置路由。
路由会继承其页面的实验状态:只有当该路由所属的每个页面都在 wpPlugin.pages 中标记为 "experimental": true 时,它才会从 WordPress Core 构建中排除。
组件
stage.tsx – 主内容(必需):
export const stage = () => <div>Content</div>;
inspector.tsx – 侧边栏内容(可选):
export const inspector = () => <div>Inspector</div>;
canvas.tsx – 自定义画布组件(可选):
export const canvas = () => <div>Custom Canvas</div>;
画布是一个全屏区域,通常用于编辑器预览。要使用自定义画布,请从 canvas.tsx 导出它,并在 route.canvas() 中返回 null。
生命周期钩子(route.tsx)
导出一个带有可选钩子的 route 对象。每个钩子都会接收 { params, search }——来自 URL 的路径参数和查询字符串值。
beforeLoad— 在导航完成前运行。用于身份验证检查、校验或重定向。抛出@wordpress/route中的redirect()或notFound()以中止导航。loader— 在路由加载时运行。用于预加载数据(例如,来自@wordpress/data的resolveSelect),以便stage组件可以从 store 读取,而无需加载状态。可以返回一个对象,其属性会合并到该路由的 loader 数据中。对于必须在 JavaScript 应用加载前可用的数据,请在页面的 PHP 渲染回调中使用rest_preload_api_request()。canvas— 与loader并行运行。根据返回值控制渲染哪个画布:CanvasData({ postType, postId, isPreview?, editLink? })→ 默认的 WordPress 编辑器画布null→ 自定义canvas.tsx组件(如果已提供)undefined或省略 → 不显示画布
export const route = {
beforeLoad: ( { params, search } ) => {
// 导航前校验、身份验证检查
},
loader: ( { params, search } ) => {
// 数据预加载
},
canvas: ( { params, search } ) => {
// 返回 CanvasData 以使用默认画布(编辑器)
return {
postType: params.type,
postId: params.id,
isPreview: true,
editLink: `/types/${ params.type }/edit/${ params.id }`,
};
// 返回 null 以使用自定义 canvas.tsx 组件
// return null;
// 返回 undefined 以不显示画布
// return undefined;
},
};
Routes 构建输出
构建系统会生成:
build/routes/{route-name}/content.js– 打包后的 stage/inspector/canvas 组件build/routes/{route-name}/route.js– 打包后的生命周期钩子(如果存在)build/routes/registry.php– 路由注册数据build/routes.php– 路由注册逻辑
Gutenberg 中的 boot 包会自动使用这些路由并使其可用。
Widgets(实验性)
[!注意]
Widgets 仍为实验性功能。“实验性”表示这是一个早期实现,可能发生重大且具有破坏性的变更。
Widgets 提供了一个基于文件的发现系统,用于构建注册为 WordPress 脚本模块的自包含 UI 组件。每个 widget 都位于仓库根目录下 widgets/ 中的独立目录内。
Widget 结构
widgets/
hello-world/
widget.json # 静态发现元数据(必需)
widget.ts # 运行时值入口(可选)
render.tsx # UI 组件入口(可选)
render.scss # 可选样式(从 render.tsx 导入时会内联打包)
package.json # 可选 npm 依赖清单
为什么有两个入口?
Widgets 使用双入口模式,这在理念上类似于区块在 block.json 和 edit.js 之间拆分元数据的方式:
| 关注点 | 所在位置 | 原因 |
|---|---|---|
身份(name) | 两者(必须一致) | 服务器端需要用它注册;客户端使用它来解析运行时入口 |
可翻译元数据(title、description、help、keywords) | widget.json | 通过 textdomain 在服务器端翻译,因此宿主无需 JS 运行时即可接收本地化字符串 |
框架信息(category、presentation) | widget.json | 纯 JSON,宿主无需 JS 运行时即可读取 |
属性 Schema(类型、选项、标签、relevance) | widget.json | 纯 JSON;标签通过 textdomain 在服务器端翻译;具有相同 id 的 widget.ts 入口可添加 JSON 无法表达的内容 |
example、需要代码的属性 props | widget.ts | 运行时值 |
经验法则:宿主在加载某个 widget 的模块之前需要的任何内容——身份、可翻译文案、框架信息、属性 Schema——都放在 widget.json 中;构建会将其转发到 build/widgets/registry.php,并通过 REST API 暴露。任何需要代码的内容(example、某个属性的 Edit 组件或自定义校验器)则放在 widget.ts 中。
widget.json — 静态发现元数据
{
"name": "my-plugin/hello-world",
"title": "Hello World",
"description": "A simple example widget.",
"help": {
"content": "A short note for compact surfaces such as tooltips."
},
"category": "demo",
"textdomain": "my-plugin"
}
字段:
name(必需):命名空间标识符(例如"my-plugin/hello-world")title(可选):人类可读的标题。使用textdomain在服务器端翻译。description(可选):简短描述。使用textdomain在服务器端翻译。help(可选):用于紧凑界面的上下文帮助说明。它是一个对象,包含content(可包含<em>/<strong>)和可选的links(label、href)。使用textdomain在服务器端翻译。keywords(可选):搜索别名。使用textdomain在服务器端翻译。attributes(可选):属性 Schema,每个属性一个条目,使用 DataViewsField中可用 JSON 表达的子集(id、type、label、elements、不含custom的isValid等),以及可选的relevance(high、medium、low)。type可以指定一个已注册的字段类型。label、header、description、placeholder,以及选项的标签和描述,会使用textdomain在服务器端翻译。category(可选):用于筛选的分组类别presentation(可选):渲染意图(framed、content-bleed、full-bleed)textdomain(可选):用于翻译title、description、help、keywords和属性标签的 Gettext 文本域
widget.ts — 运行时值
可选。导出一个默认对象,其中包含需要代码的内容:example 数据,以及为 widget.json 中具有相同 id 的属性添加 Edit 组件或自定义校验器的属性条目。构建系统会在注册时注入 render_module 句柄,因此作者无需声明它。
import type { WidgetAttributeField } from '@wordpress/widget-primitives';
import { GreetingControl } from './greeting-control';
type HelloWorldAttributes = {
greeting: string;
showDate: boolean;
};
const widget = {
name: 'my-plugin/hello-world',
attributes: [ { id: 'greeting', Edit: GreetingControl } ] satisfies Partial<
WidgetAttributeField< HelloWorldAttributes >
>[],
example: {
attributes: {
greeting: 'World',
showDate: true,
},
},
};
export default widget;
条目会通过 id 与 widget.json Schema 合并:对于共享键,JSON 值优先,模块补充其余内容,而 isValid 会逐条规则合并,因此 JSON 规则和模块 custom 校验器可以共存。如果某个 widget 的属性只是普通数据,则不需要 widget.ts。
render.tsx — UI 组件
render 入口接收与 widget.json 中声明的 Schema 匹配的 attributes。它会在打包时支持 CSS(可直接导入 .scss / .css)。
interface HelloWorldRenderProps {
attributes: {
greeting?: string;
showDate?: boolean;
};
}
export default function HelloWorld( { attributes }: HelloWorldRenderProps ) {
return (
<div>
Hello, { attributes.greeting ?? 'World' }!
{ attributes.showDate && (
<time>{ new Date().toLocaleDateString() }</time>
) }
</div>
);
}
所有非 JSON 入口都是可选的。构建系统会按优先级顺序检查这些扩展名的文件:.tsx、.ts、.jsx、.js、.mjs。
Widgets 构建输出
构建系统会生成:
build/widgets/{widget-name}/render.min.js+render.js— 打包后的 UI 组件(ESM)build/widgets/{widget-name}/render.min.asset.php— 渲染模块的资产元数据
build/widgets/{widget-name}/widget.min.js + widget.js — 捆绑元数据(ESM)build/widgets/{widget-name}/widget.min.asset.php — 小工具模块的资源元数据build/widgets/registry.php — 小工具注册数据build/widgets.php — 脚本模块注册逻辑PHP 注册
生成的 widgets.php 通过 wp_register_script_module() 将每个小工具的条目注册为脚本模块。模块句柄遵循以下模式:
{handlePrefix}/widgets/{widget-dir-name}/render
{handlePrefix}/widgets/{widget-dir-name}/widget
注册被挂载到 init 动作。SCRIPT_DEBUG 常量控制加载压缩(.min.js)还是未压缩(.js)文件。
监听模式
在开发环境(wp-build --watch)中,会监听小工具源文件的变更。当 widgets/{name}/ 内的文件发生变化时,仅重建该小工具。
为此包做贡献
这是一个独立的软件包,是 Gutenberg 项目的一部分。该项目以 monorepo 形式组织,由多个自包含的软件包组成,每个软件包都有特定用途。
此 monorepo 中的软件包发布到 npm,并被 WordPress 以及其他软件项目使用。
要了解更多关于为此软件包或整个 Gutenberg 做贡献的信息,请阅读项目的主要 贡献者指南。
许可证
GPL-2.0-or-later © WordPress 贡献者