mono-jsx 0.6.3 → 0.6.5
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 +64 -41
- package/index.mjs +5 -0
- package/jsx-runtime.mjs +54 -37
- package/package.json +1 -1
- package/types/html.d.ts +2 -0
- package/types/index.d.ts +11 -0
- package/types/jsx-runtime.d.ts +5 -4
- package/types/mono.d.ts +20 -9
- package/types/render.d.ts +3 -3
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ mono-jsx is a JSX runtime that renders `<html>` element to `Response` object in
|
|
|
10
10
|
- ⚡️ Use web components, no virtual DOM
|
|
11
11
|
- 💡 Complete Web API TypeScript definitions
|
|
12
12
|
- ⏳ Streaming rendering
|
|
13
|
-
- 🗂️ Built-in router
|
|
13
|
+
- 🗂️ Built-in router(SPA mode)
|
|
14
14
|
- 🥷 [htmx](#using-htmx) integration
|
|
15
15
|
- 🌎 Universal, works in Node.js, Deno, Bun, Cloudflare Workers, etc.
|
|
16
16
|
|
|
@@ -150,11 +150,13 @@ mono-jsx supports [pseudo classes](https://developer.mozilla.org/en-US/docs/Web/
|
|
|
150
150
|
```tsx
|
|
151
151
|
<a
|
|
152
152
|
style={{
|
|
153
|
+
display: "inline-flex",
|
|
154
|
+
gap: "0.5em",
|
|
153
155
|
color: "black",
|
|
154
156
|
"::after": { content: "↩️" },
|
|
155
157
|
":hover": { textDecoration: "underline" },
|
|
156
158
|
"@media (prefers-color-scheme: dark)": { color: "white" },
|
|
157
|
-
"& .icon": { width: "1em", height: "1em"
|
|
159
|
+
"& .icon": { width: "1em", height: "1em" },
|
|
158
160
|
}}
|
|
159
161
|
>
|
|
160
162
|
<img class="icon" src="link.png" />
|
|
@@ -361,7 +363,7 @@ interface AppSignals {
|
|
|
361
363
|
function Header(this: FC<{}, AppSignals>) {
|
|
362
364
|
return (
|
|
363
365
|
<header>
|
|
364
|
-
<h1 style={
|
|
366
|
+
<h1 style={{ color: this.app.themeColor }}>Welcome to mono-jsx!</h1>
|
|
365
367
|
</header>
|
|
366
368
|
)
|
|
367
369
|
}
|
|
@@ -369,7 +371,7 @@ function Header(this: FC<{}, AppSignals>) {
|
|
|
369
371
|
function Footer(this: FC<{}, AppSignals>) {
|
|
370
372
|
return (
|
|
371
373
|
<footer>
|
|
372
|
-
<p style={
|
|
374
|
+
<p style={{ color: this.app.themeColor }}>(c) 2025 mono-jsx.</p>
|
|
373
375
|
</footer>
|
|
374
376
|
)
|
|
375
377
|
}
|
|
@@ -836,9 +838,9 @@ export default {
|
|
|
836
838
|
}
|
|
837
839
|
```
|
|
838
840
|
|
|
839
|
-
## Using Router(SPA)
|
|
841
|
+
## Using Router(SPA Mode)
|
|
840
842
|
|
|
841
|
-
mono-jsx provides a built-in `<router>` element that allows your app to render components based on the current URL. On client side, it
|
|
843
|
+
mono-jsx provides a built-in `<router>` element that allows your app to render components based on the current URL. On client side, it listens all `click` events on `<a>` elements and asynchronously fetches the route component without reloading the entire page.
|
|
842
844
|
|
|
843
845
|
To use the router, you need to define your routes as a mapping of URL patterns to components and pass it to the `<html>` element as `routes` prop. The `request` prop is also required to match the current URL against the defined routes.
|
|
844
846
|
|
|
@@ -853,13 +855,11 @@ const routes = {
|
|
|
853
855
|
export default {
|
|
854
856
|
fetch: (req) => (
|
|
855
857
|
<html request={req} routes={routes}>
|
|
856
|
-
<
|
|
857
|
-
<
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
</nav>
|
|
862
|
-
</header>
|
|
858
|
+
<nav>
|
|
859
|
+
<a href="/">Home</a>
|
|
860
|
+
<a href="/about">About</a>
|
|
861
|
+
<a href="/blog">Blog</a>
|
|
862
|
+
</nav>
|
|
863
863
|
<router />
|
|
864
864
|
</html>
|
|
865
865
|
)
|
|
@@ -872,12 +872,10 @@ mono-jsx router requires [URLPattern](https://developer.mozilla.org/en-US/docs/W
|
|
|
872
872
|
- ✅ Cloudflare Workers
|
|
873
873
|
- ✅ Nodejs (>= 24)
|
|
874
874
|
|
|
875
|
-
For Bun users, mono-jsx provides a `
|
|
875
|
+
For Bun users, mono-jsx provides a `buildRoutes` function that uses Bun's built-in server routing:
|
|
876
876
|
|
|
877
877
|
```tsx
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
import { monoRoutes } from "mono-jsx"
|
|
878
|
+
import { buildRoutes } from "mono-jsx"
|
|
881
879
|
|
|
882
880
|
const routes = {
|
|
883
881
|
"/": Home,
|
|
@@ -886,13 +884,18 @@ const routes = {
|
|
|
886
884
|
"/post/:id": Post,
|
|
887
885
|
}
|
|
888
886
|
|
|
889
|
-
|
|
890
|
-
routes:
|
|
891
|
-
<html request={
|
|
887
|
+
Bun.serve({
|
|
888
|
+
routes: buildRoutes((req) => (
|
|
889
|
+
<html request={req} routes={routes}>
|
|
890
|
+
<nav>
|
|
891
|
+
<a href="/">Home</a>
|
|
892
|
+
<a href="/about">About</a>
|
|
893
|
+
<a href="/blog">Blog</a>
|
|
894
|
+
</nav>
|
|
892
895
|
<router />
|
|
893
896
|
</html>
|
|
894
897
|
))
|
|
895
|
-
}
|
|
898
|
+
})
|
|
896
899
|
```
|
|
897
900
|
|
|
898
901
|
### Using Route `params`
|
|
@@ -909,46 +912,66 @@ function Post(this: FC) {
|
|
|
909
912
|
}
|
|
910
913
|
```
|
|
911
914
|
|
|
912
|
-
###
|
|
915
|
+
### Navigation between Pages
|
|
913
916
|
|
|
914
|
-
|
|
917
|
+
To navigate between pages, you can use `<a>` elements with `href` attributes that match the defined routes. The router will intercept the click events of these links and fetch the corresponding route component without reloading the page:
|
|
915
918
|
|
|
916
919
|
```tsx
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
<
|
|
921
|
-
<
|
|
922
|
-
|
|
923
|
-
|
|
920
|
+
function App() {
|
|
921
|
+
export default {
|
|
922
|
+
fetch: (req) => (
|
|
923
|
+
<html request={req} routes={routes}>
|
|
924
|
+
<nav>
|
|
925
|
+
<a href="/">Home</a>
|
|
926
|
+
<a href="/about">About</a>
|
|
927
|
+
<a href="/blog">Blog</a>
|
|
928
|
+
</nav>
|
|
929
|
+
<router />
|
|
930
|
+
</html>
|
|
924
931
|
)
|
|
925
932
|
}
|
|
926
933
|
```
|
|
927
934
|
|
|
928
935
|
### Nav Links
|
|
929
936
|
|
|
930
|
-
Links under
|
|
937
|
+
Links under the `<nav>` element will be treated as navigation links by the router. When the `href` of a nav link matches a route, a active class will be added to the link element. By default, the active class is `active`, but you can customize it by setting the `data-active-class` attribute on the `<nav>` element. You can add style for the active link using nested CSS selectors in the `style` attribute of the `<nav>` element.
|
|
931
938
|
|
|
932
939
|
```tsx
|
|
933
940
|
export default {
|
|
934
941
|
fetch: (req) => (
|
|
935
942
|
<html request={req} routes={routes}>
|
|
936
|
-
<
|
|
937
|
-
<
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
</nav>
|
|
942
|
-
</header>
|
|
943
|
+
<nav style={{ "& a.active": { fontWeight: "bold" } }} data-active-class="active">
|
|
944
|
+
<a href="/">Home</a>
|
|
945
|
+
<a href="/about">About</a>
|
|
946
|
+
<a href="/blog">Blog</a>
|
|
947
|
+
</nav>
|
|
943
948
|
<router />
|
|
944
949
|
</html>
|
|
945
950
|
)
|
|
946
951
|
}
|
|
947
952
|
```
|
|
948
953
|
|
|
954
|
+
### Using DB/Storage in Route Components
|
|
955
|
+
|
|
956
|
+
Route components are always rendered on server-side, you can use any database or storage API to fetch data in your route components.
|
|
957
|
+
|
|
958
|
+
```tsx
|
|
959
|
+
async function Post(this: FC) {
|
|
960
|
+
const post = await sql`SELECT * FROM posts WHERE id = ${ this.request.params!.id }`
|
|
961
|
+
return (
|
|
962
|
+
<article>
|
|
963
|
+
<h2>{post.title}<h2>
|
|
964
|
+
<section>
|
|
965
|
+
{html(post.content)}
|
|
966
|
+
</section>
|
|
967
|
+
</article>
|
|
968
|
+
)
|
|
969
|
+
}
|
|
970
|
+
```
|
|
971
|
+
|
|
949
972
|
### Fallback(404)
|
|
950
973
|
|
|
951
|
-
You can add fallback(404) content to the `<router>` element as children, which will be displayed when no route matches the current URL
|
|
974
|
+
You can add fallback(404) content to the `<router>` element as children, which will be displayed when no route matches the current URL.
|
|
952
975
|
|
|
953
976
|
```tsx
|
|
954
977
|
export default {
|
|
@@ -1044,9 +1067,9 @@ export default {
|
|
|
1044
1067
|
}
|
|
1045
1068
|
```
|
|
1046
1069
|
|
|
1047
|
-
####
|
|
1070
|
+
#### Setup htmx Manually
|
|
1048
1071
|
|
|
1049
|
-
By default, mono-jsx
|
|
1072
|
+
By default, mono-jsx imports htmx from [esm.sh](https://esm.sh/) CDN when you set the `htmx` attribute. You can also setup htmx manually with your own CDN or local copy:
|
|
1050
1073
|
|
|
1051
1074
|
```tsx
|
|
1052
1075
|
export default {
|
package/index.mjs
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
// index.ts
|
|
2
|
+
function buildRoutes(handler) {
|
|
3
|
+
const { routes = {} } = handler(Symbol.for("mono.peek"));
|
|
4
|
+
return monoRoutes(routes, handler);
|
|
5
|
+
}
|
|
2
6
|
function monoRoutes(routes, handler) {
|
|
3
7
|
const handlers = {};
|
|
4
8
|
for (const [path, fc] of Object.entries(routes)) {
|
|
@@ -10,5 +14,6 @@ function monoRoutes(routes, handler) {
|
|
|
10
14
|
return handlers;
|
|
11
15
|
}
|
|
12
16
|
export {
|
|
17
|
+
buildRoutes,
|
|
13
18
|
monoRoutes
|
|
14
19
|
};
|
package/jsx-runtime.mjs
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
var EVENT_JS = `{var w=window;w.$emit=(e,f,s)=>f.call(w.$signals?.(s)??e.target,e.type==="mount"?e.target:e);w.$onsubmit=(e,f,s)=>{e.preventDefault();f.call(w.$signals?.(s)??e.target,new FormData(e.target),e)};}`;
|
|
5
|
-
var SIGNALS_JS = `{let p;const m=window,E=new Map,b=new Map,d=e=>b.get(e)??b.set(e,S(e)).get(e),u=(e,t)=>e.getAttribute(t),y=(e,t,r)=>e.setAttribute(t,r),M=(e,t)=>e.replaceChildren(...t),v=()=>Object.create(null),S=e=>{const t=v(),r=(c,l)=>{t[c]=l},n=new Map,i=(c,l)=>{let o=n.get(c);return o||(o=new Set,n.set(c,o)),o.add(l),()=>{o.delete(l),o.size===0&&n.delete(c)}},s=new Proxy(v(),{get:(c,l)=>document.querySelector("[data-ref='"+e+":"+l+"']")});return new Proxy(t,{get:(c,l,o)=>{switch(l){case"$init":return r;case"$watch":return i;case"app":return d(0);case"refs":return s;default:return p?.(e,l),Reflect.get(c,l,o)}},set:(c,l,o,a)=>{if(o!==Reflect.get(c,l,a)){const f=n.get(l);return f&&queueMicrotask(()=>f.forEach(g=>g())),Reflect.set(c,l,o,a)}return!1}})},k=(e,t,r)=>{if(t==="toggle"){let n;return()=>{if(!n){const i=e.firstElementChild;i&&i.tagName==="TEMPLATE"&&i.hasAttribute("m-slot")?n=[...i.content.childNodes]:n=[...e.childNodes]}M(e,r()?n:[])}}if(t==="switch"){let n,i=u(e,"match"),s,c,l=o=>s.get(o)??s.set(o,[]).get(o);return()=>{if(!s){s=new Map,c=[];for(const o of e.childNodes)if(o.nodeType===1&&o.tagName==="TEMPLATE"&&o.hasAttribute("m-slot")){for(const a of o.content.childNodes)a.nodeType===1&&a.hasAttribute("slot")?l(u(a,"slot")).push(a):c.push(a);o.remove()}else i?l(i).push(o):c.push(o)}n=""+r(),M(e,s.has(n)?s.get(n):c)}}if(t&&t.length>2&&t.startsWith("[")&&t.endsWith("]")){let n=t.slice(1,-1),i=e.parentElement;return i.tagName==="M-GROUP"&&(i=i.previousElementSibling),()=>{const s=r();s===!1||s===null||s===void 0?i.removeAttribute(n):typeof s=="object"&&s!==null&&(n==="class"||n==="style"||n==="props")?n==="class"?y(i,n,$cx(s)):n==="style"?$applyStyle(i,s):y(i,n,JSON.stringify(s)):y(i,n,s===!0?"":s)}}return()=>e.textContent=""+r()},N=e=>{const t=e.indexOf(":");return t>0?[Number(e.slice(0,t)),e.slice(t+1)]:null},w=async e=>{const t=e();return t!==void 0?t:(await new Promise(r=>setTimeout(r,0)),w(e))},T=(e,t)=>customElements.define(e,class extends HTMLElement{disposes=[];connectedCallback(){t(this)}disconnectedCallback(){this.disposes.forEach(r=>r()),this.disposes.length=0}});T("m-signal",e=>{const t=d(Number(u(e,"scope"))),r=u(e,"key");if(r)e.disposes.push(t.$watch(r,k(e,u(e,"mode"),()=>t[r])));else{const n=Number(u(e,"computed"));w(()=>E.get(n)).then(([i,s])=>{const c=k(e,u(e,"mode"),i.bind(t));s.forEach(l=>{const[o,a]=N(l);e.disposes.push(d(o).$watch(a,c))})})}}),T("m-effect",e=>{const{disposes:t}=e,r=Number(u(e,"scope")),n=Number(u(e,"n")),i=new Array(n);t.push(()=>{i.forEach(s=>typeof s=="function"&&s()),i.length=0});for(let s=0;s<n;s++){const c="$ME_"+r+"_"+s;w(()=>m[c]).then(l=>{const o=[],a=d(r),f=()=>{i[s]=l.call(a)};p=(g,h)=>o.push([g,h]),f(),p=void 0;for(const[g,h]of o)t.push(d(g).$watch(h,f))},()=>{})}}),m.$signals=e=>e!==void 0?d(e):void 0,m.$MS=(e,t)=>{const[r,n]=N(e);d(r).$init(n,t)},m.$MC=(e,t,r)=>{E.set(e,[t,r])},m.$merge=(e,...t)=>{for(const[r,...n]of t){let i=e;const s=n.pop();for(const c of n)i=i[c];i[s]=r}return e};}`;
|
|
6
|
-
var SUSPENSE_JS = `{const s=new Map,i=e=>e.getAttribute("chunk-id"),l=(e,t)=>customElements.define(e,class extends HTMLElement{connectedCallback(){t(this)}});l("m-portal",e=>{s.set(i(e),e)}),l("m-chunk",e=>{setTimeout(()=>{const t=e.firstChild?.content.childNodes,o=i(e),n=s.get(o);n&&(e.hasAttribute("next")?n.before(...t):(s.delete(o),e.hasAttribute("done")?n.remove():n.replaceWith(...t)),e.remove())})});}`;
|
|
7
|
-
var LAZY_JS = `{const r=document,o=(t,e)=>t.getAttribute(e),i=(t,e=[])=>t.replaceChildren(...e);customElements.define("m-component",class extends HTMLElement{static observedAttributes=["name","props"];#e;#i;#n;#t;#s;async#r(){const t={"x-component":this.#e,"x-props":this.#i??"{}","x-runtime-flag":""+$runtimeFlag,"x-scope-seq":""+$scopeSeq},e=new AbortController;this.#s?.abort(),this.#s=e,i(this,this.#n);const s=await fetch(location.href,{headers:t,signal:e.signal});if(!s.ok)throw i(this),new Error("Failed to fetch component '"+name+"'");const[l,n]=await s.json();this.innerHTML=l,n&&(r.body.appendChild(r.createElement("script")).textContent=n)}connectedCallback(){setTimeout(()=>{if(!this.#e){const t=o(this,"name"),e=o(this,"props");if(!t)throw new Error("Component name is required");this.#e=t,this.#i=e?.startsWith("base64,")?atob(e.slice(7)):null,this.#n=[...this.childNodes]}this.#r()})}disconnectedCallback(){i(this,this.#n),this.#s?.abort(),this.#t&&clearTimeout(this.#t),this.#s=void 0,this.#t=void 0}attributeChangedCallback(t,e,s){this.#e&&s&&e!==s&&(t==="name"?this.#e=s:t==="props"&&(this.#i=s),this.#t&&clearTimeout(this.#t),this.#t=setTimeout(()=>{this.#t=void 0,this.#r()},20))}});}`;
|
|
8
|
-
var ROUTER_JS = `{const n=document,a=t=>t.split("#",1)[0];customElements.define("m-router",class extends HTMLElement{#t;#e;#s;#i;async#o(t){const i={"x-route":"true","x-runtime-flag":""+$runtimeFlag,"x-scope-seq":""+$scopeSeq},e=new AbortController;this.#i?.abort(),this.#i=e;const s=await fetch(t,{headers:i,signal:e.signal});if(s.status===404){this.replaceChildren(...this.#t);return}if(!s.ok)throw this.replaceChildren(),new Error("Failed to fetch route: "+s.status+" "+s.statusText);const[o,r]=await s.json();this.innerHTML=o,r&&(n.body.appendChild(n.createElement("script")).textContent=r)}#n(){n.querySelectorAll("nav a").forEach(t=>{const{href:i,classList:e}=t,s=t.closest("nav").getAttribute("data-active-class")??"active";a(i)===a(location.href)?e.add(s):e.remove(s)})}#a(t){this.#o(t),this.#n()}connectedCallback(){setTimeout(()=>{if(!this.#t)if(this.getAttribute("status")==="404")this.#t=[...this.childNodes];else{this.#t=[];for(const t of this.childNodes)if(t.nodeType===1&&t.tagName==="TEMPLATE"&&t.hasAttribute("m-slot")){this.#t.push(...t.content.childNodes),t.remove();break}}}),this.#e=t=>{if(t.defaultPrevented||t.altKey||t.ctrlKey||t.metaKey||t.shiftKey||!(t.target instanceof HTMLAnchorElement))return;const{download:i,href:e,rel:s,target:o}=t.target;i||s==="external"||o==="_blank"||!e||!e.startsWith(location.origin)||a(e)===a(location.href)||(t.preventDefault(),history.pushState({},"",e),this.#a(e))},this.#s=()=>this.#a(location.href),addEventListener("popstate",this.#s),n.addEventListener("click",this.#e),setTimeout(()=>this.#n())}disconnectedCallback(){removeEventListener("popstate",this.#s),n.removeEventListener("click",this.#e),this.#i?.abort(),this.#s=void 0,this.#e=void 0,this.#i=void 0}});}`;
|
|
1
|
+
// render.ts
|
|
2
|
+
import { F_CX, F_EVENT, F_LAZY, F_ROUTER, F_SIGNALS, F_STYLE, F_SUSPENSE } from "./runtime/index.mjs";
|
|
3
|
+
import { CX_JS, EVENT_JS, LAZY_JS, ROUTER_JS, SIGNALS_JS, STYLE_JS, SUSPENSE_JS } from "./runtime/index.mjs";
|
|
9
4
|
|
|
10
5
|
// runtime/utils.ts
|
|
11
6
|
var regexpHtmlSafe = /["'&<>]/;
|
|
@@ -121,18 +116,12 @@ var $vnode = Symbol.for("jsx.vnode");
|
|
|
121
116
|
var $fragment = Symbol.for("jsx.fragment");
|
|
122
117
|
var $html = Symbol.for("jsx.html");
|
|
123
118
|
var $signal = Symbol.for("mono.signal");
|
|
119
|
+
var $peek = Symbol.for("mono.peek");
|
|
124
120
|
|
|
125
121
|
// version.ts
|
|
126
|
-
var VERSION = "0.6.
|
|
122
|
+
var VERSION = "0.6.4";
|
|
127
123
|
|
|
128
124
|
// render.ts
|
|
129
|
-
var RUNTIME_CX = 1;
|
|
130
|
-
var RUNTIME_STYLE = 2;
|
|
131
|
-
var RUNTIME_EVENT = 4;
|
|
132
|
-
var RUNTIME_SIGNALS = 8;
|
|
133
|
-
var RUNTIME_SUSPENSE = 16;
|
|
134
|
-
var RUNTIME_LAZY = 32;
|
|
135
|
-
var RUNTIME_ROUTER = 64;
|
|
136
125
|
var cdn = "https://raw.esm.sh";
|
|
137
126
|
var encoder = new TextEncoder();
|
|
138
127
|
var customElements = /* @__PURE__ */ new Map();
|
|
@@ -296,38 +285,38 @@ async function render(node, options, write, writeJS, componentMode) {
|
|
|
296
285
|
};
|
|
297
286
|
const finalize = async () => {
|
|
298
287
|
let js = "";
|
|
299
|
-
if (rc.flags.runtime &
|
|
300
|
-
runtimeFlag |=
|
|
288
|
+
if (rc.flags.runtime & F_CX && !(runtimeFlag & F_CX)) {
|
|
289
|
+
runtimeFlag |= F_CX;
|
|
301
290
|
js += CX_JS;
|
|
302
291
|
}
|
|
303
|
-
if (rc.flags.runtime &
|
|
304
|
-
runtimeFlag |=
|
|
292
|
+
if (rc.flags.runtime & F_STYLE && !(runtimeFlag & F_STYLE)) {
|
|
293
|
+
runtimeFlag |= F_STYLE;
|
|
305
294
|
js += STYLE_JS;
|
|
306
295
|
}
|
|
307
|
-
if (rc.mfs.size > 0 && !(runtimeFlag &
|
|
308
|
-
runtimeFlag |=
|
|
296
|
+
if (rc.mfs.size > 0 && !(runtimeFlag & F_EVENT)) {
|
|
297
|
+
runtimeFlag |= F_EVENT;
|
|
309
298
|
js += EVENT_JS;
|
|
310
299
|
}
|
|
311
|
-
if (signals.store.size
|
|
312
|
-
runtimeFlag |=
|
|
300
|
+
if ((signals.store.size > 0 || rc.mcs.size > 0 || signals.effects.length > 0) && !(runtimeFlag & F_SIGNALS)) {
|
|
301
|
+
runtimeFlag |= F_SIGNALS;
|
|
313
302
|
js += SIGNALS_JS;
|
|
314
303
|
}
|
|
315
|
-
if (suspenses.length > 0 && !(runtimeFlag &
|
|
316
|
-
runtimeFlag |=
|
|
304
|
+
if (suspenses.length > 0 && !(runtimeFlag & F_SUSPENSE)) {
|
|
305
|
+
runtimeFlag |= F_SUSPENSE;
|
|
317
306
|
js += SUSPENSE_JS;
|
|
318
307
|
}
|
|
319
|
-
if (rc.flags.runtime &
|
|
320
|
-
runtimeFlag |=
|
|
308
|
+
if (rc.flags.runtime & F_LAZY && !(runtimeFlag & F_LAZY)) {
|
|
309
|
+
runtimeFlag |= F_LAZY;
|
|
321
310
|
js += LAZY_JS;
|
|
322
311
|
}
|
|
323
|
-
if (rc.flags.runtime &
|
|
324
|
-
runtimeFlag |=
|
|
312
|
+
if (rc.flags.runtime & F_ROUTER && !(runtimeFlag & F_ROUTER)) {
|
|
313
|
+
runtimeFlag |= F_ROUTER;
|
|
325
314
|
js += ROUTER_JS;
|
|
326
315
|
}
|
|
327
316
|
if (js.length > 0) {
|
|
328
317
|
js = "(()=>{" + js + "})();/* --- */window.$runtimeFlag=" + runtimeFlag + ";";
|
|
329
318
|
}
|
|
330
|
-
if (runtimeFlag &
|
|
319
|
+
if (runtimeFlag & F_LAZY || runtimeFlag & F_ROUTER) {
|
|
331
320
|
js += "window.$scopeSeq=" + rc.flags.scope + ";";
|
|
332
321
|
}
|
|
333
322
|
if (rc.mfs.size > 0) {
|
|
@@ -436,8 +425,27 @@ async function renderNode(rc, node, stripSlotProp) {
|
|
|
436
425
|
}
|
|
437
426
|
// `<toggle>` element
|
|
438
427
|
case "toggle": {
|
|
439
|
-
|
|
428
|
+
let { show, hidden, children } = props;
|
|
440
429
|
if (children !== void 0) {
|
|
430
|
+
if (show === void 0 && hidden !== void 0) {
|
|
431
|
+
if (isSignal(hidden)) {
|
|
432
|
+
let { scope, key, value } = hidden[$signal];
|
|
433
|
+
if (typeof key === "string") {
|
|
434
|
+
key = {
|
|
435
|
+
compute: "()=>!this[" + JSON.stringify(key) + "]",
|
|
436
|
+
deps: /* @__PURE__ */ new Set([scope + ":" + key])
|
|
437
|
+
};
|
|
438
|
+
} else {
|
|
439
|
+
key = {
|
|
440
|
+
compute: "()=>!(" + key.compute + ")()",
|
|
441
|
+
deps: key.deps
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
show = Signal(scope, key, !value);
|
|
445
|
+
} else {
|
|
446
|
+
show = !hidden;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
441
449
|
if (isSignal(show)) {
|
|
442
450
|
const { scope, key, value } = show[$signal];
|
|
443
451
|
let buf = '<m-signal mode="toggle" scope="' + scope + '" ';
|
|
@@ -547,7 +555,7 @@ async function renderNode(rc, node, stripSlotProp) {
|
|
|
547
555
|
buf += "<m-group>" + attrModifiers + "</m-group>";
|
|
548
556
|
}
|
|
549
557
|
write(buf);
|
|
550
|
-
rc.flags.runtime |=
|
|
558
|
+
rc.flags.runtime |= F_LAZY;
|
|
551
559
|
break;
|
|
552
560
|
}
|
|
553
561
|
// `<router>` element
|
|
@@ -574,7 +582,7 @@ async function renderNode(rc, node, stripSlotProp) {
|
|
|
574
582
|
}
|
|
575
583
|
buf += "</m-router>";
|
|
576
584
|
write(buf);
|
|
577
|
-
rc.flags.runtime |=
|
|
585
|
+
rc.flags.runtime |= F_ROUTER;
|
|
578
586
|
break;
|
|
579
587
|
}
|
|
580
588
|
default: {
|
|
@@ -647,9 +655,9 @@ function renderAttr(rc, attrName, attrValue, stripSlotProp) {
|
|
|
647
655
|
}
|
|
648
656
|
if (signal) {
|
|
649
657
|
if (attrName === "class") {
|
|
650
|
-
rc.flags.runtime |=
|
|
658
|
+
rc.flags.runtime |= F_CX;
|
|
651
659
|
} else if (attrName === "style") {
|
|
652
|
-
rc.flags.runtime |=
|
|
660
|
+
rc.flags.runtime |= F_STYLE;
|
|
653
661
|
}
|
|
654
662
|
signalValue = signal;
|
|
655
663
|
attrValue = signal[$signal].value;
|
|
@@ -881,6 +889,7 @@ function createSignals(scopeId, appSignals, context = new NullProtoObj(), reques
|
|
|
881
889
|
case "refs":
|
|
882
890
|
return refs;
|
|
883
891
|
case "computed":
|
|
892
|
+
case "$":
|
|
884
893
|
return computed;
|
|
885
894
|
case "effect":
|
|
886
895
|
return (effect) => {
|
|
@@ -937,7 +946,7 @@ function computedProps({ fcCtx }, props) {
|
|
|
937
946
|
});
|
|
938
947
|
if (patches.length > 0) {
|
|
939
948
|
const { scopeId } = fcCtx;
|
|
940
|
-
const compute = "()=>$
|
|
949
|
+
const compute = "()=>$patch(" + JSON.stringify(staticProps) + ",[" + patches.join("],[") + "])";
|
|
941
950
|
return Signal(scopeId, { compute, deps }, staticProps);
|
|
942
951
|
}
|
|
943
952
|
}
|
|
@@ -977,6 +986,9 @@ var jsx = (tag, props = new NullProtoObj(), key) => {
|
|
|
977
986
|
props.key = key;
|
|
978
987
|
}
|
|
979
988
|
if (tag === "html") {
|
|
989
|
+
if (props.request === $peek) {
|
|
990
|
+
return props;
|
|
991
|
+
}
|
|
980
992
|
const renderOptions = new NullProtoObj();
|
|
981
993
|
const optionsKeys = /* @__PURE__ */ new Set(["app", "context", "components", "routes", "request", "status", "headers", "htmx"]);
|
|
982
994
|
for (const [key2, value] of Object.entries(props)) {
|
|
@@ -1011,7 +1023,12 @@ Object.assign(globalThis, {
|
|
|
1011
1023
|
});
|
|
1012
1024
|
export {
|
|
1013
1025
|
Fragment,
|
|
1026
|
+
JSX,
|
|
1027
|
+
html as css,
|
|
1028
|
+
html,
|
|
1029
|
+
html as js,
|
|
1014
1030
|
jsx,
|
|
1015
1031
|
jsx as jsxDEV,
|
|
1032
|
+
jsxEscape,
|
|
1016
1033
|
jsx as jsxs
|
|
1017
1034
|
};
|
package/package.json
CHANGED
package/types/html.d.ts
CHANGED
|
@@ -374,6 +374,7 @@ export namespace HTML {
|
|
|
374
374
|
imageSrcSet?: string;
|
|
375
375
|
imageSizes?: string;
|
|
376
376
|
referrerPolicy?: ReferrerPolicy;
|
|
377
|
+
rel?: string;
|
|
377
378
|
sizes?: string;
|
|
378
379
|
type?: string;
|
|
379
380
|
}
|
|
@@ -425,6 +426,7 @@ export namespace HTML {
|
|
|
425
426
|
charSet?: string;
|
|
426
427
|
httpEquiv?: string;
|
|
427
428
|
name?: string;
|
|
429
|
+
content?: string;
|
|
428
430
|
media?: string;
|
|
429
431
|
}
|
|
430
432
|
|
package/types/index.d.ts
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import type { FC } from "./jsx.d.ts";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* `buildRoute` creates a routing map for bun server.
|
|
5
|
+
*/
|
|
6
|
+
export function buildRoute(
|
|
7
|
+
handler: (req: Request) => Response,
|
|
8
|
+
): Record<string, (req: Request) => Response>;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* `monoRoutes` creates a routing map for bun server.
|
|
12
|
+
* @deprecated Use `buildRoute` instead.
|
|
13
|
+
*/
|
|
3
14
|
export function monoRoutes(
|
|
4
15
|
routes: Record<string, FC<any> | Promise<{ default: FC<any> }>>,
|
|
5
16
|
handler: (req: Request) => Response,
|
package/types/jsx-runtime.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { FC, VNode } from "./jsx.d.ts";
|
|
2
2
|
|
|
3
|
-
export
|
|
3
|
+
export const html: JSX.Raw;
|
|
4
|
+
export const JSX: typeof globalThis.JSX;
|
|
5
|
+
export const Fragment: (props: Record<string, unknown>) => VNode;
|
|
6
|
+
export const jsx: (tag: string | FC, props: Record<string, unknown>, key?: string | number) => VNode;
|
|
4
7
|
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
export { jsx as jsxDEV, jsx as jsxs };
|
|
8
|
+
export { html as css, html as js, jsx as jsxDEV, jsx as jsxs };
|
package/types/mono.d.ts
CHANGED
|
@@ -91,20 +91,27 @@ export interface AsyncComponentAttributes {
|
|
|
91
91
|
|
|
92
92
|
export interface Elements {
|
|
93
93
|
/**
|
|
94
|
-
* The `toggle` element is a
|
|
94
|
+
* The `toggle` element is a built-in element that toggles the visibility of its children.
|
|
95
95
|
*/
|
|
96
96
|
toggle: BaseAttributes & {
|
|
97
|
-
show?:
|
|
97
|
+
show?: any;
|
|
98
|
+
hidden?: any;
|
|
98
99
|
};
|
|
99
100
|
/**
|
|
100
|
-
* The `switch` element is a
|
|
101
|
+
* The `switch` element is a built-in element that chooses one of its children based on the `slot` attribute to display.
|
|
101
102
|
* It is similar to a switch statement in programming languages.
|
|
102
103
|
*/
|
|
103
104
|
switch: BaseAttributes & {
|
|
104
105
|
value?: string | number | boolean | null;
|
|
105
106
|
};
|
|
106
107
|
/**
|
|
107
|
-
* The `
|
|
108
|
+
* The `for` element is a built-in element for list rendering with signals.
|
|
109
|
+
*/
|
|
110
|
+
for: BaseAttributes & {
|
|
111
|
+
items?: unknown[];
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* The `component` element is a built-in element that is used to load components lazily,
|
|
108
115
|
* which can improve performance by reducing the initial load time of the application.
|
|
109
116
|
*/
|
|
110
117
|
component: BaseAttributes & AsyncComponentAttributes & {
|
|
@@ -112,7 +119,7 @@ export interface Elements {
|
|
|
112
119
|
props?: Record<string, unknown>;
|
|
113
120
|
};
|
|
114
121
|
/**
|
|
115
|
-
* The `router` element is a
|
|
122
|
+
* The `router` element is a built-in element that implements client-side routing.
|
|
116
123
|
*/
|
|
117
124
|
router: BaseAttributes & AsyncComponentAttributes & {};
|
|
118
125
|
}
|
|
@@ -130,17 +137,17 @@ declare global {
|
|
|
130
137
|
*/
|
|
131
138
|
type FC<Signals = {}, AppSignals = {}, Context = {}> = {
|
|
132
139
|
/**
|
|
133
|
-
*
|
|
140
|
+
* The global signals shared across the application.
|
|
134
141
|
*/
|
|
135
142
|
readonly app: AppSignals;
|
|
136
143
|
/**
|
|
137
|
-
*
|
|
144
|
+
* The rendering context.
|
|
138
145
|
*
|
|
139
146
|
* **⚠ This is a server-side only API.**
|
|
140
147
|
*/
|
|
141
148
|
readonly context: Context;
|
|
142
149
|
/**
|
|
143
|
-
*
|
|
150
|
+
* The `request` object contains the current request information.
|
|
144
151
|
*
|
|
145
152
|
* **⚠ This is a server-side only API.**
|
|
146
153
|
*/
|
|
@@ -153,10 +160,14 @@ declare global {
|
|
|
153
160
|
* The `computed` method is used to create a computed signal.
|
|
154
161
|
*/
|
|
155
162
|
readonly computed: <T = unknown>(fn: () => T) => T;
|
|
163
|
+
/**
|
|
164
|
+
* `this.$(fn)` is just a shortcut for `this.computed(fn)`.
|
|
165
|
+
*/
|
|
166
|
+
readonly $: <T = unknown>(fn: () => T) => T;
|
|
156
167
|
/**
|
|
157
168
|
* The `effect` method is used to create a side effect.
|
|
158
169
|
* **The effect function is only called on client side.**
|
|
159
170
|
*/
|
|
160
171
|
readonly effect: (fn: () => void | (() => void)) => void;
|
|
161
|
-
} & Omit<Signals, "app" | "context" | "request" | "computed" | "effect">;
|
|
172
|
+
} & Omit<Signals, "app" | "context" | "request" | "refs" | "computed" | "$" | "effect">;
|
|
162
173
|
}
|
package/types/render.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ type HtmxExts = {
|
|
|
11
11
|
| boolean;
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
-
export type
|
|
14
|
+
export type MaybeModule<T> = T | Promise<{ default: T }>;
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* Render options for the `render` function.
|
|
@@ -28,11 +28,11 @@ export interface RenderOptions extends Partial<HtmxExts> {
|
|
|
28
28
|
/**
|
|
29
29
|
* Components to be rendered by the `<lazy>` element.
|
|
30
30
|
*/
|
|
31
|
-
components?: Record<string,
|
|
31
|
+
components?: Record<string, MaybeModule<FC<any>>>;
|
|
32
32
|
/**
|
|
33
33
|
* Routes to be used by the `<router>` element.
|
|
34
34
|
*/
|
|
35
|
-
routes?: Record<string,
|
|
35
|
+
routes?: Record<string, MaybeModule<FC<any>>>;
|
|
36
36
|
/**
|
|
37
37
|
* Current `Request` object to be passed to components.
|
|
38
38
|
*/
|