perseus/utils/
render.rs

1#[cfg(any(client, doc))]
2use sycamore::utils::render::insert;
3#[cfg(all(not(feature = "hydrate"), any(client, doc)))]
4use sycamore::web::DomNode;
5#[cfg(engine)]
6use sycamore::web::SsrNode;
7use sycamore::{prelude::Scope, view::View};
8
9/// Renders or hydrates the given view to the given node,
10/// depending on feature flags. This will atuomatically handle
11/// proper scoping.
12///
13/// This has the option to force a render by ignoring the initial elements.
14///
15/// **Warning:** if hydration is being used, it is expected that
16/// the given view was created inside a `with_hydration_context()` closure.
17// XXX This is *highly* dependent on internal Sycamore implementation
18// details! (TODO PR for `hydrate_to_with_scope` etc.)
19#[cfg(any(client, doc))]
20#[allow(unused_variables)]
21pub(crate) fn render_or_hydrate(
22    cx: Scope,
23    view: View<crate::template::BrowserNodeType>,
24    parent: web_sys::Element,
25    force_render: bool,
26) {
27    use sycamore::utils::hydrate::{with_hydration_context, with_no_hydration_context};
28
29    #[cfg(feature = "hydrate")]
30    {
31        use sycamore::web::HydrateNode;
32
33        // If we're forcing a proper render, then we'll have to remove existing content
34        if force_render {
35            parent.set_inner_html("");
36        }
37
38        // We need `sycamore::hydrate_to_with_scope()`!
39        // --- Verbatim copy from Sycamore, changed for known scope ---
40        // Get children from parent into a View to set as the initial node value.
41        let mut children = Vec::new();
42        let child_nodes = parent.child_nodes();
43        for i in 0..child_nodes.length() {
44            children.push(child_nodes.get(i).unwrap());
45        }
46        let children = children
47            .into_iter()
48            .map(|x| View::new_node(HydrateNode::from_web_sys(x)))
49            .collect::<Vec<_>>();
50
51        insert(
52            cx,
53            &HydrateNode::from_web_sys(parent.into()),
54            if force_render {
55                with_no_hydration_context(|| view)
56            } else {
57                with_hydration_context(|| view)
58            },
59            if force_render {
60                None
61            } else {
62                Some(View::new_fragment(children))
63            },
64            None,
65            false,
66        );
67    }
68    #[cfg(not(feature = "hydrate"))]
69    {
70        // We have to delete the existing content before we can render the new stuff
71        parent.set_inner_html("");
72        insert(
73            cx,
74            &DomNode::from_web_sys(parent.into()),
75            view,
76            None,
77            None,
78            false,
79        );
80    }
81}
82
83/// Renders the given view to a string in a fallible manner, managing hydration
84/// automatically.
85// XXX This is *highly* dependent on internal Sycamore implementation
86// details!
87#[cfg(engine)]
88pub(crate) fn ssr_fallible<E>(
89    view_fn: impl FnOnce(Scope) -> Result<View<SsrNode>, E>,
90) -> Result<String, E> {
91    use sycamore::web::WriteToString;
92    use sycamore::{prelude::create_scope_immediate, utils::hydrate::with_hydration_context}; // XXX This may become private one day!
93
94    let mut ret = Ok(String::new());
95    create_scope_immediate(|cx| {
96        // Usefully, this wrapper can return anything!
97        let view_res = with_hydration_context(|| view_fn(cx));
98        match view_res {
99            Ok(view) => {
100                let mut view_str = String::new();
101                for node in view.flatten() {
102                    node.write_to_string(&mut view_str);
103                }
104                ret = Ok(view_str);
105            }
106            Err(err) => {
107                ret = Err(err);
108            }
109        }
110    });
111
112    ret
113}