1
0
Эх сурвалжийг харах

separate mount information from VNodes

Evan Almloff 1 жил өмнө
parent
commit
aed29b1dec

+ 0 - 615
packages/core/src/create.rs

@@ -1,615 +0,0 @@
-use crate::innerlude::{ElementPath, ElementRef, VComponent, VPlaceholder, VText, WriteMutations};
-use crate::nodes::VNode;
-use crate::nodes::{DynamicNode, TemplateNode};
-use crate::virtual_dom::VirtualDom;
-use crate::{AttributeValue, ElementId, RenderReturn, ScopeId, Template};
-use std::cell::Cell;
-use std::iter::Peekable;
-use TemplateNode::*;
-
-#[cfg(debug_assertions)]
-fn sort_bfs(paths: &[&'static [u8]]) -> Vec<(usize, &'static [u8])> {
-    let mut with_indecies = paths.iter().copied().enumerate().collect::<Vec<_>>();
-    with_indecies.sort_unstable_by(|(_, a), (_, b)| {
-        let mut a = a.iter();
-        let mut b = b.iter();
-        loop {
-            match (a.next(), b.next()) {
-                (Some(a), Some(b)) => {
-                    if a != b {
-                        return a.cmp(b);
-                    }
-                }
-                // The shorter path goes first
-                (None, Some(_)) => return std::cmp::Ordering::Less,
-                (Some(_), None) => return std::cmp::Ordering::Greater,
-                (None, None) => return std::cmp::Ordering::Equal,
-            }
-        }
-    });
-    with_indecies
-}
-
-#[test]
-#[cfg(debug_assertions)]
-fn sorting() {
-    let r: [(usize, &[u8]); 5] = [
-        (0, &[0, 1]),
-        (1, &[0, 2]),
-        (2, &[1, 0]),
-        (3, &[1, 0, 1]),
-        (4, &[1, 2]),
-    ];
-    assert_eq!(
-        sort_bfs(&[&[0, 1,], &[0, 2,], &[1, 0,], &[1, 0, 1,], &[1, 2,],]),
-        r
-    );
-    let r: [(usize, &[u8]); 6] = [
-        (0, &[0]),
-        (1, &[0, 1]),
-        (2, &[0, 1, 2]),
-        (3, &[1]),
-        (4, &[1, 2]),
-        (5, &[2]),
-    ];
-    assert_eq!(
-        sort_bfs(&[&[0], &[0, 1], &[0, 1, 2], &[1], &[1, 2], &[2],]),
-        r
-    );
-}
-
-impl VirtualDom {
-    /// Create a new template [`VNode`] and write it to the [`Mutations`] buffer.
-    ///
-    /// This method pushes the ScopeID to the internal scopestack and returns the number of nodes created.
-    pub(crate) fn create_scope(
-        &mut self,
-        scope: ScopeId,
-        template: &VNode,
-        to: &mut impl WriteMutations,
-    ) -> usize {
-        self.runtime.scope_stack.borrow_mut().push(scope);
-        let nodes = self.create(template, to);
-        self.runtime.scope_stack.borrow_mut().pop();
-        nodes
-    }
-
-    /// Create this template and write its mutations
-    pub(crate) fn create(&mut self, node: &VNode, to: &mut impl WriteMutations) -> usize {
-        // check for a overridden template
-        #[cfg(debug_assertions)]
-        {
-            let (path, byte_index) = node.template.get().name.rsplit_once(':').unwrap();
-            if let Some(template) = self
-                .templates
-                .get(path)
-                .and_then(|map| map.get(&byte_index.parse().unwrap()))
-            {
-                node.template.set(*template);
-
-                // Initialize the root nodes slice if it was changed
-                let mut nodes_mut = node.root_ids.borrow_mut();
-                let len = node.template.get().roots.len();
-                if nodes_mut.len() != len {
-                    *nodes_mut = vec![ElementId::default(); len].into_boxed_slice();
-                };
-            }
-        };
-
-        // The best renderers will have templates prehydrated and registered
-        // Just in case, let's create the template using instructions anyways
-        self.register_template(node.template.get(), to);
-
-        // Walk the roots, creating nodes and assigning IDs
-        // nodes in an iterator of ((dynamic_node_index, sorted_index), path)
-        // todo: adjust dynamic nodes to be in the order of roots and then leaves (ie BFS)
-        #[cfg(not(debug_assertions))]
-        let (mut attrs, mut nodes) = (
-            node.template
-                .get()
-                .attr_paths
-                .iter()
-                .copied()
-                .enumerate()
-                .peekable(),
-            node.template
-                .get()
-                .node_paths
-                .iter()
-                .copied()
-                .enumerate()
-                .map(|(i, path)| ((i, i), path))
-                .peekable(),
-        );
-        // If this is a debug build, we need to check that the paths are in the correct order because hot reloading can cause scrambled states
-
-        #[cfg(debug_assertions)]
-        let (attrs_sorted, nodes_sorted) = {
-            (
-                sort_bfs(node.template.get().attr_paths),
-                sort_bfs(node.template.get().node_paths),
-            )
-        };
-        #[cfg(debug_assertions)]
-        let (mut attrs, mut nodes) = {
-            (
-                attrs_sorted.into_iter().peekable(),
-                nodes_sorted
-                    .iter()
-                    .copied()
-                    .enumerate()
-                    .map(|(i, (id, path))| ((id, i), path))
-                    .peekable(),
-            )
-        };
-
-        node.template
-            .get()
-            .roots
-            .iter()
-            .enumerate()
-            .map(|(idx, root)| match root {
-                DynamicText { id } | Dynamic { id } => {
-                    nodes.next().unwrap();
-                    self.write_dynamic_root(node, *id, to)
-                }
-                Element { .. } => {
-                    #[cfg(not(debug_assertions))]
-                    let id = self.write_element_root(node, idx, &mut attrs, &mut nodes, &[], to);
-                    #[cfg(debug_assertions)]
-                    let id = self.write_element_root(
-                        node,
-                        idx,
-                        &mut attrs,
-                        &mut nodes,
-                        &nodes_sorted,
-                        to,
-                    );
-                    id
-                }
-                Text { .. } => self.write_static_text_root(node, idx, to),
-            })
-            .sum()
-    }
-
-    fn write_static_text_root(
-        &mut self,
-        node: &VNode,
-        idx: usize,
-        to: &mut impl WriteMutations,
-    ) -> usize {
-        // Simply just load the template root, no modifications needed
-        self.load_template_root(node, idx, to);
-
-        // Text producs just one node on the stack
-        1
-    }
-
-    fn write_dynamic_root(
-        &mut self,
-        template: &VNode,
-        idx: usize,
-        to: &mut impl WriteMutations,
-    ) -> usize {
-        use DynamicNode::*;
-        match &template.dynamic_nodes[idx] {
-            node @ Component { .. } | node @ Fragment(_) => {
-                let template_ref = ElementRef {
-                    path: ElementPath {
-                        path: template.template.get().node_paths[idx],
-                    },
-                    element: template.clone(),
-                };
-                self.create_dynamic_node(&template_ref, node, to)
-            }
-            Placeholder(VPlaceholder { id, parent }) => {
-                let template_ref = ElementRef {
-                    path: ElementPath {
-                        path: template.template.get().node_paths[idx],
-                    },
-                    element: template.clone(),
-                };
-                *parent.borrow_mut() = Some(template_ref);
-                let id = self.set_slot(id);
-                to.create_placeholder(id);
-                1
-            }
-            Text(VText { id, value }) => {
-                let id = self.set_slot(id);
-                to.create_text_node(value, id);
-                1
-            }
-        }
-    }
-
-    /// We write all the descendent data for this element
-    ///
-    /// Elements can contain other nodes - and those nodes can be dynamic or static
-    ///
-    /// We want to make sure we write these nodes while on top of the root
-    fn write_element_root(
-        &mut self,
-        template: &VNode,
-        root_idx: usize,
-        dynamic_attrs: &mut Peekable<impl Iterator<Item = (usize, &'static [u8])>>,
-        dynamic_nodes_iter: &mut Peekable<impl Iterator<Item = ((usize, usize), &'static [u8])>>,
-        dynamic_nodes: &[(usize, &'static [u8])],
-        to: &mut impl WriteMutations,
-    ) -> usize {
-        // Load the template root and get the ID for the node on the stack
-        let root_on_stack = self.load_template_root(template, root_idx, to);
-
-        // Write all the attributes below this root
-        self.write_attrs_on_root(dynamic_attrs, root_idx as u8, root_on_stack, template, to);
-
-        // Load in all of the placeholder or dynamic content under this root too
-        self.load_placeholders(
-            dynamic_nodes_iter,
-            dynamic_nodes,
-            root_idx as u8,
-            template,
-            to,
-        );
-
-        1
-    }
-
-    /// Load all of the placeholder nodes for descendents of this root node
-    ///
-    /// ```rust, ignore
-    /// rsx! {
-    ///     div {
-    ///         // This is a placeholder
-    ///         some_value,
-    ///
-    ///         // Load this too
-    ///         "{some_text}"
-    ///     }
-    /// }
-    /// ```
-    #[allow(unused)]
-    fn load_placeholders(
-        &mut self,
-        dynamic_nodes_iter: &mut Peekable<impl Iterator<Item = ((usize, usize), &'static [u8])>>,
-        dynamic_nodes: &[(usize, &'static [u8])],
-        root_idx: u8,
-        template: &VNode,
-        to: &mut impl WriteMutations,
-    ) {
-        let (start, end) = match collect_dyn_node_range(dynamic_nodes_iter, root_idx) {
-            Some((a, b)) => (a, b),
-            None => return,
-        };
-
-        // If hot reloading is enabled, we need to map the sorted index to the original index of the dynamic node. If it is disabled, we can just use the sorted index
-        #[cfg(not(debug_assertions))]
-        let reversed_iter = (start..=end).rev();
-        #[cfg(debug_assertions)]
-        let reversed_iter = (start..=end)
-            .rev()
-            .map(|sorted_index| dynamic_nodes[sorted_index].0);
-
-        for idx in reversed_iter {
-            let boundary_ref = ElementRef {
-                path: ElementPath {
-                    path: template.template.get().node_paths[idx],
-                },
-                element: template.clone(),
-            };
-            let m = self.create_dynamic_node(&boundary_ref, &template.dynamic_nodes[idx], to);
-            if m > 0 {
-                // The path is one shorter because the top node is the root
-                let path = &template.template.get().node_paths[idx][1..];
-                to.replace_placeholder_with_nodes(path, m);
-            }
-        }
-    }
-
-    fn write_attrs_on_root(
-        &mut self,
-        attrs: &mut Peekable<impl Iterator<Item = (usize, &'static [u8])>>,
-        root_idx: u8,
-        root: ElementId,
-        node: &VNode,
-        to: &mut impl WriteMutations,
-    ) {
-        while let Some((mut attr_id, path)) =
-            attrs.next_if(|(_, p)| p.first().copied() == Some(root_idx))
-        {
-            let id = self.assign_static_node_as_dynamic(path, root, to);
-
-            loop {
-                self.write_attribute(node, attr_id, &node.dynamic_attrs[attr_id], id, to);
-
-                // Only push the dynamic attributes forward if they match the current path (same element)
-                match attrs.next_if(|(_, p)| *p == path) {
-                    Some((next_attr_id, _)) => attr_id = next_attr_id,
-                    None => break,
-                }
-            }
-        }
-    }
-
-    fn write_attribute(
-        &mut self,
-        template: &VNode,
-        idx: usize,
-        attribute: &crate::Attribute,
-        id: ElementId,
-        to: &mut impl WriteMutations,
-    ) {
-        // Make sure we set the attribute's associated id
-        attribute.mounted_element.set(id);
-
-        // Safety: we promise not to re-alias this text later on after committing it to the mutation
-        let unbounded_name: &str = attribute.name;
-
-        match &attribute.value {
-            AttributeValue::Listener(_) => {
-                let path = &template.template.get().attr_paths[idx];
-                let element_ref = ElementRef {
-                    path: ElementPath { path },
-                    element: template.clone(),
-                };
-                self.elements[id.0] = Some(element_ref);
-                to.create_event_listener(&unbounded_name[2..], id);
-            }
-            _ => {
-                let unbounded_value = &attribute.value;
-
-                to.set_attribute(unbounded_name, attribute.namespace, unbounded_value, id);
-            }
-        }
-    }
-
-    fn load_template_root(
-        &mut self,
-        template: &VNode,
-        root_idx: usize,
-        to: &mut impl WriteMutations,
-    ) -> ElementId {
-        // Get an ID for this root since it's a real root
-        let this_id = self.next_element();
-        template.root_ids.borrow_mut()[root_idx] = this_id;
-
-        to.load_template(template.template.get().name, root_idx, this_id);
-
-        this_id
-    }
-
-    /// We have some dynamic attributes attached to a some node
-    ///
-    /// That node needs to be loaded at runtime, so we need to give it an ID
-    ///
-    /// If the node in question is on the stack, we just return that ID
-    ///
-    /// If the node is not on the stack, we create a new ID for it and assign it
-    fn assign_static_node_as_dynamic(
-        &mut self,
-        path: &'static [u8],
-        this_id: ElementId,
-        to: &mut impl WriteMutations,
-    ) -> ElementId {
-        if path.len() == 1 {
-            return this_id;
-        }
-
-        // if attribute is on a root node, then we've already created the element
-        // Else, it's deep in the template and we should create a new id for it
-        let id = self.next_element();
-
-        to.assign_node_id(&path[1..], id);
-
-        id
-    }
-
-    /// Insert a new template into the VirtualDom's template registry
-    pub(crate) fn register_template_first_byte_index(&mut self, mut template: Template) {
-        // First, make sure we mark the template as seen, regardless if we process it
-        let (path, _) = template.name.rsplit_once(':').unwrap();
-        if let Some((_, old_template)) = self
-            .templates
-            .entry(path)
-            .or_default()
-            .iter_mut()
-            .min_by_key(|(byte_index, _)| **byte_index)
-        {
-            // the byte index of the hot reloaded template could be different
-            template.name = old_template.name;
-            *old_template = template;
-        } else {
-            // This is a template without any current instances
-            self.templates
-                .entry(path)
-                .or_default()
-                .insert(usize::MAX, template);
-        }
-
-        // If it's all dynamic nodes, then we don't need to register it
-        if !template.is_completely_dynamic() {
-            self.queued_templates.push(template);
-        }
-    }
-
-    /// Insert a new template into the VirtualDom's template registry
-    // used in conditional compilation
-    #[allow(unused_mut)]
-    pub(crate) fn register_template(
-        &mut self,
-        mut template: Template,
-        to: &mut impl WriteMutations,
-    ) {
-        let (path, byte_index) = template.name.rsplit_once(':').unwrap();
-
-        let byte_index = byte_index.parse::<usize>().unwrap();
-        // First, check if we've already seen this template
-        if self
-            .templates
-            .get(&path)
-            .filter(|set| set.contains_key(&byte_index))
-            .is_none()
-        {
-            // if hot reloading is enabled, then we need to check for a template that has overriten this one
-            #[cfg(debug_assertions)]
-            if let Some(mut new_template) = self
-                .templates
-                .get_mut(path)
-                .and_then(|map| map.remove(&usize::MAX))
-            {
-                // the byte index of the hot reloaded template could be different
-                new_template.name = template.name;
-                template = new_template;
-            }
-
-            self.templates
-                .entry(path)
-                .or_default()
-                .insert(byte_index, template);
-
-            // If it's all dynamic nodes, then we don't need to register it
-            if !template.is_completely_dynamic() {
-                to.register_template(template)
-            }
-        }
-    }
-
-    pub(crate) fn create_dynamic_node(
-        &mut self,
-        parent: &ElementRef,
-        node: &DynamicNode,
-        to: &mut impl WriteMutations,
-    ) -> usize {
-        use DynamicNode::*;
-        match node {
-            Text(text) => self.create_dynamic_text(parent, text, to),
-            Placeholder(place) => self.create_placeholder(place, parent, to),
-            Component(component) => self.create_component_node(Some(parent), component, to),
-            Fragment(frag) => self.create_children(frag, Some(parent), to),
-        }
-    }
-
-    fn create_dynamic_text(
-        &mut self,
-        parent: &ElementRef,
-        text: &VText,
-        to: &mut impl WriteMutations,
-    ) -> usize {
-        // Allocate a dynamic element reference for this text node
-        let new_id = self.next_element();
-
-        // Make sure the text node is assigned to the correct element
-        text.id.set(Some(new_id));
-
-        // Add the mutation to the list
-        to.hydrate_text_node(&parent.path.path[1..], &text.value, new_id);
-
-        // Since we're hydrating an existing node, we don't create any new nodes
-        0
-    }
-
-    pub(crate) fn create_placeholder(
-        &mut self,
-        placeholder: &VPlaceholder,
-        parent: &ElementRef,
-        to: &mut impl WriteMutations,
-    ) -> usize {
-        // Allocate a dynamic element reference for this text node
-        let id = self.next_element();
-
-        // Make sure the text node is assigned to the correct element
-        placeholder.id.set(Some(id));
-
-        // Assign the placeholder's parent
-        *placeholder.parent.borrow_mut() = Some(parent.clone());
-
-        // Assign the ID to the existing node in the template
-        to.assign_node_id(&parent.path.path[1..], id);
-
-        // Since the placeholder is already in the DOM, we don't create any new nodes
-        0
-    }
-
-    pub(super) fn create_component_node(
-        &mut self,
-        parent: Option<&ElementRef>,
-        component: &VComponent,
-        to: &mut impl WriteMutations,
-    ) -> usize {
-        use RenderReturn::*;
-
-        // Load up a ScopeId for this vcomponent
-        let scope = self.load_scope_from_vcomponent(component, to);
-
-        component.scope.set(Some(scope));
-
-        let new = self.run_scope(scope);
-
-        self.scopes[scope.0].last_rendered_node = Some(new.clone());
-
-        match &new {
-            // Create the component's root element
-            Ready(t) => {
-                self.assign_boundary_ref(parent, t);
-                self.create_scope(scope, t, to)
-            }
-            Aborted(t) => self.mount_aborted(t, parent, to),
-        }
-    }
-
-    /// Load a scope from a vcomponent. If the scope id doesn't exist, that means the component is currently "live"
-    fn load_scope_from_vcomponent(
-        &mut self,
-        component: &VComponent,
-        _to: &mut impl WriteMutations,
-    ) -> ScopeId {
-        component.scope.get().unwrap_or_else(|| {
-            self.new_scope(component.props.clone(), component.name)
-                .context()
-                .id
-        })
-    }
-
-    fn mount_aborted(
-        &mut self,
-        placeholder: &VPlaceholder,
-        parent: Option<&ElementRef>,
-        to: &mut impl WriteMutations,
-    ) -> usize {
-        let id = self.next_element();
-        to.create_placeholder(id);
-        placeholder.id.set(Some(id));
-        *placeholder.parent.borrow_mut() = parent.cloned();
-
-        1
-    }
-
-    fn set_slot(&mut self, slot: &Cell<Option<ElementId>>) -> ElementId {
-        let id = self.next_element();
-        slot.set(Some(id));
-        id
-    }
-}
-
-fn collect_dyn_node_range(
-    dynamic_nodes: &mut Peekable<impl Iterator<Item = ((usize, usize), &'static [u8])>>,
-    root_idx: u8,
-) -> Option<(usize, usize)> {
-    let start = match dynamic_nodes.peek() {
-        Some(((_, idx), [first, ..])) if *first == root_idx => *idx,
-        _ => return None,
-    };
-
-    let mut end = start;
-
-    while let Some(((_, idx), p)) =
-        dynamic_nodes.next_if(|(_, p)| matches!(p, [idx, ..] if *idx == root_idx))
-    {
-        if p.len() == 1 {
-            continue;
-        }
-
-        end = idx;
-    }
-
-    Some((start, end))
-}

+ 0 - 1119
packages/core/src/diff.rs

@@ -1,1119 +0,0 @@
-use std::ops::Deref;
-
-use crate::{
-    any_props::AnyProps,
-    arena::ElementId,
-    innerlude::{
-        DirtyScope, ElementPath, ElementRef, VComponent, VPlaceholder, VText, WriteMutations,
-    },
-    nodes::RenderReturn,
-    nodes::{DynamicNode, VNode},
-    scopes::ScopeId,
-    virtual_dom::VirtualDom,
-    Attribute, TemplateNode,
-};
-
-use rustc_hash::{FxHashMap, FxHashSet};
-use DynamicNode::*;
-
-impl VirtualDom {
-    pub(super) fn diff_scope(
-        &mut self,
-        scope: ScopeId,
-        new_nodes: RenderReturn,
-        to: &mut impl WriteMutations,
-    ) {
-        self.runtime.scope_stack.borrow_mut().push(scope);
-        let scope_state = &mut self.scopes[scope.0];
-        // Load the old and new bump arenas
-        let new = &new_nodes;
-        let old = scope_state.last_rendered_node.take().unwrap();
-
-        use RenderReturn::{Aborted, Ready};
-
-        match (&old, new) {
-            // Normal pathway
-            (Ready(l), Ready(r)) => self.diff_node(l, r, to),
-
-            // Unwind the mutations if need be
-            (Ready(l), Aborted(p)) => self.diff_ok_to_err(l, p, to),
-
-            // Just move over the placeholder
-            (Aborted(l), Aborted(r)) => {
-                r.id.set(l.id.get());
-                *r.parent.borrow_mut() = l.parent.borrow().clone();
-            }
-
-            // Placeholder becomes something
-            // We should also clear the error now
-            (Aborted(l), Ready(r)) => {
-                let parent = l.parent.take();
-                self.replace_placeholder(
-                    l,
-                    [r],
-                    parent.as_ref().expect("root node should not be none"),
-                    to,
-                )
-            }
-        };
-
-        let scope_state = &mut self.scopes[scope.0];
-        scope_state.last_rendered_node = Some(new_nodes);
-
-        self.runtime.scope_stack.borrow_mut().pop();
-    }
-
-    fn diff_ok_to_err(&mut self, l: &VNode, p: &VPlaceholder, to: &mut impl WriteMutations) {
-        let id = self.next_element();
-        p.id.set(Some(id));
-        *p.parent.borrow_mut() = l.parent.borrow().clone();
-        to.create_placeholder(id);
-
-        to.insert_nodes_before(id, 1);
-
-        // TODO: Instead of *just* removing it, we can use the replace mutation
-        self.remove_node(l, true, to);
-    }
-
-    fn diff_node(
-        &mut self,
-        left_template: &VNode,
-        right_template: &VNode,
-        to: &mut impl WriteMutations,
-    ) {
-        // If hot reloading is enabled, we need to make sure we're using the latest template
-        #[cfg(debug_assertions)]
-        {
-            let (path, byte_index) = right_template.template.get().name.rsplit_once(':').unwrap();
-            if let Some(map) = self.templates.get(path) {
-                let byte_index = byte_index.parse::<usize>().unwrap();
-                if let Some(&template) = map.get(&byte_index) {
-                    right_template.template.set(template);
-                    if template != left_template.template.get() {
-                        let parent = left_template.parent.take();
-                        let parent = parent.as_ref();
-                        return self.replace(left_template, [right_template], parent, to);
-                    }
-                }
-            }
-        }
-
-        // Copy over the parent
-        {
-            *right_template.parent.borrow_mut() = left_template.parent.borrow().clone();
-        }
-
-        // If the templates are the same, we don't need to do anything, nor do we want to
-        if templates_are_the_same(left_template, right_template) {
-            return;
-        }
-
-        // If the templates are different by name, we need to replace the entire template
-        if templates_are_different(left_template, right_template) {
-            return self.light_diff_templates(left_template, right_template, to);
-        }
-
-        // If the templates are the same, we can diff the attributes and children
-        // Start with the attributes
-        left_template
-            .dynamic_attrs
-            .iter()
-            .zip(right_template.dynamic_attrs.iter())
-            .for_each(|(left_attr, right_attr)| {
-                // Move over the ID from the old to the new
-                let mounted_element = left_attr.mounted_element.get();
-                right_attr.mounted_element.set(mounted_element);
-
-                // If the attributes are different (or volatile), we need to update them
-                if left_attr.value != right_attr.value || left_attr.volatile {
-                    self.update_attribute(right_attr, left_attr, to);
-                }
-            });
-
-        // Now diff the dynamic nodes
-        left_template
-            .dynamic_nodes
-            .iter()
-            .zip(right_template.dynamic_nodes.iter())
-            .enumerate()
-            .for_each(|(dyn_node_idx, (left_node, right_node))| {
-                let current_ref = ElementRef {
-                    element: right_template.clone(),
-                    path: ElementPath {
-                        path: left_template.template.get().node_paths[dyn_node_idx],
-                    },
-                };
-                self.diff_dynamic_node(left_node, right_node, &current_ref, to);
-            });
-
-        // Make sure the roots get transferred over while we're here
-        {
-            let mut right = right_template.root_ids.borrow_mut();
-            let left = left_template.root_ids.borrow();
-            for (from, into) in left.iter().zip(right.iter_mut()) {
-                *into = *from;
-            }
-        }
-    }
-
-    fn diff_dynamic_node(
-        &mut self,
-        left_node: &DynamicNode,
-        right_node: &DynamicNode,
-        parent: &ElementRef,
-        to: &mut impl WriteMutations,
-    ) {
-        match (left_node, right_node) {
-            (Text(left), Text(right)) => self.diff_vtext(left, right, to),
-            (Fragment(left), Fragment(right)) => self.diff_non_empty_fragment(left, right, parent, to),
-            (Placeholder(left), Placeholder(right)) => {
-                right.id.set(left.id.get());
-                *right.parent.borrow_mut() = left.parent.borrow().clone();
-            },
-            (Component(left), Component(right)) => self.diff_vcomponent(left, right, Some(parent), to),
-            (Placeholder(left), Fragment(right)) => self.replace_placeholder(left, right, parent, to),
-            (Fragment(left), Placeholder(right)) => self.node_to_placeholder(left, right, parent, to),
-            _ => todo!("This is an usual custom case for dynamic nodes. We don't know how to handle it yet."),
-        };
-    }
-
-    fn update_attribute(
-        &mut self,
-        right_attr: &Attribute,
-        left_attr: &Attribute,
-        to: &mut impl WriteMutations,
-    ) {
-        let name = &left_attr.name;
-        let value = &right_attr.value;
-        to.set_attribute(
-            name,
-            right_attr.namespace,
-            value,
-            left_attr.mounted_element.get(),
-        );
-    }
-
-    fn diff_vcomponent(
-        &mut self,
-        left: &VComponent,
-        right: &VComponent,
-        parent: Option<&ElementRef>,
-        to: &mut impl WriteMutations,
-    ) {
-        if std::ptr::eq(left, right) {
-            return;
-        }
-
-        // Replace components that have different render fns
-        if left.render_fn != right.render_fn {
-            return self.replace_vcomponent(right, left, parent, to);
-        }
-
-        // Make sure the new vcomponent has the right scopeid associated to it
-        let scope_id = left.scope.get().unwrap();
-
-        right.scope.set(Some(scope_id));
-
-        // copy out the box for both
-        let old_scope = &self.scopes[scope_id.0];
-        let old = old_scope.props.deref();
-        let new: &dyn AnyProps = right.props.deref();
-
-        // If the props are static, then we try to memoize by setting the new with the old
-        // The target scopestate still has the reference to the old props, so there's no need to update anything
-        // This also implicitly drops the new props since they're not used
-        if old.memoize(new.props()) {
-            tracing::trace!(
-                "Memoized props for component {:#?} ({})",
-                scope_id,
-                old_scope.context().name
-            );
-            return;
-        }
-
-        // First, move over the props from the old to the new, dropping old props in the process
-        self.scopes[scope_id.0].props = right.props.clone();
-
-        // Now run the component and diff it
-        let new = self.run_scope(scope_id);
-        self.diff_scope(scope_id, new, to);
-
-        self.dirty_scopes.remove(&DirtyScope {
-            height: self.runtime.get_context(scope_id).unwrap().height,
-            id: scope_id,
-        });
-    }
-
-    fn replace_vcomponent(
-        &mut self,
-        right: &VComponent,
-        left: &VComponent,
-        parent: Option<&ElementRef>,
-        to: &mut impl WriteMutations,
-    ) {
-        let _m = self.create_component_node(parent, right, to);
-
-        // TODO: Instead of *just* removing it, we can use the replace mutation
-        self.remove_component_node(left, true, to);
-
-        todo!()
-    }
-
-    /// Lightly diff the two templates, checking only their roots.
-    ///
-    /// The goal here is to preserve any existing component state that might exist. This is to preserve some React-like
-    /// behavior where the component state is preserved when the component is re-rendered.
-    ///
-    /// This is implemented by iterating each root, checking if the component is the same, if it is, then diff it.
-    ///
-    /// We then pass the new template through "create" which should be smart enough to skip roots.
-    ///
-    /// Currently, we only handle the case where the roots are the same component list. If there's any sort of deviation,
-    /// IE more nodes, less nodes, different nodes, or expressions, then we just replace the whole thing.
-    ///
-    /// This is mostly implemented to help solve the issue where the same component is rendered under two different
-    /// conditions:
-    ///
-    /// ```rust, ignore
-    /// if enabled {
-    ///     rsx!{ Component { enabled_sign: "abc" } }
-    /// } else {
-    ///     rsx!{ Component { enabled_sign: "xyz" } }
-    /// }
-    /// ```
-    ///
-    /// However, we should not that it's explicit in the docs that this is not a guarantee. If you need to preserve state,
-    /// then you should be passing in separate props instead.
-    ///
-    /// ```rust, ignore
-    /// let props = if enabled {
-    ///     ComponentProps { enabled_sign: "abc" }
-    /// } else {
-    ///     ComponentProps { enabled_sign: "xyz" }
-    /// };
-    ///
-    /// rsx! {
-    ///     Component { ..props }
-    /// }
-    /// ```
-    fn light_diff_templates(&mut self, left: &VNode, right: &VNode, to: &mut impl WriteMutations) {
-        let parent = left.parent.take();
-        let parent = parent.as_ref();
-        match matching_components(left, right) {
-            None => self.replace(left, [right], parent, to),
-            Some(components) => components
-                .into_iter()
-                .for_each(|(l, r)| self.diff_vcomponent(l, r, parent, to)),
-        }
-    }
-
-    /// Diff the two text nodes
-    ///
-    /// This just moves the ID of the old node over to the new node, and then sets the text of the new node if it's
-    /// different.
-    fn diff_vtext(&mut self, left: &VText, right: &VText, to: &mut impl WriteMutations) {
-        let id = left.id.get().unwrap_or_else(|| self.next_element());
-
-        right.id.set(Some(id));
-        if left.value != right.value {
-            to.set_node_text(&right.value, id);
-        }
-    }
-
-    fn diff_non_empty_fragment(
-        &mut self,
-        old: &[VNode],
-        new: &[VNode],
-        parent: &ElementRef,
-        to: &mut impl WriteMutations,
-    ) {
-        let new_is_keyed = new[0].key.is_some();
-        let old_is_keyed = old[0].key.is_some();
-        debug_assert!(
-            new.iter().all(|n| n.key.is_some() == new_is_keyed),
-            "all siblings must be keyed or all siblings must be non-keyed"
-        );
-        debug_assert!(
-            old.iter().all(|o| o.key.is_some() == old_is_keyed),
-            "all siblings must be keyed or all siblings must be non-keyed"
-        );
-
-        if new_is_keyed && old_is_keyed {
-            self.diff_keyed_children(old, new, parent, to);
-        } else {
-            self.diff_non_keyed_children(old, new, parent, to);
-        }
-    }
-
-    // Diff children that are not keyed.
-    //
-    // The parent must be on the top of the change list stack when entering this
-    // function:
-    //
-    //     [... parent]
-    //
-    // the change list stack is in the same state when this function returns.
-    fn diff_non_keyed_children(
-        &mut self,
-        old: &[VNode],
-        new: &[VNode],
-        parent: &ElementRef,
-        to: &mut impl WriteMutations,
-    ) {
-        use std::cmp::Ordering;
-
-        // Handled these cases in `diff_children` before calling this function.
-        debug_assert!(!new.is_empty());
-        debug_assert!(!old.is_empty());
-
-        match old.len().cmp(&new.len()) {
-            Ordering::Greater => self.remove_nodes(&old[new.len()..], to),
-            Ordering::Less => {
-                self.create_and_insert_after(&new[old.len()..], old.last().unwrap(), parent, to)
-            }
-            Ordering::Equal => {}
-        }
-
-        for (new, old) in new.iter().zip(old.iter()) {
-            self.diff_node(old, new, to);
-        }
-    }
-
-    // Diffing "keyed" children.
-    //
-    // With keyed children, we care about whether we delete, move, or create nodes
-    // versus mutate existing nodes in place. Presumably there is some sort of CSS
-    // transition animation that makes the virtual DOM diffing algorithm
-    // observable. By specifying keys for nodes, we know which virtual DOM nodes
-    // must reuse (or not reuse) the same physical DOM nodes.
-    //
-    // This is loosely based on Inferno's keyed patching implementation. However, we
-    // have to modify the algorithm since we are compiling the diff down into change
-    // list instructions that will be executed later, rather than applying the
-    // changes to the DOM directly as we compare virtual DOMs.
-    //
-    // https://github.com/infernojs/inferno/blob/36fd96/packages/inferno/src/DOM/patching.ts#L530-L739
-    //
-    // The stack is empty upon entry.
-    fn diff_keyed_children(
-        &mut self,
-        old: &[VNode],
-        new: &[VNode],
-        parent: &ElementRef,
-        to: &mut impl WriteMutations,
-    ) {
-        if cfg!(debug_assertions) {
-            let mut keys = rustc_hash::FxHashSet::default();
-            let mut assert_unique_keys = |children: &[VNode]| {
-                keys.clear();
-                for child in children {
-                    let key = child.key.clone();
-                    debug_assert!(
-                        key.is_some(),
-                        "if any sibling is keyed, all siblings must be keyed"
-                    );
-                    keys.insert(key);
-                }
-                debug_assert_eq!(
-                    children.len(),
-                    keys.len(),
-                    "keyed siblings must each have a unique key"
-                );
-            };
-            assert_unique_keys(old);
-            assert_unique_keys(new);
-        }
-
-        // First up, we diff all the nodes with the same key at the beginning of the
-        // children.
-        //
-        // `shared_prefix_count` is the count of how many nodes at the start of
-        // `new` and `old` share the same keys.
-        let (left_offset, right_offset) = match self.diff_keyed_ends(old, new, parent, to) {
-            Some(count) => count,
-            None => return,
-        };
-
-        // Ok, we now hopefully have a smaller range of children in the middle
-        // within which to re-order nodes with the same keys, remove old nodes with
-        // now-unused keys, and create new nodes with fresh keys.
-
-        let old_middle = &old[left_offset..(old.len() - right_offset)];
-        let new_middle = &new[left_offset..(new.len() - right_offset)];
-
-        debug_assert!(
-            !((old_middle.len() == new_middle.len()) && old_middle.is_empty()),
-            "keyed children must have the same number of children"
-        );
-
-        if new_middle.is_empty() {
-            // remove the old elements
-            self.remove_nodes(old_middle, to);
-        } else if old_middle.is_empty() {
-            // there were no old elements, so just create the new elements
-            // we need to find the right "foothold" though - we shouldn't use the "append" at all
-            if left_offset == 0 {
-                // insert at the beginning of the old list
-                let foothold = &old[old.len() - right_offset];
-                self.create_and_insert_before(new_middle, foothold, parent, to);
-            } else if right_offset == 0 {
-                // insert at the end  the old list
-                let foothold = old.last().unwrap();
-                self.create_and_insert_after(new_middle, foothold, parent, to);
-            } else {
-                // inserting in the middle
-                let foothold = &old[left_offset - 1];
-                self.create_and_insert_after(new_middle, foothold, parent, to);
-            }
-        } else {
-            self.diff_keyed_middle(old_middle, new_middle, parent, to);
-        }
-    }
-
-    /// Diff both ends of the children that share keys.
-    ///
-    /// Returns a left offset and right offset of that indicates a smaller section to pass onto the middle diffing.
-    ///
-    /// If there is no offset, then this function returns None and the diffing is complete.
-    fn diff_keyed_ends(
-        &mut self,
-        old: &[VNode],
-        new: &[VNode],
-        parent: &ElementRef,
-        to: &mut impl WriteMutations,
-    ) -> Option<(usize, usize)> {
-        let mut left_offset = 0;
-
-        for (old, new) in old.iter().zip(new.iter()) {
-            // abort early if we finally run into nodes with different keys
-            if old.key != new.key {
-                break;
-            }
-            self.diff_node(old, new, to);
-            left_offset += 1;
-        }
-
-        // If that was all of the old children, then create and append the remaining
-        // new children and we're finished.
-        if left_offset == old.len() {
-            self.create_and_insert_after(&new[left_offset..], old.last().unwrap(), parent, to);
-            return None;
-        }
-
-        // And if that was all of the new children, then remove all of the remaining
-        // old children and we're finished.
-        if left_offset == new.len() {
-            self.remove_nodes(&old[left_offset..], to);
-            return None;
-        }
-
-        // if the shared prefix is less than either length, then we need to walk backwards
-        let mut right_offset = 0;
-        for (old, new) in old.iter().rev().zip(new.iter().rev()) {
-            // abort early if we finally run into nodes with different keys
-            if old.key != new.key {
-                break;
-            }
-            self.diff_node(old, new, to);
-            right_offset += 1;
-        }
-
-        Some((left_offset, right_offset))
-    }
-
-    // The most-general, expensive code path for keyed children diffing.
-    //
-    // We find the longest subsequence within `old` of children that are relatively
-    // ordered the same way in `new` (via finding a longest-increasing-subsequence
-    // of the old child's index within `new`). The children that are elements of
-    // this subsequence will remain in place, minimizing the number of DOM moves we
-    // will have to do.
-    //
-    // Upon entry to this function, the change list stack must be empty.
-    //
-    // This function will load the appropriate nodes onto the stack and do diffing in place.
-    //
-    // Upon exit from this function, it will be restored to that same self.
-    #[allow(clippy::too_many_lines)]
-    fn diff_keyed_middle(
-        &mut self,
-        old: &[VNode],
-        new: &[VNode],
-        parent: &ElementRef,
-        to: &mut impl WriteMutations,
-    ) {
-        /*
-        1. Map the old keys into a numerical ordering based on indices.
-        2. Create a map of old key to its index
-        3. Map each new key to the old key, carrying over the old index.
-            - IE if we have ABCD becomes BACD, our sequence would be 1,0,2,3
-            - if we have ABCD to ABDE, our sequence would be 0,1,3,MAX because E doesn't exist
-
-        now, we should have a list of integers that indicates where in the old list the new items map to.
-
-        4. Compute the LIS of this list
-            - this indicates the longest list of new children that won't need to be moved.
-
-        5. Identify which nodes need to be removed
-        6. Identify which nodes will need to be diffed
-
-        7. Going along each item in the new list, create it and insert it before the next closest item in the LIS.
-            - if the item already existed, just move it to the right place.
-
-        8. Finally, generate instructions to remove any old children.
-        9. Generate instructions to finally diff children that are the same between both
-        */
-        // 0. Debug sanity checks
-        // Should have already diffed the shared-key prefixes and suffixes.
-        debug_assert_ne!(new.first().map(|i| &i.key), old.first().map(|i| &i.key));
-        debug_assert_ne!(new.last().map(|i| &i.key), old.last().map(|i| &i.key));
-
-        // 1. Map the old keys into a numerical ordering based on indices.
-        // 2. Create a map of old key to its index
-        // IE if the keys were A B C, then we would have (A, 1) (B, 2) (C, 3).
-        let old_key_to_old_index = old
-            .iter()
-            .enumerate()
-            .map(|(i, o)| (o.key.as_ref().unwrap(), i))
-            .collect::<FxHashMap<_, _>>();
-
-        let mut shared_keys = FxHashSet::default();
-
-        // 3. Map each new key to the old key, carrying over the old index.
-        let new_index_to_old_index = new
-            .iter()
-            .map(|node| {
-                let key = node.key.as_ref().unwrap();
-                if let Some(&index) = old_key_to_old_index.get(&key) {
-                    shared_keys.insert(key);
-                    index
-                } else {
-                    u32::MAX as usize
-                }
-            })
-            .collect::<Vec<_>>();
-
-        // If none of the old keys are reused by the new children, then we remove all the remaining old children and
-        // create the new children afresh.
-        if shared_keys.is_empty() {
-            if !old.is_empty() {
-                self.remove_nodes(&old[1..], to);
-                self.replace(&old[0], new, Some(parent), to);
-            } else {
-                // I think this is wrong - why are we appending?
-                // only valid of the if there are no trailing elements
-                // self.create_and_append_children(new);
-
-                todo!("we should never be appending - just creating N");
-            }
-            return;
-        }
-
-        // remove any old children that are not shared
-        // todo: make this an iterator
-        for child in old {
-            let key = child.key.as_ref().unwrap();
-            if !shared_keys.contains(&key) {
-                self.remove_node(child, true, to);
-            }
-        }
-
-        // 4. Compute the LIS of this list
-        let mut lis_sequence = Vec::with_capacity(new_index_to_old_index.len());
-
-        let mut predecessors = vec![0; new_index_to_old_index.len()];
-        let mut starts = vec![0; new_index_to_old_index.len()];
-
-        longest_increasing_subsequence::lis_with(
-            &new_index_to_old_index,
-            &mut lis_sequence,
-            |a, b| a < b,
-            &mut predecessors,
-            &mut starts,
-        );
-
-        // the lis comes out backwards, I think. can't quite tell.
-        lis_sequence.sort_unstable();
-
-        // if a new node gets u32 max and is at the end, then it might be part of our LIS (because u32 max is a valid LIS)
-        if lis_sequence.last().map(|f| new_index_to_old_index[*f]) == Some(u32::MAX as usize) {
-            lis_sequence.pop();
-        }
-
-        for idx in &lis_sequence {
-            self.diff_node(&old[new_index_to_old_index[*idx]], &new[*idx], to);
-        }
-
-        let mut nodes_created = 0;
-
-        // add mount instruction for the first items not covered by the lis
-        let last = *lis_sequence.last().unwrap();
-        if last < (new.len() - 1) {
-            for (idx, new_node) in new[(last + 1)..].iter().enumerate() {
-                let new_idx = idx + last + 1;
-                let old_index = new_index_to_old_index[new_idx];
-                if old_index == u32::MAX as usize {
-                    nodes_created += self.create(new_node, to);
-                } else {
-                    self.diff_node(&old[old_index], new_node, to);
-                    nodes_created += self.push_all_real_nodes(new_node, to);
-                }
-            }
-
-            let id = self.find_last_element(&new[last]);
-            if nodes_created > 0 {
-                to.insert_nodes_after(id, nodes_created)
-            }
-            nodes_created = 0;
-        }
-
-        // for each spacing, generate a mount instruction
-        let mut lis_iter = lis_sequence.iter().rev();
-        let mut last = *lis_iter.next().unwrap();
-        for next in lis_iter {
-            if last - next > 1 {
-                for (idx, new_node) in new[(next + 1)..last].iter().enumerate() {
-                    let new_idx = idx + next + 1;
-                    let old_index = new_index_to_old_index[new_idx];
-                    if old_index == u32::MAX as usize {
-                        nodes_created += self.create(new_node, to);
-                    } else {
-                        self.diff_node(&old[old_index], new_node, to);
-                        nodes_created += self.push_all_real_nodes(new_node, to);
-                    }
-                }
-
-                let id = self.find_first_element(&new[last]);
-                if nodes_created > 0 {
-                    to.insert_nodes_before(id, nodes_created);
-                }
-
-                nodes_created = 0;
-            }
-            last = *next;
-        }
-
-        // add mount instruction for the last items not covered by the lis
-        let first_lis = *lis_sequence.first().unwrap();
-        if first_lis > 0 {
-            for (idx, new_node) in new[..first_lis].iter().enumerate() {
-                let old_index = new_index_to_old_index[idx];
-                if old_index == u32::MAX as usize {
-                    nodes_created += self.create(new_node, to);
-                } else {
-                    self.diff_node(&old[old_index], new_node, to);
-                    nodes_created += self.push_all_real_nodes(new_node, to);
-                }
-            }
-
-            let id = self.find_first_element(&new[first_lis]);
-            if nodes_created > 0 {
-                to.insert_nodes_before(id, nodes_created);
-            }
-        }
-    }
-
-    /// Push all the real nodes on the stack
-    fn push_all_real_nodes(&self, node: &VNode, to: &mut impl WriteMutations) -> usize {
-        node.template
-            .get()
-            .roots
-            .iter()
-            .enumerate()
-            .map(|(idx, _)| {
-                let node = match node.dynamic_root(idx) {
-                    Some(node) => node,
-                    None => {
-                        to.push_root(node.root_ids.borrow()[idx]);
-                        return 1;
-                    }
-                };
-
-                match node {
-                    Text(t) => {
-                        to.push_root(t.id.get().unwrap());
-                        1
-                    }
-                    Placeholder(t) => {
-                        to.push_root(t.id.get().unwrap());
-                        1
-                    }
-                    Fragment(nodes) => nodes
-                        .iter()
-                        .map(|node| self.push_all_real_nodes(node, to))
-                        .sum(),
-
-                    Component(comp) => {
-                        let scope = comp.scope.get().unwrap();
-                        match self.get_scope(scope).unwrap().root_node() {
-                            RenderReturn::Ready(node) => self.push_all_real_nodes(node, to),
-                            RenderReturn::Aborted(_node) => todo!(),
-                        }
-                    }
-                }
-            })
-            .sum()
-    }
-
-    pub(crate) fn create_children<'a>(
-        &mut self,
-        nodes: impl IntoIterator<Item = &'a VNode>,
-        parent: Option<&ElementRef>,
-        to: &mut impl WriteMutations,
-    ) -> usize {
-        nodes
-            .into_iter()
-            .map(|child| {
-                self.assign_boundary_ref(parent, child);
-                self.create(child, to)
-            })
-            .sum()
-    }
-
-    fn create_and_insert_before(
-        &mut self,
-        new: &[VNode],
-        before: &VNode,
-        parent: &ElementRef,
-        to: &mut impl WriteMutations,
-    ) {
-        let m = self.create_children(new, Some(parent), to);
-        let id = self.find_first_element(before);
-        to.insert_nodes_before(id, m);
-    }
-
-    fn create_and_insert_after(
-        &mut self,
-        new: &[VNode],
-        after: &VNode,
-        parent: &ElementRef,
-        to: &mut impl WriteMutations,
-    ) {
-        let m = self.create_children(new, Some(parent), to);
-        let id = self.find_last_element(after);
-        to.insert_nodes_after(id, m);
-    }
-
-    /// Simply replace a placeholder with a list of nodes
-    fn replace_placeholder<'a>(
-        &mut self,
-        l: &VPlaceholder,
-        r: impl IntoIterator<Item = &'a VNode>,
-        parent: &ElementRef,
-        to: &mut impl WriteMutations,
-    ) {
-        let m = self.create_children(r, Some(parent), to);
-        let id = l.id.get().unwrap();
-        to.replace_node_with(id, m);
-        self.reclaim(id);
-    }
-
-    fn replace<'a>(
-        &mut self,
-        left: &VNode,
-        right: impl IntoIterator<Item = &'a VNode>,
-        parent: Option<&ElementRef>,
-        to: &mut impl WriteMutations,
-    ) {
-        let m = self.create_children(right, parent, to);
-
-        // TODO: Instead of *just* removing it, we can use the replace mutation
-        to.insert_nodes_before(self.find_first_element(left), m);
-
-        self.remove_node(left, true, to);
-    }
-
-    fn node_to_placeholder(
-        &mut self,
-        l: &[VNode],
-        r: &VPlaceholder,
-        parent: &ElementRef,
-        to: &mut impl WriteMutations,
-    ) {
-        // Create the placeholder first, ensuring we get a dedicated ID for the placeholder
-        let placeholder = self.next_element();
-
-        r.id.set(Some(placeholder));
-        r.parent.borrow_mut().replace(parent.clone());
-
-        to.create_placeholder(placeholder);
-
-        self.replace_nodes(l, 1, to);
-    }
-
-    /// Replace many nodes with a number of nodes on the stack
-    fn replace_nodes(&mut self, nodes: &[VNode], m: usize, to: &mut impl WriteMutations) {
-        // We want to optimize the replace case to use one less mutation if possible
-        // Since mutations are done in reverse, the last node removed will be the first in the stack
-        // TODO: Instead of *just* removing it, we can use the replace mutation
-        to.insert_nodes_before(self.find_first_element(&nodes[0]), m);
-
-        debug_assert!(
-            !nodes.is_empty(),
-            "replace_nodes must have at least one node"
-        );
-
-        self.remove_nodes(nodes, to);
-    }
-
-    /// Remove these nodes from the dom
-    /// Wont generate mutations for the inner nodes
-    fn remove_nodes(&mut self, nodes: &[VNode], to: &mut impl WriteMutations) {
-        nodes
-            .iter()
-            .rev()
-            .for_each(|node| self.remove_node(node, true, to));
-    }
-
-    fn remove_node(&mut self, node: &VNode, gen_muts: bool, to: &mut impl WriteMutations) {
-        // Clean up any attributes that have claimed a static node as dynamic for mount/unmounta
-        // Will not generate mutations!
-        self.reclaim_attributes(node);
-
-        // Remove the nested dynamic nodes
-        // We don't generate mutations for these, as they will be removed by the parent (in the next line)
-        // But we still need to make sure to reclaim them from the arena and drop their hooks, etc
-        self.remove_nested_dyn_nodes(node, to);
-
-        // Clean up the roots, assuming we need to generate mutations for these
-        // This is done last in order to preserve Node ID reclaim order (reclaim in reverse order of claim)
-        self.reclaim_roots(node, gen_muts, to);
-    }
-
-    fn reclaim_roots(&mut self, node: &VNode, gen_muts: bool, to: &mut impl WriteMutations) {
-        for (idx, _) in node.template.get().roots.iter().enumerate() {
-            if let Some(dy) = node.dynamic_root(idx) {
-                self.remove_dynamic_node(dy, gen_muts, to);
-            } else {
-                let id = node.root_ids.borrow()[idx];
-                if gen_muts {
-                    to.remove_node(id);
-                }
-                self.reclaim(id);
-            }
-        }
-    }
-
-    fn reclaim_attributes(&mut self, node: &VNode) {
-        let mut id = None;
-        for (idx, attr) in node.dynamic_attrs.iter().enumerate() {
-            // We'll clean up the root nodes either way, so don't worry
-            let path_len = node
-                .template
-                .get()
-                .attr_paths
-                .get(idx)
-                .map(|path| path.len());
-            // if the path is 1 the attribute is in the root, so we don't need to clean it up
-            // if the path is 0, the attribute is a not attached at all, so we don't need to clean it up
-
-            if let Some(len) = path_len {
-                if (..=1).contains(&len) {
-                    continue;
-                }
-            }
-
-            let next_id = attr.mounted_element.get();
-
-            if id == Some(next_id) {
-                continue;
-            }
-
-            id = Some(next_id);
-
-            self.reclaim(next_id);
-        }
-    }
-
-    fn remove_nested_dyn_nodes(&mut self, node: &VNode, to: &mut impl WriteMutations) {
-        for (idx, dyn_node) in node.dynamic_nodes.iter().enumerate() {
-            let path_len = node
-                .template
-                .get()
-                .node_paths
-                .get(idx)
-                .map(|path| path.len());
-            // Roots are cleaned up automatically above and nodes with a empty path are placeholders
-            if let Some(2..) = path_len {
-                self.remove_dynamic_node(dyn_node, false, to)
-            }
-        }
-    }
-
-    fn remove_dynamic_node(
-        &mut self,
-        node: &DynamicNode,
-        gen_muts: bool,
-        to: &mut impl WriteMutations,
-    ) {
-        match node {
-            Component(comp) => self.remove_component_node(comp, gen_muts, to),
-            Text(t) => self.remove_text_node(t, gen_muts, to),
-            Placeholder(t) => self.remove_placeholder(t, gen_muts, to),
-            Fragment(nodes) => nodes
-                .iter()
-                .for_each(|node| self.remove_node(node, gen_muts, to)),
-        };
-    }
-
-    fn remove_placeholder(
-        &mut self,
-        t: &VPlaceholder,
-        gen_muts: bool,
-        to: &mut impl WriteMutations,
-    ) {
-        if let Some(id) = t.id.take() {
-            if gen_muts {
-                to.remove_node(id);
-            }
-            self.reclaim(id)
-        }
-    }
-
-    fn remove_text_node(&mut self, t: &VText, gen_muts: bool, to: &mut impl WriteMutations) {
-        if let Some(id) = t.id.take() {
-            if gen_muts {
-                to.remove_node(id);
-            }
-            self.reclaim(id)
-        }
-    }
-
-    fn remove_component_node(
-        &mut self,
-        comp: &VComponent,
-        gen_muts: bool,
-        to: &mut impl WriteMutations,
-    ) {
-        // Remove the component reference from the vcomponent so they're not tied together
-        let scope = comp
-            .scope
-            .take()
-            .expect("VComponents to always have a scope");
-
-        // Remove the component from the dom
-        match self.scopes[scope.0].last_rendered_node.take().unwrap() {
-            RenderReturn::Ready(t) => self.remove_node(&t, gen_muts, to),
-            RenderReturn::Aborted(placeholder) => {
-                self.remove_placeholder(&placeholder, gen_muts, to)
-            }
-        };
-
-        // Now drop all the resources
-        self.drop_scope(scope);
-    }
-
-    fn find_first_element(&self, node: &VNode) -> ElementId {
-        match node.dynamic_root(0) {
-            None => node.root_ids.borrow()[0],
-            Some(Text(t)) => t.id.get().unwrap(),
-            Some(Fragment(t)) => self.find_first_element(&t[0]),
-            Some(Placeholder(t)) => t.id.get().unwrap(),
-            Some(Component(comp)) => {
-                let scope = comp.scope.get().unwrap();
-                match self.get_scope(scope).unwrap().root_node() {
-                    RenderReturn::Ready(t) => self.find_first_element(t),
-                    _ => todo!("cannot handle nonstandard nodes"),
-                }
-            }
-        }
-    }
-
-    fn find_last_element(&self, node: &VNode) -> ElementId {
-        match node.dynamic_root(node.template.get().roots.len() - 1) {
-            None => *node.root_ids.borrow().last().unwrap(),
-            Some(Text(t)) => t.id.get().unwrap(),
-            Some(Fragment(t)) => self.find_last_element(t.last().unwrap()),
-            Some(Placeholder(t)) => t.id.get().unwrap(),
-            Some(Component(comp)) => {
-                let scope = comp.scope.get().unwrap();
-                match self.get_scope(scope).unwrap().root_node() {
-                    RenderReturn::Ready(t) => self.find_last_element(t),
-                    _ => todo!("cannot handle nonstandard nodes"),
-                }
-            }
-        }
-    }
-
-    pub(crate) fn assign_boundary_ref(&mut self, parent: Option<&ElementRef>, child: &VNode) {
-        if let Some(parent) = parent {
-            // assign the parent of the child
-            child.parent.borrow_mut().replace(parent.clone());
-        }
-    }
-}
-
-/// Are the templates the same?
-///
-/// We need to check for the obvious case, and the non-obvious case where the template as cloned
-///
-/// We use the pointer of the dynamic_node list in this case
-fn templates_are_the_same(left_template: &VNode, right_template: &VNode) -> bool {
-    std::ptr::eq(left_template, right_template)
-}
-
-fn templates_are_different(left_template: &VNode, right_template: &VNode) -> bool {
-    let left_template_name = left_template.template.get().name;
-    let right_template_name = right_template.template.get().name;
-    // we want to re-create the node if the template name is different by pointer even if the value is the same so that we can detect when hot reloading changes the template
-    !std::ptr::eq(left_template_name, right_template_name)
-}
-
-fn matching_components<'a>(
-    left: &'a VNode,
-    right: &'a VNode,
-) -> Option<Vec<(&'a VComponent, &'a VComponent)>> {
-    let left_template = left.template.get();
-    let right_template = right.template.get();
-    if left_template.roots.len() != right_template.roots.len() {
-        return None;
-    }
-
-    // run through the components, ensuring they're the same
-    left_template
-        .roots
-        .iter()
-        .zip(right_template.roots.iter())
-        .map(|(l, r)| {
-            let (l, r) = match (l, r) {
-                (TemplateNode::Dynamic { id: l }, TemplateNode::Dynamic { id: r }) => (l, r),
-                _ => return None,
-            };
-
-            let (l, r) = match (&left.dynamic_nodes[*l], &right.dynamic_nodes[*r]) {
-                (Component(l), Component(r)) => (l, r),
-                _ => return None,
-            };
-
-            Some((l, r))
-        })
-        .collect()
-}
-
-/// We can apply various optimizations to dynamic nodes that are the single child of their parent.
-///
-/// IE
-///  - for text - we can use SetTextContent
-///  - for clearning children we can use RemoveChildren
-///  - for appending children we can use AppendChildren
-#[allow(dead_code)]
-fn is_dyn_node_only_child(node: &VNode, idx: usize) -> bool {
-    let template = node.template.get();
-    let path = template.node_paths[idx];
-
-    // use a loop to index every static node's children until the path has run out
-    // only break if the last path index is a dynamic node
-    let mut static_node = &template.roots[path[0] as usize];
-
-    for i in 1..path.len() - 1 {
-        match static_node {
-            TemplateNode::Element { children, .. } => static_node = &children[path[i] as usize],
-            _ => return false,
-        }
-    }
-
-    match static_node {
-        TemplateNode::Element { children, .. } => children.len() == 1,
-        _ => false,
-    }
-}

+ 149 - 0
packages/core/src/diff/component.rs

@@ -0,0 +1,149 @@
+use std::{
+    cell::RefMut,
+    ops::{Deref, DerefMut},
+};
+
+use crate::{
+    any_props::AnyProps,
+    arena::ElementId,
+    innerlude::{
+        DirtyScope, ElementPath, ElementRef, VComponent, VNodeMount, VText, WriteMutations,
+    },
+    nodes::RenderReturn,
+    nodes::{DynamicNode, VNode},
+    scopes::ScopeId,
+    virtual_dom::VirtualDom,
+    TemplateNode,
+};
+
+use DynamicNode::*;
+
+impl VirtualDom {
+    pub(crate) fn diff_scope(
+        &mut self,
+        to: &mut impl WriteMutations,
+        scope: ScopeId,
+        new_nodes: RenderReturn,
+    ) {
+        self.runtime.scope_stack.borrow_mut().push(scope);
+        let scope_state = &mut self.scopes[scope.0];
+        // Load the old and new bump arenas
+        let new = &new_nodes;
+        let old = scope_state.last_rendered_node.take().unwrap();
+
+        use RenderReturn::{Aborted, Ready};
+
+        let (Ready(old) | Aborted(old), Ready(new) | Aborted(new)) = (&old, new);
+        old.diff_node(new, self, to);
+
+        let scope_state = &mut self.scopes[scope.0];
+        scope_state.last_rendered_node = Some(new_nodes);
+
+        self.runtime.scope_stack.borrow_mut().pop();
+    }
+
+    /// Create a new template [`VNode`] and write it to the [`Mutations`] buffer.
+    ///
+    /// This method pushes the ScopeID to the internal scopestack and returns the number of nodes created.
+    pub(crate) fn create_scope(
+        &mut self,
+        to: &mut impl WriteMutations,
+        scope: ScopeId,
+        new_node: &VNode,
+        parent: Option<ElementRef>,
+    ) -> usize {
+        self.runtime.scope_stack.borrow_mut().push(scope);
+        let nodes = new_node.create(self, to, parent);
+        self.runtime.scope_stack.borrow_mut().pop();
+        nodes
+    }
+}
+
+impl VNode {
+    pub(crate) fn diff_vcomponent(
+        &self,
+        mount: &mut VNodeMount,
+        idx: usize,
+        new: &VComponent,
+        old: &VComponent,
+        scope_id: ScopeId,
+        parent: Option<ElementRef>,
+        dom: &mut VirtualDom,
+        to: &mut impl WriteMutations,
+    ) {
+        // Replace components that have different render fns
+        if old.render_fn != new.render_fn {
+            return self.replace_vcomponent(mount, idx, new, parent, dom, to);
+        }
+
+        // copy out the box for both
+        let old_scope = &mut dom.scopes[scope_id.0];
+        let old_props = old_scope.props.deref();
+        let new_props: &dyn AnyProps = new.props.deref();
+
+        // If the props are static, then we try to memoize by setting the new with the old
+        // The target scopestate still has the reference to the old props, so there's no need to update anything
+        // This also implicitly drops the new props since they're not used
+        if old_props.memoize(new_props.props()) {
+            tracing::trace!(
+                "Memoized props for component {:#?} ({})",
+                scope_id,
+                old_scope.context().name
+            );
+            return;
+        }
+
+        // First, move over the props from the old to the new, dropping old props in the process
+        dom.scopes[scope_id.0].props = new.props.clone();
+
+        // Now run the component and diff it
+        let new = dom.run_scope(scope_id);
+        dom.diff_scope(to, scope_id, new);
+
+        let height = dom.runtime.get_context(scope_id).unwrap().height;
+        dom.dirty_scopes.remove(&DirtyScope {
+            height,
+            id: scope_id,
+        });
+    }
+    fn replace_vcomponent(
+        &self,
+        mount: &mut VNodeMount,
+        idx: usize,
+        new: &VComponent,
+        parent: Option<ElementRef>,
+        dom: &mut VirtualDom,
+        to: &mut impl WriteMutations,
+    ) {
+        let scope = ScopeId(mount.mounted_dynamic_nodes[idx]);
+
+        let _m = self.create_component_node(mount, idx, new, parent, dom, to);
+
+        // TODO: Instead of *just* removing it, we can use the replace mutation
+        dom.remove_component_node(to, scope, true);
+
+        todo!()
+    }
+
+    pub(super) fn create_component_node(
+        &self,
+        mount: &mut VNodeMount,
+        idx: usize,
+        component: &VComponent,
+        parent: Option<ElementRef>,
+        dom: &mut VirtualDom,
+        to: &mut impl WriteMutations,
+    ) -> usize {
+        // Load up a ScopeId for this vcomponent. If it's already mounted, then we can just use that
+        let scope = dom
+            .new_scope(component.props.clone(), component.name)
+            .context()
+            .id;
+
+        let mut new = dom.run_scope(scope);
+
+        dom.scopes[scope.0].last_rendered_node = Some(new.clone());
+
+        dom.create_scope(to, scope, &new, parent)
+    }
+}

+ 429 - 0
packages/core/src/diff/iterator.rs

@@ -0,0 +1,429 @@
+use crate::{
+    innerlude::{ElementRef, WriteMutations},
+    nodes::VNode,
+    VirtualDom,
+};
+
+use rustc_hash::{FxHashMap, FxHashSet};
+
+impl VirtualDom {
+    pub(crate) fn diff_non_empty_fragment(
+        &mut self,
+        to: &mut impl WriteMutations,
+        old: &[VNode],
+        new: &[VNode],
+        parent: Option<ElementRef>,
+    ) {
+        let new_is_keyed = new[0].key.is_some();
+        let old_is_keyed = old[0].key.is_some();
+        debug_assert!(
+            new.iter().all(|n| n.key.is_some() == new_is_keyed),
+            "all siblings must be keyed or all siblings must be non-keyed"
+        );
+        debug_assert!(
+            old.iter().all(|o| o.key.is_some() == old_is_keyed),
+            "all siblings must be keyed or all siblings must be non-keyed"
+        );
+
+        if new_is_keyed && old_is_keyed {
+            self.diff_keyed_children(to, old, new, parent);
+        } else {
+            self.diff_non_keyed_children(to, old, new, parent);
+        }
+    }
+
+    // Diff children that are not keyed.
+    //
+    // The parent must be on the top of the change list stack when entering this
+    // function:
+    //
+    //     [... parent]
+    //
+    // the change list stack is in the same state when this function returns.
+    fn diff_non_keyed_children(
+        &mut self,
+        to: &mut impl WriteMutations,
+        old: &[VNode],
+        new: &[VNode],
+        parent: Option<ElementRef>,
+    ) {
+        use std::cmp::Ordering;
+
+        // Handled these cases in `diff_children` before calling this function.
+        debug_assert!(!new.is_empty());
+        debug_assert!(!old.is_empty());
+
+        match old.len().cmp(&new.len()) {
+            Ordering::Greater => self.remove_nodes(to, &old[new.len()..]),
+            Ordering::Less => self.create_and_insert_after(
+                to,
+                &new[old.len()..],
+                &mut old.last().unwrap(),
+                parent,
+            ),
+            Ordering::Equal => {}
+        }
+
+        for (new, old) in new.iter().zip(old.iter()) {
+            old.diff_node(new, self, to);
+        }
+    }
+
+    // Diffing "keyed" children.
+    //
+    // With keyed children, we care about whether we delete, move, or create nodes
+    // versus mutate existing nodes in place. Presumably there is some sort of CSS
+    // transition animation that makes the virtual DOM diffing algorithm
+    // observable. By specifying keys for nodes, we know which virtual DOM nodes
+    // must reuse (or not reuse) the same physical DOM nodes.
+    //
+    // This is loosely based on Inferno's keyed patching implementation. However, we
+    // have to modify the algorithm since we are compiling the diff down into change
+    // list instructions that will be executed later, rather than applying the
+    // changes to the DOM directly as we compare virtual DOMs.
+    //
+    // https://github.com/infernojs/inferno/blob/36fd96/packages/inferno/src/DOM/patching.ts#L530-L739
+    //
+    // The stack is empty upon entry.
+    fn diff_keyed_children(
+        &mut self,
+        to: &mut impl WriteMutations,
+        old: &[VNode],
+        new: &[VNode],
+        parent: Option<ElementRef>,
+    ) {
+        if cfg!(debug_assertions) {
+            let mut keys = rustc_hash::FxHashSet::default();
+            let mut assert_unique_keys = |children: &[VNode]| {
+                keys.clear();
+                for child in children {
+                    let key = child.key.clone();
+                    debug_assert!(
+                        key.is_some(),
+                        "if any sibling is keyed, all siblings must be keyed"
+                    );
+                    keys.insert(key);
+                }
+                debug_assert_eq!(
+                    children.len(),
+                    keys.len(),
+                    "keyed siblings must each have a unique key"
+                );
+            };
+            assert_unique_keys(old);
+            assert_unique_keys(new);
+        }
+
+        // First up, we diff all the nodes with the same key at the beginning of the
+        // children.
+        //
+        // `shared_prefix_count` is the count of how many nodes at the start of
+        // `new` and `old` share the same keys.
+        let (left_offset, right_offset) = match self.diff_keyed_ends(to, old, new, parent.clone()) {
+            Some(count) => count,
+            None => return,
+        };
+
+        // Ok, we now hopefully have a smaller range of children in the middle
+        // within which to re-order nodes with the same keys, remove old nodes with
+        // now-unused keys, and create new nodes with fresh keys.
+
+        let old_middle = &old[left_offset..(old.len() - right_offset)];
+        let new_middle = &new[left_offset..(new.len() - right_offset)];
+
+        debug_assert!(
+            !((old_middle.len() == new_middle.len()) && old_middle.is_empty()),
+            "keyed children must have the same number of children"
+        );
+
+        if new_middle.is_empty() {
+            // remove the old elements
+            self.remove_nodes(to, old_middle);
+        } else if old_middle.is_empty() {
+            // there were no old elements, so just create the new elements
+            // we need to find the right "foothold" though - we shouldn't use the "append" at all
+            if left_offset == 0 {
+                // insert at the beginning of the old list
+                let mut foothold = &old[old.len() - right_offset];
+                self.create_and_insert_before(to, new_middle, &mut foothold, parent);
+            } else if right_offset == 0 {
+                // insert at the end  the old list
+                let mut foothold = old.last().unwrap();
+                self.create_and_insert_after(to, new_middle, &mut foothold, parent);
+            } else {
+                // inserting in the middle
+                let mut foothold = &old[left_offset - 1];
+                self.create_and_insert_after(to, new_middle, &mut foothold, parent);
+            }
+        } else {
+            self.diff_keyed_middle(to, old_middle, new_middle, parent);
+        }
+    }
+
+    /// Diff both ends of the children that share keys.
+    ///
+    /// Returns a left offset and right offset of that indicates a smaller section to pass onto the middle diffing.
+    ///
+    /// If there is no offset, then this function returns None and the diffing is complete.
+    fn diff_keyed_ends(
+        &mut self,
+        to: &mut impl WriteMutations,
+        old: &[VNode],
+        new: &[VNode],
+        parent: Option<ElementRef>,
+    ) -> Option<(usize, usize)> {
+        let mut left_offset = 0;
+
+        for (old, new) in old.iter().zip(new.iter()) {
+            // abort early if we finally run into nodes with different keys
+            if old.key != new.key {
+                break;
+            }
+            old.diff_node(new, self, to);
+            left_offset += 1;
+        }
+
+        // If that was all of the old children, then create and append the remaining
+        // new children and we're finished.
+        if left_offset == old.len() {
+            self.create_and_insert_after(to, &new[left_offset..], &mut old.last().unwrap(), parent);
+            return None;
+        }
+
+        // And if that was all of the new children, then remove all of the remaining
+        // old children and we're finished.
+        if left_offset == new.len() {
+            self.remove_nodes(to, &old[left_offset..]);
+            return None;
+        }
+
+        // if the shared prefix is less than either length, then we need to walk backwards
+        let mut right_offset = 0;
+        for (old, new) in old.iter().rev().zip(new.iter().rev()) {
+            // abort early if we finally run into nodes with different keys
+            if old.key != new.key {
+                break;
+            }
+            old.diff_node(new, self, to);
+            right_offset += 1;
+        }
+
+        Some((left_offset, right_offset))
+    }
+
+    // The most-general, expensive code path for keyed children diffing.
+    //
+    // We find the longest subsequence within `old` of children that are relatively
+    // ordered the same way in `new` (via finding a longest-increasing-subsequence
+    // of the old child's index within `new`). The children that are elements of
+    // this subsequence will remain in place, minimizing the number of DOM moves we
+    // will have to do.
+    //
+    // Upon entry to this function, the change list stack must be empty.
+    //
+    // This function will load the appropriate nodes onto the stack and do diffing in place.
+    //
+    // Upon exit from this function, it will be restored to that same self.
+    #[allow(clippy::too_many_lines)]
+    fn diff_keyed_middle(
+        &mut self,
+        to: &mut impl WriteMutations,
+        old: &[VNode],
+        new: &[VNode],
+        parent: Option<ElementRef>,
+    ) {
+        /*
+        1. Map the old keys into a numerical ordering based on indices.
+        2. Create a map of old key to its index
+        3. Map each new key to the old key, carrying over the old index.
+            - IE if we have ABCD becomes BACD, our sequence would be 1,0,2,3
+            - if we have ABCD to ABDE, our sequence would be 0,1,3,MAX because E doesn't exist
+
+        now, we should have a list of integers that indicates where in the old list the new items mapto.
+
+        4. Compute the LIS of this list
+            - this indicates the longest list of new children that won't need to be moved.
+
+        5. Identify which nodes need to be removed
+        6. Identify which nodes will need to be diffed
+
+        7. Going along each item in the new list, create it and insert it before the next closest item in the LIS.
+            - if the item already existed, just move it to the right place.
+
+        8. Finally, generate instructions to remove any old children.
+        9. Generate instructions to finally diff children that are the same between both
+        */
+        // 0. Debug sanity checks
+        // Should have already diffed the shared-key prefixes and suffixes.
+        debug_assert_ne!(new.first().map(|i| &i.key), old.first().map(|i| &i.key));
+        debug_assert_ne!(new.last().map(|i| &i.key), old.last().map(|i| &i.key));
+
+        // 1. Map the old keys into a numerical ordering based on indices.
+        // 2. Create a map of old key to its index
+        // IE if the keys were A B C, then we would have (A, 1) (B, 2) (C, 3).
+        let old_key_to_old_index = old
+            .iter()
+            .enumerate()
+            .map(|(i, o)| (o.key.as_ref().unwrap(), i))
+            .collect::<FxHashMap<_, _>>();
+
+        let mut shared_keys = FxHashSet::default();
+
+        // 3. Map each new key to the old key, carrying over the old index.
+        let new_index_to_old_index = new
+            .iter()
+            .map(|node| {
+                let key = node.key.as_ref().unwrap();
+                if let Some(&index) = old_key_to_old_index.get(&key) {
+                    shared_keys.insert(key);
+                    index
+                } else {
+                    u32::MAX as usize
+                }
+            })
+            .collect::<Vec<_>>();
+
+        // If none of the old keys are reused by the new children, then we remove all the remaining old children and
+        // create the new children afresh.
+        if shared_keys.is_empty() {
+            if !old.is_empty() {
+                self.remove_nodes(to, &old[1..]);
+                old[0].replace(new, parent, self, to);
+            } else {
+                // I think this is wrong - why are we appending?
+                // only valid of the if there are no trailing elements
+                // self.create_and_append_children(new);
+
+                todo!("we should never be appending - just creating N");
+            }
+            return;
+        }
+
+        // remove any old children that are not shared
+        // todo: make this an iterator
+        for child in old {
+            let key = child.key.as_ref().unwrap();
+            if !shared_keys.contains(&key) {
+                child.remove_node(self, to, true);
+            }
+        }
+
+        // 4. Compute the LIS of this list
+        let mut lis_sequence = Vec::with_capacity(new_index_to_old_index.len());
+
+        let mut predecessors = vec![0; new_index_to_old_index.len()];
+        let mut starts = vec![0; new_index_to_old_index.len()];
+
+        longest_increasing_subsequence::lis_with(
+            &new_index_to_old_index,
+            &mut lis_sequence,
+            |a, b| a < b,
+            &mut predecessors,
+            &mut starts,
+        );
+
+        // the lis comes out backwards, I think. can't quite tell.
+        lis_sequence.sort_unstable();
+
+        // if a new node gets u32 max and is at the end, then it might be part of our LIS (because u32 max is a valid LIS)
+        if lis_sequence.last().map(|f| new_index_to_old_index[*f]) == Some(u32::MAX as usize) {
+            lis_sequence.pop();
+        }
+
+        for idx in &lis_sequence {
+            old[new_index_to_old_index[*idx]].diff_node(&new[*idx], self, to);
+        }
+
+        let mut nodes_created = 0;
+
+        // add mount instruction for the first items not covered by the lis
+        let last = *lis_sequence.last().unwrap();
+        if last < (new.len() - 1) {
+            for (idx, new_node) in new[(last + 1)..].iter().enumerate() {
+                let new_idx = idx + last + 1;
+                let old_index = new_index_to_old_index[new_idx];
+                if old_index == u32::MAX as usize {
+                    nodes_created += new_node.create(self, to, parent.clone());
+                } else {
+                    old[old_index].diff_node(new_node, self, to);
+                    nodes_created += new_node.push_all_real_nodes(self, to);
+                }
+            }
+
+            let id = new[last].find_last_element(self);
+            if nodes_created > 0 {
+                to.insert_nodes_after(id, nodes_created)
+            }
+            nodes_created = 0;
+        }
+
+        // for each spacing, generate a mount instruction
+        let mut lis_iter = lis_sequence.iter().rev();
+        let mut last = *lis_iter.next().unwrap();
+        for next in lis_iter {
+            if last - next > 1 {
+                for (idx, new_node) in new[(next + 1)..last].iter().enumerate() {
+                    let new_idx = idx + next + 1;
+                    let old_index = new_index_to_old_index[new_idx];
+                    if old_index == u32::MAX as usize {
+                        nodes_created += new_node.create(self, to, parent.clone());
+                    } else {
+                        old[old_index].diff_node(new_node, self, to);
+                        nodes_created += new_node.push_all_real_nodes(self, to);
+                    }
+                }
+
+                let id = new[last].find_first_element(self);
+                if nodes_created > 0 {
+                    to.insert_nodes_before(id, nodes_created);
+                }
+
+                nodes_created = 0;
+            }
+            last = *next;
+        }
+
+        // add mount instruction for the last items not covered by the lis
+        let first_lis = *lis_sequence.first().unwrap();
+        if first_lis > 0 {
+            for (idx, new_node) in new[..first_lis].iter().enumerate() {
+                let old_index = new_index_to_old_index[idx];
+                if old_index == u32::MAX as usize {
+                    nodes_created += new_node.create(self, to, parent.clone());
+                } else {
+                    old[old_index].diff_node(new_node, self, to);
+                    nodes_created += new_node.push_all_real_nodes(self, to);
+                }
+            }
+
+            let id = new[first_lis].find_first_element(self);
+            if nodes_created > 0 {
+                to.insert_nodes_before(id, nodes_created);
+            }
+        }
+    }
+
+    fn create_and_insert_before(
+        &mut self,
+        to: &mut impl WriteMutations,
+        new: &[VNode],
+        before: &VNode,
+        parent: Option<ElementRef>,
+    ) {
+        let m = self.create_children(to, new, parent);
+        let id = before.find_first_element(self);
+        to.insert_nodes_before(id, m);
+    }
+
+    fn create_and_insert_after(
+        &mut self,
+        to: &mut impl WriteMutations,
+        new: &[VNode],
+        after: &VNode,
+        parent: Option<ElementRef>,
+    ) {
+        let m = self.create_children(to, new, parent);
+        let id = after.find_last_element(self);
+        to.insert_nodes_after(id, m);
+    }
+}

+ 213 - 0
packages/core/src/diff/mod.rs

@@ -0,0 +1,213 @@
+use std::{
+    cell::RefMut,
+    ops::{Deref, DerefMut},
+};
+
+use crate::{
+    any_props::AnyProps,
+    arena::ElementId,
+    innerlude::{
+        DirtyScope, ElementPath, ElementRef, VComponent, VNodeMount, VText, WriteMutations,
+    },
+    nodes::RenderReturn,
+    nodes::{DynamicNode, VNode},
+    scopes::ScopeId,
+    virtual_dom::VirtualDom,
+    Template, TemplateNode,
+};
+
+use DynamicNode::*;
+
+mod component;
+mod iterator;
+mod node;
+
+impl VirtualDom {
+    pub(crate) fn create_children<'a>(
+        &mut self,
+        to: &mut impl WriteMutations,
+        nodes: impl IntoIterator<Item = &'a VNode>,
+        parent: Option<ElementRef>,
+    ) -> usize {
+        nodes
+            .into_iter()
+            .map(|child| child.create(self, to, parent.clone()))
+            .sum()
+    }
+
+    fn remove_element_id(&mut self, to: &mut impl WriteMutations, id: ElementId, gen_muts: bool) {
+        if gen_muts {
+            to.remove_node(id);
+        }
+        self.reclaim(id)
+    }
+
+    /// Simply replace a placeholder with a list of nodes
+    fn replace_placeholder<'a>(
+        &mut self,
+        to: &mut impl WriteMutations,
+        placeholder_id: ElementId,
+        r: impl IntoIterator<Item = &'a VNode>,
+        parent: Option<ElementRef>,
+    ) {
+        let m = self.create_children(to, r, parent);
+        to.replace_node_with(placeholder_id, m);
+        self.reclaim(placeholder_id);
+    }
+
+    fn nodes_to_placeholder(
+        &mut self,
+        to: &mut impl WriteMutations,
+        mount: &mut VNodeMount,
+        dyn_node_idx: usize,
+        old_nodes: &[VNode],
+    ) {
+        // Create the placeholder first, ensuring we get a dedicated ID for the placeholder
+        let placeholder = self.next_element();
+
+        // Set the id of the placeholder
+        mount.mounted_dynamic_nodes[dyn_node_idx] = placeholder.0;
+
+        to.create_placeholder(placeholder);
+
+        self.replace_nodes(to, old_nodes, 1);
+    }
+
+    /// Replace many nodes with a number of nodes on the stack
+    fn replace_nodes(&mut self, to: &mut impl WriteMutations, nodes: &[VNode], m: usize) {
+        // We want to optimize the replace case to use one less mutation if possible
+        // Since mutations are done in reverse, the last node removed will be the first in the stack
+        // TODO: Instead of *just* removing it, we can use the replace mutation
+        let first_element = nodes[0].find_first_element(self);
+        to.insert_nodes_before(first_element, m);
+
+        debug_assert!(
+            !nodes.is_empty(),
+            "replace_nodes must have at least one node"
+        );
+
+        self.remove_nodes(to, nodes);
+    }
+
+    /// Remove these nodes from the dom
+    /// Wont generate mutations for the inner nodes
+    fn remove_nodes(&mut self, to: &mut impl WriteMutations, nodes: &[VNode]) {
+        nodes
+            .iter()
+            .rev()
+            .for_each(|node| node.remove_node(self, to, true));
+    }
+
+    pub(crate) fn remove_component_node(
+        &mut self,
+        to: &mut impl WriteMutations,
+        scope: ScopeId,
+        gen_muts: bool,
+    ) {
+        // Remove the component from the dom
+        if let Some(mut node) = self.scopes[scope.0].last_rendered_node.take() {
+            node.remove_node(self, to, gen_muts)
+        };
+
+        // Now drop all the resources
+        self.drop_scope(scope);
+    }
+
+    /// Insert a new template into the VirtualDom's template registry
+    // used in conditional compilation
+    #[allow(unused_mut)]
+    pub(crate) fn register_template(
+        &mut self,
+        to: &mut impl WriteMutations,
+        mut template: Template,
+    ) {
+        let (path, byte_index) = template.name.rsplit_once(':').unwrap();
+
+        let byte_index = byte_index.parse::<usize>().unwrap();
+        // First, check if we've already seen this template
+        if self
+            .templates
+            .get(&path)
+            .filter(|set| set.contains_key(&byte_index))
+            .is_none()
+        {
+            // if hot reloading is enabled, then we need to check for a template that has overriten this one
+            #[cfg(debug_assertions)]
+            if let Some(mut new_template) = self
+                .templates
+                .get_mut(path)
+                .and_then(|map| map.remove(&usize::MAX))
+            {
+                // the byte index of the hot reloaded template could be different
+                new_template.name = template.name;
+                template = new_template;
+            }
+
+            self.templates
+                .entry(path)
+                .or_default()
+                .insert(byte_index, template);
+
+            // If it's all dynamic nodes, then we don't need to register it
+            if !template.is_completely_dynamic() {
+                to.register_template(template)
+            }
+        }
+    }
+
+    /// Insert a new template into the VirtualDom's template registry
+    pub(crate) fn register_template_first_byte_index(&mut self, mut template: Template) {
+        // First, make sure we mark the template as seen, regardless if we process it
+        let (path, _) = template.name.rsplit_once(':').unwrap();
+        if let Some((_, old_template)) = self
+            .templates
+            .entry(path)
+            .or_default()
+            .iter_mut()
+            .min_by_key(|(byte_index, _)| **byte_index)
+        {
+            // the byte index of the hot reloaded template could be different
+            template.name = old_template.name;
+            *old_template = template;
+        } else {
+            // This is a template without any current instances
+            self.templates
+                .entry(path)
+                .or_default()
+                .insert(usize::MAX, template);
+        }
+
+        // If it's all dynamic nodes, then we don't need to register it
+        if !template.is_completely_dynamic() {
+            self.queued_templates.push(template);
+        }
+    }
+}
+
+/// We can apply various optimizations to dynamic nodes that are the single child of their parent.
+///
+/// IE
+///  - for text - we can use SetTextContent
+///  - for clearing children we can use RemoveChildren
+///  - for appending children we can use AppendChildren
+#[allow(dead_code)]
+fn is_dyn_node_only_child(node: &VNode, idx: usize) -> bool {
+    let template = node.template.get();
+    let path = template.node_paths[idx];
+
+    // use a loop to index every static node's children until the path has run out
+    // only break if the last path index is a dynamic node
+    let mut static_node = &template.roots[path[0] as usize];
+
+    for i in 1..path.len() - 1 {
+        match static_node {
+            TemplateNode::Element { children, .. } => static_node = &children[path[i] as usize],
+            _ => return false,
+        }
+    }
+
+    match static_node {
+        TemplateNode::Element { children, .. } => children.len() == 1,
+        _ => false,
+    }
+}

+ 988 - 0
packages/core/src/diff/node.rs

@@ -0,0 +1,988 @@
+use crate::{Attribute, AttributeValue, DynamicNode::*, VPlaceholder};
+use crate::{VNode, VirtualDom, WriteMutations};
+use core::iter::Peekable;
+use std::cell::Ref;
+use std::{
+    cell::RefMut,
+    ops::{Deref, DerefMut},
+};
+
+use crate::{
+    any_props::AnyProps,
+    arena::ElementId,
+    innerlude::{DirtyScope, ElementPath, ElementRef, VComponent, VNodeMount, VText},
+    nodes::DynamicNode,
+    nodes::RenderReturn,
+    scopes::ScopeId,
+    TemplateNode,
+    TemplateNode::*,
+};
+
+impl VNode {
+    pub(crate) fn diff_node(
+        &self,
+        new: &VNode,
+        dom: &mut VirtualDom,
+        to: &mut impl WriteMutations,
+    ) {
+        // If hot reloading is enabled, we need to make sure we're using the latest template
+        #[cfg(debug_assertions)]
+        {
+            let (path, byte_index) = new.template.get().name.rsplit_once(':').unwrap();
+            if let Some(map) = dom.templates.get(path) {
+                let byte_index = byte_index.parse::<usize>().unwrap();
+                if let Some(&template) = map.get(&byte_index) {
+                    new.template.set(template);
+                    if template != self.template.get() {
+                        let parent = self.parent();
+                        return self.replace([new], parent, dom, to);
+                    }
+                }
+            }
+        }
+
+        {
+            // Copy over the mount information
+            *new.mount.borrow_mut() = self.mount.borrow_mut().take();
+        }
+
+        // If the templates are the same, we don't need to do anything, except copy over the mount information
+        if self == new {
+            return;
+        }
+
+        // If the templates are different by name, we need to replace the entire template
+        if self.templates_are_different(new) {
+            return self.light_diff_templates(new, dom, to);
+        }
+
+        let mut mount = self.assert_mounted_mut();
+        let mount = &mut *mount;
+
+        // If the templates are the same, we can diff the attributes and children
+        // Start with the attributes
+        self.dynamic_attrs
+            .iter()
+            .zip(new.dynamic_attrs.iter())
+            .enumerate()
+            .for_each(|(idx, (old_attr, new_attr))| {
+                // If the attributes are different (or volatile), we need to update them
+                if old_attr.value != new_attr.value || new_attr.volatile {
+                    self.update_attribute(mount, idx, new_attr, to);
+                }
+            });
+
+        // Now diff the dynamic nodes
+        self.dynamic_nodes
+            .iter()
+            .zip(new.dynamic_nodes.iter())
+            .enumerate()
+            .for_each(|(dyn_node_idx, (old, new))| {
+                self.diff_dynamic_node(mount, dyn_node_idx, old, new, dom, to)
+            });
+    }
+
+    /// Push all the real nodes on the stack
+    pub(crate) fn push_all_real_nodes(
+        &self,
+        dom: &VirtualDom,
+        to: &mut impl WriteMutations,
+    ) -> usize {
+        let template = self.template.get();
+
+        let mount = self.assert_mounted();
+
+        template
+            .roots
+            .iter()
+            .enumerate()
+            .map(|(root_idx, _)| match &self.template.get().roots[root_idx] {
+                Dynamic { id: idx } => match &self.dynamic_nodes[*idx] {
+                    Placeholder(_) | Text(_) => {
+                        to.push_root(mount.root_ids[root_idx]);
+                        1
+                    }
+                    Fragment(nodes) => {
+                        let mut accumulated = 0;
+                        for node in nodes {
+                            accumulated += node.push_all_real_nodes(dom, to);
+                        }
+                        accumulated
+                    }
+
+                    Component(_) => {
+                        let scope = ScopeId(mount.mounted_dynamic_nodes[*idx]);
+                        let node = dom.get_scope(scope).unwrap().root_node();
+                        node.push_all_real_nodes(dom, to)
+                    }
+                },
+                _ => {
+                    to.push_root(mount.root_ids[root_idx]);
+                    1
+                }
+            })
+            .sum()
+    }
+
+    fn diff_dynamic_node(
+        &self,
+        mount: &mut VNodeMount,
+        idx: usize,
+        old_node: &DynamicNode,
+        new_node: &DynamicNode,
+        dom: &mut VirtualDom,
+        to: &mut impl WriteMutations,
+    ) {
+        let parent = || ElementRef {
+            element: self.clone_with_parent(),
+            path: ElementPath {
+                path: self.template.get().node_paths[idx],
+            },
+        };
+        match (old_node, new_node) {
+            (Text(old), Text(new)) => self.diff_vtext( to, mount, idx, old, new),
+            (Placeholder(_), Placeholder(_)) => {},
+            (Fragment(old), Fragment(new)) => dom.diff_non_empty_fragment(to, old, new, Some(parent())),
+            (Component(old), Component(new)) => {
+				let scope_id = ScopeId(mount.mounted_dynamic_nodes[idx]);
+                self.diff_vcomponent(mount, idx, new, old, scope_id, Some(parent()), dom, to)
+            },
+            (Placeholder(_), Fragment(right)) => {
+                let placeholder_id = ElementId(mount.mounted_dynamic_nodes[idx]);
+                dom.replace_placeholder(to, placeholder_id, right, Some(parent()))},
+            (Fragment(left), Placeholder(_)) => {
+                dom.nodes_to_placeholder(to, mount, idx, left,)
+            },
+            _ => todo!("This is an usual custom case for dynamic nodes. We don't know how to handle it yet."),
+        };
+    }
+
+    pub(crate) fn find_first_element(&self, dom: &VirtualDom) -> ElementId {
+        let mount = self.assert_mounted();
+        match &self.template.get().roots[0] {
+            TemplateNode::Element { .. } | TemplateNode::Text { text: _ } => mount.root_ids[0],
+            TemplateNode::Dynamic { id } | TemplateNode::DynamicText { id } => {
+                match &self.dynamic_nodes[*id] {
+                    Placeholder(_) | Text(_) => ElementId(mount.mounted_dynamic_nodes[*id]),
+                    Fragment(children) => {
+                        let child = children.first().unwrap();
+                        child.find_first_element(dom)
+                    }
+                    Component(comp) => {
+                        let scope = ScopeId(mount.mounted_dynamic_nodes[*id]);
+                        match dom.get_scope(scope).unwrap().root_node() {
+                            RenderReturn::Ready(child) => child.find_first_element(dom),
+                            _ => todo!("cannot handle nonstandard nodes"),
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    pub(crate) fn find_last_element(&self, dom: &VirtualDom) -> ElementId {
+        let mount = self.assert_mounted();
+        match &self.template.get().roots.last().unwrap() {
+            TemplateNode::Element { .. } | TemplateNode::Text { text: _ } => {
+                *mount.root_ids.last().unwrap()
+            }
+            TemplateNode::Dynamic { id } | TemplateNode::DynamicText { id } => {
+                match &self.dynamic_nodes[*id] {
+                    Placeholder(_) | Text(_) => ElementId(mount.mounted_dynamic_nodes[*id]),
+                    Fragment(t) => t.last().unwrap().find_last_element(dom),
+                    Component(comp) => {
+                        let scope = ScopeId(mount.mounted_dynamic_nodes[*id]);
+                        match dom.get_scope(scope).unwrap().root_node() {
+                            RenderReturn::Ready(node) => node.find_last_element(dom),
+                            _ => todo!("cannot handle nonstandard nodes"),
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /// Diff the two text nodes
+    ///
+    /// This just sets the text of the node if it's different.
+    fn diff_vtext(
+        &self,
+        to: &mut impl WriteMutations,
+        mount: &VNodeMount,
+        idx: usize,
+        left: &VText,
+        right: &VText,
+    ) {
+        if left.value != right.value {
+            let id = ElementId(mount.mounted_dynamic_nodes[idx]);
+            to.set_node_text(&right.value, id);
+        }
+    }
+
+    pub(crate) fn replace<'a>(
+        &self,
+        right: impl IntoIterator<Item = &'a VNode>,
+        parent: Option<ElementRef>,
+        dom: &mut VirtualDom,
+        to: &mut impl WriteMutations,
+    ) {
+        let m = dom.create_children(to, right, parent);
+
+        // TODO: Instead of *just* removing it, we can use the replace mutation
+        let first_element = self.find_first_element(dom);
+        to.insert_nodes_before(first_element, m);
+
+        self.remove_node(dom, to, true)
+    }
+
+    pub(crate) fn remove_node(
+        &self,
+        dom: &mut VirtualDom,
+        to: &mut impl WriteMutations,
+        gen_muts: bool,
+    ) {
+        let mount = self.assert_mounted();
+
+        // Clean up any attributes that have claimed a static node as dynamic for mount/unmounts
+        // Will not generate mutations!
+        self.reclaim_attributes(&mount, dom, to);
+
+        // Remove the nested dynamic nodes
+        // We don't generate mutations for these, as they will be removed by the parent (in the next line)
+        // But we still need to make sure to reclaim them from the arena and drop their hooks, etc
+        self.remove_nested_dyn_nodes(&mount, dom, to);
+
+        // Clean up the roots, assuming we need to generate mutations for these
+        // This is done last in order to preserve Node ID reclaim order (reclaim in reverse order of claim)
+        self.reclaim_roots(&mount, dom, to, gen_muts);
+    }
+
+    fn reclaim_roots(
+        &self,
+        mount: &VNodeMount,
+        dom: &mut VirtualDom,
+        to: &mut impl WriteMutations,
+        gen_muts: bool,
+    ) {
+        for (idx, node) in self.template.get().roots.iter().enumerate() {
+            if let Some(id) = node.dynamic_id() {
+                let dynamic_node = &self.dynamic_nodes[id];
+                self.remove_dynamic_node(mount, dom, to, idx, dynamic_node, gen_muts);
+            } else {
+                let id = ElementId(mount.mounted_dynamic_nodes[idx]);
+                if gen_muts {
+                    to.remove_node(id);
+                }
+                dom.reclaim(id);
+            }
+        }
+    }
+
+    fn remove_nested_dyn_nodes(
+        &self,
+        mount: &VNodeMount,
+        dom: &mut VirtualDom,
+        to: &mut impl WriteMutations,
+    ) {
+        let template = self.template.get();
+        for (idx, dyn_node) in self.dynamic_nodes.iter().enumerate() {
+            let path_len = template.node_paths.get(idx).map(|path| path.len());
+            // Roots are cleaned up automatically above and nodes with a empty path are placeholders
+            if let Some(2..) = path_len {
+                self.remove_dynamic_node(mount, dom, to, idx, dyn_node, false)
+            }
+        }
+    }
+
+    fn remove_dynamic_node(
+        &self,
+        mount: &VNodeMount,
+        dom: &mut VirtualDom,
+        to: &mut impl WriteMutations,
+        idx: usize,
+        node: &DynamicNode,
+        gen_muts: bool,
+    ) {
+        match node {
+            Component(comp) => {
+                let scope_id = ScopeId(mount.mounted_dynamic_nodes[idx]);
+                dom.remove_component_node(to, scope_id, gen_muts);
+            }
+            Text(_) | Placeholder(_) => {
+                let id = ElementId(mount.mounted_dynamic_nodes[idx]);
+                dom.remove_element_id(to, id, gen_muts)
+            }
+            Fragment(nodes) => nodes
+                .iter()
+                .for_each(|node| self.remove_node(dom, to, gen_muts)),
+        };
+    }
+
+    fn assert_mounted(&self) -> Ref<'_, VNodeMount> {
+        Ref::map(self.mount.borrow(), |mount| {
+            mount.as_ref().expect("to be mounted")
+        })
+    }
+
+    fn assert_mounted_mut(&self) -> RefMut<'_, VNodeMount> {
+        RefMut::map(self.mount.borrow_mut(), |mount| {
+            mount.as_mut().expect("to be mounted")
+        })
+    }
+
+    fn templates_are_different(&self, other: &VNode) -> bool {
+        let self_node_name = self.template.get().name;
+        let other_node_name = other.template.get().name;
+        // we want to re-create the node if the template name is different by pointer even if the value is the same so that we can detect when hot reloading changes the template
+        !std::ptr::eq(self_node_name, other_node_name)
+    }
+
+    pub(super) fn reclaim_attributes(
+        &self,
+        mount: &VNodeMount,
+        dom: &mut VirtualDom,
+        to: &mut impl WriteMutations,
+    ) {
+        let mut id = None;
+
+        for (idx, path) in self.template.get().attr_paths.iter().enumerate() {
+            let attr = &self.dynamic_attrs[idx];
+
+            // We clean up the roots in the next step, so don't worry about them here
+            if path.len() <= 1 {
+                continue;
+            }
+
+            let next_id = mount.mounted_attributes[idx];
+
+            // only reclaim the new element if it's different from the previous one
+            if id != Some(next_id) {
+                dom.reclaim(next_id);
+            }
+        }
+    }
+
+    pub(super) fn update_attribute(
+        &self,
+        mount: &VNodeMount,
+        idx: usize,
+        new_attr: &Attribute,
+        to: &mut impl WriteMutations,
+    ) {
+        let name = &new_attr.name;
+        let value = &new_attr.value;
+        let id = mount.mounted_attributes[idx];
+        to.set_attribute(name, new_attr.namespace, value, id);
+    }
+
+    /// Lightly diff the two templates, checking only their roots.
+    ///
+    /// The goal here is to preserve any existing component state that might exist. This is to preserve some React-like
+    /// behavior where the component state is preserved when the component is re-rendered.
+    ///
+    /// This is implemented by iterating each root, checking if the component is the same, if it is, then diff it.
+    ///
+    /// We then pass the new template through "create" which should be smart enough to skip roots.
+    ///
+    /// Currently, we only handle the case where the roots are the same component list. If there's any sort of deviation,
+    /// IE more nodes, less nodes, different nodes, or expressions, then we just replace the whole thing.
+    ///
+    /// This is mostly implemented to help solve the issue where the same component is rendered under two different
+    /// conditions:
+    ///
+    /// ```rust, ignore
+    /// if enabled {
+    ///     rsx!{ Component { enabled_sign: "abc" } }
+    /// } else {
+    ///     rsx!{ Component { enabled_sign: "xyz" } }
+    /// }
+    /// ```
+    ///
+    /// However, we should not that it's explicit in the docs that this is not a guarantee. If you need to preserve state,
+    /// then you should be passing in separate props instead.
+    ///
+    /// ```rust, ignore
+    /// let props = if enabled {
+    ///     ComponentProps { enabled_sign: "abc" }
+    /// } else {
+    ///     ComponentProps { enabled_sign: "xyz" }
+    /// };
+    ///
+    /// rsx! {
+    ///     Component { ..props }
+    /// }
+    /// ```
+    pub(crate) fn light_diff_templates(
+        &self,
+        new: &VNode,
+        dom: &mut VirtualDom,
+        to: &mut impl WriteMutations,
+    ) {
+        let parent = new.parent();
+        match matching_components(self, new) {
+            None => self.replace([new], parent, dom, to),
+            Some(components) => {
+                let mut mount = self.assert_mounted_mut();
+                for (idx, (old_component, new_component)) in components.into_iter().enumerate() {
+                    let scope_id = ScopeId(self.assert_mounted().mounted_dynamic_nodes[idx]);
+                    self.diff_vcomponent(
+                        &mut *mount,
+                        idx,
+                        old_component,
+                        new_component,
+                        scope_id,
+                        parent.clone(),
+                        dom,
+                        to,
+                    )
+                }
+            }
+        }
+    }
+
+    /// Create this template and write its mutations
+    pub(crate) fn create(
+        &self,
+        dom: &mut VirtualDom,
+        to: &mut impl WriteMutations,
+        parent: Option<ElementRef>,
+    ) -> usize {
+        // check for a overridden template
+        #[cfg(debug_assertions)]
+        {
+            let template = self.template.get();
+            let (path, byte_index) = template.name.rsplit_once(':').unwrap();
+            if let Some(new_template) = dom
+                .templates
+                .get(path)
+                .and_then(|map| map.get(&byte_index.parse().unwrap()))
+            {
+                self.template.set(*new_template);
+            }
+        };
+
+        let template = self.template.get();
+
+        // The best renderers will have templates pre-hydrated and registered
+        // Just in case, let's create the template using instructions anyways
+        dom.register_template(to, template);
+
+        // Walk the roots, creating nodes and assigning IDs
+        // nodes in an iterator of ((dynamic_node_index, sorted_index), path)
+        // todo: adjust dynamic nodes to be in the order of roots and then leaves (ie BFS)
+        #[cfg(not(debug_assertions))]
+        let (mut attrs, mut nodes) = (
+            template.attr_paths.iter().copied().enumerate().peekable(),
+            template
+                .node_paths
+                .iter()
+                .copied()
+                .enumerate()
+                .map(|(i, path)| ((i, i), path))
+                .peekable(),
+        );
+        // If this is a debug build, we need to check that the paths are in the correct order because hot reloading can cause scrambled states
+
+        #[cfg(debug_assertions)]
+        let (attrs_sorted, nodes_sorted) =
+            { (sort_bfs(template.attr_paths), sort_bfs(template.node_paths)) };
+        #[cfg(debug_assertions)]
+        let (mut attrs, mut nodes) = {
+            (
+                attrs_sorted.into_iter().peekable(),
+                nodes_sorted
+                    .iter()
+                    .copied()
+                    .enumerate()
+                    .map(|(i, (id, path))| ((id, i), path))
+                    .peekable(),
+            )
+        };
+
+        let mut node = self.mount.borrow_mut();
+
+        // Initialize the mount information for this template
+        *node = Some(VNodeMount {
+            parent,
+            root_ids: vec![ElementId(0); template.roots.len()].into_boxed_slice(),
+            mounted_attributes: vec![ElementId(0); template.attr_paths.len()].into_boxed_slice(),
+            mounted_dynamic_nodes: vec![0; template.node_paths.len()].into_boxed_slice(),
+        });
+
+        let mount = node.as_mut().unwrap();
+
+        template
+            .roots
+            .iter()
+            .enumerate()
+            .map(|(idx, root)| match root {
+                DynamicText { id } | Dynamic { id } => {
+                    nodes.next().unwrap();
+                    self.write_dynamic_root(mount, *id, dom, to)
+                }
+                Element { .. } => {
+                    #[cfg(not(debug_assertions))]
+                    let id = self.write_element_root(
+                        mount,
+                        node,
+                        idx,
+                        &mut attrs,
+                        &mut nodes,
+                        &[],
+                        dom,
+                        to,
+                    );
+                    #[cfg(debug_assertions)]
+                    let id = self.write_element_root(
+                        mount,
+                        idx,
+                        &mut attrs,
+                        &mut nodes,
+                        &nodes_sorted,
+                        dom,
+                        to,
+                    );
+                    id
+                }
+                TemplateNode::Text { .. } => self.write_static_text_root(mount, idx, dom, to),
+            })
+            .sum()
+    }
+}
+
+impl VNode {
+    fn write_static_text_root(
+        &self,
+        mount: &mut VNodeMount,
+        idx: usize,
+        dom: &mut VirtualDom,
+        to: &mut impl WriteMutations,
+    ) -> usize {
+        // Simply just load the template root, no modifications needed
+        self.load_template_root(mount, idx, dom, to);
+
+        // Text producs just one node on the stack
+        1
+    }
+
+    fn write_dynamic_root(
+        &self,
+        mount: &mut VNodeMount,
+        idx: usize,
+        dom: &mut VirtualDom,
+        to: &mut impl WriteMutations,
+    ) -> usize {
+        use DynamicNode::*;
+        match &self.dynamic_nodes[idx] {
+            Component(component) => {
+                let parent = Some(ElementRef {
+                    path: ElementPath {
+                        path: self.template.get().node_paths[idx],
+                    },
+                    element: self.clone_with_parent(),
+                });
+                self.create_component_node(mount, idx, component, parent, dom, to)
+            }
+            Fragment(frag) => {
+                let parent = Some(ElementRef {
+                    path: ElementPath {
+                        path: self.template.get().node_paths[idx],
+                    },
+                    element: self.clone_with_parent(),
+                });
+                dom.create_children(to, frag, parent)
+            }
+            Placeholder(_) => {
+                let id = mount.mount_node(idx, dom);
+                to.create_placeholder(id);
+                1
+            }
+            Text(VText { value }) => {
+                let id = mount.mount_node(idx, dom);
+                to.create_text_node(value, id);
+                1
+            }
+        }
+    }
+
+    /// We write all the descendent data for this element
+    ///
+    /// Elements can contain other nodes - and those nodes can be dynamic or static
+    ///
+    /// We want to make sure we write these nodes while on top of the root
+    fn write_element_root(
+        &self,
+        mount: &mut VNodeMount,
+        root_idx: usize,
+        dynamic_attrs: &mut Peekable<impl Iterator<Item = (usize, &'static [u8])>>,
+        dynamic_nodes_iter: &mut Peekable<impl Iterator<Item = ((usize, usize), &'static [u8])>>,
+        dynamic_nodes: &[(usize, &'static [u8])],
+        dom: &mut VirtualDom,
+        to: &mut impl WriteMutations,
+    ) -> usize {
+        // Load the template root and get the ID for the node on the stack
+        let root_on_stack = self.load_template_root(mount, root_idx, dom, to);
+
+        // Write all the attributes below this root
+        self.write_attrs_on_root(mount, dynamic_attrs, root_idx as u8, root_on_stack, dom, to);
+
+        // Load in all of the placeholder or dynamic content under this root too
+        self.load_placeholders(
+            mount,
+            dynamic_nodes_iter,
+            dynamic_nodes,
+            root_idx as u8,
+            dom,
+            to,
+        );
+
+        1
+    }
+
+    /// Load all of the placeholder nodes for descendents of this root node
+    ///
+    /// ```rust, ignore
+    /// rsx! {
+    ///     div {
+    ///         // This is a placeholder
+    ///         some_value,
+    ///
+    ///         // Load this too
+    ///         "{some_text}"
+    ///     }
+    /// }
+    /// ```
+    #[allow(unused)]
+    fn load_placeholders(
+        &self,
+        mount: &mut VNodeMount,
+        dynamic_nodes_iter: &mut Peekable<impl Iterator<Item = ((usize, usize), &'static [u8])>>,
+        dynamic_nodes: &[(usize, &'static [u8])],
+        root_idx: u8,
+        dom: &mut VirtualDom,
+        to: &mut impl WriteMutations,
+    ) {
+        let (start, end) = match collect_dyn_node_range(dynamic_nodes_iter, root_idx) {
+            Some((a, b)) => (a, b),
+            None => return,
+        };
+
+        // If hot reloading is enabled, we need to map the sorted index to the original index of the dynamic node. If it is disabled, we can just use the sorted index
+        #[cfg(not(debug_assertions))]
+        let reversed_iter = (start..=end).rev();
+        #[cfg(debug_assertions)]
+        let reversed_iter = (start..=end)
+            .rev()
+            .map(|sorted_index| dynamic_nodes[sorted_index].0);
+
+        for idx in reversed_iter {
+            let m = self.create_dynamic_node(mount, idx, dom, to);
+            if m > 0 {
+                // The path is one shorter because the top node is the root
+                let path = &self.template.get().node_paths[idx][1..];
+                to.replace_placeholder_with_nodes(path, m);
+            }
+        }
+    }
+
+    fn write_attrs_on_root(
+        &self,
+        mount: &mut VNodeMount,
+        attrs: &mut Peekable<impl Iterator<Item = (usize, &'static [u8])>>,
+        root_idx: u8,
+        root: ElementId,
+        dom: &mut VirtualDom,
+        to: &mut impl WriteMutations,
+    ) {
+        while let Some((mut attr_id, path)) =
+            attrs.next_if(|(_, p)| p.first().copied() == Some(root_idx))
+        {
+            let id = self.assign_static_node_as_dynamic(path, root, dom, to);
+
+            loop {
+                self.write_attribute(mount, attr_id, id, dom, to);
+
+                // Only push the dynamic attributes forward if they match the current path (same element)
+                match attrs.next_if(|(_, p)| *p == path) {
+                    Some((next_attr_id, _)) => attr_id = next_attr_id,
+                    None => break,
+                }
+            }
+        }
+    }
+
+    fn write_attribute(
+        &self,
+        mount: &mut VNodeMount,
+        idx: usize,
+        id: ElementId,
+        dom: &mut VirtualDom,
+        to: &mut impl WriteMutations,
+    ) {
+        // Make sure we set the attribute's associated id
+        mount.mounted_attributes[idx] = id;
+
+        let attribute = &self.dynamic_attrs[idx];
+
+        match &attribute.value {
+            AttributeValue::Listener(_) => {
+                // If this is a listener, we need to create an element reference for it so that when we receive an event, we can find the element
+                let path = &self.template.get().attr_paths[idx];
+                let element_ref = ElementRef {
+                    path: ElementPath { path },
+                    element: self.clone_with_parent(),
+                };
+                dom.elements[id.0] = Some(element_ref);
+                to.create_event_listener(&attribute.name[2..], id);
+            }
+            _ => {
+                to.set_attribute(&attribute.name, attribute.namespace, &attribute.value, id);
+            }
+        }
+    }
+
+    fn load_template_root(
+        &self,
+        mount: &mut VNodeMount,
+        root_idx: usize,
+        dom: &mut VirtualDom,
+        to: &mut impl WriteMutations,
+    ) -> ElementId {
+        // Get an ID for this root since it's a real root
+        let this_id = dom.next_element();
+        mount.root_ids[root_idx] = this_id;
+
+        to.load_template(self.template.get().name, root_idx, this_id);
+
+        this_id
+    }
+
+    /// We have some dynamic attributes attached to a some node
+    ///
+    /// That node needs to be loaded at runtime, so we need to give it an ID
+    ///
+    /// If the node in question is on the stack, we just return that ID
+    ///
+    /// If the node is not on the stack, we create a new ID for it and assign it
+    fn assign_static_node_as_dynamic(
+        &self,
+        path: &'static [u8],
+        this_id: ElementId,
+        dom: &mut VirtualDom,
+        to: &mut impl WriteMutations,
+    ) -> ElementId {
+        if path.len() == 1 {
+            return this_id;
+        }
+
+        // if attribute is on a root node, then we've already created the element
+        // Else, it's deep in the template and we should create a new id for it
+        let id = dom.next_element();
+
+        to.assign_node_id(&path[1..], id);
+
+        id
+    }
+
+    pub(crate) fn create_dynamic_node(
+        &self,
+        mount: &mut VNodeMount,
+        index: usize,
+        dom: &mut VirtualDom,
+        to: &mut impl WriteMutations,
+    ) -> usize {
+        use DynamicNode::*;
+        let node = &self.dynamic_nodes[index];
+        match node {
+            Text(text) => self.create_dynamic_text(mount, index, text, dom, to),
+            Placeholder(_) => self.create_placeholder(mount, index, dom, to),
+            Component(component) => {
+                let parent = Some(ElementRef {
+                    path: ElementPath {
+                        path: self.template.get().node_paths[index],
+                    },
+                    element: self.clone_with_parent(),
+                });
+                self.create_component_node(mount, index, component, parent, dom, to)
+            }
+            Fragment(frag) => {
+                let parent = Some(ElementRef {
+                    path: ElementPath {
+                        path: self.template.get().node_paths[index],
+                    },
+                    element: self.clone_with_parent(),
+                });
+                dom.create_children(to, frag, parent)
+            }
+        }
+    }
+
+    /// Mount a root node and return its ID and the path to the node
+    fn mount_dynamic_node_with_path(
+        &self,
+        mount: &mut VNodeMount,
+        idx: usize,
+        dom: &mut VirtualDom,
+    ) -> (ElementId, &'static [u8]) {
+        // Add the mutation to the list
+        let path = self.template.get().node_paths[idx];
+
+        // Allocate a dynamic element reference for this text node
+        let new_id = mount.mount_node(idx, dom);
+
+        (new_id, &path[1..])
+    }
+
+    fn create_dynamic_text(
+        &self,
+        mount: &mut VNodeMount,
+        idx: usize,
+        text: &VText,
+        dom: &mut VirtualDom,
+        to: &mut impl WriteMutations,
+    ) -> usize {
+        let (new_id, path) = self.mount_dynamic_node_with_path(mount, idx, dom);
+
+        // Hydrate the text node
+        to.hydrate_text_node(path, &text.value, new_id);
+
+        // Since we're hydrating an existing node, we don't create any new nodes
+        0
+    }
+
+    pub(crate) fn create_placeholder(
+        &self,
+        mount: &mut VNodeMount,
+        idx: usize,
+        dom: &mut VirtualDom,
+        to: &mut impl WriteMutations,
+    ) -> usize {
+        let (id, path) = self.mount_dynamic_node_with_path(mount, idx, dom);
+
+        // Assign the ID to the existing node in the template
+        to.assign_node_id(path, id);
+
+        // Since the placeholder is already in the DOM, we don't create any new nodes
+        0
+    }
+}
+
+impl VNodeMount {
+    fn mount_attribute(&mut self, attribute_index: usize, dom: &mut VirtualDom) -> ElementId {
+        let id = dom.next_element();
+        self.mounted_attributes[attribute_index] = id;
+        id
+    }
+
+    fn mount_node(&mut self, node_index: usize, dom: &mut VirtualDom) -> ElementId {
+        let id = dom.next_element();
+        self.mounted_dynamic_nodes[node_index] = id.0;
+        id
+    }
+}
+
+fn collect_dyn_node_range(
+    dynamic_nodes: &mut Peekable<impl Iterator<Item = ((usize, usize), &'static [u8])>>,
+    root_idx: u8,
+) -> Option<(usize, usize)> {
+    let start = match dynamic_nodes.peek() {
+        Some(((_, idx), [first, ..])) if *first == root_idx => *idx,
+        _ => return None,
+    };
+
+    let mut end = start;
+
+    while let Some(((_, idx), p)) =
+        dynamic_nodes.next_if(|(_, p)| matches!(p, [idx, ..] if *idx == root_idx))
+    {
+        if p.len() == 1 {
+            continue;
+        }
+
+        end = idx;
+    }
+
+    Some((start, end))
+}
+
+fn matching_components<'a>(
+    left: &'a VNode,
+    right: &'a VNode,
+) -> Option<Vec<(&'a VComponent, &'a VComponent)>> {
+    let left_node = left.template.get();
+    let right_node = right.template.get();
+    if left_node.roots.len() != right_node.roots.len() {
+        return None;
+    }
+
+    // run through the components, ensuring they're the same
+    left_node
+        .roots
+        .iter()
+        .zip(right_node.roots.iter())
+        .map(|(l, r)| {
+            let (l, r) = match (l, r) {
+                (TemplateNode::Dynamic { id: l }, TemplateNode::Dynamic { id: r }) => (l, r),
+                _ => return None,
+            };
+
+            let (l, r) = match (&left.dynamic_nodes[*l], &right.dynamic_nodes[*r]) {
+                (Component(l), Component(r)) => (l, r),
+                _ => return None,
+            };
+
+            Some((l, r))
+        })
+        .collect()
+}
+
+#[cfg(debug_assertions)]
+fn sort_bfs(paths: &[&'static [u8]]) -> Vec<(usize, &'static [u8])> {
+    let mut with_indecies = paths.iter().copied().enumerate().collect::<Vec<_>>();
+    with_indecies.sort_unstable_by(|(_, a), (_, b)| {
+        let mut a = a.iter();
+        let mut b = b.iter();
+        loop {
+            match (a.next(), b.next()) {
+                (Some(a), Some(b)) => {
+                    if a != b {
+                        return a.cmp(b);
+                    }
+                }
+                // The shorter path goes first
+                (None, Some(_)) => return std::cmp::Ordering::Less,
+                (Some(_), None) => return std::cmp::Ordering::Greater,
+                (None, None) => return std::cmp::Ordering::Equal,
+            }
+        }
+    });
+    with_indecies
+}
+
+#[test]
+#[cfg(debug_assertions)]
+fn sorting() {
+    let r: [(usize, &[u8]); 5] = [
+        (0, &[0, 1]),
+        (1, &[0, 2]),
+        (2, &[1, 0]),
+        (3, &[1, 0, 1]),
+        (4, &[1, 2]),
+    ];
+    assert_eq!(
+        sort_bfs(&[&[0, 1,], &[0, 2,], &[1, 0,], &[1, 0, 1,], &[1, 2,],]),
+        r
+    );
+    let r: [(usize, &[u8]); 6] = [
+        (0, &[0]),
+        (1, &[0, 1]),
+        (2, &[0, 1, 2]),
+        (3, &[1]),
+        (4, &[1, 2]),
+        (5, &[2]),
+    ];
+    assert_eq!(
+        sort_bfs(&[&[0], &[0, 1], &[0, 1, 2], &[1], &[1, 2], &[2],]),
+        r
+    );
+}

+ 0 - 2
packages/core/src/error_boundary.rs

@@ -287,7 +287,6 @@ fn default_handler(error: CapturedError) -> Element {
     Some(VNode::new(
         None,
         TEMPLATE,
-        Box::new([Default::default(); 1]),
         Box::new([error.to_string().into_dyn_node()]),
         Default::default(),
     ))
@@ -460,7 +459,6 @@ pub fn ErrorBoundary(props: ErrorBoundaryProps) -> Element {
             VNode::new(
                 None,
                 TEMPLATE,
-                Box::new([Default::default(); 1]),
                 Box::new([(props.children).into_dyn_node()]),
                 Default::default(),
             )

+ 0 - 1
packages/core/src/lib.rs

@@ -5,7 +5,6 @@
 
 mod any_props;
 mod arena;
-mod create;
 mod diff;
 mod dirty_scope;
 mod error_boundary;

+ 123 - 89
packages/core/src/nodes.rs

@@ -2,12 +2,13 @@ use crate::any_props::{BoxedAnyProps, VProps};
 use crate::innerlude::{ElementRef, EventHandler};
 use crate::Properties;
 use crate::{arena::ElementId, Element, Event, ScopeId};
+use std::cell::RefCell;
 use std::ops::Deref;
 use std::rc::Rc;
 use std::vec;
 use std::{
     any::{Any, TypeId},
-    cell::{Cell, RefCell},
+    cell::Cell,
     fmt::{Arguments, Debug},
 };
 
@@ -27,17 +28,44 @@ pub enum RenderReturn {
 
     /// The component aborted rendering early. It might've thrown an error.
     ///
-    /// In its place we've produced a placeholder to locate its spot in the dom when
-    /// it recovers.
-    Aborted(VPlaceholder),
+    /// In its place we've produced a placeholder to locate its spot in the dom when it recovers.
+    Aborted(VNode),
 }
 
 impl Default for RenderReturn {
     fn default() -> Self {
-        RenderReturn::Aborted(VPlaceholder::default())
+        RenderReturn::Aborted(VNode::placeholder())
     }
 }
 
+impl Deref for RenderReturn {
+    type Target = VNode;
+
+    fn deref(&self) -> &Self::Target {
+        match self {
+            RenderReturn::Ready(node) | RenderReturn::Aborted(node) => node,
+        }
+    }
+}
+
+/// The information about the
+#[derive(Debug)]
+pub(crate) struct VNodeMount {
+    /// The parent of this node
+    pub parent: Option<ElementRef>,
+
+    /// The IDs for the roots of this template - to be used when moving the template around and removing it from
+    /// the actual Dom
+    pub root_ids: Box<[ElementId]>,
+
+    /// The element in the DOM that each attribute is mounted to
+    pub(crate) mounted_attributes: Box<[ElementId]>,
+
+    /// For components: This is the ScopeId the component is mounted to
+    /// For other dynamic nodes: This is element in the DOM that each dynamic node is mounted to
+    pub(crate) mounted_dynamic_nodes: Box<[usize]>,
+}
+
 /// A reference to a template along with any context needed to hydrate it
 ///
 /// The dynamic parts of the template are stored separately from the static parts. This allows faster diffing by skipping
@@ -49,13 +77,6 @@ pub struct VNodeInner {
     /// In fragments, this is the key of the first child. In other cases, it is the key of the root.
     pub key: Option<String>,
 
-    /// When rendered, this template will be linked to its parent manually
-    pub(crate) parent: RefCell<Option<ElementRef>>,
-
-    /// The IDs for the roots of this template - to be used when moving the template around and removing it from
-    /// the actual Dom
-    pub root_ids: RefCell<Box<[ElementId]>>,
-
     /// The static nodes and static descriptor of the template
     pub template: Cell<Template>,
 
@@ -70,12 +91,26 @@ pub struct VNodeInner {
 ///
 /// The dynamic parts of the template are stored separately from the static parts. This allows faster diffing by skipping
 /// static parts of the template.
-#[derive(Clone, Debug)]
-pub struct VNode(Rc<VNodeInner>);
+#[derive(Debug)]
+pub struct VNode {
+    vnode: Rc<VNodeInner>,
+
+    /// The mount information for this template
+    pub(crate) mount: Rc<RefCell<Option<VNodeMount>>>,
+}
+
+impl Clone for VNode {
+    fn clone(&self) -> Self {
+        Self {
+            vnode: self.vnode.clone(),
+            mount: Default::default(),
+        }
+    }
+}
 
 impl PartialEq for VNode {
     fn eq(&self, other: &Self) -> bool {
-        Rc::ptr_eq(&self.0, &other.0)
+        Rc::ptr_eq(&self.vnode, &other.vnode)
     }
 }
 
@@ -83,44 +118,78 @@ impl Deref for VNode {
     type Target = VNodeInner;
 
     fn deref(&self) -> &Self::Target {
-        &self.0
+        &self.vnode
     }
 }
 
 impl VNode {
+    /// Clone the element while retaining the mounted parent of the node
+    pub(crate) fn clone_with_parent(&self) -> Self {
+        Self {
+            vnode: self.vnode.clone(),
+            mount: self.mount.clone(),
+        }
+    }
+
+    /// Try to get the parent of this node
+    ///
+    /// This will fail if the VNode is not mounted
+    pub(crate) fn parent(&self) -> Option<ElementRef> {
+        self.mount.borrow().as_ref()?.parent.clone()
+    }
+
     /// Create a template with no nodes that will be skipped over during diffing
     pub fn empty() -> Element {
-        Some(Self(Rc::new(VNodeInner {
-            key: None,
-            parent: Default::default(),
-            root_ids: Default::default(),
-            dynamic_nodes: Box::new([]),
-            dynamic_attrs: Box::new([]),
-            template: Cell::new(Template {
-                name: "dioxus-empty",
-                roots: &[],
-                node_paths: &[],
-                attr_paths: &[],
+        Some(Self {
+            vnode: Rc::new(VNodeInner {
+                key: None,
+                dynamic_nodes: Box::new([]),
+                dynamic_attrs: Box::new([]),
+                template: Cell::new(Template {
+                    name: "dioxus-empty",
+                    roots: &[],
+                    node_paths: &[],
+                    attr_paths: &[],
+                }),
             }),
-        })))
+            mount: Default::default(),
+        })
+    }
+
+    /// Create a template with a single placeholder node
+    pub fn placeholder() -> Self {
+        Self {
+            vnode: Rc::new(VNodeInner {
+                key: None,
+                dynamic_nodes: Box::new([DynamicNode::Placeholder(Default::default())]),
+                dynamic_attrs: Box::new([]),
+                template: Cell::new(Template {
+                    name: "dioxus-placeholder",
+                    roots: &[TemplateNode::Dynamic { id: 0 }],
+                    node_paths: &[&[]],
+                    attr_paths: &[],
+                }),
+            }),
+            mount: Default::default(),
+        }
     }
 
     /// Create a new VNode
     pub fn new(
         key: Option<String>,
         template: Template,
-        root_ids: Box<[ElementId]>,
         dynamic_nodes: Box<[DynamicNode]>,
         dynamic_attrs: Box<[Attribute]>,
     ) -> Self {
-        Self(Rc::new(VNodeInner {
-            key,
-            parent: Default::default(),
-            template: Cell::new(template),
-            root_ids: RefCell::new(root_ids),
-            dynamic_nodes,
-            dynamic_attrs,
-        }))
+        Self {
+            vnode: Rc::new(VNodeInner {
+                key,
+                template: Cell::new(template),
+                dynamic_nodes,
+                dynamic_attrs,
+            }),
+            mount: Default::default(),
+        }
     }
 
     /// Load a dynamic root at the given index
@@ -275,7 +344,7 @@ pub enum TemplateNode {
         )]
         namespace: Option<&'static str>,
 
-        /// A list of possibly dynamic attribues for this element
+        /// A list of possibly dynamic attributes for this element
         ///
         /// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"`.
         #[cfg_attr(feature = "serialize", serde(deserialize_with = "deserialize_leaky"))]
@@ -307,6 +376,17 @@ pub enum TemplateNode {
     },
 }
 
+impl TemplateNode {
+    /// Try to load the dynamic node at the given index
+    pub fn dynamic_id(&self) -> Option<usize> {
+        use TemplateNode::*;
+        match self {
+            Dynamic { id } | DynamicText { id } => Some(*id),
+            _ => None,
+        }
+    }
+}
+
 /// A node created at runtime
 ///
 /// This node's index in the DynamicNode list on VNode should match its repsective `Dynamic` index
@@ -357,9 +437,6 @@ pub struct VComponent {
     /// The name of this component
     pub name: &'static str,
 
-    /// The assigned Scope for this component
-    pub(crate) scope: Cell<Option<ScopeId>>,
-
     /// The function pointer of the component, known at compile time
     ///
     /// It is possible that components get folded at compile time, so these shouldn't be really used as a key
@@ -394,47 +471,29 @@ impl VComponent {
             name: fn_name,
             render_fn: component as *const (),
             props: BoxedAnyProps::new(vcomp),
-            scope: Default::default(),
         }
     }
-
-    /// Get the scope that this component is mounted to
-    pub fn mounted_scope(&self) -> Option<ScopeId> {
-        self.scope.get()
-    }
 }
 
 impl std::fmt::Debug for VComponent {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         f.debug_struct("VComponent")
             .field("name", &self.name)
-            .field("scope", &self.scope)
             .finish()
     }
 }
 
-/// An instance of some text, mounted to the DOM
+/// A text node
 #[derive(Clone, Debug)]
 pub struct VText {
     /// The actual text itself
     pub value: String,
-
-    /// The ID of this node in the real DOM
-    pub(crate) id: Cell<Option<ElementId>>,
 }
 
 impl VText {
     /// Create a new VText
     pub fn new(value: String) -> Self {
-        Self {
-            value,
-            id: Default::default(),
-        }
-    }
-
-    /// Get the mounted ID of this node
-    pub fn mounted_element(&self) -> Option<ElementId> {
-        self.id.get()
+        Self { value }
     }
 }
 
@@ -446,19 +505,8 @@ impl From<Arguments<'_>> for VText {
 
 /// A placeholder node, used by suspense and fragments
 #[derive(Clone, Debug, Default)]
-pub struct VPlaceholder {
-    /// The ID of this node in the real DOM
-    pub(crate) id: Cell<Option<ElementId>>,
-    /// The parent of this node
-    pub(crate) parent: RefCell<Option<ElementRef>>,
-}
-
-impl VPlaceholder {
-    /// Get the mounted ID of this node
-    pub fn mounted_element(&self) -> Option<ElementId> {
-        self.id.get()
-    }
-}
+#[non_exhaustive]
+pub struct VPlaceholder {}
 
 /// An attribute of the TemplateNode, created at compile time
 #[derive(Debug, PartialEq, Hash, Eq, PartialOrd, Ord)]
@@ -509,9 +557,6 @@ pub struct Attribute {
 
     /// An indication of we should always try and set the attribute. Used in controlled components to ensure changes are propagated
     pub volatile: bool,
-
-    /// The element in the DOM that this attribute belongs to
-    pub(crate) mounted_element: Cell<ElementId>,
 }
 
 impl Attribute {
@@ -529,15 +574,9 @@ impl Attribute {
             name,
             namespace,
             volatile,
-            mounted_element: Default::default(),
             value: value.into_value(),
         }
     }
-
-    /// Get the element that this attribute is mounted to
-    pub fn mounted_element(&self) -> ElementId {
-        self.mounted_element.get()
-    }
 }
 
 /// Any of the built-in values that the Dioxus VirtualDom supports as dynamic attributes on elements
@@ -688,17 +727,13 @@ impl IntoDynNode for &str {
     fn into_dyn_node(self) -> DynamicNode {
         DynamicNode::Text(VText {
             value: self.to_string(),
-            id: Default::default(),
         })
     }
 }
 
 impl IntoDynNode for String {
     fn into_dyn_node(self) -> DynamicNode {
-        DynamicNode::Text(VText {
-            value: self,
-            id: Default::default(),
-        })
+        DynamicNode::Text(VText { value: self })
     }
 }
 
@@ -706,7 +741,6 @@ impl IntoDynNode for Arguments<'_> {
     fn into_dyn_node(self) -> DynamicNode {
         DynamicNode::Text(VText {
             value: self.to_string(),
-            id: Default::default(),
         })
     }
 }

+ 11 - 18
packages/core/src/virtual_dom.rs

@@ -184,7 +184,7 @@ pub struct VirtualDom {
 
     pub(crate) dirty_scopes: BTreeSet<DirtyScope>,
 
-    // Maps a template path to a map of byteindexes to templates
+    // Maps a template path to a map of byte indexes to templates
     pub(crate) templates: FxHashMap<TemplateId, FxHashMap<usize, Template>>,
 
     // Templates changes that are queued for the next render
@@ -324,7 +324,7 @@ impl VirtualDom {
         }
     }
 
-    /// Call a listener inside the VirtualDom with data from outside the VirtualDom. **The ElementId passed in must be the id of an dynamic element, not a static node or a text node.**
+    /// Call a listener inside the VirtualDom with data from outside the VirtualDom. **The ElementId passed in must be the id of an element with a listener, not a static node or a text node.**
     ///
     /// This method will identify the appropriate element. The data must match up with the listener declared. Note that
     /// this method does not give any indication as to the success of the listener call. If the listener is not found,
@@ -417,7 +417,8 @@ impl VirtualDom {
                     }
                 }
 
-                parent_node = el_ref.parent.borrow().clone();
+                let mount = el_ref.mount.borrow();
+                parent_node = mount.as_ref().unwrap().parent.clone();
             }
         } else {
             // Otherwise, we just call the listener on the target element
@@ -551,20 +552,11 @@ impl VirtualDom {
         let _runtime = RuntimeGuard::new(self.runtime.clone());
         let new_nodes = self.run_scope(ScopeId::ROOT);
         self.scopes[ScopeId::ROOT.0].last_rendered_node = Some(new_nodes.clone());
-        match new_nodes {
-            // Rebuilding implies we append the created elements to the root
-            RenderReturn::Ready(node) => {
-                let m = self.create_scope(ScopeId::ROOT, &node, to);
-                to.append_children(ElementId(0), m);
-            }
-            // If an error occurs, we should try to render the default error component and context where the error occured
-            RenderReturn::Aborted(placeholder) => {
-                tracing::debug!("Ran into suspended or aborted scope during rebuild");
-                let id = self.next_element();
-                placeholder.id.set(Some(id));
-                to.create_placeholder(id);
-            }
-        }
+        let (RenderReturn::Ready(mut node) | RenderReturn::Aborted(mut node)) = new_nodes;
+
+        // Rebuilding implies we append the created elements to the root
+        let m = self.create_scope(to, ScopeId::ROOT, &mut node, None);
+        to.append_children(ElementId(0), m);
     }
 
     /// Render whatever the VirtualDom has ready as fast as possible without requiring an executor to progress
@@ -629,7 +621,8 @@ impl VirtualDom {
                     let _runtime = RuntimeGuard::new(self.runtime.clone());
                     // Run the scope and get the mutations
                     let new_nodes = self.run_scope(dirty.id);
-                    self.diff_scope(dirty.id, new_nodes, to);
+
+                    self.diff_scope(to, dirty.id, new_nodes);
                 }
             }