81 lines
2.4 KiB
TypeScript
81 lines
2.4 KiB
TypeScript
import { Readable } from 'node:stream'
|
|
|
|
type ExpressionPrimitives = string | number | Date | boolean | undefined | null | ExpressionPrimitives[]
|
|
|
|
type Expression = ExpressionPrimitives | (() => ExpressionPrimitives) | Promise<Expression>
|
|
|
|
type ParseGenerator = Generator<ExpressionPrimitives | Promise<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): ExpressionPrimitives | Promise<ExpressionPrimitives> {
|
|
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('')
|
|
}
|