summaryrefslogtreecommitdiff
path: root/src/frontend/h.ts
blob: c1e21dc956e63f8dea9586c2079dd4aeaa4d1ef2 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
export type HProps = Record<string, string> | null | undefined;
export type HChild = string | undefined | (() => string);
export type AstroComponent = (props: HProps, ...children: Array<HChild>) => string;
export type HTag = string | AstroComponent;

const voidTags = new Set(['area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr']);

/** Generator for primary h() function */
function* _h(tag: string, attrs: HProps, children: Array<HChild>) {
  if (tag === '!doctype') {
    yield '<!doctype ';
    if (attrs) {
      yield Object.keys(attrs).join(' ');
    }
    yield '>';
    return;
  }

  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 `</${tag}>`;
}

/** Astro‘s primary h() function. Allows it to use JSX-like syntax. */
export async function h(tag: HTag, attrs: HProps, ...pChildren: Array<Promise<HChild>>) {
  const children = await Promise.all(pChildren.flat(Infinity));
  if (typeof tag === 'function') {
    // We assume it's an astro component
    return tag(attrs, ...children);
  }

  return Array.from(_h(tag, attrs, children)).join('');
}

/** Fragment helper, similar to React.Fragment */
export function Fragment(_: HProps, ...children: Array<string>) {
  return children.join('');
}