aboutsummaryrefslogtreecommitdiff
path: root/packages/integrations/solid/src/server.ts
diff options
context:
space:
mode:
authorGravatar github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 2025-06-05 14:25:23 +0000
committerGravatar github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 2025-06-05 14:25:23 +0000
commite586d7d704d475afe3373a1de6ae20d504f79d6d (patch)
tree7e3fa24807cebd48a86bd40f866d792181191ee9 /packages/integrations/solid/src/server.ts
downloadastro-e586d7d704d475afe3373a1de6ae20d504f79d6d.tar.gz
astro-e586d7d704d475afe3373a1de6ae20d504f79d6d.tar.zst
astro-e586d7d704d475afe3373a1de6ae20d504f79d6d.zip
Sync from a8e1c0a7402940e0fc5beef669522b315052df1blatest
Diffstat (limited to 'packages/integrations/solid/src/server.ts')
-rw-r--r--packages/integrations/solid/src/server.ts143
1 files changed, 143 insertions, 0 deletions
diff --git a/packages/integrations/solid/src/server.ts b/packages/integrations/solid/src/server.ts
new file mode 100644
index 000000000..8d66fffe1
--- /dev/null
+++ b/packages/integrations/solid/src/server.ts
@@ -0,0 +1,143 @@
+import type { NamedSSRLoadedRendererValue } from 'astro';
+import {
+ NoHydration,
+ Suspense,
+ createComponent,
+ generateHydrationScript,
+ renderToString,
+ renderToStringAsync,
+ ssr,
+} from 'solid-js/web';
+import { getContext, incrementId } from './context.js';
+import type { RendererContext } from './types.js';
+
+const slotName = (str: string) => str.trim().replace(/[-_]([a-z])/g, (_, w) => w.toUpperCase());
+
+type RenderStrategy = 'sync' | 'async';
+
+async function check(
+ this: RendererContext,
+ Component: any,
+ props: Record<string, any>,
+ children: any,
+) {
+ if (typeof Component !== 'function') return false;
+ if (Component.name === 'QwikComponent') return false;
+ // Svelte component renders fine by Solid as an empty string. The only way to detect
+ // if this isn't a Solid but Svelte component is to unfortunately copy the check
+ // implementation of the Svelte renderer.
+ if (Component.toString().includes('$$payload')) return false;
+
+ // There is nothing particularly special about Solid components. Basically they are just functions.
+ // In general, components from other frameworks (eg, MDX, React, etc.) tend to render as "undefined",
+ // so we take advantage of this trick to decide if this is a Solid component or not.
+
+ let html: string | undefined;
+ try {
+ const result = await renderToStaticMarkup.call(this, Component, props, children, {
+ // The purpose of check() is just to validate that this is a Solid component and not
+ // React, etc. We should render in sync mode which should skip Suspense boundaries
+ // or loading resources like external API calls.
+ renderStrategy: 'sync' as RenderStrategy,
+ });
+ html = result.html;
+ } catch {}
+
+ return typeof html === 'string';
+}
+
+// AsyncRendererComponentFn
+async function renderToStaticMarkup(
+ this: RendererContext,
+ Component: any,
+ props: Record<string, any>,
+ { default: children, ...slotted }: any,
+ metadata?: Record<string, any>,
+) {
+ const ctx = getContext(this.result);
+ const renderId = metadata?.hydrate ? incrementId(ctx) : '';
+ const needsHydrate = metadata?.astroStaticSlot ? !!metadata.hydrate : true;
+ const tagName = needsHydrate ? 'astro-slot' : 'astro-static-slot';
+
+ const renderStrategy = (metadata?.renderStrategy ?? 'async') as RenderStrategy;
+
+ const renderFn = () => {
+ const slots: Record<string, any> = {};
+ for (const [key, value] of Object.entries(slotted)) {
+ const name = slotName(key);
+ slots[name] = ssr(`<${tagName} name="${name}">${value}</${tagName}>`);
+ }
+ // Note: create newProps to avoid mutating `props` before they are serialized
+ const newProps = {
+ ...props,
+ ...slots,
+ // In Solid SSR mode, `ssr` creates the expected structure for `children`.
+ children: children != null ? ssr(`<${tagName}>${children}</${tagName}>`) : children,
+ };
+
+ if (renderStrategy === 'sync') {
+ // Sync Render:
+ // <Component />
+ // This render mode is not exposed directly to the end user. It is only
+ // used in the check() function.
+ return createComponent(Component, newProps);
+ } else {
+ if (needsHydrate) {
+ // Hydrate + Async Render:
+ // <Suspense>
+ // <Component />
+ // </Suspense>
+ return createComponent(Suspense, {
+ get children() {
+ return createComponent(Component, newProps);
+ },
+ });
+ } else {
+ // Static + Async Render
+ // <NoHydration>
+ // <Suspense>
+ // <Component />
+ // </Suspense>
+ // </NoHydration>
+ return createComponent(NoHydration, {
+ get children() {
+ return createComponent(Suspense, {
+ get children() {
+ return createComponent(Component, newProps);
+ },
+ });
+ },
+ });
+ }
+ }
+ };
+
+ const componentHtml =
+ renderStrategy === 'async'
+ ? await renderToStringAsync(renderFn, {
+ renderId,
+ // New setting since Solid 1.8.4 that fixes an errant hydration event appearing in
+ // server only components. Not available in TypeScript types yet.
+ // https://github.com/solidjs/solid/issues/1931
+ // https://github.com/ryansolid/dom-expressions/commit/e09e255ac725fd59195aa0f3918065d4bd974e6b
+ ...({ noScripts: !needsHydrate } as any),
+ })
+ : renderToString(renderFn, { renderId });
+
+ return {
+ attrs: {
+ 'data-solid-render-id': renderId,
+ },
+ html: componentHtml,
+ };
+}
+
+const renderer: NamedSSRLoadedRendererValue = {
+ name: '@astrojs/solid',
+ check,
+ renderToStaticMarkup,
+ supportsAstroStaticSlot: true,
+ renderHydrationScript: () => generateHydrationScript(),
+};
+
+export default renderer;