Skip to main content

sycamore_web/node/
mod.rs

1//! Implementation of rendering backend.
2
3use std::fmt;
4use std::num::NonZeroU32;
5
6use crate::*;
7
8is_not_ssr!(
9    mod dom_node;
10    #[cfg(feature = "hydrate")]
11    mod hydrate_node;
12);
13is_ssr!(
14    mod ssr_node;
15);
16mod dom_render;
17mod ssr_render;
18
19// We add this so that we get IDE support in Rust Analyzer.
20#[cfg(rust_analyzer)]
21mod dom_node;
22#[cfg(rust_analyzer)]
23mod hydrate_node;
24
25#[cfg_not_ssr]
26pub use dom_node::*;
27pub use dom_render::*;
28#[cfg_not_ssr]
29#[cfg(feature = "hydrate")]
30pub use hydrate_node::*;
31#[cfg_ssr]
32pub use ssr_node::*;
33pub use ssr_render::*;
34
35/// A trait that should be implemented for anything that represents an HTML node.
36pub trait ViewHtmlNode: ViewNode {
37    /// Create a new HTML element.
38    fn create_element(tag: Cow<'static, str>) -> Self;
39    /// Create a new HTML element with a XML namespace.
40    fn create_element_ns(namespace: &'static str, tag: Cow<'static, str>) -> Self;
41    /// Create a new HTML text node.
42    fn create_text_node(text: Cow<'static, str>) -> Self;
43    /// Create a new HTML text node whose value will be changed dynamically.
44    fn create_dynamic_text_node(text: Cow<'static, str>) -> Self {
45        Self::create_text_node(text)
46    }
47    /// Create a new HTML marker (comment) node.
48    fn create_marker_node() -> Self;
49
50    /// Set an HTML attribute.
51    fn set_attribute(&mut self, name: Cow<'static, str>, value: StringAttribute);
52    /// Set a boolean HTML attribute.
53    fn set_bool_attribute(&mut self, name: Cow<'static, str>, value: BoolAttribute);
54    /// Set a JS property on an element.
55    fn set_property(&mut self, name: Cow<'static, str>, value: MaybeDyn<JsValue>);
56    /// Set an event handler on an element.
57    fn set_event_handler(
58        &mut self,
59        name: Cow<'static, str>,
60        handler: impl FnMut(web_sys::Event) + 'static,
61    );
62    /// Set the inner HTML value of an element.
63    fn set_inner_html(&mut self, inner_html: Cow<'static, str>);
64
65    /// Return the raw web-sys node.
66    fn as_web_sys(&self) -> &web_sys::Node;
67    /// Wrap a raw web-sys node.
68    fn from_web_sys(node: web_sys::Node) -> Self;
69}
70
71/// A trait for unwrapping a type into an `HtmlNode`.
72pub trait AsHtmlNode {
73    fn as_html_node(&mut self) -> &mut HtmlNode;
74}
75
76thread_local! {
77    /// Whether we are in hydration mode or not.
78    pub(crate) static IS_HYDRATING: Cell<bool> = const { Cell::new(false) };
79}
80
81/// Returns whether we are currently hydrating or not.
82pub fn is_hydrating() -> bool {
83    IS_HYDRATING.with(Cell::get)
84}
85
86/// A struct for keeping track of state used for hydration.
87#[derive(Debug, Clone, Copy)]
88pub(crate) struct HydrationRegistry {
89    next_key: Signal<HydrationKey>,
90}
91
92// This is only used when hydrating.
93#[cfg_attr(not(feature = "hydrate"), allow(dead_code))]
94impl HydrationRegistry {
95    pub fn new() -> Self {
96        HydrationRegistry {
97            next_key: create_signal(HydrationKey {
98                suspense: 0,
99                element: 0,
100            }),
101        }
102    }
103
104    /// Get the next hydration key and increment the internal state. This new key will be unique.
105    pub fn next_key(self) -> HydrationKey {
106        let key = self.next_key.get_untracked();
107        self.next_key.set_silent(HydrationKey {
108            suspense: key.suspense,
109            element: key.element + 1,
110        });
111        key
112    }
113
114    /// Run the given function within a suspense scope.
115    ///
116    /// This sets the suspense key to the passed value and resets the element key to 0.
117    pub fn in_suspense_scope<T>(suspense: NonZeroU32, f: impl FnOnce() -> T) -> T {
118        let mut ret = None;
119        create_child_scope(|| {
120            provide_context(HydrationRegistry {
121                next_key: create_signal(HydrationKey {
122                    suspense: suspense.get(),
123                    element: 0,
124                }),
125            });
126            ret = Some(f());
127        });
128        ret.unwrap()
129    }
130}
131
132impl Default for HydrationRegistry {
133    fn default() -> Self {
134        Self::new()
135    }
136}
137
138#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
139pub struct HydrationKey {
140    /// Suspense key, or 0 if not in a suspense boundary.
141    pub suspense: u32,
142    /// Element key.
143    pub element: u32,
144}
145
146impl fmt::Display for HydrationKey {
147    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148        write!(f, "{}.{}", self.suspense, self.element)
149    }
150}
151
152impl HydrationKey {
153    pub fn parse(s: &str) -> Option<Self> {
154        let mut parts = s.split('.');
155        let suspense = parts.next()?.parse().ok()?;
156        let element = parts.next()?.parse().ok()?;
157        Some(HydrationKey { suspense, element })
158    }
159}
160
161#[cfg(test)]
162mod tests {
163    use super::*;
164
165    #[test]
166    fn display_hydration_key() {
167        let key = HydrationKey {
168            suspense: 1,
169            element: 2,
170        };
171        assert_eq!(key.to_string(), "1.2");
172    }
173
174    #[test]
175    fn parse_hydration_key() {
176        assert_eq!(
177            HydrationKey::parse("1.2"),
178            Some(HydrationKey {
179                suspense: 1,
180                element: 2
181            })
182        );
183        assert_eq!(HydrationKey::parse("1"), None);
184    }
185}