UNPKG

@nuxt/nitro-server

Version:

Nitro server integration for Nuxt

128 lines (127 loc) 5.31 kB
import { appRootTag } from "#internal/nuxt.config.mjs"; //#region src/runtime/utils/renderer/islands.ts const ROOT_NODE_REGEX = new RegExp(`^<${appRootTag}[^>]*>([\\s\\S]*)<\\/${appRootTag}>$`); /** * remove the root node from the html body */ function getServerComponentHTML(body) { return body.match(ROOT_NODE_REGEX)?.[1] || body; } const SSR_SLOT_TELEPORT_MARKER = /^uid=([^;]*);slot=(.*)$/; const SSR_CLIENT_TELEPORT_MARKER = /^uid=([^;]*);client=(.*)$/; const SSR_CLIENT_SLOT_MARKER = /^island-slot=([^;]*);(.*)$/; function getSlotIslandResponse(ssrContext) { if (!ssrContext.islandContext || !Object.keys(ssrContext.islandContext.slots).length) return; const response = {}; for (const [name, slot] of Object.entries(ssrContext.islandContext.slots)) response[name] = { ...slot, fallback: ssrContext.teleports?.[`island-fallback=${name}`] }; return response; } function getClientIslandResponse(ssrContext) { if (!ssrContext.islandContext || !Object.keys(ssrContext.islandContext.components).length) return; const response = {}; for (const [clientUid, component] of Object.entries(ssrContext.islandContext.components)) { let html = ssrContext.teleports?.[clientUid]?.replaceAll("<!--teleport start anchor-->", "") || ""; if (!html && ssrContext.teleports) for (const [key, value] of Object.entries(ssrContext.teleports)) { const [, , componentUid] = key.match(SSR_CLIENT_TELEPORT_MARKER) ?? []; if (componentUid === clientUid) { html = value.replaceAll("<!--teleport start anchor-->", ""); break; } } response[clientUid] = { ...component, html, slots: getComponentSlotTeleport(clientUid, ssrContext.teleports ?? {}) }; } return response; } function getComponentSlotTeleport(clientUid, teleports) { const entries = Object.entries(teleports); const slots = {}; for (const [key, value] of entries) { const match = key.match(SSR_CLIENT_SLOT_MARKER); if (match) { const [, id, slot] = match; if (!slot || clientUid !== id) continue; slots[slot] = value; } } return slots; } const ISLAND_TELEPORT_RELOCATE_SCRIPT = `(()=>{for(const t of document.querySelectorAll('template[data-island-uid]')){const u=t.getAttribute('data-island-uid'),s=t.getAttribute('data-island-slot'),c=t.getAttribute('data-island-component'),a=document.querySelector('[data-island-uid="'+u+'"]'+(s!==null?'[data-island-slot="'+s+'"]':'[data-island-component="'+c+'"]'));if(a){a.insertBefore(t.content,a.firstChild)}t.remove()}})()`; /** * Emit each island teleport as an inert `<template>` keyed by its anchor, * followed by a relocation script that moves the content into place before * hydration. Returns an empty string when there are no island teleports. */ function renderStreamedIslandTeleports(ssrContext, nonceAttr = "") { const { teleports, islandContext } = ssrContext; if (islandContext || !teleports) return ""; let templates = ""; for (const key in teleports) { const matchClientComp = key.match(SSR_CLIENT_TELEPORT_MARKER); if (matchClientComp) { const [, uid, clientId] = matchClientComp; if (!uid || !clientId) continue; templates += `<template data-island-uid="${uid}" data-island-component="${clientId}">${teleports[key]}</template>`; continue; } const matchSlot = key.match(SSR_SLOT_TELEPORT_MARKER); if (matchSlot) { const [, uid, slot] = matchSlot; if (!uid || !slot) continue; templates += `<template data-island-uid="${uid}" data-island-slot="${slot}">${teleports[key]}</template>`; } } if (!templates) return ""; return templates + `<script${nonceAttr}>${ISLAND_TELEPORT_RELOCATE_SCRIPT}<\/script>`; } const ISLAND_TELEPORT_ANCHOR_RE = / data-island-uid="([^"]*)" data-island-(component|slot)="([^"]*)"[^>]*>/g; function replaceIslandTeleports(ssrContext, html) { const { teleports, islandContext } = ssrContext; if (islandContext || !teleports) return html; const contentsByAnchor = /* @__PURE__ */ new Map(); const uids = /* @__PURE__ */ new Set(); for (const key in teleports) { const matchClientComp = key.match(SSR_CLIENT_TELEPORT_MARKER); if (matchClientComp) { const [, uid, clientId] = matchClientComp; if (!uid || !clientId) continue; contentsByAnchor.set(`${uid};component;${clientId}`, teleports[key]); uids.add(uid); continue; } const matchSlot = key.match(SSR_SLOT_TELEPORT_MARKER); if (matchSlot) { const [, uid, slot] = matchSlot; if (!uid || !slot) continue; contentsByAnchor.set(`${uid};slot;${slot}`, teleports[key]); uids.add(uid); } } if (!contentsByAnchor.size) return html; const stitch = (html) => { const anchorRE = new RegExp(ISLAND_TELEPORT_ANCHOR_RE); let out = ""; let cursor = 0; let m; while (contentsByAnchor.size && (m = anchorRE.exec(html))) { if (!uids.has(m[1])) continue; const anchor = `${m[1]};${m[2]};${m[3]}`; const content = contentsByAnchor.get(anchor); if (content === void 0) continue; contentsByAnchor.delete(anchor); const end = m.index + m[0].length; out += html.slice(cursor, end) + stitch(content); cursor = end; } return cursor ? out + html.slice(cursor) : html; }; return stitch(html); } //#endregion export { getClientIslandResponse, getComponentSlotTeleport, getServerComponentHTML, getSlotIslandResponse, renderStreamedIslandTeleports, replaceIslandTeleports };