olova 2.0.61 → 2.0.63
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/CHANGELOG.md +5 -0
- package/README.md +42 -61
- package/dist/compiler.d.ts +44 -0
- package/dist/compiler.js +2139 -0
- package/dist/compiler.js.map +1 -0
- package/dist/core.d.ts +4 -0
- package/dist/core.js +859 -0
- package/dist/core.js.map +1 -0
- package/dist/global.d.ts +15 -0
- package/dist/global.js +226 -0
- package/dist/global.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2302 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime.d.ts +89 -0
- package/dist/runtime.js +633 -0
- package/dist/runtime.js.map +1 -0
- package/dist/signals-core-BdfWh1Yt.d.ts +43 -0
- package/dist/vite.d.ts +5 -0
- package/dist/vite.js +2302 -0
- package/dist/vite.js.map +1 -0
- package/package.json +83 -65
- package/dist/chunk-D7SIC5TC.js +0 -367
- package/dist/chunk-D7SIC5TC.js.map +0 -1
- package/dist/entry-server.cjs +0 -120
- package/dist/entry-server.cjs.map +0 -1
- package/dist/entry-server.js +0 -115
- package/dist/entry-server.js.map +0 -1
- package/dist/entry-worker.cjs +0 -133
- package/dist/entry-worker.cjs.map +0 -1
- package/dist/entry-worker.js +0 -127
- package/dist/entry-worker.js.map +0 -1
- package/dist/main.cjs +0 -18
- package/dist/main.cjs.map +0 -1
- package/dist/main.js +0 -16
- package/dist/main.js.map +0 -1
- package/dist/olova.cjs +0 -1680
- package/dist/olova.cjs.map +0 -1
- package/dist/olova.d.cts +0 -72
- package/dist/olova.d.ts +0 -72
- package/dist/olova.js +0 -1321
- package/dist/olova.js.map +0 -1
- package/dist/performance.cjs +0 -386
- package/dist/performance.cjs.map +0 -1
- package/dist/performance.js +0 -3
- package/dist/performance.js.map +0 -1
- package/dist/router.cjs +0 -646
- package/dist/router.cjs.map +0 -1
- package/dist/router.d.cts +0 -113
- package/dist/router.d.ts +0 -113
- package/dist/router.js +0 -632
- package/dist/router.js.map +0 -1
- package/main.tsx +0 -76
- package/olova.ts +0 -619
- package/src/entry-server.tsx +0 -165
- package/src/entry-worker.tsx +0 -201
- package/src/generator/index.ts +0 -409
- package/src/hydration/flight.ts +0 -320
- package/src/hydration/index.ts +0 -12
- package/src/hydration/types.ts +0 -225
- package/src/logger.ts +0 -182
- package/src/main.tsx +0 -24
- package/src/performance.ts +0 -488
- package/src/plugin/index.ts +0 -204
- package/src/router/ErrorBoundary.tsx +0 -145
- package/src/router/Link.tsx +0 -117
- package/src/router/OlovaRouter.tsx +0 -354
- package/src/router/Outlet.tsx +0 -8
- package/src/router/context.ts +0 -117
- package/src/router/index.ts +0 -29
- package/src/router/matching.ts +0 -63
- package/src/router/router.tsx +0 -23
- package/src/router/search-params.ts +0 -29
- package/src/scanner/index.ts +0 -114
- package/src/types/index.ts +0 -190
- package/src/utils/export.ts +0 -85
- package/src/utils/index.ts +0 -4
- package/src/utils/naming.ts +0 -54
- package/src/utils/path.ts +0 -45
- package/tsup.config.ts +0 -35
package/src/entry-server.tsx
DELETED
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
import { StrictMode, Suspense } from 'react';
|
|
2
|
-
import { renderToString } from 'react-dom/server';
|
|
3
|
-
import { QueryClient, QueryClientProvider, dehydrate, HydrationBoundary } from '@tanstack/react-query';
|
|
4
|
-
// @ts-expect-error - Virtual module resolved by vite-plugin-olova
|
|
5
|
-
import { layouts, notFoundPages, OlovaRouter, Outlet, routes } from 'virtual:olova-app';
|
|
6
|
-
|
|
7
|
-
// Wrapper to support "children" prop style layouts
|
|
8
|
-
const wrappedLayouts = layouts.map((item: any) => ({
|
|
9
|
-
...item,
|
|
10
|
-
layout: (props: any) => (
|
|
11
|
-
<item.layout {...props}>
|
|
12
|
-
<Outlet />
|
|
13
|
-
</item.layout>
|
|
14
|
-
)
|
|
15
|
-
}));
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Find matching route and extract params
|
|
19
|
-
*/
|
|
20
|
-
function matchRoute(url: string): { route: any; params: Record<string, string> } | null {
|
|
21
|
-
const pathname = url.split('?')[0];
|
|
22
|
-
|
|
23
|
-
for (const route of routes) {
|
|
24
|
-
// Exact match
|
|
25
|
-
if (route.path === pathname) {
|
|
26
|
-
return { route, params: {} };
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Dynamic segments
|
|
30
|
-
if (route.path.includes(':') || route.path.includes('*')) {
|
|
31
|
-
const pattern = route.path
|
|
32
|
-
.replace(/\*/g, '(.*)')
|
|
33
|
-
.replace(/:[^/]+/g, '([^/]+)');
|
|
34
|
-
const regex = new RegExp(`^${pattern}$`);
|
|
35
|
-
const match = pathname.match(regex);
|
|
36
|
-
|
|
37
|
-
if (match) {
|
|
38
|
-
const params: Record<string, string> = {};
|
|
39
|
-
const paramNames = route.path.match(/:[^/]+|\*/g) || [];
|
|
40
|
-
|
|
41
|
-
paramNames.forEach((name: string, i: number) => {
|
|
42
|
-
if (name === '*') {
|
|
43
|
-
params['*'] = match[i + 1];
|
|
44
|
-
} else {
|
|
45
|
-
params[name.slice(1)] = match[i + 1];
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
return { route, params };
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Run loader for SSG builds
|
|
59
|
-
* Simplified: loader just returns data directly
|
|
60
|
-
*/
|
|
61
|
-
async function runLoader(url: string): Promise<Record<string, unknown> | null> {
|
|
62
|
-
const match = matchRoute(url);
|
|
63
|
-
|
|
64
|
-
if (!match || !match.route.loader) {
|
|
65
|
-
return null;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
try {
|
|
69
|
-
// Simple call - loader returns data directly
|
|
70
|
-
// Use a valid URL for Request (requires absolute URL)
|
|
71
|
-
const baseUrl = 'http://localhost';
|
|
72
|
-
const result = await match.route.loader({
|
|
73
|
-
request: new Request(new URL(url, baseUrl)),
|
|
74
|
-
params: match.params
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
// If it's a Response, skip
|
|
78
|
-
if (result instanceof Response) {
|
|
79
|
-
return null;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Direct data return (simplified syntax)
|
|
83
|
-
return result as Record<string, unknown>;
|
|
84
|
-
} catch (error) {
|
|
85
|
-
console.error(`[SSR] Loader error for ${url}:`, error);
|
|
86
|
-
return null;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export async function render(url: string) {
|
|
91
|
-
// Always recreate the window mock to ensure clean state for every SSG path
|
|
92
|
-
const globalAny = globalThis as any;
|
|
93
|
-
globalAny.window = {
|
|
94
|
-
location: { pathname: url, search: '', hash: '' },
|
|
95
|
-
addEventListener: () => {},
|
|
96
|
-
removeEventListener: () => {},
|
|
97
|
-
history: {
|
|
98
|
-
pushState: () => {},
|
|
99
|
-
replaceState: () => {},
|
|
100
|
-
location: { pathname: url, search: '', hash: '' }
|
|
101
|
-
}
|
|
102
|
-
};
|
|
103
|
-
globalAny.location = globalAny.window.location;
|
|
104
|
-
globalAny.document = {
|
|
105
|
-
createElement: () => ({}),
|
|
106
|
-
getElementsByTagName: () => [],
|
|
107
|
-
getElementById: () => null,
|
|
108
|
-
querySelector: () => null,
|
|
109
|
-
title: '',
|
|
110
|
-
location: globalAny.window.location
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
// Run loader for data-only SSR
|
|
114
|
-
const loaderData = await runLoader(url);
|
|
115
|
-
|
|
116
|
-
// Inject loader data into window for components to access
|
|
117
|
-
if (loaderData) {
|
|
118
|
-
globalAny.window.__LOADER_DATA__ = loaderData;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Create QueryClient for SSR with pre-loaded data
|
|
122
|
-
const queryClient = new QueryClient({
|
|
123
|
-
defaultOptions: {
|
|
124
|
-
queries: {
|
|
125
|
-
staleTime: Infinity, // Data is fresh during SSR
|
|
126
|
-
gcTime: Infinity,
|
|
127
|
-
},
|
|
128
|
-
},
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
// Pre-populate query client with loader data
|
|
132
|
-
if (loaderData) {
|
|
133
|
-
// Convert loader data to query state format
|
|
134
|
-
// If loader returns { user: {...} }, we hydrate it
|
|
135
|
-
Object.entries(loaderData).forEach(([key, value]) => {
|
|
136
|
-
queryClient.setQueryData([key], value);
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Get dehydrated state for client hydration
|
|
141
|
-
const dehydratedState = dehydrate(queryClient);
|
|
142
|
-
|
|
143
|
-
// Wrap in Suspense to handle lazy-loaded components
|
|
144
|
-
const html = renderToString(
|
|
145
|
-
<StrictMode>
|
|
146
|
-
<QueryClientProvider client={queryClient}>
|
|
147
|
-
<HydrationBoundary state={dehydratedState}>
|
|
148
|
-
<Suspense fallback={null}>
|
|
149
|
-
<OlovaRouter routes={routes} layouts={wrappedLayouts} notFoundPages={notFoundPages} />
|
|
150
|
-
</Suspense>
|
|
151
|
-
</HydrationBoundary>
|
|
152
|
-
</QueryClientProvider>
|
|
153
|
-
</StrictMode>
|
|
154
|
-
);
|
|
155
|
-
|
|
156
|
-
// Return both html and dehydrated state for flight serialization
|
|
157
|
-
return {
|
|
158
|
-
html,
|
|
159
|
-
loaderData,
|
|
160
|
-
queryState: dehydratedState
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
export { routes };
|
|
165
|
-
|
package/src/entry-worker.tsx
DELETED
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Olova Cloudflare Workers Entry
|
|
3
|
-
* Auto-generated by plugin - handles SSR for routes with loaders only
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { renderToString } from 'react-dom/server';
|
|
7
|
-
import { StrictMode, Suspense } from 'react';
|
|
8
|
-
import { QueryClient, QueryClientProvider, dehydrate, HydrationBoundary } from '@tanstack/react-query';
|
|
9
|
-
// @ts-expect-error - Virtual module resolved by vite-plugin-olova
|
|
10
|
-
import { layouts, notFoundPages, OlovaRouter, Outlet, routes } from 'virtual:olova-app';
|
|
11
|
-
|
|
12
|
-
// =============================================================================
|
|
13
|
-
// TYPES
|
|
14
|
-
// =============================================================================
|
|
15
|
-
|
|
16
|
-
interface RouteMatch {
|
|
17
|
-
route: any;
|
|
18
|
-
params: Record<string, string>;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// =============================================================================
|
|
22
|
-
// ROUTE MATCHING
|
|
23
|
-
// =============================================================================
|
|
24
|
-
|
|
25
|
-
function matchRoute(pathname: string): RouteMatch | null {
|
|
26
|
-
for (const route of routes) {
|
|
27
|
-
// Exact match
|
|
28
|
-
if (route.path === pathname) {
|
|
29
|
-
return { route, params: {} };
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Dynamic segments
|
|
33
|
-
if (route.path.includes(':') || route.path.includes('*')) {
|
|
34
|
-
const pattern = route.path
|
|
35
|
-
.replace(/\*/g, '(.*)')
|
|
36
|
-
.replace(/:[^/]+/g, '([^/]+)');
|
|
37
|
-
const regex = new RegExp(`^${pattern}$`);
|
|
38
|
-
const match = pathname.match(regex);
|
|
39
|
-
|
|
40
|
-
if (match) {
|
|
41
|
-
const params: Record<string, string> = {};
|
|
42
|
-
const paramNames = route.path.match(/:[^/]+|\*/g) || [];
|
|
43
|
-
|
|
44
|
-
paramNames.forEach((name: string, i: number) => {
|
|
45
|
-
const key = name === '*' ? '*' : name.slice(1);
|
|
46
|
-
params[key] = match[i + 1];
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
return { route, params };
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// =============================================================================
|
|
58
|
-
// SSR RENDERING
|
|
59
|
-
// =============================================================================
|
|
60
|
-
|
|
61
|
-
// Wrapper to support "children" prop style layouts
|
|
62
|
-
const wrappedLayouts = layouts.map((item: any) => ({
|
|
63
|
-
...item,
|
|
64
|
-
layout: (props: any) => (
|
|
65
|
-
<item.layout {...props}>
|
|
66
|
-
<Outlet />
|
|
67
|
-
</item.layout>
|
|
68
|
-
)
|
|
69
|
-
}));
|
|
70
|
-
|
|
71
|
-
async function render(
|
|
72
|
-
pathname: string,
|
|
73
|
-
loaderData?: Record<string, unknown>
|
|
74
|
-
): Promise<{ html: string; queryState: unknown }> {
|
|
75
|
-
// Mock browser globals
|
|
76
|
-
const g = globalThis as any;
|
|
77
|
-
g.window = {
|
|
78
|
-
location: { pathname, search: '', hash: '' },
|
|
79
|
-
addEventListener: () => {},
|
|
80
|
-
removeEventListener: () => {},
|
|
81
|
-
history: { pushState: () => {}, replaceState: () => {} },
|
|
82
|
-
__LOADER_DATA__: loaderData
|
|
83
|
-
};
|
|
84
|
-
g.__LOADER_DATA__ = loaderData;
|
|
85
|
-
|
|
86
|
-
// Create QueryClient with pre-loaded data
|
|
87
|
-
const queryClient = new QueryClient({
|
|
88
|
-
defaultOptions: {
|
|
89
|
-
queries: {
|
|
90
|
-
staleTime: Infinity,
|
|
91
|
-
gcTime: Infinity,
|
|
92
|
-
},
|
|
93
|
-
},
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
// Pre-populate query client with loader data
|
|
97
|
-
if (loaderData) {
|
|
98
|
-
Object.entries(loaderData).forEach(([key, value]) => {
|
|
99
|
-
queryClient.setQueryData([key], value);
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Get dehydrated state for client hydration
|
|
104
|
-
const queryState = dehydrate(queryClient);
|
|
105
|
-
|
|
106
|
-
const html = renderToString(
|
|
107
|
-
<StrictMode>
|
|
108
|
-
<QueryClientProvider client={queryClient}>
|
|
109
|
-
<HydrationBoundary state={queryState}>
|
|
110
|
-
<Suspense fallback={null}>
|
|
111
|
-
<OlovaRouter
|
|
112
|
-
routes={routes}
|
|
113
|
-
layouts={wrappedLayouts}
|
|
114
|
-
notFoundPages={notFoundPages}
|
|
115
|
-
/>
|
|
116
|
-
</Suspense>
|
|
117
|
-
</HydrationBoundary>
|
|
118
|
-
</QueryClientProvider>
|
|
119
|
-
</StrictMode>
|
|
120
|
-
);
|
|
121
|
-
|
|
122
|
-
return { html, queryState };
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// =============================================================================
|
|
126
|
-
// WORKER EXPORT
|
|
127
|
-
// =============================================================================
|
|
128
|
-
|
|
129
|
-
export interface WorkerEnv {
|
|
130
|
-
ASSETS: { fetch: (request: Request | URL) => Promise<Response> };
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
export default {
|
|
134
|
-
async fetch(request: Request, env: WorkerEnv): Promise<Response> {
|
|
135
|
-
const url = new URL(request.url);
|
|
136
|
-
const pathname = url.pathname;
|
|
137
|
-
|
|
138
|
-
// 1. Non-HTML requests → serve static assets directly
|
|
139
|
-
if (!request.headers.get('accept')?.includes('text/html')) {
|
|
140
|
-
return env.ASSETS.fetch(request);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// 2. Check if route has loader (needs SSR)
|
|
144
|
-
const match = matchRoute(pathname);
|
|
145
|
-
const needsSSR = match?.route?.loader;
|
|
146
|
-
|
|
147
|
-
// 3. No SSR needed → serve pre-built static HTML
|
|
148
|
-
if (!needsSSR) {
|
|
149
|
-
const staticPath = pathname === '/' ? '/index.html' : `${pathname}/index.html`;
|
|
150
|
-
try {
|
|
151
|
-
const staticResponse = await env.ASSETS.fetch(new URL(staticPath, url));
|
|
152
|
-
if (staticResponse.ok) {
|
|
153
|
-
return new Response(staticResponse.body, {
|
|
154
|
-
status: 200,
|
|
155
|
-
headers: { 'Content-Type': 'text/html; charset=utf-8' }
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
} catch {}
|
|
159
|
-
|
|
160
|
-
// Fallback to 404.html
|
|
161
|
-
try {
|
|
162
|
-
const notFoundResponse = await env.ASSETS.fetch(new URL('/404.html', url));
|
|
163
|
-
return new Response(notFoundResponse.body, {
|
|
164
|
-
status: 404,
|
|
165
|
-
headers: { 'Content-Type': 'text/html; charset=utf-8' }
|
|
166
|
-
});
|
|
167
|
-
} catch {}
|
|
168
|
-
|
|
169
|
-
return new Response('Not Found', { status: 404 });
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// 4. Route has loader → run SSR
|
|
173
|
-
let loaderData: Record<string, unknown> | undefined;
|
|
174
|
-
|
|
175
|
-
try {
|
|
176
|
-
const result = await match.route.loader({
|
|
177
|
-
request,
|
|
178
|
-
params: match.params
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
if (!(result instanceof Response)) {
|
|
182
|
-
loaderData = result;
|
|
183
|
-
}
|
|
184
|
-
} catch (error) {
|
|
185
|
-
console.error('[Olova] Loader error:', error);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// 5. Render with loader data
|
|
189
|
-
const { html: appHtml, queryState } = await render(pathname, loaderData);
|
|
190
|
-
|
|
191
|
-
// Build full HTML document
|
|
192
|
-
const html = `<!DOCTYPE html>${appHtml}<script>window.__QUERY_STATE__=${JSON.stringify(queryState)}</script><script type="module" src="/assets/_olova/main.js"></script>`;
|
|
193
|
-
|
|
194
|
-
return new Response(html, {
|
|
195
|
-
headers: { 'Content-Type': 'text/html; charset=utf-8' }
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
};
|
|
199
|
-
|
|
200
|
-
// Export for SSG builds
|
|
201
|
-
export { render, matchRoute };
|