/** @jsxRuntime classic */ /** @jsx h */ /** @jsxFrag Fragment */ /* sample-no-react.tsx: JSX with no library at all. Free sample from 99helpers.com/file-types/tsx The three comments above tell the compiler what to turn JSX into: text becomes h("tag", { a: "1" }, "text"), and <>… becomes h(Fragment, null, …). Here h() builds HTML as text, so the file needs no React and no npm install. Run: npx tsx sample-no-react.tsx or bun sample-no-react.tsx Check: npx tsc --noEmit --jsx react sample-no-react.tsx */ // h() returns this object rather than a bare string, so finished HTML is // never escaped a second time. type Html = { readonly html: string }; type Child = Html | string | number | boolean | null | undefined | Child[]; type Props = Record | null; type Component = (props: Record) => Html; // The types TypeScript checks JSX against: what returns, which // lowercase tags exist, and which prop carries the children. declare global { namespace JSX { type Element = Html; interface IntrinsicElements { [tag: string]: Record; } interface ElementChildrenAttribute { children: unknown; } } } const ENTITIES: Record = { "&": "&", "<": "<", ">": ">", '"': """ }; const escape = (text: string): string => text.replace(/[&<>"]/g, (c) => ENTITIES[c]); function render(child: Child): string { if (Array.isArray(child)) return child.map(render).join(""); if (child === null || child === undefined || typeof child === "boolean") return ""; if (typeof child === "object") return child.html; return escape(String(child)); } export function Fragment(props: { children?: Child }): Html { return { html: render(props.children ?? []) }; } export function h(tag: string | Component, props: Props, ...children: Child[]): Html { if (typeof tag === "function") return tag({ ...props, children }); const attrs = Object.entries(props ?? {}) .map(([name, value]) => ` ${name}="${escape(String(value))}"`) .join(""); return { html: `<${tag}${attrs}>${render(children)}` }; } // A typed component, used like any other JSX tag. function Item(props: { name: string; price: number; children?: Child }) { return (
  • {props.name}: €{props.price.toFixed(2)} {props.children}
  • ); } const menu: Array<[string, number]> = [ ["Thé vert", 3.5], ["Crème brûlée", 5.25], ["Fish & chips", 12], ]; const page = ( <>

    Café menu

      {menu.map(([name, price]) => ( {price > 10 ? (main) : null} ))}
    ); console.log(page.html);