@nuxt/nitro-server
Version:
128 lines (127 loc) • 5.31 kB
JavaScript
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 };