brf/server/lib/html.ts
2025-11-24 17:09:09 +01:00

80 lines
2.3 KiB
TypeScript

import { Readable } from 'node:stream'
type ExpressionPrimitives = string | number | Date | boolean | undefined | null | ExpressionPrimitives[]
type Expression = ExpressionPrimitives | (() => ExpressionPrimitives) | Promise<ExpressionPrimitives>
type ParseGenerator = Generator<ExpressionPrimitives, void>
export interface Template {
generator: (minify?: boolean) => ParseGenerator
stream: (minify?: boolean) => Readable
text: (minify?: boolean) => string | Promise<string>
}
const cache = new WeakMap()
function apply(expression: Expression) {
if (!expression) {
return ''
} else if ((expression as Promise<ExpressionPrimitives>).then) {
return (expression as Promise<ExpressionPrimitives>).then(apply)
} else if (typeof expression === 'function') {
return apply(expression())
} else {
return (expression as ExpressionPrimitives[]).join?.('') ?? 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 await (const chunk of generator) {
result += chunk
}
return result
}
function removeWhitespace(str: string) {
return str.split(/\n\s*/).join('')
}