sibujs 4.13.0 → 4.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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-7K7W4AA5.js → chunk-2TTRW2TE.js} +6 -6
  13. package/dist/{chunk-J6FW5TV6.js → chunk-3HEX6QJQ.js} +5 -3
  14. package/dist/chunk-3HZOSMPX.js +90 -0
  15. package/dist/{chunk-GNSRXK34.js → chunk-4FJSAUKB.js} +11 -3
  16. package/dist/{chunk-IV74C2YX.js → chunk-4TQDTWWN.js} +6 -5
  17. package/dist/{chunk-L3QHS4O4.js → chunk-7ILEHTVX.js} +1 -1
  18. package/dist/{chunk-CK5F2IR5.js → chunk-A454RAEK.js} +4 -4
  19. package/dist/{chunk-BRRRVLHQ.js → chunk-AE32RGWX.js} +10 -6
  20. package/dist/{chunk-EXXSN3BW.js → chunk-B3HOT4TK.js} +1 -1
  21. package/dist/{chunk-F4W7WYXY.js → chunk-BKZ5N6NK.js} +8 -6
  22. package/dist/{chunk-MNEITZ27.js → chunk-BNXRWZZV.js} +49 -8
  23. package/dist/chunk-BOYKGGVD.js +2015 -0
  24. package/dist/{chunk-G6366I2A.js → chunk-CCKUNHVU.js} +2 -2
  25. package/dist/{chunk-346BYZL2.js → chunk-CJ7KS3DF.js} +16 -10
  26. package/dist/chunk-CNPT6NKV.js +1859 -0
  27. package/dist/{chunk-ENFRHCMK.js → chunk-F67F77IA.js} +2 -2
  28. package/dist/{chunk-TUCPL2HB.js → chunk-GIJ33UJZ.js} +1 -1
  29. package/dist/chunk-GVAMIU6K.js +195 -0
  30. package/dist/{chunk-HDKDBC6V.js → chunk-HQIAL7F2.js} +8 -6
  31. package/dist/{chunk-ZE5LF44R.js → chunk-JTFQ7IL4.js} +8 -8
  32. package/dist/{chunk-AM6DZLBM.js → chunk-LJMX4SQ7.js} +2 -2
  33. package/dist/{chunk-HVVRXRGO.js → chunk-MIRKTVH4.js} +7 -14
  34. package/dist/{chunk-YJ57VR6Y.js → chunk-N7A6FKWW.js} +5 -3
  35. package/dist/chunk-NT3ZYB5S.js +167 -0
  36. package/dist/{chunk-Z3EZX6DZ.js → chunk-QS6GMWDD.js} +151 -86
  37. package/dist/chunk-R5U7XKVJ.js +16 -0
  38. package/dist/{chunk-3C56VNCK.js → chunk-RVR5NPFL.js} +3 -3
  39. package/dist/{chunk-F2UWOZ6J.js → chunk-RVWJGWCL.js} +2 -2
  40. package/dist/{chunk-NYNYSPK7.js → chunk-S37PZD6I.js} +30 -45
  41. package/dist/{chunk-QV5WEOFD.js → chunk-SVULV7O4.js} +119 -26
  42. package/dist/{chunk-C2FAO6VQ.js → chunk-TLS4NVIB.js} +5 -5
  43. package/dist/{chunk-M25RIH6Y.js → chunk-UUIKJJIK.js} +10 -8
  44. package/dist/chunk-UYTYNCMZ.js +199 -0
  45. package/dist/chunk-VM2U5LEL.js +2596 -0
  46. package/dist/{chunk-24L5RDPS.js → chunk-Y4LUM2XS.js} +8 -6
  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 +3520 -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 +5225 -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-JpGpHnpC.d.cts +841 -0
  84. package/dist/ssr-JpGpHnpC.d.ts +841 -0
  85. package/dist/{ssr-VBM6J5SJ.js → ssr-NECEU4X4.js} +15 -6
  86. package/dist/ssr-dom.cjs +1862 -0
  87. package/dist/ssr-dom.d.cts +341 -0
  88. package/dist/ssr-dom.d.ts +341 -0
  89. package/dist/ssr-dom.js +19 -0
  90. package/dist/ssr.cjs +7630 -2064
  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 +6543 -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.d.cts CHANGED
@@ -430,8 +430,62 @@ interface PrerenderRoute {
430
430
  output?: string;
431
431
  }
432
432
  interface PrerenderOptions {
433
- /** Routes to render. */
434
- routes: Array<string | PrerenderRoute>;
433
+ /**
434
+ * Routes to render. With `server`, defaults to `"auto"`: every route of the
435
+ * exported table without params, plus the paths `getStaticPaths()` lists
436
+ * for dynamic ones. A query variant (`"/cotizar?servicio=dtf"`) is rendered
437
+ * only when listed, and needs its own `output` file.
438
+ */
439
+ routes?: Array<string | PrerenderRoute> | "auto";
440
+ /**
441
+ * A SERVER ENTRY module (relative to the Vite root) exporting the app's
442
+ * `routes`, and optionally `head` (router head defaults), `routerOptions`
443
+ * and `app` (the shell around `Route()`). Routes are then rendered with
444
+ * `renderRoute()` on the built-in server DOM — the request-scoped router,
445
+ * async data, the collected head and transferred state — with no jsdom.
446
+ * Without it, the 4.13 mode runs the client entry in jsdom.
447
+ */
448
+ server?: string;
449
+ /** Output dir (relative to the Vite root). Default: Vite's `build.outDir`. */
450
+ outDir?: string;
451
+ /**
452
+ * Data at build time: a base URL that relative `fetch()` URLs (`/api/…`)
453
+ * resolve against (a running API), or a `fetch` implementation. A request
454
+ * that cannot reach it fails the build, naming the route. Default: `origin`.
455
+ * Server-entry mode only.
456
+ */
457
+ fetch?: string | typeof fetch;
458
+ /**
459
+ * `"always"` (default): `/cotizar` is written as `cotizar/index.html`, the
460
+ * sitemap lists `/cotizar/`. `"never"`: `cotizar.html`, listed as `/cotizar`
461
+ * (the static server must map `/cotizar` to `cotizar.html`).
462
+ */
463
+ trailingSlash?: "always" | "never";
464
+ /**
465
+ * URL rendered through the `*` route and written as `404.html` (when it
466
+ * answers 404). Default `"/404"` with `server`; off otherwise. `false` to skip.
467
+ */
468
+ notFound?: string | false;
469
+ /** Write `sitemap.xml` (pages not `noindex`, not `render: "client"`). Default `true` with `server`, `false` otherwise. */
470
+ sitemap?: boolean;
471
+ /** Write `robots.txt` pointing at the sitemap; a string adds rules (`"Disallow: /admin"`). Default `false`. */
472
+ robots?: boolean | string;
473
+ /** `"adopt"`: mark the output for `hydrate(App, root, { strategy: "adopt" })`. Server-entry mode only. */
474
+ hydration?: "adopt" | "replace";
475
+ /**
476
+ * CSP nonce attribute for the transfer script. Server-entry mode only.
477
+ *
478
+ * A static file carries the SAME value on every response, and a nonce that
479
+ * never changes protects nothing: injected markup can reuse it. Use it only
480
+ * as a PLACEHOLDER your server replaces with a fresh nonce per response
481
+ * (nginx `sub_filter`, a middleware), the same value its CSP header sends.
482
+ * Without such a server, allow the script by hash (`'sha256-…'`) instead.
483
+ */
484
+ nonce?: string;
485
+ /** Cap on each page's transferred state in bytes. Server-entry mode only. */
486
+ maxStateBytes?: number;
487
+ /** Id of the element the body renders into. Default `"app"`. Server-entry mode only. */
488
+ container?: string;
435
489
  /**
436
490
  * The client entry module, relative to the Vite root. Default: the first
437
491
  * `<script type="module" src>` in the source `index.html`.
@@ -471,6 +525,13 @@ interface PrerenderResult {
471
525
  route: string;
472
526
  file: string;
473
527
  ms: number;
528
+ /** HTTP status the route answered with (server-entry mode). */
529
+ status?: number;
530
+ /**
531
+ * `page` (rendered body), `shell` (head only), `redirect` (listed in
532
+ * redirects.json), `not-found` (404.html), `skipped` (a guard rejected it).
533
+ */
534
+ kind?: "page" | "shell" | "redirect" | "not-found" | "skipped";
474
535
  }
475
536
  /** Environment variable marking the nested SSR build, so it does not pre-render again. */
476
537
  declare const PRERENDER_CHILD_ENV = "SIBU_PRERENDER_CHILD";
@@ -484,7 +545,7 @@ declare function findEntryInHtml(html: string): string | null;
484
545
  * could escape it.
485
546
  * @internal
486
547
  */
487
- declare function outputFileFor(route: PrerenderRoute): string;
548
+ declare function outputFileFor(route: PrerenderRoute, trailingSlash?: "always" | "never"): string;
488
549
  /**
489
550
  * Absolute destination of `file` inside `outDir`, refusing anything that
490
551
  * resolves outside it. The last line of defence at the write boundary, so a
@@ -752,6 +813,10 @@ declare function sibuVitePlugin(options?: SibuVitePluginOptions): {
752
813
  file: string;
753
814
  modules: unknown[];
754
815
  }) => void;
816
+ /** For tooling (`sibujs prerender`): the options this plugin was created with. */
817
+ api: {
818
+ prerender: PrerenderOptions | undefined;
819
+ };
755
820
  };
756
821
  /**
757
822
  * Generate an optimized Vite configuration for SibuJS projects.
package/dist/build.d.ts CHANGED
@@ -430,8 +430,62 @@ interface PrerenderRoute {
430
430
  output?: string;
431
431
  }
432
432
  interface PrerenderOptions {
433
- /** Routes to render. */
434
- routes: Array<string | PrerenderRoute>;
433
+ /**
434
+ * Routes to render. With `server`, defaults to `"auto"`: every route of the
435
+ * exported table without params, plus the paths `getStaticPaths()` lists
436
+ * for dynamic ones. A query variant (`"/cotizar?servicio=dtf"`) is rendered
437
+ * only when listed, and needs its own `output` file.
438
+ */
439
+ routes?: Array<string | PrerenderRoute> | "auto";
440
+ /**
441
+ * A SERVER ENTRY module (relative to the Vite root) exporting the app's
442
+ * `routes`, and optionally `head` (router head defaults), `routerOptions`
443
+ * and `app` (the shell around `Route()`). Routes are then rendered with
444
+ * `renderRoute()` on the built-in server DOM — the request-scoped router,
445
+ * async data, the collected head and transferred state — with no jsdom.
446
+ * Without it, the 4.13 mode runs the client entry in jsdom.
447
+ */
448
+ server?: string;
449
+ /** Output dir (relative to the Vite root). Default: Vite's `build.outDir`. */
450
+ outDir?: string;
451
+ /**
452
+ * Data at build time: a base URL that relative `fetch()` URLs (`/api/…`)
453
+ * resolve against (a running API), or a `fetch` implementation. A request
454
+ * that cannot reach it fails the build, naming the route. Default: `origin`.
455
+ * Server-entry mode only.
456
+ */
457
+ fetch?: string | typeof fetch;
458
+ /**
459
+ * `"always"` (default): `/cotizar` is written as `cotizar/index.html`, the
460
+ * sitemap lists `/cotizar/`. `"never"`: `cotizar.html`, listed as `/cotizar`
461
+ * (the static server must map `/cotizar` to `cotizar.html`).
462
+ */
463
+ trailingSlash?: "always" | "never";
464
+ /**
465
+ * URL rendered through the `*` route and written as `404.html` (when it
466
+ * answers 404). Default `"/404"` with `server`; off otherwise. `false` to skip.
467
+ */
468
+ notFound?: string | false;
469
+ /** Write `sitemap.xml` (pages not `noindex`, not `render: "client"`). Default `true` with `server`, `false` otherwise. */
470
+ sitemap?: boolean;
471
+ /** Write `robots.txt` pointing at the sitemap; a string adds rules (`"Disallow: /admin"`). Default `false`. */
472
+ robots?: boolean | string;
473
+ /** `"adopt"`: mark the output for `hydrate(App, root, { strategy: "adopt" })`. Server-entry mode only. */
474
+ hydration?: "adopt" | "replace";
475
+ /**
476
+ * CSP nonce attribute for the transfer script. Server-entry mode only.
477
+ *
478
+ * A static file carries the SAME value on every response, and a nonce that
479
+ * never changes protects nothing: injected markup can reuse it. Use it only
480
+ * as a PLACEHOLDER your server replaces with a fresh nonce per response
481
+ * (nginx `sub_filter`, a middleware), the same value its CSP header sends.
482
+ * Without such a server, allow the script by hash (`'sha256-…'`) instead.
483
+ */
484
+ nonce?: string;
485
+ /** Cap on each page's transferred state in bytes. Server-entry mode only. */
486
+ maxStateBytes?: number;
487
+ /** Id of the element the body renders into. Default `"app"`. Server-entry mode only. */
488
+ container?: string;
435
489
  /**
436
490
  * The client entry module, relative to the Vite root. Default: the first
437
491
  * `<script type="module" src>` in the source `index.html`.
@@ -471,6 +525,13 @@ interface PrerenderResult {
471
525
  route: string;
472
526
  file: string;
473
527
  ms: number;
528
+ /** HTTP status the route answered with (server-entry mode). */
529
+ status?: number;
530
+ /**
531
+ * `page` (rendered body), `shell` (head only), `redirect` (listed in
532
+ * redirects.json), `not-found` (404.html), `skipped` (a guard rejected it).
533
+ */
534
+ kind?: "page" | "shell" | "redirect" | "not-found" | "skipped";
474
535
  }
475
536
  /** Environment variable marking the nested SSR build, so it does not pre-render again. */
476
537
  declare const PRERENDER_CHILD_ENV = "SIBU_PRERENDER_CHILD";
@@ -484,7 +545,7 @@ declare function findEntryInHtml(html: string): string | null;
484
545
  * could escape it.
485
546
  * @internal
486
547
  */
487
- declare function outputFileFor(route: PrerenderRoute): string;
548
+ declare function outputFileFor(route: PrerenderRoute, trailingSlash?: "always" | "never"): string;
488
549
  /**
489
550
  * Absolute destination of `file` inside `outDir`, refusing anything that
490
551
  * resolves outside it. The last line of defence at the write boundary, so a
@@ -752,6 +813,10 @@ declare function sibuVitePlugin(options?: SibuVitePluginOptions): {
752
813
  file: string;
753
814
  modules: unknown[];
754
815
  }) => void;
816
+ /** For tooling (`sibujs prerender`): the options this plugin was created with. */
817
+ api: {
818
+ prerender: PrerenderOptions | undefined;
819
+ };
755
820
  };
756
821
  /**
757
822
  * Generate an optimized Vite configuration for SibuJS projects.
package/dist/build.js CHANGED
@@ -1,28 +1,32 @@
1
1
  import {
2
2
  index_exports
3
- } from "./chunk-Z3EZX6DZ.js";
4
- import "./chunk-L3QHS4O4.js";
5
- import "./chunk-2F4PSJ5Z.js";
6
- import "./chunk-AM6DZLBM.js";
7
- import "./chunk-3C56VNCK.js";
8
- import "./chunk-G6366I2A.js";
3
+ } from "./chunk-QS6GMWDD.js";
4
+ import "./chunk-7ILEHTVX.js";
5
+ import "./chunk-BOYKGGVD.js";
6
+ import "./chunk-CNPT6NKV.js";
7
+ import "./chunk-LJMX4SQ7.js";
8
+ import "./chunk-RVR5NPFL.js";
9
+ import "./chunk-CCKUNHVU.js";
10
+ import "./chunk-3HZOSMPX.js";
9
11
  import "./chunk-62BDYGAG.js";
10
- import "./chunk-346BYZL2.js";
11
- import "./chunk-HDKDBC6V.js";
12
- import "./chunk-F2UWOZ6J.js";
13
- import "./chunk-YJ57VR6Y.js";
12
+ import "./chunk-CJ7KS3DF.js";
13
+ import "./chunk-HQIAL7F2.js";
14
+ import "./chunk-RVWJGWCL.js";
15
+ import "./chunk-N7A6FKWW.js";
14
16
  import "./chunk-7ZHH77QA.js";
15
- import "./chunk-BRRRVLHQ.js";
17
+ import "./chunk-AE32RGWX.js";
16
18
  import {
17
19
  isEventHandlerAttr,
18
20
  isHtmlContentAttribute,
19
21
  isUrlAttribute
20
- } from "./chunk-EXXSN3BW.js";
21
- import "./chunk-7XHATCIH.js";
22
+ } from "./chunk-B3HOT4TK.js";
23
+ import "./chunk-UYTYNCMZ.js";
22
24
  import "./chunk-5HZXGZ6T.js";
23
- import "./chunk-GNSRXK34.js";
24
- import "./chunk-HVVRXRGO.js";
25
- import "./chunk-NYNYSPK7.js";
25
+ import "./chunk-4FJSAUKB.js";
26
+ import "./chunk-MIRKTVH4.js";
27
+ import "./chunk-S37PZD6I.js";
28
+ import "./chunk-NT3ZYB5S.js";
29
+ import "./chunk-R5U7XKVJ.js";
26
30
 
27
31
  // src/build/analyzer.ts
28
32
  var moduleSizes = {
@@ -203,6 +207,7 @@ var SUBPATH_ENTRIES = [
203
207
  "ui",
204
208
  "widgets",
205
209
  "ssr",
210
+ "ssr/dom",
206
211
  "devtools",
207
212
  "performance",
208
213
  "ecosystem",
@@ -277,7 +282,7 @@ function generateImportMap(baseUrl) {
277
282
  [PACKAGE_NAME]: `${base}/dist/index.js`
278
283
  };
279
284
  for (const entry of SUBPATH_ENTRIES) {
280
- imports[`${PACKAGE_NAME}/${entry}`] = `${base}/dist/${entry}.js`;
285
+ imports[`${PACKAGE_NAME}/${entry}`] = `${base}/dist/${entry.replace("/", "-")}.js`;
281
286
  }
282
287
  return {
283
288
  imports,
@@ -2530,7 +2535,7 @@ function findEntryInHtml(html) {
2530
2535
  const m = MODULE_SCRIPT.exec(html) ?? MODULE_SCRIPT_SRC_FIRST.exec(html);
2531
2536
  return m ? m[1] : null;
2532
2537
  }
2533
- function outputFileFor(route) {
2538
+ function outputFileFor(route, trailingSlash = "always") {
2534
2539
  if (route.output) {
2535
2540
  const output = route.output;
2536
2541
  if (/^[A-Za-z]:/.test(output) || output.includes("\0")) {
@@ -2560,6 +2565,10 @@ function outputFileFor(route) {
2560
2565
  }
2561
2566
  return decoded;
2562
2567
  });
2568
+ if (trailingSlash === "never" && segments.length > 0) {
2569
+ const last = segments.pop();
2570
+ return [...segments, `${last}.html`].join("/");
2571
+ }
2563
2572
  return [...segments, "index.html"].join("/");
2564
2573
  }
2565
2574
  function resolveInsideOutDir(path, outDir, file) {
@@ -2575,17 +2584,36 @@ async function prerender(options, ctx) {
2575
2584
  const path = await import("path");
2576
2585
  const { pathToFileURL } = await import("url");
2577
2586
  const { createRequire } = await import("module");
2587
+ const requireFromRoot = createRequire(path.join(ctx.root, "package.json"));
2588
+ if (options.server) {
2589
+ const { prerenderFromServerEntry } = await import("./prerenderServer-MXJEVWA3.js");
2590
+ return prerenderFromServerEntry(options, ctx, {
2591
+ outputFileFor,
2592
+ resolveInsideOutDir,
2593
+ loadViteBuild: () => loadViteBuild(requireFromRoot, fs, path, pathToFileURL),
2594
+ childEnv: PRERENDER_CHILD_ENV
2595
+ });
2596
+ }
2597
+ if (options.routes === void 0 || options.routes === "auto") {
2598
+ throw new Error(
2599
+ '[sibu prerender] `routes: "auto"` needs a server entry (`prerender.server`); list the routes instead.'
2600
+ );
2601
+ }
2578
2602
  const routes = options.routes.map((r) => typeof r === "string" ? { path: r } : r);
2603
+ if (options.notFound) routes.push({ path: options.notFound, output: "404.html" });
2579
2604
  if (routes.length === 0) return [];
2605
+ const trailingSlash = options.trailingSlash === "never" ? "never" : "always";
2606
+ const outDir = options.outDir ? path.resolve(ctx.root, options.outDir) : ctx.outDir;
2580
2607
  const planned = routes.map((route) => {
2581
- const file = outputFileFor(route);
2582
- resolveInsideOutDir(path, ctx.outDir, file);
2608
+ const file = outputFileFor(route, trailingSlash);
2609
+ resolveInsideOutDir(path, outDir, file);
2583
2610
  return { route, file };
2584
2611
  });
2585
2612
  const origin = (options.origin ?? "http://localhost").replace(/\/+$/, "");
2586
2613
  const timeout = options.timeout ?? 15e3;
2587
2614
  const settle = options.settle ?? 50;
2588
- const templatePath = path.join(ctx.outDir, "index.html");
2615
+ const keptShell = path.join(ctx.outDir, typeof options.shell === "string" ? options.shell : "200.html");
2616
+ const templatePath = await fs.access(keptShell).then(() => keptShell).catch(() => path.join(ctx.outDir, "index.html"));
2589
2617
  const template = await fs.readFile(templatePath, "utf8");
2590
2618
  let entry = options.entry;
2591
2619
  if (!entry) {
@@ -2603,7 +2631,7 @@ async function prerender(options, ctx) {
2603
2631
  } catch {
2604
2632
  throw new Error("[sibu prerender] needs jsdom to render routes. Install it: npm i -D jsdom");
2605
2633
  }
2606
- const tmpDir = path.join(ctx.outDir, ".sibu-prerender");
2634
+ const tmpDir = path.join(outDir, ".sibu-prerender");
2607
2635
  await fs.rm(tmpDir, { recursive: true, force: true });
2608
2636
  const viteBuild = await loadViteBuild(requireFromApp, fs, path, pathToFileURL);
2609
2637
  const prev = process.env[PRERENDER_CHILD_ENV];
@@ -2633,10 +2661,12 @@ async function prerender(options, ctx) {
2633
2661
  await fs.writeFile(workerFile, WORKER_SOURCE, "utf8");
2634
2662
  const entryUrl = pathToFileURL(path.join(tmpDir, "entry.mjs")).href;
2635
2663
  const jsdomUrl = pathToFileURL(jsdomPath).href;
2664
+ await fs.mkdir(outDir, { recursive: true });
2636
2665
  if (options.shell !== false) {
2637
- await fs.writeFile(path.join(ctx.outDir, options.shell ?? "200.html"), template, "utf8");
2666
+ await fs.writeFile(path.join(outDir, options.shell ?? "200.html"), template, "utf8");
2638
2667
  }
2639
2668
  const results = [];
2669
+ const indexable = [];
2640
2670
  const queue = [...planned];
2641
2671
  const concurrency = Math.max(1, options.concurrency ?? 4);
2642
2672
  try {
@@ -2654,16 +2684,35 @@ async function prerender(options, ctx) {
2654
2684
  timeout,
2655
2685
  settle
2656
2686
  });
2657
- const target = resolveInsideOutDir(path, ctx.outDir, file);
2687
+ const target = resolveInsideOutDir(path, outDir, file);
2658
2688
  await fs.mkdir(path.dirname(target), { recursive: true });
2659
2689
  await fs.writeFile(target, html, "utf8");
2660
2690
  results.push({ route: route.path, file, ms: Date.now() - started });
2691
+ if (file !== "404.html" && !/<meta\b[^>]*name=["']robots["'][^>]*content=["'][^"']*noindex/i.test(html)) {
2692
+ const pathname = route.path.split(/[?#]/)[0];
2693
+ indexable.push(
2694
+ trailingSlash === "always" && pathname !== "/" ? `${pathname.replace(/\/+$/, "")}/` : pathname
2695
+ );
2696
+ }
2661
2697
  }
2662
2698
  })
2663
2699
  );
2664
2700
  } finally {
2665
2701
  await fs.rm(tmpDir, { recursive: true, force: true });
2666
2702
  }
2703
+ if (options.sitemap && indexable.length > 0) {
2704
+ const { buildSitemap } = await import("./prerenderServer-MXJEVWA3.js");
2705
+ await fs.writeFile(path.join(outDir, "sitemap.xml"), buildSitemap(origin, indexable.sort()), "utf8");
2706
+ }
2707
+ if (options.robots) {
2708
+ const extra = typeof options.robots === "string" ? `${options.robots.trim()}
2709
+ ` : "";
2710
+ const sitemapLine = options.sitemap && indexable.length > 0 ? `Sitemap: ${origin}/sitemap.xml
2711
+ ` : "";
2712
+ await fs.writeFile(path.join(outDir, "robots.txt"), `User-agent: *
2713
+ Allow: /
2714
+ ${extra}${sitemapLine}`, "utf8");
2715
+ }
2667
2716
  return results;
2668
2717
  }
2669
2718
  function esmEntryOf(entry) {
@@ -2698,6 +2747,7 @@ async function loadViteBuild(requireFromApp, fs, path, pathToFileURL) {
2698
2747
  }
2699
2748
  return build;
2700
2749
  }
2750
+ var STARTUP_TIMEOUT = 6e4;
2701
2751
  async function renderInWorker(workerFile, input) {
2702
2752
  const { Worker } = await import("worker_threads");
2703
2753
  return new Promise((resolve, reject) => {
@@ -2709,12 +2759,14 @@ async function renderInWorker(workerFile, input) {
2709
2759
  fn();
2710
2760
  void worker.terminate();
2711
2761
  };
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) => {
2762
+ const backstop = (ms, what) => setTimeout(() => finish(() => reject(new Error(`[sibu prerender] ${input.route}: ${what}`))), ms);
2763
+ let timer = backstop(Math.max(STARTUP_TIMEOUT, input.timeout + 5e3), "worker did not load the app");
2764
+ worker.on("message", (msg) => {
2717
2765
  clearTimeout(timer);
2766
+ if (msg.loaded) {
2767
+ timer = backstop(input.timeout + 5e3, "worker did not respond");
2768
+ return;
2769
+ }
2718
2770
  if (typeof msg.html === "string") finish(() => resolve(msg.html));
2719
2771
  else finish(() => reject(new Error(`[sibu prerender] ${input.route}: ${msg.error}`)));
2720
2772
  });
@@ -2827,6 +2879,8 @@ try {
2827
2879
  process.on("unhandledRejection", (reason) => errors.push(reason));
2828
2880
 
2829
2881
  await import(entryUrl);
2882
+ // The route's timeout starts now; the parent's backstop restarts with it.
2883
+ parentPort.postMessage({ loaded: true });
2830
2884
 
2831
2885
  const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
2832
2886
  const started = Date.now();
@@ -3233,6 +3287,7 @@ function sibuVitePlugin(options = {}) {
3233
3287
  return {
3234
3288
  name: "sibu-vite-plugin",
3235
3289
  enforce: "pre",
3290
+ api: { prerender: prerenderOptions },
3236
3291
  config(_userConfig, env) {
3237
3292
  isDevMode = resolveDevMode(devMode, env);
3238
3293
  return {
@@ -3273,7 +3328,10 @@ function sibuVitePlugin(options = {}) {
3273
3328
  const target = prerenderTarget;
3274
3329
  prerenderTarget = null;
3275
3330
  const results = await prerender(prerenderOptions, target);
3276
- for (const r of results) console.log(`[sibu prerender] ${r.route} \u2192 ${r.file} (${r.ms} ms)`);
3331
+ for (const r of results) {
3332
+ const what = r.kind === "redirect" ? `redirect ${r.status} (redirects.json)` : r.kind === "skipped" ? `skipped (${r.status})` : r.file;
3333
+ console.log(`[sibu prerender] ${r.route} \u2192 ${what} (${r.ms} ms)`);
3334
+ }
3277
3335
  },
3278
3336
  transform(code, id) {
3279
3337
  if (!matchesPattern(id, include) || matchesPattern(id, exclude)) {