Skip to content

Commit

Permalink
fix: context treeshaking with slot (#2090)
Browse files Browse the repository at this point in the history
fixes #2087
  • Loading branch information
manucorporat authored Nov 11, 2022
1 parent aaa4854 commit 619f531
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 37 deletions.
2 changes: 2 additions & 0 deletions packages/qwik/src/core/component/component.public.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { assertQrl } from '../qrl/qrl-class';
import type { ValueOrPromise } from '../util/types';
import { invoke, newInvokeContext } from '../use/use-core';
import { verifySerializable } from '../state/common';
import { _IMMUTABLE } from '../state/constants';

/**
* Infers `Props` from the component.
Expand Down Expand Up @@ -155,6 +156,7 @@ export const componentQrl = <PROPS extends {}>(
{
[OnRenderProp]: componentQrl,
[QSlot]: props[QSlot],
[_IMMUTABLE]: (props as any)[_IMMUTABLE],
children: props.children,
props,
},
Expand Down
28 changes: 18 additions & 10 deletions packages/qwik/src/core/container/pause.ts
Original file line number Diff line number Diff line change
Expand Up @@ -587,18 +587,26 @@ export const collectElementData = (elCtx: QContext, collector: Collector, dynami
}

if (dynamic) {
let parent: QContext | null = elCtx;
while (parent) {
if (parent.$contexts$) {
for (const obj of parent.$contexts$.values()) {
collectValue(obj, collector, dynamic);
}
if (parent.$contexts$.get('_') === true) {
break;
}
collectContext(elCtx, collector);
if (elCtx.$dynamicSlots$) {
for (const slotCtx of elCtx.$dynamicSlots$) {
collectContext(slotCtx, collector);
}
}
}
};

const collectContext = (elCtx: QContext | null, collector: Collector) => {
while (elCtx) {
if (elCtx.$contexts$) {
for (const obj of elCtx.$contexts$.values()) {
collectValue(obj, collector, true);
}
if (elCtx.$contexts$.get('_') === true) {
break;
}
parent = parent.$slotParent$ ?? parent.$parent$;
}
elCtx = elCtx.$slotParent$ ?? elCtx.$parent$;
}
};

Expand Down
75 changes: 48 additions & 27 deletions packages/qwik/src/core/render/ssr/render-ssr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ const IS_HEAD = 1 << 0;
const IS_HTML = 1 << 2;
const IS_TEXT = 1 << 3;

export const createDocument = () => {
const createDocument = () => {
const doc = { nodeType: 9 };
seal(doc);
return doc;
Expand Down Expand Up @@ -154,7 +154,7 @@ export const renderSSR = async (node: JSXNode, opts: RenderSSROptions) => {
await containerState.$renderPromise$;
};

export const renderRoot = async (
const renderRoot = async (
node: JSXNode,
rCtx: RenderContext,
ssrCtx: SSRContext,
Expand Down Expand Up @@ -192,7 +192,7 @@ export const renderRoot = async (
return rCtx.$static$;
};

export const renderGenerator = async (
const renderGenerator = async (
node: JSXNode<typeof InternalSSRStream>,
rCtx: RenderContext,
ssrCtx: SSRContext,
Expand Down Expand Up @@ -222,7 +222,7 @@ export const renderGenerator = async (
}
};

export const renderNodeVirtual = (
const renderNodeVirtual = (
node: JSXNode<typeof Virtual>,
elCtx: QContext,
extraNodes: JSXNode<string>[] | undefined,
Expand Down Expand Up @@ -289,7 +289,7 @@ export const renderNodeVirtual = (

const CLOSE_VIRTUAL = `<!--/qv-->`;

export const renderAttributes = (attributes: Record<string, string>): string => {
const renderAttributes = (attributes: Record<string, string>): string => {
let text = '';
for (const prop of Object.keys(attributes)) {
if (prop === 'dangerouslySetInnerHTML') {
Expand All @@ -303,7 +303,7 @@ export const renderAttributes = (attributes: Record<string, string>): string =>
return text;
};

export const renderVirtualAttributes = (attributes: Record<string, string>): string => {
const renderVirtualAttributes = (attributes: Record<string, string>): string => {
let text = '';
for (const prop of Object.keys(attributes)) {
if (prop === 'children') {
Expand All @@ -317,7 +317,7 @@ export const renderVirtualAttributes = (attributes: Record<string, string>): str
return text;
};

export const renderNodeElementSync = (
const renderNodeElementSync = (
tagName: string,
attributes: Record<string, string>,
stream: StreamWriter
Expand All @@ -336,7 +336,7 @@ export const renderNodeElementSync = (
stream.write(`</${tagName}>`);
};

export const renderSSRComponent = (
const renderSSRComponent = (
rCtx: RenderContext,
ssrCtx: SSRContext,
stream: StreamWriter,
Expand Down Expand Up @@ -449,7 +449,7 @@ const splitProjectedChildren = (children: any, ssrCtx: SSRContext) => {
return slotMap;
};

export const createSSRContext = (nodeType: 1 | 111) => {
const createSSRContext = (nodeType: 1 | 111) => {
const elm = {
nodeType,
[Q_CTX]: null,
Expand All @@ -458,7 +458,7 @@ export const createSSRContext = (nodeType: 1 | 111) => {
return createContext(elm as any);
};

export const renderNode = (
const renderNode = (
node: JSXNode,
rCtx: RenderContext,
ssrCtx: SSRContext,
Expand All @@ -468,8 +468,13 @@ export const renderNode = (
) => {
const tagName = node.type;
const hostCtx = rCtx.$cmpCtx$;
if (hostCtx && hasDynamicChildren(node)) {
const dynamicChildren = hasDynamicChildren(node);
if (dynamicChildren && hostCtx) {
hostCtx.$flags$ |= HOST_FLAG_DYNAMIC;
const slotCtx = rCtx.$slotCtx$;
if (slotCtx) {
addDynamicSlot(hostCtx, slotCtx);
}
}
if (typeof tagName === 'string') {
const key = node.key;
Expand Down Expand Up @@ -617,8 +622,13 @@ export const renderNode = (

if (tagName === Virtual) {
const elCtx = createSSRContext(111);
elCtx.$parent$ = rCtx.$cmpCtx$!;
elCtx.$slotParent$ = rCtx.$slotCtx$!; // TODO
elCtx.$parent$ = rCtx.$cmpCtx$;
elCtx.$slotParent$ = rCtx.$slotCtx$;
if (dynamicChildren) {
if (hostCtx) {
addDynamicSlot(hostCtx, elCtx);
}
}
return renderNodeVirtual(
node as JSXNode<typeof Virtual>,
elCtx,
Expand All @@ -645,7 +655,8 @@ export const renderNode = (
const res = invoke(ssrCtx.invocationContext, tagName, node.props, node.key);
return processData(res, rCtx, ssrCtx, stream, flags, beforeClose);
};
export const processData = (

const processData = (
node: any,
rCtx: RenderContext,
ssrCtx: SSRContext,
Expand Down Expand Up @@ -687,13 +698,13 @@ export const processData = (
}
};

function walkChildren(
const walkChildren = (
children: any,
rCtx: RenderContext,
ssrContext: SSRContext,
stream: StreamWriter,
flags: number
): ValueOrPromise<void> {
): ValueOrPromise<void> => {
if (children == null) {
return;
}
Expand Down Expand Up @@ -742,9 +753,9 @@ function walkChildren(
return undefined;
}
}, undefined);
}
};

export const flatVirtualChildren = (children: any, ssrCtx: SSRContext): any[] | null => {
const flatVirtualChildren = (children: any, ssrCtx: SSRContext): any[] | null => {
if (children == null) {
return null;
}
Expand All @@ -756,7 +767,7 @@ export const flatVirtualChildren = (children: any, ssrCtx: SSRContext): any[] |
return nodes;
};

export const stringifyClass = (str: any) => {
const stringifyClass = (str: any) => {
if (!str) {
return '';
}
Expand All @@ -778,7 +789,7 @@ export const stringifyClass = (str: any) => {
return output.join(' ');
};

export const _flatVirtualChildren = (children: any, ssrCtx: SSRContext): any => {
const _flatVirtualChildren = (children: any, ssrCtx: SSRContext): any => {
if (children == null) {
return null;
}
Expand Down Expand Up @@ -825,14 +836,14 @@ const setComponentProps = (
}
};

function processPropKey(prop: string) {
const processPropKey = (prop: string) => {
if (prop === 'htmlFor') {
return 'for';
}
return prop;
}
};

function processPropValue(prop: string, value: any): string | null {
const processPropValue = (prop: string, value: any): string | null => {
if (prop === 'style') {
return stringifyStyle(value);
}
Expand All @@ -846,7 +857,7 @@ function processPropValue(prop: string, value: any): string | null {
return '';
}
return String(value);
}
};

const textOnlyElements: Record<string, true | undefined> = {
title: true,
Expand Down Expand Up @@ -887,7 +898,7 @@ export interface ServerDocument {
const ESCAPE_HTML = /[&<>]/g;
const ESCAPE_ATTRIBUTES = /[&"]/g;

export const escapeHtml = (s: string) => {
const escapeHtml = (s: string) => {
return s.replace(ESCAPE_HTML, (c) => {
switch (c) {
case '&':
Expand All @@ -902,7 +913,7 @@ export const escapeHtml = (s: string) => {
});
};

export const escapeAttr = (s: string) => {
const escapeAttr = (s: string) => {
return s.replace(ESCAPE_ATTRIBUTES, (c) => {
switch (c) {
case '&':
Expand All @@ -915,10 +926,20 @@ export const escapeAttr = (s: string) => {
});
};

export const listenersNeedId = (listeners: Listener[]) => {
const listenersNeedId = (listeners: Listener[]) => {
return listeners.some((l) => l[1].$captureRef$ && l[1].$captureRef$.length > 0);
};

const addDynamicSlot = (hostCtx: QContext, elCtx: QContext) => {
let dynamicSlots = hostCtx.$dynamicSlots$;
if (!dynamicSlots) {
hostCtx.$dynamicSlots$ = dynamicSlots = [];
}
if (!dynamicSlots.includes(elCtx)) {
dynamicSlots.push(elCtx);
}
};

const hasDynamicChildren = (node: JSXNode) => {
return (node.props as any)[_IMMUTABLE]?.children === false;
};
2 changes: 2 additions & 0 deletions packages/qwik/src/core/state/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export interface QContext {
$scopeIds$: string[] | null;
$vdom$: ProcessedJSXNode | null;
$slots$: ProcessedJSXNode[] | null;
$dynamicSlots$: QContext[] | null;
$parent$: QContext | null;
$slotParent$: QContext | null;
}
Expand Down Expand Up @@ -135,6 +136,7 @@ export const createContext = (element: Element | VirtualElement): QContext => {
$vdom$: null,
$componentQrl$: null,
$contexts$: null,
$dynamicSlots$: null,
$parent$: null,
$slotParent$: null,
};
Expand Down
53 changes: 53 additions & 0 deletions starters/apps/e2e/src/components/context/context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export const ContextRoot = component$(() => {
</ContextFromSlot>

<Issue1971 />
<Issue2087 />
</div>
);
});
Expand Down Expand Up @@ -154,3 +155,55 @@ export const Issue1971Consumer = component$(() => {
const ctx = useContext(Issue1971Context);
return <div id="issue1971-value">Value: {ctx.value}</div>;
});

export const Ctx = createContext<{ t: string }>('issue-2087');

export const Issue2087 = component$(() => {
return (
<>
<Issue2087_Root />
<Issue2087_Nested />
</>
);
});

export const Issue2087_Root = component$(() => {
const t = useSignal(0);
return (
<Provider>
<Symbol id="RootA" />
<button id="issue2087_btn1" onClick$={() => t.value++}>
Click me
</button>
{!!t.value && <Symbol id="RootB" />}
</Provider>
);
});

export const Issue2087_Nested = component$(() => {
const t = useSignal(0);
return (
<Provider>
<Symbol id="NestedA" />
<button id="issue2087_btn2" onClick$={() => t.value++}>
Click me
</button>
<div>{!!t.value && <Symbol id="NestedB" />}</div>
</Provider>
);
});

export const Symbol = component$(({ id }: any) => {
const s = useContext(Ctx);
return (
<p id={`issue2087_symbol_${id}`}>
Symbol {id}, context value: {s.t}
</p>
);
});

export const Provider = component$(() => {
const s = useStore({ t: 'yes' });
useContextProvider(Ctx, s);
return <Slot />;
});
Loading

0 comments on commit 619f531

Please sign in to comment.