wordpress-block-editor-fse
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseWordPress Block Editor & Full Site Editing
WordPress区块编辑器与全站编辑
Overview
概述
Full Site Editing (FSE) is production-ready (since WP 6.2) and treats everything as blocks—headers, footers, templates, not just content. Block themes use HTML templates + theme.json instead of PHP files + style.css.
Key Components:
- theme.json: Centralized colors, typography, spacing, layout
- HTML Templates: Block-based files (index.html, single.html)
- Template Parts: Reusable components (header.html, footer.html)
- Block Patterns: Pre-designed block layouts
- Site Editor: Visual template customization
When to Use:
✅ New themes, consistent design systems, non-technical user customization
❌ Complex server logic, team unfamiliar with blocks, heavy PHP dependencies
全站编辑(FSE)自WP 6.2起已可投入生产使用,它将所有元素都视为区块——包括页眉、页脚、模板,而不仅仅是内容。区块主题使用HTML模板 + theme.json替代PHP文件 + style.css。
核心组件:
- theme.json:集中管理颜色、排版、间距、布局
- HTML模板:基于区块的文件(index.html、single.html)
- 模板部件:可复用组件(header.html、footer.html)
- 区块模式:预设计的区块布局
- 站点编辑器:可视化模板自定义
适用场景:
✅ 新主题开发、统一设计系统、非技术用户自定义
❌ 复杂服务器逻辑、团队不熟悉区块、重度依赖PHP
Full Site Editing Architecture
全站编辑架构
Block Themes vs Classic Themes
区块主题 vs 传统主题
| Block Themes | Classic Themes |
|---|---|
| HTML files with blocks | PHP files with template tags |
| theme.json + CSS | functions.php + style.css |
| Site Editor (visual) | Customizer (settings) |
| User edits templates | Limited customization |
| 区块主题 | 传统主题 |
|---|---|
| 包含区块的HTML文件 | 包含模板标签的PHP文件 |
| theme.json + CSS | functions.php + style.css |
| 站点编辑器(可视化) | 自定义器(设置型) |
| 用户可编辑模板 | 自定义权限有限 |
Site Editor Capabilities
站点编辑器功能
- Template editing (pages, posts, archives)
- Template parts (header/footer variations)
- Global styles (colors, typography site-wide)
- Pattern library (save/reuse block compositions)
- Navigation menus (block-based)
- Style variations (alternate design presets)
- 模板编辑(页面、文章、归档页)
- 模板部件(页眉/页脚变体)
- 全局样式(全站颜色、排版)
- 模式库(保存/复用区块组合)
- 导航菜单(基于区块)
- 样式变体(备选设计预设)
theme.json Configuration
theme.json配置
theme.json v3 (WP 6.7) provides centralized design control. WordPress auto-generates CSS custom properties.
theme.json v3(WP 6.7版本)提供集中式设计控制。WordPress会自动生成CSS自定义属性。
Production Example
生产环境示例
json
{
"$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"]
}
]
}json
{
"$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 Custom Properties Auto-Generated
自动生成的CSS自定义属性
- Colors:
var(--wp--preset--color--primary) - Fonts:
var(--wp--preset--font-family--system) - Sizes:
var(--wp--preset--font-size--large) - Spacing:
var(--wp--preset--spacing--50)
- 颜色:
var(--wp--preset--color--primary) - 字体:
var(--wp--preset--font-family--system) - 尺寸:
var(--wp--preset--font-size--large) - 间距:
var(--wp--preset--spacing--50)
Fluid Typography
流体排版
Font sizes with auto-scale using :
fluid: { min, max }clamp()json
{
"slug": "large",
"size": "1.5rem",
"fluid": { "min": "1.25rem", "max": "1.5rem" }
}带有的字体大小会使用自动缩放:
fluid: { min, max }clamp()json
{
"slug": "large",
"size": "1.5rem",
"fluid": { "min": "1.25rem", "max": "1.5rem" }
}Block Theme Architecture
区块主题架构
Required Files
必需文件
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 setupmy-block-theme/
├── style.css # 主题元数据(必需)
├── theme.json # 设置/样式(必需)
├── templates/
│ ├── index.html # 回退模板(必需)
│ ├── single.html
│ ├── page.html
│ └── archive.html
├── parts/
│ ├── header.html
│ └── footer.html
├── patterns/ # 区块模式
│ └── hero.php
└── functions.php # 可选配置文件style.css Metadata
style.css元数据
css
/*
Theme Name: My Block Theme
Requires at least: 6.4
Requires PHP: 8.1
Version: 1.0.0
*/css
/*
Theme Name: My Block Theme
Requires at least: 6.4
Requires PHP: 8.1
Version: 1.0.0
*/HTML Template Structure
HTML模板结构
templates/single.html:
html
<!-- wp:template-part {"slug":"header","tagName":"header"} /-->
<!-- wp:group {"layout":{"type":"constrained"}} -->
<div class="wp-block-group">
<!-- wp:post-title {"level":1} /-->
<!-- wp:post-featured-image /-->
<!-- wp:post-content /-->
<!-- wp:post-date /-->
</div>
<!-- /wp:group -->
<!-- wp:template-part {"slug":"footer","tagName":"footer"} /-->templates/index.html (with query loop):
html
<!-- wp:template-part {"slug":"header"} /-->
<!-- wp:group {"tagName":"main"} -->
<main class="wp-block-group">
<!-- wp:query {"queryId":1,"query":{"perPage":10,"postType":"post"}} -->
<div class="wp-block-query">
<!-- wp:post-template {"layout":{"type":"grid","columnCount":3}} -->
<!-- wp:post-featured-image {"isLink":true} /-->
<!-- wp:post-title {"isLink":true} /-->
<!-- wp:post-excerpt /-->
<!-- /wp:post-template -->
<!-- wp:query-pagination -->
<!-- wp:query-pagination-previous /-->
<!-- wp:query-pagination-numbers /-->
<!-- wp:query-pagination-next /-->
<!-- /wp:query-pagination -->
</div>
<!-- /wp:query -->
</main>
<!-- /wp:group -->
<!-- wp:template-part {"slug":"footer"} /-->templates/single.html:
html
<!-- wp:template-part {"slug":"header","tagName":"header"} /-->
<!-- wp:group {"layout":{"type":"constrained"}} -->
<div class="wp-block-group">
<!-- wp:post-title {"level":1} /-->
<!-- wp:post-featured-image /-->
<!-- wp:post-content /-->
<!-- wp:post-date /-->
</div>
<!-- /wp:group -->
<!-- wp:template-part {"slug":"footer","tagName":"footer"} /-->templates/index.html(包含查询循环):
html
<!-- wp:template-part {"slug":"header"} /-->
<!-- wp:group {"tagName":"main"} -->
<main class="wp-block-group">
<!-- wp:query {"queryId":1,"query":{"perPage":10,"postType":"post"}} -->
<div class="wp-block-query">
<!-- wp:post-template {"layout":{"type":"grid","columnCount":3}} -->
<!-- wp:post-featured-image {"isLink":true} /-->
<!-- wp:post-title {"isLink":true} /-->
<!-- wp:post-excerpt /-->
<!-- /wp:post-template -->
<!-- wp:query-pagination -->
<!-- wp:query-pagination-previous /-->
<!-- wp:query-pagination-numbers /-->
<!-- wp:query-pagination-next /-->
<!-- /wp:query-pagination -->
</div>
<!-- /wp:query -->
</main>
<!-- /wp:group -->
<!-- wp:template-part {"slug":"footer"} /-->Template Parts
模板部件
parts/header.html:
html
<!-- wp:group {"layout":{"type":"flex","justifyContent":"space-between"}} -->
<div class="wp-block-group">
<!-- wp:site-logo {"width":60} /-->
<!-- wp:navigation /-->
</div>
<!-- /wp:group -->parts/header.html:
html
<!-- wp:group {"layout":{"type":"flex","justifyContent":"space-between"}} -->
<div class="wp-block-group">
<!-- wp:site-logo {"width":60} /-->
<!-- wp:navigation /-->
</div>
<!-- /wp:group -->Block Patterns
区块模式
patterns/hero.php:
php
<?php
/**
* Title: Hero Section
* Slug: my-theme/hero
* Categories: featured
*/
?>
<!-- wp:cover {"url":"<?php echo esc_url(get_template_directory_uri()); ?>/assets/images/hero.jpg","dimRatio":50,"minHeight":500,"align":"full"} -->
<div class="wp-block-cover alignfull">
<div class="wp-block-cover__inner-container">
<!-- wp:heading {"textAlign":"center","level":1,"fontSize":"xx-large"} -->
<h1>Welcome to Our Site</h1>
<!-- /wp:heading -->
<!-- wp:buttons {"layout":{"type":"flex","justifyContent":"center"}} -->
<div class="wp-block-buttons">
<!-- wp:button -->
<div class="wp-block-button"><a class="wp-block-button__link">Get Started</a></div>
<!-- /wp:button -->
</div>
<!-- /wp:buttons -->
</div>
</div>
<!-- /wp:cover -->Register pattern categories:
php
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')
]);
}patterns/hero.php:
php
<?php
/**
* Title: Hero Section
* Slug: my-theme/hero
* Categories: featured
*/
?>
<!-- wp:cover {"url":"<?php echo esc_url(get_template_directory_uri()); ?>/assets/images/hero.jpg","dimRatio":50,"minHeight":500,"align":"full"} -->
<div class="wp-block-cover alignfull">
<div class="wp-block-cover__inner-container">
<!-- wp:heading {"textAlign":"center","level":1,"fontSize":"xx-large"} -->
<h1>Welcome to Our Site</h1>
<!-- /wp:heading -->
<!-- wp:buttons {"layout":{"type":"flex","justifyContent":"center"}} -->
<div class="wp-block-buttons">
<!-- wp:button -->
<div class="wp-block-button"><a class="wp-block-button__link">Get Started</a></div>
<!-- /wp:button -->
</div>
<!-- /wp:buttons -->
</div>
</div>
<!-- /wp:cover -->注册模式分类:
php
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')
]);
}Custom Block Development
自定义区块开发
block.json Metadata (Block API v3)
block.json元数据(Block API v3)
blocks/testimonial/block.json:
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"
}blocks/testimonial/block.json:
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"
}Attribute Sources
属性来源
Different ways to extract data from HTML:
json
"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" }
}
}
}从HTML中提取数据的不同方式:
json
"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" }
}
}
}Server-Side Rendering (render.php)
服务器端渲染(render.php)
blocks/testimonial/render.php:
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>blocks/testimonial/render.php:
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>Client-Side Rendering (React)
客户端渲染(React)
blocks/testimonial/index.js:
javascript
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
});blocks/testimonial/index.js:
javascript
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
});Block Registration
区块注册
functions.php:
php
add_action('init', 'register_custom_blocks');
function register_custom_blocks() {
register_block_type(__DIR__ . '/blocks/testimonial');
}functions.php:
php
add_action('init', 'register_custom_blocks');
function register_custom_blocks() {
register_block_type(__DIR__ . '/blocks/testimonial');
}InspectorControls (Settings Sidebar)
检查器控件(设置侧边栏)
Common controls for block settings:
javascript
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>区块设置常用控件:
javascript
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>Block Supports
区块支持
Enable WordPress features:
json
"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
}
}启用WordPress功能:
json
"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
}
}Custom Post Types with Block Editor
支持区块编辑器的自定义文章类型
php
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
]);
}php
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, // 启用区块编辑器必需
'menu_icon' => 'dashicons-book',
'template' => [ // 默认区块
['core/paragraph', ['placeholder' => 'Book description...']],
['core/image'],
['my-theme/book-details'],
],
'template_lock' => 'insert', // 无法添加/删除区块
]);
// 注册分类法
register_taxonomy('genre', 'book', [
'labels' => ['name' => __('Genres', 'my-theme')],
'hierarchical' => true,
'show_in_rest' => true, // 启用必需
]);
}Template Locking
模板锁定
- : No restrictions
false - : Cannot modify structure
'all' - : Cannot add/remove, can reorder
'insert' - : Content edits only
'contentOnly'
- :无限制
false - :无法修改结构
'all' - :无法添加/删除,可重新排序
'insert' - :仅可编辑内容
'contentOnly'
Register in theme.json
在theme.json中注册
json
"customTemplates": [
{
"name": "single-book",
"title": "Book Template",
"postTypes": ["book"]
}
]json
"customTemplates": [
{
"name": "single-book",
"title": "Book Template",
"postTypes": ["book"]
}
]Development Workflow
开发工作流
@wordpress/scripts
@wordpress/scripts
package.json:
json
{
"scripts": {
"start": "wp-scripts start",
"build": "wp-scripts build"
},
"devDependencies": {
"@wordpress/scripts": "^27.0.0"
}
}Commands:
bash
npm install
npm run start # Development with hot reload
npm run build # Production build (minified)package.json:
json
{
"scripts": {
"start": "wp-scripts start",
"build": "wp-scripts build"
},
"devDependencies": {
"@wordpress/scripts": "^27.0.0"
}
}命令:
bash
npm install
npm run start # 开发模式,支持热重载
npm run build # 生产构建(已压缩)wp-env Setup
wp-env配置
.wp-env.json:
json
{
"core": "WordPress/WordPress#6.7",
"phpVersion": "8.3",
"themes": ["./my-block-theme"],
"config": {
"WP_DEBUG": true,
"SCRIPT_DEBUG": true
}
}Usage:
bash
npx @wordpress/env start.wp-env.json:
json
{
"core": "WordPress/WordPress#6.7",
"phpVersion": "8.3",
"themes": ["./my-block-theme"],
"config": {
"WP_DEBUG": true,
"SCRIPT_DEBUG": true
}
}使用方法:
bash
npx @wordpress/env startAccess: http://localhost:8888
Admin: admin / password
后台账号:admin / password
npx @wordpress/env stop
npx @wordpress/env clean # Reset database
undefinednpx @wordpress/env stop
npx @wordpress/env clean # 重置数据库
undefinedMigration from Classic Themes
从传统主题迁移
Template Tag to Block Mapping
模板标签与区块映射
| Classic | Block Equivalent |
|---|---|
| |
| |
| |
| |
| |
| |
| |
| |
| 传统标签 | 对应区块 |
|---|---|
| |
| |
| |
| |
| |
| |
| |
| |
Migration Steps
迁移步骤
- Extract design tokens from style.css → theme.json
- Convert PHP templates to HTML block templates
- Add block support in functions.php:
php
add_theme_support('wp-block-styles');
add_theme_support('align-wide');
add_theme_support('responsive-embeds');- Test thoroughly with real content
- 提取设计标记:从style.css迁移到theme.json
- 转换PHP模板:将PHP模板转为HTML区块模板
- 添加区块支持:在functions.php中添加:
php
add_theme_support('wp-block-styles');
add_theme_support('align-wide');
add_theme_support('responsive-embeds');- 全面测试:使用真实内容进行测试
Block Validation
区块验证
WordPress validates block markup against registered block definitions. Invalid blocks show errors in the editor:
Common validation errors:
- Attribute type mismatch (string vs number)
- Missing required attributes
- Incorrect HTML structure
- Changed attribute names
Fix validation errors:
javascript
// Add deprecated versions for backward compatibility
const deprecated = [
{
attributes: {
oldName: { type: 'string' }
},
migrate: (attributes) => ({
newName: attributes.oldName
}),
save: (props) => {
// Old save function
}
}
];WordPress会根据注册的区块定义验证区块标记。无效区块会在编辑器中显示错误:
常见验证错误:
- 属性类型不匹配(字符串 vs 数字)
- 缺少必需属性
- HTML结构不正确
- 属性名称变更
修复验证错误:
javascript
// 添加旧版本兼容处理
const deprecated = [
{
attributes: {
oldName: { type: 'string' }
},
migrate: (attributes) => ({
newName: attributes.oldName
}),
save: (props) => {
// 旧版保存函数
}
}
];Performance & Best Practices
性能与最佳实践
Performance
性能优化
✅ Use server-side rendering (render.php) when possible
✅ Leverage block supports (reduces custom CSS)
✅ Disable unused features:
✅ Use CSS custom properties for consistency
❌ Avoid client-side rendering for static content
❌ Don't override core blocks with
"defaultPalette": false!important✅ 尽可能使用服务器端渲染(render.php)
✅ 利用区块支持(减少自定义CSS)
✅ 禁用未使用的功能:
✅ 使用CSS自定义属性保证一致性
❌ 静态内容避免使用客户端渲染
❌ 不要用覆盖核心区块样式
"defaultPalette": false!importantAccessibility
可访问性
✅ Semantic HTML (, , )
✅ Keyboard navigation for custom blocks
✅ WCAG AA color contrast (4.5:1 minimum)
✅ Alt text for all images
❌ Don't assume FSE = accessible (test required)
<header><main><footer>✅ 使用语义化HTML(、、)
✅ 自定义区块支持键盘导航
✅ 符合WCAG AA级颜色对比度(最低4.5:1)
✅ 所有图片添加替代文本
❌ 不要假设FSE自带可访问性(需测试)
<header><main><footer>Anti-Patterns
反模式
❌ Mixing classic and block approaches
❌ Hardcoding colors (use CSS variables)
❌ Reinventing block supports
❌ Skipping accessibility testing
❌ Using in HTML templates
get_header()❌ 混合使用传统与区块开发方式
❌ 硬编码颜色(使用CSS变量)
❌ 重复实现区块支持功能
❌ 跳过可访问性测试
❌ 在HTML模板中使用
get_header()Related Skills
相关技能
- wordpress-plugin-fundamentals: Hook system, CPTs
- react: Block editor components
- typescript: Type-safe block development
- php-security: Sanitize block attributes
- wordpress-plugin-fundamentals:钩子系统、自定义文章类型
- react:区块编辑器组件
- typescript:类型安全的区块开发
- php-security:区块属性 sanitize 处理
Key Reminders
关键提醒
- theme.json is mandatory for block themes
- HTML templates replace PHP in FSE
- Server-side rendering often better than client-side
- Block supports reduce custom code
- Accessibility requires testing
- theme.json是区块主题的必需文件
- HTML模板在FSE中替代了PHP模板
- 服务器端渲染通常优于客户端渲染
- 区块支持可减少自定义代码
- 可访问性需要测试验证
Red Flags
警示信号
- More than 5 CSS files → Use theme.json
- PHP tags in HTML templates → Use blocks
- Client rendering for static content → Use render.php
- No keyboard testing → Accessibility issues
- Hardcoded values → Use CSS custom properties
WordPress: 6.7+ | PHP: 8.1+ | Tools: @wordpress/scripts, wp-env
- CSS文件超过5个 → 使用theme.json
- HTML模板中包含PHP标签 → 使用区块
- 静态内容使用客户端渲染 → 改用render.php
- 未进行键盘测试 → 存在可访问性问题
- 使用硬编码值 → 改用CSS自定义属性
WordPress版本: 6.7+ | PHP版本: 8.1+ | 工具: @wordpress/scripts, wp-env