From a03751256203be53ad863b89a1e749264e1f7b73 Mon Sep 17 00:00:00 2001 From: Julien Huang Date: Wed, 25 Oct 2023 02:34:22 +0200 Subject: [PATCH] fix(nuxt): render a div when client-only hydrates w/o element (#23899) --- packages/nuxt/src/app/components/client-only.ts | 4 ++-- packages/nuxt/src/app/components/nuxt-island.ts | 4 ++-- packages/nuxt/src/app/components/utils.ts | 4 ++-- test/basic.test.ts | 7 +++++++ test/fixtures/basic/pages/index.vue | 3 +++ 5 files changed, 16 insertions(+), 6 deletions(-) diff --git a/packages/nuxt/src/app/components/client-only.ts b/packages/nuxt/src/app/components/client-only.ts index 894decea0c..9dd40cced8 100644 --- a/packages/nuxt/src/app/components/client-only.ts +++ b/packages/nuxt/src/app/components/client-only.ts @@ -40,7 +40,7 @@ export function createClientOnly (component: T) { ? createElementVNode(res.type, res.props, res.children, res.patchFlag, res.dynamicProps, res.shapeFlag) : h(res) } else { - const fragment = getFragmentHTML(ctx._.vnode.el ?? null) + const fragment = getFragmentHTML(ctx._.vnode.el ?? null) ?? ['
'] return process.client ? createStaticVNode(fragment.join(''), fragment.length) : h('div', ctx.$attrs ?? ctx._.attrs) } } @@ -79,7 +79,7 @@ export function createClientOnly (component: T) { ? createElementVNode(res.type, res.props, res.children, res.patchFlag, res.dynamicProps, res.shapeFlag) : h(res) } else { - const fragment = getFragmentHTML(instance?.vnode.el ?? null) + const fragment = getFragmentHTML(instance?.vnode.el ?? null) ?? ['
'] return process.client ? createStaticVNode(fragment.join(''), fragment.length) : h('div', ctx.attrs) } } diff --git a/packages/nuxt/src/app/components/nuxt-island.ts b/packages/nuxt/src/app/components/nuxt-island.ts index 66978ef658..9ba2befc46 100644 --- a/packages/nuxt/src/app/components/nuxt-island.ts +++ b/packages/nuxt/src/app/components/nuxt-island.ts @@ -73,10 +73,10 @@ export default defineComponent({ const ssrHTML = ref('') if (import.meta.client) { - const renderedHTML = getFragmentHTML(instance.vnode?.el ?? null).join('') + const renderedHTML = getFragmentHTML(instance.vnode?.el ?? null)?.join('') ?? '' if (renderedHTML && nuxtApp.isHydrating) { setPayload(`${props.name}_${hashId.value}`, { - html: getFragmentHTML(instance.vnode?.el ?? null, true).join(''), + html: getFragmentHTML(instance.vnode?.el ?? null, true)?.join('') ?? '', state: {}, head: { link: [], diff --git a/packages/nuxt/src/app/components/utils.ts b/packages/nuxt/src/app/components/utils.ts index 3fac26c7bb..d2bb6b1a21 100644 --- a/packages/nuxt/src/app/components/utils.ts +++ b/packages/nuxt/src/app/components/utils.ts @@ -104,7 +104,7 @@ export function vforToArray (source: any): any[] { * @param withoutSlots purge all slots from the HTML string retrieved * @returns {string[]} An array of string which represent the content of each element. Use `.join('')` to retrieve a component vnode.el HTML */ -export function getFragmentHTML (element: RendererNode | null, withoutSlots = false): string[] { +export function getFragmentHTML (element: RendererNode | null, withoutSlots = false): string[] | null { if (element) { if (element.nodeName === '#comment' && element.nodeValue === '[') { return getFragmentChildren(element, [], withoutSlots) @@ -116,7 +116,7 @@ export function getFragmentHTML (element: RendererNode | null, withoutSlots = fa } return [element.outerHTML] } - return [] + return null } function getFragmentChildren (element: RendererNode | null, blocks: string[] = [], withoutSlots = false) { diff --git a/test/basic.test.ts b/test/basic.test.ts index 2f86ba6f3d..e7e3a43c0c 100644 --- a/test/basic.test.ts +++ b/test/basic.test.ts @@ -362,6 +362,13 @@ describe('pages', () => { expect(pageErrors).toEqual([]) await page.close() + // don't expect any errors or warning on client-side navigation + const { page: page2, consoleLogs: consoleLogs2 } = await renderPage('/') + await page2.locator('#to-client-only-components').click() + // force wait for a few ticks + await page2.waitForTimeout(50) + expect(consoleLogs2.some(log => log.type === 'error' || log.type === 'warning')).toBeFalsy() + await page2.close() }) it('/wrapper-expose/layout', async () => { diff --git a/test/fixtures/basic/pages/index.vue b/test/fixtures/basic/pages/index.vue index 18f5dcf355..5b2de01594 100644 --- a/test/fixtures/basic/pages/index.vue +++ b/test/fixtures/basic/pages/index.vue @@ -32,6 +32,9 @@ Chunk error + + createClientOnly() + Middleware abort navigation