sibujs 4.13.1 → 4.14.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 (107) hide show
  1. package/README.md +1 -1
  2. package/dist/browser.cjs +15 -2
  3. package/dist/browser.js +9 -7
  4. package/dist/build.cjs +880 -281
  5. package/dist/build.d.cts +68 -3
  6. package/dist/build.d.ts +68 -3
  7. package/dist/build.js +88 -30
  8. package/dist/cdn.dev.global.js +12 -12
  9. package/dist/cdn.full.dev.global.js +13 -13
  10. package/dist/cdn.full.global.js +12 -12
  11. package/dist/cdn.global.js +12 -12
  12. package/dist/{chunk-J6FW5TV6.js → chunk-3HEX6QJQ.js} +5 -3
  13. package/dist/chunk-3HZOSMPX.js +90 -0
  14. package/dist/{chunk-K4LNUT5L.js → chunk-3T7TWMSD.js} +8 -6
  15. package/dist/{chunk-ADZHPN2B.js → chunk-4XCWNGRY.js} +3 -3
  16. package/dist/{chunk-2NC56RGY.js → chunk-55TAOW5M.js} +2 -2
  17. package/dist/{chunk-G3Z2FEBR.js → chunk-B2NLNZ3E.js} +119 -26
  18. package/dist/{chunk-EXXSN3BW.js → chunk-B3HOT4TK.js} +1 -1
  19. package/dist/{chunk-QKFVMIAV.js → chunk-FCWOMATN.js} +11 -3
  20. package/dist/{chunk-QC3NP47A.js → chunk-FFXZ6ELZ.js} +10 -6
  21. package/dist/{chunk-TUCPL2HB.js → chunk-GIJ33UJZ.js} +1 -1
  22. package/dist/chunk-KTJC7QPA.js +195 -0
  23. package/dist/chunk-L2IPKGUJ.js +2596 -0
  24. package/dist/{chunk-4UTGDVSA.js → chunk-LAHX7XFG.js} +7 -14
  25. package/dist/{chunk-PBJYCE2Z.js → chunk-LJ2LR5P4.js} +5 -5
  26. package/dist/{chunk-AM6DZLBM.js → chunk-LJMX4SQ7.js} +2 -2
  27. package/dist/{chunk-5RIBLY2N.js → chunk-LUZXFMKH.js} +6 -6
  28. package/dist/{chunk-VFPLWUT7.js → chunk-LZKKMDQF.js} +151 -86
  29. package/dist/{chunk-J4X7ICH4.js → chunk-NIHGSXOX.js} +8 -6
  30. package/dist/chunk-NT3ZYB5S.js +167 -0
  31. package/dist/{chunk-2O6JJCUR.js → chunk-OLMZF4RU.js} +49 -8
  32. package/dist/{chunk-22OPSWMF.js → chunk-OPRQW4LI.js} +2 -2
  33. package/dist/chunk-R5U7XKVJ.js +16 -0
  34. package/dist/{chunk-KC62VURU.js → chunk-RBRITLFL.js} +10 -8
  35. package/dist/{chunk-F2UWOZ6J.js → chunk-RVWJGWCL.js} +2 -2
  36. package/dist/{chunk-NYNYSPK7.js → chunk-S37PZD6I.js} +30 -45
  37. package/dist/chunk-TOV4PN3P.js +2015 -0
  38. package/dist/{chunk-C73VBPEF.js → chunk-TS6JDK7Y.js} +8 -8
  39. package/dist/{chunk-EU7T7EB3.js → chunk-TZNR6E6D.js} +4 -4
  40. package/dist/chunk-UYTYNCMZ.js +199 -0
  41. package/dist/{chunk-OIKD7YDY.js → chunk-VIAVYBNQ.js} +6 -5
  42. package/dist/{chunk-QZ47OZ25.js → chunk-WFMMSMA5.js} +8 -6
  43. package/dist/chunk-YIXMS546.js +1901 -0
  44. package/dist/{chunk-5XUGJM3J.js → chunk-YPLQQTWS.js} +1 -1
  45. package/dist/{chunk-CPQKWCJ2.js → chunk-ZKI4SDZ2.js} +16 -10
  46. package/dist/{chunk-YFTBCCVH.js → chunk-ZYK4NZZA.js} +5 -3
  47. package/dist/data.cjs +265 -35
  48. package/dist/data.d.cts +31 -1
  49. package/dist/data.d.ts +31 -1
  50. package/dist/data.js +16 -10
  51. package/dist/devtools.cjs +15 -2
  52. package/dist/devtools.js +9 -7
  53. package/dist/{dispose-GEIG2KOF.js → dispose-EIHHHDC7.js} +3 -1
  54. package/dist/ecosystem.cjs +62 -23
  55. package/dist/ecosystem.js +14 -12
  56. package/dist/extras.cjs +3562 -270
  57. package/dist/extras.d.cts +3 -3
  58. package/dist/extras.d.ts +3 -3
  59. package/dist/extras.js +39 -29
  60. package/dist/index.cjs +442 -253
  61. package/dist/index.d.cts +93 -283
  62. package/dist/index.d.ts +93 -283
  63. package/dist/index.js +26 -16
  64. package/dist/motion.cjs +12 -1
  65. package/dist/motion.js +6 -4
  66. package/dist/patterns.cjs +15 -2
  67. package/dist/patterns.js +10 -8
  68. package/dist/performance.cjs +36 -2
  69. package/dist/performance.d.cts +2 -2
  70. package/dist/performance.d.ts +2 -2
  71. package/dist/performance.js +14 -10
  72. package/dist/plugins.cjs +5267 -1807
  73. package/dist/plugins.d.cts +8 -834
  74. package/dist/plugins.d.ts +8 -834
  75. package/dist/plugins.js +64 -2329
  76. package/dist/prerenderServer-MXJEVWA3.js +350 -0
  77. package/dist/renderRoute-DP20XwMS.d.cts +86 -0
  78. package/dist/renderRoute-fxgsm2Ir.d.ts +86 -0
  79. package/dist/routeHead-CE9SV-cH.d.cts +995 -0
  80. package/dist/routeHead-DzVxPviY.d.ts +995 -0
  81. package/dist/{routeMiddleware-B1sp4P7a.d.ts → routeMiddleware-BySbueoE.d.ts} +0 -25
  82. package/dist/{routeMiddleware-CedQTsLK.d.cts → routeMiddleware-CijZD5eW.d.cts} +0 -25
  83. package/dist/{ssr-VBM6J5SJ.js → ssr-HHU3DAD2.js} +15 -6
  84. package/dist/ssr-JpGpHnpC.d.cts +841 -0
  85. package/dist/ssr-JpGpHnpC.d.ts +841 -0
  86. package/dist/ssr-dom.cjs +1904 -0
  87. package/dist/ssr-dom.d.cts +343 -0
  88. package/dist/ssr-dom.d.ts +343 -0
  89. package/dist/ssr-dom.js +19 -0
  90. package/dist/ssr.cjs +7677 -2069
  91. package/dist/ssr.d.cts +63 -5
  92. package/dist/ssr.d.ts +63 -5
  93. package/dist/ssr.js +136 -14
  94. package/dist/testing.cjs +6585 -87
  95. package/dist/testing.d.cts +80 -1
  96. package/dist/testing.d.ts +80 -1
  97. package/dist/testing.js +117 -4
  98. package/dist/ui.cjs +36 -2
  99. package/dist/ui.js +17 -14
  100. package/dist/widgets.cjs +14 -1
  101. package/dist/widgets.js +11 -9
  102. package/package.json +9 -2
  103. package/dist/chunk-2F4PSJ5Z.js +0 -765
  104. package/dist/chunk-7XHATCIH.js +0 -121
  105. package/dist/chunk-B36ADEIR.js +0 -21
  106. package/dist/ssr-Db0Sgdu3.d.cts +0 -245
  107. package/dist/ssr-Db0Sgdu3.d.ts +0 -245
package/dist/build.cjs CHANGED
@@ -5,6 +5,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
8
11
  var __export = (target, all) => {
9
12
  for (var name in all)
10
13
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -27,6 +30,362 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
30
  ));
28
31
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
32
 
33
+ // src/build/prerenderServer.ts
34
+ var prerenderServer_exports = {};
35
+ __export(prerenderServer_exports, {
36
+ assertPrerendered: () => assertPrerendered,
37
+ buildSitemap: () => buildSitemap,
38
+ expandRoutes: () => expandRoutes,
39
+ injectIntoShell: () => injectIntoShell,
40
+ prerenderFromServerEntry: () => prerenderFromServerEntry
41
+ });
42
+ function joinPath(parent, child) {
43
+ if (child === "*" || child.startsWith("/*")) return child;
44
+ const p2 = parent.replace(/\/+$/, "");
45
+ const c = child.startsWith("/") ? child : `/${child}`;
46
+ return (p2 + c).replace(/\/{2,}/g, "/") || "/";
47
+ }
48
+ async function expandRoutes(routes) {
49
+ const out = [];
50
+ const visit = async (records, parent) => {
51
+ for (const record of records) {
52
+ if (!record || typeof record.path !== "string") continue;
53
+ const full = parent ? joinPath(parent, record.path) : record.path;
54
+ if (record.children?.length) await visit(record.children, full);
55
+ if (full.includes("*")) continue;
56
+ if ("redirect" in record && record.redirect !== void 0) {
57
+ if (!/:/.test(full)) out.push(full);
58
+ continue;
59
+ }
60
+ if (/:/.test(full)) {
61
+ if (typeof record.getStaticPaths !== "function") continue;
62
+ let entries;
63
+ try {
64
+ entries = await record.getStaticPaths();
65
+ } catch (err) {
66
+ throw new Error(
67
+ `[sibu prerender] getStaticPaths() of "${full}" failed: ${err instanceof Error ? err.message : String(err)}. If it reads an API, start it and point \`prerender.fetch\` at it.`,
68
+ { cause: err }
69
+ );
70
+ }
71
+ for (const entry of entries) {
72
+ if (typeof entry === "string") {
73
+ out.push(entry);
74
+ continue;
75
+ }
76
+ let path2 = full;
77
+ for (const [key, value] of Object.entries(entry)) {
78
+ path2 = path2.replace(new RegExp(`:${key}(?=[/?#]|$)`, "g"), encodeURIComponent(String(value)));
79
+ }
80
+ if (/:[A-Za-z_]/.test(path2)) {
81
+ throw new Error(
82
+ `[sibu prerender] getStaticPaths() of "${full}" returned ${JSON.stringify(entry)}, which leaves a param unset`
83
+ );
84
+ }
85
+ out.push(path2);
86
+ }
87
+ continue;
88
+ }
89
+ if (record.children?.length) continue;
90
+ out.push(full);
91
+ }
92
+ };
93
+ await visit(routes, "");
94
+ return Array.from(new Set(out));
95
+ }
96
+ function escapeAttr(value) {
97
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
98
+ }
99
+ function escapeXml(value) {
100
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
101
+ }
102
+ function decodeTitleText(text2) {
103
+ const named = { amp: "&", lt: "<", gt: ">", quot: '"', apos: "'", nbsp: "\xA0" };
104
+ return text2.replace(/&(#x[0-9a-f]+|#[0-9]+|[a-z]+);/gi, (ref2, body2) => {
105
+ if (body2[0] !== "#") return named[body2.toLowerCase()] ?? ref2;
106
+ const code2 = body2[1] === "x" || body2[1] === "X" ? Number.parseInt(body2.slice(2), 16) : Number(body2.slice(1));
107
+ return code2 > 0 && code2 <= 1114111 ? String.fromCodePoint(code2) : ref2;
108
+ });
109
+ }
110
+ function injectIntoShell(shell, parts, container = "app") {
111
+ let html3 = shell;
112
+ const shellTitle = /<title>([\s\S]*?)<\/title>/i.exec(html3)?.[1] ?? "";
113
+ if (parts.head) {
114
+ let head2 = parts.head;
115
+ if (/<title>/i.test(head2)) {
116
+ html3 = html3.replace(/\s*<title>[\s\S]*?<\/title>/i, () => "");
117
+ const baseTitle = `<title data-sibu-base-title="${escapeAttr(decodeTitleText(shellTitle))}">`;
118
+ head2 = head2.replace(/<title>/i, () => baseTitle);
119
+ }
120
+ const headHtml = head2;
121
+ html3 = html3.replace(/<\/head>/i, () => ` ${headHtml}
122
+ </head>`);
123
+ }
124
+ if (parts.body) {
125
+ const id = container.replace(/[^A-Za-z0-9_-]/g, "");
126
+ const open = new RegExp(`(<([a-z][a-z0-9-]*)\\b[^>]*\\bid\\s*=\\s*["']${id}["'][^>]*>)([\\s\\S]*?)(</\\2>)`, "i");
127
+ if (!open.test(html3))
128
+ throw new Error(`[sibu prerender] the built index.html has no element with id="${id}" to render into`);
129
+ html3 = html3.replace(
130
+ open,
131
+ (_m, openTag, _tag, _inner, close) => `${openTag}${parts.body}${close}`
132
+ );
133
+ }
134
+ if (parts.state) html3 = html3.replace(/<\/body>/i, () => ` ${parts.state}
135
+ </body>`);
136
+ return html3;
137
+ }
138
+ function assertPrerendered(url, result, failures, timeout) {
139
+ if (failures.length > 0) {
140
+ throw new Error(
141
+ `[sibu prerender] ${url}: ${failures[0]}. The page needs data from an API that is not reachable at build time \u2014 start it and point \`prerender.fetch\` at it, or skip the request with isPrerendering().`
142
+ );
143
+ }
144
+ const pending = result.diagnostics.pending.join(", ");
145
+ if (result.failure === "timeout") {
146
+ throw new Error(
147
+ `[sibu prerender] ${url}: the route did not render within ${timeout} ms (pending: ${pending}). A lazy import, a guard or a route load did not finish in time; fix it or raise \`prerender.timeout\`.`
148
+ );
149
+ }
150
+ if (result.diagnostics.timedOut) {
151
+ throw new Error(`[sibu prerender] ${url}: did not settle within ${timeout} ms (pending: ${pending})`);
152
+ }
153
+ }
154
+ function buildSitemap(origin, paths) {
155
+ const urls = paths.map((p2) => ` <url><loc>${escapeXml(origin + p2)}</loc></url>`).join("\n");
156
+ return `<?xml version="1.0" encoding="UTF-8"?>
157
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
158
+ ${urls}
159
+ </urlset>
160
+ `;
161
+ }
162
+ async function prerenderFromServerEntry(options, ctx, helpers) {
163
+ const fs = await import("fs/promises");
164
+ const path2 = await import("path");
165
+ const { pathToFileURL } = await import("url");
166
+ const { AsyncLocalStorage } = await import("async_hooks");
167
+ const outDir = options.outDir ? path2.resolve(ctx.root, options.outDir) : ctx.outDir;
168
+ const origin = (options.origin ?? "http://localhost").replace(/\/+$/, "");
169
+ const serverEntry = path2.resolve(ctx.root, String(options.server).replace(/^\//, ""));
170
+ const keptShell = path2.join(ctx.outDir, typeof options.shell === "string" ? options.shell : "200.html");
171
+ const shellPath = await fs.access(keptShell).then(() => keptShell).catch(() => path2.join(ctx.outDir, "index.html"));
172
+ const shell = await fs.readFile(shellPath, "utf8");
173
+ const tmpDir = path2.join(
174
+ outDir,
175
+ `.sibu-prerender-${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`
176
+ );
177
+ await fs.rm(tmpDir, { recursive: true, force: true });
178
+ const viteBuild = await helpers.loadViteBuild();
179
+ const driverSource = `import { renderRoute } from "sibujs/ssr";
180
+ import * as entry from ${JSON.stringify(serverEntry.split(path2.sep).join("/"))};
181
+ export { renderRoute, entry };
182
+ `;
183
+ const prev = process.env[helpers.childEnv];
184
+ process.env[helpers.childEnv] = "1";
185
+ try {
186
+ await viteBuild({
187
+ configFile: ctx.configFile,
188
+ root: ctx.root,
189
+ mode: ctx.mode,
190
+ logLevel: "warn",
191
+ plugins: [
192
+ {
193
+ name: "sibu-prerender-driver",
194
+ resolveId: (id) => id === DRIVER_ID ? DRIVER_RESOLVED : null,
195
+ load: (id) => id === DRIVER_RESOLVED ? driverSource : null
196
+ }
197
+ ],
198
+ build: {
199
+ ssr: true,
200
+ outDir: tmpDir,
201
+ emptyOutDir: true,
202
+ copyPublicDir: false,
203
+ minify: false,
204
+ sourcemap: false,
205
+ rollupOptions: { input: DRIVER_ID, output: { format: "es", entryFileNames: "driver.mjs" } }
206
+ }
207
+ });
208
+ } finally {
209
+ if (prev === void 0) delete process.env[helpers.childEnv];
210
+ else process.env[helpers.childEnv] = prev;
211
+ }
212
+ await fs.writeFile(path2.join(tmpDir, "package.json"), '{ "type": "module" }\n', "utf8");
213
+ const currentRoute = new AsyncLocalStorage();
214
+ const realFetch = globalThis.fetch;
215
+ const custom = typeof options.fetch === "function" ? options.fetch : null;
216
+ const apiBase = typeof options.fetch === "string" ? options.fetch.replace(/\/+$/, "") : origin;
217
+ const buildFetch = (async (input2, init) => {
218
+ const raw = typeof input2 === "string" ? input2 : input2 instanceof URL ? input2.href : input2.url;
219
+ const url = /^[a-z][a-z0-9+.-]*:/i.test(raw) ? raw : apiBase + (raw.startsWith("/") ? raw : `/${raw}`);
220
+ try {
221
+ return await (custom ?? realFetch)(
222
+ typeof input2 === "object" && !(input2 instanceof URL) ? new Request(url, input2) : url,
223
+ init
224
+ );
225
+ } catch (err) {
226
+ const store2 = currentRoute.getStore();
227
+ const reason = err instanceof Error ? err.message : String(err);
228
+ store2?.failures.push(`GET ${url} failed (${reason})`);
229
+ throw err;
230
+ }
231
+ });
232
+ const driverUrl = pathToFileURL(path2.join(tmpDir, "driver.mjs")).href;
233
+ const results = [];
234
+ globalThis.fetch = buildFetch;
235
+ try {
236
+ const driver = await import(driverUrl);
237
+ const entry = driver.entry;
238
+ if (!Array.isArray(entry.routes)) {
239
+ throw new Error(
240
+ `[sibu prerender] the server entry ${options.server} must export \`routes\` (the app's route table)`
241
+ );
242
+ }
243
+ const explicit = Array.isArray(options.routes) ? options.routes : null;
244
+ const auto = explicit === null;
245
+ const listed = (explicit ? explicit.map((r) => typeof r === "string" ? { path: r } : r) : (await expandRoutes(entry.routes)).map((p2) => ({ path: p2 }))).filter((r) => r.path !== options.notFound);
246
+ const trailingSlash = options.trailingSlash === "never" ? "never" : "always";
247
+ const planned = listed.map((route) => ({ route, file: helpers.outputFileFor(route, trailingSlash) }));
248
+ const seen = /* @__PURE__ */ new Map();
249
+ for (const { route, file } of planned) {
250
+ helpers.resolveInsideOutDir(path2, outDir, file);
251
+ const other = seen.get(file);
252
+ if (other !== void 0) {
253
+ throw new Error(
254
+ `[sibu prerender] "${other}" and "${route.path}" would both be written to ${file}; a query variant needs its own \`output\` file.`
255
+ );
256
+ }
257
+ seen.set(file, route.path);
258
+ }
259
+ const renderOptions = {
260
+ router: { ...entry.routerOptions ?? {}, head: entry.head },
261
+ app: entry.app,
262
+ timeout: options.timeout ?? 15e3,
263
+ // A per-request page is written as the shell plus its route head: it is
264
+ // never rendered at build time, so its request-only fetches never run.
265
+ headOnly: ["ssr"],
266
+ hydration: options.hydration,
267
+ nonce: options.nonce,
268
+ maxStateBytes: options.maxStateBytes
269
+ };
270
+ const render = async (url) => {
271
+ const store2 = { route: url, failures: [] };
272
+ const result = await currentRoute.run(store2, () => driver.renderRoute(entry.routes, url, renderOptions));
273
+ assertPrerendered(url, result, store2.failures, renderOptions.timeout);
274
+ return result;
275
+ };
276
+ if (options.shell !== false) await fs.writeFile(path2.join(outDir, options.shell ?? "200.html"), shell, "utf8");
277
+ const redirects = [];
278
+ const indexable = [];
279
+ const queue = [...planned];
280
+ const concurrency = Math.max(1, options.concurrency ?? 4);
281
+ const write = async (file, html3) => {
282
+ const target = helpers.resolveInsideOutDir(path2, outDir, file);
283
+ await fs.mkdir(path2.dirname(target), { recursive: true });
284
+ await fs.writeFile(target, html3, "utf8");
285
+ };
286
+ await Promise.all(
287
+ Array.from({ length: Math.min(concurrency, queue.length) }, async () => {
288
+ for (let job = queue.shift(); job; job = queue.shift()) {
289
+ const { route, file } = job;
290
+ const started = Date.now();
291
+ const result = await render(route.path);
292
+ if (result.location !== null && result.redirectBy === "guard") {
293
+ results.push({
294
+ route: route.path,
295
+ file: "",
296
+ ms: Date.now() - started,
297
+ status: result.status,
298
+ kind: "skipped"
299
+ });
300
+ continue;
301
+ }
302
+ if (result.location !== null) {
303
+ redirects.push({ from: route.path, to: result.location, status: result.status });
304
+ results.push({
305
+ route: route.path,
306
+ file: "",
307
+ ms: Date.now() - started,
308
+ status: result.status,
309
+ kind: "redirect"
310
+ });
311
+ continue;
312
+ }
313
+ if (auto && result.failure === "guard") {
314
+ results.push({
315
+ route: route.path,
316
+ file: "",
317
+ ms: Date.now() - started,
318
+ status: result.status,
319
+ kind: "skipped"
320
+ });
321
+ continue;
322
+ }
323
+ if (result.status >= 400) {
324
+ throw new Error(
325
+ `[sibu prerender] ${route.path} answered ${result.status}${result.failure ? ` (${result.failure})` : ""}; list only pages that exist (the \`*\` route becomes 404.html through \`notFound\`).`
326
+ );
327
+ }
328
+ const body2 = result.render === "client" || result.render === "ssr" ? "" : result.html;
329
+ const state2 = body2 ? result.state : "";
330
+ await write(file, injectIntoShell(shell, { body: body2, head: result.head, state: state2 }, options.container));
331
+ if (!result.noindex && result.render !== "client") {
332
+ const pathname = route.path.split(/[?#]/)[0];
333
+ indexable.push(
334
+ trailingSlash === "always" && pathname !== "/" ? `${pathname.replace(/\/+$/, "")}/` : pathname
335
+ );
336
+ }
337
+ results.push({
338
+ route: route.path,
339
+ file,
340
+ ms: Date.now() - started,
341
+ status: result.status,
342
+ kind: body2 ? "page" : "shell"
343
+ });
344
+ }
345
+ })
346
+ );
347
+ const notFound = options.notFound ?? "/404";
348
+ if (notFound) {
349
+ const started = Date.now();
350
+ const result = await render(notFound);
351
+ if (result.status === 404) {
352
+ await write(
353
+ "404.html",
354
+ injectIntoShell(shell, { body: result.html, head: result.head, state: result.state }, options.container)
355
+ );
356
+ results.push({ route: notFound, file: "404.html", ms: Date.now() - started, status: 404, kind: "not-found" });
357
+ }
358
+ }
359
+ if (redirects.length > 0) await write("redirects.json", `${JSON.stringify(redirects, null, 2)}
360
+ `);
361
+ if (options.sitemap !== false && indexable.length > 0) {
362
+ indexable.sort();
363
+ await write("sitemap.xml", buildSitemap(origin, indexable));
364
+ }
365
+ if (options.robots) {
366
+ const extra = typeof options.robots === "string" ? `${options.robots.trim()}
367
+ ` : "";
368
+ const sitemapLine = options.sitemap !== false && indexable.length > 0 ? `Sitemap: ${origin}/sitemap.xml
369
+ ` : "";
370
+ await write("robots.txt", `User-agent: *
371
+ Allow: /
372
+ ${extra}${sitemapLine}`);
373
+ }
374
+ } finally {
375
+ globalThis.fetch = realFetch;
376
+ await fs.rm(tmpDir, { recursive: true, force: true });
377
+ }
378
+ return results;
379
+ }
380
+ var DRIVER_ID, DRIVER_RESOLVED;
381
+ var init_prerenderServer = __esm({
382
+ "src/build/prerenderServer.ts"() {
383
+ "use strict";
384
+ DRIVER_ID = "virtual:sibu-prerender-driver";
385
+ DRIVER_RESOLVED = `\0${DRIVER_ID}`;
386
+ }
387
+ });
388
+
30
389
  // build.ts
31
390
  var build_exports = {};
32
391
  __export(build_exports, {
@@ -237,6 +596,7 @@ function estimateImportSize(imports) {
237
596
  // index.ts
238
597
  var index_exports = {};
239
598
  __export(index_exports, {
599
+ ClientOnly: () => ClientOnly,
240
600
  DynamicComponent: () => DynamicComponent,
241
601
  ErrorBoundary: () => ErrorBoundary,
242
602
  ErrorDisplay: () => ErrorDisplay,
@@ -342,7 +702,9 @@ __export(index_exports, {
342
702
  input: () => input,
343
703
  ins: () => ins,
344
704
  isBatching: () => isBatching,
705
+ isClient: () => isClient,
345
706
  isSSR: () => isSSR,
707
+ isServer: () => isServer,
346
708
  kbd: () => kbd,
347
709
  label: () => label,
348
710
  lazy: () => lazy,
@@ -416,7 +778,7 @@ __export(index_exports, {
416
778
  setRuntimeErrorHandler: () => setRuntimeErrorHandler,
417
779
  show: () => show,
418
780
  signal: () => signal,
419
- slot: () => slot,
781
+ slot: () => slot2,
420
782
  small: () => small,
421
783
  source: () => source,
422
784
  span: () => span,
@@ -524,12 +886,12 @@ function claimedByBoundary(error, context2) {
524
886
  }
525
887
  function resolveEventTarget(node) {
526
888
  const ELEMENT_NODE = 1;
527
- let current = node;
528
- while (current) {
529
- if (current.nodeType === ELEMENT_NODE && typeof current.dispatchEvent === "function") {
530
- return current;
889
+ let current2 = node;
890
+ while (current2) {
891
+ if (current2.nodeType === ELEMENT_NODE && typeof current2.dispatchEvent === "function") {
892
+ return current2;
531
893
  }
532
- current = current.parentNode;
894
+ current2 = current2.parentNode;
533
895
  }
534
896
  const self = node;
535
897
  return typeof self.dispatchEvent === "function" ? self : null;
@@ -538,10 +900,21 @@ function defaultReport(error, context2) {
538
900
  if (typeof console === "undefined") return;
539
901
  console.error(`[SibuJS] Uncaught error during ${describe(context2)}: ${error.message}`, error);
540
902
  }
903
+ function requestErrorSink() {
904
+ const shared = globalThis[/* @__PURE__ */ Symbol.for("sibujs.ssr.v1")];
905
+ if (!shared) return void 0;
906
+ const store2 = shared.als ? shared.als.getStore() : shared.fallbackDepth > 0 ? shared.fallbackStore : void 0;
907
+ return store2?.errors;
908
+ }
541
909
  function reportError(error, context2) {
542
910
  const err = normalizeError(error);
543
911
  untrackedRun(() => {
544
912
  if (claimedByBoundary(err, context2)) return;
913
+ const sink = requestErrorSink();
914
+ if (sink) {
915
+ sink.push({ error: err, context: context2 });
916
+ return;
917
+ }
545
918
  const s2 = state();
546
919
  const handler = s2.handler;
547
920
  if (handler && s2.handlerDepth === 0) {
@@ -589,6 +962,78 @@ function devWarn(message) {
589
962
  }
590
963
  }
591
964
 
965
+ // src/core/rendering/adoptScope.ts
966
+ var current = null;
967
+ function inAdoptScope() {
968
+ return current?.s.on === true;
969
+ }
970
+ function currentSink() {
971
+ return current?.s.on ? current.s : null;
972
+ }
973
+ function createScopedElement(tag, ns) {
974
+ const c = current;
975
+ if (c?.s.on) {
976
+ const el = c.s.el(tag, ns, c.p + (c.n++).toString(36));
977
+ if (el) return el;
978
+ }
979
+ return ns ? document.createElementNS(ns, tag) : document.createElement(tag);
980
+ }
981
+ function captureScope() {
982
+ const c = current;
983
+ return c?.s.on ? { p: `${c.p}${(c.n++).toString(36)}.`, n: 0, s: c.s } : null;
984
+ }
985
+ function keyedScope(scope, key) {
986
+ if (!scope?.s.on) return null;
987
+ const type = typeof key;
988
+ if (type !== "string" && type !== "number") {
989
+ throw new TypeError(`[SibuJS] adopt: a key must be a string or a number, not ${type}`);
990
+ }
991
+ const text2 = String(key).replace(/[^\w-]/g, (c) => `~${c.charCodeAt(0).toString(16).padStart(4, "0")}`);
992
+ return { p: `${scope.p}${type === "string" ? "_" : "-"}${text2}.`, n: 0, s: scope.s };
993
+ }
994
+ function inScope(scope, fn) {
995
+ const prev = current;
996
+ if (!scope && !prev) return fn();
997
+ current = scope?.s.on ? { p: scope.p, n: 0, s: scope.s } : null;
998
+ try {
999
+ return fn();
1000
+ } finally {
1001
+ current = prev;
1002
+ }
1003
+ }
1004
+
1005
+ // src/core/rendering/hydrationHooks.ts
1006
+ var _a;
1007
+ var slot = globalThis[_a = /* @__PURE__ */ Symbol.for("sibujs.hydration.v2")] ?? (globalThis[_a] = { a: /* @__PURE__ */ new Set(), c: null });
1008
+ function isHydrating() {
1009
+ return slot.a.size > 0;
1010
+ }
1011
+ function ownSession() {
1012
+ return currentSink()?.h ?? slot.c;
1013
+ }
1014
+ function hydrationOwners() {
1015
+ const own = ownSession();
1016
+ return own ? own.open ? [own] : [] : Array.from(slot.a);
1017
+ }
1018
+ function afterHydration(fn) {
1019
+ const owners = hydrationOwners();
1020
+ let left = owners.length;
1021
+ const once = () => {
1022
+ if (--left < 1) fn();
1023
+ };
1024
+ if (left) for (const s2 of owners) s2.done.push(once);
1025
+ else fn();
1026
+ }
1027
+ function notify(session) {
1028
+ for (const wake of Array.from(session.wake)) wake();
1029
+ }
1030
+ function holdHydration(sessions, entry) {
1031
+ for (const s2 of sessions) s2.pending.add(entry);
1032
+ return () => {
1033
+ for (const s2 of sessions) if (s2.pending.delete(entry)) notify(s2);
1034
+ };
1035
+ }
1036
+
592
1037
  // src/core/rendering/dispose.ts
593
1038
  var elementDisposers = /* @__PURE__ */ new WeakMap();
594
1039
  var MAX_DRAIN_TEARDOWNS = 1e4;
@@ -685,9 +1130,9 @@ function dispose(node) {
685
1130
  const stack = [node];
686
1131
  const order = [];
687
1132
  while (stack.length > 0) {
688
- const current = stack.pop();
689
- order.push(current);
690
- const children = Array.from(current.childNodes);
1133
+ const current2 = stack.pop();
1134
+ order.push(current2);
1135
+ const children = Array.from(current2.childNodes);
691
1136
  for (let i2 = 0; i2 < children.length; i2++) {
692
1137
  stack.push(children[i2]);
693
1138
  }
@@ -726,13 +1171,27 @@ function disposeNodeOwn(node) {
726
1171
  }
727
1172
  }
728
1173
  function replaceChildrenSafely(parent, ...next) {
1174
+ if (isHydrating() && next.length > 0 && next[0] === parent.firstChild) {
1175
+ let ok = true;
1176
+ for (let i2 = 1; i2 < next.length && ok; i2++) ok = next[i2 - 1].nextSibling === next[i2];
1177
+ if (ok) {
1178
+ let rest = next[next.length - 1].nextSibling;
1179
+ while (rest) {
1180
+ const following = rest.nextSibling;
1181
+ dispose(rest);
1182
+ parent.removeChild(rest);
1183
+ rest = following;
1184
+ }
1185
+ return;
1186
+ }
1187
+ }
729
1188
  for (let i2 = 0; i2 < next.length; i2++) {
730
1189
  const node = next[i2];
731
1190
  node.parentNode?.removeChild(node);
732
1191
  }
733
- const current = Array.from(parent.childNodes);
734
- for (let i2 = 0; i2 < current.length; i2++) {
735
- dispose(current[i2]);
1192
+ const current2 = Array.from(parent.childNodes);
1193
+ for (let i2 = 0; i2 < current2.length; i2++) {
1194
+ dispose(current2[i2]);
736
1195
  }
737
1196
  parent.replaceChildren(...next);
738
1197
  }
@@ -804,19 +1263,19 @@ function sanitizeSrcset(value) {
804
1263
  if (url.endsWith(",")) {
805
1264
  url = url.replace(/,+$/, "");
806
1265
  } else {
807
- let current = "";
1266
+ let current2 = "";
808
1267
  let inParens = false;
809
1268
  while (i2 < n) {
810
1269
  const ch = input2[i2];
811
1270
  if (inParens) {
812
- current += ch;
1271
+ current2 += ch;
813
1272
  if (ch === ")") inParens = false;
814
1273
  i2++;
815
1274
  continue;
816
1275
  }
817
1276
  if (isSrcsetWhitespace(ch)) {
818
- if (current) descriptors.push(current);
819
- current = "";
1277
+ if (current2) descriptors.push(current2);
1278
+ current2 = "";
820
1279
  i2++;
821
1280
  continue;
822
1281
  }
@@ -825,10 +1284,10 @@ function sanitizeSrcset(value) {
825
1284
  break;
826
1285
  }
827
1286
  if (ch === "(") inParens = true;
828
- current += ch;
1287
+ current2 += ch;
829
1288
  i2++;
830
1289
  }
831
- if (current) descriptors.push(current);
1290
+ if (current2) descriptors.push(current2);
832
1291
  }
833
1292
  if (url === "") continue;
834
1293
  if (descriptors.length > 1) continue;
@@ -1401,8 +1860,8 @@ function commitSafeAttribute(el, name, value, options) {
1401
1860
  else el.removeAttribute(name);
1402
1861
  return true;
1403
1862
  }
1404
- const current = ns ? el.getAttributeNS(ns, localName) : el.getAttribute(name);
1405
- if (current === safe) return true;
1863
+ const current2 = ns ? el.getAttributeNS(ns, localName) : el.getAttribute(name);
1864
+ if (current2 === safe) return true;
1406
1865
  if (ns) el.setAttributeNS(ns, name, safe);
1407
1866
  else el.setAttribute(name, safe);
1408
1867
  return true;
@@ -1990,7 +2449,7 @@ function forEachSubscriber(signal2, visit) {
1990
2449
  }
1991
2450
 
1992
2451
  // src/reactivity/track.ts
1993
- var _runtimeVersion = true ? "4.13.1" : "dev";
2452
+ var _runtimeVersion = true ? "4.14.1" : "dev";
1994
2453
  var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
1995
2454
  function resolveReactiveApi() {
1996
2455
  const g2 = globalThis;
@@ -2211,10 +2670,11 @@ function restoreFocusWithin(snapshot, nodes, where, policy) {
2211
2670
  // src/reactivity/bindChildNode.ts
2212
2671
  function bindChildNode(placeholder, getter) {
2213
2672
  let lastNodes = [];
2673
+ const scope = captureScope();
2214
2674
  function commit() {
2215
2675
  let result;
2216
2676
  try {
2217
- result = getter();
2677
+ result = inScope(scope, getter);
2218
2678
  } catch (err) {
2219
2679
  reportError(err, { phase: "binding", name: "bindChildNode", node: placeholder });
2220
2680
  return;
@@ -2473,7 +2933,7 @@ function tagFactory(tag, ns) {
2473
2933
  throw new Error(`tagFactory: refusing to create <${tag}> \u2014 tag is blocked for security reasons.`);
2474
2934
  }
2475
2935
  if (typeof document === "undefined") noDocument();
2476
- const el = ns ? document.createElementNS(ns, tag) : document.createElement(tag);
2936
+ const el = createScopedElement(tag, ns);
2477
2937
  if (first === void 0) return el;
2478
2938
  if (typeof first === "string") {
2479
2939
  if (second !== void 0) {
@@ -2683,7 +3143,7 @@ var details = tagFactory("details");
2683
3143
  var dialog = tagFactory("dialog");
2684
3144
  var menu = tagFactory("menu");
2685
3145
  var summary = tagFactory("summary");
2686
- var slot = tagFactory("slot");
3146
+ var slot2 = tagFactory("slot");
2687
3147
  var template = tagFactory("template");
2688
3148
  var base = tagFactory("base");
2689
3149
  var link = tagFactory("link");
@@ -2714,6 +3174,152 @@ var font = tagFactory("font");
2714
3174
  var marquee = tagFactory("marquee");
2715
3175
  var customElement = (tagName) => tagFactory(tagName);
2716
3176
 
3177
+ // src/core/rendering/streamBoundary.ts
3178
+ var _a2;
3179
+ var slot3 = globalThis[_a2 = /* @__PURE__ */ Symbol.for("sibujs.streamBoundary.v1")] ?? (globalThis[_a2] = { hook: null, current: null });
3180
+ function openBoundary(container) {
3181
+ return slot3.hook ? slot3.hook(container) : null;
3182
+ }
3183
+ function currentBoundary() {
3184
+ return slot3.current;
3185
+ }
3186
+
3187
+ // src/core/ssr-context.ts
3188
+ var SSR_KEY = /* @__PURE__ */ Symbol.for("sibujs.ssr.v1");
3189
+ function detectSSRShared() {
3190
+ let detected = null;
3191
+ try {
3192
+ if (typeof process !== "undefined" && process.versions && process.versions.node) {
3193
+ let mod = null;
3194
+ const getBuiltin = process.getBuiltinModule;
3195
+ if (typeof getBuiltin === "function") {
3196
+ mod = getBuiltin("node:async_hooks");
3197
+ } else if (typeof require === "function") {
3198
+ mod = require("async_hooks");
3199
+ }
3200
+ if (mod) detected = new mod.AsyncLocalStorage();
3201
+ }
3202
+ const globalAls = globalThis.AsyncLocalStorage;
3203
+ if (!detected && typeof globalAls === "function") detected = new globalAls();
3204
+ } catch {
3205
+ detected = null;
3206
+ }
3207
+ return {
3208
+ als: detected,
3209
+ fallbackStore: { ssr: false, suspenseIdCounter: 0, locale: void 0 },
3210
+ fallbackDepth: 0
3211
+ };
3212
+ }
3213
+ var _shared = globalThis[SSR_KEY] ?? (globalThis[SSR_KEY] = detectSSRShared());
3214
+ var als = _shared.als;
3215
+ var fallbackStore = _shared.fallbackStore;
3216
+ _shared.fallbackDepth ?? (_shared.fallbackDepth = 0);
3217
+ var _alsWarned = false;
3218
+ function warnMissingAsyncLocalStorage() {
3219
+ if (_alsWarned || als) return;
3220
+ const isNode = typeof process !== "undefined" && !!process.versions?.node;
3221
+ if (!isNode) return;
3222
+ _alsWarned = true;
3223
+ const version = process.versions.node;
3224
+ devWarn(
3225
+ `SSR request isolation is UNAVAILABLE on this runtime (Node ${version}). AsyncLocalStorage could not be loaded, so concurrent requests share one SSR store: request state and the query cache can bleed between them. Node >= 22.3 is required for isolated SSR under ESM; the CommonJS build works on older versions. A fully synchronous render is unaffected.`
3226
+ );
3227
+ }
3228
+ function getSSRStore() {
3229
+ if (als) {
3230
+ const s2 = als.getStore();
3231
+ if (s2) return s2;
3232
+ }
3233
+ return fallbackStore;
3234
+ }
3235
+ function getRequestStore() {
3236
+ if (als) return als.getStore() ?? null;
3237
+ return _shared.fallbackDepth > 0 ? fallbackStore : null;
3238
+ }
3239
+ function isSSR() {
3240
+ return getSSRStore().ssr;
3241
+ }
3242
+ function getRequestScopedCache(name) {
3243
+ if (!isSSR()) return null;
3244
+ const store2 = getSSRStore();
3245
+ const caches = store2.caches ?? (store2.caches = /* @__PURE__ */ new Map());
3246
+ let c = caches.get(name);
3247
+ if (!c) {
3248
+ c = /* @__PURE__ */ new Map();
3249
+ caches.set(name, c);
3250
+ }
3251
+ return c;
3252
+ }
3253
+ var PRERENDER_HOST = /* @__PURE__ */ Symbol.for("sibujs.prerender.v1");
3254
+ function isServer() {
3255
+ return getSSRStore().ssr || globalThis[PRERENDER_HOST] !== void 0;
3256
+ }
3257
+ function isClient() {
3258
+ return !isServer();
3259
+ }
3260
+ function enableSSR() {
3261
+ getSSRStore().ssr = true;
3262
+ }
3263
+ function disableSSR() {
3264
+ getSSRStore().ssr = false;
3265
+ }
3266
+ function runInSSRContext(fn) {
3267
+ const store2 = {
3268
+ ssr: true,
3269
+ suspenseIdCounter: 0,
3270
+ idCounter: 0,
3271
+ locale: void 0,
3272
+ pending: /* @__PURE__ */ new Set(),
3273
+ wake: null
3274
+ };
3275
+ if (als) {
3276
+ return als.run(store2, fn);
3277
+ }
3278
+ warnMissingAsyncLocalStorage();
3279
+ const prev = { ...fallbackStore };
3280
+ Object.assign(fallbackStore, store2);
3281
+ _shared.fallbackDepth++;
3282
+ try {
3283
+ return fn();
3284
+ } finally {
3285
+ _shared.fallbackDepth--;
3286
+ for (const key of Object.keys(fallbackStore)) {
3287
+ if (!(key in prev)) delete fallbackStore[key];
3288
+ }
3289
+ Object.assign(fallbackStore, prev);
3290
+ }
3291
+ }
3292
+ function trackPending(promise, label2, hydration = true) {
3293
+ const store2 = getRequestStore();
3294
+ const pending = store2?.pending;
3295
+ const sessions = pending || !hydration ? [] : hydrationOwners();
3296
+ if (!pending && sessions.length === 0) return promise;
3297
+ const entry = { label: label2, since: Date.now(), boundary: currentBoundary() ?? void 0 };
3298
+ pending?.add(entry);
3299
+ const release = holdHydration(sessions, entry);
3300
+ const done = () => {
3301
+ pending?.delete(entry);
3302
+ store2?.wake?.();
3303
+ release();
3304
+ };
3305
+ try {
3306
+ Promise.resolve(promise).then(done, done);
3307
+ } catch {
3308
+ done();
3309
+ }
3310
+ return promise;
3311
+ }
3312
+ function withSSR(fn) {
3313
+ const store2 = getSSRStore();
3314
+ const wasSSR = store2.ssr;
3315
+ store2.ssr = true;
3316
+ try {
3317
+ return fn();
3318
+ } finally {
3319
+ if (!wasSSR) store2.ssr = false;
3320
+ }
3321
+ }
3322
+
2717
3323
  // src/core/rendering/lazy.ts
2718
3324
  var PENDING_ERROR = "__sibuPendingError";
2719
3325
  function dispatchPropagate(node, error) {
@@ -2740,18 +3346,28 @@ function takePendingError(node) {
2740
3346
  function lazy(importFn) {
2741
3347
  let cached = null;
2742
3348
  return function LazyComponent() {
2743
- if (cached) {
3349
+ if (cached && !inAdoptScope()) {
2744
3350
  return cached();
2745
3351
  }
2746
3352
  const container = div({ class: "sibu-lazy" });
3353
+ const scope = captureScope();
3354
+ const boundary = currentBoundary();
2747
3355
  let disposed = false;
2748
- importFn().then(
3356
+ const loading = inScope(null, () => span("sibu-lazy-loading", "Loading..."));
3357
+ if (cached) {
3358
+ const loaded = cached;
3359
+ replaceChildrenSafely(container, inScope(scope, () => loaded()));
3360
+ return container;
3361
+ }
3362
+ trackPending(importFn(), "lazy").then(
2749
3363
  // The component renders after the import resolves, in the owner scope
2750
3364
  // of whoever rendered the placeholder.
2751
3365
  bindOwnerScope2((mod) => {
2752
3366
  if (disposed) return;
2753
3367
  cached = mod.default;
2754
- const rendered = cached();
3368
+ const component = cached;
3369
+ const build = () => inScope(scope, () => component());
3370
+ const rendered = boundary ? boundary.enter(build) : build();
2755
3371
  replaceChildrenSafely(container, rendered);
2756
3372
  })
2757
3373
  ).catch((err) => {
@@ -2763,7 +3379,7 @@ function lazy(importFn) {
2763
3379
  );
2764
3380
  dispatchPropagate(container, errorObj);
2765
3381
  });
2766
- container.appendChild(span("sibu-lazy-loading", "Loading..."));
3382
+ if (!(isHydrating() && container.firstChild)) container.appendChild(loading);
2767
3383
  registerDisposer(container, () => {
2768
3384
  disposed = true;
2769
3385
  });
@@ -2772,8 +3388,15 @@ function lazy(importFn) {
2772
3388
  }
2773
3389
  function Suspense({ nodes, fallback }) {
2774
3390
  const container = div({ class: "sibu-suspense" });
2775
- const fallbackEl = fallback();
2776
- container.appendChild(fallbackEl);
3391
+ const scope = captureScope();
3392
+ const boundary = openBoundary(container);
3393
+ const commit = (el) => {
3394
+ replaceChildrenSafely(container, el);
3395
+ boundary?.done();
3396
+ };
3397
+ const fallbackEl = inScope(null, fallback);
3398
+ if (isHydrating() && container.firstChild) dispose(fallbackEl);
3399
+ else container.appendChild(fallbackEl);
2777
3400
  let suspenseDisposed = false;
2778
3401
  let observer = null;
2779
3402
  let childEl = null;
@@ -2788,11 +3411,11 @@ function Suspense({ nodes, fallback }) {
2788
3411
  const renderContent = bindOwnerScope2(() => {
2789
3412
  if (suspenseDisposed) return;
2790
3413
  try {
2791
- const el = nodes();
3414
+ const el = boundary ? boundary.enter(() => inScope(scope, nodes)) : inScope(scope, nodes);
2792
3415
  childEl = el;
2793
3416
  if (el.classList.contains("sibu-lazy")) {
2794
3417
  if (!el.querySelector(".sibu-lazy-loading")) {
2795
- replaceChildrenSafely(container, el);
3418
+ commit(el);
2796
3419
  return;
2797
3420
  }
2798
3421
  observer = new MutationObserver(() => {
@@ -2801,12 +3424,12 @@ function Suspense({ nodes, fallback }) {
2801
3424
  if (!loading) {
2802
3425
  observer?.disconnect();
2803
3426
  observer = null;
2804
- replaceChildrenSafely(container, el);
3427
+ commit(el);
2805
3428
  }
2806
3429
  });
2807
3430
  observer.observe(el, { childList: true, subtree: true });
2808
3431
  } else {
2809
- replaceChildrenSafely(container, el);
3432
+ commit(el);
2810
3433
  }
2811
3434
  } catch (err) {
2812
3435
  const errorObj = err instanceof Error ? err : new Error(String(err));
@@ -2996,7 +3619,7 @@ function registerUnmountWatcher(element, cb) {
2996
3619
  };
2997
3620
  }
2998
3621
  function onMount(callback, element) {
2999
- if (typeof document === "undefined") return;
3622
+ if (typeof document === "undefined" || isSSR()) return;
3000
3623
  const scope = getOwnerScope2();
3001
3624
  callback = pinScope(scope, callback);
3002
3625
  if (element) {
@@ -3031,7 +3654,7 @@ function onMount(callback, element) {
3031
3654
  }
3032
3655
  }
3033
3656
  function onUnmount(callback, element) {
3034
- if (typeof document === "undefined") return;
3657
+ if (typeof document === "undefined" || isSSR()) return;
3035
3658
  watchUnmount(pinScope(getOwnerScope2(), callback), element);
3036
3659
  }
3037
3660
  function watchUnmount(callback, element) {
@@ -3068,112 +3691,6 @@ function emitDevtools(hook, event, payload) {
3068
3691
  }
3069
3692
  }
3070
3693
 
3071
- // src/core/ssr-context.ts
3072
- var SSR_KEY = /* @__PURE__ */ Symbol.for("sibujs.ssr.v1");
3073
- function detectSSRShared() {
3074
- let detected = null;
3075
- try {
3076
- if (typeof process !== "undefined" && process.versions && process.versions.node) {
3077
- let mod = null;
3078
- const getBuiltin = process.getBuiltinModule;
3079
- if (typeof getBuiltin === "function") {
3080
- mod = getBuiltin("node:async_hooks");
3081
- } else if (typeof require === "function") {
3082
- mod = require("async_hooks");
3083
- }
3084
- if (mod) detected = new mod.AsyncLocalStorage();
3085
- }
3086
- } catch {
3087
- detected = null;
3088
- }
3089
- return {
3090
- als: detected,
3091
- fallbackStore: { ssr: false, suspenseIdCounter: 0, locale: void 0 },
3092
- fallbackDepth: 0
3093
- };
3094
- }
3095
- var _shared = globalThis[SSR_KEY] ?? (globalThis[SSR_KEY] = detectSSRShared());
3096
- var als = _shared.als;
3097
- var fallbackStore = _shared.fallbackStore;
3098
- _shared.fallbackDepth ?? (_shared.fallbackDepth = 0);
3099
- var _alsWarned = false;
3100
- function warnMissingAsyncLocalStorage() {
3101
- if (_alsWarned || als) return;
3102
- const isNode = typeof process !== "undefined" && !!process.versions?.node;
3103
- if (!isNode) return;
3104
- _alsWarned = true;
3105
- const version = process.versions.node;
3106
- devWarn(
3107
- `SSR request isolation is UNAVAILABLE on this runtime (Node ${version}). AsyncLocalStorage could not be loaded, so concurrent requests share one SSR store: request state and the query cache can bleed between them. Node >= 22.3 is required for isolated SSR under ESM; the CommonJS build works on older versions. A fully synchronous render is unaffected.`
3108
- );
3109
- }
3110
- function getSSRStore() {
3111
- if (als) {
3112
- const s2 = als.getStore();
3113
- if (s2) return s2;
3114
- }
3115
- return fallbackStore;
3116
- }
3117
- function getRequestStore() {
3118
- if (als) return als.getStore() ?? null;
3119
- return _shared.fallbackDepth > 0 ? fallbackStore : null;
3120
- }
3121
- function isSSR() {
3122
- return getSSRStore().ssr;
3123
- }
3124
- function getRequestScopedCache(name) {
3125
- if (!isSSR()) return null;
3126
- const store2 = getSSRStore();
3127
- const caches = store2.caches ?? (store2.caches = /* @__PURE__ */ new Map());
3128
- let c = caches.get(name);
3129
- if (!c) {
3130
- c = /* @__PURE__ */ new Map();
3131
- caches.set(name, c);
3132
- }
3133
- return c;
3134
- }
3135
- function enableSSR() {
3136
- getSSRStore().ssr = true;
3137
- }
3138
- function disableSSR() {
3139
- getSSRStore().ssr = false;
3140
- }
3141
- function runInSSRContext(fn) {
3142
- const store2 = { ssr: true, suspenseIdCounter: 0, idCounter: 0, locale: void 0 };
3143
- if (als) {
3144
- return als.run(store2, fn);
3145
- }
3146
- warnMissingAsyncLocalStorage();
3147
- const prevSSR = fallbackStore.ssr;
3148
- const prevCounter = fallbackStore.suspenseIdCounter;
3149
- const prevIdCounter = fallbackStore.idCounter;
3150
- const prevLocale = fallbackStore.locale;
3151
- fallbackStore.ssr = true;
3152
- fallbackStore.suspenseIdCounter = 0;
3153
- fallbackStore.idCounter = 0;
3154
- fallbackStore.locale = void 0;
3155
- _shared.fallbackDepth++;
3156
- try {
3157
- return fn();
3158
- } finally {
3159
- _shared.fallbackDepth--;
3160
- fallbackStore.ssr = prevSSR;
3161
- fallbackStore.suspenseIdCounter = prevCounter;
3162
- fallbackStore.idCounter = prevIdCounter;
3163
- fallbackStore.locale = prevLocale;
3164
- }
3165
- }
3166
- function withSSR(fn) {
3167
- const store2 = getSSRStore();
3168
- const wasSSR = store2.ssr;
3169
- store2.ssr = true;
3170
- try {
3171
- return fn();
3172
- } finally {
3173
- if (!wasSSR) store2.ssr = false;
3174
- }
3175
- }
3176
-
3177
3694
  // src/core/signals/effect.ts
3178
3695
  var _g = globalThis;
3179
3696
  function on(deps, handler) {
@@ -3254,7 +3771,7 @@ function disposeEffect(ctx) {
3254
3771
  }
3255
3772
  function effect(effectFn, options) {
3256
3773
  devAssert(typeof effectFn === "function", "effect: argument must be a function.");
3257
- if (isSSR()) return () => {
3774
+ if (isSSR() && !options?.server) return () => {
3258
3775
  };
3259
3776
  const ctx = {
3260
3777
  fn: effectFn,
@@ -4060,21 +4577,21 @@ function ErrorBoundary(optionsOrChildren, maybeChildren) {
4060
4577
  stopResetKeysWatcher();
4061
4578
  let baseline = null;
4062
4579
  resetKeysTeardown = effect(() => {
4063
- const current = readResetKeyValues(keys);
4580
+ const current2 = readResetKeyValues(keys);
4064
4581
  if (baseline === null) {
4065
- baseline = current;
4582
+ baseline = current2;
4066
4583
  return;
4067
4584
  }
4068
- let changed = current.length !== baseline.length;
4585
+ let changed = current2.length !== baseline.length;
4069
4586
  if (!changed) {
4070
- for (let i2 = 0; i2 < current.length; i2++) {
4071
- if (!Object.is(current[i2], baseline[i2])) {
4587
+ for (let i2 = 0; i2 < current2.length; i2++) {
4588
+ if (!Object.is(current2[i2], baseline[i2])) {
4072
4589
  changed = true;
4073
4590
  break;
4074
4591
  }
4075
4592
  }
4076
4593
  }
4077
- baseline = current;
4594
+ baseline = current2;
4078
4595
  if (changed) retry();
4079
4596
  });
4080
4597
  };
@@ -4466,68 +4983,6 @@ function setGlobalErrorHandler(handler) {
4466
4983
  globalErrorHandler = handler;
4467
4984
  }
4468
4985
 
4469
- // src/core/rendering/context.ts
4470
- function context(defaultValue) {
4471
- const [getValue, setValue] = signal(defaultValue);
4472
- const warnIfSSR = (method) => {
4473
- if (!DEV || !isSSR()) return;
4474
- devWarn(
4475
- `context.${method}() called during SSR. SibuJS context() is application-global \u2014 it is NOT isolated per request, so a concurrent request can observe this value. Do not use context() for request-specific SSR data; pass it explicitly or use request-scoped storage instead.`
4476
- );
4477
- };
4478
- const ctx = {
4479
- provide(value) {
4480
- warnIfSSR("provide");
4481
- const previous = getValue();
4482
- setValue(value);
4483
- return () => setValue(previous);
4484
- },
4485
- use() {
4486
- return getValue;
4487
- },
4488
- get() {
4489
- return getValue();
4490
- },
4491
- set(value) {
4492
- warnIfSSR("set");
4493
- setValue(value);
4494
- },
4495
- withContext(value, fn) {
4496
- warnIfSSR("withContext");
4497
- const previous = getValue();
4498
- setValue(value);
4499
- try {
4500
- const result = fn();
4501
- if (DEV && result !== null && typeof result?.then === "function") {
4502
- devWarn(
4503
- "context.withContext() received an async callback. Scoping is synchronous only \u2014 the previous value is restored as soon as the callback returns its promise, so anything after an `await` runs outside the scope."
4504
- );
4505
- }
4506
- return result;
4507
- } finally {
4508
- setValue(previous);
4509
- }
4510
- }
4511
- };
4512
- return ctx;
4513
- }
4514
-
4515
- // src/core/rendering/createId.ts
4516
- var COUNTER_KEY = /* @__PURE__ */ Symbol.for("sibujs.createId.v1");
4517
- var _counter = globalThis[COUNTER_KEY] ?? (globalThis[COUNTER_KEY] = { n: 0 });
4518
- function createId(prefix = "sibu") {
4519
- const request = getRequestStore();
4520
- if (request) {
4521
- request.idCounter = (request.idCounter ?? 0) + 1;
4522
- return `${prefix}-${request.idCounter}`;
4523
- }
4524
- _counter.n++;
4525
- return `${prefix}-${_counter.n}`;
4526
- }
4527
- function __resetIdCounter() {
4528
- _counter.n = 0;
4529
- }
4530
-
4531
4986
  // src/core/rendering/directives.ts
4532
4987
  function show(condition, element) {
4533
4988
  const resolved = typeof element === "function" ? element() : element;
@@ -4553,7 +5008,7 @@ function attachBranch(parent, anchor, branch, attachedOnce, where) {
4553
5008
  () => `${where}: ${handedIn ? "a branch was given as an element rather than a function" : "a branch factory returned a node it had already returned"}, so the SAME node is being re-attached \u2014 any state it accumulated while detached (input value, scroll position, classes set imperatively) comes back with it. Its reactive bindings are left intact rather than disposed, because the node is not this directive's to tear down. Return a FRESH node per call (\`() => div(\u2026)\`) for rebuild semantics, or keep this form deliberately when the reuse is what you want.`
4554
5009
  );
4555
5010
  }
4556
- parent.insertBefore(node, anchor.nextSibling);
5011
+ if (anchor.nextSibling !== node) parent.insertBefore(node, anchor.nextSibling);
4557
5012
  return { node, owned };
4558
5013
  }
4559
5014
  function releaseBranchNode(node, owned) {
@@ -4569,6 +5024,7 @@ function when(condition, thenBranch, elseBranch, options) {
4569
5024
  const elseNode = elseBranch;
4570
5025
  const focusPolicy = options?.focus;
4571
5026
  const anchor = document.createComment("when");
5027
+ const scope = captureScope();
4572
5028
  let currentNode = null;
4573
5029
  let lastCondition;
4574
5030
  let initialized = false;
@@ -4588,7 +5044,7 @@ function when(condition, thenBranch, elseBranch, options) {
4588
5044
  currentNode = null;
4589
5045
  }
4590
5046
  const branch = show2 ? thenBranch : elseNode !== void 0 ? elseNode : null;
4591
- const attached = untracked2(() => attachBranch(parent, anchor, branch, attachedOnce, "when"));
5047
+ const attached = untracked2(() => inScope(scope, () => attachBranch(parent, anchor, branch, attachedOnce, "when")));
4592
5048
  currentNode = attached.node;
4593
5049
  currentNodeOwned = attached.owned;
4594
5050
  restoreFocusWithin(focused, currentNode ? [currentNode] : [], "when", focusPolicy);
@@ -4619,6 +5075,7 @@ function match(value, cases, fallback, options) {
4619
5075
  const fallbackNode = fallback;
4620
5076
  const focusPolicy = options?.focus;
4621
5077
  const anchor = document.createComment("match");
5078
+ const scope = captureScope();
4622
5079
  let currentNode = null;
4623
5080
  let lastKey;
4624
5081
  let initialized = false;
@@ -4638,7 +5095,9 @@ function match(value, cases, fallback, options) {
4638
5095
  currentNode = null;
4639
5096
  }
4640
5097
  const branch = Object.hasOwn(cases, key) ? cases[key] : fallbackNode;
4641
- const attached = untracked2(() => attachBranch(parent, anchor, branch ?? null, attachedOnce, "match"));
5098
+ const attached = untracked2(
5099
+ () => inScope(scope, () => attachBranch(parent, anchor, branch ?? null, attachedOnce, "match"))
5100
+ );
4642
5101
  currentNode = attached.node;
4643
5102
  currentNodeOwned = attached.owned;
4644
5103
  restoreFocusWithin(focused, currentNode ? [currentNode] : [], "match", focusPolicy);
@@ -4661,6 +5120,76 @@ function match(value, cases, fallback, options) {
4661
5120
  return anchor;
4662
5121
  }
4663
5122
 
5123
+ // src/core/rendering/clientOnly.ts
5124
+ function ClientOnly(content, options = {}) {
5125
+ const server = isServer();
5126
+ const [ready, setReady] = signal(!server && !isHydrating());
5127
+ if (!server && !ready()) afterHydration(() => setReady(true));
5128
+ return when(ready, content, options.fallback);
5129
+ }
5130
+
5131
+ // src/core/rendering/context.ts
5132
+ function context(defaultValue) {
5133
+ const [getValue, setValue] = signal(defaultValue);
5134
+ const warnIfSSR = (method) => {
5135
+ if (!DEV || !isSSR()) return;
5136
+ devWarn(
5137
+ `context.${method}() called during SSR. SibuJS context() is application-global \u2014 it is NOT isolated per request, so a concurrent request can observe this value. Do not use context() for request-specific SSR data; pass it explicitly or use request-scoped storage instead.`
5138
+ );
5139
+ };
5140
+ const ctx = {
5141
+ provide(value) {
5142
+ warnIfSSR("provide");
5143
+ const previous = getValue();
5144
+ setValue(value);
5145
+ return () => setValue(previous);
5146
+ },
5147
+ use() {
5148
+ return getValue;
5149
+ },
5150
+ get() {
5151
+ return getValue();
5152
+ },
5153
+ set(value) {
5154
+ warnIfSSR("set");
5155
+ setValue(value);
5156
+ },
5157
+ withContext(value, fn) {
5158
+ warnIfSSR("withContext");
5159
+ const previous = getValue();
5160
+ setValue(value);
5161
+ try {
5162
+ const result = fn();
5163
+ if (DEV && result !== null && typeof result?.then === "function") {
5164
+ devWarn(
5165
+ "context.withContext() received an async callback. Scoping is synchronous only \u2014 the previous value is restored as soon as the callback returns its promise, so anything after an `await` runs outside the scope."
5166
+ );
5167
+ }
5168
+ return result;
5169
+ } finally {
5170
+ setValue(previous);
5171
+ }
5172
+ }
5173
+ };
5174
+ return ctx;
5175
+ }
5176
+
5177
+ // src/core/rendering/createId.ts
5178
+ var COUNTER_KEY = /* @__PURE__ */ Symbol.for("sibujs.createId.v1");
5179
+ var _counter = globalThis[COUNTER_KEY] ?? (globalThis[COUNTER_KEY] = { n: 0 });
5180
+ function createId(prefix = "sibu") {
5181
+ const request = getRequestStore();
5182
+ if (request) {
5183
+ request.idCounter = (request.idCounter ?? 0) + 1;
5184
+ return `${prefix}-${request.idCounter}`;
5185
+ }
5186
+ _counter.n++;
5187
+ return `${prefix}-${_counter.n}`;
5188
+ }
5189
+ function __resetIdCounter() {
5190
+ _counter.n = 0;
5191
+ }
5192
+
4664
5193
  // src/utils/globalSingleton.ts
4665
5194
  function globalSingleton(key, create) {
4666
5195
  const g2 = globalThis;
@@ -4762,6 +5291,7 @@ function each(getArray, render, options) {
4762
5291
  );
4763
5292
  requireDocument();
4764
5293
  const anchor = document.createComment("each:anchor");
5294
+ const scope = captureScope();
4765
5295
  const end = document.createComment("each:end");
4766
5296
  const oldKeysBufA = [];
4767
5297
  const oldKeysBufB = [];
@@ -4791,7 +5321,9 @@ function each(getArray, render, options) {
4791
5321
  const parent = anchor.parentNode;
4792
5322
  if (!parent) return;
4793
5323
  if (!sentinelInserted) {
4794
- parent.insertBefore(end, anchor.nextSibling);
5324
+ const serverEnd = isHydrating() ? findServerEnd(anchor) : null;
5325
+ if (serverEnd) parent.replaceChild(end, serverEnd);
5326
+ else parent.insertBefore(end, anchor.nextSibling);
4795
5327
  sentinelInserted = true;
4796
5328
  }
4797
5329
  if (newKeysBuf.length < newLen) {
@@ -4832,7 +5364,7 @@ function each(getArray, render, options) {
4832
5364
  try {
4833
5365
  suspendTracking2();
4834
5366
  try {
4835
- node = resolveNodeChild(render(itemGetter, indexGetter));
5367
+ node = resolveNodeChild(inScope(keyedScope(scope, key), () => render(itemGetter, indexGetter)));
4836
5368
  } finally {
4837
5369
  resumeTracking2();
4838
5370
  }
@@ -4966,6 +5498,19 @@ function each(getArray, render, options) {
4966
5498
  }
4967
5499
  return anchor;
4968
5500
  }
5501
+ function findServerEnd(anchor) {
5502
+ let depth = 0;
5503
+ for (let n = anchor.nextSibling; n; n = n.nextSibling) {
5504
+ if (n.nodeType !== 8) continue;
5505
+ const data2 = n.data;
5506
+ if (data2 === "each:anchor") depth++;
5507
+ else if (data2 === "each:end") {
5508
+ if (depth === 0) return n;
5509
+ depth--;
5510
+ }
5511
+ }
5512
+ return null;
5513
+ }
4969
5514
 
4970
5515
  // src/core/rendering/fragment.ts
4971
5516
  function Fragment(nodes) {
@@ -5089,19 +5634,19 @@ function parseTemplate(strings) {
5089
5634
  pos++;
5090
5635
  const statics = [];
5091
5636
  const exprs = [];
5092
- let current = "";
5637
+ let current2 = "";
5093
5638
  while (pos < len && template2[pos] !== quote2) {
5094
5639
  const innerIdx = tryExprIdx();
5095
5640
  if (innerIdx >= 0) {
5096
- statics.push(current);
5097
- current = "";
5641
+ statics.push(current2);
5642
+ current2 = "";
5098
5643
  exprs.push(innerIdx);
5099
5644
  } else {
5100
- current += template2[pos++];
5645
+ current2 += template2[pos++];
5101
5646
  }
5102
5647
  }
5103
5648
  if (pos < len) pos++;
5104
- statics.push(current);
5649
+ statics.push(current2);
5105
5650
  if (exprs.length === 0) {
5106
5651
  return { kind: "static", value: statics[0] };
5107
5652
  }
@@ -5110,20 +5655,20 @@ function parseTemplate(strings) {
5110
5655
  {
5111
5656
  const statics = [];
5112
5657
  const exprs = [];
5113
- let current = "";
5658
+ let current2 = "";
5114
5659
  while (pos < len) {
5115
5660
  const c = template2.charCodeAt(pos);
5116
5661
  if (c === 32 || c === 9 || c === 10 || c === 13 || c === 62) break;
5117
5662
  const innerIdx = tryExprIdx();
5118
5663
  if (innerIdx >= 0) {
5119
- statics.push(current);
5120
- current = "";
5664
+ statics.push(current2);
5665
+ current2 = "";
5121
5666
  exprs.push(innerIdx);
5122
5667
  } else {
5123
- current += template2[pos++];
5668
+ current2 += template2[pos++];
5124
5669
  }
5125
5670
  }
5126
- statics.push(current);
5671
+ statics.push(current2);
5127
5672
  if (exprs.length === 0) {
5128
5673
  return { kind: "static", value: statics[0] };
5129
5674
  }
@@ -5247,7 +5792,7 @@ function parseTemplate(strings) {
5247
5792
  return parseChildren();
5248
5793
  }
5249
5794
  function executeElement(tmpl, values) {
5250
- const el = tmpl.svg ? document.createElementNS(SVG_NS3, tmpl.tag) : document.createElement(tmpl.tag);
5795
+ const el = createScopedElement(tmpl.tag, tmpl.svg ? SVG_NS3 : void 0);
5251
5796
  for (let i2 = 0; i2 < tmpl.attrs.length; i2++) {
5252
5797
  const attr = tmpl.attrs[i2];
5253
5798
  switch (attr.t) {
@@ -5340,7 +5885,7 @@ function renderTemplate(tmpl, values) {
5340
5885
  if (tmpl.length === 1 && tmpl[0].t === 0) {
5341
5886
  return executeElement(tmpl[0].el, values);
5342
5887
  }
5343
- const wrapper = document.createElement("div");
5888
+ const wrapper = createScopedElement("div");
5344
5889
  for (let i2 = 0; i2 < tmpl.length; i2++) {
5345
5890
  const child = tmpl[i2];
5346
5891
  switch (child.t) {
@@ -5584,8 +6129,8 @@ function Portal(nodes, target) {
5584
6129
  // src/core/rendering/slots.ts
5585
6130
  function getSlot(slots, name = "default") {
5586
6131
  if (slots == null || !Object.hasOwn(slots, name)) return void 0;
5587
- const slot2 = slots[name];
5588
- return typeof slot2 === "function" ? slot2 : void 0;
6132
+ const slot4 = slots[name];
6133
+ return typeof slot4 === "function" ? slot4 : void 0;
5589
6134
  }
5590
6135
 
5591
6136
  // src/core/signals/array.ts
@@ -5669,7 +6214,7 @@ function reactiveArray(initial = []) {
5669
6214
  let data2 = [...initial];
5670
6215
  let snapshot = null;
5671
6216
  const signal2 = { __v: 0 };
5672
- function notify() {
6217
+ function notify2() {
5673
6218
  snapshot = null;
5674
6219
  signal2.__v++;
5675
6220
  if (!enqueueBatchedSignal(signal2)) {
@@ -5688,52 +6233,52 @@ function reactiveArray(initial = []) {
5688
6233
  push(...items) {
5689
6234
  if (items.length === 0) return;
5690
6235
  data2.push(...items);
5691
- notify();
6236
+ notify2();
5692
6237
  },
5693
6238
  pop() {
5694
6239
  if (data2.length === 0) return void 0;
5695
6240
  const removed = data2.pop();
5696
- notify();
6241
+ notify2();
5697
6242
  return removed;
5698
6243
  },
5699
6244
  shift() {
5700
6245
  if (data2.length === 0) return void 0;
5701
6246
  const removed = data2.shift();
5702
- notify();
6247
+ notify2();
5703
6248
  return removed;
5704
6249
  },
5705
6250
  unshift(...items) {
5706
6251
  if (items.length === 0) return;
5707
6252
  data2.unshift(...items);
5708
- notify();
6253
+ notify2();
5709
6254
  },
5710
6255
  splice(start, ...rest) {
5711
6256
  const removed = nativeSplice(data2, start, rest);
5712
6257
  if (removed.length > 0 || rest.length > 1) {
5713
- notify();
6258
+ notify2();
5714
6259
  }
5715
6260
  return removed;
5716
6261
  },
5717
6262
  remove(index) {
5718
6263
  if (index < 0 || index >= data2.length) return;
5719
6264
  data2.splice(index, 1);
5720
- notify();
6265
+ notify2();
5721
6266
  },
5722
6267
  removeWhere(predicate) {
5723
6268
  const idx = data2.findIndex(predicate);
5724
6269
  if (idx === -1) return;
5725
6270
  data2.splice(idx, 1);
5726
- notify();
6271
+ notify2();
5727
6272
  },
5728
6273
  set(items) {
5729
6274
  data2 = [...items];
5730
- notify();
6275
+ notify2();
5731
6276
  },
5732
6277
  update(index, value) {
5733
6278
  if (index < 0 || index >= data2.length) return;
5734
6279
  if (Object.is(data2[index], value)) return;
5735
6280
  data2[index] = value;
5736
- notify();
6281
+ notify2();
5737
6282
  },
5738
6283
  updateWhere(predicate, updater) {
5739
6284
  let changed = false;
@@ -5746,23 +6291,23 @@ function reactiveArray(initial = []) {
5746
6291
  }
5747
6292
  }
5748
6293
  }
5749
- if (changed) notify();
6294
+ if (changed) notify2();
5750
6295
  },
5751
6296
  sort(compareFn) {
5752
6297
  if (data2.length <= 1) return;
5753
6298
  data2.sort(compareFn);
5754
- notify();
6299
+ notify2();
5755
6300
  },
5756
6301
  reverse() {
5757
6302
  if (data2.length <= 1) return;
5758
6303
  data2.reverse();
5759
- notify();
6304
+ notify2();
5760
6305
  },
5761
6306
  filter(predicate) {
5762
6307
  const filtered = data2.filter(predicate);
5763
6308
  if (filtered.length === data2.length) return;
5764
6309
  data2 = filtered;
5765
- notify();
6310
+ notify2();
5766
6311
  },
5767
6312
  map(transform) {
5768
6313
  let changed = false;
@@ -5773,12 +6318,12 @@ function reactiveArray(initial = []) {
5773
6318
  changed = true;
5774
6319
  }
5775
6320
  }
5776
- if (changed) notify();
6321
+ if (changed) notify2();
5777
6322
  },
5778
6323
  clear() {
5779
6324
  if (data2.length === 0) return;
5780
6325
  data2 = [];
5781
- notify();
6326
+ notify2();
5782
6327
  }
5783
6328
  };
5784
6329
  return [get, actions];
@@ -6116,8 +6661,8 @@ function store(initialState) {
6116
6661
  return snapshot;
6117
6662
  };
6118
6663
  const setState = (patch) => {
6119
- const current = getSnapshot();
6120
- const nextState = typeof patch === "function" ? patch(current) : patch;
6664
+ const current2 = getSnapshot();
6665
+ const nextState = typeof patch === "function" ? patch(current2) : patch;
6121
6666
  batch(() => {
6122
6667
  Object.entries(nextState).forEach(([key, value]) => {
6123
6668
  if (Object.hasOwn(signals, key)) {
@@ -6154,18 +6699,18 @@ function store(initialState) {
6154
6699
  let prev;
6155
6700
  let first = true;
6156
6701
  return effect(() => {
6157
- const current = signals[key][0]();
6702
+ const current2 = signals[key][0]();
6158
6703
  if (first) {
6159
- prev = current;
6704
+ prev = current2;
6160
6705
  first = false;
6161
6706
  return;
6162
6707
  }
6163
- if (!Object.is(current, prev)) {
6708
+ if (!Object.is(current2, prev)) {
6164
6709
  const oldPrev = prev;
6165
- prev = current;
6710
+ prev = current2;
6166
6711
  suspendTracking2();
6167
6712
  try {
6168
- callback(current, oldPrev);
6713
+ callback(current2, oldPrev);
6169
6714
  } finally {
6170
6715
  resumeTracking2();
6171
6716
  }
@@ -6802,6 +7347,7 @@ var SUBPATH_ENTRIES = [
6802
7347
  "ui",
6803
7348
  "widgets",
6804
7349
  "ssr",
7350
+ "ssr/dom",
6805
7351
  "devtools",
6806
7352
  "performance",
6807
7353
  "ecosystem",
@@ -6876,7 +7422,7 @@ function generateImportMap(baseUrl) {
6876
7422
  [PACKAGE_NAME]: `${base2}/dist/index.js`
6877
7423
  };
6878
7424
  for (const entry of SUBPATH_ENTRIES) {
6879
- imports[`${PACKAGE_NAME}/${entry}`] = `${base2}/dist/${entry}.js`;
7425
+ imports[`${PACKAGE_NAME}/${entry}`] = `${base2}/dist/${entry.replace("/", "-")}.js`;
6880
7426
  }
6881
7427
  return {
6882
7428
  imports,
@@ -7692,38 +8238,38 @@ function parseTemplate2(strings) {
7692
8238
  pos++;
7693
8239
  const statics2 = [];
7694
8240
  const exprs2 = [];
7695
- let current2 = "";
8241
+ let current3 = "";
7696
8242
  while (pos < len && template2[pos] !== quote2) {
7697
8243
  const innerIdx = tryExprIdx();
7698
8244
  if (innerIdx >= 0) {
7699
- statics2.push(current2);
7700
- current2 = "";
8245
+ statics2.push(current3);
8246
+ current3 = "";
7701
8247
  exprs2.push(innerIdx);
7702
8248
  } else {
7703
- current2 += template2[pos++];
8249
+ current3 += template2[pos++];
7704
8250
  }
7705
8251
  }
7706
8252
  if (pos < len) pos++;
7707
- statics2.push(current2);
8253
+ statics2.push(current3);
7708
8254
  if (exprs2.length === 0) return { kind: "static", value: statics2[0] };
7709
8255
  return { kind: "mixed", statics: statics2, exprs: exprs2 };
7710
8256
  }
7711
8257
  const statics = [];
7712
8258
  const exprs = [];
7713
- let current = "";
8259
+ let current2 = "";
7714
8260
  while (pos < len) {
7715
8261
  const c = template2.charCodeAt(pos);
7716
8262
  if (c === 32 || c === 9 || c === 10 || c === 13 || c === 62) break;
7717
8263
  const innerIdx = tryExprIdx();
7718
8264
  if (innerIdx >= 0) {
7719
- statics.push(current);
7720
- current = "";
8265
+ statics.push(current2);
8266
+ current2 = "";
7721
8267
  exprs.push(innerIdx);
7722
8268
  } else {
7723
- current += template2[pos++];
8269
+ current2 += template2[pos++];
7724
8270
  }
7725
8271
  }
7726
- statics.push(current);
8272
+ statics.push(current2);
7727
8273
  if (exprs.length === 0) return { kind: "static", value: statics[0] };
7728
8274
  return { kind: "mixed", statics, exprs };
7729
8275
  }
@@ -9129,7 +9675,7 @@ function findEntryInHtml(html3) {
9129
9675
  const m = MODULE_SCRIPT.exec(html3) ?? MODULE_SCRIPT_SRC_FIRST.exec(html3);
9130
9676
  return m ? m[1] : null;
9131
9677
  }
9132
- function outputFileFor(route) {
9678
+ function outputFileFor(route, trailingSlash = "always") {
9133
9679
  if (route.output) {
9134
9680
  const output2 = route.output;
9135
9681
  if (/^[A-Za-z]:/.test(output2) || output2.includes("\0")) {
@@ -9159,6 +9705,10 @@ function outputFileFor(route) {
9159
9705
  }
9160
9706
  return decoded;
9161
9707
  });
9708
+ if (trailingSlash === "never" && segments.length > 0) {
9709
+ const last = segments.pop();
9710
+ return [...segments, `${last}.html`].join("/");
9711
+ }
9162
9712
  return [...segments, "index.html"].join("/");
9163
9713
  }
9164
9714
  function resolveInsideOutDir(path2, outDir, file) {
@@ -9174,17 +9724,36 @@ async function prerender(options, ctx) {
9174
9724
  const path2 = await import("path");
9175
9725
  const { pathToFileURL } = await import("url");
9176
9726
  const { createRequire } = await import("module");
9727
+ const requireFromRoot = createRequire(path2.join(ctx.root, "package.json"));
9728
+ if (options.server) {
9729
+ const { prerenderFromServerEntry: prerenderFromServerEntry2 } = await Promise.resolve().then(() => (init_prerenderServer(), prerenderServer_exports));
9730
+ return prerenderFromServerEntry2(options, ctx, {
9731
+ outputFileFor,
9732
+ resolveInsideOutDir,
9733
+ loadViteBuild: () => loadViteBuild(requireFromRoot, fs, path2, pathToFileURL),
9734
+ childEnv: PRERENDER_CHILD_ENV
9735
+ });
9736
+ }
9737
+ if (options.routes === void 0 || options.routes === "auto") {
9738
+ throw new Error(
9739
+ '[sibu prerender] `routes: "auto"` needs a server entry (`prerender.server`); list the routes instead.'
9740
+ );
9741
+ }
9177
9742
  const routes = options.routes.map((r) => typeof r === "string" ? { path: r } : r);
9743
+ if (options.notFound) routes.push({ path: options.notFound, output: "404.html" });
9178
9744
  if (routes.length === 0) return [];
9745
+ const trailingSlash = options.trailingSlash === "never" ? "never" : "always";
9746
+ const outDir = options.outDir ? path2.resolve(ctx.root, options.outDir) : ctx.outDir;
9179
9747
  const planned = routes.map((route) => {
9180
- const file = outputFileFor(route);
9181
- resolveInsideOutDir(path2, ctx.outDir, file);
9748
+ const file = outputFileFor(route, trailingSlash);
9749
+ resolveInsideOutDir(path2, outDir, file);
9182
9750
  return { route, file };
9183
9751
  });
9184
9752
  const origin = (options.origin ?? "http://localhost").replace(/\/+$/, "");
9185
9753
  const timeout = options.timeout ?? 15e3;
9186
9754
  const settle = options.settle ?? 50;
9187
- const templatePath = path2.join(ctx.outDir, "index.html");
9755
+ const keptShell = path2.join(ctx.outDir, typeof options.shell === "string" ? options.shell : "200.html");
9756
+ const templatePath = await fs.access(keptShell).then(() => keptShell).catch(() => path2.join(ctx.outDir, "index.html"));
9188
9757
  const template2 = await fs.readFile(templatePath, "utf8");
9189
9758
  let entry = options.entry;
9190
9759
  if (!entry) {
@@ -9202,7 +9771,7 @@ async function prerender(options, ctx) {
9202
9771
  } catch {
9203
9772
  throw new Error("[sibu prerender] needs jsdom to render routes. Install it: npm i -D jsdom");
9204
9773
  }
9205
- const tmpDir = path2.join(ctx.outDir, ".sibu-prerender");
9774
+ const tmpDir = path2.join(outDir, ".sibu-prerender");
9206
9775
  await fs.rm(tmpDir, { recursive: true, force: true });
9207
9776
  const viteBuild = await loadViteBuild(requireFromApp, fs, path2, pathToFileURL);
9208
9777
  const prev = process.env[PRERENDER_CHILD_ENV];
@@ -9232,10 +9801,12 @@ async function prerender(options, ctx) {
9232
9801
  await fs.writeFile(workerFile, WORKER_SOURCE, "utf8");
9233
9802
  const entryUrl = pathToFileURL(path2.join(tmpDir, "entry.mjs")).href;
9234
9803
  const jsdomUrl = pathToFileURL(jsdomPath).href;
9804
+ await fs.mkdir(outDir, { recursive: true });
9235
9805
  if (options.shell !== false) {
9236
- await fs.writeFile(path2.join(ctx.outDir, options.shell ?? "200.html"), template2, "utf8");
9806
+ await fs.writeFile(path2.join(outDir, options.shell ?? "200.html"), template2, "utf8");
9237
9807
  }
9238
9808
  const results = [];
9809
+ const indexable = [];
9239
9810
  const queue = [...planned];
9240
9811
  const concurrency = Math.max(1, options.concurrency ?? 4);
9241
9812
  try {
@@ -9253,16 +9824,35 @@ async function prerender(options, ctx) {
9253
9824
  timeout,
9254
9825
  settle
9255
9826
  });
9256
- const target = resolveInsideOutDir(path2, ctx.outDir, file);
9827
+ const target = resolveInsideOutDir(path2, outDir, file);
9257
9828
  await fs.mkdir(path2.dirname(target), { recursive: true });
9258
9829
  await fs.writeFile(target, html3, "utf8");
9259
9830
  results.push({ route: route.path, file, ms: Date.now() - started });
9831
+ if (file !== "404.html" && !/<meta\b[^>]*name=["']robots["'][^>]*content=["'][^"']*noindex/i.test(html3)) {
9832
+ const pathname = route.path.split(/[?#]/)[0];
9833
+ indexable.push(
9834
+ trailingSlash === "always" && pathname !== "/" ? `${pathname.replace(/\/+$/, "")}/` : pathname
9835
+ );
9836
+ }
9260
9837
  }
9261
9838
  })
9262
9839
  );
9263
9840
  } finally {
9264
9841
  await fs.rm(tmpDir, { recursive: true, force: true });
9265
9842
  }
9843
+ if (options.sitemap && indexable.length > 0) {
9844
+ const { buildSitemap: buildSitemap2 } = await Promise.resolve().then(() => (init_prerenderServer(), prerenderServer_exports));
9845
+ await fs.writeFile(path2.join(outDir, "sitemap.xml"), buildSitemap2(origin, indexable.sort()), "utf8");
9846
+ }
9847
+ if (options.robots) {
9848
+ const extra = typeof options.robots === "string" ? `${options.robots.trim()}
9849
+ ` : "";
9850
+ const sitemapLine = options.sitemap && indexable.length > 0 ? `Sitemap: ${origin}/sitemap.xml
9851
+ ` : "";
9852
+ await fs.writeFile(path2.join(outDir, "robots.txt"), `User-agent: *
9853
+ Allow: /
9854
+ ${extra}${sitemapLine}`, "utf8");
9855
+ }
9266
9856
  return results;
9267
9857
  }
9268
9858
  function esmEntryOf(entry) {
@@ -9297,6 +9887,7 @@ async function loadViteBuild(requireFromApp, fs, path2, pathToFileURL) {
9297
9887
  }
9298
9888
  return build;
9299
9889
  }
9890
+ var STARTUP_TIMEOUT = 6e4;
9300
9891
  async function renderInWorker(workerFile, input2) {
9301
9892
  const { Worker } = await import("worker_threads");
9302
9893
  return new Promise((resolve, reject) => {
@@ -9308,12 +9899,14 @@ async function renderInWorker(workerFile, input2) {
9308
9899
  fn();
9309
9900
  void worker.terminate();
9310
9901
  };
9311
- const timer = setTimeout(
9312
- () => finish(() => reject(new Error(`[sibu prerender] ${input2.route}: worker did not respond`))),
9313
- input2.timeout + 5e3
9314
- );
9315
- worker.once("message", (msg) => {
9902
+ const backstop = (ms, what) => setTimeout(() => finish(() => reject(new Error(`[sibu prerender] ${input2.route}: ${what}`))), ms);
9903
+ let timer = backstop(Math.max(STARTUP_TIMEOUT, input2.timeout + 5e3), "worker did not load the app");
9904
+ worker.on("message", (msg) => {
9316
9905
  clearTimeout(timer);
9906
+ if (msg.loaded) {
9907
+ timer = backstop(input2.timeout + 5e3, "worker did not respond");
9908
+ return;
9909
+ }
9317
9910
  if (typeof msg.html === "string") finish(() => resolve(msg.html));
9318
9911
  else finish(() => reject(new Error(`[sibu prerender] ${input2.route}: ${msg.error}`)));
9319
9912
  });
@@ -9426,6 +10019,8 @@ try {
9426
10019
  process.on("unhandledRejection", (reason) => errors.push(reason));
9427
10020
 
9428
10021
  await import(entryUrl);
10022
+ // The route's timeout starts now; the parent's backstop restarts with it.
10023
+ parentPort.postMessage({ loaded: true });
9429
10024
 
9430
10025
  const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
9431
10026
  const started = Date.now();
@@ -9640,7 +10235,7 @@ function parserSafeText(s2) {
9640
10235
  function escapeHtml(str) {
9641
10236
  return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
9642
10237
  }
9643
- function escapeAttr(str) {
10238
+ function escapeAttr2(str) {
9644
10239
  return str.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
9645
10240
  }
9646
10241
  function attributeHtml(name, lit) {
@@ -9656,7 +10251,7 @@ function attributeHtml(name, lit) {
9656
10251
  return on2 ? `${lower}=""` : "";
9657
10252
  }
9658
10253
  if (!parserSafeText(lit.value)) return null;
9659
- return `${lower}="${escapeAttr(lit.value)}"`;
10254
+ return `${lower}="${escapeAttr2(lit.value)}"`;
9660
10255
  }
9661
10256
  function parseCall(code2, list, i2) {
9662
10257
  const at = (k2) => list[k2];
@@ -9731,8 +10326,8 @@ function parseCall(code2, list, i2) {
9731
10326
  }
9732
10327
  function toHtml(tag, call) {
9733
10328
  const attrs = [];
9734
- if (call.cls !== null) attrs.push(`class="${escapeAttr(call.cls)}"`);
9735
- if (call.id !== null) attrs.push(`id="${escapeAttr(call.id)}"`);
10329
+ if (call.cls !== null) attrs.push(`class="${escapeAttr2(call.cls)}"`);
10330
+ if (call.id !== null) attrs.push(`id="${escapeAttr2(call.id)}"`);
9736
10331
  attrs.push(...call.attrs);
9737
10332
  const attrStr = attrs.length > 0 ? ` ${attrs.join(" ")}` : "";
9738
10333
  if (VOID_ELEMENTS3.has(tag)) {
@@ -9832,6 +10427,7 @@ function sibuVitePlugin(options = {}) {
9832
10427
  return {
9833
10428
  name: "sibu-vite-plugin",
9834
10429
  enforce: "pre",
10430
+ api: { prerender: prerenderOptions },
9835
10431
  config(_userConfig, env) {
9836
10432
  isDevMode = resolveDevMode(devMode, env);
9837
10433
  return {
@@ -9872,7 +10468,10 @@ function sibuVitePlugin(options = {}) {
9872
10468
  const target = prerenderTarget;
9873
10469
  prerenderTarget = null;
9874
10470
  const results = await prerender(prerenderOptions, target);
9875
- for (const r of results) console.log(`[sibu prerender] ${r.route} \u2192 ${r.file} (${r.ms} ms)`);
10471
+ for (const r of results) {
10472
+ const what = r.kind === "redirect" ? `redirect ${r.status} (redirects.json)` : r.kind === "skipped" ? `skipped (${r.status})` : r.file;
10473
+ console.log(`[sibu prerender] ${r.route} \u2192 ${what} (${r.ms} ms)`);
10474
+ }
9876
10475
  },
9877
10476
  transform(code2, id) {
9878
10477
  if (!matchesPattern(id, include) || matchesPattern(id, exclude)) {