Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions packages/qwik/src/core/render/dom/notify-render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,10 @@ export const notifyChange = (subscriber: Subscriber, containerState: ContainerSt
* @public
*/
const notifyRender = (hostElement: QwikElement, containerState: ContainerState): void => {
if (qDev && !qTest && containerState.$platform$.isServer) {
logWarn('Can not rerender in server platform');
return undefined;
const isServer = qDev && !qTest && containerState.$platform$.isServer;
if (!isServer) {
resumeIfNeeded(containerState.$containerEl$);
}
resumeIfNeeded(containerState.$containerEl$);

const ctx = getContext(hostElement);
assertDefined(
Expand All @@ -74,6 +73,10 @@ const notifyRender = (hostElement: QwikElement, containerState: ContainerState):
);
containerState.$hostsStaging$.add(hostElement);
} else {
if (isServer) {
logWarn('Can not rerender in server platform');
return undefined;
}
containerState.$hostsNext$.add(hostElement);
scheduleFrame(containerState);
}
Expand Down
19 changes: 17 additions & 2 deletions packages/qwik/src/core/render/ssr/render-ssr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ export const renderSSR = async (doc: Document, node: JSXNode, opts: RenderSSROpt
headNodes: [],
};
const beforeContent = opts.beforeContent;
const beforeClose = opts.beforeClose;
if (beforeContent) {
ssrCtx.headNodes.push(...beforeContent);
}
Expand Down Expand Up @@ -119,12 +118,28 @@ export const renderSSR = async (doc: Document, node: JSXNode, opts: RenderSSROpt
children: [...ssrCtx.headNodes, node],
});
}
await renderNode(node, ssrCtx, opts.stream, 0, (stream) => {
containerState.$hostsRendering$ = new Set();
containerState.$renderPromise$ = Promise.resolve().then(() =>
renderRoot(node, ssrCtx, opts.stream, containerState, opts)
);
await containerState.$renderPromise$;
};

export const renderRoot = async (
node: JSXNode<any>,
ssrCtx: SSRContext,
stream: StreamWriter,
containerState: ContainerState,
opts: RenderSSROptions
) => {
const beforeClose = opts.beforeClose;
await renderNode(node, ssrCtx, stream, 0, (stream) => {
const result = beforeClose?.(ssrCtx.$contexts$, containerState);
if (result) {
return processData(result, ssrCtx, stream, 0, undefined);
}
});
return ssrCtx.rctx;
};

export const renderNodeFunction = (
Expand Down
5 changes: 5 additions & 0 deletions packages/qwik/src/core/use/use-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@ export const invoke = <ARGS extends any[] = any[], RET = any>(
return returnValue;
};

export const waitAndRun = (ctx: Required<InvokeContext>, callback: () => any) => {
const previousWait = ctx.$waitOn$.slice();
ctx.$waitOn$.push(Promise.allSettled(previousWait).then(callback));
};

export const newInvokeContext = (
doc?: Document,
hostElement?: QwikElement,
Expand Down
1 change: 1 addition & 0 deletions packages/qwik/src/core/use/use-resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export const useResourceQrl = <T>(
) as ResourceDescriptor<any>;
const previousWait = Promise.all(ctx.$waitOn$.slice());
runResource(watch, containerState, previousWait);

getContext(el).$watches$.push(watch);
set(resource);

Expand Down
58 changes: 32 additions & 26 deletions packages/qwik/src/core/use/use-watch.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { getProxyTarget, noSerialize, NoSerialize, unwrapProxy } from '../object/q-object';
import { getContext } from '../props/props';
import { newInvokeContext, invoke } from './use-core';
import { newInvokeContext, invoke, waitAndRun } from './use-core';
import { logError, logErrorAndStop } from '../util/log';
import { delay, safeCall, then } from '../util/promises';
import { getDocument } from '../util/dom';
Expand Down Expand Up @@ -251,18 +251,20 @@ export interface UseWatchOptions {
// </docs>
export const useWatchQrl = (qrl: QRL<WatchFn>, opts?: UseWatchOptions): void => {
const { get, set, ctx, i } = useSequentialScope<boolean>();
if (!get) {
assertQrl(qrl);
const el = ctx.$hostElement$;
const containerState = ctx.$renderCtx$.$containerState$;
const watch = new Watch(WatchFlagsIsDirty | WatchFlagsIsWatch, i, el, qrl, undefined);
set(true);
getContext(el).$watches$.push(watch);
const previousWait = ctx.$waitOn$.slice();
ctx.$waitOn$.push(Promise.all(previousWait).then(() => runSubscriber(watch, containerState)));
if (isServer(ctx)) {
useRunWatch(watch, opts?.eagerness);
}
if (get) {
return;
}
assertQrl(qrl);

const el = ctx.$hostElement$;
const containerState = ctx.$renderCtx$.$containerState$;
const watch = new Watch(WatchFlagsIsDirty | WatchFlagsIsWatch, i, el, qrl, undefined);
set(true);
qrl.$resolveLazy$(el);
getContext(el).$watches$.push(watch);
waitAndRun(ctx, () => runSubscriber(watch, containerState));
if (isServer(ctx)) {
useRunWatch(watch, opts?.eagerness);
}
};

Expand Down Expand Up @@ -359,17 +361,19 @@ export const useWatch$ = /*#__PURE__*/ implicit$FirstArg(useWatchQrl);
// </docs>
export const useClientEffectQrl = (qrl: QRL<WatchFn>, opts?: UseEffectOptions): void => {
const { get, set, i, ctx } = useSequentialScope<boolean>();
if (!get) {
assertQrl(qrl);
const el = ctx.$hostElement$;
const watch = new Watch(WatchFlagsIsEffect, i, el, qrl, undefined);
const eagerness = opts?.eagerness ?? 'visible';
set(true);
getContext(el).$watches$.push(watch);
useRunWatch(watch, eagerness);
if (!isServer(ctx)) {
notifyWatch(watch, ctx.$renderCtx$.$containerState$);
}
if (get) {
return;
}
assertQrl(qrl);
const el = ctx.$hostElement$;
const watch = new Watch(WatchFlagsIsEffect, i, el, qrl, undefined);
const eagerness = opts?.eagerness ?? 'visible';
set(true);
getContext(el).$watches$.push(watch);
useRunWatch(watch, eagerness);
if (!isServer(ctx)) {
qrl.$resolveLazy$(el);
notifyWatch(watch, ctx.$renderCtx$.$containerState$);
}
};

Expand Down Expand Up @@ -450,7 +454,7 @@ export const useServerMountQrl = <T>(mountQrl: QRL<MountFn<T>>): void => {
}

if (isServer(ctx)) {
ctx.$waitOn$.push(mountQrl());
waitAndRun(ctx, mountQrl);
set(true);
} else {
throw qError(QError_canNotMountUseServerMount, ctx.$hostElement$);
Expand Down Expand Up @@ -538,7 +542,9 @@ export const useMountQrl = <T>(mountQrl: QRL<MountFn<T>>): void => {
if (get) {
return;
}
ctx.$waitOn$.push(mountQrl());
assertQrl(mountQrl);
mountQrl.$resolveLazy$(ctx.$hostElement$);
waitAndRun(ctx, mountQrl);
set(true);
};

Expand Down
59 changes: 59 additions & 0 deletions starters/apps/e2e/src/components/mount/mount.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { component$, useMount$, useServerMount$, useStore, useWatch$ } from '@builder.io/qwik';
import { delay } from '../async/async';

export const MountRoot = component$(() => {
const internal = useStore(
{
renders: 0,
},
{
reactive: false,
}
);
const store = useStore({
logs: '',
});
useServerMount$(async () => {
store.logs += 'BEFORE useServerMount1()\n';
await delay(100);
store.logs += 'AFTER useServerMount1()\n';
});

useMount$(async () => {
store.logs += 'BEFORE useMount2()\n';
await delay(50);
store.logs += 'AFTER useMount2()\n';
});

useWatch$(async () => {
store.logs += 'BEFORE useWatch3()\n';
await delay(20);
store.logs += 'AFTER useWatch3()\n';
});

useServerMount$(async () => {
store.logs += 'BEFORE useServerMount4()\n';
await delay(10);
store.logs += 'AFTER useServerMount4()\n';
});

internal.renders++;

return (
<>
<button
onClick$={() => {
store.logs += 'Click\n';
}}
>
Rerender
</button>
<pre id="renders">Renders: {internal.renders}</pre>
<pre id="logs">{store.logs}</pre>
</>
);
});

export const Child = component$((props: { counter: { count: number } }) => {
return <>Rerender {props.counter.count}</>;
});
2 changes: 2 additions & 0 deletions starters/apps/e2e/src/entry.ssr.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { ResourceApp } from './components/resource/resource';
import { TreeshakingApp } from './components/treeshaking/treeshaking';
import { Streaming } from './components/streaming/streaming';
import { ResourceSerialization } from './components/resource/resource-serialization';
import { MountRoot } from './components/mount/mount';

/**
* Entry point for server-side pre-rendering.
Expand Down Expand Up @@ -50,6 +51,7 @@ export default function (opts: RenderToStreamOptions) {
'/e2e/resource-serialization': () => <ResourceSerialization />,
'/e2e/treeshaking': () => <TreeshakingApp />,
'/e2e/streaming': () => <Streaming />,
'/e2e/mount': () => <MountRoot />,
};
const Test = tests[url.pathname];

Expand Down
3 changes: 3 additions & 0 deletions starters/apps/e2e/src/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ export const Root = component$(() => {
<p>
<a href="/e2e/resource-serialization">Resource serialization</a>
</p>
<p>
<a href="/e2e/mount">Mount</a>
</p>
</section>
);
});
34 changes: 34 additions & 0 deletions starters/e2e/e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -690,4 +690,38 @@ test.describe('e2e', () => {
await expect(el2.length).toBe(3);
});
});

test.describe('mount', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/e2e/mount');
page.on('pageerror', (err) => expect(err).toEqual(undefined));
});

test('should render logs correctly', async ({ page }) => {
const btn = await page.locator('button');
const logs = await page.locator('#logs');
const renders = await page.locator('#renders');
await expect(renders).toHaveText('Renders: 2');
await expect(logs).toHaveText(`BEFORE useServerMount1()
AFTER useServerMount1()
BEFORE useMount2()
AFTER useMount2()
BEFORE useWatch3()
AFTER useWatch3()
BEFORE useServerMount4()
AFTER useServerMount4()`);

await btn.click();
await expect(renders).toHaveText('Renders: 3');
await expect(logs).toHaveText(`BEFORE useServerMount1()
AFTER useServerMount1()
BEFORE useMount2()
AFTER useMount2()
BEFORE useWatch3()
AFTER useWatch3()
BEFORE useServerMount4()
AFTER useServerMount4()
Click`);
});
});
});