应使用 domparser 替代正则解析 html,因其能自动修复语法错误;需跳过 html/head 节点取 body,预处理模板语法,按语义区分块级、内联与保护区节点,规范属性顺序及引号,并对大文件分片或用 web worker 处理。

用 DOMParser 替代正则匹配,避免解析崩坏
正则处理 HTML 几乎必然出错——<script></script> 里的 、<pre class="brush:php;toolbar:false;"></pre> 中的尖括号、属性值里的 >,全都会让正则逻辑失控。浏览器原生的 DOMParser 才是唯一可靠起点,它自动修复缺失闭合、纠正非法嵌套、忽略注释干扰。
实操建议:
- 别写
/<div>]*>/g 这类表达式,直接用 <code>new DOMParser().parseFromString(rawHtml, 'text/html') - 取
doc.body而非doc.documentElement,跳过干扰,专注内容主体 - 若原始 HTML 含模板语法(如
{% if %}或{{name}}),先临时替换再解析,格式化完再还原 - 用
node.nodeType === Node.ELEMENT_NODE过滤非元素节点,跳过文本节点中的冗余换行 - 通过
getComputedStyle(node).display判断是否为块级?不行——服务端无样式计算。改用白名单:['div', 'p', 'header', 'main', 'footer', 'section', 'article'] - 对自闭合标签(
<img>、<input>、<br>)不生成结束标签,也不缩进其后内容 - 提取所有属性名,按优先级分组:
['id', 'class']、data-*(用attr.name.startsWith('data-')判断)、['src', 'href', 'alt', 'title']、['onclick', 'onchange', 'onsubmit'] - 布尔属性(
disabled、required)不带=和引号,直接输出为disabled - 属性值统一用双引号,哪怕值里没特殊字符——避免和单引号混用导致 IDE 高亮异常
- 超过 5000 行时,改用
setTimeout分片遍历,每次处理 200 个节点,留出渲染帧 - 更稳妥的做法是移入
Web Worker,用postMessage传入 HTML 字符串,完成后回调结果 - 本地工具(如岱码工具箱)默认启用分片,但 VS Code 插件或 Prettier CLI 不具备该能力,需手动加
--range-start/--range-end控制处理范围
递归遍历中区分三类节点:块级、内联、保护区
缩进不是简单加空格,关键在语义识别。块级元素(<div>、<code><p></p>、<section></section>)必须换行+缩进;内联元素(<span></span>、<a></a>、<strong></strong>)默认不折行;而 <script></script>、<style></style>、<pre class="brush:php;toolbar:false;"></pre> 必须原样保留,连内部空白都不能动。
实操建议:
属性排序比缩进更重要:id → class → data-* → 其他 → 事件
混乱 HTML 最扎眼的不是缩进错位,而是同一元素的 onclick 写在最前、id 挤在最后。属性顺序直接影响可读性与 diff 效果——Git 提交时,class 和 id 靠前,能一眼看出组件身份。
实操建议:
大文件必须分片或 Web Worker,否则卡死页面
10 万行 HTML 在主线程递归遍历,会触发浏览器“脚本执行时间过长”警告,用户操作完全冻结。这不是性能优化问题,是可用性底线。
实操建议:
真正难的不是写缩进逻辑,而是判断哪段该缩、哪段该锁、哪段该跳过。模板语法、服务端 include、CDN 注释、遗留的 IE 条件注释——这些都得在解析前识别并隔离,否则格式化完反而破坏运行逻辑。











