dinou 6.1.0 → 7.0.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 (135) hide show
  1. package/CHANGELOG.md +99 -0
  2. package/README.md +9461 -1080
  3. package/cli.js +392 -169
  4. package/dinou/adapters/bun.js +62 -0
  5. package/dinou/adapters/cloudflare.js +65 -0
  6. package/dinou/adapters/deno.js +48 -0
  7. package/dinou/adapters/netlify.js +85 -0
  8. package/dinou/adapters/node.mjs +31 -0
  9. package/dinou/bun/build.mjs +1077 -0
  10. package/dinou/cloudflare/build.mjs +1121 -0
  11. package/dinou/cloudflare/shims/child_process.js +14 -0
  12. package/dinou/cloudflare/shims/empty.js +2 -0
  13. package/dinou/cloudflare/shims/fs.js +99 -0
  14. package/dinou/cloudflare/shims/os.js +29 -0
  15. package/dinou/config.js +1 -0
  16. package/dinou/config.mjs +2 -0
  17. package/dinou/constants.js +29 -4
  18. package/dinou/core/babel-esm-loader.js +66 -37
  19. package/dinou/core/bailout-proxy.js +61 -0
  20. package/dinou/core/build-static-pages.js +309 -157
  21. package/dinou/core/cache-revalidate.js +471 -144
  22. package/dinou/core/cache-slot.js +387 -0
  23. package/dinou/core/client-redirect.jsx +12 -7
  24. package/dinou/core/client-webpack.jsx +457 -82
  25. package/dinou/core/client.jsx +452 -82
  26. package/dinou/core/config.js +11 -0
  27. package/dinou/core/dinou-paths.js +52 -0
  28. package/dinou/core/edge-ssr.js +122 -0
  29. package/dinou/core/export-static.mjs +64 -0
  30. package/dinou/core/generate-route-types.js +252 -0
  31. package/dinou/core/generate-static-page.js +55 -94
  32. package/dinou/core/generate-static-rsc.js +47 -15
  33. package/dinou/core/generate-static.js +212 -10
  34. package/dinou/core/generating-isg.js +5 -0
  35. package/dinou/core/get-asset-from-manifest.js +9 -3
  36. package/dinou/core/get-error-jsx.js +221 -41
  37. package/dinou/core/get-file-path-and-dynamic-params.js +9 -13
  38. package/dinou/core/get-jsx.js +172 -95
  39. package/dinou/core/handler.js +2296 -0
  40. package/dinou/core/http-adapter.js +115 -0
  41. package/dinou/core/import-module.js +83 -53
  42. package/dinou/core/layout-functions.js +181 -0
  43. package/dinou/core/link.jsx +18 -6
  44. package/dinou/core/load-dinou-config.js +60 -0
  45. package/dinou/core/manifest-provider.js +261 -0
  46. package/dinou/core/navigation-utils.js +4 -0
  47. package/dinou/core/navigation.js +50 -39
  48. package/dinou/core/parse-exports.js +207 -2
  49. package/dinou/core/ppr-context.js +105 -0
  50. package/dinou/core/ppr-runtime.js +263 -0
  51. package/dinou/core/redirect.jsx +20 -19
  52. package/dinou/core/render-jsx-to-client-jsx.js +133 -15
  53. package/dinou/core/request-context.js +47 -13
  54. package/dinou/core/resolve-module-ppr.js +32 -0
  55. package/dinou/core/revalidating.js +28 -9
  56. package/dinou/core/route-generator.js +107 -0
  57. package/dinou/core/rsc-renderer.js +90 -0
  58. package/dinou/core/run-ssg.js +19 -0
  59. package/dinou/core/scan-dependency-components.js +218 -0
  60. package/dinou/core/server-function-proxy-webpack.js +3 -0
  61. package/dinou/core/server-function-proxy.js +3 -0
  62. package/dinou/core/slot.js +302 -0
  63. package/dinou/core/status-manifest.js +41 -1
  64. package/dinou/core/storage-adapter.js +522 -0
  65. package/dinou/core/url-resolver.js +16 -16
  66. package/dinou/core/vfs.js +139 -68
  67. package/dinou/deno/build.mjs +1112 -0
  68. package/dinou/deno/seed-kv.js +115 -0
  69. package/dinou/esbuild/build.mjs +51 -1
  70. package/dinou/esbuild/dev.mjs +290 -224
  71. package/dinou/esbuild/helpers-esbuild/get-config-esbuild-prod.mjs +3 -2
  72. package/dinou/esbuild/helpers-esbuild/get-config-esbuild.mjs +104 -16
  73. package/dinou/esbuild/helpers-esbuild/get-esbuild-entries.mjs +31 -19
  74. package/dinou/esbuild/helpers-esbuild/strip-namespace.mjs +9 -0
  75. package/dinou/esbuild/helpers-esbuild/swc-disk-cache.mjs +225 -0
  76. package/dinou/esbuild/helpers-esbuild/update-manifest-for-module.mjs +7 -0
  77. package/dinou/esbuild/helpers-esbuild/write.mjs +111 -2
  78. package/dinou/esbuild/plugins-esbuild/assets-plugin.mjs +236 -161
  79. package/dinou/esbuild/plugins-esbuild/css-processor-plugin.mjs +101 -5
  80. package/dinou/esbuild/plugins-esbuild/react-client-manifest-plugin.mjs +63 -24
  81. package/dinou/esbuild/plugins-esbuild/server-functions-plugin.mjs +177 -56
  82. package/dinou/esbuild/plugins-esbuild/skip-missing-entry-points-plugin.mjs +3 -0
  83. package/dinou/esbuild/plugins-esbuild/stable-chunk-names-and-maps-plugin.mjs +182 -137
  84. package/dinou/esbuild/plugins-postcss/postcss-extract-plugin.js +38 -11
  85. package/dinou/esbuild/react-refresh/esm-hmr/client.mjs +55 -8
  86. package/dinou/esbuild/react-refresh/esm-hmr/server.js +1 -1
  87. package/dinou/esbuild/react-refresh/esm-hmr-plugin.mjs +238 -101
  88. package/dinou/esbuild/react-refresh/is-react-refresh-boundary.mjs +18 -4
  89. package/dinou/index.d.ts +315 -11
  90. package/dinou/index.js +12 -6
  91. package/dinou/index.mjs +9 -0
  92. package/dinou/node/build.mjs +195 -0
  93. package/dinou/node/bundle-dual-engine.mjs +1012 -0
  94. package/dinou/node/dev.mjs +2589 -0
  95. package/dinou/node/port-selector.mjs +164 -0
  96. package/dinou/node/terminal-status.mjs +345 -0
  97. package/dinou/package.json +17 -14
  98. package/dinou/rollup/react-refresh/esm-hmr/client.js +66 -10
  99. package/dinou/rollup/react-refresh/esm-hmr/server.js +1 -1
  100. package/dinou/rollup/react-refresh/is-react-refresh-boundary.js +24 -5
  101. package/dinou/rollup/react-refresh/react-refresh-runtime.js +13 -6
  102. package/dinou/rollup/react-refresh/react-refresh-wrap-modules.js +56 -11
  103. package/dinou/rollup/react-refresh/rollup-plugin-esm-hmr.js +176 -48
  104. package/dinou/rollup/rollup-plugins/dinou-asset-plugin.js +9 -5
  105. package/dinou/rollup/rollup-plugins/rollup-plugin-memory.js +66 -0
  106. package/dinou/rollup/rollup-plugins/rollup-plugin-react-client-manifest.js +468 -123
  107. package/dinou/rollup/rollup-plugins/rollup-plugin-server-functions.js +44 -12
  108. package/dinou/rollup/rollup-plugins/rollup-plugin-swc.js +100 -0
  109. package/dinou/rollup/rollup.config.js +155 -43
  110. package/dinou/server.d.ts +114 -12
  111. package/dinou/server.js +35 -10
  112. package/dinou/server.mjs +30 -9
  113. package/dinou/webpack/helpers/get-webpack-entries.js +14 -19
  114. package/dinou/webpack/loaders/server-functions-loader.js +22 -1
  115. package/dinou/webpack/loaders/swc-loader.js +76 -0
  116. package/dinou/webpack/plugins/manifest-generator-plugin.js +1 -0
  117. package/dinou/webpack/plugins/server-functions-plugin.js +75 -19
  118. package/dinou/webpack/plugins/webpack-memory-plugin.js +66 -0
  119. package/dinou/webpack/webpack.config.js +227 -29
  120. package/eject.js +173 -94
  121. package/package.json +110 -107
  122. package/dinou/core/asset-require-hook.js +0 -58
  123. package/dinou/core/client-error-webpack.jsx +0 -432
  124. package/dinou/core/client-error.jsx +0 -432
  125. package/dinou/core/concurrency-manager.js +0 -48
  126. package/dinou/core/context-proxy.js +0 -68
  127. package/dinou/core/generate-static-pages.js +0 -130
  128. package/dinou/core/generate-static-rscs.js +0 -151
  129. package/dinou/core/get-ssg-metadata.js +0 -31
  130. package/dinou/core/register-loader.mjs +0 -11
  131. package/dinou/core/register-paths.js +0 -27
  132. package/dinou/core/render-app-to-html.js +0 -430
  133. package/dinou/core/render-html.js +0 -407
  134. package/dinou/core/server.js +0 -1542
  135. package/dinou/esbuild/plugins-esbuild/write-metafile-plugin.mjs +0 -16
@@ -7,9 +7,60 @@ const {
7
7
  } = require("./get-file-path-and-dynamic-params");
8
8
  const importModule = require("./import-module");
9
9
  const { requestStorage } = require("./request-context.js");
10
+ const { getLayoutProps, resolveLayoutFunctionsConfig } = require("./layout-functions");
11
+ const { runWithPprContext, getRegisteredHoles } = require("./ppr-context.js");
12
+ const { resolveModulePpr } = require("./resolve-module-ppr");
13
+ const { getDinouConfig } = require("./load-dinou-config");
14
+
15
+ async function resolveBuildPprForRoute(segments, pagePath, pageFunctionsPath, srcFolder) {
16
+ const globalCfg = await getDinouConfig();
17
+ let inheritedPpr = Boolean(globalCfg?.ppr);
18
+ try {
19
+ const parentLayouts = getFilePathAndDynamicParams(
20
+ segments || [],
21
+ {},
22
+ srcFolder,
23
+ "layout",
24
+ true,
25
+ false,
26
+ undefined,
27
+ 0,
28
+ {},
29
+ true
30
+ );
31
+ if (parentLayouts && Array.isArray(parentLayouts)) {
32
+ for (const [lPath, lParams] of parentLayouts) {
33
+ const lConfig = await resolveLayoutFunctionsConfig(lPath, lParams);
34
+ if (lConfig && lConfig.ppr !== null && lConfig.ppr !== undefined) {
35
+ inheritedPpr = Boolean(lConfig.ppr);
36
+ }
37
+ }
38
+ }
39
+ } catch (e) {}
40
+
41
+ let explicitPagePpr = null;
42
+ if (pageFunctionsPath) {
43
+ try {
44
+ const module = await importModule(pageFunctionsPath);
45
+ explicitPagePpr = await resolveModulePpr(module);
46
+ } catch (e) {}
47
+ }
48
+ if (explicitPagePpr === null && typeof globalThis !== "undefined" && globalThis.__DINOU_ROUTE_METADATA__) {
49
+ const normKey = pageFunctionsPath ? pageFunctionsPath.replace(/\\/g, "/") : "";
50
+ for (const [k, meta] of Object.entries(globalThis.__DINOU_ROUTE_METADATA__)) {
51
+ if (meta.ppr !== undefined && (normKey === k || normKey.endsWith("/" + k) || normKey.endsWith(k))) {
52
+ explicitPagePpr = Boolean(meta.ppr);
53
+ break;
54
+ }
55
+ }
56
+ }
57
+
58
+ return explicitPagePpr !== null ? explicitPagePpr : inheritedPpr;
59
+ }
10
60
 
11
61
  const staticRoutes = new Set();
12
62
  const staticMetadata = new Map();
63
+ const staticLayouts = new Map();
13
64
 
14
65
  function safeDecode(val) {
15
66
  try {
@@ -19,65 +70,21 @@ function safeDecode(val) {
19
70
  }
20
71
  }
21
72
 
22
- /**
23
- * Creates a spy that detects access to properties and marks the page as dynamic.
24
- * @param {Object} target - The real object (cookies, headers, query...)
25
- * @param {string} label - Name for the log (e.g., "Headers", "Cookies")
26
- * @param {Function} onBailout - Callback to execute when access is detected
27
- */
28
- function createBailoutProxy(target, label, onBailout) {
29
- // If there is no target, use an empty object to avoid crashes,
30
- // but ideally pass whatever you have.
31
- const safeTarget = target || {};
32
-
33
- return new Proxy(safeTarget, {
34
- get(t, prop, receiver) {
35
- // Ignore internal Node/Console symbols
36
- if (
37
- typeof prop === "symbol" ||
38
- prop === "inspect" ||
39
- prop === "valueOf" ||
40
- prop === "toString" // Sometimes useful to ignore
41
- ) {
42
- return Reflect.get(t, prop, receiver);
43
- }
44
-
45
- // 🚨 ALARM: Access detected
46
- console.log(
47
- `[StaticBailout] Access to ${label} detected: "${String(prop)}".`,
48
- );
49
-
50
- // Execute logic to mark as dynamic
51
- onBailout();
52
-
53
- // IMPORTANT: Return the real value of the original object
54
- return Reflect.get(t, prop, receiver);
55
- },
56
-
57
- ownKeys(t) {
58
- console.log(`[StaticBailout] Iteration of ${label} detected.`);
59
- onBailout();
60
- return Reflect.ownKeys(t);
61
- },
62
-
63
- // Optional: Detect if they try to ask "prop in headers"
64
- has(t, prop) {
65
- console.log(
66
- `[StaticBailout] Existence check (IN) in ${label}: "${String(prop)}".`,
67
- );
68
- onBailout();
69
- return Reflect.has(t, prop);
70
- },
71
- });
73
+ async function isDynamicRoute(revalidate) {
74
+ if (revalidate === undefined || revalidate === false) return false;
75
+ const val = typeof revalidate === "function" ? await revalidate() : revalidate;
76
+ return typeof val === "number" && val < 1;
72
77
  }
73
78
 
79
+ const { createBailoutProxy } = require("./bailout-proxy.js");
80
+
74
81
  /**
75
82
  * Compiles all static pages in the application.
76
83
  * Crawls the "src/" folder to collect all routes, resolves dynamic path segments,
77
84
  * mock-renders them inside a server context, and serializes successful static renders
78
85
  * into RSC JSON payloads.
79
86
  */
80
- async function buildStaticPages() {
87
+ async function buildStaticPages(onProgress = null) {
81
88
  const srcFolder = path.resolve(process.cwd(), "src");
82
89
 
83
90
  /**
@@ -95,8 +102,32 @@ async function buildStaticPages() {
95
102
  const entries = readdirSync(currentPath, { withFileTypes: true });
96
103
  const pages = [];
97
104
 
105
+ // 🛡️ Descubrir layout en esta carpeta si existe
106
+ const layoutExtensions = [".tsx", ".jsx", ".ts", ".js"];
107
+ for (const ext of layoutExtensions) {
108
+ const candidateLayout = path.join(currentPath, `layout${ext}`);
109
+ if (existsSync(candidateLayout)) {
110
+ const routePath = segments.length ? "/" + segments.join("/") : "/";
111
+ const { resolveLayoutFunctionsConfig } = require("./layout-functions");
112
+ const layoutConfig = await resolveLayoutFunctionsConfig(candidateLayout, params);
113
+ if (!layoutConfig.isDynamic) {
114
+ staticLayouts.set(routePath, {
115
+ folderPath: currentPath,
116
+ layoutPath: candidateLayout,
117
+ routePath,
118
+ segments,
119
+ params,
120
+ });
121
+ }
122
+ break;
123
+ }
124
+ }
125
+
98
126
  for (const entry of entries) {
99
127
  if (entry.isDirectory()) {
128
+ if (entry.name.startsWith("_")) {
129
+ continue;
130
+ }
100
131
  if (entry.name.startsWith("(") && entry.name.endsWith(")")) {
101
132
  pages.push(
102
133
  ...(await collectPages(
@@ -134,22 +165,26 @@ async function buildStaticPages() {
134
165
  segments.length,
135
166
  );
136
167
 
137
- let dynamic, getStaticPaths;
168
+ let revalidate, getStaticPaths;
138
169
  if (pageFunctionsPath) {
139
170
  const module = await importModule(pageFunctionsPath);
140
171
  getStaticPaths = module.getStaticPaths;
141
- dynamic = module.dynamic;
172
+ revalidate = module.revalidate;
142
173
  }
143
174
  const isLocalPage =
144
175
  pagePath && path.dirname(pagePath) === dynamicPath;
145
- if (isLocalPage && !dynamic?.()) {
146
- console.log(
147
- `Found optional catch-all route: ${segments.join("/") ?? ""
148
- }/[[...${paramName}]]`,
149
- );
176
+ if (isLocalPage && !(await isDynamicRoute(revalidate))) {
177
+ if (process.env.DINOU_DEBUG) {
178
+ console.log(
179
+ `Found optional catch-all route: ${segments.join("/") ?? ""
180
+ }/[[...${paramName}]]`,
181
+ );
182
+ }
150
183
  try {
151
184
  if (getStaticPaths) {
152
- const paths = await getStaticPaths();
185
+ const paths = await (typeof getStaticPaths === "function"
186
+ ? getStaticPaths()
187
+ : getStaticPaths);
153
188
  for (const pathItem of paths) {
154
189
  // 1. Preparation of Structure and Extraction
155
190
  const currentStructure = [...dynamicStructure, paramName];
@@ -275,22 +310,26 @@ async function buildStaticPages() {
275
310
  segments.length,
276
311
  );
277
312
 
278
- let dynamic, getStaticPaths;
313
+ let revalidate, getStaticPaths;
279
314
  if (pageFunctionsPath) {
280
315
  const module = await importModule(pageFunctionsPath);
281
316
  getStaticPaths = module.getStaticPaths;
282
- dynamic = module.dynamic;
317
+ revalidate = module.revalidate;
283
318
  }
284
319
  const isLocalPage =
285
320
  pagePath && path.dirname(pagePath) === dynamicPath;
286
- if (isLocalPage && !dynamic?.()) {
287
- console.log(
288
- `Found catch-all route: ${segments.join("/") ?? ""
289
- }/[...${paramName}]`,
290
- );
321
+ if (isLocalPage && !(await isDynamicRoute(revalidate))) {
322
+ if (process.env.DINOU_DEBUG) {
323
+ console.log(
324
+ `Found catch-all route: ${segments.join("/") ?? ""
325
+ }/[...${paramName}]`,
326
+ );
327
+ }
291
328
  try {
292
329
  if (getStaticPaths) {
293
- const paths = await getStaticPaths();
330
+ const paths = await (typeof getStaticPaths === "function"
331
+ ? getStaticPaths()
332
+ : getStaticPaths);
294
333
  for (const pathItem of paths) {
295
334
  const currentStructure = [...dynamicStructure, paramName];
296
335
  const isObject =
@@ -379,22 +418,26 @@ async function buildStaticPages() {
379
418
  segments.length,
380
419
  );
381
420
 
382
- let dynamic, getStaticPaths;
421
+ let revalidate, getStaticPaths;
383
422
  if (pageFunctionsPath) {
384
423
  const module = await importModule(pageFunctionsPath);
385
424
  getStaticPaths = module.getStaticPaths;
386
- dynamic = module.dynamic;
425
+ revalidate = module.revalidate;
387
426
  }
388
427
  const isLocalPage =
389
428
  pagePath && path.dirname(pagePath) === dynamicPath;
390
- if (isLocalPage && !dynamic?.()) {
391
- console.log(
392
- `Found optional dynamic route: ${segments.join("/") ?? ""
393
- }/[[${paramName}]]`,
394
- );
429
+ if (isLocalPage && !(await isDynamicRoute(revalidate))) {
430
+ if (process.env.DINOU_DEBUG) {
431
+ console.log(
432
+ `Found optional dynamic route: ${segments.join("/") ?? ""
433
+ }/[[${paramName}]]`,
434
+ );
435
+ }
395
436
  try {
396
437
  if (getStaticPaths) {
397
- const paths = await getStaticPaths();
438
+ const paths = await (typeof getStaticPaths === "function"
439
+ ? getStaticPaths()
440
+ : getStaticPaths);
398
441
  for (const pathItem of paths) {
399
442
  // 1. Preparation of Structure and Extraction
400
443
  const currentStructure = [...dynamicStructure, paramName];
@@ -502,20 +545,50 @@ async function buildStaticPages() {
502
545
  );
503
546
  let dynamic;
504
547
  let getStaticPaths;
548
+ let revalidate;
505
549
  if (pageFunctionsPath) {
506
550
  const module = await importModule(pageFunctionsPath);
507
551
  getStaticPaths = module.getStaticPaths;
508
- dynamic = module.dynamic;
552
+ revalidate = module.revalidate;
553
+ }
554
+ const { getLayoutFunctionsPath } = require("./layout-functions");
555
+ const lfPath = getLayoutFunctionsPath(dynamicPath);
556
+ if (lfPath) {
557
+ const lmod = await importModule(lfPath);
558
+ const lmodGetStaticPaths = lmod.getStaticPaths || lmod.default?.getStaticPaths;
559
+ const lmodRevalidate = lmod.revalidate || lmod.default?.revalidate;
560
+ if (!getStaticPaths && lmodGetStaticPaths) {
561
+ getStaticPaths = lmodGetStaticPaths;
562
+ }
563
+ if (revalidate === undefined && lmodRevalidate !== undefined) {
564
+ revalidate = lmodRevalidate;
565
+ }
509
566
  }
510
567
  const isLocalPage =
511
568
  pagePath && path.dirname(pagePath) === dynamicPath;
512
- if (isLocalPage && !dynamic?.()) {
513
- console.log(
514
- `Found dynamic route: ${segments.join("/") ?? ""}/[${paramName}]`,
515
- );
569
+ const [layoutPath] = getFilePathAndDynamicParams(
570
+ segments,
571
+ {},
572
+ dynamicPath,
573
+ "layout",
574
+ true,
575
+ true,
576
+ undefined,
577
+ segments.length,
578
+ );
579
+ const hasLocalLayout =
580
+ layoutPath && path.dirname(layoutPath) === dynamicPath;
581
+ if ((isLocalPage || hasLocalLayout) && !(await isDynamicRoute(revalidate))) {
582
+ if (process.env.DINOU_DEBUG) {
583
+ console.log(
584
+ `Found dynamic route: ${segments.join("/") ?? ""}/[${paramName}]`,
585
+ );
586
+ }
516
587
  try {
517
588
  if (getStaticPaths) {
518
- const paths = await getStaticPaths();
589
+ const paths = await (typeof getStaticPaths === "function"
590
+ ? getStaticPaths()
591
+ : getStaticPaths);
519
592
  for (const pathItem of paths) {
520
593
  const currentStructure = [...dynamicStructure, paramName];
521
594
  const isObject =
@@ -598,19 +671,21 @@ async function buildStaticPages() {
598
671
  undefined,
599
672
  segments.length,
600
673
  );
601
- let dynamic;
674
+ let revalidate;
602
675
  let getStaticPaths;
603
676
  if (pageFunctionsPath) {
604
677
  const module = await importModule(pageFunctionsPath);
605
678
  getStaticPaths = module.getStaticPaths;
606
- dynamic = module.dynamic;
679
+ revalidate = module.revalidate;
607
680
  }
608
681
  const isLocalPage =
609
682
  pagePath && path.dirname(pagePath) === dynamicPath;
610
- if (isLocalPage && !dynamic?.()) {
683
+ if (isLocalPage && !(await isDynamicRoute(revalidate))) {
611
684
  try {
612
685
  if (getStaticPaths) {
613
- const paths = await getStaticPaths();
686
+ const paths = await (typeof getStaticPaths === "function"
687
+ ? getStaticPaths()
688
+ : getStaticPaths);
614
689
  for (const pathItem of paths) {
615
690
  const currentStructure = dynamicStructure;
616
691
  const isObject =
@@ -707,19 +782,30 @@ async function buildStaticPages() {
707
782
  undefined,
708
783
  segments.length,
709
784
  );
710
- let dynamic;
785
+ let revalidate;
711
786
  if (pageFunctionsPath) {
712
- const module = await importModule(pageFunctionsPath);
713
- dynamic = module.dynamic;
787
+ try {
788
+ const module = await importModule(pageFunctionsPath);
789
+ revalidate = module.revalidate;
790
+ } catch (e) {}
714
791
  }
715
792
 
716
- if (pagePath && !dynamic?.() && !doNotPushAtEnd) {
793
+ const isPpr = await resolveBuildPprForRoute(segments, pagePath, pageFunctionsPath, srcFolder);
794
+
795
+ if (pagePath && (!(await isDynamicRoute(revalidate)) || isPpr) && !doNotPushAtEnd) {
717
796
  pages.push({
718
797
  path: currentPath,
719
798
  segments,
720
799
  params: dParams,
800
+ ppr: isPpr,
721
801
  });
722
- console.log(`Found static route: ${segments.join("/") || "/"}`);
802
+ const routeName = segments.join("/") ? "/" + segments.join("/") : "/";
803
+ if (typeof onProgress === "function") {
804
+ onProgress({ phase: "crawling", route: routeName, count: pages.length });
805
+ }
806
+ if (process.env.DINOU_DEBUG) {
807
+ console.log(`Found static route: ${routeName}${isPpr ? " (PPR)" : ""}`);
808
+ }
723
809
  }
724
810
 
725
811
  return pages;
@@ -728,9 +814,14 @@ async function buildStaticPages() {
728
814
  // Crawl complete, start rendering and serialization loop for all discovered static pages
729
815
  const pages = await collectPages(srcFolder);
730
816
 
731
- for (const { path: folderPath, segments, params } of pages) {
817
+ let pageIdx = 0;
818
+ for (const { path: folderPath, segments, params, ppr: pageIsPpr } of pages) {
819
+ pageIdx++;
732
820
  try {
733
821
  const reqPath = segments.length ? "/" + segments.join("/") + "/" : "/";
822
+ if (typeof onProgress === "function") {
823
+ onProgress({ phase: "discovering", route: reqPath, current: pageIdx, total: pages.length });
824
+ }
734
825
  // ====================================================================
735
826
  // 1. MOCK RES: Fulfilling ResponseProxy interface
736
827
  // ====================================================================
@@ -780,9 +871,11 @@ async function buildStaticPages() {
780
871
  this._redirectUrl = url;
781
872
 
782
873
  // Log warning because a redirect in SSG is usually problematic
783
- console.warn(
784
- `⚠️ [SSG] Redirect detected in ${reqPath} -> ${url} (${status})`,
785
- );
874
+ if (process.env.DINOU_DEBUG) {
875
+ console.warn(
876
+ `⚠️ [SSG] Redirect detected in ${reqPath} -> ${url} (${status})`,
877
+ );
878
+ }
786
879
  },
787
880
  };
788
881
 
@@ -826,7 +919,8 @@ async function buildStaticPages() {
826
919
  let revalidate;
827
920
  let cacheTags = [];
828
921
  const jsx = await requestStorage.run(mockContext, async () => {
829
- const [pagePath] = getFilePathAndDynamicParams(
922
+ return await runWithPprContext({ isPpr: Boolean(pageIsPpr) }, async () => {
923
+ const [pagePath] = getFilePathAndDynamicParams(
830
924
  segments,
831
925
  {},
832
926
  folderPath,
@@ -858,15 +952,18 @@ async function buildStaticPages() {
858
952
  const pageFunctionsModule = await importModule(pageFunctionsPath);
859
953
  const getProps = pageFunctionsModule.getProps;
860
954
  revalidate = pageFunctionsModule.revalidate;
861
- if (pageFunctionsModule.getCacheTags) {
955
+ const rawTags = pageFunctionsModule.getCacheTags ?? pageFunctionsModule.default?.getCacheTags;
956
+ if (typeof rawTags === "function") {
862
957
  try {
863
- cacheTags = await pageFunctionsModule.getCacheTags(params);
958
+ cacheTags = await rawTags(params);
864
959
  } catch (e) {
865
960
  console.error("Error running getCacheTags in buildStaticPages:", e);
866
961
  }
962
+ } else if (Array.isArray(rawTags)) {
963
+ cacheTags = rawTags;
867
964
  }
868
965
  pageFunctionsProps = await getProps?.(params);
869
- props = { ...props, ...(pageFunctionsProps?.page ?? {}) };
966
+ props = { ...props, ...(pageFunctionsProps ?? {}) };
870
967
  }
871
968
 
872
969
  let jsx = React.createElement(Page, props);
@@ -902,19 +999,30 @@ async function buildStaticPages() {
902
999
  false,
903
1000
  )[0];
904
1001
  const updatedSlots = {};
905
- for (const [slotName, slotElement] of Object.entries(slots)) {
906
- const alreadyFoundPath = slotElement.props?.__modulePath;
907
- const slotElementWithModule = {
908
- ...slotElement,
909
- __modulePath: alreadyFoundPath ?? null,
910
- };
911
-
1002
+ for (const [slotName, slotValue] of Object.entries(slots)) {
912
1003
  let updatedSlotElement;
1004
+ const slotFilePath = slotValue?.slotPath || slotValue?.props?.__modulePath;
1005
+ const slotParams = slotValue?.slotParams || slotValue?.props?.params || {};
1006
+
913
1007
  try {
914
- await asyncRenderJSXToClientJSX(slotElementWithModule);
915
- updatedSlotElement = slotElementWithModule;
1008
+ let elementToRender;
1009
+ if (slotValue && slotValue.slotPath) {
1010
+ const slotModule = await importModule(slotValue.slotPath);
1011
+ const Slot = slotModule.default ?? slotModule;
1012
+ elementToRender = React.createElement(Slot, {
1013
+ params: slotParams,
1014
+ key: slotName,
1015
+ __modulePath: slotValue.slotPath,
1016
+ });
1017
+ } else {
1018
+ elementToRender = {
1019
+ ...slotValue,
1020
+ __modulePath: slotFilePath ?? null,
1021
+ };
1022
+ }
1023
+ await asyncRenderJSXToClientJSX(elementToRender);
1024
+ updatedSlotElement = elementToRender;
916
1025
  } catch (e) {
917
- const slotFilePath = alreadyFoundPath;
918
1026
  if (slotFilePath) {
919
1027
  const realSlotFolder = path.dirname(slotFilePath);
920
1028
  const [slotErrorPath, slotErrorParams] =
@@ -959,13 +1067,12 @@ async function buildStaticPages() {
959
1067
  }
960
1068
  updatedSlots[slotName] = updatedSlotElement;
961
1069
  }
1070
+ const layoutProps = await getLayoutProps(layoutPath, dParams);
962
1071
  let props = {
963
1072
  params: dParams,
964
1073
  ...updatedSlots,
1074
+ ...(layoutProps ?? {}),
965
1075
  };
966
- if (index === layouts.length - 1 || resetLayoutPath) {
967
- props = { ...props, ...(pageFunctionsProps?.layout ?? {}) };
968
- }
969
1076
  jsx = React.createElement(Layout, props, jsx);
970
1077
  jsx = { ...jsx, __modulePath: layoutPath };
971
1078
  if (resetLayoutPath) {
@@ -978,16 +1085,19 @@ async function buildStaticPages() {
978
1085
 
979
1086
  // Render the layout-nested React element tree to client-side RSC JSON format
980
1087
  return await asyncRenderJSXToClientJSX(jsx);
1088
+ });
981
1089
  });
982
1090
 
983
- if (!isStatic) {
1091
+ if (!isStatic && !pageIsPpr) {
984
1092
  // ❌ DO NOT save file.
985
1093
  // Will behave as pure SSR at runtime.
986
- console.log(
987
- `Skipping static generation for ${segments.join(
988
- "/",
989
- )} due to dynamic usage.`,
990
- );
1094
+ if (process.env.DINOU_DEBUG) {
1095
+ console.log(
1096
+ `Skipping static generation for ${segments.join(
1097
+ "/",
1098
+ )} due to dynamic usage.`,
1099
+ );
1100
+ }
991
1101
  continue;
992
1102
  }
993
1103
 
@@ -996,20 +1106,27 @@ async function buildStaticPages() {
996
1106
  cookies: mockRes._cookies,
997
1107
  };
998
1108
 
1109
+ const holes = pageIsPpr ? getRegisteredHoles() : [];
999
1110
  staticRoutes.add(reqPath);
1000
1111
  staticMetadata.set(reqPath, {
1001
- revalidate: revalidate?.(),
1112
+ revalidate: typeof revalidate === "function" ? await revalidate() : revalidate,
1002
1113
  effects: sideEffects,
1003
1114
  tags: cacheTags,
1115
+ ppr: Boolean(pageIsPpr),
1116
+ holes,
1004
1117
  });
1005
- console.log(`Registered static page at ${reqPath}`);
1118
+ if (process.env.DINOU_DEBUG) {
1119
+ console.log(`Registered static page at ${reqPath}${pageIsPpr ? " (PPR)" : ""}`);
1120
+ }
1006
1121
  } catch (err) {
1007
1122
  console.error(`Error building page ${segments.join("/")}:`, err);
1008
1123
  continue;
1009
1124
  }
1010
1125
  }
1011
1126
 
1012
- console.log(`Static site generated with ${pages.length} pages`);
1127
+ if (process.env.DINOU_DEBUG) {
1128
+ console.log(`Static site generated with ${pages.length} pages`);
1129
+ }
1013
1130
  }
1014
1131
 
1015
1132
  /**
@@ -1031,7 +1148,8 @@ async function buildStaticPage(reqPath, isDynamic = null) {
1031
1148
  segment &&
1032
1149
  ((segment.startsWith("(") && segment.endsWith(")")) ||
1033
1150
  (segment.startsWith("[") && segment.endsWith("]")) ||
1034
- segment.startsWith("@"));
1151
+ segment.startsWith("@") ||
1152
+ segment.startsWith("_"));
1035
1153
  const currentPath = path.join(folderPath, segment);
1036
1154
  if (existsSync(currentPath) && !isRouterSyntaxInSegment) {
1037
1155
  folderPath = currentPath;
@@ -1110,9 +1228,11 @@ async function buildStaticPage(reqPath, isDynamic = null) {
1110
1228
  this._redirectUrl = url;
1111
1229
 
1112
1230
  // We log a warning because a redirect in SSG is usually problematic
1113
- console.warn(
1114
- `⚠️ [SSG] Redirect detected in ${reqPath} -> ${url} (${status})`,
1115
- );
1231
+ if (process.env.DINOU_DEBUG) {
1232
+ console.warn(
1233
+ `⚠️ [SSG] Redirect detected in ${reqPath} -> ${url} (${status})`,
1234
+ );
1235
+ }
1116
1236
  },
1117
1237
  };
1118
1238
 
@@ -1155,6 +1275,7 @@ async function buildStaticPage(reqPath, isDynamic = null) {
1155
1275
 
1156
1276
  let revalidate;
1157
1277
  let cacheTags = [];
1278
+ let pageIsPpr = false;
1158
1279
  const jsx = await requestStorage.run(mockContext, async () => {
1159
1280
  const [pagePath, dParams] = getFilePathAndDynamicParams(
1160
1281
  segments,
@@ -1171,8 +1292,6 @@ async function buildStaticPage(reqPath, isDynamic = null) {
1171
1292
 
1172
1293
  const pageModule = await importModule(pagePath);
1173
1294
  const Page = pageModule.default ?? pageModule;
1174
-
1175
- let props = { params: dParams };
1176
1295
  const [pageFunctionsPath] = getFilePathAndDynamicParams(
1177
1296
  segments,
1178
1297
  {},
@@ -1184,22 +1303,30 @@ async function buildStaticPage(reqPath, isDynamic = null) {
1184
1303
  segments.length,
1185
1304
  );
1186
1305
 
1306
+ pageIsPpr = await resolveBuildPprForRoute(segments, pagePath, pageFunctionsPath, srcFolder);
1307
+
1308
+ return await runWithPprContext({ isPpr: pageIsPpr }, async () => {
1309
+ let props = { params: dParams };
1310
+
1187
1311
  let pageFunctionsProps;
1188
1312
  if (pageFunctionsPath) {
1189
1313
  const pageFunctionsModule = await importModule(pageFunctionsPath);
1190
1314
  const getProps = pageFunctionsModule.getProps;
1191
- if (isDynamic && (isDynamic.value = pageFunctionsModule.dynamic?.()))
1192
- return;
1193
1315
  revalidate = pageFunctionsModule.revalidate;
1194
- if (pageFunctionsModule.getCacheTags) {
1316
+ if (isDynamic && (isDynamic.value = await isDynamicRoute(revalidate)))
1317
+ return;
1318
+ const rawTags = pageFunctionsModule.getCacheTags ?? pageFunctionsModule.default?.getCacheTags;
1319
+ if (typeof rawTags === "function") {
1195
1320
  try {
1196
- cacheTags = await pageFunctionsModule.getCacheTags(dParams);
1321
+ cacheTags = await rawTags(dParams);
1197
1322
  } catch (e) {
1198
1323
  console.error("Error running getCacheTags:", e);
1199
1324
  }
1325
+ } else if (Array.isArray(rawTags)) {
1326
+ cacheTags = rawTags;
1200
1327
  }
1201
1328
  pageFunctionsProps = await getProps?.(dParams);
1202
- props = { ...props, ...(pageFunctionsProps?.page ?? {}) };
1329
+ props = { ...props, ...(pageFunctionsProps ?? {}) };
1203
1330
  }
1204
1331
 
1205
1332
  let jsx = React.createElement(Page, props);
@@ -1233,19 +1360,30 @@ async function buildStaticPage(reqPath, isDynamic = null) {
1233
1360
  false,
1234
1361
  )[0];
1235
1362
  const updatedSlots = {};
1236
- for (const [slotName, slotElement] of Object.entries(slots)) {
1237
- const alreadyFoundPath = slotElement.props?.__modulePath;
1238
- const slotElementWithModule = {
1239
- ...slotElement,
1240
- __modulePath: alreadyFoundPath ?? null,
1241
- };
1242
-
1363
+ for (const [slotName, slotValue] of Object.entries(slots)) {
1243
1364
  let updatedSlotElement;
1365
+ const slotFilePath = slotValue?.slotPath || slotValue?.props?.__modulePath;
1366
+ const slotParams = slotValue?.slotParams || slotValue?.props?.params || {};
1367
+
1244
1368
  try {
1245
- await asyncRenderJSXToClientJSX(slotElementWithModule);
1246
- updatedSlotElement = slotElementWithModule;
1369
+ let elementToRender;
1370
+ if (slotValue && slotValue.slotPath) {
1371
+ const slotModule = await importModule(slotValue.slotPath);
1372
+ const Slot = slotModule.default ?? slotModule;
1373
+ elementToRender = React.createElement(Slot, {
1374
+ params: slotParams,
1375
+ key: slotName,
1376
+ __modulePath: slotValue.slotPath,
1377
+ });
1378
+ } else {
1379
+ elementToRender = {
1380
+ ...slotValue,
1381
+ __modulePath: slotFilePath ?? null,
1382
+ };
1383
+ }
1384
+ await asyncRenderJSXToClientJSX(elementToRender);
1385
+ updatedSlotElement = elementToRender;
1247
1386
  } catch (e) {
1248
- const slotFilePath = alreadyFoundPath;
1249
1387
  if (slotFilePath) {
1250
1388
  const realSlotFolder = path.dirname(slotFilePath);
1251
1389
  const [slotErrorPath, slotErrorParams] =
@@ -1294,12 +1432,11 @@ async function buildStaticPage(reqPath, isDynamic = null) {
1294
1432
  params: dParams,
1295
1433
  ...updatedSlots,
1296
1434
  };
1297
- if (index === layouts.length - 1 || resetLayoutPath) {
1298
- layoutProps = {
1299
- ...layoutProps,
1300
- ...(pageFunctionsProps?.layout ?? {}),
1301
- };
1302
- }
1435
+ const customLayoutProps = await getLayoutProps(layoutPath, dParams);
1436
+ layoutProps = {
1437
+ ...layoutProps,
1438
+ ...(customLayoutProps ?? {}),
1439
+ };
1303
1440
  jsx = React.createElement(Layout, layoutProps, jsx);
1304
1441
  jsx = { ...jsx, __modulePath: layoutPath };
1305
1442
  if (resetLayoutPath) {
@@ -1310,17 +1447,20 @@ async function buildStaticPage(reqPath, isDynamic = null) {
1310
1447
  }
1311
1448
  }
1312
1449
 
1313
- return await asyncRenderJSXToClientJSX(jsx);
1450
+ return await asyncRenderJSXToClientJSX(jsx);
1451
+ });
1314
1452
  });
1315
1453
 
1316
- if (!isStatic) {
1454
+ if (!isStatic && !pageIsPpr) {
1317
1455
  // ❌ DO NOT save file.
1318
1456
  // It will behave as pure SSR at runtime.
1319
- console.log(
1320
- `Skipping static generation for ${segments.join(
1321
- "/",
1322
- )} due to dynamic usage.`,
1323
- );
1457
+ if (process.env.DINOU_DEBUG) {
1458
+ console.log(
1459
+ `Skipping static generation for ${segments.join(
1460
+ "/",
1461
+ )} due to dynamic usage.`,
1462
+ );
1463
+ }
1324
1464
  if (isDynamic) {
1325
1465
  isDynamic.value = true;
1326
1466
  }
@@ -1332,13 +1472,16 @@ async function buildStaticPage(reqPath, isDynamic = null) {
1332
1472
  cookies: mockRes._cookies,
1333
1473
  };
1334
1474
 
1475
+ const holes = pageIsPpr ? getRegisteredHoles() : [];
1335
1476
  staticRoutes.add(reqPath);
1336
1477
  staticMetadata.set(reqPath, {
1337
- revalidate: revalidate?.(),
1478
+ revalidate: typeof revalidate === "function" ? await revalidate() : revalidate,
1338
1479
  effects: sideEffects,
1339
1480
  tags: cacheTags,
1481
+ ppr: pageIsPpr,
1482
+ holes,
1340
1483
  });
1341
- console.warn(`Registered rebuilt static page at ${reqPath}`);
1484
+ console.warn(`Registered rebuilt static page at ${reqPath}${pageIsPpr ? " (PPR)" : ""}`);
1342
1485
  } catch (error) {
1343
1486
  console.error(`Error building page ${reqPath}:`, error);
1344
1487
  throw error;
@@ -1350,7 +1493,14 @@ function getStaticPaths() {
1350
1493
  }
1351
1494
 
1352
1495
  function getStaticMetadata(reqPath) {
1353
- return staticMetadata.get(reqPath);
1496
+ if (!reqPath) return null;
1497
+ const noSlash = reqPath.endsWith("/") ? reqPath.slice(0, -1) : reqPath;
1498
+ const withSlash = noSlash + "/";
1499
+ return staticMetadata.get(reqPath) || staticMetadata.get(noSlash) || staticMetadata.get(withSlash) || null;
1500
+ }
1501
+
1502
+ function getStaticLayouts() {
1503
+ return Array.from(staticLayouts.values());
1354
1504
  }
1355
1505
 
1356
1506
  module.exports = {
@@ -1358,4 +1508,6 @@ module.exports = {
1358
1508
  buildStaticPage,
1359
1509
  getStaticPaths,
1360
1510
  getStaticMetadata,
1511
+ getStaticLayouts,
1361
1512
  };
1513
+