UNPKG

@travetto/email-inky

Version:

Email Inky templating module

92 lines (84 loc) 3.36 kB
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); } }