Sfoglia il codice sorgente

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 mesi fa
parent
commit
0de3bf7aeb
40 ha cambiato i file con 596 aggiunte e 1037 eliminazioni
  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(),
         };
         manganis_cli_support::Config::default()
-            .with_assets_serve_location(&base)
+            .with_assets_serve_location(base)
             .save();
         Self {}
     }

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

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

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

@@ -10,11 +10,10 @@
 #![allow(clippy::too_many_arguments)]
 
 use crate::{
-    arena::ElementId,
-    innerlude::{ElementRef, MountId, WriteMutations},
+    innerlude::{ElementRef, WriteMutations},
     nodes::VNode,
     virtual_dom::VirtualDom,
-    Template, TemplateNode,
+    TemplateNode,
 };
 
 mod component;
@@ -34,62 +33,6 @@ impl VirtualDom {
             .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
     /// Wont generate mutations for the inner nodes
     fn remove_nodes(
@@ -103,26 +46,6 @@ impl VirtualDom {
             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.

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

@@ -5,7 +5,7 @@ use core::iter::Peekable;
 
 use crate::{
     arena::ElementId,
-    innerlude::{ElementPath, ElementRef, VComponent, VNodeMount, VText},
+    innerlude::{ElementPath, ElementRef, VNodeMount, VText},
     nodes::DynamicNode,
     scopes::ScopeId,
     TemplateNode,
@@ -21,9 +21,12 @@ impl VNode {
         // The node we are diffing from should always be mounted
         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();
@@ -73,73 +76,60 @@ impl VNode {
         old_node: &DynamicNode,
         new_node: &DynamicNode,
         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:?}");
         match (old_node, new_node) {
             (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
-                if let Some(to) = to{
+                if let Some(to) = to {
                     let mount = &dom.mounts[mount.0];
                     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)) => {
-				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
@@ -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) {
         let mut next_id = None;
         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.
     pub(crate) fn create(
         &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
         // 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();
@@ -671,7 +571,13 @@ impl VNode {
                         // Take a dynamic node off the depth first iterator
                         nodes.next().unwrap();
                         // 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
                     TemplateNode::Text { .. } | TemplateNode::Element { .. } => {
@@ -720,13 +626,13 @@ impl VNode {
 
     pub(crate) fn create_dynamic_node(
         &self,
+        node: &DynamicNode,
         mount: MountId,
         dynamic_node_id: usize,
         dom: &mut VirtualDom,
         to: Option<&mut impl WriteMutations>,
     ) -> usize {
         use DynamicNode::*;
-        let node = &self.dynamic_nodes[dynamic_node_id];
         match node {
             Component(component) => {
                 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
         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) {
-            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 we actually created real new nodes, we need to replace the placeholder for this dynamic node with the new dynamic nodes
                 if m > 0 {
@@ -856,7 +768,7 @@ impl VNode {
         let this_id = dom.next_element();
         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
     }
@@ -888,22 +800,6 @@ impl VNode {
         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(
         &self,
         mount: MountId,
@@ -912,19 +808,12 @@ impl VNode {
         dom: &mut VirtualDom,
         to: &mut impl WriteMutations,
     ) -> 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 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(
@@ -934,19 +823,12 @@ impl VNode {
         dom: &mut VirtualDom,
         to: &mut impl WriteMutations,
     ) -> 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 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
     }
 }
-
-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 {
     static TEMPLATE: Template = Template {
-        name: "error_handle.rs:42:5:884",
         roots: &[TemplateNode::Element {
             tag: "div",
             namespace: None,
@@ -549,7 +548,6 @@ fn default_handler(errors: ErrorContext) -> Element {
             .iter()
             .map(|e| {
                 static TEMPLATE: Template = Template {
-                    name: "error_handle.rs:43:5:884",
                     roots: &[TemplateNode::Element {
                         tag: "pre",
                         namespace: None,
@@ -753,7 +751,6 @@ pub fn ErrorBoundary(props: ErrorBoundaryProps) -> Element {
     if errors.is_empty() {
         std::result::Result::Ok({
             static TEMPLATE: Template = Template {
-                name: "examples/error_handle.rs:81:17:2342",
                 roots: &[TemplateNode::Dynamic { id: 0usize }],
                 node_paths: &[&[0u8]],
                 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 {
         // 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
             .key
             .as_ref()
@@ -259,7 +250,7 @@ impl DynamicValuePool {
             .map(|attr| self.render_attribute(attr))
             .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 {
@@ -318,8 +309,8 @@ pub struct HotReloadTemplateWithLocation {
 #[doc(hidden)]
 #[derive(Debug, PartialEq, Clone)]
 #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
+#[cfg_attr(feature = "serialize", serde(bound(deserialize = "'de: 'static")))]
 pub struct HotReloadedTemplate {
-    pub name: &'static str,
     pub key: Option<FmtedSegments>,
     pub dynamic_nodes: Vec<HotReloadDynamicNode>,
     pub dynamic_attributes: Vec<HotReloadDynamicAttribute>,
@@ -329,28 +320,37 @@ pub struct HotReloadedTemplate {
         serde(deserialize_with = "crate::nodes::deserialize_leaky")
     )]
     pub roots: &'static [TemplateNode],
+    /// The template that is computed from the hot reload roots
+    template: Template,
 }
 
 impl HotReloadedTemplate {
     pub fn new(
-        name: &'static str,
         key: Option<FmtedSegments>,
         dynamic_nodes: Vec<HotReloadDynamicNode>,
         dynamic_attributes: Vec<HotReloadDynamicAttribute>,
         component_values: Vec<HotReloadLiteral>,
         roots: &'static [TemplateNode],
     ) -> Self {
+        let node_paths = Self::node_paths(roots);
+        let attr_paths = Self::attr_paths(roots);
+
+        let template = Template {
+            roots,
+            node_paths,
+            attr_paths,
+        };
         Self {
-            name,
             key,
             dynamic_nodes,
             dynamic_attributes,
             component_values,
             roots,
+            template,
         }
     }
 
-    fn node_paths(&self) -> &'static [&'static [u8]] {
+    fn node_paths(roots: &'static [TemplateNode]) -> &'static [&'static [u8]] {
         fn add_node_paths(
             roots: &[TemplateNode],
             node_paths: &mut Vec<&'static [u8]>,
@@ -373,12 +373,12 @@ impl HotReloadedTemplate {
         }
 
         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());
         leaked
     }
 
-    fn attr_paths(&self) -> &'static [&'static [u8]] {
+    fn attr_paths(roots: &'static [TemplateNode]) -> &'static [&'static [u8]] {
         fn add_attr_paths(
             roots: &[TemplateNode],
             attr_paths: &mut Vec<&'static [u8]>,
@@ -403,7 +403,7 @@ impl HotReloadedTemplate {
         }
 
         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());
         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
 ///
@@ -14,9 +12,6 @@ use crate::{arena::ElementId, AttributeValue, ScopeId, Template};
 ///
 /// Mutations are the only link between the RealDOM and the VirtualDOM.
 pub trait WriteMutations {
-    /// Register a template with the renderer
-    fn register_template(&mut self, template: Template);
-
     /// Add these m children to the target element
     ///
     /// 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.
     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
     ///
     /// 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
     /// 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)
-    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
     ///
@@ -129,12 +115,6 @@ pub trait WriteMutations {
     ///
     /// Id: The ID of the root node to push.
     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
@@ -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
     /// element, hence the use of a single byte.
-    ///
-    ///
     AssignId {
         /// The path of the child of the topmost node on the stack
         ///
@@ -192,33 +170,11 @@ pub enum Mutation {
         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.
     /// When the template is picked up in the template list, it should be saved under its "name" - here, the name
     LoadTemplate {
-        /// The "name" of the template. When paired with `rsx!`, this is autogenerated
-        name: &'static str,
-
         /// 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
@@ -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
 #[derive(Debug, PartialEq, Default)]
 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
     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 {
-    fn register_template(&mut self, template: Template) {
-        self.templates.push(template)
-    }
-
     fn append_children(&mut self, id: ElementId, m: usize) {
         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) {
@@ -457,20 +378,12 @@ impl WriteMutations for Mutations {
     fn push_root(&mut self, id: ElementId) {
         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
 pub struct NoOpMutations;
 
 impl WriteMutations for NoOpMutations {
-    fn register_template(&mut self, _: Template) {}
-
     fn append_children(&mut self, _: ElementId, _: usize) {}
 
     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 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) {}
 

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

@@ -15,8 +15,6 @@ use std::{
     fmt::{Arguments, Debug},
 };
 
-pub type TemplateId = &'static str;
-
 /// The actual state of the component's most recent computation
 ///
 /// 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_attrs: Box::new([]),
                     template: Template {
-                        name: "packages/core/nodes.rs:198:0:0",
                         roots: &[TemplateNode::Dynamic { id: 0 }],
                         node_paths: &[&[0]],
                         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
 /// ways, with the suggested approach being the unique code location (file, line, col, etc).
 #[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 {
-    /// 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
     ///
     /// 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]],
 }
 
+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")]
 pub(crate) fn deserialize_string_leaky<'a, 'de, D>(deserializer: D) -> Result<&'a str, D::Error>
 where
@@ -442,13 +447,6 @@ impl Template {
         use TemplateNode::*;
         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.

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

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

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

@@ -11,14 +11,12 @@ use crate::{
         ElementRef, NoOpMutations, SchedulerMsg, ScopeOrder, ScopeState, VNodeMount, VProps,
         WriteMutations,
     },
-    nodes::{Template, TemplateId},
     runtime::{Runtime, RuntimeGuard},
     scopes::ScopeId,
     AttributeValue, ComponentFunction, Element, Event, Mutations,
 };
 use crate::{Task, VComponent};
 use futures_util::StreamExt;
-use rustc_hash::FxHashSet;
 use slab::Slab;
 use std::collections::BTreeSet;
 use std::{any::Any, rc::Rc};
@@ -208,12 +206,6 @@ pub struct VirtualDom {
 
     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
     // These mark a specific place in a whole rsx block
     pub(crate) elements: Slab<Option<ElementRef>>,
@@ -336,8 +328,6 @@ impl VirtualDom {
             runtime: Runtime::new(tx),
             scopes: Default::default(),
             dirty_scopes: Default::default(),
-            templates: Default::default(),
-            queued_templates: Default::default(),
             elements: Default::default(),
             mounts: Default::default(),
             resolved_scopes: Default::default(),
@@ -612,7 +602,6 @@ impl VirtualDom {
     /// ```
     #[instrument(skip(self, to), level = "trace", name = "VirtualDom::rebuild")]
     pub fn rebuild(&mut self, to: &mut impl WriteMutations) {
-        self.flush_templates(to);
         let _runtime = RuntimeGuard::new(self.runtime.clone());
         let new_nodes = self.run_scope(ScopeId::ROOT);
 
@@ -628,8 +617,6 @@ impl VirtualDom {
     /// suspended subtrees.
     #[instrument(skip(self, to), level = "trace", name = "VirtualDom::render_immediate")]
     pub fn render_immediate(&mut self, to: &mut impl WriteMutations) {
-        self.flush_templates(to);
-
         // Process any events that might be pending in the queue
         // 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
@@ -785,14 +772,6 @@ impl VirtualDom {
         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

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

@@ -21,18 +21,18 @@ fn attrs_cycle() {
     });
 
     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) },
         ]
     );
 
     dom.mark_dirty(ScopeId::APP);
     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,) },
             SetAttribute { name: "class", 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);
     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 }
         ]
     );
 
     dom.mark_dirty(ScopeId::APP);
     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) },
             SetAttribute {
                 name: "class",
@@ -74,9 +74,9 @@ fn attrs_cycle() {
     // we take the node taken by attributes since we reused it
     dom.mark_dirty(ScopeId::APP);
     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 }
         ]
     );

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

@@ -6,9 +6,9 @@ fn bool_test() {
     let mut app = VirtualDom::new(|| rsx!(div { hidden: false }));
 
     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 {
                 name: "hidden",
                 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 _edits = dom.rebuild_to_vec().sanitize();
+        let _edits = dom.rebuild_to_vec();
     }
 
     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 {
         if generation() == 0 {
-            rsx! {Child {}}
+            rsx! { Child {} }
         } else {
             rsx! {}
         }

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

@@ -20,7 +20,7 @@ fn state_shares() {
 
     let mut dom = VirtualDom::new(app);
     assert_eq!(
-        dom.rebuild_to_vec().sanitize().edits,
+        dom.rebuild_to_vec().edits,
         [
             CreateTextNode { value: "Value is 0".to_string(), id: ElementId(1,) },
             AppendChildren { m: 1, id: ElementId(0) },
@@ -41,7 +41,7 @@ fn state_shares() {
 
     dom.mark_dirty(ScopeId(ScopeId::APP.0 + 2));
     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,) },]
     );
 
@@ -49,7 +49,7 @@ fn state_shares() {
     dom.mark_dirty(ScopeId(ScopeId::APP.0 + 2));
     let edits = dom.render_immediate_to_vec();
     assert_eq!(
-        edits.sanitize().edits,
+        edits.edits,
         [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!(
         edits.edits,
         [
             // add to root
-            LoadTemplate { name: "template", index: 0, id: ElementId(1) },
+            LoadTemplate { index: 0, id: ElementId(1) },
             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
 
@@ -64,11 +64,11 @@ fn create() {
     //         AppendChildren { m: 1 },
     //         AppendChildren { m: 2 },
     //         AppendChildren { m: 1 },
-    //         SaveTemplate { name: "template", m: 1 },
+    //         SaveTemplate {  m: 1 },
     //         // The fragment child template
     //         CreateStaticText { value: "hello" },
     //         CreateStaticText { value: "world" },
-    //         SaveTemplate { name: "template", m: 2 },
+    //         SaveTemplate {  m: 2 },
     //     ]
     // );
 }
@@ -77,7 +77,7 @@ fn create() {
 fn create_list() {
     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
     // assert_eq!(
@@ -87,7 +87,7 @@ fn create_list() {
     //         CreateElement { name: "div" },
     //         CreateStaticText { value: "hello" },
     //         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
     // assert_eq!(
@@ -115,7 +115,7 @@ fn create_simple() {
     //         CreateElement { name: "div" },
     //         CreateElement { name: "div" },
     //         // 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
 }
@@ -161,7 +161,7 @@ fn anchors() {
     });
 
     // 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
     // assert_eq!(
@@ -178,7 +178,7 @@ fn anchors() {
     assert_eq!(
         edits.edits,
         [
-            LoadTemplate { name: "template", index: 0, id: ElementId(1) },
+            LoadTemplate { index: 0, id: ElementId(1) },
             CreatePlaceholder { id: ElementId(2) },
             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::prelude::*;
 use dioxus_core::ElementId;
+use pretty_assertions::assert_eq;
 
 // A real-world usecase of templates at peak performance
 // In react, this would be a lot of node creation.
@@ -25,7 +26,7 @@ fn app() -> Element {
 fn list_renders() {
     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
     // assert_eq!(
@@ -37,7 +38,7 @@ fn list_renders() {
     //         // append when modify the values (IE no need for a placeholder)
     //         CreateStaticPlaceholder,
     //         AppendChildren { m: 1 },
-    //         SaveTemplate { name: "template", m: 1 },
+    //         SaveTemplate {  m: 1 },
     //         // Create the inner template div
     //         CreateElement { name: "div" },
     //         CreateElement { name: "h1" },
@@ -47,7 +48,7 @@ fn list_renders() {
     //         CreateTextPlaceholder,
     //         AppendChildren { m: 1 },
     //         AppendChildren { m: 2 },
-    //         SaveTemplate { name: "template", m: 1 }
+    //         SaveTemplate {  m: 1 }
     //     ],
     // );
 
@@ -55,14 +56,17 @@ fn list_renders() {
         edits.edits,
         [
             // 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
-            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
             ReplacePlaceholder { m: 3, path: &[0] },
             // 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 edits = dom.rebuild_to_vec().sanitize();
+    let edits = dom.rebuild_to_vec();
 
     assert_eq!(
         edits.edits,
         [
-            LoadTemplate { name: "template", index: 0, id: ElementId(1) },
+            LoadTemplate { index: 0, id: ElementId(1) },
             AppendChildren { m: 1, id: ElementId(0) },
         ]
     )
@@ -60,16 +60,16 @@ fn nested_passthru_creates_add() {
     let mut dom = VirtualDom::new(app);
 
     assert_eq!(
-        dom.rebuild_to_vec().sanitize().edits,
+        dom.rebuild_to_vec().edits,
         [
             // load 1
-            LoadTemplate { name: "template", index: 0, id: ElementId(1) },
+            LoadTemplate { index: 0, id: ElementId(1) },
             // load 2
-            LoadTemplate { name: "template", index: 0, id: ElementId(2) },
+            LoadTemplate { index: 0, id: ElementId(2) },
             // load 3
-            LoadTemplate { name: "template", index: 0, id: ElementId(3) },
+            LoadTemplate { index: 0, id: ElementId(3) },
             // 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 },
         ]
     );
@@ -85,11 +85,9 @@ fn dynamic_node_as_root() {
     }
 
     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
-    assert!(edits.templates.is_empty());
-
     // The root node is text, so we just create it on the spot
     assert_eq!(
         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!(
             edits.edits,
             [
-                LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
+                LoadTemplate { index: 0, id: ElementId(1,) },
                 AppendChildren { m: 1, id: ElementId(0) },
             ]
         );
@@ -23,9 +23,9 @@ fn cycling_elements() {
 
     dom.mark_dirty(ScopeId::APP);
     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 },
         ]
     );
@@ -33,18 +33,18 @@ fn cycling_elements() {
     // notice that the IDs cycle back to ElementId(1), preserving a minimal memory footprint
     dom.mark_dirty(ScopeId::APP);
     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 },
         ]
     );
 
     dom.mark_dirty(ScopeId::APP);
     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 },
         ]
     );

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

@@ -1,5 +1,6 @@
 use dioxus::dioxus_core::{ElementId, Mutation::*};
 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
 ///
@@ -7,6 +8,15 @@ use dioxus::prelude::*;
 /// different pointers
 #[test]
 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 {
         let mut render_phase = use_signal(|| 0);
 
@@ -62,16 +72,16 @@ fn component_swap() {
 
     let mut dom = VirtualDom::new(app);
     {
-        let edits = dom.rebuild_to_vec().sanitize();
+        let edits = dom.rebuild_to_vec();
         assert_eq!(
             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 },
-                LoadTemplate { name: "template", index: 0, id: ElementId(5) },
+                LoadTemplate { index: 0, id: ElementId(5) },
                 AppendChildren { m: 2, id: ElementId(0) }
             ]
         );
@@ -79,27 +89,27 @@ fn component_swap() {
 
     dom.mark_dirty(ScopeId::APP);
     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 }
         ]
     );
 
     dom.mark_dirty(ScopeId::APP);
     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 }
         ]
     );
 
     dom.mark_dirty(ScopeId::APP);
     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 }
         ]
     );

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

@@ -7,6 +7,7 @@ fn toggle_option_text() {
     let mut dom = VirtualDom::new(|| {
         let gen = generation();
         let text = if gen % 2 != 0 { Some("hello") } else { None };
+        println!("{:?}", text);
 
         rsx! {
             div {
@@ -17,10 +18,11 @@ fn toggle_option_text() {
 
     // load the div and then assign the None as a placeholder
     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 },
         ]
     );
@@ -28,7 +30,7 @@ fn toggle_option_text() {
     // Rendering again should replace the placeholder with an text node
     dom.mark_dirty(ScopeId::APP);
     assert_eq!(
-        dom.render_immediate_to_vec().sanitize().edits,
+        dom.render_immediate_to_vec().edits,
         [
             CreateTextNode { value: "hello".to_string(), id: ElementId(3,) },
             ReplaceWith { id: ElementId(2,), m: 1 },
@@ -38,7 +40,7 @@ fn toggle_option_text() {
     // Rendering again should replace the placeholder with an text node
     dom.mark_dirty(ScopeId::APP);
     assert_eq!(
-        dom.render_immediate_to_vec().sanitize().edits,
+        dom.render_immediate_to_vec().edits,
         [
             CreatePlaceholder { id: ElementId(2,) },
             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);
     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 },
         ]
     );
 
     vdom.mark_dirty(ScopeId::APP);
     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 },
         ]
     );
 
     vdom.mark_dirty(ScopeId::APP);
     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 },
         ]
     );
 
     vdom.mark_dirty(ScopeId::APP);
     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 },
         ]
     );
@@ -128,7 +128,7 @@ fn attribute_diff() {
 
     vdom.mark_dirty(ScopeId::APP);
     assert_eq!(
-        vdom.render_immediate_to_vec().sanitize().edits,
+        vdom.render_immediate_to_vec().edits,
         [
             SetAttribute {
                 name: "b",
@@ -147,7 +147,7 @@ fn attribute_diff() {
 
     vdom.mark_dirty(ScopeId::APP);
     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: "b", value: AttributeValue::None, id: ElementId(1,), ns: None },
@@ -168,7 +168,7 @@ fn attribute_diff() {
 
     vdom.mark_dirty(ScopeId::APP);
     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 {
@@ -196,7 +196,7 @@ fn diff_empty() {
     vdom.rebuild(&mut NoOpMutations);
 
     vdom.mark_dirty(ScopeId::APP);
-    let edits = vdom.render_immediate_to_vec().sanitize().edits;
+    let edits = vdom.render_immediate_to_vec().edits;
 
     assert_eq!(
         edits,

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

@@ -22,18 +22,18 @@ fn keyed_diffing_out_of_order() {
 
     {
         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) },
             ]
         );
@@ -169,10 +169,10 @@ fn keyed_diffing_additions() {
 
     dom.mark_dirty(ScopeId::APP);
     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 }
         ]
     );
@@ -194,11 +194,11 @@ fn keyed_diffing_additions_and_moves_on_ends() {
 
     dom.mark_dirty(ScopeId::APP);
     assert_eq!(
-        dom.render_immediate_to_vec().sanitize().edits,
+        dom.render_immediate_to_vec().edits,
         [
             // 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 },
             // move 7 to the front
             PushRoot { id: ElementId(4) },
@@ -224,15 +224,15 @@ fn keyed_diffing_additions_and_moves_in_middle() {
     // LIS: 4, 5, 6
     dom.mark_dirty(ScopeId::APP);
     assert_eq!(
-        dom.render_immediate_to_vec().sanitize().edits,
+        dom.render_immediate_to_vec().edits,
         [
             // 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 },
             // 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 },
             // move 7
             PushRoot { id: ElementId(4) },
@@ -258,7 +258,7 @@ fn controlled_keyed_diffing_out_of_order() {
     // LIS: 5, 6
     dom.mark_dirty(ScopeId::APP);
     assert_eq!(
-        dom.render_immediate_to_vec().sanitize().edits,
+        dom.render_immediate_to_vec().edits,
         [
             // remove 7
             Remove { id: ElementId(4,) },
@@ -266,10 +266,10 @@ fn controlled_keyed_diffing_out_of_order() {
             PushRoot { id: ElementId(1) },
             InsertAfter { id: ElementId(3,), m: 1 },
             // 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 },
             // 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 },
         ]
     );
@@ -291,10 +291,10 @@ fn controlled_keyed_diffing_out_of_order_max_test() {
 
     dom.mark_dirty(ScopeId::APP);
     assert_eq!(
-        dom.render_immediate_to_vec().sanitize().edits,
+        dom.render_immediate_to_vec().edits,
         [
             Remove { id: ElementId(5,) },
-            LoadTemplate { name: "template", index: 0, id: ElementId(5) },
+            LoadTemplate { index: 0, id: ElementId(5) },
             InsertBefore { id: ElementId(3,), m: 1 },
             PushRoot { id: ElementId(4) },
             InsertBefore { id: ElementId(1,), m: 1 },
@@ -320,7 +320,7 @@ fn remove_list() {
 
     dom.mark_dirty(ScopeId::APP);
     assert_eq!(
-        dom.render_immediate_to_vec().sanitize().edits,
+        dom.render_immediate_to_vec().edits,
         [
             Remove { id: ElementId(5) },
             Remove { id: ElementId(4) },
@@ -345,11 +345,11 @@ fn no_common_keys() {
 
     dom.mark_dirty(ScopeId::APP);
     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(2) },
             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::prelude::*;
+use dioxus_core::Mutation;
 use pretty_assertions::assert_eq;
 
 #[test]
@@ -18,10 +21,11 @@ fn list_creates_one_by_one() {
 
     // load the div and then assign the empty fragment as a placeholder
     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 },
         ]
     );
@@ -29,10 +33,11 @@ fn list_creates_one_by_one() {
     // Rendering the first item should replace the placeholder with an element
     dom.mark_dirty(ScopeId::APP);
     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 },
         ]
     );
@@ -40,10 +45,11 @@ fn list_creates_one_by_one() {
     // Rendering the next item should insert after the previous
     dom.mark_dirty(ScopeId::APP);
     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 },
         ]
     );
@@ -51,10 +57,11 @@ fn list_creates_one_by_one() {
     // ... and again!
     dom.mark_dirty(ScopeId::APP);
     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 },
         ]
     );
@@ -62,10 +69,11 @@ fn list_creates_one_by_one() {
     // once more
     dom.mark_dirty(ScopeId::APP);
     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 },
         ]
     );
@@ -87,17 +95,20 @@ fn removes_one_by_one() {
 
     // load the div and then assign the empty fragment as a placeholder
     assert_eq!(
-        dom.rebuild_to_vec().sanitize().edits,
+        dom.rebuild_to_vec().edits,
         [
             // The container
-            LoadTemplate { name: "template", index: 0, id: ElementId(1) },
+            LoadTemplate { index: 0, id: ElementId(1) },
             // 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
             ReplacePlaceholder { m: 3, path: &[0] },
             // Mount the div
@@ -109,14 +120,14 @@ fn removes_one_by_one() {
     // Rendering the first item should replace the placeholder with an element
     dom.mark_dirty(ScopeId::APP);
     assert_eq!(
-        dom.render_immediate_to_vec().sanitize().edits,
+        dom.render_immediate_to_vec().edits,
         [Remove { id: ElementId(6) }]
     );
 
     // Remove div(2)
     dom.mark_dirty(ScopeId::APP);
     assert_eq!(
-        dom.render_immediate_to_vec().sanitize().edits,
+        dom.render_immediate_to_vec().edits,
         [Remove { id: ElementId(4) }]
     );
 
@@ -124,7 +135,7 @@ fn removes_one_by_one() {
     // todo: this should just be a remove with no placeholder
     dom.mark_dirty(ScopeId::APP);
     assert_eq!(
-        dom.render_immediate_to_vec().sanitize().edits,
+        dom.render_immediate_to_vec().edits,
         [
             CreatePlaceholder { id: ElementId(4) },
             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
     dom.mark_dirty(ScopeId::APP);
     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 }
         ]
     );
@@ -162,46 +176,53 @@ fn list_shrink_multiroot() {
     });
 
     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 }
         ]
     );
 
     dom.mark_dirty(ScopeId::APP);
     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 }
         ]
     );
 
     dom.mark_dirty(ScopeId::APP);
     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 }
         ]
     );
 
     dom.mark_dirty(ScopeId::APP);
     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 }
         ]
     );
@@ -224,46 +245,48 @@ fn removes_one_by_one_multiroot() {
 
     // load the div and then assign the empty fragment as a placeholder
     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 },
-            //
             AppendChildren { id: ElementId(0), m: 1 }
         ]
     );
 
     dom.mark_dirty(ScopeId::APP);
     assert_eq!(
-        dom.render_immediate_to_vec().sanitize().edits,
+        dom.render_immediate_to_vec().edits,
         [Remove { id: ElementId(10) }, Remove { id: ElementId(12) }]
     );
 
     dom.mark_dirty(ScopeId::APP);
     assert_eq!(
-        dom.render_immediate_to_vec().sanitize().edits,
+        dom.render_immediate_to_vec().edits,
         [Remove { id: ElementId(6) }, Remove { id: ElementId(8) }]
     );
 
     dom.mark_dirty(ScopeId::APP);
     assert_eq!(
-        dom.render_immediate_to_vec().sanitize().edits,
+        dom.render_immediate_to_vec().edits,
         [
             CreatePlaceholder { id: ElementId(8) },
             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!(
             edits.edits,
             [
@@ -329,62 +352,76 @@ fn remove_many() {
         );
     }
 
+    // len = 1
     {
         dom.mark_dirty(ScopeId::APP);
-        let edits = dom.render_immediate_to_vec().sanitize();
+        let edits = dom.render_immediate_to_vec();
         assert_eq!(
             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 },
             ]
         );
     }
 
+    // len = 5
     {
         dom.mark_dirty(ScopeId::APP);
-        let edits = dom.render_immediate_to_vec().sanitize();
+        let edits = dom.render_immediate_to_vec();
         assert_eq!(
             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 },
             ]
         );
     }
 
+    // len = 0
     {
         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!(
-            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);
-        let edits = dom.render_immediate_to_vec().sanitize();
+        let edits = dom.render_immediate_to_vec();
         assert_eq!(
             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 },
             ]
         )
@@ -415,12 +452,13 @@ fn replace_and_add_items() {
 
     // The list starts empty with a placeholder
     {
-        let edits = dom.rebuild_to_vec().sanitize();
+        let edits = dom.rebuild_to_vec();
         assert_eq!(
             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 },
             ]
         );
@@ -429,11 +467,11 @@ fn replace_and_add_items() {
     // Rerendering adds an a static template
     {
         dom.mark_dirty(ScopeId::APP);
-        let edits = dom.render_immediate_to_vec().sanitize();
+        let edits = dom.render_immediate_to_vec();
         assert_eq!(
             edits.edits,
             [
-                LoadTemplate { name: "template", index: 0, id: ElementId(3,) },
+                LoadTemplate { index: 0, id: ElementId(3,) },
                 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
     {
         dom.mark_dirty(ScopeId::APP);
-        let edits = dom.render_immediate_to_vec().sanitize();
+        let edits = dom.render_immediate_to_vec();
         assert_eq!(
             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
     {
         dom.mark_dirty(ScopeId::APP);
-        let edits = dom.render_immediate_to_vec().sanitize();
+        let edits = dom.render_immediate_to_vec();
         assert_eq!(
             edits.edits,
             [
-                LoadTemplate { name: "template", index: 0, id: ElementId(3,) },
+                LoadTemplate { index: 0, id: ElementId(3,) },
                 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 },
-                LoadTemplate { name: "template", index: 0, id: ElementId(4,) },
+                LoadTemplate { index: 0, id: ElementId(4,) },
                 ReplaceWith { id: ElementId(2,), m: 1 },
             ]
         );

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

@@ -1,10 +1,8 @@
 #![cfg(not(miri))]
 
 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> {
     let namespace = rand::random::<u8>() % 2;
@@ -129,7 +127,7 @@ enum DynamicNodeType {
     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 template_idx = 0;
     let mut attr_idx = 0;
@@ -160,7 +158,7 @@ fn create_random_template(name: &'static str) -> (Template, Vec<DynamicNodeType>
             .into_boxed_slice(),
     );
     (
-        Template { name, roots, node_paths, attr_paths },
+        Template { roots, node_paths, attr_paths },
         dynamic_node_type,
     )
 }
@@ -174,7 +172,6 @@ fn create_random_dynamic_node(depth: usize) -> DynamicNode {
                 VNode::new(
                     None,
                     Template {
-                        name: create_template_location(),
                         roots: &[TemplateNode::Dynamic { id: 0 }],
                         node_paths: &[&[0]],
                         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)]
 struct DepthProps {
     depth: usize,
@@ -245,7 +229,7 @@ fn create_random_element(cx: DepthProps) -> Element {
     let range = if cx.root { 2 } else { 3 };
     let node = match rand::random::<usize>() % range {
         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(
                 None,
                 template,
@@ -314,8 +298,6 @@ fn diff() {
 struct InsertEventListenerMutationHandler<'a>(&'a mut HashSet<ElementId>);
 
 impl WriteMutations for InsertEventListenerMutationHandler<'_> {
-    fn register_template(&mut self, _: Template) {}
-
     fn append_children(&mut self, _: ElementId, _: usize) {}
 
     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 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) {}
 

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

@@ -33,13 +33,14 @@ fn basic_syntax_is_a_template() -> Element {
 #[test]
 fn dual_stream() {
     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::*;
     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 {
                 name: "class",
                 value: "asd 123 123 ".into_value(),

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

@@ -30,10 +30,11 @@ fn manual_diffing() {
     *value.lock().unwrap() = "goodbye";
 
     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) }
         ]
     );

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

@@ -415,20 +415,20 @@ fn toggle_suspense() {
         .unwrap()
         .block_on(async {
             let mut dom = VirtualDom::new(app);
-            let mutations = dom.rebuild_to_vec().sanitize();
+            let mutations = dom.rebuild_to_vec();
 
             // First create goodbye world
             println!("{:#?}", mutations);
             assert_eq!(
                 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 }
                 ]
             );
 
             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
             println!("{:#?}", mutations);
@@ -441,20 +441,20 @@ fn toggle_suspense() {
             );
 
             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
             println!("{:#?}", mutations);
             assert_eq!(
                 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 },
                 ]
             );
 
             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
             println!("{:#?}", mutations);
@@ -463,7 +463,7 @@ fn toggle_suspense() {
                 [
                     Mutation::CreatePlaceholder { id: ElementId(2,) },
                     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 },
                 ]
             );
@@ -600,14 +600,14 @@ fn nested_suspense_resolves_client() {
             // DOM STATE:
             // placeholder // ID: 1
             // "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
             // The title also finishes loading
             assert_eq!(
                 mutations.edits,
                 vec![
                     // 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.
                     // Then we need to replace what we just rendered with the suspense placeholder
                     CreatePlaceholder { id: ElementId(3,) },
@@ -622,12 +622,9 @@ fn nested_suspense_resolves_client() {
                     ReplaceWith { id: ElementId(2,), m: 1 },
 
                     // 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 {
                         name: "id",
                         ns: None,
@@ -636,12 +633,9 @@ fn nested_suspense_resolves_client() {
                     },
 
                     // 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 {
                         name: "id",
                         ns: None,
@@ -650,7 +644,7 @@ fn nested_suspense_resolves_client() {
                     },
 
                     // 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 2...".to_string(), id: ElementId(9,) },
                     ReplacePlaceholder { path: &[0,], m: 2 },
@@ -674,7 +668,7 @@ fn nested_suspense_resolves_client() {
             // div // ID: 7
             //   "Loading 1..." // ID: 8
             //   "Loading 2..." // ID: 9
-            let mutations = dom.render_immediate_to_vec().sanitize();
+            let mutations = dom.render_immediate_to_vec();
             assert_eq!(
                 mutations.edits,
                 vec![
@@ -693,20 +687,18 @@ fn nested_suspense_resolves_client() {
 
                     // Load the nested suspense
                     LoadTemplate {
-                        name: "template",
+
                         index: 0,
                         id: ElementId(
                             8,
                         ),
                     },
-                    HydrateText {
+                    CreateTextNode { value: "The world says hello back".to_string(), id: ElementId(10,) },
+                    ReplacePlaceholder {
                         path: &[
                             0,
                         ],
-                        value: "The world says hello back".to_string(),
-                        id: ElementId(
-                            10,
-                        ),
+                        m: 1,
                     },
                     SetAttribute {
                         name: "id",
@@ -717,20 +709,18 @@ fn nested_suspense_resolves_client() {
                         ),
                     },
                     LoadTemplate {
-                        name: "template",
+
                         index: 1,
                         id: ElementId(
                             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: &[
                             0,
                         ],
-                        value: "In a stunning turn of events, the world collectively unites and says hello back".to_string(),
-                        id: ElementId(
-                            12,
-                        ),
+                        m: 1,
                     },
                     SetAttribute {
                         name: "id",
@@ -741,19 +731,17 @@ fn nested_suspense_resolves_client() {
                         ),
                     },
                     LoadTemplate {
-                        name: "template",
                         index: 2,
                         id: ElementId(
                             13,
                         ),
                     },
-                    AssignId {
+                    CreatePlaceholder { id: ElementId(14,) },
+                    ReplacePlaceholder {
                         path: &[
                             0,
                         ],
-                        id: ElementId(
-                            14,
-                        ),
+                        m: 1,
                     },
                     SetAttribute {
                         name: "id",
@@ -783,20 +771,17 @@ fn nested_suspense_resolves_client() {
                         m: 1,
                     },
                     LoadTemplate {
-                        name: "template",
                         index: 0,
                         id: ElementId(
                             9,
                         ),
                     },
-                    HydrateText {
+                    CreateTextNode { value: "Goodbye Robot".to_string(), id: ElementId(15,) },
+                    ReplacePlaceholder {
                         path: &[
                             0,
                         ],
-                        value: "Goodbye Robot".to_string(),
-                        id: ElementId(
-                            15,
-                        ),
+                        m: 1,
                     },
                     SetAttribute {
                         name: "id",
@@ -807,20 +792,17 @@ fn nested_suspense_resolves_client() {
                         ),
                     },
                     LoadTemplate {
-                        name: "template",
                         index: 1,
                         id: ElementId(
                             16,
                         ),
                     },
-                    HydrateText {
+                    CreateTextNode { value: "The robot says goodbye".to_string(), id: ElementId(17,) },
+                    ReplacePlaceholder {
                         path: &[
                             0,
                         ],
-                        value: "The robot says goodbye".to_string(),
-                        id: ElementId(
-                            17,
-                        ),
+                        m: 1,
                     },
                     SetAttribute {
                         name: "id",
@@ -831,7 +813,7 @@ fn nested_suspense_resolves_client() {
                         ),
                     },
                     LoadTemplate {
-                        name: "template",
+
                         index: 2,
                         id: ElementId(
                             18,
@@ -860,7 +842,7 @@ fn nested_suspense_resolves_client() {
             );
 
             dom.wait_for_work().await;
-            let mutations = dom.render_immediate_to_vec().sanitize();
+            let mutations = dom.render_immediate_to_vec();
             assert_eq!(
                 mutations.edits,
                 vec![
@@ -876,20 +858,18 @@ fn nested_suspense_resolves_client() {
                         m: 1,
                     },
                     LoadTemplate {
-                        name: "template",
+
                         index: 0,
                         id: ElementId(
                             19,
                         ),
                     },
-                    HydrateText {
+                    CreateTextNode { value: "Goodbye Robot again".to_string(), id: ElementId(20,) },
+                    ReplacePlaceholder {
                         path: &[
                             0,
                         ],
-                        value: "Goodbye Robot again".to_string(),
-                        id: ElementId(
-                            20,
-                        ),
+                        m: 1,
                     },
                     SetAttribute {
                         name: "id",
@@ -900,20 +880,17 @@ fn nested_suspense_resolves_client() {
                         ),
                     },
                     LoadTemplate {
-                        name: "template",
                         index: 1,
                         id: ElementId(
                             21,
                         ),
                     },
-                    HydrateText {
+                    CreateTextNode { value: "The robot says goodbye again".to_string(), id: ElementId(22,) },
+                    ReplacePlaceholder {
                         path: &[
                             0,
                         ],
-                        value: "The robot says goodbye again".to_string(),
-                        id: ElementId(
-                            22,
-                        ),
+                        m: 1,
                     },
                     SetAttribute {
                         name: "id",
@@ -924,19 +901,17 @@ fn nested_suspense_resolves_client() {
                         ),
                     },
                     LoadTemplate {
-                        name: "template",
                         index: 2,
                         id: ElementId(
                             23,
                         ),
                     },
-                    AssignId {
+                    CreatePlaceholder { id: ElementId(24,) },
+                    ReplacePlaceholder {
                         path: &[
-                            0,
+                            0
                         ],
-                        id: ElementId(
-                            24,
-                        ),
+                        m: 1,
                     },
                     SetAttribute {
                         name: "id",

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

@@ -165,11 +165,11 @@ fn test_mouse_dblclick_div() -> Element {
     utils::mock_event(
         "mouse_dblclick_div",
         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(
         "mouse_down_div",
         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(
         "mouse_up_div",
         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(
         "wheel_div",
         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();
 }
 
+#[cfg(all(not(feature = "desktop"), not(feature = "server")))]
+fn main() {}
+
 pub fn app() -> Element {
     let mut count = use_signal(|| 0);
     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) {
         "{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) {
         "{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")]
     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$);}"
     }
 
-    /// 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")]
     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);}"

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

@@ -1,16 +1,13 @@
 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 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
 #[derive(Default)]
 pub struct MutationState {
-    /// The maximum number of templates that we have registered
-    max_template_count: u16,
-
     /// The currently registered templates with the template ids
-    templates: FxHashMap<String, u16>,
+    templates: FxHashMap<Template, u16>,
 
     /// The channel that we are applying mutations to
     channel: Channel,
@@ -81,19 +78,6 @@ impl 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) {
         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);
     }
 
-    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) {

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

@@ -70,9 +70,6 @@ use dioxus_core::internal::{
     HotReloadLiteral, HotReloadedTemplate, NamedAttribute,
 };
 use std::collections::HashMap;
-use std::hash::DefaultHasher;
-use std::hash::Hash;
-use std::hash::Hasher;
 
 use super::last_build_state::LastBuildState;
 
@@ -200,23 +197,7 @@ impl HotReloadResult {
             .collect();
         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(
-            name,
             key,
             new_dynamic_nodes,
             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 diagnostics = &self.diagnostics;
         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! {
             dioxus_core::Element::Ok({
                 #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))) =
             self.implicit_key()
         {
@@ -346,7 +339,6 @@ impl TemplateBody {
             .map(|literal| literal.quote_as_hot_reload_literal());
         quote! {
             dioxus_core::internal::HotReloadedTemplate::new(
-                #name,
                 #key,
                 vec![ #( #dynamic_nodes ),* ],
                 vec![ #( #dyn_attr_printer ),* ],

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

@@ -20,7 +20,7 @@ pub struct Renderer {
     render_components: Option<ComponentRenderCallback>,
 
     /// 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
     dynamic_node_id: usize,
@@ -108,7 +108,7 @@ impl Renderer {
     ) -> std::fmt::Result {
         let entry = self
             .template_cache
-            .entry(template.template.id())
+            .entry(template.template)
             .or_insert_with(move || Arc::new(StringCache::from_template(template).unwrap()))
             .clone();
 

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

@@ -6,7 +6,7 @@
 //! - tests to ensure dyn_into works for various event types.
 //! - Partial delegation?
 
-use dioxus_core::ElementId;
+use dioxus_core::{ElementId, Template};
 use dioxus_html::PlatformEventData;
 use dioxus_interpreter_js::unified_bindings::Interpreter;
 use futures_channel::mpsc;
@@ -20,8 +20,7 @@ pub struct WebsysDom {
     #[allow(dead_code)]
     pub(crate) root: Element,
     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,
 
     #[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.
     // 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
     #[cfg(feature = "hydrate")]
-    pub(crate) only_write_templates: bool,
+    pub(crate) skip_mutations: bool,
 
     #[cfg(feature = "hydrate")]
     pub(crate) suspense_hydration_ids: crate::hydration::SuspenseHydrationIds,
@@ -145,13 +144,12 @@ impl WebsysDom {
             root,
             interpreter,
             templates: FxHashMap::default(),
-            max_template_id: 0,
             #[cfg(feature = "mounted")]
             event_channel,
             #[cfg(feature = "mounted")]
             queued_mounted_events: Default::default(),
             #[cfg(feature = "hydrate")]
-            only_write_templates: false,
+            skip_mutations: false,
             #[cfg(feature = "hydrate")]
             suspense_hydration_ids: Default::default(),
         }

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

@@ -151,11 +151,11 @@ impl WebsysDom {
                 self,
                 |to| {
                     // Switch to only writing templates
-                    to.only_write_templates = true;
+                    to.skip_mutations = true;
                 },
                 children.len(),
             );
-            self.only_write_templates = false;
+            self.skip_mutations = false;
         });
 
         // 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;
 use crate::hydration::SuspenseMessage;
 use dioxus_core::VirtualDom;
+use dom::WebsysDom;
 use futures_util::{pin_mut, select, FutureExt, StreamExt};
 
 mod cfg;
@@ -77,7 +78,7 @@ pub async fn run(virtual_dom: VirtualDom, web_config: Config) -> ! {
 
     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>> =
         None;
@@ -85,7 +86,7 @@ pub async fn run(virtual_dom: VirtualDom, web_config: Config) -> ! {
     if should_hydrate {
         #[cfg(feature = "hydrate")]
         {
-            websys_dom.only_write_templates = true;
+            websys_dom.skip_mutations = true;
             // Get the initial hydration data from the client
             #[wasm_bindgen::prelude::wasm_bindgen(inline_js = r#"
                 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, || {
                 dom.rebuild(&mut websys_dom);
             });
-            websys_dom.only_write_templates = false;
+            websys_dom.skip_mutations = false;
 
             let rx = websys_dom.rehydrate(&dom).unwrap();
             hydration_receiver = Some(rx);

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

@@ -79,10 +79,10 @@ impl WebsysDom {
     }
 
     #[inline]
-    fn only_write_templates(&self) -> bool {
+    fn skip_mutations(&self) -> bool {
         #[cfg(feature = "hydrate")]
         {
-            self.only_write_templates
+            self.skip_mutations
         }
         #[cfg(not(feature = "hydrate"))]
         {
@@ -92,28 +92,15 @@ impl 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) {
-        if self.only_write_templates() {
+        if self.skip_mutations() {
             return;
         }
         self.interpreter.append_children(id.0 as u32, m as u16)
     }
 
     fn assign_node_id(&mut self, path: &'static [u8], id: ElementId) {
-        if self.only_write_templates() {
+        if self.skip_mutations() {
             return;
         }
         self.interpreter
@@ -121,46 +108,47 @@ impl WriteMutations for WebsysDom {
     }
 
     fn create_placeholder(&mut self, id: ElementId) {
-        if self.only_write_templates() {
+        if self.skip_mutations() {
             return;
         }
         self.interpreter.create_placeholder(id.0 as u32)
     }
 
     fn create_text_node(&mut self, value: &str, id: ElementId) {
-        if self.only_write_templates() {
+        if self.skip_mutations() {
             return;
         }
         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;
         }
-        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) {
-        if self.only_write_templates() {
+        if self.skip_mutations() {
             return;
         }
         self.interpreter.replace_with(id.0 as u32, m as u16)
     }
 
     fn replace_placeholder_with_nodes(&mut self, path: &'static [u8], m: usize) {
-        if self.only_write_templates() {
+        if self.skip_mutations() {
             return;
         }
         self.interpreter
@@ -168,14 +156,14 @@ impl WriteMutations for WebsysDom {
     }
 
     fn insert_nodes_after(&mut self, id: ElementId, m: usize) {
-        if self.only_write_templates() {
+        if self.skip_mutations() {
             return;
         }
         self.interpreter.insert_after(id.0 as u32, m as u16)
     }
 
     fn insert_nodes_before(&mut self, id: ElementId, m: usize) {
-        if self.only_write_templates() {
+        if self.skip_mutations() {
             return;
         }
         self.interpreter.insert_before(id.0 as u32, m as u16)
@@ -188,7 +176,7 @@ impl WriteMutations for WebsysDom {
         value: &AttributeValue,
         id: ElementId,
     ) {
-        if self.only_write_templates() {
+        if self.skip_mutations() {
             return;
         }
         match value {
@@ -223,14 +211,14 @@ impl WriteMutations for WebsysDom {
     }
 
     fn set_node_text(&mut self, value: &str, id: ElementId) {
-        if self.only_write_templates() {
+        if self.skip_mutations() {
             return;
         }
         self.interpreter.set_text(id.0 as u32, value)
     }
 
     fn create_event_listener(&mut self, name: &'static str, id: ElementId) {
-        if self.only_write_templates() {
+        if self.skip_mutations() {
             return;
         }
         // 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) {
-        if self.only_write_templates() {
+        if self.skip_mutations() {
             return;
         }
         if name == "mounted" {
@@ -257,14 +245,14 @@ impl WriteMutations for WebsysDom {
     }
 
     fn remove_node(&mut self, id: ElementId) {
-        if self.only_write_templates() {
+        if self.skip_mutations() {
             return;
         }
         self.interpreter.remove(id.0 as u32)
     }
 
     fn push_root(&mut self, id: ElementId) {
-        if self.only_write_templates() {
+        if self.skip_mutations() {
             return;
         }
         self.interpreter.push_root(id.0 as u32)