Skip to content

Instantly share code, notes, and snippets.

@axelhamil
Last active June 24, 2025 20:41
Show Gist options
  • Select an option

  • Save axelhamil/d68f74b44b5b2827a89ae0094f9e9a44 to your computer and use it in GitHub Desktop.

Select an option

Save axelhamil/d68f74b44b5b2827a89ae0094f9e9a44 to your computer and use it in GitHub Desktop.
Mini ReactTree in TypeScript: Typed Components to Render HTML
type NodeReactLike = {
  tag: string;
  attrs: Record<string, string>;
  children: NodeReactLike[];
  text: string | null;
};

const El = (
  tag: string,
  attrs: Record<string, string>,
  children: NodeReactLike[],
  text: string | null,
): NodeReactLike => {
  return {
    tag,
    attrs,
    children,
    text,
  };
};

const Txt = (text: string): NodeReactLike => {
  return {
    tag: "",
    attrs: {},
    children: [],
    text,
  };
};

const render = (node: NodeReactLike): string => {
  if (node.tag === "" && node.text !== null) return escapeHtml(node.text);

  let html = `<${node.tag}`;

  for (const [key, value] of Object.entries(node.attrs)) {
    html += ` ${key}="${escapeHtml(value)}"`;
  }

  html += ">";

  for (const child of node.children) {
    html += render(child);
  }

  html += `</${node.tag}>`;

  return html;
};

const escapeHtml = (text: string): string => {
  return text
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#39;");
};

const Button = (label: string, onClick: () => void): NodeReactLike => {
  return El("button", { onClick: onClick.toString() }, [Txt(label)], null);
};

const Page = El(
  "div",
  { className: "page" },
  [
    El(
      "header",
      { className: "header" },
      [Txt("🌟 Bienvenue sur notre site 🌟")],
      null,
    ),
    El(
      "main",
      { className: "main-content" },
      [
        El("h1", { className: "title" }, [Txt("Découvrez nos services")], null),
        El(
          "p",
          { className: "description" },
          [Txt("Une expérience incroyable vous attend")],
          null,
        ),
        El(
          "div",
          { className: "button-container" },
          [
            Button("🚀 Commencer", () => alert("Aventure commencée !")),
            Button("📖 En savoir plus", () =>
              alert("Plus d'informations bientôt !"),
            ),
          ],
          null,
        ),
      ],
      null,
    ),
    El(
      "footer",
      { className: "footer" },
      [Txt("© 2024 - Tous droits réservés")],
      null,
    ),
  ],
  null,
);

const renderPage = (): string => {
  return render(Page);
};

console.log(renderPage());
/*
<div className="page">
   <header className="header">🌟 Bienvenue sur notre site 🌟</header>
   <main className="main-content">
      <h1 className="title">Découvrez nos services</h1>
      <p className="description">Une expérience incroyable vous attend</p>
      <div className="button-container"><button onClick="() =&gt; alert(&quot;Aventure commencée !&quot;)">🚀 Commencer</button><button onClick="() =&gt; alert(&quot;Plus d&#39;informations bientôt !&quot;)">📖 En savoir plus</button></div>
   </main>
   <footer className="footer">© 2024 - Tous droits réservés</footer>
</div>
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment