Browse Source

Merge branch 'master' into jk/suspense-rollover

Jonathan Kelley 2 years ago
parent
commit
8887d65e1b
35 changed files with 2516 additions and 2640 deletions
  1. 3 5
      packages/core/src/arena.rs
  2. 206 43
      packages/core/src/create.rs
  3. 53 25
      packages/core/src/diff.rs
  4. 2 2
      packages/core/src/fragment.rs
  5. 169 11
      packages/core/src/nodes.rs
  6. 28 2
      packages/core/src/virtual_dom.rs
  7. 19 5
      packages/desktop/src/controller.rs
  8. 0 6
      packages/desktop/src/desktop_context.rs
  9. 5 7
      packages/desktop/src/hot_reload.rs
  10. 3 0
      packages/desktop/src/lib.rs
  11. 1 1
      packages/dioxus/benches/jsframework.rs
  12. 2 0
      packages/html/Cargo.toml
  13. 669 506
      packages/html/src/elements.rs
  14. 2 2
      packages/html/src/events/form.rs
  15. 45 3
      packages/html/src/global_attributes.rs
  16. 2 0
      packages/html/src/lib.rs
  17. 7 7
      packages/native-core/src/real_dom.rs
  18. 16 15
      packages/native-core/src/utils/persistant_iterator.rs
  19. 2 0
      packages/rsx/Cargo.toml
  20. 0 892
      packages/rsx/src/attributes.rs
  21. 1 1
      packages/rsx/src/component.rs
  22. 637 0
      packages/rsx/src/hot_reload/hot_reload_diff.rs
  23. 19 0
      packages/rsx/src/hot_reload/hot_reloading_context.rs
  24. 122 0
      packages/rsx/src/hot_reload/hot_reloading_file_map.rs
  25. 6 0
      packages/rsx/src/hot_reload/mod.rs
  26. 1 1
      packages/rsx/src/ifmt.rs
  27. 470 24
      packages/rsx/src/lib.rs
  28. 1 3
      packages/rsx/src/node.rs
  29. 0 1064
      packages/rsx/src/template.rs
  30. 2 2
      packages/ssr/src/cache.rs
  31. 1 1
      packages/ssr/src/renderer.rs
  32. 2 2
      packages/tui/examples/tui_colorpicker.rs
  33. 1 1
      packages/tui/src/widgets/mod.rs
  34. 10 4
      packages/web/src/hot_reload.rs
  35. 9 5
      packages/web/src/lib.rs

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

@@ -126,11 +126,9 @@ impl VirtualDom {
             }
         });
 
-        for root in node.root_ids {
-            if let Some(id) = root.get() {
-                if id.0 != 0 {
-                    self.try_reclaim(id);
-                }
+        for id in &node.root_ids {
+            if id.0 != 0 {
+                self.try_reclaim(id);
             }
         }
     }

+ 206 - 43
packages/core/src/create.rs

@@ -5,13 +5,50 @@ use crate::mutations::Mutation::*;
 use crate::nodes::VNode;
 use crate::nodes::{DynamicNode, TemplateNode};
 use crate::virtual_dom::VirtualDom;
-use crate::{AttributeValue, ElementId, RenderReturn, ScopeId, SuspenseContext};
+use crate::{AttributeValue, ElementId, RenderReturn, ScopeId, SuspenseContext, Template};
 use std::cell::Cell;
-use std::iter::{Enumerate, Peekable};
+use std::iter::Peekable;
 use std::rc::Rc;
-use std::slice;
 use TemplateNode::*;
 
+fn sort_bfs(paths: &[&'static [u8]]) -> Vec<(usize, &'static [u8])> {
+    let mut with_indecies = paths.iter().copied().enumerate().collect::<Vec<_>>();
+    with_indecies.sort_unstable_by(|(_, a), (_, b)| {
+        let mut a = a.iter();
+        let mut b = b.iter();
+        loop {
+            match (a.next(), b.next()) {
+                (Some(a), Some(b)) => {
+                    if a != b {
+                        return a.cmp(b);
+                    }
+                }
+                // The shorter path goes first
+                (Some(_), None) => return std::cmp::Ordering::Less,
+                (None, Some(_)) => return std::cmp::Ordering::Greater,
+                (None, None) => return std::cmp::Ordering::Equal,
+            }
+        }
+    });
+    with_indecies
+}
+
+#[test]
+fn sorting() {
+    let r: [(usize, &[u8]); 5] = [
+        (0, &[0, 1]),
+        (1, &[0, 2]),
+        (2, &[1, 0]),
+        (4, &[1, 1]),
+        (3, &[1, 2]),
+    ];
+    assert_eq!(
+        sort_bfs(&[&[0, 1,], &[0, 2,], &[1, 0,], &[1, 2,], &[1, 1,],]),
+        r
+    );
+    assert!(matches!(&[0], &[_, ..]))
+}
+
 impl<'b> VirtualDom {
     /// Create a new template [`VNode`] and write it to the [`Mutations`] buffer.
     ///
@@ -25,21 +62,79 @@ impl<'b> VirtualDom {
 
     /// Create this template and write its mutations
     pub(crate) fn create(&mut self, node: &'b VNode<'b>) -> usize {
+        // check for a overriden template
+        #[cfg(debug_assertions)]
+        {
+            let (path, byte_index) = node.template.get().name.rsplit_once(':').unwrap();
+            if let Some(template) = self
+                .templates
+                .get(path)
+                .and_then(|map| map.get(&byte_index.parse().unwrap()))
+            {
+                node.template.set(*template);
+            }
+        }
+
+        // Intialize the root nodes slice
+        node.root_ids
+            .intialize(vec![ElementId(0); node.template.get().roots.len()].into_boxed_slice());
+
         // The best renderers will have templates prehydrated and registered
         // Just in case, let's create the template using instructions anyways
-        if !self.templates.contains_key(&node.template.name) {
-            self.register_template(node);
+        if !self.templates.contains_key(&node.template.get().name) {
+            self.register_template(node.template.get());
         }
 
         // we know that this will generate at least one mutation per node
-        self.mutations.edits.reserve(node.template.roots.len());
+        self.mutations
+            .edits
+            .reserve(node.template.get().roots.len());
 
         // Walk the roots, creating nodes and assigning IDs
+        // nodes in an iterator of ((dynamic_node_index, sorted_index), path)
         // todo: adjust dynamic nodes to be in the order of roots and then leaves (ie BFS)
-        let mut attrs = node.template.attr_paths.iter().enumerate().peekable();
-        let mut nodes = node.template.node_paths.iter().enumerate().peekable();
+        #[cfg(not(debug_assertions))]
+        let (mut attrs, mut nodes) = (
+            node.template
+                .get()
+                .attr_paths
+                .iter()
+                .copied()
+                .enumerate()
+                .peekable(),
+            node.template
+                .get()
+                .node_paths
+                .iter()
+                .copied()
+                .enumerate()
+                .map(|(i, path)| ((i, i), path))
+                .peekable(),
+        );
+        // If this is a debug build, we need to check that the paths are in the correct order because hot reloading can cause scrambled states
+
+        #[cfg(debug_assertions)]
+        let (attrs_sorted, nodes_sorted) = {
+            (
+                sort_bfs(node.template.get().attr_paths),
+                sort_bfs(node.template.get().node_paths),
+            )
+        };
+        #[cfg(debug_assertions)]
+        let (mut attrs, mut nodes) = {
+            (
+                attrs_sorted.into_iter().peekable(),
+                nodes_sorted
+                    .iter()
+                    .copied()
+                    .enumerate()
+                    .map(|(i, (id, path))| ((id, i), path))
+                    .peekable(),
+            )
+        };
 
         node.template
+            .get()
             .roots
             .iter()
             .enumerate()
@@ -48,7 +143,14 @@ impl<'b> VirtualDom {
                     nodes.next().unwrap();
                     self.write_dynamic_root(node, *id)
                 }
-                Element { .. } => self.write_element_root(node, idx, &mut attrs, &mut nodes),
+                Element { .. } => {
+                    #[cfg(not(debug_assertions))]
+                    let id = self.write_element_root(node, idx, &mut attrs, &mut nodes, &[]);
+                    #[cfg(debug_assertions)]
+                    let id =
+                        self.write_element_root(node, idx, &mut attrs, &mut nodes, &nodes_sorted);
+                    id
+                }
                 Text { .. } => self.write_static_text_root(node, idx),
             })
             .sum()
@@ -65,8 +167,9 @@ impl<'b> VirtualDom {
     fn write_dynamic_root(&mut self, template: &'b VNode<'b>, idx: usize) -> usize {
         use DynamicNode::*;
         match &template.dynamic_nodes[idx] {
-            node @ Fragment(_) => self.create_dynamic_node(template, node, idx),
-            node @ Component { .. } => self.create_dynamic_node(template, node, idx),
+            node @ Component { .. } | node @ Fragment(_) => {
+                self.create_dynamic_node(template, node, idx)
+            }
             Placeholder(VPlaceholder { id }) => {
                 let id = self.set_slot(template, id, idx);
                 self.mutations.push(CreatePlaceholder { id });
@@ -98,17 +201,18 @@ impl<'b> VirtualDom {
         &mut self,
         template: &'b VNode<'b>,
         root_idx: usize,
-        dynamic_attrs: &mut Peekable<Enumerate<slice::Iter<&'static [u8]>>>,
-        dynamic_nodes: &mut Peekable<Enumerate<slice::Iter<&'static [u8]>>>,
+        dynamic_attrs: &mut Peekable<impl Iterator<Item = (usize, &'static [u8])>>,
+        dynamic_nodes_iter: &mut Peekable<impl Iterator<Item = ((usize, usize), &'static [u8])>>,
+        dynamic_nodes: &[(usize, &'static [u8])],
     ) -> usize {
         // Load the template root and get the ID for the node on the stack
         let root_on_stack = self.load_template_root(template, root_idx);
 
         // Write all the attributes below this root
-        self.write_attrs_on_root(dynamic_attrs, root_idx, root_on_stack, template);
+        self.write_attrs_on_root(dynamic_attrs, root_idx as u8, root_on_stack, template);
 
         // Load in all of the placeholder or dynamic content under this root too
-        self.load_placeholders(dynamic_nodes, root_idx, template);
+        self.load_placeholders(dynamic_nodes_iter, dynamic_nodes, root_idx as u8, template);
 
         1
     }
@@ -126,22 +230,32 @@ impl<'b> VirtualDom {
     ///     }
     /// }
     /// ```
+    #[allow(unused)]
     fn load_placeholders(
         &mut self,
-        dynamic_nodes: &mut Peekable<Enumerate<slice::Iter<&'static [u8]>>>,
-        root_idx: usize,
+        dynamic_nodes_iter: &mut Peekable<impl Iterator<Item = ((usize, usize), &'static [u8])>>,
+        dynamic_nodes: &[(usize, &'static [u8])],
+        root_idx: u8,
         template: &'b VNode<'b>,
     ) {
-        let (start, end) = match collect_dyn_node_range(dynamic_nodes, root_idx) {
+        let (start, end) = match collect_dyn_node_range(dynamic_nodes_iter, root_idx) {
             Some((a, b)) => (a, b),
             None => return,
         };
 
-        for idx in (start..=end).rev() {
+        // If hot reloading is enabled, we need to map the sorted index to the original index of the dynamic node. If it is disabled, we can just use the sorted index
+        #[cfg(not(debug_assertions))]
+        let reversed_iter = (start..=end).rev();
+        #[cfg(debug_assertions)]
+        let reversed_iter = (start..=end)
+            .rev()
+            .map(|sorted_index| dynamic_nodes[sorted_index].0);
+
+        for idx in reversed_iter {
             let m = self.create_dynamic_node(template, &template.dynamic_nodes[idx], idx);
             if m > 0 {
                 // The path is one shorter because the top node is the root
-                let path = &template.template.node_paths[idx][1..];
+                let path = &template.template.get().node_paths[idx][1..];
                 self.mutations.push(ReplacePlaceholder { m, path });
             }
         }
@@ -149,12 +263,14 @@ impl<'b> VirtualDom {
 
     fn write_attrs_on_root(
         &mut self,
-        attrs: &mut Peekable<Enumerate<slice::Iter<&'static [u8]>>>,
-        root_idx: usize,
+        attrs: &mut Peekable<impl Iterator<Item = (usize, &'static [u8])>>,
+        root_idx: u8,
         root: ElementId,
         node: &VNode,
     ) {
-        while let Some((mut attr_id, path)) = attrs.next_if(|(_, p)| p[0] == root_idx as u8) {
+        while let Some((mut attr_id, path)) =
+            attrs.next_if(|(_, p)| p.first().copied() == Some(root_idx))
+        {
             let id = self.assign_static_node_as_dynamic(path, root, node, attr_id);
 
             loop {
@@ -210,10 +326,10 @@ impl<'b> VirtualDom {
     fn load_template_root(&mut self, template: &VNode, root_idx: usize) -> ElementId {
         // Get an ID for this root since it's a real root
         let this_id = self.next_root(template, root_idx);
-        template.root_ids[root_idx].set(Some(this_id));
+        template.root_ids.set(root_idx, this_id);
 
         self.mutations.push(LoadTemplate {
-            name: template.template.name,
+            name: template.template.get().name,
             index: root_idx,
             id: this_id,
         });
@@ -241,7 +357,7 @@ impl<'b> VirtualDom {
 
         // if attribute is on a root node, then we've already created the element
         // Else, it's deep in the template and we should create a new id for it
-        let id = self.next_element(template, template.template.attr_paths[attr_id]);
+        let id = self.next_element(template, template.template.get().attr_paths[attr_id]);
 
         self.mutations.push(Mutation::AssignId {
             path: &path[1..],
@@ -252,14 +368,59 @@ impl<'b> VirtualDom {
     }
 
     /// Insert a new template into the VirtualDom's template registry
-    fn register_template(&mut self, template: &'b VNode<'b>) {
+    pub(crate) fn register_template_first_byte_index(&mut self, mut template: Template<'static>) {
         // First, make sure we mark the template as seen, regardless if we process it
+        let (path, _) = template.name.rsplit_once(':').unwrap();
+        if let Some((_, old_template)) = self
+            .templates
+            .entry(path)
+            .or_default()
+            .iter_mut()
+            .min_by_key(|(byte_index, _)| **byte_index)
+        {
+            // the byte index of the hot reloaded template could be different
+            template.name = old_template.name;
+            *old_template = template;
+        } else {
+            // This is a template without any current instances
+            self.templates
+                .entry(path)
+                .or_default()
+                .insert(usize::MAX, template);
+        }
+
+        // If it's all dynamic nodes, then we don't need to register it
+        if !template.is_completely_dynamic() {
+            self.mutations.templates.push(template);
+        }
+    }
+
+    /// Insert a new template into the VirtualDom's template registry
+    pub(crate) fn register_template(&mut self, mut template: Template<'static>) {
+        // First, make sure we mark the template as seen, regardless if we process it
+        let (path, byte_index) = template.name.rsplit_once(':').unwrap();
+        let byte_index = byte_index.parse::<usize>().unwrap();
+
+        // if hot reloading is enabled, then we need to check for a template that has overriten this one
+        #[cfg(debug_assertions)]
+        if let Some(mut new_template) = self
+            .templates
+            .get_mut(path)
+            .and_then(|map| map.remove(&usize::MAX))
+        {
+            // the byte index of the hot reloaded template could be different
+            new_template.name = template.name;
+            template = new_template;
+        }
+
         self.templates
-            .insert(template.template.name, template.template);
+            .entry(path)
+            .or_default()
+            .insert(byte_index, template);
 
         // If it's all dynamic nodes, then we don't need to register it
-        if !template.template.is_completely_dynamic() {
-            self.mutations.templates.push(template.template);
+        if !template.is_completely_dynamic() {
+            self.mutations.templates.push(template);
         }
     }
 
@@ -285,7 +446,7 @@ impl<'b> VirtualDom {
         idx: usize,
     ) -> usize {
         // Allocate a dynamic element reference for this text node
-        let new_id = self.next_element(template, template.template.node_paths[idx]);
+        let new_id = self.next_element(template, template.template.get().node_paths[idx]);
 
         // Make sure the text node is assigned to the correct element
         text.id.set(Some(new_id));
@@ -296,7 +457,7 @@ impl<'b> VirtualDom {
         // Add the mutation to the list
         self.mutations.push(HydrateText {
             id: new_id,
-            path: &template.template.node_paths[idx][1..],
+            path: &template.template.get().node_paths[idx][1..],
             value,
         });
 
@@ -311,14 +472,14 @@ impl<'b> VirtualDom {
         idx: usize,
     ) -> usize {
         // Allocate a dynamic element reference for this text node
-        let id = self.next_element(template, template.template.node_paths[idx]);
+        let id = self.next_element(template, template.template.get().node_paths[idx]);
 
         // Make sure the text node is assigned to the correct element
         placeholder.id.set(Some(id));
 
         // Assign the ID to the existing node in the template
         self.mutations.push(AssignId {
-            path: &template.template.node_paths[idx][1..],
+            path: &template.template.get().node_paths[idx][1..],
             id,
         });
 
@@ -384,7 +545,7 @@ impl<'b> VirtualDom {
         };
 
         // Since this is a boundary, use its placeholder within the template as the placeholder for the suspense tree
-        let new_id = self.next_element(new, parent.template.node_paths[idx]);
+        let new_id = self.next_element(new, parent.template.get().node_paths[idx]);
 
         // Now connect everything to the boundary
         self.scopes[scope.0].placeholder.set(Some(new_id));
@@ -406,7 +567,7 @@ impl<'b> VirtualDom {
         // Now assign the placeholder in the DOM
         self.mutations.push(AssignId {
             id: new_id,
-            path: &parent.template.node_paths[idx][1..],
+            path: &parent.template.get().node_paths[idx][1..],
         });
 
         0
@@ -423,7 +584,7 @@ impl<'b> VirtualDom {
     ///
     /// IE simply assign an ID to the placeholder
     fn mount_async(&mut self, template: &VNode, idx: usize, scope: ScopeId) -> usize {
-        let new_id = self.next_element(template, template.template.node_paths[idx]);
+        let new_id = self.next_element(template, template.template.get().node_paths[idx]);
 
         // Set the placeholder of the scope
         self.scopes[scope.0].placeholder.set(Some(new_id));
@@ -431,7 +592,7 @@ impl<'b> VirtualDom {
         // Since the placeholder is already in the DOM, we don't create any new nodes
         self.mutations.push(AssignId {
             id: new_id,
-            path: &template.template.node_paths[idx][1..],
+            path: &template.template.get().node_paths[idx][1..],
         });
 
         0
@@ -443,24 +604,26 @@ impl<'b> VirtualDom {
         slot: &'b Cell<Option<ElementId>>,
         id: usize,
     ) -> ElementId {
-        let id = self.next_element(template, template.template.node_paths[id]);
+        let id = self.next_element(template, template.template.get().node_paths[id]);
         slot.set(Some(id));
         id
     }
 }
 
 fn collect_dyn_node_range(
-    dynamic_nodes: &mut Peekable<Enumerate<slice::Iter<&[u8]>>>,
-    root_idx: usize,
+    dynamic_nodes: &mut Peekable<impl Iterator<Item = ((usize, usize), &'static [u8])>>,
+    root_idx: u8,
 ) -> Option<(usize, usize)> {
     let start = match dynamic_nodes.peek() {
-        Some((idx, p)) if p[0] == root_idx as u8 => *idx,
+        Some(((_, idx), [first, ..])) if *first == root_idx => *idx,
         _ => return None,
     };
 
     let mut end = start;
 
-    while let Some((idx, p)) = dynamic_nodes.next_if(|(_, p)| p[0] == root_idx as u8) {
+    while let Some(((_, idx), p)) =
+        dynamic_nodes.next_if(|(_, p)| matches!(p, [idx, ..] if *idx == root_idx))
+    {
         if p.len() == 1 {
             continue;
         }

+ 53 - 25
packages/core/src/diff.rs

@@ -70,6 +70,21 @@ impl<'b> VirtualDom {
     }
 
     fn diff_node(&mut self, left_template: &'b VNode<'b>, right_template: &'b VNode<'b>) {
+        // If hot reloading is enabled, we need to make sure we're using the latest template
+        #[cfg(debug_assertions)]
+        {
+            let (path, byte_index) = right_template.template.get().name.rsplit_once(':').unwrap();
+            if let Some(map) = self.templates.get(path) {
+                let byte_index = byte_index.parse::<usize>().unwrap();
+                if let Some(&template) = map.get(&byte_index) {
+                    right_template.template.set(template);
+                    if template != left_template.template.get() {
+                        return self.replace(left_template, [right_template]);
+                    }
+                }
+            }
+        }
+
         // If the templates are the same, we don't need to do anything, nor do we want to
         if templates_are_the_same(left_template, right_template) {
             return;
@@ -114,11 +129,7 @@ impl<'b> VirtualDom {
             });
 
         // Make sure the roots get transferred over while we're here
-        left_template
-            .root_ids
-            .iter()
-            .zip(right_template.root_ids.iter())
-            .for_each(|(left, right)| right.set(left.get()));
+        right_template.root_ids.transfer(&left_template.root_ids);
     }
 
     fn diff_dynamic_node(
@@ -663,6 +674,7 @@ impl<'b> VirtualDom {
     /// Push all the real nodes on the stack
     fn push_all_real_nodes(&mut self, node: &'b VNode<'b>) -> usize {
         node.template
+            .get()
             .roots
             .iter()
             .enumerate()
@@ -671,7 +683,7 @@ impl<'b> VirtualDom {
                     Some(node) => node,
                     None => {
                         self.mutations.push(Mutation::PushRoot {
-                            id: node.root_ids[idx].get().unwrap(),
+                            id: node.root_ids.get(idx).unwrap(),
                         });
                         return 1;
                     }
@@ -803,11 +815,11 @@ impl<'b> VirtualDom {
     }
 
     fn reclaim_roots(&mut self, node: &VNode, gen_muts: bool) {
-        for idx in 0..node.template.roots.len() {
+        for (idx, _) in node.template.get().roots.iter().enumerate() {
             if let Some(dy) = node.dynamic_root(idx) {
                 self.remove_dynamic_node(dy, gen_muts);
             } else {
-                let id = node.root_ids[idx].get().unwrap();
+                let id = node.root_ids.get(idx).unwrap();
                 if gen_muts {
                     self.mutations.push(Mutation::Remove { id });
                 }
@@ -820,7 +832,15 @@ impl<'b> VirtualDom {
         let mut id = None;
         for (idx, attr) in node.dynamic_attrs.iter().enumerate() {
             // We'll clean up the root nodes either way, so don't worry
-            if node.template.attr_paths[idx].len() == 1 {
+            let path_len = node
+                .template
+                .get()
+                .attr_paths
+                .get(idx)
+                .map(|path| path.len());
+            // if the path is 1 the attribute is in the root, so we don't need to clean it up
+            // if the path is 0, the attribute is a not attached at all, so we don't need to clean it up
+            if let Some(..=1) = path_len {
                 continue;
             }
 
@@ -838,12 +858,16 @@ impl<'b> VirtualDom {
 
     fn remove_nested_dyn_nodes(&mut self, node: &VNode) {
         for (idx, dyn_node) in node.dynamic_nodes.iter().enumerate() {
-            // Roots are cleaned up automatically above
-            if node.template.node_paths[idx].len() == 1 {
-                continue;
+            let path_len = node
+                .template
+                .get()
+                .node_paths
+                .get(idx)
+                .map(|path| path.len());
+            // Roots are cleaned up automatically above and nodes with a empty path are placeholders
+            if let Some(2..) = path_len {
+                self.remove_dynamic_node(dyn_node, false)
             }
-
-            self.remove_dynamic_node(dyn_node, false);
         }
     }
 
@@ -903,7 +927,7 @@ impl<'b> VirtualDom {
 
     fn find_first_element(&self, node: &'b VNode<'b>) -> ElementId {
         match node.dynamic_root(0) {
-            None => node.root_ids[0].get().unwrap(),
+            None => node.root_ids.get(0).unwrap(),
             Some(Text(t)) => t.id.get().unwrap(),
             Some(Fragment(t)) => self.find_first_element(&t[0]),
             Some(Placeholder(t)) => t.id.get().unwrap(),
@@ -918,8 +942,8 @@ impl<'b> VirtualDom {
     }
 
     fn find_last_element(&self, node: &'b VNode<'b>) -> ElementId {
-        match node.dynamic_root(node.template.roots.len() - 1) {
-            None => node.root_ids.last().unwrap().get().unwrap(),
+        match node.dynamic_root(node.template.get().roots.len() - 1) {
+            None => node.root_ids.last().unwrap(),
             Some(Text(t)) => t.id.get().unwrap(),
             Some(Fragment(t)) => self.find_last_element(t.last().unwrap()),
             Some(Placeholder(t)) => t.id.get().unwrap(),
@@ -941,27 +965,30 @@ impl<'b> VirtualDom {
 /// We use the pointer of the dynamic_node list in this case
 fn templates_are_the_same<'b>(left_template: &'b VNode<'b>, right_template: &'b VNode<'b>) -> bool {
     std::ptr::eq(left_template, right_template)
-        || std::ptr::eq(left_template.dynamic_nodes, right_template.dynamic_nodes)
 }
 
 fn templates_are_different(left_template: &VNode, right_template: &VNode) -> bool {
-    !std::ptr::eq(left_template.template.name, right_template.template.name)
-        && left_template.template.name != right_template.template.name
+    let left_template_name = left_template.template.get().name;
+    let right_template_name = right_template.template.get().name;
+    // we want to re-create the node if the template name is different by pointer even if the value is the same so that we can detect when hot reloading changes the template
+    !std::ptr::eq(left_template_name, right_template_name)
 }
 
 fn matching_components<'a>(
     left: &'a VNode<'a>,
     right: &'a VNode<'a>,
 ) -> Option<Vec<(&'a VComponent<'a>, &'a VComponent<'a>)>> {
-    if left.template.roots.len() != right.template.roots.len() {
+    let left_template = left.template.get();
+    let right_template = right.template.get();
+    if left_template.roots.len() != right_template.roots.len() {
         return None;
     }
 
     // run through the components, ensuring they're the same
-    left.template
+    left_template
         .roots
         .iter()
-        .zip(right.template.roots.iter())
+        .zip(right_template.roots.iter())
         .map(|(l, r)| {
             let (l, r) = match (l, r) {
                 (TemplateNode::Dynamic { id: l }, TemplateNode::Dynamic { id: r }) => (l, r),
@@ -986,11 +1013,12 @@ fn matching_components<'a>(
 ///  - for appending children we can use AppendChildren
 #[allow(dead_code)]
 fn is_dyn_node_only_child(node: &VNode, idx: usize) -> bool {
-    let path = node.template.node_paths[idx];
+    let template = node.template.get();
+    let path = template.node_paths[idx];
 
     // use a loop to index every static node's children until the path has run out
     // only break if the last path index is a dynamic node
-    let mut static_node = &node.template.roots[path[0] as usize];
+    let mut static_node = &template.roots[path[0] as usize];
 
     for i in 1..path.len() - 1 {
         match static_node {

+ 2 - 2
packages/core/src/fragment.rs

@@ -31,8 +31,8 @@ pub fn Fragment<'a>(cx: Scope<'a, FragmentProps<'a>>) -> Element {
     Some(VNode {
         key: children.key,
         parent: children.parent,
-        template: children.template,
-        root_ids: children.root_ids,
+        template: children.template.clone(),
+        root_ids: children.root_ids.clone(),
         dynamic_nodes: children.dynamic_nodes,
         dynamic_attrs: children.dynamic_attrs,
     })

+ 169 - 11
packages/core/src/nodes.rs

@@ -5,7 +5,7 @@ use bumpalo::boxed::Box as BumpBox;
 use bumpalo::Bump;
 use std::{
     any::{Any, TypeId},
-    cell::{Cell, RefCell},
+    cell::{Cell, RefCell, UnsafeCell},
     fmt::Arguments,
     future::Future,
 };
@@ -54,11 +54,11 @@ pub struct VNode<'a> {
     pub parent: Option<ElementId>,
 
     /// The static nodes and static descriptor of the template
-    pub template: Template<'static>,
+    pub template: Cell<Template<'static>>,
 
     /// The IDs for the roots of this template - to be used when moving the template around and removing it from
     /// the actual Dom
-    pub root_ids: &'a [Cell<Option<ElementId>>],
+    pub root_ids: BoxedCellSlice,
 
     /// The dynamic parts of the template
     pub dynamic_nodes: &'a [DynamicNode<'a>],
@@ -67,21 +67,120 @@ pub struct VNode<'a> {
     pub dynamic_attrs: &'a [Attribute<'a>],
 }
 
+// Saftey: There is no way to get references to the internal data of this struct so no refrences will be invalidated by mutating the data with a immutable reference (The same principle behind Cell)
+#[derive(Debug, Default)]
+pub struct BoxedCellSlice(UnsafeCell<Option<Box<[ElementId]>>>);
+
+impl Clone for BoxedCellSlice {
+    fn clone(&self) -> Self {
+        Self(UnsafeCell::new(unsafe { (*self.0.get()).clone() }))
+    }
+}
+
+impl BoxedCellSlice {
+    pub fn last(&self) -> Option<ElementId> {
+        unsafe {
+            (*self.0.get())
+                .as_ref()
+                .and_then(|inner| inner.as_ref().last().copied())
+        }
+    }
+
+    pub fn get(&self, idx: usize) -> Option<ElementId> {
+        unsafe {
+            (*self.0.get())
+                .as_ref()
+                .and_then(|inner| inner.as_ref().get(idx).copied())
+        }
+    }
+
+    pub unsafe fn get_unchecked(&self, idx: usize) -> Option<ElementId> {
+        (*self.0.get())
+            .as_ref()
+            .and_then(|inner| inner.as_ref().get(idx).copied())
+    }
+
+    pub fn set(&self, idx: usize, new: ElementId) {
+        unsafe {
+            if let Some(inner) = &mut *self.0.get() {
+                inner[idx] = new;
+            }
+        }
+    }
+
+    pub fn intialize(&self, contents: Box<[ElementId]>) {
+        unsafe {
+            *self.0.get() = Some(contents);
+        }
+    }
+
+    pub fn transfer(&self, other: &Self) {
+        unsafe {
+            *self.0.get() = (*other.0.get()).clone();
+        }
+    }
+
+    pub fn take_from(&self, other: &Self) {
+        unsafe {
+            *self.0.get() = (*other.0.get()).take();
+        }
+    }
+
+    pub fn len(&self) -> usize {
+        unsafe {
+            (*self.0.get())
+                .as_ref()
+                .map(|inner| inner.len())
+                .unwrap_or(0)
+        }
+    }
+}
+
+impl<'a> IntoIterator for &'a BoxedCellSlice {
+    type Item = ElementId;
+
+    type IntoIter = BoxedCellSliceIter<'a>;
+
+    fn into_iter(self) -> Self::IntoIter {
+        BoxedCellSliceIter {
+            index: 0,
+            borrow: self,
+        }
+    }
+}
+
+pub struct BoxedCellSliceIter<'a> {
+    index: usize,
+    borrow: &'a BoxedCellSlice,
+}
+
+impl Iterator for BoxedCellSliceIter<'_> {
+    type Item = ElementId;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        let result = self.borrow.get(self.index);
+        if result.is_some() {
+            self.index += 1;
+        }
+        result
+    }
+}
+
 impl<'a> VNode<'a> {
     /// Create a template with no nodes that will be skipped over during diffing
     pub fn empty() -> Element<'a> {
         Some(VNode {
             key: None,
             parent: None,
-            root_ids: &[],
+            root_ids: BoxedCellSlice::default(),
             dynamic_nodes: &[],
             dynamic_attrs: &[],
-            template: Template {
+            template: Cell::new(Template {
                 name: "dioxus-empty",
                 roots: &[],
                 node_paths: &[],
                 attr_paths: &[],
-            },
+            }),
         })
     }
 
@@ -89,7 +188,7 @@ impl<'a> VNode<'a> {
     ///
     /// Returns [`None`] if the root is actually a static node (Element/Text)
     pub fn dynamic_root(&self, idx: usize) -> Option<&'a DynamicNode<'a>> {
-        match &self.template.roots[idx] {
+        match &self.template.get().roots[idx] {
             TemplateNode::Element { .. } | TemplateNode::Text { text: _ } => None,
             TemplateNode::Dynamic { id } | TemplateNode::DynamicText { id } => {
                 Some(&self.dynamic_nodes[*id])
@@ -114,32 +213,85 @@ impl<'a> VNode<'a> {
 ///
 /// 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))]
+#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
 #[derive(Debug, Clone, Copy, PartialEq, Hash, Eq, PartialOrd, Ord)]
 pub struct Template<'a> {
     /// 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: &'a 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.
+    #[cfg_attr(feature = "serialize", serde(deserialize_with = "deserialize_leaky"))]
     pub roots: &'a [TemplateNode<'a>],
 
     /// The paths of each node relative to the root of the template.
     ///
     /// These will be one segment shorter than the path sent to the renderer since those paths are relative to the
     /// topmost element, not the `roots` field.
+    #[cfg_attr(
+        feature = "serialize",
+        serde(deserialize_with = "deserialize_bytes_leaky")
+    )]
     pub node_paths: &'a [&'a [u8]],
 
     /// The paths of each dynamic attribute relative to the root of the template
     ///
     /// These will be one segment shorter than the path sent to the renderer since those paths are relative to the
     /// topmost element, not the `roots` field.
+    #[cfg_attr(
+        feature = "serialize",
+        serde(deserialize_with = "deserialize_bytes_leaky")
+    )]
     pub attr_paths: &'a [&'a [u8]],
 }
 
+#[cfg(feature = "serialize")]
+fn deserialize_string_leaky<'a, 'de, D>(deserializer: D) -> Result<&'a str, D::Error>
+where
+    D: serde::Deserializer<'de>,
+{
+    use serde::Deserialize;
+
+    let deserialized = String::deserialize(deserializer)?;
+    Ok(&*Box::leak(deserialized.into_boxed_str()))
+}
+
+#[cfg(feature = "serialize")]
+fn deserialize_bytes_leaky<'a, 'de, D>(deserializer: D) -> Result<&'a [&'a [u8]], D::Error>
+where
+    D: serde::Deserializer<'de>,
+{
+    use serde::Deserialize;
+
+    let deserialized = Vec::<Vec<u8>>::deserialize(deserializer)?;
+    let deserialized = deserialized
+        .into_iter()
+        .map(|v| &*Box::leak(v.into_boxed_slice()))
+        .collect::<Vec<_>>();
+    Ok(&*Box::leak(deserialized.into_boxed_slice()))
+}
+
+#[cfg(feature = "serialize")]
+fn deserialize_leaky<'a, 'de, T: serde::Deserialize<'de>, D>(
+    deserializer: D,
+) -> Result<&'a [T], D::Error>
+where
+    T: serde::Deserialize<'de>,
+    D: serde::Deserializer<'de>,
+{
+    use serde::Deserialize;
+
+    let deserialized = Box::<[T]>::deserialize(deserializer)?;
+    Ok(&*Box::leak(deserialized))
+}
+
 impl<'a> Template<'a> {
     /// Is this template worth caching at all, since it's completely runtime?
     ///
@@ -156,7 +308,11 @@ impl<'a> Template<'a> {
 ///
 /// This can be created at compile time, saving the VirtualDom time when diffing the tree
 #[derive(Debug, Clone, Copy, PartialEq, Hash, Eq, PartialOrd, Ord)]
-#[cfg_attr(feature = "serialize", derive(serde::Serialize), serde(tag = "type"))]
+#[cfg_attr(
+    feature = "serialize",
+    derive(serde::Serialize, serde::Deserialize),
+    serde(tag = "type")
+)]
 pub enum TemplateNode<'a> {
     /// An statically known element in the dom.
     ///
@@ -176,9 +332,11 @@ pub enum TemplateNode<'a> {
         /// A list of possibly dynamic attribues for this element
         ///
         /// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"`.
+        #[cfg_attr(feature = "serialize", serde(deserialize_with = "deserialize_leaky"))]
         attrs: &'a [TemplateAttribute<'a>],
 
         /// A list of template nodes that define another set of template nodes
+        #[cfg_attr(feature = "serialize", serde(deserialize_with = "deserialize_leaky"))]
         children: &'a [TemplateNode<'a>],
     },
 
@@ -527,8 +685,8 @@ impl<'a> IntoDynNode<'a> for &'a VNode<'a> {
     fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
         DynamicNode::Fragment(_cx.bump().alloc([VNode {
             parent: self.parent,
-            template: self.template,
-            root_ids: self.root_ids,
+            template: self.template.clone(),
+            root_ids: self.root_ids.clone(),
             key: self.key,
             dynamic_nodes: self.dynamic_nodes,
             dynamic_attrs: self.dynamic_attrs,

+ 28 - 2
packages/core/src/virtual_dom.rs

@@ -142,7 +142,8 @@ use std::{any::Any, borrow::BorrowMut, cell::Cell, collections::BTreeSet, future
 /// }
 /// ```
 pub struct VirtualDom {
-    pub(crate) templates: FxHashMap<TemplateId, Template<'static>>,
+    // Maps a template path to a map of byteindexes to templates
+    pub(crate) templates: FxHashMap<TemplateId, FxHashMap<usize, Template<'static>>>,
     pub(crate) scopes: Slab<Box<ScopeState>>,
     pub(crate) dirty_scopes: BTreeSet<DirtyScope>,
     pub(crate) scheduler: Rc<Scheduler>,
@@ -354,10 +355,11 @@ impl VirtualDom {
         while let Some(el_ref) = parent_path {
             // safety: we maintain references of all vnodes in the element slab
             let template = unsafe { &*el_ref.template };
+            let node_template = template.template.get();
             let target_path = el_ref.path;
 
             for (idx, attr) in template.dynamic_attrs.iter().enumerate() {
-                let this_path = template.template.attr_paths[idx];
+                let this_path = node_template.attr_paths[idx];
 
                 // listeners are required to be prefixed with "on", but they come back to the virtualdom with that missing
                 // we should fix this so that we look for "onclick" instead of "click"
@@ -454,6 +456,30 @@ impl VirtualDom {
         }
     }
 
+    /// Replace a template at runtime. This will re-render all components that use this template.
+    /// This is the primitive that enables hot-reloading.
+    ///
+    /// The caller must ensure that the template refrences the same dynamic attributes and nodes as the original template.
+    ///
+    /// This will only replace the the parent template, not any nested templates.
+    pub fn replace_template(&mut self, template: Template<'static>) {
+        self.register_template_first_byte_index(template);
+        // iterating a slab is very inefficient, but this is a rare operation that will only happen during development so it's fine
+        for (_, scope) in &self.scopes {
+            if let Some(RenderReturn::Sync(Some(sync))) = scope.try_root_node() {
+                if sync.template.get().name.rsplit_once(':').unwrap().0
+                    == template.name.rsplit_once(':').unwrap().0
+                {
+                    let height = scope.height;
+                    self.dirty_scopes.insert(DirtyScope {
+                        height,
+                        id: scope.id,
+                    });
+                }
+            }
+        }
+    }
+
     /// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom from scratch.
     ///
     /// The mutations item expects the RealDom's stack to be the root of the application.

+ 19 - 5
packages/desktop/src/controller.rs

@@ -1,7 +1,7 @@
 use crate::desktop_context::{DesktopContext, UserWindowEvent};
 use dioxus_core::*;
 use dioxus_html::HtmlEvent;
-use futures_channel::mpsc::{unbounded, UnboundedSender};
+use futures_channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender};
 use futures_util::StreamExt;
 #[cfg(target_os = "ios")]
 use objc::runtime::Object;
@@ -25,7 +25,10 @@ pub(super) struct DesktopController {
     pub(super) quit_app_on_close: bool,
     pub(super) is_ready: Arc<AtomicBool>,
     pub(super) proxy: EventLoopProxy<UserWindowEvent>,
+
     pub(super) event_tx: UnboundedSender<serde_json::Value>,
+    #[cfg(debug_assertions)]
+    pub(super) templates_tx: UnboundedSender<Template<'static>>,
 
     #[cfg(target_os = "ios")]
     pub(super) views: Vec<*mut Object>,
@@ -41,6 +44,7 @@ impl DesktopController {
     ) -> Self {
         let edit_queue = Arc::new(Mutex::new(Vec::new()));
         let (event_tx, mut event_rx) = unbounded();
+        let (templates_tx, mut templates_rx) = unbounded();
         let proxy2 = proxy.clone();
 
         let pending_edits = edit_queue.clone();
@@ -68,6 +72,18 @@ impl DesktopController {
 
                 loop {
                     tokio::select! {
+                        template = {
+                            #[allow(unused)]
+                            fn maybe_future<'a>(templates_rx: &'a mut UnboundedReceiver<Template<'static>>) -> impl Future<Output = dioxus_core::Template<'static>> + 'a {
+                                #[cfg(debug_assertions)]
+                                return templates_rx.select_next_some();
+                                #[cfg(not(debug_assertions))]
+                                return std::future::pending();
+                            }
+                            maybe_future(&mut templates_rx)
+                        } => {
+                            dom.replace_template(template);
+                        }
                         _ = dom.wait_for_work() => {}
                         Some(json_value) = event_rx.next() => {
                             if let Ok(value) = serde_json::from_value::<HtmlEvent>(json_value) {
@@ -103,6 +119,8 @@ impl DesktopController {
             quit_app_on_close: true,
             proxy: proxy2,
             event_tx,
+            #[cfg(debug_assertions)]
+            templates_tx,
             #[cfg(target_os = "ios")]
             views: vec![],
         }
@@ -133,8 +151,4 @@ impl DesktopController {
             }
         }
     }
-
-    pub(crate) fn set_template(&self, _serialized_template: String) {
-        todo!("hot reloading currently WIP")
-    }
 }

+ 0 - 6
packages/desktop/src/desktop_context.rs

@@ -179,11 +179,6 @@ pub enum UserWindowEvent {
     DragWindow,
     FocusWindow,
 
-    /// Set a new Dioxus template for hot-reloading
-    ///
-    /// Is a no-op in release builds. Must fit the right format for templates
-    SetTemplate(String),
-
     Visible(bool),
     Minimize(bool),
     Maximize(bool),
@@ -232,7 +227,6 @@ impl DesktopController {
 
         match user_event {
             Initialize | EditsReady => self.try_load_ready_webviews(),
-            SetTemplate(template) => self.set_template(template),
             CloseWindow => *control_flow = ControlFlow::Exit,
             DragWindow => {
                 // if the drag_window has any errors, we don't do anything

+ 5 - 7
packages/desktop/src/hot_reload.rs

@@ -1,6 +1,6 @@
 #![allow(dead_code)]
 
-use dioxus_core::VirtualDom;
+use dioxus_core::Template;
 
 use interprocess::local_socket::{LocalSocketListener, LocalSocketStream};
 use std::io::{BufRead, BufReader};
@@ -13,7 +13,7 @@ fn handle_error(connection: std::io::Result<LocalSocketStream>) -> Option<LocalS
         .ok()
 }
 
-pub(crate) fn init(_dom: &VirtualDom) {
+pub(crate) fn init(proxy: futures_channel::mpsc::UnboundedSender<Template<'static>>) {
     let latest_in_connection: Arc<Mutex<Option<BufReader<LocalSocketStream>>>> =
         Arc::new(Mutex::new(None));
 
@@ -36,11 +36,9 @@ pub(crate) fn init(_dom: &VirtualDom) {
                 let mut buf = String::new();
                 match conn.read_line(&mut buf) {
                     Ok(_) => {
-                        todo!()
-                        // let msg: SetTemplateMsg = serde_json::from_str(&buf).unwrap();
-                        // channel
-                        //     .start_send(SchedulerMsg::SetTemplate(Box::new(msg)))
-                        //     .unwrap();
+                        let msg: Template<'static> =
+                            serde_json::from_str(Box::leak(buf.into_boxed_str())).unwrap();
+                        proxy.unbounded_send(msg).unwrap();
                     }
                     Err(err) => {
                         if err.kind() != std::io::ErrorKind::WouldBlock {

+ 3 - 0
packages/desktop/src/lib.rs

@@ -106,6 +106,9 @@ pub fn launch_with_props<P: 'static + Send>(root: Component<P>, props: P, mut cf
     let event_loop = EventLoop::with_user_event();
     let mut desktop = DesktopController::new_on_tokio(root, props, event_loop.create_proxy());
 
+    #[cfg(debug_assertions)]
+    hot_reload::init(desktop.templates_tx.clone());
+
     event_loop.run(move |window_event, event_loop, control_flow| {
         *control_flow = ControlFlow::Wait;
 

+ 1 - 1
packages/dioxus/benches/jsframework.rs

@@ -42,7 +42,7 @@ fn create_rows(c: &mut Criterion) {
 
     c.bench_function("create rows", |b| {
         let mut dom = VirtualDom::new(app);
-        dom.rebuild();
+        let _ = dom.rebuild();
 
         b.iter(|| {
             let g = dom.rebuild();

+ 2 - 0
packages/html/Cargo.toml

@@ -12,6 +12,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
 
 [dependencies]
 dioxus-core = { path = "../core", version = "^0.2.1" }
+dioxus-rsx = { path = "../rsx", optional = true }
 serde = { version = "1", features = ["derive"], optional = true }
 serde_repr = { version = "0.1", optional = true }
 wasm-bindgen = { version = "0.2.79", optional = true }
@@ -47,3 +48,4 @@ serde_json = "*"
 default = ["serialize"]
 serialize = ["serde", "serde_repr", "euclid/serde", "keyboard-types/serde", "dioxus-core/serialize"]
 wasm-bind = ["web-sys", "wasm-bindgen"]
+hot-reload-context = ["dioxus-rsx"]

+ 669 - 506
packages/html/src/elements.rs

@@ -1,61 +1,258 @@
 #![allow(non_upper_case_globals)]
-
+#[cfg(feature = "hot-reload-context")]
+use crate::{map_global_attributes, map_svg_attributes};
 use crate::{GlobalAttributes, SvgAttributes};
+#[cfg(feature = "hot-reload-context")]
+use dioxus_rsx::HotReloadingContext;
 
 pub type AttributeDiscription = (&'static str, Option<&'static str>, bool);
 
+macro_rules! impl_attribute {
+    (
+        $(#[$attr_method:meta])*
+        $fil:ident: $vil:ident (DEFAULT),
+    ) => {
+        pub const $fil: AttributeDiscription = (stringify!($fil), None, false);
+    };
+
+    (
+        $(#[$attr_method:meta])*
+        $fil:ident: $vil:ident ($name:literal),
+    ) => {
+        pub const $fil: AttributeDiscription = ($name, None, false);
+    };
+
+    (
+        $(#[$attr_method:meta])*
+        $fil:ident: $vil:ident (volatile),
+    ) => {
+        pub const $fil: AttributeDiscription = (stringify!($fil), None, true);
+    };
+
+    (
+        $(#[$attr_method:meta])*
+        $fil:ident: $vil:ident (in $ns:ident),
+    ) => {
+        pub const $fil: AttributeDiscription = (stringify!($fil), Some(stringify!($ns)), false)
+    };
+
+    (
+        $(#[$attr_method:meta])*
+        $fil:ident: $vil:ident (in $ns:ident : volatile),
+    ) => {
+        pub const $fil: AttributeDiscription = (stringify!($fil), Some(stringify!($ns)), true)
+    };
+}
+
+#[cfg(feature = "hot-reload-context")]
+macro_rules! impl_attribute_match {
+    (
+        $attr:ident $fil:ident: $vil:ident (DEFAULT),
+    ) => {
+        if $attr == stringify!($fil) {
+            return Some((stringify!($fil), None));
+        }
+    };
+
+    (
+        $attr:ident $fil:ident: $vil:ident (volatile),
+    ) => {
+        if $attr == stringify!($fil) {
+            return Some((stringify!($fil), None));
+        }
+    };
+
+    (
+        $attr:ident $fil:ident: $vil:ident ($name:literal),
+    ) => {
+        if $attr == stringify!($fil) {
+            return Some(($name, None));
+        }
+    };
+
+    (
+        $attr:ident $fil:ident: $vil:ident (in $ns:ident),
+    ) => {
+        if $attr == stringify!($fil) {
+            return Some((stringify!(fil), Some(stringify!(ns))));
+        }
+    };
+}
+
+macro_rules! impl_element {
+    (
+        $(#[$attr:meta])*
+        $name:ident None {
+            $(
+                $(#[$attr_method:meta])*
+                $fil:ident: $vil:ident $extra:tt,
+            )*
+        }
+    ) => {
+        #[allow(non_camel_case_types)]
+        $(#[$attr])*
+        pub struct $name;
+
+        impl $name {
+            pub const TAG_NAME: &'static str = stringify!($name);
+            pub const NAME_SPACE: Option<&'static str> = None;
+
+            $(
+                impl_attribute!(
+                    $(#[$attr_method])*
+                    $fil: $vil ($extra),
+                );
+            )*
+        }
+
+        impl GlobalAttributes for $name {}
+    };
+
+    (
+        $(#[$attr:meta])*
+        $name:ident $namespace:tt {
+            $(
+                $(#[$attr_method:meta])*
+                $fil:ident: $vil:ident $extra:tt,
+            )*
+        }
+    ) => {
+        #[allow(non_camel_case_types)]
+        $(#[$attr])*
+        pub struct $name;
+
+        impl SvgAttributes for $name {}
+
+        impl $name {
+            pub const TAG_NAME: &'static str = stringify!($name);
+            pub const NAME_SPACE: Option<&'static str> = Some($namespace);
+
+            $(
+                impl_attribute!(
+                    $(#[$attr_method])*
+                    $fil: $vil in $namespace $extra
+                );
+            )*
+        }
+    }
+}
+
+#[cfg(feature = "hot-reload-context")]
+macro_rules! impl_element_match {
+    (
+        $el:ident $name:ident None {
+            $(
+                $fil:ident: $vil:ident $extra:tt,
+            )*
+        }
+    ) => {
+        if $el == stringify!($name) {
+            return Some((stringify!($name), None));
+        }
+    };
+
+    (
+        $el:ident $name:ident $namespace:tt {
+            $(
+                $fil:ident: $vil:ident $extra:tt,
+            )*
+        }
+    ) => {
+        if $el == stringify!($name) {
+            return Some((stringify!($name), Some(stringify!($namespace))));
+        }
+    };
+}
+
+#[cfg(feature = "hot-reload-context")]
+macro_rules! impl_element_match_attributes {
+    (
+        $el:ident $attr:ident $name:ident None {
+            $(
+                $fil:ident: $vil:ident $extra:tt,
+            )*
+        }
+    ) => {
+        if $el == stringify!($name) {
+            $(
+                impl_attribute_match!(
+                    $attr $fil: $vil ($extra),
+                );
+            )*
+        }
+    };
+
+    (
+        $el:ident $attr:ident $name:ident $namespace:tt {
+            $(
+                $fil:ident: $vil:ident $extra:tt,
+            )*
+        }
+    ) => {
+        if $el == stringify!($name) {
+            $(
+                impl_attribute_match!(
+                    $attr $fil: $vil in $namespace $extra
+                );
+            )*
+        }
+    }
+}
+
 macro_rules! builder_constructors {
     (
         $(
             $(#[$attr:meta])*
-            $name:ident {
+            $name:ident $namespace:tt {
                 $(
                     $(#[$attr_method:meta])*
-                    $fil:ident: $vil:ident,
+                    $fil:ident: $vil:ident $extra:tt,
                 )*
             };
          )*
-    ) => {
-        $(
-            #[allow(non_camel_case_types)]
-            $(#[$attr])*
-            pub struct $name;
-
-
-            impl $name {
-                pub const TAG_NAME: &'static str = stringify!($name);
-                pub const NAME_SPACE: Option<&'static str> = None;
+        ) => {
+        #[cfg(feature = "hot-reload-context")]
+        pub struct HtmlCtx;
 
+        #[cfg(feature = "hot-reload-context")]
+        impl HotReloadingContext for HtmlCtx {
+            fn map_attribute(element: &str, attribute: &str) -> Option<(&'static str, Option<&'static str>)> {
                 $(
-                    pub const $fil: AttributeDiscription = (stringify!($fil), None, false);
+                    impl_element_match_attributes!(
+                        element attribute $name $namespace {
+                            $(
+                                $fil: $vil $extra,
+                            )*
+                        }
+                    );
                 )*
+                map_global_attributes(attribute).or_else(|| map_svg_attributes(attribute))
             }
 
-            impl GlobalAttributes for $name {}
-        )*
-    };
-
-    ( $(
-        $(#[$attr:meta])*
-        $name:ident <> $namespace:tt {
-            $($fil:ident: $vil:ident,)*
-        };
-    )* ) => {
-        $(
-            #[allow(non_camel_case_types)]
-            $(#[$attr])*
-            pub struct $name;
-
-            impl SvgAttributes for $name {}
-
-            impl $name {
-                pub const TAG_NAME: &'static str = stringify!($name);
-                pub const NAME_SPACE: Option<&'static str> = Some($namespace);
-
+            fn map_element(element: &str) -> Option<(&'static str, Option<&'static str>)> {
                 $(
-                    pub const $fil: AttributeDiscription = (stringify!($fil), Some(stringify!($namespace)), false)
+                    impl_element_match!(
+                        element $name $namespace {
+                            $(
+                                $fil: $vil $extra,
+                            )*
+                        }
+                    );
                 )*
+                None
             }
+        }
+
+        $(
+            impl_element!(
+                $(#[$attr])*
+                $name $namespace {
+                    $(
+                        $(#[$attr_method])*
+                        $fil: $vil $extra,
+                    )*
+                }
+            );
         )*
     };
 }
@@ -75,63 +272,63 @@ builder_constructors! {
     /// [`<base>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base)
     /// element.
     ///
-    base {
-        href: Uri,
-        target: Target,
+    base None {
+        href: Uri DEFAULT,
+        target: Target DEFAULT,
     };
 
     /// Build a
     /// [`<head>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head)
     /// element.
-    head {};
+    head None {};
 
     /// Build a
     /// [`<link>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link)
     /// element.
-    link {
+    link None {
         // as: Mime,
-        crossorigin: CrossOrigin,
-        href: Uri,
-        hreflang: LanguageTag,
-        media: String, // FIXME media query
-        rel: LinkType,
-        sizes: String, // FIXME
-        title: String, // FIXME
-        r#type: Mime,
-        integrity: String,
+        crossorigin: CrossOrigin DEFAULT,
+        href: Uri DEFAULT,
+        hreflang: LanguageTag DEFAULT,
+        media: String DEFAULT, // FIXME media query
+        rel: LinkType DEFAULT,
+        sizes: String DEFAULT, // FIXME
+        title: String DEFAULT, // FIXME
+        r#type: Mime DEFAULT,
+        integrity: String DEFAULT,
     };
 
     /// Build a
     /// [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta)
     /// element.
-    meta {
-        charset: String, // FIXME IANA standard names
-        content: String,
-        http_equiv: HTTPEquiv,
-        name: Metadata,
+    meta None {
+        charset: String DEFAULT, // FIXME IANA standard names
+        content: String DEFAULT,
+        http_equiv: HTTPEquiv DEFAULT,
+        name: Metadata DEFAULT,
     };
 
     /// Build a
     /// [`<style>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/style)
     /// element.
-    style {
-        r#type: Mime,
-        media: String, // FIXME media query
-        nonce: Nonce,
-        title: String, // FIXME
+    style None {
+        r#type: Mime DEFAULT,
+        media: String DEFAULT, // FIXME media query
+        nonce: Nonce DEFAULT,
+        title: String DEFAULT, // FIXME
     };
 
     /// Build a
     /// [`<title>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/title)
     /// element.
-    title { };
+    title None { };
 
     // Sectioning root
 
     /// Build a
     /// [`<body>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/body)
     /// element.
-    body {};
+    body None {};
 
     // ------------------
     // Content sectioning
@@ -140,27 +337,27 @@ builder_constructors! {
     /// Build a
     /// [`<address>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/address)
     /// element.
-    address {};
+    address None {};
 
     /// Build a
     /// [`<article>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/article)
     /// element.
-    article {};
+    article None {};
 
     /// Build a
     /// [`<aside>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/aside)
     /// element.
-    aside {};
+    aside None {};
 
     /// Build a
     /// [`<footer>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/footer)
     /// element.
-    footer {};
+    footer None {};
 
     /// Build a
     /// [`<header>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/header)
     /// element.
-    header {};
+    header None {};
 
     /// Build a
     /// [`<h1>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h1)
@@ -180,7 +377,7 @@ builder_constructors! {
     /// rsx!(h1 { "A header element" })
     /// LazyNodes::new(|f| f.el(h1).children([f.text("A header element")]).finish())
     /// ```
-    h1 {};
+    h1 None {};
 
     /// Build a
     /// [`<h2>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h2)
@@ -199,7 +396,7 @@ builder_constructors! {
     /// rsx!(h2 { "A header element" })
     /// LazyNodes::new(|f| f.el(h2).children([f.text("A header element")]).finish())
     /// ```
-    h2 {};
+    h2 None {};
 
     /// Build a
     /// [`<h3>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h3)
@@ -211,50 +408,50 @@ builder_constructors! {
     /// - The most important heading is <h1> and the least important heading is <h6>.
     /// - The <h1> heading is the first heading in the document.
     /// - The <h1> heading is usually a large bolded font.
-    h3 {};
+    h3 None {};
 
     /// Build a
     /// [`<h4>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h4)
     /// element.
-    h4 {};
+    h4 None {};
 
     /// Build a
     /// [`<h5>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h5)
     /// element.
-    h5 {};
+    h5 None {};
 
     /// Build a
     /// [`<h6>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h6)
     /// element.
-    h6 {};
+    h6 None {};
 
     /// Build a
     /// [`<main>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/main)
     /// element.
-    main {};
+    main None {};
 
     /// Build a
     /// [`<nav>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/nav)
     /// element.
-    nav {};
+    nav None {};
 
     /// Build a
     /// [`<section>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/section)
     /// element.
-    section {};
+    section None {};
 
     // Text content
 
     /// Build a
     /// [`<blockquote>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/blockquote)
     /// element.
-    blockquote {
-        cite: Uri,
+    blockquote None {
+        cite: Uri DEFAULT,
     };
     /// Build a
     /// [`<dd>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dd)
     /// element.
-    dd {};
+    dd None {};
 
     /// Build a
     /// [`<div>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div)
@@ -280,63 +477,63 @@ builder_constructors! {
     /// ## References:
     /// - <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div>
     /// - <https://www.w3schools.com/tags/tag_div.asp>
-    div {};
+    div None {};
 
     /// Build a
     /// [`<dl>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dl)
     /// element.
-    dl {};
+    dl None {};
 
     /// Build a
     /// [`<dt>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dt)
     /// element.
-    dt {};
+    dt None {};
 
     /// Build a
     /// [`<figcaption>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/figcaption)
     /// element.
-    figcaption {};
+    figcaption None {};
 
     /// Build a
     /// [`<figure>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/figure)
     /// element.
-    figure {};
+    figure None {};
 
     /// Build a
     /// [`<hr>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/hr)
     /// element.
-    hr {};
+    hr None {};
 
     /// Build a
     /// [`<li>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/li)
     /// element.
-    li {
-        value: isize,
+    li None {
+        value: isize DEFAULT,
     };
 
     /// Build a
     /// [`<ol>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ol)
     /// element.
-    ol {
-        reversed: Bool,
-        start: isize,
-        r#type: OrderedListType,
+    ol None {
+        reversed: Bool DEFAULT,
+        start: isize DEFAULT,
+        r#type: OrderedListType DEFAULT,
     };
 
     /// Build a
     /// [`<p>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/p)
     /// element.
-    p {};
+    p None {};
 
     /// Build a
     /// [`<pre>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/pre)
     /// element.
-    pre {};
+    pre None {};
 
     /// Build a
     /// [`<ul>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ul)
     /// element.
-    ul {};
+    ul None {};
 
 
     // Inline text semantics
@@ -344,173 +541,173 @@ builder_constructors! {
     /// Build a
     /// [`<a>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a)
     /// element.
-    a {
-        download: String,
-        href: Uri,
-        hreflang: LanguageTag,
-        target: Target,
-        r#type: Mime,
+    a None {
+        download: String DEFAULT,
+        href: Uri DEFAULT,
+        hreflang: LanguageTag DEFAULT,
+        target: Target DEFAULT,
+        r#type: Mime DEFAULT,
         // ping: SpacedList<Uri>,
         // rel: SpacedList<LinkType>,
-        ping: SpacedList,
-        rel: SpacedList,
+        ping: SpacedList DEFAULT,
+        rel: SpacedList DEFAULT,
     };
 
     /// Build a
     /// [`<abbr>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/abbr)
     /// element.
-    abbr {};
+    abbr None {};
 
     /// Build a
     /// [`<b>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/b)
     /// element.
-    b {};
+    b None {};
 
     /// Build a
     /// [`<bdi>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/bdi)
     /// element.
-    bdi {};
+    bdi None {};
 
     /// Build a
     /// [`<bdo>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/bdo)
     /// element.
-    bdo {};
+    bdo None {};
 
     /// Build a
     /// [`<br>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/br)
     /// element.
-    br {};
+    br None {};
 
     /// Build a
     /// [`<cite>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/cite)
     /// element.
-    cite {};
+    cite None {};
 
     /// Build a
     /// [`<code>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/code)
     /// element.
-    code {
-        language: String,
+    code None {
+        language: String DEFAULT,
     };
 
     /// Build a
     /// [`<data>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/data)
     /// element.
-    data {
-        value: String,
+    data None {
+        value: String DEFAULT,
     };
 
     /// Build a
     /// [`<dfn>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dfn)
     /// element.
-    dfn {};
+    dfn None {};
 
     /// Build a
     /// [`<em>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/em)
     /// element.
-    em {};
+    em None {};
 
     /// Build a
     /// [`<i>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/i)
     /// element.
-    i {};
+    i None {};
 
     /// Build a
     /// [`<kbd>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/kbd)
     /// element.
-    kbd {};
+    kbd None {};
 
     /// Build a
     /// [`<mark>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/mark)
     /// element.
-    mark {};
+    mark None {};
 
     /// Build a
     /// [`<menu>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/menu)
     /// element.
-    menu {};
+    menu None {};
 
     /// Build a
     /// [`<q>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/q)
     /// element.
-    q {
-        cite: Uri,
+    q None {
+        cite: Uri DEFAULT,
     };
 
 
     /// Build a
     /// [`<rp>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/rp)
     /// element.
-    rp {};
+    rp None {};
 
 
     /// Build a
     /// [`<rt>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/rt)
     /// element.
-    rt {};
+    rt None {};
 
 
     /// Build a
     /// [`<ruby>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ruby)
     /// element.
-    ruby {};
+    ruby None {};
 
     /// Build a
     /// [`<s>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/s)
     /// element.
-    s {};
+    s None {};
 
     /// Build a
     /// [`<samp>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/samp)
     /// element.
-    samp {};
+    samp None {};
 
     /// Build a
     /// [`<small>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/small)
     /// element.
-    small {};
+    small None {};
 
     /// Build a
     /// [`<span>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/span)
     /// element.
-    span {};
+    span None {};
 
     /// Build a
     /// [`<strong>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/strong)
     /// element.
-    strong {};
+    strong None {};
 
     /// Build a
     /// [`<sub>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/sub)
     /// element.
-    sub {};
+    sub None {};
 
     /// Build a
     /// [`<sup>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/sup)
     /// element.
-    sup {};
+    sup None {};
 
     /// Build a
     /// [`<time>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/time)
     /// element.
-    time {
-        datetime: Datetime,
+    time None {
+        datetime: Datetime DEFAULT,
     };
 
     /// Build a
     /// [`<u>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/u)
     /// element.
-    u {};
+    u None {};
 
     /// Build a
     /// [`<var>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/var)
     /// element.
-    var {};
+    var None {};
 
     /// Build a
     /// [`<wbr>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/wbr)
     /// element.
-    wbr {};
+    wbr None {};
 
 
     // Image and multimedia
@@ -518,14 +715,14 @@ builder_constructors! {
     /// Build a
     /// [`<area>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/area)
     /// element.
-    area {
-        alt: String,
-        coords: String, // TODO could perhaps be validated
-        download: Bool,
-        href: Uri,
-        hreflang: LanguageTag,
-        shape: AreaShape,
-        target: Target,
+    area None {
+        alt: String DEFAULT,
+        coords: String DEFAULT, // TODO could perhaps be validated
+        download: Bool DEFAULT,
+        href: Uri DEFAULT,
+        hreflang: LanguageTag DEFAULT,
+        shape: AreaShape DEFAULT,
+        target: Target DEFAULT,
         // ping: SpacedList<Uri>,
         // rel: SpacedSet<LinkType>,
     };
@@ -533,66 +730,66 @@ builder_constructors! {
     /// Build a
     /// [`<audio>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/audio)
     /// element.
-    audio {
-        autoplay: Bool,
-        controls: Bool,
-        crossorigin: CrossOrigin,
-        muted: Bool,
-        preload: Preload,
-        src: Uri,
-        r#loop: Bool,
+    audio None {
+        autoplay: Bool DEFAULT,
+        controls: Bool DEFAULT,
+        crossorigin: CrossOrigin DEFAULT,
+        muted: Bool DEFAULT,
+        preload: Preload DEFAULT,
+        src: Uri DEFAULT,
+        r#loop: Bool DEFAULT,
     };
 
     /// Build a
     /// [`<img>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img)
     /// element.
-    img {
-        alt: String,
-        crossorigin: CrossOrigin,
-        decoding: ImageDecoding,
-        height: usize,
-        ismap: Bool,
-        src: Uri,
-        srcset: String, // FIXME this is much more complicated
-        usemap: String, // FIXME should be a fragment starting with '#'
-        width: usize,
-        referrerpolicy: String,
+    img None {
+        alt: String DEFAULT,
+        crossorigin: CrossOrigin DEFAULT,
+        decoding: ImageDecoding DEFAULT,
+        height: usize DEFAULT,
+        ismap: Bool DEFAULT,
+        src: Uri DEFAULT,
+        srcset: String DEFAULT, // FIXME this is much more complicated
+        usemap: String DEFAULT, // FIXME should be a fragment starting with '#'
+        width: usize DEFAULT,
+        referrerpolicy: String DEFAULT,
         // sizes: SpacedList<String>, // FIXME it's not really just a string
     };
 
     /// Build a
     /// [`<map>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/map)
     /// element.
-    map {
-        name: Id,
+    map None {
+        name: Id DEFAULT,
     };
 
     /// Build a
     /// [`<track>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/track)
     /// element.
-    track {
-        default: Bool,
-        kind: VideoKind,
-        label: String,
-        src: Uri,
-        srclang: LanguageTag,
+    track None {
+        default: Bool DEFAULT,
+        kind: VideoKind DEFAULT,
+        label: String DEFAULT,
+        src: Uri DEFAULT,
+        srclang: LanguageTag DEFAULT,
     };
 
     /// Build a
     /// [`<video>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video)
     /// element.
-    video {
-        autoplay: Bool,
-        controls: Bool,
-        crossorigin: CrossOrigin,
-        height: usize,
-        r#loop: Bool,
-        muted: Bool,
-        preload: Preload,
-        playsinline: Bool,
-        poster: Uri,
-        src: Uri,
-        width: usize,
+    video None {
+        autoplay: Bool DEFAULT,
+        controls: Bool DEFAULT,
+        crossorigin: CrossOrigin DEFAULT,
+        height: usize DEFAULT,
+        r#loop: Bool DEFAULT,
+        muted: Bool DEFAULT,
+        preload: Preload DEFAULT,
+        playsinline: Bool DEFAULT,
+        poster: Uri DEFAULT,
+        src: Uri DEFAULT,
+        width: usize DEFAULT,
     };
 
 
@@ -601,70 +798,70 @@ builder_constructors! {
     /// Build a
     /// [`<embed>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/embed)
     /// element.
-    embed {
-        height: usize,
-        src: Uri,
-        r#type: Mime,
-        width: usize,
+    embed None {
+        height: usize DEFAULT,
+        src: Uri DEFAULT,
+        r#type: Mime DEFAULT,
+        width: usize DEFAULT,
     };
 
     /// Build a
     /// [`<iframe>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe)
     /// element.
-    iframe {
-        allow: FeaturePolicy,
-        allowfullscreen: Bool,
-        allowpaymentrequest: Bool,
-        height: usize,
-        name: Id,
-        referrerpolicy: ReferrerPolicy,
-        src: Uri,
-        srcdoc: Uri,
-        width: usize,
-
-        marginWidth: String,
-        align: String,
-        longdesc: String,
-
-        scrolling: String,
-        marginHeight: String,
-        frameBorder: String,
+    iframe None {
+        allow: FeaturePolicy DEFAULT,
+        allowfullscreen: Bool DEFAULT,
+        allowpaymentrequest: Bool DEFAULT,
+        height: usize DEFAULT,
+        name: Id DEFAULT,
+        referrerpolicy: ReferrerPolicy DEFAULT,
+        src: Uri DEFAULT,
+        srcdoc: Uri DEFAULT,
+        width: usize DEFAULT,
+
+        marginWidth: String DEFAULT,
+        align: String DEFAULT,
+        longdesc: String DEFAULT,
+
+        scrolling: String DEFAULT,
+        marginHeight: String DEFAULT,
+        frameBorder: String DEFAULT,
         // sandbox: SpacedSet<Sandbox>,
     };
 
     /// Build a
     /// [`<object>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/object)
     /// element.
-    object {
-        data: Uri,
-        form: Id,
-        height: usize,
-        name: Id,
-        r#type: Mime,
-        typemustmatch: Bool,
-        usemap: String, // TODO should be a fragment starting with '#'
-        width: usize,
+    object None {
+        data: Uri DEFAULT,
+        form: Id DEFAULT,
+        height: usize DEFAULT,
+        name: Id DEFAULT,
+        r#type: Mime DEFAULT,
+        typemustmatch: Bool DEFAULT,
+        usemap: String DEFAULT, // TODO should be a fragment starting with '#'
+        width: usize DEFAULT,
     };
 
     /// Build a
     /// [`<param>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/param)
     /// element.
-    param {
-        name: String,
-        value: String,
+    param None {
+        name: String DEFAULT,
+        value: String DEFAULT,
     };
 
     /// Build a
     /// [`<picture>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture)
     /// element.
-    picture {};
+    picture None {};
 
     /// Build a
     /// [`<source>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/source)
     /// element.
-    source {
-        src: Uri,
-        r#type: Mime,
+    source None {
+        src: Uri DEFAULT,
+        r#type: Mime DEFAULT,
     };
 
 
@@ -673,15 +870,15 @@ builder_constructors! {
     /// Build a
     /// [`<canvas>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas)
     /// element.
-    canvas {
-        height: usize,
-        width: usize,
+    canvas None {
+        height: usize DEFAULT,
+        width: usize DEFAULT,
     };
 
     /// Build a
     /// [`<noscript>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/noscript)
     /// element.
-    noscript {};
+    noscript None {};
 
     /// Build a
     /// [`<script>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script)
@@ -690,11 +887,11 @@ builder_constructors! {
     /// The [`script`] HTML element is used to embed executable code or data; this is typically used to embed or refer to
     /// JavaScript code. The [`script`] element can also be used with other languages, such as WebGL's GLSL shader
     /// programming language and JSON.
-    script {
+    script None {
         /// Normal script elements pass minimal information to the window.onerror for scripts which do not pass the
         /// standard CORS checks. To allow error logging for sites which use a separate domain for static media, use
         /// this attribute. See CORS settings attributes for a more descriptive explanation of its valid arguments.
-        crossorigin: CrossOrigin,
+        crossorigin: CrossOrigin DEFAULT,
 
         /// This Boolean attribute is set to indicate to a browser that the script is meant to be executed after the
         /// document has been parsed, but before firing DOMContentLoaded.
@@ -715,13 +912,17 @@ builder_constructors! {
         ///
         /// This attribute allows the elimination of parser-blocking JavaScript where the browser would have to load and
         /// evaluate scripts before continuing to parse. async has a similar effect in this case.
-        defer: Bool,
-        integrity: Integrity,
-        nomodule: Bool,
-        nonce: Nonce,
-        src: Uri,
-        text: String,
-
+        defer: Bool DEFAULT,
+        integrity: Integrity DEFAULT,
+        nomodule: Bool DEFAULT,
+        nonce: Nonce DEFAULT,
+        src: Uri DEFAULT,
+        text: String DEFAULT,
+
+        // r#async: Bool,
+        // r#type: String, // TODO could be an enum
+        r#type: String "type",
+        r#script: String "script",
     };
 
 
@@ -730,17 +931,17 @@ builder_constructors! {
     /// Build a
     /// [`<del>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/del)
     /// element.
-    del {
-        cite: Uri,
-        datetime: Datetime,
+    del None {
+        cite: Uri DEFAULT,
+        datetime: Datetime DEFAULT,
     };
 
     /// Build a
     /// [`<ins>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ins)
     /// element.
-    ins {
-        cite: Uri,
-        datetime: Datetime,
+    ins None {
+        cite: Uri DEFAULT,
+        datetime: Datetime DEFAULT,
     };
 
 
@@ -749,66 +950,66 @@ builder_constructors! {
     /// Build a
     /// [`<caption>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/caption)
     /// element.
-    caption {};
+    caption None {};
 
     /// Build a
     /// [`<col>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/col)
     /// element.
-    col {
-        span: usize,
+    col None {
+        span: usize DEFAULT,
     };
 
     /// Build a
     /// [`<colgroup>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/colgroup)
     /// element.
-    colgroup {
-        span: usize,
+    colgroup None {
+        span: usize DEFAULT,
     };
 
     /// Build a
     /// [`<table>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/table)
     /// element.
-    table {};
+    table None {};
 
     /// Build a
     /// [`<tbody>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tbody)
     /// element.
-    tbody {};
+    tbody None {};
 
     /// Build a
     /// [`<td>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/td)
     /// element.
-    td {
-        colspan: usize,
-        rowspan: usize,
+    td None {
+        colspan: usize DEFAULT,
+        rowspan: usize DEFAULT,
         // headers: SpacedSet<Id>,
     };
 
     /// Build a
     /// [`<tfoot>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tfoot)
     /// element.
-    tfoot {};
+    tfoot None {};
 
     /// Build a
     /// [`<th>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/th)
     /// element.
-    th {
-        abbr: String,
-        colspan: usize,
-        rowspan: usize,
-        scope: TableHeaderScope,
+    th None {
+        abbr: String DEFAULT,
+        colspan: usize DEFAULT,
+        rowspan: usize DEFAULT,
+        scope: TableHeaderScope DEFAULT,
         // headers: SpacedSet<Id>,
     };
 
     /// Build a
     /// [`<thead>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/thead)
     /// element.
-    thead {};
+    thead None {};
 
     /// Build a
     /// [`<tr>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tr)
     /// element.
-    tr {};
+    tr None {};
 
 
     // Forms
@@ -816,183 +1017,212 @@ builder_constructors! {
     /// Build a
     /// [`<button>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button)
     /// element.
-    button {
-        autofocus: Bool,
-        disabled: Bool,
-        form: Id,
-        formaction: Uri,
-        formenctype: FormEncodingType,
-        formmethod: FormMethod,
-        formnovalidate: Bool,
-        formtarget: Target,
-        name: Id,
-        value: String,
+    button None {
+        autofocus: Bool DEFAULT,
+        disabled: Bool DEFAULT,
+        form: Id DEFAULT,
+        formaction: Uri DEFAULT,
+        formenctype: FormEncodingType DEFAULT,
+        formmethod: FormMethod DEFAULT,
+        formnovalidate: Bool DEFAULT,
+        formtarget: Target DEFAULT,
+        name: Id DEFAULT,
+        value: String DEFAULT,
+        r#type: String "type",
     };
 
     /// Build a
     /// [`<datalist>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/datalist)
     /// element.
-    datalist {};
+    datalist None {};
 
     /// Build a
     /// [`<fieldset>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/fieldset)
     /// element.
-    fieldset {};
+    fieldset None {};
 
     /// Build a
     /// [`<form>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form)
     /// element.
-    form {
+    form None {
         // accept-charset: SpacedList<CharacterEncoding>,
-        action: Uri,
-        autocomplete: OnOff,
-        enctype: FormEncodingType,
-        method: FormMethod,
-        name: Id,
-        novalidate: Bool,
-        target: Target,
+        action: Uri DEFAULT,
+        autocomplete: OnOff DEFAULT,
+        enctype: FormEncodingType DEFAULT,
+        method: FormMethod DEFAULT,
+        name: Id DEFAULT,
+        novalidate: Bool DEFAULT,
+        target: Target DEFAULT,
     };
 
     /// Build a
     /// [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input)
     /// element.
-    input {
-        accept: String,
-        alt: String,
-        autocomplete: String,
-        autofocus: Bool,
-        capture: String,
-        checked: Bool,
-        disabled: Bool,
-        form: Id,
-        formaction: Uri,
-        formenctype: FormEncodingType,
-        formmethod: FormDialogMethod,
-        formnovalidate: Bool,
-        formtarget: Target,
-        height: isize,
-        list: Id,
-        max: String,
-        maxlength: usize,
-        min: String,
-        minlength: usize,
-        multiple: Bool,
-        name: Id,
-        pattern: String,
-        placeholder: String,
-        readonly: Bool,
-        required: Bool,
-        size: usize,
-        spellcheck: Bool,
-        src: Uri,
-        step: String,
-        tabindex: usize,
-        width: isize,
-
-        // Manual implementations below...
-        // r#type: InputType,
+    input None {
+        accept: String DEFAULT,
+        alt: String DEFAULT,
+        autocomplete: String DEFAULT,
+        autofocus: Bool DEFAULT,
+        capture: String DEFAULT,
+        checked: Bool DEFAULT,
+        disabled: Bool DEFAULT,
+        form: Id DEFAULT,
+        formaction: Uri DEFAULT,
+        formenctype: FormEncodingType DEFAULT,
+        formmethod: FormDialogMethod DEFAULT,
+        formnovalidate: Bool DEFAULT,
+        formtarget: Target DEFAULT,
+        height: isize DEFAULT,
+        list: Id DEFAULT,
+        max: String DEFAULT,
+        maxlength: usize DEFAULT,
+        min: String DEFAULT,
+        minlength: usize DEFAULT,
+        multiple: Bool DEFAULT,
+        name: Id DEFAULT,
+        pattern: String DEFAULT,
+        placeholder: String DEFAULT,
+        readonly: Bool DEFAULT,
+        required: Bool DEFAULT,
+        size: usize DEFAULT,
+        spellcheck: Bool DEFAULT,
+        src: Uri DEFAULT,
+        step: String DEFAULT,
+        tabindex: usize DEFAULT,
+        width: isize DEFAULT,
+
+        /// The type of input
+        ///
+        /// Here are the different input types you can use in HTML:
+        ///
+        /// - `button`
+        /// - `checkbox`
+        /// - `color`
+        /// - `date`
+        /// - `datetime-local`
+        /// - `email`
+        /// - `file`
+        /// - `hidden`
+        /// - `image`
+        /// - `month`
+        /// - `number`
+        /// - `password`
+        /// - `radio`
+        /// - `range`
+        /// - `reset`
+        /// - `search`
+        /// - `submit`
+        /// - `tel`
+        /// - `text`
+        /// - `time`
+        /// - `url`
+        /// - `week`
+
+        r#type: InputType "type",
         // value: String,
+        value: String volatile,
     };
 
     /// Build a
     /// [`<label>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label)
     /// element.
-    label {
-        form: Id,
-        // r#for: Id,
+    label None {
+        form: Id DEFAULT,
+        r#for: Id "for",
     };
 
     /// Build a
     /// [`<legend>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/legend)
     /// element.
-    legend {};
+    legend None {};
 
     /// Build a
     /// [`<meter>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meter)
     /// element.
-    meter {
-        value: isize,
-        min: isize,
-        max: isize,
-        low: isize,
-        high: isize,
-        optimum: isize,
-        form: Id,
+    meter None {
+        value: isize DEFAULT,
+        min: isize DEFAULT,
+        max: isize DEFAULT,
+        low: isize DEFAULT,
+        high: isize DEFAULT,
+        optimum: isize DEFAULT,
+        form: Id DEFAULT,
     };
 
     /// Build a
     /// [`<optgroup>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/optgroup)
     /// element.
-    optgroup {
-        disabled: Bool,
-        label: String,
+    optgroup None {
+        disabled: Bool DEFAULT,
+        label: String DEFAULT,
     };
 
     /// Build a
     /// [`<option>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option)
     /// element.
-    option {
-        disabled: Bool,
-        label: String,
+    option None {
+        disabled: Bool DEFAULT,
+        label: String DEFAULT,
 
 
-        value: String,
+        value: String DEFAULT,
 
-        // defined below
-        // selected: Bool,
+        selected: Bool volatile,
     };
 
     /// Build a
     /// [`<output>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/output)
     /// element.
-    output {
-        form: Id,
-        name: Id,
+    output None {
+        form: Id DEFAULT,
+        name: Id DEFAULT,
         // r#for: SpacedSet<Id>,
     };
 
     /// Build a
     /// [`<progress>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/progress)
     /// element.
-    progress {
-        max: f64,
-        value: f64,
+    progress None {
+        max: f64 DEFAULT,
+        value: f64 DEFAULT,
     };
 
     /// Build a
     /// [`<select>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select)
     /// element.
-    select {
+    select None {
         // defined below
         // value: String,
-        autocomplete: String,
-        autofocus: Bool,
-        disabled: Bool,
-        form: Id,
-        multiple: Bool,
-        name: Id,
-        required: Bool,
-        size: usize,
+        autocomplete: String DEFAULT,
+        autofocus: Bool DEFAULT,
+        disabled: Bool DEFAULT,
+        form: Id DEFAULT,
+        multiple: Bool DEFAULT,
+        name: Id DEFAULT,
+        required: Bool DEFAULT,
+        size: usize DEFAULT,
+        value: String volatile,
     };
 
     /// Build a
     /// [`<textarea>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea)
     /// element.
-    textarea {
-        autocomplete: OnOff,
-        autofocus: Bool,
-        cols: usize,
-        disabled: Bool,
-        form: Id,
-        maxlength: usize,
-        minlength: usize,
-        name: Id,
-        placeholder: String,
-        readonly: Bool,
-        required: Bool,
-        rows: usize,
-        spellcheck: BoolOrDefault,
-        wrap: Wrap,
+    textarea None {
+        autocomplete: OnOff DEFAULT,
+        autofocus: Bool DEFAULT,
+        cols: usize DEFAULT,
+        disabled: Bool DEFAULT,
+        form: Id DEFAULT,
+        maxlength: usize DEFAULT,
+        minlength: usize DEFAULT,
+        name: Id DEFAULT,
+        placeholder: String DEFAULT,
+        readonly: Bool DEFAULT,
+        required: Bool DEFAULT,
+        rows: usize DEFAULT,
+        spellcheck: BoolOrDefault DEFAULT,
+        wrap: Wrap DEFAULT,
+        value: Strign volatile,
     };
 
 
@@ -1001,437 +1231,370 @@ builder_constructors! {
     /// Build a
     /// [`<details>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details)
     /// element.
-    details {
-        open: Bool,
+    details None {
+        open: Bool DEFAULT,
     };
 
     /// Build dialog
     /// [`<dialog>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog)
     /// element.
-    dialog {
-        open: Bool,
+    dialog None {
+        open: Bool DEFAULT,
     };
 
     /// Build a
     /// [`<summary>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/summary)
     /// element.
-    summary {};
+    summary None {};
 
     // Web components
 
     /// Build a
     /// [`<slot>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot)
     /// element.
-    slot {};
+    slot None {};
 
     /// Build a
     /// [`<template>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template)
     /// element.
-    template {};
-}
-
-impl input {
-    /// The type of input
-    ///
-    /// Here are the different input types you can use in HTML:
-    ///
-    /// - `button`
-    /// - `checkbox`
-    /// - `color`
-    /// - `date`
-    /// - `datetime-local`
-    /// - `email`
-    /// - `file`
-    /// - `hidden`
-    /// - `image`
-    /// - `month`
-    /// - `number`
-    /// - `password`
-    /// - `radio`
-    /// - `range`
-    /// - `reset`
-    /// - `search`
-    /// - `submit`
-    /// - `tel`
-    /// - `text`
-    /// - `time`
-    /// - `url`
-    /// - `week`
-
-    pub const r#type: AttributeDiscription = ("type", None, false);
-
-    pub const value: AttributeDiscription = ("value", None, true);
-}
-
-/*
-volatile attributes
-*/
-
-impl script {
-    // r#async: Bool,
-    // r#type: String, // TODO could be an enum
+    template None {};
 
-    pub const r#type: AttributeDiscription = ("type", None, false);
-
-    pub const r#script: AttributeDiscription = ("script", None, false);
-}
-
-impl button {
-    pub const r#type: AttributeDiscription = ("type", None, false);
-}
-
-impl select {
-    pub const value: AttributeDiscription = ("value", None, true);
-}
-
-impl option {
-    pub const selected: AttributeDiscription = ("selected", None, true);
-}
-
-impl textarea {
-    pub const value: AttributeDiscription = ("value", None, true);
-}
-impl label {
-    pub const r#for: AttributeDiscription = ("for", None, false);
-}
-
-builder_constructors! {
     // SVG components
     /// Build a
     /// [`<svg>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/svg)
     /// element.
-    svg <> "http://www.w3.org/2000/svg" { };
+    svg "http://www.w3.org/2000/svg" { };
 
 
     // /// Build a
     // /// [`<a>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/a)
     // /// element.
-    // a <> "http://www.w3.org/2000/svg" {};
+    // a "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<animate>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/animate)
     /// element.
-    animate <> "http://www.w3.org/2000/svg" {};
+    animate "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<animateMotion>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/animateMotion)
     /// element.
-    animateMotion <> "http://www.w3.org/2000/svg" {};
+    animateMotion "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<animateTransform>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/animateTransform)
     /// element.
-    animateTransform <> "http://www.w3.org/2000/svg" {};
+    animateTransform "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<circle>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/circle)
     /// element.
-    circle <> "http://www.w3.org/2000/svg" {};
+    circle "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<clipPath>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/clipPath)
     /// element.
-    clipPath <> "http://www.w3.org/2000/svg" {};
+    clipPath "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<defs>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/defs)
     /// element.
-    defs <> "http://www.w3.org/2000/svg" {};
+    defs "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<desc>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/desc)
     /// element.
-    desc <> "http://www.w3.org/2000/svg" {};
+    desc "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<discard>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/discard)
     /// element.
-    discard <> "http://www.w3.org/2000/svg" {};
+    discard "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<ellipse>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/ellipse)
     /// element.
-    ellipse <> "http://www.w3.org/2000/svg" {};
+    ellipse "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<feBlend>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feBlend)
     /// element.
-    feBlend <> "http://www.w3.org/2000/svg" {};
+    feBlend "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<feColorMatrix>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feColorMatrix)
     /// element.
-    feColorMatrix <> "http://www.w3.org/2000/svg" {};
+    feColorMatrix "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<feComponentTransfer>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feComponentTransfer)
     /// element.
-    feComponentTransfer <> "http://www.w3.org/2000/svg" {};
+    feComponentTransfer "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<feComposite>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feComposite)
     /// element.
-    feComposite <> "http://www.w3.org/2000/svg" {};
+    feComposite "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<feConvolveMatrix>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feConvolveMatrix)
     /// element.
-    feConvolveMatrix <> "http://www.w3.org/2000/svg" {};
+    feConvolveMatrix "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<feDiffuseLighting>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feDiffuseLighting)
     /// element.
-    feDiffuseLighting <> "http://www.w3.org/2000/svg" {};
+    feDiffuseLighting "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<feDisplacementMap>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feDisplacementMap)
     /// element.
-    feDisplacementMap <> "http://www.w3.org/2000/svg" {};
+    feDisplacementMap "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<feDistantLight>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feDistantLight)
     /// element.
-    feDistantLight <> "http://www.w3.org/2000/svg" {};
+    feDistantLight "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<feDropShadow>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feDropShadow)
     /// element.
-    feDropShadow <> "http://www.w3.org/2000/svg" {};
+    feDropShadow "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<feFlood>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feFlood)
     /// element.
-    feFlood <> "http://www.w3.org/2000/svg" {};
+    feFlood "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<feFuncA>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feFuncA)
     /// element.
-    feFuncA <> "http://www.w3.org/2000/svg" {};
+    feFuncA "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<feFuncB>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feFuncB)
     /// element.
-    feFuncB <> "http://www.w3.org/2000/svg" {};
+    feFuncB "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<feFuncG>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feFuncG)
     /// element.
-    feFuncG <> "http://www.w3.org/2000/svg" {};
+    feFuncG "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<feFuncR>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feFuncR)
     /// element.
-    feFuncR <> "http://www.w3.org/2000/svg" {};
+    feFuncR "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<feGaussianBlur>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feGaussianBlur)
     /// element.
-    feGaussianBlur <> "http://www.w3.org/2000/svg" {};
+    feGaussianBlur "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<feImage>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feImage)
     /// element.
-    feImage <> "http://www.w3.org/2000/svg" {};
+    feImage "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<feMerge>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feMerge)
     /// element.
-    feMerge <> "http://www.w3.org/2000/svg" {};
+    feMerge "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<feMergeNode>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feMergeNode)
     /// element.
-    feMergeNode <> "http://www.w3.org/2000/svg" {};
+    feMergeNode "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<feMorphology>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feMorphology)
     /// element.
-    feMorphology <> "http://www.w3.org/2000/svg" {};
+    feMorphology "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<feOffset>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feOffset)
     /// element.
-    feOffset <> "http://www.w3.org/2000/svg" {};
+    feOffset "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<fePointLight>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/fePointLight)
     /// element.
-    fePointLight <> "http://www.w3.org/2000/svg" {};
+    fePointLight "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<feSpecularLighting>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feSpecularLighting)
     /// element.
-    feSpecularLighting <> "http://www.w3.org/2000/svg" {};
+    feSpecularLighting "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<feSpotLight>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feSpotLight)
     /// element.
-    feSpotLight <> "http://www.w3.org/2000/svg" {};
+    feSpotLight "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<feTile>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feTile)
     /// element.
-    feTile <> "http://www.w3.org/2000/svg" {};
+    feTile "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<feTurbulence>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feTurbulence)
     /// element.
-    feTurbulence <> "http://www.w3.org/2000/svg" {};
+    feTurbulence "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<filter>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/filter)
     /// element.
-    filter <> "http://www.w3.org/2000/svg" {};
+    filter "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<foreignObject>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/foreignObject)
     /// element.
-    foreignObject <> "http://www.w3.org/2000/svg" {};
+    foreignObject "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<g>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g)
     /// element.
-    g <> "http://www.w3.org/2000/svg" {};
+    g "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<hatch>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/hatch)
     /// element.
-    hatch <> "http://www.w3.org/2000/svg" {};
+    hatch "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<hatchpath>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/hatchpath)
     /// element.
-    hatchpath <> "http://www.w3.org/2000/svg" {};
+    hatchpath "http://www.w3.org/2000/svg" {};
 
     // /// Build a
     // /// [`<image>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/image)
     // /// element.
-    // image <> "http://www.w3.org/2000/svg" {};
+    // image "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<line>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/line)
     /// element.
-    line <> "http://www.w3.org/2000/svg" {};
+    line "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<linearGradient>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/linearGradient)
     /// element.
-    linearGradient <> "http://www.w3.org/2000/svg" {};
+    linearGradient "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<marker>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/marker)
     /// element.
-    marker <> "http://www.w3.org/2000/svg" {};
+    marker "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<mask>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/mask)
     /// element.
-    mask <> "http://www.w3.org/2000/svg" {};
+    mask "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<metadata>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/metadata)
     /// element.
-    metadata <> "http://www.w3.org/2000/svg" {};
+    metadata "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<mpath>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/mpath)
     /// element.
-    mpath <> "http://www.w3.org/2000/svg" {};
+    mpath "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<path>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path)
     /// element.
-    path <> "http://www.w3.org/2000/svg" {};
+    path "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<pattern>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/pattern)
     /// element.
-    pattern <> "http://www.w3.org/2000/svg" {};
+    pattern "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<polygon>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/polygon)
     /// element.
-    polygon <> "http://www.w3.org/2000/svg" {};
+    polygon "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<polyline>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/polyline)
     /// element.
-    polyline <> "http://www.w3.org/2000/svg" {};
+    polyline "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<radialGradient>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/radialGradient)
     /// element.
-    radialGradient <> "http://www.w3.org/2000/svg" {};
+    radialGradient "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<rect>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/rect)
     /// element.
-    rect <> "http://www.w3.org/2000/svg" {};
+    rect "http://www.w3.org/2000/svg" {};
 
     // /// Build a
     // /// [`<script>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/script)
     // /// element.
-    // script <> "http://www.w3.org/2000/svg" {};
+    // script "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<set>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/set)
     /// element.
-    set <> "http://www.w3.org/2000/svg" {};
+    set "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<stop>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/stop)
     /// element.
-    stop <> "http://www.w3.org/2000/svg" {};
+    stop "http://www.w3.org/2000/svg" {};
 
     // /// Build a
     // /// [`<style>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/style)
     // /// element.
-    // style <> "http://www.w3.org/2000/svg" {};
+    // style "http://www.w3.org/2000/svg" {};
 
     // /// Build a
     // /// [`<svg>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/svg)
     // /// element.
-    // svg <> "http://www.w3.org/2000/svg" {};
+    // svg "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<switch>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/switch)
     /// element.
-    switch <> "http://www.w3.org/2000/svg" {};
+    switch "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<symbol>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/symbol)
     /// element.
-    symbol <> "http://www.w3.org/2000/svg" {};
+    symbol "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<text>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/text)
     /// element.
-    text <> "http://www.w3.org/2000/svg" {};
+    text "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<textPath>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/textPath)
     /// element.
-    textPath <> "http://www.w3.org/2000/svg" {};
+    textPath "http://www.w3.org/2000/svg" {};
 
     // /// Build a
     // /// [`<title>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/title)
     // /// element.
-    // title <> "http://www.w3.org/2000/svg" {};
+    // title "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<tspan>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/tspan)
     /// element.
-    tspan <> "http://www.w3.org/2000/svg" {};
+    tspan "http://www.w3.org/2000/svg" {};
 
     /// Build a
     /// [`<view>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/view)
     /// element.
-    view <> "http://www.w3.org/2000/svg" {};
+    view "http://www.w3.org/2000/svg" {};
 
     // /// Build a
     // /// [`<use>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/use)
     // /// element.
-    // use <> "http://www.w3.org/2000/svg" {};
+    // use "http://www.w3.org/2000/svg" {};
 
 
 }

+ 2 - 2
packages/html/src/events/form.rs

@@ -1,4 +1,4 @@
-use std::{collections::HashMap, fmt::Debug, sync::Arc};
+use std::{collections::HashMap, fmt::Debug};
 
 use dioxus_core::Event;
 
@@ -13,7 +13,7 @@ pub struct FormData {
     pub values: HashMap<String, String>,
 
     #[cfg_attr(feature = "serialize", serde(skip))]
-    pub files: Option<Arc<dyn FileEngine>>,
+    pub files: Option<std::sync::Arc<dyn FileEngine>>,
 }
 
 impl PartialEq for FormData {

+ 45 - 3
packages/html/src/global_attributes.rs

@@ -2,12 +2,43 @@
 
 use crate::AttributeDiscription;
 
+#[cfg(feature = "hot-reload-context")]
+macro_rules! trait_method_mapping {
+    (
+        $matching:ident;
+        $(#[$attr:meta])*
+        $name:ident;
+    ) => {
+        if $matching == stringify!($name) {
+            return Some((stringify!($name), None));
+        }
+    };
+    (
+        $matching:ident;
+        $(#[$attr:meta])*
+        $name:ident: $lit:literal;
+    ) => {
+        if $matching == stringify!($name) {
+            return Some(($lit, None));
+        }
+    };
+    (
+        $matching:ident;
+        $(#[$attr:meta])*
+        $name:ident: $lit:literal, $ns:literal;
+    ) => {
+        if $matching == stringify!($name) {
+            return Some(($lit, Some($ns)));
+        }
+    };
+}
+
 macro_rules! trait_methods {
     (
         @base
-
         $(#[$trait_attr:meta])*
         $trait:ident;
+        $fn:ident;
         $(
             $(#[$attr:meta])*
             $name:ident $(: $($arg:literal),*)*;
@@ -20,6 +51,17 @@ macro_rules! trait_methods {
                 const $name: AttributeDiscription = trait_methods! { $name $(: $($arg),*)*; };
             )*
         }
+
+        #[cfg(feature = "hot-reload-context")]
+        pub(crate) fn $fn(attr: &str) -> Option<(&'static str, Option<&'static str>)> {
+            $(
+                trait_method_mapping! {
+                    attr;
+                    $name$(: $($arg),*)*;
+                }
+            )*
+            None
+        }
     };
 
     // Rename the incoming ident and apply a custom namespace
@@ -36,6 +78,7 @@ trait_methods! {
     @base
 
     GlobalAttributes;
+    map_global_attributes;
 
     /// Prevent the default action for this element.
     ///
@@ -1545,8 +1588,8 @@ trait_methods! {
 
 trait_methods! {
     @base
-
     SvgAttributes;
+    map_svg_attributes;
 
     /// Prevent the default action for this element.
     ///
@@ -1554,7 +1597,6 @@ trait_methods! {
     /// <https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault>
     prevent_default: "dioxus-prevent-default";
 
-
     /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/accent-height>
     accent_height: "accent-height";
 

+ 2 - 0
packages/html/src/lib.rs

@@ -14,6 +14,8 @@
 //! Currently, we don't validate for structures, but do validate attributes.
 
 mod elements;
+#[cfg(feature = "hot-reload-context")]
+pub use elements::HtmlCtx;
 pub mod events;
 pub mod geometry;
 mod global_attributes;

+ 7 - 7
packages/native-core/src/real_dom.rs

@@ -174,7 +174,7 @@ impl<S: State> RealDom<S> {
             use dioxus_core::Mutation::*;
             match e {
                 AppendChildren { id, m } => {
-                    let children = self.stack.split_off(m);
+                    let children = self.stack.split_off(self.stack.len() - m);
                     let parent = self.element_to_node_id(id);
                     for child in children {
                         self.add_child(parent, child);
@@ -222,10 +222,10 @@ impl<S: State> RealDom<S> {
                     self.stack.push(clone_id);
                 }
                 ReplaceWith { id, m } => {
-                    let new_nodes = self.stack.split_off(m);
+                    let new_nodes = self.stack.split_off(self.stack.len() - m);
                     let old_node_id = self.element_to_node_id(id);
                     for new in new_nodes {
-                        self.tree.insert_after(old_node_id, new);
+                        self.tree.insert_before(old_node_id, new);
                         mark_dirty(new, NodeMask::ALL, &mut nodes_updated);
                     }
                     self.tree.remove(old_node_id);
@@ -234,21 +234,21 @@ impl<S: State> RealDom<S> {
                     let new_nodes = self.stack.split_off(self.stack.len() - m);
                     let old_node_id = self.load_child(path);
                     for new in new_nodes {
-                        self.tree.insert_after(old_node_id, new);
+                        self.tree.insert_before(old_node_id, new);
                         mark_dirty(new, NodeMask::ALL, &mut nodes_updated);
                     }
                     self.tree.remove(old_node_id);
                 }
                 InsertAfter { id, m } => {
-                    let new_nodes = self.stack.split_off(m);
+                    let new_nodes = self.stack.split_off(self.stack.len() - m);
                     let old_node_id = self.element_to_node_id(id);
-                    for new in new_nodes {
+                    for new in new_nodes.into_iter().rev() {
                         self.tree.insert_after(old_node_id, new);
                         mark_dirty(new, NodeMask::ALL, &mut nodes_updated);
                     }
                 }
                 InsertBefore { id, m } => {
-                    let new_nodes = self.stack.split_off(m);
+                    let new_nodes = self.stack.split_off(self.stack.len() - m);
                     let old_node_id = self.element_to_node_id(id);
                     for new in new_nodes {
                         self.tree.insert_before(old_node_id, new);

+ 16 - 15
packages/native-core/src/utils/persistant_iterator.rs

@@ -73,11 +73,11 @@ impl PersistantElementIter {
             .edits
             .iter()
             .filter_map(|m| {
-                // nodes within templates will never be removedns
-                if let Mutation::Remove { id } = m {
-                    Some(rdom.element_to_node_id(*id))
-                } else {
-                    None
+                // nodes within templates will never be removed
+                match m {
+                    Mutation::Remove { id } => Some(rdom.element_to_node_id(*id)),
+                    Mutation::ReplaceWith { id, .. } => Some(rdom.element_to_node_id(*id)),
+                    _ => None,
                 }
             })
             .collect();
@@ -358,12 +358,16 @@ fn persist_removes() {
     let mut rdom: RealDom<Empty> = RealDom::new();
 
     let build = vdom.rebuild();
+    println!("{:#?}", build);
     let _to_update = rdom.apply_mutations(build);
 
     // this will end on the node that is removed
     let mut iter1 = PersistantElementIter::new();
     // this will end on the after node that is removed
     let mut iter2 = PersistantElementIter::new();
+    // root
+    iter1.next(&rdom).id();
+    iter2.next(&rdom).id();
     // div
     iter1.next(&rdom).id();
     iter2.next(&rdom).id();
@@ -386,27 +390,24 @@ fn persist_removes() {
 
     vdom.mark_dirty(ScopeId(0));
     let update = vdom.render_immediate();
+    println!("{:#?}", update);
     iter1.prune(&update, &rdom);
     iter2.prune(&update, &rdom);
     let _to_update = rdom.apply_mutations(update);
 
-    let p_tag = "1".to_string();
+    let root_tag = "Root".to_string();
     let idx = iter1.next(&rdom).id();
+    dbg!(&rdom[idx].node_data.node_type);
     assert!(matches!(
         &rdom[idx].node_data.node_type,
-        NodeType::Element { tag: p_tag, .. }
+        NodeType::Element { tag: root_tag, .. }
     ));
-    let text = "2".to_string();
-    let idx = iter1.next(&rdom).id();
-    assert!(matches!(
-        &rdom[idx].node_data.node_type,
-        NodeType::Text { text, .. }
-    ));
-    let div_tag = "div".to_string();
+
     let idx = iter2.next(&rdom).id();
+    dbg!(&rdom[idx].node_data.node_type);
     assert!(matches!(
         &rdom[idx].node_data.node_type,
-        NodeType::Element { tag: div_tag, .. }
+        NodeType::Element { tag: root_tag, .. }
     ));
 }
 

+ 2 - 0
packages/rsx/Cargo.toml

@@ -13,6 +13,8 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
 
 [dependencies]
 proc-macro2 = { version = "1.0", features = ["span-locations"] }
+dioxus-core = { path = "../core" }
 syn = { version = "1.0", features = ["full", "extra-traits"] }
 quote = { version = "1.0" }
 serde = { version = "1.0", features = ["derive"] }
+internment = "0.7.0"

+ 0 - 892
packages/rsx/src/attributes.rs

@@ -1,892 +0,0 @@
-use crate::elements::*;
-
-// map the rsx name of the attribute to the html name of the attribute, the namespace that contains it, and if the attribute is volitile
-pub fn attrbute_to_static_str(
-    attr: &str,
-    element: &'static str,
-    namespace: Option<&'static str>,
-) -> Option<(&'static str, Option<&'static str>, bool)> {
-    if namespace == Some("http://www.w3.org/2000/svg") {
-        svg::MAPPED_ATTRIBUTES
-            .iter()
-            .find(|(a, _)| *a == attr)
-            .map(|(_, b)| (*b, None, false))
-    } else {
-        NO_NAMESPACE_ATTRIBUTES
-            .iter()
-            .find(|&a| *a == attr)
-            .map(|a| (*a, None, false))
-            .or_else(|| {
-                STYLE_ATTRIBUTES
-                    .iter()
-                    .find(|(a, _)| *a == attr)
-                    .map(|(_, b)| (*b, Some("style"), false))
-            })
-            .or_else(|| {
-                MAPPED_ATTRIBUTES
-                    .iter()
-                    .find(|(a, _)| *a == attr)
-                    .map(|(_, b)| (*b, None, false))
-            })
-    }
-    .or_else(|| {
-        ELEMENTS_WITH_MAPPED_ATTRIBUTES
-            .iter()
-            .find_map(|(el, attrs)| {
-                (element == *el)
-                    .then(|| {
-                        attrs
-                            .iter()
-                            .find(|(a, _, _)| *a == attr)
-                            .map(|(_, b, volitile)| (*b, None, *volitile))
-                    })
-                    .flatten()
-            })
-    })
-    .or_else(|| {
-        ELEMENTS_WITH_NAMESPACE.iter().find_map(|(el, ns, attrs)| {
-            (element == *el && namespace == Some(*ns))
-                .then(|| {
-                    attrs
-                        .iter()
-                        .find(|a| **a == attr)
-                        .map(|a| (*a, None, false))
-                })
-                .flatten()
-        })
-    })
-    .or_else(|| {
-        ELEMENTS_WITHOUT_NAMESPACE.iter().find_map(|(el, attrs)| {
-            (element == *el)
-                .then(|| {
-                    attrs
-                        .iter()
-                        .find(|a| **a == attr)
-                        .map(|a| (*a, None, false))
-                })
-                .flatten()
-        })
-    })
-}
-
-macro_rules! no_namespace_trait_methods {
-    (
-        $(
-            $(#[$attr:meta])*
-            $name:ident;
-        )*
-    ) => {
-        pub const NO_NAMESPACE_ATTRIBUTES: &'static [&'static str] = &[
-            $(
-                stringify!($name),
-            )*
-        ];
-    };
-}
-
-macro_rules! style_trait_methods {
-    (
-        $(
-            $(#[$attr:meta])*
-            $name:ident: $lit:literal,
-        )*
-    ) => {
-        pub const STYLE_ATTRIBUTES: &'static [(&'static str, &'static str)] = &[
-            $(
-                (stringify!($name), $lit),
-            )*
-        ];
-    };
-}
-
-macro_rules! mapped_trait_methods {
-    (
-        $(
-            $(#[$attr:meta])*
-            $name:ident: $lit:literal,
-        )*
-    ) => {
-        pub const MAPPED_ATTRIBUTES: &'static [(&'static str, &'static str)] = &[
-            $(
-                (stringify!($name), $lit),
-            )*
-            ("prevent_default", "dioxus-prevent-default"),
-        ];
-    };
-}
-
-no_namespace_trait_methods! {
-    accesskey;
-
-    /// The HTML class attribute is used to specify a class for an HTML element.
-    ///
-    /// ## Details
-    /// Multiple HTML elements can share the same class.
-    ///
-    /// The class global attribute is a space-separated list of the case-sensitive classes of the element.
-    /// Classes allow CSS and Javascript to select and access specific elements via the class selectors or
-    /// functions like the DOM method document.getElementsByClassName.
-    ///
-    /// ## Example
-    ///
-    /// ### HTML:
-    /// ```html
-    /// <p class="note editorial">Above point sounds a bit obvious. Remove/rewrite?</p>
-    /// ```
-    ///
-    /// ### CSS:
-    /// ```css
-    /// .note {
-    ///     font-style: italic;
-    ///     font-weight: bold;
-    /// }
-    ///
-    /// .editorial {
-    ///     background: rgb(255, 0, 0, .25);
-    ///     padding: 10px;
-    /// }
-    /// ```
-    class;
-    contenteditable;
-    data;
-    dir;
-    draggable;
-    hidden;
-    id;
-    lang;
-    spellcheck;
-    style;
-    tabindex;
-    title;
-    translate;
-
-    role;
-
-    /// dangerous_inner_html is Dioxus's replacement for using innerHTML in the browser DOM. In general, setting
-    /// HTML from code is risky because it’s easy to inadvertently expose your users to a cross-site scripting (XSS)
-    /// attack. So, you can set HTML directly from Dioxus, but you have to type out dangerous_inner_html to remind
-    /// yourself that it’s dangerous
-    dangerous_inner_html;
-}
-
-// This macro creates an explicit method call for each of the style attributes.
-//
-// The left token specifies the name of the attribute in the rsx! macro, and the right string literal specifies the
-// actual name of the attribute generated.
-//
-// This roughly follows the html spec
-style_trait_methods! {
-    align_content: "align-content",
-    align_items: "align-items",
-    align_self: "align-self",
-    alignment_adjust: "alignment-adjust",
-    alignment_baseline: "alignment-baseline",
-    all: "all",
-    alt: "alt",
-    animation: "animation",
-    animation_delay: "animation-delay",
-    animation_direction: "animation-direction",
-    animation_duration: "animation-duration",
-    animation_fill_mode: "animation-fill-mode",
-    animation_iteration_count: "animation-iteration-count",
-    animation_name: "animation-name",
-    animation_play_state: "animation-play-state",
-    animation_timing_function: "animation-timing-function",
-    azimuth: "azimuth",
-    backface_visibility: "backface-visibility",
-    background: "background",
-    background_attachment: "background-attachment",
-    background_clip: "background-clip",
-    background_color: "background-color",
-    background_image: "background-image",
-    background_origin: "background-origin",
-    background_position: "background-position",
-    background_repeat: "background-repeat",
-    background_size: "background-size",
-    background_blend_mode: "background-blend-mode",
-    baseline_shift: "baseline-shift",
-    bleed: "bleed",
-    bookmark_label: "bookmark-label",
-    bookmark_level: "bookmark-level",
-    bookmark_state: "bookmark-state",
-    border: "border",
-    border_color: "border-color",
-    border_style: "border-style",
-    border_width: "border-width",
-    border_bottom: "border-bottom",
-    border_bottom_color: "border-bottom-color",
-    border_bottom_style: "border-bottom-style",
-    border_bottom_width: "border-bottom-width",
-    border_left: "border-left",
-    border_left_color: "border-left-color",
-    border_left_style: "border-left-style",
-    border_left_width: "border-left-width",
-    border_right: "border-right",
-    border_right_color: "border-right-color",
-    border_right_style: "border-right-style",
-    border_right_width: "border-right-width",
-    border_top: "border-top",
-    border_top_color: "border-top-color",
-    border_top_style: "border-top-style",
-    border_top_width: "border-top-width",
-    border_collapse: "border-collapse",
-    border_image: "border-image",
-    border_image_outset: "border-image-outset",
-    border_image_repeat: "border-image-repeat",
-    border_image_slice: "border-image-slice",
-    border_image_source: "border-image-source",
-    border_image_width: "border-image-width",
-    border_radius: "border-radius",
-    border_bottom_left_radius: "border-bottom-left-radius",
-    border_bottom_right_radius: "border-bottom-right-radius",
-    border_top_left_radius: "border-top-left-radius",
-    border_top_right_radius: "border-top-right-radius",
-    border_spacing: "border-spacing",
-    bottom: "bottom",
-    box_decoration_break: "box-decoration-break",
-    box_shadow: "box-shadow",
-    box_sizing: "box-sizing",
-    box_snap: "box-snap",
-    break_after: "break-after",
-    break_before: "break-before",
-    break_inside: "break-inside",
-    buffered_rendering: "buffered-rendering",
-    caption_side: "caption-side",
-    clear: "clear",
-    clear_side: "clear-side",
-    clip: "clip",
-    clip_path: "clip-path",
-    clip_rule: "clip-rule",
-    color: "color",
-    color_adjust: "color-adjust",
-    color_correction: "color-correction",
-    color_interpolation: "color-interpolation",
-    color_interpolation_filters: "color-interpolation-filters",
-    color_profile: "color-profile",
-    color_rendering: "color-rendering",
-    column_fill: "column-fill",
-    column_gap: "column-gap",
-    column_rule: "column-rule",
-    column_rule_color: "column-rule-color",
-    column_rule_style: "column-rule-style",
-    column_rule_width: "column-rule-width",
-    column_span: "column-span",
-    columns: "columns",
-    column_count: "column-count",
-    column_width: "column-width",
-    contain: "contain",
-    content: "content",
-    counter_increment: "counter-increment",
-    counter_reset: "counter-reset",
-    counter_set: "counter-set",
-    cue: "cue",
-    cue_after: "cue-after",
-    cue_before: "cue-before",
-    cursor: "cursor",
-    direction: "direction",
-    display: "display",
-    display_inside: "display-inside",
-    display_outside: "display-outside",
-    display_extras: "display-extras",
-    display_box: "display-box",
-    dominant_baseline: "dominant-baseline",
-    elevation: "elevation",
-    empty_cells: "empty-cells",
-    enable_background: "enable-background",
-    fill: "fill",
-    fill_opacity: "fill-opacity",
-    fill_rule: "fill-rule",
-    filter: "filter",
-    float: "float",
-    float_defer_column: "float-defer-column",
-    float_defer_page: "float-defer-page",
-    float_offset: "float-offset",
-    float_wrap: "float-wrap",
-    flow_into: "flow-into",
-    flow_from: "flow-from",
-    flex: "flex",
-    flex_basis: "flex-basis",
-    flex_grow: "flex-grow",
-    flex_shrink: "flex-shrink",
-    flex_flow: "flex-flow",
-    flex_direction: "flex-direction",
-    flex_wrap: "flex-wrap",
-    flood_color: "flood-color",
-    flood_opacity: "flood-opacity",
-    font: "font",
-    font_family: "font-family",
-    font_size: "font-size",
-    font_stretch: "font-stretch",
-    font_style: "font-style",
-    font_weight: "font-weight",
-    font_feature_settings: "font-feature-settings",
-    font_kerning: "font-kerning",
-    font_language_override: "font-language-override",
-    font_size_adjust: "font-size-adjust",
-    font_synthesis: "font-synthesis",
-    font_variant: "font-variant",
-    font_variant_alternates: "font-variant-alternates",
-    font_variant_caps: "font-variant-caps",
-    font_variant_east_asian: "font-variant-east-asian",
-    font_variant_ligatures: "font-variant-ligatures",
-    font_variant_numeric: "font-variant-numeric",
-    font_variant_position: "font-variant-position",
-    footnote_policy: "footnote-policy",
-    glyph_orientation_horizontal: "glyph-orientation-horizontal",
-    glyph_orientation_vertical: "glyph-orientation-vertical",
-    grid: "grid",
-    grid_auto_flow: "grid-auto-flow",
-    grid_auto_columns: "grid-auto-columns",
-    grid_auto_rows: "grid-auto-rows",
-    grid_template: "grid-template",
-    grid_template_areas: "grid-template-areas",
-    grid_template_columns: "grid-template-columns",
-    grid_template_rows: "grid-template-rows",
-    grid_area: "grid-area",
-    grid_column: "grid-column",
-    grid_column_start: "grid-column-start",
-    grid_column_end: "grid-column-end",
-    grid_row: "grid-row",
-    grid_row_start: "grid-row-start",
-    grid_row_end: "grid-row-end",
-    hanging_punctuation: "hanging-punctuation",
-    height: "height",
-    hyphenate_character: "hyphenate-character",
-    hyphenate_limit_chars: "hyphenate-limit-chars",
-    hyphenate_limit_last: "hyphenate-limit-last",
-    hyphenate_limit_lines: "hyphenate-limit-lines",
-    hyphenate_limit_zone: "hyphenate-limit-zone",
-    hyphens: "hyphens",
-    icon: "icon",
-    image_orientation: "image-orientation",
-    image_resolution: "image-resolution",
-    image_rendering: "image-rendering",
-    ime: "ime",
-    ime_align: "ime-align",
-    ime_mode: "ime-mode",
-    ime_offset: "ime-offset",
-    ime_width: "ime-width",
-    initial_letters: "initial-letters",
-    inline_box_align: "inline-box-align",
-    isolation: "isolation",
-    justify_content: "justify-content",
-    justify_items: "justify-items",
-    justify_self: "justify-self",
-    kerning: "kerning",
-    left: "left",
-    letter_spacing: "letter-spacing",
-    lighting_color: "lighting-color",
-    line_box_contain: "line-box-contain",
-    line_break: "line-break",
-    line_grid: "line-grid",
-    line_height: "line-height",
-    line_slack: "line-slack",
-    line_snap: "line-snap",
-    list_style: "list-style",
-    list_style_image: "list-style-image",
-    list_style_position: "list-style-position",
-    list_style_type: "list-style-type",
-    margin: "margin",
-    margin_bottom: "margin-bottom",
-    margin_left: "margin-left",
-    margin_right: "margin-right",
-    margin_top: "margin-top",
-    marker: "marker",
-    marker_end: "marker-end",
-    marker_mid: "marker-mid",
-    marker_pattern: "marker-pattern",
-    marker_segment: "marker-segment",
-    marker_start: "marker-start",
-    marker_knockout_left: "marker-knockout-left",
-    marker_knockout_right: "marker-knockout-right",
-    marker_side: "marker-side",
-    marks: "marks",
-    marquee_direction: "marquee-direction",
-    marquee_play_count: "marquee-play-count",
-    marquee_speed: "marquee-speed",
-    marquee_style: "marquee-style",
-    mask: "mask",
-    mask_image: "mask-image",
-    mask_repeat: "mask-repeat",
-    mask_position: "mask-position",
-    mask_clip: "mask-clip",
-    mask_origin: "mask-origin",
-    mask_size: "mask-size",
-    mask_box: "mask-box",
-    mask_box_outset: "mask-box-outset",
-    mask_box_repeat: "mask-box-repeat",
-    mask_box_slice: "mask-box-slice",
-    mask_box_source: "mask-box-source",
-    mask_box_width: "mask-box-width",
-    mask_type: "mask-type",
-    max_height: "max-height",
-    max_lines: "max-lines",
-    max_width: "max-width",
-    min_height: "min-height",
-    min_width: "min-width",
-    mix_blend_mode: "mix-blend-mode",
-    nav_down: "nav-down",
-    nav_index: "nav-index",
-    nav_left: "nav-left",
-    nav_right: "nav-right",
-    nav_up: "nav-up",
-    object_fit: "object-fit",
-    object_position: "object-position",
-    offset_after: "offset-after",
-    offset_before: "offset-before",
-    offset_end: "offset-end",
-    offset_start: "offset-start",
-    opacity: "opacity",
-    order: "order",
-    orphans: "orphans",
-    outline: "outline",
-    outline_color: "outline-color",
-    outline_style: "outline-style",
-    outline_width: "outline-width",
-    outline_offset: "outline-offset",
-    overflow: "overflow",
-    overflow_x: "overflow-x",
-    overflow_y: "overflow-y",
-    overflow_style: "overflow-style",
-    overflow_wrap: "overflow-wrap",
-    padding: "padding",
-    padding_bottom: "padding-bottom",
-    padding_left: "padding-left",
-    padding_right: "padding-right",
-    padding_top: "padding-top",
-    page: "page",
-    page_break_after: "page-break-after",
-    page_break_before: "page-break-before",
-    page_break_inside: "page-break-inside",
-    paint_order: "paint-order",
-    pause: "pause",
-    pause_after: "pause-after",
-    pause_before: "pause-before",
-    perspective: "perspective",
-    perspective_origin: "perspective-origin",
-    pitch: "pitch",
-    pitch_range: "pitch-range",
-    play_during: "play-during",
-    pointer_events: "pointer-events",
-    position: "position",
-    quotes: "quotes",
-    region_fragment: "region-fragment",
-    resize: "resize",
-    rest: "rest",
-    rest_after: "rest-after",
-    rest_before: "rest-before",
-    richness: "richness",
-    right: "right",
-    ruby_align: "ruby-align",
-    ruby_merge: "ruby-merge",
-    ruby_position: "ruby-position",
-    scroll_behavior: "scroll-behavior",
-    scroll_snap_coordinate: "scroll-snap-coordinate",
-    scroll_snap_destination: "scroll-snap-destination",
-    scroll_snap_points_x: "scroll-snap-points-x",
-    scroll_snap_points_y: "scroll-snap-points-y",
-    scroll_snap_type: "scroll-snap-type",
-    shape_image_threshold: "shape-image-threshold",
-    shape_inside: "shape-inside",
-    shape_margin: "shape-margin",
-    shape_outside: "shape-outside",
-    shape_padding: "shape-padding",
-    shape_rendering: "shape-rendering",
-    size: "size",
-    speak: "speak",
-    speak_as: "speak-as",
-    speak_header: "speak-header",
-    speak_numeral: "speak-numeral",
-    speak_punctuation: "speak-punctuation",
-    speech_rate: "speech-rate",
-    stop_color: "stop-color",
-    stop_opacity: "stop-opacity",
-    stress: "stress",
-    string_set: "string-set",
-    stroke: "stroke",
-    stroke_dasharray: "stroke-dasharray",
-    stroke_dashoffset: "stroke-dashoffset",
-    stroke_linecap: "stroke-linecap",
-    stroke_linejoin: "stroke-linejoin",
-    stroke_miterlimit: "stroke-miterlimit",
-    stroke_opacity: "stroke-opacity",
-    stroke_width: "stroke-width",
-    tab_size: "tab-size",
-    table_layout: "table-layout",
-    text_align: "text-align",
-    text_align_all: "text-align-all",
-    text_align_last: "text-align-last",
-    text_anchor: "text-anchor",
-    text_combine_upright: "text-combine-upright",
-    text_decoration: "text-decoration",
-    text_decoration_color: "text-decoration-color",
-    text_decoration_line: "text-decoration-line",
-    text_decoration_style: "text-decoration-style",
-    text_decoration_skip: "text-decoration-skip",
-    text_emphasis: "text-emphasis",
-    text_emphasis_color: "text-emphasis-color",
-    text_emphasis_style: "text-emphasis-style",
-    text_emphasis_position: "text-emphasis-position",
-    text_emphasis_skip: "text-emphasis-skip",
-    text_height: "text-height",
-    text_indent: "text-indent",
-    text_justify: "text-justify",
-    text_orientation: "text-orientation",
-    text_overflow: "text-overflow",
-    text_rendering: "text-rendering",
-    text_shadow: "text-shadow",
-    text_size_adjust: "text-size-adjust",
-    text_space_collapse: "text-space-collapse",
-    text_spacing: "text-spacing",
-    text_transform: "text-transform",
-    text_underline_position: "text-underline-position",
-    text_wrap: "text-wrap",
-    top: "top",
-    touch_action: "touch-action",
-    transform: "transform",
-    transform_box: "transform-box",
-    transform_origin: "transform-origin",
-    transform_style: "transform-style",
-    transition: "transition",
-    transition_delay: "transition-delay",
-    transition_duration: "transition-duration",
-    transition_property: "transition-property",
-    unicode_bidi: "unicode-bidi",
-    vector_effect: "vector-effect",
-    vertical_align: "vertical-align",
-    visibility: "visibility",
-    voice_balance: "voice-balance",
-    voice_duration: "voice-duration",
-    voice_family: "voice-family",
-    voice_pitch: "voice-pitch",
-    voice_range: "voice-range",
-    voice_rate: "voice-rate",
-    voice_stress: "voice-stress",
-    voice_volumn: "voice-volumn",
-    volume: "volume",
-    white_space: "white-space",
-    widows: "widows",
-    width: "width",
-    will_change: "will-change",
-    word_break: "word-break",
-    word_spacing: "word-spacing",
-    word_wrap: "word-wrap",
-    wrap_flow: "wrap-flow",
-    wrap_through: "wrap-through",
-    writing_mode: "writing-mode",
-    gap: "gap",
-    list_styler_type: "list-style-type",
-    row_gap: "row-gap",
-    transition_timing_function: "transition-timing-function",
-    user_select: "user-select",
-    webkit_user_select: "-webkit-user-select",
-    z_index : "z-index",
-}
-mapped_trait_methods! {
-    aria_current: "aria-current",
-    aria_details: "aria-details",
-    aria_disabled: "aria-disabled",
-    aria_hidden: "aria-hidden",
-    aria_invalid: "aria-invalid",
-    aria_keyshortcuts: "aria-keyshortcuts",
-    aria_label: "aria-label",
-    aria_roledescription: "aria-roledescription",
-    // Widget Attributes
-    aria_autocomplete: "aria-autocomplete",
-    aria_checked: "aria-checked",
-    aria_expanded: "aria-expanded",
-    aria_haspopup: "aria-haspopup",
-    aria_level: "aria-level",
-    aria_modal: "aria-modal",
-    aria_multiline: "aria-multiline",
-    aria_multiselectable: "aria-multiselectable",
-    aria_orientation: "aria-orientation",
-    aria_placeholder: "aria-placeholder",
-    aria_pressed: "aria-pressed",
-    aria_readonly: "aria-readonly",
-    aria_required: "aria-required",
-    aria_selected: "aria-selected",
-    aria_sort: "aria-sort",
-    aria_valuemax: "aria-valuemax",
-    aria_valuemin: "aria-valuemin",
-    aria_valuenow: "aria-valuenow",
-    aria_valuetext: "aria-valuetext",
-    // Live Region Attributes
-    aria_atomic: "aria-atomic",
-    aria_busy: "aria-busy",
-    aria_live: "aria-live",
-    aria_relevant: "aria-relevant",
-
-    aria_dropeffect: "aria-dropeffect",
-    aria_grabbed: "aria-grabbed",
-    // Relationship Attributes
-    aria_activedescendant: "aria-activedescendant",
-    aria_colcount: "aria-colcount",
-    aria_colindex: "aria-colindex",
-    aria_colspan: "aria-colspan",
-    aria_controls: "aria-controls",
-    aria_describedby: "aria-describedby",
-    aria_errormessage: "aria-errormessage",
-    aria_flowto: "aria-flowto",
-    aria_labelledby: "aria-labelledby",
-    aria_owns: "aria-owns",
-    aria_posinset: "aria-posinset",
-    aria_rowcount: "aria-rowcount",
-    aria_rowindex: "aria-rowindex",
-    aria_rowspan: "aria-rowspan",
-    aria_setsize: "aria-setsize",
-}
-
-pub mod svg {
-    mapped_trait_methods! {
-        accent_height: "accent-height",
-        accumulate: "accumulate",
-        additive: "additive",
-        alignment_baseline: "alignment-baseline",
-        alphabetic: "alphabetic",
-        amplitude: "amplitude",
-        arabic_form: "arabic-form",
-        ascent: "ascent",
-        attributeName: "attributeName",
-        attributeType: "attributeType",
-        azimuth: "azimuth",
-        baseFrequency: "baseFrequency",
-        baseline_shift: "baseline-shift",
-        baseProfile: "baseProfile",
-        bbox: "bbox",
-        begin: "begin",
-        bias: "bias",
-        by: "by",
-        calcMode: "calcMode",
-        cap_height: "cap-height",
-        class: "class",
-        clip: "clip",
-        clipPathUnits: "clipPathUnits",
-        clip_path: "clip-path",
-        clip_rule: "clip-rule",
-        color: "color",
-        color_interpolation: "color-interpolation",
-        color_interpolation_filters: "color-interpolation-filters",
-        color_profile: "color-profile",
-        color_rendering: "color-rendering",
-        contentScriptType: "contentScriptType",
-        contentStyleType: "contentStyleType",
-        crossorigin: "crossorigin",
-        cursor: "cursor",
-        cx: "cx",
-        cy: "cy",
-        d: "d",
-        decelerate: "decelerate",
-        descent: "descent",
-        diffuseConstant: "diffuseConstant",
-        direction: "direction",
-        display: "display",
-        divisor: "divisor",
-        dominant_baseline: "dominant-baseline",
-        dur: "dur",
-        dx: "dx",
-        dy: "dy",
-        edgeMode: "edgeMode",
-        elevation: "elevation",
-        enable_background: "enable-background",
-        end: "end",
-        exponent: "exponent",
-        fill: "fill",
-        fill_opacity: "fill-opacity",
-        fill_rule: "fill-rule",
-        filter: "filter",
-        filterRes: "filterRes",
-        filterUnits: "filterUnits",
-        flood_color: "flood-color",
-        flood_opacity: "flood-opacity",
-        font_family: "font-family",
-        font_size: "font-size",
-        font_size_adjust: "font-size-adjust",
-        font_stretch: "font-stretch",
-        font_style: "font-style",
-        font_variant: "font-variant",
-        font_weight: "font-weight",
-        format: "format",
-        from: "from",
-        fr: "fr",
-        fx: "fx",
-        fy: "fy",
-        g1: "g1",
-        g2: "g2",
-        glyph_name: "glyph-name",
-        glyph_orientation_horizontal: "glyph-orientation-horizontal",
-        glyph_orientation_vertical: "glyph-orientation-vertical",
-        glyphRef: "glyphRef",
-        gradientTransform: "gradientTransform",
-        gradientUnits: "gradientUnits",
-        hanging: "hanging",
-        height: "height",
-        href: "href",
-        hreflang: "hreflang",
-        horiz_adv_x: "horiz-adv-x",
-        horiz_origin_x: "horiz-origin-x",
-        id: "id",
-        ideographic: "ideographic",
-        image_rendering: "image-rendering",
-        _in: "_in",
-        in2: "in2",
-        intercept: "intercept",
-        k: "k",
-        k1: "k1",
-        k2: "k2",
-        k3: "k3",
-        k4: "k4",
-        kernelMatrix: "kernelMatrix",
-        kernelUnitLength: "kernelUnitLength",
-        kerning: "kerning",
-        keyPoints: "keyPoints",
-        keySplines: "keySplines",
-        keyTimes: "keyTimes",
-        lang: "lang",
-        lengthAdjust: "lengthAdjust",
-        letter_spacing: "letter-spacing",
-        lighting_color: "lighting-color",
-        limitingConeAngle: "limitingConeAngle",
-        local: "local",
-        marker_end: "marker-end",
-        marker_mid: "marker-mid",
-        marker_start: "marker_start",
-        markerHeight: "markerHeight",
-        markerUnits: "markerUnits",
-        markerWidth: "markerWidth",
-        mask: "mask",
-        maskContentUnits: "maskContentUnits",
-        maskUnits: "maskUnits",
-        mathematical: "mathematical",
-        max: "max",
-        media: "media",
-        method: "method",
-        min: "min",
-        mode: "mode",
-        name: "name",
-        numOctaves: "numOctaves",
-        offset: "offset",
-        opacity: "opacity",
-        operator: "operator",
-        order: "order",
-        orient: "orient",
-        orientation: "orientation",
-        origin: "origin",
-        overflow: "overflow",
-        overline_position: "overline-position",
-        overline_thickness: "overline-thickness",
-        panose_1: "panose-1",
-        paint_order: "paint-order",
-        path: "path",
-        pathLength: "pathLength",
-        patternContentUnits: "patternContentUnits",
-        patternTransform: "patternTransform",
-        patternUnits: "patternUnits",
-        ping: "ping",
-        pointer_events: "pointer-events",
-        points: "points",
-        pointsAtX: "pointsAtX",
-        pointsAtY: "pointsAtY",
-        pointsAtZ: "pointsAtZ",
-        preserveAlpha: "preserveAlpha",
-        preserveAspectRatio: "preserveAspectRatio",
-        primitiveUnits: "primitiveUnits",
-        r: "r",
-        radius: "radius",
-        referrerPolicy: "referrerPolicy",
-        refX: "refX",
-        refY: "refY",
-        rel: "rel",
-        rendering_intent: "rendering-intent",
-        repeatCount: "repeatCount",
-        repeatDur: "repeatDur",
-        requiredExtensions: "requiredExtensions",
-        requiredFeatures: "requiredFeatures",
-        restart: "restart",
-        result: "result",
-        role: "role",
-        rotate: "rotate",
-        rx: "rx",
-        ry: "ry",
-        scale: "scale",
-        seed: "seed",
-        shape_rendering: "shape-rendering",
-        slope: "slope",
-        spacing: "spacing",
-        specularConstant: "specularConstant",
-        specularExponent: "specularExponent",
-        speed: "speed",
-        spreadMethod: "spreadMethod",
-        startOffset: "startOffset",
-        stdDeviation: "stdDeviation",
-        stemh: "stemh",
-        stemv: "stemv",
-        stitchTiles: "stitchTiles",
-        stop_color: "stop_color",
-        stop_opacity: "stop_opacity",
-        strikethrough_position: "strikethrough-position",
-        strikethrough_thickness: "strikethrough-thickness",
-        string: "string",
-        stroke: "stroke",
-        stroke_dasharray: "stroke-dasharray",
-        stroke_dashoffset: "stroke-dashoffset",
-        stroke_linecap: "stroke-linecap",
-        stroke_linejoin: "stroke-linejoin",
-        stroke_miterlimit: "stroke-miterlimit",
-        stroke_opacity: "stroke-opacity",
-        stroke_width: "stroke-width",
-        style: "style",
-        surfaceScale: "surfaceScale",
-        systemLanguage: "systemLanguage",
-        tabindex: "tabindex",
-        tableValues: "tableValues",
-        target: "target",
-        targetX: "targetX",
-        targetY: "targetY",
-        text_anchor: "text-anchor",
-        text_decoration: "text-decoration",
-        text_rendering: "text-rendering",
-        textLength: "textLength",
-        to: "to",
-        transform: "transform",
-        transform_origin: "transform-origin",
-        r#type: "_type",
-        u1: "u1",
-        u2: "u2",
-        underline_position: "underline-position",
-        underline_thickness: "underline-thickness",
-        unicode: "unicode",
-        unicode_bidi: "unicode-bidi",
-        unicode_range: "unicode-range",
-        units_per_em: "units-per-em",
-        v_alphabetic: "v-alphabetic",
-        v_hanging: "v-hanging",
-        v_ideographic: "v-ideographic",
-        v_mathematical: "v-mathematical",
-        values: "values",
-        vector_effect: "vector-effect",
-        version: "version",
-        vert_adv_y: "vert-adv-y",
-        vert_origin_x: "vert-origin-x",
-        vert_origin_y: "vert-origin-y",
-        view_box: "viewBox",
-        view_target: "viewTarget",
-        visibility: "visibility",
-        width: "width",
-        widths: "widths",
-        word_spacing: "word-spacing",
-        writing_mode: "writing-mode",
-        x: "x",
-        x_height: "x-height",
-        x1: "x1",
-        x2: "x2",
-        xmlns: "xmlns",
-        x_channel_selector: "xChannelSelector",
-        y: "y",
-        y1: "y1",
-        y2: "y2",
-        y_channel_selector: "yChannelSelector",
-        z: "z",
-        zoomAndPan: "zoomAndPan",
-    }
-}

+ 1 - 1
packages/rsx/src/component.rs

@@ -165,7 +165,7 @@ impl ToTokens for Component {
                 }
 
                 if !self.children.is_empty() {
-                    let renderer = TemplateRenderer {
+                    let renderer: TemplateRenderer = TemplateRenderer {
                         roots: &self.children,
                     };
 

+ 637 - 0
packages/rsx/src/hot_reload/hot_reload_diff.rs

@@ -0,0 +1,637 @@
+use proc_macro2::TokenStream;
+use syn::{File, Macro};
+
+pub enum DiffResult {
+    CodeChanged,
+    RsxChanged(Vec<(Macro, TokenStream)>),
+}
+
+/// Find any rsx calls in the given file and return a list of all the rsx calls that have changed.
+pub fn find_rsx(new: &File, old: &File) -> DiffResult {
+    let mut rsx_calls = Vec::new();
+    if new.items.len() != old.items.len() {
+        return DiffResult::CodeChanged;
+    }
+    for (new, old) in new.items.iter().zip(old.items.iter()) {
+        if find_rsx_item(new, old, &mut rsx_calls) {
+            return DiffResult::CodeChanged;
+        }
+    }
+    DiffResult::RsxChanged(rsx_calls)
+}
+
+fn find_rsx_item(
+    new: &syn::Item,
+    old: &syn::Item,
+    rsx_calls: &mut Vec<(Macro, TokenStream)>,
+) -> bool {
+    match (new, old) {
+        (syn::Item::Const(new_item), syn::Item::Const(old_item)) => {
+            find_rsx_expr(&new_item.expr, &old_item.expr, rsx_calls)
+                || new_item.attrs != old_item.attrs
+                || new_item.vis != old_item.vis
+                || new_item.const_token != old_item.const_token
+                || new_item.ident != old_item.ident
+                || new_item.colon_token != old_item.colon_token
+                || new_item.ty != old_item.ty
+                || new_item.eq_token != old_item.eq_token
+                || new_item.semi_token != old_item.semi_token
+        }
+        (syn::Item::Enum(new_item), syn::Item::Enum(old_item)) => {
+            if new_item.variants.len() != old_item.variants.len() {
+                return true;
+            }
+            for (new_varient, old_varient) in new_item.variants.iter().zip(old_item.variants.iter())
+            {
+                match (&new_varient.discriminant, &old_varient.discriminant) {
+                    (Some((new_eq, new_expr)), Some((old_eq, old_expr))) => {
+                        if find_rsx_expr(new_expr, old_expr, rsx_calls) || new_eq != old_eq {
+                            return true;
+                        }
+                    }
+                    (None, None) => (),
+                    _ => return true,
+                }
+                if new_varient.attrs != old_varient.attrs
+                    || new_varient.ident != old_varient.ident
+                    || new_varient.fields != old_varient.fields
+                {
+                    return true;
+                }
+            }
+            new_item.attrs != old_item.attrs
+                || new_item.vis != old_item.vis
+                || new_item.enum_token != old_item.enum_token
+                || new_item.ident != old_item.ident
+                || new_item.generics != old_item.generics
+                || new_item.brace_token != old_item.brace_token
+        }
+        (syn::Item::ExternCrate(new_item), syn::Item::ExternCrate(old_item)) => {
+            old_item != new_item
+        }
+        (syn::Item::Fn(new_item), syn::Item::Fn(old_item)) => {
+            find_rsx_block(&new_item.block, &old_item.block, rsx_calls)
+                || new_item.attrs != old_item.attrs
+                || new_item.vis != old_item.vis
+                || new_item.sig != old_item.sig
+        }
+        (syn::Item::ForeignMod(new_item), syn::Item::ForeignMod(old_item)) => old_item != new_item,
+        (syn::Item::Impl(new_item), syn::Item::Impl(old_item)) => {
+            if new_item.items.len() != old_item.items.len() {
+                return true;
+            }
+            for (new_item, old_item) in new_item.items.iter().zip(old_item.items.iter()) {
+                if match (new_item, old_item) {
+                    (syn::ImplItem::Const(new_item), syn::ImplItem::Const(old_item)) => {
+                        find_rsx_expr(&new_item.expr, &old_item.expr, rsx_calls)
+                    }
+                    (syn::ImplItem::Method(new_item), syn::ImplItem::Method(old_item)) => {
+                        find_rsx_block(&new_item.block, &old_item.block, rsx_calls)
+                    }
+                    (syn::ImplItem::Type(new_item), syn::ImplItem::Type(old_item)) => {
+                        old_item != new_item
+                    }
+                    (syn::ImplItem::Macro(new_item), syn::ImplItem::Macro(old_item)) => {
+                        old_item != new_item
+                    }
+                    _ => true,
+                } {
+                    return true;
+                }
+            }
+            new_item.attrs != old_item.attrs
+                || new_item.defaultness != old_item.defaultness
+                || new_item.unsafety != old_item.unsafety
+                || new_item.impl_token != old_item.impl_token
+                || new_item.generics != old_item.generics
+                || new_item.trait_ != old_item.trait_
+                || new_item.self_ty != old_item.self_ty
+                || new_item.brace_token != old_item.brace_token
+        }
+        (syn::Item::Macro(new_item), syn::Item::Macro(old_item)) => {
+            find_rsx_macro(&new_item.mac, &old_item.mac, rsx_calls)
+                || new_item.attrs != old_item.attrs
+                || new_item.semi_token != old_item.semi_token
+                || new_item.ident != old_item.ident
+        }
+        (syn::Item::Macro2(new_item), syn::Item::Macro2(old_item)) => old_item != new_item,
+        (syn::Item::Mod(new_item), syn::Item::Mod(old_item)) => {
+            match (&new_item.content, &old_item.content) {
+                (Some((_, new_items)), Some((_, old_items))) => {
+                    if new_items.len() != old_items.len() {
+                        return true;
+                    }
+                    for (new_item, old_item) in new_items.iter().zip(old_items.iter()) {
+                        if find_rsx_item(new_item, old_item, rsx_calls) {
+                            return true;
+                        }
+                    }
+                    new_item.attrs != old_item.attrs
+                        || new_item.vis != old_item.vis
+                        || new_item.mod_token != old_item.mod_token
+                        || new_item.ident != old_item.ident
+                        || new_item.semi != old_item.semi
+                }
+                (None, None) => {
+                    new_item.attrs != old_item.attrs
+                        || new_item.vis != old_item.vis
+                        || new_item.mod_token != old_item.mod_token
+                        || new_item.ident != old_item.ident
+                        || new_item.semi != old_item.semi
+                }
+                _ => true,
+            }
+        }
+        (syn::Item::Static(new_item), syn::Item::Static(old_item)) => {
+            find_rsx_expr(&new_item.expr, &old_item.expr, rsx_calls)
+                || new_item.attrs != old_item.attrs
+                || new_item.vis != old_item.vis
+                || new_item.static_token != old_item.static_token
+                || new_item.mutability != old_item.mutability
+                || new_item.ident != old_item.ident
+                || new_item.colon_token != old_item.colon_token
+                || new_item.ty != old_item.ty
+                || new_item.eq_token != old_item.eq_token
+                || new_item.semi_token != old_item.semi_token
+        }
+        (syn::Item::Struct(new_item), syn::Item::Struct(old_item)) => old_item != new_item,
+        (syn::Item::Trait(new_item), syn::Item::Trait(old_item)) => {
+            find_rsx_trait(new_item, old_item, rsx_calls)
+        }
+        (syn::Item::TraitAlias(new_item), syn::Item::TraitAlias(old_item)) => old_item != new_item,
+        (syn::Item::Type(new_item), syn::Item::Type(old_item)) => old_item != new_item,
+        (syn::Item::Union(new_item), syn::Item::Union(old_item)) => old_item != new_item,
+        (syn::Item::Use(new_item), syn::Item::Use(old_item)) => old_item != new_item,
+        (syn::Item::Verbatim(_), syn::Item::Verbatim(_)) => false,
+        _ => true,
+    }
+}
+
+fn find_rsx_trait(
+    new_item: &syn::ItemTrait,
+    old_item: &syn::ItemTrait,
+    rsx_calls: &mut Vec<(Macro, TokenStream)>,
+) -> bool {
+    if new_item.items.len() != old_item.items.len() {
+        return true;
+    }
+    for (new_item, old_item) in new_item.items.iter().zip(old_item.items.iter()) {
+        if match (new_item, old_item) {
+            (syn::TraitItem::Const(new_item), syn::TraitItem::Const(old_item)) => {
+                if let (Some((_, new_expr)), Some((_, old_expr))) =
+                    (&new_item.default, &old_item.default)
+                {
+                    find_rsx_expr(new_expr, old_expr, rsx_calls)
+                } else {
+                    true
+                }
+            }
+            (syn::TraitItem::Method(new_item), syn::TraitItem::Method(old_item)) => {
+                if let (Some(new_block), Some(old_block)) = (&new_item.default, &old_item.default) {
+                    find_rsx_block(new_block, old_block, rsx_calls)
+                } else {
+                    true
+                }
+            }
+            (syn::TraitItem::Type(new_item), syn::TraitItem::Type(old_item)) => {
+                old_item != new_item
+            }
+            (syn::TraitItem::Macro(new_item), syn::TraitItem::Macro(old_item)) => {
+                old_item != new_item
+            }
+            _ => true,
+        } {
+            return true;
+        }
+    }
+    new_item.attrs != old_item.attrs
+        || new_item.vis != old_item.vis
+        || new_item.unsafety != old_item.unsafety
+        || new_item.auto_token != old_item.auto_token
+        || new_item.ident != old_item.ident
+        || new_item.generics != old_item.generics
+        || new_item.colon_token != old_item.colon_token
+        || new_item.supertraits != old_item.supertraits
+        || new_item.brace_token != old_item.brace_token
+}
+
+fn find_rsx_block(
+    new_block: &syn::Block,
+    old_block: &syn::Block,
+    rsx_calls: &mut Vec<(Macro, TokenStream)>,
+) -> bool {
+    if new_block.stmts.len() != old_block.stmts.len() {
+        return true;
+    }
+    for (new_stmt, old_stmt) in new_block.stmts.iter().zip(old_block.stmts.iter()) {
+        if find_rsx_stmt(new_stmt, old_stmt, rsx_calls) {
+            return true;
+        }
+    }
+    new_block.brace_token != old_block.brace_token
+}
+
+fn find_rsx_stmt(
+    new_stmt: &syn::Stmt,
+    old_stmt: &syn::Stmt,
+    rsx_calls: &mut Vec<(Macro, TokenStream)>,
+) -> bool {
+    match (new_stmt, old_stmt) {
+        (syn::Stmt::Local(new_local), syn::Stmt::Local(old_local)) => {
+            (match (&new_local.init, &old_local.init) {
+                (Some((new_eq, new_expr)), Some((old_eq, old_expr))) => {
+                    find_rsx_expr(new_expr, old_expr, rsx_calls) || new_eq != old_eq
+                }
+                (None, None) => false,
+                _ => true,
+            } || new_local.attrs != old_local.attrs
+                || new_local.let_token != old_local.let_token
+                || new_local.pat != old_local.pat
+                || new_local.semi_token != old_local.semi_token)
+        }
+        (syn::Stmt::Item(new_item), syn::Stmt::Item(old_item)) => {
+            find_rsx_item(new_item, old_item, rsx_calls)
+        }
+        (syn::Stmt::Expr(new_expr), syn::Stmt::Expr(old_expr)) => {
+            find_rsx_expr(new_expr, old_expr, rsx_calls)
+        }
+        (syn::Stmt::Semi(new_expr, new_semi), syn::Stmt::Semi(old_expr, old_semi)) => {
+            find_rsx_expr(new_expr, old_expr, rsx_calls) || new_semi != old_semi
+        }
+        _ => true,
+    }
+}
+
+fn find_rsx_expr(
+    new_expr: &syn::Expr,
+    old_expr: &syn::Expr,
+    rsx_calls: &mut Vec<(Macro, TokenStream)>,
+) -> bool {
+    match (new_expr, old_expr) {
+        (syn::Expr::Array(new_expr), syn::Expr::Array(old_expr)) => {
+            if new_expr.elems.len() != old_expr.elems.len() {
+                return true;
+            }
+            for (new_el, old_el) in new_expr.elems.iter().zip(old_expr.elems.iter()) {
+                if find_rsx_expr(new_el, old_el, rsx_calls) {
+                    return true;
+                }
+            }
+            new_expr.attrs != old_expr.attrs || new_expr.bracket_token != old_expr.bracket_token
+        }
+        (syn::Expr::Assign(new_expr), syn::Expr::Assign(old_expr)) => {
+            find_rsx_expr(&new_expr.left, &old_expr.left, rsx_calls)
+                || find_rsx_expr(&new_expr.right, &old_expr.right, rsx_calls)
+                || new_expr.attrs != old_expr.attrs
+                || new_expr.eq_token != old_expr.eq_token
+        }
+        (syn::Expr::AssignOp(new_expr), syn::Expr::AssignOp(old_expr)) => {
+            find_rsx_expr(&new_expr.left, &old_expr.left, rsx_calls)
+                || find_rsx_expr(&new_expr.right, &old_expr.right, rsx_calls)
+                || new_expr.attrs != old_expr.attrs
+                || new_expr.op != old_expr.op
+        }
+        (syn::Expr::Async(new_expr), syn::Expr::Async(old_expr)) => {
+            find_rsx_block(&new_expr.block, &old_expr.block, rsx_calls)
+                || new_expr.attrs != old_expr.attrs
+                || new_expr.async_token != old_expr.async_token
+                || new_expr.capture != old_expr.capture
+        }
+        (syn::Expr::Await(new_expr), syn::Expr::Await(old_expr)) => {
+            find_rsx_expr(&new_expr.base, &old_expr.base, rsx_calls)
+                || new_expr.attrs != old_expr.attrs
+                || new_expr.dot_token != old_expr.dot_token
+                || new_expr.await_token != old_expr.await_token
+        }
+        (syn::Expr::Binary(new_expr), syn::Expr::Binary(old_expr)) => {
+            find_rsx_expr(&new_expr.left, &old_expr.left, rsx_calls)
+                || find_rsx_expr(&new_expr.right, &old_expr.right, rsx_calls)
+                || new_expr.attrs != old_expr.attrs
+                || new_expr.op != old_expr.op
+        }
+        (syn::Expr::Block(new_expr), syn::Expr::Block(old_expr)) => {
+            find_rsx_block(&new_expr.block, &old_expr.block, rsx_calls)
+                || new_expr.attrs != old_expr.attrs
+                || new_expr.label != old_expr.label
+        }
+        (syn::Expr::Box(new_expr), syn::Expr::Box(old_expr)) => {
+            find_rsx_expr(&new_expr.expr, &old_expr.expr, rsx_calls)
+                || new_expr.attrs != old_expr.attrs
+                || new_expr.box_token != old_expr.box_token
+        }
+        (syn::Expr::Break(new_expr), syn::Expr::Break(old_expr)) => {
+            match (&new_expr.expr, &old_expr.expr) {
+                (Some(new_inner), Some(old_inner)) => {
+                    find_rsx_expr(new_inner, old_inner, rsx_calls)
+                        || new_expr.attrs != old_expr.attrs
+                        || new_expr.break_token != old_expr.break_token
+                        || new_expr.label != old_expr.label
+                }
+                (None, None) => {
+                    new_expr.attrs != old_expr.attrs
+                        || new_expr.break_token != old_expr.break_token
+                        || new_expr.label != old_expr.label
+                }
+                _ => true,
+            }
+        }
+        (syn::Expr::Call(new_expr), syn::Expr::Call(old_expr)) => {
+            find_rsx_expr(&new_expr.func, &old_expr.func, rsx_calls);
+            if new_expr.args.len() != old_expr.args.len() {
+                return true;
+            }
+            for (new_arg, old_arg) in new_expr.args.iter().zip(old_expr.args.iter()) {
+                if find_rsx_expr(new_arg, old_arg, rsx_calls) {
+                    return true;
+                }
+            }
+            new_expr.attrs != old_expr.attrs || new_expr.paren_token != old_expr.paren_token
+        }
+        (syn::Expr::Cast(new_expr), syn::Expr::Cast(old_expr)) => {
+            find_rsx_expr(&new_expr.expr, &old_expr.expr, rsx_calls)
+                || new_expr.attrs != old_expr.attrs
+                || new_expr.as_token != old_expr.as_token
+                || new_expr.ty != old_expr.ty
+        }
+        (syn::Expr::Closure(new_expr), syn::Expr::Closure(old_expr)) => {
+            find_rsx_expr(&new_expr.body, &old_expr.body, rsx_calls)
+                || new_expr.attrs != old_expr.attrs
+                || new_expr.movability != old_expr.movability
+                || new_expr.asyncness != old_expr.asyncness
+                || new_expr.capture != old_expr.capture
+                || new_expr.or1_token != old_expr.or1_token
+                || new_expr.inputs != old_expr.inputs
+                || new_expr.or2_token != old_expr.or2_token
+                || new_expr.output != old_expr.output
+        }
+        (syn::Expr::Continue(new_expr), syn::Expr::Continue(old_expr)) => old_expr != new_expr,
+        (syn::Expr::Field(new_expr), syn::Expr::Field(old_expr)) => {
+            find_rsx_expr(&new_expr.base, &old_expr.base, rsx_calls)
+                || new_expr.attrs != old_expr.attrs
+                || new_expr.dot_token != old_expr.dot_token
+                || new_expr.member != old_expr.member
+        }
+        (syn::Expr::ForLoop(new_expr), syn::Expr::ForLoop(old_expr)) => {
+            find_rsx_block(&new_expr.body, &old_expr.body, rsx_calls)
+                || find_rsx_expr(&new_expr.expr, &old_expr.expr, rsx_calls)
+                || new_expr.attrs != old_expr.attrs
+                || new_expr.label != old_expr.label
+                || new_expr.for_token != old_expr.for_token
+                || new_expr.pat != old_expr.pat
+                || new_expr.in_token != old_expr.in_token
+        }
+        (syn::Expr::Group(new_expr), syn::Expr::Group(old_expr)) => {
+            find_rsx_expr(&new_expr.expr, &old_expr.expr, rsx_calls)
+        }
+        (syn::Expr::If(new_expr), syn::Expr::If(old_expr)) => {
+            if find_rsx_expr(&new_expr.cond, &old_expr.cond, rsx_calls)
+                || find_rsx_block(&new_expr.then_branch, &old_expr.then_branch, rsx_calls)
+            {
+                return true;
+            }
+            match (&new_expr.else_branch, &old_expr.else_branch) {
+                (Some((new_tok, new_else)), Some((old_tok, old_else))) => {
+                    find_rsx_expr(new_else, old_else, rsx_calls)
+                        || new_expr.attrs != old_expr.attrs
+                        || new_expr.if_token != old_expr.if_token
+                        || new_expr.cond != old_expr.cond
+                        || new_tok != old_tok
+                }
+                (None, None) => {
+                    new_expr.attrs != old_expr.attrs
+                        || new_expr.if_token != old_expr.if_token
+                        || new_expr.cond != old_expr.cond
+                }
+                _ => true,
+            }
+        }
+        (syn::Expr::Index(new_expr), syn::Expr::Index(old_expr)) => {
+            find_rsx_expr(&new_expr.expr, &old_expr.expr, rsx_calls)
+                || find_rsx_expr(&new_expr.index, &old_expr.index, rsx_calls)
+                || new_expr.attrs != old_expr.attrs
+                || new_expr.bracket_token != old_expr.bracket_token
+        }
+        (syn::Expr::Let(new_expr), syn::Expr::Let(old_expr)) => {
+            find_rsx_expr(&new_expr.expr, &old_expr.expr, rsx_calls)
+                || new_expr.attrs != old_expr.attrs
+                || new_expr.let_token != old_expr.let_token
+                || new_expr.pat != old_expr.pat
+                || new_expr.eq_token != old_expr.eq_token
+        }
+        (syn::Expr::Lit(new_expr), syn::Expr::Lit(old_expr)) => old_expr != new_expr,
+        (syn::Expr::Loop(new_expr), syn::Expr::Loop(old_expr)) => {
+            find_rsx_block(&new_expr.body, &old_expr.body, rsx_calls)
+                || new_expr.attrs != old_expr.attrs
+                || new_expr.label != old_expr.label
+                || new_expr.loop_token != old_expr.loop_token
+        }
+        (syn::Expr::Macro(new_expr), syn::Expr::Macro(old_expr)) => {
+            find_rsx_macro(&new_expr.mac, &old_expr.mac, rsx_calls)
+                || new_expr.attrs != old_expr.attrs
+        }
+        (syn::Expr::Match(new_expr), syn::Expr::Match(old_expr)) => {
+            if find_rsx_expr(&new_expr.expr, &old_expr.expr, rsx_calls) {
+                return true;
+            }
+            for (new_arm, old_arm) in new_expr.arms.iter().zip(old_expr.arms.iter()) {
+                match (&new_arm.guard, &old_arm.guard) {
+                    (Some((new_tok, new_expr)), Some((old_tok, old_expr))) => {
+                        if find_rsx_expr(new_expr, old_expr, rsx_calls) || new_tok != old_tok {
+                            return true;
+                        }
+                    }
+                    (None, None) => (),
+                    _ => return true,
+                }
+                if find_rsx_expr(&new_arm.body, &old_arm.body, rsx_calls)
+                    || new_arm.attrs != old_arm.attrs
+                    || new_arm.pat != old_arm.pat
+                    || new_arm.fat_arrow_token != old_arm.fat_arrow_token
+                    || new_arm.comma != old_arm.comma
+                {
+                    return true;
+                }
+            }
+            new_expr.attrs != old_expr.attrs
+                || new_expr.match_token != old_expr.match_token
+                || new_expr.brace_token != old_expr.brace_token
+        }
+        (syn::Expr::MethodCall(new_expr), syn::Expr::MethodCall(old_expr)) => {
+            if find_rsx_expr(&new_expr.receiver, &old_expr.receiver, rsx_calls) {
+                return true;
+            }
+            for (new_arg, old_arg) in new_expr.args.iter().zip(old_expr.args.iter()) {
+                if find_rsx_expr(new_arg, old_arg, rsx_calls) {
+                    return true;
+                }
+            }
+            new_expr.attrs != old_expr.attrs
+                || new_expr.dot_token != old_expr.dot_token
+                || new_expr.method != old_expr.method
+                || new_expr.turbofish != old_expr.turbofish
+                || new_expr.paren_token != old_expr.paren_token
+        }
+        (syn::Expr::Paren(new_expr), syn::Expr::Paren(old_expr)) => {
+            find_rsx_expr(&new_expr.expr, &old_expr.expr, rsx_calls)
+                || new_expr.attrs != old_expr.attrs
+                || new_expr.paren_token != old_expr.paren_token
+        }
+        (syn::Expr::Path(new_expr), syn::Expr::Path(old_expr)) => old_expr != new_expr,
+        (syn::Expr::Range(new_expr), syn::Expr::Range(old_expr)) => {
+            match (&new_expr.from, &old_expr.from) {
+                (Some(new_expr), Some(old_expr)) => {
+                    if find_rsx_expr(new_expr, old_expr, rsx_calls) {
+                        return true;
+                    }
+                }
+                (None, None) => (),
+                _ => return true,
+            }
+            match (&new_expr.to, &old_expr.to) {
+                (Some(new_inner), Some(old_inner)) => {
+                    find_rsx_expr(new_inner, old_inner, rsx_calls)
+                        || new_expr.attrs != old_expr.attrs
+                        || new_expr.limits != old_expr.limits
+                }
+                (None, None) => {
+                    new_expr.attrs != old_expr.attrs || new_expr.limits != old_expr.limits
+                }
+                _ => true,
+            }
+        }
+        (syn::Expr::Reference(new_expr), syn::Expr::Reference(old_expr)) => {
+            find_rsx_expr(&new_expr.expr, &old_expr.expr, rsx_calls)
+                || new_expr.attrs != old_expr.attrs
+                || new_expr.and_token != old_expr.and_token
+                || new_expr.mutability != old_expr.mutability
+        }
+        (syn::Expr::Repeat(new_expr), syn::Expr::Repeat(old_expr)) => {
+            find_rsx_expr(&new_expr.expr, &old_expr.expr, rsx_calls)
+                || find_rsx_expr(&new_expr.len, &old_expr.len, rsx_calls)
+                || new_expr.attrs != old_expr.attrs
+                || new_expr.bracket_token != old_expr.bracket_token
+                || new_expr.semi_token != old_expr.semi_token
+        }
+        (syn::Expr::Return(new_expr), syn::Expr::Return(old_expr)) => {
+            match (&new_expr.expr, &old_expr.expr) {
+                (Some(new_inner), Some(old_inner)) => {
+                    find_rsx_expr(new_inner, old_inner, rsx_calls)
+                        || new_expr.attrs != old_expr.attrs
+                        || new_expr.return_token != old_expr.return_token
+                }
+                (None, None) => {
+                    new_expr.attrs != old_expr.attrs
+                        || new_expr.return_token != old_expr.return_token
+                }
+                _ => true,
+            }
+        }
+        (syn::Expr::Struct(new_expr), syn::Expr::Struct(old_expr)) => {
+            match (&new_expr.rest, &old_expr.rest) {
+                (Some(new_expr), Some(old_expr)) => {
+                    if find_rsx_expr(new_expr, old_expr, rsx_calls) {
+                        return true;
+                    }
+                }
+                (None, None) => (),
+                _ => return true,
+            }
+            for (new_field, old_field) in new_expr.fields.iter().zip(old_expr.fields.iter()) {
+                if find_rsx_expr(&new_field.expr, &old_field.expr, rsx_calls)
+                    || new_field.attrs != old_field.attrs
+                    || new_field.member != old_field.member
+                    || new_field.colon_token != old_field.colon_token
+                {
+                    return true;
+                }
+            }
+            new_expr.attrs != old_expr.attrs
+                || new_expr.path != old_expr.path
+                || new_expr.brace_token != old_expr.brace_token
+                || new_expr.dot2_token != old_expr.dot2_token
+        }
+        (syn::Expr::Try(new_expr), syn::Expr::Try(old_expr)) => {
+            find_rsx_expr(&new_expr.expr, &old_expr.expr, rsx_calls)
+                || new_expr.attrs != old_expr.attrs
+                || new_expr.question_token != old_expr.question_token
+        }
+        (syn::Expr::TryBlock(new_expr), syn::Expr::TryBlock(old_expr)) => {
+            find_rsx_block(&new_expr.block, &old_expr.block, rsx_calls)
+                || new_expr.attrs != old_expr.attrs
+                || new_expr.try_token != old_expr.try_token
+        }
+        (syn::Expr::Tuple(new_expr), syn::Expr::Tuple(old_expr)) => {
+            for (new_el, old_el) in new_expr.elems.iter().zip(old_expr.elems.iter()) {
+                if find_rsx_expr(new_el, old_el, rsx_calls) {
+                    return true;
+                }
+            }
+            new_expr.attrs != old_expr.attrs || new_expr.paren_token != old_expr.paren_token
+        }
+        (syn::Expr::Type(new_expr), syn::Expr::Type(old_expr)) => {
+            find_rsx_expr(&new_expr.expr, &old_expr.expr, rsx_calls)
+                || new_expr.attrs != old_expr.attrs
+                || new_expr.colon_token != old_expr.colon_token
+                || new_expr.ty != old_expr.ty
+        }
+        (syn::Expr::Unary(new_expr), syn::Expr::Unary(old_expr)) => {
+            find_rsx_expr(&new_expr.expr, &old_expr.expr, rsx_calls)
+                || new_expr.attrs != old_expr.attrs
+                || new_expr.op != old_expr.op
+        }
+        (syn::Expr::Unsafe(new_expr), syn::Expr::Unsafe(old_expr)) => {
+            find_rsx_block(&new_expr.block, &old_expr.block, rsx_calls)
+                || new_expr.attrs != old_expr.attrs
+                || new_expr.unsafe_token != old_expr.unsafe_token
+        }
+        (syn::Expr::While(new_expr), syn::Expr::While(old_expr)) => {
+            find_rsx_expr(&new_expr.cond, &old_expr.cond, rsx_calls)
+                || find_rsx_block(&new_expr.body, &old_expr.body, rsx_calls)
+                || new_expr.attrs != old_expr.attrs
+                || new_expr.label != old_expr.label
+                || new_expr.while_token != old_expr.while_token
+        }
+        (syn::Expr::Yield(new_expr), syn::Expr::Yield(old_expr)) => {
+            match (&new_expr.expr, &old_expr.expr) {
+                (Some(new_inner), Some(old_inner)) => {
+                    find_rsx_expr(new_inner, old_inner, rsx_calls)
+                        || new_expr.attrs != old_expr.attrs
+                        || new_expr.yield_token != old_expr.yield_token
+                }
+                (None, None) => {
+                    new_expr.attrs != old_expr.attrs || new_expr.yield_token != old_expr.yield_token
+                }
+                _ => true,
+            }
+        }
+        (syn::Expr::Verbatim(_), syn::Expr::Verbatim(_)) => false,
+        _ => true,
+    }
+}
+
+fn find_rsx_macro(
+    new_mac: &syn::Macro,
+    old_mac: &syn::Macro,
+    rsx_calls: &mut Vec<(Macro, TokenStream)>,
+) -> bool {
+    if matches!(
+        new_mac
+            .path
+            .get_ident()
+            .map(|ident| ident.to_string())
+            .as_deref(),
+        Some("rsx" | "render")
+    ) && matches!(
+        old_mac
+            .path
+            .get_ident()
+            .map(|ident| ident.to_string())
+            .as_deref(),
+        Some("rsx" | "render")
+    ) {
+        rsx_calls.push((old_mac.clone(), new_mac.tokens.clone()));
+        false
+    } else {
+        new_mac != old_mac
+    }
+}

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

@@ -0,0 +1,19 @@
+pub trait HotReloadingContext {
+    fn map_attribute(
+        element_name_rust: &str,
+        attribute_name_rust: &str,
+    ) -> Option<(&'static str, Option<&'static str>)>;
+    fn map_element(element_name_rust: &str) -> Option<(&'static str, Option<&'static str>)>;
+}
+
+pub struct Empty;
+
+impl HotReloadingContext for Empty {
+    fn map_attribute(_: &str, _: &str) -> Option<(&'static str, Option<&'static str>)> {
+        None
+    }
+
+    fn map_element(_: &str) -> Option<(&'static str, Option<&'static str>)> {
+        None
+    }
+}

+ 122 - 0
packages/rsx/src/hot_reload/hot_reloading_file_map.rs

@@ -0,0 +1,122 @@
+use crate::{CallBody, HotReloadingContext};
+use dioxus_core::Template;
+pub use proc_macro2::TokenStream;
+pub use std::collections::HashMap;
+use std::path::PathBuf;
+pub use std::sync::Mutex;
+pub use std::time::SystemTime;
+pub use std::{fs, io, path::Path};
+pub use std::{fs::File, io::Read};
+pub use syn::__private::ToTokens;
+use syn::spanned::Spanned;
+
+use super::hot_reload_diff::{find_rsx, DiffResult};
+
+pub enum UpdateResult {
+    UpdatedRsx(Vec<Template<'static>>),
+    NeedsRebuild,
+}
+
+pub struct FileMap<Ctx: HotReloadingContext> {
+    pub map: HashMap<PathBuf, (String, Option<Template<'static>>)>,
+    phantom: std::marker::PhantomData<Ctx>,
+}
+
+impl<Ctx: HotReloadingContext> FileMap<Ctx> {
+    /// Create a new FileMap from a crate directory
+    pub fn new(path: PathBuf) -> Self {
+        fn find_rs_files(
+            root: PathBuf,
+        ) -> io::Result<HashMap<PathBuf, (String, Option<Template<'static>>)>> {
+            let mut files = HashMap::new();
+            if root.is_dir() {
+                for entry in (fs::read_dir(root)?).flatten() {
+                    let path = entry.path();
+                    files.extend(find_rs_files(path)?);
+                }
+            } else if root.extension().and_then(|s| s.to_str()) == Some("rs") {
+                if let Ok(mut file) = File::open(root.clone()) {
+                    let mut src = String::new();
+                    file.read_to_string(&mut src).expect("Unable to read file");
+                    files.insert(root, (src, None));
+                }
+            }
+            Ok(files)
+        }
+
+        let result = Self {
+            map: find_rs_files(path).unwrap(),
+            phantom: std::marker::PhantomData,
+        };
+        result
+    }
+
+    /// Try to update the rsx in a file
+    pub fn update_rsx(&mut self, file_path: &Path, crate_dir: &Path) -> UpdateResult {
+        let mut file = File::open(file_path).unwrap();
+        let mut src = String::new();
+        file.read_to_string(&mut src).expect("Unable to read file");
+        if let Ok(syntax) = syn::parse_file(&src) {
+            if let Some((old_src, template_slot)) = self.map.get_mut(file_path) {
+                if let Ok(old) = syn::parse_file(old_src) {
+                    match find_rsx(&syntax, &old) {
+                        DiffResult::CodeChanged => {
+                            self.map.insert(file_path.to_path_buf(), (src, None));
+                        }
+                        DiffResult::RsxChanged(changed) => {
+                            let mut messages: Vec<Template<'static>> = Vec::new();
+                            for (old, new) in changed.into_iter() {
+                                let old_start = old.span().start();
+
+                                if let (Ok(old_call_body), Ok(new_call_body)) = (
+                                    syn::parse2::<CallBody>(old.tokens),
+                                    syn::parse2::<CallBody>(new),
+                                ) {
+                                    if let Ok(file) = file_path.strip_prefix(crate_dir) {
+                                        let line = old_start.line;
+                                        let column = old_start.column + 1;
+                                        let location = file.display().to_string()
+                                        + ":"
+                                        + &line.to_string()
+                                        + ":"
+                                        + &column.to_string()
+                                        // the byte index doesn't matter, but dioxus needs it
+                                        + ":0";
+
+                                        if let Some(template) = new_call_body
+                                            .update_template::<Ctx>(
+                                                Some(old_call_body),
+                                                Box::leak(location.into_boxed_str()),
+                                            )
+                                        {
+                                            // dioxus cannot handle empty templates
+                                            if template.roots.is_empty() {
+                                                return UpdateResult::NeedsRebuild;
+                                            } else {
+                                                // if the template is the same, don't send it
+                                                if let Some(old_template) = template_slot {
+                                                    if old_template == &template {
+                                                        continue;
+                                                    }
+                                                }
+                                                *template_slot = Some(template);
+                                                messages.push(template);
+                                            }
+                                        } else {
+                                            return UpdateResult::NeedsRebuild;
+                                        }
+                                    }
+                                }
+                            }
+                            return UpdateResult::UpdatedRsx(messages);
+                        }
+                    }
+                }
+            } else {
+                // if this is a new file, rebuild the project
+                *self = FileMap::new(crate_dir.to_path_buf());
+            }
+        }
+        UpdateResult::NeedsRebuild
+    }
+}

+ 6 - 0
packages/rsx/src/hot_reload/mod.rs

@@ -0,0 +1,6 @@
+mod hot_reload_diff;
+pub use hot_reload_diff::*;
+mod hot_reloading_context;
+pub use hot_reloading_context::*;
+mod hot_reloading_file_map;
+pub use hot_reloading_file_map::*;

+ 1 - 1
packages/rsx/src/ifmt.rs

@@ -21,7 +21,7 @@ pub struct IfmtInput {
 
 impl IfmtInput {
     pub fn is_static(&self) -> bool {
-        matches!(self.segments.as_slice(), &[Segment::Literal(_)])
+        matches!(self.segments.as_slice(), &[Segment::Literal(_)] | &[])
     }
 }
 

+ 470 - 24
packages/rsx/src/lib.rs

@@ -15,14 +15,19 @@
 mod errors;
 mod component;
 mod element;
+pub mod hot_reload;
 mod ifmt;
 mod node;
-mod template;
+
+use std::{collections::HashMap, fmt::Debug, hash::Hash};
 
 // Re-export the namespaces into each other
 pub use component::*;
+use dioxus_core::{Template, TemplateAttribute, TemplateNode};
 pub use element::*;
+pub use hot_reload::HotReloadingContext;
 pub use ifmt::*;
+use internment::Intern;
 pub use node::*;
 
 // imports
@@ -33,12 +38,32 @@ use syn::{
     Result, Token,
 };
 
+// interns a object into a static object, resusing the value if it already exists
+fn intern<T: Eq + Hash + Send + Sync + ?Sized + 'static>(s: impl Into<Intern<T>>) -> &'static T {
+    s.into().as_ref()
+}
+
 /// Fundametnally, every CallBody is a template
 #[derive(Default, Debug)]
 pub struct CallBody {
     pub roots: Vec<BodyNode>,
 }
 
+impl CallBody {
+    /// This will try to create a new template from the current body and the previous body. This will return None if the rsx has some dynamic part that has changed.
+    /// This function intentionally leaks memory to create a static template.
+    /// Keeping the template static allows us to simplify the core of dioxus and leaking memory in dev mode is less of an issue.
+    /// the previous_location is the location of the previous template at the time the template was originally compiled.
+    pub fn update_template<Ctx: HotReloadingContext>(
+        &self,
+        template: Option<CallBody>,
+        location: &'static str,
+    ) -> Option<Template<'static>> {
+        let mut renderer: TemplateRenderer = TemplateRenderer { roots: &self.roots };
+        renderer.update_template::<Ctx>(template, location)
+    }
+}
+
 impl Parse for CallBody {
     fn parse(input: ParseStream) -> Result<Self> {
         let mut roots = Vec::new();
@@ -75,7 +100,7 @@ pub struct RenderCallBody(pub CallBody);
 
 impl ToTokens for RenderCallBody {
     fn to_tokens(&self, out_tokens: &mut TokenStream2) {
-        let body = TemplateRenderer {
+        let body: TemplateRenderer = TemplateRenderer {
             roots: &self.0.roots,
         };
 
@@ -92,15 +117,49 @@ pub struct TemplateRenderer<'a> {
     pub roots: &'a [BodyNode],
 }
 
+impl<'a> TemplateRenderer<'a> {
+    fn update_template<Ctx: HotReloadingContext>(
+        &mut self,
+        previous_call: Option<CallBody>,
+        location: &'static str,
+    ) -> Option<Template<'static>> {
+        let mut mapping = previous_call.map(|call| DynamicMapping::from(call.roots));
+
+        let mut context = DynamicContext::default();
+
+        let mut roots = Vec::new();
+        for (idx, root) in self.roots.iter().enumerate() {
+            context.current_path.push(idx as u8);
+            roots.push(context.update_node::<Ctx>(root, &mut mapping)?);
+            context.current_path.pop();
+        }
+
+        Some(Template {
+            name: location,
+            roots: intern(roots.as_slice()),
+            node_paths: intern(
+                context
+                    .node_paths
+                    .into_iter()
+                    .map(|path| intern(path.as_slice()))
+                    .collect::<Vec<_>>()
+                    .as_slice(),
+            ),
+            attr_paths: intern(
+                context
+                    .attr_paths
+                    .into_iter()
+                    .map(|path| intern(path.as_slice()))
+                    .collect::<Vec<_>>()
+                    .as_slice(),
+            ),
+        })
+    }
+}
+
 impl<'a> ToTokens for TemplateRenderer<'a> {
     fn to_tokens(&self, out_tokens: &mut TokenStream2) {
-        let mut context = DynamicContext {
-            dynamic_nodes: vec![],
-            dynamic_attributes: vec![],
-            current_path: vec![],
-            attr_paths: vec![],
-            node_paths: vec![],
-        };
+        let mut context = DynamicContext::default();
 
         let key = match self.roots.get(0) {
             Some(BodyNode::Element(el)) if self.roots.len() == 1 => el.key.clone(),
@@ -127,7 +186,6 @@ impl<'a> ToTokens for TemplateRenderer<'a> {
         });
 
         // Render and release the mutable borrow on context
-        let num_roots = self.roots.len();
         let roots = quote! { #( #root_printer ),* };
         let node_printer = &context.dynamic_nodes;
         let dyn_attr_printer = &context.dynamic_attributes;
@@ -152,16 +210,106 @@ impl<'a> ToTokens for TemplateRenderer<'a> {
             ::dioxus::core::VNode {
                 parent: None,
                 key: #key_tokens,
-                template: TEMPLATE,
-                root_ids: std::cell::Cell::from_mut( __cx.bump().alloc([None; #num_roots]) as &mut _).as_slice_of_cells(),
+                template: std::cell::Cell::new(TEMPLATE),
+                root_ids: Default::default(),
                 dynamic_nodes: __cx.bump().alloc([ #( #node_printer ),* ]),
                 dynamic_attrs: __cx.bump().alloc([ #( #dyn_attr_printer ),* ]),
             }
         });
     }
 }
-// As we print out the dynamic nodes, we want to keep track of them in a linear fashion
-// We'll use the size of the vecs to determine the index of the dynamic node in the final
+
+#[derive(Default, Debug)]
+struct DynamicMapping {
+    attribute_to_idx: HashMap<ElementAttr, Vec<usize>>,
+    last_attribute_idx: usize,
+    node_to_idx: HashMap<BodyNode, Vec<usize>>,
+    last_element_idx: usize,
+}
+
+impl DynamicMapping {
+    fn from(nodes: Vec<BodyNode>) -> Self {
+        let mut new = Self::default();
+        for node in nodes {
+            new.add_node(node);
+        }
+        new
+    }
+
+    fn get_attribute_idx(&mut self, attr: &ElementAttr) -> Option<usize> {
+        self.attribute_to_idx
+            .get_mut(attr)
+            .and_then(|idxs| idxs.pop())
+    }
+
+    fn get_node_idx(&mut self, node: &BodyNode) -> Option<usize> {
+        self.node_to_idx.get_mut(node).and_then(|idxs| idxs.pop())
+    }
+
+    fn insert_attribute(&mut self, attr: ElementAttr) -> usize {
+        let idx = self.last_attribute_idx;
+        self.last_attribute_idx += 1;
+
+        self.attribute_to_idx
+            .entry(attr)
+            .or_insert_with(Vec::new)
+            .push(idx);
+
+        idx
+    }
+
+    fn insert_node(&mut self, node: BodyNode) -> usize {
+        let idx = self.last_element_idx;
+        self.last_element_idx += 1;
+
+        self.node_to_idx
+            .entry(node)
+            .or_insert_with(Vec::new)
+            .push(idx);
+
+        idx
+    }
+
+    fn add_node(&mut self, node: BodyNode) {
+        match node {
+            BodyNode::Element(el) => {
+                for attr in el.attributes {
+                    match &attr.attr {
+                        ElementAttr::CustomAttrText { value, .. }
+                        | ElementAttr::AttrText { value, .. }
+                            if value.is_static() => {}
+
+                        ElementAttr::AttrExpression { .. }
+                        | ElementAttr::AttrText { .. }
+                        | ElementAttr::CustomAttrText { .. }
+                        | ElementAttr::CustomAttrExpression { .. }
+                        | ElementAttr::EventTokens { .. } => {
+                            self.insert_attribute(attr.attr);
+                        }
+                    }
+                }
+
+                for child in el.children {
+                    self.add_node(child);
+                }
+            }
+
+            BodyNode::Text(text) if text.is_static() => {}
+
+            BodyNode::RawExpr(_)
+            | BodyNode::Text(_)
+            | BodyNode::ForLoop(_)
+            | BodyNode::IfChain(_)
+            | BodyNode::Component(_) => {
+                self.insert_node(node);
+            }
+        }
+    }
+}
+
+// As we create the dynamic nodes, we want to keep track of them in a linear fashion
+// We'll use the size of the vecs to determine the index of the dynamic node in the final output
+#[derive(Default, Debug)]
 pub struct DynamicContext<'a> {
     dynamic_nodes: Vec<&'a BodyNode>,
     dynamic_attributes: Vec<&'a ElementAttrNamed>,
@@ -172,20 +320,112 @@ pub struct DynamicContext<'a> {
 }
 
 impl<'a> DynamicContext<'a> {
-    fn render_static_node(&mut self, root: &'a BodyNode) -> TokenStream2 {
+    fn update_node<Ctx: HotReloadingContext>(
+        &mut self,
+        root: &'a BodyNode,
+        mapping: &mut Option<DynamicMapping>,
+    ) -> Option<TemplateNode<'static>> {
         match root {
             BodyNode::Element(el) => {
-                let el_name = &el.name;
+                let element_name_rust = el.name.to_string();
+
+                let mut static_attrs = Vec::new();
+                for attr in &el.attributes {
+                    match &attr.attr {
+                        ElementAttr::AttrText { name, value } if value.is_static() => {
+                            let value = value.source.as_ref().unwrap();
+                            let attribute_name_rust = name.to_string();
+                            let (name, namespace) =
+                                Ctx::map_attribute(&element_name_rust, &attribute_name_rust)
+                                    .unwrap_or((intern(attribute_name_rust.as_str()), None));
+                            static_attrs.push(TemplateAttribute::Static {
+                                name,
+                                namespace,
+                                value: intern(value.value().as_str()),
+                            })
+                        }
 
-                // dynamic attributes
-                // [0]
-                // [0, 1]
-                // [0, 1]
-                // [0, 1]
-                // [0, 1, 2]
-                // [0, 2]
-                // [0, 2, 1]
+                        ElementAttr::CustomAttrText { name, value } if value.is_static() => {
+                            let value = value.source.as_ref().unwrap();
+                            static_attrs.push(TemplateAttribute::Static {
+                                name: intern(name.value().as_str()),
+                                namespace: None,
+                                value: intern(value.value().as_str()),
+                            })
+                        }
+
+                        ElementAttr::AttrExpression { .. }
+                        | ElementAttr::AttrText { .. }
+                        | ElementAttr::CustomAttrText { .. }
+                        | ElementAttr::CustomAttrExpression { .. }
+                        | ElementAttr::EventTokens { .. } => {
+                            let idx = match mapping {
+                                Some(mapping) => mapping.get_attribute_idx(&attr.attr)?,
+                                None => self.dynamic_attributes.len(),
+                            };
+                            self.dynamic_attributes.push(attr);
+
+                            if self.attr_paths.len() <= idx {
+                                self.attr_paths.resize_with(idx + 1, Vec::new);
+                            }
+                            self.attr_paths[idx] = self.current_path.clone();
+                            static_attrs.push(TemplateAttribute::Dynamic { id: idx })
+                        }
+                    }
+                }
+
+                let mut children = Vec::new();
+                for (idx, root) in el.children.iter().enumerate() {
+                    self.current_path.push(idx as u8);
+                    children.push(self.update_node::<Ctx>(root, mapping)?);
+                    self.current_path.pop();
+                }
+
+                let (tag, namespace) = Ctx::map_element(&element_name_rust)
+                    .unwrap_or((intern(element_name_rust.as_str()), None));
+                Some(TemplateNode::Element {
+                    tag,
+                    namespace,
+                    attrs: intern(static_attrs.into_boxed_slice()),
+                    children: intern(children.as_slice()),
+                })
+            }
+
+            BodyNode::Text(text) if text.is_static() => {
+                let text = text.source.as_ref().unwrap();
+                Some(TemplateNode::Text {
+                    text: intern(text.value().as_str()),
+                })
+            }
+
+            BodyNode::RawExpr(_)
+            | BodyNode::Text(_)
+            | BodyNode::ForLoop(_)
+            | BodyNode::IfChain(_)
+            | BodyNode::Component(_) => {
+                let idx = match mapping {
+                    Some(mapping) => mapping.get_node_idx(root)?,
+                    None => self.dynamic_nodes.len(),
+                };
+                self.dynamic_nodes.push(root);
 
+                if self.node_paths.len() <= idx {
+                    self.node_paths.resize_with(idx + 1, Vec::new);
+                }
+                self.node_paths[idx] = self.current_path.clone();
+
+                Some(match root {
+                    BodyNode::Text(_) => TemplateNode::DynamicText { id: idx },
+                    _ => TemplateNode::Dynamic { id: idx },
+                })
+            }
+        }
+    }
+
+    fn render_static_node(&mut self, root: &'a BodyNode) -> TokenStream2 {
+        match root {
+            BodyNode::Element(el) => {
+                let el_name = &el.name;
                 let static_attrs = el.attributes.iter().map(|attr| match &attr.attr {
                     ElementAttr::AttrText { name, value } if value.is_static() => {
                         let value = value.to_static().unwrap();
@@ -273,3 +513,209 @@ impl<'a> DynamicContext<'a> {
         }
     }
 }
+
+#[test]
+fn create_template() {
+    let input = quote! {
+        svg {
+            width: 100,
+            height: "100px",
+            "width2": 100,
+            "height2": "100px",
+            p {
+                "hello world"
+            }
+            (0..10).map(|i| rsx!{"{i}"})
+        }
+    };
+
+    struct Mock;
+
+    impl HotReloadingContext for Mock {
+        fn map_attribute(
+            element_name_rust: &str,
+            attribute_name_rust: &str,
+        ) -> Option<(&'static str, Option<&'static str>)> {
+            match element_name_rust {
+                "svg" => match attribute_name_rust {
+                    "width" => Some(("width", Some("style"))),
+                    "height" => Some(("height", Some("style"))),
+                    _ => None,
+                },
+                _ => None,
+            }
+        }
+
+        fn map_element(element_name_rust: &str) -> Option<(&'static str, Option<&'static str>)> {
+            match element_name_rust {
+                "svg" => Some(("svg", Some("svg"))),
+                _ => None,
+            }
+        }
+    }
+
+    let call_body: CallBody = syn::parse2(input).unwrap();
+
+    let template = call_body.update_template::<Mock>(None, "testing").unwrap();
+
+    dbg!(template);
+
+    assert_eq!(
+        template,
+        Template {
+            name: "testing",
+            roots: &[TemplateNode::Element {
+                tag: "svg",
+                namespace: Some("svg"),
+                attrs: &[
+                    TemplateAttribute::Dynamic { id: 0 },
+                    TemplateAttribute::Static {
+                        name: "height",
+                        namespace: Some("style"),
+                        value: "100px",
+                    },
+                    TemplateAttribute::Dynamic { id: 1 },
+                    TemplateAttribute::Static {
+                        name: "height2",
+                        namespace: None,
+                        value: "100px",
+                    },
+                ],
+                children: &[
+                    TemplateNode::Element {
+                        tag: "p",
+                        namespace: None,
+                        attrs: &[],
+                        children: &[TemplateNode::Text {
+                            text: "hello world",
+                        }],
+                    },
+                    TemplateNode::Dynamic { id: 0 }
+                ],
+            }],
+            node_paths: &[&[0, 1,],],
+            attr_paths: &[&[0,], &[0,],],
+        },
+    )
+}
+
+#[test]
+fn diff_template() {
+    use dioxus_core::Scope;
+    #[allow(unused, non_snake_case)]
+    fn Comp(_: Scope) -> dioxus_core::Element {
+        None
+    }
+
+    let input = quote! {
+        svg {
+            width: 100,
+            height: "100px",
+            "width2": 100,
+            "height2": "100px",
+            p {
+                "hello world"
+            }
+            (0..10).map(|i| rsx!{"{i}"}),
+            (0..10).map(|i| rsx!{"{i}"}),
+            (0..11).map(|i| rsx!{"{i}"}),
+            Comp{}
+        }
+    };
+
+    #[derive(Debug)]
+    struct Mock;
+
+    impl HotReloadingContext for Mock {
+        fn map_attribute(
+            element_name_rust: &str,
+            attribute_name_rust: &str,
+        ) -> Option<(&'static str, Option<&'static str>)> {
+            match element_name_rust {
+                "svg" => match attribute_name_rust {
+                    "width" => Some(("width", Some("style"))),
+                    "height" => Some(("height", Some("style"))),
+                    _ => None,
+                },
+                _ => None,
+            }
+        }
+
+        fn map_element(element_name_rust: &str) -> Option<(&'static str, Option<&'static str>)> {
+            match element_name_rust {
+                "svg" => Some(("svg", Some("svg"))),
+                _ => None,
+            }
+        }
+    }
+
+    let call_body1: CallBody = syn::parse2(input).unwrap();
+
+    let template = call_body1.update_template::<Mock>(None, "testing").unwrap();
+    dbg!(template);
+
+    // scrambling the attributes should not cause a full rebuild
+    let input = quote! {
+        div {
+            "width2": 100,
+            height: "100px",
+            "height2": "100px",
+            width: 100,
+            Comp{}
+            (0..11).map(|i| rsx!{"{i}"}),
+            (0..10).map(|i| rsx!{"{i}"}),
+            (0..10).map(|i| rsx!{"{i}"}),
+            p {
+                "hello world"
+            }
+        }
+    };
+
+    let call_body2: CallBody = syn::parse2(input).unwrap();
+
+    let template = call_body2
+        .update_template::<Mock>(Some(call_body1), "testing")
+        .unwrap();
+    dbg!(template);
+
+    assert_eq!(
+        template,
+        Template {
+            name: "testing",
+            roots: &[TemplateNode::Element {
+                tag: "div",
+                namespace: None,
+                attrs: &[
+                    TemplateAttribute::Dynamic { id: 1 },
+                    TemplateAttribute::Static {
+                        name: "height",
+                        namespace: None,
+                        value: "100px",
+                    },
+                    TemplateAttribute::Static {
+                        name: "height2",
+                        namespace: None,
+                        value: "100px",
+                    },
+                    TemplateAttribute::Dynamic { id: 0 },
+                ],
+                children: &[
+                    TemplateNode::Dynamic { id: 3 },
+                    TemplateNode::Dynamic { id: 2 },
+                    TemplateNode::Dynamic { id: 1 },
+                    TemplateNode::Dynamic { id: 0 },
+                    TemplateNode::Element {
+                        tag: "p",
+                        namespace: None,
+                        attrs: &[],
+                        children: &[TemplateNode::Text {
+                            text: "hello world",
+                        }],
+                    },
+                ],
+            }],
+            node_paths: &[&[0, 3], &[0, 2], &[0, 1], &[0, 0]],
+            attr_paths: &[&[0], &[0]]
+        },
+    )
+}

+ 1 - 3
packages/rsx/src/node.rs

@@ -1,5 +1,3 @@
-// use crate::{raw_expr::RawExprNode, text::TextNode};
-
 use super::*;
 
 use proc_macro2::{Span, TokenStream as TokenStream2};
@@ -143,7 +141,7 @@ impl ToTokens for BodyNode {
                     pat, expr, body, ..
                 } = exp;
 
-                let renderer = TemplateRenderer { roots: body };
+                let renderer: TemplateRenderer = TemplateRenderer { roots: body };
 
                 tokens.append_all(quote! {
                      __cx.make_node(

+ 0 - 1064
packages/rsx/src/template.rs

@@ -1,1064 +0,0 @@
-// use proc_macro2::TokenStream;
-// use quote::TokenStreamExt;
-// use quote::{quote, ToTokens};
-// use syn::{Expr, Ident, LitStr};
-
-// #[cfg(any(feature = "hot-reload", debug_assertions))]
-// pub fn try_parse_template(
-//     rsx: &str,
-//     location: OwnedCodeLocation,
-//     previous_template: Option<DynamicTemplateContextBuilder>,
-// ) -> Result<(OwnedTemplate, DynamicTemplateContextBuilder), Error> {
-//     use crate::CallBody;
-
-//     let call_body: CallBody =
-//         syn::parse_str(rsx).map_err(|e| Error::ParseError(ParseError::new(e, location.clone())))?;
-//     let mut template_builder = TemplateBuilder::from_roots_always(call_body.roots);
-//     if let Some(prev) = previous_template {
-//         template_builder = template_builder
-//             .try_switch_dynamic_context(prev)
-//             .ok_or_else(|| {
-//                 Error::RecompileRequiredError(RecompileReason::Variable(
-//                     "dynamic context updated".to_string(),
-//                 ))
-//             })?;
-//     }
-//     let dyn_ctx = template_builder.dynamic_context.clone();
-//     Ok((template_builder.try_into_owned(&location)?, dyn_ctx))
-// }
-
-// use crate::{BodyNode, ElementAttr, FormattedSegment, Segment};
-
-// struct TemplateElementBuilder {
-//     tag: Ident,
-//     attributes: Vec<TemplateAttributeBuilder>,
-//     children: Vec<TemplateNodeId>,
-//     listeners: Vec<usize>,
-//     locally_static: bool,
-// }
-
-// #[cfg(any(feature = "hot-reload", debug_assertions))]
-// type OwndedTemplateElement = TemplateElement<
-//     Vec<TemplateAttribute<OwnedAttributeValue>>,
-//     OwnedAttributeValue,
-//     Vec<TemplateNodeId>,
-//     Vec<usize>,
-// >;
-
-// impl TemplateElementBuilder {
-//     #[cfg(any(feature = "hot-reload", debug_assertions))]
-//     fn try_into_owned(self, location: &OwnedCodeLocation) -> Result<OwndedTemplateElement, Error> {
-//         let Self {
-//             tag,
-//             attributes,
-//             children,
-//             listeners,
-//             ..
-//         } = self;
-//         let (element_tag, element_ns) =
-//             element_to_static_str(&tag.to_string()).ok_or_else(|| {
-//                 Error::ParseError(ParseError::new(
-//                     syn::Error::new(tag.span(), format!("unknown element: {}", tag)),
-//                     location.clone(),
-//                 ))
-//             })?;
-
-//         let mut owned_attributes = Vec::new();
-//         for a in attributes {
-//             owned_attributes.push(a.try_into_owned(location, element_tag, element_ns)?);
-//         }
-
-//         Ok(TemplateElement::new(
-//             element_tag,
-//             element_ns,
-//             owned_attributes,
-//             children,
-//             listeners,
-//         ))
-//     }
-// }
-
-// impl ToTokens for TemplateElementBuilder {
-//     fn to_tokens(&self, tokens: &mut TokenStream) {
-//         let Self {
-//             tag,
-//             attributes,
-//             children,
-//             listeners,
-//             ..
-//         } = self;
-//         let children = children.iter().map(|id| {
-//             let raw = id.0;
-//             quote! {TemplateNodeId(#raw)}
-//         });
-//         tokens.append_all(quote! {
-//             TemplateElement::new(
-//                 dioxus_elements::#tag::TAG_NAME,
-//                 dioxus_elements::#tag::NAME_SPACE,
-//                 &[#(#attributes),*],
-//                 &[#(#children),*],
-//                 &[#(#listeners),*],
-//             )
-//         })
-//     }
-// }
-
-// enum AttributeName {
-//     Ident(Ident),
-//     Str(LitStr),
-// }
-
-// struct TemplateAttributeBuilder {
-//     element_tag: Ident,
-//     name: AttributeName,
-//     value: TemplateAttributeValue<OwnedAttributeValue>,
-// }
-
-// impl TemplateAttributeBuilder {
-//     #[cfg(any(feature = "hot-reload", debug_assertions))]
-//     fn try_into_owned(
-//         self,
-//         location: &OwnedCodeLocation,
-//         element_tag: &'static str,
-//         element_ns: Option<&'static str>,
-//     ) -> Result<TemplateAttribute<OwnedAttributeValue>, Error> {
-//         let Self { name, value, .. } = self;
-//         let (name, span, literal) = match name {
-//             AttributeName::Ident(name) => (name.to_string(), name.span(), false),
-//             AttributeName::Str(name) => (name.value(), name.span(), true),
-//         };
-//         let (name, namespace, volatile) = attrbute_to_static_str(&name, element_tag, element_ns)
-//             .ok_or_else(|| {
-//                 if literal {
-//                     // literals will be captured when a full recompile is triggered
-//                     Error::RecompileRequiredError(RecompileReason::Attribute(name.to_string()))
-//                 } else {
-//                     Error::ParseError(ParseError::new(
-//                         syn::Error::new(span, format!("unknown attribute: {}", name)),
-//                         location.clone(),
-//                     ))
-//                 }
-//             })?;
-//         let attribute = AttributeDiscription {
-//             name,
-//             namespace,
-//             volatile,
-//         };
-//         Ok(TemplateAttribute { value, attribute })
-//     }
-// }
-
-// impl ToTokens for TemplateAttributeBuilder {
-//     fn to_tokens(&self, tokens: &mut TokenStream) {
-//         let Self {
-//             element_tag,
-//             name,
-//             value,
-//         } = self;
-//         let value = match value {
-//             TemplateAttributeValue::Static(val) => {
-//                 let val = match val {
-//                     OwnedAttributeValue::Text(txt) => quote! {StaticAttributeValue::Text(#txt)},
-//                     OwnedAttributeValue::Float32(f) => quote! {StaticAttributeValue::Float32(#f)},
-//                     OwnedAttributeValue::Float64(f) => quote! {StaticAttributeValue::Float64(#f)},
-//                     OwnedAttributeValue::Int32(i) => quote! {StaticAttributeValue::Int32(#i)},
-//                     OwnedAttributeValue::Int64(i) => quote! {StaticAttributeValue::Int64(#i)},
-//                     OwnedAttributeValue::Uint32(u) => quote! {StaticAttributeValue::Uint32(#u)},
-//                     OwnedAttributeValue::Uint64(u) => quote! {StaticAttributeValue::Uint64(#u)},
-//                     OwnedAttributeValue::Bool(b) => quote! {StaticAttributeValue::Bool(#b)},
-//                     OwnedAttributeValue::Vec3Float(f1, f2, f3) => {
-//                         quote! {StaticAttributeValue::Vec3Float(#f1, #f2, #f3)}
-//                     }
-//                     OwnedAttributeValue::Vec3Int(f1, f2, f3) => {
-//                         quote! {StaticAttributeValue::Vec3Int(#f1, #f2, #f3)}
-//                     }
-//                     OwnedAttributeValue::Vec3Uint(f1, f2, f3) => {
-//                         quote! {StaticAttributeValue::Vec3Uint(#f1, #f2, #f3)}
-//                     }
-//                     OwnedAttributeValue::Vec4Float(f1, f2, f3, f4) => {
-//                         quote! {StaticAttributeValue::Vec4Float(#f1, #f2, #f3, #f4)}
-//                     }
-//                     OwnedAttributeValue::Vec4Int(f1, f2, f3, f4) => {
-//                         quote! {StaticAttributeValue::Vec4Int(#f1, #f2, #f3, #f4)}
-//                     }
-//                     OwnedAttributeValue::Vec4Uint(f1, f2, f3, f4) => {
-//                         quote! {StaticAttributeValue::Vec4Uint(#f1, #f2, #f3, #f4)}
-//                     }
-//                     OwnedAttributeValue::Bytes(b) => {
-//                         quote! {StaticAttributeValue::Bytes(&[#(#b),*])}
-//                     }
-//                     OwnedAttributeValue::Any(_) => todo!(),
-//                 };
-//                 quote! {TemplateAttributeValue::Static(#val)}
-//             }
-//             TemplateAttributeValue::Dynamic(idx) => quote! {TemplateAttributeValue::Dynamic(#idx)},
-//         };
-//         match name {
-//             AttributeName::Ident(name) => tokens.append_all(quote! {
-//                 TemplateAttribute{
-//                     attribute: dioxus_elements::#element_tag::#name,
-//                     value: #value,
-//                 }
-//             }),
-//             AttributeName::Str(lit) => tokens.append_all(quote! {
-//                 TemplateAttribute{
-//                     attribute: dioxus::prelude::AttributeDiscription{
-//                         name: #lit,
-//                         namespace: None,
-//                         volatile: false
-//                     },
-//                     value: #value,
-//                 }
-//             }),
-//         }
-//     }
-// }
-
-// enum TemplateNodeTypeBuilder {
-//     Element(TemplateElementBuilder),
-//     Text(TextTemplate<Vec<TextTemplateSegment<String>>, String>),
-//     DynamicNode(usize),
-// }
-
-// #[cfg(any(feature = "hot-reload", debug_assertions))]
-// type OwnedTemplateNodeType = TemplateNodeType<
-//     Vec<TemplateAttribute<OwnedAttributeValue>>,
-//     OwnedAttributeValue,
-//     Vec<TemplateNodeId>,
-//     Vec<usize>,
-//     Vec<TextTemplateSegment<String>>,
-//     String,
-// >;
-
-// impl TemplateNodeTypeBuilder {
-//     #[cfg(any(feature = "hot-reload", debug_assertions))]
-//     fn try_into_owned(self, location: &OwnedCodeLocation) -> Result<OwnedTemplateNodeType, Error> {
-//         match self {
-//             TemplateNodeTypeBuilder::Element(el) => {
-//                 Ok(TemplateNodeType::Element(el.try_into_owned(location)?))
-//             }
-//             TemplateNodeTypeBuilder::Text(txt) => Ok(TemplateNodeType::Text(txt)),
-//             TemplateNodeTypeBuilder::DynamicNode(idx) => Ok(TemplateNodeType::DynamicNode(idx)),
-//         }
-//     }
-// }
-
-// impl ToTokens for TemplateNodeTypeBuilder {
-//     fn to_tokens(&self, tokens: &mut TokenStream) {
-//         match self {
-//             TemplateNodeTypeBuilder::Element(el) => tokens.append_all(quote! {
-//                 TemplateNodeType::Element(#el)
-//             }),
-//             TemplateNodeTypeBuilder::Text(txt) => {
-//                 let mut length = 0;
-
-//                 let segments = txt.segments.iter().map(|seg| match seg {
-//                     TextTemplateSegment::Static(s) => {
-//                         length += s.len();
-//                         quote!(TextTemplateSegment::Static(#s))
-//                     }
-//                     TextTemplateSegment::Dynamic(idx) => quote!(TextTemplateSegment::Dynamic(#idx)),
-//                 });
-
-//                 tokens.append_all(quote! {
-//                     TemplateNodeType::Text(TextTemplate::new(&[#(#segments),*], #length))
-//                 });
-//             }
-//             TemplateNodeTypeBuilder::DynamicNode(idx) => tokens.append_all(quote! {
-//                 TemplateNodeType::DynamicNode(#idx)
-//             }),
-//         }
-//     }
-// }
-
-// struct TemplateNodeBuilder {
-//     id: TemplateNodeId,
-//     depth: usize,
-//     parent: Option<TemplateNodeId>,
-//     node_type: TemplateNodeTypeBuilder,
-//     fully_static: bool,
-// }
-
-// impl TemplateNodeBuilder {
-//     #[cfg(any(feature = "hot-reload", debug_assertions))]
-//     fn try_into_owned(self, location: &OwnedCodeLocation) -> Result<OwnedTemplateNode, Error> {
-//         let TemplateNodeBuilder {
-//             id,
-//             node_type,
-//             parent,
-//             depth,
-//             ..
-//         } = self;
-//         let node_type = node_type.try_into_owned(location)?;
-//         Ok(OwnedTemplateNode {
-//             id,
-//             node_type,
-//             parent,
-//             depth,
-//         })
-//     }
-
-//     fn to_tokens(&self, tokens: &mut TokenStream) {
-//         let Self {
-//             id,
-//             node_type,
-//             parent,
-//             depth,
-//             ..
-//         } = self;
-//         let raw_id = id.0;
-//         let parent = match parent {
-//             Some(id) => {
-//                 let id = id.0;
-//                 quote! {Some(TemplateNodeId(#id))}
-//             }
-//             None => quote! {None},
-//         };
-
-//         tokens.append_all(quote! {
-//             TemplateNode {
-//                 id: TemplateNodeId(#raw_id),
-//                 node_type: #node_type,
-//                 parent: #parent,
-//                 depth: #depth,
-//             }
-//         })
-//     }
-// }
-
-// #[derive(Default)]
-// pub struct TemplateBuilder {
-//     nodes: Vec<TemplateNodeBuilder>,
-//     root_nodes: Vec<TemplateNodeId>,
-//     dynamic_context: DynamicTemplateContextBuilder,
-// }
-
-// impl TemplateBuilder {
-//     /// Create a template builder from nodes if it would improve performance to do so.
-//     pub fn from_roots(roots: Vec<BodyNode>) -> Option<Self> {
-//         let mut builder = Self::default();
-
-//         for (i, root) in roots.into_iter().enumerate() {
-//             let id = builder.build_node(root, None, vec![i], 0);
-//             builder.root_nodes.push(id);
-//         }
-
-//         // only build a template if there is at least one static node
-//         if builder
-//             .nodes
-//             .iter()
-//             .all(|r| matches!(&r.node_type, TemplateNodeTypeBuilder::DynamicNode(_)))
-//         {
-//             None
-//         } else {
-//             Some(builder)
-//         }
-//     }
-
-//     /// Create a template builder from nodes regardless of performance.
-//     #[cfg(any(feature = "hot-reload", debug_assertions))]
-//     fn from_roots_always(roots: Vec<BodyNode>) -> Self {
-//         let mut builder = Self::default();
-
-//         for (i, root) in roots.into_iter().enumerate() {
-//             let id = builder.build_node(root, None, vec![i], 0);
-//             builder.root_nodes.push(id);
-//         }
-
-//         builder
-//     }
-
-//     fn build_node(
-//         &mut self,
-//         node: BodyNode,
-//         parent: Option<TemplateNodeId>,
-//         path: Vec<usize>,
-//         depth: usize,
-//     ) -> TemplateNodeId {
-//         let id = TemplateNodeId(self.nodes.len());
-//         match node {
-//             BodyNode::Element(el) => {
-//                 let mut locally_static = true;
-//                 let mut attributes = Vec::new();
-//                 let mut listeners = Vec::new();
-//                 for attr in el.attributes {
-//                     match attr.attr {
-//                         ElementAttr::AttrText { name, value } => {
-//                             if let Some(static_value) = value.to_static() {
-//                                 attributes.push(TemplateAttributeBuilder {
-//                                     element_tag: el.name.clone(),
-//                                     name: AttributeName::Ident(name),
-//                                     value: TemplateAttributeValue::Static(
-//                                         OwnedAttributeValue::Text(static_value),
-//                                     ),
-//                                 })
-//                             } else {
-//                                 locally_static = false;
-//                                 attributes.push(TemplateAttributeBuilder {
-//                                     element_tag: el.name.clone(),
-//                                     name: AttributeName::Ident(name),
-//                                     value: TemplateAttributeValue::Dynamic(
-//                                         self.dynamic_context.add_attr(quote!(#value)),
-//                                     ),
-//                                 })
-//                             }
-//                         }
-//                         ElementAttr::CustomAttrText { name, value } => {
-//                             if let Some(static_value) = value.to_static() {
-//                                 attributes.push(TemplateAttributeBuilder {
-//                                     element_tag: el.name.clone(),
-//                                     name: AttributeName::Str(name),
-//                                     value: TemplateAttributeValue::Static(
-//                                         OwnedAttributeValue::Text(static_value),
-//                                     ),
-//                                 })
-//                             } else {
-//                                 locally_static = false;
-//                                 attributes.push(TemplateAttributeBuilder {
-//                                     element_tag: el.name.clone(),
-//                                     name: AttributeName::Str(name),
-//                                     value: TemplateAttributeValue::Dynamic(
-//                                         self.dynamic_context.add_attr(quote!(#value)),
-//                                     ),
-//                                 })
-//                             }
-//                         }
-//                         ElementAttr::AttrExpression { name, value } => {
-//                             locally_static = false;
-//                             attributes.push(TemplateAttributeBuilder {
-//                                 element_tag: el.name.clone(),
-//                                 name: AttributeName::Ident(name),
-//                                 value: TemplateAttributeValue::Dynamic(
-//                                     self.dynamic_context.add_attr(quote!(#value)),
-//                                 ),
-//                             })
-//                         }
-//                         ElementAttr::CustomAttrExpression { name, value } => {
-//                             locally_static = false;
-//                             attributes.push(TemplateAttributeBuilder {
-//                                 element_tag: el.name.clone(),
-//                                 name: AttributeName::Str(name),
-//                                 value: TemplateAttributeValue::Dynamic(
-//                                     self.dynamic_context.add_attr(quote!(#value)),
-//                                 ),
-//                             })
-//                         }
-//                         ElementAttr::EventTokens { name, tokens } => {
-//                             locally_static = false;
-//                             listeners.push(self.dynamic_context.add_listener(name, tokens))
-//                         }
-//                     }
-//                 }
-//                 if let Some(key) = el.key {
-//                     self.dynamic_context.add_key(quote!(
-//                         dioxus::core::exports::bumpalo::format!(in __bump, "{}", #key)
-//                             .into_bump_str()
-//                     ));
-//                 }
-//                 self.nodes.push(TemplateNodeBuilder {
-//                     id,
-//                     node_type: TemplateNodeTypeBuilder::Element(TemplateElementBuilder {
-//                         tag: el.name,
-//                         attributes,
-//                         children: Vec::new(),
-//                         listeners,
-//                         locally_static,
-//                     }),
-//                     parent,
-//                     depth,
-//                     fully_static: false,
-//                 });
-//                 let children: Vec<_> = el
-//                     .children
-//                     .into_iter()
-//                     .enumerate()
-//                     .map(|(i, child)| {
-//                         let mut new_path = path.clone();
-//                         new_path.push(i);
-//                         self.build_node(child, Some(id), new_path, depth + 1)
-//                     })
-//                     .collect();
-//                 let children_fully_static = children.iter().all(|c| self.nodes[c.0].fully_static);
-//                 let parent = &mut self.nodes[id.0];
-//                 parent.fully_static = locally_static && children_fully_static;
-//                 if let TemplateNodeTypeBuilder::Element(element) = &mut parent.node_type {
-//                     element.children = children;
-//                 }
-//             }
-
-//             BodyNode::Component(comp) => {
-//                 self.nodes.push(TemplateNodeBuilder {
-//                     id,
-//                     node_type: TemplateNodeTypeBuilder::DynamicNode(
-//                         self.dynamic_context.add_node(BodyNode::Component(comp)),
-//                     ),
-//                     parent,
-//                     depth,
-//                     fully_static: false,
-//                 });
-//             }
-
-//             BodyNode::Text(txt) => {
-//                 let mut segments = Vec::new();
-//                 let mut length = 0;
-//                 let mut fully_static = true;
-
-//                 for segment in txt.segments {
-//                     segments.push(match segment {
-//                         Segment::Literal(lit) => {
-//                             length += lit.len();
-//                             TextTemplateSegment::Static(lit)
-//                         }
-//                         Segment::Formatted(fmted) => {
-//                             fully_static = false;
-//                             TextTemplateSegment::Dynamic(self.dynamic_context.add_text(fmted))
-//                         }
-//                     })
-//                 }
-
-//                 self.nodes.push(TemplateNodeBuilder {
-//                     id,
-//                     node_type: TemplateNodeTypeBuilder::Text(TextTemplate::new(segments, length)),
-//                     parent,
-//                     depth,
-//                     fully_static,
-//                 });
-//             }
-
-//             BodyNode::RawExpr(expr) => {
-//                 self.nodes.push(TemplateNodeBuilder {
-//                     id,
-//                     node_type: TemplateNodeTypeBuilder::DynamicNode(
-//                         self.dynamic_context.add_node(BodyNode::RawExpr(expr)),
-//                     ),
-//                     parent,
-//                     depth,
-//                     fully_static: false,
-//                 });
-//             }
-//         }
-//         id
-//     }
-
-//     #[cfg(any(feature = "hot-reload", debug_assertions))]
-//     pub fn try_switch_dynamic_context(
-//         mut self,
-//         dynamic_context: DynamicTemplateContextBuilder,
-//     ) -> Option<Self> {
-//         let attribute_mapping: HashMap<String, usize> = dynamic_context
-//             .attributes
-//             .iter()
-//             .enumerate()
-//             .map(|(i, ts)| (ts.to_string(), i))
-//             .collect();
-//         let text_mapping: HashMap<String, usize> = dynamic_context
-//             .text
-//             .iter()
-//             .enumerate()
-//             .map(|(i, ts)| (ts.to_token_stream().to_string(), i))
-//             .collect();
-//         let listener_mapping: HashMap<(String, Expr), usize> = dynamic_context
-//             .listeners
-//             .iter()
-//             .enumerate()
-//             .map(|(i, ts)| (ts.clone(), i))
-//             .collect();
-//         let node_mapping: HashMap<String, usize> = dynamic_context
-//             .nodes
-//             .iter()
-//             .enumerate()
-//             .map(|(i, ts)| (ts.to_token_stream().to_string(), i))
-//             .collect();
-
-//         for node in &mut self.nodes {
-//             match &mut node.node_type {
-//                 TemplateNodeTypeBuilder::Element(element) => {
-//                     for listener in &mut element.listeners {
-//                         *listener =
-//                             *listener_mapping.get(&self.dynamic_context.listeners[*listener])?;
-//                     }
-//                     for attribute in &mut element.attributes {
-//                         if let TemplateAttributeValue::Dynamic(idx) = &mut attribute.value {
-//                             *idx = *attribute_mapping
-//                                 .get(&self.dynamic_context.attributes[*idx].to_string())?;
-//                         }
-//                     }
-//                 }
-//                 TemplateNodeTypeBuilder::Text(txt) => {
-//                     for seg in &mut txt.segments {
-//                         if let TextTemplateSegment::Dynamic(idx) = seg {
-//                             *idx = *text_mapping.get(
-//                                 &self.dynamic_context.text[*idx]
-//                                     .to_token_stream()
-//                                     .to_string(),
-//                             )?;
-//                         }
-//                     }
-//                 }
-//                 TemplateNodeTypeBuilder::DynamicNode(idx) => {
-//                     *idx = *node_mapping.get(
-//                         &self.dynamic_context.nodes[*idx]
-//                             .to_token_stream()
-//                             .to_string(),
-//                     )?;
-//                 }
-//             }
-//         }
-//         self.dynamic_context = dynamic_context;
-
-//         Some(self)
-//     }
-
-//     #[cfg(any(feature = "hot-reload", debug_assertions))]
-//     pub fn try_into_owned(self, location: &OwnedCodeLocation) -> Result<OwnedTemplate, Error> {
-//         let mut nodes = Vec::new();
-//         let dynamic_mapping = self.dynamic_mapping(&nodes);
-//         let dynamic_path = self.dynamic_path();
-//         for node in self.nodes {
-//             nodes.push(node.try_into_owned(location)?);
-//         }
-
-//         Ok(OwnedTemplate {
-//             nodes,
-//             root_nodes: self.root_nodes,
-//             dynamic_mapping,
-//             dynamic_path,
-//         })
-//     }
-
-//     #[cfg(any(feature = "hot-reload", debug_assertions))]
-//     pub fn dynamic_mapping(
-//         &self,
-//         resolved_nodes: &Vec<OwnedTemplateNode>,
-//     ) -> OwnedDynamicNodeMapping {
-//         let dynamic_context = &self.dynamic_context;
-//         let mut node_mapping = vec![None; dynamic_context.nodes.len()];
-//         let nodes = &self.nodes;
-//         for n in nodes {
-//             if let TemplateNodeTypeBuilder::DynamicNode(idx) = &n.node_type {
-//                 node_mapping[*idx] = Some(n.id)
-//             }
-//         }
-//         let mut text_mapping = vec![Vec::new(); dynamic_context.text.len()];
-//         for n in nodes {
-//             if let TemplateNodeTypeBuilder::Text(txt) = &n.node_type {
-//                 for seg in &txt.segments {
-//                     match seg {
-//                         TextTemplateSegment::Static(_) => (),
-//                         TextTemplateSegment::Dynamic(idx) => text_mapping[*idx].push(n.id),
-//                     }
-//                 }
-//             }
-//         }
-//         let mut attribute_mapping = vec![Vec::new(); dynamic_context.attributes.len()];
-//         for n in nodes {
-//             if let TemplateNodeTypeBuilder::Element(el) = &n.node_type {
-//                 for (i, attr) in el.attributes.iter().enumerate() {
-//                     match attr.value {
-//                         TemplateAttributeValue::Static(_) => (),
-//                         TemplateAttributeValue::Dynamic(idx) => {
-//                             attribute_mapping[idx].push((n.id, i));
-//                         }
-//                     }
-//                 }
-//             }
-//         }
-//         let mut listener_mapping = Vec::new();
-//         for n in nodes {
-//             if let TemplateNodeTypeBuilder::Element(el) = &n.node_type {
-//                 if !el.listeners.is_empty() {
-//                     listener_mapping.push(n.id);
-//                 }
-//             }
-//         }
-
-//         let mut volatile_mapping = Vec::new();
-//         for n in resolved_nodes {
-//             if let TemplateNodeType::Element(el) = &n.node_type {
-//                 for (i, attr) in el.attributes.iter().enumerate() {
-//                     if attr.attribute.volatile {
-//                         volatile_mapping.push((n.id, i));
-//                     }
-//                 }
-//             }
-//         }
-
-//         OwnedDynamicNodeMapping::new(
-//             node_mapping,
-//             text_mapping,
-//             attribute_mapping,
-//             volatile_mapping,
-//             listener_mapping,
-//         )
-//     }
-
-//     fn dynamic_path(&self) -> Option<OwnedPathSeg> {
-//         let mut last_seg: Option<OwnedPathSeg> = None;
-//         let mut nodes_to_insert_after = Vec::new();
-//         // iterating from the last root to the first
-//         for root in self.root_nodes.iter().rev() {
-//             let root_node = &self.nodes[root.0];
-//             if let TemplateNodeTypeBuilder::DynamicNode(_) = root_node.node_type {
-//                 match &mut last_seg {
-//                     // if there has been no static nodes, we can append the child to the parent node
-//                     None => nodes_to_insert_after.push(*root),
-//                     // otherwise we insert the child before the last static node
-//                     Some(seg) => {
-//                         seg.ops.push(UpdateOp::InsertBefore(*root));
-//                     }
-//                 }
-//             } else if let Some(mut new) = self.construct_path_segment(*root) {
-//                 if let Some(last) = last_seg.take() {
-//                     match new.traverse {
-//                         OwnedTraverse::Halt => {
-//                             new.traverse = OwnedTraverse::NextSibling(Box::new(last));
-//                         }
-//                         OwnedTraverse::FirstChild(b) => {
-//                             new.traverse = OwnedTraverse::Both(Box::new((*b, last)));
-//                         }
-//                         _ => unreachable!(),
-//                     }
-//                 } else {
-//                     for node in nodes_to_insert_after.drain(..) {
-//                         new.ops.push(UpdateOp::InsertAfter(node));
-//                     }
-//                 }
-//                 last_seg = Some(new);
-//             } else if let Some(last) = last_seg.take() {
-//                 last_seg = Some(OwnedPathSeg {
-//                     ops: Vec::new(),
-//                     traverse: OwnedTraverse::NextSibling(Box::new(last)),
-//                 });
-//             }
-//         }
-//         last_seg
-//     }
-
-//     fn construct_path_segment(&self, node_id: TemplateNodeId) -> Option<OwnedPathSeg> {
-//         let n = &self.nodes[node_id.0];
-//         if n.fully_static {
-//             return None;
-//         }
-//         match &n.node_type {
-//             TemplateNodeTypeBuilder::Element(el) => {
-//                 let mut last_seg: Option<OwnedPathSeg> = None;
-//                 let mut children_to_append = Vec::new();
-//                 // iterating from the last child to the first
-//                 for child in el.children.iter().rev() {
-//                     let child_node = &self.nodes[child.0];
-//                     if let TemplateNodeTypeBuilder::DynamicNode(_) = child_node.node_type {
-//                         match &mut last_seg {
-//                             // if there has been no static nodes, we can append the child to the parent node
-//                             None => children_to_append.push(*child),
-//                             // otherwise we insert the child before the last static node
-//                             Some(seg) => {
-//                                 seg.ops.push(UpdateOp::InsertBefore(*child));
-//                             }
-//                         }
-//                     } else if let Some(mut new) = self.construct_path_segment(*child) {
-//                         if let Some(last) = last_seg.take() {
-//                             match new.traverse {
-//                                 OwnedTraverse::Halt => {
-//                                     new.traverse = OwnedTraverse::NextSibling(Box::new(last));
-//                                 }
-//                                 OwnedTraverse::FirstChild(b) => {
-//                                     new.traverse = OwnedTraverse::Both(Box::new((*b, last)));
-//                                 }
-//                                 _ => unreachable!(),
-//                             }
-//                         }
-//                         last_seg = Some(new);
-//                     } else if let Some(last) = last_seg.take() {
-//                         last_seg = Some(OwnedPathSeg {
-//                             ops: Vec::new(),
-//                             traverse: OwnedTraverse::NextSibling(Box::new(last)),
-//                         });
-//                     }
-//                 }
-//                 let mut ops = Vec::new();
-//                 if !el.locally_static || n.parent.is_none() {
-//                     ops.push(UpdateOp::StoreNode(node_id));
-//                 }
-//                 for child in children_to_append.into_iter().rev() {
-//                     ops.push(UpdateOp::AppendChild(child));
-//                 }
-//                 Some(OwnedPathSeg {
-//                     ops,
-//                     traverse: match last_seg {
-//                         Some(last) => OwnedTraverse::FirstChild(Box::new(last)),
-//                         None => OwnedTraverse::Halt,
-//                     },
-//                 })
-//             }
-//             TemplateNodeTypeBuilder::Text(_) => Some(OwnedPathSeg {
-//                 ops: vec![UpdateOp::StoreNode(n.id)],
-//                 traverse: dioxus_core::OwnedTraverse::Halt,
-//             }),
-//             TemplateNodeTypeBuilder::DynamicNode(_) => unreachable!(
-//                 "constructing path segment for dynamic nodes is handled in the parent node"
-//             ),
-//         }
-//     }
-// }
-
-// impl ToTokens for TemplateBuilder {
-//     fn to_tokens(&self, tokens: &mut TokenStream) {
-//         let Self {
-//             nodes,
-//             root_nodes,
-//             dynamic_context,
-//         } = self;
-
-//         let mut node_mapping = vec![None; dynamic_context.nodes.len()];
-//         for n in nodes {
-//             if let TemplateNodeTypeBuilder::DynamicNode(idx) = &n.node_type {
-//                 node_mapping[*idx] = Some(n.id);
-//             }
-//         }
-//         let mut text_mapping = vec![Vec::new(); dynamic_context.text.len()];
-//         for n in nodes {
-//             if let TemplateNodeTypeBuilder::Text(txt) = &n.node_type {
-//                 for seg in &txt.segments {
-//                     match seg {
-//                         TextTemplateSegment::Static(_) => (),
-//                         TextTemplateSegment::Dynamic(idx) => text_mapping[*idx].push(n.id),
-//                     }
-//                 }
-//             }
-//         }
-//         let mut attribute_mapping = vec![Vec::new(); dynamic_context.attributes.len()];
-//         for n in nodes {
-//             if let TemplateNodeTypeBuilder::Element(el) = &n.node_type {
-//                 for (i, attr) in el.attributes.iter().enumerate() {
-//                     match attr.value {
-//                         TemplateAttributeValue::Static(_) => (),
-//                         TemplateAttributeValue::Dynamic(idx) => {
-//                             attribute_mapping[idx].push((n.id, i));
-//                         }
-//                     }
-//                 }
-//             }
-//         }
-//         let mut listener_mapping = Vec::new();
-//         for n in nodes {
-//             if let TemplateNodeTypeBuilder::Element(el) = &n.node_type {
-//                 if !el.listeners.is_empty() {
-//                     listener_mapping.push(n.id);
-//                 }
-//             }
-//         }
-
-//         let root_nodes = root_nodes.iter().map(|id| {
-//             let raw = id.0;
-//             quote! { TemplateNodeId(#raw) }
-//         });
-//         let node_mapping_quoted = node_mapping.iter().map(|op| match op {
-//             Some(id) => {
-//                 let raw_id = id.0;
-//                 quote! {Some(TemplateNodeId(#raw_id))}
-//             }
-//             None => quote! {None},
-//         });
-//         let text_mapping_quoted = text_mapping.iter().map(|inner| {
-//             let raw = inner.iter().map(|id| id.0);
-//             quote! {&[#(TemplateNodeId(#raw)),*]}
-//         });
-//         let attribute_mapping_quoted = attribute_mapping.iter().map(|inner| {
-//             let raw = inner.iter().map(|(id, _)| id.0);
-//             let indecies = inner.iter().map(|(_, idx)| idx);
-//             quote! {&[#((TemplateNodeId(#raw), #indecies)),*]}
-//         });
-//         let listener_mapping_quoted = listener_mapping.iter().map(|id| {
-//             let raw = id.0;
-//             quote! {TemplateNodeId(#raw)}
-//         });
-//         let mut nodes_quoted = TokenStream::new();
-//         for n in nodes {
-//             n.to_tokens(&mut nodes_quoted);
-//             quote! {,}.to_tokens(&mut nodes_quoted);
-//         }
-
-//         let dynamic_path = match self.dynamic_path() {
-//             Some(seg) => {
-//                 let seg = quote_owned_segment(seg);
-//                 quote! {Some(#seg)}
-//             }
-//             None => quote! {None},
-//         };
-
-//         let quoted = quote! {
-//             {
-//                 const __NODES: dioxus::prelude::StaticTemplateNodes = &[#nodes_quoted];
-//                 const __TEXT_MAPPING: &'static [&'static [dioxus::prelude::TemplateNodeId]] = &[#(#text_mapping_quoted),*];
-//                 const __ATTRIBUTE_MAPPING: &'static [&'static [(dioxus::prelude::TemplateNodeId, usize)]] = &[#(#attribute_mapping_quoted),*];
-//                 const __ROOT_NODES: &'static [dioxus::prelude::TemplateNodeId] = &[#(#root_nodes),*];
-//                 const __NODE_MAPPING: &'static [Option<dioxus::prelude::TemplateNodeId>] = &[#(#node_mapping_quoted),*];
-//                 const __NODES_WITH_LISTENERS: &'static [dioxus::prelude::TemplateNodeId] = &[#(#listener_mapping_quoted),*];
-//                 static __VOLITALE_MAPPING_INNER: dioxus::core::exports::once_cell::sync::Lazy<Vec<(dioxus::prelude::TemplateNodeId, usize)>> = dioxus::core::exports::once_cell::sync::Lazy::new(||{
-//                     // check each property to see if it is volatile
-//                     let mut volatile = Vec::new();
-//                     for n in __NODES {
-//                         if let TemplateNodeType::Element(el) = &n.node_type {
-//                             for (i, attr) in el.attributes.iter().enumerate() {
-//                                 if attr.attribute.volatile {
-//                                     volatile.push((n.id, i));
-//                                 }
-//                             }
-//                         }
-//                     }
-//                     volatile
-//                 });
-//                 static __VOLITALE_MAPPING: &'static dioxus::core::exports::once_cell::sync::Lazy<Vec<(dioxus::prelude::TemplateNodeId, usize)>> = &__VOLITALE_MAPPING_INNER;
-//                 static __STATIC_VOLITALE_MAPPING: dioxus::prelude::LazyStaticVec<(dioxus::prelude::TemplateNodeId, usize)> = LazyStaticVec(__VOLITALE_MAPPING);
-//                 static __TEMPLATE: dioxus::prelude::Template = Template::Static(&StaticTemplate {
-//                     nodes: __NODES,
-//                     root_nodes: __ROOT_NODES,
-//                     dynamic_mapping: StaticDynamicNodeMapping::new(__NODE_MAPPING, __TEXT_MAPPING, __ATTRIBUTE_MAPPING, __STATIC_VOLITALE_MAPPING, __NODES_WITH_LISTENERS),
-//                     dynamic_path: #dynamic_path,
-//                 });
-
-//                 let __bump = __cx.bump();
-//                 __cx.template_ref(dioxus::prelude::TemplateId(get_line_num!()), __TEMPLATE.clone(), #dynamic_context)
-//             }
-//         };
-
-//         tokens.append_all(quoted)
-//     }
-// }
-
-// #[derive(Default, Clone, Debug)]
-// pub struct DynamicTemplateContextBuilder {
-//     nodes: Vec<BodyNode>,
-//     text: Vec<FormattedSegment>,
-//     attributes: Vec<TokenStream>,
-//     listeners: Vec<(String, Expr)>,
-//     key: Option<TokenStream>,
-// }
-
-// impl DynamicTemplateContextBuilder {
-//     fn add_node(&mut self, node: BodyNode) -> usize {
-//         let node_id = self.nodes.len();
-
-//         self.nodes.push(node);
-
-//         node_id
-//     }
-
-//     fn add_text(&mut self, text: FormattedSegment) -> usize {
-//         let text_id = self.text.len();
-
-//         self.text.push(text);
-
-//         text_id
-//     }
-
-//     fn add_attr(&mut self, attr: TokenStream) -> usize {
-//         let attr_id = self.attributes.len();
-
-//         self.attributes.push(attr);
-
-//         attr_id
-//     }
-
-//     fn add_listener(&mut self, name: Ident, listener: Expr) -> usize {
-//         let listener_id = self.listeners.len();
-
-//         self.listeners.push((name.to_string(), listener));
-
-//         listener_id
-//     }
-
-//     fn add_key(&mut self, key: TokenStream) {
-//         self.key = Some(key);
-//     }
-// }
-
-// impl ToTokens for DynamicTemplateContextBuilder {
-//     fn to_tokens(&self, tokens: &mut TokenStream) {
-//         let nodes = &self.nodes;
-//         let text = &self.text;
-//         let attributes = &self.attributes;
-//         let listeners_names = self
-//             .listeners
-//             .iter()
-//             .map(|(n, _)| syn::parse_str::<Ident>(n).expect(n));
-//         let listeners_exprs = self.listeners.iter().map(|(_, e)| e);
-//         let key = match &self.key {
-//             Some(k) => quote!(Some(#k)),
-//             None => quote!(None),
-//         };
-//         tokens.append_all(quote! {
-//             TemplateContext {
-//                 nodes: __cx.bump().alloc([#(#nodes),*]),
-//                 text_segments: __cx.bump().alloc([#(&*dioxus::core::exports::bumpalo::format!(in __bump, "{}", #text).into_bump_str()),*]),
-//                 attributes: __cx.bump().alloc([#({#attributes}.into_value(__cx.bump())),*]),
-//                 listeners: __cx.bump().alloc([#(dioxus_elements::on::#listeners_names(__cx, #listeners_exprs)),*]),
-//                 key: #key,
-//             }
-//         })
-//     }
-// }
-
-// fn quote_owned_segment(seg: OwnedPathSeg) -> proc_macro2::TokenStream {
-//     let OwnedPathSeg { ops, traverse } = seg;
-
-//     let ops = ops
-//         .into_iter()
-//         .map(|op| match op {
-//             UpdateOp::StoreNode(id) => {
-//                 let id = quote_template_node_id(id);
-//                 quote!(UpdateOp::StoreNode(#id))
-//             }
-//             UpdateOp::InsertBefore(id) => {
-//                 let id = quote_template_node_id(id);
-//                 quote!(UpdateOp::InsertBefore(#id))
-//             }
-//             UpdateOp::InsertAfter(id) => {
-//                 let id = quote_template_node_id(id);
-//                 quote!(UpdateOp::InsertAfter(#id))
-//             }
-//             UpdateOp::AppendChild(id) => {
-//                 let id = quote_template_node_id(id);
-//                 quote!(UpdateOp::AppendChild(#id))
-//             }
-//         })
-//         .collect::<Vec<_>>();
-
-//     let traverse = quote_owned_traverse(traverse);
-
-//     quote! {
-//         StaticPathSeg {
-//             ops: &[#(#ops),*],
-//             traverse: #traverse,
-//         }
-//     }
-// }
-
-// fn quote_owned_traverse(traverse: OwnedTraverse) -> proc_macro2::TokenStream {
-//     match traverse {
-//         OwnedTraverse::Halt => {
-//             quote! {StaticTraverse::Halt}
-//         }
-//         OwnedTraverse::FirstChild(seg) => {
-//             let seg = quote_owned_segment(*seg);
-//             quote! {StaticTraverse::FirstChild(&#seg)}
-//         }
-//         OwnedTraverse::NextSibling(seg) => {
-//             let seg = quote_owned_segment(*seg);
-//             quote! {StaticTraverse::NextSibling(&#seg)}
-//         }
-//         OwnedTraverse::Both(b) => {
-//             let (child, sibling) = *b;
-//             let child = quote_owned_segment(child);
-//             let sibling = quote_owned_segment(sibling);
-//             quote! {StaticTraverse::Both(&(#child, #sibling))}
-//         }
-//     }
-// }
-
-// fn quote_template_node_id(id: TemplateNodeId) -> proc_macro2::TokenStream {
-//     let raw = id.0;
-//     quote! {
-//         TemplateNodeId(#raw)
-//     }
-// }

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

@@ -36,13 +36,13 @@ impl StringCache {
 
         let mut cur_path = vec![];
 
-        for (root_idx, root) in template.template.roots.iter().enumerate() {
+        for (root_idx, root) in template.template.get().roots.iter().enumerate() {
             Self::recurse(root, &mut cur_path, root_idx, &mut chain)?;
         }
 
         Ok(Self {
             segments: chain.segments,
-            template: template.template,
+            template: template.template.get(),
         })
     }
 

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

@@ -66,7 +66,7 @@ impl Renderer {
     ) -> std::fmt::Result {
         let entry = self
             .template_cache
-            .entry(template.template.name)
+            .entry(template.template.get().name)
             .or_insert_with(|| Rc::new(StringCache::from_template(template).unwrap()))
             .clone();
 

+ 2 - 2
packages/tui/examples/tui_colorpicker.rs

@@ -10,7 +10,7 @@ fn main() {
 fn app(cx: Scope) -> Element {
     let hue = use_state(cx, || 0.0);
     let brightness = use_state(cx, || 0.0);
-    let tui_query: &Query = cx.consume_context().unwrap();
+    let tui_query: Query = cx.consume_context().unwrap();
     // disable templates so that every node has an id and can be queried
     cx.render(rsx! {
         div{
@@ -18,7 +18,7 @@ fn app(cx: Scope) -> Element {
             background_color: "hsl({hue}, 70%, {brightness}%)",
             onmousemove: move |evt| {
                 if let RenderReturn::Ready(node) = cx.root_node() {
-                    if let Some(id) = node.root_ids[0].get() {
+                    if let Some(id) = node.root_ids.get(0){
                         let node = tui_query.get(id);
                         let Size{width, height} = node.size().unwrap();
                         let pos = evt.inner().element_coordinates();

+ 1 - 1
packages/tui/src/widgets/mod.rs

@@ -11,7 +11,7 @@ pub use input::*;
 
 pub(crate) fn get_root_id<T>(cx: Scope<T>) -> Option<ElementId> {
     if let RenderReturn::Ready(sync) = cx.root_node() {
-        sync.root_ids.get(0).and_then(|id| id.get())
+        sync.root_ids.get(0)
     } else {
         None
     }

+ 10 - 4
packages/web/src/hot_reload.rs

@@ -2,12 +2,13 @@
 
 use futures_channel::mpsc::UnboundedReceiver;
 
+use dioxus_core::Template;
 use wasm_bindgen::closure::Closure;
 use wasm_bindgen::JsCast;
 use web_sys::{MessageEvent, WebSocket};
 
 #[cfg(not(debug_assertions))]
-pub(crate) fn init() -> UnboundedReceiver<String> {
+pub(crate) fn init() -> UnboundedReceiver<Template<'static>> {
     let (tx, rx) = futures_channel::mpsc::unbounded();
 
     std::mem::forget(tx);
@@ -16,7 +17,8 @@ pub(crate) fn init() -> UnboundedReceiver<String> {
 }
 
 #[cfg(debug_assertions)]
-pub(crate) fn init() -> UnboundedReceiver<String> {
+pub(crate) fn init() -> UnboundedReceiver<Template<'static>> {
+    use core::panic;
     use std::convert::TryInto;
 
     let window = web_sys::window().unwrap();
@@ -39,8 +41,12 @@ pub(crate) fn init() -> UnboundedReceiver<String> {
     // change the rsx when new data is received
     let cl = Closure::wrap(Box::new(move |e: MessageEvent| {
         if let Ok(text) = e.data().dyn_into::<js_sys::JsString>() {
-            if let Ok(val) = text.try_into() {
-                _ = tx.unbounded_send(val);
+            let text: Result<String, _> = text.try_into();
+            if let Ok(string) = text {
+                match serde_json::from_str(Box::leak(string.into_boxed_str())) {
+                    Ok(template) => _ = tx.unbounded_send(template),
+                    Err(e) => panic!("Failed to parse template: {}", e),
+                }
             }
         }
     }) as Box<dyn FnMut(MessageEvent)>);

+ 9 - 5
packages/web/src/lib.rs

@@ -202,19 +202,23 @@ pub async fn run_with_props<T: 'static>(root: fn(Scope<T>) -> Element, root_prop
 
         // if virtualdom has nothing, wait for it to have something before requesting idle time
         // if there is work then this future resolves immediately.
-        let mut res = {
+        let (mut res, template) = {
             let work = dom.wait_for_work().fuse();
             pin_mut!(work);
 
             futures_util::select! {
-                _ = work => None,
-                _new_template = hotreload_rx.next() => {
-                    todo!("Implement hot reload");
+                _ = work => (None, None),
+                new_template = hotreload_rx.next() => {
+                    (None, new_template)
                 }
-                evt = rx.next() => evt
+                evt = rx.next() => (evt, None)
             }
         };
 
+        if let Some(template) = template {
+            dom.replace_template(template);
+        }
+
         // Dequeue all of the events from the channel in send order
         // todo: we should re-order these if possible
         while let Some(evt) = res {