mono-jsx 0.3.2 β†’ 0.3.4

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 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` to define state in your components. The view will automatically update when the state changes:
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
- ### Limitation of States
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\. States cannot be used in arrow function components.
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\. States cannot be computed outside of the `this.computed` method.
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
- ## Built-in Elements
487
+ ## Using `this` in Components
439
488
 
440
- mono-jsx provides built-in elements to help you build reactive UIs.
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
- ### `<toggle>` element
491
+ The `this` object contains the following properties:
443
492
 
444
- The `<toggle>` element conditionally renders content based on a boolean value:
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
- ```tsx
447
- function App(this: FC<{ show: boolean }>) {
448
- this.show = false;
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
- function toggle() {
451
- this.show = !this.show;
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
- <toggle value={this.show}>
457
- <h1>Welcome to mono-jsx!</h1>
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
- ### `<switch>` element
539
+ ### Accessing Request Info
469
540
 
470
- 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:
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 App(this: FC<{ lang: "en" | "zh" | "emoji" }>) {
474
- this.lang = "en";
475
-
544
+ function RequestInfo(this: FC) {
545
+ const { request } = this;
476
546
  return (
477
547
  <div>
478
- <switch value={this.lang}>
479
- <h1 slot="en">Hello, world!</h1>
480
- <h1 slot="zh">δ½ ε₯½οΌŒδΈ–η•ŒοΌ</h1>
481
- <h1>βœ‹πŸŒŽβ—οΈ</h1>
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
- ## Using Context
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
- You can use the `context` property in `this` to access context values in your components. The context is defined on the root `<html>` element:
539
-
540
- ```tsx
541
- function Dash(this: FC<{}, {}, { auth: { uuid: string; name: string } }>) {
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 request={req}>
584
- <RequestInfo />
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
- ## Using htmx
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
- ### Adding htmx Extensions
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
- ### Specifying htmx Version
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
- ### Installing htmx Manually
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 p=new Map,f=e=>p.get(e)??p.set(e,E(e)).get(e),u=(e,t)=>e.getAttribute(t),d=(e,t)=>e.hasAttribute(t),E=e=>{const t=Object.create(null),l=new Map,s=(i,c)=>{let r=c;Object.defineProperty(t,i,{get:()=>r,set:o=>{if(o!==r){const a=l.get(i);a&&queueMicrotask(()=>a.forEach(m=>m())),r=o}}})},n=(i,c)=>{let r=l.get(i);r||(r=[],l.set(i,r)),r.push(c)};return e>0&&Object.defineProperty(t,"app",{get:()=>f(0).store,enumerable:!1,configurable:!1}),{store:t,define:s,watch:n}},g=(e,t,l)=>{if(t==="toggle"){let s;return()=>{if(!s){const n=e.firstElementChild;n&&n.tagName==="TEMPLATE"&&d(n,"m-slot")?(s=n.content.childNodes,e.innerHTML=""):s=e.childNodes}l()?e.append(...s):e.innerHTML=""}}if(t==="switch"){let s=u(e,"match"),n,i,c=o=>n.get(o)??n.set(o,[]).get(o),r;return()=>{if(!n){n=new Map,i=[];for(const o of e.childNodes)if(o.nodeType===1&&o.tagName==="TEMPLATE"&&d(o,"m-slot")){for(const a of o.content.childNodes)a.nodeType===1&&d(a,"slot")?c(u(a,"slot")).push(a):i.push(a);o.remove()}else s?c(s).push(o):i.push(o)}r=l(),e.innerHTML="",e.append(...n.has(r)?n.get(r):i)}}if(t&&t.length>2&&t.startsWith("[")&&t.endsWith("]")){let s=t.slice(1,-1),n=e.parentElement;return n.tagName==="M-GROUP"&&(n=n.previousElementSibling),()=>{const i=l();i===!1?n.removeAttribute(s):(s==="class"||s==="style")&&i&&typeof i=="object"?n.setAttribute(s,s==="class"?cx(i):styleToCSS(i)):n.setAttribute(s,i===!0?"":""+i)}}return()=>e.textContent=""+l()},h=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(Number(u(e,"fc"))),l=u(e,"mode"),s=u(e,"key");s?t.watch(s,g(e,l,()=>t.store[s])):d(e,"computed")&&setTimeout(()=>{const n=e.firstChild;if(n&&n.nodeType===1&&n.type==="computed"){const i=n.textContent;i&&new Function("$",i).call(t.store,(c,r)=>{const o=g(e,l,c);for(const a of r){const[m,T]=h(a);f(m).watch(T,o)}})}})}}),Object.assign(globalThis,{$state:e=>e!==void 0?f(e).store:void 0,$defineState:(e,t)=>{const[l,s]=h(e);f(l).define(s,t)}});`;
55
- 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)}})}`;
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)}}}});`;
10
+ var SUSPENSE_JS = `const n={},o=e=>e.getAttribute("chunk-id");c("m-portal",e=>{n[o(e)]=e}),c("m-chunk",e=>{setTimeout(()=>{const t=o(e),s=n[t];s&&(s.replaceWith(...e.firstChild.content.childNodes),e.remove(),delete n[t])})});function c(e,t){customElements.define(e,class extends HTMLElement{connectedCallback(){t(this)}})}`;
56
11
  var UTILS_JS = {
57
- /** cx.js (239 bytes) */
58
- cx: `let 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(" "):""}return t;})();`,
59
- /** styleToCSS.js (1203 bytes) */
60
- styleToCSS: `let styleToCSS=(()=>{var c=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"]),s=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 l(e){if(s(e))return e;if(u(e)){let r="";for(let[o,t]of Array.isArray(e)?e:Object.entries(e)){if(t==null||t===!1||Number.isNaN(t)||!s(o))return"";let n=f(o),i=typeof t=="number"?c.has(n)?""+t:t+"px":a(""+t);r+=(r!==""?";":"")+a(n)+":"+(n==="content"?JSON.stringify(i):i)}return r}return""}function a(e){return e.replace(/["<>]/g,r=>r==="<"?"&lt;":r===">"?"&gt;":"'")}return l;})();`,
61
- /** event.js (169 bytes) */
62
- event: `let w=window;w.$emit=(evt,el,fn,fc)=>fn.call(w.$state?.(fc)??el,evt);w.$onsubmit=(evt,el,fn,fc)=>{evt.preventDefault();fn.call(w.$state?.(fc)??el,new FormData(el),evt)};`
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==="<"?"&lt;":r===">"?"&gt;":"'")}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 (v === null || v === void 0 || v === false || Number.isNaN(v) || !isString(k)) return "";
134
- const cssKey = toHyphenCase(k);
135
- const cssValue = typeof v === "number" ? cssBareUnitProps.has(cssKey) ? "" + v : v + "px" : escapeCSSText("" + v);
136
- css += (css !== "" ? ";" : "") + escapeCSSText(cssKey) + ":" + (cssKey === "content" ? JSON.stringify(cssValue) : cssValue);
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(str) {
143
- return str.replace(/["<>]/g, (m) => {
98
+ function escapeCSSText(str2) {
99
+ return str2.replace(/["<>]/g, (m) => {
144
100
  if (m === "<") return "&lt;";
145
101
  if (m === ">") return "&gt;";
146
102
  return "'";
147
103
  });
148
104
  }
149
105
  var regexpHtmlSafe = /["'&<>]/;
150
- function escapeHTML(str) {
151
- if (typeof Bun === "object" && "escapeHTML" in Bun) return Bun.escapeHTML(str);
152
- const match = regexpHtmlSafe.exec(str);
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 str;
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 < str.length; index++) {
161
- switch (str.charCodeAt(index)) {
116
+ for (index = match.index; index < str2.length; index++) {
117
+ switch (str2.charCodeAt(index)) {
162
118
  case 34:
163
119
  escape = "&quot;";
164
120
  break;
@@ -178,21 +134,66 @@ function escapeHTML(str) {
178
134
  continue;
179
135
  }
180
136
  if (lastIndex !== index) {
181
- html2 += str.slice(lastIndex, index);
137
+ html2 += str2.slice(lastIndex, index);
182
138
  }
183
139
  lastIndex = index + 1;
184
140
  html2 += escape;
185
141
  }
186
- return lastIndex !== index ? html2 + str.slice(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 = (str) => '"' + escapeHTML(str) + '"';
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 || tag === "htmx") {
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, fn, fc } = props;
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, fn, fc } = valueProp[1];
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><script type="computed">$(' + fn + ", " + JSON.stringify(Object.keys(deps)) + ")<\/script>");
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, fn, fc } = valueProp[1];
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><script type="computed">$(' + fn + ", " + JSON.stringify(Object.keys(deps)) + ")<\/script>";
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,56 @@ async function renderNode(rc, node, stripSlotProp) {
358
357
  break;
359
358
  }
360
359
  if (typeof tag === "function") {
361
- const fcIndex = ++rc.index.fc;
362
- const { rendering, placeholder, catch: catchFC, ...fcProps } = props ?? /* @__PURE__ */ Object.create(null);
360
+ const fcIndex = ++rc.status.fcIndex;
361
+ const { children, rendering, placeholder, catch: catchFC } = props;
363
362
  try {
364
- const v = tag.call(createState(fcIndex, rc.appState, rc.context, rc.request), fcProps);
365
- const { children } = fcProps;
363
+ const v = tag.call(createThis(fcIndex, rc.appState, rc.context, rc.request), props);
366
364
  const fcSlots = children !== void 0 ? Array.isArray(children) ? isVNode(children) ? [children] : children : [children] : void 0;
367
- const eager = (rendering ?? tag.rendering) === "eager" || rc.eager;
365
+ const eager = (rendering ?? tag.rendering) === "eager";
368
366
  if (v instanceof Promise) {
369
367
  if (eager) {
370
- await renderNode({ ...rc, fcIndex, eager: true, fcSlots }, await v);
368
+ await renderNode({ ...rc, fcIndex, fcSlots }, await v);
371
369
  } else {
372
- const chunkId = (rc.suspenses.length + 1).toString(36);
373
- rc.suspenses.push(v.then(async (c) => {
374
- write('<m-chunk chunk-id="' + chunkId + '"><template>');
375
- await renderNode({ ...rc, fcIndex, eager, fcSlots }, c);
376
- write("</template></m-chunk>");
370
+ const chunkIdAttr = 'chunk-id="' + (rc.status.chunkIndex++).toString(36) + '"';
371
+ rc.suspenses.push(v.then(async (node2) => {
372
+ let buf = "<m-chunk " + chunkIdAttr + "><template>";
373
+ await renderNode({
374
+ ...rc,
375
+ fcIndex,
376
+ fcSlots,
377
+ write: (chunk) => {
378
+ buf += chunk;
379
+ }
380
+ }, node2);
381
+ return buf + "</template></m-chunk>";
377
382
  }));
378
- write('<m-portal chunk-id="' + chunkId + '">');
383
+ write("<m-portal " + chunkIdAttr + ">");
379
384
  if (placeholder) {
380
- await renderNode({ ...rc, fcIndex, eager: true }, placeholder);
385
+ await renderNode({ ...rc, fcIndex }, placeholder);
381
386
  }
382
387
  write("</m-portal>");
383
388
  }
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
389
  } else if (isObject(v) && Symbol.asyncIterator in v) {
389
390
  if (eager) {
390
391
  for await (const c of v) {
391
- await renderNode({ ...rc, fcIndex, eager: true, fcSlots }, c);
392
+ await renderNode({ ...rc, fcIndex, fcSlots }, c);
392
393
  }
393
394
  } else {
394
395
  }
396
+ } else if (isObject(v) && Symbol.iterator in v && !isVNode(v)) {
397
+ for (const node2 of v) {
398
+ await renderNode({ ...rc, fcIndex, fcSlots }, node2);
399
+ }
395
400
  } else {
396
- await renderNode({ ...rc, fcIndex, eager, fcSlots }, v);
401
+ await renderNode({ ...rc, fcIndex, fcSlots }, v);
397
402
  }
398
403
  } catch (err) {
399
404
  if (err instanceof Error) {
400
405
  if (typeof catchFC === "function") {
401
- await renderNode({ ...rc, fcIndex, eager: true }, catchFC(err));
406
+ await renderNode({ ...rc, fcIndex }, catchFC(err));
402
407
  } else {
403
- write('<pre style="color:red;font-size:1rem"><code>' + escapeHTML(err.stack ?? err.message) + "</code></pre>");
408
+ write('<pre style="color:red;font-size:1rem"><code>' + escapeHTML(err.message) + "</code></pre>");
409
+ console.error(err);
404
410
  }
405
411
  }
406
412
  }
@@ -408,32 +414,31 @@ async function renderNode(rc, node, stripSlotProp) {
408
414
  }
409
415
  if (isString(tag) && regexpHtmlTag.test(tag)) {
410
416
  let buffer = "<" + tag;
411
- let propEffects = [];
412
- let onMountHandler;
417
+ let stateTags = "";
413
418
  for (let [propName, propValue] of Object.entries(props)) {
414
419
  if (propName === "children") {
415
420
  continue;
416
421
  }
417
422
  if (isVNode(propValue) && (propValue[0] === $state || propValue[0] === $computed)) {
418
- const { key, value, deps, fn, fc } = propValue[1];
423
+ const { key, value, deps, fc } = propValue[1];
419
424
  if (propName === "class") {
420
- rc.runtimeUtils.cx = true;
425
+ rc.status.cx = true;
421
426
  } else if (propName === "style") {
422
- rc.runtimeUtils.styleToCSS = true;
427
+ rc.status.styleToCSS = true;
423
428
  }
424
- propEffects.push("<m-state mode=" + toAttrStringLit("[" + propName + "]") + ' fc="' + fc + '" ');
429
+ stateTags += "<m-state mode=" + toAttrStringLit("[" + propName + "]") + ' fc="' + fc + '" ';
425
430
  if (key) {
426
- propEffects.push("key=" + toAttrStringLit(key) + ">");
431
+ stateTags += "key=" + toAttrStringLit(key) + ">";
427
432
  stateStore.set(fc + ":" + key, value);
428
433
  } else {
429
- propEffects.push('computed><script type="computed">$(' + fn + ", " + JSON.stringify(Object.keys(deps)) + ")<\/script>");
434
+ stateTags += 'computed="' + rc.mcs.gen(propValue) + '">';
430
435
  for (const [key2, value2] of Object.entries(deps)) {
431
436
  if (!stateStore.has(key2)) {
432
437
  stateStore.set(key2, value2);
433
438
  }
434
439
  }
435
440
  }
436
- propEffects.push("</m-state>");
441
+ stateTags += "</m-state>";
437
442
  propValue = value;
438
443
  }
439
444
  switch (propName) {
@@ -469,19 +474,19 @@ async function renderNode(rc, node, stripSlotProp) {
469
474
  if (pseudoStyles.length > 0 || atRuleStyles.length > 0 || nestingStyles.length > 0) {
470
475
  let css = "";
471
476
  let raw = "";
472
- let styleIds;
473
477
  let id;
474
478
  let cssSelector;
479
+ let cssIds;
475
480
  if (style.length > 0) {
476
481
  css = styleToCSS(style);
477
482
  raw += css + "|";
478
483
  }
479
484
  raw += [pseudoStyles, atRuleStyles, nestingStyles].flat(1).map(([k, v]) => k + ">" + v).join("|");
480
- styleIds = rc.styleIds ?? (rc.styleIds = /* @__PURE__ */ new Set());
481
485
  id = hashCode(raw).toString(36);
482
486
  cssSelector = "[data-css-" + id + "]";
483
- if (!styleIds.has(id)) {
484
- styleIds.add(id);
487
+ cssIds = rc.cssIds ?? (rc.cssIds = /* @__PURE__ */ new Set());
488
+ if (!cssIds.has(id)) {
489
+ cssIds.add(id);
485
490
  if (css) {
486
491
  css = cssSelector + "{" + css + "}";
487
492
  }
@@ -504,14 +509,13 @@ async function renderNode(rc, node, stripSlotProp) {
504
509
  break;
505
510
  case "onMount":
506
511
  if (typeof propValue === "function") {
507
- onMountHandler = propValue;
512
+ rc.status.onmount++;
513
+ buffer += ' onmount="$emit(event,' + rc.mfs.gen(propValue) + str(rc.fcIndex, (i) => "," + i) + ')"';
508
514
  }
509
515
  break;
510
516
  case "action":
511
517
  if (typeof propValue === "function" && tag === "form") {
512
- const fn = "$MF_" + (rc.index.mf++).toString(36);
513
- write("<script>function " + fn + "(fd){(" + propValue.toString() + ")(fd)}<\/script>");
514
- buffer += ' onsubmit="$onsubmit(event,this,' + fn + (rc.fcIndex !== void 0 ? "," + rc.fcIndex : "") + ')"';
518
+ buffer += ' onsubmit="$onsubmit(event,' + rc.mfs.gen(propValue) + str(rc.fcIndex, (i) => "," + i) + ')"';
515
519
  } else if (isString(propValue)) {
516
520
  buffer += " action=" + toAttrStringLit(propValue);
517
521
  }
@@ -525,9 +529,7 @@ async function renderNode(rc, node, stripSlotProp) {
525
529
  if (regexpHtmlTag.test(propName) && propValue !== void 0) {
526
530
  if (propName.startsWith("on")) {
527
531
  if (typeof propValue === "function") {
528
- const fn = "$MF_" + (rc.index.mf++).toString(36);
529
- write("<script>function " + fn + "(e){(" + propValue.toString() + ")(e)}<\/script>");
530
- buffer += " " + propName.toLowerCase() + '="$emit(event,this,' + fn + (rc.fcIndex !== void 0 ? "," + rc.fcIndex : "") + ')"';
532
+ buffer += " " + propName.toLowerCase() + '="$emit(event,' + rc.mfs.gen(propValue) + str(rc.fcIndex, (i) => "," + i) + ')"';
531
533
  }
532
534
  } else if (typeof propValue === "boolean") {
533
535
  if (propValue) {
@@ -541,8 +543,8 @@ async function renderNode(rc, node, stripSlotProp) {
541
543
  }
542
544
  write(buffer + ">");
543
545
  if (!selfClosingTags.has(tag)) {
544
- if (propEffects.length > 0) {
545
- write(propEffects.join(""));
546
+ if (stateTags) {
547
+ write(stateTags);
546
548
  }
547
549
  if (props.innerHTML) {
548
550
  write(props.innerHTML);
@@ -550,16 +552,12 @@ async function renderNode(rc, node, stripSlotProp) {
550
552
  await renderChildren(rc, props.children);
551
553
  }
552
554
  write("</" + tag + ">");
553
- } else if (propEffects.length > 0) {
554
- write("<m-group>" + propEffects.join("") + "</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
- );
555
+ } else if (stateTags) {
556
+ write("<m-group>" + stateTags + "</m-group>");
561
557
  }
562
558
  }
559
+ } else if (Array.isArray(node) && node.length > 0) {
560
+ renderChildren(rc, node, stripSlotProp);
563
561
  }
564
562
  break;
565
563
  }
@@ -573,6 +571,21 @@ async function renderChildren(rc, children, stripSlotProp) {
573
571
  await renderNode(rc, children, stripSlotProp);
574
572
  }
575
573
  }
574
+ var IdGenImpl = class extends Map {
575
+ constructor(_prefix) {
576
+ super();
577
+ this._prefix = _prefix;
578
+ }
579
+ _id = 0;
580
+ gen(v) {
581
+ let id = this.get(v);
582
+ if (id === void 0) {
583
+ id = this._prefix + (this._id++).toString(36);
584
+ this.set(v, id);
585
+ }
586
+ return id;
587
+ }
588
+ };
576
589
  function render(node, renderOptions = {}) {
577
590
  const { request, status, headers: headersInit } = renderOptions;
578
591
  const headers = new Headers();
@@ -596,64 +609,99 @@ function render(node, renderOptions = {}) {
596
609
  return new Response(
597
610
  new ReadableStream({
598
611
  async start(controller) {
599
- const { appState: appStateInit, context, rendering, htmx } = renderOptions;
612
+ const { appState: appStateInit, context, htmx } = renderOptions;
600
613
  const write = (chunk) => controller.enqueue(encoder.encode(chunk));
601
- const appState = createState(0, null, context, request);
614
+ const appState = createThis(0, null, context, request);
602
615
  const stateStore = /* @__PURE__ */ new Map();
603
616
  const suspenses = [];
604
- const rtComponents = { cx: false, styleToCSS: false };
605
617
  const rc = {
606
618
  write,
619
+ suspenses,
607
620
  context,
608
621
  request,
609
622
  appState,
610
623
  stateStore,
611
- suspenses,
612
- runtimeUtils: rtComponents,
613
- index: { fc: 0, mf: 0 },
614
- eager: rendering === "eager"
624
+ mcs: new IdGenImpl("$MC_"),
625
+ mfs: new IdGenImpl("$MF_"),
626
+ status: { fcIndex: 0, chunkIndex: 0, onmount: 0 }
615
627
  };
616
- if (appStateInit) {
617
- for (const [key, value] of Object.entries(appStateInit)) {
618
- if (value !== void 0) {
619
- appState[key] = value;
620
- }
621
- }
622
- }
623
- try {
624
- write("<!DOCTYPE html>");
625
- await renderNode(rc, node);
628
+ const runtimeJS = {};
629
+ const finalize = async () => {
626
630
  let js = "";
627
- if (rc.index.mf > 0) {
631
+ if (rc.status.cx && !runtimeJS.cx) {
632
+ runtimeJS.cx = true;
633
+ js += UTILS_JS.cx;
634
+ }
635
+ if (rc.status.styleToCSS && !runtimeJS.styleToCSS) {
636
+ runtimeJS.styleToCSS = true;
637
+ js += UTILS_JS.styleToCSS;
638
+ }
639
+ if (rc.mfs.size > 0 && !runtimeJS.event) {
640
+ runtimeJS.event = true;
628
641
  js += UTILS_JS.event;
629
642
  }
643
+ if (suspenses.length > 0 && !runtimeJS.suspense) {
644
+ runtimeJS.suspense = true;
645
+ js += SUSPENSE_JS;
646
+ }
647
+ if (stateStore.size > 0 && !runtimeJS.state) {
648
+ runtimeJS.state = true;
649
+ js += STATE_JS;
650
+ }
651
+ if (js) {
652
+ write("<script>/* runtime.js (generated by mono-jsx) */(()=>{" + js + "})()<\/script>");
653
+ }
654
+ js = "";
655
+ if (rc.mfs.size > 0) {
656
+ for (const [fn, fname] of rc.mfs.entries()) {
657
+ js += "function " + fname + "(){(" + fn.toString() + ").apply(this,arguments)};";
658
+ }
659
+ rc.mfs.clear();
660
+ }
630
661
  if (stateStore.size > 0) {
631
- if (rtComponents.cx) {
632
- js += UTILS_JS.cx;
662
+ for (const [key, value] of stateStore.entries()) {
663
+ js += "$defineState(" + JSON.stringify(key) + (value !== void 0 ? "," + JSON.stringify(value) : "") + ");";
633
664
  }
634
- if (rtComponents.styleToCSS) {
635
- js += UTILS_JS.styleToCSS;
665
+ stateStore.clear();
666
+ }
667
+ if (rc.mcs.size > 0) {
668
+ for (const [vnode, fname] of rc.mcs.entries()) {
669
+ const { fn, deps } = vnode[1];
670
+ js += '$defineComputed("' + fname + '",function(){return(' + fn.toString() + ").call(this)}," + JSON.stringify(Object.keys(deps)) + ");";
636
671
  }
637
- js += STATE_JS;
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);";
672
+ rc.mcs.clear();
639
673
  }
640
- if (suspenses.length > 0) {
641
- js += SUSPENSE_JS;
674
+ if (rc.status.onmount > 0) {
675
+ rc.status.onmount = 0;
676
+ js += "$onstage();";
642
677
  }
643
678
  if (js) {
644
- write("<script>(()=>{" + js + "})()<\/script>");
679
+ write("<script>/* app.js (generated by mono-jsx) */" + js + "<\/script>");
680
+ }
681
+ if (suspenses.length > 0) {
682
+ await Promise.all(suspenses.splice(0, suspenses.length).map((suspense) => suspense.then(write)));
683
+ await finalize();
684
+ }
685
+ };
686
+ if (appStateInit) {
687
+ for (const [key, value] of Object.entries(appStateInit)) {
688
+ if (value !== void 0) {
689
+ appState[key] = value;
690
+ }
645
691
  }
692
+ }
693
+ try {
694
+ write("<!DOCTYPE html>");
695
+ await renderNode(rc, node);
646
696
  if (htmx) {
647
- write(`<script src="https://raw.esm.sh/htmx.org${htmx === true ? "" : escapeHTML("@" + htmx)}/dist/htmx.min.js"><\/script>`);
697
+ write(`<script src="${cdn}/htmx.org${htmx === true ? "" : escapeHTML("@" + htmx)}/dist/htmx.min.js"><\/script>`);
648
698
  for (const [key, value] of Object.entries(renderOptions)) {
649
699
  if (key.startsWith("htmx-ext-") && value) {
650
- write(`<script src="https://raw.esm.sh/${key}${value === true ? "" : escapeHTML("@" + value)}"><\/script>`);
700
+ write(`<script src="${cdn}/${key}${value === true ? "" : escapeHTML("@" + value)}"><\/script>`);
651
701
  }
652
702
  }
653
703
  }
654
- if (suspenses.length > 0) {
655
- await Promise.all(suspenses);
656
- }
704
+ await finalize();
657
705
  } finally {
658
706
  controller.close();
659
707
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mono-jsx",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "`<html>` as a `Response`.",
5
5
  "type": "module",
6
6
  "module": "./index.mjs",
package/types/css.d.ts CHANGED
@@ -6107,7 +6107,8 @@ export interface StandardShorthandProperties<TLength = (string & {}) | 0, TTime
6107
6107
  }
6108
6108
 
6109
6109
  export interface StandardProperties<TLength = (string & {}) | 0, TTime = string & {}>
6110
- extends StandardLonghandProperties<TLength, TTime>, StandardShorthandProperties<TLength, TTime> {}
6110
+ extends StandardLonghandProperties<TLength, TTime>, StandardShorthandProperties<TLength, TTime>
6111
+ {}
6111
6112
 
6112
6113
  export interface VendorLonghandProperties<TLength = (string & {}) | 0, TTime = string & {}> {
6113
6114
  /**
@@ -8018,7 +8019,8 @@ export interface VendorShorthandProperties<TLength = (string & {}) | 0, TTime =
8018
8019
  }
8019
8020
 
8020
8021
  export interface VendorProperties<TLength = (string & {}) | 0, TTime = string & {}>
8021
- extends VendorLonghandProperties<TLength, TTime>, VendorShorthandProperties<TLength, TTime> {}
8022
+ extends VendorLonghandProperties<TLength, TTime>, VendorShorthandProperties<TLength, TTime>
8023
+ {}
8022
8024
 
8023
8025
  export interface ObsoleteProperties<TLength = (string & {}) | 0, TTime = string & {}> {
8024
8026
  /**
@@ -9143,7 +9145,8 @@ export interface Properties<TLength = (string & {}) | 0, TTime = string & {}>
9143
9145
  StandardProperties<TLength, TTime>,
9144
9146
  VendorProperties<TLength, TTime>,
9145
9147
  ObsoleteProperties<TLength, TTime>,
9146
- SvgProperties<TLength, TTime> {}
9148
+ SvgProperties<TLength, TTime>
9149
+ {}
9147
9150
 
9148
9151
  export type AtRules =
9149
9152
  | "@charset"
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?: (e: { type: "mount"; currentTarget: T; target: T }) => void | Promise<void>;
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 | null)[] | string | number | bigint | boolean | null;
70
70
 
71
71
  export interface BaseAttributes {
72
- children?: ChildType | (ChildType)[];
72
+ children?: ChildType | ChildType[];
73
73
  key?: string | number;
74
74
  slot?: string;
75
75
  }
76
76
 
77
77
  export interface AsyncComponentAttributes {
78
- rendering?: "eager";
79
- placeholder?: JSX.Element | string;
78
+ /**
79
+ * Try to catch errors in the component.
80
+ */
80
81
  catch?: (err: any) => JSX.Element;
82
+ /**
83
+ * The loading spinner for the async component.
84
+ */
85
+ placeholder?: JSX.Element;
86
+ /**
87
+ * Rendering mode
88
+ * - `eager`: render async component eagerly
89
+ */
90
+ rendering?: "eager";
81
91
  }
82
92
 
83
93
  export interface Elements {
84
- toggle: {
85
- value: boolean;
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
- switch: {
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
- type FC<S = Record<string, unknown>, AppState = Record<string, unknown>, Context = Record<string, unknown>> = {
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<S, "app" | "context" | "request" | "computed">;
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/