Skip to content

Commit d368215

Browse files
authored
Merge pull request #7953 from QwikDev/v2-memory-perf
perf: scheduler and rendering improvements
2 parents c4752e6 + c4ceaad commit d368215

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1734
-1328
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@
178178
}
179179
],
180180
"kind": "Function",
181-
"content": "```typescript\nexport declare function ssrRenderToDom(jsx: JSXOutput, opts?: {\n debug?: boolean;\n raw?: boolean;\n}): Promise<{\n container: _DomContainer;\n document: Document;\n vNode: _VirtualVNode | 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; raw?: boolean; }\n\n\n</td><td>\n\n_(Optional)_\n\n\n</td></tr>\n</tbody></table>\n\n**Returns:**\n\nPromise&lt;{ container: \\_DomContainer; document: Document; vNode: \\_VirtualVNode \\| null; getStyles: () =&gt; Record&lt;string, string \\| string\\[\\]&gt;; }&gt;",
181+
"content": "```typescript\nexport declare function ssrRenderToDom(jsx: JSXOutput, opts?: {\n debug?: boolean;\n raw?: boolean;\n}): Promise<{\n container: _DomContainer;\n document: Document;\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; raw?: boolean; }\n\n\n</td><td>\n\n_(Optional)_\n\n\n</td></tr>\n</tbody></table>\n\n**Returns:**\n\nPromise&lt;{ container: \\_DomContainer; document: Document; vNode: \\_VNode \\| null; getStyles: () =&gt; Record&lt;string, string \\| string\\[\\]&gt;; }&gt;",
182182
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/testing/rendering.unit-util.tsx",
183183
"mdFile": "core.ssrrendertodom.md"
184184
},
@@ -223,7 +223,7 @@
223223
}
224224
],
225225
"kind": "Function",
226-
"content": "```typescript\nexport declare function vnode_fromJSX(jsx: JSXOutput): {\n vParent: _ElementVNode;\n vNode: _VNode | null;\n document: _QDocument;\n container: ClientContainer;\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</tbody></table>\n\n**Returns:**\n\n{ vParent: \\_ElementVNode; vNode: \\_VNode \\| null; document: \\_QDocument; container: ClientContainer; }",
226+
"content": "```typescript\nexport declare function vnode_fromJSX(jsx: JSXOutput): {\n vParent: _ElementVNode | _VirtualVNode;\n vNode: _VNode | null;\n document: _QDocument;\n container: ClientContainer;\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</tbody></table>\n\n**Returns:**\n\n{ vParent: \\_ElementVNode \\| \\_VirtualVNode; vNode: \\_VNode \\| null; document: \\_QDocument; container: ClientContainer; }",
227227
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/testing/vdom-diff.unit-util.ts",
228228
"mdFile": "core.vnode_fromjsx.md"
229229
},

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -439,7 +439,7 @@ export declare function ssrRenderToDom(
439439
): Promise<{
440440
container: _DomContainer;
441441
document: Document;
442-
vNode: _VirtualVNode | null;
442+
vNode: _VNode | null;
443443
getStyles: () => Record<string, string | string[]>;
444444
}>;
445445
```
@@ -485,7 +485,7 @@ _(Optional)_
485485

486486
**Returns:**
487487

488-
Promise&lt;\{ container: \_DomContainer; document: Document; vNode: \_VirtualVNode \| null; getStyles: () =&gt; Record&lt;string, string \| string[]&gt;; }&gt;
488+
Promise&lt;\{ container: \_DomContainer; document: Document; vNode: \_VNode \| null; getStyles: () =&gt; Record&lt;string, string \| string[]&gt;; }&gt;
489489

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

@@ -597,7 +597,7 @@ Promise&lt;void&gt;
597597

598598
```typescript
599599
export declare function vnode_fromJSX(jsx: JSXOutput): {
600-
vParent: _ElementVNode;
600+
vParent: _ElementVNode | _VirtualVNode;
601601
vNode: _VNode | null;
602602
document: _QDocument;
603603
container: ClientContainer;
@@ -632,7 +632,7 @@ JSXOutput
632632

633633
**Returns:**
634634

635-
\{ vParent: \_ElementVNode; vNode: \_VNode \| null; document: \_QDocument; container: ClientContainer; }
635+
\{ vParent: \_ElementVNode \| \_VirtualVNode; vNode: \_VNode \| null; document: \_QDocument; container: ClientContainer; }
636636

637637
[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/testing/vdom-diff.unit-util.ts)
638638

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import { AsyncComputedSignalImpl } from '../reactive-primitives/impl/async-computed-signal-impl';
2+
import { StoreHandler } from '../reactive-primitives/impl/store';
3+
import { assertFalse } from '../shared/error/assert';
4+
import { isQrl } from '../shared/qrl/qrl-utils';
5+
import type { Chore } from '../shared/scheduler';
6+
import {
7+
ssrNodeDocumentPosition,
8+
vnode_documentPosition,
9+
} from '../shared/scheduler-document-position';
10+
import { ChoreType } from '../shared/util-chore-type';
11+
import type { ISsrNode } from '../ssr/ssr-types';
12+
import { vnode_isVNode } from './vnode';
13+
14+
export class ChoreArray extends Array<Chore> {
15+
add(value: Chore): number {
16+
/// We need to ensure that the `queue` is sorted by priority.
17+
/// 1. Find a place where to insert into.
18+
const idx = sortedFindIndex(this, value);
19+
20+
if (idx < 0) {
21+
/// 2. Insert the chore into the queue.
22+
this.splice(~idx, 0, value);
23+
return idx;
24+
}
25+
26+
const existing = this[idx];
27+
/**
28+
* When a derived signal is updated we need to run vnode_diff. However the signal can update
29+
* multiple times during component execution. For this reason it is necessary for us to update
30+
* the chore with the latest result of the signal.
31+
*/
32+
if (existing.$payload$ !== value.$payload$) {
33+
existing.$payload$ = value.$payload$;
34+
}
35+
return idx;
36+
}
37+
38+
delete(value: Chore) {
39+
// const idx = this.sortedFindIndex(this, value);
40+
// if (idx >= 0) {
41+
// this.splice(idx, 1);
42+
// }
43+
// return idx;
44+
const idx = this.indexOf(value);
45+
if (idx >= 0) {
46+
this.splice(idx, 1);
47+
}
48+
return idx;
49+
}
50+
}
51+
52+
export function sortedFindIndex(sortedArray: Chore[], value: Chore): number {
53+
/// We need to ensure that the `queue` is sorted by priority.
54+
/// 1. Find a place where to insert into.
55+
let bottom = 0;
56+
let top = sortedArray.length;
57+
while (bottom < top) {
58+
const middle = bottom + ((top - bottom) >> 1);
59+
const midChore = sortedArray[middle];
60+
const comp = choreComparator(value, midChore);
61+
if (comp < 0) {
62+
top = middle;
63+
} else if (comp > 0) {
64+
bottom = middle + 1;
65+
} else {
66+
// We already have the host in the queue.
67+
return middle;
68+
}
69+
}
70+
return ~bottom;
71+
}
72+
73+
/**
74+
* Compares two chores to determine their execution order in the scheduler's queue.
75+
*
76+
* @param a - The first chore to compare
77+
* @param b - The second chore to compare
78+
* @returns A number indicating the relative order of the chores. A negative number means `a` runs
79+
* before `b`.
80+
*/
81+
export function choreComparator(a: Chore, b: Chore): number {
82+
const macroTypeDiff = (a.$type$ & ChoreType.MACRO) - (b.$type$ & ChoreType.MACRO);
83+
if (macroTypeDiff !== 0) {
84+
return macroTypeDiff;
85+
}
86+
87+
const aHost = a.$host$;
88+
const bHost = b.$host$;
89+
90+
if (aHost !== bHost && aHost !== null && bHost !== null) {
91+
if (vnode_isVNode(aHost) && vnode_isVNode(bHost)) {
92+
// we are running on the client.
93+
const hostDiff = vnode_documentPosition(aHost, bHost);
94+
if (hostDiff !== 0) {
95+
return hostDiff;
96+
}
97+
} else {
98+
assertFalse(vnode_isVNode(aHost), 'expected aHost to be SSRNode but it is a VNode');
99+
assertFalse(vnode_isVNode(bHost), 'expected bHost to be SSRNode but it is a VNode');
100+
const hostDiff = ssrNodeDocumentPosition(aHost as ISsrNode, bHost as ISsrNode);
101+
if (hostDiff !== 0) {
102+
return hostDiff;
103+
}
104+
}
105+
}
106+
107+
const microTypeDiff = (a.$type$ & ChoreType.MICRO) - (b.$type$ & ChoreType.MICRO);
108+
if (microTypeDiff !== 0) {
109+
return microTypeDiff;
110+
}
111+
// types are the same
112+
113+
const idxDiff = toNumber(a.$idx$) - toNumber(b.$idx$);
114+
if (idxDiff !== 0) {
115+
return idxDiff;
116+
}
117+
118+
// If the host is the same (or missing), and the type is the same, we need to compare the target.
119+
if (a.$target$ !== b.$target$) {
120+
if (isQrl(a.$target$) && isQrl(b.$target$) && a.$target$.$hash$ === b.$target$.$hash$) {
121+
return 0;
122+
}
123+
// 1 means that we are going to process chores as FIFO
124+
return 1;
125+
}
126+
127+
// ensure that the effect chores are scheduled for the same target
128+
// TODO: can we do this better?
129+
if (
130+
a.$type$ === ChoreType.RECOMPUTE_AND_SCHEDULE_EFFECTS &&
131+
b.$type$ === ChoreType.RECOMPUTE_AND_SCHEDULE_EFFECTS &&
132+
((a.$target$ instanceof StoreHandler && b.$target$ instanceof StoreHandler) ||
133+
(a.$target$ instanceof AsyncComputedSignalImpl &&
134+
b.$target$ instanceof AsyncComputedSignalImpl)) &&
135+
a.$payload$ !== b.$payload$
136+
) {
137+
return 1;
138+
}
139+
140+
// The chores are the same and will run only once
141+
return 0;
142+
}
143+
144+
function toNumber(value: number | string): number {
145+
return typeof value === 'number' ? value : -1;
146+
}

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

Lines changed: 19 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import {
2525
QLocaleAttr,
2626
QManifestHashAttr,
2727
QScopedStyle,
28-
QSlotParent,
2928
QStyle,
3029
QStyleSelector,
3130
Q_PROPS_SEPARATOR,
@@ -42,32 +41,26 @@ import type { ContextId } from '../use/use-context';
4241
import { processVNodeData } from './process-vnode-data';
4342
import {
4443
VNodeFlags,
45-
VNodeProps,
4644
type ContainerElement,
47-
type ElementVNode,
4845
type ClientContainer as IClientContainer,
4946
type QDocument,
50-
type VNode,
51-
type VirtualVNode,
5247
} from './types';
5348
import { mapArray_get, mapArray_has, mapArray_set } from './util-mapArray';
5449
import {
5550
VNodeJournalOpCode,
5651
vnode_applyJournal,
5752
vnode_createErrorDiv,
5853
vnode_getDomParent,
59-
vnode_getNextSibling,
60-
vnode_getParent,
61-
vnode_getProp,
6254
vnode_getProps,
6355
vnode_insertBefore,
6456
vnode_isElementVNode,
57+
vnode_isVNode,
6558
vnode_isVirtualVNode,
6659
vnode_locate,
6760
vnode_newUnMaterializedElement,
68-
vnode_setProp,
6961
type VNodeJournal,
7062
} from './vnode';
63+
import type { ElementVNode, VirtualVNode, VNode } from './vnode-impl';
7164

7265
/** @public */
7366
export function getDomContainer(element: Element | VNode): IClientContainer {
@@ -89,7 +82,7 @@ export function getDomContainerFromQContainerElement(qContainerElement: Element)
8982

9083
/** @internal */
9184
export function _getQContainerElement(element: Element | VNode): Element | null {
92-
const qContainerElement: Element | null = Array.isArray(element)
85+
const qContainerElement: Element | null = vnode_isVNode(element)
9386
? (vnode_getDomParent(element, true) as Element)
9487
: element;
9588
return qContainerElement.closest(QContainerSelector);
@@ -175,13 +168,13 @@ export class DomContainer extends _SharedContainer implements IClientContainer {
175168
return inflateQRL(this, parseQRL(qrl)) as QRL<T>;
176169
}
177170

178-
handleError(err: any, host: HostElement | null): void {
171+
handleError(err: any, host: VNode | null): void {
179172
if (qDev && host) {
180173
if (typeof document !== 'undefined') {
181174
const vHost = host as VirtualVNode;
182175
const journal: VNodeJournal = [];
183-
const vHostParent = vnode_getParent(vHost) as VirtualVNode | ElementVNode | undefined;
184-
const vHostNextSibling = vnode_getNextSibling(vHost);
176+
const vHostParent = vHost.parent;
177+
const vHostNextSibling = vHost.nextSibling as VNode | null;
185178
const vErrorDiv = vnode_createErrorDiv(document, vHost, err, journal);
186179
// If the host is an element node, we need to insert the error div into its parent.
187180
const insertHost = vnode_isElementVNode(vHost) ? vHostParent || vHost : vHost;
@@ -207,15 +200,15 @@ export class DomContainer extends _SharedContainer implements IClientContainer {
207200
errorStore.error = err;
208201
}
209202

210-
setContext<T>(host: HostElement, context: ContextId<T>, value: T): void {
203+
setContext<T>(host: VNode, context: ContextId<T>, value: T): void {
211204
let ctx = this.getHostProp<Array<string | unknown>>(host, QCtxAttr);
212205
if (ctx == null) {
213206
this.setHostProp(host, QCtxAttr, (ctx = []));
214207
}
215208
mapArray_set(ctx, context.id, value, 0, true);
216209
}
217210

218-
resolveContext<T>(host: HostElement, contextId: ContextId<T>): T | undefined {
211+
resolveContext<T>(host: VNode, contextId: ContextId<T>): T | undefined {
219212
while (host) {
220213
const ctx = this.getHostProp<Array<string | unknown>>(host, QCtxAttr);
221214
if (ctx != null && mapArray_has(ctx, contextId.id, 0)) {
@@ -226,27 +219,27 @@ export class DomContainer extends _SharedContainer implements IClientContainer {
226219
return undefined;
227220
}
228221

229-
getParentHost(host: HostElement): HostElement | null {
230-
let vNode = vnode_getParent(host as any);
222+
getParentHost(host: VNode): VNode | null {
223+
let vNode: VNode | null = host.parent;
231224
while (vNode) {
232225
if (vnode_isVirtualVNode(vNode)) {
233-
if (vnode_getProp(vNode, OnRenderProp, null) !== null) {
234-
return vNode as any as HostElement;
226+
if (vNode.getProp(OnRenderProp, null) !== null) {
227+
return vNode;
235228
}
236229
vNode =
237-
vnode_getParent(vNode) ||
230+
vNode.parent ||
238231
// If virtual node, than it could be a slot so we need to read its parent.
239-
vnode_getProp<VNode>(vNode, QSlotParent, this.vNodeLocate);
232+
vNode.slotParent;
240233
} else {
241-
vNode = vnode_getParent(vNode);
234+
vNode = vNode.parent;
242235
}
243236
}
244237
return null;
245238
}
246239

247240
setHostProp<T>(host: HostElement, name: string, value: T): void {
248241
const vNode: VirtualVNode = host as any;
249-
vnode_setProp(vNode, name, value);
242+
vNode.setProp(name, value);
250243
}
251244

252245
getHostProp<T>(host: HostElement, name: string): T | null {
@@ -265,12 +258,12 @@ export class DomContainer extends _SharedContainer implements IClientContainer {
265258
getObjectById = parseInt;
266259
break;
267260
}
268-
return vnode_getProp(vNode, name, getObjectById);
261+
return vNode.getProp(name, getObjectById);
269262
}
270263

271264
ensureProjectionResolved(vNode: VirtualVNode): void {
272-
if ((vNode[VNodeProps.flags] & VNodeFlags.Resolved) === 0) {
273-
vNode[VNodeProps.flags] |= VNodeFlags.Resolved;
265+
if ((vNode.flags & VNodeFlags.Resolved) === 0) {
266+
vNode.flags |= VNodeFlags.Resolved;
274267
const props = vnode_getProps(vNode);
275268
for (let i = 0; i < props.length; i = i + 2) {
276269
const prop = props[i] as string;
@@ -279,7 +272,6 @@ export class DomContainer extends _SharedContainer implements IClientContainer {
279272
if (typeof value == 'string') {
280273
const projection = this.vNodeLocate(value);
281274
props[i + 1] = projection;
282-
vnode_getProp(projection, QSlotParent, (id) => this.vNodeLocate(id));
283275
}
284276
}
285277
}

packages/qwik/src/core/client/process-vnode-data.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// NOTE: we want to move this function to qwikloader, and therefore this function should not have any external dependencies
22
import { VNodeDataChar, VNodeDataSeparator } from '../shared/vnode-data-types';
3-
import type { ContainerElement, ElementVNode, QDocument } from './types';
3+
import type { ContainerElement, QDocument } from './types';
4+
import type { ElementVNode } from './vnode-impl';
45

56
/**
67
* Process the VNodeData script tags and store the VNodeData in the VNodeDataMap.

0 commit comments

Comments
 (0)