12export class Html {3 constructor(readonly value: string) {}4 toString(): string {5 return this.value;6 }7}89export type Part = Html | string | number | null | undefined | false | Part[];1011const ENTITIES: Record<string, string> = {12 "&": "&",13 "<": "<",14 ">": ">",15 '"': """,16 "'": "'",17};181920export function escapeHtml(text: string): string {21 return text.replace(/[&<>"']/g, (c) => ENTITIES[c] as string);22}2324function render(part: Part): string {25 if (part instanceof Html) return part.value;26 if (Array.isArray(part)) return part.map(render).join("");27 if (part === null || part === undefined || part === false) return "";28 return escapeHtml(String(part));29}3031export function html(strings: TemplateStringsArray, ...values: Part[]): Html {32 let out = strings[0] as string;33 values.forEach((value, i) => {34 out += render(value) + strings[i + 1];35 });36 return new Html(out);37}383940export function trusted(markup: string): Html {41 return new Html(markup);42}