full site update
This commit is contained in:
223
node_modules/astro/dist/runtime/server/render/server-islands.js
generated
vendored
223
node_modules/astro/dist/runtime/server/render/server-islands.js
generated
vendored
@@ -1,5 +1,8 @@
|
||||
import { encryptString } from "../../../core/encryption.js";
|
||||
import { encryptString, generateCspDigest } from "../../../core/encryption.js";
|
||||
import { markHTMLString } from "../escape.js";
|
||||
import { renderChild } from "./any.js";
|
||||
import { createThinHead } from "./astro/head-and-content.js";
|
||||
import { createRenderInstruction } from "./instruction.js";
|
||||
import { renderSlotToString } from "./slot.js";
|
||||
const internalProps = /* @__PURE__ */ new Set([
|
||||
"server:component-path",
|
||||
@@ -10,74 +13,176 @@ const internalProps = /* @__PURE__ */ new Set([
|
||||
function containsServerDirective(props) {
|
||||
return "server:component-directive" in props;
|
||||
}
|
||||
const SCRIPT_RE = /<\/script/giu;
|
||||
const COMMENT_RE = /<!--/gu;
|
||||
const SCRIPT_REPLACER = "<\\/script";
|
||||
const COMMENT_REPLACER = "\\u003C!--";
|
||||
function safeJsonStringify(obj) {
|
||||
return JSON.stringify(obj).replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029").replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/\//g, "\\u002f");
|
||||
return JSON.stringify(obj).replace(SCRIPT_RE, SCRIPT_REPLACER).replace(COMMENT_RE, COMMENT_REPLACER);
|
||||
}
|
||||
function renderServerIsland(result, _displayName, props, slots) {
|
||||
return {
|
||||
async render(destination) {
|
||||
const componentPath = props["server:component-path"];
|
||||
const componentExport = props["server:component-export"];
|
||||
const componentId = result.serverIslandNameMap.get(componentPath);
|
||||
if (!componentId) {
|
||||
throw new Error(`Could not find server component name`);
|
||||
function createSearchParams(componentExport, encryptedProps, slots) {
|
||||
const params = new URLSearchParams();
|
||||
params.set("e", componentExport);
|
||||
params.set("p", encryptedProps);
|
||||
params.set("s", slots);
|
||||
return params;
|
||||
}
|
||||
function isWithinURLLimit(pathname, params) {
|
||||
const url = pathname + "?" + params.toString();
|
||||
const chars = url.length;
|
||||
return chars < 2048;
|
||||
}
|
||||
class ServerIslandComponent {
|
||||
result;
|
||||
props;
|
||||
slots;
|
||||
displayName;
|
||||
hostId;
|
||||
islandContent;
|
||||
componentPath;
|
||||
componentExport;
|
||||
componentId;
|
||||
constructor(result, props, slots, displayName) {
|
||||
this.result = result;
|
||||
this.props = props;
|
||||
this.slots = slots;
|
||||
this.displayName = displayName;
|
||||
}
|
||||
async init() {
|
||||
const content = await this.getIslandContent();
|
||||
if (this.result.cspDestination) {
|
||||
this.result._metadata.extraScriptHashes.push(
|
||||
await generateCspDigest(SERVER_ISLAND_REPLACER, this.result.cspAlgorithm)
|
||||
);
|
||||
const contentDigest = await generateCspDigest(content, this.result.cspAlgorithm);
|
||||
this.result._metadata.extraScriptHashes.push(contentDigest);
|
||||
}
|
||||
return createThinHead();
|
||||
}
|
||||
async render(destination) {
|
||||
const hostId = await this.getHostId();
|
||||
const islandContent = await this.getIslandContent();
|
||||
destination.write(createRenderInstruction({ type: "server-island-runtime" }));
|
||||
destination.write("<!--[if astro]>server-island-start<![endif]-->");
|
||||
for (const name in this.slots) {
|
||||
if (name === "fallback") {
|
||||
await renderChild(destination, this.slots.fallback(this.result));
|
||||
}
|
||||
for (const key2 of Object.keys(props)) {
|
||||
if (internalProps.has(key2)) {
|
||||
delete props[key2];
|
||||
}
|
||||
}
|
||||
destination.write(
|
||||
`<script type="module" data-astro-rerun data-island-id="${hostId}">${islandContent}</script>`
|
||||
);
|
||||
}
|
||||
getComponentPath() {
|
||||
if (this.componentPath) {
|
||||
return this.componentPath;
|
||||
}
|
||||
const componentPath = this.props["server:component-path"];
|
||||
if (!componentPath) {
|
||||
throw new Error(`Could not find server component path`);
|
||||
}
|
||||
this.componentPath = componentPath;
|
||||
return componentPath;
|
||||
}
|
||||
getComponentExport() {
|
||||
if (this.componentExport) {
|
||||
return this.componentExport;
|
||||
}
|
||||
const componentExport = this.props["server:component-export"];
|
||||
if (!componentExport) {
|
||||
throw new Error(`Could not find server component export`);
|
||||
}
|
||||
this.componentExport = componentExport;
|
||||
return componentExport;
|
||||
}
|
||||
async getHostId() {
|
||||
if (!this.hostId) {
|
||||
this.hostId = await crypto.randomUUID();
|
||||
}
|
||||
return this.hostId;
|
||||
}
|
||||
async getIslandContent() {
|
||||
if (this.islandContent) {
|
||||
return this.islandContent;
|
||||
}
|
||||
const componentPath = this.getComponentPath();
|
||||
const componentExport = this.getComponentExport();
|
||||
const componentId = this.result.serverIslandNameMap.get(componentPath);
|
||||
if (!componentId) {
|
||||
throw new Error(`Could not find server component name`);
|
||||
}
|
||||
for (const key2 of Object.keys(this.props)) {
|
||||
if (internalProps.has(key2)) {
|
||||
delete this.props[key2];
|
||||
}
|
||||
destination.write("<!--[if astro]>server-island-start<![endif]-->");
|
||||
const renderedSlots = {};
|
||||
for (const name in slots) {
|
||||
if (name !== "fallback") {
|
||||
const content = await renderSlotToString(result, slots[name]);
|
||||
renderedSlots[name] = content.toString();
|
||||
} else {
|
||||
await renderChild(destination, slots.fallback(result));
|
||||
}
|
||||
}
|
||||
const renderedSlots = {};
|
||||
for (const name in this.slots) {
|
||||
if (name !== "fallback") {
|
||||
const content = await renderSlotToString(this.result, this.slots[name]);
|
||||
renderedSlots[name] = content.toString();
|
||||
}
|
||||
const key = await result.key;
|
||||
const propsEncrypted = await encryptString(key, JSON.stringify(props));
|
||||
const hostId = crypto.randomUUID();
|
||||
const slash = result.base.endsWith("/") ? "" : "/";
|
||||
const serverIslandUrl = `${result.base}${slash}_server-islands/${componentId}${result.trailingSlash === "always" ? "/" : ""}`;
|
||||
destination.write(`<script async type="module" data-island-id="${hostId}">
|
||||
let componentId = ${safeJsonStringify(componentId)};
|
||||
let componentExport = ${safeJsonStringify(componentExport)};
|
||||
let script = document.querySelector('script[data-island-id="${hostId}"]');
|
||||
let data = {
|
||||
componentExport,
|
||||
}
|
||||
const key = await this.result.key;
|
||||
const propsEncrypted = Object.keys(this.props).length === 0 ? "" : await encryptString(key, JSON.stringify(this.props));
|
||||
const hostId = await this.getHostId();
|
||||
const slash = this.result.base.endsWith("/") ? "" : "/";
|
||||
let serverIslandUrl = `${this.result.base}${slash}_server-islands/${componentId}${this.result.trailingSlash === "always" ? "/" : ""}`;
|
||||
const potentialSearchParams = createSearchParams(
|
||||
componentExport,
|
||||
propsEncrypted,
|
||||
safeJsonStringify(renderedSlots)
|
||||
);
|
||||
const useGETRequest = isWithinURLLimit(serverIslandUrl, potentialSearchParams);
|
||||
if (useGETRequest) {
|
||||
serverIslandUrl += "?" + potentialSearchParams.toString();
|
||||
this.result._metadata.extraHead.push(
|
||||
markHTMLString(
|
||||
`<link rel="preload" as="fetch" href="${serverIslandUrl}" crossorigin="anonymous">`
|
||||
)
|
||||
);
|
||||
}
|
||||
const method = useGETRequest ? (
|
||||
// GET request
|
||||
`let response = await fetch('${serverIslandUrl}');`
|
||||
) : (
|
||||
// POST request
|
||||
`let data = {
|
||||
componentExport: ${safeJsonStringify(componentExport)},
|
||||
encryptedProps: ${safeJsonStringify(propsEncrypted)},
|
||||
slots: ${safeJsonStringify(renderedSlots)},
|
||||
};
|
||||
|
||||
let response = await fetch('${serverIslandUrl}', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
if (script) {
|
||||
if(response.status === 200 && response.headers.get('content-type') === 'text/html') {
|
||||
let html = await response.text();
|
||||
|
||||
// Swap!
|
||||
while(script.previousSibling &&
|
||||
script.previousSibling.nodeType !== 8 &&
|
||||
script.previousSibling.data !== '[if astro]>server-island-start<![endif]') {
|
||||
script.previousSibling.remove();
|
||||
}
|
||||
script.previousSibling?.remove();
|
||||
|
||||
let frag = document.createRange().createContextualFragment(html);
|
||||
script.before(frag);
|
||||
});`
|
||||
);
|
||||
this.islandContent = `${method}replaceServerIsland('${hostId}', response);`;
|
||||
return this.islandContent;
|
||||
}
|
||||
}
|
||||
script.remove();
|
||||
}
|
||||
</script>`);
|
||||
}
|
||||
};
|
||||
}
|
||||
export {
|
||||
containsServerDirective,
|
||||
renderServerIsland
|
||||
const renderServerIslandRuntime = () => {
|
||||
return `<script>${SERVER_ISLAND_REPLACER}</script>`;
|
||||
};
|
||||
const SERVER_ISLAND_REPLACER = markHTMLString(
|
||||
`async function replaceServerIsland(id, r) {
|
||||
let s = document.querySelector(\`script[data-island-id="\${id}"]\`);
|
||||
// If there's no matching script, or the request fails then return
|
||||
if (!s || r.status !== 200 || r.headers.get('content-type')?.split(';')[0].trim() !== 'text/html') return;
|
||||
// Load the HTML before modifying the DOM in case of errors
|
||||
let html = await r.text();
|
||||
// Remove any placeholder content before the island script
|
||||
while (s.previousSibling && s.previousSibling.nodeType !== 8 && s.previousSibling.data !== '[if astro]>server-island-start<![endif]')
|
||||
s.previousSibling.remove();
|
||||
s.previousSibling?.remove();
|
||||
// Insert the new HTML
|
||||
s.before(document.createRange().createContextualFragment(html));
|
||||
// Remove the script. Prior to v5.4.2, this was the trick to force rerun of scripts. Keeping it to minimize change to the existing behavior.
|
||||
s.remove();
|
||||
}`.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("//")).join(" ")
|
||||
);
|
||||
export {
|
||||
ServerIslandComponent,
|
||||
containsServerDirective,
|
||||
renderServerIslandRuntime
|
||||
};
|
||||
|
Reference in New Issue
Block a user