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