Skip to content

Commit 464587d

Browse files
Varixowmertens
authored andcommitted
feat: split loaders from qdata.json
1 parent 60e4064 commit 464587d

File tree

10 files changed

+386
-228
lines changed

10 files changed

+386
-228
lines changed

packages/qwik-router/src/buildtime/vite/plugin.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { generateQwikRouterEntries } from '../runtime-generation/generate-entrie
1313
import { generateQwikRouterConfig } from '../runtime-generation/generate-qwik-router-config';
1414
import { generateServiceWorkerRegister } from '../runtime-generation/generate-service-worker';
1515
import type { RoutingContext } from '../types';
16+
import { getRouterIndexTags, makeRouterDevMiddleware } from './dev-middleware';
1617
import { getRouteImports } from './get-route-imports';
1718
import { imagePlugin } from './image-jsx';
1819
import type {
@@ -21,7 +22,6 @@ import type {
2122
QwikRouterVitePluginOptions,
2223
} from './types';
2324
import { validatePlugin } from './validate-plugin';
24-
import { getRouterIndexTags, makeRouterDevMiddleware } from './dev-middleware';
2525

2626
export const QWIK_ROUTER_CONFIG_ID = '@qwik-router-config';
2727
const QWIK_ROUTER_ENTRIES_ID = '@qwik-router-entries';
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import { _serialize, _UNINITIALIZED } from '@qwik.dev/core/internal';
2+
import type {
3+
DataValidator,
4+
LoaderInternal,
5+
RequestHandler,
6+
ValidatorReturn,
7+
} from '../../runtime/src/types';
8+
import {
9+
getRequestLoaders,
10+
getRequestLoaderSerializationStrategyMap,
11+
getRequestMode,
12+
} from './request-event';
13+
import { measure, verifySerializable } from './resolve-request-handlers';
14+
import type { RequestEvent } from './types';
15+
import { IsQLoader, IsQLoaderData, QLoaderId } from './user-response';
16+
17+
export function loaderDataHandler(routeLoaders: LoaderInternal[]): RequestHandler {
18+
return async (requestEvent: RequestEvent) => {
19+
const requestEv = requestEvent as RequestEventInternal;
20+
21+
const isQLoaderData = requestEv.sharedMap.has(IsQLoaderData);
22+
if (!isQLoaderData) {
23+
return;
24+
}
25+
26+
if (requestEv.headersSent || requestEv.exited) {
27+
return;
28+
}
29+
30+
// Set cache headers - aggressive for loaders
31+
requestEv.cacheControl({
32+
maxAge: 300, // 5 minutes
33+
staleWhileRevalidate: 3600, // 1 hour
34+
});
35+
36+
// return loader ids
37+
const loaderIds = routeLoaders.map((l) => l.__id);
38+
return requestEv.json(200, { loaderIds });
39+
};
40+
}
41+
42+
export function singleLoaderHandler(routeLoaders: LoaderInternal[]): RequestHandler {
43+
return async (requestEvent: RequestEvent) => {
44+
const requestEv = requestEvent as RequestEventInternal;
45+
46+
const isQLoader = requestEv.sharedMap.has(IsQLoader);
47+
if (!isQLoader) {
48+
return;
49+
}
50+
51+
if (requestEv.headersSent || requestEv.exited) {
52+
return;
53+
}
54+
const loaderId = requestEv.sharedMap.get(QLoaderId);
55+
56+
try {
57+
// Execute just this loader
58+
const loaders = getRequestLoaders(requestEv);
59+
const isDev = getRequestMode(requestEv) === 'dev';
60+
61+
let loader: LoaderInternal | undefined;
62+
for (const routeLoader of routeLoaders) {
63+
if (routeLoader.__id === loaderId) {
64+
loader = routeLoader;
65+
} else if (!loaders[routeLoader.__id]) {
66+
loaders[routeLoader.__id] = _UNINITIALIZED;
67+
}
68+
}
69+
70+
if (!loader) {
71+
return requestEv.json(404, { error: 'Loader not found' });
72+
}
73+
74+
await executeLoader(loader, loaders, requestEv, isDev);
75+
76+
// Set cache headers - aggressive for loaders
77+
requestEv.cacheControl({
78+
maxAge: 300, // 5 minutes
79+
staleWhileRevalidate: 3600, // 1 hour
80+
});
81+
82+
const data = await _serialize([loaders[loaderId]]);
83+
84+
requestEv.headers.set('Content-Type', 'application/json; charset=utf-8');
85+
86+
// Return just this loader's result
87+
return requestEv.send(200, data);
88+
} catch (error) {
89+
console.error(`Loader ${loaderId} failed:`, error);
90+
return requestEv.json(500, { error: 'Loader execution failed' });
91+
}
92+
};
93+
}
94+
95+
export async function executeLoader(
96+
loader: LoaderInternal,
97+
loaders: Record<string, unknown>,
98+
requestEv: RequestEventInternal,
99+
isDev: boolean
100+
) {
101+
const loaderId = loader.__id;
102+
loaders[loaderId] = runValidators(
103+
requestEv,
104+
loader.__validators,
105+
undefined, // data
106+
isDev
107+
)
108+
.then((res) => {
109+
if (res.success) {
110+
if (isDev) {
111+
return measure<Promise<unknown>>(requestEv, loader.__qrl.getHash(), () =>
112+
loader.__qrl.call(requestEv, requestEv)
113+
);
114+
} else {
115+
return loader.__qrl.call(requestEv, requestEv);
116+
}
117+
} else {
118+
return requestEv.fail(res.status ?? 500, res.error);
119+
}
120+
})
121+
.then((resolvedLoader) => {
122+
if (typeof resolvedLoader === 'function') {
123+
loaders[loaderId] = resolvedLoader();
124+
} else {
125+
if (isDev) {
126+
verifySerializable(resolvedLoader, loader.__qrl);
127+
}
128+
loaders[loaderId] = resolvedLoader;
129+
}
130+
return resolvedLoader;
131+
});
132+
const loadersSerializationStrategy = getRequestLoaderSerializationStrategyMap(requestEv);
133+
loadersSerializationStrategy.set(loaderId, loader.__serializationStrategy);
134+
return loaders[loaderId];
135+
}
136+
137+
export async function runValidators(
138+
requestEv: RequestEvent,
139+
validators: DataValidator[] | undefined,
140+
data: unknown,
141+
isDev: boolean
142+
) {
143+
let lastResult: ValidatorReturn = {
144+
success: true,
145+
data,
146+
};
147+
if (validators) {
148+
for (const validator of validators) {
149+
if (isDev) {
150+
lastResult = await measure(requestEv, `validator$`, () =>
151+
validator.validate(requestEv, data)
152+
);
153+
} else {
154+
lastResult = await validator.validate(requestEv, data);
155+
}
156+
if (!lastResult.success) {
157+
return lastResult;
158+
} else {
159+
data = lastResult.data;
160+
}
161+
}
162+
}
163+
return lastResult;
164+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// requestEv.sharedMap.get(RequestEvSharedActionId)
2+
3+
import type { RequestEvent } from '@qwik.dev/router';
4+
import { _serialize } from 'packages/qwik/core-internal';
5+
import { RequestEvIsRewrite, RequestEvSharedActionId } from './request-event';
6+
import { getPathname } from './resolve-request-handlers';
7+
import { IsQData } from './user-response';
8+
9+
export interface QData {
10+
status: number;
11+
href: string;
12+
action?: string;
13+
redirect?: string;
14+
isRewrite?: boolean;
15+
}
16+
17+
export async function qDataHandler(requestEv: RequestEvent) {
18+
const isPageDataReq = requestEv.sharedMap.has(IsQData);
19+
if (!isPageDataReq) {
20+
return;
21+
}
22+
23+
if (requestEv.headersSent || requestEv.exited) {
24+
return;
25+
}
26+
27+
const status = requestEv.status();
28+
const redirectLocation = requestEv.headers.get('Location');
29+
30+
requestEv.headers.set('Content-Type', 'application/json; charset=utf-8');
31+
32+
const qData: QData = {
33+
status,
34+
href: getPathname(requestEv.url),
35+
action: requestEv.sharedMap.get(RequestEvSharedActionId),
36+
redirect: redirectLocation ?? undefined,
37+
isRewrite: requestEv.sharedMap.get(RequestEvIsRewrite),
38+
};
39+
40+
// Set cache headers
41+
requestEv.cacheControl({
42+
maxAge: 300, // 5 minutes
43+
staleWhileRevalidate: 3600, // 1 hour
44+
});
45+
46+
// write just the page json data to the response body
47+
const data = await _serialize([qData]);
48+
49+
requestEv.send(200, data);
50+
}

packages/qwik-router/src/middleware/request-handler/request-event.ts

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {
1919
ServerError,
2020
RewriteMessage,
2121
} from '@qwik.dev/router/middleware/request-handler';
22-
import { encoder, getRouteLoaderPromise } from './resolve-request-handlers';
22+
import { encoder } from './resolve-request-handlers';
2323
import type {
2424
CacheControl,
2525
CacheControlTarget,
@@ -31,7 +31,18 @@ import type {
3131
ServerRequestEvent,
3232
ServerRequestMode,
3333
} from './types';
34-
import { IsQData, QDATA_JSON, QDATA_JSON_LEN } from './user-response';
34+
import {
35+
IsQData,
36+
IsQLoader,
37+
IsQLoaderData,
38+
Q_LOADER_DATA_JSON,
39+
Q_LOADER_DATA_JSON_LEN,
40+
QDATA_JSON,
41+
QDATA_JSON_LEN,
42+
QLoaderId,
43+
SINGLE_LOADER_REGEX,
44+
} from './user-response';
45+
import { executeLoader } from './loader-endpoints';
3546

3647
const RequestEvLoaders = Symbol('RequestEvLoaders');
3748
const RequestEvMode = Symbol('RequestEvMode');
@@ -61,12 +72,26 @@ export function createRequestEvent(
6172
const cookie = new Cookie(request.headers.get('cookie'));
6273
const headers = new Headers();
6374
const url = new URL(request.url);
64-
if (url.pathname.endsWith(QDATA_JSON)) {
65-
url.pathname = url.pathname.slice(0, -QDATA_JSON_LEN);
75+
76+
const trimEnd = (length: number) => {
77+
url.pathname = url.pathname.slice(0, -length);
6678
if (!globalThis.__NO_TRAILING_SLASH__ && !url.pathname.endsWith('/')) {
6779
url.pathname += '/';
6880
}
81+
};
82+
83+
if (url.pathname.endsWith(QDATA_JSON)) {
84+
trimEnd(QDATA_JSON_LEN);
6985
sharedMap.set(IsQData, true);
86+
} else if (url.pathname.endsWith(Q_LOADER_DATA_JSON)) {
87+
trimEnd(Q_LOADER_DATA_JSON_LEN);
88+
sharedMap.set(IsQLoaderData, true);
89+
}
90+
const loaderMatch = url.pathname.match(SINGLE_LOADER_REGEX);
91+
if (loaderMatch) {
92+
trimEnd(loaderMatch[0].length);
93+
sharedMap.set(IsQLoader, true);
94+
sharedMap.set(QLoaderId, loaderMatch[1]); // Store which loader was requested
7095
}
7196

7297
let routeModuleIndex = -1;
@@ -211,7 +236,7 @@ export function createRequestEvent(
211236
}
212237
if (loaders[id] === _UNINITIALIZED) {
213238
const isDev = getRequestMode(requestEv) === 'dev';
214-
await getRouteLoaderPromise(loaderOrAction, loaders, requestEv, isDev);
239+
await executeLoader(loaderOrAction, loaders, requestEv, isDev);
215240
}
216241
}
217242

0 commit comments

Comments
 (0)