Gutenberg Custom HTML(core/html)块深度解析:从 block.json 到编辑渲染的完整实现
2026/9/17 1:23:27 网站建设 项目流程

Gutenberg Custom HTML(core/html)块深度解析:从 block.json 到编辑渲染的完整实现

【免费下载链接】gutenbergThe Block Editor project for WordPress and beyond. Plugin is available from the official repository.项目地址: https://gitcode.com/GitHub_Trending/gu/gutenberg

导读

本文基于 Gutenberg 插件仓库中 Custom HTML 块的官方文档 及其完整源码实现,深入剖析core/html这一核心静态块的元数据定义、属性与支持能力、序列化标记结构,以及它的编辑体验、代码转换与权限控制机制。读完本文,你将掌握 Custom HTML 块的完整工作方式,理解其“静态块 + inner content”的存储模型,并能据此排查二次开发中遇到的序列化、权限与预览相关问题。

一、Custom HTML 块是什么

Custom HTML(core/html)是 Gutenberg 内置的“自定义 HTML”块,官方定位一句话即可概括:添加自定义 HTML 代码,并在编辑时实时预览效果(Add custom HTML code and preview it as you edit)。它属于widgets(小工具)分类,块类型为静态块(Static block)——这意味着它的标记会以原始 HTML 的形式直接保存在文章内容中,而不是像动态块那样由服务端渲染函数在输出时生成。

从 block.json 可以看到它的元数据全貌:

元数据字段说明
namecore/html块的唯一注册名,出现在注释分隔符中
titleCustom HTML块在编辑器中的显示名称
categorywidgets归入小工具分类
descriptionAdd custom HTML code and preview it as you edit官方描述
apiVersion3使用第 3 版块 API
keywords["embed"]搜索关键词
editorStylewp-block-html-editor编辑器专用样式句柄
textdomaindefault文本域

在 index.js 中,块通过initBlock完成注册,并配置了图标、示例内容与编辑/保存/转换实现。值得注意的是它的示例内容是一个<marquee>标签——这是块作者用来展示“可以放入任意 HTML”的经典演示片段。

二、属性(Attributes):唯一的contentrole: local语义

按照块 API 规范,属性通过 block.json 中的attributes字段定义。Custom HTML 块只有一个属性:

属性类型默认值说明
contentstringRole:local

contentrole: local非常关键:它表示该属性是编辑器本地状态,不会作为 JSON 序列化进块注释分隔符中。这一点在 test/index.jsdom.test.js 中有专门的测试断言:

it( 'keeps the content attribute out of the block delimiter', () => { const block = createBlock( 'core/html', { content: '<marquee>Hello</marquee>', } ); // `role: 'local'` prevents the attribute from being written into // the comment delimiter as JSON. expect( serialize( block ) ).not.toContain( '{"content"' ); } );

历史遗留:content属性的迁移逻辑

从源码注释与 edit.jsx 可以看到一段重要的兼容性设计:早期版本的 Custom HTML 块把标记存放在content属性中,而现在的实现把标记迁移到了块的inner content(内部内容片段)中。为了兼容旧的程序化创建方式(例如createBlock( 'core/html', { content } )),块在加载时一旦发现attributes.content存在,就会:

  1. 通过deprecated()输出废弃警告(自 7.1 版本起,建议改用 inner content);
  2. updateBlockattributes.content中的标记写入innerContent,同时清除content属性。

这正是“不丢数据”的渐进式迁移策略:旧内容自动升级到新存储模型,而不会在版本升级时丢失。

三、Supports 能力矩阵:为何它如此“克制”

Supports 决定了编辑器为该块提供哪些通用能力(类名、锚点、可见性开关等)。Custom HTML 块的 supports 配置相当克制,block.json 中定义如下:

Supports 项影响
customClassNamefalse不允许用户在额外 CSS 类中自定义类名
classNamefalse不自动注入.wp-block-html
htmlfalse不提供“以 HTML 方式编辑”的块级切换
interactivity.clientNavigationtrue支持客户端导航(在交互式前端路由中保留)
listViewtrue在列表视图中可见
customCSSfalse不提供自定义 CSS 支持
visibilityfalse不提供块可见性控制

这些“关闭”项是有意为之:Custom HTML 块的定位就是把原始 HTML 原样交还给用户,如果套上自动类名或自定义 CSS 包装,反而会污染用户输入的标记。这种“少即是多”的设计,与core/paragraph等富文本块形成鲜明对比。

四、块标记(Block Markup):静态块的序列化模型

由于是静态块,Custom HTML 的标记会原样写入文章内容,以<!-- wp:html --><!-- /wp:html -->注释分隔符包裹。文档给出了典型示例:

<!-- wp:core/html --> <h1>Some HTML code</h1> <div>This is a div</div> <!-- /wp:core/html -->

源码层面的真相:save()返回null

值得深入说明的是,save.js 的实现只有一行:

// The block's markup is serialized from its `innerContent` (static HTML // fragments interleaved with inner blocks), not from a save implementation. export default function save() { return null; }

也就是说,Custom HTML 块的标记并非由save()函数生成,而是直接取自块的innerContent——即“静态 HTML 片段与内部块(inner blocks)交错排列”的数组。这正是 test/index.jsdom.test.js 中验证的行为:当 innerContent 为['<div>', null, '</div>']且内嵌一个段落块时,序列化结果是:

<!-- wp:html --> <div><!-- wp:paragraph --> Editable <!-- /wp:paragraph --></div> <!-- /wp:html -->

这意味着 Custom HTML 块内部可以嵌套其他块——用户在 HTML 代码里书写的<!-- wp:* -->分隔符会被解析为真实的内部块,这些块仍然可编辑,而其余部分保持为静态 HTML 片段。

五、编辑体验:占位符、代码弹窗与实时预览

Custom HTML 的编辑界面由 edit.jsx 驱动,整体流程分三种状态:

  1. 空内容占位状态:当内容为空时,显示带“Custom HTML”标签和“Edit HTML”主按钮的Placeholder占位符,点击后弹出编辑弹窗(HTMLEditModal)。
  2. 正常编辑状态:通过InnerContent(来自 block-editor 私有 API)渲染已解析的标记;工具栏(BlockControls)提供“Edit code”按钮,右侧检查器(InspectorControls)也提供同名的“Edit code”次按钮。
  3. 弹窗编辑:点击任一“Edit code”都会打开 modal.jsx 中的HTMLEditModal

编辑弹窗的三栏式设计

编辑弹窗是本块最富特色的部分,它在一个大型Modal中提供:

  • Tab 页签:HTML / CSS / JavaScript 三个页签(CSS 与 JavaScript 页签仅在用户拥有 unfiltered HTML 权限时显示);
  • 分栏布局:左侧是带等宽字体、LTR 方向(无论语言设置如何,HTML 代码始终从左到右书写)的代码编辑区,右侧是实时预览区;窄屏(移动端视口)自动切换为上下布局;
  • 全屏切换:桌面端提供全屏按钮(fullscreen/square图标切换);
  • 原生撤销:通过useNativeUndo保留浏览器自身的撤销/重做行为——因为弹窗内字段的本地状态只在点击“Update”时才提交给块,期间的撤销必须交给浏览器;
  • 底栏按钮:Cancel 与 Update(点击 Update 提交并关闭弹窗)。

预览是如何实现的

预览组件在 preview.jsx 中实现,核心是@wordpress/componentsSandBox——一个沙箱化的 iframe。它做了两件事:

  1. 注入一组DEFAULT_STYLES,清除编辑器可能继承给预览内容的边距/内边距等样式;
  2. 通过transformStyles把编辑器全局样式设置(getSettings().styles)转换后注入预览 iframe,使预览尽可能接近真实前台效果。

此外,当块未被选中时,会渲染一个.block-library-html__preview-overlay透明覆盖层。源码注释解释了原因:部分浏览器不会把沙箱 iframe 内的点击事件冒泡出来,这个覆盖层保证用户在预览区域点击时能重新选中该块。

权限与内容剥离:unfiltered HTML 检查

编辑弹窗通过settings.__experimentalCanUserUseUnfilteredHTML判断用户是否拥有unfiltered_html权限(通常仅管理员/编辑者具备)。这一判断直接影响两处行为:

  • CSS 与 JavaScript 页签是否显示;
  • 提交内容时是否保留 CSS/JS 段。

在 modal.jsx 的handleUpdate中可以看到:没有该权限的用户,其 CSS 和 JS 段会在保存时被剥离,只保留 HTML。同时,若块内原本就含有 CSS/JS 而用户无权限,弹窗顶部会显示一条不可关闭的警告 Notice:这些内容将在保存时被移除。这一设计是为了避免内容经过服务端 kses 过滤后留下残缺标记——与其让用户看到被截断的半成品,不如在编辑器内主动剥离并明示。

六、CSS / JS 的分段存取:parseContentserializeContent

编辑弹窗把 HTML、CSS、JS 分成三个独立编辑区,这背后依赖 utils.js 中两个对称的工具函数:

  • parseContent( content ):解析合并内容字符串。它创建一个临时 HTML 文档(document.implementation.createHTMLDocument),从中提取带data-wp-block-html="css"标记的<style>标签和带data-wp-block-html="js"标记的<script>标签,其余内容归为 HTML。注意:未带标记的<style>/<script>标签不会被提取,它们按普通 HTML 处理;多个同类型标记标签只取第一个。
  • serializeContent( { html, css, js } ):反向操作,按“CSS → JS → HTML”的顺序拼接,并为 CSS/JS 段重新加上data-wp-block-html标记;空白段会被忽略。

两者配合实现无损的往返(round-trip)序列化,这在 test/utils.jsdom.test.js 中有完整验证——包括空内容、纯 CSS、纯 JS、三段俱全、畸形 HTML 容错、多标记标签取首个、空白裁剪等十余个用例。这套分段机制的价值在于:编辑器把 CSS/JS 与 HTML 分开管理,权限检查与内容剥离只需针对标记段进行,不会误伤用户写在 HTML 里的<style>/<script>标签。

七、编辑时内容如何回流:onUpdate的再解析机制

当用户在弹窗点击“Update”,edit.jsx 的onUpdate会执行一次完整的“再解析”流程:

  1. parse()把新内容包进<!-- wp:html -->分隔符后解析,得到parsedBlock
  2. 静态 HTML 部分成为块的innerContent片段,<!-- wp:* -->分隔段则成为内部块;
  3. 通过serialize()逐块比较新旧内部块:若内部块标记未变化(例如只改了周边的静态 HTML),则保留原有内部块及其 clientId 与选中状态,避免编辑静态代码时无辜重置内部块;
  4. 所有更新放在registry.batch()中批量执行,保证updateBlockreplaceInnerBlocks原子性生效。

这套机制保证了“静态 HTML 与可编辑内部块共存”模型的健壮性:用户在弹窗里编辑的是整体源码,但提交后,其中被识别为块的部分依然保持为真正可交互的嵌套块。

八、块转换:从core/code一键转为 Custom HTML

transforms.js 定义了块的转换规则:支持从core/code(代码块)转换为 Custom HTML。转换逻辑值得细读:

const text = create( { html } ).text; const [ block ] = parse( `<!-- wp:html -->\n${ text }\n<!-- /wp:html -->` ); return block ?? createBlock( 'core/html', {}, [], [ text ] );

流程是:先把代码块的 HTML 内容转成纯文本(@wordpress/rich-textcreate().text),再包上wp:html分隔符重新解析。这样做的目的是:如果代码块里恰好含有<!-- wp:paragraph -->之类的块分隔符文本,它们会被还原为可编辑的真实内部块,而不是变成惰性注释文本。若解析失败则退化为仅含单个静态文本片段的core/html块。

test/index.jsdom.test.js 用两个用例验证了这一行为:包含分隔符的代码块转换后内部多出一个段落块(innerContentnull占位),而纯静态 HTML 的代码块转换后内部块数量为 0。

九、注册与初始化:init.jsinitBlock

块的初始化入口在 init.js:

import { init } from './'; export default init();

index.js中的init调用initBlock({ name, metadata, settings })(来自 ../utils/init-block),完成registerBlockType注册。Custom HTML 块的完整源码目录结构如下,方便读者继续深入:

  • block.json:元数据定义
  • edit.jsx:编辑组件
  • modal.jsx:编辑弹窗
  • preview.jsx:沙箱预览
  • save.js:序列化逻辑(返回 null,标记来自 innerContent)
  • transforms.js:块转换
  • utils.js:HTML/CSS/JS 分段工具
  • editor.scss:编辑器样式
  • test/index.jsdom.test.js 与 test/utils.jsdom.test.js:行为与工具函数测试

十、前端渲染与安全边界:理解“原样输出”的双刃剑

最后需要明确 Custom HTML 块的运行边界:

  • 静态存储:块标记保存在文章内容中,由 WordPress 前端原样输出,不存在服务端渲染回调;
  • 权限防线:是否允许在块内保存<script>/<style>(以及 CSS/JS 页签是否可用)完全取决于用户的unfiltered_html能力,无权限用户保存时 CSS/JS 段会被主动剥离,以避免 kses 过滤产生残缺标记;
  • 编辑期沙箱:预览通过SandBoxiframe 隔离运行,避免编辑器环境与预览内容互相干扰;
  • 嵌套块能力:静态 HTML 中嵌入的<!-- wp:* -->分隔符会被解析为内部块,实现“整体不可分割、局部仍可编辑”的混合模型。

因此在实际使用中,Custom HTML 块适合有经验的开发者放置第三方嵌入代码、统计脚本或定制标记;对于普通内容作者,应优先使用官方块并借助权限体系控制谁能写入脚本类内容。

总结

本文从 官方块文档 出发,结合 block.json 与 edit.jsx、modal.jsx、save.js、transforms.js、utils.js 等源码文件,完整还原了 Custom HTML 块的设计与实现:content属性的role: local语义与迁移逻辑、克制的 supports 配置、基于 innerContent 的静态序列化模型、三栏式编辑弹窗与沙箱预览、unfiltered HTML 权限下的 CSS/JS 剥离机制,以及从core/code的智能转换。理解这些内部机制,无论对插件开发、块扩展还是内容架构设计,都能提供扎实的底层依据。

【免费下载链接】gutenbergThe Block Editor project for WordPress and beyond. Plugin is available from the official repository.项目地址: https://gitcode.com/GitHub_Trending/gu/gutenberg

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询