Skip to content

Commit 90a9d68

Browse files
authored
Merge pull request #7666 from QwikDev/v2-empty-props-ssr
fix: correctly serialize vnode props in production mode
2 parents 495e8d9 + 027a024 commit 90a9d68

File tree

5 files changed

+52
-7
lines changed

5 files changed

+52
-7
lines changed

.changeset/quick-moons-show.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: correctly serialize vnode props in production mode

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, '', [], [], [] as any);
515+
const obj = new SsrNode(null, '', -1, [], [] as any);
516516
const container = await withContainer((ssr) => ssr.addRoot(obj));
517517
expect(container.$getObjectById$(0)).toEqual(container.element.ownerDocument);
518518
});
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { _EMPTY_ARRAY } from '@qwik.dev/core';
2+
import { describe, expect, it } from 'vitest';
3+
import { SsrNode } from './ssr-node';
4+
import { OPEN_FRAGMENT, type VNodeData } from './vnode-data';
5+
import { VNodeDataFlag } from './types';
6+
7+
describe('ssr-node', () => {
8+
it('should create empty array as attrs if attributesIndex is -1', () => {
9+
const vNodeData: VNodeData = [VNodeDataFlag.VIRTUAL_NODE];
10+
vNodeData.push(OPEN_FRAGMENT);
11+
const ssrNode = new SsrNode(null, '1', -1, [], vNodeData);
12+
ssrNode.setProp('a', 1);
13+
expect(vNodeData[(ssrNode as any).attributesIndex]).toEqual(['a', 1]);
14+
});
15+
16+
it('should create new empty array as attrs if attrs are equal to EMPTY_ARRAY', () => {
17+
const vNodeData: VNodeData = [VNodeDataFlag.VIRTUAL_NODE];
18+
const attrs = _EMPTY_ARRAY;
19+
vNodeData.push(attrs, OPEN_FRAGMENT);
20+
const ssrNode = new SsrNode(null, '1', 1, [], vNodeData);
21+
ssrNode.setProp('a', 1);
22+
expect(vNodeData[(ssrNode as any).attributesIndex]).toEqual(['a', 1]);
23+
});
24+
});

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

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export class SsrNode implements ISsrNode {
3939

4040
public parentSsrNode: ISsrNode | null;
4141
public children: ISsrNode[] | null = null;
42+
private attrs: SsrAttrs;
4243

4344
/** Local props which don't serialize; */
4445
private localProps: SsrAttrs | null = null;
@@ -50,21 +51,23 @@ export class SsrNode implements ISsrNode {
5051
constructor(
5152
parentSsrNode: ISsrNode | null,
5253
id: string,
53-
private attrs: SsrAttrs,
54+
private attributesIndex: number,
5455
private cleanupQueue: CleanupQueue,
5556
public vnodeData: VNodeData
5657
) {
5758
this.parentSsrNode = parentSsrNode;
5859
this.parentSsrNode?.addChild(this);
5960
this.id = id;
61+
this.attrs =
62+
this.attributesIndex >= 0 ? (this.vnodeData[this.attributesIndex] as SsrAttrs) : _EMPTY_ARRAY;
6063
if (isDev && id.indexOf('undefined') != -1) {
6164
throw new Error(`Invalid SSR node id: ${id}`);
6265
}
6366
}
6467

6568
setProp(name: string, value: any): void {
6669
if (this.attrs === _EMPTY_ARRAY) {
67-
this.attrs = [];
70+
this.setEmptyArrayAsVNodeDataAttributes();
6871
}
6972
if (name.startsWith(NON_SERIALIZABLE_MARKER_PREFIX)) {
7073
mapArray_set(this.localProps || (this.localProps = []), name, value, 0);
@@ -78,6 +81,20 @@ export class SsrNode implements ISsrNode {
7881
}
7982
}
8083

84+
private setEmptyArrayAsVNodeDataAttributes() {
85+
if (this.attributesIndex >= 0) {
86+
this.vnodeData[this.attributesIndex] = [];
87+
this.attrs = this.vnodeData[this.attributesIndex] as SsrAttrs;
88+
} else {
89+
// we need to insert a new empty array at index 1
90+
// this can be inefficient, but it is only done once per node and probably not often
91+
const newAttributesIndex = this.vnodeData.length > 1 ? 1 : 0;
92+
this.vnodeData.splice(newAttributesIndex, 0, []);
93+
this.attributesIndex = newAttributesIndex;
94+
this.attrs = this.vnodeData[this.attributesIndex] as SsrAttrs;
95+
}
96+
}
97+
8198
getProp(name: string): any {
8299
if (name.startsWith(NON_SERIALIZABLE_MARKER_PREFIX)) {
83100
return this.localProps ? mapArray_get(this.localProps, name, 0) : null;

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,13 @@ export function vNodeData_createSsrNodeReference(
8787
cleanupQueue: CleanupQueue
8888
): ISsrNode {
8989
vNodeData[0] |= VNodeDataFlag.REFERENCE;
90-
let fragmentAttrs: SsrAttrs = _EMPTY_ARRAY;
9190
const stack: number[] = [-1];
9291
// We are referring to a virtual node. We need to descend into the tree to find the path to the node.
92+
let attributesIndex = -1;
9393
for (let i = 1; i < vNodeData.length; i++) {
9494
const value = vNodeData[i];
9595
if (Array.isArray(value)) {
96-
fragmentAttrs = value as SsrAttrs;
96+
attributesIndex = i;
9797
i++; // skip the `OPEN_FRAGMENT` or `WRITE_ELEMENT_ATTRS` value
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
@@ -102,7 +102,6 @@ export function vNodeData_createSsrNodeReference(
102102
}
103103
} else if (value === CLOSE_FRAGMENT) {
104104
stack.pop(); // pop count
105-
fragmentAttrs = _EMPTY_ARRAY;
106105
} else if (value < 0) {
107106
// Negative numbers are element counts.
108107
const numberOfElements = 0 - value;
@@ -124,7 +123,7 @@ export function vNodeData_createSsrNodeReference(
124123
}
125124
}
126125
}
127-
return new SsrNode(currentComponentNode, refId, fragmentAttrs, cleanupQueue, vNodeData);
126+
return new SsrNode(currentComponentNode, refId, attributesIndex, cleanupQueue, vNodeData);
128127
}
129128

130129
/**

0 commit comments

Comments
 (0)