不能用 Babel 或 TypeScript 编译器处理 HTML,因其解析器不支持 HTML 语法且 AST 缺失 HTML 特有字段;应选 parse5(语义保真)或 cheerio(便捷但不校验),Vue/Angular 模板需专用编译器;修改 class 等属性须安全合并,自闭合标签需显式设置 isSelfClosing,遍历时需区分节点类型,替换逻辑需明确定义结构一致性标准。

为什么不能用 Babel 或 TypeScript 编译器处理 HTML
因为 @babel/parser 遇到 <div> 会直接抛出 SyntaxError: Unexpected token <;TypeScript 编译器只接受 .ts/.tsx,且其 AST 不包含 tagName、attrs 这类 HTML 特有字段。硬塞 HTML 字符串进去,解析阶段就失败——不是配置问题,是设计边界问题。
常见错误包括:在 Webpack 插件里调用 babel.transformSync(htmlString),或误以为 ts.createSourceFile 能加载 HTML 文件。这些操作根本不会进入 AST 遍历环节,连“重构”两个字都谈不上。
该用哪个 HTML 解析器做 AST 重构
选 parse5 还是 cheerio,取决于你要不要浏览器兼容性语义:
-
parse5严格遵循 HTML5 标准,能正确处理<table>嵌套、自动闭合规则(如把<p>hello<div>world</div>补全为两个<p>),适合需要语义保真的批量重构 -
cheerio接口类似 jQuery,写起来快,但不校验嵌套合法性,<ul><div></ul>也能 parse 成功,后续生成的 HTML 可能在真实浏览器中被重排 - 如果项目含 Vue 模板,必须用
@vue/compiler-dom;含 Angular 模板,则用@angular/compiler——它们暴露的 AST 才带v-if、*ngIf等指令节点
替换 class 属性时最容易踩的坑
直接覆盖 class 属性值会丢掉原有 class,比如把 class="btn primary" 改成 class="btn secondary",就丢了 primary。
立即学习“前端免费学习笔记(深入)”;
安全做法是用工具函数提取并合并:
- 用正则
/\s+/g拆分再去重,比手动拼字符串可靠 -
parse5的attrs是数组,不是对象,修改需重建整个attrs数组:[...oldAttrs.filter(a => a.key !== 'class'), { key: 'class', value: newClassStr }] - 遇到空格/换行/制表符混排的 class 值(如
class=" btn \n primary "),先.trim().split(/\s+/).filter(Boolean)再合并
自闭合标签和序列化一致性问题
把 <img src="x"> 替换成 <picture>...</picture> 时,若新节点没设 isSelfClosing: false,parse5.serialize() 可能输出 <picture/>——这是非法 HTML。
关键点:
-
parse5中自闭合标签靠isSelfClosing字段控制,不是靠标签名自动推断 - 手动创建节点时,
t.element({ tagName: 'img', isSelfClosing: true })必须显式声明 - 遍历子节点时,
node.nodeName === '#text'和node.nodeType === 1(Element)要分开判断,否则对文本节点调用getAttribute会报错
真正难的不是遍历树,而是定义“什么算一次安全替换”:是否保留原始缩进?属性顺序要不要标准化?<input type="text"> 和 <input type = "text"> 是否视为同一结构?这些边界不厘清,自动化就容易产出合法但风格割裂的 HTML。



















