浏览代码

Fix nested rsx expansion by not using template titles (#2799)

* Fix nested rsx expansion by not using template titles

* fix writers with nameless templates

* fix clippy

* dont commit vscode fix

* fix release mode, pull out __template_name

* fix axum_desktop

* Fix core tests

* Make most fields of HotReloadedTemplate public for testing

* wip: formatting, compare all diff cases

* slightly smarter diffing for dynamic nodes

* add a comment about generic node diffing

* clean up mutations a bit

* fix load template

* simplify maybe_rebuild

* Restore write mutations flag in web

* write_mutations -> skip_mutations

---------

Co-authored-by: Evan Almloff <evanalmloff@gmail.com>
Jonathan Kelley 10 月之前
父节点
当前提交
0de3bf7aeb
共有 40 个文件被更改,包括 596 次插入1037 次删除
  1. 1 1
      packages/cli/src/assets.rs
  2. 3 3
      packages/core/src/arena.rs
  3. 2 79
      packages/core/src/diff/mod.rs
  4. 74 223
      packages/core/src/diff/node.rs
  5. 0 3
      packages/core/src/error_boundary.rs
  6. 17 17
      packages/core/src/hotreload_utils.rs
  7. 6 95
      packages/core/src/mutations.rs
  8. 18 20
      packages/core/src/nodes.rs
  9. 0 1
      packages/core/src/root_wrapper.rs
  10. 0 21
      packages/core/src/virtual_dom.rs
  11. 10 10
      packages/core/tests/attr_cleanup.rs
  12. 2 2
      packages/core/tests/boolattrs.rs
  13. 1 1
      packages/core/tests/bubble_error.rs
  14. 1 1
      packages/core/tests/children_drop_futures.rs
  15. 3 3
      packages/core/tests/context_api.rs
  16. 12 12
      packages/core/tests/create_dom.rs
  17. 0 32
      packages/core/tests/create_element.rs
  18. 14 10
      packages/core/tests/create_lists.rs
  19. 8 10
      packages/core/tests/create_passthru.rs
  20. 8 8
      packages/core/tests/cycle.rs
  21. 22 12
      packages/core/tests/diff_component.rs
  22. 7 5
      packages/core/tests/diff_dynamic_node.rs
  23. 12 12
      packages/core/tests/diff_element.rs
  24. 32 32
      packages/core/tests/diff_keyed_list.rs
  25. 147 109
      packages/core/tests/diff_unkeyed_list.rs
  26. 6 26
      packages/core/tests/fuzzing.rs
  27. 4 3
      packages/core/tests/kitchen_sink.rs
  28. 4 3
      packages/core/tests/lifecycle.rs
  29. 47 72
      packages/core/tests/suspense.rs
  30. 21 21
      packages/desktop/headless_tests/events.rs
  31. 3 0
      packages/fullstack/examples/desktop/src/main.rs
  32. 5 34
      packages/interpreter/src/unified_bindings.rs
  33. 16 26
      packages/interpreter/src/write_native_mutations.rs
  34. 0 19
      packages/rsx/src/hot_reload/diff.rs
  35. 47 55
      packages/rsx/src/template_body.rs
  36. 2 2
      packages/ssr/src/renderer.rs
  37. 5 7
      packages/web/src/dom.rs
  38. 2 2
      packages/web/src/hydration/hydrate.rs
  39. 4 3
      packages/web/src/lib.rs
  40. 30 42
      packages/web/src/mutations.rs

+ 1 - 1
packages/cli/src/assets.rs

@@ -102,7 +102,7 @@ impl AssetConfigDropGuard {
             None => "/".to_string(),
             None => "/".to_string(),
         };
         };
         manganis_cli_support::Config::default()
         manganis_cli_support::Config::default()
-            .with_assets_serve_location(&base)
+            .with_assets_serve_location(base)
             .save();
             .save();
         Self {}
         Self {}
     }
     }

+ 3 - 3
packages/core/src/arena.rs

@@ -27,10 +27,10 @@ impl MountId {
     pub(crate) const PLACEHOLDER: Self = Self(usize::MAX);
     pub(crate) const PLACEHOLDER: Self = Self(usize::MAX);
 
 
     pub(crate) fn as_usize(self) -> Option<usize> {
     pub(crate) fn as_usize(self) -> Option<usize> {
-        if self == Self::PLACEHOLDER {
-            None
-        } else {
+        if self.mounted() {
             Some(self.0)
             Some(self.0)
+        } else {
+            None
         }
         }
     }
     }
 
 

+ 2 - 79
packages/core/src/diff/mod.rs

@@ -10,11 +10,10 @@
 #![allow(clippy::too_many_arguments)]
 #![allow(clippy::too_many_arguments)]
 
 
 use crate::{
 use crate::{
-    arena::ElementId,
-    innerlude::{ElementRef, MountId, WriteMutations},
+    innerlude::{ElementRef, WriteMutations},
     nodes::VNode,
     nodes::VNode,
     virtual_dom::VirtualDom,
     virtual_dom::VirtualDom,
-    Template, TemplateNode,
+    TemplateNode,
 };
 };
 
 
 mod component;
 mod component;
@@ -34,62 +33,6 @@ impl VirtualDom {
             .sum()
             .sum()
     }
     }
 
 
-    /// Simply replace a placeholder with a list of nodes
-    fn replace_placeholder(
-        &mut self,
-        mut to: Option<&mut impl WriteMutations>,
-        placeholder_id: ElementId,
-        r: &[VNode],
-        parent: Option<ElementRef>,
-    ) {
-        let m = self.create_children(to.as_deref_mut(), r, parent);
-        if let Some(to) = to {
-            self.replace_placeholder_with_nodes_on_stack(to, placeholder_id, m)
-        }
-    }
-
-    fn replace_placeholder_with_nodes_on_stack(
-        &mut self,
-        to: &mut impl WriteMutations,
-        placeholder_id: ElementId,
-        m: usize,
-    ) {
-        to.replace_node_with(placeholder_id, m);
-        self.reclaim(placeholder_id);
-    }
-
-    fn nodes_to_placeholder(
-        &mut self,
-        mut to: Option<&mut impl WriteMutations>,
-        mount: MountId,
-        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
-        self.mounts[mount.0].mounted_dynamic_nodes[dyn_node_idx] = placeholder.0;
-
-        if let Some(to) = to.as_deref_mut() {
-            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: Option<&mut impl WriteMutations>, nodes: &[VNode], m: usize) {
-        debug_assert!(
-            !nodes.is_empty(),
-            "replace_nodes must have at least one node"
-        );
-
-        // We want to optimize the replace case to use one less mutation if possible
-        // Instead of *just* removing it, we can use the replace mutation
-        self.remove_nodes(to, nodes, Some(m));
-    }
-
     /// Remove these nodes from the dom
     /// Remove these nodes from the dom
     /// Wont generate mutations for the inner nodes
     /// Wont generate mutations for the inner nodes
     fn remove_nodes(
     fn remove_nodes(
@@ -103,26 +46,6 @@ impl VirtualDom {
             node.remove_node(self, to.as_deref_mut(), replace_with.filter(|_| last_node));
             node.remove_node(self, to.as_deref_mut(), replace_with.filter(|_| last_node));
         }
         }
     }
     }
-
-    /// 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,
-    ) {
-        if self.templates.contains(&template.name) {
-            return;
-        }
-
-        _ = self.templates.insert(template.name);
-
-        // If it's all dynamic nodes, then we don't need to register it
-        if !template.is_completely_dynamic() {
-            to.register_template(template)
-        }
-    }
 }
 }
 
 
 /// We can apply various optimizations to dynamic nodes that are the single child of their parent.
 /// We can apply various optimizations to dynamic nodes that are the single child of their parent.

+ 74 - 223
packages/core/src/diff/node.rs

@@ -5,7 +5,7 @@ use core::iter::Peekable;
 
 
 use crate::{
 use crate::{
     arena::ElementId,
     arena::ElementId,
-    innerlude::{ElementPath, ElementRef, VComponent, VNodeMount, VText},
+    innerlude::{ElementPath, ElementRef, VNodeMount, VText},
     nodes::DynamicNode,
     nodes::DynamicNode,
     scopes::ScopeId,
     scopes::ScopeId,
     TemplateNode,
     TemplateNode,
@@ -21,9 +21,12 @@ impl VNode {
         // The node we are diffing from should always be mounted
         // The node we are diffing from should always be mounted
         debug_assert!(dom.mounts.get(self.mount.get().0).is_some() || to.is_none());
         debug_assert!(dom.mounts.get(self.mount.get().0).is_some() || to.is_none());
 
 
-        // 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);
+        // If the templates are different, we need to replace the entire template
+        if self.template != new.template {
+            let mount_id = self.mount.get();
+            let mount = &dom.mounts[mount_id.0];
+            let parent = mount.parent;
+            return self.replace(std::slice::from_ref(new), parent, dom, to);
         }
         }
 
 
         let mount_id = self.mount.get();
         let mount_id = self.mount.get();
@@ -73,73 +76,60 @@ impl VNode {
         old_node: &DynamicNode,
         old_node: &DynamicNode,
         new_node: &DynamicNode,
         new_node: &DynamicNode,
         dom: &mut VirtualDom,
         dom: &mut VirtualDom,
-        to: Option<&mut impl WriteMutations>,
+        mut to: Option<&mut impl WriteMutations>,
     ) {
     ) {
         tracing::trace!("diffing dynamic node from {old_node:?} to {new_node:?}");
         tracing::trace!("diffing dynamic node from {old_node:?} to {new_node:?}");
         match (old_node, new_node) {
         match (old_node, new_node) {
             (Text(old), Text(new)) => {
             (Text(old), Text(new)) => {
                 // Diffing text is just a side effect, if we are diffing suspended nodes and are not outputting mutations, we can skip it
                 // Diffing text is just a side effect, if we are diffing suspended nodes and are not outputting mutations, we can skip it
-                if let Some(to) = to{
+                if let Some(to) = to {
                     let mount = &dom.mounts[mount.0];
                     let mount = &dom.mounts[mount.0];
                     self.diff_vtext(to, mount, idx, old, new)
                     self.diff_vtext(to, mount, idx, old, new)
                 }
                 }
-            },
-            (Text(_), Placeholder(_)) => {
-                self.replace_text_with_placeholder(to, mount, idx, dom)
-            },
-            (Placeholder(_), Text(new)) => {
-                self.replace_placeholder_with_text(to, mount, idx, new, dom)
-            },
-            (Placeholder(_), Placeholder(_)) => {},
-            (Fragment(old), Fragment(new)) => dom.diff_non_empty_fragment(to, old, new, Some(self.reference_to_dynamic_node(mount, idx))),
+            }
+            (Placeholder(_), Placeholder(_)) => {}
+            (Fragment(old), Fragment(new)) => dom.diff_non_empty_fragment(
+                to,
+                old,
+                new,
+                Some(self.reference_to_dynamic_node(mount, idx)),
+            ),
             (Component(old), Component(new)) => {
             (Component(old), Component(new)) => {
-				let scope_id = ScopeId(dom.mounts[mount.0].mounted_dynamic_nodes[idx]);
-                self.diff_vcomponent(mount, idx, new, old, scope_id, Some(self.reference_to_dynamic_node(mount, idx)), dom, to)
-            },
-            (Placeholder(_), Fragment(right)) => {
-                let placeholder_id = ElementId(dom.mounts[mount.0].mounted_dynamic_nodes[idx]);
-                dom.replace_placeholder(to, placeholder_id, right, Some(self.reference_to_dynamic_node(mount, idx)))},
-            (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."),
-        };
-    }
+                let scope_id = ScopeId(dom.mounts[mount.0].mounted_dynamic_nodes[idx]);
+                self.diff_vcomponent(
+                    mount,
+                    idx,
+                    new,
+                    old,
+                    scope_id,
+                    Some(self.reference_to_dynamic_node(mount, idx)),
+                    dom,
+                    to,
+                )
+            }
+            (old, new) => {
+                // TODO: we should pass around the mount instead of the mount id
+                // that would make moving the mount around here much easier
 
 
-    /// Replace a text node with a placeholder node
-    pub(crate) fn replace_text_with_placeholder(
-        &self,
-        to: Option<&mut impl WriteMutations>,
-        mount: MountId,
-        idx: usize,
-        dom: &mut VirtualDom,
-    ) {
-        if let Some(to) = to {
-            // Grab the text element id from the mount and replace it with a new placeholder
-            let text_id = ElementId(dom.mounts[mount.0].mounted_dynamic_nodes[idx]);
-            let (id, _) = self.create_dynamic_node_with_path(mount, idx, dom);
-            to.create_placeholder(id);
-            to.replace_node_with(text_id, 1);
-            dom.reclaim(text_id);
-        }
-    }
+                // Mark the mount as unused. When a scope is created, it reads the mount and
+                // if it is the placeholder value, it will create the scope, otherwise it will
+                // reuse the scope
+                let old_mount = dom.mounts[mount.0].mounted_dynamic_nodes[idx];
+                dom.mounts[mount.0].mounted_dynamic_nodes[idx] = usize::MAX;
 
 
-    /// Replace a placeholder node with a text node
-    pub(crate) fn replace_placeholder_with_text(
-        &self,
-        to: Option<&mut impl WriteMutations>,
-        mount: MountId,
-        idx: usize,
-        new: &VText,
-        dom: &mut VirtualDom,
-    ) {
-        if let Some(to) = to {
-            // Grab the placeholder id from the mount and replace it with a new text node
-            let placeholder_id = ElementId(dom.mounts[mount.0].mounted_dynamic_nodes[idx]);
-            let (new_id, _) = self.create_dynamic_node_with_path(mount, idx, dom);
-            to.create_text_node(&new.value, new_id);
-            dom.replace_placeholder_with_nodes_on_stack(to, placeholder_id, 1);
-        }
+                let new_nodes_on_stack =
+                    self.create_dynamic_node(new, mount, idx, dom, to.as_deref_mut());
+
+                // Restore the mount for the scope we are removing
+                let new_mount = dom.mounts[mount.0].mounted_dynamic_nodes[idx];
+                dom.mounts[mount.0].mounted_dynamic_nodes[idx] = old_mount;
+
+                self.remove_dynamic_node(mount, dom, to, true, idx, old, Some(new_nodes_on_stack));
+
+                // Restore the mount for the node we created
+                dom.mounts[mount.0].mounted_dynamic_nodes[idx] = new_mount;
+            }
+        };
     }
     }
 
 
     /// Try to get the dynamic node and its index for a root node
     /// Try to get the dynamic node and its index for a root node
@@ -401,12 +391,6 @@ impl VNode {
         };
         };
     }
     }
 
 
-    fn templates_are_different(&self, other: &VNode) -> bool {
-        let self_node_name = self.template.id();
-        let other_node_name = other.template.id();
-        self_node_name != other_node_name
-    }
-
     pub(super) fn reclaim_attributes(&self, mount: MountId, dom: &mut VirtualDom) {
     pub(super) fn reclaim_attributes(&self, mount: MountId, dom: &mut VirtualDom) {
         let mut next_id = None;
         let mut next_id = None;
         for (idx, path) in self.template.attr_paths.iter().enumerate() {
         for (idx, path) in self.template.attr_paths.iter().enumerate() {
@@ -530,83 +514,6 @@ impl VNode {
         }
         }
     }
     }
 
 
-    /// 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, no_run
-    /// # use dioxus::prelude::*;
-    /// # let enabled = true;
-    /// # #[component]
-    /// # fn Component(enabled_sign: String) -> Element { unimplemented!() }
-    /// 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, no_run
-    /// # use dioxus::prelude::*;
-    /// # #[component]
-    /// # fn Component(enabled_sign: String) -> Element { unimplemented!() }
-    /// # let enabled = true;
-    /// let props = if enabled {
-    ///     ComponentProps { enabled_sign: "abc".to_string() }
-    /// } else {
-    ///     ComponentProps { enabled_sign: "xyz".to_string() }
-    /// };
-    ///
-    /// rsx! {
-    ///     Component { ..props }
-    /// };
-    /// ```
-    pub(crate) fn light_diff_templates(
-        &self,
-        new: &VNode,
-        dom: &mut VirtualDom,
-        mut to: Option<&mut impl WriteMutations>,
-    ) {
-        let mount_id = self.mount.get();
-        let mount = &dom.mounts[mount_id.0];
-        let parent = mount.parent;
-        match matching_components(self, new) {
-            None => self.replace(std::slice::from_ref(new), parent, dom, to),
-            Some(components) => {
-                self.move_mount_to(new, dom);
-
-                for (idx, (old_component, new_component)) in components.into_iter().enumerate() {
-                    let mount = &dom.mounts[mount_id.0];
-                    let scope_id = ScopeId(mount.mounted_dynamic_nodes[idx]);
-                    self.diff_vcomponent(
-                        mount_id,
-                        idx,
-                        new_component,
-                        old_component,
-                        scope_id,
-                        parent,
-                        dom,
-                        to.as_deref_mut(),
-                    )
-                }
-            }
-        }
-    }
-
     /// Create this rsx block. This will create scopes from components that this rsx block contains, but it will not write anything to the DOM.
     /// Create this rsx block. This will create scopes from components that this rsx block contains, but it will not write anything to the DOM.
     pub(crate) fn create(
     pub(crate) fn create(
         &self,
         &self,
@@ -634,13 +541,6 @@ impl VNode {
             });
             });
         }
         }
 
 
-        // If we are outputting mutations, mount the node as well
-        if let Some(to) = to.as_deref_mut() {
-            // 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
         // Walk the roots, creating nodes and assigning IDs
         // nodes in an iterator of (dynamic_node_index, path) and attrs in an iterator of (attr_index, path)
         // nodes in an iterator of (dynamic_node_index, path) and attrs in an iterator of (attr_index, path)
         let mut nodes = template.node_paths.iter().copied().enumerate().peekable();
         let mut nodes = template.node_paths.iter().copied().enumerate().peekable();
@@ -671,7 +571,13 @@ impl VNode {
                         // Take a dynamic node off the depth first iterator
                         // Take a dynamic node off the depth first iterator
                         nodes.next().unwrap();
                         nodes.next().unwrap();
                         // Then mount the node
                         // Then mount the node
-                        self.create_dynamic_node(mount, *id, dom, to.as_deref_mut())
+                        self.create_dynamic_node(
+                            &self.dynamic_nodes[*id],
+                            mount,
+                            *id,
+                            dom,
+                            to.as_deref_mut(),
+                        )
                     }
                     }
                     // For static text and element nodes, just load the template root. This may be a placeholder or just a static node. We now know that each root node has a unique id
                     // For static text and element nodes, just load the template root. This may be a placeholder or just a static node. We now know that each root node has a unique id
                     TemplateNode::Text { .. } | TemplateNode::Element { .. } => {
                     TemplateNode::Text { .. } | TemplateNode::Element { .. } => {
@@ -720,13 +626,13 @@ impl VNode {
 
 
     pub(crate) fn create_dynamic_node(
     pub(crate) fn create_dynamic_node(
         &self,
         &self,
+        node: &DynamicNode,
         mount: MountId,
         mount: MountId,
         dynamic_node_id: usize,
         dynamic_node_id: usize,
         dom: &mut VirtualDom,
         dom: &mut VirtualDom,
         to: Option<&mut impl WriteMutations>,
         to: Option<&mut impl WriteMutations>,
     ) -> usize {
     ) -> usize {
         use DynamicNode::*;
         use DynamicNode::*;
-        let node = &self.dynamic_nodes[dynamic_node_id];
         match node {
         match node {
             Component(component) => {
             Component(component) => {
                 let parent = Some(self.reference_to_dynamic_node(mount, dynamic_node_id));
                 let parent = Some(self.reference_to_dynamic_node(mount, dynamic_node_id));
@@ -786,7 +692,13 @@ impl VNode {
         // Only take nodes that are under this root node
         // Only take nodes that are under this root node
         let from_root_node = |(_, path): &(usize, &[u8])| path.first() == Some(&root_idx);
         let from_root_node = |(_, path): &(usize, &[u8])| path.first() == Some(&root_idx);
         while let Some((dynamic_node_id, path)) = dynamic_nodes_iter.next_if(from_root_node) {
         while let Some((dynamic_node_id, path)) = dynamic_nodes_iter.next_if(from_root_node) {
-            let m = self.create_dynamic_node(mount, dynamic_node_id, dom, to.as_deref_mut());
+            let m = self.create_dynamic_node(
+                &self.dynamic_nodes[dynamic_node_id],
+                mount,
+                dynamic_node_id,
+                dom,
+                to.as_deref_mut(),
+            );
             if let Some(to) = to.as_deref_mut() {
             if let Some(to) = to.as_deref_mut() {
                 // If we actually created real new nodes, we need to replace the placeholder for this dynamic node with the new dynamic nodes
                 // If we actually created real new nodes, we need to replace the placeholder for this dynamic node with the new dynamic nodes
                 if m > 0 {
                 if m > 0 {
@@ -856,7 +768,7 @@ impl VNode {
         let this_id = dom.next_element();
         let this_id = dom.next_element();
         dom.mounts[mount.0].root_ids[root_idx] = this_id;
         dom.mounts[mount.0].root_ids[root_idx] = this_id;
 
 
-        to.load_template(self.template.name, root_idx, this_id);
+        to.load_template(self.template, root_idx, this_id);
 
 
         this_id
         this_id
     }
     }
@@ -888,22 +800,6 @@ impl VNode {
         id
         id
     }
     }
 
 
-    /// Mount a root node and return its ID and the path to the node
-    fn create_dynamic_node_with_path(
-        &self,
-        mount: MountId,
-        idx: usize,
-        dom: &mut VirtualDom,
-    ) -> (ElementId, &'static [u8]) {
-        // Add the mutation to the list
-        let path = self.template.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(
     fn create_dynamic_text(
         &self,
         &self,
         mount: MountId,
         mount: MountId,
@@ -912,19 +808,12 @@ impl VNode {
         dom: &mut VirtualDom,
         dom: &mut VirtualDom,
         to: &mut impl WriteMutations,
         to: &mut impl WriteMutations,
     ) -> usize {
     ) -> usize {
-        let (new_id, path) = self.create_dynamic_node_with_path(mount, idx, dom);
+        let new_id = mount.mount_node(idx, dom);
 
 
         // If this is a root node, the path is empty and we need to create a new text node
         // If this is a root node, the path is empty and we need to create a new text node
-        if path.is_empty() {
-            to.create_text_node(&text.value, new_id);
-            // We create one node on the stack
-            1
-        } else {
-            // Dynamic text nodes always exist as a placeholder text node in the template, we can just hydrate that text node instead of creating a new one
-            to.hydrate_text_node(path, &text.value, new_id);
-            // Since we're hydrating an existing node, we don't create any new nodes
-            0
-        }
+        to.create_text_node(&text.value, new_id);
+        // We create one node on the stack
+        1
     }
     }
 
 
     pub(crate) fn create_placeholder(
     pub(crate) fn create_placeholder(
@@ -934,19 +823,12 @@ impl VNode {
         dom: &mut VirtualDom,
         dom: &mut VirtualDom,
         to: &mut impl WriteMutations,
         to: &mut impl WriteMutations,
     ) -> usize {
     ) -> usize {
-        let (id, path) = self.create_dynamic_node_with_path(mount, idx, dom);
+        let new_id = mount.mount_node(idx, dom);
 
 
         // If this is a root node, the path is empty and we need to create a new placeholder node
         // If this is a root node, the path is empty and we need to create a new placeholder node
-        if path.is_empty() {
-            to.create_placeholder(id);
-            // We create one node on the stack
-            1
-        } else {
-            // 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
-        }
+        to.create_placeholder(new_id);
+        // We create one node on the stack
+        1
     }
     }
 }
 }
 
 
@@ -957,34 +839,3 @@ impl MountId {
         id
         id
     }
     }
 }
 }
-
-fn matching_components<'a>(
-    left: &'a VNode,
-    right: &'a VNode,
-) -> Option<Vec<(&'a VComponent, &'a VComponent)>> {
-    let left_node = left.template;
-    let right_node = right.template;
-    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()
-}

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

@@ -527,7 +527,6 @@ impl<F: Fn(ErrorContext) -> Element + 'static> From<F> for ErrorHandler {
 
 
 fn default_handler(errors: ErrorContext) -> Element {
 fn default_handler(errors: ErrorContext) -> Element {
     static TEMPLATE: Template = Template {
     static TEMPLATE: Template = Template {
-        name: "error_handle.rs:42:5:884",
         roots: &[TemplateNode::Element {
         roots: &[TemplateNode::Element {
             tag: "div",
             tag: "div",
             namespace: None,
             namespace: None,
@@ -549,7 +548,6 @@ fn default_handler(errors: ErrorContext) -> Element {
             .iter()
             .iter()
             .map(|e| {
             .map(|e| {
                 static TEMPLATE: Template = Template {
                 static TEMPLATE: Template = Template {
-                    name: "error_handle.rs:43:5:884",
                     roots: &[TemplateNode::Element {
                     roots: &[TemplateNode::Element {
                         tag: "pre",
                         tag: "pre",
                         namespace: None,
                         namespace: None,
@@ -753,7 +751,6 @@ pub fn ErrorBoundary(props: ErrorBoundaryProps) -> Element {
     if errors.is_empty() {
     if errors.is_empty() {
         std::result::Result::Ok({
         std::result::Result::Ok({
             static TEMPLATE: Template = Template {
             static TEMPLATE: Template = Template {
-                name: "examples/error_handle.rs:81:17:2342",
                 roots: &[TemplateNode::Dynamic { id: 0usize }],
                 roots: &[TemplateNode::Dynamic { id: 0usize }],
                 node_paths: &[&[0u8]],
                 node_paths: &[&[0u8]],
                 attr_paths: &[],
                 attr_paths: &[],

+ 17 - 17
packages/core/src/hotreload_utils.rs

@@ -235,15 +235,6 @@ impl DynamicValuePool {
 
 
     pub fn render_with(&mut self, hot_reload: &HotReloadedTemplate) -> VNode {
     pub fn render_with(&mut self, hot_reload: &HotReloadedTemplate) -> VNode {
         // Get the node_paths from a depth first traversal of the template
         // Get the node_paths from a depth first traversal of the template
-        let node_paths = hot_reload.node_paths();
-        let attr_paths = hot_reload.attr_paths();
-
-        let template = Template {
-            name: hot_reload.name,
-            roots: hot_reload.roots,
-            node_paths,
-            attr_paths,
-        };
         let key = hot_reload
         let key = hot_reload
             .key
             .key
             .as_ref()
             .as_ref()
@@ -259,7 +250,7 @@ impl DynamicValuePool {
             .map(|attr| self.render_attribute(attr))
             .map(|attr| self.render_attribute(attr))
             .collect();
             .collect();
 
 
-        VNode::new(key, template, dynamic_nodes, dynamic_attrs)
+        VNode::new(key, hot_reload.template, dynamic_nodes, dynamic_attrs)
     }
     }
 
 
     fn render_dynamic_node(&mut self, node: &HotReloadDynamicNode) -> DynamicNode {
     fn render_dynamic_node(&mut self, node: &HotReloadDynamicNode) -> DynamicNode {
@@ -318,8 +309,8 @@ pub struct HotReloadTemplateWithLocation {
 #[doc(hidden)]
 #[doc(hidden)]
 #[derive(Debug, PartialEq, Clone)]
 #[derive(Debug, PartialEq, Clone)]
 #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
 #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
+#[cfg_attr(feature = "serialize", serde(bound(deserialize = "'de: 'static")))]
 pub struct HotReloadedTemplate {
 pub struct HotReloadedTemplate {
-    pub name: &'static str,
     pub key: Option<FmtedSegments>,
     pub key: Option<FmtedSegments>,
     pub dynamic_nodes: Vec<HotReloadDynamicNode>,
     pub dynamic_nodes: Vec<HotReloadDynamicNode>,
     pub dynamic_attributes: Vec<HotReloadDynamicAttribute>,
     pub dynamic_attributes: Vec<HotReloadDynamicAttribute>,
@@ -329,28 +320,37 @@ pub struct HotReloadedTemplate {
         serde(deserialize_with = "crate::nodes::deserialize_leaky")
         serde(deserialize_with = "crate::nodes::deserialize_leaky")
     )]
     )]
     pub roots: &'static [TemplateNode],
     pub roots: &'static [TemplateNode],
+    /// The template that is computed from the hot reload roots
+    template: Template,
 }
 }
 
 
 impl HotReloadedTemplate {
 impl HotReloadedTemplate {
     pub fn new(
     pub fn new(
-        name: &'static str,
         key: Option<FmtedSegments>,
         key: Option<FmtedSegments>,
         dynamic_nodes: Vec<HotReloadDynamicNode>,
         dynamic_nodes: Vec<HotReloadDynamicNode>,
         dynamic_attributes: Vec<HotReloadDynamicAttribute>,
         dynamic_attributes: Vec<HotReloadDynamicAttribute>,
         component_values: Vec<HotReloadLiteral>,
         component_values: Vec<HotReloadLiteral>,
         roots: &'static [TemplateNode],
         roots: &'static [TemplateNode],
     ) -> Self {
     ) -> Self {
+        let node_paths = Self::node_paths(roots);
+        let attr_paths = Self::attr_paths(roots);
+
+        let template = Template {
+            roots,
+            node_paths,
+            attr_paths,
+        };
         Self {
         Self {
-            name,
             key,
             key,
             dynamic_nodes,
             dynamic_nodes,
             dynamic_attributes,
             dynamic_attributes,
             component_values,
             component_values,
             roots,
             roots,
+            template,
         }
         }
     }
     }
 
 
-    fn node_paths(&self) -> &'static [&'static [u8]] {
+    fn node_paths(roots: &'static [TemplateNode]) -> &'static [&'static [u8]] {
         fn add_node_paths(
         fn add_node_paths(
             roots: &[TemplateNode],
             roots: &[TemplateNode],
             node_paths: &mut Vec<&'static [u8]>,
             node_paths: &mut Vec<&'static [u8]>,
@@ -373,12 +373,12 @@ impl HotReloadedTemplate {
         }
         }
 
 
         let mut node_paths = Vec::new();
         let mut node_paths = Vec::new();
-        add_node_paths(self.roots, &mut node_paths, Vec::new());
+        add_node_paths(roots, &mut node_paths, Vec::new());
         let leaked: &'static [&'static [u8]] = Box::leak(node_paths.into_boxed_slice());
         let leaked: &'static [&'static [u8]] = Box::leak(node_paths.into_boxed_slice());
         leaked
         leaked
     }
     }
 
 
-    fn attr_paths(&self) -> &'static [&'static [u8]] {
+    fn attr_paths(roots: &'static [TemplateNode]) -> &'static [&'static [u8]] {
         fn add_attr_paths(
         fn add_attr_paths(
             roots: &[TemplateNode],
             roots: &[TemplateNode],
             attr_paths: &mut Vec<&'static [u8]>,
             attr_paths: &mut Vec<&'static [u8]>,
@@ -403,7 +403,7 @@ impl HotReloadedTemplate {
         }
         }
 
 
         let mut attr_paths = Vec::new();
         let mut attr_paths = Vec::new();
-        add_attr_paths(self.roots, &mut attr_paths, Vec::new());
+        add_attr_paths(roots, &mut attr_paths, Vec::new());
         let leaked: &'static [&'static [u8]] = Box::leak(attr_paths.into_boxed_slice());
         let leaked: &'static [&'static [u8]] = Box::leak(attr_paths.into_boxed_slice());
         leaked
         leaked
     }
     }

+ 6 - 95
packages/core/src/mutations.rs

@@ -1,6 +1,4 @@
-use rustc_hash::FxHashSet;
-
-use crate::{arena::ElementId, AttributeValue, ScopeId, Template};
+use crate::{arena::ElementId, AttributeValue, Template};
 
 
 /// Something that can handle the mutations that are generated by the diffing process and apply them to the Real DOM
 /// Something that can handle the mutations that are generated by the diffing process and apply them to the Real DOM
 ///
 ///
@@ -14,9 +12,6 @@ use crate::{arena::ElementId, AttributeValue, ScopeId, Template};
 ///
 ///
 /// Mutations are the only link between the RealDOM and the VirtualDOM.
 /// Mutations are the only link between the RealDOM and the VirtualDOM.
 pub trait WriteMutations {
 pub trait WriteMutations {
-    /// Register a template with the renderer
-    fn register_template(&mut self, template: Template);
-
     /// Add these m children to the target element
     /// Add these m children to the target element
     ///
     ///
     /// Id: The ID of the element being mounted to
     /// Id: The ID of the element being mounted to
@@ -45,15 +40,6 @@ pub trait WriteMutations {
     /// Id: The ID we're assigning to this specific text nodes. This will be used later to modify the element or replace it with another element.
     /// Id: The ID we're assigning to this specific text nodes. This will be used later to modify the element or replace it with another element.
     fn create_text_node(&mut self, value: &str, id: ElementId);
     fn create_text_node(&mut self, value: &str, id: ElementId);
 
 
-    /// Hydrate an existing text node at the given path with the given text.
-    ///
-    /// Assign this text node the given ID since we will likely need to modify this text at a later point
-    ///
-    /// Path: The path of the child of the topmost node on the stack. A path of `[]` represents the topmost node. A path of `[0]` represents the first child. `[0,1,2]` represents 1st child's 2nd child's 3rd child.
-    /// Value: The value of the textnode that we want to set the placeholder with
-    /// Id: The ID we're assigning to this specific text nodes. This will be used later to modify the element or replace it with another element.
-    fn hydrate_text_node(&mut self, path: &'static [u8], value: &str, id: ElementId);
-
     /// Load and clone an existing node from a template saved under that specific name
     /// Load and clone an existing node from a template saved under that specific name
     ///
     ///
     /// Dioxus guarantees that the renderer will have already been provided the template.
     /// Dioxus guarantees that the renderer will have already been provided the template.
@@ -62,7 +48,7 @@ pub trait WriteMutations {
     /// Name: The unique "name" of the template based on the template location. When paired with `rsx!`, this is autogenerated
     /// Name: The unique "name" of the template based on the template location. When paired with `rsx!`, this is autogenerated
     /// Index: The index root we loading from the template. The template is stored as a list of nodes. This index represents the position of that root
     /// Index: The index root we loading from the template. The template is stored as a list of nodes. This index represents the position of that root
     /// Id: The ID we're assigning to this element being loaded from the template (This will be used later to move the element around in lists)
     /// Id: The ID we're assigning to this element being loaded from the template (This will be used later to move the element around in lists)
-    fn load_template(&mut self, name: &'static str, index: usize, id: ElementId);
+    fn load_template(&mut self, template: Template, index: usize, id: ElementId);
 
 
     /// Replace the target element (given by its ID) with the topmost m nodes on the stack
     /// Replace the target element (given by its ID) with the topmost m nodes on the stack
     ///
     ///
@@ -129,12 +115,6 @@ pub trait WriteMutations {
     ///
     ///
     /// Id: The ID of the root node to push.
     /// Id: The ID of the root node to push.
     fn push_root(&mut self, id: ElementId);
     fn push_root(&mut self, id: ElementId);
-
-    /// Swap to a new subtree
-    fn swap_subtree(&mut self, _subtree_index: usize) {}
-
-    /// Mark a scope as dirty
-    fn mark_scope_dirty(&mut self, _scope_id: ScopeId) {}
 }
 }
 
 
 /// A `Mutation` represents a single instruction for the renderer to use to modify the UI tree to match the state
 /// A `Mutation` represents a single instruction for the renderer to use to modify the UI tree to match the state
@@ -156,8 +136,6 @@ pub enum Mutation {
     ///
     ///
     /// The path is in the form of a list of indices based on children. Templates cannot have more than 255 children per
     /// The path is in the form of a list of indices based on children. Templates cannot have more than 255 children per
     /// element, hence the use of a single byte.
     /// element, hence the use of a single byte.
-    ///
-    ///
     AssignId {
     AssignId {
         /// The path of the child of the topmost node on the stack
         /// The path of the child of the topmost node on the stack
         ///
         ///
@@ -192,33 +170,11 @@ pub enum Mutation {
         id: ElementId,
         id: ElementId,
     },
     },
 
 
-    /// Hydrate an existing text node at the given path with the given text.
-    ///
-    /// Assign this text node the given ID since we will likely need to modify this text at a later point
-    HydrateText {
-        /// The path of the child of the topmost node on the stack
-        ///
-        /// A path of `[]` represents the topmost node. A path of `[0]` represents the first child.
-        /// `[0,1,2]` represents 1st child's 2nd child's 3rd child.
-        path: &'static [u8],
-
-        /// The value of the textnode that we want to set the placeholder with
-        value: String,
-
-        /// The ID we're assigning to this specific text nodes
-        ///
-        /// This will be used later to modify the element or replace it with another element.
-        id: ElementId,
-    },
-
-    /// Load and clone an existing node from a template saved under that specific name
+    /// Load and clone an existing node from a template with a given ID
     ///
     ///
     /// Dioxus guarantees that the renderer will have already been provided the template.
     /// Dioxus guarantees that the renderer will have already been provided the template.
     /// When the template is picked up in the template list, it should be saved under its "name" - here, the name
     /// When the template is picked up in the template list, it should be saved under its "name" - here, the name
     LoadTemplate {
     LoadTemplate {
-        /// The "name" of the template. When paired with `rsx!`, this is autogenerated
-        name: &'static str,
-
         /// Which root are we loading from the template?
         /// Which root are we loading from the template?
         ///
         ///
         /// The template is stored as a list of nodes. This index represents the position of that root
         /// The template is stored as a list of nodes. This index represents the position of that root
@@ -328,38 +284,11 @@ pub enum Mutation {
 /// A static list of mutations that can be applied to the DOM. Note: this list does not contain any `Any` attribute values
 /// A static list of mutations that can be applied to the DOM. Note: this list does not contain any `Any` attribute values
 #[derive(Debug, PartialEq, Default)]
 #[derive(Debug, PartialEq, Default)]
 pub struct Mutations {
 pub struct Mutations {
-    /// The list of Scopes that were diffed, created, and removed during the Diff process.
-    pub dirty_scopes: FxHashSet<ScopeId>,
-
-    /// Any templates encountered while diffing the DOM.
-    ///
-    /// These must be loaded into a cache before applying the edits
-    pub templates: Vec<Template>,
-
     /// Any mutations required to patch the renderer to match the layout of the VirtualDom
     /// Any mutations required to patch the renderer to match the layout of the VirtualDom
     pub edits: Vec<Mutation>,
     pub edits: Vec<Mutation>,
 }
 }
 
 
-impl Mutations {
-    /// Rewrites IDs to just be "template", so you can compare the mutations
-    ///
-    /// Used really only for testing
-    pub fn sanitize(mut self) -> Self {
-        for edit in self.edits.iter_mut() {
-            if let Mutation::LoadTemplate { name, .. } = edit {
-                *name = "template"
-            }
-        }
-
-        self
-    }
-}
-
 impl WriteMutations for Mutations {
 impl WriteMutations for Mutations {
-    fn register_template(&mut self, template: Template) {
-        self.templates.push(template)
-    }
-
     fn append_children(&mut self, id: ElementId, m: usize) {
     fn append_children(&mut self, id: ElementId, m: usize) {
         self.edits.push(Mutation::AppendChildren { id, m })
         self.edits.push(Mutation::AppendChildren { id, m })
     }
     }
@@ -379,16 +308,8 @@ impl WriteMutations for Mutations {
         })
         })
     }
     }
 
 
-    fn hydrate_text_node(&mut self, path: &'static [u8], value: &str, id: ElementId) {
-        self.edits.push(Mutation::HydrateText {
-            path,
-            value: value.into(),
-            id,
-        })
-    }
-
-    fn load_template(&mut self, name: &'static str, index: usize, id: ElementId) {
-        self.edits.push(Mutation::LoadTemplate { name, index, id })
+    fn load_template(&mut self, _template: Template, index: usize, id: ElementId) {
+        self.edits.push(Mutation::LoadTemplate { index, id })
     }
     }
 
 
     fn replace_node_with(&mut self, id: ElementId, m: usize) {
     fn replace_node_with(&mut self, id: ElementId, m: usize) {
@@ -457,20 +378,12 @@ impl WriteMutations for Mutations {
     fn push_root(&mut self, id: ElementId) {
     fn push_root(&mut self, id: ElementId) {
         self.edits.push(Mutation::PushRoot { id })
         self.edits.push(Mutation::PushRoot { id })
     }
     }
-
-    fn swap_subtree(&mut self, _subtree_index: usize) {}
-
-    fn mark_scope_dirty(&mut self, scope_id: ScopeId) {
-        self.dirty_scopes.insert(scope_id);
-    }
 }
 }
 
 
 /// A struct that ignores all mutations
 /// A struct that ignores all mutations
 pub struct NoOpMutations;
 pub struct NoOpMutations;
 
 
 impl WriteMutations for NoOpMutations {
 impl WriteMutations for NoOpMutations {
-    fn register_template(&mut self, _: Template) {}
-
     fn append_children(&mut self, _: ElementId, _: usize) {}
     fn append_children(&mut self, _: ElementId, _: usize) {}
 
 
     fn assign_node_id(&mut self, _: &'static [u8], _: ElementId) {}
     fn assign_node_id(&mut self, _: &'static [u8], _: ElementId) {}
@@ -479,9 +392,7 @@ impl WriteMutations for NoOpMutations {
 
 
     fn create_text_node(&mut self, _: &str, _: ElementId) {}
     fn create_text_node(&mut self, _: &str, _: ElementId) {}
 
 
-    fn hydrate_text_node(&mut self, _: &'static [u8], _: &str, _: ElementId) {}
-
-    fn load_template(&mut self, _: &'static str, _: usize, _: ElementId) {}
+    fn load_template(&mut self, _: Template, _: usize, _: ElementId) {}
 
 
     fn replace_node_with(&mut self, _: ElementId, _: usize) {}
     fn replace_node_with(&mut self, _: ElementId, _: usize) {}
 
 

+ 18 - 20
packages/core/src/nodes.rs

@@ -15,8 +15,6 @@ use std::{
     fmt::{Arguments, Debug},
     fmt::{Arguments, Debug},
 };
 };
 
 
-pub type TemplateId = &'static str;
-
 /// The actual state of the component's most recent computation
 /// The actual state of the component's most recent computation
 ///
 ///
 /// If the component returned early (e.g. `return None`), this will be Aborted(None)
 /// If the component returned early (e.g. `return None`), this will be Aborted(None)
@@ -252,7 +250,6 @@ impl VNode {
                     dynamic_nodes: Box::new([DynamicNode::Placeholder(Default::default())]),
                     dynamic_nodes: Box::new([DynamicNode::Placeholder(Default::default())]),
                     dynamic_attrs: Box::new([]),
                     dynamic_attrs: Box::new([]),
                     template: Template {
                     template: Template {
-                        name: "packages/core/nodes.rs:198:0:0",
                         roots: &[TemplateNode::Dynamic { id: 0 }],
                         roots: &[TemplateNode::Dynamic { id: 0 }],
                         node_paths: &[&[0]],
                         node_paths: &[&[0]],
                         attr_paths: &[],
                         attr_paths: &[],
@@ -345,17 +342,9 @@ impl VNode {
 /// For this to work properly, the [`Template::name`] *must* be unique across your entire project. This can be done via variety of
 /// For this to work properly, the [`Template::name`] *must* be unique across your entire project. This can be done via variety of
 /// ways, with the suggested approach being the unique code location (file, line, col, etc).
 /// ways, with the suggested approach being the unique code location (file, line, col, etc).
 #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
 #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
-#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq, PartialOrd, Ord)]
+#[cfg_attr(feature = "serialize", serde(bound(deserialize = "'de: 'static")))]
+#[derive(Debug, Clone, Copy, Eq, PartialOrd, Ord)]
 pub struct Template {
 pub struct Template {
-    /// The name of the template. This must be unique across your entire program for template diffing to work properly
-    ///
-    /// If two templates have the same name, it's likely that Dioxus will panic when diffing.
-    #[cfg_attr(
-        feature = "serialize",
-        serde(deserialize_with = "deserialize_string_leaky")
-    )]
-    pub name: &'static str,
-
     /// The list of template nodes that make up the template
     /// The list of template nodes that make up the template
     ///
     ///
     /// Unlike react, calls to `rsx!` can have multiple roots. This list supports that paradigm.
     /// Unlike react, calls to `rsx!` can have multiple roots. This list supports that paradigm.
@@ -383,6 +372,22 @@ pub struct Template {
     pub attr_paths: &'static [&'static [u8]],
     pub attr_paths: &'static [&'static [u8]],
 }
 }
 
 
+impl std::hash::Hash for Template {
+    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+        std::ptr::hash(self.roots as *const _, state);
+        std::ptr::hash(self.node_paths as *const _, state);
+        std::ptr::hash(self.attr_paths as *const _, state);
+    }
+}
+
+impl PartialEq for Template {
+    fn eq(&self, other: &Self) -> bool {
+        std::ptr::eq(self.roots as *const _, other.roots as *const _)
+            && std::ptr::eq(self.node_paths as *const _, other.node_paths as *const _)
+            && std::ptr::eq(self.attr_paths as *const _, other.attr_paths as *const _)
+    }
+}
+
 #[cfg(feature = "serialize")]
 #[cfg(feature = "serialize")]
 pub(crate) fn deserialize_string_leaky<'a, 'de, D>(deserializer: D) -> Result<&'a str, D::Error>
 pub(crate) fn deserialize_string_leaky<'a, 'de, D>(deserializer: D) -> Result<&'a str, D::Error>
 where
 where
@@ -442,13 +447,6 @@ impl Template {
         use TemplateNode::*;
         use TemplateNode::*;
         self.roots.iter().all(|root| matches!(root, Dynamic { .. }))
         self.roots.iter().all(|root| matches!(root, Dynamic { .. }))
     }
     }
-
-    /// Get a unique id for this template. If the id between two templates are different, the contents of the template may be different.
-    pub fn id(&self) -> usize {
-        // We compare the template name by pointer so that the id is different after hot reloading even if the name is the same
-        let ptr: *const str = self.name;
-        ptr as *const () as usize
-    }
 }
 }
 
 
 /// A statically known node in a layout.
 /// A statically known node in a layout.

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

@@ -5,7 +5,6 @@ use crate::{prelude::*, properties::RootProps, DynamicNode, VComponent};
 #[allow(clippy::let_and_return)]
 #[allow(clippy::let_and_return)]
 pub(crate) fn RootScopeWrapper(props: RootProps<VComponent>) -> Element {
 pub(crate) fn RootScopeWrapper(props: RootProps<VComponent>) -> Element {
     static TEMPLATE: Template = Template {
     static TEMPLATE: Template = Template {
-        name: "root_wrapper.rs:16:5:561",
         roots: &[TemplateNode::Dynamic { id: 0usize }],
         roots: &[TemplateNode::Dynamic { id: 0usize }],
         node_paths: &[&[0u8]],
         node_paths: &[&[0u8]],
         attr_paths: &[],
         attr_paths: &[],

+ 0 - 21
packages/core/src/virtual_dom.rs

@@ -11,14 +11,12 @@ use crate::{
         ElementRef, NoOpMutations, SchedulerMsg, ScopeOrder, ScopeState, VNodeMount, VProps,
         ElementRef, NoOpMutations, SchedulerMsg, ScopeOrder, ScopeState, VNodeMount, VProps,
         WriteMutations,
         WriteMutations,
     },
     },
-    nodes::{Template, TemplateId},
     runtime::{Runtime, RuntimeGuard},
     runtime::{Runtime, RuntimeGuard},
     scopes::ScopeId,
     scopes::ScopeId,
     AttributeValue, ComponentFunction, Element, Event, Mutations,
     AttributeValue, ComponentFunction, Element, Event, Mutations,
 };
 };
 use crate::{Task, VComponent};
 use crate::{Task, VComponent};
 use futures_util::StreamExt;
 use futures_util::StreamExt;
-use rustc_hash::FxHashSet;
 use slab::Slab;
 use slab::Slab;
 use std::collections::BTreeSet;
 use std::collections::BTreeSet;
 use std::{any::Any, rc::Rc};
 use std::{any::Any, rc::Rc};
@@ -208,12 +206,6 @@ pub struct VirtualDom {
 
 
     pub(crate) dirty_scopes: BTreeSet<ScopeOrder>,
     pub(crate) dirty_scopes: BTreeSet<ScopeOrder>,
 
 
-    // A map of templates we have sent to the renderer
-    pub(crate) templates: FxHashSet<TemplateId>,
-
-    // Templates changes that are queued for the next render
-    pub(crate) queued_templates: Vec<Template>,
-
     // The element ids that are used in the renderer
     // The element ids that are used in the renderer
     // These mark a specific place in a whole rsx block
     // These mark a specific place in a whole rsx block
     pub(crate) elements: Slab<Option<ElementRef>>,
     pub(crate) elements: Slab<Option<ElementRef>>,
@@ -336,8 +328,6 @@ impl VirtualDom {
             runtime: Runtime::new(tx),
             runtime: Runtime::new(tx),
             scopes: Default::default(),
             scopes: Default::default(),
             dirty_scopes: Default::default(),
             dirty_scopes: Default::default(),
-            templates: Default::default(),
-            queued_templates: Default::default(),
             elements: Default::default(),
             elements: Default::default(),
             mounts: Default::default(),
             mounts: Default::default(),
             resolved_scopes: Default::default(),
             resolved_scopes: Default::default(),
@@ -612,7 +602,6 @@ impl VirtualDom {
     /// ```
     /// ```
     #[instrument(skip(self, to), level = "trace", name = "VirtualDom::rebuild")]
     #[instrument(skip(self, to), level = "trace", name = "VirtualDom::rebuild")]
     pub fn rebuild(&mut self, to: &mut impl WriteMutations) {
     pub fn rebuild(&mut self, to: &mut impl WriteMutations) {
-        self.flush_templates(to);
         let _runtime = RuntimeGuard::new(self.runtime.clone());
         let _runtime = RuntimeGuard::new(self.runtime.clone());
         let new_nodes = self.run_scope(ScopeId::ROOT);
         let new_nodes = self.run_scope(ScopeId::ROOT);
 
 
@@ -628,8 +617,6 @@ impl VirtualDom {
     /// suspended subtrees.
     /// suspended subtrees.
     #[instrument(skip(self, to), level = "trace", name = "VirtualDom::render_immediate")]
     #[instrument(skip(self, to), level = "trace", name = "VirtualDom::render_immediate")]
     pub fn render_immediate(&mut self, to: &mut impl WriteMutations) {
     pub fn render_immediate(&mut self, to: &mut impl WriteMutations) {
-        self.flush_templates(to);
-
         // Process any events that might be pending in the queue
         // Process any events that might be pending in the queue
         // Signals marked with .write() need a chance to be handled by the effect driver
         // Signals marked with .write() need a chance to be handled by the effect driver
         // This also processes futures which might progress into immediately rerunning a scope
         // This also processes futures which might progress into immediately rerunning a scope
@@ -785,14 +772,6 @@ impl VirtualDom {
         self.runtime.clone()
         self.runtime.clone()
     }
     }
 
 
-    /// Flush any queued template changes
-    #[instrument(skip(self, to), level = "trace", name = "VirtualDom::flush_templates")]
-    fn flush_templates(&mut self, to: &mut impl WriteMutations) {
-        for template in self.queued_templates.drain(..) {
-            to.register_template(template);
-        }
-    }
-
     /*
     /*
     ------------------------
     ------------------------
     The algorithm works by walking through the list of dynamic attributes, checking their paths, and breaking when
     The algorithm works by walking through the list of dynamic attributes, checking their paths, and breaking when

+ 10 - 10
packages/core/tests/attr_cleanup.rs

@@ -21,18 +21,18 @@ fn attrs_cycle() {
     });
     });
 
 
     assert_eq!(
     assert_eq!(
-        dom.rebuild_to_vec().sanitize().edits,
+        dom.rebuild_to_vec().edits,
         [
         [
-            LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
+            LoadTemplate { index: 0, id: ElementId(1,) },
             AppendChildren { m: 1, id: ElementId(0) },
             AppendChildren { m: 1, id: ElementId(0) },
         ]
         ]
     );
     );
 
 
     dom.mark_dirty(ScopeId::APP);
     dom.mark_dirty(ScopeId::APP);
     assert_eq!(
     assert_eq!(
-        dom.render_immediate_to_vec().sanitize().edits,
+        dom.render_immediate_to_vec().edits,
         [
         [
-            LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
+            LoadTemplate { index: 0, id: ElementId(2,) },
             AssignId { path: &[0,], id: ElementId(3,) },
             AssignId { path: &[0,], id: ElementId(3,) },
             SetAttribute { name: "class", value: "1".into_value(), id: ElementId(3,), ns: None },
             SetAttribute { name: "class", value: "1".into_value(), id: ElementId(3,), ns: None },
             SetAttribute { name: "id", value: "1".into_value(), id: ElementId(3,), ns: None },
             SetAttribute { name: "id", value: "1".into_value(), id: ElementId(3,), ns: None },
@@ -42,18 +42,18 @@ fn attrs_cycle() {
 
 
     dom.mark_dirty(ScopeId::APP);
     dom.mark_dirty(ScopeId::APP);
     assert_eq!(
     assert_eq!(
-        dom.render_immediate_to_vec().sanitize().edits,
+        dom.render_immediate_to_vec().edits,
         [
         [
-            LoadTemplate { name: "template", index: 0, id: ElementId(1) },
+            LoadTemplate { index: 0, id: ElementId(1) },
             ReplaceWith { id: ElementId(2), m: 1 }
             ReplaceWith { id: ElementId(2), m: 1 }
         ]
         ]
     );
     );
 
 
     dom.mark_dirty(ScopeId::APP);
     dom.mark_dirty(ScopeId::APP);
     assert_eq!(
     assert_eq!(
-        dom.render_immediate_to_vec().sanitize().edits,
+        dom.render_immediate_to_vec().edits,
         [
         [
-            LoadTemplate { name: "template", index: 0, id: ElementId(2) },
+            LoadTemplate { index: 0, id: ElementId(2) },
             AssignId { path: &[0], id: ElementId(3) },
             AssignId { path: &[0], id: ElementId(3) },
             SetAttribute {
             SetAttribute {
                 name: "class",
                 name: "class",
@@ -74,9 +74,9 @@ fn attrs_cycle() {
     // we take the node taken by attributes since we reused it
     // we take the node taken by attributes since we reused it
     dom.mark_dirty(ScopeId::APP);
     dom.mark_dirty(ScopeId::APP);
     assert_eq!(
     assert_eq!(
-        dom.render_immediate_to_vec().sanitize().edits,
+        dom.render_immediate_to_vec().edits,
         [
         [
-            LoadTemplate { name: "template", index: 0, id: ElementId(1) },
+            LoadTemplate { index: 0, id: ElementId(1) },
             ReplaceWith { id: ElementId(2), m: 1 }
             ReplaceWith { id: ElementId(2), m: 1 }
         ]
         ]
     );
     );

+ 2 - 2
packages/core/tests/boolattrs.rs

@@ -6,9 +6,9 @@ fn bool_test() {
     let mut app = VirtualDom::new(|| rsx!(div { hidden: false }));
     let mut app = VirtualDom::new(|| rsx!(div { hidden: false }));
 
 
     assert_eq!(
     assert_eq!(
-        app.rebuild_to_vec().sanitize().edits,
+        app.rebuild_to_vec().edits,
         [
         [
-            LoadTemplate { name: "template", index: 0, id: ElementId(1) },
+            LoadTemplate { index: 0, id: ElementId(1) },
             SetAttribute {
             SetAttribute {
                 name: "hidden",
                 name: "hidden",
                 value: dioxus_core::AttributeValue::Bool(false),
                 value: dioxus_core::AttributeValue::Bool(false),

+ 1 - 1
packages/core/tests/bubble_error.rs

@@ -19,7 +19,7 @@ fn bubbles_error() {
     let mut dom = VirtualDom::new(app);
     let mut dom = VirtualDom::new(app);
 
 
     {
     {
-        let _edits = dom.rebuild_to_vec().sanitize();
+        let _edits = dom.rebuild_to_vec();
     }
     }
 
 
     dom.mark_dirty(ScopeId::APP);
     dom.mark_dirty(ScopeId::APP);

+ 1 - 1
packages/core/tests/children_drop_futures.rs

@@ -10,7 +10,7 @@ async fn child_futures_drop_first() {
 
 
     fn app() -> Element {
     fn app() -> Element {
         if generation() == 0 {
         if generation() == 0 {
-            rsx! {Child {}}
+            rsx! { Child {} }
         } else {
         } else {
             rsx! {}
             rsx! {}
         }
         }

+ 3 - 3
packages/core/tests/context_api.rs

@@ -20,7 +20,7 @@ fn state_shares() {
 
 
     let mut dom = VirtualDom::new(app);
     let mut dom = VirtualDom::new(app);
     assert_eq!(
     assert_eq!(
-        dom.rebuild_to_vec().sanitize().edits,
+        dom.rebuild_to_vec().edits,
         [
         [
             CreateTextNode { value: "Value is 0".to_string(), id: ElementId(1,) },
             CreateTextNode { value: "Value is 0".to_string(), id: ElementId(1,) },
             AppendChildren { m: 1, id: ElementId(0) },
             AppendChildren { m: 1, id: ElementId(0) },
@@ -41,7 +41,7 @@ fn state_shares() {
 
 
     dom.mark_dirty(ScopeId(ScopeId::APP.0 + 2));
     dom.mark_dirty(ScopeId(ScopeId::APP.0 + 2));
     assert_eq!(
     assert_eq!(
-        dom.render_immediate_to_vec().sanitize().edits,
+        dom.render_immediate_to_vec().edits,
         [SetText { value: "Value is 2".to_string(), id: ElementId(1,) },]
         [SetText { value: "Value is 2".to_string(), id: ElementId(1,) },]
     );
     );
 
 
@@ -49,7 +49,7 @@ fn state_shares() {
     dom.mark_dirty(ScopeId(ScopeId::APP.0 + 2));
     dom.mark_dirty(ScopeId(ScopeId::APP.0 + 2));
     let edits = dom.render_immediate_to_vec();
     let edits = dom.render_immediate_to_vec();
     assert_eq!(
     assert_eq!(
-        edits.sanitize().edits,
+        edits.edits,
         [SetText { value: "Value is 3".to_string(), id: ElementId(1,) },]
         [SetText { value: "Value is 3".to_string(), id: ElementId(1,) },]
     );
     );
 }
 }

+ 12 - 12
packages/core/tests/create_dom.rs

@@ -17,13 +17,13 @@ fn test_original_diff() {
         }
         }
     });
     });
 
 
-    let edits = dom.rebuild_to_vec().sanitize();
+    let edits = dom.rebuild_to_vec();
 
 
     assert_eq!(
     assert_eq!(
         edits.edits,
         edits.edits,
         [
         [
             // add to root
             // add to root
-            LoadTemplate { name: "template", index: 0, id: ElementId(1) },
+            LoadTemplate { index: 0, id: ElementId(1) },
             AppendChildren { m: 1, id: ElementId(0) }
             AppendChildren { m: 1, id: ElementId(0) }
         ]
         ]
     )
     )
@@ -46,7 +46,7 @@ fn create() {
         }
         }
     });
     });
 
 
-    let _edits = dom.rebuild_to_vec().sanitize();
+    let _edits = dom.rebuild_to_vec();
 
 
     // todo: we don't test template mutations anymore since the templates are passed along
     // todo: we don't test template mutations anymore since the templates are passed along
 
 
@@ -64,11 +64,11 @@ fn create() {
     //         AppendChildren { m: 1 },
     //         AppendChildren { m: 1 },
     //         AppendChildren { m: 2 },
     //         AppendChildren { m: 2 },
     //         AppendChildren { m: 1 },
     //         AppendChildren { m: 1 },
-    //         SaveTemplate { name: "template", m: 1 },
+    //         SaveTemplate {  m: 1 },
     //         // The fragment child template
     //         // The fragment child template
     //         CreateStaticText { value: "hello" },
     //         CreateStaticText { value: "hello" },
     //         CreateStaticText { value: "world" },
     //         CreateStaticText { value: "world" },
-    //         SaveTemplate { name: "template", m: 2 },
+    //         SaveTemplate {  m: 2 },
     //     ]
     //     ]
     // );
     // );
 }
 }
@@ -77,7 +77,7 @@ fn create() {
 fn create_list() {
 fn create_list() {
     let mut dom = VirtualDom::new(|| rsx! {{(0..3).map(|f| rsx!( div { "hello" } ))}});
     let mut dom = VirtualDom::new(|| rsx! {{(0..3).map(|f| rsx!( div { "hello" } ))}});
 
 
-    let _edits = dom.rebuild_to_vec().sanitize();
+    let _edits = dom.rebuild_to_vec();
 
 
     // note: we dont test template edits anymore
     // note: we dont test template edits anymore
     // assert_eq!(
     // assert_eq!(
@@ -87,7 +87,7 @@ fn create_list() {
     //         CreateElement { name: "div" },
     //         CreateElement { name: "div" },
     //         CreateStaticText { value: "hello" },
     //         CreateStaticText { value: "hello" },
     //         AppendChildren { m: 1 },
     //         AppendChildren { m: 1 },
-    //         SaveTemplate { name: "template", m: 1 }
+    //         SaveTemplate {  m: 1 }
     //     ]
     //     ]
     // );
     // );
 }
 }
@@ -103,7 +103,7 @@ fn create_simple() {
         }
         }
     });
     });
 
 
-    let edits = dom.rebuild_to_vec().sanitize();
+    let edits = dom.rebuild_to_vec();
 
 
     // note: we dont test template edits anymore
     // note: we dont test template edits anymore
     // assert_eq!(
     // assert_eq!(
@@ -115,7 +115,7 @@ fn create_simple() {
     //         CreateElement { name: "div" },
     //         CreateElement { name: "div" },
     //         CreateElement { name: "div" },
     //         CreateElement { name: "div" },
     //         // add to root
     //         // add to root
-    //         SaveTemplate { name: "template", m: 4 }
+    //         SaveTemplate {  m: 4 }
     //     ]
     //     ]
     // );
     // );
 }
 }
@@ -142,7 +142,7 @@ fn create_components() {
         }
         }
     }
     }
 
 
-    let _edits = dom.rebuild_to_vec().sanitize();
+    let _edits = dom.rebuild_to_vec();
 
 
     // todo: test this
     // todo: test this
 }
 }
@@ -161,7 +161,7 @@ fn anchors() {
     });
     });
 
 
     // note that the template under "false" doesn't show up since it's not loaded
     // note that the template under "false" doesn't show up since it's not loaded
-    let edits = dom.rebuild_to_vec().sanitize();
+    let edits = dom.rebuild_to_vec();
 
 
     // note: we dont test template edits anymore
     // note: we dont test template edits anymore
     // assert_eq!(
     // assert_eq!(
@@ -178,7 +178,7 @@ fn anchors() {
     assert_eq!(
     assert_eq!(
         edits.edits,
         edits.edits,
         [
         [
-            LoadTemplate { name: "template", index: 0, id: ElementId(1) },
+            LoadTemplate { index: 0, id: ElementId(1) },
             CreatePlaceholder { id: ElementId(2) },
             CreatePlaceholder { id: ElementId(2) },
             AppendChildren { m: 2, id: ElementId(0) }
             AppendChildren { m: 2, id: ElementId(0) }
         ]
         ]

+ 0 - 32
packages/core/tests/create_element.rs

@@ -1,32 +0,0 @@
-// use dioxus::dioxus_core::Mutation::*;
-use dioxus::prelude::*;
-
-#[test]
-fn multiroot() {
-    let mut dom = VirtualDom::new(|| {
-        rsx! {
-            div { "Hello a" }
-            div { "Hello b" }
-            div { "Hello c" }
-        }
-    });
-
-    // note: we dont test template edits anymore
-    let _templates = dom.rebuild_to_vec().sanitize().templates;
-
-    // assert_eq!(
-    //     dom.rebuild_to_vec().sanitize().templates,
-    //     [
-    //         CreateElement { name: "div" },
-    //         CreateStaticText { value: "Hello a" },
-    //         AppendChildren { m: 1 },
-    //         CreateElement { name: "div" },
-    //         CreateStaticText { value: "Hello b" },
-    //         AppendChildren { m: 1 },
-    //         CreateElement { name: "div" },
-    //         CreateStaticText { value: "Hello c" },
-    //         AppendChildren { m: 1 },
-    //         SaveTemplate { name: "template", m: 3 }
-    //     ]
-    // )
-}

+ 14 - 10
packages/core/tests/create_lists.rs

@@ -1,6 +1,7 @@
 use dioxus::dioxus_core::Mutation::*;
 use dioxus::dioxus_core::Mutation::*;
 use dioxus::prelude::*;
 use dioxus::prelude::*;
 use dioxus_core::ElementId;
 use dioxus_core::ElementId;
+use pretty_assertions::assert_eq;
 
 
 // A real-world usecase of templates at peak performance
 // A real-world usecase of templates at peak performance
 // In react, this would be a lot of node creation.
 // In react, this would be a lot of node creation.
@@ -25,7 +26,7 @@ fn app() -> Element {
 fn list_renders() {
 fn list_renders() {
     let mut dom = VirtualDom::new(app);
     let mut dom = VirtualDom::new(app);
 
 
-    let edits = dom.rebuild_to_vec().sanitize();
+    let edits = dom.rebuild_to_vec();
 
 
     // note: we dont test template edits anymore
     // note: we dont test template edits anymore
     // assert_eq!(
     // assert_eq!(
@@ -37,7 +38,7 @@ fn list_renders() {
     //         // append when modify the values (IE no need for a placeholder)
     //         // append when modify the values (IE no need for a placeholder)
     //         CreateStaticPlaceholder,
     //         CreateStaticPlaceholder,
     //         AppendChildren { m: 1 },
     //         AppendChildren { m: 1 },
-    //         SaveTemplate { name: "template", m: 1 },
+    //         SaveTemplate {  m: 1 },
     //         // Create the inner template div
     //         // Create the inner template div
     //         CreateElement { name: "div" },
     //         CreateElement { name: "div" },
     //         CreateElement { name: "h1" },
     //         CreateElement { name: "h1" },
@@ -47,7 +48,7 @@ fn list_renders() {
     //         CreateTextPlaceholder,
     //         CreateTextPlaceholder,
     //         AppendChildren { m: 1 },
     //         AppendChildren { m: 1 },
     //         AppendChildren { m: 2 },
     //         AppendChildren { m: 2 },
-    //         SaveTemplate { name: "template", m: 1 }
+    //         SaveTemplate {  m: 1 }
     //     ],
     //     ],
     // );
     // );
 
 
@@ -55,14 +56,17 @@ fn list_renders() {
         edits.edits,
         edits.edits,
         [
         [
             // Load the outer div
             // Load the outer div
-            LoadTemplate { name: "template", index: 0, id: ElementId(1) },
+            LoadTemplate { index: 0, id: ElementId(1) },
             // Load each template one-by-one, rehydrating it
             // Load each template one-by-one, rehydrating it
-            LoadTemplate { name: "template", index: 0, id: ElementId(2) },
-            HydrateText { path: &[1, 0], value: "0".to_string(), id: ElementId(3) },
-            LoadTemplate { name: "template", index: 0, id: ElementId(4) },
-            HydrateText { path: &[1, 0], value: "1".to_string(), id: ElementId(5) },
-            LoadTemplate { name: "template", index: 0, id: ElementId(6) },
-            HydrateText { path: &[1, 0], value: "2".to_string(), id: ElementId(7) },
+            LoadTemplate { index: 0, id: ElementId(2) },
+            CreateTextNode { value: "0".to_string(), id: ElementId(3) },
+            ReplacePlaceholder { path: &[1, 0], m: 1 },
+            LoadTemplate { index: 0, id: ElementId(4) },
+            CreateTextNode { value: "1".to_string(), id: ElementId(5) },
+            ReplacePlaceholder { path: &[1, 0], m: 1 },
+            LoadTemplate { index: 0, id: ElementId(6) },
+            CreateTextNode { value: "2".to_string(), id: ElementId(7) },
+            ReplacePlaceholder { path: &[1, 0], m: 1 },
             // Replace the 0th childn on the div with the 3 templates on the stack
             // Replace the 0th childn on the div with the 3 templates on the stack
             ReplacePlaceholder { m: 3, path: &[0] },
             ReplacePlaceholder { m: 3, path: &[0] },
             // Append the container div to the dom
             // Append the container div to the dom

+ 8 - 10
packages/core/tests/create_passthru.rs

@@ -21,12 +21,12 @@ fn nested_passthru_creates() {
     }
     }
 
 
     let mut dom = VirtualDom::new(app);
     let mut dom = VirtualDom::new(app);
-    let edits = dom.rebuild_to_vec().sanitize();
+    let edits = dom.rebuild_to_vec();
 
 
     assert_eq!(
     assert_eq!(
         edits.edits,
         edits.edits,
         [
         [
-            LoadTemplate { name: "template", index: 0, id: ElementId(1) },
+            LoadTemplate { index: 0, id: ElementId(1) },
             AppendChildren { m: 1, id: ElementId(0) },
             AppendChildren { m: 1, id: ElementId(0) },
         ]
         ]
     )
     )
@@ -60,16 +60,16 @@ fn nested_passthru_creates_add() {
     let mut dom = VirtualDom::new(app);
     let mut dom = VirtualDom::new(app);
 
 
     assert_eq!(
     assert_eq!(
-        dom.rebuild_to_vec().sanitize().edits,
+        dom.rebuild_to_vec().edits,
         [
         [
             // load 1
             // load 1
-            LoadTemplate { name: "template", index: 0, id: ElementId(1) },
+            LoadTemplate { index: 0, id: ElementId(1) },
             // load 2
             // load 2
-            LoadTemplate { name: "template", index: 0, id: ElementId(2) },
+            LoadTemplate { index: 0, id: ElementId(2) },
             // load 3
             // load 3
-            LoadTemplate { name: "template", index: 0, id: ElementId(3) },
+            LoadTemplate { index: 0, id: ElementId(3) },
             // load div that contains 4
             // load div that contains 4
-            LoadTemplate { name: "template", index: 1, id: ElementId(4) },
+            LoadTemplate { index: 1, id: ElementId(4) },
             AppendChildren { id: ElementId(0), m: 4 },
             AppendChildren { id: ElementId(0), m: 4 },
         ]
         ]
     );
     );
@@ -85,11 +85,9 @@ fn dynamic_node_as_root() {
     }
     }
 
 
     let mut dom = VirtualDom::new(app);
     let mut dom = VirtualDom::new(app);
-    let edits = dom.rebuild_to_vec().sanitize();
+    let edits = dom.rebuild_to_vec();
 
 
     // Since the roots were all dynamic, they should not cause any template muations
     // Since the roots were all dynamic, they should not cause any template muations
-    assert!(edits.templates.is_empty());
-
     // The root node is text, so we just create it on the spot
     // The root node is text, so we just create it on the spot
     assert_eq!(
     assert_eq!(
         edits.edits,
         edits.edits,

+ 8 - 8
packages/core/tests/cycle.rs

@@ -11,11 +11,11 @@ fn cycling_elements() {
     });
     });
 
 
     {
     {
-        let edits = dom.rebuild_to_vec().sanitize();
+        let edits = dom.rebuild_to_vec();
         assert_eq!(
         assert_eq!(
             edits.edits,
             edits.edits,
             [
             [
-                LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
+                LoadTemplate { index: 0, id: ElementId(1,) },
                 AppendChildren { m: 1, id: ElementId(0) },
                 AppendChildren { m: 1, id: ElementId(0) },
             ]
             ]
         );
         );
@@ -23,9 +23,9 @@ fn cycling_elements() {
 
 
     dom.mark_dirty(ScopeId::APP);
     dom.mark_dirty(ScopeId::APP);
     assert_eq!(
     assert_eq!(
-        dom.render_immediate_to_vec().sanitize().edits,
+        dom.render_immediate_to_vec().edits,
         [
         [
-            LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
+            LoadTemplate { index: 0, id: ElementId(2,) },
             ReplaceWith { id: ElementId(1,), m: 1 },
             ReplaceWith { id: ElementId(1,), m: 1 },
         ]
         ]
     );
     );
@@ -33,18 +33,18 @@ fn cycling_elements() {
     // notice that the IDs cycle back to ElementId(1), preserving a minimal memory footprint
     // notice that the IDs cycle back to ElementId(1), preserving a minimal memory footprint
     dom.mark_dirty(ScopeId::APP);
     dom.mark_dirty(ScopeId::APP);
     assert_eq!(
     assert_eq!(
-        dom.render_immediate_to_vec().sanitize().edits,
+        dom.render_immediate_to_vec().edits,
         [
         [
-            LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
+            LoadTemplate { index: 0, id: ElementId(1,) },
             ReplaceWith { id: ElementId(2,), m: 1 },
             ReplaceWith { id: ElementId(2,), m: 1 },
         ]
         ]
     );
     );
 
 
     dom.mark_dirty(ScopeId::APP);
     dom.mark_dirty(ScopeId::APP);
     assert_eq!(
     assert_eq!(
-        dom.render_immediate_to_vec().sanitize().edits,
+        dom.render_immediate_to_vec().edits,
         [
         [
-            LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
+            LoadTemplate { index: 0, id: ElementId(2,) },
             ReplaceWith { id: ElementId(1,), m: 1 },
             ReplaceWith { id: ElementId(1,), m: 1 },
         ]
         ]
     );
     );

+ 22 - 12
packages/core/tests/diff_component.rs

@@ -1,5 +1,6 @@
 use dioxus::dioxus_core::{ElementId, Mutation::*};
 use dioxus::dioxus_core::{ElementId, Mutation::*};
 use dioxus::prelude::*;
 use dioxus::prelude::*;
+use pretty_assertions::assert_eq;
 
 
 /// When returning sets of components, we do a light diff of the contents to preserve some react-like functionality
 /// When returning sets of components, we do a light diff of the contents to preserve some react-like functionality
 ///
 ///
@@ -7,6 +8,15 @@ use dioxus::prelude::*;
 /// different pointers
 /// different pointers
 #[test]
 #[test]
 fn component_swap() {
 fn component_swap() {
+    // Check that templates with the same structure are deduplicated at compile time
+    // If they are not, this test will fail because it is being run in debug mode where templates are not deduped
+    let dynamic = 0;
+    let template_1 = rsx! { "{dynamic}" };
+    let template_2 = rsx! { "{dynamic}" };
+    if template_1.unwrap().template != template_2.unwrap().template {
+        return;
+    }
+
     fn app() -> Element {
     fn app() -> Element {
         let mut render_phase = use_signal(|| 0);
         let mut render_phase = use_signal(|| 0);
 
 
@@ -62,16 +72,16 @@ fn component_swap() {
 
 
     let mut dom = VirtualDom::new(app);
     let mut dom = VirtualDom::new(app);
     {
     {
-        let edits = dom.rebuild_to_vec().sanitize();
+        let edits = dom.rebuild_to_vec();
         assert_eq!(
         assert_eq!(
             edits.edits,
             edits.edits,
             [
             [
-                LoadTemplate { name: "template", index: 0, id: ElementId(1) },
-                LoadTemplate { name: "template", index: 0, id: ElementId(2) },
-                LoadTemplate { name: "template", index: 0, id: ElementId(3) },
-                LoadTemplate { name: "template", index: 0, id: ElementId(4) },
+                LoadTemplate { index: 0, id: ElementId(1) },
+                LoadTemplate { index: 0, id: ElementId(2) },
+                LoadTemplate { index: 0, id: ElementId(3) },
+                LoadTemplate { index: 0, id: ElementId(4) },
                 ReplacePlaceholder { path: &[1], m: 3 },
                 ReplacePlaceholder { path: &[1], m: 3 },
-                LoadTemplate { name: "template", index: 0, id: ElementId(5) },
+                LoadTemplate { index: 0, id: ElementId(5) },
                 AppendChildren { m: 2, id: ElementId(0) }
                 AppendChildren { m: 2, id: ElementId(0) }
             ]
             ]
         );
         );
@@ -79,27 +89,27 @@ fn component_swap() {
 
 
     dom.mark_dirty(ScopeId::APP);
     dom.mark_dirty(ScopeId::APP);
     assert_eq!(
     assert_eq!(
-        dom.render_immediate_to_vec().sanitize().edits,
+        dom.render_immediate_to_vec().edits,
         [
         [
-            LoadTemplate { name: "template", index: 0, id: ElementId(6) },
+            LoadTemplate { index: 0, id: ElementId(6) },
             ReplaceWith { id: ElementId(5), m: 1 }
             ReplaceWith { id: ElementId(5), m: 1 }
         ]
         ]
     );
     );
 
 
     dom.mark_dirty(ScopeId::APP);
     dom.mark_dirty(ScopeId::APP);
     assert_eq!(
     assert_eq!(
-        dom.render_immediate_to_vec().sanitize().edits,
+        dom.render_immediate_to_vec().edits,
         [
         [
-            LoadTemplate { name: "template", index: 0, id: ElementId(5) },
+            LoadTemplate { index: 0, id: ElementId(5) },
             ReplaceWith { id: ElementId(6), m: 1 }
             ReplaceWith { id: ElementId(6), m: 1 }
         ]
         ]
     );
     );
 
 
     dom.mark_dirty(ScopeId::APP);
     dom.mark_dirty(ScopeId::APP);
     assert_eq!(
     assert_eq!(
-        dom.render_immediate_to_vec().sanitize().edits,
+        dom.render_immediate_to_vec().edits,
         [
         [
-            LoadTemplate { name: "template", index: 0, id: ElementId(6) },
+            LoadTemplate { index: 0, id: ElementId(6) },
             ReplaceWith { id: ElementId(5), m: 1 }
             ReplaceWith { id: ElementId(5), m: 1 }
         ]
         ]
     );
     );

+ 7 - 5
packages/core/tests/diff_dynamic_node.rs

@@ -7,6 +7,7 @@ fn toggle_option_text() {
     let mut dom = VirtualDom::new(|| {
     let mut dom = VirtualDom::new(|| {
         let gen = generation();
         let gen = generation();
         let text = if gen % 2 != 0 { Some("hello") } else { None };
         let text = if gen % 2 != 0 { Some("hello") } else { None };
+        println!("{:?}", text);
 
 
         rsx! {
         rsx! {
             div {
             div {
@@ -17,10 +18,11 @@ fn toggle_option_text() {
 
 
     // load the div and then assign the None as a placeholder
     // load the div and then assign the None as a placeholder
     assert_eq!(
     assert_eq!(
-        dom.rebuild_to_vec().sanitize().edits,
+        dom.rebuild_to_vec().edits,
         [
         [
-            LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
-            AssignId { path: &[0], id: ElementId(2,) },
+            LoadTemplate { index: 0, id: ElementId(1,) },
+            CreatePlaceholder { id: ElementId(2,) },
+            ReplacePlaceholder { path: &[0], m: 1 },
             AppendChildren { id: ElementId(0), m: 1 },
             AppendChildren { id: ElementId(0), m: 1 },
         ]
         ]
     );
     );
@@ -28,7 +30,7 @@ fn toggle_option_text() {
     // Rendering again should replace the placeholder with an text node
     // Rendering again should replace the placeholder with an text node
     dom.mark_dirty(ScopeId::APP);
     dom.mark_dirty(ScopeId::APP);
     assert_eq!(
     assert_eq!(
-        dom.render_immediate_to_vec().sanitize().edits,
+        dom.render_immediate_to_vec().edits,
         [
         [
             CreateTextNode { value: "hello".to_string(), id: ElementId(3,) },
             CreateTextNode { value: "hello".to_string(), id: ElementId(3,) },
             ReplaceWith { id: ElementId(2,), m: 1 },
             ReplaceWith { id: ElementId(2,), m: 1 },
@@ -38,7 +40,7 @@ fn toggle_option_text() {
     // Rendering again should replace the placeholder with an text node
     // Rendering again should replace the placeholder with an text node
     dom.mark_dirty(ScopeId::APP);
     dom.mark_dirty(ScopeId::APP);
     assert_eq!(
     assert_eq!(
-        dom.render_immediate_to_vec().sanitize().edits,
+        dom.render_immediate_to_vec().edits,
         [
         [
             CreatePlaceholder { id: ElementId(2,) },
             CreatePlaceholder { id: ElementId(2,) },
             ReplaceWith { id: ElementId(3,), m: 1 },
             ReplaceWith { id: ElementId(3,), m: 1 },

+ 12 - 12
packages/core/tests/diff_element.rs

@@ -48,36 +48,36 @@ fn element_swap() {
 
 
     vdom.mark_dirty(ScopeId::APP);
     vdom.mark_dirty(ScopeId::APP);
     assert_eq!(
     assert_eq!(
-        vdom.render_immediate_to_vec().sanitize().edits,
+        vdom.render_immediate_to_vec().edits,
         [
         [
-            LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
+            LoadTemplate { index: 0, id: ElementId(2,) },
             ReplaceWith { id: ElementId(1,), m: 1 },
             ReplaceWith { id: ElementId(1,), m: 1 },
         ]
         ]
     );
     );
 
 
     vdom.mark_dirty(ScopeId::APP);
     vdom.mark_dirty(ScopeId::APP);
     assert_eq!(
     assert_eq!(
-        vdom.render_immediate_to_vec().sanitize().edits,
+        vdom.render_immediate_to_vec().edits,
         [
         [
-            LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
+            LoadTemplate { index: 0, id: ElementId(1,) },
             ReplaceWith { id: ElementId(2,), m: 1 },
             ReplaceWith { id: ElementId(2,), m: 1 },
         ]
         ]
     );
     );
 
 
     vdom.mark_dirty(ScopeId::APP);
     vdom.mark_dirty(ScopeId::APP);
     assert_eq!(
     assert_eq!(
-        vdom.render_immediate_to_vec().sanitize().edits,
+        vdom.render_immediate_to_vec().edits,
         [
         [
-            LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
+            LoadTemplate { index: 0, id: ElementId(2,) },
             ReplaceWith { id: ElementId(1,), m: 1 },
             ReplaceWith { id: ElementId(1,), m: 1 },
         ]
         ]
     );
     );
 
 
     vdom.mark_dirty(ScopeId::APP);
     vdom.mark_dirty(ScopeId::APP);
     assert_eq!(
     assert_eq!(
-        vdom.render_immediate_to_vec().sanitize().edits,
+        vdom.render_immediate_to_vec().edits,
         [
         [
-            LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
+            LoadTemplate { index: 0, id: ElementId(1,) },
             ReplaceWith { id: ElementId(2,), m: 1 },
             ReplaceWith { id: ElementId(2,), m: 1 },
         ]
         ]
     );
     );
@@ -128,7 +128,7 @@ fn attribute_diff() {
 
 
     vdom.mark_dirty(ScopeId::APP);
     vdom.mark_dirty(ScopeId::APP);
     assert_eq!(
     assert_eq!(
-        vdom.render_immediate_to_vec().sanitize().edits,
+        vdom.render_immediate_to_vec().edits,
         [
         [
             SetAttribute {
             SetAttribute {
                 name: "b",
                 name: "b",
@@ -147,7 +147,7 @@ fn attribute_diff() {
 
 
     vdom.mark_dirty(ScopeId::APP);
     vdom.mark_dirty(ScopeId::APP);
     assert_eq!(
     assert_eq!(
-        vdom.render_immediate_to_vec().sanitize().edits,
+        vdom.render_immediate_to_vec().edits,
         [
         [
             SetAttribute { name: "a", value: AttributeValue::None, id: ElementId(1,), ns: None },
             SetAttribute { name: "a", value: AttributeValue::None, id: ElementId(1,), ns: None },
             SetAttribute { name: "b", value: AttributeValue::None, id: ElementId(1,), ns: None },
             SetAttribute { name: "b", value: AttributeValue::None, id: ElementId(1,), ns: None },
@@ -168,7 +168,7 @@ fn attribute_diff() {
 
 
     vdom.mark_dirty(ScopeId::APP);
     vdom.mark_dirty(ScopeId::APP);
     assert_eq!(
     assert_eq!(
-        vdom.render_immediate_to_vec().sanitize().edits,
+        vdom.render_immediate_to_vec().edits,
         [
         [
             SetAttribute { name: "c", value: AttributeValue::None, id: ElementId(1,), ns: None },
             SetAttribute { name: "c", value: AttributeValue::None, id: ElementId(1,), ns: None },
             SetAttribute {
             SetAttribute {
@@ -196,7 +196,7 @@ fn diff_empty() {
     vdom.rebuild(&mut NoOpMutations);
     vdom.rebuild(&mut NoOpMutations);
 
 
     vdom.mark_dirty(ScopeId::APP);
     vdom.mark_dirty(ScopeId::APP);
-    let edits = vdom.render_immediate_to_vec().sanitize().edits;
+    let edits = vdom.render_immediate_to_vec().edits;
 
 
     assert_eq!(
     assert_eq!(
         edits,
         edits,

+ 32 - 32
packages/core/tests/diff_keyed_list.rs

@@ -22,18 +22,18 @@ fn keyed_diffing_out_of_order() {
 
 
     {
     {
         assert_eq!(
         assert_eq!(
-            dom.rebuild_to_vec().sanitize().edits,
+            dom.rebuild_to_vec().edits,
             [
             [
-                LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
-                LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
-                LoadTemplate { name: "template", index: 0, id: ElementId(3,) },
-                LoadTemplate { name: "template", index: 0, id: ElementId(4,) },
-                LoadTemplate { name: "template", index: 0, id: ElementId(5,) },
-                LoadTemplate { name: "template", index: 0, id: ElementId(6,) },
-                LoadTemplate { name: "template", index: 0, id: ElementId(7,) },
-                LoadTemplate { name: "template", index: 0, id: ElementId(8,) },
-                LoadTemplate { name: "template", index: 0, id: ElementId(9,) },
-                LoadTemplate { name: "template", index: 0, id: ElementId(10,) },
+                LoadTemplate { index: 0, id: ElementId(1,) },
+                LoadTemplate { index: 0, id: ElementId(2,) },
+                LoadTemplate { index: 0, id: ElementId(3,) },
+                LoadTemplate { index: 0, id: ElementId(4,) },
+                LoadTemplate { index: 0, id: ElementId(5,) },
+                LoadTemplate { index: 0, id: ElementId(6,) },
+                LoadTemplate { index: 0, id: ElementId(7,) },
+                LoadTemplate { index: 0, id: ElementId(8,) },
+                LoadTemplate { index: 0, id: ElementId(9,) },
+                LoadTemplate { index: 0, id: ElementId(10,) },
                 AppendChildren { m: 10, id: ElementId(0) },
                 AppendChildren { m: 10, id: ElementId(0) },
             ]
             ]
         );
         );
@@ -169,10 +169,10 @@ fn keyed_diffing_additions() {
 
 
     dom.mark_dirty(ScopeId::APP);
     dom.mark_dirty(ScopeId::APP);
     assert_eq!(
     assert_eq!(
-        dom.render_immediate_to_vec().sanitize().edits,
+        dom.render_immediate_to_vec().edits,
         [
         [
-            LoadTemplate { name: "template", index: 0, id: ElementId(6) },
-            LoadTemplate { name: "template", index: 0, id: ElementId(7) },
+            LoadTemplate { index: 0, id: ElementId(6) },
+            LoadTemplate { index: 0, id: ElementId(7) },
             InsertAfter { id: ElementId(5), m: 2 }
             InsertAfter { id: ElementId(5), m: 2 }
         ]
         ]
     );
     );
@@ -194,11 +194,11 @@ fn keyed_diffing_additions_and_moves_on_ends() {
 
 
     dom.mark_dirty(ScopeId::APP);
     dom.mark_dirty(ScopeId::APP);
     assert_eq!(
     assert_eq!(
-        dom.render_immediate_to_vec().sanitize().edits,
+        dom.render_immediate_to_vec().edits,
         [
         [
             // create 11, 12
             // create 11, 12
-            LoadTemplate { name: "template", index: 0, id: ElementId(5) },
-            LoadTemplate { name: "template", index: 0, id: ElementId(6) },
+            LoadTemplate { index: 0, id: ElementId(5) },
+            LoadTemplate { index: 0, id: ElementId(6) },
             InsertAfter { id: ElementId(3), m: 2 },
             InsertAfter { id: ElementId(3), m: 2 },
             // move 7 to the front
             // move 7 to the front
             PushRoot { id: ElementId(4) },
             PushRoot { id: ElementId(4) },
@@ -224,15 +224,15 @@ fn keyed_diffing_additions_and_moves_in_middle() {
     // LIS: 4, 5, 6
     // LIS: 4, 5, 6
     dom.mark_dirty(ScopeId::APP);
     dom.mark_dirty(ScopeId::APP);
     assert_eq!(
     assert_eq!(
-        dom.render_immediate_to_vec().sanitize().edits,
+        dom.render_immediate_to_vec().edits,
         [
         [
             // create 5, 6
             // create 5, 6
-            LoadTemplate { name: "template", index: 0, id: ElementId(5) },
-            LoadTemplate { name: "template", index: 0, id: ElementId(6) },
+            LoadTemplate { index: 0, id: ElementId(5) },
+            LoadTemplate { index: 0, id: ElementId(6) },
             InsertBefore { id: ElementId(3), m: 2 },
             InsertBefore { id: ElementId(3), m: 2 },
             // create 7, 8
             // create 7, 8
-            LoadTemplate { name: "template", index: 0, id: ElementId(7) },
-            LoadTemplate { name: "template", index: 0, id: ElementId(8) },
+            LoadTemplate { index: 0, id: ElementId(7) },
+            LoadTemplate { index: 0, id: ElementId(8) },
             InsertBefore { id: ElementId(2), m: 2 },
             InsertBefore { id: ElementId(2), m: 2 },
             // move 7
             // move 7
             PushRoot { id: ElementId(4) },
             PushRoot { id: ElementId(4) },
@@ -258,7 +258,7 @@ fn controlled_keyed_diffing_out_of_order() {
     // LIS: 5, 6
     // LIS: 5, 6
     dom.mark_dirty(ScopeId::APP);
     dom.mark_dirty(ScopeId::APP);
     assert_eq!(
     assert_eq!(
-        dom.render_immediate_to_vec().sanitize().edits,
+        dom.render_immediate_to_vec().edits,
         [
         [
             // remove 7
             // remove 7
             Remove { id: ElementId(4,) },
             Remove { id: ElementId(4,) },
@@ -266,10 +266,10 @@ fn controlled_keyed_diffing_out_of_order() {
             PushRoot { id: ElementId(1) },
             PushRoot { id: ElementId(1) },
             InsertAfter { id: ElementId(3,), m: 1 },
             InsertAfter { id: ElementId(3,), m: 1 },
             // create 9 and insert before 6
             // create 9 and insert before 6
-            LoadTemplate { name: "template", index: 0, id: ElementId(4) },
+            LoadTemplate { index: 0, id: ElementId(4) },
             InsertBefore { id: ElementId(3,), m: 1 },
             InsertBefore { id: ElementId(3,), m: 1 },
             // create 0 and insert before 5
             // create 0 and insert before 5
-            LoadTemplate { name: "template", index: 0, id: ElementId(5) },
+            LoadTemplate { index: 0, id: ElementId(5) },
             InsertBefore { id: ElementId(2,), m: 1 },
             InsertBefore { id: ElementId(2,), m: 1 },
         ]
         ]
     );
     );
@@ -291,10 +291,10 @@ fn controlled_keyed_diffing_out_of_order_max_test() {
 
 
     dom.mark_dirty(ScopeId::APP);
     dom.mark_dirty(ScopeId::APP);
     assert_eq!(
     assert_eq!(
-        dom.render_immediate_to_vec().sanitize().edits,
+        dom.render_immediate_to_vec().edits,
         [
         [
             Remove { id: ElementId(5,) },
             Remove { id: ElementId(5,) },
-            LoadTemplate { name: "template", index: 0, id: ElementId(5) },
+            LoadTemplate { index: 0, id: ElementId(5) },
             InsertBefore { id: ElementId(3,), m: 1 },
             InsertBefore { id: ElementId(3,), m: 1 },
             PushRoot { id: ElementId(4) },
             PushRoot { id: ElementId(4) },
             InsertBefore { id: ElementId(1,), m: 1 },
             InsertBefore { id: ElementId(1,), m: 1 },
@@ -320,7 +320,7 @@ fn remove_list() {
 
 
     dom.mark_dirty(ScopeId::APP);
     dom.mark_dirty(ScopeId::APP);
     assert_eq!(
     assert_eq!(
-        dom.render_immediate_to_vec().sanitize().edits,
+        dom.render_immediate_to_vec().edits,
         [
         [
             Remove { id: ElementId(5) },
             Remove { id: ElementId(5) },
             Remove { id: ElementId(4) },
             Remove { id: ElementId(4) },
@@ -345,11 +345,11 @@ fn no_common_keys() {
 
 
     dom.mark_dirty(ScopeId::APP);
     dom.mark_dirty(ScopeId::APP);
     assert_eq!(
     assert_eq!(
-        dom.render_immediate_to_vec().sanitize().edits,
+        dom.render_immediate_to_vec().edits,
         [
         [
-            LoadTemplate { name: "template", index: 0, id: ElementId(4) },
-            LoadTemplate { name: "template", index: 0, id: ElementId(5) },
-            LoadTemplate { name: "template", index: 0, id: ElementId(6) },
+            LoadTemplate { index: 0, id: ElementId(4) },
+            LoadTemplate { index: 0, id: ElementId(5) },
+            LoadTemplate { index: 0, id: ElementId(6) },
             Remove { id: ElementId(3) },
             Remove { id: ElementId(3) },
             Remove { id: ElementId(2) },
             Remove { id: ElementId(2) },
             ReplaceWith { id: ElementId(1), m: 3 }
             ReplaceWith { id: ElementId(1), m: 3 }

+ 147 - 109
packages/core/tests/diff_unkeyed_list.rs

@@ -1,5 +1,8 @@
+use std::collections::HashSet;
+
 use dioxus::dioxus_core::{ElementId, Mutation::*};
 use dioxus::dioxus_core::{ElementId, Mutation::*};
 use dioxus::prelude::*;
 use dioxus::prelude::*;
+use dioxus_core::Mutation;
 use pretty_assertions::assert_eq;
 use pretty_assertions::assert_eq;
 
 
 #[test]
 #[test]
@@ -18,10 +21,11 @@ fn list_creates_one_by_one() {
 
 
     // load the div and then assign the empty fragment as a placeholder
     // load the div and then assign the empty fragment as a placeholder
     assert_eq!(
     assert_eq!(
-        dom.rebuild_to_vec().sanitize().edits,
+        dom.rebuild_to_vec().edits,
         [
         [
-            LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
-            AssignId { path: &[0], id: ElementId(2,) },
+            LoadTemplate { index: 0, id: ElementId(1,) },
+            CreatePlaceholder { id: ElementId(2,) },
+            ReplacePlaceholder { path: &[0], m: 1 },
             AppendChildren { id: ElementId(0), m: 1 },
             AppendChildren { id: ElementId(0), m: 1 },
         ]
         ]
     );
     );
@@ -29,10 +33,11 @@ fn list_creates_one_by_one() {
     // Rendering the first item should replace the placeholder with an element
     // Rendering the first item should replace the placeholder with an element
     dom.mark_dirty(ScopeId::APP);
     dom.mark_dirty(ScopeId::APP);
     assert_eq!(
     assert_eq!(
-        dom.render_immediate_to_vec().sanitize().edits,
+        dom.render_immediate_to_vec().edits,
         [
         [
-            LoadTemplate { name: "template", index: 0, id: ElementId(3,) },
-            HydrateText { path: &[0], value: "0".to_string(), id: ElementId(4,) },
+            LoadTemplate { index: 0, id: ElementId(3,) },
+            CreateTextNode { value: "0".to_string(), id: ElementId(4,) },
+            ReplacePlaceholder { path: &[0], m: 1 },
             ReplaceWith { id: ElementId(2,), m: 1 },
             ReplaceWith { id: ElementId(2,), m: 1 },
         ]
         ]
     );
     );
@@ -40,10 +45,11 @@ fn list_creates_one_by_one() {
     // Rendering the next item should insert after the previous
     // Rendering the next item should insert after the previous
     dom.mark_dirty(ScopeId::APP);
     dom.mark_dirty(ScopeId::APP);
     assert_eq!(
     assert_eq!(
-        dom.render_immediate_to_vec().sanitize().edits,
+        dom.render_immediate_to_vec().edits,
         [
         [
-            LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
-            HydrateText { path: &[0], value: "1".to_string(), id: ElementId(5,) },
+            LoadTemplate { index: 0, id: ElementId(2,) },
+            CreateTextNode { value: "1".to_string(), id: ElementId(5,) },
+            ReplacePlaceholder { path: &[0], m: 1 },
             InsertAfter { id: ElementId(3,), m: 1 },
             InsertAfter { id: ElementId(3,), m: 1 },
         ]
         ]
     );
     );
@@ -51,10 +57,11 @@ fn list_creates_one_by_one() {
     // ... and again!
     // ... and again!
     dom.mark_dirty(ScopeId::APP);
     dom.mark_dirty(ScopeId::APP);
     assert_eq!(
     assert_eq!(
-        dom.render_immediate_to_vec().sanitize().edits,
+        dom.render_immediate_to_vec().edits,
         [
         [
-            LoadTemplate { name: "template", index: 0, id: ElementId(6,) },
-            HydrateText { path: &[0], value: "2".to_string(), id: ElementId(7,) },
+            LoadTemplate { index: 0, id: ElementId(6,) },
+            CreateTextNode { value: "2".to_string(), id: ElementId(7,) },
+            ReplacePlaceholder { path: &[0], m: 1 },
             InsertAfter { id: ElementId(2,), m: 1 },
             InsertAfter { id: ElementId(2,), m: 1 },
         ]
         ]
     );
     );
@@ -62,10 +69,11 @@ fn list_creates_one_by_one() {
     // once more
     // once more
     dom.mark_dirty(ScopeId::APP);
     dom.mark_dirty(ScopeId::APP);
     assert_eq!(
     assert_eq!(
-        dom.render_immediate_to_vec().sanitize().edits,
+        dom.render_immediate_to_vec().edits,
         [
         [
-            LoadTemplate { name: "template", index: 0, id: ElementId(8,) },
-            HydrateText { path: &[0], value: "3".to_string(), id: ElementId(9,) },
+            LoadTemplate { index: 0, id: ElementId(8,) },
+            CreateTextNode { value: "3".to_string(), id: ElementId(9,) },
+            ReplacePlaceholder { path: &[0], m: 1 },
             InsertAfter { id: ElementId(6,), m: 1 },
             InsertAfter { id: ElementId(6,), m: 1 },
         ]
         ]
     );
     );
@@ -87,17 +95,20 @@ fn removes_one_by_one() {
 
 
     // load the div and then assign the empty fragment as a placeholder
     // load the div and then assign the empty fragment as a placeholder
     assert_eq!(
     assert_eq!(
-        dom.rebuild_to_vec().sanitize().edits,
+        dom.rebuild_to_vec().edits,
         [
         [
             // The container
             // The container
-            LoadTemplate { name: "template", index: 0, id: ElementId(1) },
+            LoadTemplate { index: 0, id: ElementId(1) },
             // each list item
             // each list item
-            LoadTemplate { name: "template", index: 0, id: ElementId(2) },
-            HydrateText { path: &[0], value: "0".to_string(), id: ElementId(3) },
-            LoadTemplate { name: "template", index: 0, id: ElementId(4) },
-            HydrateText { path: &[0], value: "1".to_string(), id: ElementId(5) },
-            LoadTemplate { name: "template", index: 0, id: ElementId(6) },
-            HydrateText { path: &[0], value: "2".to_string(), id: ElementId(7) },
+            LoadTemplate { index: 0, id: ElementId(2) },
+            CreateTextNode { value: "0".to_string(), id: ElementId(3) },
+            ReplacePlaceholder { path: &[0], m: 1 },
+            LoadTemplate { index: 0, id: ElementId(4) },
+            CreateTextNode { value: "1".to_string(), id: ElementId(5) },
+            ReplacePlaceholder { path: &[0], m: 1 },
+            LoadTemplate { index: 0, id: ElementId(6) },
+            CreateTextNode { value: "2".to_string(), id: ElementId(7) },
+            ReplacePlaceholder { path: &[0], m: 1 },
             // replace the placeholder in the template with the 3 templates on the stack
             // replace the placeholder in the template with the 3 templates on the stack
             ReplacePlaceholder { m: 3, path: &[0] },
             ReplacePlaceholder { m: 3, path: &[0] },
             // Mount the div
             // Mount the div
@@ -109,14 +120,14 @@ fn removes_one_by_one() {
     // Rendering the first item should replace the placeholder with an element
     // Rendering the first item should replace the placeholder with an element
     dom.mark_dirty(ScopeId::APP);
     dom.mark_dirty(ScopeId::APP);
     assert_eq!(
     assert_eq!(
-        dom.render_immediate_to_vec().sanitize().edits,
+        dom.render_immediate_to_vec().edits,
         [Remove { id: ElementId(6) }]
         [Remove { id: ElementId(6) }]
     );
     );
 
 
     // Remove div(2)
     // Remove div(2)
     dom.mark_dirty(ScopeId::APP);
     dom.mark_dirty(ScopeId::APP);
     assert_eq!(
     assert_eq!(
-        dom.render_immediate_to_vec().sanitize().edits,
+        dom.render_immediate_to_vec().edits,
         [Remove { id: ElementId(4) }]
         [Remove { id: ElementId(4) }]
     );
     );
 
 
@@ -124,7 +135,7 @@ fn removes_one_by_one() {
     // todo: this should just be a remove with no placeholder
     // todo: this should just be a remove with no placeholder
     dom.mark_dirty(ScopeId::APP);
     dom.mark_dirty(ScopeId::APP);
     assert_eq!(
     assert_eq!(
-        dom.render_immediate_to_vec().sanitize().edits,
+        dom.render_immediate_to_vec().edits,
         [
         [
             CreatePlaceholder { id: ElementId(4) },
             CreatePlaceholder { id: ElementId(4) },
             ReplaceWith { id: ElementId(2), m: 1 }
             ReplaceWith { id: ElementId(2), m: 1 }
@@ -135,14 +146,17 @@ fn removes_one_by_one() {
     // todo: this should actually be append to, but replace placeholder is fine for now
     // todo: this should actually be append to, but replace placeholder is fine for now
     dom.mark_dirty(ScopeId::APP);
     dom.mark_dirty(ScopeId::APP);
     assert_eq!(
     assert_eq!(
-        dom.render_immediate_to_vec().sanitize().edits,
+        dom.render_immediate_to_vec().edits,
         [
         [
-            LoadTemplate { name: "template", index: 0, id: ElementId(2) },
-            HydrateText { path: &[0], value: "0".to_string(), id: ElementId(6) },
-            LoadTemplate { name: "template", index: 0, id: ElementId(8) },
-            HydrateText { path: &[0], value: "1".to_string(), id: ElementId(9) },
-            LoadTemplate { name: "template", index: 0, id: ElementId(10) },
-            HydrateText { path: &[0], value: "2".to_string(), id: ElementId(11) },
+            LoadTemplate { index: 0, id: ElementId(2) },
+            CreateTextNode { value: "0".to_string(), id: ElementId(6) },
+            ReplacePlaceholder { path: &[0], m: 1 },
+            LoadTemplate { index: 0, id: ElementId(8) },
+            CreateTextNode { value: "1".to_string(), id: ElementId(9) },
+            ReplacePlaceholder { path: &[0], m: 1 },
+            LoadTemplate { index: 0, id: ElementId(10) },
+            CreateTextNode { value: "2".to_string(), id: ElementId(11) },
+            ReplacePlaceholder { path: &[0], m: 1 },
             ReplaceWith { id: ElementId(4), m: 3 }
             ReplaceWith { id: ElementId(4), m: 3 }
         ]
         ]
     );
     );
@@ -162,46 +176,53 @@ fn list_shrink_multiroot() {
     });
     });
 
 
     assert_eq!(
     assert_eq!(
-        dom.rebuild_to_vec().sanitize().edits,
+        dom.rebuild_to_vec().edits,
         [
         [
-            LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
-            AssignId { path: &[0,], id: ElementId(2,) },
+            LoadTemplate { index: 0, id: ElementId(1,) },
+            CreatePlaceholder { id: ElementId(2,) },
+            ReplacePlaceholder { path: &[0,], m: 1 },
             AppendChildren { id: ElementId(0), m: 1 }
             AppendChildren { id: ElementId(0), m: 1 }
         ]
         ]
     );
     );
 
 
     dom.mark_dirty(ScopeId::APP);
     dom.mark_dirty(ScopeId::APP);
     assert_eq!(
     assert_eq!(
-        dom.render_immediate_to_vec().sanitize().edits,
+        dom.render_immediate_to_vec().edits,
         [
         [
-            LoadTemplate { name: "template", index: 0, id: ElementId(3) },
-            HydrateText { path: &[0], value: "0".to_string(), id: ElementId(4) },
-            LoadTemplate { name: "template", index: 1, id: ElementId(5) },
-            HydrateText { path: &[0], value: "0".to_string(), id: ElementId(6) },
+            LoadTemplate { index: 0, id: ElementId(3) },
+            CreateTextNode { value: "0".to_string(), id: ElementId(4) },
+            ReplacePlaceholder { path: &[0], m: 1 },
+            LoadTemplate { index: 1, id: ElementId(5) },
+            CreateTextNode { value: "0".to_string(), id: ElementId(6) },
+            ReplacePlaceholder { path: &[0], m: 1 },
             ReplaceWith { id: ElementId(2), m: 2 }
             ReplaceWith { id: ElementId(2), m: 2 }
         ]
         ]
     );
     );
 
 
     dom.mark_dirty(ScopeId::APP);
     dom.mark_dirty(ScopeId::APP);
     assert_eq!(
     assert_eq!(
-        dom.render_immediate_to_vec().sanitize().edits,
+        dom.render_immediate_to_vec().edits,
         [
         [
-            LoadTemplate { name: "template", index: 0, id: ElementId(2) },
-            HydrateText { path: &[0], value: "1".to_string(), id: ElementId(7) },
-            LoadTemplate { name: "template", index: 1, id: ElementId(8) },
-            HydrateText { path: &[0], value: "1".to_string(), id: ElementId(9) },
+            LoadTemplate { index: 0, id: ElementId(2) },
+            CreateTextNode { value: "1".to_string(), id: ElementId(7) },
+            ReplacePlaceholder { path: &[0], m: 1 },
+            LoadTemplate { index: 1, id: ElementId(8) },
+            CreateTextNode { value: "1".to_string(), id: ElementId(9) },
+            ReplacePlaceholder { path: &[0], m: 1 },
             InsertAfter { id: ElementId(5), m: 2 }
             InsertAfter { id: ElementId(5), m: 2 }
         ]
         ]
     );
     );
 
 
     dom.mark_dirty(ScopeId::APP);
     dom.mark_dirty(ScopeId::APP);
     assert_eq!(
     assert_eq!(
-        dom.render_immediate_to_vec().sanitize().edits,
+        dom.render_immediate_to_vec().edits,
         [
         [
-            LoadTemplate { name: "template", index: 0, id: ElementId(10) },
-            HydrateText { path: &[0], value: "2".to_string(), id: ElementId(11) },
-            LoadTemplate { name: "template", index: 1, id: ElementId(12) },
-            HydrateText { path: &[0], value: "2".to_string(), id: ElementId(13) },
+            LoadTemplate { index: 0, id: ElementId(10) },
+            CreateTextNode { value: "2".to_string(), id: ElementId(11) },
+            ReplacePlaceholder { path: &[0], m: 1 },
+            LoadTemplate { index: 1, id: ElementId(12) },
+            CreateTextNode { value: "2".to_string(), id: ElementId(13) },
+            ReplacePlaceholder { path: &[0], m: 1 },
             InsertAfter { id: ElementId(8), m: 2 }
             InsertAfter { id: ElementId(8), m: 2 }
         ]
         ]
     );
     );
@@ -224,46 +245,48 @@ fn removes_one_by_one_multiroot() {
 
 
     // load the div and then assign the empty fragment as a placeholder
     // load the div and then assign the empty fragment as a placeholder
     assert_eq!(
     assert_eq!(
-        dom.rebuild_to_vec().sanitize().edits,
+        dom.rebuild_to_vec().edits,
         [
         [
-            LoadTemplate { name: "template", index: 0, id: ElementId(1) },
-            //
-            LoadTemplate { name: "template", index: 0, id: ElementId(2) },
-            HydrateText { path: &[0], value: "0".to_string(), id: ElementId(3) },
-            LoadTemplate { name: "template", index: 1, id: ElementId(4) },
-            HydrateText { path: &[0], value: "0".to_string(), id: ElementId(5) },
-            //
-            LoadTemplate { name: "template", index: 0, id: ElementId(6) },
-            HydrateText { path: &[0], value: "1".to_string(), id: ElementId(7) },
-            LoadTemplate { name: "template", index: 1, id: ElementId(8) },
-            HydrateText { path: &[0], value: "1".to_string(), id: ElementId(9) },
-            //
-            LoadTemplate { name: "template", index: 0, id: ElementId(10) },
-            HydrateText { path: &[0], value: "2".to_string(), id: ElementId(11) },
-            LoadTemplate { name: "template", index: 1, id: ElementId(12) },
-            HydrateText { path: &[0], value: "2".to_string(), id: ElementId(13) },
+            LoadTemplate { index: 0, id: ElementId(1) },
             //
             //
+            LoadTemplate { index: 0, id: ElementId(2) },
+            CreateTextNode { value: "0".to_string(), id: ElementId(3) },
+            ReplacePlaceholder { path: &[0], m: 1 },
+            LoadTemplate { index: 1, id: ElementId(4) },
+            CreateTextNode { value: "0".to_string(), id: ElementId(5) },
+            ReplacePlaceholder { path: &[0], m: 1 },
+            LoadTemplate { index: 0, id: ElementId(6) },
+            CreateTextNode { value: "1".to_string(), id: ElementId(7) },
+            ReplacePlaceholder { path: &[0], m: 1 },
+            LoadTemplate { index: 1, id: ElementId(8) },
+            CreateTextNode { value: "1".to_string(), id: ElementId(9) },
+            ReplacePlaceholder { path: &[0], m: 1 },
+            LoadTemplate { index: 0, id: ElementId(10) },
+            CreateTextNode { value: "2".to_string(), id: ElementId(11) },
+            ReplacePlaceholder { path: &[0], m: 1 },
+            LoadTemplate { index: 1, id: ElementId(12) },
+            CreateTextNode { value: "2".to_string(), id: ElementId(13) },
+            ReplacePlaceholder { path: &[0], m: 1 },
             ReplacePlaceholder { path: &[0], m: 6 },
             ReplacePlaceholder { path: &[0], m: 6 },
-            //
             AppendChildren { id: ElementId(0), m: 1 }
             AppendChildren { id: ElementId(0), m: 1 }
         ]
         ]
     );
     );
 
 
     dom.mark_dirty(ScopeId::APP);
     dom.mark_dirty(ScopeId::APP);
     assert_eq!(
     assert_eq!(
-        dom.render_immediate_to_vec().sanitize().edits,
+        dom.render_immediate_to_vec().edits,
         [Remove { id: ElementId(10) }, Remove { id: ElementId(12) }]
         [Remove { id: ElementId(10) }, Remove { id: ElementId(12) }]
     );
     );
 
 
     dom.mark_dirty(ScopeId::APP);
     dom.mark_dirty(ScopeId::APP);
     assert_eq!(
     assert_eq!(
-        dom.render_immediate_to_vec().sanitize().edits,
+        dom.render_immediate_to_vec().edits,
         [Remove { id: ElementId(6) }, Remove { id: ElementId(8) }]
         [Remove { id: ElementId(6) }, Remove { id: ElementId(8) }]
     );
     );
 
 
     dom.mark_dirty(ScopeId::APP);
     dom.mark_dirty(ScopeId::APP);
     assert_eq!(
     assert_eq!(
-        dom.render_immediate_to_vec().sanitize().edits,
+        dom.render_immediate_to_vec().edits,
         [
         [
             CreatePlaceholder { id: ElementId(8) },
             CreatePlaceholder { id: ElementId(8) },
             Remove { id: ElementId(2) },
             Remove { id: ElementId(2) },
@@ -317,9 +340,9 @@ fn remove_many() {
         }
         }
     });
     });
 
 
+    // len = 0
     {
     {
-        let edits = dom.rebuild_to_vec().sanitize();
-        assert!(edits.templates.is_empty());
+        let edits = dom.rebuild_to_vec();
         assert_eq!(
         assert_eq!(
             edits.edits,
             edits.edits,
             [
             [
@@ -329,62 +352,76 @@ fn remove_many() {
         );
         );
     }
     }
 
 
+    // len = 1
     {
     {
         dom.mark_dirty(ScopeId::APP);
         dom.mark_dirty(ScopeId::APP);
-        let edits = dom.render_immediate_to_vec().sanitize();
+        let edits = dom.render_immediate_to_vec();
         assert_eq!(
         assert_eq!(
             edits.edits,
             edits.edits,
             [
             [
-                LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
-                HydrateText { path: &[0,], value: "hello 0".to_string(), id: ElementId(3,) },
+                LoadTemplate { index: 0, id: ElementId(2,) },
+                CreateTextNode { value: "hello 0".to_string(), id: ElementId(3,) },
+                ReplacePlaceholder { path: &[0,], m: 1 },
                 ReplaceWith { id: ElementId(1,), m: 1 },
                 ReplaceWith { id: ElementId(1,), m: 1 },
             ]
             ]
         );
         );
     }
     }
 
 
+    // len = 5
     {
     {
         dom.mark_dirty(ScopeId::APP);
         dom.mark_dirty(ScopeId::APP);
-        let edits = dom.render_immediate_to_vec().sanitize();
+        let edits = dom.render_immediate_to_vec();
         assert_eq!(
         assert_eq!(
             edits.edits,
             edits.edits,
             [
             [
-                LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
-                HydrateText { path: &[0,], value: "hello 1".to_string(), id: ElementId(4,) },
-                LoadTemplate { name: "template", index: 0, id: ElementId(5,) },
-                HydrateText { path: &[0,], value: "hello 2".to_string(), id: ElementId(6,) },
-                LoadTemplate { name: "template", index: 0, id: ElementId(7,) },
-                HydrateText { path: &[0,], value: "hello 3".to_string(), id: ElementId(8,) },
-                LoadTemplate { name: "template", index: 0, id: ElementId(9,) },
-                HydrateText { path: &[0,], value: "hello 4".to_string(), id: ElementId(10,) },
+                LoadTemplate { index: 0, id: ElementId(1,) },
+                CreateTextNode { value: "hello 1".to_string(), id: ElementId(4,) },
+                ReplacePlaceholder { path: &[0,], m: 1 },
+                LoadTemplate { index: 0, id: ElementId(5,) },
+                CreateTextNode { value: "hello 2".to_string(), id: ElementId(6,) },
+                ReplacePlaceholder { path: &[0,], m: 1 },
+                LoadTemplate { index: 0, id: ElementId(7,) },
+                CreateTextNode { value: "hello 3".to_string(), id: ElementId(8,) },
+                ReplacePlaceholder { path: &[0,], m: 1 },
+                LoadTemplate { index: 0, id: ElementId(9,) },
+                CreateTextNode { value: "hello 4".to_string(), id: ElementId(10,) },
+                ReplacePlaceholder { path: &[0,], m: 1 },
                 InsertAfter { id: ElementId(2,), m: 4 },
                 InsertAfter { id: ElementId(2,), m: 4 },
             ]
             ]
         );
         );
     }
     }
 
 
+    // len = 0
     {
     {
         dom.mark_dirty(ScopeId::APP);
         dom.mark_dirty(ScopeId::APP);
-        let edits = dom.render_immediate_to_vec().sanitize();
+        let edits = dom.render_immediate_to_vec();
+        assert_eq!(edits.edits[0], CreatePlaceholder { id: ElementId(11,) });
+        let removed = edits.edits[1..5]
+            .iter()
+            .map(|edit| match edit {
+                Mutation::Remove { id } => *id,
+                _ => panic!("Expected remove"),
+            })
+            .collect::<HashSet<_>>();
         assert_eq!(
         assert_eq!(
-            edits.edits,
-            [
-                CreatePlaceholder { id: ElementId(11,) },
-                Remove { id: ElementId(9,) },
-                Remove { id: ElementId(7,) },
-                Remove { id: ElementId(5,) },
-                Remove { id: ElementId(1,) },
-                ReplaceWith { id: ElementId(2,), m: 1 },
-            ]
+            removed,
+            [ElementId(7), ElementId(5), ElementId(2), ElementId(1)]
+                .into_iter()
+                .collect::<HashSet<_>>()
         );
         );
+        assert_eq!(edits.edits[5..], [ReplaceWith { id: ElementId(9,), m: 1 },]);
     }
     }
 
 
+    // len = 1
     {
     {
         dom.mark_dirty(ScopeId::APP);
         dom.mark_dirty(ScopeId::APP);
-        let edits = dom.render_immediate_to_vec().sanitize();
+        let edits = dom.render_immediate_to_vec();
         assert_eq!(
         assert_eq!(
             edits.edits,
             edits.edits,
             [
             [
-                LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
-                HydrateText { path: &[0,], value: "hello 0".to_string(), id: ElementId(1,) },
+                LoadTemplate { index: 0, id: ElementId(9,) },
+                CreateTextNode { value: "hello 0".to_string(), id: ElementId(7,) },
+                ReplacePlaceholder { path: &[0,], m: 1 },
                 ReplaceWith { id: ElementId(11,), m: 1 },
                 ReplaceWith { id: ElementId(11,), m: 1 },
             ]
             ]
         )
         )
@@ -415,12 +452,13 @@ fn replace_and_add_items() {
 
 
     // The list starts empty with a placeholder
     // The list starts empty with a placeholder
     {
     {
-        let edits = dom.rebuild_to_vec().sanitize();
+        let edits = dom.rebuild_to_vec();
         assert_eq!(
         assert_eq!(
             edits.edits,
             edits.edits,
             [
             [
-                LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
-                AssignId { path: &[0], id: ElementId(2,) },
+                LoadTemplate { index: 0, id: ElementId(1,) },
+                CreatePlaceholder { id: ElementId(2,) },
+                ReplacePlaceholder { path: &[0], m: 1 },
                 AppendChildren { id: ElementId(0), m: 1 },
                 AppendChildren { id: ElementId(0), m: 1 },
             ]
             ]
         );
         );
@@ -429,11 +467,11 @@ fn replace_and_add_items() {
     // Rerendering adds an a static template
     // Rerendering adds an a static template
     {
     {
         dom.mark_dirty(ScopeId::APP);
         dom.mark_dirty(ScopeId::APP);
-        let edits = dom.render_immediate_to_vec().sanitize();
+        let edits = dom.render_immediate_to_vec();
         assert_eq!(
         assert_eq!(
             edits.edits,
             edits.edits,
             [
             [
-                LoadTemplate { name: "template", index: 0, id: ElementId(3,) },
+                LoadTemplate { index: 0, id: ElementId(3,) },
                 ReplaceWith { id: ElementId(2,), m: 1 },
                 ReplaceWith { id: ElementId(2,), m: 1 },
             ]
             ]
         );
         );
@@ -442,7 +480,7 @@ fn replace_and_add_items() {
     // Rerendering replaces the old node with a placeholder and adds a new placeholder
     // Rerendering replaces the old node with a placeholder and adds a new placeholder
     {
     {
         dom.mark_dirty(ScopeId::APP);
         dom.mark_dirty(ScopeId::APP);
-        let edits = dom.render_immediate_to_vec().sanitize();
+        let edits = dom.render_immediate_to_vec();
         assert_eq!(
         assert_eq!(
             edits.edits,
             edits.edits,
             [
             [
@@ -457,15 +495,15 @@ fn replace_and_add_items() {
     // Rerendering replaces both placeholders with the static nodes and add a new static node
     // Rerendering replaces both placeholders with the static nodes and add a new static node
     {
     {
         dom.mark_dirty(ScopeId::APP);
         dom.mark_dirty(ScopeId::APP);
-        let edits = dom.render_immediate_to_vec().sanitize();
+        let edits = dom.render_immediate_to_vec();
         assert_eq!(
         assert_eq!(
             edits.edits,
             edits.edits,
             [
             [
-                LoadTemplate { name: "template", index: 0, id: ElementId(3,) },
+                LoadTemplate { index: 0, id: ElementId(3,) },
                 InsertAfter { id: ElementId(2,), m: 1 },
                 InsertAfter { id: ElementId(2,), m: 1 },
-                LoadTemplate { name: "template", index: 0, id: ElementId(5,) },
+                LoadTemplate { index: 0, id: ElementId(5,) },
                 ReplaceWith { id: ElementId(4,), m: 1 },
                 ReplaceWith { id: ElementId(4,), m: 1 },
-                LoadTemplate { name: "template", index: 0, id: ElementId(4,) },
+                LoadTemplate { index: 0, id: ElementId(4,) },
                 ReplaceWith { id: ElementId(2,), m: 1 },
                 ReplaceWith { id: ElementId(2,), m: 1 },
             ]
             ]
         );
         );

+ 6 - 26
packages/core/tests/fuzzing.rs

@@ -1,10 +1,8 @@
 #![cfg(not(miri))]
 #![cfg(not(miri))]
 
 
 use dioxus::prelude::*;
 use dioxus::prelude::*;
-use dioxus_core::{AttributeValue, DynamicNode, NoOpMutations, VComponent, VNode, *};
-use std::{
-    cfg, collections::HashSet, default::Default, sync::atomic::AtomicUsize, sync::atomic::Ordering,
-};
+use dioxus_core::{AttributeValue, DynamicNode, NoOpMutations, Template, VComponent, VNode, *};
+use std::{cfg, collections::HashSet, default::Default};
 
 
 fn random_ns() -> Option<&'static str> {
 fn random_ns() -> Option<&'static str> {
     let namespace = rand::random::<u8>() % 2;
     let namespace = rand::random::<u8>() % 2;
@@ -129,7 +127,7 @@ enum DynamicNodeType {
     Other,
     Other,
 }
 }
 
 
-fn create_random_template(name: &'static str) -> (Template, Vec<DynamicNodeType>) {
+fn create_random_template() -> (Template, Vec<DynamicNodeType>) {
     let mut dynamic_node_type = Vec::new();
     let mut dynamic_node_type = Vec::new();
     let mut template_idx = 0;
     let mut template_idx = 0;
     let mut attr_idx = 0;
     let mut attr_idx = 0;
@@ -160,7 +158,7 @@ fn create_random_template(name: &'static str) -> (Template, Vec<DynamicNodeType>
             .into_boxed_slice(),
             .into_boxed_slice(),
     );
     );
     (
     (
-        Template { name, roots, node_paths, attr_paths },
+        Template { roots, node_paths, attr_paths },
         dynamic_node_type,
         dynamic_node_type,
     )
     )
 }
 }
@@ -174,7 +172,6 @@ fn create_random_dynamic_node(depth: usize) -> DynamicNode {
                 VNode::new(
                 VNode::new(
                     None,
                     None,
                     Template {
                     Template {
-                        name: create_template_location(),
                         roots: &[TemplateNode::Dynamic { id: 0 }],
                         roots: &[TemplateNode::Dynamic { id: 0 }],
                         node_paths: &[&[0]],
                         node_paths: &[&[0]],
                         attr_paths: &[],
                         attr_paths: &[],
@@ -219,19 +216,6 @@ fn create_random_dynamic_attr() -> Attribute {
     )
     )
 }
 }
 
 
-static TEMPLATE_COUNT: AtomicUsize = AtomicUsize::new(0);
-
-fn create_template_location() -> &'static str {
-    Box::leak(
-        format!(
-            "{}{}",
-            concat!(file!(), ":", line!(), ":", column!(), ":"),
-            TEMPLATE_COUNT.fetch_add(1, Ordering::Relaxed)
-        )
-        .into_boxed_str(),
-    )
-}
-
 #[derive(PartialEq, Props, Clone)]
 #[derive(PartialEq, Props, Clone)]
 struct DepthProps {
 struct DepthProps {
     depth: usize,
     depth: usize,
@@ -245,7 +229,7 @@ fn create_random_element(cx: DepthProps) -> Element {
     let range = if cx.root { 2 } else { 3 };
     let range = if cx.root { 2 } else { 3 };
     let node = match rand::random::<usize>() % range {
     let node = match rand::random::<usize>() % range {
         0 | 1 => {
         0 | 1 => {
-            let (template, dynamic_node_types) = create_random_template(create_template_location());
+            let (template, dynamic_node_types) = create_random_template();
             let node = VNode::new(
             let node = VNode::new(
                 None,
                 None,
                 template,
                 template,
@@ -314,8 +298,6 @@ fn diff() {
 struct InsertEventListenerMutationHandler<'a>(&'a mut HashSet<ElementId>);
 struct InsertEventListenerMutationHandler<'a>(&'a mut HashSet<ElementId>);
 
 
 impl WriteMutations for InsertEventListenerMutationHandler<'_> {
 impl WriteMutations for InsertEventListenerMutationHandler<'_> {
-    fn register_template(&mut self, _: Template) {}
-
     fn append_children(&mut self, _: ElementId, _: usize) {}
     fn append_children(&mut self, _: ElementId, _: usize) {}
 
 
     fn assign_node_id(&mut self, _: &'static [u8], _: ElementId) {}
     fn assign_node_id(&mut self, _: &'static [u8], _: ElementId) {}
@@ -324,9 +306,7 @@ impl WriteMutations for InsertEventListenerMutationHandler<'_> {
 
 
     fn create_text_node(&mut self, _: &str, _: ElementId) {}
     fn create_text_node(&mut self, _: &str, _: ElementId) {}
 
 
-    fn hydrate_text_node(&mut self, _: &'static [u8], _: &str, _: ElementId) {}
-
-    fn load_template(&mut self, _: &'static str, _: usize, _: ElementId) {}
+    fn load_template(&mut self, _: Template, _: usize, _: ElementId) {}
 
 
     fn replace_node_with(&mut self, _: ElementId, _: usize) {}
     fn replace_node_with(&mut self, _: ElementId, _: usize) {}
 
 

+ 4 - 3
packages/core/tests/kitchen_sink.rs

@@ -33,13 +33,14 @@ fn basic_syntax_is_a_template() -> Element {
 #[test]
 #[test]
 fn dual_stream() {
 fn dual_stream() {
     let mut dom = VirtualDom::new(basic_syntax_is_a_template);
     let mut dom = VirtualDom::new(basic_syntax_is_a_template);
-    let edits = dom.rebuild_to_vec().sanitize();
+    let edits = dom.rebuild_to_vec();
 
 
     use Mutation::*;
     use Mutation::*;
     assert_eq!(edits.edits, {
     assert_eq!(edits.edits, {
         [
         [
-            LoadTemplate { name: "template", index: 0, id: ElementId(1) },
-            HydrateText { path: &[0, 0], value: "123".to_string(), id: ElementId(2) },
+            LoadTemplate { index: 0, id: ElementId(1) },
+            CreateTextNode { value: "123".to_string(), id: ElementId(2) },
+            ReplacePlaceholder { path: &[0, 0], m: 1 },
             SetAttribute {
             SetAttribute {
                 name: "class",
                 name: "class",
                 value: "asd 123 123 ".into_value(),
                 value: "asd 123 123 ".into_value(),

+ 4 - 3
packages/core/tests/lifecycle.rs

@@ -30,10 +30,11 @@ fn manual_diffing() {
     *value.lock().unwrap() = "goodbye";
     *value.lock().unwrap() = "goodbye";
 
 
     assert_eq!(
     assert_eq!(
-        dom.rebuild_to_vec().sanitize().edits,
+        dom.rebuild_to_vec().edits,
         [
         [
-            LoadTemplate { name: "template", index: 0, id: ElementId(3) },
-            HydrateText { path: &[0], value: "goodbye".to_string(), id: ElementId(4) },
+            LoadTemplate { index: 0, id: ElementId(3) },
+            CreateTextNode { value: "goodbye".to_string(), id: ElementId(4) },
+            ReplacePlaceholder { path: &[0], m: 1 },
             AppendChildren { m: 1, id: ElementId(0) }
             AppendChildren { m: 1, id: ElementId(0) }
         ]
         ]
     );
     );

+ 47 - 72
packages/core/tests/suspense.rs

@@ -415,20 +415,20 @@ fn toggle_suspense() {
         .unwrap()
         .unwrap()
         .block_on(async {
         .block_on(async {
             let mut dom = VirtualDom::new(app);
             let mut dom = VirtualDom::new(app);
-            let mutations = dom.rebuild_to_vec().sanitize();
+            let mutations = dom.rebuild_to_vec();
 
 
             // First create goodbye world
             // First create goodbye world
             println!("{:#?}", mutations);
             println!("{:#?}", mutations);
             assert_eq!(
             assert_eq!(
                 mutations.edits,
                 mutations.edits,
                 [
                 [
-                    Mutation::LoadTemplate { name: "template", index: 0, id: ElementId(1) },
+                    Mutation::LoadTemplate { index: 0, id: ElementId(1) },
                     Mutation::AppendChildren { id: ElementId(0), m: 1 }
                     Mutation::AppendChildren { id: ElementId(0), m: 1 }
                 ]
                 ]
             );
             );
 
 
             dom.mark_dirty(ScopeId::APP);
             dom.mark_dirty(ScopeId::APP);
-            let mutations = dom.render_immediate_to_vec().sanitize();
+            let mutations = dom.render_immediate_to_vec();
 
 
             // Then replace that with nothing
             // Then replace that with nothing
             println!("{:#?}", mutations);
             println!("{:#?}", mutations);
@@ -441,20 +441,20 @@ fn toggle_suspense() {
             );
             );
 
 
             dom.wait_for_work().await;
             dom.wait_for_work().await;
-            let mutations = dom.render_immediate_to_vec().sanitize();
+            let mutations = dom.render_immediate_to_vec();
 
 
             // Then replace it with a placeholder
             // Then replace it with a placeholder
             println!("{:#?}", mutations);
             println!("{:#?}", mutations);
             assert_eq!(
             assert_eq!(
                 mutations.edits,
                 mutations.edits,
                 [
                 [
-                    Mutation::LoadTemplate { name: "template", index: 0, id: ElementId(1) },
+                    Mutation::LoadTemplate { index: 0, id: ElementId(1) },
                     Mutation::ReplaceWith { id: ElementId(2), m: 1 },
                     Mutation::ReplaceWith { id: ElementId(2), m: 1 },
                 ]
                 ]
             );
             );
 
 
             dom.wait_for_work().await;
             dom.wait_for_work().await;
-            let mutations = dom.render_immediate_to_vec().sanitize();
+            let mutations = dom.render_immediate_to_vec();
 
 
             // Then replace it with the resolved node
             // Then replace it with the resolved node
             println!("{:#?}", mutations);
             println!("{:#?}", mutations);
@@ -463,7 +463,7 @@ fn toggle_suspense() {
                 [
                 [
                     Mutation::CreatePlaceholder { id: ElementId(2,) },
                     Mutation::CreatePlaceholder { id: ElementId(2,) },
                     Mutation::ReplaceWith { id: ElementId(1,), m: 1 },
                     Mutation::ReplaceWith { id: ElementId(1,), m: 1 },
-                    Mutation::LoadTemplate { name: "template", index: 0, id: ElementId(1) },
+                    Mutation::LoadTemplate { index: 0, id: ElementId(1) },
                     Mutation::ReplaceWith { id: ElementId(2), m: 1 },
                     Mutation::ReplaceWith { id: ElementId(2), m: 1 },
                 ]
                 ]
             );
             );
@@ -600,14 +600,14 @@ fn nested_suspense_resolves_client() {
             // DOM STATE:
             // DOM STATE:
             // placeholder // ID: 1
             // placeholder // ID: 1
             // "Loading 0..." // ID: 2
             // "Loading 0..." // ID: 2
-            let mutations = dom.render_immediate_to_vec().sanitize();
+            let mutations = dom.render_immediate_to_vec();
             // Fill in the contents of the initial message and start loading the nested suspense
             // Fill in the contents of the initial message and start loading the nested suspense
             // The title also finishes loading
             // The title also finishes loading
             assert_eq!(
             assert_eq!(
                 mutations.edits,
                 mutations.edits,
                 vec![
                 vec![
                     // Creating and swapping these placeholders doesn't do anything
                     // Creating and swapping these placeholders doesn't do anything
-                    // It is just extra work that we are forced to do because mutations are not 
+                    // It is just extra work that we are forced to do because mutations are not
                     // reversible. We start rendering the children and then realize it is suspended.
                     // reversible. We start rendering the children and then realize it is suspended.
                     // Then we need to replace what we just rendered with the suspense placeholder
                     // Then we need to replace what we just rendered with the suspense placeholder
                     CreatePlaceholder { id: ElementId(3,) },
                     CreatePlaceholder { id: ElementId(3,) },
@@ -622,12 +622,9 @@ fn nested_suspense_resolves_client() {
                     ReplaceWith { id: ElementId(2,), m: 1 },
                     ReplaceWith { id: ElementId(2,), m: 1 },
 
 
                     // Load the title
                     // Load the title
-                    LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
-                    HydrateText {
-                        path: &[0,],
-                        value: "The robot says hello world".to_string(),
-                        id: ElementId(4,),
-                    },
+                    LoadTemplate {  index: 0, id: ElementId(2,) },
+                    CreateTextNode { value: "The robot says hello world".to_string(), id: ElementId(4,) },
+                    ReplacePlaceholder { path: &[0,], m: 1 },
                     SetAttribute {
                     SetAttribute {
                         name: "id",
                         name: "id",
                         ns: None,
                         ns: None,
@@ -636,12 +633,9 @@ fn nested_suspense_resolves_client() {
                     },
                     },
 
 
                     // Then load the body
                     // Then load the body
-                    LoadTemplate { name: "template", index: 1, id: ElementId(5,) },
-                    HydrateText {
-                        path: &[0,],
-                        value: "The robot becomes sentient and says hello world".to_string(),
-                        id: ElementId(6,),
-                    },
+                    LoadTemplate {  index: 1, id: ElementId(5,) },
+                    CreateTextNode { value: "The robot becomes sentient and says hello world".to_string(), id: ElementId(6,) },
+                    ReplacePlaceholder { path: &[0,], m: 1 },
                     SetAttribute {
                     SetAttribute {
                         name: "id",
                         name: "id",
                         ns: None,
                         ns: None,
@@ -650,7 +644,7 @@ fn nested_suspense_resolves_client() {
                     },
                     },
 
 
                     // Then load the suspended children
                     // Then load the suspended children
-                    LoadTemplate { name: "template", index: 2, id: ElementId(7,) },
+                    LoadTemplate {  index: 2, id: ElementId(7,) },
                     CreateTextNode { value: "Loading 1...".to_string(), id: ElementId(8,) },
                     CreateTextNode { value: "Loading 1...".to_string(), id: ElementId(8,) },
                     CreateTextNode { value: "Loading 2...".to_string(), id: ElementId(9,) },
                     CreateTextNode { value: "Loading 2...".to_string(), id: ElementId(9,) },
                     ReplacePlaceholder { path: &[0,], m: 2 },
                     ReplacePlaceholder { path: &[0,], m: 2 },
@@ -674,7 +668,7 @@ fn nested_suspense_resolves_client() {
             // div // ID: 7
             // div // ID: 7
             //   "Loading 1..." // ID: 8
             //   "Loading 1..." // ID: 8
             //   "Loading 2..." // ID: 9
             //   "Loading 2..." // ID: 9
-            let mutations = dom.render_immediate_to_vec().sanitize();
+            let mutations = dom.render_immediate_to_vec();
             assert_eq!(
             assert_eq!(
                 mutations.edits,
                 mutations.edits,
                 vec![
                 vec![
@@ -693,20 +687,18 @@ fn nested_suspense_resolves_client() {
 
 
                     // Load the nested suspense
                     // Load the nested suspense
                     LoadTemplate {
                     LoadTemplate {
-                        name: "template",
+
                         index: 0,
                         index: 0,
                         id: ElementId(
                         id: ElementId(
                             8,
                             8,
                         ),
                         ),
                     },
                     },
-                    HydrateText {
+                    CreateTextNode { value: "The world says hello back".to_string(), id: ElementId(10,) },
+                    ReplacePlaceholder {
                         path: &[
                         path: &[
                             0,
                             0,
                         ],
                         ],
-                        value: "The world says hello back".to_string(),
-                        id: ElementId(
-                            10,
-                        ),
+                        m: 1,
                     },
                     },
                     SetAttribute {
                     SetAttribute {
                         name: "id",
                         name: "id",
@@ -717,20 +709,18 @@ fn nested_suspense_resolves_client() {
                         ),
                         ),
                     },
                     },
                     LoadTemplate {
                     LoadTemplate {
-                        name: "template",
+
                         index: 1,
                         index: 1,
                         id: ElementId(
                         id: ElementId(
                             11,
                             11,
                         ),
                         ),
                     },
                     },
-                    HydrateText {
+                    CreateTextNode { value: "In a stunning turn of events, the world collectively unites and says hello back".to_string(), id: ElementId(12,) },
+                    ReplacePlaceholder {
                         path: &[
                         path: &[
                             0,
                             0,
                         ],
                         ],
-                        value: "In a stunning turn of events, the world collectively unites and says hello back".to_string(),
-                        id: ElementId(
-                            12,
-                        ),
+                        m: 1,
                     },
                     },
                     SetAttribute {
                     SetAttribute {
                         name: "id",
                         name: "id",
@@ -741,19 +731,17 @@ fn nested_suspense_resolves_client() {
                         ),
                         ),
                     },
                     },
                     LoadTemplate {
                     LoadTemplate {
-                        name: "template",
                         index: 2,
                         index: 2,
                         id: ElementId(
                         id: ElementId(
                             13,
                             13,
                         ),
                         ),
                     },
                     },
-                    AssignId {
+                    CreatePlaceholder { id: ElementId(14,) },
+                    ReplacePlaceholder {
                         path: &[
                         path: &[
                             0,
                             0,
                         ],
                         ],
-                        id: ElementId(
-                            14,
-                        ),
+                        m: 1,
                     },
                     },
                     SetAttribute {
                     SetAttribute {
                         name: "id",
                         name: "id",
@@ -783,20 +771,17 @@ fn nested_suspense_resolves_client() {
                         m: 1,
                         m: 1,
                     },
                     },
                     LoadTemplate {
                     LoadTemplate {
-                        name: "template",
                         index: 0,
                         index: 0,
                         id: ElementId(
                         id: ElementId(
                             9,
                             9,
                         ),
                         ),
                     },
                     },
-                    HydrateText {
+                    CreateTextNode { value: "Goodbye Robot".to_string(), id: ElementId(15,) },
+                    ReplacePlaceholder {
                         path: &[
                         path: &[
                             0,
                             0,
                         ],
                         ],
-                        value: "Goodbye Robot".to_string(),
-                        id: ElementId(
-                            15,
-                        ),
+                        m: 1,
                     },
                     },
                     SetAttribute {
                     SetAttribute {
                         name: "id",
                         name: "id",
@@ -807,20 +792,17 @@ fn nested_suspense_resolves_client() {
                         ),
                         ),
                     },
                     },
                     LoadTemplate {
                     LoadTemplate {
-                        name: "template",
                         index: 1,
                         index: 1,
                         id: ElementId(
                         id: ElementId(
                             16,
                             16,
                         ),
                         ),
                     },
                     },
-                    HydrateText {
+                    CreateTextNode { value: "The robot says goodbye".to_string(), id: ElementId(17,) },
+                    ReplacePlaceholder {
                         path: &[
                         path: &[
                             0,
                             0,
                         ],
                         ],
-                        value: "The robot says goodbye".to_string(),
-                        id: ElementId(
-                            17,
-                        ),
+                        m: 1,
                     },
                     },
                     SetAttribute {
                     SetAttribute {
                         name: "id",
                         name: "id",
@@ -831,7 +813,7 @@ fn nested_suspense_resolves_client() {
                         ),
                         ),
                     },
                     },
                     LoadTemplate {
                     LoadTemplate {
-                        name: "template",
+
                         index: 2,
                         index: 2,
                         id: ElementId(
                         id: ElementId(
                             18,
                             18,
@@ -860,7 +842,7 @@ fn nested_suspense_resolves_client() {
             );
             );
 
 
             dom.wait_for_work().await;
             dom.wait_for_work().await;
-            let mutations = dom.render_immediate_to_vec().sanitize();
+            let mutations = dom.render_immediate_to_vec();
             assert_eq!(
             assert_eq!(
                 mutations.edits,
                 mutations.edits,
                 vec![
                 vec![
@@ -876,20 +858,18 @@ fn nested_suspense_resolves_client() {
                         m: 1,
                         m: 1,
                     },
                     },
                     LoadTemplate {
                     LoadTemplate {
-                        name: "template",
+
                         index: 0,
                         index: 0,
                         id: ElementId(
                         id: ElementId(
                             19,
                             19,
                         ),
                         ),
                     },
                     },
-                    HydrateText {
+                    CreateTextNode { value: "Goodbye Robot again".to_string(), id: ElementId(20,) },
+                    ReplacePlaceholder {
                         path: &[
                         path: &[
                             0,
                             0,
                         ],
                         ],
-                        value: "Goodbye Robot again".to_string(),
-                        id: ElementId(
-                            20,
-                        ),
+                        m: 1,
                     },
                     },
                     SetAttribute {
                     SetAttribute {
                         name: "id",
                         name: "id",
@@ -900,20 +880,17 @@ fn nested_suspense_resolves_client() {
                         ),
                         ),
                     },
                     },
                     LoadTemplate {
                     LoadTemplate {
-                        name: "template",
                         index: 1,
                         index: 1,
                         id: ElementId(
                         id: ElementId(
                             21,
                             21,
                         ),
                         ),
                     },
                     },
-                    HydrateText {
+                    CreateTextNode { value: "The robot says goodbye again".to_string(), id: ElementId(22,) },
+                    ReplacePlaceholder {
                         path: &[
                         path: &[
                             0,
                             0,
                         ],
                         ],
-                        value: "The robot says goodbye again".to_string(),
-                        id: ElementId(
-                            22,
-                        ),
+                        m: 1,
                     },
                     },
                     SetAttribute {
                     SetAttribute {
                         name: "id",
                         name: "id",
@@ -924,19 +901,17 @@ fn nested_suspense_resolves_client() {
                         ),
                         ),
                     },
                     },
                     LoadTemplate {
                     LoadTemplate {
-                        name: "template",
                         index: 2,
                         index: 2,
                         id: ElementId(
                         id: ElementId(
                             23,
                             23,
                         ),
                         ),
                     },
                     },
-                    AssignId {
+                    CreatePlaceholder { id: ElementId(24,) },
+                    ReplacePlaceholder {
                         path: &[
                         path: &[
-                            0,
+                            0
                         ],
                         ],
-                        id: ElementId(
-                            24,
-                        ),
+                        m: 1,
                     },
                     },
                     SetAttribute {
                     SetAttribute {
                         name: "id",
                         name: "id",

+ 21 - 21
packages/desktop/headless_tests/events.rs

@@ -165,11 +165,11 @@ fn test_mouse_dblclick_div() -> Element {
     utils::mock_event(
     utils::mock_event(
         "mouse_dblclick_div",
         "mouse_dblclick_div",
         r#"new MouseEvent("dblclick", {
         r#"new MouseEvent("dblclick", {
-        view: window,
-        bubbles: true,
-        cancelable: true,
-        buttons: 1|2,
-        button: 2,
+            view: window,
+            bubbles: true,
+            cancelable: true,
+            buttons: 1|2,
+            button: 2,
         })"#,
         })"#,
     );
     );
 
 
@@ -205,11 +205,11 @@ fn test_mouse_down_div() -> Element {
     utils::mock_event(
     utils::mock_event(
         "mouse_down_div",
         "mouse_down_div",
         r#"new MouseEvent("mousedown", {
         r#"new MouseEvent("mousedown", {
-        view: window,
-        bubbles: true,
-        cancelable: true,
-        buttons: 2,
-        button: 2,
+            view: window,
+            bubbles: true,
+            cancelable: true,
+            buttons: 2,
+            button: 2,
         })"#,
         })"#,
     );
     );
 
 
@@ -239,11 +239,11 @@ fn test_mouse_up_div() -> Element {
     utils::mock_event(
     utils::mock_event(
         "mouse_up_div",
         "mouse_up_div",
         r#"new MouseEvent("mouseup", {
         r#"new MouseEvent("mouseup", {
-        view: window,
-        bubbles: true,
-        cancelable: true,
-        buttons: 0,
-        button: 0,
+            view: window,
+            bubbles: true,
+            cancelable: true,
+            buttons: 0,
+            button: 0,
         })"#,
         })"#,
     );
     );
 
 
@@ -268,12 +268,12 @@ fn test_mouse_scroll_div() -> Element {
     utils::mock_event(
     utils::mock_event(
         "wheel_div",
         "wheel_div",
         r#"new WheelEvent("wheel", {
         r#"new WheelEvent("wheel", {
-        view: window,
-        deltaX: 1.0,
-        deltaY: 2.0,
-        deltaZ: 3.0,
-        deltaMode: 0x00,
-        bubbles: true,
+            view: window,
+            deltaX: 1.0,
+            deltaY: 2.0,
+            deltaZ: 3.0,
+            deltaMode: 0x00,
+            bubbles: true,
         })"#,
         })"#,
     );
     );
 
 

+ 3 - 0
packages/fullstack/examples/desktop/src/main.rs

@@ -34,6 +34,9 @@ async fn main() {
     .unwrap();
     .unwrap();
 }
 }
 
 
+#[cfg(all(not(feature = "desktop"), not(feature = "server")))]
+fn main() {}
+
 pub fn app() -> Element {
 pub fn app() -> Element {
     let mut count = use_signal(|| 0);
     let mut count = use_signal(|| 0);
     let mut text = use_signal(|| "...".to_string());
     let mut text = use_signal(|| "...".to_string());

+ 5 - 34
packages/interpreter/src/unified_bindings.rs

@@ -126,19 +126,6 @@ mod js {
     fn assign_id(ptr: u32, len: u8, id: u32) {
     fn assign_id(ptr: u32, len: u8, id: u32) {
         "{this.nodes[$id$] = this.loadChild($ptr$, $len$);}"
         "{this.nodes[$id$] = this.loadChild($ptr$, $len$);}"
     }
     }
-    fn hydrate_text(ptr: u32, len: u8, value: &str, id: u32) {
-        r#"{
-            let node = this.loadChild($ptr$, $len$);
-            if (node.nodeType == node.TEXT_NODE) {
-                node.textContent = value;
-            } else {
-                let text = document.createTextNode(value);
-                node.replaceWith(text);
-                node = text;
-            }
-            this.nodes[$id$] = node;
-        }"#
-    }
     fn replace_placeholder(ptr: u32, len: u8, n: u16) {
     fn replace_placeholder(ptr: u32, len: u8, n: u16) {
         "{let els = this.stack.splice(this.stack.length - $n$); let node = this.loadChild($ptr$, $len$); node.replaceWith(...els);}"
         "{let els = this.stack.splice(this.stack.length - $n$); let node = this.loadChild($ptr$, $len$); node.replaceWith(...els);}"
     }
     }
@@ -149,11 +136,11 @@ mod js {
     #[cfg(feature = "binary-protocol")]
     #[cfg(feature = "binary-protocol")]
     fn append_children_to_top(many: u16) {
     fn append_children_to_top(many: u16) {
         "{
         "{
-        let root = this.stack[this.stack.length-many-1];
-        let els = this.stack.splice(this.stack.length-many);
-        for (let k = 0; k < many; k++) {
-            root.appendChild(els[k]);
-        }
+            let root = this.stack[this.stack.length-many-1];
+            let els = this.stack.splice(this.stack.length-many);
+            for (let k = 0; k < many; k++) {
+                root.appendChild(els[k]);
+            }
         }"
         }"
     }
     }
 
 
@@ -218,22 +205,6 @@ mod js {
         "{this.nodes[$id$] = this.loadChild($array$);}"
         "{this.nodes[$id$] = this.loadChild($array$);}"
     }
     }
 
 
-    /// The coolest ID ever!
-    #[cfg(feature = "binary-protocol")]
-    fn hydrate_text_ref(array: &[u8], value: &str, id: u32) {
-        r#"{
-        let node = this.loadChild($array$);
-        if (node.nodeType == node.TEXT_NODE) {
-            node.textContent = value;
-        } else {
-            let text = document.createTextNode(value);
-            node.replaceWith(text);
-            node = text;
-        }
-        this.nodes[$id$] = node;
-    }"#
-    }
-
     #[cfg(feature = "binary-protocol")]
     #[cfg(feature = "binary-protocol")]
     fn replace_placeholder_ref(array: &[u8], n: u16) {
     fn replace_placeholder_ref(array: &[u8], n: u16) {
         "{let els = this.stack.splice(this.stack.length - $n$); let node = this.loadChild($array$); node.replaceWith(...els);}"
         "{let els = this.stack.splice(this.stack.length - $n$); let node = this.loadChild($array$); node.replaceWith(...els);}"

+ 16 - 26
packages/interpreter/src/write_native_mutations.rs

@@ -1,16 +1,13 @@
 use crate::unified_bindings::Interpreter as Channel;
 use crate::unified_bindings::Interpreter as Channel;
-use dioxus_core::{TemplateAttribute, TemplateNode, WriteMutations};
+use dioxus_core::{Template, TemplateAttribute, TemplateNode, WriteMutations};
 use dioxus_html::event_bubbles;
 use dioxus_html::event_bubbles;
 use sledgehammer_utils::rustc_hash::FxHashMap;
 use sledgehammer_utils::rustc_hash::FxHashMap;
 
 
 /// The state needed to apply mutations to a channel. This state should be kept across all mutations for the app
 /// The state needed to apply mutations to a channel. This state should be kept across all mutations for the app
 #[derive(Default)]
 #[derive(Default)]
 pub struct MutationState {
 pub struct MutationState {
-    /// The maximum number of templates that we have registered
-    max_template_count: u16,
-
     /// The currently registered templates with the template ids
     /// The currently registered templates with the template ids
-    templates: FxHashMap<String, u16>,
+    templates: FxHashMap<Template, u16>,
 
 
     /// The channel that we are applying mutations to
     /// The channel that we are applying mutations to
     channel: Channel,
     channel: Channel,
@@ -81,19 +78,6 @@ impl MutationState {
 }
 }
 
 
 impl WriteMutations for MutationState {
 impl WriteMutations for MutationState {
-    fn register_template(&mut self, template: dioxus_core::prelude::Template) {
-        let current_max_template_count = self.max_template_count;
-        for root in template.roots.iter() {
-            self.create_template_node(root);
-            self.templates
-                .insert(template.name.to_owned(), current_max_template_count);
-        }
-        self.channel
-            .add_templates(current_max_template_count, template.roots.len() as u16);
-
-        self.max_template_count += 1;
-    }
-
     fn append_children(&mut self, id: dioxus_core::ElementId, m: usize) {
     fn append_children(&mut self, id: dioxus_core::ElementId, m: usize) {
         self.channel.append_children(id.0 as u32, m as u16);
         self.channel.append_children(id.0 as u32, m as u16);
     }
     }
@@ -110,15 +94,21 @@ impl WriteMutations for MutationState {
         self.channel.create_text_node(value, id.0 as u32);
         self.channel.create_text_node(value, id.0 as u32);
     }
     }
 
 
-    fn hydrate_text_node(&mut self, path: &'static [u8], value: &str, id: dioxus_core::ElementId) {
-        self.channel.hydrate_text_ref(path, value, id.0 as u32);
-    }
+    fn load_template(&mut self, template: Template, index: usize, id: dioxus_core::ElementId) {
+        // Get the template or create it if we haven't seen it before
+        let tmpl_id = self.templates.get(&template).cloned().unwrap_or_else(|| {
+            let current_max_template_count = self.templates.len() as u16;
+            for root in template.roots.iter() {
+                self.create_template_node(root);
+                self.templates.insert(template, current_max_template_count);
+            }
+            let id = template.roots.len() as u16;
+            self.channel.add_templates(current_max_template_count, id);
+            current_max_template_count
+        });
 
 
-    fn load_template(&mut self, name: &'static str, index: usize, id: dioxus_core::ElementId) {
-        if let Some(tmpl_id) = self.templates.get(name) {
-            self.channel
-                .load_template(*tmpl_id, index as u16, id.0 as u32)
-        }
+        self.channel
+            .load_template(tmpl_id, index as u16, id.0 as u32);
     }
     }
 
 
     fn replace_node_with(&mut self, id: dioxus_core::ElementId, m: usize) {
     fn replace_node_with(&mut self, id: dioxus_core::ElementId, m: usize) {

+ 0 - 19
packages/rsx/src/hot_reload/diff.rs

@@ -70,9 +70,6 @@ use dioxus_core::internal::{
     HotReloadLiteral, HotReloadedTemplate, NamedAttribute,
     HotReloadLiteral, HotReloadedTemplate, NamedAttribute,
 };
 };
 use std::collections::HashMap;
 use std::collections::HashMap;
-use std::hash::DefaultHasher;
-use std::hash::Hash;
-use std::hash::Hasher;
 
 
 use super::last_build_state::LastBuildState;
 use super::last_build_state::LastBuildState;
 
 
@@ -200,23 +197,7 @@ impl HotReloadResult {
             .collect();
             .collect();
         let roots: &[dioxus_core::TemplateNode] = intern(&*roots);
         let roots: &[dioxus_core::TemplateNode] = intern(&*roots);
 
 
-        // Add the template name, the dyn index and the hash of the template to get a unique name
-        let name = {
-            let mut hasher = DefaultHasher::new();
-            key.hash(&mut hasher);
-            new_dynamic_attributes.hash(&mut hasher);
-            new_dynamic_nodes.hash(&mut hasher);
-            literal_component_properties.hash(&mut hasher);
-            roots.hash(&mut hasher);
-            let hash = hasher.finish();
-            let name = &self.full_rebuild_state.name;
-
-            format!("{}:{}-{}", name, hash, new.template_idx.get())
-        };
-        let name = Box::leak(name.into_boxed_str());
-
         let template = HotReloadedTemplate::new(
         let template = HotReloadedTemplate::new(
-            name,
             key,
             key,
             new_dynamic_nodes,
             new_dynamic_nodes,
             new_dynamic_attributes,
             new_dynamic_attributes,

+ 47 - 55
packages/rsx/src/template_body.rs

@@ -139,65 +139,58 @@ impl ToTokens for TemplateBody {
 
 
         let dynamic_text = self.dynamic_text_segments.iter();
         let dynamic_text = self.dynamic_text_segments.iter();
 
 
+        let diagnostics = &self.diagnostics;
         let index = self.template_idx.get();
         let index = self.template_idx.get();
+        let hot_reload_mapping = self.hot_reload_mapping();
 
 
-        let diagnostics = &self.diagnostics;
-        let hot_reload_mapping = self.hot_reload_mapping(quote! { ___TEMPLATE_NAME });
-
-        let vnode = quote! {
-            #[doc(hidden)] // vscode please stop showing these in symbol search
-            const ___TEMPLATE_NAME: &str = {
-                const PATH: &str = dioxus_core::const_format::str_replace!(file!(), "\\\\", "/");
-                const NORMAL: &str = dioxus_core::const_format::str_replace!(PATH, '\\', "/");
-                dioxus_core::const_format::concatcp!(NORMAL, ':', line!(), ':', column!(), ':', #index)
-            };
-            #[cfg(debug_assertions)]
-            {
-                // The key is important here - we're creating a new GlobalSignal each call to this
-                // But the key is what's keeping it stable
-                let __template = GlobalSignal::with_key(
-                    || #hot_reload_mapping,
-                    ___TEMPLATE_NAME
-                );
-
-                __template.maybe_with_rt(|__template_read| {
-                    let mut __dynamic_literal_pool = dioxus_core::internal::DynamicLiteralPool::new(
-                        vec![ #( #dynamic_text.to_string() ),* ],
-                    );
-                    let mut __dynamic_value_pool = dioxus_core::internal::DynamicValuePool::new(
-                        vec![ #( #dynamic_nodes ),* ],
-                        vec![ #( #dyn_attr_printer ),* ],
-                        __dynamic_literal_pool
-                    );
-                    __dynamic_value_pool.render_with(__template_read)
-                })
-            }
-            #[cfg(not(debug_assertions))]
-            {
-                #[doc(hidden)] // vscode please stop showing these in symbol search
-                static ___TEMPLATE: dioxus_core::Template = dioxus_core::Template {
-                    name: ___TEMPLATE_NAME,
-                    roots: &[ #( #roots ),* ],
-                    node_paths: &[ #( #node_paths ),* ],
-                    attr_paths: &[ #( #attr_paths ),* ],
-                };
-
-                // NOTE: Allocating a temporary is important to make reads within rsx drop before the value is returned
-                #[allow(clippy::let_and_return)]
-                let __vnodes = dioxus_core::VNode::new(
-                    #key_tokens,
-                    ___TEMPLATE,
-                    Box::new([ #( #dynamic_nodes ),* ]),
-                    Box::new([ #( #dyn_attr_printer ),* ]),
-                );
-                __vnodes
-            }
-        };
         tokens.append_all(quote! {
         tokens.append_all(quote! {
             dioxus_core::Element::Ok({
             dioxus_core::Element::Ok({
                 #diagnostics
                 #diagnostics
 
 
-                #vnode
+                #[cfg(debug_assertions)]
+                {
+                    // The key is important here - we're creating a new GlobalSignal each call to this
+                    // But the key is what's keeping it stable
+                    let __template = GlobalSignal::with_key(
+                        || #hot_reload_mapping,
+                        {
+                            const PATH: &str = dioxus_core::const_format::str_replace!(file!(), "\\\\", "/");
+                            const NORMAL: &str = dioxus_core::const_format::str_replace!(PATH, '\\', "/");
+                            dioxus_core::const_format::concatcp!(NORMAL, ':', line!(), ':', column!(), ':', #index)
+                        }
+                    );
+
+                    __template.maybe_with_rt(|__template_read| {
+                        let mut __dynamic_literal_pool = dioxus_core::internal::DynamicLiteralPool::new(
+                            vec![ #( #dynamic_text.to_string() ),* ],
+                        );
+                        let mut __dynamic_value_pool = dioxus_core::internal::DynamicValuePool::new(
+                            vec![ #( #dynamic_nodes ),* ],
+                            vec![ #( #dyn_attr_printer ),* ],
+                            __dynamic_literal_pool
+                        );
+                        __dynamic_value_pool.render_with(__template_read)
+                    })
+                }
+                #[cfg(not(debug_assertions))]
+                {
+                    #[doc(hidden)] // vscode please stop showing these in symbol search
+                    static ___TEMPLATE: dioxus_core::Template = dioxus_core::Template {
+                        roots: &[ #( #roots ),* ],
+                        node_paths: &[ #( #node_paths ),* ],
+                        attr_paths: &[ #( #attr_paths ),* ],
+                    };
+
+                    // NOTE: Allocating a temporary is important to make reads within rsx drop before the value is returned
+                    #[allow(clippy::let_and_return)]
+                    let __vnodes = dioxus_core::VNode::new(
+                        #key_tokens,
+                        ___TEMPLATE,
+                        Box::new([ #( #dynamic_nodes ),* ]),
+                        Box::new([ #( #dyn_attr_printer ),* ]),
+                    );
+                    __vnodes
+                }
             })
             })
         });
         });
     }
     }
@@ -324,7 +317,7 @@ impl TemplateBody {
             })
             })
     }
     }
 
 
-    fn hot_reload_mapping(&self, name: impl ToTokens) -> TokenStream2 {
+    fn hot_reload_mapping(&self) -> TokenStream2 {
         let key = if let Some(AttributeValue::AttrLiteral(HotLiteral::Fmted(key))) =
         let key = if let Some(AttributeValue::AttrLiteral(HotLiteral::Fmted(key))) =
             self.implicit_key()
             self.implicit_key()
         {
         {
@@ -346,7 +339,6 @@ impl TemplateBody {
             .map(|literal| literal.quote_as_hot_reload_literal());
             .map(|literal| literal.quote_as_hot_reload_literal());
         quote! {
         quote! {
             dioxus_core::internal::HotReloadedTemplate::new(
             dioxus_core::internal::HotReloadedTemplate::new(
-                #name,
                 #key,
                 #key,
                 vec![ #( #dynamic_nodes ),* ],
                 vec![ #( #dynamic_nodes ),* ],
                 vec![ #( #dyn_attr_printer ),* ],
                 vec![ #( #dyn_attr_printer ),* ],

+ 2 - 2
packages/ssr/src/renderer.rs

@@ -20,7 +20,7 @@ pub struct Renderer {
     render_components: Option<ComponentRenderCallback>,
     render_components: Option<ComponentRenderCallback>,
 
 
     /// A cache of templates that have been rendered
     /// A cache of templates that have been rendered
-    template_cache: FxHashMap<usize, Arc<StringCache>>,
+    template_cache: FxHashMap<Template, Arc<StringCache>>,
 
 
     /// The current dynamic node id for hydration
     /// The current dynamic node id for hydration
     dynamic_node_id: usize,
     dynamic_node_id: usize,
@@ -108,7 +108,7 @@ impl Renderer {
     ) -> std::fmt::Result {
     ) -> std::fmt::Result {
         let entry = self
         let entry = self
             .template_cache
             .template_cache
-            .entry(template.template.id())
+            .entry(template.template)
             .or_insert_with(move || Arc::new(StringCache::from_template(template).unwrap()))
             .or_insert_with(move || Arc::new(StringCache::from_template(template).unwrap()))
             .clone();
             .clone();
 
 

+ 5 - 7
packages/web/src/dom.rs

@@ -6,7 +6,7 @@
 //! - tests to ensure dyn_into works for various event types.
 //! - tests to ensure dyn_into works for various event types.
 //! - Partial delegation?
 //! - Partial delegation?
 
 
-use dioxus_core::ElementId;
+use dioxus_core::{ElementId, Template};
 use dioxus_html::PlatformEventData;
 use dioxus_html::PlatformEventData;
 use dioxus_interpreter_js::unified_bindings::Interpreter;
 use dioxus_interpreter_js::unified_bindings::Interpreter;
 use futures_channel::mpsc;
 use futures_channel::mpsc;
@@ -20,8 +20,7 @@ pub struct WebsysDom {
     #[allow(dead_code)]
     #[allow(dead_code)]
     pub(crate) root: Element,
     pub(crate) root: Element,
     pub(crate) document: Document,
     pub(crate) document: Document,
-    pub(crate) templates: FxHashMap<String, u16>,
-    pub(crate) max_template_id: u16,
+    pub(crate) templates: FxHashMap<Template, u16>,
     pub(crate) interpreter: Interpreter,
     pub(crate) interpreter: Interpreter,
 
 
     #[cfg(feature = "mounted")]
     #[cfg(feature = "mounted")]
@@ -41,10 +40,10 @@ pub struct WebsysDom {
     // Instead we now store a flag to see if we should be writing templates at all if hydration is enabled.
     // Instead we now store a flag to see if we should be writing templates at all if hydration is enabled.
     // This has a small overhead, but it avoids dynamic dispatch and reduces the binary size
     // This has a small overhead, but it avoids dynamic dispatch and reduces the binary size
     //
     //
-    // NOTE: running the virtual dom with the `only_write_templates` flag set to true is different from running
+    // NOTE: running the virtual dom with the `write_mutations` flag set to true is different from running
     // it with no mutation writer because it still assigns ids to nodes, but it doesn't write them to the dom
     // it with no mutation writer because it still assigns ids to nodes, but it doesn't write them to the dom
     #[cfg(feature = "hydrate")]
     #[cfg(feature = "hydrate")]
-    pub(crate) only_write_templates: bool,
+    pub(crate) skip_mutations: bool,
 
 
     #[cfg(feature = "hydrate")]
     #[cfg(feature = "hydrate")]
     pub(crate) suspense_hydration_ids: crate::hydration::SuspenseHydrationIds,
     pub(crate) suspense_hydration_ids: crate::hydration::SuspenseHydrationIds,
@@ -145,13 +144,12 @@ impl WebsysDom {
             root,
             root,
             interpreter,
             interpreter,
             templates: FxHashMap::default(),
             templates: FxHashMap::default(),
-            max_template_id: 0,
             #[cfg(feature = "mounted")]
             #[cfg(feature = "mounted")]
             event_channel,
             event_channel,
             #[cfg(feature = "mounted")]
             #[cfg(feature = "mounted")]
             queued_mounted_events: Default::default(),
             queued_mounted_events: Default::default(),
             #[cfg(feature = "hydrate")]
             #[cfg(feature = "hydrate")]
-            only_write_templates: false,
+            skip_mutations: false,
             #[cfg(feature = "hydrate")]
             #[cfg(feature = "hydrate")]
             suspense_hydration_ids: Default::default(),
             suspense_hydration_ids: Default::default(),
         }
         }

+ 2 - 2
packages/web/src/hydration/hydrate.rs

@@ -151,11 +151,11 @@ impl WebsysDom {
                 self,
                 self,
                 |to| {
                 |to| {
                     // Switch to only writing templates
                     // Switch to only writing templates
-                    to.only_write_templates = true;
+                    to.skip_mutations = true;
                 },
                 },
                 children.len(),
                 children.len(),
             );
             );
-            self.only_write_templates = false;
+            self.skip_mutations = false;
         });
         });
 
 
         // Flush the mutations that will swap the placeholder nodes with the resolved nodes
         // Flush the mutations that will swap the placeholder nodes with the resolved nodes

+ 4 - 3
packages/web/src/lib.rs

@@ -25,6 +25,7 @@ use std::{panic, rc::Rc};
 pub use crate::cfg::Config;
 pub use crate::cfg::Config;
 use crate::hydration::SuspenseMessage;
 use crate::hydration::SuspenseMessage;
 use dioxus_core::VirtualDom;
 use dioxus_core::VirtualDom;
+use dom::WebsysDom;
 use futures_util::{pin_mut, select, FutureExt, StreamExt};
 use futures_util::{pin_mut, select, FutureExt, StreamExt};
 
 
 mod cfg;
 mod cfg;
@@ -77,7 +78,7 @@ pub async fn run(virtual_dom: VirtualDom, web_config: Config) -> ! {
 
 
     let should_hydrate = web_config.hydrate;
     let should_hydrate = web_config.hydrate;
 
 
-    let mut websys_dom = dom::WebsysDom::new(web_config, tx);
+    let mut websys_dom = WebsysDom::new(web_config, tx);
 
 
     let mut hydration_receiver: Option<futures_channel::mpsc::UnboundedReceiver<SuspenseMessage>> =
     let mut hydration_receiver: Option<futures_channel::mpsc::UnboundedReceiver<SuspenseMessage>> =
         None;
         None;
@@ -85,7 +86,7 @@ pub async fn run(virtual_dom: VirtualDom, web_config: Config) -> ! {
     if should_hydrate {
     if should_hydrate {
         #[cfg(feature = "hydrate")]
         #[cfg(feature = "hydrate")]
         {
         {
-            websys_dom.only_write_templates = true;
+            websys_dom.skip_mutations = true;
             // Get the initial hydration data from the client
             // Get the initial hydration data from the client
             #[wasm_bindgen::prelude::wasm_bindgen(inline_js = r#"
             #[wasm_bindgen::prelude::wasm_bindgen(inline_js = r#"
                 export function get_initial_hydration_data() {
                 export function get_initial_hydration_data() {
@@ -105,7 +106,7 @@ pub async fn run(virtual_dom: VirtualDom, web_config: Config) -> ! {
             with_server_data(server_data, || {
             with_server_data(server_data, || {
                 dom.rebuild(&mut websys_dom);
                 dom.rebuild(&mut websys_dom);
             });
             });
-            websys_dom.only_write_templates = false;
+            websys_dom.skip_mutations = false;
 
 
             let rx = websys_dom.rehydrate(&dom).unwrap();
             let rx = websys_dom.rehydrate(&dom).unwrap();
             hydration_receiver = Some(rx);
             hydration_receiver = Some(rx);

+ 30 - 42
packages/web/src/mutations.rs

@@ -79,10 +79,10 @@ impl WebsysDom {
     }
     }
 
 
     #[inline]
     #[inline]
-    fn only_write_templates(&self) -> bool {
+    fn skip_mutations(&self) -> bool {
         #[cfg(feature = "hydrate")]
         #[cfg(feature = "hydrate")]
         {
         {
-            self.only_write_templates
+            self.skip_mutations
         }
         }
         #[cfg(not(feature = "hydrate"))]
         #[cfg(not(feature = "hydrate"))]
         {
         {
@@ -92,28 +92,15 @@ impl WebsysDom {
 }
 }
 
 
 impl WriteMutations for WebsysDom {
 impl WriteMutations for WebsysDom {
-    fn register_template(&mut self, template: Template) {
-        let mut roots = vec![];
-        for root in template.roots {
-            roots.push(self.create_template_node(root))
-        }
-        self.templates
-            .insert(template.name.to_owned(), self.max_template_id);
-        self.interpreter
-            .base()
-            .save_template(roots, self.max_template_id);
-        self.max_template_id += 1
-    }
-
     fn append_children(&mut self, id: ElementId, m: usize) {
     fn append_children(&mut self, id: ElementId, m: usize) {
-        if self.only_write_templates() {
+        if self.skip_mutations() {
             return;
             return;
         }
         }
         self.interpreter.append_children(id.0 as u32, m as u16)
         self.interpreter.append_children(id.0 as u32, m as u16)
     }
     }
 
 
     fn assign_node_id(&mut self, path: &'static [u8], id: ElementId) {
     fn assign_node_id(&mut self, path: &'static [u8], id: ElementId) {
-        if self.only_write_templates() {
+        if self.skip_mutations() {
             return;
             return;
         }
         }
         self.interpreter
         self.interpreter
@@ -121,46 +108,47 @@ impl WriteMutations for WebsysDom {
     }
     }
 
 
     fn create_placeholder(&mut self, id: ElementId) {
     fn create_placeholder(&mut self, id: ElementId) {
-        if self.only_write_templates() {
+        if self.skip_mutations() {
             return;
             return;
         }
         }
         self.interpreter.create_placeholder(id.0 as u32)
         self.interpreter.create_placeholder(id.0 as u32)
     }
     }
 
 
     fn create_text_node(&mut self, value: &str, id: ElementId) {
     fn create_text_node(&mut self, value: &str, id: ElementId) {
-        if self.only_write_templates() {
+        if self.skip_mutations() {
             return;
             return;
         }
         }
         self.interpreter.create_text_node(value, id.0 as u32)
         self.interpreter.create_text_node(value, id.0 as u32)
     }
     }
 
 
-    fn hydrate_text_node(&mut self, path: &'static [u8], value: &str, id: ElementId) {
-        if self.only_write_templates() {
+    fn load_template(&mut self, template: Template, index: usize, id: ElementId) {
+        if self.skip_mutations() {
             return;
             return;
         }
         }
-        self.interpreter
-            .hydrate_text(path.as_ptr() as u32, path.len() as u8, value, id.0 as u32)
-    }
+        let tmpl_id = self.templates.get(&template).cloned().unwrap_or_else(|| {
+            let mut roots = vec![];
+            for root in template.roots {
+                roots.push(self.create_template_node(root))
+            }
+            let id = self.templates.len() as u16;
+            self.templates.insert(template, id);
+            self.interpreter.base().save_template(roots, id);
+            id
+        });
 
 
-    fn load_template(&mut self, name: &'static str, index: usize, id: ElementId) {
-        if self.only_write_templates() {
-            return;
-        }
-        if let Some(tmpl_id) = self.templates.get(name) {
-            self.interpreter
-                .load_template(*tmpl_id, index as u16, id.0 as u32)
-        }
+        self.interpreter
+            .load_template(tmpl_id, index as u16, id.0 as u32)
     }
     }
 
 
     fn replace_node_with(&mut self, id: ElementId, m: usize) {
     fn replace_node_with(&mut self, id: ElementId, m: usize) {
-        if self.only_write_templates() {
+        if self.skip_mutations() {
             return;
             return;
         }
         }
         self.interpreter.replace_with(id.0 as u32, m as u16)
         self.interpreter.replace_with(id.0 as u32, m as u16)
     }
     }
 
 
     fn replace_placeholder_with_nodes(&mut self, path: &'static [u8], m: usize) {
     fn replace_placeholder_with_nodes(&mut self, path: &'static [u8], m: usize) {
-        if self.only_write_templates() {
+        if self.skip_mutations() {
             return;
             return;
         }
         }
         self.interpreter
         self.interpreter
@@ -168,14 +156,14 @@ impl WriteMutations for WebsysDom {
     }
     }
 
 
     fn insert_nodes_after(&mut self, id: ElementId, m: usize) {
     fn insert_nodes_after(&mut self, id: ElementId, m: usize) {
-        if self.only_write_templates() {
+        if self.skip_mutations() {
             return;
             return;
         }
         }
         self.interpreter.insert_after(id.0 as u32, m as u16)
         self.interpreter.insert_after(id.0 as u32, m as u16)
     }
     }
 
 
     fn insert_nodes_before(&mut self, id: ElementId, m: usize) {
     fn insert_nodes_before(&mut self, id: ElementId, m: usize) {
-        if self.only_write_templates() {
+        if self.skip_mutations() {
             return;
             return;
         }
         }
         self.interpreter.insert_before(id.0 as u32, m as u16)
         self.interpreter.insert_before(id.0 as u32, m as u16)
@@ -188,7 +176,7 @@ impl WriteMutations for WebsysDom {
         value: &AttributeValue,
         value: &AttributeValue,
         id: ElementId,
         id: ElementId,
     ) {
     ) {
-        if self.only_write_templates() {
+        if self.skip_mutations() {
             return;
             return;
         }
         }
         match value {
         match value {
@@ -223,14 +211,14 @@ impl WriteMutations for WebsysDom {
     }
     }
 
 
     fn set_node_text(&mut self, value: &str, id: ElementId) {
     fn set_node_text(&mut self, value: &str, id: ElementId) {
-        if self.only_write_templates() {
+        if self.skip_mutations() {
             return;
             return;
         }
         }
         self.interpreter.set_text(id.0 as u32, value)
         self.interpreter.set_text(id.0 as u32, value)
     }
     }
 
 
     fn create_event_listener(&mut self, name: &'static str, id: ElementId) {
     fn create_event_listener(&mut self, name: &'static str, id: ElementId) {
-        if self.only_write_templates() {
+        if self.skip_mutations() {
             return;
             return;
         }
         }
         // mounted events are fired immediately after the element is mounted.
         // mounted events are fired immediately after the element is mounted.
@@ -245,7 +233,7 @@ impl WriteMutations for WebsysDom {
     }
     }
 
 
     fn remove_event_listener(&mut self, name: &'static str, id: ElementId) {
     fn remove_event_listener(&mut self, name: &'static str, id: ElementId) {
-        if self.only_write_templates() {
+        if self.skip_mutations() {
             return;
             return;
         }
         }
         if name == "mounted" {
         if name == "mounted" {
@@ -257,14 +245,14 @@ impl WriteMutations for WebsysDom {
     }
     }
 
 
     fn remove_node(&mut self, id: ElementId) {
     fn remove_node(&mut self, id: ElementId) {
-        if self.only_write_templates() {
+        if self.skip_mutations() {
             return;
             return;
         }
         }
         self.interpreter.remove(id.0 as u32)
         self.interpreter.remove(id.0 as u32)
     }
     }
 
 
     fn push_root(&mut self, id: ElementId) {
     fn push_root(&mut self, id: ElementId) {
-        if self.only_write_templates() {
+        if self.skip_mutations() {
             return;
             return;
         }
         }
         self.interpreter.push_root(id.0 as u32)
         self.interpreter.push_root(id.0 as u32)