Skip to main content
This is unreleased documentation for Yew Next version.
For up-to-date documentation, see the latest version on docs.rs.

yew/virtual_dom/
mod.rs

1//! This module contains Yew's implementation of a reactive virtual DOM.
2
3#[doc(hidden)]
4pub mod key;
5#[doc(hidden)]
6pub mod listeners;
7#[doc(hidden)]
8pub mod vcomp;
9#[doc(hidden)]
10pub mod vlist;
11#[doc(hidden)]
12pub mod vnode;
13#[doc(hidden)]
14pub mod vportal;
15#[doc(hidden)]
16pub mod vraw;
17#[doc(hidden)]
18pub mod vsuspense;
19#[doc(hidden)]
20pub mod vtag;
21#[doc(hidden)]
22pub mod vtext;
23
24use std::hint::unreachable_unchecked;
25use std::rc::Rc;
26
27use indexmap::IndexMap;
28use wasm_bindgen::JsValue;
29
30#[doc(inline)]
31pub use self::key::Key;
32#[doc(inline)]
33pub use self::listeners::*;
34#[doc(inline)]
35pub use self::vcomp::{VChild, VComp};
36#[doc(hidden)]
37pub use self::vlist::FullyKeyedState;
38#[doc(inline)]
39pub use self::vlist::VList;
40#[doc(inline)]
41pub use self::vnode::VNode;
42#[doc(inline)]
43pub use self::vportal::VPortal;
44#[doc(inline)]
45pub use self::vraw::VRaw;
46#[doc(inline)]
47pub use self::vsuspense::VSuspense;
48#[doc(inline)]
49pub use self::vtag::VTag;
50#[doc(inline)]
51pub use self::vtext::VText;
52
53/// Attribute value
54pub type AttrValue = implicit_clone::unsync::IString;
55
56#[cfg(any(feature = "ssr", feature = "hydration"))]
57mod feat_ssr_hydration {
58    #[cfg(debug_assertions)]
59    type ComponentName = &'static str;
60    #[cfg(not(debug_assertions))]
61    type ComponentName = std::marker::PhantomData<()>;
62
63    #[cfg(feature = "hydration")]
64    use std::borrow::Cow;
65
66    /// A collectable.
67    ///
68    /// This indicates a kind that can be collected from fragment to be processed at a later time
69    pub enum Collectable {
70        Component(ComponentName),
71        Raw,
72        Suspense,
73    }
74
75    impl Collectable {
76        #[cfg(not(debug_assertions))]
77        #[inline(always)]
78        pub fn for_component<T: 'static>() -> Self {
79            use std::marker::PhantomData;
80            // This suppresses the clippy lint about unused generic.
81            // We inline this function
82            // so the function body is copied to its caller and generics get optimised away.
83            let _comp_type: PhantomData<T> = PhantomData;
84            Self::Component(PhantomData)
85        }
86
87        #[cfg(debug_assertions)]
88        pub fn for_component<T: 'static>() -> Self {
89            let comp_name = std::any::type_name::<T>();
90            Self::Component(comp_name)
91        }
92
93        pub fn open_start_mark(&self) -> &'static str {
94            match self {
95                Self::Component(_) => "<[",
96                Self::Raw => "<#",
97                Self::Suspense => "<?",
98            }
99        }
100
101        pub fn close_start_mark(&self) -> &'static str {
102            match self {
103                Self::Component(_) => "</[",
104                Self::Raw => "</#",
105                Self::Suspense => "</?",
106            }
107        }
108
109        pub fn end_mark(&self) -> &'static str {
110            match self {
111                Self::Component(_) => "]>",
112                Self::Raw => ">",
113                Self::Suspense => ">",
114            }
115        }
116
117        #[cfg(feature = "hydration")]
118        pub fn name(&self) -> Cow<'static, str> {
119            match self {
120                #[cfg(debug_assertions)]
121                Self::Component(m) => format!("Component({m})").into(),
122                #[cfg(not(debug_assertions))]
123                Self::Component(_) => "Component".into(),
124                Self::Raw => "Raw".into(),
125                Self::Suspense => "Suspense".into(),
126            }
127        }
128    }
129}
130
131#[cfg(any(feature = "ssr", feature = "hydration"))]
132pub(crate) use feat_ssr_hydration::*;
133
134#[cfg(feature = "ssr")]
135mod feat_ssr {
136    use std::fmt::Write;
137
138    use super::*;
139    use crate::platform::fmt::BufWriter;
140
141    impl Collectable {
142        pub(crate) fn write_open_tag(&self, w: &mut BufWriter) {
143            let _ = w.write_str("<!--");
144            let _ = w.write_str(self.open_start_mark());
145
146            #[cfg(debug_assertions)]
147            match self {
148                Self::Component(type_name) => {
149                    let _ = w.write_str(type_name);
150                }
151                Self::Raw => {}
152                Self::Suspense => {}
153            }
154
155            let _ = w.write_str(self.end_mark());
156            let _ = w.write_str("-->");
157        }
158
159        pub(crate) fn write_close_tag(&self, w: &mut BufWriter) {
160            let _ = w.write_str("<!--");
161            let _ = w.write_str(self.close_start_mark());
162
163            #[cfg(debug_assertions)]
164            match self {
165                Self::Component(type_name) => {
166                    let _ = w.write_str(type_name);
167                }
168                Self::Raw => {}
169                Self::Suspense => {}
170            }
171
172            let _ = w.write_str(self.end_mark());
173            let _ = w.write_str("-->");
174        }
175    }
176}
177
178/// Defines if the [`Attributes`] is set as element's attribute or property and its value.
179#[expect(missing_docs)]
180#[derive(PartialEq, Clone, Debug)]
181pub enum AttributeOrProperty {
182    Attribute(AttrValue),
183    Property(JsValue),
184}
185
186/// A collection of attributes for an element
187#[derive(PartialEq, Clone, Debug)]
188pub enum Attributes {
189    /// Static list of attributes.
190    ///
191    /// Allows optimizing comparison to a simple pointer equality check and reducing allocations,
192    /// if the attributes do not change on a node.
193    Static(&'static [(&'static str, AttributeOrProperty)]),
194
195    /// Static list of attribute keys with possibility to exclude attributes and dynamic attribute
196    /// values.
197    ///
198    /// Allows optimizing comparison to a simple pointer equality check and reducing allocations,
199    /// if the attributes keys do not change on a node.
200    Dynamic {
201        /// Attribute keys. Includes both always set and optional attribute keys.
202        keys: &'static [&'static str],
203
204        /// Attribute values. Matches [keys](Attributes::Dynamic::keys). Optional attributes are
205        /// designated by setting [None].
206        values: Box<[Option<AttributeOrProperty>]>,
207    },
208
209    /// IndexMap is used to provide runtime attribute deduplication in cases where the html! macro
210    /// was not used to guarantee it.
211    IndexMap(Rc<IndexMap<AttrValue, AttributeOrProperty>>),
212}
213
214impl Attributes {
215    /// Construct a default Attributes instance
216    pub fn new() -> Self {
217        Self::default()
218    }
219
220    /// Return iterator over attribute key-value pairs.
221    /// This function is suboptimal and does not inline well. Avoid on hot paths.
222    ///
223    /// This function only returns attributes
224    pub fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = (&'a str, &'a str)> + 'a> {
225        match self {
226            Self::Static(arr) => Box::new(arr.iter().filter_map(|(k, v)| match v {
227                AttributeOrProperty::Attribute(v) => Some((*k, v.as_ref())),
228                AttributeOrProperty::Property(_) => None,
229            })),
230            Self::Dynamic { keys, values } => {
231                Box::new(keys.iter().zip(values.iter()).filter_map(|(k, v)| match v {
232                    Some(AttributeOrProperty::Attribute(v)) => Some((*k, v.as_ref())),
233                    _ => None,
234                }))
235            }
236            Self::IndexMap(m) => Box::new(m.iter().filter_map(|(k, v)| match v {
237                AttributeOrProperty::Attribute(v) => Some((k.as_ref(), v.as_ref())),
238                _ => None,
239            })),
240        }
241    }
242
243    /// Get a mutable reference to the underlying `IndexMap`.
244    /// If the attributes are stored in the `Vec` variant, it will be converted.
245    pub fn get_mut_index_map(&mut self) -> &mut IndexMap<AttrValue, AttributeOrProperty> {
246        macro_rules! unpack {
247            () => {
248                match self {
249                    Self::IndexMap(m) => Rc::make_mut(m),
250                    // SAFETY: unreachable because we set self to the `IndexMap` variant above.
251                    _ => unsafe { unreachable_unchecked() },
252                }
253            };
254        }
255
256        match self {
257            Self::IndexMap(m) => Rc::make_mut(m),
258            Self::Static(arr) => {
259                *self = Self::IndexMap(Rc::new(
260                    arr.iter().map(|(k, v)| ((*k).into(), v.clone())).collect(),
261                ));
262                unpack!()
263            }
264            Self::Dynamic { keys, values } => {
265                *self = Self::IndexMap(Rc::new(
266                    std::mem::take(values)
267                        .iter_mut()
268                        .zip(keys.iter())
269                        .filter_map(|(v, k)| v.take().map(|v| (AttrValue::from(*k), v)))
270                        .collect(),
271                ));
272                unpack!()
273            }
274        }
275    }
276}
277
278impl From<IndexMap<AttrValue, AttrValue>> for Attributes {
279    fn from(map: IndexMap<AttrValue, AttrValue>) -> Self {
280        let v = map
281            .into_iter()
282            .map(|(k, v)| (k, AttributeOrProperty::Attribute(v)))
283            .collect();
284        Self::IndexMap(Rc::new(v))
285    }
286}
287
288impl From<IndexMap<&'static str, AttrValue>> for Attributes {
289    fn from(v: IndexMap<&'static str, AttrValue>) -> Self {
290        let v = v
291            .into_iter()
292            .map(|(k, v)| (AttrValue::Static(k), (AttributeOrProperty::Attribute(v))))
293            .collect();
294        Self::IndexMap(Rc::new(v))
295    }
296}
297
298impl From<IndexMap<&'static str, JsValue>> for Attributes {
299    fn from(v: IndexMap<&'static str, JsValue>) -> Self {
300        let v = v
301            .into_iter()
302            .map(|(k, v)| (AttrValue::Static(k), (AttributeOrProperty::Property(v))))
303            .collect();
304        Self::IndexMap(Rc::new(v))
305    }
306}
307
308impl Default for Attributes {
309    fn default() -> Self {
310        Self::Static(&[])
311    }
312}