结论是别从零写HTML模板组件解析器,应基于gumbo-parser或htmlparser2构建轻量级模板语义层;因DOMParser会执行脚本、加载资源,正则无法处理嵌套、转义与注释等复杂场景,而gumbo-parser提供源位置与结构化节点,htmlparser2支持流式事件驱动编译。

直接说结论:别从零写 HTML 模板组件解析器,用 gumbo-parser 或 htmlparser2 做底层解析,再叠加轻量级模板语义层——这是目前最可控、最易维护的路径。
为什么不用原生 DOM API 或正则硬解析?
原生 DOMParser 会执行脚本、加载外部资源、触发样式计算,完全不适合模板解析场景;而正则匹配 {{…}} 或 v-if 类指令,在嵌套、转义、注释块里极易漏判或误判。比如:
-
<div>{{ text }}<!-- {{ comment }} --></div>中的注释内容会被正则误当作模板变量 -
<script>console.log("{{ raw }}");</script>里的双大括号不该被提取 - 多层嵌套如
v-for="item in list" v-if="item.active"需要属性顺序与作用域判断,正则无法建模
如何用 gumbo-parser 提取并标记模板指令节点?
gumbo-parser 不自带模板支持,但它的 DOM 树保留了原始文本位置(node->source_position)和完整标签结构,足够你做安全的指令定位。关键点是:只扫描 GUMBO_NODE_ELEMENT 节点的属性,跳过 script、style 和注释节点。
- 遍历前先过滤掉
GUMBO_TAG_SCRIPT、GUMBO_TAG_STYLE、GUMBO_NODE_COMMENT节点 - 对每个元素节点,检查
node->v.element.attributes中是否存在以v-、@、:开头的属性名(如v-if、@click) - 提取时保留
attribute->original_value(原始字符串),而非attribute->value(已解码值),避免丢失转义逻辑 - 不要修改
output->root树本身——它由gumbo-parser管理内存,需用gumbo_destroy_output统一释放
htmlparser2 怎么配合流式模板编译?
htmlparser2 的事件流天然适合边解析边编译,尤其适合构建类似 Vue SFC 或 Astro 的“模板 + 脚本 + 样式”分块处理流程。它的 onopentagname、onattribute、ontext 可直接映射为 AST 节点生成事件。
立即学习“前端免费学习笔记(深入)”;
- 在
onopentagname触发时,立即检查是否为自定义组件标签(如my-button),并启动组件作用域上下文 -
onattribute中识别bind:、on:等前缀,将其转换为绑定描述符对象,而非简单字符串 -
ontext返回的文本需结合当前上下文判断是否处于插值区域——例如在<span>{{ msg }}</span>中,msg是表达式;但在<pre>{{ raw }}里可能只是纯文本 - 务必启用
xmlMode: false和decodeEntities: false,否则会被提前转义,破坏后续表达式解析
最容易被忽略的边界:模板字符串嵌套与编码一致性
几乎所有失败案例都卡在两件事上:一是把 UTF-8 编码的 HTML 字符串传给 gumbo_parse 却没确保输入 buffer 真的是 UTF-8(Windows 上常见 ANSI 乱码);二是模板中出现 JS 字符串字面量,比如 v-bind:title="'{{ name }}'",此时外层单引号内的双大括号不是模板语法,而是 JS 字符串内容——必须依赖 AST 层级识别,不能只靠文本扫描。



















