mcpField
W
wp-block-development

wp-block-development

github.com/jorgerosal开发11360 次安装21 周前更新
代码仓库

关于 wp-block-development

面向 WordPress 区块编辑器开发的代码审查技能,检查 PHP 与 JavaScript/React 实现。它按文件、行号和严重级别整理问题,并提供正反代码示例。

主要功能

  1. 01校验 block.json 架构与属性
  2. 02检查 React 编辑器组件模式
  3. 03审查 PHP 服务端渲染
  4. 04检查区块弃用与迁移
  5. 05审查 Interactivity API 用法
  6. 06按严重级别生成审查报告

适用场景

  1. 01审查单区块或多区块插件
  2. 02排查区块验证错误
  3. 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.md
dynamic-blocks-guide.md
editor-patterns.md
interactivity-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)

代码审查工作流

按以下七个步骤执行系统化区块审查:

  1. 识别区块类型和上下文

    • 单区块插件 → 一个 block.json,结构简单
    • 多区块插件 → 多个区块,位于 src/block-one/、src/block-two/
    • 区块库/集合 → 以软件包形式发布,命名空间至关重要
    • 主题区块 → 在 functions.php 中注册,加载上下文不同
  2. 验证 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 字段以便验证
  3. 检查 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 的 __() 包装,并指定文本域
  4. 检查保存函数或渲染回调

    • 静态区块: 必须调用 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;
  5. 扫描严重问题模式

    • 缺少 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() 调用
  6. 检查警告模式

    • 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(性能反模式)
  7. 记录可改进项

    • 在 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.

评论

支持 Markdown