stellation_backend/
renderer.rs

1use std::fmt;
2use std::fmt::Write;
3use std::marker::PhantomData;
4use std::rc::Rc;
5
6use bounce::helmet::render_static;
7use stellation_bridge::links::{Link, PhantomLink};
8use stellation_bridge::Bridge;
9use yew::BaseComponent;
10
11use crate::hooks::HeadContents;
12use crate::request::RenderRequest;
13use crate::root::{StellationRoot, StellationRootProps};
14use crate::{html, ServerAppProps};
15
16/// The Stellation Backend Renderer.
17///
18/// This type wraps the [Yew Server Renderer](yew::ServerRenderer) and provides additional features.
19///
20/// # Note
21///
22/// Stellation provides [`BrowserRouter`](yew_router::BrowserRouter) and
23/// [`BounceRoot`](bounce::BounceRoot) to all applications.
24///
25/// Bounce Helmet is also bridged automatically.
26///
27/// You do not need to add them manually.
28pub struct ServerRenderer<COMP, REQ = (), CTX = (), L = PhantomLink>
29where
30    COMP: BaseComponent,
31{
32    request: REQ,
33    bridge: Option<Bridge<L>>,
34    _marker: PhantomData<(COMP, REQ, CTX)>,
35}
36
37impl<COMP, REQ, CTX, L> fmt::Debug for ServerRenderer<COMP, REQ, CTX, L>
38where
39    COMP: BaseComponent,
40{
41    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42        f.write_str("ServerRenderer<_>")
43    }
44}
45
46impl<COMP, REQ, CTX> ServerRenderer<COMP, REQ, CTX>
47where
48    COMP: BaseComponent<Properties = ServerAppProps<CTX, REQ>>,
49{
50    /// Creates a Renderer with specified request.
51    pub fn new(request: REQ) -> ServerRenderer<COMP, REQ, CTX> {
52        ServerRenderer {
53            request,
54            bridge: None,
55            _marker: PhantomData,
56        }
57    }
58}
59
60impl<COMP, REQ, CTX, L> ServerRenderer<COMP, REQ, CTX, L>
61where
62    COMP: BaseComponent<Properties = ServerAppProps<CTX, REQ>>,
63{
64    /// Connects a bridge to the application.
65    pub fn bridge<T>(self, bridge: Bridge<T>) -> ServerRenderer<COMP, REQ, CTX, T> {
66        ServerRenderer {
67            request: self.request,
68            bridge: Some(bridge),
69            _marker: PhantomData,
70        }
71    }
72
73    /// Renders the application.
74    ///
75    /// # Note:
76    ///
77    /// This future is `!Send`.
78    pub async fn render(self) -> String
79    where
80        CTX: 'static,
81        REQ: 'static,
82        L: 'static + Link,
83        REQ: RenderRequest<Context = CTX>,
84    {
85        let Self {
86            bridge, request, ..
87        } = self;
88
89        let mut head_s = String::new();
90        let mut helmet_tags = Vec::new();
91        let mut body_s = String::new();
92
93        let request: Rc<_> = request.into();
94
95        if !request.is_client_only() {
96            let head_contents = HeadContents::new();
97
98            let (reader, writer) = render_static();
99
100            let props = ServerAppProps::from_request(request.clone());
101
102            body_s = yew::LocalServerRenderer::<StellationRoot<COMP, CTX, REQ, L>>::with_props(
103                StellationRootProps {
104                    server_app_props: props,
105                    helmet_writer: writer,
106                    bridge,
107                    head_contents: head_contents.clone(),
108                },
109            )
110            .render()
111            .await;
112
113            helmet_tags.append(&mut reader.render().await);
114            let _ = write!(
115                &mut head_s,
116                r#"<meta name="stellation-mode" content="hydrate">"#
117            );
118
119            head_contents.render_into(&mut head_s).await;
120        }
121
122        html::format_html(request.template(), helmet_tags, head_s, body_s).await
123    }
124}