olova 2.0.39 → 2.0.41

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 (156) hide show
  1. package/README.md +305 -12
  2. package/dist/client.d.ts +9 -0
  3. package/dist/client.d.ts.map +1 -0
  4. package/dist/client.js +8 -0
  5. package/dist/client.js.map +1 -0
  6. package/dist/core/config.d.ts +179 -0
  7. package/dist/core/config.d.ts.map +1 -0
  8. package/dist/core/config.js +33 -0
  9. package/dist/core/config.js.map +1 -0
  10. package/dist/core/index.d.ts +8 -0
  11. package/dist/core/index.d.ts.map +1 -0
  12. package/dist/core/index.js +7 -0
  13. package/dist/core/index.js.map +1 -0
  14. package/dist/core/types.d.ts +16 -0
  15. package/dist/core/types.d.ts.map +1 -0
  16. package/dist/core/types.js +2 -0
  17. package/dist/core/types.js.map +1 -0
  18. package/dist/data/hooks.d.ts +5 -0
  19. package/dist/data/hooks.d.ts.map +1 -0
  20. package/dist/data/hooks.js +19 -0
  21. package/dist/data/hooks.js.map +1 -0
  22. package/dist/data/index.d.ts +9 -0
  23. package/dist/data/index.d.ts.map +1 -0
  24. package/dist/data/index.js +9 -0
  25. package/dist/data/index.js.map +1 -0
  26. package/dist/data/provider.d.ts +10 -0
  27. package/dist/data/provider.d.ts.map +1 -0
  28. package/dist/data/provider.js +10 -0
  29. package/dist/data/provider.js.map +1 -0
  30. package/dist/data/serialize.d.ts +10 -0
  31. package/dist/data/serialize.d.ts.map +1 -0
  32. package/dist/data/serialize.js +16 -0
  33. package/dist/data/serialize.js.map +1 -0
  34. package/dist/data/types.d.ts +9 -0
  35. package/dist/data/types.d.ts.map +1 -0
  36. package/dist/data/types.js +2 -0
  37. package/dist/data/types.js.map +1 -0
  38. package/dist/head/Head.d.ts +8 -0
  39. package/dist/head/Head.d.ts.map +1 -0
  40. package/dist/head/Head.js +56 -0
  41. package/dist/head/Head.js.map +1 -0
  42. package/dist/head/HeadProvider.d.ts +31 -0
  43. package/dist/head/HeadProvider.d.ts.map +1 -0
  44. package/dist/head/HeadProvider.js +17 -0
  45. package/dist/head/HeadProvider.js.map +1 -0
  46. package/dist/head/index.d.ts +8 -0
  47. package/dist/head/index.d.ts.map +1 -0
  48. package/dist/head/index.js +8 -0
  49. package/dist/head/index.js.map +1 -0
  50. package/dist/head/ssr.d.ts +9 -0
  51. package/dist/head/ssr.d.ts.map +1 -0
  52. package/dist/head/ssr.js +35 -0
  53. package/dist/head/ssr.js.map +1 -0
  54. package/dist/hydration/context.d.ts +14 -0
  55. package/dist/hydration/context.d.ts.map +1 -0
  56. package/dist/hydration/context.js +20 -0
  57. package/dist/hydration/context.js.map +1 -0
  58. package/dist/hydration/index.d.ts +15 -0
  59. package/dist/hydration/index.d.ts.map +1 -0
  60. package/dist/hydration/index.js +25 -0
  61. package/dist/hydration/index.js.map +1 -0
  62. package/dist/hydration/payload.d.ts +46 -0
  63. package/dist/hydration/payload.d.ts.map +1 -0
  64. package/dist/hydration/payload.js +99 -0
  65. package/dist/hydration/payload.js.map +1 -0
  66. package/dist/hydration/scheduler.d.ts +16 -0
  67. package/dist/hydration/scheduler.d.ts.map +1 -0
  68. package/dist/hydration/scheduler.js +83 -0
  69. package/dist/hydration/scheduler.js.map +1 -0
  70. package/dist/hydration/seo.d.ts +10 -0
  71. package/dist/hydration/seo.d.ts.map +1 -0
  72. package/dist/hydration/seo.js +110 -0
  73. package/dist/hydration/seo.js.map +1 -0
  74. package/dist/hydration/serialization.d.ts +30 -0
  75. package/dist/hydration/serialization.d.ts.map +1 -0
  76. package/dist/hydration/serialization.js +99 -0
  77. package/dist/hydration/serialization.js.map +1 -0
  78. package/dist/hydration/types.d.ts +104 -0
  79. package/dist/hydration/types.d.ts.map +1 -0
  80. package/dist/hydration/types.js +2 -0
  81. package/dist/hydration/types.js.map +1 -0
  82. package/dist/index.d.ts +15 -84
  83. package/dist/index.d.ts.map +1 -0
  84. package/dist/index.js +17 -0
  85. package/dist/index.js.map +1 -1
  86. package/dist/plugin/cache.d.ts +66 -0
  87. package/dist/plugin/cache.d.ts.map +1 -0
  88. package/dist/plugin/cache.js +129 -0
  89. package/dist/plugin/cache.js.map +1 -0
  90. package/dist/plugin/index.d.ts +8 -0
  91. package/dist/plugin/index.d.ts.map +1 -0
  92. package/dist/plugin/index.js +8 -0
  93. package/dist/plugin/index.js.map +1 -0
  94. package/dist/plugin/plugin.d.ts +25 -0
  95. package/dist/plugin/plugin.d.ts.map +1 -0
  96. package/dist/plugin/plugin.js +601 -0
  97. package/dist/plugin/plugin.js.map +1 -0
  98. package/dist/router/Link.d.ts +7 -0
  99. package/dist/router/Link.d.ts.map +1 -0
  100. package/dist/router/Link.js +53 -0
  101. package/dist/router/Link.js.map +1 -0
  102. package/dist/router/Router.d.ts +7 -0
  103. package/dist/router/Router.d.ts.map +1 -0
  104. package/dist/router/Router.js +263 -0
  105. package/dist/router/Router.js.map +1 -0
  106. package/dist/router/context.d.ts +13 -0
  107. package/dist/router/context.d.ts.map +1 -0
  108. package/dist/router/context.js +47 -0
  109. package/dist/router/context.js.map +1 -0
  110. package/dist/router/hooks.d.ts +30 -0
  111. package/dist/router/hooks.d.ts.map +1 -0
  112. package/dist/router/hooks.js +59 -0
  113. package/dist/router/hooks.js.map +1 -0
  114. package/dist/router/index.d.ts +13 -0
  115. package/dist/router/index.d.ts.map +1 -0
  116. package/dist/router/index.js +14 -0
  117. package/dist/router/index.js.map +1 -0
  118. package/dist/router/matching.d.ts +19 -0
  119. package/dist/router/matching.d.ts.map +1 -0
  120. package/dist/router/matching.js +115 -0
  121. package/dist/router/matching.js.map +1 -0
  122. package/dist/router/navigation.d.ts +26 -0
  123. package/dist/router/navigation.d.ts.map +1 -0
  124. package/dist/router/navigation.js +65 -0
  125. package/dist/router/navigation.js.map +1 -0
  126. package/dist/router/types.d.ts +61 -0
  127. package/dist/router/types.d.ts.map +1 -0
  128. package/dist/router/types.js +6 -0
  129. package/dist/router/types.js.map +1 -0
  130. package/dist/server.d.ts +10 -16
  131. package/dist/server.d.ts.map +1 -0
  132. package/dist/server.js +7 -19
  133. package/dist/server.js.map +1 -1
  134. package/dist/streaming/html.d.ts +29 -0
  135. package/dist/streaming/html.d.ts.map +1 -0
  136. package/dist/streaming/html.js +72 -0
  137. package/dist/streaming/html.js.map +1 -0
  138. package/dist/streaming/index.d.ts +7 -0
  139. package/dist/streaming/index.d.ts.map +1 -0
  140. package/dist/streaming/index.js +7 -0
  141. package/dist/streaming/index.js.map +1 -0
  142. package/dist/streaming/render.d.ts +32 -0
  143. package/dist/streaming/render.d.ts.map +1 -0
  144. package/dist/streaming/render.js +83 -0
  145. package/dist/streaming/render.js.map +1 -0
  146. package/dist/streaming/utils.d.ts +30 -0
  147. package/dist/streaming/utils.d.ts.map +1 -0
  148. package/dist/streaming/utils.js +61 -0
  149. package/dist/streaming/utils.js.map +1 -0
  150. package/package.json +43 -35
  151. package/dist/router.d.ts +0 -3
  152. package/dist/router.js +0 -229
  153. package/dist/router.js.map +0 -1
  154. package/dist/vite.d.ts +0 -98
  155. package/dist/vite.js +0 -1828
  156. package/dist/vite.js.map +0 -1
package/dist/vite.js DELETED
@@ -1,1828 +0,0 @@
1
- // src/plugins/config.ts
2
- import fs from "fs";
3
- import path from "path";
4
- var colors = {
5
- reset: "\x1B[0m",
6
- bold: "\x1B[1m",
7
- dim: "\x1B[2m",
8
- cyan: "\x1B[36m",
9
- green: "\x1B[32m",
10
- white: "\x1B[37m"
11
- };
12
- function configPlugin() {
13
- const olovaDir = path.resolve(".olova");
14
- return {
15
- name: "olova-config",
16
- config(_config, { command }) {
17
- if (!fs.existsSync(olovaDir)) {
18
- fs.mkdirSync(olovaDir, { recursive: true });
19
- }
20
- if (process.env.IS_SSG_BUILD) {
21
- return {
22
- appType: "custom",
23
- cacheDir: "./.olova/cache"
24
- };
25
- }
26
- if (command === "serve") {
27
- console.log("");
28
- console.log(` ${colors.bold}${colors.cyan}\u{1F7E2} Olova${colors.reset} ${colors.dim}v1.0.0${colors.reset}`);
29
- console.log("");
30
- }
31
- return {
32
- appType: "custom",
33
- cacheDir: "./.olova/cache",
34
- build: {
35
- outDir: "./.olova/dist",
36
- sourcemap: false,
37
- chunkSizeWarningLimit: 500,
38
- rollupOptions: {
39
- onwarn(warning, warn) {
40
- if (warning.code === "PLUGIN_WARNING" && warning.message?.includes("dynamic import will not move")) {
41
- return;
42
- }
43
- if (warning.message?.includes("Module level directives") && warning.message?.includes("static")) {
44
- return;
45
- }
46
- if (warning.message?.includes("sourcemap") && warning.message?.includes("Can't resolve original location")) {
47
- return;
48
- }
49
- warn(warning);
50
- },
51
- output: {
52
- chunkFileNames: "pro_olova_static/chunks/[name]-[hash].js",
53
- entryFileNames: "pro_olova_static/olova-[hash].js",
54
- assetFileNames: "pro_olova_static/[name]-[hash][extname]",
55
- manualChunks(id) {
56
- if (id.includes("node_modules")) {
57
- if (id.includes("react-dom")) {
58
- return "vendor-react-dom";
59
- }
60
- if (id.includes("react")) {
61
- return "vendor-react";
62
- }
63
- if (id.includes("olova")) {
64
- return void 0;
65
- }
66
- return "vendor";
67
- }
68
- }
69
- }
70
- }
71
- },
72
- preview: {
73
- port: 4173,
74
- strictPort: false
75
- },
76
- // Exclude olova from dependency optimization so our virtual modules work
77
- optimizeDeps: {
78
- exclude: ["olova", "olova/router"]
79
- },
80
- // Also configure SSR to handle these modules
81
- ssr: {
82
- noExternal: ["olova", /^virtual:/]
83
- }
84
- };
85
- },
86
- configureServer(server) {
87
- server.httpServer?.once("listening", () => {
88
- const address = server.httpServer?.address();
89
- const port = typeof address === "object" && address ? address.port : 5173;
90
- setTimeout(() => {
91
- console.log(` ${colors.green}\u2713${colors.reset} Ready in ${colors.dim}${Math.round(performance.now())}ms${colors.reset}`);
92
- console.log("");
93
- console.log(` ${colors.dim}\u279C${colors.reset} ${colors.bold}Local:${colors.reset} ${colors.cyan}http://localhost:${port}/${colors.reset}`);
94
- console.log("");
95
- }, 100);
96
- });
97
- }
98
- };
99
- }
100
-
101
- // src/plugins/router.ts
102
- import fs2 from "fs";
103
- import path2 from "path";
104
- function routerPlugin() {
105
- const virtualModuleId = "virtual:olova-routes";
106
- const resolvedVirtualModuleId = "\0" + virtualModuleId;
107
- let server = null;
108
- const invalidateRoutes = () => {
109
- if (server) {
110
- const mod = server.moduleGraph.getModuleById(resolvedVirtualModuleId);
111
- if (mod) {
112
- server.moduleGraph.invalidateModule(mod);
113
- server.ws.send({
114
- type: "full-reload",
115
- path: "*"
116
- });
117
- }
118
- }
119
- };
120
- return {
121
- name: "olova-router",
122
- enforce: "pre",
123
- // Configure dev server to watch for file changes
124
- configureServer(devServer) {
125
- server = devServer;
126
- const srcDir = path2.resolve(process.cwd(), "src");
127
- const watcher = devServer.watcher;
128
- watcher.on("add", (filePath) => {
129
- if (filePath.startsWith(srcDir) && /\.(tsx|jsx|html|md)$/.test(filePath)) {
130
- console.log("\x1B[36m[olova]\x1B[0m New route detected:", path2.relative(srcDir, filePath));
131
- invalidateRoutes();
132
- }
133
- });
134
- watcher.on("unlink", (filePath) => {
135
- if (filePath.startsWith(srcDir) && /\.(tsx|jsx|html|md)$/.test(filePath)) {
136
- console.log("\x1B[36m[olova]\x1B[0m Route removed:", path2.relative(srcDir, filePath));
137
- invalidateRoutes();
138
- }
139
- });
140
- watcher.on("addDir", (dirPath) => {
141
- if (dirPath.startsWith(srcDir) && dirPath !== srcDir) {
142
- console.log("\x1B[36m[olova]\x1B[0m New route folder detected:", path2.relative(srcDir, dirPath));
143
- }
144
- });
145
- watcher.on("unlinkDir", (dirPath) => {
146
- if (dirPath.startsWith(srcDir) && dirPath !== srcDir) {
147
- console.log("\x1B[36m[olova]\x1B[0m Route folder removed:", path2.relative(srcDir, dirPath));
148
- invalidateRoutes();
149
- }
150
- });
151
- },
152
- resolveId(id) {
153
- if (id === virtualModuleId) {
154
- return resolvedVirtualModuleId;
155
- }
156
- },
157
- // Mark the virtual module as having side effects for proper HMR
158
- handleHotUpdate({ file, server: devServer }) {
159
- const srcDir = path2.resolve(process.cwd(), "src");
160
- if (file.startsWith(srcDir) && /\.(tsx|jsx)$/.test(file)) {
161
- const mod = devServer.moduleGraph.getModuleById(resolvedVirtualModuleId);
162
- if (mod) {
163
- devServer.moduleGraph.invalidateModule(mod);
164
- }
165
- }
166
- },
167
- load(id) {
168
- if (id === resolvedVirtualModuleId) {
169
- const srcDir = path2.resolve(process.cwd(), "src");
170
- const getRoutes = (dir, baseRoute = "", baseImportPath = "") => {
171
- const entries = fs2.readdirSync(dir, { withFileTypes: true });
172
- let routes = [];
173
- for (const entry of entries) {
174
- const entryName = entry.name;
175
- const fullPath = path2.join(dir, entryName);
176
- if (entry.isDirectory()) {
177
- const isRouteGroup = /^\(.+\)$/.test(entryName);
178
- if (isRouteGroup) {
179
- routes = routes.concat(getRoutes(
180
- fullPath,
181
- baseRoute,
182
- // URL path stays the same (group is ignored)
183
- `${baseImportPath}/${entryName}`
184
- // Import path includes the folder
185
- ));
186
- } else {
187
- routes = routes.concat(getRoutes(
188
- fullPath,
189
- `${baseRoute}/${entryName}`,
190
- `${baseImportPath}/${entryName}`
191
- ));
192
- }
193
- } else {
194
- const ext = path2.extname(entryName);
195
- const supportedExts = [".tsx", ".jsx", ".html", ".md"];
196
- if (supportedExts.includes(ext)) {
197
- const nameNoExt = entryName.replace(/\.(tsx|jsx|html|md)$/, "");
198
- if (nameNoExt === "root") continue;
199
- let routePath = baseRoute;
200
- if (nameNoExt !== "index" && nameNoExt !== "App") {
201
- routePath = `${baseRoute}/${nameNoExt}`;
202
- }
203
- if (routePath === "") routePath = "/";
204
- const normalizedRoutePath = routePath.replace(/\$(.+?)(?=\/|$)/g, ":$1");
205
- const importPath = `/src${baseImportPath}/${entryName}`;
206
- if (ext === ".html") {
207
- routes.push(` "${normalizedRoutePath}": () => import("${importPath}?raw").then(m => ({ default: () => { const div = document.createElement('div'); div.innerHTML = m.default; return div.innerHTML; }, __isHtml: true, __isStatic: true, __rawHtml: m.default })),`);
208
- } else if (ext === ".md") {
209
- routes.push(` "${normalizedRoutePath}": () => import("${importPath}?raw").then(m => ({ default: m.default, __isMd: true, __isStatic: true })),`);
210
- } else {
211
- const fileContent = fs2.readFileSync(fullPath, "utf-8");
212
- const firstLine = fileContent.trim().split("\n")[0].trim();
213
- const hasStaticDirective = firstLine === '"static"' || firstLine === "'static'";
214
- if (hasStaticDirective) {
215
- routes.push(` "${normalizedRoutePath}": () => import("${importPath}").then(m => Object.assign({}, m, { __isStatic: true })),`);
216
- } else {
217
- routes.push(` "${normalizedRoutePath}": () => import("${importPath}"),`);
218
- }
219
- }
220
- }
221
- }
222
- }
223
- return routes;
224
- };
225
- let routeLines = [];
226
- if (fs2.existsSync(srcDir)) {
227
- routeLines = getRoutes(srcDir);
228
- }
229
- return `export const routes = {
230
- ${routeLines.join("\n")}
231
- };`;
232
- }
233
- }
234
- };
235
- }
236
-
237
- // src/plugins/framework.ts
238
- import { transformWithEsbuild } from "vite";
239
- function frameworkPlugin() {
240
- const virtualClientEntry = "olova/client";
241
- const resolvedVirtualClientEntry = "\0" + virtualClientEntry;
242
- const virtualServerEntry = "olova/server";
243
- const resolvedVirtualServerEntry = "\0" + virtualServerEntry;
244
- return {
245
- name: "olova-framework",
246
- enforce: "pre",
247
- resolveId(id) {
248
- if (id === virtualClientEntry || id === "/olova/client" || id === "olova/client.tsx") return resolvedVirtualClientEntry;
249
- if (id === virtualServerEntry || id === "/olova/server" || id === "olova/server.tsx") return resolvedVirtualServerEntry;
250
- },
251
- async load(id) {
252
- if (id === resolvedVirtualClientEntry) {
253
- const code = `
254
- import React from 'react';
255
- import { hydrateRoot, createRoot } from 'react-dom/client';
256
- import Layout, { metadata as defaultMetadata } from '/src/root.tsx';
257
- import { Router, loadRoute } from 'olova/router';
258
-
259
- // Helper to generate SEO meta tags
260
- function generateSeoTags(metadata) {
261
- const meta = { ...defaultMetadata, ...metadata };
262
- const tags = [];
263
-
264
- // Title
265
- if (meta.title) {
266
- document.title = meta.title;
267
- }
268
-
269
- // Description
270
- if (meta.description) {
271
- let tag = document.querySelector('meta[name="description"]');
272
- if (!tag) {
273
- tag = document.createElement('meta');
274
- tag.setAttribute('name', 'description');
275
- document.head.appendChild(tag);
276
- }
277
- tag.setAttribute('content', meta.description);
278
- }
279
-
280
- // Keywords
281
- if (meta.keywords) {
282
- const content = Array.isArray(meta.keywords) ? meta.keywords.join(', ') : meta.keywords;
283
- let tag = document.querySelector('meta[name="keywords"]');
284
- if (!tag) {
285
- tag = document.createElement('meta');
286
- tag.setAttribute('name', 'keywords');
287
- document.head.appendChild(tag);
288
- }
289
- tag.setAttribute('content', content);
290
- }
291
-
292
- // Open Graph
293
- if (meta.openGraph) {
294
- const og = meta.openGraph;
295
- const ogTags = [
296
- ['og:title', og.title || meta.title],
297
- ['og:description', og.description],
298
- ['og:url', og.url],
299
- ['og:site_name', og.siteName],
300
- ['og:type', og.type],
301
- ];
302
- ogTags.forEach(([prop, content]) => {
303
- if (content) {
304
- let tag = document.querySelector(\`meta[property="\${prop}"]\`);
305
- if (!tag) {
306
- tag = document.createElement('meta');
307
- tag.setAttribute('property', prop);
308
- document.head.appendChild(tag);
309
- }
310
- tag.setAttribute('content', content);
311
- }
312
- });
313
- }
314
-
315
- // Twitter Card
316
- if (meta.twitter) {
317
- const tw = meta.twitter;
318
- const twTags = [
319
- ['twitter:card', tw.card || 'summary'],
320
- ['twitter:site', tw.site],
321
- ['twitter:creator', tw.creator],
322
- ['twitter:title', tw.title || meta.title],
323
- ['twitter:description', tw.description],
324
- ];
325
- twTags.forEach(([name, content]) => {
326
- if (content) {
327
- let tag = document.querySelector(\`meta[name="\${name}"]\`);
328
- if (!tag) {
329
- tag = document.createElement('meta');
330
- tag.setAttribute('name', name);
331
- document.head.appendChild(tag);
332
- }
333
- tag.setAttribute('content', content);
334
- }
335
- });
336
- }
337
- }
338
-
339
- const path = window.location.pathname;
340
- loadRoute(path).then((result) => {
341
- const Component = result ? result.module.default : () => <div>404 Not Found</div>;
342
- // @ts-ignore
343
- const serverData = window.__OLOVA_DATA__ || window.$OLOVA?.$route || {};
344
- const params = serverData.params || (result ? result.params : {});
345
- const metadata = serverData.metadata || (result ? result.metadata : undefined);
346
-
347
- // Framework handles SEO automatically (like Next.js)
348
- generateSeoTags(metadata);
349
-
350
- const app = (
351
- <React.StrictMode>
352
- <Layout>
353
- <Router url={path} initialComponent={Component} initialParams={params} onRouteChange={(newMetadata) => generateSeoTags(newMetadata)} />
354
- </Layout>
355
- </React.StrictMode>
356
- );
357
-
358
- // Always hydrate using the document as root since we always have a shell from SSG
359
- console.log("[Olova] Hydrating document with component:", Component ? (Component.name || 'Anonymous') : "NULL");
360
-
361
- // Debug loaded route result
362
- if (result) {
363
- console.log("[Olova] Route module keys:", Object.keys(result.module));
364
- } else {
365
- console.warn("[Olova] Route result is null/undefined");
366
- }
367
-
368
- hydrateRoot(document, app);
369
- });`;
370
- const result = await transformWithEsbuild(code, "olova-client.tsx", {
371
- loader: "tsx",
372
- jsx: "automatic"
373
- });
374
- return result.code;
375
- }
376
- if (id === resolvedVirtualServerEntry) {
377
- const code = `
378
- import React from 'react';
379
- import { renderToString } from 'react-dom/server';
380
- import Layout, { metadata as defaultMetadata } from '/src/root.tsx';
381
- import { routes } from 'virtual:olova-routes';
382
-
383
- // =============================================================================
384
- // INLINE ROUTER UTILITIES (to avoid external olova/router import)
385
- // =============================================================================
386
- const normalizePath = (path) => {
387
- let p = path.split('?')[0].split('#')[0];
388
- if (p.length > 1 && p.endsWith('/')) p = p.slice(0, -1);
389
- return p || '/';
390
- };
391
-
392
- function matchRoute(path) {
393
- const normalizedPath = normalizePath(path);
394
- const routeKeys = Object.keys(routes);
395
-
396
- for (const route of routeKeys) {
397
- if (route === normalizedPath) {
398
- return { loader: routes[route], params: {}, pattern: route };
399
- }
400
-
401
- const regexPath = route
402
- .replace(/:[^\\/]+/g, '([^/]+)')
403
- .replace(/\\$[^\\/]+/g, '([^/]+)');
404
-
405
- const regex = new RegExp(\`^\${regexPath}$\`);
406
- const match = normalizedPath.match(regex);
407
-
408
- if (match) {
409
- const params = {};
410
- const paramNames = (route.match(/[:\\$][^\\/]+/g) || []).map(s => s.slice(1));
411
- paramNames.forEach((name, i) => {
412
- params[name] = match[i + 1];
413
- });
414
- return { loader: routes[route], params, pattern: route };
415
- }
416
- }
417
- return null;
418
- }
419
-
420
- function parseMarkdown(md) {
421
- return md
422
- .replace(/^### (.*$)/gim, '<h3>$1</h3>')
423
- .replace(/^## (.*$)/gim, '<h2>$1</h2>')
424
- .replace(/^# (.*$)/gim, '<h1>$1</h1>')
425
- .replace(/\\*\\*\\*(.*?)\\*\\*\\*/g, '<strong><em>$1</em></strong>')
426
- .replace(/\\*\\*(.*?)\\*\\*/g, '<strong>$1</strong>')
427
- .replace(/\\*(.*?)\\*/g, '<em>$1</em>')
428
- .replace(/\`\`\`([\\s\\S]*?)\`\`\`/g, '<pre><code>$1</code></pre>')
429
- .replace(/\`(.*?)\`/g, '<code>$1</code>')
430
- .replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, '<a href="$2">$1</a>')
431
- .replace(/\\n\\n/g, '</p><p>')
432
- .replace(/\\n/g, '<br>')
433
- .replace(/^(.*)$/, '<p>$1</p>');
434
- }
435
-
436
- function HtmlContent({ html }) {
437
- return React.createElement('div', {
438
- dangerouslySetInnerHTML: { __html: html },
439
- className: 'olova-html-content'
440
- });
441
- }
442
-
443
- function MarkdownContent({ markdown }) {
444
- const html = parseMarkdown(markdown);
445
- return React.createElement('article', {
446
- dangerouslySetInnerHTML: { __html: html },
447
- className: 'olova-markdown-content'
448
- });
449
- }
450
-
451
- async function loadRoute(path) {
452
- const match = matchRoute(path);
453
- if (match) {
454
- const module = await match.loader();
455
-
456
- if (module.__isHtml) {
457
- return {
458
- module: {
459
- default: () => HtmlContent({ html: module.__rawHtml }),
460
- },
461
- params: match.params,
462
- metadata: undefined
463
- };
464
- }
465
-
466
- if (module.__isMd) {
467
- return {
468
- module: {
469
- default: () => MarkdownContent({ markdown: module.default }),
470
- },
471
- params: match.params,
472
- metadata: undefined
473
- };
474
- }
475
-
476
- return {
477
- module,
478
- params: match.params,
479
- metadata: module.metadata
480
- };
481
- }
482
- return null;
483
- }
484
-
485
- const RouterContext = React.createContext({ params: {}, path: '/' });
486
-
487
- function Router({ url, initialComponent, initialParams }) {
488
- const Component = initialComponent || (() => <div>Loading...</div>);
489
- const params = initialParams || {};
490
- // Server-side: Just render the Component directly (no Context needed)
491
- // The Context.Provider wrapping happens on client for hooks to work
492
- return <Component {...params} />;
493
- }
494
-
495
- // =============================================================================
496
- // SEO HEAD GENERATION
497
- // =============================================================================
498
- function generateSeoHead(metadata) {
499
- const meta = { ...defaultMetadata, ...metadata };
500
- let head = '';
501
-
502
- if (meta.title) {
503
- head += \`<title>\${meta.title}</title>\\n\`;
504
- }
505
-
506
- if (meta.description) {
507
- head += \`<meta name="description" content="\${meta.description}" />\\n\`;
508
- }
509
- if (meta.keywords) {
510
- const content = Array.isArray(meta.keywords) ? meta.keywords.join(', ') : meta.keywords;
511
- head += \`<meta name="keywords" content="\${content}" />\\n\`;
512
- }
513
- if (meta.robots) {
514
- head += \`<meta name="robots" content="\${meta.robots}" />\\n\`;
515
- }
516
- if (meta.canonical) {
517
- head += \`<link rel="canonical" href="\${meta.canonical}" />\\n\`;
518
- }
519
-
520
- if (meta.openGraph) {
521
- const og = meta.openGraph;
522
- head += \`<meta property="og:title" content="\${og.title || meta.title}" />\\n\`;
523
- if (og.description) head += \`<meta property="og:description" content="\${og.description}" />\\n\`;
524
- if (og.url) head += \`<meta property="og:url" content="\${og.url}" />\\n\`;
525
- if (og.siteName) head += \`<meta property="og:site_name" content="\${og.siteName}" />\\n\`;
526
- if (og.type) head += \`<meta property="og:type" content="\${og.type}" />\\n\`;
527
- if (og.images) {
528
- og.images.forEach(img => {
529
- head += \`<meta property="og:image" content="\${img.url}" />\\n\`;
530
- });
531
- }
532
- }
533
-
534
- if (meta.twitter) {
535
- const tw = meta.twitter;
536
- head += \`<meta name="twitter:card" content="\${tw.card || 'summary'}" />\\n\`;
537
- if (tw.site) head += \`<meta name="twitter:site" content="\${tw.site}" />\\n\`;
538
- if (tw.creator) head += \`<meta name="twitter:creator" content="\${tw.creator}" />\\n\`;
539
- head += \`<meta name="twitter:title" content="\${tw.title || meta.title}" />\\n\`;
540
- if (tw.description) head += \`<meta name="twitter:description" content="\${tw.description}" />\\n\`;
541
- if (tw.images) {
542
- tw.images.forEach(img => {
543
- head += \`<meta name="twitter:image" content="\${img}" />\\n\`;
544
- });
545
- }
546
- }
547
-
548
- return head;
549
- }
550
-
551
- // =============================================================================
552
- // SERVER RENDER FUNCTIONS
553
- // =============================================================================
554
- export async function render(url) {
555
- const result = await loadRoute(url);
556
- const Component = result ? result.module.default : () => <div>404 Not Found</div>;
557
- const params = result ? result.params : {};
558
- const metadata = result ? result.metadata : undefined;
559
-
560
- const seoHead = generateSeoHead(metadata);
561
-
562
- let html = renderToString(
563
- <Layout>
564
- <Router url={url} initialComponent={Component} initialParams={params} />
565
- </Layout>
566
- );
567
-
568
- if (html.includes('</head>')) {
569
- html = html.replace('</head>', seoHead + '</head>');
570
- }
571
-
572
- return { html, hydrationData: { params, metadata } };
573
- }
574
-
575
- export function renderShell() {
576
- const seoHead = generateSeoHead({});
577
- let html = renderToString(<Layout>{null}</Layout>);
578
- if (html.includes('</head>')) {
579
- html = html.replace('</head>', seoHead + '</head>');
580
- }
581
- return html;
582
- }
583
-
584
- export function renderShellWithMetadata(metadata) {
585
- const seoHead = generateSeoHead(metadata);
586
- let html = renderToString(<Layout>{null}</Layout>);
587
- if (html.includes('</head>')) {
588
- html = html.replace('</head>', seoHead + '</head>');
589
- }
590
- return html;
591
- }
592
-
593
- export { loadRoute };`;
594
- const result = await transformWithEsbuild(code, "olova-server.tsx", {
595
- loader: "tsx",
596
- jsx: "automatic"
597
- });
598
- return result.code;
599
- }
600
- }
601
- };
602
- }
603
-
604
- // src/plugins/virtual-html.ts
605
- import fs3 from "fs";
606
- import path3 from "path";
607
-
608
- // src/plugins/hydration.ts
609
- function generateOlovaHydration(data, buildId) {
610
- const meta = data.metadata || {};
611
- const scripts = [];
612
- scripts.push(`<script>self.__olova_f=self.__olova_f||[];function _oF(d){self.__olova_f.push(d)}</script>`);
613
- const routeChunk = {
614
- type: "R",
615
- data: {
616
- path: data.route,
617
- params: data.params || {},
618
- pattern: data.route === "/" ? "/" : data.route.replace(/\/[^/]+$/, "/:slug"),
619
- isStatic: data.isStatic ?? true,
620
- buildId
621
- }
622
- };
623
- scripts.push(`<script>_oF([0,"${routeChunk.type}",${JSON.stringify(routeChunk.data)}])</script>`);
624
- const metadataChunk = {
625
- type: "M",
626
- data: {
627
- title: meta.title || "Olova App",
628
- description: meta.description || "",
629
- keywords: Array.isArray(meta.keywords) ? meta.keywords : [],
630
- robots: meta.robots || "index, follow",
631
- canonical: meta.canonical || null,
632
- og: {
633
- type: "website",
634
- locale: "en_US",
635
- ...meta.openGraph || {}
636
- },
637
- twitter: {
638
- card: "summary_large_image",
639
- ...meta.twitter || {}
640
- }
641
- }
642
- };
643
- scripts.push(`<script>_oF([1,"${metadataChunk.type}",${JSON.stringify(metadataChunk.data)}])</script>`);
644
- const pageName = data.route === "/" ? "HomePage" : data.route.slice(1).split("/").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("") + "Page";
645
- const treeChunk = {
646
- type: "T",
647
- data: {
648
- layout: "RootLayout",
649
- page: pageName,
650
- template: null,
651
- loading: null,
652
- error: null,
653
- notFound: null
654
- }
655
- };
656
- scripts.push(`<script>_oF([2,"${treeChunk.type}",${JSON.stringify(treeChunk.data)}])</script>`);
657
- const structuredData = {
658
- type: "D",
659
- data: [
660
- {
661
- "@context": "https://schema.org",
662
- "@type": "WebPage",
663
- name: meta.title || "Olova App",
664
- description: meta.description || "",
665
- url: data.route
666
- },
667
- {
668
- "@context": "https://schema.org",
669
- "@type": "WebApplication",
670
- name: "Olova",
671
- applicationCategory: "WebApplication",
672
- operatingSystem: "Any"
673
- }
674
- ]
675
- };
676
- scripts.push(`<script>_oF([3,"${structuredData.type}",${JSON.stringify(structuredData.data)}])</script>`);
677
- const assetsChunk = {
678
- type: "A",
679
- data: {
680
- chunks: data.chunks || [],
681
- styles: [],
682
- // Prefetch hints for next likely navigations
683
- prefetch: (data.chunks || []).slice(0, 5)
684
- }
685
- };
686
- scripts.push(`<script>_oF([4,"${assetsChunk.type}",${JSON.stringify(assetsChunk.data)}])</script>`);
687
- const hintsChunk = {
688
- type: "H",
689
- data: {
690
- dnsPrefetch: ["fonts.googleapis.com", "fonts.gstatic.com"],
691
- preconnect: ["https://fonts.googleapis.com", "https://fonts.gstatic.com"],
692
- modulePreload: (data.chunks || []).slice(0, 3)
693
- }
694
- };
695
- scripts.push(`<script>_oF([5,"${hintsChunk.type}",${JSON.stringify(hintsChunk.data)}])</script>`);
696
- const stateChunk = {
697
- type: "S",
698
- data: {
699
- hydrated: false,
700
- streaming: false,
701
- ready: true,
702
- timestamp: Date.now(),
703
- version: "1.0.0",
704
- buildId
705
- }
706
- };
707
- scripts.push(`<script>_oF([6,"${stateChunk.type}",${JSON.stringify(stateChunk.data)}])</script>`);
708
- scripts.push(`<script>_oF([7,"E",null])</script>`);
709
- const globalPayload = {
710
- $route: routeChunk.data,
711
- $meta: metadataChunk.data,
712
- $tree: treeChunk.data,
713
- $schema: structuredData.data,
714
- $assets: assetsChunk.data,
715
- $hints: hintsChunk.data,
716
- $state: stateChunk.data,
717
- $build: {
718
- id: buildId,
719
- version: "1.0.0",
720
- timestamp: Date.now(),
721
- env: "production"
722
- }
723
- };
724
- scripts.push(`<script>$OLOVA=${JSON.stringify(globalPayload)}</script>`);
725
- return scripts.join("");
726
- }
727
- function generateJsonLd(data) {
728
- const meta = data.metadata || {};
729
- const jsonLd = {
730
- "@context": "https://schema.org",
731
- "@graph": [
732
- {
733
- "@type": "WebPage",
734
- "@id": data.route,
735
- name: meta.title || "Olova App",
736
- description: meta.description || "",
737
- url: data.route,
738
- isPartOf: {
739
- "@type": "WebSite",
740
- name: "Olova App"
741
- }
742
- },
743
- {
744
- "@type": "BreadcrumbList",
745
- itemListElement: data.route.split("/").filter(Boolean).map((segment, index, arr) => ({
746
- "@type": "ListItem",
747
- position: index + 1,
748
- name: segment.charAt(0).toUpperCase() + segment.slice(1),
749
- item: "/" + arr.slice(0, index + 1).join("/")
750
- }))
751
- }
752
- ]
753
- };
754
- return `<script type="application/ld+json">${JSON.stringify(jsonLd)}</script>`;
755
- }
756
- function parseFlightData() {
757
- if (typeof globalThis === "undefined" || typeof globalThis.document === "undefined") {
758
- return null;
759
- }
760
- const flightArray = globalThis.__olova_f;
761
- if (!flightArray) return null;
762
- const result = {};
763
- const typeMap = {
764
- "M": "$meta",
765
- "T": "$tree",
766
- "R": "$route",
767
- "P": "$params",
768
- "A": "$assets",
769
- "S": "$state",
770
- "D": "$schema",
771
- "H": "$hints",
772
- "E": "$end"
773
- };
774
- for (const chunk of flightArray) {
775
- if (Array.isArray(chunk) && chunk.length >= 3) {
776
- const [_index, type, data] = chunk;
777
- const key = typeMap[type];
778
- if (key && key !== "$end") {
779
- result[key] = data;
780
- }
781
- }
782
- }
783
- return result;
784
- }
785
-
786
- // src/plugins/virtual-html.ts
787
- function virtualHtmlPlugin() {
788
- let viteServer = null;
789
- const isStaticRoute = (routePath) => {
790
- const srcDir = path3.resolve("src");
791
- let filePath = routePath === "/" ? "" : routePath.slice(1);
792
- const htmlMdPaths = [
793
- path3.join(srcDir, filePath, "index.html"),
794
- path3.join(srcDir, filePath, "index.md"),
795
- path3.join(srcDir, filePath + ".html"),
796
- path3.join(srcDir, filePath + ".md")
797
- ];
798
- for (const p of htmlMdPaths) {
799
- if (fs3.existsSync(p)) {
800
- const ext = path3.extname(p).slice(1);
801
- return { isStatic: true, fileType: ext };
802
- }
803
- }
804
- const tsxJsxPaths = [
805
- path3.join(srcDir, filePath, "index.tsx"),
806
- path3.join(srcDir, filePath, "index.jsx"),
807
- path3.join(srcDir, filePath + ".tsx"),
808
- path3.join(srcDir, filePath + ".jsx"),
809
- path3.join(srcDir, "App.tsx"),
810
- path3.join(srcDir, "App.jsx")
811
- ];
812
- for (const p of tsxJsxPaths) {
813
- if (fs3.existsSync(p)) {
814
- const content = fs3.readFileSync(p, "utf-8");
815
- const firstLine = content.trim().split("\n")[0].trim();
816
- const isStatic = firstLine === '"static"' || firstLine === "'static'";
817
- const ext = path3.extname(p).slice(1);
818
- return { isStatic, fileType: ext };
819
- }
820
- }
821
- return { isStatic: false, fileType: null };
822
- };
823
- const extractMetadata = (filePath) => {
824
- try {
825
- if (!fs3.existsSync(filePath)) return {};
826
- const content = fs3.readFileSync(filePath, "utf-8");
827
- const metadataMatch = content.match(/export\s+const\s+metadata\s*=\s*(\{[\s\S]*?\});/);
828
- if (metadataMatch) {
829
- try {
830
- let metaStr = metadataMatch[1].replace(/'/g, '"').replace(/(\w+):/g, '"$1":').replace(/,\s*}/g, "}").replace(/,\s*]/g, "]");
831
- return JSON.parse(metaStr);
832
- } catch {
833
- const titleMatch = content.match(/title:\s*['"]([^'"]+)['"]/);
834
- const descMatch = content.match(/description:\s*['"]([^'"]+)['"]/);
835
- return {
836
- title: titleMatch?.[1] || "Olova App",
837
- description: descMatch?.[1] || ""
838
- };
839
- }
840
- }
841
- return {};
842
- } catch {
843
- return {};
844
- }
845
- };
846
- const getRouteMetadata = (routePath) => {
847
- const srcDir = path3.resolve("src");
848
- let filePath = routePath === "/" ? "" : routePath.slice(1);
849
- const possiblePaths = [
850
- path3.join(srcDir, filePath, "index.tsx"),
851
- path3.join(srcDir, filePath, "index.jsx"),
852
- path3.join(srcDir, filePath + ".tsx"),
853
- path3.join(srcDir, filePath + ".jsx"),
854
- path3.join(srcDir, "App.tsx"),
855
- path3.join(srcDir, "App.jsx")
856
- ];
857
- for (const p of possiblePaths) {
858
- if (fs3.existsSync(p)) {
859
- return extractMetadata(p);
860
- }
861
- }
862
- const rootPath = path3.join(srcDir, "root.tsx");
863
- if (fs3.existsSync(rootPath)) {
864
- return extractMetadata(rootPath);
865
- }
866
- return {};
867
- };
868
- const buildSeoHead = (routePath, metadata) => {
869
- const meta = metadata || getRouteMetadata(routePath);
870
- let headContent = "";
871
- if (meta.title) {
872
- headContent += `<title>${meta.title}</title>
873
- `;
874
- } else {
875
- headContent += `<title>Olova App</title>
876
- `;
877
- }
878
- if (meta.description) {
879
- headContent += `<meta name="description" content="${meta.description}" />
880
- `;
881
- }
882
- if (meta.keywords) {
883
- const keywords = Array.isArray(meta.keywords) ? meta.keywords.join(", ") : meta.keywords;
884
- headContent += `<meta name="keywords" content="${keywords}" />
885
- `;
886
- }
887
- const jsonLdScript = generateJsonLd({ route: routePath, metadata: meta });
888
- headContent += jsonLdScript + "\n";
889
- headContent += `<style id="olova-critical">*{box-sizing:border-box}html{-webkit-text-size-adjust:100%;line-height:1.5}body{font-family:system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif}</style>
890
- `;
891
- return headContent;
892
- };
893
- const buildBodyScripts = (routePath, metadata, isStatic) => {
894
- const meta = metadata || getRouteMetadata(routePath);
895
- const devBuildId = "dev-" + Date.now().toString(36);
896
- return generateOlovaHydration({
897
- route: routePath,
898
- metadata: meta,
899
- chunks: [],
900
- isStatic: isStatic || false
901
- }, devBuildId);
902
- };
903
- const renderShell = async () => {
904
- if (!viteServer) return "";
905
- try {
906
- const serverModule = await viteServer.ssrLoadModule("olova/server");
907
- if (serverModule?.renderShell) {
908
- return serverModule.renderShell() || "";
909
- }
910
- } catch (e) {
911
- }
912
- return "";
913
- };
914
- const renderFullPage = async (routePath) => {
915
- if (!viteServer) return { html: "", metadata: {} };
916
- try {
917
- const serverModule = await viteServer.ssrLoadModule("olova/server");
918
- if (serverModule?.render) {
919
- const result = await serverModule.render(routePath);
920
- return {
921
- html: result.html || "",
922
- metadata: result.hydrationData?.metadata || {}
923
- };
924
- }
925
- } catch (e) {
926
- console.error("[Olova] Full page render error:", e.message);
927
- }
928
- return { html: "", metadata: {} };
929
- };
930
- const injectIntoHtml = (html, seoHead, bodyScripts) => {
931
- let result = html;
932
- if (result.includes("</head>")) {
933
- result = result.replace("</head>", seoHead + "</head>");
934
- }
935
- if (result.includes("</body>")) {
936
- result = result.replace(
937
- "</body>",
938
- bodyScripts + '<script type="module" src="/olova/client"></script>\n</body>'
939
- );
940
- } else {
941
- result += bodyScripts + '<script type="module" src="/olova/client"></script>';
942
- }
943
- return result;
944
- };
945
- return {
946
- name: "olova-virtual-html",
947
- enforce: "pre",
948
- resolveId(id) {
949
- if (id === "index.html" || id === "virtual:index.html" || id === "/index.html") {
950
- return "virtual:olova-index.html";
951
- }
952
- },
953
- load(id) {
954
- if (id === "virtual:olova-index.html") {
955
- return `<!DOCTYPE html>
956
- <html lang="en">
957
- <head>
958
- <meta charset="UTF-8" />
959
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
960
- </head>
961
- <body>
962
- <script type="module" src="/olova/client"></script>
963
- </body>
964
- </html>`;
965
- }
966
- },
967
- configureServer(server) {
968
- viteServer = server;
969
- return () => {
970
- server.middlewares.use(async (req, res, next) => {
971
- const url = req.url || "/";
972
- const accept = req.headers.accept || "";
973
- if (!accept.includes("text/html")) {
974
- return next();
975
- }
976
- if (url.startsWith("/@") || url.startsWith("/__") || url.startsWith("/node_modules")) {
977
- return next();
978
- }
979
- if (/\.\w+$/.test(url) && !url.endsWith(".html")) {
980
- return next();
981
- }
982
- const routePath = url.split("?")[0].replace(/\/+$/, "") || "/";
983
- try {
984
- const { isStatic, fileType } = isStaticRoute(routePath);
985
- const routeMetadata = getRouteMetadata(routePath);
986
- let finalHtml;
987
- let metadata = { ...routeMetadata };
988
- if (isStatic) {
989
- const { html: fullHtml, metadata: ssrMetadata } = await renderFullPage(routePath);
990
- metadata = { ...routeMetadata, ...ssrMetadata };
991
- if (fullHtml) {
992
- const seoHead = buildSeoHead(routePath, metadata);
993
- const bodyScripts = buildBodyScripts(routePath, metadata, true);
994
- finalHtml = injectIntoHtml(fullHtml, seoHead, bodyScripts);
995
- } else {
996
- const shellHtml = await renderShell();
997
- const seoHead = buildSeoHead(routePath, metadata);
998
- const bodyScripts = buildBodyScripts(routePath, metadata, true);
999
- if (shellHtml) {
1000
- finalHtml = injectIntoHtml(shellHtml, seoHead, bodyScripts);
1001
- } else {
1002
- finalHtml = `<!DOCTYPE html>
1003
- <html lang="en">
1004
- <head>
1005
- <meta charset="UTF-8" />
1006
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
1007
- ${seoHead}
1008
- </head>
1009
- <body>
1010
- ${bodyScripts}
1011
- <script type="module" src="/olova/client"></script>
1012
- </body>
1013
- </html>`;
1014
- }
1015
- }
1016
- } else {
1017
- const shellHtml = await renderShell();
1018
- const seoHead = buildSeoHead(routePath, metadata);
1019
- const bodyScripts = buildBodyScripts(routePath, metadata, false);
1020
- if (shellHtml) {
1021
- finalHtml = injectIntoHtml(shellHtml, seoHead, bodyScripts);
1022
- } else {
1023
- finalHtml = `<!DOCTYPE html>
1024
- <html lang="en">
1025
- <head>
1026
- <meta charset="UTF-8" />
1027
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
1028
- ${seoHead}
1029
- </head>
1030
- <body>
1031
- ${bodyScripts}
1032
- <script type="module" src="/olova/client"></script>
1033
- </body>
1034
- </html>`;
1035
- }
1036
- }
1037
- if (!finalHtml.trim().toLowerCase().startsWith("<!doctype")) {
1038
- finalHtml = "<!DOCTYPE html>\n" + finalHtml;
1039
- }
1040
- const transformedHtml = await server.transformIndexHtml(url, finalHtml);
1041
- res.setHeader("Content-Type", "text/html");
1042
- res.statusCode = 200;
1043
- res.end(transformedHtml);
1044
- } catch (e) {
1045
- console.error("[Olova] Error:", e);
1046
- next(e);
1047
- }
1048
- });
1049
- };
1050
- }
1051
- };
1052
- }
1053
-
1054
- // src/plugins/ssg.ts
1055
- import { build } from "vite";
1056
- import fs4 from "fs";
1057
- import path4 from "path";
1058
- import { pathToFileURL } from "url";
1059
-
1060
- // src/plugins/utils.ts
1061
- function minifyHtml(html) {
1062
- return html.replace(/<!--(?!\[if).*?-->/gs, "").replace(/\s+/g, " ").replace(/> </g, "><").replace(/\s*(<[^>]+>)\s*/g, "$1").trim();
1063
- }
1064
- function generateBuildId() {
1065
- return Math.random().toString(36).substring(2, 15) + Date.now().toString(36);
1066
- }
1067
- function generateResourceHints() {
1068
- return `
1069
- <link rel="dns-prefetch" href="//fonts.googleapis.com">
1070
- <link rel="dns-prefetch" href="//fonts.gstatic.com">
1071
- <link rel="dns-prefetch" href="//cdn.jsdelivr.net">
1072
- <link rel="preconnect" href="https://fonts.googleapis.com" crossorigin>
1073
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
1074
- `.trim().replace(/\n\s+/g, "");
1075
- }
1076
- function generateCriticalCSS() {
1077
- return `<style id="olova-critical">
1078
- *{box-sizing:border-box;margin:0;padding:0}
1079
- html{-webkit-text-size-adjust:100%;line-height:1.5}
1080
- body{font-family:system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif}
1081
- img,video{max-width:100%;height:auto}
1082
- [hidden]{display:none!important}
1083
- </style>`.replace(/\n\s*/g, "");
1084
- }
1085
- function generateServiceWorkerScript() {
1086
- return `<script>if('serviceWorker'in navigator){window.addEventListener('load',()=>{navigator.serviceWorker.register('/sw.js').catch(()=>{})})}</script>`;
1087
- }
1088
- function generateServiceWorkerContent(buildId, assets) {
1089
- return `// Olova Service Worker v${buildId}
1090
- const CACHE_NAME = 'olova-cache-${buildId}';
1091
- const ASSETS = ${JSON.stringify(assets)};
1092
-
1093
- self.addEventListener('install', (e) => {
1094
- e.waitUntil(caches.open(CACHE_NAME).then(cache => cache.addAll(ASSETS)));
1095
- self.skipWaiting();
1096
- });
1097
-
1098
- self.addEventListener('activate', (e) => {
1099
- e.waitUntil(caches.keys().then(keys => Promise.all(
1100
- keys.filter(k => k !== CACHE_NAME).map(k => caches.delete(k))
1101
- )));
1102
- });
1103
-
1104
- self.addEventListener('fetch', (e) => {
1105
- e.respondWith(caches.match(e.request).then(r => r || fetch(e.request)));
1106
- });
1107
- `;
1108
- }
1109
- function generatePerformanceMeta() {
1110
- return `<meta http-equiv="x-dns-prefetch-control" content="on"><meta name="color-scheme" content="light dark">`;
1111
- }
1112
-
1113
- // src/plugins/ssg.ts
1114
- var colors2 = {
1115
- reset: "\x1B[0m",
1116
- bold: "\x1B[1m",
1117
- dim: "\x1B[2m",
1118
- green: "\x1B[32m",
1119
- cyan: "\x1B[36m",
1120
- yellow: "\x1B[33m",
1121
- magenta: "\x1B[35m",
1122
- blue: "\x1B[34m",
1123
- red: "\x1B[31m",
1124
- white: "\x1B[37m",
1125
- gray: "\x1B[90m",
1126
- bgGreen: "\x1B[42m",
1127
- bgCyan: "\x1B[46m",
1128
- bgMagenta: "\x1B[45m"
1129
- };
1130
- var symbols = {
1131
- success: "\u2713",
1132
- arrow: "\u2192",
1133
- bullet: "\u25CB",
1134
- filled: "\u25CF",
1135
- lambda: "\u03BB",
1136
- static: "\u25CB",
1137
- ssr: "\u25CF",
1138
- info: "\u2139"
1139
- };
1140
- function formatTime(ms) {
1141
- if (ms < 1) return "<1ms";
1142
- if (ms < 10) return `${ms.toFixed(1)}ms`;
1143
- if (ms < 1e3) return `${Math.round(ms)}ms`;
1144
- if (ms < 6e4) return `${(ms / 1e3).toFixed(2)}s`;
1145
- const mins = Math.floor(ms / 6e4);
1146
- const secs = Math.round(ms % 6e4 / 1e3);
1147
- return `${mins}m ${secs}s`;
1148
- }
1149
- function formatBytes(bytes) {
1150
- if (bytes < 1024) return `${bytes} B`;
1151
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} kB`;
1152
- return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
1153
- }
1154
- var logger = {
1155
- header: (text) => {
1156
- console.log(`
1157
- ${colors2.bold}${colors2.white}${text}${colors2.reset}`);
1158
- },
1159
- info: (text) => {
1160
- console.log(`${colors2.cyan}${symbols.info}${colors2.reset} ${text}`);
1161
- },
1162
- success: (text) => {
1163
- console.log(`${colors2.green}${symbols.success}${colors2.reset} ${text}`);
1164
- },
1165
- route: (route, type, size, time) => {
1166
- const symbol = type === "static" ? symbols.static : symbols.ssr;
1167
- const color = type === "static" ? colors2.white : colors2.magenta;
1168
- const sizeStr = size ? ` ${colors2.dim}${formatBytes(size)}${colors2.reset}` : "";
1169
- const timeStr = time ? ` ${colors2.gray}(${formatTime(time)})${colors2.reset}` : "";
1170
- console.log(`${color}${symbol}${colors2.reset} ${route}${sizeStr}${timeStr}`);
1171
- },
1172
- step: (step, time) => {
1173
- const timeStr = time ? ` ${colors2.gray}${formatTime(time)}${colors2.reset}` : "";
1174
- console.log(` ${colors2.dim}${symbols.arrow}${colors2.reset} ${step}${timeStr}`);
1175
- },
1176
- buildComplete: (totalRoutes, staticRoutes, totalTime) => {
1177
- console.log("");
1178
- console.log(`${colors2.bold}${colors2.green}${symbols.success} Build completed!${colors2.reset}`);
1179
- console.log("");
1180
- console.log(` ${colors2.dim}Routes:${colors2.reset} ${totalRoutes} total`);
1181
- console.log(` ${colors2.dim}Static:${colors2.reset} ${staticRoutes} prerendered`);
1182
- console.log(` ${colors2.dim}Time:${colors2.reset} ${formatTime(totalTime)}`);
1183
- console.log("");
1184
- },
1185
- legend: () => {
1186
- console.log(`${colors2.dim}${symbols.static} Static${colors2.reset} ${colors2.dim}${symbols.ssr} SSR${colors2.reset} ${colors2.dim}\u03BB ISR${colors2.reset}`);
1187
- },
1188
- banner: (buildId) => {
1189
- console.log("");
1190
- console.log(`${colors2.bold}${colors2.cyan} \u{1F7E2} Olova${colors2.reset} ${colors2.dim}Static Export${colors2.reset}`);
1191
- console.log(`${colors2.dim} Build ID: ${buildId}${colors2.reset}`);
1192
- console.log("");
1193
- },
1194
- error: (text) => {
1195
- console.log(`${colors2.red}\u2717${colors2.reset} ${colors2.red}${text}${colors2.reset}`);
1196
- },
1197
- warn: (text) => {
1198
- console.log(`${colors2.yellow}\u26A0${colors2.reset} ${colors2.yellow}${text}${colors2.reset}`);
1199
- }
1200
- };
1201
- function ssgPlugin() {
1202
- return {
1203
- name: "olova-ssg",
1204
- apply: "build",
1205
- config() {
1206
- if (process.env.IS_SSG_BUILD) {
1207
- return {};
1208
- }
1209
- return {
1210
- build: {
1211
- manifest: true,
1212
- rollupOptions: {
1213
- input: "index.html"
1214
- }
1215
- }
1216
- };
1217
- },
1218
- async closeBundle() {
1219
- if (process.env.IS_SSG_BUILD) return;
1220
- process.env.IS_SSG_BUILD = "true";
1221
- const totalStartTime = performance.now();
1222
- const buildId = generateBuildId();
1223
- logger.banner(buildId);
1224
- logger.header("Generating static pages...");
1225
- const serverBuildStart = performance.now();
1226
- try {
1227
- await build({
1228
- configFile: "./vite.config.ts",
1229
- build: {
1230
- ssr: true,
1231
- outDir: ".olova/server",
1232
- rollupOptions: {
1233
- input: "olova/server"
1234
- },
1235
- emptyOutDir: true,
1236
- reportCompressedSize: false
1237
- },
1238
- // Ensure virtual modules are bundled, not left as external imports
1239
- ssr: {
1240
- noExternal: true
1241
- },
1242
- logLevel: "silent"
1243
- });
1244
- logger.step("Server bundle compiled", performance.now() - serverBuildStart);
1245
- } catch (e) {
1246
- logger.error("SSG Build Failed");
1247
- console.error(e);
1248
- process.env.IS_SSG_BUILD = "";
1249
- return;
1250
- } finally {
1251
- process.env.IS_SSG_BUILD = "";
1252
- }
1253
- const scanStart = performance.now();
1254
- const allRoutes = [];
1255
- const scan = (dir, base = "") => {
1256
- if (!fs4.existsSync(dir)) return;
1257
- const entries = fs4.readdirSync(dir, { withFileTypes: true });
1258
- for (const entry of entries) {
1259
- const fullPath = path4.join(dir, entry.name);
1260
- if (entry.isDirectory()) {
1261
- const isRouteGroup = /^\(.+\)$/.test(entry.name);
1262
- if (isRouteGroup) {
1263
- scan(fullPath, base);
1264
- } else {
1265
- scan(fullPath, `${base}/${entry.name}`);
1266
- }
1267
- } else {
1268
- const ext = path4.extname(entry.name);
1269
- const supportedExts = [".tsx", ".jsx", ".html", ".md"];
1270
- if (supportedExts.includes(ext)) {
1271
- const nameNoExt = entry.name.replace(/\.(tsx|jsx|html|md)$/, "");
1272
- if (nameNoExt === "root") continue;
1273
- let route = base;
1274
- if (nameNoExt !== "index" && nameNoExt !== "App") {
1275
- route = `${base}/${nameNoExt}`;
1276
- }
1277
- if (route === "") route = "/";
1278
- if (route.includes("$")) continue;
1279
- if (ext === ".html" || ext === ".md") {
1280
- allRoutes.push({ route, isStatic: true, filePath: fullPath });
1281
- } else {
1282
- const fileContent = fs4.readFileSync(fullPath, "utf-8");
1283
- const firstLine = fileContent.trim().split("\n")[0].trim();
1284
- const isStatic = firstLine === '"static"' || firstLine === "'static'";
1285
- allRoutes.push({ route, isStatic, filePath: fullPath });
1286
- }
1287
- }
1288
- }
1289
- }
1290
- };
1291
- scan(path4.resolve("src"));
1292
- logger.step(`Found ${allRoutes.length} routes`, performance.now() - scanStart);
1293
- const serverDir = path4.resolve(".olova/server");
1294
- let serverFile = fs4.readdirSync(serverDir).find(
1295
- (f) => (f === "server.js" || f.includes("server-entry")) && f.endsWith(".js")
1296
- );
1297
- if (!serverFile) {
1298
- logger.error("Could not find server build artifact");
1299
- return;
1300
- }
1301
- const serverEntryPath = path4.join(serverDir, serverFile);
1302
- const { render, renderShell, renderShellWithMetadata, loadRoute } = await import(pathToFileURL(serverEntryPath).href);
1303
- const assetsStart = performance.now();
1304
- const distDir = path4.resolve(".olova/dist");
1305
- const clientHtmlPath = path4.join(distDir, "index.html");
1306
- let shellHtml = renderShell();
1307
- if (!shellHtml.startsWith("<!DOCTYPE html>")) {
1308
- shellHtml = `<!DOCTYPE html>
1309
- ${shellHtml}`;
1310
- }
1311
- let template = "";
1312
- if (fs4.existsSync(clientHtmlPath)) {
1313
- template = fs4.readFileSync(clientHtmlPath, "utf-8");
1314
- }
1315
- const manifestPath = path4.join(distDir, ".vite", "manifest.json");
1316
- let cssLinks = [];
1317
- let entryScript = "";
1318
- if (fs4.existsSync(manifestPath)) {
1319
- try {
1320
- const manifest = JSON.parse(fs4.readFileSync(manifestPath, "utf-8"));
1321
- for (const [key, value] of Object.entries(manifest)) {
1322
- const entry = value;
1323
- if (entry.css && entry.css.length > 0) {
1324
- for (const cssFile of entry.css) {
1325
- const cssLink = `<link rel="stylesheet" href="/${cssFile}" />`;
1326
- if (!cssLinks.includes(cssLink)) {
1327
- cssLinks.push(cssLink);
1328
- }
1329
- }
1330
- }
1331
- if (entry.isEntry && entry.file) {
1332
- entryScript = `<script type="module" crossorigin src="/${entry.file}"></script>`;
1333
- logger.step(`Found entry script: ${entry.file}`);
1334
- }
1335
- }
1336
- if (cssLinks.length > 0) {
1337
- logger.step(`Found ${cssLinks.length} CSS file(s) from manifest`);
1338
- }
1339
- if (!entryScript) {
1340
- const staticDir = path4.join(distDir, "pro_olova_static");
1341
- if (fs4.existsSync(staticDir)) {
1342
- const files = fs4.readdirSync(staticDir);
1343
- const entryFile = files.find((f) => f.startsWith("olova-") && f.endsWith(".js"));
1344
- if (entryFile) {
1345
- entryScript = `<script type="module" crossorigin src="/pro_olova_static/${entryFile}"></script>`;
1346
- logger.step(`Found entry script (fallback): ${entryFile}`);
1347
- }
1348
- }
1349
- }
1350
- } catch (e) {
1351
- logger.warn("Could not parse manifest.json");
1352
- }
1353
- }
1354
- const scripts = template.match(/<script[\s\S]*?>[\s\S]*?<\/script>/gi) || [];
1355
- const links = template.match(/<link[\s\S]*?>/gi) || [];
1356
- const chunksDir = path4.join(distDir, "pro_olova_static", "chunks");
1357
- let preloadLinks = [];
1358
- if (fs4.existsSync(chunksDir)) {
1359
- const chunks = fs4.readdirSync(chunksDir).filter((f) => f.endsWith(".js"));
1360
- preloadLinks = chunks.map(
1361
- (chunk) => `<link rel="modulepreload" crossorigin href="/pro_olova_static/chunks/${chunk}" />`
1362
- );
1363
- }
1364
- const mainScriptMatch = template.match(/src="(\/pro_olova_static\/olova-[^"]+\.js)"/);
1365
- if (mainScriptMatch) {
1366
- preloadLinks.unshift(`<link rel="modulepreload" crossorigin href="${mainScriptMatch[1]}" />`);
1367
- }
1368
- const chunkList = [];
1369
- if (fs4.existsSync(chunksDir)) {
1370
- const chunks = fs4.readdirSync(chunksDir).filter((f) => f.endsWith(".js"));
1371
- chunks.forEach((chunk) => chunkList.push(`/pro_olova_static/chunks/${chunk}`));
1372
- }
1373
- const resourceHints = generateResourceHints();
1374
- const criticalCSS = generateCriticalCSS();
1375
- const performanceMeta = generatePerformanceMeta();
1376
- const swScript = generateServiceWorkerScript();
1377
- const performanceHead = [performanceMeta, resourceHints, criticalCSS].join("");
1378
- const allAssetsForSW = [
1379
- "/",
1380
- "/index.html",
1381
- ...chunkList,
1382
- ...cssLinks.map((l) => l.match(/href="([^"]+)"/)?.[1]).filter(Boolean),
1383
- ...links.map((l) => l.match(/href="([^"]+)"/)?.[1]).filter(Boolean),
1384
- ...entryScript.match(/src="([^"]+)"/) ? [entryScript.match(/src="([^"]+)"/)[1]] : []
1385
- ];
1386
- const swContent = generateServiceWorkerContent(buildId, allAssetsForSW);
1387
- fs4.writeFileSync(path4.join(distDir, "sw.js"), swContent);
1388
- const assets = [performanceHead, ...cssLinks, ...links, ...preloadLinks, ...scripts, entryScript, swScript].join("\n");
1389
- logger.step(`Assets optimized (${cssLinks.length} styles, ${preloadLinks.length} preloads, SW ready)`, performance.now() - assetsStart);
1390
- console.log("");
1391
- logger.header("Route");
1392
- logger.legend();
1393
- console.log("");
1394
- let staticCount = 0;
1395
- const routeResults = [];
1396
- for (const { route, isStatic } of allRoutes) {
1397
- const routeStart = performance.now();
1398
- let finalHtml = "";
1399
- let isFullRender = false;
1400
- try {
1401
- const { html, hydrationData } = await render(route);
1402
- if (html) {
1403
- finalHtml = html;
1404
- isFullRender = true;
1405
- staticCount++;
1406
- if (Object.keys(hydrationData.params || {}).length > 0) {
1407
- const scriptTag = `<script>window.__OLOVA_DATA__ = ${JSON.stringify(hydrationData)};</script>`;
1408
- if (finalHtml.includes("</body>")) {
1409
- finalHtml = finalHtml.replace("</body>", `${scriptTag}
1410
- </body>`);
1411
- } else {
1412
- finalHtml += `
1413
- ${scriptTag}`;
1414
- }
1415
- }
1416
- const jsonLdScript = generateJsonLd({ route, metadata: hydrationData.metadata });
1417
- if (finalHtml.includes("</head>")) {
1418
- finalHtml = finalHtml.replace("</head>", `${jsonLdScript}${assets}
1419
- </head>`);
1420
- } else {
1421
- finalHtml = finalHtml.replace("</body>", `${assets}
1422
- </body>`);
1423
- }
1424
- if (!finalHtml.startsWith("<!DOCTYPE html>")) {
1425
- finalHtml = `<!DOCTYPE html>
1426
- ${finalHtml}`;
1427
- }
1428
- const flightScripts = generateOlovaHydration({
1429
- route,
1430
- metadata: hydrationData.metadata,
1431
- params: hydrationData.params,
1432
- chunks: chunkList,
1433
- isStatic: true
1434
- }, buildId);
1435
- if (finalHtml.includes("</body>")) {
1436
- finalHtml = finalHtml.replace("</body>", `${flightScripts}</body>`);
1437
- }
1438
- finalHtml = minifyHtml(finalHtml);
1439
- }
1440
- } catch (e) {
1441
- console.error(`[SSG] Render failed for ${route}:`, e);
1442
- }
1443
- if (!isFullRender) {
1444
- let pageMetadata = {};
1445
- try {
1446
- const routeResult = await loadRoute(route);
1447
- if (routeResult && routeResult.metadata) {
1448
- pageMetadata = routeResult.metadata;
1449
- }
1450
- } catch (e) {
1451
- }
1452
- finalHtml = renderShellWithMetadata(pageMetadata);
1453
- if (!finalHtml.startsWith("<!DOCTYPE html>")) {
1454
- finalHtml = `<!DOCTYPE html>
1455
- ${finalHtml}`;
1456
- }
1457
- const jsonLdScript = generateJsonLd({ route, metadata: pageMetadata });
1458
- if (finalHtml.includes("</head>")) {
1459
- finalHtml = finalHtml.replace("</head>", `${jsonLdScript}${assets}
1460
- </head>`);
1461
- } else {
1462
- finalHtml = finalHtml.replace("</body>", `${assets}
1463
- </body>`);
1464
- }
1465
- const flightScripts = generateOlovaHydration({
1466
- route,
1467
- metadata: pageMetadata,
1468
- chunks: chunkList,
1469
- isStatic: false
1470
- }, buildId);
1471
- if (finalHtml.includes("</body>")) {
1472
- finalHtml = finalHtml.replace("</body>", `${flightScripts}</body>`);
1473
- }
1474
- }
1475
- const outPath = path4.join(distDir, route === "/" ? "index.html" : `${route}/index.html`);
1476
- fs4.mkdirSync(path4.dirname(outPath), { recursive: true });
1477
- fs4.writeFileSync(outPath, finalHtml);
1478
- const routeTime = performance.now() - routeStart;
1479
- const routeSize = Buffer.byteLength(finalHtml, "utf8");
1480
- routeResults.push({ route, type: isStatic ? "static" : "ssr", size: routeSize, time: routeTime });
1481
- logger.route(route, isStatic ? "static" : "ssr", routeSize, routeTime);
1482
- }
1483
- const fallbackPath = path4.join(distDir, "404.html");
1484
- if (fs4.existsSync(clientHtmlPath)) {
1485
- fs4.copyFileSync(clientHtmlPath, fallbackPath);
1486
- }
1487
- const totalTime = performance.now() - totalStartTime;
1488
- logger.buildComplete(allRoutes.length, staticCount, totalTime);
1489
- console.log(`${colors2.dim} Output:${colors2.reset} .olova/dist`);
1490
- console.log("");
1491
- }
1492
- };
1493
- }
1494
-
1495
- // src/plugins/clean-url.ts
1496
- import fs5 from "fs";
1497
- import path5 from "path";
1498
- function cleanUrlPlugin() {
1499
- return {
1500
- name: "olova-clean-url",
1501
- configurePreviewServer(server) {
1502
- server.middlewares.use((req, res, next) => {
1503
- const urlPath = (req.url || "/").split("?")[0];
1504
- const distDir = path5.resolve(".olova/dist");
1505
- if (urlPath === "/") {
1506
- const indexPath2 = path5.join(distDir, "index.html");
1507
- if (fs5.existsSync(indexPath2)) {
1508
- const content = fs5.readFileSync(indexPath2, "utf-8");
1509
- res.setHeader("Content-Type", "text/html");
1510
- res.end(content);
1511
- return;
1512
- }
1513
- }
1514
- if (urlPath.includes(".")) return next();
1515
- const indexPath = path5.join(distDir, urlPath, "index.html");
1516
- if (fs5.existsSync(indexPath)) {
1517
- const content = fs5.readFileSync(indexPath, "utf-8");
1518
- res.setHeader("Content-Type", "text/html");
1519
- res.end(content);
1520
- return;
1521
- }
1522
- next();
1523
- });
1524
- }
1525
- };
1526
- }
1527
-
1528
- // src/plugins/auto-generate.ts
1529
- import fs6 from "fs";
1530
- import path6 from "path";
1531
- function toPascalCase(str) {
1532
- const cleanStr = str.replace(/^\$/, "");
1533
- return cleanStr.split(/[-_]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
1534
- }
1535
- function generateBoilerplate(folderName, isDynamic) {
1536
- const componentName = toPascalCase(folderName);
1537
- if (isDynamic) {
1538
- const paramName = folderName.replace(/^\$/, "");
1539
- return `import { useParams } from '../../route';
1540
-
1541
- export default function ${componentName}() {
1542
- const { ${paramName} } = useParams();
1543
- return (
1544
- <div>
1545
- <h1>${componentName} Page</h1>
1546
- <p>{${paramName}}</p>
1547
- </div>
1548
- );
1549
- }
1550
- `;
1551
- }
1552
- return `export default function ${componentName}() {
1553
- return (
1554
- <div>
1555
- <h1>${componentName} Page</h1>
1556
- <p>Welcome to ${componentName}</p>
1557
- </div>
1558
- );
1559
- }
1560
- `;
1561
- }
1562
- function autoGeneratePlugin() {
1563
- let srcDir;
1564
- return {
1565
- name: "olova-auto-generate",
1566
- configResolved(config) {
1567
- srcDir = path6.resolve(config.root, "src");
1568
- },
1569
- configureServer(server) {
1570
- console.log(
1571
- "\x1B[36m[olova] Auto-generate plugin active - watching for new route files\x1B[0m"
1572
- );
1573
- server.watcher.on("add", (filePath) => {
1574
- const normalizedPath = path6.normalize(filePath);
1575
- const normalizedSrcDir = path6.normalize(srcDir);
1576
- const ext = path6.extname(normalizedPath);
1577
- if (ext !== ".tsx" && ext !== ".jsx") {
1578
- return;
1579
- }
1580
- if (!normalizedPath.startsWith(normalizedSrcDir)) {
1581
- return;
1582
- }
1583
- const fileName = path6.basename(normalizedPath, ext);
1584
- if (fileName !== "index") {
1585
- return;
1586
- }
1587
- const folderPath = path6.dirname(normalizedPath);
1588
- const folderName = path6.basename(folderPath);
1589
- if (folderName === "src" || folderName.startsWith("(")) {
1590
- return;
1591
- }
1592
- try {
1593
- fs6.statSync(normalizedPath);
1594
- const content = fs6.readFileSync(normalizedPath, "utf-8");
1595
- if (content.trim().length > 10) {
1596
- return;
1597
- }
1598
- } catch (err) {
1599
- return;
1600
- }
1601
- const isDynamic = folderName.startsWith("$");
1602
- const boilerplate = generateBoilerplate(folderName, isDynamic);
1603
- try {
1604
- fs6.writeFileSync(normalizedPath, boilerplate, "utf-8");
1605
- console.log(
1606
- `\x1B[32m\u2713 [olova] Auto-generated: ${folderName}/index${ext}\x1B[0m`
1607
- );
1608
- } catch (err) {
1609
- console.error(
1610
- `\x1B[31m\u2717 [olova] Failed to write boilerplate:\x1B[0m`,
1611
- err
1612
- );
1613
- }
1614
- });
1615
- }
1616
- };
1617
- }
1618
-
1619
- // src/plugins/error-overlay.ts
1620
- import path7 from "path";
1621
- function proactiveErrorPlugin() {
1622
- return {
1623
- name: "olova-proactive-error",
1624
- enforce: "post",
1625
- configureServer(devServer) {
1626
- const srcDir = path7.resolve(process.cwd(), "src");
1627
- const validateFile = async (filePath) => {
1628
- if (!filePath.startsWith(srcDir)) return;
1629
- if (!/\.(tsx?|jsx?)$/.test(filePath)) return;
1630
- const relativePath = path7.relative(srcDir, filePath);
1631
- console.log("\x1B[36m[olova]\x1B[0m Checking:", relativePath);
1632
- try {
1633
- const url = "/" + path7.relative(process.cwd(), filePath).replace(/\\/g, "/");
1634
- await devServer.transformRequest(url);
1635
- console.log("\x1B[32m[olova]\x1B[0m \u2713", relativePath, "OK");
1636
- } catch (err) {
1637
- console.log("\x1B[31m[olova]\x1B[0m \u2717", relativePath);
1638
- devServer.ws.send({
1639
- type: "error",
1640
- err: {
1641
- message: err.message || String(err),
1642
- stack: err.stack || "",
1643
- id: err.id || filePath,
1644
- frame: err.frame || "",
1645
- plugin: err.plugin || "olova",
1646
- loc: err.loc || void 0
1647
- }
1648
- });
1649
- }
1650
- };
1651
- devServer.watcher.on("change", async (filePath) => {
1652
- await new Promise((r) => setTimeout(r, 50));
1653
- await validateFile(filePath);
1654
- });
1655
- devServer.watcher.on("add", async (filePath) => {
1656
- await new Promise((r) => setTimeout(r, 100));
1657
- await validateFile(filePath);
1658
- });
1659
- }
1660
- };
1661
- }
1662
-
1663
- // src/plugins/index.ts
1664
- function olovaPlugins() {
1665
- return [
1666
- configPlugin(),
1667
- // Must be first - creates .olova folder and sets config
1668
- routerPlugin(),
1669
- frameworkPlugin(),
1670
- virtualHtmlPlugin(),
1671
- ssgPlugin(),
1672
- cleanUrlPlugin(),
1673
- autoGeneratePlugin(),
1674
- // Auto-generates boilerplate for new route files
1675
- proactiveErrorPlugin()
1676
- // Checks all files on save for instant error detection
1677
- ];
1678
- }
1679
-
1680
- // src/plugins/terminal.ts
1681
- var colors3 = {
1682
- // Reset
1683
- reset: "\x1B[0m",
1684
- // Styles
1685
- bold: "\x1B[1m",
1686
- dim: "\x1B[2m",
1687
- italic: "\x1B[3m",
1688
- underline: "\x1B[4m",
1689
- // Foreground colors
1690
- black: "\x1B[30m",
1691
- red: "\x1B[31m",
1692
- green: "\x1B[32m",
1693
- yellow: "\x1B[33m",
1694
- blue: "\x1B[34m",
1695
- magenta: "\x1B[35m",
1696
- cyan: "\x1B[36m",
1697
- white: "\x1B[37m",
1698
- gray: "\x1B[90m",
1699
- // Background colors
1700
- bgBlack: "\x1B[40m",
1701
- bgRed: "\x1B[41m",
1702
- bgGreen: "\x1B[42m",
1703
- bgYellow: "\x1B[43m",
1704
- bgBlue: "\x1B[44m",
1705
- bgMagenta: "\x1B[45m",
1706
- bgCyan: "\x1B[46m",
1707
- bgWhite: "\x1B[47m"
1708
- };
1709
- var symbols2 = {
1710
- success: "\u2713",
1711
- error: "\u2717",
1712
- warning: "\u26A0",
1713
- info: "\u2139",
1714
- arrow: "\u2192",
1715
- arrowRight: "\u279C",
1716
- bullet: "\u25CB",
1717
- filled: "\u25CF",
1718
- lambda: "\u03BB",
1719
- triangle: "\u{1F7E2}",
1720
- square: "\u25A0",
1721
- diamond: "\u25C6",
1722
- star: "\u2605",
1723
- check: "\u2714",
1724
- cross: "\u2716",
1725
- pointer: "\u276F"
1726
- };
1727
- function formatTime2(ms) {
1728
- if (ms < 1) return "<1ms";
1729
- if (ms < 10) return `${ms.toFixed(1)}ms`;
1730
- if (ms < 1e3) return `${Math.round(ms)}ms`;
1731
- if (ms < 6e4) return `${(ms / 1e3).toFixed(2)}s`;
1732
- const mins = Math.floor(ms / 6e4);
1733
- const secs = Math.round(ms % 6e4 / 1e3);
1734
- return `${mins}m ${secs}s`;
1735
- }
1736
- function formatBytes2(bytes) {
1737
- if (bytes === 0) return "0 B";
1738
- const k = 1024;
1739
- const sizes = ["B", "kB", "MB", "GB"];
1740
- const i = Math.floor(Math.log(bytes) / Math.log(k));
1741
- return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`;
1742
- }
1743
- var logger2 = {
1744
- // Print banner
1745
- banner: (name, version = "1.0.0") => {
1746
- console.log("");
1747
- console.log(` ${colors3.bold}${colors3.cyan}${symbols2.triangle} ${name}${colors3.reset} ${colors3.dim}v${version}${colors3.reset}`);
1748
- console.log("");
1749
- },
1750
- // Print header
1751
- header: (text) => {
1752
- console.log(`
1753
- ${colors3.bold}${colors3.white}${text}${colors3.reset}`);
1754
- },
1755
- // Info message
1756
- info: (text) => {
1757
- console.log(`${colors3.cyan}${symbols2.info}${colors3.reset} ${text}`);
1758
- },
1759
- // Success message
1760
- success: (text, time) => {
1761
- const timeStr = time !== void 0 ? ` ${colors3.dim}${formatTime2(time)}${colors3.reset}` : "";
1762
- console.log(`${colors3.green}${symbols2.success}${colors3.reset} ${text}${timeStr}`);
1763
- },
1764
- // Error message
1765
- error: (text) => {
1766
- console.log(`${colors3.red}${symbols2.error}${colors3.reset} ${colors3.red}${text}${colors3.reset}`);
1767
- },
1768
- // Warning message
1769
- warn: (text) => {
1770
- console.log(`${colors3.yellow}${symbols2.warning}${colors3.reset} ${colors3.yellow}${text}${colors3.reset}`);
1771
- },
1772
- // Step in a process
1773
- step: (text, time) => {
1774
- const timeStr = time !== void 0 ? ` ${colors3.gray}${formatTime2(time)}${colors3.reset}` : "";
1775
- console.log(` ${colors3.dim}${symbols2.arrow}${colors3.reset} ${text}${timeStr}`);
1776
- },
1777
- // Route log (for SSG)
1778
- route: (route, type, size, time) => {
1779
- const symbol = type === "static" ? symbols2.bullet : type === "ssr" ? symbols2.filled : symbols2.lambda;
1780
- const color = type === "static" ? colors3.white : type === "ssr" ? colors3.magenta : colors3.cyan;
1781
- const sizeStr = size ? ` ${colors3.dim}${formatBytes2(size)}${colors3.reset}` : "";
1782
- const timeStr = time ? ` ${colors3.gray}(${formatTime2(time)})${colors3.reset}` : "";
1783
- console.log(`${color}${symbol}${colors3.reset} ${route}${sizeStr}${timeStr}`);
1784
- },
1785
- // Indent text
1786
- indent: (text, level = 1) => {
1787
- console.log(`${" ".repeat(level)}${text}`);
1788
- },
1789
- // Empty line
1790
- newline: () => console.log(""),
1791
- // Dim text
1792
- dim: (text) => `${colors3.dim}${text}${colors3.reset}`,
1793
- // Bold text
1794
- bold: (text) => `${colors3.bold}${text}${colors3.reset}`,
1795
- // Colored text
1796
- cyan: (text) => `${colors3.cyan}${text}${colors3.reset}`,
1797
- green: (text) => `${colors3.green}${text}${colors3.reset}`,
1798
- yellow: (text) => `${colors3.yellow}${text}${colors3.reset}`,
1799
- red: (text) => `${colors3.red}${text}${colors3.reset}`,
1800
- magenta: (text) => `${colors3.magenta}${text}${colors3.reset}`
1801
- };
1802
- function createTimer() {
1803
- const start = performance.now();
1804
- return {
1805
- elapsed: () => performance.now() - start,
1806
- format: () => formatTime2(performance.now() - start)
1807
- };
1808
- }
1809
- export {
1810
- colors3 as colors,
1811
- createTimer,
1812
- formatBytes2 as formatBytes,
1813
- formatTime2 as formatTime,
1814
- generateBuildId,
1815
- generateCriticalCSS,
1816
- generateJsonLd,
1817
- generateOlovaHydration,
1818
- generatePerformanceMeta,
1819
- generateResourceHints,
1820
- generateServiceWorkerContent,
1821
- generateServiceWorkerScript,
1822
- logger2 as logger,
1823
- minifyHtml,
1824
- olovaPlugins,
1825
- parseFlightData,
1826
- symbols2 as symbols
1827
- };
1828
- //# sourceMappingURL=vite.js.map