Skip to content

Commit d006823

Browse files
committed
feat: implement serializationStrategy for route loaders
1 parent 4219781 commit d006823

File tree

18 files changed

+102
-84
lines changed

18 files changed

+102
-84
lines changed

packages/qwik-router/src/middleware/request-handler/middleware.request-handler.api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import type { RenderOptions } from '@qwik.dev/core/server';
1717
import { RequestEvent as RequestEvent_2 } from '@qwik.dev/router/middleware/request-handler';
1818
import type { RequestHandler as RequestHandler_2 } from '@qwik.dev/router/middleware/request-handler';
1919
import type { ResolveSyncValue as ResolveSyncValue_2 } from '@qwik.dev/router/middleware/request-handler';
20+
import type { SerializationStrategy } from '@qwik.dev/core/internal';
2021
import type { _serialize } from '@qwik.dev/core/internal';
2122
import type { ValueOrPromise } from '@qwik.dev/core';
2223
import type { _verifySerializable } from '@qwik.dev/core/internal';

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { ValueOrPromise } from '@qwik.dev/core';
2+
import type { SerializationStrategy } from '@qwik.dev/core/internal';
23
import { QDATA_KEY } from '../../runtime/src/constants';
34
import {
45
LoadedRouteProp,
@@ -33,6 +34,9 @@ const RequestEvLoaders = Symbol('RequestEvLoaders');
3334
const RequestEvMode = Symbol('RequestEvMode');
3435
const RequestEvRoute = Symbol('RequestEvRoute');
3536
export const RequestEvQwikSerializer = Symbol('RequestEvQwikSerializer');
37+
export const RequestEvLoadersSerializationStrategy = Symbol(
38+
'RequestEvLoadersSerializationStrategy'
39+
);
3640
export const RequestEvTrailingSlash = Symbol('RequestEvTrailingSlash');
3741
export const RequestRouteName = '@routeName';
3842
export const RequestEvSharedActionId = '@actionId';
@@ -149,6 +153,7 @@ export function createRequestEvent(
149153
const loaders: Record<string, Promise<any>> = {};
150154
const requestEv: RequestEventInternal = {
151155
[RequestEvLoaders]: loaders,
156+
[RequestEvLoadersSerializationStrategy]: new Map(),
152157
[RequestEvMode]: serverRequestEv.mode,
153158
[RequestEvTrailingSlash]: trailingSlash,
154159
get [RequestEvRoute]() {
@@ -328,6 +333,7 @@ export function createRequestEvent(
328333

329334
export interface RequestEventInternal extends RequestEvent, RequestEventLoader {
330335
[RequestEvLoaders]: Record<string, ValueOrPromise<unknown> | undefined>;
336+
[RequestEvLoadersSerializationStrategy]: Map<string, SerializationStrategy>;
331337
[RequestEvMode]: ServerRequestMode;
332338
[RequestEvTrailingSlash]: boolean;
333339
[RequestEvRoute]: LoadedRoute | null;
@@ -358,6 +364,10 @@ export function getRequestLoaders(requestEv: RequestEventCommon) {
358364
return (requestEv as RequestEventInternal)[RequestEvLoaders];
359365
}
360366

367+
export function getRequestLoadersSerializationStrategy(requestEv: RequestEventCommon) {
368+
return (requestEv as RequestEventInternal)[RequestEvLoadersSerializationStrategy];
369+
}
370+
361371
export function getRequestTrailingSlash(requestEv: RequestEventCommon) {
362372
return (requestEv as RequestEventInternal)[RequestEvTrailingSlash];
363373
}

packages/qwik-router/src/middleware/request-handler/resolve-request-handlers.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { type QRL } from '@qwik.dev/core';
22
import type { Render, RenderToStringResult } from '@qwik.dev/core/server';
3-
import { QACTION_KEY, QFN_KEY, QLOADER_EAGER, QLOADER_KEY } from '../../runtime/src/constants';
3+
import { QACTION_KEY, QFN_KEY, QLOADER_KEY } from '../../runtime/src/constants';
44
import {
55
type ActionInternal,
66
type ClientPageData,
@@ -26,6 +26,7 @@ import {
2626
type RequestEventInternal,
2727
RequestEvShareServerTiming,
2828
RequestEvShareQData,
29+
getRequestLoadersSerializationStrategy,
2930
} from './request-event';
3031
import { getQwikRouterServerData } from './response-page';
3132
import type {
@@ -307,7 +308,8 @@ async function getRouteLoaderPromise(
307308
}
308309
return resolvedLoader;
309310
});
310-
(loaders[loaderId] as any)[QLOADER_EAGER] = loader.__eager;
311+
const loadersSerializationStrategy = getRequestLoadersSerializationStrategy(requestEv);
312+
loadersSerializationStrategy.set(loaderId, loader.__serializationStrategy);
311313
return loaders[loaderId];
312314
}
313315

packages/qwik-router/src/middleware/request-handler/response-page.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { QwikRouterEnvData } from '../../runtime/src/types';
22
import {
33
getRequestLoaders,
4+
getRequestLoadersSerializationStrategy,
45
getRequestRoute,
56
RequestEvSharedActionFormData,
67
RequestEvSharedActionId,
@@ -32,6 +33,7 @@ export function getQwikRouterServerData(requestEv: RequestEvent) {
3233
}
3334

3435
const loaders = getRequestLoaders(requestEv);
36+
const loadersSerializationStrategy = getRequestLoadersSerializationStrategy(requestEv);
3537

3638
return {
3739
url: reconstructedUrl.href,
@@ -49,6 +51,7 @@ export function getQwikRouterServerData(requestEv: RequestEvent) {
4951
response: {
5052
status: status(),
5153
loaders,
54+
loadersSerializationStrategy,
5255
action,
5356
formData,
5457
},

packages/qwik-router/src/runtime/src/constants.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,3 @@ export const QFN_KEY = 'qfunc';
1313
export const QDATA_KEY = 'qdata';
1414

1515
export const Q_ROUTE = 'q:route';
16-
17-
export const QLOADER_EAGER = Symbol('qloader_eager');

packages/qwik-router/src/runtime/src/qwik-router-component.tsx

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ export interface QwikRouterProps {
106106
*
107107
* @see https://github.com/WICG/view-transitions/blob/main/explainer.md
108108
* @see https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API
109-
* @see https://caniuse.com/mdn-api_viewtransition
109+
* @see https://caniuse.com/mdn_api_viewtransition
110110
*/
111111
viewTransition?: boolean;
112112
}
@@ -176,26 +176,29 @@ export const QwikRouterProvider = component$<QwikRouterProps>((props) => {
176176
);
177177
const navResolver: { r?: () => void } = {};
178178
const container = _getContextContainer();
179-
const spaLoaderState: Record<string, unknown> = Object.fromEntries(
180-
Object.entries(env.response.loaders).map(([k, v]) => {
181-
return [k, v];
182-
})
183-
);
184-
(spaLoaderState as any)[SerializerSymbol] = (obj: Record<string, unknown>) => {
185-
return Object.fromEntries(
186-
Object.entries(obj).map(([k, v]) => {
187-
return [k, _UNINITIALIZED];
188-
})
179+
const getSerializationStrategy = (loaderId: string) => {
180+
return env.response.loadersSerializationStrategy.get(loaderId) || 'never';
181+
};
182+
183+
const loadersObject: Record<string, unknown> = {};
184+
const loaderState: Record<string, Signal<unknown>> = {};
185+
for (const [key, value] of Object.entries(env.response.loaders)) {
186+
loadersObject[key] = value;
187+
loaderState[key] = createLoaderSignal(
188+
loadersObject,
189+
key,
190+
url,
191+
getSerializationStrategy(key),
192+
container
189193
);
194+
}
195+
(loadersObject as any)[SerializerSymbol] = (obj: Record<string, unknown>) => {
196+
const loadersSerializationObject: Record<string, unknown> = {};
197+
for (const [k, v] of Object.entries(obj)) {
198+
loadersSerializationObject[k] = getSerializationStrategy(k) === 'always' ? v : _UNINITIALIZED;
199+
}
200+
return loadersSerializationObject;
190201
};
191-
const loaderState = Object.fromEntries(
192-
Object.entries(env.response.loaders).map(([k, v]) => {
193-
// const isEager = (v as any)[QLOADER_EAGER];
194-
// const value = isEager ? createSignal(v) : createLoaderSignal(spaLoaderState, k, v, url);
195-
const value = createLoaderSignal(spaLoaderState, k, url, container);
196-
return [k, value];
197-
})
198-
);
199202

200203
const routeInternal = useSignal<RouteStateInternal>({
201204
type: 'initial',
@@ -515,9 +518,15 @@ export const QwikRouterProvider = component$<QwikRouterProps>((props) => {
515518
for (const [key, value] of Object.entries(loaders)) {
516519
const signal = loaderState[key] as Signal<unknown>;
517520
const awaitedValue = await value;
518-
spaLoaderState[key] = awaitedValue;
521+
loadersObject[key] = awaitedValue;
519522
if (!signal) {
520-
loaderState[key] = createLoaderSignal(spaLoaderState, key, trackUrl, container);
523+
loaderState[key] = createLoaderSignal(
524+
loadersObject,
525+
key,
526+
trackUrl,
527+
'never',
528+
container
529+
);
521530
} else {
522531
(signal as any).force();
523532
}

packages/qwik-router/src/runtime/src/qwik-router.runtime.api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { RequestEventCommon } from '@qwik.dev/router/middleware/request-handler'
2323
import { RequestEventLoader } from '@qwik.dev/router/middleware/request-handler';
2424
import { RequestHandler } from '@qwik.dev/router/middleware/request-handler';
2525
import type { ResolveSyncValue } from '@qwik.dev/router/middleware/request-handler';
26+
import type { SerializationStrategy } from '@qwik.dev/core/internal';
2627
import type * as v from 'valibot';
2728
import type { ValueOrPromise } from '@qwik.dev/core';
2829
import { z } from 'zod';

packages/qwik-router/src/runtime/src/server-functions.ts

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
_serialize,
1818
_useInvokeContext,
1919
_UNINITIALIZED,
20+
type SerializationStrategy,
2021
} from '@qwik.dev/core/internal';
2122

2223
import * as v from 'valibot';
@@ -29,9 +30,8 @@ import type {
2930
ActionConstructorQRL,
3031
ActionInternal,
3132
ActionStore,
32-
CommonLoaderActionOptions,
33+
ActionOptions,
3334
DataValidator,
34-
EagerLoaderOptions,
3535
Editable,
3636
JSONObject,
3737
LoaderConstructor,
@@ -54,6 +54,7 @@ import type {
5454
ZodConstructor,
5555
ZodConstructorQRL,
5656
ZodDataValidator,
57+
LoaderOptions,
5758
} from './types';
5859
import { useAction, useLocation, useQwikRouterEnv } from './use-functions';
5960

@@ -64,7 +65,7 @@ import { loadClientData } from './use-endpoint';
6465
/** @internal */
6566
export const routeActionQrl = ((
6667
actionQrl: QRL<(form: JSONObject, event: RequestEventAction) => unknown>,
67-
...rest: (CommonLoaderActionOptions | DataValidator)[]
68+
...rest: (ActionOptions | DataValidator)[]
6869
) => {
6970
const { id, validators } = getValidators(rest, actionQrl);
7071
function action() {
@@ -168,7 +169,7 @@ Action.run() can only be called on the browser, for example when a user clicks a
168169
/** @internal */
169170
export const globalActionQrl = ((
170171
actionQrl: QRL<(form: JSONObject, event: RequestEventAction) => unknown>,
171-
...rest: (CommonLoaderActionOptions | DataValidator)[]
172+
...rest: (ActionOptions | DataValidator)[]
172173
) => {
173174
const action = routeActionQrl(actionQrl, ...(rest as any));
174175
if (isServer) {
@@ -193,9 +194,9 @@ export const globalAction$: ActionConstructor = /*#__PURE__*/ implicit$FirstArg(
193194
/** @internal */
194195
export const routeLoaderQrl = ((
195196
loaderQrl: QRL<(event: RequestEventLoader) => unknown>,
196-
...rest: (EagerLoaderOptions | DataValidator)[]
197+
...rest: (LoaderOptions | DataValidator)[]
197198
): LoaderInternal => {
198-
const { id, validators, eager } = getValidators(rest, loaderQrl);
199+
const { id, validators, serializationStrategy } = getValidators(rest, loaderQrl);
199200
function loader() {
200201
const iCtx = _useInvokeContext();
201202
const state = iCtx.$container$.resolveContext(iCtx.$hostElement$, RouteStateContext)!;
@@ -210,7 +211,7 @@ export const routeLoaderQrl = ((
210211
For more information check: https://qwik.dev/docs/re-exporting-loaders/`);
211212
}
212213
const loaderData = untrack(() => state[id].value);
213-
if (loaderData === _UNINITIALIZED && isBrowser) {
214+
if (isBrowser && loaderData === _UNINITIALIZED) {
214215
// Request the loader data from the server and throw the Promise
215216
// so the client can load it synchronously.
216217
throw loadClientData(location.url, iCtx.$hostElement$, {
@@ -225,7 +226,7 @@ export const routeLoaderQrl = ((
225226
loader.__qrl = loaderQrl;
226227
loader.__validators = validators;
227228
loader.__id = id;
228-
loader.__eager = eager;
229+
loader.__serializationStrategy = serializationStrategy;
229230
Object.freeze(loader);
230231

231232
return loader;
@@ -515,9 +516,9 @@ export const serverQrl = <T extends ServerFunction>(
515516
/** @public */
516517
export const server$ = /*#__PURE__*/ implicit$FirstArg(serverQrl);
517518

518-
const getValidators = (rest: (EagerLoaderOptions | DataValidator)[], qrl: QRL) => {
519+
const getValidators = (rest: (LoaderOptions | DataValidator)[], qrl: QRL) => {
519520
let id: string | undefined;
520-
let eager = false;
521+
let serializationStrategy: SerializationStrategy = 'never';
521522
const validators: DataValidator[] = [];
522523
if (rest.length === 1) {
523524
const options = rest[0];
@@ -526,7 +527,7 @@ const getValidators = (rest: (EagerLoaderOptions | DataValidator)[], qrl: QRL) =
526527
validators.push(options);
527528
} else {
528529
id = options.id;
529-
eager = options.eager || false;
530+
serializationStrategy = options.serializationStrategy || 'never';
530531
if (options.validation) {
531532
validators.push(...options.validation);
532533
}
@@ -549,7 +550,7 @@ const getValidators = (rest: (EagerLoaderOptions | DataValidator)[], qrl: QRL) =
549550
return {
550551
validators: validators.reverse(),
551552
id,
552-
eager,
553+
serializationStrategy,
553554
};
554555
};
555556

packages/qwik-router/src/runtime/src/types.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type {
66
Signal,
77
ValueOrPromise,
88
} from '@qwik.dev/core';
9+
import type { SerializationStrategy } from '@qwik.dev/core/internal';
910
import type {
1011
EnvGetter,
1112
RequestEvent,
@@ -319,11 +320,13 @@ export const enum LoadedRouteProp {
319320
export interface EndpointResponse {
320321
status: number;
321322
loaders: Record<string, unknown>;
323+
loadersSerializationStrategy: Map<string, SerializationStrategy>;
322324
formData?: FormData;
323325
action?: string;
324326
}
325327

326-
export interface ClientPageData extends Omit<EndpointResponse, 'status'> {
328+
export interface ClientPageData
329+
extends Omit<EndpointResponse, 'status' | 'loadersSerializationStrategy'> {
327330
status: number;
328331
href: string;
329332
redirect?: string;
@@ -403,15 +406,11 @@ export type GetValidatorType<VALIDATOR extends TypedDataValidator> =
403406
GetValidatorOutputType<VALIDATOR>;
404407

405408
/** @public */
406-
export type CommonLoaderActionOptions = {
409+
export type ActionOptions = {
407410
readonly id?: string;
408411
readonly validation?: DataValidator[];
409412
};
410413

411-
export interface EagerLoaderOptions extends CommonLoaderActionOptions {
412-
readonly eager?: boolean;
413-
}
414-
415414
/** @public */
416415
export type FailOfRest<REST extends readonly DataValidator[]> = REST extends readonly DataValidator<
417416
infer ERROR
@@ -651,8 +650,9 @@ export type ActionConstructorQRL = {
651650

652651
/** @public */
653652
export type LoaderOptions = {
654-
id?: string;
655-
eager?: boolean;
653+
readonly id?: string;
654+
readonly validation?: DataValidator[];
655+
readonly serializationStrategy?: SerializationStrategy;
656656
};
657657

658658
/** @public */
@@ -796,7 +796,7 @@ export interface LoaderInternal extends Loader<any> {
796796
__qrl: QRL<(event: RequestEventLoader) => ValueOrPromise<unknown>>;
797797
__id: string;
798798
__validators: DataValidator[] | undefined;
799-
__eager: boolean;
799+
__serializationStrategy: SerializationStrategy;
800800
(): LoaderSignal<unknown>;
801801
}
802802

packages/qwik-router/src/runtime/src/utils.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
_getDomContainer,
88
_useInvokeContext,
99
type ClientContainer,
10+
type SerializationStrategy,
1011
} from '@qwik.dev/core/internal';
1112
import { createAsyncComputed$, isBrowser } from '@qwik.dev/core';
1213
import { loadClientData } from './use-endpoint';
@@ -105,25 +106,25 @@ export const deepFreeze = (obj: any) => {
105106
};
106107

107108
export const createLoaderSignal = (
108-
spaLoaderState: Record<string, unknown>,
109+
loadersObject: Record<string, unknown>,
109110
loaderId: string,
110111
url: URL,
112+
serializationStrategy: SerializationStrategy,
111113
container?: ClientContainer
112114
) => {
113-
// container?.$ignoredComputedValues$.add(value);
114115
return createAsyncComputed$(
115116
async () => {
116-
if (isBrowser && spaLoaderState[loaderId] === _UNINITIALIZED) {
117+
if (isBrowser && loadersObject[loaderId] === _UNINITIALIZED) {
117118
const data = await loadClientData(url, undefined, {
118119
loaderIds: [loaderId],
119120
});
120-
spaLoaderState[loaderId] = data?.loaders[loaderId] ?? _UNINITIALIZED;
121+
loadersObject[loaderId] = data?.loaders[loaderId] ?? _UNINITIALIZED;
121122
}
122-
return spaLoaderState[loaderId];
123+
return loadersObject[loaderId];
123124
},
124125
{
125126
container: container as ClientContainer,
126-
serializationStrategy: 'never',
127+
serializationStrategy,
127128
}
128129
);
129130
};

0 commit comments

Comments
 (0)