olova 2.0.55 → 2.0.56

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 (84) hide show
  1. package/README.md +28 -288
  2. package/dist/chunk-23UAGQ6N.js +2208 -0
  3. package/dist/chunk-23UAGQ6N.js.map +1 -0
  4. package/dist/chunk-D7SIC5TC.js +367 -0
  5. package/dist/chunk-D7SIC5TC.js.map +1 -0
  6. package/dist/entry-server.cjs +2341 -0
  7. package/dist/entry-server.cjs.map +1 -0
  8. package/dist/entry-server.js +114 -0
  9. package/dist/entry-server.js.map +1 -0
  10. package/dist/entry-worker.cjs +2354 -0
  11. package/dist/entry-worker.cjs.map +1 -0
  12. package/dist/entry-worker.js +126 -0
  13. package/dist/entry-worker.js.map +1 -0
  14. package/dist/main.cjs +18 -0
  15. package/dist/main.cjs.map +1 -0
  16. package/dist/main.js +16 -0
  17. package/dist/main.js.map +1 -0
  18. package/dist/olova.cjs +1684 -0
  19. package/dist/olova.cjs.map +1 -0
  20. package/dist/olova.d.cts +72 -0
  21. package/dist/olova.d.ts +72 -0
  22. package/dist/olova.js +1325 -0
  23. package/dist/olova.js.map +1 -0
  24. package/dist/performance.cjs +386 -0
  25. package/dist/performance.cjs.map +1 -0
  26. package/dist/performance.js +3 -0
  27. package/dist/performance.js.map +1 -0
  28. package/dist/router.cjs +646 -0
  29. package/dist/router.cjs.map +1 -0
  30. package/dist/router.d.cts +113 -0
  31. package/dist/router.d.ts +113 -0
  32. package/dist/router.js +632 -0
  33. package/dist/router.js.map +1 -0
  34. package/main.tsx +76 -0
  35. package/olova.ts +619 -0
  36. package/package.json +42 -61
  37. package/src/entry-server.tsx +165 -0
  38. package/src/entry-worker.tsx +201 -0
  39. package/src/generator/index.ts +409 -0
  40. package/src/hydration/flight.ts +320 -0
  41. package/src/hydration/index.ts +12 -0
  42. package/src/hydration/types.ts +225 -0
  43. package/src/logger.ts +182 -0
  44. package/src/main.tsx +24 -0
  45. package/src/performance.ts +488 -0
  46. package/src/plugin/index.ts +204 -0
  47. package/src/router/ErrorBoundary.tsx +145 -0
  48. package/src/router/Link.tsx +117 -0
  49. package/src/router/OlovaRouter.tsx +354 -0
  50. package/src/router/Outlet.tsx +8 -0
  51. package/src/router/context.ts +117 -0
  52. package/src/router/index.ts +29 -0
  53. package/src/router/matching.ts +63 -0
  54. package/src/router/router.tsx +23 -0
  55. package/src/router/search-params.ts +29 -0
  56. package/src/scanner/index.ts +116 -0
  57. package/src/types/index.ts +191 -0
  58. package/src/utils/export.ts +85 -0
  59. package/src/utils/index.ts +4 -0
  60. package/src/utils/naming.ts +54 -0
  61. package/src/utils/path.ts +45 -0
  62. package/tsup.config.ts +35 -0
  63. package/CHANGELOG.md +0 -31
  64. package/LICENSE +0 -21
  65. package/dist/index.cjs +0 -883
  66. package/dist/index.cjs.map +0 -1
  67. package/dist/index.d.cts +0 -138
  68. package/dist/index.d.ts +0 -138
  69. package/dist/index.js +0 -832
  70. package/dist/index.js.map +0 -1
  71. package/dist/plugin.cjs +0 -927
  72. package/dist/plugin.cjs.map +0 -1
  73. package/dist/plugin.d.cts +0 -18
  74. package/dist/plugin.d.ts +0 -18
  75. package/dist/plugin.js +0 -894
  76. package/dist/plugin.js.map +0 -1
  77. package/dist/ssg.cjs +0 -637
  78. package/dist/ssg.cjs.map +0 -1
  79. package/dist/ssg.d.cts +0 -191
  80. package/dist/ssg.d.ts +0 -191
  81. package/dist/ssg.js +0 -585
  82. package/dist/ssg.js.map +0 -1
  83. package/dist/types-BT6YsBGO.d.cts +0 -143
  84. package/dist/types-BT6YsBGO.d.ts +0 -143
package/dist/olova.js ADDED
@@ -0,0 +1,1325 @@
1
+ import { olovaPerformance, logger_default, generatePreloadHints, generatePreloadTags, createManualChunks } from './chunk-D7SIC5TC.js';
2
+ import fs3, { existsSync } from 'fs';
3
+ import fs4 from 'fs/promises';
4
+ import path4 from 'path';
5
+ import { pathToFileURL, fileURLToPath } from 'url';
6
+ import mdx from '@mdx-js/rollup';
7
+ import { build } from 'vite';
8
+
9
+ function extractBalancedBraces(content, startIndex) {
10
+ if (content[startIndex] !== "{") return null;
11
+ let depth = 0;
12
+ for (let i = startIndex; i < content.length; i++) {
13
+ if (content[i] === "{") depth++;
14
+ else if (content[i] === "}") {
15
+ depth--;
16
+ if (depth === 0) return content.substring(startIndex, i + 1);
17
+ }
18
+ }
19
+ return null;
20
+ }
21
+ function extractMetadataSource(content) {
22
+ const match = content.match(/export\s+(?:const|let|var)\s+metadata\s*(?::\s*\w+\s*)?=\s*/);
23
+ if (!match || match.index === void 0) return void 0;
24
+ const afterEquals = match.index + match[0].length;
25
+ return extractBalancedBraces(content, afterEquals) || void 0;
26
+ }
27
+ function detectExportType(filePath) {
28
+ try {
29
+ const content = fs3.readFileSync(filePath, "utf-8");
30
+ const hasMetadata = /export\s+(?:const|let|var)\s+metadata\s*/.test(content) || /export\s*\{\s*metadata\s*\}/.test(content);
31
+ const hasRoute = /export\s+(?:const|let|var)\s+route\s*=/.test(content) || /export\s*\{\s*route\s*\}/.test(content);
32
+ const hasGetStaticPaths = /export\s+(?:async\s+)?function\s+getStaticPaths/.test(content) || /export\s+(?:const|let|var)\s+getStaticPaths\s*=/.test(content) || /export\s*\{[^}]*getStaticPaths[^}]*\}/.test(content);
33
+ const hasLoader = /export\s+(?:async\s+)?function\s+loader/.test(content) || /export\s+(?:const|let|var)\s+loader\s*=/.test(content) || /export\s*\{[^}]*loader[^}]*\}/.test(content);
34
+ const metadataSource = extractMetadataSource(content);
35
+ if (filePath.toLowerCase().endsWith(".mdx")) {
36
+ return {
37
+ hasDefault: true,
38
+ namedExport: null,
39
+ hasMetadata: !!metadataSource,
40
+ hasRoute: false,
41
+ hasGetStaticPaths: false,
42
+ hasLoader: false,
43
+ metadataSource
44
+ };
45
+ }
46
+ if (/export\s+default\s+/.test(content)) {
47
+ return { hasDefault: true, namedExport: null, hasMetadata, metadataSource, hasRoute, hasGetStaticPaths, hasLoader };
48
+ }
49
+ const namedMatch = content.match(/export\s+(?:const|function|class)\s+(\w+)/);
50
+ if (namedMatch) {
51
+ return { hasDefault: false, namedExport: namedMatch[1], hasMetadata, metadataSource, hasRoute, hasGetStaticPaths, hasLoader };
52
+ }
53
+ const exportMatch = content.match(/export\s*\{\s*(\w+)(?:\s+as\s+default)?\s*\}/);
54
+ if (exportMatch) {
55
+ if (content.includes("as default")) {
56
+ return { hasDefault: true, namedExport: null, hasMetadata, metadataSource, hasRoute, hasGetStaticPaths, hasLoader };
57
+ }
58
+ return { hasDefault: false, namedExport: exportMatch[1], hasMetadata, metadataSource, hasRoute, hasGetStaticPaths, hasLoader };
59
+ }
60
+ return { hasDefault: false, namedExport: null, hasMetadata, metadataSource, hasRoute, hasGetStaticPaths, hasLoader };
61
+ } catch {
62
+ return { hasDefault: true, namedExport: null, hasMetadata: false, hasRoute: false, hasGetStaticPaths: false, hasLoader: false };
63
+ }
64
+ }
65
+
66
+ // src/utils/naming.ts
67
+ function getRouteName(path5) {
68
+ if (path5 === "/" || path5 === "") return "Root";
69
+ const segments = path5.replace(/^\/|\/$/g, "").split(/[\\/]/);
70
+ const nameSegments = segments.map((segment) => {
71
+ let cleanSegment = segment;
72
+ if (cleanSegment.startsWith(":")) {
73
+ cleanSegment = cleanSegment.slice(1);
74
+ }
75
+ if (cleanSegment.startsWith("[") && cleanSegment.endsWith("]")) {
76
+ cleanSegment = cleanSegment.slice(1, -1);
77
+ if (cleanSegment.startsWith("...")) {
78
+ cleanSegment = cleanSegment.slice(3);
79
+ }
80
+ }
81
+ if (cleanSegment === "*") {
82
+ return "All";
83
+ }
84
+ if (cleanSegment.toLowerCase() === "index") {
85
+ return "";
86
+ }
87
+ if (cleanSegment.startsWith("(") && cleanSegment.endsWith(")")) {
88
+ cleanSegment = cleanSegment.slice(1, -1);
89
+ }
90
+ return capitalize(cleanSegment);
91
+ });
92
+ const finalName = nameSegments.join("");
93
+ if (/^\d/.test(finalName)) {
94
+ return "Page" + finalName;
95
+ }
96
+ return finalName;
97
+ }
98
+ function capitalize(str) {
99
+ return str.charAt(0).toUpperCase() + str.slice(1).replace(/[^a-zA-Z0-9]/g, "");
100
+ }
101
+
102
+ // src/utils/path.ts
103
+ function parseDynamicSegment(segment) {
104
+ if (segment.match(/^\[\.\.\.(.+)\]$/)) {
105
+ const paramName = segment.match(/^\[\.\.\.(.+)\]$/)?.[1] || "slug";
106
+ return { isDynamic: true, paramName, isCatchAll: true };
107
+ }
108
+ const bracketMatch = segment.match(/^\[(.+)\]$/);
109
+ if (bracketMatch) {
110
+ return { isDynamic: true, paramName: bracketMatch[1], isCatchAll: false };
111
+ }
112
+ return { isDynamic: false, paramName: null, isCatchAll: false };
113
+ }
114
+ function isRouteGroup(segment) {
115
+ return /^\(.+\)$/.test(segment);
116
+ }
117
+ function pathToRoute(relativePath, sep) {
118
+ const params = [];
119
+ let hasCatchAll = false;
120
+ const segments = relativePath.split(sep).filter(Boolean);
121
+ const routeSegments = segments.filter((segment) => !isRouteGroup(segment)).map((segment) => {
122
+ const { isDynamic, paramName, isCatchAll } = parseDynamicSegment(segment);
123
+ if (isDynamic && paramName) {
124
+ params.push(paramName);
125
+ if (isCatchAll) {
126
+ hasCatchAll = true;
127
+ return `*`;
128
+ }
129
+ return `:${paramName}`;
130
+ }
131
+ return segment;
132
+ });
133
+ const routePath = "/" + routeSegments.join("/");
134
+ return { routePath: routePath === "/" ? "/" : routePath, params, hasCatchAll };
135
+ }
136
+
137
+ // src/generator/index.ts
138
+ function stripImportExtension(filePath) {
139
+ return filePath.replace(/\.tsx?$/, "");
140
+ }
141
+ function pathToRouteId(routePath) {
142
+ if (routePath === "/") return "_root";
143
+ return routePath.replace(/^\//, "").replace(/\//g, "_").replace(/:/g, "$").replace(/\*/g, "$catchall");
144
+ }
145
+ function extractParamNames(routePath) {
146
+ const params = [];
147
+ for (const segment of routePath.split("/")) {
148
+ if (segment.startsWith(":")) params.push(segment.slice(1));
149
+ else if (segment === "*") params.push("*");
150
+ }
151
+ return params;
152
+ }
153
+ function generateParamType(routePath) {
154
+ const params = extractParamNames(routePath);
155
+ if (params.length === 0) return null;
156
+ const fields = params.map(
157
+ (p) => p === "*" ? "'*': string" : `${p}: string`
158
+ );
159
+ return `{ ${fields.join("; ")} }`;
160
+ }
161
+ function generateRouteTree(routes, notFoundPages, layouts, loadingPages, errorPages, middlewares, srcDir, packageName = "olovastart") {
162
+ const usedNames = /* @__PURE__ */ new Map();
163
+ const getUniqueName = (baseName) => {
164
+ const count = usedNames.get(baseName) || 0;
165
+ usedNames.set(baseName, count + 1);
166
+ return count === 0 ? baseName : `${baseName}${count}`;
167
+ };
168
+ const getRouteName2 = (filePath, suffix = "") => {
169
+ const relPath = path4.relative(srcDir, filePath);
170
+ const pathNoExt = relPath.replace(/\.(tsx?|mdx)$/, "");
171
+ if (pathNoExt === "" || pathNoExt === "." || pathNoExt === "index") {
172
+ return getUniqueName("Root" + suffix);
173
+ }
174
+ const name = getRouteName(pathNoExt);
175
+ if (!name) return getUniqueName("Root" + suffix);
176
+ if (suffix && name.toLowerCase().endsWith(suffix.toLowerCase())) {
177
+ return getUniqueName(name);
178
+ }
179
+ return getUniqueName(name + suffix);
180
+ };
181
+ const findClosestEntry = (routePath, entries) => {
182
+ let best = null;
183
+ for (const entry of entries) {
184
+ const ep = entry.path;
185
+ if (ep === "/" || routePath === ep || routePath.startsWith(ep + "/")) {
186
+ if (!best || ep.length > best.path.length) best = entry;
187
+ }
188
+ }
189
+ return best;
190
+ };
191
+ const routeNames = [];
192
+ const routeImports = routes.map((route) => {
193
+ const relativePath = stripImportExtension("./" + path4.relative(srcDir, route.component).replace(/\\/g, "/"));
194
+ const moduleName = getRouteName2(route.component, "RouteModule");
195
+ const lazyName = getRouteName2(route.component, "Route");
196
+ routeNames.push({ moduleName, lazyName, eager: true });
197
+ return `import * as ${moduleName} from '${relativePath}';`;
198
+ }).join("\n");
199
+ const notFoundNames = [];
200
+ const notFoundImports = notFoundPages.map((nf) => {
201
+ const relativePath = stripImportExtension("./" + path4.relative(srcDir, nf.filePath).replace(/\\/g, "/"));
202
+ if (nf.hasMetadata) {
203
+ const moduleName = getRouteName2(nf.filePath, "NotFoundModule");
204
+ notFoundNames.push(moduleName);
205
+ return `import * as ${moduleName} from '${relativePath}';`;
206
+ }
207
+ const importName = getRouteName2(nf.filePath, "NotFound");
208
+ notFoundNames.push(importName);
209
+ if (nf.hasDefault) return `import ${importName} from '${relativePath}';`;
210
+ if (nf.namedExport) return `import { ${nf.namedExport} as ${importName} } from '${relativePath}';`;
211
+ return `import ${importName} from '${relativePath}';`;
212
+ }).join("\n");
213
+ const layoutNames = [];
214
+ const layoutImports = layouts.map((layout) => {
215
+ const relativePath = stripImportExtension("./" + path4.relative(srcDir, layout.filePath).replace(/\\/g, "/"));
216
+ if (layout.hasMetadata) {
217
+ const moduleName = getRouteName2(layout.filePath, "LayoutModule");
218
+ layoutNames.push(moduleName);
219
+ return `import * as ${moduleName} from '${relativePath}';`;
220
+ }
221
+ const importName = getRouteName2(layout.filePath, "Layout");
222
+ layoutNames.push(importName);
223
+ if (layout.hasDefault) return `import ${importName} from '${relativePath}';`;
224
+ if (layout.namedExport) return `import { ${layout.namedExport} as ${importName} } from '${relativePath}';`;
225
+ return `import ${importName} from '${relativePath}';`;
226
+ }).join("\n");
227
+ const loadingNames = [];
228
+ const loadingImports = loadingPages.map((lp) => {
229
+ const relativePath = stripImportExtension("./" + path4.relative(srcDir, lp.filePath).replace(/\\/g, "/"));
230
+ const importName = getRouteName2(lp.filePath, "Loading");
231
+ loadingNames.push(importName);
232
+ if (lp.hasDefault) return `import ${importName} from '${relativePath}';`;
233
+ if (lp.namedExport) return `import { ${lp.namedExport} as ${importName} } from '${relativePath}';`;
234
+ return `import ${importName} from '${relativePath}';`;
235
+ }).join("\n");
236
+ const errorNames = [];
237
+ const errorImports = errorPages.map((ep) => {
238
+ const relativePath = stripImportExtension("./" + path4.relative(srcDir, ep.filePath).replace(/\\/g, "/"));
239
+ const importName = getRouteName2(ep.filePath, "Error");
240
+ errorNames.push(importName);
241
+ if (ep.hasDefault) return `import ${importName} from '${relativePath}';`;
242
+ if (ep.namedExport) return `import { ${ep.namedExport} as ${importName} } from '${relativePath}';`;
243
+ return `import ${importName} from '${relativePath}';`;
244
+ }).join("\n");
245
+ const middlewareNames = [];
246
+ const middlewareImports = middlewares.map((mw) => {
247
+ const relativePath = stripImportExtension("./" + path4.relative(srcDir, mw.filePath).replace(/\\/g, "/"));
248
+ const importName = getRouteName2(mw.filePath, "Middleware");
249
+ middlewareNames.push(importName);
250
+ if (mw.hasDefault) return `import ${importName} from '${relativePath}';`;
251
+ if (mw.namedExport) return `import { ${mw.namedExport} as ${importName} } from '${relativePath}';`;
252
+ return `import ${importName} from '${relativePath}';`;
253
+ }).join("\n");
254
+ const routeObjects = routes.map((route, index) => {
255
+ const { moduleName, lazyName, eager } = routeNames[index];
256
+ const component = eager ? `${moduleName}.default` : lazyName;
257
+ const routeId = pathToRouteId(route.path);
258
+ const fields = [
259
+ `id: '${routeId}'`,
260
+ `path: '${route.path}'`,
261
+ `component: ${component}`
262
+ ];
263
+ if (route.params && route.params.length > 0) {
264
+ fields.push(`params: [${route.params.map((p) => `'${p}'`).join(", ")}]`);
265
+ }
266
+ if (route.metadataSource) {
267
+ fields.push(`metadata: ${route.metadataSource}`);
268
+ } else if (route.hasMetadata) {
269
+ fields.push(`metadata: ${moduleName}.metadata`);
270
+ }
271
+ if (route.hasGetStaticPaths) {
272
+ fields.push(`getStaticPaths: ${moduleName}.getStaticPaths`);
273
+ }
274
+ if (route.hasLoader) {
275
+ fields.push(`loader: ${moduleName}.loader`);
276
+ }
277
+ const closestLoading = findClosestEntry(route.path, loadingPages);
278
+ if (closestLoading) {
279
+ const idx = loadingPages.indexOf(closestLoading);
280
+ fields.push(`loading: ${loadingNames[idx]}`);
281
+ }
282
+ const closestError = findClosestEntry(route.path, errorPages);
283
+ if (closestError) {
284
+ const idx = errorPages.indexOf(closestError);
285
+ fields.push(`error: ${errorNames[idx]}`);
286
+ }
287
+ return ` {
288
+ ${fields.join(",\n ")}
289
+ }`;
290
+ }).join(",\n");
291
+ const notFoundObjects = notFoundPages.map((nf, index) => {
292
+ const name = notFoundNames[index];
293
+ const isModule = nf.hasMetadata;
294
+ const component = isModule ? `${name}.default` : name;
295
+ const fields = [
296
+ `pathPrefix: '${nf.pathPrefix}'`,
297
+ `component: ${component}`
298
+ ];
299
+ if (isModule) fields.push(`metadata: ${name}.metadata`);
300
+ return ` {
301
+ ${fields.join(",\n ")}
302
+ }`;
303
+ }).join(",\n");
304
+ const layoutObjects = layouts.map((layout, index) => {
305
+ const name = layoutNames[index];
306
+ const isModule = layout.hasMetadata;
307
+ const component = isModule ? `${name}.default` : name;
308
+ const childRouteIds = routes.filter((r) => {
309
+ if (layout.path === "/") return true;
310
+ return r.path === layout.path || r.path.startsWith(layout.path + "/");
311
+ }).map((r) => `'${pathToRouteId(r.path)}'`);
312
+ const fields = [
313
+ `path: '${layout.path}'`,
314
+ `layout: ${component}`,
315
+ `children: [${childRouteIds.join(", ")}]`
316
+ ];
317
+ if (isModule) fields.push(`metadata: ${name}.metadata`);
318
+ return ` {
319
+ ${fields.join(",\n ")}
320
+ }`;
321
+ }).join(",\n");
322
+ const middlewareEntries = middlewares.map((mw, index) => {
323
+ return ` '${mw.path}': ${middlewareNames[index]}`;
324
+ }).join(",\n");
325
+ const routeParamTypes = routes.filter((r) => r.params && r.params.length > 0).map((r) => {
326
+ const paramType = generateParamType(r.path);
327
+ return paramType ? ` '${r.path}': ${paramType};` : null;
328
+ }).filter(Boolean).join("\n");
329
+ const routePaths = routes.length > 0 ? routes.map((r) => `'${r.path}'`).join(" | ") : "never";
330
+ const routeIds = routes.length > 0 ? routes.map((r) => `'${pathToRouteId(r.path)}'`).join(" | ") : "never";
331
+ const manifestEntries = routes.map((r) => {
332
+ const paramNames = extractParamNames(r.path);
333
+ const isDynamic = paramNames.length > 0;
334
+ const hasCatchAll = r.path.includes("*");
335
+ return ` '${pathToRouteId(r.path)}': { id: '${pathToRouteId(r.path)}', path: '${r.path}', params: [${paramNames.map((p) => `'${p}'`).join(", ")}], isDynamic: ${isDynamic}, hasCatchAll: ${hasCatchAll}, hasMetadata: ${r.hasMetadata}, hasGetStaticPaths: ${r.hasGetStaticPaths} }`;
336
+ }).join(",\n");
337
+ const allImports = [
338
+ routeImports,
339
+ notFoundImports,
340
+ layoutImports,
341
+ loadingImports,
342
+ errorImports,
343
+ middlewareImports
344
+ ].filter(Boolean).join("\n");
345
+ return `/* prettier-ignore-start */
346
+
347
+ /* eslint-disable */
348
+
349
+ // @ts-nocheck
350
+
351
+ // noinspection JSUnusedGlobalSymbols
352
+
353
+ /**
354
+ * This file was automatically generated by Olova Router.
355
+ * DO NOT MODIFY IT BY HAND. Instead, modify the source route files
356
+ * and regenerate this file by running the dev server or build command.
357
+ *
358
+ * @generated Olova Route Tree
359
+ * @routes ${routes.length}
360
+ * @layouts ${layouts.length}
361
+ * @404s ${notFoundPages.length}
362
+ * @loading ${loadingPages.length}
363
+ * @errors ${errorPages.length}
364
+ * @middleware ${middlewares.length}
365
+ */
366
+
367
+ import { lazy } from 'react';
368
+ import {
369
+ createLink,
370
+ OlovaRouter,
371
+ Outlet,
372
+ redirect,
373
+ useIsNavigating,
374
+ useNavigationEvent,
375
+ useParams,
376
+ usePathname,
377
+ useRedirect,
378
+ useRouter,
379
+ useSearchParams,
380
+ useSelectedLayoutSegment,
381
+ useSelectedLayoutSegments,
382
+ } from '${packageName}/router';
383
+ ${allImports}
384
+
385
+ /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
386
+ * Route Definitions
387
+ * \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
388
+
389
+ export const routes = [
390
+ ${routeObjects}
391
+ ];
392
+
393
+ export const notFoundPages = [
394
+ ${notFoundObjects}
395
+ ];
396
+
397
+ export const layouts = [
398
+ ${layoutObjects}
399
+ ];
400
+ ${middlewares.length > 0 ? `
401
+ export const middlewares = {
402
+ ${middlewareEntries}
403
+ };
404
+ ` : `
405
+ export const middlewares = {};
406
+ `}
407
+ /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
408
+ * Route Manifest (build-time introspection, sitemap generation, etc.)
409
+ * \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
410
+
411
+ export const routeManifest = {
412
+ ${manifestEntries}
413
+ } as const;
414
+
415
+ /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
416
+ * Type-Safe Route Types
417
+ * \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
418
+
419
+ export type RoutePaths = ${routePaths};
420
+
421
+ export type RouteIds = ${routeIds};
422
+
423
+ export interface RouteParams {
424
+ ${routeParamTypes || " // No dynamic routes"}
425
+ }
426
+
427
+ export type ParamsFor<P extends RoutePaths> = P extends keyof RouteParams ? RouteParams[P] : Record<string, never>;
428
+
429
+ /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
430
+ * Route Lookup Helpers
431
+ * \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
432
+
433
+ const _routeById = new Map(routes.map(r => [r.id, r]));
434
+
435
+ export function getRouteById(id: RouteIds) {
436
+ return _routeById.get(id);
437
+ }
438
+
439
+ export function getRouteByPath(path: RoutePaths) {
440
+ return routes.find(r => r.path === path);
441
+ }
442
+
443
+ /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
444
+ * Exports
445
+ * \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
446
+
447
+ export const Link = createLink<RoutePaths>();
448
+
449
+ export {
450
+ OlovaRouter,
451
+ Outlet,
452
+ redirect,
453
+ useIsNavigating,
454
+ useNavigationEvent,
455
+ useParams,
456
+ usePathname,
457
+ useRedirect,
458
+ useRouter,
459
+ useSearchParams,
460
+ useSelectedLayoutSegment,
461
+ useSelectedLayoutSegments,
462
+ };
463
+
464
+ export type {
465
+ NavigateOptions,
466
+ NotFoundPageConfig,
467
+ SearchParams,
468
+ SetSearchParamsOptions,
469
+ LayoutRoute,
470
+ Metadata,
471
+ } from '${packageName}/router';
472
+
473
+ /* prettier-ignore-end */
474
+ `;
475
+ }
476
+ var RESERVED_NAMES = /* @__PURE__ */ new Set([
477
+ "index",
478
+ "layout",
479
+ "loading",
480
+ "error",
481
+ "404",
482
+ "middleware",
483
+ "App",
484
+ "main",
485
+ "route.tree"
486
+ ]);
487
+ function scanDirectory(dir, rootDir, extensions, routes, notFoundPages, layouts, loadingPages, errorPages, middlewares, isRoot = false) {
488
+ if (!fs3.existsSync(dir)) return;
489
+ const entries = fs3.readdirSync(dir, { withFileTypes: true });
490
+ for (const entry of entries) {
491
+ const fullPath = path4.join(dir, entry.name);
492
+ if (entry.isDirectory()) {
493
+ if (entry.name === "node_modules" || entry.name === "assets" || entry.name.startsWith("_")) continue;
494
+ scanDirectory(fullPath, rootDir, extensions, routes, notFoundPages, layouts, loadingPages, errorPages, middlewares, false);
495
+ } else if (entry.isFile()) {
496
+ const ext = path4.extname(entry.name);
497
+ const baseName = path4.basename(entry.name, ext);
498
+ if (baseName === "layout" && extensions.includes(ext)) {
499
+ const relativePath = path4.relative(rootDir, dir);
500
+ const { routePath } = pathToRoute(relativePath, path4.sep);
501
+ layouts.push({
502
+ path: isRoot ? "/" : routePath,
503
+ filePath: fullPath
504
+ });
505
+ } else if (baseName === "loading" && extensions.includes(ext)) {
506
+ const relativePath = path4.relative(rootDir, dir);
507
+ const { routePath } = pathToRoute(relativePath, path4.sep);
508
+ loadingPages.push({
509
+ path: isRoot ? "/" : routePath,
510
+ filePath: fullPath
511
+ });
512
+ } else if (baseName === "error" && extensions.includes(ext)) {
513
+ const relativePath = path4.relative(rootDir, dir);
514
+ const { routePath } = pathToRoute(relativePath, path4.sep);
515
+ errorPages.push({
516
+ path: isRoot ? "/" : routePath,
517
+ filePath: fullPath
518
+ });
519
+ } else if (baseName === "middleware" && extensions.includes(ext)) {
520
+ const relativePath = path4.relative(rootDir, dir);
521
+ const { routePath } = pathToRoute(relativePath, path4.sep);
522
+ middlewares.push({
523
+ path: isRoot ? "/" : routePath,
524
+ filePath: fullPath
525
+ });
526
+ } else if (baseName === "404" && extensions.includes(ext)) {
527
+ const relativeParts = path4.relative(rootDir, dir).split(path4.sep).filter(Boolean);
528
+ const filteredParts = relativeParts.filter((p) => !isRouteGroup(p));
529
+ const pathPrefix = isRoot ? "" : "/" + filteredParts.join("/");
530
+ notFoundPages.push({ pathPrefix: pathPrefix || "", filePath: fullPath });
531
+ } else if (isRoot && baseName === "App" && extensions.includes(ext)) {
532
+ routes.push({ path: "/", filePath: fullPath, isDynamic: false, params: [] });
533
+ } else if (!isRoot && baseName === "index" && extensions.includes(ext)) {
534
+ const relativePath = path4.relative(rootDir, path4.dirname(fullPath));
535
+ const { routePath, params } = pathToRoute(relativePath, path4.sep);
536
+ routes.push({ path: routePath, filePath: fullPath, isDynamic: params.length > 0, params });
537
+ } else if (!RESERVED_NAMES.has(baseName) && !baseName.endsWith(".d") && !baseName.startsWith("_") && extensions.includes(ext)) {
538
+ const relativePath = path4.relative(rootDir, fullPath);
539
+ const relativePathNoExt = relativePath.substring(0, relativePath.length - ext.length);
540
+ const { routePath, params } = pathToRoute(relativePathNoExt, path4.sep);
541
+ routes.push({ path: routePath, filePath: fullPath, isDynamic: params.length > 0, params });
542
+ }
543
+ }
544
+ }
545
+ }
546
+ function scanRoutes(rootDir, extensions) {
547
+ const routes = [];
548
+ const notFoundPages = [];
549
+ const layouts = [];
550
+ const loadingPages = [];
551
+ const errorPages = [];
552
+ const middlewares = [];
553
+ const absoluteRoot = path4.isAbsolute(rootDir) ? rootDir : path4.resolve(rootDir);
554
+ if (!fs3.existsSync(absoluteRoot)) {
555
+ throw new Error(`Olova Router: Root directory does not exist: ${absoluteRoot}`);
556
+ }
557
+ scanDirectory(absoluteRoot, absoluteRoot, extensions, routes, notFoundPages, layouts, loadingPages, errorPages, middlewares, true);
558
+ routes.sort((a, b) => a.isDynamic !== b.isDynamic ? a.isDynamic ? 1 : -1 : a.path.localeCompare(b.path));
559
+ notFoundPages.sort((a, b) => b.pathPrefix.length - a.pathPrefix.length);
560
+ layouts.sort((a, b) => a.path.length - b.path.length);
561
+ loadingPages.sort((a, b) => b.path.length - a.path.length);
562
+ errorPages.sort((a, b) => b.path.length - a.path.length);
563
+ middlewares.sort((a, b) => a.path.length - b.path.length);
564
+ return { routes, notFoundPages, layouts, loadingPages, errorPages, middlewares };
565
+ }
566
+
567
+ // src/plugin/index.ts
568
+ function olovaRouter(options = {}) {
569
+ const rootDir = options.rootDir || "src";
570
+ const extensions = options.extensions || [".tsx", ".ts", ".mdx"];
571
+ const packageName = options.packageName || "olovastart";
572
+ let config;
573
+ let absoluteRootDir;
574
+ let watcher = null;
575
+ let timer = null;
576
+ function generateRouteTreeFile() {
577
+ const { routes, notFoundPages, layouts, loadingPages, errorPages, middlewares } = scanRoutes(absoluteRootDir, extensions);
578
+ const routeConfigs = routes.map((r) => {
579
+ let exportInfo = detectExportType(r.filePath);
580
+ if (r.filePath.toLowerCase().endsWith(".mdx")) {
581
+ exportInfo = {
582
+ ...exportInfo,
583
+ hasDefault: true,
584
+ namedExport: null
585
+ };
586
+ }
587
+ return {
588
+ path: r.path,
589
+ component: r.filePath.replace(/\\/g, "/"),
590
+ params: r.params.length > 0 ? r.params : void 0,
591
+ hasDefault: exportInfo.hasDefault,
592
+ namedExport: exportInfo.namedExport,
593
+ hasMetadata: exportInfo.hasMetadata,
594
+ metadataSource: exportInfo.metadataSource,
595
+ hasRoute: exportInfo.hasRoute,
596
+ hasGetStaticPaths: exportInfo.hasGetStaticPaths,
597
+ hasLoader: exportInfo.hasLoader
598
+ };
599
+ });
600
+ const notFoundConfigs = notFoundPages.map((nf) => {
601
+ const exportInfo = detectExportType(nf.filePath);
602
+ return {
603
+ pathPrefix: nf.pathPrefix,
604
+ filePath: nf.filePath.replace(/\\/g, "/"),
605
+ hasDefault: exportInfo.hasDefault,
606
+ namedExport: exportInfo.namedExport,
607
+ hasMetadata: exportInfo.hasMetadata
608
+ };
609
+ });
610
+ const layoutConfigs = layouts.map((l) => {
611
+ const exportInfo = detectExportType(l.filePath);
612
+ return {
613
+ path: l.path,
614
+ filePath: l.filePath.replace(/\\/g, "/"),
615
+ hasDefault: exportInfo.hasDefault,
616
+ namedExport: exportInfo.namedExport,
617
+ hasMetadata: exportInfo.hasMetadata
618
+ };
619
+ });
620
+ const loadingConfigs = loadingPages.map((lp) => {
621
+ const exportInfo = detectExportType(lp.filePath);
622
+ return {
623
+ path: lp.path,
624
+ filePath: lp.filePath.replace(/\\/g, "/"),
625
+ hasDefault: exportInfo.hasDefault,
626
+ namedExport: exportInfo.namedExport
627
+ };
628
+ });
629
+ const errorConfigs = errorPages.map((ep) => {
630
+ const exportInfo = detectExportType(ep.filePath);
631
+ return {
632
+ path: ep.path,
633
+ filePath: ep.filePath.replace(/\\/g, "/"),
634
+ hasDefault: exportInfo.hasDefault,
635
+ namedExport: exportInfo.namedExport
636
+ };
637
+ });
638
+ const middlewareConfigs = middlewares.map((mw) => {
639
+ const exportInfo = detectExportType(mw.filePath);
640
+ return {
641
+ path: mw.path,
642
+ filePath: mw.filePath.replace(/\\/g, "/"),
643
+ hasDefault: exportInfo.hasDefault,
644
+ namedExport: exportInfo.namedExport
645
+ };
646
+ });
647
+ const content = generateRouteTree(routeConfigs, notFoundConfigs, layoutConfigs, loadingConfigs, errorConfigs, middlewareConfigs, absoluteRootDir, packageName);
648
+ const treePath = path4.resolve(absoluteRootDir, "route.tree.ts");
649
+ const existing = fs3.existsSync(treePath) ? fs3.readFileSync(treePath, "utf-8") : "";
650
+ if (existing !== content) {
651
+ fs3.writeFileSync(treePath, content);
652
+ console.log("\x1B[32m[olova]\x1B[0m Route tree updated");
653
+ }
654
+ }
655
+ function startWatcher() {
656
+ if (watcher) return;
657
+ watcher = fs3.watch(absoluteRootDir, { recursive: true }, (eventType, filename) => {
658
+ if (!filename) return;
659
+ if (filename.includes("route.tree.ts")) return;
660
+ const ext = path4.extname(filename);
661
+ const isConfiguredExtension = extensions.includes(ext);
662
+ const isIndexFile = filename.includes("index") && isConfiguredExtension;
663
+ const isAppFile = filename.includes("App") && isConfiguredExtension;
664
+ const is404File = filename.includes("404") && isConfiguredExtension;
665
+ const isLayoutFile = filename.includes("layout") && isConfiguredExtension;
666
+ const isLoadingFile = filename.includes("loading") && isConfiguredExtension;
667
+ const isErrorFile = filename.includes("error") && isConfiguredExtension;
668
+ const isMiddlewareFile = filename.includes("middleware") && isConfiguredExtension;
669
+ const isDirectory = !filename.includes(".");
670
+ const isDynamicSegment = filename.includes("[");
671
+ const isRenameEvent = eventType === "rename";
672
+ if (isIndexFile || isAppFile || is404File || isLayoutFile || isLoadingFile || isErrorFile || isMiddlewareFile || isDirectory || isDynamicSegment || isRenameEvent) {
673
+ if (isIndexFile && filename) {
674
+ const fullPath = path4.join(absoluteRootDir, filename);
675
+ if (fs3.existsSync(fullPath)) {
676
+ const stat = fs3.statSync(fullPath);
677
+ if (stat.size === 0 && !filename.endsWith(".mdx")) {
678
+ const relativeDir = path4.relative(absoluteRootDir, path4.dirname(fullPath));
679
+ const pascalCaseName = getRouteName(relativeDir);
680
+ const boilerplate = `
681
+ export const metadata = {
682
+ title: "${pascalCaseName}",
683
+ description: "${pascalCaseName} page",
684
+ }
685
+
686
+ export default function ${pascalCaseName}() {
687
+ return (
688
+ <div>
689
+ <h1>${pascalCaseName}</h1>
690
+ </div>
691
+ );
692
+ }
693
+ `;
694
+ fs3.writeFileSync(fullPath, boilerplate);
695
+ console.log(`\x1B[32m[olova]\x1B[0m Generated boilerplate for ${filename}`);
696
+ }
697
+ }
698
+ }
699
+ if (timer) clearTimeout(timer);
700
+ timer = setTimeout(() => {
701
+ try {
702
+ generateRouteTreeFile();
703
+ } catch (error) {
704
+ console.error("\x1B[31m[olova]\x1B[0m Error generating route tree:", error);
705
+ }
706
+ }, 100);
707
+ }
708
+ });
709
+ console.log("\x1B[32m[olova]\x1B[0m Watching for route changes...");
710
+ }
711
+ const routerPlugin = {
712
+ name: "olova-router",
713
+ configResolved(resolvedConfig) {
714
+ config = resolvedConfig;
715
+ absoluteRootDir = path4.resolve(config.root, rootDir);
716
+ },
717
+ buildStart() {
718
+ generateRouteTreeFile();
719
+ if (config.command === "serve") {
720
+ startWatcher();
721
+ }
722
+ },
723
+ buildEnd() {
724
+ if (watcher) {
725
+ watcher.close();
726
+ watcher = null;
727
+ }
728
+ }
729
+ };
730
+ return [
731
+ { enforce: "pre", ...mdx() },
732
+ routerPlugin
733
+ ];
734
+ }
735
+
736
+ // src/hydration/flight.ts
737
+ function safeStringify(data) {
738
+ return JSON.stringify(data).replace(/[<>\/\u2028\u2029]/g, (char) => {
739
+ switch (char) {
740
+ case "<":
741
+ return "\\u003c";
742
+ case ">":
743
+ return "\\u003e";
744
+ case "/":
745
+ return "\\u002f";
746
+ case "\u2028":
747
+ return "\\u2028";
748
+ case "\u2029":
749
+ return "\\u2029";
750
+ default:
751
+ return char;
752
+ }
753
+ });
754
+ }
755
+ function generatePageName(route) {
756
+ if (route === "/") return "HomePage";
757
+ return route.slice(1).split("/").filter(Boolean).map((s) => s.charAt(0).toUpperCase() + s.slice(1).replace(/[^a-zA-Z0-9]/g, "")).join("") + "Page";
758
+ }
759
+ function generatePattern(route) {
760
+ if (route === "/") return "/";
761
+ return route.replace(/\[\.\.\.([^\]]+)\]/g, "*").replace(/\[([^\]]+)\]/g, ":$1");
762
+ }
763
+ function generateFlightRuntime() {
764
+ return `<script>
765
+ (function(){
766
+ var f=self.__olova_f||[];
767
+ var p={R:'$route',M:'$meta',T:'$tree',D:'$schema',A:'$assets',H:'$hints',S:'$state',L:'$loader',Q:'$query'};
768
+ var g=self.$OLOVA={};
769
+ function h(c){var t=c[1],d=c[2];if(t==='E')return;var k=p[t];if(k){if(t==='L'||t==='Q'){g[k]={data:d,timestamp:c[3]||Date.now()}}else{g[k]=d}}}
770
+ for(var i=0;i<f.length;i++)h(f[i]);
771
+ self.__olova_f={push:h,length:0};
772
+ })();
773
+ </script>`;
774
+ }
775
+ function generateOlovaHydration(data, buildId) {
776
+ const meta = data.metadata || {};
777
+ const chunks = [];
778
+ const routeData = {
779
+ path: data.route,
780
+ params: data.params || {},
781
+ pattern: generatePattern(data.route),
782
+ isStatic: data.isStatic ?? true,
783
+ buildId
784
+ };
785
+ chunks.push(`<script>(self.__olova_f=self.__olova_f||[]).push([0,"R",${safeStringify(routeData)}])</script>`);
786
+ const metadataData = {
787
+ title: meta.title || "Olova App",
788
+ description: meta.description || "",
789
+ keywords: Array.isArray(meta.keywords) ? meta.keywords : [],
790
+ robots: meta.robots || "index, follow",
791
+ canonical: meta.canonical || null,
792
+ og: {
793
+ type: "website",
794
+ locale: "en_US",
795
+ ...meta.openGraph || {}
796
+ },
797
+ twitter: {
798
+ card: "summary_large_image",
799
+ ...meta.twitter || {}
800
+ }
801
+ };
802
+ chunks.push(`<script>(self.__olova_f).push([1,"M",${safeStringify(metadataData)}])</script>`);
803
+ const treeData = {
804
+ layout: "RootLayout",
805
+ page: generatePageName(data.route),
806
+ template: null,
807
+ loading: null,
808
+ error: null,
809
+ notFound: null
810
+ };
811
+ chunks.push(`<script>(self.__olova_f).push([2,"T",${safeStringify(treeData)}])</script>`);
812
+ const assetsData = {
813
+ chunks: data.chunks || [],
814
+ styles: [],
815
+ prefetch: (data.chunks || []).slice(0, 5)
816
+ };
817
+ chunks.push(`<script>(self.__olova_f).push([3,"A",${safeStringify(assetsData)}])</script>`);
818
+ const hintsData = {
819
+ dnsPrefetch: ["fonts.googleapis.com", "fonts.gstatic.com"],
820
+ preconnect: ["https://fonts.googleapis.com", "https://fonts.gstatic.com"],
821
+ modulePreload: (data.chunks || []).slice(0, 3)
822
+ };
823
+ chunks.push(`<script>(self.__olova_f).push([4,"H",${safeStringify(hintsData)}])</script>`);
824
+ const stateData = {
825
+ hydrated: false,
826
+ streaming: false,
827
+ ready: true,
828
+ timestamp: Date.now(),
829
+ version: "1.0.0",
830
+ buildId
831
+ };
832
+ chunks.push(`<script>(self.__olova_f).push([5,"S",${safeStringify(stateData)}])</script>`);
833
+ if (data.loaderData) {
834
+ chunks.push(`<script>(self.__olova_f).push([6,"L",${safeStringify(data.loaderData)},${Date.now()}])</script>`);
835
+ }
836
+ if (data.queryState) {
837
+ chunks.push(`<script>(self.__olova_f).push([7,"Q",${safeStringify(data.queryState)},${Date.now()}])</script>`);
838
+ }
839
+ chunks.push(`<script>(self.__olova_f).push([8,"E",null])</script>`);
840
+ chunks.push(generateFlightRuntime());
841
+ return chunks.join("");
842
+ }
843
+ function generateJsonLd(data) {
844
+ const meta = data.metadata || {};
845
+ const jsonLd = {
846
+ "@context": "https://schema.org",
847
+ "@graph": [
848
+ {
849
+ "@type": "WebPage",
850
+ "@id": data.route,
851
+ name: meta.title || "Olova App",
852
+ description: meta.description || "",
853
+ url: data.route,
854
+ isPartOf: {
855
+ "@type": "WebSite",
856
+ name: "Olova App"
857
+ }
858
+ },
859
+ {
860
+ "@type": "BreadcrumbList",
861
+ itemListElement: data.route.split("/").filter(Boolean).map((segment, index, arr) => ({
862
+ "@type": "ListItem",
863
+ position: index + 1,
864
+ name: segment.charAt(0).toUpperCase() + segment.slice(1),
865
+ item: "/" + arr.slice(0, index + 1).join("/")
866
+ }))
867
+ }
868
+ ]
869
+ };
870
+ if (meta.schema) {
871
+ const graph = jsonLd["@graph"];
872
+ if (Array.isArray(meta.schema)) {
873
+ graph.push(...meta.schema);
874
+ } else {
875
+ graph.push(meta.schema);
876
+ }
877
+ }
878
+ return `<script type="application/ld+json">${JSON.stringify(jsonLd)}</script>`;
879
+ }
880
+ function generateResourceHints(data) {
881
+ const hints = [];
882
+ hints.push(`<link rel="dns-prefetch" href="//fonts.googleapis.com">`);
883
+ hints.push(`<link rel="dns-prefetch" href="//fonts.gstatic.com">`);
884
+ hints.push(`<link rel="preconnect" href="https://fonts.googleapis.com" crossorigin>`);
885
+ hints.push(`<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>`);
886
+ if (data.chunks) {
887
+ for (const chunk of data.chunks.slice(0, 3)) {
888
+ hints.push(`<link rel="modulepreload" href="/${chunk}">`);
889
+ }
890
+ }
891
+ return hints.join("");
892
+ }
893
+ function generateBuildId() {
894
+ const timestamp = Date.now().toString(36);
895
+ const random = Math.random().toString(36).substring(2, 8);
896
+ return `${timestamp}-${random}`;
897
+ }
898
+
899
+ // olova.ts
900
+ function olova(options = {}) {
901
+ const virtualModuleId = "virtual:olova-entry";
902
+ const serverVirtualModuleId = "virtual:olova-server-entry";
903
+ const workerVirtualModuleId = "virtual:olova-worker-entry";
904
+ const appVirtualModuleId = "virtual:olova-app";
905
+ const resolvedAppVirtualModuleId = "\0" + appVirtualModuleId;
906
+ let config;
907
+ const htmlContent = `<!--app-html-->`;
908
+ const minifyHtml = (html) => {
909
+ return html.replace(/>\s+</g, "><").replace(/\s{2,}/g, " ").replace(/<!--[\s\S]*?-->/g, "").trim();
910
+ };
911
+ return [
912
+ olovaRouter({ packageName: options.packageName }),
913
+ {
914
+ name: "vite-plugin-olova",
915
+ config(userConfig, { isSsrBuild }) {
916
+ const perfEnabled = options.performance?.enabled !== false;
917
+ if (isSsrBuild) {
918
+ return {
919
+ build: {
920
+ rollupOptions: {
921
+ input: userConfig.build?.rollupOptions?.input || virtualModuleId,
922
+ output: {
923
+ // Simple output for SSR
924
+ entryFileNames: "index.js",
925
+ chunkFileNames: "[name].js",
926
+ assetFileNames: "[name].[ext]",
927
+ format: "esm",
928
+ manualChunks: void 0
929
+ }
930
+ }
931
+ }
932
+ };
933
+ }
934
+ const buildConfig = {
935
+ // Output directory for assets
936
+ assetsDir: "assets/_olova",
937
+ // Report compressed size in build output
938
+ reportCompressedSize: true,
939
+ rollupOptions: {
940
+ input: userConfig.build?.rollupOptions?.input || virtualModuleId,
941
+ output: {
942
+ // Optimized chunk naming for caching
943
+ chunkFileNames: "assets/_olova/[name]-[hash].js",
944
+ entryFileNames: "assets/_olova/[name]-[hash].js",
945
+ assetFileNames: "assets/_olova/[name]-[hash].[ext]",
946
+ // Enable smart code splitting
947
+ ...perfEnabled && {
948
+ manualChunks: createManualChunks()
949
+ }
950
+ }
951
+ },
952
+ // Increase warning limit since we're doing smart chunking
953
+ chunkSizeWarningLimit: 500,
954
+ // Inline small assets (< 4KB)
955
+ assetsInlineLimit: 4096,
956
+ // Enable terser minification for smaller bundles
957
+ minify: "terser",
958
+ terserOptions: {
959
+ compress: {
960
+ // Aggressive optimizations for production
961
+ drop_console: false,
962
+ // Keep console for debugging
963
+ drop_debugger: true,
964
+ pure_funcs: ["console.debug"],
965
+ passes: 2
966
+ },
967
+ mangle: {
968
+ properties: false
969
+ },
970
+ format: {
971
+ comments: false
972
+ }
973
+ },
974
+ // Disable source maps for smaller builds
975
+ sourcemap: false,
976
+ // CSS code splitting
977
+ cssCodeSplit: true,
978
+ // Target modern browsers (smaller bundles)
979
+ target: "es2020"
980
+ };
981
+ return {
982
+ build: buildConfig,
983
+ // Optimize deps for faster dev startup
984
+ optimizeDeps: {
985
+ include: ["react", "react-dom"],
986
+ exclude: ["olova"]
987
+ },
988
+ // SSR options
989
+ ssr: {
990
+ noExternal: ["olova"]
991
+ },
992
+ // esbuild optimizations
993
+ esbuild: {
994
+ treeShaking: true,
995
+ legalComments: "none"
996
+ },
997
+ // Preview server configuration (for testing production builds)
998
+ preview: {
999
+ headers: {
1000
+ // Long-term caching for static assets
1001
+ "Cache-Control": "public, max-age=31536000"
1002
+ }
1003
+ }
1004
+ };
1005
+ },
1006
+ async configResolved(resolvedConfig) {
1007
+ config = resolvedConfig;
1008
+ },
1009
+ async resolveId(id) {
1010
+ if (id === virtualModuleId || id === serverVirtualModuleId || id === workerVirtualModuleId) {
1011
+ const isServer = id === serverVirtualModuleId;
1012
+ const isWorker = id === workerVirtualModuleId;
1013
+ const fileName = isWorker ? "entry-worker" : isServer ? "entry-server" : "main";
1014
+ const possibleLocalPaths = [
1015
+ path4.resolve(config.root, `src/${fileName}.tsx`),
1016
+ path4.resolve(config.root, `src/${fileName}.ts`),
1017
+ path4.resolve(config.root, `${fileName}.tsx`),
1018
+ path4.resolve(config.root, `plugins/${fileName}.tsx`)
1019
+ ];
1020
+ for (const p of possibleLocalPaths) {
1021
+ if (existsSync(p)) return p;
1022
+ }
1023
+ const exportName = isWorker ? "olova/entry-worker" : isServer ? "olova/entry-server" : "olova/main";
1024
+ try {
1025
+ const resolved = await this.resolve(exportName, void 0, { skipSelf: true });
1026
+ if (resolved && !resolved.external) {
1027
+ return resolved.id;
1028
+ }
1029
+ } catch (e) {
1030
+ }
1031
+ const pkgDir = path4.dirname(fileURLToPath(import.meta.url));
1032
+ const possiblePkgPaths = [
1033
+ path4.resolve(pkgDir, `dist/${fileName}.js`),
1034
+ path4.resolve(pkgDir, `${fileName}.js`),
1035
+ path4.resolve(pkgDir, `${fileName}.mjs`),
1036
+ path4.resolve(pkgDir, `${fileName}.ts`),
1037
+ path4.resolve(pkgDir, `${fileName}.tsx`),
1038
+ path4.resolve(pkgDir, `src/${fileName}.tsx`),
1039
+ path4.resolve(pkgDir, `src/${fileName}.ts`),
1040
+ // If running from dist, check parent directories
1041
+ path4.resolve(pkgDir, "..", `dist/${fileName}.js`),
1042
+ path4.resolve(pkgDir, "..", `${fileName}.js`),
1043
+ path4.resolve(pkgDir, "..", `${fileName}.tsx`),
1044
+ path4.resolve(pkgDir, "..", `src/${fileName}.tsx`)
1045
+ ];
1046
+ for (const p of possiblePkgPaths) {
1047
+ if (existsSync(p)) return p;
1048
+ }
1049
+ }
1050
+ if (id === appVirtualModuleId) {
1051
+ return resolvedAppVirtualModuleId;
1052
+ }
1053
+ return null;
1054
+ },
1055
+ load(id) {
1056
+ if (id === resolvedAppVirtualModuleId) {
1057
+ return `
1058
+ export { OlovaRouter, Outlet, routes, layouts, notFoundPages } from '@/route.tree';
1059
+ import '@/index.css';
1060
+ `;
1061
+ }
1062
+ return null;
1063
+ },
1064
+ configureServer(server) {
1065
+ const devBuildId = generateBuildId();
1066
+ logger_default.printBanner();
1067
+ server.middlewares.use(async (req, res, next) => {
1068
+ const url = req.url?.split("?")[0];
1069
+ if (url === "/" || url === "/index.html" || req.headers.accept?.includes("text/html") && !url?.match(/\.[a-z]+$/)) {
1070
+ try {
1071
+ logger_default.printSSRRender(url || "/");
1072
+ let template = htmlContent;
1073
+ const { render } = await server.ssrLoadModule(
1074
+ serverVirtualModuleId
1075
+ );
1076
+ const renderResult = await render(url || "/");
1077
+ const appHtml = typeof renderResult === "string" ? renderResult : renderResult.html;
1078
+ const routeMetadata = typeof renderResult === "string" ? {} : renderResult.metadata || {};
1079
+ const loaderData = typeof renderResult === "string" ? void 0 : renderResult.loaderData;
1080
+ const queryState = typeof renderResult === "string" ? void 0 : renderResult.queryState;
1081
+ let fullHtml = template.replace("<!--app-html-->", appHtml);
1082
+ const hydrationData = {
1083
+ route: url || "/",
1084
+ params: {},
1085
+ metadata: routeMetadata,
1086
+ chunks: [],
1087
+ // Dev mode doesn't have pre-built chunks
1088
+ isStatic: false,
1089
+ loaderData,
1090
+ queryState
1091
+ };
1092
+ const flightScripts = generateOlovaHydration(hydrationData, devBuildId);
1093
+ const jsonLdScript = generateJsonLd(hydrationData);
1094
+ const resourceHints = `<link rel="dns-prefetch" href="//fonts.googleapis.com"><link rel="preconnect" href="https://fonts.googleapis.com" crossorigin>`;
1095
+ if (fullHtml.includes("</head>")) {
1096
+ fullHtml = fullHtml.replace("</head>", `${jsonLdScript}${resourceHints}</head>`);
1097
+ }
1098
+ if (fullHtml.includes("</body>")) {
1099
+ fullHtml = fullHtml.replace(
1100
+ "</body>",
1101
+ `${flightScripts}<script type="module" src="/@id/${virtualModuleId}"></script></body>`
1102
+ );
1103
+ } else {
1104
+ fullHtml += `${flightScripts}<script type="module" src="/@id/${virtualModuleId}"></script>`;
1105
+ }
1106
+ fullHtml = await server.transformIndexHtml(url || "/", fullHtml);
1107
+ if (!fullHtml.trim().toLowerCase().startsWith("<!doctype html>")) {
1108
+ fullHtml = `<!DOCTYPE html>${fullHtml}`;
1109
+ }
1110
+ res.statusCode = 200;
1111
+ res.setHeader("Content-Type", "text/html");
1112
+ res.end(minifyHtml(fullHtml));
1113
+ return;
1114
+ } catch (e) {
1115
+ server.ssrFixStacktrace(e);
1116
+ console.error(e);
1117
+ res.statusCode = 500;
1118
+ res.end(e.stack);
1119
+ return;
1120
+ }
1121
+ }
1122
+ next();
1123
+ });
1124
+ },
1125
+ async writeBundle(_options, bundle) {
1126
+ if (config.command === "serve" || config.build.ssr) return;
1127
+ const buildStartTime = Date.now();
1128
+ logger_default.printBuildStart();
1129
+ const outDir = config.build.outDir;
1130
+ const serverOutDir = path4.resolve(config.root, "dist/server");
1131
+ const clientEntry = Object.values(bundle).find(
1132
+ (chunk) => chunk.type === "chunk" && chunk.isEntry && (chunk.facadeModuleId?.includes("main") || chunk.name === "main")
1133
+ );
1134
+ const clientEntryFileName = clientEntry ? clientEntry.fileName : "assets/index.js";
1135
+ const cssAssets = Object.values(bundle).filter(
1136
+ (chunk) => chunk.type === "asset" && chunk.fileName.endsWith(".css")
1137
+ );
1138
+ const cssLinks = cssAssets.map(
1139
+ (css) => `<link rel="stylesheet" crossorigin href="/${css.fileName}">`
1140
+ ).join("");
1141
+ await build({
1142
+ configFile: config.configFile,
1143
+ root: config.root,
1144
+ build: {
1145
+ ssr: true,
1146
+ emptyOutDir: false,
1147
+ outDir: serverOutDir,
1148
+ minify: false,
1149
+ // Keep readable for debugging
1150
+ rollupOptions: {
1151
+ input: { index: serverVirtualModuleId },
1152
+ output: {
1153
+ entryFileNames: "index.js",
1154
+ chunkFileNames: "[name].js",
1155
+ assetFileNames: "[name].[ext]",
1156
+ format: "esm",
1157
+ // Explicitly set to undefined to prevent client-side chunking config
1158
+ manualChunks: void 0
1159
+ }
1160
+ }
1161
+ },
1162
+ logLevel: "error"
1163
+ });
1164
+ const serverEntryPath = path4.resolve(serverOutDir, "index.js");
1165
+ const serverEntryUrl = pathToFileURL(serverEntryPath).toString();
1166
+ const { routes } = await import(serverEntryUrl);
1167
+ const paths = ["/"];
1168
+ function extractPaths(routesArr) {
1169
+ routesArr.forEach((r) => {
1170
+ if (r.path && !r.path.includes("*") && !r.path.includes(":")) {
1171
+ paths.push(r.path);
1172
+ }
1173
+ });
1174
+ }
1175
+ if (Array.isArray(routes)) extractPaths(routes);
1176
+ if (Array.isArray(routes)) {
1177
+ for (const r of routes) {
1178
+ const isDynamic = r.path.includes(":") || r.path.includes("*");
1179
+ if (r.getStaticPaths && isDynamic) {
1180
+ try {
1181
+ const staticPaths = await r.getStaticPaths();
1182
+ if (Array.isArray(staticPaths)) {
1183
+ for (const entry of staticPaths) {
1184
+ let expandedPath = r.path;
1185
+ if (entry.params) {
1186
+ for (const [key, value] of Object.entries(entry.params)) {
1187
+ expandedPath = expandedPath.replace(`:${key}`, value);
1188
+ expandedPath = expandedPath.replace("*", value);
1189
+ }
1190
+ }
1191
+ paths.push(expandedPath);
1192
+ }
1193
+ logger_default.info(`getStaticPaths for ${r.path}: ${staticPaths.length} paths expanded`);
1194
+ }
1195
+ } catch (e) {
1196
+ logger_default.warn(`getStaticPaths() failed for ${r.path}: ${e.message}`);
1197
+ }
1198
+ }
1199
+ }
1200
+ }
1201
+ if (options.staticPaths) {
1202
+ options.staticPaths.forEach((p) => paths.push(p));
1203
+ }
1204
+ const uniquePaths = [...new Set(paths)];
1205
+ const buildId = generateBuildId();
1206
+ logger_default.printSSGStart(buildId);
1207
+ const routeInfo = uniquePaths.map((p) => ({
1208
+ path: p,
1209
+ type: p.includes(":") || p.includes("*") ? "dynamic" : "static"
1210
+ }));
1211
+ logger_default.printRoutes(routeInfo);
1212
+ logger_default.printFlightInfo();
1213
+ const jsChunks = Object.values(bundle).filter((chunk) => chunk.type === "chunk" && chunk.fileName.endsWith(".js")).map((chunk) => chunk.fileName);
1214
+ let successCount = 0;
1215
+ let failCount = 0;
1216
+ for (const p of uniquePaths) {
1217
+ try {
1218
+ if (typeof globalThis.window !== "undefined") {
1219
+ globalThis.window.location.pathname = p;
1220
+ }
1221
+ const cacheBuster = `?t=${Date.now()}-${Math.random()}`;
1222
+ const { render } = await import(serverEntryUrl + cacheBuster);
1223
+ const renderResult = await render(p);
1224
+ const appHtml = typeof renderResult === "string" ? renderResult : renderResult.html;
1225
+ const routeMetadata = typeof renderResult === "string" ? {} : renderResult.metadata || {};
1226
+ const loaderData = typeof renderResult === "string" ? void 0 : renderResult.loaderData;
1227
+ const queryState = typeof renderResult === "string" ? void 0 : renderResult.queryState;
1228
+ let html = htmlContent.replace("<!--app-html-->", appHtml);
1229
+ const preloadHints = generatePreloadHints(jsChunks, clientEntryFileName);
1230
+ const preloadTags = generatePreloadTags(preloadHints);
1231
+ const hydrationData = {
1232
+ route: p,
1233
+ params: {},
1234
+ metadata: routeMetadata,
1235
+ chunks: jsChunks,
1236
+ isStatic: true,
1237
+ loaderData,
1238
+ queryState
1239
+ };
1240
+ const flightScripts = generateOlovaHydration(hydrationData, buildId);
1241
+ const jsonLdScript = generateJsonLd(hydrationData);
1242
+ const resourceHints = generateResourceHints(hydrationData);
1243
+ if (html.includes("</head>")) {
1244
+ html = html.replace("</head>", `${jsonLdScript}${resourceHints}${cssLinks}${preloadTags}</head>`);
1245
+ }
1246
+ if (html.includes("</body>")) {
1247
+ html = html.replace(
1248
+ "</body>",
1249
+ `${flightScripts}<script type="module" src="/${clientEntryFileName}"></script></body>`
1250
+ );
1251
+ } else {
1252
+ html += `${flightScripts}<script type="module" src="/${clientEntryFileName}"></script>`;
1253
+ }
1254
+ if (!html.trim().toLowerCase().startsWith("<!doctype html>")) {
1255
+ html = `<!DOCTYPE html>${html}`;
1256
+ }
1257
+ const itemPath = p === "/" ? "index.html" : `${p.substring(1)}/index.html`;
1258
+ const finalPath = path4.resolve(outDir, itemPath);
1259
+ await fs4.mkdir(path4.dirname(finalPath), { recursive: true });
1260
+ await fs4.writeFile(finalPath, minifyHtml(html));
1261
+ logger_default.printPageGenerated(itemPath, true);
1262
+ successCount++;
1263
+ } catch (e) {
1264
+ logger_default.printPageError(p, e.message);
1265
+ failCount++;
1266
+ }
1267
+ }
1268
+ try {
1269
+ const notFoundPath = "/__olova_404__";
1270
+ if (typeof globalThis.window !== "undefined") {
1271
+ globalThis.window.location.pathname = notFoundPath;
1272
+ }
1273
+ const cacheBuster404 = `?t=${Date.now()}-${Math.random()}`;
1274
+ const { render: render404 } = await import(serverEntryUrl + cacheBuster404);
1275
+ const result404 = await render404(notFoundPath);
1276
+ const html404Content = typeof result404 === "string" ? result404 : result404.html;
1277
+ let html404 = htmlContent.replace("<!--app-html-->", html404Content);
1278
+ const preloadHints404 = generatePreloadHints(jsChunks, clientEntryFileName);
1279
+ const preloadTags404 = generatePreloadTags(preloadHints404);
1280
+ const hydrationData404 = {
1281
+ route: "/404",
1282
+ params: {},
1283
+ metadata: { title: "404 - Page Not Found" },
1284
+ chunks: jsChunks,
1285
+ isStatic: true
1286
+ };
1287
+ const flightScripts404 = generateOlovaHydration(hydrationData404, buildId);
1288
+ const jsonLd404 = generateJsonLd(hydrationData404);
1289
+ const resourceHints404 = generateResourceHints(hydrationData404);
1290
+ if (html404.includes("</head>")) {
1291
+ html404 = html404.replace("</head>", `${jsonLd404}${resourceHints404}${cssLinks}${preloadTags404}</head>`);
1292
+ }
1293
+ if (html404.includes("</body>")) {
1294
+ html404 = html404.replace("</body>", `${flightScripts404}<script type="module" src="/${clientEntryFileName}"></script></body>`);
1295
+ } else {
1296
+ html404 += `${flightScripts404}<script type="module" src="/${clientEntryFileName}"></script>`;
1297
+ }
1298
+ if (!html404.trim().toLowerCase().startsWith("<!doctype html>")) {
1299
+ html404 = `<!DOCTYPE html>${html404}`;
1300
+ }
1301
+ const notFoundFinalPath = path4.resolve(outDir, "404.html");
1302
+ await fs4.writeFile(notFoundFinalPath, minifyHtml(html404));
1303
+ logger_default.printPageGenerated("404.html", true);
1304
+ successCount++;
1305
+ } catch (e) {
1306
+ logger_default.warn(`Failed to generate 404.html: ${e.message}`);
1307
+ }
1308
+ await fs4.rm(serverOutDir, { recursive: true, force: true });
1309
+ const buildTime = Date.now() - buildStartTime;
1310
+ logger_default.printSSGComplete({
1311
+ totalPages: successCount,
1312
+ successPages: successCount,
1313
+ failedPages: failCount,
1314
+ buildTime
1315
+ });
1316
+ }
1317
+ },
1318
+ // Add performance optimization plugins
1319
+ ...options.performance?.enabled !== false ? olovaPerformance(options.performance) : []
1320
+ ];
1321
+ }
1322
+
1323
+ export { olova };
1324
+ //# sourceMappingURL=olova.js.map
1325
+ //# sourceMappingURL=olova.js.map