HTML 内嵌脚本与样式:结束标签边界及格式化限制
script 和 style 的结束边界属于 HTML 层,不能仅靠 JavaScript/CSS 引号保护。本文提供 DOMParser 对照示例,说明安全嵌入和文本检查的方法,以及格式化无法自动保证的边界。
一、问题概述
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。
来源与延伸阅读
用于核对文章技术细节的规范与参考资料。
- HTML Standard — Restrictions for contents of script elements
WHATWG
- ECMAScript Language Specification
Ecma International
相关文章
HTML 格式化与压缩:空白折叠 (Space Collapse) 与 inline-block 布局间距处理
区分 HTML 代码格式化与空白压缩对内联元素的影响,深入剖析 display: inline-block 标签间换行折叠为空格的渲染原理与避坑方案。
最佳实践语义化 HTML 标准、DOM 结构干净度排查与 SEO 影响分析
通过浏览器示例检查文档顺序中的标题跳级和图片 alt 属性缺失,区分格式化、语义结构、装饰图片与项目内部约定,不承诺排名或收录提升。
实现原理Base64 编解码算法原理:3字节转4字节、= 填充符根因与 URL-Safe (RFC 4648) 变种
深度拆解 Base64 3 字节 (24bit) 转 4 字符 (4 * 6bit) 算法推导,解释末尾 = 与 == 填充符的生成条件,以及 URL-Safe (- 与 _) 替换规则与无 padding 还原。
继续阅读
可打开关联的浏览器工具,使用自己的样本验证文中的处理流程。
打开关联工具