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