mono-jsx 0.3.2 β 0.3.3
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 +130 -94
- package/jsx-runtime.mjs +200 -159
- package/package.json +1 -1
- package/types/html.d.ts +1 -1
- package/types/jsx.d.ts +2 -7
- package/types/mono.d.ts +47 -19
- package/types/render.d.ts +0 -5
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@ mono-jsx is a JSX runtime that renders `<html>` element to `Response` object in
|
|
|
9
9
|
- π« Minimal state runtime
|
|
10
10
|
- π¨ Complete Web API TypeScript definitions
|
|
11
11
|
- β³ Streaming rendering
|
|
12
|
+
- π₯· [htmx](#using-htmx) integration
|
|
12
13
|
- π Universal, works in Node.js, Deno, Bun, Cloudflare Workers, etc.
|
|
13
14
|
|
|
14
15
|
## Installation
|
|
@@ -65,6 +66,7 @@ mono-jsx allows you to return an `<html>` JSX element as a `Response` object in
|
|
|
65
66
|
|
|
66
67
|
```tsx
|
|
67
68
|
// app.tsx
|
|
69
|
+
|
|
68
70
|
export default {
|
|
69
71
|
fetch: (req) => (
|
|
70
72
|
<html>
|
|
@@ -91,6 +93,7 @@ npx wrangler dev app.tsx
|
|
|
91
93
|
|
|
92
94
|
```tsx
|
|
93
95
|
// app.tsx
|
|
96
|
+
|
|
94
97
|
import { serve } from "srvx";
|
|
95
98
|
|
|
96
99
|
serve({
|
|
@@ -157,7 +160,7 @@ mono-jsx supports [pseudo classes](https://developer.mozilla.org/en-US/docs/Web/
|
|
|
157
160
|
</a>;
|
|
158
161
|
```
|
|
159
162
|
|
|
160
|
-
### `<slot>` Element
|
|
163
|
+
### Using `<slot>` Element
|
|
161
164
|
|
|
162
165
|
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:
|
|
163
166
|
|
|
@@ -185,7 +188,7 @@ function App() {
|
|
|
185
188
|
}
|
|
186
189
|
```
|
|
187
190
|
|
|
188
|
-
### `html` Tag Function
|
|
191
|
+
### Using `html` Tag Function
|
|
189
192
|
|
|
190
193
|
mono-jsx provides an `html` tag function to render raw HTML in JSX instead of React's `dangerouslySetInnerHTML`:
|
|
191
194
|
|
|
@@ -270,12 +273,7 @@ mono-jsx also accepts functions for the `action` property on `form` elements, wh
|
|
|
270
273
|
```tsx
|
|
271
274
|
function App() {
|
|
272
275
|
return (
|
|
273
|
-
<form
|
|
274
|
-
action={(data: FormData, event: SubmitEvent) => {
|
|
275
|
-
event.preventDefault(); // true
|
|
276
|
-
console.log(data.get("name"));
|
|
277
|
-
}}
|
|
278
|
-
>
|
|
276
|
+
<form action={(data: FormData, event: SubmitEvent) => console.log(data.get("name"))}>
|
|
279
277
|
<input type="text" name="name" />
|
|
280
278
|
<button type="submit">Submit</button>
|
|
281
279
|
</form>
|
|
@@ -285,11 +283,11 @@ function App() {
|
|
|
285
283
|
|
|
286
284
|
## Reactive
|
|
287
285
|
|
|
288
|
-
mono-jsx provides a minimal state runtime for updating the view based on client-side state changes
|
|
286
|
+
mono-jsx provides a minimal state runtime for updating the view based on client-side state changes.
|
|
289
287
|
|
|
290
|
-
### Using State
|
|
288
|
+
### Using Component State
|
|
291
289
|
|
|
292
|
-
You can use `this`
|
|
290
|
+
You can use the `this` keyword in your components to manage state. The state is bound to the component instance and can be updated directly, and will automatically re-render the view when the state changes:
|
|
293
291
|
|
|
294
292
|
```tsx
|
|
295
293
|
function Counter(
|
|
@@ -377,9 +375,60 @@ function App(this: FC<{ input: string }>) {
|
|
|
377
375
|
}
|
|
378
376
|
```
|
|
379
377
|
|
|
380
|
-
###
|
|
378
|
+
### Using `<toggle>` Element with State
|
|
379
|
+
|
|
380
|
+
The `<toggle>` element conditionally renders content based on the value of a state.
|
|
381
|
+
|
|
382
|
+
```tsx
|
|
383
|
+
function App(this: FC<{ show: boolean }>) {
|
|
384
|
+
this.show = false;
|
|
385
|
+
|
|
386
|
+
function toggle() {
|
|
387
|
+
this.show = !this.show;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return (
|
|
391
|
+
<div>
|
|
392
|
+
<toggle value={this.show}>
|
|
393
|
+
<h1>Welcome to mono-jsx!</h1>
|
|
394
|
+
</toggle>
|
|
395
|
+
|
|
396
|
+
<button onClick={toggle}>
|
|
397
|
+
{this.computed(() => this.show ? "Hide" : "Show")}
|
|
398
|
+
</button>
|
|
399
|
+
</div>
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
### Using `<switch>` Element with State
|
|
405
|
+
|
|
406
|
+
The `<switch>` element renders different content based on the value of a state. Elements with matching `slot` attributes are displayed when their value matches, otherwise default content is shown:
|
|
407
|
+
|
|
408
|
+
```tsx
|
|
409
|
+
function App(this: FC<{ lang: "en" | "zh" | "emoji" }>) {
|
|
410
|
+
this.lang = "en";
|
|
411
|
+
|
|
412
|
+
return (
|
|
413
|
+
<div>
|
|
414
|
+
<switch value={this.lang}>
|
|
415
|
+
<h1 slot="en">Hello, world!</h1>
|
|
416
|
+
<h1 slot="zh">δ½ ε₯½οΌδΈηοΌ</h1>
|
|
417
|
+
<h1>βπβοΈ</h1>
|
|
418
|
+
</switch>
|
|
419
|
+
<p>
|
|
420
|
+
<button onClick={() => this.lang = "en"}>English</button>
|
|
421
|
+
<button onClick={() => this.lang = "zh"}>δΈζ</button>
|
|
422
|
+
<button onClick={() => this.lang = "emoji"}>Emoji</button>
|
|
423
|
+
</p>
|
|
424
|
+
</div>
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
### Limitation of State
|
|
381
430
|
|
|
382
|
-
1\.
|
|
431
|
+
1\. Component state cannot be used in arrow function components.
|
|
383
432
|
|
|
384
433
|
```tsx
|
|
385
434
|
// β Won't work - state updates won't refresh the view
|
|
@@ -405,7 +454,7 @@ function App(this: FC) {
|
|
|
405
454
|
}
|
|
406
455
|
```
|
|
407
456
|
|
|
408
|
-
2\.
|
|
457
|
+
2\. Component state cannot be computed outside of the `this.computed` method.
|
|
409
458
|
|
|
410
459
|
```tsx
|
|
411
460
|
// β Won't work - state updates won't refresh the view
|
|
@@ -435,58 +484,82 @@ function App(this: FC) {
|
|
|
435
484
|
}
|
|
436
485
|
```
|
|
437
486
|
|
|
438
|
-
##
|
|
487
|
+
## Using `this` in Components
|
|
439
488
|
|
|
440
|
-
mono-jsx
|
|
489
|
+
mono-jsx binds a special `this` object to your components when they are rendered. This object contains properties and methods that you can use to manage state, context, and other features.
|
|
441
490
|
|
|
442
|
-
|
|
491
|
+
The `this` object contains the following properties:
|
|
443
492
|
|
|
444
|
-
|
|
493
|
+
- `app`: The app state defined on the root `<html>` element.
|
|
494
|
+
- `context`: The context defined on the root `<html>` element.
|
|
495
|
+
- `request`: The request object from the `fetch` handler.
|
|
496
|
+
- `computed`: A method to create computed properties based on state.
|
|
445
497
|
|
|
446
|
-
```
|
|
447
|
-
|
|
448
|
-
|
|
498
|
+
```ts
|
|
499
|
+
type FC<State = {}, AppState = {}, Context = {}> = {
|
|
500
|
+
readonly app: AppState;
|
|
501
|
+
readonly context: Context;
|
|
502
|
+
readonly request: Request;
|
|
503
|
+
readonly computed: <V = unknown>(computeFn: () => V) => V;
|
|
504
|
+
} & Omit<State, "app" | "context" | "request" | "computed">;
|
|
505
|
+
```
|
|
449
506
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
507
|
+
### Using State
|
|
508
|
+
|
|
509
|
+
Check the [Using State](#using-state) section for more details on how to use state in your components.
|
|
510
|
+
|
|
511
|
+
### Using Context
|
|
453
512
|
|
|
513
|
+
You can use the `context` property in `this` to access context values in your components. The context is defined on the root `<html>` element:
|
|
514
|
+
|
|
515
|
+
```tsx
|
|
516
|
+
function Dash(this: FC<{}, {}, { auth: { uuid: string; name: string } }>) {
|
|
517
|
+
const { auth } = this.context;
|
|
454
518
|
return (
|
|
455
519
|
<div>
|
|
456
|
-
<
|
|
457
|
-
|
|
458
|
-
</toggle>
|
|
459
|
-
|
|
460
|
-
<button onClick={toggle}>
|
|
461
|
-
{this.computed(() => this.show ? "Hide" : "Show")}
|
|
462
|
-
</button>
|
|
520
|
+
<h1>Welcome back, {auth.name}!</h1>
|
|
521
|
+
<p>Your UUID is {auth.uuid}</p>
|
|
463
522
|
</div>
|
|
464
523
|
);
|
|
465
524
|
}
|
|
525
|
+
|
|
526
|
+
export default {
|
|
527
|
+
fetch: async (req) => {
|
|
528
|
+
const auth = await doAuth(req);
|
|
529
|
+
return (
|
|
530
|
+
<html context={{ auth }} request={req}>
|
|
531
|
+
{!auth && <p>Please Login</p>}
|
|
532
|
+
{auth && <Dash />}
|
|
533
|
+
</html>
|
|
534
|
+
);
|
|
535
|
+
},
|
|
536
|
+
};
|
|
466
537
|
```
|
|
467
538
|
|
|
468
|
-
###
|
|
539
|
+
### Accessing Request Info
|
|
469
540
|
|
|
470
|
-
|
|
541
|
+
You can access request information in components via the `request` property in `this` which is set on the root `<html>` element:
|
|
471
542
|
|
|
472
543
|
```tsx
|
|
473
|
-
function
|
|
474
|
-
|
|
475
|
-
|
|
544
|
+
function RequestInfo(this: FC) {
|
|
545
|
+
const { request } = this;
|
|
476
546
|
return (
|
|
477
547
|
<div>
|
|
478
|
-
<
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
</switch>
|
|
483
|
-
|
|
484
|
-
<button onClick={() => this.lang = "en"}>English</button>
|
|
485
|
-
<button onClick={() => this.lang = "zh"}>δΈζ</button>
|
|
486
|
-
<button onClick={() => this.lang = "emoji"}>Emoji</button>
|
|
548
|
+
<h1>Request Info</h1>
|
|
549
|
+
<p>{request.method}</p>
|
|
550
|
+
<p>{request.url}</p>
|
|
551
|
+
<p>{request.headers.get("user-agent")}</p>
|
|
487
552
|
</div>
|
|
488
553
|
);
|
|
489
554
|
}
|
|
555
|
+
|
|
556
|
+
export default {
|
|
557
|
+
fetch: (req) => (
|
|
558
|
+
<html request={req}>
|
|
559
|
+
<RequestInfo />
|
|
560
|
+
</html>
|
|
561
|
+
),
|
|
562
|
+
};
|
|
490
563
|
```
|
|
491
564
|
|
|
492
565
|
## Streaming Rendering
|
|
@@ -533,61 +606,24 @@ export default {
|
|
|
533
606
|
};
|
|
534
607
|
```
|
|
535
608
|
|
|
536
|
-
|
|
609
|
+
You can add the `catch` attribute to handle errors in async components. The `catch` attribute should be a function that returns a JSX element:
|
|
537
610
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
const { auth } = this.context;
|
|
543
|
-
return (
|
|
544
|
-
<div>
|
|
545
|
-
<h1>Welcome back, {auth.name}!</h1>
|
|
546
|
-
<p>Your UUID is {auth.uuid}</p>
|
|
547
|
-
</div>
|
|
548
|
-
);
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
export default {
|
|
552
|
-
fetch: async (req) => {
|
|
553
|
-
const auth = await doAuth(req);
|
|
554
|
-
return (
|
|
555
|
-
<html context={{ auth }} request={req}>
|
|
556
|
-
{!auth && <p>Please Login</p>}
|
|
557
|
-
{auth && <Dash />}
|
|
558
|
-
</html>
|
|
559
|
-
);
|
|
560
|
-
},
|
|
561
|
-
};
|
|
562
|
-
```
|
|
563
|
-
|
|
564
|
-
## Accessing Request Info
|
|
565
|
-
|
|
566
|
-
You can access request information in components via the `request` property in `this` which is set on the root `<html>` element:
|
|
567
|
-
|
|
568
|
-
```tsx
|
|
569
|
-
function RequestInfo(this: FC) {
|
|
570
|
-
const { request } = this;
|
|
571
|
-
return (
|
|
572
|
-
<div>
|
|
573
|
-
<h1>Request Info</h1>
|
|
574
|
-
<p>{request.method}</p>
|
|
575
|
-
<p>{request.url}</p>
|
|
576
|
-
<p>{request.headers.get("user-agent")}</p>
|
|
577
|
-
</div>
|
|
578
|
-
);
|
|
611
|
+
```jsx
|
|
612
|
+
async function Hello() {
|
|
613
|
+
throw new Error("Something went wrong!");
|
|
614
|
+
return <p>Hello world!</p>;
|
|
579
615
|
}
|
|
580
616
|
|
|
581
617
|
export default {
|
|
582
618
|
fetch: (req) => (
|
|
583
|
-
<html
|
|
584
|
-
<
|
|
619
|
+
<html>
|
|
620
|
+
<Hello catch={err => <p>{err.messaage}</p>} />
|
|
585
621
|
</html>
|
|
586
622
|
),
|
|
587
623
|
};
|
|
588
624
|
```
|
|
589
625
|
|
|
590
|
-
## Customizing Response
|
|
626
|
+
## Customizing html Response
|
|
591
627
|
|
|
592
628
|
Add `status` or `headers` attributes to the root `<html>` element to customize the response:
|
|
593
629
|
|
|
@@ -607,7 +643,7 @@ export default {
|
|
|
607
643
|
};
|
|
608
644
|
```
|
|
609
645
|
|
|
610
|
-
|
|
646
|
+
### Using htmx
|
|
611
647
|
|
|
612
648
|
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
649
|
|
|
@@ -635,7 +671,7 @@ export default {
|
|
|
635
671
|
};
|
|
636
672
|
```
|
|
637
673
|
|
|
638
|
-
|
|
674
|
+
#### Adding htmx Extensions
|
|
639
675
|
|
|
640
676
|
You can add htmx [extensions](https://htmx.org/docs/#extensions) by adding the `htmx-ext-*` attribute to the root `<html>` element:
|
|
641
677
|
|
|
@@ -651,7 +687,7 @@ export default {
|
|
|
651
687
|
};
|
|
652
688
|
```
|
|
653
689
|
|
|
654
|
-
|
|
690
|
+
#### Specifying htmx Version
|
|
655
691
|
|
|
656
692
|
You can specify the htmx version by setting the `htmx` attribute to a specific version:
|
|
657
693
|
|
|
@@ -667,7 +703,7 @@ export default {
|
|
|
667
703
|
};
|
|
668
704
|
```
|
|
669
705
|
|
|
670
|
-
|
|
706
|
+
#### Installing htmx Manually
|
|
671
707
|
|
|
672
708
|
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
709
|
|
package/jsx-runtime.mjs
CHANGED
|
@@ -5,61 +5,16 @@ var $html = Symbol.for("jsx.html");
|
|
|
5
5
|
var $state = Symbol.for("mono.state");
|
|
6
6
|
var $computed = Symbol.for("mono.computed");
|
|
7
7
|
|
|
8
|
-
// state.ts
|
|
9
|
-
var collectDeps;
|
|
10
|
-
function createState(fc, appState, context, request) {
|
|
11
|
-
const computed = (fn) => {
|
|
12
|
-
const deps = /* @__PURE__ */ Object.create(null);
|
|
13
|
-
collectDeps = (fc2, key, value2) => deps[fc2 + ":" + key] = value2;
|
|
14
|
-
const value = fn.call(proxy);
|
|
15
|
-
collectDeps = void 0;
|
|
16
|
-
if (value instanceof Promise || deps.size === 0) return value;
|
|
17
|
-
return [$computed, { value, deps, fn: fn.toString(), fc }, $vnode];
|
|
18
|
-
};
|
|
19
|
-
const proxy = new Proxy(/* @__PURE__ */ Object.create(null), {
|
|
20
|
-
get(target, key, receiver) {
|
|
21
|
-
switch (key) {
|
|
22
|
-
case "app":
|
|
23
|
-
return appState;
|
|
24
|
-
case "context":
|
|
25
|
-
return context ?? {};
|
|
26
|
-
case "request":
|
|
27
|
-
if (!request) {
|
|
28
|
-
throw new Error("request is not defined");
|
|
29
|
-
}
|
|
30
|
-
return request;
|
|
31
|
-
case "computed":
|
|
32
|
-
return computed;
|
|
33
|
-
default: {
|
|
34
|
-
const value = Reflect.get(target, key, receiver);
|
|
35
|
-
if (typeof key === "symbol") {
|
|
36
|
-
return value;
|
|
37
|
-
}
|
|
38
|
-
if (collectDeps) {
|
|
39
|
-
collectDeps(fc, key, value);
|
|
40
|
-
return value;
|
|
41
|
-
}
|
|
42
|
-
return [$state, { key, value, fc }, $vnode];
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
},
|
|
46
|
-
set(target, key, value, receiver) {
|
|
47
|
-
return Reflect.set(target, key, value, receiver);
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
return proxy;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
8
|
// runtime/index.ts
|
|
54
|
-
var STATE_JS = `const
|
|
9
|
+
var STATE_JS = `const m=new Map,u=e=>m.get(e)??m.set(e,b(e)).get(e),f=(e,t)=>e.getAttribute(t),d=(e,t)=>e.hasAttribute(t),b=e=>{const t=Object.create(null),r=new Map,n=(i,c)=>{let a=c;Object.defineProperty(t,i,{get:()=>a,set:o=>{if(o!==a){const l=r.get(i);l&&queueMicrotask(()=>l.forEach(h=>h())),a=o}}})},s=(i,c)=>{let a=r.get(i);a||(a=[],r.set(i,a)),a.push(c)};return e>0&&Object.defineProperty(t,"app",{get:()=>u(0).store,enumerable:!1,configurable:!1}),{store:t,define:n,watch:s}},g=(e,t,r)=>{if(t==="toggle"){let n;return()=>{if(!n){const s=e.firstElementChild;s&&s.tagName==="TEMPLATE"&&d(s,"m-slot")?(n=s.content.childNodes,e.innerHTML=""):n=e.childNodes}r()?e.append(...n):e.innerHTML=""}}if(t==="switch"){let n=f(e,"match"),s,i,c=o=>s.get(o)??s.set(o,[]).get(o),a;return()=>{if(!s){s=new Map,i=[];for(const o of e.childNodes)if(o.nodeType===1&&o.tagName==="TEMPLATE"&&d(o,"m-slot")){for(const l of o.content.childNodes)l.nodeType===1&&d(l,"slot")?c(f(l,"slot")).push(l):i.push(l);o.remove()}else n?c(n).push(o):i.push(o)}a=r(),e.innerHTML="",e.append(...s.has(a)?s.get(a):i)}}if(t&&t.length>2&&t.startsWith("[")&&t.endsWith("]")){let n=t.slice(1,-1),s=e.parentElement;return s.tagName==="M-GROUP"&&(s=s.previousElementSibling),()=>{const i=r();i===!1?s.removeAttribute(n):(n==="class"||n==="style")&&i&&typeof i=="object"?s.setAttribute(n,n==="class"?$cx(i):$styleToCSS(i)):s.setAttribute(n,i===!0?"":""+i)}}return()=>e.textContent=""+r()},p=e=>{const t=e.indexOf(":");if(t>0)return[Number(e.slice(0,t)),e.slice(t+1)];throw new Error("Invalid state key")};customElements.define("m-state",class extends HTMLElement{connectedCallback(){const e=this,t=f(e,"key");if(t){const r=u(Number(f(e,"fc")));r.watch(t,g(e,f(e,"mode"),()=>r.store[t]));return}}}),Object.assign(window,{$state:e=>e!==void 0?u(e).store:void 0,$defineState:(e,t)=>{const[r,n]=p(e);u(r).define(n,t)},$defineComputed:(e,t,r)=>{const n=document.querySelector("m-state[computed='"+e+"']");if(n){const s=u(Number(f(n,"fc"))).store,i=g(n,f(n,"mode"),t.bind(s));for(const c of r){const[a,o]=p(c);u(a).watch(o,i)}}}});`;
|
|
55
10
|
var SUSPENSE_JS = `const n={},o=e=>e.getAttribute("chunk-id");c("m-portal",e=>{n[o(e)]=e}),c("m-chunk",e=>{const t=o(e),s=n[t];s&&setTimeout(()=>{s.replaceWith(...e.firstChild.content.childNodes),delete n[t],e.remove()})});function c(e,t){customElements.define(e,class extends HTMLElement{connectedCallback(){t(this)}})}`;
|
|
56
11
|
var UTILS_JS = {
|
|
57
|
-
/** cx.js (
|
|
58
|
-
cx: `
|
|
59
|
-
/** styleToCSS.js (
|
|
60
|
-
styleToCSS: `
|
|
61
|
-
/** event.js (
|
|
62
|
-
event: `
|
|
12
|
+
/** cx.js (225 bytes) */
|
|
13
|
+
cx: `var n=e=>typeof e=="string",o=e=>typeof e=="object"&&e!==null;function t(e){return n(e)?e:o(e)?Array.isArray(e)?e.map(t).filter(Boolean).join(" "):Object.entries(e).filter(([,r])=>!!r).map(([r])=>r).join(" "):""}window.$cx=t;`,
|
|
14
|
+
/** styleToCSS.js (1168 bytes) */
|
|
15
|
+
styleToCSS: `var a=new Set(["animation-iteration-count","aspect-ratio","border-image-outset","border-image-slice","border-image-width","box-flex-group","box-flex","box-ordinal-group","column-count","columns","fill-opacity","flex-grow","flex-negative","flex-order","flex-positive","flex-shrink","flex","flood-opacity","font-weight","grid-area","grid-column-end","grid-column-span","grid-column-start","grid-column","grid-row-end","grid-row-span","grid-row-start","grid-row","line-clamp","line-height","opacity","order","orphans","stop-opacity","stroke-dasharray","stroke-dashoffset","stroke-miterlimit","stroke-opacity","stroke-width","tab-size","widows","z-index","zoom"]),o=e=>typeof e=="string",u=e=>typeof e=="object"&&e!==null,f=e=>e.replace(/[a-z][A-Z]/g,r=>r.charAt(0)+"-"+r.charAt(1).toLowerCase());function p(e){if(o(e))return e;if(u(e)){let r="";for(let[i,t]of Array.isArray(e)?e:Object.entries(e))if(o(i)&&(o(t)||typeof t=="number")){let n=f(i),c=typeof t=="number"?a.has(n)?""+t:t+"px":s(""+t);r+=(r?";":"")+s(n)+":"+(n==="content"?JSON.stringify(c):c)}return r}return""}function s(e){return e.replace(/["<>]/g,r=>r==="<"?"<":r===">"?">":"'")}window.$styleToCSS=p;`,
|
|
16
|
+
/** event.js (347 bytes) */
|
|
17
|
+
event: `var w=window;w.$emit=(e,fn,fc)=>fn.call(w.$state?.(fc)??e.target,e);w.$onsubmit=(e,fn,fc)=>{e.preventDefault();fn.call(w.$state?.(fc)??e.target,new FormData(e.target),e)};w.$onstage=()=>document.querySelectorAll("[onmount]").forEach(t=>{const k="onmount",j=t.getAttribute(k);t.removeAttribute(k);new Function("event",j)({type:"mount",target:t})});`
|
|
63
18
|
};
|
|
64
19
|
|
|
65
20
|
// runtime/utils.ts
|
|
@@ -130,35 +85,36 @@ function styleToCSS(style) {
|
|
|
130
85
|
if (isObject(style)) {
|
|
131
86
|
let css = "";
|
|
132
87
|
for (const [k, v] of Array.isArray(style) ? style : Object.entries(style)) {
|
|
133
|
-
if (
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
88
|
+
if (isString(k) && (isString(v) || typeof v === "number")) {
|
|
89
|
+
const cssKey = toHyphenCase(k);
|
|
90
|
+
const cssValue = typeof v === "number" ? cssBareUnitProps.has(cssKey) ? "" + v : v + "px" : escapeCSSText("" + v);
|
|
91
|
+
css += (css ? ";" : "") + escapeCSSText(cssKey) + ":" + (cssKey === "content" ? JSON.stringify(cssValue) : cssValue);
|
|
92
|
+
}
|
|
137
93
|
}
|
|
138
94
|
return css;
|
|
139
95
|
}
|
|
140
96
|
return "";
|
|
141
97
|
}
|
|
142
|
-
function escapeCSSText(
|
|
143
|
-
return
|
|
98
|
+
function escapeCSSText(str2) {
|
|
99
|
+
return str2.replace(/["<>]/g, (m) => {
|
|
144
100
|
if (m === "<") return "<";
|
|
145
101
|
if (m === ">") return ">";
|
|
146
102
|
return "'";
|
|
147
103
|
});
|
|
148
104
|
}
|
|
149
105
|
var regexpHtmlSafe = /["'&<>]/;
|
|
150
|
-
function escapeHTML(
|
|
151
|
-
if (typeof Bun === "object" && "escapeHTML" in Bun) return Bun.escapeHTML(
|
|
152
|
-
const match = regexpHtmlSafe.exec(
|
|
106
|
+
function escapeHTML(str2) {
|
|
107
|
+
if (typeof Bun === "object" && "escapeHTML" in Bun) return Bun.escapeHTML(str2);
|
|
108
|
+
const match = regexpHtmlSafe.exec(str2);
|
|
153
109
|
if (!match) {
|
|
154
|
-
return
|
|
110
|
+
return str2;
|
|
155
111
|
}
|
|
156
112
|
let escape;
|
|
157
113
|
let index;
|
|
158
114
|
let lastIndex = 0;
|
|
159
115
|
let html2 = "";
|
|
160
|
-
for (index = match.index; index <
|
|
161
|
-
switch (
|
|
116
|
+
for (index = match.index; index < str2.length; index++) {
|
|
117
|
+
switch (str2.charCodeAt(index)) {
|
|
162
118
|
case 34:
|
|
163
119
|
escape = """;
|
|
164
120
|
break;
|
|
@@ -178,21 +134,66 @@ function escapeHTML(str) {
|
|
|
178
134
|
continue;
|
|
179
135
|
}
|
|
180
136
|
if (lastIndex !== index) {
|
|
181
|
-
html2 +=
|
|
137
|
+
html2 += str2.slice(lastIndex, index);
|
|
182
138
|
}
|
|
183
139
|
lastIndex = index + 1;
|
|
184
140
|
html2 += escape;
|
|
185
141
|
}
|
|
186
|
-
return lastIndex !== index ? html2 +
|
|
142
|
+
return lastIndex !== index ? html2 + str2.slice(lastIndex, index) : html2;
|
|
187
143
|
}
|
|
188
144
|
|
|
189
145
|
// render.ts
|
|
190
146
|
var encoder = new TextEncoder();
|
|
191
147
|
var regexpHtmlTag = /^[a-z][\w\-$]*$/;
|
|
192
148
|
var selfClosingTags = new Set("area,base,br,col,embed,hr,img,input,keygen,link,meta,param,source,track,wbr".split(","));
|
|
149
|
+
var cdn = "https://raw.esm.sh";
|
|
193
150
|
var isVNode = (v) => Array.isArray(v) && v.length === 3 && v[2] === $vnode;
|
|
194
151
|
var hashCode = (s) => [...s].reduce((hash, c) => Math.imul(31, hash) + c.charCodeAt(0) | 0, 0);
|
|
195
|
-
var toAttrStringLit = (
|
|
152
|
+
var toAttrStringLit = (str2) => '"' + escapeHTML(str2) + '"';
|
|
153
|
+
var str = (v, str2) => v !== void 0 ? str2(v) : "";
|
|
154
|
+
var collectDeps;
|
|
155
|
+
function createThis(fc, appState, context, request) {
|
|
156
|
+
const computed = (fn) => {
|
|
157
|
+
const deps = /* @__PURE__ */ Object.create(null);
|
|
158
|
+
collectDeps = (fc2, key, value2) => deps[fc2 + ":" + key] = value2;
|
|
159
|
+
const value = fn.call(thisProxy);
|
|
160
|
+
collectDeps = void 0;
|
|
161
|
+
if (value instanceof Promise || Object.keys(deps).length === 0) return value;
|
|
162
|
+
return [$computed, { value, deps, fc, fn }, $vnode];
|
|
163
|
+
};
|
|
164
|
+
const thisProxy = new Proxy(/* @__PURE__ */ Object.create(null), {
|
|
165
|
+
get(target, key, receiver) {
|
|
166
|
+
switch (key) {
|
|
167
|
+
case "app":
|
|
168
|
+
return appState;
|
|
169
|
+
case "context":
|
|
170
|
+
return context ?? {};
|
|
171
|
+
case "request":
|
|
172
|
+
if (!request) {
|
|
173
|
+
throw new Error("request is not defined");
|
|
174
|
+
}
|
|
175
|
+
return request;
|
|
176
|
+
case "computed":
|
|
177
|
+
return computed;
|
|
178
|
+
default: {
|
|
179
|
+
const value = Reflect.get(target, key, receiver);
|
|
180
|
+
if (typeof key === "symbol") {
|
|
181
|
+
return value;
|
|
182
|
+
}
|
|
183
|
+
if (collectDeps) {
|
|
184
|
+
collectDeps(fc, key, value);
|
|
185
|
+
return value;
|
|
186
|
+
}
|
|
187
|
+
return [$state, { key, value, fc }, $vnode];
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
set(target, key, value, receiver) {
|
|
192
|
+
return Reflect.set(target, key, value, receiver);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
return thisProxy;
|
|
196
|
+
}
|
|
196
197
|
async function renderNode(rc, node, stripSlotProp) {
|
|
197
198
|
const { write } = rc;
|
|
198
199
|
switch (typeof node) {
|
|
@@ -207,7 +208,7 @@ async function renderNode(rc, node, stripSlotProp) {
|
|
|
207
208
|
if (isVNode(node)) {
|
|
208
209
|
const [tag, props] = node;
|
|
209
210
|
const { stateStore } = rc;
|
|
210
|
-
if (tag === $fragment
|
|
211
|
+
if (tag === $fragment) {
|
|
211
212
|
if (props.children !== void 0) {
|
|
212
213
|
await renderChildren(rc, props.children);
|
|
213
214
|
}
|
|
@@ -230,10 +231,8 @@ async function renderNode(rc, node, stripSlotProp) {
|
|
|
230
231
|
break;
|
|
231
232
|
}
|
|
232
233
|
if (tag === $computed) {
|
|
233
|
-
const { deps, value,
|
|
234
|
-
write(
|
|
235
|
-
'<m-state fc="' + fc + '" computed><script type="computed">$(' + fn + ", " + JSON.stringify(Object.keys(deps)) + ")<\/script>"
|
|
236
|
-
);
|
|
234
|
+
const { deps, value, fc } = props;
|
|
235
|
+
write('<m-state fc="' + fc + '" computed="' + rc.mcs.gen(node) + '">');
|
|
237
236
|
if (value !== void 0) {
|
|
238
237
|
write(escapeHTML("" + value));
|
|
239
238
|
}
|
|
@@ -264,14 +263,14 @@ async function renderNode(rc, node, stripSlotProp) {
|
|
|
264
263
|
if (tag === "toggle") {
|
|
265
264
|
const { value: valueProp, children } = props;
|
|
266
265
|
if (children !== void 0) {
|
|
267
|
-
if (isVNode(valueProp) && valueProp[0] === $state || valueProp[0] === $computed) {
|
|
268
|
-
const { key, deps, value,
|
|
266
|
+
if (isVNode(valueProp) && (valueProp[0] === $state || valueProp[0] === $computed)) {
|
|
267
|
+
const { key, deps, value, fc } = valueProp[1];
|
|
269
268
|
write('<m-state mode="toggle" fc="' + fc + '" ');
|
|
270
269
|
if (key) {
|
|
271
270
|
write("key=" + toAttrStringLit(key) + ">");
|
|
272
271
|
stateStore.set(fc + ":" + key, !!value);
|
|
273
272
|
} else {
|
|
274
|
-
write('computed
|
|
273
|
+
write('computed="' + rc.mcs.gen(valueProp) + '">');
|
|
275
274
|
for (const [key2, value2] of Object.entries(deps)) {
|
|
276
275
|
if (!stateStore.has(key2)) {
|
|
277
276
|
stateStore.set(key2, value2);
|
|
@@ -300,14 +299,14 @@ async function renderNode(rc, node, stripSlotProp) {
|
|
|
300
299
|
let computed;
|
|
301
300
|
let valueOrDefault;
|
|
302
301
|
if (isVNode(valueProp) && (valueProp[0] === $state || valueProp[0] === $computed)) {
|
|
303
|
-
const { key, deps, value,
|
|
302
|
+
const { key, deps, value, fc } = valueProp[1];
|
|
304
303
|
valueOrDefault = value ?? defaultValue;
|
|
305
304
|
stateful = '<m-state mode="switch" fc="' + fc + '" ';
|
|
306
305
|
if (key) {
|
|
307
306
|
stateful += "key=" + toAttrStringLit(key) + ">";
|
|
308
307
|
stateStore.set(fc + ":" + key, valueOrDefault);
|
|
309
308
|
} else {
|
|
310
|
-
stateful += 'computed
|
|
309
|
+
stateful += 'computed="' + rc.mcs.gen(valueProp) + '">';
|
|
311
310
|
for (const [key2, value2] of Object.entries(deps)) {
|
|
312
311
|
if (!stateStore.has(key2)) {
|
|
313
312
|
stateStore.set(key2, value2);
|
|
@@ -358,49 +357,49 @@ async function renderNode(rc, node, stripSlotProp) {
|
|
|
358
357
|
break;
|
|
359
358
|
}
|
|
360
359
|
if (typeof tag === "function") {
|
|
361
|
-
const fcIndex = ++rc.
|
|
360
|
+
const fcIndex = ++rc.status.fcIndex;
|
|
362
361
|
const { rendering, placeholder, catch: catchFC, ...fcProps } = props ?? /* @__PURE__ */ Object.create(null);
|
|
363
362
|
try {
|
|
364
|
-
const v = tag.call(
|
|
363
|
+
const v = tag.call(createThis(fcIndex, rc.appState, rc.context, rc.request), fcProps);
|
|
365
364
|
const { children } = fcProps;
|
|
366
365
|
const fcSlots = children !== void 0 ? Array.isArray(children) ? isVNode(children) ? [children] : children : [children] : void 0;
|
|
367
|
-
const eager = (rendering ?? tag.rendering) === "eager" || rc.eager;
|
|
368
366
|
if (v instanceof Promise) {
|
|
369
|
-
if (eager) {
|
|
370
|
-
await renderNode({ ...rc, fcIndex,
|
|
367
|
+
if ((rendering ?? tag.rendering) === "eager") {
|
|
368
|
+
await renderNode({ ...rc, fcIndex, fcSlots }, await v);
|
|
371
369
|
} else {
|
|
372
|
-
const
|
|
373
|
-
rc.suspenses.push(v.then(async (
|
|
374
|
-
write(
|
|
375
|
-
await renderNode({ ...rc, fcIndex,
|
|
370
|
+
const chunkIdAttr = 'chunk-id="' + (rc.status.chunkIndex++).toString(36) + '"';
|
|
371
|
+
rc.suspenses.push(v.then(async (node2) => {
|
|
372
|
+
write("<m-chunk " + chunkIdAttr + "><template>");
|
|
373
|
+
await renderNode({ ...rc, fcIndex, fcSlots }, node2);
|
|
376
374
|
write("</template></m-chunk>");
|
|
377
375
|
}));
|
|
378
|
-
write(
|
|
376
|
+
write("<m-portal " + chunkIdAttr + ">");
|
|
379
377
|
if (placeholder) {
|
|
380
|
-
await renderNode({ ...rc, fcIndex
|
|
378
|
+
await renderNode({ ...rc, fcIndex }, placeholder);
|
|
381
379
|
}
|
|
382
380
|
write("</m-portal>");
|
|
383
381
|
}
|
|
384
|
-
} else if (isObject(v) && Symbol.iterator in v && !isVNode(v)) {
|
|
385
|
-
for (const c of v) {
|
|
386
|
-
await renderNode({ ...rc, fcIndex, eager, fcSlots }, c);
|
|
387
|
-
}
|
|
388
382
|
} else if (isObject(v) && Symbol.asyncIterator in v) {
|
|
389
|
-
if (eager) {
|
|
383
|
+
if ((rendering ?? tag.rendering) === "eager") {
|
|
390
384
|
for await (const c of v) {
|
|
391
|
-
await renderNode({ ...rc, fcIndex,
|
|
385
|
+
await renderNode({ ...rc, fcIndex, fcSlots }, c);
|
|
392
386
|
}
|
|
393
387
|
} else {
|
|
394
388
|
}
|
|
389
|
+
} else if (isObject(v) && Symbol.iterator in v && !isVNode(v)) {
|
|
390
|
+
for (const node2 of v) {
|
|
391
|
+
await renderNode({ ...rc, fcIndex, fcSlots }, node2);
|
|
392
|
+
}
|
|
395
393
|
} else {
|
|
396
|
-
await renderNode({ ...rc, fcIndex,
|
|
394
|
+
await renderNode({ ...rc, fcIndex, fcSlots }, v);
|
|
397
395
|
}
|
|
398
396
|
} catch (err) {
|
|
399
397
|
if (err instanceof Error) {
|
|
400
398
|
if (typeof catchFC === "function") {
|
|
401
|
-
await renderNode({ ...rc, fcIndex
|
|
399
|
+
await renderNode({ ...rc, fcIndex }, catchFC(err));
|
|
402
400
|
} else {
|
|
403
|
-
write('<pre style="color:red;font-size:1rem"><code>' + escapeHTML(err.
|
|
401
|
+
write('<pre style="color:red;font-size:1rem"><code>' + escapeHTML(err.message) + "</code></pre>");
|
|
402
|
+
console.error(err);
|
|
404
403
|
}
|
|
405
404
|
}
|
|
406
405
|
}
|
|
@@ -408,32 +407,31 @@ async function renderNode(rc, node, stripSlotProp) {
|
|
|
408
407
|
}
|
|
409
408
|
if (isString(tag) && regexpHtmlTag.test(tag)) {
|
|
410
409
|
let buffer = "<" + tag;
|
|
411
|
-
let
|
|
412
|
-
let onMountHandler;
|
|
410
|
+
let stateTags = "";
|
|
413
411
|
for (let [propName, propValue] of Object.entries(props)) {
|
|
414
412
|
if (propName === "children") {
|
|
415
413
|
continue;
|
|
416
414
|
}
|
|
417
415
|
if (isVNode(propValue) && (propValue[0] === $state || propValue[0] === $computed)) {
|
|
418
|
-
const { key, value, deps,
|
|
416
|
+
const { key, value, deps, fc } = propValue[1];
|
|
419
417
|
if (propName === "class") {
|
|
420
|
-
rc.
|
|
418
|
+
rc.status.cx = true;
|
|
421
419
|
} else if (propName === "style") {
|
|
422
|
-
rc.
|
|
420
|
+
rc.status.styleToCSS = true;
|
|
423
421
|
}
|
|
424
|
-
|
|
422
|
+
stateTags += "<m-state mode=" + toAttrStringLit("[" + propName + "]") + ' fc="' + fc + '" ';
|
|
425
423
|
if (key) {
|
|
426
|
-
|
|
424
|
+
stateTags += "key=" + toAttrStringLit(key) + ">";
|
|
427
425
|
stateStore.set(fc + ":" + key, value);
|
|
428
426
|
} else {
|
|
429
|
-
|
|
427
|
+
stateTags += 'computed="' + rc.mcs.gen(propValue) + '">';
|
|
430
428
|
for (const [key2, value2] of Object.entries(deps)) {
|
|
431
429
|
if (!stateStore.has(key2)) {
|
|
432
430
|
stateStore.set(key2, value2);
|
|
433
431
|
}
|
|
434
432
|
}
|
|
435
433
|
}
|
|
436
|
-
|
|
434
|
+
stateTags += "</m-state>";
|
|
437
435
|
propValue = value;
|
|
438
436
|
}
|
|
439
437
|
switch (propName) {
|
|
@@ -469,19 +467,19 @@ async function renderNode(rc, node, stripSlotProp) {
|
|
|
469
467
|
if (pseudoStyles.length > 0 || atRuleStyles.length > 0 || nestingStyles.length > 0) {
|
|
470
468
|
let css = "";
|
|
471
469
|
let raw = "";
|
|
472
|
-
let styleIds;
|
|
473
470
|
let id;
|
|
474
471
|
let cssSelector;
|
|
472
|
+
let cssIds;
|
|
475
473
|
if (style.length > 0) {
|
|
476
474
|
css = styleToCSS(style);
|
|
477
475
|
raw += css + "|";
|
|
478
476
|
}
|
|
479
477
|
raw += [pseudoStyles, atRuleStyles, nestingStyles].flat(1).map(([k, v]) => k + ">" + v).join("|");
|
|
480
|
-
styleIds = rc.styleIds ?? (rc.styleIds = /* @__PURE__ */ new Set());
|
|
481
478
|
id = hashCode(raw).toString(36);
|
|
482
479
|
cssSelector = "[data-css-" + id + "]";
|
|
483
|
-
|
|
484
|
-
|
|
480
|
+
cssIds = rc.cssIds ?? (rc.cssIds = /* @__PURE__ */ new Set());
|
|
481
|
+
if (!cssIds.has(id)) {
|
|
482
|
+
cssIds.add(id);
|
|
485
483
|
if (css) {
|
|
486
484
|
css = cssSelector + "{" + css + "}";
|
|
487
485
|
}
|
|
@@ -504,14 +502,13 @@ async function renderNode(rc, node, stripSlotProp) {
|
|
|
504
502
|
break;
|
|
505
503
|
case "onMount":
|
|
506
504
|
if (typeof propValue === "function") {
|
|
507
|
-
|
|
505
|
+
rc.status.onmount++;
|
|
506
|
+
buffer += ' onmount="$emit(event,' + rc.mfs.gen(propValue) + str(rc.fcIndex, (i) => "," + i) + ')"';
|
|
508
507
|
}
|
|
509
508
|
break;
|
|
510
509
|
case "action":
|
|
511
510
|
if (typeof propValue === "function" && tag === "form") {
|
|
512
|
-
|
|
513
|
-
write("<script>function " + fn + "(fd){(" + propValue.toString() + ")(fd)}<\/script>");
|
|
514
|
-
buffer += ' onsubmit="$onsubmit(event,this,' + fn + (rc.fcIndex !== void 0 ? "," + rc.fcIndex : "") + ')"';
|
|
511
|
+
buffer += ' onsubmit="$onsubmit(event,' + rc.mfs.gen(propValue) + str(rc.fcIndex, (i) => "," + i) + ')"';
|
|
515
512
|
} else if (isString(propValue)) {
|
|
516
513
|
buffer += " action=" + toAttrStringLit(propValue);
|
|
517
514
|
}
|
|
@@ -525,9 +522,7 @@ async function renderNode(rc, node, stripSlotProp) {
|
|
|
525
522
|
if (regexpHtmlTag.test(propName) && propValue !== void 0) {
|
|
526
523
|
if (propName.startsWith("on")) {
|
|
527
524
|
if (typeof propValue === "function") {
|
|
528
|
-
|
|
529
|
-
write("<script>function " + fn + "(e){(" + propValue.toString() + ")(e)}<\/script>");
|
|
530
|
-
buffer += " " + propName.toLowerCase() + '="$emit(event,this,' + fn + (rc.fcIndex !== void 0 ? "," + rc.fcIndex : "") + ')"';
|
|
525
|
+
buffer += " " + propName.toLowerCase() + '="$emit(event,' + rc.mfs.gen(propValue) + str(rc.fcIndex, (i) => "," + i) + ')"';
|
|
531
526
|
}
|
|
532
527
|
} else if (typeof propValue === "boolean") {
|
|
533
528
|
if (propValue) {
|
|
@@ -541,8 +536,8 @@ async function renderNode(rc, node, stripSlotProp) {
|
|
|
541
536
|
}
|
|
542
537
|
write(buffer + ">");
|
|
543
538
|
if (!selfClosingTags.has(tag)) {
|
|
544
|
-
if (
|
|
545
|
-
write(
|
|
539
|
+
if (stateTags) {
|
|
540
|
+
write(stateTags);
|
|
546
541
|
}
|
|
547
542
|
if (props.innerHTML) {
|
|
548
543
|
write(props.innerHTML);
|
|
@@ -550,16 +545,12 @@ async function renderNode(rc, node, stripSlotProp) {
|
|
|
550
545
|
await renderChildren(rc, props.children);
|
|
551
546
|
}
|
|
552
547
|
write("</" + tag + ">");
|
|
553
|
-
} else if (
|
|
554
|
-
write("<m-group>" +
|
|
555
|
-
}
|
|
556
|
-
if (onMountHandler) {
|
|
557
|
-
rc.index.mf++;
|
|
558
|
-
write(
|
|
559
|
-
'<script>{const target=document.currentScript.previousElementSibling;addEventListener("load",()=>$emit({type:"mount",currentTarget:target,target},target,' + onMountHandler.toString() + (rc.fcIndex !== void 0 ? "," + rc.fcIndex : "") + "))}<\/script>"
|
|
560
|
-
);
|
|
548
|
+
} else if (stateTags) {
|
|
549
|
+
write("<m-group>" + stateTags + "</m-group>");
|
|
561
550
|
}
|
|
562
551
|
}
|
|
552
|
+
} else if (Array.isArray(node) && node.length > 0) {
|
|
553
|
+
renderChildren(rc, node, stripSlotProp);
|
|
563
554
|
}
|
|
564
555
|
break;
|
|
565
556
|
}
|
|
@@ -573,6 +564,21 @@ async function renderChildren(rc, children, stripSlotProp) {
|
|
|
573
564
|
await renderNode(rc, children, stripSlotProp);
|
|
574
565
|
}
|
|
575
566
|
}
|
|
567
|
+
var IdGenImpl = class extends Map {
|
|
568
|
+
constructor(_prefix) {
|
|
569
|
+
super();
|
|
570
|
+
this._prefix = _prefix;
|
|
571
|
+
}
|
|
572
|
+
_id = 0;
|
|
573
|
+
gen(v) {
|
|
574
|
+
let id = this.get(v);
|
|
575
|
+
if (id === void 0) {
|
|
576
|
+
id = this._prefix + (this._id++).toString(36);
|
|
577
|
+
this.set(v, id);
|
|
578
|
+
}
|
|
579
|
+
return id;
|
|
580
|
+
}
|
|
581
|
+
};
|
|
576
582
|
function render(node, renderOptions = {}) {
|
|
577
583
|
const { request, status, headers: headersInit } = renderOptions;
|
|
578
584
|
const headers = new Headers();
|
|
@@ -596,64 +602,99 @@ function render(node, renderOptions = {}) {
|
|
|
596
602
|
return new Response(
|
|
597
603
|
new ReadableStream({
|
|
598
604
|
async start(controller) {
|
|
599
|
-
const { appState: appStateInit, context,
|
|
605
|
+
const { appState: appStateInit, context, htmx } = renderOptions;
|
|
600
606
|
const write = (chunk) => controller.enqueue(encoder.encode(chunk));
|
|
601
|
-
const appState =
|
|
607
|
+
const appState = createThis(0, null, context, request);
|
|
602
608
|
const stateStore = /* @__PURE__ */ new Map();
|
|
603
609
|
const suspenses = [];
|
|
604
|
-
const rtComponents = { cx: false, styleToCSS: false };
|
|
605
610
|
const rc = {
|
|
606
611
|
write,
|
|
612
|
+
suspenses,
|
|
607
613
|
context,
|
|
608
614
|
request,
|
|
609
615
|
appState,
|
|
610
616
|
stateStore,
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
eager: rendering === "eager"
|
|
617
|
+
mcs: new IdGenImpl("$MC_"),
|
|
618
|
+
mfs: new IdGenImpl("$MF_"),
|
|
619
|
+
status: { fcIndex: 0, chunkIndex: 0, onmount: 0 }
|
|
615
620
|
};
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
if (value !== void 0) {
|
|
619
|
-
appState[key] = value;
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
try {
|
|
624
|
-
write("<!DOCTYPE html>");
|
|
625
|
-
await renderNode(rc, node);
|
|
621
|
+
const runtimeJS = {};
|
|
622
|
+
const finalize = async () => {
|
|
626
623
|
let js = "";
|
|
627
|
-
if (rc.
|
|
624
|
+
if (rc.status.cx && !runtimeJS.cx) {
|
|
625
|
+
runtimeJS.cx = true;
|
|
626
|
+
js += UTILS_JS.cx;
|
|
627
|
+
}
|
|
628
|
+
if (rc.status.styleToCSS && !runtimeJS.styleToCSS) {
|
|
629
|
+
runtimeJS.styleToCSS = true;
|
|
630
|
+
js += UTILS_JS.styleToCSS;
|
|
631
|
+
}
|
|
632
|
+
if (rc.mfs.size > 0 && !runtimeJS.event) {
|
|
633
|
+
runtimeJS.event = true;
|
|
628
634
|
js += UTILS_JS.event;
|
|
629
635
|
}
|
|
636
|
+
if (suspenses.length > 0 && !runtimeJS.suspense) {
|
|
637
|
+
runtimeJS.suspense = true;
|
|
638
|
+
js += SUSPENSE_JS;
|
|
639
|
+
}
|
|
640
|
+
if (stateStore.size > 0 && !runtimeJS.state) {
|
|
641
|
+
runtimeJS.state = true;
|
|
642
|
+
js += STATE_JS;
|
|
643
|
+
}
|
|
644
|
+
if (js) {
|
|
645
|
+
write("<script>/* runtime.js (generated by mono-jsx) */(()=>{" + js + "})()<\/script>");
|
|
646
|
+
}
|
|
647
|
+
js = "";
|
|
648
|
+
if (rc.mfs.size > 0) {
|
|
649
|
+
for (const [fn, fname] of rc.mfs.entries()) {
|
|
650
|
+
js += "function " + fname + "(){(" + fn.toString() + ").apply(this,arguments)};";
|
|
651
|
+
}
|
|
652
|
+
rc.mfs.clear();
|
|
653
|
+
}
|
|
630
654
|
if (stateStore.size > 0) {
|
|
631
|
-
|
|
632
|
-
js +=
|
|
655
|
+
for (const [key, value] of stateStore.entries()) {
|
|
656
|
+
js += "$defineState(" + JSON.stringify(key) + (value !== void 0 ? "," + JSON.stringify(value) : "") + ");";
|
|
633
657
|
}
|
|
634
|
-
|
|
635
|
-
|
|
658
|
+
stateStore.clear();
|
|
659
|
+
}
|
|
660
|
+
if (rc.mcs.size > 0) {
|
|
661
|
+
for (const [vnode, fname] of rc.mcs.entries()) {
|
|
662
|
+
const { fn, deps } = vnode[1];
|
|
663
|
+
js += '$defineComputed("' + fname + '",function(){return(' + fn.toString() + ").call(this)}," + JSON.stringify(Object.keys(deps)) + ");";
|
|
636
664
|
}
|
|
637
|
-
|
|
638
|
-
js += "for(let[k,v]of" + JSON.stringify(Array.from(stateStore.entries()).map((e) => e[1] === void 0 ? [e[0]] : e)) + ")$defineState(k,v);";
|
|
665
|
+
rc.mcs.clear();
|
|
639
666
|
}
|
|
640
|
-
if (
|
|
641
|
-
|
|
667
|
+
if (rc.status.onmount > 0) {
|
|
668
|
+
rc.status.onmount = 0;
|
|
669
|
+
js += "$onstage();";
|
|
642
670
|
}
|
|
643
671
|
if (js) {
|
|
644
|
-
write("<script
|
|
672
|
+
write("<script>/* app.js (generated by mono-jsx) */" + js + "<\/script>");
|
|
645
673
|
}
|
|
674
|
+
if (suspenses.length > 0) {
|
|
675
|
+
await Promise.all(suspenses.splice(0, suspenses.length));
|
|
676
|
+
await finalize();
|
|
677
|
+
}
|
|
678
|
+
};
|
|
679
|
+
if (appStateInit) {
|
|
680
|
+
for (const [key, value] of Object.entries(appStateInit)) {
|
|
681
|
+
if (value !== void 0) {
|
|
682
|
+
appState[key] = value;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
try {
|
|
687
|
+
write("<!DOCTYPE html>");
|
|
688
|
+
await renderNode(rc, node);
|
|
646
689
|
if (htmx) {
|
|
647
|
-
write(`<script src="
|
|
690
|
+
write(`<script src="${cdn}/htmx.org${htmx === true ? "" : escapeHTML("@" + htmx)}/dist/htmx.min.js"><\/script>`);
|
|
648
691
|
for (const [key, value] of Object.entries(renderOptions)) {
|
|
649
692
|
if (key.startsWith("htmx-ext-") && value) {
|
|
650
|
-
write(`<script src="
|
|
693
|
+
write(`<script src="${cdn}/${key}${value === true ? "" : escapeHTML("@" + value)}"><\/script>`);
|
|
651
694
|
}
|
|
652
695
|
}
|
|
653
696
|
}
|
|
654
|
-
|
|
655
|
-
await Promise.all(suspenses);
|
|
656
|
-
}
|
|
697
|
+
await finalize();
|
|
657
698
|
} finally {
|
|
658
699
|
controller.close();
|
|
659
700
|
}
|
package/package.json
CHANGED
package/types/html.d.ts
CHANGED
|
@@ -822,7 +822,7 @@ export namespace HTML {
|
|
|
822
822
|
|
|
823
823
|
interface EventAttributes<T extends EventTarget> {
|
|
824
824
|
// mono-jsx specific
|
|
825
|
-
onMount?: (
|
|
825
|
+
onMount?: (event: { type: "mount"; target: T }) => void | Promise<void>;
|
|
826
826
|
|
|
827
827
|
// Input Events
|
|
828
828
|
onBeforeInput?: EventHandler<Event, T>;
|
package/types/jsx.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import type * as Mono from "./mono.d.ts";
|
|
4
4
|
import type { HTML } from "./html.d.ts";
|
|
5
5
|
|
|
6
|
-
export type ChildType = VNode | string | number | bigint | boolean | null;
|
|
6
|
+
export type ChildType = VNode | VNode[] | string | number | bigint | boolean | null;
|
|
7
7
|
|
|
8
8
|
export type VNode = readonly [
|
|
9
9
|
tag: string | symbol | FC<any>,
|
|
@@ -13,14 +13,9 @@ export type VNode = readonly [
|
|
|
13
13
|
|
|
14
14
|
export interface FC<P = {}> {
|
|
15
15
|
(props: P): ChildType | Promise<ChildType> | Generator<ChildType> | AsyncGenerator<ChildType>;
|
|
16
|
-
displayName?: string;
|
|
17
16
|
rendering?: string;
|
|
18
17
|
}
|
|
19
18
|
|
|
20
|
-
export interface TC {
|
|
21
|
-
(strings: TemplateStringsArray, ...values: unknown[]): VNode;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
19
|
declare global {
|
|
25
20
|
namespace JSX {
|
|
26
21
|
type ElementType<P = any> =
|
|
@@ -28,9 +23,9 @@ declare global {
|
|
|
28
23
|
[K in keyof IntrinsicElements]: P extends IntrinsicElements[K] ? K : never;
|
|
29
24
|
}[keyof IntrinsicElements]
|
|
30
25
|
| FC<P>;
|
|
26
|
+
type Raw = (strings: TemplateStringsArray, ...values: unknown[]) => JSX.Element;
|
|
31
27
|
interface Element extends VNode, Response {}
|
|
32
28
|
interface IntrinsicAttributes extends Mono.BaseAttributes, Mono.AsyncComponentAttributes {}
|
|
33
29
|
interface IntrinsicElements extends HTML.Elements, HTML.SVGElements, Mono.Elements {}
|
|
34
30
|
}
|
|
35
|
-
var html: TC, css: TC, js: TC;
|
|
36
31
|
}
|
package/types/mono.d.ts
CHANGED
|
@@ -66,45 +66,73 @@ export interface CSSProperties extends BaseCSSProperties, AtRuleCSSProperties, P
|
|
|
66
66
|
[key: `&${" " | "." | "["}${string}`]: CSSProperties;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
export type ChildType = JSX.Element | string | number | bigint | boolean | null;
|
|
69
|
+
export type ChildType = JSX.Element | JSX.Element[] | string | number | bigint | boolean | null;
|
|
70
70
|
|
|
71
71
|
export interface BaseAttributes {
|
|
72
|
-
children?: ChildType |
|
|
72
|
+
children?: ChildType | ChildType[];
|
|
73
73
|
key?: string | number;
|
|
74
74
|
slot?: string;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
export interface AsyncComponentAttributes {
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
/**
|
|
79
|
+
* Error handler.
|
|
80
|
+
*/
|
|
80
81
|
catch?: (err: any) => JSX.Element;
|
|
82
|
+
/**
|
|
83
|
+
* Loading spinner.
|
|
84
|
+
*/
|
|
85
|
+
placeholder?: JSX.Element | string;
|
|
86
|
+
/**
|
|
87
|
+
* Rendering mode
|
|
88
|
+
* - `eager`: render immediately
|
|
89
|
+
*/
|
|
90
|
+
rendering?: "eager";
|
|
81
91
|
}
|
|
82
92
|
|
|
83
93
|
export interface Elements {
|
|
84
|
-
|
|
85
|
-
|
|
94
|
+
/**
|
|
95
|
+
* The `<toggle>` element is a custom element that represents a toggle switch.
|
|
96
|
+
*/
|
|
97
|
+
toggle: BaseAttributes & {
|
|
98
|
+
value?: boolean | string | number | null;
|
|
86
99
|
};
|
|
87
|
-
|
|
100
|
+
/**
|
|
101
|
+
* The `<switch>` element is a custom element that represents a switch.
|
|
102
|
+
*/
|
|
103
|
+
switch: BaseAttributes & {
|
|
88
104
|
value?: string;
|
|
89
105
|
defaultValue?: string;
|
|
90
106
|
};
|
|
91
|
-
cache: {
|
|
92
|
-
/** The cache key is used to identify the cache. */
|
|
93
|
-
key: string;
|
|
94
|
-
/** The `etag` (or **entity tag**) is an identifier for a specific version of a rendering cache. */
|
|
95
|
-
etag?: string;
|
|
96
|
-
/** The `max-age=N` prop indicates that the cache remains fresh until N seconds after the cache is generated. */
|
|
97
|
-
maxAge?: number;
|
|
98
|
-
/** The `stale-while-revalidate` prop indicates that the cache could reuse a stale rendering while it revalidates it to a cache. */
|
|
99
|
-
swr?: number;
|
|
100
|
-
};
|
|
101
107
|
}
|
|
102
108
|
|
|
103
109
|
declare global {
|
|
104
|
-
|
|
110
|
+
/**
|
|
111
|
+
* The `html` function is used to create XSS-unsafe HTML elements.
|
|
112
|
+
*/
|
|
113
|
+
var html: JSX.Raw, css: JSX.Raw, js: JSX.Raw;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* mono-jsx `this` object that is bound to the function component.
|
|
117
|
+
*/
|
|
118
|
+
type FC<State = {}, AppState = {}, Context = {}> = {
|
|
119
|
+
/**
|
|
120
|
+
* Application state.
|
|
121
|
+
* This is the state that is shared across the entire application.
|
|
122
|
+
*/
|
|
105
123
|
readonly app: AppState;
|
|
124
|
+
/**
|
|
125
|
+
* Context object.
|
|
126
|
+
*/
|
|
106
127
|
readonly context: Context;
|
|
128
|
+
/**
|
|
129
|
+
* Current request object.
|
|
130
|
+
*/
|
|
107
131
|
readonly request: Request;
|
|
132
|
+
/**
|
|
133
|
+
* The `computed` function is used to create a computed property.
|
|
134
|
+
* It takes a function that returns a value and returns the value.
|
|
135
|
+
*/
|
|
108
136
|
readonly computed: <V = unknown>(computeFn: () => V) => V;
|
|
109
|
-
} & Omit<
|
|
137
|
+
} & Omit<State, "app" | "context" | "request" | "computed">;
|
|
110
138
|
}
|
package/types/render.d.ts
CHANGED
|
@@ -41,11 +41,6 @@ export interface RenderOptions extends Partial<HtmxExts> {
|
|
|
41
41
|
lastModified?: string;
|
|
42
42
|
setCookie?: string;
|
|
43
43
|
};
|
|
44
|
-
/**
|
|
45
|
-
* Rendering mode.
|
|
46
|
-
* - **eager**: Render the component immediately.
|
|
47
|
-
*/
|
|
48
|
-
rendering?: "eager";
|
|
49
44
|
/**
|
|
50
45
|
* Install htmx script with the given version.
|
|
51
46
|
* @see https://htmx.org/
|