Skip to content

Commit e7ac66d

Browse files
committed
feat: add react-19 app demo
1 parent 35ce4f7 commit e7ac66d

6 files changed

Lines changed: 164 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ steps.
88
* [Hono 4](./hono-4/)
99
* [Lit 3](./lit-3/)
1010
* [Preact 10](./preact-10/)
11+
* [React 19](./react-19/)
1112
* [Remix 3](./remix-3/)
1213
* [Solid 1](./solid-1/)
1314
* [Solid 2](./solid-2/)

react-19/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Buildless React 19 app

react-19/deno.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"imports": {
3+
"react": "https://esm.sh/*react@19.2.4?dev",
4+
"react/": "https://esm.sh/*react@19.2.4&dev/",
5+
"react-dom": "https://esm.sh/*react-dom@19.2.4?dev",
6+
"react-dom/": "https://esm.sh/*react-dom@19.2.4&dev/",
7+
"scheduler": "https://esm.sh/*scheduler@^0.27.0?dev"
8+
},
9+
"compilerOptions": {
10+
"erasableSyntaxOnly": true,
11+
"lib": ["esnext", "dom", "dom.iterable", "dom.asynciterable"]
12+
}
13+
}

react-19/index.html

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Buildless React 19 app</title>
7+
<script type="importmap">
8+
{
9+
"imports": {
10+
"react": "https://esm.sh/*react@19.2.4?dev",
11+
"react/": "https://esm.sh/*react@19.2.4&dev/",
12+
"react-dom": "https://esm.sh/*react-dom@19.2.4?dev",
13+
"react-dom/": "https://esm.sh/*react-dom@19.2.4&dev/",
14+
"scheduler": "https://esm.sh/*scheduler@^0.27.0?dev"
15+
}
16+
}
17+
</script>
18+
<script type="module">
19+
import {
20+
availablePresets,
21+
registerPlugin,
22+
registerPreset,
23+
} from "https://esm.sh/@babel/standalone@7.28.1";
24+
import react from "https://esm.sh/babel-plugin-react-compiler@1.0.0";
25+
26+
registerPreset("jsx", {
27+
presets: [[availablePresets["react"], { runtime: "automatic" }]],
28+
});
29+
registerPlugin("react", react);
30+
</script>
31+
<script
32+
type="text/babel"
33+
data-presets="jsx,typescript"
34+
data-plugins="react"
35+
data-type="module"
36+
src="./main.tsx"
37+
></script>
38+
</head>
39+
<body></body>
40+
</html>

react-19/main.tsx

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/** @jsxImportSource react */
2+
3+
import { useEffect, useState } from "react";
4+
import { createRoot } from "react-dom/client";
5+
import { fetchReadmeHTML, fetchSourceHTML } from "./utils.js";
6+
7+
const sources = {
8+
"./index.html": { lang: "html" },
9+
"./main.tsx": { lang: "tsx" },
10+
"./utils.js": { lang: "javascript" },
11+
} as const satisfies { [url: string]: { lang: string } };
12+
13+
function useSourceHTML(url: keyof typeof sources) {
14+
const [sourceHTML, setSourceHTML] = useState<string | undefined>(undefined);
15+
const [loading, setLoading] = useState(false);
16+
17+
useEffect(function updateSourceHTML() {
18+
const ctrl = new AbortController();
19+
setLoading(true);
20+
fetchSourceHTML(url, sources[url].lang, ctrl.signal)
21+
.then(setSourceHTML, (error) => {
22+
if (!ctrl.signal.aborted) throw error;
23+
})
24+
.finally(() => {
25+
if (!ctrl.signal.aborted) setLoading(false);
26+
});
27+
return () => ctrl.abort();
28+
}, [url]);
29+
30+
return [sourceHTML, { loading }] as const;
31+
}
32+
33+
function useReadmeHTML() {
34+
const [readmeHTML, setReadmeHTML] = useState<string | undefined>(undefined);
35+
36+
useEffect(function updateReadmeHTML() {
37+
const ctrl = new AbortController();
38+
fetchReadmeHTML(ctrl.signal)
39+
.then(setReadmeHTML, (error) => {
40+
if (!ctrl.signal.aborted) throw error;
41+
});
42+
return () => ctrl.abort();
43+
}, []);
44+
45+
return [readmeHTML] as const;
46+
}
47+
48+
function SourceView(props: {
49+
url: keyof typeof sources;
50+
onLoadingChange?: (loading: boolean) => void;
51+
}) {
52+
const [sourceHTML, { loading }] = useSourceHTML(props.url);
53+
54+
useEffect(() => {
55+
props.onLoadingChange?.(loading);
56+
}, [loading, props.onLoadingChange]);
57+
58+
return <div dangerouslySetInnerHTML={{ __html: sourceHTML ?? "" }}></div>;
59+
}
60+
61+
function SourcesView() {
62+
const [selectedSourceUrl, setSelectedSourceUrl] = useState<
63+
keyof typeof sources
64+
>("./index.html");
65+
const [loading, setLoading] = useState(false);
66+
67+
return (
68+
<>
69+
<label>
70+
Source:
71+
<select
72+
onChange={function handleChange(event) {
73+
setSelectedSourceUrl(
74+
(event.currentTarget as HTMLSelectElement)
75+
.value as keyof typeof sources,
76+
);
77+
}}
78+
>
79+
{Object.keys(sources).map((url) => (
80+
<option key={url} value={url}>{url}</option>
81+
))}
82+
</select>
83+
</label>
84+
{loading && <progress />}
85+
<SourceView
86+
url={selectedSourceUrl}
87+
onLoadingChange={setLoading}
88+
/>
89+
</>
90+
);
91+
}
92+
93+
function ReadmeView() {
94+
const [readmeHTML] = useReadmeHTML();
95+
96+
return <div dangerouslySetInnerHTML={{ __html: readmeHTML ?? "" }}></div>;
97+
}
98+
99+
function App() {
100+
return (
101+
<>
102+
<ReadmeView />
103+
<SourcesView />
104+
</>
105+
);
106+
}
107+
108+
createRoot(document.body).render(<App />);

react-19/utils.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../utils.js

0 commit comments

Comments
 (0)