From dcef9d94cd0e8fb25d6b77d5755e693092078e86 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Wed, 12 Jul 2023 07:28:22 +0100 Subject: [PATCH] perf(nuxt): prepopulate island payloads from rendered html (#22049) Co-authored-by: Julien Huang --- .../nuxt/src/app/components/nuxt-island.ts | 43 ++++++++++++------- packages/nuxt/src/app/components/utils.ts | 27 +++++++++--- 2 files changed, 50 insertions(+), 20 deletions(-) diff --git a/packages/nuxt/src/app/components/nuxt-island.ts b/packages/nuxt/src/app/components/nuxt-island.ts index 4cd284b1eb..d465417772 100644 --- a/packages/nuxt/src/app/components/nuxt-island.ts +++ b/packages/nuxt/src/app/components/nuxt-island.ts @@ -48,7 +48,33 @@ export default defineComponent({ const mounted = ref(false) onMounted(() => { mounted.value = true }) - const ssrHTML = ref(process.client ? getFragmentHTML(instance.vnode?.el ?? null).join('') ?? '
' : '
') + function setPayload (key: string, result: NuxtIslandResponse) { + nuxtApp.payload.data[key] = { + __nuxt_island: { + key, + ...(process.server && process.env.prerender) + ? {} + : { params: { ...props.context, props: props.props ? JSON.stringify(props.props) : undefined } } + }, + ...result + } + } + + const ssrHTML = ref('
') + if (process.client) { + 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(''), + state: {}, + head: { + link: [], + style: [] + } + }) + } + ssrHTML.value = renderedHTML ?? '
' + } const slotProps = computed(() => getSlotProps(ssrHTML.value)) const uid = ref(ssrHTML.value.match(SSR_UID_RE)?.[1] ?? randomUUID()) const availableSlots = computed(() => [...ssrHTML.value.matchAll(SLOTNAME_RE)].map(m => m[1])) @@ -91,20 +117,7 @@ export default defineComponent({ appendResponseHeader(event, 'x-nitro-prerender', hints) } } - nuxtApp.payload.data[key] = { - __nuxt_island: { - key, - ...(process.server && process.env.prerender) - ? {} - : { - params: { - ...props.context, - props: props.props ? JSON.stringify(props.props) : undefined - } - } - }, - ...result - } + setPayload(key, result) return result } const key = ref(0) diff --git a/packages/nuxt/src/app/components/utils.ts b/packages/nuxt/src/app/components/utils.ts index d74da69896..d95ed64f50 100644 --- a/packages/nuxt/src/app/components/utils.ts +++ b/packages/nuxt/src/app/components/utils.ts @@ -99,25 +99,42 @@ export function vforToArray (source: any): any[] { return [] } -export function getFragmentHTML (element: RendererNode | null) { +/** + * Retrieve the HTML content from an element + * Handles `` Fragment elements + * + * @param element the element to retrieve the HTML + * @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) { if (element) { if (element.nodeName === '#comment' && element.nodeValue === '[') { - return getFragmentChildren(element) + return getFragmentChildren(element, [], withoutSlots) + } + if (withoutSlots) { + const clone = element.cloneNode(true) + clone.querySelectorAll('[nuxt-ssr-slot-name]').forEach((n: Element) => { n.innerHTML = '' }) + return [clone.outerHTML] } return [element.outerHTML] } return [] } -function getFragmentChildren (element: RendererNode | null, blocks: string[] = []) { +function getFragmentChildren (element: RendererNode | null, blocks: string[] = [], withoutSlots = false) { if (element && element.nodeName) { if (isEndFragment(element)) { return blocks } else if (!isStartFragment(element)) { - blocks.push(element.outerHTML) + const clone = element.cloneNode(true) as Element + if (withoutSlots) { + clone.querySelectorAll('[nuxt-ssr-slot-name]').forEach((n) => { n.innerHTML = '' }) + } + blocks.push(clone.outerHTML) } - getFragmentChildren(element.nextSibling, blocks) + getFragmentChildren(element.nextSibling, blocks, withoutSlots) } return blocks }