diff --git a/packages/runtime-core/__tests__/helpers/useId.spec.ts b/packages/runtime-core/__tests__/helpers/useId.spec.ts index d71260cdaff..3860d43db20 100644 --- a/packages/runtime-core/__tests__/helpers/useId.spec.ts +++ b/packages/runtime-core/__tests__/helpers/useId.spec.ts @@ -8,6 +8,7 @@ import { defineAsyncComponent, defineComponent, h, + onServerPrefetch, useId, } from 'vue' import { renderToString } from '@vue/server-renderer' @@ -145,6 +146,40 @@ describe('useId', () => { expect(await getOutput(() => factory(16, 0))).toBe(expected) }) + test('components with serverPrefetch', async () => { + const factory = (): ReturnType => { + const SPOne = defineComponent({ + setup() { + onServerPrefetch(() => {}) + return () => h(BasicComponentWithUseId) + }, + }) + + const SPTwo = defineComponent({ + render() { + return h(BasicComponentWithUseId) + }, + }) + + const app = createApp({ + setup() { + const id1 = useId() + const id2 = useId() + return () => [id1, ' ', id2, ' ', h(SPOne), ' ', h(SPTwo)] + }, + }) + return [app, []] + } + + const expected = + 'v-0 v-1 ' + // root + 'v-0-0 v-0-1 ' + // inside first async subtree + 'v-2 v-3' // inside second async subtree + // assert different async resolution order does not affect id stable-ness + expect(await getOutput(() => factory())).toBe(expected) + expect(await getOutput(() => factory())).toBe(expected) + }) + test('async setup()', async () => { const factory = ( delay1: number, diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index a1ce1de4eb9..939f5a401f0 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -856,11 +856,10 @@ function setupStatefulComponent( // 2. call setup() const { setup } = Component if (setup) { + pauseTracking() const setupContext = (instance.setupContext = setup.length > 1 ? createSetupContext(instance) : null) - const reset = setCurrentInstance(instance) - pauseTracking() const setupResult = callWithErrorHandling( setup, instance, @@ -870,12 +869,16 @@ function setupStatefulComponent( setupContext, ], ) + const isAsyncSetup = isPromise(setupResult) resetTracking() reset() - if (isPromise(setupResult)) { - // async setup, mark as async boundary for useId() - if (!isAsyncWrapper(instance)) markAsyncBoundary(instance) + if ((isAsyncSetup || instance.sp) && !isAsyncWrapper(instance)) { + // async setup / serverPrefetch, mark as async boundary for useId() + markAsyncBoundary(instance) + } + + if (isAsyncSetup) { setupResult.then(unsetCurrentInstance, unsetCurrentInstance) if (isSSR) { // return the promise so server-renderer can wait on it