UNPKG

nuxt

Version:

[![Nuxt banner](./.github/assets/banner.png)](https://nuxt.com)

137 lines (136 loc) 4.8 kB
import { Fragment, Teleport, computed, createStaticVNode, createVNode, defineComponent, getCurrentInstance, h, nextTick, onMounted, ref, watch } from "vue"; import { debounce } from "perfect-debounce"; import { hash } from "ohash"; import { appendResponseHeader } from "h3"; import { useHead } from "@unhead/vue"; import { randomUUID } from "uncrypto"; import { getFragmentHTML, getSlotProps } from "./utils.js"; import { useNuxtApp } from "#app/nuxt"; import { useRequestEvent } from "#app/composables/ssr"; const pKey = "_islandPromises"; const SSR_UID_RE = /nuxt-ssr-component-uid="([^"]*)"/; const UID_ATTR = /nuxt-ssr-component-uid(="([^"]*)")?/; const SLOTNAME_RE = /nuxt-ssr-slot-name="([^"]*)"/g; const SLOT_FALLBACK_RE = /<div nuxt-slot-fallback-start="([^"]*)"[^>]*><\/div>(((?!<div nuxt-slot-fallback-end[^>]*>)[\s\S])*)<div nuxt-slot-fallback-end[^>]*><\/div>/g; let id = 0; const getId = process.client ? () => (id++).toString() : randomUUID; export default defineComponent({ name: "NuxtIsland", props: { name: { type: String, required: true }, props: { type: Object, default: () => void 0 }, context: { type: Object, default: () => ({}) } }, async setup(props, { slots }) { const nuxtApp = useNuxtApp(); const hashId = computed(() => hash([props.name, props.props, props.context])); const instance = getCurrentInstance(); const event = useRequestEvent(); const mounted = ref(false); onMounted(() => { mounted.value = true; }); const ssrHTML = ref(process.client ? getFragmentHTML(instance.vnode?.el ?? null).join("") ?? "<div></div>" : "<div></div>"); const uid = ref(ssrHTML.value.match(SSR_UID_RE)?.[1] ?? randomUUID()); const availableSlots = computed(() => { return [...ssrHTML.value.matchAll(SLOTNAME_RE)].map((m) => m[1]); }); const html = computed(() => { const currentSlots = Object.keys(slots); return ssrHTML.value.replace(SLOT_FALLBACK_RE, (full, slotName, content) => { if (currentSlots.includes(slotName)) { return ""; } return content; }); }); function setUid() { uid.value = ssrHTML.value.match(SSR_UID_RE)?.[1] ?? getId(); } const cHead = ref({ link: [], style: [] }); useHead(cHead); const slotProps = computed(() => { return getSlotProps(ssrHTML.value); }); async function _fetchComponent() { const key2 = `${props.name}_${hashId.value}`; if (nuxtApp.payload.data[key2]) { return nuxtApp.payload.data[key2]; } const url = `/__nuxt_island/${key2}`; if (process.server && process.env.prerender) { appendResponseHeader(event, "x-nitro-prerender", url); } const result = await $fetch(url, { responseType: "json", params: { ...props.context, props: props.props ? JSON.stringify(props.props) : void 0 } }); nuxtApp.payload.data[key2] = { __nuxt_island: { key: key2, ...process.server && process.env.prerender ? {} : { params: { ...props.context, props: props.props ? JSON.stringify(props.props) : void 0 } } }, ...result }; return result; } const key = ref(0); async function fetchComponent() { nuxtApp[pKey] = nuxtApp[pKey] || {}; if (!nuxtApp[pKey][uid.value]) { nuxtApp[pKey][uid.value] = _fetchComponent().finally(() => { delete nuxtApp[pKey][uid.value]; }); } const res = await nuxtApp[pKey][uid.value]; cHead.value.link = res.head.link; cHead.value.style = res.head.style; ssrHTML.value = res.html.replace(UID_ATTR, () => { return `nuxt-ssr-component-uid="${getId()}"`; }); key.value++; if (process.client) { await nextTick(); } setUid(); } if (process.client) { watch(props, debounce(fetchComponent, 100)); } if (process.server || !nuxtApp.isHydrating) { await fetchComponent(); } return () => { const nodes = [createVNode(Fragment, { key: key.value }, [h(createStaticVNode(html.value, 1))])]; if (uid.value && (mounted.value || nuxtApp.isHydrating || process.server)) { for (const slot in slots) { if (availableSlots.value.includes(slot)) { nodes.push(createVNode(Teleport, { to: process.client ? `[nuxt-ssr-component-uid='${uid.value}'] [nuxt-ssr-slot-name='${slot}']` : `uid=${uid.value};slot=${slot}` }, { default: () => (slotProps.value[slot] ?? [void 0]).map((data) => slots[slot]?.(data)) })); } } } return nodes; }; } });