export type HProps = Record | null | undefined; export type HChild = string | undefined | (() => string); export type HMXComponent = (props: HProps, ...children: Array) => string; export type HTag = string | HMXComponent; const voidTags = new Set(['area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr']); function* _h(tag: string, attrs: HProps, children: Array) { yield `<${tag}`; if (attrs) { yield ' '; for (let [key, value] of Object.entries(attrs)) { yield `${key}="${value}"`; } } yield '>'; // Void tags have no children. if (voidTags.has(tag)) { return; } for (let child of children) { // Special: If a child is a function, call it automatically. // This lets you do {() => ...} without the extra boilerplate // of wrapping it in a function and calling it. if (typeof child === 'function') { yield child(); } else if (typeof child === 'string') { yield child; } else if (!child) { // do nothing, safe to ignore falsey values. } else { yield child; } } yield ``; } export async function h(tag: HTag, attrs: HProps, ...pChildren: Array>) { const children = await Promise.all(pChildren.flat(Infinity)); if (typeof tag === 'function') { // We assume it's an hmx component return tag(attrs, ...children); } return Array.from(_h(tag, attrs, children)).join(''); } export function Fragment(_: HProps, ...children: Array) { return children.join(''); }