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/plugin.js DELETED
@@ -1,894 +0,0 @@
1
- var __defProp = Object.defineProperty;
2
- var __getOwnPropNames = Object.getOwnPropertyNames;
3
- var __esm = (fn, res) => function __init() {
4
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
- };
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
-
11
- // src/ssg/crawler.ts
12
- import fs from "fs";
13
- import path from "path";
14
- async function crawlRoutes(config) {
15
- const appPath = path.resolve(config.root, config.appDir);
16
- if (!fs.existsSync(appPath)) {
17
- console.warn(`[olova-ssg] App directory not found: ${appPath}`);
18
- return [];
19
- }
20
- const pages = [];
21
- await scanDirectory(appPath, "/", config, pages);
22
- return pages;
23
- }
24
- async function scanDirectory(dir, routePath, config, pages) {
25
- const entries = fs.readdirSync(dir, { withFileTypes: true });
26
- for (const entry of entries) {
27
- const fullPath = path.join(dir, entry.name);
28
- if (entry.isDirectory()) {
29
- const segment = entry.name;
30
- if (segment.startsWith("(") && segment.endsWith(")")) {
31
- await scanDirectory(fullPath, routePath, config, pages);
32
- continue;
33
- }
34
- if (segment.startsWith("@")) {
35
- continue;
36
- }
37
- if (segment.startsWith("[")) {
38
- const newRoutePath2 = buildDynamicRoutePath(routePath, segment);
39
- await scanDirectory(fullPath, newRoutePath2, config, pages);
40
- continue;
41
- }
42
- const newRoutePath = routePath === "/" ? `/${segment}` : `${routePath}/${segment}`;
43
- await scanDirectory(fullPath, newRoutePath, config, pages);
44
- } else if (entry.isFile()) {
45
- const isPage = config.extensions.some((ext) => entry.name === `page${ext}`);
46
- if (isPage) {
47
- const isDynamicRoute = routePath.includes(":") || routePath.includes("*");
48
- const { metadata, hasGenerateMetadata } = await extractPageMetadata(fullPath);
49
- pages.push({
50
- path: routePath || "/",
51
- componentPath: fullPath,
52
- isStatic: !isDynamicRoute,
53
- params: {},
54
- metadata,
55
- hasGenerateMetadata
56
- });
57
- }
58
- }
59
- }
60
- }
61
- function buildDynamicRoutePath(basePath, segment) {
62
- let pathSegment;
63
- if (segment.startsWith("[[...") && segment.endsWith("]]")) {
64
- const paramName = segment.slice(5, -2);
65
- pathSegment = `:${paramName}*`;
66
- } else if (segment.startsWith("[...") && segment.endsWith("]")) {
67
- const paramName = segment.slice(4, -1);
68
- pathSegment = `:${paramName}*`;
69
- } else if (segment.startsWith("[") && segment.endsWith("]")) {
70
- const paramName = segment.slice(1, -1);
71
- pathSegment = `:${paramName}`;
72
- } else {
73
- pathSegment = segment;
74
- }
75
- return basePath === "/" ? `/${pathSegment}` : `${basePath}/${pathSegment}`;
76
- }
77
- async function extractPageMetadata(filePath) {
78
- try {
79
- const content = fs.readFileSync(filePath, "utf-8");
80
- const hasGenerateMetadata = /export\s+(async\s+)?function\s+generateMetadata/.test(content);
81
- const metadataMatch = content.match(/export\s+const\s+metadata\s*=\s*(\{[\s\S]*?\n\};?)/);
82
- if (metadataMatch) {
83
- try {
84
- const metadataStr = metadataMatch[1];
85
- const metadata = Function('"use strict"; return (' + metadataStr + ")")();
86
- return { metadata, hasGenerateMetadata };
87
- } catch {
88
- return { metadata: extractMetadataSimple(content), hasGenerateMetadata };
89
- }
90
- }
91
- return { metadata: null, hasGenerateMetadata };
92
- } catch (e) {
93
- console.warn(`[olova-ssg] Failed to extract metadata from ${filePath}`);
94
- return { metadata: null, hasGenerateMetadata: false };
95
- }
96
- }
97
- function extractMetadataSimple(content) {
98
- const metadata = {};
99
- const titleMatch = content.match(/title:\s*['"`]([^'"`]+)['"`]/);
100
- if (titleMatch) metadata.title = titleMatch[1];
101
- const descMatch = content.match(/description:\s*['"`]([^'"`]+)['"`]/);
102
- if (descMatch) metadata.description = descMatch[1];
103
- const keywordsMatch = content.match(/keywords:\s*\[([^\]]+)\]/);
104
- if (keywordsMatch) {
105
- const keywords = keywordsMatch[1].split(",").map((k) => k.trim().replace(/['"`]/g, "")).filter(Boolean);
106
- if (keywords.length) metadata.keywords = keywords;
107
- }
108
- return Object.keys(metadata).length > 0 ? metadata : null;
109
- }
110
- function getStaticPages(pages) {
111
- return pages.filter((page) => page.isStatic);
112
- }
113
- function getDynamicPages(pages) {
114
- return pages.filter((page) => !page.isStatic);
115
- }
116
- var init_crawler = __esm({
117
- "src/ssg/crawler.ts"() {
118
- "use strict";
119
- }
120
- });
121
-
122
- // src/ssg/generator.ts
123
- import fs2 from "fs";
124
- import path2 from "path";
125
- function generateSSREntry(pages, config) {
126
- const entryPath = path2.resolve(config.root, ".olova-ssg-entry.tsx");
127
- const imports = [];
128
- const routeMap = [];
129
- const appDir = path2.resolve(config.root, config.appDir);
130
- const layoutPath = findLayoutFile(appDir, config.extensions);
131
- if (layoutPath) {
132
- const relativePath = path2.relative(config.root, layoutPath).replace(/\\/g, "/");
133
- imports.push(`import Layout from './${relativePath}';`);
134
- }
135
- pages.forEach((page, index) => {
136
- const relativePath = path2.relative(config.root, page.componentPath).replace(/\\/g, "/");
137
- imports.push(`import Page${index} from './${relativePath}';`);
138
- routeMap.push(` '${page.path}': Page${index},`);
139
- });
140
- const entryContent = `
141
- // Auto-generated SSR entry for Olova SSG
142
- import React from 'react';
143
- import { renderToString } from 'react-dom/server';
144
- import { setSSRContext } from 'olova';
145
-
146
- ${imports.join("\n")}
147
-
148
- const routeComponents: Record<string, React.ComponentType<any>> = {
149
- ${routeMap.join("\n")}
150
- };
151
-
152
- export async function render(routePath: string, params: Record<string, string> = {}) {
153
- const Component = routeComponents[routePath];
154
-
155
- if (!Component) {
156
- console.warn('[olova-ssg] No component found for route:', routePath);
157
- return '';
158
- }
159
-
160
- try {
161
- // Set the SSR context before rendering
162
- setSSRContext(routePath, params);
163
-
164
- // Build component hierarchy
165
- const pageElement = React.createElement(Component, { params, searchParams: {} });
166
- ${layoutPath ? `const element = React.createElement(Layout, { children: pageElement, params });` : `const element = pageElement;`}
167
-
168
- return renderToString(element);
169
- } catch (error: any) {
170
- console.error('[olova-ssg] SSR Error for', routePath, ':', error.message);
171
- throw error;
172
- }
173
- }
174
-
175
- export const routes = ${JSON.stringify(pages.map((p) => ({ path: p.path, isStatic: p.isStatic })), null, 2)};
176
- `;
177
- fs2.writeFileSync(entryPath, entryContent, "utf-8");
178
- return entryPath;
179
- }
180
- function findLayoutFile(dir, extensions) {
181
- for (const ext of extensions) {
182
- const layoutPath = path2.join(dir, `layout${ext}`);
183
- if (fs2.existsSync(layoutPath)) {
184
- return layoutPath;
185
- }
186
- }
187
- return null;
188
- }
189
- function cleanupSSREntry(entryPath) {
190
- try {
191
- if (fs2.existsSync(entryPath)) {
192
- fs2.unlinkSync(entryPath);
193
- }
194
- } catch (e) {
195
- console.warn("[olova-ssg] Failed to cleanup SSR entry file");
196
- }
197
- }
198
- function generateHydrationScript(routePath, params) {
199
- return `
200
- <script>
201
- window.__OLOVA_DATA__ = ${JSON.stringify({ route: routePath, params, hydrated: false })};
202
- </script>
203
- `;
204
- }
205
- var init_generator = __esm({
206
- "src/ssg/generator.ts"() {
207
- "use strict";
208
- }
209
- });
210
-
211
- // src/ssg/renderer.ts
212
- import fs3 from "fs";
213
- import path3 from "path";
214
- import { pathToFileURL } from "url";
215
- import { build as viteBuild } from "vite";
216
- async function renderPage(page, config, ssrEntryPath) {
217
- const tempSSRDir = path3.join(config.root, ".olova", "ssr");
218
- if (!fs3.existsSync(tempSSRDir)) {
219
- fs3.mkdirSync(tempSSRDir, { recursive: true });
220
- }
221
- const pkgPath = path3.join(tempSSRDir, "package.json");
222
- if (!fs3.existsSync(pkgPath)) {
223
- fs3.writeFileSync(pkgPath, JSON.stringify({ type: "module" }));
224
- }
225
- const outputFileName = `ssr-entry-${Date.now()}.js`;
226
- const outputPath = path3.join(tempSSRDir, outputFileName);
227
- try {
228
- await viteBuild({
229
- configFile: false,
230
- root: config.root,
231
- logLevel: "silent",
232
- build: {
233
- ssr: true,
234
- write: true,
235
- outDir: tempSSRDir,
236
- emptyOutDir: false,
237
- rollupOptions: {
238
- input: ssrEntryPath,
239
- output: {
240
- format: "esm",
241
- entryFileNames: outputFileName
242
- },
243
- onwarn: (warning, handler) => {
244
- if (warning.code === "MODULE_LEVEL_DIRECTIVE") return;
245
- handler(warning);
246
- }
247
- }
248
- },
249
- plugins: [
250
- {
251
- name: "olova-ssr-transforms",
252
- transform(code) {
253
- return code.replace(/['"]use client['"];?/g, "").replace(/['"]use static['"];?/g, "");
254
- }
255
- }
256
- ]
257
- });
258
- if (fs3.existsSync(outputPath)) {
259
- const moduleUrl = pathToFileURL(outputPath).href;
260
- const mod = await import(moduleUrl);
261
- const content = await mod.render(page.path, page.params);
262
- return {
263
- content: content || "",
264
- metadata: page.metadata
265
- };
266
- }
267
- throw new Error("Built SSR artifact not found");
268
- } finally {
269
- if (fs3.existsSync(outputPath)) {
270
- try {
271
- fs3.unlinkSync(outputPath);
272
- } catch {
273
- }
274
- }
275
- }
276
- }
277
- async function renderPages(pages, config, ssrEntryPath, onProgress) {
278
- const results = /* @__PURE__ */ new Map();
279
- for (let i = 0; i < pages.length; i++) {
280
- const page = pages[i];
281
- if (onProgress) {
282
- onProgress(page, i, pages.length);
283
- }
284
- try {
285
- const result = await renderPage(page, config, ssrEntryPath);
286
- results.set(page.path, result);
287
- } catch (error) {
288
- console.error(`[olova-ssg] Failed to render ${page.path}:`, error.message);
289
- results.set(page.path, { content: "", metadata: page.metadata });
290
- }
291
- }
292
- return results;
293
- }
294
- var init_renderer = __esm({
295
- "src/ssg/renderer.ts"() {
296
- "use strict";
297
- }
298
- });
299
-
300
- // src/ssg/html.ts
301
- import fs4 from "fs";
302
- import path4 from "path";
303
- function generateHTML(options) {
304
- const {
305
- content,
306
- jsEntry,
307
- cssEntries,
308
- metadata,
309
- routePath,
310
- params
311
- } = options;
312
- const metaTags = metadata ? generateMetaTags(metadata) : "";
313
- const cssLinks = cssEntries.map((css) => `<link rel="stylesheet" href="${css}">`).join("\n ");
314
- return `<!doctype html>
315
- <html lang="en">
316
- <head>
317
- <meta charset="UTF-8" />
318
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
319
- ${metaTags}
320
- ${cssLinks}
321
- </head>
322
- <body>
323
- <div id="root">${content}</div>
324
- <script>
325
- window.__OLOVA_DATA__ = ${JSON.stringify({ route: routePath, params, hydrated: false })};
326
- </script>
327
- ${jsEntry ? `<script type="module" src="${jsEntry}"></script>` : ""}
328
- </body>
329
- </html>`;
330
- }
331
- function generateMetaTags(metadata) {
332
- const tags = [];
333
- if (metadata.title) {
334
- tags.push(`<title>${escapeHtml(metadata.title)}</title>`);
335
- }
336
- if (metadata.description) {
337
- tags.push(`<meta name="description" content="${escapeHtml(metadata.description)}" />`);
338
- }
339
- if (metadata.keywords?.length) {
340
- tags.push(`<meta name="keywords" content="${escapeHtml(metadata.keywords.join(", "))}" />`);
341
- }
342
- if (metadata.robots) {
343
- tags.push(`<meta name="robots" content="${escapeHtml(metadata.robots)}" />`);
344
- }
345
- if (metadata.canonical) {
346
- tags.push(`<link rel="canonical" href="${escapeHtml(metadata.canonical)}" />`);
347
- }
348
- if (metadata.openGraph) {
349
- const og = metadata.openGraph;
350
- if (og.title) {
351
- tags.push(`<meta property="og:title" content="${escapeHtml(og.title)}" />`);
352
- }
353
- if (og.description) {
354
- tags.push(`<meta property="og:description" content="${escapeHtml(og.description)}" />`);
355
- }
356
- if (og.url) {
357
- tags.push(`<meta property="og:url" content="${escapeHtml(og.url)}" />`);
358
- }
359
- if (og.siteName) {
360
- tags.push(`<meta property="og:site_name" content="${escapeHtml(og.siteName)}" />`);
361
- }
362
- if (og.type) {
363
- tags.push(`<meta property="og:type" content="${og.type}" />`);
364
- }
365
- if (og.images?.length) {
366
- for (const image of og.images) {
367
- tags.push(`<meta property="og:image" content="${escapeHtml(image.url)}" />`);
368
- if (image.width) {
369
- tags.push(`<meta property="og:image:width" content="${image.width}" />`);
370
- }
371
- if (image.height) {
372
- tags.push(`<meta property="og:image:height" content="${image.height}" />`);
373
- }
374
- if (image.alt) {
375
- tags.push(`<meta property="og:image:alt" content="${escapeHtml(image.alt)}" />`);
376
- }
377
- }
378
- }
379
- }
380
- if (metadata.twitter) {
381
- const tw = metadata.twitter;
382
- if (tw.card) {
383
- tags.push(`<meta name="twitter:card" content="${tw.card}" />`);
384
- }
385
- if (tw.title) {
386
- tags.push(`<meta name="twitter:title" content="${escapeHtml(tw.title)}" />`);
387
- }
388
- if (tw.description) {
389
- tags.push(`<meta name="twitter:description" content="${escapeHtml(tw.description)}" />`);
390
- }
391
- if (tw.creator) {
392
- tags.push(`<meta name="twitter:creator" content="${escapeHtml(tw.creator)}" />`);
393
- }
394
- if (tw.site) {
395
- tags.push(`<meta name="twitter:site" content="${escapeHtml(tw.site)}" />`);
396
- }
397
- if (tw.images?.length) {
398
- tags.push(`<meta name="twitter:image" content="${escapeHtml(tw.images[0])}" />`);
399
- }
400
- }
401
- if (metadata.authors?.length) {
402
- for (const author of metadata.authors) {
403
- tags.push(`<meta name="author" content="${escapeHtml(author.name)}" />`);
404
- }
405
- }
406
- return tags.join("\n ");
407
- }
408
- function discoverBuildAssets(distDir) {
409
- let jsEntry = "";
410
- const cssEntries = [];
411
- const assetsDir = path4.join(distDir, "assets");
412
- if (fs4.existsSync(assetsDir)) {
413
- const assets = fs4.readdirSync(assetsDir);
414
- for (const asset of assets) {
415
- if (asset.startsWith("index") && asset.endsWith(".js")) {
416
- jsEntry = `/assets/${asset}`;
417
- } else if (asset.endsWith(".css")) {
418
- cssEntries.push(`/assets/${asset}`);
419
- }
420
- }
421
- }
422
- return { jsEntry, cssEntries };
423
- }
424
- function injectContentIntoHTML(baseHtml, content, metadata) {
425
- let html = baseHtml;
426
- html = html.replace(
427
- /<div id="root"[^>]*>[\s\S]*?<\/div>/,
428
- `<div id="root">${content}</div>`
429
- );
430
- if (metadata) {
431
- const metaTags = generateMetaTags(metadata);
432
- if (metadata.title) {
433
- html = html.replace(/<title>[^<]*<\/title>/g, "");
434
- }
435
- if (metadata.description) {
436
- html = html.replace(/<meta\s+name="description"[^>]*>/g, "");
437
- }
438
- if (metadata.openGraph) {
439
- html = html.replace(/<meta\s+property="og:[^"]*"[^>]*>/g, "");
440
- }
441
- if (metadata.twitter) {
442
- html = html.replace(/<meta\s+name="twitter:[^"]*"[^>]*>/g, "");
443
- }
444
- html = html.replace(/<head>/, `<head>
445
- ${metaTags}`);
446
- }
447
- return html;
448
- }
449
- function escapeHtml(text) {
450
- return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
451
- }
452
- function generateSitemap(pages, siteUrl) {
453
- const urls = pages.map((page) => ` <url>
454
- <loc>${siteUrl}${page.path === "/" ? "" : page.path}</loc>
455
- <lastmod>${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}</lastmod>
456
- <changefreq>daily</changefreq>
457
- <priority>${page.path === "/" ? "1.0" : "0.8"}</priority>
458
- </url>`).join("\n");
459
- return `<?xml version="1.0" encoding="UTF-8"?>
460
- <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
461
- ${urls}
462
- </urlset>`;
463
- }
464
- function generateRobotsTxt(siteUrl) {
465
- return `User-agent: *
466
- Allow: /
467
-
468
- Sitemap: ${siteUrl}/sitemap.xml`;
469
- }
470
- var init_html = __esm({
471
- "src/ssg/html.ts"() {
472
- "use strict";
473
- }
474
- });
475
-
476
- // src/ssg/prerender.ts
477
- import fs5 from "fs";
478
- import path5 from "path";
479
- async function runSSG(config) {
480
- const startTime = Date.now();
481
- const failedPages = [];
482
- console.log("\n\u{1F4E6} Olova SSG - Starting static generation...\n");
483
- console.log("\u{1F50D} Discovering routes...");
484
- const allPages = await crawlRoutes(config);
485
- const staticPages = getStaticPages(allPages);
486
- console.log(` Found ${allPages.length} total routes`);
487
- console.log(` ${staticPages.length} static routes will be pre-rendered
488
- `);
489
- if (staticPages.length === 0) {
490
- console.log("\u26A0\uFE0F No static pages found to pre-render\n");
491
- return {
492
- totalPages: allPages.length,
493
- staticPages: 0,
494
- failedPages: [],
495
- duration: Date.now() - startTime
496
- };
497
- }
498
- console.log("\u{1F4DD} Generating SSR entry...");
499
- const ssrEntryPath = generateSSREntry(staticPages, config);
500
- const distDir = path5.resolve(config.root, config.outDir);
501
- const { jsEntry, cssEntries } = discoverBuildAssets(distDir);
502
- console.log(` JS entry: ${jsEntry || "(none)"}`);
503
- console.log(` CSS files: ${cssEntries.length}
504
- `);
505
- const baseHtmlPath = path5.join(distDir, "index.html");
506
- let baseHtml = "";
507
- if (fs5.existsSync(baseHtmlPath)) {
508
- baseHtml = fs5.readFileSync(baseHtmlPath, "utf-8");
509
- } else {
510
- console.warn("\u26A0\uFE0F No index.html found in dist, generating from scratch");
511
- }
512
- console.log("\u{1F528} Pre-rendering pages...\n");
513
- for (const page of staticPages) {
514
- const pageStartTime = Date.now();
515
- try {
516
- const result = await renderPage(page, config, ssrEntryPath);
517
- const outputPath = page.path === "/" ? path5.join(distDir, "index.html") : path5.join(distDir, page.path, "index.html");
518
- const outputDir = path5.dirname(outputPath);
519
- if (!fs5.existsSync(outputDir)) {
520
- fs5.mkdirSync(outputDir, { recursive: true });
521
- }
522
- let html;
523
- if (baseHtml) {
524
- html = injectContentIntoHTML(baseHtml, result.content, result.metadata);
525
- } else {
526
- html = generateHTML({
527
- content: result.content,
528
- jsEntry,
529
- cssEntries,
530
- metadata: result.metadata,
531
- routePath: page.path,
532
- params: page.params
533
- });
534
- }
535
- if (!html.includes("__OLOVA_DATA__")) {
536
- html = html.replace(
537
- "</body>",
538
- `<script>window.__OLOVA_DATA__ = ${JSON.stringify({ route: page.path, params: page.params, hydrated: false })};</script>
539
- </body>`
540
- );
541
- }
542
- fs5.writeFileSync(outputPath, html, "utf-8");
543
- const duration2 = Date.now() - pageStartTime;
544
- console.log(` \u2705 ${page.path} (${duration2}ms)`);
545
- } catch (error) {
546
- failedPages.push(page.path);
547
- console.log(` \u274C ${page.path} - ${error.message}`);
548
- }
549
- }
550
- console.log("\n\u{1F4C4} Generating SEO files...");
551
- const siteUrl = "https://example.com";
552
- const sitemap = generateSitemap(staticPages, siteUrl);
553
- fs5.writeFileSync(path5.join(distDir, "sitemap.xml"), sitemap, "utf-8");
554
- console.log(" \u2705 sitemap.xml");
555
- const robotsPath = path5.join(distDir, "robots.txt");
556
- if (!fs5.existsSync(robotsPath)) {
557
- fs5.writeFileSync(robotsPath, generateRobotsTxt(siteUrl), "utf-8");
558
- console.log(" \u2705 robots.txt");
559
- }
560
- cleanupSSREntry(ssrEntryPath);
561
- const buildManifest = {
562
- version: Date.now().toString(36),
563
- buildTime: (/* @__PURE__ */ new Date()).toISOString(),
564
- pages: allPages.map((p) => ({ path: p.path, isStatic: p.isStatic })),
565
- assets: { js: jsEntry, css: cssEntries }
566
- };
567
- fs5.writeFileSync(
568
- path5.join(distDir, "build-manifest.json"),
569
- JSON.stringify(buildManifest, null, 2),
570
- "utf-8"
571
- );
572
- const duration = Date.now() - startTime;
573
- const successCount = staticPages.length - failedPages.length;
574
- console.log(`
575
- \u2728 SSG Complete!`);
576
- console.log(` ${successCount}/${staticPages.length} pages generated`);
577
- console.log(` Duration: ${duration}ms
578
- `);
579
- return {
580
- totalPages: allPages.length,
581
- staticPages: successCount,
582
- failedPages,
583
- duration
584
- };
585
- }
586
- function createSSGConfig(root, options = {}) {
587
- return {
588
- root,
589
- appDir: options.appDir || "src/app",
590
- outDir: options.outDir || "dist",
591
- extensions: options.extensions || [".tsx", ".ts", ".jsx", ".js"],
592
- basePath: options.basePath || ""
593
- };
594
- }
595
- var init_prerender = __esm({
596
- "src/ssg/prerender.ts"() {
597
- "use strict";
598
- init_crawler();
599
- init_generator();
600
- init_renderer();
601
- init_html();
602
- }
603
- });
604
-
605
- // src/ssg/index.ts
606
- var ssg_exports = {};
607
- __export(ssg_exports, {
608
- cleanupSSREntry: () => cleanupSSREntry,
609
- crawlRoutes: () => crawlRoutes,
610
- createSSGConfig: () => createSSGConfig,
611
- discoverBuildAssets: () => discoverBuildAssets,
612
- generateHTML: () => generateHTML,
613
- generateHydrationScript: () => generateHydrationScript,
614
- generateMetaTags: () => generateMetaTags,
615
- generateRobotsTxt: () => generateRobotsTxt,
616
- generateSSREntry: () => generateSSREntry,
617
- generateSitemap: () => generateSitemap,
618
- getDynamicPages: () => getDynamicPages,
619
- getStaticPages: () => getStaticPages,
620
- injectContentIntoHTML: () => injectContentIntoHTML,
621
- renderPage: () => renderPage,
622
- renderPages: () => renderPages,
623
- runSSG: () => runSSG
624
- });
625
- var init_ssg = __esm({
626
- "src/ssg/index.ts"() {
627
- "use strict";
628
- init_prerender();
629
- init_crawler();
630
- init_generator();
631
- init_renderer();
632
- init_html();
633
- }
634
- });
635
-
636
- // src/plugin.ts
637
- var VIRTUAL_MODULE_ID = "virtual:olova-routes";
638
- var RESOLVED_VIRTUAL_MODULE_ID = "\0" + VIRTUAL_MODULE_ID;
639
- var VIRTUAL_METADATA_ID = "virtual:olova-metadata";
640
- var RESOLVED_VIRTUAL_METADATA_ID = "\0" + VIRTUAL_METADATA_ID;
641
- function olova(options = {}) {
642
- const {
643
- appDir = "src/app",
644
- extensions = [".tsx", ".ts", ".jsx", ".js"],
645
- basePath = "",
646
- trailingSlash = false,
647
- ssg = false
648
- } = options;
649
- let root;
650
- let routeManifest = null;
651
- let routeMetadataMap = /* @__PURE__ */ new Map();
652
- let rootMetadata = null;
653
- return {
654
- name: "vite-plugin-olova",
655
- enforce: "pre",
656
- configResolved(config) {
657
- root = config.root;
658
- },
659
- resolveId(id) {
660
- if (id === VIRTUAL_MODULE_ID) {
661
- return RESOLVED_VIRTUAL_MODULE_ID;
662
- }
663
- if (id === VIRTUAL_METADATA_ID) {
664
- return RESOLVED_VIRTUAL_METADATA_ID;
665
- }
666
- },
667
- async load(id) {
668
- if (id === RESOLVED_VIRTUAL_MODULE_ID) {
669
- const manifest = await generateRouteManifest(root, appDir, extensions);
670
- return generateRouteCode(manifest, appDir);
671
- }
672
- if (id === RESOLVED_VIRTUAL_METADATA_ID) {
673
- const metadataEntries = Array.from(routeMetadataMap.entries());
674
- return generateMetadataCode(metadataEntries, rootMetadata);
675
- }
676
- },
677
- // Configure Vite for SPA mode
678
- config() {
679
- return {
680
- appType: "spa"
681
- };
682
- },
683
- configureServer(server) {
684
- server.watcher.add(`${root}/${appDir}/**/*`);
685
- server.watcher.on("add", (file) => {
686
- if (isRouteFile(file, extensions)) {
687
- const mod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_MODULE_ID);
688
- if (mod) {
689
- server.moduleGraph.invalidateModule(mod);
690
- server.ws.send({ type: "full-reload" });
691
- }
692
- }
693
- });
694
- server.watcher.on("unlink", (file) => {
695
- if (isRouteFile(file, extensions)) {
696
- const mod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_MODULE_ID);
697
- if (mod) {
698
- server.moduleGraph.invalidateModule(mod);
699
- server.ws.send({ type: "full-reload" });
700
- }
701
- }
702
- });
703
- server.watcher.on("change", (file) => {
704
- if (isRouteFile(file, extensions)) {
705
- const mod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_MODULE_ID);
706
- if (mod) {
707
- server.moduleGraph.invalidateModule(mod);
708
- }
709
- const metaMod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_METADATA_ID);
710
- if (metaMod) {
711
- server.moduleGraph.invalidateModule(metaMod);
712
- }
713
- }
714
- });
715
- server.middlewares.use((req, res, next) => {
716
- const url = req.url || "";
717
- const accept = req.headers.accept || "";
718
- if (!accept.includes("text/html")) {
719
- return next();
720
- }
721
- if (url === "/" || url === "/index.html" || /\.[a-zA-Z0-9]+$/.test(url) || url.startsWith("/@") || url.startsWith("/node_modules") || url.startsWith("/__vite") || url.startsWith("/src") || url.startsWith("/api")) {
722
- return next();
723
- }
724
- req.url = "/index.html";
725
- next();
726
- });
727
- },
728
- async buildStart() {
729
- routeManifest = await generateRouteManifest(root, appDir, extensions);
730
- const routes = await scanRoutesWithMetadata(root, appDir, extensions);
731
- routeMetadataMap.clear();
732
- for (const route of routes) {
733
- routeMetadataMap.set(route.path, {
734
- routePath: route.path,
735
- metadata: route.metadata,
736
- hasGenerateMetadata: route.hasGenerateMetadata
737
- });
738
- if (route.path === "/") {
739
- rootMetadata = route.metadata;
740
- }
741
- }
742
- },
743
- // SSG: Generate separate HTML files for each route with pre-rendered content
744
- async closeBundle() {
745
- if (!ssg) return;
746
- const { runSSG: runSSG2, createSSGConfig: createSSGConfig2 } = await Promise.resolve().then(() => (init_ssg(), ssg_exports));
747
- const ssgConfig = createSSGConfig2(root, {
748
- appDir,
749
- outDir: "dist",
750
- extensions
751
- });
752
- try {
753
- await runSSG2(ssgConfig);
754
- } catch (error) {
755
- console.error("[olova] SSG failed:", error.message);
756
- }
757
- }
758
- };
759
- }
760
- function isRouteFile(file, extensions) {
761
- const routeFiles = ["page", "layout", "loading", "error", "not-found"];
762
- return extensions.some(
763
- (ext) => routeFiles.some((name) => file.endsWith(`${name}${ext}`))
764
- );
765
- }
766
- async function scanRoutesWithMetadata(root, appDir, extensions) {
767
- const fs6 = await import("fs");
768
- const path6 = await import("path");
769
- const routes = [];
770
- const appPath = path6.join(root, appDir);
771
- if (!fs6.existsSync(appPath)) {
772
- return routes;
773
- }
774
- async function scanDir(dir, routePath) {
775
- const entries = fs6.readdirSync(dir, { withFileTypes: true });
776
- for (const entry of entries) {
777
- const fullPath = path6.join(dir, entry.name);
778
- if (entry.isDirectory()) {
779
- let segment = entry.name;
780
- if (segment.startsWith("(") && segment.endsWith(")")) {
781
- await scanDir(fullPath, routePath);
782
- continue;
783
- }
784
- if (segment.startsWith("[") && segment.endsWith("]")) {
785
- if (segment.startsWith("[...")) {
786
- segment = ":" + segment.slice(4, -1) + "*";
787
- } else if (segment.startsWith("[[...")) {
788
- segment = ":" + segment.slice(5, -2) + "*";
789
- } else {
790
- segment = ":" + segment.slice(1, -1);
791
- }
792
- }
793
- const newRoutePath = routePath === "/" ? `/${segment}` : `${routePath}/${segment}`;
794
- await scanDir(fullPath, newRoutePath);
795
- } else if (entry.isFile()) {
796
- const isPage = extensions.some((ext) => entry.name === `page${ext}`);
797
- if (isPage) {
798
- const metadata = await extractMetadataFromFile(fullPath);
799
- const hasGenerateMetadata = await checkForGenerateMetadata(fullPath);
800
- routes.push({
801
- path: routePath || "/",
802
- filePath: fullPath,
803
- metadata,
804
- hasGenerateMetadata
805
- });
806
- }
807
- }
808
- }
809
- }
810
- await scanDir(appPath, "/");
811
- return routes;
812
- }
813
- async function extractMetadataFromFile(filePath) {
814
- const fs6 = await import("fs");
815
- try {
816
- const content = fs6.readFileSync(filePath, "utf-8");
817
- const metadataMatch = content.match(/export\s+const\s+metadata\s*=\s*(\{[\s\S]*?\n\};?)/);
818
- if (metadataMatch) {
819
- try {
820
- const metadataStr = metadataMatch[1];
821
- const jsonLike = metadataStr.replace(/'/g, '"').replace(/,(\s*[}\]])/g, "$1").replace(/(\w+):/g, '"$1":').replace(/""(\w+)"":/g, '"$1":');
822
- const metadata = Function('"use strict"; return (' + metadataStr + ")")();
823
- return metadata;
824
- } catch (e) {
825
- return extractMetadataSimple2(content);
826
- }
827
- }
828
- return null;
829
- } catch (e) {
830
- console.warn(`[olova] Failed to extract metadata from ${filePath}:`, e);
831
- return null;
832
- }
833
- }
834
- function extractMetadataSimple2(content) {
835
- const metadata = {};
836
- const titleMatch = content.match(/title:\s*['"`]([^'"`]+)['"`]/);
837
- if (titleMatch) metadata.title = titleMatch[1];
838
- const descMatch = content.match(/description:\s*['"`]([^'"`]+)['"`]/);
839
- if (descMatch) metadata.description = descMatch[1];
840
- const keywordsMatch = content.match(/keywords:\s*\[([^\]]+)\]/);
841
- if (keywordsMatch) {
842
- const keywords = keywordsMatch[1].split(",").map((k) => k.trim().replace(/['"`]/g, "")).filter(Boolean);
843
- if (keywords.length) metadata.keywords = keywords;
844
- }
845
- return Object.keys(metadata).length > 0 ? metadata : null;
846
- }
847
- async function checkForGenerateMetadata(filePath) {
848
- const fs6 = await import("fs");
849
- try {
850
- const content = fs6.readFileSync(filePath, "utf-8");
851
- return /export\s+(async\s+)?function\s+generateMetadata/.test(content);
852
- } catch (e) {
853
- return false;
854
- }
855
- }
856
- async function generateRouteManifest(root, appDir, extensions) {
857
- return {
858
- routes: [],
859
- staticPaths: /* @__PURE__ */ new Map(),
860
- generatedAt: Date.now()
861
- };
862
- }
863
- function generateRouteCode(manifest, appDir) {
864
- return `
865
- import { lazy } from 'react';
866
-
867
- // Auto-generated by olova
868
- // This file is generated at build time based on your file structure
869
-
870
- export const routes = [];
871
- export const manifest = ${JSON.stringify({ generatedAt: manifest.generatedAt })};
872
- `;
873
- }
874
- function generateMetadataCode(entries, rootMetadata) {
875
- const metadataMap = {};
876
- for (const [path6, routeMeta] of entries) {
877
- metadataMap[path6] = routeMeta.metadata;
878
- }
879
- return `
880
- // Auto-generated by olova - Route Metadata
881
- export const routeMetadata = ${JSON.stringify(metadataMap, null, 2)};
882
- export const rootMetadata = ${JSON.stringify(rootMetadata, null, 2)};
883
-
884
- export function getMetadataForRoute(path) {
885
- return routeMetadata[path] || rootMetadata || null;
886
- }
887
- `;
888
- }
889
- var plugin_default = olova;
890
- export {
891
- plugin_default as default,
892
- olova
893
- };
894
- //# sourceMappingURL=plugin.js.map