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.
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.
// 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>'
Object.entries(props) directly — do not sort. The output is deterministic because JS object key order is.<, >, 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).children is text content. Render it on its own line, indented one level deeper than its parent, after HTML-escaping.<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.