Skip to content

Commit c474a8b

Browse files
committed
refactor: update ISsrNode structure and related serialization logic
1 parent dc59c00 commit c474a8b

12 files changed

+47
-62
lines changed

.changeset/honest-pears-sniff.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@qwik.dev/core': patch
3+
---
4+
5+
fix: serialize less vnode data

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -892,12 +892,11 @@ export abstract class _SharedContainer implements Container {
892892
// (undocumented)
893893
serializationCtxFactory(NodeConstructor: {
894894
new (...rest: any[]): {
895-
nodeType: number;
896-
id: string;
895+
__brand__: 'SsrNode';
897896
};
898897
} | null, DomRefConstructor: {
899898
new (...rest: any[]): {
900-
$ssrNode$: ISsrNode;
899+
__brand__: 'DomRef';
901900
};
902901
} | null, symbolToChunkResolver: SymbolToChunkResolver, writer?: StreamWriter): SerializationContext;
903902
// (undocumented)

packages/qwik/src/core/reactive-primitives/subscriber.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,5 @@ export function getSubscriber(
3030
}
3131

3232
function isSsrNode(value: any): value is ISsrNode {
33-
return '__brand__' in value && 'currentComponentNode' in value;
33+
return '__brand__' in value && value.__brand__ === 'SsrNode';
3434
}

packages/qwik/src/core/shared/scheduler-document-position.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,11 +96,11 @@ export const ssrNodeDocumentPosition = (a: ISsrNode, b: ISsrNode): -1 | 0 | 1 =>
9696
let bDepth = -1;
9797
while (a) {
9898
const ssrNode = (aSsrNodePath[++aDepth] = a);
99-
a = ssrNode.currentComponentNode!;
99+
a = ssrNode.parentSsrNode!;
100100
}
101101
while (b) {
102102
const ssrNode = (bSsrNodePath[++bDepth] = b);
103-
b = ssrNode.currentComponentNode!;
103+
b = ssrNode.parentSsrNode!;
104104
}
105105

106106
while (aDepth >= 0 && bDepth >= 0) {

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { trackSignalAndAssignHost } from '../use/use-core';
33
import { version } from '../version';
44
import type { SubscriptionData } from '../reactive-primitives/subscription-data';
55
import type { Signal } from '../reactive-primitives/signal.public';
6-
import type { ISsrNode, StreamWriter, SymbolToChunkResolver } from '../ssr/ssr-types';
6+
import type { StreamWriter, SymbolToChunkResolver } from '../ssr/ssr-types';
77
import type { Scheduler } from './scheduler';
88
import { createScheduler } from './scheduler';
99
import { createSerializationContext, type SerializationContext } from './shared-serialization';
@@ -51,10 +51,10 @@ export abstract class _SharedContainer implements Container {
5151

5252
serializationCtxFactory(
5353
NodeConstructor: {
54-
new (...rest: any[]): { nodeType: number; id: string };
54+
new (...rest: any[]): { __brand__: 'SsrNode' };
5555
} | null,
5656
DomRefConstructor: {
57-
new (...rest: any[]): { $ssrNode$: ISsrNode };
57+
new (...rest: any[]): { __brand__: 'DomRef' };
5858
} | null,
5959
symbolToChunkResolver: SymbolToChunkResolver,
6060
writer?: StreamWriter

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -601,7 +601,6 @@ export function inflateQRL(container: DeserializeContainer, qrl: QRLInternal<any
601601

602602
/** A selection of attributes of the real thing */
603603
type SsrNode = {
604-
nodeType: number;
605604
id: string;
606605
children: ISsrNode[] | null;
607606
vnodeData: VNodeData;
@@ -689,11 +688,11 @@ export const createSerializationContext = (
689688
* server will not know what to do with them.
690689
*/
691690
NodeConstructor: {
692-
new (...rest: any[]): { nodeType: number; id: string };
691+
new (...rest: any[]): { __brand__: 'SsrNode' };
693692
} | null,
694693
/** DomRef constructor, for instanceof checks. */
695694
DomRefConstructor: {
696-
new (...rest: any[]): { $ssrNode$: ISsrNode };
695+
new (...rest: any[]): { __brand__: 'DomRef' };
697696
} | null,
698697
symbolToChunkResolver: SymbolToChunkResolver,
699698
getProp: (obj: any, prop: string) => any,
@@ -760,9 +759,10 @@ export const createSerializationContext = (
760759
return seen.$rootIndex$;
761760
};
762761

763-
const isSsrNode = (NodeConstructor ? (obj) => obj instanceof NodeConstructor : () => false) as (
764-
obj: unknown
765-
) => obj is SsrNode;
762+
const isSsrNode = (
763+
NodeConstructor ? (obj) => obj instanceof NodeConstructor : ((() => false) as any)
764+
) as (obj: unknown) => obj is SsrNode;
765+
766766
isDomRef = (
767767
DomRefConstructor ? (obj) => obj instanceof DomRefConstructor : ((() => false) as any)
768768
) as (obj: unknown) => obj is DomRef;

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,10 @@ export interface Container {
4545
ensureProjectionResolved(host: HostElement): void;
4646
serializationCtxFactory(
4747
NodeConstructor: {
48-
new (...rest: any[]): { nodeType: number; id: string };
48+
new (...rest: any[]): { __brand__: 'SsrNode' };
4949
} | null,
5050
DomRefConstructor: {
51-
new (...rest: any[]): { $ssrNode$: ISsrNode };
51+
new (...rest: any[]): { __brand__: 'DomRef' };
5252
} | null,
5353
symbolToChunkResolver: SymbolToChunkResolver,
5454
writer?: StreamWriter

packages/qwik/src/core/ssr/ssr-types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export interface StreamWriter {
2525

2626
export interface ISsrNode {
2727
id: string;
28-
currentComponentNode: ISsrNode | null;
28+
parentSsrNode: ISsrNode | null;
2929
vnodeData?: VNodeData;
3030
setProp(name: string, value: any): void;
3131
getProp(name: string): any;

packages/qwik/src/core/tests/container.spec.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,7 @@ describe('serializer v2', () => {
512512

513513
describe('DocumentSerializer, //////', () => {
514514
it('should serialize and deserialize', async () => {
515-
const obj = new SsrNode(null, SsrNode.DOCUMENT_NODE, '', [], [], [] as any);
515+
const obj = new SsrNode(null, '', [], [], [] as any);
516516
const container = await withContainer((ssr) => ssr.addRoot(obj));
517517
expect(container.$getObjectById$(0)).toEqual(container.element.ownerDocument);
518518
});

packages/qwik/src/server/ssr-container.ts

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -296,14 +296,14 @@ class SSRContainer extends _SharedContainer implements ISSRContainer {
296296
return value;
297297
}
298298
}
299-
ssrNode = ssrNode.currentComponentNode;
299+
ssrNode = ssrNode.parentSsrNode;
300300
}
301301
return undefined;
302302
}
303303

304304
getParentHost(host: HostElement): HostElement | null {
305305
const ssrNode: ISsrNode = host as any;
306-
return ssrNode.currentComponentNode as ISsrNode | null;
306+
return ssrNode.parentSsrNode as ISsrNode | null;
307307
}
308308

309309
setHostProp<T>(host: ISsrNode, name: string, value: T): void {
@@ -510,7 +510,7 @@ class SSRContainer extends _SharedContainer implements ISSRContainer {
510510
const componentFrame = this.componentStack.pop()!;
511511
componentFrame.releaseUnclaimedProjections(this.unclaimedProjections);
512512
this.closeFragment();
513-
this.currentComponentNode = this.currentComponentNode?.currentComponentNode || null;
513+
this.currentComponentNode = this.currentComponentNode?.parentSsrNode || null;
514514
}
515515

516516
/** Write a text node with correct escaping. Save the length of the text node in the vNodeData. */
@@ -726,7 +726,6 @@ class SSRContainer extends _SharedContainer implements ISSRContainer {
726726
}
727727
value = String(rootId);
728728
}
729-
let skip = false;
730729
switch (key) {
731730
case QScopedStyle:
732731
write(VNodeDataChar.SCOPED_STYLE_CHAR);
@@ -741,11 +740,7 @@ class SSRContainer extends _SharedContainer implements ISSRContainer {
741740
write(VNodeDataChar.PROPS_CHAR);
742741
break;
743742
case ELEMENT_KEY:
744-
if (Object.keys(value).length === 0) {
745-
skip = true;
746-
} else {
747-
write(VNodeDataChar.KEY_CHAR);
748-
}
743+
write(VNodeDataChar.KEY_CHAR);
749744
break;
750745
case ELEMENT_SEQ:
751746
write(VNodeDataChar.SEQ_CHAR);
@@ -771,9 +766,7 @@ class SSRContainer extends _SharedContainer implements ISSRContainer {
771766
write(key);
772767
write(VNodeDataChar.SEPARATOR_CHAR);
773768
}
774-
if (!skip) {
775-
write(value);
776-
}
769+
write(value);
777770
}
778771
}
779772

packages/qwik/src/server/ssr-node.ts

Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -28,43 +28,34 @@ import type { VNodeData } from './vnode-data';
2828
* Once deserialized the client, they will be turned to ElementVNodes.
2929
*/
3030
export class SsrNode implements ISsrNode {
31-
__brand__!: 'SsrNode';
32-
33-
static ELEMENT_NODE = 1 as const;
34-
static TEXT_NODE = 3 as const;
35-
static DOCUMENT_NODE = 9 as const;
36-
static DOCUMENT_FRAGMENT_NODE = 11 as const;
37-
38-
/** @param nodeType - Node type: ELEMENT_NODE, TEXT_NODE, DOCUMENT_NODE */
39-
public nodeType: SsrNodeType;
31+
__brand__ = 'SsrNode' as const;
4032

4133
/**
4234
* ID which the deserialize will use to retrieve the node.
4335
*
44-
* @param refId - Unique id for the node.
36+
* @param id - Unique id for the node.
4537
*/
4638
public id: string;
4739

48-
/** Local props which don't serialize; */
49-
private locals: SsrAttrs | null = null;
50-
public currentComponentNode: ISsrNode | null;
40+
public parentSsrNode: ISsrNode | null;
5141
public children: ISsrNode[] | null = null;
5242

43+
/** Local props which don't serialize; */
44+
private localProps: SsrAttrs | null = null;
45+
5346
get [_EFFECT_BACK_REF]() {
5447
return this.getProp(QBackRefs);
5548
}
5649

5750
constructor(
58-
currentComponentNode: ISsrNode | null,
59-
nodeType: SsrNodeType,
51+
parentSsrNode: ISsrNode | null,
6052
id: string,
6153
private attrs: SsrAttrs,
6254
private cleanupQueue: CleanupQueue,
6355
public vnodeData: VNodeData
6456
) {
65-
this.currentComponentNode = currentComponentNode;
66-
this.currentComponentNode?.addChild(this);
67-
this.nodeType = nodeType;
57+
this.parentSsrNode = parentSsrNode;
58+
this.parentSsrNode?.addChild(this);
6859
this.id = id;
6960
if (isDev && id.indexOf('undefined') != -1) {
7061
throw new Error(`Invalid SSR node id: ${id}`);
@@ -76,7 +67,7 @@ export class SsrNode implements ISsrNode {
7667
this.attrs = [];
7768
}
7869
if (name.startsWith(NON_SERIALIZABLE_MARKER_PREFIX)) {
79-
mapArray_set(this.locals || (this.locals = []), name, value, 0);
70+
mapArray_set(this.localProps || (this.localProps = []), name, value, 0);
8071
} else {
8172
mapArray_set(this.attrs, name, value, 0);
8273
}
@@ -89,16 +80,16 @@ export class SsrNode implements ISsrNode {
8980

9081
getProp(name: string): any {
9182
if (name.startsWith(NON_SERIALIZABLE_MARKER_PREFIX)) {
92-
return this.locals ? mapArray_get(this.locals, name, 0) : null;
83+
return this.localProps ? mapArray_get(this.localProps, name, 0) : null;
9384
} else {
9485
return mapArray_get(this.attrs, name, 0);
9586
}
9687
}
9788

9889
removeProp(name: string): void {
9990
if (name.startsWith(NON_SERIALIZABLE_MARKER_PREFIX)) {
100-
if (this.locals) {
101-
mapApp_remove(this.locals, name, 0);
91+
if (this.localProps) {
92+
mapApp_remove(this.localProps, name, 0);
10293
}
10394
} else {
10495
mapApp_remove(this.attrs, name, 0);
@@ -129,11 +120,10 @@ export class SsrNode implements ISsrNode {
129120

130121
/** A ref to a DOM element */
131122
export class DomRef {
123+
__brand__ = 'DomRef' as const;
132124
constructor(public $ssrNode$: ISsrNode) {}
133125
}
134126

135-
export type SsrNodeType = 1 | 3 | 9 | 11;
136-
137127
export class SsrComponentFrame implements ISsrComponentFrame {
138128
public slots = [];
139129
public projectionDepth = 0;

packages/qwik/src/server/vnode-data.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { ISsrNode, SsrAttrs } from './qwik-types';
2-
import { SsrNode, type SsrNodeType } from './ssr-node';
2+
import { SsrNode } from './ssr-node';
33
import type { CleanupQueue } from './ssr-container';
44
import { VNodeDataFlag } from './types';
55
import { _EMPTY_ARRAY } from '@qwik.dev/core';
@@ -88,7 +88,7 @@ export function vNodeData_createSsrNodeReference(
8888
): ISsrNode {
8989
vNodeData[0] |= VNodeDataFlag.REFERENCE;
9090
let fragmentAttrs: SsrAttrs = _EMPTY_ARRAY;
91-
const stack: (SsrNodeType | number)[] = [SsrNode.ELEMENT_NODE, -1];
91+
const stack: number[] = [-1];
9292
// We are referring to a virtual node. We need to descend into the tree to find the path to the node.
9393
for (let i = 1; i < vNodeData.length; i++) {
9494
const value = vNodeData[i];
@@ -98,11 +98,10 @@ export function vNodeData_createSsrNodeReference(
9898
if (vNodeData[i] !== WRITE_ELEMENT_ATTRS) {
9999
// ignore pushing to the stack for WRITE_ELEMENT_ATTRS, because we don't want to create more depth. It is the same element
100100
stack[stack.length - 1]++;
101-
stack.push(SsrNode.DOCUMENT_FRAGMENT_NODE, -1);
101+
stack.push(-1);
102102
}
103103
} else if (value === CLOSE_FRAGMENT) {
104104
stack.pop(); // pop count
105-
stack.pop(); // pop nodeType
106105
fragmentAttrs = _EMPTY_ARRAY;
107106
} else if (value < 0) {
108107
// Negative numbers are element counts.
@@ -118,15 +117,14 @@ export function vNodeData_createSsrNodeReference(
118117
let refId = String(depthFirstElementIdx);
119118
if (vNodeData[0] & (VNodeDataFlag.VIRTUAL_NODE | VNodeDataFlag.TEXT_DATA)) {
120119
// encode as alphanumeric only for virtual and text nodes
121-
for (let i = 1; i < stack.length; i += 2) {
120+
for (let i = 0; i < stack.length; i++) {
122121
const childCount = stack[i] as number;
123122
if (childCount >= 0) {
124123
refId += encodeAsAlphanumeric(childCount);
125124
}
126125
}
127126
}
128-
const type = stack[stack.length - 2] as SsrNodeType;
129-
return new SsrNode(currentComponentNode, type, refId, fragmentAttrs, cleanupQueue, vNodeData);
127+
return new SsrNode(currentComponentNode, refId, fragmentAttrs, cleanupQueue, vNodeData);
130128
}
131129

132130
/**

0 commit comments

Comments
 (0)