1use std::fmt;
2
3use futures::pin_mut;
4use futures::stream::{Stream, StreamExt};
5use tracing::Instrument;
6
7use crate::html::{BaseComponent, Scope};
8use crate::platform::fmt::BufStream;
9use crate::platform::{LocalHandle, Runtime};
10
11#[cfg(feature = "ssr")]
12pub(crate) mod feat_ssr {
13 #[derive(Default, Clone, Copy)]
18 pub(crate) enum VTagKind {
19 Style,
21 Script,
23 #[default]
24 Other,
26 }
27
28 impl<T: AsRef<str>> From<T> for VTagKind {
29 fn from(value: T) -> Self {
30 let value = value.as_ref();
31 if value.eq_ignore_ascii_case("style") {
32 Self::Style
33 } else if value.eq_ignore_ascii_case("script") {
34 Self::Script
35 } else {
36 Self::Other
37 }
38 }
39 }
40}
41
42#[cfg(feature = "ssr")]
52#[derive(Debug)]
53pub struct LocalServerRenderer<COMP>
54where
55 COMP: BaseComponent,
56{
57 props: COMP::Properties,
58 hydratable: bool,
59}
60
61impl<COMP> Default for LocalServerRenderer<COMP>
62where
63 COMP: BaseComponent<Properties: Default>,
64{
65 fn default() -> Self {
66 Self::with_props(COMP::Properties::default())
67 }
68}
69
70impl<COMP> LocalServerRenderer<COMP>
71where
72 COMP: BaseComponent<Properties: Default>,
73{
74 pub fn new() -> Self {
76 Self::default()
77 }
78}
79
80impl<COMP> LocalServerRenderer<COMP>
81where
82 COMP: BaseComponent,
83{
84 pub fn with_props(props: COMP::Properties) -> Self {
86 Self {
87 props,
88 hydratable: true,
89 }
90 }
91
92 pub fn hydratable(mut self, val: bool) -> Self {
99 self.hydratable = val;
100
101 self
102 }
103
104 pub async fn render(self) -> String {
106 let s = self.render_stream();
107 futures::pin_mut!(s);
108
109 s.collect().await
110 }
111
112 pub async fn render_to_string(self, w: &mut String) {
114 let s = self.render_stream();
115 futures::pin_mut!(s);
116
117 while let Some(m) = s.next().await {
118 w.push_str(&m);
119 }
120 }
121
122 fn render_stream_inner(self) -> impl Stream<Item = String> {
123 let scope = Scope::<COMP>::new(None);
124
125 let outer_span = tracing::Span::current();
126 BufStream::new(move |mut w| async move {
127 let render_span = tracing::debug_span!("render_stream_item");
128 render_span.follows_from(outer_span);
129 scope
130 .render_into_stream(
131 &mut w,
132 self.props.into(),
133 self.hydratable,
134 Default::default(),
135 )
136 .instrument(render_span)
137 .await;
138 })
139 }
140
141 #[tracing::instrument(
146 level = tracing::Level::DEBUG,
147 name = "render_stream",
148 skip(self),
149 fields(hydratable = self.hydratable),
150 )]
151 #[inline(always)]
152 pub fn render_stream(self) -> impl Stream<Item = String> {
153 self.render_stream_inner()
154 }
155}
156
157#[cfg(feature = "ssr")]
164pub struct ServerRenderer<COMP>
165where
166 COMP: BaseComponent,
167{
168 create_props: Box<dyn Send + FnOnce() -> COMP::Properties>,
169 hydratable: bool,
170 rt: Option<Runtime>,
171}
172
173impl<COMP> fmt::Debug for ServerRenderer<COMP>
174where
175 COMP: BaseComponent,
176{
177 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
178 f.debug_struct("ServerRenderer<_>").finish_non_exhaustive()
179 }
180}
181
182impl<COMP> Default for ServerRenderer<COMP>
183where
184 COMP: BaseComponent<Properties: Default>,
185{
186 fn default() -> Self {
187 Self::with_props(Default::default)
188 }
189}
190
191impl<COMP> ServerRenderer<COMP>
192where
193 COMP: BaseComponent<Properties: Default>,
194{
195 pub fn new() -> Self {
197 Self::default()
198 }
199}
200
201impl<COMP> ServerRenderer<COMP>
202where
203 COMP: BaseComponent,
204{
205 pub fn with_props<F>(create_props: F) -> Self
212 where
213 F: 'static + Send + FnOnce() -> COMP::Properties,
214 {
215 Self {
216 create_props: Box::new(create_props),
217 hydratable: true,
218 rt: None,
219 }
220 }
221
222 pub fn with_runtime(mut self, rt: Runtime) -> Self {
224 self.rt = Some(rt);
225
226 self
227 }
228
229 pub fn hydratable(mut self, val: bool) -> Self {
236 self.hydratable = val;
237
238 self
239 }
240
241 pub async fn render(self) -> String {
243 let Self {
244 create_props,
245 hydratable,
246 rt,
247 } = self;
248
249 let (tx, rx) = futures::channel::oneshot::channel();
250 let create_task = move || async move {
251 let props = create_props();
252 let s = LocalServerRenderer::<COMP>::with_props(props)
253 .hydratable(hydratable)
254 .render()
255 .await;
256
257 let _ = tx.send(s);
258 };
259
260 Self::spawn_rendering_task(rt, create_task);
261
262 rx.await.expect("failed to render application")
263 }
264
265 pub async fn render_to_string(self, w: &mut String) {
267 let mut s = self.render_stream();
268
269 while let Some(m) = s.next().await {
270 w.push_str(&m);
271 }
272 }
273
274 #[inline]
275 fn spawn_rendering_task<F, Fut>(rt: Option<Runtime>, create_task: F)
276 where
277 F: 'static + Send + FnOnce() -> Fut,
278 Fut: Future<Output = ()> + 'static,
279 {
280 match rt {
281 Some(m) => m.spawn_pinned(create_task),
283 None => match LocalHandle::try_current() {
284 Some(m) => m.spawn_local(create_task()),
286 None => Runtime::default().spawn_pinned(create_task),
288 },
289 }
290 }
291
292 pub fn render_stream(self) -> impl Send + Stream<Item = String> {
294 let Self {
295 create_props,
296 hydratable,
297 rt,
298 } = self;
299
300 let (tx, rx) = futures::channel::mpsc::unbounded();
301 let create_task = move || async move {
302 let props = create_props();
303 let s = LocalServerRenderer::<COMP>::with_props(props)
304 .hydratable(hydratable)
305 .render_stream();
306 pin_mut!(s);
307
308 while let Some(m) = s.next().await {
309 let _ = tx.unbounded_send(m);
310 }
311 };
312
313 Self::spawn_rendering_task(rt, create_task);
314
315 rx
316 }
317}