尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Gutenberg 区块序列化默认解析器:@wordpress/block-serialization-default-parser 原理与源码解析
Gutenberg 区块序列化默认解析器wordpress/block-serialization-default-parser 原理与源码解析【免费下载链接】gutenbergThe Block Editor project for WordPress and beyond. Plugin is available from the official repository.项目地址: https://gitcode.com/GitHub_Trending/gu/gutenberg本篇指南聚焦 Gutenberg 仓库中的wordpress/block-serialization-default-parser包它负责把存储在post_content中的区块注释block comment delimiters文档解析成结构化的区块树。读完本文你将理解parse()API 的输入输出契约、递归下降 蹦床trampoline的解析架构、innerHTML/innerContent的索引重建规则以及该包相比 PEG 规范解析器在性能上做了哪些工程取舍。包定位区块序列化管线的“第一遍”解析器该包包含 WordPress 文档的默认区块序列化解析器实现提供原生 PHP 和 JavaScript 两套等价实现实现的是wordpress/block-serialization-spec-parser所定义的解析规范通常作用于文档中存储的post_content字段。仓库内该包的核心文件文件职责src/index.tsJavaScript 解析器实现导出parse函数parser.phpPHP 入口加载三个类文件class-wp-block-parser.phpWP_Block_Parser类PHP 解析器主体class-wp-block-parser-block.phpWP_Block_Parser_Block类内存中的区块结构class-wp-block-parser-frame.phpWP_Block_Parser_Frame类解析栈帧test/index.jsJS 端共享测试入口test/test-parser.phpPHP 端测试辅助脚本与基于 PEGParsing Expression Grammar生成的block-serialization-spec-parser不同这个包是一个手写的、为运行时性能优化的实现它单次线性扫描输入文档把文档切分成原始区块raw block而“区块内部 HTML 内容如何被解释”则交由上层如 packages/blocks/src/api/parser/index.ts处理。安装与环境要求安装模块npm install wordpress/block-serialization-default-parser --save该包假设代码运行在ES2015环境。如果你的运行环境对该类语言特性支持有限或完全不支持应在代码中引入wordpress/babel-preset-default提供的 polyfill。从 package.json 可以确认包的工程约束main为build/index.cjsmodule为build-module/index.mjsexports字段分别声明了import/require/types入口sideEffects: false对打包器的 tree-shaking 友好运行环境要求node 18.12.0、npm 8.19.2devDependencies中引用了wordpress/block-serialization-spec-parserfile:../block-serialization-spec-parser用于跑两套实现共享的测试用例。APIparse 函数函数签名parse是唯一的对外导出函数将输入 HTML 转换为基于区块的结构export const parse ( doc: string ): ParsedBlock[]参数docstring——要解析的 HTML 文档返回值ParsedBlock[]——输入 HTML 的区块化表示。完整示例三列布局文档包文档给出的标准示例输入是一份包含columns与三个column的序列化文档!-- wp:columns {columns:3} -- div classwp-block-columns has-3-columns !-- wp:column -- div classwp-block-column !-- wp:paragraph -- pLeft/p !-- /wp:paragraph -- /div !-- /wp:column -- !-- wp:column -- div classwp-block-column !-- wp:paragraph -- pstrongMiddle/strong/p !-- /wp:paragraph -- /div !-- /wp:column -- !-- wp:column -- div classwp-block-column/div !-- /wp:column -- /div !-- /wp:columns --调用代码与解析结果import { parse } from wordpress/block-serialization-default-parser; parse( post ) [ { blockName: core/columns, attrs: { columns: 3 }, innerBlocks: [ { blockName: core/column, attrs: null, innerBlocks: [ { blockName: core/paragraph, attrs: null, innerBlocks: [], innerHTML: \npLeft/p\n, }, ], innerHTML: \ndiv classwp-block-column/div\n, }, { blockName: core/column, attrs: null, innerBlocks: [ { blockName: core/paragraph, attrs: null, innerBlocks: [], innerHTML: \npstrongMiddle/strong/p\n, }, ], innerHTML: \ndiv classwp-block-column/div\n, }, { blockName: core/column, attrs: null, innerBlocks: [], innerHTML: \ndiv classwp-block-column/div\n, }, ], innerHTML: \ndiv classwp-block-columns has-3-columns\n\n\n\n/div\n, }, ];结合当前源码可以对示例做两点补充说明命名空间默认值wp:column这类未写命名空间的分隔符会被自动补全为core/column逻辑在 src/index.tsconst namespace namespaceMatch || core/;attrs 的实际取值README 示例中无属性块写作attrs: null而当前源码对无属性块写入的是空对象{}见 src/index.ts 的hasAttrs ? parseJSON( attrsMatch ) : {}只有当分隔符中带了属性但 JSON 解析失败时parseJSON才会返回null。ParsedBlock 结构从 src/index.ts 的类型定义看每个解析出的区块包含五个字段字段类型说明blockNamestring \| null区块名如core/paragraph自由格式freeform内容为nullattrsRecordstring, any \| null区块属性无属性时为{}JSON 无效时为nullinnerBlocksParsedBlock[]内部区块列表innerHTMLstring移除内部区块后、分隔符之间的原始 HTMLinnerContentArraystring \| null字符串片段与null标记的交错数组标记内部区块在父块 HTML 流中的插入位置innerContent是一个容易忽视但很关键的字段例如innerHTML为BeforeInnerAfter、innerBlocks有两个区块时innerContent形如[Before, null, Inner, null, After]字段注释见 class-wp-block-parser-block.php它保证区块与静态 HTML 片段的相对位置可逆地还原这也是下游 Custom HTML 区块等场景保持内容原样的基础。解析理论它和 spec-parser 有什么不同包文档的 Theory 章节解释了这一实现的设计动机这里完整继承并结合源码展开。递归下降 蹦床而非直接递归这是一个递归下降解析器对输入文档做一次线性扫描。它不做直接递归而是利用蹦床trampoline机制防止栈溢出同时通过“全局”变量JS 端的模块级document/offset/output/stackPHP 端的类属性跟踪解析状态最小化数据拷贝与传递。每个 token区块注释分隔符之间都可以插入观测点便于设置解析时长硬上限或输出调试诊断信息。JS 端的蹦床体现在 src/index.tsexport const parse ( doc: string ): ParsedBlock[] { document doc; offset 0; output []; stack []; tokenizer.lastIndex 0; do { // twiddle our thumbs } while ( proceed() ); return output; };parse本身不递归proceed()每消费一个 token 就返回布尔值决定是否继续——嵌套再深的文档也不会增长调用栈。PHP 端是完全对应的结构见 class-wp-block-parser.php 的while ( $this-proceed() ) { continue; }。spec-parser 由 PEG 定义很多判断是隐式的而本实现的目标是匹配 PEG 的行为特征使得两者可以直接互换唯一变化是更好的运行时性能与内存占用。状态机只有两种转移每个序列化后的 Gutenberg 文档名义上是一个 HTML 文档其中混入了特制的 HTML 注释——区块注释分隔符——用于分隔和隔离各区块。解析器围绕这些分隔符触发的转移构建了一个状态机每发现一个 token只做两件事之一进入enter一个新区块退出exit一个区块。退出时的行为取决于上下文要么把区块加入顶层输出列表要么把它作为innerBlocks追加到栈中比它低一层的父区块上栈用于跟踪所有“打开中”的区块。四种 token 类型及其处理从 src/index.ts 看token 被归为四种类型proceed()的 switch 分支src/index.ts即状态机的全部行为token 类型含义处理逻辑block-opener区块开始如!-- wp:column --构造区块对象连同 token 位置信息压入栈stackblock-closer区块结束如!-- /wp:column --栈深为 1 时弹出并追加尾部 HTML 后输出嵌套时弹出、补全父块 HTML 后作为父区块的 innerBlockvoid-block自闭合区块如!-- wp:paragraph /--顶层直接输出嵌套时直接addInnerBlock无需压栈no-more-tokens无更多 token栈空则 flush 剩余 freeform 内容否则按错误恢复处理一个巧妙的工程细节是开/闭分隔符之间唯一的区别是wp:前有没有/且闭包不带属性因此用同一个正则同时捕获两者匹配后再在语言层判断类型见 src/index.ts 中nextToken()的注释与实现。错误恢复尽力解析而非报错文档明确说明与规范解析器不同这个解析器在非法输入上不返回错误而是返回尽力而为best-effort的解析结果PHP 端parse()的 docblock 也如此声明见 class-wp-block-parser.php。源码中的具体策略缺少闭合分隔符no-more-tokens时栈非空假设隐式闭合。栈深为 1 时直接弹栈输出更深的嵌套则“逐块折叠”整个栈while ( 0 stack.length ) { addBlockFromStack(); }见 src/index.ts出现孤立的闭合分隔符栈深为 0放弃解析把剩余内容作为 freeform 块收尾addFreeform()见 src/index.ts分隔符前的 HTML “汤”leadingHtmlStart在下一个 token 与当前 offset 之间存在的游离 HTML 会被切出作为blockName: null的 freeform 块单独输出JS 端Freeform()构造器见 src/index.ts。innerHTML 的索引重建规则文档指出该解析器最大的挑战是在每一层嵌套深度上都要正确地记账索引才能构造出每个区块的innerHTML。它采用的规则是每个新打开的区块以空innerHTML开始向innerBlocks列表压入第一个区块时把“父区块内容开始处”到“该内部区块开始处”之间的内容加进去向innerBlocks列表压入后续区块时把“上一个内部区块结束处”到“该区块开始处”之间的内容加进去关闭一个打开中的区块时把“最后一个内部区块结束处”到“闭合分隔符开始处”之间的内容加进去如果没有内部区块则取开闭分隔符之间的全部内容作为innerHTML。对应源码实现集中在三处addInnerBlocksrc/index.ts负责规则 2、3addBlockFromStacksrc/index.ts负责规则 4空块初始状态实现规则 1规则 5 由“无 innerBlocks 时prevOffset到结束位置一次截取”自然成立。栈帧 ParsedFramePHP 端为 WP_Block_Parser_Frame保存记账所需的全部位置信息tokenStarttoken 起点、tokenLengthtoken 长度、prevOffset上一个 token 之后、即下一次截取 HTML 的起点、leadingHtmlStarttoken 前游离 HTML 的起点。性能设计为什么比 PEG 生成的解析器更快文档的 “how does it perform” 章节给出的核心结论是这个解析器运行得比规范生成的解析器快得多因为它“比 PEG 更了解解析对象”可以利用若干技巧token 语言极其简单本质上只有一两种 token开/闭分隔符全部可以用一个正则匹配。不必逐字符解析而是让正则引擎替我们跳过大段文档直接定位 token只传偏移量不传子串PHP 的preg_match()接受offset参数因此可以在输入上“爬行”而不需要在每一步传递输入文本的拷贝只需跟踪位置并传一个数字不拷贝字符串意味着更少的内存分配。进一步用正则做 token 化还有额外好处PEG 生成的解析器以可预测的性能换来了对 token 化规则的控制——它不允许在规则里定义正则从而防止灾难性回溯cataclysmic backtracking破坏 PEG 的线性保证。而区块注释分隔符的“token 语言”本身是正则语言可以平凡地用正则匹配于是解析器直接“跳出” PHP/JS 虚拟机进入宿主系统上以 C/C 编写的高度优化的正则引擎绕开虚拟机开销。反灾难性回溯的两种实现这是实现中最有含金量的部分两个语言用了不同手法JavaScript 端用“前瞻 反向引用”技巧模拟 JS 正则缺失的独占量词/原子组。src/index.ts 的注释给出了具体推导(a)*c ((?(a))\1)*c即/(?(a))\1/会先匹配一长串字符但“不捕获”它再以整体单位在模式中引用从而禁止引擎在失败时把它拆散回溯。注释中的实测对比/(a)*c/.test(aaaaaaaaaaaaad)失败前跑了49,000步/(a)*c独占量词失败前只跑 85 步/(?a)*c原子组失败前跑 126 步而前瞻反向引用的写法得到与原子组相同的防回溯行为。由此得到 JS 端的 tokenizer 正则src/index.ts/!--\s(\/)?wp:([a-z][a-z0-9_-]*\/)?([a-z][a-z0-9_-]*)\s({(?:(?([^}]|}(?})|(?!}\s\/?--)[^])*)\5|[^]*?)}\s)?(\/)?--/gPHP 端则直接使用 PCRE 支持的独占量词*该修复自 4.6.1 引入docblock 注明 “fixed a bug in attribute parsing which caused catastrophic backtracking on invalid block comments”正则见 class-wp-block-parser.php$has_match preg_match( /!--\s(?Pcloser\/)?wp:(?Pnamespace[a-z][a-z0-9_-]*\/)?(?Pname[a-z][a-z0-9_-]*)\s(?Pattrs{(?:(?:[^}]|}(?})|(?!}\s\/?--).)*)?}\s)?(?Pvoid\/)?--/s, $this-document, $matches, PREG_OFFSET_CAPTURE, $this-offset );注意PREG_OFFSET_CAPTURE与末尾的$this-offset参数——正是文档所述“只传偏移、不拷贝字符串”的落地。PHP 端还有一层保险若preg_match返回false意味着 PCRE 内部发生灾难性回溯或内存耗尽直接把该 token 当作no-more-tokens收尾而不是崩溃class-wp-block-parser.php。双端一致性共享测试套件该包如何保证 JS 与 PHP 两套实现行为一致答案在 test/index.js它从wordpress/block-serialization-spec-parser/shared-tests导入jsTester与phpTester对同一套共享用例分别驱动 JS 的parse与 PHP 端脚本import { jsTester, phpTester, } from wordpress/block-serialization-spec-parser/shared-tests; import { parse } from ../src; describe( block-serialization-default-parser-js, jsTester( parse, testRunner ) ); phpTester( block-serialization-default-parser-php, fileURLToPath( new URL( ./test-parser.php, import.meta.url ) ), testRunner );其中 test/test-parser.php 是一个极简的 PHP 测试辅助脚本从 stdin 读入文档、调用( new WP_Block_Parser() )-parse( ... )、把结果json_encode后打印。也就是说规范解析器包充当“事实裁判”默认解析器的两个语言实现都必须通过同一份行为测试——这正是文档中“可以直接互换”承诺的验证手段。另外注意 PHP 端的一个核心兼容细节WP_Block_Parser::parse()自 4.0.0 起返回数组而非对象所有属性均为数组输出时通过(array)转换docblock 标注 “Required for backward compatibility in WordPress Core”见 class-wp-block-parser.php保证与 WordPress 核心中wp_parse_blocks()依赖的数据形状兼容。在 Gutenberg 区块管线中的位置单独使用本包你得到的是“原始区块”raw blocks——只有结构没有属性提取与校验。它在上层管线中的调用方式见 packages/blocks/src/api/parser/index.tsimport { parse as grammarParse } from wordpress/block-serialization-default-parser; export default function parse( content: string, options?: ParseOptions ): Block[] { return grammarParse( content ).reduce( ( accumulator: Block[], rawBlock ) { const block parseRawBlock( rawBlock as unknown as RawBlock, options ); if ( block ) { accumulator.push( block ); } return accumulator; }, [] as Block[] ); }其 JSDoc 对本包职责的概括与 README 的 Theory 章节完全一致“a recursive-descent parser that scans linearly once through the input document … This initial pass is mainly interested in separating and isolating the blocks serialized in the document andmanifestly not in the content within the blocks.”parseRawBlock在此之上完成后续加工freeform 内容的autop段落化、旧区块名与属性的遗留转换、未注册区块类型的兜底处理保留originalContent/originalUndelimitedContent、从innerHTML中二次提取属性、基于save的校验与内置自动修复、以及 deprecation 迁移。理解这层分工后阅读源码时就不会混淆本包只负责“按分隔符切块 位置记账”其余语义处理都不在这里。小结wordpress/block-serialization-default-parser是 Gutenberg 区块内容加载链路的性能关键件它用一个正则完成 token 化把匹配工作下放到宿主系统的 C/C 正则引擎用蹦床式状态机替代递归嵌套深度与调用栈完全解耦用偏移量记账tokenStart/tokenLength/prevOffset/leadingHtmlStart零拷贝地重建每一层innerHTML与innerContent对非法输入采取尽力解析策略隐式闭合、游离 HTML 归入 freeform 块保证存量内容可打开通过 spec-parser 的 shared-tests 保证JS/PHP 双实现与规范的一致性。如需继续深入可以从 包文档、JS 实现 src/index.ts 与 PHP 实现 class-wp-block-parser.php 出发再对照 packages/block-serialization-spec-parser 中的 PEG 定义理解两种实现“行为等价、性能取向不同”的分工。【免费下载链接】gutenbergThe Block Editor project for WordPress and beyond. Plugin is available from the official repository.项目地址: https://gitcode.com/GitHub_Trending/gu/gutenberg创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED

相关推荐

Feast Operator 实战(七):用 OpenLineage 实现数据血缘追踪与 Materialization 物化调优

Feast Operator 实战(七):用 OpenLineage 实现数据血缘追踪与 Materialization 物化调优

Feast Operator 实战(七):用 OpenLineage 实现数据血缘追踪与 Materialization 物化调优 【免费下载链接】feast The Open Source Feature Store for AI/ML 项目地址: https://gitcode.com/GitHub_Trending/fe/feast 本指南基于 Feast…

📅 2026/9/17 2:25:42
基于微信小程序与Java后端的家庭理财管理系统全解析

基于微信小程序与Java后端的家庭理财管理系统全解析

简介:基于微信小程序构建的家庭理财管理系统毕业设计项目,面向计算机相关专业学生、Java后端开发者以及需要快速搭建理财类小程序demo的人群。系统围绕家庭收支核心场景,实现用户注册登录、工资管理、记账本管理、贷款管理、管理员后台等模块…

📅 2026/9/17 2:25:42
radix-vue 中 ColorSwatchPickerItemIndicator 组件详解:选中色块的指示器渲染与定制

radix-vue 中 ColorSwatchPickerItemIndicator 组件详解:选中色块的指示器渲染与定制

radix-vue 中 ColorSwatchPickerItemIndicator 组件详解:选中色块的指示器渲染与定制 【免费下载链接】radix-vue An open-source UI component library for building high-quality, accessible design systems and web apps for Vue. Previously Radix Vue 项目地…

📅 2026/9/17 2:20:42
MORE NEWS

更多资讯

📰

MySQL数据出海:跨地域同步方案选型与避坑实践

“MySQL 数据出海”,这个题目我盯了挺久。业务做到海外,服务器分散在不同区域,数据库却还窝在家里当孤岛,这种痛我太熟了。数据同步这件事,往小了说是一张订单表没同步,往大了说就是业务方天天来问“为什么…

📰

微信小程序源码拆解:从工程结构、数据绑定到GIF动效优化

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

2.8T MoE大模型部署实践:显存工程、量化与分布式切分

1. 这题不是“塞不塞得下”,而是一道显存工程的综合题一个朋友问了我一个听起来特别离谱的需求:要把 2.8T 总参数量的 Kimi K3 部署到 32 张 H20 上。我第一反应是疯了吧,2.8T 参数用 BF16 存,光权重就得 5.6TB,32 张 …

📰

Kubernetes 1.35.1二进制部署全攻略:从证书到CNI网络

直接从头开始给您写一篇完全符合要求的博文。整个正文将围绕 Kubernetes 1.35.1 二进制部署展开,内容全部链路、原理、步骤和排错都会写进去,直接可用。1. 为什么我坚持用二进制方式部署 Kubernetes 1.35.1先说结论:如果你只是想快速搞一套开…

📰

从四肢竞赛到大脑竞赛:世界模型如何重塑具身智能与机器人导航

“具脑磐石”这四个字先读懂:行业正在从“四肢竞赛”转向“大脑竞赛”第一次看到《世界模型50人》里朱森华这篇访谈的标题时,我愣了一下。“具脑磐石”这个栏目名起得挺妙,谐音“具身智能”,落点却在一个“脑”字上。这几年机器人…

📰

Java后端消息队列实战:RabbitMQ在分布式架构中的落地与部署

做Java后端开发这些年,我越来越觉得消息队列是绕不开的一块硬骨头。尤其是当你在简历上写过“熟悉分布式架构”之后,面试官大概率会追问:RabbitMQ 在你的项目里到底扮演什么角色?消息丢了怎么办?重复消费怎么解决&…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬