From abc54c62d107c1e6b6903f51d293b1a5b7dbdba4 Mon Sep 17 00:00:00 2001 From: Matt Garrett Date: Wed, 8 May 2024 14:32:53 -0700 Subject: [PATCH 1/6] test(server-renderer): create serverPrefetch w/async setup test case --- .../server-renderer/__tests__/render.spec.ts | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/packages/server-renderer/__tests__/render.spec.ts b/packages/server-renderer/__tests__/render.spec.ts index 705cb2a8b6f..1b1d6256e8c 100644 --- a/packages/server-renderer/__tests__/render.spec.ts +++ b/packages/server-renderer/__tests__/render.spec.ts @@ -873,6 +873,26 @@ function testRender(type: string, render: typeof renderToString) { expect(html).toBe(`
hello
`) }) + test('serverPrefetch w/ async setup', async () => { + const msg = Promise.resolve('hello') + const app = createApp({ + data() { + return { + msg: '', + } + }, + async serverPrefetch() { + this.msg = await msg + }, + render() { + return h('div', this.msg) + }, + async setup() {}, + }) + const html = await render(app) + expect(html).toBe(`
hello
`) + }) + // #2763 test('error handling w/ async setup', async () => { const fn = vi.fn() From a6f421f293cc58184428f3e1a211af8087a15a30 Mon Sep 17 00:00:00 2001 From: Matt Garrett Date: Wed, 8 May 2024 15:51:43 -0700 Subject: [PATCH 2/6] fix(server-renderer): Fix renderComponentVNode to call serverPrefetch option w/async setup This fixes a bug where the serverPrefetch option would not be called with an async setup method. A `prefetches` reference was created while `instance.sp` was `null`. However, with an async setup, `instance.sp` has the serverPrefetch methods only after setup resolves. The fix is achieved by waiting to for setup to resolve before trying to access the the serverPrefetch options from `instance.sp`. --- packages/server-renderer/src/render.ts | 30 +++++++++++++++----------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/packages/server-renderer/src/render.ts b/packages/server-renderer/src/render.ts index 3029df6e540..68f4490e4d7 100644 --- a/packages/server-renderer/src/render.ts +++ b/packages/server-renderer/src/render.ts @@ -94,22 +94,26 @@ export function renderComponentVNode( const instance = createComponentInstance(vnode, parentComponent, null) const res = setupComponent(instance, true /* isSSR */) const hasAsyncSetup = isPromise(res) - const prefetches = instance.sp /* LifecycleHooks.SERVER_PREFETCH */ - if (hasAsyncSetup || prefetches) { - let p: Promise = hasAsyncSetup + const getPrefetches = () => instance.sp /* LifecycleHooks.SERVER_PREFETCH */ + if (hasAsyncSetup || getPrefetches()) { + const setupResolved: Promise = hasAsyncSetup ? (res as Promise) : Promise.resolve() - if (prefetches) { - p = p - .then(() => - Promise.all( + const setupAndPrefetched = setupResolved + .then(() => { + // instance.sp may be null until an async setup resolves, so evaluate it here + const prefetches = getPrefetches() + if (prefetches) { + return Promise.all( prefetches.map(prefetch => prefetch.call(instance.proxy)), - ), - ) - // Note: error display is already done by the wrapped lifecycle hook function. - .catch(NOOP) - } - return p.then(() => renderComponentSubTree(instance, slotScopeId)) + ) + } + }) + // Note: error display is already done by the wrapped lifecycle hook function. + .catch(NOOP) + return setupAndPrefetched.then(() => + renderComponentSubTree(instance, slotScopeId), + ) } else { return renderComponentSubTree(instance, slotScopeId) } From 4ec094b87422c4002e3da912be7650de841f5eb0 Mon Sep 17 00:00:00 2001 From: Matt Garrett Date: Fri, 16 Aug 2024 09:18:25 -0700 Subject: [PATCH 3/6] fix(server-renderer): refactor renderComponentVNode to be async and simplify internal logic --- packages/server-renderer/src/render.ts | 39 +++++++++----------------- 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/packages/server-renderer/src/render.ts b/packages/server-renderer/src/render.ts index 68f4490e4d7..5a3cacdbb00 100644 --- a/packages/server-renderer/src/render.ts +++ b/packages/server-renderer/src/render.ts @@ -86,37 +86,26 @@ export function createBuffer() { } } -export function renderComponentVNode( +export async function renderComponentVNode( vnode: VNode, parentComponent: ComponentInternalInstance | null = null, slotScopeId?: string, -): SSRBuffer | Promise { +): Promise { const instance = createComponentInstance(vnode, parentComponent, null) - const res = setupComponent(instance, true /* isSSR */) - const hasAsyncSetup = isPromise(res) - const getPrefetches = () => instance.sp /* LifecycleHooks.SERVER_PREFETCH */ - if (hasAsyncSetup || getPrefetches()) { - const setupResolved: Promise = hasAsyncSetup - ? (res as Promise) - : Promise.resolve() - const setupAndPrefetched = setupResolved - .then(() => { - // instance.sp may be null until an async setup resolves, so evaluate it here - const prefetches = getPrefetches() - if (prefetches) { - return Promise.all( - prefetches.map(prefetch => prefetch.call(instance.proxy)), - ) - } - }) + await setupComponent(instance, true /* isSSR */) + const prefetches = instance.sp + if (prefetches) { + /* LifecycleHooks.SERVER_PREFETCH */ + try { + await Promise.all( + prefetches.map(prefetch => prefetch.call(instance.proxy)), + ) + } catch (error) { + // NOOP // Note: error display is already done by the wrapped lifecycle hook function. - .catch(NOOP) - return setupAndPrefetched.then(() => - renderComponentSubTree(instance, slotScopeId), - ) - } else { - return renderComponentSubTree(instance, slotScopeId) + } } + return renderComponentSubTree(instance, slotScopeId) } function renderComponentSubTree( From 7af4f77182b3838fac07acc994ec3f846570a02d Mon Sep 17 00:00:00 2001 From: Matt Garrett Date: Mon, 19 Aug 2024 18:46:46 -0700 Subject: [PATCH 4/6] fix(server-renderer): undo async renderComponentVNode, preserve fast path --- packages/server-renderer/src/render.ts | 37 ++++++++++++++++---------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/packages/server-renderer/src/render.ts b/packages/server-renderer/src/render.ts index 5a3cacdbb00..5f105b745e0 100644 --- a/packages/server-renderer/src/render.ts +++ b/packages/server-renderer/src/render.ts @@ -86,26 +86,35 @@ export function createBuffer() { } } -export async function renderComponentVNode( +export function renderComponentVNode( vnode: VNode, parentComponent: ComponentInternalInstance | null = null, slotScopeId?: string, -): Promise { +): SSRBuffer | Promise { const instance = createComponentInstance(vnode, parentComponent, null) - await setupComponent(instance, true /* isSSR */) - const prefetches = instance.sp - if (prefetches) { - /* LifecycleHooks.SERVER_PREFETCH */ - try { - await Promise.all( - prefetches.map(prefetch => prefetch.call(instance.proxy)), - ) - } catch (error) { - // NOOP + const res = setupComponent(instance, true /* isSSR */) + const hasAsyncSetup = isPromise(res) + let prefetches = instance.sp /* LifecycleHooks.SERVER_PREFETCH */ + if (hasAsyncSetup || prefetches) { + let p: Promise = ( + hasAsyncSetup + ? // instance.sp may be null until an async setup resolves, so evaluate it here + (res as Promise).then(() => (prefetches = instance.sp)) + : Promise.resolve() + ) + .then(() => { + if (prefetches) { + return Promise.all( + prefetches.map(prefetch => prefetch.call(instance.proxy)), + ) + } + }) // Note: error display is already done by the wrapped lifecycle hook function. - } + .catch(NOOP) + return p.then(() => renderComponentSubTree(instance, slotScopeId)) + } else { + return renderComponentSubTree(instance, slotScopeId) } - return renderComponentSubTree(instance, slotScopeId) } function renderComponentSubTree( From e159b16ef3dc65de9a1c5f319d59e06d89547bc9 Mon Sep 17 00:00:00 2001 From: Matt Garrett Date: Mon, 19 Aug 2024 19:01:08 -0700 Subject: [PATCH 5/6] fix(server-renderer): simplify logic, remove two promises --- packages/server-renderer/src/render.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/server-renderer/src/render.ts b/packages/server-renderer/src/render.ts index 5f105b745e0..ff95dbe60a3 100644 --- a/packages/server-renderer/src/render.ts +++ b/packages/server-renderer/src/render.ts @@ -96,13 +96,10 @@ export function renderComponentVNode( const hasAsyncSetup = isPromise(res) let prefetches = instance.sp /* LifecycleHooks.SERVER_PREFETCH */ if (hasAsyncSetup || prefetches) { - let p: Promise = ( - hasAsyncSetup - ? // instance.sp may be null until an async setup resolves, so evaluate it here - (res as Promise).then(() => (prefetches = instance.sp)) - : Promise.resolve() - ) + const p: Promise = Promise.resolve(res as Promise) .then(() => { + // instance.sp may be null until an async setup resolves, so evaluate it here + prefetches = instance.sp if (prefetches) { return Promise.all( prefetches.map(prefetch => prefetch.call(instance.proxy)), From 7bf6732e17e4f6cee6c0bc3a149fbc5a9258c404 Mon Sep 17 00:00:00 2001 From: Matt Garrett Date: Mon, 19 Aug 2024 20:02:39 -0700 Subject: [PATCH 6/6] fix(server-renderer): guard reassignment of prefetches behind hasAsyncSetup conditional --- packages/server-renderer/src/render.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server-renderer/src/render.ts b/packages/server-renderer/src/render.ts index ff95dbe60a3..2a0311536fa 100644 --- a/packages/server-renderer/src/render.ts +++ b/packages/server-renderer/src/render.ts @@ -99,7 +99,7 @@ export function renderComponentVNode( const p: Promise = Promise.resolve(res as Promise) .then(() => { // instance.sp may be null until an async setup resolves, so evaluate it here - prefetches = instance.sp + if (hasAsyncSetup) prefetches = instance.sp if (prefetches) { return Promise.all( prefetches.map(prefetch => prefetch.call(instance.proxy)),