@travetto/email-inky
Version:
Email Inky templating module
92 lines (84 loc) • 3.36 kB
text/typescript
import { isJSXElement, JSXElement, createFragment, JSXFragmentType, JSXChild } from '@travetto/email-inky/jsx-runtime';
import { castTo } from '@travetto/runtime';
import { EMPTY_ELEMENT, getComponentName, JSXElementByFn, c } from '../components';
import { RenderProvider, RenderState } from '../types';
import { RenderContext, RenderContextInit } from './context';
/**
* Inky Renderer
*/
export class InkyRenderer {
static async #render(
ctx: RenderContext,
renderer: RenderProvider<RenderContext>,
node: JSXChild[] | JSXChild | null | undefined,
stack: JSXElement[] = []
): Promise<string> {
if (node === null || node === undefined) {
return '';
} else if (Array.isArray(node)) {
const out: string[] = [];
const nextStack = [...stack, { key: '', props: { children: node }, type: JSXFragmentType }];
for (const el of node) {
out.push(await this.#render(ctx, renderer, el, nextStack));
}
return out.join('');
} else if (isJSXElement(node)) {
let final: JSXElement = node;
// Render simple element if needed
if (typeof node.type === 'function' && node.type !== JSXFragmentType) {
const out = castTo<Function>(node.type)(node.props);
final = out !== EMPTY_ELEMENT ? out : final;
}
if (final.type === JSXFragmentType) {
return this.#render(ctx, renderer, final.props.children ?? [], stack);
}
if (Array.isArray(final)) {
return this.#render(ctx, renderer, final, stack);
}
const name = getComponentName(final.type);
if (name in renderer) {
const recurse = (): Promise<string> => this.#render(ctx, renderer, final.props.children ?? [], [...stack, final]);
// @ts-expect-error
const state: RenderState<JSXElement, RenderContext> = {
el: final, props: final.props, recurse, stack, context: ctx
};
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
state.createState = (key, props) => this.createState(ctx, renderer, state, key, props);
// @ts-expect-error
return renderer[name](state);
} else {
console.log(final);
throw new Error(`Unknown element: ${final.type}`);
}
} else {
return `${node}`;
}
}
static createState<K extends keyof typeof c>(
ctx: RenderContext,
renderer: RenderProvider<RenderContext>,
state: RenderState<JSXElement, RenderContext>,
key: K,
props: JSXElementByFn<K>['props'],
// @ts-expect-error
): RenderState<JSXElementByFn<K>, RenderContext> {
const el = ctx.createElement(key, props);
const newStack: JSXElement[] = castTo([...state.stack, el]);
return { ...state, el, props: el.props, recurse: () => this.#render(ctx, renderer, el.props.children ?? [], newStack) };
}
/**
* Render a context given a specific renderer
* @param renderer
*/
static async render(
root: JSXElement,
provider: RenderProvider<RenderContext>,
context: RenderContextInit,
isRoot = true
): Promise<string> {
const ctx = new RenderContext(context);
const par: JSXElement = root.type === JSXFragmentType ? root : createFragment({ children: [root] });
const text = await this.#render(ctx, provider, par, []);
return provider.finalize(text, ctx, isRoot);
}
}