Skip to content
Merged
49 changes: 38 additions & 11 deletions packages/qwik/src/core/client/run-qrl.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,36 @@
import type { QRLInternal } from '../shared/qrl/qrl-class';
import type { QRL } from '../shared/qrl/qrl.public';
import type { Container } from '../shared/types';
import { ITERATION_ITEM_MULTI, ITERATION_ITEM_SINGLE } from '../shared/utils/markers';
import { retryOnPromise } from '../shared/utils/promises';
import type { ValueOrPromise } from '../shared/utils/types';
import type { ElementVNode } from '../shared/vnode/element-vnode';
import type { VNode } from '../shared/vnode/vnode';
import { getInvokeContext } from '../use/use-core';
import { useLexicalScope } from '../use/use-lexical-scope.public';
import { getDomContainer } from './dom-container';
import { VNodeFlags } from './types';
import { vnode_ensureElementInflated, vnode_getProp } from './vnode-utils';

export function callQrl(
container: Container | undefined,
host: VNode,
qrl: QRL,
event: unknown,
element: unknown,
useGetObjectById: boolean
): Promise<unknown> {
const getObjectById = useGetObjectById ? container?.$getObjectById$ || null : null;
const singleItem = vnode_getProp<unknown>(host, ITERATION_ITEM_SINGLE, getObjectById);
if (singleItem !== null) {
return qrl(event, element, singleItem);
}
const multiItems = vnode_getProp<unknown[]>(host, ITERATION_ITEM_MULTI, getObjectById);
if (multiItems !== null) {
return qrl(event, element, ...multiItems);
}
return qrl(event, element);
}

/**
* This is called by qwik-loader to run a QRL. It has to be synchronous.
Expand All @@ -14,22 +39,24 @@ import { VNodeFlags } from './types';
*/
export const _run = (...args: unknown[]): ValueOrPromise<unknown> => {
// This will already check container
const [runQrl] = useLexicalScope<[QRLInternal<(...args: unknown[]) => unknown>]>();
const [qrl] = useLexicalScope<[QRLInternal<(...args: unknown[]) => unknown>]>();
const context = getInvokeContext();
const hostElement = context.$hostElement$;
const hostElement = context.$hostElement$ as VNode;
if (hostElement) {
context.$container$ ||= getDomContainer((hostElement as ElementVNode).node as Element);
vnode_ensureElementInflated(hostElement);
return retryOnPromise(() => {
if (!(hostElement.flags & VNodeFlags.Deleted)) {
return runQrl(...args).catch((err) => {
const container = (context.$container$ ||= getDomContainer(
(hostElement as ElementVNode).node
));
if (container) {
container.handleError(err, hostElement);
} else {
throw err;
return callQrl(context.$container$, hostElement, qrl, args[0], args[1], true).catch(
(err) => {
const container = context.$container$;
if (container) {
container.handleError(err, hostElement);
} else {
throw err;
}
}
});
);
}
});
}
Expand Down
21 changes: 15 additions & 6 deletions packages/qwik/src/core/client/vnode-diff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ import { getFileLocationFromJsx } from '../shared/utils/jsx-filename';
import {
ELEMENT_PROPS,
ELEMENT_SEQ,
ITERATION_ITEM_MULTI,
ITERATION_ITEM_SINGLE,
OnRenderProp,
QBackRefs,
QContainerAttr,
Expand Down Expand Up @@ -101,6 +103,7 @@ import { ChoreBits } from '../shared/vnode/enums/chore-bits.enum';
import { _EFFECT_BACK_REF } from '../reactive-primitives/backref';
import type { Cursor } from '../shared/cursor/cursor';
import { createSetAttributeOperation } from '../shared/vnode/types/dom-vnode-operation';
import { callQrl } from './run-qrl';

export interface DiffContext {
container: ClientContainer;
Expand Down Expand Up @@ -248,10 +251,11 @@ function diff(diffContext: DiffContext, jsxNode: JSXChildren, vStartNode: VNode)

while (diffContext.stack.length) {
while (diffContext.jsxIdx < diffContext.jsxCount) {
assertFalse(
diffContext.vParent === diffContext.vCurrent,
"Parent and current can't be the same"
);
isDev &&
assertFalse(
diffContext.vParent === diffContext.vCurrent,
"Parent and current can't be the same"
);
if (typeof diffContext.jsxValue === 'string') {
expectText(diffContext, diffContext.jsxValue);
} else if (typeof diffContext.jsxValue === 'number') {
Expand Down Expand Up @@ -964,7 +968,7 @@ function expectElement(diffContext: DiffContext, jsx: JSXNodeInternal, elementNa

for (const qrl of qrls.flat(2)) {
if (qrl) {
qrl(event, element).catch((e) => {
callQrl(diffContext.container, vNode, qrl, event, vNode.node, false).catch((e) => {
diffContext.container.handleError(e, vNode);
});
}
Expand Down Expand Up @@ -1040,7 +1044,12 @@ const patchProperty = (
value: any,
currentFile: string | null
) => {
if (key.startsWith(':')) {
if (
// set only property for iteration item, not an attribute
key === ITERATION_ITEM_SINGLE ||
key === ITERATION_ITEM_MULTI ||
key.startsWith(':')
) {
// TODO: there is a potential deoptimization here, because we are setting different keys on props.
// Eager bailout - Insufficient type feedback for generic keyed access
vnode_setProp(vnode, key, value);
Expand Down
3 changes: 3 additions & 0 deletions packages/qwik/src/core/shared/utils/markers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ export const ELEMENT_SEQ_IDX = 'q:seqIdx';
export const ELEMENT_BACKPATCH_DATA = 'qwik/backpatch';
export const Q_PREFIX = 'q:';

export const ITERATION_ITEM_SINGLE = 'q:p'; // Single iteration parameter (not an array)
export const ITERATION_ITEM_MULTI = 'q:ps'; // Multiple iteration parameters (array)

/** Non serializable markers - always begins with `:` character */
export const NON_SERIALIZABLE_MARKER_PREFIX = ':';
export const USE_ON_LOCAL = NON_SERIALIZABLE_MARKER_PREFIX + 'on';
Expand Down
4 changes: 4 additions & 0 deletions packages/qwik/src/core/ssr/ssr-render-jsx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import { getFileLocationFromJsx } from '../shared/utils/jsx-filename';
import {
ELEMENT_KEY,
FLUSH_COMMENT,
ITERATION_ITEM_MULTI,
ITERATION_ITEM_SINGLE,
QDefaultSlot,
QScopedStyle,
QSlot,
Expand Down Expand Up @@ -360,6 +362,8 @@ export function toSsrAttrs(

if (isPreventDefault(key)) {
addPreventDefaultEventToSerializationContext(options.serializationCtx, key);
} else if (key === ITERATION_ITEM_SINGLE || key === ITERATION_ITEM_MULTI) {
value = options.serializationCtx.$addRoot$(value);
}

value = serializeAttribute(key, value, options.styleScopedId);
Expand Down
1 change: 1 addition & 0 deletions packages/qwik/src/core/use/use-lexical-scope.public.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export const useLexicalScope = <VARS extends any[]>(): VARS => {
const containerElement = _getQContainerElement(el) as HTMLElement;
assertDefined(containerElement, `invoke: cant find parent q:container of`, el);
const container = getDomContainer(containerElement);
context.$container$ ||= container;
qrl = container.parseQRL(decodeURIComponent(String(context.$url$))) as QRLInternal<unknown>;
} else {
assertQrl(qrl);
Expand Down
Loading
Loading