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