html结构性坏味道需用ast自动重组修复,正则无法安全处理class重排序,因无法识别注释、js字符串、多行及模板语法,且破坏css语义依赖;实操应基于parse5等标准解析器,仅对静态class值归一化,并配合lint规则同步更新。

HTML 代码里嵌套过深、属性顺序混乱、冗余标签、混用空格与制表符——这些不是“风格偏好”,而是 AST 层面可检测、可修复的结构性坏味道。靠人工肉眼 review 或正则替换,既不可靠又易出错;真正稳定高效的解法,是基于 AST 的自动重组。
为什么不能用正则处理 class 属性重排序?
正则匹配 class 属性时,会漏掉注释内伪属性、JS 字符串中的假 class、多行写法(如换行后带空格)、以及 class={...} 这类模板语法。更关键的是:它无法感知 DOM 结构层级,可能把 <div class="a b"><span class="b a"> 里的两组 class 错误地统一成相同顺序,破坏语义依赖(比如 CSS 里 <code>.a.b 和 .b.a 并不等价)。
实操建议:
- 用
parse5或htmlparser2构建 AST,确保解析符合 HTML 标准(如自闭合标签处理、属性名大小写不敏感) - 只对静态字面量
class值做归一化:提取 token → 按字母序或约定序(如 BEM 前缀优先)排序 → 重新拼接 - 跳过含大括号、插值符号(
{{}}、${})、JS 表达式的节点,标记为skip: true
prettier 的 htmlWhitespaceSensitivity 怎么选?
这个配置直接影响 AST 重组后换行和缩进是否“安全”。设为 "css" 时,Prettier 会保留所有空白符(包括文本节点间的换行),适合 SSR 渲染或需要精确控制 inline 元素间距的场景;设为 "strict" 则会合并相邻文本节点并删除无意义空白,但可能意外折叠 <pre class="brush:php;toolbar:false;"></pre> 或 <textarea></textarea> 内容。
实操建议:
- 若项目使用 React/Vue/Svelte,且组件内含大量内联文本或
white-space: pre样式,强制设为"css" - 若纯静态页或构建后经 HTML minifier 处理,可用
"strict",但需在 CI 中加校验:diff 输出前后<pre class="brush:php;toolbar:false;"></pre>内容 hash - 不要全局设为
"ignore"—— 它会让 Prettier 放弃所有 whitespace 决策权,导致 AST 重组失去一致性
如何安全删除无作用的 <div> 包裹器?
<p>AST 重组中“删 div”看似简单,实则高危:CSS 选择器可能依赖层级(<code>.parent > .child)、JS 可能通过 parentNode 获取上下文、甚至某些 CSS-in-JS 库会把父 div 当作 scope 容器。
实操建议:
- 仅当满足全部条件时才移除:
node.tagName === 'DIV'且node.attributes.length === 0且node.children.length === 1且node.children[0].type !== 'Comment' - 跳过含
data-属性、id、class(哪怕为空字符串)的节点 —— 空 class 可能被 JS 动态添加样式 - 对每个被删节点生成 warning 日志,记录路径(如
body > div > main > div),供前端同学复查
AST 重组不是“格式化工具升级版”,它动的是结构契约。最常被忽略的点是:没有配套的 lint 规则同步更新,就会出现“今天自动删了 div,明天 CI 里 ESLint 报 missing wrapper”这种冲突。重组逻辑和校验逻辑必须版本对齐、共用同一份 AST 节点判定谓词。











