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.
- package/README.md +28 -288
- package/dist/chunk-23UAGQ6N.js +2208 -0
- package/dist/chunk-23UAGQ6N.js.map +1 -0
- package/dist/chunk-D7SIC5TC.js +367 -0
- package/dist/chunk-D7SIC5TC.js.map +1 -0
- package/dist/entry-server.cjs +2341 -0
- package/dist/entry-server.cjs.map +1 -0
- package/dist/entry-server.js +114 -0
- package/dist/entry-server.js.map +1 -0
- package/dist/entry-worker.cjs +2354 -0
- package/dist/entry-worker.cjs.map +1 -0
- package/dist/entry-worker.js +126 -0
- package/dist/entry-worker.js.map +1 -0
- package/dist/main.cjs +18 -0
- package/dist/main.cjs.map +1 -0
- package/dist/main.js +16 -0
- package/dist/main.js.map +1 -0
- package/dist/olova.cjs +1684 -0
- package/dist/olova.cjs.map +1 -0
- package/dist/olova.d.cts +72 -0
- package/dist/olova.d.ts +72 -0
- package/dist/olova.js +1325 -0
- package/dist/olova.js.map +1 -0
- package/dist/performance.cjs +386 -0
- package/dist/performance.cjs.map +1 -0
- package/dist/performance.js +3 -0
- package/dist/performance.js.map +1 -0
- package/dist/router.cjs +646 -0
- package/dist/router.cjs.map +1 -0
- package/dist/router.d.cts +113 -0
- package/dist/router.d.ts +113 -0
- package/dist/router.js +632 -0
- package/dist/router.js.map +1 -0
- package/main.tsx +76 -0
- package/olova.ts +619 -0
- package/package.json +42 -61
- package/src/entry-server.tsx +165 -0
- package/src/entry-worker.tsx +201 -0
- package/src/generator/index.ts +409 -0
- package/src/hydration/flight.ts +320 -0
- package/src/hydration/index.ts +12 -0
- package/src/hydration/types.ts +225 -0
- package/src/logger.ts +182 -0
- package/src/main.tsx +24 -0
- package/src/performance.ts +488 -0
- package/src/plugin/index.ts +204 -0
- package/src/router/ErrorBoundary.tsx +145 -0
- package/src/router/Link.tsx +117 -0
- package/src/router/OlovaRouter.tsx +354 -0
- package/src/router/Outlet.tsx +8 -0
- package/src/router/context.ts +117 -0
- package/src/router/index.ts +29 -0
- package/src/router/matching.ts +63 -0
- package/src/router/router.tsx +23 -0
- package/src/router/search-params.ts +29 -0
- package/src/scanner/index.ts +116 -0
- package/src/types/index.ts +191 -0
- package/src/utils/export.ts +85 -0
- package/src/utils/index.ts +4 -0
- package/src/utils/naming.ts +54 -0
- package/src/utils/path.ts +45 -0
- package/tsup.config.ts +35 -0
- package/CHANGELOG.md +0 -31
- package/LICENSE +0 -21
- package/dist/index.cjs +0 -883
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -138
- package/dist/index.d.ts +0 -138
- package/dist/index.js +0 -832
- package/dist/index.js.map +0 -1
- package/dist/plugin.cjs +0 -927
- package/dist/plugin.cjs.map +0 -1
- package/dist/plugin.d.cts +0 -18
- package/dist/plugin.d.ts +0 -18
- package/dist/plugin.js +0 -894
- package/dist/plugin.js.map +0 -1
- package/dist/ssg.cjs +0 -637
- package/dist/ssg.cjs.map +0 -1
- package/dist/ssg.d.cts +0 -191
- package/dist/ssg.d.ts +0 -191
- package/dist/ssg.js +0 -585
- package/dist/ssg.js.map +0 -1
- package/dist/types-BT6YsBGO.d.cts +0 -143
- 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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
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
|