关于 wordpress-block-editor-fse
面向 WordPress 主题与区块开发者的 FSE 技能指南,涵盖区块主题架构、站点编辑器和自定义区块开发,并提供迁移与无障碍实践建议。
主要功能
- 01区块主题文件与架构
- 02theme.json 设计系统配置
- 03站点编辑器模板定制
- 04自定义区块开发
- 05经典主题迁移指导
- 06性能与无障碍实践
适用场景
- 01构建新的区块主题
- 02通过站点编辑器配置模板
- 03开发服务器端或 React 区块
- 04将经典主题迁移到 FSE
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ bobmatnyc/ claude-mpm-skills --skill wordpress-block-editor-fse安装后在 Claude Code 中输入/wordpress-block-editor-fse确认已加载。
附带文件metadata.json
详细介绍
WordPress 区块编辑器与全站编辑
概述
全站编辑(FSE)自 WP 6.2 起已可用于生产环境,并将一切都视为区块——页眉、页脚、模板,而不仅仅是内容。区块主题使用 HTML 模板 + theme.json,而非 PHP 文件 + style.css。
主要组件:
- theme.json:集中管理颜色、排版、间距和布局
- HTML 模板:基于区块的文件(index.html、single.html)
- 模板部件:可复用组件(header.html、footer.html)
- 区块模式:预先设计的区块布局
- 站点编辑器:可视化自定义模板
适用场景: ✅ 新主题、一致的设计系统、供非技术用户自定义 ❌ 复杂的服务器逻辑、团队不熟悉区块、依赖大量 PHP
全站编辑架构
区块主题与经典主题
| 区块主题 | 经典主题 |
|---|---|
| 包含区块的 HTML 文件 | 包含模板标签的 PHP 文件 |
| theme.json + CSS | functions.php + style.css |
| 站点编辑器(可视化) | 自定义器(设置) |
| 用户可编辑模板 | 自定义功能有限 |
站点编辑器功能
- 模板编辑(页面、文章、归档)
- 模板部件(页眉/页脚变体)
- 全局样式(全站颜色、排版)
- 模式库(保存/复用区块组合)
- 导航菜单(基于区块)
- 样式变体(备用设计预设)
theme.json 配置
theme.json v3(WP 6.7)提供集中式设计控制。WordPress 会自动生成 CSS 自定义属性。
生产环境示例
{
"$schema": "https://schemas.wp.org/trunk/theme.json",
"version": 3,
"settings": {
"appearanceTools": true,
"useRootPaddingAwareAlignments": true,
"layout": {
"contentSize": "800px",
"wideSize": "1200px"
},
"color": {
"palette": [
{ "slug": "primary", "color": "#0073aa", "name": "Primary" },
{ "slug": "secondary", "color": "#005177", "name": "Secondary" },
{ "slug": "base", "color": "#ffffff", "name": "Base" },
{ "slug": "contrast", "color": "#000000", "name": "Contrast" }
],
"defaultPalette": false,
"defaultGradients": false
},
"typography": {
"fontFamilies": [
{
"fontFamily": "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
"slug": "system",
"name": "System Font"
}
],
"fontSizes": [
{ "slug": "small", "size": "0.875rem", "name": "Small" },
{ "slug": "medium", "size": "1rem", "name": "Medium" },
{
"slug": "large",
"size": "1.5rem",
"name": "Large",
"fluid": { "min": "1.25rem", "max": "1.5rem" }
}
],
"fontWeight": true,
"lineHeight": true
},
"spacing": {
"units": ["px", "em", "rem", "vh", "vw", "%"],
"padding": true,
"margin": true,
"spacingSizes": [
{ "slug": "30", "size": "0.5rem", "name": "XS" },
{ "slug": "40", "size": "1rem", "name": "S" },
{ "slug": "50", "size": "1.5rem", "name": "M" },
{ "slug": "60", "size": "2rem", "name": "L" }
]
},
"border": { "radius": true, "color": true, "width": true }
},
"styles": {
"color": {
"background": "var(--wp--preset--color--base)",
"text": "var(--wp--preset--color--contrast)"
},
"typography": {
"fontFamily": "var(--wp--preset--font-family--system)",
"fontSize": "var(--wp--preset--font-size--medium)",
"lineHeight": "1.6"
},
"elements": {
"link": {
"color": { "text": "var(--wp--preset--color--primary)" },
":hover": {
"color": { "text": "var(--wp--preset--color--secondary)" }
}
},
"h1": {
"typography": {
"fontSize": "var(--wp--preset--font-size--large)",
"fontWeight": "700"
}
},
"button": {
"color": {
"background": "var(--wp--preset--color--primary)",
"text": "var(--wp--preset--color--base)"
},
"border": { "radius": "4px" },
":hover": {
"color": { "background": "var(--wp--preset--color--secondary)" }
}
}
},
"blocks": {
"core/quote": {
"border": {
"width": "0 0 0 4px",
"color": "var(--wp--preset--color--primary)"
},
"spacing": { "padding": { "left": "var(--wp--preset--spacing--60)" } }
}
}
},
"customTemplates": [
{
"name": "page-wide",
"title": "Full Width Page",
"postTypes": ["page"]
}
]
}
自动生成的 CSS 自定义属性
- 颜色:
var(--wp--preset--color--primary) - 字体:
var(--wp--preset--font-family--system) - 字号:
var(--wp--preset--font-size--large) - 间距:
var(--wp--preset--spacing--50)
流体排版
带有 fluid: { min, max } 的字号会使用 clamp() 自动缩放:
{
"slug": "large",
"size": "1.5rem",
"fluid": { "min": "1.25rem", "max": "1.5rem" }
}
区块主题架构
必需文件
my-block-theme/
├── style.css # Theme metadata (REQUIRED)
├── theme.json # Settings/styles (REQUIRED)
├── templates/
│ ├── index.html # Fallback (REQUIRED)
│ ├── single.html
│ ├── page.html
│ └── archive.html
├── parts/
│ ├── header.html
│ └── footer.html
├── patterns/ # Block patterns
│ └── hero.php
└── functions.php # Optional setup
style.css 元数据
/*
Theme Name: My Block Theme
Requires at least: 6.4
Requires PHP: 8.1
Version: 1.0.0
*/
HTML 模板结构
templates/single.html:
<div class="wp-block-group">
</div>
templates/index.html(含查询循环):
<main class="wp-block-group">
<div class="wp-block-query">
</div>
</main>
模板部件
parts/header.html:
<div class="wp-block-group">
</div>
区块模式
patterns/hero.php:
<?php
/**
* Title: Hero Section
* Slug: my-theme/hero
* Categories: featured
*/
?>
<div class="wp-block-cover alignfull">
<div class="wp-block-cover__inner-container">
<h1>Welcome to Our Site</h1>
<div class="wp-block-buttons">
<div class="wp-block-button"><a class="wp-block-button__link">Get Started</a></div>
</div>
</div>
</div>
注册模式类别:
add_action('init', 'register_pattern_categories');
function register_pattern_categories() {
register_block_pattern_category('hero', [
'label' => __('Hero Sections', 'my-theme')
]);
register_block_pattern_category('cta', [
'label' => __('Call to Action', 'my-theme')
]);
}
自定义区块开发
block.json 元数据(区块 API v3)
blocks/testimonial/block.json:
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "my-theme/testimonial",
"title": "Testimonial",
"category": "widgets",
"icon": "format-quote",
"attributes": {
"content": {
"type": "string",
"source": "html",
"selector": ".testimonial-content"
},
"author": { "type": "string", "default": "" },
"role": { "type": "string", "default": "" },
"rating": { "type": "number", "default": 5 }
},
"supports": {
"html": false,
"align": ["wide", "full"],
"color": { "background": true, "text": true },
"spacing": { "padding": true, "margin": true }
},
"render": "file:./render.php"
}
属性来源
从 HTML 提取数据的不同方式:
"attributes": {
"title": {
"type": "string",
"source": "html",
"selector": "h2"
},
"linkUrl": {
"type": "string",
"source": "attribute",
"selector": "a",
"attribute": "href"
},
"isActive": {
"type": "boolean",
"default": false
},
"items": {
"type": "array",
"source": "query",
"selector": ".item",
"query": {
"text": { "type": "string", "source": "text" }
}
}
}
服务端渲染(render.php)
blocks/testimonial/render.php:
<?php
$content = $attributes['content'] ?? '';
$author = $attributes['author'] ?? '';
$role = $attributes['role'] ?? '';
$rating = absint($attributes['rating'] ?? 5);
$wrapper_attributes = get_block_wrapper_attributes([
'class' => 'testimonial-block',
]);
?>
<div <?php echo $wrapper_attributes; ?>>
<blockquote class="testimonial-content">
<?php echo wp_kses_post($content); ?>
</blockquote>
<?php if ($rating > 0) : ?>
<div class="testimonial-rating">
<?php for ($i = 1; $i <= 5; $i++) : ?>
<span class="star <?php echo $i <= $rating ? 'filled' : 'empty'; ?>">
<?php echo $i <= $rating ? '★' : '☆'; ?>
</span>
<?php endfor; ?>
</div>
<?php endif; ?>
<?php if ($author || $role) : ?>
<cite class="testimonial-author">
<span class="author-name"><?php echo esc_html($author); ?></span>
<?php if ($role) : ?>
<span class="author-role"><?php echo esc_html($role); ?></span>
<?php endif; ?>
</cite>
<?php endif; ?>
</div>
客户端渲染(React)
blocks/testimonial/index.js:
import { registerBlockType } from '@wordpress/blocks';
import { useBlockProps, RichText, InspectorControls } from '@wordpress/block-editor';
import { PanelBody, RangeControl, TextControl } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
registerBlockType('my-theme/testimonial', {
edit: ({ attributes, setAttributes }) => {
const { content, author, role, rating } = attributes;
const blockProps = useBlockProps();
return (
<>
<InspectorControls>
<PanelBody title={__('Settings', 'my-theme')}>
<TextControl
label={__('Author', 'my-theme')}
value={author}
onChange={(v) => setAttributes({ author: v })}
/>
<TextControl
label={__('Role', 'my-theme')}
value={role}
onChange={(v) => setAttributes({ role: v })}
/>
<RangeControl
label={__('Rating', 'my-theme')}
value={rating}
onChange={(v) => setAttributes({ rating: v })}
min={1}
max={5}
/>
</PanelBody>
</InspectorControls>
<div {...blockProps}>
<RichText
tagName="blockquote"
value={content}
onChange={(v) => setAttributes({ content: v })}
placeholder={__('Testimonial text...', 'my-theme')}
/>
<div className="testimonial-rating">
{[1, 2, 3, 4, 5].map((star) => (
<span
key={star}
onClick={() => setAttributes({ rating: star })}
>
{star <= rating ? '★' : '☆'}
</span>
))}
</div>
<cite>
<RichText
tagName="span"
value={author}
onChange={(v) => setAttributes({ author: v })}
placeholder={__('Author', 'my-theme')}
/>
</cite>
</div>
</>
);
},
save: () => null, // Server-side rendering
});
区块注册
functions.php:
add_action('init', 'register_custom_blocks');
function register_custom_blocks() {
register_block_type(__DIR__ . '/blocks/testimonial');
}
InspectorControls(设置侧边栏)
区块设置的常用控件:
import {
InspectorControls,
PanelColorSettings,
MediaUpload
} from '@wordpress/block-editor';
import {
PanelBody,
SelectControl,
ToggleControl,
RangeControl,
Button
} from '@wordpress/components';
<InspectorControls>
<PanelBody title="Layout">
<SelectControl
label="Columns"
value={columns}
options={[
{ label: '2', value: 2 },
{ label: '3', value: 3 },
{ label: '4', value: 4 }
]}
onChange={(v) => setAttributes({ columns: parseInt(v) })}
/>
<ToggleControl
label="Enable Shadow"
checked={enableShadow}
onChange={(v) => setAttributes({ enableShadow: v })}
/>
<RangeControl
label="Border Radius"
value={borderRadius}
onChange={(v) => setAttributes({ borderRadius: v })}
min={0}
max={50}
/>
</PanelBody>
<PanelBody title="Media">
<MediaUpload
onSelect={(media) => setAttributes({ imageUrl: media.url })}
allowedTypes={['image']}
render={({ open }) => (
<Button onClick={open} variant="secondary">
{imageUrl ? 'Change Image' : 'Select Image'}
</Button>
)}
/>
</PanelBody>
<PanelColorSettings
title="Colors"
colorSettings={[
{
value: bgColor,
onChange: (v) => setAttributes({ bgColor: v }),
label: 'Background'
}
]}
/>
</InspectorControls>
区块支持
启用 WordPress 功能:
"supports": {
"html": false,
"anchor": true,
"align": ["wide", "full"],
"color": {
"background": true,
"text": true,
"gradients": true
},
"spacing": {
"padding": true,
"margin": true,
"blockGap": true
},
"typography": {
"fontSize": true,
"lineHeight": true,
"fontWeight": true
}
}
使用区块编辑器的自定义文章类型
add_action('init', 'register_book_cpt');
function register_book_cpt() {
register_post_type('book', [
'labels' => [
'name' => __('Books', 'my-theme'),
'singular_name' => __('Book', 'my-theme'),
],
'public' => true,
'has_archive' => true,
'supports' => ['title', 'editor', 'thumbnail'],
'show_in_rest' => true, // REQUIRED for block editor
'menu_icon' => 'dashicons-book',
'template' => [ // Default blocks
['core/paragraph', ['placeholder' => 'Book description...']],
['core/image'],
['my-theme/book-details'],
],
'template_lock' => 'insert', // Can't add/remove blocks
]);
// Register taxonomy
register_taxonomy('genre', 'book', [
'labels' => ['name' => __('Genres', 'my-theme')],
'hierarchical' => true,
'show_in_rest' => true, // REQUIRED
]);
}
模板锁定
false:无限制'all':无法修改结构'insert':无法添加/移除,但可以重新排序'contentOnly':仅可编辑内容
在 theme.json 中注册
"customTemplates": [
{
"name": "single-book",
"title": "Book Template",
"postTypes": ["book"]
}
]
开发工作流
@wordpress/scripts
package.json:
{
"scripts": {
"start": "wp-scripts start",
"build": "wp-scripts build"
},
"devDependencies": {
"@wordpress/scripts": "^27.0.0"
}
}
命令:
npm install
npm run start # Development with hot reload
npm run build # Production build (minified)
wp-env 设置
.wp-env.json:
{
"core": "WordPress/WordPress#6.7",
"phpVersion": "8.3",
"themes": ["./my-block-theme"],
"config": {
"WP_DEBUG": true,
"SCRIPT_DEBUG": true
}
}
用法:
npx @wordpress/env start
# Access: http://localhost:8888
# Admin: admin / password
npx @wordpress/env stop
npx @wordpress/env clean # Reset database
从经典主题迁移
模板标签到区块的映射
| 经典主题 | 对应区块 |
|---|---|
the_title() | `` |
the_content() | `` |
the_post_thumbnail() | `` |
the_date() | `` |
wp_nav_menu() | `` |
get_header() | `` |
get_footer() | `` |
get_sidebar() | `` |
迁移步骤
- 提取设计令牌,从 style.css 提取到 theme.json
- 转换 PHP 模板为 HTML 区块模板
- 在 functions.php 中添加区块支持:
add_theme_support('wp-block-styles');
add_theme_support('align-wide');
add_theme_support('responsive-embeds');
- 使用真实内容全面测试
区块验证
WordPress 会根据已注册的区块定义验证区块标记。无效区块会在编辑器中显示错误:
常见验证错误:
- 属性类型不匹配(字符串与数字)
- 缺少必需属性
- HTML 结构不正确
- 属性名称已更改
修复验证错误:
// Add deprecated versions for backward compatibility
const deprecated = [
{
attributes: {
oldName: { type: 'string' }
},
migrate: (attributes) => ({
newName: attributes.oldName
}),
save: (props) => {
// Old save function
}
}
];
性能与最佳实践
性能
✅ 尽可能使用服务器端渲染(render.php)
✅ 利用区块支持(减少自定义 CSS)
✅ 禁用未使用的功能:"defaultPalette": false
✅ 使用 CSS 自定义属性以保持一致性
❌ 避免对静态内容进行客户端渲染
❌ 不要使用 !important 覆盖核心区块
无障碍
✅ 语义化 HTML(<header>、<main>、<footer>)
✅ 为自定义区块提供键盘导航
✅ WCAG AA 颜色对比度(最低 4.5:1)
✅ 为所有图像提供替代文本
❌ 不要假设 FSE 就意味着无障碍(必须进行测试)
反模式
❌ 混用经典方式和区块方式
❌ 将颜色硬编码(使用 CSS 变量)
❌ 重复实现区块支持
❌ 跳过无障碍测试
❌ 在 HTML 模板中使用 get_header()
相关技能
- wordpress-plugin-fundamentals:钩子系统、CPT
- react:区块编辑器组件
- typescript:类型安全的区块开发
- php-security:清理区块属性
重要提醒
- 区块主题必须使用 theme.json
- FSE 中的 HTML 模板取代 PHP
- 服务器端渲染通常优于客户端渲染
- 区块支持可减少自定义代码
- 无障碍需要通过测试来确保
危险信号
- CSS 文件超过 5 个 → 使用 theme.json
- HTML 模板中包含 PHP 标签 → 使用区块
- 对静态内容进行客户端渲染 → 使用 render.php
- 未进行键盘测试 → 存在无障碍问题
- 值被硬编码 → 使用 CSS 自定义属性
WordPress: 6.7+ | PHP: 8.1+ | 工具: @wordpress/scripts、wp-env