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