diff --git a/deno.json b/deno.json
index cf2b718..5a45916 100644
--- a/deno.json
+++ b/deno.json
@@ -2,6 +2,9 @@
"tasks": {
"dev": "deno run --allow-net src/main.ts"
},
+ "imports": {
+ "interest/jsx-runtime": "./src/jsx.ts"
+ },
"lint": {
"rules": {
"tags": [
@@ -11,14 +14,13 @@
}
},
"compilerOptions": {
+ "jsx": "react-jsx",
+ "jsxImportSource": "interest",
"lib": [
"deno.ns",
"esnext",
"dom",
"dom.iterable"
]
- },
- "imports": {
- "@std/assert": "jsr:@std/assert@1"
}
}
diff --git a/deno.lock b/deno.lock
index b058552..0bdf3be 100644
--- a/deno.lock
+++ b/deno.lock
@@ -1,20 +1,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": {
"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/join.ts": "8d03530ab89195185103b7da9dfc6327af13eabdcd44c7c63e42e27808f50ecf",
"https://deno.land/std@0.224.0/path/windows/normalize.ts": "78126170ab917f0ca355a9af9e65ad6bfa5be14d574c5fb09bb1920f52577780"
- },
- "workspace": {
- "dependencies": [
- "jsr:@std/assert@1"
- ]
}
}
diff --git a/src/app.tsx b/src/app.tsx
new file mode 100644
index 0000000..d1560ac
--- /dev/null
+++ b/src/app.tsx
@@ -0,0 +1,13 @@
+/**
+ * Copyright (c) 2025 xwra
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+export default function App() {
+ return (
+ <>
+
JSX Page
+ meowing chunk by chunk
+ >
+ );
+}
diff --git a/src/global.d.ts b/src/global.d.ts
new file mode 100644
index 0000000..855ec42
--- /dev/null
+++ b/src/global.d.ts
@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) 2025 xwra
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+///
+
+import type { ChunkedStream } from "./stream.ts";
+
+declare global {
+ namespace JSX {
+ type Element = (chunks: ChunkedStream) => Promise;
+ interface IntrinsicElements {
+ [key: string]: ElementProps;
+ }
+ interface ElementProps {
+ [key: string]: any;
+ }
+ }
+}
diff --git a/src/html.ts b/src/html.ts
index a0f3aef..fcb9bdd 100644
--- a/src/html.ts
+++ b/src/html.ts
@@ -71,9 +71,9 @@ type TagFn = {
(fn: () => any): TagRes;
};
-type HtmlProxy = { [K in keyof HTMLElementTagNameMap]: TagFn } & {
+export type HtmlProxy = { [K in keyof HTMLElementTagNameMap]: TagFn } & {
[key: string]: TagFn;
-};
+} & { render(child: any): Promise };
const isTemplateLiteral = (arg: any): arg is TemplateStringsArray =>
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;
}
diff --git a/src/jsx.ts b/src/jsx.ts
new file mode 100644
index 0000000..d508e50
--- /dev/null
+++ b/src/jsx.ts
@@ -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 = {},
+) {
+ return async (chunks: ChunkedStream) => {
+ 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;
diff --git a/src/main.ts b/src/main.ts
index e59c383..d5772a7 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -5,18 +5,18 @@
import { html } from "./html.ts";
import { createHtmlStream } from "./http.ts";
+import App from "./app.tsx";
Deno.serve({
port: 8080,
async handler() {
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 p({ class: "oh hey" }, "meowing chunk by chunk");
+ await App()(stream.chunks);
ol(async () => {
- const fruits = ["Apple", "Banana", "Cherry"];
+ const fruits = ["TSX support", "Apple", "Banana", "Cherry"];
for (const fruit of fruits) {
await new Promise((r) => setTimeout(r, 500));
await li(fruit);