1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
|
import {
NoHydration,
Suspense,
createComponent,
generateHydrationScript,
renderToString,
renderToStringAsync,
ssr,
} from 'solid-js/web';
import { getContext, incrementId } from './context.js';
import type { RendererContext } from './types.js';
import type { NamedSSRLoadedRendererValue } from 'astro';
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,
};
}
const renderer: NamedSSRLoadedRendererValue = {
name: '@astrojs/solid',
check,
renderToStaticMarkup,
supportsAstroStaticSlot: true,
renderHydrationScript: () => generateHydrationScript(),
};
export default renderer;
|