关于 wp-block-development
面向 WordPress 区块编辑器开发的代码审查技能,检查 PHP 与 JavaScript/React 实现。它按文件、行号和严重级别整理问题,并提供正反代码示例。
主要功能
- 01校验 block.json 架构与属性
- 02检查 React 编辑器组件模式
- 03审查 PHP 服务端渲染
- 04检查区块弃用与迁移
- 05审查 Interactivity API 用法
- 06按严重级别生成审查报告
适用场景
- 01审查单区块或多区块插件
- 02排查区块验证错误
- 03检查动态区块渲染实现
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jorgerosal/ wordpress-skills --skill wp-block-development安装后在 Claude Code 中输入/wp-block-development确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/block-json-guide.mddynamic-blocks-guide.mdeditor-patterns.mdinteractivity-api-guide.md详细介绍
WordPress 区块开发审查技能
概述
针对 WordPress 6.x+ 区块编辑器(Gutenberg)的系统化区块开发审查。核心原则: WordPress 区块遵循双重架构模式:编辑器使用 React 组件(edit 函数),前端则使用静态 HTML(save 函数)或服务器端 PHP(render_callback/render 文件)。block.json 是唯一事实来源。审查会验证 block.json 架构、编辑器模式(React/JSX)、服务器端渲染(PHP)、属性处理、弃用管理和 Interactivity API 的使用情况。按文件分组报告问题(PHP 和 JS/JSX 文件按实际路径交错排列),并标注行号、严重程度(CRITICAL/WARNING/INFO),以及 BAD/GOOD 代码对比。
注意: 此技能同时审查 PHP 和 JavaScript/React 代码。PHP 遵循 WordPress PHP 编码标准(括号内使用空格、使用 array() 而非 []、Yoda 条件)。JavaScript/JSX 遵循 WordPress JS 编码标准(使用制表符缩进、JSDoc 注释、变量和函数使用 camelCase、组件使用 PascalCase)。
使用场景
适用于:
- 区块插件代码审查(单个区块、多个区块或区块库)
- block.json 架构验证和字段核查
- 编辑器组件审查(edit/save 函数、React/JSX 模式)
- 渲染回调或渲染文件审计(服务器端 PHP)
- InnerBlocks 模式审查(嵌套区块、模板、templateLock)
- 区块弃用检查(save 函数迁移)
- Interactivity API 指令审查(WP 6.5+ 前端交互)
- 区块属性架构验证(type、source、selector、default)
- @wordpress/scripts 构建配置检查
- 区块验证错误调查
- useBlockProps、RichText、InspectorControls、BlockControls 的使用
不适用于:
- theme.json 配置(如可用,请使用 wp-theme-development)
- 通用 React 应用审查(本技能专用于区块编辑器)
- WooCommerce 区块扩展(如可用,请使用 wp-woocommerce-dev)
- 仅限安全性的审计(请使用 wp-security-review 进行全面安全分析)
- 插件架构审计(请使用 wp-plugin-development 检查插件结构)
- 仅限性能的审计(请使用 wp-performance-review)
代码审查工作流
按以下七个步骤执行系统化区块审查:
-
识别区块类型和上下文
- 单区块插件 → 一个 block.json,结构简单
- 多区块插件 → 多个区块,位于 src/block-one/、src/block-two/
- 区块库/集合 → 以软件包形式发布,命名空间至关重要
- 主题区块 → 在 functions.php 中注册,加载上下文不同
-
验证 block.json 架构(BLK-02、BLK-06、BLK-08、BLK-10)
- apiVersion 必须为 3(WP 6.3+)。若为 1 或 2,则标记为 WARNING 并提供升级指导。
- name 必须采用 "namespace/block-name" 格式(小写字母、数字、连字符)
- title、category 为必填项
- attributes:验证 type、source、selector 和 default 值。标记类型不匹配的情况。
- supports:color、spacing、typography、align、anchor、html
- editorScript、script、viewScript、viewScriptModule:验证 "file:./path" 格式
- style、editorStyle:验证路径
- render:动态区块使用 "file:./render.php"
- 建议添加 $schema 字段以便验证
-
检查 edit 函数(BLK-05、BLK-20)
- 必须调用 useBlockProps() 并将其展开到包装元素上
- 导入模式:使用 @wordpress/* 软件包(GOOD),而非 window.wp.*(BAD - 旧式写法)
- 使用 InspectorControls 设置侧边栏选项,使用 BlockControls 设置工具栏
- 正确使用 RichText(tagName、value、onChange、allowedFormats)
- InnerBlocks 使用 allowedBlocks 和 template 属性
- 使用 @wordpress/data 中的 useSelect/useDispatch(将选择器合并在单个 useSelect 调用中)
- i18n:所有面向用户的字符串都应使用来自 @wordpress/i18n 的 __() 包装,并指定文本域
-
检查保存函数或渲染回调
- 静态区块: 必须调用 useBlockProps.save();富文本使用 RichText.Content,嵌套区块使用 InnerBlocks.Content
- 动态区块: save 返回 null(如果使用了嵌套区块,则返回 InnerBlocks.Content - BLK-14 严重)
- 保存函数必须是确定性的——不得使用随机值、Date.now() 或产生副作用
- 渲染回调/render.php: 使用 get_block_wrapper_attributes() 设置包装元素,转义所有输出(esc_html、esc_attr、esc_url、wp_kses_post,但不得对来自 InnerBlocks 的 $content 使用 wp_kses_post - BLK-21),并在文件顶部添加 defined( 'ABSPATH' ) || exit;
-
扫描严重问题模式
- 缺少 apiVersion,或使用 1/2 而非 3
- 在 JS/JSX 中使用 window.wp.,而非导入 @wordpress/
- save 函数未使用 useBlockProps.save()(apiVersion 3)
- 渲染回调对 $content 参数使用 wp_kses_post()(会破坏嵌入内容)
- 使用 InnerBlocks 的动态区块,其 save 返回 null(缺少 InnerBlocks.Content)
- 属性使用 source: 'meta'(已弃用——改用 useEntityProp)
- 属性的类型/source 组合无效
- 缺少 register_block_type() 调用
-
检查警告模式
- block.json 缺少 supports 字段
- edit 函数未使用 useBlockProps()
- JS/JSX 或 PHP 中的硬编码字符串未使用 __() 包装
- render.php 未使用 get_block_wrapper_attributes()
- PHP 渲染文件未检查 defined( 'ABSPATH' )
- save 函数产生副作用(Math.random、Date.now)
- block.json 缺少 $schema 字段
- 多次单独调用 useSelect(性能反模式)
-
记录可改进项
- 在 PHP 中使用 render_callback,而非在 block.json 中使用渲染文件
- block.json 缺少 viewScript/viewScriptModule(没有前端 JS)
- apiVersion 为 2(建议升级到 3)
- block.json 缺少 keywords
- 未使用区块钩子实现自动插入
使用下方的输出格式进行报告。如果发现安全问题(渲染输出未转义、Interactivity API 状态中包含用户输入),请添加说明:“检测到安全问题。运行 /wp-sec-review 进行全面的安全分析。”如果发现插件架构问题(init 钩子注册、缺少 ABSPATH 检查),请添加说明:“检测到插件架构问题。运行 /wp-plugin-review 进行全面的插件审查。”
源代码与构建产物审查: 检查 src/ 文件中的代码模式(开发者意图)。如果 build/ 目录缺失或已过时,请标记(比较 index.asset.php 时间戳与 src/ 修改时间)。不要审查 build/ 文件的代码质量——它们是编译后的产物。
文件类型特定检查
block.json(BLK-02、BLK-06、BLK-08、BLK-10)
apiVersion 字段:
- 严重:缺少 apiVersion → 区块无法正确注册
- 警告:apiVersion 为 1 或 2 → 升级到 3,以启用 iframe 隔离(WP 6.3+)
- 模式:
"apiVersion": 3
name 字段:
- 严重:缺少 name → 区块无法注册
- 严重:格式无效(不是 "namespace/block-name")→ 注册失败
- 模式:
"name": "my-plugin/my-block"(小写字母、连字符、字母和数字)
attributes 字段:
- 严重:类型无效(不是 string/number/boolean/object/array/integer/null)→ 验证失败
- 严重:source:'meta' → 已弃用,请改用 useEntityProp hook
- 警告:type 与 source 数据类型不匹配 → 属性无法正确填充
- 模式:验证 type、source、selector、default 的组合
- 来源:attribute、text、html、query(meta 已弃用)
supports 字段:
- 警告:缺少 supports → 用户无法自定义颜色、间距和排版
- 信息:可添加常见的 supports(color、spacing、typography、align、anchor)
- 模式:为每种支持类型配置嵌套设置的对象
editorScript/script/viewScript/viewScriptModule 字段:
- 警告:路径格式无效 → 资源无法加载
- 模式:
"file:./index.js",相对于 block.json 所在位置 - 注意:viewScriptModule 用于 Interactivity API(WP 6.5+)
render 字段:
- 信息:动态区块可以使用 render 文件,而不是 render_callback
- 模式:
"file:./render.php",相对于 block.json 所在位置
$schema 字段:
- 信息:缺少 $schema → 无法在 IDE 中验证 schema
- 模式:
"$schema": "https://schemas.wp.org/trunk/block.json"
Edit 函数 / edit.js(BLK-05、BLK-20)
useBlockProps 用法:
- 严重:未调用 useBlockProps() → 区块无法在编辑器中正确呈现
- 严重:未将 useBlockProps 的结果展开到包装元素上 → 缺少区块类和属性
- 模式:
const blockProps = useBlockProps();然后使用<div { ...blockProps }>
导入模式:
- 警告:通过 window.wp.* 全局对象访问 → 旧版模式,会导致现代构建失败
- 模式:推荐:
import { useBlockProps } from '@wordpress/block-editor';不推荐:const { useBlockProps } = window.wp.blockEditor;
InspectorControls 和 BlockControls:
- 信息:可使用 InspectorControls 添加设置侧边栏
- 信息:可使用 BlockControls 添加工具栏控件
- 模式:InspectorControls 用于 PanelBody/ToggleControl/SelectControl,BlockControls 用于 AlignmentToolbar/ToolbarGroup
RichText 用法:
- 警告:RichText 未设置 tagName → 可能无法正确呈现
- 警告:RichText 未设置 value/onChange → 不是受控组件
- 模式:
<RichText tagName="p" value={ attributes.content } onChange={ ( content ) => setAttributes( { content } ) } />
InnerBlocks 用法:
- 信息:考虑使用 allowedBlocks 限制嵌套内容
- 信息:考虑使用 template 设置默认区块结构
- 模式:
<InnerBlocks allowedBlocks={ [ 'core/paragraph' ] } template={ [ [ 'core/heading' ] ] } />
useSelect/useDispatch 性能:
- 警告:多次单独调用 useSelect → 区块较多时会降低性能
- 模式:读取多个值时,在单个 useSelect 中组合选择器
国际化:
- 警告:字符串硬编码且未使用 __() → 无法翻译
- 模式:所有面向用户的字符串都使用
__( 'Text', 'text-domain' )
保存函数 / save.js(BLK-05、BLK-07)
静态区块(save 返回 JSX):
- 严重:缺少 useBlockProps.save() → 区块验证错误(apiVersion 3)
- 警告:RichText 未使用 RichText.Content → 无法正确保存富文本
- 警告:InnerBlocks 未使用 InnerBlocks.Content → 嵌套区块无法保存
- 模式:先执行
const blockProps = useBlockProps.save();,然后使用<div { ...blockProps }>
动态区块(save 返回 null 或 InnerBlocks.Content):
- 严重:动态区块包含 InnerBlocks,但 save 返回 null → 嵌套区块丢失(BLK-14)
- 模式:如果区块使用 InnerBlocks,save 必须返回
<InnerBlocks.Content />,即使是动态区块也不例外
确定性保存:
- 警告:在 save 中使用 Math.random() 或 Date.now() → 重新保存时出现区块验证错误
- 警告:在 save 中产生副作用 → 行为不可预测
- 模式:对于相同的属性,save 必须返回相同的标记
渲染回调 / render.php(BLK-11)
ABSPATH 检查:
- 警告:缺少 defined( 'ABSPATH' ) || exit; → 可能被直接访问文件
- 模式:<?php 后的第一行应为 ABSPATH 检查
- 交叉参考:有关安全性的详细信息,请参阅 wp-security-review
get_block_wrapper_attributes():
- 警告:手动拼接类名,而不是使用 get_block_wrapper_attributes() → 区块支持项缺失
- 模式:先执行
$wrapper_attributes = get_block_wrapper_attributes();,然后使用<div <?php echo $wrapper_attributes; ?>>
输出转义:
- 严重:输出未转义 → 存在 XSS 漏洞(CWE-79)
- 严重:对来自 InnerBlocks 的 $content 使用 wp_kses_post() → 会破坏嵌入内容(BLK-21)
- 模式:文本使用 esc_html(),属性使用 esc_attr(),URL 使用 esc_url(),可信 HTML 使用 wp_kses_post()(但 InnerBlocks 的 $content 除外)
- 交叉参考:有关全面的转义模式,请参阅 wp-security-review
函数参数:
- $attributes:区块属性数组
- $content:InnerBlocks HTML(如果使用 InnerBlocks)
- $block:包含上下文和其他元数据的 WP_Block 实例
- 模式:使用全部三个参数以实现完整的动态区块功能
插件主文件 / plugin.php
register_block_type():
- 严重:缺少 register_block_type() → 区块无法注册
- 警告:未挂接到 'init' → 注册可能过早
- 模式:先执行
add_action( 'init', 'callback' );,然后执行register_block_type( __DIR__ . '/build/block-name' );
多区块注册:
- 警告:多区块插件只调用一次 register_block_type() → 其他区块无法注册
- 模式:为每个区块的构建目录调用 register_block_type()
插件头部和 ABSPATH:
- 交叉参考:有关插件头部要求和 ABSPATH 检查,请参阅 wp-plugin-development
区块弃用(BLK-09)
deprecated 数组:
- 严重:更改 save 函数但未声明弃用 → 现有内容出现区块验证错误
- 警告:弃用条目缺少 apiVersion → 弃用可能无法匹配
- 模式:为每个版本提供包含 save 和 attributes 的 deprecated 数组
migrate 函数:
- 信息:可以为属性架构变更添加 migrate 函数
- 模式:
migrate( attributes ) { return { ...attributes, newField: 'default' }; }
排序:
- 警告:弃用项按从旧到新的顺序排列 → WordPress 会先检查最新项,导致性能问题
- 模式:数组中最新的弃用项应排在最前面
交互 API 文件(BLK-12)
WP 6.5+ 版本标记:
- INFO:交互 API 需要 WordPress 6.5+ → 添加版本检查或插件要求
- 模式:在插件头部检查
Requires at least: 6.5
render.php 指令:
- data-wp-interactive="namespace" → 交互 API 作用域必需
- wp_interactivity_state() → 服务端状态初始化
- wp_interactivity_config() → 服务端配置
- data-wp-context → 嵌套元素的上下文提供器
- 指令:data-wp-bind、data-wp-on、data-wp-class、data-wp-style、data-wp-text、data-wp-watch、data-wp-init、data-wp-each
view.js(前端 store):
- WARNING:从 window.wp.interactivity 导入 → 旧版模式,请使用 @wordpress/interactivity
- 模式:先执行
import { store } from '@wordpress/interactivity';,再执行store( 'namespace', { actions, state } );
关键区别:
- 交互 API = 仅限前端(view.js、render.php 指令)
- 编辑器仍使用 React(edit.js)
- 不要将前端状态管理与编辑器组件状态混淆
安全交叉检查:
- WARNING:wp_interactivity_state() 中的用户输入未经过清理 → 存在 XSS 风险
- 交叉参考:请参阅 wp-security-review 中的输入清理模式
区块上下文(BLK-16)
providesContext(父区块):
- 模式:在父区块的 block.json 中添加
"providesContext": { "myPlugin/keyName": "attributeName" } - 命名空间上下文键:使用 "myPlugin/keyName" 格式
usesContext(子区块):
- 模式:在子区块的 block.json 中添加
"usesContext": [ "myPlugin/keyName" ] - 在编辑器中访问:
function Edit( { context } ) { const value = context['myPlugin/keyName']; }
表层检查
区块转换(BLK-13 部分):
- INFO:可以添加与其他区块相互转换的转换项
- 模式:在区块注册中使用 from/to 数组
区块变体(BLK-13):
- INFO:可以添加预设配置变体
- 模式:变体选择器、isDefault、scope 属性
区块样式:
- INFO:可以在 block.json 或 PHP 中注册样式变体
- 模式:在 block.json 中使用 styles 数组,或在 PHP 中使用 register_block_style()
区块样板(BLK-18):
- 模式:register_block_pattern() 结构、分类、关键词
- 注意:样板是多区块布局,与变体(单区块预设)不同
区块钩子(BLK-19):
- INFO:可以使用 blockHooks 自动插入(WP 6.4+)
- CRITICAL:区块钩子仅适用于动态区块(save 返回 null)
- 模式:在 block.json 中添加 blockHooks 属性
区块绑定 API(BLK-17):
- INFO:可以使用绑定建立动态属性连接(WP 6.5+)
- 模式:仅进行表层检测
@wordpress/scripts 构建配置:
- WARNING:缺少 @wordpress/scripts → 没有构建工具链
- WARNING:package.json 缺少 build/start 脚本 → 无法编译区块
- 模式:检查 package.json 中是否包含 "scripts": { "build": "wp-scripts build", "start": "wp-scripts start" }
模板锁定:
- INFO:可以在 InnerBlocks 上使用 templateLock 固定布局
- 模式:
<InnerBlocks templateLock="all" />可防止添加、移除或重新排列区块
快速检测的搜索模式(BLK-22)
使用以下 rg 命令快速扫描区块。按严重级别整理,涵盖 PHP 和 JS/JSX 文件。
CRITICAL 模式
# block.json without apiVersion 3
rg -l --iglob 'block.json' . | xargs -I{} sh -c "rg -q '\"apiVersion\"\\s*:\\s*3' '{}' || echo '{}'"
# JS/JSX files with window.wp.* instead of @wordpress/* imports
rg -n "window\.wp\." . -g '*.{js,jsx}'
# save function without useBlockProps.save() in JSX files (manual candidate list)
rg -n "function save|const save\s*=|save:\s*\(" . -g '*.{js,jsx}'
# render callback using wp_kses_post on $content parameter (breaks embeds)
rg -n "wp_kses_post\s*\(\s*\$content\s*\)" . -g '*.php'
# Dynamic block with InnerBlocks but save returns null
# Manual check: if edit.js uses InnerBlocks, save.js should include InnerBlocks.Content.
rg -n "InnerBlocks" . -g '*.{js,jsx}'
rg -n "return\s+null" . -g '*.{js,jsx}'
# Attribute with source: 'meta' (deprecated)
rg -n "\"source\"\s*:\s*\"meta\"" . -g 'block.json'
# Missing register_block_type() call in plugin bootstrap files
rg -n "register_block_type\s*\(" . -g '*.php'
WARNING 模式
# block.json missing supports field
rg -l --iglob 'block.json' . | xargs -I{} sh -c "rg -q '\"supports\"' '{}' || echo '{}'"
# edit function without useBlockProps() in JS/JSX files (manual candidate list)
rg -n "function Edit|export default function Edit|const Edit\s*=" . -g '*.{js,jsx}'
# Hardcoded strings not wrapped in __() in JS/JSX files
rg -n "title\s*:\s*['\"][A-Z]" . -g '*.{js,jsx}'
# render.php without get_block_wrapper_attributes()
rg -l --iglob 'render.php' . | xargs -I{} sh -c "rg -q 'get_block_wrapper_attributes' '{}' || echo '{}'"
# PHP render files without defined( 'ABSPATH' ) check
rg -l --iglob 'render.php' . | xargs -I{} sh -c "rg -q 'defined.*ABSPATH' '{}' || echo '{}'"
# save function with side effects (Math.random, Date.now)
rg -n "Math\.random|Date\.now|new Date\(" . -g 'save.js'
# Missing block.json $schema field
rg -l --iglob 'block.json' . | xargs -I{} sh -c "rg -q '\"\\$schema\"' '{}' || echo '{}'"
# useSelect with multiple separate calls (performance anti-pattern)
# Manual check: inspect files with repeated useSelect() calls.
rg -n "useSelect\s*\(" . -g '*.{js,jsx}'
INFO 模式
# Using render_callback in PHP instead of render file in block.json
grep -rn "render_callback" --include="*.php" .
# Missing viewScript/viewScriptModule in block.json (no frontend JS)
grep -L "viewScript\|viewScriptModule" --include="block.json" .
# apiVersion 2 (suggest upgrade to 3)
grep -rn "\"apiVersion\": 2" --include="block.json" .
# Missing keywords in block.json
grep -L "\"keywords\"" --include="block.json" .
# Not using block hooks for auto-insertion opportunities
# (Manual check: Review block purpose for auto-insertion potential)
注意: JavaScript 的 Grep 模式需要使用不同于 PHP 模式的正则表达式。对 JS 文件使用 --include="*.js" --include="*.jsx",对 PHP 文件使用 --include="*.php"。
区块上下文检测
根据区块分布和加载上下文提供的上下文感知审查说明:
单区块插件
结构: 根目录或 src/ 下有一个 block.json,只有一个构建输出 审查调整: 标准审查,无特殊考虑 模式: 插件文件夹包含一个 src/ 目录,其中有 index.js、edit.js、save.js
多区块插件
结构: src/block-one/、src/block-two/ 中包含多个区块,每个目录都有 block.json
审核调整:
- 检查
src/shared/或src/components/中的共享组件 - 确认所有区块遵循一致的命名约定
- 确保构建配置涵盖所有区块(多个入口点或通配符)
- 确认每个区块的构建目录都调用了
register_block_type()模式: 插件文件夹中包含多个src/*/block.json文件
区块库/集合
结构: 作为 npm 包或包含多个区块的 WordPress.org 插件发布 审核调整:
- 命名空间至关重要(所有区块必须使用一致的命名空间)
- 检查命名是否一致:「namespace/block-one」、「namespace/block-two」
- 确认所有区块都使用同一命名空间
- Package.json 应包含指向构建入口的 "main" 字段 模式: package.json 包含 "name" 字段,并发布到 npm 或 WordPress.org
主题区块
结构: 在 functions.php 中注册,区块文件位于 /blocks/ 或 /inc/blocks/ 审核调整:
- 加载上下文不同(主题激活与插件激活)
- 区块可能依赖 theme.json 设置(颜色、间距、排版)
- 简要说明 theme.json 集成,详细内容留待第 4 阶段(主题开发)
- 确认区块已挂接到 'init' 或 'after_setup_theme' 钩子进行注册 模式: functions.php 中调用 register_block_type(),区块位于主题子目录
快速参考:区块开发模式(BLK-23)
按关注点整理的常见区块模式。所有示例均遵循 WordPress 编码标准(PHP:括号内留空格,使用 array() 而非 [],使用 Yoda 条件;JS:使用制表符缩进,camelCase,组件使用 PascalCase)。
block.json 架构
包含所有字段注释的完整架构(apiVersion 3):
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "my-plugin/my-block",
"title": "My Block",
"category": "widgets",
"icon": "smiley",
"description": "A custom block example",
"keywords": [ "custom", "example" ],
"version": "1.0.0",
"textdomain": "my-plugin",
"attributes": {
"content": {
"type": "string",
"source": "html",
"selector": "p",
"default": ""
},
"showImage": {
"type": "boolean",
"default": false
}
},
"supports": {
"html": false,
"color": {
"background": true,
"text": true
},
"spacing": {
"margin": true,
"padding": true
},
"typography": {
"fontSize": true,
"lineHeight": true
},
"align": [ "wide", "full" ],
"anchor": true
},
"editorScript": "file:./index.js",
"editorStyle": "file:./index.css",
"style": "file:./style-index.css",
"viewScriptModule": "file:./view.js"
}
区块注册
PHP + JS 注册模式:
❌ 错误:缺少 ABSPATH 检查,未挂接到 init
<?php
function my_plugin_register_block() {
register_block_type( __DIR__ . '/build/my-block' );
}
my_plugin_register_block(); // Runs immediately, not on init
✅ 正确:使用 ABSPATH 和 init 钩子的完整注册
<?php
defined( 'ABSPATH' ) || exit;
function my_plugin_register_blocks() {
register_block_type( __DIR__ . '/build/my-block' );
}
add_action( 'init', 'my_plugin_register_blocks' );
✅ 正确:多区块注册
<?php
defined( 'ABSPATH' ) || exit;
function my_plugin_register_blocks() {
register_block_type( __DIR__ . '/build/block-one' );
register_block_type( __DIR__ . '/build/block-two' );
register_block_type( __DIR__ . '/build/block-three' );
}
add_action( 'init', 'my_plugin_register_blocks' );
静态区块模式
使用 useBlockProps 编辑,使用 useBlockProps.save 保存,结合 RichText + RichText.Content:
❌ 错误:编辑和保存时缺少 useBlockProps
// edit.js
export default function Edit( { attributes, setAttributes } ) {
return (
<div className="my-block">
<input
value={ attributes.content }
onChange={ ( e ) => setAttributes( { content: e.target.value } ) }
/>
</div>
);
}
// save.js
export default function save( { attributes } ) {
return (
<div className="my-block">
<p>{ attributes.content }</p>
</div>
);
}
✅ 正确:使用 useBlockProps 和 RichText 的完整静态区块
// edit.js
import { useBlockProps, RichText } from '@wordpress/block-editor';
export default function Edit( { attributes, setAttributes } ) {
const blockProps = useBlockProps();
return (
<div { ...blockProps }>
<RichText
tagName="p"
value={ attributes.content }
onChange={ ( content ) => setAttributes( { content } ) }
placeholder="Enter content..."
/>
</div>
);
}
// save.js
import { useBlockProps, RichText } from '@wordpress/block-editor';
export default function save( { attributes } ) {
const blockProps = useBlockProps.save();
return (
<div { ...blockProps }>
<RichText.Content tagName="p" value={ attributes.content } />
</div>
);
}
动态区块模式
使用 useBlockProps 编辑,保存时返回 null(或 InnerBlocks.Content),render.php 使用 get_block_wrapper_attributes 并进行转义:
❌ 错误:render 回调未设置包装器属性,输出未转义
<?php
function my_plugin_render_callback( $attributes ) {
return '<div><p>' . $attributes['content'] . '</p></div>';
}
✅ 正确:进行适当转义的完整动态区块
<?php
// render.php
defined( 'ABSPATH' ) || exit;
$wrapper_attributes = get_block_wrapper_attributes();
?>
<div <?php echo $wrapper_attributes; ?>>
<p><?php echo esc_html( $attributes['content'] ); ?></p>
</div>
// save.js - Dynamic blocks return null
export default function save() {
return null;
}
✅ 正确:使用渲染文件注册动态区块
<?php
// plugin.php
defined( 'ABSPATH' ) || exit;
function my_plugin_register_blocks() {
register_block_type( __DIR__ . '/build/my-block' );
// render file specified in block.json "render": "file:./render.php"
}
add_action( 'init', 'my_plugin_register_blocks' );
InnerBlocks 模式
allowedBlocks、template、templateLock,以及在 save 中使用 InnerBlocks.Content(即使是动态区块):
❌ 错误:包含 InnerBlocks 的动态区块在 save 中返回 null
// edit.js
import { useBlockProps, InnerBlocks } from '@wordpress/block-editor';
export default function Edit() {
const blockProps = useBlockProps();
return (
<div { ...blockProps }>
<InnerBlocks />
</div>
);
}
// save.js - CRITICAL: Nested blocks will be lost!
export default function save() {
return null;
}
✅ 正确:包含 InnerBlocks 的动态区块会保存嵌套内容
// edit.js
import { useBlockProps, InnerBlocks } from '@wordpress/block-editor';
export default function Edit() {
const blockProps = useBlockProps();
const ALLOWED_BLOCKS = [ 'core/paragraph', 'core/image', 'core/heading' ];
const TEMPLATE = [
[ 'core/heading', { placeholder: 'Card Title' } ],
[ 'core/paragraph', { placeholder: 'Card content...' } ],
];
return (
<div { ...blockProps }>
<InnerBlocks
allowedBlocks={ ALLOWED_BLOCKS }
template={ TEMPLATE }
templateLock={ false }
/>
</div>
);
}
// save.js - MUST save InnerBlocks.Content even for dynamic blocks
import { InnerBlocks } from '@wordpress/block-editor';
export default function save() {
return <InnerBlocks.Content />;
}
✅ 正确:render.php 使用 $content 参数获取 InnerBlocks HTML
<?php
// render.php
defined( 'ABSPATH' ) || exit;
$wrapper_attributes = get_block_wrapper_attributes();
?>
<div <?php echo $wrapper_attributes; ?>>
<h3><?php echo esc_html( $attributes['title'] ); ?></h3>
<?php echo $content; // InnerBlocks HTML - DO NOT use wp_kses_post() ?>
</div>
useBlockProps
在 edit 中使用 useBlockProps(),在 save 中使用 useBlockProps.save(),并合并自定义属性:
❌ 错误:缺少 useBlockProps
export default function Edit() {
return <div className="my-block">Content</div>;
}
function save() {
return <div className="my-block">Content</div>;
}
✅ 正确:将自定义属性与 useBlockProps 合并
import { useBlockProps } from '@wordpress/block-editor';
export default function Edit( { attributes } ) {
const blockProps = useBlockProps( {
className: 'my-custom-class',
style: { backgroundColor: attributes.bgColor },
} );
return <div { ...blockProps }>Content</div>;
}
function save( { attributes } ) {
const blockProps = useBlockProps.save( {
className: 'my-custom-class',
style: { backgroundColor: attributes.bgColor },
} );
return <div { ...blockProps }>Content</div>;
}
属性
type/source/selector/default 模式,属性类型匹配:
❌ 错误:类型不匹配、无默认值、使用已弃用的 source
{
"attributes": {
"count": {
"type": "number",
"source": "text",
"selector": ".count"
},
"metaValue": {
"type": "string",
"source": "meta",
"meta": "my_meta_key"
}
}
}
✅ 正确:正确匹配类型和 source、设置默认值、使用现代模式
{
"attributes": {
"content": {
"type": "string",
"source": "html",
"selector": "p",
"default": ""
},
"count": {
"type": "number",
"source": "attribute",
"selector": ".count",
"attribute": "data-count",
"default": 0
},
"url": {
"type": "string",
"source": "attribute",
"selector": "a",
"attribute": "href",
"default": ""
},
"items": {
"type": "array",
"source": "query",
"selector": "li",
"query": {
"text": {
"type": "string",
"source": "text"
}
},
"default": []
}
}
}
✅ 正确:使用 useEntityProp 设置 Meta 属性(而非已弃用的 source)
import { useEntityProp } from '@wordpress/core-data';
export default function Edit() {
const [ meta, setMeta ] = useEntityProp( 'postType', 'post', 'meta' );
const value = meta.my_meta_key;
const updateMeta = ( newValue ) => {
setMeta( { ...meta, my_meta_key: newValue } );
};
// Use value and updateMeta in component
}
区块控件
使用 InspectorControls 管理侧边栏,使用 BlockControls 管理工具栏,使用 PanelBody、ToggleControl、SelectControl:
❌ 错误:设置硬编码在区块标记中,且没有侧边栏控件
export default function Edit( { attributes, setAttributes } ) {
return (
<div>
<label>
Show Image:
<input
type="checkbox"
checked={ attributes.showImage }
onChange={ ( e ) => setAttributes( { showImage: e.target.checked } ) }
/>
</label>
</div>
);
}
✅ 正确:使用 InspectorControls 管理侧边栏,使用 BlockControls 管理工具栏
import { useBlockProps, InspectorControls, BlockControls, AlignmentToolbar } from '@wordpress/block-editor';
import { PanelBody, ToggleControl, SelectControl } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
export default function Edit( { attributes, setAttributes } ) {
const blockProps = useBlockProps();
return (
<>
<BlockControls>
<AlignmentToolbar
value={ attributes.align }
onChange={ ( align ) => setAttributes( { align } ) }
/>
</BlockControls>
<InspectorControls>
<PanelBody title={ __( 'Settings', 'my-plugin' ) }>
<ToggleControl
label={ __( 'Show featured image', 'my-plugin' ) }
checked={ attributes.showImage }
onChange={ ( showImage ) => setAttributes( { showImage } ) }
/>
<SelectControl
label={ __( 'Display style', 'my-plugin' ) }
value={ attributes.style }
options={ [
{ label: 'Default', value: 'default' },
{ label: 'Card', value: 'card' },
{ label: 'List', value: 'list' },
] }
onChange={ ( style ) => setAttributes( { style } ) }
/>
</PanelBody>
</InspectorControls>
<div { ...blockProps }>
{/* Block content */}
</div>
</>
);
}
区块弃用
deprecated 数组、save 函数、属性和迁移方式,条目中的 apiVersion,按从新到旧排序:
❌ 错误:更改 save 函数但未处理弃用
// Changed save markup - existing blocks will show validation error
function save( { attributes } ) {
const blockProps = useBlockProps.save();
return <div { ...blockProps }>{ attributes.content }</div>;
}
✅ 正确:为旧 save 函数添加弃用条目
import { useBlockProps } from '@wordpress/block-editor';
const deprecated = [
{
// Version 2: Added useBlockProps (newest deprecation first)
apiVersion: 2,
attributes: {
content: { type: 'string' },
},
save( { attributes } ) {
return <div className="my-block">{ attributes.content }</div>;
},
},
{
// Version 1: Original block
apiVersion: 2,
attributes: {
content: { type: 'string' },
},
save( { attributes } ) {
return <p>{ attributes.content }</p>;
},
migrate( attributes ) {
// Transform old attributes to new schema
return {
...attributes,
newField: 'default',
};
},
},
];
// Current version
function save( { attributes } ) {
const blockProps = useBlockProps.save();
return <div { ...blockProps }>{ attributes.content }</div>;
}
export default {
edit: Edit,
save,
deprecated,
};
Interactivity API
data-wp-interactive、data-wp-bind、data-wp-on、store()、wp_interactivity_state(),仅强调前端:
❌ 错误:使用自定义 JavaScript 实现前端交互
<?php
// render.php
?>
<div class="my-block">
<button id="toggle-button">Toggle</button>
<div id="content" style="display: none;">Hidden content</div>
</div>
<script>
document.getElementById('toggle-button').addEventListener('click', function() {
var content = document.getElementById('content');
content.style.display = content.style.display === 'none' ? 'block' : 'none';
});
</script>
✅ 正确:使用带指令的 Interactivity API(WP 6.5+)
<?php
// render.php
defined( 'ABSPATH' ) || exit;
wp_interactivity_state( 'myPlugin', array(
'isOpen' => false,
) );
$wrapper_attributes = get_block_wrapper_attributes();
?>
<div
<?php echo $wrapper_attributes; ?>
data-wp-interactive="myPlugin"
data-wp-context='{ "id": "<?php echo esc_attr( uniqid() ); ?>" }'
>
<button data-wp-on--click="actions.toggle">
<?php esc_html_e( 'Toggle', 'my-plugin' ); ?>
</button>
<div data-wp-bind--hidden="!state.isOpen">
<p><?php esc_html_e( 'Hidden content', 'my-plugin' ); ?></p>
</div>
</div>
// view.js - Frontend store (NOT editor code)
import { store } from '@wordpress/interactivity';
store( 'myPlugin', {
state: {
isOpen: false,
},
actions: {
toggle: ( { state } ) => {
state.isOpen = ! state.isOpen;
},
},
} );
block.json 配置:
{
"viewScriptModule": "file:./view.js"
}
注意: Interactivity API 仅用于前端。编辑器仍使用 React(edit.js)。
导入模式
@wordpress/* 包导入与 window.wp.* 旧版访问方式:
❌ 错误:访问 window.wp. 全局对象(旧版)*
const { registerBlockType } = window.wp.blocks;
const { useBlockProps } = window.wp.blockEditor;
const { __ } = window.wp.i18n;
✅ 正确:导入 @wordpress/ 包(现代方式)*
import { registerBlockType } from '@wordpress/blocks';
import { useBlockProps } from '@wordpress/block-editor';
import { __ } from '@wordpress/i18n';
区块支持
在 block.json 中配置 color、spacing、typography、align、anchor:
❌ 错误:缺少 supports 字段
{
"name": "my-plugin/my-block",
"title": "My Block"
}
✅ 正确:完整的 supports 配置
{
"name": "my-plugin/my-block",
"title": "My Block",
"supports": {
"html": false,
"color": {
"background": true,
"text": true,
"gradients": true,
"link": true
},
"spacing": {
"margin": true,
"padding": true,
"blockGap": true
},
"typography": {
"fontSize": true,
"lineHeight": true,
"fontFamily": true,
"fontWeight": true
},
"align": [ "wide", "full" ],
"anchor": true
}
}
区块变体
包含 attributes 和 innerBlocks 的 variation 对象:
import { registerBlockVariation } from '@wordpress/blocks';
registerBlockVariation( 'core/columns', {
name: 'three-columns-equal',
title: 'Three Columns (Equal)',
description: 'Three columns with equal width',
icon: 'columns',
isDefault: false,
scope: [ 'block' ],
attributes: {
columns: 3,
},
innerBlocks: [
[ 'core/column' ],
[ 'core/column' ],
[ 'core/column' ],
],
} );
区块转换
from/to 模式:
import { createBlock } from '@wordpress/blocks';
export default {
edit: Edit,
save,
transforms: {
from: [
{
type: 'block',
blocks: [ 'core/paragraph' ],
transform: ( { content } ) => {
return createBlock( 'my-plugin/my-block', {
content,
} );
},
},
],
to: [
{
type: 'block',
blocks: [ 'core/paragraph' ],
transform: ( { content } ) => {
return createBlock( 'core/paragraph', {
content,
} );
},
},
],
},
};
区块上下文
providesContext/usesContext:
// Parent block.json
{
"providesContext": {
"myPlugin/userId": "userId",
"myPlugin/userName": "userName"
},
"attributes": {
"userId": { "type": "number" },
"userName": { "type": "string" }
}
}
// Child block.json
{
"usesContext": [ "myPlugin/userId", "myPlugin/userName" ]
}
// Child edit.js
export default function Edit( { context } ) {
const userId = context['myPlugin/userId'];
const userName = context['myPlugin/userName'];
return (
<div>
<p>User: { userName } ({ userId })</p>
</div>
);
}
区块钩子
blockHooks 仅用于动态区块:
❌ 错误:静态区块使用区块钩子
{
"blockHooks": {
"core/post-content": "after"
}
}
// save.js - Static save = block hooks won't work
function save() {
return <div>Content</div>;
}
✅ 正确:动态区块使用区块钩子
{
"blockHooks": {
"core/post-content": "after"
},
"render": "file:./render.php"
}
// save.js - Dynamic block (null save) = block hooks work
function save() {
return null;
}
@wordpress/scripts
package.json 脚本配置:
❌ 错误:缺少构建脚本
{
"scripts": {
"test": "echo \"No build toolchain configured\""
}
}
✅ 正确:完整的 @wordpress/scripts 配置
{
"scripts": {
"build": "wp-scripts build",
"start": "wp-scripts start",
"lint:js": "wp-scripts lint-js",
"format": "wp-scripts format",
"packages-update": "wp-scripts packages-update"
},
"devDependencies": {
"@wordpress/scripts": "^27.0.0"
}
}
严重程度定义(BLK-24)
| 严重程度 | 定义 | 示例 |
|---|---|---|
| CRITICAL | 区块无法渲染、导致编辑器崩溃或引发区块验证错误 | 缺少 apiVersion,或使用 apiVersion 1/2 且没有迁移路径;无效的属性类型导致保存验证错误;save 函数不匹配且没有弃用条目;apiVersion 3 区块缺少 useBlockProps.save();edit 中使用 InnerBlocks,但 save 返回 null(而不是 InnerBlocks.Content);对 InnerBlocks 的 $content 参数使用 wp_kses_post();访问 window.wp.* 导致现代构建失效;缺少 register_block_type() 调用;已弃用的 source:'meta' 属性模式 |
| WARNING | 区块可正常工作,但存在质量或兼容性问题,或使用了非标准模式 | 使用已弃用的 API(apiVersion 1/2,但没有严重问题);缺少区块支持(color、spacing、typography);更改 save 函数后没有弃用处理程序;JS/JSX 或 PHP 中的硬编码字符串未使用 i18n;多次单独调用 useSelect(性能问题);render.php 中缺少 get_block_wrapper_attributes();PHP 文件中缺少 defined( 'ABSPATH' );save 函数存在副作用(Math.random、Date.now) |
| INFO | 最佳实践改进或优化机会 | 可以使用 render PHP 文件代替 render_callback 函数;缺少用于前端交互的 viewScript/viewScriptModule;未使用 block.json 的 $schema 字段;使用 apiVersion 2(可升级到 3);block.json 中缺少 keywords;可使用区块钩子实现自动插入;可以为常用预设添加区块变体;固定 InnerBlocks 布局缺少模板锁定 |
输出格式(BLK-24)
按文件分组报告问题(PHP 和 JS/JSX 文件按实际文件路径混合排列),并标明行号和严重程度。每个问题都使用 BAD/GOOD 代码对。
# WordPress Block Review: my-block-plugin
## FILE: src/index.js
### Line 3: WARNING - Legacy global access
window.wp.* access is legacy pattern. Use @wordpress/* package imports for modern build toolchain.
❌ **BAD:**
```javascript
const { registerBlockType } = window.wp.blocks;
✅ 正确:
import { registerBlockType } from '@wordpress/blocks';
文件:src/edit.js
第 12 行:CRITICAL - 缺少 useBlockProps
edit 函数中未调用 useBlockProps()。区块在编辑器中无法正确渲染。
❌ 错误:
export default function Edit() {
return <div className="my-block">Content</div>;
}
✅ 正确:
import { useBlockProps } from '@wordpress/block-editor';
export default function Edit() {
const blockProps = useBlockProps();
return <div { ...blockProps }>Content</div>;
}
文件:src/save.js
第 5 行:CRITICAL - 缺少 useBlockProps.save()
apiVersion 3 区块必须使用 useBlockProps.save()。否则会出现区块验证错误。
❌ 错误:
function save() {
return <div className="my-block">Content</div>;
}
✅ 正确:
import { useBlockProps } from '@wordpress/block-editor';
function save() {
const blockProps = useBlockProps.save();
return <div { ...blockProps }>Content</div>;
}
文件:render.php
第 15 行:严重 - 对 InnerBlocks 内容使用 wp_kses_post
对 $content 使用 wp_kses_post() 会破坏嵌入区块和 oEmbed 处理。InnerBlocks 内容已经过清理。
❌ 错误:
<?php echo wp_kses_post( $content ); ?>
✅ 正确:
<?php echo $content; // InnerBlocks already sanitized ?>
第 1 行:警告 - 缺少 ABSPATH 检查
在文件顶部添加 ABSPATH 检查,以防止直接访问。
❌ 错误:
<?php
$wrapper_attributes = get_block_wrapper_attributes();
✅ 正确:
<?php
defined( 'ABSPATH' ) || exit;
$wrapper_attributes = get_block_wrapper_attributes();
文件:block.json
第 2 行:警告 - apiVersion 2(可升级)
apiVersion 2 仍可使用,但建议升级到 3,以支持 iframe 编辑器隔离(WP 6.3+)。
❌ 错误:
{
"apiVersion": 2
}
✅ 正确:
{
"apiVersion": 3
}
第 10 行:信息 - 缺少 $schema 字段
添加 $schema 字段,以支持 IDE 验证。
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
...
}
摘要
问题总数:7
- 严重:3(必须修复,否则区块无法正确渲染)
- 警告:3(质量/兼容性问题)
- 信息:1(最佳实践改进)
区块验证风险: 高 - 缺少 useBlockProps.save() 会导致验证错误
安全提示: 检测到缺少 ABSPATH 检查。运行 /wp-sec-review 进行全面的安全分析。
插件提示: 检测到区块注册。运行 /wp-plugin-review 进行插件架构审查。
## Common Mistakes (BLK-25)
Patterns that look like issues but are NOT problems:
| Pattern | Why It's NOT a Problem | Context |
|---------|------------------------|---------|
| **Dynamic block save() returning null** | Server-side PHP renders output. save() doesn't need to return markup. | Correct for render_callback or render file usage |
| **InnerBlocks.Content without useBlockProps wrapper** | Valid for dynamic blocks that only need to persist nested blocks. | Dynamic blocks can return bare InnerBlocks.Content in save() |
| **viewScript not needed** | Block has no frontend JavaScript behavior. | Editor-only blocks or static HTML blocks don't need viewScript |
| **render_callback without return** | Valid if callback echoes output (though return is preferred). | WordPress handles both echo and return patterns |
| **Missing style/editorStyle in block.json** | Block uses theme defaults or has no custom styling. | Not all blocks need custom styles |
| **apiVersion 2 blocks** | Still work in WordPress 6.x+, just missing iframe isolation features. | Upgrade recommended but not critical |
| **Block without supports** | Valid for simple blocks that don't need color/spacing/typography. | Intentional for minimal blocks |
| **save() function with className in wrapper** | apiVersion 2 auto-injects classes. apiVersion 3 requires useBlockProps. | apiVersion-dependent behavior, not a bug |
| **window.wp.* in browser console** | Expected for debugging. Only @wordpress/* imports in source code matter. | Console access fine, source imports should use packages |
| **Interactivity API not used** | Blocks with no frontend interaction don't need Interactivity API. | Editor-only or static blocks work without frontend JS |
| **No block.json deprecation for attribute additions** | Adding new optional attributes doesn't break existing blocks. | Only save() markup changes need deprecation |
| **InnerBlocks without template** | Free-form nesting is valid. Template is optional. | Intentional when users should choose any blocks |
## Version Compatibility Reference
Quick reference for WordPress version requirements:
| Feature | WordPress Version | Notes |
|---------|-------------------|-------|
| block.json metadata support | 5.8+ | Single source of truth for block registration |
| apiVersion 2 | 5.6+ | Automatic wrapper class injection in save() |
| apiVersion 3 | 6.3+ | Iframe editor isolation, manual wrapper handling required |
| InspectorControls group prop | 6.2+ | Settings/Appearance tabs in sidebar |
| Block Hooks API | 6.4+ | Auto-insertion, dynamic blocks only |
| Interactivity API | 6.5+ | data-wp-* directives, viewScriptModule |
| viewScriptModule field | 6.5+ | ES module support for Interactivity API |
| Block Bindings API | 6.5+ | Dynamic attribute connections |
| useEntityProp hook | 5.9+ | Replacement for deprecated source:'meta' |
## Deep-Dive References
For advanced block development patterns, load these companion reference documents:
| Task | Reference to Load |
|------|-------------------|
| block.json schema validation, field-by-field reference, attribute type/source/selector patterns | `references/block-json-guide.md` |
| React/JSX editor component patterns, useBlockProps, RichText, InspectorControls, BlockControls, useSelect/useDispatch | `references/editor-patterns.md` |
| Server-side rendering, render_callback vs render file, get_block_wrapper_attributes, output escaping, $attributes/$content/$block parameters | `references/dynamic-blocks-guide.md` |
| Interactivity API directives (data-wp-*), wp_interactivity_state(), store() patterns, frontend state management | `references/interactivity-api-guide.md` |
**Note:** Reference docs provide deep-dive content. This SKILL.md is self-sufficient for standard block reviews.
**Security crossover:** When encountering security-relevant patterns (unescaped render output, user input in Interactivity API state, missing ABSPATH checks), this skill provides brief reminders but defers to wp-security-review for comprehensive security analysis. For detailed security patterns, use `/wp-sec-review` command.
**Plugin crossover:** When encountering plugin-level patterns (register_block_type() on init hook, ABSPATH checks, plugin headers), this skill provides brief mentions but defers to wp-plugin-development for plugin architecture depth. For detailed plugin architecture review, use `/wp-plugin-review` command.