mono-jsx 0.7.4 → 0.7.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 +31 -17
- package/jsx-runtime.mjs +31 -17
- package/package.json +3 -3
- package/types/mono.d.ts +4 -0
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
mono-jsx is a JSX runtime that renders the `<html>` element to a `Response` object.
|
|
6
6
|
|
|
7
7
|
- 🚀 No build step needed
|
|
8
|
-
- 🦋 Lightweight (
|
|
8
|
+
- 🦋 Lightweight (12KB gzipped), zero dependencies
|
|
9
9
|
- 🚦 Signals as reactive primitives
|
|
10
10
|
- ⚡️ Use web components, no virtual DOM
|
|
11
11
|
- 💡 Complete Web API TypeScript definitions
|
|
@@ -428,6 +428,8 @@ export default {
|
|
|
428
428
|
|
|
429
429
|
mono-jsx renders HTML on the server side and sends no hydration JavaScript to the client. To render a component dynamically on the client, you can use the `<component>` element to ask the server to render a component:
|
|
430
430
|
|
|
431
|
+
To render a component by name, you can use the `<component>` element with the `name` prop, and ensure the component is registered in the `components` prop of root `<html>` element.
|
|
432
|
+
|
|
431
433
|
```tsx
|
|
432
434
|
export default {
|
|
433
435
|
fetch: (req) => (
|
|
@@ -438,31 +440,19 @@ export default {
|
|
|
438
440
|
}
|
|
439
441
|
```
|
|
440
442
|
|
|
441
|
-
|
|
443
|
+
Or you can use the `<component>` element with the `is` prop to render a component by function reference without registering the component in the `components` prop of root `<html>` element.
|
|
442
444
|
|
|
443
445
|
```tsx
|
|
444
|
-
async function Lazy(this: FC<{ show: boolean }>, props: { url: string }) {
|
|
445
|
-
this.show = false;
|
|
446
|
-
return (
|
|
447
|
-
<div>
|
|
448
|
-
<toggle show={this.show}>
|
|
449
|
-
<component name="Foo" props={{ /* props for the component */ }} placeholder={<p>Loading...</p>} />
|
|
450
|
-
</toggle>
|
|
451
|
-
<button onClick={() => this.show = true }>Load `Foo` Component</button>
|
|
452
|
-
</div>
|
|
453
|
-
)
|
|
454
|
-
}
|
|
455
|
-
|
|
456
446
|
export default {
|
|
457
447
|
fetch: (req) => (
|
|
458
|
-
<html
|
|
459
|
-
<
|
|
448
|
+
<html>
|
|
449
|
+
<component is={Foo} props={{ /* props for the component */ }} placeholder={<p>Loading...</p>} />
|
|
460
450
|
</html>
|
|
461
451
|
)
|
|
462
452
|
}
|
|
463
453
|
```
|
|
464
454
|
|
|
465
|
-
You can also use signals for `name` or `props` attributes of a component. Changing the signal value will trigger the component to re-render with the new name or props:
|
|
455
|
+
You can also use [signals](#using-signals) for `name` or `props` attributes of a component. Changing the signal value will trigger the component to re-render with the new name or props:
|
|
466
456
|
|
|
467
457
|
```tsx
|
|
468
458
|
import { Profile, Projects, Settings } from "./pages.tsx"
|
|
@@ -493,6 +483,30 @@ export default {
|
|
|
493
483
|
}
|
|
494
484
|
```
|
|
495
485
|
|
|
486
|
+
You can use the `<toggle>` element to control when to render a component:
|
|
487
|
+
|
|
488
|
+
```tsx
|
|
489
|
+
async function Lazy(this: FC<{ show: boolean }>, props: { url: string }) {
|
|
490
|
+
this.show = false;
|
|
491
|
+
return (
|
|
492
|
+
<div>
|
|
493
|
+
<toggle show={this.show}>
|
|
494
|
+
<component name="Foo" props={{ /* props for the component */ }} placeholder={<p>Loading...</p>} />
|
|
495
|
+
</toggle>
|
|
496
|
+
<button onClick={() => this.show = true }>Load `Foo` Component</button>
|
|
497
|
+
</div>
|
|
498
|
+
)
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
export default {
|
|
502
|
+
fetch: (req) => (
|
|
503
|
+
<html components={{ Foo }}>
|
|
504
|
+
<Lazy />
|
|
505
|
+
</html>
|
|
506
|
+
)
|
|
507
|
+
}
|
|
508
|
+
```
|
|
509
|
+
|
|
496
510
|
## Using Signals
|
|
497
511
|
|
|
498
512
|
mono-jsx uses signals for updating the view when a signal changes. Signals are similar to React's state, but they are more lightweight and efficient. You can use signals to manage state in your components.
|
package/jsx-runtime.mjs
CHANGED
|
@@ -12,7 +12,7 @@ var ROUTER = 512;
|
|
|
12
12
|
var FORM = 1024;
|
|
13
13
|
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)};}`;
|
|
14
14
|
var CX_JS = `{var n=e=>typeof e=="string"?e:typeof e=="object"&&e!==null?Array.isArray(e)?e.map(n).filter(Boolean).join(" "):Object.entries(e).filter(([,t])=>!!t).map(([t])=>t).join(" "):"";window.$cx=n;}`;
|
|
15
|
-
var STYLE_JS = `{var l=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;var u=e=>typeof e=="string",p=e=>typeof e=="object"&&e!==null,f=e=>e.replace(/[a-z][A-Z]/g,o=>o.charAt(0)+"-"+o.charAt(1).toLowerCase());var g=e=>{let o=[],n=[],r=new
|
|
15
|
+
var STYLE_JS = `{var l=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;var u=e=>typeof e=="string",p=e=>typeof e=="object"&&e!==null,f=e=>e.replace(/[a-z][A-Z]/g,o=>o.charAt(0)+"-"+o.charAt(1).toLowerCase());var g=e=>{let o=[],n=[],r=new h;for(let[t,s]of Object.entries(e))switch(t.charCodeAt(0)){case 58:n.push(t.startsWith("::view-")?"":null,t+"{"+c(s)+"}");break;case 64:t.startsWith("@keyframes ")||t.startsWith("@view-")?p(s)&&n.push(t+"{"+Object.entries(s).map(([i,a])=>i+"{"+c(a)+"}").join("")+"}"):n.push(t+"{",null,"{"+c(s)+"}}");break;case 38:n.push(null,t.slice(1)+"{"+c(s)+"}");break;default:o.push([t,s])}return o.length>0&&(r.inline=c(o)),n.length>0&&(r.css=n),r},b=(e,o)=>{let{inline:n,css:r}=g(o);if(r){let t="data-css-",s="["+t+(Date.now()+Math.random()).toString(36).replace(".","")+"]";document.head.appendChild(document.createElement("style")).textContent=(n?s+"{"+n+"}":"")+r.map(i=>i===null?s:i).join(""),e.getAttributeNames().forEach(i=>i.startsWith(t)&&e.removeAttribute(i)),e.setAttribute(s.slice(1,-1),"")}else n&&e.setAttribute("style",n)},c=e=>{if(typeof e=="object"&&e!==null){let o="";for(let[n,r]of Array.isArray(e)?e:Object.entries(e))if(u(r)||typeof r=="number"){let t=f(n),s=typeof r=="number"?l.test(n)?""+r:r+"px":""+r;o+=(o?";":"")+t+":"+(t==="content"?JSON.stringify(s):s)}return o}return""},h=(()=>{function e(){}return e.prototype=Object.freeze(Object.create(null)),e})();window.$applyStyle=b;}`;
|
|
16
16
|
var RENDER_ATTR_JS = `{var s=(l,n,r)=>{let e=l.parentElement;return e.tagName==="M-GROUP"&&(e=e.previousElementSibling),()=>{let t=r();n==="value"?e.value=String(t):n==="checked"?e.checked=!!t:typeof t=="boolean"?e.toggleAttribute(n,t):t==null?e.removeAttribute(n):typeof t=="object"?n==="class"?e.setAttribute(n,$cx(t)):n==="style"?$applyStyle(e,t):e.setAttribute(n,JSON.stringify(t)):e.setAttribute(n,String(t))}};window.$renderAttr=s;}`;
|
|
17
17
|
var RENDER_TOGGLE_JS = `{var i=(e,s)=>{let t,l=()=>e.replaceChildren(...s()?t:[]);return()=>{if(!t){let n=e.firstElementChild;n&&n.tagName==="TEMPLATE"&&n.hasAttribute("m-slot")?t=[...n.content.childNodes]:t=[...e.childNodes]}e.hasAttribute("vt")&&document.startViewTransition?document.startViewTransition(l):l()}};window.$renderToggle=i;}`;
|
|
18
18
|
var RENDER_SWITCH_JS = `{var a=(l,u)=>{let s,r=l.getAttribute("value"),t,i,o=e=>t.get(e)??t.set(e,[]).get(e),d=()=>l.replaceChildren(...t.has(s)?t.get(s):i);return()=>{if(!t){t=new Map,i=[];for(let e of l.childNodes)if(e.nodeType===1&&e.tagName==="TEMPLATE"&&e.hasAttribute("m-slot")){for(let n of e.content.childNodes)n.nodeType===1&&n.hasAttribute("slot")?o(n.getAttribute("slot")).push(n):i.push(n);e.remove()}else r?o(r).push(e):i.push(e)}s=""+u(),l.hasAttribute("vt")&&document.startViewTransition?document.startViewTransition(d):d()}};window.$renderSwitch=a;}`;
|
|
@@ -28,6 +28,19 @@ var regexpHtmlSafe = /["'&<>]/;
|
|
|
28
28
|
var isString = (v) => typeof v === "string";
|
|
29
29
|
var isObject = (v) => typeof v === "object" && v !== null;
|
|
30
30
|
var toHyphenCase = (k) => k.replace(/[a-z][A-Z]/g, (m) => m.charAt(0) + "-" + m.charAt(1).toLowerCase());
|
|
31
|
+
var IdGen = class extends Map {
|
|
32
|
+
#seq = 0;
|
|
33
|
+
gen(v) {
|
|
34
|
+
return this.get(v) ?? this.set(v, this.#seq++).get(v);
|
|
35
|
+
}
|
|
36
|
+
getById(id) {
|
|
37
|
+
for (const [v, i] of this.entries()) {
|
|
38
|
+
if (i === id) {
|
|
39
|
+
return v;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
};
|
|
31
44
|
var cx = (className) => {
|
|
32
45
|
if (typeof className === "string") {
|
|
33
46
|
return className;
|
|
@@ -143,15 +156,16 @@ var $signal = Symbol.for("mono.signal");
|
|
|
143
156
|
var $vnode = Symbol.for("jsx.vnode");
|
|
144
157
|
|
|
145
158
|
// version.ts
|
|
146
|
-
var VERSION = "0.7.
|
|
159
|
+
var VERSION = "0.7.4";
|
|
147
160
|
|
|
148
161
|
// render.ts
|
|
149
162
|
var cdn = "https://raw.esm.sh";
|
|
150
|
-
var subtle = crypto.subtle;
|
|
151
163
|
var encoder = new TextEncoder();
|
|
152
164
|
var customElements = /* @__PURE__ */ new Map();
|
|
153
165
|
var voidTags = new Set("area,base,br,col,embed,hr,img,input,keygen,link,meta,param,source,track,wbr".split(","));
|
|
154
166
|
var cache = /* @__PURE__ */ new Map();
|
|
167
|
+
var componentsMap = new IdGen();
|
|
168
|
+
var subtle = crypto.subtle;
|
|
155
169
|
var stringify = JSON.stringify;
|
|
156
170
|
var isVNode = (v) => Array.isArray(v) && v.length === 3 && v[2] === $vnode;
|
|
157
171
|
var isSignal = (v) => isObject(v) && !!v[$signal];
|
|
@@ -165,12 +179,6 @@ var Ref = class {
|
|
|
165
179
|
this.name = name;
|
|
166
180
|
}
|
|
167
181
|
};
|
|
168
|
-
var IdGen = class extends Map {
|
|
169
|
-
#seq = 0;
|
|
170
|
-
gen(v) {
|
|
171
|
-
return this.get(v) ?? this.set(v, this.#seq++).get(v);
|
|
172
|
-
}
|
|
173
|
-
};
|
|
174
182
|
var IdGenManager = class {
|
|
175
183
|
#scopes = /* @__PURE__ */ new Map();
|
|
176
184
|
size = 0;
|
|
@@ -209,11 +217,11 @@ function renderHtml(node, options) {
|
|
|
209
217
|
const request = options.request;
|
|
210
218
|
const headers = new Headers();
|
|
211
219
|
const reqHeaders = request?.headers;
|
|
212
|
-
const
|
|
220
|
+
const compHeader = reqHeaders?.get("x-component");
|
|
221
|
+
let status = options.status;
|
|
213
222
|
let routeFC = request ? Reflect.get(request, "x-route") : void 0;
|
|
214
223
|
let routeForm;
|
|
215
|
-
let component =
|
|
216
|
-
let status = options.status;
|
|
224
|
+
let component = compHeader ? compHeader.startsWith("@comp_") ? componentsMap.getById(Number(compHeader.slice(6))) : components?.[compHeader] : null;
|
|
217
225
|
if (request) {
|
|
218
226
|
request.URL = new URL(request.url);
|
|
219
227
|
}
|
|
@@ -295,7 +303,7 @@ function renderHtml(node, options) {
|
|
|
295
303
|
}),
|
|
296
304
|
{ headers }
|
|
297
305
|
);
|
|
298
|
-
} else if (
|
|
306
|
+
} else if (compHeader) {
|
|
299
307
|
return new Response("Component not found: " + component, { status: 404 });
|
|
300
308
|
}
|
|
301
309
|
headers.set("content-type", "text/html; charset=utf-8");
|
|
@@ -633,19 +641,25 @@ async function renderNode(rc, node, stripSlotProp) {
|
|
|
633
641
|
}
|
|
634
642
|
// `<component>` element
|
|
635
643
|
case "component": {
|
|
636
|
-
let { placeholder, viewTransition } = props;
|
|
644
|
+
let { placeholder, viewTransition, is } = props;
|
|
637
645
|
let attrs = "";
|
|
638
646
|
let attrModifiers = "";
|
|
639
647
|
for (const p of ["name", "props", "ref"]) {
|
|
640
|
-
|
|
641
|
-
let [attr, , attrSignal] = renderAttr(rc, p, propValue);
|
|
648
|
+
const [attr, , attrSignal] = renderAttr(rc, p, props[p]);
|
|
642
649
|
if (attrSignal) {
|
|
643
650
|
attrModifiers += renderSignal(rc, attrSignal, [p]);
|
|
644
651
|
rc.flags.runtime |= RENDER_ATTR;
|
|
645
|
-
propValue = attrSignal[$signal].value;
|
|
646
652
|
}
|
|
647
653
|
attrs += attr;
|
|
648
654
|
}
|
|
655
|
+
if (!props.name && typeof is === "function" && is.name) {
|
|
656
|
+
const c = is.name.charCodeAt(0);
|
|
657
|
+
if (c >= /*A*/
|
|
658
|
+
65 && c <= /*Z*/
|
|
659
|
+
90) {
|
|
660
|
+
attrs += ' name="@comp_' + componentsMap.gen(is) + '"';
|
|
661
|
+
}
|
|
662
|
+
}
|
|
649
663
|
attrs += renderViewTransitionAttr(viewTransition);
|
|
650
664
|
let buf = "<m-component" + attrs + ">";
|
|
651
665
|
if (placeholder) {
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mono-jsx",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.5",
|
|
4
4
|
"description": "`<html>` as a `Response`.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./index.mjs",
|
|
7
7
|
"types": "./types/index.d.ts",
|
|
8
8
|
"bin": {
|
|
9
|
-
"mono-jsx": "
|
|
9
|
+
"mono-jsx": "bin/mono-jsx"
|
|
10
10
|
},
|
|
11
11
|
"exports": {
|
|
12
12
|
".": {
|
|
@@ -35,6 +35,6 @@
|
|
|
35
35
|
"license": "MIT",
|
|
36
36
|
"repository": {
|
|
37
37
|
"type": "git",
|
|
38
|
-
"url": "https://github.com/ije/mono-jsx"
|
|
38
|
+
"url": "git+https://github.com/ije/mono-jsx.git"
|
|
39
39
|
}
|
|
40
40
|
}
|
package/types/mono.d.ts
CHANGED