Skip to content

Instantly share code, notes, and snippets.

@jdanyow
Last active May 21, 2021 02:59
Show Gist options
  • Save jdanyow/a4761670c16ff0901789a1496282572d to your computer and use it in GitHub Desktop.
Save jdanyow/a4761670c16ff0901789a1496282572d to your computer and use it in GitHub Desktop.
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