Skip to content

Commit 0f5d600

Browse files
committed
feat(serdes): dedupe qrls
1 parent fefa0a5 commit 0f5d600

File tree

2 files changed

+40
-19
lines changed

2 files changed

+40
-19
lines changed

packages/qwik/src/core/shared/serdes/serdes.unit.ts

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ describe('shared-serialization', () => {
345345
).toMatchInlineSnapshot(`
346346
"
347347
0 Task [
348-
QRL 2
348+
QRL "mock-chunk#task_qrl[1]"
349349
{number} 0
350350
{number} 0
351351
RootRef 1
@@ -356,12 +356,11 @@ describe('shared-serialization', () => {
356356
]
357357
]
358358
1 Object [
359-
RootRef 3
359+
RootRef 2
360360
{number} 1
361361
]
362-
2 {string} "mock-chunk#task_qrl[1]"
363-
3 RootRef "0 5 0"
364-
(95 chars)"
362+
2 RootRef "0 5 0"
363+
(91 chars)"
365364
`);
366365
});
367366
it(title(TypeIds.Resource), async () => {
@@ -390,10 +389,9 @@ describe('shared-serialization', () => {
390389
`
391390
"
392391
0 Component [
393-
QRL 1
392+
QRL "mock-chunk#dump_component"
394393
]
395-
1 {string} "mock-chunk#dump_component"
396-
(41 chars)"
394+
(37 chars)"
397395
`
398396
);
399397
});
@@ -944,6 +942,23 @@ describe('shared-serialization', () => {
944942
(103 chars)"
945943
`);
946944
});
945+
it('should dedupe identical qrls', async () => {
946+
const fn = () => 'hi';
947+
const a = {};
948+
const qrl1 = inlinedQrl(fn, 'dump_qrl', [a]);
949+
const qrl2 = inlinedQrl(fn, 'dump_qrl', [a]);
950+
expect(qrl1).not.toBe(qrl2);
951+
const objs = await serialize(qrl1, [qrl2]);
952+
expect(dumpState(objs)).toMatchInlineSnapshot(`
953+
"
954+
0 QRL "mock-chunk#dump_qrl[2]"
955+
1 Array [
956+
RootRef 0
957+
]
958+
2 Object []
959+
(42 chars)"
960+
`);
961+
});
947962
});
948963

949964
describe('Serialization Weak Ref', () => {

packages/qwik/src/core/shared/serdes/serialize.ts

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ export async function serialize(serializationContext: SerializationContext): Pro
7070
const preloadQrls = new Set<QRLInternal>();
7171
const s11nWeakRefs = new Map<unknown, number>();
7272
let parent: unknown = null;
73-
const isRootObject = () => depth === 0;
73+
const qrlMap = new Map<string, QRLInternal>();
7474

7575
const outputArray = (value: unknown[], writeFn: (value: unknown, idx: number) => void) => {
7676
$writer$.write('[');
@@ -168,13 +168,21 @@ export async function serialize(serializationContext: SerializationContext): Pro
168168
} else if (isQrl(value)) {
169169
if (!outputAsRootRef(value)) {
170170
const qrl = qrlToString(serializationContext, value);
171-
const type = preloadQrls.has(value) ? TypeIds.PreloadQRL : TypeIds.QRL;
172-
if (isRootObject()) {
173-
output(type, qrl);
171+
172+
// Since we map QRLs to strings, we need to keep track of this secondary mapping
173+
const existing = qrlMap.get(qrl);
174+
if (existing) {
175+
// We encountered the same QRL again, make it a root
176+
$addRoot$(existing);
177+
// We need to force because we might be adding the same root again
178+
if (outputAsRootRef(existing, 0, true)) {
179+
return;
180+
}
174181
} else {
175-
const id = serializationContext.$addRoot$(qrl);
176-
output(type, id);
182+
qrlMap.set(qrl, value);
177183
}
184+
const type = preloadQrls.has(value) ? TypeIds.PreloadQRL : TypeIds.QRL;
185+
output(type, qrl);
178186
}
179187
} else if (isQwikComponent(value)) {
180188
const [qrl]: [QRLInternal] = (value as any)[SERIALIZABLE_STATE];
@@ -672,7 +680,8 @@ export function shouldTrackObj(obj: unknown) {
672680
*/
673681
(typeof obj === 'string' && obj.length > 1) ||
674682
/** Same reasoning but for bigint */
675-
(typeof obj === 'bigint' && (obj > 9 || obj < 0))
683+
(typeof obj === 'bigint' && (obj > 9 || obj < 0)) ||
684+
isQrl(obj)
676685
);
677686
} /**
678687
* When serializing the object we need check if it is URL, RegExp, Map, Set, etc. This is time
@@ -697,8 +706,5 @@ export function isResource<T = unknown>(value: object): value is ResourceReturnI
697706
}
698707

699708
export const frameworkType = (obj: any) => {
700-
return (
701-
(isObject(obj) && (obj instanceof SignalImpl || obj instanceof Task || isJSXNode(obj))) ||
702-
isQrl(obj)
703-
);
709+
return obj && (obj instanceof SignalImpl || obj instanceof Task || isJSXNode(obj));
704710
};

0 commit comments

Comments
 (0)