mono-jsx 0.2.0 → 0.3.1
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 +143 -16
- package/bin/mono-jsx +1 -42
- package/jsx-runtime.mjs +138 -138
- package/package.json +1 -1
- package/setup.mjs +44 -0
- package/types/css.d.ts +1 -1
- package/types/html.d.ts +3 -2
- package/types/htmx.d.ts +791 -0
- package/types/mono.d.ts +6 -5
- package/types/render.d.ts +42 -1
package/README.md
CHANGED
|
@@ -109,6 +109,9 @@ You'll need [tsx](https://www.npmjs.com/package/tsx) to start the app without a
|
|
|
109
109
|
npx tsx app.tsx
|
|
110
110
|
```
|
|
111
111
|
|
|
112
|
+
> [!NOTE]
|
|
113
|
+
> Only root `<html>` element will be rendered as a `Response` object. You cannot return a `<div>` or any other element directly from the `fetch` handler. This is a limitation of the mono-jsx runtime.
|
|
114
|
+
|
|
112
115
|
## Using JSX
|
|
113
116
|
|
|
114
117
|
mono-jsx uses [**JSX**](https://react.dev/learn/describing-the-ui) to describe the user interface, similar to React but with key differences.
|
|
@@ -309,22 +312,63 @@ function Counter(
|
|
|
309
312
|
}
|
|
310
313
|
```
|
|
311
314
|
|
|
312
|
-
### Using
|
|
315
|
+
### Using App State
|
|
313
316
|
|
|
314
|
-
You can
|
|
317
|
+
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:
|
|
318
|
+
|
|
319
|
+
```tsx
|
|
320
|
+
function Header(this: FC<{}, { title: string }>) {
|
|
321
|
+
return (
|
|
322
|
+
<header>
|
|
323
|
+
<h1>{this.app.title}</h1>
|
|
324
|
+
</header>
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function Footer(this: FC<{}, { title: string }>) {
|
|
329
|
+
return (
|
|
330
|
+
<footer>
|
|
331
|
+
<p>(c) 2025 {this.app.title}</p>
|
|
332
|
+
</footer>
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function Main(this: FC<{}, { title: string }>) {
|
|
337
|
+
return (
|
|
338
|
+
<main>
|
|
339
|
+
<h1>{this.app.title}</h1>
|
|
340
|
+
<h2>Changing the title</h2>
|
|
341
|
+
<input
|
|
342
|
+
onInput={(evt) => this.app.title = evt.target.value}
|
|
343
|
+
placeholder="Enter a new title"
|
|
344
|
+
/>
|
|
345
|
+
</main>
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
export default {
|
|
350
|
+
fetch: (req) => (
|
|
351
|
+
<html appState={{ title: "Welcome to mono-jsx!" }}>
|
|
352
|
+
<Header />
|
|
353
|
+
<Main />
|
|
354
|
+
<Footer />
|
|
355
|
+
</html>
|
|
356
|
+
),
|
|
357
|
+
};
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### Using Computed State
|
|
361
|
+
|
|
362
|
+
You can use `this.computed` to create computed state based on state. The computed state will automatically update when the state changes:
|
|
315
363
|
|
|
316
364
|
```tsx
|
|
317
365
|
function App(this: FC<{ input: string }>) {
|
|
318
|
-
this.input =
|
|
366
|
+
this.input = "Welcome to mono-jsx!";
|
|
319
367
|
return (
|
|
320
368
|
<div>
|
|
321
|
-
<h1>{this.computed(()
|
|
369
|
+
<h1>{this.computed(() => this.input + "!")}</h1>
|
|
322
370
|
|
|
323
|
-
<form
|
|
324
|
-
action={(data: FormData ) => {
|
|
325
|
-
this.input = data.get("input") as string;
|
|
326
|
-
}}
|
|
327
|
-
>
|
|
371
|
+
<form action={(fd) => this.input = fd.get("input") as string}>
|
|
328
372
|
<input type="text" name="input" value={this.input} />
|
|
329
373
|
<button type="submit">Submit</button>
|
|
330
374
|
</form>
|
|
@@ -366,7 +410,7 @@ function App(this: FC) {
|
|
|
366
410
|
```tsx
|
|
367
411
|
// ❌ Won't work - state updates won't refresh the view
|
|
368
412
|
function App(this: FC<{ message: string }>) {
|
|
369
|
-
this.message = "
|
|
413
|
+
this.message = "Welcome to mono-jsx!";
|
|
370
414
|
return (
|
|
371
415
|
<div>
|
|
372
416
|
<h1 title={this.message + "!"}>{this.message + "!"}</h1>
|
|
@@ -379,7 +423,7 @@ function App(this: FC<{ message: string }>) {
|
|
|
379
423
|
|
|
380
424
|
// ✅ Works correctly
|
|
381
425
|
function App(this: FC) {
|
|
382
|
-
this.message = "
|
|
426
|
+
this.message = "Welcome to mono-jsx!";
|
|
383
427
|
return (
|
|
384
428
|
<div>
|
|
385
429
|
<h1 title={this.computed(() => this.message + "!")}>{this.computed(() => this.message + "!")}</h1>
|
|
@@ -494,22 +538,23 @@ export default {
|
|
|
494
538
|
You can use the `context` property in `this` to access context values in your components. The context is defined on the root `<html>` element:
|
|
495
539
|
|
|
496
540
|
```tsx
|
|
497
|
-
function Dash(this: FC<{}, { auth: { uuid: string; name: string } }>) {
|
|
541
|
+
function Dash(this: FC<{}, {}, { auth: { uuid: string; name: string } }>) {
|
|
498
542
|
const { auth } = this.context;
|
|
499
543
|
return (
|
|
500
544
|
<div>
|
|
501
|
-
<h1>Welcome back, {auth.name}!</h1
|
|
545
|
+
<h1>Welcome back, {auth.name}!</h1>
|
|
502
546
|
<p>Your UUID is {auth.uuid}</p>
|
|
503
547
|
</div>
|
|
504
548
|
);
|
|
505
549
|
}
|
|
506
550
|
|
|
507
551
|
export default {
|
|
508
|
-
fetch: (req) => {
|
|
509
|
-
const auth = doAuth(req);
|
|
552
|
+
fetch: async (req) => {
|
|
553
|
+
const auth = await doAuth(req);
|
|
510
554
|
return (
|
|
511
555
|
<html context={{ auth }} request={req}>
|
|
512
|
-
<
|
|
556
|
+
{!auth && <p>Please Login</p>}
|
|
557
|
+
{auth && <Dash />}
|
|
513
558
|
</html>
|
|
514
559
|
);
|
|
515
560
|
},
|
|
@@ -562,6 +607,88 @@ export default {
|
|
|
562
607
|
};
|
|
563
608
|
```
|
|
564
609
|
|
|
610
|
+
## Using htmx
|
|
611
|
+
|
|
612
|
+
mono-jsx integrates with [htmx](https://htmx.org/) and [typed-htmx](https://github.com/Desdaemon/typed-htmx). To use htmx, add the `htmx` attribute to the root `<html>` element:
|
|
613
|
+
|
|
614
|
+
```jsx
|
|
615
|
+
export default {
|
|
616
|
+
fetch: (req) => {
|
|
617
|
+
const url = new URL(req.url);
|
|
618
|
+
|
|
619
|
+
if (url.pathname === "/clicked") {
|
|
620
|
+
return (
|
|
621
|
+
<html>
|
|
622
|
+
<span>Clicked!</span>
|
|
623
|
+
</html>
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
return (
|
|
628
|
+
<html htmx>
|
|
629
|
+
<button hx-get="/clicked" hx-swap="outerHTML">
|
|
630
|
+
Click Me
|
|
631
|
+
</button>
|
|
632
|
+
</html>
|
|
633
|
+
);
|
|
634
|
+
},
|
|
635
|
+
};
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
### Adding htmx Extensions
|
|
639
|
+
|
|
640
|
+
You can add htmx [extensions](https://htmx.org/docs/#extensions) by adding the `htmx-ext-*` attribute to the root `<html>` element:
|
|
641
|
+
|
|
642
|
+
```jsx
|
|
643
|
+
export default {
|
|
644
|
+
fetch: (req) => (
|
|
645
|
+
<html htmx htmx-ext-response-targets htmx-ext-ws>
|
|
646
|
+
<button hx-get="/clicked" hx-swap="outerHTML">
|
|
647
|
+
Click Me
|
|
648
|
+
</button>
|
|
649
|
+
</html>
|
|
650
|
+
),
|
|
651
|
+
};
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
### Specifying htmx Version
|
|
655
|
+
|
|
656
|
+
You can specify the htmx version by setting the `htmx` attribute to a specific version:
|
|
657
|
+
|
|
658
|
+
```jsx
|
|
659
|
+
export default {
|
|
660
|
+
fetch: (req) => (
|
|
661
|
+
<html htmx="2.0.4" htmx-ext-response-targets="2.0.2" htmx-ext-ws="2.0.2">
|
|
662
|
+
<button hx-get="/clicked" hx-swap="outerHTML">
|
|
663
|
+
Click Me
|
|
664
|
+
</button>
|
|
665
|
+
</html>
|
|
666
|
+
),
|
|
667
|
+
};
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
### Installing htmx Manually
|
|
671
|
+
|
|
672
|
+
By default, mono-jsx installs htmx from [esm.sh](https://esm.sh/) CDN when you set the `htmx` attribute. You can also install htmx manually with your own CDN or local copy:
|
|
673
|
+
|
|
674
|
+
```jsx
|
|
675
|
+
export default {
|
|
676
|
+
fetch: (req) => (
|
|
677
|
+
<html>
|
|
678
|
+
<head>
|
|
679
|
+
<script src="https://unpkg.com/htmx.org@2.0.4" integrity="sha384-HGfztofotfshcF7+8n44JQL2oJmowVChPTg48S+jvZoztPfvwD79OC/LTtG6dMp+" crossorigin="anonymous"></script>
|
|
680
|
+
<script src="https://unpkg.com/htmx-ext-ws@2.0.2" integrity="sha384-vuKxTKv5TX/b3lLzDKP2U363sOAoRo5wSvzzc3LJsbaQRSBSS+3rKKHcOx5J8doU" crossorigin="anonymous"></script>
|
|
681
|
+
</head>
|
|
682
|
+
<body>
|
|
683
|
+
<button hx-get="/clicked" hx-swap="outerHTML">
|
|
684
|
+
Click Me
|
|
685
|
+
</button>
|
|
686
|
+
</body>
|
|
687
|
+
</html>
|
|
688
|
+
),
|
|
689
|
+
};
|
|
690
|
+
```
|
|
691
|
+
|
|
565
692
|
## License
|
|
566
693
|
|
|
567
694
|
[MIT](LICENSE)
|
package/bin/mono-jsx
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { existsSync } from "node:fs";
|
|
4
|
-
import { readFile, writeFile } from "node:fs/promises";
|
|
5
3
|
import process from "node:process";
|
|
4
|
+
import { setup } from "../setup.mjs";
|
|
6
5
|
|
|
7
6
|
switch (process.argv[2]) {
|
|
8
7
|
case "setup":
|
|
@@ -11,43 +10,3 @@ switch (process.argv[2]) {
|
|
|
11
10
|
default:
|
|
12
11
|
process.exit(0);
|
|
13
12
|
}
|
|
14
|
-
|
|
15
|
-
async function setup() {
|
|
16
|
-
if (globalThis.Deno && existsSync("deno.jsonc")) {
|
|
17
|
-
console.log("Please add the following options to your deno.jsonc file:");
|
|
18
|
-
console.log(
|
|
19
|
-
[
|
|
20
|
-
`{`,
|
|
21
|
-
` "compilerOptions": {`,
|
|
22
|
-
` %c"jsx": "react-jsx",`,
|
|
23
|
-
` "jsxImportSource": "mono-jsx",%c`,
|
|
24
|
-
` }`,
|
|
25
|
-
`}`,
|
|
26
|
-
].join("\n"),
|
|
27
|
-
"color:green",
|
|
28
|
-
"",
|
|
29
|
-
);
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
let tsConfigFilename = globalThis.Deno ? "deno.json" : "tsconfig.json";
|
|
33
|
-
let tsConfig = Object.create(null);
|
|
34
|
-
try {
|
|
35
|
-
const data = await readFile(tsConfigFilename, "utf8");
|
|
36
|
-
tsConfig = JSON.parse(data);
|
|
37
|
-
} catch {
|
|
38
|
-
// ignore
|
|
39
|
-
}
|
|
40
|
-
const compilerOptions = tsConfig.compilerOptions ?? (tsConfig.compilerOptions = {});
|
|
41
|
-
if (compilerOptions.jsx === "react-jsx" && compilerOptions.jsxImportSource === "mono-jsx") {
|
|
42
|
-
console.log("%cmono-jsx already setup.","color:grey");
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
if (!globalThis.Deno) {
|
|
46
|
-
compilerOptions.module ??= "es2022";
|
|
47
|
-
compilerOptions.moduleResolution ??= "bundler";
|
|
48
|
-
}
|
|
49
|
-
compilerOptions.jsx = "react-jsx";
|
|
50
|
-
compilerOptions.jsxImportSource = "mono-jsx";
|
|
51
|
-
await writeFile(tsConfigFilename, JSON.stringify(tsConfig, null, 2));
|
|
52
|
-
console.log("✅ mono-jsx setup complete.")
|
|
53
|
-
}
|