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#[allow(missing_docs)]
180#[derive(PartialEq, Clone, Debug)]
181pub enum AttributeOrProperty {
182    // This exists as a workaround to support Rust <1.72
183    // Previous versions of Rust did not See
184    // `AttributeOrProperty::Attribute(AttrValue::Static(_))` as `'static` that html! macro
185    // used, and thus failed with "temporary value dropped while borrowed"
186    //
187    // See: https://github.com/yewstack/yew/pull/3458#discussion_r1350362215
188    Static(&'static str),
189    Attribute(AttrValue),
190    Property(JsValue),
191}
192
193/// A collection of attributes for an element
194#[derive(PartialEq, Clone, Debug)]
195pub enum Attributes {
196    /// Static list of attributes.
197    ///
198    /// Allows optimizing comparison to a simple pointer equality check and reducing allocations,
199    /// if the attributes do not change on a node.
200    Static(&'static [(&'static str, AttributeOrProperty)]),
201
202    /// Static list of attribute keys with possibility to exclude attributes and dynamic attribute
203    /// values.
204    ///
205    /// Allows optimizing comparison to a simple pointer equality check and reducing allocations,
206    /// if the attributes keys do not change on a node.
207    Dynamic {
208        /// Attribute keys. Includes both always set and optional attribute keys.
209        keys: &'static [&'static str],
210
211        /// Attribute values. Matches [keys](Attributes::Dynamic::keys). Optional attributes are
212        /// designated by setting [None].
213        values: Box<[Option<AttributeOrProperty>]>,
214    },
215
216    /// IndexMap is used to provide runtime attribute deduplication in cases where the html! macro
217    /// was not used to guarantee it.
218    IndexMap(Rc<IndexMap<AttrValue, AttributeOrProperty>>),
219}
220
221impl Attributes {
222    /// Construct a default Attributes instance
223    pub fn new() -> Self {
224        Self::default()
225    }
226
227    /// Return iterator over attribute key-value pairs.
228    /// This function is suboptimal and does not inline well. Avoid on hot paths.
229    ///
230    /// This function only returns attributes
231    pub fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = (&'a str, &'a str)> + 'a> {
232        match self {
233            Self::Static(arr) => Box::new(arr.iter().filter_map(|(k, v)| match v {
234                AttributeOrProperty::Attribute(v) => Some((*k, v.as_ref())),
235                AttributeOrProperty::Property(_) => None,
236                AttributeOrProperty::Static(v) => Some((*k, v)),
237            })),
238            Self::Dynamic { keys, values } => {
239                Box::new(keys.iter().zip(values.iter()).filter_map(|(k, v)| match v {
240                    Some(AttributeOrProperty::Attribute(v)) => Some((*k, v.as_ref())),
241                    _ => None,
242                }))
243            }
244            Self::IndexMap(m) => Box::new(m.iter().filter_map(|(k, v)| match v {
245                AttributeOrProperty::Attribute(v) => Some((k.as_ref(), v.as_ref())),
246                _ => None,
247            })),
248        }
249    }
250
251    /// Get a mutable reference to the underlying `IndexMap`.
252    /// If the attributes are stored in the `Vec` variant, it will be converted.
253    pub fn get_mut_index_map(&mut self) -> &mut IndexMap<AttrValue, AttributeOrProperty> {
254        macro_rules! unpack {
255            () => {
256                match self {
257                    Self::IndexMap(m) => Rc::make_mut(m),
258                    // SAFETY: unreachable because we set self to the `IndexMap` variant above.
259                    _ => unsafe { unreachable_unchecked() },
260                }
261            };
262        }
263
264        match self {
265            Self::IndexMap(m) => Rc::make_mut(m),
266            Self::Static(arr) => {
267                *self = Self::IndexMap(Rc::new(
268                    arr.iter().map(|(k, v)| ((*k).into(), v.clone())).collect(),
269                ));
270                unpack!()
271            }
272            Self::Dynamic { keys, values } => {
273                *self = Self::IndexMap(Rc::new(
274                    std::mem::take(values)
275                        .iter_mut()
276                        .zip(keys.iter())
277                        .filter_map(|(v, k)| v.take().map(|v| (AttrValue::from(*k), v)))
278                        .collect(),
279                ));
280                unpack!()
281            }
282        }
283    }
284}
285
286impl From<IndexMap<AttrValue, AttrValue>> for Attributes {
287    fn from(map: IndexMap<AttrValue, AttrValue>) -> Self {
288        let v = map
289            .into_iter()
290            .map(|(k, v)| (k, AttributeOrProperty::Attribute(v)))
291            .collect();
292        Self::IndexMap(Rc::new(v))
293    }
294}
295
296impl From<IndexMap<&'static str, AttrValue>> for Attributes {
297    fn from(v: IndexMap<&'static str, AttrValue>) -> Self {
298        let v = v
299            .into_iter()
300            .map(|(k, v)| (AttrValue::Static(k), (AttributeOrProperty::Attribute(v))))
301            .collect();
302        Self::IndexMap(Rc::new(v))
303    }
304}
305
306impl From<IndexMap<&'static str, JsValue>> for Attributes {
307    fn from(v: IndexMap<&'static str, JsValue>) -> Self {
308        let v = v
309            .into_iter()
310            .map(|(k, v)| (AttrValue::Static(k), (AttributeOrProperty::Property(v))))
311            .collect();
312        Self::IndexMap(Rc::new(v))
313    }
314}
315
316impl Default for Attributes {
317    fn default() -> Self {
318        Self::Static(&[])
319    }
320}