Last active
May 21, 2021 02:59
-
-
Save jdanyow/a4761670c16ff0901789a1496282572d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import escape_html from "escape-html"; | |
import { Readable } from "stream"; | |
// following imports are for testing | |
import { createReadStream } from "fs"; | |
import * as assert from "assert"; | |
export class TemplateResult { | |
constructor( | |
public readonly strings: TemplateStringsArray, | |
public readonly values: unknown[] | |
) {} | |
} | |
export class UnsafeHTML { | |
public readonly value: string; | |
constructor(value: string) { | |
this.value = typeof value === "string" ? value : String(value); | |
} | |
public toString(): string { | |
return this.value; | |
} | |
} | |
export function html( | |
strings: TemplateStringsArray, | |
...values: unknown[] | |
): TemplateResult { | |
return new TemplateResult(strings, values); | |
} | |
export function unsafeHTML(value: string): UnsafeHTML { | |
return new UnsafeHTML(value); | |
} | |
async function* renderInternal(value: unknown): AsyncIterable<string> { | |
if (value === null || value === undefined || value === "") { | |
return; | |
} else if (Array.isArray(value)) { | |
for (const item of value) { | |
for await (const x of renderInternal(item)) { | |
yield x; | |
} | |
} | |
} else if (value instanceof TemplateResult) { | |
const { strings, values } = value; | |
yield strings[0]; | |
for (let i = 1; i < strings.length; i++) { | |
const value = values[i - 1]; | |
for await (const x of renderInternal(value)) { | |
yield x; | |
} | |
yield strings[i]; | |
} | |
} else if (value instanceof UnsafeHTML) { | |
yield value.value; | |
} else if (value instanceof Readable) { | |
for await (const x of value) { | |
yield x; | |
} | |
} else { | |
yield escape_html(typeof value === "string" ? value : String(value)); | |
} | |
} | |
export function render(value: unknown) { | |
return Readable.from(renderInternal(value), { encoding: "utf8" }); | |
} | |
// unit tests | |
async function test() { | |
// helper | |
async function streamToString(stream: Readable) { | |
let s = ""; | |
for await (const chunk of stream) { | |
s += chunk; | |
} | |
return s; | |
} | |
// templates | |
const a = html`<p>hello</p>`; | |
const b = [a, a, null, "", undefined, a]; | |
const c = html`<div>${a}</div>`; | |
// render a single template result | |
const stream1 = render(a); | |
assert.strictEqual(await streamToString(stream1), "<p>hello</p>"); | |
// render a mixed array | |
const stream2 =render(b); | |
assert.strictEqual( | |
await streamToString(stream2), | |
"<p>hello</p><p>hello</p><p>hello</p>" | |
); | |
// render a mixed array including streams | |
const stream3 = Readable.from( | |
renderInternal([ | |
null, | |
a, | |
render(a), | |
render(a) | |
]), | |
{ encoding: "utf8" } | |
); | |
assert.strictEqual( | |
await streamToString(stream3), | |
"<p>hello</p><p>hello</p><p>hello</p>" | |
); | |
// render nothing | |
const stream4 = render(null); | |
assert.strictEqual(await streamToString(stream4), ""); | |
// render nested template | |
const stream5 = render(c); | |
assert.strictEqual(await streamToString(stream5), "<div><p>hello</p></div>"); | |
// render another stream | |
const contentStream = createReadStream("./content.txt", { encoding: "utf8" }); | |
const d = html`<main>${contentStream}</main>`; | |
const stream6 = render(d); | |
assert.strictEqual( | |
await streamToString(stream6), | |
"<main><span>some content</span></main>" | |
); | |
} | |
test(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment