sibujs 1.0.0-beta.1

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.
Files changed (302) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1630 -0
  3. package/dist/browser.cjs +815 -0
  4. package/dist/browser.d.cts +174 -0
  5. package/dist/browser.d.ts +174 -0
  6. package/dist/browser.js +458 -0
  7. package/dist/build.cjs +4970 -0
  8. package/dist/build.d.cts +630 -0
  9. package/dist/build.d.ts +630 -0
  10. package/dist/build.js +2478 -0
  11. package/dist/cdn.global.js +115 -0
  12. package/dist/chunk-27QC4FPL.js +67 -0
  13. package/dist/chunk-2ABBWCGC.js +65 -0
  14. package/dist/chunk-2MUNQYZ7.js +26 -0
  15. package/dist/chunk-2PSPKNUI.js +1711 -0
  16. package/dist/chunk-35CDLDX5.js +1758 -0
  17. package/dist/chunk-36MU4CFV.js +41 -0
  18. package/dist/chunk-3FIQOFI6.js +182 -0
  19. package/dist/chunk-3GHNC2BN.js +28 -0
  20. package/dist/chunk-3HLWWEPU.js +909 -0
  21. package/dist/chunk-3IVI3J54.js +252 -0
  22. package/dist/chunk-3KZ72WNW.js +944 -0
  23. package/dist/chunk-4AU64SQV.js +182 -0
  24. package/dist/chunk-4MOK7HAR.js +84 -0
  25. package/dist/chunk-4QK6FBDH.js +1429 -0
  26. package/dist/chunk-566Z7HXB.js +737 -0
  27. package/dist/chunk-5CRBB7XP.js +358 -0
  28. package/dist/chunk-5G67D3IZ.js +168 -0
  29. package/dist/chunk-5NCPAWBE.js +99 -0
  30. package/dist/chunk-5O2RKXR3.js +1444 -0
  31. package/dist/chunk-6BTBDO6A.js +633 -0
  32. package/dist/chunk-6IWEHW57.js +43 -0
  33. package/dist/chunk-6JGMNCD6.js +282 -0
  34. package/dist/chunk-6QRLJNXR.js +1425 -0
  35. package/dist/chunk-7HM5UE5T.js +270 -0
  36. package/dist/chunk-7JOLTGUH.js +58 -0
  37. package/dist/chunk-7MCWJCQK.js +909 -0
  38. package/dist/chunk-7RIIFP3E.js +1758 -0
  39. package/dist/chunk-7UASYN3G.js +254 -0
  40. package/dist/chunk-7W2WYHDI.js +741 -0
  41. package/dist/chunk-7Y35RDSJ.js +872 -0
  42. package/dist/chunk-A65GFJBL.js +65 -0
  43. package/dist/chunk-AD6ZIEDK.js +67 -0
  44. package/dist/chunk-AK5Y72F3.js +1426 -0
  45. package/dist/chunk-APOMMWH4.js +282 -0
  46. package/dist/chunk-ARZVTWIQ.js +1750 -0
  47. package/dist/chunk-AWWBM2BI.js +664 -0
  48. package/dist/chunk-AX5VEQTY.js +58 -0
  49. package/dist/chunk-AYTXVOW3.js +1708 -0
  50. package/dist/chunk-BG4A246G.js +1746 -0
  51. package/dist/chunk-BNFJJA2L.js +1425 -0
  52. package/dist/chunk-BPKPBVU5.js +59 -0
  53. package/dist/chunk-BPKPPSXC.js +282 -0
  54. package/dist/chunk-BPWKKK7F.js +1711 -0
  55. package/dist/chunk-CCKX6YTC.js +1735 -0
  56. package/dist/chunk-CIF5Z3MP.js +58 -0
  57. package/dist/chunk-CSXYU7IO.js +457 -0
  58. package/dist/chunk-D6JD4FDC.js +26 -0
  59. package/dist/chunk-E7NGA7X2.js +59 -0
  60. package/dist/chunk-EEPPJKAE.js +443 -0
  61. package/dist/chunk-EJMYGAGQ.js +717 -0
  62. package/dist/chunk-EL6Z5MDY.js +55 -0
  63. package/dist/chunk-EP7VRLEB.js +41 -0
  64. package/dist/chunk-ETMEC6FH.js +99 -0
  65. package/dist/chunk-EZ2WHYVL.js +65 -0
  66. package/dist/chunk-EZRVMSZK.js +67 -0
  67. package/dist/chunk-F2TRGINX.js +254 -0
  68. package/dist/chunk-F5JCIH3Q.js +642 -0
  69. package/dist/chunk-FGK3JKMN.js +909 -0
  70. package/dist/chunk-FQWPKSTD.js +1437 -0
  71. package/dist/chunk-FWHVLMCI.js +26 -0
  72. package/dist/chunk-GBEYQRO2.js +303 -0
  73. package/dist/chunk-GBLES3NK.js +248 -0
  74. package/dist/chunk-GQVGUQW6.js +1436 -0
  75. package/dist/chunk-HCV2T76T.js +457 -0
  76. package/dist/chunk-HS7ZKVPR.js +182 -0
  77. package/dist/chunk-HXDVV7HZ.js +909 -0
  78. package/dist/chunk-IB23VMO3.js +1746 -0
  79. package/dist/chunk-IEMZ7RTT.js +99 -0
  80. package/dist/chunk-IPGRSN42.js +1750 -0
  81. package/dist/chunk-IVMOK2QN.js +1750 -0
  82. package/dist/chunk-JCLGQO7T.js +443 -0
  83. package/dist/chunk-JDXL7KDB.js +1436 -0
  84. package/dist/chunk-JIIFW636.js +270 -0
  85. package/dist/chunk-JWGEEH7H.js +944 -0
  86. package/dist/chunk-K2BESAG7.js +1688 -0
  87. package/dist/chunk-K2U5YGF4.js +877 -0
  88. package/dist/chunk-K45FQ4Y4.js +175 -0
  89. package/dist/chunk-K7BPE427.js +1432 -0
  90. package/dist/chunk-KL3266RS.js +26 -0
  91. package/dist/chunk-KNN4P7DZ.js +84 -0
  92. package/dist/chunk-KP2DZH5Q.js +254 -0
  93. package/dist/chunk-KZHAJSQR.js +1636 -0
  94. package/dist/chunk-LBKGHMQV.js +1750 -0
  95. package/dist/chunk-LBTEPL7A.js +1731 -0
  96. package/dist/chunk-LEBBPTDB.js +1444 -0
  97. package/dist/chunk-LLH63WVQ.js +98 -0
  98. package/dist/chunk-LWVR2C4G.js +1711 -0
  99. package/dist/chunk-M3MDTVV2.js +896 -0
  100. package/dist/chunk-M5GNLDEO.js +303 -0
  101. package/dist/chunk-MFHVGKET.js +267 -0
  102. package/dist/chunk-MGWSG3PM.js +358 -0
  103. package/dist/chunk-MJNB47HB.js +19 -0
  104. package/dist/chunk-MLKGABMK.js +9 -0
  105. package/dist/chunk-MQWTY3JY.js +944 -0
  106. package/dist/chunk-MZZOQHNI.js +642 -0
  107. package/dist/chunk-NIHWGZS4.js +1426 -0
  108. package/dist/chunk-NSVVHQK5.js +41 -0
  109. package/dist/chunk-NVI2WE7D.js +443 -0
  110. package/dist/chunk-O7QBO3PH.js +58 -0
  111. package/dist/chunk-OAUPQBO2.js +270 -0
  112. package/dist/chunk-OB2LMD7C.js +297 -0
  113. package/dist/chunk-OHEYBWQU.js +58 -0
  114. package/dist/chunk-OI6OXUHJ.js +443 -0
  115. package/dist/chunk-OX2VMRMV.js +633 -0
  116. package/dist/chunk-P4FYE5TX.js +866 -0
  117. package/dist/chunk-P5KFWM4H.js +98 -0
  118. package/dist/chunk-PUMLE7RJ.js +1711 -0
  119. package/dist/chunk-Q4MFANBF.js +282 -0
  120. package/dist/chunk-QLEKZMMU.js +282 -0
  121. package/dist/chunk-RGGNGVO3.js +98 -0
  122. package/dist/chunk-RKJDRVV6.js +443 -0
  123. package/dist/chunk-S5BHU353.js +43 -0
  124. package/dist/chunk-SHQUSFH7.js +1426 -0
  125. package/dist/chunk-SMB4DBMD.js +182 -0
  126. package/dist/chunk-SNYHQP3D.js +743 -0
  127. package/dist/chunk-T24L3TBF.js +1717 -0
  128. package/dist/chunk-TAQNSOKT.js +692 -0
  129. package/dist/chunk-TDNY4SUA.js +41 -0
  130. package/dist/chunk-TNNF56IQ.js +1750 -0
  131. package/dist/chunk-TR7E6LYX.js +457 -0
  132. package/dist/chunk-URWUFH45.js +98 -0
  133. package/dist/chunk-UUSIH3XH.js +1429 -0
  134. package/dist/chunk-UYFNXLKR.js +1436 -0
  135. package/dist/chunk-V6F7KUWD.js +270 -0
  136. package/dist/chunk-VCZLXRMR.js +254 -0
  137. package/dist/chunk-VDHXSSBT.js +1426 -0
  138. package/dist/chunk-VM4QMKVK.js +254 -0
  139. package/dist/chunk-VWGYKYL2.js +737 -0
  140. package/dist/chunk-VX2OFBJN.js +1426 -0
  141. package/dist/chunk-VXVIE6DG.js +84 -0
  142. package/dist/chunk-W4OH7HG4.js +40 -0
  143. package/dist/chunk-WBVJX4GZ.js +98 -0
  144. package/dist/chunk-WDU2ZV4I.js +1426 -0
  145. package/dist/chunk-X6VUCICU.js +457 -0
  146. package/dist/chunk-XAY7FM7Y.js +618 -0
  147. package/dist/chunk-XJZ5Z2CM.js +642 -0
  148. package/dist/chunk-XKVFQTJJ.js +254 -0
  149. package/dist/chunk-XRLFASCY.js +22 -0
  150. package/dist/chunk-XYU6TZOW.js +182 -0
  151. package/dist/chunk-Y745CBVB.js +944 -0
  152. package/dist/chunk-YLBJSXYY.js +944 -0
  153. package/dist/chunk-YQJIKVPZ.js +1429 -0
  154. package/dist/chunk-YRM2VCZF.js +457 -0
  155. package/dist/chunk-YS33KBVJ.js +944 -0
  156. package/dist/chunk-Z27DZPDG.js +41 -0
  157. package/dist/chunk-ZXQ5NAEN.js +32 -0
  158. package/dist/contracts-B552GopR.d.cts +245 -0
  159. package/dist/contracts-B552GopR.d.ts +245 -0
  160. package/dist/contracts-Bg1ECISC.d.cts +245 -0
  161. package/dist/contracts-Bg1ECISC.d.ts +245 -0
  162. package/dist/contracts-CMriKJ6P.d.cts +245 -0
  163. package/dist/contracts-CMriKJ6P.d.ts +245 -0
  164. package/dist/contracts-DOrhwbke.d.cts +245 -0
  165. package/dist/contracts-DOrhwbke.d.ts +245 -0
  166. package/dist/data.cjs +1373 -0
  167. package/dist/data.d.cts +434 -0
  168. package/dist/data.d.ts +434 -0
  169. package/dist/data.js +945 -0
  170. package/dist/devtools.cjs +1357 -0
  171. package/dist/devtools.d.cts +473 -0
  172. package/dist/devtools.d.ts +473 -0
  173. package/dist/devtools.js +1084 -0
  174. package/dist/ecosystem.cjs +1046 -0
  175. package/dist/ecosystem.d.cts +247 -0
  176. package/dist/ecosystem.d.ts +247 -0
  177. package/dist/ecosystem.js +369 -0
  178. package/dist/extras.cjs +8457 -0
  179. package/dist/extras.d.cts +2356 -0
  180. package/dist/extras.d.ts +2356 -0
  181. package/dist/extras.js +5152 -0
  182. package/dist/index.cjs +2648 -0
  183. package/dist/index.d.cts +869 -0
  184. package/dist/index.d.ts +869 -0
  185. package/dist/index.js +386 -0
  186. package/dist/motion.cjs +604 -0
  187. package/dist/motion.d.cts +146 -0
  188. package/dist/motion.d.ts +146 -0
  189. package/dist/motion.js +346 -0
  190. package/dist/patterns.cjs +815 -0
  191. package/dist/patterns.d.cts +163 -0
  192. package/dist/patterns.d.ts +163 -0
  193. package/dist/patterns.js +296 -0
  194. package/dist/performance.cjs +927 -0
  195. package/dist/performance.d.cts +416 -0
  196. package/dist/performance.d.ts +416 -0
  197. package/dist/performance.js +654 -0
  198. package/dist/plugins.cjs +2487 -0
  199. package/dist/plugins.d.cts +393 -0
  200. package/dist/plugins.d.ts +393 -0
  201. package/dist/plugins.js +1504 -0
  202. package/dist/signal-BnWpq6WB.d.cts +5 -0
  203. package/dist/signal-BnWpq6WB.d.ts +5 -0
  204. package/dist/src/components/ErrorBoundary.d.ts +15 -0
  205. package/dist/src/components/ErrorBoundary.js +119 -0
  206. package/dist/src/core/catch.d.ts +11 -0
  207. package/dist/src/core/catch.js +28 -0
  208. package/dist/src/core/each.d.ts +13 -0
  209. package/dist/src/core/each.js +68 -0
  210. package/dist/src/core/for.d.ts +12 -0
  211. package/dist/src/core/for.js +67 -0
  212. package/dist/src/core/html.d.ts +137 -0
  213. package/dist/src/core/html.js +155 -0
  214. package/dist/src/core/htmlIf.d.ts +11 -0
  215. package/dist/src/core/htmlIf.js +18 -0
  216. package/dist/src/core/lazy.d.ts +7 -0
  217. package/dist/src/core/lazy.js +16 -0
  218. package/dist/src/core/mount.d.ts +7 -0
  219. package/dist/src/core/mount.js +12 -0
  220. package/dist/src/core/slots.d.ts +3 -0
  221. package/dist/src/core/slots.js +3 -0
  222. package/dist/src/core/suspense.d.ts +10 -0
  223. package/dist/src/core/suspense.js +33 -0
  224. package/dist/src/core/tagFactory.d.ts +13 -0
  225. package/dist/src/core/tagFactory.js +86 -0
  226. package/dist/src/core/test.d.ts +11 -0
  227. package/dist/src/core/test.js +28 -0
  228. package/dist/src/core/types.d.ts +2 -0
  229. package/dist/src/core/types.js +1 -0
  230. package/dist/src/core/useComputed.d.ts +6 -0
  231. package/dist/src/core/useComputed.js +30 -0
  232. package/dist/src/core/useEffect.d.ts +6 -0
  233. package/dist/src/core/useEffect.js +23 -0
  234. package/dist/src/core/useState.d.ts +10 -0
  235. package/dist/src/core/useState.js +34 -0
  236. package/dist/src/core/useStore.d.ts +19 -0
  237. package/dist/src/core/useStore.js +53 -0
  238. package/dist/src/core/useWatch.d.ts +8 -0
  239. package/dist/src/core/useWatch.js +23 -0
  240. package/dist/src/plugins/i18n.d.ts +6 -0
  241. package/dist/src/plugins/i18n.js +16 -0
  242. package/dist/src/plugins/router.d.ts +188 -0
  243. package/dist/src/plugins/router.js +1178 -0
  244. package/dist/src/reactivity/bindAttribute.d.ts +5 -0
  245. package/dist/src/reactivity/bindAttribute.js +31 -0
  246. package/dist/src/reactivity/bindChildNode.d.ts +10 -0
  247. package/dist/src/reactivity/bindChildNode.js +46 -0
  248. package/dist/src/reactivity/bindTextNode.d.ts +10 -0
  249. package/dist/src/reactivity/bindTextNode.js +27 -0
  250. package/dist/src/reactivity/signal.d.ts +3 -0
  251. package/dist/src/reactivity/signal.js +1 -0
  252. package/dist/src/reactivity/track.d.ts +18 -0
  253. package/dist/src/reactivity/track.js +73 -0
  254. package/dist/src/reactivity/useComputed.d.ts +6 -0
  255. package/dist/src/reactivity/useComputed.js +30 -0
  256. package/dist/src/reactivity/useEffect.d.ts +6 -0
  257. package/dist/src/reactivity/useEffect.js +23 -0
  258. package/dist/src/reactivity/useState.d.ts +10 -0
  259. package/dist/src/reactivity/useState.js +34 -0
  260. package/dist/src/reactivity/useStore.d.ts +19 -0
  261. package/dist/src/reactivity/useStore.js +53 -0
  262. package/dist/src/reactivity/useWatch.d.ts +8 -0
  263. package/dist/src/reactivity/useWatch.js +23 -0
  264. package/dist/src/utils/sanitize.d.ts +1 -0
  265. package/dist/src/utils/sanitize.js +8 -0
  266. package/dist/ssr-27FOM46T.js +35 -0
  267. package/dist/ssr-GFUTTSJD.js +22 -0
  268. package/dist/ssr-K7DCR6BZ.js +35 -0
  269. package/dist/ssr-O6LFMRFP.js +35 -0
  270. package/dist/ssr-QZEVGMMK.js +35 -0
  271. package/dist/ssr-SGVBCAGC.js +35 -0
  272. package/dist/ssr-UB2IXCYX.js +35 -0
  273. package/dist/ssr-XBZQNV4O.js +22 -0
  274. package/dist/ssr-Y76FSXDU.js +35 -0
  275. package/dist/ssr-YQJ4AYBD.js +35 -0
  276. package/dist/ssr.cjs +1757 -0
  277. package/dist/ssr.d.cts +478 -0
  278. package/dist/ssr.d.ts +478 -0
  279. package/dist/ssr.js +743 -0
  280. package/dist/tagFactory-CZPO4RXF.d.cts +34 -0
  281. package/dist/tagFactory-CZPO4RXF.d.ts +34 -0
  282. package/dist/tagFactory-CgImPVMY.d.cts +22 -0
  283. package/dist/tagFactory-CgImPVMY.d.ts +22 -0
  284. package/dist/tagFactory-Cw1iv5if.d.cts +22 -0
  285. package/dist/tagFactory-Cw1iv5if.d.ts +22 -0
  286. package/dist/tagFactory-DeAXq9ef.d.cts +30 -0
  287. package/dist/tagFactory-DeAXq9ef.d.ts +30 -0
  288. package/dist/tagFactory-SkY0a7L1.d.cts +22 -0
  289. package/dist/tagFactory-SkY0a7L1.d.ts +22 -0
  290. package/dist/testing.cjs +1919 -0
  291. package/dist/testing.d.cts +491 -0
  292. package/dist/testing.d.ts +491 -0
  293. package/dist/testing.js +1862 -0
  294. package/dist/ui.cjs +1497 -0
  295. package/dist/ui.d.cts +264 -0
  296. package/dist/ui.d.ts +264 -0
  297. package/dist/ui.js +900 -0
  298. package/dist/widgets.cjs +919 -0
  299. package/dist/widgets.d.cts +165 -0
  300. package/dist/widgets.d.ts +165 -0
  301. package/dist/widgets.js +545 -0
  302. package/package.json +134 -0
package/README.md ADDED
@@ -0,0 +1,1630 @@
1
+ # SibuJS
2
+
3
+ A function-based frontend framework with fine-grained reactivity, direct DOM rendering, and zero compilation. No virtual DOM. No JSX. No magic.
4
+
5
+ Named after **Sibu** -- the creator deity of the Bribri and Cabecar indigenous peoples of Costa Rica -- representing clarity, harmony, and simplicity in creation.
6
+
7
+ ```
8
+ npm install sibujs
9
+ ```
10
+
11
+ ---
12
+
13
+ ## Quick Start
14
+
15
+ ```ts
16
+ import { html, signal, mount } from "sibujs";
17
+
18
+ function Counter() {
19
+ const [count, setCount] = signal(0);
20
+
21
+ return html`<div>
22
+ <h1>${() => `Count: ${count()}`}</h1>
23
+ <button on:click=${() => setCount(c => c + 1)}>Increment</button>
24
+ </div>`;
25
+ }
26
+
27
+ const { unmount } = mount(Counter, document.getElementById("app"));
28
+ ```
29
+
30
+ Components are plain functions that return DOM elements. State is a getter/setter pair. Reactive nodes are wrapped in `() =>`. That is the entire mental model.
31
+
32
+ ---
33
+
34
+ ## Three Ways to Write Components
35
+
36
+ Sibu offers three authoring styles. All produce identical DOM elements and are fully interoperable -- mix them freely.
37
+
38
+ ### 1. `html` Tagged Template
39
+
40
+ HTML-like syntax using tagged template literals. No compiler needed -- it's a runtime function.
41
+
42
+ ```ts
43
+ import { html, signal } from "sibujs";
44
+
45
+ function Counter() {
46
+ const [count, setCount] = signal(0);
47
+
48
+ return html`<div class="counter">
49
+ <h1>${() => `Count: ${count()}`}</h1>
50
+ <button on:click=${() => setCount(c => c + 1)}>Increment</button>
51
+ <button on:click=${() => setCount(0)}>Reset</button>
52
+ </div>`;
53
+ }
54
+ ```
55
+
56
+ **Best for:** Most components. Familiar HTML syntax, minimal nesting, easy to scan.
57
+
58
+ ### 2. Positional Shorthand
59
+
60
+ Concise function calls with optional class and children arguments.
61
+
62
+ ```ts
63
+ import { div, h1, button, signal } from "sibujs";
64
+
65
+ function Counter() {
66
+ const [count, setCount] = signal(0);
67
+
68
+ return div("counter", [
69
+ h1(() => `Count: ${count()}`),
70
+ button({ nodes: "Increment", on: { click: () => setCount(c => c + 1) } }),
71
+ button({ nodes: "Reset", on: { click: () => setCount(0) } }),
72
+ ]);
73
+ }
74
+ ```
75
+
76
+ **Signatures:**
77
+
78
+ - `div("Hello")` -- text only
79
+ - `div([child1, child2])` -- children only
80
+ - `div("my-class", "Hello")` -- class + text
81
+ - `div("my-class", [child1, child2])` -- class + children
82
+
83
+ **Best for:** Quick layouts where you just need class + children, no events.
84
+
85
+ ### 3. Full Props Object (original API)
86
+
87
+ Maximum control with explicit props object containing all configuration.
88
+
89
+ ```ts
90
+ import { div, h1, button, signal } from "sibujs";
91
+
92
+ function Counter() {
93
+ const [count, setCount] = signal(0);
94
+
95
+ return div({
96
+ class: "counter",
97
+ nodes: [
98
+ h1({ nodes: () => `Count: ${count()}` }),
99
+ button({
100
+ nodes: "Increment",
101
+ on: { click: () => setCount(c => c + 1) },
102
+ }),
103
+ button({
104
+ nodes: "Reset",
105
+ on: { click: () => setCount(0) },
106
+ }),
107
+ ],
108
+ });
109
+ }
110
+ ```
111
+
112
+ **Best for:** Complex elements with refs, styles, custom attributes, or dynamic props.
113
+
114
+ ### Mixing Styles
115
+
116
+ All three styles produce the same DOM elements, so you can combine them:
117
+
118
+ ```ts
119
+ import { html, div, button, signal } from "sibujs";
120
+
121
+ function App() {
122
+ const [count, setCount] = signal(0);
123
+
124
+ return html`<div class="app">
125
+ <h1>My App</h1>
126
+ ${div("content", [
127
+ html`<p>Count: ${() => count()}</p>`,
128
+ button({ nodes: "Click", on: { click: () => setCount(c => c + 1) } }),
129
+ ])}
130
+ </div>`;
131
+ }
132
+ ```
133
+
134
+ ### Performance by Authoring Style
135
+
136
+ All three styles produce the same DOM output, but their runtime cost differs:
137
+
138
+ | Style | First render | Subsequent renders | With Vite plugin |
139
+ | ----------------------------------------- | ---------------------------------------------- | ---------------------- | -------------------------------------------- |
140
+ | **Props Object / Positional** | Fastest — direct function calls, zero parsing | Fastest | No change needed |
141
+ | **`html\`\`` with build step** | Same as above — compiled to direct calls | Same as above | `compileTemplates: true` (default in prod) |
142
+ | **`html\`\`` without build step** | ~1.5x slower — runtime parser runs once | Same as above (cached) | N/A |
143
+
144
+ The `html` tagged template parser caches its output per call site using a `WeakMap` keyed by the template's static strings identity. This means:
145
+
146
+ - **First call** at a given source location pays the parsing cost (~1.5x overhead)
147
+ - **Every subsequent call** at the same location skips parsing entirely and replays the cached structure with fresh expression values
148
+
149
+ With the Vite plugin, even the first-call cost disappears.
150
+
151
+ ### Compiling Templates (Build-Time Optimization)
152
+
153
+ The Sibu Vite plugin includes a **template compiler** that transforms `html\`...\`` tagged templates into direct function calls at build time. The runtime parser is never loaded — the output is identical to writing Props Object code by hand.
154
+
155
+ **Setup:**
156
+
157
+ ```ts
158
+ // vite.config.ts
159
+ import { sibuVitePlugin } from "sibujs/build";
160
+
161
+ export default {
162
+ plugins: [
163
+ sibuVitePlugin()
164
+ // compileTemplates is enabled by default in production builds
165
+ ]
166
+ };
167
+ ```
168
+
169
+ **What it does:**
170
+
171
+ ```ts
172
+ // Your source code:
173
+ const el = html`<div class=${cls}>
174
+ <span>${() => count()}</span>
175
+ <button on:click=${handler}>Click</button>
176
+ </div>`;
177
+
178
+ // After compilation (production build):
179
+ const el = ((v) => div({
180
+ class: v[0],
181
+ nodes: [
182
+ span({ nodes: v[1] }),
183
+ button({ on: { click: v[2] }, nodes: "Click" })
184
+ ]
185
+ }))([cls, () => count(), handler]);
186
+ ```
187
+
188
+ The compiler handles all template features: static/dynamic attributes, event handlers (`on:click`), expression children, nested elements, self-closing/void elements, and SVG.
189
+
190
+ **Plugin options:**
191
+
192
+ ```ts
193
+ sibuVitePlugin({
194
+ compileTemplates: true, // Compile html`` to direct calls (default: true in prod, false in dev)
195
+ staticOptimize: true, // Convert fully-static tag calls to template cloning (default: true in prod)
196
+ pureAnnotations: true, // Add /*#__PURE__*/ for tree-shaking (default: true)
197
+ hmr: true, // HMR support for Sibu components (default: true)
198
+ devMode: false, // Dev helpers: __SIBU_DEV__ flag, debug logging (default: auto from NODE_ENV)
199
+ })
200
+ ```
201
+
202
+ **Without a build step:** The runtime `html` parser still works. Templates are parsed once per call site and cached via `WeakMap` — subsequent renders at the same source location skip parsing entirely. The ~1.5x overhead only applies to the very first render of each component.
203
+
204
+ **Recommendation:** Use whichever style reads best for your team. If you use Vite (or any build step), the `html` style has zero performance penalty. Without a build step, the overhead is only on first render per component and is negligible for most applications.
205
+
206
+ ---
207
+
208
+ ## Why Sibu Instead of React, Vue, Svelte, or Solid
209
+
210
+ Every mainstream framework makes a set of tradeoffs. Sibu makes different ones.
211
+
212
+ ### vs. React
213
+
214
+ React uses a virtual DOM. Every state change re-executes the entire component function, diffs a virtual tree against the previous one, and patches the real DOM. This is conceptually simple but inherently wasteful -- most of the tree hasn't changed.
215
+
216
+ Sibu has no virtual DOM. When `setCount` is called, only the text node displaying `count()` updates. The `div`, the `h1`, the `button` -- none of them are re-evaluated or diffed. They were created once and they persist. This is not an optimization layered on top; it is the fundamental architecture.
217
+
218
+ React also requires JSX, which requires a compiler. Sibu runs as plain TypeScript or JavaScript. No Babel plugin. No compiler transform. The code you write is the code that runs.
219
+
220
+ | | React | Sibu |
221
+ | ---------------------- | ------------------------------- | ------------------------ |
222
+ | Rendering model | Virtual DOM diffing | Direct DOM, signal-based |
223
+ | Compilation | Required (JSX) | None |
224
+ | Component re-execution | Entire function on every render | Never -- created once |
225
+ | Granularity of updates | Component-level | Node-level |
226
+ | Bundle overhead | ~45 KB min (react + react-dom) | Core only |
227
+
228
+ ### vs. Vue
229
+
230
+ Vue's template compiler generates optimized render functions behind the scenes. Its reactivity system (Proxy-based in Vue 3) is powerful but requires understanding refs, reactive objects, `computed`, `watch`, `toRefs`, `unref`, and the distinction between `.value` access and template auto-unwrapping.
231
+
232
+ Sibu's reactivity is simpler. There is one primitive: `signal` returns `[getter, setter]`. You call the getter to read, the setter to write. Dependencies are tracked automatically. There is no `.value`, no `ref()` vs `reactive()`, no unwrapping rules.
233
+
234
+ Vue templates are a separate language with their own directives (`v-if`, `v-for`, `v-bind`, `v-model`, `v-slot`). In Sibu, all of these are plain functions (`when`, `each`, reactive props, slots-as-functions).
235
+
236
+ | | Vue | Sibu |
237
+ | ---------------------- | --------------------------------------------- | ------------------------------- |
238
+ | Template language | Custom (SFCs, directives) | HTML tagged templates (runtime) |
239
+ | Reactivity API surface | ref, reactive, computed, watch, toRefs, unref | signal, derived, watch |
240
+ | Build requirement | Vite/Vue CLI for SFCs | None |
241
+ | Learning curve | Moderate (Options + Composition API) | Minimal (functions + signals) |
242
+
243
+ ### vs. Svelte
244
+
245
+ Svelte shifts work to compile time: it analyzes `.svelte` files and generates imperative DOM operations. The result is fast and small, but you must use the Svelte compiler, Svelte's file format, and Svelte's reactivity syntax (`$:`, `$state`, etc.).
246
+
247
+ Sibu achieves similar fine-grained DOM updates at runtime, without a compiler. The tradeoff is that Svelte can optimize away more framework code at build time. The benefit is that Sibu is just TypeScript -- your existing tooling, type checking, and editor support work without any Svelte-specific plugins.
248
+
249
+ | | Svelte | Sibu |
250
+ | ---------------------- | ----------------------------- | ---------------------- |
251
+ | Compilation | Required (.svelte files) | None |
252
+ | File format | Custom (.svelte) | Standard .ts / .js |
253
+ | Reactivity | Compiler-analyzed ($:, runes) | Runtime signals |
254
+ | Editor support | Requires Svelte plugin | Standard TypeScript |
255
+ | DOM update granularity | Fine-grained (compiled) | Fine-grained (runtime) |
256
+
257
+ ### vs. Solid
258
+
259
+ Solid is the closest comparison. Both use fine-grained reactivity with signals. Both skip the virtual DOM. Both update at the node level.
260
+
261
+ The key differences:
262
+
263
+ 1. **No compiler.** Solid strongly recommends JSX with its Babel plugin for optimal performance. Sibu's `html` tagged template gives you familiar HTML syntax without any build step.
264
+ 2. **API style.** Solid uses `createSignal`, `createEffect`, `createMemo` and JSX. Sibu uses `signal`, `effect`, `derived` and `html` templates. If you prefer a runtime-only approach with zero tooling, Sibu is a more natural fit.
265
+ 3. **Architecture.** Sibu provides a disposal system (`dispose`/`registerDisposer`), an explicit `mount`/`unmount` lifecycle, and a modular package split (core / extras / plugins / build). Solid bundles more into its core and relies on its compiler for tree shaking.
266
+
267
+ | | Solid | Sibu |
268
+ | --------------------- | --------------------------- | ---------------------------------- |
269
+ | Recommended authoring | JSX (compiled) | `html` tagged template (runtime) |
270
+ | Signal API | createSignal / createEffect | signal / effect |
271
+ | Disposal model | Owner tree (automatic) | Explicit dispose + WeakMap |
272
+ | Package structure | Monolithic core | Modular (core / extras / plugins) |
273
+
274
+ ### The Core Principle
275
+
276
+ Most frameworks ask you to describe the UI and then figure out how to update the DOM efficiently. Sibu asks you to build the DOM directly and tell it which parts are reactive. The result is code that is predictable, inspectable, and close to the metal.
277
+
278
+ ---
279
+
280
+ ## Architecture
281
+
282
+ ### Reactivity
283
+
284
+ The reactivity system is three functions: `track`, `recordDependency`, and `notifySubscribers`.
285
+
286
+ When a signal's getter is called, `recordDependency` links the signal to the current subscriber. When a signal's setter is called, `notifySubscribers` calls every subscriber. `track` runs a function while setting up the subscriber context. That is the entire reactivity engine.
287
+
288
+ ```
289
+ signal(0)
290
+ |
291
+ +--> getter: calls recordDependency(signal)
292
+ | |
293
+ | +--> links signal <-> current subscriber (set by track)
294
+ |
295
+ +--> setter: calls notifySubscribers(signal)
296
+ |
297
+ +--> runs every subscriber linked to signal
298
+ ```
299
+
300
+ Dependencies are tracked via `WeakMap<Signal, Set<Subscriber>>`, so unused signals and subscribers are garbage collected automatically.
301
+
302
+ ### The `html` Tagged Template
303
+
304
+ The `html` function is a runtime tagged template literal that parses HTML-like syntax and creates DOM elements via Sibu's tag factories. No compiler, no build step -- just a function call.
305
+
306
+ ```ts
307
+ import { html, signal } from "sibujs";
308
+
309
+ html`<div id="app" class="container">
310
+ <h1>${() => `Hello, ${name()}`}</h1>
311
+ <button on:click=${handler}>Click me</button>
312
+ <input type="text" value=${() => text()} />
313
+ </div>`;
314
+ ```
315
+
316
+ **Supported features:**
317
+
318
+ - All HTML and SVG tags
319
+ - Static attributes: `class="foo"`
320
+ - Dynamic attributes: `class=${() => "active"}`, `id=${myId}`
321
+ - Event handlers: `on:click=${handler}`, `on:input=${handler}`
322
+ - Reactive text children: `${() => count()}`
323
+ - Element children: `${MyComponent()}`, `${each(...)}`, `${when(...)}`
324
+ - Self-closing tags: `<br />`, `<img src="..." />`
325
+ - Void elements: `<br>`, `<input>`, `<hr>`
326
+
327
+ ### Reactive Props (full props API)
328
+
329
+ When using the tag factory API directly, every prop can be reactive:
330
+
331
+ ```ts
332
+ import { div } from "sibujs";
333
+
334
+ div({
335
+ // Static class
336
+ class: "card",
337
+
338
+ // Reactive class (string)
339
+ class: () => isActive() ? "card active" : "card",
340
+
341
+ // Conditional class (object)
342
+ class: { card: true, active: isActive, bold: () => isBold() },
343
+
344
+ // Static style
345
+ style: { color: "red", fontSize: "14px" },
346
+
347
+ // Reactive style (per-property)
348
+ style: { color: () => theme().primary },
349
+
350
+ // Reactive nodes
351
+ nodes: () => `Count: ${count()}`,
352
+
353
+ // Events
354
+ on: { click: handleClick, mouseover: handleHover },
355
+
356
+ // Ref
357
+ ref: myRef,
358
+
359
+ // Any other attribute
360
+ "data-testid": "my-card",
361
+ disabled: () => isDisabled(),
362
+ });
363
+ ```
364
+
365
+ ### Disposal
366
+
367
+ Reactive bindings (class, style, attribute, node) register teardown functions on their DOM nodes via `registerDisposer`. When you call `dispose(node)`, it walks the subtree depth-first and tears down every binding, preventing memory leaks.
368
+
369
+ `mount()` returns an `unmount` function that calls `dispose` and removes the node:
370
+
371
+ ```ts
372
+ const { node, unmount } = mount(App, document.getElementById("root"));
373
+
374
+ // Later:
375
+ unmount(); // disposes all reactive bindings + removes from DOM
376
+ ```
377
+
378
+ ---
379
+
380
+ ## Core API
381
+
382
+ ### State and Reactivity
383
+
384
+ ```ts
385
+ import { signal, effect, derived, watch, batch } from "sibujs";
386
+
387
+ // Reactive state
388
+ const [count, setCount] = signal(0);
389
+ count(); // read (tracks dependency)
390
+ setCount(5); // write (notifies subscribers)
391
+ setCount(c => c + 1); // updater function
392
+
393
+ // Derived state
394
+ const doubled = derived(() => count() * 2);
395
+ doubled(); // always 2x count, auto-updates
396
+
397
+ // Side effects
398
+ const cleanup = effect(() => {
399
+ console.log("count changed:", count());
400
+ });
401
+ cleanup(); // stop watching
402
+
403
+ // Watch with old/new values
404
+ const stop = watch(count, (newVal, oldVal) => {
405
+ console.log(`${oldVal} -> ${newVal}`);
406
+ });
407
+
408
+ // Batch multiple updates into one notification
409
+ batch(() => {
410
+ setCount(10);
411
+ setName("Alice");
412
+ }); // subscribers notified once
413
+ ```
414
+
415
+ ### Additional Signals & Utilities
416
+
417
+ ```ts
418
+ import { ref, memo, memoFn, array, deepSignal, store } from "sibujs";
419
+
420
+ // Mutable ref (non-reactive — changing .current does NOT trigger updates)
421
+ // Use for imperative access (focus, measure). For reactive element tracking, use signal instead.
422
+ const ref = ref<HTMLElement>();
423
+ ref.current; // the element
424
+
425
+ // Memoized value (alias for derived)
426
+ const expensive = memo(() => heavyComputation(data()));
427
+
428
+ // Memoized callback
429
+ const handler = memoFn(() => (e: Event) => process(e, count()));
430
+
431
+ // Reactive array with mutation methods
432
+ const [items, actions] = array<string>(["a", "b"]);
433
+ actions.push("c");
434
+ actions.removeWhere(item => item === "a");
435
+ actions.sort((a, b) => a.localeCompare(b));
436
+
437
+ // Deep equality state (objects/arrays)
438
+ const [config, setConfig] = deepSignal({ theme: "dark", lang: "en" });
439
+
440
+ // Shared store with actions
441
+ const [store, { setState, reset, subscribe }] = store({ count: 0, name: "" });
442
+ ```
443
+
444
+ ### Rendering
445
+
446
+ ```ts
447
+ import { html, mount, each, when, match, show, Fragment, Portal, signal } from "sibujs";
448
+
449
+ // Mount with unmount support
450
+ const { unmount } = mount(App, document.getElementById("root"));
451
+
452
+ // Keyed list rendering with LIS-based diffing
453
+ html`<ul>
454
+ ${each(
455
+ () => items(),
456
+ (item, i) => html`<li>${item.name}</li>`,
457
+ { key: item => item.id }
458
+ )}
459
+ </ul>`;
460
+
461
+ // Conditional rendering (swaps DOM nodes)
462
+ html`<div>
463
+ ${when(
464
+ () => isLoggedIn(),
465
+ () => Dashboard(),
466
+ () => LoginForm()
467
+ )}
468
+ </div>`;
469
+
470
+ // Toggle visibility (keeps node, toggles display)
471
+ show(() => isVisible(), myElement);
472
+
473
+ // Pattern matching
474
+ html`<div>
475
+ ${match(
476
+ () => status(),
477
+ {
478
+ loading: () => Spinner(),
479
+ error: () => ErrorView(),
480
+ success: () => Content(),
481
+ },
482
+ () => html`<div>Unknown</div>`
483
+ )}
484
+ </div>`;
485
+
486
+ // Fragment (no wrapper element)
487
+ Fragment([child1, child2, child3]);
488
+
489
+ // Portal (render into a different container)
490
+ Portal(() => Modal(), document.getElementById("modal-root"));
491
+ ```
492
+
493
+ ### Dynamic Components
494
+
495
+ ```ts
496
+ import { html, DynamicComponent, registerComponent, signal } from "sibujs";
497
+
498
+ // Register components by name
499
+ registerComponent("greeting", () => html`<div>Hello!</div>`);
500
+ registerComponent("farewell", () => html`<div>Goodbye!</div>`);
501
+
502
+ // Reactively switch between components
503
+ const [view, setView] = signal("greeting");
504
+ DynamicComponent(() => view()); // renders "greeting" component
505
+ setView("farewell"); // swaps to "farewell" component
506
+
507
+ // Or pass a component function directly
508
+ DynamicComponent(() => view() === "admin" ? AdminPanel : UserPanel);
509
+ ```
510
+
511
+ ### Error Handling
512
+
513
+ ```ts
514
+ import { catchError, catchErrorAsync, setGlobalErrorHandler } from "sibujs";
515
+
516
+ // Wrap sync functions
517
+ const result = catchError(
518
+ () => JSON.parse(input),
519
+ (err, context) => console.error(`${context} error:`, err),
520
+ );
521
+
522
+ // Wrap async functions
523
+ const data = await catchErrorAsync(
524
+ () => fetch("/api/data").then(r => r.json()),
525
+ (err) => showError(err),
526
+ );
527
+
528
+ // Global fallback handler
529
+ setGlobalErrorHandler((err, context) => {
530
+ reportToSentry(err);
531
+ });
532
+ ```
533
+
534
+ ### Loading Component
535
+
536
+ ```ts
537
+ import { Loading } from "sibujs";
538
+
539
+ Loading(); // default spinner
540
+ Loading({ text: "Loading..." }); // with text
541
+ Loading({ variant: "dots" }); // dots animation
542
+ Loading({ size: "lg", text: "Please wait" }); // large with text
543
+ ```
544
+
545
+ ### Dynamic Attribute Binding
546
+
547
+ ```ts
548
+ import { html, bindDynamic, signal } from "sibujs";
549
+
550
+ const el = html`<div>Hover me</div>` as HTMLElement;
551
+
552
+ // Both attribute name and value can be reactive
553
+ const [attr, setAttr] = signal("title");
554
+ const [value, setValue] = signal("Tooltip text");
555
+ const teardown = bindDynamic(el, () => attr(), () => value());
556
+
557
+ setAttr("aria-label"); // old "title" removed, new "aria-label" set
558
+ teardown(); // stops tracking and removes the attribute
559
+ ```
560
+
561
+ ### Lazy Loading and Suspense
562
+
563
+ ```ts
564
+ import { html, lazy, Suspense } from "sibujs";
565
+
566
+ const LazyDashboard = lazy(() => import("./Dashboard"));
567
+
568
+ Suspense({
569
+ nodes: () => LazyDashboard(),
570
+ fallback: () => html`<div>Loading...</div>`,
571
+ });
572
+ ```
573
+
574
+ ### Components and Composition
575
+
576
+ ```ts
577
+ import { html, getSlot, context, ErrorBoundary } from "sibujs";
578
+ import type { Slots } from "sibujs";
579
+
580
+ // Slots (named functions)
581
+ function Card({ slots }: { slots?: Slots }) {
582
+ return html`<div class="card">
583
+ <div class="card-header">${getSlot(slots, "header")?.() ?? ""}</div>
584
+ <div class="card-body">${getSlot(slots, "default")?.() ?? ""}</div>
585
+ </div>`;
586
+ }
587
+
588
+ Card({
589
+ slots: {
590
+ header: () => html`<h2>Title</h2>`,
591
+ default: () => html`<p>Body content</p>`,
592
+ },
593
+ });
594
+
595
+ // Context (dependency injection)
596
+ const ThemeCtx = context("light");
597
+ ThemeCtx.provide("dark");
598
+ const theme = ThemeCtx.use(); // "dark"
599
+
600
+ // Error boundaries
601
+ ErrorBoundary({
602
+ nodes: RiskyComponent(),
603
+ fallback: (err, retry) => html`<div>
604
+ <p>Error: ${err.message}</p>
605
+ <button on:click=${retry}>Retry</button>
606
+ </div>`,
607
+ });
608
+ ```
609
+
610
+ ### Lifecycle
611
+
612
+ ```ts
613
+ import { html, onMount, onUnmount, dispose } from "sibujs";
614
+
615
+ function MyComponent() {
616
+ const el = html`<div>Hello</div>`;
617
+
618
+ onMount(() => {
619
+ console.log("mounted");
620
+ return () => console.log("cleanup on unmount");
621
+ }, el);
622
+
623
+ onUnmount(() => console.log("removed"), el);
624
+
625
+ return el;
626
+ }
627
+
628
+ // Manual disposal of reactive bindings
629
+ dispose(someElement); // tears down element + all descendants
630
+ ```
631
+
632
+ ---
633
+
634
+ ## Plugins (`sibu/plugins`)
635
+
636
+ ### Router
637
+
638
+ Full client-side router with history/hash modes, guards, nested routes, lazy loading, transitions, and SSR support.
639
+
640
+ ```ts
641
+ import { html, mount } from "sibujs";
642
+ import { createRouter, setRoutes, navigate, Route, RouterLink, lazy } from "sibujs/plugins";
643
+
644
+ // Define routes
645
+ setRoutes([
646
+ { path: "/", component: Home },
647
+ { path: "/about", component: About },
648
+ {
649
+ path: "/dashboard",
650
+ component: Dashboard,
651
+ guard: () => isLoggedIn(),
652
+ redirectTo: "/login",
653
+ children: [
654
+ { path: "settings", component: Settings },
655
+ ],
656
+ },
657
+ {
658
+ path: "/admin",
659
+ component: lazy(() => import("./Admin")), // code splitting
660
+ },
661
+ ]);
662
+
663
+ // Navigate programmatically
664
+ await navigate("/about");
665
+ await navigate({ name: "user", params: { id: "42" } });
666
+
667
+ // Components
668
+ function App() {
669
+ return html`<div>
670
+ <nav>
671
+ ${RouterLink({ to: "/", nodes: "Home" })}
672
+ ${RouterLink({ to: "/about", nodes: "About" })}
673
+ </nav>
674
+ ${route()}
675
+ </div>`;
676
+ }
677
+
678
+ // Guards
679
+ beforeEach(async (to, from) => {
680
+ if (to.path === "/admin" && !isAdmin()) return "/login";
681
+ return true;
682
+ });
683
+ ```
684
+
685
+ ### Internationalization (i18n)
686
+
687
+ Reactive translations with parameter interpolation.
688
+
689
+ ```ts
690
+ import { setLocale, registerTranslations, t, Trans } from "sibujs/plugins";
691
+
692
+ registerTranslations("en", {
693
+ greeting: "Hello, {name}!",
694
+ items: "You have {count} items",
695
+ });
696
+
697
+ registerTranslations("es", {
698
+ greeting: "Hola, {name}!",
699
+ items: "Tienes {count} elementos",
700
+ });
701
+
702
+ setLocale("en");
703
+
704
+ // Imperative
705
+ t("greeting", { name: "Mark" }); // "Hello, Mark!"
706
+
707
+ // Reactive component (auto-updates on locale change)
708
+ Trans("greeting", { name: "Mark" });
709
+ ```
710
+
711
+ ---
712
+
713
+ ## Extras (`sibu/extras`)
714
+
715
+ Advanced features, imported separately to keep the core lean.
716
+
717
+ ### State Machines
718
+
719
+ ```ts
720
+ import { machine } from "sibujs/extras";
721
+
722
+ const { state, send, matches, can } = machine({
723
+ initial: "idle",
724
+ context: { retries: 0 },
725
+ states: {
726
+ idle: {
727
+ on: { FETCH: "loading" },
728
+ },
729
+ loading: {
730
+ on: {
731
+ SUCCESS: "success",
732
+ FAILURE: { target: "error", action: (ctx) => ({ ...ctx, retries: ctx.retries + 1 }) },
733
+ },
734
+ },
735
+ success: { on: { RESET: "idle" } },
736
+ error: {
737
+ on: {
738
+ RETRY: { target: "loading", guard: (ctx) => ctx.retries < 3 },
739
+ },
740
+ },
741
+ },
742
+ });
743
+
744
+ send("FETCH");
745
+ matches("loading"); // true
746
+ can("RETRY"); // false (not in error state)
747
+ ```
748
+
749
+ ### Form Validation
750
+
751
+ ```ts
752
+ import { form, required, email, minLength } from "sibujs/extras";
753
+
754
+ const form = form({
755
+ fields: {
756
+ username: { initial: "", validators: [required(), minLength(3)] },
757
+ email: { initial: "", validators: [required(), email()] },
758
+ },
759
+ onSubmit: (values) => api.register(values),
760
+ });
761
+
762
+ // Reactive getters
763
+ form.fields.username.value();
764
+ form.errors.username();
765
+ form.isValid();
766
+ form.isDirty();
767
+
768
+ // Handle submission
769
+ form.handleSubmit();
770
+ ```
771
+
772
+ ### Global Store
773
+
774
+ ```ts
775
+ import { globalStore } from "sibujs/extras";
776
+
777
+ const store = globalStore({
778
+ state: { count: 0, user: null },
779
+ actions: {
780
+ increment: (state) => ({ ...state, count: state.count + 1 }),
781
+ setUser: (state, user) => ({ ...state, user }),
782
+ },
783
+ middleware: [(action, state) => console.log(action, state)],
784
+ });
785
+
786
+ store.dispatch("increment");
787
+ const count = store.select((s) => s.count); // reactive selector
788
+ count(); // 1
789
+ ```
790
+
791
+ ### Persistent State
792
+
793
+ ```ts
794
+ import { persisted } from "sibujs/extras";
795
+
796
+ // Auto-saves to localStorage, restores on page load
797
+ const [theme, setTheme] = persisted("app-theme", "light");
798
+ setTheme("dark"); // saved to localStorage automatically
799
+ ```
800
+
801
+ ### Time Travel
802
+
803
+ ```ts
804
+ import { timeline } from "sibujs/extras";
805
+
806
+ const { value, set, undo, redo, canUndo, canRedo } = timeline("initial");
807
+ set("second");
808
+ set("third");
809
+ undo(); // value() === "second"
810
+ redo(); // value() === "third"
811
+ ```
812
+
813
+ ### Optimistic Updates
814
+
815
+ ```ts
816
+ import { optimistic } from "sibujs/extras";
817
+
818
+ const [likes, addLike] = optimistic(0);
819
+ addLike(likes() + 1, async () => {
820
+ return await api.like(postId); // reverts if this throws
821
+ });
822
+ ```
823
+
824
+ ### Data Fetching
825
+
826
+ ```ts
827
+ import { query, mutation, infiniteQuery, resource } from "sibujs/extras";
828
+
829
+ // Query with caching, stale-while-revalidate, and auto-refetch
830
+ const { data, loading, error, refetch } = query("users", async ({ signal }) => {
831
+ const res = await fetch("/api/users", { signal });
832
+ return res.json();
833
+ }, {
834
+ staleTime: 30_000,
835
+ refetchOnWindowFocus: true,
836
+ });
837
+
838
+ // Cache management
839
+ import { invalidateQueries, setQueryData } from "sibujs/extras";
840
+ invalidateQueries("users");
841
+ setQueryData("users", (prev) => [...prev, newUser]);
842
+
843
+ // Mutations with optimistic updates
844
+ const { mutate, mutateAsync, loading: saving } = mutation(
845
+ (user) => fetch("/api/users", { method: "POST", body: JSON.stringify(user) }),
846
+ {
847
+ onMutate: (variables) => {
848
+ const prev = getQueryData("users");
849
+ setQueryData("users", (old) => [...old, variables]);
850
+ return prev; // context for rollback
851
+ },
852
+ onError: (_err, _vars, context) => setQueryData("users", context),
853
+ onSuccess: () => invalidateQueries("users"),
854
+ }
855
+ );
856
+
857
+ // Infinite / paginated queries
858
+ const { pages, fetchNextPage, hasNextPage } = infiniteQuery(
859
+ "feed",
860
+ ({ pageParam }) => fetch(`/api/feed?page=${pageParam}`).then(r => r.json()),
861
+ { getNextPageParam: (lastPage) => lastPage.nextCursor }
862
+ );
863
+
864
+ // Low-level resource (Solid-style)
865
+ const resource = resource(
866
+ () => userId(),
867
+ (id, { signal }) => fetch(`/api/users/${id}`, { signal }).then(r => r.json())
868
+ );
869
+ resource.data(); // reactive
870
+ ```
871
+
872
+ ### Debounce, Throttle, and Previous
873
+
874
+ ```ts
875
+ import { debounce, throttle, previous } from "sibujs/extras";
876
+
877
+ // Debounced reactive value (updates after 300ms of inactivity)
878
+ const debouncedSearch = debounce(() => searchInput(), 300);
879
+
880
+ // Throttled reactive value (updates at most once per 100ms)
881
+ const throttledScroll = throttle(() => scrollY(), 100);
882
+
883
+ // Track previous value
884
+ const prevCount = previous(count);
885
+ // prevCount() is the value before the last change
886
+ ```
887
+
888
+ ### Browser APIs
889
+
890
+ All browser APIs return reactive getters and a `dispose` function for cleanup.
891
+
892
+ ```ts
893
+ import {
894
+ media,
895
+ online,
896
+ clipboard,
897
+ title,
898
+ colorScheme,
899
+ draggable,
900
+ dropZone,
901
+ resize,
902
+ scroll,
903
+ geo,
904
+ battery,
905
+ idle,
906
+ permissions,
907
+ } from "sibujs/extras";
908
+
909
+ // Media queries
910
+ const { matches: isMobile } = media("(max-width: 768px)");
911
+
912
+ // Online/offline status
913
+ const { online } = online();
914
+
915
+ // Clipboard
916
+ const { text, copy, copied } = clipboard();
917
+ await copy("Hello!");
918
+ copied(); // true (resets after 2s)
919
+
920
+ // Reactive document title
921
+ const disposeTitle = title(() => `(${unread()}) My App`);
922
+
923
+ // Dark/light mode preference
924
+ const { scheme } = colorScheme();
925
+ scheme(); // "dark" | "light"
926
+
927
+ // Drag and drop
928
+ const { isDragging } = draggable(() => dragEl, { type: "card", id: 1 });
929
+ const { isOver } = dropZone(() => dropEl, {
930
+ onDrop: (data, event) => handleDrop(data),
931
+ });
932
+
933
+ // Resize observer
934
+ const { width, height } = resize(() => element);
935
+
936
+ // Scroll position
937
+ const { scrollX, scrollY } = scroll();
938
+
939
+ // Geolocation
940
+ const { latitude, longitude, error } = geo();
941
+
942
+ // Battery status
943
+ const { level, charging } = battery();
944
+
945
+ // Idle detection
946
+ const { idle } = idle(60_000); // idle after 60s
947
+
948
+ // Permission status
949
+ const { state: cameraPermission } = permissions("camera");
950
+ ```
951
+
952
+ ### Real-Time Communication
953
+
954
+ ```ts
955
+ import { socket, stream, eventBus } from "sibujs/extras";
956
+
957
+ // WebSocket with auto-reconnect and heartbeat
958
+ const { data, status, send, close } = socket("wss://api.example.com/ws", {
959
+ autoReconnect: true,
960
+ reconnectDelay: 1000,
961
+ maxReconnects: 5,
962
+ heartbeat: { interval: 30_000, message: "ping" },
963
+ });
964
+ send("hello");
965
+ status(); // "connecting" | "open" | "closing" | "closed"
966
+
967
+ // Server-Sent Events (SSE)
968
+ const { data: sseData, event, status: sseStatus } = stream("/api/events", {
969
+ withCredentials: true,
970
+ });
971
+
972
+ // Typed event bus
973
+ const bus = eventBus<{ notify: string; update: { id: number } }>();
974
+ const off = bus.on("notify", (msg) => console.log(msg));
975
+ bus.emit("notify", "Hello!");
976
+ off(); // unsubscribe
977
+ ```
978
+
979
+ ### Virtual List
980
+
981
+ ```ts
982
+ import { html, signal } from "sibujs";
983
+ import { VirtualList } from "sibujs/extras";
984
+
985
+ VirtualList({
986
+ items: () => largeArray(),
987
+ itemHeight: 40,
988
+ containerHeight: 400,
989
+ overscan: 5,
990
+ renderItem: (item, index) => html`<div>${item.name}</div>`,
991
+ });
992
+ ```
993
+
994
+ ### Transitions and Animations
995
+
996
+ ```ts
997
+ import { transition, TransitionGroup, viewTransition } from "sibujs/extras";
998
+
999
+ // Single element transition
1000
+ const { enter, leave } = transition(element, {
1001
+ property: "opacity",
1002
+ duration: 300,
1003
+ easing: "ease-in-out",
1004
+ });
1005
+ await enter(); // fade in
1006
+ await leave(); // fade out
1007
+
1008
+ // Group transitions with FLIP animations
1009
+ const group = TransitionGroup({
1010
+ enter: (el) => el.animate([{ opacity: 0 }, { opacity: 1 }], 300).finished,
1011
+ leave: (el) => el.animate([{ opacity: 1 }, { opacity: 0 }], 300).finished,
1012
+ });
1013
+ group.add(newElement);
1014
+ await group.remove(oldElement);
1015
+
1016
+ // View Transitions API (with fallback)
1017
+ const { start, isTransitioning } = viewTransition(() => {
1018
+ setPage("next");
1019
+ });
1020
+ await start();
1021
+ ```
1022
+
1023
+ ### Dialogs and Toasts
1024
+
1025
+ ```ts
1026
+ import { dialog, toast } from "sibujs/extras";
1027
+
1028
+ // Dialog state
1029
+ const dialog = dialog();
1030
+ dialog.open();
1031
+ dialog.isOpen(); // true
1032
+ dialog.close(); // also closes on Escape
1033
+
1034
+ // Toast notifications
1035
+ const { toasts, show, dismiss, dismissAll } = toast({
1036
+ duration: 5000,
1037
+ maxToasts: 3,
1038
+ });
1039
+ const id = show("Saved successfully!", "success");
1040
+ dismiss(id);
1041
+ ```
1042
+
1043
+ ### Pagination and Infinite Scroll
1044
+
1045
+ ```ts
1046
+ import { pagination, infiniteScroll } from "sibujs/extras";
1047
+
1048
+ // Pagination
1049
+ const { page, totalPages, next, prev, goTo, startIndex, endIndex } = pagination({
1050
+ totalItems: () => items().length,
1051
+ pageSize: 20,
1052
+ });
1053
+
1054
+ // Infinite scroll with IntersectionObserver
1055
+ const { sentinelRef, loading } = infiniteScroll({
1056
+ onLoadMore: () => fetchNextPage(),
1057
+ hasMore: () => hasNextPage(),
1058
+ threshold: 0.5,
1059
+ });
1060
+ ```
1061
+
1062
+ ### Intersection Observer and Lazy Loading
1063
+
1064
+ ```ts
1065
+ import { intersection, lazyLoad } from "sibujs/extras";
1066
+
1067
+ // Track element visibility
1068
+ const { isIntersecting, intersectionRatio, observe } = intersection({
1069
+ threshold: 0.5,
1070
+ });
1071
+ observe(myElement);
1072
+
1073
+ // Lazy-load content when visible
1074
+ const cleanup = lazyLoad(placeholder, () => {
1075
+ placeholder.replaceWith(HeavyComponent());
1076
+ });
1077
+ ```
1078
+
1079
+ ### Input Masks
1080
+
1081
+ ```ts
1082
+ import { inputMask, phoneMask, dateMask, creditCardMask } from "sibujs/extras";
1083
+
1084
+ const phone = inputMask(phoneMask()); // (999) 999-9999
1085
+ const date = inputMask(dateMask()); // 99/99/9999
1086
+ const card = inputMask(creditCardMask()); // 9999 9999 9999 9999
1087
+
1088
+ phone.bind(inputElement);
1089
+ phone.value(); // formatted: "(555) 123-4567"
1090
+ phone.rawValue(); // unformatted: "5551234567"
1091
+ ```
1092
+
1093
+ ### Accessibility
1094
+
1095
+ ```ts
1096
+ import { html } from "sibujs";
1097
+ import { aria, FocusTrap, hotkey, announce } from "sibujs/extras";
1098
+
1099
+ // Reactive ARIA attributes
1100
+ aria(element, {
1101
+ expanded: () => isOpen(),
1102
+ label: "Navigation menu",
1103
+ });
1104
+
1105
+ // Focus trapping (modals, dialogs)
1106
+ FocusTrap(modalContent, { autoFocus: true, restoreFocus: true });
1107
+
1108
+ // Keyboard shortcuts
1109
+ const cleanup = hotkey("s", (e) => save(), { ctrl: true });
1110
+
1111
+ // Screen reader announcements
1112
+ announce("Item deleted", "polite");
1113
+ ```
1114
+
1115
+ ### Scoped Styles
1116
+
1117
+ ```ts
1118
+ import { html } from "sibujs";
1119
+ import { scopedStyle, withScopedStyle } from "sibujs/extras";
1120
+
1121
+ // Manual scoping
1122
+ const { scope, attr } = scopedStyle(`
1123
+ .card { border: 1px solid #ccc; padding: 16px; }
1124
+ .card:hover { box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
1125
+ `);
1126
+
1127
+ // Auto-scoped component
1128
+ const StyledCard = withScopedStyle(`
1129
+ .card { background: white; border-radius: 8px; }
1130
+ `, (props) => html`<div class="card">${props.content}</div>`);
1131
+ ```
1132
+
1133
+ ### Higher-Order Components
1134
+
1135
+ ```ts
1136
+ import { withDefaults, withWrapper, compose } from "sibujs/extras";
1137
+
1138
+ const Button = withDefaults(BaseButton, { variant: "primary", size: "md" });
1139
+
1140
+ const LoggedButton = withWrapper(BaseButton, (Component, props) => {
1141
+ console.log("rendering button", props);
1142
+ return Component(props);
1143
+ });
1144
+
1145
+ const EnhancedButton = compose(withLogging, withTheme, withTooltip)(BaseButton);
1146
+ ```
1147
+
1148
+ ### Composables
1149
+
1150
+ ```ts
1151
+ import { html, signal } from "sibujs";
1152
+ import { composable } from "sibujs/extras";
1153
+
1154
+ const counterSetup = composable(() => {
1155
+ const [count, setCount] = signal(0);
1156
+ return { count, increment: () => setCount(c => c + 1) };
1157
+ });
1158
+
1159
+ // Reuse in any component
1160
+ function MyComponent() {
1161
+ const { count, increment } = counterSetup();
1162
+ return html`<button on:click=${increment}>${() => count()}</button>`;
1163
+ }
1164
+ ```
1165
+
1166
+ ---
1167
+
1168
+ ## Widgets (`sibu/extras`)
1169
+
1170
+ Headless UI primitives -- state logic and keyboard navigation without opinions on markup. Build your own UI on top.
1171
+
1172
+ ### Tabs
1173
+
1174
+ ```ts
1175
+ import { tabs } from "sibujs/extras";
1176
+
1177
+ const tabs = tabs({
1178
+ tabs: [
1179
+ { id: "general", label: "General" },
1180
+ { id: "security", label: "Security" },
1181
+ { id: "billing", label: "Billing", disabled: true },
1182
+ ],
1183
+ defaultTab: "general",
1184
+ });
1185
+
1186
+ tabs.activeTab(); // "general"
1187
+ tabs.setActiveTab("security");
1188
+ tabs.nextTab(); // keyboard arrow navigation
1189
+ tabs.prevTab();
1190
+ ```
1191
+
1192
+ ### Select
1193
+
1194
+ ```ts
1195
+ import { select } from "sibujs/extras";
1196
+
1197
+ const select = select({
1198
+ items: ["Apple", "Banana", "Cherry"],
1199
+ multiple: false,
1200
+ });
1201
+
1202
+ select.open();
1203
+ select.highlightNext();
1204
+ select.selectHighlighted();
1205
+ select.selectedItem(); // "Apple"
1206
+ select.isSelected("Apple"); // true
1207
+ ```
1208
+
1209
+ ### Accordion
1210
+
1211
+ ```ts
1212
+ import { accordion } from "sibujs/extras";
1213
+
1214
+ const accordion = accordion({
1215
+ items: [
1216
+ { id: "faq-1", label: "What is Sibu?" },
1217
+ { id: "faq-2", label: "How does reactivity work?" },
1218
+ ],
1219
+ multiple: false,
1220
+ });
1221
+
1222
+ accordion.toggle("faq-1");
1223
+ accordion.items(); // [{ id: "faq-1", label: "...", isExpanded: true }, ...]
1224
+ accordion.expandAll();
1225
+ accordion.collapseAll();
1226
+ ```
1227
+
1228
+ ### Combobox
1229
+
1230
+ ```ts
1231
+ import { combobox } from "sibujs/extras";
1232
+
1233
+ const combo = combobox({
1234
+ items: ["New York", "Los Angeles", "Chicago", "Houston"],
1235
+ filterFn: (item, query) => item.toLowerCase().includes(query.toLowerCase()),
1236
+ });
1237
+
1238
+ combo.setQuery("chi");
1239
+ combo.filteredItems(); // ["Chicago"]
1240
+ combo.selectHighlighted();
1241
+ combo.selectedItem(); // "Chicago"
1242
+ ```
1243
+
1244
+ ### Popover and Tooltip
1245
+
1246
+ ```ts
1247
+ import { popover, tooltip } from "sibujs/extras";
1248
+
1249
+ const popover = popover();
1250
+ popover.toggle();
1251
+ popover.isOpen(); // true
1252
+
1253
+ const tooltip = tooltip({ delay: 200 });
1254
+ tooltip.setContent("More info");
1255
+ tooltip.show();
1256
+ tooltip.isVisible(); // true (after 200ms delay)
1257
+ ```
1258
+
1259
+ ### File Upload
1260
+
1261
+ ```ts
1262
+ import { fileUpload } from "sibujs/extras";
1263
+
1264
+ const upload = fileUpload({
1265
+ accept: "image/*",
1266
+ multiple: true,
1267
+ maxSize: 5 * 1024 * 1024, // 5 MB
1268
+ onFiles: (files) => console.log("Selected:", files),
1269
+ });
1270
+
1271
+ upload.files(); // reactive list of File objects
1272
+ upload.errors(); // validation errors (e.g., "File exceeds max size")
1273
+ upload.isDragOver(); // true when dragging over drop zone
1274
+ upload.clear();
1275
+ ```
1276
+
1277
+ ### Date Picker
1278
+
1279
+ ```ts
1280
+ import { datePicker } from "sibujs/extras";
1281
+
1282
+ const picker = datePicker({
1283
+ minDate: new Date(2020, 0, 1),
1284
+ maxDate: new Date(2030, 11, 31),
1285
+ });
1286
+
1287
+ picker.nextMonth();
1288
+ picker.daysInMonth(); // [{ date, isCurrentMonth, isToday, isSelected, isDisabled }, ...]
1289
+ picker.select(new Date(2025, 5, 15));
1290
+ picker.selectedDate(); // Date
1291
+ ```
1292
+
1293
+ ### Content Editable
1294
+
1295
+ ```ts
1296
+ import { contentEditable } from "sibujs/extras";
1297
+
1298
+ const editor = contentEditable();
1299
+ editor.setContent("<b>Hello</b> world");
1300
+ editor.bold(); // execCommand("bold")
1301
+ editor.italic();
1302
+ editor.underline();
1303
+ editor.content(); // reactive HTML string
1304
+ ```
1305
+
1306
+ ---
1307
+
1308
+ ## Web Components (`sibu/extras`)
1309
+
1310
+ ```ts
1311
+ import { html, signal } from "sibujs";
1312
+ import { defineElement } from "sibujs/extras";
1313
+
1314
+ defineElement("my-counter", (props) => {
1315
+ const [count, setCount] = signal(Number(props.initial) || 0);
1316
+ return html`<button on:click=${() => setCount(c => c + 1)}>${() => count()}</button>`;
1317
+ }, {
1318
+ shadow: true,
1319
+ observedAttributes: ["initial"],
1320
+ });
1321
+ ```
1322
+
1323
+ ```html
1324
+ <my-counter initial="5"></my-counter>
1325
+ ```
1326
+
1327
+ ---
1328
+
1329
+ ## SSR and Static Generation (`sibu/extras`)
1330
+
1331
+ ### Server-Side Rendering
1332
+
1333
+ ```ts
1334
+ import {
1335
+ renderToString,
1336
+ renderToStream,
1337
+ renderToDocument,
1338
+ hydrate,
1339
+ } from "sibujs/extras";
1340
+
1341
+ // Render component to HTML string
1342
+ const markup = renderToString(App());
1343
+
1344
+ // Full document with head management
1345
+ const page = renderToDocument(App, {
1346
+ title: "My App",
1347
+ meta: [{ name: "description", content: "A Sibu app" }],
1348
+ scripts: ["/app.js"],
1349
+ });
1350
+
1351
+ // Streaming SSR
1352
+ const stream = renderToStream(App());
1353
+ for await (const chunk of stream) {
1354
+ res.write(chunk);
1355
+ }
1356
+
1357
+ // Client-side hydration
1358
+ hydrate(App, document.getElementById("root"));
1359
+ ```
1360
+
1361
+ ### Islands Architecture
1362
+
1363
+ ```ts
1364
+ import { island, hydrateIslands, hydrateProgressively } from "sibujs/extras";
1365
+
1366
+ // Server: mark interactive islands
1367
+ const header = island("header", () => InteractiveHeader());
1368
+
1369
+ // Client: hydrate only interactive parts
1370
+ hydrateIslands(document.body, {
1371
+ header: () => InteractiveHeader(),
1372
+ sidebar: () => InteractiveSidebar(),
1373
+ });
1374
+
1375
+ // Progressive hydration (hydrates when scrolled into view)
1376
+ hydrateProgressively(document.body, islands, { threshold: 0.1 });
1377
+ ```
1378
+
1379
+ ### Suspense SSR
1380
+
1381
+ ```ts
1382
+ import { html } from "sibujs";
1383
+ import { ssrSuspense, renderToSuspenseStream, suspenseSwapScript } from "sibujs/extras";
1384
+
1385
+ const boundary = ssrSuspense({
1386
+ fallback: () => html`<div>Loading...</div>`,
1387
+ content: () => fetchAndRender(),
1388
+ });
1389
+
1390
+ // Stream HTML with out-of-order suspense resolution
1391
+ const stream = renderToSuspenseStream(shell, [boundary.promise]);
1392
+ ```
1393
+
1394
+ ### Static Site Generation
1395
+
1396
+ ```ts
1397
+ import { generateStaticSite } from "sibujs/extras";
1398
+
1399
+ const result = await generateStaticSite({
1400
+ routes: ["/", "/about", "/blog/1", "/blog/2"],
1401
+ renderFn: async (path) => renderToDocument(App, { title: path }),
1402
+ outDir: "./dist",
1403
+ });
1404
+
1405
+ result.pages; // [{ path: "/", html: "..." }, ...]
1406
+ result.errors; // [{ path: "/blog/2", error: Error }]
1407
+ ```
1408
+
1409
+ ---
1410
+
1411
+ ## Concurrent Rendering (`sibu/extras`)
1412
+
1413
+ ```ts
1414
+ import {
1415
+ startTransition,
1416
+ deferredValue,
1417
+ transitionState,
1418
+ id,
1419
+ scheduleUpdate,
1420
+ yieldToMain,
1421
+ processInChunks,
1422
+ Priority,
1423
+ } from "sibujs/extras";
1424
+
1425
+ // Non-blocking state updates
1426
+ startTransition(() => {
1427
+ setSearchResults(filterLargeList(query()));
1428
+ });
1429
+
1430
+ // Deferred value (updates at lower priority)
1431
+ const deferredQuery = deferredValue(() => query());
1432
+
1433
+ // Transition with pending state
1434
+ const [isPending, startTransition] = transitionState();
1435
+
1436
+ // Unique IDs (SSR-safe)
1437
+ const id = id(); // "sibu-0"
1438
+ const labelId = uniqueId("label"); // "sibu-1-label"
1439
+
1440
+ // Priority-based scheduling
1441
+ scheduleUpdate(Priority.USER_BLOCKING, () => updateUI());
1442
+ scheduleUpdate(Priority.IDLE, () => prefetchData());
1443
+
1444
+ // Yield to main thread
1445
+ await yieldToMain();
1446
+
1447
+ // Process large arrays without blocking
1448
+ await processInChunks(bigArray, (item) => processItem(item), 50);
1449
+ ```
1450
+
1451
+ ---
1452
+
1453
+ ## DevTools (`sibu/extras`)
1454
+
1455
+ ### Debugging and Performance
1456
+
1457
+ ```ts
1458
+ import {
1459
+ enableDebug,
1460
+ debugLog,
1461
+ performance,
1462
+ measureRender,
1463
+ getPerformanceReport,
1464
+ checkLeaks,
1465
+ } from "sibujs/extras";
1466
+
1467
+ enableDebug();
1468
+ debugLog("Counter", "increment", { value: 5 });
1469
+
1470
+ // Measure component render times
1471
+ const MeasuredList = measureRender("ItemList", ItemList);
1472
+
1473
+ // Manual performance tracking
1474
+ const perf = performance("search");
1475
+ perf.startMeasure();
1476
+ // ... expensive operation
1477
+ perf.endMeasure();
1478
+ perf.getAverageTime();
1479
+
1480
+ // Get full report
1481
+ getPerformanceReport();
1482
+ // { "search": { count: 10, average: 4.2, min: 2, max: 8, total: 42 } }
1483
+
1484
+ // Check for cleanup leaks
1485
+ checkLeaks(); // { "Counter": 2 } -- 2 unclean instances
1486
+ ```
1487
+
1488
+ ### DevTools Integration
1489
+
1490
+ ```ts
1491
+ import { initDevTools, devState, getActiveDevTools } from "sibujs/extras";
1492
+
1493
+ const devtools = initDevTools({ maxEvents: 1000 });
1494
+
1495
+ // State with automatic change tracking
1496
+ const [count, setCount] = devState("counter", 0);
1497
+ // Changes are recorded: { type: "state-change", component: "counter", ... }
1498
+
1499
+ // Register components
1500
+ devtools.registerComponent("App", rootElement, { count: 0 });
1501
+
1502
+ // Query events
1503
+ devtools.getEvents({ type: "state-change", component: "counter" });
1504
+
1505
+ // Snapshot all registered state
1506
+ devtools.snapshot();
1507
+ ```
1508
+
1509
+ ### Hot Module Replacement
1510
+
1511
+ ```ts
1512
+ import { hmrState, registerHMR, createHMRBoundary } from "sibujs/extras";
1513
+
1514
+ // State that persists across HMR updates
1515
+ const [count, setCount] = hmrState("counter", 0);
1516
+
1517
+ // Register component for hot replacement
1518
+ const { update, dispose } = registerHMR("App", App, container);
1519
+
1520
+ // HMR boundary
1521
+ const boundary = createHMRBoundary("feature");
1522
+ const wrapped = boundary.wrap(() => FeatureComponent());
1523
+ boundary.accept(() => console.log("Module updated"));
1524
+ ```
1525
+
1526
+ ---
1527
+
1528
+ ## Ecosystem Adapters (`sibu/extras`)
1529
+
1530
+ ### State Management
1531
+
1532
+ ```ts
1533
+ import { reduxAdapter, zustandAdapter, mobXAdapter } from "sibujs/extras";
1534
+
1535
+ // Use Redux store with Sibu reactivity
1536
+ const { useSelector, dispatch } = reduxAdapter(reduxStore);
1537
+ const count = useSelector((s) => s.counter.value);
1538
+
1539
+ // Use Zustand store
1540
+ const { store } = zustandAdapter(zustandStore);
1541
+
1542
+ // Use MobX observables
1543
+ const { useObservable } = mobXAdapter();
1544
+ ```
1545
+
1546
+ ### UI Framework Integration
1547
+
1548
+ ```ts
1549
+ import { componentAdapter, createTheme } from "sibujs/extras";
1550
+
1551
+ const adapter = componentAdapter();
1552
+ const theme = createTheme({ colors: { primary: "#007bff" } });
1553
+ ```
1554
+
1555
+ ---
1556
+
1557
+ ## Build (`sibu/build`)
1558
+
1559
+ Bundler plugins and deployment utilities.
1560
+
1561
+ ```ts
1562
+ // Vite
1563
+ import { sibuVitePlugin } from "sibujs/build";
1564
+ export default { plugins: [sibuVitePlugin()] };
1565
+
1566
+ // Webpack
1567
+ import { sibuWebpackPlugin } from "sibujs/build";
1568
+ module.exports = { plugins: [sibuWebpackPlugin()] };
1569
+ ```
1570
+
1571
+ Additional build utilities: CDN deployment, type declaration generation, bundle analyzer, linting rules, IDE support, and static analysis tools.
1572
+
1573
+ ---
1574
+
1575
+ ## Testing (`sibu/testing`)
1576
+
1577
+ Component testing utilities, accessibility testing, E2E helpers, snapshot testing, and visual regression support. Works with Vitest, Jest, and Playwright.
1578
+
1579
+ ```ts
1580
+ import { render, fireEvent, waitFor } from "sibujs/testing";
1581
+
1582
+ const { container, unmount } = render(Counter);
1583
+ fireEvent.click(container.querySelector("button"));
1584
+ expect(container.textContent).toContain("Count: 1");
1585
+ unmount();
1586
+ ```
1587
+
1588
+ ---
1589
+
1590
+ ## Package Structure
1591
+
1592
+ Sibu is split into modular entry points. Import only what you use.
1593
+
1594
+ ```
1595
+ sibujs Core: signal, effect, derived, mount, each, when, html, tags, ErrorBoundary
1596
+ sibujs/data Data fetching: query, mutation, infiniteQuery, socket, stream
1597
+ sibujs/browser Browser APIs: media, geo, resize, scroll, online, battery, ...
1598
+ sibujs/patterns State patterns: machine, persisted, timeline, optimistic, store
1599
+ sibujs/motion Transitions: transition, spring, viewTransition, reducedMotion
1600
+ sibujs/ui Forms, a11y, dialogs, toasts, virtual lists, composables, web components
1601
+ sibujs/plugins Router, i18n
1602
+ sibujs/build Vite plugin, Webpack plugin, CDN utilities, template compiler
1603
+ sibujs/testing Component testing utilities
1604
+ ```
1605
+
1606
+ The core has zero dependencies beyond TypeScript. Tree shaking works at the module level -- unused subpaths are not included in your bundle.
1607
+
1608
+ ---
1609
+
1610
+ ## Development
1611
+
1612
+ ```bash
1613
+ # Install
1614
+ npm install
1615
+
1616
+ # Run tests
1617
+ npm test
1618
+
1619
+ # Type check
1620
+ npx tsc --noEmit
1621
+
1622
+ # Build
1623
+ npm run build
1624
+ ```
1625
+
1626
+ ---
1627
+
1628
+ ## License
1629
+
1630
+ MIT -- (c) 2025 [hexplus](https://github.com/hexplus)