mono-jsx 0.1.3 β 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +230 -63
- package/bin/mono-jsx +2 -24
- package/jsx-runtime.mjs +166 -165
- package/package.json +10 -6
- package/setup.mjs +58 -0
- package/types/css.d.ts +1 -1
- package/types/html.d.ts +3 -2
- package/types/htmx.d.ts +791 -0
- package/types/index.d.ts +1 -0
- package/types/jsx.d.ts +0 -1
- package/types/mono.d.ts +9 -4
- package/types/render.d.ts +43 -1
package/README.md
CHANGED
|
@@ -2,32 +2,34 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|
|
|
5
|
-
mono-jsx is a JSX runtime that renders `<html>` element to
|
|
5
|
+
mono-jsx is a JSX runtime that renders `<html>` element to `Response` object in JavaScript runtimes like Node.js, Deno, Bun, Cloudflare Workers, etc.
|
|
6
6
|
|
|
7
7
|
- π No build step needed
|
|
8
8
|
- π¦ Lightweight (8KB gzipped), zero dependencies
|
|
9
9
|
- π« Minimal state runtime
|
|
10
|
-
- π¨ Complete Web API
|
|
10
|
+
- π¨ Complete Web API TypeScript definitions
|
|
11
11
|
- β³ Streaming rendering
|
|
12
12
|
- π Universal, works in Node.js, Deno, Bun, Cloudflare Workers, etc.
|
|
13
13
|
|
|
14
14
|
## Installation
|
|
15
15
|
|
|
16
|
-
mono-jsx supports all modern JavaScript runtimes including Node.js, Deno, Bun, Cloudflare Workers
|
|
17
|
-
You can install it via `npm
|
|
16
|
+
mono-jsx supports all modern JavaScript runtimes including Node.js, Deno, Bun, and Cloudflare Workers.
|
|
17
|
+
You can install it via `npm`, `deno`, or `bun`:
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
20
|
# Node.js, Cloudflare Workers, or other node-compatible runtimes
|
|
21
21
|
npm i mono-jsx
|
|
22
|
+
|
|
22
23
|
# Deno
|
|
23
24
|
deno add npm:mono-jsx
|
|
25
|
+
|
|
24
26
|
# Bun
|
|
25
27
|
bun add mono-jsx
|
|
26
28
|
```
|
|
27
29
|
|
|
28
30
|
## Setup JSX Runtime
|
|
29
31
|
|
|
30
|
-
To use mono-jsx as JSX runtime, add the following configuration to your `tsconfig.json`(`deno.json` for Deno):
|
|
32
|
+
To use mono-jsx as your JSX runtime, add the following configuration to your `tsconfig.json` (or `deno.json` for Deno):
|
|
31
33
|
|
|
32
34
|
```jsonc
|
|
33
35
|
{
|
|
@@ -38,30 +40,31 @@ To use mono-jsx as JSX runtime, add the following configuration to your `tsconfi
|
|
|
38
40
|
}
|
|
39
41
|
```
|
|
40
42
|
|
|
41
|
-
Alternatively, you can
|
|
43
|
+
Alternatively, you can use a pragma directive in your JSX file:
|
|
42
44
|
|
|
43
45
|
```js
|
|
44
46
|
/** @jsxImportSource mono-jsx */
|
|
45
47
|
```
|
|
46
48
|
|
|
47
|
-
You can also run `mono-jsx setup` to automatically add the configuration to your
|
|
49
|
+
You can also run `mono-jsx setup` to automatically add the configuration to your project:
|
|
48
50
|
|
|
49
51
|
```bash
|
|
50
52
|
# Node.js, Cloudflare Workers, or other node-compatible runtimes
|
|
51
53
|
npx mono-jsx setup
|
|
54
|
+
|
|
52
55
|
# Deno
|
|
53
|
-
deno run npm:mono-jsx setup
|
|
56
|
+
deno run -A npm:mono-jsx setup
|
|
57
|
+
|
|
54
58
|
# Bun
|
|
55
59
|
bunx mono-jsx setup
|
|
56
60
|
```
|
|
57
61
|
|
|
58
62
|
## Usage
|
|
59
63
|
|
|
60
|
-
mono-jsx allows you to return an `<html>` JSX element as a `Response` object in the `fetch` handler
|
|
64
|
+
mono-jsx allows you to return an `<html>` JSX element as a `Response` object in the `fetch` handler:
|
|
61
65
|
|
|
62
66
|
```tsx
|
|
63
67
|
// app.tsx
|
|
64
|
-
|
|
65
68
|
export default {
|
|
66
69
|
fetch: (req) => (
|
|
67
70
|
<html>
|
|
@@ -71,24 +74,23 @@ export default {
|
|
|
71
74
|
};
|
|
72
75
|
```
|
|
73
76
|
|
|
74
|
-
For Deno/Bun users, you can run the `app.tsx` directly
|
|
77
|
+
For Deno/Bun users, you can run the `app.tsx` directly:
|
|
75
78
|
|
|
76
79
|
```bash
|
|
77
80
|
deno serve app.tsx
|
|
78
81
|
bun run app.tsx
|
|
79
82
|
```
|
|
80
83
|
|
|
81
|
-
If you
|
|
84
|
+
If you're building a web app with [Cloudflare Workers](https://developers.cloudflare.com/workers/wrangler/commands/#dev), use `wrangler dev` to start local development:
|
|
82
85
|
|
|
83
86
|
```bash
|
|
84
87
|
npx wrangler dev app.tsx
|
|
85
88
|
```
|
|
86
89
|
|
|
87
|
-
**Node.js
|
|
90
|
+
**Node.js doesn't support JSX syntax or declarative fetch servers**, so we recommend using mono-jsx with [srvx](https://srvx.h3.dev/):
|
|
88
91
|
|
|
89
92
|
```tsx
|
|
90
93
|
// app.tsx
|
|
91
|
-
|
|
92
94
|
import { serve } from "srvx";
|
|
93
95
|
|
|
94
96
|
serve({
|
|
@@ -101,7 +103,7 @@ serve({
|
|
|
101
103
|
});
|
|
102
104
|
```
|
|
103
105
|
|
|
104
|
-
|
|
106
|
+
You'll need [tsx](https://www.npmjs.com/package/tsx) to start the app without a build step:
|
|
105
107
|
|
|
106
108
|
```bash
|
|
107
109
|
npx tsx app.tsx
|
|
@@ -109,27 +111,33 @@ npx tsx app.tsx
|
|
|
109
111
|
|
|
110
112
|
## Using JSX
|
|
111
113
|
|
|
112
|
-
mono-jsx uses [**JSX**](https://react.dev/learn/describing-the-ui) to describe the user interface, similar to React but with
|
|
114
|
+
mono-jsx uses [**JSX**](https://react.dev/learn/describing-the-ui) to describe the user interface, similar to React but with key differences.
|
|
113
115
|
|
|
114
116
|
### Using Standard HTML Property Names
|
|
115
117
|
|
|
116
|
-
mono-jsx adopts standard HTML property names, avoiding React's custom
|
|
118
|
+
mono-jsx adopts standard HTML property names, avoiding React's custom naming conventions:
|
|
117
119
|
|
|
118
|
-
- `className`
|
|
119
|
-
- `htmlFor`
|
|
120
|
-
- `onChange`
|
|
120
|
+
- `className` β `class`
|
|
121
|
+
- `htmlFor` β `for`
|
|
122
|
+
- `onChange` β `onInput`
|
|
121
123
|
|
|
122
|
-
### Composition `class`
|
|
124
|
+
### Composition with `class`
|
|
123
125
|
|
|
124
|
-
mono-jsx allows you to compose the `class` property using
|
|
126
|
+
mono-jsx allows you to compose the `class` property using arrays of strings, objects, or expressions:
|
|
125
127
|
|
|
126
128
|
```jsx
|
|
127
|
-
<div
|
|
129
|
+
<div
|
|
130
|
+
class={[
|
|
131
|
+
"container box",
|
|
132
|
+
isActive && "active",
|
|
133
|
+
{ hover: isHover },
|
|
134
|
+
]}
|
|
135
|
+
/>;
|
|
128
136
|
```
|
|
129
137
|
|
|
130
|
-
### Using Pseudo Classes and Media Queries in
|
|
138
|
+
### Using Pseudo Classes and Media Queries in `style`
|
|
131
139
|
|
|
132
|
-
mono-jsx
|
|
140
|
+
mono-jsx supports [pseudo classes](https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-classes), [pseudo elements](https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-elements), [media queries](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_media_queries/Using_media_queries), and [CSS nesting](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting/Using_CSS_nesting) in the `style` property:
|
|
133
141
|
|
|
134
142
|
```jsx
|
|
135
143
|
<a
|
|
@@ -148,14 +156,16 @@ mono-jsx allows you to use [pseudo classes](https://developer.mozilla.org/en-US/
|
|
|
148
156
|
|
|
149
157
|
### `<slot>` Element
|
|
150
158
|
|
|
151
|
-
mono-jsx uses [`<slot>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot)
|
|
159
|
+
mono-jsx uses [`<slot>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot) elements to render slotted content (equivalent to React's `children` property). You can also add the `name` attribute to define named slots:
|
|
152
160
|
|
|
153
161
|
```jsx
|
|
154
162
|
function Container() {
|
|
155
163
|
return (
|
|
156
164
|
<div class="container">
|
|
157
|
-
|
|
158
|
-
<slot
|
|
165
|
+
{/* Default slot */}
|
|
166
|
+
<slot />
|
|
167
|
+
{/* Named slot */}
|
|
168
|
+
<slot name="desc" />
|
|
159
169
|
</div>
|
|
160
170
|
);
|
|
161
171
|
}
|
|
@@ -163,7 +173,9 @@ function Container() {
|
|
|
163
173
|
function App() {
|
|
164
174
|
return (
|
|
165
175
|
<Container>
|
|
176
|
+
{/* This goes to the named slot */}
|
|
166
177
|
<p slot="desc">This is a description.</p>
|
|
178
|
+
{/* This goes to the default slot */}
|
|
167
179
|
<h1>Hello world!</h1>
|
|
168
180
|
</Container>
|
|
169
181
|
);
|
|
@@ -172,7 +184,7 @@ function App() {
|
|
|
172
184
|
|
|
173
185
|
### `html` Tag Function
|
|
174
186
|
|
|
175
|
-
mono-jsx
|
|
187
|
+
mono-jsx provides an `html` tag function to render raw HTML in JSX instead of React's `dangerouslySetInnerHTML`:
|
|
176
188
|
|
|
177
189
|
```jsx
|
|
178
190
|
function App() {
|
|
@@ -180,8 +192,7 @@ function App() {
|
|
|
180
192
|
}
|
|
181
193
|
```
|
|
182
194
|
|
|
183
|
-
The `html` tag function is
|
|
184
|
-
You also can use the `css` and `js`, that are just aliases of the `html` tag function, to render CSS and JavaScript code.
|
|
195
|
+
The `html` tag function is globally available without importing. You can also use `css` and `js` tag functions for CSS and JavaScript:
|
|
185
196
|
|
|
186
197
|
```jsx
|
|
187
198
|
function App() {
|
|
@@ -195,31 +206,38 @@ function App() {
|
|
|
195
206
|
```
|
|
196
207
|
|
|
197
208
|
> [!WARNING]
|
|
198
|
-
>
|
|
209
|
+
> The `html` tag function is **unsafe** and can cause [**XSS**](https://en.wikipedia.org/wiki/Cross-site_scripting) vulnerabilities.
|
|
199
210
|
|
|
200
211
|
### Event Handlers
|
|
201
212
|
|
|
202
|
-
mono-jsx
|
|
213
|
+
mono-jsx lets you write event handlers directly in JSX, similar to React:
|
|
203
214
|
|
|
204
215
|
```jsx
|
|
205
216
|
function Button() {
|
|
206
|
-
return
|
|
217
|
+
return (
|
|
218
|
+
<button onClick={(evt) => alert("BOOM!")}>
|
|
219
|
+
Click Me
|
|
220
|
+
</button>
|
|
221
|
+
);
|
|
207
222
|
}
|
|
208
223
|
```
|
|
209
224
|
|
|
210
225
|
> [!NOTE]
|
|
211
|
-
>
|
|
226
|
+
> Event handlers are never called on the server-side. They're serialized to strings and sent to the client. **This means you should NOT use server-side variables or functions in event handlers.**
|
|
212
227
|
|
|
213
228
|
```tsx
|
|
229
|
+
import { doSomething } from "some-library";
|
|
230
|
+
|
|
214
231
|
function Button(this: FC, props: { role: string }) {
|
|
215
232
|
let message = "BOOM!";
|
|
216
|
-
console.log(message); // only
|
|
233
|
+
console.log(message); // only executes on server-side
|
|
217
234
|
return (
|
|
218
235
|
<button
|
|
219
236
|
role={props.role}
|
|
220
237
|
onClick={(evt) => {
|
|
221
238
|
alert(message); // β `message` is a server-side variable
|
|
222
239
|
console.log(props.role); // β `props` is a server-side variable
|
|
240
|
+
doSomething(); // β `doSomething` is imported on the server-side
|
|
223
241
|
Deno.exit(0); // β `Deno` is unavailable in the browser
|
|
224
242
|
document.title = "BOOM!"; // β
`document` is a browser API
|
|
225
243
|
console.log(evt.target); // β
`evt` is the event object
|
|
@@ -232,7 +250,7 @@ function Button(this: FC, props: { role: string }) {
|
|
|
232
250
|
}
|
|
233
251
|
```
|
|
234
252
|
|
|
235
|
-
|
|
253
|
+
Additionally, mono-jsx supports the `mount` event for when elements are mounted in the client-side DOM:
|
|
236
254
|
|
|
237
255
|
```jsx
|
|
238
256
|
function App() {
|
|
@@ -244,14 +262,14 @@ function App() {
|
|
|
244
262
|
}
|
|
245
263
|
```
|
|
246
264
|
|
|
247
|
-
mono-jsx also accepts
|
|
265
|
+
mono-jsx also accepts functions for the `action` property on `form` elements, which will be called on form submission:
|
|
248
266
|
|
|
249
267
|
```tsx
|
|
250
268
|
function App() {
|
|
251
269
|
return (
|
|
252
270
|
<form
|
|
253
|
-
action={(data: FormData,
|
|
254
|
-
|
|
271
|
+
action={(data: FormData, event: SubmitEvent) => {
|
|
272
|
+
event.preventDefault(); // true
|
|
255
273
|
console.log(data.get("name"));
|
|
256
274
|
}}
|
|
257
275
|
>
|
|
@@ -262,23 +280,28 @@ function App() {
|
|
|
262
280
|
}
|
|
263
281
|
```
|
|
264
282
|
|
|
265
|
-
##
|
|
283
|
+
## Reactive
|
|
284
|
+
|
|
285
|
+
mono-jsx provides a minimal state runtime for updating the view based on client-side state changes:
|
|
286
|
+
|
|
287
|
+
### Using State
|
|
266
288
|
|
|
267
|
-
|
|
289
|
+
You can use `this` to define state in your components. The view will automatically update when the state changes:
|
|
268
290
|
|
|
269
291
|
```tsx
|
|
270
292
|
function Counter(
|
|
271
293
|
this: FC<{ count: number }>,
|
|
272
294
|
props: { initialCount?: number },
|
|
273
295
|
) {
|
|
296
|
+
// Initialize state
|
|
274
297
|
this.count = props.initialCount ?? 0;
|
|
298
|
+
|
|
275
299
|
return (
|
|
276
300
|
<div>
|
|
277
|
-
{/*
|
|
301
|
+
{/* render state */}
|
|
278
302
|
<span>{this.count}</span>
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
{/* update the state in event handlers */}
|
|
303
|
+
|
|
304
|
+
{/* Update state to trigger re-render */}
|
|
282
305
|
<button onClick={() => this.count--}>-</button>
|
|
283
306
|
<button onClick={() => this.count++}>+</button>
|
|
284
307
|
</div>
|
|
@@ -286,11 +309,77 @@ function Counter(
|
|
|
286
309
|
}
|
|
287
310
|
```
|
|
288
311
|
|
|
289
|
-
|
|
290
|
-
> The state cannot be used in an arrow function component, you should use a `function` declaration instead.
|
|
312
|
+
### Using App State
|
|
291
313
|
|
|
292
|
-
|
|
293
|
-
|
|
314
|
+
You can define app state by adding `appState` prop to the root `<html>` element. The app state is available in all components via `this.app.<stateKey>`. Changes to the app state will trigger re-renders in all components that use it:
|
|
315
|
+
|
|
316
|
+
```tsx
|
|
317
|
+
function Header(this: FC<{}, { title: string }>) {
|
|
318
|
+
return (
|
|
319
|
+
<header>
|
|
320
|
+
<h1>{this.app.title}</h1>
|
|
321
|
+
</header>
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function Footer(this: FC<{}, { title: string }>) {
|
|
326
|
+
return (
|
|
327
|
+
<footer>
|
|
328
|
+
<p>(c) 2025 {this.app.title}</p>
|
|
329
|
+
</footer>
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function Main(this: FC<{}, { title: string }>) {
|
|
334
|
+
return (
|
|
335
|
+
<main>
|
|
336
|
+
<h1>{this.app.title}</h1>
|
|
337
|
+
<h2>Changing the title</h2>
|
|
338
|
+
<input
|
|
339
|
+
onInput={(evt) => this.app.title = evt.target.value }
|
|
340
|
+
placeholder="Enter a new title"
|
|
341
|
+
/>
|
|
342
|
+
</main>
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export default {
|
|
347
|
+
fetch: (req) => (
|
|
348
|
+
<html appState={{ title: "Welcome to mono-jsx!" }}>
|
|
349
|
+
<Header />
|
|
350
|
+
<Main />
|
|
351
|
+
<Footer />
|
|
352
|
+
</html>
|
|
353
|
+
),
|
|
354
|
+
};
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### Using Computed State
|
|
358
|
+
|
|
359
|
+
You can use `this.computed` to create computed state based on state. The computed state will automatically update when the state changes:
|
|
360
|
+
|
|
361
|
+
```tsx
|
|
362
|
+
function App(this: FC<{ input: string }>) {
|
|
363
|
+
this.input = "Welcome to mono-jsx!";
|
|
364
|
+
return (
|
|
365
|
+
<div>
|
|
366
|
+
<h1>{this.computed(() => this.input + "!")}</h1>
|
|
367
|
+
|
|
368
|
+
<form action={(fd) => this.input = fd.get("input") as string}>
|
|
369
|
+
<input type="text" name="input" value={this.input} />
|
|
370
|
+
<button type="submit">Submit</button>
|
|
371
|
+
</form>
|
|
372
|
+
</div>
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### Limitation of States
|
|
378
|
+
|
|
379
|
+
1\. States cannot be used in arrow function components.
|
|
380
|
+
|
|
381
|
+
```tsx
|
|
382
|
+
// β Won't work - state updates won't refresh the view
|
|
294
383
|
const App = () => {
|
|
295
384
|
this.count = 0;
|
|
296
385
|
return (
|
|
@@ -300,40 +389,87 @@ const App = () => {
|
|
|
300
389
|
</div>
|
|
301
390
|
);
|
|
302
391
|
};
|
|
392
|
+
|
|
393
|
+
// β
Works correctly
|
|
394
|
+
function App(this: FC) {
|
|
395
|
+
this.count = 0;
|
|
396
|
+
return (
|
|
397
|
+
<div>
|
|
398
|
+
<span>{this.count}</span>
|
|
399
|
+
<button onClick={() => this.count++}>+</button>
|
|
400
|
+
</div>
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
2\. States cannot be computed outside of the `this.computed` method.
|
|
406
|
+
|
|
407
|
+
```tsx
|
|
408
|
+
// β Won't work - state updates won't refresh the view
|
|
409
|
+
function App(this: FC<{ message: string }>) {
|
|
410
|
+
this.message = "Welcome to mono-jsx!";
|
|
411
|
+
return (
|
|
412
|
+
<div>
|
|
413
|
+
<h1 title={this.message + "!"}>{this.message + "!"}</h1>
|
|
414
|
+
<button onClick={() => this.message = "Clicked"}>
|
|
415
|
+
Click Me
|
|
416
|
+
</button>
|
|
417
|
+
</div>
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// β
Works correctly
|
|
422
|
+
function App(this: FC) {
|
|
423
|
+
this.message = "Welcome to mono-jsx!";
|
|
424
|
+
return (
|
|
425
|
+
<div>
|
|
426
|
+
<h1 title={this.computed(() => this.message + "!")}>{this.computed(() => this.message + "!")}</h1>
|
|
427
|
+
<button onClick={() => this.message = "Clicked"}>
|
|
428
|
+
Click Me
|
|
429
|
+
</button>
|
|
430
|
+
</div>
|
|
431
|
+
);
|
|
432
|
+
}
|
|
303
433
|
```
|
|
304
434
|
|
|
305
435
|
## Built-in Elements
|
|
306
436
|
|
|
307
|
-
mono-jsx provides
|
|
437
|
+
mono-jsx provides built-in elements to help you build reactive UIs.
|
|
308
438
|
|
|
309
439
|
### `<toggle>` element
|
|
310
440
|
|
|
311
|
-
`<toggle>` element
|
|
441
|
+
The `<toggle>` element conditionally renders content based on a boolean value:
|
|
312
442
|
|
|
313
443
|
```tsx
|
|
314
444
|
function App(this: FC<{ show: boolean }>) {
|
|
315
445
|
this.show = false;
|
|
446
|
+
|
|
447
|
+
function toggle() {
|
|
448
|
+
this.show = !this.show;
|
|
449
|
+
}
|
|
450
|
+
|
|
316
451
|
return (
|
|
317
452
|
<div>
|
|
318
453
|
<toggle value={this.show}>
|
|
319
454
|
<h1>Welcome to mono-jsx!</h1>
|
|
320
455
|
</toggle>
|
|
321
|
-
|
|
456
|
+
|
|
457
|
+
<button onClick={toggle}>
|
|
458
|
+
{this.computed(() => this.show ? "Hide" : "Show")}
|
|
459
|
+
</button>
|
|
322
460
|
</div>
|
|
323
461
|
);
|
|
324
|
-
function toggle() {
|
|
325
|
-
this.show = !this.show;
|
|
326
|
-
}
|
|
327
462
|
}
|
|
328
463
|
```
|
|
329
464
|
|
|
330
465
|
### `<switch>` element
|
|
331
466
|
|
|
332
|
-
`<switch>` element
|
|
467
|
+
The `<switch>` element renders different content based on a value. Elements with matching `slot` attributes are displayed when their value matches, otherwise default content is shown:
|
|
333
468
|
|
|
334
469
|
```tsx
|
|
335
470
|
function App(this: FC<{ lang: "en" | "zh" | "emoji" }>) {
|
|
336
471
|
this.lang = "en";
|
|
472
|
+
|
|
337
473
|
return (
|
|
338
474
|
<div>
|
|
339
475
|
<switch value={this.lang}>
|
|
@@ -341,6 +477,7 @@ function App(this: FC<{ lang: "en" | "zh" | "emoji" }>) {
|
|
|
341
477
|
<h1 slot="zh">δ½ ε₯½οΌδΈηοΌ</h1>
|
|
342
478
|
<h1>βπβοΈ</h1>
|
|
343
479
|
</switch>
|
|
480
|
+
|
|
344
481
|
<button onClick={() => this.lang = "en"}>English</button>
|
|
345
482
|
<button onClick={() => this.lang = "zh"}>δΈζ</button>
|
|
346
483
|
<button onClick={() => this.lang = "emoji"}>Emoji</button>
|
|
@@ -351,10 +488,10 @@ function App(this: FC<{ lang: "en" | "zh" | "emoji" }>) {
|
|
|
351
488
|
|
|
352
489
|
## Streaming Rendering
|
|
353
490
|
|
|
354
|
-
mono-jsx renders your `<html>` as a readable stream,
|
|
491
|
+
mono-jsx renders your `<html>` as a readable stream, allowing async components to render asynchronously. You can use `placeholder` to display a loading state while waiting for async components to render:
|
|
355
492
|
|
|
356
493
|
```jsx
|
|
357
|
-
async function Sleep(ms) {
|
|
494
|
+
async function Sleep({ ms }) {
|
|
358
495
|
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
359
496
|
return <slot />;
|
|
360
497
|
}
|
|
@@ -363,6 +500,7 @@ export default {
|
|
|
363
500
|
fetch: (req) => (
|
|
364
501
|
<html>
|
|
365
502
|
<h1>Welcome to mono-jsx!</h1>
|
|
503
|
+
|
|
366
504
|
<Sleep ms={1000} placeholder={<p>Sleeping...</p>}>
|
|
367
505
|
<p>After 1 second</p>
|
|
368
506
|
</Sleep>
|
|
@@ -371,10 +509,10 @@ export default {
|
|
|
371
509
|
};
|
|
372
510
|
```
|
|
373
511
|
|
|
374
|
-
You can
|
|
512
|
+
You can set the `rendering` attribute to `"eager"` to force synchronous rendering (the `placeholder` will be ignored):
|
|
375
513
|
|
|
376
514
|
```jsx
|
|
377
|
-
async function Sleep(ms) {
|
|
515
|
+
async function Sleep({ ms }) {
|
|
378
516
|
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
379
517
|
return <slot />;
|
|
380
518
|
}
|
|
@@ -383,6 +521,7 @@ export default {
|
|
|
383
521
|
fetch: (req) => (
|
|
384
522
|
<html>
|
|
385
523
|
<h1>Welcome to mono-jsx!</h1>
|
|
524
|
+
|
|
386
525
|
<Sleep ms={1000} rendering="eager">
|
|
387
526
|
<p>After 1 second</p>
|
|
388
527
|
</Sleep>
|
|
@@ -391,9 +530,37 @@ export default {
|
|
|
391
530
|
};
|
|
392
531
|
```
|
|
393
532
|
|
|
533
|
+
## Using Context
|
|
534
|
+
|
|
535
|
+
You can use the `context` property in `this` to access context values in your components. The context is defined on the root `<html>` element:
|
|
536
|
+
|
|
537
|
+
```tsx
|
|
538
|
+
function Dash(this: FC<{}, {}, { auth: { uuid: string; name: string } }>) {
|
|
539
|
+
const { auth } = this.context;
|
|
540
|
+
return (
|
|
541
|
+
<div>
|
|
542
|
+
<h1>Welcome back, {auth.name}!</h1>
|
|
543
|
+
<p>Your UUID is {auth.uuid}</p>
|
|
544
|
+
</div>
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
export default {
|
|
549
|
+
fetch: async (req) => {
|
|
550
|
+
const auth = await doAuth(req);
|
|
551
|
+
return (
|
|
552
|
+
<html context={{ auth }} request={req}>
|
|
553
|
+
{!auth && <p>Please Login</p>}
|
|
554
|
+
{auth && <Dash />}
|
|
555
|
+
</html>
|
|
556
|
+
);
|
|
557
|
+
},
|
|
558
|
+
};
|
|
559
|
+
```
|
|
560
|
+
|
|
394
561
|
## Accessing Request Info
|
|
395
562
|
|
|
396
|
-
You can access
|
|
563
|
+
You can access request information in components via the `request` property in `this` which is set on the root `<html>` element:
|
|
397
564
|
|
|
398
565
|
```tsx
|
|
399
566
|
function RequestInfo(this: FC) {
|
|
@@ -419,7 +586,7 @@ export default {
|
|
|
419
586
|
|
|
420
587
|
## Customizing Response
|
|
421
588
|
|
|
422
|
-
|
|
589
|
+
Add `status` or `headers` attributes to the root `<html>` element to customize the response:
|
|
423
590
|
|
|
424
591
|
```jsx
|
|
425
592
|
export default {
|
package/bin/mono-jsx
CHANGED
|
@@ -1,34 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { readFile, writeFile } from "node:fs/promises";
|
|
4
3
|
import process from "node:process";
|
|
4
|
+
import { setup } from "../setup.mjs";
|
|
5
5
|
|
|
6
6
|
switch (process.argv[2]) {
|
|
7
7
|
case "setup":
|
|
8
|
-
setup()
|
|
9
|
-
console.log("β
JSX runtime setup complete.");
|
|
10
|
-
});
|
|
8
|
+
setup()
|
|
11
9
|
break;
|
|
12
10
|
default:
|
|
13
11
|
process.exit(0);
|
|
14
12
|
}
|
|
15
|
-
|
|
16
|
-
async function setup() {
|
|
17
|
-
let tsConfigPath = globalThis.Deno ? "deno.json" : "tsconfig.json";
|
|
18
|
-
let tsConfig = Object.create(null);
|
|
19
|
-
try {
|
|
20
|
-
const json = await readFile(tsConfigPath, "utf8");
|
|
21
|
-
tsConfig = JSON.parse(json);
|
|
22
|
-
} catch {
|
|
23
|
-
// ignore
|
|
24
|
-
}
|
|
25
|
-
tsConfig.compilerOptions = {
|
|
26
|
-
...tsConfig.compilerOptions,
|
|
27
|
-
jsx: "react-jsx",
|
|
28
|
-
jsxImportSource: "mono-jsx",
|
|
29
|
-
};
|
|
30
|
-
if (!globalThis.Deno) {
|
|
31
|
-
tsConfig.compilerOptions.alllowJs = true
|
|
32
|
-
}
|
|
33
|
-
await writeFile(tsConfigPath, JSON.stringify(tsConfig, null, 2));
|
|
34
|
-
}
|