refac: move ren to another package
This commit is contained in:
parent
016889f6ef
commit
bae5c0d8be
11 changed files with 37 additions and 189 deletions
28
package-lock.json
generated
28
package-lock.json
generated
|
@ -4,6 +4,25 @@
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
|
"dependencies": {
|
||||||
|
"ren": "github:pleshevskiy/ren"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^17.0.21",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^5.14.0",
|
||||||
|
"@typescript-eslint/parser": "^5.14.0",
|
||||||
|
"eslint": "^8.10.0",
|
||||||
|
"eslint-config-prettier": "^8.5.0",
|
||||||
|
"eslint-plugin-prettier": "^4.0.0",
|
||||||
|
"prettier": "^2.5.1",
|
||||||
|
"tsc-watch": "^4.6.0",
|
||||||
|
"typescript": "^4.6.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"../../sandbox/ren": {
|
||||||
|
"version": "0.0.1",
|
||||||
|
"extraneous": true,
|
||||||
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^17.0.21",
|
"@types/node": "^17.0.21",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.14.0",
|
"@typescript-eslint/eslint-plugin": "^5.14.0",
|
||||||
|
@ -1327,6 +1346,11 @@
|
||||||
"url": "https://github.com/sponsors/mysticatea"
|
"url": "https://github.com/sponsors/mysticatea"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ren": {
|
||||||
|
"version": "0.0.1",
|
||||||
|
"resolved": "git+ssh://git@github.com/pleshevskiy/ren.git#9a783eba321b32fdbbb385fee3277fc08e80cb92",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/resolve-from": {
|
"node_modules/resolve-from": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
||||||
|
@ -2597,6 +2621,10 @@
|
||||||
"integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==",
|
"integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"ren": {
|
||||||
|
"version": "git+ssh://git@github.com/pleshevskiy/ren.git#9a783eba321b32fdbbb385fee3277fc08e80cb92",
|
||||||
|
"from": "ren@git+ssh://git@github.com:pleshevskiy/ren.git"
|
||||||
|
},
|
||||||
"resolve-from": {
|
"resolve-from": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
||||||
|
|
|
@ -9,5 +9,8 @@
|
||||||
"prettier": "^2.5.1",
|
"prettier": "^2.5.1",
|
||||||
"tsc-watch": "^4.6.0",
|
"tsc-watch": "^4.6.0",
|
||||||
"typescript": "^4.6.2"
|
"typescript": "^4.6.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"ren": "github:pleshevskiy/ren"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { AnyNode, Elem } from "../ren/nodes.mjs";
|
import { AnyNode, Elem } from "ren";
|
||||||
|
|
||||||
export async function Layout(page: AnyNode): Promise<Elem> {
|
export async function Layout(page: AnyNode): Promise<Elem> {
|
||||||
return new Elem("html")
|
return new Elem("html")
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { AnyNode, Elem, Frag } from "../ren/nodes.mjs";
|
import { AnyNode, Elem, Frag } from "ren";
|
||||||
|
|
||||||
export async function PageLayout(children: AnyNode[]): Promise<Frag> {
|
export async function PageLayout(children: AnyNode[]): Promise<Frag> {
|
||||||
return new Frag()
|
return new Frag()
|
||||||
|
|
|
@ -1,120 +0,0 @@
|
||||||
import { isNil, Nilable } from "../lang.mjs";
|
|
||||||
|
|
||||||
export type AnyNode = AnySyncNode | AnyAsyncNode;
|
|
||||||
export type AnyAsyncNode = Promise<AnySyncNode>;
|
|
||||||
export type AnySyncNode = TextNode | Elem | Frag;
|
|
||||||
|
|
||||||
export class TextNode extends String {}
|
|
||||||
|
|
||||||
export function F(children: AnyNode[]): Frag {
|
|
||||||
return new Frag().withChildren(children);
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Frag {
|
|
||||||
#children: Nilable<AnyNode[]>;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.#children = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
get children(): Nilable<AnyNode[]> {
|
|
||||||
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 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;
|
|
||||||
#attrs: ElemAttrs;
|
|
||||||
#isSelfClosed: boolean;
|
|
||||||
|
|
||||||
constructor(tagName: string) {
|
|
||||||
super();
|
|
||||||
this.#tagName = tagName;
|
|
||||||
this.#attrs = {};
|
|
||||||
this.#isSelfClosed = selfClosedTagNames.has(tagName);
|
|
||||||
}
|
|
||||||
|
|
||||||
get tagName(): string {
|
|
||||||
return this.#tagName;
|
|
||||||
}
|
|
||||||
|
|
||||||
get attrs(): Record<string, unknown> {
|
|
||||||
return this.#attrs;
|
|
||||||
}
|
|
||||||
|
|
||||||
withAttrs(attrs: Record<string, unknown>): Elem {
|
|
||||||
Object.entries(attrs).forEach(([key, value]) => this.addAttr(key, value));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
withAttr(name: string, value: unknown): Elem {
|
|
||||||
this.addAttr(name, value);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
addAttr(name: string, value: unknown): void {
|
|
||||||
this.#attrs[name] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
addChild(node: AnySyncNode): void {
|
|
||||||
if (this.#isSelfClosed)
|
|
||||||
throw new Error("You cannot add child to self closed element");
|
|
||||||
super.addChild(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const selfClosedTagNames = new Set([
|
|
||||||
"area",
|
|
||||||
"base",
|
|
||||||
"br",
|
|
||||||
"col",
|
|
||||||
"embed",
|
|
||||||
"hr",
|
|
||||||
"img",
|
|
||||||
"input",
|
|
||||||
"link",
|
|
||||||
"meta",
|
|
||||||
"param",
|
|
||||||
"source",
|
|
||||||
"track",
|
|
||||||
"wbr",
|
|
||||||
]);
|
|
|
@ -1,58 +0,0 @@
|
||||||
import { Renderer } from "./types.mjs";
|
|
||||||
import { isBool, isNil, Nullable } from "../lang.mjs";
|
|
||||||
import { AnyNode, Elem, Frag, TextNode } from "./nodes.mjs";
|
|
||||||
|
|
||||||
export class StrRenderer implements Renderer<string> {
|
|
||||||
async render(node: Elem | Promise<Elem>): Promise<string> {
|
|
||||||
return encodeNode(await node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function encodeAnyNode(node: AnyNode): Promise<string> {
|
|
||||||
const syncNode = await node;
|
|
||||||
return syncNode instanceof TextNode
|
|
||||||
? encodeTextNode(syncNode)
|
|
||||||
: encodeNode(syncNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
function encodeTextNode(node: TextNode): string {
|
|
||||||
return String(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function encodeNode(node: Elem | Frag): Promise<string> {
|
|
||||||
const encodedChildren = isNil(node.children)
|
|
||||||
? undefined
|
|
||||||
: await Promise.all(node.children.map(encodeAnyNode));
|
|
||||||
return node instanceof Elem
|
|
||||||
? encodeHtmlElement(node.tagName, node.attrs, encodedChildren)
|
|
||||||
: encodeHtmlFragment(encodedChildren);
|
|
||||||
}
|
|
||||||
|
|
||||||
function encodeHtmlFragment(children?: string[]): string {
|
|
||||||
return children?.join("") ?? "";
|
|
||||||
}
|
|
||||||
|
|
||||||
function encodeHtmlElement(
|
|
||||||
tagName: string,
|
|
||||||
attrs?: Record<string, unknown>,
|
|
||||||
children?: string[]
|
|
||||||
): string {
|
|
||||||
const open = `<${tagName} ${encodeAttrs(attrs)}>`;
|
|
||||||
if (isNil(children)) return open;
|
|
||||||
return `${open}${children.join("")}</${tagName}>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function encodeAttrs(attrs?: Record<string, unknown>): Nullable<string> {
|
|
||||||
if (!attrs) return "";
|
|
||||||
|
|
||||||
return Object.entries(attrs)
|
|
||||||
.map(([key, value]) => encodeAttr(key, value))
|
|
||||||
.filter(Boolean)
|
|
||||||
.join(" ");
|
|
||||||
}
|
|
||||||
|
|
||||||
function encodeAttr(key: string, value: unknown): Nullable<string> {
|
|
||||||
if (isNil(value)) return null;
|
|
||||||
if (isBool(value)) return value ? key : null;
|
|
||||||
return `${key}="${value}"`;
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
import { Elem } from "./nodes.mjs";
|
|
||||||
|
|
||||||
export interface Renderer<T> {
|
|
||||||
render(node: Elem | Promise<Elem>): Promise<T>;
|
|
||||||
}
|
|
|
@ -2,7 +2,7 @@ import * as http from "http";
|
||||||
import { Layout } from "./components/layout.mjs";
|
import { Layout } from "./components/layout.mjs";
|
||||||
import { ServerConfig } from "./config.mjs";
|
import { ServerConfig } from "./config.mjs";
|
||||||
import { debug, info } from "./log.mjs";
|
import { debug, info } from "./log.mjs";
|
||||||
import { StrRenderer } from "./ren/str.mjs";
|
import { StrRenderer } from "ren";
|
||||||
import { AboutPage } from "./views/about.mjs";
|
import { AboutPage } from "./views/about.mjs";
|
||||||
import { E404 } from "./views/e404.mjs";
|
import { E404 } from "./views/e404.mjs";
|
||||||
import { WorksPage } from "./views/works.mjs";
|
import { WorksPage } from "./views/works.mjs";
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { PageLayout } from "../components/page_layout.mjs";
|
import { PageLayout } from "../components/page_layout.mjs";
|
||||||
import { AnyAsyncNode, Elem } from "../ren/nodes.mjs";
|
import { AnyAsyncNode, Elem } from "ren";
|
||||||
|
|
||||||
export async function AboutPage(): AnyAsyncNode {
|
export async function AboutPage(): AnyAsyncNode {
|
||||||
return PageLayout([new Elem("p").withText("Привет мир")]);
|
return PageLayout([new Elem("p").withText("Привет мир")]);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { PageLayout } from "../components/page_layout.mjs";
|
import { PageLayout } from "../components/page_layout.mjs";
|
||||||
import { AnyAsyncNode, Elem } from "../ren/nodes.mjs";
|
import { AnyAsyncNode, Elem } from "ren";
|
||||||
|
|
||||||
export async function E404(): AnyAsyncNode {
|
export async function E404(): AnyAsyncNode {
|
||||||
return PageLayout([new Elem("p").withText("Page not found")]);
|
return PageLayout([new Elem("p").withText("Page not found")]);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { PageLayout } from "../components/page_layout.mjs";
|
import { PageLayout } from "../components/page_layout.mjs";
|
||||||
import { AnyAsyncNode, Elem } from "../ren/nodes.mjs";
|
import { AnyAsyncNode, Elem } from "ren";
|
||||||
|
|
||||||
export async function WorksPage(): AnyAsyncNode {
|
export async function WorksPage(): AnyAsyncNode {
|
||||||
return PageLayout([new Elem("p").withText("Works")]);
|
return PageLayout([new Elem("p").withText("Works")]);
|
||||||
|
|
Loading…
Reference in a new issue