EP06. "wp-block-development Block 开发审查"
🔒 登录后可标记已读- Dynamic block 里塞了 InnerBlocks,结果 save 函数返回 null——内容一发布,用户辛辛苦苦排好的嵌套 block 直接从数据库消失,这是最容易被忽略又最伤的一种坑
wp-block-development是jorgerosal/wordpress-skills里专盯 Gutenberg block 开发的技能,同时审 PHP(register_block_type()、render callback)和 React/JSX(edit/save 函数)两边的代码- 核心原则:block 是「编辑器用 React 组件、前台用静态 HTML 或 PHP 服务端渲染」的双轨架构,
block.json是唯一真相来源(single source of truth) - 前置知识:先看过 Overview 系列「Claude Code 是什么与新手上手」「进阶功能:Skills、Plugins 与 Routines」,以及 EP01「wp-security-review 安全审查」了解这套 skill 包怎么调用
重点内容
这个 Skill 是做什么的
系统审查 WordPress 6.x+ block editor(Gutenberg)代码:block.json schema 校验、editor 组件模式(edit/save 函数)、服务端渲染(render callback / render 文件)、属性(attributes)处理、block 版本迁移(deprecation)管理、Interactivity API 用法。审查同时覆盖 PHP(遵循 WordPress PHP Coding Standards:括号内加空格、用 array() 不用 []、Yoda 条件)和 JS/JSX(遵循 WordPress JS 规范:tab 缩进、JSDoc 注释、变量用 camelCase、组件用 PascalCase)。
适合用在:
- Block 插件代码审查(单个 block、多 block、或 block 库)
block.jsonschema 校验与字段核实- Editor 组件审查(edit/save 函数、React/JSX 模式)
- Render callback 或 render 文件审计(服务端 PHP)
- InnerBlocks 模式审查(嵌套 block、template、templateLock)
- Block deprecation 检查(save 函数迁移)
- Interactivity API 指令审查(WP 6.5+ 前端交互)
useBlockProps、RichText、InspectorControls、BlockControls用法
不适合用在:
theme.json配置(交给wp-theme-development)- 跟 block editor 无关的通用 React 应用审查
- WooCommerce block 扩展
- 纯安全审查(交给
wp-security-review) - 纯插件架构审查(交给
wp-plugin-development) - 纯性能审查(交给
wp-performance-review)
触发方式
| 方式 | 写法 | 说明 |
|---|---|---|
| 斜线指令(完整版) | /wp-block-review [path] | 完整审查,PHP 和 JS/JSX 按实际路径混排分组 |
| 斜线指令(快速版) | /wp-block [path] | 只抓关键 block 问题,适合日常小改动后快速过一遍 |
| 自然语言 | 「审查这个 block 的 edit/save 函数」「check this block.json schema」 | Claude 判断意图符合就自动触发 |
📌 只审 src/ 里的源码(反映开发者真实意图),build/ 目录是编译产物,不审代码质量,只检查是不是缺失或过期(对比 index.asset.php 时间戳跟 src/ 修改时间)。
按检查对象分类的重点
| 检查对象 | CRITICAL 举例 | WARNING 举例 |
|---|---|---|
block.json | 缺 apiVersion(block 注册失败);name 格式不对(不是 namespace/block-name) | apiVersion 还停在 1 或 2(应升到 3,享受 WP 6.3+ 的 iframe 隔离) |
| Edit 函数 | — | 没调用 useBlockProps();用 window.wp.* 而不是 @wordpress/* 引入(老写法,破坏现代构建流程) |
| Save 函数 / 静态 block | apiVersion 3 下 save 没调用 useBlockProps.save()(会造成 block validation 报错) | save 函数里用 Math.random()/Date.now()(save 必须是确定性的,同样的属性要输出同样的 markup) |
| Dynamic block | 用了 InnerBlocks 但 save 返回 null(嵌套内容直接丢失) | — |
Render callback / render.php | 对 InnerBlocks 的 $content 参数用 wp_kses_post()(会破坏 embed) | 没用 get_block_wrapper_attributes(),手动拼 class(丢失 block supports) |
| Attributes | type 跟 source 组合不合法;用 source: 'meta'(已弃用,改用 useEntityProp) | type 跟实际数据类型对不上 |
实操示例:Static Block(静态区块)
// ❌ BAD:edit 和 save 都没调用 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>
);
}
// ✅ GOOD:完整的静态 block,正确用 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>
);
}
实操示例:Dynamic Block + InnerBlocks(最容易踩的坑)
// ❌ CRITICAL:dynamic block 用了 InnerBlocks,但 save 返回 null
// —— 用户排好的嵌套 block 内容会直接消失
// edit.js
import { useBlockProps, InnerBlocks } from '@wordpress/block-editor';
export default function Edit() {
const blockProps = useBlockProps();
return (
<div { ...blockProps }>
<InnerBlocks />
</div>
);
}
// save.js —— 嵌套内容会丢失!
export default function save() {
return null;
}
// ✅ GOOD:即使是 dynamic block,只要用了 InnerBlocks,
// save 也必须返回 <InnerBlocks.Content />,前台渲染再靠 render.php 的 $content 参数接住
// save.js
import { InnerBlocks } from '@wordpress/block-editor';
export default function save() {
return <InnerBlocks.Content />;
}
<?php
// render.php —— 用 $content 参数拿到 InnerBlocks 生成的 HTML
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 —— 千万不要对它用 wp_kses_post(),会破坏 embed ?>
</div>
怎么安装
这个 skill 是 wordpress-skills 这个开源仓库(jorgerosal/wordpress-skills)打包的 18 个技能之一,安装一次,18 个技能一起到位,不用逐个装:
Claude Code:
| 方式 | 指令 | 适用场景 |
|---|---|---|
| 装进单个项目(推荐) | git submodule add https://github.com/jorgerosal/wordpress-skills.git .claude/plugins/wordpress-skills | 只想在这个项目用,团队成员 clone 项目就一起有 |
| 装到自己账号 | git clone https://github.com/jorgerosal/wordpress-skills.git ~/.claude/plugins/wordpress-skills | 所有项目都能用 |
| 只装这一个 skill | cp -r claude-skills/wp-block-development ~/.claude/skills/ | 只想要 block 开发审查这一个功能,不要其他 17 个 |
装完重启 Claude Code,进到一个 WordPress 项目里跑 /wordpress-skills:wp-block-review 验证有没有装成功(用 marketplace/submodule 方式装的话,指令前面会带插件命名空间 wordpress-skills:;用「只装这一个 skill」的方式则不带命名空间,直接 /wp-block-review)。
Claude Desktop / claude.ai: 走 Settings → Capabilities → Skills,上传技能文件夹(把 claude-skills/wp-block-development 这个文件夹打包上传,里面要包含 SKILL.md)——跟 Claude Code 的 git submodule/marketplace 安装方式不同,Desktop 端是手动上传 UI,装好之后同样能用自然语言或 slash 指令触发。
常见错误
- ❌ Dynamic block 的 save 函数返回 null 就被当成 bug——这本来就是对的,服务端 PHP 负责渲染输出,save 不需要返回 markup(只有「用了 InnerBlocks 却返回 null」才是真的 CRITICAL)
- ❌ Block 没有
viewScript就被要求补上——block 没有前端交互行为的话(纯编辑器展示或静态 HTML),本来就不需要viewScript - ❌
apiVersion还是 2 就直接判 CRITICAL——2 在 WordPress 6.x+ 里照样能跑,只是少了 iframe 隔离,建议升级但不是必须 - ❌ Block 没有
supports字段就扣分——简单 block 本来就不一定需要 color/spacing/typography 这些自定义能力,这是刻意的极简设计 - ❌ 浏览器 console 里出现
window.wp.*就当成源码问题——console 调试时用没关系,只有源码(.js文件)里的 import 用window.wp.*才是该抓的反模式 - ❌ 只加了新的可选属性(optional attribute)就要求写
deprecated数组——只有 save 函数产出的 markup 变了才需要写 deprecation,新增可选属性不会破坏已有内容 - 💡 发现未转义输出或 Interactivity API 里塞了用户输入,报告里提醒一句「安全问题,建议跑
/wp-sec-review」;发现 init 钩子注册、ABSPATH检查缺失这类插件架构问题,提醒跑/wp-plugin-review——这个 skill 本身不深挖这两块
Sources
官方文档:
- wordpress-skills(GitHub 仓库)— https://github.com/jorgerosal/wordpress-skills
- wp-block-development SKILL.md — https://github.com/jorgerosal/wordpress-skills/blob/main/claude-skills/wp-block-development/SKILL.md