0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2025-02-03 22:29:08 -05:00
astro/packages/integrations/solid/src/server.ts
Patrick Miller 825ba1a598 [ci] format
2024-01-04 11:38:04 +00:00

131 lines
3.9 KiB
TypeScript

import {
createComponent,
generateHydrationScript,
NoHydration,
renderToString,
renderToStringAsync,
ssr,
Suspense,
} 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;
// 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.
const { html } = 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,
});
return typeof html === 'string';
}
// AsyncRendererComponentFn
async function renderToStaticMarkup(
this: RendererContext,
Component: any,
props: Record<string, any>,
{ default: children, ...slotted }: any,
metadata?: undefined | 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,
};
}
export default {
check,
renderToStaticMarkup,
supportsAstroStaticSlot: true,
renderHydrationScript: () => generateHydrationScript(),
};