sibujs 4.12.0 → 4.13.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 (74) hide show
  1. package/dist/browser.cjs +71 -18
  2. package/dist/browser.js +4 -4
  3. package/dist/build.cjs +6618 -6178
  4. package/dist/build.d.cts +554 -415
  5. package/dist/build.d.ts +554 -415
  6. package/dist/build.js +2091 -1735
  7. package/dist/cdn.dev.global.js +12 -12
  8. package/dist/cdn.full.dev.global.js +13 -13
  9. package/dist/cdn.full.global.js +12 -12
  10. package/dist/cdn.global.js +12 -12
  11. package/dist/{chunk-WFRQ6D7H.js → chunk-22OPSWMF.js} +1 -1
  12. package/dist/{chunk-ZJCHY2IB.js → chunk-2NC56RGY.js} +1 -1
  13. package/dist/{chunk-NB7TFSJE.js → chunk-2O6JJCUR.js} +4 -4
  14. package/dist/{chunk-B2Y5BJSP.js → chunk-4UTGDVSA.js} +70 -16
  15. package/dist/{chunk-NAVLCP4S.js → chunk-5RIBLY2N.js} +4 -4
  16. package/dist/{chunk-T5O2CUHV.js → chunk-5XUGJM3J.js} +1 -1
  17. package/dist/{chunk-HXT2RNH5.js → chunk-ADZHPN2B.js} +1 -1
  18. package/dist/chunk-B36ADEIR.js +21 -0
  19. package/dist/{chunk-DFZL3AX7.js → chunk-C73VBPEF.js} +9 -5
  20. package/dist/{chunk-WH4U2VUE.js → chunk-CPQKWCJ2.js} +13 -5
  21. package/dist/{chunk-LEUEKK4G.js → chunk-EU7T7EB3.js} +2 -2
  22. package/dist/{chunk-QLG5IK7F.js → chunk-G3Z2FEBR.js} +4 -4
  23. package/dist/{chunk-ZPY2UAKM.js → chunk-J4X7ICH4.js} +3 -3
  24. package/dist/{chunk-XX4BRMWF.js → chunk-K4LNUT5L.js} +1 -1
  25. package/dist/{chunk-CI4W6XAU.js → chunk-KC62VURU.js} +3 -3
  26. package/dist/chunk-N5TBYXHW.js +17 -0
  27. package/dist/{chunk-653KGQS2.js → chunk-OIKD7YDY.js} +2 -2
  28. package/dist/{chunk-LYTUTXQP.js → chunk-PBJYCE2Z.js} +46 -4
  29. package/dist/{chunk-EMB5PG7V.js → chunk-QC3NP47A.js} +1 -1
  30. package/dist/{chunk-IHA62NGJ.js → chunk-QKFVMIAV.js} +1 -1
  31. package/dist/{chunk-LXBMVGVB.js → chunk-QZ47OZ25.js} +3 -3
  32. package/dist/{chunk-ZTYCCBU3.js → chunk-VFPLWUT7.js} +44 -26
  33. package/dist/{chunk-RK7EPQPG.js → chunk-YFTBCCVH.js} +1 -1
  34. package/dist/data.cjs +71 -18
  35. package/dist/data.js +5 -5
  36. package/dist/datePicker-DxuxWpgF.d.cts +285 -0
  37. package/dist/datePicker-DxuxWpgF.d.ts +285 -0
  38. package/dist/devtools.cjs +71 -18
  39. package/dist/devtools.js +4 -4
  40. package/dist/ecosystem.cjs +82 -21
  41. package/dist/ecosystem.js +7 -7
  42. package/dist/extras.cjs +131 -23
  43. package/dist/extras.d.cts +2 -2
  44. package/dist/extras.d.ts +2 -2
  45. package/dist/extras.js +21 -20
  46. package/dist/index.cjs +115 -37
  47. package/dist/index.d.cts +59 -5
  48. package/dist/index.d.ts +59 -5
  49. package/dist/index.js +10 -10
  50. package/dist/motion.cjs +69 -16
  51. package/dist/motion.js +3 -3
  52. package/dist/patterns.cjs +71 -18
  53. package/dist/patterns.js +5 -5
  54. package/dist/performance.cjs +69 -16
  55. package/dist/performance.js +4 -4
  56. package/dist/plugins.cjs +1421 -996
  57. package/dist/plugins.d.cts +132 -3
  58. package/dist/plugins.d.ts +132 -3
  59. package/dist/plugins.js +262 -11
  60. package/dist/routeMiddleware-B1sp4P7a.d.ts +688 -0
  61. package/dist/routeMiddleware-CedQTsLK.d.cts +688 -0
  62. package/dist/ssr.cjs +3607 -3524
  63. package/dist/ssr.d.cts +20 -671
  64. package/dist/ssr.d.ts +20 -671
  65. package/dist/ssr.js +15 -7
  66. package/dist/testing.cjs +71 -18
  67. package/dist/testing.js +2 -2
  68. package/dist/ui.cjs +78 -18
  69. package/dist/ui.js +8 -7
  70. package/dist/widgets.cjs +1352 -952
  71. package/dist/widgets.d.cts +100 -268
  72. package/dist/widgets.d.ts +100 -268
  73. package/dist/widgets.js +316 -7
  74. package/package.json +9 -1
package/dist/build.js CHANGED
@@ -1,18 +1,18 @@
1
1
  import {
2
2
  index_exports
3
- } from "./chunk-ZTYCCBU3.js";
4
- import "./chunk-T5O2CUHV.js";
3
+ } from "./chunk-VFPLWUT7.js";
4
+ import "./chunk-5XUGJM3J.js";
5
5
  import "./chunk-2F4PSJ5Z.js";
6
6
  import "./chunk-AM6DZLBM.js";
7
- import "./chunk-HXT2RNH5.js";
8
- import "./chunk-WFRQ6D7H.js";
7
+ import "./chunk-ADZHPN2B.js";
8
+ import "./chunk-22OPSWMF.js";
9
9
  import "./chunk-62BDYGAG.js";
10
- import "./chunk-WH4U2VUE.js";
11
- import "./chunk-XX4BRMWF.js";
10
+ import "./chunk-CPQKWCJ2.js";
11
+ import "./chunk-K4LNUT5L.js";
12
12
  import "./chunk-F2UWOZ6J.js";
13
- import "./chunk-RK7EPQPG.js";
13
+ import "./chunk-YFTBCCVH.js";
14
14
  import "./chunk-7ZHH77QA.js";
15
- import "./chunk-EMB5PG7V.js";
15
+ import "./chunk-QC3NP47A.js";
16
16
  import {
17
17
  isEventHandlerAttr,
18
18
  isHtmlContentAttribute,
@@ -20,78 +20,352 @@ import {
20
20
  } from "./chunk-EXXSN3BW.js";
21
21
  import "./chunk-7XHATCIH.js";
22
22
  import "./chunk-5HZXGZ6T.js";
23
- import "./chunk-IHA62NGJ.js";
24
- import "./chunk-B2Y5BJSP.js";
23
+ import "./chunk-QKFVMIAV.js";
24
+ import "./chunk-4UTGDVSA.js";
25
25
  import "./chunk-NYNYSPK7.js";
26
26
 
27
- // src/build/sourceEdit.ts
28
- function isLineBreak(c) {
29
- return c === 10 || c === 13 || c === 8232 || c === 8233;
27
+ // src/build/analyzer.ts
28
+ var moduleSizes = {
29
+ // Core modules
30
+ "core/html": 1800,
31
+ "core/mount": 120,
32
+ "core/each": 450,
33
+ "core/slots": 200,
34
+ "core/fragment": 180,
35
+ "core/catch": 280,
36
+ "core/portal": 350,
37
+ "core/directives": 520,
38
+ "core/dynamic": 380,
39
+ "core/head": 300,
40
+ "core/ssr": 1200,
41
+ "core/customElement": 650,
42
+ "core/scopedStyle": 480,
43
+ "core/domRecycler": 420,
44
+ "core/bundleOptimize": 200,
45
+ "core/compiled": 350,
46
+ "core/normalize": 280,
47
+ "core/reactiveAttr": 320,
48
+ "core/componentProps": 400,
49
+ "core/contracts": 350,
50
+ "core/worker": 550,
51
+ "core/wasm": 480,
52
+ "core/concurrent": 600,
53
+ "core/microfrontend": 700,
54
+ "core/chunkLoader": 450,
55
+ "core/versioning": 300,
56
+ "core/ecosystem": 250,
57
+ // Signals
58
+ "core/signal": 280,
59
+ "core/effect": 350,
60
+ "core/derived": 250,
61
+ "core/watch": 300,
62
+ "core/store": 380,
63
+ "core/ref": 150,
64
+ "core/array": 420,
65
+ "core/deepSignal": 500,
66
+ "core/lifecycle": 300,
67
+ "core/context": 350,
68
+ "core/persist": 400,
69
+ "core/hoc": 280,
70
+ "core/transition": 600,
71
+ "core/form": 750,
72
+ "core/globalStore": 450,
73
+ "core/machine": 550,
74
+ "core/optimistic": 380,
75
+ "core/timeTravel": 500,
76
+ "core/scheduler": 350,
77
+ "core/plugin": 300,
78
+ "core/virtualList": 800,
79
+ "core/intersection": 350,
80
+ "core/inputMask": 450,
81
+ "core/a11y": 500,
82
+ "core/debug": 400,
83
+ "core/serviceWorker": 350,
84
+ "core/composable": 280,
85
+ // Reactivity
86
+ "reactivity/signal": 100,
87
+ "reactivity/track": 250,
88
+ "reactivity/batch": 200,
89
+ "reactivity/bindAttribute": 300,
90
+ "reactivity/bindChildNode": 250,
91
+ "reactivity/bindTextNode": 200,
92
+ // Components
93
+ "components/ErrorBoundary": 350,
94
+ "components/Loading": 250,
95
+ // Plugins
96
+ "plugins/i18n": 600,
97
+ "plugins/router": 900,
98
+ "plugins/routerSSR": 500,
99
+ // Build utilities
100
+ "build/vite": 800,
101
+ "build/webpack": 750,
102
+ "build/cdn": 400,
103
+ "build/declarations": 500,
104
+ "build/analyzer": 600,
105
+ // Testing
106
+ "testing/index": 700,
107
+ "testing/e2e": 500
108
+ };
109
+ function formatBytes(bytes) {
110
+ if (bytes < 1024) return `${bytes} B`;
111
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(2)} KB`;
112
+ return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
30
113
  }
31
- function lineBreaksIn(code, start, end) {
32
- let out = "";
33
- for (let i = start; i < end; i++) {
34
- if (isLineBreak(code.charCodeAt(i))) out += code[i];
114
+ function analyzeBundle(modules) {
115
+ const sizes = {};
116
+ let totalSize = 0;
117
+ for (const [name, source] of Object.entries(modules)) {
118
+ let byteLength;
119
+ if (typeof TextEncoder !== "undefined") {
120
+ byteLength = new TextEncoder().encode(source).length;
121
+ } else {
122
+ byteLength = source.length;
123
+ }
124
+ sizes[name] = byteLength;
125
+ totalSize += byteLength;
35
126
  }
36
- return out;
127
+ const sorted = Object.entries(sizes).map(([name, size]) => ({
128
+ name,
129
+ size,
130
+ percentage: totalSize > 0 ? size / totalSize * 100 : 0
131
+ })).sort((a, b) => b.size - a.size);
132
+ return {
133
+ totalSize,
134
+ moduleSizes: sizes,
135
+ sorted,
136
+ report() {
137
+ const lines = [
138
+ "=== SibuJS Bundle Analysis ===",
139
+ "",
140
+ `Total size: ${formatBytes(totalSize)}`,
141
+ `Modules: ${sorted.length}`,
142
+ "",
143
+ "Module breakdown:",
144
+ "-".repeat(60)
145
+ ];
146
+ for (const entry of sorted) {
147
+ const sizeStr = formatBytes(entry.size).padStart(10);
148
+ const pctStr = `${entry.percentage.toFixed(1)}%`.padStart(6);
149
+ const bar = generateBar(entry.percentage, 20);
150
+ lines.push(` ${sizeStr} ${pctStr} ${bar} ${entry.name}`);
151
+ }
152
+ lines.push("-".repeat(60));
153
+ return lines.join("\n");
154
+ }
155
+ };
37
156
  }
38
- var LINE_SEPARATOR = String.fromCharCode(8232);
39
- var PARAGRAPH_SEPARATOR = String.fromCharCode(8233);
40
- function jsStringLiteral(value) {
41
- const json = JSON.stringify(value);
42
- if (!json.includes(LINE_SEPARATOR) && !json.includes(PARAGRAPH_SEPARATOR)) return json;
43
- const bs = "\\";
44
- return json.split(LINE_SEPARATOR).join(`${bs}u2028`).split(PARAGRAPH_SEPARATOR).join(`${bs}u2029`);
157
+ function generateBar(percentage, width) {
158
+ const filled = Math.round(percentage / 100 * width);
159
+ const empty = width - filled;
160
+ return `[${"#".repeat(filled)}${".".repeat(empty)}]`;
45
161
  }
46
- function normalizeEdits(edits) {
47
- const sorted = edits.map((edit, order) => ({ edit, order })).sort(
48
- (a, b) => a.edit.start - b.edit.start || a.edit.end - a.edit.start - (b.edit.end - b.edit.start) || a.order - b.order
49
- ).map((x) => x.edit);
50
- for (let i = 1; i < sorted.length; i++) {
51
- if (sorted[i].start < sorted[i - 1].end) return null;
162
+ function estimateImportSize(imports) {
163
+ const breakdown = {};
164
+ let estimated = 0;
165
+ const implicitDeps = ["reactivity/signal", "reactivity/track"];
166
+ const allImports = [.../* @__PURE__ */ new Set([...implicitDeps, ...imports])];
167
+ for (const importPath of allImports) {
168
+ const size = moduleSizes[importPath];
169
+ if (size !== void 0) {
170
+ breakdown[importPath] = size;
171
+ estimated += size;
172
+ } else {
173
+ const avgSize = 350;
174
+ breakdown[importPath] = avgSize;
175
+ estimated += avgSize;
176
+ }
52
177
  }
53
- return sorted;
54
- }
55
- function applyEdits(code, edits, append = "") {
56
- let out = "";
57
- let pos = 0;
58
- for (const edit of edits) {
59
- out += code.slice(pos, edit.start) + edit.text;
60
- pos = edit.end;
178
+ const sortedEntries = Object.entries(breakdown).sort(([, a], [, b]) => b - a);
179
+ const lines = [`Estimated bundle size: ${formatBytes(estimated)} (minified + gzipped)`, ""];
180
+ for (const [modulePath, size] of sortedEntries) {
181
+ const known = moduleSizes[modulePath] !== void 0;
182
+ lines.push(` ${modulePath}: ${formatBytes(size)}${known ? "" : " (estimated)"}`);
61
183
  }
62
- return out + code.slice(pos) + append;
184
+ if (imports.length > 0) {
185
+ lines.push("");
186
+ lines.push("Note: Actual sizes may vary based on tree-shaking and build configuration.");
187
+ lines.push("Implicit dependencies (reactivity/signal, reactivity/track) are included.");
188
+ }
189
+ return {
190
+ estimated,
191
+ breakdown,
192
+ formatted: lines.join("\n")
193
+ };
63
194
  }
64
- var BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
65
- function vlq(value) {
66
- let v = value < 0 ? -value << 1 | 1 : value << 1;
67
- let out = "";
68
- do {
69
- let digit = v & 31;
70
- v >>>= 5;
71
- if (v > 0) digit |= 32;
72
- out += BASE64[digit];
73
- } while (v > 0);
74
- return out;
195
+
196
+ // src/build/cdn.ts
197
+ var PACKAGE_NAME = "sibujs";
198
+ var SUBPATH_ENTRIES = [
199
+ "data",
200
+ "browser",
201
+ "patterns",
202
+ "motion",
203
+ "ui",
204
+ "widgets",
205
+ "ssr",
206
+ "devtools",
207
+ "performance",
208
+ "ecosystem",
209
+ "plugins",
210
+ "build",
211
+ "testing",
212
+ "extras"
213
+ ];
214
+ function registerGlobal() {
215
+ if (typeof window === "undefined") return;
216
+ window.Sibu = { ...index_exports };
75
217
  }
76
- function isWordStart(c) {
77
- return c >= 97 && c <= 122 || c >= 65 && c <= 90 || c === 95 || c === 36 || c > 127 || c >= 48 && c <= 57;
218
+ function umdWrapper(name, factory) {
219
+ const factoryStr = factory.toString();
220
+ return `(function (root, factory) {
221
+ if (typeof define === 'function' && define.amd) {
222
+ // AMD (RequireJS)
223
+ define([], factory);
224
+ } else if (typeof module === 'object' && module.exports) {
225
+ // CommonJS (Node.js)
226
+ module.exports = factory();
227
+ } else {
228
+ // Browser globals
229
+ root.${name} = factory();
230
+ }
231
+ }(typeof self !== 'undefined' ? self : this, ${factoryStr}));`;
78
232
  }
79
- function editsSourceMap(code, edits, append, source) {
80
- const lineStarts = [0];
81
- for (let i = 0; i < code.length; i++) if (code.charCodeAt(i) === 10) lineStarts.push(i + 1);
82
- const origLine = (offset) => {
83
- let lo = 0;
84
- let hi = lineStarts.length - 1;
85
- while (lo < hi) {
86
- const mid = lo + hi + 1 >> 1;
87
- if (lineStarts[mid] <= offset) lo = mid;
88
- else hi = mid - 1;
89
- }
90
- return lo;
91
- };
92
- const lines = [[]];
93
- let col = 0;
94
- const mark = (offset) => {
233
+ var cdnUrls = {
234
+ /**
235
+ * Generate an unpkg CDN URL.
236
+ * @param version - Package version (defaults to 'latest')
237
+ */
238
+ unpkg: (version) => `https://unpkg.com/${PACKAGE_NAME}@${version || "latest"}/dist/cdn.global.js`,
239
+ /**
240
+ * Generate a jsDelivr CDN URL.
241
+ * @param version - Package version (defaults to 'latest')
242
+ */
243
+ jsdelivr: (version) => `https://cdn.jsdelivr.net/npm/${PACKAGE_NAME}@${version || "latest"}/dist/cdn.global.js`,
244
+ /**
245
+ * Generate a Skypack CDN URL (ESM-native).
246
+ * @param version - Package version (defaults to 'latest')
247
+ */
248
+ skypack: (version) => `https://cdn.skypack.dev/${PACKAGE_NAME}@${version || "latest"}`,
249
+ /**
250
+ * Generate a complete HTML script tag for including SibuJS from a CDN.
251
+ *
252
+ * @param provider - CDN provider to use (defaults to 'jsdelivr')
253
+ * @param version - Package version (defaults to 'latest')
254
+ * @returns An HTML script tag string
255
+ *
256
+ * @example
257
+ * ```ts
258
+ * cdnUrls.scriptTag('jsdelivr', '1.0.0')
259
+ * // => '<script src="https://cdn.jsdelivr.net/npm/sibujs@1.0.0/dist/cdn.global.js"></script>'
260
+ * ```
261
+ */
262
+ scriptTag: (provider = "jsdelivr", version) => {
263
+ const url = cdnUrls[provider](version);
264
+ if (provider === "skypack") {
265
+ return `<script type="module">
266
+ import * as Sibu from '${url}';
267
+ window.Sibu = Sibu;
268
+ </script>`;
269
+ }
270
+ return `<script src="${url}"></script>`;
271
+ }
272
+ };
273
+ function generateImportMap(baseUrl) {
274
+ const base = baseUrl || `https://cdn.jsdelivr.net/npm/${PACKAGE_NAME}@latest`;
275
+ const imports = {
276
+ // Main package entry
277
+ [PACKAGE_NAME]: `${base}/dist/index.js`
278
+ };
279
+ for (const entry of SUBPATH_ENTRIES) {
280
+ imports[`${PACKAGE_NAME}/${entry}`] = `${base}/dist/${entry}.js`;
281
+ }
282
+ return {
283
+ imports,
284
+ /**
285
+ * Serialize the import map to a JSON string.
286
+ */
287
+ toJSON() {
288
+ return JSON.stringify({ imports }, null, 2);
289
+ },
290
+ /**
291
+ * Generate a complete `<script type="importmap">` tag.
292
+ */
293
+ toScriptTag() {
294
+ return `<script type="importmap">
295
+ ${JSON.stringify({ imports }, null, 2)}
296
+ </script>`;
297
+ }
298
+ };
299
+ }
300
+
301
+ // src/build/sourceEdit.ts
302
+ function isLineBreak(c) {
303
+ return c === 10 || c === 13 || c === 8232 || c === 8233;
304
+ }
305
+ function lineBreaksIn(code, start, end) {
306
+ let out = "";
307
+ for (let i = start; i < end; i++) {
308
+ if (isLineBreak(code.charCodeAt(i))) out += code[i];
309
+ }
310
+ return out;
311
+ }
312
+ var LINE_SEPARATOR = String.fromCharCode(8232);
313
+ var PARAGRAPH_SEPARATOR = String.fromCharCode(8233);
314
+ function jsStringLiteral(value) {
315
+ const json = JSON.stringify(value);
316
+ if (!json.includes(LINE_SEPARATOR) && !json.includes(PARAGRAPH_SEPARATOR)) return json;
317
+ const bs = "\\";
318
+ return json.split(LINE_SEPARATOR).join(`${bs}u2028`).split(PARAGRAPH_SEPARATOR).join(`${bs}u2029`);
319
+ }
320
+ function normalizeEdits(edits) {
321
+ const sorted = edits.map((edit, order) => ({ edit, order })).sort(
322
+ (a, b) => a.edit.start - b.edit.start || a.edit.end - a.edit.start - (b.edit.end - b.edit.start) || a.order - b.order
323
+ ).map((x) => x.edit);
324
+ for (let i = 1; i < sorted.length; i++) {
325
+ if (sorted[i].start < sorted[i - 1].end) return null;
326
+ }
327
+ return sorted;
328
+ }
329
+ function applyEdits(code, edits, append = "") {
330
+ let out = "";
331
+ let pos = 0;
332
+ for (const edit of edits) {
333
+ out += code.slice(pos, edit.start) + edit.text;
334
+ pos = edit.end;
335
+ }
336
+ return out + code.slice(pos) + append;
337
+ }
338
+ var BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
339
+ function vlq(value) {
340
+ let v = value < 0 ? -value << 1 | 1 : value << 1;
341
+ let out = "";
342
+ do {
343
+ let digit = v & 31;
344
+ v >>>= 5;
345
+ if (v > 0) digit |= 32;
346
+ out += BASE64[digit];
347
+ } while (v > 0);
348
+ return out;
349
+ }
350
+ function isWordStart(c) {
351
+ return c >= 97 && c <= 122 || c >= 65 && c <= 90 || c === 95 || c === 36 || c > 127 || c >= 48 && c <= 57;
352
+ }
353
+ function editsSourceMap(code, edits, append, source) {
354
+ const lineStarts = [0];
355
+ for (let i = 0; i < code.length; i++) if (code.charCodeAt(i) === 10) lineStarts.push(i + 1);
356
+ const origLine = (offset) => {
357
+ let lo = 0;
358
+ let hi = lineStarts.length - 1;
359
+ while (lo < hi) {
360
+ const mid = lo + hi + 1 >> 1;
361
+ if (lineStarts[mid] <= offset) lo = mid;
362
+ else hi = mid - 1;
363
+ }
364
+ return lo;
365
+ };
366
+ const lines = [[]];
367
+ let col = 0;
368
+ const mark = (offset) => {
95
369
  const line = lines[lines.length - 1];
96
370
  const last = line[line.length - 1];
97
371
  if (last && last[0] === col) return;
@@ -725,285 +999,77 @@ function importsSibu(code) {
725
999
  return !!scan?.imports.some((d) => isSibuSource(d.source));
726
1000
  }
727
1001
 
728
- // src/build/staticAnalysis.ts
729
- var STATIC_TAGS = /* @__PURE__ */ new Set([
730
- "div",
731
- "span",
732
- "section",
733
- "article",
734
- "header",
735
- "footer",
736
- "nav",
737
- "main",
738
- "aside",
739
- "p",
740
- "h1",
741
- "h2",
742
- "h3",
743
- "h4",
744
- "h5",
745
- "h6",
746
- "blockquote",
747
- "li",
748
- "ol",
749
- "ul",
750
- "a",
751
- "b",
752
- "em",
753
- "i",
754
- "strong",
755
- "small",
756
- "code",
757
- "mark",
758
- "img",
759
- "br",
760
- "hr",
761
- "input",
762
- "button",
763
- "form",
764
- "label",
765
- "details",
766
- "summary",
767
- "dialog"
768
- ]);
1002
+ // src/build/templateCompiler.ts
1003
+ var RAW_TEXT_TAGS = /* @__PURE__ */ new Set(["script", "style"]);
769
1004
  var VOID_ELEMENTS = /* @__PURE__ */ new Set([
1005
+ "area",
1006
+ "base",
770
1007
  "br",
1008
+ "col",
1009
+ "embed",
771
1010
  "hr",
772
1011
  "img",
773
1012
  "input",
774
- "meta",
775
1013
  "link",
776
- "area",
777
- "base",
778
- "col",
779
- "embed",
1014
+ "meta",
780
1015
  "param",
781
1016
  "source",
782
1017
  "track",
783
1018
  "wbr"
784
1019
  ]);
785
- var SPECIAL_PROPS = /* @__PURE__ */ new Set(["style", "ref", "on", "onElement"]);
786
- var NON_REFLECTING_BOOLEANS = /* @__PURE__ */ new Set(["checked", "selected"]);
787
- function readLiteral(code, tok) {
788
- if (!tok) return null;
789
- if (tok.type === "string") {
790
- const value = stringTokenValue(code, tok);
791
- return value === null ? null : { kind: "string", value };
1020
+ var SVG_TAGS = /* @__PURE__ */ new Set([
1021
+ "svg",
1022
+ "circle",
1023
+ "ellipse",
1024
+ "g",
1025
+ "line",
1026
+ "path",
1027
+ "polygon",
1028
+ "polyline",
1029
+ "rect",
1030
+ "text",
1031
+ "tspan",
1032
+ "defs",
1033
+ "clipPath",
1034
+ "mask",
1035
+ "pattern",
1036
+ "linearGradient",
1037
+ "radialGradient",
1038
+ "stop",
1039
+ "use",
1040
+ "symbol",
1041
+ "marker"
1042
+ ]);
1043
+ var SVG_NS = "http://www.w3.org/2000/svg";
1044
+ var RuntimeParseError = class extends Error {
1045
+ };
1046
+ function parseTemplate(strings) {
1047
+ const exprCount = strings.length - 1;
1048
+ let template = strings[0];
1049
+ for (let i = 0; i < exprCount; i++) {
1050
+ template += `\0${i}\0${strings[i + 1]}`;
792
1051
  }
793
- if (tok.type === "number" && /^(0|[1-9]\d*)(\.\d+)?$/.test(tok.value)) {
794
- return { kind: "number", value: String(Number(tok.value)) };
1052
+ let pos = 0;
1053
+ const len = template.length;
1054
+ function skipWs() {
1055
+ while (pos < len && (template[pos] === " " || template[pos] === " " || template[pos] === "\n" || template[pos] === "\r"))
1056
+ pos++;
795
1057
  }
796
- if (tok.type === "ident" && ["true", "false", "null", "undefined"].includes(tok.value)) {
797
- return { kind: "keyword", value: tok.value };
798
- }
799
- return null;
800
- }
801
- function parserSafeText(s) {
802
- return !s.includes("\r") && !s.includes("\0");
803
- }
804
- function escapeHtml(str) {
805
- return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
806
- }
807
- function escapeAttr(str) {
808
- return str.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
809
- }
810
- function attributeHtml(name, lit) {
811
- if (!/^[a-zA-Z][a-zA-Z0-9-]*$/.test(name)) return null;
812
- const lower = name.toLowerCase();
813
- if (SPECIAL_PROPS.has(name) || lower === "style" || lower === "srcset") return null;
814
- if (isEventHandlerAttr(name) || isUrlAttribute(name) || isHtmlContentAttribute(name)) return null;
815
- if (lit.kind === "keyword") {
816
- if (lit.value === "null" || lit.value === "undefined") return "";
817
- const on = lit.value === "true";
818
- if (lower.startsWith("aria-")) return `${lower}="${on}"`;
819
- if (NON_REFLECTING_BOOLEANS.has(lower)) return null;
820
- return on ? `${lower}=""` : "";
821
- }
822
- if (!parserSafeText(lit.value)) return null;
823
- return `${lower}="${escapeAttr(lit.value)}"`;
824
- }
825
- function parseCall(code, list, i) {
826
- const at = (k2) => list[k2];
827
- const isPunct = (k2, v) => {
828
- const t = at(k2);
829
- return !!t && t.type === "punct" && t.value === v;
830
- };
831
- let k = i + 1;
832
- const result = { end: 0, cls: null, id: null, attrs: [], text: null };
833
- let nodesText = null;
834
- if (isPunct(k, "{")) {
835
- k++;
836
- const seen = /* @__PURE__ */ new Set();
837
- while (!isPunct(k, "}")) {
838
- const keyTok = at(k);
839
- let key = null;
840
- if (keyTok?.type === "ident") key = keyTok.value;
841
- else if (keyTok?.type === "string") key = stringTokenValue(code, keyTok);
842
- if (key === null || !isPunct(k + 1, ":")) return null;
843
- if (seen.has(key.toLowerCase())) return null;
844
- seen.add(key.toLowerCase());
845
- const lit = readLiteral(code, at(k + 2));
846
- if (lit === null) return null;
847
- k += 3;
848
- if (isPunct(k, ",")) k++;
849
- else if (!isPunct(k, "}")) return null;
850
- if (key === "class" || key === "id") {
851
- if (lit.kind !== "string" || !parserSafeText(lit.value)) return null;
852
- if (key === "class") result.cls = lit.value;
853
- else result.id = lit.value;
854
- } else if (key === "nodes") {
855
- if (lit.kind !== "string" || !parserSafeText(lit.value)) return null;
856
- nodesText = lit.value;
857
- } else {
858
- const html = attributeHtml(key, lit);
859
- if (html === null) return null;
860
- if (html) result.attrs.push(html);
861
- }
862
- }
863
- k++;
864
- if (isPunct(k, ",")) {
865
- const second = readLiteral(code, at(k + 1));
866
- if (second === null) {
867
- if (!isPunct(k + 1, ")")) return null;
868
- k++;
869
- } else {
870
- if (second.kind !== "string" || !parserSafeText(second.value)) return null;
871
- result.text = second.value;
872
- k += 2;
873
- if (isPunct(k, ",")) k++;
874
- }
875
- }
876
- if (result.text === null) result.text = nodesText;
877
- } else {
878
- const first = readLiteral(code, at(k));
879
- if (first === null || first.kind !== "string" || !parserSafeText(first.value)) return null;
880
- k++;
881
- if (isPunct(k, ",") && at(k + 1)?.type === "string") {
882
- const second = readLiteral(code, at(k + 1));
883
- if (second === null || second.kind !== "string" || !parserSafeText(second.value)) return null;
884
- result.cls = first.value;
885
- result.text = second.value;
886
- k += 2;
887
- } else {
888
- result.text = first.value;
889
- }
890
- if (isPunct(k, ",")) k++;
891
- }
892
- if (!isPunct(k, ")")) return null;
893
- result.end = at(k).end;
894
- return result;
895
- }
896
- function toHtml(tag, call) {
897
- const attrs = [];
898
- if (call.cls !== null) attrs.push(`class="${escapeAttr(call.cls)}"`);
899
- if (call.id !== null) attrs.push(`id="${escapeAttr(call.id)}"`);
900
- attrs.push(...call.attrs);
901
- const attrStr = attrs.length > 0 ? ` ${attrs.join(" ")}` : "";
902
- if (VOID_ELEMENTS.has(tag)) {
903
- if (call.text) return null;
904
- return `<${tag}${attrStr} />`;
905
- }
906
- return `<${tag}${attrStr}>${escapeHtml(call.text ?? "")}</${tag}>`;
907
- }
908
- function analyzeStaticTemplates(code) {
909
- const none = { hasStaticPatterns: false, patterns: [] };
910
- if (!code.includes("sibujs")) return none;
911
- const scan = scanModule(code);
912
- if (!scan) return none;
913
- const factories = /* @__PURE__ */ new Map();
914
- for (const decl of scan.imports) {
915
- if (decl.source !== "sibujs") continue;
916
- for (const b of decl.bindings) if (STATIC_TAGS.has(b.imported)) factories.set(b.local, b.imported);
917
- }
918
- if (factories.size === 0) return none;
919
- const uses = bindingUses(code, scan, new Set(factories.keys()));
920
- const unsafe = new Set(uses.shadowed);
921
- for (const ref of uses.tagged) unsafe.add(ref.tok.value);
922
- const patterns = [];
923
- for (const { tok, list, index } of uses.calls) {
924
- if (unsafe.has(tok.value)) continue;
925
- const tag = factories.get(tok.value);
926
- const call = parseCall(code, list, index + 1);
927
- if (!call) continue;
928
- const templateHtml = toHtml(tag, call);
929
- if (templateHtml === null) continue;
930
- const start = list[index].start;
931
- patterns.push({ original: code.slice(start, call.end), tag, templateHtml, start, end: call.end });
932
- }
933
- return { hasStaticPatterns: patterns.length > 0, patterns };
934
- }
935
-
936
- // src/build/templateCompiler.ts
937
- var RAW_TEXT_TAGS = /* @__PURE__ */ new Set(["script", "style"]);
938
- var VOID_ELEMENTS2 = /* @__PURE__ */ new Set([
939
- "area",
940
- "base",
941
- "br",
942
- "col",
943
- "embed",
944
- "hr",
945
- "img",
946
- "input",
947
- "link",
948
- "meta",
949
- "param",
950
- "source",
951
- "track",
952
- "wbr"
953
- ]);
954
- var SVG_TAGS = /* @__PURE__ */ new Set([
955
- "svg",
956
- "circle",
957
- "ellipse",
958
- "g",
959
- "line",
960
- "path",
961
- "polygon",
962
- "polyline",
963
- "rect",
964
- "text",
965
- "tspan",
966
- "defs",
967
- "clipPath",
968
- "mask",
969
- "pattern",
970
- "linearGradient",
971
- "radialGradient",
972
- "stop",
973
- "use",
974
- "symbol",
975
- "marker"
976
- ]);
977
- var SVG_NS = "http://www.w3.org/2000/svg";
978
- var RuntimeParseError = class extends Error {
979
- };
980
- function parseTemplate(strings) {
981
- const exprCount = strings.length - 1;
982
- let template = strings[0];
983
- for (let i = 0; i < exprCount; i++) {
984
- template += `\0${i}\0${strings[i + 1]}`;
985
- }
986
- let pos = 0;
987
- const len = template.length;
988
- function skipWs() {
989
- while (pos < len && (template[pos] === " " || template[pos] === " " || template[pos] === "\n" || template[pos] === "\r"))
990
- pos++;
991
- }
992
- function tryExprIdx() {
993
- if (template.charCodeAt(pos) !== 0) return -1;
994
- const start = pos;
995
- pos++;
996
- let idx = 0;
997
- while (pos < len && template.charCodeAt(pos) !== 0) {
998
- idx = idx * 10 + (template.charCodeAt(pos) - 48);
999
- pos++;
1000
- }
1001
- if (pos < len) pos++;
1002
- if (idx < 0 || idx >= exprCount) {
1003
- pos = start;
1004
- return -1;
1005
- }
1006
- return idx;
1058
+ function tryExprIdx() {
1059
+ if (template.charCodeAt(pos) !== 0) return -1;
1060
+ const start = pos;
1061
+ pos++;
1062
+ let idx = 0;
1063
+ while (pos < len && template.charCodeAt(pos) !== 0) {
1064
+ idx = idx * 10 + (template.charCodeAt(pos) - 48);
1065
+ pos++;
1066
+ }
1067
+ if (pos < len) pos++;
1068
+ if (idx < 0 || idx >= exprCount) {
1069
+ pos = start;
1070
+ return -1;
1071
+ }
1072
+ return idx;
1007
1073
  }
1008
1074
  function readTagName() {
1009
1075
  const start = pos;
@@ -1143,7 +1209,7 @@ function parseTemplate(strings) {
1143
1209
  const tag = readTagName();
1144
1210
  const attrs = parseAttrs();
1145
1211
  skipWs();
1146
- const isVoid = VOID_ELEMENTS2.has(tag);
1212
+ const isVoid = VOID_ELEMENTS.has(tag);
1147
1213
  const isSelfClosing = template[pos] === "/";
1148
1214
  if (isSelfClosing) pos++;
1149
1215
  if (pos < len) pos++;
@@ -1433,847 +1499,693 @@ ${trailer}
1433
1499
  return plan;
1434
1500
  }
1435
1501
 
1436
- // src/build/vite.ts
1437
- var DEFAULT_INCLUDE = ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"];
1438
- var DEFAULT_EXCLUDE = ["node_modules/**", "dist/**", "**/*.test.*", "**/*.spec.*"];
1439
- function matchesPattern(filePath, patterns) {
1440
- const normalized = filePath.replace(/\\/g, "/");
1441
- return patterns.some((pattern) => {
1442
- const regexStr = pattern.replace(/\./g, "\\.").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\{\{GLOBSTAR\}\}/g, ".*");
1443
- return new RegExp(regexStr).test(normalized);
1444
- });
1445
- }
1446
- function devPrologueEdit(code) {
1447
- let at = 0;
1448
- if (code.startsWith("#!")) {
1449
- const nl = code.indexOf("\n");
1450
- if (nl === -1) return null;
1451
- at = nl + 1;
1452
- }
1502
+ // src/build/compileTemplates.ts
1503
+ function compileHtmlTemplates(code) {
1504
+ const plan = planHtmlTemplates(code);
1505
+ const edits = plan.compiledCount > 0 ? normalizeEdits(plan.edits) : null;
1453
1506
  return {
1454
- start: at,
1455
- end: at,
1456
- text: '/* SibuJS Dev Mode */ if (typeof globalThis !== "undefined") { globalThis.__SIBU_DEV__ = true; } '
1507
+ code: edits ? applyEdits(code, edits, plan.append) : null,
1508
+ usedTags: plan.usedTags,
1509
+ usesSvg: plan.usesSvg,
1510
+ compiledCount: edits ? plan.compiledCount : 0,
1511
+ skippedCount: plan.skippedCount
1457
1512
  };
1458
1513
  }
1459
- function staticOptimizationEdits(code) {
1460
- const analysis = analyzeStaticTemplates(code);
1461
- if (!analysis.hasStaticPatterns) return null;
1462
- const P = uniquePrefix(code);
1463
- const edits = analysis.patterns.map((pattern) => ({
1464
- start: pattern.start,
1465
- end: pattern.end,
1466
- // A call spanning several lines keeps its line breaks.
1467
- text: `${P}static(${jsStringLiteral(pattern.templateHtml)}${lineBreaksIn(code, pattern.start, pattern.end)})`
1468
- }));
1469
- const append = `
1470
- import { staticTemplate as ${P}staticTemplate } from "sibujs/performance";
1471
- function ${P}static(markup) {
1472
- return document.adoptNode(${P}staticTemplate(markup));
1473
- }
1474
- `;
1475
- return { edits, append };
1476
- }
1477
- function resolveDevMode(explicit, info) {
1478
- if (explicit !== void 0) return explicit;
1479
- if (info?.command === "build") return info.mode === "development";
1480
- if (info?.command === "serve") return true;
1481
- return typeof process !== "undefined" && process.env?.NODE_ENV !== "production";
1482
- }
1483
- function sibuVitePlugin(options = {}) {
1484
- const {
1485
- hmr = true,
1486
- pureAnnotations = true,
1487
- include = DEFAULT_INCLUDE,
1488
- exclude = DEFAULT_EXCLUDE,
1489
- devMode,
1490
- staticOptimize = false,
1491
- compileTemplates
1492
- } = options;
1493
- let isDevMode = resolveDevMode(devMode, void 0);
1514
+
1515
+ // src/build/declarations.ts
1516
+ var RECOMMENDED_OPTIONS = {
1517
+ target: "ES2020",
1518
+ module: "ESNext",
1519
+ moduleResolution: "bundler",
1520
+ strict: true,
1521
+ esModuleInterop: true,
1522
+ skipLibCheck: true,
1523
+ declaration: true,
1524
+ declarationMap: true,
1525
+ sourceMap: true,
1526
+ jsx: "preserve",
1527
+ lib: ["ES2020", "DOM", "DOM.Iterable"],
1528
+ isolatedModules: true,
1529
+ resolveJsonModule: true,
1530
+ allowSyntheticDefaultImports: true,
1531
+ forceConsistentCasingInFileNames: true,
1532
+ noUncheckedIndexedAccess: true,
1533
+ noEmit: false
1534
+ };
1535
+ var REQUIRED_OPTIONS = {
1536
+ strict: {
1537
+ value: true,
1538
+ reason: "SibuJS relies on strict type checking for safe reactive state handling."
1539
+ },
1540
+ esModuleInterop: {
1541
+ value: true,
1542
+ reason: "Required for correct ESM interop with SibuJS module exports."
1543
+ },
1544
+ moduleResolution: {
1545
+ value: ["node", "bundler", "node16", "nodenext"],
1546
+ reason: "SibuJS requires a module resolution strategy that supports package.json exports."
1547
+ }
1548
+ };
1549
+ var SUGGESTED_OPTIONS = {
1550
+ declarationMap: {
1551
+ value: true,
1552
+ reason: 'Enables "Go to Definition" to navigate to SibuJS source files in your IDE.'
1553
+ },
1554
+ sourceMap: {
1555
+ value: true,
1556
+ reason: "Enables source map debugging for SibuJS components."
1557
+ },
1558
+ noUncheckedIndexedAccess: {
1559
+ value: true,
1560
+ reason: "Improves type safety when accessing reactive state objects."
1561
+ },
1562
+ isolatedModules: {
1563
+ value: true,
1564
+ reason: "Required for compatibility with esbuild/swc transpilers used by Vite."
1565
+ }
1566
+ };
1567
+ function generateTsConfig(options) {
1568
+ const { target = "ES2020", outDir = "dist", declarationMap = true, paths } = options || {};
1569
+ const compilerOptions = {
1570
+ ...RECOMMENDED_OPTIONS,
1571
+ target,
1572
+ outDir,
1573
+ declarationMap
1574
+ };
1575
+ if (paths && Object.keys(paths).length > 0) {
1576
+ compilerOptions.baseUrl = ".";
1577
+ compilerOptions.paths = {
1578
+ // Default sibujs alias
1579
+ sibujs: ["node_modules/sibujs/dist/index.d.ts"],
1580
+ "sibujs/*": ["node_modules/sibujs/dist/*"],
1581
+ // User-provided paths
1582
+ ...paths
1583
+ };
1584
+ }
1494
1585
  return {
1495
- name: "sibu-vite-plugin",
1496
- enforce: "pre",
1497
- config(_userConfig, env) {
1498
- isDevMode = resolveDevMode(devMode, env);
1499
- return {
1500
- // Optimize dependency pre-bundling for sibujs
1501
- optimizeDeps: {
1502
- include: ["sibujs"]
1503
- },
1504
- // Ensure sibujs is treated correctly for SSR
1505
- ssr: {
1506
- noExternal: ["sibujs"]
1507
- },
1508
- // Define global constants for dead code elimination
1509
- define: {
1510
- __SIBU_DEV__: JSON.stringify(isDevMode),
1511
- __SIBU_HMR__: JSON.stringify(hmr)
1512
- },
1513
- // Enable source maps in dev
1514
- build: {
1515
- sourcemap: isDevMode
1516
- }
1517
- };
1518
- },
1519
- configResolved(config) {
1520
- isDevMode = resolveDevMode(devMode, config);
1521
- },
1522
- transform(code, id) {
1523
- if (!matchesPattern(id, include) || matchesPattern(id, exclude)) {
1524
- return null;
1525
- }
1526
- if (!code.includes("sibujs")) return null;
1527
- if (!scanModule(code)) return null;
1528
- const edits = [];
1529
- let append = "";
1530
- if (isDevMode && importsSibu(code)) {
1531
- const prologue = devPrologueEdit(code);
1532
- if (prologue) edits.push(prologue);
1533
- }
1534
- if (pureAnnotations) {
1535
- edits.push(...pureAnnotationEdits(code));
1536
- }
1537
- const shouldCompile = compileTemplates ?? !isDevMode;
1538
- if (shouldCompile) {
1539
- const plan = planHtmlTemplates(code);
1540
- if (plan.compiledCount > 0) {
1541
- edits.push(...plan.edits);
1542
- append += plan.append;
1543
- }
1544
- }
1545
- if (staticOptimize) {
1546
- const optimized = staticOptimizationEdits(code);
1547
- if (optimized) {
1548
- edits.push(...optimized.edits);
1549
- append += optimized.append;
1550
- }
1551
- }
1552
- if (edits.length === 0 && append === "") return null;
1553
- const sorted = normalizeEdits(edits);
1554
- if (!sorted) return null;
1555
- const transformed = applyEdits(code, sorted, append);
1556
- if (transformed === code) return null;
1557
- return {
1558
- code: transformed,
1559
- map: editsSourceMap(code, sorted, append, id)
1560
- };
1561
- },
1562
- handleHotUpdate(ctx) {
1563
- if (!hmr) return;
1564
- const { file } = ctx;
1565
- if (matchesPattern(file, include) && !matchesPattern(file, exclude)) {
1566
- if (isDevMode) {
1567
- console.log(`[sibu-vite-plugin] HMR update: ${file}`);
1568
- }
1569
- return;
1570
- }
1571
- }
1586
+ compilerOptions,
1587
+ include: ["src/**/*.ts", "src/**/*.tsx"],
1588
+ exclude: ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
1572
1589
  };
1573
1590
  }
1574
- function createViteConfig(options = {}) {
1575
- const { entry = "src/main.ts", outDir = "dist", ssr = false, overrides = {} } = options;
1576
- const baseConfig = {
1577
- // Use the SibuJS Vite plugin
1578
- plugins: [sibuVitePlugin()],
1579
- // Build configuration
1580
- build: {
1581
- outDir,
1582
- target: "es2020",
1583
- minify: "esbuild",
1584
- sourcemap: true,
1585
- // Library mode configuration when building a library
1586
- lib: ssr ? void 0 : {
1587
- entry,
1588
- formats: ["es", "cjs"]
1589
- },
1590
- // Rollup-specific options
1591
- rollupOptions: {
1592
- input: ssr ? entry : void 0,
1593
- output: {
1594
- // Ensure consistent chunk naming
1595
- chunkFileNames: "chunks/[name]-[hash].js",
1596
- // Preserve pure annotations
1597
- generatedCode: {
1598
- constBindings: true
1599
- }
1600
- },
1601
- // Tree-shaking configuration
1602
- treeshake: {
1603
- moduleSideEffects: false,
1604
- propertyReadSideEffects: false,
1605
- annotations: true
1606
- }
1591
+ function validateTsConfig(config) {
1592
+ const warnings = [];
1593
+ const suggestions = [];
1594
+ const compilerOptions = config.compilerOptions || {};
1595
+ for (const [option, requirement] of Object.entries(REQUIRED_OPTIONS)) {
1596
+ const currentValue = compilerOptions[option];
1597
+ if (Array.isArray(requirement.value)) {
1598
+ if (currentValue === void 0) {
1599
+ warnings.push(
1600
+ `Missing required option "${option}". ${requirement.reason} Recommended value: one of ${requirement.value.map((v) => `"${v}"`).join(", ")}.`
1601
+ );
1602
+ } else if (!requirement.value.includes(currentValue)) {
1603
+ warnings.push(
1604
+ `Option "${option}" is set to "${currentValue}" which may not be compatible with SibuJS. ${requirement.reason} Recommended: one of ${requirement.value.map((v) => `"${v}"`).join(", ")}.`
1605
+ );
1607
1606
  }
1608
- },
1609
- // Resolve configuration
1610
- resolve: {
1611
- // Prefer ESM versions of packages
1612
- mainFields: ["module", "jsnext:main", "jsnext", "main"],
1613
- extensions: [".ts", ".tsx", ".js", ".jsx", ".json"]
1614
- },
1615
- // SSR configuration
1616
- ...ssr ? {
1617
- ssr: {
1618
- noExternal: ["sibujs"],
1619
- target: "node"
1620
- },
1621
- build: {
1622
- outDir,
1623
- target: "node18",
1624
- ssr: true,
1625
- rollupOptions: {
1626
- input: entry,
1627
- output: {
1628
- format: "esm"
1629
- }
1630
- }
1607
+ } else {
1608
+ if (currentValue === void 0) {
1609
+ warnings.push(
1610
+ `Missing required option "${option}". ${requirement.reason} Recommended value: ${JSON.stringify(requirement.value)}.`
1611
+ );
1612
+ } else if (currentValue !== requirement.value) {
1613
+ warnings.push(
1614
+ `Option "${option}" is set to ${JSON.stringify(currentValue)} but SibuJS recommends ${JSON.stringify(requirement.value)}. ${requirement.reason}`
1615
+ );
1631
1616
  }
1632
- } : {},
1633
- // Optimize dependency handling
1634
- optimizeDeps: {
1635
- include: ["sibujs"],
1636
- // Force pre-bundling of sibujs for faster dev startup
1637
- force: false
1638
- },
1639
- // Environment variable handling
1640
- define: {
1641
- __SIBU_SSR__: JSON.stringify(ssr)
1642
1617
  }
1643
- };
1644
- return deepMerge(baseConfig, overrides);
1645
- }
1646
- function deepMerge(target, source) {
1647
- const result = { ...target };
1648
- for (const key of Object.keys(source)) {
1649
- if (source[key] && typeof source[key] === "object" && !Array.isArray(source[key]) && target[key] && typeof target[key] === "object" && !Array.isArray(target[key])) {
1650
- result[key] = deepMerge(target[key], source[key]);
1651
- } else {
1652
- result[key] = source[key];
1618
+ }
1619
+ for (const [option, suggestion] of Object.entries(SUGGESTED_OPTIONS)) {
1620
+ const currentValue = compilerOptions[option];
1621
+ if (currentValue === void 0 || currentValue !== suggestion.value) {
1622
+ suggestions.push(`Consider setting "${option}" to ${JSON.stringify(suggestion.value)}. ${suggestion.reason}`);
1653
1623
  }
1654
1624
  }
1655
- return result;
1625
+ const target = typeof compilerOptions.target === "string" ? compilerOptions.target.toUpperCase() : void 0;
1626
+ if (target) {
1627
+ const es2020Plus = ["ES2020", "ES2021", "ES2022", "ES2023", "ES2024", "ESNEXT"];
1628
+ if (!es2020Plus.includes(target)) {
1629
+ warnings.push(
1630
+ `Target "${target}" may not support all features used by SibuJS. Recommended: "ES2020" or newer. SibuJS uses optional chaining, nullish coalescing, and other ES2020+ features.`
1631
+ );
1632
+ }
1633
+ }
1634
+ const module = typeof compilerOptions.module === "string" ? compilerOptions.module.toLowerCase() : void 0;
1635
+ if (module && !["esnext", "es2020", "es2022", "node16", "nodenext"].includes(module)) {
1636
+ suggestions.push(
1637
+ `Module "${compilerOptions.module}" may not provide optimal results with SibuJS. Consider using "ESNext" for best tree-shaking and bundle optimization.`
1638
+ );
1639
+ }
1640
+ const lib = compilerOptions.lib;
1641
+ if (Array.isArray(lib)) {
1642
+ const hasDom = lib.some((l) => l.toUpperCase() === "DOM" || l.toUpperCase() === "DOM.ITERABLE");
1643
+ if (!hasDom) {
1644
+ suggestions.push(
1645
+ 'The "lib" array does not include "DOM". SibuJS requires DOM types for component rendering. Add "DOM" and "DOM.Iterable" to your lib array.'
1646
+ );
1647
+ }
1648
+ }
1649
+ if (Array.isArray(config.include)) {
1650
+ const includesTs = config.include.some(
1651
+ (pattern) => pattern.includes(".ts") || pattern.includes("*")
1652
+ );
1653
+ if (!includesTs) {
1654
+ suggestions.push(
1655
+ 'Your "include" patterns may not capture TypeScript files. Ensure patterns like "src/**/*.ts" are included.'
1656
+ );
1657
+ }
1658
+ }
1659
+ return {
1660
+ valid: warnings.length === 0,
1661
+ warnings,
1662
+ suggestions
1663
+ };
1656
1664
  }
1657
1665
 
1658
- // src/build/webpack.ts
1659
- function addPureAnnotations(source) {
1660
- return injectPureAnnotations(source);
1661
- }
1662
- function sibuWebpackPlugin(options = {}) {
1663
- const { devMode } = options;
1664
- return {
1665
- name: "SibuWebpackPlugin",
1666
- apply(compiler) {
1667
- if (options.pureAnnotations === true) {
1668
- console.warn(
1669
- '[SibuWebpackPlugin] `pureAnnotations` is not applied by the plugin: webpack needs a loader file. Create sibu-pure-loader.cjs containing `module.exports = require("sibujs/build").createPureAnnotationsLoader();` and add a module rule `{ test: /\\.[cm]?[jt]sx?$/, exclude: /node_modules/, use: "./sibu-pure-loader.cjs" }`.'
1670
- );
1671
- }
1672
- compiler.hooks?.compilation?.tap("SibuWebpackPlugin", (compilation) => {
1673
- const comp = compilation;
1674
- if (comp.hooks?.optimizeModules) {
1675
- comp.hooks.optimizeModules.tap("SibuWebpackPlugin", () => {
1676
- });
1677
- }
1678
- });
1679
- compiler.hooks?.afterResolvers?.tap("SibuWebpackPlugin", () => {
1680
- if (!compiler.options.resolve) {
1681
- compiler.options.resolve = {};
1682
- }
1683
- if (!compiler.options.resolve.mainFields) {
1684
- compiler.options.resolve.mainFields = ["module", "main"];
1685
- }
1686
- if (!compiler.options.resolve.mainFields.includes("module")) {
1687
- compiler.options.resolve.mainFields.unshift("module");
1666
+ // src/build/ide.ts
1667
+ function getComponentMetadata() {
1668
+ return [
1669
+ // ── Reactive Primitives ──────────────────────────────────────────
1670
+ {
1671
+ name: "signal",
1672
+ description: "Creates a reactive signal that holds a value. Returns a [getter, setter] tuple. The getter registers reactive dependencies when called.",
1673
+ props: [
1674
+ {
1675
+ name: "initial",
1676
+ type: "T",
1677
+ required: true,
1678
+ description: "The initial value of the state"
1688
1679
  }
1689
- });
1690
- const tapDoneLogger = () => compiler.hooks?.done?.tap("SibuWebpackPlugin", (stats) => {
1691
- const statsObj = stats;
1692
- const info = statsObj?.toJson?.({ modules: false, chunks: false });
1693
- if (info) {
1694
- console.log(`[SibuWebpackPlugin] Build completed in ${info.time || 0}ms`);
1695
- const warnings = info.warnings;
1696
- if (warnings?.length) {
1697
- console.log(`[SibuWebpackPlugin] ${warnings.length} warning(s)`);
1698
- }
1680
+ ]
1681
+ },
1682
+ {
1683
+ name: "effect",
1684
+ description: "Runs a side effect function immediately and re-runs it whenever any reactive dependency changes. Returns a cleanup function.",
1685
+ props: [
1686
+ {
1687
+ name: "effectFn",
1688
+ type: "() => void",
1689
+ required: true,
1690
+ description: "The effect function to run. It is tracked for reactive dependencies."
1699
1691
  }
1700
- });
1701
- if (devMode === true) tapDoneLogger();
1702
- compiler.hooks?.environment?.tap("SibuWebpackPlugin", () => {
1703
- const isDevMode = devMode ?? resolveWebpackDevMode(compiler.options);
1704
- if (devMode === void 0 && isDevMode) tapDoneLogger();
1705
- if (!compiler.options.plugins) {
1706
- compiler.options.plugins = [];
1692
+ ]
1693
+ },
1694
+ {
1695
+ name: "derived",
1696
+ description: "Creates a derived reactive signal whose value updates automatically when dependencies change. Returns a getter function.",
1697
+ props: [
1698
+ {
1699
+ name: "getter",
1700
+ type: "() => T",
1701
+ required: true,
1702
+ description: "Function returning the computed value based on reactive signals"
1707
1703
  }
1708
- const defines = {
1709
- __SIBU_DEV__: JSON.stringify(isDevMode)
1710
- };
1711
- compiler.__sibuDefines = defines;
1712
- const DefinePlugin = compiler.webpack?.DefinePlugin;
1713
- if (DefinePlugin) {
1714
- new DefinePlugin(defines).apply(compiler);
1704
+ ]
1705
+ },
1706
+ {
1707
+ name: "ref",
1708
+ description: "Creates a mutable reference object that persists across renders. Updating a ref does NOT trigger re-renders.",
1709
+ props: [
1710
+ {
1711
+ name: "initial",
1712
+ type: "T",
1713
+ required: false,
1714
+ default: "undefined",
1715
+ description: "Optional initial value for the ref"
1715
1716
  }
1716
- });
1717
- }
1718
- };
1719
- }
1720
- function resolveWebpackDevMode(options) {
1721
- const mode = options.mode;
1722
- if (mode === "development") return true;
1723
- if (mode === "production" || mode === void 0) return false;
1724
- return typeof process !== "undefined" && process.env?.NODE_ENV !== "production";
1725
- }
1726
- function createPureAnnotationsLoader() {
1727
- return function sibuPureAnnotationsLoader(source) {
1728
- return addPureAnnotations(source);
1729
- };
1730
- }
1731
- function createWebpackConfig(options = {}) {
1732
- const { entry = "./src/index.ts", outputPath = "dist", mode = "production" } = options;
1733
- const isDev = mode === "development";
1734
- return {
1735
- mode,
1736
- entry,
1737
- output: {
1738
- path: outputPath,
1739
- filename: isDev ? "[name].js" : "[name].[contenthash:8].js",
1740
- chunkFilename: isDev ? "[name].chunk.js" : "[name].[contenthash:8].chunk.js",
1741
- clean: true,
1742
- // Use ESM output
1743
- module: true,
1744
- library: {
1745
- type: "module"
1746
- }
1717
+ ]
1747
1718
  },
1748
- // Enable experiments for ESM output
1749
- experiments: {
1750
- outputModule: true
1719
+ {
1720
+ name: "watch",
1721
+ description: "Watches a reactive getter and calls a callback with (newValue, oldValue) when the watched value changes. Returns a teardown function.",
1722
+ props: [
1723
+ {
1724
+ name: "getter",
1725
+ type: "() => T",
1726
+ required: true,
1727
+ description: "Function that returns the value to watch"
1728
+ },
1729
+ {
1730
+ name: "callback",
1731
+ type: "(value: T, prev: T | undefined) => void",
1732
+ required: true,
1733
+ description: "Function called when the watched value changes"
1734
+ }
1735
+ ]
1751
1736
  },
1752
- resolve: {
1753
- extensions: [".ts", ".tsx", ".js", ".jsx", ".json"],
1754
- mainFields: ["module", "main"],
1755
- alias: {}
1737
+ {
1738
+ name: "store",
1739
+ description: "Creates a global store with reactive properties and subscription support. Returns a [store, actions] tuple.",
1740
+ props: [
1741
+ {
1742
+ name: "initialState",
1743
+ type: "T extends object",
1744
+ required: true,
1745
+ description: "Initial state object for the store"
1746
+ }
1747
+ ]
1756
1748
  },
1757
- module: {
1758
- rules: [
1759
- // TypeScript/JavaScript handling
1749
+ // ── Lifecycle ────────────────────────────────────────────────────
1750
+ {
1751
+ name: "onMount",
1752
+ description: "Runs a callback once the component element has been inserted into the DOM. Uses queueMicrotask for deferred execution.",
1753
+ props: [
1760
1754
  {
1761
- test: /\.[jt]sx?$/,
1762
- exclude: /node_modules/,
1763
- use: [
1764
- {
1765
- // Users should configure their preferred TS loader
1766
- // (ts-loader, babel-loader, esbuild-loader, swc-loader)
1767
- loader: "ts-loader",
1768
- options: {
1769
- transpileOnly: true,
1770
- compilerOptions: {
1771
- module: "esnext",
1772
- moduleResolution: "node",
1773
- target: "es2020"
1774
- }
1775
- }
1776
- }
1777
- ]
1755
+ name: "callback",
1756
+ type: "() => void | CleanupFn",
1757
+ required: true,
1758
+ description: "Function to run after mount. May return a cleanup function."
1759
+ },
1760
+ {
1761
+ name: "element",
1762
+ type: "HTMLElement",
1763
+ required: false,
1764
+ description: "Optional element to observe; if provided, waits until it is connected."
1778
1765
  }
1779
1766
  ]
1780
1767
  },
1781
- plugins: [
1782
- // SibuJS plugin for optimizations
1783
- sibuWebpackPlugin({ devMode: isDev })
1784
- ],
1785
- optimization: {
1786
- minimize: !isDev,
1787
- // Enable tree-shaking
1788
- usedExports: true,
1789
- sideEffects: true,
1790
- // Split chunks for better caching
1791
- splitChunks: isDev ? false : {
1792
- chunks: "all",
1793
- cacheGroups: {
1794
- // Separate sibujs framework code into its own chunk (the package
1795
- // directory is `sibujs`; matching `sibu` never selected anything)
1796
- sibu: {
1797
- test: /[\\/]node_modules[\\/]sibujs[\\/]/,
1798
- name: "sibu",
1799
- chunks: "all",
1800
- priority: 20
1801
- },
1802
- // Separate other vendor code
1803
- vendor: {
1804
- test: /[\\/]node_modules[\\/]/,
1805
- name: "vendor",
1806
- chunks: "all",
1807
- priority: 10
1808
- }
1768
+ {
1769
+ name: "onUnmount",
1770
+ description: "Runs a callback when the given element is removed from the DOM. Uses MutationObserver to watch for disconnection.",
1771
+ props: [
1772
+ {
1773
+ name: "callback",
1774
+ type: "() => void",
1775
+ required: true,
1776
+ description: "Function to run on unmount"
1777
+ },
1778
+ {
1779
+ name: "element",
1780
+ type: "HTMLElement",
1781
+ required: true,
1782
+ description: "The element to watch for removal"
1809
1783
  }
1810
- }
1784
+ ]
1811
1785
  },
1812
- // Source maps
1813
- devtool: isDev ? "eval-cheap-module-source-map" : "source-map",
1814
- // Dev server configuration
1815
- devServer: isDev ? {
1816
- hot: true,
1817
- open: true,
1818
- port: 3e3,
1819
- historyApiFallback: true
1820
- } : void 0,
1821
- // Performance hints
1822
- performance: {
1823
- hints: isDev ? false : "warning",
1824
- maxEntrypointSize: 25e4,
1825
- maxAssetSize: 25e4
1786
+ // ── Rendering ────────────────────────────────────────────────────
1787
+ {
1788
+ name: "mount",
1789
+ description: "Mounts a root component into a DOM element. Returns an object with the mounted node and an unmount function.",
1790
+ props: [
1791
+ {
1792
+ name: "component",
1793
+ type: "(() => HTMLElement) | HTMLElement | Node",
1794
+ required: true,
1795
+ description: "Function returning HTMLElement, or an HTMLElement directly"
1796
+ },
1797
+ {
1798
+ name: "container",
1799
+ type: "HTMLElement",
1800
+ required: true,
1801
+ description: "Target DOM element to append the result"
1802
+ }
1803
+ ]
1826
1804
  },
1827
- // Cache for faster rebuilds
1828
- cache: {
1829
- type: "filesystem",
1830
- buildDependencies: {
1831
- config: []
1832
- }
1805
+ {
1806
+ name: "each",
1807
+ description: "Renders a list of nodes efficiently with key-based diffing and LIS-based move minimization.",
1808
+ props: [
1809
+ {
1810
+ name: "getArray",
1811
+ type: "() => T[]",
1812
+ required: true,
1813
+ description: "A reactive getter returning an array"
1814
+ },
1815
+ {
1816
+ name: "render",
1817
+ type: "(item: T, index: number) => NodeChild",
1818
+ required: true,
1819
+ description: "A function that returns a NodeChild for each item"
1820
+ },
1821
+ {
1822
+ name: "options",
1823
+ type: "{ key: (item: T) => string | number }",
1824
+ required: true,
1825
+ description: "A key function for unique identity of items"
1826
+ }
1827
+ ]
1828
+ },
1829
+ {
1830
+ name: "lazy",
1831
+ description: "Enables code-splitting by deferring the import of a component until it is first rendered.",
1832
+ props: [
1833
+ {
1834
+ name: "importFn",
1835
+ type: "() => Promise<{ default: Component }>",
1836
+ required: true,
1837
+ description: "Dynamic import function returning { default: Component }"
1838
+ }
1839
+ ]
1840
+ },
1841
+ {
1842
+ name: "Suspense",
1843
+ description: "Provides a fallback UI while lazy or async nodes are loading.",
1844
+ props: [
1845
+ {
1846
+ name: "nodes",
1847
+ type: "() => HTMLElement",
1848
+ required: true,
1849
+ description: "Function that returns the async/lazy component"
1850
+ },
1851
+ {
1852
+ name: "fallback",
1853
+ type: "() => HTMLElement",
1854
+ required: true,
1855
+ description: "Function that returns the loading UI"
1856
+ }
1857
+ ]
1858
+ },
1859
+ // ── Context ──────────────────────────────────────────────────────
1860
+ {
1861
+ name: "context",
1862
+ description: "Creates a context for dependency injection across component trees without prop drilling. Returns a Context object with provide, use, get, and set methods.",
1863
+ props: [
1864
+ {
1865
+ name: "defaultValue",
1866
+ type: "T",
1867
+ required: true,
1868
+ description: "The fallback value when no provider is found"
1869
+ }
1870
+ ]
1871
+ },
1872
+ // ── Dynamic Components ───────────────────────────────────────────
1873
+ {
1874
+ name: "DynamicComponent",
1875
+ description: "Reactively switches between components based on a reactive getter returning a component name or function.",
1876
+ props: [
1877
+ {
1878
+ name: "is",
1879
+ type: "() => string | Component",
1880
+ required: true,
1881
+ description: "Reactive getter returning component name (string) or component function"
1882
+ }
1883
+ ]
1884
+ },
1885
+ {
1886
+ name: "registerComponent",
1887
+ description: "Registers a component by name for dynamic resolution via DynamicComponent or resolveComponent.",
1888
+ props: [
1889
+ {
1890
+ name: "name",
1891
+ type: "string",
1892
+ required: true,
1893
+ description: "Unique component identifier"
1894
+ },
1895
+ {
1896
+ name: "component",
1897
+ type: "() => HTMLElement",
1898
+ required: true,
1899
+ description: "The component function"
1900
+ }
1901
+ ]
1902
+ },
1903
+ // ── HTML Tag Factories ───────────────────────────────────────────
1904
+ {
1905
+ name: "div",
1906
+ description: "Creates a reactive <div> element. Accepts TagProps including class, style, on, nodes, and arbitrary attributes.",
1907
+ props: [
1908
+ {
1909
+ name: "props",
1910
+ type: "TagProps",
1911
+ required: false,
1912
+ default: "{}",
1913
+ description: "Props object with id, class, style, ref, nodes, on, and other attributes"
1914
+ }
1915
+ ],
1916
+ events: [
1917
+ {
1918
+ name: "click",
1919
+ description: "Fires when the element is clicked"
1920
+ },
1921
+ {
1922
+ name: "input",
1923
+ description: "Fires when user input is received"
1924
+ }
1925
+ ]
1833
1926
  }
1834
- };
1835
- }
1836
-
1837
- // src/build/cdn.ts
1838
- var PACKAGE_NAME = "sibujs";
1839
- var SUBPATH_ENTRIES = [
1840
- "data",
1841
- "browser",
1842
- "patterns",
1843
- "motion",
1844
- "ui",
1845
- "widgets",
1846
- "ssr",
1847
- "devtools",
1848
- "performance",
1849
- "ecosystem",
1850
- "plugins",
1851
- "build",
1852
- "testing",
1853
- "extras"
1854
- ];
1855
- function registerGlobal() {
1856
- if (typeof window === "undefined") return;
1857
- window.Sibu = { ...index_exports };
1858
- }
1859
- function umdWrapper(name, factory) {
1860
- const factoryStr = factory.toString();
1861
- return `(function (root, factory) {
1862
- if (typeof define === 'function' && define.amd) {
1863
- // AMD (RequireJS)
1864
- define([], factory);
1865
- } else if (typeof module === 'object' && module.exports) {
1866
- // CommonJS (Node.js)
1867
- module.exports = factory();
1868
- } else {
1869
- // Browser globals
1870
- root.${name} = factory();
1871
- }
1872
- }(typeof self !== 'undefined' ? self : this, ${factoryStr}));`;
1927
+ ];
1873
1928
  }
1874
- var cdnUrls = {
1875
- /**
1876
- * Generate an unpkg CDN URL.
1877
- * @param version - Package version (defaults to 'latest')
1878
- */
1879
- unpkg: (version) => `https://unpkg.com/${PACKAGE_NAME}@${version || "latest"}/dist/cdn.global.js`,
1880
- /**
1881
- * Generate a jsDelivr CDN URL.
1882
- * @param version - Package version (defaults to 'latest')
1883
- */
1884
- jsdelivr: (version) => `https://cdn.jsdelivr.net/npm/${PACKAGE_NAME}@${version || "latest"}/dist/cdn.global.js`,
1885
- /**
1886
- * Generate a Skypack CDN URL (ESM-native).
1887
- * @param version - Package version (defaults to 'latest')
1888
- */
1889
- skypack: (version) => `https://cdn.skypack.dev/${PACKAGE_NAME}@${version || "latest"}`,
1890
- /**
1891
- * Generate a complete HTML script tag for including SibuJS from a CDN.
1892
- *
1893
- * @param provider - CDN provider to use (defaults to 'jsdelivr')
1894
- * @param version - Package version (defaults to 'latest')
1895
- * @returns An HTML script tag string
1896
- *
1897
- * @example
1898
- * ```ts
1899
- * cdnUrls.scriptTag('jsdelivr', '1.0.0')
1900
- * // => '<script src="https://cdn.jsdelivr.net/npm/sibujs@1.0.0/dist/cdn.global.js"></script>'
1901
- * ```
1902
- */
1903
- scriptTag: (provider = "jsdelivr", version) => {
1904
- const url = cdnUrls[provider](version);
1905
- if (provider === "skypack") {
1906
- return `<script type="module">
1907
- import * as Sibu from '${url}';
1908
- window.Sibu = Sibu;
1909
- </script>`;
1929
+ function generateVSCodeSnippets() {
1930
+ return {
1931
+ "SibuJS Component": {
1932
+ prefix: "sibu-component",
1933
+ body: [
1934
+ "import { div } from 'sibujs';",
1935
+ "",
1936
+ "export function ${1:ComponentName}(): HTMLElement {",
1937
+ " return div({",
1938
+ " class: '${2:component-class}',",
1939
+ " nodes: [",
1940
+ " ${3:// nodes here}",
1941
+ " ],",
1942
+ " });",
1943
+ "}"
1944
+ ],
1945
+ description: "Create a basic SibuJS component"
1946
+ },
1947
+ "SibuJS Component with State": {
1948
+ prefix: "sibu-component-state",
1949
+ body: [
1950
+ "import { div, span } from 'sibujs';",
1951
+ "import { signal } from 'sibujs';",
1952
+ "",
1953
+ "export function ${1:ComponentName}(): HTMLElement {",
1954
+ " const [${2:value}, ${3:setValue}] = signal(${4:initialValue});",
1955
+ "",
1956
+ " return div({",
1957
+ " class: '${5:component-class}',",
1958
+ " nodes: [",
1959
+ " () => span({ nodes: String(${2:value}()) }),",
1960
+ " ],",
1961
+ " });",
1962
+ "}"
1963
+ ],
1964
+ description: "Create a SibuJS component with reactive state"
1965
+ },
1966
+ "SibuJS signal": {
1967
+ prefix: "sibu-state",
1968
+ body: ["const [${1:value}, ${2:setValue}] = signal(${3:initialValue});"],
1969
+ description: "Create a reactive state with signal"
1970
+ },
1971
+ "SibuJS effect": {
1972
+ prefix: "sibu-effect",
1973
+ body: ["const cleanup = effect(() => {", " ${1:// effect logic}", "});"],
1974
+ description: "Create a reactive side effect with effect"
1975
+ },
1976
+ "SibuJS derived": {
1977
+ prefix: "sibu-computed",
1978
+ body: ["const ${1:computed} = derived(() => {", " return ${2:// derived value};", "});"],
1979
+ description: "Create a derived reactive value with derived"
1980
+ },
1981
+ "SibuJS watch": {
1982
+ prefix: "sibu-watch",
1983
+ body: [
1984
+ "const teardown = watch(",
1985
+ " () => ${1:watchedValue}(),",
1986
+ " (newVal, oldVal) => {",
1987
+ " ${2:// handle change}",
1988
+ " }",
1989
+ ");"
1990
+ ],
1991
+ description: "Watch a reactive value for changes"
1992
+ },
1993
+ "SibuJS each": {
1994
+ prefix: "sibu-each",
1995
+ body: [
1996
+ "each(",
1997
+ " () => ${1:items}(),",
1998
+ " (item) => {",
1999
+ " return ${2:div({ nodes: () => String(item()) })};",
2000
+ " },",
2001
+ " { key: (item) => ${3:item.id} }",
2002
+ ")"
2003
+ ],
2004
+ description: "Create a reactive list with each()"
2005
+ },
2006
+ "SibuJS Context": {
2007
+ prefix: "sibu-context",
2008
+ body: [
2009
+ "const ${1:MyContext} = context(${2:defaultValue});",
2010
+ "",
2011
+ "// In provider component:",
2012
+ "// ${1:MyContext}.provide(value);",
2013
+ "",
2014
+ "// In consumer component:",
2015
+ "// const ${3:value} = ${1:MyContext}.use();"
2016
+ ],
2017
+ description: "Create a context for dependency injection"
2018
+ },
2019
+ "SibuJS store": {
2020
+ prefix: "sibu-store",
2021
+ body: [
2022
+ "const [${1:store}, { setState: ${2:setStore}, reset, subscribe }] = store({",
2023
+ " ${3:key}: ${4:value},",
2024
+ "});"
2025
+ ],
2026
+ description: "Create a reactive global store"
2027
+ },
2028
+ "SibuJS Lazy Component": {
2029
+ prefix: "sibu-lazy",
2030
+ body: ["const ${1:LazyComponent} = lazy(() => import('./${2:ComponentPath}'));"],
2031
+ description: "Create a lazy-loaded component"
2032
+ },
2033
+ "SibuJS Suspense": {
2034
+ prefix: "sibu-suspense",
2035
+ body: [
2036
+ "Suspense({",
2037
+ " nodes: () => ${1:LazyComponent}(),",
2038
+ " fallback: () => div({ nodes: '${2:Loading...}' }),",
2039
+ "})"
2040
+ ],
2041
+ description: "Wrap a lazy component with a loading fallback"
2042
+ },
2043
+ "SibuJS onMount": {
2044
+ prefix: "sibu-mount",
2045
+ body: ["onMount(() => {", " ${1:// runs after component enters the DOM}", "});"],
2046
+ description: "Schedule a callback to run after mount"
2047
+ },
2048
+ "SibuJS onUnmount": {
2049
+ prefix: "sibu-unmount",
2050
+ body: ["onUnmount(() => {", " ${1:// cleanup when removed from DOM}", "}, ${2:element});"],
2051
+ description: "Schedule a callback to run on unmount"
2052
+ },
2053
+ "SibuJS Event Handler": {
2054
+ prefix: "sibu-event",
2055
+ body: [
2056
+ "div({",
2057
+ " on: {",
2058
+ " ${1:click}: (e) => {",
2059
+ " ${2:// handle event}",
2060
+ " },",
2061
+ " },",
2062
+ " nodes: '${3:Click me}',",
2063
+ "})"
2064
+ ],
2065
+ description: "Create an element with event handlers"
2066
+ },
2067
+ "SibuJS Form": {
2068
+ prefix: "sibu-form",
2069
+ body: [
2070
+ "const form = form({",
2071
+ " ${1:fieldName}: {",
2072
+ " initial: '${2:}',",
2073
+ " validators: [",
2074
+ " (value) => !value ? '${3:Required}' : null,",
2075
+ " ],",
2076
+ " },",
2077
+ "});"
2078
+ ],
2079
+ description: "Create a reactive form with validation"
1910
2080
  }
1911
- return `<script src="${url}"></script>`;
1912
- }
1913
- };
1914
- function generateImportMap(baseUrl) {
1915
- const base = baseUrl || `https://cdn.jsdelivr.net/npm/${PACKAGE_NAME}@latest`;
1916
- const imports = {
1917
- // Main package entry
1918
- [PACKAGE_NAME]: `${base}/dist/index.js`
1919
2081
  };
1920
- for (const entry of SUBPATH_ENTRIES) {
1921
- imports[`${PACKAGE_NAME}/${entry}`] = `${base}/dist/${entry}.js`;
1922
- }
2082
+ }
2083
+ function generateLanguageConfig() {
1923
2084
  return {
1924
- imports,
1925
- /**
1926
- * Serialize the import map to a JSON string.
1927
- */
1928
- toJSON() {
1929
- return JSON.stringify({ imports }, null, 2);
2085
+ comments: {
2086
+ lineComment: "//",
2087
+ blockComment: ["/*", "*/"]
1930
2088
  },
1931
- /**
1932
- * Generate a complete `<script type="importmap">` tag.
1933
- */
1934
- toScriptTag() {
1935
- return `<script type="importmap">
1936
- ${JSON.stringify({ imports }, null, 2)}
1937
- </script>`;
1938
- }
2089
+ brackets: [
2090
+ ["{", "}"],
2091
+ ["[", "]"],
2092
+ ["(", ")"],
2093
+ ["<", ">"]
2094
+ ],
2095
+ autoClosingPairs: [
2096
+ { open: "{", close: "}" },
2097
+ { open: "[", close: "]" },
2098
+ { open: "(", close: ")" },
2099
+ { open: "<", close: ">" },
2100
+ { open: "'", close: "'" },
2101
+ { open: '"', close: '"' },
2102
+ { open: "`", close: "`" }
2103
+ ]
1939
2104
  };
1940
2105
  }
1941
-
1942
- // src/build/declarations.ts
1943
- var RECOMMENDED_OPTIONS = {
1944
- target: "ES2020",
1945
- module: "ESNext",
1946
- moduleResolution: "bundler",
1947
- strict: true,
1948
- esModuleInterop: true,
1949
- skipLibCheck: true,
1950
- declaration: true,
1951
- declarationMap: true,
1952
- sourceMap: true,
1953
- jsx: "preserve",
1954
- lib: ["ES2020", "DOM", "DOM.Iterable"],
1955
- isolatedModules: true,
1956
- resolveJsonModule: true,
1957
- allowSyntheticDefaultImports: true,
1958
- forceConsistentCasingInFileNames: true,
1959
- noUncheckedIndexedAccess: true,
1960
- noEmit: false
1961
- };
1962
- var REQUIRED_OPTIONS = {
1963
- strict: {
1964
- value: true,
1965
- reason: "SibuJS relies on strict type checking for safe reactive state handling."
1966
- },
1967
- esModuleInterop: {
1968
- value: true,
1969
- reason: "Required for correct ESM interop with SibuJS module exports."
1970
- },
1971
- moduleResolution: {
1972
- value: ["node", "bundler", "node16", "nodenext"],
1973
- reason: "SibuJS requires a module resolution strategy that supports package.json exports."
1974
- }
1975
- };
1976
- var SUGGESTED_OPTIONS = {
1977
- declarationMap: {
1978
- value: true,
1979
- reason: 'Enables "Go to Definition" to navigate to SibuJS source files in your IDE.'
1980
- },
1981
- sourceMap: {
1982
- value: true,
1983
- reason: "Enables source map debugging for SibuJS components."
1984
- },
1985
- noUncheckedIndexedAccess: {
1986
- value: true,
1987
- reason: "Improves type safety when accessing reactive state objects."
1988
- },
1989
- isolatedModules: {
1990
- value: true,
1991
- reason: "Required for compatibility with esbuild/swc transpilers used by Vite."
1992
- }
1993
- };
1994
- function generateTsConfig(options) {
1995
- const { target = "ES2020", outDir = "dist", declarationMap = true, paths } = options || {};
1996
- const compilerOptions = {
1997
- ...RECOMMENDED_OPTIONS,
1998
- target,
1999
- outDir,
2000
- declarationMap
2001
- };
2002
- if (paths && Object.keys(paths).length > 0) {
2003
- compilerOptions.baseUrl = ".";
2004
- compilerOptions.paths = {
2005
- // Default sibujs alias
2006
- sibujs: ["node_modules/sibujs/dist/index.d.ts"],
2007
- "sibujs/*": ["node_modules/sibujs/dist/*"],
2008
- // User-provided paths
2009
- ...paths
2010
- };
2011
- }
2106
+ function generateTypeStubs() {
2012
2107
  return {
2013
- compilerOptions,
2014
- include: ["src/**/*.ts", "src/**/*.tsx"],
2015
- exclude: ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
2108
+ signal: ["declare function signal<T>(initial: T): [() => T, (next: T | ((prev: T) => T)) => void];"].join("\n"),
2109
+ effect: ["declare function effect(effectFn: () => void): () => void;"].join("\n"),
2110
+ derived: ["declare function derived<T>(getter: () => T): () => T;"].join("\n"),
2111
+ ref: [
2112
+ "interface Ref<T> { current: T; }",
2113
+ "declare function ref<T>(initial: T): Ref<T>;",
2114
+ "declare function ref<T = undefined>(): Ref<T | undefined>;"
2115
+ ].join("\n"),
2116
+ watch: [
2117
+ "declare function watch<T>(getter: () => T, callback: (value: T, prev: T | undefined) => void): () => void;"
2118
+ ].join("\n"),
2119
+ store: [
2120
+ "interface StoreActions<T> {",
2121
+ " setState: (patch: Partial<T> | ((state: T) => T)) => void;",
2122
+ " reset: () => void;",
2123
+ " subscribe: (callback: (state: T) => void) => () => void;",
2124
+ " subscribeKey: <K extends keyof T>(key: K, callback: (value: T[K], prev: T[K]) => void) => () => void;",
2125
+ " getSnapshot: () => T;",
2126
+ "}",
2127
+ "declare function store<T extends object>(initialState: T): [{ readonly [K in keyof T]: T[K] }, StoreActions<T>];"
2128
+ ].join("\n"),
2129
+ mount: [
2130
+ "declare function mount(component: (() => HTMLElement) | HTMLElement | Node, container: HTMLElement | null): { node: Node; unmount: () => void };"
2131
+ ].join("\n"),
2132
+ each: [
2133
+ "declare function each<T>(getArray: () => T[], render: (item: T, index: number) => NodeChild, options: { key: (item: T) => string | number }): Comment;"
2134
+ ].join("\n"),
2135
+ onMount: ["declare function onMount(callback: () => void | (() => void), element?: HTMLElement): void;"].join("\n"),
2136
+ onUnmount: ["declare function onUnmount(callback: () => void, element: HTMLElement): void;"].join("\n"),
2137
+ context: [
2138
+ "interface Context<T> {",
2139
+ " provide(value: T): void;",
2140
+ " use(): () => T;",
2141
+ " get(): T;",
2142
+ " set(value: T): void;",
2143
+ "}",
2144
+ "declare function context<T>(defaultValue: T): Context<T>;"
2145
+ ].join("\n"),
2146
+ lazy: ["declare function lazy(importFn: () => Promise<{ default: () => HTMLElement }>): () => HTMLElement;"].join(
2147
+ "\n"
2148
+ ),
2149
+ Suspense: [
2150
+ "interface SuspenseProps { nodes: () => HTMLElement; fallback: () => HTMLElement; }",
2151
+ "declare function Suspense(props: SuspenseProps): HTMLElement;"
2152
+ ].join("\n"),
2153
+ tagFactory: [
2154
+ "interface TagProps {",
2155
+ " id?: string;",
2156
+ " class?: string | (() => string) | Record<string, boolean | (() => boolean)>;",
2157
+ " style?: Record<string, string | number | (() => string | number)> | string | (() => string);",
2158
+ " ref?: { current: unknown };",
2159
+ " nodes?: NodeChildren;",
2160
+ " on?: Record<string, (ev: Event) => void>;",
2161
+ " [attr: string]: unknown;",
2162
+ "}",
2163
+ "declare function tagFactory(tag: string, ns?: string): (first?: TagProps | NodeChildren, second?: NodeChildren) => Element;"
2164
+ ].join("\n"),
2165
+ DynamicComponent: ["declare function DynamicComponent(is: () => string | (() => HTMLElement)): HTMLElement;"].join(
2166
+ "\n"
2167
+ ),
2168
+ registerComponent: ["declare function registerComponent(name: string, component: () => HTMLElement): void;"].join(
2169
+ "\n"
2170
+ )
2016
2171
  };
2017
2172
  }
2018
- function validateTsConfig(config) {
2019
- const warnings = [];
2020
- const suggestions = [];
2021
- const compilerOptions = config.compilerOptions || {};
2022
- for (const [option, requirement] of Object.entries(REQUIRED_OPTIONS)) {
2023
- const currentValue = compilerOptions[option];
2024
- if (Array.isArray(requirement.value)) {
2025
- if (currentValue === void 0) {
2026
- warnings.push(
2027
- `Missing required option "${option}". ${requirement.reason} Recommended value: one of ${requirement.value.map((v) => `"${v}"`).join(", ")}.`
2028
- );
2029
- } else if (!requirement.value.includes(currentValue)) {
2030
- warnings.push(
2031
- `Option "${option}" is set to "${currentValue}" which may not be compatible with SibuJS. ${requirement.reason} Recommended: one of ${requirement.value.map((v) => `"${v}"`).join(", ")}.`
2032
- );
2033
- }
2034
- } else {
2035
- if (currentValue === void 0) {
2036
- warnings.push(
2037
- `Missing required option "${option}". ${requirement.reason} Recommended value: ${JSON.stringify(requirement.value)}.`
2038
- );
2039
- } else if (currentValue !== requirement.value) {
2040
- warnings.push(
2041
- `Option "${option}" is set to ${JSON.stringify(currentValue)} but SibuJS recommends ${JSON.stringify(requirement.value)}. ${requirement.reason}`
2042
- );
2043
- }
2044
- }
2045
- }
2046
- for (const [option, suggestion] of Object.entries(SUGGESTED_OPTIONS)) {
2047
- const currentValue = compilerOptions[option];
2048
- if (currentValue === void 0 || currentValue !== suggestion.value) {
2049
- suggestions.push(`Consider setting "${option}" to ${JSON.stringify(suggestion.value)}. ${suggestion.reason}`);
2050
- }
2051
- }
2052
- const target = typeof compilerOptions.target === "string" ? compilerOptions.target.toUpperCase() : void 0;
2053
- if (target) {
2054
- const es2020Plus = ["ES2020", "ES2021", "ES2022", "ES2023", "ES2024", "ESNEXT"];
2055
- if (!es2020Plus.includes(target)) {
2056
- warnings.push(
2057
- `Target "${target}" may not support all features used by SibuJS. Recommended: "ES2020" or newer. SibuJS uses optional chaining, nullish coalescing, and other ES2020+ features.`
2058
- );
2059
- }
2060
- }
2061
- const module = typeof compilerOptions.module === "string" ? compilerOptions.module.toLowerCase() : void 0;
2062
- if (module && !["esnext", "es2020", "es2022", "node16", "nodenext"].includes(module)) {
2063
- suggestions.push(
2064
- `Module "${compilerOptions.module}" may not provide optimal results with SibuJS. Consider using "ESNext" for best tree-shaking and bundle optimization.`
2065
- );
2066
- }
2067
- const lib = compilerOptions.lib;
2068
- if (Array.isArray(lib)) {
2069
- const hasDom = lib.some((l) => l.toUpperCase() === "DOM" || l.toUpperCase() === "DOM.ITERABLE");
2070
- if (!hasDom) {
2071
- suggestions.push(
2072
- 'The "lib" array does not include "DOM". SibuJS requires DOM types for component rendering. Add "DOM" and "DOM.Iterable" to your lib array.'
2073
- );
2074
- }
2173
+
2174
+ // src/build/linting.ts
2175
+ function getLineNumber(source, index) {
2176
+ let line = 1;
2177
+ for (let i = 0; i < index && i < source.length; i++) {
2178
+ if (source[i] === "\n") line++;
2075
2179
  }
2076
- if (Array.isArray(config.include)) {
2077
- const includesTs = config.include.some(
2078
- (pattern) => pattern.includes(".ts") || pattern.includes("*")
2079
- );
2080
- if (!includesTs) {
2081
- suggestions.push(
2082
- 'Your "include" patterns may not capture TypeScript files. Ensure patterns like "src/**/*.ts" are included.'
2083
- );
2084
- }
2180
+ return line;
2181
+ }
2182
+ function getColumnNumber(source, index) {
2183
+ let col = 1;
2184
+ for (let i = index - 1; i >= 0; i--) {
2185
+ if (source[i] === "\n") break;
2186
+ col++;
2085
2187
  }
2086
- return {
2087
- valid: warnings.length === 0,
2088
- warnings,
2089
- suggestions
2090
- };
2091
- }
2092
-
2093
- // src/build/analyzer.ts
2094
- var moduleSizes = {
2095
- // Core modules
2096
- "core/html": 1800,
2097
- "core/mount": 120,
2098
- "core/each": 450,
2099
- "core/slots": 200,
2100
- "core/fragment": 180,
2101
- "core/catch": 280,
2102
- "core/portal": 350,
2103
- "core/directives": 520,
2104
- "core/dynamic": 380,
2105
- "core/head": 300,
2106
- "core/ssr": 1200,
2107
- "core/customElement": 650,
2108
- "core/scopedStyle": 480,
2109
- "core/domRecycler": 420,
2110
- "core/bundleOptimize": 200,
2111
- "core/compiled": 350,
2112
- "core/normalize": 280,
2113
- "core/reactiveAttr": 320,
2114
- "core/componentProps": 400,
2115
- "core/contracts": 350,
2116
- "core/worker": 550,
2117
- "core/wasm": 480,
2118
- "core/concurrent": 600,
2119
- "core/microfrontend": 700,
2120
- "core/chunkLoader": 450,
2121
- "core/versioning": 300,
2122
- "core/ecosystem": 250,
2123
- // Signals
2124
- "core/signal": 280,
2125
- "core/effect": 350,
2126
- "core/derived": 250,
2127
- "core/watch": 300,
2128
- "core/store": 380,
2129
- "core/ref": 150,
2130
- "core/array": 420,
2131
- "core/deepSignal": 500,
2132
- "core/lifecycle": 300,
2133
- "core/context": 350,
2134
- "core/persist": 400,
2135
- "core/hoc": 280,
2136
- "core/transition": 600,
2137
- "core/form": 750,
2138
- "core/globalStore": 450,
2139
- "core/machine": 550,
2140
- "core/optimistic": 380,
2141
- "core/timeTravel": 500,
2142
- "core/scheduler": 350,
2143
- "core/plugin": 300,
2144
- "core/virtualList": 800,
2145
- "core/intersection": 350,
2146
- "core/inputMask": 450,
2147
- "core/a11y": 500,
2148
- "core/debug": 400,
2149
- "core/serviceWorker": 350,
2150
- "core/composable": 280,
2151
- // Reactivity
2152
- "reactivity/signal": 100,
2153
- "reactivity/track": 250,
2154
- "reactivity/batch": 200,
2155
- "reactivity/bindAttribute": 300,
2156
- "reactivity/bindChildNode": 250,
2157
- "reactivity/bindTextNode": 200,
2158
- // Components
2159
- "components/ErrorBoundary": 350,
2160
- "components/Loading": 250,
2161
- // Plugins
2162
- "plugins/i18n": 600,
2163
- "plugins/router": 900,
2164
- "plugins/routerSSR": 500,
2165
- // Build utilities
2166
- "build/vite": 800,
2167
- "build/webpack": 750,
2168
- "build/cdn": 400,
2169
- "build/declarations": 500,
2170
- "build/analyzer": 600,
2171
- // Testing
2172
- "testing/index": 700,
2173
- "testing/e2e": 500
2174
- };
2175
- function formatBytes(bytes) {
2176
- if (bytes < 1024) return `${bytes} B`;
2177
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(2)} KB`;
2178
- return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
2179
- }
2180
- function analyzeBundle(modules) {
2181
- const sizes = {};
2182
- let totalSize = 0;
2183
- for (const [name, source] of Object.entries(modules)) {
2184
- let byteLength;
2185
- if (typeof TextEncoder !== "undefined") {
2186
- byteLength = new TextEncoder().encode(source).length;
2187
- } else {
2188
- byteLength = source.length;
2189
- }
2190
- sizes[name] = byteLength;
2191
- totalSize += byteLength;
2192
- }
2193
- const sorted = Object.entries(sizes).map(([name, size]) => ({
2194
- name,
2195
- size,
2196
- percentage: totalSize > 0 ? size / totalSize * 100 : 0
2197
- })).sort((a, b) => b.size - a.size);
2198
- return {
2199
- totalSize,
2200
- moduleSizes: sizes,
2201
- sorted,
2202
- report() {
2203
- const lines = [
2204
- "=== SibuJS Bundle Analysis ===",
2205
- "",
2206
- `Total size: ${formatBytes(totalSize)}`,
2207
- `Modules: ${sorted.length}`,
2208
- "",
2209
- "Module breakdown:",
2210
- "-".repeat(60)
2211
- ];
2212
- for (const entry of sorted) {
2213
- const sizeStr = formatBytes(entry.size).padStart(10);
2214
- const pctStr = `${entry.percentage.toFixed(1)}%`.padStart(6);
2215
- const bar = generateBar(entry.percentage, 20);
2216
- lines.push(` ${sizeStr} ${pctStr} ${bar} ${entry.name}`);
2217
- }
2218
- lines.push("-".repeat(60));
2219
- return lines.join("\n");
2220
- }
2221
- };
2222
- }
2223
- function generateBar(percentage, width) {
2224
- const filled = Math.round(percentage / 100 * width);
2225
- const empty = width - filled;
2226
- return `[${"#".repeat(filled)}${".".repeat(empty)}]`;
2227
- }
2228
- function estimateImportSize(imports) {
2229
- const breakdown = {};
2230
- let estimated = 0;
2231
- const implicitDeps = ["reactivity/signal", "reactivity/track"];
2232
- const allImports = [.../* @__PURE__ */ new Set([...implicitDeps, ...imports])];
2233
- for (const importPath of allImports) {
2234
- const size = moduleSizes[importPath];
2235
- if (size !== void 0) {
2236
- breakdown[importPath] = size;
2237
- estimated += size;
2238
- } else {
2239
- const avgSize = 350;
2240
- breakdown[importPath] = avgSize;
2241
- estimated += avgSize;
2242
- }
2243
- }
2244
- const sortedEntries = Object.entries(breakdown).sort(([, a], [, b]) => b - a);
2245
- const lines = [`Estimated bundle size: ${formatBytes(estimated)} (minified + gzipped)`, ""];
2246
- for (const [modulePath, size] of sortedEntries) {
2247
- const known = moduleSizes[modulePath] !== void 0;
2248
- lines.push(` ${modulePath}: ${formatBytes(size)}${known ? "" : " (estimated)"}`);
2249
- }
2250
- if (imports.length > 0) {
2251
- lines.push("");
2252
- lines.push("Note: Actual sizes may vary based on tree-shaking and build configuration.");
2253
- lines.push("Implicit dependencies (reactivity/signal, reactivity/track) are included.");
2254
- }
2255
- return {
2256
- estimated,
2257
- breakdown,
2258
- formatted: lines.join("\n")
2259
- };
2260
- }
2261
-
2262
- // src/build/linting.ts
2263
- function getLineNumber(source, index) {
2264
- let line = 1;
2265
- for (let i = 0; i < index && i < source.length; i++) {
2266
- if (source[i] === "\n") line++;
2267
- }
2268
- return line;
2269
- }
2270
- function getColumnNumber(source, index) {
2271
- let col = 1;
2272
- for (let i = index - 1; i >= 0; i--) {
2273
- if (source[i] === "\n") break;
2274
- col++;
2275
- }
2276
- return col;
2188
+ return col;
2277
2189
  }
2278
2190
  var lintRules = {
2279
2191
  /** Detect signal inside loops or conditionals */
@@ -2610,636 +2522,1075 @@ function generateEslintConfig(options) {
2610
2522
  };
2611
2523
  }
2612
2524
 
2613
- // src/build/ide.ts
2614
- function getComponentMetadata() {
2615
- return [
2616
- // ── Reactive Primitives ──────────────────────────────────────────
2617
- {
2618
- name: "signal",
2619
- description: "Creates a reactive signal that holds a value. Returns a [getter, setter] tuple. The getter registers reactive dependencies when called.",
2620
- props: [
2621
- {
2622
- name: "initial",
2623
- type: "T",
2624
- required: true,
2625
- description: "The initial value of the state"
2626
- }
2627
- ]
2628
- },
2629
- {
2630
- name: "effect",
2631
- description: "Runs a side effect function immediately and re-runs it whenever any reactive dependency changes. Returns a cleanup function.",
2632
- props: [
2633
- {
2634
- name: "effectFn",
2635
- type: "() => void",
2636
- required: true,
2637
- description: "The effect function to run. It is tracked for reactive dependencies."
2638
- }
2639
- ]
2640
- },
2641
- {
2642
- name: "derived",
2643
- description: "Creates a derived reactive signal whose value updates automatically when dependencies change. Returns a getter function.",
2644
- props: [
2645
- {
2646
- name: "getter",
2647
- type: "() => T",
2648
- required: true,
2649
- description: "Function returning the computed value based on reactive signals"
2650
- }
2651
- ]
2652
- },
2653
- {
2654
- name: "ref",
2655
- description: "Creates a mutable reference object that persists across renders. Updating a ref does NOT trigger re-renders.",
2656
- props: [
2657
- {
2658
- name: "initial",
2659
- type: "T",
2660
- required: false,
2661
- default: "undefined",
2662
- description: "Optional initial value for the ref"
2663
- }
2664
- ]
2665
- },
2666
- {
2667
- name: "watch",
2668
- description: "Watches a reactive getter and calls a callback with (newValue, oldValue) when the watched value changes. Returns a teardown function.",
2669
- props: [
2670
- {
2671
- name: "getter",
2672
- type: "() => T",
2673
- required: true,
2674
- description: "Function that returns the value to watch"
2675
- },
2676
- {
2677
- name: "callback",
2678
- type: "(value: T, prev: T | undefined) => void",
2679
- required: true,
2680
- description: "Function called when the watched value changes"
2681
- }
2682
- ]
2683
- },
2684
- {
2685
- name: "store",
2686
- description: "Creates a global store with reactive properties and subscription support. Returns a [store, actions] tuple.",
2687
- props: [
2688
- {
2689
- name: "initialState",
2690
- type: "T extends object",
2691
- required: true,
2692
- description: "Initial state object for the store"
2693
- }
2694
- ]
2695
- },
2696
- // ── Lifecycle ────────────────────────────────────────────────────
2697
- {
2698
- name: "onMount",
2699
- description: "Runs a callback once the component element has been inserted into the DOM. Uses queueMicrotask for deferred execution.",
2700
- props: [
2701
- {
2702
- name: "callback",
2703
- type: "() => void | CleanupFn",
2704
- required: true,
2705
- description: "Function to run after mount. May return a cleanup function."
2706
- },
2707
- {
2708
- name: "element",
2709
- type: "HTMLElement",
2710
- required: false,
2711
- description: "Optional element to observe; if provided, waits until it is connected."
2712
- }
2713
- ]
2714
- },
2715
- {
2716
- name: "onUnmount",
2717
- description: "Runs a callback when the given element is removed from the DOM. Uses MutationObserver to watch for disconnection.",
2718
- props: [
2719
- {
2720
- name: "callback",
2721
- type: "() => void",
2722
- required: true,
2723
- description: "Function to run on unmount"
2724
- },
2725
- {
2726
- name: "element",
2727
- type: "HTMLElement",
2728
- required: true,
2729
- description: "The element to watch for removal"
2525
+ // src/build/prerender.ts
2526
+ var PRERENDER_CHILD_ENV = "SIBU_PRERENDER_CHILD";
2527
+ var MODULE_SCRIPT = /<script\b[^>]*\btype\s*=\s*["']module["'][^>]*\bsrc\s*=\s*["']([^"']+)["'][^>]*>/i;
2528
+ var MODULE_SCRIPT_SRC_FIRST = /<script\b[^>]*\bsrc\s*=\s*["']([^"']+)["'][^>]*\btype\s*=\s*["']module["'][^>]*>/i;
2529
+ function findEntryInHtml(html) {
2530
+ const m = MODULE_SCRIPT.exec(html) ?? MODULE_SCRIPT_SRC_FIRST.exec(html);
2531
+ return m ? m[1] : null;
2532
+ }
2533
+ function outputFileFor(route) {
2534
+ if (route.output) {
2535
+ const output = route.output;
2536
+ if (/^[A-Za-z]:/.test(output) || output.includes("\0")) {
2537
+ throw new Error(`[sibu prerender] unsafe output "${output}": must be a path relative to the output dir`);
2538
+ }
2539
+ const segments2 = output.split(/[\\/]+/).filter(Boolean);
2540
+ if (segments2.length === 0 || segments2.some((s) => s === "." || s === "..")) {
2541
+ throw new Error(`[sibu prerender] unsafe output "${output}": "." and ".." segments are not allowed`);
2542
+ }
2543
+ return segments2.join("/");
2544
+ }
2545
+ const pathname = route.path.split(/[?#]/)[0];
2546
+ if (!pathname.startsWith("/")) throw new Error(`[sibu prerender] route "${route.path}" must start with "/"`);
2547
+ const segments = pathname.split("/").filter(Boolean).map((raw) => {
2548
+ let decoded;
2549
+ try {
2550
+ decoded = decodeURIComponent(raw);
2551
+ } catch {
2552
+ throw new Error(
2553
+ `[sibu prerender] route "${route.path}" has malformed percent-encoding in "${raw}"; encode a literal % as %25.`
2554
+ );
2555
+ }
2556
+ if (decoded === "." || decoded === ".." || /[\\/\0]/.test(decoded)) {
2557
+ throw new Error(
2558
+ `[sibu prerender] unsafe route "${route.path}": segment "${raw}" decodes to "${decoded}", which could leave the output dir (".", "..", or a path separator).`
2559
+ );
2560
+ }
2561
+ return decoded;
2562
+ });
2563
+ return [...segments, "index.html"].join("/");
2564
+ }
2565
+ function resolveInsideOutDir(path, outDir, file) {
2566
+ const root = path.resolve(outDir);
2567
+ const target = path.resolve(root, file);
2568
+ const rel = path.relative(root, target);
2569
+ const escapes = rel === "" || rel === ".." || /^\.\.[\\/]/.test(rel) || path.isAbsolute(rel);
2570
+ if (escapes) throw new Error(`[sibu prerender] refusing to write "${file}": it resolves outside ${root}`);
2571
+ return target;
2572
+ }
2573
+ async function prerender(options, ctx) {
2574
+ const fs = await import("fs/promises");
2575
+ const path = await import("path");
2576
+ const { pathToFileURL } = await import("url");
2577
+ const { createRequire } = await import("module");
2578
+ const routes = options.routes.map((r) => typeof r === "string" ? { path: r } : r);
2579
+ if (routes.length === 0) return [];
2580
+ const planned = routes.map((route) => {
2581
+ const file = outputFileFor(route);
2582
+ resolveInsideOutDir(path, ctx.outDir, file);
2583
+ return { route, file };
2584
+ });
2585
+ const origin = (options.origin ?? "http://localhost").replace(/\/+$/, "");
2586
+ const timeout = options.timeout ?? 15e3;
2587
+ const settle = options.settle ?? 50;
2588
+ const templatePath = path.join(ctx.outDir, "index.html");
2589
+ const template = await fs.readFile(templatePath, "utf8");
2590
+ let entry = options.entry;
2591
+ if (!entry) {
2592
+ const sourceHtml = await fs.readFile(path.join(ctx.root, "index.html"), "utf8");
2593
+ entry = findEntryInHtml(sourceHtml) ?? void 0;
2594
+ if (!entry) {
2595
+ throw new Error("[sibu prerender] could not find the client entry in index.html; pass `prerender.entry`.");
2596
+ }
2597
+ }
2598
+ const entryAbs = path.resolve(ctx.root, entry.replace(/^\//, ""));
2599
+ const requireFromApp = createRequire(path.join(ctx.root, "package.json"));
2600
+ let jsdomPath;
2601
+ try {
2602
+ jsdomPath = requireFromApp.resolve("jsdom");
2603
+ } catch {
2604
+ throw new Error("[sibu prerender] needs jsdom to render routes. Install it: npm i -D jsdom");
2605
+ }
2606
+ const tmpDir = path.join(ctx.outDir, ".sibu-prerender");
2607
+ await fs.rm(tmpDir, { recursive: true, force: true });
2608
+ const viteBuild = await loadViteBuild(requireFromApp, fs, path, pathToFileURL);
2609
+ const prev = process.env[PRERENDER_CHILD_ENV];
2610
+ process.env[PRERENDER_CHILD_ENV] = "1";
2611
+ try {
2612
+ await viteBuild({
2613
+ configFile: ctx.configFile,
2614
+ root: ctx.root,
2615
+ mode: ctx.mode,
2616
+ logLevel: "warn",
2617
+ build: {
2618
+ ssr: entryAbs,
2619
+ outDir: tmpDir,
2620
+ emptyOutDir: true,
2621
+ copyPublicDir: false,
2622
+ minify: false,
2623
+ sourcemap: false,
2624
+ rollupOptions: { output: { format: "es", entryFileNames: "entry.mjs" } }
2625
+ }
2626
+ });
2627
+ } finally {
2628
+ if (prev === void 0) delete process.env[PRERENDER_CHILD_ENV];
2629
+ else process.env[PRERENDER_CHILD_ENV] = prev;
2630
+ }
2631
+ await fs.writeFile(path.join(tmpDir, "package.json"), '{ "type": "module" }\n', "utf8");
2632
+ const workerFile = path.join(tmpDir, "render-worker.mjs");
2633
+ await fs.writeFile(workerFile, WORKER_SOURCE, "utf8");
2634
+ const entryUrl = pathToFileURL(path.join(tmpDir, "entry.mjs")).href;
2635
+ const jsdomUrl = pathToFileURL(jsdomPath).href;
2636
+ if (options.shell !== false) {
2637
+ await fs.writeFile(path.join(ctx.outDir, options.shell ?? "200.html"), template, "utf8");
2638
+ }
2639
+ const results = [];
2640
+ const queue = [...planned];
2641
+ const concurrency = Math.max(1, options.concurrency ?? 4);
2642
+ try {
2643
+ await Promise.all(
2644
+ Array.from({ length: Math.min(concurrency, queue.length) }, async () => {
2645
+ for (let job = queue.shift(); job; job = queue.shift()) {
2646
+ const { route, file } = job;
2647
+ const started = Date.now();
2648
+ const html = await renderInWorker(workerFile, {
2649
+ entryUrl,
2650
+ jsdomUrl,
2651
+ html: template,
2652
+ url: origin + route.path,
2653
+ route: route.path,
2654
+ timeout,
2655
+ settle
2656
+ });
2657
+ const target = resolveInsideOutDir(path, ctx.outDir, file);
2658
+ await fs.mkdir(path.dirname(target), { recursive: true });
2659
+ await fs.writeFile(target, html, "utf8");
2660
+ results.push({ route: route.path, file, ms: Date.now() - started });
2730
2661
  }
2731
- ]
2732
- },
2733
- // ── Rendering ────────────────────────────────────────────────────
2734
- {
2735
- name: "mount",
2736
- description: "Mounts a root component into a DOM element. Returns an object with the mounted node and an unmount function.",
2737
- props: [
2738
- {
2739
- name: "component",
2740
- type: "(() => HTMLElement) | HTMLElement | Node",
2741
- required: true,
2742
- description: "Function returning HTMLElement, or an HTMLElement directly"
2743
- },
2744
- {
2745
- name: "container",
2746
- type: "HTMLElement",
2747
- required: true,
2748
- description: "Target DOM element to append the result"
2749
- }
2750
- ]
2751
- },
2752
- {
2753
- name: "each",
2754
- description: "Renders a list of nodes efficiently with key-based diffing and LIS-based move minimization.",
2755
- props: [
2756
- {
2757
- name: "getArray",
2758
- type: "() => T[]",
2759
- required: true,
2760
- description: "A reactive getter returning an array"
2761
- },
2762
- {
2763
- name: "render",
2764
- type: "(item: T, index: number) => NodeChild",
2765
- required: true,
2766
- description: "A function that returns a NodeChild for each item"
2767
- },
2768
- {
2769
- name: "options",
2770
- type: "{ key: (item: T) => string | number }",
2771
- required: true,
2772
- description: "A key function for unique identity of items"
2773
- }
2774
- ]
2775
- },
2776
- {
2777
- name: "lazy",
2778
- description: "Enables code-splitting by deferring the import of a component until it is first rendered.",
2779
- props: [
2780
- {
2781
- name: "importFn",
2782
- type: "() => Promise<{ default: Component }>",
2783
- required: true,
2784
- description: "Dynamic import function returning { default: Component }"
2785
- }
2786
- ]
2787
- },
2788
- {
2789
- name: "Suspense",
2790
- description: "Provides a fallback UI while lazy or async nodes are loading.",
2791
- props: [
2792
- {
2793
- name: "nodes",
2794
- type: "() => HTMLElement",
2795
- required: true,
2796
- description: "Function that returns the async/lazy component"
2797
- },
2798
- {
2799
- name: "fallback",
2800
- type: "() => HTMLElement",
2801
- required: true,
2802
- description: "Function that returns the loading UI"
2803
- }
2804
- ]
2805
- },
2806
- // ── Context ──────────────────────────────────────────────────────
2807
- {
2808
- name: "context",
2809
- description: "Creates a context for dependency injection across component trees without prop drilling. Returns a Context object with provide, use, get, and set methods.",
2810
- props: [
2811
- {
2812
- name: "defaultValue",
2813
- type: "T",
2814
- required: true,
2815
- description: "The fallback value when no provider is found"
2816
- }
2817
- ]
2818
- },
2819
- // ── Dynamic Components ───────────────────────────────────────────
2820
- {
2821
- name: "DynamicComponent",
2822
- description: "Reactively switches between components based on a reactive getter returning a component name or function.",
2823
- props: [
2824
- {
2825
- name: "is",
2826
- type: "() => string | Component",
2827
- required: true,
2828
- description: "Reactive getter returning component name (string) or component function"
2829
- }
2830
- ]
2831
- },
2832
- {
2833
- name: "registerComponent",
2834
- description: "Registers a component by name for dynamic resolution via DynamicComponent or resolveComponent.",
2835
- props: [
2836
- {
2837
- name: "name",
2838
- type: "string",
2839
- required: true,
2840
- description: "Unique component identifier"
2841
- },
2842
- {
2843
- name: "component",
2844
- type: "() => HTMLElement",
2845
- required: true,
2846
- description: "The component function"
2847
- }
2848
- ]
2849
- },
2850
- // ── HTML Tag Factories ───────────────────────────────────────────
2851
- {
2852
- name: "div",
2853
- description: "Creates a reactive <div> element. Accepts TagProps including class, style, on, nodes, and arbitrary attributes.",
2854
- props: [
2855
- {
2856
- name: "props",
2857
- type: "TagProps",
2858
- required: false,
2859
- default: "{}",
2860
- description: "Props object with id, class, style, ref, nodes, on, and other attributes"
2861
- }
2862
- ],
2863
- events: [
2864
- {
2865
- name: "click",
2866
- description: "Fires when the element is clicked"
2867
- },
2868
- {
2869
- name: "input",
2870
- description: "Fires when user input is received"
2871
- }
2872
- ]
2662
+ })
2663
+ );
2664
+ } finally {
2665
+ await fs.rm(tmpDir, { recursive: true, force: true });
2666
+ }
2667
+ return results;
2668
+ }
2669
+ function esmEntryOf(entry) {
2670
+ if (typeof entry === "string") return entry;
2671
+ if (entry === null || typeof entry !== "object") return void 0;
2672
+ const conditions = entry;
2673
+ for (const key of ["import", "module-sync", "default"]) {
2674
+ const found = esmEntryOf(conditions[key]);
2675
+ if (found) return found;
2676
+ }
2677
+ return void 0;
2678
+ }
2679
+ async function loadViteBuild(requireFromApp, fs, path, pathToFileURL) {
2680
+ let resolved;
2681
+ try {
2682
+ resolved = requireFromApp.resolve("vite");
2683
+ } catch {
2684
+ throw new Error("[sibu prerender] needs Vite to build the entry for Node, and could not resolve it from the app.");
2685
+ }
2686
+ let url = pathToFileURL(resolved).href;
2687
+ try {
2688
+ const pkgPath = requireFromApp.resolve("vite/package.json");
2689
+ const pkg = JSON.parse(await fs.readFile(pkgPath, "utf8"));
2690
+ const entry = esmEntryOf(pkg.exports?.["."]);
2691
+ if (entry) url = pathToFileURL(path.join(path.dirname(pkgPath), entry)).href;
2692
+ } catch {
2693
+ }
2694
+ const mod = await import(url);
2695
+ const build = mod.build ?? mod.default?.build;
2696
+ if (typeof build !== "function") {
2697
+ throw new Error(`[sibu prerender] the Vite module at ${url} has no build() function.`);
2698
+ }
2699
+ return build;
2700
+ }
2701
+ async function renderInWorker(workerFile, input) {
2702
+ const { Worker } = await import("worker_threads");
2703
+ return new Promise((resolve, reject) => {
2704
+ const worker = new Worker(workerFile, { workerData: input });
2705
+ let done = false;
2706
+ const finish = (fn) => {
2707
+ if (done) return;
2708
+ done = true;
2709
+ fn();
2710
+ void worker.terminate();
2711
+ };
2712
+ const timer = setTimeout(
2713
+ () => finish(() => reject(new Error(`[sibu prerender] ${input.route}: worker did not respond`))),
2714
+ input.timeout + 5e3
2715
+ );
2716
+ worker.once("message", (msg) => {
2717
+ clearTimeout(timer);
2718
+ if (typeof msg.html === "string") finish(() => resolve(msg.html));
2719
+ else finish(() => reject(new Error(`[sibu prerender] ${input.route}: ${msg.error}`)));
2720
+ });
2721
+ worker.once("error", (err) => {
2722
+ clearTimeout(timer);
2723
+ finish(() => reject(new Error(`[sibu prerender] ${input.route}: ${err instanceof Error ? err.stack : err}`)));
2724
+ });
2725
+ worker.once("exit", (code) => {
2726
+ clearTimeout(timer);
2727
+ finish(() => reject(new Error(`[sibu prerender] ${input.route}: worker exited (${code}) before rendering`)));
2728
+ });
2729
+ });
2730
+ }
2731
+ var WORKER_SOURCE = `
2732
+ import { parentPort, workerData } from "node:worker_threads";
2733
+
2734
+ const { entryUrl, jsdomUrl, html, url, route, timeout, settle } = workerData;
2735
+ const fail = (err) => parentPort.postMessage({ error: err instanceof Error ? (err.stack || err.message) : String(err) });
2736
+
2737
+ try {
2738
+ const jsdom = await import(jsdomUrl);
2739
+ const JSDOM = jsdom.JSDOM ?? jsdom.default.JSDOM;
2740
+ const dom = new JSDOM(html, { url, pretendToBeVisual: true });
2741
+ const win = dom.window;
2742
+ // Read before the app runs: the router may replace it with the route title.
2743
+ const templateTitle = win.document.title;
2744
+
2745
+ // Register the document as THE global DOM. Node already has a few of these
2746
+ // names (Event, EventTarget, \u2026); the DOM's own must win, or an event the app
2747
+ // builds could not be dispatched on a DOM node.
2748
+ const FORCE = new Set(["Event", "EventTarget", "CustomEvent", "MessageEvent", "DOMException"]);
2749
+ for (const key of Object.getOwnPropertyNames(win)) {
2750
+ if (key in globalThis && !FORCE.has(key)) continue;
2751
+ try { globalThis[key] = win[key]; } catch {}
2752
+ }
2753
+ globalThis.window = win;
2754
+ globalThis.self = win;
2755
+ globalThis.document = win.document;
2756
+
2757
+ let lastActivity = Date.now();
2758
+ const pending = new Set();
2759
+ const errors = [];
2760
+ const track = (p) => {
2761
+ const tracked = Promise.resolve(p).then(() => {}, () => {});
2762
+ pending.add(tracked);
2763
+ tracked.finally(() => { pending.delete(tracked); lastActivity = Date.now(); });
2764
+ };
2765
+
2766
+ // Hold serialization while \`p\` is pending, WITHOUT handling it: the caller
2767
+ // receives a promise that settles exactly as \`p\` does, so a rejection the
2768
+ // app handles behaves normally and one it ignores still reaches the
2769
+ // unhandledRejection listener below (attaching a handler to \`p\` itself
2770
+ // would silently mark it handled).
2771
+ const observe = (p) => {
2772
+ let release;
2773
+ const tracked = new Promise((r) => { release = r; });
2774
+ pending.add(tracked);
2775
+ tracked.finally(() => { pending.delete(tracked); lastActivity = Date.now(); });
2776
+ return Promise.resolve(p).then(
2777
+ (value) => { release(); return value; },
2778
+ (error) => { release(); throw error; },
2779
+ );
2780
+ };
2781
+
2782
+ // Every fetch holds serialization until it settles; relative URLs resolve
2783
+ // against the route's URL, as they would in the browser.
2784
+ const nodeFetch = globalThis.fetch;
2785
+ if (typeof nodeFetch === "function") {
2786
+ const wrapped = (input, init) => {
2787
+ const target = typeof input === "string" || input instanceof URL ? new URL(String(input), url) : input;
2788
+ return observe(nodeFetch(target, init));
2789
+ };
2790
+ globalThis.fetch = wrapped;
2791
+ win.fetch = wrapped;
2792
+ }
2793
+
2794
+ // A fetch settles when the HEADERS arrive; the data the page renders comes
2795
+ // from a body reader that may still be waiting for the rest of the response.
2796
+ // Each reader call is held like the fetch itself. Patched on the prototype \u2014
2797
+ // this thread exists only to render one route \u2014 so clones, and Responses the
2798
+ // app builds itself, are covered, and \`this\` / brand checks are the native
2799
+ // ones. Bodies nobody reads are never touched, so an unused or long-lived
2800
+ // body does not hold the page. Reading \`response.body\` directly as a stream
2801
+ // is NOT held (a stream may never end); wrap such a read in prerenderWait().
2802
+ const ResponseCtor = globalThis.Response;
2803
+ if (typeof ResponseCtor === "function") {
2804
+ const proto = ResponseCtor.prototype;
2805
+ for (const name of ["arrayBuffer", "blob", "bytes", "formData", "json", "text"]) {
2806
+ const descriptor = Object.getOwnPropertyDescriptor(proto, name);
2807
+ const original = descriptor?.value;
2808
+ if (typeof original !== "function") continue;
2809
+ Object.defineProperty(proto, name, {
2810
+ ...descriptor,
2811
+ value: {
2812
+ // A method-shorthand keeps the native name and is not constructible.
2813
+ [name](...args) {
2814
+ return observe(Reflect.apply(original, this, args));
2815
+ },
2816
+ }[name],
2817
+ });
2873
2818
  }
2874
- ];
2819
+ }
2820
+
2821
+ globalThis[Symbol.for("sibujs.prerender.v1")] = { route, wait: track };
2822
+
2823
+ new win.MutationObserver(() => { lastActivity = Date.now(); }).observe(win.document, {
2824
+ subtree: true, childList: true, attributes: true, characterData: true,
2825
+ });
2826
+ win.addEventListener("error", (e) => errors.push(e.error ?? e.message));
2827
+ process.on("unhandledRejection", (reason) => errors.push(reason));
2828
+
2829
+ await import(entryUrl);
2830
+
2831
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
2832
+ const started = Date.now();
2833
+ for (;;) {
2834
+ if (errors.length > 0) throw errors[0];
2835
+ if (Date.now() - started > timeout) {
2836
+ throw new Error("did not settle within " + timeout + " ms (" + pending.size + " pending request(s) or prerenderWait() promise(s)). " +
2837
+ "Await the data, skip it with isPrerendering(), or drop the route from prerender.routes.");
2838
+ }
2839
+ if (pending.size > 0) await Promise.race([Promise.all([...pending]), sleep(settle)]);
2840
+ await sleep(settle);
2841
+ if (pending.size === 0 && Date.now() - lastActivity >= settle) break;
2842
+ }
2843
+ if (errors.length > 0) throw errors[0];
2844
+
2845
+ // The template title travels with the page, so the client router can give it
2846
+ // back to a route that sets no title of its own.
2847
+ const titleEl = win.document.head.querySelector("title");
2848
+ if (titleEl && !titleEl.hasAttribute("data-sibu-base-title")) titleEl.setAttribute("data-sibu-base-title", templateTitle);
2849
+
2850
+ parentPort.postMessage({ html: dom.serialize() });
2851
+ } catch (err) {
2852
+ fail(err);
2875
2853
  }
2876
- function generateVSCodeSnippets() {
2877
- return {
2878
- "SibuJS Component": {
2879
- prefix: "sibu-component",
2880
- body: [
2881
- "import { div } from 'sibujs';",
2882
- "",
2883
- "export function ${1:ComponentName}(): HTMLElement {",
2884
- " return div({",
2885
- " class: '${2:component-class}',",
2886
- " nodes: [",
2887
- " ${3:// nodes here}",
2888
- " ],",
2889
- " });",
2890
- "}"
2891
- ],
2892
- description: "Create a basic SibuJS component"
2893
- },
2894
- "SibuJS Component with State": {
2895
- prefix: "sibu-component-state",
2896
- body: [
2897
- "import { div, span } from 'sibujs';",
2898
- "import { signal } from 'sibujs';",
2899
- "",
2900
- "export function ${1:ComponentName}(): HTMLElement {",
2901
- " const [${2:value}, ${3:setValue}] = signal(${4:initialValue});",
2902
- "",
2903
- " return div({",
2904
- " class: '${5:component-class}',",
2905
- " nodes: [",
2906
- " () => span({ nodes: String(${2:value}()) }),",
2907
- " ],",
2908
- " });",
2909
- "}"
2910
- ],
2911
- description: "Create a SibuJS component with reactive state"
2912
- },
2913
- "SibuJS signal": {
2914
- prefix: "sibu-state",
2915
- body: ["const [${1:value}, ${2:setValue}] = signal(${3:initialValue});"],
2916
- description: "Create a reactive state with signal"
2917
- },
2918
- "SibuJS effect": {
2919
- prefix: "sibu-effect",
2920
- body: ["const cleanup = effect(() => {", " ${1:// effect logic}", "});"],
2921
- description: "Create a reactive side effect with effect"
2922
- },
2923
- "SibuJS derived": {
2924
- prefix: "sibu-computed",
2925
- body: ["const ${1:computed} = derived(() => {", " return ${2:// derived value};", "});"],
2926
- description: "Create a derived reactive value with derived"
2927
- },
2928
- "SibuJS watch": {
2929
- prefix: "sibu-watch",
2930
- body: [
2931
- "const teardown = watch(",
2932
- " () => ${1:watchedValue}(),",
2933
- " (newVal, oldVal) => {",
2934
- " ${2:// handle change}",
2935
- " }",
2936
- ");"
2937
- ],
2938
- description: "Watch a reactive value for changes"
2939
- },
2940
- "SibuJS each": {
2941
- prefix: "sibu-each",
2942
- body: [
2943
- "each(",
2944
- " () => ${1:items}(),",
2945
- " (item) => {",
2946
- " return ${2:div({ nodes: () => String(item()) })};",
2947
- " },",
2948
- " { key: (item) => ${3:item.id} }",
2949
- ")"
2950
- ],
2951
- description: "Create a reactive list with each()"
2952
- },
2953
- "SibuJS Context": {
2954
- prefix: "sibu-context",
2955
- body: [
2956
- "const ${1:MyContext} = context(${2:defaultValue});",
2957
- "",
2958
- "// In provider component:",
2959
- "// ${1:MyContext}.provide(value);",
2960
- "",
2961
- "// In consumer component:",
2962
- "// const ${3:value} = ${1:MyContext}.use();"
2963
- ],
2964
- description: "Create a context for dependency injection"
2965
- },
2966
- "SibuJS store": {
2967
- prefix: "sibu-store",
2968
- body: [
2969
- "const [${1:store}, { setState: ${2:setStore}, reset, subscribe }] = store({",
2970
- " ${3:key}: ${4:value},",
2971
- "});"
2972
- ],
2973
- description: "Create a reactive global store"
2974
- },
2975
- "SibuJS Lazy Component": {
2976
- prefix: "sibu-lazy",
2977
- body: ["const ${1:LazyComponent} = lazy(() => import('./${2:ComponentPath}'));"],
2978
- description: "Create a lazy-loaded component"
2979
- },
2980
- "SibuJS Suspense": {
2981
- prefix: "sibu-suspense",
2982
- body: [
2983
- "Suspense({",
2984
- " nodes: () => ${1:LazyComponent}(),",
2985
- " fallback: () => div({ nodes: '${2:Loading...}' }),",
2986
- "})"
2987
- ],
2988
- description: "Wrap a lazy component with a loading fallback"
2989
- },
2990
- "SibuJS onMount": {
2991
- prefix: "sibu-mount",
2992
- body: ["onMount(() => {", " ${1:// runs after component enters the DOM}", "});"],
2993
- description: "Schedule a callback to run after mount"
2994
- },
2995
- "SibuJS onUnmount": {
2996
- prefix: "sibu-unmount",
2997
- body: ["onUnmount(() => {", " ${1:// cleanup when removed from DOM}", "}, ${2:element});"],
2998
- description: "Schedule a callback to run on unmount"
2999
- },
3000
- "SibuJS Event Handler": {
3001
- prefix: "sibu-event",
3002
- body: [
3003
- "div({",
3004
- " on: {",
3005
- " ${1:click}: (e) => {",
3006
- " ${2:// handle event}",
3007
- " },",
3008
- " },",
3009
- " nodes: '${3:Click me}',",
3010
- "})"
3011
- ],
3012
- description: "Create an element with event handlers"
3013
- },
3014
- "SibuJS Form": {
3015
- prefix: "sibu-form",
3016
- body: [
3017
- "const form = form({",
3018
- " ${1:fieldName}: {",
3019
- " initial: '${2:}',",
3020
- " validators: [",
3021
- " (value) => !value ? '${3:Required}' : null,",
3022
- " ],",
3023
- " },",
3024
- "});"
3025
- ],
3026
- description: "Create a reactive form with validation"
2854
+ `;
2855
+
2856
+ // src/build/routeSplitting.ts
2857
+ function fileToRoute(relativePath) {
2858
+ const basename = relativePath.split("/").pop() || "";
2859
+ if (basename.startsWith("_")) return null;
2860
+ let route = relativePath.replace(/\.(ts|tsx|js|jsx)$/, "");
2861
+ route = route.replace(/\/index$/, "").replace(/^index$/, "");
2862
+ route = route.replace(/\[\.\.\.(\w+)\]/g, "*");
2863
+ route = route.replace(/\[(\w+)\]/g, ":$1");
2864
+ return `/${route}`;
2865
+ }
2866
+ async function scanRoutes(routesDir, exclude, fs) {
2867
+ const entries = await fs.readdir(routesDir, { withFileTypes: true, recursive: true });
2868
+ const files = [];
2869
+ for (const entry of entries) {
2870
+ if (!entry.isFile()) continue;
2871
+ if (!/\.(ts|tsx|js|jsx)$/.test(entry.name)) continue;
2872
+ const dir = (entry.parentPath || entry.path || routesDir).replace(/\\/g, "/");
2873
+ const routesDirNorm = routesDir.replace(/\\/g, "/");
2874
+ const relative = `${dir}/${entry.name}`.replace(`${routesDirNorm}/`, "").replace(/\\/g, "/");
2875
+ const excluded = exclude.some((pattern) => {
2876
+ const regex = pattern.replace(/\*/g, ".*");
2877
+ return new RegExp(regex).test(relative);
2878
+ });
2879
+ if (!excluded) {
2880
+ files.push(relative);
3027
2881
  }
3028
- };
2882
+ }
2883
+ return files.sort();
3029
2884
  }
3030
- function generateLanguageConfig() {
2885
+ function buildRouteEntries(files, chunkPrefix) {
2886
+ const entries = [];
2887
+ for (const file of files) {
2888
+ const path = fileToRoute(file);
2889
+ if (path === null) continue;
2890
+ const chunkName = `${chunkPrefix}${file.replace(/\.(ts|tsx|js|jsx)$/, "").replace(/[/\\[\].]/g, "-").replace(/^-+|-+$/g, "").toLowerCase()}`;
2891
+ entries.push({
2892
+ path,
2893
+ importPath: file,
2894
+ chunkName,
2895
+ isDynamic: path.includes(":") || path.includes("*")
2896
+ });
2897
+ }
2898
+ return entries.sort((a, b) => {
2899
+ if (a.path.includes("*") && !b.path.includes("*")) return 1;
2900
+ if (!a.path.includes("*") && b.path.includes("*")) return -1;
2901
+ if (a.isDynamic && !b.isDynamic) return 1;
2902
+ if (!a.isDynamic && b.isDynamic) return -1;
2903
+ return a.path.localeCompare(b.path);
2904
+ });
2905
+ }
2906
+ function generateRouteModule(entries, routesDir) {
2907
+ const imports = entries.map(
2908
+ (e, i) => `const route${i} = () => import(/* webpackChunkName: "${e.chunkName}" */ "${routesDir}/${e.importPath}");`
2909
+ ).join("\n");
2910
+ const routes = entries.map((e, i) => ` { path: "${e.path}", component: lazy(route${i}) }`).join(",\n");
2911
+ return `import { lazy } from "sibujs";
2912
+
2913
+ ${imports}
2914
+
2915
+ export const routes = [
2916
+ ${routes}
2917
+ ];
2918
+ `;
2919
+ }
2920
+ var VIRTUAL_ID = "virtual:sibu-routes";
2921
+ var RESOLVED_VIRTUAL_ID = `\0${VIRTUAL_ID}`;
2922
+ function sibuRouteSplitting(options = {}) {
2923
+ const { routesDir = "src/routes", exclude = [], chunkPrefix = "route-" } = options;
2924
+ let projectRoot = "";
3031
2925
  return {
3032
- comments: {
3033
- lineComment: "//",
3034
- blockComment: ["/*", "*/"]
2926
+ name: "sibu-route-splitting",
2927
+ enforce: "pre",
2928
+ resolveId(id) {
2929
+ if (id === VIRTUAL_ID) {
2930
+ return RESOLVED_VIRTUAL_ID;
2931
+ }
2932
+ return void 0;
3035
2933
  },
3036
- brackets: [
3037
- ["{", "}"],
3038
- ["[", "]"],
3039
- ["(", ")"],
3040
- ["<", ">"]
3041
- ],
3042
- autoClosingPairs: [
3043
- { open: "{", close: "}" },
3044
- { open: "[", close: "]" },
3045
- { open: "(", close: ")" },
3046
- { open: "<", close: ">" },
3047
- { open: "'", close: "'" },
3048
- { open: '"', close: '"' },
3049
- { open: "`", close: "`" }
3050
- ]
2934
+ async load(id) {
2935
+ if (id !== RESOLVED_VIRTUAL_ID) return void 0;
2936
+ const { readdir } = await import("fs/promises");
2937
+ const { resolve } = await import("path");
2938
+ if (!projectRoot) {
2939
+ projectRoot = process.cwd();
2940
+ }
2941
+ const fullRoutesDir = resolve(projectRoot, routesDir);
2942
+ try {
2943
+ const files = await scanRoutes(fullRoutesDir, exclude, {
2944
+ readdir
2945
+ });
2946
+ const entries = buildRouteEntries(files, chunkPrefix);
2947
+ return generateRouteModule(entries, routesDir.startsWith("/") ? routesDir : `./${routesDir}`);
2948
+ } catch {
2949
+ return "export const routes = [];\n";
2950
+ }
2951
+ },
2952
+ handleHotUpdate(ctx) {
2953
+ const normalizedRoutesDir = routesDir.replace(/\\/g, "/");
2954
+ if (ctx.file.replace(/\\/g, "/").includes(normalizedRoutesDir)) {
2955
+ const mod = ctx.server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_ID);
2956
+ if (mod) {
2957
+ ctx.server.moduleGraph.invalidateModule(mod);
2958
+ ctx.server.ws.send({ type: "full-reload" });
2959
+ }
2960
+ }
2961
+ }
3051
2962
  };
3052
2963
  }
3053
- function generateTypeStubs() {
3054
- return {
3055
- signal: ["declare function signal<T>(initial: T): [() => T, (next: T | ((prev: T) => T)) => void];"].join("\n"),
3056
- effect: ["declare function effect(effectFn: () => void): () => void;"].join("\n"),
3057
- derived: ["declare function derived<T>(getter: () => T): () => T;"].join("\n"),
3058
- ref: [
3059
- "interface Ref<T> { current: T; }",
3060
- "declare function ref<T>(initial: T): Ref<T>;",
3061
- "declare function ref<T = undefined>(): Ref<T | undefined>;"
3062
- ].join("\n"),
3063
- watch: [
3064
- "declare function watch<T>(getter: () => T, callback: (value: T, prev: T | undefined) => void): () => void;"
3065
- ].join("\n"),
3066
- store: [
3067
- "interface StoreActions<T> {",
3068
- " setState: (patch: Partial<T> | ((state: T) => T)) => void;",
3069
- " reset: () => void;",
3070
- " subscribe: (callback: (state: T) => void) => () => void;",
3071
- " subscribeKey: <K extends keyof T>(key: K, callback: (value: T[K], prev: T[K]) => void) => () => void;",
3072
- " getSnapshot: () => T;",
3073
- "}",
3074
- "declare function store<T extends object>(initialState: T): [{ readonly [K in keyof T]: T[K] }, StoreActions<T>];"
3075
- ].join("\n"),
3076
- mount: [
3077
- "declare function mount(component: (() => HTMLElement) | HTMLElement | Node, container: HTMLElement | null): { node: Node; unmount: () => void };"
3078
- ].join("\n"),
3079
- each: [
3080
- "declare function each<T>(getArray: () => T[], render: (item: T, index: number) => NodeChild, options: { key: (item: T) => string | number }): Comment;"
3081
- ].join("\n"),
3082
- onMount: ["declare function onMount(callback: () => void | (() => void), element?: HTMLElement): void;"].join("\n"),
3083
- onUnmount: ["declare function onUnmount(callback: () => void, element: HTMLElement): void;"].join("\n"),
3084
- context: [
3085
- "interface Context<T> {",
3086
- " provide(value: T): void;",
3087
- " use(): () => T;",
3088
- " get(): T;",
3089
- " set(value: T): void;",
3090
- "}",
3091
- "declare function context<T>(defaultValue: T): Context<T>;"
3092
- ].join("\n"),
3093
- lazy: ["declare function lazy(importFn: () => Promise<{ default: () => HTMLElement }>): () => HTMLElement;"].join(
3094
- "\n"
3095
- ),
3096
- Suspense: [
3097
- "interface SuspenseProps { nodes: () => HTMLElement; fallback: () => HTMLElement; }",
3098
- "declare function Suspense(props: SuspenseProps): HTMLElement;"
3099
- ].join("\n"),
3100
- tagFactory: [
3101
- "interface TagProps {",
3102
- " id?: string;",
3103
- " class?: string | (() => string) | Record<string, boolean | (() => boolean)>;",
3104
- " style?: Record<string, string | number | (() => string | number)> | string | (() => string);",
3105
- " ref?: { current: unknown };",
3106
- " nodes?: NodeChildren;",
3107
- " on?: Record<string, (ev: Event) => void>;",
3108
- " [attr: string]: unknown;",
3109
- "}",
3110
- "declare function tagFactory(tag: string, ns?: string): (first?: TagProps | NodeChildren, second?: NodeChildren) => Element;"
3111
- ].join("\n"),
3112
- DynamicComponent: ["declare function DynamicComponent(is: () => string | (() => HTMLElement)): HTMLElement;"].join(
3113
- "\n"
3114
- ),
3115
- registerComponent: ["declare function registerComponent(name: string, component: () => HTMLElement): void;"].join(
3116
- "\n"
3117
- )
2964
+
2965
+ // src/build/staticAnalysis.ts
2966
+ var STATIC_TAGS = /* @__PURE__ */ new Set([
2967
+ "div",
2968
+ "span",
2969
+ "section",
2970
+ "article",
2971
+ "header",
2972
+ "footer",
2973
+ "nav",
2974
+ "main",
2975
+ "aside",
2976
+ "p",
2977
+ "h1",
2978
+ "h2",
2979
+ "h3",
2980
+ "h4",
2981
+ "h5",
2982
+ "h6",
2983
+ "blockquote",
2984
+ "li",
2985
+ "ol",
2986
+ "ul",
2987
+ "a",
2988
+ "b",
2989
+ "em",
2990
+ "i",
2991
+ "strong",
2992
+ "small",
2993
+ "code",
2994
+ "mark",
2995
+ "img",
2996
+ "br",
2997
+ "hr",
2998
+ "input",
2999
+ "button",
3000
+ "form",
3001
+ "label",
3002
+ "details",
3003
+ "summary",
3004
+ "dialog"
3005
+ ]);
3006
+ var VOID_ELEMENTS2 = /* @__PURE__ */ new Set([
3007
+ "br",
3008
+ "hr",
3009
+ "img",
3010
+ "input",
3011
+ "meta",
3012
+ "link",
3013
+ "area",
3014
+ "base",
3015
+ "col",
3016
+ "embed",
3017
+ "param",
3018
+ "source",
3019
+ "track",
3020
+ "wbr"
3021
+ ]);
3022
+ var SPECIAL_PROPS = /* @__PURE__ */ new Set(["style", "ref", "on", "onElement"]);
3023
+ var NON_REFLECTING_BOOLEANS = /* @__PURE__ */ new Set(["checked", "selected"]);
3024
+ function readLiteral(code, tok) {
3025
+ if (!tok) return null;
3026
+ if (tok.type === "string") {
3027
+ const value = stringTokenValue(code, tok);
3028
+ return value === null ? null : { kind: "string", value };
3029
+ }
3030
+ if (tok.type === "number" && /^(0|[1-9]\d*)(\.\d+)?$/.test(tok.value)) {
3031
+ return { kind: "number", value: String(Number(tok.value)) };
3032
+ }
3033
+ if (tok.type === "ident" && ["true", "false", "null", "undefined"].includes(tok.value)) {
3034
+ return { kind: "keyword", value: tok.value };
3035
+ }
3036
+ return null;
3037
+ }
3038
+ function parserSafeText(s) {
3039
+ return !s.includes("\r") && !s.includes("\0");
3040
+ }
3041
+ function escapeHtml(str) {
3042
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
3043
+ }
3044
+ function escapeAttr(str) {
3045
+ return str.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
3046
+ }
3047
+ function attributeHtml(name, lit) {
3048
+ if (!/^[a-zA-Z][a-zA-Z0-9-]*$/.test(name)) return null;
3049
+ const lower = name.toLowerCase();
3050
+ if (SPECIAL_PROPS.has(name) || lower === "style" || lower === "srcset") return null;
3051
+ if (isEventHandlerAttr(name) || isUrlAttribute(name) || isHtmlContentAttribute(name)) return null;
3052
+ if (lit.kind === "keyword") {
3053
+ if (lit.value === "null" || lit.value === "undefined") return "";
3054
+ const on = lit.value === "true";
3055
+ if (lower.startsWith("aria-")) return `${lower}="${on}"`;
3056
+ if (NON_REFLECTING_BOOLEANS.has(lower)) return null;
3057
+ return on ? `${lower}=""` : "";
3058
+ }
3059
+ if (!parserSafeText(lit.value)) return null;
3060
+ return `${lower}="${escapeAttr(lit.value)}"`;
3061
+ }
3062
+ function parseCall(code, list, i) {
3063
+ const at = (k2) => list[k2];
3064
+ const isPunct = (k2, v) => {
3065
+ const t = at(k2);
3066
+ return !!t && t.type === "punct" && t.value === v;
3118
3067
  };
3068
+ let k = i + 1;
3069
+ const result = { end: 0, cls: null, id: null, attrs: [], text: null };
3070
+ let nodesText = null;
3071
+ if (isPunct(k, "{")) {
3072
+ k++;
3073
+ const seen = /* @__PURE__ */ new Set();
3074
+ while (!isPunct(k, "}")) {
3075
+ const keyTok = at(k);
3076
+ let key = null;
3077
+ if (keyTok?.type === "ident") key = keyTok.value;
3078
+ else if (keyTok?.type === "string") key = stringTokenValue(code, keyTok);
3079
+ if (key === null || !isPunct(k + 1, ":")) return null;
3080
+ if (seen.has(key.toLowerCase())) return null;
3081
+ seen.add(key.toLowerCase());
3082
+ const lit = readLiteral(code, at(k + 2));
3083
+ if (lit === null) return null;
3084
+ k += 3;
3085
+ if (isPunct(k, ",")) k++;
3086
+ else if (!isPunct(k, "}")) return null;
3087
+ if (key === "class" || key === "id") {
3088
+ if (lit.kind !== "string" || !parserSafeText(lit.value)) return null;
3089
+ if (key === "class") result.cls = lit.value;
3090
+ else result.id = lit.value;
3091
+ } else if (key === "nodes") {
3092
+ if (lit.kind !== "string" || !parserSafeText(lit.value)) return null;
3093
+ nodesText = lit.value;
3094
+ } else {
3095
+ const html = attributeHtml(key, lit);
3096
+ if (html === null) return null;
3097
+ if (html) result.attrs.push(html);
3098
+ }
3099
+ }
3100
+ k++;
3101
+ if (isPunct(k, ",")) {
3102
+ const second = readLiteral(code, at(k + 1));
3103
+ if (second === null) {
3104
+ if (!isPunct(k + 1, ")")) return null;
3105
+ k++;
3106
+ } else {
3107
+ if (second.kind !== "string" || !parserSafeText(second.value)) return null;
3108
+ result.text = second.value;
3109
+ k += 2;
3110
+ if (isPunct(k, ",")) k++;
3111
+ }
3112
+ }
3113
+ if (result.text === null) result.text = nodesText;
3114
+ } else {
3115
+ const first = readLiteral(code, at(k));
3116
+ if (first === null || first.kind !== "string" || !parserSafeText(first.value)) return null;
3117
+ k++;
3118
+ if (isPunct(k, ",") && at(k + 1)?.type === "string") {
3119
+ const second = readLiteral(code, at(k + 1));
3120
+ if (second === null || second.kind !== "string" || !parserSafeText(second.value)) return null;
3121
+ result.cls = first.value;
3122
+ result.text = second.value;
3123
+ k += 2;
3124
+ } else {
3125
+ result.text = first.value;
3126
+ }
3127
+ if (isPunct(k, ",")) k++;
3128
+ }
3129
+ if (!isPunct(k, ")")) return null;
3130
+ result.end = at(k).end;
3131
+ return result;
3132
+ }
3133
+ function toHtml(tag, call) {
3134
+ const attrs = [];
3135
+ if (call.cls !== null) attrs.push(`class="${escapeAttr(call.cls)}"`);
3136
+ if (call.id !== null) attrs.push(`id="${escapeAttr(call.id)}"`);
3137
+ attrs.push(...call.attrs);
3138
+ const attrStr = attrs.length > 0 ? ` ${attrs.join(" ")}` : "";
3139
+ if (VOID_ELEMENTS2.has(tag)) {
3140
+ if (call.text) return null;
3141
+ return `<${tag}${attrStr} />`;
3142
+ }
3143
+ return `<${tag}${attrStr}>${escapeHtml(call.text ?? "")}</${tag}>`;
3144
+ }
3145
+ function analyzeStaticTemplates(code) {
3146
+ const none = { hasStaticPatterns: false, patterns: [] };
3147
+ if (!code.includes("sibujs")) return none;
3148
+ const scan = scanModule(code);
3149
+ if (!scan) return none;
3150
+ const factories = /* @__PURE__ */ new Map();
3151
+ for (const decl of scan.imports) {
3152
+ if (decl.source !== "sibujs") continue;
3153
+ for (const b of decl.bindings) if (STATIC_TAGS.has(b.imported)) factories.set(b.local, b.imported);
3154
+ }
3155
+ if (factories.size === 0) return none;
3156
+ const uses = bindingUses(code, scan, new Set(factories.keys()));
3157
+ const unsafe = new Set(uses.shadowed);
3158
+ for (const ref of uses.tagged) unsafe.add(ref.tok.value);
3159
+ const patterns = [];
3160
+ for (const { tok, list, index } of uses.calls) {
3161
+ if (unsafe.has(tok.value)) continue;
3162
+ const tag = factories.get(tok.value);
3163
+ const call = parseCall(code, list, index + 1);
3164
+ if (!call) continue;
3165
+ const templateHtml = toHtml(tag, call);
3166
+ if (templateHtml === null) continue;
3167
+ const start = list[index].start;
3168
+ patterns.push({ original: code.slice(start, call.end), tag, templateHtml, start, end: call.end });
3169
+ }
3170
+ return { hasStaticPatterns: patterns.length > 0, patterns };
3119
3171
  }
3120
3172
 
3121
- // src/build/compileTemplates.ts
3122
- function compileHtmlTemplates(code) {
3123
- const plan = planHtmlTemplates(code);
3124
- const edits = plan.compiledCount > 0 ? normalizeEdits(plan.edits) : null;
3173
+ // src/build/vite.ts
3174
+ var DEFAULT_INCLUDE = ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"];
3175
+ var DEFAULT_EXCLUDE = ["node_modules/**", "dist/**", "**/*.test.*", "**/*.spec.*"];
3176
+ function matchesPattern(filePath, patterns) {
3177
+ const normalized = filePath.replace(/\\/g, "/");
3178
+ return patterns.some((pattern) => {
3179
+ const regexStr = pattern.replace(/\./g, "\\.").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\{\{GLOBSTAR\}\}/g, ".*");
3180
+ return new RegExp(regexStr).test(normalized);
3181
+ });
3182
+ }
3183
+ function devPrologueEdit(code) {
3184
+ let at = 0;
3185
+ if (code.startsWith("#!")) {
3186
+ const nl = code.indexOf("\n");
3187
+ if (nl === -1) return null;
3188
+ at = nl + 1;
3189
+ }
3125
3190
  return {
3126
- code: edits ? applyEdits(code, edits, plan.append) : null,
3127
- usedTags: plan.usedTags,
3128
- usesSvg: plan.usesSvg,
3129
- compiledCount: edits ? plan.compiledCount : 0,
3130
- skippedCount: plan.skippedCount
3191
+ start: at,
3192
+ end: at,
3193
+ text: '/* SibuJS Dev Mode */ if (typeof globalThis !== "undefined") { globalThis.__SIBU_DEV__ = true; } '
3131
3194
  };
3132
3195
  }
3133
-
3134
- // src/build/routeSplitting.ts
3135
- function fileToRoute(relativePath) {
3136
- const basename = relativePath.split("/").pop() || "";
3137
- if (basename.startsWith("_")) return null;
3138
- let route = relativePath.replace(/\.(ts|tsx|js|jsx)$/, "");
3139
- route = route.replace(/\/index$/, "").replace(/^index$/, "");
3140
- route = route.replace(/\[\.\.\.(\w+)\]/g, "*");
3141
- route = route.replace(/\[(\w+)\]/g, ":$1");
3142
- return `/${route}`;
3196
+ function staticOptimizationEdits(code) {
3197
+ const analysis = analyzeStaticTemplates(code);
3198
+ if (!analysis.hasStaticPatterns) return null;
3199
+ const P = uniquePrefix(code);
3200
+ const edits = analysis.patterns.map((pattern) => ({
3201
+ start: pattern.start,
3202
+ end: pattern.end,
3203
+ // A call spanning several lines keeps its line breaks.
3204
+ text: `${P}static(${jsStringLiteral(pattern.templateHtml)}${lineBreaksIn(code, pattern.start, pattern.end)})`
3205
+ }));
3206
+ const append = `
3207
+ import { staticTemplate as ${P}staticTemplate } from "sibujs/performance";
3208
+ function ${P}static(markup) {
3209
+ return document.adoptNode(${P}staticTemplate(markup));
3143
3210
  }
3144
- async function scanRoutes(routesDir, exclude, fs) {
3145
- const entries = await fs.readdir(routesDir, { withFileTypes: true, recursive: true });
3146
- const files = [];
3147
- for (const entry of entries) {
3148
- if (!entry.isFile()) continue;
3149
- if (!/\.(ts|tsx|js|jsx)$/.test(entry.name)) continue;
3150
- const dir = (entry.parentPath || entry.path || routesDir).replace(/\\/g, "/");
3151
- const routesDirNorm = routesDir.replace(/\\/g, "/");
3152
- const relative = `${dir}/${entry.name}`.replace(`${routesDirNorm}/`, "").replace(/\\/g, "/");
3153
- const excluded = exclude.some((pattern) => {
3154
- const regex = pattern.replace(/\*/g, ".*");
3155
- return new RegExp(regex).test(relative);
3156
- });
3157
- if (!excluded) {
3158
- files.push(relative);
3211
+ `;
3212
+ return { edits, append };
3213
+ }
3214
+ function resolveDevMode(explicit, info) {
3215
+ if (explicit !== void 0) return explicit;
3216
+ if (info?.command === "build") return info.mode === "development";
3217
+ if (info?.command === "serve") return true;
3218
+ return typeof process !== "undefined" && process.env?.NODE_ENV !== "production";
3219
+ }
3220
+ function sibuVitePlugin(options = {}) {
3221
+ const {
3222
+ hmr = true,
3223
+ pureAnnotations = true,
3224
+ include = DEFAULT_INCLUDE,
3225
+ exclude = DEFAULT_EXCLUDE,
3226
+ devMode,
3227
+ staticOptimize = false,
3228
+ compileTemplates,
3229
+ prerender: prerenderOptions
3230
+ } = options;
3231
+ let prerenderTarget = null;
3232
+ let isDevMode = resolveDevMode(devMode, void 0);
3233
+ return {
3234
+ name: "sibu-vite-plugin",
3235
+ enforce: "pre",
3236
+ config(_userConfig, env) {
3237
+ isDevMode = resolveDevMode(devMode, env);
3238
+ return {
3239
+ // Optimize dependency pre-bundling for sibujs
3240
+ optimizeDeps: {
3241
+ include: ["sibujs"]
3242
+ },
3243
+ // Ensure sibujs is treated correctly for SSR
3244
+ ssr: {
3245
+ noExternal: ["sibujs"]
3246
+ },
3247
+ // Define global constants for dead code elimination
3248
+ define: {
3249
+ __SIBU_DEV__: JSON.stringify(isDevMode),
3250
+ __SIBU_HMR__: JSON.stringify(hmr)
3251
+ },
3252
+ // Enable source maps in dev
3253
+ build: {
3254
+ sourcemap: isDevMode
3255
+ }
3256
+ };
3257
+ },
3258
+ configResolved(config) {
3259
+ isDevMode = resolveDevMode(devMode, config);
3260
+ if (prerenderOptions && config.command === "build" && !config.build?.ssr && typeof process !== "undefined" && !process.env?.[PRERENDER_CHILD_ENV] && config.root) {
3261
+ const outDir = config.build?.outDir ?? "dist";
3262
+ const absolute = /^([a-zA-Z]:)?[\\/]/.test(outDir);
3263
+ prerenderTarget = {
3264
+ root: config.root,
3265
+ outDir: absolute ? outDir : `${config.root.replace(/[\\/]+$/, "")}/${outDir}`,
3266
+ configFile: config.configFile,
3267
+ mode: config.mode
3268
+ };
3269
+ }
3270
+ },
3271
+ async closeBundle() {
3272
+ if (!prerenderOptions || !prerenderTarget) return;
3273
+ const target = prerenderTarget;
3274
+ prerenderTarget = null;
3275
+ const results = await prerender(prerenderOptions, target);
3276
+ for (const r of results) console.log(`[sibu prerender] ${r.route} \u2192 ${r.file} (${r.ms} ms)`);
3277
+ },
3278
+ transform(code, id) {
3279
+ if (!matchesPattern(id, include) || matchesPattern(id, exclude)) {
3280
+ return null;
3281
+ }
3282
+ if (!code.includes("sibujs")) return null;
3283
+ if (!scanModule(code)) return null;
3284
+ const edits = [];
3285
+ let append = "";
3286
+ if (isDevMode && importsSibu(code)) {
3287
+ const prologue = devPrologueEdit(code);
3288
+ if (prologue) edits.push(prologue);
3289
+ }
3290
+ if (pureAnnotations) {
3291
+ edits.push(...pureAnnotationEdits(code));
3292
+ }
3293
+ const shouldCompile = compileTemplates ?? !isDevMode;
3294
+ if (shouldCompile) {
3295
+ const plan = planHtmlTemplates(code);
3296
+ if (plan.compiledCount > 0) {
3297
+ edits.push(...plan.edits);
3298
+ append += plan.append;
3299
+ }
3300
+ }
3301
+ if (staticOptimize) {
3302
+ const optimized = staticOptimizationEdits(code);
3303
+ if (optimized) {
3304
+ edits.push(...optimized.edits);
3305
+ append += optimized.append;
3306
+ }
3307
+ }
3308
+ if (edits.length === 0 && append === "") return null;
3309
+ const sorted = normalizeEdits(edits);
3310
+ if (!sorted) return null;
3311
+ const transformed = applyEdits(code, sorted, append);
3312
+ if (transformed === code) return null;
3313
+ return {
3314
+ code: transformed,
3315
+ map: editsSourceMap(code, sorted, append, id)
3316
+ };
3317
+ },
3318
+ handleHotUpdate(ctx) {
3319
+ if (!hmr) return;
3320
+ const { file } = ctx;
3321
+ if (matchesPattern(file, include) && !matchesPattern(file, exclude)) {
3322
+ if (isDevMode) {
3323
+ console.log(`[sibu-vite-plugin] HMR update: ${file}`);
3324
+ }
3325
+ return;
3326
+ }
3327
+ }
3328
+ };
3329
+ }
3330
+ function createViteConfig(options = {}) {
3331
+ const { entry = "src/main.ts", outDir = "dist", ssr = false, overrides = {} } = options;
3332
+ const baseConfig = {
3333
+ // Use the SibuJS Vite plugin
3334
+ plugins: [sibuVitePlugin()],
3335
+ // Build configuration
3336
+ build: {
3337
+ outDir,
3338
+ target: "es2020",
3339
+ minify: "esbuild",
3340
+ sourcemap: true,
3341
+ // Library mode configuration when building a library
3342
+ lib: ssr ? void 0 : {
3343
+ entry,
3344
+ formats: ["es", "cjs"]
3345
+ },
3346
+ // Rollup-specific options
3347
+ rollupOptions: {
3348
+ input: ssr ? entry : void 0,
3349
+ output: {
3350
+ // Ensure consistent chunk naming
3351
+ chunkFileNames: "chunks/[name]-[hash].js",
3352
+ // Preserve pure annotations
3353
+ generatedCode: {
3354
+ constBindings: true
3355
+ }
3356
+ },
3357
+ // Tree-shaking configuration
3358
+ treeshake: {
3359
+ moduleSideEffects: false,
3360
+ propertyReadSideEffects: false,
3361
+ annotations: true
3362
+ }
3363
+ }
3364
+ },
3365
+ // Resolve configuration
3366
+ resolve: {
3367
+ // Prefer ESM versions of packages
3368
+ mainFields: ["module", "jsnext:main", "jsnext", "main"],
3369
+ extensions: [".ts", ".tsx", ".js", ".jsx", ".json"]
3370
+ },
3371
+ // SSR configuration
3372
+ ...ssr ? {
3373
+ ssr: {
3374
+ noExternal: ["sibujs"],
3375
+ target: "node"
3376
+ },
3377
+ build: {
3378
+ outDir,
3379
+ target: "node18",
3380
+ ssr: true,
3381
+ rollupOptions: {
3382
+ input: entry,
3383
+ output: {
3384
+ format: "esm"
3385
+ }
3386
+ }
3387
+ }
3388
+ } : {},
3389
+ // Optimize dependency handling
3390
+ optimizeDeps: {
3391
+ include: ["sibujs"],
3392
+ // Force pre-bundling of sibujs for faster dev startup
3393
+ force: false
3394
+ },
3395
+ // Environment variable handling
3396
+ define: {
3397
+ __SIBU_SSR__: JSON.stringify(ssr)
3159
3398
  }
3160
- }
3161
- return files.sort();
3399
+ };
3400
+ return deepMerge(baseConfig, overrides);
3162
3401
  }
3163
- function buildRouteEntries(files, chunkPrefix) {
3164
- const entries = [];
3165
- for (const file of files) {
3166
- const path = fileToRoute(file);
3167
- if (path === null) continue;
3168
- const chunkName = `${chunkPrefix}${file.replace(/\.(ts|tsx|js|jsx)$/, "").replace(/[/\\[\].]/g, "-").replace(/^-+|-+$/g, "").toLowerCase()}`;
3169
- entries.push({
3170
- path,
3171
- importPath: file,
3172
- chunkName,
3173
- isDynamic: path.includes(":") || path.includes("*")
3174
- });
3402
+ function deepMerge(target, source) {
3403
+ const result = { ...target };
3404
+ for (const key of Object.keys(source)) {
3405
+ if (source[key] && typeof source[key] === "object" && !Array.isArray(source[key]) && target[key] && typeof target[key] === "object" && !Array.isArray(target[key])) {
3406
+ result[key] = deepMerge(target[key], source[key]);
3407
+ } else {
3408
+ result[key] = source[key];
3409
+ }
3175
3410
  }
3176
- return entries.sort((a, b) => {
3177
- if (a.path.includes("*") && !b.path.includes("*")) return 1;
3178
- if (!a.path.includes("*") && b.path.includes("*")) return -1;
3179
- if (a.isDynamic && !b.isDynamic) return 1;
3180
- if (!a.isDynamic && b.isDynamic) return -1;
3181
- return a.path.localeCompare(b.path);
3182
- });
3411
+ return result;
3183
3412
  }
3184
- function generateRouteModule(entries, routesDir) {
3185
- const imports = entries.map(
3186
- (e, i) => `const route${i} = () => import(/* webpackChunkName: "${e.chunkName}" */ "${routesDir}/${e.importPath}");`
3187
- ).join("\n");
3188
- const routes = entries.map((e, i) => ` { path: "${e.path}", component: lazy(route${i}) }`).join(",\n");
3189
- return `import { lazy } from "sibujs";
3190
-
3191
- ${imports}
3192
3413
 
3193
- export const routes = [
3194
- ${routes}
3195
- ];
3196
- `;
3414
+ // src/build/webpack.ts
3415
+ function addPureAnnotations(source) {
3416
+ return injectPureAnnotations(source);
3197
3417
  }
3198
- var VIRTUAL_ID = "virtual:sibu-routes";
3199
- var RESOLVED_VIRTUAL_ID = `\0${VIRTUAL_ID}`;
3200
- function sibuRouteSplitting(options = {}) {
3201
- const { routesDir = "src/routes", exclude = [], chunkPrefix = "route-" } = options;
3202
- let projectRoot = "";
3418
+ function sibuWebpackPlugin(options = {}) {
3419
+ const { devMode } = options;
3203
3420
  return {
3204
- name: "sibu-route-splitting",
3205
- enforce: "pre",
3206
- resolveId(id) {
3207
- if (id === VIRTUAL_ID) {
3208
- return RESOLVED_VIRTUAL_ID;
3209
- }
3210
- return void 0;
3211
- },
3212
- async load(id) {
3213
- if (id !== RESOLVED_VIRTUAL_ID) return void 0;
3214
- const { readdir } = await import("fs/promises");
3215
- const { resolve } = await import("path");
3216
- if (!projectRoot) {
3217
- projectRoot = process.cwd();
3421
+ name: "SibuWebpackPlugin",
3422
+ apply(compiler) {
3423
+ if (options.pureAnnotations === true) {
3424
+ console.warn(
3425
+ '[SibuWebpackPlugin] `pureAnnotations` is not applied by the plugin: webpack needs a loader file. Create sibu-pure-loader.cjs containing `module.exports = require("sibujs/build").createPureAnnotationsLoader();` and add a module rule `{ test: /\\.[cm]?[jt]sx?$/, exclude: /node_modules/, use: "./sibu-pure-loader.cjs" }`.'
3426
+ );
3218
3427
  }
3219
- const fullRoutesDir = resolve(projectRoot, routesDir);
3220
- try {
3221
- const files = await scanRoutes(fullRoutesDir, exclude, {
3222
- readdir
3223
- });
3224
- const entries = buildRouteEntries(files, chunkPrefix);
3225
- return generateRouteModule(entries, routesDir.startsWith("/") ? routesDir : `./${routesDir}`);
3226
- } catch {
3227
- return "export const routes = [];\n";
3428
+ compiler.hooks?.compilation?.tap("SibuWebpackPlugin", (compilation) => {
3429
+ const comp = compilation;
3430
+ if (comp.hooks?.optimizeModules) {
3431
+ comp.hooks.optimizeModules.tap("SibuWebpackPlugin", () => {
3432
+ });
3433
+ }
3434
+ });
3435
+ compiler.hooks?.afterResolvers?.tap("SibuWebpackPlugin", () => {
3436
+ if (!compiler.options.resolve) {
3437
+ compiler.options.resolve = {};
3438
+ }
3439
+ if (!compiler.options.resolve.mainFields) {
3440
+ compiler.options.resolve.mainFields = ["module", "main"];
3441
+ }
3442
+ if (!compiler.options.resolve.mainFields.includes("module")) {
3443
+ compiler.options.resolve.mainFields.unshift("module");
3444
+ }
3445
+ });
3446
+ const tapDoneLogger = () => compiler.hooks?.done?.tap("SibuWebpackPlugin", (stats) => {
3447
+ const statsObj = stats;
3448
+ const info = statsObj?.toJson?.({ modules: false, chunks: false });
3449
+ if (info) {
3450
+ console.log(`[SibuWebpackPlugin] Build completed in ${info.time || 0}ms`);
3451
+ const warnings = info.warnings;
3452
+ if (warnings?.length) {
3453
+ console.log(`[SibuWebpackPlugin] ${warnings.length} warning(s)`);
3454
+ }
3455
+ }
3456
+ });
3457
+ if (devMode === true) tapDoneLogger();
3458
+ compiler.hooks?.environment?.tap("SibuWebpackPlugin", () => {
3459
+ const isDevMode = devMode ?? resolveWebpackDevMode(compiler.options);
3460
+ if (devMode === void 0 && isDevMode) tapDoneLogger();
3461
+ if (!compiler.options.plugins) {
3462
+ compiler.options.plugins = [];
3463
+ }
3464
+ const defines = {
3465
+ __SIBU_DEV__: JSON.stringify(isDevMode)
3466
+ };
3467
+ compiler.__sibuDefines = defines;
3468
+ const DefinePlugin = compiler.webpack?.DefinePlugin;
3469
+ if (DefinePlugin) {
3470
+ new DefinePlugin(defines).apply(compiler);
3471
+ }
3472
+ });
3473
+ }
3474
+ };
3475
+ }
3476
+ function resolveWebpackDevMode(options) {
3477
+ const mode = options.mode;
3478
+ if (mode === "development") return true;
3479
+ if (mode === "production" || mode === void 0) return false;
3480
+ return typeof process !== "undefined" && process.env?.NODE_ENV !== "production";
3481
+ }
3482
+ function createPureAnnotationsLoader() {
3483
+ return function sibuPureAnnotationsLoader(source) {
3484
+ return addPureAnnotations(source);
3485
+ };
3486
+ }
3487
+ function createWebpackConfig(options = {}) {
3488
+ const { entry = "./src/index.ts", outputPath = "dist", mode = "production" } = options;
3489
+ const isDev = mode === "development";
3490
+ return {
3491
+ mode,
3492
+ entry,
3493
+ output: {
3494
+ path: outputPath,
3495
+ filename: isDev ? "[name].js" : "[name].[contenthash:8].js",
3496
+ chunkFilename: isDev ? "[name].chunk.js" : "[name].[contenthash:8].chunk.js",
3497
+ clean: true,
3498
+ // Use ESM output
3499
+ module: true,
3500
+ library: {
3501
+ type: "module"
3228
3502
  }
3229
3503
  },
3230
- handleHotUpdate(ctx) {
3231
- const normalizedRoutesDir = routesDir.replace(/\\/g, "/");
3232
- if (ctx.file.replace(/\\/g, "/").includes(normalizedRoutesDir)) {
3233
- const mod = ctx.server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_ID);
3234
- if (mod) {
3235
- ctx.server.moduleGraph.invalidateModule(mod);
3236
- ctx.server.ws.send({ type: "full-reload" });
3504
+ // Enable experiments for ESM output
3505
+ experiments: {
3506
+ outputModule: true
3507
+ },
3508
+ resolve: {
3509
+ extensions: [".ts", ".tsx", ".js", ".jsx", ".json"],
3510
+ mainFields: ["module", "main"],
3511
+ alias: {}
3512
+ },
3513
+ module: {
3514
+ rules: [
3515
+ // TypeScript/JavaScript handling
3516
+ {
3517
+ test: /\.[jt]sx?$/,
3518
+ exclude: /node_modules/,
3519
+ use: [
3520
+ {
3521
+ // Users should configure their preferred TS loader
3522
+ // (ts-loader, babel-loader, esbuild-loader, swc-loader)
3523
+ loader: "ts-loader",
3524
+ options: {
3525
+ transpileOnly: true,
3526
+ compilerOptions: {
3527
+ module: "esnext",
3528
+ moduleResolution: "node",
3529
+ target: "es2020"
3530
+ }
3531
+ }
3532
+ }
3533
+ ]
3534
+ }
3535
+ ]
3536
+ },
3537
+ plugins: [
3538
+ // SibuJS plugin for optimizations
3539
+ sibuWebpackPlugin({ devMode: isDev })
3540
+ ],
3541
+ optimization: {
3542
+ minimize: !isDev,
3543
+ // Enable tree-shaking
3544
+ usedExports: true,
3545
+ sideEffects: true,
3546
+ // Split chunks for better caching
3547
+ splitChunks: isDev ? false : {
3548
+ chunks: "all",
3549
+ cacheGroups: {
3550
+ // Separate sibujs framework code into its own chunk (the package
3551
+ // directory is `sibujs`; matching `sibu` never selected anything)
3552
+ sibu: {
3553
+ test: /[\\/]node_modules[\\/]sibujs[\\/]/,
3554
+ name: "sibu",
3555
+ chunks: "all",
3556
+ priority: 20
3557
+ },
3558
+ // Separate other vendor code
3559
+ vendor: {
3560
+ test: /[\\/]node_modules[\\/]/,
3561
+ name: "vendor",
3562
+ chunks: "all",
3563
+ priority: 10
3564
+ }
3237
3565
  }
3238
3566
  }
3567
+ },
3568
+ // Source maps
3569
+ devtool: isDev ? "eval-cheap-module-source-map" : "source-map",
3570
+ // Dev server configuration
3571
+ devServer: isDev ? {
3572
+ hot: true,
3573
+ open: true,
3574
+ port: 3e3,
3575
+ historyApiFallback: true
3576
+ } : void 0,
3577
+ // Performance hints
3578
+ performance: {
3579
+ hints: isDev ? false : "warning",
3580
+ maxEntrypointSize: 25e4,
3581
+ maxAssetSize: 25e4
3582
+ },
3583
+ // Cache for faster rebuilds
3584
+ cache: {
3585
+ type: "filesystem",
3586
+ buildDependencies: {
3587
+ config: []
3588
+ }
3239
3589
  }
3240
3590
  };
3241
3591
  }
3242
3592
  export {
3593
+ PRERENDER_CHILD_ENV,
3243
3594
  analyzeBundle,
3244
3595
  analyzeStaticTemplates,
3245
3596
  buildRouteEntries,
@@ -3248,8 +3599,10 @@ export {
3248
3599
  createPureAnnotationsLoader,
3249
3600
  createViteConfig,
3250
3601
  createWebpackConfig,
3602
+ esmEntryOf,
3251
3603
  estimateImportSize,
3252
3604
  fileToRoute,
3605
+ findEntryInHtml,
3253
3606
  generateEslintConfig,
3254
3607
  generateImportMap,
3255
3608
  generateLanguageConfig,
@@ -3260,7 +3613,10 @@ export {
3260
3613
  lintRules,
3261
3614
  lintSource,
3262
3615
  moduleSizes,
3616
+ outputFileFor,
3617
+ prerender,
3263
3618
  registerGlobal,
3619
+ resolveInsideOutDir,
3264
3620
  sibuRouteSplitting,
3265
3621
  sibuVitePlugin,
3266
3622
  sibuWebpackPlugin,