@wordpress/build 是一个专为 WordPress 插件设计的构建工具,提供代码转译、样式编译、捆绑打包、PHP 文件生成等功能。它支持开发和生产模式,并通过 package.json 配置实现灵活的脚本、样式和路由管理。
// 生产构建命令
wp-build
// 开发监视模式
wp-build --watch
// package.json 配置示例
{
"scripts": {
"build": "wp-build",
"dev": "wp-build --watch"
},
"wpScript": true,
"wpPlugin": {
"name": "myPlugin",
"scriptGlobal": "myPlugin",
"packageNamespace": "my-plugin"
}
}Build tool for WordPress plugins.
@wordpress/build is an opinionated build system designed for WordPress plugins. It provides:
build/) and ESM (build-module/) formats using esbuildnpm install @wordpress/build --save-dev
wp-build
or via npm script:
{
"scripts": {
"build": "wp-build"
}
}
wp-build --watch
or via npm script:
{
"scripts": {
"dev": "wp-build --watch"
}
}
Configure your package.json with the following optional fields:
wpScriptControls whether the package is exposed as a bundled WordPress script/module and accessible via the configured global variable.
true: The package will be bundled and exposed as a WordPress script. It will be available in WordPress as part of the configured global (e.g., wp.blockEditor, wp.data, or a custom global name if configured differently).
Omitted or false (default): The package will not be exposed as a WordPress script. Use this for packages designed solely as dependencies for other packages. The package can still be used as a dependency via npm imports by other packages.
{
"wpScript": true
}
For more details on when to omit or set this to false, see the package guidelines.
wpScriptModuleExportsDefine script module entry points:
{
"wpScriptModuleExports": {
"./interactivity": "./build-module/interactivity/index.js"
}
}
wpScriptDefaultExportHandle default export wrapping:
{
"wpScriptDefaultExport": true
}
wpScriptExtraDependenciesAdditional script dependencies:
{
"wpScriptExtraDependencies": ["wp-polyfill"]
}
wpStyleEntryPointsCustom SCSS entry point patterns:
{
"wpStyleEntryPoints": {
"style": "src/style.scss"
}
}
wpCopyFilesFiles to copy with optional PHP transformations:
{
"wpCopyFiles": [
{
"from": "src/index.php",
"to": "build/index.php",
"transform": "php"
}
]
}
wpWorkersWorker bundle definitions for packages that need self-contained Web Worker files.
Workers are bundled with all dependencies included and can be loaded via Blob URLs.
String shorthand — entry path only:
{
"wpWorkers": {
"./worker": "./src/worker.ts"
}
}
Object format — entry path with module resolve redirects:
{
"wpWorkers": {
"./worker": {
"entry": "./src/worker.ts",
"resolve": {
"vips-es6.js": "vips.js"
}
}
}
}
The resolve map redirects module loads during bundling. Keys are filename
patterns to match; values are replacement filenames in the same directory.
This is useful when a dependency’s ES module entry point uses import.meta.url,
which fails in Blob URL Worker contexts. By redirecting to an alternative
entry point (e.g., a CommonJS version), the issue is avoided.
Configure your root package.json with a wpPlugin object to control global namespace and externalization behavior:
wpPlugin.nameName used to prefix genereated PHP functions. Must follow function name rules in PHP, i.e. valid name starts with a letter or underscore, followed by any number of letters, numbers, or underscores.
{
"wpPlugin": {
"name": "myPlugin"
}
}
wpPlugin.scriptGlobalThe global variable name for your packages (e.g., "wp", "myPlugin"). Set to false to disable global exposure:
{
"wpPlugin": {
"scriptGlobal": "myPlugin"
}
}
wpPlugin.packageNamespaceThe package scope to match for global exposure (without @ prefix). Only packages matching @{packageNamespace}/* will expose globals:
{
"wpPlugin": {
"scriptGlobal": "myPlugin",
"packageNamespace": "my-plugin"
}
}
wpPlugin.handlePrefixThe prefix used for WordPress script handles in .asset.php files (e.g., wp-data, my-plugin-editor). Defaults to packageNamespace:
{
"wpPlugin": {
"scriptGlobal": "myPlugin",
"packageNamespace": "my-plugin",
"handlePrefix": "mp"
}
}
With this configuration:
– @my-plugin/editor window.myPlugin.editor with handle mp-editor
– @my-plugin/data window.myPlugin.data with handle mp-data
wpPlugin.externalNamespacesAdditional package namespaces to externalize (consume as externals, not expose). Each namespace must be an object with global and optional handlePrefix:
{
"wpPlugin": {
"externalNamespaces": {
"woo": {
"global": "woo",
"handlePrefix": "woocommerce"
},
"acme": {
"global": "acme",
"handlePrefix": "acme-plugin"
}
}
}
}
This allows your packages to consume third-party dependencies as externals:
– import { Cart } from '@woo/cart' window.woo.cart with handle woocommerce-cart
– import { Button } from '@acme/ui' window.acme.ui with handle acme-plugin-ui
– Dependencies are tracked in .asset.php files
If handlePrefix is omitted, it defaults to the namespace key (e.g., "woo" woo-cart).
wpPlugin.pages (Experimental)Define admin pages that support routes. Each page gets generated PHP functions for route registration and can be extended by other plugins.
Pages can be defined as simple strings or as objects with initialization modules:
{
"wpPlugin": {
"pages": [
"my-admin-page",
{
"id": "my-other-page",
"init": ["@my-plugin/my-page-init"]
}
]
}
}
Page Configuration:
– String format: "my-admin-page" – Simple page with no init modules
– Object format: { "id": "page-slug", "init": ["@scope/package"] } – Page with optional init modules
– id (required): The page slug used in WordPress admin URLs
– init (optional): Array of script module IDs to execute during page initialization
Generated Files:
This generates two page modes:
– build/pages/my-admin-page/page.php – Full-page mode (takes over entire admin screen with custom sidebar)
– build/pages/my-admin-page/page-wp-admin.php – WP-Admin mode (integrates within standard wp-admin interface)
– build/pages.php – Loader for all pages
Each mode provides route/menu registration functions and a render callback. Routes are automatically registered for both modes.
Registering a menu item for WP-Admin mode:
WP-Admin mode integrates within the standard WordPress admin interface (keeping the sidebar and header). Menu items should be registered with a simple slug and callback:
add_submenu_page(
'themes.php', // Parent menu
__( 'My Page', 'my-plugin' ), // Page title
__( 'My Page', 'my-plugin' ), // Menu title
'edit_theme_options', // Capability
'my-admin-page-wp-admin', // Menu slug (simple)
'my_plugin_my_admin_page_wp_admin_render_page' // Callback from generated PHP (prefixed)
);
Note: The callback function name is prefixed with your plugin name (from wpPlugin.name in root package.json). For example, if your plugin name is my-plugin, the function will be my_plugin_my_admin_page_wp_admin_render_page.
The page slug is my-admin-page-wp-admin (your page ID + -wp-admin). WordPress routes all requests to this callback, and the JavaScript router handles internal navigation.
Deep linking with the p query parameter:
Users and extensions can link directly to specific routes using the p query parameter:
// Link to a specific route
$url = admin_url( 'admin.php?page=my-admin-page-wp-admin&p=' . urlencode( '/settings' ) );
When the page loads, the JavaScript boot system reads the p parameter and navigates to that route automatically.
Registering a menu item for full-page mode:
Full-page mode takes over the entire admin screen with a custom sidebar:
add_menu_page( 'Title', 'Menu', 'capability', 'my-admin-page', 'my_plugin_my_admin_page_render_page', 'icon', 20 );
Init Modules:
Init modules are JavaScript packages that execute during page initialization, before routes are registered and the app renders. They’re ideal for:
– Adding icons to menu items (icons can’t be passed from PHP)
– Registering command palette entries
Creating an Init Module:
In 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"
}
}
In 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';
/**
* Initialize page - this function is mandatory.
* All init modules must export an 'init' function.
*/
export async function init() {
// Add icons to menu items
dispatch( bootStore ).updateMenuItem( 'home', { icon: home } );
dispatch( bootStore ).updateMenuItem( 'styles', { icon: styles } );
}
The init() function is mandatory – all init modules must export this named function. Init modules are loaded as static dependencies and executed sequentially before the boot system registers menu items and routes.
{
"wpPlugin": {
"scriptGlobal": "wp",
"packageNamespace": "wordpress"
}
}
This configuration:
– Packages like @wordpress/data expose window.wp.data
– Packages like @wordpress/block-editor expose window.wp.blockEditor
– All packages can consume @wordpress/* as externals
{
"wpPlugin": {
"name": "acme",
"scriptGlobal": "acme",
"packageNamespace": "acme"
}
}
This configuration:
– Packages like @acme/editor expose window.acme.editor
– Packages like @acme/data expose window.acme.data
– All packages can still consume @wordpress/* window.wp.*
– All packages can still consume vendors (react, lodash) window.React, window.lodash
wpScript: true matching the namespace: Bundled with global exposurewpScript: true not matching the namespace: Bundled without global exposure@wordpress/* packages are always externalized to wp.* globals.asset.php files are always generated for WordPress dependency managementThe built tool generates several files in the build/ directory, but the primary output is the PHP registration file.
Make sure to include the generated PHP file in your plugin file.
require_once plugin_dir_path( __FILE__ ) . 'build/build.php';
Routes provide a file-based routing system for WordPress admin pages. Each route must be associated with a page defined in wpPlugin.pages (see above). Create a routes/ directory at your repository root with subdirectories for each route.
routes/
home/
package.json # Route configuration
stage.tsx # Main content component
inspector.tsx # Optional sidebar component
canvas.tsx # Optional custom canvas component
route.tsx # Optional lifecycle hooks (beforeLoad, loader, canvas)
In routes/{route-name}/package.json:
{
"route": {
"path": "/",
"page": "my-admin-page"
}
}
For routes that should appear on multiple pages:
{
"route": {
"path": "/settings",
"page": ["my-admin-page", "other-page"]
}
}
The page field can be either:
– String: Route belongs to a single page
– Array: Route appears on multiple pages (the build system will register the route for each page)
Each page ID must match one of the pages defined in wpPlugin.pages in your root package.json. This tells the build system which page(s) this route belongs to. It can also map to existing pages registered by other plugins.
Multi-page routes are useful for shared functionality across different admin pages, such as settings routes accessible from both a main page and a dedicated settings page.
stage.tsx – Main content (required):
export const stage = () => <div>Content</div>;
inspector.tsx – Sidebar content (optional):
export const inspector = () => <div>Inspector</div>;
canvas.tsx – Custom canvas component (optional):
export const canvas = () => <div>Custom Canvas</div>;
The canvas is a full-screen area typically used for editor previews. You can provide a custom canvas component that will be conditionally rendered based on the canvas() function’s return value in route.tsx.
route.tsx – Lifecycle hooks (optional):
export const route = {
beforeLoad: ({ params, search }) => {
// Pre-navigation validation, auth checks
},
loader: ({ params, search }) => {
// Data preloading
},
canvas: ({ params, search }) => {
// Return CanvasData to use default canvas (editor)
return {
postType: 'post',
postId: '123',
isPreview: true
};
// Return null to use custom canvas.tsx component
// return null;
// Return undefined to show no canvas
// return undefined;
}
};
The canvas() function controls which canvas is rendered:
– Returns CanvasData object ({ postType, postId, isPreview? }) Renders the default WordPress editor canvas
– Returns null Renders the custom canvas component from canvas.tsx (if provided)
– Returns undefined or is omitted No canvas is rendered
The build system generates:
– build/routes/{route-name}/content.js – Bundled stage/inspector/canvas components
– build/routes/{route-name}/route.js – Bundled lifecycle hooks (if present)
– build/routes/registry.php – Route registry data
– build/routes.php – Route registration logic
The boot package in Gutenberg will automatically use these routes and make them available.
This is an individual package that’s part of the Gutenberg project. The project is organized as a monorepo. It’s made up of multiple self-contained software packages, each with a specific purpose.
The packages in this monorepo are published to npm and used by WordPress as well as other software projects.
To find out more about contributing to this package or Gutenberg as a whole, please read the project’s main contributor guide.
GPL-2.0-or-later © The WordPress Contributors