dom-serializer
Renders domhandler DOM nodes back into HTML or XML strings, with fine control over entity encoding and tag formatting.
Repository Health
Technical Analysis
dom-serializer turns a domhandler DOM node (or array of nodes) back into a markup string — effectively the outerHTML of whatever tree you hand it. It is the write-side counterpart to htmlparser2’s parser: parse markup into a domhandler tree, mutate it however you like, then serialize it back out with this package.
It underpins cheerio’s .html() and .toString() output, so its behavior is exercised by every cheerio consumer even when they never import it directly. The API is a single render() function with an options object covering entity encoding, self-closing tags, XML mode, and empty-attribute formatting, letting callers reproduce either strict HTML or XML serialization semantics from the same DOM representation.
What You Get
- A single
render(node, options)function that serializes one node or an array of nodes to a markup string - Configurable entity encoding (
encodeEntities/decodeEntities) with a documented round-trip mode for parsers that don’t decode entities on input - XML mode (
xmlMode) for self-closing tags and empty-attribute printing, plus a"foreign"mode that restores mixed-case SVG/MathML element and attribute names - Correct handling of HTML void elements, CDATA sections, comments, and processing instructions
- Full TypeScript types for
DomSerializerOptionsshipped alongside the compiled output
Common Use Cases
- Serializing a cheerio-manipulated DOM back to an HTML string after scraping or templating edits
- Round-tripping HTML or XML through htmlparser2 (parse) and dom-serializer (serialize) without corrupting entity references
- Rendering SVG or MathML fragments that require foreign-content name casing to be preserved
- Building custom HTML/XML processing tools on top of domhandler’s DOM representation without pulling in a full browser DOM
Under The Hood
Architecture
The package is a small, function-first module with no classes: render() wraps a single node or array into a list and delegates to a recursive renderNode()/renderChildren() pair that switches on domelementtype’s node-type enum (Root, Directive, Comment, CDATA, Tag/Script/Style, Text). xmlMode is threaded as a plain parameter rather than mutating a shared options object, so entering and leaving SVG/MathML “foreign” mode during tag rendering (renderTag in src/index.ts) doesn’t require copying options at each recursion level. Attribute serialization is isolated in formatAttributes, which picks an encoder function once per tag rather than branching per attribute.
Tech Stack
Written in TypeScript, published as ESM-only ("type": "module") targeting Node >=20.19.0, with domelementtype, domhandler, and entities as the only runtime dependencies. The build is a plain tsc compile; linting layers ESLint (via @feedic/eslint-config and typescript-eslint) on top of Biome for formatting and additional static checks, and tests run under Vitest.
Code Quality
The test suite (src/index.spec.ts) is behavioral rather than unit-level: it loads markup through cheerio/htmlparser2 and asserts on the serialized round-trip output, covering quote-escaping edge cases, entity encoding modes, self-closing tag variants, XML mode, and non-string attribute values. A companion index.bench.ts benchmarks serialization performance. Types are strict throughout, JSDoc comments document every option’s default and security implications (notably around disabling entity encoding), and GitHub Actions CI (nodejs-test.yml) runs the suite on each push. No dedicated unit tests target the internal helper functions in isolation, but the integration-style coverage exercises them indirectly through realistic markup.
API Design
The public surface is deliberately minimal — one default-exported render() function plus a single options interface — and the option names (decodeEntities, xmlMode) are chosen to mirror htmlparser2’s parser options so a single options object can be threaded through both parse and serialize calls for a faithful round trip. This is a standard, well-documented ergonomic choice rather than a novel technical approach; the library’s value is in doing HTML/XML serialization edge cases correctly, not in API novelty.