reroute-js 0.9.4 → 0.10.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 (200) hide show
  1. package/_/basic/src/index.ts +1 -8
  2. package/_/blog/src/client/index.html +0 -1
  3. package/_/blog/src/client/routes/blog/[layout].tsx +53 -3
  4. package/_/blog/src/client/routes/docs.tsx +1 -1
  5. package/_/blog/src/index.ts +1 -8
  6. package/_/store/src/index.ts +1 -8
  7. package/cli/bin.d.ts +1 -1
  8. package/cli/bin.js +3666 -2682
  9. package/cli/bin.js.map +40 -29
  10. package/cli/index.d.ts +2 -2
  11. package/cli/index.d.ts.map +1 -1
  12. package/cli/index.js +74 -10
  13. package/cli/index.js.map +7 -5
  14. package/cli/src/cli.d.ts +1 -1
  15. package/cli/src/commands/analyze.d.ts +1 -1
  16. package/cli/src/commands/build.d.ts +1 -1
  17. package/cli/src/commands/dev.d.ts +1 -1
  18. package/cli/src/commands/gen.d.ts +1 -1
  19. package/cli/src/commands/gen.d.ts.map +1 -1
  20. package/{core/src/template → cli/src/commands}/index.d.ts +2 -2
  21. package/cli/src/commands/index.d.ts.map +1 -0
  22. package/cli/src/commands/init.d.ts +1 -1
  23. package/cli/src/{libs → commands/lib}/command.d.ts +1 -1
  24. package/cli/src/commands/lib/command.d.ts.map +1 -0
  25. package/cli/src/{libs → commands/lib}/index.d.ts +3 -2
  26. package/cli/src/commands/lib/index.d.ts.map +1 -0
  27. package/cli/src/{libs → commands/lib}/log.d.ts +1 -1
  28. package/cli/src/commands/lib/log.d.ts.map +1 -0
  29. package/cli/src/{libs/markdown.d.ts → commands/lib/markdown/availability.d.ts} +2 -2
  30. package/cli/src/commands/lib/markdown/availability.d.ts.map +1 -0
  31. package/cli/src/commands/lib/markdown/index.d.ts +12 -0
  32. package/cli/src/commands/lib/markdown/index.d.ts.map +1 -0
  33. package/cli/src/{libs/markdown-processor.d.ts → commands/lib/markdown/processor.d.ts} +2 -2
  34. package/cli/src/commands/lib/markdown/processor.d.ts.map +1 -0
  35. package/cli/src/{libs → commands/lib}/production.d.ts +1 -1
  36. package/cli/src/commands/lib/production.d.ts.map +1 -0
  37. package/cli/src/{libs → commands/lib}/server.d.ts +1 -1
  38. package/cli/src/commands/lib/server.d.ts.map +1 -0
  39. package/cli/src/commands/lib/streaming/analyzer.d.ts +26 -0
  40. package/cli/src/commands/lib/streaming/analyzer.d.ts.map +1 -0
  41. package/cli/src/commands/lib/streaming/suspense.d.ts +20 -0
  42. package/cli/src/commands/lib/streaming/suspense.d.ts.map +1 -0
  43. package/cli/src/{libs → commands/lib}/tailwind.d.ts +1 -1
  44. package/cli/src/commands/lib/tailwind.d.ts.map +1 -0
  45. package/cli/src/{libs → commands/lib}/version.d.ts +1 -1
  46. package/cli/src/commands/lib/version.d.ts.map +1 -0
  47. package/cli/src/commands/start.d.ts +2 -2
  48. package/cli/src/commands/start.d.ts.map +1 -1
  49. package/cli/src/index.d.ts +11 -0
  50. package/cli/src/index.d.ts.map +1 -0
  51. package/core/index.d.ts +1 -1
  52. package/core/index.js +1169 -471
  53. package/core/index.js.map +22 -13
  54. package/core/src/bundler/hash.d.ts +1 -1
  55. package/core/src/bundler/index.d.ts +1 -1
  56. package/core/src/config.d.ts +42 -0
  57. package/core/src/config.d.ts.map +1 -0
  58. package/core/src/content/discovery.d.ts +1 -1
  59. package/core/src/content/index.d.ts +1 -1
  60. package/core/src/content/metadata.d.ts +2 -2
  61. package/core/src/content/metadata.d.ts.map +1 -1
  62. package/core/src/index.d.ts +2 -4
  63. package/core/src/index.d.ts.map +1 -1
  64. package/core/src/ssr/index.d.ts +3 -3
  65. package/core/src/ssr/index.d.ts.map +1 -1
  66. package/core/src/ssr/lib/cache.d.ts +67 -0
  67. package/core/src/ssr/lib/cache.d.ts.map +1 -0
  68. package/core/src/ssr/lib/collections.d.ts +18 -0
  69. package/core/src/ssr/lib/collections.d.ts.map +1 -0
  70. package/core/src/{utils → ssr/lib}/compression.d.ts +9 -2
  71. package/core/src/ssr/lib/compression.d.ts.map +1 -0
  72. package/core/src/ssr/lib/compute.d.ts +28 -0
  73. package/core/src/ssr/lib/compute.d.ts.map +1 -0
  74. package/core/src/ssr/{data.d.ts → lib/data.d.ts} +1 -1
  75. package/core/src/ssr/lib/data.d.ts.map +1 -0
  76. package/core/src/{template → ssr/lib}/html.d.ts +2 -2
  77. package/core/src/ssr/lib/html.d.ts.map +1 -0
  78. package/core/src/ssr/lib/index.d.ts +26 -0
  79. package/core/src/ssr/lib/index.d.ts.map +1 -0
  80. package/core/src/ssr/lib/metadata.d.ts +20 -0
  81. package/core/src/ssr/lib/metadata.d.ts.map +1 -0
  82. package/core/src/{utils → ssr/lib}/mime.d.ts +1 -1
  83. package/core/src/ssr/lib/mime.d.ts.map +1 -0
  84. package/core/src/ssr/{modules.d.ts → lib/modules.d.ts} +2 -2
  85. package/core/src/ssr/lib/modules.d.ts.map +1 -0
  86. package/core/src/{utils → ssr/lib}/path.d.ts +1 -1
  87. package/core/src/ssr/lib/path.d.ts.map +1 -0
  88. package/core/src/ssr/lib/preload.d.ts +29 -0
  89. package/core/src/ssr/lib/preload.d.ts.map +1 -0
  90. package/core/src/ssr/lib/scripts.d.ts +31 -0
  91. package/core/src/ssr/lib/scripts.d.ts.map +1 -0
  92. package/core/src/ssr/{seed.d.ts → lib/seed.d.ts} +1 -1
  93. package/core/src/ssr/lib/seed.d.ts.map +1 -0
  94. package/core/src/ssr/lib/styles.d.ts +14 -0
  95. package/core/src/ssr/lib/styles.d.ts.map +1 -0
  96. package/core/src/ssr/lib/template.d.ts +19 -0
  97. package/core/src/ssr/lib/template.d.ts.map +1 -0
  98. package/core/src/{types.d.ts → ssr/lib/types.d.ts} +1 -1
  99. package/core/src/ssr/lib/types.d.ts.map +1 -0
  100. package/core/src/ssr/render.d.ts +2 -1
  101. package/core/src/ssr/render.d.ts.map +1 -1
  102. package/core/src/ssr/stream.d.ts +30 -0
  103. package/core/src/ssr/stream.d.ts.map +1 -0
  104. package/elysia/index.d.ts +1 -1
  105. package/elysia/index.js +1917 -794
  106. package/elysia/index.js.map +34 -19
  107. package/elysia/src/index.d.ts +1 -1
  108. package/elysia/src/libs/cache.d.ts +1 -1
  109. package/elysia/src/libs/http.d.ts +1 -1
  110. package/elysia/src/libs/image.d.ts +1 -1
  111. package/elysia/src/plugin.d.ts +1 -1
  112. package/elysia/src/plugin.d.ts.map +1 -1
  113. package/elysia/src/routes/artifacts.d.ts +1 -1
  114. package/elysia/src/routes/content.d.ts +1 -1
  115. package/elysia/src/routes/image.d.ts +2 -1
  116. package/elysia/src/routes/image.d.ts.map +1 -1
  117. package/elysia/src/routes/internal.d.ts +36 -0
  118. package/elysia/src/routes/internal.d.ts.map +1 -0
  119. package/elysia/src/routes/ssr.d.ts +3 -1
  120. package/elysia/src/routes/ssr.d.ts.map +1 -1
  121. package/elysia/src/routes/static.d.ts +2 -1
  122. package/elysia/src/routes/static.d.ts.map +1 -1
  123. package/elysia/src/types.d.ts +11 -4
  124. package/elysia/src/types.d.ts.map +1 -1
  125. package/package.json +1 -1
  126. package/react/index.d.ts +1 -1
  127. package/react/index.js +295 -53
  128. package/react/index.js.map +10 -9
  129. package/react/src/components/ClientOnly.d.ts +1 -1
  130. package/react/src/components/ContentRoute.d.ts +1 -1
  131. package/react/src/components/ContentRoute.d.ts.map +1 -1
  132. package/react/src/components/Image.d.ts +1 -1
  133. package/react/src/components/LazyRoute.d.ts +26 -0
  134. package/react/src/components/LazyRoute.d.ts.map +1 -0
  135. package/react/src/components/Link.d.ts +1 -1
  136. package/react/src/components/Link.d.ts.map +1 -1
  137. package/react/src/components/Markdown.d.ts +1 -1
  138. package/react/src/components/Outlet.d.ts +1 -1
  139. package/react/src/components/index.d.ts +1 -1
  140. package/react/src/hooks/index.d.ts +2 -1
  141. package/react/src/hooks/index.d.ts.map +1 -1
  142. package/react/src/hooks/useContent.d.ts +1 -1
  143. package/react/src/hooks/useData.d.ts +1 -1
  144. package/react/src/hooks/useData.d.ts.map +1 -1
  145. package/react/src/hooks/useLayoutData.d.ts +18 -0
  146. package/react/src/hooks/useLayoutData.d.ts.map +1 -0
  147. package/react/src/hooks/useNavigate.d.ts +1 -1
  148. package/react/src/hooks/useParams.d.ts +1 -1
  149. package/react/src/hooks/useRouter.d.ts +1 -1
  150. package/react/src/hooks/useSearchParams.d.ts +1 -1
  151. package/react/src/index.d.ts +2 -2
  152. package/react/src/index.d.ts.map +1 -1
  153. package/react/src/{utils → lib}/content.d.ts +1 -1
  154. package/react/src/lib/content.d.ts.map +1 -0
  155. package/react/src/{utils → lib}/head.d.ts +1 -1
  156. package/react/src/lib/head.d.ts.map +1 -0
  157. package/react/src/{utils → lib}/index.d.ts +2 -2
  158. package/react/src/lib/index.d.ts.map +1 -0
  159. package/react/src/{utils → lib}/lazy-route.d.ts +1 -1
  160. package/react/src/lib/lazy-route.d.ts.map +1 -0
  161. package/react/src/lib/route-loader.d.ts +31 -0
  162. package/react/src/lib/route-loader.d.ts.map +1 -0
  163. package/react/src/providers/ContentProvider.d.ts +1 -1
  164. package/react/src/providers/ContentProvider.d.ts.map +1 -1
  165. package/react/src/providers/RerouteProvider.d.ts +1 -1
  166. package/react/src/providers/RerouteProvider.d.ts.map +1 -1
  167. package/react/src/providers/RouterProvider.d.ts +1 -1
  168. package/react/src/providers/RouterProvider.d.ts.map +1 -1
  169. package/react/src/providers/index.d.ts +1 -1
  170. package/react/src/types/any.d.ts +1 -1
  171. package/react/src/types/index.d.ts +1 -1
  172. package/react/src/types/router.d.ts +1 -1
  173. package/cli/src/libs/command.d.ts.map +0 -1
  174. package/cli/src/libs/index.d.ts.map +0 -1
  175. package/cli/src/libs/log.d.ts.map +0 -1
  176. package/cli/src/libs/markdown-processor.d.ts.map +0 -1
  177. package/cli/src/libs/markdown.d.ts.map +0 -1
  178. package/cli/src/libs/production.d.ts.map +0 -1
  179. package/cli/src/libs/server.d.ts.map +0 -1
  180. package/cli/src/libs/tailwind.d.ts.map +0 -1
  181. package/cli/src/libs/version.d.ts.map +0 -1
  182. package/core/src/ssr/data.d.ts.map +0 -1
  183. package/core/src/ssr/modules.d.ts.map +0 -1
  184. package/core/src/ssr/seed.d.ts.map +0 -1
  185. package/core/src/template/html.d.ts.map +0 -1
  186. package/core/src/template/index.d.ts.map +0 -1
  187. package/core/src/types.d.ts.map +0 -1
  188. package/core/src/utils/cache.d.ts +0 -21
  189. package/core/src/utils/cache.d.ts.map +0 -1
  190. package/core/src/utils/compression.d.ts.map +0 -1
  191. package/core/src/utils/index.d.ts +0 -14
  192. package/core/src/utils/index.d.ts.map +0 -1
  193. package/core/src/utils/mime.d.ts.map +0 -1
  194. package/core/src/utils/path.d.ts.map +0 -1
  195. package/elysia/src/routes/dev.d.ts +0 -18
  196. package/elysia/src/routes/dev.d.ts.map +0 -1
  197. package/react/src/utils/content.d.ts.map +0 -1
  198. package/react/src/utils/head.d.ts.map +0 -1
  199. package/react/src/utils/index.d.ts.map +0 -1
  200. package/react/src/utils/lazy-route.d.ts.map +0 -1
package/core/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * reroute-js v0.9.4
2
+ * reroute-js v0.10.0
3
3
  *
4
4
  * @license MIT
5
5
  * @copyright 2025 stewones <hi@stewan.io>
@@ -17,10 +17,29 @@ async function generateContentHash(content) {
17
17
  hex += b.toString(16).padStart(2, "0");
18
18
  return hex.slice(0, 8);
19
19
  }
20
+ // packages/core/src/config.ts
21
+ import { pathToFileURL } from "node:url";
22
+ function defineConfig(config) {
23
+ return config;
24
+ }
25
+ async function loadConfig(cwd) {
26
+ const configPath = `${cwd}/reroute.config.ts`;
27
+ const configFile = Bun.file(configPath);
28
+ if (!await configFile.exists()) {
29
+ return {};
30
+ }
31
+ try {
32
+ const mod = await import(`${pathToFileURL(configPath).href}?t=${Date.now()}`);
33
+ return mod.default || mod;
34
+ } catch (error) {
35
+ console.warn("[reroute] Failed to load reroute.config.ts:", error);
36
+ return {};
37
+ }
38
+ }
20
39
  // packages/core/src/content/discovery.ts
21
40
  import { readdir, stat } from "node:fs/promises";
22
41
 
23
- // packages/core/src/utils/path.ts
42
+ // packages/core/src/ssr/lib/path.ts
24
43
  function join(...parts) {
25
44
  return parts.join("/").replace(/\/+/g, "/");
26
45
  }
@@ -78,10 +97,10 @@ async function discoverCollections(clientDir) {
78
97
  }
79
98
  // packages/core/src/content/metadata.ts
80
99
  import { stat as stat2 } from "node:fs/promises";
81
- import { pathToFileURL } from "node:url";
100
+ import { pathToFileURL as pathToFileURL2 } from "node:url";
82
101
  async function getContentMeta(absPath, isWatchMode) {
83
102
  try {
84
- const url = pathToFileURL(absPath).href;
103
+ const url = pathToFileURL2(absPath).href;
85
104
  const mod = await import(`${url}${isWatchMode ? `?t=${Date.now()}` : ""}`);
86
105
  const meta = mod.meta || mod.frontmatter || {};
87
106
  if (meta && typeof meta === "object")
@@ -113,12 +132,448 @@ ${parts.join(`
113
132
  function escapeHtml(input) {
114
133
  return input.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
115
134
  }
116
- // packages/core/src/ssr/data.ts
135
+ // packages/core/src/ssr/lib/cache.ts
136
+ class SSRDataCache {
137
+ cache = new Map;
138
+ static cleanupInterval = null;
139
+ constructor() {
140
+ if (!SSRDataCache.cleanupInterval) {
141
+ SSRDataCache.cleanupInterval = setInterval(() => {
142
+ this.cleanup();
143
+ }, 60000);
144
+ }
145
+ }
146
+ get(key) {
147
+ const entry = this.cache.get(key);
148
+ if (!entry)
149
+ return;
150
+ const age = Date.now() - entry.timestamp;
151
+ if (age > entry.maxAge) {
152
+ this.cache.delete(key);
153
+ return;
154
+ }
155
+ return entry.data;
156
+ }
157
+ set(key, data, maxAge) {
158
+ if (maxAge <= 0)
159
+ return;
160
+ this.cache.set(key, {
161
+ data,
162
+ timestamp: Date.now(),
163
+ maxAge
164
+ });
165
+ }
166
+ has(key) {
167
+ return this.get(key) !== undefined;
168
+ }
169
+ clear() {
170
+ this.cache.clear();
171
+ }
172
+ cleanup() {
173
+ const now = Date.now();
174
+ for (const [key, entry] of this.cache.entries()) {
175
+ if (now - entry.timestamp > entry.maxAge) {
176
+ this.cache.delete(key);
177
+ }
178
+ }
179
+ }
180
+ stats() {
181
+ return {
182
+ size: this.cache.size,
183
+ keys: Array.from(this.cache.keys())
184
+ };
185
+ }
186
+ destroy() {
187
+ if (SSRDataCache.cleanupInterval) {
188
+ clearInterval(SSRDataCache.cleanupInterval);
189
+ SSRDataCache.cleanupInterval = null;
190
+ }
191
+ this.clear();
192
+ }
193
+ }
194
+
195
+ class LRUCache {
196
+ cache = new Map;
197
+ maxSize;
198
+ constructor(maxSize = 100) {
199
+ this.maxSize = maxSize;
200
+ }
201
+ get(key) {
202
+ const value = this.cache.get(key);
203
+ if (value !== undefined) {
204
+ this.cache.delete(key);
205
+ this.cache.set(key, value);
206
+ }
207
+ return value;
208
+ }
209
+ set(key, value) {
210
+ this.cache.delete(key);
211
+ if (this.cache.size >= this.maxSize) {
212
+ const firstKey = this.cache.keys().next().value;
213
+ this.cache.delete(firstKey);
214
+ }
215
+ this.cache.set(key, value);
216
+ }
217
+ has(key) {
218
+ return this.cache.has(key);
219
+ }
220
+ clear() {
221
+ this.cache.clear();
222
+ }
223
+ }
224
+ var getGlobalCache = () => {
225
+ const g = globalThis;
226
+ if (!g.__REROUTE_SSR_CACHE__) {
227
+ g.__REROUTE_SSR_CACHE__ = new SSRDataCache;
228
+ g.__REROUTE_SSR_CACHE_INIT__ = true;
229
+ }
230
+ return g.__REROUTE_SSR_CACHE__;
231
+ };
232
+ var ssrCache = getGlobalCache();
233
+ // packages/core/src/ssr/lib/collections.ts
117
234
  import { pathToFileURL as pathToFileURL3 } from "node:url";
235
+ async function loadCollections(cwd, isWatchMode) {
236
+ try {
237
+ const p = join(cwd, ".reroute", "content.ts");
238
+ const mod = await import(pathToFileURL3(p).href + (isWatchMode ? `?t=${Date.now()}` : ""));
239
+ return mod?.byCollection || {};
240
+ } catch {
241
+ return {};
242
+ }
243
+ }
244
+ function setGlobalCollections(byCollectionForSSR) {
245
+ try {
246
+ globalThis.__REROUTE_COLLECTIONS__ = byCollectionForSSR;
247
+ } catch {}
248
+ }
249
+ // packages/core/src/ssr/lib/compression.ts
250
+ import { brotliCompressSync } from "node:zlib";
251
+
252
+ // packages/core/src/ssr/lib/mime.ts
253
+ function getMimeType(filePath) {
254
+ const ext = filePath.split(".").pop()?.toLowerCase();
255
+ const mimeTypes = {
256
+ html: "text/html",
257
+ css: "text/css",
258
+ js: "application/javascript",
259
+ ts: "application/javascript",
260
+ json: "application/json",
261
+ png: "image/png",
262
+ jpg: "image/jpeg",
263
+ jpeg: "image/jpeg",
264
+ gif: "image/gif",
265
+ svg: "image/svg+xml",
266
+ ico: "image/x-icon",
267
+ woff: "font/woff",
268
+ woff2: "font/woff2",
269
+ ttf: "font/ttf"
270
+ };
271
+ return mimeTypes[ext || ""] || "application/octet-stream";
272
+ }
273
+ function isCompressible(contentType) {
274
+ if (!contentType)
275
+ return false;
276
+ const ct = contentType.split(";")[0].trim();
277
+ return ct.startsWith("text/") || ct === "application/javascript" || ct === "application/json" || ct === "application/xml" || ct === "image/svg+xml" || ct === "font/ttf" || ct === "font/woff";
278
+ }
279
+
280
+ // packages/core/src/ssr/lib/compression.ts
281
+ function acceptsGzip(acceptEncoding) {
282
+ return Boolean(acceptEncoding && /gzip/i.test(acceptEncoding));
283
+ }
284
+ function acceptsBrotli(acceptEncoding) {
285
+ return Boolean(acceptEncoding && /\bbr\b/i.test(acceptEncoding));
286
+ }
287
+ function toBytes(input) {
288
+ return typeof input === "string" ? new TextEncoder().encode(input) : input;
289
+ }
290
+ function gzipIfAccepted(body, contentType, acceptEncoding) {
291
+ const extraHeaders = {};
292
+ if (isCompressible(contentType)) {
293
+ if (acceptsBrotli(acceptEncoding)) {
294
+ try {
295
+ const compressed = brotliCompressSync(toBytes(body));
296
+ extraHeaders["Content-Encoding"] = "br";
297
+ extraHeaders.Vary = "Accept-Encoding";
298
+ return { body: compressed, extraHeaders };
299
+ } catch {}
300
+ }
301
+ if (acceptsGzip(acceptEncoding)) {
302
+ try {
303
+ const compressed = Bun.gzipSync(toBytes(body));
304
+ extraHeaders["Content-Encoding"] = "gzip";
305
+ extraHeaders.Vary = "Accept-Encoding";
306
+ return { body: compressed, extraHeaders };
307
+ } catch {}
308
+ }
309
+ }
310
+ return { body, extraHeaders };
311
+ }
312
+ function compressStreamIfAccepted(stream, contentType, _acceptEncoding) {
313
+ const extraHeaders = {};
314
+ if (!isCompressible(contentType)) {
315
+ return { stream, extraHeaders };
316
+ }
317
+ return { stream, extraHeaders };
318
+ }
319
+ // packages/core/src/ssr/lib/compute.ts
320
+ import { pathToFileURL as pathToFileURL4 } from "node:url";
321
+ var isThenable = (value) => {
322
+ return typeof value === "object" && value !== null && typeof value.then === "function";
323
+ };
324
+ async function computeSSRData(pathname, clientDir, cwd, isWatchMode, options = {}) {
325
+ const ssrData = {};
326
+ let error;
327
+ const pendingTasks = [];
328
+ const streaming = options.streaming === true;
329
+ const maxAge = options.maxAge || 0;
330
+ const maxAgeMs = maxAge * 1000;
331
+ const toPromise = (input) => input instanceof Promise ? input : Promise.resolve(input);
332
+ const storeResult = (key, task) => {
333
+ pendingTasks.push(task.then((value) => {
334
+ ssrData[key] = value;
335
+ }).catch(() => {}));
336
+ };
337
+ try {
338
+ const routesPath = join(cwd, ".reroute", "routes.ts");
339
+ const m = await import(pathToFileURL4(routesPath).href + (isWatchMode ? `?t=${Date.now()}` : ""));
340
+ try {
341
+ const layouts = m?.layouts;
342
+ if (Array.isArray(layouts)) {
343
+ const matchingLayouts = layouts.filter((layout) => {
344
+ const pattern = String(layout?.pattern || "/");
345
+ if (pattern === "/")
346
+ return true;
347
+ return pathname.startsWith(pattern);
348
+ }).sort((a, b) => {
349
+ const aDepth = String(a?.pattern || "/").split("/").filter(Boolean).length;
350
+ const bDepth = String(b?.pattern || "/").split("/").filter(Boolean).length;
351
+ return aDepth - bDepth;
352
+ });
353
+ for (const layout of matchingLayouts) {
354
+ if (typeof layout?.path === "string") {
355
+ try {
356
+ const abs = join(clientDir, "routes", String(layout.path));
357
+ const mod = await import(pathToFileURL4(abs).href + (isWatchMode ? `?t=${Date.now()}` : ""));
358
+ const ssr = mod?.ssr;
359
+ const dataFn = ssr?.data;
360
+ if (typeof dataFn === "function") {
361
+ const pattern = String(layout.pattern || "/");
362
+ const layoutKey = `__layout:${pattern}`;
363
+ const cacheKey = `layout:${pattern}:${pathname}`;
364
+ const cached = maxAgeMs > 0 ? ssrCache.get(cacheKey) : undefined;
365
+ if (cached !== undefined) {
366
+ if (streaming) {
367
+ ssrData[layoutKey] = cached;
368
+ } else {
369
+ ssrData[layoutKey] = cached;
370
+ }
371
+ } else {
372
+ const task = toPromise(dataFn({ pathname, params: {} })).catch((err) => {
373
+ const msg = err instanceof Error ? err.message : String(err);
374
+ console.error(`[reroute] Layout SSR data() failed for ${layout.pattern}:`, msg);
375
+ throw err;
376
+ });
377
+ if (streaming) {
378
+ ssrData[layoutKey] = task;
379
+ storeResult(layoutKey, task.then((value) => {
380
+ if (maxAgeMs > 0) {
381
+ ssrCache.set(cacheKey, value, maxAgeMs);
382
+ }
383
+ return value;
384
+ }));
385
+ } else {
386
+ const result = await task;
387
+ ssrData[layoutKey] = result;
388
+ if (maxAgeMs > 0) {
389
+ ssrCache.set(cacheKey, result, maxAgeMs);
390
+ }
391
+ }
392
+ }
393
+ }
394
+ } catch {}
395
+ }
396
+ }
397
+ }
398
+ } catch {}
399
+ try {
400
+ const parts = pathname.split("/").filter(Boolean);
401
+ if (parts.length >= 2) {
402
+ const g = globalThis;
403
+ const key = `${parts[0]}:${parts[1]}`;
404
+ const exp = g.__REROUTE_SSR_EXPORTS__?.[key];
405
+ const dataFn = exp?.ssr?.data;
406
+ if (typeof dataFn === "function") {
407
+ const cacheKey = `content:${pathname}:${parts[1]}`;
408
+ const cached = maxAgeMs > 0 ? ssrCache.get(cacheKey) : undefined;
409
+ if (cached !== undefined) {
410
+ ssrData[pathname] = cached;
411
+ } else {
412
+ const task = toPromise(dataFn({
413
+ pathname,
414
+ params: { name: parts[1] }
415
+ })).catch((err) => {
416
+ const msg = err instanceof Error ? err.message : String(err);
417
+ error = `SSR data() failed: ${msg}`;
418
+ console.error(`[reroute] ${error}`);
419
+ throw err;
420
+ });
421
+ if (streaming) {
422
+ ssrData[pathname] = task;
423
+ storeResult(pathname, task.then((value) => {
424
+ if (maxAgeMs > 0) {
425
+ ssrCache.set(cacheKey, value, maxAgeMs);
426
+ }
427
+ return value;
428
+ }));
429
+ } else {
430
+ const result = await task;
431
+ ssrData[pathname] = result;
432
+ if (maxAgeMs > 0) {
433
+ ssrCache.set(cacheKey, result, maxAgeMs);
434
+ }
435
+ }
436
+ }
437
+ }
438
+ }
439
+ } catch {}
440
+ try {
441
+ const match = typeof m.matchRoute === "function" ? m.matchRoute(pathname) : null;
442
+ const r = match?.route;
443
+ const params = match?.params || {};
444
+ if (r && typeof r.path === "string") {
445
+ try {
446
+ const abs = join(clientDir, "routes", String(r.path));
447
+ const mod = await import(pathToFileURL4(abs).href + (isWatchMode ? `?t=${Date.now()}` : ""));
448
+ const ssr = mod?.ssr;
449
+ const dataFn = ssr?.data;
450
+ if (typeof dataFn === "function") {
451
+ const cacheKey = `route:${pathname}:${JSON.stringify(params)}`;
452
+ const cached = maxAgeMs > 0 ? ssrCache.get(cacheKey) : undefined;
453
+ if (cached !== undefined) {
454
+ if (streaming && typeof cached === "object" && cached !== null && !Array.isArray(cached)) {
455
+ for (const [key, value] of Object.entries(cached)) {
456
+ ssrData[key] = value;
457
+ }
458
+ } else {
459
+ ssrData[pathname] = cached;
460
+ }
461
+ } else {
462
+ const rawResult = dataFn({ pathname, params });
463
+ if (streaming) {
464
+ if (typeof rawResult === "object" && rawResult !== null && !Array.isArray(rawResult) && !(rawResult instanceof Promise)) {
465
+ const entries = Object.entries(rawResult);
466
+ const hasMultipleKeys = entries.length > 1;
467
+ const hasPromises = entries.some(([_, v]) => isThenable(v));
468
+ if (hasMultipleKeys || hasPromises) {
469
+ const promisesToResolve = [];
470
+ for (const [key, val] of entries) {
471
+ if (isThenable(val)) {
472
+ const wrappedPromise = val.then((resolved) => {
473
+ return resolved;
474
+ }).catch((err) => {
475
+ const msg = err instanceof Error ? err.message : String(err);
476
+ error = `SSR data() failed for key ${key}: ${msg}`;
477
+ console.error(`[reroute] ${error}`);
478
+ throw err;
479
+ });
480
+ ssrData[key] = wrappedPromise;
481
+ storeResult(key, wrappedPromise);
482
+ promisesToResolve.push(wrappedPromise.then((resolved) => [key, resolved]));
483
+ } else {
484
+ ssrData[key] = val;
485
+ promisesToResolve.push(Promise.resolve([key, val]));
486
+ }
487
+ }
488
+ if (maxAgeMs > 0) {
489
+ Promise.all(promisesToResolve).then((resolvedEntries) => {
490
+ const resolvedResult = {};
491
+ for (const [key, value] of resolvedEntries) {
492
+ resolvedResult[key] = value;
493
+ }
494
+ ssrCache.set(cacheKey, resolvedResult, maxAgeMs);
495
+ }).catch(() => {});
496
+ }
497
+ } else {
498
+ const task = toPromise(rawResult).catch((err) => {
499
+ const msg = err instanceof Error ? err.message : String(err);
500
+ error = `SSR data() failed: ${msg}`;
501
+ console.error(`[reroute] ${error}`);
502
+ throw err;
503
+ });
504
+ ssrData[pathname] = task;
505
+ storeResult(pathname, task.then((value) => {
506
+ if (maxAgeMs > 0) {
507
+ ssrCache.set(cacheKey, value, maxAgeMs);
508
+ }
509
+ return value;
510
+ }));
511
+ }
512
+ } else {
513
+ const task = toPromise(rawResult).catch((err) => {
514
+ const msg = err instanceof Error ? err.message : String(err);
515
+ error = `SSR data() failed: ${msg}`;
516
+ console.error(`[reroute] ${error}`);
517
+ throw err;
518
+ });
519
+ ssrData[pathname] = task;
520
+ storeResult(pathname, task.then((value) => {
521
+ if (maxAgeMs > 0) {
522
+ ssrCache.set(cacheKey, value, maxAgeMs);
523
+ }
524
+ return value;
525
+ }));
526
+ }
527
+ } else {
528
+ const task = toPromise(rawResult).catch((err) => {
529
+ const msg = err instanceof Error ? err.message : String(err);
530
+ error = `SSR data() failed: ${msg}`;
531
+ console.error(`[reroute] ${error}`);
532
+ throw err;
533
+ });
534
+ const result = await task;
535
+ ssrData[pathname] = result;
536
+ if (maxAgeMs > 0) {
537
+ ssrCache.set(cacheKey, result, maxAgeMs);
538
+ }
539
+ }
540
+ }
541
+ }
542
+ } catch {}
543
+ }
544
+ } catch {}
545
+ } catch {}
546
+ const layoutKeys = Object.keys(ssrData).filter((key) => key.startsWith("__layout:"));
547
+ if (streaming) {} else {
548
+ const layoutData = {};
549
+ for (const key of layoutKeys) {
550
+ Object.assign(layoutData, ssrData[key]);
551
+ }
552
+ if (pathname in ssrData) {
553
+ ssrData[pathname] = {
554
+ ...layoutData,
555
+ ...typeof ssrData[pathname] === "object" && ssrData[pathname] !== null ? ssrData[pathname] : {}
556
+ };
557
+ } else if (Object.keys(layoutData).length > 0) {
558
+ ssrData[pathname] = layoutData;
559
+ }
560
+ }
561
+ const pending = streaming && pendingTasks.length ? Promise.allSettled(pendingTasks).then(() => {
562
+ return;
563
+ }) : undefined;
564
+ return { data: ssrData, error, pending };
565
+ }
566
+ function setGlobalSSRData(data) {
567
+ try {
568
+ globalThis.__REROUTE_DATA__ = data;
569
+ } catch {}
570
+ }
571
+ // packages/core/src/ssr/lib/data.ts
572
+ import { pathToFileURL as pathToFileURL6 } from "node:url";
118
573
 
119
- // packages/core/src/ssr/modules.ts
574
+ // packages/core/src/ssr/lib/modules.ts
120
575
  import { readdir as readdir2, stat as stat3 } from "node:fs/promises";
121
- import { pathToFileURL as pathToFileURL2 } from "node:url";
576
+ import { pathToFileURL as pathToFileURL5 } from "node:url";
122
577
  async function importContentModuleForPath(pathname, clientDir, cwd, isWatchMode) {
123
578
  try {
124
579
  const parts = pathname.split("/").filter(Boolean);
@@ -128,15 +583,29 @@ async function importContentModuleForPath(pathname, clientDir, cwd, isWatchMode)
128
583
  const name = parts[1];
129
584
  try {
130
585
  const registryPath = join(cwd, ".reroute", "content.ts");
131
- const reg = await import(pathToFileURL2(registryPath).href + (isWatchMode ? `?t=${Date.now()}` : ""));
586
+ const reg = await import(pathToFileURL5(registryPath).href + (isWatchMode ? `?t=${Date.now()}` : ""));
132
587
  const get = reg?.getContentEntry;
133
588
  const entry = typeof get === "function" ? get(collection, name) : undefined;
134
589
  const moduleUrl = entry?.module;
135
590
  if (moduleUrl?.endsWith(".js")) {
136
- const abs = join(cwd, moduleUrl.replace(/^\//, ""));
137
- const href = pathToFileURL2(abs).href + (isWatchMode ? `?t=${Date.now()}` : "");
138
- const mod = await import(href);
139
- return mod;
591
+ const raw = moduleUrl.split("?")[0] || "";
592
+ const normalized = raw.replace(/^\/+/, "");
593
+ const candidates = [];
594
+ if (normalized) {
595
+ candidates.push(join(cwd, normalized));
596
+ if (!normalized.startsWith(".reroute/")) {
597
+ candidates.push(join(cwd, ".reroute", normalized));
598
+ }
599
+ }
600
+ for (const abs of candidates) {
601
+ try {
602
+ if (!await Bun.file(abs).exists())
603
+ continue;
604
+ const href = pathToFileURL5(abs).href + (isWatchMode ? `?t=${Date.now()}` : "");
605
+ const mod = await import(href);
606
+ return mod;
607
+ } catch {}
608
+ }
140
609
  }
141
610
  } catch {}
142
611
  try {
@@ -156,7 +625,7 @@ async function importContentModuleForPath(pathname, clientDir, cwd, isWatchMode)
156
625
  } catch {}
157
626
  }
158
627
  const absChunk = join(chunkDir, latest);
159
- const href = pathToFileURL2(absChunk).href + (isWatchMode ? `?t=${Date.now()}` : "");
628
+ const href = pathToFileURL5(absChunk).href + (isWatchMode ? `?t=${Date.now()}` : "");
160
629
  const mod = await import(href);
161
630
  return mod;
162
631
  }
@@ -170,7 +639,7 @@ async function importContentModuleForPath(pathname, clientDir, cwd, isWatchMode)
170
639
  else if (await Bun.file(srcTs).exists())
171
640
  absSrc = srcTs;
172
641
  if (absSrc) {
173
- const href = pathToFileURL2(absSrc).href;
642
+ const href = pathToFileURL5(absSrc).href;
174
643
  const mod = await (isWatchMode ? import(`${href}?t=${Date.now()}`) : import(href));
175
644
  return mod;
176
645
  }
@@ -179,7 +648,7 @@ async function importContentModuleForPath(pathname, clientDir, cwd, isWatchMode)
179
648
  return null;
180
649
  }
181
650
 
182
- // packages/core/src/ssr/seed.ts
651
+ // packages/core/src/ssr/lib/seed.ts
183
652
  async function seedSSRModuleForPath(pathname, clientDir, cwd, isWatchMode) {
184
653
  try {
185
654
  const parts = pathname.split("/").filter(Boolean);
@@ -206,12 +675,74 @@ async function seedSSRModuleForPath(pathname, clientDir, cwd, isWatchMode) {
206
675
  } catch {}
207
676
  }
208
677
 
209
- // packages/core/src/ssr/data.ts
678
+ // packages/core/src/ssr/lib/data.ts
210
679
  async function computeSSRDataForPath(params) {
211
680
  const { pathname, clientDir, cwd, isWatchMode } = params;
681
+ const allData = {};
212
682
  try {
213
683
  await seedSSRModuleForPath(pathname, clientDir, cwd, isWatchMode);
214
684
  } catch {}
685
+ try {
686
+ const routesPath = join(cwd, ".reroute", "routes.ts");
687
+ const m = await import(pathToFileURL6(routesPath).href + (isWatchMode ? `?t=${Date.now()}` : ""));
688
+ const layouts = m?.layouts;
689
+ if (Array.isArray(layouts)) {
690
+ const matchingLayouts = layouts.filter((layout) => {
691
+ const pattern = String(layout?.pattern || "/");
692
+ if (pattern === "/")
693
+ return true;
694
+ return pathname.startsWith(pattern);
695
+ }).sort((a, b) => {
696
+ const aDepth = String(a?.pattern || "/").split("/").filter(Boolean).length;
697
+ const bDepth = String(b?.pattern || "/").split("/").filter(Boolean).length;
698
+ return aDepth - bDepth;
699
+ });
700
+ for (const layout of matchingLayouts) {
701
+ if (typeof layout?.path === "string") {
702
+ try {
703
+ const abs = join(clientDir, "routes", String(layout.path));
704
+ const mod = await import(pathToFileURL6(abs).href + (isWatchMode ? `?t=${Date.now()}` : ""));
705
+ const ssr = mod?.ssr;
706
+ const dataFn = ssr?.data;
707
+ if (typeof dataFn === "function") {
708
+ const pattern = String(layout.pattern || "/");
709
+ const layoutKey = `__layout:${pattern}`;
710
+ const rawResult = dataFn({ pathname, params: {} });
711
+ if (rawResult instanceof Promise) {
712
+ allData[layoutKey] = {
713
+ __lazy__: true,
714
+ __fetch__: `/__reroute_data?path=${encodeURIComponent(pathname)}&key=${encodeURIComponent(layoutKey)}`
715
+ };
716
+ } else if (typeof rawResult === "object" && rawResult !== null && !Array.isArray(rawResult)) {
717
+ const entries = Object.entries(rawResult);
718
+ const hasPromises = entries.some(([_, v]) => v instanceof Promise);
719
+ if (hasPromises) {
720
+ const flattened = {
721
+ __flatten__: true
722
+ };
723
+ for (const [key, value] of entries) {
724
+ if (value instanceof Promise) {
725
+ flattened[key] = {
726
+ __lazy__: true,
727
+ __fetch__: `/__reroute_data?path=${encodeURIComponent(pathname)}&key=${encodeURIComponent(key)}`
728
+ };
729
+ } else {
730
+ flattened[key] = value;
731
+ }
732
+ }
733
+ allData[layoutKey] = flattened;
734
+ } else {
735
+ allData[layoutKey] = rawResult;
736
+ }
737
+ } else {
738
+ allData[layoutKey] = rawResult;
739
+ }
740
+ }
741
+ } catch {}
742
+ }
743
+ }
744
+ }
745
+ } catch {}
215
746
  try {
216
747
  const parts = pathname.split("/").filter(Boolean);
217
748
  if (parts.length >= 2) {
@@ -220,155 +751,101 @@ async function computeSSRDataForPath(params) {
220
751
  const exp = g.__REROUTE_SSR_EXPORTS__?.[key];
221
752
  const dataFn = exp?.ssr?.data;
222
753
  if (typeof dataFn === "function") {
223
- return await dataFn({ pathname, params: { name: parts[1] } });
754
+ const rawResult = dataFn({
755
+ pathname,
756
+ params: { name: parts[1] }
757
+ });
758
+ if (rawResult instanceof Promise) {
759
+ return {
760
+ __lazy__: true,
761
+ __fetch__: `/__reroute_data?path=${encodeURIComponent(pathname)}&key=${encodeURIComponent(pathname)}`
762
+ };
763
+ } else if (typeof rawResult === "object" && rawResult !== null && !Array.isArray(rawResult)) {
764
+ const entries = Object.entries(rawResult);
765
+ const hasPromises = entries.some(([_, v]) => v instanceof Promise);
766
+ if (hasPromises) {
767
+ const flattened = { __flatten__: true };
768
+ for (const [key2, value] of entries) {
769
+ if (value instanceof Promise) {
770
+ flattened[key2] = {
771
+ __lazy__: true,
772
+ __fetch__: `/__reroute_data?path=${encodeURIComponent(pathname)}&key=${encodeURIComponent(key2)}`
773
+ };
774
+ } else {
775
+ flattened[key2] = value;
776
+ }
777
+ }
778
+ return flattened;
779
+ } else {
780
+ return rawResult;
781
+ }
782
+ } else {
783
+ return rawResult;
784
+ }
224
785
  }
225
786
  }
226
787
  } catch {}
227
788
  try {
228
789
  const routesPath = join(cwd, ".reroute", "routes.ts");
229
- const m = await import(pathToFileURL3(routesPath).href + (isWatchMode ? `?t=${Date.now()}` : ""));
790
+ const m = await import(pathToFileURL6(routesPath).href + (isWatchMode ? `?t=${Date.now()}` : ""));
230
791
  const match = typeof m.matchRoute === "function" ? m.matchRoute(pathname) : null;
231
792
  const r = match?.route;
232
793
  const paramsValue = match?.params || {};
233
794
  if (r && typeof r.path === "string") {
234
795
  try {
235
796
  const abs = join(clientDir, "routes", String(r.path));
236
- const mod = await import(pathToFileURL3(abs).href + (isWatchMode ? `?t=${Date.now()}` : ""));
797
+ const mod = await import(pathToFileURL6(abs).href + (isWatchMode ? `?t=${Date.now()}` : ""));
237
798
  const ssr = mod?.ssr;
238
799
  const dataFn = ssr?.data;
239
800
  if (typeof dataFn === "function") {
240
- return await dataFn({ pathname, params: paramsValue });
801
+ const rawResult = dataFn({
802
+ pathname,
803
+ params: paramsValue
804
+ });
805
+ if (rawResult instanceof Promise) {
806
+ allData[pathname] = {
807
+ __lazy__: true,
808
+ __fetch__: `/__reroute_data?path=${encodeURIComponent(pathname)}&key=${encodeURIComponent(pathname)}`
809
+ };
810
+ } else if (typeof rawResult === "object" && rawResult !== null && !Array.isArray(rawResult)) {
811
+ const obj = rawResult;
812
+ const entries = Object.entries(obj);
813
+ const hasPromises = entries.some(([_, v]) => v instanceof Promise);
814
+ if (hasPromises) {
815
+ const flattened = { __flatten__: true };
816
+ for (const [key, value] of entries) {
817
+ if (value instanceof Promise) {
818
+ flattened[key] = {
819
+ __lazy__: true,
820
+ __fetch__: `/__reroute_data?path=${encodeURIComponent(pathname)}&key=${encodeURIComponent(key)}`
821
+ };
822
+ } else {
823
+ flattened[key] = value;
824
+ }
825
+ }
826
+ allData[pathname] = flattened;
827
+ } else {
828
+ allData[pathname] = rawResult;
829
+ }
830
+ } else {
831
+ allData[pathname] = rawResult;
832
+ }
241
833
  }
242
834
  } catch {}
243
835
  }
244
836
  } catch {}
245
- return null;
246
- }
247
- // packages/core/src/ssr/render.ts
248
- import { readdir as readdir3, stat as stat4 } from "node:fs/promises";
249
- import { pathToFileURL as pathToFileURL4 } from "node:url";
250
-
251
- // node_modules/.bun/dedent@1.7.0/node_modules/dedent/dist/dedent.mjs
252
- function ownKeys(object, enumerableOnly) {
253
- var keys = Object.keys(object);
254
- if (Object.getOwnPropertySymbols) {
255
- var symbols = Object.getOwnPropertySymbols(object);
256
- enumerableOnly && (symbols = symbols.filter(function(sym) {
257
- return Object.getOwnPropertyDescriptor(object, sym).enumerable;
258
- })), keys.push.apply(keys, symbols);
259
- }
260
- return keys;
261
- }
262
- function _objectSpread(target) {
263
- for (var i = 1;i < arguments.length; i++) {
264
- var source = arguments[i] != null ? arguments[i] : {};
265
- i % 2 ? ownKeys(Object(source), true).forEach(function(key) {
266
- _defineProperty(target, key, source[key]);
267
- }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function(key) {
268
- Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
269
- });
837
+ const dataKeys = Object.keys(allData);
838
+ if (dataKeys.length > 1) {
839
+ return {
840
+ __reroute_multiple__: true,
841
+ ...allData
842
+ };
843
+ } else if (dataKeys.length === 1) {
844
+ return allData[dataKeys[0]];
270
845
  }
271
- return target;
846
+ return null;
272
847
  }
273
- function _defineProperty(obj, key, value) {
274
- key = _toPropertyKey(key);
275
- if (key in obj) {
276
- Object.defineProperty(obj, key, { value, enumerable: true, configurable: true, writable: true });
277
- } else {
278
- obj[key] = value;
279
- }
280
- return obj;
281
- }
282
- function _toPropertyKey(arg) {
283
- var key = _toPrimitive(arg, "string");
284
- return typeof key === "symbol" ? key : String(key);
285
- }
286
- function _toPrimitive(input, hint) {
287
- if (typeof input !== "object" || input === null)
288
- return input;
289
- var prim = input[Symbol.toPrimitive];
290
- if (prim !== undefined) {
291
- var res = prim.call(input, hint || "default");
292
- if (typeof res !== "object")
293
- return res;
294
- throw new TypeError("@@toPrimitive must return a primitive value.");
295
- }
296
- return (hint === "string" ? String : Number)(input);
297
- }
298
- var dedent = createDedent({});
299
- var dedent_default = dedent;
300
- function createDedent(options) {
301
- dedent2.withOptions = (newOptions) => createDedent(_objectSpread(_objectSpread({}, options), newOptions));
302
- return dedent2;
303
- function dedent2(strings, ...values) {
304
- const raw = typeof strings === "string" ? [strings] : strings.raw;
305
- const {
306
- alignValues = false,
307
- escapeSpecialCharacters = Array.isArray(strings),
308
- trimWhitespace = true
309
- } = options;
310
- let result = "";
311
- for (let i = 0;i < raw.length; i++) {
312
- let next = raw[i];
313
- if (escapeSpecialCharacters) {
314
- next = next.replace(/\\\n[ \t]*/g, "").replace(/\\`/g, "`").replace(/\\\$/g, "$").replace(/\\\{/g, "{");
315
- }
316
- result += next;
317
- if (i < values.length) {
318
- const value = alignValues ? alignValue(values[i], result) : values[i];
319
- result += value;
320
- }
321
- }
322
- const lines = result.split(`
323
- `);
324
- let mindent = null;
325
- for (const l of lines) {
326
- const m = l.match(/^(\s+)\S+/);
327
- if (m) {
328
- const indent = m[1].length;
329
- if (!mindent) {
330
- mindent = indent;
331
- } else {
332
- mindent = Math.min(mindent, indent);
333
- }
334
- }
335
- }
336
- if (mindent !== null) {
337
- const m = mindent;
338
- result = lines.map((l) => l[0] === " " || l[0] === "\t" ? l.slice(m) : l).join(`
339
- `);
340
- }
341
- if (trimWhitespace) {
342
- result = result.trim();
343
- }
344
- if (escapeSpecialCharacters) {
345
- result = result.replace(/\\n/g, `
346
- `);
347
- }
348
- return result;
349
- }
350
- }
351
- function alignValue(value, precedingText) {
352
- if (typeof value !== "string" || !value.includes(`
353
- `)) {
354
- return value;
355
- }
356
- const currentLine = precedingText.slice(precedingText.lastIndexOf(`
357
- `) + 1);
358
- const indentMatch = currentLine.match(/^(\s+)/);
359
- if (indentMatch) {
360
- const indent = indentMatch[1];
361
- return value.replace(/\n/g, `
362
- ${indent}`);
363
- }
364
- return value;
365
- }
366
-
367
- // packages/core/src/ssr/render.ts
368
- import { cloneElement } from "react";
369
- import { renderToString } from "react-dom/server";
370
-
371
- // packages/core/src/template/html.ts
848
+ // packages/core/src/ssr/lib/html.ts
372
849
  async function loadIndexHtml(clientDir) {
373
850
  const templatePath = join(clientDir, "index.html");
374
851
  try {
@@ -426,12 +903,30 @@ function applyIndexTemplate(templateHtml, appHtml, {
426
903
  </head>`);
427
904
  }
428
905
  html = html.replace(/<script\b[^>]*src\s*=\s*["'][^"']+\.(ts|tsx)(?:[?#][^"']*)?["'][^>]*>\s*<\/script>/gi, "");
906
+ let cleanAppHtml = appHtml;
907
+ const preloadLinks = [];
908
+ const linkPrefixRegex = /^(\s*(?:<link\b[^>]*>\s*)+)/;
909
+ const prefixMatch = appHtml.match(linkPrefixRegex);
910
+ if (prefixMatch?.[1]) {
911
+ const linkMatches = prefixMatch[1].match(/<link\b[^>]*>/g);
912
+ if (linkMatches) {
913
+ preloadLinks.push(...linkMatches);
914
+ cleanAppHtml = appHtml.slice(prefixMatch[1].length);
915
+ }
916
+ }
917
+ if (preloadLinks.length > 0) {
918
+ const preloadHtml = preloadLinks.join(`
919
+ `);
920
+ html = html.replace(/<\/head>/i, ` ${preloadHtml}
921
+ </head>`);
922
+ }
429
923
  const appIdEscaped = appId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
430
924
  const rootDivRegex = new RegExp(`(<div[^>]*\\bid=(\\"|')${appIdEscaped}\\2[^>]*>)([\\s\\S]*?)(<\\/div>)`, "i");
431
925
  if (rootDivRegex.test(html)) {
432
- html = html.replace(rootDivRegex, `$1${appHtml}$4`);
926
+ const escapedAppHtml = cleanAppHtml.replace(/\$/g, "$$$$");
927
+ html = html.replace(rootDivRegex, `$1${escapedAppHtml}$4`);
433
928
  } else {
434
- html = html.replace(/<\/body>/i, ` <div id="${appId}">${appHtml}</div>
929
+ html = html.replace(/<\/body>/i, ` <div id="${appId}">${cleanAppHtml}</div>
435
930
  </body>`);
436
931
  }
437
932
  if (hydrationScript) {
@@ -440,33 +935,144 @@ function applyIndexTemplate(templateHtml, appHtml, {
440
935
  }
441
936
  return html;
442
937
  }
443
- // packages/core/src/ssr/render.ts
444
- async function renderSSRDocument(options) {
445
- const {
446
- pathname,
447
- rootComponent,
448
- clientDir,
449
- cwd,
450
- isWatchMode,
451
- bundleUrl,
452
- head = "",
453
- lang = "en",
454
- appId = "root",
455
- minify = false
456
- } = options;
457
- const scripts = [bundleUrl];
458
- let hydrationScript = "";
459
- let extraHead = "";
938
+ // packages/core/src/ssr/lib/metadata.ts
939
+ import { pathToFileURL as pathToFileURL7 } from "node:url";
940
+ async function extractPageMetadata(pathname, clientDir, cwd, isWatchMode, currentStatusOverride) {
941
+ let perPageHead = "";
942
+ let pageLang;
943
+ let statusOverride = currentStatusOverride;
460
944
  try {
461
- if (typeof bundleUrl === "string" && bundleUrl.endsWith(".js")) {
462
- extraHead += `
463
- <link rel="modulepreload" href="${bundleUrl}" />`;
945
+ const routesPath = join(cwd, ".reroute", "routes.ts");
946
+ const m = await import(pathToFileURL7(routesPath).href + (isWatchMode ? `?t=${Date.now()}` : ""));
947
+ const layouts = m?.layouts;
948
+ if (Array.isArray(layouts)) {
949
+ const matchingLayouts = layouts.filter((layout) => {
950
+ const pattern = String(layout?.pattern || "/");
951
+ if (pattern === "/")
952
+ return true;
953
+ return pathname.startsWith(pattern);
954
+ }).sort((a, b) => {
955
+ const aDepth = String(a?.pattern || "/").split("/").filter(Boolean).length;
956
+ const bDepth = String(b?.pattern || "/").split("/").filter(Boolean).length;
957
+ return aDepth - bDepth;
958
+ });
959
+ for (const layout of matchingLayouts) {
960
+ if (typeof layout?.path === "string") {
961
+ try {
962
+ const abs = join(clientDir, "routes", String(layout.path));
963
+ const mod = await import(pathToFileURL7(abs).href + (isWatchMode ? `?t=${Date.now()}` : ""));
964
+ const meta = mod?.meta;
965
+ const ssr = mod?.ssr;
966
+ if (meta)
967
+ perPageHead += buildHeadFromMeta(meta);
968
+ if (ssr) {
969
+ const ssrHead = typeof ssr.head === "string" ? ssr.head : Array.isArray(ssr.head) ? String(ssr.head.join(`
970
+ `)) : "";
971
+ if (ssrHead)
972
+ perPageHead += `
973
+ ${ssrHead}`;
974
+ if (typeof ssr.lang === "string" && ssr.lang.trim() && !pageLang) {
975
+ pageLang = ssr.lang.trim();
976
+ }
977
+ }
978
+ } catch {}
979
+ }
980
+ }
464
981
  }
465
982
  } catch {}
466
- let statusOverride;
467
983
  try {
468
- globalThis.__REROUTE_SSR_ACCESSED__ = {};
984
+ const parts = pathname.split("/").filter(Boolean);
985
+ if (parts.length >= 2) {
986
+ const key = `${parts[0]}:${parts[1]}`;
987
+ const g = globalThis;
988
+ const exp = g.__REROUTE_SSR_EXPORTS__?.[key];
989
+ const meta = exp?.meta;
990
+ const ssr = exp?.ssr;
991
+ perPageHead += buildHeadFromMeta(meta);
992
+ const ssrHead = typeof ssr?.head === "string" ? ssr.head : Array.isArray(ssr?.head) ? String(ssr.head.join(`
993
+ `)) : "";
994
+ if (ssrHead)
995
+ perPageHead += `
996
+ ${ssrHead}`;
997
+ if (typeof ssr?.lang === "string" && ssr.lang.trim()) {
998
+ pageLang = ssr.lang.trim();
999
+ }
1000
+ }
1001
+ } catch {}
1002
+ try {
1003
+ const routesPath = join(cwd, ".reroute", "routes.ts");
1004
+ const m = await import(pathToFileURL7(routesPath).href + (isWatchMode ? `?t=${Date.now()}` : ""));
1005
+ const match = typeof m.matchRoute === "function" ? m.matchRoute(pathname) : null;
1006
+ const r = match?.route;
1007
+ if (!r) {
1008
+ statusOverride = statusOverride || 404;
1009
+ }
1010
+ if (r && typeof r.path === "string") {
1011
+ try {
1012
+ const abs = join(clientDir, "routes", String(r.path));
1013
+ const mod = await import(pathToFileURL7(abs).href + (isWatchMode ? `?t=${Date.now()}` : ""));
1014
+ const meta = mod?.meta;
1015
+ const ssr = mod?.ssr;
1016
+ if (meta)
1017
+ perPageHead += buildHeadFromMeta(meta);
1018
+ if (ssr) {
1019
+ const ssrHead = typeof ssr.head === "string" ? ssr.head : Array.isArray(ssr.head) ? String(ssr.head.join(`
1020
+ `)) : "";
1021
+ if (ssrHead)
1022
+ perPageHead += `
1023
+ ${ssrHead}`;
1024
+ if (typeof ssr.lang === "string" && ssr.lang.trim()) {
1025
+ pageLang = ssr.lang.trim();
1026
+ }
1027
+ }
1028
+ } catch {}
1029
+ } else {
1030
+ try {
1031
+ const list = m?.notFoundRoutes;
1032
+ if (Array.isArray(list)) {
1033
+ let chosen;
1034
+ let bestLen = -1;
1035
+ for (const nf of list) {
1036
+ const pat = String(nf?.pattern || "/");
1037
+ if (!pathname.startsWith(pat))
1038
+ continue;
1039
+ const len = pat.split("/").filter(Boolean).length;
1040
+ if (len >= bestLen) {
1041
+ bestLen = len;
1042
+ chosen = nf;
1043
+ }
1044
+ }
1045
+ if (chosen && typeof chosen.path === "string") {
1046
+ try {
1047
+ const abs = join(clientDir, "routes", String(chosen.path));
1048
+ const mod = await import(pathToFileURL7(abs).href + (isWatchMode ? `?t=${Date.now()}` : ""));
1049
+ const meta = mod?.meta;
1050
+ const ssr = mod?.ssr;
1051
+ if (meta)
1052
+ perPageHead += buildHeadFromMeta(meta);
1053
+ const ssrHead = typeof ssr?.head === "string" ? ssr.head : Array.isArray(ssr?.head) ? String(ssr.head.join(`
1054
+ `)) : "";
1055
+ if (ssrHead)
1056
+ perPageHead += `
1057
+ ${ssrHead}`;
1058
+ if (typeof ssr?.lang === "string" && ssr.lang?.trim()) {
1059
+ pageLang = String(ssr.lang).trim();
1060
+ }
1061
+ } catch {}
1062
+ }
1063
+ }
1064
+ } catch {}
1065
+ }
469
1066
  } catch {}
1067
+ return { perPageHead, pageLang, statusOverride };
1068
+ }
1069
+ // packages/core/src/ssr/lib/preload.ts
1070
+ import { readdir as readdir3, stat as stat4 } from "node:fs/promises";
1071
+ import { pathToFileURL as pathToFileURL8 } from "node:url";
1072
+ async function preloadContentModule(pathname, clientDir, cwd, isWatchMode) {
1073
+ let extraHead = "";
1074
+ let hydrationScript = "";
1075
+ let statusOverride;
470
1076
  try {
471
1077
  const parts = pathname.split("/").filter(Boolean);
472
1078
  if (parts.length >= 2) {
@@ -486,7 +1092,7 @@ async function renderSSRDocument(options) {
486
1092
  }
487
1093
  try {
488
1094
  const registryPath = join(cwd, ".reroute", "content.ts");
489
- const reg = await import(pathToFileURL4(registryPath).href + (isWatchMode ? `?t=${Date.now()}` : ""));
1095
+ const reg = await import(pathToFileURL8(registryPath).href + (isWatchMode ? `?t=${Date.now()}` : ""));
490
1096
  const get = reg?.getContentEntry;
491
1097
  const entry = typeof get === "function" ? get(collection, name) : undefined;
492
1098
  const moduleUrl = entry?.module;
@@ -540,63 +1146,89 @@ async function renderSSRDocument(options) {
540
1146
  }
541
1147
  }
542
1148
  } catch {}
543
- await seedSSRModuleForPath(pathname, clientDir, cwd, isWatchMode);
544
- const __SSR_DATA__ = {};
1149
+ return { modulePath: undefined, extraHead, hydrationScript, statusOverride };
1150
+ }
1151
+ function resetSSRAccessTracking() {
545
1152
  try {
546
- try {
547
- const parts = pathname.split("/").filter(Boolean);
548
- if (parts.length >= 2) {
549
- const g = globalThis;
550
- const key = `${parts[0]}:${parts[1]}`;
551
- const exp = g.__REROUTE_SSR_EXPORTS__?.[key];
552
- const dataFn = exp?.ssr?.data;
553
- if (typeof dataFn === "function") {
554
- const out = await dataFn({
555
- pathname,
556
- params: { name: parts[1] }
557
- });
558
- __SSR_DATA__[pathname] = out;
559
- }
560
- }
561
- } catch {}
562
- try {
563
- const routesPath = join(cwd, ".reroute", "routes.ts");
564
- const m = await import(pathToFileURL4(routesPath).href + (isWatchMode ? `?t=${Date.now()}` : ""));
565
- const match = typeof m.matchRoute === "function" ? m.matchRoute(pathname) : null;
566
- const r = match?.route;
567
- const params = match?.params || {};
568
- if (r && typeof r.path === "string") {
569
- try {
570
- const abs = join(clientDir, "routes", String(r.path));
571
- const mod = await import(pathToFileURL4(abs).href + (isWatchMode ? `?t=${Date.now()}` : ""));
572
- const ssr = mod?.ssr;
573
- const dataFn = ssr?.data;
574
- if (typeof dataFn === "function") {
575
- const out = await dataFn({ pathname, params });
576
- __SSR_DATA__[pathname] = out;
577
- }
578
- } catch {}
579
- }
580
- } catch {}
1153
+ globalThis.__REROUTE_SSR_ACCESSED__ = {};
581
1154
  } catch {}
1155
+ }
1156
+ function createBundlePreload(bundleUrl) {
582
1157
  try {
583
- globalThis.__REROUTE_DATA__ = __SSR_DATA__;
1158
+ if (typeof bundleUrl === "string" && bundleUrl.endsWith(".js")) {
1159
+ return `
1160
+ <link rel="modulepreload" href="${bundleUrl}" />`;
1161
+ }
584
1162
  } catch {}
585
- let __byCollectionForSSR = {};
586
- try {
587
- const p = join(cwd, ".reroute", "content.ts");
588
- const mod = await import(pathToFileURL4(p).href + (isWatchMode ? `?t=${Date.now()}` : ""));
589
- __byCollectionForSSR = mod?.byCollection || {};
1163
+ return "";
1164
+ }
1165
+ // packages/core/src/ssr/lib/scripts.ts
1166
+ function escapeJsonForScript(json) {
1167
+ return json.replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026");
1168
+ }
1169
+ function createDataScript(ssrData, errorMsg) {
1170
+ let script = "";
1171
+ if (ssrData && Object.keys(ssrData).length > 0) {
590
1172
  try {
591
- globalThis.__REROUTE_COLLECTIONS__ = __byCollectionForSSR;
592
- } catch {}
593
- } catch {}
594
- const componentWithPathname = cloneElement(rootComponent, {
595
- pathname
596
- });
597
- const appHtml = renderToString(componentWithPathname);
598
- const baseTemplate = await loadIndexHtml(clientDir);
599
- let inlineStyleTag = "";
1173
+ const dataJson = escapeJsonForScript(JSON.stringify(ssrData));
1174
+ script += `<script>(function(){try{var w=(typeof window!=='undefined'?window:globalThis);w.__REROUTE_DATA__=Object.assign({},w.__REROUTE_DATA__||{},${dataJson});try{w.dispatchEvent(new CustomEvent('__reroute_data__',{detail:{keys:Object.keys(${dataJson})}}));}catch(e){}}catch(e){}})();</script>`;
1175
+ } catch (error) {
1176
+ console.error("[reroute] Failed to serialize SSR data:", error);
1177
+ }
1178
+ }
1179
+ if (errorMsg) {
1180
+ const escapedMsg = JSON.stringify(errorMsg);
1181
+ script += `<script>(function(){try{console.warn('%c[reroute] SSR Data Error','background: #ff5555; color: white; padding: 2px 6px; border-radius: 3px;',${escapedMsg});}catch(e){}})();</script>`;
1182
+ }
1183
+ return script;
1184
+ }
1185
+ function createCollectionScript(subset, partial) {
1186
+ const subsetJson = escapeJsonForScript(JSON.stringify(subset));
1187
+ const partialJson = escapeJsonForScript(JSON.stringify(partial));
1188
+ return `<script>(function(){try{var w=(typeof window!=='undefined'?window:globalThis);w.__REROUTE_COLLECTIONS__=w.__REROUTE_COLLECTIONS__||{};Object.assign(w.__REROUTE_COLLECTIONS__,${subsetJson});w.__REROUTE_COLLECTIONS_PARTIAL__=w.__REROUTE_COLLECTIONS_PARTIAL__||{};Object.assign(w.__REROUTE_COLLECTIONS_PARTIAL__,${partialJson});}catch(e){}})();</script>`;
1189
+ }
1190
+ function processCollections(accessedCollections, byCollectionForSSR) {
1191
+ const sortByDate = (order) => (a, b) => {
1192
+ const da = a?.meta?.date ? Date.parse(String(a.meta.date)) : 0;
1193
+ const db = b?.meta?.date ? Date.parse(String(b.meta.date)) : 0;
1194
+ return order === "asc" ? da - db : db - da;
1195
+ };
1196
+ const usage = {};
1197
+ if (accessedCollections && typeof accessedCollections.forEach === "function") {
1198
+ accessedCollections.forEach((c) => {
1199
+ if (typeof c === "string")
1200
+ usage[c] = { limit: Number.POSITIVE_INFINITY, sort: "custom" };
1201
+ });
1202
+ } else if (accessedCollections && typeof accessedCollections === "object") {
1203
+ for (const [k, v] of Object.entries(accessedCollections)) {
1204
+ const lim = typeof v?.limit === "number" ? v.limit : Number.POSITIVE_INFINITY;
1205
+ const s = typeof v?.sort === "string" ? v.sort : "custom";
1206
+ usage[k] = { limit: lim, sort: s };
1207
+ }
1208
+ }
1209
+ const collections = Object.keys(usage);
1210
+ const subset = {};
1211
+ const partial = {};
1212
+ for (const c of collections) {
1213
+ const conf = usage[c];
1214
+ const full = byCollectionForSSR?.[c] || [];
1215
+ let arr = full;
1216
+ if (Number.isFinite(conf.limit) && conf.limit > 0 && conf.sort !== "custom") {
1217
+ if (conf.sort === "date-desc") {
1218
+ arr = [...full].sort(sortByDate("desc")).slice(0, conf.limit);
1219
+ } else if (conf.sort === "date-asc") {
1220
+ arr = [...full].sort(sortByDate("asc")).slice(0, conf.limit);
1221
+ } else if (conf.sort === "none") {
1222
+ arr = full.slice(0, conf.limit);
1223
+ }
1224
+ }
1225
+ subset[c] = arr;
1226
+ partial[c] = Array.isArray(arr) && Array.isArray(full) ? arr.length < full.length : false;
1227
+ }
1228
+ return { subset, partial };
1229
+ }
1230
+ // packages/core/src/ssr/lib/styles.ts
1231
+ async function inlineTailwindCSS(clientDir, minify = false) {
600
1232
  try {
601
1233
  const candidates = [
602
1234
  join(clientDir, "..", ".reroute", "theme.css"),
@@ -611,169 +1243,204 @@ async function renderSSRDocument(options) {
611
1243
  break;
612
1244
  }
613
1245
  }
614
- if (cssPath) {
615
- let css = await Bun.file(cssPath).text();
616
- if (minify) {
617
- css = css.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\s+/g, " ").replace(/\s*([{}:;,>+~])\s*/g, "$1").replace(/;}/g, "}").trim();
618
- }
619
- inlineStyleTag = `<style data-reroute="tailwind">${css}</style>`;
1246
+ if (!cssPath)
1247
+ return "";
1248
+ let css = await Bun.file(cssPath).text();
1249
+ if (minify) {
1250
+ css = css.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\s+/g, " ").replace(/\s*([{}:;,>+~])\s*/g, "$1").replace(/;}/g, "}").trim();
620
1251
  }
621
- } catch {}
622
- try {
623
- const g = globalThis;
624
- const acc = g.__REROUTE_SSR_ACCESSED__;
625
- const sortByDate = (order) => (a, b) => {
626
- const da = a?.meta?.date ? Date.parse(String(a.meta.date)) : 0;
627
- const db = b?.meta?.date ? Date.parse(String(b.meta.date)) : 0;
628
- return order === "asc" ? da - db : db - da;
629
- };
630
- const usage = {};
631
- if (acc && typeof acc.forEach === "function") {
632
- acc.forEach((c) => {
633
- if (typeof c === "string")
634
- usage[c] = { limit: Number.POSITIVE_INFINITY, sort: "custom" };
635
- });
636
- } else if (acc && typeof acc === "object") {
637
- for (const [k, v] of Object.entries(acc)) {
638
- const lim = typeof v?.limit === "number" ? v.limit : Number.POSITIVE_INFINITY;
639
- const s = typeof v?.sort === "string" ? v.sort : "custom";
640
- usage[k] = { limit: lim, sort: s };
1252
+ return `<style data-reroute="tailwind">${css}</style>`;
1253
+ } catch {
1254
+ return "";
1255
+ }
1256
+ }
1257
+ // packages/core/src/ssr/lib/template.ts
1258
+ function splitTemplate(html, appId = "root") {
1259
+ const appIdEscaped = appId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1260
+ const rootDivRegex = new RegExp(`(<div[^>]*\\bid=["']${appIdEscaped}["'][^>]*>)([\\s\\S]*?)(<\\/div>)`, "i");
1261
+ const match = html.match(rootDivRegex);
1262
+ if (!match || match.index === undefined) {
1263
+ throw new Error(`Could not find app root div with id="${appId}"`);
1264
+ }
1265
+ const [fullMatch, rootStart, , rootEnd] = match;
1266
+ const matchIndex = match.index;
1267
+ return {
1268
+ htmlHead: html.substring(0, matchIndex),
1269
+ rootStart,
1270
+ rootEnd,
1271
+ htmlTail: html.substring(matchIndex + fullMatch.length)
1272
+ };
1273
+ }
1274
+ // node_modules/.bun/dedent@1.7.0/node_modules/dedent/dist/dedent.mjs
1275
+ function ownKeys(object, enumerableOnly) {
1276
+ var keys = Object.keys(object);
1277
+ if (Object.getOwnPropertySymbols) {
1278
+ var symbols = Object.getOwnPropertySymbols(object);
1279
+ enumerableOnly && (symbols = symbols.filter(function(sym) {
1280
+ return Object.getOwnPropertyDescriptor(object, sym).enumerable;
1281
+ })), keys.push.apply(keys, symbols);
1282
+ }
1283
+ return keys;
1284
+ }
1285
+ function _objectSpread(target) {
1286
+ for (var i = 1;i < arguments.length; i++) {
1287
+ var source = arguments[i] != null ? arguments[i] : {};
1288
+ i % 2 ? ownKeys(Object(source), true).forEach(function(key) {
1289
+ _defineProperty(target, key, source[key]);
1290
+ }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function(key) {
1291
+ Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
1292
+ });
1293
+ }
1294
+ return target;
1295
+ }
1296
+ function _defineProperty(obj, key, value) {
1297
+ key = _toPropertyKey(key);
1298
+ if (key in obj) {
1299
+ Object.defineProperty(obj, key, { value, enumerable: true, configurable: true, writable: true });
1300
+ } else {
1301
+ obj[key] = value;
1302
+ }
1303
+ return obj;
1304
+ }
1305
+ function _toPropertyKey(arg) {
1306
+ var key = _toPrimitive(arg, "string");
1307
+ return typeof key === "symbol" ? key : String(key);
1308
+ }
1309
+ function _toPrimitive(input, hint) {
1310
+ if (typeof input !== "object" || input === null)
1311
+ return input;
1312
+ var prim = input[Symbol.toPrimitive];
1313
+ if (prim !== undefined) {
1314
+ var res = prim.call(input, hint || "default");
1315
+ if (typeof res !== "object")
1316
+ return res;
1317
+ throw new TypeError("@@toPrimitive must return a primitive value.");
1318
+ }
1319
+ return (hint === "string" ? String : Number)(input);
1320
+ }
1321
+ var dedent = createDedent({});
1322
+ var dedent_default = dedent;
1323
+ function createDedent(options) {
1324
+ dedent2.withOptions = (newOptions) => createDedent(_objectSpread(_objectSpread({}, options), newOptions));
1325
+ return dedent2;
1326
+ function dedent2(strings, ...values) {
1327
+ const raw = typeof strings === "string" ? [strings] : strings.raw;
1328
+ const {
1329
+ alignValues = false,
1330
+ escapeSpecialCharacters = Array.isArray(strings),
1331
+ trimWhitespace = true
1332
+ } = options;
1333
+ let result = "";
1334
+ for (let i = 0;i < raw.length; i++) {
1335
+ let next = raw[i];
1336
+ if (escapeSpecialCharacters) {
1337
+ next = next.replace(/\\\n[ \t]*/g, "").replace(/\\`/g, "`").replace(/\\\$/g, "$").replace(/\\\{/g, "{");
1338
+ }
1339
+ result += next;
1340
+ if (i < values.length) {
1341
+ const value = alignValues ? alignValue(values[i], result) : values[i];
1342
+ result += value;
641
1343
  }
642
1344
  }
643
- const collections = Object.keys(usage);
644
- if (collections.length) {
645
- const subset = {};
646
- const partial = {};
647
- for (const c of collections) {
648
- const conf = usage[c];
649
- const full = __byCollectionForSSR?.[c] || [];
650
- let arr = full;
651
- if (Number.isFinite(conf.limit) && conf.limit > 0 && conf.sort !== "custom") {
652
- if (conf.sort === "date-desc") {
653
- arr = [...full].sort(sortByDate("desc")).slice(0, conf.limit);
654
- } else if (conf.sort === "date-asc") {
655
- arr = [...full].sort(sortByDate("asc")).slice(0, conf.limit);
656
- } else if (conf.sort === "none") {
657
- arr = full.slice(0, conf.limit);
658
- }
1345
+ const lines = result.split(`
1346
+ `);
1347
+ let mindent = null;
1348
+ for (const l of lines) {
1349
+ const m = l.match(/^(\s+)\S+/);
1350
+ if (m) {
1351
+ const indent = m[1].length;
1352
+ if (!mindent) {
1353
+ mindent = indent;
1354
+ } else {
1355
+ mindent = Math.min(mindent, indent);
659
1356
  }
660
- subset[c] = arr;
661
- partial[c] = Array.isArray(arr) && Array.isArray(full) ? arr.length < full.length : false;
662
1357
  }
663
- const subsetJson = JSON.stringify(subset);
664
- const partialJson = JSON.stringify(partial);
665
- hydrationScript += `
666
- <script>
667
- (function(){ try {
668
- var w = (typeof window!== 'undefined'? window : globalThis);
669
- w.__REROUTE_COLLECTIONS__ = w.__REROUTE_COLLECTIONS__ || {};
670
- Object.assign(w.__REROUTE_COLLECTIONS__, ${subsetJson});
671
- w.__REROUTE_COLLECTIONS_PARTIAL__ = w.__REROUTE_COLLECTIONS_PARTIAL__ || {};
672
- Object.assign(w.__REROUTE_COLLECTIONS_PARTIAL__, ${partialJson});
673
- } catch(e){} })();
674
- </script>`;
675
1358
  }
676
- } catch {}
677
- try {
678
- const seededJson = JSON.stringify(__SSR_DATA__);
679
- hydrationScript += `
680
- <script>
681
- (function(){ try {
682
- var w = (typeof window!== 'undefined'? window : globalThis);
683
- w.__REROUTE_DATA__ = Object.assign({}, w.__REROUTE_DATA__ || {}, ${seededJson});
684
- } catch(e){} })();
685
- </script>`;
686
- } catch {}
687
- hydrationScript += scripts.map((src) => `<script type="module" src="${src}"></script>`).join("");
688
- if (isWatchMode) {
689
- hydrationScript += `<script type="module" src="/__reroute_watch.js"></script>`;
1359
+ if (mindent !== null) {
1360
+ const m = mindent;
1361
+ result = lines.map((l) => l[0] === " " || l[0] === "\t" ? l.slice(m) : l).join(`
1362
+ `);
1363
+ }
1364
+ if (trimWhitespace) {
1365
+ result = result.trim();
1366
+ }
1367
+ if (escapeSpecialCharacters) {
1368
+ result = result.replace(/\\n/g, `
1369
+ `);
1370
+ }
1371
+ return result;
690
1372
  }
691
- let perPageHead = "";
692
- let pageLang;
1373
+ }
1374
+ function alignValue(value, precedingText) {
1375
+ if (typeof value !== "string" || !value.includes(`
1376
+ `)) {
1377
+ return value;
1378
+ }
1379
+ const currentLine = precedingText.slice(precedingText.lastIndexOf(`
1380
+ `) + 1);
1381
+ const indentMatch = currentLine.match(/^(\s+)/);
1382
+ if (indentMatch) {
1383
+ const indent = indentMatch[1];
1384
+ return value.replace(/\n/g, `
1385
+ ${indent}`);
1386
+ }
1387
+ return value;
1388
+ }
1389
+
1390
+ // packages/core/src/ssr/render.ts
1391
+ import { cloneElement } from "react";
1392
+ import { renderToString } from "react-dom/server";
1393
+ async function renderSSRDocument(options) {
1394
+ const {
1395
+ pathname,
1396
+ rootComponent,
1397
+ clientDir,
1398
+ cwd,
1399
+ isWatchMode,
1400
+ bundleUrl,
1401
+ head = "",
1402
+ lang = "en",
1403
+ appId = "root",
1404
+ minify = false,
1405
+ maxAge = 0
1406
+ } = options;
1407
+ const scripts2 = [bundleUrl];
1408
+ resetSSRAccessTracking();
1409
+ const bundlePreload = createBundlePreload(bundleUrl);
1410
+ const preloadResult = await preloadContentModule(pathname, clientDir, cwd, isWatchMode);
1411
+ const extraHead = bundlePreload + preloadResult.extraHead;
1412
+ let hydrationScript = preloadResult.hydrationScript;
1413
+ let statusOverride = preloadResult.statusOverride;
1414
+ await seedSSRModuleForPath(pathname, clientDir, cwd, isWatchMode);
1415
+ const { data: __SSR_DATA__, error: __SSR_DATA_ERROR__ } = await computeSSRData(pathname, clientDir, cwd, isWatchMode, { maxAge });
1416
+ setGlobalSSRData(__SSR_DATA__);
1417
+ const __byCollectionForSSR = await loadCollections(cwd, isWatchMode);
1418
+ setGlobalCollections(__byCollectionForSSR);
1419
+ const componentWithPathname = cloneElement(rootComponent, {
1420
+ pathname
1421
+ });
1422
+ const appHtml = renderToString(componentWithPathname);
1423
+ const baseTemplate = await loadIndexHtml(clientDir);
1424
+ const inlineStyleTag = await inlineTailwindCSS(clientDir, minify);
693
1425
  try {
694
- const parts = pathname.split("/").filter(Boolean);
695
- if (parts.length >= 2) {
696
- const key = `${parts[0]}:${parts[1]}`;
697
- const g = globalThis;
698
- const exp = g.__REROUTE_SSR_EXPORTS__?.[key];
699
- const meta = exp?.meta;
700
- const ssr = exp?.ssr;
701
- perPageHead += buildHeadFromMeta(meta);
702
- const ssrHead = typeof ssr?.head === "string" ? ssr.head : Array.isArray(ssr?.head) ? String(ssr.head.join(`
703
- `)) : "";
704
- if (ssrHead)
705
- perPageHead += `
706
- ${ssrHead}`;
707
- if (typeof ssr?.lang === "string" && ssr.lang.trim()) {
708
- pageLang = ssr.lang.trim();
1426
+ const g = globalThis;
1427
+ const acc = g.__REROUTE_SSR_ACCESSED__;
1428
+ if (acc) {
1429
+ const { subset, partial } = processCollections(acc, __byCollectionForSSR);
1430
+ if (Object.keys(subset).length > 0) {
1431
+ hydrationScript += createCollectionScript(subset, partial);
709
1432
  }
710
1433
  }
711
1434
  } catch {}
712
- try {
713
- const routesPath = join(cwd, ".reroute", "routes.ts");
714
- const m = await import(pathToFileURL4(routesPath).href + (isWatchMode ? `?t=${Date.now()}` : ""));
715
- const match = typeof m.matchRoute === "function" ? m.matchRoute(pathname) : null;
716
- const r = match?.route;
717
- if (!r) {
718
- statusOverride = statusOverride || 404;
719
- }
720
- if (r && typeof r.path === "string") {
721
- try {
722
- const abs = join(clientDir, "routes", String(r.path));
723
- const mod = await import(pathToFileURL4(abs).href + (isWatchMode ? `?t=${Date.now()}` : ""));
724
- const meta = mod?.meta;
725
- const ssr = mod?.ssr;
726
- if (meta)
727
- perPageHead += buildHeadFromMeta(meta);
728
- if (ssr) {
729
- const ssrHead = typeof ssr.head === "string" ? ssr.head : Array.isArray(ssr.head) ? String(ssr.head.join(`
730
- `)) : "";
731
- if (ssrHead)
732
- perPageHead += `
733
- ${ssrHead}`;
734
- if (typeof ssr.lang === "string" && ssr.lang.trim()) {
735
- pageLang = ssr.lang.trim();
736
- }
737
- }
738
- } catch {}
739
- } else {
740
- try {
741
- const list = m?.notFoundRoutes;
742
- if (Array.isArray(list)) {
743
- let chosen;
744
- let bestLen = -1;
745
- for (const nf of list) {
746
- const pat = String(nf?.pattern || "/");
747
- if (!pathname.startsWith(pat))
748
- continue;
749
- const len = pat.split("/").filter(Boolean).length;
750
- if (len >= bestLen) {
751
- bestLen = len;
752
- chosen = nf;
753
- }
754
- }
755
- if (chosen && typeof chosen.path === "string") {
756
- try {
757
- const abs = join(clientDir, "routes", String(chosen.path));
758
- const mod = await import(pathToFileURL4(abs).href + (isWatchMode ? `?t=${Date.now()}` : ""));
759
- const meta = mod?.meta;
760
- const ssr = mod?.ssr;
761
- if (meta)
762
- perPageHead += buildHeadFromMeta(meta);
763
- const ssrHead = typeof ssr?.head === "string" ? ssr.head : Array.isArray(ssr?.head) ? String(ssr.head.join(`
764
- `)) : "";
765
- if (ssrHead)
766
- perPageHead += `
767
- ${ssrHead}`;
768
- if (typeof ssr?.lang === "string" && ssr.lang?.trim()) {
769
- pageLang = String(ssr.lang).trim();
770
- }
771
- } catch {}
772
- }
773
- }
774
- } catch {}
775
- }
776
- } catch {}
1435
+ hydrationScript += createDataScript(__SSR_DATA__, isWatchMode ? __SSR_DATA_ERROR__ : undefined);
1436
+ hydrationScript += scripts2.map((src) => `<script type="module" src="${src}"></script>`).join("");
1437
+ if (isWatchMode) {
1438
+ hydrationScript += `<script type="module" src="/__reroute_watch.js"></script>`;
1439
+ }
1440
+ const metadataResult = await extractPageMetadata(pathname, clientDir, cwd, isWatchMode, statusOverride);
1441
+ const perPageHead = metadataResult.perPageHead;
1442
+ const pageLang = metadataResult.pageLang;
1443
+ statusOverride = metadataResult.statusOverride;
777
1444
  const combinedHead = dedent_default([dedent_default(head) || "", dedent_default(extraHead), dedent_default(perPageHead)].filter(Boolean).join(`
778
1445
  `));
779
1446
  const html2 = applyIndexTemplate(baseTemplate, appHtml, {
@@ -788,129 +1455,160 @@ ${ssrHead}`;
788
1455
  status: statusOverride || 200
789
1456
  };
790
1457
  }
791
- // packages/core/src/utils/cache.ts
792
- class LRUCache {
793
- cache;
794
- maxSize;
795
- constructor(maxSize = 100) {
796
- this.cache = new Map;
797
- this.maxSize = maxSize;
798
- }
799
- get(key) {
800
- if (!this.cache.has(key)) {
801
- return;
802
- }
803
- const value = this.cache.get(key);
804
- this.cache.delete(key);
805
- this.cache.set(key, value);
806
- return value;
807
- }
808
- set(key, value) {
809
- if (this.cache.has(key)) {
810
- this.cache.delete(key);
811
- }
812
- this.cache.set(key, value);
813
- if (this.cache.size > this.maxSize) {
814
- const firstKey = this.cache.keys().next().value;
815
- this.cache.delete(firstKey);
816
- }
817
- }
818
- has(key) {
819
- return this.cache.has(key);
820
- }
821
- clear() {
822
- this.cache.clear();
823
- }
824
- get size() {
825
- return this.cache.size;
826
- }
827
- }
828
- // packages/core/src/utils/compression.ts
829
- import { brotliCompressSync } from "node:zlib";
830
-
831
- // packages/core/src/utils/mime.ts
832
- function getMimeType(filePath) {
833
- const ext = filePath.split(".").pop()?.toLowerCase();
834
- const mimeTypes = {
835
- html: "text/html",
836
- css: "text/css",
837
- js: "application/javascript",
838
- ts: "application/javascript",
839
- json: "application/json",
840
- png: "image/png",
841
- jpg: "image/jpeg",
842
- jpeg: "image/jpeg",
843
- gif: "image/gif",
844
- svg: "image/svg+xml",
845
- ico: "image/x-icon",
846
- woff: "font/woff",
847
- woff2: "font/woff2",
848
- ttf: "font/ttf"
849
- };
850
- return mimeTypes[ext || ""] || "application/octet-stream";
851
- }
852
- function isCompressible(contentType) {
853
- if (!contentType)
854
- return false;
855
- const ct = contentType.split(";")[0].trim();
856
- return ct.startsWith("text/") || ct === "application/javascript" || ct === "application/json" || ct === "application/xml" || ct === "image/svg+xml";
857
- }
858
-
859
- // packages/core/src/utils/compression.ts
860
- function acceptsGzip(acceptEncoding) {
861
- return Boolean(acceptEncoding && /gzip/i.test(acceptEncoding));
862
- }
863
- function acceptsBrotli(acceptEncoding) {
864
- return Boolean(acceptEncoding && /\bbr\b/i.test(acceptEncoding));
865
- }
866
- function toBytes(input) {
867
- return typeof input === "string" ? new TextEncoder().encode(input) : input;
868
- }
869
- function gzipIfAccepted(body, contentType, acceptEncoding) {
870
- const extraHeaders = {};
871
- if (isCompressible(contentType)) {
872
- if (acceptsBrotli(acceptEncoding)) {
873
- try {
874
- const compressed = brotliCompressSync(toBytes(body));
875
- extraHeaders["Content-Encoding"] = "br";
876
- extraHeaders.Vary = "Accept-Encoding";
877
- return { body: compressed, extraHeaders };
878
- } catch {}
879
- }
880
- if (acceptsGzip(acceptEncoding)) {
1458
+ // packages/core/src/ssr/stream.ts
1459
+ import { cloneElement as cloneElement2 } from "react";
1460
+ import { renderToReadableStream } from "react-dom/server";
1461
+ var isThenable2 = (value) => {
1462
+ return typeof value === "object" && value !== null && typeof value.then === "function";
1463
+ };
1464
+ async function renderSSRDocumentStream(options) {
1465
+ const {
1466
+ pathname,
1467
+ rootComponent,
1468
+ clientDir,
1469
+ cwd,
1470
+ isWatchMode,
1471
+ bundleUrl,
1472
+ head = "",
1473
+ lang = "en",
1474
+ appId = "root",
1475
+ maxAge = 0
1476
+ } = options;
1477
+ await seedSSRModuleForPath(pathname, clientDir, cwd, isWatchMode);
1478
+ const { data: __SSR_DATA__, error: __SSR_DATA_ERROR__ } = await computeSSRData(pathname, clientDir, cwd, isWatchMode, {
1479
+ streaming: true,
1480
+ maxAge
1481
+ });
1482
+ setGlobalSSRData(__SSR_DATA__);
1483
+ const __byCollectionForSSR = await loadCollections(cwd, isWatchMode);
1484
+ setGlobalCollections(__byCollectionForSSR);
1485
+ resetSSRAccessTracking();
1486
+ const bundlePreload = createBundlePreload(bundleUrl);
1487
+ const preloadResult = await preloadContentModule(pathname, clientDir, cwd, isWatchMode);
1488
+ let extraHead = bundlePreload + preloadResult.extraHead;
1489
+ const hydrationScript = preloadResult.hydrationScript;
1490
+ let statusOverride = preloadResult.statusOverride;
1491
+ const encoder = new TextEncoder;
1492
+ const { readable, writable } = new TransformStream;
1493
+ const writer = writable.getWriter();
1494
+ const baseTemplate = await loadIndexHtml(clientDir);
1495
+ const { htmlHead, rootStart, rootEnd, htmlTail } = splitTemplate(baseTemplate, appId);
1496
+ const headWithLang = htmlHead.replace(/<html([^>]*)>/i, `<html$1 lang="${lang}">`);
1497
+ const inlineStyleTag = await inlineTailwindCSS(clientDir, false);
1498
+ const metadataResult = await extractPageMetadata(pathname, clientDir, cwd, isWatchMode, statusOverride);
1499
+ extraHead += metadataResult.perPageHead;
1500
+ const pageLang = metadataResult.pageLang;
1501
+ statusOverride = metadataResult.statusOverride;
1502
+ (async () => {
1503
+ try {
1504
+ const combinedHead = [inlineStyleTag, head, extraHead].filter(Boolean).join(`
1505
+ `);
1506
+ const headContent = `${headWithLang.replace(/<html([^>]*)>/i, `<html$1 lang="${pageLang || lang}">`)}${combinedHead ? `
1507
+ ${combinedHead}` : ""}</head><body>`;
1508
+ await writer.write(encoder.encode(headContent));
1509
+ await writer.write(encoder.encode(rootStart));
1510
+ const componentWithPathname = cloneElement2(rootComponent, {
1511
+ pathname
1512
+ });
1513
+ let streamError = null;
1514
+ const streamPromise = renderToReadableStream(componentWithPathname, {
1515
+ onError(error) {
1516
+ console.error("[reroute] SSR stream error:", error);
1517
+ streamError = error instanceof Error ? error : new Error(String(error));
1518
+ }
1519
+ }).catch((err) => {
1520
+ streamError = err;
1521
+ throw err;
1522
+ });
1523
+ const reactStream = await streamPromise;
1524
+ if (streamError) {
1525
+ throw streamError;
1526
+ }
1527
+ const reader = reactStream.getReader();
1528
+ while (true) {
1529
+ const { done, value } = await reader.read();
1530
+ if (done)
1531
+ break;
1532
+ await writer.write(value);
1533
+ }
1534
+ await writer.write(encoder.encode(rootEnd));
1535
+ let collectionScripts = "";
881
1536
  try {
882
- const compressed = Bun.gzipSync(toBytes(body));
883
- extraHeaders["Content-Encoding"] = "gzip";
884
- extraHeaders.Vary = "Accept-Encoding";
885
- return { body: compressed, extraHeaders };
1537
+ const g = globalThis;
1538
+ const acc = g.__REROUTE_SSR_ACCESSED__;
1539
+ if (acc) {
1540
+ const { subset, partial } = processCollections(acc, __byCollectionForSSR);
1541
+ if (Object.keys(subset).length > 0) {
1542
+ collectionScripts = createCollectionScript(subset, partial);
1543
+ }
1544
+ }
886
1545
  } catch {}
1546
+ const ssrDataSnapshot = {};
1547
+ for (const key of Object.keys(__SSR_DATA__)) {
1548
+ const value = __SSR_DATA__[key];
1549
+ if (!isThenable2(value)) {
1550
+ ssrDataSnapshot[key] = value;
1551
+ }
1552
+ }
1553
+ const dataScript = createDataScript(ssrDataSnapshot, isWatchMode ? __SSR_DATA_ERROR__ : undefined);
1554
+ let allScripts = dataScript + collectionScripts + hydrationScript;
1555
+ allScripts += `<script type="module" src="${bundleUrl}"></script>`;
1556
+ if (isWatchMode) {
1557
+ allScripts += `<script type="module" src="/__reroute_watch.js"></script>`;
1558
+ }
1559
+ await writer.write(encoder.encode(allScripts + htmlTail));
1560
+ await writer.close();
1561
+ } catch (error) {
1562
+ console.error("[reroute] Stream error:", error);
1563
+ await writer.abort(error);
887
1564
  }
888
- }
889
- return { body, extraHeaders };
1565
+ })();
1566
+ return { stream: readable, status: statusOverride || 200 };
890
1567
  }
891
1568
  export {
892
1569
  toBytes,
893
1570
  stripStart,
894
1571
  stripEnd,
1572
+ ssrCache,
1573
+ splitTemplate,
1574
+ setGlobalSSRData,
1575
+ setGlobalCollections,
895
1576
  seedSSRModuleForPath,
1577
+ resetSSRAccessTracking,
1578
+ renderSSRDocumentStream,
896
1579
  renderSSRDocument,
1580
+ processCollections,
1581
+ preloadContentModule,
897
1582
  loadIndexHtml,
1583
+ loadConfig,
1584
+ loadCollections,
898
1585
  listContentFiles,
899
1586
  join,
900
1587
  isCompressible,
1588
+ inlineTailwindCSS,
1589
+ importContentModuleForPath,
901
1590
  gzipIfAccepted,
902
1591
  getMimeType,
903
1592
  getContentMeta,
904
1593
  generateContentHash,
1594
+ extractPageMetadata,
905
1595
  extname,
1596
+ escapeJsonForScript,
906
1597
  discoverCollections,
1598
+ defineConfig,
1599
+ createDataScript,
1600
+ createCollectionScript,
1601
+ createBundlePreload,
907
1602
  computeSSRDataForPath,
1603
+ computeSSRData,
1604
+ compressStreamIfAccepted,
908
1605
  buildHeadFromMeta,
909
1606
  basename,
910
1607
  applyIndexTemplate,
911
1608
  acceptsGzip,
912
1609
  acceptsBrotli,
1610
+ SSRDataCache,
913
1611
  LRUCache
914
1612
  };
915
1613
 
916
- //# debugId=11BA8A280240C7F064756E2164756E21
1614
+ //# debugId=393BB814878CEFAA64756E2164756E21