initial jsx support

This commit is contained in:
laura 2025-11-01 14:50:30 -03:00
parent 2301c5d631
commit 11be5e979c
Signed by: w
GPG key ID: BCD2117C99E69817
7 changed files with 77 additions and 30 deletions

View file

@ -2,6 +2,9 @@
"tasks": { "tasks": {
"dev": "deno run --allow-net src/main.ts" "dev": "deno run --allow-net src/main.ts"
}, },
"imports": {
"interest/jsx-runtime": "./src/jsx.ts"
},
"lint": { "lint": {
"rules": { "rules": {
"tags": [ "tags": [
@ -11,14 +14,13 @@
} }
}, },
"compilerOptions": { "compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "interest",
"lib": [ "lib": [
"deno.ns", "deno.ns",
"esnext", "esnext",
"dom", "dom",
"dom.iterable" "dom.iterable"
] ]
},
"imports": {
"@std/assert": "jsr:@std/assert@1"
} }
} }

20
deno.lock generated
View file

@ -1,20 +1,5 @@
{ {
"version": "5", "version": "5",
"specifiers": {
"jsr:@std/assert@1": "1.0.15",
"jsr:@std/internal@^1.0.12": "1.0.12"
},
"jsr": {
"@std/assert@1.0.15": {
"integrity": "d64018e951dbdfab9777335ecdb000c0b4e3df036984083be219ce5941e4703b",
"dependencies": [
"jsr:@std/internal"
]
},
"@std/internal@1.0.12": {
"integrity": "972a634fd5bc34b242024402972cd5143eac68d8dffaca5eaa4dba30ce17b027"
}
},
"redirects": { "redirects": {
"https://deno.land/std/fs/walk.ts": "https://deno.land/std@0.224.0/fs/walk.ts" "https://deno.land/std/fs/walk.ts": "https://deno.land/std@0.224.0/fs/walk.ts"
}, },
@ -46,10 +31,5 @@
"https://deno.land/std@0.224.0/path/windows/from_file_url.ts": "ced2d587b6dff18f963f269d745c4a599cf82b0c4007356bd957cb4cb52efc01", "https://deno.land/std@0.224.0/path/windows/from_file_url.ts": "ced2d587b6dff18f963f269d745c4a599cf82b0c4007356bd957cb4cb52efc01",
"https://deno.land/std@0.224.0/path/windows/join.ts": "8d03530ab89195185103b7da9dfc6327af13eabdcd44c7c63e42e27808f50ecf", "https://deno.land/std@0.224.0/path/windows/join.ts": "8d03530ab89195185103b7da9dfc6327af13eabdcd44c7c63e42e27808f50ecf",
"https://deno.land/std@0.224.0/path/windows/normalize.ts": "78126170ab917f0ca355a9af9e65ad6bfa5be14d574c5fb09bb1920f52577780" "https://deno.land/std@0.224.0/path/windows/normalize.ts": "78126170ab917f0ca355a9af9e65ad6bfa5be14d574c5fb09bb1920f52577780"
},
"workspace": {
"dependencies": [
"jsr:@std/assert@1"
]
} }
} }

13
src/app.tsx Normal file
View file

@ -0,0 +1,13 @@
/**
* Copyright (c) 2025 xwra
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
export default function App() {
return (
<>
<h1>JSX Page</h1>
<p class="oh hey">meowing chunk by chunk</p>
</>
);
}

20
src/global.d.ts vendored Normal file
View file

@ -0,0 +1,20 @@
/**
* Copyright (c) 2025 xwra
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/// <reference types="interest/jsx-runtime" />
import type { ChunkedStream } from "./stream.ts";
declare global {
namespace JSX {
type Element = (chunks: ChunkedStream<string>) => Promise<void>;
interface IntrinsicElements {
[key: string]: ElementProps;
}
interface ElementProps {
[key: string]: any;
}
}
}

View file

@ -71,9 +71,9 @@ type TagFn = {
(fn: () => any): TagRes; (fn: () => any): TagRes;
}; };
type HtmlProxy = { [K in keyof HTMLElementTagNameMap]: TagFn } & { export type HtmlProxy = { [K in keyof HTMLElementTagNameMap]: TagFn } & {
[key: string]: TagFn; [key: string]: TagFn;
}; } & { render(child: any): Promise<void> };
const isTemplateLiteral = (arg: any): arg is TemplateStringsArray => const isTemplateLiteral = (arg: any): arg is TemplateStringsArray =>
Array.isArray(arg) && "raw" in arg; Array.isArray(arg) && "raw" in arg;
@ -125,5 +125,8 @@ export function html(
}, },
}; };
return new Proxy({}, handler) as HtmlProxy; const proxy = new Proxy({}, handler) as HtmlProxy;
proxy.render = async (stuff: any) => void write(await render(stuff));
return proxy;
} }

29
src/jsx.ts Normal file
View file

@ -0,0 +1,29 @@
/**
* Copyright (c) 2025 xwra
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { html, type HtmlProxy } from "./html.ts";
import { ChunkedStream } from "./stream.ts";
let context;
export function jsx(
tag: string | typeof Fragment,
{ children }: Record<string, any> = {},
) {
return async (chunks: ChunkedStream<string>) => {
if (tag === Fragment) {
context = html(chunks);
for (const child of children) {
await context.render?.(child);
}
return;
}
await (context ||= html(chunks))[tag](...children);
};
}
export const Fragment = Symbol("Fragment");
export const jsxs = jsx;

View file

@ -5,18 +5,18 @@
import { html } from "./html.ts"; import { html } from "./html.ts";
import { createHtmlStream } from "./http.ts"; import { createHtmlStream } from "./http.ts";
import App from "./app.tsx";
Deno.serve({ Deno.serve({
port: 8080, port: 8080,
async handler() { async handler() {
const stream = await createHtmlStream({ lang: "en" }); const stream = await createHtmlStream({ lang: "en" });
const { h1, ol, p, li } = html(stream.chunks); const { ol, li } = html(stream.chunks);
await h1`Normal Streaming Page`; await App()(stream.chunks);
await p({ class: "oh hey" }, "meowing chunk by chunk");
ol(async () => { ol(async () => {
const fruits = ["Apple", "Banana", "Cherry"]; const fruits = ["TSX support", "Apple", "Banana", "Cherry"];
for (const fruit of fruits) { for (const fruit of fruits) {
await new Promise((r) => setTimeout(r, 500)); await new Promise((r) => setTimeout(r, 500));
await li(fruit); await li(fruit);