import { AstroError, AstroErrorData } from "../../../core/errors/index.js"; import { markHTMLString } from "../escape.js"; import { extractDirectives, generateHydrateScript } from "../hydration.js"; import { serializeProps } from "../serialize.js"; import { shorthash } from "../shorthash.js"; import { isPromise } from "../util.js"; import { createAstroComponentInstance, isAstroComponentFactory, isAstroComponentInstance, renderAstroTemplateResult, renderTemplate } from "./astro/index.js"; import { Fragment, Renderer, stringifyChunk } from "./common.js"; import { componentIsHTMLElement, renderHTMLElement } from "./dom.js"; import { renderSlots, renderSlotToString } from "./slot.js"; import { formatList, internalSpreadAttributes, renderElement, voidElementNames } from "./util.js"; const rendererAliases = /* @__PURE__ */ new Map([["solid", "solid-js"]]); function guessRenderers(componentUrl) { const extname = componentUrl == null ? void 0 : componentUrl.split(".").pop(); switch (extname) { case "svelte": return ["@astrojs/svelte"]; case "vue": return ["@astrojs/vue"]; case "jsx": case "tsx": return ["@astrojs/react", "@astrojs/preact", "@astrojs/solid-js", "@astrojs/vue (jsx)"]; default: return [ "@astrojs/react", "@astrojs/preact", "@astrojs/solid-js", "@astrojs/vue", "@astrojs/svelte", "@astrojs/lit" ]; } } function isFragmentComponent(Component) { return Component === Fragment; } function isHTMLComponent(Component) { return Component && Component["astro:html"] === true; } const ASTRO_SLOT_EXP = /\<\/?astro-slot\b[^>]*>/g; const ASTRO_STATIC_SLOT_EXP = /\<\/?astro-static-slot\b[^>]*>/g; function removeStaticAstroSlot(html, supportsAstroStaticSlot) { const exp = supportsAstroStaticSlot ? ASTRO_STATIC_SLOT_EXP : ASTRO_SLOT_EXP; return html.replace(exp, ""); } async function renderFrameworkComponent(result, displayName, Component, _props, slots = {}) { var _a, _b, _c; if (!Component && !_props["client:only"]) { throw new Error( `Unable to render ${displayName} because it is ${Component}! Did you forget to import the component or is it possible there is a typo?` ); } const { renderers, clientDirectives } = result; const metadata = { astroStaticSlot: true, displayName }; const { hydration, isPage, props } = extractDirectives(_props, clientDirectives); let html = ""; let attrs = void 0; if (hydration) { metadata.hydrate = hydration.directive; metadata.hydrateArgs = hydration.value; metadata.componentExport = hydration.componentExport; metadata.componentUrl = hydration.componentUrl; } const probableRendererNames = guessRenderers(metadata.componentUrl); const validRenderers = renderers.filter((r) => r.name !== "astro:jsx"); const { children, slotInstructions } = await renderSlots(result, slots); let renderer; if (metadata.hydrate !== "only") { let isTagged = false; try { isTagged = Component && Component[Renderer]; } catch { } if (isTagged) { const rendererName = Component[Renderer]; renderer = renderers.find(({ name }) => name === rendererName); } if (!renderer) { let error; for (const r of renderers) { try { if (await r.ssr.check.call({ result }, Component, props, children)) { renderer = r; break; } } catch (e) { error ??= e; } } if (!renderer && error) { throw error; } } if (!renderer && typeof HTMLElement === "function" && componentIsHTMLElement(Component)) { const output = renderHTMLElement(result, Component, _props, slots); return output; } } else { if (metadata.hydrateArgs) { const passedName = metadata.hydrateArgs; const rendererName = rendererAliases.has(passedName) ? rendererAliases.get(passedName) : passedName; renderer = renderers.find( ({ name }) => name === `@astrojs/${rendererName}` || name === rendererName ); } if (!renderer && validRenderers.length === 1) { renderer = validRenderers[0]; } if (!renderer) { const extname = (_a = metadata.componentUrl) == null ? void 0 : _a.split(".").pop(); renderer = renderers.filter( ({ name }) => name === `@astrojs/${extname}` || name === extname )[0]; } } if (!renderer) { if (metadata.hydrate === "only") { throw new AstroError({ ...AstroErrorData.NoClientOnlyHint, message: AstroErrorData.NoClientOnlyHint.message(metadata.displayName), hint: AstroErrorData.NoClientOnlyHint.hint( probableRendererNames.map((r) => r.replace("@astrojs/", "")).join("|") ) }); } else if (typeof Component !== "string") { const matchingRenderers = validRenderers.filter( (r) => probableRendererNames.includes(r.name) ); const plural = validRenderers.length > 1; if (matchingRenderers.length === 0) { throw new AstroError({ ...AstroErrorData.NoMatchingRenderer, message: AstroErrorData.NoMatchingRenderer.message( metadata.displayName, (_b = metadata == null ? void 0 : metadata.componentUrl) == null ? void 0 : _b.split(".").pop(), plural, validRenderers.length ), hint: AstroErrorData.NoMatchingRenderer.hint( formatList(probableRendererNames.map((r) => "`" + r + "`")) ) }); } else if (matchingRenderers.length === 1) { renderer = matchingRenderers[0]; ({ html, attrs } = await renderer.ssr.renderToStaticMarkup.call( { result }, Component, props, children, metadata )); } else { throw new Error(`Unable to render ${metadata.displayName}! This component likely uses ${formatList(probableRendererNames)}, but Astro encountered an error during server-side rendering. Please ensure that ${metadata.displayName}: 1. Does not unconditionally access browser-specific globals like \`window\` or \`document\`. If this is unavoidable, use the \`client:only\` hydration directive. 2. Does not conditionally return \`null\` or \`undefined\` when rendered on the server. If you're still stuck, please open an issue on GitHub or join us at https://astro.build/chat.`); } } } else { if (metadata.hydrate === "only") { html = await renderSlotToString(result, slots == null ? void 0 : slots.fallback); } else { ({ html, attrs } = await renderer.ssr.renderToStaticMarkup.call( { result }, Component, props, children, metadata )); } } if (renderer && !renderer.clientEntrypoint && renderer.name !== "@astrojs/lit" && metadata.hydrate) { throw new AstroError({ ...AstroErrorData.NoClientEntrypoint, message: AstroErrorData.NoClientEntrypoint.message( displayName, metadata.hydrate, renderer.name ) }); } if (!html && typeof Component === "string") { const Tag = sanitizeElementName(Component); const childSlots = Object.values(children).join(""); const iterable = renderAstroTemplateResult( await renderTemplate`<${Tag}${internalSpreadAttributes(props)}${markHTMLString( childSlots === "" && voidElementNames.test(Tag) ? `/>` : `>${childSlots}` )}` ); html = ""; for await (const chunk of iterable) { html += chunk; } } if (!hydration) { return async function* () { var _a2; if (slotInstructions) { yield* slotInstructions; } if (isPage || (renderer == null ? void 0 : renderer.name) === "astro:jsx") { yield html; } else if (html && html.length > 0) { yield markHTMLString( removeStaticAstroSlot(html, ((_a2 = renderer == null ? void 0 : renderer.ssr) == null ? void 0 : _a2.supportsAstroStaticSlot) ?? false) ); } else { yield ""; } }(); } const astroId = shorthash( ` ${html} ${serializeProps( props, metadata )}` ); const island = await generateHydrateScript( { renderer, result, astroId, props, attrs }, metadata ); let unrenderedSlots = []; if (html) { if (Object.keys(children).length > 0) { for (const key of Object.keys(children)) { let tagName = ((_c = renderer == null ? void 0 : renderer.ssr) == null ? void 0 : _c.supportsAstroStaticSlot) ? !!metadata.hydrate ? "astro-slot" : "astro-static-slot" : "astro-slot"; let expectedHTML = key === "default" ? `<${tagName}>` : `<${tagName} name="${key}">`; if (!html.includes(expectedHTML)) { unrenderedSlots.push(key); } } } } else { unrenderedSlots = Object.keys(children); } const template = unrenderedSlots.length > 0 ? unrenderedSlots.map( (key) => `` ).join("") : ""; island.children = `${html ?? ""}${template}`; if (island.children) { island.props["await-children"] = ""; } async function* renderAll() { if (slotInstructions) { yield* slotInstructions; } yield { type: "directive", hydration, result }; yield markHTMLString(renderElement("astro-island", island, false)); } return renderAll(); } function sanitizeElementName(tag) { const unsafe = /[&<>'"\s]+/g; if (!unsafe.test(tag)) return tag; return tag.trim().split(unsafe)[0].trim(); } async function renderFragmentComponent(result, slots = {}) { const children = await renderSlotToString(result, slots == null ? void 0 : slots.default); if (children == null) { return children; } return markHTMLString(children); } async function renderHTMLComponent(result, Component, _props, slots = {}) { const { slotInstructions, children } = await renderSlots(result, slots); const html = Component({ slots: children }); const hydrationHtml = slotInstructions ? slotInstructions.map((instr) => stringifyChunk(result, instr)).join("") : ""; return markHTMLString(hydrationHtml + html); } function renderComponent(result, displayName, Component, props, slots = {}) { if (isPromise(Component)) { return Promise.resolve(Component).then((Unwrapped) => { return renderComponent(result, displayName, Unwrapped, props, slots); }); } if (isFragmentComponent(Component)) { return renderFragmentComponent(result, slots); } if (isHTMLComponent(Component)) { return renderHTMLComponent(result, Component, props, slots); } if (isAstroComponentFactory(Component)) { return createAstroComponentInstance(result, displayName, Component, props, slots); } return renderFrameworkComponent(result, displayName, Component, props, slots); } function renderComponentToIterable(result, displayName, Component, props, slots = {}) { const renderResult = renderComponent(result, displayName, Component, props, slots); if (isAstroComponentInstance(renderResult)) { return renderResult.render(); } return renderResult; } export { renderComponent, renderComponentToIterable };