Skip to content

Commit

Permalink
change effect node prop data impl
Browse files Browse the repository at this point in the history
  • Loading branch information
Varixo committed Sep 21, 2024
1 parent 5b15250 commit 02ae97a
Show file tree
Hide file tree
Showing 14 changed files with 98 additions and 57 deletions.
17 changes: 9 additions & 8 deletions packages/qwik/src/core/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -335,13 +335,12 @@ export { DomContainer as _DomContainer }
// @public (undocumented)
export type EagernessOptions = 'visible' | 'load' | 'idle';

// Warning: (ae-forgotten-export) The symbol "Task" needs to be exported by the entry point index.d.ts
// Warning: (ae-forgotten-export) The symbol "ISsrNode" needs to be exported by the entry point index.d.ts
// Warning: (ae-forgotten-export) The symbol "Signal_3" needs to be exported by the entry point index.d.ts
// Warning: (ae-internal-missing-underscore) The name "Effect" should be prefixed with an underscore because the declaration is marked as @internal
//
// @internal
export type Effect = Task | _VNode | ISsrNode | Signal_3;
// @internal (undocumented)
export class _EffectData<T extends Record<string, any> = Record<string, any>> {
constructor(data: T);
// (undocumented)
data: T;
}

// @internal (undocumented)
export type _ElementVNode = [
Expand Down Expand Up @@ -1132,8 +1131,10 @@ export abstract class _SharedContainer implements Container2 {
abstract setContext<T>(host: HostElement, context: ContextId<T>, value: T): void;
// (undocumented)
abstract setHostProp<T>(host: HostElement, name: string, value: T): void;
// Warning: (ae-forgotten-export) The symbol "Effect" needs to be exported by the entry point index.d.ts
//
// (undocumented)
trackSignalValue<T>(signal: Signal_2, subscriber: Effect, property: string, data: any): T;
trackSignalValue<T>(signal: Signal_2, subscriber: Effect, property: string, data: _EffectData): T;
}

// @public
Expand Down
2 changes: 1 addition & 1 deletion packages/qwik/src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,6 @@ export { useComputed$, useTask$, useVisibleTask$ } from './use/use-task-dollar';
export { useErrorBoundary } from './use/use-error-boundary';
export type { ErrorBoundaryStore } from './render/error-handling';
export {
type Effect,
type ReadonlySignal,
type Signal,
type ComputedSignal,
Expand All @@ -138,6 +137,7 @@ export {
createComputedQrl,
createComputed$,
} from './v2/signal/v2-signal.public';
export { EffectData as _EffectData } from './v2/signal/v2-signal';

//////////////////////////////////////////////////////////////////////////////////////////
// Developer Low-Level API
Expand Down
13 changes: 10 additions & 3 deletions packages/qwik/src/core/use/use-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ import type { Container2 } from '../v2/shared/types';
import { vnode_getNode, vnode_isElementVNode, vnode_isVNode } from '../v2/client/vnode';
import { _getQContainerElement } from '../v2/client/dom-container';
import type { ContainerElement } from '../v2/client/types';
import type { EffectSubscriptions, EffectSubscriptionsProp } from '../v2/signal/v2-signal';
import type {
EffectData,
EffectSubscriptions,
EffectSubscriptionsProp,
} from '../v2/signal/v2-signal';

declare const document: QwikDocument;

Expand Down Expand Up @@ -260,12 +264,15 @@ export const trackSignal = <T>(
subscriber: EffectSubscriptions[EffectSubscriptionsProp.EFFECT],
property: EffectSubscriptions[EffectSubscriptionsProp.PROPERTY],
container: Container2,
data: EffectSubscriptions[EffectSubscriptionsProp.DATA] = null
data?: EffectData
): T => {
const previousSubscriber = trackInvocation.$effectSubscriber$;
const previousContainer = trackInvocation.$container2$;
try {
trackInvocation.$effectSubscriber$ = [subscriber, property, data];
trackInvocation.$effectSubscriber$ = [subscriber, property];
if (data) {
trackInvocation.$effectSubscriber$.push(data);
}
trackInvocation.$container2$ = container;
return invoke(trackInvocation, fn);
} finally {
Expand Down
4 changes: 2 additions & 2 deletions packages/qwik/src/core/use/use-task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ export const runTask2 = (

const track: Tracker = (obj: (() => unknown) | object | Signal<unknown>, prop?: string) => {
const ctx = newInvokeContext();
ctx.$effectSubscriber$ = [task, EffectProperty.COMPONENT, null];
ctx.$effectSubscriber$ = [task, EffectProperty.COMPONENT];
ctx.$container2$ = container;
return invoke(ctx, () => {
if (isFunction(obj)) {
Expand Down Expand Up @@ -556,7 +556,7 @@ export const runResource = <T>(

const track: Tracker = (obj: (() => unknown) | object | Signal<unknown>, prop?: string) => {
const ctx = newInvokeContext();
ctx.$effectSubscriber$ = [task, EffectProperty.COMPONENT, null];
ctx.$effectSubscriber$ = [task, EffectProperty.COMPONENT];
ctx.$container2$ = container;
return invoke(ctx, () => {
if (isFunction(obj)) {
Expand Down
10 changes: 7 additions & 3 deletions packages/qwik/src/core/v2/client/vnode-diff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import {
isHtmlAttributeAnEventName,
isJsxPropertyAnEventName,
} from '../shared/event-names';
import { ChoreType } from '../shared/scheduler';
import { ChoreType, type NodePropData } from '../shared/scheduler';
import { hasClassAttr } from '../shared/scoped-styles';
import type {
HostElement,
Expand Down Expand Up @@ -92,7 +92,7 @@ import {
type VNodeJournal,
} from './vnode';
import { getNewElementNamespaceData } from './vnode-namespace';
import { WrappedSignal, EffectProperty, isSignal } from '../signal/v2-signal';
import { WrappedSignal, EffectProperty, isSignal, EffectData } from '../signal/v2-signal';
import type { Signal } from '../signal/v2-signal.public';
import { executeComponent2 } from '../shared/component-execution';
import { isParentSlotProp, isSlotProp } from '../../util/prop';
Expand Down Expand Up @@ -629,12 +629,16 @@ export const vnode_diff = (
}

if (isSignal(value)) {
const signalData = new EffectData<NodePropData>({
$scopedStyleIdPrefix$: scopedStyleIdPrefix,
$isConst$: true,
});
value = trackSignal(
() => (value as Signal<unknown>).value,
vNewNode as ElementVNode,
key,
container,
scopedStyleIdPrefix
signalData
);
}

Expand Down
2 changes: 1 addition & 1 deletion packages/qwik/src/core/v2/shared/component-execution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export const executeComponent2 = (
undefined,
RenderEvent
);
iCtx.$effectSubscriber$ = [subscriptionHost, EffectProperty.COMPONENT, null];
iCtx.$effectSubscriber$ = [subscriptionHost, EffectProperty.COMPONENT];
iCtx.$container2$ = container;
let componentFn: (props: unknown) => ValueOrPromise<JSXOutput>;
container.ensureProjectionResolved(renderHost);
Expand Down
18 changes: 10 additions & 8 deletions packages/qwik/src/core/v2/shared/scheduler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,13 @@ export interface Chore {
$executed$: boolean;
}

export interface NodePropPayload {
value: Signal<unknown>;
scopedStyleIdPrefix: string | null;
export interface NodePropData {
$scopedStyleIdPrefix$: string | null;
$isConst$: boolean;
}

export interface NodePropPayload extends NodePropData {
$value$: Signal<unknown>;
}

export type Scheduler = ReturnType<typeof createScheduler>;
Expand Down Expand Up @@ -372,16 +376,14 @@ export const createScheduler = (
case ChoreType.NODE_PROP:
const virtualNode = chore.$host$ as unknown as ElementVNode;
const payload = chore.$payload$ as NodePropPayload;
let value: Signal<any> | string = payload.value;
// TODO: temp solution!
let isConst = false;
let value: Signal<any> | string = payload.$value$;
if (isSignal(value)) {
value = value.value as any;
isConst = true;
}
const isConst = payload.$isConst$;
const journal = (container as DomContainer).$journal$;
const property = chore.$idx$ as string;
value = serializeAttribute(property, value, payload.scopedStyleIdPrefix);
value = serializeAttribute(property, value, payload.$scopedStyleIdPrefix$);
if (isConst) {
const element = virtualNode[ElementVNodeProps.element] as Element;
journal.push(VNodeJournalOpCode.SetAttribute, element, property, value);
Expand Down
4 changes: 2 additions & 2 deletions packages/qwik/src/core/v2/shared/shared-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { ContextId } from '../../use/use-context';
import { trackSignal } from '../../use/use-core';
import type { ValueOrPromise } from '../../util/types';
import { version } from '../../version';
import type { Effect } from '../signal/v2-signal';
import type { Effect, EffectData } from '../signal/v2-signal';
import type { StreamWriter, SymbolToChunkResolver } from '../ssr/ssr-types';
import type { Scheduler } from './scheduler';
import { createScheduler } from './scheduler';
Expand Down Expand Up @@ -42,7 +42,7 @@ export abstract class _SharedContainer implements Container2 {
this.$scheduler$ = createScheduler(this, scheduleDrain, journalFlush);
}

trackSignalValue<T>(signal: Signal, subscriber: Effect, property: string, data: any): T {
trackSignalValue<T>(signal: Signal, subscriber: Effect, property: string, data: EffectData): T {
return trackSignal(() => signal.value, subscriber, property, this, data);
}

Expand Down
27 changes: 20 additions & 7 deletions packages/qwik/src/core/v2/shared/shared-serialization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
EffectSubscriptionsProp,
Signal,
type EffectSubscriptions,
EffectData,
} from '../signal/v2-signal';
import {
STORE_ARRAY_PROP,
Expand Down Expand Up @@ -1029,9 +1030,14 @@ function serializeEffectSubs(
const effectSubscription = effects[i];
const effect = effectSubscription[EffectSubscriptionsProp.EFFECT];
const prop = effectSubscription[EffectSubscriptionsProp.PROPERTY];
const additionalData = effectSubscription[EffectSubscriptionsProp.DATA];
data += ';' + addRoot(effect) + ' ' + prop + ' ' + addRoot(additionalData);
for (let j = EffectSubscriptionsProp.FIRST_BACK_REF; j < effectSubscription.length; j++) {
data += ';' + addRoot(effect) + ' ' + prop;
let effectSubscriptionDataIndex = EffectSubscriptionsProp.FIRST_BACK_REF_OR_DATA;
const effectSubscriptionData = effectSubscription[effectSubscriptionDataIndex];
if (effectSubscriptionData instanceof EffectData) {
data += ' |' + addRoot(effectSubscriptionData.data);
effectSubscriptionDataIndex++;
}
for (let j = effectSubscriptionDataIndex; j < effectSubscription.length; j++) {
data += ' ' + addRoot(effectSubscription[j]);
}
}
Expand Down Expand Up @@ -1172,10 +1178,17 @@ function deserializeSignal2Effect(
) {
while (idx < parts.length) {
// idx == 1 is the attribute name
const effect = parts[idx++]
.split(' ')
.map((obj, idx) => (idx == 1 ? obj : container.$getObjectById$(obj)));
effects.push(effect as fixMeAny);
const effect = parts[idx++].split(' ').map((obj, idx) => {
if (idx === EffectSubscriptionsProp.PROPERTY) {
return obj;
} else {
if (obj[0] === '|') {
return new EffectData<any>(container.$getObjectById$(parseInt(obj.substring(1))));
}
return container.$getObjectById$(obj);
}
}) as EffectSubscriptions;
effects.push(effect);
}
return idx;
}
Expand Down
2 changes: 0 additions & 2 deletions packages/qwik/src/core/v2/signal/v2-signal.public.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import {

export { isSignal } from './v2-signal';

export type { Effect } from './v2-signal';

/** @public */
export interface ReadonlySignal<T = unknown> {
readonly value: T;
Expand Down
43 changes: 26 additions & 17 deletions packages/qwik/src/core/v2/signal/v2-signal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { isPromise } from '../../util/promises';
import { qDev } from '../../util/qdev';
import type { VNode } from '../client/types';
import { vnode_getProp, vnode_isVirtualVNode, vnode_isVNode, vnode_setProp } from '../client/vnode';
import { ChoreType, type NodePropPayload } from '../shared/scheduler';
import { ChoreType, type NodePropData, type NodePropPayload } from '../shared/scheduler';
import type { Container2, HostElement, fixMeAny } from '../shared/types';
import type { ISsrNode } from '../ssr/ssr-types';
import type { Signal as ISignal, ReadonlySignal } from './v2-signal.public';
Expand Down Expand Up @@ -77,11 +77,18 @@ export const isSignal = (value: any): value is ISignal<unknown> => {
* - `Task`: `useTask`, `useVisibleTask`, `useResource`
* - `VNode` and `ISsrNode`: Either a component or `<Signal>`
* - `Signal2`: A derived signal which contains a computation function.
*
* @internal
*/
export type Effect = Task | VNode | ISsrNode | Signal;

/** @internal */
export class EffectData<T extends Record<string, any> = Record<string, any>> {
data: T;

constructor(data: T) {
this.data = data;
}
}

/**
* An effect plus a list of subscriptions effect depends on.
*
Expand Down Expand Up @@ -121,20 +128,20 @@ export type Effect = Task | VNode | ISsrNode | Signal;
export type EffectSubscriptions = [
Effect, // EffectSubscriptionsProp.EFFECT
string, // EffectSubscriptionsProp.PROPERTY
any | null, // EffectSubscriptionsProp.DATA
...// NOTE even thought this is shown as `...(string|Signal2)`
// it is a list of strings followed by a list of signals (not intermingled)
...// NOTE even thought this is shown as `...(string|Signal)`
// it is a list of strings followed by optional EffectData
// and a list of signals (not intermingled)
(
| string // List of properties (Only used with Store2 (not with Signal2))
| EffectData // only used at the start
| string // List of properties (Only used with Store (not with Signal))
| Signal
| TargetType // List of signals to release
)[],
];
export const enum EffectSubscriptionsProp {
EFFECT = 0,
PROPERTY = 1,
DATA = 2,
FIRST_BACK_REF = 3,
FIRST_BACK_REF_OR_DATA = 2,
}
export const enum EffectProperty {
COMPONENT = ':',
Expand Down Expand Up @@ -359,13 +366,15 @@ export const triggerEffects = (
container.$scheduler$(ChoreType.NODE_DIFF, host, target, signal as fixMeAny);
} else {
const host: HostElement = effect as any;
const scopedStyleIdPrefix: string | null =
effectSubscriptions[EffectSubscriptionsProp.DATA];
const payload: NodePropPayload = {
value: signal as Signal<any>,
scopedStyleIdPrefix,
};
container.$scheduler$(ChoreType.NODE_PROP, host, property, payload);
let effectData = effectSubscriptions[EffectSubscriptionsProp.FIRST_BACK_REF_OR_DATA];
if (effectData instanceof EffectData) {
effectData = effectData as EffectData<NodePropData>;
const payload: NodePropPayload = {
...effectData.data,
$value$: signal,
};
container.$scheduler$(ChoreType.NODE_PROP, host, property, payload);
}
}
};
effects.forEach(scheduleEffect);
Expand Down Expand Up @@ -440,7 +449,7 @@ export class ComputedSignal<T> extends Signal<T> {
const ctx = tryGetInvokeContext();
assertDefined(computeQrl, 'Signal is marked as dirty, but no compute function is provided.');
const previousEffectSubscription = ctx?.$effectSubscriber$;
ctx && (ctx.$effectSubscriber$ = [this, EffectProperty.VNODE, null]);
ctx && (ctx.$effectSubscriber$ = [this, EffectProperty.VNODE]);
assertTrue(
!!computeQrl.resolved,
'Computed signals must run sync. Expected the QRL to be resolved at this point.'
Expand Down
2 changes: 1 addition & 1 deletion packages/qwik/src/core/v2/signal/v2-signal.unit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ describe('v2-signal', () => {
} else {
const ctx = newInvokeContext();
ctx.$container2$ = container;
const subscriber: EffectSubscriptions = [task, EffectProperty.COMPONENT, null, ctx];
const subscriber: EffectSubscriptions = [task, EffectProperty.COMPONENT, ctx];
ctx.$effectSubscriber$ = subscriber;
return invoke(ctx, qrl.getFn(ctx));
}
Expand Down
1 change: 1 addition & 0 deletions packages/qwik/src/server/qwik-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,4 @@ export type {
export type { ResolvedManifest, SymbolMapper } from '../optimizer/src/types';
export type { SymbolToChunkResolver } from '../core/v2/ssr/ssr-types';
export type { fixMeAny } from '../core/v2/shared/types';
export type { NodePropData } from '../core/v2/shared/scheduler';
10 changes: 8 additions & 2 deletions packages/qwik/src/server/v2-ssr-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
_walkJSX,
isSignal,
type JSXNode,
_EffectData as EffectData,
} from '@builder.io/qwik';
import { isDev } from '@builder.io/qwik/build';
import type { ResolvedManifest } from '@builder.io/qwik/optimizer';
Expand Down Expand Up @@ -57,6 +58,7 @@ import {
type ISsrNode,
type JSXChildren,
type JSXOutput,
type NodePropData,
type SerializationContext,
type SsrAttrKey,
type SsrAttrValue,
Expand Down Expand Up @@ -1022,7 +1024,7 @@ class SSRContainer extends _SharedContainer implements ISSRContainer {
}
}

private writeAttrs(tag: string, attrs: SsrAttrs, immutable: boolean): string | undefined {
private writeAttrs(tag: string, attrs: SsrAttrs, isConst: boolean): string | undefined {
let innerHTML: string | undefined = undefined;
if (attrs.length) {
for (let i = 0; i < attrs.length; i++) {
Expand Down Expand Up @@ -1057,7 +1059,11 @@ class SSRContainer extends _SharedContainer implements ISSRContainer {

if (isSignal(value)) {
const lastNode = this.getLastNode();
value = this.trackSignalValue(value, lastNode, key, styleScopedId);
const signalData = new EffectData<NodePropData>({
$scopedStyleIdPrefix$: styleScopedId,
$isConst$: isConst,
});
value = this.trackSignalValue(value, lastNode, key, signalData);
}

if (key === dangerouslySetInnerHTML) {
Expand Down

0 comments on commit 02ae97a

Please sign in to comment.