Skip to content

Commit 1a40e8e

Browse files
committed
feat: ssr node serialization
1 parent dea848e commit 1a40e8e

File tree

8 files changed

+104
-76
lines changed

8 files changed

+104
-76
lines changed

packages/qwik/src/core/client/dom-container.ts

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ import { assertTrue } from '../shared/error/assert';
44
import { QError, qError } from '../shared/error/error';
55
import { ERROR_CONTEXT, isRecoverable } from '../shared/error/error-handling';
66
import { getPlatform } from '../shared/platform/platform';
7-
import { emitEvent } from '../shared/qrl/qrl-class';
7+
import { emitEvent, type QRLInternal } from '../shared/qrl/qrl-class';
88
import type { QRL } from '../shared/qrl/qrl.public';
99
import { ChoreType } from '../shared/util-chore-type';
1010
import { _SharedContainer } from '../shared/shared-container';
1111
import {
12+
getObjectById,
1213
inflateQRL,
1314
parseQRL,
1415
preprocessState,
@@ -118,6 +119,7 @@ export class DomContainer extends _SharedContainer implements IClientContainer {
118119
public $qFuncs$: Array<(...args: unknown[]) => unknown>;
119120
public $instanceHash$: string;
120121
public $forwardRefs$: Array<number> | null = null;
122+
public $initialQRLsIndexes$: Array<number> | null = null;
121123
public vNodeLocate: (id: string | Element) => VNode = (id) => vnode_locate(this.rootVNode, id);
122124

123125
private $stateData$: unknown[];
@@ -167,6 +169,7 @@ export class DomContainer extends _SharedContainer implements IClientContainer {
167169
this.$rawStateData$ = JSON.parse(lastState.textContent!);
168170
preprocessState(this.$rawStateData$, this);
169171
this.$stateData$ = wrapDeserializerProxy(this, this.$rawStateData$) as unknown[];
172+
this.$scheduleInitialQRLs$();
170173
}
171174
}
172175

@@ -323,14 +326,7 @@ export class DomContainer extends _SharedContainer implements IClientContainer {
323326
}
324327

325328
$getObjectById$ = (id: number | string): unknown => {
326-
if (typeof id === 'string') {
327-
id = parseFloat(id);
328-
}
329-
assertTrue(
330-
id < this.$rawStateData$.length / 2,
331-
`Invalid reference: ${id} >= ${this.$rawStateData$.length / 2}`
332-
);
333-
return this.$stateData$[id];
329+
return getObjectById(id, this.$stateData$);
334330
};
335331

336332
getSyncFn(id: number): (...args: unknown[]) => unknown {
@@ -378,4 +374,17 @@ export class DomContainer extends _SharedContainer implements IClientContainer {
378374
}
379375
this.$serverData$ = { containerAttributes };
380376
}
377+
378+
private $scheduleInitialQRLs$(): void {
379+
if (this.$initialQRLsIndexes$) {
380+
for (const index of this.$initialQRLsIndexes$) {
381+
this.$scheduler$(
382+
ChoreType.QRL_RESOLVE,
383+
null,
384+
this.$getObjectById$(index) as QRLInternal<(...args: unknown[]) => unknown>
385+
);
386+
}
387+
this.$initialQRLsIndexes$ = null;
388+
}
389+
}
381390
}

packages/qwik/src/core/client/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export interface ClientContainer extends Container {
1919
$journal$: VNodeJournal;
2020
renderDone: Promise<void> | null;
2121
$forwardRefs$: Array<number> | null;
22+
$initialQRLsIndexes$: Array<number> | null;
2223
parseQRL<T = unknown>(qrl: string): QRL<T>;
2324
$setRawState$(id: number, vParent: ElementVNode | VirtualVNode): void;
2425
}

packages/qwik/src/core/core.api.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ export type ClassList = string | undefined | null | false | Record<string, boole
2626
export interface ClientContainer extends Container {
2727
// (undocumented)
2828
$forwardRefs$: Array<number> | null;
29+
// (undocumented)
30+
$initialQRLsIndexes$: Array<number> | null;
2931
// Warning: (ae-forgotten-export) The symbol "VNodeJournal" needs to be exported by the entry point index.d.ts
3032
//
3133
// (undocumented)
@@ -183,6 +185,8 @@ class DomContainer extends _SharedContainer implements ClientContainer {
183185
// (undocumented)
184186
$getObjectById$: (id: number | string) => unknown;
185187
// (undocumented)
188+
$initialQRLsIndexes$: Array<number> | null;
189+
// (undocumented)
186190
$instanceHash$: string;
187191
// (undocumented)
188192
$journal$: VNodeJournal;

packages/qwik/src/core/shared/shared-serialization.ts

Lines changed: 56 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
getStoreTarget,
1515
isStore,
1616
} from '../reactive-primitives/impl/store';
17-
import type { ISsrNode, SymbolToChunkResolver } from '../ssr/ssr-types';
17+
import type { ISsrNode, SsrAttrs, SymbolToChunkResolver } from '../ssr/ssr-types';
1818
import { untrack } from '../use/use-core';
1919
import { createResourceReturn, type ResourceReturnInternal } from '../use/use-resource';
2020
import { isTask, Task } from '../use/use-task';
@@ -646,7 +646,7 @@ export interface SerializationContext {
646646
* Returns a path string representing the path from roots through all parents to the object.
647647
* Format: "3 2 0" where each number is the index within its parent, from root to leaf.
648648
*/
649-
$addRoot$: (obj: unknown, parent: unknown) => string | number;
649+
$addRoot$: (obj: unknown, parent?: unknown) => number;
650650

651651
/**
652652
* Get root path of the object without creating a new root.
@@ -759,7 +759,8 @@ export const createSerializationContext = (
759759
seen.$rootIndex$ = roots.length;
760760
roots.push(obj);
761761
}
762-
return $addRootPath$(obj);
762+
$addRootPath$(obj);
763+
return seen.$rootIndex$;
763764
};
764765

765766
const isSsrNode = (NodeConstructor ? (obj) => obj instanceof NodeConstructor : () => false) as (
@@ -771,7 +772,7 @@ export const createSerializationContext = (
771772

772773
return {
773774
async $serialize$(): Promise<void> {
774-
return serialize(this);
775+
return await serialize(this);
775776
},
776777
$isSsrNode$: isSsrNode,
777778
$isDomRef$: isDomRef,
@@ -839,6 +840,23 @@ function $discoverRoots$(
839840
}
840841
}
841842

843+
const isSsrAttrs = (value: number | SsrAttrs): value is SsrAttrs =>
844+
Array.isArray(value) && value.length > 0;
845+
846+
const discoverValuesForVNodeData = (vnodeData: VNodeData, callback: (value: unknown) => void) => {
847+
for (const value of vnodeData) {
848+
if (isSsrAttrs(value)) {
849+
for (let i = 1; i < value.length; i += 2) {
850+
const attrValue = value[i];
851+
if (typeof attrValue === 'string') {
852+
continue;
853+
}
854+
callback(attrValue);
855+
}
856+
}
857+
}
858+
};
859+
842860
class PromiseResult {
843861
constructor(
844862
public $resolved$: boolean,
@@ -874,16 +892,8 @@ class SerializerResult extends PromiseResult {
874892
* - Therefore root indexes need to be doubled to get the actual index.
875893
*/
876894
async function serialize(serializationContext: SerializationContext): Promise<void> {
877-
const {
878-
$writer$,
879-
$isSsrNode$,
880-
$isDomRef$,
881-
$setProp$,
882-
$storeProxyMap$,
883-
$addRoot$,
884-
$pathMap$,
885-
$wasSeen$,
886-
} = serializationContext;
895+
const { $writer$, $isSsrNode$, $isDomRef$, $storeProxyMap$, $addRoot$, $pathMap$, $wasSeen$ } =
896+
serializationContext;
887897
let depth = 0;
888898
const forwardRefs: number[] = [];
889899
let forwardRefsId = 0;
@@ -923,13 +933,13 @@ async function serialize(serializationContext: SerializationContext): Promise<vo
923933
depth++;
924934
outputArray(value, (valueItem, idx) => {
925935
$discoverRoots$(serializationContext, valueItem, parent, idx);
926-
writeValue(valueItem, idx);
936+
writeValue(valueItem);
927937
});
928938
depth--;
929939
}
930940
};
931941

932-
const writeValue = (value: unknown, idx: number) => {
942+
const writeValue = (value: unknown) => {
933943
if (fastSkipSerialize(value as object)) {
934944
output(TypeIds.Constant, Constants.Undefined);
935945
} else if (typeof value === 'bigint') {
@@ -959,7 +969,7 @@ async function serialize(serializationContext: SerializationContext): Promise<vo
959969
} else {
960970
const qrl = qrlToString(serializationContext, value);
961971
if (!isRootObject) {
962-
const id = serializationContext.$addRoot$(qrl, null);
972+
const id = serializationContext.$addRoot$(qrl);
963973
output(TypeIds.QRL, id);
964974
} else {
965975
output(TypeIds.QRL, qrl);
@@ -1002,7 +1012,7 @@ async function serialize(serializationContext: SerializationContext): Promise<vo
10021012
depth++;
10031013
const oldParent = parent;
10041014
parent = value;
1005-
writeObjectValue(value, idx);
1015+
writeObjectValue(value);
10061016
parent = oldParent;
10071017
depth--;
10081018
}
@@ -1043,7 +1053,7 @@ async function serialize(serializationContext: SerializationContext): Promise<vo
10431053
}
10441054
};
10451055

1046-
const writeObjectValue = (value: {}, idx: number) => {
1056+
const writeObjectValue = (value: {}) => {
10471057
/**
10481058
* The object writer outputs an array object (without type prefix) and this increases the depth
10491059
* for the objects within (depth 1).
@@ -1061,11 +1071,9 @@ async function serialize(serializationContext: SerializationContext): Promise<vo
10611071
output(TypeIds.RootRef, rootPath);
10621072
return;
10631073
}
1064-
}
1065-
if (depth > 1) {
1074+
} else if (depth > 1) {
10661075
const seen = $wasSeen$(value);
10671076
if (seen && seen.$rootIndex$ !== -1) {
1068-
// console.log('writeObjectValue', value, $wasSeen$(value), depth);
10691077
// We have seen this object before, so we can serialize it as a reference.
10701078
// Otherwise serialize as normal
10711079
output(TypeIds.RootRef, seen.$rootIndex$);
@@ -1104,7 +1112,7 @@ async function serialize(serializationContext: SerializationContext): Promise<vo
11041112
if ($storeProxyMap$.has(propValue)) {
11051113
const innerStore = $storeProxyMap$.get(propValue);
11061114
innerStores.push(innerStore);
1107-
serializationContext.$addRoot$(innerStore, null);
1115+
serializationContext.$addRoot$(innerStore);
11081116
}
11091117
}
11101118

@@ -1123,7 +1131,7 @@ async function serialize(serializationContext: SerializationContext): Promise<vo
11231131
output(TypeIds.ForwardRef, forwardRef);
11241132
} else {
11251133
depth--;
1126-
writeValue(result, idx);
1134+
writeValue(result);
11271135
depth++;
11281136
}
11291137
} else if (isObjectLiteral(value)) {
@@ -1200,25 +1208,21 @@ async function serialize(serializationContext: SerializationContext): Promise<vo
12001208
}
12011209
output(TypeIds.Error, out);
12021210
} else if ($isSsrNode$(value)) {
1203-
if (isRootObject) {
1204-
// Tell the SsrNode which root id it is
1205-
$setProp$(value, ELEMENT_ID, String(idx));
1206-
// we need to output before the vnode overwrites its values
1207-
output(TypeIds.VNode, value.id);
1208-
const vNodeData = value.vnodeData;
1209-
if (vNodeData) {
1210-
serializationContext.$prepVNodeData$?.(vNodeData);
1211+
const rootIndex = $addRoot$(value);
1212+
serializationContext.$setProp$(value, ELEMENT_ID, String(rootIndex));
1213+
// we need to output before the vnode overwrites its values
1214+
output(TypeIds.VNode, value.id);
1215+
const vNodeData = value.vnodeData;
1216+
if (vNodeData) {
1217+
serializationContext.$prepVNodeData$?.(vNodeData);
1218+
discoverValuesForVNodeData(vNodeData, (vNodeDataValue) => $addRoot$(vNodeDataValue));
1219+
vNodeData[0] |= VNodeDataFlag.SERIALIZE;
1220+
}
1221+
if (value.childrenVNodeData) {
1222+
for (const vNodeData of value.childrenVNodeData) {
1223+
discoverValuesForVNodeData(vNodeData, (vNodeDataValue) => $addRoot$(vNodeDataValue));
12111224
vNodeData[0] |= VNodeDataFlag.SERIALIZE;
12121225
}
1213-
if (value.childrenVNodeData) {
1214-
for (const vNodeData of value.childrenVNodeData) {
1215-
vNodeData[0] |= VNodeDataFlag.SERIALIZE;
1216-
}
1217-
}
1218-
} else {
1219-
// Promote the vnode to a root
1220-
serializationContext.$addRoot$(value, null);
1221-
output(TypeIds.RootRef, serializationContext.$roots$.length - 1);
12221226
}
12231227
} else if (typeof FormData !== 'undefined' && value instanceof FormData) {
12241228
// FormData is generally used only once so don't bother with references
@@ -1273,7 +1277,7 @@ async function serialize(serializationContext: SerializationContext): Promise<vo
12731277
output(TypeIds.Resource, [value.$resolved$, value.$value$, value.$effects$]);
12741278
} else if (value instanceof SerializerResult) {
12751279
if (value.$resolved$) {
1276-
writeValue(value.$value$, idx);
1280+
writeValue(value.$value$);
12771281
} else {
12781282
console.error(value.$value$);
12791283
throw qError(QError.serializerSymbolRejectedPromise);
@@ -1297,18 +1301,18 @@ async function serialize(serializationContext: SerializationContext): Promise<vo
12971301

12981302
function $resolvePromise$(
12991303
promise: Promise<unknown>,
1300-
$addRoot$: (obj: unknown, parent: unknown) => string | number,
1304+
$addRoot$: (obj: unknown) => string | number,
13011305
classCreator: (resolved: boolean, resolvedValue: unknown) => PromiseResult
13021306
) {
13031307
const forwardRefId = forwardRefsId++;
13041308
promise
13051309
.then((resolvedValue) => {
13061310
promises.delete(promise);
1307-
forwardRefs[forwardRefId] = $addRoot$(classCreator(true, resolvedValue), null) as number;
1311+
forwardRefs[forwardRefId] = $addRoot$(classCreator(true, resolvedValue)) as number;
13081312
})
13091313
.catch((err) => {
13101314
promises.delete(promise);
1311-
forwardRefs[forwardRefId] = $addRoot$(classCreator(false, err), null) as number;
1315+
forwardRefs[forwardRefId] = $addRoot$(classCreator(false, err)) as number;
13121316
});
13131317

13141318
promises.add(promise);
@@ -1326,7 +1330,7 @@ async function serialize(serializationContext: SerializationContext): Promise<vo
13261330
}
13271331
for (let i = lastRootsLength; i < rootsLength; i++) {
13281332
const root = serializationContext.$roots$[i];
1329-
writeValue(root, 0);
1333+
writeValue(root);
13301334
const isLast = i === rootsLength - 1;
13311335
if (!isLast) {
13321336
$writer$.write(',');
@@ -1458,7 +1462,7 @@ export function qrlToString(
14581462
serializedReferences += ' ';
14591463
}
14601464
// We refer by id so every capture needs to be a root
1461-
serializedReferences += serializationContext.$addRoot$(value.$captureRef$[i], null);
1465+
serializedReferences += serializationContext.$addRoot$(value.$captureRef$[i]);
14621466
}
14631467
qrlStringInline += `[${serializedReferences}]`;
14641468
} else if (value.$capture$ && value.$capture$.length > 0) {
@@ -1484,9 +1488,8 @@ export async function _serialize(data: unknown[]): Promise<string> {
14841488
);
14851489

14861490
for (const root of data) {
1487-
serializationContext.$addRoot$(root, null);
1491+
serializationContext.$addRoot$(root);
14881492
}
1489-
// await serializationContext.$breakCircularDepsAndAwaitPromises$();
14901493
await serializationContext.$serialize$();
14911494
return serializationContext.$writer$.toString();
14921495
}
@@ -1531,7 +1534,7 @@ function deserializeData(container: DeserializeContainer, typeId: number, value:
15311534
return propValue;
15321535
}
15331536

1534-
function getObjectById(id: number | string, stateData: unknown[]): unknown {
1537+
export function getObjectById(id: number | string, stateData: unknown[]): unknown {
15351538
if (typeof id === 'string') {
15361539
id = parseInt(id, 10);
15371540
}
@@ -1554,6 +1557,7 @@ export function _createDeserializeContainer(
15541557
$storeProxyMap$: new WeakMap(),
15551558
element: null,
15561559
$forwardRefs$: null,
1560+
$initialQRLsIndexes$: null,
15571561
$scheduler$: null,
15581562
};
15591563
preprocessState(stateData, container);
@@ -1628,9 +1632,6 @@ export function preprocessState(data: unknown[], container: DeserializeContainer
16281632
let valueIndex = 0;
16291633
let parent: unknown[] | null = null;
16301634

1631-
// console.log(dumpState(data));
1632-
// console.log('--------------------------------');
1633-
16341635
for (let i = 0; i < rootRefPath.length; i++) {
16351636
parent = object;
16361637

@@ -1654,9 +1655,6 @@ export function preprocessState(data: unknown[], container: DeserializeContainer
16541655
}
16551656
data[index] = objectType;
16561657
data[index + 1] = object;
1657-
1658-
// console.log(dumpState(data));
1659-
// console.log('--------------------------------');
16601658
};
16611659

16621660
for (let i = 0; i < data.length; i += 2) {
@@ -1665,11 +1663,8 @@ export function preprocessState(data: unknown[], container: DeserializeContainer
16651663
} else if (isForwardRefsMap(data[i] as TypeIds)) {
16661664
container.$forwardRefs$ = data[i + 1] as number[];
16671665
} else if (isQrlType(data[i] as TypeIds)) {
1668-
container.$scheduler$?.(
1669-
ChoreType.QRL_RESOLVE,
1670-
null,
1671-
data[i + 1] as QRLInternal<(...args: unknown[]) => unknown>
1672-
);
1666+
container.$initialQRLsIndexes$ ||= [];
1667+
container.$initialQRLsIndexes$.push(i / 2);
16731668
}
16741669
}
16751670
}

0 commit comments

Comments
 (0)