import { h } from 'vue' import type { Component, RendererNode } from 'vue' // eslint-disable-next-line import { isString, isPromise, isArray, isObject } from '@vue/shared' import destr from 'destr' /** * Internal utility * @private */ export const _wrapIf = (component: Component, props: any, slots: any) => { props = props === true ? {} : props return { default: () => props ? h(component, props, slots) : slots.default?.() } } // eslint-disable-next-line no-use-before-define export type SSRBuffer = SSRBufferItem[] & { hasAsync?: boolean } export type SSRBufferItem = string | SSRBuffer | Promise /** * create buffer retrieved from @vue/server-renderer * @see https://github.com/vuejs/core/blob/9617dd4b2abc07a5dc40de6e5b759e851b4d0da1/packages/server-renderer/src/render.ts#L57 * @private */ export function createBuffer () { let appendable = false const buffer: SSRBuffer = [] return { getBuffer (): SSRBuffer { return buffer }, push (item: SSRBufferItem) { const isStringItem = isString(item) if (appendable && isStringItem) { buffer[buffer.length - 1] += item as string } else { buffer.push(item) } appendable = isStringItem if (isPromise(item) || (isArray(item) && item.hasAsync)) { buffer.hasAsync = true } } } } const TRANSLATE_RE = /&(nbsp|amp|quot|lt|gt);/g const NUMSTR_RE = /&#(\d+);/gi export function decodeHtmlEntities (html: string) { const translateDict = { nbsp: ' ', amp: '&', quot: '"', lt: '<', gt: '>' } as const return html.replace(TRANSLATE_RE, function (_, entity: keyof typeof translateDict) { return translateDict[entity] }).replace(NUMSTR_RE, function (_, numStr: string) { const num = parseInt(numStr, 10) return String.fromCharCode(num) }) } /** * helper for NuxtIsland to generate a correct array for scoped data */ export function vforToArray (source: any): any[] { if (isArray(source)) { return source } else if (isString(source)) { return source.split('') } else if (typeof source === 'number') { if (import.meta.dev && !Number.isInteger(source)) { console.warn(`The v-for range expect an integer value but got ${source}.`) } const array = [] for (let i = 0; i < source; i++) { array[i] = i } return array } else if (isObject(source)) { if (source[Symbol.iterator as any]) { return Array.from(source as Iterable, item => item ) } else { const keys = Object.keys(source) const array = new Array(keys.length) for (let i = 0, l = keys.length; i < l; i++) { const key = keys[i] array[i] = source[key] } return array } } return [] } /** * 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, [], 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[] = [], withoutSlots = false) { if (element && element.nodeName) { if (isEndFragment(element)) { return blocks } else if (!isStartFragment(element)) { 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, withoutSlots) } return blocks } function isStartFragment (element: RendererNode) { return element.nodeName === '#comment' && element.nodeValue === '[' } function isEndFragment (element: RendererNode) { return element.nodeName === '#comment' && element.nodeValue === ']' } const SLOT_PROPS_RE = /]*nuxt-ssr-slot-name="([^"]*)" nuxt-ssr-slot-data="([^"]*)"[^/|>]*>/g export function getSlotProps (html: string) { const slotsDivs = html.matchAll(SLOT_PROPS_RE) const data: Record = {} for (const slot of slotsDivs) { const [_, slotName, json] = slot const slotData = destr(decodeHtmlEntities(json)) data[slotName] = slotData } return data }