131 lines
2.7 KiB
TypeScript
131 lines
2.7 KiB
TypeScript
import "./types.ts";
|
|
|
|
const Fragment = Symbol("fragment");
|
|
|
|
declare namespace JSX {
|
|
interface Element {}
|
|
interface IntrinsicElements {
|
|
div: any;
|
|
}
|
|
}
|
|
|
|
export { Fragment };
|
|
|
|
export type Element = {
|
|
component: Component | string;
|
|
props: any;
|
|
children: any[];
|
|
};
|
|
export type ComponentRetElm = Element | Element[];
|
|
export type Component = (
|
|
props: any,
|
|
children: any
|
|
) => ComponentRetElm | Promise<ComponentRetElm>;
|
|
|
|
export function h(
|
|
component: string | Component,
|
|
props: any,
|
|
...children: Element[]
|
|
): Element {
|
|
return {
|
|
component,
|
|
props,
|
|
children,
|
|
};
|
|
}
|
|
|
|
export async function renderSSR(element: Element | string): Promise<string> {
|
|
if (typeof element === "string") return element;
|
|
else if (typeof element.component === "string")
|
|
return await renderHTML(element as Element);
|
|
else if (typeof element.component === "function")
|
|
return await renderCustom(element as Element);
|
|
|
|
console.warn("renderSSR: invalid element", element);
|
|
|
|
return "";
|
|
}
|
|
|
|
const selfClosing = new Set([
|
|
"area",
|
|
"base",
|
|
"br",
|
|
"col",
|
|
"embed",
|
|
"hr",
|
|
"img",
|
|
"input",
|
|
"link",
|
|
"meta",
|
|
"param",
|
|
"source",
|
|
"track",
|
|
"wbr",
|
|
]);
|
|
|
|
function flatDeep(arr: any, d = Infinity): any[] {
|
|
if (Array.isArray(arr) && d >= 0) {
|
|
let res = [];
|
|
for (const val of arr) {
|
|
const v = flatDeep(val, d - 1);
|
|
res.push(...v);
|
|
}
|
|
return res;
|
|
}
|
|
return [arr];
|
|
}
|
|
|
|
function cleanChildren(children: any) {
|
|
return flatDeep(children).filter((e) => !!e);
|
|
}
|
|
|
|
async function renderHTML(element: Element) {
|
|
if (typeof element.component !== "string")
|
|
throw new Error("Internal consistency error");
|
|
|
|
let props = "";
|
|
|
|
for (const key in element.props) {
|
|
if (key == "innerHTML") continue;
|
|
props += `${key}="${element.props[key] || ""}" `;
|
|
}
|
|
|
|
const tag = element.component;
|
|
|
|
if (selfClosing.has(element.component)) {
|
|
return `<${tag} ${props}/>`;
|
|
} else {
|
|
let inner = "";
|
|
if (element.props && element.props["innerHTML"]) {
|
|
inner = element.props["innerHTML"];
|
|
} else {
|
|
const children = cleanChildren(element.children);
|
|
inner = (
|
|
await Promise.all(children.map((child) => renderSSR(child)))
|
|
).join("");
|
|
}
|
|
return `<${tag} ${props}>${inner || ""}</${tag}>`;
|
|
}
|
|
}
|
|
|
|
async function renderCustom(element: Element) {
|
|
if (typeof element.component === "string")
|
|
throw new Error("Internal consistency error");
|
|
|
|
const res = await Promise.resolve(
|
|
element.component(
|
|
{
|
|
...element.props,
|
|
children: element.children,
|
|
},
|
|
element.children
|
|
)
|
|
);
|
|
|
|
const ch = (
|
|
await Promise.all(cleanChildren(res).map((child) => renderSSR(child)))
|
|
).join("");
|
|
|
|
return ch;
|
|
}
|