import { Readable } from 'node:stream' type ExpressionPrimitives = string | number | Date | boolean | undefined | null | ExpressionPrimitives[] type Expression = ExpressionPrimitives | (() => ExpressionPrimitives) | Promise type ParseGenerator = Generator, void> export interface Template { generator: (minify?: boolean) => ParseGenerator stream: (minify?: boolean) => Readable text: (minify?: boolean) => string | Promise } const cache = new WeakMap() function apply(expression: Expression): ExpressionPrimitives | Promise { if (expression == null) { return '' } else if (expression instanceof Promise) { return expression.then((resolved) => apply(resolved)) } else if (typeof expression === 'function') { return apply(expression()) } else if (Array.isArray(expression)) { return expression.join('') } return expression } function* parse(strings: TemplateStringsArray, expressions: Expression[], minify?: boolean): ParseGenerator { if (minify) { let minified = cache.get(strings) if (!minified) { minified = strings.map(removeWhitespace) cache.set(strings, minified) } strings = minified } for (let i = 0; i < strings.length; i++) { yield strings[i] const expression = expressions[i] yield apply(expression) } } export default function html(strings: TemplateStringsArray, ...expressions: Expression[]): Template { return { generator: (minify?: boolean) => parse(strings, expressions, minify), stream: (minify?: boolean) => stream(parse(strings, expressions, minify)), text: (minify?: boolean) => text(parse(strings, expressions, minify)), } } html.generator = (strings: TemplateStringsArray, ...expressions: Expression[]) => parse(strings, expressions) html.stream = (strings: TemplateStringsArray, ...expressions: Expression[]) => stream(parse(strings, expressions)) html.text = (strings: TemplateStringsArray, ...expressions: Expression[]) => text(parse(strings, expressions)) function stream(generator: ParseGenerator) { return Readable.from(generator) } async function text(generator: ParseGenerator) { let result = '' for (const chunk of generator) { result += chunk instanceof Promise ? await chunk : chunk } return result } function removeWhitespace(str: string) { return str.split(/\n\s*/).join('') }