mono-jsx 0.0.0-alpha.13 → 0.0.0-alpha.15
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 +97 -86
- package/jsx-runtime.mjs +134 -125
- package/package.json +4 -8
- package/types/jsx-runtime.d.ts +1 -7
- package/types/jsx.d.ts +15 -15
- package/types/mono.d.ts +6 -12
- package/types/render.d.ts +0 -1
- package/index.cjs +0 -2
- package/jsx-runtime.cjs +0 -705
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
mono-jsx is a JSX runtime that renders `<html>` element to a `Response` object in JavaScript runtimes like Node.js, Deno, Bun, Cloudflare Workers, etc.
|
|
6
6
|
|
|
7
7
|
- No build step needed
|
|
8
|
-
- Lightweight(
|
|
8
|
+
- Lightweight(8KB gzipped), zero dependencies
|
|
9
9
|
- Minimal state runtime
|
|
10
10
|
- Streaming rendering
|
|
11
11
|
- Universal, works in Node.js, Deno, Bun, Cloudflare Workers, etc.
|
|
@@ -36,14 +36,14 @@ bun add mono-jsx
|
|
|
36
36
|
|
|
37
37
|
## Setup JSX runtime
|
|
38
38
|
|
|
39
|
-
To use mono-jsx as
|
|
39
|
+
To use mono-jsx as JSX runtime, add the following configuration to your `tsconfig.json`(`deno.json` for Deno):
|
|
40
40
|
|
|
41
41
|
```jsonc
|
|
42
42
|
{
|
|
43
43
|
"compilerOptions": {
|
|
44
|
-
"allowJs": true, // required for `.jsx` extension in Node.js
|
|
45
44
|
"jsx": "react-jsx",
|
|
46
|
-
"jsxImportSource": "mono-jsx"
|
|
45
|
+
"jsxImportSource": "mono-jsx",
|
|
46
|
+
"allowJs": true // required for `.jsx` extension in Node.js
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
```
|
|
@@ -86,6 +86,8 @@ bun run app.jsx
|
|
|
86
86
|
**Node.js does not support JSX module and declarative fetch server**, we recommend using mono-jsx with [hono](https://hono.dev).
|
|
87
87
|
|
|
88
88
|
```jsx
|
|
89
|
+
// app.jsx
|
|
90
|
+
|
|
89
91
|
import { serve } from "@hono/node-server";
|
|
90
92
|
import { Hono } from "hono";
|
|
91
93
|
const app = new Hono();
|
|
@@ -214,16 +216,20 @@ function Button() {
|
|
|
214
216
|
> [!NOTE]
|
|
215
217
|
> the event handler would never be called in server-side. Instead it will be serialized to a string and sent to the client-side. **This means you should NOT use any server-side variables or functions in the event handler.**
|
|
216
218
|
|
|
217
|
-
```
|
|
218
|
-
function Button() {
|
|
219
|
+
```tsx
|
|
220
|
+
function Button(this: FC, props: { role: string }) {
|
|
219
221
|
let message = "BOOM!";
|
|
222
|
+
console.log(message); // only print message in server-side
|
|
220
223
|
return (
|
|
221
224
|
<button
|
|
225
|
+
role={props.role}
|
|
222
226
|
onClick={(evt) => {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
+
alert(message); // ❌ `message` is a server-side variable
|
|
228
|
+
console.log(props.role); // ❌ `props` is a server-side variable
|
|
229
|
+
Deno.exit(0); // ❌ `Deno` is unavailable in the browser
|
|
230
|
+
document.title = "BOOM!"; // ✅ `document` is a browser API
|
|
231
|
+
console.log(evt.target); // ✅ `evt` is the event object
|
|
232
|
+
this.count++; // ✅ update the state `count`
|
|
227
233
|
}}
|
|
228
234
|
>
|
|
229
235
|
Click Me
|
|
@@ -248,79 +254,40 @@ function App() {
|
|
|
248
254
|
|
|
249
255
|
mono-jsx provides a minimal state runtime that allows you to update view based on state changes in client-side.
|
|
250
256
|
|
|
251
|
-
```
|
|
252
|
-
function App(
|
|
253
|
-
|
|
254
|
-
|
|
257
|
+
```tsx
|
|
258
|
+
function App(
|
|
259
|
+
this: FC<{ count: number }>,
|
|
260
|
+
props: { initialCount?: number },
|
|
261
|
+
) {
|
|
262
|
+
this.count = props.initialCount ?? 0;
|
|
255
263
|
return (
|
|
256
264
|
<div>
|
|
257
265
|
{/* use the state */}
|
|
258
|
-
<span>{
|
|
259
|
-
{/* computed state */}
|
|
260
|
-
<span>doubled: {
|
|
266
|
+
<span>{this.count}</span>
|
|
267
|
+
{/* use computed state */}
|
|
268
|
+
<span>doubled: {this.computed(() => 2 * this.count)}</span>
|
|
261
269
|
{/* update the state in event handlers */}
|
|
262
|
-
<button onClick={() =>
|
|
263
|
-
<button onClick={() =>
|
|
270
|
+
<button onClick={() => this.count--}>-</button>
|
|
271
|
+
<button onClick={() => this.count++}>+</button>
|
|
264
272
|
</div>
|
|
265
273
|
);
|
|
266
274
|
}
|
|
267
275
|
```
|
|
268
276
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
```ts
|
|
272
|
-
declare global {
|
|
273
|
-
interface State {
|
|
274
|
-
count: number;
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
> [!NOTE]
|
|
280
|
-
> The `$state` and `$computed` are global variables injected by mono-jsx.
|
|
281
|
-
|
|
282
|
-
## Using `$context` Hook
|
|
283
|
-
|
|
284
|
-
The `$context` hook allows you to access `data` which is set in the root `<html>` element.
|
|
285
|
-
|
|
286
|
-
```tsx
|
|
287
|
-
interface Data {
|
|
288
|
-
foo: string;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
async function App() {
|
|
292
|
-
const { request, data } = $context<Data>();
|
|
293
|
-
return (
|
|
294
|
-
<p>
|
|
295
|
-
{request.method} {request.url} {data.foo}
|
|
296
|
-
</p>
|
|
297
|
-
);
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
export default {
|
|
301
|
-
fetch: (req) => (
|
|
302
|
-
<html request={req} data={{ foo: "bar" }}>
|
|
303
|
-
<h1>Hello World!</h1>
|
|
304
|
-
<App />
|
|
305
|
-
</html>
|
|
306
|
-
),
|
|
307
|
-
};
|
|
308
|
-
```
|
|
309
|
-
|
|
310
|
-
> [!NOTE]
|
|
311
|
-
> If you are using hooks in an async function component, you need to call these hooks before any `await` statement.
|
|
277
|
+
> [!WARNING]
|
|
278
|
+
> The state cannot be used in an arrow function component, you should use a `function` declaration instead.
|
|
312
279
|
|
|
313
280
|
```jsx
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
const { request } = $context(); // ❌ request is undefined
|
|
281
|
+
// ❌ `this.count++` won't update the view, please use a function declaration instead
|
|
282
|
+
const App = () => {
|
|
283
|
+
this.count = 0;
|
|
318
284
|
return (
|
|
319
|
-
<
|
|
320
|
-
{
|
|
321
|
-
|
|
285
|
+
<div>
|
|
286
|
+
<span>{this.count}</span>
|
|
287
|
+
<button onClick={() => this.count++}>+</button>
|
|
288
|
+
</div>
|
|
322
289
|
);
|
|
323
|
-
}
|
|
290
|
+
};
|
|
324
291
|
```
|
|
325
292
|
|
|
326
293
|
## Built-in Elements
|
|
@@ -331,19 +298,19 @@ mono-jsx provides some built-in elements to help you build your app.
|
|
|
331
298
|
|
|
332
299
|
`<toggle>` element allows you to toggle the visibility of the slotted content.
|
|
333
300
|
|
|
334
|
-
```
|
|
335
|
-
function App() {
|
|
336
|
-
|
|
301
|
+
```tsx
|
|
302
|
+
function App(this: FC<{ show: boolean }>) {
|
|
303
|
+
this.show = false;
|
|
337
304
|
return (
|
|
338
305
|
<div>
|
|
339
|
-
<toggle value={
|
|
306
|
+
<toggle value={this.show}>
|
|
340
307
|
<h1>Hello World!</h1>
|
|
341
308
|
</toggle>
|
|
342
|
-
<button onClick={toggle}>{
|
|
309
|
+
<button onClick={toggle}>{this.computed(() => this.show ? "Hide" : "Show")}</button>
|
|
343
310
|
</div>
|
|
344
311
|
);
|
|
345
312
|
function toggle() {
|
|
346
|
-
|
|
313
|
+
this.show = !this.show;
|
|
347
314
|
}
|
|
348
315
|
}
|
|
349
316
|
```
|
|
@@ -352,26 +319,24 @@ function App() {
|
|
|
352
319
|
|
|
353
320
|
`<switch>` element allows you to switch the slotted content based on the `value` property. You need to define the `slot` attribute in the slotted content to match the `value`, otherwise, the default slots will be rendered.
|
|
354
321
|
|
|
355
|
-
```
|
|
356
|
-
function App() {
|
|
322
|
+
```tsx
|
|
323
|
+
function App(this: FC<{ lang: "en" | "zh" | "emoji" }>) {
|
|
324
|
+
this.lang = "en";
|
|
357
325
|
return (
|
|
358
326
|
<div>
|
|
359
|
-
<switch value={
|
|
327
|
+
<switch value={this.lang}>
|
|
360
328
|
<h1 slot="en">Hello, world!</h1>
|
|
361
329
|
<h1 slot="zh">你好,世界!</h1>
|
|
362
330
|
<h1>✋🌎❗️</h1>
|
|
363
331
|
</switch>
|
|
364
|
-
<button onClick={() =>
|
|
365
|
-
<button onClick={() =>
|
|
332
|
+
<button onClick={() => this.lang = "en"}>English</button>
|
|
333
|
+
<button onClick={() => this.lang = "zh"}>中文</button>
|
|
334
|
+
<button onClick={() => this.lang = "emoji"}>Emoji</button>
|
|
366
335
|
</div>
|
|
367
336
|
);
|
|
368
337
|
}
|
|
369
338
|
```
|
|
370
339
|
|
|
371
|
-
### `<cache>` element
|
|
372
|
-
|
|
373
|
-
_Work in progress..._
|
|
374
|
-
|
|
375
340
|
## Streaming Rendering
|
|
376
341
|
|
|
377
342
|
mono-jsx renders your `<html>` as a readable stream, that allows async function components are rendered asynchrously. You can set a `placeholder` attribute to show a loading state while the async component is loading.
|
|
@@ -394,7 +359,7 @@ export default {
|
|
|
394
359
|
};
|
|
395
360
|
```
|
|
396
361
|
|
|
397
|
-
You can also set `rendering` attribute to
|
|
362
|
+
You can also set `rendering` attribute to "eager" to render the async component eagerly, which means the async component will be rendered as a sync function component and the `placeholder` will be ignored.
|
|
398
363
|
|
|
399
364
|
```jsx
|
|
400
365
|
async function Sleep(ms) {
|
|
@@ -413,3 +378,49 @@ export default {
|
|
|
413
378
|
),
|
|
414
379
|
};
|
|
415
380
|
```
|
|
381
|
+
|
|
382
|
+
## Accessing Request Info
|
|
383
|
+
|
|
384
|
+
You can access the request info in a function component by using the `request` property in the `this` context. And you must pass the `request` object to the root `<html>` element to make it work.
|
|
385
|
+
|
|
386
|
+
```jsx
|
|
387
|
+
function RequestInfo(this: FC) {
|
|
388
|
+
const { request } = this;
|
|
389
|
+
return (
|
|
390
|
+
<div>
|
|
391
|
+
<h1>Request Info</h1>
|
|
392
|
+
<p>{request.method}</p>
|
|
393
|
+
<p>{request.url}</p>
|
|
394
|
+
<p>{request.headers.get("user-agent")}</p>
|
|
395
|
+
</div>
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
export default {
|
|
400
|
+
fetch: (req) => (
|
|
401
|
+
<html request={req}>
|
|
402
|
+
<RequestInfo />
|
|
403
|
+
</html>
|
|
404
|
+
),
|
|
405
|
+
};
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
## Customizing Response
|
|
409
|
+
|
|
410
|
+
You can add `status` or `headers` attribute to the `<html>` element to customize the response.
|
|
411
|
+
|
|
412
|
+
```jsx
|
|
413
|
+
export default {
|
|
414
|
+
fetch: (req) => (
|
|
415
|
+
<html
|
|
416
|
+
status={404}
|
|
417
|
+
headers={{
|
|
418
|
+
cacheControl: "public, max-age=0, must-revalidate",
|
|
419
|
+
setCookie: "name=value",
|
|
420
|
+
}}
|
|
421
|
+
>
|
|
422
|
+
<h1>Page Not Found</h1>
|
|
423
|
+
</html>
|
|
424
|
+
),
|
|
425
|
+
};
|
|
426
|
+
```
|