Skip to content

Commit dc59c00

Browse files
committed
chore: serialize less vnode data
1 parent e38fff6 commit dc59c00

10 files changed

+63
-98
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -899,7 +899,7 @@ export abstract class _SharedContainer implements Container {
899899
new (...rest: any[]): {
900900
$ssrNode$: ISsrNode;
901901
};
902-
} | null, symbolToChunkResolver: SymbolToChunkResolver, writer?: StreamWriter, prepVNodeData?: (vNode: any) => void): SerializationContext;
902+
} | null, symbolToChunkResolver: SymbolToChunkResolver, writer?: StreamWriter): SerializationContext;
903903
// (undocumented)
904904
abstract setContext<T>(host: HostElement, context: ContextId<T>, value: T): void;
905905
// (undocumented)

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,7 @@ export abstract class _SharedContainer implements Container {
5757
new (...rest: any[]): { $ssrNode$: ISsrNode };
5858
} | null,
5959
symbolToChunkResolver: SymbolToChunkResolver,
60-
writer?: StreamWriter,
61-
prepVNodeData?: (vNode: any) => void
60+
writer?: StreamWriter
6261
): SerializationContext {
6362
return createSerializationContext(
6463
NodeConstructor,
@@ -67,8 +66,7 @@ export abstract class _SharedContainer implements Container {
6766
this.getHostProp.bind(this),
6867
this.setHostProp.bind(this),
6968
this.$storeProxyMap$,
70-
writer,
71-
prepVNodeData
69+
writer
7270
);
7371
}
7472

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

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ import type { DeserializeContainer, HostElement, ObjToProxyMap } from './types';
3838
import { _CONST_PROPS, _VAR_PROPS } from './utils/constants';
3939
import { isElement, isNode } from './utils/element';
4040
import { EMPTY_ARRAY, EMPTY_OBJ } from './utils/flyweight';
41-
import { ELEMENT_ID } from './utils/markers';
41+
import { ELEMENT_ID, ELEMENT_PROPS, QBackRefs } from './utils/markers';
4242
import { isPromise } from './utils/promises';
4343
import { SerializerSymbol, fastSkipSerialize } from './utils/serialize-utils';
4444
import {
@@ -603,7 +603,7 @@ export function inflateQRL(container: DeserializeContainer, qrl: QRLInternal<any
603603
type SsrNode = {
604604
nodeType: number;
605605
id: string;
606-
childrenVNodeData: VNodeData[] | null;
606+
children: ISsrNode[] | null;
607607
vnodeData: VNodeData;
608608
[_EFFECT_BACK_REF]: Map<EffectProperty | string, EffectSubscription> | null;
609609
};
@@ -679,7 +679,6 @@ export interface SerializationContext {
679679

680680
$getProp$: (obj: any, prop: string) => any;
681681
$setProp$: (obj: any, prop: string, value: any) => void;
682-
$prepVNodeData$?: (vNodeData: VNodeData) => void;
683682
}
684683

685684
export const createSerializationContext = (
@@ -700,9 +699,7 @@ export const createSerializationContext = (
700699
getProp: (obj: any, prop: string) => any,
701700
setProp: (obj: any, prop: string, value: any) => void,
702701
storeProxyMap: ObjToProxyMap,
703-
writer?: StreamWriter,
704-
// temporary until we serdes the vnode data here
705-
prepVNodeData?: (vNodeData: VNodeData) => void
702+
writer?: StreamWriter
706703
): SerializationContext => {
707704
if (!writer) {
708705
const buffer: string[] = [];
@@ -816,7 +813,6 @@ export const createSerializationContext = (
816813
$storeProxyMap$: storeProxyMap,
817814
$getProp$: getProp,
818815
$setProp$: setProp,
819-
$prepVNodeData$: prepVNodeData,
820816
$pathMap$: rootsPathMap,
821817
};
822818
};
@@ -847,8 +843,14 @@ const discoverValuesForVNodeData = (vnodeData: VNodeData, callback: (value: unkn
847843
for (const value of vnodeData) {
848844
if (isSsrAttrs(value)) {
849845
for (let i = 1; i < value.length; i += 2) {
846+
const keyValue = value[i - 1];
850847
const attrValue = value[i];
851-
if (typeof attrValue === 'string') {
848+
if (
849+
typeof attrValue === 'string' ||
850+
// skip empty props
851+
(keyValue === ELEMENT_PROPS &&
852+
Object.keys(attrValue as Record<string, unknown>).length === 0)
853+
) {
852854
continue;
853855
}
854856
callback(attrValue);
@@ -1192,14 +1194,25 @@ async function serialize(serializationContext: SerializationContext): Promise<vo
11921194
output(TypeIds.VNode, value.id);
11931195
const vNodeData = value.vnodeData;
11941196
if (vNodeData) {
1195-
serializationContext.$prepVNodeData$?.(vNodeData);
11961197
discoverValuesForVNodeData(vNodeData, (vNodeDataValue) => $addRoot$(vNodeDataValue));
11971198
vNodeData[0] |= VNodeDataFlag.SERIALIZE;
11981199
}
1199-
if (value.childrenVNodeData) {
1200-
for (const vNodeData of value.childrenVNodeData) {
1201-
discoverValuesForVNodeData(vNodeData, (vNodeDataValue) => $addRoot$(vNodeDataValue));
1202-
vNodeData[0] |= VNodeDataFlag.SERIALIZE;
1200+
if (value.children) {
1201+
// can be static, but we need to save vnode data structure + discover the back refs
1202+
for (const child of value.children) {
1203+
const childVNodeData = child.vnodeData;
1204+
if (childVNodeData) {
1205+
// add all back refs to the roots
1206+
for (const value of childVNodeData) {
1207+
if (isSsrAttrs(value)) {
1208+
const backRefKeyIndex = value.findIndex((v) => v === QBackRefs);
1209+
if (backRefKeyIndex !== -1) {
1210+
$addRoot$(value[backRefKeyIndex + 1]);
1211+
}
1212+
}
1213+
}
1214+
childVNodeData[0] |= VNodeDataFlag.SERIALIZE;
1215+
}
12031216
}
12041217
}
12051218
} else if (typeof FormData !== 'undefined' && value instanceof FormData) {

packages/qwik/src/core/ssr/ssr-render-component.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export const applyInlineComponent = (
1414
inlineComponentFunction: OnRenderFn<any>,
1515
jsx: JSXNode
1616
) => {
17-
const host = ssr.getLastNode();
17+
const host = ssr.getOrCreateLastNode();
1818
return executeComponent(ssr, host, componentHost, inlineComponentFunction, jsx.props);
1919
};
2020

@@ -23,7 +23,7 @@ export const applyQwikComponentBody = (
2323
jsx: JSXNode,
2424
component: Component
2525
): ValueOrPromise<JSXOutput> => {
26-
const host = ssr.getLastNode();
26+
const host = ssr.getOrCreateLastNode();
2727
const [componentQrl] = (component as any)[SERIALIZABLE_STATE] as [QRLInternal<OnRenderFn<any>>];
2828
const srcProps = jsx.props;
2929
if (srcProps && srcProps.children) {

packages/qwik/src/core/ssr/ssr-render-jsx.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ function processJSXNode(
112112
}
113113
} else if (isSignal(value)) {
114114
ssr.openFragment(isDev ? [DEBUG_TYPE, VirtualType.WrappedSignal] : EMPTY_ARRAY);
115-
const signalNode = ssr.getLastNode();
115+
const signalNode = ssr.getOrCreateLastNode();
116116
enqueue(ssr.closeFragment);
117117
enqueue(trackSignalAndAssignHost(value, signalNode, EffectProperty.VNODE, ssr));
118118
} else if (isPromise(value)) {
@@ -184,7 +184,6 @@ function processJSXNode(
184184
attrs = [DEBUG_TYPE, VirtualType.Fragment, ...attrs]; // Add debug info.
185185
}
186186
ssr.openFragment(attrs);
187-
ssr.addCurrentElementFrameAsComponentChild();
188187
enqueue(ssr.closeFragment);
189188
// In theory we could get functions or regexes, but we assume all is well
190189
const children = jsx.children as JSXOutput;
@@ -198,7 +197,7 @@ function processJSXNode(
198197
projectionAttrs.push(QSlotParent, compId);
199198
ssr.openProjection(projectionAttrs);
200199
const host = componentFrame.componentNode;
201-
const node = ssr.getLastNode();
200+
const node = ssr.getOrCreateLastNode();
202201
const slotName = getSlotName(host, jsx, ssr);
203202
projectionAttrs.push(QSlot, slotName);
204203

@@ -249,7 +248,7 @@ function processJSXNode(
249248
} else if (isQwikComponent(type)) {
250249
// prod: use new instance of an array for props, we always modify props for a component
251250
ssr.openComponent(isDev ? [DEBUG_TYPE, VirtualType.Component] : []);
252-
const host = ssr.getLastNode();
251+
const host = ssr.getOrCreateLastNode();
253252
const componentFrame = ssr.getParentComponentFrame()!;
254253
componentFrame!.distributeChildrenIntoSlots(
255254
jsx.children,

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export interface ISsrNode {
3030
setProp(name: string, value: any): void;
3131
getProp(name: string): any;
3232
removeProp(name: string): void;
33-
addChildVNodeData(child: VNodeData): void;
33+
addChild(child: ISsrNode): void;
3434
}
3535

3636
/** @internal */
@@ -77,7 +77,6 @@ export interface SSRContainer extends Container {
7777

7878
openFragment(attrs: SsrAttrs): void;
7979
closeFragment(): void;
80-
addCurrentElementFrameAsComponentChild(): void;
8180

8281
openProjection(attrs: SsrAttrs): void;
8382
closeProjection(): void;
@@ -91,7 +90,7 @@ export interface SSRContainer extends Container {
9190
htmlNode(rawHtml: string): void;
9291
commentNode(text: string): void;
9392
addRoot(obj: any): number | undefined;
94-
getLastNode(): ISsrNode;
93+
getOrCreateLastNode(): ISsrNode;
9594
addUnclaimedProjection(frame: ISsrComponentFrame, name: string, children: JSXChildren): void;
9695
isStatic(): boolean;
9796
render(jsx: JSXOutput): Promise<void>;

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ describe('serializer v2', () => {
114114
ssr.openElement('div', ['id', 'parent']);
115115
ssr.textNode('Hello');
116116
ssr.openElement('span', ['id', 'myId']);
117-
const node = ssr.getLastNode();
117+
const node = ssr.getOrCreateLastNode();
118118
ssr.addRoot({ someProp: node });
119119
ssr.textNode('Hello');
120120
ssr.openElement('b', ['id', 'child']);
@@ -133,7 +133,7 @@ describe('serializer v2', () => {
133133
ssr.textNode('Greetings');
134134
ssr.textNode(' ');
135135
ssr.textNode('World');
136-
const node = ssr.getLastNode();
136+
const node = ssr.getOrCreateLastNode();
137137
expect(node.id).toBe('2C');
138138
ssr.textNode('!');
139139
ssr.addRoot({ someProp: node });
@@ -154,7 +154,7 @@ describe('serializer v2', () => {
154154
ssr.textNode(' '); // 2B
155155
ssr.openFragment([]); // 2C
156156
ssr.textNode('World'); // 2CA
157-
const node = ssr.getLastNode();
157+
const node = ssr.getOrCreateLastNode();
158158
expect(node.id).toBe('2CA');
159159
ssr.textNode('!');
160160
ssr.addRoot({ someProp: node });

packages/qwik/src/core/tests/use-context.spec.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ describe.each([
255255
<WrappedSignal ssr-required>1</WrappedSignal>
256256
</p>
257257
<p>
258-
<Awaited>0</Awaited>
258+
<Awaited ssr-required>0</Awaited>
259259
</p>
260260
<p>
261261
<WrappedSignal ssr-required>0</WrappedSignal>

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

Lines changed: 17 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -236,8 +236,7 @@ class SSRContainer extends _SharedContainer implements ISSRContainer {
236236
SsrNode,
237237
DomRef,
238238
this.symbolToChunkResolver,
239-
opts.writer,
240-
(vNodeData: VNodeData) => this.addVNodeToSerializationRoots(vNodeData)
239+
opts.writer
241240
);
242241
this.renderTimer = createTimer();
243242
this.tag = opts.tagName;
@@ -453,6 +452,8 @@ class SSRContainer extends _SharedContainer implements ISSRContainer {
453452
openFragment(attrs: SsrAttrs) {
454453
this.lastNode = null;
455454
vNodeData_openFragment(this.currentElementFrame!.vNodeData, attrs);
455+
// create SSRNode and add it as component child to serialize its vnode data
456+
this.getOrCreateLastNode();
456457
}
457458

458459
/** Writes closing data to vNodeData for fragment boundaries */
@@ -461,13 +462,6 @@ class SSRContainer extends _SharedContainer implements ISSRContainer {
461462
this.lastNode = null;
462463
}
463464

464-
addCurrentElementFrameAsComponentChild() {
465-
const vNode = this.currentElementFrame?.vNodeData;
466-
if (vNode) {
467-
this.currentComponentNode?.addChildVNodeData(vNode);
468-
}
469-
}
470-
471465
openProjection(attrs: SsrAttrs) {
472466
this.openFragment(attrs);
473467
const componentFrame = this.getComponentFrame();
@@ -489,7 +483,7 @@ class SSRContainer extends _SharedContainer implements ISSRContainer {
489483
/** Writes opening data to vNodeData for component boundaries */
490484
openComponent(attrs: SsrAttrs) {
491485
this.openFragment(attrs);
492-
this.currentComponentNode = this.getLastNode();
486+
this.currentComponentNode = this.getOrCreateLastNode();
493487
this.componentStack.push(new SsrComponentFrame(this.currentComponentNode));
494488
}
495489

@@ -541,7 +535,7 @@ class SSRContainer extends _SharedContainer implements ISSRContainer {
541535
return this.serializationCtx.$addRoot$(obj);
542536
}
543537

544-
getLastNode(): ISsrNode {
538+
getOrCreateLastNode(): ISsrNode {
545539
if (!this.lastNode) {
546540
this.lastNode = vNodeData_createSsrNodeReference(
547541
this.currentComponentNode,
@@ -732,6 +726,7 @@ class SSRContainer extends _SharedContainer implements ISSRContainer {
732726
}
733727
value = String(rootId);
734728
}
729+
let skip = false;
735730
switch (key) {
736731
case QScopedStyle:
737732
write(VNodeDataChar.SCOPED_STYLE_CHAR);
@@ -746,7 +741,11 @@ class SSRContainer extends _SharedContainer implements ISSRContainer {
746741
write(VNodeDataChar.PROPS_CHAR);
747742
break;
748743
case ELEMENT_KEY:
749-
write(VNodeDataChar.KEY_CHAR);
744+
if (Object.keys(value).length === 0) {
745+
skip = true;
746+
} else {
747+
write(VNodeDataChar.KEY_CHAR);
748+
}
750749
break;
751750
case ELEMENT_SEQ:
752751
write(VNodeDataChar.SEQ_CHAR);
@@ -772,58 +771,15 @@ class SSRContainer extends _SharedContainer implements ISSRContainer {
772771
write(key);
773772
write(VNodeDataChar.SEPARATOR_CHAR);
774773
}
775-
write(value);
774+
if (!skip) {
775+
write(value);
776+
}
776777
}
777778
}
778779

779780
this.closeElement();
780781
}
781782

782-
/** This adds the vnode's data to the serialization roots */
783-
addVNodeToSerializationRoots(vNodeData: VNodeData) {
784-
const vNodeAttrsStack: SsrAttrs[] = [];
785-
const flag = vNodeData[0];
786-
if (flag !== VNodeDataFlag.NONE) {
787-
if (flag & (VNodeDataFlag.TEXT_DATA | VNodeDataFlag.VIRTUAL_NODE)) {
788-
let fragmentAttrs: SsrAttrs | null = null;
789-
let depth = 0;
790-
for (let i = 1; i < vNodeData.length; i++) {
791-
const value = vNodeData[i];
792-
if (Array.isArray(value)) {
793-
vNodeAttrsStack.push(fragmentAttrs!);
794-
fragmentAttrs = value;
795-
} else if (value === OPEN_FRAGMENT) {
796-
depth++;
797-
} else if (value === CLOSE_FRAGMENT) {
798-
// write out fragment attributes
799-
if (fragmentAttrs) {
800-
for (let i = 1; i < fragmentAttrs.length; i += 2) {
801-
const value = fragmentAttrs[i] as string;
802-
if (typeof value !== 'string') {
803-
fragmentAttrs[i] = String(this.addRoot(value));
804-
}
805-
}
806-
fragmentAttrs = vNodeAttrsStack.pop()!;
807-
}
808-
depth--;
809-
}
810-
}
811-
812-
while (depth-- > 0) {
813-
if (fragmentAttrs) {
814-
for (let i = 0; i < fragmentAttrs.length; i++) {
815-
const value = fragmentAttrs[i] as string;
816-
if (typeof value !== 'string') {
817-
fragmentAttrs[i] = String(this.addRoot(value));
818-
}
819-
}
820-
fragmentAttrs = vNodeAttrsStack.pop()!;
821-
}
822-
}
823-
}
824-
}
825-
}
826-
827783
private emitStateData(): ValueOrPromise<void> {
828784
if (!this.serializationCtx.$roots$.length) {
829785
return;
@@ -990,7 +946,7 @@ class SSRContainer extends _SharedContainer implements ISSRContainer {
990946
? [DEBUG_TYPE, VirtualType.Projection, QSlotParent, ssrComponentNode!.id]
991947
: [QSlotParent, ssrComponentNode!.id]
992948
);
993-
const lastNode = this.getLastNode();
949+
const lastNode = this.getOrCreateLastNode();
994950
if (lastNode.vnodeData) {
995951
lastNode.vnodeData[0] |= VNodeDataFlag.SERIALIZE;
996952
}
@@ -1149,7 +1105,7 @@ class SSRContainer extends _SharedContainer implements ISSRContainer {
11491105
}
11501106

11511107
if (key === 'ref') {
1152-
const lastNode = this.getLastNode();
1108+
const lastNode = this.getOrCreateLastNode();
11531109
if (isSignal(value)) {
11541110
(value as SignalImpl<unknown>).$untrackedValue$ = new DomRef(lastNode);
11551111
continue;
@@ -1164,7 +1120,7 @@ class SSRContainer extends _SharedContainer implements ISSRContainer {
11641120
}
11651121

11661122
if (isSignal(value)) {
1167-
const lastNode = this.getLastNode();
1123+
const lastNode = this.getOrCreateLastNode();
11681124
const signalData = new SubscriptionData({
11691125
$scopedStyleIdPrefix$: styleScopedId,
11701126
$isConst$: isConst,

0 commit comments

Comments
 (0)