mono-jsx 0.3.3 → 0.4.0
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 +52 -13
- package/jsx-runtime.mjs +66 -32
- package/package.json +1 -1
- package/types/css.d.ts +6 -3
- package/types/mono.d.ts +5 -5
package/README.md
CHANGED
|
@@ -281,6 +281,53 @@ function App() {
|
|
|
281
281
|
}
|
|
282
282
|
```
|
|
283
283
|
|
|
284
|
+
## Async Components
|
|
285
|
+
|
|
286
|
+
mono-jsx supports async components that return a `Promise` or an async function. With [streaming rendering](#streaming-rendering), async components are rendered asynchronously, allowing you to fetch data or perform other async operations before rendering the component.
|
|
287
|
+
|
|
288
|
+
```tsx
|
|
289
|
+
async function Loader(props: { url: string }) {
|
|
290
|
+
const data = await fetch(url).then((res) => res.json());
|
|
291
|
+
return <JsonViewer data={data} />;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
default {
|
|
295
|
+
fetch: (req) => (
|
|
296
|
+
<html>
|
|
297
|
+
<Loader url="https://api.example.com/data" placeholder={<p>Loading...</p>} />
|
|
298
|
+
</html>
|
|
299
|
+
),
|
|
300
|
+
};
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
You can also use async generators to yield multiple elements over time. This is useful for streaming rendering of LLM tokens:
|
|
304
|
+
|
|
305
|
+
```jsx
|
|
306
|
+
async function* Chat(props: { prompt: string }) {
|
|
307
|
+
const stream = await openai.chat.completions.create({
|
|
308
|
+
model: "gpt-4",
|
|
309
|
+
messages: [{ role: "user", content: prompt }],
|
|
310
|
+
stream: true,
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
for await (const event of stream) {
|
|
314
|
+
const text = event.choices[0]?.delta.content;
|
|
315
|
+
if (text) {
|
|
316
|
+
yield <span>{text}</span>;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
default {
|
|
323
|
+
fetch: (req) => (
|
|
324
|
+
<html>
|
|
325
|
+
<Chat prompt="Tell me a story" placeholder={<span style="color:grey">●</span>} />
|
|
326
|
+
</html>
|
|
327
|
+
),
|
|
328
|
+
};
|
|
329
|
+
```
|
|
330
|
+
|
|
284
331
|
## Reactive
|
|
285
332
|
|
|
286
333
|
mono-jsx provides a minimal state runtime for updating the view based on client-side state changes.
|
|
@@ -575,9 +622,7 @@ async function Sleep({ ms }) {
|
|
|
575
622
|
export default {
|
|
576
623
|
fetch: (req) => (
|
|
577
624
|
<html>
|
|
578
|
-
<
|
|
579
|
-
|
|
580
|
-
<Sleep ms={1000} placeholder={<p>Sleeping...</p>}>
|
|
625
|
+
<Sleep ms={1000} placeholder={<p>Loading...</p>}>
|
|
581
626
|
<p>After 1 second</p>
|
|
582
627
|
</Sleep>
|
|
583
628
|
</html>
|
|
@@ -588,16 +633,9 @@ export default {
|
|
|
588
633
|
You can set the `rendering` attribute to `"eager"` to force synchronous rendering (the `placeholder` will be ignored):
|
|
589
634
|
|
|
590
635
|
```jsx
|
|
591
|
-
async function Sleep({ ms }) {
|
|
592
|
-
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
593
|
-
return <slot />;
|
|
594
|
-
}
|
|
595
|
-
|
|
596
636
|
export default {
|
|
597
637
|
fetch: (req) => (
|
|
598
638
|
<html>
|
|
599
|
-
<h1>Welcome to mono-jsx!</h1>
|
|
600
|
-
|
|
601
639
|
<Sleep ms={1000} rendering="eager">
|
|
602
640
|
<p>After 1 second</p>
|
|
603
641
|
</Sleep>
|
|
@@ -606,7 +644,7 @@ export default {
|
|
|
606
644
|
};
|
|
607
645
|
```
|
|
608
646
|
|
|
609
|
-
You can add the `catch` attribute to handle errors in async
|
|
647
|
+
You can add the `catch` attribute to handle errors in the async component. The `catch` attribute should be a function that returns a JSX element:
|
|
610
648
|
|
|
611
649
|
```jsx
|
|
612
650
|
async function Hello() {
|
|
@@ -617,7 +655,7 @@ async function Hello() {
|
|
|
617
655
|
export default {
|
|
618
656
|
fetch: (req) => (
|
|
619
657
|
<html>
|
|
620
|
-
<Hello catch={err => <p>{err.
|
|
658
|
+
<Hello catch={err => <p>{err.message}</p>} />
|
|
621
659
|
</html>
|
|
622
660
|
),
|
|
623
661
|
};
|
|
@@ -625,7 +663,7 @@ export default {
|
|
|
625
663
|
|
|
626
664
|
## Customizing html Response
|
|
627
665
|
|
|
628
|
-
|
|
666
|
+
You can add `status` or `headers` attributes to the root `<html>` element to customize the http response:
|
|
629
667
|
|
|
630
668
|
```jsx
|
|
631
669
|
export default {
|
|
@@ -635,6 +673,7 @@ export default {
|
|
|
635
673
|
headers={{
|
|
636
674
|
cacheControl: "public, max-age=0, must-revalidate",
|
|
637
675
|
setCookie: "name=value",
|
|
676
|
+
"x-foo": "bar",
|
|
638
677
|
}}
|
|
639
678
|
>
|
|
640
679
|
<h1>Page Not Found</h1>
|
package/jsx-runtime.mjs
CHANGED
|
@@ -7,7 +7,7 @@ var $computed = Symbol.for("mono.computed");
|
|
|
7
7
|
|
|
8
8
|
// runtime/index.ts
|
|
9
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
|
|
10
|
+
var SUSPENSE_JS = `const s=new Map,i=(e,t)=>e.hasAttribute(t),l=e=>e.getAttribute("chunk-id"),c=(e,t)=>customElements.define(e,class extends HTMLElement{connectedCallback(){t(this)}});c("m-portal",e=>{s.set(l(e),e)}),c("m-chunk",e=>{setTimeout(()=>{const t=e.firstChild?.content.childNodes,o=l(e),n=s.get(o);n&&(i(e,"next")?n.before(...t):(s.delete(o),i(e,"done")?n.remove():n.replaceWith(...t)),e.remove())})});`;
|
|
11
11
|
var UTILS_JS = {
|
|
12
12
|
/** cx.js (225 bytes) */
|
|
13
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;`,
|
|
@@ -357,46 +357,80 @@ async function renderNode(rc, node, stripSlotProp) {
|
|
|
357
357
|
break;
|
|
358
358
|
}
|
|
359
359
|
if (typeof tag === "function") {
|
|
360
|
+
const { children } = props;
|
|
360
361
|
const fcIndex = ++rc.status.fcIndex;
|
|
361
|
-
const { rendering, placeholder, catch: catchFC, ...fcProps } = props ?? /* @__PURE__ */ Object.create(null);
|
|
362
362
|
try {
|
|
363
|
-
const v = tag.call(createThis(fcIndex, rc.appState, rc.context, rc.request), fcProps);
|
|
364
|
-
const { children } = fcProps;
|
|
365
363
|
const fcSlots = children !== void 0 ? Array.isArray(children) ? isVNode(children) ? [children] : children : [children] : void 0;
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
364
|
+
const v = tag.call(createThis(fcIndex, rc.appState, rc.context, rc.request), props);
|
|
365
|
+
if (isObject(v) && !isVNode(v)) {
|
|
366
|
+
if (v instanceof Promise) {
|
|
367
|
+
if ((props.rendering ?? tag.rendering) === "eager") {
|
|
368
|
+
await renderNode({ ...rc, fcIndex, fcSlots }, await v);
|
|
369
|
+
} else {
|
|
370
|
+
const chunkIdAttr = 'chunk-id="' + (rc.status.chunkIndex++).toString(36) + '"';
|
|
371
|
+
write("<m-portal " + chunkIdAttr + ">");
|
|
372
|
+
if (props.placeholder) {
|
|
373
|
+
await renderNode({ ...rc, fcIndex }, props.placeholder);
|
|
374
|
+
}
|
|
375
|
+
write("</m-portal>");
|
|
376
|
+
rc.suspenses.push(v.then(async (node2) => {
|
|
377
|
+
let buf = "<m-chunk " + chunkIdAttr + "><template>";
|
|
378
|
+
await renderNode({
|
|
379
|
+
...rc,
|
|
380
|
+
fcIndex,
|
|
381
|
+
fcSlots,
|
|
382
|
+
write: (chunk) => {
|
|
383
|
+
buf += chunk;
|
|
384
|
+
}
|
|
385
|
+
}, node2);
|
|
386
|
+
return buf + "</template></m-chunk>";
|
|
387
|
+
}));
|
|
379
388
|
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
389
|
+
} else if (Symbol.asyncIterator in v) {
|
|
390
|
+
if ((props.rendering ?? tag.rendering) === "eager") {
|
|
391
|
+
for await (const c of v) {
|
|
392
|
+
await renderNode({ ...rc, fcIndex, fcSlots }, c);
|
|
393
|
+
}
|
|
394
|
+
} else {
|
|
395
|
+
const chunkIdAttr = 'chunk-id="' + (rc.status.chunkIndex++).toString(36) + '"';
|
|
396
|
+
write("<m-portal " + chunkIdAttr + ">");
|
|
397
|
+
if (props.placeholder) {
|
|
398
|
+
await renderNode({ ...rc, fcIndex }, props.placeholder);
|
|
399
|
+
}
|
|
400
|
+
write("</m-portal>");
|
|
401
|
+
const iter = () => rc.suspenses.push(
|
|
402
|
+
v.next().then(async ({ done, value }) => {
|
|
403
|
+
let buf = "<m-chunk " + chunkIdAttr;
|
|
404
|
+
if (done) {
|
|
405
|
+
return buf + " done></m-chunk>";
|
|
406
|
+
}
|
|
407
|
+
buf += " next><template>";
|
|
408
|
+
await renderNode({
|
|
409
|
+
...rc,
|
|
410
|
+
fcIndex,
|
|
411
|
+
fcSlots,
|
|
412
|
+
write: (chunk) => {
|
|
413
|
+
buf += chunk;
|
|
414
|
+
}
|
|
415
|
+
}, value);
|
|
416
|
+
iter();
|
|
417
|
+
return buf + "</template></m-chunk>";
|
|
418
|
+
})
|
|
419
|
+
);
|
|
420
|
+
iter();
|
|
421
|
+
}
|
|
422
|
+
} else if (Symbol.iterator in v) {
|
|
423
|
+
for (const node2 of v) {
|
|
424
|
+
await renderNode({ ...rc, fcIndex, fcSlots }, node2);
|
|
386
425
|
}
|
|
387
|
-
} else {
|
|
388
|
-
}
|
|
389
|
-
} else if (isObject(v) && Symbol.iterator in v && !isVNode(v)) {
|
|
390
|
-
for (const node2 of v) {
|
|
391
|
-
await renderNode({ ...rc, fcIndex, fcSlots }, node2);
|
|
392
426
|
}
|
|
393
|
-
} else {
|
|
427
|
+
} else if (v || v === 0) {
|
|
394
428
|
await renderNode({ ...rc, fcIndex, fcSlots }, v);
|
|
395
429
|
}
|
|
396
430
|
} catch (err) {
|
|
397
431
|
if (err instanceof Error) {
|
|
398
|
-
if (
|
|
399
|
-
await renderNode({ ...rc, fcIndex },
|
|
432
|
+
if (props.catch) {
|
|
433
|
+
await renderNode({ ...rc, fcIndex }, props.catch(err));
|
|
400
434
|
} else {
|
|
401
435
|
write('<pre style="color:red;font-size:1rem"><code>' + escapeHTML(err.message) + "</code></pre>");
|
|
402
436
|
console.error(err);
|
|
@@ -672,7 +706,7 @@ function render(node, renderOptions = {}) {
|
|
|
672
706
|
write("<script>/* app.js (generated by mono-jsx) */" + js + "<\/script>");
|
|
673
707
|
}
|
|
674
708
|
if (suspenses.length > 0) {
|
|
675
|
-
await Promise.all(suspenses.splice(0, suspenses.length));
|
|
709
|
+
await Promise.all(suspenses.splice(0, suspenses.length).map((suspense) => suspense.then(write)));
|
|
676
710
|
await finalize();
|
|
677
711
|
}
|
|
678
712
|
};
|
package/package.json
CHANGED
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/mono.d.ts
CHANGED
|
@@ -66,7 +66,7 @@ export interface CSSProperties extends BaseCSSProperties, AtRuleCSSProperties, P
|
|
|
66
66
|
[key: `&${" " | "." | "["}${string}`]: CSSProperties;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
export type ChildType = JSX.Element | 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
72
|
children?: ChildType | ChildType[];
|
|
@@ -76,16 +76,16 @@ export interface BaseAttributes {
|
|
|
76
76
|
|
|
77
77
|
export interface AsyncComponentAttributes {
|
|
78
78
|
/**
|
|
79
|
-
*
|
|
79
|
+
* Try to catch errors in the component.
|
|
80
80
|
*/
|
|
81
81
|
catch?: (err: any) => JSX.Element;
|
|
82
82
|
/**
|
|
83
|
-
*
|
|
83
|
+
* The loading spinner for the async component.
|
|
84
84
|
*/
|
|
85
|
-
placeholder?: JSX.Element
|
|
85
|
+
placeholder?: JSX.Element;
|
|
86
86
|
/**
|
|
87
87
|
* Rendering mode
|
|
88
|
-
* - `eager`: render
|
|
88
|
+
* - `eager`: render async component eagerly
|
|
89
89
|
*/
|
|
90
90
|
rendering?: "eager";
|
|
91
91
|
}
|