格式化是代码工作流里最容易被忽略、也最容易被后悔的一环:一堵 HTML 墙读起来像一个长单词,缩进一摆正,同样的内容就变成一棵树。格式化工具不改变标记的含义,只改变你查看它的成本,而调试时间的大半正是花在这个成本上的。HTML 格式化完全在你的浏览器里运行:粘贴标记、选择模式,输出立刻出现在旁边的面板里。没有任何上传,内容也不离开当前标签页,这一点在处理含 API 密钥、内网主机名或客户数据的页面时尤其重要,你不需要把它们贴进任何第三方服务。
工具朝两个方向工作,回答的是两个不同的问题。格式化模式问的是:当空白字符都诚实时,这个结构长什么样?压缩模式问的是:这个文件在不产生任何行为变化的前提下,能变得多小?下文会讲清两种模式各自如何处理,一个 458 字符的真实页面在两种模式下分别变成什么样,压缩模式对文档哪些部分坚决不碰、哪些部分直接删除,缩进设置和行内元素列表如何塑造格式化结果,以及这个工具与同族其他格式化工具各守什么位置。
HTML 格式化工具怎么工作:两种模式,一次粘贴
两种模式读的是同一个输入字符串,输出都是写进右侧面板的纯文本,差别在变换本身。格式化模式把标记交给 js-beautify 的 HTML 引擎,配一组固定参数:缩进两格或四格空格,不做行宽包裹,不追加结尾换行,不缩进行内元素内部,并指定一份内容永不被拆到多行的元素列表。压缩模式走的是一条专门搭的流水线:先把文档里所有 script、textarea、pre 块整体抽出搁置,再剥掉 HTML 注释,把所有连续空白折成一个空格,去掉标签接缝两侧的空格,最后把受保护的块逐字节放回去。
这个顺序就是全部设计。空白折叠对标记本身是安全的,对 style 标签里的 CSS 也是安全的,因为这两种语言里一长串空格换行和一个空格意思相同。它对 pre 标签内部不安全,因为那里面的空白就是内容;对 script 也不安全,因为压缩器会把语句愉快地粘在一起。先抽出再还原,让工具可以对其余文档做激进处理,同时让这三类区域原样出来,连换行符都不少一个。
标准案例:458 字符的页面变成 1 行 281 字符
拿一个手写的页面:doctype、带 charset 的 head、title、一小段 style,以及 body 里一张含标题、段落和按钮的卡片。按人们真实写代码的方式,带你会打的缩进和一条遗留的草稿注释,它是 458 字符、28 行。跑一遍格式化模式,页面回来时是 390 字符、27 行:缩进过深的 title 和标题各自收拢到一行,结构重新锚定到整齐的两空格节奏,草稿注释原样保留。如果你们团队标准是四空格,同一遍处理返回相同行数的文档,只是缩进更宽。
对同一个 458 字符的页面跑压缩模式,结果是 1 行 281 字符,减少 38.6%。草稿注释没了,缩进没了,标签之间的空格没了,剩下的还是同一份文档:同样的元素、同样的属性、同样的文本、同样的 CSS。如果不想手算前后差值,把任意一个面板的长度读出来摆在一起,字符计数器与分析器直接给你能在代码评审里引用的那个数字。
压缩保护什么、删除什么
三类块在任何折叠发生之前就被整体抽出:script、textarea、pre。一个含两行赋值脚本的页面,压缩后两行的换行完整保留,因为从开标签到闭标签的整块原样搬到输出里。依赖双空格和换行才能渲染成代码列表的 pre 块,每一个字符都不动。保护是逐字节的,不是逐行的,所以那些看起来像手误、实际承重的位置也被保住,比如防止行内元素与邻居粘连的空格。
受保护区域是边界,不是终点。脚本块里的 JavaScript 只被 HTML 处理保护,而 HTML 处理只能承诺这些:真正的 JS 压缩是语法层面的问题,死语句、冗余括号、安全的标识符重命名都能删,那是JavaScript 格式化与压缩用真正解析代码的引擎完成的工作,而不是靠模式匹配。style 标签在边界的另一侧:里面的 CSS 不受保护,只接受空白处理,规则结构完整、间距收窄,结果仍是合法 CSS。样式表需要更深一层处理时,由CSS 压缩器接手,它的引擎知道选择器与声明之间的哪些空格承重、哪些纯是噪音。
这些块之外的一切都接受完整处理,有两个细节值得知道。HTML 注释被直接删除,而且注释剥离会跑两遍,空白折叠前一遍、折叠后一遍,因为折叠可能把两个半截注释拼成只在折叠后才存在的完整注释。标签接缝两侧的空格,也就是闭括号和下一个开括号之间的间隙,会消失,输出因此变成一整行不间断的尖括号。一个可预期的怪癖:受保护块原来所在的流式位置,可能残留一个空格,因为折叠是在占位符周围做的,不是在真正的标签周围做的。什么都不坏,文件仍然合法,只是那一处不如整行其他地方紧凑。
格式化模式:缩进、行宽与行内元素列表
格式化模式只有一个多数用户会改的设置:缩进宽度,默认两空格,四空格为替代。在带一条注释和一小段脚本的 197 字符五行样例上,两空格处理返回 244 字符 18 行,四空格处理返回同样 18 行但 278 字符。同样的结构、同样的行数、更宽的横向空间;这是团队习惯,不是正确性问题。行宽包裹被刻意关掉,一长串属性因此留在同一行,而不是在某个任意列上被折断,输出在最后一个标签结束处结束,不追加结尾换行。
更微妙的设置是行内元素列表:a、span、td、th、small、pre、code。对这些标签,引擎拒绝在开闭标签之间断行,所以句中包裹一个词的链接留在同一行,表格单元格的内容也不会碎到整页各处。正是这份列表让排版密集的页面格式化后读起来像你手工缩进的样子:块级元素堆叠,行内元素流动,句子中间不会被硬塞一个换行。
相邻格式:JSON、XML 与各自的位置
HTML 是标记语言里最宽容的一种,正因如此它也最被滥用空白。JSON 则完全相反,多一个逗号或一个尾随空格就整体报错,所以它有自己的工具,验证器会告诉你错误在哪一行、为什么:JSON 格式化读入同样粘贴来的内容,要么返回排版好的结果,要么明确拒绝。这正是你在清理的 HTML 恰好把配置对象嵌在 script 标签里时该有的搭档。XML 是更严格的表亲,括号不匹配是硬错误而不是浏览器猜,XML 格式化用同样的格式化加压缩模式处理这一族文档。
拆分的意义在于每种格式都得到一个真正会说该格式语言的引擎:HTML 做结构重排,CSS 用感知 CSS 的引擎,脚本用 JS 解析器。四个工具跑同一个页面,就是构建系统对工具链做的那种分工,只是没有构建:格式化文档、压缩负载、发布结果。HTML 格式化是中间那个保住结构可读性的环节,也是通用压缩器最先搞错的部分,因为通用压缩器优化的是文件体积,而你还在改。
发布之前先读一遍输出
输出面板就是整个审阅面,值得像审阅者那样去读。格式化模式下先看结构:块级嵌套是否符合预期,行内元素列表是否保住了可读性,有没有哪个元素被两空格节奏弄得反而更难读。压缩删掉的那条注释,是两种模式对文件内容唯一意见分歧的地方;如果那条注释对下一个人承重,就该保留格式化模式的版本。
压缩模式下的审阅更短更机械:文件应该是一行,标签应该连续,受保护区域应该幸存——打开输出找到 script,确认换行还在。如果最终交付的是纯文本而不是标记,HTML 转纯文本剥掉标签还你阅读视图,交付物是贴进文档而不是部署时这是正确动作。而驱动页面的配置如果住在标记旁边的 YAML 文件里,YAML 格式化给那份文件同样的待遇。粘贴、选模式、读输出、复制走:整个循环始终在有这个文件的标签页里,从头到尾。