diff --git a/packages/yew-macro/src/html_tree/html_element.rs b/packages/yew-macro/src/html_tree/html_element.rs index 3770de65111..24d0b7ac894 100644 --- a/packages/yew-macro/src/html_tree/html_element.rs +++ b/packages/yew-macro/src/html_tree/html_element.rs @@ -221,11 +221,13 @@ impl ToTokens for HtmlElement { quote! { ::std::vec![#(#listeners_it),*].into_iter().flatten().collect() } }; + // TODO: if none of the children have possibly None expressions or literals as keys, we can + // compute `VList.fully_keyed` at compile time. let child_list = quote! { - ::yew::virtual_dom::VList{ - key: ::std::option::Option::None, - children: #children, - } + ::yew::virtual_dom::VList::with_children( + #children, + ::std::option::Option::None, + ) }; tokens.extend(match &name { diff --git a/packages/yew-macro/src/html_tree/html_list.rs b/packages/yew-macro/src/html_tree/html_list.rs index a9a75ce93f0..6a88d317e76 100644 --- a/packages/yew-macro/src/html_tree/html_list.rs +++ b/packages/yew-macro/src/html_tree/html_list.rs @@ -77,7 +77,7 @@ impl ToTokens for HtmlList { tokens.extend(quote_spanned! {spanned.span()=> ::yew::virtual_dom::VNode::VList( - ::yew::virtual_dom::VList::new_with_children(#children, #key) + ::yew::virtual_dom::VList::with_children(#children, #key) ) }); } diff --git a/packages/yew/src/html/component/scope.rs b/packages/yew/src/html/component/scope.rs index 4f4eb692ea7..1916f1a94d1 100644 --- a/packages/yew/src/html/component/scope.rs +++ b/packages/yew/src/html/component/scope.rs @@ -172,7 +172,7 @@ impl Scope { ) { let placeholder = { let placeholder: Node = document().create_text_node("").into(); - insert_node(&placeholder, &parent, next_sibling.get()); + insert_node(&placeholder, &parent, next_sibling.get().as_ref()); node_ref.set(Some(placeholder.clone())); VNode::VRef(placeholder) }; diff --git a/packages/yew/src/html/mod.rs b/packages/yew/src/html/mod.rs index 6e761a2f959..efda4d1fb18 100644 --- a/packages/yew/src/html/mod.rs +++ b/packages/yew/src/html/mod.rs @@ -66,7 +66,7 @@ pub type Html = VNode; /// ``` /// ## Relevant examples /// - [Node Refs](https://github.com/yewstack/yew/tree/master/examples/node_refs) -#[derive(Debug, Default, Clone)] +#[derive(Default, Clone)] pub struct NodeRef(Rc>); impl PartialEq for NodeRef { @@ -75,6 +75,16 @@ impl PartialEq for NodeRef { } } +impl std::fmt::Debug for NodeRef { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "NodeRef {{ references: {:?} }}", + self.get().map(|n| crate::utils::print_node(&n)) + ) + } +} + #[derive(PartialEq, Debug, Default, Clone)] struct NodeRefInner { node: Option, diff --git a/packages/yew/src/utils/mod.rs b/packages/yew/src/utils/mod.rs index e2788fbaaab..c9c0c52a8d8 100644 --- a/packages/yew/src/utils/mod.rs +++ b/packages/yew/src/utils/mod.rs @@ -94,3 +94,13 @@ impl IntoIterator for NodeSeq { self.0.into_iter() } } + +/// Print the [web_sys::Node]'s contents as a string for debugging purposes +pub fn print_node(n: &web_sys::Node) -> String { + use wasm_bindgen::JsCast; + + match n.dyn_ref::() { + Some(el) => el.outer_html(), + None => n.text_content().unwrap_or_default(), + } +} diff --git a/packages/yew/src/virtual_dom/mod.rs b/packages/yew/src/virtual_dom/mod.rs index d9c134ed52c..a6ee9d32ee6 100644 --- a/packages/yew/src/virtual_dom/mod.rs +++ b/packages/yew/src/virtual_dom/mod.rs @@ -422,10 +422,10 @@ pub(crate) trait VDiff { ) -> NodeRef; } -pub(crate) fn insert_node(node: &Node, parent: &Element, next_sibling: Option) { +pub(crate) fn insert_node(node: &Node, parent: &Element, next_sibling: Option<&Node>) { match next_sibling { Some(next_sibling) => parent - .insert_before(&node, Some(&next_sibling)) + .insert_before(&node, Some(next_sibling)) .expect("failed to insert tag before next sibling"), None => parent.append_child(node).expect("failed to append child"), }; diff --git a/packages/yew/src/virtual_dom/vcomp.rs b/packages/yew/src/virtual_dom/vcomp.rs index e49f417c462..d74c4cdb46a 100644 --- a/packages/yew/src/virtual_dom/vcomp.rs +++ b/packages/yew/src/virtual_dom/vcomp.rs @@ -99,7 +99,6 @@ impl VComp { } } - #[allow(unused)] pub(crate) fn root_vnode(&self) -> Option + '_> { self.scope.as_ref().and_then(|scope| scope.root_vnode()) } @@ -202,7 +201,7 @@ impl PartialEq for VComp { impl fmt::Debug for VComp { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("VComp") + write!(f, "VComp {{ root: {:?} }}", self.root_vnode().as_deref()) } } diff --git a/packages/yew/src/virtual_dom/vlist.rs b/packages/yew/src/virtual_dom/vlist.rs index edaf899d2f1..a146820a832 100644 --- a/packages/yew/src/virtual_dom/vlist.rs +++ b/packages/yew/src/virtual_dom/vlist.rs @@ -1,18 +1,32 @@ //! This module contains fragments implementation. use super::{Key, VDiff, VNode, VText}; use crate::html::{AnyScope, NodeRef}; -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; use std::ops::{Deref, DerefMut}; use web_sys::Element; /// This struct represents a fragment of the Virtual DOM tree. -#[derive(Clone, Debug, PartialEq, Default)] +#[derive(Clone, Debug, PartialEq)] pub struct VList { - /// The list of children nodes. - pub children: Vec, + /// The list of child [VNode]s + children: Vec, + + /// All [VNode]s in the VList have keys + fully_keyed: bool, + pub key: Option, } +impl Default for VList { + fn default() -> Self { + Self { + children: Default::default(), + key: None, + fully_keyed: true, + } + } +} + impl Deref for VList { type Target = Vec; @@ -23,41 +37,272 @@ impl Deref for VList { impl DerefMut for VList { fn deref_mut(&mut self) -> &mut Self::Target { + // Caller might change the keys of the VList or add unkeyed children. + // Defensively assume they will. + self.fully_keyed = false; + &mut self.children } } +/// Log an operation during tests for debugging purposes +/// Set RUSTFLAGS="--cfg verbose_tests" environment variable to activate. +macro_rules! test_log { + ($fmt:literal, $($arg:expr),* $(,)?) => { + #[cfg(all(feature = "wasm_test", verbose_tests))] + ::wasm_bindgen_test::console_log!(concat!("\t ", $fmt), $($arg),*); + }; +} + +/// Advance the next sibling reference (from right to left) and log it for testing purposes +/// Set RUSTFLAGS="--cfg verbose_tests" environment variable to activate. +macro_rules! advance_next_sibling { + ($current:expr, $advance:expr) => {{ + #[cfg(all(feature = "wasm_test", verbose_tests))] + let current = format!("{:?}", $current); + let next = $advance(); + test_log!("advance next_sibling: {} -> {:?}", current, next); + next + }}; +} + impl VList { - /// Creates a new empty `VList` instance. + /// Creates a new empty [VList] instance. pub fn new() -> Self { Self::default() } - /// Creates a new `VList` instance with children. - pub fn new_with_children(children: Vec, key: Option) -> Self { - VList { children, key } + /// Creates a new [VList] instance with children. + pub fn with_children(children: Vec, key: Option) -> Self { + VList { + fully_keyed: children.iter().all(|ch| ch.has_key()), + children, + key, + } } - /// Add `VNode` child. + /// Add [VNode] child. pub fn add_child(&mut self, child: VNode) { + if self.fully_keyed && !child.has_key() { + self.fully_keyed = false; + } self.children.push(child); } - /// Add multiple `VNode` children. + /// Add multiple [VNode] children. pub fn add_children(&mut self, children: impl IntoIterator) { - self.children.extend(children); + let it = children.into_iter(); + let bound = it.size_hint(); + self.children.reserve(bound.1.unwrap_or(bound.0)); + for ch in it { + self.add_child(ch); + } + } + + /// Recheck, if the all the children have keys. + /// + /// Run this, after modifying the child list that contained only keyed children prior to the + /// mutable dereference. + pub fn recheck_fully_keyed(&mut self) { + self.fully_keyed = self.children.iter().all(|ch| ch.has_key()); + } + + /// Diff and patch unkeyed child lists + fn apply_unkeyed( + parent_scope: &AnyScope, + parent: &Element, + mut next_sibling: NodeRef, + lefts: &mut [VNode], + rights: Vec, + ) -> NodeRef { + let mut diff = lefts.len() as isize - rights.len() as isize; + let mut lefts_it = lefts.iter_mut().rev(); + let mut rights_it = rights.into_iter().rev(); + + macro_rules! apply { + ($l:expr, $r:expr) => { + test_log!("parent={:?}", parent.outer_html()); + next_sibling = advance_next_sibling!(next_sibling, || $l.apply( + parent_scope, + parent, + next_sibling, + $r + )); + }; + } + + // Add missing nodes + while diff > 0 { + let l = lefts_it.next().unwrap(); + test_log!("adding: {:?}", l); + apply!(l, None); + diff -= 1; + } + // Remove extra nodes + while diff < 0 { + let mut r = rights_it.next().unwrap(); + test_log!("removing: {:?}", r); + r.detach(parent); + diff += 1; + } + + for (l, r) in lefts_it.zip(rights_it) { + test_log!("patching: {:?} -> {:?}", r, l); + apply!(l, r.into()); + } + + next_sibling } - fn children_keys(&self, warn: bool) -> HashSet { - let mut hash_set = HashSet::with_capacity(self.children.len()); - for l in self.children.iter() { - if let Some(k) = l.key() { - if !hash_set.insert(k.clone()) && warn { - log::warn!("Key '{}' is not unique in list but must be.", k); + /// Diff and patch fully keyed child lists. + /// + /// Optimized for node addition or removal from either end of the list and small changes in the + /// middle. + fn apply_keyed( + parent_scope: &AnyScope, + parent: &Element, + mut next_sibling: NodeRef, + lefts: &mut [VNode], + rights: Vec, + ) -> NodeRef { + use std::mem::{transmute, MaybeUninit}; + + macro_rules! map_keys { + ($src:expr) => { + $src.iter() + .map(|v| v.key().expect("unkeyed child in fully keyed list")) + .collect::>() + }; + } + let lefts_keys = map_keys!(lefts); + let rights_keys = map_keys!(rights); + + /// Find the first differing key in 2 iterators + fn diff_i<'a, 'b>( + a: impl Iterator, + b: impl Iterator, + ) -> usize { + a.zip(b).take_while(|(a, b)| a == b).count() + } + + // Find first key mismatch from the front + let from_start = diff_i(lefts_keys.iter(), rights_keys.iter()); + + if from_start == std::cmp::min(lefts.len(), rights.len()) { + // No key changes + return Self::apply_unkeyed(parent_scope, parent, next_sibling, lefts, rights); + } + + // Find first key mismatch from the back + let from_end = diff_i( + lefts_keys[from_start..].iter().rev(), + rights_keys[from_start..].iter().rev(), + ); + + macro_rules! apply { + ($l:expr, $r:expr) => { + test_log!("patching: {:?} -> {:?}", $r, $l); + apply!(Some($r) => $l); + }; + ($l:expr) => { + test_log!("adding: {:?}", $l); + apply!(None => $l); + }; + ($ancestor:expr => $l:expr) => { + test_log!("parent={:?}", parent.outer_html()); + next_sibling = advance_next_sibling!( + next_sibling, + || $l.apply(parent_scope, parent, next_sibling, $ancestor) + ); + }; + } + + // We partially deconstruct the rights vector in several steps. + // This can not be done without default swapping or 2 big vector reallocation overhead in + // safe Rust. + let mut rights: Vec> = unsafe { transmute(rights) }; + + // Takes a value from the rights vector. + // + // We guarantee this is only done once because the consumer routine ranges don't overlap. + // We guarantee this is done for all nodes because all ranges are processed. There are no + // early returns (except panics) possible before full vector depletion. + macro_rules! take { + ($src:expr) => { + unsafe { + let mut dst = MaybeUninit::::uninit(); + std::ptr::copy_nonoverlapping($src.as_mut_ptr(), dst.as_mut_ptr(), 1); + dst.assume_init() + } + }; + } + + // Diff matching children at the end + let lefts_to = lefts_keys.len() - from_end; + let rights_to = rights_keys.len() - from_end; + for (l, r) in lefts[lefts_to..] + .iter_mut() + .zip(rights[rights_to..].iter_mut()) + .rev() + { + apply!(l, take!(r)); + } + + // Diff mismatched children in the middle + let mut next: Option<&Key> = None; + let mut rights_diff: HashMap<&Key, (VNode, Option<&Key>)> = + HashMap::with_capacity(rights_to - from_start); + for (k, v) in rights_keys[from_start..rights_to] + .iter() + .zip(rights[from_start..rights_to].iter_mut()) + .rev() + { + rights_diff.insert(k, (take!(v), next.take())); + next = Some(k); + } + next = None; + for (l_k, l) in lefts_keys[from_start..lefts_to] + .iter() + .zip(lefts[from_start..lefts_to].iter_mut()) + .rev() + { + match rights_diff.remove(l_k) { + // Reorder and diff any existing children + Some((r, r_next)) => { + match (r_next, next) { + // If the next sibling was already the same, we don't need to move the node + (Some(r_next), Some(l_next)) if r_next == l_next => (), + _ => { + test_log!("moving as next: {:?}", r); + r.move_before(parent, &next_sibling.get()); + } + } + apply!(l, r); + } + // Add new children + None => { + apply!(l); } } + next = Some(l_k); + } + + // Remove any extra rights + for (_, (mut r, _)) in rights_diff.drain() { + test_log!("removing: {:?}", r); + r.detach(parent); } - hash_set + + // Diff matching children at the start + for (l, r) in lefts[..from_start] + .iter_mut() + .zip(rights[..from_start].iter_mut()) + .rev() + { + apply!(l, take!(r)); + } + + next_sibling } } @@ -88,143 +333,35 @@ impl VDiff for VList { // Without a placeholder the next element becomes first // and corrupts the order of rendering // We use empty text element to stake out a place - let placeholder = VText::new(""); - self.children.push(placeholder.into()); + self.add_child(VText::new("").into()); } - let left_keys = self.children_keys(true); - let lefts_keyed = left_keys.len() == self.children.len(); + let lefts = &mut self.children; + let (rights, rights_fully_keyed) = match ancestor { + // If the ancestor is also a VList, then the "right" list is the previously + // rendered items. + Some(VNode::VList(v)) => (v.children, v.fully_keyed), - let right_keys = if let Some(VNode::VList(vlist)) = &ancestor { - vlist.children_keys(false) - } else { - HashSet::new() - }; - - let mut right_children = match ancestor { - // If the ancestor is also a VList, then the "right" list is the - // previously rendered items. - Some(VNode::VList(vlist)) => vlist.children, // If the ancestor was not a VList, then the "right" list is a single node - Some(vnode) => vec![vnode], - None => vec![], - }; - let rights_keyed = right_keys.len() == right_children.len(); - - // If the existing list and the new list are both properly keyed, - // then move existing list nodes into the new list's order before diffing - if lefts_keyed && rights_keyed { - // Find the intersection of keys to determine which right nodes can be reused - let matched_keys: HashSet<_> = left_keys.intersection(&right_keys).collect(); - - // Detach any right nodes that were not matched with a left node - right_children = right_children - .into_iter() - .filter_map(|mut right| { - if matched_keys.contains(right.key().as_ref().unwrap()) { - Some(right) - } else { - right.detach(parent); - None - } - }) - .collect(); - - // Determine which rights are already in correct order and which - // rights need to be moved in the DOM before being reused - let mut rights_to_move = HashMap::with_capacity(right_children.len()); - let mut matched_lefts = self - .children - .iter() - .filter(|left| matched_keys.contains(left.key().as_ref().unwrap())) - .peekable(); - let mut left = matched_lefts.next(); - - // Note: `filter_map` is used to move rights into `rights_to_move` - #[allow(clippy::unnecessary_filter_map)] - let rights_in_place: Vec<_> = right_children - .into_iter() - .filter_map(|right| { - if right.key() == left.and_then(|l| l.key()) { - left = matched_lefts.next(); - return Some(right); - } else if right.key() == matched_lefts.peek().and_then(|l| l.key()) { - matched_lefts.next(); - left = matched_lefts.next(); - return Some(right); - } - - rights_to_move.insert(right.key().unwrap(), right); - None - }) - .collect(); - - // Move rights into correct order and build `right_children` - right_children = Vec::with_capacity(matched_keys.len()); - let mut matched_lefts = self - .children - .iter() - .filter(|left| matched_keys.contains(left.key().as_ref().unwrap())); - - for right in rights_in_place.into_iter() { - let mut left = matched_lefts.next().unwrap(); - while right.key() != left.key() { - let right_to_move = rights_to_move.remove(&left.key().unwrap()).unwrap(); - right_to_move.move_before(parent, Some(right.first_node())); - right_children.push(right_to_move); - left = matched_lefts.next().unwrap(); - } - right_children.push(right); - } - - for left in matched_lefts { - let right_to_move = rights_to_move.remove(&left.key().unwrap()).unwrap(); - right_to_move.move_before(parent, next_sibling.get()); - right_children.push(right_to_move); + Some(v) => { + let has_key = v.has_key(); + (vec![v], has_key) } - assert!(rights_to_move.is_empty()) - } - - let mut rights = right_children.into_iter().peekable(); - let mut last_next_sibling = NodeRef::default(); - let mut nodes: Vec = self - .children - .iter_mut() - .map(|left| { - let ancestor = rights.next(); - - // Create a new `next_sibling` reference which points to the next `right` or - // the outer list's `next_sibling` if there are no more `rights`. - let new_next_sibling = NodeRef::default(); - if let Some(next_right) = rights.peek() { - new_next_sibling.set(Some(next_right.first_node())); - } else { - new_next_sibling.link(next_sibling.clone()); - } - - // Update the next list item and then link the previous left's `next_sibling` to the - // returned `node` reference so that the previous left has an up-to-date `next_sibling`. - // This is important for rendering a `VComp` because each `VComp` keeps track of its - // `next_sibling` to properly render its children. - let node = left.apply(parent_scope, parent, new_next_sibling.clone(), ancestor); - last_next_sibling.link(node.clone()); - last_next_sibling = new_next_sibling; - node - }) - .collect(); - - // If there are more `rights` than `lefts`, we need to make sure to link the last left's `next_sibling` - // to the outer list's `next_sibling` so that it doesn't point at a `right` that is detached. - last_next_sibling.link(next_sibling); - - // Detach all extra rights - for mut right in rights { - right.detach(parent); - } + // No unkeyed nodes in an empty VList + _ => (vec![], true), + }; + test_log!("lefts: {:?}", lefts); + test_log!("rights: {:?}", rights); - assert!(!nodes.is_empty(), "VList should have at least one child"); - nodes.swap_remove(0) + #[allow(clippy::let_and_return)] + let first = if self.fully_keyed && rights_fully_keyed { + Self::apply_keyed(parent_scope, parent, next_sibling, lefts, rights) + } else { + Self::apply_unkeyed(parent_scope, parent, next_sibling, lefts, rights) + }; + test_log!("result: {:?}", lefts); + first } } diff --git a/packages/yew/src/virtual_dom/vnode.rs b/packages/yew/src/virtual_dom/vnode.rs index 0012fee1a38..c259adfd99e 100644 --- a/packages/yew/src/virtual_dom/vnode.rs +++ b/packages/yew/src/virtual_dom/vnode.rs @@ -35,6 +35,16 @@ impl VNode { } } + /// Returns true if the [VNode] has a key without needlessly cloning the key. + pub fn has_key(&self) -> bool { + match self { + VNode::VComp(vcomp) => vcomp.key.is_some(), + VNode::VList(vlist) => vlist.key.is_some(), + VNode::VRef(_) | VNode::VText(_) => false, + VNode::VTag(vtag) => vtag.key.is_some(), + } + } + /// Returns the first DOM node that is used to designate the position of the virtual DOM node. pub(crate) fn first_node(&self) -> Node { match self { @@ -48,20 +58,16 @@ impl VNode { text_node.clone().into() } VNode::VComp(vcomp) => vcomp.node_ref.get().expect("VComp is not mounted"), - VNode::VList(vlist) => vlist - .children - .get(0) - .expect("VList is not mounted") - .first_node(), + VNode::VList(vlist) => vlist.get(0).expect("VList is not mounted").first_node(), VNode::VRef(node) => node.clone(), } } - pub(crate) fn move_before(&self, parent: &Element, next_sibling: Option) { + pub(crate) fn move_before(&self, parent: &Element, next_sibling: &Option) { match self { VNode::VList(vlist) => { - for node in vlist.children.iter() { - node.move_before(parent, next_sibling.clone()); + for node in vlist.iter() { + node.move_before(parent, next_sibling); } } VNode::VComp(vcomp) => { @@ -70,7 +76,7 @@ impl VNode { .expect("VComp has no root vnode") .move_before(parent, next_sibling); } - _ => super::insert_node(&self.first_node(), parent, next_sibling), + _ => super::insert_node(&self.first_node(), parent, next_sibling.as_ref()), }; } } @@ -118,7 +124,7 @@ impl VDiff for VNode { } ancestor.detach(parent); } - super::insert_node(node, parent, next_sibling.get()); + super::insert_node(node, parent, next_sibling.get().as_ref()); NodeRef::new(node.clone()) } } @@ -176,10 +182,10 @@ impl From for VNode { impl> FromIterator for VNode { fn from_iter>(iter: T) -> Self { - VNode::VList(VList { - key: None, - children: iter.into_iter().map(|n| n.into()).collect(), - }) + VNode::VList(VList::with_children( + iter.into_iter().map(|n| n.into()).collect(), + None, + )) } } @@ -190,7 +196,7 @@ impl fmt::Debug for VNode { VNode::VText(ref vtext) => vtext.fmt(f), VNode::VComp(ref vcomp) => vcomp.fmt(f), VNode::VList(ref vlist) => vlist.fmt(f), - VNode::VRef(ref vref) => vref.fmt(f), + VNode::VRef(ref vref) => write!(f, "VRef ( \"{}\" )", crate::utils::print_node(vref)), } } } diff --git a/packages/yew/src/virtual_dom/vtag.rs b/packages/yew/src/virtual_dom/vtag.rs index 30a99a38857..32b75f97323 100644 --- a/packages/yew/src/virtual_dom/vtag.rs +++ b/packages/yew/src/virtual_dom/vtag.rs @@ -594,14 +594,14 @@ impl VDiff for VTag { } } else { let el = self.create_element(parent); - super::insert_node(&el, parent, Some(ancestor.first_node())); + super::insert_node(&el, parent, Some(&ancestor.first_node())); ancestor.detach(parent); (None, el) } } None => (None, { let el = self.create_element(parent); - super::insert_node(&el, parent, next_sibling.get()); + super::insert_node(&el, parent, next_sibling.get().as_ref()); el }), }; diff --git a/packages/yew/src/virtual_dom/vtext.rs b/packages/yew/src/virtual_dom/vtext.rs index b33c2ef9b5f..a44ba7ac2a0 100644 --- a/packages/yew/src/virtual_dom/vtext.rs +++ b/packages/yew/src/virtual_dom/vtext.rs @@ -10,7 +10,7 @@ use web_sys::{Element, Text as TextNode}; /// A type for a virtual /// [`TextNode`](https://developer.mozilla.org/en-US/docs/Web/API/Document/createTextNode) /// representation. -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct VText { /// Contains a text of the node. pub text: AttrValue, @@ -28,6 +28,20 @@ impl VText { } } +impl std::fmt::Debug for VText { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "VText {{ text: \"{}\", reference: {} }}", + self.text, + match &self.reference { + Some(_) => "Some(...)", + None => "None", + } + ) + } +} + impl VDiff for VText { /// Remove VText from parent. fn detach(&mut self, parent: &Element) { @@ -66,7 +80,7 @@ impl VDiff for VText { } let text_node = document().create_text_node(&self.text); - super::insert_node(&text_node, parent, next_sibling.get()); + super::insert_node(&text_node, parent, next_sibling.get().as_ref()); self.reference = Some(text_node.clone()); NodeRef::new(text_node.into()) }