sycamore_web/node/dom_render.rs
1use super::*;
2
3/// Render a [`View`] into the DOM.
4/// Alias for [`render_to`] with `parent` being the `<body>` tag.
5pub fn render(view: impl FnOnce() -> View) {
6 render_to(view, &document().body().unwrap());
7}
8
9/// Render a [`View`] under a `parent` node.
10/// For rendering under the `<body>` tag, use [`render`] instead.
11pub fn render_to(view: impl FnOnce() -> View, parent: &web_sys::Node) {
12 // Do not call the destructor function, effectively leaking the scope.
13 let _ = create_root(|| render_in_scope(view, parent));
14}
15
16/// Render a [`View`] under a `parent` node, in a way that can be cleaned up.
17///
18/// This function is intended to be used for injecting an ephemeral sycamore view into a
19/// non-sycamore app (for example, a file upload modal where you want to cancel the upload if the
20/// modal is closed).
21///
22/// It is, however, preferable to have a single call to [`render`] or [`render_to`] at the top
23/// level of your app long-term. For rendering a view that will never be unmounted from the dom,
24/// use [`render_to`] instead. For rendering under the `<body>` tag, use [`render`] instead.
25///
26/// It is expected that this function will be called inside a reactive root, usually created using
27/// [`create_root`].
28pub fn render_in_scope(view: impl FnOnce() -> View, parent: &web_sys::Node) {
29 if is_ssr!() {
30 panic!("`render_in_scope` is not available in SSR mode");
31 } else {
32 IS_HYDRATING.set(false);
33 let nodes = view().nodes;
34 for node in nodes {
35 parent.append_child(node.as_web_sys()).unwrap();
36 }
37 }
38}
39
40/// Render a [`View`] under a `parent` node by reusing existing nodes (client side
41/// hydration).
42///
43/// Alias for [`hydrate_to`] with `parent` being the `<body>` tag.
44/// For rendering without hydration, use [`render`](super::render) instead.
45#[cfg(feature = "hydrate")]
46pub fn hydrate(view: impl FnOnce() -> View) {
47 hydrate_to(view, &document().body().unwrap());
48}
49
50/// Render a [`View`] under a `parent` node by reusing existing nodes (client side
51/// hydration).
52///
53/// For rendering under the `<body>` tag, use [`hydrate`] instead.
54/// For rendering without hydration, use [`render`](super::render) instead.
55#[cfg(feature = "hydrate")]
56pub fn hydrate_to(view: impl FnOnce() -> View, parent: &web_sys::Node) {
57 // Do not call the destructor function, effectively leaking the scope.
58 let _ = create_root(|| hydrate_in_scope(view, parent));
59}
60
61/// Render a [`View`] under a `parent` node, in a way that can be cleaned up.
62///
63/// This function is intended to be used for injecting an ephemeral sycamore view into a
64/// non-sycamore app (for example, a file upload modal where you want to cancel the upload if the
65/// modal is closed).
66///
67/// It is expected that this function will be called inside a reactive root, usually created using
68/// [`create_root`].
69#[cfg(feature = "hydrate")]
70pub fn hydrate_in_scope(view: impl FnOnce() -> View, parent: &web_sys::Node) {
71 is_ssr! {
72 let _ = view;
73 let _ = parent;
74 panic!("`hydrate_in_scope` is not available in SSR mode");
75 }
76 is_not_ssr! {
77 // Get mode from HydrationScript.
78 let mode = js_sys::Reflect::get(&window(), &"__sycamore_ssr_mode".into()).unwrap();
79 let mode = if mode.is_undefined() {
80 SsrMode::Sync
81 } else if mode == "blocking" {
82 SsrMode::Blocking
83 } else if mode == "streaming" {
84 SsrMode::Streaming
85 } else {
86 panic!("invalid SSR mode {mode:?}")
87 };
88
89 // Get all nodes with `data-hk` attribute.
90 let existing_nodes = parent
91 .unchecked_ref::<web_sys::Element>()
92 .query_selector_all("[data-hk]")
93 .unwrap();
94
95 HYDRATE_NODES.with(|nodes| {
96 let mut nodes = nodes.borrow_mut();
97 let len = existing_nodes.length();
98 for i in 0..len {
99 let node = existing_nodes.get(i).unwrap();
100 let hk = node.unchecked_ref::<web_sys::Element>().get_attribute("data-hk").unwrap();
101 let key = HydrationKey::parse(&hk).expect("could not parse hydration key");
102 let node = HydrateNode::from_web_sys(node);
103 nodes.insert(key, node);
104 }
105 });
106
107 IS_HYDRATING.set(true);
108 provide_context(mode);
109 provide_context(HydrationRegistry::new());
110 let nodes = view().nodes;
111 // We need to append `nodes` to the `parent` so that the top level nodes also get properly
112 // hydrated.
113 let mut parent = HydrateNode::from_web_sys(parent.clone());
114 for node in nodes {
115 parent.append_child(node);
116 }
117
118 // Wait until suspense is resolved before setting `IS_HYDRATING` to `false`.
119 #[cfg(not(feature = "suspense"))]
120 IS_HYDRATING.set(false);
121 #[cfg(feature = "suspense")]
122 create_effect(|| {
123 if IS_HYDRATING.get() && !sycamore_futures::use_is_loading_global() {
124 IS_HYDRATING.set(false);
125 }
126 });
127 }
128}