HTML SerializerLoading saved progress…

HTML Serializer

Implement htmlSerialize(tree, indent = 2) — take a tree-shaped object that describes an HTML document and turn it into a pretty-printed multi-line HTML string with consistent indentation at every depth. The tree shape is intentionally minimal: it's the same shape React's virtual DOM and lit-html use under the hood — a node is { type, props?, children? }, and children are either other nodes or plain strings. Your job is to flatten that tree back into the string a browser would parse.

Signature

type Node = {
  type: string;                       // tag name, e.g. 'div'
  props?: Record<string, string>;     // optional attributes
  children?: Array<Node | string>;    // strings are text content
};

function htmlSerialize(tree: Node, indent?: number): string;
// `indent` is the number of spaces per depth level. Default 2.

Examples

// Leaf — no children, no attributes.
htmlSerialize({ type: 'div' });
// → '<div></div>'
// Single text child — text sits on its own indented line.
htmlSerialize({ type: 'p', children: ['hi'] });
// → '<p>\n  hi\n</p>'
// Nested elements — each level indented by `indent` more spaces.
htmlSerialize({ type: 'div', children: [{ type: 'p', children: ['hi'] }] });
// → '<div>\n  <p>\n    hi\n  </p>\n</div>'
// Attributes render in insertion order, separated by a single space.
htmlSerialize({ type: 'a', props: { href: '/x' }, children: ['link'] });
// → '<a href="/x">\n  link\n</a>'
// Mixed text + element children, custom indent width.
htmlSerialize(
  { type: 'p', children: ['hello ', { type: 'b', children: ['world'] }, '!'] },
  4,
);
// → '<p>\n    hello \n    <b>\n        world\n    </b>\n    !\n</p>'

Notes

  • Attribute order is insertion order. Use Object.entries(props) directly — do not sort. The output is deterministic because JS object key order is.
  • Escape text content for <, >, and & so that '<script>' cannot become a real tag in the output. Attribute values additionally escape " so the double-quoted attribute syntax stays well-formed.
  • children may be omitted, empty, or a list of nodes/strings. A node with no children renders as <tag></tag> on a single line (no inner newlines, no inner padding).
  • A bare string in children is text content. Render it on its own line, indented one level deeper than its parent, after HTML-escaping.
  • Void elements (<br>, <img>) are out of scope. Treat every node uniformly with an open + close tag; document this in the solution.
  • indent is the spaces-per-level count, not a prefix string. htmlSerialize(tree, 4) indents 4 spaces per depth; htmlSerialize(tree, 0) collapses indentation but still emits newlines between open tag, children, and close tag.
Loading editor…