html代码质量需在提交、构建、预发三阶段设卡:本地用html-validate拦截doctype缺失等结构性错误,ci用puppeteer+axe-core验证运行时语义,pr评审聚焦等语义边界变动,并通过$$('*').length监测dom膨胀。

HTML代码质量在敏捷迭代中不会自然保持,必须靠可感知、可拦截、可回滚的轻量级控制点来平滑托底——不是靠一次重构,而是靠每次提交、每次构建、每次预览时的微小约束。
本地提交前用html-validate做最小结构拦截
pre-commit钩子不是万能的,但它对doctype缺失、根标签错位、或遗漏这类结构性硬伤,拦截率接近100%。关键在配置和执行范围。
- 只校验
.html文件不够:Vue/React组件里的<template></template>也得覆盖,加html-validate-vue插件或自定义parser - 规则别照搬默认:关掉
no-inline-style这类易误报项,但必须开require-lang-attr、require-title、no-duplicate-id - 禁止跳过:
git commit --no-verify必须被脚本拦截,exit 1写死,不设例外路径 - 别只扫变更文件:结构性错误常藏在被include的
header.inc里,需全量扫描src/**/*.html和public/*.html
CI阶段用Puppeteer+axe-core抓运行时语义断裂
静态检查看不到JS动态插入的div[role="button"]没加tabindex,也发现不了SSR后aria-live区域被CSR覆盖丢失。真实浏览器环境才是语义验证的底线。
- 不要只跑首页:对核心路由(如
/login、/product/:id)逐个启动页面,分别采集axe.run()结果 - baseline必须存档:把上一版的
violationsJSON存为基线,CI中对比新增critical数,超2条即失败 - 忽略框架干扰:在
axe.configure()里显式过滤data-v-*、data-reactroot等非业务属性,避免误报 - 快照比对要剔除动态内容:时间戳、随机
id、用户昵称等字段需在DOM序列化前removeAttribute或textContent = ''
PR Review时盯住三类“语义边界变动”
Review HTML变更不是读代码,是看结构拓扑是否被悄悄改写。重点不在“写了什么”,而在“删了什么”“包了什么”“绕过了什么”。
- 检查
<main></main>、<nav></nav>、<aside></aside>容器的增减:一个<main></main>被拆成两个,或被<div class="wrapper">意外包裹,都会破坏语义层级 <li>对比<code>role和aria-*属性变化:比如role="dialog"改成role="region",或删掉了aria-labelledby,这类改动必须附带无障碍测试截图 - 警惕
style和!important新增:它们常是绕过CSS架构的信号,后续极易引发样式不可维护,PR里出现就标为high-risk - 用Lighthouse跑变更页:不看总分,只看“Fails”列表里新增项是否直接关联本次修改,比如新增
<img>没alt,就该立刻驳回 - 基线值要手动记:空闲态执行
$$('*').length,比如124789,写进README或团队Wiki - 交互后差值超50–100就该查:打开弹窗→关闭→再执行,差值持续正向累积,说明Detached节点没释放
- 别混用
document.querySelectorAll('body *').length:它漏掉iframe和Shadow DOM,而现代组件库(Lit、Stencil)大量用后者 - 配合Chrome Memory面板:Performance里
Nodes曲线回落缓慢,Memory快照中Retaining path末尾指向window.cacheMap或timer.callback,就是泄漏锚点
用$$('*').length做日常劣化血压监测
这不是诊断工具,是每个开发者每天该看一眼的“DOM血压计”。它比内存占用更早暴露结构膨胀,且不受框架虚拟DOM干扰。
真正难的不是工具链搭建,而是让每个成员在“赶需求”的间隙,愿意多敲一行$$('*').length、多点一次Lighthouse、多看一眼PR里<main></main>有没有被套进<div>——这些动作本身不耗时,但缺一不可。</div>











