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

自动化重写不是“批量查找替换”,而是用 AST 精准干预 DOM 结构——不理解这点,所有工具链都会在第一次上线后炸掉生产页面。
为什么 parse5 + tree-walker 是唯一靠谱的 HTML 重写起点
正则匹配 <div class="header"> 会同时命中 <code><div class="header">、<code><div class="header navbar-fixed">、甚至注释里的 <code><!-- <div class="header"> -->;Babel 或 TypeScript 解析器压根不认 .html 文件,transformSync() 直接抛 SyntaxError: Unexpected token 。只有 parse5 这类符合 HTML5 标准的解析器,能把字符串转成带父子关系、属性列表、命名空间的完整 AST。<p>实操建议:</p>
<ul>
<li>用 <code>parse5.parseFragment() 而非 parse5.parse() 处理片段(如模板 include),避免强制补全 外壳导致结构错位
treeAdapter.getChildren(node),别依赖 node.childNodes —— 后者在某些 treeAdapter 下可能为空node.attrs 必须重建整个数组:node.attrs = node.attrs.map(...),直接改 attr.value 不生效<div onclick="doX()">),先加 <code>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 卡点必须落在“新增”环节,而不是“存量修复”。<p>关键动作:</p>
<ul><li>在 CI 的 <code>html-lint job 里启用 html-validate 的 "semantic-elements": "error" 规则,禁止 <div class="header"> 新增,但允许已存在的继续存在<li>Git pre-commit hook 执行 <code>npx html-validate --config .htmlvalidate.json --max-warnings 0 src/**/*.html,本地就能拦截
btn-primary),在配置中加白名单:"class-pattern": ["btn-.*", "text-.*"],新 class 必须匹配才放行<header></header> 被悄悄换回 <div>
<h3>iframe 和模板语法是自动化重写的硬性禁区</h3>
<p><code><iframe src="legacy-widget.html"></iframe> 里的内容不能被主页面的重写规则触达;服务端模板语法(如 、{% if user %})会被 parse5 当作非法标签报错,vnu 验证也会失败。
处理原则很直接:
- 用
--skip-non-html参数跳过含模板语法的文件,或前置用sed/replace-in-file清洗掉类占位符 - 对 iframe 嵌入页,单独建目录(如
src/legacy-iframes/),在 lint 配置中用"ignore": ["src/legacy-iframes/**/*.html"]排除 - 不要试图用重写工具处理
<frameset></frameset>—— 它已被 HTML5 彻底废弃,浏览器拒绝渲染,应直接替换成<iframe></iframe>并隔离验证 - 若页面含大量动态 JS 注入 DOM(如
document.write('<div class="footer">')),重写工具无能为力,需先转为模块化加载,再统一处理静态骨架<p>真正难的不是把 <code><div class="card"> 换成 <code><article></article>,而是让每个新写的<section></section>都自带aria-labelledby,每个<img>的alt都非空且有意义——这需要规则嵌进开发流程,而不是靠一次重写解决。











