ajo 0.1.28 → 0.1.30
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/dist/context.js +1 -1
- package/dist/html.cjs +1 -1
- package/dist/html.js +42 -62
- package/dist/index.cjs +1 -1
- package/dist/index.js +67 -67
- package/package.json +54 -59
- package/readme.md +275 -195
- package/types.ts +27 -36
- package/dist/stream.cjs +0 -1
- package/dist/stream.js +0 -24
package/dist/context.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const e = Symbol.for("ajo.context"), r = (t, o = Symbol()) => function(...l) {
|
|
1
|
+
const e = /* @__PURE__ */ Symbol.for("ajo.context"), r = (t, o = /* @__PURE__ */ Symbol()) => function(...l) {
|
|
2
2
|
const n = this ?? c;
|
|
3
3
|
return n ? l.length ? n[e][o] = l[0] : o in n[e] ? n[e][o] : t : t;
|
|
4
4
|
};
|
package/dist/html.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const s=require("./context.cjs"),m=new Set(["area","base","br","col","command","embed","hr","img","input","keygen","link","meta","param","source","track","wbr"]),b=Symbol.for("ajo.args"),y=e=>e.replace(/[&<>"']/g,r=>`&#${r.charCodeAt(0)};`),f=()=>{},p=e=>[...a(e)].join(""),a=function*(e){for(e of c(e))typeof e=="string"?yield y(e):yield*k(e)},k=function*({nodeName:e,children:r,...i}){let o="";for(const n in i)n.startsWith("set:")||i[n]==null||i[n]===!1||(i[n]===!0?o+=` ${n}`:o+=` ${n}="${y(String(i[n]))}"`);m.has(e)?yield`<${e}${o}>`:(yield`<${e}${o}>`,r!=null&&(yield*a(r)),yield`</${e}>`)},c=function*(e){if(e==null)return;const r=typeof e;if(r!="boolean")if(r=="string")yield e;else if(r=="number"||r=="bigint")yield String(e);else if(Symbol.iterator in e)for(e of e)yield*c(e);else"nodeName"in e?typeof e.nodeName=="function"?yield*$(e):yield d(e):yield String(e)},$=function*({nodeName:e,fallback:r=e.fallback,...i}){e.constructor.name=="GeneratorFunction"?yield S(e,i):yield*c(e(i))},S=(e,r)=>{const i={...e.attrs},o={...e.args};for(const t in r)t.startsWith("attr:")?i[t.slice(5)]=r[t]:t=="key"||t=="skip"||t=="memo"||t=="ref"||t.startsWith("set:")?i[t]=r[t]:o[t]=r[t];const n={[s.Context]:Object.create(s.current()?.[s.Context]??null),[b]:o,next:f,return:f,throw:t=>{throw t}},l=e.call(n,o),g=s.current();s.current(n);const u=t=>({...i,nodeName:e.is??"div",...d({children:t})});try{return u(l.next().value)}catch(t){return u(l.throw(t).value)}finally{l.return?.(),s.current(g)}},d=({key:e,skip:r,memo:i,ref:o,...n})=>{if("children"in n){const l=[...c(n.children)];l.length?n.children=l.length==1?l[0]:l:delete n.children}return n};exports.html=a;exports.render=p;
|
package/dist/html.js
CHANGED
|
@@ -1,73 +1,53 @@
|
|
|
1
|
-
import { Context as
|
|
2
|
-
const
|
|
3
|
-
},
|
|
4
|
-
for (e of
|
|
5
|
-
typeof e == "string" ? yield
|
|
6
|
-
},
|
|
7
|
-
let
|
|
8
|
-
for (const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
},
|
|
1
|
+
import { Context as f, current as s } from "./context.js";
|
|
2
|
+
const p = /* @__PURE__ */ new Set(["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param", "source", "track", "wbr"]), b = /* @__PURE__ */ Symbol.for("ajo.args"), y = (e) => e.replace(/[&<>"']/g, (n) => `&#${n.charCodeAt(0)};`), u = () => {
|
|
3
|
+
}, v = (e) => [...d(e)].join(""), d = function* (e) {
|
|
4
|
+
for (e of c(e))
|
|
5
|
+
typeof e == "string" ? yield y(e) : yield* k(e);
|
|
6
|
+
}, k = function* ({ nodeName: e, children: n, ...i }) {
|
|
7
|
+
let o = "";
|
|
8
|
+
for (const r in i)
|
|
9
|
+
r.startsWith("set:") || i[r] == null || i[r] === !1 || (i[r] === !0 ? o += ` ${r}` : o += ` ${r}="${y(String(i[r]))}"`);
|
|
10
|
+
p.has(e) ? yield `<${e}${o}>` : (yield `<${e}${o}>`, n != null && (yield* d(n)), yield `</${e}>`);
|
|
11
|
+
}, c = function* (e) {
|
|
12
12
|
if (e == null) return;
|
|
13
|
-
const
|
|
14
|
-
if (
|
|
15
|
-
if (
|
|
16
|
-
else if (
|
|
17
|
-
else if (Symbol.iterator in e) for (e of e) yield*
|
|
18
|
-
else "nodeName" in e ? typeof e.nodeName == "function" ? yield*
|
|
19
|
-
},
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
c.startsWith("attr:") ? n[c.slice(5)] = r[c] : c == "key" || c == "skip" || c == "memo" || c == "ref" || c.startsWith("set:") ? n[c] = r[c] : l[c] = r[c];
|
|
29
|
-
const i = {
|
|
30
|
-
[y]: Object.create(f()?.[y] ?? null),
|
|
31
|
-
[b]: l,
|
|
13
|
+
const n = typeof e;
|
|
14
|
+
if (n != "boolean")
|
|
15
|
+
if (n == "string") yield e;
|
|
16
|
+
else if (n == "number" || n == "bigint") yield String(e);
|
|
17
|
+
else if (Symbol.iterator in e) for (e of e) yield* c(e);
|
|
18
|
+
else "nodeName" in e ? typeof e.nodeName == "function" ? yield* $(e) : yield m(e) : yield String(e);
|
|
19
|
+
}, $ = function* ({ nodeName: e, fallback: n = e.fallback, ...i }) {
|
|
20
|
+
e.constructor.name == "GeneratorFunction" ? yield w(e, i) : yield* c(e(i));
|
|
21
|
+
}, w = (e, n) => {
|
|
22
|
+
const i = { ...e.attrs }, o = { ...e.args };
|
|
23
|
+
for (const t in n)
|
|
24
|
+
t.startsWith("attr:") ? i[t.slice(5)] = n[t] : t == "key" || t == "skip" || t == "memo" || t == "ref" || t.startsWith("set:") ? i[t] = n[t] : o[t] = n[t];
|
|
25
|
+
const r = {
|
|
26
|
+
[f]: Object.create(s()?.[f] ?? null),
|
|
27
|
+
[b]: o,
|
|
32
28
|
next: u,
|
|
33
29
|
return: u,
|
|
34
|
-
throw: (
|
|
35
|
-
throw
|
|
30
|
+
throw: (t) => {
|
|
31
|
+
throw t;
|
|
36
32
|
}
|
|
37
|
-
},
|
|
38
|
-
|
|
33
|
+
}, l = e.call(r, o), g = s();
|
|
34
|
+
s(r);
|
|
35
|
+
const a = (t) => ({ ...i, nodeName: e.is ?? "div", ...m({ children: t }) });
|
|
39
36
|
try {
|
|
40
|
-
|
|
41
|
-
|
|
37
|
+
return a(l.next().value);
|
|
38
|
+
} catch (t) {
|
|
39
|
+
return a(l.throw(t).value);
|
|
42
40
|
} finally {
|
|
43
|
-
|
|
41
|
+
l.return?.(), s(g);
|
|
44
42
|
}
|
|
45
|
-
},
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
n = { ...n, alloc: (s = l) => n.alloc(s) };
|
|
50
|
-
try {
|
|
51
|
-
for (t = await i.next(); !t.done; )
|
|
52
|
-
n.push({ id: l, h: a(t.value, n), done: !1 }), t = await i.next();
|
|
53
|
-
n.push({ id: l, h: a(t.value, n), done: !0 });
|
|
54
|
-
} catch (s) {
|
|
55
|
-
n.push({ id: l, h: a(s, n), done: !0 });
|
|
56
|
-
} finally {
|
|
57
|
-
i.return?.();
|
|
58
|
-
}
|
|
59
|
-
}), n.placeholder(l, r);
|
|
60
|
-
}, A = (e, r, t) => {
|
|
61
|
-
const n = t.alloc();
|
|
62
|
-
return r.then((l) => t.push({ id: n, h: a(l, { ...t, alloc: (i = n) => t.alloc(i) }), done: !0 })), t.placeholder(n, e);
|
|
63
|
-
}, a = ({ key: e, skip: r, memo: t, ref: n, ...l }, i) => {
|
|
64
|
-
if ("children" in l) {
|
|
65
|
-
const s = [...o(l.children, i)];
|
|
66
|
-
s.length ? l.children = s.length == 1 ? s[0] : s : delete l.children;
|
|
43
|
+
}, m = ({ key: e, skip: n, memo: i, ref: o, ...r }) => {
|
|
44
|
+
if ("children" in r) {
|
|
45
|
+
const l = [...c(r.children)];
|
|
46
|
+
l.length ? r.children = l.length == 1 ? l[0] : l : delete r.children;
|
|
67
47
|
}
|
|
68
|
-
return
|
|
48
|
+
return r;
|
|
69
49
|
};
|
|
70
50
|
export {
|
|
71
|
-
|
|
72
|
-
|
|
51
|
+
d as html,
|
|
52
|
+
v as render
|
|
73
53
|
};
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const o=require("./context.cjs"),y=Symbol.for("ajo.key"),j=Symbol.for("ajo.memo"),g=Symbol.for("ajo.ref"),
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const o=require("./context.cjs"),y=Symbol.for("ajo.key"),j=Symbol.for("ajo.memo"),g=Symbol.for("ajo.ref"),A=Symbol.for("ajo.cache"),c=Symbol.for("ajo.generator"),a=Symbol.for("ajo.iterator"),b=Symbol.for("ajo.render"),l=Symbol.for("ajo.args"),N=e=>e.children,k=(e,t,...r)=>((t??={}).nodeName=e,!("children"in t)&&r.length&&(t.children=r.length==1?r[0]:r),t),f=(e,t,r=t.firstChild,s=null)=>{for(e of S(e)){const n=E(e,t,r);r==null?m(t,n,s):n==r?r=n.nextSibling:n==r.nextSibling?(m(t,r,s),r=n.nextSibling):m(t,n,r)}for(;r!=s;){const n=r.nextSibling;r.nodeType==1&&p(r),t.removeChild(r),r=n}},S=function*(e){if(e==null)return;const t=typeof e;if(t!="boolean")if(t=="string")yield e;else if(t=="number"||t=="bigint")yield String(e);else if(Symbol.iterator in e)for(e of e)yield*S(e);else"nodeName"in e?typeof e.nodeName=="function"?yield*O(e):yield e:yield String(e)},O=function*({nodeName:e,...t}){e.constructor.name=="GeneratorFunction"?yield T(e,t):yield*S(e(t))},T=(e,t)=>{const r={...e.attrs},s={...e.args};for(const n in t)n.startsWith("attr:")?r[n.slice(5)]=t[n]:n=="key"||n=="skip"||n=="memo"||n=="ref"||n.startsWith("set:")?r[n]=t[n]:s[n]=t[n];return{...r,nodeName:e.is??"div",[c]:e,[l]:s}},E=(e,t,r)=>typeof e=="string"?F(e,r):G(e,t,r),F=(e,t)=>{for(;t&&t.nodeType!=3;)t=t.nextSibling;return t?t.data!=e&&(t.data=e):t=document.createTextNode(e),t},G=({nodeName:e,children:t,key:r,skip:s,memo:n,ref:x,[c]:u,[l]:v,...w},C,i)=>{for(;i&&(i.localName!=e||i[y]!=null&&i[y]!=r||i[c]&&i[c]!=u);)i=i.nextSibling;return i??=document.createElementNS(w.xmlns??C.namespaceURI,e),r!=null&&(i[y]=r),(n==null||W(i[j],i[j]=n))&&(R(i[A]??B(i),i[A]=w,i),s||(u?I(u,v,i):f(t,i)),typeof x=="function"&&(i[g]=x)(i)),i},R=(e,t,r)=>{for(const s in{...e,...t})e[s]!==t[s]&&(s.startsWith("set:")?r[s.slice(4)]=t[s]:t[s]==null||t[s]===!1?r.removeAttribute(s):r.setAttribute(s,t[s]===!0?"":t[s]))},W=(e,t)=>Array.isArray(e)&&Array.isArray(t)?e.some((r,s)=>r!==t[s]):e!==t,B=e=>Array.from(e.attributes).reduce((t,r)=>(t[r.name]=r.value,t),{}),m=(e,t,r)=>{if(t.contains(document.activeElement)){const s=t.nextSibling;for(;r&&r!=t;){const n=r.nextSibling;e.insertBefore(r,s),r=n}}else e.insertBefore(t,r)},p=e=>{for(const t of e.children)p(t);typeof e.return=="function"&&e.return(),e[g]?.(null)},I=(e,t,r)=>{r[c]??=(M(r),e),Object.assign(r[l]??={},t),r[b]()},M=e=>{Object.assign(e,h),e[o.Context]=Object.create(o.current()?.[o.Context]??null)},h={[b](){const e=o.current();o.current(this);try{const{value:t,done:r}=(this[a]??=this[c].call(this,this[l])).next();f(t,this),this[g]?.(this),r&&this.return()}catch(t){this.throw(t)}finally{o.current(e)}},next(e){try{e?.call(this,this[l])}catch(t){return this.throw(t)}o.current()?.contains(this)||this[b]()},throw(e){for(let t=this;t;t=t.parentNode)if(t[a]?.throw)try{return f(t[a].throw(e).value,t)}catch(r){e=new Error(r?.message??r,{cause:e})}throw e},return(){try{this[a]?.return()}catch(e){this.throw(e)}finally{this[a]=null}}};exports.Fragment=N;exports.h=k;exports.render=f;
|
package/dist/index.js
CHANGED
|
@@ -1,86 +1,86 @@
|
|
|
1
|
-
import { Context as j, current as
|
|
2
|
-
const u = Symbol.for("ajo.key"), p = Symbol.for("ajo.memo"), g = Symbol.for("ajo.ref"), A = Symbol.for("ajo.cache"), o = Symbol.for("ajo.generator"), a = Symbol.for("ajo.iterator"), m = Symbol.for("ajo.render"),
|
|
3
|
-
for (
|
|
4
|
-
const n = G(t, r
|
|
5
|
-
|
|
1
|
+
import { Context as j, current as c } from "./context.js";
|
|
2
|
+
const u = /* @__PURE__ */ Symbol.for("ajo.key"), p = /* @__PURE__ */ Symbol.for("ajo.memo"), g = /* @__PURE__ */ Symbol.for("ajo.ref"), A = /* @__PURE__ */ Symbol.for("ajo.cache"), o = /* @__PURE__ */ Symbol.for("ajo.generator"), a = /* @__PURE__ */ Symbol.for("ajo.iterator"), m = /* @__PURE__ */ Symbol.for("ajo.render"), l = /* @__PURE__ */ Symbol.for("ajo.args"), U = (e) => e.children, h = (e, t, ...r) => ((t ??= {}).nodeName = e, !("children" in t) && r.length && (t.children = r.length == 1 ? r[0] : r), t), b = (e, t, r = t.firstChild, s = null) => {
|
|
3
|
+
for (e of S(e)) {
|
|
4
|
+
const n = G(e, t, r);
|
|
5
|
+
r == null ? y(t, n, s) : n == r ? r = n.nextSibling : n == r.nextSibling ? (y(t, r, s), r = n.nextSibling) : y(t, n, r);
|
|
6
6
|
}
|
|
7
|
-
for (;
|
|
8
|
-
const n =
|
|
9
|
-
|
|
7
|
+
for (; r != s; ) {
|
|
8
|
+
const n = r.nextSibling;
|
|
9
|
+
r.nodeType == 1 && N(r), t.removeChild(r), r = n;
|
|
10
10
|
}
|
|
11
|
-
}, S = function* (
|
|
12
|
-
if (
|
|
13
|
-
const
|
|
14
|
-
if (
|
|
15
|
-
if (
|
|
16
|
-
else if (
|
|
17
|
-
else if (Symbol.iterator in
|
|
18
|
-
else "nodeName" in
|
|
19
|
-
}, C = function* ({ nodeName:
|
|
20
|
-
|
|
21
|
-
}, E =
|
|
22
|
-
const
|
|
23
|
-
for (const n in
|
|
24
|
-
n.startsWith("attr:") ?
|
|
25
|
-
return { ...
|
|
26
|
-
}, G = (t, r
|
|
27
|
-
for (;
|
|
28
|
-
return
|
|
29
|
-
}, R = ({ nodeName:
|
|
30
|
-
for (;
|
|
31
|
-
return
|
|
32
|
-
}, T = (t, r
|
|
33
|
-
for (const
|
|
34
|
-
|
|
35
|
-
}, W = (
|
|
36
|
-
if (
|
|
37
|
-
const
|
|
38
|
-
for (;
|
|
39
|
-
const n =
|
|
40
|
-
|
|
11
|
+
}, S = function* (e) {
|
|
12
|
+
if (e == null) return;
|
|
13
|
+
const t = typeof e;
|
|
14
|
+
if (t != "boolean")
|
|
15
|
+
if (t == "string") yield e;
|
|
16
|
+
else if (t == "number" || t == "bigint") yield String(e);
|
|
17
|
+
else if (Symbol.iterator in e) for (e of e) yield* S(e);
|
|
18
|
+
else "nodeName" in e ? typeof e.nodeName == "function" ? yield* C(e) : yield e : yield String(e);
|
|
19
|
+
}, C = function* ({ nodeName: e, ...t }) {
|
|
20
|
+
e.constructor.name == "GeneratorFunction" ? yield E(e, t) : yield* S(e(t));
|
|
21
|
+
}, E = (e, t) => {
|
|
22
|
+
const r = { ...e.attrs }, s = { ...e.args };
|
|
23
|
+
for (const n in t)
|
|
24
|
+
n.startsWith("attr:") ? r[n.slice(5)] = t[n] : n == "key" || n == "skip" || n == "memo" || n == "ref" || n.startsWith("set:") ? r[n] = t[n] : s[n] = t[n];
|
|
25
|
+
return { ...r, nodeName: e.is ?? "div", [o]: e, [l]: s };
|
|
26
|
+
}, G = (e, t, r) => typeof e == "string" ? O(e, r) : R(e, t, r), O = (e, t) => {
|
|
27
|
+
for (; t && t.nodeType != 3; ) t = t.nextSibling;
|
|
28
|
+
return t ? t.data != e && (t.data = e) : t = document.createTextNode(e), t;
|
|
29
|
+
}, R = ({ nodeName: e, children: t, key: r, skip: s, memo: n, ref: x, [o]: f, [l]: k, ...w }, v, i) => {
|
|
30
|
+
for (; i && (i.localName != e || i[u] != null && i[u] != r || i[o] && i[o] != f); ) i = i.nextSibling;
|
|
31
|
+
return i ??= document.createElementNS(w.xmlns ?? v.namespaceURI, e), r != null && (i[u] = r), (n == null || W(i[p], i[p] = n)) && (T(i[A] ?? B(i), i[A] = w, i), s || (f ? F(f, k, i) : b(t, i)), typeof x == "function" && (i[g] = x)(i)), i;
|
|
32
|
+
}, T = (e, t, r) => {
|
|
33
|
+
for (const s in { ...e, ...t })
|
|
34
|
+
e[s] !== t[s] && (s.startsWith("set:") ? r[s.slice(4)] = t[s] : t[s] == null || t[s] === !1 ? r.removeAttribute(s) : r.setAttribute(s, t[s] === !0 ? "" : t[s]));
|
|
35
|
+
}, W = (e, t) => Array.isArray(e) && Array.isArray(t) ? e.some((r, s) => r !== t[s]) : e !== t, B = (e) => Array.from(e.attributes).reduce((t, r) => (t[r.name] = r.value, t), {}), y = (e, t, r) => {
|
|
36
|
+
if (t.contains(document.activeElement)) {
|
|
37
|
+
const s = t.nextSibling;
|
|
38
|
+
for (; r && r != t; ) {
|
|
39
|
+
const n = r.nextSibling;
|
|
40
|
+
e.insertBefore(r, s), r = n;
|
|
41
41
|
}
|
|
42
|
-
} else
|
|
43
|
-
},
|
|
44
|
-
for (const
|
|
45
|
-
typeof
|
|
46
|
-
}, F = (t, r
|
|
47
|
-
|
|
48
|
-
}, I = (
|
|
49
|
-
Object.assign(
|
|
42
|
+
} else e.insertBefore(t, r);
|
|
43
|
+
}, N = (e) => {
|
|
44
|
+
for (const t of e.children) N(t);
|
|
45
|
+
typeof e.return == "function" && e.return(), e[g]?.(null);
|
|
46
|
+
}, F = (e, t, r) => {
|
|
47
|
+
r[o] ??= (I(r), e), Object.assign(r[l] ??= {}, t), r[m]();
|
|
48
|
+
}, I = (e) => {
|
|
49
|
+
Object.assign(e, K), e[j] = Object.create(c()?.[j] ?? null);
|
|
50
50
|
}, K = {
|
|
51
51
|
[m]() {
|
|
52
|
-
const
|
|
53
|
-
|
|
52
|
+
const e = c();
|
|
53
|
+
c(this);
|
|
54
54
|
try {
|
|
55
|
-
const { value:
|
|
56
|
-
b(
|
|
57
|
-
} catch (
|
|
58
|
-
this.throw(
|
|
55
|
+
const { value: t, done: r } = (this[a] ??= this[o].call(this, this[l])).next();
|
|
56
|
+
b(t, this), this[g]?.(this), r && this.return();
|
|
57
|
+
} catch (t) {
|
|
58
|
+
this.throw(t);
|
|
59
59
|
} finally {
|
|
60
|
-
|
|
60
|
+
c(e);
|
|
61
61
|
}
|
|
62
62
|
},
|
|
63
|
-
next(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
} catch (
|
|
67
|
-
this.throw(
|
|
63
|
+
next(e) {
|
|
64
|
+
try {
|
|
65
|
+
e?.call(this, this[l]);
|
|
66
|
+
} catch (t) {
|
|
67
|
+
return this.throw(t);
|
|
68
68
|
}
|
|
69
|
-
|
|
69
|
+
c()?.contains(this) || this[m]();
|
|
70
70
|
},
|
|
71
|
-
throw(
|
|
72
|
-
for (let
|
|
73
|
-
return b(
|
|
74
|
-
} catch (
|
|
75
|
-
|
|
71
|
+
throw(e) {
|
|
72
|
+
for (let t = this; t; t = t.parentNode) if (t[a]?.throw) try {
|
|
73
|
+
return b(t[a].throw(e).value, t);
|
|
74
|
+
} catch (r) {
|
|
75
|
+
e = new Error(r?.message ?? r, { cause: e });
|
|
76
76
|
}
|
|
77
|
-
throw
|
|
77
|
+
throw e;
|
|
78
78
|
},
|
|
79
79
|
return() {
|
|
80
80
|
try {
|
|
81
81
|
this[a]?.return();
|
|
82
|
-
} catch (
|
|
83
|
-
this.throw(
|
|
82
|
+
} catch (e) {
|
|
83
|
+
this.throw(e);
|
|
84
84
|
} finally {
|
|
85
85
|
this[a] = null;
|
|
86
86
|
}
|
package/package.json
CHANGED
|
@@ -1,60 +1,55 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
"build": "vite build",
|
|
57
|
-
"bump": "pnpm version patch && git push && git push --tags",
|
|
58
|
-
"release": "pnpm test && pnpm build && pnpm bump && pnpm publish"
|
|
59
|
-
}
|
|
60
|
-
}
|
|
2
|
+
"name": "ajo",
|
|
3
|
+
"version": "0.1.30",
|
|
4
|
+
"description": "ajo is a JavaScript view library for building user interfaces",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"types": "./types.ts",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"main": "./dist/index.cjs",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./types.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
},
|
|
15
|
+
"./context": {
|
|
16
|
+
"types": "./types.ts",
|
|
17
|
+
"import": "./dist/context.js",
|
|
18
|
+
"require": "./dist/context.cjs"
|
|
19
|
+
},
|
|
20
|
+
"./html": {
|
|
21
|
+
"types": "./types.ts",
|
|
22
|
+
"import": "./dist/html.js",
|
|
23
|
+
"require": "./dist/html.cjs"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"types.ts"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"test": "vitest --run",
|
|
32
|
+
"build": "vite build",
|
|
33
|
+
"bump": "pnpm version patch && git push && git push --tags",
|
|
34
|
+
"release": "pnpm test && pnpm build && pnpm bump && pnpm publish"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "25.2.3",
|
|
38
|
+
"happy-dom": "20.6.1",
|
|
39
|
+
"vite": "7.3.1",
|
|
40
|
+
"vite-tsconfig-paths": "6.1.1",
|
|
41
|
+
"vitest": "4.0.18"
|
|
42
|
+
},
|
|
43
|
+
"keywords": [
|
|
44
|
+
"ui",
|
|
45
|
+
"frontend",
|
|
46
|
+
"web",
|
|
47
|
+
"dom",
|
|
48
|
+
"jsx"
|
|
49
|
+
],
|
|
50
|
+
"repository": "cristianfalcone/ajo",
|
|
51
|
+
"author": "Cristian Falcone",
|
|
52
|
+
"license": "ISC",
|
|
53
|
+
"bugs": "https://github.com/cristianfalcone/ajo/issues",
|
|
54
|
+
"homepage": "https://github.com/cristianfalcone/ajo#readme"
|
|
55
|
+
}
|
package/readme.md
CHANGED
|
@@ -13,326 +13,406 @@
|
|
|
13
13
|
</a>
|
|
14
14
|
</div>
|
|
15
15
|
|
|
16
|
-
A modern JavaScript library for building user interfaces with generator-based state management
|
|
16
|
+
A modern JavaScript library for building user interfaces with generator-based state management and efficient DOM updates.
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
- **Generator-Based Components**: Use JavaScript generator functions (`function*`) for stateful components with built-in lifecycle management
|
|
21
|
-
- **Efficient DOM Updates**: In-place DOM reconciliation minimizes DOM manipulation and maximizes performance
|
|
22
|
-
- **Declarative JSX**: Write components using familiar JSX syntax with full TypeScript support
|
|
23
|
-
- **Performance Optimization**: Built-in `memo` attribute for fine-grained performance optimization
|
|
24
|
-
- **Context API**: Share state across component trees without prop drilling
|
|
25
|
-
- **Server-Side Rendering**: Complete SSR solution with streaming and hydration support
|
|
26
|
-
- **Islands Architecture**: Selective hydration for maximum performance with minimal client-side JavaScript
|
|
18
|
+
- **Generator-Based Components**: Use `function*` for stateful components with built-in lifecycle
|
|
19
|
+
- **Efficient DOM Updates**: In-place reconciliation minimizes DOM manipulation
|
|
27
20
|
|
|
28
21
|
## Quick Start
|
|
29
22
|
|
|
30
|
-
Install Ajo using your preferred package manager:
|
|
31
|
-
|
|
32
23
|
```bash
|
|
33
24
|
npm install ajo
|
|
34
25
|
```
|
|
35
26
|
|
|
36
|
-
Create your first component:
|
|
37
|
-
|
|
38
27
|
```javascript
|
|
39
28
|
import { render } from 'ajo'
|
|
40
29
|
|
|
41
|
-
// Stateless component
|
|
42
|
-
const Greeting = ({ name }) => <p>Hello, {name}!</p>
|
|
43
|
-
|
|
44
|
-
// Stateful component
|
|
45
30
|
function* Counter() {
|
|
46
31
|
|
|
47
32
|
let count = 0
|
|
48
33
|
|
|
49
|
-
const increment = () => this.next(() => count++)
|
|
50
|
-
|
|
51
34
|
while (true) yield (
|
|
52
|
-
<button set:onclick={
|
|
35
|
+
<button set:onclick={() => this.next(() => count++)}>
|
|
53
36
|
Count: {count}
|
|
54
37
|
</button>
|
|
55
38
|
)
|
|
56
39
|
}
|
|
57
40
|
|
|
58
|
-
// Render to DOM
|
|
59
41
|
render(<Counter />, document.body)
|
|
60
42
|
```
|
|
61
43
|
|
|
62
|
-
|
|
44
|
+
### Build Configuration
|
|
63
45
|
|
|
64
|
-
|
|
46
|
+
Configure your build tool to use Ajo's JSX factory:
|
|
65
47
|
|
|
66
|
-
**
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
48
|
+
**Vite:**
|
|
49
|
+
```typescript
|
|
50
|
+
// vite.config.ts
|
|
51
|
+
import { defineConfig } from 'vite'
|
|
52
|
+
|
|
53
|
+
export default defineConfig({
|
|
54
|
+
esbuild: {
|
|
55
|
+
jsxFactory: 'h',
|
|
56
|
+
jsxFragment: 'Fragment',
|
|
57
|
+
jsxInject: `import { h, Fragment } from 'ajo'`,
|
|
58
|
+
},
|
|
59
|
+
})
|
|
74
60
|
```
|
|
75
61
|
|
|
76
|
-
**
|
|
77
|
-
```
|
|
78
|
-
|
|
62
|
+
**TypeScript:**
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"compilerOptions": {
|
|
66
|
+
"jsx": "react",
|
|
67
|
+
"jsxFactory": "h",
|
|
68
|
+
"jsxFragmentFactory": "Fragment"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
79
72
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const addTodo = text => this.next(() => todos.push({ id: Date.now(), text }))
|
|
73
|
+
**Other build tools:** Set `jsxFactory: 'h'`, `jsxFragment: 'Fragment'`, and auto-import `{ h, Fragment }` from `'ajo'`.
|
|
83
74
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
75
|
+
## Core Concepts
|
|
76
|
+
|
|
77
|
+
### Stateless Components
|
|
78
|
+
|
|
79
|
+
Pure functions that receive props and return JSX:
|
|
80
|
+
|
|
81
|
+
```javascript
|
|
82
|
+
const Greeting = ({ name }) => <p>Hello, {name}!</p>
|
|
93
83
|
```
|
|
94
84
|
|
|
95
|
-
###
|
|
85
|
+
### Stateful Components
|
|
86
|
+
|
|
87
|
+
Generator functions with automatic wrapper elements. The structure provides a natural mental model:
|
|
96
88
|
|
|
97
|
-
|
|
98
|
-
- **
|
|
99
|
-
- **Inside the loop**: Re-executed on each render for derived values
|
|
89
|
+
- **Before the loop**: Persistent state and handlers (survives re-renders)
|
|
90
|
+
- **Inside the loop**: Derived values (computed fresh each render)
|
|
100
91
|
|
|
101
92
|
```javascript
|
|
102
|
-
function*
|
|
93
|
+
function* TodoList() {
|
|
103
94
|
|
|
104
|
-
|
|
105
|
-
let
|
|
95
|
+
let todos = []
|
|
96
|
+
let text = ''
|
|
106
97
|
|
|
107
|
-
|
|
108
|
-
|
|
98
|
+
const add = () => this.next(() => {
|
|
99
|
+
if (text.trim()) {
|
|
100
|
+
todos.push({ id: Date.now(), text })
|
|
101
|
+
text = ''
|
|
102
|
+
}
|
|
103
|
+
})
|
|
109
104
|
|
|
110
|
-
// Main render loop
|
|
111
105
|
while (true) {
|
|
112
106
|
|
|
113
|
-
|
|
114
|
-
const total = items.reduce((sum, item) => sum + item.price, 0)
|
|
115
|
-
const itemCount = items.length
|
|
107
|
+
const count = todos.length
|
|
116
108
|
|
|
117
109
|
yield (
|
|
118
110
|
<>
|
|
119
|
-
<
|
|
120
|
-
|
|
121
|
-
|
|
111
|
+
<input
|
|
112
|
+
set:value={text}
|
|
113
|
+
set:oninput={e => text = e.target.value}
|
|
114
|
+
set:onkeydown={e => e.key === 'Enter' && add()}
|
|
115
|
+
/>
|
|
116
|
+
<button set:onclick={add}>Add ({count})</button>
|
|
117
|
+
<ul>
|
|
118
|
+
{todos.map(t => <li key={t.id}>{t.text}</li>)}
|
|
119
|
+
</ul>
|
|
122
120
|
</>
|
|
123
121
|
)
|
|
124
122
|
}
|
|
125
123
|
}
|
|
126
124
|
```
|
|
127
125
|
|
|
128
|
-
###
|
|
126
|
+
### Re-rendering with `this.next()`
|
|
129
127
|
|
|
130
|
-
-
|
|
131
|
-
- **`ref`**: DOM element access
|
|
132
|
-
- **`memo`**: Performance optimization
|
|
133
|
-
- **`skip`**: Third-party DOM management
|
|
134
|
-
- **`set:`**: Direct property setting
|
|
135
|
-
- **`attr:`**: Force HTML attributes
|
|
128
|
+
Call `this.next()` to trigger a re-render. The optional callback receives current props, useful when props may have changed:
|
|
136
129
|
|
|
137
130
|
```javascript
|
|
138
|
-
function*
|
|
131
|
+
function* Stepper(args) {
|
|
132
|
+
|
|
133
|
+
let count = 0
|
|
139
134
|
|
|
140
|
-
|
|
135
|
+
// Access current props in callback
|
|
136
|
+
const inc = () => this.next(({ step = 1 }) => count += step)
|
|
141
137
|
|
|
142
138
|
while (true) yield (
|
|
143
|
-
<
|
|
144
|
-
ref={el => {
|
|
145
|
-
if (el && !mapRef) {
|
|
146
|
-
mapRef = el
|
|
147
|
-
// Third-party map library controls this DOM
|
|
148
|
-
new GoogleMap(el, args.config)
|
|
149
|
-
}
|
|
150
|
-
}}
|
|
151
|
-
skip
|
|
152
|
-
>
|
|
153
|
-
{/* Google Maps API manages children elements */}
|
|
154
|
-
</div>
|
|
139
|
+
<button set:onclick={inc}>Count: {count} (+{args.step})</button>
|
|
155
140
|
)
|
|
156
141
|
}
|
|
157
142
|
```
|
|
158
143
|
|
|
159
|
-
|
|
144
|
+
**Never destructure args in the generator signature** - it locks values to initial props:
|
|
160
145
|
|
|
161
|
-
### Static SSR
|
|
162
146
|
```javascript
|
|
163
|
-
|
|
147
|
+
// DON'T - values frozen at mount
|
|
148
|
+
function* Bad({ step }) { let count = step }
|
|
164
149
|
|
|
165
|
-
|
|
150
|
+
// DO - use args directly, or destructure inside the loop
|
|
151
|
+
function* Good(args) {
|
|
152
|
+
while (true) {
|
|
153
|
+
const { step } = args // fresh each render
|
|
154
|
+
yield ...
|
|
155
|
+
}
|
|
156
|
+
}
|
|
166
157
|
```
|
|
167
158
|
|
|
168
|
-
###
|
|
159
|
+
### Lifecycle and Cleanup
|
|
160
|
+
|
|
161
|
+
Use `try...finally` for cleanup when the component unmounts:
|
|
162
|
+
|
|
169
163
|
```javascript
|
|
170
|
-
|
|
164
|
+
function* Clock() {
|
|
171
165
|
|
|
172
|
-
|
|
166
|
+
let time = new Date()
|
|
167
|
+
|
|
168
|
+
const interval = setInterval(() => this.next(() => time = new Date()), 1000)
|
|
169
|
+
|
|
170
|
+
try {
|
|
171
|
+
while (true) yield <p>{time.toLocaleTimeString()}</p>
|
|
172
|
+
} finally {
|
|
173
|
+
clearInterval(interval)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
173
176
|
```
|
|
174
177
|
|
|
175
|
-
|
|
178
|
+
### Error Boundaries
|
|
176
179
|
|
|
177
|
-
|
|
178
|
-
2. **Don't destructure props in function signatures** - use `args` parameter or destructure inside the render loop
|
|
179
|
-
3. **Leverage the generator structure** for natural state and lifecycle management
|
|
180
|
-
4. **Use TypeScript** for enhanced developer experience
|
|
180
|
+
Use `try...catch` **inside** the loop to catch errors and recover:
|
|
181
181
|
|
|
182
|
-
|
|
182
|
+
```javascript
|
|
183
|
+
function* ErrorBoundary(args) {
|
|
184
|
+
while (true) {
|
|
185
|
+
try {
|
|
186
|
+
yield args.children
|
|
187
|
+
} catch (error) {
|
|
188
|
+
yield (
|
|
189
|
+
<>
|
|
190
|
+
<p>Error: {error.message}</p>
|
|
191
|
+
<button set:onclick={() => this.next()}>Retry</button>
|
|
192
|
+
</>
|
|
193
|
+
)
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
```
|
|
183
198
|
|
|
184
|
-
|
|
199
|
+
## Special Attributes
|
|
185
200
|
|
|
186
|
-
|
|
187
|
-
|
|
201
|
+
| Attribute | Description |
|
|
202
|
+
|-----------|-------------|
|
|
203
|
+
| `key` | Unique identifier for list reconciliation |
|
|
204
|
+
| `ref` | Callback receiving DOM element (or `null` on unmount) |
|
|
205
|
+
| `memo` | Skip reconciliation: `memo={[deps]}`, `memo={value}`, or `memo` (render once) |
|
|
206
|
+
| `skip` | Exclude children from reconciliation (required with `set:innerHTML`) |
|
|
207
|
+
| `set:*` | Set DOM properties instead of HTML attributes |
|
|
208
|
+
| `attr:*` | Force HTML attributes on stateful component wrappers |
|
|
209
|
+
|
|
210
|
+
### `set:` - DOM Properties vs HTML Attributes
|
|
188
211
|
|
|
189
212
|
```javascript
|
|
190
|
-
|
|
213
|
+
// Events (always use set:)
|
|
214
|
+
<button set:onclick={handleClick}>Click</button>
|
|
191
215
|
|
|
192
|
-
|
|
216
|
+
// Dynamic values that need to sync with state
|
|
217
|
+
<input set:value={text} /> // DOM property (syncs)
|
|
218
|
+
<input value="initial" /> // HTML attribute (initial only)
|
|
193
219
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
render(<main>Updated</main>, container, container.querySelector('main'), container.querySelector('footer'))
|
|
197
|
-
```
|
|
220
|
+
<input type="checkbox" set:checked={bool} />
|
|
221
|
+
<video set:currentTime={0} set:muted />
|
|
198
222
|
|
|
199
|
-
|
|
200
|
-
|
|
223
|
+
// innerHTML requires skip
|
|
224
|
+
<div set:innerHTML={html} skip />
|
|
225
|
+
```
|
|
201
226
|
|
|
202
|
-
|
|
203
|
-
JSX fragment component for rendering multiple elements without a wrapper.
|
|
227
|
+
### `ref` - DOM Access
|
|
204
228
|
|
|
205
229
|
```javascript
|
|
206
|
-
|
|
207
|
-
<>
|
|
208
|
-
<li>Item 1</li>
|
|
209
|
-
<li>Item 2</li>
|
|
210
|
-
</>
|
|
211
|
-
)
|
|
212
|
-
```
|
|
230
|
+
function* AutoFocus() {
|
|
213
231
|
|
|
214
|
-
|
|
232
|
+
let input = null
|
|
215
233
|
|
|
216
|
-
|
|
234
|
+
while (true) yield (
|
|
235
|
+
<>
|
|
236
|
+
<input ref={el => el?.focus()} />
|
|
237
|
+
<button set:onclick={() => input?.select()}>Select</button>
|
|
238
|
+
</>
|
|
239
|
+
)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Ref to stateful component includes control methods
|
|
243
|
+
let timer = null
|
|
244
|
+
<Clock ref={el => timer = el} />
|
|
245
|
+
timer?.next() // trigger re-render from outside
|
|
246
|
+
```
|
|
217
247
|
|
|
218
|
-
|
|
219
|
-
Triggers a re-render of the component by advancing to the next yield point. Optionally accepts a callback function that receives the component's current props/args as the first parameter.
|
|
248
|
+
### `memo` - Performance Optimization
|
|
220
249
|
|
|
221
250
|
```javascript
|
|
222
|
-
|
|
251
|
+
<div memo={[user.id]}>...</div> // re-render when user.id changes
|
|
252
|
+
<div memo={count}>...</div> // re-render when count changes
|
|
253
|
+
<footer memo>Static content</footer> // render once, never update
|
|
254
|
+
```
|
|
223
255
|
|
|
224
|
-
|
|
256
|
+
### `skip` - Third-Party DOM
|
|
225
257
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
this.next(() => count++)
|
|
229
|
-
}
|
|
258
|
+
```javascript
|
|
259
|
+
function* Chart(args) {
|
|
230
260
|
|
|
231
|
-
|
|
232
|
-
// Access current props in callback
|
|
233
|
-
this.next(({ step }) => count += step)
|
|
234
|
-
}
|
|
261
|
+
let chart = null
|
|
235
262
|
|
|
236
|
-
|
|
263
|
+
while (true) yield (
|
|
264
|
+
<div skip ref={el => el && (chart ??= new ChartLib(el, args.data))} />
|
|
265
|
+
)
|
|
237
266
|
}
|
|
238
267
|
```
|
|
239
268
|
|
|
240
|
-
|
|
241
|
-
Throws an error that can be caught by parent error boundaries.
|
|
269
|
+
### `attr:` - Wrapper Attributes
|
|
242
270
|
|
|
243
|
-
|
|
244
|
-
|
|
271
|
+
```javascript
|
|
272
|
+
<Counter
|
|
273
|
+
initial={0} // → args
|
|
274
|
+
attr:id="main" // → wrapper HTML attribute
|
|
275
|
+
attr:class="widget" // → wrapper HTML attribute
|
|
276
|
+
set:onclick={fn} // → wrapper DOM property
|
|
277
|
+
/>
|
|
278
|
+
```
|
|
245
279
|
|
|
246
|
-
|
|
280
|
+
## Context API
|
|
247
281
|
|
|
248
|
-
|
|
249
|
-
Creates a context for sharing data across component trees.
|
|
282
|
+
Share data across component trees without prop drilling:
|
|
250
283
|
|
|
251
284
|
```javascript
|
|
252
285
|
import { context } from 'ajo/context'
|
|
253
286
|
|
|
254
287
|
const ThemeContext = context('light')
|
|
288
|
+
```
|
|
255
289
|
|
|
256
|
-
|
|
257
|
-
|
|
290
|
+
**Stateless**: read only.
|
|
291
|
+
**Stateful**: read/write. Write inside the loop to update each render, or outside for a one-time set.
|
|
258
292
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
293
|
+
```javascript
|
|
294
|
+
// Stateless - read only
|
|
295
|
+
const Card = ({ title }) => {
|
|
296
|
+
const theme = ThemeContext()
|
|
297
|
+
return <div class={`card theme-${theme}`}>{title}</div>
|
|
298
|
+
}
|
|
262
299
|
|
|
263
|
-
|
|
300
|
+
// Stateful - write inside loop (updates each render)
|
|
301
|
+
function* ThemeProvider(args) {
|
|
264
302
|
|
|
265
|
-
|
|
266
|
-
Renders JSX to an HTML string for static site generation.
|
|
303
|
+
let theme = 'light'
|
|
267
304
|
|
|
268
|
-
|
|
269
|
-
|
|
305
|
+
while (true) {
|
|
306
|
+
ThemeContext(theme)
|
|
307
|
+
yield (
|
|
308
|
+
<>
|
|
309
|
+
<button set:onclick={() => this.next(() => theme = theme === 'light' ? 'dark' : 'light')}>
|
|
310
|
+
{theme}
|
|
311
|
+
</button>
|
|
312
|
+
{args.children}
|
|
313
|
+
</>
|
|
314
|
+
)
|
|
315
|
+
}
|
|
316
|
+
}
|
|
270
317
|
|
|
271
|
-
|
|
318
|
+
// Stateful - write outside loop (one-time set)
|
|
319
|
+
function* FixedTheme(args) {
|
|
320
|
+
ThemeContext('dark') // set once at mount
|
|
321
|
+
while (true) yield args.children
|
|
322
|
+
}
|
|
272
323
|
```
|
|
273
324
|
|
|
274
|
-
|
|
275
|
-
Low-level HTML streaming function with custom hooks.
|
|
325
|
+
## Async Operations
|
|
276
326
|
|
|
277
|
-
|
|
327
|
+
```javascript
|
|
328
|
+
function* UserProfile(args) {
|
|
278
329
|
|
|
279
|
-
|
|
280
|
-
Renders components to an async stream for progressive SSR.
|
|
330
|
+
let data = null, error = null, loading = true
|
|
281
331
|
|
|
282
|
-
|
|
283
|
-
|
|
332
|
+
fetch(`/api/users/${args.id}`)
|
|
333
|
+
.then(r => r.json())
|
|
334
|
+
.then(d => this.next(() => { data = d; loading = false }))
|
|
335
|
+
.catch(e => this.next(() => { error = e; loading = false }))
|
|
284
336
|
|
|
285
|
-
|
|
337
|
+
while (true) {
|
|
338
|
+
if (loading) yield <p>Loading...</p>
|
|
339
|
+
else if (error) yield <p>Error: {error.message}</p>
|
|
340
|
+
else yield <h1>{data.name}</h1>
|
|
341
|
+
}
|
|
342
|
+
}
|
|
286
343
|
```
|
|
287
344
|
|
|
288
|
-
|
|
289
|
-
Client-side function for applying streamed patches during hydration.
|
|
345
|
+
## Server-Side Rendering
|
|
290
346
|
|
|
291
347
|
```javascript
|
|
292
|
-
import {
|
|
293
|
-
|
|
294
|
-
window.$stream = { push: hydrate }
|
|
348
|
+
import { render } from 'ajo/html'
|
|
349
|
+
const html = render(<App />)
|
|
295
350
|
```
|
|
296
351
|
|
|
297
|
-
|
|
352
|
+
## TypeScript
|
|
298
353
|
|
|
299
354
|
```typescript
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
355
|
+
import type { Stateless, Stateful, WithChildren } from 'ajo'
|
|
356
|
+
|
|
357
|
+
// Stateless
|
|
358
|
+
type CardProps = WithChildren<{ title: string }>
|
|
359
|
+
const Card: Stateless<CardProps> = ({ title, children }) => (
|
|
360
|
+
<div class="card"><h3>{title}</h3>{children}</div>
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
// Stateful with custom wrapper element
|
|
364
|
+
type CounterProps = { initial: number; step?: number }
|
|
365
|
+
|
|
366
|
+
const Counter: Stateful<CounterProps, 'section'> = function* (args) {
|
|
367
|
+
|
|
368
|
+
let count = args.initial
|
|
369
|
+
|
|
370
|
+
while (true) {
|
|
371
|
+
const { step = 1 } = args
|
|
372
|
+
yield <button set:onclick={() => this.next(() => count += step)}>+{step}</button>
|
|
373
|
+
}
|
|
307
374
|
}
|
|
308
375
|
|
|
309
|
-
//
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
// Element types
|
|
317
|
-
type Children = unknown
|
|
318
|
-
type VNode<Type, Props> = Props & {
|
|
319
|
-
nodeName: Type
|
|
320
|
-
children?: Children
|
|
321
|
-
};
|
|
322
|
-
|
|
323
|
-
// Special attributes
|
|
324
|
-
type SpecialAttributes = {
|
|
325
|
-
key?: unknown
|
|
326
|
-
ref?: (element: Element | null) => void
|
|
327
|
-
memo?: unknown[]
|
|
328
|
-
skip?: boolean
|
|
329
|
-
};
|
|
376
|
+
Counter.is = 'section' // wrapper element (default: 'div')
|
|
377
|
+
Counter.attrs = { class: 'counter' } // default wrapper attributes
|
|
378
|
+
Counter.args = { step: 1 } // default args
|
|
379
|
+
|
|
380
|
+
// Ref typing
|
|
381
|
+
let ref: ThisParameterType<typeof Counter> | null = null
|
|
382
|
+
<Counter ref={el => ref = el} initial={0} />
|
|
330
383
|
```
|
|
331
384
|
|
|
332
|
-
##
|
|
385
|
+
## API Reference
|
|
386
|
+
|
|
387
|
+
### `ajo`
|
|
388
|
+
| Export | Description |
|
|
389
|
+
|--------|-------------|
|
|
390
|
+
| `render(children, container, start?, end?)` | Render to DOM. Optional `start`/`end` for targeted updates. |
|
|
391
|
+
| `h`, `Fragment` | JSX factory and fragment |
|
|
392
|
+
|
|
393
|
+
### `ajo/context`
|
|
394
|
+
| Export | Description |
|
|
395
|
+
|--------|-------------|
|
|
396
|
+
| `context<T>(fallback?)` | Create context. Call with value to write, without to read. |
|
|
397
|
+
|
|
398
|
+
### `ajo/html`
|
|
399
|
+
| Export | Description |
|
|
400
|
+
|--------|-------------|
|
|
401
|
+
| `render(children)` | Render to HTML string |
|
|
402
|
+
|
|
403
|
+
### Stateful `this`
|
|
404
|
+
| Method | Description |
|
|
405
|
+
|--------|-------------|
|
|
406
|
+
| `this.next(fn?)` | Re-render. Callback receives current args. |
|
|
407
|
+
| `this.throw(error)` | Throw to parent boundary |
|
|
408
|
+
| `this.return()` | Terminate generator |
|
|
409
|
+
|
|
410
|
+
`this` is also the wrapper element (`this.addEventListener()`, etc).
|
|
411
|
+
|
|
412
|
+
## For AI Assistants
|
|
333
413
|
|
|
334
|
-
|
|
414
|
+
See [LLMs.md](./LLMs.md) for a condensed reference.
|
|
335
415
|
|
|
336
416
|
## License
|
|
337
417
|
|
|
338
|
-
ISC © [Cristian Falcone](cristianfalcone.com)
|
|
418
|
+
ISC © [Cristian Falcone](https://cristianfalcone.com)
|
package/types.ts
CHANGED
|
@@ -2,13 +2,13 @@ declare module 'ajo' {
|
|
|
2
2
|
|
|
3
3
|
type Tag = keyof (HTMLElementTagNameMap & SVGElementTagNameMap)
|
|
4
4
|
|
|
5
|
-
type Type<TArguments extends
|
|
5
|
+
type Type<TArguments extends Args = {}, TTag extends Tag = 'div'> = Tag | Stateless<TArguments> | Stateful<TArguments, TTag>
|
|
6
6
|
|
|
7
|
-
type Component<TArguments extends
|
|
7
|
+
type Component<TArguments extends Args = {}> = Stateless<TArguments> | Stateful<TArguments>
|
|
8
8
|
|
|
9
|
-
type
|
|
9
|
+
type Args = Record<string, unknown>
|
|
10
10
|
|
|
11
|
-
type VNode<TTag extends Type,
|
|
11
|
+
type VNode<TTag extends Type, TArgs extends Args> = TArgs & {
|
|
12
12
|
nodeName: TTag,
|
|
13
13
|
children?: Children,
|
|
14
14
|
}
|
|
@@ -21,7 +21,7 @@ declare module 'ajo' {
|
|
|
21
21
|
? SVGElementTagNameMap[TTag]
|
|
22
22
|
: never
|
|
23
23
|
|
|
24
|
-
type
|
|
24
|
+
type SpecialAttrs<TElement> = {
|
|
25
25
|
key: unknown,
|
|
26
26
|
skip: boolean,
|
|
27
27
|
memo: unknown,
|
|
@@ -36,61 +36,52 @@ declare module 'ajo' {
|
|
|
36
36
|
[key: `attr:${string}`]: unknown
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
type Stateless<TArguments extends
|
|
39
|
+
type Stateless<TArguments extends Args = {}> = (args: TArguments) => Children
|
|
40
40
|
|
|
41
|
-
type Stateful<TArguments extends
|
|
42
|
-
(this: StatefulElement<TArguments, TTag>, args:
|
|
43
|
-
} & (TTag extends 'div' ? { is?: TTag } : { is: TTag }) & {
|
|
41
|
+
type Stateful<TArguments extends Args = {}, TTag extends Tag = 'div'> = {
|
|
42
|
+
(this: StatefulElement<TArguments, TTag>, args: StatefulArgs<TArguments, TTag>): Iterator<Children>
|
|
43
|
+
} & (TTag extends 'div' ? { is?: TTag } : { is: TTag }) & {
|
|
44
|
+
attrs?: Partial<PropSetter<TTag>> & Args,
|
|
45
|
+
args?: Partial<TArguments>,
|
|
46
|
+
src?: string,
|
|
47
|
+
fallback?: Children,
|
|
48
|
+
}
|
|
44
49
|
|
|
45
|
-
type
|
|
46
|
-
Partial<
|
|
50
|
+
type StatefulArgs<TArguments, TTag> =
|
|
51
|
+
Partial<SpecialAttrs<StatefulElement<TArguments, TTag>> & PropSetter<TTag>> &
|
|
47
52
|
AttrSetter &
|
|
48
53
|
TArguments
|
|
49
54
|
|
|
50
55
|
type StatefulElement<TArguments, TTag> = ElementType<TTag> & {
|
|
51
|
-
next: (fn?: (this: StatefulElement<TArguments, TTag>, args:
|
|
56
|
+
next: (fn?: (this: StatefulElement<TArguments, TTag>, args: StatefulArgs<TArguments, TTag>) => void) => void,
|
|
52
57
|
throw: (value?: unknown) => void,
|
|
53
58
|
return: () => void,
|
|
54
59
|
}
|
|
55
60
|
|
|
56
61
|
type IntrinsicElements = {
|
|
57
|
-
[TTag in Tag]: Partial<PropSetter<TTag> &
|
|
62
|
+
[TTag in Tag]: Partial<PropSetter<TTag> & SpecialAttrs<ElementType<TTag>>> & Args
|
|
58
63
|
}
|
|
59
64
|
|
|
60
65
|
type ElementChildrenAttribute = { children: Children }
|
|
61
66
|
|
|
67
|
+
type WithChildren<T extends Args = {}> = T & Partial<ElementChildrenAttribute>
|
|
68
|
+
|
|
62
69
|
function Fragment({ children }: ElementChildrenAttribute): typeof children
|
|
63
|
-
function h(tag: Type,
|
|
70
|
+
function h(tag: Type, attrs?: Args | null, ...children: Children[]): VNode<Type, Args>
|
|
64
71
|
function render(h: Children, el: ParentNode, child?: ChildNode, ref?: ChildNode): void
|
|
65
72
|
}
|
|
66
73
|
|
|
67
74
|
declare module 'ajo/context' {
|
|
68
|
-
function context<T>(fallback?: T):
|
|
69
|
-
|
|
75
|
+
function context<T>(fallback?: T): {
|
|
76
|
+
(): T
|
|
77
|
+
<V extends T>(value: V): V
|
|
78
|
+
}
|
|
79
|
+
function current(): import('ajo').StatefulElement<any, any> | null
|
|
70
80
|
}
|
|
71
81
|
|
|
72
82
|
declare module 'ajo/html' {
|
|
73
|
-
|
|
74
|
-
type Patch = {
|
|
75
|
-
id: string,
|
|
76
|
-
h: import('ajo').Children,
|
|
77
|
-
src?: string,
|
|
78
|
-
done: boolean,
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
type Hooks = {
|
|
82
|
-
alloc?: (parentId: string) => string,
|
|
83
|
-
placeholder?: (id: string, children: import('ajo').Children) => unknown,
|
|
84
|
-
push?: (patch: Patch) => void,
|
|
85
|
-
}
|
|
86
|
-
|
|
87
83
|
function render(h: import('ajo').Children): string
|
|
88
|
-
function html(h: import('ajo').Children
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
declare module 'ajo/stream' {
|
|
92
|
-
function stream(h: import('ajo').Children): AsyncIterableIterator<string>
|
|
93
|
-
function hydrate(patch: import('ajo/html').Patch): Promise<void>
|
|
84
|
+
function html(h: import('ajo').Children): IterableIterator<string>
|
|
94
85
|
}
|
|
95
86
|
|
|
96
87
|
declare namespace JSX {
|
package/dist/stream.cjs
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const a=require("./index.cjs"),m=require("./html.cjs"),y=async function*(o,n=""){const s=new Map([[n,0]]),t=new Set,i=[],r=(e=n)=>(s.set(e,(s.get(e)??0)+1),e?`${e}:${s.get(e)-1}`:String(s.get(e)-1)),h=(e,c)=>({nodeName:"div","data-ssr":e,children:c}),f=e=>{const c=Promise.resolve(`<script>window.$stream?.push(${JSON.stringify(e)})<\/script>`);t.add(c),c.then(u=>i.push(u)).finally(()=>t.delete(c))};for(const e of m.html(o,{alloc:r,placeholder:h,push:f}))yield e;for(;t.size||i.length;){for(;i.length;)yield i.shift();t.size&&await Promise.race(t)}},d=new Set;async function l({id:o,src:n,h:s}){const t=document.querySelector(`[data-ssr="${o}"]`);if(!t)return d.add({id:o,src:n,h:s});n?a.render(a.h((await import(n)).default,s),t):a.render(s,t);const i=o+":";for(const r of d)r.id.startsWith(i)&&(d.delete(r),l(r))}exports.hydrate=l;exports.stream=y;
|
package/dist/stream.js
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { render as l, h as m } from "./index.js";
|
|
2
|
-
import { html as u } from "./html.js";
|
|
3
|
-
const y = async function* (i, n = "") {
|
|
4
|
-
const s = /* @__PURE__ */ new Map([[n, 0]]), e = /* @__PURE__ */ new Set(), o = [], c = (t = n) => (s.set(t, (s.get(t) ?? 0) + 1), t ? `${t}:${s.get(t) - 1}` : String(s.get(t) - 1)), d = (t, r) => ({ nodeName: "div", "data-ssr": t, children: r }), f = (t) => {
|
|
5
|
-
const r = Promise.resolve(`<script>window.$stream?.push(${JSON.stringify(t)})<\/script>`);
|
|
6
|
-
e.add(r), r.then((h) => o.push(h)).finally(() => e.delete(r));
|
|
7
|
-
};
|
|
8
|
-
for (const t of u(i, { alloc: c, placeholder: d, push: f })) yield t;
|
|
9
|
-
for (; e.size || o.length; ) {
|
|
10
|
-
for (; o.length; ) yield o.shift();
|
|
11
|
-
e.size && await Promise.race(e);
|
|
12
|
-
}
|
|
13
|
-
}, a = /* @__PURE__ */ new Set();
|
|
14
|
-
async function p({ id: i, src: n, h: s }) {
|
|
15
|
-
const e = document.querySelector(`[data-ssr="${i}"]`);
|
|
16
|
-
if (!e) return a.add({ id: i, src: n, h: s });
|
|
17
|
-
n ? l(m((await import(n)).default, s), e) : l(s, e);
|
|
18
|
-
const o = i + ":";
|
|
19
|
-
for (const c of a) c.id.startsWith(o) && (a.delete(c), p(c));
|
|
20
|
-
}
|
|
21
|
-
export {
|
|
22
|
-
p as hydrate,
|
|
23
|
-
y as stream
|
|
24
|
-
};
|