230 lines
6.9 KiB
JavaScript
230 lines
6.9 KiB
JavaScript
![]() |
import { AstroJSX, isVNode } from "../../jsx-runtime/index.js";
|
||
|
import {
|
||
|
escapeHTML,
|
||
|
HTMLString,
|
||
|
markHTMLString,
|
||
|
renderComponentToIterable,
|
||
|
renderToString,
|
||
|
spreadAttributes,
|
||
|
voidElementNames
|
||
|
} from "./index.js";
|
||
|
import { HTMLParts } from "./render/common.js";
|
||
|
const ClientOnlyPlaceholder = "astro-client-only";
|
||
|
class Skip {
|
||
|
constructor(vnode) {
|
||
|
this.vnode = vnode;
|
||
|
this.count = 0;
|
||
|
}
|
||
|
increment() {
|
||
|
this.count++;
|
||
|
}
|
||
|
haveNoTried() {
|
||
|
return this.count === 0;
|
||
|
}
|
||
|
isCompleted() {
|
||
|
return this.count > 2;
|
||
|
}
|
||
|
}
|
||
|
Skip.symbol = Symbol("astro:jsx:skip");
|
||
|
let originalConsoleError;
|
||
|
let consoleFilterRefs = 0;
|
||
|
async function renderJSX(result, vnode) {
|
||
|
switch (true) {
|
||
|
case vnode instanceof HTMLString:
|
||
|
if (vnode.toString().trim() === "") {
|
||
|
return "";
|
||
|
}
|
||
|
return vnode;
|
||
|
case typeof vnode === "string":
|
||
|
return markHTMLString(escapeHTML(vnode));
|
||
|
case typeof vnode === "function":
|
||
|
return vnode;
|
||
|
case (!vnode && vnode !== 0):
|
||
|
return "";
|
||
|
case Array.isArray(vnode):
|
||
|
return markHTMLString(
|
||
|
(await Promise.all(vnode.map((v) => renderJSX(result, v)))).join("")
|
||
|
);
|
||
|
}
|
||
|
let skip;
|
||
|
if (vnode.props) {
|
||
|
if (vnode.props[Skip.symbol]) {
|
||
|
skip = vnode.props[Skip.symbol];
|
||
|
} else {
|
||
|
skip = new Skip(vnode);
|
||
|
}
|
||
|
} else {
|
||
|
skip = new Skip(vnode);
|
||
|
}
|
||
|
return renderJSXVNode(result, vnode, skip);
|
||
|
}
|
||
|
async function renderJSXVNode(result, vnode, skip) {
|
||
|
if (isVNode(vnode)) {
|
||
|
switch (true) {
|
||
|
case !vnode.type: {
|
||
|
throw new Error(`Unable to render ${result.pathname} because it contains an undefined Component!
|
||
|
Did you forget to import the component or is it possible there is a typo?`);
|
||
|
}
|
||
|
case vnode.type === Symbol.for("astro:fragment"):
|
||
|
return renderJSX(result, vnode.props.children);
|
||
|
case vnode.type.isAstroComponentFactory: {
|
||
|
let props = {};
|
||
|
let slots = {};
|
||
|
for (const [key, value] of Object.entries(vnode.props ?? {})) {
|
||
|
if (key === "children" || value && typeof value === "object" && value["$$slot"]) {
|
||
|
slots[key === "children" ? "default" : key] = () => renderJSX(result, value);
|
||
|
} else {
|
||
|
props[key] = value;
|
||
|
}
|
||
|
}
|
||
|
const html = markHTMLString(await renderToString(result, vnode.type, props, slots));
|
||
|
return html;
|
||
|
}
|
||
|
case (!vnode.type && vnode.type !== 0):
|
||
|
return "";
|
||
|
case (typeof vnode.type === "string" && vnode.type !== ClientOnlyPlaceholder):
|
||
|
return markHTMLString(await renderElement(result, vnode.type, vnode.props ?? {}));
|
||
|
}
|
||
|
if (vnode.type) {
|
||
|
let extractSlots2 = function(child) {
|
||
|
if (Array.isArray(child)) {
|
||
|
return child.map((c) => extractSlots2(c));
|
||
|
}
|
||
|
if (!isVNode(child)) {
|
||
|
_slots.default.push(child);
|
||
|
return;
|
||
|
}
|
||
|
if ("slot" in child.props) {
|
||
|
_slots[child.props.slot] = [..._slots[child.props.slot] ?? [], child];
|
||
|
delete child.props.slot;
|
||
|
return;
|
||
|
}
|
||
|
_slots.default.push(child);
|
||
|
};
|
||
|
var extractSlots = extractSlots2;
|
||
|
if (typeof vnode.type === "function" && vnode.type["astro:renderer"]) {
|
||
|
skip.increment();
|
||
|
}
|
||
|
if (typeof vnode.type === "function" && vnode.props["server:root"]) {
|
||
|
const output2 = await vnode.type(vnode.props ?? {});
|
||
|
return await renderJSX(result, output2);
|
||
|
}
|
||
|
if (typeof vnode.type === "function") {
|
||
|
if (skip.haveNoTried() || skip.isCompleted()) {
|
||
|
useConsoleFilter();
|
||
|
try {
|
||
|
const output2 = await vnode.type(vnode.props ?? {});
|
||
|
let renderResult;
|
||
|
if (output2 == null ? void 0 : output2[AstroJSX]) {
|
||
|
renderResult = await renderJSXVNode(result, output2, skip);
|
||
|
return renderResult;
|
||
|
} else if (!output2) {
|
||
|
renderResult = await renderJSXVNode(result, output2, skip);
|
||
|
return renderResult;
|
||
|
}
|
||
|
} catch (e) {
|
||
|
if (skip.isCompleted()) {
|
||
|
throw e;
|
||
|
}
|
||
|
skip.increment();
|
||
|
} finally {
|
||
|
finishUsingConsoleFilter();
|
||
|
}
|
||
|
} else {
|
||
|
skip.increment();
|
||
|
}
|
||
|
}
|
||
|
const { children = null, ...props } = vnode.props ?? {};
|
||
|
const _slots = {
|
||
|
default: []
|
||
|
};
|
||
|
extractSlots2(children);
|
||
|
for (const [key, value] of Object.entries(props)) {
|
||
|
if (value["$$slot"]) {
|
||
|
_slots[key] = value;
|
||
|
delete props[key];
|
||
|
}
|
||
|
}
|
||
|
const slotPromises = [];
|
||
|
const slots = {};
|
||
|
for (const [key, value] of Object.entries(_slots)) {
|
||
|
slotPromises.push(
|
||
|
renderJSX(result, value).then((output2) => {
|
||
|
if (output2.toString().trim().length === 0)
|
||
|
return;
|
||
|
slots[key] = () => output2;
|
||
|
})
|
||
|
);
|
||
|
}
|
||
|
await Promise.all(slotPromises);
|
||
|
props[Skip.symbol] = skip;
|
||
|
let output;
|
||
|
if (vnode.type === ClientOnlyPlaceholder && vnode.props["client:only"]) {
|
||
|
output = await renderComponentToIterable(
|
||
|
result,
|
||
|
vnode.props["client:display-name"] ?? "",
|
||
|
null,
|
||
|
props,
|
||
|
slots
|
||
|
);
|
||
|
} else {
|
||
|
output = await renderComponentToIterable(
|
||
|
result,
|
||
|
typeof vnode.type === "function" ? vnode.type.name : vnode.type,
|
||
|
vnode.type,
|
||
|
props,
|
||
|
slots
|
||
|
);
|
||
|
}
|
||
|
if (typeof output !== "string" && Symbol.asyncIterator in output) {
|
||
|
let parts = new HTMLParts();
|
||
|
for await (const chunk of output) {
|
||
|
parts.append(chunk, result);
|
||
|
}
|
||
|
return markHTMLString(parts.toString());
|
||
|
} else {
|
||
|
return markHTMLString(output);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return markHTMLString(`${vnode}`);
|
||
|
}
|
||
|
async function renderElement(result, tag, { children, ...props }) {
|
||
|
return markHTMLString(
|
||
|
`<${tag}${spreadAttributes(props)}${markHTMLString(
|
||
|
(children == null || children == "") && voidElementNames.test(tag) ? `/>` : `>${children == null ? "" : await renderJSX(result, prerenderElementChildren(tag, children))}</${tag}>`
|
||
|
)}`
|
||
|
);
|
||
|
}
|
||
|
function prerenderElementChildren(tag, children) {
|
||
|
if (typeof children === "string" && (tag === "style" || tag === "script")) {
|
||
|
return markHTMLString(children);
|
||
|
} else {
|
||
|
return children;
|
||
|
}
|
||
|
}
|
||
|
function useConsoleFilter() {
|
||
|
consoleFilterRefs++;
|
||
|
if (!originalConsoleError) {
|
||
|
originalConsoleError = console.error;
|
||
|
try {
|
||
|
console.error = filteredConsoleError;
|
||
|
} catch (error) {
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
function finishUsingConsoleFilter() {
|
||
|
consoleFilterRefs--;
|
||
|
}
|
||
|
function filteredConsoleError(msg, ...rest) {
|
||
|
if (consoleFilterRefs > 0 && typeof msg === "string") {
|
||
|
const isKnownReactHookError = msg.includes("Warning: Invalid hook call.") && msg.includes("https://reactjs.org/link/invalid-hook-call");
|
||
|
if (isKnownReactHookError)
|
||
|
return;
|
||
|
}
|
||
|
originalConsoleError(msg, ...rest);
|
||
|
}
|
||
|
export {
|
||
|
renderJSX
|
||
|
};
|