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 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 (10KB gzipped), zero dependencies
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
- You can use the `<toggle>` element to control when to render a component:
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 components={{ Foo }}>
459
- <Lazy />
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 y;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""},y=(()=>{function e(){}return e.prototype=Object.freeze(Object.create(null)),e})();window.$applyStyle=b;}`;
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.3";
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 componentHeader = reqHeaders?.get("x-component");
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 = componentHeader ? components?.[componentHeader] : null;
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 (componentHeader) {
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
- let propValue = props[p];
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.4",
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": "./bin/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
@@ -162,6 +162,10 @@ export interface Elements {
162
162
  * The name of the component to render.
163
163
  */
164
164
  name?: string;
165
+ /**
166
+ * The component to render.
167
+ */
168
+ is?: import("./jsx.d.ts").FC<any>;
165
169
  /**
166
170
  * The props of the component to render.
167
171
  */