Archived
1
0
Fork 0

feat(node): add fragment

feat(node): add async nodes
This commit is contained in:
Dmitriy Pleshevskiy 2022-03-16 22:23:29 +03:00
parent 0978aa8754
commit a0df5e48fa
3 changed files with 85 additions and 55 deletions

View file

@ -1,29 +1,75 @@
import { isNil, Nilable } from "./lang.mjs"; import { isNil, Nilable } from "./lang.mjs";
export type AnyNode = TextNode | Node; export type AnyNode = AnySyncNode | AnyAsyncNode;
export type AnyAsyncNode = Promise<AnySyncNode>;
export type AnySyncNode = TextNode | Elem | Frag;
export class TextNode { export class TextNode extends String {}
#text: string;
constructor(text: string) { export function F(children: AnyNode[]): Frag {
this.#text = text; return new Frag().withChildren(children);
}
export class Frag {
#children: Nilable<AnyNode[]>;
constructor() {
this.#children = undefined;
} }
get text(): string { get children(): Nilable<AnyNode[]> {
return this.#text; return this.#children;
}
withText(text: string): this {
this.addText(text);
return this;
}
addText(text: string): void {
this.addChild(new TextNode(text));
}
maybeWithChildren(nodes?: Nilable<AnyNode[]>): this {
if (isNil(nodes)) return this;
return this.withChildren(nodes);
}
withChildren(nodes: AnyNode[]): this {
nodes.forEach((n) => this.addChild(n));
return this;
}
withChild(node: AnyNode): this {
this.addChild(node);
return this;
}
addChild(node: AnyNode): void {
if (isNil(this.#children)) this.#children = [];
this.#children.push(node);
} }
} }
export class Node { export function E(
tagName: string,
attrs: ElemAttrs,
children?: Nilable<AnyNode[]>
): Elem {
return new Elem(tagName).withAttrs(attrs).maybeWithChildren(children);
}
export type ElemAttrs = Record<string, unknown>;
export class Elem extends Frag {
#tagName: string; #tagName: string;
#attrs: Record<string, unknown>; #attrs: ElemAttrs;
#children: Nilable<AnyNode[]>;
#isSelfClosed: boolean; #isSelfClosed: boolean;
constructor(tagName: string) { constructor(tagName: string) {
super();
this.#tagName = tagName; this.#tagName = tagName;
this.#attrs = {}; this.#attrs = {};
this.#children = undefined;
this.#isSelfClosed = selfClosedTagNames.has(tagName); this.#isSelfClosed = selfClosedTagNames.has(tagName);
} }
@ -35,16 +81,12 @@ export class Node {
return this.#attrs; return this.#attrs;
} }
get children(): Nilable<AnyNode[]> { withAttrs(attrs: Record<string, unknown>): Elem {
return this.#children;
}
withAttrs(attrs: Record<string, unknown>): Node {
Object.entries(attrs).forEach(([key, value]) => this.addAttr(key, value)); Object.entries(attrs).forEach(([key, value]) => this.addAttr(key, value));
return this; return this;
} }
withAttr(name: string, value: unknown): Node { withAttr(name: string, value: unknown): Elem {
this.addAttr(name, value); this.addAttr(name, value);
return this; return this;
} }
@ -53,30 +95,10 @@ export class Node {
this.#attrs[name] = value; this.#attrs[name] = value;
} }
withText(text: string): Node { addChild(node: AnySyncNode): void {
this.addText(text);
return this;
}
addText(text: string): void {
this.addChild(new TextNode(text));
}
withChildren(nodes: AnyNode[]): Node {
nodes.forEach((n) => this.addChild(n));
return this;
}
withChild(node: AnyNode): Node {
this.addChild(node);
return this;
}
addChild(node: AnyNode): void {
if (this.#isSelfClosed) if (this.#isSelfClosed)
throw new Error("You cannot add child to self closed element"); throw new Error("You cannot add child to self closed element");
if (isNil(this.#children)) this.#children = []; super.addChild(node);
this.#children.push(node);
} }
} }

View file

@ -1,30 +1,38 @@
import { Renderer } from "./types.mjs"; import { Renderer } from "./types.mjs";
import { AnyNode, Node, TextNode } from "./node.mjs";
import { isBool, isNil, Nullable } from "./lang.mjs"; import { isBool, isNil, Nullable } from "./lang.mjs";
import { AnyNode, Elem, Frag, TextNode } from "./nodes.mjs";
export class StrRenderer implements Renderer<string> { export class StrRenderer implements Renderer<string> {
async render(node: Node): Promise<string> { async render(node: Elem | Promise<Elem>): Promise<string> {
return encodeNode(node); return encodeNode(await node);
} }
} }
function encodeAnyNode(node: AnyNode): string { async function encodeAnyNode(node: AnyNode): Promise<string> {
return node instanceof TextNode ? encodeTextNode(node) : encodeNode(node); const syncNode = await node;
return syncNode instanceof TextNode
? encodeTextNode(syncNode)
: encodeNode(syncNode);
} }
function encodeTextNode(node: TextNode): string { function encodeTextNode(node: TextNode): string {
return node.text; return String(node);
} }
function encodeNode(node: Node): string { async function encodeNode(node: Elem | Frag): Promise<string> {
return encodeHtml( const encodedChildren = isNil(node.children)
node.tagName, ? undefined
node.attrs, : await Promise.all(node.children.map(encodeAnyNode));
node.children?.map(encodeAnyNode) return node instanceof Elem
); ? encodeHtmlElement(node.tagName, node.attrs, encodedChildren)
: encodeHtmlFragment(encodedChildren);
} }
function encodeHtml( function encodeHtmlFragment(children?: string[]): string {
return children?.join("") ?? "";
}
function encodeHtmlElement(
tagName: string, tagName: string,
attrs?: Record<string, unknown>, attrs?: Record<string, unknown>,
children?: string[] children?: string[]
@ -34,7 +42,7 @@ function encodeHtml(
return `${open}${children.join("")}</${tagName}>`; return `${open}${children.join("")}</${tagName}>`;
} }
function encodeAttrs(attrs?: Record<string, unknown>): string { function encodeAttrs(attrs?: Record<string, unknown>): Nullable<string> {
if (!attrs) return ""; if (!attrs) return "";
return Object.entries(attrs) return Object.entries(attrs)

View file

@ -1,5 +1,5 @@
import { Node } from "./node.mjs"; import { Elem } from "./nodes.mjs";
export interface Renderer<T> { export interface Renderer<T> {
render(node: Node): Promise<T>; render(node: Elem | Promise<Elem>): Promise<T>;
} }