自动化重写必须基于AST而非正则,parse5是唯一合规起点;class重写需安全提取去重重组;CI卡点应拦截新增非语义标签;iframe和模板语法须跳过;真正难点在于将可访问性规则嵌入开发流程。

自动化重写不是“批量查找替换”,而是用 AST 精准干预 DOM 结构——不理解这点,所有工具链都会在第一次上线后炸掉生产页面。
为什么 parse5 + tree-walker 是唯一靠谱的 HTML 重写起点
正则匹配 <div class="header"> 会同时命中 <div class="header">、<div class="header navbar-fixed">、甚至注释里的 <!-- <div class="header"> -->;Babel 或 TypeScript 解析器压根不认 .html 文件,transformSync() 直接抛 SyntaxError: Unexpected token <。只有 parse5 这类符合 HTML5 标准的解析器,能把字符串转成带父子关系、属性列表、命名空间的完整 AST。
实操建议:
- 用
parse5.parseFragment()而非parse5.parse()处理片段(如模板 include),避免强制补全<html>外壳导致结构错位 - 遍历节点时优先用
treeAdapter.getChildren(node),别依赖node.childNodes—— 后者在某些 treeAdapter 下可能为空 - 修改
node.attrs必须重建整个数组:node.attrs = node.attrs.map(...),直接改attr.value不生效 - 对含内联 JS 的节点(如
<div onclick="doX()">),先加data-legacy-js属性标记,不删逻辑,留待后续解耦
class 属性重写最容易踩的三个边界坑
把 class="btn primary" 替成 class="button primary" 看似简单,但硬拼字符串或正则会漏掉:换行分隔(class="btn\nprimary")、多余空格(class=" btn ")、重复值(class="btn btn")、引号不统一(class='btn')。这些在真实遗留代码里高频出现。
立即学习“前端免费学习笔记(深入)”;
正确做法是提取、去重、重组:
- 用
/\s+/切分,再.filter(Boolean)去空字符串,别用.split(' ') - 更新前检查原值是否包含目标 class:
classes.includes(oldClass),而非classAttr.value.includes(oldClass)(防子串误判) - 写回时用
updated.join(' '),保留单空格分隔,不引入制表符或双空格 - 若旧 class 是复合名(如
section-nav),确认新 class(如nav-section)在 CSS 中已存在,否则样式立即断裂
重构后如何防止新提交倒退成老样子
人工改完 20 个 <div class="main">,下次 PR 又冒出 15 个,说明没建防线。CI 卡点必须落在“新增”环节,而不是“存量修复”。
关键动作:
- 在 CI 的
html-lintjob 里启用html-validate的"semantic-elements": "error"规则,禁止<div class="header">新增,但允许已存在的继续存在 - Git pre-commit hook 执行
npx html-validate --config .htmlvalidate.json --max-warnings 0 src/**/*.html,本地就能拦截 - 对必须保留的 legacy class(如
btn-primary),在配置中加白名单:"class-pattern": ["btn-.*", "text-.*"],新 class 必须匹配才放行 - 把重构后的语义化片段存为 Storybook 组件,每次提交触发截图比对,视觉回归自动发现
<header>被悄悄换回<div>
iframe 和模板语法是自动化重写的硬性禁区
<iframe src="legacy-widget.html"> 里的内容不能被主页面的重写规则触达;服务端模板语法(如 <%= title %>、{% if user %})会被 parse5 当作非法标签报错,vnu 验证也会失败。
处理原则很直接:
- 用
--skip-non-html参数跳过含模板语法的文件,或前置用sed/replace-in-file清洗掉<%=.*?%>类占位符 - 对 iframe 嵌入页,单独建目录(如
src/legacy-iframes/),在 lint 配置中用"ignore": ["src/legacy-iframes/**/*.html"]排除 - 不要试图用重写工具处理
<frameset>—— 它已被 HTML5 彻底废弃,浏览器拒绝渲染,应直接替换成<iframe>并隔离验证 - 若页面含大量动态 JS 注入 DOM(如
document.write('<div class="footer">')),重写工具无能为力,需先转为模块化加载,再统一处理静态骨架
真正难的不是把 <div class="card"> 换成 <article>,而是让每个新写的 <section> 都自带 aria-labelledby,每个 <img> 的 alt 都非空且有意义——这需要规则嵌进开发流程,而不是靠一次重写解决。



















