UNPKG

@travetto/email-inky

Version:

Email Inky templating module

92 lines (84 loc) 3.42 kB
import { castTo } from '@travetto/runtime'; import { EMPTY_ELEMENT, getComponentName, type JSXElementByFn, type c } from '../components.ts'; import type { RenderProvider, RenderState } from '../types.ts'; import { RenderContext, type RenderContextInit } from './context.ts'; import { isJSXElement, type JSXElement, createFragment, JSXFragmentType, type JSXChild } from '../../support/jsx-runtime.ts'; /** * Inky Renderer */ export class InkyRenderer { static async #render( ctx: RenderContext, renderer: RenderProvider<RenderContext>, input: JSXChild[] | JSXChild | null | undefined, stack: JSXElement[] = [] ): Promise<string> { if (input === null || input === undefined) { return ''; } else if (Array.isArray(input)) { const out: string[] = []; const nextStack = [...stack, { key: '', props: { children: input }, type: JSXFragmentType }]; for (const node of input) { out.push(await this.#render(ctx, renderer, node, nextStack)); } return out.join(''); } else if (isJSXElement(input)) { let final: JSXElement = input; // Render simple element if needed if (typeof input.type === 'function' && input.type !== JSXFragmentType) { const out = castTo<Function>(input.type)(input.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> = { node: 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 `${input}`; } } 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 node = ctx.createElement(key, props); const newStack: JSXElement[] = castTo([...state.stack, node]); return { ...state, node, props: node.props, recurse: () => this.#render(ctx, renderer, node.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); } }