Skip to content

Commit cbb05cb

Browse files
authored
Merge pull request #8012 from QwikDev/v2-serdes-dedupe
perf(serdes): improve deduping
2 parents 232a303 + 467db63 commit cbb05cb

29 files changed

+1187
-1055
lines changed

packages/docs/src/routes/api/qwik-testing/api.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@
7474
}
7575
],
7676
"kind": "Function",
77-
"content": "```typescript\nexport declare function domRender(jsx: JSXOutput, opts?: {\n debug?: boolean;\n}): Promise<{\n document: Document;\n container: import(\"@qwik.dev/core/internal\").ClientContainer;\n vNode: _VNode | null;\n getStyles: () => Record<string, string | string[]>;\n}>;\n```\n\n\n<table><thead><tr><th>\n\nParameter\n\n\n</th><th>\n\nType\n\n\n</th><th>\n\nDescription\n\n\n</th></tr></thead>\n<tbody><tr><td>\n\njsx\n\n\n</td><td>\n\nJSXOutput\n\n\n</td><td>\n\n\n</td></tr>\n<tr><td>\n\nopts\n\n\n</td><td>\n\n{ debug?: boolean; }\n\n\n</td><td>\n\n_(Optional)_\n\n\n</td></tr>\n</tbody></table>\n\n**Returns:**\n\nPromise&lt;{ document: Document; container: import(\"@qwik.dev/core/internal\").ClientContainer; vNode: \\_VNode \\| null; getStyles: () =&gt; Record&lt;string, string \\| string\\[\\]&gt;; }&gt;",
77+
"content": "```typescript\nexport declare function domRender(jsx: JSXOutput, opts?: {\n debug?: boolean;\n}): Promise<{\n document: Document;\n container: import(\"@qwik.dev/core\").ClientContainer;\n vNode: _VNode | null;\n getStyles: () => Record<string, string | string[]>;\n}>;\n```\n\n\n<table><thead><tr><th>\n\nParameter\n\n\n</th><th>\n\nType\n\n\n</th><th>\n\nDescription\n\n\n</th></tr></thead>\n<tbody><tr><td>\n\njsx\n\n\n</td><td>\n\nJSXOutput\n\n\n</td><td>\n\n\n</td></tr>\n<tr><td>\n\nopts\n\n\n</td><td>\n\n{ debug?: boolean; }\n\n\n</td><td>\n\n_(Optional)_\n\n\n</td></tr>\n</tbody></table>\n\n**Returns:**\n\nPromise&lt;{ document: Document; container: import(\"@qwik.dev/core\").ClientContainer; vNode: \\_VNode \\| null; getStyles: () =&gt; Record&lt;string, string \\| string\\[\\]&gt;; }&gt;",
7878
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/testing/rendering.unit-util.tsx",
7979
"mdFile": "core.domrender.md"
8080
},

packages/docs/src/routes/api/qwik-testing/index.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ export declare function domRender(
121121
},
122122
): Promise<{
123123
document: Document;
124-
container: import("@qwik.dev/core/internal").ClientContainer;
124+
container: import("@qwik.dev/core").ClientContainer;
125125
vNode: _VNode | null;
126126
getStyles: () => Record<string, string | string[]>;
127127
}>;
@@ -168,7 +168,7 @@ _(Optional)_
168168

169169
**Returns:**
170170

171-
Promise&lt;\{ document: Document; container: import("@qwik.dev/core/internal").ClientContainer; vNode: \_VNode \| null; getStyles: () =&gt; Record&lt;string, string \| string[]&gt;; }&gt;
171+
Promise&lt;\{ document: Document; container: import("@qwik.dev/core").ClientContainer; vNode: \_VNode \| null; getStyles: () =&gt; Record&lt;string, string \| string[]&gt;; }&gt;
172172

173173
[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/testing/rendering.unit-util.tsx)
174174

packages/docs/src/routes/playground/parser/html/index.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import { component$, useSignal, useComputed$, useStyles$ } from '@qwik.dev/core';
2-
import { _getDomContainer } from '@qwik.dev/core/internal';
3-
import { _dumpState, _preprocessState, _vnode_toString } from '@qwik.dev/core/internal';
1+
import { component$, useComputed$, useSignal, useStyles$ } from '@qwik.dev/core';
2+
import { _getDomContainer, _vnode_toString } from '@qwik.dev/core/internal';
43
import type { DocumentHead } from '@qwik.dev/router';
54

65
export default component$(() => {

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ import { assertTrue } from '../shared/error/assert';
66
import { QError, qError } from '../shared/error/error';
77
import { ERROR_CONTEXT, isRecoverable } from '../shared/error/error-handling';
88
import type { QRL } from '../shared/qrl/qrl.public';
9-
import { _SharedContainer } from '../shared/shared-container';
10-
import { getObjectById, inflateQRL, parseQRL, preprocessState } from '../shared/serdes/index';
119
import { wrapDeserializerProxy } from '../shared/serdes/deser-proxy';
10+
import { _inflateQRL, getObjectById, parseQRL, preprocessState } from '../shared/serdes/index';
11+
import { _SharedContainer } from '../shared/shared-container';
1212
import { QContainerValue, type HostElement, type ObjToProxyMap } from '../shared/types';
1313
import { EMPTY_ARRAY } from '../shared/utils/flyweight';
1414
import {
@@ -60,7 +60,7 @@ import {
6060
vnode_newUnMaterializedElement,
6161
type VNodeJournal,
6262
} from './vnode';
63-
import type { ElementVNode, VirtualVNode, VNode } from './vnode-impl';
63+
import type { ElementVNode, VNode, VirtualVNode } from './vnode-impl';
6464

6565
/** @public */
6666
export function getDomContainer(element: Element | VNode): IClientContainer {
@@ -165,7 +165,7 @@ export class DomContainer extends _SharedContainer implements IClientContainer {
165165
}
166166

167167
parseQRL<T = unknown>(qrl: string): QRL<T> {
168-
return inflateQRL(this, parseQRL(qrl)) as QRL<T>;
168+
return _inflateQRL(this, parseQRL(qrl)) as QRL<T>;
169169
}
170170

171171
handleError(err: any, host: VNode | null): void {

packages/qwik/src/core/internal.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,8 @@ export { _fnSignal } from './shared/qrl/inlined-fn';
5555
export { _SharedContainer } from './shared/shared-container';
5656
export {
5757
_deserialize,
58-
dumpState as _dumpState,
58+
_dumpState,
5959
preprocessState as _preprocessState,
60-
_serializationWeakRef,
6160
_serialize,
6261
} from './shared/serdes/index';
6362
export { _CONST_PROPS, _IMMUTABLE, _VAR_PROPS, _UNINITIALIZED } from './shared/utils/constants';

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

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import { isBrowser } from '@qwik.dev/core/build';
1010
import { isDev } from '@qwik.dev/core/build';
1111
import { isServer } from '@qwik.dev/core/build';
1212
import { QRL as QRL_2 } from './qrl.public';
13-
import type { StreamWriter as StreamWriter_2 } from '@qwik.dev/core';
1413

1514
// @public
1615
export const $: <T>(expression: T) => QRL<T>;
@@ -900,11 +899,6 @@ export const _run: (...args: unknown[]) => ValueOrPromise<unknown>;
900899
// @public (undocumented)
901900
export type SerializationStrategy = 'never' | 'always';
902901

903-
// Warning: (ae-forgotten-export) The symbol "SerializationWeakRef" needs to be exported by the entry point index.d.ts
904-
//
905-
// @internal (undocumented)
906-
export const _serializationWeakRef: (obj: unknown) => SerializationWeakRef;
907-
908902
// @internal
909903
export function _serialize(data: unknown[]): Promise<string>;
910904

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

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
import { TypeIds, _constants, type Constants, parseQRL, resolvers } from './index';
21
import type { DomContainer } from '../../client/dom-container';
2+
import { ensureMaterialized, vnode_getNode, vnode_isVNode, vnode_locate } from '../../client/vnode';
33
import type { ElementVNode, VNode } from '../../client/vnode-impl';
4-
import { vnode_isVNode, ensureMaterialized, vnode_getNode, vnode_locate } from '../../client/vnode';
54
import { AsyncComputedSignalImpl } from '../../reactive-primitives/impl/async-computed-signal-impl';
65
import { ComputedSignalImpl } from '../../reactive-primitives/impl/computed-signal-impl';
76
import { SerializerSignalImpl } from '../../reactive-primitives/impl/serializer-signal-impl';
@@ -14,12 +13,15 @@ import { createResourceReturn } from '../../use/use-resource';
1413
import { Task } from '../../use/use-task';
1514
import { componentQrl } from '../component.public';
1615
import { qError, QError } from '../error/error';
17-
import { JSXNodeImpl, createPropsProxy } from '../jsx/jsx-runtime';
16+
import { createPropsProxy, JSXNodeImpl } from '../jsx/jsx-runtime';
1817
import type { DeserializeContainer } from '../types';
1918
import { _UNINITIALIZED } from '../utils/constants';
19+
import { _constants, TypeIds, type Constants } from './constants';
2020
import { needsInflation } from './deser-proxy';
21+
import { createQRLWithBackChannel } from './qrl-to-string';
2122

22-
export const pendingStoreTargents = new Map<object, { t: TypeIds; v: unknown }>();
23+
export const resolvers = new WeakMap<Promise<any>, [Function, Function]>();
24+
export const pendingStoreTargets = new Map<object, { t: TypeIds; v: unknown }>();
2325

2426
export const allocate = (container: DeserializeContainer, typeId: number, value: unknown): any => {
2527
switch (typeId) {
@@ -46,13 +48,17 @@ export const allocate = (container: DeserializeContainer, typeId: number, value:
4648
case TypeIds.Object:
4749
return {};
4850
case TypeIds.QRL:
49-
case TypeIds.PreloadQRL:
50-
const qrl =
51-
typeof value === 'number'
52-
? // root reference
53-
container.$getObjectById$(value)
54-
: value;
55-
return parseQRL(qrl as string);
51+
case TypeIds.PreloadQRL: {
52+
if (typeof value === 'string') {
53+
const data = value.split(' ').map(Number);
54+
const chunk = container.$getObjectById$(data[0]) as string;
55+
const symbol = container.$getObjectById$(data[1]) as string;
56+
const captureIds = data.length > 2 ? data.slice(2) : null;
57+
return createQRLWithBackChannel(chunk, symbol, captureIds);
58+
} else {
59+
return createQRLWithBackChannel('', String(value));
60+
}
61+
}
5662
case TypeIds.Task:
5763
return new Task(-1, -1, null!, null!, null!, null);
5864
case TypeIds.Resource: {
@@ -96,7 +102,7 @@ export const allocate = (container: DeserializeContainer, typeId: number, value:
96102
const storeValue = allocate(container, t, v);
97103
const store = getOrCreateStore(storeValue, StoreFlags.NONE, container as DomContainer);
98104
if (needsInflation(t)) {
99-
pendingStoreTargents.set(store, { t, v });
105+
pendingStoreTargets.set(store, { t, v });
100106
}
101107
// We must store the reference so it doesn't get deserialized again in inflate()
102108
data[0] = TypeIds.Plain;
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { isDomRef } from './serialization-context';
2+
import { isStore, getStoreTarget } from '../../reactive-primitives/impl/store';
3+
import { untrack } from '../../use/use-core';
4+
import { isTask } from '../../use/use-task';
5+
import { isQwikComponent } from '../component.public';
6+
import { isPropsProxy, isJSXNode } from '../jsx/jsx-runtime';
7+
import { isQrl } from '../qrl/qrl-utils';
8+
import { _UNINITIALIZED } from '../utils/constants';
9+
import { isPromise } from '../utils/promises';
10+
11+
export const canSerialize = (value: any, seen: WeakSet<any> = new WeakSet()): boolean => {
12+
if (
13+
value == null ||
14+
typeof value === 'string' ||
15+
typeof value === 'number' ||
16+
typeof value === 'boolean' ||
17+
typeof value === 'bigint'
18+
) {
19+
return true;
20+
} else if (typeof value === 'object') {
21+
if (seen.has(value)) {
22+
return true;
23+
}
24+
seen.add(value);
25+
const proto = Object.getPrototypeOf(value);
26+
if (isStore(value)) {
27+
value = getStoreTarget(value);
28+
}
29+
if (proto == Object.prototype) {
30+
for (const key in value) {
31+
// if the value is a props proxy, then sometimes we could create a component-level subscription,
32+
// so we should call untrack here to avoid tracking the value
33+
if (
34+
!canSerialize(
35+
untrack(() => value[key]),
36+
seen
37+
)
38+
) {
39+
return false;
40+
}
41+
}
42+
return true;
43+
} else if (proto == Array.prototype) {
44+
for (let i = 0; i < value.length; i++) {
45+
if (!canSerialize(value[i], seen)) {
46+
return false;
47+
}
48+
}
49+
return true;
50+
} else if (isTask(value)) {
51+
return true;
52+
} else if (isPropsProxy(value)) {
53+
return true;
54+
} else if (isPromise(value)) {
55+
return true;
56+
} else if (isJSXNode(value)) {
57+
return true;
58+
} else if (value instanceof Error) {
59+
return true;
60+
} else if (value instanceof URL) {
61+
return true;
62+
} else if (value instanceof Date) {
63+
return true;
64+
} else if (value instanceof RegExp) {
65+
return true;
66+
} else if (value instanceof URLSearchParams) {
67+
return true;
68+
} else if (value instanceof FormData) {
69+
return true;
70+
} else if (value instanceof Set) {
71+
return true;
72+
} else if (value instanceof Map) {
73+
return true;
74+
} else if (value instanceof Uint8Array) {
75+
return true;
76+
} else if (isDomRef?.(value)) {
77+
return true;
78+
}
79+
} else if (typeof value === 'function') {
80+
if (isQrl(value) || isQwikComponent(value)) {
81+
return true;
82+
}
83+
} else if (value === _UNINITIALIZED) {
84+
return true;
85+
}
86+
return false;
87+
};
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import { NEEDS_COMPUTATION, STORE_ALL_PROPS } from '../../reactive-primitives/types';
2+
import { Fragment } from '../jsx/jsx-runtime';
3+
import { Slot } from '../jsx/slot.public';
4+
import { _UNINITIALIZED } from '../utils/constants';
5+
import { EMPTY_ARRAY, EMPTY_OBJ } from '../utils/flyweight';
6+
7+
export const enum Constants {
8+
Undefined,
9+
Null,
10+
True,
11+
False,
12+
EmptyString,
13+
EMPTY_ARRAY,
14+
EMPTY_OBJ,
15+
NEEDS_COMPUTATION,
16+
STORE_ALL_PROPS,
17+
UNINITIALIZED,
18+
Slot,
19+
Fragment,
20+
NaN,
21+
PositiveInfinity,
22+
NegativeInfinity,
23+
MaxSafeInt,
24+
// used for close fragment
25+
AlmostMaxSafeInt,
26+
MinSafeInt,
27+
}
28+
29+
// Used for allocate, make sure they are in sync with Constants
30+
export const _constants = [
31+
undefined,
32+
null,
33+
true,
34+
false,
35+
'',
36+
EMPTY_ARRAY,
37+
EMPTY_OBJ,
38+
NEEDS_COMPUTATION,
39+
STORE_ALL_PROPS,
40+
_UNINITIALIZED,
41+
Slot,
42+
Fragment,
43+
NaN,
44+
Infinity,
45+
-Infinity,
46+
Number.MAX_SAFE_INTEGER,
47+
Number.MAX_SAFE_INTEGER - 1,
48+
Number.MIN_SAFE_INTEGER,
49+
] as const;
50+
51+
// Used for dumpState, make sure they are in sync with Constants
52+
export const _constantNames = [
53+
'undefined',
54+
'null',
55+
'true',
56+
'false',
57+
"''",
58+
'EMPTY_ARRAY',
59+
'EMPTY_OBJ',
60+
'NEEDS_COMPUTATION',
61+
'STORE_ALL_PROPS',
62+
'_UNINITIALIZED',
63+
'Slot',
64+
'Fragment',
65+
'NaN',
66+
'Infinity',
67+
'-Infinity',
68+
'MAX_SAFE_INTEGER',
69+
'MAX_SAFE_INTEGER-1',
70+
'MIN_SAFE_INTEGER',
71+
] as const;
72+
73+
export const enum TypeIds {
74+
Plain,
75+
RootRef,
76+
ForwardRef,
77+
/** Undefined, null, true, false, NaN, +Inf, -Inf, Slot, Fragment */
78+
Constant,
79+
Array,
80+
Object,
81+
URL,
82+
Date,
83+
Regex,
84+
VNode,
85+
/// ^ single-digit types ^
86+
RefVNode,
87+
BigInt,
88+
URLSearchParams,
89+
ForwardRefs,
90+
/// All types below will be inflate()d
91+
Error,
92+
Promise,
93+
Set,
94+
Map,
95+
Uint8Array,
96+
QRL,
97+
PreloadQRL,
98+
Task,
99+
Resource,
100+
Component,
101+
Signal,
102+
WrappedSignal,
103+
ComputedSignal,
104+
AsyncComputedSignal,
105+
SerializerSignal,
106+
Store,
107+
FormData,
108+
JSXNode,
109+
PropsProxy,
110+
SubscriptionData,
111+
}
112+
113+
// Used for dumpState, make sure they are in sync with TypeIds
114+
export const _typeIdNames = [
115+
'Plain',
116+
'RootRef',
117+
'ForwardRef',
118+
'Constant',
119+
'Array',
120+
'Object',
121+
'URL',
122+
'Date',
123+
'Regex',
124+
'VNode',
125+
'RefVNode',
126+
'BigInt',
127+
'URLSearchParams',
128+
'ForwardRefs',
129+
'Error',
130+
'Promise',
131+
'Set',
132+
'Map',
133+
'Uint8Array',
134+
'QRL',
135+
'PreloadQRL',
136+
'Task',
137+
'Resource',
138+
'Component',
139+
'Signal',
140+
'WrappedSignal',
141+
'ComputedSignal',
142+
'AsyncComputedSignal',
143+
'SerializerSignal',
144+
'Store',
145+
'FormData',
146+
'JSXNode',
147+
'PropsProxy',
148+
'SubscriptionData',
149+
];

0 commit comments

Comments
 (0)