olova 2.0.21 → 2.0.23
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/dist/index.d.ts +83 -10
- package/dist/index.js +1 -32
- package/dist/index.js.map +1 -0
- package/dist/router.d.ts +3 -0
- package/dist/{chunk-Y3CEUWVD.js → router.js} +92 -28
- package/dist/router.js.map +1 -0
- package/dist/routes.d.ts +3 -0
- package/dist/routes.js +6 -0
- package/dist/routes.js.map +1 -0
- package/dist/vite.d.ts +100 -0
- package/dist/vite.js +1533 -0
- package/dist/vite.js.map +1 -0
- package/package.json +35 -38
- package/dist/Link.d.ts +0 -8
- package/dist/Link.js +0 -8
- package/dist/Router.d.ts +0 -53
- package/dist/Router.js +0 -11
- package/dist/chunk-CYX762OE.js +0 -21
- package/dist/chunk-K6EDUQDD.js +0 -19
- package/dist/chunk-LOK5UEME.js +0 -7
- package/dist/chunk-NEMX72XA.js +0 -39
- package/dist/chunk-XGX5YRJV.js +0 -27
- package/dist/client.d.ts +0 -5
- package/dist/client.js +0 -72
- package/dist/server.d.ts +0 -13
- package/dist/server.js +0 -87
- package/dist/useParams.d.ts +0 -4
- package/dist/useParams.js +0 -11
- package/dist/usePathname.d.ts +0 -7
- package/dist/usePathname.js +0 -8
- package/dist/useSearchParams.d.ts +0 -27
- package/dist/useSearchParams.js +0 -8
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,86 @@
|
|
|
1
|
-
import * as
|
|
2
|
-
|
|
3
|
-
export { ReadonlyURLSearchParams, default as useSearchParams } from './useSearchParams.js';
|
|
4
|
-
export { default as usePathname } from './usePathname.js';
|
|
5
|
-
export { default as useParams, usePath } from './useParams.js';
|
|
6
|
-
export { Metadata, Router, loadRoute, matchRoute } from './Router.js';
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import React, { ReactNode } from 'react';
|
|
7
3
|
|
|
8
|
-
declare
|
|
4
|
+
declare function useParams(): Record<string, string>;
|
|
5
|
+
/**
|
|
6
|
+
* Returns the current pathname (without query string or hash)
|
|
7
|
+
* Similar to Next.js usePathname hook
|
|
8
|
+
*/
|
|
9
|
+
declare function usePathname(): string;
|
|
10
|
+
/**
|
|
11
|
+
* A read-only interface for URLSearchParams
|
|
12
|
+
* Similar to Next.js useSearchParams hook
|
|
13
|
+
*/
|
|
14
|
+
interface ReadonlyURLSearchParams {
|
|
15
|
+
get(name: string): string | null;
|
|
16
|
+
getAll(name: string): string[];
|
|
17
|
+
has(name: string): boolean;
|
|
18
|
+
keys(): IterableIterator<string>;
|
|
19
|
+
values(): IterableIterator<string>;
|
|
20
|
+
entries(): IterableIterator<[string, string]>;
|
|
21
|
+
forEach(callback: (value: string, key: string, parent: URLSearchParams) => void): void;
|
|
22
|
+
toString(): string;
|
|
23
|
+
size: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Returns the current URL search parameters
|
|
27
|
+
* Similar to Next.js useSearchParams hook
|
|
28
|
+
*
|
|
29
|
+
* Example usage:
|
|
30
|
+
* const searchParams = useSearchParams();
|
|
31
|
+
* const query = searchParams.get('q'); // ?q=hello -> 'hello'
|
|
32
|
+
* const tags = searchParams.getAll('tag'); // ?tag=a&tag=b -> ['a', 'b']
|
|
33
|
+
*/
|
|
34
|
+
declare function useSearchParams(): ReadonlyURLSearchParams;
|
|
35
|
+
declare function matchRoute(path: string): {
|
|
36
|
+
loader: any;
|
|
9
37
|
params: Record<string, string>;
|
|
10
|
-
|
|
11
|
-
}
|
|
38
|
+
pattern: string;
|
|
39
|
+
} | null;
|
|
40
|
+
interface Metadata {
|
|
41
|
+
title?: string;
|
|
42
|
+
description?: string;
|
|
43
|
+
keywords?: string | string[];
|
|
44
|
+
openGraph?: {
|
|
45
|
+
title?: string;
|
|
46
|
+
description?: string;
|
|
47
|
+
url?: string;
|
|
48
|
+
siteName?: string;
|
|
49
|
+
images?: {
|
|
50
|
+
url: string;
|
|
51
|
+
width?: number;
|
|
52
|
+
height?: number;
|
|
53
|
+
alt?: string;
|
|
54
|
+
}[];
|
|
55
|
+
type?: string;
|
|
56
|
+
};
|
|
57
|
+
twitter?: {
|
|
58
|
+
card?: 'summary' | 'summary_large_image' | 'app' | 'player';
|
|
59
|
+
site?: string;
|
|
60
|
+
creator?: string;
|
|
61
|
+
title?: string;
|
|
62
|
+
description?: string;
|
|
63
|
+
images?: string[];
|
|
64
|
+
};
|
|
65
|
+
robots?: string;
|
|
66
|
+
canonical?: string;
|
|
67
|
+
jsonLd?: object | object[];
|
|
68
|
+
}
|
|
69
|
+
declare function loadRoute(path: string): Promise<{
|
|
70
|
+
module: any;
|
|
71
|
+
params: Record<string, string>;
|
|
72
|
+
metadata: Metadata | undefined;
|
|
73
|
+
} | null>;
|
|
74
|
+
interface RouterProps {
|
|
75
|
+
url?: string;
|
|
76
|
+
initialComponent?: React.ComponentType;
|
|
77
|
+
initialParams?: Record<string, string>;
|
|
78
|
+
onRouteChange?: (metadata: Metadata | undefined) => void;
|
|
79
|
+
}
|
|
80
|
+
declare function Router({ url, initialComponent, initialParams, onRouteChange }: RouterProps): react_jsx_runtime.JSX.Element;
|
|
81
|
+
declare function Link({ href, children, ...props }: {
|
|
82
|
+
href: string;
|
|
83
|
+
children: ReactNode;
|
|
84
|
+
} & React.AnchorHTMLAttributes<HTMLAnchorElement>): React.ReactElement;
|
|
12
85
|
|
|
13
|
-
export {
|
|
86
|
+
export { Link as L, type Metadata, Router as R, usePathname as a, useSearchParams as b, loadRoute as l, matchRoute as m, useParams as u };
|
package/dist/index.js
CHANGED
|
@@ -1,32 +1 @@
|
|
|
1
|
-
|
|
2
|
-
Link
|
|
3
|
-
} from "./chunk-CYX762OE.js";
|
|
4
|
-
import {
|
|
5
|
-
Router,
|
|
6
|
-
loadRoute,
|
|
7
|
-
matchRoute
|
|
8
|
-
} from "./chunk-Y3CEUWVD.js";
|
|
9
|
-
import {
|
|
10
|
-
useParams,
|
|
11
|
-
usePath
|
|
12
|
-
} from "./chunk-K6EDUQDD.js";
|
|
13
|
-
import {
|
|
14
|
-
RouterContext
|
|
15
|
-
} from "./chunk-LOK5UEME.js";
|
|
16
|
-
import {
|
|
17
|
-
usePathname
|
|
18
|
-
} from "./chunk-XGX5YRJV.js";
|
|
19
|
-
import {
|
|
20
|
-
useSearchParams
|
|
21
|
-
} from "./chunk-NEMX72XA.js";
|
|
22
|
-
export {
|
|
23
|
-
Link,
|
|
24
|
-
Router,
|
|
25
|
-
RouterContext,
|
|
26
|
-
loadRoute,
|
|
27
|
-
matchRoute,
|
|
28
|
-
useParams,
|
|
29
|
-
usePath,
|
|
30
|
-
usePathname,
|
|
31
|
-
useSearchParams
|
|
32
|
-
};
|
|
1
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/router.d.ts
ADDED
|
@@ -1,25 +1,60 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
} from "
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
function parseMarkdown(md) {
|
|
9
|
-
return md.replace(/^### (.*$)/gim, "<h3>$1</h3>").replace(/^## (.*$)/gim, "<h2>$1</h2>").replace(/^# (.*$)/gim, "<h1>$1</h1>").replace(/\*\*\*(.*?)\*\*\*/g, "<strong><em>$1</em></strong>").replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>").replace(/\*(.*?)\*/g, "<em>$1</em>").replace(/```([\s\S]*?)```/g, "<pre><code>$1</code></pre>").replace(/`(.*?)`/g, "<code>$1</code>").replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>').replace(/\n\n/g, "</p><p>").replace(/\n/g, "<br>").replace(/^(.*)$/, "<p>$1</p>");
|
|
1
|
+
// src/client/router.tsx
|
|
2
|
+
import React, { useState, useEffect, createContext, useContext } from "react";
|
|
3
|
+
import { routes } from "olova/routes";
|
|
4
|
+
import { jsx } from "react/jsx-runtime";
|
|
5
|
+
var RouterContext = createContext({ params: {}, path: "/" });
|
|
6
|
+
function useParams() {
|
|
7
|
+
return useContext(RouterContext).params;
|
|
10
8
|
}
|
|
11
|
-
function
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
function usePathname() {
|
|
10
|
+
const [pathname, setPathname] = useState(() => {
|
|
11
|
+
if (typeof window === "undefined") return "/";
|
|
12
|
+
return window.location.pathname;
|
|
15
13
|
});
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (typeof window === "undefined") return;
|
|
16
|
+
const handleNavigation = () => {
|
|
17
|
+
setPathname(window.location.pathname);
|
|
18
|
+
};
|
|
19
|
+
window.addEventListener("popstate", handleNavigation);
|
|
20
|
+
window.addEventListener("pushstate", handleNavigation);
|
|
21
|
+
return () => {
|
|
22
|
+
window.removeEventListener("popstate", handleNavigation);
|
|
23
|
+
window.removeEventListener("pushstate", handleNavigation);
|
|
24
|
+
};
|
|
25
|
+
}, []);
|
|
26
|
+
return pathname;
|
|
16
27
|
}
|
|
17
|
-
function
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
className: "olova-markdown-content"
|
|
28
|
+
function useSearchParams() {
|
|
29
|
+
const [searchParams, setSearchParams] = useState(() => {
|
|
30
|
+
if (typeof window === "undefined") return new URLSearchParams();
|
|
31
|
+
return new URLSearchParams(window.location.search);
|
|
22
32
|
});
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (typeof window === "undefined") return;
|
|
35
|
+
const handleNavigation = () => {
|
|
36
|
+
setSearchParams(new URLSearchParams(window.location.search));
|
|
37
|
+
};
|
|
38
|
+
window.addEventListener("popstate", handleNavigation);
|
|
39
|
+
window.addEventListener("pushstate", handleNavigation);
|
|
40
|
+
return () => {
|
|
41
|
+
window.removeEventListener("popstate", handleNavigation);
|
|
42
|
+
window.removeEventListener("pushstate", handleNavigation);
|
|
43
|
+
};
|
|
44
|
+
}, []);
|
|
45
|
+
return {
|
|
46
|
+
get: (name) => searchParams.get(name),
|
|
47
|
+
getAll: (name) => searchParams.getAll(name),
|
|
48
|
+
has: (name) => searchParams.has(name),
|
|
49
|
+
keys: () => searchParams.keys(),
|
|
50
|
+
values: () => searchParams.values(),
|
|
51
|
+
entries: () => searchParams.entries(),
|
|
52
|
+
forEach: (callback) => searchParams.forEach(callback),
|
|
53
|
+
toString: () => searchParams.toString(),
|
|
54
|
+
get size() {
|
|
55
|
+
return Array.from(searchParams.keys()).length;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
23
58
|
}
|
|
24
59
|
var normalizePath = (path) => {
|
|
25
60
|
let p = path.split("?")[0].split("#")[0];
|
|
@@ -47,6 +82,22 @@ function matchRoute(path) {
|
|
|
47
82
|
}
|
|
48
83
|
return null;
|
|
49
84
|
}
|
|
85
|
+
function parseMarkdown(md) {
|
|
86
|
+
return md.replace(/^### (.*$)/gim, "<h3>$1</h3>").replace(/^## (.*$)/gim, "<h2>$1</h2>").replace(/^# (.*$)/gim, "<h1>$1</h1>").replace(/\*\*\*(.*?)\*\*\*/g, "<strong><em>$1</em></strong>").replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>").replace(/\*(.*?)\*/g, "<em>$1</em>").replace(/```([\s\S]*?)```/g, "<pre><code>$1</code></pre>").replace(/`(.*?)`/g, "<code>$1</code>").replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>').replace(/\n\n/g, "</p><p>").replace(/\n/g, "<br>").replace(/^(.*)$/, "<p>$1</p>");
|
|
87
|
+
}
|
|
88
|
+
function HtmlContent({ html }) {
|
|
89
|
+
return React.createElement("div", {
|
|
90
|
+
dangerouslySetInnerHTML: { __html: html },
|
|
91
|
+
className: "olova-html-content"
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
function MarkdownContent({ markdown }) {
|
|
95
|
+
const html = parseMarkdown(markdown);
|
|
96
|
+
return React.createElement("article", {
|
|
97
|
+
dangerouslySetInnerHTML: { __html: html },
|
|
98
|
+
className: "olova-markdown-content"
|
|
99
|
+
});
|
|
100
|
+
}
|
|
50
101
|
async function loadRoute(path) {
|
|
51
102
|
const match = matchRoute(path);
|
|
52
103
|
if (match) {
|
|
@@ -124,7 +175,7 @@ function Router({ url, initialComponent, initialParams, onRouteChange }) {
|
|
|
124
175
|
onRouteChange(fallbackResult.metadata);
|
|
125
176
|
}
|
|
126
177
|
} else {
|
|
127
|
-
setComponent(() => () =>
|
|
178
|
+
setComponent(() => () => /* @__PURE__ */ jsx("div", { children: "404 Not Found" }));
|
|
128
179
|
setParams({});
|
|
129
180
|
if (onRouteChange) {
|
|
130
181
|
onRouteChange(void 0);
|
|
@@ -138,16 +189,29 @@ function Router({ url, initialComponent, initialParams, onRouteChange }) {
|
|
|
138
189
|
isCancelled = true;
|
|
139
190
|
};
|
|
140
191
|
}, [path, onRouteChange]);
|
|
141
|
-
if (!Component) return
|
|
142
|
-
return
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
192
|
+
if (!Component) return /* @__PURE__ */ jsx("div", { children: "Loading..." });
|
|
193
|
+
return /* @__PURE__ */ jsx(RouterContext.Provider, { value: { params, path }, children: /* @__PURE__ */ jsx(Component, { ...params }) });
|
|
194
|
+
}
|
|
195
|
+
function Link({ href, children, ...props }) {
|
|
196
|
+
const handleClick = (e) => {
|
|
197
|
+
if (e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
|
|
198
|
+
if (href.startsWith("http") || href.startsWith("//")) return;
|
|
199
|
+
e.preventDefault();
|
|
200
|
+
const currentUrl = window.location.pathname + window.location.search;
|
|
201
|
+
const targetUrl = href.startsWith("/") ? href : "/" + href;
|
|
202
|
+
if (currentUrl === targetUrl) return;
|
|
203
|
+
window.history.pushState({}, "", href);
|
|
204
|
+
window.dispatchEvent(new Event("pushstate"));
|
|
205
|
+
};
|
|
206
|
+
return /* @__PURE__ */ jsx("a", { href, onClick: handleClick, ...props, children });
|
|
147
207
|
}
|
|
148
|
-
|
|
149
208
|
export {
|
|
150
|
-
|
|
209
|
+
Link,
|
|
210
|
+
Router,
|
|
151
211
|
loadRoute,
|
|
152
|
-
|
|
212
|
+
matchRoute,
|
|
213
|
+
useParams,
|
|
214
|
+
usePathname,
|
|
215
|
+
useSearchParams
|
|
153
216
|
};
|
|
217
|
+
//# sourceMappingURL=router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/client/router.tsx"],"sourcesContent":["import React, { useState, useEffect, createContext, useContext, type ReactNode } from 'react';\n\n// @ts-ignore\nimport { routes } from 'olova/routes';\n\nconst RouterContext = createContext<{ params: Record<string, string>, path: string }>({ params: {}, path: '/' });\n\nexport function useParams() {\n return useContext(RouterContext).params;\n}\n\nexport function usePath() {\n return useContext(RouterContext).path;\n}\n\n/**\n * Returns the current pathname (without query string or hash)\n * Similar to Next.js usePathname hook\n */\nexport function usePathname(): string {\n const [pathname, setPathname] = useState(() => {\n if (typeof window === 'undefined') return '/';\n return window.location.pathname;\n });\n\n useEffect(() => {\n if (typeof window === 'undefined') return;\n\n const handleNavigation = () => {\n setPathname(window.location.pathname);\n };\n\n window.addEventListener('popstate', handleNavigation);\n window.addEventListener('pushstate', handleNavigation);\n\n return () => {\n window.removeEventListener('popstate', handleNavigation);\n window.removeEventListener('pushstate', handleNavigation);\n };\n }, []);\n\n return pathname;\n}\n\n/**\n * A read-only interface for URLSearchParams\n * Similar to Next.js useSearchParams hook\n */\ninterface ReadonlyURLSearchParams {\n get(name: string): string | null;\n getAll(name: string): string[];\n has(name: string): boolean;\n keys(): IterableIterator<string>;\n values(): IterableIterator<string>;\n entries(): IterableIterator<[string, string]>;\n forEach(callback: (value: string, key: string, parent: URLSearchParams) => void): void;\n toString(): string;\n size: number;\n}\n\n/**\n * Returns the current URL search parameters\n * Similar to Next.js useSearchParams hook\n * \n * Example usage:\n * const searchParams = useSearchParams();\n * const query = searchParams.get('q'); // ?q=hello -> 'hello'\n * const tags = searchParams.getAll('tag'); // ?tag=a&tag=b -> ['a', 'b']\n */\nexport function useSearchParams(): ReadonlyURLSearchParams {\n const [searchParams, setSearchParams] = useState<URLSearchParams>(() => {\n if (typeof window === 'undefined') return new URLSearchParams();\n return new URLSearchParams(window.location.search);\n });\n\n useEffect(() => {\n if (typeof window === 'undefined') return;\n\n const handleNavigation = () => {\n setSearchParams(new URLSearchParams(window.location.search));\n };\n\n window.addEventListener('popstate', handleNavigation);\n window.addEventListener('pushstate', handleNavigation);\n\n return () => {\n window.removeEventListener('popstate', handleNavigation);\n window.removeEventListener('pushstate', handleNavigation);\n };\n }, []);\n\n // Return a readonly interface that wraps URLSearchParams\n return {\n get: (name: string) => searchParams.get(name),\n getAll: (name: string) => searchParams.getAll(name),\n has: (name: string) => searchParams.has(name),\n keys: () => searchParams.keys(),\n values: () => searchParams.values(),\n entries: () => searchParams.entries(),\n forEach: (callback) => searchParams.forEach(callback),\n toString: () => searchParams.toString(),\n get size() { return Array.from(searchParams.keys()).length; }\n };\n}\n\n// Helper to normalize paths\nconst normalizePath = (path: string) => {\n let p = path.split('?')[0].split('#')[0];\n if (p.length > 1 && p.endsWith('/')) p = p.slice(0, -1);\n return p || '/';\n};\n\n// Route matching with param extraction\nexport function matchRoute(path: string) {\n const normalizedPath = normalizePath(path);\n const routeKeys = Object.keys(routes);\n \n for (const route of routeKeys) {\n // Handle exact match first\n if (route === normalizedPath) {\n return { loader: routes[route], params: {}, pattern: route };\n }\n \n // Convert :param and $param to regex group\n const regexPath = route\n .replace(/:[^\\/]+/g, '([^/]+)')\n .replace(/\\$[^\\/]+/g, '([^/]+)');\n \n const regex = new RegExp(`^${regexPath}$`);\n const match = normalizedPath.match(regex);\n \n if (match) {\n const params: Record<string, string> = {};\n const paramNames = (route.match(/[:$][^\\/]+/g) || []).map(s => s.slice(1));\n paramNames.forEach((name, i) => {\n params[name] = match[i + 1];\n });\n return { loader: routes[route], params, pattern: route };\n }\n }\n return null;\n}\n\n// Metadata type - like Next.js\nexport interface Metadata {\n title?: string;\n description?: string;\n keywords?: string | string[];\n openGraph?: {\n title?: string;\n description?: string;\n url?: string;\n siteName?: string;\n images?: { url: string; width?: number; height?: number; alt?: string }[];\n type?: string;\n };\n twitter?: {\n card?: 'summary' | 'summary_large_image' | 'app' | 'player';\n site?: string;\n creator?: string;\n title?: string;\n description?: string;\n images?: string[];\n };\n robots?: string;\n canonical?: string;\n jsonLd?: object | object[];\n}\n\n// Simple markdown to HTML converter (basic support)\nfunction parseMarkdown(md: string): string {\n return md\n // Headers\n .replace(/^### (.*$)/gim, '<h3>$1</h3>')\n .replace(/^## (.*$)/gim, '<h2>$1</h2>')\n .replace(/^# (.*$)/gim, '<h1>$1</h1>')\n // Bold and italic\n .replace(/\\*\\*\\*(.*?)\\*\\*\\*/g, '<strong><em>$1</em></strong>')\n .replace(/\\*\\*(.*?)\\*\\*/g, '<strong>$1</strong>')\n .replace(/\\*(.*?)\\*/g, '<em>$1</em>')\n // Code blocks\n .replace(/```([\\s\\S]*?)```/g, '<pre><code>$1</code></pre>')\n .replace(/`(.*?)`/g, '<code>$1</code>')\n // Links\n .replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, '<a href=\"$2\">$1</a>')\n // Line breaks and paragraphs\n .replace(/\\n\\n/g, '</p><p>')\n .replace(/\\n/g, '<br>')\n // Wrap in paragraph\n .replace(/^(.*)$/, '<p>$1</p>');\n}\n\n// React component for rendering HTML content\nfunction HtmlContent({ html }: { html: string }) {\n return React.createElement('div', { \n dangerouslySetInnerHTML: { __html: html },\n className: 'olova-html-content'\n });\n}\n\n// React component for rendering Markdown content\nfunction MarkdownContent({ markdown }: { markdown: string }) {\n const html = parseMarkdown(markdown);\n return React.createElement('article', { \n dangerouslySetInnerHTML: { __html: html },\n className: 'olova-markdown-content'\n });\n}\n\nexport async function loadRoute(path: string) {\n const match = matchRoute(path);\n if (match) {\n console.log(`[Router] Loading route: ${path} (pattern: ${match.pattern})`);\n const module = await match.loader();\n \n // Handle HTML files\n if (module.__isHtml) {\n return { \n module: {\n default: () => HtmlContent({ html: module.__rawHtml }),\n },\n params: match.params,\n metadata: undefined\n };\n }\n \n // Handle Markdown files\n if (module.__isMd) {\n return { \n module: {\n default: () => MarkdownContent({ markdown: module.default }),\n },\n params: match.params,\n metadata: undefined\n };\n }\n \n return { \n module,\n params: match.params,\n metadata: module.metadata as Metadata | undefined\n };\n }\n console.warn(`[Router] No match for: ${path}`);\n return null;\n}\n\ninterface RouterProps {\n url?: string;\n initialComponent?: React.ComponentType;\n initialParams?: Record<string, string>;\n onRouteChange?: (metadata: Metadata | undefined) => void;\n}\n\nexport function Router({ url, initialComponent, initialParams, onRouteChange }: RouterProps) {\n const [path, setPath] = useState(() => normalizePath(url || (typeof window !== 'undefined' ? window.location.pathname : '/')));\n const [Component, setComponent] = useState<React.ComponentType | null>(() => initialComponent || null);\n const [params, setParams] = useState<Record<string, string>>(() => initialParams || {});\n const hasHydrated = React.useRef(false);\n\n useEffect(() => {\n if (typeof window === 'undefined') return;\n\n const handleNavigation = () => {\n const newPath = normalizePath(window.location.pathname);\n console.log(`[Router] Navigation event: ${newPath}`);\n setPath(newPath);\n };\n\n window.addEventListener('popstate', handleNavigation);\n window.addEventListener('pushstate', handleNavigation);\n\n return () => {\n window.removeEventListener('popstate', handleNavigation);\n window.removeEventListener('pushstate', handleNavigation);\n };\n }, []);\n\n useEffect(() => {\n // Skip loading on mount if we already have the initial component for the current path\n if (!hasHydrated.current && initialComponent && path === normalizePath(url || '')) {\n console.log(`[Router] Hydration skipped for: ${path}`);\n hasHydrated.current = true;\n return;\n }\n\n let isCancelled = false;\n const load = async () => {\n const result = await loadRoute(path);\n if (!isCancelled) {\n if (result) {\n setComponent(() => result.module.default);\n setParams(result.params);\n // Call onRouteChange with new metadata for SEO updates\n if (onRouteChange) {\n onRouteChange(result.metadata);\n }\n } else {\n // Try to load custom 404 page\n const fallbackResult = await loadRoute('/404');\n if (fallbackResult) {\n console.log('[Router] Serving custom 404 page');\n setComponent(() => fallbackResult.module.default);\n setParams(fallbackResult.params);\n if (onRouteChange) {\n onRouteChange(fallbackResult.metadata);\n }\n } else {\n // Default 404 if no custom page exists\n setComponent(() => () => <div>404 Not Found</div>);\n setParams({});\n if (onRouteChange) {\n onRouteChange(undefined);\n }\n }\n }\n }\n };\n\n load();\n return () => { isCancelled = true; };\n }, [path, onRouteChange]);\n\n if (!Component) return <div>Loading...</div>;\n \n return (\n <RouterContext.Provider value={{ params, path }}>\n <Component {...params} />\n </RouterContext.Provider>\n );\n}\n\n// Link component - always uses SPA navigation (like Next.js)\n// Pre-rendered HTML is only for initial page load and SEO crawlers\nexport function Link({ href, children, ...props }: { href: string, children: ReactNode } & React.AnchorHTMLAttributes<HTMLAnchorElement>): React.ReactElement {\n const handleClick = (e: React.MouseEvent<HTMLAnchorElement>) => {\n // Allow modifier keys for new tab, etc.\n if (e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;\n // Allow external links\n if (href.startsWith('http') || href.startsWith('//')) return;\n\n e.preventDefault();\n \n // Compare full URLs (including query strings) to allow search param changes\n const currentUrl = window.location.pathname + window.location.search;\n const targetUrl = href.startsWith('/') ? href : '/' + href;\n \n if (currentUrl === targetUrl) return;\n \n // SPA navigation - no page reload\n window.history.pushState({}, '', href);\n window.dispatchEvent(new Event('pushstate'));\n };\n\n return (\n <a href={href} onClick={handleClick} {...props}>\n {children}\n </a>\n );\n}"],"mappings":";AAAA,OAAO,SAAS,UAAU,WAAW,eAAe,kBAAkC;AAGtF,SAAS,cAAc;AAkT0B;AAhTjD,IAAM,gBAAgB,cAAgE,EAAE,QAAQ,CAAC,GAAG,MAAM,IAAI,CAAC;AAExG,SAAS,YAAY;AACxB,SAAO,WAAW,aAAa,EAAE;AACrC;AAUO,SAAS,cAAsB;AAClC,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,MAAM;AAC3C,QAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,WAAO,OAAO,SAAS;AAAA,EAC3B,CAAC;AAED,YAAU,MAAM;AACZ,QAAI,OAAO,WAAW,YAAa;AAEnC,UAAM,mBAAmB,MAAM;AAC3B,kBAAY,OAAO,SAAS,QAAQ;AAAA,IACxC;AAEA,WAAO,iBAAiB,YAAY,gBAAgB;AACpD,WAAO,iBAAiB,aAAa,gBAAgB;AAErD,WAAO,MAAM;AACT,aAAO,oBAAoB,YAAY,gBAAgB;AACvD,aAAO,oBAAoB,aAAa,gBAAgB;AAAA,IAC5D;AAAA,EACJ,GAAG,CAAC,CAAC;AAEL,SAAO;AACX;AA2BO,SAAS,kBAA2C;AACvD,QAAM,CAAC,cAAc,eAAe,IAAI,SAA0B,MAAM;AACpE,QAAI,OAAO,WAAW,YAAa,QAAO,IAAI,gBAAgB;AAC9D,WAAO,IAAI,gBAAgB,OAAO,SAAS,MAAM;AAAA,EACrD,CAAC;AAED,YAAU,MAAM;AACZ,QAAI,OAAO,WAAW,YAAa;AAEnC,UAAM,mBAAmB,MAAM;AAC3B,sBAAgB,IAAI,gBAAgB,OAAO,SAAS,MAAM,CAAC;AAAA,IAC/D;AAEA,WAAO,iBAAiB,YAAY,gBAAgB;AACpD,WAAO,iBAAiB,aAAa,gBAAgB;AAErD,WAAO,MAAM;AACT,aAAO,oBAAoB,YAAY,gBAAgB;AACvD,aAAO,oBAAoB,aAAa,gBAAgB;AAAA,IAC5D;AAAA,EACJ,GAAG,CAAC,CAAC;AAGL,SAAO;AAAA,IACH,KAAK,CAAC,SAAiB,aAAa,IAAI,IAAI;AAAA,IAC5C,QAAQ,CAAC,SAAiB,aAAa,OAAO,IAAI;AAAA,IAClD,KAAK,CAAC,SAAiB,aAAa,IAAI,IAAI;AAAA,IAC5C,MAAM,MAAM,aAAa,KAAK;AAAA,IAC9B,QAAQ,MAAM,aAAa,OAAO;AAAA,IAClC,SAAS,MAAM,aAAa,QAAQ;AAAA,IACpC,SAAS,CAAC,aAAa,aAAa,QAAQ,QAAQ;AAAA,IACpD,UAAU,MAAM,aAAa,SAAS;AAAA,IACtC,IAAI,OAAO;AAAE,aAAO,MAAM,KAAK,aAAa,KAAK,CAAC,EAAE;AAAA,IAAQ;AAAA,EAChE;AACJ;AAGA,IAAM,gBAAgB,CAAC,SAAiB;AACpC,MAAI,IAAI,KAAK,MAAM,GAAG,EAAE,CAAC,EAAE,MAAM,GAAG,EAAE,CAAC;AACvC,MAAI,EAAE,SAAS,KAAK,EAAE,SAAS,GAAG,EAAG,KAAI,EAAE,MAAM,GAAG,EAAE;AACtD,SAAO,KAAK;AAChB;AAGO,SAAS,WAAW,MAAc;AACrC,QAAM,iBAAiB,cAAc,IAAI;AACzC,QAAM,YAAY,OAAO,KAAK,MAAM;AAEpC,aAAW,SAAS,WAAW;AAE3B,QAAI,UAAU,gBAAgB;AAC1B,aAAO,EAAE,QAAQ,OAAO,KAAK,GAAG,QAAQ,CAAC,GAAG,SAAS,MAAM;AAAA,IAC/D;AAGA,UAAM,YAAY,MACb,QAAQ,YAAY,SAAS,EAC7B,QAAQ,aAAa,SAAS;AAEnC,UAAM,QAAQ,IAAI,OAAO,IAAI,SAAS,GAAG;AACzC,UAAM,QAAQ,eAAe,MAAM,KAAK;AAExC,QAAI,OAAO;AACP,YAAM,SAAiC,CAAC;AACxC,YAAM,cAAc,MAAM,MAAM,aAAa,KAAK,CAAC,GAAG,IAAI,OAAK,EAAE,MAAM,CAAC,CAAC;AACzE,iBAAW,QAAQ,CAAC,MAAM,MAAM;AAC5B,eAAO,IAAI,IAAI,MAAM,IAAI,CAAC;AAAA,MAC9B,CAAC;AACD,aAAO,EAAE,QAAQ,OAAO,KAAK,GAAG,QAAQ,SAAS,MAAM;AAAA,IAC3D;AAAA,EACJ;AACA,SAAO;AACX;AA6BA,SAAS,cAAc,IAAoB;AACvC,SAAO,GAEF,QAAQ,iBAAiB,aAAa,EACtC,QAAQ,gBAAgB,aAAa,EACrC,QAAQ,eAAe,aAAa,EAEpC,QAAQ,sBAAsB,8BAA8B,EAC5D,QAAQ,kBAAkB,qBAAqB,EAC/C,QAAQ,cAAc,aAAa,EAEnC,QAAQ,qBAAqB,4BAA4B,EACzD,QAAQ,YAAY,iBAAiB,EAErC,QAAQ,4BAA4B,qBAAqB,EAEzD,QAAQ,SAAS,SAAS,EAC1B,QAAQ,OAAO,MAAM,EAErB,QAAQ,UAAU,WAAW;AACtC;AAGA,SAAS,YAAY,EAAE,KAAK,GAAqB;AAC7C,SAAO,MAAM,cAAc,OAAO;AAAA,IAC9B,yBAAyB,EAAE,QAAQ,KAAK;AAAA,IACxC,WAAW;AAAA,EACf,CAAC;AACL;AAGA,SAAS,gBAAgB,EAAE,SAAS,GAAyB;AACzD,QAAM,OAAO,cAAc,QAAQ;AACnC,SAAO,MAAM,cAAc,WAAW;AAAA,IAClC,yBAAyB,EAAE,QAAQ,KAAK;AAAA,IACxC,WAAW;AAAA,EACf,CAAC;AACL;AAEA,eAAsB,UAAU,MAAc;AAC1C,QAAM,QAAQ,WAAW,IAAI;AAC7B,MAAI,OAAO;AACP,YAAQ,IAAI,2BAA2B,IAAI,cAAc,MAAM,OAAO,GAAG;AACzE,UAAM,SAAS,MAAM,MAAM,OAAO;AAGlC,QAAI,OAAO,UAAU;AACjB,aAAO;AAAA,QACH,QAAQ;AAAA,UACJ,SAAS,MAAM,YAAY,EAAE,MAAM,OAAO,UAAU,CAAC;AAAA,QACzD;AAAA,QACA,QAAQ,MAAM;AAAA,QACd,UAAU;AAAA,MACd;AAAA,IACJ;AAGA,QAAI,OAAO,QAAQ;AACf,aAAO;AAAA,QACH,QAAQ;AAAA,UACJ,SAAS,MAAM,gBAAgB,EAAE,UAAU,OAAO,QAAQ,CAAC;AAAA,QAC/D;AAAA,QACA,QAAQ,MAAM;AAAA,QACd,UAAU;AAAA,MACd;AAAA,IACJ;AAEA,WAAO;AAAA,MACJ;AAAA,MACA,QAAQ,MAAM;AAAA,MACd,UAAU,OAAO;AAAA,IACpB;AAAA,EACJ;AACA,UAAQ,KAAK,0BAA0B,IAAI,EAAE;AAC7C,SAAO;AACX;AASO,SAAS,OAAO,EAAE,KAAK,kBAAkB,eAAe,cAAc,GAAgB;AACzF,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,MAAM,cAAc,QAAQ,OAAO,WAAW,cAAc,OAAO,SAAS,WAAW,IAAI,CAAC;AAC7H,QAAM,CAAC,WAAW,YAAY,IAAI,SAAqC,MAAM,oBAAoB,IAAI;AACrG,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAiC,MAAM,iBAAiB,CAAC,CAAC;AACtF,QAAM,cAAc,MAAM,OAAO,KAAK;AAEtC,YAAU,MAAM;AACZ,QAAI,OAAO,WAAW,YAAa;AAEnC,UAAM,mBAAmB,MAAM;AAC3B,YAAM,UAAU,cAAc,OAAO,SAAS,QAAQ;AACtD,cAAQ,IAAI,8BAA8B,OAAO,EAAE;AACnD,cAAQ,OAAO;AAAA,IACnB;AAEA,WAAO,iBAAiB,YAAY,gBAAgB;AACpD,WAAO,iBAAiB,aAAa,gBAAgB;AAErD,WAAO,MAAM;AACT,aAAO,oBAAoB,YAAY,gBAAgB;AACvD,aAAO,oBAAoB,aAAa,gBAAgB;AAAA,IAC5D;AAAA,EACJ,GAAG,CAAC,CAAC;AAEL,YAAU,MAAM;AAEZ,QAAI,CAAC,YAAY,WAAW,oBAAoB,SAAS,cAAc,OAAO,EAAE,GAAG;AAC/E,cAAQ,IAAI,mCAAmC,IAAI,EAAE;AACrD,kBAAY,UAAU;AACtB;AAAA,IACJ;AAEA,QAAI,cAAc;AAClB,UAAM,OAAO,YAAY;AACrB,YAAM,SAAS,MAAM,UAAU,IAAI;AACnC,UAAI,CAAC,aAAa;AACd,YAAI,QAAQ;AACR,uBAAa,MAAM,OAAO,OAAO,OAAO;AACxC,oBAAU,OAAO,MAAM;AAEvB,cAAI,eAAe;AACf,0BAAc,OAAO,QAAQ;AAAA,UACjC;AAAA,QACJ,OAAO;AAEH,gBAAM,iBAAiB,MAAM,UAAU,MAAM;AAC7C,cAAI,gBAAgB;AAChB,oBAAQ,IAAI,kCAAkC;AAC9C,yBAAa,MAAM,eAAe,OAAO,OAAO;AAChD,sBAAU,eAAe,MAAM;AAC/B,gBAAI,eAAe;AACf,4BAAc,eAAe,QAAQ;AAAA,YACzC;AAAA,UACJ,OAAO;AAEH,yBAAa,MAAM,MAAM,oBAAC,SAAI,2BAAa,CAAM;AACjD,sBAAU,CAAC,CAAC;AACZ,gBAAI,eAAe;AACf,4BAAc,MAAS;AAAA,YAC3B;AAAA,UACJ;AAAA,QACJ;AAAA,MACJ;AAAA,IACJ;AAEA,SAAK;AACL,WAAO,MAAM;AAAE,oBAAc;AAAA,IAAM;AAAA,EACvC,GAAG,CAAC,MAAM,aAAa,CAAC;AAExB,MAAI,CAAC,UAAW,QAAO,oBAAC,SAAI,wBAAU;AAEtC,SACI,oBAAC,cAAc,UAAd,EAAuB,OAAO,EAAE,QAAQ,KAAK,GAC1C,8BAAC,aAAW,GAAG,QAAQ,GAC3B;AAER;AAIO,SAAS,KAAK,EAAE,MAAM,UAAU,GAAG,MAAM,GAA8G;AAC1J,QAAM,cAAc,CAAC,MAA2C;AAE5D,QAAI,EAAE,WAAW,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,EAAE,OAAQ;AAExE,QAAI,KAAK,WAAW,MAAM,KAAK,KAAK,WAAW,IAAI,EAAG;AAEtD,MAAE,eAAe;AAGjB,UAAM,aAAa,OAAO,SAAS,WAAW,OAAO,SAAS;AAC9D,UAAM,YAAY,KAAK,WAAW,GAAG,IAAI,OAAO,MAAM;AAEtD,QAAI,eAAe,UAAW;AAG9B,WAAO,QAAQ,UAAU,CAAC,GAAG,IAAI,IAAI;AACrC,WAAO,cAAc,IAAI,MAAM,WAAW,CAAC;AAAA,EAC/C;AAEA,SACI,oBAAC,OAAE,MAAY,SAAS,aAAc,GAAG,OACpC,UACL;AAER;","names":[]}
|
package/dist/routes.d.ts
ADDED
package/dist/routes.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/routes.ts"],"sourcesContent":["// Stub for the virtual routes module - actual routes are injected by the router plugin at build time\r\n// This is externalized so the import resolves to 'olova/routes' in user projects\r\nexport const routes: Record<string, () => Promise<any>> = {};\r\n"],"mappings":";AAEO,IAAM,SAA6C,CAAC;","names":[]}
|
package/dist/vite.d.ts
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
|
|
3
|
+
declare function olovaPlugins(): Plugin[];
|
|
4
|
+
|
|
5
|
+
declare function minifyHtml(html: string): string;
|
|
6
|
+
declare function generateBuildId(): string;
|
|
7
|
+
declare function generateResourceHints(): string;
|
|
8
|
+
declare function generateCriticalCSS(): string;
|
|
9
|
+
declare function generateServiceWorkerScript(): string;
|
|
10
|
+
declare function generateServiceWorkerContent(buildId: string, assets: string[]): string;
|
|
11
|
+
declare function generatePerformanceMeta(): string;
|
|
12
|
+
|
|
13
|
+
interface OlovaHydrationData {
|
|
14
|
+
route: string;
|
|
15
|
+
metadata?: Record<string, unknown>;
|
|
16
|
+
params?: Record<string, string>;
|
|
17
|
+
chunks?: string[];
|
|
18
|
+
isStatic?: boolean;
|
|
19
|
+
}
|
|
20
|
+
declare function generateOlovaHydration(data: OlovaHydrationData, buildId: string): string;
|
|
21
|
+
/**
|
|
22
|
+
* Generate JSON-LD structured data script for SEO
|
|
23
|
+
* This is injected into <head> for search engine crawlers
|
|
24
|
+
*/
|
|
25
|
+
declare function generateJsonLd(data: OlovaHydrationData): string;
|
|
26
|
+
/**
|
|
27
|
+
* Parse flight data from the page (client-side utility)
|
|
28
|
+
* Use this to access hydration data in your components
|
|
29
|
+
*/
|
|
30
|
+
declare function parseFlightData(): Record<string, unknown> | null;
|
|
31
|
+
|
|
32
|
+
declare const colors: {
|
|
33
|
+
reset: string;
|
|
34
|
+
bold: string;
|
|
35
|
+
dim: string;
|
|
36
|
+
italic: string;
|
|
37
|
+
underline: string;
|
|
38
|
+
black: string;
|
|
39
|
+
red: string;
|
|
40
|
+
green: string;
|
|
41
|
+
yellow: string;
|
|
42
|
+
blue: string;
|
|
43
|
+
magenta: string;
|
|
44
|
+
cyan: string;
|
|
45
|
+
white: string;
|
|
46
|
+
gray: string;
|
|
47
|
+
bgBlack: string;
|
|
48
|
+
bgRed: string;
|
|
49
|
+
bgGreen: string;
|
|
50
|
+
bgYellow: string;
|
|
51
|
+
bgBlue: string;
|
|
52
|
+
bgMagenta: string;
|
|
53
|
+
bgCyan: string;
|
|
54
|
+
bgWhite: string;
|
|
55
|
+
};
|
|
56
|
+
declare const symbols: {
|
|
57
|
+
success: string;
|
|
58
|
+
error: string;
|
|
59
|
+
warning: string;
|
|
60
|
+
info: string;
|
|
61
|
+
arrow: string;
|
|
62
|
+
arrowRight: string;
|
|
63
|
+
bullet: string;
|
|
64
|
+
filled: string;
|
|
65
|
+
lambda: string;
|
|
66
|
+
triangle: string;
|
|
67
|
+
square: string;
|
|
68
|
+
diamond: string;
|
|
69
|
+
star: string;
|
|
70
|
+
check: string;
|
|
71
|
+
cross: string;
|
|
72
|
+
pointer: string;
|
|
73
|
+
};
|
|
74
|
+
declare function formatTime(ms: number): string;
|
|
75
|
+
declare function formatBytes(bytes: number): string;
|
|
76
|
+
declare const logger: {
|
|
77
|
+
banner: (name: string, version?: string) => void;
|
|
78
|
+
header: (text: string) => void;
|
|
79
|
+
info: (text: string) => void;
|
|
80
|
+
success: (text: string, time?: number) => void;
|
|
81
|
+
error: (text: string) => void;
|
|
82
|
+
warn: (text: string) => void;
|
|
83
|
+
step: (text: string, time?: number) => void;
|
|
84
|
+
route: (route: string, type: "static" | "ssr" | "isr", size?: number, time?: number) => void;
|
|
85
|
+
indent: (text: string, level?: number) => void;
|
|
86
|
+
newline: () => void;
|
|
87
|
+
dim: (text: string) => string;
|
|
88
|
+
bold: (text: string) => string;
|
|
89
|
+
cyan: (text: string) => string;
|
|
90
|
+
green: (text: string) => string;
|
|
91
|
+
yellow: (text: string) => string;
|
|
92
|
+
red: (text: string) => string;
|
|
93
|
+
magenta: (text: string) => string;
|
|
94
|
+
};
|
|
95
|
+
declare function createTimer(): {
|
|
96
|
+
elapsed: () => number;
|
|
97
|
+
format: () => string;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export { type OlovaHydrationData, colors, createTimer, formatBytes, formatTime, generateBuildId, generateCriticalCSS, generateJsonLd, generateOlovaHydration, generatePerformanceMeta, generateResourceHints, generateServiceWorkerContent, generateServiceWorkerScript, logger, minifyHtml, olovaPlugins, parseFlightData, symbols };
|