Javascript is required
实现原理发布于 2026-07-28更新于 2026-10-115 分钟阅读

HTML 内嵌脚本与样式:结束标签边界及格式化限制

script 和 style 的结束边界属于 HTML 层,不能仅靠 JavaScript/CSS 引号保护。本文提供 DOMParser 对照示例,说明安全嵌入和文本检查的方法,以及格式化无法自动保证的边界。

作者: Tiny
HTML FormatterScript BlockStyle BlockNested Parsing

一、问题概述

text/html 中,HTML 解析器先决定 script/style 的文本边界,然后才有 JavaScript/CSS 解析。JavaScript 字符串中的字面 </script> 也可能结束 script;这不是只有正则才会遇到的“伪标签”。本文用浏览器 DOMParser 定位问题,不实现通用多语言格式化器。

二、最小复现

在浏览器控制台执行以下代码。broken 的脚本文本在引号后结束;fixed 使用 JavaScript 的 \x3C 转义表示小于号,使 HTML 源码中没有该结束标签序列。此示例只解析和读取,不把节点插入活动文档。

// Browser console: HTML parsing runs before JavaScript parsing.
const broken = '<script>const value = "</script>";</script>';
const fixed = String.raw`<script>const value = "\x3C/script>";</script>`;
const parser = new DOMParser();
console.log(parser.parseFromString(broken, 'text/html').querySelector('script')?.textContent);
// const value = "
console.log(parser.parseFromString(fixed, 'text/html').querySelector('script')?.textContent);
// const value = "\x3C/script>";

三、根因分析

HTML tokenizer 的 script data/RAWTEXT 等状态不同于 JavaScript/CSS 的字符串和注释状态。它不会因为结束标签出现在 JavaScript 引号里就忽略它。style 中的 </style> 也受 HTML 层约束。text/html 与 XML/XHTML 的解析规则不能互换。

四、推荐方案

已损坏的嵌入源码先修复生成端:JavaScript 字面量按规范避开危险 HTML 序列,或改用外部脚本。若要输出 script 内的 JSON 数据,可以在 JSON.stringify 的结果中将 < 替换为 \u003c,并验证 parse 后的值。格式化器应先用 HTML tokenizer 确认边界,再按已知语言调度子解析器。

五、完整代码

下面函数使用浏览器 DOMParser 读取 script/style 的解析后文本和属性,便于排查分派。它不是保留原始字节或位置的 parser,也不格式化、不执行、不插入这些节点。DOMParser 不是清洗器;不要将不可信解析结果直接插入页面。

function inspectEmbeddedText(html: string) {
  const doc = new DOMParser().parseFromString(html, 'text/html');
  return Array.from(doc.querySelectorAll('script, style'), (node) => ({
    tag: node.localName,
    type: node.getAttribute('type') ?? '',
    lang: node.getAttribute('lang') ?? '',
    text: node.textContent ?? '',
  }));
}

const sample = '<script type="application/ld+json">{"name":"Tiny"}</script>'
  + '<style>.item { color: red; }</style>';
console.log(inspectEmbeddedText(sample));
// Two records: JSON data and CSS text. Nothing is executed or inserted.

六、常见错误方案

给每行先 trim 再加空格,会改写模板字符串中的多行值;仅语法或 AST 相似不足以证明运行值不变。正则扫描不能覆盖带 > 的属性、HTML 注释及复杂 script 状态。不能用 JavaScript 字符串状态覆盖 HTML 的真实结束边界。

七、边界条件

type=module、application/ld+json、importmap 和自定义数据类型用途不同;未知类型应保留原文或明确拒绝。lang=scss 是构建工具约定,浏览器不会自动编译 SCSS。DOMParser 会规范化某些输入,若要保持源码区间应采用保留位置信息的 HTML tokenizer。

八、如何验证

验证真实结束标签与大小写变体、转义后的结束序列、JSON 数据块、CSS 文本、带 > 的属性以及模板字符串。对嵌入代码除了语法,还要检查字符串值和数据;不要盲目在每行增加基准缩进。

九、FAQ

问:完整 HTML parser 会忽略 JS 字符串里的 </script> 吗?答:不会,HTML 层先决定结束边界。

问:DOMParser 会执行这些脚本吗?答:本示例只读取独立解析文档,不插入节点;它也不是安全清洗方案。

问:能统一给每行加缩进吗?答:不能,模板字面量中的空白可能是数据。

十、总结

先修复嵌入方式,再核对 HTML 边界与语言类型;无法证明语义保持时保留原文。使用 /tools/format/html-formatter 比较结果,空白问题参考 /articles/html-formatter-inline-block-space-collapse,JSON 层转义参考 /articles/nested-json-escaping。

来源与延伸阅读

用于核对文章技术细节的规范与参考资料。

相关文章

继续阅读

可打开关联的浏览器工具,使用自己的样本验证文中的处理流程。

打开关联工具