Javascript is required
How it worksPublished 2026-07-28Updated 2026-10-115 min read

HTML Formatting: Embedded Script and Style Blocks

HTML controls the closing boundaries of script and style elements. Use DOMParser comparisons to diagnose embedding, inspect parsed text, and identify cases where formatting cannot automatically preserve values.

By: Tiny
HTML FormatterScript BlockStyle BlockNested Parsing

1. Problem

For text/html, HTML parsing establishes script/style boundaries before JavaScript or CSS parsing. A literal </script> inside a JavaScript string can close the script element; it is not merely a regex-only fake tag. This guide diagnoses boundaries with browser DOMParser rather than implementing a general formatter.

2. Minimal reproduction

Run this in a browser console. The broken script ends after the opening quote. The fixed version uses the JavaScript \x3C escape for <, avoiding the literal end-tag sequence in HTML source. The example parses and reads without inserting nodes into the active document.

// 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>";

3. Root cause

HTML tokenizer script-data and RAWTEXT states differ from JavaScript/CSS string and comment states. JavaScript quotes do not make an HTML end tag invisible. Literal </style> has a related HTML-layer boundary. Do not apply XML/XHTML parsing assumptions to text/html.

4. Recommendation

Fix the producer first: escape problematic HTML sequences in JavaScript literals as the specification describes, or use external scripts. For JSON data inside script, replacing < in JSON.stringify output with \u003c avoids the literal sequence; verify that JSON.parse restores the value. A formatter should establish HTML boundaries before dispatching a known language parser.

5. Complete code

This browser DOMParser helper reads parsed script/style text and attributes for inspection. It does not preserve original bytes or source locations, format code, execute it, or insert nodes. DOMParser is not a sanitizer; do not insert untrusted results into a live page.

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.

6. Incorrect approaches

Trimming every line and adding a prefix changes multiline template-string values. Syntax or similar AST structure alone does not prove equal values. Regex scanning misses quoted > attributes, HTML comments, and complex script states. JavaScript string states cannot override HTML end-tag boundaries.

7. Boundaries

module, application/ld+json, importmap, and custom data types have distinct purposes. Preserve unknown content or reject it explicitly. lang=scss is a build-tool convention, not automatic browser compilation. Use a location-preserving HTML tokenizer when original source ranges matter.

8. Verification

Check real and case-varied end tags, escaped sequences, JSON data blocks, CSS, quoted > attributes, and template literals. Verify data and string values as well as syntax. Do not blindly add a base indentation prefix to every line.

9. FAQ

Will a full HTML parser ignore </script> inside a JS string? No; the HTML layer establishes the boundary first.

Does this DOMParser example run the script? It only reads a detached document and does not insert nodes; it is also not a sanitization solution.

Can every line receive an indentation prefix? No; template-literal whitespace may be data.

10. Summary

Fix embedding, then check HTML boundaries and language types. Preserve original text when equivalence is unproven. Compare output with /tools/format/html-formatter; see /articles/html-formatter-inline-block-space-collapse for whitespace and /articles/nested-json-escaping for JSON layers.

Sources and further reading

Specifications and references for checking the article’s technical details.

Related articles

Keep exploring

Use the related browser tool to test the workflow with your own sample data.

Open related tool