Jelajahi Sumber

Subtree memorization / reactive templates (#488)

This commit adds subtree memoization to Dioxus.

Subtree memoization is basically a compile-time step that drastically 
reduces the amount of work the diffing engine needs to do at runtime by
extracting non-changing nodes out into a static "template." Templates 
are then understood by the various renderers in the ecosystem as a 
faster way of rendering the same items. 

For example, in the web, templates are simply a set of DOM Nodes created 
once and then cloned later. This is the same pattern frameworks like Lithtml
and SolidJS use to achieve near-perfect performance. 

Subtree memoization adds an additional level of complexity to Dioxus. The RSX
macro needs to be much smarter to identify changing/nonchanging nodes and
generate a mapping between the Template and its runtime counterparts.

This commit represents a working starter point for this work, adding support 
for templates for the web, desktop, liveview, ssr, and native-core renderers.
In the future we will try to shrink code generation, generally improve 
performance, and simplify our implementation.
Demonthos 2 tahun lalu
induk
melakukan
047ed1e553
80 mengubah file dengan 6760 tambahan dan 2538 penghapusan
  1. 6 2
      Cargo.toml
  2. 2 2
      docs/guide/src/en/roadmap.md
  3. 5 5
      packages/autofmt/src/buffer.rs
  4. 7 2
      packages/autofmt/src/component.rs
  5. 13 5
      packages/autofmt/src/element.rs
  6. 0 2
      packages/core-macro/Cargo.toml
  7. 12 31
      packages/core-macro/src/lib.rs
  8. 2 1
      packages/core/Cargo.toml
  9. 201 0
      packages/core/src/arbitrary_value.rs
  10. 336 29
      packages/core/src/diff.rs
  11. 195 0
      packages/core/src/dynamic_template_context.rs
  12. 2 2
      packages/core/src/events.rs
  13. 28 7
      packages/core/src/lib.rs
  14. 201 33
      packages/core/src/mutations.rs
  15. 162 28
      packages/core/src/nodes.rs
  16. 145 88
      packages/core/src/scopes.rs
  17. 1051 0
      packages/core/src/template.rs
  18. 45 1
      packages/core/src/util.rs
  19. 52 8
      packages/core/src/virtual_dom.rs
  20. 2 3
      packages/desktop/Cargo.toml
  21. 1 1
      packages/desktop/src/controller.rs
  22. 3 3
      packages/desktop/src/events.rs
  23. 7 36
      packages/desktop/src/hot_reload.rs
  24. 1 1
      packages/desktop/src/lib.rs
  25. 1 8
      packages/dioxus/Cargo.toml
  26. 0 6
      packages/dioxus/src/lib.rs
  27. 158 46
      packages/dioxus/tests/create_dom.rs
  28. 95 95
      packages/dioxus/tests/diffing.rs
  29. 18 8
      packages/dioxus/tests/earlyabort.rs
  30. 23 23
      packages/dioxus/tests/lifecycle.rs
  31. 9 9
      packages/dioxus/tests/sharedstate.rs
  32. 30 6
      packages/dioxus/tests/vdom_rebuild.rs
  33. 68 35
      packages/html/src/elements.rs
  34. 30 20
      packages/html/src/global_attributes.rs
  35. 40 0
      packages/interpreter/src/bindings.rs
  36. 343 51
      packages/interpreter/src/interpreter.js
  37. 4 3
      packages/liveview/src/events.rs
  38. 601 64
      packages/liveview/src/interpreter.js
  39. 33 14
      packages/native-core-macro/src/lib.rs
  40. 1 1
      packages/native-core-macro/tests/called_minimally_on_build.rs
  41. 75 107
      packages/native-core-macro/tests/change_nodes.rs
  42. 66 76
      packages/native-core-macro/tests/initial_build.rs
  43. 34 27
      packages/native-core-macro/tests/peristant_iterator.rs
  44. 68 24
      packages/native-core-macro/tests/update_state.rs
  45. 1 1
      packages/native-core/README.md
  46. 1 0
      packages/native-core/src/lib.rs
  47. 43 40
      packages/native-core/src/node_ref.rs
  48. 806 137
      packages/native-core/src/real_dom.rs
  49. 10 5
      packages/native-core/src/state.rs
  50. 44 0
      packages/native-core/src/template.rs
  51. 30 34
      packages/native-core/src/utils.rs
  52. 6 1
      packages/rsx/Cargo.toml
  53. 20 11
      packages/rsx/src/attributes.rs
  54. 11 7
      packages/rsx/src/component.rs
  55. 12 17
      packages/rsx/src/element.rs
  56. 16 8
      packages/rsx/src/elements.rs
  57. 3 4
      packages/rsx/src/error.rs
  58. 131 87
      packages/rsx/src/ifmt.rs
  59. 38 5
      packages/rsx/src/lib.rs
  60. 4 4
      packages/rsx/src/node.rs
  61. 917 0
      packages/rsx/src/template.rs
  62. 0 24
      packages/rsx_interpreter/Cargo.toml
  63. 0 209
      packages/rsx_interpreter/src/captuered_context.rs
  64. 0 277
      packages/rsx_interpreter/src/interperter.rs
  65. 0 183
      packages/rsx_interpreter/src/lib.rs
  66. 0 407
      packages/rsx_interpreter/tests/render.rs
  67. 339 67
      packages/ssr/src/lib.rs
  68. 18 11
      packages/tui/src/focus.rs
  69. 9 8
      packages/tui/src/hooks.rs
  70. 12 4
      packages/tui/src/layout.rs
  71. 6 4
      packages/tui/src/lib.rs
  72. 5 5
      packages/tui/src/node.rs
  73. 2 2
      packages/tui/src/render.rs
  74. 9 4
      packages/tui/src/style_attributes.rs
  75. 7 3
      packages/web/Cargo.toml
  76. 1 0
      packages/web/examples/hydrate.rs
  77. 41 2
      packages/web/src/dom.rs
  78. 9 40
      packages/web/src/hot_reload.rs
  79. 8 8
      packages/web/src/lib.rs
  80. 25 6
      packages/web/src/rehydrate.rs

+ 6 - 2
Cargo.toml

@@ -17,7 +17,6 @@ members = [
     "packages/liveview",
     "packages/autofmt",
     "packages/rsx",
-    "packages/rsx_interpreter",
     "packages/native-core",
     "packages/native-core-macro",
     "docs/guide",
@@ -42,7 +41,7 @@ rust-version = "1.60.0"
 
 [dev-dependencies]
 dioxus = { path = "./packages/dioxus" }
-dioxus-desktop = { path = "./packages/desktop" }
+dioxus-desktop = { path = "./packages/desktop", features = ["hot-reload"] }
 dioxus-ssr = { path = "./packages/ssr" }
 dioxus-router = { path = "./packages/router" }
 fermi = { path = "./packages/fermi" }
@@ -60,3 +59,8 @@ reqwest = { version = "0.11.9", features = ["json"] }
 fern = { version = "0.6.0", features = ["colored"] }
 thiserror = "1.0.30"
 env_logger = "0.9.0"
+
+[profile.release]
+opt-level = 3
+lto = true
+debug = true

+ 2 - 2
docs/guide/src/en/roadmap.md

@@ -50,7 +50,7 @@ Generally, here's the status of each platform:
 | 1st class global state    | ✅      | redux/recoil/mobx on top of context                                  |
 | Runs natively             | ✅      | runs as a portable binary w/o a runtime (Node)                       |
 | Subtree Memoization       | ✅      | skip diffing static element subtrees                                 |
-| High-efficiency templates | 🛠      | rsx! calls are translated to templates on the DOM's side             |
+| High-efficiency templates |       | rsx! calls are translated to templates on the DOM's side             |
 | Compile-time correct      | ✅      | Throw errors on invalid template layouts                             |
 | Heuristic Engine          | ✅      | track component memory usage to minimize future allocations          |
 | Fine-grained reactivity   | 👀      | Skip diffing for fine-grain updates                                  |
@@ -66,7 +66,7 @@ These Features are planned for the future of Dioxus:
 ### Core
 - [x] Release of Dioxus Core
 - [x] Upgrade documentation to include more theory and be more comprehensive
-- [ ] Support for HTML-side templates for lightning-fast dom manipulation
+- [x] Support for HTML-side templates for lightning-fast dom manipulation
 - [ ] Support for multiple renderers for same virtualdom (subtrees)
 - [ ] Support for ThreadSafe (Send + Sync)
 - [ ] Support for Portals

+ 5 - 5
packages/autofmt/src/buffer.rs

@@ -3,7 +3,7 @@ use std::{
     fmt::{Result, Write},
 };
 
-use dioxus_rsx::{BodyNode, ElementAttr, ElementAttrNamed};
+use dioxus_rsx::{BodyNode, ElementAttr, ElementAttrNamed, IfmtInput};
 use proc_macro2::{LineColumn, Span};
 use syn::{spanned::Spanned, Expr};
 
@@ -72,8 +72,8 @@ impl Buffer {
         }
     }
 
-    pub fn write_text(&mut self, text: &syn::LitStr) -> Result {
-        write!(self.buf, "\"{}\"", text.value())
+    pub fn write_text(&mut self, text: &IfmtInput) -> Result {
+        write!(self.buf, "\"{}\"", text.source.as_ref().unwrap().value())
     }
 
     pub fn consume(self) -> Option<String> {
@@ -155,13 +155,13 @@ impl Buffer {
 
             total += match &attr.attr {
                 ElementAttr::AttrText { value, name } => {
-                    value.value().len() + name.span().line_length() + 3
+                    value.source.as_ref().unwrap().value().len() + name.span().line_length() + 3
                 }
                 ElementAttr::AttrExpression { name, value } => {
                     value.span().line_length() + name.span().line_length() + 3
                 }
                 ElementAttr::CustomAttrText { value, name } => {
-                    value.value().len() + name.value().len() + 3
+                    value.source.as_ref().unwrap().value().len() + name.value().len() + 3
                 }
                 ElementAttr::CustomAttrExpression { name, value } => {
                     name.value().len() + value.span().line_length() + 3

+ 7 - 2
packages/autofmt/src/component.rs

@@ -167,7 +167,12 @@ impl Buffer {
                     write!(self.buf, "{}: {}", name, out)?;
                 }
                 ContentField::Formatted(s) => {
-                    write!(self.buf, "{}: \"{}\"", name, s.value())?;
+                    write!(
+                        self.buf,
+                        "{}: \"{}\"",
+                        name,
+                        s.source.as_ref().unwrap().value()
+                    )?;
                 }
                 ContentField::OnHandlerRaw(exp) => {
                     let out = prettyplease::unparse_expr(exp);
@@ -209,7 +214,7 @@ impl Buffer {
         let attr_len = fields
             .iter()
             .map(|field| match &field.content {
-                ContentField::Formatted(s) => s.value().len() ,
+                ContentField::Formatted(s) => s.source.as_ref().unwrap().value().len() ,
                 ContentField::OnHandlerRaw(exp) | ContentField::ManExpr(exp) => {
                     let formatted = prettyplease::unparse_expr(exp);
                     let len = if formatted.contains('\n') {

+ 13 - 5
packages/autofmt/src/element.rs

@@ -132,7 +132,7 @@ impl Buffer {
     fn write_attributes(
         &mut self,
         attributes: &[ElementAttrNamed],
-        key: &Option<syn::LitStr>,
+        key: &Option<IfmtInput>,
         sameline: bool,
     ) -> Result {
         let mut attr_iter = attributes.iter().peekable();
@@ -141,7 +141,11 @@ impl Buffer {
             if !sameline {
                 self.indented_tabbed_line()?;
             }
-            write!(self.buf, "key: \"{}\"", key.value())?;
+            write!(
+                self.buf,
+                "key: \"{}\"",
+                key.source.as_ref().unwrap().value()
+            )?;
             if !attributes.is_empty() {
                 write!(self.buf, ",")?;
                 if sameline {
@@ -178,7 +182,11 @@ impl Buffer {
     fn write_attribute(&mut self, attr: &ElementAttrNamed) -> Result {
         match &attr.attr {
             ElementAttr::AttrText { name, value } => {
-                write!(self.buf, "{name}: \"{value}\"", value = value.value())?;
+                write!(
+                    self.buf,
+                    "{name}: \"{value}\"",
+                    value = value.source.as_ref().unwrap().value()
+                )?;
             }
             ElementAttr::AttrExpression { name, value } => {
                 let out = prettyplease::unparse_expr(value);
@@ -190,7 +198,7 @@ impl Buffer {
                     self.buf,
                     "\"{name}\": \"{value}\"",
                     name = name.value(),
-                    value = value.value()
+                    value = value.source.as_ref().unwrap().value()
                 )?;
             }
 
@@ -272,7 +280,7 @@ impl Buffer {
         }
 
         match children {
-            [BodyNode::Text(ref text)] => Some(text.value().len()),
+            [BodyNode::Text(ref text)] => Some(text.source.as_ref().unwrap().value().len()),
             [BodyNode::Component(ref comp)] => {
                 let attr_len = self.field_len(&comp.fields, &comp.manual_props);
 

+ 0 - 2
packages/core-macro/Cargo.toml

@@ -19,7 +19,6 @@ proc-macro2 = { version = "1.0" }
 quote = "1.0"
 syn = { version = "1.0", features = ["full", "extra-traits"] }
 dioxus-rsx = {  path = "../rsx" }
-dioxus-rsx-interpreter = { path = "../rsx_interpreter", optional = true }
 
 # testing
 [dev-dependencies]
@@ -28,4 +27,3 @@ trybuild = "1.0"
 
 [features]
 default = []
-hot-reload = ["dioxus-rsx-interpreter"]

+ 12 - 31
packages/core-macro/src/lib.rs

@@ -34,40 +34,21 @@ pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::Token
 /// ```
 #[proc_macro]
 pub fn rsx(s: TokenStream) -> TokenStream {
-    #[cfg(feature = "hot-reload")]
-    let rsx_text = s.to_string();
     match syn::parse::<rsx::CallBody>(s) {
         Err(err) => err.to_compile_error().into(),
-        Ok(body) => {
-            #[cfg(feature = "hot-reload")]
-            {
-                use dioxus_rsx_interpreter::captuered_context::CapturedContextBuilder;
-
-                match CapturedContextBuilder::from_call_body(body) {
-                    Ok(captured) => {
-                        let lazy = quote::quote! {
-                            LazyNodes::new(move |__cx|{
-                                let code_location = get_line_num!();
-                                let captured = #captured;
-                                let text = #rsx_text;
+        Ok(body) => body.to_token_stream().into(),
+    }
+}
 
-                                resolve_scope(code_location, text, captured, __cx)
-                            })
-                        };
-                        if let Some(cx) = captured.custom_context {
-                            quote::quote! {
-                                #cx.render(#lazy)
-                            }
-                            .into()
-                        } else {
-                            lazy.into()
-                        }
-                    }
-                    Err(err) => err.into_compile_error().into(),
-                }
-            }
-            #[cfg(not(feature = "hot-reload"))]
-            body.to_token_stream().into()
+/// A version of the rsx! macro that does not use templates. Used for testing diffing
+#[proc_macro]
+pub fn rsx_without_templates(s: TokenStream) -> TokenStream {
+    match syn::parse::<rsx::CallBody>(s) {
+        Err(err) => err.to_compile_error().into(),
+        Ok(body) => {
+            let mut tokens = proc_macro2::TokenStream::new();
+            body.to_tokens_without_template(&mut tokens);
+            tokens.into()
         }
     }
 }

+ 2 - 1
packages/core/Cargo.toml

@@ -48,4 +48,5 @@ backtrace = "0.3"
 [features]
 default = []
 serialize = ["serde"]
-debug_vdom = []
+debug_vdom = []
+hot-reload = []

+ 201 - 0
packages/core/src/arbitrary_value.rs

@@ -233,3 +233,204 @@ impl<'a> AttributeValue<'a> {
         }
     }
 }
+
+/// A owned attribute value.
+#[derive(Debug, Clone, PartialEq)]
+#[cfg_attr(
+    all(feature = "serialize"),
+    derive(serde::Serialize, serde::Deserialize)
+)]
+#[allow(missing_docs)]
+pub enum OwnedAttributeValue {
+    Text(String),
+    Float32(f32),
+    Float64(f64),
+    Int32(i32),
+    Int64(i64),
+    Uint32(u32),
+    Uint64(u64),
+    Bool(bool),
+
+    Vec3Float(f32, f32, f32),
+    Vec3Int(i32, i32, i32),
+    Vec3Uint(u32, u32, u32),
+
+    Vec4Float(f32, f32, f32, f32),
+    Vec4Int(i32, i32, i32, i32),
+    Vec4Uint(u32, u32, u32, u32),
+
+    Bytes(Vec<u8>),
+    // TODO: support other types
+    // Any(ArbitraryAttributeValue<'a>),
+}
+
+impl PartialEq<AttributeValue<'_>> for OwnedAttributeValue {
+    fn eq(&self, other: &AttributeValue<'_>) -> bool {
+        match (self, other) {
+            (Self::Text(l0), AttributeValue::Text(r0)) => l0 == r0,
+            (Self::Float32(l0), AttributeValue::Float32(r0)) => l0 == r0,
+            (Self::Float64(l0), AttributeValue::Float64(r0)) => l0 == r0,
+            (Self::Int32(l0), AttributeValue::Int32(r0)) => l0 == r0,
+            (Self::Int64(l0), AttributeValue::Int64(r0)) => l0 == r0,
+            (Self::Uint32(l0), AttributeValue::Uint32(r0)) => l0 == r0,
+            (Self::Uint64(l0), AttributeValue::Uint64(r0)) => l0 == r0,
+            (Self::Bool(l0), AttributeValue::Bool(r0)) => l0 == r0,
+            (Self::Vec3Float(l0, l1, l2), AttributeValue::Vec3Float(r0, r1, r2)) => {
+                l0 == r0 && l1 == r1 && l2 == r2
+            }
+            (Self::Vec3Int(l0, l1, l2), AttributeValue::Vec3Int(r0, r1, r2)) => {
+                l0 == r0 && l1 == r1 && l2 == r2
+            }
+            (Self::Vec3Uint(l0, l1, l2), AttributeValue::Vec3Uint(r0, r1, r2)) => {
+                l0 == r0 && l1 == r1 && l2 == r2
+            }
+            (Self::Vec4Float(l0, l1, l2, l3), AttributeValue::Vec4Float(r0, r1, r2, r3)) => {
+                l0 == r0 && l1 == r1 && l2 == r2 && l3 == r3
+            }
+            (Self::Vec4Int(l0, l1, l2, l3), AttributeValue::Vec4Int(r0, r1, r2, r3)) => {
+                l0 == r0 && l1 == r1 && l2 == r2 && l3 == r3
+            }
+            (Self::Vec4Uint(l0, l1, l2, l3), AttributeValue::Vec4Uint(r0, r1, r2, r3)) => {
+                l0 == r0 && l1 == r1 && l2 == r2 && l3 == r3
+            }
+            (Self::Bytes(l0), AttributeValue::Bytes(r0)) => l0 == r0,
+            (_, _) => false,
+        }
+    }
+}
+
+impl<'a> From<AttributeValue<'a>> for OwnedAttributeValue {
+    fn from(attr: AttributeValue<'a>) -> Self {
+        match attr {
+            AttributeValue::Text(t) => OwnedAttributeValue::Text(t.to_owned()),
+            AttributeValue::Float32(f) => OwnedAttributeValue::Float32(f),
+            AttributeValue::Float64(f) => OwnedAttributeValue::Float64(f),
+            AttributeValue::Int32(i) => OwnedAttributeValue::Int32(i),
+            AttributeValue::Int64(i) => OwnedAttributeValue::Int64(i),
+            AttributeValue::Uint32(u) => OwnedAttributeValue::Uint32(u),
+            AttributeValue::Uint64(u) => OwnedAttributeValue::Uint64(u),
+            AttributeValue::Bool(b) => OwnedAttributeValue::Bool(b),
+            AttributeValue::Vec3Float(f1, f2, f3) => OwnedAttributeValue::Vec3Float(f1, f2, f3),
+            AttributeValue::Vec3Int(f1, f2, f3) => OwnedAttributeValue::Vec3Int(f1, f2, f3),
+            AttributeValue::Vec3Uint(f1, f2, f3) => OwnedAttributeValue::Vec3Uint(f1, f2, f3),
+            AttributeValue::Vec4Float(f1, f2, f3, f4) => {
+                OwnedAttributeValue::Vec4Float(f1, f2, f3, f4)
+            }
+            AttributeValue::Vec4Int(f1, f2, f3, f4) => OwnedAttributeValue::Vec4Int(f1, f2, f3, f4),
+            AttributeValue::Vec4Uint(f1, f2, f3, f4) => {
+                OwnedAttributeValue::Vec4Uint(f1, f2, f3, f4)
+            }
+            AttributeValue::Bytes(b) => OwnedAttributeValue::Bytes(b.to_owned()),
+            AttributeValue::Any(_) => todo!(),
+        }
+    }
+}
+
+// todo
+#[allow(missing_docs)]
+impl OwnedAttributeValue {
+    pub fn as_text(&self) -> Option<&str> {
+        match self {
+            OwnedAttributeValue::Text(s) => Some(s),
+            _ => None,
+        }
+    }
+
+    pub fn as_float32(&self) -> Option<f32> {
+        match self {
+            OwnedAttributeValue::Float32(f) => Some(*f),
+            _ => None,
+        }
+    }
+
+    pub fn as_float64(&self) -> Option<f64> {
+        match self {
+            OwnedAttributeValue::Float64(f) => Some(*f),
+            _ => None,
+        }
+    }
+
+    pub fn as_int32(&self) -> Option<i32> {
+        match self {
+            OwnedAttributeValue::Int32(i) => Some(*i),
+            _ => None,
+        }
+    }
+
+    pub fn as_int64(&self) -> Option<i64> {
+        match self {
+            OwnedAttributeValue::Int64(i) => Some(*i),
+            _ => None,
+        }
+    }
+
+    pub fn as_uint32(&self) -> Option<u32> {
+        match self {
+            OwnedAttributeValue::Uint32(i) => Some(*i),
+            _ => None,
+        }
+    }
+
+    pub fn as_uint64(&self) -> Option<u64> {
+        match self {
+            OwnedAttributeValue::Uint64(i) => Some(*i),
+            _ => None,
+        }
+    }
+
+    pub fn as_bool(&self) -> Option<bool> {
+        match self {
+            OwnedAttributeValue::Bool(b) => Some(*b),
+            _ => None,
+        }
+    }
+
+    pub fn as_vec3_float(&self) -> Option<(f32, f32, f32)> {
+        match self {
+            OwnedAttributeValue::Vec3Float(x, y, z) => Some((*x, *y, *z)),
+            _ => None,
+        }
+    }
+
+    pub fn as_vec3_int(&self) -> Option<(i32, i32, i32)> {
+        match self {
+            OwnedAttributeValue::Vec3Int(x, y, z) => Some((*x, *y, *z)),
+            _ => None,
+        }
+    }
+
+    pub fn as_vec3_uint(&self) -> Option<(u32, u32, u32)> {
+        match self {
+            OwnedAttributeValue::Vec3Uint(x, y, z) => Some((*x, *y, *z)),
+            _ => None,
+        }
+    }
+
+    pub fn as_vec4_float(&self) -> Option<(f32, f32, f32, f32)> {
+        match self {
+            OwnedAttributeValue::Vec4Float(x, y, z, w) => Some((*x, *y, *z, *w)),
+            _ => None,
+        }
+    }
+
+    pub fn as_vec4_int(&self) -> Option<(i32, i32, i32, i32)> {
+        match self {
+            OwnedAttributeValue::Vec4Int(x, y, z, w) => Some((*x, *y, *z, *w)),
+            _ => None,
+        }
+    }
+
+    pub fn as_vec4_uint(&self) -> Option<(u32, u32, u32, u32)> {
+        match self {
+            OwnedAttributeValue::Vec4Uint(x, y, z, w) => Some((*x, *y, *z, *w)),
+            _ => None,
+        }
+    }
+
+    pub fn as_bytes(&self) -> Option<&[u8]> {
+        match self {
+            OwnedAttributeValue::Bytes(b) => Some(b),
+            _ => None,
+        }
+    }
+}

+ 336 - 29
packages/core/src/diff.rs

@@ -91,10 +91,19 @@
 //! More info on how to improve this diffing algorithm:
 //!  - <https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/>
 
-use crate::innerlude::{
-    AnyProps, ElementId, Mutations, ScopeArena, ScopeId, VComponent, VElement, VFragment, VNode,
-    VPlaceholder, VText,
+use crate::{
+    dynamic_template_context::TemplateContext,
+    innerlude::{
+        AnyProps, ElementId, GlobalNodeId, Mutations, RendererTemplateId, ScopeArena, ScopeId,
+        VComponent, VElement, VFragment, VNode, VPlaceholder, VText,
+    },
+    template::{
+        Template, TemplateAttribute, TemplateElement, TemplateNode, TemplateNodeId,
+        TemplateNodeType, TemplateValue, TextTemplateSegment, VTemplateRef,
+    },
+    Attribute, TemplateAttributeValue,
 };
+use bumpalo::Bump;
 use fxhash::{FxHashMap, FxHashSet};
 use smallvec::{smallvec, SmallVec};
 
@@ -102,7 +111,7 @@ pub(crate) struct DiffState<'bump> {
     pub(crate) scopes: &'bump ScopeArena,
     pub(crate) mutations: Mutations<'bump>,
     pub(crate) force_diff: bool,
-    pub(crate) element_stack: SmallVec<[ElementId; 10]>,
+    pub(crate) element_stack: SmallVec<[GlobalNodeId; 10]>,
     pub(crate) scope_stack: SmallVec<[ScopeId; 5]>,
 }
 
@@ -133,7 +142,7 @@ impl<'b> DiffState<'b> {
     }
 
     pub fn diff_node(&mut self, old_node: &'b VNode<'b>, new_node: &'b VNode<'b>) {
-        use VNode::{Component, Element, Fragment, Placeholder, Text};
+        use VNode::{Component, Element, Fragment, Placeholder, TemplateRef, Text};
         match (old_node, new_node) {
             (Text(old), Text(new)) => {
                 self.diff_text_nodes(old, new, old_node, new_node);
@@ -148,16 +157,20 @@ impl<'b> DiffState<'b> {
             }
 
             (Component(old), Component(new)) => {
-                self.diff_component_nodes(old_node, new_node, *old, *new);
+                self.diff_component_nodes(*old, *new, old_node, new_node);
             }
 
             (Fragment(old), Fragment(new)) => {
                 self.diff_fragment_nodes(old, new);
             }
 
+            (TemplateRef(old), TemplateRef(new)) => {
+                self.diff_template_ref_nodes(old, new, old_node, new_node);
+            }
+
             (
-                Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_),
-                Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_),
+                Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_) | TemplateRef(_),
+                Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_) | TemplateRef(_),
             ) => self.replace_node(old_node, new_node),
         }
     }
@@ -169,6 +182,7 @@ impl<'b> DiffState<'b> {
             VNode::Element(element) => self.create_element_node(element, node),
             VNode::Fragment(frag) => self.create_fragment_node(frag),
             VNode::Component(component) => self.create_component_node(*component),
+            VNode::TemplateRef(temp) => self.create_template_ref_node(*temp, node),
         }
     }
 
@@ -204,19 +218,21 @@ impl<'b> DiffState<'b> {
 
         dom_id.set(Some(real_id));
 
-        self.element_stack.push(real_id);
+        self.element_stack.push(GlobalNodeId::VNodeId(real_id));
         {
             self.mutations.create_element(tag_name, *namespace, real_id);
 
             let cur_scope_id = self.current_scope();
 
             for listener in listeners.iter() {
-                listener.mounted_node.set(Some(real_id));
+                listener
+                    .mounted_node
+                    .set(Some(GlobalNodeId::VNodeId(real_id)));
                 self.mutations.new_event_listener(listener, cur_scope_id);
             }
 
             for attr in attributes.iter() {
-                self.mutations.set_attribute(attr, real_id.as_u64());
+                self.mutations.set_attribute(attr, real_id);
             }
 
             if !children.is_empty() {
@@ -249,7 +265,6 @@ impl<'b> DiffState<'b> {
                 props,
                 Some(parent_idx),
                 self.element_stack.last().copied().unwrap(),
-                0,
             )
         };
 
@@ -286,6 +301,37 @@ impl<'b> DiffState<'b> {
         created
     }
 
+    pub(crate) fn create_template_ref_node(
+        &mut self,
+        new: &'b VTemplateRef<'b>,
+        node: &'b VNode<'b>,
+    ) -> usize {
+        let (id, created) = {
+            let mut resolver = self.scopes.template_resolver.borrow_mut();
+            resolver.get_or_create_client_id(&new.template_id)
+        };
+
+        let template = {
+            let templates = self.scopes.templates.borrow();
+            templates.get(&new.template_id).unwrap().clone()
+        };
+        let template = template.borrow();
+
+        if created {
+            self.register_template(&template, id);
+        }
+
+        let real_id = self.scopes.reserve_node(node);
+
+        new.id.set(Some(real_id));
+
+        self.mutations.create_template_ref(real_id, id.into());
+
+        new.hydrate(&template, self);
+
+        1
+    }
+
     pub(crate) fn diff_text_nodes(
         &mut self,
         old: &'b VText<'b>,
@@ -304,7 +350,7 @@ impl<'b> DiffState<'b> {
         };
 
         if old.text != new.text {
-            self.mutations.set_text(new.text, root.as_u64());
+            self.mutations.set_text(new.text, root);
         }
 
         self.scopes.update_node(new_node, root);
@@ -377,16 +423,18 @@ impl<'b> DiffState<'b> {
         // TODO: take a more efficient path than this
         if old.attributes.len() == new.attributes.len() {
             for (old_attr, new_attr) in old.attributes.iter().zip(new.attributes.iter()) {
-                if old_attr.value != new_attr.value || new_attr.is_volatile {
-                    self.mutations.set_attribute(new_attr, root.as_u64());
+                if !old_attr.is_static && old_attr.value != new_attr.value
+                    || new_attr.attribute.volatile
+                {
+                    self.mutations.set_attribute(new_attr, root);
                 }
             }
         } else {
             for attribute in old.attributes {
-                self.mutations.remove_attribute(attribute, root.as_u64());
+                self.mutations.remove_attribute(attribute, root);
             }
             for attribute in new.attributes {
-                self.mutations.set_attribute(attribute, root.as_u64());
+                self.mutations.set_attribute(attribute, root);
             }
         }
 
@@ -404,18 +452,16 @@ impl<'b> DiffState<'b> {
             for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) {
                 new_l.mounted_node.set(old_l.mounted_node.get());
                 if old_l.event != new_l.event {
-                    self.mutations
-                        .remove_event_listener(old_l.event, root.as_u64());
+                    self.mutations.remove_event_listener(old_l.event, root);
                     self.mutations.new_event_listener(new_l, cur_scope_id);
                 }
             }
         } else {
             for listener in old.listeners {
-                self.mutations
-                    .remove_event_listener(listener.event, root.as_u64());
+                self.mutations.remove_event_listener(listener.event, root);
             }
             for listener in new.listeners {
-                listener.mounted_node.set(Some(root));
+                listener.mounted_node.set(Some(GlobalNodeId::VNodeId(root)));
                 self.mutations.new_event_listener(listener, cur_scope_id);
             }
         }
@@ -434,10 +480,10 @@ impl<'b> DiffState<'b> {
 
     fn diff_component_nodes(
         &mut self,
-        old_node: &'b VNode<'b>,
-        new_node: &'b VNode<'b>,
         old: &'b VComponent<'b>,
         new: &'b VComponent<'b>,
+        old_node: &'b VNode<'b>,
+        new_node: &'b VNode<'b>,
     ) {
         let scope_addr = old
             .scope
@@ -526,6 +572,228 @@ impl<'b> DiffState<'b> {
         self.diff_children(old.children, new.children);
     }
 
+    fn diff_template_ref_nodes(
+        &mut self,
+        old: &'b VTemplateRef<'b>,
+        new: &'b VTemplateRef<'b>,
+        old_node: &'b VNode<'b>,
+        new_node: &'b VNode<'b>,
+    ) {
+        if std::ptr::eq(old, new) {
+            return;
+        }
+
+        // if the templates are different, just rebuild it
+        if old.template_id != new.template_id
+            || self
+                .scopes
+                .template_resolver
+                .borrow()
+                .is_dirty(&new.template_id)
+        {
+            self.replace_node(old_node, new_node);
+            return;
+        }
+
+        // if the node is comming back not assigned, that means it was borrowed but removed
+        let root = match old.id.get() {
+            Some(id) => id,
+            None => self.scopes.reserve_node(new_node),
+        };
+
+        self.scopes.update_node(new_node, root);
+
+        new.id.set(Some(root));
+
+        self.element_stack.push(GlobalNodeId::VNodeId(root));
+
+        self.mutations.enter_template_ref(root);
+
+        let scope_bump = &self.current_scope_bump();
+
+        let template = {
+            let templates = self.scopes.templates.borrow();
+            templates.get(&new.template_id).unwrap().clone()
+        };
+        let template = template.borrow();
+
+        fn diff_attributes<'b, Nodes, Attributes, V, Children, Listeners, TextSegments, Text>(
+            nodes: &Nodes,
+            ctx: (
+                &mut Mutations<'b>,
+                &'b Bump,
+                &VTemplateRef<'b>,
+                &Template,
+                usize,
+            ),
+        ) where
+            Nodes: AsRef<[TemplateNode<Attributes, V, Children, Listeners, TextSegments, Text>]>,
+            Attributes: AsRef<[TemplateAttribute<V>]>,
+            V: TemplateValue,
+            Children: AsRef<[TemplateNodeId]>,
+            Listeners: AsRef<[usize]>,
+            TextSegments: AsRef<[TextTemplateSegment<Text>]>,
+            Text: AsRef<str>,
+        {
+            let (mutations, scope_bump, new, template, idx) = ctx;
+            for (node_id, attr_idx) in template.get_dynamic_nodes_for_attribute_index(idx) {
+                if let TemplateNodeType::Element(el) = &nodes.as_ref()[node_id.0].node_type {
+                    let TemplateElement { attributes, .. } = el;
+                    let attr = &attributes.as_ref()[*attr_idx];
+                    let attribute = Attribute {
+                        attribute: attr.attribute,
+                        value: new.dynamic_context.resolve_attribute(idx).clone(),
+                        is_static: false,
+                    };
+                    mutations.set_attribute(scope_bump.alloc(attribute), *node_id);
+                } else {
+                    panic!("expected element node");
+                }
+            }
+        }
+
+        // diff dynamic attributes
+        for (idx, (old_attr, new_attr)) in old
+            .dynamic_context
+            .attributes
+            .iter()
+            .zip(new.dynamic_context.attributes.iter())
+            .enumerate()
+        {
+            if old_attr != new_attr {
+                template.with_nodes(
+                    diff_attributes,
+                    diff_attributes,
+                    (&mut self.mutations, scope_bump, new, &template, idx),
+                );
+            }
+        }
+
+        fn set_attribute<'b, Attributes, V, Children, Listeners, TextSegments, Text>(
+            node: &TemplateNode<Attributes, V, Children, Listeners, TextSegments, Text>,
+            ctx: (&mut Mutations<'b>, &'b Bump, &VTemplateRef<'b>, usize),
+        ) where
+            Attributes: AsRef<[TemplateAttribute<V>]>,
+            V: TemplateValue,
+            Children: AsRef<[TemplateNodeId]>,
+            Listeners: AsRef<[usize]>,
+            TextSegments: AsRef<[TextTemplateSegment<Text>]>,
+            Text: AsRef<str>,
+        {
+            let (mutations, scope_bump, new, template_attr_idx) = ctx;
+            if let TemplateNodeType::Element(el) = &node.node_type {
+                let TemplateElement { attributes, .. } = el;
+                let attr = &attributes.as_ref()[template_attr_idx];
+                let value = match &attr.value {
+                    TemplateAttributeValue::Dynamic(idx) => {
+                        new.dynamic_context.resolve_attribute(*idx).clone()
+                    }
+                    TemplateAttributeValue::Static(value) => value.allocate(scope_bump),
+                };
+                let attribute = Attribute {
+                    attribute: attr.attribute,
+                    value,
+                    is_static: false,
+                };
+                mutations.set_attribute(scope_bump.alloc(attribute), node.id);
+            } else {
+                panic!("expected element node");
+            }
+        }
+
+        // set all volatile attributes
+        for (id, idx) in template.volatile_attributes() {
+            template.with_node(
+                id,
+                set_attribute,
+                set_attribute,
+                (&mut self.mutations, scope_bump, new, idx),
+            )
+        }
+
+        fn diff_dynamic_node<'b, Attributes, V, Children, Listeners, TextSegments, Text>(
+            node: &TemplateNode<Attributes, V, Children, Listeners, TextSegments, Text>,
+            ctx: (&mut DiffState<'b>, &'b VNode<'b>, &'b VNode<'b>, ElementId),
+        ) where
+            Attributes: AsRef<[TemplateAttribute<V>]>,
+            V: TemplateValue,
+            Children: AsRef<[TemplateNodeId]>,
+            Listeners: AsRef<[usize]>,
+            TextSegments: AsRef<[TextTemplateSegment<Text>]>,
+            Text: AsRef<str>,
+        {
+            let (diff, old_node, new_node, root) = ctx;
+            if let TemplateNodeType::Element { .. } = node.node_type {
+                diff.element_stack.push(GlobalNodeId::VNodeId(root));
+                diff.diff_node(old_node, new_node);
+                diff.element_stack.pop();
+            } else {
+                diff.diff_node(old_node, new_node);
+            }
+        }
+
+        // diff dynmaic nodes
+        for (idx, (old_node, new_node)) in old
+            .dynamic_context
+            .nodes
+            .iter()
+            .zip(new.dynamic_context.nodes.iter())
+            .enumerate()
+        {
+            if let Some(id) = template.get_dynamic_nodes_for_node_index(idx) {
+                template.with_node(
+                    id,
+                    diff_dynamic_node,
+                    diff_dynamic_node,
+                    (self, old_node, new_node, root),
+                );
+            }
+        }
+
+        // diff dynamic text
+        // text nodes could rely on multiple dynamic text parts, so we keep a record of which ones to rerender and send the diffs at the end
+        let mut dirty_text_nodes = FxHashSet::default();
+        for (idx, (old_text, new_text)) in old
+            .dynamic_context
+            .text_segments
+            .iter()
+            .zip(new.dynamic_context.text_segments.iter())
+            .enumerate()
+        {
+            if old_text != new_text {
+                for node_id in template.get_dynamic_nodes_for_text_index(idx) {
+                    dirty_text_nodes.insert(*node_id);
+                }
+            }
+        }
+        for node_id in dirty_text_nodes {
+            fn diff_text<'b, Attributes, V, Children, Listeners, TextSegments, Text>(
+                node: &TemplateNode<Attributes, V, Children, Listeners, TextSegments, Text>,
+                ctx: (&mut DiffState<'b>, &TemplateContext<'b>),
+            ) where
+                Attributes: AsRef<[TemplateAttribute<V>]>,
+                V: TemplateValue,
+                Children: AsRef<[TemplateNodeId]>,
+                Listeners: AsRef<[usize]>,
+                TextSegments: AsRef<[TextTemplateSegment<Text>]>,
+                Text: AsRef<str>,
+            {
+                let (diff, dynamic_context) = ctx;
+                if let TemplateNodeType::Text(text) = &node.node_type {
+                    let text = dynamic_context.resolve_text(&text.segments.as_ref());
+                    diff.mutations
+                        .set_text(diff.current_scope_bump().alloc(text), node.id);
+                } else {
+                    panic!("expected text node");
+                }
+            }
+            template.with_node(node_id, diff_text, diff_text, (self, &new.dynamic_context));
+        }
+
+        self.mutations.exit_template_ref();
+        self.element_stack.pop();
+    }
+
     // Diff the given set of old and new children.
     //
     // The parent must be on top of the change list stack when this function is
@@ -970,6 +1238,16 @@ impl<'b> DiffState<'b> {
                 }
                 self.leave_scope();
             }
+
+            VNode::TemplateRef(t) => {
+                let id = old
+                    .try_mounted_id()
+                    .unwrap_or_else(|| panic!("broke on {:?}", old));
+
+                self.mutations.replace_with(id, nodes_created as u32);
+                self.remove_nodes(t.dynamic_context.nodes, true);
+                self.scopes.collect_garbage(id);
+            }
         }
     }
 
@@ -983,7 +1261,7 @@ impl<'b> DiffState<'b> {
                         t.id.set(None);
 
                         if gen_muts {
-                            self.mutations.remove(id.as_u64());
+                            self.mutations.remove(id);
                         }
                     }
                 }
@@ -993,14 +1271,14 @@ impl<'b> DiffState<'b> {
                     a.id.set(None);
 
                     if gen_muts {
-                        self.mutations.remove(id.as_u64());
+                        self.mutations.remove(id);
                     }
                 }
                 VNode::Element(e) => {
                     let id = e.id.get().unwrap();
 
                     if gen_muts {
-                        self.mutations.remove(id.as_u64());
+                        self.mutations.remove(id);
                     }
 
                     self.scopes.collect_garbage(id);
@@ -1029,6 +1307,19 @@ impl<'b> DiffState<'b> {
                     }
                     self.leave_scope();
                 }
+
+                VNode::TemplateRef(t) => {
+                    let id = t.id.get().unwrap();
+
+                    if gen_muts {
+                        self.mutations.remove(id);
+                    }
+
+                    self.scopes.collect_garbage(id);
+                    t.id.set(None);
+
+                    self.remove_nodes(t.dynamic_context.nodes, gen_muts);
+                }
             }
         }
     }
@@ -1058,7 +1349,7 @@ impl<'b> DiffState<'b> {
         self.mutations.insert_before(first, created as u32);
     }
 
-    fn current_scope(&self) -> ScopeId {
+    pub fn current_scope(&self) -> ScopeId {
         self.scope_stack.last().copied().expect("no current scope")
     }
 
@@ -1082,6 +1373,7 @@ impl<'b> DiffState<'b> {
                     let scope_id = el.scope.get().unwrap();
                     search_node = Some(self.scopes.root_node(scope_id));
                 }
+                VNode::TemplateRef(t) => break t.id.get(),
             }
         }
     }
@@ -1098,6 +1390,7 @@ impl<'b> DiffState<'b> {
                     let scope = el.scope.get().expect("element to have a scope assigned");
                     search_node = Some(self.scopes.root_node(scope));
                 }
+                VNode::TemplateRef(t) => break t.id.get(),
             }
         }
     }
@@ -1105,7 +1398,7 @@ impl<'b> DiffState<'b> {
     // recursively push all the nodes of a tree onto the stack and return how many are there
     fn push_all_real_nodes(&mut self, node: &'b VNode<'b>) -> usize {
         match node {
-            VNode::Text(_) | VNode::Placeholder(_) | VNode::Element(_) => {
+            VNode::Text(_) | VNode::Placeholder(_) | VNode::Element(_) | VNode::TemplateRef(_) => {
                 self.mutations.push_root(node.mounted_id());
                 1
             }
@@ -1125,4 +1418,18 @@ impl<'b> DiffState<'b> {
             }
         }
     }
+
+    pub(crate) fn current_scope_bump(&self) -> &'b Bump {
+        &self
+            .scopes
+            .get_scope(self.current_scope())
+            .unwrap()
+            .fin_frame()
+            .bump
+    }
+
+    pub fn register_template(&mut self, template: &Template, id: RendererTemplateId) {
+        let bump = &self.scopes.template_bump;
+        template.create(&mut self.mutations, bump, id);
+    }
 }

+ 195 - 0
packages/core/src/dynamic_template_context.rs

@@ -0,0 +1,195 @@
+use std::{marker::PhantomData, ops::Deref};
+
+use once_cell::sync::Lazy;
+
+use crate::{
+    template::{TemplateNodeId, TextTemplateSegment},
+    AttributeValue, Listener, VNode,
+};
+
+/// A lazily initailized vector
+#[derive(Debug, Clone, Copy)]
+pub struct LazyStaticVec<T: 'static>(pub &'static Lazy<Vec<T>>);
+
+impl<T: 'static> AsRef<[T]> for LazyStaticVec<T> {
+    fn as_ref(&self) -> &[T] {
+        let v: &Vec<_> = self.0.deref();
+        v.as_ref()
+    }
+}
+
+impl<T> PartialEq for LazyStaticVec<T> {
+    fn eq(&self, other: &Self) -> bool {
+        std::ptr::eq(self.0, other.0)
+    }
+}
+
+/// Stores what nodes depend on specific dynamic parts of the template to allow the diffing algorithm to jump to that part of the template instead of travering it
+/// This makes adding constant template nodes add no additional cost to diffing.
+#[derive(Debug, Clone, Copy, PartialEq)]
+#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
+pub struct DynamicNodeMapping<
+    Nodes,
+    TextOuter,
+    TextInner,
+    AttributesOuter,
+    AttributesInner,
+    Volatile,
+    Listeners,
+> where
+    Nodes: AsRef<[Option<TemplateNodeId>]>,
+    TextOuter: AsRef<[TextInner]>,
+    TextInner: AsRef<[TemplateNodeId]>,
+    AttributesOuter: AsRef<[AttributesInner]>,
+    AttributesInner: AsRef<[(TemplateNodeId, usize)]>,
+    Volatile: AsRef<[(TemplateNodeId, usize)]>,
+    Listeners: AsRef<[TemplateNodeId]>,
+{
+    /// The node that depend on each node in the dynamic template
+    pub nodes: Nodes,
+    text_inner: PhantomData<TextInner>,
+    /// The text nodes that depend on each text segment of the dynamic template
+    pub text: TextOuter,
+    /// The attributes along with the attribute index in the template that depend on each attribute of the dynamic template
+    pub attributes: AttributesOuter,
+    attributes_inner: PhantomData<AttributesInner>,
+    /// The attributes that are marked as volatile in the template
+    pub volatile_attributes: Volatile,
+    /// The listeners that depend on each listener of the dynamic template
+    pub nodes_with_listeners: Listeners,
+}
+
+impl<Nodes, TextOuter, TextInner, AttributesOuter, AttributesInner, Volatile, Listeners>
+    DynamicNodeMapping<
+        Nodes,
+        TextOuter,
+        TextInner,
+        AttributesOuter,
+        AttributesInner,
+        Volatile,
+        Listeners,
+    >
+where
+    Nodes: AsRef<[Option<TemplateNodeId>]>,
+    TextOuter: AsRef<[TextInner]>,
+    TextInner: AsRef<[TemplateNodeId]>,
+    AttributesOuter: AsRef<[AttributesInner]>,
+    AttributesInner: AsRef<[(TemplateNodeId, usize)]>,
+    Volatile: AsRef<[(TemplateNodeId, usize)]>,
+    Listeners: AsRef<[TemplateNodeId]>,
+{
+    /// Creates a new dynamic node mapping
+    pub const fn new(
+        nodes: Nodes,
+        text: TextOuter,
+        attributes: AttributesOuter,
+        volatile_attributes: Volatile,
+        listeners: Listeners,
+    ) -> Self {
+        DynamicNodeMapping {
+            nodes,
+            text_inner: PhantomData,
+            text,
+            attributes,
+            attributes_inner: PhantomData,
+            volatile_attributes,
+            nodes_with_listeners: listeners,
+        }
+    }
+
+    pub(crate) fn all_dynamic<'a>(&'a self) -> impl Iterator<Item = TemplateNodeId> + 'a {
+        self.nodes
+            .as_ref()
+            .iter()
+            .filter_map(|o| o.as_ref())
+            .chain(
+                self.text
+                    .as_ref()
+                    .iter()
+                    .map(|ids| ids.as_ref().iter())
+                    .flatten(),
+            )
+            .copied()
+            .chain(
+                self.attributes
+                    .as_ref()
+                    .iter()
+                    .map(|ids| ids.as_ref().iter())
+                    .flatten()
+                    .map(|dynamic| dynamic.0),
+            )
+            .chain(self.nodes_with_listeners.as_ref().iter().copied())
+    }
+}
+
+/// A dynamic node mapping that is stack allocated
+pub type StaticDynamicNodeMapping = DynamicNodeMapping<
+    &'static [Option<TemplateNodeId>],
+    &'static [&'static [TemplateNodeId]],
+    &'static [TemplateNodeId],
+    &'static [&'static [(TemplateNodeId, usize)]],
+    &'static [(TemplateNodeId, usize)],
+    // volatile attribute information is available at compile time, but there is no way for the macro to generate it, so we initialize it lazily instead
+    LazyStaticVec<(TemplateNodeId, usize)>,
+    &'static [TemplateNodeId],
+>;
+
+#[cfg(any(feature = "hot-reload", debug_assertions))]
+/// A dynamic node mapping that is heap allocated
+pub type OwnedDynamicNodeMapping = DynamicNodeMapping<
+    Vec<Option<TemplateNodeId>>,
+    Vec<Vec<TemplateNodeId>>,
+    Vec<TemplateNodeId>,
+    Vec<Vec<(TemplateNodeId, usize)>>,
+    Vec<(TemplateNodeId, usize)>,
+    Vec<(TemplateNodeId, usize)>,
+    Vec<TemplateNodeId>,
+>;
+
+/// The dynamic parts used to saturate a template durring runtime
+pub struct TemplateContext<'b> {
+    /// The dynamic nodes
+    pub nodes: &'b [VNode<'b>],
+    /// The dynamic text
+    pub text_segments: &'b [&'b str],
+    /// The dynamic attributes
+    pub attributes: &'b [AttributeValue<'b>],
+    /// The dynamic attributes
+    // The listeners must not change during the lifetime of the context, use a dynamic node if the listeners change
+    pub listeners: &'b [Listener<'b>],
+    /// A optional key for diffing
+    pub key: Option<&'b str>,
+}
+
+impl<'b> TemplateContext<'b> {
+    /// Resolve text segments to a string
+    pub fn resolve_text<TextSegments, Text>(&self, text: &TextSegments) -> String
+    where
+        TextSegments: AsRef<[TextTemplateSegment<Text>]>,
+        Text: AsRef<str>,
+    {
+        let mut result = String::new();
+        for seg in text.as_ref() {
+            match seg {
+                TextTemplateSegment::Static(s) => result += s.as_ref(),
+                TextTemplateSegment::Dynamic(idx) => result += self.text_segments[*idx],
+            }
+        }
+        result
+    }
+
+    /// Resolve an attribute value
+    pub fn resolve_attribute(&self, idx: usize) -> &'b AttributeValue<'b> {
+        &self.attributes[idx]
+    }
+
+    /// Resolve a listener
+    pub fn resolve_listener(&self, idx: usize) -> &'b Listener<'b> {
+        &self.listeners[idx]
+    }
+
+    /// Resolve a node
+    pub fn resolve_node(&self, idx: usize) -> &'b VNode<'b> {
+        &self.nodes[idx]
+    }
+}

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

@@ -3,7 +3,7 @@
 //!
 //! This is all kinda WIP, but the bones are there.
 
-use crate::{ElementId, ScopeId};
+use crate::{nodes::GlobalNodeId, ScopeId};
 use std::{any::Any, cell::Cell, fmt::Debug, rc::Rc, sync::Arc};
 
 pub(crate) struct BubbleState {
@@ -58,7 +58,7 @@ pub struct UserEvent {
     pub priority: EventPriority,
 
     /// The optional real node associated with the trigger
-    pub element: Option<ElementId>,
+    pub element: Option<GlobalNodeId>,
 
     /// The event type IE "onclick" or "onmouseover"
     pub name: &'static str,

+ 28 - 7
packages/core/src/lib.rs

@@ -4,23 +4,27 @@
 
 pub(crate) mod arbitrary_value;
 pub(crate) mod diff;
+pub(crate) mod dynamic_template_context;
 pub(crate) mod events;
 pub(crate) mod lazynodes;
 pub(crate) mod mutations;
 pub(crate) mod nodes;
 pub(crate) mod properties;
 pub(crate) mod scopes;
+pub(crate) mod template;
 pub(crate) mod util;
 pub(crate) mod virtual_dom;
 
 pub(crate) mod innerlude {
     pub use crate::arbitrary_value::*;
+    pub use crate::dynamic_template_context::*;
     pub use crate::events::*;
     pub use crate::lazynodes::*;
     pub use crate::mutations::*;
     pub use crate::nodes::*;
     pub use crate::properties::*;
     pub use crate::scopes::*;
+    pub use crate::template::*;
     pub use crate::util::*;
     pub use crate::virtual_dom::*;
 
@@ -63,20 +67,36 @@ pub(crate) mod innerlude {
 }
 
 pub use crate::innerlude::{
-    AnyEvent, ArbitraryAttributeValue, Attribute, AttributeValue, Component, DioxusElement,
-    DomEdit, Element, ElementId, ElementIdIterator, EventHandler, EventPriority, IntoVNode,
-    LazyNodes, Listener, Mutations, NodeFactory, Properties, SchedulerMsg, Scope, ScopeId,
-    ScopeState, TaskId, UiEvent, UserEvent, VComponent, VElement, VFragment, VNode, VPlaceholder,
-    VText, VirtualDom,
+    AnyEvent, ArbitraryAttributeValue, Attribute, AttributeDiscription, AttributeValue,
+    CodeLocation, Component, DioxusElement, DomEdit, DynamicNodeMapping, Element, ElementId,
+    ElementIdIterator, EventHandler, EventPriority, GlobalNodeId, IntoVNode, LazyNodes, Listener,
+    Mutations, NodeFactory, OwnedAttributeValue, Properties, RendererTemplateId, SchedulerMsg,
+    Scope, ScopeId, ScopeState, StaticCodeLocation, StaticDynamicNodeMapping, StaticTemplateNode,
+    StaticTemplateNodes, TaskId, Template, TemplateAttribute, TemplateAttributeValue,
+    TemplateContext, TemplateElement, TemplateId, TemplateNode, TemplateNodeId, TemplateNodeType,
+    TemplateValue, TextTemplate, TextTemplateSegment, UiEvent, UserEvent, VComponent, VElement,
+    VFragment, VNode, VPlaceholder, VText, VirtualDom, JS_MAX_INT,
+};
+#[cfg(any(feature = "hot-reload", debug_assertions))]
+pub use crate::innerlude::{
+    OwnedCodeLocation, OwnedDynamicNodeMapping, OwnedTemplateNode, OwnedTemplateNodes,
+    SetTemplateMsg,
 };
 
 /// The purpose of this module is to alleviate imports of many common types
 ///
 /// This includes types like [`Scope`], [`Element`], and [`Component`].
 pub mod prelude {
+    pub use crate::get_line_num;
+    #[cfg(any(feature = "hot-reload", debug_assertions))]
+    pub use crate::innerlude::OwnedTemplate;
     pub use crate::innerlude::{
-        fc_to_builder, Attributes, Component, DioxusElement, Element, EventHandler, Fragment,
-        LazyNodes, NodeFactory, Properties, Scope, ScopeId, ScopeState, VNode, VirtualDom,
+        fc_to_builder, AttributeDiscription, AttributeValue, Attributes, CodeLocation, Component,
+        DioxusElement, Element, EventHandler, Fragment, LazyNodes, LazyStaticVec, NodeFactory,
+        Properties, Scope, ScopeId, ScopeState, StaticAttributeValue, StaticCodeLocation,
+        StaticDynamicNodeMapping, StaticTemplate, StaticTemplateNodes, Template, TemplateAttribute,
+        TemplateAttributeValue, TemplateContext, TemplateElement, TemplateId, TemplateNode,
+        TemplateNodeId, TemplateNodeType, TextTemplate, TextTemplateSegment, VNode, VirtualDom,
     };
 }
 
@@ -85,6 +105,7 @@ pub mod exports {
     //! Feel free to just add the dependencies in your own Crates.toml
     pub use bumpalo;
     pub use futures_channel;
+    pub use once_cell;
 }
 
 /// Functions that wrap unsafe functionality to prevent us from misusing it at the callsite

+ 201 - 33
packages/core/src/mutations.rs

@@ -128,6 +128,59 @@ pub enum DomEdit<'bump> {
         root: u64,
     },
 
+    /// Create a new purely-text node in a template
+    CreateTextNodeTemplate {
+        /// The ID the new node should have.
+        root: u64,
+
+        /// The textcontent of the noden
+        text: &'bump str,
+
+        /// If the id of the node must be kept in the refrences
+        locally_static: bool,
+    },
+
+    /// Create a new purely-element node in a template
+    CreateElementTemplate {
+        /// The ID the new node should have.
+        root: u64,
+
+        /// The tagname of the node
+        tag: &'bump str,
+
+        /// If the id of the node must be kept in the refrences
+        locally_static: bool,
+
+        /// If any children of this node must be kept in the references
+        fully_static: bool,
+    },
+
+    /// Create a new purely-comment node with a given namespace in a template
+    CreateElementNsTemplate {
+        /// The ID the new node should have.
+        root: u64,
+
+        /// The namespace of the node
+        tag: &'bump str,
+
+        /// The namespace of the node (like `SVG`)
+        ns: &'static str,
+
+        /// If the id of the node must be kept in the refrences
+        locally_static: bool,
+
+        /// If any children of this node must be kept in the references
+        fully_static: bool,
+    },
+
+    /// Create a new placeholder node.
+    /// In most implementations, this will either be a hidden div or a comment node. in a template
+    /// Always both locally and fully static
+    CreatePlaceholderTemplate {
+        /// The ID the new node should have.
+        root: u64,
+    },
+
     /// Create a new Event Listener.
     NewEventListener {
         /// The name of the event to listen for.
@@ -189,6 +242,38 @@ pub enum DomEdit<'bump> {
 
     /// Manually pop a root node from the stack.
     PopRoot {},
+
+    /// Enter a TemplateRef tree
+    EnterTemplateRef {
+        /// The ID of the node to enter.
+        root: u64,
+    },
+
+    /// Exit a TemplateRef tree
+    ExitTemplateRef {},
+
+    /// Create a refrence to a template node.
+    CreateTemplateRef {
+        /// The ID of the new template refrence.
+        id: u64,
+
+        /// The ID of the template the node is refrencing.
+        template_id: u64,
+    },
+
+    /// Create a new templete.
+    /// IMPORTANT: When adding nodes to a templete, id's will reset to zero, so they must be allocated on a different stack.
+    /// It is recommended to use Cow<NativeNode>.
+    CreateTemplate {
+        /// The ID of the new template.
+        id: u64,
+    },
+
+    /// Finish a templete
+    FinishTemplate {
+        /// The number of root nodes in the template
+        len: u32,
+    },
 }
 
 use fxhash::FxHashSet;
@@ -204,8 +289,8 @@ impl<'a> Mutations<'a> {
     }
 
     // Navigation
-    pub(crate) fn push_root(&mut self, root: ElementId) {
-        let id = root.as_u64();
+    pub(crate) fn push_root(&mut self, root: impl Into<u64>) {
+        let id = root.into();
         self.edits.push(PushRoot { root: id });
     }
 
@@ -214,18 +299,18 @@ impl<'a> Mutations<'a> {
         self.edits.push(PopRoot {});
     }
 
-    pub(crate) fn replace_with(&mut self, root: ElementId, m: u32) {
-        let root = root.as_u64();
+    pub(crate) fn replace_with(&mut self, root: impl Into<u64>, m: u32) {
+        let root = root.into();
         self.edits.push(ReplaceWith { m, root });
     }
 
-    pub(crate) fn insert_after(&mut self, root: ElementId, n: u32) {
-        let root = root.as_u64();
+    pub(crate) fn insert_after(&mut self, root: impl Into<u64>, n: u32) {
+        let root = root.into();
         self.edits.push(InsertAfter { n, root });
     }
 
-    pub(crate) fn insert_before(&mut self, root: ElementId, n: u32) {
-        let root = root.as_u64();
+    pub(crate) fn insert_before(&mut self, root: impl Into<u64>, n: u32) {
+        let root = root.into();
         self.edits.push(InsertBefore { n, root });
     }
 
@@ -234,13 +319,13 @@ impl<'a> Mutations<'a> {
     }
 
     // Remove Nodes from the dom
-    pub(crate) fn remove(&mut self, id: u64) {
-        self.edits.push(Remove { root: id });
+    pub(crate) fn remove(&mut self, id: impl Into<u64>) {
+        self.edits.push(Remove { root: id.into() });
     }
 
     // Create
-    pub(crate) fn create_text_node(&mut self, text: &'a str, id: ElementId) {
-        let id = id.as_u64();
+    pub(crate) fn create_text_node(&mut self, text: &'a str, id: impl Into<u64>) {
+        let id = id.into();
         self.edits.push(CreateTextNode { text, root: id });
     }
 
@@ -248,20 +333,68 @@ impl<'a> Mutations<'a> {
         &mut self,
         tag: &'static str,
         ns: Option<&'static str>,
-        id: ElementId,
+        id: impl Into<u64>,
     ) {
-        let id = id.as_u64();
+        let id = id.into();
         match ns {
             Some(ns) => self.edits.push(CreateElementNs { root: id, ns, tag }),
             None => self.edits.push(CreateElement { root: id, tag }),
         }
     }
+
     // placeholders are nodes that don't get rendered but still exist as an "anchor" in the real dom
-    pub(crate) fn create_placeholder(&mut self, id: ElementId) {
-        let id = id.as_u64();
+    pub(crate) fn create_placeholder(&mut self, id: impl Into<u64>) {
+        let id = id.into();
         self.edits.push(CreatePlaceholder { root: id });
     }
 
+    // Create
+    pub(crate) fn create_text_node_template(
+        &mut self,
+        text: &'a str,
+        id: impl Into<u64>,
+        locally_static: bool,
+    ) {
+        let id = id.into();
+        self.edits.push(CreateTextNodeTemplate {
+            text,
+            root: id,
+            locally_static,
+        });
+    }
+
+    pub(crate) fn create_element_template(
+        &mut self,
+        tag: &'static str,
+        ns: Option<&'static str>,
+        id: impl Into<u64>,
+        locally_static: bool,
+        fully_static: bool,
+    ) {
+        let id = id.into();
+        match ns {
+            Some(ns) => self.edits.push(CreateElementNsTemplate {
+                root: id,
+                ns,
+                tag,
+                locally_static,
+                fully_static,
+            }),
+            None => self.edits.push(CreateElementTemplate {
+                root: id,
+                tag,
+                locally_static,
+                fully_static,
+            }),
+        }
+    }
+
+    // placeholders are nodes that don't get rendered but still exist as an "anchor" in the real dom
+    pub(crate) fn create_placeholder_template(&mut self, id: impl Into<u64>) {
+        let id = id.into();
+        self.edits.push(CreatePlaceholderTemplate { root: id });
+    }
+
     // events
     pub(crate) fn new_event_listener(&mut self, listener: &Listener, scope: ScopeId) {
         let Listener {
@@ -270,7 +403,13 @@ impl<'a> Mutations<'a> {
             ..
         } = listener;
 
-        let element_id = mounted_node.get().unwrap().as_u64();
+        let element_id = match mounted_node.get().unwrap() {
+            GlobalNodeId::TemplateId {
+                template_ref_id: _,
+                template_node_id,
+            } => template_node_id.into(),
+            GlobalNodeId::VNodeId(id) => id.into(),
+        };
 
         self.edits.push(NewEventListener {
             scope,
@@ -278,39 +417,41 @@ impl<'a> Mutations<'a> {
             root: element_id,
         });
     }
-    pub(crate) fn remove_event_listener(&mut self, event: &'static str, root: u64) {
-        self.edits.push(RemoveEventListener { event, root });
+
+    pub(crate) fn remove_event_listener(&mut self, event: &'static str, root: impl Into<u64>) {
+        self.edits.push(RemoveEventListener {
+            event,
+            root: root.into(),
+        });
     }
 
     // modify
-    pub(crate) fn set_text(&mut self, text: &'a str, root: u64) {
+    pub(crate) fn set_text(&mut self, text: &'a str, root: impl Into<u64>) {
+        let root = root.into();
         self.edits.push(SetText { text, root });
     }
 
-    pub(crate) fn set_attribute(&mut self, attribute: &'a Attribute<'a>, root: u64) {
+    pub(crate) fn set_attribute(&mut self, attribute: &'a Attribute<'a>, root: impl Into<u64>) {
+        let root = root.into();
         let Attribute {
-            name,
-            value,
-            namespace,
-            ..
+            value, attribute, ..
         } = attribute;
 
         self.edits.push(SetAttribute {
-            field: name,
+            field: attribute.name,
             value: value.clone(),
-            ns: *namespace,
+            ns: attribute.namespace,
             root,
         });
     }
 
-    pub(crate) fn remove_attribute(&mut self, attribute: &Attribute, root: u64) {
-        let Attribute {
-            name, namespace, ..
-        } = attribute;
+    pub(crate) fn remove_attribute(&mut self, attribute: &Attribute, root: impl Into<u64>) {
+        let root = root.into();
+        let Attribute { attribute, .. } = attribute;
 
         self.edits.push(RemoveAttribute {
-            name,
-            ns: *namespace,
+            name: attribute.name,
+            ns: attribute.namespace,
             root,
         });
     }
@@ -318,6 +459,33 @@ impl<'a> Mutations<'a> {
     pub(crate) fn mark_dirty_scope(&mut self, scope: ScopeId) {
         self.dirty_scopes.insert(scope);
     }
+
+    pub(crate) fn create_templete(&mut self, id: impl Into<u64>) {
+        self.edits.push(CreateTemplate { id: id.into() });
+    }
+
+    pub(crate) fn finish_templete(&mut self, len: u32) {
+        self.edits.push(FinishTemplate { len });
+    }
+
+    pub(crate) fn create_template_ref(&mut self, id: impl Into<u64>, template_id: u64) {
+        self.edits.push(CreateTemplateRef {
+            id: id.into(),
+            template_id,
+        })
+    }
+
+    pub(crate) fn enter_template_ref(&mut self, id: impl Into<u64>) {
+        self.edits.push(EnterTemplateRef { root: id.into() });
+    }
+
+    pub(crate) fn exit_template_ref(&mut self) {
+        if let Some(&DomEdit::EnterTemplateRef { .. }) = self.edits.last() {
+            self.edits.pop();
+        } else {
+            self.edits.push(ExitTemplateRef {});
+        }
+    }
 }
 
 // refs are only assigned once

+ 162 - 28
packages/core/src/nodes.rs

@@ -3,16 +3,70 @@
 //! VNodes represent lazily-constructed VDom trees that support diffing and event handlers. These VNodes should be *very*
 //! cheap and *very* fast to construct - building a full tree should be quick.
 use crate::{
-    innerlude::{AttributeValue, ComponentPtr, Element, Properties, Scope, ScopeId, ScopeState},
+    dynamic_template_context::TemplateContext,
+    innerlude::{
+        AttributeValue, ComponentPtr, Element, Properties, Scope, ScopeId, ScopeState, Template,
+        TemplateId,
+    },
     lazynodes::LazyNodes,
+    template::{TemplateNodeId, VTemplateRef},
     AnyEvent, Component,
 };
 use bumpalo::{boxed::Box as BumpBox, Bump};
 use std::{
     cell::{Cell, RefCell},
     fmt::{Arguments, Debug, Formatter},
+    num::ParseIntError,
+    rc::Rc,
+    str::FromStr,
 };
 
+/// The ID of a node in the vdom that is either standalone or in a template
+#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq)]
+#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
+#[cfg_attr(feature = "serialize", serde(untagged))]
+pub enum GlobalNodeId {
+    /// The ID of a node and the template that contains it
+    TemplateId {
+        /// The template that contains the node
+        template_ref_id: ElementId,
+        /// The ID of the node in the template
+        template_node_id: TemplateNodeId,
+    },
+    /// The ID of a regular node
+    VNodeId(ElementId),
+}
+
+impl Into<GlobalNodeId> for ElementId {
+    fn into(self) -> GlobalNodeId {
+        GlobalNodeId::VNodeId(self)
+    }
+}
+
+impl PartialEq<ElementId> for GlobalNodeId {
+    fn eq(&self, other: &ElementId) -> bool {
+        match self {
+            GlobalNodeId::TemplateId { .. } => false,
+            GlobalNodeId::VNodeId(id) => id == other,
+        }
+    }
+}
+
+impl FromStr for GlobalNodeId {
+    type Err = ParseIntError;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        if let Some((tmpl_id, node_id)) = s.split_once(',') {
+            Ok(GlobalNodeId::TemplateId {
+                template_ref_id: ElementId(tmpl_id.parse()?),
+                template_node_id: TemplateNodeId(node_id.parse()?),
+            })
+        } else {
+            Ok(GlobalNodeId::VNodeId(ElementId(s.parse()?)))
+        }
+    }
+}
+
 /// A composable "VirtualNode" to declare a User Interface in the Dioxus VirtualDOM.
 ///
 /// VNodes are designed to be lightweight and used with with a bump allocator. To create a VNode, you can use either of:
@@ -112,6 +166,9 @@ pub enum VNode<'src> {
     /// }
     /// ```
     Placeholder(&'src VPlaceholder),
+
+    /// Templetes ase generated by the rsx macro to eleminate diffing static nodes.
+    TemplateRef(&'src VTemplateRef<'src>),
 }
 
 impl<'src> VNode<'src> {
@@ -123,6 +180,7 @@ impl<'src> VNode<'src> {
             VNode::Fragment(f) => f.key,
             VNode::Text(_t) => None,
             VNode::Placeholder(_f) => None,
+            VNode::TemplateRef(t) => t.dynamic_context.key,
         }
     }
 
@@ -143,6 +201,7 @@ impl<'src> VNode<'src> {
             VNode::Placeholder(el) => el.id.get(),
             VNode::Fragment(_) => None,
             VNode::Component(_) => None,
+            VNode::TemplateRef(t) => t.id.get(),
         }
     }
 
@@ -154,6 +213,7 @@ impl<'src> VNode<'src> {
             VNode::Component(c) => VNode::Component(c),
             VNode::Placeholder(a) => VNode::Placeholder(a),
             VNode::Fragment(f) => VNode::Fragment(f),
+            VNode::TemplateRef(t) => VNode::TemplateRef(t),
         }
     }
 }
@@ -189,6 +249,11 @@ impl Debug for VNode<'_> {
                 .field("key", &comp.key)
                 .field("scope", &comp.scope)
                 .finish(),
+            VNode::TemplateRef(temp) => s
+                .debug_struct("VNode::TemplateRef")
+                .field("template_id", &temp.template_id)
+                .field("id", &temp.id)
+                .finish(),
         }
     }
 }
@@ -198,6 +263,8 @@ impl Debug for VNode<'_> {
 /// `ElementId` is a `usize` that is unique across the entire VirtualDOM - but not unique across time. If a component is
 /// unmounted, then the `ElementId` will be reused for a new component.
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
+#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
+#[cfg_attr(feature = "serialize", serde(transparent))]
 pub struct ElementId(pub usize);
 impl std::fmt::Display for ElementId {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@@ -206,13 +273,19 @@ impl std::fmt::Display for ElementId {
 }
 
 impl ElementId {
-    /// Convertt the ElementId to a `u64`.
-    pub fn as_u64(self) -> u64 {
+    /// Convert the ElementId to a `u64`.
+    pub fn as_u64(&self) -> u64 {
+        (*self).into()
+    }
+}
+
+impl Into<u64> for ElementId {
+    fn into(self) -> u64 {
         self.0 as u64
     }
 }
 
-fn empty_cell() -> Cell<Option<ElementId>> {
+fn empty_cell<T>() -> Cell<Option<T>> {
     Cell::new(None)
 }
 
@@ -267,7 +340,7 @@ pub struct VElement<'a> {
     /// The parent of the Element (if any).
     ///
     /// Used when bubbling events
-    pub parent: Cell<Option<ElementId>>,
+    pub parent: Cell<Option<GlobalNodeId>>,
 
     /// The Listeners of the VElement.
     pub listeners: &'a [Listener<'a>],
@@ -330,30 +403,52 @@ pub trait DioxusElement {
     }
 }
 
+type StaticStr = &'static str;
+
+/// A discription of the attribute
+#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq)]
+#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
+#[cfg_attr(
+    all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
+    derive(serde::Deserialize)
+)]
+pub struct AttributeDiscription {
+    /// The name of the attribute.
+    #[cfg_attr(
+        all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
+        serde(deserialize_with = "crate::util::deserialize_static_leaky")
+    )]
+    pub name: StaticStr,
+
+    /// The namespace of the attribute.
+    ///
+    /// Doesn't exist in the html spec.
+    /// Used in Dioxus to denote "style" tags and other attribute groups.
+    #[cfg_attr(
+        all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
+        serde(deserialize_with = "crate::util::deserialize_static_leaky_ns")
+    )]
+    pub namespace: Option<StaticStr>,
+
+    /// An indication of we should always try and set the attribute.
+    /// Used in controlled components to ensure changes are propagated.
+    pub volatile: bool,
+}
+
 /// An attribute on a DOM node, such as `id="my-thing"` or
 /// `href="https://example.com"`.
 #[derive(Clone, Debug)]
 pub struct Attribute<'a> {
-    /// The name of the attribute.
-    pub name: &'static str,
-
-    /// The value of the attribute.
-    pub value: AttributeValue<'a>,
+    /// The discription of the attribute.
+    pub attribute: AttributeDiscription,
 
     /// An indication if this attribute can be ignored during diffing
     ///
     /// Usually only when there are no strings to be formatted (so the value is &'static str)
     pub is_static: bool,
 
-    /// An indication of we should always try and set the attribute.
-    /// Used in controlled components to ensure changes are propagated.
-    pub is_volatile: bool,
-
-    /// The namespace of the attribute.
-    ///
-    /// Doesn't exist in the html spec.
-    /// Used in Dioxus to denote "style" tags and other attribute groups.
-    pub namespace: Option<&'static str>,
+    /// The value of the attribute.
+    pub value: AttributeValue<'a>,
 }
 
 /// An event listener.
@@ -361,7 +456,7 @@ pub struct Attribute<'a> {
 pub struct Listener<'bump> {
     /// The ID of the node that this listener is mounted to
     /// Used to generate the event listener's ID on the DOM
-    pub mounted_node: Cell<Option<ElementId>>,
+    pub mounted_node: Cell<Option<GlobalNodeId>>,
 
     /// The type of event to listen for.
     ///
@@ -599,6 +694,20 @@ impl<'a> NodeFactory<'a> {
         }))
     }
 
+    /// Create a new [`Attribute`] from a attribute discrimination and a value
+    pub fn attr_disciption(
+        &self,
+        discription: AttributeDiscription,
+        val: Arguments,
+    ) -> Attribute<'a> {
+        let (value, is_static) = self.raw_text(val);
+        Attribute {
+            attribute: discription,
+            is_static,
+            value: AttributeValue::Text(value),
+        }
+    }
+
     /// Create a new [`Attribute`]
     pub fn attr(
         &self,
@@ -609,11 +718,13 @@ impl<'a> NodeFactory<'a> {
     ) -> Attribute<'a> {
         let (value, is_static) = self.raw_text(val);
         Attribute {
-            name,
-            value: AttributeValue::Text(value),
+            attribute: AttributeDiscription {
+                name,
+                namespace,
+                volatile: is_volatile,
+            },
             is_static,
-            namespace,
-            is_volatile,
+            value: AttributeValue::Text(value),
         }
     }
 
@@ -627,11 +738,13 @@ impl<'a> NodeFactory<'a> {
         is_static: bool,
     ) -> Attribute<'a> {
         Attribute {
-            name,
-            value,
+            attribute: AttributeDiscription {
+                name,
+                namespace,
+                volatile: is_volatile,
+            },
             is_static,
-            namespace,
-            is_volatile,
+            value,
         }
     }
 
@@ -743,6 +856,27 @@ impl<'a> NodeFactory<'a> {
         let callback = RefCell::new(Some(caller));
         EventHandler { callback }
     }
+
+    /// Create a refrence to a template
+    pub fn template_ref(
+        &self,
+        id: TemplateId,
+        template: Template,
+        dynamic_context: TemplateContext<'a>,
+    ) -> VNode<'a> {
+        let borrow_ref = self.scope.templates.borrow();
+        // We only create the template if it doesn't already exist to allow for hot reloading
+        if !borrow_ref.contains_key(&id) {
+            drop(borrow_ref);
+            let mut borrow_mut = self.scope.templates.borrow_mut();
+            borrow_mut.insert(id.clone(), Rc::new(RefCell::new(template)));
+        }
+        VNode::TemplateRef(self.bump.alloc(VTemplateRef {
+            id: empty_cell(),
+            dynamic_context,
+            template_id: id,
+        }))
+    }
 }
 
 impl Debug for NodeFactory<'_> {

+ 145 - 88
packages/core/src/scopes.rs

@@ -1,12 +1,19 @@
-use crate::{innerlude::*, unsafe_utils::extend_vnode};
+use crate::{
+    dynamic_template_context::TemplateContext,
+    innerlude::*,
+    template::{
+        TemplateAttribute, TemplateElement, TemplateNode, TemplateNodeId, TemplateNodeType,
+        TemplateValue, TextTemplateSegment,
+    },
+    unsafe_utils::extend_vnode,
+};
 use bumpalo::Bump;
 use futures_channel::mpsc::UnboundedSender;
 use fxhash::FxHashMap;
 use slab::Slab;
 use std::{
     any::{Any, TypeId},
-    borrow::Borrow,
-    cell::{Cell, RefCell},
+    cell::{Cell, Ref, RefCell},
     collections::{HashMap, HashSet},
     future::Future,
     pin::Pin,
@@ -35,6 +42,10 @@ pub(crate) struct ScopeArena {
     pub free_scopes: RefCell<Vec<*mut ScopeState>>,
     pub nodes: RefCell<Slab<*const VNode<'static>>>,
     pub tasks: Rc<TaskQueue>,
+    pub template_resolver: RefCell<TemplateResolver>,
+    pub templates: Rc<RefCell<FxHashMap<TemplateId, Rc<RefCell<Template>>>>>,
+    // this is used to store intermidiate artifacts of creating templates, so that the lifetime aligns with Mutations<'bump>.
+    pub template_bump: Bump,
 }
 
 impl ScopeArena {
@@ -74,6 +85,9 @@ impl ScopeArena {
                 gen: Cell::new(0),
                 sender,
             }),
+            template_resolver: RefCell::new(TemplateResolver::default()),
+            templates: Rc::new(RefCell::new(FxHashMap::default())),
+            template_bump: Bump::new(),
         }
     }
 
@@ -93,8 +107,7 @@ impl ScopeArena {
         fc_ptr: ComponentPtr,
         vcomp: Box<dyn AnyProps>,
         parent_scope: Option<ScopeId>,
-        container: ElementId,
-        subtree: u32,
+        container: GlobalNodeId,
     ) -> ScopeId {
         // Increment the ScopeId system. ScopeIDs are never reused
         let new_scope_id = ScopeId(self.scope_gen.get());
@@ -124,7 +137,6 @@ impl ScopeArena {
             scope.height = height;
             scope.fnptr = fc_ptr;
             scope.props.get_mut().replace(vcomp);
-            scope.subtree.set(subtree);
             scope.frames[0].reset();
             scope.frames[1].reset();
             scope.shared_contexts.get_mut().clear();
@@ -155,10 +167,6 @@ impl ScopeArena {
                     props: RefCell::new(Some(vcomp)),
                     frames: [BumpFrame::new(node_capacity), BumpFrame::new(node_capacity)],
 
-                    // todo: subtrees
-                    subtree: Cell::new(0),
-                    is_subtree_root: Cell::default(),
-
                     generation: 0.into(),
 
                     tasks: self.tasks.clone(),
@@ -172,6 +180,8 @@ impl ScopeArena {
                     hook_arena: Bump::new(),
                     hook_vals: RefCell::new(Vec::with_capacity(hook_capacity)),
                     hook_idx: Cell::default(),
+
+                    templates: self.templates.clone(),
                 }),
             );
         }
@@ -314,44 +324,140 @@ impl ScopeArena {
         scope.cycle_frame();
     }
 
-    pub fn call_listener_with_bubbling(&self, event: &UserEvent, element: ElementId) {
+    pub fn call_listener_with_bubbling(&self, event: &UserEvent, element: GlobalNodeId) {
         let nodes = self.nodes.borrow();
         let mut cur_el = Some(element);
 
         let state = Rc::new(BubbleState::new());
 
         while let Some(id) = cur_el.take() {
-            if let Some(el) = nodes.get(id.0) {
-                let real_el = unsafe { &**el };
-
-                if let VNode::Element(real_el) = real_el {
-                    for listener in real_el.listeners.borrow().iter() {
-                        if listener.event == event.name {
-                            if state.canceled.get() {
-                                // stop bubbling if canceled
-                                return;
+            if state.canceled.get() {
+                // stop bubbling if canceled
+                return;
+            }
+            match id {
+                GlobalNodeId::TemplateId {
+                    template_ref_id,
+                    template_node_id,
+                } => {
+                    log::trace!(
+                        "looking for listener in {:?} in node {:?}",
+                        template_ref_id,
+                        template_node_id
+                    );
+                    if let Some(template) = nodes.get(template_ref_id.0) {
+                        let template = unsafe { &**template };
+                        if let VNode::TemplateRef(template_ref) = template {
+                            let templates = self.templates.borrow();
+                            let template = templates.get(&template_ref.template_id).unwrap();
+                            cur_el = template.borrow().with_node(
+                                template_node_id,
+                                bubble_template,
+                                bubble_template,
+                                (
+                                    &nodes,
+                                    &template_ref.dynamic_context,
+                                    &event,
+                                    &state,
+                                    template_ref_id,
+                                ),
+                            );
+                        }
+                    }
+                }
+                GlobalNodeId::VNodeId(id) => {
+                    if let Some(el) = nodes.get(id.0) {
+                        let real_el = unsafe { &**el };
+                        log::trace!("looking for listener on {:?}", real_el);
+
+                        if let VNode::Element(real_el) = real_el {
+                            for listener in real_el.listeners.iter() {
+                                if listener.event == event.name {
+                                    log::trace!("calling listener {:?}", listener.event);
+
+                                    let mut cb = listener.callback.borrow_mut();
+                                    if let Some(cb) = cb.as_mut() {
+                                        // todo: arcs are pretty heavy to clone
+                                        // we really want to convert arc to rc
+                                        // unfortunately, the SchedulerMsg must be send/sync to be sent across threads
+                                        // we could convert arc to rc internally or something
+                                        (cb)(AnyEvent {
+                                            bubble_state: state.clone(),
+                                            data: event.data.clone(),
+                                        });
+                                    }
+                                    break;
+                                }
                             }
 
-                            let mut cb = listener.callback.borrow_mut();
-                            if let Some(cb) = cb.as_mut() {
-                                // todo: arcs are pretty heavy to clone
-                                // we really want to convert arc to rc
-                                // unfortunately, the SchedulerMsg must be send/sync to be sent across threads
-                                // we could convert arc to rc internally or something
-                                (cb)(AnyEvent {
-                                    bubble_state: state.clone(),
-                                    data: event.data.clone(),
-                                });
-                            }
+                            cur_el = real_el.parent.get();
+                        }
+                    }
+                }
+            }
+            if !event.bubbles {
+                return;
+            }
+        }
 
-                            if !event.bubbles {
-                                return;
-                            }
+        fn bubble_template<'b, Attributes, V, Children, Listeners, TextSegments, Text>(
+            node: &TemplateNode<Attributes, V, Children, Listeners, TextSegments, Text>,
+            ctx: (
+                &Ref<Slab<*const VNode>>,
+                &TemplateContext<'b>,
+                &UserEvent,
+                &Rc<BubbleState>,
+                ElementId,
+            ),
+        ) -> Option<GlobalNodeId>
+        where
+            Attributes: AsRef<[TemplateAttribute<V>]>,
+            V: TemplateValue,
+            Children: AsRef<[TemplateNodeId]>,
+            Listeners: AsRef<[usize]>,
+            TextSegments: AsRef<[TextTemplateSegment<Text>]>,
+            Text: AsRef<str>,
+        {
+            let (vnodes, dynamic_context, event, state, template_ref_id) = ctx;
+            if let TemplateNodeType::Element(el) = &node.node_type {
+                let TemplateElement { listeners, .. } = el;
+                for listener_idx in listeners.as_ref() {
+                    let listener = dynamic_context.resolve_listener(*listener_idx);
+                    if listener.event == event.name {
+                        log::trace!("calling listener {:?}", listener.event);
+
+                        let mut cb = listener.callback.borrow_mut();
+                        if let Some(cb) = cb.as_mut() {
+                            // todo: arcs are pretty heavy to clone
+                            // we really want to convert arc to rc
+                            // unfortunately, the SchedulerMsg must be send/sync to be sent across threads
+                            // we could convert arc to rc internally or something
+                            (cb)(AnyEvent {
+                                bubble_state: state.clone(),
+                                data: event.data.clone(),
+                            });
                         }
+                        break;
                     }
+                }
 
-                    cur_el = real_el.parent.get();
+                if let Some(id) = el.parent {
+                    Some(GlobalNodeId::TemplateId {
+                        template_ref_id,
+                        template_node_id: id,
+                    })
+                } else {
+                    vnodes.get(template_ref_id.0).and_then(|el| {
+                        let real_el = unsafe { &**el };
+                        if let VNode::Element(real_el) = real_el {
+                            real_el.parent.get()
+                        } else {
+                            None
+                        }
+                    })
                 }
+            } else {
+                None
             }
         }
     }
@@ -463,14 +569,10 @@ pub struct TaskId {
 /// use case they might have.
 pub struct ScopeState {
     pub(crate) parent_scope: Option<*mut ScopeState>,
-    pub(crate) container: ElementId,
+    pub(crate) container: GlobalNodeId,
     pub(crate) our_arena_idx: ScopeId,
     pub(crate) height: u32,
     pub(crate) fnptr: ComponentPtr,
-
-    // todo: subtrees
-    pub(crate) is_subtree_root: Cell<bool>,
-    pub(crate) subtree: Cell<u32>,
     pub(crate) props: RefCell<Option<Box<dyn AnyProps>>>,
 
     // nodes, items
@@ -486,6 +588,9 @@ pub struct ScopeState {
     // shared state -> todo: move this out of scopestate
     pub(crate) shared_contexts: RefCell<HashMap<TypeId, Box<dyn Any>>>,
     pub(crate) tasks: Rc<TaskQueue>,
+
+    // templates
+    pub(crate) templates: Rc<RefCell<FxHashMap<TemplateId, Rc<RefCell<Template>>>>>,
 }
 
 pub struct SelfReferentialItems<'a> {
@@ -495,52 +600,6 @@ pub struct SelfReferentialItems<'a> {
 
 // Public methods exposed to libraries and components
 impl ScopeState {
-    /// Get the subtree ID that this scope belongs to.
-    ///
-    /// Each component has its own subtree ID - the root subtree has an ID of 0. This ID is used by the renderer to route
-    /// the mutations to the correct window/portal/subtree.
-    ///
-    ///
-    /// # Example
-    ///
-    /// ```rust, ignore
-    /// let mut dom = VirtualDom::new(|cx| cx.render(rsx!{ div {} }));
-    /// dom.rebuild();
-    ///
-    /// let base = dom.base_scope();
-    ///
-    /// assert_eq!(base.subtree(), 0);
-    /// ```
-    ///
-    /// todo: enable
-    pub(crate) fn _subtree(&self) -> u32 {
-        self.subtree.get()
-    }
-
-    /// Create a new subtree with this scope as the root of the subtree.
-    ///
-    /// Each component has its own subtree ID - the root subtree has an ID of 0. This ID is used by the renderer to route
-    /// the mutations to the correct window/portal/subtree.
-    ///
-    /// This method
-    ///
-    /// # Example
-    ///
-    /// ```rust, ignore
-    /// fn App(cx: Scope) -> Element {
-    ///     render!(div { "Subtree {id}"})
-    /// };
-    /// ```
-    ///
-    /// todo: enable subtree
-    pub(crate) fn _create_subtree(&self) -> Option<u32> {
-        if self.is_subtree_root.get() {
-            None
-        } else {
-            todo!()
-        }
-    }
-
     /// Get the height of this Scope - IE the number of scopes above it.
     ///
     /// A Scope with a height of `0` is the root scope - there are no other scopes above it.
@@ -903,8 +962,6 @@ impl ScopeState {
         self.hook_idx.set(0);
         self.parent_scope = None;
         self.generation.set(0);
-        self.is_subtree_root.set(false);
-        self.subtree.set(0);
 
         // next: shared context data
         self.shared_contexts.get_mut().clear();

+ 1051 - 0
packages/core/src/template.rs

@@ -0,0 +1,1051 @@
+//! Templates are used to skip diffing on any static parts of the rsx.
+//! TemplateNodes are different from VNodes in that they can contain partial dynamic and static content in the same node.
+//! For example:
+//! ```
+//! rsx! {
+//!     div {
+//!         color: "{color}",
+//!         "Hello, world",
+//!         "{dynamic_text_1}",
+//!         "{dynamic_text_2}",
+//!         dynamic_iterator
+//!     }
+//! }
+//! ```
+//! The above will turn into a template that contains information on how to build div { "Hello, world" } and then every refrence to the template will hydrate with the value of dynamic_text_1, dynamic_text_2, dynamic_iterator, and the color property.
+//! The rsx macro will both generate the template and the `DynamicNodeMapping` struct that contains the information on what parts of the template depend on each value of the dynamic context.
+//! In templates with many dynamic parts, this allows the diffing algorithm to skip traversing the template to find what part to hydrate.
+//! Each dynamic part will contain a index into the dynamic context to determine what value to use. The indexes are origionally ordered by traversing the tree depth first from the root.
+//! The indexes for the above would be as follows:
+//! ```
+//! rsx! {
+//!     div {
+//!         color: "{color}", // attribute index 0
+//!         "Hello, world",
+//!         "{dynamic_text_1}", // text index 0
+//!         "{dynamic_text_2}", // text index 1
+//!         dynamic_iterator // node index 0
+//!     }
+//! }
+//! ```
+//! Including these indexes allows hot reloading to move the dynamic parts of the template around.
+//! The templates generated by rsx are stored as 'static refrences, but you can change the template at runtime to allow hot reloading.
+//! The template could be replaced with a new one at runtime:
+//! ```
+//! rsx! {
+//!     div {
+//!         "Hello, world",
+//!         dynamic_iterator // node index 0
+//!         h1 {
+//!             background_color: "{color}" // attribute index 0
+//!             "{dynamic_text_2}", // text index 1
+//!         }
+//!         h1 {
+//!            color: "{color}", // attribute index 0
+//!            "{dynamic_text_1}", // text index 0
+//!         }
+//!     }
+//! }
+//! ```
+//! Notice how the indecies are no longer in depth first traversal order, and indecies are no longer unique. Attributes and dynamic parts of the text can be duplicated, but dynamic vnodes and componets cannot.
+//! To minimize the cost of allowing hot reloading on applications that do not use it there are &'static and owned versions of template nodes, and dynamic node mapping.
+//!
+//! Notes:
+//! 1) Why does the template need to exist outside of the virtual dom?
+//! The main use of the template is skipping diffing on static parts of the dom, but it is also used to make renderes more efficient. Renderers can create a template once and the clone it into place.
+//! When the renderers clone the template we could those new nodes as normal vnodes, but that would interfere with the passive memory management of the nodes. This would mean that static nodes memory must be managed by the virtual dom even though those static nodes do not exist in the virtual dom.
+//! 2) The template allow diffing to scale with reactivity.
+//! With a virtual dom the diffing cost scales with the number of nodes in the dom. With templates the cost scales with the number of dynamic parts of the dom. The dynamic template context links any parts of the template that can change which allows the diffing algorithm to skip traversing the template and find what part to hydrate in constant time.
+
+/// The maxiumum integer in JS
+pub const JS_MAX_INT: u64 = 9007199254740991;
+
+use fxhash::FxHashMap;
+use std::{cell::Cell, hash::Hash, marker::PhantomData, ops::Index};
+
+use bumpalo::Bump;
+
+use crate::{
+    diff::DiffState, dynamic_template_context::TemplateContext, innerlude::GlobalNodeId,
+    nodes::AttributeDiscription, Attribute, AttributeValue, ElementId, Mutations,
+    OwnedAttributeValue, StaticDynamicNodeMapping,
+};
+
+/// The location of a charicter. Used to track the location of rsx calls for hot reloading.
+#[derive(Debug, Clone, PartialEq, Eq)]
+#[cfg_attr(
+    all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
+    derive(serde::Serialize)
+)]
+pub struct StaticCodeLocation {
+    /// the path to the crate that contains the location
+    pub crate_path: &'static str,
+    /// the path within the crate to the file that contains the location
+    pub file_path: &'static str,
+    /// the line number of the location
+    pub line: u32,
+    /// the column number of the location
+    pub column: u32,
+}
+
+/// The location of a charicter. Used to track the location of rsx calls for hot reloading.
+#[derive(Debug, Clone, PartialEq, Eq)]
+#[cfg(any(feature = "hot-reload", debug_assertions))]
+#[cfg_attr(
+    all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
+    derive(serde::Serialize, serde::Deserialize)
+)]
+pub struct OwnedCodeLocation {
+    /// the path to the crate that contains the location
+    pub crate_path: String,
+    /// the path within the crate to the file that contains the location
+    pub file_path: String,
+    /// the line number of the location
+    pub line: u32,
+    /// the column number of the location
+    pub column: u32,
+}
+
+/// The location of a charicter. Used to track the location of rsx calls for hot reloading.
+#[derive(Clone, Eq, Debug)]
+#[cfg_attr(
+    all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
+    derive(serde::Serialize)
+)]
+#[cfg_attr(
+    all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
+    serde(untagged)
+)]
+pub enum CodeLocation {
+    /// A loctation that is created at compile time.
+    Static(&'static StaticCodeLocation),
+    #[cfg(any(feature = "hot-reload", debug_assertions))]
+    /// A loctation that is created at runtime.
+    Dynamic(Box<OwnedCodeLocation>),
+}
+
+#[cfg(all(feature = "serialize", any(feature = "hot-reload", debug_assertions)))]
+impl<'de> serde::Deserialize<'de> for CodeLocation {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: serde::Deserializer<'de>,
+    {
+        Ok(Self::Dynamic(Box::new(OwnedCodeLocation::deserialize(
+            deserializer,
+        )?)))
+    }
+}
+
+impl Hash for CodeLocation {
+    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+        match self {
+            CodeLocation::Static(loc) => {
+                loc.crate_path.hash(state);
+                loc.file_path.hash(state);
+                state.write_u32(loc.line);
+                state.write_u32(loc.column);
+            }
+            #[cfg(any(feature = "hot-reload", debug_assertions))]
+            CodeLocation::Dynamic(loc) => {
+                let (crate_path, file_path): (&str, &str) = (&loc.crate_path, &loc.file_path);
+                crate_path.hash(state);
+                file_path.hash(state);
+                state.write_u32(loc.line);
+                state.write_u32(loc.column);
+            }
+        }
+    }
+}
+
+impl PartialEq for CodeLocation {
+    fn eq(&self, other: &Self) -> bool {
+        match (self, other) {
+            (Self::Static(l), Self::Static(r)) => l == r,
+            #[cfg(any(feature = "hot-reload", debug_assertions))]
+            (Self::Dynamic(l), Self::Dynamic(r)) => l == r,
+            #[cfg(any(feature = "hot-reload", debug_assertions))]
+            (Self::Static(l), Self::Dynamic(r)) => **r == **l,
+            #[cfg(any(feature = "hot-reload", debug_assertions))]
+            (Self::Dynamic(l), Self::Static(r)) => **l == **r,
+        }
+    }
+}
+
+#[cfg(any(feature = "hot-reload", debug_assertions))]
+impl PartialEq<StaticCodeLocation> for OwnedCodeLocation {
+    fn eq(&self, other: &StaticCodeLocation) -> bool {
+        self.crate_path == other.crate_path
+            && self.file_path == other.file_path
+            && self.line == other.line
+            && self.column == other.column
+    }
+}
+
+impl CodeLocation {
+    /// Get the line number of the location.
+    pub fn line(&self) -> u32 {
+        match self {
+            CodeLocation::Static(loc) => loc.line,
+            #[cfg(any(feature = "hot-reload", debug_assertions))]
+            CodeLocation::Dynamic(loc) => loc.line,
+        }
+    }
+
+    /// Get the column number of the location.
+    pub fn column(&self) -> u32 {
+        match self {
+            CodeLocation::Static(loc) => loc.column,
+            #[cfg(any(feature = "hot-reload", debug_assertions))]
+            CodeLocation::Dynamic(loc) => loc.column,
+        }
+    }
+
+    /// Get the path within the crate to the location.
+    pub fn file_path(&self) -> &str {
+        match self {
+            CodeLocation::Static(loc) => loc.file_path,
+            #[cfg(any(feature = "hot-reload", debug_assertions))]
+            CodeLocation::Dynamic(loc) => loc.file_path.as_str(),
+        }
+    }
+
+    /// Get the path of the crate to the location.
+    pub fn crate_path(&self) -> &str {
+        match self {
+            CodeLocation::Static(loc) => loc.crate_path,
+            #[cfg(any(feature = "hot-reload", debug_assertions))]
+            CodeLocation::Dynamic(loc) => loc.crate_path.as_str(),
+        }
+    }
+
+    #[cfg(any(feature = "hot-reload", debug_assertions))]
+    /// Create an owned code location from a code location.
+    pub fn to_owned(&self) -> OwnedCodeLocation {
+        match self {
+            CodeLocation::Static(loc) => OwnedCodeLocation {
+                crate_path: loc.crate_path.to_owned(),
+                file_path: loc.file_path.to_owned(),
+                line: loc.line,
+                column: loc.column,
+            },
+            #[cfg(any(feature = "hot-reload", debug_assertions))]
+            CodeLocation::Dynamic(loc) => *loc.clone(),
+        }
+    }
+}
+
+/// get the code location of the code that called this function
+#[macro_export]
+macro_rules! get_line_num {
+    () => {{
+        const LOC: CodeLocation = CodeLocation::Static(&StaticCodeLocation {
+            crate_path: env!("CARGO_MANIFEST_DIR"),
+            file_path: file!(),
+            line: line!(),
+            column: column!(),
+        });
+        LOC
+    }};
+}
+
+/// An Template's unique identifier within the vdom.
+///
+/// `TemplateId` is a refrence to the location in the code the template was created.
+#[derive(Clone, PartialEq, Eq, Hash, Debug)]
+#[cfg_attr(
+    all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
+    derive(serde::Serialize, serde::Deserialize)
+)]
+pub struct TemplateId(pub CodeLocation);
+
+/// An Template's unique identifier within the renderer.
+///
+/// `RendererTemplateId` is a unique id of the template sent to the renderer. It is unique across time.
+#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
+pub struct RendererTemplateId(pub usize);
+
+impl Into<u64> for RendererTemplateId {
+    fn into(self) -> u64 {
+        self.0 as u64
+    }
+}
+
+/// A TemplateNode's unique identifier.
+///
+/// `TemplateNodeId` is a `usize` that is only unique across the template that contains it, it is not unique across multaple instances of that template.
+#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
+#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
+#[cfg_attr(feature = "serialize", serde(transparent))]
+pub struct TemplateNodeId(pub usize);
+
+impl Into<u64> for TemplateNodeId {
+    fn into(self) -> u64 {
+        JS_MAX_INT / 2 + self.0 as u64
+    }
+}
+
+/// A refrence to a template along with any context needed to hydrate it
+pub struct VTemplateRef<'a> {
+    pub id: Cell<Option<ElementId>>,
+    pub template_id: TemplateId,
+    pub dynamic_context: TemplateContext<'a>,
+}
+
+impl<'a> VTemplateRef<'a> {
+    // update the template with content from the dynamic context
+    pub(crate) fn hydrate<'b>(&self, template: &'b Template, diff_state: &mut DiffState<'a>) {
+        fn hydrate_inner<'b, Nodes, Attributes, V, Children, Listeners, TextSegments, Text>(
+            nodes: &Nodes,
+            ctx: (&mut DiffState<'b>, &VTemplateRef<'b>, &Template),
+        ) where
+            Nodes: AsRef<[TemplateNode<Attributes, V, Children, Listeners, TextSegments, Text>]>,
+            Attributes: AsRef<[TemplateAttribute<V>]>,
+            V: TemplateValue,
+            Children: AsRef<[TemplateNodeId]>,
+            Listeners: AsRef<[usize]>,
+            TextSegments: AsRef<[TextTemplateSegment<Text>]>,
+            Text: AsRef<str>,
+        {
+            let (diff_state, template_ref, template) = ctx;
+            for id in template.all_dynamic() {
+                let dynamic_node = &nodes.as_ref()[id.0];
+                dynamic_node.hydrate(diff_state, template_ref);
+            }
+        }
+
+        template.with_nodes(hydrate_inner, hydrate_inner, (diff_state, self, template));
+    }
+}
+
+/// A template that is created at compile time
+#[derive(Debug, PartialEq)]
+pub struct StaticTemplate {
+    /// The nodes in the template
+    pub nodes: StaticTemplateNodes,
+    /// The ids of the root nodes in the template
+    pub root_nodes: StaticRootNodes,
+    /// Any nodes that contain dynamic components. This is stored in the tmeplate to avoid traversing the tree every time a template is refrenced.
+    pub dynamic_mapping: StaticDynamicNodeMapping,
+}
+
+/// A template that is created at runtime
+#[derive(Debug, Clone, PartialEq)]
+#[cfg_attr(
+    all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
+    derive(serde::Serialize, serde::Deserialize)
+)]
+#[cfg(any(feature = "hot-reload", debug_assertions))]
+pub struct OwnedTemplate {
+    /// The nodes in the template
+    pub nodes: OwnedTemplateNodes,
+    /// The ids of the root nodes in the template
+    pub root_nodes: OwnedRootNodes,
+    /// Any nodes that contain dynamic components. This is stored in the tmeplate to avoid traversing the tree every time a template is refrenced.
+    pub dynamic_mapping: crate::OwnedDynamicNodeMapping,
+}
+
+/// A template used to skip diffing on some static parts of the rsx
+#[derive(Debug, Clone, PartialEq)]
+pub enum Template {
+    /// A template that is createded at compile time
+    Static(&'static StaticTemplate),
+    #[cfg(any(feature = "hot-reload", debug_assertions))]
+    /// A template that is created at runtime
+    Owned(OwnedTemplate),
+}
+
+impl Template {
+    pub(crate) fn create<'b>(
+        &self,
+        mutations: &mut Mutations<'b>,
+        bump: &'b Bump,
+        id: RendererTemplateId,
+    ) {
+        mutations.create_templete(id);
+        let empty = match self {
+            Template::Static(s) => s.nodes.is_empty(),
+            #[cfg(any(feature = "hot-reload", debug_assertions))]
+            Template::Owned(o) => o.nodes.is_empty(),
+        };
+        let mut len = 0;
+        if !empty {
+            let roots = match self {
+                Template::Static(s) => s.root_nodes,
+                #[cfg(any(feature = "hot-reload", debug_assertions))]
+                Template::Owned(o) => &o.root_nodes,
+            };
+            for root in roots {
+                len += 1;
+                self.create_node(mutations, bump, *root);
+            }
+        }
+        mutations.finish_templete(len as u32);
+    }
+
+    fn create_node<'b>(&self, mutations: &mut Mutations<'b>, bump: &'b Bump, id: TemplateNodeId) {
+        fn crate_node_inner<'b, Attributes, V, Children, Listeners, TextSegments, Text>(
+            node: &TemplateNode<Attributes, V, Children, Listeners, TextSegments, Text>,
+            ctx: (&mut Mutations<'b>, &'b Bump, &Template),
+        ) where
+            Attributes: AsRef<[TemplateAttribute<V>]>,
+            V: TemplateValue,
+            Children: AsRef<[TemplateNodeId]>,
+            Listeners: AsRef<[usize]>,
+            TextSegments: AsRef<[TextTemplateSegment<Text>]>,
+            Text: AsRef<str>,
+        {
+            let (mutations, bump, template) = ctx;
+            let id = node.id;
+            let locally_static = node.locally_static;
+            let fully_static = node.fully_static;
+            match &node.node_type {
+                TemplateNodeType::Element(el) => {
+                    let TemplateElement {
+                        tag,
+                        namespace,
+                        attributes,
+                        children,
+                        ..
+                    } = el;
+                    mutations.create_element_template(
+                        tag,
+                        *namespace,
+                        id,
+                        locally_static,
+                        fully_static,
+                    );
+                    for attr in attributes.as_ref() {
+                        if let TemplateAttributeValue::Static(val) = &attr.value {
+                            let val: AttributeValue<'b> = val.allocate(bump);
+                            let attribute = Attribute {
+                                attribute: attr.attribute,
+                                is_static: true,
+                                value: val,
+                            };
+                            mutations.set_attribute(bump.alloc(attribute), id);
+                        }
+                    }
+                    let mut children_created = 0;
+                    for child in children.as_ref() {
+                        template.create_node(mutations, bump, *child);
+                        children_created += 1;
+                    }
+
+                    mutations.append_children(children_created);
+                }
+                TemplateNodeType::Text(text) => {
+                    let mut text_iter = text.segments.as_ref().into_iter();
+                    if let (Some(TextTemplateSegment::Static(txt)), None) =
+                        (text_iter.next(), text_iter.next())
+                    {
+                        mutations.create_text_node_template(
+                            bump.alloc_str(txt.as_ref()),
+                            id,
+                            locally_static,
+                        );
+                    } else {
+                        mutations.create_text_node_template("", id, locally_static);
+                    }
+                }
+                TemplateNodeType::DynamicNode(_) => {
+                    mutations.create_placeholder_template(id);
+                }
+            }
+        }
+        self.with_node(
+            id,
+            crate_node_inner,
+            crate_node_inner,
+            (mutations, bump, self),
+        );
+    }
+
+    #[cfg(any(feature = "hot-reload", debug_assertions))]
+    pub(crate) fn with_node<F1, F2, Ctx, R>(
+        &self,
+        id: TemplateNodeId,
+        mut f1: F1,
+        mut f2: F2,
+        ctx: Ctx,
+    ) -> R
+    where
+        F1: FnMut(&StaticTemplateNode, Ctx) -> R,
+        F2: FnMut(&OwnedTemplateNode, Ctx) -> R,
+    {
+        match self {
+            Template::Static(s) => f1(&s.nodes[id.0], ctx),
+            Template::Owned(o) => f2(&o.nodes[id.0], ctx),
+        }
+    }
+
+    #[cfg(not(any(feature = "hot-reload", debug_assertions)))]
+    pub(crate) fn with_node<F1, F2, Ctx, R>(
+        &self,
+        id: TemplateNodeId,
+        mut f1: F1,
+        _f2: F2,
+        ctx: Ctx,
+    ) -> R
+    where
+        F1: FnMut(&StaticTemplateNode, Ctx) -> R,
+        F2: FnMut(&StaticTemplateNode, Ctx) -> R,
+    {
+        match self {
+            Template::Static(s) => f1(&s.nodes[id.0], ctx),
+        }
+    }
+
+    #[cfg(any(feature = "hot-reload", debug_assertions))]
+    pub(crate) fn with_nodes<'a, F1, F2, Ctx>(&'a self, mut f1: F1, mut f2: F2, ctx: Ctx)
+    where
+        F1: FnMut(&'a &'static [StaticTemplateNode], Ctx),
+        F2: FnMut(&'a Vec<OwnedTemplateNode>, Ctx),
+    {
+        match self {
+            Template::Static(s) => f1(&s.nodes, ctx),
+            Template::Owned(o) => f2(&o.nodes, ctx),
+        }
+    }
+
+    #[cfg(not(any(feature = "hot-reload", debug_assertions)))]
+    pub(crate) fn with_nodes<'a, F1, F2, Ctx>(&'a self, mut f1: F1, _f2: F2, ctx: Ctx)
+    where
+        F1: FnMut(&'a &'static [StaticTemplateNode], Ctx),
+        F2: FnMut(&'a &'static [StaticTemplateNode], Ctx),
+    {
+        match self {
+            Template::Static(s) => f1(&s.nodes, ctx),
+        }
+    }
+
+    pub(crate) fn all_dynamic<'a>(&'a self) -> Box<dyn Iterator<Item = TemplateNodeId> + 'a> {
+        match self {
+            Template::Static(s) => Box::new(s.dynamic_mapping.all_dynamic()),
+            #[cfg(any(feature = "hot-reload", debug_assertions))]
+            Template::Owned(o) => Box::new(o.dynamic_mapping.all_dynamic()),
+        }
+    }
+
+    pub(crate) fn volatile_attributes<'a>(
+        &'a self,
+    ) -> Box<dyn Iterator<Item = (TemplateNodeId, usize)> + 'a> {
+        match self {
+            Template::Static(s) => Box::new(
+                s.dynamic_mapping
+                    .volatile_attributes
+                    .as_ref()
+                    .iter()
+                    .copied(),
+            ),
+            #[cfg(any(feature = "hot-reload", debug_assertions))]
+            Template::Owned(o) => Box::new(o.dynamic_mapping.volatile_attributes.iter().copied()),
+        }
+    }
+
+    pub(crate) fn get_dynamic_nodes_for_node_index(&self, idx: usize) -> Option<TemplateNodeId> {
+        match self {
+            Template::Static(s) => s.dynamic_mapping.nodes[idx],
+            #[cfg(any(feature = "hot-reload", debug_assertions))]
+            Template::Owned(o) => o.dynamic_mapping.nodes[idx],
+        }
+    }
+
+    pub(crate) fn get_dynamic_nodes_for_text_index<'a>(
+        &'a self,
+        idx: usize,
+    ) -> &'a [TemplateNodeId] {
+        match self {
+            Template::Static(s) => s.dynamic_mapping.text[idx].as_ref(),
+            #[cfg(any(feature = "hot-reload", debug_assertions))]
+            Template::Owned(o) => o.dynamic_mapping.text[idx].as_ref(),
+        }
+    }
+
+    pub(crate) fn get_dynamic_nodes_for_attribute_index<'a>(
+        &'a self,
+        idx: usize,
+    ) -> &'a [(TemplateNodeId, usize)] {
+        match self {
+            Template::Static(s) => s.dynamic_mapping.attributes[idx].as_ref(),
+            #[cfg(any(feature = "hot-reload", debug_assertions))]
+            Template::Owned(o) => o.dynamic_mapping.attributes[idx].as_ref(),
+        }
+    }
+}
+
+/// A array of stack allocated Template nodes
+pub type StaticTemplateNodes = &'static [StaticTemplateNode];
+#[cfg(any(feature = "hot-reload", debug_assertions))]
+/// A vec of heep allocated Template nodes
+pub type OwnedTemplateNodes = Vec<OwnedTemplateNode>;
+
+/// A stack allocated Template node
+pub type StaticTemplateNode = TemplateNode<
+    &'static [TemplateAttribute<StaticAttributeValue>],
+    StaticAttributeValue,
+    &'static [TemplateNodeId],
+    &'static [usize],
+    &'static [TextTemplateSegment<&'static str>],
+    &'static str,
+>;
+
+#[cfg(any(feature = "hot-reload", debug_assertions))]
+/// A heap allocated Template node
+pub type OwnedTemplateNode = TemplateNode<
+    Vec<TemplateAttribute<OwnedAttributeValue>>,
+    OwnedAttributeValue,
+    Vec<TemplateNodeId>,
+    Vec<usize>,
+    Vec<TextTemplateSegment<String>>,
+    String,
+>;
+
+/// A stack allocated list of root Template nodes
+pub type StaticRootNodes = &'static [TemplateNodeId];
+
+#[cfg(any(feature = "hot-reload", debug_assertions))]
+/// A heap allocated list of root Template nodes
+pub type OwnedRootNodes = Vec<TemplateNodeId>;
+
+/// Templates can only contain a limited subset of VNodes and keys are not needed, as diffing will be skipped.
+/// Dynamic parts of the Template are inserted into the VNode using the `TemplateContext` by traversing the tree in order and filling in dynamic parts
+/// This template node is generic over the storage of the nodes to allow for owned and &'static versions.
+#[derive(Debug, Clone, Copy, PartialEq)]
+#[cfg_attr(
+    all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
+    derive(serde::Serialize, serde::Deserialize)
+)]
+pub struct TemplateNode<Attributes, V, Children, Listeners, TextSegments, Text>
+where
+    Attributes: AsRef<[TemplateAttribute<V>]>,
+    V: TemplateValue,
+    Children: AsRef<[TemplateNodeId]>,
+    Listeners: AsRef<[usize]>,
+    TextSegments: AsRef<[TextTemplateSegment<Text>]>,
+    Text: AsRef<str>,
+{
+    /// The ID of the [`TemplateNode`]. Note that this is not an elenemt id, and should be allocated seperately from VNodes on the frontend.
+    pub id: TemplateNodeId,
+    /// If the id of the node must be kept in the refrences
+    pub locally_static: bool,
+    /// If any children of this node must be kept in the references
+    pub fully_static: bool,
+    /// The type of the [`TemplateNode`].
+    pub node_type: TemplateNodeType<Attributes, V, Children, Listeners, TextSegments, Text>,
+}
+
+impl<Attributes, V, Children, Listeners, TextSegments, Text>
+    TemplateNode<Attributes, V, Children, Listeners, TextSegments, Text>
+where
+    Attributes: AsRef<[TemplateAttribute<V>]>,
+    V: TemplateValue,
+    Children: AsRef<[TemplateNodeId]>,
+    Listeners: AsRef<[usize]>,
+    TextSegments: AsRef<[TextTemplateSegment<Text>]>,
+    Text: AsRef<str>,
+{
+    fn hydrate<'b>(&self, diff_state: &mut DiffState<'b>, template_ref: &VTemplateRef<'b>) {
+        let real_id = template_ref.id.get().unwrap();
+
+        diff_state.element_stack.push(GlobalNodeId::TemplateId {
+            template_ref_id: real_id,
+            template_node_id: self.id,
+        });
+        diff_state.mutations.enter_template_ref(real_id);
+        match &self.node_type {
+            TemplateNodeType::Element(el) => {
+                let TemplateElement {
+                    attributes,
+                    listeners,
+                    ..
+                } = el;
+                for attr in attributes.as_ref() {
+                    if let TemplateAttributeValue::Dynamic(idx) = attr.value {
+                        let attribute = Attribute {
+                            attribute: attr.attribute,
+                            value: template_ref
+                                .dynamic_context
+                                .resolve_attribute(idx)
+                                .to_owned(),
+                            is_static: false,
+                        };
+                        let scope_bump = diff_state.current_scope_bump();
+                        diff_state
+                            .mutations
+                            .set_attribute(scope_bump.alloc(attribute), self.id);
+                    }
+                }
+                for listener_idx in listeners.as_ref() {
+                    let listener = template_ref.dynamic_context.resolve_listener(*listener_idx);
+                    let global_id = GlobalNodeId::TemplateId {
+                        template_ref_id: real_id,
+                        template_node_id: self.id,
+                    };
+                    listener.mounted_node.set(Some(global_id));
+                    diff_state
+                        .mutations
+                        .new_event_listener(listener, diff_state.current_scope());
+                }
+            }
+            TemplateNodeType::Text(text) => {
+                let new_text = template_ref
+                    .dynamic_context
+                    .resolve_text(&text.segments.as_ref());
+                let scope_bump = diff_state.current_scope_bump();
+                diff_state
+                    .mutations
+                    .set_text(scope_bump.alloc(new_text), self.id)
+            }
+            TemplateNodeType::DynamicNode(idx) => {
+                drop(self);
+                // this will only be triggered for root elements
+                let created =
+                    diff_state.create_node(&template_ref.dynamic_context.resolve_node(*idx));
+                diff_state.mutations.replace_with(self.id, created as u32);
+            }
+        }
+        diff_state.mutations.exit_template_ref();
+        diff_state.element_stack.pop();
+    }
+}
+
+/// A template for an attribute
+#[derive(Debug, Clone, PartialEq)]
+#[cfg_attr(
+    all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
+    derive(serde::Serialize, serde::Deserialize)
+)]
+pub struct TemplateAttribute<V: TemplateValue> {
+    /// The discription of the attribute
+    pub attribute: AttributeDiscription,
+    /// The value of the attribute
+    pub value: TemplateAttributeValue<V>,
+}
+
+/// A template attribute value that is either dynamic or static
+#[derive(Debug, Clone, PartialEq)]
+#[cfg_attr(
+    all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
+    derive(serde::Serialize, serde::Deserialize)
+)]
+pub enum TemplateAttributeValue<V: TemplateValue> {
+    /// A static attribute
+    Static(V),
+    /// A dynamic attribute
+    Dynamic(usize),
+}
+
+/// The value for an attribute in a template
+pub trait TemplateValue {
+    /// Allocates the attribute in a bump allocator
+    fn allocate<'b>(&self, bump: &'b Bump) -> AttributeValue<'b>;
+}
+
+impl TemplateValue for StaticAttributeValue {
+    fn allocate<'b>(&self, bump: &'b Bump) -> AttributeValue<'b> {
+        match self.clone() {
+            StaticAttributeValue::Text(txt) => AttributeValue::Text(bump.alloc_str(&txt)),
+            StaticAttributeValue::Bytes(bytes) => {
+                AttributeValue::Bytes(bump.alloc_slice_copy(&bytes))
+            }
+            StaticAttributeValue::Float32(f) => AttributeValue::Float32(f),
+            StaticAttributeValue::Float64(f) => AttributeValue::Float64(f),
+            StaticAttributeValue::Int32(i) => AttributeValue::Int32(i),
+            StaticAttributeValue::Int64(i) => AttributeValue::Int64(i),
+            StaticAttributeValue::Uint32(u) => AttributeValue::Uint32(u),
+            StaticAttributeValue::Uint64(u) => AttributeValue::Uint64(u),
+            StaticAttributeValue::Bool(b) => AttributeValue::Bool(b),
+            StaticAttributeValue::Vec3Float(f1, f2, f3) => AttributeValue::Vec3Float(f1, f2, f3),
+            StaticAttributeValue::Vec3Int(i1, i2, i3) => AttributeValue::Vec3Int(i1, i2, i3),
+            StaticAttributeValue::Vec3Uint(u1, u2, u3) => AttributeValue::Vec3Uint(u1, u2, u3),
+            StaticAttributeValue::Vec4Float(f1, f2, f3, f4) => {
+                AttributeValue::Vec4Float(f1, f2, f3, f4)
+            }
+            StaticAttributeValue::Vec4Int(i1, i2, i3, i4) => {
+                AttributeValue::Vec4Int(i1, i2, i3, i4)
+            }
+            StaticAttributeValue::Vec4Uint(u1, u2, u3, u4) => {
+                AttributeValue::Vec4Uint(u1, u2, u3, u4)
+            }
+        }
+    }
+}
+
+#[cfg(any(feature = "hot-reload", debug_assertions))]
+impl TemplateValue for OwnedAttributeValue {
+    fn allocate<'b>(&self, bump: &'b Bump) -> AttributeValue<'b> {
+        match self.clone() {
+            OwnedAttributeValue::Text(txt) => AttributeValue::Text(bump.alloc(txt)),
+            OwnedAttributeValue::Bytes(bytes) => AttributeValue::Bytes(bump.alloc(bytes)),
+            OwnedAttributeValue::Float32(f) => AttributeValue::Float32(f),
+            OwnedAttributeValue::Float64(f) => AttributeValue::Float64(f),
+            OwnedAttributeValue::Int32(i) => AttributeValue::Int32(i),
+            OwnedAttributeValue::Int64(i) => AttributeValue::Int64(i),
+            OwnedAttributeValue::Uint32(u) => AttributeValue::Uint32(u),
+            OwnedAttributeValue::Uint64(u) => AttributeValue::Uint64(u),
+            OwnedAttributeValue::Bool(b) => AttributeValue::Bool(b),
+            OwnedAttributeValue::Vec3Float(f1, f2, f3) => AttributeValue::Vec3Float(f1, f2, f3),
+            OwnedAttributeValue::Vec3Int(i1, i2, i3) => AttributeValue::Vec3Int(i1, i2, i3),
+            OwnedAttributeValue::Vec3Uint(u1, u2, u3) => AttributeValue::Vec3Uint(u1, u2, u3),
+            OwnedAttributeValue::Vec4Float(f1, f2, f3, f4) => {
+                AttributeValue::Vec4Float(f1, f2, f3, f4)
+            }
+            OwnedAttributeValue::Vec4Int(i1, i2, i3, i4) => AttributeValue::Vec4Int(i1, i2, i3, i4),
+            OwnedAttributeValue::Vec4Uint(u1, u2, u3, u4) => {
+                AttributeValue::Vec4Uint(u1, u2, u3, u4)
+            }
+        }
+    }
+}
+
+/// The kind of node the template is.
+#[derive(Debug, Clone, Copy, PartialEq)]
+#[cfg_attr(
+    all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
+    derive(serde::Serialize, serde::Deserialize)
+)]
+pub enum TemplateNodeType<Attributes, V, Children, Listeners, TextSegments, Text>
+where
+    Attributes: AsRef<[TemplateAttribute<V>]>,
+    Children: AsRef<[TemplateNodeId]>,
+    Listeners: AsRef<[usize]>,
+    V: TemplateValue,
+    TextSegments: AsRef<[TextTemplateSegment<Text>]>,
+    Text: AsRef<str>,
+{
+    /// A element node (e.g. div{}).
+    Element(TemplateElement<Attributes, V, Children, Listeners>),
+    /// A text node (e.g. "Hello World").
+    Text(TextTemplate<TextSegments, Text>),
+    /// A dynamic node (e.g. (0..10).map(|i| cx.render(rsx!{div{}})))
+    /// The index in the dynamic node array this node should be replaced with
+    DynamicNode(usize),
+}
+
+impl<Attributes, V, Children, Listeners, TextSegments, Text>
+    TemplateNodeType<Attributes, V, Children, Listeners, TextSegments, Text>
+where
+    Attributes: AsRef<[TemplateAttribute<V>]>,
+    Children: AsRef<[TemplateNodeId]>,
+    Listeners: AsRef<[usize]>,
+    V: TemplateValue,
+    TextSegments: AsRef<[TextTemplateSegment<Text>]>,
+    Text: AsRef<str>,
+{
+    /// Returns if this node, and its children, are static.
+    pub fn fully_static<Nodes: Index<usize, Output = Self>>(&self, nodes: &Nodes) -> bool {
+        self.locally_static()
+            && match self {
+                TemplateNodeType::Element(e) => e
+                    .children
+                    .as_ref()
+                    .iter()
+                    .all(|c| nodes[c.0].fully_static(nodes)),
+                TemplateNodeType::Text(_) => true,
+                TemplateNodeType::DynamicNode(_) => unreachable!(),
+            }
+    }
+
+    /// Returns if this node is static.
+    pub fn locally_static(&self) -> bool {
+        match self {
+            TemplateNodeType::Element(e) => {
+                e.attributes.as_ref().iter().all(|a| match a.value {
+                    TemplateAttributeValue::Static(_) => true,
+                    TemplateAttributeValue::Dynamic(_) => false,
+                }) && e.listeners.as_ref().is_empty()
+            }
+            TemplateNodeType::Text(t) => t.segments.as_ref().iter().all(|seg| match seg {
+                TextTemplateSegment::Static(_) => true,
+                TextTemplateSegment::Dynamic(_) => false,
+            }),
+            TemplateNodeType::DynamicNode(_) => false,
+        }
+    }
+}
+
+type StaticStr = &'static str;
+
+/// A element template
+#[derive(Debug, Clone, Copy, PartialEq)]
+#[cfg_attr(
+    all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
+    derive(serde::Serialize, serde::Deserialize)
+)]
+pub struct TemplateElement<Attributes, V, Children, Listeners>
+where
+    Attributes: AsRef<[TemplateAttribute<V>]>,
+    Children: AsRef<[TemplateNodeId]>,
+    Listeners: AsRef<[usize]>,
+    V: TemplateValue,
+{
+    /// The tag name of the element
+    #[cfg_attr(
+        all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
+        serde(deserialize_with = "crate::util::deserialize_static_leaky")
+    )]
+    pub tag: StaticStr,
+    /// The namespace of the element
+    #[cfg_attr(
+        all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
+        serde(deserialize_with = "crate::util::deserialize_static_leaky_ns")
+    )]
+    pub namespace: Option<StaticStr>,
+    /// The attributes that modify the element
+    pub attributes: Attributes,
+    /// The ids of the children of the element
+    pub children: Children,
+    /// The ids of the listeners of the element
+    pub listeners: Listeners,
+    /// The parent of the element
+    pub parent: Option<TemplateNodeId>,
+    value: PhantomData<V>,
+}
+
+impl<Attributes, V, Children, Listeners> TemplateElement<Attributes, V, Children, Listeners>
+where
+    Attributes: AsRef<[TemplateAttribute<V>]>,
+    Children: AsRef<[TemplateNodeId]>,
+    Listeners: AsRef<[usize]>,
+    V: TemplateValue,
+{
+    /// create a new element template
+    pub const fn new(
+        tag: &'static str,
+        namespace: Option<&'static str>,
+        attributes: Attributes,
+        children: Children,
+        listeners: Listeners,
+        parent: Option<TemplateNodeId>,
+    ) -> Self {
+        TemplateElement {
+            tag,
+            namespace,
+            attributes,
+            children,
+            listeners,
+            parent,
+            value: PhantomData,
+        }
+    }
+}
+
+/// A template for some text that may contain dynamic segments for example "Hello {name}" contains the static segment "Hello " and the dynamic segment "{name}".
+#[derive(Debug, Clone, Copy, PartialEq)]
+#[cfg_attr(
+    all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
+    derive(serde::Serialize, serde::Deserialize)
+)]
+pub struct TextTemplate<Segments, Text>
+where
+    Segments: AsRef<[TextTemplateSegment<Text>]>,
+    Text: AsRef<str>,
+{
+    /// The segments of the template.
+    pub segments: Segments,
+    text: PhantomData<Text>,
+}
+
+impl<Segments, Text> TextTemplate<Segments, Text>
+where
+    Segments: AsRef<[TextTemplateSegment<Text>]>,
+    Text: AsRef<str>,
+{
+    /// create a new template from the segments it is composed of.
+    pub const fn new(segments: Segments) -> Self {
+        TextTemplate {
+            segments,
+            text: PhantomData,
+        }
+    }
+}
+
+/// A segment of a text template that may be dynamic or static.
+#[derive(Debug, Clone, PartialEq)]
+#[cfg_attr(
+    all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
+    derive(serde::Serialize, serde::Deserialize)
+)]
+pub enum TextTemplateSegment<Text>
+where
+    Text: AsRef<str>,
+{
+    /// A constant text segment
+    Static(Text),
+    /// A dynamic text segment
+    Dynamic(usize),
+}
+
+/// A template value that is created at compile time that is sync.
+#[derive(Debug, Clone, PartialEq)]
+#[allow(missing_docs)]
+pub enum StaticAttributeValue {
+    Text(&'static str),
+    Float32(f32),
+    Float64(f64),
+    Int32(i32),
+    Int64(i64),
+    Uint32(u32),
+    Uint64(u64),
+    Bool(bool),
+
+    Vec3Float(f32, f32, f32),
+    Vec3Int(i32, i32, i32),
+    Vec3Uint(u32, u32, u32),
+
+    Vec4Float(f32, f32, f32, f32),
+    Vec4Int(i32, i32, i32, i32),
+    Vec4Uint(u32, u32, u32, u32),
+
+    Bytes(&'static [u8]),
+}
+
+#[derive(Default)]
+pub(crate) struct TemplateResolver {
+    // maps a id to the rendererid and if that template needs to be re-created
+    pub template_id_mapping: FxHashMap<TemplateId, (RendererTemplateId, bool)>,
+    pub template_count: usize,
+}
+
+impl TemplateResolver {
+    #[cfg(any(feature = "hot-reload", debug_assertions))]
+    pub fn mark_dirty(&mut self, id: &TemplateId) {
+        if let Some((_, dirty)) = self.template_id_mapping.get_mut(id) {
+            println!("marking dirty {:?}", id);
+            *dirty = true;
+        } else {
+            println!("failed {:?}", id);
+        }
+    }
+
+    pub fn is_dirty(&self, id: &TemplateId) -> bool {
+        if let Some((_, true)) = self.template_id_mapping.get(id) {
+            true
+        } else {
+            false
+        }
+    }
+
+    // returns (id, if the id was created)
+    pub fn get_or_create_client_id(
+        &mut self,
+        template_id: &TemplateId,
+    ) -> (RendererTemplateId, bool) {
+        if let Some(id) = self.template_id_mapping.get(template_id) {
+            *id
+        } else {
+            let id = self.template_count;
+            let renderer_id = RendererTemplateId(id);
+            self.template_id_mapping
+                .insert(template_id.clone(), (renderer_id, false));
+            self.template_count += 1;
+            (renderer_id, true)
+        }
+    }
+}
+
+#[cfg(any(feature = "hot-reload", debug_assertions))]
+/// A message telling the virtual dom to set a template
+#[derive(Debug, Clone)]
+#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
+pub struct SetTemplateMsg(pub TemplateId, pub OwnedTemplate);

+ 45 - 1
packages/core/src/util.rs

@@ -34,7 +34,10 @@ impl<'a> Iterator for ElementIdIterator<'a> {
             if let Some((count, node)) = self.stack.last_mut() {
                 match node {
                     // We can only exit our looping when we get "real" nodes
-                    VNode::Element(_) | VNode::Text(_) | VNode::Placeholder(_) => {
+                    VNode::Element(_)
+                    | VNode::Text(_)
+                    | VNode::Placeholder(_)
+                    | VNode::TemplateRef(_) => {
                         // We've recursed INTO an element/text
                         // We need to recurse *out* of it and move forward to the next
                         // println!("Found element! Returning it!");
@@ -81,3 +84,44 @@ impl<'a> Iterator for ElementIdIterator<'a> {
         returned_node
     }
 }
+
+/// This intentionally leaks once per element name to allow more flexability when hot reloding templetes
+#[cfg(all(any(feature = "hot-reload", debug_assertions), feature = "serde"))]
+mod leaky {
+    use std::sync::Mutex;
+
+    use fxhash::FxHashSet;
+    use once_cell::sync::Lazy;
+    static STATIC_CACHE: Lazy<Mutex<FxHashSet<&'static str>>> =
+        Lazy::new(|| Mutex::new(FxHashSet::default()));
+
+    pub fn deserialize_static_leaky<'de, D>(d: D) -> Result<&'static str, D::Error>
+    where
+        D: serde::Deserializer<'de>,
+    {
+        use serde::Deserialize;
+        let s = <&str>::deserialize(d)?;
+        Ok(if let Some(stat) = STATIC_CACHE.lock().unwrap().get(s) {
+            *stat
+        } else {
+            Box::leak(s.into())
+        })
+    }
+
+    pub fn deserialize_static_leaky_ns<'de, D>(d: D) -> Result<Option<&'static str>, D::Error>
+    where
+        D: serde::Deserializer<'de>,
+    {
+        use serde::Deserialize;
+        Ok(<Option<&str>>::deserialize(d)?.map(|s| {
+            if let Some(stat) = STATIC_CACHE.lock().unwrap().get(s) {
+                *stat
+            } else {
+                Box::leak(s.into())
+            }
+        }))
+    }
+}
+
+#[cfg(all(any(feature = "hot-reload", debug_assertions), feature = "serde"))]
+pub use leaky::*;

+ 52 - 8
packages/core/src/virtual_dom.rs

@@ -128,6 +128,10 @@ pub enum SchedulerMsg {
     /// Mark all components as dirty and update them
     DirtyAll,
 
+    #[cfg(any(feature = "hot-reload", debug_assertions))]
+    /// Mark a template as dirty, used for hot reloading
+    SetTemplate(Box<SetTemplateMsg>),
+
     /// New tasks from components that should be polled when the next poll is ready
     NewTask(ScopeId),
 }
@@ -226,8 +230,7 @@ impl VirtualDom {
                 render_fn: root,
             }),
             None,
-            ElementId(0),
-            0,
+            GlobalNodeId::VNodeId(ElementId(0)),
         );
 
         Self {
@@ -398,6 +401,25 @@ impl VirtualDom {
                     self.dirty_scopes.insert(*id);
                 }
             }
+            #[cfg(any(feature = "hot-reload", debug_assertions))]
+            SchedulerMsg::SetTemplate(msg) => {
+                let SetTemplateMsg(id, tmpl) = *msg;
+                if self
+                    .scopes
+                    .templates
+                    .borrow_mut()
+                    .insert(
+                        id.clone(),
+                        std::rc::Rc::new(std::cell::RefCell::new(Template::Owned(tmpl))),
+                    )
+                    .is_some()
+                {
+                    self.scopes.template_resolver.borrow_mut().mark_dirty(&id)
+                }
+
+                // mark any scopes that used the template as dirty
+                self.process_message(SchedulerMsg::DirtyAll);
+            }
         }
     }
 
@@ -449,6 +471,7 @@ impl VirtualDom {
     #[allow(unused)]
     pub fn work_with_deadline(&mut self, mut deadline: impl FnMut() -> bool) -> Vec<Mutations> {
         let mut committed_mutations = vec![];
+        self.scopes.template_bump.reset();
 
         while !self.dirty_scopes.is_empty() {
             let scopes = &self.scopes;
@@ -524,11 +547,13 @@ impl VirtualDom {
     /// ```
     pub fn rebuild(&mut self) -> Mutations {
         let scope_id = ScopeId(0);
-        let mut diff_state = DiffState::new(&self.scopes);
 
+        let mut diff_state = DiffState::new(&self.scopes);
         self.scopes.run_scope(scope_id);
 
-        diff_state.element_stack.push(ElementId(0));
+        diff_state
+            .element_stack
+            .push(GlobalNodeId::VNodeId(ElementId(0)));
         diff_state.scope_stack.push(scope_id);
 
         let node = self.scopes.fin_head(scope_id);
@@ -625,7 +650,9 @@ impl VirtualDom {
     /// ```
     pub fn diff_vnodes<'a>(&'a self, old: &'a VNode<'a>, new: &'a VNode<'a>) -> Mutations<'a> {
         let mut machine = DiffState::new(&self.scopes);
-        machine.element_stack.push(ElementId(0));
+        machine
+            .element_stack
+            .push(GlobalNodeId::VNodeId(ElementId(0)));
         machine.scope_stack.push(ScopeId(0));
         machine.diff_node(old, new);
 
@@ -648,7 +675,9 @@ impl VirtualDom {
     pub fn create_vnodes<'a>(&'a self, nodes: LazyNodes<'a, '_>) -> Mutations<'a> {
         let mut machine = DiffState::new(&self.scopes);
         machine.scope_stack.push(ScopeId(0));
-        machine.element_stack.push(ElementId(0));
+        machine
+            .element_stack
+            .push(GlobalNodeId::VNodeId(ElementId(0)));
         let node = self.render_vnodes(nodes);
         let created = machine.create_node(node);
         machine.mutations.append_children(created as u32);
@@ -677,17 +706,32 @@ impl VirtualDom {
 
         let mut create = DiffState::new(&self.scopes);
         create.scope_stack.push(ScopeId(0));
-        create.element_stack.push(ElementId(0));
+        create
+            .element_stack
+            .push(GlobalNodeId::VNodeId(ElementId(0)));
         let created = create.create_node(old);
         create.mutations.append_children(created as u32);
 
         let mut edit = DiffState::new(&self.scopes);
         edit.scope_stack.push(ScopeId(0));
-        edit.element_stack.push(ElementId(0));
+        edit.element_stack.push(GlobalNodeId::VNodeId(ElementId(0)));
         edit.diff_node(old, new);
 
         (create.mutations, edit.mutations)
     }
+
+    /// Runs a function with the template associated with a given id.
+    pub fn with_template<R>(&self, id: &TemplateId, mut f: impl FnMut(&Template) -> R) -> R {
+        self.scopes
+            .templates
+            .borrow()
+            .get(id)
+            .map(|inner| {
+                let borrow = &*inner;
+                f(&borrow.borrow())
+            })
+            .unwrap()
+    }
 }
 
 /*

+ 2 - 3
packages/desktop/Cargo.toml

@@ -15,7 +15,6 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
 dioxus-core = { path = "../core", version = "^0.2.1", features = ["serialize"] }
 dioxus-html = { path = "../html", features = ["serialize"], version = "^0.2.1" }
 dioxus-interpreter-js = { path = "../interpreter", version = "^0.2.1" }
-dioxus-rsx-interpreter = { path = "../rsx_interpreter", optional = true }
 
 serde = "1.0.136"
 serde_json = "1.0.79"
@@ -33,7 +32,7 @@ webbrowser = "0.7.1"
 infer = "0.9.0"
 dunce = "1.0.2"
 
-interprocess = { version = "1.1.1", optional = true }
+interprocess = { version = "1.1.1" }
 
 [target.'cfg(target_os = "macos")'.dependencies]
 core-foundation = "0.9.3"
@@ -44,7 +43,7 @@ tokio_runtime = ["tokio"]
 fullscreen = ["wry/fullscreen"]
 transparent = ["wry/transparent"]
 tray = ["wry/tray"]
-hot-reload = ["dioxus-rsx-interpreter", "interprocess"]
+hot-reload = ["dioxus-core/hot-reload"]
 
 [dev-dependencies]
 dioxus-core-macro = { path = "../core-macro" }

+ 1 - 1
packages/desktop/src/controller.rs

@@ -51,7 +51,7 @@ impl DesktopController {
                 dom.base_scope().provide_context(window_context);
 
                 // allow other proccesses to send the new rsx text to the @dioxusin ipc channel and recieve erros on the @dioxusout channel
-                #[cfg(feature = "hot-reload")]
+                #[cfg(any(feature = "hot-reload", debug_assertions))]
                 crate::hot_reload::init(&dom);
 
                 let edits = dom.rebuild();

+ 3 - 3
packages/desktop/src/events.rs

@@ -3,7 +3,7 @@
 use std::any::Any;
 use std::sync::Arc;
 
-use dioxus_core::{ElementId, EventPriority, UserEvent};
+use dioxus_core::{EventPriority, GlobalNodeId, UserEvent};
 use dioxus_html::event_bubbles;
 use dioxus_html::on::*;
 use serde::{Deserialize, Serialize};
@@ -37,7 +37,7 @@ pub(crate) fn parse_ipc_message(payload: &str) -> Option<IpcMessage> {
 #[derive(Deserialize, Serialize)]
 struct ImEvent {
     event: String,
-    mounted_dom_id: u64,
+    mounted_dom_id: GlobalNodeId,
     contents: serde_json::Value,
 }
 
@@ -48,7 +48,7 @@ pub fn trigger_from_serialized(val: serde_json::Value) -> UserEvent {
         contents,
     } = serde_json::from_value(val).unwrap();
 
-    let mounted_dom_id = Some(ElementId(mounted_dom_id as usize));
+    let mounted_dom_id = Some(mounted_dom_id);
     let name = event_name_from_type(&event);
 
     let event = make_synthetic_event(&event, contents);

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

@@ -1,7 +1,6 @@
-use dioxus_core::VirtualDom;
-use dioxus_rsx_interpreter::{error::Error, ErrorHandler, SetManyRsxMessage, RSX_CONTEXT};
+use dioxus_core::{SchedulerMsg, SetTemplateMsg, VirtualDom};
 use interprocess::local_socket::{LocalSocketListener, LocalSocketStream};
-use std::io::{BufRead, BufReader, Write};
+use std::io::{BufRead, BufReader};
 use std::time::Duration;
 use std::{sync::Arc, sync::Mutex};
 
@@ -15,29 +14,6 @@ pub(crate) fn init(dom: &VirtualDom) {
     let latest_in_connection: Arc<Mutex<Option<BufReader<LocalSocketStream>>>> =
         Arc::new(Mutex::new(None));
     let latest_in_connection_handle = latest_in_connection.clone();
-    let latest_out_connection: Arc<Mutex<Option<BufReader<LocalSocketStream>>>> =
-        Arc::new(Mutex::new(None));
-    let latest_out_connection_handle = latest_out_connection.clone();
-
-    struct DesktopErrorHandler {
-        latest_connection: Arc<Mutex<Option<BufReader<LocalSocketStream>>>>,
-    }
-    impl ErrorHandler for DesktopErrorHandler {
-        fn handle_error(&self, err: Error) {
-            if let Some(conn) = &mut *self.latest_connection.lock().unwrap() {
-                conn.get_mut()
-                    .write_all((serde_json::to_string(&err).unwrap() + "\n").as_bytes())
-                    .unwrap();
-            } else {
-                panic!("{}", err);
-            }
-        }
-    }
-
-    RSX_CONTEXT.set_error_handler(DesktopErrorHandler {
-        latest_connection: latest_out_connection_handle,
-    });
-    RSX_CONTEXT.provide_scheduler_channel(dom.get_scheduler_channel());
 
     // connect to processes for incoming data
     std::thread::spawn(move || {
@@ -48,14 +24,7 @@ pub(crate) fn init(dom: &VirtualDom) {
         }
     });
 
-    // connect to processes for outgoing errors
-    std::thread::spawn(move || {
-        if let Ok(listener) = LocalSocketListener::bind("@dioxusout") {
-            for conn in listener.incoming().filter_map(handle_error) {
-                *latest_out_connection.lock().unwrap() = Some(BufReader::new(conn));
-            }
-        }
-    });
+    let mut channel = dom.get_scheduler_channel();
 
     std::thread::spawn(move || {
         loop {
@@ -63,8 +32,10 @@ pub(crate) fn init(dom: &VirtualDom) {
                 let mut buf = String::new();
                 match conn.read_line(&mut buf) {
                     Ok(_) => {
-                        let msgs: SetManyRsxMessage = serde_json::from_str(&buf).unwrap();
-                        RSX_CONTEXT.extend(msgs);
+                        let msg: SetTemplateMsg = serde_json::from_str(&buf).unwrap();
+                        channel
+                            .start_send(SchedulerMsg::SetTemplate(Box::new(msg)))
+                            .unwrap();
                     }
                     Err(err) => {
                         if err.kind() != std::io::ErrorKind::WouldBlock {

+ 1 - 1
packages/desktop/src/lib.rs

@@ -8,7 +8,7 @@ mod controller;
 mod desktop_context;
 mod escape;
 mod events;
-#[cfg(feature = "hot-reload")]
+#[cfg(any(feature = "hot-reload", debug_assertions))]
 mod hot_reload;
 mod protocol;
 

+ 1 - 8
packages/dioxus/Cargo.toml

@@ -17,12 +17,6 @@ dioxus-html = { path = "../html", version = "^0.2.1", optional = true }
 dioxus-core-macro = { path = "../core-macro", version = "^0.2.1", optional = true }
 dioxus-hooks = { path = "../hooks", version = "^0.2.1", optional = true }
 dioxus-rsx = { path = "../rsx", optional = true }
-dioxus-rsx-interpreter = { path = "../rsx_interpreter", optional = true }
-
-
-# dioxus-interpreter-js = { path = "./packages/interpreter", version = "^0.2.1", optional = true }
-# dioxus-native-core = { path = "./packages/native-core", version = "^0.2.0", optional = true }
-# dioxus-native-core-macro = { path = "./packages/native-core-macro", version = "^0.2.0", optional = true }
 
 [features]
 default = ["macro", "hooks", "html"]
@@ -30,8 +24,7 @@ macro = ["dioxus-core-macro", "dioxus-rsx"]
 html = ["dioxus-html"]
 hooks = ["dioxus-hooks"]
 hot-reload = [
-    "dioxus-core-macro/hot-reload",
-    "dioxus-rsx-interpreter",
+    "dioxus-core/hot-reload",
 ]
 
 [dev-dependencies]

+ 0 - 6
packages/dioxus/src/lib.rs

@@ -33,10 +33,4 @@ pub mod prelude {
 
     #[cfg(feature = "html")]
     pub use dioxus_elements::{GlobalAttributes, SvgAttributes};
-
-    #[cfg(feature = "hot-reload")]
-    pub use dioxus_rsx_interpreter::{
-        captuered_context::{CapturedContext, FormattedArg, IfmtArgs},
-        get_line_num, resolve_scope, CodeLocation, RsxContext,
-    };
 }

+ 158 - 46
packages/dioxus/tests/create_dom.rs

@@ -30,12 +30,29 @@ fn test_original_diff() {
     assert_eq!(
         mutations.edits,
         [
-            CreateElement { root: 1, tag: "div" },
-            CreateElement { root: 2, tag: "div" },
-            CreateTextNode { root: 3, text: "Hello, world!" },
-            AppendChildren { many: 1 },
+            CreateTemplate { id: 0 },
+            CreateElementTemplate {
+                root: 4503599627370495,
+                tag: "div",
+                locally_static: true,
+                fully_static: true
+            },
+            CreateElementTemplate {
+                root: 4503599627370496,
+                tag: "div",
+                locally_static: true,
+                fully_static: true
+            },
+            CreateTextNodeTemplate {
+                root: 4503599627370497,
+                text: "Hello, world!",
+                locally_static: true
+            },
             AppendChildren { many: 1 },
             AppendChildren { many: 1 },
+            FinishTemplate { len: 1 },
+            CreateTemplateRef { id: 1, template_id: 0 },
+            AppendChildren { many: 1 }
         ]
     );
 }
@@ -66,18 +83,49 @@ fn create() {
     assert_eq!(
         mutations.edits,
         [
-            CreateElement { root: 1, tag: "div" },
-            CreateElement { root: 2, tag: "div" },
-            CreateTextNode { root: 3, text: "Hello, world!" },
-            CreateElement { root: 4, tag: "div" },
-            CreateElement { root: 5, tag: "div" },
-            CreateTextNode { root: 6, text: "hello" },
-            CreateTextNode { root: 7, text: "world" },
-            AppendChildren { many: 2 },
+            CreateTemplate { id: 0 },
+            CreateElementTemplate {
+                root: 4503599627370495,
+                tag: "div",
+                locally_static: true,
+                fully_static: false
+            },
+            CreateElementTemplate {
+                root: 4503599627370496,
+                tag: "div",
+                locally_static: true,
+                fully_static: false
+            },
+            CreateTextNodeTemplate {
+                root: 4503599627370497,
+                text: "Hello, world!",
+                locally_static: true
+            },
+            CreateElementTemplate {
+                root: 4503599627370498,
+                tag: "div",
+                locally_static: true,
+                fully_static: false
+            },
+            CreateElementTemplate {
+                root: 4503599627370499,
+                tag: "div",
+                locally_static: true,
+                fully_static: false
+            },
+            CreatePlaceholderTemplate { root: 4503599627370500 },
             AppendChildren { many: 1 },
-            AppendChildren { many: 2 },
             AppendChildren { many: 1 },
+            AppendChildren { many: 2 },
             AppendChildren { many: 1 },
+            FinishTemplate { len: 1 },
+            CreateTemplateRef { id: 1, template_id: 0 },
+            EnterTemplateRef { root: 1 },
+            CreateTextNode { root: 2, text: "hello" },
+            CreateTextNode { root: 3, text: "world" },
+            ReplaceWith { root: 4503599627370500, m: 2 },
+            ExitTemplateRef {},
+            AppendChildren { many: 1 }
         ]
     );
 }
@@ -99,16 +147,20 @@ fn create_list() {
     assert_eq!(
         mutations.edits,
         [
-            CreateElement { root: 1, tag: "div" },
-            CreateTextNode { root: 2, text: "hello" },
-            AppendChildren { many: 1 },
-            CreateElement { root: 3, tag: "div" },
-            CreateTextNode { root: 4, text: "hello" },
+            CreateTemplate { id: 0 },
+            CreateElementTemplate {
+                root: 4503599627370495,
+                tag: "div",
+                locally_static: true,
+                fully_static: true
+            },
+            CreateTextNodeTemplate { root: 4503599627370496, text: "hello", locally_static: true },
             AppendChildren { many: 1 },
-            CreateElement { root: 5, tag: "div" },
-            CreateTextNode { root: 6, text: "hello" },
-            AppendChildren { many: 1 },
-            AppendChildren { many: 3 },
+            FinishTemplate { len: 1 },
+            CreateTemplateRef { id: 1, template_id: 0 },
+            CreateTemplateRef { id: 2, template_id: 0 },
+            CreateTemplateRef { id: 3, template_id: 0 },
+            AppendChildren { many: 3 }
         ]
     );
 }
@@ -131,11 +183,38 @@ fn create_simple() {
     assert_eq!(
         mutations.edits,
         [
-            CreateElement { root: 1, tag: "div" },
-            CreateElement { root: 2, tag: "div" },
-            CreateElement { root: 3, tag: "div" },
-            CreateElement { root: 4, tag: "div" },
-            AppendChildren { many: 4 },
+            CreateTemplate { id: 0 },
+            CreateElementTemplate {
+                root: 4503599627370495,
+                tag: "div",
+                locally_static: true,
+                fully_static: true
+            },
+            AppendChildren { many: 0 },
+            CreateElementTemplate {
+                root: 4503599627370496,
+                tag: "div",
+                locally_static: true,
+                fully_static: true
+            },
+            AppendChildren { many: 0 },
+            CreateElementTemplate {
+                root: 4503599627370497,
+                tag: "div",
+                locally_static: true,
+                fully_static: true
+            },
+            AppendChildren { many: 0 },
+            CreateElementTemplate {
+                root: 4503599627370498,
+                tag: "div",
+                locally_static: true,
+                fully_static: true
+            },
+            AppendChildren { many: 0 },
+            FinishTemplate { len: 4 },
+            CreateTemplateRef { id: 1, template_id: 0 },
+            AppendChildren { many: 1 }
         ]
     );
 }
@@ -168,25 +247,50 @@ fn create_components() {
     assert_eq!(
         mutations.edits,
         [
-            CreateElement { root: 1, tag: "h1" },
-            CreateElement { root: 2, tag: "div" },
-            CreateTextNode { root: 3, text: "abc1" },
-            AppendChildren { many: 1 },
-            CreateElement { root: 4, tag: "p" },
-            CreateElement { root: 5, tag: "h1" },
-            CreateElement { root: 6, tag: "div" },
-            CreateTextNode { root: 7, text: "abc2" },
-            AppendChildren { many: 1 },
-            CreateElement { root: 8, tag: "p" },
-            CreateElement { root: 9, tag: "h1" },
-            CreateElement { root: 10, tag: "div" },
-            CreateTextNode { root: 11, text: "abc3" },
+            CreateTemplate { id: 0 },
+            CreateElementTemplate {
+                root: 4503599627370495,
+                tag: "h1",
+                locally_static: true,
+                fully_static: true
+            },
+            AppendChildren { many: 0 },
+            CreateElementTemplate {
+                root: 4503599627370496,
+                tag: "div",
+                locally_static: true,
+                fully_static: false
+            },
+            CreatePlaceholderTemplate { root: 4503599627370497 },
             AppendChildren { many: 1 },
-            CreateElement { root: 12, tag: "p" },
-            AppendChildren { many: 9 },
+            CreateElementTemplate {
+                root: 4503599627370498,
+                tag: "p",
+                locally_static: true,
+                fully_static: true
+            },
+            AppendChildren { many: 0 },
+            FinishTemplate { len: 3 },
+            CreateTemplateRef { id: 1, template_id: 0 },
+            EnterTemplateRef { root: 1 },
+            CreateTextNode { root: 2, text: "abc1" },
+            ReplaceWith { root: 4503599627370497, m: 1 },
+            ExitTemplateRef {},
+            CreateTemplateRef { id: 3, template_id: 0 },
+            EnterTemplateRef { root: 3 },
+            CreateTextNode { root: 4, text: "abc2" },
+            ReplaceWith { root: 4503599627370497, m: 1 },
+            ExitTemplateRef {},
+            CreateTemplateRef { id: 5, template_id: 0 },
+            EnterTemplateRef { root: 5 },
+            CreateTextNode { root: 6, text: "abc3" },
+            ReplaceWith { root: 4503599627370497, m: 1 },
+            ExitTemplateRef {},
+            AppendChildren { many: 3 }
         ]
     );
 }
+
 #[test]
 fn anchors() {
     static App: Component = |cx| {
@@ -201,11 +305,19 @@ fn anchors() {
     assert_eq!(
         mutations.edits,
         [
-            CreateElement { root: 1, tag: "div" },
-            CreateTextNode { root: 2, text: "hello" },
+            CreateTemplate { id: 0 },
+            CreateElementTemplate {
+                root: 4503599627370495,
+                tag: "div",
+                locally_static: true,
+                fully_static: true
+            },
+            CreateTextNodeTemplate { root: 4503599627370496, text: "hello", locally_static: true },
             AppendChildren { many: 1 },
-            CreatePlaceholder { root: 3 },
-            AppendChildren { many: 2 },
+            FinishTemplate { len: 1 },
+            CreateTemplateRef { id: 1, template_id: 0 },
+            CreatePlaceholder { root: 2 },
+            AppendChildren { many: 2 }
         ]
     );
 }

+ 95 - 95
packages/dioxus/tests/diffing.rs

@@ -6,10 +6,10 @@
 //!
 //! It does not validated that component lifecycles work properly. This is done in another test file.
 
-use dioxus::prelude::*;
+use dioxus::{core_macro::rsx_without_templates, prelude::*};
 
 fn new_dom() -> VirtualDom {
-    VirtualDom::new(|cx| render!("hi"))
+    VirtualDom::new(|cx| cx.render(rsx_without_templates!("hi")))
 }
 
 use dioxus_core::DomEdit::*;
@@ -19,8 +19,8 @@ use dioxus_core::DomEdit::*;
 fn html_and_rsx_generate_the_same_output() {
     let dom = new_dom();
     let (create, change) = dom.diff_lazynodes(
-        rsx! ( div { "Hello world" } ),
-        rsx! ( div { "Goodbye world" } ),
+        rsx_without_templates! ( div { "Hello world" } ),
+        rsx_without_templates! ( div { "Goodbye world" } ),
     );
     assert_eq!(
         create.edits,
@@ -40,7 +40,7 @@ fn html_and_rsx_generate_the_same_output() {
 fn fragments_create_properly() {
     let dom = new_dom();
 
-    let create = dom.create_vnodes(rsx! {
+    let create = dom.create_vnodes(rsx_without_templates! {
         div { "Hello a" }
         div { "Hello b" }
         div { "Hello c" }
@@ -68,8 +68,8 @@ fn fragments_create_properly() {
 fn empty_fragments_create_anchors() {
     let dom = new_dom();
 
-    let left = rsx!({ (0..0).map(|_f| rsx! { div {}}) });
-    let right = rsx!({ (0..1).map(|_f| rsx! { div {}}) });
+    let left = rsx_without_templates!({ (0..0).map(|_f| rsx_without_templates! { div {}}) });
+    let right = rsx_without_templates!({ (0..1).map(|_f| rsx_without_templates! { div {}}) });
 
     let (create, change) = dom.diff_lazynodes(left, right);
 
@@ -91,8 +91,8 @@ fn empty_fragments_create_anchors() {
 fn empty_fragments_create_many_anchors() {
     let dom = new_dom();
 
-    let left = rsx!({ (0..0).map(|_f| rsx! { div {}}) });
-    let right = rsx!({ (0..5).map(|_f| rsx! { div {}}) });
+    let left = rsx_without_templates!({ (0..0).map(|_f| rsx_without_templates! { div {}}) });
+    let right = rsx_without_templates!({ (0..5).map(|_f| rsx_without_templates! { div {}}) });
 
     let (create, change) = dom.diff_lazynodes(left, right);
     assert_eq!(
@@ -119,10 +119,10 @@ fn empty_fragments_create_many_anchors() {
 fn empty_fragments_create_anchors_with_many_children() {
     let dom = new_dom();
 
-    let left = rsx!({ (0..0).map(|_| rsx! { div {} }) });
-    let right = rsx!({
+    let left = rsx_without_templates!({ (0..0).map(|_| rsx_without_templates! { div {} }) });
+    let right = rsx_without_templates!({
         (0..3).map(|f| {
-            rsx! { div { "hello: {f}" }}
+            rsx_without_templates! { div { "hello: {f}" }}
         })
     });
 
@@ -154,12 +154,12 @@ fn empty_fragments_create_anchors_with_many_children() {
 fn many_items_become_fragment() {
     let dom = new_dom();
 
-    let left = rsx!({
+    let left = rsx_without_templates!({
         (0..2).map(|_| {
-            rsx! { div { "hello" }}
+            rsx_without_templates! { div { "hello" }}
         })
     });
-    let right = rsx!({ (0..0).map(|_| rsx! { div {} }) });
+    let right = rsx_without_templates!({ (0..0).map(|_| rsx_without_templates! { div {} }) });
 
     let (create, change) = dom.diff_lazynodes(left, right);
     assert_eq!(
@@ -190,14 +190,14 @@ fn many_items_become_fragment() {
 fn two_equal_fragments_are_equal() {
     let dom = new_dom();
 
-    let left = rsx!({
+    let left = rsx_without_templates!({
         (0..2).map(|_| {
-            rsx! { div { "hello" }}
+            rsx_without_templates! { div { "hello" }}
         })
     });
-    let right = rsx!({
+    let right = rsx_without_templates!({
         (0..2).map(|_| {
-            rsx! { div { "hello" }}
+            rsx_without_templates! { div { "hello" }}
         })
     });
 
@@ -210,12 +210,12 @@ fn two_equal_fragments_are_equal() {
 fn two_fragments_with_differrent_elements_are_differet() {
     let dom = new_dom();
 
-    let left = rsx!(
-        { (0..2).map(|_| rsx! { div {  }} ) }
+    let left = rsx_without_templates!(
+        { (0..2).map(|_| rsx_without_templates! { div {  }} ) }
         p {}
     );
-    let right = rsx!(
-        { (0..5).map(|_| rsx! (h1 {  }) ) }
+    let right = rsx_without_templates!(
+        { (0..5).map(|_| rsx_without_templates! (h1 {  }) ) }
         p {}
     );
 
@@ -243,12 +243,12 @@ fn two_fragments_with_differrent_elements_are_differet() {
 fn two_fragments_with_differrent_elements_are_differet_shorter() {
     let dom = new_dom();
 
-    let left = rsx!(
-        {(0..5).map(|f| {rsx! { div {  }}})}
+    let left = rsx_without_templates!(
+        {(0..5).map(|f| {rsx_without_templates! { div {  }}})}
         p {}
     );
-    let right = rsx!(
-        {(0..2).map(|f| {rsx! { h1 {  }}})}
+    let right = rsx_without_templates!(
+        {(0..2).map(|f| {rsx_without_templates! { h1 {  }}})}
         p {}
     );
 
@@ -287,12 +287,12 @@ fn two_fragments_with_differrent_elements_are_differet_shorter() {
 fn two_fragments_with_same_elements_are_differet() {
     let dom = new_dom();
 
-    let left = rsx!(
-        {(0..2).map(|f| {rsx! { div {  }}})}
+    let left = rsx_without_templates!(
+        {(0..2).map(|f| rsx_without_templates! { div {  }})}
         p {}
     );
-    let right = rsx!(
-        {(0..5).map(|f| {rsx! { div {  }}})}
+    let right = rsx_without_templates!(
+        {(0..5).map(|f| rsx_without_templates! { div {  }})}
         p {}
     );
 
@@ -322,12 +322,12 @@ fn two_fragments_with_same_elements_are_differet() {
 fn keyed_diffing_order() {
     let dom = new_dom();
 
-    let left = rsx!(
-        {(0..5).map(|f| {rsx! { div { key: "{f}"  }}})}
+    let left = rsx_without_templates!(
+        {(0..5).map(|f| {rsx_without_templates! { div { key: "{f}"  }}})}
         p {"e"}
     );
-    let right = rsx!(
-        {(0..2).map(|f| rsx! { div { key: "{f}" }})}
+    let right = rsx_without_templates!(
+        {(0..2).map(|f| rsx_without_templates! { div { key: "{f}" }})}
         p {"e"}
     );
 
@@ -343,15 +343,15 @@ fn keyed_diffing_order() {
 fn keyed_diffing_out_of_order() {
     let dom = new_dom();
 
-    let left = rsx!({
+    let left = rsx_without_templates!({
         [0, 1, 2, 3, /**/ 4, 5, 6, /**/ 7, 8, 9].iter().map(|f| {
-            rsx! { div { key: "{f}"  }}
+            rsx_without_templates! { div { key: "{f}"  }}
         })
     });
 
-    let right = rsx!({
+    let right = rsx_without_templates!({
         [0, 1, 2, 3, /**/ 6, 4, 5, /**/ 7, 8, 9].iter().map(|f| {
-            rsx! { div { key: "{f}"  }}
+            rsx_without_templates! { div { key: "{f}"  }}
         })
     });
 
@@ -368,15 +368,15 @@ fn keyed_diffing_out_of_order() {
 fn keyed_diffing_out_of_order_adds() {
     let dom = new_dom();
 
-    let left = rsx!({
+    let left = rsx_without_templates!({
         [/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| {
-            rsx! { div { key: "{f}"  }}
+            rsx_without_templates! { div { key: "{f}"  }}
         })
     });
 
-    let right = rsx!({
+    let right = rsx_without_templates!({
         [/**/ 8, 7, 4, 5, 6 /**/].iter().map(|f| {
-            rsx! { div { key: "{f}"  }}
+            rsx_without_templates! { div { key: "{f}"  }}
         })
     });
 
@@ -395,15 +395,15 @@ fn keyed_diffing_out_of_order_adds() {
 fn keyed_diffing_out_of_order_adds_2() {
     let dom = new_dom();
 
-    let left = rsx!({
+    let left = rsx_without_templates!({
         [/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| {
-            rsx! { div { key: "{f}"  }}
+            rsx_without_templates! { div { key: "{f}"  }}
         })
     });
 
-    let right = rsx!({
+    let right = rsx_without_templates!({
         [/**/ 7, 8, 4, 5, 6 /**/].iter().map(|f| {
-            rsx! { div { key: "{f}"  }}
+            rsx_without_templates! { div { key: "{f}"  }}
         })
     });
 
@@ -423,15 +423,15 @@ fn keyed_diffing_out_of_order_adds_2() {
 fn keyed_diffing_out_of_order_adds_3() {
     let dom = new_dom();
 
-    let left = rsx!({
+    let left = rsx_without_templates!({
         [/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| {
-            rsx! { div { key: "{f}"  }}
+            rsx_without_templates! { div { key: "{f}"  }}
         })
     });
 
-    let right = rsx!({
+    let right = rsx_without_templates!({
         [/**/ 4, 8, 7, 5, 6 /**/].iter().map(|f| {
-            rsx! { div { key: "{f}"  }}
+            rsx_without_templates! { div { key: "{f}"  }}
         })
     });
 
@@ -451,15 +451,15 @@ fn keyed_diffing_out_of_order_adds_3() {
 fn keyed_diffing_out_of_order_adds_4() {
     let dom = new_dom();
 
-    let left = rsx!({
+    let left = rsx_without_templates!({
         [/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| {
-            rsx! { div { key: "{f}"  }}
+            rsx_without_templates! { div { key: "{f}"  }}
         })
     });
 
-    let right = rsx!({
+    let right = rsx_without_templates!({
         [/**/ 4, 5, 8, 7, 6 /**/].iter().map(|f| {
-            rsx! { div { key: "{f}"  }}
+            rsx_without_templates! { div { key: "{f}"  }}
         })
     });
 
@@ -479,15 +479,15 @@ fn keyed_diffing_out_of_order_adds_4() {
 fn keyed_diffing_out_of_order_adds_5() {
     let dom = new_dom();
 
-    let left = rsx!({
+    let left = rsx_without_templates!({
         [/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| {
-            rsx! { div { key: "{f}"  }}
+            rsx_without_templates! { div { key: "{f}"  }}
         })
     });
 
-    let right = rsx!({
+    let right = rsx_without_templates!({
         [/**/ 4, 5, 6, 8, 7 /**/].iter().map(|f| {
-            rsx! { div { key: "{f}"  }}
+            rsx_without_templates! { div { key: "{f}"  }}
         })
     });
 
@@ -502,15 +502,15 @@ fn keyed_diffing_out_of_order_adds_5() {
 fn keyed_diffing_additions() {
     let dom = new_dom();
 
-    let left = rsx!({
+    let left = rsx_without_templates!({
         [/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| {
-            rsx! { div { key: "{f}"  }}
+            rsx_without_templates! { div { key: "{f}"  }}
         })
     });
 
-    let right = rsx!({
+    let right = rsx_without_templates!({
         [/**/ 4, 5, 6, 7, 8, 9, 10 /**/].iter().map(|f| {
-            rsx! { div { key: "{f}"  }}
+            rsx_without_templates! { div { key: "{f}"  }}
         })
     });
 
@@ -529,15 +529,15 @@ fn keyed_diffing_additions() {
 fn keyed_diffing_additions_and_moves_on_ends() {
     let dom = new_dom();
 
-    let left = rsx!({
+    let left = rsx_without_templates!({
         [/**/ 4, 5, 6, 7 /**/].iter().map(|f| {
-            rsx! { div { key: "{f}"  }}
+            rsx_without_templates! { div { key: "{f}"  }}
         })
     });
 
-    let right = rsx!({
+    let right = rsx_without_templates!({
         [/**/ 7, 4, 5, 6, 11, 12 /**/].iter().map(|f| {
-            rsx! { div { key: "{f}"  }}
+            rsx_without_templates! { div { key: "{f}"  }}
         })
     });
 
@@ -561,15 +561,15 @@ fn keyed_diffing_additions_and_moves_on_ends() {
 fn keyed_diffing_additions_and_moves_in_middle() {
     let dom = new_dom();
 
-    let left = rsx!({
+    let left = rsx_without_templates!({
         [/**/ 1, 2, 3, 4 /**/].iter().map(|f| {
-            rsx! { div { key: "{f}"  }}
+            rsx_without_templates! { div { key: "{f}"  }}
         })
     });
 
-    let right = rsx!({
+    let right = rsx_without_templates!({
         [/**/ 4, 1, 7, 8, 2, 5, 6, 3 /**/].iter().map(|f| {
-            rsx! { div { key: "{f}"  }}
+            rsx_without_templates! { div { key: "{f}"  }}
         })
     });
 
@@ -598,15 +598,15 @@ fn keyed_diffing_additions_and_moves_in_middle() {
 fn controlled_keyed_diffing_out_of_order() {
     let dom = new_dom();
 
-    let left = rsx!({
+    let left = rsx_without_templates!({
         [4, 5, 6, 7].iter().map(|f| {
-            rsx! { div { key: "{f}" }}
+            rsx_without_templates! { div { key: "{f}" }}
         })
     });
 
-    let right = rsx!({
+    let right = rsx_without_templates!({
         [0, 5, 9, 6, 4].iter().map(|f| {
-            rsx! { div { key: "{f}" }}
+            rsx_without_templates! { div { key: "{f}" }}
         })
     });
 
@@ -636,15 +636,15 @@ fn controlled_keyed_diffing_out_of_order() {
 fn controlled_keyed_diffing_out_of_order_max_test() {
     let dom = new_dom();
 
-    let left = rsx!({
+    let left = rsx_without_templates!({
         [0, 1, 2, 3, 4].iter().map(|f| {
-            rsx! { div { key: "{f}"  }}
+            rsx_without_templates! { div { key: "{f}"  }}
         })
     });
 
-    let right = rsx!({
+    let right = rsx_without_templates!({
         [3, 0, 1, 10, 2].iter().map(|f| {
-            rsx! { div { key: "{f}"  }}
+            rsx_without_templates! { div { key: "{f}"  }}
         })
     });
 
@@ -668,15 +668,15 @@ fn controlled_keyed_diffing_out_of_order_max_test() {
 fn remove_list() {
     let dom = new_dom();
 
-    let left = rsx!({
+    let left = rsx_without_templates!({
         (0..10).rev().take(5).map(|i| {
-            rsx! { Fragment { key: "{i}", "{i}" }}
+            rsx_without_templates! { Fragment { key: "{i}", "{i}" }}
         })
     });
 
-    let right = rsx!({
+    let right = rsx_without_templates!({
         (0..10).rev().take(2).map(|i| {
-            rsx! { Fragment { key: "{i}", "{i}" }}
+            rsx_without_templates! { Fragment { key: "{i}", "{i}" }}
         })
     });
 
@@ -702,15 +702,15 @@ fn remove_list() {
 fn remove_list_nokeyed() {
     let dom = new_dom();
 
-    let left = rsx!({
+    let left = rsx_without_templates!({
         (0..10).rev().take(5).map(|i| {
-            rsx! { Fragment { "{i}" }}
+            rsx_without_templates! { Fragment { "{i}" }}
         })
     });
 
-    let right = rsx!({
+    let right = rsx_without_templates!({
         (0..10).rev().take(2).map(|i| {
-            rsx! { Fragment { "{i}" }}
+            rsx_without_templates! { Fragment { "{i}" }}
         })
     });
 
@@ -732,10 +732,10 @@ fn add_nested_elements() {
     let vdom = new_dom();
 
     let (_create, change) = vdom.diff_lazynodes(
-        rsx! {
+        rsx_without_templates! {
             div{}
         },
-        rsx! {
+        rsx_without_templates! {
             div{
                 div{}
             }
@@ -758,10 +758,10 @@ fn add_listeners() {
     let vdom = new_dom();
 
     let (_create, change) = vdom.diff_lazynodes(
-        rsx! {
+        rsx_without_templates! {
             div{}
         },
-        rsx! {
+        rsx_without_templates! {
             div{
                 onkeyup: |_| {},
                 onkeydown: |_| {},
@@ -783,13 +783,13 @@ fn remove_listeners() {
     let vdom = new_dom();
 
     let (_create, change) = vdom.diff_lazynodes(
-        rsx! {
+        rsx_without_templates! {
             div{
                 onkeyup: |_| {},
                 onkeydown: |_| {},
             }
         },
-        rsx! {
+        rsx_without_templates! {
             div{}
         },
     );
@@ -808,12 +808,12 @@ fn diff_listeners() {
     let vdom = new_dom();
 
     let (_create, change) = vdom.diff_lazynodes(
-        rsx! {
+        rsx_without_templates! {
             div{
                 onkeydown: |_| {},
             }
         },
-        rsx! {
+        rsx_without_templates! {
             div{
                 onkeyup: |_| {},
             }

+ 18 - 8
packages/dioxus/tests/earlyabort.rs

@@ -37,27 +37,37 @@ fn test_early_abort() {
     assert_eq!(
         edits.edits,
         [
-            CreateElement { tag: "div", root: 1 },
-            CreateTextNode { text: "Hello, world!", root: 2 },
-            AppendChildren { many: 1 },
+            CreateTemplate { id: 0 },
+            CreateElementTemplate {
+                root: 4503599627370495,
+                tag: "div",
+                locally_static: true,
+                fully_static: true
+            },
+            CreateTextNodeTemplate {
+                root: 4503599627370496,
+                text: "Hello, world!",
+                locally_static: true
+            },
             AppendChildren { many: 1 },
+            FinishTemplate { len: 1 },
+            CreateTemplateRef { id: 1, template_id: 0 },
+            AppendChildren { many: 1 }
         ]
     );
 
     let edits = dom.hard_diff(ScopeId(0));
     assert_eq!(
         edits.edits,
-        [CreatePlaceholder { root: 3 }, ReplaceWith { root: 1, m: 1 },],
+        [CreatePlaceholder { root: 2 }, ReplaceWith { root: 1, m: 1 },],
     );
 
     let edits = dom.hard_diff(ScopeId(0));
     assert_eq!(
         edits.edits,
         [
-            CreateElement { tag: "div", root: 1 }, // keys get reused
-            CreateTextNode { text: "Hello, world!", root: 2 }, // keys get reused
-            AppendChildren { many: 1 },
-            ReplaceWith { root: 3, m: 1 },
+            CreateTemplateRef { id: 1, template_id: 0 }, // gets reused
+            ReplaceWith { root: 2, m: 1 }
         ]
     );
 }

+ 23 - 23
packages/dioxus/tests/lifecycle.rs

@@ -2,7 +2,7 @@
 #![allow(non_snake_case)]
 
 //! Tests for the lifecycle of components.
-use dioxus::prelude::*;
+use dioxus::{core_macro::rsx_without_templates, prelude::*};
 use dioxus_core::DomEdit::*;
 use std::sync::{Arc, Mutex};
 
@@ -16,7 +16,7 @@ fn manual_diffing() {
 
     static App: Component<AppProps> = |cx| {
         let val = cx.props.value.lock().unwrap();
-        cx.render(rsx! { div { "{val}" } })
+        cx.render(rsx_without_templates! { div { "{val}" } })
     };
 
     let value = Arc::new(Mutex::new("Hello"));
@@ -38,7 +38,7 @@ fn events_generate() {
 
         let inner = match *count {
             0 => {
-                rsx! {
+                rsx_without_templates! {
                     div {
                         onclick: move |_| *count += 1,
                         div {
@@ -81,21 +81,21 @@ fn components_generate() {
         *render_phase += 1;
 
         cx.render(match *render_phase {
-            1 => rsx!("Text0"),
-            2 => rsx!(div {}),
-            3 => rsx!("Text2"),
-            4 => rsx!(Child {}),
-            5 => rsx!({ None as Option<()> }),
-            6 => rsx!("text 3"),
-            7 => rsx!({ (0..2).map(|f| rsx!("text {f}")) }),
-            8 => rsx!(Child {}),
+            1 => rsx_without_templates!("Text0"),
+            2 => rsx_without_templates!(div {}),
+            3 => rsx_without_templates!("Text2"),
+            4 => rsx_without_templates!(Child {}),
+            5 => rsx_without_templates!({ None as Option<()> }),
+            6 => rsx_without_templates!("text 3"),
+            7 => rsx_without_templates!({ (0..2).map(|f| rsx_without_templates!("text {f}")) }),
+            8 => rsx_without_templates!(Child {}),
             _ => todo!(),
         })
     };
 
     fn Child(cx: Scope) -> Element {
         println!("Running child");
-        cx.render(rsx! {
+        cx.render(rsx_without_templates! {
             h1 {}
         })
     }
@@ -175,53 +175,53 @@ fn component_swap() {
         *render_phase += 1;
 
         cx.render(match *render_phase {
-            0 => rsx!(
+            0 => rsx_without_templates!(
                 div {
                     NavBar {}
                     Dashboard {}
                 }
             ),
-            1 => rsx!(
+            1 => rsx_without_templates!(
                 div {
                     NavBar {}
                     Results {}
                 }
             ),
-            2 => rsx!(
+            2 => rsx_without_templates!(
                 div {
                     NavBar {}
                     Dashboard {}
                 }
             ),
-            3 => rsx!(
+            3 => rsx_without_templates!(
                 div {
                     NavBar {}
                     Results {}
                 }
             ),
-            4 => rsx!(
+            4 => rsx_without_templates!(
                 div {
                     NavBar {}
                     Dashboard {}
                 }
             ),
-            _ => rsx!("blah"),
+            _ => rsx_without_templates!("blah"),
         })
     };
 
     static NavBar: Component = |cx| {
         println!("running navbar");
-        cx.render(rsx! {
+        cx.render(rsx_without_templates! {
             h1 {
                 "NavBar"
-                {(0..3).map(|f| rsx!(NavLink {}))}
+                {(0..3).map(|f| rsx_without_templates!(NavLink {}))}
             }
         })
     };
 
     static NavLink: Component = |cx| {
         println!("running navlink");
-        cx.render(rsx! {
+        cx.render(rsx_without_templates! {
             h1 {
                 "NavLink"
             }
@@ -230,7 +230,7 @@ fn component_swap() {
 
     static Dashboard: Component = |cx| {
         println!("running dashboard");
-        cx.render(rsx! {
+        cx.render(rsx_without_templates! {
             div {
                 "dashboard"
             }
@@ -239,7 +239,7 @@ fn component_swap() {
 
     static Results: Component = |cx| {
         println!("running results");
-        cx.render(rsx! {
+        cx.render(rsx_without_templates! {
             div {
                 "results"
             }

+ 9 - 9
packages/dioxus/tests/sharedstate.rs

@@ -1,6 +1,6 @@
 #![allow(unused, non_upper_case_globals, non_snake_case)]
 
-use dioxus::prelude::*;
+use dioxus::{core_macro::rsx_without_templates, prelude::*};
 use dioxus_core::{DomEdit, Mutations, SchedulerMsg, ScopeId};
 use std::rc::Rc;
 use DomEdit::*;
@@ -11,12 +11,12 @@ fn shared_state_test() {
 
     static App: Component = |cx| {
         cx.provide_context(Rc::new(MySharedState("world!")));
-        cx.render(rsx!(Child {}))
+        cx.render(rsx_without_templates!(Child {}))
     };
 
     static Child: Component = |cx| {
         let shared = cx.consume_context::<Rc<MySharedState>>()?;
-        cx.render(rsx!("Hello, {shared.0}"))
+        cx.render(rsx_without_templates!("Hello, {shared.0}"))
     };
 
     let mut dom = VirtualDom::new(App);
@@ -42,13 +42,13 @@ fn swap_test() {
         cx.provide_context(Rc::new(MySharedState("world!")));
 
         let child = match *val % 2 {
-            0 => rsx!(
+            0 => rsx_without_templates!(
                 Child1 {
                     Child1 { }
                     Child2 { }
                 }
             ),
-            _ => rsx!(
+            _ => rsx_without_templates!(
                 Child2 {
                     Child2 { }
                     Child2 { }
@@ -56,7 +56,7 @@ fn swap_test() {
             ),
         };
 
-        cx.render(rsx!(
+        cx.render(rsx_without_templates!(
             Router {
                 div { child }
             }
@@ -65,14 +65,14 @@ fn swap_test() {
 
     #[inline_props]
     fn Router<'a>(cx: Scope, children: Element<'a>) -> Element<'a> {
-        cx.render(rsx!(div { children }))
+        cx.render(rsx_without_templates!(div { children }))
     }
 
     #[inline_props]
     fn Child1<'a>(cx: Scope, children: Element<'a>) -> Element {
         let shared = cx.consume_context::<Rc<MySharedState>>().unwrap();
         println!("Child1: {}", shared.0);
-        cx.render(rsx! {
+        cx.render(rsx_without_templates! {
             div {
                 "{shared.0}",
                 children
@@ -84,7 +84,7 @@ fn swap_test() {
     fn Child2<'a>(cx: Scope, children: Element<'a>) -> Element {
         let shared = cx.consume_context::<Rc<MySharedState>>().unwrap();
         println!("Child2: {}", shared.0);
-        cx.render(rsx! {
+        cx.render(rsx_without_templates! {
             h1 {
                 "{shared.0}",
                 children

+ 30 - 6
packages/dioxus/tests/vdom_rebuild.rs

@@ -63,14 +63,38 @@ fn conditional_rendering() {
     assert_eq!(
         mutations.edits,
         [
-            CreateElement { root: 1, tag: "h1" },
-            CreateTextNode { root: 2, text: "hello" },
+            CreateTemplate { id: 0 },
+            CreateElementTemplate {
+                root: 4503599627370495,
+                tag: "h1",
+                locally_static: true,
+                fully_static: true
+            },
+            CreateTextNodeTemplate { root: 4503599627370496, text: "hello", locally_static: true },
             AppendChildren { many: 1 },
-            CreateElement { root: 3, tag: "span" },
-            CreateTextNode { root: 4, text: "a" },
+            CreatePlaceholderTemplate { root: 4503599627370497 },
+            CreatePlaceholderTemplate { root: 4503599627370498 },
+            FinishTemplate { len: 3 },
+            CreateTemplateRef { id: 1, template_id: 0 },
+            EnterTemplateRef { root: 1 },
+            CreateTemplate { id: 1 },
+            CreateElementTemplate {
+                root: 4503599627370495,
+                tag: "span",
+                locally_static: true,
+                fully_static: true
+            },
+            CreateTextNodeTemplate { root: 4503599627370496, text: "a", locally_static: true },
             AppendChildren { many: 1 },
-            CreatePlaceholder { root: 5 },
-            AppendChildren { many: 3 },
+            FinishTemplate { len: 1 },
+            CreateTemplateRef { id: 2, template_id: 1 },
+            ReplaceWith { root: 4503599627370497, m: 1 },
+            ExitTemplateRef {},
+            EnterTemplateRef { root: 1 },
+            CreatePlaceholder { root: 3 },
+            ReplaceWith { root: 4503599627370498, m: 1 },
+            ExitTemplateRef {},
+            AppendChildren { many: 1 }
         ]
     )
 }

+ 68 - 35
packages/html/src/elements.rs

@@ -1,6 +1,5 @@
 use crate::{GlobalAttributes, SvgAttributes};
 use dioxus_core::*;
-use std::fmt::Arguments;
 
 macro_rules! builder_constructors {
     (
@@ -28,10 +27,12 @@ macro_rules! builder_constructors {
 
             impl $name {
                 $(
-                    $(#[$attr_method])*
-                    pub fn $fil<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
-                        cx.attr(stringify!($fil), val, None, false)
-                    }
+                    #[allow(non_upper_case_globals)]
+                    pub const $fil: AttributeDiscription = AttributeDiscription{
+                        name: stringify!($fil),
+                        namespace: None,
+                        volatile: false
+                    };
                 )*
             }
         )*
@@ -57,9 +58,13 @@ macro_rules! builder_constructors {
 
             impl $name {
                 $(
-                    pub fn $fil<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
-                        cx.attr(stringify!($fil), val, Some(stringify!($namespace)), false)
-                    }
+
+                    #[allow(non_upper_case_globals)]
+                    pub const $fil: AttributeDiscription = AttributeDiscription{
+                        name: stringify!($fil),
+                        namespace: Some(stringify!($namespace)),
+                        volatile: false
+                    };
                 )*
             }
         )*
@@ -1061,13 +1066,19 @@ impl input {
     /// - `time`
     /// - `url`
     /// - `week`
-    pub fn r#type<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
-        cx.attr("type", val, None, false)
-    }
+    #[allow(non_upper_case_globals)]
+    pub const r#type: AttributeDiscription = AttributeDiscription {
+        name: "type",
+        namespace: None,
+        volatile: false,
+    };
 
-    pub fn value<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
-        cx.attr("value", val, None, true)
-    }
+    #[allow(non_upper_case_globals)]
+    pub const value: AttributeDiscription = AttributeDiscription {
+        name: "value",
+        namespace: None,
+        volatile: true,
+    };
 }
 
 /*
@@ -1077,41 +1088,63 @@ volatile attributes
 impl script {
     // r#async: Bool,
     // r#type: String, // TODO could be an enum
-    pub fn r#type<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
-        cx.attr("type", val, None, false)
-    }
-    pub fn r#script<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
-        cx.attr("script", val, None, false)
-    }
+    #[allow(non_upper_case_globals)]
+    pub const r#type: AttributeDiscription = AttributeDiscription {
+        name: "type",
+        namespace: None,
+        volatile: false,
+    };
+
+    #[allow(non_upper_case_globals)]
+    pub const r#script: AttributeDiscription = AttributeDiscription {
+        name: "script",
+        namespace: None,
+        volatile: false,
+    };
 }
 
 impl button {
-    pub fn r#type<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
-        cx.attr("type", val, None, false)
-    }
+    #[allow(non_upper_case_globals)]
+    pub const r#type: AttributeDiscription = AttributeDiscription {
+        name: "type",
+        namespace: None,
+        volatile: false,
+    };
 }
 
 impl select {
-    pub fn value<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
-        cx.attr("value", val, None, true)
-    }
+    #[allow(non_upper_case_globals)]
+    pub const value: AttributeDiscription = AttributeDiscription {
+        name: "value",
+        namespace: None,
+        volatile: true,
+    };
 }
 
 impl option {
-    pub fn selected<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
-        cx.attr("selected", val, None, true)
-    }
+    #[allow(non_upper_case_globals)]
+    pub const selected: AttributeDiscription = AttributeDiscription {
+        name: "selected",
+        namespace: None,
+        volatile: true,
+    };
 }
 
 impl textarea {
-    pub fn value<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
-        cx.attr("value", val, None, true)
-    }
+    #[allow(non_upper_case_globals)]
+    pub const value: AttributeDiscription = AttributeDiscription {
+        name: "value",
+        namespace: None,
+        volatile: true,
+    };
 }
 impl label {
-    pub fn r#for<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
-        cx.attr("for", val, None, false)
-    }
+    #[allow(non_upper_case_globals)]
+    pub const r#for: AttributeDiscription = AttributeDiscription {
+        name: "for",
+        namespace: None,
+        volatile: false,
+    };
 }
 
 builder_constructors! {

+ 30 - 20
packages/html/src/global_attributes.rs

@@ -1,5 +1,4 @@
 use dioxus_core::*;
-use std::fmt::Arguments;
 
 macro_rules! no_namespace_trait_methods {
     (
@@ -9,10 +8,12 @@ macro_rules! no_namespace_trait_methods {
         )*
     ) => {
         $(
-            $(#[$attr])*
-            fn $name<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
-                cx.attr(stringify!($name), val, None, false)
-            }
+            #[allow(non_upper_case_globals)]
+            const $name: AttributeDiscription = AttributeDiscription{
+                name: stringify!($name),
+                namespace: None,
+                volatile: false
+            };
         )*
     };
 }
@@ -24,11 +25,12 @@ macro_rules! style_trait_methods {
         )*
     ) => {
         $(
-            #[inline]
-            $(#[$attr])*
-            fn $name<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
-                cx.attr($lit, val, Some("style"), false)
-            }
+            #[allow(non_upper_case_globals)]
+            const $name: AttributeDiscription = AttributeDiscription{
+                name: $lit,
+                namespace: Some("style"),
+                volatile: false
+            };
         )*
     };
 }
@@ -40,10 +42,12 @@ macro_rules! aria_trait_methods {
         )*
     ) => {
         $(
-            $(#[$attr])*
-            fn $name<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
-                cx.attr($lit, val, None, false)
-            }
+            #[allow(non_upper_case_globals)]
+            const $name: AttributeDiscription = AttributeDiscription{
+                name: $lit,
+                namespace: None,
+                volatile: false
+            };
         )*
     };
 }
@@ -53,9 +57,12 @@ pub trait GlobalAttributes {
     ///
     /// For more information, see the MDN docs:
     /// <https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault>
-    fn prevent_default<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
-        cx.attr("dioxus-prevent-default", val, None, false)
-    }
+    #[allow(non_upper_case_globals)]
+    const prevent_default: AttributeDiscription = AttributeDiscription {
+        name: "dioxus-prevent-default",
+        namespace: None,
+        volatile: false,
+    };
 
     no_namespace_trait_methods! {
         accesskey;
@@ -597,9 +604,12 @@ pub trait SvgAttributes {
     ///
     /// For more information, see the MDN docs:
     /// <https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault>
-    fn prevent_default<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
-        cx.attr("dioxus-prevent-default", val, None, false)
-    }
+    #[allow(non_upper_case_globals)]
+    const prevent_default: AttributeDiscription = AttributeDiscription {
+        name: "dioxus-prevent-default",
+        namespace: None,
+        volatile: false,
+    };
     aria_trait_methods! {
         accent_height: "accent-height",
         accumulate: "accumulate",

+ 40 - 0
packages/interpreter/src/bindings.rs

@@ -73,4 +73,44 @@ extern "C" {
 
     #[wasm_bindgen(method)]
     pub fn RemoveAttribute(this: &Interpreter, root: u64, field: &str, ns: Option<&str>);
+
+    #[wasm_bindgen(method)]
+    pub fn CreateTemplateRef(this: &Interpreter, id: u64, template_id: u64);
+
+    #[wasm_bindgen(method)]
+    pub fn CreateTemplate(this: &Interpreter, id: u64);
+
+    #[wasm_bindgen(method)]
+    pub fn FinishTemplate(this: &Interpreter, len: u32);
+
+    #[wasm_bindgen(method)]
+    pub fn EnterTemplateRef(this: &Interpreter, id: u64);
+
+    #[wasm_bindgen(method)]
+    pub fn ExitTemplateRef(this: &Interpreter);
+
+    #[wasm_bindgen(method)]
+    pub fn CreateElementTemplate(
+        this: &Interpreter,
+        tag: &str,
+        root: u64,
+        locally_static: bool,
+        fully_static: bool,
+    );
+
+    #[wasm_bindgen(method)]
+    pub fn CreateElementNsTemplate(
+        this: &Interpreter,
+        tag: &str,
+        id: u64,
+        ns: &str,
+        locally_static: bool,
+        fully_static: bool,
+    );
+
+    #[wasm_bindgen(method)]
+    pub fn CreateTextNodeTemplate(this: &Interpreter, text: &str, root: u64, locally_static: bool);
+
+    #[wasm_bindgen(method)]
+    pub fn CreatePlaceholderTemplate(this: &Interpreter, root: u64);
 }

+ 343 - 51
packages/interpreter/src/interpreter.js

@@ -1,3 +1,7 @@
+// id > Number.MAX_SAFE_INTEGER/2 in template ref
+// id <= Number.MAX_SAFE_INTEGER/2 in global nodes
+const templateIdLimit = BigInt((Number.MAX_SAFE_INTEGER - 1) / 2);
+
 export function main() {
   let root = window.document.getElementById("main");
   if (root != null) {
@@ -6,6 +10,123 @@ export function main() {
   }
 }
 
+class TemplateRef {
+  constructor(fragment, dynamicNodePaths, roots, id) {
+    this.fragment = fragment;
+    this.dynamicNodePaths = dynamicNodePaths;
+    this.roots = roots;
+    this.id = id;
+    this.placed = false;
+    this.nodes = [];
+  }
+
+  build(id) {
+    if (!this.nodes[id]) {
+      let current = this.fragment;
+      const path = this.dynamicNodePaths[id];
+      for (let i = 0; i < path.length; i++) {
+        const idx = path[i];
+        current = current.firstChild;
+        for (let i2 = 0; i2 < idx; i2++) {
+          current = current.nextSibling;
+        }
+      }
+      this.nodes[id] = current;
+    }
+  }
+
+  get(id) {
+    this.build(id);
+    return this.nodes[id];
+  }
+
+  parent() {
+    return this.roots[0].parentNode;
+  }
+
+  first() {
+    return this.roots[0];
+  }
+
+  last() {
+    return this.roots[this.roots.length - 1];
+  }
+
+  move() {
+    // move the root nodes into a new template
+    this.fragment = new DocumentFragment();
+    for (let n of this.roots) {
+      this.fragment.appendChild(n);
+    }
+  }
+
+  getFragment() {
+    if (!this.placed) {
+      this.placed = true;
+    }
+    else {
+      this.move();
+    }
+    return this.fragment;
+  }
+}
+
+class Template {
+  constructor(template_id, id) {
+    this.nodes = [];
+    this.dynamicNodePaths = [];
+    this.template_id = template_id;
+    this.id = id;
+    this.template = document.createElement("template");
+    this.reconstructingRefrencesIndex = null;
+  }
+
+  finalize(roots) {
+    for (let i = 0; i < roots.length; i++) {
+      let node = roots[i];
+      let path = [i];
+      const is_element = node.nodeType == 1;
+      const locally_static = is_element && !node.hasAttribute("data-dioxus-dynamic");
+      if (!locally_static) {
+        this.dynamicNodePaths[node.tmplId] = [...path];
+      }
+      const traverse_children = is_element && !node.hasAttribute("data-dioxus-fully-static");
+      if (traverse_children) {
+        this.createIds(path, node);
+      }
+      this.template.content.appendChild(node);
+    }
+    document.head.appendChild(this.template);
+  }
+
+  createIds(path, root) {
+    let i = 0;
+    for (let node = root.firstChild; node != null; node = node.nextSibling) {
+      let new_path = [...path, i];
+      const is_element = node.nodeType == 1;
+      const locally_static = is_element && !node.hasAttribute("data-dioxus-dynamic");
+      if (!locally_static) {
+        this.dynamicNodePaths[node.tmplId] = [...new_path];
+      }
+      const traverse_children = is_element && !node.hasAttribute("data-dioxus-fully-static");
+      if (traverse_children) {
+        this.createIds(new_path, node);
+      }
+      i++;
+    }
+  }
+
+  ref(id) {
+    const template = this.template.content.cloneNode(true);
+    let roots = [];
+    this.reconstructingRefrencesIndex = 0;
+    for (let node = template.firstChild; node != null; node = node.nextSibling) {
+      roots.push(node);
+    }
+    return new TemplateRef(template, this.dynamicNodePaths, roots, id);
+  }
+}
+
 class ListenerMap {
   constructor(root) {
     // bubbling events can listen at the root element
@@ -53,21 +174,18 @@ class ListenerMap {
       element.removeEventListener(event_name, handler);
     }
   }
-
-  removeAllNonBubbling(element) {
-    const id = element.getAttribute("data-dioxus-id");
-    delete this.local[id];
-  }
 }
 
 export class Interpreter {
   constructor(root) {
     this.root = root;
     this.stack = [root];
+    this.templateInProgress = null;
+    this.insideTemplateRef = [];
     this.listeners = new ListenerMap(root);
     this.handlers = {};
-    this.lastNodeWasText = false;
     this.nodes = [root];
+    this.templates = [];
   }
   top() {
     return this.stack[this.stack.length - 1];
@@ -75,11 +193,45 @@ export class Interpreter {
   pop() {
     return this.stack.pop();
   }
+  currentTemplateId() {
+    if (this.insideTemplateRef.length) {
+      return this.insideTemplateRef[this.insideTemplateRef.length - 1].id;
+    }
+    else {
+      return null;
+    }
+  }
+  getId(id) {
+    if (this.templateInProgress !== null) {
+      return this.templates[this.templateInProgress].nodes[id - templateIdLimit];
+    }
+    else if (this.insideTemplateRef.length && id >= templateIdLimit) {
+      return this.insideTemplateRef[this.insideTemplateRef.length - 1].get(id - templateIdLimit);
+    }
+    else {
+      return this.nodes[id];
+    }
+  }
   SetNode(id, node) {
-    this.nodes[id] = node;
+    if (this.templateInProgress !== null) {
+      id -= templateIdLimit;
+      node.tmplId = id;
+      this.templates[this.templateInProgress].nodes[id] = node;
+    }
+    else if (this.insideTemplateRef.length && id >= templateIdLimit) {
+      id -= templateIdLimit;
+      let last = this.insideTemplateRef[this.insideTemplateRef.length - 1];
+      last.childNodes[id] = node;
+      if (last.nodeCache[id]) {
+        last.nodeCache[id] = node;
+      }
+    }
+    else {
+      this.nodes[id] = node;
+    }
   }
   PushRoot(root) {
-    const node = this.nodes[root];
+    const node = this.getId(root);
     this.stack.push(node);
   }
   PopRoot() {
@@ -89,73 +241,126 @@ export class Interpreter {
     let root = this.stack[this.stack.length - (1 + many)];
     let to_add = this.stack.splice(this.stack.length - many);
     for (let i = 0; i < many; i++) {
-      root.appendChild(to_add[i]);
+      const child = to_add[i];
+      if (child instanceof TemplateRef) {
+        root.appendChild(child.getFragment());
+      }
+      else {
+        root.appendChild(child);
+      }
     }
   }
   ReplaceWith(root_id, m) {
-    let root = this.nodes[root_id];
-    let els = this.stack.splice(this.stack.length - m);
-    if (is_element_node(root.nodeType)) {
-      this.listeners.removeAllNonBubbling(root);
+    let root = this.getId(root_id);
+    if (root instanceof TemplateRef) {
+      this.InsertBefore(root_id, m);
+      this.Remove(root_id);
+    }
+    else {
+      let els = this.stack.splice(this.stack.length - m).map(function (el) {
+        if (el instanceof TemplateRef) {
+          return el.getFragment();
+        }
+        else {
+          return el;
+        }
+      });
+      root.replaceWith(...els);
     }
-    root.replaceWith(...els);
   }
   InsertAfter(root, n) {
-    let old = this.nodes[root];
-    let new_nodes = this.stack.splice(this.stack.length - n);
-    old.after(...new_nodes);
+    const old = this.getId(root);
+    const new_nodes = this.stack.splice(this.stack.length - n).map(function (el) {
+      if (el instanceof TemplateRef) {
+        return el.getFragment();
+      }
+      else {
+        return el;
+      }
+    });
+    if (old instanceof TemplateRef) {
+      const last = old.last();
+      last.after(...new_nodes);
+    }
+    else {
+      old.after(...new_nodes);
+    }
   }
   InsertBefore(root, n) {
-    let old = this.nodes[root];
-    let new_nodes = this.stack.splice(this.stack.length - n);
-    old.before(...new_nodes);
+    const old = this.getId(root);
+    const new_nodes = this.stack.splice(this.stack.length - n).map(function (el) {
+      if (el instanceof TemplateRef) {
+        return el.getFragment();
+      }
+      else {
+        return el;
+      }
+    });
+    if (old instanceof TemplateRef) {
+      const first = old.first();
+      first.before(...new_nodes);
+    }
+    else {
+      old.before(...new_nodes);
+    }
   }
   Remove(root) {
-    let node = this.nodes[root];
+    let node = this.getId(root);
     if (node !== undefined) {
-      if (is_element_node(node)) {
-        this.listeners.removeAllNonBubbling(node);
+      if (node instanceof TemplateRef) {
+        for (let child of node.roots) {
+          child.remove();
+        }
+      }
+      else {
+        node.remove();
       }
-      node.remove();
     }
   }
   CreateTextNode(text, root) {
     const node = document.createTextNode(text);
-    this.nodes[root] = node;
     this.stack.push(node);
+    this.SetNode(root, node);
   }
   CreateElement(tag, root) {
     const el = document.createElement(tag);
-    this.nodes[root] = el;
     this.stack.push(el);
+    this.SetNode(root, el);
   }
   CreateElementNs(tag, root, ns) {
     let el = document.createElementNS(ns, tag);
     this.stack.push(el);
-    this.nodes[root] = el;
+    this.SetNode(root, el);
   }
   CreatePlaceholder(root) {
     let el = document.createElement("pre");
     el.hidden = true;
     this.stack.push(el);
-    this.nodes[root] = el;
+    this.SetNode(root, el);
   }
   NewEventListener(event_name, root, handler, bubbles) {
-    const element = this.nodes[root];
-    element.setAttribute("data-dioxus-id", `${root}`);
+    const element = this.getId(root);
+    if (root >= templateIdLimit) {
+      let currentTemplateRefId = this.currentTemplateId();
+      root -= templateIdLimit;
+      element.setAttribute("data-dioxus-id", `${currentTemplateRefId},${root}`);
+    }
+    else {
+      element.setAttribute("data-dioxus-id", `${root}`);
+    }
     this.listeners.create(event_name, element, handler, bubbles);
   }
   RemoveEventListener(root, event_name, bubbles) {
-    const element = this.nodes[root];
+    const element = this.getId(root);
     element.removeAttribute(`data-dioxus-id`);
     this.listeners.remove(element, event_name, bubbles);
   }
   SetText(root, text) {
-    this.nodes[root].textContent = text;
+    this.getId(root).data = text;
   }
   SetAttribute(root, field, value, ns) {
     const name = field;
-    const node = this.nodes[root];
+    const node = this.getId(root);
     if (ns === "style") {
       // @ts-ignore
       node.style[name] = value;
@@ -189,7 +394,7 @@ export class Interpreter {
   }
   RemoveAttribute(root, field, ns) {
     const name = field;
-    const node = this.nodes[root];
+    const node = this.getId(root);
     if (ns == "style") {
       node.style.removeProperty(name);
     } else if (ns !== null || ns !== undefined) {
@@ -206,52 +411,99 @@ export class Interpreter {
       node.removeAttribute(name);
     }
   }
+  CreateTemplateRef(id, template_id) {
+    const el = this.templates[template_id].ref(id);
+    this.nodes[id] = el;
+    this.stack.push(el);
+  }
+  CreateTemplate(template_id) {
+    this.templateInProgress = template_id;
+    this.templates[template_id] = new Template(template_id, 0);
+  }
+  FinishTemplate(many) {
+    this.templates[this.templateInProgress].finalize(this.stack.splice(this.stack.length - many));
+    this.templateInProgress = null;
+  }
+  EnterTemplateRef(id) {
+    this.insideTemplateRef.push(this.nodes[id]);
+  }
+  ExitTemplateRef() {
+    this.insideTemplateRef.pop();
+  }
   handleEdits(edits) {
-    this.stack.push(this.root);
     for (let edit of edits) {
       this.handleEdit(edit);
     }
   }
+  CreateElementTemplate(tag, root, locally_static, fully_static) {
+    const el = document.createElement(tag);
+    this.stack.push(el);
+    this.SetNode(root, el);
+    if (!locally_static)
+      el.setAttribute("data-dioxus-dynamic", "true");
+    if (fully_static)
+      el.setAttribute("data-dioxus-fully-static", fully_static);
+  }
+  CreateElementNsTemplate(tag, root, ns, locally_static, fully_static) {
+    const el = document.createElementNS(ns, tag);
+    this.stack.push(el);
+    this.SetNode(root, el);
+    if (!locally_static)
+      el.setAttribute("data-dioxus-dynamic", "true");
+    if (fully_static)
+      el.setAttribute("data-dioxus-fully-static", fully_static);
+  }
+  CreateTextNodeTemplate(text, root, locally_static) {
+    const node = document.createTextNode(text);
+    this.stack.push(node);
+    this.SetNode(root, node);
+  }
+  CreatePlaceholderTemplate(root) {
+    const el = document.createElement("pre");
+    el.setAttribute("data-dioxus-dynamic", "true");
+    el.hidden = true;
+    this.stack.push(el);
+    this.SetNode(root, el);
+  }
   handleEdit(edit) {
     switch (edit.type) {
       case "PushRoot":
-        this.PushRoot(edit.root);
+        this.PushRoot(BigInt(edit.root));
         break;
       case "AppendChildren":
         this.AppendChildren(edit.many);
         break;
       case "ReplaceWith":
-        this.ReplaceWith(edit.root, edit.m);
+        this.ReplaceWith(BigInt(edit.root), edit.m);
         break;
       case "InsertAfter":
-        this.InsertAfter(edit.root, edit.n);
+        this.InsertAfter(BigInt(edit.root), edit.n);
         break;
       case "InsertBefore":
-        this.InsertBefore(edit.root, edit.n);
+        this.InsertBefore(BigInt(edit.root), edit.n);
         break;
       case "Remove":
-        this.Remove(edit.root);
+        this.Remove(BigInt(edit.root));
         break;
       case "CreateTextNode":
-        this.CreateTextNode(edit.text, edit.root);
+        this.CreateTextNode(edit.text, BigInt(edit.root));
         break;
       case "CreateElement":
-        this.CreateElement(edit.tag, edit.root);
+        this.CreateElement(edit.tag, BigInt(edit.root));
         break;
       case "CreateElementNs":
-        this.CreateElementNs(edit.tag, edit.root, edit.ns);
+        this.CreateElementNs(edit.tag, BigInt(edit.root), edit.ns);
         break;
       case "CreatePlaceholder":
-        this.CreatePlaceholder(edit.root);
+        this.CreatePlaceholder(BigInt(edit.root));
         break;
       case "RemoveEventListener":
-        this.RemoveEventListener(edit.root, edit.event_name);
+        this.RemoveEventListener(BigInt(edit.root), edit.event_name);
         break;
       case "NewEventListener":
         // this handler is only provided on desktop implementations since this
         // method is not used by the web implementation
         let handler = (event) => {
-
           let target = event.target;
           if (target != null) {
             let realId = target.getAttribute(`data-dioxus-id`);
@@ -330,26 +582,66 @@ export class Interpreter {
             if (realId === null) {
               return;
             }
+            if (realId.includes(",")) {
+              realId = realId.split(',');
+              realId = {
+                template_ref_id: parseInt(realId[0]),
+                template_node_id: parseInt(realId[1]),
+              };
+            }
+            else {
+              realId = parseInt(realId);
+            }
             window.ipc.postMessage(
               serializeIpcMessage("user_event", {
                 event: edit.event_name,
-                mounted_dom_id: parseInt(realId),
+                mounted_dom_id: realId,
                 contents: contents,
               })
             );
           }
         };
-        this.NewEventListener(edit.event_name, edit.root, handler, event_bubbles(edit.event_name));
+        this.NewEventListener(edit.event_name, BigInt(edit.root), handler, event_bubbles(edit.event_name));
 
         break;
       case "SetText":
-        this.SetText(edit.root, edit.text);
+        this.SetText(BigInt(edit.root), edit.text);
         break;
       case "SetAttribute":
-        this.SetAttribute(edit.root, edit.field, edit.value, edit.ns);
+        this.SetAttribute(BigInt(edit.root), edit.field, edit.value, edit.ns);
         break;
       case "RemoveAttribute":
-        this.RemoveAttribute(edit.root, edit.name, edit.ns);
+        this.RemoveAttribute(BigInt(edit.root), edit.name, edit.ns);
+        break;
+      case "PopRoot":
+        this.PopRoot();
+        break;
+      case "CreateTemplateRef":
+        this.CreateTemplateRef(BigInt(edit.id), edit.template_id);
+        break;
+      case "CreateTemplate":
+        this.CreateTemplate(BigInt(edit.id));
+        break;
+      case "FinishTemplate":
+        this.FinishTemplate(edit.len);
+        break;
+      case "EnterTemplateRef":
+        this.EnterTemplateRef(BigInt(edit.root));
+        break;
+      case "ExitTemplateRef":
+        this.ExitTemplateRef();
+        break;
+      case "CreateElementTemplate":
+        this.CreateElementTemplate(edit.tag, BigInt(edit.root), edit.locally_static, edit.fully_static);
+        break;
+      case "CreateElementNsTemplate":
+        this.CreateElementNsTemplate(edit.tag, BigInt(edit.root), edit.ns, edit.locally_static, edit.fully_static);
+        break;
+      case "CreateTextNodeTemplate":
+        this.CreateTextNodeTemplate(edit.text, BigInt(edit.root), edit.locally_static);
+        break;
+      case "CreatePlaceholderTemplate":
+        this.CreatePlaceholderTemplate(BigInt(edit.root));
         break;
     }
   }

+ 4 - 3
packages/liveview/src/events.rs

@@ -5,7 +5,8 @@
 use std::any::Any;
 use std::sync::Arc;
 
-use dioxus_core::{ElementId, EventPriority, UserEvent};
+use dioxus_core::GlobalNodeId;
+use dioxus_core::{EventPriority, UserEvent};
 use dioxus_html::event_bubbles;
 use dioxus_html::on::*;
 
@@ -25,7 +26,7 @@ pub(crate) fn parse_ipc_message(payload: &str) -> Option<IpcMessage> {
 #[derive(serde::Serialize, serde::Deserialize)]
 struct ImEvent {
     event: String,
-    mounted_dom_id: u64,
+    mounted_dom_id: GlobalNodeId,
     contents: serde_json::Value,
 }
 
@@ -36,7 +37,7 @@ pub fn trigger_from_serialized(val: serde_json::Value) -> UserEvent {
         contents,
     } = serde_json::from_value(val).unwrap();
 
-    let mounted_dom_id = Some(ElementId(mounted_dom_id as usize));
+    let mounted_dom_id = Some(mounted_dom_id);
 
     let name = event_name_from_type(&event);
     let event = make_synthetic_event(&event, contents);

+ 601 - 64
packages/liveview/src/interpreter.js

@@ -35,14 +35,186 @@ class IPC {
   }
 }
 
+// id > Number.MAX_SAFE_INTEGER/2 in template ref
+// id <= Number.MAX_SAFE_INTEGER/2 in global nodes
+const templateIdLimit = BigInt((Number.MAX_SAFE_INTEGER - 1) / 2);
+
+class TemplateRef {
+  constructor(fragment, dynamicNodePaths, roots, id) {
+    this.fragment = fragment;
+    this.dynamicNodePaths = dynamicNodePaths;
+    this.roots = roots;
+    this.id = id;
+    this.placed = false;
+    this.nodes = [];
+  }
+
+  build(id) {
+    if (!this.nodes[id]) {
+      let current = this.fragment;
+      const path = this.dynamicNodePaths[id];
+      for (let i = 0; i < path.length; i++) {
+        const idx = path[i];
+        current = current.firstChild;
+        for (let i2 = 0; i2 < idx; i2++) {
+          current = current.nextSibling;
+        }
+      }
+      this.nodes[id] = current;
+    }
+  }
+
+  get(id) {
+    this.build(id);
+    return this.nodes[id];
+  }
+
+  parent() {
+    return this.roots[0].parentNode;
+  }
+
+  first() {
+    return this.roots[0];
+  }
+
+  last() {
+    return this.roots[this.roots.length - 1];
+  }
+
+  move() {
+    // move the root nodes into a new template
+    this.fragment = new DocumentFragment();
+    for (let n of this.roots) {
+      this.fragment.appendChild(n);
+    }
+  }
+
+  getFragment() {
+    if (!this.placed) {
+      this.placed = true;
+    }
+    else {
+      this.move();
+    }
+    return this.fragment;
+  }
+}
+
+class Template {
+  constructor(template_id, id) {
+    this.nodes = [];
+    this.dynamicNodePaths = [];
+    this.template_id = template_id;
+    this.id = id;
+    this.template = document.createElement("template");
+    this.reconstructingRefrencesIndex = null;
+  }
+
+  finalize(roots) {
+    for (let i = 0; i < roots.length; i++) {
+      let node = roots[i];
+      let path = [i];
+      const is_element = node.nodeType == 1;
+      const locally_static = is_element && !node.hasAttribute("data-dioxus-dynamic");
+      if (!locally_static) {
+        this.dynamicNodePaths[node.tmplId] = [...path];
+      }
+      const traverse_children = is_element && !node.hasAttribute("data-dioxus-fully-static");
+      if (traverse_children) {
+        this.createIds(path, node);
+      }
+      this.template.content.appendChild(node);
+    }
+    document.head.appendChild(this.template);
+  }
+
+  createIds(path, root) {
+    let i = 0;
+    for (let node = root.firstChild; node != null; node = node.nextSibling) {
+      let new_path = [...path, i];
+      const is_element = node.nodeType == 1;
+      const locally_static = is_element && !node.hasAttribute("data-dioxus-dynamic");
+      if (!locally_static) {
+        this.dynamicNodePaths[node.tmplId] = [...new_path];
+      }
+      const traverse_children = is_element && !node.hasAttribute("data-dioxus-fully-static");
+      if (traverse_children) {
+        this.createIds(new_path, node);
+      }
+      i++;
+    }
+  }
+
+  ref(id) {
+    const template = this.template.content.cloneNode(true);
+    let roots = [];
+    this.reconstructingRefrencesIndex = 0;
+    for (let node = template.firstChild; node != null; node = node.nextSibling) {
+      roots.push(node);
+    }
+    return new TemplateRef(template, this.dynamicNodePaths, roots, id);
+  }
+}
+
+class ListenerMap {
+  constructor(root) {
+    // bubbling events can listen at the root element
+    this.global = {};
+    // non bubbling events listen at the element the listener was created at
+    this.local = {};
+    this.root = root;
+  }
+
+  create(event_name, element, handler, bubbles) {
+    if (bubbles) {
+      if (this.global[event_name] === undefined) {
+        this.global[event_name] = {};
+        this.global[event_name].active = 1;
+        this.global[event_name].callback = handler;
+        this.root.addEventListener(event_name, handler);
+      } else {
+        this.global[event_name].active++;
+      }
+    }
+    else {
+      const id = element.getAttribute("data-dioxus-id");
+      if (!this.local[id]) {
+        this.local[id] = {};
+      }
+      this.local[id][event_name] = handler;
+      element.addEventListener(event_name, handler);
+    }
+  }
+
+  remove(element, event_name, bubbles) {
+    if (bubbles) {
+      this.global[event_name].active--;
+      if (this.global[event_name].active === 0) {
+        this.root.removeEventListener(event_name, this.global[event_name].callback);
+        delete this.global[event_name];
+      }
+    }
+    else {
+      const id = element.getAttribute("data-dioxus-id");
+      delete this.local[id][event_name];
+      if (this.local[id].length === 0) {
+        delete this.local[id];
+      }
+      element.removeEventListener(event_name, handler);
+    }
+  }
+}
+
 class Interpreter {
   constructor(root) {
     this.root = root;
     this.stack = [root];
-    this.listeners = {};
+    this.templateInProgress = null;
+    this.insideTemplateRef = [];
+    this.listeners = new ListenerMap(root);
     this.handlers = {};
-    this.lastNodeWasText = false;
     this.nodes = [root];
+    this.templates = [];
   }
   top() {
     return this.stack[this.stack.length - 1];
@@ -50,88 +222,174 @@ class Interpreter {
   pop() {
     return this.stack.pop();
   }
+  currentTemplateId() {
+    if (this.insideTemplateRef.length) {
+      return this.insideTemplateRef[this.insideTemplateRef.length - 1].id;
+    }
+    else {
+      return null;
+    }
+  }
+  getId(id) {
+    if (this.templateInProgress !== null) {
+      return this.templates[this.templateInProgress].nodes[id - templateIdLimit];
+    }
+    else if (this.insideTemplateRef.length && id >= templateIdLimit) {
+      return this.insideTemplateRef[this.insideTemplateRef.length - 1].get(id - templateIdLimit);
+    }
+    else {
+      return this.nodes[id];
+    }
+  }
+  SetNode(id, node) {
+    if (this.templateInProgress !== null) {
+      id -= templateIdLimit;
+      node.tmplId = id;
+      this.templates[this.templateInProgress].nodes[id] = node;
+    }
+    else if (this.insideTemplateRef.length && id >= templateIdLimit) {
+      id -= templateIdLimit;
+      let last = this.insideTemplateRef[this.insideTemplateRef.length - 1];
+      last.childNodes[id] = node;
+      if (last.nodeCache[id]) {
+        last.nodeCache[id] = node;
+      }
+    }
+    else {
+      this.nodes[id] = node;
+    }
+  }
   PushRoot(root) {
-    const node = this.nodes[root];
+    const node = this.getId(root);
     this.stack.push(node);
   }
+  PopRoot() {
+    this.stack.pop();
+  }
   AppendChildren(many) {
     let root = this.stack[this.stack.length - (1 + many)];
     let to_add = this.stack.splice(this.stack.length - many);
     for (let i = 0; i < many; i++) {
-      root.appendChild(to_add[i]);
+      const child = to_add[i];
+      if (child instanceof TemplateRef) {
+        root.appendChild(child.getFragment());
+      }
+      else {
+        root.appendChild(child);
+      }
     }
   }
   ReplaceWith(root_id, m) {
-    let root = this.nodes[root_id];
-    let els = this.stack.splice(this.stack.length - m);
-    root.replaceWith(...els);
+    let root = this.getId(root_id);
+    if (root instanceof TemplateRef) {
+      this.InsertBefore(root_id, m);
+      this.Remove(root_id);
+    }
+    else {
+      let els = this.stack.splice(this.stack.length - m).map(function (el) {
+        if (el instanceof TemplateRef) {
+          return el.getFragment();
+        }
+        else {
+          return el;
+        }
+      });
+      root.replaceWith(...els);
+    }
   }
   InsertAfter(root, n) {
-    let old = this.nodes[root];
-    let new_nodes = this.stack.splice(this.stack.length - n);
-    old.after(...new_nodes);
+    const old = this.getId(root);
+    const new_nodes = this.stack.splice(this.stack.length - n).map(function (el) {
+      if (el instanceof TemplateRef) {
+        return el.getFragment();
+      }
+      else {
+        return el;
+      }
+    });
+    if (old instanceof TemplateRef) {
+      const last = old.last();
+      last.after(...new_nodes);
+    }
+    else {
+      old.after(...new_nodes);
+    }
   }
   InsertBefore(root, n) {
-    let old = this.nodes[root];
-    let new_nodes = this.stack.splice(this.stack.length - n);
-    old.before(...new_nodes);
+    const old = this.getId(root);
+    const new_nodes = this.stack.splice(this.stack.length - n).map(function (el) {
+      if (el instanceof TemplateRef) {
+        return el.getFragment();
+      }
+      else {
+        return el;
+      }
+    });
+    if (old instanceof TemplateRef) {
+      const first = old.first();
+      first.before(...new_nodes);
+    }
+    else {
+      old.before(...new_nodes);
+    }
   }
   Remove(root) {
-    let node = this.nodes[root];
+    let node = this.getId(root);
     if (node !== undefined) {
-      node.remove();
+      if (node instanceof TemplateRef) {
+        for (let child of node.roots) {
+          child.remove();
+        }
+      }
+      else {
+        node.remove();
+      }
     }
   }
   CreateTextNode(text, root) {
-    // todo: make it so the types are okay
     const node = document.createTextNode(text);
-    this.nodes[root] = node;
     this.stack.push(node);
+    this.SetNode(root, node);
   }
   CreateElement(tag, root) {
     const el = document.createElement(tag);
-    // el.setAttribute("data-dioxus-id", `${root}`);
-    this.nodes[root] = el;
     this.stack.push(el);
+    this.SetNode(root, el);
   }
   CreateElementNs(tag, root, ns) {
     let el = document.createElementNS(ns, tag);
     this.stack.push(el);
-    this.nodes[root] = el;
+    this.SetNode(root, el);
   }
   CreatePlaceholder(root) {
     let el = document.createElement("pre");
     el.hidden = true;
     this.stack.push(el);
-    this.nodes[root] = el;
-  }
-  NewEventListener(event_name, root, handler) {
-    const element = this.nodes[root];
-    element.setAttribute("data-dioxus-id", `${root}`);
-    if (this.listeners[event_name] === undefined) {
-      this.listeners[event_name] = 0;
-      this.handlers[event_name] = handler;
-      this.root.addEventListener(event_name, handler);
-    } else {
-      this.listeners[event_name]++;
+    this.SetNode(root, el);
+  }
+  NewEventListener(event_name, root, handler, bubbles) {
+    const element = this.getId(root);
+    if (root >= templateIdLimit) {
+      let currentTemplateRefId = this.currentTemplateId();
+      root -= templateIdLimit;
+      element.setAttribute("data-dioxus-id", `${currentTemplateRefId},${root}`);
+    }
+    else {
+      element.setAttribute("data-dioxus-id", `${root}`);
     }
+    this.listeners.create(event_name, element, handler, bubbles);
   }
-  RemoveEventListener(root, event_name) {
-    const element = this.nodes[root];
+  RemoveEventListener(root, event_name, bubbles) {
+    const element = this.getId(root);
     element.removeAttribute(`data-dioxus-id`);
-    this.listeners[event_name]--;
-    if (this.listeners[event_name] === 0) {
-      this.root.removeEventListener(event_name, this.handlers[event_name]);
-      delete this.listeners[event_name];
-      delete this.handlers[event_name];
-    }
+    this.listeners.remove(element, event_name, bubbles);
   }
   SetText(root, text) {
-    this.nodes[root].textContent = text;
+    this.getId(root).data = text;
   }
   SetAttribute(root, field, value, ns) {
     const name = field;
-    const node = this.nodes[root];
+    const node = this.getId(root);
     if (ns === "style") {
       // @ts-ignore
       node.style[name] = value;
@@ -163,10 +421,14 @@ class Interpreter {
       }
     }
   }
-  RemoveAttribute(root, name) {
-    const node = this.nodes[root];
-
-    if (name === "value") {
+  RemoveAttribute(root, field, ns) {
+    const name = field;
+    const node = this.getId(root);
+    if (ns == "style") {
+      node.style.removeProperty(name);
+    } else if (ns !== null || ns !== undefined) {
+      node.removeAttributeNS(ns, name);
+    } else if (name === "value") {
       node.value = "";
     } else if (name === "checked") {
       node.checked = false;
@@ -178,46 +440,94 @@ class Interpreter {
       node.removeAttribute(name);
     }
   }
+  CreateTemplateRef(id, template_id) {
+    const el = this.templates[template_id].ref(id);
+    this.nodes[id] = el;
+    this.stack.push(el);
+  }
+  CreateTemplate(template_id) {
+    this.templateInProgress = template_id;
+    this.templates[template_id] = new Template(template_id, 0);
+  }
+  FinishTemplate(many) {
+    this.templates[this.templateInProgress].finalize(this.stack.splice(this.stack.length - many));
+    this.templateInProgress = null;
+  }
+  EnterTemplateRef(id) {
+    this.insideTemplateRef.push(this.nodes[id]);
+  }
+  ExitTemplateRef() {
+    this.insideTemplateRef.pop();
+  }
   handleEdits(edits) {
-    this.stack.push(this.root);
     for (let edit of edits) {
       this.handleEdit(edit);
     }
   }
+  CreateElementTemplate(tag, root, locally_static, fully_static) {
+    const el = document.createElement(tag);
+    this.stack.push(el);
+    this.SetNode(root, el);
+    if (!locally_static)
+      el.setAttribute("data-dioxus-dynamic", "true");
+    if (fully_static)
+      el.setAttribute("data-dioxus-fully-static", fully_static);
+  }
+  CreateElementNsTemplate(tag, root, ns, locally_static, fully_static) {
+    const el = document.createElementNS(ns, tag);
+    this.stack.push(el);
+    this.SetNode(root, el);
+    if (!locally_static)
+      el.setAttribute("data-dioxus-dynamic", "true");
+    if (fully_static)
+      el.setAttribute("data-dioxus-fully-static", fully_static);
+  }
+  CreateTextNodeTemplate(text, root, locally_static) {
+    const node = document.createTextNode(text);
+    this.stack.push(node);
+    this.SetNode(root, node);
+  }
+  CreatePlaceholderTemplate(root) {
+    const el = document.createElement("pre");
+    el.setAttribute("data-dioxus-dynamic", "true");
+    el.hidden = true;
+    this.stack.push(el);
+    this.SetNode(root, el);
+  }
   handleEdit(edit) {
     switch (edit.type) {
       case "PushRoot":
-        this.PushRoot(edit.root);
+        this.PushRoot(BigInt(edit.root));
         break;
       case "AppendChildren":
         this.AppendChildren(edit.many);
         break;
       case "ReplaceWith":
-        this.ReplaceWith(edit.root, edit.m);
+        this.ReplaceWith(BigInt(edit.root), edit.m);
         break;
       case "InsertAfter":
-        this.InsertAfter(edit.root, edit.n);
+        this.InsertAfter(BigInt(edit.root), edit.n);
         break;
       case "InsertBefore":
-        this.InsertBefore(edit.root, edit.n);
+        this.InsertBefore(BigInt(edit.root), edit.n);
         break;
       case "Remove":
-        this.Remove(edit.root);
+        this.Remove(BigInt(edit.root));
         break;
       case "CreateTextNode":
-        this.CreateTextNode(edit.text, edit.root);
+        this.CreateTextNode(edit.text, BigInt(edit.root));
         break;
       case "CreateElement":
-        this.CreateElement(edit.tag, edit.root);
+        this.CreateElement(edit.tag, BigInt(edit.root));
         break;
       case "CreateElementNs":
-        this.CreateElementNs(edit.tag, edit.root, edit.ns);
+        this.CreateElementNs(edit.tag, BigInt(edit.root), edit.ns);
         break;
       case "CreatePlaceholder":
-        this.CreatePlaceholder(edit.root);
+        this.CreatePlaceholder(BigInt(edit.root));
         break;
       case "RemoveEventListener":
-        this.RemoveEventListener(edit.root, edit.event_name);
+        this.RemoveEventListener(BigInt(edit.root), edit.event_name);
         break;
       case "NewEventListener":
         // this handler is only provided on desktop implementations since this
@@ -245,7 +555,7 @@ class Interpreter {
               }
 
               // also prevent buttons from submitting
-              if (target.tagName === "BUTTON") {
+              if (target.tagName === "BUTTON" && event.type == "submit") {
                 event.preventDefault();
               }
             }
@@ -269,11 +579,15 @@ class Interpreter {
             if (shouldPreventDefault === `on${event.type}`) {
               event.preventDefault();
             }
+
             if (event.type === "submit") {
               event.preventDefault();
             }
 
-            if (target.tagName === "FORM") {
+            if (
+              target.tagName === "FORM" &&
+              (event.type === "submit" || event.type === "input")
+            ) {
               for (let x = 0; x < target.elements.length; x++) {
                 let element = target.elements[x];
                 let name = element.getAttribute("name");
@@ -281,6 +595,10 @@ class Interpreter {
                   if (element.getAttribute("type") === "checkbox") {
                     // @ts-ignore
                     contents.values[name] = element.checked ? "true" : "false";
+                  } else if (element.getAttribute("type") === "radio") {
+                    if (element.checked) {
+                      contents.values[name] = element.value;
+                    }
                   } else {
                     // @ts-ignore
                     contents.values[name] =
@@ -290,28 +608,69 @@ class Interpreter {
               }
             }
 
-            if (realId == null) {
+            if (realId === null) {
               return;
             }
+            if (realId.includes(",")) {
+              realId = realId.split(',');
+              realId = {
+                template_ref_id: parseInt(realId[0]),
+                template_node_id: parseInt(realId[1]),
+              };
+            }
+            else {
+              realId = parseInt(realId);
+            }
             window.ipc.send(
               serializeIpcMessage("user_event", {
                 event: edit.event_name,
-                mounted_dom_id: parseInt(realId),
+                mounted_dom_id: realId,
                 contents: contents,
               })
             );
           }
         };
-        this.NewEventListener(edit.event_name, edit.root, handler);
+        this.NewEventListener(edit.event_name, BigInt(edit.root), handler, event_bubbles(edit.event_name));
+
         break;
       case "SetText":
-        this.SetText(edit.root, edit.text);
+        this.SetText(BigInt(edit.root), edit.text);
         break;
       case "SetAttribute":
-        this.SetAttribute(edit.root, edit.field, edit.value, edit.ns);
+        this.SetAttribute(BigInt(edit.root), edit.field, edit.value, edit.ns);
         break;
       case "RemoveAttribute":
-        this.RemoveAttribute(edit.root, edit.name);
+        this.RemoveAttribute(BigInt(edit.root), edit.name, edit.ns);
+        break;
+      case "PopRoot":
+        this.PopRoot();
+        break;
+      case "CreateTemplateRef":
+        this.CreateTemplateRef(BigInt(edit.id), edit.template_id);
+        break;
+      case "CreateTemplate":
+        this.CreateTemplate(BigInt(edit.id));
+        break;
+      case "FinishTemplate":
+        this.FinishTemplate(edit.len);
+        break;
+      case "EnterTemplateRef":
+        this.EnterTemplateRef(BigInt(edit.root));
+        break;
+      case "ExitTemplateRef":
+        this.ExitTemplateRef();
+        break;
+      case "CreateElementTemplate":
+        this.CreateElementTemplate(edit.tag, BigInt(edit.root), edit.locally_static, edit.fully_static);
+        break;
+      case "CreateElementNsTemplate":
+        this.CreateElementNsTemplate(edit.tag, BigInt(edit.root), edit.ns, edit.locally_static, edit.fully_static);
+        break;
+      case "CreateTextNodeTemplate":
+        this.CreateTextNodeTemplate(edit.text, BigInt(edit.root), edit.locally_static);
+        break;
+      case "CreatePlaceholderTemplate":
+        this.CreatePlaceholderTemplate(BigInt(edit.root));
         break;
     }
   }
@@ -346,6 +705,7 @@ function serialize_event(event) {
         location,
         repeat,
         which,
+        code,
       } = event;
       return {
         char_code: charCode,
@@ -358,6 +718,7 @@ function serialize_event(event) {
         location: location,
         repeat: repeat,
         which: which,
+        code,
       };
     }
     case "focus":
@@ -383,9 +744,11 @@ function serialize_event(event) {
     case "submit": {
       let target = event.target;
       let value = target.value ?? target.textContent;
+
       if (target.type === "checkbox") {
         value = target.checked ? "true" : "false";
       }
+
       return {
         value: value,
         values: {},
@@ -394,6 +757,7 @@ function serialize_event(event) {
     case "click":
     case "contextmenu":
     case "doubleclick":
+    case "dblclick":
     case "drag":
     case "dragend":
     case "dragenter":
@@ -613,3 +977,176 @@ const bool_attrs = {
   selected: true,
   truespeed: true,
 };
+
+function is_element_node(node) {
+  return node.nodeType == 1;
+}
+
+function event_bubbles(event) {
+  switch (event) {
+    case "copy":
+      return true;
+    case "cut":
+      return true;
+    case "paste":
+      return true;
+    case "compositionend":
+      return true;
+    case "compositionstart":
+      return true;
+    case "compositionupdate":
+      return true;
+    case "keydown":
+      return true;
+    case "keypress":
+      return true;
+    case "keyup":
+      return true;
+    case "focus":
+      return false;
+    case "focusout":
+      return true;
+    case "focusin":
+      return true;
+    case "blur":
+      return false;
+    case "change":
+      return true;
+    case "input":
+      return true;
+    case "invalid":
+      return true;
+    case "reset":
+      return true;
+    case "submit":
+      return true;
+    case "click":
+      return true;
+    case "contextmenu":
+      return true;
+    case "doubleclick":
+      return true;
+    case "dblclick":
+      return true;
+    case "drag":
+      return true;
+    case "dragend":
+      return true;
+    case "dragenter":
+      return false;
+    case "dragexit":
+      return false;
+    case "dragleave":
+      return true;
+    case "dragover":
+      return true;
+    case "dragstart":
+      return true;
+    case "drop":
+      return true;
+    case "mousedown":
+      return true;
+    case "mouseenter":
+      return false;
+    case "mouseleave":
+      return false;
+    case "mousemove":
+      return true;
+    case "mouseout":
+      return true;
+    case "scroll":
+      return false;
+    case "mouseover":
+      return true;
+    case "mouseup":
+      return true;
+    case "pointerdown":
+      return true;
+    case "pointermove":
+      return true;
+    case "pointerup":
+      return true;
+    case "pointercancel":
+      return true;
+    case "gotpointercapture":
+      return true;
+    case "lostpointercapture":
+      return true;
+    case "pointerenter":
+      return false;
+    case "pointerleave":
+      return false;
+    case "pointerover":
+      return true;
+    case "pointerout":
+      return true;
+    case "select":
+      return true;
+    case "touchcancel":
+      return true;
+    case "touchend":
+      return true;
+    case "touchmove":
+      return true;
+    case "touchstart":
+      return true;
+    case "wheel":
+      return true;
+    case "abort":
+      return false;
+    case "canplay":
+      return false;
+    case "canplaythrough":
+      return false;
+    case "durationchange":
+      return false;
+    case "emptied":
+      return false;
+    case "encrypted":
+      return true;
+    case "ended":
+      return false;
+    case "error":
+      return false;
+    case "loadeddata":
+      return false;
+    case "loadedmetadata":
+      return false;
+    case "loadstart":
+      return false;
+    case "pause":
+      return false;
+    case "play":
+      return false;
+    case "playing":
+      return false;
+    case "progress":
+      return false;
+    case "ratechange":
+      return false;
+    case "seeked":
+      return false;
+    case "seeking":
+      return false;
+    case "stalled":
+      return false;
+    case "suspend":
+      return false;
+    case "timeupdate":
+      return false;
+    case "volumechange":
+      return false;
+    case "waiting":
+      return false;
+    case "animationstart":
+      return true;
+    case "animationend":
+      return true;
+    case "animationiteration":
+      return true;
+    case "transitionend":
+      return true;
+    case "toggle":
+      return true;
+  }
+}

+ 33 - 14
packages/native-core-macro/src/lib.rs

@@ -164,20 +164,20 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream {
 
             let gen = quote! {
                 impl State for #type_name {
-                    fn update<'a, T: dioxus_native_core::traversable::Traversable<Node = Self, Id = dioxus_core::ElementId>>(
-                        dirty: &[(dioxus_core::ElementId, dioxus_native_core::node_ref::NodeMask)],
+                    fn update<'a, T: dioxus_native_core::traversable::Traversable<Node = Self, Id = dioxus_core::GlobalNodeId>,T2: dioxus_native_core::traversable::Traversable<Node = dioxus_native_core::real_dom::NodeData, Id = dioxus_core::GlobalNodeId>>(
+                        dirty: &[(dioxus_core::GlobalNodeId, dioxus_native_core::node_ref::NodeMask)],
                         state_tree: &'a mut T,
-                        vdom: &'a dioxus_core::VirtualDom,
+                        rdom: &'a T2,
                         ctx: &anymap::AnyMap,
-                    ) -> fxhash::FxHashSet<dioxus_core::ElementId>{
+                    ) -> fxhash::FxHashSet<dioxus_core::GlobalNodeId>{
                         #[derive(Eq, PartialEq)]
                         struct HeightOrdering {
                             height: u16,
-                            id: dioxus_core::ElementId,
+                            id: dioxus_core::GlobalNodeId,
                         }
 
                         impl HeightOrdering {
-                            fn new(height: u16, id: dioxus_core::ElementId) -> Self {
+                            fn new(height: u16, id: dioxus_core::GlobalNodeId) -> Self {
                                 HeightOrdering {
                                     height,
                                     id,
@@ -185,9 +185,29 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream {
                             }
                         }
 
+                        // not the ordering after height is just for deduplication it can be any ordering as long as it is consistent
                         impl Ord for HeightOrdering {
                             fn cmp(&self, other: &Self) -> std::cmp::Ordering {
-                                self.height.cmp(&other.height).then(self.id.0.cmp(&other.id.0))
+                                self.height.cmp(&other.height).then(match (self.id, other.id){
+                                    (
+                                        dioxus_core::GlobalNodeId::TemplateId {
+                                            template_ref_id,
+                                            template_node_id,
+                                        },
+                                        dioxus_core::GlobalNodeId::TemplateId {
+                                            template_ref_id: o_template_ref_id,
+                                            template_node_id: o_template_node_id,
+                                        },
+                                    ) => template_ref_id
+                                        .0
+                                        .cmp(&o_template_ref_id.0)
+                                        .then(template_node_id.0.cmp(&o_template_node_id.0)),
+                                    (dioxus_core::GlobalNodeId::TemplateId { .. }, dioxus_core::GlobalNodeId::VNodeId(_)) => std::cmp::Ordering::Less,
+                                    (dioxus_core::GlobalNodeId::VNodeId(_), dioxus_core::GlobalNodeId::TemplateId { .. }) => {
+                                        std::cmp::Ordering::Greater
+                                    }
+                                    (dioxus_core::GlobalNodeId::VNodeId(s_id), dioxus_core::GlobalNodeId::VNodeId(o_id)) => s_id.0.cmp(&o_id.0),
+                                })
                             }
                         }
 
@@ -218,7 +238,7 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream {
 
                         let mut dirty_elements = fxhash::FxHashSet::default();
                         // the states of any elements that are dirty
-                        let mut states: fxhash::FxHashMap<dioxus_core::ElementId, MembersDirty> = fxhash::FxHashMap::default();
+                        let mut states: fxhash::FxHashMap<dioxus_core::GlobalNodeId, MembersDirty> = fxhash::FxHashMap::default();
 
                         for (id, mask) in dirty {
                             let members_dirty = MembersDirty {
@@ -244,7 +264,7 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream {
                                 <#child_types as dioxus_native_core::state::State>::update(
                                     dirty,
                                     &mut state_tree.map(|n| &n.#child_members, |n| &mut n.#child_members),
-                                    vdom,
+                                    rdom,
                                     ctx,
                                 )
                             );
@@ -493,7 +513,7 @@ impl<'a> StateStruct<'a> {
                     let mut i = 0;
                     while i < resolution_order.len(){
                         let id = resolution_order[i].id;
-                        let vnode = vdom.get_element(id).unwrap();
+                        let node = rdom.get(id).unwrap();
                         let members_dirty = states.get_mut(&id).unwrap();
                         let (current_state, parent) = state_tree.get_node_parent_mut(id);
                         let current_state = current_state.unwrap();
@@ -515,7 +535,7 @@ impl<'a> StateStruct<'a> {
                     let mut i = 0;
                     while i < resolution_order.len(){
                         let id = resolution_order[i].id;
-                        let vnode = vdom.get_element(id).unwrap();
+                        let node = rdom.get(id).unwrap();
                         let members_dirty = states.get_mut(&id).unwrap();
                         let (current_state, children) = state_tree.get_node_children_mut(id);
                         let current_state = current_state.unwrap();
@@ -534,7 +554,7 @@ impl<'a> StateStruct<'a> {
                     let mut i = 0;
                     while i < resolution_order.len(){
                         let id = resolution_order[i];
-                        let vnode = vdom.get_element(id).unwrap();
+                        let node = rdom.get(id).unwrap();
                         let members_dirty = states.get_mut(&id).unwrap();
                         let current_state = state_tree.get_mut(id).unwrap();
                         if members_dirty.#member && #reduce_member {
@@ -679,8 +699,7 @@ impl<'a> StateMember<'a> {
         };
 
         let ty = &self.mem.ty;
-        let node_view =
-            quote!(dioxus_native_core::node_ref::NodeView::new(vnode, #ty::NODE_MASK, vdom));
+        let node_view = quote!(dioxus_native_core::node_ref::NodeView::new(node, #ty::NODE_MASK));
         let dep_idents = self.dep_mems.iter().map(|m| &m.0.ident);
         match self.dep_kind {
             DependencyKind::Node => {

+ 1 - 1
packages/native-core-macro/tests/called_minimally_on_build.rs

@@ -109,7 +109,7 @@ macro_rules! test_state{
             let mut dom: RealDom<$s> = RealDom::new();
 
             let nodes_updated = dom.apply_mutations(vec![mutations]);
-            let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new());
+            let _to_rerender = dom.update_state(nodes_updated, AnyMap::new());
 
             dom.traverse_depth_first(|n| {
                 $(

+ 75 - 107
packages/native-core-macro/tests/change_nodes.rs

@@ -1,10 +1,8 @@
-use dioxus::core as dioxus_core;
-use dioxus::core::{ElementId, VElement};
+use dioxus::core::{self as dioxus_core, GlobalNodeId};
 use dioxus::prelude::*;
 use dioxus_native_core::real_dom::RealDom;
 use dioxus_native_core::state::State;
 use dioxus_native_core_macro::State;
-use std::cell::Cell;
 
 #[derive(State, Default, Clone)]
 struct Empty {}
@@ -18,44 +16,8 @@ fn remove_node() {
 
     let vdom = VirtualDom::new(Base);
 
-    let mutations = vdom.create_vnodes(rsx! {
-        div{
-            div{}
-        }
-    });
-
     let mut dom: RealDom<Empty> = RealDom::new();
-
-    let _to_update = dom.apply_mutations(vec![mutations]);
-    let child_div = VElement {
-        id: Cell::new(Some(ElementId(2))),
-        key: None,
-        tag: "div",
-        namespace: None,
-        parent: Cell::new(Some(ElementId(1))),
-        listeners: &[],
-        attributes: &[],
-        children: &[],
-    };
-    let child_div_el = VNode::Element(&child_div);
-    let root_div = VElement {
-        id: Cell::new(Some(ElementId(1))),
-        key: None,
-        tag: "div",
-        namespace: None,
-        parent: Cell::new(Some(ElementId(0))),
-        listeners: &[],
-        attributes: &[],
-        children: &[child_div_el],
-    };
-
-    assert_eq!(dom.size(), 2);
-    assert!(&dom.contains_node(&VNode::Element(&root_div)));
-    assert_eq!(dom[ElementId(1)].height, 1);
-    assert_eq!(dom[ElementId(2)].height, 2);
-
-    let vdom = VirtualDom::new(Base);
-    let mutations = vdom.diff_lazynodes(
+    let (create, edit) = vdom.diff_lazynodes(
         rsx! {
             div{
                 div{}
@@ -65,22 +27,43 @@ fn remove_node() {
             div{}
         },
     );
-    dom.apply_mutations(vec![mutations.1]);
-
-    let new_root_div = VElement {
-        id: Cell::new(Some(ElementId(1))),
-        key: None,
-        tag: "div",
-        namespace: None,
-        parent: Cell::new(Some(ElementId(0))),
-        listeners: &[],
-        attributes: &[],
-        children: &[],
-    };
+
+    println!("create: {:#?}", create);
+    println!("edit: {:#?}", edit);
+
+    let _to_update = dom.apply_mutations(vec![create]);
+
+    assert_eq!(
+        dom[GlobalNodeId::TemplateId {
+            template_ref_id: dioxus_core::ElementId(1),
+            template_node_id: dioxus::prelude::TemplateNodeId(0),
+        }]
+        .node_data
+        .height,
+        1
+    );
+    assert_eq!(
+        dom[GlobalNodeId::TemplateId {
+            template_ref_id: dioxus_core::ElementId(1),
+            template_node_id: dioxus::prelude::TemplateNodeId(1),
+        }]
+        .node_data
+        .height,
+        2
+    );
+
+    dom.apply_mutations(vec![edit]);
 
     assert_eq!(dom.size(), 1);
-    assert!(&dom.contains_node(&VNode::Element(&new_root_div)));
-    assert_eq!(dom[ElementId(1)].height, 1);
+    assert_eq!(
+        dom[GlobalNodeId::TemplateId {
+            template_ref_id: dioxus_core::ElementId(2),
+            template_node_id: dioxus::prelude::TemplateNodeId(0),
+        }]
+        .node_data
+        .height,
+        1
+    );
 }
 
 #[test]
@@ -92,31 +75,7 @@ fn add_node() {
 
     let vdom = VirtualDom::new(Base);
 
-    let mutations = vdom.create_vnodes(rsx! {
-        div{}
-    });
-
-    let mut dom: RealDom<Empty> = RealDom::new();
-
-    let _to_update = dom.apply_mutations(vec![mutations]);
-
-    let root_div = VElement {
-        id: Cell::new(Some(ElementId(1))),
-        key: None,
-        tag: "div",
-        namespace: None,
-        parent: Cell::new(Some(ElementId(0))),
-        listeners: &[],
-        attributes: &[],
-        children: &[],
-    };
-
-    assert_eq!(dom.size(), 1);
-    assert!(&dom.contains_node(&VNode::Element(&root_div)));
-    assert_eq!(dom[ElementId(1)].height, 1);
-
-    let vdom = VirtualDom::new(Base);
-    let mutations = vdom.diff_lazynodes(
+    let (create, update) = vdom.diff_lazynodes(
         rsx! {
             div{}
         },
@@ -126,32 +85,41 @@ fn add_node() {
             }
         },
     );
-    dom.apply_mutations(vec![mutations.1]);
-
-    let child_div = VElement {
-        id: Cell::new(Some(ElementId(2))),
-        key: None,
-        tag: "p",
-        namespace: None,
-        parent: Cell::new(Some(ElementId(1))),
-        listeners: &[],
-        attributes: &[],
-        children: &[],
-    };
-    let child_div_el = VNode::Element(&child_div);
-    let new_root_div = VElement {
-        id: Cell::new(Some(ElementId(1))),
-        key: None,
-        tag: "div",
-        namespace: None,
-        parent: Cell::new(Some(ElementId(0))),
-        listeners: &[],
-        attributes: &[],
-        children: &[child_div_el],
-    };
-
-    assert_eq!(dom.size(), 2);
-    assert!(&dom.contains_node(&VNode::Element(&new_root_div)));
-    assert_eq!(dom[ElementId(1)].height, 1);
-    assert_eq!(dom[ElementId(2)].height, 2);
+
+    let mut dom: RealDom<Empty> = RealDom::new();
+
+    let _to_update = dom.apply_mutations(vec![create]);
+
+    assert_eq!(dom.size(), 1);
+    assert_eq!(
+        dom[GlobalNodeId::TemplateId {
+            template_ref_id: dioxus_core::ElementId(1),
+            template_node_id: dioxus::prelude::TemplateNodeId(0),
+        }]
+        .node_data
+        .height,
+        1
+    );
+
+    dom.apply_mutations(vec![update]);
+
+    assert_eq!(dom.size(), 1);
+    assert_eq!(
+        dom[GlobalNodeId::TemplateId {
+            template_ref_id: dioxus_core::ElementId(2),
+            template_node_id: dioxus::prelude::TemplateNodeId(0),
+        }]
+        .node_data
+        .height,
+        1
+    );
+    assert_eq!(
+        dom[GlobalNodeId::TemplateId {
+            template_ref_id: dioxus_core::ElementId(2),
+            template_node_id: dioxus::prelude::TemplateNodeId(1),
+        }]
+        .node_data
+        .height,
+        2
+    );
 }

+ 66 - 76
packages/native-core-macro/tests/initial_build.rs

@@ -1,7 +1,4 @@
-use std::cell::Cell;
-
-use dioxus::core as dioxus_core;
-use dioxus::core::{ElementId, VElement, VText};
+use dioxus::core::{self as dioxus_core, GlobalNodeId};
 use dioxus::prelude::*;
 use dioxus_native_core::real_dom::RealDom;
 use dioxus_native_core::state::State;
@@ -12,8 +9,6 @@ struct Empty {}
 
 #[test]
 fn initial_build_simple() {
-    use std::cell::Cell;
-
     #[allow(non_snake_case)]
     fn Base(cx: Scope) -> Element {
         render!(div {})
@@ -28,19 +23,17 @@ fn initial_build_simple() {
     let mut dom: RealDom<Empty> = RealDom::new();
 
     let _to_update = dom.apply_mutations(vec![mutations]);
-    let root_div = VElement {
-        id: Cell::new(Some(ElementId(1))),
-        key: None,
-        tag: "div",
-        namespace: None,
-        parent: Cell::new(Some(ElementId(0))),
-        listeners: &[],
-        attributes: &[],
-        children: &[],
-    };
+
     assert_eq!(dom.size(), 1);
-    assert!(&dom.contains_node(&VNode::Element(&root_div)));
-    assert_eq!(dom[ElementId(1)].height, 1);
+    assert_eq!(
+        dom[GlobalNodeId::TemplateId {
+            template_ref_id: dioxus_core::ElementId(1),
+            template_node_id: dioxus::prelude::TemplateNodeId(0),
+        }]
+        .node_data
+        .height,
+        1
+    );
 }
 
 #[test]
@@ -67,62 +60,59 @@ fn initial_build_with_children() {
     let mut dom: RealDom<Empty> = RealDom::new();
 
     let _to_update = dom.apply_mutations(vec![mutations]);
-    let first_text = VText {
-        id: Cell::new(Some(ElementId(3))),
-        text: "hello",
-        is_static: true,
-    };
-    let first_text_node = VNode::Text(&first_text);
-    let child_text = VText {
-        id: Cell::new(Some(ElementId(5))),
-        text: "world",
-        is_static: true,
-    };
-    let child_text_node = VNode::Text(&child_text);
-    let child_p_el = VElement {
-        id: Cell::new(Some(ElementId(4))),
-        key: None,
-        tag: "p",
-        namespace: None,
-        parent: Cell::new(Some(ElementId(2))),
-        listeners: &[],
-        attributes: &[],
-        children: &[child_text_node],
-    };
-    let child_p_node = VNode::Element(&child_p_el);
-    let second_text = VText {
-        id: Cell::new(Some(ElementId(6))),
-        text: "hello world",
-        is_static: true,
-    };
-    let second_text_node = VNode::Text(&second_text);
-    let child_div_el = VElement {
-        id: Cell::new(Some(ElementId(2))),
-        key: None,
-        tag: "div",
-        namespace: None,
-        parent: Cell::new(Some(ElementId(1))),
-        listeners: &[],
-        attributes: &[],
-        children: &[first_text_node, child_p_node, second_text_node],
-    };
-    let child_div_node = VNode::Element(&child_div_el);
-    let root_div = VElement {
-        id: Cell::new(Some(ElementId(1))),
-        key: None,
-        tag: "div",
-        namespace: None,
-        parent: Cell::new(Some(ElementId(0))),
-        listeners: &[],
-        attributes: &[],
-        children: &[child_div_node],
-    };
-    assert_eq!(dom.size(), 6);
-    assert!(&dom.contains_node(&VNode::Element(&root_div)));
-    assert_eq!(dom[ElementId(1)].height, 1);
-    assert_eq!(dom[ElementId(2)].height, 2);
-    assert_eq!(dom[ElementId(3)].height, 3);
-    assert_eq!(dom[ElementId(4)].height, 3);
-    assert_eq!(dom[ElementId(5)].height, 4);
-    assert_eq!(dom[ElementId(6)].height, 3);
+    assert_eq!(dom.size(), 1);
+    assert_eq!(
+        dom[GlobalNodeId::TemplateId {
+            template_ref_id: dioxus_core::ElementId(1),
+            template_node_id: dioxus::prelude::TemplateNodeId(0),
+        }]
+        .node_data
+        .height,
+        1
+    );
+    assert_eq!(
+        dom[GlobalNodeId::TemplateId {
+            template_ref_id: dioxus_core::ElementId(1),
+            template_node_id: dioxus::prelude::TemplateNodeId(1),
+        }]
+        .node_data
+        .height,
+        2
+    );
+    assert_eq!(
+        dom[GlobalNodeId::TemplateId {
+            template_ref_id: dioxus_core::ElementId(1),
+            template_node_id: dioxus::prelude::TemplateNodeId(2),
+        }]
+        .node_data
+        .height,
+        3
+    );
+    assert_eq!(
+        dom[GlobalNodeId::TemplateId {
+            template_ref_id: dioxus_core::ElementId(1),
+            template_node_id: dioxus::prelude::TemplateNodeId(3),
+        }]
+        .node_data
+        .height,
+        3
+    );
+    assert_eq!(
+        dom[GlobalNodeId::TemplateId {
+            template_ref_id: dioxus_core::ElementId(1),
+            template_node_id: dioxus::prelude::TemplateNodeId(4),
+        }]
+        .node_data
+        .height,
+        4
+    );
+    assert_eq!(
+        dom[GlobalNodeId::TemplateId {
+            template_ref_id: dioxus_core::ElementId(1),
+            template_node_id: dioxus::prelude::TemplateNodeId(5),
+        }]
+        .node_data
+        .height,
+        3
+    );
 }

+ 34 - 27
packages/native-core-macro/tests/peristant_iterator.rs

@@ -1,4 +1,5 @@
 use dioxus::core as dioxus_core;
+use dioxus::core_macro::rsx_without_templates;
 use dioxus::prelude::*;
 use dioxus_native_core::{
     real_dom::{NodeType, RealDom},
@@ -37,68 +38,68 @@ fn traverse() {
     let mut iter = PersistantElementIter::new();
     let div_tag = "div".to_string();
     assert!(matches!(
-        &rdom[iter.next(&rdom).id()].node_type,
+        &rdom[iter.next(&rdom).id()].node_data.node_type,
         NodeType::Element { tag: div_tag, .. }
     ));
     assert!(matches!(
-        &rdom[iter.next(&rdom).id()].node_type,
+        &rdom[iter.next(&rdom).id()].node_data.node_type,
         NodeType::Element { tag: div_tag, .. }
     ));
     let text1 = "hello".to_string();
     assert!(matches!(
-        &rdom[iter.next(&rdom).id()].node_type,
+        &rdom[iter.next(&rdom).id()].node_data.node_type,
         NodeType::Text { text: text1, .. }
     ));
     let p_tag = "p".to_string();
     assert!(matches!(
-        &rdom[iter.next(&rdom).id()].node_type,
+        &rdom[iter.next(&rdom).id()].node_data.node_type,
         NodeType::Element { tag: p_tag, .. }
     ));
     let text2 = "world".to_string();
     assert!(matches!(
-        &rdom[iter.next(&rdom).id()].node_type,
+        &rdom[iter.next(&rdom).id()].node_data.node_type,
         NodeType::Text { text: text2, .. }
     ));
     let text3 = "hello world".to_string();
     assert!(matches!(
-        &rdom[iter.next(&rdom).id()].node_type,
+        &rdom[iter.next(&rdom).id()].node_data.node_type,
         NodeType::Text { text: text3, .. }
     ));
     assert!(matches!(
-        &rdom[iter.next(&rdom).id()].node_type,
+        &rdom[iter.next(&rdom).id()].node_data.node_type,
         NodeType::Element { tag: div_tag, .. }
     ));
 
     assert!(matches!(
-        &rdom[iter.prev(&rdom).id()].node_type,
+        &rdom[iter.prev(&rdom).id()].node_data.node_type,
         NodeType::Text { text: text3, .. }
     ));
     assert!(matches!(
-        &rdom[iter.prev(&rdom).id()].node_type,
+        &rdom[iter.prev(&rdom).id()].node_data.node_type,
         NodeType::Text { text: text2, .. }
     ));
     assert!(matches!(
-        &rdom[iter.prev(&rdom).id()].node_type,
+        &rdom[iter.prev(&rdom).id()].node_data.node_type,
         NodeType::Element { tag: p_tag, .. }
     ));
     assert!(matches!(
-        &rdom[iter.prev(&rdom).id()].node_type,
+        &rdom[iter.prev(&rdom).id()].node_data.node_type,
         NodeType::Text { text: text1, .. }
     ));
     assert!(matches!(
-        &rdom[iter.prev(&rdom).id()].node_type,
+        &rdom[iter.prev(&rdom).id()].node_data.node_type,
         NodeType::Element { tag: div_tag, .. }
     ));
     assert!(matches!(
-        &rdom[iter.prev(&rdom).id()].node_type,
+        &rdom[iter.prev(&rdom).id()].node_data.node_type,
         NodeType::Element { tag: div_tag, .. }
     ));
     assert!(matches!(
-        &rdom[iter.prev(&rdom).id()].node_type,
+        &rdom[iter.prev(&rdom).id()].node_data.node_type,
         NodeType::Element { tag: div_tag, .. }
     ));
     assert!(matches!(
-        &rdom[iter.prev(&rdom).id()].node_type,
+        &rdom[iter.prev(&rdom).id()].node_data.node_type,
         NodeType::Text { text: text3, .. }
     ));
 }
@@ -112,7 +113,7 @@ fn persist_removes() {
     }
     let vdom = VirtualDom::new(Base);
     let (build, update) = vdom.diff_lazynodes(
-        rsx! {
+        rsx_without_templates! {
             div{
                 p{
                     key: "1",
@@ -128,7 +129,7 @@ fn persist_removes() {
                 }
             }
         },
-        rsx! {
+        rsx_without_templates! {
             div{
                 p{
                     key: "1",
@@ -177,16 +178,19 @@ fn persist_removes() {
     let p_tag = "p".to_string();
     let idx = iter1.next(&rdom).id();
     assert!(matches!(
-        &rdom[idx].node_type,
+        &rdom[idx].node_data.node_type,
         NodeType::Element { tag: p_tag, .. }
     ));
     let text = "hello world".to_string();
     let idx = iter1.next(&rdom).id();
-    assert!(matches!(&rdom[idx].node_type, NodeType::Text { text, .. }));
+    assert!(matches!(
+        &rdom[idx].node_data.node_type,
+        NodeType::Text { text, .. }
+    ));
     let div_tag = "div".to_string();
     let idx = iter2.next(&rdom).id();
     assert!(matches!(
-        &rdom[idx].node_type,
+        &rdom[idx].node_data.node_type,
         NodeType::Element { tag: div_tag, .. }
     ));
 }
@@ -200,7 +204,7 @@ fn persist_instertions_before() {
     }
     let vdom = VirtualDom::new(Base);
     let (build, update) = vdom.diff_lazynodes(
-        rsx! {
+        rsx_without_templates! {
             div{
                 p{
                     key: "1",
@@ -212,7 +216,7 @@ fn persist_instertions_before() {
                 }
             }
         },
-        rsx! {
+        rsx_without_templates! {
             div{
                 p{
                     key: "1",
@@ -252,7 +256,7 @@ fn persist_instertions_before() {
     let p_tag = "div".to_string();
     let idx = iter.next(&rdom).id();
     assert!(matches!(
-        &rdom[idx].node_type,
+        &rdom[idx].node_data.node_type,
         NodeType::Element { tag: p_tag, .. }
     ));
 }
@@ -266,7 +270,7 @@ fn persist_instertions_after() {
     }
     let vdom = VirtualDom::new(Base);
     let (build, update) = vdom.diff_lazynodes(
-        rsx! {
+        rsx_without_templates! {
             div{
                 p{
                     key: "1",
@@ -278,7 +282,7 @@ fn persist_instertions_after() {
                 }
             }
         },
-        rsx! {
+        rsx_without_templates! {
             div{
                 p{
                     key: "1",
@@ -318,10 +322,13 @@ fn persist_instertions_after() {
     let p_tag = "p".to_string();
     let idx = iter.next(&rdom).id();
     assert!(matches!(
-        &rdom[idx].node_type,
+        &rdom[idx].node_data.node_type,
         NodeType::Element { tag: p_tag, .. }
     ));
     let text = "hello world".to_string();
     let idx = iter.next(&rdom).id();
-    assert!(matches!(&rdom[idx].node_type, NodeType::Text { text, .. }));
+    assert!(matches!(
+        &rdom[idx].node_data.node_type,
+        NodeType::Text { text, .. }
+    ));
 }

+ 68 - 24
packages/native-core-macro/tests/update_state.rs

@@ -1,7 +1,8 @@
 use anymap::AnyMap;
-use dioxus::core as dioxus_core;
 use dioxus::core::ElementId;
+use dioxus::core::{self as dioxus_core, GlobalNodeId};
 use dioxus::core::{AttributeValue, DomEdit, Mutations};
+use dioxus::core_macro::rsx_without_templates;
 use dioxus::prelude::*;
 use dioxus_native_core::node_ref::*;
 use dioxus_native_core::real_dom::*;
@@ -50,14 +51,13 @@ impl ChildDepState for ChildDepCallCounter {
     const NODE_MASK: NodeMask = NodeMask::ALL;
     fn reduce<'a>(
         &mut self,
-        node: NodeView,
+        _: NodeView,
         _: impl Iterator<Item = &'a Self::DepState>,
         _: &Self::Ctx,
     ) -> bool
     where
         Self::DepState: 'a,
     {
-        println!("{self:?} {:?}: {} {:?}", node.tag(), node.id(), node.text());
         self.0 += 1;
         true
     }
@@ -142,8 +142,16 @@ impl NodeDepState<()> for NodeStateTester {
         *self = NodeStateTester(
             node.tag().map(|s| s.to_string()),
             node.attributes()
-                .map(|a| (a.name.to_string(), a.value.to_string()))
-                .collect(),
+                .map(|iter| {
+                    iter.map(|a| {
+                        (
+                            a.attribute.name.to_string(),
+                            format!("{}", a.value.as_text().unwrap()),
+                        )
+                    })
+                    .collect()
+                })
+                .unwrap_or_default(),
         );
         true
     }
@@ -185,9 +193,12 @@ fn state_initial() {
     let nodes_updated = dom.apply_mutations(vec![mutations]);
     let mut ctx = AnyMap::new();
     ctx.insert(42u32);
-    let _to_rerender = dom.update_state(&vdom, nodes_updated, ctx);
+    let _to_rerender = dom.update_state(nodes_updated, ctx);
 
-    let root_div = &dom[ElementId(1)];
+    let root_div = &dom[GlobalNodeId::TemplateId {
+        template_ref_id: dioxus_core::ElementId(1),
+        template_node_id: dioxus::prelude::TemplateNodeId(0),
+    }];
     assert_eq!(root_div.state.bubbled.0, Some("div".to_string()));
     assert_eq!(
         root_div.state.bubbled.1,
@@ -199,12 +210,18 @@ fn state_initial() {
     assert_eq!(root_div.state.pushed.0, Some("div".to_string()));
     assert_eq!(
         root_div.state.pushed.1,
-        Some(Box::new(PushedDownStateTester(None, None)))
+        Some(Box::new(PushedDownStateTester(
+            Some("Root".to_string()),
+            None
+        )))
     );
     assert_eq!(root_div.state.node.0, Some("div".to_string()));
     assert_eq!(root_div.state.node.1, vec![]);
 
-    let child_p = &dom[ElementId(2)];
+    let child_p = &dom[GlobalNodeId::TemplateId {
+        template_ref_id: dioxus_core::ElementId(1),
+        template_node_id: dioxus::prelude::TemplateNodeId(1),
+    }];
     assert_eq!(child_p.state.bubbled.0, Some("p".to_string()));
     assert_eq!(child_p.state.bubbled.1, Vec::new());
     assert_eq!(child_p.state.pushed.0, Some("p".to_string()));
@@ -212,7 +229,10 @@ fn state_initial() {
         child_p.state.pushed.1,
         Some(Box::new(PushedDownStateTester(
             Some("div".to_string()),
-            Some(Box::new(PushedDownStateTester(None, None)))
+            Some(Box::new(PushedDownStateTester(
+                Some("Root".to_string()),
+                None
+            )))
         )))
     );
     assert_eq!(child_p.state.node.0, Some("p".to_string()));
@@ -221,7 +241,10 @@ fn state_initial() {
         vec![("color".to_string(), "red".to_string())]
     );
 
-    let child_h1 = &dom[ElementId(3)];
+    let child_h1 = &dom[GlobalNodeId::TemplateId {
+        template_ref_id: dioxus_core::ElementId(1),
+        template_node_id: dioxus::prelude::TemplateNodeId(2),
+    }];
     assert_eq!(child_h1.state.bubbled.0, Some("h1".to_string()));
     assert_eq!(child_h1.state.bubbled.1, Vec::new());
     assert_eq!(child_h1.state.pushed.0, Some("h1".to_string()));
@@ -229,7 +252,10 @@ fn state_initial() {
         child_h1.state.pushed.1,
         Some(Box::new(PushedDownStateTester(
             Some("div".to_string()),
-            Some(Box::new(PushedDownStateTester(None, None)))
+            Some(Box::new(PushedDownStateTester(
+                Some("Root".to_string()),
+                None
+            )))
         )))
     );
     assert_eq!(child_h1.state.node.0, Some("h1".to_string()));
@@ -261,7 +287,7 @@ fn state_reduce_parent_called_minimally_on_update() {
 
     let vdom = VirtualDom::new(Base);
 
-    let mutations = vdom.create_vnodes(rsx! {
+    let mutations = vdom.create_vnodes(rsx_without_templates! {
         div {
             width: "100%",
             div{
@@ -284,7 +310,7 @@ fn state_reduce_parent_called_minimally_on_update() {
     let mut dom: RealDom<CallCounterState> = RealDom::new();
 
     let nodes_updated = dom.apply_mutations(vec![mutations]);
-    let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new());
+    let _to_rerender = dom.update_state(nodes_updated, AnyMap::new());
     let nodes_updated = dom.apply_mutations(vec![Mutations {
         edits: vec![DomEdit::SetAttribute {
             root: 1,
@@ -295,7 +321,7 @@ fn state_reduce_parent_called_minimally_on_update() {
         dirty_scopes: fxhash::FxHashSet::default(),
         refs: Vec::new(),
     }]);
-    let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new());
+    let _to_rerender = dom.update_state(nodes_updated, AnyMap::new());
 
     dom.traverse_depth_first(|n| {
         assert_eq!(n.state.part2.parent_counter.0, 2);
@@ -307,7 +333,7 @@ fn state_reduce_parent_called_minimally_on_update() {
 fn state_reduce_child_called_minimally_on_update() {
     #[allow(non_snake_case)]
     fn Base(cx: Scope) -> Element {
-        render!(div {
+        cx.render(rsx_without_templates!(div {
             div{
                 div{
                     p{
@@ -324,12 +350,12 @@ fn state_reduce_child_called_minimally_on_update() {
                     "world"
                 }
             }
-        })
+        }))
     }
 
     let vdom = VirtualDom::new(Base);
 
-    let mutations = vdom.create_vnodes(rsx! {
+    let mutations = vdom.create_vnodes(rsx_without_templates! {
         div {
             div{
                 div{
@@ -353,7 +379,7 @@ fn state_reduce_child_called_minimally_on_update() {
     let mut dom: RealDom<CallCounterState> = RealDom::new();
 
     let nodes_updated = dom.apply_mutations(vec![mutations]);
-    let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new());
+    let _to_rerender = dom.update_state(nodes_updated, AnyMap::new());
     let nodes_updated = dom.apply_mutations(vec![Mutations {
         edits: vec![DomEdit::SetAttribute {
             root: 4,
@@ -364,15 +390,33 @@ fn state_reduce_child_called_minimally_on_update() {
         dirty_scopes: fxhash::FxHashSet::default(),
         refs: Vec::new(),
     }]);
-    let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new());
+    let _to_rerender = dom.update_state(nodes_updated, AnyMap::new());
 
     dom.traverse_depth_first(|n| {
-        println!("{:?}", n);
         assert_eq!(
             n.state.part1.child_counter.0,
-            if n.id.0 > 4 { 1 } else { 2 }
+            if let GlobalNodeId::VNodeId(ElementId(id)) = n.node_data.id {
+                if id > 4 {
+                    1
+                } else {
+                    2
+                }
+            } else {
+                panic!()
+            }
+        );
+        assert_eq!(
+            n.state.child_counter.0,
+            if let GlobalNodeId::VNodeId(ElementId(id)) = n.node_data.id {
+                if id > 4 {
+                    1
+                } else {
+                    2
+                }
+            } else {
+                panic!()
+            }
         );
-        assert_eq!(n.state.child_counter.0, if n.id.0 > 4 { 1 } else { 2 });
     });
 }
 
@@ -457,7 +501,7 @@ fn dependancies_order_independant() {
     let mut dom: RealDom<UnorderedDependanciesState> = RealDom::new();
 
     let nodes_updated = dom.apply_mutations(vec![mutations]);
-    let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new());
+    let _to_rerender = dom.update_state(nodes_updated, AnyMap::new());
 
     let c = CDepCallCounter(1);
     let b = BDepCallCounter(1, c.clone());

+ 1 - 1
packages/native-core/README.md

@@ -1 +1 @@
-# Dioxus Native-Core: An ECS approach to GUI state and trees
+# Dioxus Native-Core: An lazy approach to GUI state and trees

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

@@ -2,6 +2,7 @@ pub mod layout_attributes;
 pub mod node_ref;
 pub mod real_dom;
 pub mod state;
+pub mod template;
 #[doc(hidden)]
 pub mod traversable;
 pub mod utils;

+ 43 - 40
packages/native-core/src/node_ref.rs

@@ -1,37 +1,39 @@
 use dioxus_core::*;
 
-use crate::state::union_ordered_iter;
+use crate::{
+    real_dom::{NodeData, NodeType, OwnedAttributeView},
+    state::union_ordered_iter,
+};
 
 /// A view into a [VNode] with limited access.
 #[derive(Debug)]
 pub struct NodeView<'a> {
-    inner: &'a VNode<'a>,
+    inner: &'a NodeData,
     mask: NodeMask,
 }
 
 impl<'a> NodeView<'a> {
     /// Create a new NodeView from a VNode, and mask.
-    pub fn new(mut vnode: &'a VNode<'a>, view: NodeMask, vdom: &'a VirtualDom) -> Self {
-        if let VNode::Component(sc) = vnode {
-            let scope = vdom.get_scope(sc.scope.get().unwrap()).unwrap();
-            vnode = scope.root_node();
-        }
+    pub fn new(node: &'a NodeData, view: NodeMask) -> Self {
         Self {
-            inner: vnode,
+            inner: node,
             mask: view,
         }
     }
 
     /// Get the id of the node
-    pub fn id(&self) -> ElementId {
-        self.inner.mounted_id()
+    pub fn id(&self) -> GlobalNodeId {
+        self.inner.id
     }
 
     /// Get the tag of the node if the tag is enabled in the mask
     pub fn tag(&self) -> Option<&'a str> {
         self.mask
             .tag
-            .then(|| self.try_element().map(|el| el.tag))
+            .then(|| match &self.inner.node_type {
+                NodeType::Element { tag, .. } => Some(&**tag),
+                _ => None,
+            })
             .flatten()
     }
 
@@ -39,47 +41,47 @@ impl<'a> NodeView<'a> {
     pub fn namespace(&self) -> Option<&'a str> {
         self.mask
             .namespace
-            .then(|| self.try_element().and_then(|el| el.namespace))
+            .then(|| match &self.inner.node_type {
+                NodeType::Element { namespace, .. } => namespace.map(|s| &*s),
+                _ => None,
+            })
             .flatten()
     }
 
     /// Get any attributes that are enabled in the mask
-    pub fn attributes(&self) -> impl Iterator<Item = &Attribute<'a>> {
-        self.try_element()
-            .map(|el| el.attributes)
-            .unwrap_or_default()
-            .iter()
-            .filter(|a| self.mask.attritutes.contains_attribute(a.name))
+    pub fn attributes<'b>(&'b self) -> Option<impl Iterator<Item = OwnedAttributeView<'a>> + 'b> {
+        match &self.inner.node_type {
+            NodeType::Element { attributes, .. } => Some(
+                attributes
+                    .iter()
+                    .filter(move |(attr, _)| self.mask.attritutes.contains_attribute(&attr.name))
+                    .map(|(attr, val)| OwnedAttributeView {
+                        attribute: attr,
+                        value: val,
+                    }),
+            ),
+            _ => None,
+        }
     }
 
     /// Get the text if it is enabled in the mask
     pub fn text(&self) -> Option<&str> {
         self.mask
             .text
-            .then(|| self.try_text().map(|txt| txt.text))
+            .then(|| match &self.inner.node_type {
+                NodeType::Text { text } => Some(&**text),
+                _ => None,
+            })
             .flatten()
     }
 
     /// Get the listeners if it is enabled in the mask
-    pub fn listeners(&self) -> &'a [Listener<'a>] {
-        self.try_element()
-            .map(|el| el.listeners)
-            .unwrap_or_default()
-    }
-
-    /// Try to get the underlying element.
-    fn try_element(&self) -> Option<&'a VElement<'a>> {
-        if let VNode::Element(el) = &self.inner {
-            Some(el)
-        } else {
-            None
-        }
-    }
-
-    /// Try to get the underlying text node.
-    fn try_text(&self) -> Option<&'a VText<'a>> {
-        if let VNode::Text(txt) = &self.inner {
-            Some(txt)
+    pub fn listeners(&self) -> Option<impl Iterator<Item = &'a str> + '_> {
+        if self.mask.listeners {
+            match &self.inner.node_type {
+                NodeType::Element { listeners, .. } => Some(listeners.iter().map(|l| &**l)),
+                _ => None,
+            }
         } else {
             None
         }
@@ -100,7 +102,7 @@ impl AttributeMask {
     /// A empty attribute mask
     pub const NONE: Self = Self::Static(&[]);
 
-    fn contains_attribute(&self, attr: &'static str) -> bool {
+    fn contains_attribute(&self, attr: &str) -> bool {
         match self {
             AttributeMask::All => true,
             AttributeMask::Dynamic(l) => l.binary_search(&attr).is_ok(),
@@ -215,7 +217,8 @@ impl NodeMask {
     /// A node mask with every part visible.
     pub const ALL: Self = Self::new_with_attrs(AttributeMask::All)
         .with_text()
-        .with_element();
+        .with_element()
+        .with_listeners();
 
     /// Check if two masks overlap
     pub fn overlaps(&self, other: &Self) -> bool {

+ 806 - 137
packages/native-core/src/real_dom.rs

@@ -2,21 +2,30 @@ use anymap::AnyMap;
 use fxhash::{FxHashMap, FxHashSet};
 use std::ops::{Index, IndexMut};
 
-use dioxus_core::{ElementId, Mutations, VNode, VirtualDom};
+use dioxus_core::{
+    AttributeDiscription, ElementId, GlobalNodeId, Mutations, OwnedAttributeValue,
+    RendererTemplateId, TemplateNodeId, VNode, JS_MAX_INT,
+};
 
 use crate::node_ref::{AttributeMask, NodeMask};
 use crate::state::State;
+use crate::template::{NativeTemplate, TemplateRefOrNode};
 use crate::traversable::Traversable;
 
+pub(crate) type TemplateMapping<S> = FxHashMap<RendererTemplateId, NativeTemplate<S>>;
+
 /// A Dom that can sync with the VirtualDom mutations intended for use in lazy renderers.
 /// The render state passes from parent to children and or accumulates state from children to parents.
 /// To get started implement [crate::state::ParentDepState], [crate::state::NodeDepState], or [crate::state::ChildDepState] and call [RealDom::apply_mutations] to update the dom and [RealDom::update_state] to update the state of the nodes.
 #[derive(Debug)]
 pub struct RealDom<S: State> {
     root: usize,
-    nodes: Vec<Option<Node<S>>>,
-    nodes_listening: FxHashMap<&'static str, FxHashSet<ElementId>>,
-    node_stack: smallvec::SmallVec<[usize; 10]>,
+    nodes: Vec<Option<Box<TemplateRefOrNode<S>>>>,
+    nodes_listening: FxHashMap<&'static str, FxHashSet<GlobalNodeId>>,
+    templates: TemplateMapping<S>,
+    template_stack: smallvec::SmallVec<[ElementId; 5]>,
+    template_in_progress: Option<RendererTemplateId>,
+    node_stack: smallvec::SmallVec<[GlobalNodeId; 10]>,
 }
 
 impl<S: State> Default for RealDom<S> {
@@ -30,29 +39,38 @@ impl<S: State> RealDom<S> {
         RealDom {
             root: 0,
             nodes: {
-                let v = vec![Some(Node::new(
-                    0,
+                let v = vec![Some(Box::new(TemplateRefOrNode::Node(Node::new(
+                    GlobalNodeId::VNodeId(ElementId(0)),
                     NodeType::Element {
                         tag: "Root".to_string(),
                         namespace: Some("Root"),
+                        attributes: FxHashMap::default(),
+                        listeners: FxHashSet::default(),
                         children: Vec::new(),
                     },
-                ))];
+                ))))];
                 v
             },
             nodes_listening: FxHashMap::default(),
             node_stack: smallvec::SmallVec::new(),
+            templates: FxHashMap::default(),
+            template_stack: smallvec::SmallVec::new(),
+            template_in_progress: None,
         }
     }
 
     /// Updates the dom with some mutations and return a set of nodes that were updated. Pass the dirty nodes to update_state.
-    pub fn apply_mutations(&mut self, mutations_vec: Vec<Mutations>) -> Vec<(ElementId, NodeMask)> {
+    pub fn apply_mutations(
+        &mut self,
+        mutations_vec: Vec<Mutations>,
+    ) -> Vec<(GlobalNodeId, NodeMask)> {
         let mut nodes_updated = Vec::new();
+        nodes_updated.push((GlobalNodeId::VNodeId(ElementId(0)), NodeMask::ALL));
         for mutations in mutations_vec {
             for e in mutations.edits {
                 use dioxus_core::DomEdit::*;
                 match e {
-                    PushRoot { root } => self.node_stack.push(root as usize),
+                    PushRoot { root } => self.node_stack.push(self.decode_id(root)),
                     AppendChildren { many } => {
                         let target = if self.node_stack.len() > many as usize {
                             *self
@@ -60,53 +78,61 @@ impl<S: State> RealDom<S> {
                                 .get(self.node_stack.len() - (many as usize + 1))
                                 .unwrap()
                         } else {
-                            0
+                            GlobalNodeId::VNodeId(ElementId(0))
                         };
                         let drained: Vec<_> = self
                             .node_stack
                             .drain(self.node_stack.len() - many as usize..)
                             .collect();
-                        for ns in drained {
-                            let id = ElementId(ns);
-                            self.link_child(id, ElementId(target)).unwrap();
-                            nodes_updated.push((id, NodeMask::ALL));
+                        for id in drained {
+                            self.mark_dirty(id, NodeMask::ALL, &mut nodes_updated);
+                            self.link_child(id, target).unwrap();
                         }
                     }
                     ReplaceWith { root, m } => {
-                        let root = self.remove(ElementId(root as usize)).unwrap();
-                        let target = root.parent.unwrap().0;
-                        let drained: Vec<_> = self.node_stack.drain(0..m as usize).collect();
-                        for ns in drained {
-                            let id = ElementId(ns);
-                            nodes_updated.push((id, NodeMask::ALL));
-                            self.link_child(id, ElementId(target)).unwrap();
+                        let id = self.decode_id(root);
+                        let root = self.remove(id).unwrap();
+                        let target = root.parent().unwrap();
+                        let drained: Vec<_> = self
+                            .node_stack
+                            .drain(self.node_stack.len() - m as usize..)
+                            .collect();
+                        for id in drained {
+                            self.mark_dirty(id, NodeMask::ALL, &mut nodes_updated);
+                            self.link_child(id, target).unwrap();
                         }
                     }
                     InsertAfter { root, n } => {
-                        let target = self[ElementId(root as usize)].parent.unwrap().0;
-                        let drained: Vec<_> = self.node_stack.drain(0..n as usize).collect();
-                        for ns in drained {
-                            let id = ElementId(ns);
-                            nodes_updated.push((id, NodeMask::ALL));
-                            self.link_child(id, ElementId(target)).unwrap();
+                        let target = self.parent(self.decode_id(root)).unwrap();
+                        let drained: Vec<_> = self
+                            .node_stack
+                            .drain(self.node_stack.len() - n as usize..)
+                            .collect();
+                        for id in drained {
+                            self.mark_dirty(id, NodeMask::ALL, &mut nodes_updated);
+                            self.link_child(id, target).unwrap();
                         }
                     }
                     InsertBefore { root, n } => {
-                        let target = self[ElementId(root as usize)].parent.unwrap().0;
-                        let drained: Vec<_> = self.node_stack.drain(0..n as usize).collect();
-                        for ns in drained {
-                            let id = ElementId(ns);
-                            nodes_updated.push((id, NodeMask::ALL));
-                            self.link_child(id, ElementId(target)).unwrap();
+                        let target = self.parent(self.decode_id(root)).unwrap();
+                        let drained: Vec<_> = self
+                            .node_stack
+                            .drain(self.node_stack.len() - n as usize..)
+                            .collect();
+                        for id in drained {
+                            self.mark_dirty(id, NodeMask::ALL, &mut nodes_updated);
+                            self.link_child(id, target).unwrap();
                         }
                     }
                     Remove { root } => {
-                        if let Some(parent) = self[ElementId(root as usize)].parent {
-                            nodes_updated.push((parent, NodeMask::NONE));
+                        if let Some(parent) = self.parent(self.decode_id(root)) {
+                            self.mark_dirty(parent, NodeMask::NONE, &mut nodes_updated);
                         }
-                        self.remove(ElementId(root as usize)).unwrap();
+                        let id = self.decode_id(root);
+                        self.remove(id).unwrap();
                     }
                     CreateTextNode { root, text } => {
+                        let root = self.decode_id(root);
                         let n = Node::new(
                             root,
                             NodeType::Text {
@@ -114,45 +140,120 @@ impl<S: State> RealDom<S> {
                             },
                         );
                         self.insert(n);
-                        self.node_stack.push(root as usize)
+                        self.node_stack.push(root)
+                    }
+                    CreateTextNodeTemplate {
+                        root,
+                        text,
+                        locally_static: _,
+                    } => {
+                        let root = self.decode_id(root);
+                        let n = Node::new(
+                            root,
+                            NodeType::Text {
+                                text: text.to_string(),
+                            },
+                        );
+                        self.current_template_mut().unwrap().insert(n);
+                        self.node_stack.push(root)
                     }
                     CreateElement { root, tag } => {
+                        let root = self.decode_id(root);
                         let n = Node::new(
                             root,
                             NodeType::Element {
                                 tag: tag.to_string(),
                                 namespace: None,
+                                attributes: FxHashMap::default(),
+                                listeners: FxHashSet::default(),
                                 children: Vec::new(),
                             },
                         );
                         self.insert(n);
-                        self.node_stack.push(root as usize)
+                        self.node_stack.push(root)
+                    }
+                    CreateElementTemplate {
+                        root,
+                        tag,
+                        locally_static: _,
+                        fully_static: _,
+                    } => {
+                        let root = self.decode_id(root);
+                        let n = Node::new(
+                            root,
+                            NodeType::Element {
+                                tag: tag.to_string(),
+                                namespace: None,
+                                attributes: FxHashMap::default(),
+                                listeners: FxHashSet::default(),
+                                children: Vec::new(),
+                            },
+                        );
+                        self.current_template_mut().unwrap().insert(n);
+                        self.node_stack.push(root)
                     }
                     CreateElementNs { root, tag, ns } => {
+                        let root = self.decode_id(root);
                         let n = Node::new(
                             root,
                             NodeType::Element {
                                 tag: tag.to_string(),
                                 namespace: Some(ns),
+                                attributes: FxHashMap::default(),
+                                listeners: FxHashSet::default(),
                                 children: Vec::new(),
                             },
                         );
                         self.insert(n);
-                        self.node_stack.push(root as usize)
+                        self.node_stack.push(root)
+                    }
+                    CreateElementNsTemplate {
+                        root,
+                        tag,
+                        ns,
+                        locally_static: _,
+                        fully_static: _,
+                    } => {
+                        let root = self.decode_id(root);
+                        let n = Node::new(
+                            root,
+                            NodeType::Element {
+                                tag: tag.to_string(),
+                                namespace: Some(ns),
+                                attributes: FxHashMap::default(),
+                                listeners: FxHashSet::default(),
+                                children: Vec::new(),
+                            },
+                        );
+                        self.current_template_mut().unwrap().insert(n);
+                        self.node_stack.push(root)
                     }
                     CreatePlaceholder { root } => {
+                        let root = self.decode_id(root);
                         let n = Node::new(root, NodeType::Placeholder);
                         self.insert(n);
-                        self.node_stack.push(root as usize)
+                        self.node_stack.push(root)
+                    }
+                    CreatePlaceholderTemplate { root } => {
+                        let root = self.decode_id(root);
+                        let n = Node::new(root, NodeType::Placeholder);
+                        self.current_template_mut().unwrap().insert(n);
+                        self.node_stack.push(root)
                     }
-
                     NewEventListener {
                         event_name,
                         scope: _,
                         root,
                     } => {
-                        let id = ElementId(root as usize);
-                        nodes_updated.push((id, NodeMask::new().with_listeners()));
+                        let id = self.decode_id(root);
+                        self.mark_dirty(id, NodeMask::new().with_listeners(), &mut nodes_updated);
+                        match &mut self[id].node_data.node_type {
+                            NodeType::Text { .. } => panic!("Text nodes cannot have listeners"),
+                            NodeType::Element { listeners, .. } => {
+                                listeners.insert(event_name.to_string());
+                            }
+                            NodeType::Placeholder => panic!("Placeholder cannot have listeners"),
+                        }
                         if let Some(v) = self.nodes_listening.get_mut(event_name) {
                             v.insert(id);
                         } else {
@@ -162,8 +263,8 @@ impl<S: State> RealDom<S> {
                         }
                     }
                     RemoveEventListener { root, event } => {
-                        let id = ElementId(root as usize);
-                        nodes_updated.push((id, NodeMask::new().with_listeners()));
+                        let id = self.decode_id(root);
+                        self.mark_dirty(id, NodeMask::new().with_listeners(), &mut nodes_updated);
                         let v = self.nodes_listening.get_mut(event).unwrap();
                         v.remove(&id);
                     }
@@ -171,108 +272,452 @@ impl<S: State> RealDom<S> {
                         root,
                         text: new_text,
                     } => {
-                        let id = ElementId(root as usize);
+                        let id = self.decode_id(root);
+                        self.mark_dirty(id, NodeMask::new().with_text(), &mut nodes_updated);
                         let target = &mut self[id];
-                        nodes_updated.push((id, NodeMask::new().with_text()));
-                        match &mut target.node_type {
+                        match &mut target.node_data.node_type {
                             NodeType::Text { text } => {
                                 *text = new_text.to_string();
                             }
                             _ => unreachable!(),
                         }
                     }
-                    SetAttribute { root, field, .. } => {
-                        let id = ElementId(root as usize);
-                        nodes_updated
-                            .push((id, NodeMask::new_with_attrs(AttributeMask::single(field))));
+                    SetAttribute {
+                        root,
+                        field,
+                        ns,
+                        value,
+                    } => {
+                        let id = self.decode_id(root);
+                        if let NodeType::Element { attributes, .. } =
+                            &mut self[id].node_data.node_type
+                        {
+                            attributes.insert(
+                                OwnedAttributeDiscription {
+                                    name: field.to_owned(),
+                                    namespace: ns.map(|a| a.to_owned()),
+                                    volatile: false,
+                                },
+                                value.into(),
+                            );
+                        } else {
+                            panic!("tried to call set attribute on a non element");
+                        }
+                        self.mark_dirty(
+                            id,
+                            NodeMask::new_with_attrs(AttributeMask::single(field)),
+                            &mut nodes_updated,
+                        );
                     }
                     RemoveAttribute {
                         root, name: field, ..
                     } => {
-                        let id = ElementId(root as usize);
-                        nodes_updated
-                            .push((id, NodeMask::new_with_attrs(AttributeMask::single(field))));
+                        let id = self.decode_id(root);
+                        self.mark_dirty(
+                            id,
+                            NodeMask::new_with_attrs(AttributeMask::single(field)),
+                            &mut nodes_updated,
+                        );
                     }
                     PopRoot {} => {
                         self.node_stack.pop();
                     }
+                    CreateTemplateRef { id, template_id } => {
+                        let template_id = RendererTemplateId(template_id as usize);
+                        let template = self.templates.get(&template_id).unwrap();
+                        let nodes = template.nodes.clone();
+                        let id = ElementId(id as usize);
+                        fn update_refrences<S: State>(
+                            real_dom: &mut RealDom<S>,
+                            nodes_updated: &mut Vec<(GlobalNodeId, NodeMask)>,
+                            node_id: GlobalNodeId,
+                            template_id: ElementId,
+                        ) {
+                            nodes_updated.push((node_id, NodeMask::ALL));
+                            let node_id = if let GlobalNodeId::TemplateId {
+                                template_node_id, ..
+                            } = node_id
+                            {
+                                GlobalNodeId::TemplateId {
+                                    template_ref_id: template_id,
+                                    template_node_id,
+                                }
+                            } else {
+                                node_id
+                            };
+                            let n = real_dom.get_mut(node_id).unwrap();
+                            if let GlobalNodeId::TemplateId {
+                                template_node_id, ..
+                            } = n.node_data.id
+                            {
+                                n.node_data.id = GlobalNodeId::TemplateId {
+                                    template_ref_id: template_id,
+                                    template_node_id,
+                                };
+                                if let Some(GlobalNodeId::TemplateId {
+                                    template_ref_id: ElementId(0),
+                                    template_node_id,
+                                }) = n.node_data.parent
+                                {
+                                    n.node_data.parent = Some(GlobalNodeId::TemplateId {
+                                        template_ref_id: template_id,
+                                        template_node_id,
+                                    });
+                                }
+                            }
+                            if let NodeType::Element { children, .. } = &mut n.node_data.node_type {
+                                for c in children.iter_mut() {
+                                    if let GlobalNodeId::TemplateId {
+                                        template_node_id, ..
+                                    } = c
+                                    {
+                                        *c = GlobalNodeId::TemplateId {
+                                            template_ref_id: template_id,
+                                            template_node_id: *template_node_id,
+                                        };
+                                    } else {
+                                        panic!("non-template node in template");
+                                    }
+                                }
+                                for c in children.clone() {
+                                    update_refrences(real_dom, nodes_updated, c, template_id);
+                                }
+                            }
+                        }
+                        let template = self.templates.get(&template_id).unwrap();
+                        let roots: Vec<_> = template
+                            .roots
+                            .iter()
+                            .map(|n| GlobalNodeId::TemplateId {
+                                template_ref_id: id,
+                                template_node_id: TemplateNodeId(*n),
+                            })
+                            .collect();
+                        let template_ref = TemplateRefOrNode::Ref {
+                            nodes,
+                            roots: roots.clone(),
+                            parent: None,
+                        };
+                        self.resize_to(id.0);
+                        self.nodes[id.0] = Some(Box::new(template_ref));
+                        for node_id in roots {
+                            update_refrences(self, &mut nodes_updated, node_id, id);
+                        }
+                        self.node_stack.push(dioxus_core::GlobalNodeId::VNodeId(id));
+                    }
+                    CreateTemplate { id } => {
+                        let id = RendererTemplateId(id as usize);
+                        self.templates.insert(id, NativeTemplate::default());
+                        self.template_in_progress = Some(id);
+                    }
+                    FinishTemplate { len } => {
+                        let len = len as usize;
+                        let roots = self
+                            .node_stack
+                            .drain((self.node_stack.len() - len)..)
+                            .map(|id| {
+                                if let GlobalNodeId::TemplateId {
+                                    template_node_id, ..
+                                } = id
+                                {
+                                    template_node_id.0
+                                } else {
+                                    panic!("tried to add a non-template node to a template")
+                                }
+                            })
+                            .collect();
+                        let current_template = self.current_template_mut();
+                        current_template.unwrap().roots = roots;
+                        self.template_in_progress = None;
+                    }
+                    EnterTemplateRef { root } => self.template_stack.push(ElementId(root as usize)),
+                    ExitTemplateRef {} => {
+                        self.template_stack.pop();
+                    }
                 }
             }
         }
 
+        debug_assert!(self.template_stack.is_empty());
+        debug_assert_eq!(self.template_in_progress, None);
+
+        // remove any nodes that were created and then removed in the same mutations from the dirty nodes list
+        nodes_updated.retain(|n| match &n.0 {
+            GlobalNodeId::TemplateId {
+                template_ref_id,
+                template_node_id,
+            } => self
+                .nodes
+                .get(template_ref_id.0)
+                .map(|o| o.as_ref())
+                .flatten()
+                .and_then(|t| match &**t {
+                    TemplateRefOrNode::Ref { nodes, .. } => {
+                        nodes.get(template_node_id.0).map(|o| o.as_ref()).flatten()
+                    }
+                    TemplateRefOrNode::Node(_) => None,
+                })
+                .is_some(),
+            GlobalNodeId::VNodeId(n) => self
+                .nodes
+                .get(n.0)
+                .map(|o| o.as_ref())
+                .flatten()
+                .and_then(|n| match &**n {
+                    TemplateRefOrNode::Ref { .. } => None,
+                    TemplateRefOrNode::Node(_) => Some(n),
+                })
+                .is_some(),
+        });
+
         nodes_updated
     }
 
+    fn mark_dirty(
+        &self,
+        gid: GlobalNodeId,
+        mask: NodeMask,
+        dirty_nodes: &mut Vec<(GlobalNodeId, NodeMask)>,
+    ) {
+        if self.template_in_progress.is_some() {
+            return;
+        }
+        if let GlobalNodeId::VNodeId(id) = gid {
+            if let TemplateRefOrNode::Ref { roots, .. } = &**self.nodes[id.0].as_ref().unwrap() {
+                for r in roots {
+                    dirty_nodes.push((*r, mask.clone()));
+                }
+            } else {
+                dirty_nodes.push((gid, mask));
+            }
+        } else {
+            dirty_nodes.push((gid, mask));
+        }
+    }
+
+    fn current_template_mut(&mut self) -> Option<&mut NativeTemplate<S>> {
+        self.templates.get_mut(self.template_in_progress.as_ref()?)
+    }
+
+    fn current_template(&self) -> Option<&NativeTemplate<S>> {
+        self.templates.get(self.template_in_progress.as_ref()?)
+    }
+
     /// Update the state of the dom, after appling some mutations. This will keep the nodes in the dom up to date with their VNode counterparts.
     pub fn update_state(
         &mut self,
-        vdom: &VirtualDom,
-        nodes_updated: Vec<(ElementId, NodeMask)>,
+        nodes_updated: Vec<(GlobalNodeId, NodeMask)>,
         ctx: AnyMap,
-    ) -> FxHashSet<ElementId> {
-        S::update(
-            &nodes_updated,
-            &mut self.map(|n| &n.state, |n| &mut n.state),
-            vdom,
-            &ctx,
-        )
+    ) -> FxHashSet<GlobalNodeId> {
+        let (mut state_tree, node_tree) = self.split();
+        S::update(&nodes_updated, &mut state_tree, &node_tree, &ctx)
     }
 
     /// Link a child and parent together
-    fn link_child(&mut self, child_id: ElementId, parent_id: ElementId) -> Option<()> {
-        debug_assert_ne!(child_id, parent_id);
+    fn link_child(&mut self, child_id: GlobalNodeId, parent_id: GlobalNodeId) -> Option<()> {
+        if let GlobalNodeId::VNodeId(id) = parent_id {
+            if let TemplateRefOrNode::Ref { .. } = &**self.nodes[id.0].as_ref().unwrap() {
+                return Some(());
+            }
+        }
+        let mut created = false;
+        if let GlobalNodeId::VNodeId(id) = child_id {
+            let unbounded_self: &mut Self = unsafe { std::mem::transmute(&*self as *const Self) };
+            if let TemplateRefOrNode::Ref { roots, .. } = &**self.nodes[id.0].as_mut()? {
+                // this is safe because we know that no parent will be it's own child
+                let parent = &mut unbounded_self[parent_id];
+                for r in roots {
+                    parent.add_child(*r);
+                }
+                created = true;
+            }
+        }
         let parent = &mut self[parent_id];
-        parent.add_child(child_id);
-        let parent_height = parent.height + 1;
-        self[child_id].set_parent(parent_id);
-        self.increase_height(child_id, parent_height);
+        if !created {
+            parent.add_child(child_id);
+        }
+        let parent_height = parent.node_data.height + 1;
+        match child_id {
+            GlobalNodeId::VNodeId(id) => {
+                match &mut **self.nodes.get_mut(id.0).unwrap().as_mut().unwrap() {
+                    TemplateRefOrNode::Ref { roots, parent, .. } => {
+                        *parent = Some(parent_id);
+                        for r in roots.clone() {
+                            self[r].node_data.parent = Some(parent_id);
+                        }
+                    }
+                    TemplateRefOrNode::Node(n) => n.node_data.parent = Some(parent_id),
+                }
+            }
+            GlobalNodeId::TemplateId {
+                template_ref_id,
+                template_node_id,
+            } => {
+                let n = if let Some(template) = self.current_template_mut() {
+                    &mut **template.nodes[template_node_id.0].as_mut().unwrap()
+                } else {
+                    let nodes = match self
+                        .nodes
+                        .get_mut(template_ref_id.0)
+                        .unwrap()
+                        .as_mut()
+                        .unwrap()
+                        .as_mut()
+                    {
+                        TemplateRefOrNode::Ref { nodes, .. } => nodes,
+                        TemplateRefOrNode::Node(_) => panic!("Expected template ref"),
+                    };
+                    nodes
+                        .get_mut(template_node_id.0)
+                        .map(|n| n.as_mut())
+                        .flatten()
+                        .map(|n| n.as_mut())
+                        .unwrap()
+                };
+
+                n.set_parent(parent_id);
+            }
+        }
+        self.set_height(child_id, parent_height);
+
         Some(())
     }
 
     /// Recursively increase the height of a node and its children
-    fn increase_height(&mut self, id: ElementId, amount: u16) {
-        let n = &mut self[id];
-        n.height += amount;
-        if let NodeType::Element { children, .. } = &n.node_type {
-            for c in children.clone() {
-                self.increase_height(c, amount);
+    fn set_height(&mut self, id: GlobalNodeId, height: u16) {
+        match id {
+            GlobalNodeId::VNodeId(id) => {
+                let n = &mut **self.nodes.get_mut(id.0).unwrap().as_mut().unwrap();
+                match n {
+                    TemplateRefOrNode::Ref { roots, .. } => {
+                        for root in roots.clone() {
+                            self.set_height(root, height);
+                        }
+                    }
+                    TemplateRefOrNode::Node(n) => {
+                        n.node_data.height = height;
+                        if let NodeType::Element { children, .. } = &n.node_data.node_type {
+                            for c in children.clone() {
+                                self.set_height(c, height + 1);
+                            }
+                        }
+                    }
+                }
+            }
+            GlobalNodeId::TemplateId {
+                template_ref_id,
+                template_node_id,
+            } => {
+                let n = if let Some(template) = self.current_template_mut() {
+                    &mut **template.nodes[template_node_id.0].as_mut().unwrap()
+                } else {
+                    let nodes = match self
+                        .nodes
+                        .get_mut(template_ref_id.0)
+                        .unwrap()
+                        .as_mut()
+                        .unwrap()
+                        .as_mut()
+                    {
+                        TemplateRefOrNode::Ref { nodes, .. } => nodes,
+                        TemplateRefOrNode::Node(_) => panic!("Expected template ref"),
+                    };
+                    nodes
+                        .get_mut(template_node_id.0)
+                        .map(|n| n.as_mut())
+                        .flatten()
+                        .map(|n| n.as_mut())
+                        .unwrap()
+                };
+
+                n.node_data.height = height;
+                if let NodeType::Element { children, .. } = &n.node_data.node_type {
+                    for c in children.clone() {
+                        self.set_height(c, height + 1);
+                    }
+                }
             }
         }
     }
 
     // remove a node and it's children from the dom.
-    fn remove(&mut self, id: ElementId) -> Option<Node<S>> {
+    fn remove(&mut self, id: GlobalNodeId) -> Option<TemplateRefOrNode<S>> {
         // We do not need to remove the node from the parent's children list for children.
-        fn inner<S: State>(dom: &mut RealDom<S>, id: ElementId) -> Option<Node<S>> {
-            let mut node = dom.nodes[id.0].take()?;
-            if let NodeType::Element { children, .. } = &mut node.node_type {
-                for c in children {
-                    inner(dom, *c)?;
+        fn inner<S: State>(dom: &mut RealDom<S>, id: GlobalNodeId) -> Option<TemplateRefOrNode<S>> {
+            let mut either = match id {
+                GlobalNodeId::VNodeId(id) => *dom.nodes[id.0].take()?,
+                GlobalNodeId::TemplateId {
+                    template_ref_id,
+                    template_node_id,
+                } => {
+                    let template_ref = &mut dom.nodes[template_ref_id.0].as_mut().unwrap();
+                    if let TemplateRefOrNode::Ref { nodes, roots, .. } = template_ref.as_mut() {
+                        roots.retain(|r| *r != id);
+                        TemplateRefOrNode::Node(*nodes[template_node_id.0].take().unwrap())
+                    } else {
+                        unreachable!()
+                    }
+                }
+            };
+            match &mut either {
+                TemplateRefOrNode::Node(node) => {
+                    if let NodeType::Element { children, .. } = &mut node.node_data.node_type {
+                        for c in children {
+                            inner(dom, *c);
+                        }
+                    }
+                    Some(either)
                 }
+                TemplateRefOrNode::Ref { .. } => Some(either),
             }
-            Some(node)
         }
-        let mut node = self.nodes[id.0].take()?;
-        if let Some(parent) = node.parent {
+        let mut node = match id {
+            GlobalNodeId::VNodeId(id) => *self.nodes[id.0].take()?,
+            GlobalNodeId::TemplateId {
+                template_ref_id,
+                template_node_id,
+            } => {
+                let template_ref = &mut self.nodes[template_ref_id.0].as_mut().unwrap();
+                if let TemplateRefOrNode::Ref { nodes, roots, .. } = template_ref.as_mut() {
+                    roots.retain(|r| *r != id);
+                    TemplateRefOrNode::Node(*nodes[template_node_id.0].take().unwrap())
+                } else {
+                    unreachable!()
+                }
+            }
+        };
+        if let Some(parent) = node.parent() {
             let parent = &mut self[parent];
             parent.remove_child(id);
         }
-        if let NodeType::Element { children, .. } = &mut node.node_type {
-            for c in children {
-                inner(self, *c)?;
+        match &mut node {
+            TemplateRefOrNode::Ref { .. } => {}
+            TemplateRefOrNode::Node(node) => {
+                if let NodeType::Element { children, .. } = &mut node.node_data.node_type {
+                    for c in children {
+                        inner(self, *c)?;
+                    }
+                }
             }
         }
         Some(node)
     }
 
-    /// Create a node
-    fn insert(&mut self, node: Node<S>) {
+    fn resize_to(&mut self, id: usize) {
         let current_len = self.nodes.len();
-        let id = node.id.0;
-        if current_len - 1 < node.id.0 {
+        if current_len - 1 < id {
             self.nodes.extend((0..1 + id - current_len).map(|_| None));
         }
-        self.nodes[id] = Some(node);
+    }
+
+    fn insert(&mut self, node: Node<S>) {
+        match node.node_data.id {
+            GlobalNodeId::TemplateId { .. } => panic!("cannot insert into template"),
+            GlobalNodeId::VNodeId(id) => {
+                self.resize_to(id.0);
+                self.nodes[id.0] = Some(Box::new(TemplateRefOrNode::Node(node)));
+            }
+        }
     }
 
     /// Find all nodes that are listening for an event, sorted by there height in the dom progressing starting at the bottom and progressing up.
@@ -280,7 +725,7 @@ impl<S: State> RealDom<S> {
     pub fn get_listening_sorted(&self, event: &'static str) -> Vec<&Node<S>> {
         if let Some(nodes) = self.nodes_listening.get(event) {
             let mut listening: Vec<_> = nodes.iter().map(|id| &self[*id]).collect();
-            listening.sort_by(|n1, n2| (n1.height).cmp(&n2.height).reverse());
+            listening.sort_by(|n1, n2| (n1.node_data.height).cmp(&n2.node_data.height).reverse());
             listening
         } else {
             Vec::new()
@@ -296,23 +741,39 @@ impl<S: State> RealDom<S> {
             VNode::Element(e) => {
                 if let Some(id) = e.id.get() {
                     let dom_node = &self[id];
-                    match &dom_node.node_type {
+                    match &dom_node.node_data.node_type {
                         NodeType::Element {
                             tag,
                             namespace,
                             children,
+                            attributes,
+                            listeners,
                         } => {
                             tag == e.tag
                                 && namespace == &e.namespace
-                                && children.iter().copied().collect::<FxHashSet<_>>()
-                                    == e.children
-                                        .iter()
-                                        .map(|c| c.mounted_id())
-                                        .collect::<FxHashSet<_>>()
+                                && children
+                                    .iter()
+                                    .zip(
+                                        e.children
+                                            .iter()
+                                            .map(|c| GlobalNodeId::VNodeId(c.mounted_id())),
+                                    )
+                                    .all(|(c1, c2)| *c1 == c2)
                                 && e.children.iter().all(|c| {
                                     self.contains_node(c)
-                                        && self[c.mounted_id()].parent == e.id.get()
+                                        && self[GlobalNodeId::VNodeId(c.mounted_id())]
+                                            .node_data
+                                            .parent
+                                            == e.id.get().map(|id| GlobalNodeId::VNodeId(id))
                                 })
+                                && attributes
+                                    .iter()
+                                    .zip(e.attributes.iter())
+                                    .all(|((disc, val), b)| *disc == b.attribute && *val == b.value)
+                                && listeners
+                                    .iter()
+                                    .zip(e.listeners.iter())
+                                    .all(|(a, b)| *a == b.event)
                         }
                         _ => false,
                     }
@@ -324,8 +785,8 @@ impl<S: State> RealDom<S> {
             VNode::Placeholder(_) => true,
             VNode::Text(t) => {
                 if let Some(id) = t.id.get() {
-                    let dom_node = &self[id];
-                    match &dom_node.node_type {
+                    let dom_node = &self[GlobalNodeId::VNodeId(id)];
+                    match &dom_node.node_data.node_type {
                         NodeType::Text { text } => t.text == text,
                         _ => false,
                     }
@@ -333,6 +794,7 @@ impl<S: State> RealDom<S> {
                     true
                 }
             }
+            VNode::TemplateRef(_) => todo!(),
         }
     }
 
@@ -349,16 +811,20 @@ impl<S: State> RealDom<S> {
 
     /// Call a function for each node in the dom, depth first.
     pub fn traverse_depth_first(&self, mut f: impl FnMut(&Node<S>)) {
-        fn inner<S: State>(dom: &RealDom<S>, id: ElementId, f: &mut impl FnMut(&Node<S>)) {
+        fn inner<S: State>(dom: &RealDom<S>, id: GlobalNodeId, f: &mut impl FnMut(&Node<S>)) {
             let node = &dom[id];
             f(node);
-            if let NodeType::Element { children, .. } = &node.node_type {
+            if let NodeType::Element { children, .. } = &node.node_data.node_type {
                 for c in children {
                     inner(dom, *c, f);
                 }
             }
         }
-        if let NodeType::Element { children, .. } = &self[ElementId(self.root)].node_type {
+        if let NodeType::Element { children, .. } = &self
+            [GlobalNodeId::VNodeId(ElementId(self.root))]
+        .node_data
+        .node_type
+        {
             for c in children {
                 inner(self, *c, &mut f);
             }
@@ -367,47 +833,139 @@ impl<S: State> RealDom<S> {
 
     /// Call a function for each node in the dom, depth first.
     pub fn traverse_depth_first_mut(&mut self, mut f: impl FnMut(&mut Node<S>)) {
-        fn inner<S: State>(dom: &mut RealDom<S>, id: ElementId, f: &mut impl FnMut(&mut Node<S>)) {
+        fn inner<S: State>(
+            dom: &mut RealDom<S>,
+            id: GlobalNodeId,
+            f: &mut impl FnMut(&mut Node<S>),
+        ) {
             let node = &mut dom[id];
             f(node);
-            if let NodeType::Element { children, .. } = &mut node.node_type {
+            if let NodeType::Element { children, .. } = &mut node.node_data.node_type {
                 for c in children.clone() {
                     inner(dom, c, f);
                 }
             }
         }
         let root = self.root;
-        if let NodeType::Element { children, .. } = &mut self[ElementId(root)].node_type {
+        if let NodeType::Element { children, .. } = &mut self
+            [GlobalNodeId::VNodeId(ElementId(root))]
+        .node_data
+        .node_type
+        {
             for c in children.clone() {
                 inner(self, c, &mut f);
             }
         }
     }
+
+    pub fn decode_id(&self, id: impl Into<u64>) -> GlobalNodeId {
+        let mut id = id.into();
+        if id >= JS_MAX_INT / 2 {
+            id -= JS_MAX_INT / 2;
+            if self.current_template().is_some() {
+                GlobalNodeId::TemplateId {
+                    template_ref_id: ElementId(0),
+                    template_node_id: TemplateNodeId(id as usize),
+                }
+            } else {
+                let template_ref_id = *self.template_stack.last().unwrap();
+                let template_node_id = TemplateNodeId(id as usize);
+                GlobalNodeId::TemplateId {
+                    template_ref_id,
+                    template_node_id,
+                }
+            }
+        } else {
+            GlobalNodeId::VNodeId(ElementId(id as usize))
+        }
+    }
+
+    pub fn split<'a>(
+        &'a mut self,
+    ) -> (
+        impl Traversable<Id = GlobalNodeId, Node = S> + 'a,
+        impl Traversable<Id = GlobalNodeId, Node = NodeData> + 'a,
+    ) {
+        let raw = self as *mut Self;
+        // this is safe beacuse the traversable trait does not allow mutation of the position of elements, and within elements the access is disjoint.
+        (
+            unsafe { &mut *raw }.map(|n| &n.state, |n| &mut n.state),
+            unsafe { &mut *raw }.map(|n| &n.node_data, |n| &mut n.node_data),
+        )
+    }
 }
 
 impl<S: State> Index<ElementId> for RealDom<S> {
     type Output = Node<S>;
 
     fn index(&self, idx: ElementId) -> &Self::Output {
+        self.get(GlobalNodeId::VNodeId(idx)).unwrap()
+    }
+}
+
+impl<S: State> Index<GlobalNodeId> for RealDom<S> {
+    type Output = Node<S>;
+
+    fn index(&self, idx: GlobalNodeId) -> &Self::Output {
         self.get(idx).unwrap()
     }
 }
 
+impl<S: State> Index<usize> for RealDom<S> {
+    type Output = Node<S>;
+
+    fn index(&self, idx: usize) -> &Self::Output {
+        if let Some(template) = self.current_template() {
+            template.nodes[idx].as_ref().unwrap()
+        } else {
+            &self[GlobalNodeId::VNodeId(dioxus_core::ElementId(idx))]
+        }
+    }
+}
+
 impl<S: State> IndexMut<ElementId> for RealDom<S> {
     fn index_mut(&mut self, idx: ElementId) -> &mut Self::Output {
+        self.get_mut(GlobalNodeId::VNodeId(idx)).unwrap()
+    }
+}
+
+impl<S: State> IndexMut<GlobalNodeId> for RealDom<S> {
+    fn index_mut(&mut self, idx: GlobalNodeId) -> &mut Self::Output {
         self.get_mut(idx).unwrap()
     }
 }
 
+impl<S: State> IndexMut<usize> for RealDom<S> {
+    fn index_mut(&mut self, idx: usize) -> &mut Self::Output {
+        if self.template_stack.is_empty() {
+            &mut self[GlobalNodeId::VNodeId(dioxus_core::ElementId(idx))]
+        } else {
+            self.current_template_mut()
+                .unwrap()
+                .nodes
+                .get_mut(idx)
+                .unwrap()
+                .as_mut()
+                .unwrap()
+        }
+    }
+}
+
 /// The node is stored client side and stores only basic data about the node.
 #[derive(Debug, Clone)]
 pub struct Node<S: State> {
+    /// The transformed state of the node.
+    pub state: S,
+    /// The raw data for the node
+    pub node_data: NodeData,
+}
+
+#[derive(Debug, Clone)]
+pub struct NodeData {
     /// The id of the node this node was created from.
-    pub id: ElementId,
+    pub id: GlobalNodeId,
     /// The parent id of the node.
-    pub parent: Option<ElementId>,
-    /// State of the node.
-    pub state: S,
+    pub parent: Option<GlobalNodeId>,
     /// Additional inforation specific to the node type
     pub node_type: NodeType,
     /// The number of parents before the root node. The root node has height 1.
@@ -423,67 +981,123 @@ pub enum NodeType {
     Element {
         tag: String,
         namespace: Option<&'static str>,
-        children: Vec<ElementId>,
+        attributes: FxHashMap<OwnedAttributeDiscription, OwnedAttributeValue>,
+        listeners: FxHashSet<String>,
+        children: Vec<GlobalNodeId>,
     },
     Placeholder,
 }
 
 impl<S: State> Node<S> {
-    fn new(id: u64, node_type: NodeType) -> Self {
+    fn new(id: GlobalNodeId, node_type: NodeType) -> Self {
         Node {
-            id: ElementId(id as usize),
-            parent: None,
-            node_type,
             state: S::default(),
-            height: 0,
+            node_data: NodeData {
+                id,
+                parent: None,
+                node_type,
+                height: 0,
+            },
         }
     }
 
-    /// Returns a reference to the element that this node refrences.
-    pub fn element<'b>(&self, vdom: &'b VirtualDom) -> &'b VNode<'b> {
-        vdom.get_element(self.id).unwrap()
-    }
-
     /// link a child node
-    fn add_child(&mut self, child: ElementId) {
-        if let NodeType::Element { children, .. } = &mut self.node_type {
+    fn add_child(&mut self, child: GlobalNodeId) {
+        if let NodeType::Element { children, .. } = &mut self.node_data.node_type {
             children.push(child);
         }
     }
 
     /// remove a child node
-    fn remove_child(&mut self, child: ElementId) {
-        if let NodeType::Element { children, .. } = &mut self.node_type {
+    fn remove_child(&mut self, child: GlobalNodeId) {
+        if let NodeType::Element { children, .. } = &mut self.node_data.node_type {
             children.retain(|c| c != &child);
         }
     }
 
     /// link the parent node
-    fn set_parent(&mut self, parent: ElementId) {
-        self.parent = Some(parent);
+    fn set_parent(&mut self, parent: GlobalNodeId) {
+        self.node_data.parent = Some(parent);
     }
 }
 
 impl<T: State> Traversable for RealDom<T> {
-    type Id = ElementId;
+    type Id = GlobalNodeId;
     type Node = Node<T>;
 
     fn height(&self, id: Self::Id) -> Option<u16> {
-        Some(<Self as Traversable>::get(self, id)?.height)
+        let node = <Self as Traversable>::get(self, id);
+        Some(node?.node_data.height)
     }
 
     fn get(&self, id: Self::Id) -> Option<&Self::Node> {
-        self.nodes.get(id.0)?.as_ref()
+        match id {
+            GlobalNodeId::VNodeId(id) => match self.nodes.get(id.0)?.as_ref()?.as_ref() {
+                TemplateRefOrNode::Ref { .. } => panic!("Template nodes should not be indexable"),
+                TemplateRefOrNode::Node(n) => Some(n),
+            },
+            GlobalNodeId::TemplateId {
+                template_ref_id,
+                template_node_id,
+            } => {
+                if self.template_in_progress.is_some() {
+                    let template = self.current_template().unwrap();
+                    template.nodes[template_node_id.0]
+                        .as_ref()
+                        .map(|n| n.as_ref())
+                } else {
+                    let nodes = match self.nodes.get(template_ref_id.0)?.as_ref()?.as_ref() {
+                        TemplateRefOrNode::Ref { nodes, .. } => nodes,
+                        TemplateRefOrNode::Node(_) => {
+                            panic!("Expected template ref")
+                        }
+                    };
+
+                    nodes
+                        .get(template_node_id.0)
+                        .map(|n| n.as_ref())
+                        .flatten()
+                        .map(|n| n.as_ref())
+                }
+            }
+        }
     }
 
     fn get_mut(&mut self, id: Self::Id) -> Option<&mut Self::Node> {
-        self.nodes.get_mut(id.0)?.as_mut()
+        match id {
+            GlobalNodeId::VNodeId(id) => match self.nodes.get_mut(id.0)?.as_mut()?.as_mut() {
+                TemplateRefOrNode::Ref { .. } => panic!("Template nodes should not be indexable"),
+                TemplateRefOrNode::Node(n) => Some(n),
+            },
+            GlobalNodeId::TemplateId {
+                template_ref_id,
+                template_node_id,
+            } => {
+                if self.template_in_progress.is_some() {
+                    let template = self.current_template_mut().unwrap();
+                    template.nodes[template_node_id.0]
+                        .as_mut()
+                        .map(|n| n.as_mut())
+                } else {
+                    let nodes = match self.nodes.get_mut(template_ref_id.0)?.as_mut()?.as_mut() {
+                        TemplateRefOrNode::Ref { nodes, .. } => nodes,
+                        TemplateRefOrNode::Node(_) => panic!("Expected template ref"),
+                    };
+
+                    nodes
+                        .get_mut(template_node_id.0)
+                        .map(|n| n.as_mut())
+                        .flatten()
+                        .map(|n| n.as_mut())
+                }
+            }
+        }
     }
 
     fn children(&self, id: Self::Id) -> &[Self::Id] {
         if let Some(node) = <Self as Traversable>::get(self, id) {
-            match &node.node_type {
-                NodeType::Element { children, .. } => children,
+            match &node.node_data.node_type {
+                NodeType::Element { children, .. } => &children,
                 _ => &[],
             }
         } else {
@@ -492,6 +1106,61 @@ impl<T: State> Traversable for RealDom<T> {
     }
 
     fn parent(&self, id: Self::Id) -> Option<Self::Id> {
-        <Self as Traversable>::get(self, id).and_then(|n| n.parent)
+        match id {
+            GlobalNodeId::VNodeId(id) => self.nodes.get(id.0).as_ref()?.as_ref()?.parent(),
+            GlobalNodeId::TemplateId {
+                template_ref_id,
+                template_node_id,
+            } => {
+                if self.template_in_progress.is_some() {
+                    let template = self.current_template().unwrap();
+                    template.nodes[template_node_id.0]
+                        .as_ref()
+                        .map(|n| n.as_ref())
+                } else {
+                    let nodes = match self.nodes.get(template_ref_id.0)?.as_ref()?.as_ref() {
+                        TemplateRefOrNode::Ref { nodes, .. } => nodes,
+                        TemplateRefOrNode::Node(_) => panic!("Expected template ref"),
+                    };
+
+                    nodes
+                        .get(template_node_id.0)
+                        .map(|n| n.as_deref())
+                        .flatten()
+                }?
+                .node_data
+                .parent
+            }
+        }
     }
 }
+
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
+pub struct OwnedAttributeDiscription {
+    pub name: String,
+    pub namespace: Option<String>,
+    pub volatile: bool,
+}
+
+impl PartialEq<AttributeDiscription> for OwnedAttributeDiscription {
+    fn eq(&self, other: &AttributeDiscription) -> bool {
+        self.name == other.name
+            && match (&self.namespace, other.namespace) {
+                (Some(a), Some(b)) => a == b,
+                (None, None) => true,
+                _ => false,
+            }
+            && self.volatile == other.volatile
+    }
+}
+
+/// An attribute on a DOM node, such as `id="my-thing"` or
+/// `href="https://example.com"`.
+#[derive(Clone, Debug)]
+pub struct OwnedAttributeView<'a> {
+    /// The discription of the attribute.
+    pub attribute: &'a OwnedAttributeDiscription,
+
+    /// The value of the attribute.
+    pub value: &'a OwnedAttributeValue,
+}

+ 10 - 5
packages/native-core/src/state.rs

@@ -1,10 +1,11 @@
 use std::{cmp::Ordering, fmt::Debug};
 
 use anymap::AnyMap;
-use dioxus_core::ElementId;
+use dioxus_core::GlobalNodeId;
 use fxhash::FxHashSet;
 
 use crate::node_ref::{NodeMask, NodeView};
+use crate::real_dom::NodeData;
 use crate::traversable::Traversable;
 
 /// Join two sorted iterators
@@ -206,12 +207,16 @@ pub trait NodeDepState<DepState = ()> {
 /// Do not implement this trait. It is only meant to be derived and used through [crate::real_dom::RealDom].
 pub trait State: Default + Clone {
     #[doc(hidden)]
-    fn update<'a, T: Traversable<Node = Self, Id = ElementId>>(
-        dirty: &[(ElementId, NodeMask)],
+    fn update<
+        'a,
+        T: Traversable<Node = Self, Id = GlobalNodeId>,
+        T2: Traversable<Node = NodeData, Id = GlobalNodeId>,
+    >(
+        dirty: &[(GlobalNodeId, NodeMask)],
         state_tree: &'a mut T,
-        vdom: &'a dioxus_core::VirtualDom,
+        rdom: &'a T2,
         ctx: &AnyMap,
-    ) -> FxHashSet<ElementId>;
+    ) -> FxHashSet<GlobalNodeId>;
 }
 
 impl ChildDepState for () {

+ 44 - 0
packages/native-core/src/template.rs

@@ -0,0 +1,44 @@
+use dioxus_core::{GlobalNodeId, TemplateNodeId};
+
+use crate::{real_dom::Node, state::State};
+
+#[derive(Debug, Default)]
+pub struct NativeTemplate<S: State> {
+    pub(crate) nodes: Vec<Option<Box<Node<S>>>>,
+    pub(crate) roots: Vec<usize>,
+}
+
+impl<S: State> NativeTemplate<S> {
+    pub fn insert(&mut self, node: Node<S>) {
+        let id = node.node_data.id;
+        match id {
+            GlobalNodeId::TemplateId {
+                template_node_id: TemplateNodeId(id),
+                ..
+            } => {
+                self.nodes.resize(id + 1, None);
+                self.nodes[id] = Some(Box::new(node));
+            }
+            GlobalNodeId::VNodeId(_) => panic!("Cannot insert a VNode into a template"),
+        }
+    }
+}
+
+#[derive(Debug)]
+pub(crate) enum TemplateRefOrNode<S: State> {
+    Ref {
+        nodes: Vec<Option<Box<Node<S>>>>,
+        roots: Vec<GlobalNodeId>,
+        parent: Option<GlobalNodeId>,
+    },
+    Node(Node<S>),
+}
+
+impl<S: State> TemplateRefOrNode<S> {
+    pub fn parent(&self) -> Option<GlobalNodeId> {
+        match self {
+            TemplateRefOrNode::Ref { parent, .. } => *parent,
+            TemplateRefOrNode::Node(node) => node.node_data.parent,
+        }
+    }
+}

+ 30 - 34
packages/native-core/src/utils.rs

@@ -2,16 +2,16 @@ use crate::{
     real_dom::{NodeType, RealDom},
     state::State,
 };
-use dioxus_core::{DomEdit, ElementId, Mutations};
+use dioxus_core::{DomEdit, ElementId, GlobalNodeId, Mutations};
 
 pub enum ElementProduced {
     /// The iterator produced an element by progressing to the next node in a depth first order.
-    Progressed(ElementId),
+    Progressed(GlobalNodeId),
     /// The iterator reached the end of the tree and looped back to the root
-    Looped(ElementId),
+    Looped(GlobalNodeId),
 }
 impl ElementProduced {
-    pub fn id(&self) -> ElementId {
+    pub fn id(&self) -> GlobalNodeId {
         match self {
             ElementProduced::Progressed(id) => *id,
             ElementProduced::Looped(id) => *id,
@@ -50,13 +50,16 @@ impl NodePosition {
 /// The iterator loops around when it reaches the end or the beginning.
 pub struct PersistantElementIter {
     // stack of elements and fragments
-    stack: smallvec::SmallVec<[(ElementId, NodePosition); 5]>,
+    stack: smallvec::SmallVec<[(GlobalNodeId, NodePosition); 5]>,
 }
 
 impl Default for PersistantElementIter {
     fn default() -> Self {
         PersistantElementIter {
-            stack: smallvec::smallvec![(ElementId(0), NodePosition::AtNode)],
+            stack: smallvec::smallvec![(
+                GlobalNodeId::VNodeId(dioxus_core::ElementId(0)),
+                NodePosition::AtNode
+            )],
         }
     }
 }
@@ -74,8 +77,10 @@ impl PersistantElementIter {
             .edits
             .iter()
             .filter_map(|e| {
+                // nodes within templates will never be removed
                 if let DomEdit::Remove { root } = e {
-                    Some(*root)
+                    let id = rdom.decode_id(*root);
+                    Some(id)
                 } else {
                     None
                 }
@@ -85,7 +90,7 @@ impl PersistantElementIter {
         if let Some(r) = self
             .stack
             .iter()
-            .position(|(el_id, _)| ids_removed.iter().any(|id| el_id.as_u64() == *id))
+            .position(|(el_id, _)| ids_removed.iter().any(|id| el_id == id))
         {
             self.stack.truncate(r);
             changed = true;
@@ -93,33 +98,24 @@ impl PersistantElementIter {
         // if a child is removed or inserted before or at the current element, update the child index
         for (el_id, child_idx) in self.stack.iter_mut() {
             if let NodePosition::InChild(child_idx) = child_idx {
-                if let NodeType::Element { children, .. } = &rdom[*el_id].node_type {
+                if let NodeType::Element { children, .. } = &rdom[*el_id].node_data.node_type {
                     for m in &mutations.edits {
                         match m {
                             DomEdit::Remove { root } => {
-                                if children
-                                    .iter()
-                                    .take(*child_idx + 1)
-                                    .any(|c| c.as_u64() == *root)
-                                {
+                                let id = rdom.decode_id(*root);
+                                if children.iter().take(*child_idx + 1).any(|c| *c == id) {
                                     *child_idx -= 1;
                                 }
                             }
                             DomEdit::InsertBefore { root, n } => {
-                                if children
-                                    .iter()
-                                    .take(*child_idx + 1)
-                                    .any(|c| c.as_u64() == *root)
-                                {
+                                let id = rdom.decode_id(*root);
+                                if children.iter().take(*child_idx + 1).any(|c| *c == id) {
                                     *child_idx += *n as usize;
                                 }
                             }
                             DomEdit::InsertAfter { root, n } => {
-                                if children
-                                    .iter()
-                                    .take(*child_idx)
-                                    .any(|c| c.as_u64() == *root)
-                                {
+                                let id = rdom.decode_id(*root);
+                                if children.iter().take(*child_idx).any(|c| *c == id) {
                                     *child_idx += *n as usize;
                                 }
                             }
@@ -135,14 +131,14 @@ impl PersistantElementIter {
     /// get the next element
     pub fn next<S: State>(&mut self, rdom: &RealDom<S>) -> ElementProduced {
         if self.stack.is_empty() {
-            let id = ElementId(0);
+            let id = GlobalNodeId::VNodeId(ElementId(0));
             let new = (id, NodePosition::AtNode);
             self.stack.push(new);
             ElementProduced::Looped(id)
         } else {
             let (last, o_child_idx) = self.stack.last_mut().unwrap();
             let node = &rdom[*last];
-            match &node.node_type {
+            match &node.node_data.node_type {
                 NodeType::Element { children, .. } => {
                     *o_child_idx = o_child_idx.map(|i| i + 1);
                     // if we have children, go to the next child
@@ -152,7 +148,7 @@ impl PersistantElementIter {
                         self.next(rdom)
                     } else {
                         let id = children[child_idx];
-                        if let NodeType::Element { .. } = &rdom[id].node_type {
+                        if let NodeType::Element { .. } = &rdom[id].node_data.node_type {
                             self.stack.push((id, NodePosition::AtNode));
                         }
                         ElementProduced::Progressed(id)
@@ -171,11 +167,11 @@ impl PersistantElementIter {
     pub fn prev<S: State>(&mut self, rdom: &RealDom<S>) -> ElementProduced {
         // recursively add the last child element to the stack
         fn push_back<S: State>(
-            stack: &mut smallvec::SmallVec<[(ElementId, NodePosition); 5]>,
-            new_node: ElementId,
+            stack: &mut smallvec::SmallVec<[(GlobalNodeId, NodePosition); 5]>,
+            new_node: GlobalNodeId,
             rdom: &RealDom<S>,
-        ) -> ElementId {
-            match &rdom[new_node].node_type {
+        ) -> GlobalNodeId {
+            match &rdom[new_node].node_data.node_type {
                 NodeType::Element { children, .. } => {
                     if children.is_empty() {
                         new_node
@@ -188,12 +184,12 @@ impl PersistantElementIter {
             }
         }
         if self.stack.is_empty() {
-            let new_node = ElementId(0);
+            let new_node = GlobalNodeId::VNodeId(ElementId(0));
             ElementProduced::Looped(push_back(&mut self.stack, new_node, rdom))
         } else {
             let (last, o_child_idx) = self.stack.last_mut().unwrap();
             let node = &rdom[*last];
-            match &node.node_type {
+            match &node.node_data.node_type {
                 NodeType::Element { children, .. } => {
                     // if we have children, go to the next child
                     if let NodePosition::InChild(0) = o_child_idx {
@@ -227,7 +223,7 @@ impl PersistantElementIter {
         }
     }
 
-    fn pop(&mut self) -> ElementId {
+    fn pop(&mut self) -> GlobalNodeId {
         self.stack.pop().unwrap().0
     }
 }

+ 6 - 1
packages/rsx/Cargo.toml

@@ -7,6 +7,11 @@ license = "MIT/Apache-2.0"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-proc-macro2 = { version = "1.0" }
+proc-macro2 = { version = "1.0", features = ["span-locations"] }
 syn = { version = "1.0", features = ["full", "extra-traits"] }
 quote = { version = "1.0" }
+dioxus-core = { path = "../core", features = ["serialize", "hot-reload"] }
+serde = { version = "1.0", features = ["derive"] }
+
+[features]
+hot-reload = []

+ 20 - 11
packages/rsx_interpreter/src/attributes.rs → packages/rsx/src/attributes.rs

@@ -1,32 +1,32 @@
 use crate::elements::*;
 
-// map the rsx name of the attribute to the html name of the attribute and the namespace that contains it
+// 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>)> {
+) -> 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))
+            .map(|(_, b)| (*b, None, false))
     } else {
         NO_NAMESPACE_ATTRIBUTES
             .iter()
             .find(|&a| *a == attr)
-            .map(|a| (*a, None))
+            .map(|a| (*a, None, false))
             .or_else(|| {
                 STYLE_ATTRIBUTES
                     .iter()
                     .find(|(a, _)| *a == attr)
-                    .map(|(_, b)| (*b, Some("style")))
+                    .map(|(_, b)| (*b, Some("style"), false))
             })
             .or_else(|| {
                 MAPPED_ATTRIBUTES
                     .iter()
                     .find(|(a, _)| *a == attr)
-                    .map(|(_, b)| (*b, None))
+                    .map(|(_, b)| (*b, None, false))
             })
     }
     .or_else(|| {
@@ -37,8 +37,8 @@ pub fn attrbute_to_static_str(
                     .then(|| {
                         attrs
                             .iter()
-                            .find(|(a, _)| *a == attr)
-                            .map(|(_, b)| (*b, None))
+                            .find(|(a, _, _)| *a == attr)
+                            .map(|(_, b, volitile)| (*b, None, *volitile))
                     })
                     .flatten()
             })
@@ -46,14 +46,24 @@ pub fn attrbute_to_static_str(
     .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)))
+                .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)))
+                .then(|| {
+                    attrs
+                        .iter()
+                        .find(|a| **a == attr)
+                        .map(|a| (*a, None, false))
+                })
                 .flatten()
         })
     })
@@ -628,7 +638,6 @@ mapped_trait_methods! {
 }
 
 pub mod svg {
-
     mapped_trait_methods! {
         accent_height: "accent-height",
         accumulate: "accumulate",

+ 11 - 7
packages/rsx/src/component.rs

@@ -23,7 +23,7 @@ use syn::{
     Token,
 };
 
-#[derive(PartialEq, Eq)]
+#[derive(PartialEq, Eq, Clone, Debug, Hash)]
 pub struct Component {
     pub name: syn::Path,
     pub prop_gen_args: Option<AngleBracketedGenericArguments>,
@@ -174,7 +174,11 @@ impl ToTokens for Component {
         let key_token = match has_key {
             Some(field) => {
                 let inners = &field.content;
-                quote! { Some(format_args_f!(#inners)) }
+                if let ContentField::Formatted(ifmt) = inners {
+                    quote! { Some(#ifmt) }
+                } else {
+                    unreachable!()
+                }
             }
             None => quote! { None },
         };
@@ -193,16 +197,16 @@ impl ToTokens for Component {
 }
 
 // the struct's fields info
-#[derive(PartialEq, Eq)]
+#[derive(PartialEq, Eq, Clone, Debug, Hash)]
 pub struct ComponentField {
     pub name: Ident,
     pub content: ContentField,
 }
 
-#[derive(PartialEq, Eq)]
+#[derive(PartialEq, Eq, Clone, Debug, Hash)]
 pub enum ContentField {
     ManExpr(Expr),
-    Formatted(LitStr),
+    Formatted(IfmtInput),
     OnHandlerRaw(Expr),
 }
 
@@ -211,7 +215,7 @@ impl ToTokens for ContentField {
         match self {
             ContentField::ManExpr(e) => e.to_tokens(tokens),
             ContentField::Formatted(s) => tokens.append_all(quote! {
-                __cx.raw_text(format_args_f!(#s)).0
+                __cx.raw_text(#s).0
             }),
             ContentField::OnHandlerRaw(e) => tokens.append_all(quote! {
                 __cx.event_handler(#e)
@@ -231,7 +235,7 @@ impl Parse for ComponentField {
         }
 
         if name == "key" {
-            let content = ContentField::ManExpr(input.parse()?);
+            let content = ContentField::Formatted(input.parse()?);
             return Ok(Self { name, content });
         }
         if input.peek(LitStr) {

+ 12 - 17
packages/rsx/src/element.rs

@@ -10,10 +10,10 @@ use syn::{
 // =======================================
 // Parse the VNode::Element type
 // =======================================
-#[derive(PartialEq, Eq)]
+#[derive(PartialEq, Eq, Clone, Debug, Hash)]
 pub struct Element {
     pub name: Ident,
-    pub key: Option<LitStr>,
+    pub key: Option<IfmtInput>,
     pub attributes: Vec<ElementAttrNamed>,
     pub children: Vec<BodyNode>,
     pub _is_static: bool,
@@ -46,7 +46,7 @@ impl Parse for Element {
                 content.parse::<Token![:]>()?;
 
                 if content.peek(LitStr) && content.peek2(Token![,]) {
-                    let value = content.parse::<LitStr>()?;
+                    let value = content.parse()?;
                     attributes.push(ElementAttrNamed {
                         el_name: el_name.clone(),
                         attr: ElementAttr::CustomAttrText { name, value },
@@ -164,7 +164,7 @@ impl ToTokens for Element {
         let children = &self.children;
 
         let key = match &self.key {
-            Some(ty) => quote! { Some(format_args_f!(#ty)) },
+            Some(ty) => quote! { Some(#ty) },
             None => quote! { None },
         };
 
@@ -190,16 +190,16 @@ impl ToTokens for Element {
     }
 }
 
-#[derive(PartialEq, Eq)]
+#[derive(PartialEq, Eq, Clone, Debug, Hash)]
 pub enum ElementAttr {
     /// attribute: "valuee {}"
-    AttrText { name: Ident, value: LitStr },
+    AttrText { name: Ident, value: IfmtInput },
 
     /// attribute: true,
     AttrExpression { name: Ident, value: Expr },
 
     /// "attribute": "value {}"
-    CustomAttrText { name: LitStr, value: LitStr },
+    CustomAttrText { name: LitStr, value: IfmtInput },
 
     /// "attribute": true,
     CustomAttrExpression { name: LitStr, value: Expr },
@@ -231,7 +231,7 @@ impl ElementAttr {
     }
 }
 
-#[derive(PartialEq, Eq)]
+#[derive(PartialEq, Eq, Clone, Debug, Hash)]
 pub struct ElementAttrNamed {
     pub el_name: Ident,
     pub attr: ElementAttr,
@@ -244,29 +244,24 @@ impl ToTokens for ElementAttrNamed {
         tokens.append_all(match attr {
             ElementAttr::AttrText { name, value } => {
                 quote! {
-                    dioxus_elements::#el_name.#name(__cx, format_args_f!(#value))
+                    __cx.attr_disciption( dioxus_elements::#el_name::#name, #value)
                 }
             }
             ElementAttr::AttrExpression { name, value } => {
                 quote! {
-                    dioxus_elements::#el_name.#name(__cx, #value)
+                    __cx.attr_disciption( dioxus_elements::#el_name::#name, #value)
                 }
             }
             ElementAttr::CustomAttrText { name, value } => {
                 quote! {
-                    __cx.attr( #name, format_args_f!(#value), None, false )
+                    __cx.attr( #name, #value, None, false )
                 }
             }
             ElementAttr::CustomAttrExpression { name, value } => {
                 quote! {
-                    __cx.attr( #name, format_args_f!(#value), None, false )
+                    __cx.attr( #name, #value, None, false )
                 }
             }
-            // ElementAttr::EventClosure { name, closure } => {
-            //     quote! {
-            //         dioxus_elements::on::#name(__cx, #closure)
-            //     }
-            // }
             ElementAttr::EventTokens { name, tokens } => {
                 quote! {
                     dioxus_elements::on::#name(__cx, #tokens)

+ 16 - 8
packages/rsx_interpreter/src/elements.rs → packages/rsx/src/elements.rs

@@ -65,14 +65,22 @@ macro_rules! builder_constructors {
         ];
     };
 }
-pub const ELEMENTS_WITH_MAPPED_ATTRIBUTES: &[(&str, &[(&str, &str)])] = &[
-    ("script", &[("r#type", "type"), ("r#script", "script")]),
-    ("button", &[("r#type", "type")]),
-    ("select", &[("value", "value")]),
-    ("option", &[("selected", "selected")]),
-    ("textarea", &[("value", "value")]),
-    ("label", &[("r#for", "for")]),
-    ("input", &[("r#type", "type"), ("value", "value")]),
+
+/// All attributes that are tied to a specific element that either have a different name, or are volitile
+pub const ELEMENTS_WITH_MAPPED_ATTRIBUTES: &[(&str, &[(&str, &str, bool)])] = &[
+    (
+        "script",
+        &[("r#type", "type", false), ("r#script", "script", false)],
+    ),
+    ("button", &[("r#type", "type", false)]),
+    ("select", &[("value", "value", true)]),
+    ("option", &[("selected", "selected", true)]),
+    ("textarea", &[("value", "value", true)]),
+    ("label", &[("r#for", "for", false)]),
+    (
+        "input",
+        &[("r#type", "type", false), ("value", "value", true)],
+    ),
 ];
 
 // Organized in the same order as

+ 3 - 4
packages/rsx_interpreter/src/error.rs → packages/rsx/src/error.rs

@@ -1,9 +1,8 @@
 use std::fmt::Display;
 
+use dioxus_core::OwnedCodeLocation;
 use serde::{Deserialize, Serialize};
 
-use crate::CodeLocation;
-
 /// An error produced when interperting the rsx
 #[derive(Debug, Serialize, Deserialize)]
 pub enum Error {
@@ -23,11 +22,11 @@ pub enum RecompileReason {
 #[derive(Debug, Serialize, Deserialize)]
 pub struct ParseError {
     pub message: String,
-    pub location: CodeLocation,
+    pub location: OwnedCodeLocation,
 }
 
 impl ParseError {
-    pub fn new(error: syn::Error, mut location: CodeLocation) -> Self {
+    pub fn new(error: syn::Error, mut location: OwnedCodeLocation) -> Self {
         let message = error.to_string();
         let syn_call_site = error.span().start();
         location.line += syn_call_site.line as u32;

+ 131 - 87
packages/rsx/src/ifmt.rs

@@ -2,87 +2,37 @@ use std::{collections::HashSet, str::FromStr};
 
 use proc_macro2::{Span, TokenStream};
 
-use quote::{quote, ToTokens};
+use quote::{quote, ToTokens, TokenStreamExt};
 use syn::{
     parse::{Parse, ParseStream},
     *,
 };
 
 pub fn format_args_f_impl(input: IfmtInput) -> Result<TokenStream> {
-    // build format_literal
-    let mut format_literal = String::new();
-    let mut expr_counter = 0;
-    for segment in input.segments.iter() {
-        match segment {
-            Segment::Literal(s) => format_literal += &s.replace('{', "{{").replace('}', "}}"),
-            Segment::Formatted {
-                format_args,
-                segment,
-            } => {
-                format_literal += "{";
-                match segment {
-                    FormattedSegment::Expr(_) => {
-                        format_literal += &expr_counter.to_string();
-                        expr_counter += 1;
-                    }
-                    FormattedSegment::Ident(ident) => {
-                        format_literal += &ident.to_string();
-                    }
-                }
-                format_literal += ":";
-                format_literal += format_args;
-                format_literal += "}";
-            }
-        }
-    }
-
-    let positional_args = input.segments.iter().filter_map(|seg| {
-        if let Segment::Formatted {
-            segment: FormattedSegment::Expr(expr),
-            ..
-        } = seg
-        {
-            Some(expr)
-        } else {
-            None
-        }
-    });
-
-    // remove duplicate idents
-    let named_args_idents: HashSet<_> = input
-        .segments
-        .iter()
-        .filter_map(|seg| {
-            if let Segment::Formatted {
-                segment: FormattedSegment::Ident(ident),
-                ..
-            } = seg
-            {
-                Some(ident)
-            } else {
-                None
-            }
-        })
-        .collect();
-    let named_args = named_args_idents
-        .iter()
-        .map(|ident| quote!(#ident = #ident));
-
-    Ok(quote! {
-        format_args!(
-            #format_literal
-            #(, #positional_args)*
-            #(, #named_args)*
-        )
-    })
+    Ok(input.into_token_stream())
 }
 
 #[allow(dead_code)] // dumb compiler does not see the struct being used...
-#[derive(Debug)]
+#[derive(Debug, PartialEq, Eq, Clone, Hash)]
 pub struct IfmtInput {
+    pub source: Option<LitStr>,
     pub segments: Vec<Segment>,
 }
 
+impl IfmtInput {
+    pub fn to_static(&self) -> Option<String> {
+        self.segments
+            .iter()
+            .try_fold(String::new(), |acc, segment| {
+                if let Segment::Literal(seg) = segment {
+                    Some(acc + seg)
+                } else {
+                    None
+                }
+            })
+    }
+}
+
 impl FromStr for IfmtInput {
     type Err = syn::Error;
 
@@ -96,7 +46,9 @@ impl FromStr for IfmtInput {
                     current_literal.push(c);
                     continue;
                 }
-                segments.push(Segment::Literal(current_literal));
+                if !current_literal.is_empty() {
+                    segments.push(Segment::Literal(current_literal));
+                }
                 current_literal = String::new();
                 let mut current_captured = String::new();
                 while let Some(c) = chars.next() {
@@ -104,10 +56,10 @@ impl FromStr for IfmtInput {
                         let mut current_format_args = String::new();
                         for c in chars.by_ref() {
                             if c == '}' {
-                                segments.push(Segment::Formatted {
+                                segments.push(Segment::Formatted(FormattedSegment {
                                     format_args: current_format_args,
-                                    segment: FormattedSegment::parse(&current_captured)?,
-                                });
+                                    segment: FormattedSegmentType::parse(&current_captured)?,
+                                }));
                                 break;
                             }
                             current_format_args.push(c);
@@ -115,10 +67,10 @@ impl FromStr for IfmtInput {
                         break;
                     }
                     if c == '}' {
-                        segments.push(Segment::Formatted {
+                        segments.push(Segment::Formatted(FormattedSegment {
                             format_args: String::new(),
-                            segment: FormattedSegment::parse(&current_captured)?,
-                        });
+                            segment: FormattedSegmentType::parse(&current_captured)?,
+                        }));
                         break;
                     }
                     current_captured.push(c);
@@ -138,27 +90,117 @@ impl FromStr for IfmtInput {
                 current_literal.push(c);
             }
         }
-        segments.push(Segment::Literal(current_literal));
-        Ok(Self { segments })
+        if !current_literal.is_empty() {
+            segments.push(Segment::Literal(current_literal));
+        }
+        Ok(Self {
+            segments,
+            source: None,
+        })
+    }
+}
+
+impl ToTokens for IfmtInput {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        // build format_literal
+        let mut format_literal = String::new();
+        let mut expr_counter = 0;
+        for segment in self.segments.iter() {
+            match segment {
+                Segment::Literal(s) => format_literal += &s.replace('{', "{{").replace('}', "}}"),
+                Segment::Formatted(FormattedSegment {
+                    format_args,
+                    segment,
+                }) => {
+                    format_literal += "{";
+                    match segment {
+                        FormattedSegmentType::Expr(_) => {
+                            format_literal += &expr_counter.to_string();
+                            expr_counter += 1;
+                        }
+                        FormattedSegmentType::Ident(ident) => {
+                            format_literal += &ident.to_string();
+                        }
+                    }
+                    format_literal += ":";
+                    format_literal += format_args;
+                    format_literal += "}";
+                }
+            }
+        }
+
+        let positional_args = self.segments.iter().filter_map(|seg| {
+            if let Segment::Formatted(FormattedSegment {
+                segment: FormattedSegmentType::Expr(expr),
+                ..
+            }) = seg
+            {
+                Some(expr)
+            } else {
+                None
+            }
+        });
+
+        // remove duplicate idents
+        let named_args_idents: HashSet<_> = self
+            .segments
+            .iter()
+            .filter_map(|seg| {
+                if let Segment::Formatted(FormattedSegment {
+                    segment: FormattedSegmentType::Ident(ident),
+                    ..
+                }) = seg
+                {
+                    Some(ident)
+                } else {
+                    None
+                }
+            })
+            .collect();
+        let named_args = named_args_idents
+            .iter()
+            .map(|ident| quote!(#ident = #ident));
+
+        quote! {
+            format_args!(
+                #format_literal
+                #(, #positional_args)*
+                #(, #named_args)*
+            )
+        }
+        .to_tokens(tokens)
     }
 }
 
-#[derive(Debug)]
+#[derive(Debug, PartialEq, Eq, Clone, Hash)]
 pub enum Segment {
     Literal(String),
-    Formatted {
-        format_args: String,
-        segment: FormattedSegment,
-    },
+    Formatted(FormattedSegment),
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Hash)]
+pub struct FormattedSegment {
+    format_args: String,
+    segment: FormattedSegmentType,
 }
 
-#[derive(Debug)]
-pub enum FormattedSegment {
+impl ToTokens for FormattedSegment {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        let (fmt, seg) = (&self.format_args, &self.segment);
+        let fmt = format!("{{0:{}}}", fmt);
+        tokens.append_all(quote! {
+            format_args!(#fmt, #seg)
+        });
+    }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Hash)]
+pub enum FormattedSegmentType {
     Expr(Box<Expr>),
     Ident(Ident),
 }
 
-impl FormattedSegment {
+impl FormattedSegmentType {
     fn parse(input: &str) -> Result<Self> {
         if let Ok(ident) = parse_str::<Ident>(input) {
             if ident == input {
@@ -176,7 +218,7 @@ impl FormattedSegment {
     }
 }
 
-impl ToTokens for FormattedSegment {
+impl ToTokens for FormattedSegmentType {
     fn to_tokens(&self, tokens: &mut TokenStream) {
         match self {
             Self::Expr(expr) => expr.to_tokens(tokens),
@@ -189,6 +231,8 @@ impl Parse for IfmtInput {
     fn parse(input: ParseStream) -> Result<Self> {
         let input: LitStr = input.parse()?;
         let input_str = input.value();
-        IfmtInput::from_str(&input_str)
+        let mut ifmt = IfmtInput::from_str(&input_str)?;
+        ifmt.source = Some(input);
+        Ok(ifmt)
     }
 }

+ 38 - 5
packages/rsx/src/lib.rs

@@ -13,17 +13,25 @@
 
 #[macro_use]
 mod errors;
-
+#[cfg(any(feature = "hot-reload", debug_assertions))]
+mod attributes;
 mod component;
 mod element;
+#[cfg(any(feature = "hot-reload", debug_assertions))]
+mod elements;
+#[cfg(any(feature = "hot-reload", debug_assertions))]
+mod error;
 mod ifmt;
 mod node;
+mod template;
 
 // Re-export the namespaces into each other
 pub use component::*;
 pub use element::*;
 pub use ifmt::*;
 pub use node::*;
+#[cfg(any(feature = "hot-reload", debug_assertions))]
+pub use template::{try_parse_template, DynamicTemplateContextBuilder};
 
 // imports
 use proc_macro2::TokenStream as TokenStream2;
@@ -32,6 +40,7 @@ use syn::{
     parse::{Parse, ParseStream},
     Result, Token,
 };
+use template::TemplateBuilder;
 
 pub struct CallBody {
     pub roots: Vec<BodyNode>,
@@ -58,15 +67,39 @@ impl Parse for CallBody {
 /// Serialize the same way, regardless of flavor
 impl ToTokens for CallBody {
     fn to_tokens(&self, out_tokens: &mut TokenStream2) {
-        let inner = if self.roots.len() == 1 {
+        let template = TemplateBuilder::from_roots(self.roots.clone());
+        let inner = if let Some(template) = template {
+            quote! { #template }
+        } else {
+            let children = &self.roots;
+            if children.len() == 1 {
+                let inner = &self.roots[0];
+                quote! { #inner }
+            } else {
+                quote! { __cx.fragment_root([ #(#children),* ]) }
+            }
+        };
+
+        // Otherwise we just build the LazyNode wrapper
+        out_tokens.append_all(quote! {
+            LazyNodes::new(move |__cx: NodeFactory| -> VNode {
+                use dioxus_elements::{GlobalAttributes, SvgAttributes};
+                #inner
+            })
+        })
+    }
+}
+
+impl CallBody {
+    pub fn to_tokens_without_template(&self, out_tokens: &mut TokenStream2) {
+        let children = &self.roots;
+        let inner = if children.len() == 1 {
             let inner = &self.roots[0];
             quote! { #inner }
         } else {
-            let childs = &self.roots;
-            quote! { __cx.fragment_root([ #(#childs),* ]) }
+            quote! { __cx.fragment_root([ #(#children),* ]) }
         };
 
-        // Otherwise we just build the LazyNode wrapper
         out_tokens.append_all(quote! {
             LazyNodes::new(move |__cx: NodeFactory| -> VNode {
                 use dioxus_elements::{GlobalAttributes, SvgAttributes};

+ 4 - 4
packages/rsx/src/node.rs

@@ -16,11 +16,11 @@ Parse
 -> "text {with_args}"
 -> (0..10).map(|f| rsx!("asd")),  // <--- notice the comma - must be a complete expr
 */
-#[derive(PartialEq, Eq)]
+#[derive(PartialEq, Eq, Clone, Debug, Hash)]
 pub enum BodyNode {
     Element(Element),
     Component(Component),
-    Text(LitStr),
+    Text(IfmtInput),
     RawExpr(Expr),
 }
 
@@ -33,7 +33,7 @@ impl BodyNode {
         match self {
             BodyNode::Element(el) => el.name.span(),
             BodyNode::Component(component) => component.name.span(),
-            BodyNode::Text(text) => text.span(),
+            BodyNode::Text(text) => text.source.span(),
             BodyNode::RawExpr(exp) => exp.span(),
         }
     }
@@ -98,7 +98,7 @@ impl ToTokens for BodyNode {
             BodyNode::Element(el) => el.to_tokens(tokens),
             BodyNode::Component(comp) => comp.to_tokens(tokens),
             BodyNode::Text(txt) => tokens.append_all(quote! {
-                __cx.text(format_args_f!(#txt))
+                __cx.text(#txt)
             }),
             BodyNode::RawExpr(exp) => tokens.append_all(quote! {
                  __cx.fragment_from_iter(#exp)

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

@@ -0,0 +1,917 @@
+use dioxus_core::{
+    OwnedAttributeValue, TemplateAttributeValue, TemplateNodeId, TextTemplate, TextTemplateSegment,
+};
+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::CapturedVariable(
+                    "dynamic context updated".to_string(),
+                ))
+            })?;
+    }
+    let dyn_ctx = template_builder.dynamic_context.clone();
+    Ok((template_builder.try_into_owned(&location)?, dyn_ctx))
+}
+
+#[cfg(any(feature = "hot-reload", debug_assertions))]
+use hot_reload_imports::*;
+#[cfg(any(feature = "hot-reload", debug_assertions))]
+mod hot_reload_imports {
+    pub use crate::{
+        attributes::attrbute_to_static_str,
+        elements::element_to_static_str,
+        error::{Error, ParseError, RecompileReason},
+    };
+    pub use dioxus_core::prelude::OwnedTemplate;
+    pub use dioxus_core::{
+        AttributeDiscription, OwnedAttributeValue, OwnedCodeLocation, OwnedDynamicNodeMapping,
+        OwnedTemplateNode, Template, TemplateAttribute, TemplateAttributeValue, TemplateElement,
+        TemplateNodeId, TemplateNodeType, TextTemplate, TextTemplateSegment,
+    };
+    pub use std::collections::HashMap;
+}
+use crate::{BodyNode, ElementAttr, FormattedSegment, Segment};
+
+struct TemplateElementBuilder {
+    tag: Ident,
+    attributes: Vec<TemplateAttributeBuilder>,
+    children: Vec<TemplateNodeId>,
+    listeners: Vec<usize>,
+    parent: Option<TemplateNodeId>,
+}
+
+impl TemplateElementBuilder {
+    #[cfg(any(feature = "hot-reload", debug_assertions))]
+    fn try_into_owned(
+        self,
+        location: &OwnedCodeLocation,
+    ) -> Result<
+        TemplateElement<
+            Vec<TemplateAttribute<OwnedAttributeValue>>,
+            OwnedAttributeValue,
+            Vec<TemplateNodeId>,
+            Vec<usize>,
+        >,
+        Error,
+    > {
+        let Self {
+            tag,
+            attributes,
+            children,
+            listeners,
+            parent,
+        } = 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,
+            parent,
+        ))
+    }
+}
+
+impl ToTokens for TemplateElementBuilder {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        let Self {
+            tag,
+            attributes,
+            children,
+            listeners,
+            parent,
+        } = self;
+        let children = children.iter().map(|id| {
+            let raw = id.0;
+            quote! {TemplateNodeId(#raw)}
+        });
+        let parent = match parent {
+            Some(id) => {
+                let raw = id.0;
+                quote! {Some(TemplateNodeId(#raw))}
+            }
+            None => quote! {None},
+        };
+        tokens.append_all(quote! {
+            TemplateElement::new(
+                dioxus_elements::#tag::TAG_NAME,
+                dioxus_elements::#tag::NAME_SPACE,
+                &[#(#attributes),*],
+                &[#(#children),*],
+                &[#(#listeners),*],
+                #parent,
+            )
+        })
+    }
+}
+
+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::CapturedAttribute(
+                        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),*])}
+                    }
+                };
+                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),
+}
+
+impl TemplateNodeTypeBuilder {
+    #[cfg(any(feature = "hot-reload", debug_assertions))]
+    fn try_into_owned(
+        self,
+        location: &OwnedCodeLocation,
+    ) -> Result<
+        TemplateNodeType<
+            Vec<TemplateAttribute<OwnedAttributeValue>>,
+            OwnedAttributeValue,
+            Vec<TemplateNodeId>,
+            Vec<usize>,
+            Vec<TextTemplateSegment<String>>,
+            String,
+        >,
+        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 segments = txt.segments.iter().map(|seg| match seg {
+                    TextTemplateSegment::Static(s) => quote!(TextTemplateSegment::Static(#s)),
+                    TextTemplateSegment::Dynamic(idx) => quote!(TextTemplateSegment::Dynamic(#idx)),
+                });
+                tokens.append_all(quote! {
+                    TemplateNodeType::Text(TextTemplate::new(&[#(#segments),*]))
+                });
+            }
+            TemplateNodeTypeBuilder::DynamicNode(idx) => tokens.append_all(quote! {
+                TemplateNodeType::DynamicNode(#idx)
+            }),
+        }
+    }
+}
+
+struct TemplateNodeBuilder {
+    id: TemplateNodeId,
+    node_type: TemplateNodeTypeBuilder,
+}
+
+impl TemplateNodeBuilder {
+    #[cfg(any(feature = "hot-reload", debug_assertions))]
+    fn try_into_owned(self, location: &OwnedCodeLocation) -> Result<OwnedTemplateNode, Error> {
+        let TemplateNodeBuilder { id, node_type } = self;
+        let node_type = node_type.try_into_owned(location)?;
+        Ok(OwnedTemplateNode {
+            id,
+            node_type,
+            locally_static: false,
+            fully_static: false,
+        })
+    }
+
+    fn is_fully_static(&self, nodes: &Vec<TemplateNodeBuilder>) -> bool {
+        self.is_locally_static()
+            && match &self.node_type {
+                TemplateNodeTypeBuilder::Element(el) => el
+                    .children
+                    .iter()
+                    .all(|child| nodes[child.0].is_fully_static(nodes)),
+                TemplateNodeTypeBuilder::Text(_) => true,
+                TemplateNodeTypeBuilder::DynamicNode(_) => unreachable!(),
+            }
+    }
+
+    fn is_locally_static(&self) -> bool {
+        match &self.node_type {
+            TemplateNodeTypeBuilder::Element(el) => {
+                el.attributes.iter().all(|attr| match &attr.value {
+                    TemplateAttributeValue::Static(_) => true,
+                    TemplateAttributeValue::Dynamic(_) => false,
+                }) && el.listeners.is_empty()
+            }
+            TemplateNodeTypeBuilder::Text(txt) => txt.segments.iter().all(|seg| match seg {
+                TextTemplateSegment::Static(_) => true,
+                TextTemplateSegment::Dynamic(_) => false,
+            }),
+            TemplateNodeTypeBuilder::DynamicNode(_) => false,
+        }
+    }
+
+    fn to_tokens(&self, tokens: &mut TokenStream, nodes: &Vec<TemplateNodeBuilder>) {
+        let Self { id, node_type } = self;
+        let raw_id = id.0;
+        let fully_static = self.is_fully_static(nodes);
+        let locally_static = self.is_locally_static();
+
+        tokens.append_all(quote! {
+            TemplateNode {
+                id: TemplateNodeId(#raw_id),
+                node_type: #node_type,
+                locally_static: #locally_static,
+                fully_static: #fully_static,
+            }
+        })
+    }
+}
+
+#[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 root in roots {
+            let id = builder.build_node(root, None);
+            builder.root_nodes.push(id);
+        }
+
+        // only build a template if there is at least one static node
+        if builder.nodes.iter().all(|r| {
+            if let TemplateNodeTypeBuilder::DynamicNode(_) = &r.node_type {
+                true
+            } else {
+                false
+            }
+        }) {
+            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 root in roots {
+            let id = builder.build_node(root, None);
+            builder.root_nodes.push(id);
+        }
+
+        builder
+    }
+
+    fn build_node(&mut self, node: BodyNode, parent: Option<TemplateNodeId>) -> TemplateNodeId {
+        let id = TemplateNodeId(self.nodes.len());
+        match node {
+            BodyNode::Element(el) => {
+                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 {
+                                attributes.push(TemplateAttributeBuilder {
+                                    element_tag: el.name.clone(),
+                                    name: AttributeName::Ident(name),
+                                    value: TemplateAttributeValue::Dynamic(
+                                        self.dynamic_context.add_attr(quote!(AttributeValue::Text(
+                                            dioxus::core::exports::bumpalo::format!(in __bump, "{}", #value)
+                                                .into_bump_str()
+                                        ))),
+                                    ),
+                                })
+                            }
+                        }
+                        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 {
+                                attributes.push(TemplateAttributeBuilder {
+                                    element_tag: el.name.clone(),
+                                    name: AttributeName::Str(name),
+                                    value: TemplateAttributeValue::Dynamic(
+                                        self.dynamic_context.add_attr(quote!(AttributeValue::Text(
+                                            dioxus::core::exports::bumpalo::format!(in __bump, "{}", #value)
+                                                .into_bump_str()
+                                        ))),
+                                    ),
+                                })
+                            }
+                        }
+                        ElementAttr::AttrExpression { name, value } => {
+                            attributes.push(TemplateAttributeBuilder {
+                                element_tag: el.name.clone(),
+                                name: AttributeName::Ident(name),
+                                value: TemplateAttributeValue::Dynamic(
+                                    self.dynamic_context.add_attr(quote!(AttributeValue::Text(
+                                        dioxus::core::exports::bumpalo::format!(in __bump, "{}", #value)
+                                            .into_bump_str()
+                                    ))),
+                                ),
+                            })
+                        }
+                        ElementAttr::CustomAttrExpression { name, value } => {
+                            attributes.push(TemplateAttributeBuilder {
+                                element_tag: el.name.clone(),
+                                name: AttributeName::Str(name),
+                                value: TemplateAttributeValue::Dynamic(
+                                    self.dynamic_context.add_attr(quote!(AttributeValue::Text(
+                                        dioxus::core::exports::bumpalo::format!(in __bump, "{}", #value)
+                                            .into_bump_str()
+                                    ))),
+                                ),
+                            })
+                        }
+                        ElementAttr::EventTokens { name, tokens } => {
+                            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,
+                        parent,
+                    }),
+                });
+
+                let children: Vec<_> = el
+                    .children
+                    .into_iter()
+                    .map(|child| self.build_node(child, Some(id)))
+                    .collect();
+                let parent = &mut self.nodes[id.0];
+                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)),
+                    ),
+                });
+            }
+
+            BodyNode::Text(txt) => {
+                let mut segments = Vec::new();
+
+                for segment in txt.segments {
+                    segments.push(match segment {
+                        Segment::Literal(lit) => TextTemplateSegment::Static(lit),
+                        Segment::Formatted(fmted) => {
+                            TextTemplateSegment::Dynamic(self.dynamic_context.add_text(fmted))
+                        }
+                    })
+                }
+
+                self.nodes.push(TemplateNodeBuilder {
+                    id,
+                    node_type: TemplateNodeTypeBuilder::Text(TextTemplate::new(segments)),
+                });
+            }
+
+            BodyNode::RawExpr(expr) => {
+                self.nodes.push(TemplateNodeBuilder {
+                    id,
+                    node_type: TemplateNodeTypeBuilder::DynamicNode(
+                        self.dynamic_context.add_node(BodyNode::RawExpr(expr)),
+                    ),
+                });
+            }
+        }
+        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);
+        for node in self.nodes {
+            nodes.push(node.try_into_owned(location)?);
+        }
+
+        Ok(OwnedTemplate {
+            nodes,
+            root_nodes: self.root_nodes,
+            dynamic_mapping,
+        })
+    }
+
+    #[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 {
+            match &n.node_type {
+                TemplateNodeTypeBuilder::DynamicNode(idx) => node_mapping[*idx] = Some(n.id),
+                _ => (),
+            }
+        }
+        let mut text_mapping = vec![Vec::new(); dynamic_context.text.len()];
+        for n in nodes {
+            match &n.node_type {
+                TemplateNodeTypeBuilder::Text(txt) => {
+                    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 {
+            match &n.node_type {
+                TemplateNodeTypeBuilder::Element(el) => {
+                    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 {
+            match &n.node_type {
+                TemplateNodeTypeBuilder::Element(el) => {
+                    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,
+        )
+    }
+}
+
+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 {
+            match &n.node_type {
+                TemplateNodeTypeBuilder::DynamicNode(idx) => node_mapping[*idx] = Some(n.id),
+                _ => (),
+            }
+        }
+        let mut text_mapping = vec![Vec::new(); dynamic_context.text.len()];
+        for n in nodes {
+            match &n.node_type {
+                TemplateNodeTypeBuilder::Text(txt) => {
+                    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 {
+            match &n.node_type {
+                TemplateNodeTypeBuilder::Element(el) => {
+                    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 {
+            match &n.node_type {
+                TemplateNodeTypeBuilder::Element(el) => {
+                    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, nodes);
+            quote! {,}.to_tokens(&mut nodes_quoted);
+        }
+
+        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),
+                });
+
+                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),*]),
+                listeners: __cx.bump().alloc([#(dioxus_elements::on::#listeners_names(__cx, #listeners_exprs)),*]),
+                key: #key,
+            }
+        })
+    }
+}

+ 0 - 24
packages/rsx_interpreter/Cargo.toml

@@ -1,24 +0,0 @@
-[package]
-name = "dioxus-rsx-interpreter"
-version = "0.1.0"
-edition = "2021"
-license = "MIT/Apache-2.0"
-
-[dependencies]
-syn = { version = "1.0", features = ["extra-traits"] }
-proc-macro2 = { version = "1.0.39", features = ["span-locations"] }
-quote = "1.0"
-serde = { version = "1.0", features = ["derive"] }
-serde_json = "1.0"
-lazy_static = "1.4.0"
-
-dioxus-rsx = { path = "../rsx", default-features = false }
-dioxus-ssr = { path = "../ssr" }
-dioxus-core = { path = "../core" }
-dioxus-html = { path = "../html" }
-dioxus-hooks = { path = "../hooks"}
-
-[dev-dependencies]
-dioxus-core-macro = { path = "../core-macro" }
-bumpalo = { version = "3.6", features = ["collections", "boxed"] }
-dioxus = { path = "../dioxus", features = ["hot-reload"]}

+ 0 - 209
packages/rsx_interpreter/src/captuered_context.rs

@@ -1,209 +0,0 @@
-use std::collections::HashSet;
-
-use dioxus_core::{Listener, VNode};
-use dioxus_rsx::{
-    BodyNode, CallBody, Component, ElementAttr, ElementAttrNamed, IfmtInput, Segment,
-};
-use quote::{quote, ToTokens, TokenStreamExt};
-use syn::{Expr, Ident, LitStr, Result};
-
-use crate::{attributes::attrbute_to_static_str, CodeLocation};
-#[derive(Default)]
-pub struct CapturedContextBuilder {
-    pub ifmted: Vec<IfmtInput>,
-    pub components: Vec<Component>,
-    pub iterators: Vec<BodyNode>,
-    pub captured_expressions: Vec<Expr>,
-    pub listeners: Vec<ElementAttrNamed>,
-    pub custom_context: Option<Ident>,
-    pub custom_attributes: HashSet<LitStr>,
-}
-
-impl CapturedContextBuilder {
-    pub fn extend(&mut self, other: CapturedContextBuilder) {
-        self.ifmted.extend(other.ifmted);
-        self.components.extend(other.components);
-        self.iterators.extend(other.iterators);
-        self.listeners.extend(other.listeners);
-        self.captured_expressions.extend(other.captured_expressions);
-        self.custom_attributes.extend(other.custom_attributes);
-    }
-
-    pub fn from_call_body(body: CallBody) -> Result<Self> {
-        let mut new = Self::default();
-        for node in body.roots {
-            new.extend(Self::find_captured(node)?);
-        }
-        Ok(new)
-    }
-
-    fn find_captured(node: BodyNode) -> Result<Self> {
-        let mut captured = CapturedContextBuilder::default();
-        match node {
-            BodyNode::Element(el) => {
-                for attr in el.attributes {
-                    match attr.attr {
-                        ElementAttr::AttrText { value, .. } => {
-                            let value_tokens = value.to_token_stream();
-                            let formated: IfmtInput = syn::parse2(value_tokens)?;
-                            captured.ifmted.push(formated);
-                        }
-                        ElementAttr::CustomAttrText { value, name } => {
-                            captured.custom_attributes.insert(name);
-                            let value_tokens = value.to_token_stream();
-                            let formated: IfmtInput = syn::parse2(value_tokens)?;
-                            captured.ifmted.push(formated);
-                        }
-                        ElementAttr::AttrExpression { name: _, value } => {
-                            captured.captured_expressions.push(value);
-                        }
-                        ElementAttr::CustomAttrExpression { name, value } => {
-                            captured.custom_attributes.insert(name);
-                            captured.captured_expressions.push(value);
-                        }
-                        ElementAttr::EventTokens { .. } => captured.listeners.push(attr),
-                    }
-                }
-
-                if let Some(key) = el.key {
-                    let value_tokens = key.to_token_stream();
-                    let formated: IfmtInput = syn::parse2(value_tokens)?;
-                    captured.ifmted.push(formated);
-                }
-
-                for child in el.children {
-                    captured.extend(Self::find_captured(child)?);
-                }
-            }
-            BodyNode::Component(comp) => {
-                captured.components.push(comp);
-            }
-            BodyNode::Text(t) => {
-                let tokens = t.to_token_stream();
-                let formated: IfmtInput = syn::parse2(tokens).unwrap();
-                captured.ifmted.push(formated);
-            }
-            BodyNode::RawExpr(_) => captured.iterators.push(node),
-        }
-        Ok(captured)
-    }
-}
-
-impl ToTokens for CapturedContextBuilder {
-    fn to_tokens(&self, tokens: &mut quote::__private::TokenStream) {
-        let CapturedContextBuilder {
-            ifmted,
-            components,
-            iterators,
-            captured_expressions,
-            listeners,
-            custom_context: _,
-            custom_attributes,
-        } = self;
-        let listeners_str = listeners
-            .iter()
-            .map(|comp| comp.to_token_stream().to_string());
-        let compontents_str = components
-            .iter()
-            .map(|comp| comp.to_token_stream().to_string());
-        let iterators_str = iterators.iter().map(|node| match node {
-            BodyNode::RawExpr(expr) => expr.to_token_stream().to_string(),
-            _ => unreachable!(),
-        });
-        let captured: Vec<_> = ifmted
-            .iter()
-            .flat_map(|input| input.segments.iter())
-            .filter_map(|seg| match seg {
-                Segment::Formatted {
-                    format_args,
-                    segment,
-                } => {
-                    let expr = segment.to_token_stream();
-                    let as_string = expr.to_string();
-                    let format_expr = if format_args.is_empty() {
-                        "{".to_string() + format_args + "}"
-                    } else {
-                        "{".to_string() + ":" + format_args + "}"
-                    };
-                    Some(quote! {
-                        FormattedArg{
-                            expr: #as_string,
-                            format_args: #format_args,
-                            result: format!(#format_expr, #expr)
-                        }
-                    })
-                }
-                _ => None,
-            })
-            .collect();
-        let captured_attr_expressions_text = captured_expressions
-            .iter()
-            .map(|e| format!("{}", e.to_token_stream()));
-        let custom_attributes_iter = custom_attributes.iter();
-        tokens.append_all(quote! {
-            CapturedContext {
-                captured: IfmtArgs{
-                    named_args: vec![#(#captured),*]
-                },
-                components: vec![#((#compontents_str, #components)),*],
-                iterators: vec![#((#iterators_str, #iterators)),*],
-                expressions: vec![#((#captured_attr_expressions_text, #captured_expressions.to_string())),*],
-                listeners: vec![#((#listeners_str, #listeners)),*],
-                custom_attributes: &[#(#custom_attributes_iter),*],
-                location: code_location.clone()
-            }
-        })
-    }
-}
-
-pub struct CapturedContext<'a> {
-    // map of the variable name to the formated value
-    pub captured: IfmtArgs,
-    // map of the attribute name and element path to the formated value
-    // pub captured_attribute_values: IfmtArgs,
-    // the only thing we can update in component is the children
-    pub components: Vec<(&'static str, VNode<'a>)>,
-    // we can't reasonably interpert iterators, so they are staticly inserted
-    pub iterators: Vec<(&'static str, VNode<'a>)>,
-    // map expression to the value resulting from the expression
-    pub expressions: Vec<(&'static str, String)>,
-    // map listener code to the resulting listener
-    pub listeners: Vec<(&'static str, Listener<'a>)>,
-    // used to map custom attrbutes form &'a str to &'static str
-    pub custom_attributes: &'static [&'static str],
-    // used to provide better error messages
-    pub location: CodeLocation,
-}
-
-impl<'a> CapturedContext<'a> {
-    pub fn attrbute_to_static_str(
-        &self,
-        attr: &str,
-        tag: &'static str,
-        ns: Option<&'static str>,
-        literal: bool,
-    ) -> Option<(&'static str, Option<&'static str>)> {
-        if let Some(attr) = attrbute_to_static_str(attr, tag, ns) {
-            Some(attr)
-        } else if literal {
-            self.custom_attributes
-                .iter()
-                .find(|attribute| attr == **attribute)
-                .map(|attribute| (*attribute, None))
-        } else {
-            None
-        }
-    }
-}
-
-pub struct IfmtArgs {
-    // All expressions that have been resolved
-    pub named_args: Vec<FormattedArg>,
-}
-
-/// A formated segment that has been resolved
-pub struct FormattedArg {
-    pub expr: &'static str,
-    pub format_args: &'static str,
-    pub result: String,
-}

+ 0 - 277
packages/rsx_interpreter/src/interperter.rs

@@ -1,277 +0,0 @@
-use dioxus_core::{Attribute, AttributeValue, NodeFactory, VNode};
-use dioxus_rsx::{BodyNode, CallBody, ElementAttr, IfmtInput, Segment};
-use quote::ToTokens;
-use quote::__private::Span;
-use std::str::FromStr;
-use syn::{parse2, parse_str, Expr};
-
-use crate::captuered_context::{CapturedContext, IfmtArgs};
-use crate::elements::element_to_static_str;
-use crate::error::{Error, ParseError, RecompileReason};
-
-fn resolve_ifmt(ifmt: &IfmtInput, captured: &IfmtArgs) -> Result<String, Error> {
-    let mut result = String::new();
-    for seg in &ifmt.segments {
-        match seg {
-            Segment::Formatted {
-                segment,
-                format_args,
-            } => {
-                let expr = segment.to_token_stream();
-                let expr: Expr = parse2(expr).unwrap();
-                let search = captured.named_args.iter().find(|fmted| {
-                    parse_str::<Expr>(fmted.expr).unwrap() == expr
-                        && fmted.format_args == format_args
-                });
-                match search {
-                    Some(formatted) => {
-                        result.push_str(&formatted.result);
-                    }
-                    None => {
-                        let expr_str = segment.to_token_stream().to_string();
-                        return Err(Error::RecompileRequiredError(
-                            RecompileReason::CapturedExpression(format!(
-                                "could not resolve {{{}:{}}}",
-                                expr_str, format_args
-                            )),
-                        ));
-                    }
-                }
-            }
-            Segment::Literal(lit) => result.push_str(lit),
-        }
-    }
-    Ok(result)
-}
-
-pub fn build<'a>(
-    rsx: CallBody,
-    mut ctx: CapturedContext<'a>,
-    factory: &NodeFactory<'a>,
-) -> Result<VNode<'a>, Error> {
-    let children_built = factory.bump().alloc(Vec::new());
-    for child in rsx.roots {
-        children_built.push(build_node(child, &mut ctx, factory)?);
-    }
-
-    if children_built.len() == 1 {
-        Ok(children_built.pop().unwrap())
-    } else {
-        Ok(factory.fragment_from_iter(children_built.iter()))
-    }
-}
-
-fn build_node<'a>(
-    node: BodyNode,
-    ctx: &mut CapturedContext<'a>,
-    factory: &NodeFactory<'a>,
-) -> Result<VNode<'a>, Error> {
-    let bump = factory.bump();
-    match node {
-        BodyNode::Text(text) => {
-            let ifmt = IfmtInput::from_str(&text.value())
-                .map_err(|err| Error::ParseError(ParseError::new(err, ctx.location.clone())))?;
-            let text = bump.alloc(resolve_ifmt(&ifmt, &ctx.captured)?);
-            Ok(factory.text(format_args!("{}", text)))
-        }
-        BodyNode::Element(el) => {
-            let attributes: &mut Vec<Attribute> = bump.alloc(Vec::new());
-            let tag = &el.name.to_string();
-            if let Some((tag, ns)) = element_to_static_str(tag) {
-                for attr in &el.attributes {
-                    match &attr.attr {
-                        ElementAttr::AttrText { .. } | ElementAttr::CustomAttrText { .. } => {
-                            let (name, value, span, literal): (String, IfmtInput, Span, bool) =
-                                match &attr.attr {
-                                    ElementAttr::AttrText { name, value } => (
-                                        name.to_string(),
-                                        IfmtInput::from_str(&value.value()).map_err(|err| {
-                                            Error::ParseError(ParseError::new(
-                                                err,
-                                                ctx.location.clone(),
-                                            ))
-                                        })?,
-                                        name.span(),
-                                        false,
-                                    ),
-                                    ElementAttr::CustomAttrText { name, value } => (
-                                        name.value(),
-                                        IfmtInput::from_str(&value.value()).map_err(|err| {
-                                            Error::ParseError(ParseError::new(
-                                                err,
-                                                ctx.location.clone(),
-                                            ))
-                                        })?,
-                                        name.span(),
-                                        true,
-                                    ),
-                                    _ => unreachable!(),
-                                };
-
-                            if let Some((name, namespace)) =
-                                ctx.attrbute_to_static_str(&name, tag, ns, literal)
-                            {
-                                let value = bump.alloc(resolve_ifmt(&value, &ctx.captured)?);
-                                attributes.push(Attribute {
-                                    name,
-                                    value: AttributeValue::Text(value),
-                                    is_static: true,
-                                    is_volatile: false,
-                                    namespace,
-                                });
-                            } else if literal {
-                                // literals will be captured when a full recompile is triggered
-                                return Err(Error::RecompileRequiredError(
-                                    RecompileReason::CapturedAttribute(name.to_string()),
-                                ));
-                            } else {
-                                return Err(Error::ParseError(ParseError::new(
-                                    syn::Error::new(span, format!("unknown attribute: {}", name)),
-                                    ctx.location.clone(),
-                                )));
-                            }
-                        }
-
-                        ElementAttr::AttrExpression { .. }
-                        | ElementAttr::CustomAttrExpression { .. } => {
-                            let (name, value, span, literal) = match &attr.attr {
-                                ElementAttr::AttrExpression { name, value } => {
-                                    (name.to_string(), value, name.span(), false)
-                                }
-                                ElementAttr::CustomAttrExpression { name, value } => {
-                                    (name.value(), value, name.span(), true)
-                                }
-                                _ => unreachable!(),
-                            };
-                            if let Some((_, resulting_value)) = ctx
-                                .expressions
-                                .iter()
-                                .find(|(n, _)| parse_str::<Expr>(*n).unwrap() == *value)
-                            {
-                                if let Some((name, namespace)) =
-                                    ctx.attrbute_to_static_str(&name, tag, ns, literal)
-                                {
-                                    let value = bump.alloc(resulting_value.clone());
-                                    attributes.push(Attribute {
-                                        name,
-                                        value: AttributeValue::Text(value),
-                                        is_static: true,
-                                        is_volatile: false,
-                                        namespace,
-                                    });
-                                } else if literal {
-                                    // literals will be captured when a full recompile is triggered
-                                    return Err(Error::RecompileRequiredError(
-                                        RecompileReason::CapturedAttribute(name.to_string()),
-                                    ));
-                                } else {
-                                    return Err(Error::ParseError(ParseError::new(
-                                        syn::Error::new(
-                                            span,
-                                            format!("unknown attribute: {}", name),
-                                        ),
-                                        ctx.location.clone(),
-                                    )));
-                                }
-                            }
-                        }
-                        _ => (),
-                    }
-                }
-                let children = bump.alloc(Vec::new());
-                for child in el.children {
-                    let node = build_node(child, ctx, factory)?;
-                    children.push(node);
-                }
-                let listeners = bump.alloc(Vec::new());
-                for attr in el.attributes {
-                    if let ElementAttr::EventTokens { .. } = attr.attr {
-                        let expr: Expr = parse2(attr.to_token_stream()).map_err(|err| {
-                            Error::ParseError(ParseError::new(err, ctx.location.clone()))
-                        })?;
-                        if let Some(idx) = ctx.listeners.iter().position(|(code, _)| {
-                            if let Ok(parsed) = parse_str::<Expr>(*code) {
-                                parsed == expr
-                            } else {
-                                false
-                            }
-                        }) {
-                            let (_, listener) = ctx.listeners.remove(idx);
-                            listeners.push(listener)
-                        } else {
-                            return Err(Error::RecompileRequiredError(
-                                RecompileReason::CapturedListener(
-                                    expr.to_token_stream().to_string(),
-                                ),
-                            ));
-                        }
-                    }
-                }
-                match el.key {
-                    None => Ok(factory.raw_element(
-                        tag,
-                        ns,
-                        listeners,
-                        attributes.as_slice(),
-                        children.as_slice(),
-                        None,
-                    )),
-                    Some(lit) => {
-                        let ifmt: IfmtInput = lit.value().parse().map_err(|err| {
-                            Error::ParseError(ParseError::new(err, ctx.location.clone()))
-                        })?;
-                        let key = bump.alloc(resolve_ifmt(&ifmt, &ctx.captured)?);
-
-                        Ok(factory.raw_element(
-                            tag,
-                            ns,
-                            listeners,
-                            attributes.as_slice(),
-                            children.as_slice(),
-                            Some(format_args!("{}", key)),
-                        ))
-                    }
-                }
-            } else {
-                Err(Error::ParseError(ParseError::new(
-                    syn::Error::new(el.name.span(), format!("unknown element: {}", tag)),
-                    ctx.location.clone(),
-                )))
-            }
-        }
-        BodyNode::Component(comp) => {
-            let expr: Expr = parse2(comp.to_token_stream())
-                .map_err(|err| Error::ParseError(ParseError::new(err, ctx.location.clone())))?;
-            if let Some(idx) = ctx.components.iter().position(|(code, _)| {
-                if let Ok(parsed) = parse_str::<Expr>(*code) {
-                    parsed == expr
-                } else {
-                    false
-                }
-            }) {
-                let (_, vnode) = ctx.components.remove(idx);
-                Ok(vnode)
-            } else {
-                Err(Error::RecompileRequiredError(
-                    RecompileReason::CapturedComponent(comp.name.to_token_stream().to_string()),
-                ))
-            }
-        }
-        BodyNode::RawExpr(iterator) => {
-            if let Some(idx) = ctx.iterators.iter().position(|(code, _)| {
-                if let Ok(parsed) = parse_str::<Expr>(*code) {
-                    parsed == iterator
-                } else {
-                    false
-                }
-            }) {
-                let (_, vnode) = ctx.iterators.remove(idx);
-                Ok(vnode)
-            } else {
-                Err(Error::RecompileRequiredError(
-                    RecompileReason::CapturedExpression(iterator.to_token_stream().to_string()),
-                ))
-            }
-        }
-    }
-}

+ 0 - 183
packages/rsx_interpreter/src/lib.rs

@@ -1,183 +0,0 @@
-use captuered_context::CapturedContext;
-use dioxus_core::{NodeFactory, SchedulerMsg, VNode};
-use dioxus_hooks::UnboundedSender;
-use error::{Error, ParseError};
-use interperter::build;
-use lazy_static::lazy_static;
-use serde::{Deserialize, Serialize};
-use std::collections::HashMap;
-use std::sync::{RwLock, RwLockReadGuard};
-use syn::parse_str;
-
-mod attributes;
-pub mod captuered_context;
-mod elements;
-pub mod error;
-mod interperter;
-
-lazy_static! {
-    /// This a a global store of the current rsx text for each call to rsx
-    // Global mutable data is genrally not great, but it allows users to not worry about passing down the text RsxContex every time they switch to hot reloading.
-    pub static ref RSX_CONTEXT: RsxContext = RsxContext::new(RsxData::default());
-}
-
-// the location of the code relative to the current crate based on [std::panic::Location]
-#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
-pub struct CodeLocation {
-    pub crate_path: String,
-    pub file_path: String,
-    pub line: u32,
-    pub column: u32,
-}
-
-/// Get the resolved rsx given the origional rsx, a captured context of dynamic components, and a factory to build the resulting node
-#[cfg_attr(
-    not(debug_assertions),
-    deprecated(
-        note = "The hot reload feature is enabled in release mode. This feature should be disabled for production builds."
-    )
-)]
-pub fn resolve_scope<'a>(
-    location: CodeLocation,
-    rsx: &'static str,
-    captured: CapturedContext<'a>,
-    factory: NodeFactory<'a>,
-) -> VNode<'a> {
-    let rsx_text_index = &*RSX_CONTEXT;
-    // only the insert the rsx text once
-    if !rsx_text_index.read().hm.contains_key(&location) {
-        rsx_text_index.insert(location.clone(), rsx.to_string());
-    }
-    if let Some(text) = {
-        let read = rsx_text_index.read();
-        // clone prevents deadlock on nested rsx calls
-        read.hm.get(&location).cloned()
-    } {
-        match interpert_rsx(factory, &text, captured) {
-            Ok(vnode) => vnode,
-            Err(err) => {
-                rsx_text_index.report_error(err);
-                factory.text(format_args!(""))
-            }
-        }
-    } else {
-        panic!("rsx: line number {:?} not found in rsx index", location);
-    }
-}
-
-fn interpert_rsx<'a>(
-    factory: dioxus_core::NodeFactory<'a>,
-    text: &str,
-    context: captuered_context::CapturedContext<'a>,
-) -> Result<VNode<'a>, Error> {
-    build(
-        parse_str(text)
-            .map_err(|err| Error::ParseError(ParseError::new(err, context.location.clone())))?,
-        context,
-        &factory,
-    )
-}
-
-/// get the code location of the code that called this function
-#[macro_export]
-macro_rules! get_line_num {
-    () => {{
-        let line = line!();
-        let column = column!();
-        let file_path = file!().to_string();
-        let crate_path = env!("CARGO_MANIFEST_DIR").to_string();
-
-        CodeLocation {
-            crate_path,
-            file_path,
-            line: line,
-            column: column,
-        }
-    }};
-}
-
-/// A handle to the rsx context with interior mutability
-#[derive(Debug)]
-pub struct RsxContext {
-    data: RwLock<RsxData>,
-}
-
-/// A store of the text for the rsx macro for each call to rsx
-#[derive(Default)]
-pub struct RsxData {
-    pub hm: HashMap<CodeLocation, String>,
-    pub error_handler: Option<Box<dyn ErrorHandler>>,
-    pub scheduler_channel: Option<UnboundedSender<SchedulerMsg>>,
-}
-
-impl std::fmt::Debug for RsxData {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.debug_struct("RsxData").field("hm", &self.hm).finish()
-    }
-}
-
-impl RsxContext {
-    pub fn new(data: RsxData) -> Self {
-        Self {
-            data: RwLock::new(data),
-        }
-    }
-
-    /// Set the text for an rsx call at some location
-    pub fn insert(&self, loc: CodeLocation, text: String) {
-        let mut write = self.data.write().unwrap();
-        write.hm.insert(loc, text);
-        if let Some(channel) = &mut write.scheduler_channel {
-            channel.unbounded_send(SchedulerMsg::DirtyAll).unwrap()
-        }
-    }
-
-    /// Set the text for many rsx calls
-    pub fn extend(&self, msg: SetManyRsxMessage) {
-        let mut write = self.data.write().unwrap();
-        for rsx in msg.0 {
-            write.hm.insert(rsx.location, rsx.new_text);
-        }
-        if let Some(channel) = &mut write.scheduler_channel {
-            channel.unbounded_send(SchedulerMsg::DirtyAll).unwrap()
-        }
-    }
-
-    fn read(&self) -> RwLockReadGuard<RsxData> {
-        self.data.read().unwrap()
-    }
-
-    fn report_error(&self, error: Error) {
-        if let Some(handler) = &self.data.write().unwrap().error_handler {
-            handler.handle_error(error)
-        } else {
-            panic!("no error handler set for this platform...\n{}", error);
-        }
-    }
-
-    /// Set the handler for errors interperting the rsx
-    pub fn set_error_handler(&self, handler: impl ErrorHandler + 'static) {
-        self.data.write().unwrap().error_handler = Some(Box::new(handler));
-    }
-
-    /// Provide the scduler channel from [dioxus_code::VirtualDom::get_scheduler_channel].
-    /// The channel allows the interpreter to force re-rendering of the dom when the rsx is changed.
-    pub fn provide_scheduler_channel(&self, channel: UnboundedSender<SchedulerMsg>) {
-        self.data.write().unwrap().scheduler_channel = Some(channel)
-    }
-}
-
-/// A error handler for errors reported by the rsx interperter
-pub trait ErrorHandler: Send + Sync {
-    fn handle_error(&self, err: Error);
-}
-
-#[derive(Serialize, Deserialize, Clone, Debug)]
-pub struct SetRsxMessage {
-    pub location: CodeLocation,
-    pub new_text: String,
-}
-
-/// Set many rsx texts at once to avoid duplicate errors
-#[derive(Serialize, Deserialize, Clone, Debug)]
-pub struct SetManyRsxMessage(pub Vec<SetRsxMessage>);

+ 0 - 407
packages/rsx_interpreter/tests/render.rs

@@ -1,407 +0,0 @@
-use dioxus::prelude::*;
-
-#[test]
-#[allow(non_snake_case)]
-fn render_basic() {
-    fn Base(cx: Scope) -> Element {
-        render!(div {})
-    }
-
-    let dom = VirtualDom::new(Base);
-    let static_vnodes = rsx!(div{"hello world"});
-    let location = CodeLocation {
-        file_path: String::new(),
-        crate_path: String::new(),
-        line: 0,
-        column: 0,
-    };
-    let empty_context = CapturedContext {
-        captured: IfmtArgs {
-            named_args: Vec::new(),
-        },
-        components: Vec::new(),
-        iterators: Vec::new(),
-        expressions: Vec::new(),
-        listeners: Vec::new(),
-        location: location.clone(),
-        custom_attributes: &[],
-    };
-    let interperted_vnodes = LazyNodes::new(|factory| {
-        dioxus_rsx_interpreter::resolve_scope(
-            location,
-            "div{\"hello world\"}",
-            empty_context,
-            factory,
-        )
-    });
-
-    let interperted_vnodes = dom.render_vnodes(interperted_vnodes);
-    let static_vnodes = dom.render_vnodes(static_vnodes);
-    assert!(check_eq(interperted_vnodes, static_vnodes));
-}
-
-#[test]
-#[allow(non_snake_case)]
-fn render_nested() {
-    fn Base(cx: Scope) -> Element {
-        render!(div {})
-    }
-
-    let dom = VirtualDom::new(Base);
-    let static_vnodes = rsx! {
-        div {
-            p { "hello world" }
-            div {
-                p { "hello world" }
-            }
-        }
-    };
-    let location = CodeLocation {
-        file_path: String::new(),
-        crate_path: String::new(),
-        line: 1,
-        column: 0,
-    };
-    let empty_context = CapturedContext {
-        captured: IfmtArgs {
-            named_args: Vec::new(),
-        },
-        components: Vec::new(),
-        iterators: Vec::new(),
-        expressions: Vec::new(),
-        listeners: Vec::new(),
-        location: location.clone(),
-        custom_attributes: &[],
-    };
-    let interperted_vnodes = LazyNodes::new(|factory| {
-        dioxus_rsx_interpreter::resolve_scope(
-            location,
-            r#"div {
-                p { "hello world" }
-                div {
-                    p { "hello world" }
-                }
-            }"#,
-            empty_context,
-            factory,
-        )
-    });
-
-    let interperted_vnodes = dom.render_vnodes(interperted_vnodes);
-    let static_vnodes = dom.render_vnodes(static_vnodes);
-    assert!(check_eq(interperted_vnodes, static_vnodes));
-}
-
-#[test]
-#[allow(non_snake_case)]
-fn render_custom_attribute() {
-    fn Base(cx: Scope) -> Element {
-        render!(div {})
-    }
-
-    let dom = VirtualDom::new(Base);
-    let static_vnodes = rsx! {
-        div {
-            "data-test-1": 0,
-            "data-test-2": "1",
-        }
-    };
-    let location = CodeLocation {
-        file_path: String::new(),
-        crate_path: String::new(),
-        line: 2,
-        column: 0,
-    };
-    let empty_context = CapturedContext {
-        captured: IfmtArgs {
-            named_args: Vec::new(),
-        },
-        components: Vec::new(),
-        iterators: Vec::new(),
-        expressions: vec![("0", "0".to_string())],
-        listeners: Vec::new(),
-        location: location.clone(),
-        custom_attributes: &["data-test-1", "data-test-2"],
-    };
-    let interperted_vnodes = LazyNodes::new(|factory| {
-        dioxus_rsx_interpreter::resolve_scope(
-            location,
-            r#"div {
-                "data-test-1": 0,
-                "data-test-2": "1",
-            }"#,
-            empty_context,
-            factory,
-        )
-    });
-
-    let interperted_vnodes = dom.render_vnodes(interperted_vnodes);
-    let static_vnodes = dom.render_vnodes(static_vnodes);
-    assert!(check_eq(interperted_vnodes, static_vnodes));
-}
-
-#[test]
-#[allow(non_snake_case)]
-fn render_component() {
-    fn Comp(cx: Scope) -> Element {
-        render!(div {})
-    }
-
-    fn Base(cx: Scope) -> Element {
-        render!(div {})
-    }
-
-    let dom = VirtualDom::new(Base);
-    let static_vnodes = rsx! {
-        div {
-            Comp {}
-        }
-    };
-    let location = CodeLocation {
-        file_path: String::new(),
-        crate_path: String::new(),
-        line: 3,
-        column: 0,
-    };
-
-    let interperted_vnodes = LazyNodes::new(|factory| {
-        let context = CapturedContext {
-            captured: IfmtArgs {
-                named_args: Vec::new(),
-            },
-            components: vec![(
-                r#"__cx.component(Comp, fc_to_builder(Comp).build(), None, "Comp")"#,
-                factory.component(Comp, (), None, "Comp"),
-            )],
-            iterators: Vec::new(),
-            expressions: Vec::new(),
-            listeners: Vec::new(),
-            location: location.clone(),
-            custom_attributes: &[],
-        };
-        dioxus_rsx_interpreter::resolve_scope(
-            location,
-            r#"div {
-                Comp {}
-            }"#,
-            context,
-            factory,
-        )
-    });
-
-    let interperted_vnodes = dom.render_vnodes(interperted_vnodes);
-    let static_vnodes = dom.render_vnodes(static_vnodes);
-    println!("{:#?}", interperted_vnodes);
-    println!("{:#?}", static_vnodes);
-    assert!(check_eq(interperted_vnodes, static_vnodes));
-}
-
-#[test]
-#[allow(non_snake_case)]
-fn render_iterator() {
-    fn Base(cx: Scope) -> Element {
-        render!(div {})
-    }
-
-    let dom = VirtualDom::new(Base);
-    let iter = (0..10).map(|i| dom.render_vnodes(rsx! {"{i}"}));
-    let static_vnodes = rsx! {
-        div {
-            iter
-        }
-    };
-    let location = CodeLocation {
-        file_path: String::new(),
-        crate_path: String::new(),
-        line: 4,
-        column: 0,
-    };
-
-    let interperted_vnodes = LazyNodes::new(|factory| {
-        let context = CapturedContext {
-            captured: IfmtArgs {
-                named_args: Vec::new(),
-            },
-            components: Vec::new(),
-            iterators: vec![(
-                r#"
-            (0..10).map(|i| dom.render_vnodes(rsx!{"{i}"}))"#,
-                factory.fragment_from_iter((0..10).map(|i| factory.text(format_args!("{i}")))),
-            )],
-            expressions: Vec::new(),
-            listeners: Vec::new(),
-            location: location.clone(),
-            custom_attributes: &[],
-        };
-        dioxus_rsx_interpreter::resolve_scope(
-            location,
-            r#"div {
-                (0..10).map(|i| dom.render_vnodes(rsx!{"{i}"}))
-            }"#,
-            context,
-            factory,
-        )
-    });
-
-    let interperted_vnodes = dom.render_vnodes(interperted_vnodes);
-    let static_vnodes = dom.render_vnodes(static_vnodes);
-    println!("{:#?}", interperted_vnodes);
-    println!("{:#?}", static_vnodes);
-    assert!(check_eq(interperted_vnodes, static_vnodes));
-}
-
-#[test]
-#[allow(non_snake_case)]
-fn render_captured_variable() {
-    fn Base(cx: Scope) -> Element {
-        render!(div {})
-    }
-
-    let dom = VirtualDom::new(Base);
-
-    let x = 10;
-    let static_vnodes = rsx! {
-        div {
-            "{x}"
-        }
-    };
-    let location = CodeLocation {
-        file_path: String::new(),
-        crate_path: String::new(),
-        line: 5,
-        column: 0,
-    };
-
-    let interperted_vnodes = LazyNodes::new(|factory| {
-        let context = CapturedContext {
-            captured: IfmtArgs {
-                named_args: vec![FormattedArg {
-                    expr: "x",
-                    format_args: "",
-                    result: x.to_string(),
-                }],
-            },
-            components: Vec::new(),
-            iterators: Vec::new(),
-            expressions: Vec::new(),
-            listeners: Vec::new(),
-            location: location.clone(),
-            custom_attributes: &[],
-        };
-        dioxus_rsx_interpreter::resolve_scope(
-            location,
-            r#"div {
-                "{x}"
-            }"#,
-            context,
-            factory,
-        )
-    });
-
-    let interperted_vnodes = dom.render_vnodes(interperted_vnodes);
-    let static_vnodes = dom.render_vnodes(static_vnodes);
-    println!("{:#?}", interperted_vnodes);
-    println!("{:#?}", static_vnodes);
-    assert!(check_eq(interperted_vnodes, static_vnodes));
-}
-
-#[test]
-#[allow(non_snake_case)]
-fn render_listener() {
-    fn Base(cx: Scope) -> Element {
-        render!(div {})
-    }
-
-    let dom = VirtualDom::new(Base);
-    let static_vnodes = rsx! {
-        div {
-            onclick: |_| println!("clicked")
-        }
-    };
-    let location = CodeLocation {
-        file_path: String::new(),
-        crate_path: String::new(),
-        line: 6,
-        column: 0,
-    };
-
-    let interperted_vnodes = LazyNodes::new(|factory| {
-        let f = |_| println!("clicked");
-        let f = factory.bump().alloc(f);
-        let context = CapturedContext {
-            captured: IfmtArgs {
-                named_args: Vec::new(),
-            },
-            components: Vec::new(),
-            iterators: Vec::new(),
-            expressions: Vec::new(),
-            listeners: vec![(
-                r#"dioxus_elements::on::onclick(__cx, |_| println!("clicked"))"#,
-                dioxus_elements::on::onclick(factory, f),
-            )],
-            location: location.clone(),
-            custom_attributes: &[],
-        };
-        dioxus_rsx_interpreter::resolve_scope(
-            location,
-            r#"div {
-                onclick: |_| println!("clicked")
-            }"#,
-            context,
-            factory,
-        )
-    });
-
-    let interperted_vnodes = dom.render_vnodes(interperted_vnodes);
-    let static_vnodes = dom.render_vnodes(static_vnodes);
-    println!("{:#?}", interperted_vnodes);
-    println!("{:#?}", static_vnodes);
-    assert!(check_eq(interperted_vnodes, static_vnodes));
-}
-
-fn check_eq<'a>(a: &'a VNode<'a>, b: &'a VNode<'a>) -> bool {
-    match (a, b) {
-        (VNode::Text(t_a), VNode::Text(t_b)) => t_a.text == t_b.text,
-        (VNode::Element(e_a), VNode::Element(e_b)) => {
-            e_a.attributes
-                .iter()
-                .zip(e_b.attributes.iter())
-                .all(|(a, b)| {
-                    a.is_static == b.is_static
-                        && a.is_volatile == b.is_volatile
-                        && a.name == b.name
-                        && a.value == b.value
-                        && a.namespace == b.namespace
-                })
-                && e_a
-                    .children
-                    .iter()
-                    .zip(e_b.children.iter())
-                    .all(|(a, b)| check_eq(a, b))
-                && e_a.key == e_b.key
-                && e_a.tag == e_b.tag
-                && e_a.namespace == e_b.namespace
-                && e_a
-                    .listeners
-                    .iter()
-                    .zip(e_b.listeners.iter())
-                    .all(|(a, b)| a.event == b.event)
-        }
-        (VNode::Fragment(f_a), VNode::Fragment(f_b)) => {
-            f_a.key == f_b.key
-                && f_a
-                    .children
-                    .iter()
-                    .zip(f_b.children.iter())
-                    .all(|(a, b)| check_eq(a, b))
-        }
-        (VNode::Component(c_a), VNode::Component(c_b)) => {
-            c_a.can_memoize == c_b.can_memoize
-                && c_a.key == c_b.key
-                && c_a.fn_name == c_b.fn_name
-                && c_a.user_fc == c_b.user_fc
-        }
-        (VNode::Placeholder(_), VNode::Placeholder(_)) => true,
-        _ => false,
-    }
-}

+ 339 - 67
packages/ssr/src/lib.rs

@@ -1,7 +1,8 @@
 #![doc = include_str!("../README.md")]
 
-use std::fmt::{Display, Formatter};
+use std::fmt::{Display, Formatter, Write};
 
+use dioxus_core::exports::bumpalo;
 use dioxus_core::IntoVNode;
 use dioxus_core::*;
 
@@ -32,7 +33,8 @@ impl SsrRenderer {
             TextRenderer {
                 cfg: self.cfg.clone(),
                 root: &root,
-                vdom: None
+                vdom: Some(&self.vdom),
+                bump: bumpalo::Bump::new(),
             }
         )
     }
@@ -57,14 +59,18 @@ pub fn render_lazy<'a>(f: LazyNodes<'a, '_>) -> String {
 
     let root = f.into_vnode(NodeFactory::new(scope));
 
-    format!(
-        "{:}",
-        TextRenderer {
-            cfg: SsrConfig::default(),
-            root: &root,
-            vdom: None
-        }
-    )
+    let vdom = Some(&vdom);
+
+    let ssr_renderer = TextRenderer {
+        cfg: SsrConfig::default(),
+        root: &root,
+        vdom,
+        bump: bumpalo::Bump::new(),
+    };
+    let r = ssr_renderer.to_string();
+    drop(ssr_renderer);
+    drop(vdom);
+    r
 }
 
 pub fn render_vdom(dom: &VirtualDom) -> String {
@@ -91,7 +97,8 @@ pub fn render_vdom_scope(vdom: &VirtualDom, scope: ScopeId) -> Option<String> {
         TextRenderer {
             cfg: SsrConfig::default(),
             root: vdom.get_scope(scope).unwrap().root_node(),
-            vdom: Some(vdom)
+            vdom: Some(vdom),
+            bump: bumpalo::Bump::new()
         }
     ))
 }
@@ -114,32 +121,36 @@ pub fn render_vdom_scope(vdom: &VirtualDom, scope: ScopeId) -> Option<String> {
 /// let output = format!("{}", renderer);
 /// assert_eq!(output, "<div>hello world</div>");
 /// ```
-pub struct TextRenderer<'a, 'b> {
-    vdom: Option<&'a VirtualDom>,
+pub struct TextRenderer<'a, 'b, 'c> {
+    vdom: Option<&'c VirtualDom>,
     root: &'b VNode<'a>,
     cfg: SsrConfig,
+    bump: bumpalo::Bump,
 }
 
-impl Display for TextRenderer<'_, '_> {
+impl<'a: 'c, 'c> Display for TextRenderer<'a, '_, 'c> {
     fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
         let mut last_node_was_text = false;
         self.html_render(self.root, f, 0, &mut last_node_was_text)
     }
 }
 
-impl<'a> TextRenderer<'a, '_> {
+impl<'a> TextRenderer<'a, '_, 'a> {
     pub fn from_vdom(vdom: &'a VirtualDom, cfg: SsrConfig) -> Self {
         Self {
             cfg,
             root: vdom.base_scope().root_node(),
             vdom: Some(vdom),
+            bump: bumpalo::Bump::new(),
         }
     }
+}
 
+impl<'a: 'c, 'c> TextRenderer<'a, '_, 'c> {
     fn html_render(
         &self,
         node: &VNode,
-        f: &mut std::fmt::Formatter,
+        f: &mut impl Write,
         il: u16,
         last_node_was_text: &mut bool,
     ) -> std::fmt::Result {
@@ -180,59 +191,226 @@ impl<'a> TextRenderer<'a, '_> {
 
                 write!(f, "<{}", el.tag)?;
 
+                let inner_html = render_attributes(el.attributes.iter(), f)?;
+
+                match self.cfg.newline {
+                    true => writeln!(f, ">")?,
+                    false => write!(f, ">")?,
+                }
+
+                if let Some(inner_html) = inner_html {
+                    write!(f, "{}", inner_html)?;
+                } else {
+                    let mut last_node_was_text = false;
+                    for child in el.children {
+                        self.html_render(child, f, il + 1, &mut last_node_was_text)?;
+                    }
+                }
+
+                if self.cfg.newline {
+                    writeln!(f)?;
+                }
+                if self.cfg.indent {
+                    for _ in 0..il {
+                        write!(f, "    ")?;
+                    }
+                }
+
+                write!(f, "</{}>", el.tag)?;
+                if self.cfg.newline {
+                    writeln!(f)?;
+                }
+            }
+            VNode::Fragment(frag) => {
+                for child in frag.children {
+                    self.html_render(child, f, il + 1, last_node_was_text)?;
+                }
+            }
+            VNode::Component(vcomp) => {
+                let idx = vcomp.scope.get().unwrap();
+
+                if let (Some(vdom), false) = (self.vdom, self.cfg.skip_components) {
+                    let new_node = vdom.get_scope(idx).unwrap().root_node();
+                    self.html_render(new_node, f, il + 1, last_node_was_text)?;
+                } else {
+                }
+            }
+            VNode::TemplateRef(tmpl) => {
+                if let Some(vdom) = self.vdom {
+                    let template_id = &tmpl.template_id;
+                    let dynamic_context = &tmpl.dynamic_context;
+                    vdom.with_template(template_id, move |tmpl| {
+                        match tmpl {
+                            Template::Static(s) => {
+                                for r in s.root_nodes {
+                                    self.render_template_node(
+                                        &s.nodes,
+                                        &s.nodes[r.0],
+                                        dynamic_context,
+                                        &s.dynamic_mapping,
+                                        f,
+                                        last_node_was_text,
+                                        il,
+                                    )?;
+                                }
+                            }
+                            Template::Owned(o) => {
+                                for r in &o.root_nodes {
+                                    self.render_template_node(
+                                        &o.nodes,
+                                        &o.nodes[r.0],
+                                        dynamic_context,
+                                        &o.dynamic_mapping,
+                                        f,
+                                        last_node_was_text,
+                                        il,
+                                    )?;
+                                }
+                            }
+                        };
+                        Ok(())
+                    })?
+                } else {
+                    panic!("Cannot render template without vdom");
+                }
+            }
+        }
+        Ok(())
+    }
+
+    fn render_template_node<
+        TemplateNodes,
+        Attributes,
+        V,
+        Children,
+        Listeners,
+        TextSegments,
+        Text,
+        Nodes,
+        TextOuter,
+        TextInner,
+        AttributesOuter,
+        AttributesInner,
+        Volatile,
+        Listeners2,
+    >(
+        &self,
+        template_nodes: &TemplateNodes,
+        node: &TemplateNode<Attributes, V, Children, Listeners, TextSegments, Text>,
+        dynamic_context: &TemplateContext,
+        dynamic_node_mapping: &DynamicNodeMapping<
+            Nodes,
+            TextOuter,
+            TextInner,
+            AttributesOuter,
+            AttributesInner,
+            Volatile,
+            Listeners2,
+        >,
+        f: &mut impl Write,
+        last_node_was_text: &mut bool,
+        il: u16,
+    ) -> std::fmt::Result
+    where
+        TemplateNodes:
+            AsRef<[TemplateNode<Attributes, V, Children, Listeners, TextSegments, Text>]>,
+        Attributes: AsRef<[TemplateAttribute<V>]>,
+        AttributesInner: AsRef<[(TemplateNodeId, usize)]>,
+        AttributesOuter: AsRef<[AttributesInner]>,
+        Children: AsRef<[TemplateNodeId]>,
+        Listeners: AsRef<[usize]>,
+        Listeners2: AsRef<[TemplateNodeId]>,
+        Nodes: AsRef<[Option<TemplateNodeId>]>,
+        Text: AsRef<str>,
+        TextInner: AsRef<[TemplateNodeId]>,
+        TextOuter: AsRef<[TextInner]>,
+        TextSegments: AsRef<[TextTemplateSegment<Text>]>,
+        V: TemplateValue,
+        Volatile: AsRef<[(TemplateNodeId, usize)]>,
+    {
+        match &node.node_type {
+            TemplateNodeType::Element(el) => {
+                *last_node_was_text = false;
+
+                if self.cfg.indent {
+                    for _ in 0..il {
+                        write!(f, "    ")?;
+                    }
+                }
+
+                write!(f, "<{}", el.tag)?;
+
                 let mut inner_html = None;
-                let mut attr_iter = el.attributes.iter().peekable();
+
+                let mut attr_iter = el.attributes.as_ref().into_iter().peekable();
 
                 while let Some(attr) = attr_iter.next() {
-                    match attr.namespace {
-                        None => match attr.name {
-                            "dangerous_inner_html" => {
-                                inner_html = Some(attr.value.as_text().unwrap())
-                            }
-                            "allowfullscreen"
-                            | "allowpaymentrequest"
-                            | "async"
-                            | "autofocus"
-                            | "autoplay"
-                            | "checked"
-                            | "controls"
-                            | "default"
-                            | "defer"
-                            | "disabled"
-                            | "formnovalidate"
-                            | "hidden"
-                            | "ismap"
-                            | "itemscope"
-                            | "loop"
-                            | "multiple"
-                            | "muted"
-                            | "nomodule"
-                            | "novalidate"
-                            | "open"
-                            | "playsinline"
-                            | "readonly"
-                            | "required"
-                            | "reversed"
-                            | "selected"
-                            | "truespeed" => {
-                                if attr.value.is_truthy() {
-                                    write!(f, " {}=\"{}\"", attr.name, attr.value)?
+                    match attr.attribute.namespace {
+                        None => {
+                            if attr.attribute.name == "dangerous_inner_html" {
+                                inner_html = {
+                                    let text = match &attr.value {
+                                        TemplateAttributeValue::Static(val) => {
+                                            val.allocate(&self.bump).as_text().unwrap()
+                                        }
+                                        TemplateAttributeValue::Dynamic(idx) => dynamic_context
+                                            .resolve_attribute(*idx)
+                                            .as_text()
+                                            .unwrap(),
+                                    };
+                                    Some(text)
+                                }
+                            } else if is_boolean_attribute(attr.attribute.name) {
+                                match &attr.value {
+                                    TemplateAttributeValue::Static(val) => {
+                                        let val = val.allocate(&self.bump);
+                                        if val.is_truthy() {
+                                            write!(f, " {}=\"{}\"", attr.attribute.name, val)?
+                                        }
+                                    }
+                                    TemplateAttributeValue::Dynamic(idx) => {
+                                        let val = dynamic_context.resolve_attribute(*idx);
+                                        if val.is_truthy() {
+                                            write!(f, " {}=\"{}\"", attr.attribute.name, val)?
+                                        }
+                                    }
+                                }
+                            } else {
+                                match &attr.value {
+                                    TemplateAttributeValue::Static(val) => {
+                                        let val = val.allocate(&self.bump);
+                                        write!(f, " {}=\"{}\"", attr.attribute.name, val)?
+                                    }
+                                    TemplateAttributeValue::Dynamic(idx) => {
+                                        let val = dynamic_context.resolve_attribute(*idx);
+                                        write!(f, " {}=\"{}\"", attr.attribute.name, val)?
+                                    }
                                 }
                             }
-                            _ => write!(f, " {}=\"{}\"", attr.name, attr.value)?,
-                        },
+                        }
 
                         Some(ns) => {
                             // write the opening tag
                             write!(f, " {}=\"", ns)?;
                             let mut cur_ns_el = attr;
-                            'ns_parse: loop {
-                                write!(f, "{}:{};", cur_ns_el.name, cur_ns_el.value)?;
+                            loop {
+                                match &attr.value {
+                                    TemplateAttributeValue::Static(val) => {
+                                        let val = val.allocate(&self.bump);
+                                        write!(f, "{}:{};", cur_ns_el.attribute.name, val)?;
+                                    }
+                                    TemplateAttributeValue::Dynamic(idx) => {
+                                        let val = dynamic_context.resolve_attribute(*idx);
+                                        write!(f, "{}:{};", cur_ns_el.attribute.name, val)?;
+                                    }
+                                }
                                 match attr_iter.peek() {
-                                    Some(next_attr) if next_attr.namespace == Some(ns) => {
+                                    Some(next_attr)
+                                        if next_attr.attribute.namespace == Some(ns) =>
+                                    {
                                         cur_ns_el = attr_iter.next().unwrap();
                                     }
-                                    _ => break 'ns_parse,
+                                    _ => break,
                                 }
                             }
                             // write the closing tag
@@ -250,8 +428,16 @@ impl<'a> TextRenderer<'a, '_> {
                     write!(f, "{}", inner_html)?;
                 } else {
                     let mut last_node_was_text = false;
-                    for child in el.children {
-                        self.html_render(child, f, il + 1, &mut last_node_was_text)?;
+                    for child in el.children.as_ref() {
+                        self.render_template_node(
+                            template_nodes,
+                            &template_nodes.as_ref()[child.0],
+                            dynamic_context,
+                            dynamic_node_mapping,
+                            f,
+                            &mut last_node_was_text,
+                            il + 1,
+                        )?;
                     }
                 }
 
@@ -269,22 +455,108 @@ impl<'a> TextRenderer<'a, '_> {
                     writeln!(f)?;
                 }
             }
-            VNode::Fragment(frag) => {
-                for child in frag.children {
-                    self.html_render(child, f, il + 1, last_node_was_text)?;
+            TemplateNodeType::Text(txt) => {
+                if *last_node_was_text {
+                    write!(f, "<!--spacer-->")?;
+                }
+
+                if self.cfg.indent {
+                    for _ in 0..il {
+                        write!(f, "    ")?;
+                    }
                 }
+
+                *last_node_was_text = true;
+
+                let text = dynamic_context.resolve_text(&txt.segments);
+
+                write!(f, "{}", text)?
             }
-            VNode::Component(vcomp) => {
-                let idx = vcomp.scope.get().unwrap();
+            TemplateNodeType::DynamicNode(idx) => {
+                let node = dynamic_context.resolve_node(*idx);
+                self.html_render(node, f, il, last_node_was_text)?;
+            }
+        }
+        Ok(())
+    }
+}
 
-                if let (Some(vdom), false) = (self.vdom, self.cfg.skip_components) {
-                    let new_node = vdom.get_scope(idx).unwrap().root_node();
-                    self.html_render(new_node, f, il + 1, last_node_was_text)?;
+fn render_attributes<'a, 'b: 'a, I>(
+    attrs: I,
+    f: &mut impl Write,
+) -> Result<Option<&'b str>, std::fmt::Error>
+where
+    I: Iterator<Item = &'a Attribute<'b>>,
+{
+    let mut inner_html = None;
+    let mut attr_iter = attrs.peekable();
+
+    while let Some(attr) = attr_iter.next() {
+        match attr.attribute.namespace {
+            None => {
+                if attr.attribute.name == "dangerous_inner_html" {
+                    inner_html = Some(attr.value.as_text().unwrap())
                 } else {
+                    if is_boolean_attribute(attr.attribute.name) {
+                        if !attr.value.is_truthy() {
+                            continue;
+                        }
+                    }
+                    write!(f, " {}=\"{}\"", attr.attribute.name, attr.value)?
                 }
             }
+            Some(ns) => {
+                // write the opening tag
+                write!(f, " {}=\"", ns)?;
+                let mut cur_ns_el = attr;
+                loop {
+                    write!(f, "{}:{};", cur_ns_el.attribute.name, cur_ns_el.value)?;
+                    match attr_iter.peek() {
+                        Some(next_attr) if next_attr.attribute.namespace == Some(ns) => {
+                            cur_ns_el = attr_iter.next().unwrap();
+                        }
+                        _ => break,
+                    }
+                }
+                // write the closing tag
+                write!(f, "\"")?;
+            }
         }
-        Ok(())
+    }
+    Ok(inner_html)
+}
+
+fn is_boolean_attribute(attribute: &'static str) -> bool {
+    if let "allowfullscreen"
+    | "allowpaymentrequest"
+    | "async"
+    | "autofocus"
+    | "autoplay"
+    | "checked"
+    | "controls"
+    | "default"
+    | "defer"
+    | "disabled"
+    | "formnovalidate"
+    | "hidden"
+    | "ismap"
+    | "itemscope"
+    | "loop"
+    | "multiple"
+    | "muted"
+    | "nomodule"
+    | "novalidate"
+    | "open"
+    | "playsinline"
+    | "readonly"
+    | "required"
+    | "reversed"
+    | "selected"
+    | "truespeed" = attribute
+    {
+        true
+    } else {
+        false
     }
 }
 

+ 18 - 11
packages/tui/src/focus.rs

@@ -1,6 +1,6 @@
 use crate::{node::PreventDefault, Dom};
 
-use dioxus_core::ElementId;
+use dioxus_core::GlobalNodeId;
 use dioxus_native_core::utils::{ElementProduced, PersistantElementIter};
 use dioxus_native_core_macro::sorted_str_slice;
 
@@ -69,7 +69,10 @@ impl NodeDepState<()> for Focus {
 
     fn reduce(&mut self, node: NodeView<'_>, _sibling: (), _: &Self::Ctx) -> bool {
         let new = Focus {
-            level: if let Some(a) = node.attributes().find(|a| a.name == "tabindex") {
+            level: if let Some(a) = node
+                .attributes()
+                .and_then(|mut a| a.find(|a| a.attribute.name == "tabindex"))
+            {
                 if let Some(index) = a
                     .value
                     .as_int32()
@@ -87,8 +90,12 @@ impl NodeDepState<()> for Focus {
                 }
             } else if node
                 .listeners()
-                .iter()
-                .any(|l| FOCUS_EVENTS.binary_search(&l.event).is_ok())
+                .map(|mut listeners| {
+                    listeners
+                        .any(|l| FOCUS_EVENTS.binary_search(&l).is_ok())
+                        .then(|| ())
+                })
+                .is_some()
             {
                 FocusLevel::Focusable
             } else {
@@ -110,7 +117,7 @@ const FOCUS_ATTRIBUTES: &[&str] = &sorted_str_slice!(["tabindex"]);
 #[derive(Default)]
 pub(crate) struct FocusState {
     pub(crate) focus_iter: PersistantElementIter,
-    pub(crate) last_focused_id: Option<ElementId>,
+    pub(crate) last_focused_id: Option<GlobalNodeId>,
     pub(crate) focus_level: FocusLevel,
     pub(crate) dirty: bool,
 }
@@ -224,9 +231,9 @@ impl FocusState {
 
     pub(crate) fn prune(&mut self, mutations: &dioxus_core::Mutations, rdom: &Dom) {
         fn remove_children(
-            to_prune: &mut [&mut Option<ElementId>],
+            to_prune: &mut [&mut Option<GlobalNodeId>],
             rdom: &Dom,
-            removed: ElementId,
+            removed: GlobalNodeId,
         ) {
             for opt in to_prune.iter_mut() {
                 if let Some(id) = opt {
@@ -235,7 +242,7 @@ impl FocusState {
                     }
                 }
             }
-            if let NodeType::Element { children, .. } = &rdom[removed].node_type {
+            if let NodeType::Element { children, .. } = &rdom[removed].node_data.node_type {
                 for child in children {
                     remove_children(to_prune, rdom, *child);
                 }
@@ -249,19 +256,19 @@ impl FocusState {
                 dioxus_core::DomEdit::ReplaceWith { root, .. } => remove_children(
                     &mut [&mut self.last_focused_id],
                     rdom,
-                    ElementId(*root as usize),
+                    rdom.decode_id(*root),
                 ),
                 dioxus_core::DomEdit::Remove { root } => remove_children(
                     &mut [&mut self.last_focused_id],
                     rdom,
-                    ElementId(*root as usize),
+                    rdom.decode_id(*root),
                 ),
                 _ => (),
             }
         }
     }
 
-    pub(crate) fn set_focus(&mut self, rdom: &mut Dom, id: ElementId) {
+    pub(crate) fn set_focus(&mut self, rdom: &mut Dom, id: GlobalNodeId) {
         if let Some(old) = self.last_focused_id.replace(id) {
             rdom[old].state.focused = false;
         }

+ 9 - 8
packages/tui/src/hooks.rs

@@ -243,23 +243,24 @@ impl InnerInputState {
         fn try_create_event(
             name: &'static str,
             data: Arc<dyn Any + Send + Sync>,
-            will_bubble: &mut FxHashSet<ElementId>,
+            will_bubble: &mut FxHashSet<GlobalNodeId>,
             resolved_events: &mut Vec<UserEvent>,
             node: &Node,
             dom: &Dom,
         ) {
             // only trigger event if the event was not triggered already by a child
-            if will_bubble.insert(node.id) {
-                let mut parent = node.parent;
+            let id = node.node_data.id;
+            if will_bubble.insert(id) {
+                let mut parent = node.node_data.parent;
                 while let Some(parent_id) = parent {
                     will_bubble.insert(parent_id);
-                    parent = dom[parent_id].parent;
+                    parent = dom[parent_id].node_data.parent;
                 }
                 resolved_events.push(UserEvent {
                     scope_id: None,
                     priority: EventPriority::Medium,
                     name,
-                    element: Some(node.id),
+                    element: Some(id),
                     data,
                     bubbles: event_bubbles(name),
                 })
@@ -546,7 +547,7 @@ impl InnerInputState {
                     let currently_contains = layout_contains_point(node_layout, new_pos);
 
                     if currently_contains && node.state.focus.level.focusable() {
-                        focus_id = Some(node.id);
+                        focus_id = Some(node.node_data.id);
                     }
                 });
                 if let Some(id) = focus_id {
@@ -565,7 +566,7 @@ fn get_abs_layout(node: &Node, dom: &Dom, taffy: &Taffy) -> Layout {
     let mut node_layout = *taffy.layout(node.state.layout.node.unwrap()).unwrap();
     let mut current = node;
 
-    while let Some(parent_id) = current.parent {
+    while let Some(parent_id) = current.node_data.parent {
         let parent = &dom[parent_id];
         current = parent;
         let parent_layout = taffy.layout(parent.state.layout.node.unwrap()).unwrap();
@@ -664,7 +665,7 @@ impl RinkInputHandler {
                             scope_id: None,
                             priority: EventPriority::Medium,
                             name: event,
-                            element: Some(node.id),
+                            element: Some(node.node_data.id),
                             data: data.clone(),
                             bubbles: event_bubbles(event),
                         });

+ 12 - 4
packages/tui/src/layout.rs

@@ -4,6 +4,7 @@ use std::rc::Rc;
 use dioxus_core::*;
 use dioxus_native_core::layout_attributes::apply_layout_attributes;
 use dioxus_native_core::node_ref::{AttributeMask, NodeMask, NodeView};
+use dioxus_native_core::real_dom::OwnedAttributeView;
 use dioxus_native_core::state::ChildDepState;
 use dioxus_native_core_macro::sorted_str_slice;
 use taffy::prelude::*;
@@ -84,10 +85,17 @@ impl ChildDepState for TaffyLayout {
             }
         } else {
             // gather up all the styles from the attribute list
-            for Attribute { name, value, .. } in node.attributes() {
-                assert!(SORTED_LAYOUT_ATTRS.binary_search(name).is_ok());
-                if let Some(text) = value.as_text() {
-                    apply_layout_attributes(name, text, &mut style);
+            if let Some(attributes) = node.attributes() {
+                for OwnedAttributeView {
+                    attribute, value, ..
+                } in attributes
+                {
+                    assert!(SORTED_LAYOUT_ATTRS
+                        .binary_search(&attribute.name.as_ref())
+                        .is_ok());
+                    if let Some(text) = value.as_text() {
+                        apply_layout_attributes(&&attribute.name, text, &mut style);
+                    }
                 }
             }
 

+ 6 - 4
packages/tui/src/lib.rs

@@ -94,7 +94,7 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) {
         let to_update = rdom.apply_mutations(vec![mutations]);
         let mut any_map = AnyMap::new();
         any_map.insert(taffy.clone());
-        let _to_rerender = rdom.update_state(&dom, to_update, any_map);
+        let _to_rerender = rdom.update_state(to_update, any_map);
     }
 
     render_vdom(
@@ -133,8 +133,10 @@ fn render_vdom(
                 terminal.clear().unwrap();
             }
 
-            let mut to_rerender: fxhash::FxHashSet<ElementId> =
-                vec![ElementId(0)].into_iter().collect();
+            let mut to_rerender: fxhash::FxHashSet<GlobalNodeId> =
+                vec![GlobalNodeId::VNodeId(ElementId(0))]
+                    .into_iter()
+                    .collect();
             let mut updated = true;
 
             loop {
@@ -250,7 +252,7 @@ fn render_vdom(
                     // update the style and layout
                     let mut any_map = AnyMap::new();
                     any_map.insert(taffy.clone());
-                    to_rerender = rdom.update_state(vdom, to_update, any_map);
+                    to_rerender = rdom.update_state(to_update, any_map);
                 }
             }
 

+ 5 - 5
packages/tui/src/node.rs

@@ -61,11 +61,11 @@ impl NodeDepState<()> for PreventDefault {
         _sibling: (),
         _ctx: &Self::Ctx,
     ) -> bool {
-        let new = match node
-            .attributes()
-            .find(|a| a.name == "dioxus-prevent-default")
-            .and_then(|a| a.value.as_text())
-        {
+        let new = match node.attributes().and_then(|mut attrs| {
+            attrs
+                .find(|a| a.attribute.name == "dioxus-prevent-default")
+                .and_then(|a| a.value.as_text())
+        }) {
             Some("onfocus") => PreventDefault::Focus,
             Some("onkeypress") => PreventDefault::KeyPress,
             Some("onkeyrelease") => PreventDefault::KeyRelease,

+ 2 - 2
packages/tui/src/render.rs

@@ -25,7 +25,7 @@ pub(crate) fn render_vnode(
 ) {
     use dioxus_native_core::real_dom::NodeType;
 
-    if let NodeType::Placeholder = &node.node_type {
+    if let NodeType::Placeholder = &node.node_data.node_type {
         return;
     }
 
@@ -38,7 +38,7 @@ pub(crate) fn render_vnode(
     let Point { x, y } = location;
     let Size { width, height } = size;
 
-    match &node.node_type {
+    match &node.node_data.node_type {
         NodeType::Text { text } => {
             #[derive(Default)]
             struct Label<'a> {

+ 9 - 4
packages/tui/src/style_attributes.rs

@@ -29,10 +29,10 @@
 - [ ] pub aspect_ratio: Number,
 */
 
-use dioxus_core::Attribute;
 use dioxus_native_core::{
     layout_attributes::parse_value,
     node_ref::{AttributeMask, NodeMask, NodeView},
+    real_dom::OwnedAttributeView,
     state::ParentDepState,
 };
 use dioxus_native_core_macro::sorted_str_slice;
@@ -79,9 +79,14 @@ impl ParentDepState for StyleModifier {
         }
 
         // gather up all the styles from the attribute list
-        for Attribute { name, value, .. } in node.attributes() {
-            if let Some(text) = value.as_text() {
-                apply_style_attributes(name, text, &mut new);
+        if let Some(attrs) = node.attributes() {
+            for OwnedAttributeView {
+                attribute, value, ..
+            } in attrs
+            {
+                if let Some(text) = value.as_text() {
+                    apply_style_attributes(&attribute.name, text, &mut new);
+                }
             }
         }
 

+ 7 - 3
packages/web/Cargo.toml

@@ -16,7 +16,6 @@ dioxus-html = { path = "../html", version = "^0.2.1", features = ["wasm-bind"] }
 dioxus-interpreter-js = { path = "../interpreter", version = "^0.2.1", features = [
     "web"
 ] }
-dioxus-rsx-interpreter = { path = "../rsx_interpreter", version = "^0.1.0", optional = true }
 
 js-sys = "0.3.56"
 wasm-bindgen = { version = "0.2.79", features = ["enable-interning"] }
@@ -31,7 +30,7 @@ futures-util = "0.3.19"
 smallstr = "0.2.0"
 serde-wasm-bindgen = "0.4.2"
 futures-channel = "0.3.21"
-serde_json = { version = "1.0", optional = true }
+serde_json = { version = "1.0" }
 
 [dependencies.web-sys]
 version = "0.3.56"
@@ -72,12 +71,17 @@ features = [
     "SvgAnimatedString",
     "HtmlOptionElement",
     "IdleDeadline",
+    "WebSocket",
+    "Location",
+    "MessageEvent",
+    "console",
 ]
 
 [features]
 default = ["panic_hook"]
 panic_hook = ["console_error_panic_hook"]
-hot-reload = ["dioxus-rsx-interpreter", "web-sys/WebSocket", "web-sys/Location", "web-sys/MessageEvent", "web-sys/console", "serde_json"]
+hot-reload = ["dioxus/hot-reload"]
+
 
 [dev-dependencies]
 dioxus = { path = "../dioxus" }

+ 1 - 0
packages/web/examples/hydrate.rs

@@ -24,6 +24,7 @@ fn app(cx: Scope) -> Element {
     })
 }
 
+#[allow(non_snake_case)]
 fn Bapp(cx: Scope) -> Element {
     cx.render(rsx! {
         div {

+ 41 - 2
packages/web/src/dom.rs

@@ -7,7 +7,7 @@
 //! - tests to ensure dyn_into works for various event types.
 //! - Partial delegation?>
 
-use dioxus_core::{DomEdit, ElementId, SchedulerMsg, UserEvent};
+use dioxus_core::{DomEdit, SchedulerMsg, UserEvent};
 use dioxus_html::event_bubbles;
 use dioxus_interpreter_js::Interpreter;
 use js_sys::Function;
@@ -43,7 +43,7 @@ impl WebsysDom {
                         break Ok(UserEvent {
                             name: event_name_from_typ(&typ),
                             data: virtual_event_from_websys_event(event.clone(), target.clone()),
-                            element: Some(ElementId(id)),
+                            element: Some(id),
                             scope_id: None,
                             priority: dioxus_core::EventPriority::Medium,
                             bubbles: event.bubbles(),
@@ -157,6 +157,45 @@ impl WebsysDom {
                     let value = serde_wasm_bindgen::to_value(&value).unwrap();
                     self.interpreter.SetAttribute(root, field, value, ns)
                 }
+                DomEdit::CreateTemplateRef { id, template_id } => {
+                    self.interpreter.CreateTemplateRef(id, template_id)
+                }
+                DomEdit::CreateTemplate { id } => self.interpreter.CreateTemplate(id),
+                DomEdit::FinishTemplate { len } => self.interpreter.FinishTemplate(len),
+                DomEdit::EnterTemplateRef { root } => self.interpreter.EnterTemplateRef(root),
+                DomEdit::ExitTemplateRef {} => self.interpreter.ExitTemplateRef(),
+                DomEdit::CreateTextNodeTemplate {
+                    root,
+                    text,
+                    locally_static,
+                } => self
+                    .interpreter
+                    .CreateTextNodeTemplate(text, root, locally_static),
+                DomEdit::CreateElementTemplate {
+                    root,
+                    tag,
+                    locally_static,
+                    fully_static,
+                } => {
+                    self.interpreter
+                        .CreateElementTemplate(tag, root, locally_static, fully_static)
+                }
+                DomEdit::CreateElementNsTemplate {
+                    root,
+                    tag,
+                    ns,
+                    locally_static,
+                    fully_static,
+                } => self.interpreter.CreateElementNsTemplate(
+                    tag,
+                    root,
+                    ns,
+                    locally_static,
+                    fully_static,
+                ),
+                DomEdit::CreatePlaceholderTemplate { root } => {
+                    self.interpreter.CreatePlaceholderTemplate(root)
+                }
             }
         }
     }

+ 9 - 40
packages/web/src/hot_reload.rs

@@ -1,12 +1,9 @@
+use dioxus_core::SchedulerMsg;
+use dioxus_core::SetTemplateMsg;
 use dioxus_core::VirtualDom;
-use dioxus_rsx_interpreter::error::Error;
-use dioxus_rsx_interpreter::{ErrorHandler, SetManyRsxMessage, RSX_CONTEXT};
-use futures_channel::mpsc::unbounded;
-use futures_channel::mpsc::UnboundedSender;
-use futures_util::StreamExt;
 use wasm_bindgen::closure::Closure;
 use wasm_bindgen::JsCast;
-use web_sys::{console, MessageEvent, WebSocket};
+use web_sys::{MessageEvent, WebSocket};
 
 pub(crate) fn init(dom: &VirtualDom) {
     let window = web_sys::window().unwrap();
@@ -23,46 +20,18 @@ pub(crate) fn init(dom: &VirtualDom) {
     );
 
     let ws = WebSocket::new(&url).unwrap();
+    let mut channel = dom.get_scheduler_channel();
 
     // change the rsx when new data is received
-    let cl = Closure::wrap(Box::new(|e: MessageEvent| {
+    let cl = Closure::wrap(Box::new(move |e: MessageEvent| {
         if let Ok(text) = e.data().dyn_into::<js_sys::JsString>() {
-            let msgs: SetManyRsxMessage = serde_json::from_str(&format!("{text}")).unwrap();
-            RSX_CONTEXT.extend(msgs);
+            let msg: SetTemplateMsg = serde_json::from_str(&format!("{text}")).unwrap();
+            channel
+                .start_send(SchedulerMsg::SetTemplate(Box::new(msg)))
+                .unwrap();
         }
     }) as Box<dyn FnMut(MessageEvent)>);
 
     ws.set_onmessage(Some(cl.as_ref().unchecked_ref()));
     cl.forget();
-
-    let (error_channel_sender, mut error_channel_receiver) = unbounded();
-
-    struct WebErrorHandler {
-        sender: UnboundedSender<Error>,
-    }
-
-    impl ErrorHandler for WebErrorHandler {
-        fn handle_error(&self, err: dioxus_rsx_interpreter::error::Error) {
-            self.sender.unbounded_send(err).unwrap();
-        }
-    }
-
-    RSX_CONTEXT.set_error_handler(WebErrorHandler {
-        sender: error_channel_sender,
-    });
-
-    RSX_CONTEXT.provide_scheduler_channel(dom.get_scheduler_channel());
-
-    // forward stream to the websocket
-    dom.base_scope().spawn_forever(async move {
-        while let Some(err) = error_channel_receiver.next().await {
-            if ws.ready_state() == WebSocket::OPEN {
-                ws.send_with_str(serde_json::to_string(&err).unwrap().as_str())
-                    .unwrap();
-            } else {
-                console::warn_1(&"WebSocket is not open, cannot send error. Run with dioxus serve --hot-reload to enable hot reloading.".into());
-                panic!("{}", err);
-            }
-        }
-    });
 }

+ 8 - 8
packages/web/src/lib.rs

@@ -61,15 +61,14 @@ pub use crate::util::use_eval;
 use dioxus_core::prelude::Component;
 use dioxus_core::SchedulerMsg;
 use dioxus_core::VirtualDom;
-use futures_util::FutureExt;
 
 mod cache;
 mod cfg;
 mod dom;
-#[cfg(feature = "hot-reload")]
+#[cfg(any(feature = "hot-reload", debug_assertions))]
 mod hot_reload;
 mod rehydrate;
-mod ric_raf;
+// mod ric_raf;
 mod util;
 
 /// Launch the VirtualDOM given a root component and a configuration.
@@ -169,7 +168,7 @@ pub async fn run_with_props<T: 'static + Send>(root: Component<T>, root_props: T
         console_error_panic_hook::set_once();
     }
 
-    #[cfg(feature = "hot-reload")]
+    #[cfg(any(feature = "hot-reload", debug_assertions))]
     hot_reload::init(&dom);
 
     for s in crate::cache::BUILTIN_INTERNED_STRINGS {
@@ -214,7 +213,7 @@ pub async fn run_with_props<T: 'static + Send>(root: Component<T>, root_props: T
         websys_dom.apply_edits(edits.edits);
     }
 
-    let mut work_loop = ric_raf::RafLoop::new();
+    // let mut work_loop = ric_raf::RafLoop::new();
 
     loop {
         log::trace!("waiting for work");
@@ -225,13 +224,14 @@ pub async fn run_with_props<T: 'static + Send>(root: Component<T>, root_props: T
         log::trace!("working..");
 
         // wait for the mainthread to schedule us in
-        let mut deadline = work_loop.wait_for_idle_time().await;
+        // let mut deadline = work_loop.wait_for_idle_time().await;
 
         // run the virtualdom work phase until the frame deadline is reached
-        let mutations = dom.work_with_deadline(|| (&mut deadline).now_or_never().is_some());
+        // let mutations = dom.work_with_deadline(|| (&mut deadline).now_or_never().is_some());
+        let mutations = dom.work_with_deadline(|| false);
 
         // wait for the animation frame to fire so we can apply our changes
-        work_loop.wait_for_raf().await;
+        // work_loop.wait_for_raf().await;
 
         for edit in mutations {
             // actually apply our changes during the animation frame

+ 25 - 6
packages/web/src/rehydrate.rs

@@ -108,12 +108,30 @@ impl WebsysDom {
                 }
 
                 for listener in vel.listeners {
-                    self.interpreter.NewEventListener(
-                        listener.event,
-                        listener.mounted_node.get().unwrap().as_u64(),
-                        self.handler.as_ref().unchecked_ref(),
-                        event_bubbles(listener.event),
-                    );
+                    let global_id = listener.mounted_node.get().unwrap();
+                    match global_id {
+                        dioxus_core::GlobalNodeId::TemplateId {
+                            template_ref_id,
+                            template_node_id: id,
+                        } => {
+                            self.interpreter.EnterTemplateRef(template_ref_id.into());
+                            self.interpreter.NewEventListener(
+                                listener.event,
+                                id.into(),
+                                self.handler.as_ref().unchecked_ref(),
+                                event_bubbles(listener.event),
+                            );
+                            self.interpreter.ExitTemplateRef();
+                        }
+                        dioxus_core::GlobalNodeId::VNodeId(id) => {
+                            self.interpreter.NewEventListener(
+                                listener.event,
+                                id.into(),
+                                self.handler.as_ref().unchecked_ref(),
+                                event_bubbles(listener.event),
+                            );
+                        }
+                    }
                 }
 
                 if !vel.listeners.is_empty() {
@@ -165,6 +183,7 @@ impl WebsysDom {
                 let node = scope.root_node();
                 self.rehydrate_single(nodes, place, dom, node, last_node_was_text)?;
             }
+            VNode::TemplateRef(_) => todo!(),
         }
         Ok(())
     }