perf(nuxt): remove code duplication in client-only (#30460)

This commit is contained in:
Julien Huang 2025-01-05 15:35:59 +01:00 committed by GitHub
parent 2438130f6b
commit 8b8a731dff
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 26 additions and 16 deletions

View File

@ -1,12 +1,14 @@
import { cloneVNode, createElementBlock, createStaticVNode, defineComponent, getCurrentInstance, h, onMounted, provide, ref } from 'vue'
import { cloneVNode, createElementBlock, defineComponent, getCurrentInstance, h, onMounted, provide, ref } from 'vue'
import type { ComponentInternalInstance, ComponentOptions, InjectionKey } from 'vue'
import { isPromise } from '@vue/shared'
import { useNuxtApp } from '../nuxt'
import { getFragmentHTML } from './utils'
import ServerPlaceholder from './server-placeholder'
import { elToStaticVNode } from './utils'
export const clientOnlySymbol: InjectionKey<boolean> = Symbol.for('nuxt:client-only')
const STATIC_DIV = '<div></div>'
export default defineComponent({
name: 'ClientOnly',
inheritAttrs: false,
@ -54,16 +56,14 @@ export function createClientOnly<T extends ComponentOptions> (component: T) {
return (res.children === null || typeof res.children === 'string')
? cloneVNode(res)
: h(res)
} else {
const fragment = getFragmentHTML(ctx._.vnode.el ?? null) ?? ['<div></div>']
return createStaticVNode(fragment.join(''), fragment.length)
}
return elToStaticVNode(ctx._.vnode.el, STATIC_DIV)
}
} else if (clone.template) {
// handle runtime-compiler template
clone.template = `
<template v-if="mounted$">${component.template}</template>
<template v-else><div></div></template>
<template v-else>${STATIC_DIV}</template>
`
}
@ -105,10 +105,8 @@ export function createClientOnly<T extends ComponentOptions> (component: T) {
return (res.children === null || typeof res.children === 'string')
? cloneVNode(res)
: h(res)
} else {
const fragment = getFragmentHTML(instance?.vnode.el ?? null) ?? ['<div></div>']
return createStaticVNode(fragment.join(''), fragment.length)
}
return elToStaticVNode(instance?.vnode.el, STATIC_DIV)
}
})
} else {
@ -117,8 +115,7 @@ export function createClientOnly<T extends ComponentOptions> (component: T) {
if (mounted$.value) {
return h(setupState(...args), ctx.attrs)
}
const fragment = getFragmentHTML(instance?.vnode.el ?? null) ?? ['<div></div>']
return createStaticVNode(fragment.join(''), fragment.length)
return elToStaticVNode(instance?.vnode.el, STATIC_DIV)
}
}
return Object.assign(setupState, { mounted$ })

View File

@ -1,5 +1,5 @@
import { h } from 'vue'
import type { Component, RendererNode } from 'vue'
import { createStaticVNode, h } from 'vue'
import type { Component, RendererNode, VNode } from 'vue'
// eslint-disable-next-line
import { isString, isPromise, isArray, isObject } from '@vue/shared'
import type { RouteLocationNormalized } from 'vue-router'
@ -117,9 +117,9 @@ export function vforToArray (source: any): any[] {
* 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
* @returns {string[]|undefined} 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[] | null {
export function getFragmentHTML (element: RendererNode | null, withoutSlots = false): string[] | undefined {
if (element) {
if (element.nodeName === '#comment' && element.nodeValue === '[') {
return getFragmentChildren(element, [], withoutSlots)
@ -131,7 +131,6 @@ export function getFragmentHTML (element: RendererNode | null, withoutSlots = fa
}
return [element.outerHTML]
}
return null
}
function getFragmentChildren (element: RendererNode | null, blocks: string[] = [], withoutSlots = false) {
@ -151,6 +150,20 @@ function getFragmentChildren (element: RendererNode | null, blocks: string[] = [
return blocks
}
/**
* Return a static vnode from an element
* Default to a div if the element is not found and if a fallback is not provided
* @param el renderer node retrieved from the component internal instance
* @param staticNodeFallback fallback string to use if the element is not found. Must be a valid HTML string
*/
export function elToStaticVNode (el: RendererNode | null, staticNodeFallback?: string): VNode {
const fragment: string[] | undefined = el ? getFragmentHTML(el) : staticNodeFallback ? [staticNodeFallback] : undefined
if (fragment) {
return createStaticVNode(fragment.join(''), fragment.length)
}
return h('div')
}
function isStartFragment (element: RendererNode) {
return element.nodeName === '#comment' && element.nodeValue === '['
}