Browse Source

Merge branch 'upstream' into return-from-js

Evan Almloff 2 năm trước cách đây
mục cha
commit
bf47f64852
100 tập tin đã thay đổi với 3098 bổ sung4429 xóa
  1. 7 0
      .github/dependabot.yml
  2. 2 2
      .github/workflows/docs.yml
  3. 1 1
      .github/workflows/macos.yml
  4. 5 5
      .github/workflows/main.yml
  5. 1 1
      .github/workflows/windows.yml
  6. 5 7
      Cargo.toml
  7. 1 1
      README.md
  8. 1 1
      docs/guide/examples/component_children_inspect.rs
  9. 2 2
      docs/guide/examples/conditional_rendering.rs
  10. 0 1
      docs/guide/examples/event_handler_prop.rs
  11. 1 1
      docs/guide/examples/event_nested.rs
  12. 1 1
      docs/guide/examples/hello_world_tui_no_ctrl_c.rs
  13. 7 7
      docs/guide/examples/hooks_bad.rs
  14. 1 1
      docs/guide/examples/hooks_composed.rs
  15. 1 1
      docs/guide/examples/hooks_counter.rs
  16. 2 2
      docs/guide/examples/hooks_counter_two_state.rs
  17. 1 1
      docs/guide/examples/hooks_use_ref.rs
  18. 1 1
      docs/guide/examples/input_controlled.rs
  19. 1 2
      docs/guide/examples/meme_editor.rs
  20. 7 8
      docs/guide/examples/meme_editor_dark_mode.rs
  21. 3 3
      docs/guide/examples/rendering_lists.rs
  22. 1 1
      docs/guide/examples/rsx_overview.rs
  23. 8 8
      docs/guide/examples/spawn.rs
  24. 3 3
      docs/guide/examples/use_future.rs
  25. 11 11
      docs/guide/src/en/__unused/advanced-guides/12-signals.md
  26. 1 1
      docs/guide/src/en/__unused/composing.md
  27. 6 6
      docs/guide/src/en/__unused/fanout.md
  28. 7 7
      docs/guide/src/en/__unused/localstate.md
  29. 2 2
      docs/guide/src/en/__unused/memoization.md
  30. 1 1
      docs/guide/src/en/__unused/model_pattern.md
  31. 12 12
      docs/guide/src/en/async/use_coroutine.md
  32. 5 5
      docs/guide/src/en/best_practices/error_handling.md
  33. 2 2
      docs/guide/src/en/index.md
  34. 12 12
      docs/guide/src/pt-br/async/use_coroutine.md
  35. 5 5
      docs/guide/src/pt-br/best_practices/error_handling.md
  36. 1 1
      docs/guide/src/pt-br/index.md
  37. 7 7
      docs/posts/release-0-2-0.md
  38. 5 5
      docs/posts/release.md
  39. 11 11
      docs/router/src/guide/building-a-nest.md
  40. 401 399
      examples/all_css.rs
  41. 2 2
      examples/all_events.rs
  42. 2 6
      examples/borrowed.rs
  43. 11 23
      examples/calculator.rs
  44. 22 0
      examples/callback.rs
  45. 4 4
      examples/crm.rs
  46. 9 7
      examples/custom_element.rs
  47. 1 1
      examples/disabled.rs
  48. 26 34
      examples/dog_app.rs
  49. 13 1
      examples/error_handle.rs
  50. 3 3
      examples/eval.rs
  51. 4 4
      examples/fermi.rs
  52. 3 3
      examples/file_explorer.rs
  53. 3 3
      examples/framework_benchmark.rs
  54. 13 0
      examples/generic_component.rs
  55. 2 2
      examples/hydration.rs
  56. 1 1
      examples/inlineprops.rs
  57. 3 5
      examples/inputs.rs
  58. 2 4
      examples/login_form.rs
  59. 1 1
      examples/nested_listeners.rs
  60. 2 1
      examples/pattern_model.rs
  61. 1 1
      examples/pattern_reducer.rs
  62. 1 1
      examples/readme.rs
  63. 3 3
      examples/router.rs
  64. 7 6
      examples/rsx_compile_fail.rs
  65. 4 4
      examples/rsx_usage.rs
  66. 14 11
      examples/simple_list.rs
  67. 5 12
      examples/ssr.rs
  68. 1 1
      examples/suspense.rs
  69. 15 13
      examples/svg.rs
  70. 2 2
      examples/tasks.rs
  71. 1 1
      examples/textarea.rs
  72. 8 6
      examples/todomvc.rs
  73. 10 10
      examples/window_event.rs
  74. 2 2
      examples/window_zoom.rs
  75. 1 1
      examples/xss_safety.rs
  76. 2 2
      notes/README.md
  77. 99 102
      notes/README/ZH_CN.md
  78. 2 2
      notes/SOLVEDPROBLEMS.md
  79. 5 1
      packages/core-macro/src/inlineprops.rs
  80. 4 26
      packages/core-macro/src/lib.rs
  81. 1 1
      packages/core-macro/src/props/mod.rs
  82. 4 17
      packages/core/Cargo.toml
  83. 64 46
      packages/core/README.md
  84. 5 0
      packages/core/architecture.md
  85. 73 0
      packages/core/src/any_props.rs
  86. 0 642
      packages/core/src/arbitrary_value.rs
  87. 163 0
      packages/core/src/arena.rs
  88. 29 0
      packages/core/src/bump_frame.rs
  89. 401 0
      packages/core/src/create.rs
  90. 538 1115
      packages/core/src/diff.rs
  91. 19 0
      packages/core/src/dirty_scope.rs
  92. 0 190
      packages/core/src/dynamic_template_context.rs
  93. 19 0
      packages/core/src/error_boundary.rs
  94. 127 144
      packages/core/src/events.rs
  95. 103 0
      packages/core/src/fragment.rs
  96. 19 91
      packages/core/src/lazynodes.rs
  97. 34 83
      packages/core/src/lib.rs
  98. 186 333
      packages/core/src/mutations.rs
  99. 446 811
      packages/core/src/nodes.rs
  100. 1 96
      packages/core/src/properties.rs

+ 7 - 0
.github/dependabot.yml

@@ -0,0 +1,7 @@
+version: 2
+updates:
+  # Maintain dependencies for GitHub Actions
+  - package-ecosystem: "github-actions"
+    directory: "/"
+    schedule:
+      interval: "weekly"

+ 2 - 2
.github/workflows/docs.yml

@@ -13,7 +13,7 @@ jobs:
     runs-on: ubuntu-latest
     environment: docs
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
 
       # NOTE: Comment out when https://github.com/rust-lang/mdBook/pull/1306 is merged and released
       # - name: Setup mdBook
@@ -32,7 +32,7 @@ jobs:
           cd router && mdbook build -d ../nightly/router  && cd ..
 
       - name: Deploy 🚀
-        uses: JamesIves/github-pages-deploy-action@v4.2.3
+        uses: JamesIves/github-pages-deploy-action@v4.4.1
         with:
           branch: gh-pages # The branch the action should deploy to.
           folder: docs/nightly # The folder the action should deploy.

+ 1 - 1
.github/workflows/macos.yml

@@ -30,7 +30,7 @@ jobs:
     name: Test Suite
     runs-on: macos-latest
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
       - uses: actions-rs/toolchain@v1
         with:
           profile: minimal

+ 5 - 5
.github/workflows/main.yml

@@ -32,7 +32,7 @@ jobs:
     name: Check
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
       - uses: actions-rs/toolchain@v1
         with:
           profile: minimal
@@ -51,7 +51,7 @@ jobs:
     name: Test Suite
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
       - uses: actions-rs/toolchain@v1
         with:
           profile: minimal
@@ -73,7 +73,7 @@ jobs:
     name: Rustfmt
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
       - uses: actions-rs/toolchain@v1
         with:
           profile: minimal
@@ -91,7 +91,7 @@ jobs:
     name: Clippy
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
       - uses: actions-rs/toolchain@v1
         with:
           profile: minimal
@@ -115,7 +115,7 @@ jobs:
   #     options: --security-opt seccomp=unconfined
   #   steps:
   #     - name: Checkout repository
-  #       uses: actions/checkout@v2
+  #       uses: actions/checkout@v3
   #     - name: Generate code coverage
   #       run: |
   #         apt-get update &&\

+ 1 - 1
.github/workflows/windows.yml

@@ -48,7 +48,7 @@ jobs:
       - name: disable git eol translation
         run: git config --global core.autocrlf false
       - name: checkout
-        uses: actions/checkout@v2
+        uses: actions/checkout@v3
 
         # Run build
       - name: Install Rustup using win.rustup.rs

+ 5 - 7
Cargo.toml

@@ -1,4 +1,3 @@
-
 [workspace]
 members = [
     "packages/dioxus",
@@ -13,16 +12,15 @@ members = [
     "packages/mobile",
     "packages/interpreter",
     "packages/fermi",
-    "packages/tui",
     "packages/liveview",
     "packages/autofmt",
     "packages/rsx",
+    "docs/guide",
+    "packages/tui",
     "packages/native-core",
     "packages/native-core-macro",
-    "docs/guide",
 ]
 
-
 # This is a "virtual package"
 # It is not meant to be published, but is used so "cargo run --example XYZ" works properly
 [package]
@@ -37,11 +35,11 @@ homepage = "https://dioxuslabs.com"
 documentation = "https://dioxuslabs.com"
 keywords = ["dom", "ui", "gui", "react", "wasm"]
 rust-version = "1.60.0"
-
+publish = false
 
 [dev-dependencies]
 dioxus = { path = "./packages/dioxus" }
-dioxus-desktop = { path = "./packages/desktop", features = ["hot-reload"] }
+dioxus-desktop = { path = "./packages/desktop" }
 dioxus-ssr = { path = "./packages/ssr" }
 dioxus-router = { path = "./packages/router" }
 fermi = { path = "./packages/fermi" }
@@ -63,4 +61,4 @@ env_logger = "0.9.0"
 [profile.release]
 opt-level = 3
 lto = true
-debug = true
+debug = true

+ 1 - 1
README.md

@@ -54,7 +54,7 @@ Dioxus is a portable, performant, and ergonomic framework for building cross-pla
 
 ```rust
 fn app(cx: Scope) -> Element {
-    let mut count = use_state(&cx, || 0);
+    let mut count = use_state(cx, || 0);
 
     cx.render(rsx! {
         h1 { "High-Five counter: {count}" }

+ 1 - 1
docs/guide/examples/component_children_inspect.rs

@@ -26,7 +26,7 @@ struct ClickableProps<'a> {
 // ANCHOR: Clickable
 fn Clickable<'a>(cx: Scope<'a, ClickableProps<'a>>) -> Element {
     match cx.props.children {
-        Some(VNode::Text(_)) => {
+        Ok(VNode { dynamic_nodes, .. }) => {
             todo!("render some stuff")
         }
         _ => {

+ 2 - 2
docs/guide/examples/conditional_rendering.rs

@@ -6,7 +6,7 @@ fn main() {
 }
 
 pub fn App(cx: Scope) -> Element {
-    let is_logged_in = use_state(&cx, || false);
+    let is_logged_in = use_state(cx, || false);
 
     cx.render(rsx!(LogIn {
         is_logged_in: **is_logged_in,
@@ -48,7 +48,7 @@ fn LogIn<'a>(
 fn LogInWarning(cx: Scope, is_logged_in: bool) -> Element {
     // ANCHOR: conditional_none
     if *is_logged_in {
-        return None;
+        return cx.render(rsx!(()));
     }
 
     cx.render(rsx! {

+ 0 - 1
docs/guide/examples/event_handler_prop.rs

@@ -1,6 +1,5 @@
 #![allow(non_snake_case)]
 
-use dioxus::events::MouseEvent;
 use dioxus::prelude::*;
 
 fn main() {

+ 1 - 1
docs/guide/examples/event_nested.rs

@@ -14,7 +14,7 @@ fn App(cx: Scope) -> Element {
             button {
                 onclick: move |event| {
                     // now, outer won't be triggered
-                    event.cancel_bubble();
+                    event.stop_propogation();
                 },
                 "inner"
             }

+ 1 - 1
docs/guide/examples/hello_world_tui_no_ctrl_c.rs

@@ -26,7 +26,7 @@ fn App(cx: Scope) -> Element {
             background_color: "red",
             justify_content: "center",
             align_items: "center",
-            onkeydown: move |k: KeyboardEvent| if let KeyCode::Q = k.data.key_code {
+            onkeydown: move |k: KeyboardEvent| if let KeyCode::Q = k.key_code {
                 tui_ctx.quit();
             },
 

+ 7 - 7
docs/guide/examples/hooks_bad.rs

@@ -17,13 +17,13 @@ fn App(cx: Scope) -> Element {
     // But `if` statements only run if the conditional is true!
     // So we might violate rule 2.
     if you_are_happy && you_know_it {
-        let something = use_state(&cx, || "hands");
+        let something = use_state(cx, || "hands");
         println!("clap your {something}")
     }
 
     // ✅ instead, *always* call use_state
     // You can put other stuff in the conditional though
-    let something = use_state(&cx, || "hands");
+    let something = use_state(cx, || "hands");
     if you_are_happy && you_know_it {
         println!("clap your {something}")
     }
@@ -33,12 +33,12 @@ fn App(cx: Scope) -> Element {
     // ❌ don't call hooks inside closures!
     // We can't guarantee that the closure, if used, will be called at the same time every time
     let _a = || {
-        let b = use_state(&cx, || 0);
+        let b = use_state(cx, || 0);
         b.get()
     };
 
     // ✅ instead, move hook `b` outside
-    let b = use_state(&cx, || 0);
+    let b = use_state(cx, || 0);
     let _a = || b.get();
     // ANCHOR_END: closure
 
@@ -50,12 +50,12 @@ fn App(cx: Scope) -> Element {
     // ❌ Do not use hooks in loops!
     // In this case, if the length of the Vec changes, we break rule 2
     for _name in &names {
-        let is_selected = use_state(&cx, || false);
+        let is_selected = use_state(cx, || false);
         println!("selected: {is_selected}");
     }
 
     // ✅ Instead, use a hashmap with use_ref
-    let selection_map = use_ref(&cx, HashMap::<&str, bool>::new);
+    let selection_map = use_ref(cx, HashMap::<&str, bool>::new);
 
     for name in &names {
         let is_selected = selection_map.read()[name];
@@ -63,5 +63,5 @@ fn App(cx: Scope) -> Element {
     }
     // ANCHOR_END: loop
 
-    None
+    cx.render(rsx!(()))
 }

+ 1 - 1
docs/guide/examples/hooks_composed.rs

@@ -8,6 +8,6 @@ struct AppSettings {}
 
 // ANCHOR: wrap_context
 fn use_settings(cx: &ScopeState) -> UseSharedState<AppSettings> {
-    use_context::<AppSettings>(cx).expect("App settings not provided")
+    use_shared_state::<AppSettings>(cx).expect("App settings not provided")
 }
 // ANCHOR_END: wrap_context

+ 1 - 1
docs/guide/examples/hooks_counter.rs

@@ -7,7 +7,7 @@ fn main() {
 
 // ANCHOR: component
 fn App(cx: Scope) -> Element {
-    let mut count = use_state(&cx, || 0);
+    let mut count = use_state(cx, || 0);
 
     cx.render(rsx!(
         h1 { "High-Five counter: {count}" }

+ 2 - 2
docs/guide/examples/hooks_counter_two_state.rs

@@ -8,8 +8,8 @@ fn main() {
 // ANCHOR: component
 fn App(cx: Scope) -> Element {
     // ANCHOR: use_state_calls
-    let mut count_a = use_state(&cx, || 0);
-    let mut count_b = use_state(&cx, || 0);
+    let mut count_a = use_state(cx, || 0);
+    let mut count_b = use_state(cx, || 0);
     // ANCHOR_END: use_state_calls
 
     cx.render(rsx!(

+ 1 - 1
docs/guide/examples/hooks_use_ref.rs

@@ -7,7 +7,7 @@ fn main() {
 
 // ANCHOR: component
 fn App(cx: Scope) -> Element {
-    let list = use_ref(&cx, Vec::new);
+    let list = use_ref(cx, Vec::new);
     let list_formatted = format!("{:?}", *list.read());
 
     cx.render(rsx!(

+ 1 - 1
docs/guide/examples/input_controlled.rs

@@ -7,7 +7,7 @@ fn main() {
 
 // ANCHOR: component
 fn App(cx: Scope) -> Element {
-    let name = use_state(&cx, || "bob".to_string());
+    let name = use_state(cx, || "bob".to_string());
 
     cx.render(rsx! {
         input {

+ 1 - 2
docs/guide/examples/meme_editor.rs

@@ -1,7 +1,6 @@
 // ANCHOR: all
 #![allow(non_snake_case)]
 
-use dioxus::events::FormEvent;
 use dioxus::prelude::*;
 
 fn main() {
@@ -18,7 +17,7 @@ fn MemeEditor(cx: Scope) -> Element {
         width: fit-content;
     ";
 
-    let caption = use_state(&cx, || "me waiting for my rust code to compile".to_string());
+    let caption = use_state(cx, || "me waiting for my rust code to compile".to_string());
 
     cx.render(rsx! {
         div {

+ 7 - 8
docs/guide/examples/meme_editor_dark_mode.rs

@@ -1,7 +1,6 @@
 // ANCHOR: all
 #![allow(non_snake_case)]
 
-use dioxus::events::FormEvent;
 use dioxus::prelude::*;
 
 fn main() {
@@ -14,10 +13,10 @@ struct DarkMode(bool);
 
 pub fn App(cx: Scope) -> Element {
     // ANCHOR: context_provider
-    use_context_provider(&cx, || DarkMode(false));
+    use_shared_state_provider(cx, || DarkMode(false));
     // ANCHOR_END: context_provider
 
-    let is_dark_mode = use_is_dark_mode(&cx);
+    let is_dark_mode = use_is_dark_mode(cx);
 
     let wrapper_style = if is_dark_mode {
         r"
@@ -37,7 +36,7 @@ pub fn App(cx: Scope) -> Element {
 
 pub fn use_is_dark_mode(cx: &ScopeState) -> bool {
     // ANCHOR: use_context
-    let dark_mode_context = use_context::<DarkMode>(cx);
+    let dark_mode_context = use_shared_state::<DarkMode>(cx);
     // ANCHOR_END: use_context
 
     dark_mode_context
@@ -47,7 +46,7 @@ pub fn use_is_dark_mode(cx: &ScopeState) -> bool {
 
 // ANCHOR: toggle
 pub fn DarkModeToggle(cx: Scope) -> Element {
-    let dark_mode = use_context::<DarkMode>(&cx)?;
+    let dark_mode = use_shared_state::<DarkMode>(cx).unwrap();
 
     let style = if dark_mode.read().0 {
         "color:white"
@@ -71,7 +70,7 @@ pub fn DarkModeToggle(cx: Scope) -> Element {
 
 // ANCHOR: meme_editor
 fn MemeEditor(cx: Scope) -> Element {
-    let is_dark_mode = use_is_dark_mode(&cx);
+    let is_dark_mode = use_is_dark_mode(cx);
     let heading_style = if is_dark_mode { "color: white" } else { "" };
 
     let container_style = r"
@@ -82,7 +81,7 @@ fn MemeEditor(cx: Scope) -> Element {
         width: fit-content;
     ";
 
-    let caption = use_state(&cx, || "me waiting for my rust code to compile".to_string());
+    let caption = use_state(cx, || "me waiting for my rust code to compile".to_string());
 
     cx.render(rsx! {
         div {
@@ -152,7 +151,7 @@ fn CaptionEditor<'a>(
     caption: &'a str,
     on_input: EventHandler<'a, FormEvent>,
 ) -> Element<'a> {
-    let is_dark_mode = use_is_dark_mode(&cx);
+    let is_dark_mode = use_is_dark_mode(cx);
 
     let colors = if is_dark_mode {
         r"

+ 3 - 3
docs/guide/examples/rendering_lists.rs

@@ -14,9 +14,9 @@ struct Comment {
 
 pub fn App(cx: Scope) -> Element {
     // ANCHOR: render_list
-    let comment_field = use_state(&cx, String::new);
-    let mut next_id = use_state(&cx, || 0);
-    let comments = use_ref(&cx, Vec::<Comment>::new);
+    let comment_field = use_state(cx, String::new);
+    let mut next_id = use_state(cx, || 0);
+    let comments = use_ref(cx, Vec::<Comment>::new);
 
     let comments_lock = comments.read();
     let comments_rendered = comments_lock.iter().map(|comment| {

+ 1 - 1
docs/guide/examples/rsx_overview.rs

@@ -103,7 +103,7 @@ pub fn Expression(cx: Scope) -> Element {
     // ANCHOR: expression
     let text = "Dioxus";
     cx.render(rsx!(span {
-        [text.to_uppercase()]
+        text.to_uppercase()
     }))
     // ANCHOR_END: expression
 }

+ 8 - 8
docs/guide/examples/spawn.rs

@@ -8,7 +8,7 @@ fn main() {
 
 fn App(cx: Scope) -> Element {
     // ANCHOR: spawn
-    let logged_in = use_state(&cx, || false);
+    let logged_in = use_state(cx, || false);
 
     let log_in = move |_| {
         cx.spawn({
@@ -58,18 +58,18 @@ pub fn Tokio(cx: Scope) -> Element {
         // ANCHOR_END: tokio
     };
 
-    None
+    cx.render(rsx!(()))
 }
 
 pub fn ToOwnedMacro(cx: Scope) -> Element {
-    let count = use_state(&cx, || 0);
-    let age = use_state(&cx, || 0);
-    let name = use_state(&cx, || 0);
-    let description = use_state(&cx, || 0);
+    let count = use_state(cx, || 0);
+    let age = use_state(cx, || 0);
+    let name = use_state(cx, || 0);
+    let description = use_state(cx, || 0);
 
     let _ = || {
         // ANCHOR: to_owned_macro
-        use dioxus::core::to_owned;
+        use dioxus::hooks::to_owned;
 
         cx.spawn({
             to_owned![count, age, name, description];
@@ -80,5 +80,5 @@ pub fn ToOwnedMacro(cx: Scope) -> Element {
         // ANCHOR_END: to_owned_macro
     };
 
-    None
+    cx.render(rsx!(()))
 }

+ 3 - 3
docs/guide/examples/use_future.rs

@@ -14,7 +14,7 @@ struct ApiResponse {
 
 fn App(cx: Scope) -> Element {
     // ANCHOR: use_future
-    let future = use_future(&cx, (), |_| async move {
+    let future = use_future(cx, (), |_| async move {
         reqwest::get("https://dog.ceo/api/breeds/image/random")
             .await
             .unwrap()
@@ -47,7 +47,7 @@ fn App(cx: Scope) -> Element {
 #[inline_props]
 fn RandomDog(cx: Scope, breed: String) -> Element {
     // ANCHOR: dependency
-    let future = use_future(&cx, (breed,), |(breed,)| async move {
+    let future = use_future(cx, (breed,), |(breed,)| async move {
         reqwest::get(format!("https://dog.ceo/api/breed/{breed}/images/random"))
             .await
             .unwrap()
@@ -56,5 +56,5 @@ fn RandomDog(cx: Scope, breed: String) -> Element {
     });
     // ANCHOR_END: dependency
 
-    None
+    cx.render(rsx!(()))
 }

+ 11 - 11
docs/guide/src/en/__unused/advanced-guides/12-signals.md

@@ -12,7 +12,7 @@ Your component today might look something like this:
 
 ```rust
 fn Comp(cx: Scope) -> DomTree {
-    let (title, set_title) = use_state(&cx, || "Title".to_string());
+    let (title, set_title) = use_state(cx, || "Title".to_string());
     cx.render(rsx!{
         input {
             value: title,
@@ -26,7 +26,7 @@ This component is fairly straightforward – the input updates its own value on
 
 ```rust
 fn Comp(cx: Scope) -> DomTree {
-    let (title, set_title) = use_state(&cx, || "Title".to_string());
+    let (title, set_title) = use_state(cx, || "Title".to_string());
     cx.render(rsx!{
         div {
             input {
@@ -49,7 +49,7 @@ We can use signals to generate a two-way binding between data and the input box.
 
 ```rust
 fn Comp(cx: Scope) -> DomTree {
-    let mut title = use_signal(&cx, || String::from("Title"));
+    let mut title = use_signal(cx, || String::from("Title"));
     cx.render(rsx!(input { value: title }))
 }
 ```
@@ -58,8 +58,8 @@ For a slightly more interesting example, this component calculates the sum betwe
 
 ```rust
 fn Calculator(cx: Scope) -> DomTree {
-    let mut a = use_signal(&cx, || 0);
-    let mut b = use_signal(&cx, || 0);
+    let mut a = use_signal(cx, || 0);
+    let mut b = use_signal(cx, || 0);
     let mut c = a + b;
     rsx! {
         input { value: a }
@@ -72,8 +72,8 @@ fn Calculator(cx: Scope) -> DomTree {
 Do you notice how we can use built-in operations on signals? Under the hood, we actually create a new derived signal that depends on `a` and `b`. Whenever `a` or `b` update, then `c` will update. If we need to create a new derived signal that's more complex than a basic operation (`std::ops`) we can either chain signals together or combine them:
 
 ```rust
-let mut a = use_signal(&cx, || 0);
-let mut b = use_signal(&cx, || 0);
+let mut a = use_signal(cx, || 0);
+let mut b = use_signal(cx, || 0);
 
 // Chain signals together using the `with` method
 let c = a.with(b).map(|(a, b)| *a + *b);
@@ -84,7 +84,7 @@ let c = a.with(b).map(|(a, b)| *a + *b);
 If we ever need to get the value out of a signal, we can simply `deref` it.
 
 ```rust
-let mut a = use_signal(&cx, || 0);
+let mut a = use_signal(cx, || 0);
 let c = *a + *b;
 ```
 
@@ -97,7 +97,7 @@ Sometimes you want a signal to propagate across your app, either through far-awa
 ```rust
 const TITLE: Atom<String> = || "".to_string();
 const Provider: Component = |cx|{
-    let title = use_signal(&cx, &TITLE);
+    let title = use_signal(cx, &TITLE);
     render!(input { value: title })
 };
 ```
@@ -106,7 +106,7 @@ If we use the `TITLE` atom in another component, we can cause updates to flow be
 
 ```rust
 const Receiver: Component = |cx|{
-    let title = use_signal(&cx, &TITLE);
+    let title = use_signal(cx, &TITLE);
     log::info!("This will only be called once!");
     rsx!(cx,
         div {
@@ -133,7 +133,7 @@ Dioxus automatically understands how to use your signals when mixed with iterato
 ```rust
 const DICT: AtomFamily<String, String> = |_| {};
 const List: Component = |cx|{
-    let dict = use_signal(&cx, &DICT);
+    let dict = use_signal(cx, &DICT);
     cx.render(rsx!(
         ul {
             For { each: dict, map: |k, v| rsx!( li { "{v}" }) }

+ 1 - 1
docs/guide/src/en/__unused/composing.md

@@ -59,7 +59,7 @@ If we represented the reactive graph presented above in Dioxus, it would look ve
 // Declare a component that holds our datasources and calculates `g`
 fn RenderGraph(cx: Scope) -> Element {
     let seconds = use_datasource(SECONDS);
-    let constant = use_state(&cx, || 1);
+    let constant = use_state(cx, || 1);
 
     cx.render(rsx!(
         RenderG { seconds: seconds }

+ 6 - 6
docs/guide/src/en/__unused/fanout.md

@@ -28,8 +28,8 @@ If we used global state like use_context or fermi, we might be tempted to inject
 
 ```rust
 fn Titlebar(cx: Scope<TitlebarProps>) -> Element {
-    let title = use_read(&cx, TITLE);
-    let subtitle = use_read(&cx, SUBTITLE);
+    let title = use_read(cx, TITLE);
+    let subtitle = use_read(cx, SUBTITLE);
 
     cx.render(rsx!{/* ui */})
 }
@@ -43,11 +43,11 @@ To enable our titlebar component to be used across apps, we want to lift our ato
 
 ```rust
 fn DocsiteTitlesection(cx: Scope) {
-    let title = use_read(&cx, TITLE);
-    let subtitle = use_read(&cx, SUBTITLE);
+    let title = use_read(cx, TITLE);
+    let subtitle = use_read(cx, SUBTITLE);
 
-    let username = use_read(&cx, USERNAME);
-    let points = use_read(&cx, POINTS);
+    let username = use_read(cx, USERNAME);
+    let points = use_read(cx, POINTS);
 
     cx.render(rsx!{
         TitleBar { title: title, subtitle: subtitle }

+ 7 - 7
docs/guide/src/en/__unused/localstate.md

@@ -13,7 +13,7 @@ struct Todo {
     is_editing: bool,
 }
 
-let todos = use_ref(&cx, || vec![Todo::new()]);
+let todos = use_ref(cx, || vec![Todo::new()]);
 
 cx.render(rsx!{
     ul {
@@ -40,7 +40,7 @@ Instead, let's refactor our Todo component to handle its own state:
 ```rust
 #[inline_props]
 fn Todo<'a>(cx: Scope, todo: &'a Todo) -> Element {
-    let is_hovered = use_state(&cx, || false);
+    let is_hovered = use_state(cx, || false);
 
     cx.render(rsx!{
         li {
@@ -80,15 +80,15 @@ struct State {
 }
 
 // in the component
-let state = use_ref(&cx, State::new)
+let state = use_ref(cx, State::new)
 ```
 
 The "better" approach for this particular component would be to break the state apart into different values:
 
 ```rust
-let count = use_state(&cx, || 0);
-let color = use_state(&cx, || "red");
-let names = use_state(&cx, HashMap::new);
+let count = use_state(cx, || 0);
+let color = use_state(cx, || "red");
+let names = use_state(cx, HashMap::new);
 ```
 
 You might recognize that our "names" value is a HashMap – which is not terribly cheap to clone every time we update its value. To solve this issue, we *highly* suggest using a library like [`im`](https://crates.io/crates/im) which will take advantage of structural sharing to make clones and mutations much cheaper.
@@ -96,7 +96,7 @@ You might recognize that our "names" value is a HashMap – which is not terribl
 When combined with the `make_mut` method on `use_state`, you can get really succinct updates to collections:
 
 ```rust
-let todos = use_state(&cx, im_rc::HashMap::default);
+let todos = use_state(cx, im_rc::HashMap::default);
 
 todos.make_mut().insert("new todo", Todo {
     contents: "go get groceries",

+ 2 - 2
docs/guide/src/en/__unused/memoization.md

@@ -8,8 +8,8 @@ For example, let's say we have a component that has two children:
 ```rust
 fn Demo(cx: Scope) -> Element {
     // don't worry about these 2, we'll cover them later
-    let name = use_state(&cx, || String::from("bob"));
-    let age = use_state(&cx, || 21);
+    let name = use_state(cx, || String::from("bob"));
+    let age = use_state(cx, || 21);
 
     cx.render(rsx!{
         Name { name: name }

+ 1 - 1
docs/guide/src/en/__unused/model_pattern.md

@@ -20,7 +20,7 @@ Our component is really simple – we just call `use_ref` to get an initial calc
 
 ```rust
 fn app(cx: Scope) -> Element {
-    let state = use_ref(&cx, Calculator::new);
+    let state = use_ref(cx, Calculator::new);
 
     cx.render(rsx!{
         // the rendering code

+ 12 - 12
docs/guide/src/en/async/use_coroutine.md

@@ -10,7 +10,7 @@ The basic setup for coroutines is the `use_coroutine` hook. Most coroutines we w
 
 ```rust
 fn app(cx: Scope) -> Element {
-    let ws: &UseCoroutine<()> = use_coroutine(&cx, |rx| async move {
+    let ws: &UseCoroutine<()> = use_coroutine(cx, |rx| async move {
         // Connect to some sort of service
         let mut conn = connect_to_ws_server().await;
 
@@ -27,7 +27,7 @@ For many services, a simple async loop will handle the majority of use cases.
 However, if we want to temporarily disable the coroutine, we can "pause" it using the `pause` method, and "resume" it using the `resume` method:
 
 ```rust
-let sync: &UseCoroutine<()> = use_coroutine(&cx, |rx| async move {
+let sync: &UseCoroutine<()> = use_coroutine(cx, |rx| async move {
     // code for syncing
 });
 
@@ -63,7 +63,7 @@ enum ProfileUpdate {
     SetAge(i32)
 }
 
-let profile = use_coroutine(&cx, |mut rx: UnboundedReciver<ProfileUpdate>| async move {
+let profile = use_coroutine(cx, |mut rx: UnboundedReciver<ProfileUpdate>| async move {
     let mut server = connect_to_server().await;
 
     while let Ok(msg) = rx.next().await {
@@ -86,9 +86,9 @@ cx.render(rsx!{
 For sufficiently complex apps, we could build a bunch of different useful "services" that loop on channels to update the app.
 
 ```rust
-let profile = use_coroutine(&cx, profile_service);
-let editor = use_coroutine(&cx, editor_service);
-let sync = use_coroutine(&cx, sync_service);
+let profile = use_coroutine(cx, profile_service);
+let editor = use_coroutine(cx, editor_service);
+let sync = use_coroutine(cx, sync_service);
 
 async fn profile_service(rx: UnboundedReceiver<ProfileCommand>) {
     // do stuff
@@ -109,9 +109,9 @@ We can combine coroutines with Fermi to emulate Redux Toolkit's Thunk system wit
 static USERNAME: Atom<String> = |_| "default".to_string();
 
 fn app(cx: Scope) -> Element {
-    let atoms = use_atom_root(&cx);
+    let atoms = use_atom_root(cx);
 
-    use_coroutine(&cx, |rx| sync_service(rx, atoms.clone()));
+    use_coroutine(cx, |rx| sync_service(rx, atoms.clone()));
 
     cx.render(rsx!{
         Banner {}
@@ -119,7 +119,7 @@ fn app(cx: Scope) -> Element {
 }
 
 fn Banner(cx: Scope) -> Element {
-    let username = use_read(&cx, USERNAME);
+    let username = use_read(cx, USERNAME);
 
     cx.render(rsx!{
         h1 { "Welcome back, {username}" }
@@ -158,8 +158,8 @@ To yield values from a coroutine, simply bring in a `UseState` handle and set th
 
 
 ```rust
-let sync_status = use_state(&cx, || Status::Launching);
-let sync_task = use_coroutine(&cx, |rx: UnboundedReceiver<SyncAction>| {
+let sync_status = use_state(cx, || Status::Launching);
+let sync_task = use_coroutine(cx, |rx: UnboundedReceiver<SyncAction>| {
     to_owned![sync_status];
     async move {
         loop {
@@ -176,7 +176,7 @@ Coroutine handles are automatically injected through the context API. `use_corou
 
 ```rust
 fn Child(cx: Scope) -> Element {
-    let sync_task = use_coroutine_handle::<SyncAction>(&cx);
+    let sync_task = use_coroutine_handle::<SyncAction>(cx);
 
     sync_task.send(SyncAction::SetUsername);
 }

+ 5 - 5
docs/guide/src/en/best_practices/error_handling.md

@@ -56,7 +56,7 @@ The next "best" way of handling errors in Dioxus is to match on the error locall
 To do this, we simply have an error state built into our component:
 
 ```rust
-let err = use_state(&cx, || None);
+let err = use_state(cx, || None);
 ```
 
 Whenever we perform an action that generates an error, we'll set that error state. We can then match on the error in a number of ways (early return, return Element, etc).
@@ -64,7 +64,7 @@ Whenever we perform an action that generates an error, we'll set that error stat
 
 ```rust
 fn Commandline(cx: Scope) -> Element {
-    let error = use_state(&cx, || None);
+    let error = use_state(cx, || None);
 
     cx.render(match *error {
         Some(error) => rsx!(
@@ -85,7 +85,7 @@ If you're dealing with a handful of components with minimal nesting, you can jus
 
 ```rust
 fn Commandline(cx: Scope) -> Element {
-    let error = use_state(&cx, || None);
+    let error = use_state(cx, || None);
 
     if let Some(error) = **error {
         return cx.render(rsx!{ "An error occured" });
@@ -125,7 +125,7 @@ Then, in our top level component, we want to explicitly handle the possible erro
 
 ```rust
 fn TopLevel(cx: Scope) -> Element {
-    let error = use_read(&cx, INPUT_ERROR);
+    let error = use_read(cx, INPUT_ERROR);
 
     match error {
         TooLong => return cx.render(rsx!{ "FAILED: Too long!" }),
@@ -139,7 +139,7 @@ Now, whenever a downstream component has an error in its actions, it can simply
 
 ```rust
 fn Commandline(cx: Scope) -> Element {
-    let set_error = use_set(&cx, INPUT_ERROR);
+    let set_error = use_set(cx, INPUT_ERROR);
 
     cx.render(rsx!{
         input {

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

@@ -6,7 +6,7 @@ Dioxus is a portable, performant, and ergonomic framework for building cross-pla
 
 ```rust
 fn app(cx: Scope) -> Element {
-    let mut count = use_state(&cx, || 0);
+    let mut count = use_state(cx, || 0);
 
     cx.render(rsx!(
         h1 { "High-Five counter: {count}" }
@@ -48,4 +48,4 @@ Web: Since the web is a fairly mature platform, we expect there to be very littl
 
 Desktop: APIs will likely be in flux as we figure out better patterns than our ElectronJS counterpart.
 
-SSR: We don't expect the SSR API to change drastically in the future.
+SSR: We don't expect the SSR API to change drastically in the future.

+ 12 - 12
docs/guide/src/pt-br/async/use_coroutine.md

@@ -10,7 +10,7 @@ A configuração básica para corrotinas é o _hook_ `use_coroutine`. A maioria
 
 ```rust
 fn app(cx: Scope) -> Element {
-    let ws: &UseCoroutine<()> = use_coroutine(&cx, |rx| async move {
+    let ws: &UseCoroutine<()> = use_coroutine(cx, |rx| async move {
         // Connect to some sort of service
         let mut conn = connect_to_ws_server().await;
 
@@ -27,7 +27,7 @@ Para muitos serviços, um _loop_ assíncrono simples lidará com a maioria dos c
 No entanto, se quisermos desabilitar temporariamente a corrotina, podemos "pausá-la" usando o método `pause` e "retomá-la" usando o método `resume`:
 
 ```rust
-let sync: &UseCoroutine<()> = use_coroutine(&cx, |rx| async move {
+let sync: &UseCoroutine<()> = use_coroutine(cx, |rx| async move {
     // code for syncing
 });
 
@@ -62,7 +62,7 @@ enum ProfileUpdate {
     SetAge(i32)
 }
 
-let profile = use_coroutine(&cx, |mut rx: UnboundedReciver<ProfileUpdate>| async move {
+let profile = use_coroutine(cx, |mut rx: UnboundedReciver<ProfileUpdate>| async move {
     let mut server = connect_to_server().await;
 
     while let Ok(msg) = rx.next().await {
@@ -85,9 +85,9 @@ cx.render(rsx!{
 Para aplicativos suficientemente complexos, poderíamos criar vários "serviços" úteis diferentes que fazem um _loop_ nos canais para atualizar o aplicativo.
 
 ```rust
-let profile = use_coroutine(&cx, profile_service);
-let editor = use_coroutine(&cx, editor_service);
-let sync = use_coroutine(&cx, sync_service);
+let profile = use_coroutine(cx, profile_service);
+let editor = use_coroutine(cx, editor_service);
+let sync = use_coroutine(cx, sync_service);
 
 async fn profile_service(rx: UnboundedReceiver<ProfileCommand>) {
     // do stuff
@@ -108,9 +108,9 @@ Podemos combinar corrotinas com `Fermi` para emular o sistema `Thunk` do **Redux
 static USERNAME: Atom<String> = |_| "default".to_string();
 
 fn app(cx: Scope) -> Element {
-    let atoms = use_atom_root(&cx);
+    let atoms = use_atom_root(cx);
 
-    use_coroutine(&cx, |rx| sync_service(rx, atoms.clone()));
+    use_coroutine(cx, |rx| sync_service(rx, atoms.clone()));
 
     cx.render(rsx!{
         Banner {}
@@ -118,7 +118,7 @@ fn app(cx: Scope) -> Element {
 }
 
 fn Banner(cx: Scope) -> Element {
-    let username = use_read(&cx, USERNAME);
+    let username = use_read(cx, USERNAME);
 
     cx.render(rsx!{
         h1 { "Welcome back, {username}" }
@@ -156,8 +156,8 @@ async fn sync_service(mut rx: UnboundedReceiver<SyncAction>, atoms: AtomRoot) {
 Para obter valores de uma corrotina, basta usar um identificador `UseState` e definir o valor sempre que sua corrotina concluir seu trabalho.
 
 ```rust
-let sync_status = use_state(&cx, || Status::Launching);
-let sync_task = use_coroutine(&cx, |rx: UnboundedReceiver<SyncAction>| {
+let sync_status = use_state(cx, || Status::Launching);
+let sync_task = use_coroutine(cx, |rx: UnboundedReceiver<SyncAction>| {
     to_owned![sync_status];
     async move {
         loop {
@@ -174,7 +174,7 @@ Os identificadores de corrotina são injetados automaticamente por meio da API d
 
 ```rust
 fn Child(cx: Scope) -> Element {
-    let sync_task = use_coroutine_handle::<SyncAction>(&cx);
+    let sync_task = use_coroutine_handle::<SyncAction>(cx);
 
     sync_task.send(SyncAction::SetUsername);
 }

+ 5 - 5
docs/guide/src/pt-br/best_practices/error_handling.md

@@ -53,14 +53,14 @@ A próxima "melhor" maneira de lidar com erros no Dioxus é combinar (`match`) o
 Para fazer isso, simplesmente temos um estado de erro embutido em nosso componente:
 
 ```rust
-let err = use_state(&cx, || None);
+let err = use_state(cx, || None);
 ```
 
 Sempre que realizarmos uma ação que gere um erro, definiremos esse estado de erro. Podemos então combinar o erro de várias maneiras (retorno antecipado, elemento de retorno etc.).
 
 ```rust
 fn Commandline(cx: Scope) -> Element {
-    let error = use_state(&cx, || None);
+    let error = use_state(cx, || None);
 
     cx.render(match *error {
         Some(error) => rsx!(
@@ -81,7 +81,7 @@ Se você estiver lidando com alguns componentes com um mínimo de aninhamento, b
 
 ```rust
 fn Commandline(cx: Scope) -> Element {
-    let error = use_state(&cx, || None);
+    let error = use_state(cx, || None);
 
     if let Some(error) = **error {
         return cx.render(rsx!{ "An error occured" });
@@ -120,7 +120,7 @@ Então, em nosso componente de nível superior, queremos tratar explicitamente o
 
 ```rust
 fn TopLevel(cx: Scope) -> Element {
-    let error = use_read(&cx, INPUT_ERROR);
+    let error = use_read(cx, INPUT_ERROR);
 
     match error {
         TooLong => return cx.render(rsx!{ "FAILED: Too long!" }),
@@ -134,7 +134,7 @@ Agora, sempre que um componente _downstream_ tiver um erro em suas ações, ele
 
 ```rust
 fn Commandline(cx: Scope) -> Element {
-    let set_error = use_set(&cx, INPUT_ERROR);
+    let set_error = use_set(cx, INPUT_ERROR);
 
     cx.render(rsx!{
         input {

+ 1 - 1
docs/guide/src/pt-br/index.md

@@ -6,7 +6,7 @@ Dioxus é uma estrutura portátil, de alto desempenho e ergonômica para a const
 
 ```rust
 fn app(cx: Scope) -> Element {
-    let mut count = use_state(&cx, || 0);
+    let mut count = use_state(cx, || 0);
 
     cx.render(rsx!(
         h1 { "High-Five counter: {count}" }

+ 7 - 7
docs/posts/release-0-2-0.md

@@ -27,7 +27,7 @@ Dioxus is a recently-released library for building interactive user interfaces (
 
 ```rust
 fn app(cx: Scope) -> Element {
-    let mut count = use_state(&cx, || 0);
+    let mut count = use_state(cx, || 0);
 
     cx.render(rsx! {
         h1 { "Count: {count}" }
@@ -102,8 +102,8 @@ We're also using hooks to parse the URL parameters and segments so you can inter
 struct Query { name: String }
 
 fn BlogPost(cx: Scope) -> Element {
-    let post = use_route(&cx).segment("post")?;
-    let query = use_route(&cx).query::<Query>()?;
+    let post = use_route(cx).segment("post")?;
+    let query = use_route(cx).query::<Query>()?;
 
     cx.render(rsx!{
         "Viewing post {post}"
@@ -128,7 +128,7 @@ static TITLE: Atom<&str> = |_| "Hello";
 
 // Read the value from anywhere in the app, subscribing to any changes
 fn app(cx: Scope) -> Element {
-    let title = use_read(&cx, TITLE);
+    let title = use_read(cx, TITLE);
     cx.render(rsx!{
         h1 { "{title}" }
         Child {}
@@ -137,7 +137,7 @@ fn app(cx: Scope) -> Element {
 
 // Set the value from anywhere in the app
 fn Child(cx: Scope) -> Element {
-    let set_title = use_set(&cx, TITLE);
+    let set_title = use_set(cx, TITLE);
     cx.render(rsx!{
         button {
             onclick: move |_| set_title("goodbye"),
@@ -245,7 +245,7 @@ First, we upgraded the `use_future` hook. It now supports dependencies, which le
 
 ```rust
 fn RenderDog(cx: Scope, breed: String) -> Element {
-    let dog_request = use_future(&cx, (breed,), |(breed,)| async move {
+    let dog_request = use_future(cx, (breed,), |(breed,)| async move {
         reqwest::get(format!("https://dog.ceo/api/breed/{}/images/random", breed))
             .await
             .unwrap()
@@ -265,7 +265,7 @@ Additionally, we added better support for coroutines. You can now start, stop, r
 
 ```rust
 fn App(cx: Scope) -> Element {
-    let sync_task = use_coroutine(&cx, |rx| async move {
+    let sync_task = use_coroutine(cx, |rx| async move {
         connect_to_server().await;
         let state = MyState::new();
 

+ 5 - 5
docs/posts/release.md

@@ -28,7 +28,7 @@ fn main() {
 }
 
 fn app(cx: Scope) -> Element {
-    let mut count = use_state(&cx, || 0);
+    let mut count = use_state(cx, || 0);
 
     cx.render(rsx! {
         h1 { "Count: {count}" }
@@ -138,7 +138,7 @@ struct CardProps {
 }
 
 static Card: Component<CardProps> = |cx| {
-	let mut count = use_state(&cx, || 0);
+	let mut count = use_state(cx, || 0);
 	cx.render(rsx!(
 		aside {
 			h2 { "{cx.props.title}" }
@@ -191,7 +191,7 @@ fn main() {
 }
 
 fn app(cx: Scope) -> Element {
-    let mut count = use_state(&cx, || 0);
+    let mut count = use_state(cx, || 0);
 
     cx.render(rsx! {
         h1 { "Count: {count}" }
@@ -260,7 +260,7 @@ Dioxus understands the lifetimes of data borrowed from `Scope`, so you can safel
 
 
 ```rust
-let name = use_state(&cx, || "asd");
+let name = use_state(cx, || "asd");
 rsx! {
 	div {
 		button { onclick: move |_| name.set("abc") }
@@ -274,7 +274,7 @@ Because we know the lifetime of your handlers, we can also expose this to childr
 
 ```rust
 fn app(cx: Scope) -> Element {
-	let name = use_state(&cx, || "asd");
+	let name = use_state(cx, || "asd");
 	cx.render(rsx!{
 		Button { name: name }
 	})

+ 11 - 11
docs/router/src/guide/building-a-nest.md

@@ -10,12 +10,12 @@ Let's create a new ``navbar`` component:
 fn navbar(cx: Scope) -> Element {
     cx.render(rsx! {
         ul {
-            
+
         }
     })
 }
 ```
-Our navbar will be a list of links going between our pages. We could always use an HTML anchor element but that would cause our page to unnecessarily reload. Instead we want to use the ``Link`` component provided by Dioxus Router. 
+Our navbar will be a list of links going between our pages. We could always use an HTML anchor element but that would cause our page to unnecessarily reload. Instead we want to use the ``Link`` component provided by Dioxus Router.
 
 The Link component is very similar to the Route component. It takes a path and an element. Add the Link component into your use statement and then add some links:
 ```rs
@@ -38,7 +38,7 @@ fn navbar(cx: Scope) -> Element {
 }
 ```
 >By default, the Link component only works for links within your application. To link to external sites, add the ``external: true`` property.
->```rs 
+>```rs
 >Link { to: "https://github.com", external: true, "GitHub"}
 >```
 
@@ -66,7 +66,7 @@ We want to store our blogs in a database and load them as needed. This'll help p
 We could utilize a search page that loads a blog when clicked but then our users won't be able to share our blogs easily. This is where URL parameters come in. And finally, we also want our site to tell users they are on a blog page whenever the URL starts with``/blog``.
 
 The path to our blog will look like ``/blog/myBlogPage``. ``myBlogPage`` being the URL parameter.
-Dioxus Router uses the ``:name`` pattern so our route will look like ``/blog/:post``.  
+Dioxus Router uses the ``:name`` pattern so our route will look like ``/blog/:post``.
 
 First, lets tell users when they are on a blog page. Add a new route in your app component.
 ```rs
@@ -77,7 +77,7 @@ fn app(cx: Scope) -> Element {
             self::navbar {}
             Route { to: "/", self::homepage {}}
             // NEW
-            Route { 
+            Route {
                 to: "/blog",
             }
             Route { to: "", self::page_not_found {}}
@@ -93,7 +93,7 @@ fn app(cx: Scope) -> Element {
             p { "-- Dioxus Blog --" }
             self::navbar {}
             Route { to: "/", self::homepage {}}
-            Route { 
+            Route {
                 to: "/blog",
                 Route { to: "/:post", "This is my blog post!" } // NEW
             }
@@ -109,7 +109,7 @@ fn app(cx: Scope) -> Element {
         Router {
             self::navbar {}
             Route { to: "/", self::homepage {}}
-            Route { 
+            Route {
                 to: "/blog",
                 p { "-- Dioxus Blog --" } // MOVED
                 Route { to: "/:post", "This is my blog post!" }
@@ -119,7 +119,7 @@ fn app(cx: Scope) -> Element {
     })
 }
 ```
-Now our ``-- Dioxus Blog --`` text will be displayed whenever a user is on a path that starts with ``/blog``. Displaying content in a way that is page-agnostic is useful when building navigation menus, footers, and similar. 
+Now our ``-- Dioxus Blog --`` text will be displayed whenever a user is on a path that starts with ``/blog``. Displaying content in a way that is page-agnostic is useful when building navigation menus, footers, and similar.
 
 All that's left is to handle our URL parameter. We will begin by creating a ``get_blog_post`` function. In a real site, this function would call an API endpoint to get a blog post from the database. However, that is out of the scope of this guide so we will be utilizing static text.
 ```rs
@@ -153,7 +153,7 @@ use dioxus::{
 ...
 
 fn blog_post(cx: Scope) -> Element {
-    let route = use_route(&cx); // NEW
+    let route = use_route(cx); // NEW
     let blog_text = "";
 
     cx.render(rsx! {
@@ -165,7 +165,7 @@ Dioxus Router provides built in methods to extract information from a route. We
 The ``segment`` method also parses the parameter into any type for us. We'll use a match expression that handles a parsing error and on success, uses our helper function to grab the blog post.
 ```rs
 fn blog_post(cx: Scope) -> Element {
-    let route = use_route(&cx);
+    let route = use_route(cx);
 
     // NEW
     let blog_text = match route.segment::<String>("post").unwrap() {
@@ -198,4 +198,4 @@ fn app(cx: Scope) -> Element {
 That's it! If you head to ``/blog/foo`` you should see ``Welcome to the foo blog post!``.
 
 ### Conclusion
-In this chapter we utilized Dioxus Router's Link, URL Parameter, and ``use_route`` functionality to build the blog portion of our application. In the next and final chapter, we will go over the ``Redirect`` component to redirect non-authorized users to another page.
+In this chapter we utilized Dioxus Router's Link, URL Parameter, and ``use_route`` functionality to build the blog portion of our application. In the next and final chapter, we will go over the ``Redirect`` component to redirect non-authorized users to another page.

+ 401 - 399
examples/all_css.rs

@@ -7,405 +7,407 @@ fn main() {
 fn app(cx: Scope) -> Element {
     cx.render(rsx! {
         div {
-          align_content: "a",
-          align_items: "a",
-          align_self: "a",
-          alignment_adjust: "a",
-          alignment_baseline: "a",
-          all: "a",
-          alt: "a",
-          animation: "a",
-          animation_delay: "a",
-          animation_direction: "a",
-          animation_duration: "a",
-          animation_fill_mode: "a",
-          animation_iteration_count: "a",
-          animation_name: "a",
-          animation_play_state: "a",
-          animation_timing_function: "a",
-          azimuth: "a",
-          backface_visibility: "a",
-          background: "a",
-          background_attachment: "a",
-          background_clip: "a",
-          background_color: "a",
-          background_image: "a",
-          background_origin: "a",
-          background_position: "a",
-          background_repeat: "a",
-          background_size: "a",
-          background_blend_mode: "a",
-          baseline_shift: "a",
-          bleed: "a",
-          bookmark_label: "a",
-          bookmark_level: "a",
-          bookmark_state: "a",
-          border: "a",
-          border_color: "a",
-          border_style: "a",
-          border_width: "a",
-          border_bottom: "a",
-          border_bottom_color: "a",
-          border_bottom_style: "a",
-          border_bottom_width: "a",
-          border_left: "a",
-          border_left_color: "a",
-          border_left_style: "a",
-          border_left_width: "a",
-          border_right: "a",
-          border_right_color: "a",
-          border_right_style: "a",
-          border_right_width: "a",
-          border_top: "a",
-          border_top_color: "a",
-          border_top_style: "a",
-          border_top_width: "a",
-          border_collapse: "a",
-          border_image: "a",
-          border_image_outset: "a",
-          border_image_repeat: "a",
-          border_image_slice: "a",
-          border_image_source: "a",
-          border_image_width: "a",
-          border_radius: "a",
-          border_bottom_left_radius: "a",
-          border_bottom_right_radius: "a",
-          border_top_left_radius: "a",
-          border_top_right_radius: "a",
-          border_spacing: "a",
-          bottom: "a",
-          box_decoration_break: "a",
-          box_shadow: "a",
-          box_sizing: "a",
-          box_snap: "a",
-          break_after: "a",
-          break_before: "a",
-          break_inside: "a",
-          buffered_rendering: "a",
-          caption_side: "a",
-          clear: "a",
-          clear_side: "a",
-          clip: "a",
-          clip_path: "a",
-          clip_rule: "a",
-          color: "a",
-          color_adjust: "a",
-          color_correction: "a",
-          color_interpolation: "a",
-          color_interpolation_filters: "a",
-          color_profile: "a",
-          color_rendering: "a",
-          column_fill: "a",
-          column_gap: "a",
-          column_rule: "a",
-          column_rule_color: "a",
-          column_rule_style: "a",
-          column_rule_width: "a",
-          column_span: "a",
-          columns: "a",
-          column_count: "a",
-          column_width: "a",
-          contain: "a",
-          content: "a",
-          counter_increment: "a",
-          counter_reset: "a",
-          counter_set: "a",
-          cue: "a",
-          cue_after: "a",
-          cue_before: "a",
-          cursor: "a",
-          direction: "a",
-          display: "a",
-          display_inside: "a",
-          display_outside: "a",
-          display_extras: "a",
-          display_box: "a",
-          dominant_baseline: "a",
-          elevation: "a",
-          empty_cells: "a",
-          enable_background: "a",
-          fill: "a",
-          fill_opacity: "a",
-          fill_rule: "a",
-          filter: "a",
-          float: "a",
-          float_defer_column: "a",
-          float_defer_page: "a",
-          float_offset: "a",
-          float_wrap: "a",
-          flow_into: "a",
-          flow_from: "a",
-          flex: "a",
-          flex_basis: "a",
-          flex_grow: "a",
-          flex_shrink: "a",
-          flex_flow: "a",
-          flex_direction: "a",
-          flex_wrap: "a",
-          flood_color: "a",
-          flood_opacity: "a",
-          font: "a",
-          font_family: "a",
-          font_size: "a",
-          font_stretch: "a",
-          font_style: "a",
-          font_weight: "a",
-          font_feature_settings: "a",
-          font_kerning: "a",
-          font_language_override: "a",
-          font_size_adjust: "a",
-          font_synthesis: "a",
-          font_variant: "a",
-          font_variant_alternates: "a",
-          font_variant_caps: "a",
-          font_variant_east_asian: "a",
-          font_variant_ligatures: "a",
-          font_variant_numeric: "a",
-          font_variant_position: "a",
-          footnote_policy: "a",
-          glyph_orientation_horizontal: "a",
-          glyph_orientation_vertical: "a",
-          grid: "a",
-          grid_auto_flow: "a",
-          grid_auto_columns: "a",
-          grid_auto_rows: "a",
-          grid_template: "a",
-          grid_template_areas: "a",
-          grid_template_columns: "a",
-          grid_template_rows: "a",
-          grid_area: "a",
-          grid_column: "a",
-          grid_column_start: "a",
-          grid_column_end: "a",
-          grid_row: "a",
-          grid_row_start: "a",
-          grid_row_end: "a",
-          hanging_punctuation: "a",
-          height: "a",
-          hyphenate_character: "a",
-          hyphenate_limit_chars: "a",
-          hyphenate_limit_last: "a",
-          hyphenate_limit_lines: "a",
-          hyphenate_limit_zone: "a",
-          hyphens: "a",
-          icon: "a",
-          image_orientation: "a",
-          image_resolution: "a",
-          image_rendering: "a",
-          ime: "a",
-          ime_align: "a",
-          ime_mode: "a",
-          ime_offset: "a",
-          ime_width: "a",
-          initial_letters: "a",
-          inline_box_align: "a",
-          isolation: "a",
-          justify_content: "a",
-          justify_items: "a",
-          justify_self: "a",
-          kerning: "a",
-          left: "a",
-          letter_spacing: "a",
-          lighting_color: "a",
-          line_box_contain: "a",
-          line_break: "a",
-          line_grid: "a",
-          line_height: "a",
-          line_slack: "a",
-          line_snap: "a",
-          list_style: "a",
-          list_style_image: "a",
-          list_style_position: "a",
-          list_style_type: "a",
-          margin: "a",
-          margin_bottom: "a",
-          margin_left: "a",
-          margin_right: "a",
-          margin_top: "a",
-          marker: "a",
-          marker_end: "a",
-          marker_mid: "a",
-          marker_pattern: "a",
-          marker_segment: "a",
-          marker_start: "a",
-          marker_knockout_left: "a",
-          marker_knockout_right: "a",
-          marker_side: "a",
-          marks: "a",
-          marquee_direction: "a",
-          marquee_play_count: "a",
-          marquee_speed: "a",
-          marquee_style: "a",
-          mask: "a",
-          mask_image: "a",
-          mask_repeat: "a",
-          mask_position: "a",
-          mask_clip: "a",
-          mask_origin: "a",
-          mask_size: "a",
-          mask_box: "a",
-          mask_box_outset: "a",
-          mask_box_repeat: "a",
-          mask_box_slice: "a",
-          mask_box_source: "a",
-          mask_box_width: "a",
-          mask_type: "a",
-          max_height: "a",
-          max_lines: "a",
-          max_width: "a",
-          min_height: "a",
-          min_width: "a",
-          mix_blend_mode: "a",
-          nav_down: "a",
-          nav_index: "a",
-          nav_left: "a",
-          nav_right: "a",
-          nav_up: "a",
-          object_fit: "a",
-          object_position: "a",
-          offset_after: "a",
-          offset_before: "a",
-          offset_end: "a",
-          offset_start: "a",
-          opacity: "a",
-          order: "a",
-          orphans: "a",
-          outline: "a",
-          outline_color: "a",
-          outline_style: "a",
-          outline_width: "a",
-          outline_offset: "a",
-          overflow: "a",
-          overflow_x: "a",
-          overflow_y: "a",
-          overflow_style: "a",
-          overflow_wrap: "a",
-          padding: "a",
-          padding_bottom: "a",
-          padding_left: "a",
-          padding_right: "a",
-          padding_top: "a",
-          page: "a",
-          page_break_after: "a",
-          page_break_before: "a",
-          page_break_inside: "a",
-          paint_order: "a",
-          pause: "a",
-          pause_after: "a",
-          pause_before: "a",
-          perspective: "a",
-          perspective_origin: "a",
-          pitch: "a",
-          pitch_range: "a",
-          play_during: "a",
-          pointer_events: "a",
-          position: "a",
-          quotes: "a",
-          region_fragment: "a",
-          resize: "a",
-          rest: "a",
-          rest_after: "a",
-          rest_before: "a",
-          richness: "a",
-          right: "a",
-          ruby_align: "a",
-          ruby_merge: "a",
-          ruby_position: "a",
-          scroll_behavior: "a",
-          scroll_snap_coordinate: "a",
-          scroll_snap_destination: "a",
-          scroll_snap_points_x: "a",
-          scroll_snap_points_y: "a",
-          scroll_snap_type: "a",
-          shape_image_threshold: "a",
-          shape_inside: "a",
-          shape_margin: "a",
-          shape_outside: "a",
-          shape_padding: "a",
-          shape_rendering: "a",
-          size: "a",
-          speak: "a",
-          speak_as: "a",
-          speak_header: "a",
-          speak_numeral: "a",
-          speak_punctuation: "a",
-          speech_rate: "a",
-          stop_color: "a",
-          stop_opacity: "a",
-          stress: "a",
-          string_set: "a",
-          stroke: "a",
-          stroke_dasharray: "a",
-          stroke_dashoffset: "a",
-          stroke_linecap: "a",
-          stroke_linejoin: "a",
-          stroke_miterlimit: "a",
-          stroke_opacity: "a",
-          stroke_width: "a",
-          tab_size: "a",
-          table_layout: "a",
-          text_align: "a",
-          text_align_all: "a",
-          text_align_last: "a",
-          text_anchor: "a",
-          text_combine_upright: "a",
-          text_decoration: "a",
-          text_decoration_color: "a",
-          text_decoration_line: "a",
-          text_decoration_style: "a",
-          text_decoration_skip: "a",
-          text_emphasis: "a",
-          text_emphasis_color: "a",
-          text_emphasis_style: "a",
-          text_emphasis_position: "a",
-          text_emphasis_skip: "a",
-          text_height: "a",
-          text_indent: "a",
-          text_justify: "a",
-          text_orientation: "a",
-          text_overflow: "a",
-          text_rendering: "a",
-          text_shadow: "a",
-          text_size_adjust: "a",
-          text_space_collapse: "a",
-          text_spacing: "a",
-          text_transform: "a",
-          text_underline_position: "a",
-          text_wrap: "a",
-          top: "a",
-          touch_action: "a",
-          transform: "a",
-          transform_box: "a",
-          transform_origin: "a",
-          transform_style: "a",
-          transition: "a",
-          transition_delay: "a",
-          transition_duration: "a",
-          transition_property: "a",
-          unicode_bidi: "a",
-          vector_effect: "a",
-          vertical_align: "a",
-          visibility: "a",
-          voice_balance: "a",
-          voice_duration: "a",
-          voice_family: "a",
-          voice_pitch: "a",
-          voice_range: "a",
-          voice_rate: "a",
-          voice_stress: "a",
-          voice_volumn: "a",
-          volume: "a",
-          white_space: "a",
-          widows: "a",
-          width: "a",
-          will_change: "a",
-          word_break: "a",
-          word_spacing: "a",
-          word_wrap: "a",
-          wrap_flow: "a",
-          wrap_through: "a",
-          writing_mode: "a",
-          z_index: "a",
+            align_content: "a",
+            align_items: "a",
+            align_self: "a",
+            alignment_adjust: "a",
+            alignment_baseline: "a",
+            all: "a",
+            alt: "a",
+            animation: "a",
+            animation_delay: "a",
+            animation_direction: "a",
+            animation_duration: "a",
+            animation_fill_mode: "a",
+            animation_iteration_count: "a",
+            animation_name: "a",
+            animation_play_state: "a",
+            animation_timing_function: "a",
+            azimuth: "a",
+            backface_visibility: "a",
+            background: "a",
+            background_attachment: "a",
+            background_clip: "a",
+            background_color: "a",
+            background_image: "a",
+            background_origin: "a",
+            background_position: "a",
+            background_repeat: "a",
+            background_size: "a",
+            background_blend_mode: "a",
+            baseline_shift: "a",
+            bleed: "a",
+            bookmark_label: "a",
+            bookmark_level: "a",
+            bookmark_state: "a",
+            border: "a",
+            border_color: "a",
+            border_style: "a",
+            border_width: "a",
+            border_bottom: "a",
+            border_bottom_color: "a",
+            border_bottom_style: "a",
+            border_bottom_width: "a",
+            border_left: "a",
+            border_left_color: "a",
+            border_left_style: "a",
+            border_left_width: "a",
+            border_right: "a",
+            border_right_color: "a",
+            border_right_style: "a",
+            border_right_width: "a",
+            border_top: "a",
+            border_top_color: "a",
+            border_top_style: "a",
+            border_top_width: "a",
+            border_collapse: "a",
+            border_image: "a",
+            border_image_outset: "a",
+            border_image_repeat: "a",
+            border_image_slice: "a",
+            border_image_source: "a",
+            border_image_width: "a",
+            border_radius: "a",
+            border_bottom_left_radius: "a",
+            border_bottom_right_radius: "a",
+            border_top_left_radius: "a",
+            border_top_right_radius: "a",
+            border_spacing: "a",
+            bottom: "a",
+            box_decoration_break: "a",
+            box_shadow: "a",
+            box_sizing: "a",
+            box_snap: "a",
+            break_after: "a",
+            break_before: "a",
+            break_inside: "a",
+            buffered_rendering: "a",
+            caption_side: "a",
+            clear: "a",
+            clear_side: "a",
+            clip: "a",
+            clip_path: "a",
+            clip_rule: "a",
+            color: "a",
+            color_adjust: "a",
+            color_correction: "a",
+            color_interpolation: "a",
+            color_interpolation_filters: "a",
+            color_profile: "a",
+            color_rendering: "a",
+            column_fill: "a",
+            column_gap: "a",
+            column_rule: "a",
+            column_rule_color: "a",
+            column_rule_style: "a",
+            column_rule_width: "a",
+            column_span: "a",
+            columns: "a",
+            column_count: "a",
+            column_width: "a",
+            contain: "a",
+            content: "a",
+            counter_increment: "a",
+            counter_reset: "a",
+            counter_set: "a",
+            cue: "a",
+            cue_after: "a",
+            cue_before: "a",
+            cursor: "a",
+            direction: "a",
+            display: "a",
+            display_inside: "a",
+            display_outside: "a",
+            display_extras: "a",
+            display_box: "a",
+            dominant_baseline: "a",
+            elevation: "a",
+            empty_cells: "a",
+            enable_background: "a",
+            fill: "a",
+            fill_opacity: "a",
+            fill_rule: "a",
+            filter: "a",
+            float: "a",
+            float_defer_column: "a",
+            float_defer_page: "a",
+            float_offset: "a",
+            float_wrap: "a",
+            flow_into: "a",
+            flow_from: "a",
+            flex: "a",
+            flex_basis: "a",
+            flex_grow: "a",
+            flex_shrink: "a",
+            flex_flow: "a",
+            flex_direction: "a",
+            flex_wrap: "a",
+            flood_color: "a",
+            flood_opacity: "a",
+            font: "a",
+            font_family: "a",
+            font_size: "a",
+            font_stretch: "a",
+            font_style: "a",
+            font_weight: "a",
+            font_feature_settings: "a",
+            font_kerning: "a",
+            font_language_override: "a",
+            font_size_adjust: "a",
+            font_synthesis: "a",
+            font_variant: "a",
+            font_variant_alternates: "a",
+            font_variant_caps: "a",
+            font_variant_east_asian: "a",
+            font_variant_ligatures: "a",
+            font_variant_numeric: "a",
+            font_variant_position: "a",
+            footnote_policy: "a",
+            glyph_orientation_horizontal: "a",
+            glyph_orientation_vertical: "a",
+            grid: "a",
+            grid_auto_flow: "a",
+            grid_auto_columns: "a",
+            grid_auto_rows: "a",
+            grid_template: "a",
+            grid_template_areas: "a",
+            grid_template_columns: "a",
+            grid_template_rows: "a",
+            grid_area: "a",
+            grid_column: "a",
+            grid_column_start: "a",
+            grid_column_end: "a",
+            grid_row: "a",
+            grid_row_start: "a",
+            grid_row_end: "a",
+            hanging_punctuation: "a",
+            height: "a",
+            hyphenate_character: "a",
+            hyphenate_limit_chars: "a",
+            hyphenate_limit_last: "a",
+            hyphenate_limit_lines: "a",
+            hyphenate_limit_zone: "a",
+            hyphens: "a",
+            icon: "a",
+            image_orientation: "a",
+            image_resolution: "a",
+            image_rendering: "a",
+            ime: "a",
+            ime_align: "a",
+            ime_mode: "a",
+            ime_offset: "a",
+            ime_width: "a",
+            initial_letters: "a",
+            inline_box_align: "a",
+            isolation: "a",
+            justify_content: "a",
+            justify_items: "a",
+            justify_self: "a",
+            kerning: "a",
+            left: "a",
+            letter_spacing: "a",
+            lighting_color: "a",
+            line_box_contain: "a",
+            line_break: "a",
+            line_grid: "a",
+            line_height: "a",
+            line_slack: "a",
+            line_snap: "a",
+            list_style: "a",
+            list_style_image: "a",
+            list_style_position: "a",
+            list_style_type: "a",
+            margin: "a",
+            margin_bottom: "a",
+            margin_left: "a",
+            margin_right: "a",
+            margin_top: "a",
+            marker: "a",
+            marker_end: "a",
+            marker_mid: "a",
+            marker_pattern: "a",
+            marker_segment: "a",
+            marker_start: "a",
+            marker_knockout_left: "a",
+            marker_knockout_right: "a",
+            marker_side: "a",
+            marks: "a",
+            marquee_direction: "a",
+            marquee_play_count: "a",
+            marquee_speed: "a",
+            marquee_style: "a",
+            mask: "a",
+            mask_image: "a",
+            mask_repeat: "a",
+            mask_position: "a",
+            mask_clip: "a",
+            mask_origin: "a",
+            mask_size: "a",
+            mask_box: "a",
+            mask_box_outset: "a",
+            mask_box_repeat: "a",
+            mask_box_slice: "a",
+            mask_box_source: "a",
+            mask_box_width: "a",
+            mask_type: "a",
+            max_height: "a",
+            max_lines: "a",
+            max_width: "a",
+            min_height: "a",
+            min_width: "a",
+            mix_blend_mode: "a",
+            nav_down: "a",
+            nav_index: "a",
+            nav_left: "a",
+            nav_right: "a",
+            nav_up: "a",
+            object_fit: "a",
+            object_position: "a",
+            offset_after: "a",
+            offset_before: "a",
+            offset_end: "a",
+            offset_start: "a",
+            opacity: "a",
+            order: "a",
+            orphans: "a",
+            outline: "a",
+            outline_color: "a",
+            outline_style: "a",
+            outline_width: "a",
+            outline_offset: "a",
+            overflow: "a",
+            overflow_x: "a",
+            overflow_y: "a",
+            overflow_style: "a",
+            overflow_wrap: "a",
+            padding: "a",
+            padding_bottom: "a",
+            padding_left: "a",
+            padding_right: "a",
+            padding_top: "a",
+            page: "a",
+            page_break_after: "a",
+            page_break_before: "a",
+            page_break_inside: "a",
+            paint_order: "a",
+            pause: "a",
+            pause_after: "a",
+            pause_before: "a",
+            perspective: "a",
+            perspective_origin: "a",
+            pitch: "a",
+            pitch_range: "a",
+            play_during: "a",
+            pointer_events: "a",
+            position: "a",
+            quotes: "a",
+            region_fragment: "a",
+            resize: "a",
+            rest: "a",
+            rest_after: "a",
+            rest_before: "a",
+            richness: "a",
+            right: "a",
+            ruby_align: "a",
+            ruby_merge: "a",
+            ruby_position: "a",
+            scroll_behavior: "a",
+            scroll_snap_coordinate: "a",
+            scroll_snap_destination: "a",
+            scroll_snap_points_x: "a",
+            scroll_snap_points_y: "a",
+            scroll_snap_type: "a",
+            shape_image_threshold: "a",
+            shape_inside: "a",
+            shape_margin: "a",
+            shape_outside: "a",
+            shape_padding: "a",
+            shape_rendering: "a",
+            size: "a",
+            speak: "a",
+            speak_as: "a",
+            speak_header: "a",
+            speak_numeral: "a",
+            speak_punctuation: "a",
+            speech_rate: "a",
+            stop_color: "a",
+            stop_opacity: "a",
+            stress: "a",
+            string_set: "a",
+            stroke: "a",
+            stroke_dasharray: "a",
+            stroke_dashoffset: "a",
+            stroke_linecap: "a",
+            stroke_linejoin: "a",
+            stroke_miterlimit: "a",
+            stroke_opacity: "a",
+            stroke_width: "a",
+            tab_size: "a",
+            table_layout: "a",
+            text_align: "a",
+            text_align_all: "a",
+            text_align_last: "a",
+            text_anchor: "a",
+            text_combine_upright: "a",
+            text_decoration: "a",
+            text_decoration_color: "a",
+            text_decoration_line: "a",
+            text_decoration_style: "a",
+            text_decoration_skip: "a",
+            text_emphasis: "a",
+            text_emphasis_color: "a",
+            text_emphasis_style: "a",
+            text_emphasis_position: "a",
+            text_emphasis_skip: "a",
+            text_height: "a",
+            text_indent: "a",
+            text_justify: "a",
+            text_orientation: "a",
+            text_overflow: "a",
+            text_rendering: "a",
+            text_shadow: "a",
+            text_size_adjust: "a",
+            text_space_collapse: "a",
+            text_spacing: "a",
+            text_transform: "a",
+            text_underline_position: "a",
+            text_wrap: "a",
+            top: "a",
+            touch_action: "a",
+            transform: "a",
+            transform_box: "a",
+            transform_origin: "a",
+            transform_style: "a",
+            transition: "a",
+            transition_delay: "a",
+            transition_duration: "a",
+            transition_property: "a",
+            unicode_bidi: "a",
+            vector_effect: "a",
+            vertical_align: "a",
+            visibility: "a",
+            voice_balance: "a",
+            voice_duration: "a",
+            voice_family: "a",
+            voice_pitch: "a",
+            voice_range: "a",
+            voice_rate: "a",
+            voice_stress: "a",
+            voice_volumn: "a",
+            volume: "a",
+            white_space: "a",
+            widows: "a",
+            width: "a",
+            will_change: "a",
+            word_break: "a",
+            word_spacing: "a",
+            word_wrap: "a",
+            wrap_flow: "a",
+            wrap_through: "a",
+            writing_mode: "a",
+            z_index: "a",
+
+            "This example isn't quite useful yet"
         }
     })
 }

+ 2 - 2
examples/all_events.rs

@@ -1,4 +1,4 @@
-use dioxus::{events::*, prelude::*};
+use dioxus::{events::*, html::MouseEvent, prelude::*};
 
 fn main() {
     dioxus_desktop::launch(app);
@@ -41,7 +41,7 @@ const RECT_STYLE: &str = r#"
     "#;
 
 fn app(cx: Scope) -> Element {
-    let events = use_ref(&cx, std::collections::VecDeque::new);
+    let events = use_ref(cx, std::collections::VecDeque::new);
 
     let log_event = move |event: Event| {
         let mut events = events.write();

+ 2 - 6
examples/borrowed.rs

@@ -29,9 +29,7 @@ fn app(cx: Scope) -> Element {
 
     cx.render(rsx! {
         div {
-            Child1 {
-                text: first
-            }
+            Child1 { text: first }
         }
     })
 }
@@ -59,9 +57,7 @@ struct C2Props<'a> {
 
 fn Child2<'a>(cx: Scope<'a, C2Props<'a>>) -> Element {
     cx.render(rsx! {
-        Child3 {
-            text: cx.props.text
-        }
+        Child3 { text: cx.props.text }
     })
 }
 

+ 11 - 23
examples/calculator.rs

@@ -19,12 +19,13 @@ fn main() {
 }
 
 fn app(cx: Scope) -> Element {
-    let val = use_state(&cx, || String::from("0"));
+    let val = use_state(cx, || String::from("0"));
 
     let input_digit = move |num: u8| {
         if val.get() == "0" {
             val.set(String::new());
         }
+
         val.make_mut().push_str(num.to_string().as_str());
     };
 
@@ -99,12 +100,8 @@ fn app(cx: Scope) -> Element {
                                 }
                             }
                             div { class: "digit-keys",
-                                button { class: "calculator-key key-0", onclick: move |_| input_digit(0),
-                                    "0"
-                                }
-                                button { class: "calculator-key key-dot", onclick: move |_| val.make_mut().push('.'),
-                                    "●"
-                                }
+                                button { class: "calculator-key key-0", onclick: move |_| input_digit(0), "0" }
+                                button { class: "calculator-key key-dot", onclick: move |_| val.make_mut().push('.'), "●" }
                                 (1..10).map(|k| rsx!{
                                     button {
                                         class: "calculator-key {k}",
@@ -116,22 +113,13 @@ fn app(cx: Scope) -> Element {
                             }
                         }
                         div { class: "operator-keys",
-                            button { class: "calculator-key key-divide", onclick: move |_| input_operator("/"),
-                                "÷"
-                            }
-                            button { class: "calculator-key key-multiply", onclick: move |_| input_operator("*"),
-                                "×"
-                            }
-                            button { class: "calculator-key key-subtract", onclick: move |_| input_operator("-"),
-                                "−"
-                            }
-                            button { class: "calculator-key key-add", onclick: move |_| input_operator("+"),
-                                "+"
-                            }
-                            button { class: "calculator-key key-equals",
-                                onclick: move |_| {
-                                    val.set(format!("{}", calc_val(val.as_str())));
-                                },
+                            button { class: "calculator-key key-divide", onclick: move |_| input_operator("/"), "÷" }
+                            button { class: "calculator-key key-multiply", onclick: move |_| input_operator("*"), "×" }
+                            button { class: "calculator-key key-subtract", onclick: move |_| input_operator("-"), "−" }
+                            button { class: "calculator-key key-add", onclick: move |_| input_operator("+"), "+" }
+                            button {
+                                class: "calculator-key key-equals",
+                                onclick: move |_| val.set(format!("{}", calc_val(val.as_str()))),
                                 "="
                             }
                         }

+ 22 - 0
examples/callback.rs

@@ -0,0 +1,22 @@
+use dioxus::prelude::*;
+
+fn main() {
+    dioxus_desktop::launch(app);
+}
+
+fn app(cx: Scope) -> Element {
+    let login = use_callback!(cx, move |_| async move {
+        let res = reqwest::get("https://dog.ceo/api/breeds/list/all")
+            .await
+            .unwrap()
+            .text()
+            .await
+            .unwrap();
+
+        println!("{:#?}, ", res);
+    });
+
+    cx.render(rsx! {
+        button { onclick: login, "Click me!" }
+    })
+}

+ 4 - 4
examples/crm.rs

@@ -16,10 +16,10 @@ pub struct Client {
 }
 
 fn app(cx: Scope) -> Element {
-    let clients = use_ref(&cx, || vec![] as Vec<Client>);
-    let firstname = use_state(&cx, String::new);
-    let lastname = use_state(&cx, String::new);
-    let description = use_state(&cx, String::new);
+    let clients = use_ref(cx, || vec![] as Vec<Client>);
+    let firstname = use_state(cx, String::new);
+    let lastname = use_state(cx, String::new);
+    let description = use_state(cx, String::new);
 
     cx.render(rsx!(
         body {

+ 9 - 7
examples/custom_element.rs

@@ -10,21 +10,23 @@ fn main() {
     let mut dom = VirtualDom::new(app);
     let _ = dom.rebuild();
 
-    let output = dioxus_ssr::render_vdom(&dom);
+    let output = dioxus_ssr::render(&dom);
 
     println!("{}", output);
 }
 
 fn app(cx: Scope) -> Element {
-    let nf = NodeFactory::new(&cx);
+    // let nf = NodeFactory::new(cx);
 
-    let mut attrs = dioxus::core::exports::bumpalo::collections::Vec::new_in(nf.bump());
+    // let mut attrs = dioxus::core::exports::bumpalo::collections::Vec::new_in(nf.bump());
 
-    attrs.push(nf.attr("client-id", format_args!("abc123"), None, false));
+    // attrs.push(nf.attr("client-id", format_args!("abc123"), None, false));
 
-    attrs.push(nf.attr("name", format_args!("bob"), None, false));
+    // attrs.push(nf.attr("name", format_args!("bob"), None, false));
 
-    attrs.push(nf.attr("age", format_args!("47"), None, false));
+    // attrs.push(nf.attr("age", format_args!("47"), None, false));
 
-    Some(nf.raw_element("my-element", None, &[], attrs.into_bump_slice(), &[], None))
+    // Some(nf.raw_element("my-element", None, &[], attrs.into_bump_slice(), &[], None))
+
+    todo!()
 }

+ 1 - 1
examples/disabled.rs

@@ -5,7 +5,7 @@ fn main() {
 }
 
 fn app(cx: Scope) -> Element {
-    let disabled = use_state(&cx, || false);
+    let disabled = use_state(cx, || false);
 
     cx.render(rsx! {
         div {

+ 26 - 34
examples/dog_app.rs

@@ -1,12 +1,8 @@
-#![allow(non_snake_case)]
-
-//! Render a bunch of doggos!
-
 use dioxus::prelude::*;
 use std::collections::HashMap;
 
 fn main() {
-    dioxus_desktop::launch(app);
+    dioxus_desktop::launch(|cx| render!(app_root {}));
 }
 
 #[derive(Debug, Clone, PartialEq, serde::Deserialize)]
@@ -14,10 +10,10 @@ struct ListBreeds {
     message: HashMap<String, Vec<String>>,
 }
 
-fn app(cx: Scope) -> Element {
-    let breed = use_state(&cx, || None);
+async fn app_root(cx: Scope<'_>) -> Element {
+    let breed = use_state(cx, || "deerhound".to_string());
 
-    let breeds = use_future(&cx, (), |_| async move {
+    let breeds = use_future!(cx, || async move {
         reqwest::get("https://dog.ceo/api/breeds/list/all")
             .await
             .unwrap()
@@ -25,32 +21,26 @@ fn app(cx: Scope) -> Element {
             .await
     });
 
-    match breeds.value() {
-        Some(Ok(breeds)) => cx.render(rsx! {
-            div {
+    match breeds.await {
+        Ok(breeds) => cx.render(rsx! {
+            div { height: "500px",
                 h1 { "Select a dog breed!" }
                 div { display: "flex",
                     ul { flex: "50%",
-                        breeds.message.keys().map(|cur_breed| rsx!(
-                            li {
+                        for cur_breed in breeds.message.keys().take(10) {
+                            li { key: "{cur_breed}",
                                 button {
-                                    onclick: move |_| breed.set(Some(cur_breed.clone())),
+                                    onclick: move |_| breed.set(cur_breed.clone()),
                                     "{cur_breed}"
                                 }
                             }
-                        ))
-                    }
-                    div { flex: "50%",
-                        match breed.get() {
-                            Some(breed) => rsx!( Breed { breed: breed.clone() } ),
-                            None => rsx!("No Breed selected"),
                         }
                     }
+                    div { flex: "50%", breed_pic { breed: breed.to_string() } }
                 }
             }
         }),
-        Some(Err(_e)) => cx.render(rsx! { div { "Error fetching breeds" } }),
-        None => cx.render(rsx! { div { "Loading dogs..." } }),
+        Err(_e) => cx.render(rsx! { div { "Error fetching breeds" } }),
     }
 }
 
@@ -60,8 +50,8 @@ struct DogApi {
 }
 
 #[inline_props]
-fn Breed(cx: Scope, breed: String) -> Element {
-    let fut = use_future(&cx, (breed,), |(breed,)| async move {
+async fn breed_pic(cx: Scope, breed: String) -> Element {
+    let fut = use_future!(cx, |breed| async move {
         reqwest::get(format!("https://dog.ceo/api/breed/{}/images/random", breed))
             .await
             .unwrap()
@@ -69,21 +59,23 @@ fn Breed(cx: Scope, breed: String) -> Element {
             .await
     });
 
-    cx.render(match fut.value() {
-        Some(Ok(resp)) => rsx! {
-            button {
-                onclick: move |_| fut.restart(),
-                "Click to fetch another doggo"
-            }
+    match fut.await {
+        Ok(resp) => render! {
             div {
+                button {
+                    onclick: move |_| {
+                        println!("clicked");
+                        fut.restart()
+                    },
+                    "Click to fetch another doggo"
+                }
                 img {
+                    src: "{resp.message}",
                     max_width: "500px",
                     max_height: "500px",
-                    src: "{resp.message}",
                 }
             }
         },
-        Some(Err(_)) => rsx! { div { "loading dogs failed" } },
-        None => rsx! { div { "loading dogs..." } },
-    })
+        Err(_) => render! { div { "loading dogs failed" } },
+    }
 }

+ 13 - 1
examples/error_handle.rs

@@ -5,7 +5,7 @@ fn main() {
 }
 
 fn app(cx: Scope) -> Element {
-    let val = use_state(&cx, || "0.0001");
+    let val = use_state(cx, || "0.0001");
 
     let num = match val.parse::<f32>() {
         Err(_) => return cx.render(rsx!("Parsing failed")),
@@ -18,5 +18,17 @@ fn app(cx: Scope) -> Element {
             onclick: move |_| val.set("invalid"),
             "Set an invalid number"
         }
+        (0..5).map(|i| rsx! {
+            demo_c { x: i }
+        })
+    })
+}
+
+#[inline_props]
+fn demo_c(cx: Scope, x: i32) -> Element {
+    cx.render(rsx! {
+        h1 {
+            "asdasdasdasd {x}"
+        }
     })
 }

+ 3 - 3
examples/eval.rs

@@ -6,9 +6,9 @@ fn main() {
 }
 
 fn app(cx: Scope) -> Element {
-    let script = use_state(&cx, String::new);
-    let eval = dioxus_desktop::use_eval(&cx);
-    let future: &UseRef<Option<EvalResult>> = use_ref(&cx, || None);
+    let script = use_state(cx, String::new);
+    let eval = dioxus_desktop::use_eval(cx);
+    let future: &UseRef<Option<EvalResult>> = use_ref(cx, || None);
     if future.read().is_some() {
         let future_clone = future.clone();
         cx.spawn(async move {

+ 4 - 4
examples/fermi.rs

@@ -10,17 +10,17 @@ fn main() {
 static NAME: Atom<String> = |_| "world".to_string();
 
 fn app(cx: Scope) -> Element {
-    let name = use_read(&cx, NAME);
+    let name = use_read(cx, NAME);
 
     cx.render(rsx! {
         div { "hello {name}!" }
         Child {}
-        ChildWithRef{}
+        ChildWithRef {}
     })
 }
 
 fn Child(cx: Scope) -> Element {
-    let set_name = use_set(&cx, NAME);
+    let set_name = use_set(cx, NAME);
 
     cx.render(rsx! {
         button {
@@ -33,7 +33,7 @@ fn Child(cx: Scope) -> Element {
 static NAMES: AtomRef<Vec<String>> = |_| vec!["world".to_string()];
 
 fn ChildWithRef(cx: Scope) -> Element {
-    let names = use_atom_ref(&cx, NAMES);
+    let names = use_atom_ref(cx, NAMES);
 
     cx.render(rsx! {
         div {

+ 3 - 3
examples/file_explorer.rs

@@ -19,9 +19,9 @@ fn main() {
 }
 
 fn app(cx: Scope) -> Element {
-    let files = use_ref(&cx, Files::new);
+    let files = use_ref(cx, Files::new);
 
-    render! {
+    cx.render(rsx! {
         div {
             link { href:"https://fonts.googleapis.com/icon?family=Material+Icons", rel:"stylesheet", }
             style { include_str!("./assets/fileexplorer.css") }
@@ -62,7 +62,7 @@ fn app(cx: Scope) -> Element {
                 })
             }
         }
-    }
+    })
 }
 
 struct Files {

+ 3 - 3
examples/framework_benchmark.rs

@@ -32,8 +32,8 @@ impl Label {
 }
 
 fn app(cx: Scope) -> Element {
-    let items = use_ref(&cx, Vec::new);
-    let selected = use_state(&cx, || None);
+    let items = use_ref(cx, Vec::new);
+    let selected = use_state(cx, || None);
 
     cx.render(rsx! {
         div { class: "container",
@@ -72,7 +72,7 @@ fn app(cx: Scope) -> Element {
                             td { class:"col-md-1" }
                             td { class:"col-md-1", "{item.key}" }
                             td { class:"col-md-1", onclick: move |_| selected.set(Some(id)),
-                                a { class: "lbl", item.labels }
+                                a { class: "lbl", "{item.labels[0]}{item.labels[1]}{item.labels[2]}" }
                             }
                             td { class: "col-md-1",
                                 a { class: "remove", onclick: move |_| { items.write().remove(id); },

+ 13 - 0
examples/generic_component.rs

@@ -0,0 +1,13 @@
+use dioxus::prelude::*;
+
+fn main() {
+    dioxus_desktop::launch(app);
+}
+
+fn app(cx: Scope) -> Element {
+    cx.render(rsx! { generic_child::<i32>{} })
+}
+
+fn generic_child<T>(cx: Scope) -> Element {
+    cx.render(rsx! { div {} })
+}

+ 2 - 2
examples/hydration.rs

@@ -14,13 +14,13 @@ use dioxus_desktop::Config;
 
 fn main() {
     let vdom = VirtualDom::new(app);
-    let content = dioxus_ssr::render_vdom_cfg(&vdom, |f| f.pre_render(true));
+    let content = dioxus_ssr::pre_render(&vdom);
 
     dioxus_desktop::launch_cfg(app, Config::new().with_prerendered(content));
 }
 
 fn app(cx: Scope) -> Element {
-    let val = use_state(&cx, || 0);
+    let val = use_state(cx, || 0);
 
     cx.render(rsx! {
         div {

+ 1 - 1
examples/inlineprops.rs

@@ -28,7 +28,7 @@ fn main() {
 }
 
 fn app(cx: Scope) -> Element {
-    let state = use_state(&cx, || 1);
+    let state = use_state(cx, || 1);
 
     cx.render(rsx! {
         div {

+ 3 - 5
examples/inputs.rs

@@ -2,7 +2,7 @@
 //!
 //! There is some conversion happening when input types are checkbox/radio/select/textarea etc.
 
-use dioxus::{events::FormEvent, prelude::*};
+use dioxus::prelude::*;
 
 fn main() {
     dioxus_desktop::launch(app);
@@ -28,7 +28,6 @@ const FIELDS: &[(&str, &str)] = &[
     ("text", ""),
     ("time", ""),
     ("url", ""),
-    //
     // less supported things
     ("hidden", ""),
     ("month", ""), // degrades to text most of the time, but works properly as "value'"
@@ -114,7 +113,7 @@ fn app(cx: Scope) -> Element {
                 }
             }
 
-            FIELDS.iter().map(|(field, value)| rsx!(
+            FIELDS.iter().map(|(field, value)| rsx! {
                 div {
                     input {
                         id: "{field}",
@@ -131,8 +130,7 @@ fn app(cx: Scope) -> Element {
                     }
                     br {}
                 }
-            ))
-
+            })
         }
     })
 }

+ 2 - 4
examples/login_form.rs

@@ -1,7 +1,6 @@
 //! This example demonstrates the following:
 //! Futures in a callback, Router, and Forms
 
-use dioxus::events::*;
 use dioxus::prelude::*;
 
 fn main() {
@@ -37,11 +36,10 @@ fn app(cx: Scope) -> Element {
         form {
             onsubmit: onsubmit,
             prevent_default: "onsubmit", // Prevent the default behavior of <form> to post
-
-            input { "type": "text", id: "username", name: "username" }
+            input { r#type: "text", id: "username", name: "username" }
             label { "Username" }
             br {}
-            input { "type": "password", id: "password", name: "password" }
+            input { r#type: "password", id: "password", name: "password" }
             label { "Password" }
             br {}
             button { "Login" }

+ 1 - 1
examples/nested_listeners.rs

@@ -22,7 +22,7 @@ fn app(cx: Scope) -> Element {
             button {
                 onclick: move |evt| {
                     println!("clicked! bottom no bubbling");
-                    evt.cancel_bubble();
+                    evt.stop_propogation();
                 },
                 "Dont propogate"
             }

+ 2 - 1
examples/pattern_model.rs

@@ -19,6 +19,7 @@
 
 use dioxus::events::*;
 use dioxus::html::input_data::keyboard_types::Key;
+use dioxus::html::MouseEvent;
 use dioxus::prelude::*;
 use dioxus_desktop::wry::application::dpi::LogicalSize;
 use dioxus_desktop::{Config, WindowBuilder};
@@ -35,7 +36,7 @@ fn main() {
 }
 
 fn app(cx: Scope) -> Element {
-    let state = use_ref(&cx, Calculator::new);
+    let state = use_ref(cx, Calculator::new);
 
     cx.render(rsx! {
         style { include_str!("./assets/calculator.css") }

+ 1 - 1
examples/pattern_reducer.rs

@@ -15,7 +15,7 @@ fn main() {
 }
 
 fn app(cx: Scope) -> Element {
-    let state = use_state(&cx, PlayerState::new);
+    let state = use_state(cx, PlayerState::new);
 
     cx.render(rsx!(
         div {

+ 1 - 1
examples/readme.rs

@@ -9,7 +9,7 @@ fn main() {
 }
 
 fn app(cx: Scope) -> Element {
-    let mut count = use_state(&cx, || 0);
+    let mut count = use_state(cx, || 0);
 
     cx.render(rsx! {
         h1 { "High-Five counter: {count}" }

+ 3 - 3
examples/router.rs

@@ -30,7 +30,7 @@ fn app(cx: Scope) -> Element {
 }
 
 fn BlogPost(cx: Scope) -> Element {
-    let post = dioxus_router::use_route(&cx).last_segment()?;
+    let post = dioxus_router::use_route(cx).last_segment().unwrap();
 
     cx.render(rsx! {
         div {
@@ -46,9 +46,9 @@ struct Query {
 }
 
 fn User(cx: Scope) -> Element {
-    let post = dioxus_router::use_route(&cx).last_segment()?;
+    let post = dioxus_router::use_route(cx).last_segment().unwrap();
 
-    let query = dioxus_router::use_route(&cx)
+    let query = dioxus_router::use_route(cx)
         .query::<Query>()
         .unwrap_or(Query { bold: false });
 

+ 7 - 6
examples/rsx_compile_fail.rs

@@ -5,21 +5,22 @@ use dioxus::prelude::*;
 
 fn main() {
     let mut vdom = VirtualDom::new(example);
-    vdom.rebuild();
+    _ = vdom.rebuild();
 
-    let out = dioxus_ssr::render_vdom_cfg(&vdom, |c| c.newline(true).indent(true));
-    println!("{}", out);
+    let mut renderer = dioxus_ssr::Renderer::new();
+    renderer.pretty = true;
+    renderer.render(&vdom);
 }
 
 fn example(cx: Scope) -> Element {
-    let items = use_state(&cx, || {
+    let items = use_state(cx, || {
         vec![Thing {
             a: "asd".to_string(),
             b: 10,
         }]
     });
 
-    let things = use_ref(&cx, || {
+    let things = use_ref(cx, || {
         vec![Thing {
             a: "asd".to_string(),
             b: 10,
@@ -27,7 +28,7 @@ fn example(cx: Scope) -> Element {
     });
     let things_list = things.read();
 
-    let mything = use_ref(&cx, || Some(String::from("asd")));
+    let mything = use_ref(cx, || Some(String::from("asd")));
     let mything_read = mything.read();
 
     cx.render(rsx!(

+ 4 - 4
examples/rsx_usage.rs

@@ -165,13 +165,13 @@ fn app(cx: Scope) -> Element {
 
             // Can pass in props directly as an expression
             {
-                let props = TallerProps {a: "hello", children: Default::default()};
+                let props = TallerProps {a: "hello", children: cx.render(rsx!(()))};
                 rsx!(Taller { ..props })
             }
 
             // Spreading can also be overridden manually
             Taller {
-                ..TallerProps { a: "ballin!", children: Default::default() },
+                ..TallerProps { a: "ballin!", children: cx.render(rsx!(()) )},
                 a: "not ballin!"
             }
 
@@ -183,7 +183,7 @@ fn app(cx: Scope) -> Element {
 
             // Components can be generic too
             // This component takes i32 type to give you typed input
-            TypedInput::<TypedInputProps<i32>> {}
+            TypedInput::<i32> {}
 
             // Type inference can be used too
             TypedInput { initial: 10.0 }
@@ -200,7 +200,7 @@ fn app(cx: Scope) -> Element {
 
             // helper functions
             // Anything that implements IntoVnode can be dropped directly into Rsx
-            helper(&cx, "hello world!")
+            helper(cx, "hello world!")
 
             // Strings can be supplied directly
             String::from("Hello world!")

+ 14 - 11
examples/simple_list.rs

@@ -6,23 +6,26 @@ fn main() {
 
 fn app(cx: Scope) -> Element {
     cx.render(rsx!(
-        // Use Map directly to lazily pull elements
-        (0..10).map(|f| rsx! { "{f}" }),
+        div {
+            // Use Map directly to lazily pull elements
+            (0..10).map(|f| rsx! { "{f}" }),
 
-        // Collect into an intermediate collection if necessary
-        ["a", "b", "c"]
-            .into_iter()
-            .map(|f| rsx! { "{f}" })
-            .collect::<Vec<_>>(),
+            // Collect into an intermediate collection if necessary, and call into_iter
+            ["a", "b", "c", "d", "e", "f"]
+                .into_iter()
+                .map(|f| rsx! { "{f}" })
+                .collect::<Vec<_>>()
+                .into_iter(),
 
-        // Use optionals
-        Some(rsx! { "Some" }),
+            // Use optionals
+            Some(rsx! { "Some" }),
 
-        div {
+            // use a for loop where the body itself is RSX
             for name in 0..10 {
-                rsx! { "{name}" }
+                div {"{name}"}
             }
 
+            // Or even use an unterminated conditional
             if true {
                 rsx!{ "hello world!" }
             }

+ 5 - 12
examples/ssr.rs

@@ -2,15 +2,13 @@
 //!
 //! This example shows how we can render the Dioxus Virtualdom using SSR.
 
-use std::fmt::Write;
-
 use dioxus::prelude::*;
 
 fn main() {
     // We can render VirtualDoms
     let mut vdom = VirtualDom::new(app);
     let _ = vdom.rebuild();
-    println!("{}", dioxus_ssr::render_vdom(&vdom));
+    println!("{}", dioxus_ssr::render(&vdom));
 
     // Or we can render rsx! calls themselves
     println!(
@@ -23,17 +21,12 @@ fn main() {
     );
 
     // We can configure the SSR rendering to add ids for rehydration
-    println!(
-        "{}",
-        dioxus_ssr::render_vdom_cfg(&vdom, |c| c.pre_render(true))
-    );
+    println!("{}", dioxus_ssr::pre_render(&vdom));
 
-    // We can even render as a writer
+    // We can render to a buf directly too
     let mut file = String::new();
-    let _ = file.write_fmt(format_args!(
-        "{}",
-        dioxus_ssr::TextRenderer::from_vdom(&vdom, Default::default())
-    ));
+    let mut renderer = dioxus_ssr::Renderer::default();
+    renderer.render_to(&mut file, &vdom).unwrap();
     println!("{}", file);
 }
 

+ 1 - 1
examples/suspense.rs

@@ -53,7 +53,7 @@ fn app(cx: Scope) -> Element {
 /// Suspense is achieved my moving the future into only the component that
 /// actually renders the data.
 fn Doggo(cx: Scope) -> Element {
-    let fut = use_future(&cx, (), |_| async move {
+    let fut = use_future(cx, (), |_| async move {
         reqwest::get("https://dog.ceo/api/breeds/image/random/")
             .await
             .unwrap()

+ 15 - 13
examples/svg.rs

@@ -1,15 +1,15 @@
 // Thanks to @japsu and their project https://github.com/japsu/jatsi for the example!
 
-use dioxus::{events::MouseEvent, prelude::*};
+use dioxus::prelude::*;
 
 fn main() {
     dioxus_desktop::launch(app);
 }
 
 fn app(cx: Scope) -> Element {
-    let val = use_state(&cx, || 5);
+    let val = use_state(cx, || 5);
 
-    render! {
+    cx.render(rsx! {
         div {
             user_select: "none",
             webkit_user_select: "none",
@@ -31,7 +31,7 @@ fn app(cx: Scope) -> Element {
                 }
             }
         }
-    }
+    })
 }
 
 #[derive(Props)]
@@ -70,19 +70,21 @@ pub fn Die<'a>(cx: Scope<'a, DieProps<'a>>) -> Element {
         .map(|((x, y), _)| {
             let dcx = x * OFFSET;
             let dcy = y * OFFSET;
-            rsx!(circle {
-                cx: "{dcx}",
-                cy: "{dcy}",
-                r: "{DOT_RADIUS}",
-                fill: "#333"
-            })
+
+            rsx! {
+                circle {
+                    cx: "{dcx}",
+                    cy: "{dcy}",
+                    r: "{DOT_RADIUS}",
+                    fill: "#333"
+                }
+            }
         });
 
-    render! {
+    cx.render(rsx! {
       svg {
         onclick: move |e| cx.props.onclick.call(e),
         prevent_default: "onclick",
-        "dioxus-prevent-default": "onclick",
         class: "die",
         view_box: "-1000 -1000 2000 2000",
 
@@ -97,5 +99,5 @@ pub fn Die<'a>(cx: Scope<'a, DieProps<'a>>) -> Element {
 
         dots
       }
-    }
+    })
 }

+ 2 - 2
examples/tasks.rs

@@ -10,9 +10,9 @@ fn main() {
 }
 
 fn app(cx: Scope) -> Element {
-    let count = use_state(&cx, || 0);
+    let count = use_state(cx, || 0);
 
-    use_future(&cx, (), move |_| {
+    use_future(cx, (), move |_| {
         let mut count = count.clone();
         async move {
             loop {

+ 1 - 1
examples/textarea.rs

@@ -7,7 +7,7 @@ fn main() {
 }
 
 fn app(cx: Scope) -> Element {
-    let model = use_state(&cx, || String::from("asd"));
+    let model = use_state(cx, || String::from("asd"));
 
     println!("{}", model);
 

+ 8 - 6
examples/todomvc.rs

@@ -22,10 +22,10 @@ pub struct TodoItem {
 }
 
 pub fn app(cx: Scope<()>) -> Element {
-    let todos = use_state(&cx, im_rc::HashMap::<u32, TodoItem>::default);
-    let filter = use_state(&cx, || FilterState::All);
-    let draft = use_state(&cx, || "".to_string());
-    let todo_id = use_state(&cx, || 0);
+    let todos = use_state(cx, im_rc::HashMap::<u32, TodoItem>::default);
+    let filter = use_state(cx, || FilterState::All);
+    let draft = use_state(cx, || "".to_string());
+    let todo_id = use_state(cx, || 0);
 
     // Filter the todos based on the filter state
     let mut filtered_todos = todos
@@ -57,7 +57,9 @@ pub fn app(cx: Scope<()>) -> Element {
                         placeholder: "What needs to be done?",
                         value: "{draft}",
                         autofocus: "true",
-                        oninput: move |evt| draft.set(evt.value.clone()),
+                        oninput: move |evt| {
+                            draft.set(evt.value.clone());
+                        },
                         onkeydown: move |evt| {
                             if evt.key() == Key::Enter && !draft.is_empty() {
                                 todos.make_mut().insert(
@@ -114,7 +116,7 @@ pub struct TodoEntryProps<'a> {
 }
 
 pub fn TodoEntry<'a>(cx: Scope<'a, TodoEntryProps<'a>>) -> Element {
-    let is_editing = use_state(&cx, || false);
+    let is_editing = use_state(cx, || false);
 
     let todos = cx.props.todos.get();
     let todo = &todos[&cx.props.id];

+ 10 - 10
examples/window_event.rs

@@ -12,15 +12,15 @@ fn main() {
 }
 
 fn app(cx: Scope) -> Element {
-    let window = dioxus_desktop::use_window(&cx);
+    let window = dioxus_desktop::use_window(cx);
 
     // if you want to make window fullscreen, you need close the resizable.
     // window.set_fullscreen(true);
     // window.set_resizable(false);
 
-    let fullscreen = use_state(&cx, || false);
-    let always_on_top = use_state(&cx, || false);
-    let decorations = use_state(&cx, || false);
+    let fullscreen = use_state(cx, || false);
+    let always_on_top = use_state(cx, || false);
+    let decorations = use_state(cx, || false);
 
     cx.render(rsx!(
         link { href:"https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css", rel:"stylesheet" }
@@ -35,13 +35,13 @@ fn app(cx: Scope) -> Element {
                 nav { class: "md:ml-auto flex flex-wrap items-center text-base justify-center" }
                 button {
                     class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0",
-                    onmousedown: |evt| evt.cancel_bubble(),
+                    onmousedown: |evt| evt.stop_propogation(),
                     onclick: move |_| window.set_minimized(true),
                     "Minimize"
                 }
                 button {
                     class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0",
-                    onmousedown: |evt| evt.cancel_bubble(),
+                    onmousedown: |evt| evt.stop_propogation(),
                     onclick: move |_| {
 
                         window.set_fullscreen(!**fullscreen);
@@ -52,7 +52,7 @@ fn app(cx: Scope) -> Element {
                 }
                 button {
                     class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0",
-                    onmousedown: |evt| evt.cancel_bubble(),
+                    onmousedown: |evt| evt.stop_propogation(),
                     onclick: move |_| window.close(),
                     "Close"
                 }
@@ -66,7 +66,7 @@ fn app(cx: Scope) -> Element {
                 div {
                     button {
                         class: "inline-flex items-center text-white bg-green-500 border-0 py-1 px-3 hover:bg-green-700 rounded",
-                        onmousedown: |evt| evt.cancel_bubble(),
+                        onmousedown: |evt| evt.stop_propogation(),
                         onclick: move |_| {
                             window.set_always_on_top(!always_on_top);
                             always_on_top.set(!always_on_top);
@@ -77,7 +77,7 @@ fn app(cx: Scope) -> Element {
                 div {
                     button {
                         class: "inline-flex items-center text-white bg-blue-500 border-0 py-1 px-3 hover:bg-green-700 rounded",
-                        onmousedown: |evt| evt.cancel_bubble(),
+                        onmousedown: |evt| evt.stop_propogation(),
                         onclick: move |_| {
                             window.set_decorations(!decorations);
                             decorations.set(!decorations);
@@ -88,7 +88,7 @@ fn app(cx: Scope) -> Element {
                 div {
                     button {
                         class: "inline-flex items-center text-white bg-blue-500 border-0 py-1 px-3 hover:bg-green-700 rounded",
-                        onmousedown: |evt| evt.cancel_bubble(),
+                        onmousedown: |evt| evt.stop_propogation(),
                         onclick: move |_| window.set_title("Dioxus Application"),
                         "Change Title"
                     }

+ 2 - 2
examples/window_zoom.rs

@@ -6,9 +6,9 @@ fn main() {
 }
 
 fn app(cx: Scope) -> Element {
-    let window = use_window(&cx);
+    let window = use_window(cx);
 
-    let level = use_state(&cx, || 1.0);
+    let level = use_state(cx, || 1.0);
     cx.render(rsx! {
         input {
             r#type: "number",

+ 1 - 1
examples/xss_safety.rs

@@ -9,7 +9,7 @@ fn main() {
 }
 
 fn app(cx: Scope) -> Element {
-    let contents = use_state(&cx, || {
+    let contents = use_state(cx, || {
         String::from("<script>alert(\"hello world\")</script>")
     });
 

+ 2 - 2
notes/README.md

@@ -233,7 +233,7 @@ use hooks to define state and modify it from within listeners.
 
 ```rust, ignore
 fn app(cx: Scope) -> Element {
-    let name = use_state(&cx, || "world");
+    let name = use_state(cx, || "world");
 
     render!("hello {name}!")
 }
@@ -280,7 +280,7 @@ fn main() {
 }
 
 fn App(cx: Scope) -> Element {
-    let count = use_state(&cx, || 0);
+    let count = use_state(cx, || 0);
 
     cx.render(rsx!(
         div { "Count: {count}" }

+ 99 - 102
notes/README/ZH_CN.md

@@ -1,9 +1,6 @@
-<div align="center">
-  <h1>🌗🚀 Dioxus</h1>
-  <p>
-    <strong>Frontend that scales.</strong>
-  </p>
-</div>
+<p align="center">
+  <img src="../header.svg">
+</p>
 
 <div align="center">
   <!-- Crates version -->
@@ -26,9 +23,7 @@
     <img src="https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg"
       alt="CI status" />
   </a>
-</div>
 
-<div align="center">
   <!--Awesome -->
   <a href="https://github.com/dioxuslabs/awesome-dioxus">
     <img src="https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg" alt="Awesome Page" />
@@ -39,33 +34,27 @@
   </a>
 </div>
 
-
 <div align="center">
   <h3>
-    <a href="https://dioxuslabs.com"> 官网 </a>
+    <a href="https://dioxuslabs.com"> 官 </a>
     <span> | </span>
-    <a href="https://dioxus.mrxzx.info/"> 手册 </a>
+    <a href="https://github.com/DioxusLabs/example-projects"> 代码示例 </a>
+    <span> | </span>
+    <a href="https://dioxuslabs.com/guide"> 开发指南 </a>
     <span> | </span>
-    <a href="https://github.com/DioxusLabs/example-projects"> 示例 </a>
-  </h3>
-</div>
-
-<div align="center">
-  <h4>
     <a href="https://github.com/DioxusLabs/dioxus/blob/master/README.md"> English </a>
     <span> | </span>
-    <a href="https://github.com/DioxusLabs/dioxus/blob/master/README.md"> 中文 </a>
+    <a href="https://github.com/DioxusLabs/dioxus/blob/master/translations/pt-br/README.md"> PT-BR </a>
   </h3>
 </div>
 
-
 <br/>
 
-Dioxus 是一个可移植、高性能的框架,用于在 Rust 中构建跨平台的用户界面。
+Dioxus 是一个可移植的、高性能的、符合人体工程学的框架,使用 Rust 语言构建跨平台的用户界面。
 
 ```rust
 fn app(cx: Scope) -> Element {
-    let mut count = use_state(&cx, || 0);
+    let mut count = use_state(cx, || 0);
 
     cx.render(rsx! {
         h1 { "High-Five counter: {count}" }
@@ -75,103 +64,111 @@ fn app(cx: Scope) -> Element {
 }
 ```
 
-Dioxus 可用于制作 网页程序、桌面应用、静态站点、移动端应用。
-
-Dioxus 为不同的平台都提供了很好的开发文档。
-
-如果你会使用 React ,那 Dioxus 对你来说会很简单。 
-
-### 项目特点:
-- 对桌面应用的原生支持。
-- 强大的状态管理工具。
-- 支持所有 HTML 标签,监听器和事件。
-- 超高的内存使用率,稳定的组件分配器。
-- 多通道异步调度器,超强的异步支持。
-- 更多信息请查阅: [版本发布文档](https://dioxuslabs.com/blog/introducing-dioxus/).
-
-### 示例
-
-本项目中的所有例子都是 `桌面应用` 程序,请使用 `cargo run --example XYZ` 运行这些例子。
+Dioxus 可用于生成 网页前端、桌面应用、静态网站、移动端应用、TUI程序、等多类平台应用。
 
-```
-cargo run --example EXAMPLE
-```
+如果你能够熟悉使用 React 框架,那 Dioxus 对你来说将非常简单。
 
-## 进入学习
+## 独特的特性:
+- 桌面程序完全基于本地环境运行(并非 Electron 的封装)
+- 符合人体工程学的设计以及拥有强大的状态管理
+- 全面的内联文档 - 包含所有 HTML 元素、监听器 和 事件 指南。
+- 极快的运行效率和极高的内存效率
+- 智能项目热更新和高效的项目迭代
+- 一流的异步支持🔥
+- 更多内容请查看 [版本发布信息](https://dioxuslabs.com/blog/introducing-dioxus/).
 
-<table style="width:100%" align="center">
-    <tr >
-        <th><a href="https://dioxuslabs.com/guide/">教程</a></th>
-        <th><a href="https://dioxuslabs.com/reference/web">网页端</a></th>
-        <th><a href="https://dioxuslabs.com/reference/desktop/">桌面端</a></th>
-        <th><a href="https://dioxuslabs.com/reference/ssr/">SSR</a></th>
-        <th><a href="https://dioxuslabs.com/reference/mobile/">移动端</a></th>
-        <th><a href="https://dioxuslabs.com/guide/concepts/managing_state.html">状态管理</a></th>
+## 已支持的平台
+<div align="center">
+  <table style="width:100%">
     <tr>
-</table>
-
-
-## Dioxus 项目
-
-| 文件浏览器 (桌面应用)                                                                                                                                                           | WiFi 扫描器 (桌面应用)                                                                                                                                                                 | Todo管理 (所有平台)                                                                                                                                                     | 商城系统 (SSR/liveview)                                                                                                                                                               |
-| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| [![File Explorer](https://github.com/DioxusLabs/example-projects/raw/master/file-explorer/image.png)](https://github.com/DioxusLabs/example-projects/blob/master/file-explorer) | [![Wifi Scanner Demo](https://github.com/DioxusLabs/example-projects/raw/master/wifi-scanner/demo_small.png)](https://github.com/DioxusLabs/example-projects/blob/master/wifi-scanner) | [![TodoMVC example](https://github.com/DioxusLabs/example-projects/raw/master/todomvc/example.png)](https://github.com/DioxusLabs/example-projects/blob/master/todomvc) | [![E-commerce Example](https://github.com/DioxusLabs/example-projects/raw/master/ecommerce-site/demo.png)](https://github.com/DioxusLabs/example-projects/blob/master/ecommerce-site) |
-
-
-查看 [awesome-dioxus](https://github.com/DioxusLabs/awesome-dioxus) 查看更多有趣(~~NiuBi~~)的项目!
-
-## 为什么使用 Dioxus 和 Rust ?
-
-TypeScript 是一个不错的 JavaScript 拓展集,但它仍然算是 JavaScript。
-
-TS 代码运行效率不高,而且有大量的配置项。
+      <td><em>网站项目</em></td>
+      <td>
+        <ul>
+          <li>使用 WebAssembly 直接对 DOM 进行渲染</li>
+          <li>为 SSR 提供预渲染或作为客户端使用</li>
+          <li>简单的 "Hello World" 仅仅 65kb, 媲美 React 框架</li>
+          <li>CLI 提供热更新支持,方便项目快速迭代</li>
+        </ul>
+      </td>
+    </tr>
+    <tr>
+      <td><em>桌面应用</em></td>
+      <td>
+        <ul>
+          <li>使用 Webview 进行渲染 或 使用 WGPU 和 Skia(试验性的)</li>
+          <li>无多余配置,使用 `cargo build` 即可快速构建</li>
+          <li>对原生系统的全面支持</li>
+          <li>支持 Macos、Linux、Windows 等系统,极小的二进制文件</li>
+        </ul>
+      </td>
+    </tr>
+    <tr>
+      <td><em>移动端应用</em></td>
+      <td>
+        <ul>
+          <li>使用 Webview 进行渲染 或 使用 WGPU 和 Skia(试验性的)</li>
+          <li>支持 IOS 和 安卓系统</li>
+          <li><em>显著的</em> 性能强于 React Native 框架 </li>
+        </ul>
+      </td>
+    </tr>
+    <tr>
+      <td><em>Liveview</em></td>
+      <td>
+        <ul>
+          <li>使用服务器渲染组件与应用程序</li>
+          <li>与受欢迎的后端框架进行融合(Axum、Wrap)</li>
+          <li>及低的延迟</li>
+        </ul>
+      </td>
+    </tr>
+    <tr>
+      <td><em>终端程序</em></td>
+      <td>
+        <ul>
+          <li>在终端程序中渲染,类似于: <a href="https://github.com/vadimdemedes/ink"> ink.js</a></li>
+          <li>支持 CSS 相关模型(类似于浏览器内的)</li>
+          <li>Built-in widgets like text input, buttons, and focus system</li>
+        </ul>
+      </td>
+    </tr>
+  </table>
+</div>
 
-相比之下,Dioxus 使用 Rust 编写将大大的提高效能。
+## Why Dioxus?
 
-使用 Rust 开发,我们能获得:
+目前有非常多的应用开发选择,为什么偏偏要选择 Dioxus 呢?
 
-- 静态类型支持。
-- 变量默认不变性。
-- 简单直观的模块系统。
-- 内部集成的文档系统。
-- 先进的模式匹配系统。
-- 简洁、高效、强大的迭代器。
-- 内置的 单元测试 / 集成测试。
-- 优秀的异常处理系统。
-- 强大且健全的标准库。
-- 灵活的 `宏` 系统。
-- 使用 `crates.io` 管理包。
+首先,Dioxus将开发者的经验放在首位。这反映在 Dioxus 特有的各种功能上。
 
-Dioxus 能为开发者提供的:
+- 自动格式化 RSX 格式代码,并拥有 VSCode 插件作为支持。
+- 热加载基于 RSX 代码解析器,同时支持桌面程序和网页程序。
+- 强调文档的重要性,我们对所有 HTML 元素都提供文档支持。
 
-- 安全使用数据结构。
-- 安全的错误处理结果。
-- 拥有原生移动端的性能。
-- 直接访问系统的IO层。
+Dioxus 也是一个可扩展化的平台。
 
-Dioxus 使 Rust 应用程序的编写速度和 React 应用程序一样快,但提供了更多的健壮性,让团队能在更短的时间内做出强大功能。
+- 通过实现一个非常简单的优化堆栈机,轻松构建新的渲染器。
+- 构建并分享开发者自定义的组件代码。
 
-### 不建议使用 Dioxus 的情况?
+Dioxus 那么优秀,但什么时候它不适合我呢?
+- 它还没有完全成熟。api仍在变化,可能会出现故障(尽管我们试图避免)
+- 您需要运行在 no-std 的环境之中。
+- 你不喜欢使用 React-like 的方式构建 UI 项目。
 
-您不该在这些情况下使用 Dioxus :
 
-- 您不喜欢类似 React 的开发风格。
-- 您需要一个 `no-std` 的渲染器。
-- 您希望应用运行在 `不支持 Wasm 或 asm.js` 的浏览器。
-- 您需要一个 `Send + Sync` UI 解决方案(目前不支持)。
+## 贡献代码
+- 在我们的 [问题追踪](https://github.com/dioxuslabs/dioxus/issues) 中汇报你遇到的问题。
+- 加入我们的 Discord 与我们交流。
 
-### 项目生态
 
-想要加入我们一起为 Dioxus 生态努力吗?有很多项目都能在您的帮助下获得改变:
+<a href="https://github.com/dioxuslabs/dioxus/graphs/contributors">
+  <img src="https://contrib.rocks/image?repo=dioxuslabs/dioxus&max=30&columns=10" />
+</a>
 
-- [TUI 渲染器](https://github.com/dioxusLabs/rink)
-- [CLI 开发工具](https://github.com/dioxusLabs/cli)
-- [官网及文档](https://github.com/dioxusLabs/docsite)
-- 动态网站 及 Web 服务器
-- 资源系统
+## 开源协议
 
-## 协议
+本项目使用 [MIT license].
 
-这个项目使用 [MIT 协议].
+[mit license]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT
 
-[MIT 协议]: https://github.com/dioxuslabs/dioxus/blob/master/LICENSE
+除非您另有明确声明,否则有意提交的任何贡献将被授权为 MIT 协议,没有任何附加条款或条件。

+ 2 - 2
notes/SOLVEDPROBLEMS.md

@@ -406,8 +406,8 @@ enum Patch {
 ```
 
 ```rust
-let node_ref = use_node_ref(&cx);
-use_effect(&cx, || {
+let node_ref = use_node_ref(cx);
+use_effect(cx, || {
 
 }, []);
 div { ref: node_ref,

+ 5 - 1
packages/core-macro/src/inlineprops.rs

@@ -9,6 +9,7 @@ use syn::{
 pub struct InlinePropsBody {
     pub attrs: Vec<Attribute>,
     pub vis: syn::Visibility,
+    pub maybe_async: Option<Token![async]>,
     pub fn_token: Token![fn],
     pub ident: Ident,
     pub cx_token: Box<Pat>,
@@ -25,6 +26,7 @@ pub struct InlinePropsBody {
 impl Parse for InlinePropsBody {
     fn parse(input: ParseStream) -> Result<Self> {
         let attrs: Vec<Attribute> = input.call(Attribute::parse_outer)?;
+        let maybe_async: Option<Token![async]> = input.parse().ok();
         let vis: Visibility = input.parse()?;
 
         let fn_token = input.parse()?;
@@ -57,6 +59,7 @@ impl Parse for InlinePropsBody {
 
         Ok(Self {
             vis,
+            maybe_async,
             fn_token,
             ident,
             generics,
@@ -84,6 +87,7 @@ impl ToTokens for InlinePropsBody {
             block,
             cx_token,
             attrs,
+            maybe_async,
             ..
         } = self;
 
@@ -151,7 +155,7 @@ impl ToTokens for InlinePropsBody {
             }
 
             #(#attrs)*
-            #vis fn #ident #fn_generics (#cx_token: Scope<#scope_lifetime #struct_name #generics>) #output
+            #maybe_async #vis fn #ident #fn_generics (#cx_token: Scope<#scope_lifetime #struct_name #generics>) #output
             #where_clause
             {
                 let #struct_name { #(#field_names),* } = &cx.props;

+ 4 - 26
packages/core-macro/src/lib.rs

@@ -11,8 +11,7 @@ use dioxus_rsx as rsx;
 #[proc_macro]
 pub fn format_args_f(input: TokenStream) -> TokenStream {
     use rsx::*;
-    let item = parse_macro_input!(input as IfmtInput);
-    format_args_f_impl(item)
+    format_args_f_impl(parse_macro_input!(input as IfmtInput))
         .unwrap_or_else(|err| err.to_compile_error())
         .into()
 }
@@ -40,19 +39,6 @@ pub fn rsx(s: TokenStream) -> TokenStream {
     }
 }
 
-/// 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()
-        }
-    }
-}
-
 /// The render! macro makes it easy for developers to write jsx-style markup in their components.
 ///
 /// The render macro automatically renders rsx - making it unhygenic.
@@ -65,18 +51,10 @@ pub fn rsx_without_templates(s: TokenStream) -> TokenStream {
 pub fn render(s: TokenStream) -> TokenStream {
     match syn::parse::<rsx::CallBody>(s) {
         Err(err) => err.to_compile_error().into(),
-        Ok(body) => {
-            let mut inner = proc_macro2::TokenStream::new();
-            body.to_tokens_without_lazynodes(&mut inner);
-            quote::quote! {
-                {
-                    let __cx = NodeFactory::new(&cx.scope);
-                    Some(#inner)
-                }
-            }
+        Ok(mut body) => {
+            body.inline_cx = true;
+            body.into_token_stream().into()
         }
-        .into_token_stream()
-        .into(),
     }
 }
 

+ 1 - 1
packages/core-macro/src/props/mod.rs

@@ -194,7 +194,7 @@ mod field_info {
                 // children field is automatically defaulted to None
                 if name == "children" {
                     builder_attr.default =
-                        Some(syn::parse(quote!(Default::default()).into()).unwrap());
+                        Some(syn::parse(quote!(::dioxus::core::VNode::empty()).into()).unwrap());
                 }
 
                 // auto detect optional

+ 4 - 17
packages/core/Cargo.toml

@@ -18,40 +18,27 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
 bumpalo = { version = "3.6", features = ["collections", "boxed"] }
 
 # faster hashmaps
-rustc-hash = "1.1.0"
+fxhash = "0.2"
 
 # Used in diffing
 longest-increasing-subsequence = "0.1.0"
 
 futures-util = { version = "0.3", default-features = false }
 
-smallvec = "1.6"
-
 slab = "0.4"
 
 futures-channel = "0.3.21"
 
-# internally used
-log = "0.4"
-
-# used for noderefs
-once_cell = "1.8"
-
+indexmap = "1.7"
 
 # Serialize the Edits for use in Webview/Liveview instances
 serde = { version = "1", features = ["derive"], optional = true }
-
-# todo: I want to get rid of this
-backtrace = { version = "0.3" }
-
-# allows cloing trait objects
-dyn-clone = "1.0.9"
+anyhow = "1.0.66"
 
 [dev-dependencies]
+tokio = { version = "*", features = ["full"] }
 dioxus = { path = "../dioxus" }
 
 [features]
 default = []
 serialize = ["serde"]
-debug_vdom = []
-hot-reload = []

+ 64 - 46
packages/core/README.md

@@ -1,52 +1,84 @@
-# Dioxus-core
+# dioxus-core
 
-This is the core crate for the Dioxus Virtual DOM. This README will focus on the technical design and layout of this Virtual DOM implementation. If you want to read more about using Dioxus, then check out the Dioxus crate, documentation, and website.
+dioxus-core is a fast and featureful VirtualDom implementation written in and for Rust.
 
-To build new apps with Dioxus or to extend the ecosystem with new hooks or components, use the higher-level `dioxus` crate with the appropriate feature flags.
+# Features
 
+- Functions as components
+- Hooks for local state
+- Task pool for spawning futures
+- Template-based architecture
+- Asynchronous components
+- Suspense boundaries
+- Error boundaries through the `anyhow` crate
+- Customizable memoization
+
+If just starting out, check out the Guides first.
+
+# General Theory
+
+The dioxus-core `VirtualDom` object is built around the concept of a `Template`. Templates describe a layout tree known at compile time with dynamic parts filled at runtime.
+
+Each component in the VirtualDom works as a dedicated render loop where re-renders are triggered by events external to the VirtualDom, or from the components themselves.
+
+When each component re-renders, it must return an `Element`. In Dioxus, the `Element` type is an alias for `Result<VNode>`. Between two renders, Dioxus compares the inner `VNode` object, and calculates the differences of the dynamic portions of each internal `Template`. If any attributes or elements are different between the old layout and new layout, Dioxus will write modifications to the `Mutations` object.
+
+Dioxus expects the target renderer to save its nodes in a list. Each element is given a numerical ID which can be used to directly index into that list for O(1) lookups.
+
+# Usage
+
+All Dioxus apps start as just a function that takes the [`Scope`] object and returns an [`Element`].
+
+The `dioxus` crate exports the `rsx` macro which transforms a helpful, simpler syntax of Rust into the logic required to build Templates.
+
+First, start with your app:
 
 ```rust, ignore
 fn app(cx: Scope) -> Element {
-    render!(div { "hello world" })
+    cx.render(rsx!( div { "hello world" } ))
 }
+```
 
-fn main() {
-    let mut renderer = SomeRenderer::new();
+Then, we'll want to create a new VirtualDom using this app as the root component.
 
-    // Creating a new virtualdom from a component
-    let mut dom = VirtualDom::new(app);
+```rust, ignore
+let mut dom = VirtualDom::new(app);
+```
 
-    // Patching the renderer with the changes to draw the screen
-    let edits = dom.rebuild();
-    renderer.apply(edits);
+To build the app into a stream of mutations, we'll use [`VirtualDom::rebuild`]:
 
-    // Injecting events
-    dom.handle_message(SchedulerMsg::Event(UserEvent {
-        scope_id: None,
-        priority: EventPriority::High,
-        element: ElementId(0),
-        name: "onclick",
-        data: Arc::new(()),
-    }));
+```rust, ignore
+let mutations = dom.rebuild();
 
-    // polling asynchronously
-    dom.wait_for_work().await;
+apply_edits_to_real_dom(mutations);
+```
 
-    // working with a deadline
-    if let Some(edits) = dom.work_with_deadline(|| false) {
-        renderer.apply(edits);
-    }
+We can then wait for any asynchronous components or pending futures using the `wait_for_work()` method. If we have a deadline, then we can use render_with_deadline instead:
 
-    // getting state of scopes
-    let scope = dom.get_scope(ScopeId(0)).unwrap();
+```rust, ignore
+// Wait for the dom to be marked dirty internally
+dom.wait_for_work().await;
+
+// Or wait for a deadline and then collect edits
+dom.render_with_deadline(tokio::time::sleep(Duration::from_millis(16)));
+```
+
+If an event occurs from outside the virtualdom while waiting for work, then we can cancel the wait using a `select!` block and inject the event.
 
-    // iterating through the tree
-    match scope.root_node() {
-        VNodes::Text(vtext) => dbg!(vtext),
-        VNodes::Element(vel) => dbg!(vel),
-        _ => todo!()
+```rust, ignore
+loop {
+    select! {
+        evt = real_dom.event() => dom.handle_event("click", evt.data, evt.element, evt.bubbles),
+        _ = dom.wait_for_work() => {}
     }
+
+    // Render any work without blocking the main thread for too long
+    let mutations = dom.render_with_deadline(tokio::time::sleep(Duration::from_millis(10)));
+
+    // And then apply the edits
+    real_dom.apply(mutations);
 }
+
 ```
 
 ## Internals
@@ -82,17 +114,3 @@ The final implementation of Dioxus must:
 - Support server-side-rendering (SSR). VNodes should render to a string that can be served via a web server.
 - Be "live". Components should be able to be both server rendered and client rendered without needing frontend APIs.
 - Be modular. Components and hooks should be work anywhere without worrying about target platform.
-
-
-## Safety
-
-Dioxus uses unsafe. The design of Dioxus *requires* unsafe (self-referential trees).
-
-All of our test suite passes MIRI without errors.
-
-Dioxus deals with arenas, lifetimes, asynchronous tasks, custom allocators, pinning, and a lot more foundational low-level work that is very difficult to implement with 0 unsafe.
-
-If you don't want to use a crate that uses unsafe, then this crate is not for you.
-
-However, we are always interested in decreasing the scope of the core VirtualDom to make it easier to review. We'd be happy to welcome PRs that can eliminate unsafe code while still upholding the numerous invariants required to execute certain features.
-

+ 5 - 0
packages/core/architecture.md

@@ -114,6 +114,11 @@ Some essential reading:
 - https://web.dev/rail/
 - https://indepth.dev/posts/1008/inside-fiber-in-depth-overview-of-the-new-reconciliation-algorithm-in-react
 
+# Templates
+
+If everything is a template, then we'll have the idea that the only children can b Templates
+
+
 # What's going on?
 
 Dioxus is a framework for "user experience" - not just "user interfaces." Part of the "experience" is keeping the UI

+ 73 - 0
packages/core/src/any_props.rs

@@ -0,0 +1,73 @@
+use std::marker::PhantomData;
+
+use crate::{
+    innerlude::Scoped,
+    nodes::{ComponentReturn, RenderReturn},
+    scopes::{Scope, ScopeState},
+    Element,
+};
+
+/// A trait that essentially allows VComponentProps to be used generically
+///
+/// # Safety
+///
+/// This should not be implemented outside this module
+pub(crate) unsafe trait AnyProps<'a> {
+    fn props_ptr(&self) -> *const ();
+    fn render(&'a self, bump: &'a ScopeState) -> RenderReturn<'a>;
+    unsafe fn memoize(&self, other: &dyn AnyProps) -> bool;
+}
+
+pub(crate) struct VProps<'a, P, A, F: ComponentReturn<'a, A> = Element<'a>> {
+    pub render_fn: fn(Scope<'a, P>) -> F,
+    pub memo: unsafe fn(&P, &P) -> bool,
+    pub props: P,
+    _marker: PhantomData<A>,
+}
+
+impl<'a, P, A, F> VProps<'a, P, A, F>
+where
+    F: ComponentReturn<'a, A>,
+{
+    pub(crate) fn new(
+        render_fn: fn(Scope<'a, P>) -> F,
+        memo: unsafe fn(&P, &P) -> bool,
+        props: P,
+    ) -> Self {
+        Self {
+            render_fn,
+            memo,
+            props,
+            _marker: PhantomData,
+        }
+    }
+}
+
+unsafe impl<'a, P, A, F> AnyProps<'a> for VProps<'a, P, A, F>
+where
+    F: ComponentReturn<'a, A>,
+{
+    fn props_ptr(&self) -> *const () {
+        &self.props as *const _ as *const ()
+    }
+
+    // Safety:
+    // this will downcast the other ptr as our swallowed type!
+    // you *must* make this check *before* calling this method
+    // if your functions are not the same, then you will downcast a pointer into a different type (UB)
+    unsafe fn memoize(&self, other: &dyn AnyProps) -> bool {
+        let real_other: &P = &*(other.props_ptr() as *const _ as *const P);
+        let real_us: &P = &*(self.props_ptr() as *const _ as *const P);
+        (self.memo)(real_us, real_other)
+    }
+
+    fn render(&'a self, cx: &'a ScopeState) -> RenderReturn<'a> {
+        let scope: &mut Scoped<P> = cx.bump().alloc(Scoped {
+            props: &self.props,
+            scope: cx,
+        });
+
+        // Call the render function directly
+        (self.render_fn)(scope).into_return(cx)
+    }
+}

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

@@ -1,642 +0,0 @@
-use std::fmt::{Arguments, Formatter};
-
-use bumpalo::Bump;
-use dyn_clone::{clone_box, DynClone};
-
-/// Possible values for an attribute
-// trying to keep values at 3 bytes
-#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
-#[cfg_attr(feature = "serialize", serde(untagged))]
-#[derive(Clone, Debug, PartialEq)]
-#[allow(missing_docs)]
-pub enum AttributeValue<'a> {
-    Text(&'a 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(&'a [u8]),
-    Any(ArbitraryAttributeValue<'a>),
-}
-
-/// A value that can be converted into an attribute value
-pub trait IntoAttributeValue<'a> {
-    /// Convert into an attribute value
-    fn into_value(self, bump: &'a Bump) -> AttributeValue<'a>;
-}
-
-impl<'a> IntoAttributeValue<'a> for u32 {
-    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
-        AttributeValue::Uint32(self)
-    }
-}
-
-impl<'a> IntoAttributeValue<'a> for u64 {
-    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
-        AttributeValue::Uint64(self)
-    }
-}
-
-impl<'a> IntoAttributeValue<'a> for i32 {
-    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
-        AttributeValue::Int32(self)
-    }
-}
-
-impl<'a> IntoAttributeValue<'a> for i64 {
-    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
-        AttributeValue::Int64(self)
-    }
-}
-
-impl<'a> IntoAttributeValue<'a> for f32 {
-    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
-        AttributeValue::Float32(self)
-    }
-}
-
-impl<'a> IntoAttributeValue<'a> for f64 {
-    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
-        AttributeValue::Float64(self)
-    }
-}
-
-impl<'a> IntoAttributeValue<'a> for bool {
-    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
-        AttributeValue::Bool(self)
-    }
-}
-
-impl<'a> IntoAttributeValue<'a> for &'a str {
-    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
-        AttributeValue::Text(self)
-    }
-}
-
-impl<'a> IntoAttributeValue<'a> for Arguments<'_> {
-    fn into_value(self, bump: &'a Bump) -> AttributeValue<'a> {
-        use bumpalo::core_alloc::fmt::Write;
-        let mut str_buf = bumpalo::collections::String::new_in(bump);
-        str_buf.write_fmt(self).unwrap();
-        AttributeValue::Text(str_buf.into_bump_str())
-    }
-}
-
-impl<'a> IntoAttributeValue<'a> for &'a [u8] {
-    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
-        AttributeValue::Bytes(self)
-    }
-}
-
-impl<'a> IntoAttributeValue<'a> for (f32, f32, f32) {
-    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
-        AttributeValue::Vec3Float(self.0, self.1, self.2)
-    }
-}
-
-impl<'a> IntoAttributeValue<'a> for (i32, i32, i32) {
-    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
-        AttributeValue::Vec3Int(self.0, self.1, self.2)
-    }
-}
-
-impl<'a> IntoAttributeValue<'a> for (u32, u32, u32) {
-    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
-        AttributeValue::Vec3Uint(self.0, self.1, self.2)
-    }
-}
-
-impl<'a> IntoAttributeValue<'a> for (f32, f32, f32, f32) {
-    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
-        AttributeValue::Vec4Float(self.0, self.1, self.2, self.3)
-    }
-}
-
-impl<'a> IntoAttributeValue<'a> for (i32, i32, i32, i32) {
-    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
-        AttributeValue::Vec4Int(self.0, self.1, self.2, self.3)
-    }
-}
-
-impl<'a> IntoAttributeValue<'a> for (u32, u32, u32, u32) {
-    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
-        AttributeValue::Vec4Uint(self.0, self.1, self.2, self.3)
-    }
-}
-
-impl<'a, T> IntoAttributeValue<'a> for &'a T
-where
-    T: AnyClone + PartialEq,
-{
-    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
-        AttributeValue::Any(ArbitraryAttributeValue {
-            value: self,
-            cmp: |a, b| {
-                if let Some(a) = a.as_any().downcast_ref::<T>() {
-                    if let Some(b) = b.as_any().downcast_ref::<T>() {
-                        a == b
-                    } else {
-                        false
-                    }
-                } else {
-                    false
-                }
-            },
-        })
-    }
-}
-
-// todo
-#[allow(missing_docs)]
-impl<'a> AttributeValue<'a> {
-    pub fn is_truthy(&self) -> bool {
-        match self {
-            AttributeValue::Text(t) => *t == "true",
-            AttributeValue::Bool(t) => *t,
-            _ => false,
-        }
-    }
-
-    pub fn is_falsy(&self) -> bool {
-        match self {
-            AttributeValue::Text(t) => *t == "false",
-            AttributeValue::Bool(t) => !(*t),
-            _ => false,
-        }
-    }
-}
-
-impl<'a> std::fmt::Display for AttributeValue<'a> {
-    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
-        match self {
-            AttributeValue::Text(a) => write!(f, "{}", a),
-            AttributeValue::Float32(a) => write!(f, "{}", a),
-            AttributeValue::Float64(a) => write!(f, "{}", a),
-            AttributeValue::Int32(a) => write!(f, "{}", a),
-            AttributeValue::Int64(a) => write!(f, "{}", a),
-            AttributeValue::Uint32(a) => write!(f, "{}", a),
-            AttributeValue::Uint64(a) => write!(f, "{}", a),
-            AttributeValue::Bool(a) => write!(f, "{}", a),
-            AttributeValue::Vec3Float(_, _, _) => todo!(),
-            AttributeValue::Vec3Int(_, _, _) => todo!(),
-            AttributeValue::Vec3Uint(_, _, _) => todo!(),
-            AttributeValue::Vec4Float(_, _, _, _) => todo!(),
-            AttributeValue::Vec4Int(_, _, _, _) => todo!(),
-            AttributeValue::Vec4Uint(_, _, _, _) => todo!(),
-            AttributeValue::Bytes(a) => write!(f, "{:?}", a),
-            AttributeValue::Any(a) => write!(f, "{:?}", a),
-        }
-    }
-}
-
-#[derive(Clone, Copy)]
-#[allow(missing_docs)]
-pub struct ArbitraryAttributeValue<'a> {
-    pub value: &'a dyn AnyClone,
-    pub cmp: fn(&dyn AnyClone, &dyn AnyClone) -> bool,
-}
-
-impl PartialEq for ArbitraryAttributeValue<'_> {
-    fn eq(&self, other: &Self) -> bool {
-        (self.cmp)(self.value, other.value)
-    }
-}
-
-impl std::fmt::Debug for ArbitraryAttributeValue<'_> {
-    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
-        f.debug_struct("ArbitraryAttributeValue").finish()
-    }
-}
-
-#[cfg(feature = "serialize")]
-impl<'a> serde::Serialize for ArbitraryAttributeValue<'a> {
-    fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
-    where
-        S: serde::Serializer,
-    {
-        panic!("ArbitraryAttributeValue should not be serialized")
-    }
-}
-#[cfg(feature = "serialize")]
-impl<'de, 'a> serde::Deserialize<'de> for &'a ArbitraryAttributeValue<'a> {
-    fn deserialize<D>(_deserializer: D) -> Result<Self, D::Error>
-    where
-        D: serde::Deserializer<'de>,
-    {
-        panic!("ArbitraryAttributeValue is not deserializable!")
-    }
-}
-#[cfg(feature = "serialize")]
-impl<'de, 'a> serde::Deserialize<'de> for ArbitraryAttributeValue<'a> {
-    fn deserialize<D>(_deserializer: D) -> Result<Self, D::Error>
-    where
-        D: serde::Deserializer<'de>,
-    {
-        panic!("ArbitraryAttributeValue is not deserializable!")
-    }
-}
-
-/// A clone, sync and send version of `Any`
-// we only need the Sync + Send bound when hot reloading is enabled
-#[cfg(any(feature = "hot-reload", debug_assertions))]
-pub trait AnyClone: std::any::Any + DynClone + Send + Sync {
-    fn as_any(&self) -> &dyn std::any::Any;
-}
-#[cfg(not(any(feature = "hot-reload", debug_assertions)))]
-pub trait AnyClone: std::any::Any + DynClone {
-    fn as_any(&self) -> &dyn std::any::Any;
-}
-
-#[cfg(any(feature = "hot-reload", debug_assertions))]
-impl<T: std::any::Any + DynClone + Send + Sync> AnyClone for T {
-    fn as_any(&self) -> &dyn std::any::Any {
-        self
-    }
-}
-#[cfg(not(any(feature = "hot-reload", debug_assertions)))]
-impl<T: std::any::Any + DynClone> AnyClone for T {
-    fn as_any(&self) -> &dyn std::any::Any {
-        self
-    }
-}
-
-dyn_clone::clone_trait_object!(AnyClone);
-
-#[derive(Clone)]
-#[allow(missing_docs)]
-pub struct OwnedArbitraryAttributeValue {
-    pub value: Box<dyn AnyClone>,
-    pub cmp: fn(&dyn AnyClone, &dyn AnyClone) -> bool,
-}
-
-impl PartialEq for OwnedArbitraryAttributeValue {
-    fn eq(&self, other: &Self) -> bool {
-        (self.cmp)(&*self.value, &*other.value)
-    }
-}
-
-impl std::fmt::Debug for OwnedArbitraryAttributeValue {
-    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
-        f.debug_struct("OwnedArbitraryAttributeValue").finish()
-    }
-}
-
-#[cfg(feature = "serialize")]
-impl serde::Serialize for OwnedArbitraryAttributeValue {
-    fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
-    where
-        S: serde::Serializer,
-    {
-        panic!("OwnedArbitraryAttributeValue should not be serialized")
-    }
-}
-#[cfg(feature = "serialize")]
-impl<'de> serde::Deserialize<'de> for &OwnedArbitraryAttributeValue {
-    fn deserialize<D>(_deserializer: D) -> Result<Self, D::Error>
-    where
-        D: serde::Deserializer<'de>,
-    {
-        panic!("OwnedArbitraryAttributeValue is not deserializable!")
-    }
-}
-#[cfg(feature = "serialize")]
-impl<'de> serde::Deserialize<'de> for OwnedArbitraryAttributeValue {
-    fn deserialize<D>(_deserializer: D) -> Result<Self, D::Error>
-    where
-        D: serde::Deserializer<'de>,
-    {
-        panic!("OwnedArbitraryAttributeValue is not deserializable!")
-    }
-}
-
-// todo
-#[allow(missing_docs)]
-impl<'a> AttributeValue<'a> {
-    pub fn as_text(&self) -> Option<&'a str> {
-        match self {
-            AttributeValue::Text(s) => Some(s),
-            _ => None,
-        }
-    }
-
-    pub fn as_float32(&self) -> Option<f32> {
-        match self {
-            AttributeValue::Float32(f) => Some(*f),
-            _ => None,
-        }
-    }
-
-    pub fn as_float64(&self) -> Option<f64> {
-        match self {
-            AttributeValue::Float64(f) => Some(*f),
-            _ => None,
-        }
-    }
-
-    pub fn as_int32(&self) -> Option<i32> {
-        match self {
-            AttributeValue::Int32(i) => Some(*i),
-            _ => None,
-        }
-    }
-
-    pub fn as_int64(&self) -> Option<i64> {
-        match self {
-            AttributeValue::Int64(i) => Some(*i),
-            _ => None,
-        }
-    }
-
-    pub fn as_uint32(&self) -> Option<u32> {
-        match self {
-            AttributeValue::Uint32(i) => Some(*i),
-            _ => None,
-        }
-    }
-
-    pub fn as_uint64(&self) -> Option<u64> {
-        match self {
-            AttributeValue::Uint64(i) => Some(*i),
-            _ => None,
-        }
-    }
-
-    pub fn as_bool(&self) -> Option<bool> {
-        match self {
-            AttributeValue::Bool(b) => Some(*b),
-            _ => None,
-        }
-    }
-
-    pub fn as_vec3_float(&self) -> Option<(f32, f32, f32)> {
-        match self {
-            AttributeValue::Vec3Float(x, y, z) => Some((*x, *y, *z)),
-            _ => None,
-        }
-    }
-
-    pub fn as_vec3_int(&self) -> Option<(i32, i32, i32)> {
-        match self {
-            AttributeValue::Vec3Int(x, y, z) => Some((*x, *y, *z)),
-            _ => None,
-        }
-    }
-
-    pub fn as_vec3_uint(&self) -> Option<(u32, u32, u32)> {
-        match self {
-            AttributeValue::Vec3Uint(x, y, z) => Some((*x, *y, *z)),
-            _ => None,
-        }
-    }
-
-    pub fn as_vec4_float(&self) -> Option<(f32, f32, f32, f32)> {
-        match self {
-            AttributeValue::Vec4Float(x, y, z, w) => Some((*x, *y, *z, *w)),
-            _ => None,
-        }
-    }
-
-    pub fn as_vec4_int(&self) -> Option<(i32, i32, i32, i32)> {
-        match self {
-            AttributeValue::Vec4Int(x, y, z, w) => Some((*x, *y, *z, *w)),
-            _ => None,
-        }
-    }
-
-    pub fn as_vec4_uint(&self) -> Option<(u32, u32, u32, u32)> {
-        match self {
-            AttributeValue::Vec4Uint(x, y, z, w) => Some((*x, *y, *z, *w)),
-            _ => None,
-        }
-    }
-
-    pub fn as_bytes(&self) -> Option<&[u8]> {
-        match self {
-            AttributeValue::Bytes(b) => Some(b),
-            _ => None,
-        }
-    }
-
-    pub fn as_any(&self) -> Option<&'a ArbitraryAttributeValue> {
-        match self {
-            AttributeValue::Any(a) => Some(a),
-            _ => None,
-        }
-    }
-}
-
-/// 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(OwnedArbitraryAttributeValue),
-}
-
-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(a) => OwnedAttributeValue::Any(OwnedArbitraryAttributeValue {
-                value: clone_box(a.value),
-                cmp: a.cmp,
-            }),
-        }
-    }
-}
-
-// 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,
-        }
-    }
-}

+ 163 - 0
packages/core/src/arena.rs

@@ -0,0 +1,163 @@
+use crate::{nodes::RenderReturn, nodes::VNode, virtual_dom::VirtualDom, DynamicNode, ScopeId};
+use bumpalo::boxed::Box as BumpBox;
+
+/// An Element's unique identifier.
+///
+/// `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.
+#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
+pub struct ElementId(pub usize);
+
+pub(crate) struct ElementRef {
+    // the pathway of the real element inside the template
+    pub path: ElementPath,
+
+    // The actual template
+    pub template: *const VNode<'static>,
+}
+
+#[derive(Clone, Copy)]
+pub enum ElementPath {
+    Deep(&'static [u8]),
+    Root(usize),
+}
+
+impl ElementRef {
+    pub(crate) fn null() -> Self {
+        Self {
+            template: std::ptr::null_mut(),
+            path: ElementPath::Root(0),
+        }
+    }
+}
+
+impl VirtualDom {
+    pub(crate) fn next_element(&mut self, template: &VNode, path: &'static [u8]) -> ElementId {
+        self.next(template, ElementPath::Deep(path))
+    }
+
+    pub(crate) fn next_root(&mut self, template: &VNode, path: usize) -> ElementId {
+        self.next(template, ElementPath::Root(path))
+    }
+
+    fn next(&mut self, template: &VNode, path: ElementPath) -> ElementId {
+        let entry = self.elements.vacant_entry();
+        let id = entry.key();
+
+        entry.insert(ElementRef {
+            template: template as *const _ as *mut _,
+            path,
+        });
+        ElementId(id)
+    }
+
+    pub(crate) fn reclaim(&mut self, el: ElementId) {
+        self.try_reclaim(el)
+            .unwrap_or_else(|| panic!("cannot reclaim {:?}", el));
+    }
+
+    pub(crate) fn try_reclaim(&mut self, el: ElementId) -> Option<ElementRef> {
+        if el.0 == 0 {
+            panic!(
+                "Cannot reclaim the root element - {:#?}",
+                std::backtrace::Backtrace::force_capture()
+            );
+        }
+
+        self.elements.try_remove(el.0)
+    }
+
+    pub(crate) fn update_template(&mut self, el: ElementId, node: &VNode) {
+        let node: *const VNode = node as *const _;
+        self.elements[el.0].template = unsafe { std::mem::transmute(node) };
+    }
+
+    // Drop a scope and all its children
+    pub(crate) fn drop_scope(&mut self, id: ScopeId) {
+        if let Some(root) = self.scopes[id.0].as_ref().try_root_node() {
+            if let RenderReturn::Sync(Ok(node)) = unsafe { root.extend_lifetime_ref() } {
+                self.drop_scope_inner(node)
+            }
+        }
+
+        self.scopes[id.0].props.take();
+
+        let scope = &mut self.scopes[id.0];
+
+        // Drop all the hooks once the children are dropped
+        // this means we'll drop hooks bottom-up
+        for hook in scope.hook_list.get_mut().drain(..) {
+            drop(unsafe { BumpBox::from_raw(hook) });
+        }
+    }
+
+    fn drop_scope_inner(&mut self, node: &VNode) {
+        node.clear_listeners();
+        node.dynamic_nodes.iter().for_each(|node| match node {
+            DynamicNode::Component(c) => self.drop_scope(c.scope.get().unwrap()),
+            DynamicNode::Fragment(nodes) => {
+                nodes.iter().for_each(|node| self.drop_scope_inner(node))
+            }
+            DynamicNode::Placeholder(t) => {
+                self.try_reclaim(t.get());
+            }
+            DynamicNode::Text(t) => {
+                self.try_reclaim(t.id.get());
+            }
+        });
+
+        for root in node.root_ids {
+            let id = root.get();
+            if id.0 != 0 {
+                self.try_reclaim(id);
+            }
+        }
+    }
+
+    /// Descend through the tree, removing any borrowed props and listeners
+    pub(crate) fn ensure_drop_safety(&self, scope: ScopeId) {
+        let node = unsafe { self.scopes[scope.0].previous_frame().try_load_node() };
+
+        // And now we want to make sure the previous frame has dropped anything that borrows self
+        if let Some(RenderReturn::Sync(Ok(node))) = node {
+            self.ensure_drop_safety_inner(node);
+        }
+    }
+
+    fn ensure_drop_safety_inner(&self, node: &VNode) {
+        node.clear_listeners();
+
+        node.dynamic_nodes.iter().for_each(|child| match child {
+            // Only descend if the props are borrowed
+            DynamicNode::Component(c) if !c.static_props => {
+                self.ensure_drop_safety(c.scope.get().unwrap());
+                c.props.set(None);
+            }
+
+            DynamicNode::Fragment(f) => f
+                .iter()
+                .for_each(|node| self.ensure_drop_safety_inner(node)),
+
+            _ => {}
+        });
+    }
+}
+
+impl ElementPath {
+    pub(crate) fn is_ascendant(&self, big: &&[u8]) -> bool {
+        match *self {
+            ElementPath::Deep(small) => small.len() <= big.len() && small == &big[..small.len()],
+            ElementPath::Root(r) => big.len() == 1 && big[0] == r as u8,
+        }
+    }
+}
+
+impl PartialEq<&[u8]> for ElementPath {
+    fn eq(&self, other: &&[u8]) -> bool {
+        match *self {
+            ElementPath::Deep(deep) => deep.eq(*other),
+            ElementPath::Root(r) => other.len() == 1 && other[0] == r as u8,
+        }
+    }
+}

+ 29 - 0
packages/core/src/bump_frame.rs

@@ -0,0 +1,29 @@
+use crate::nodes::RenderReturn;
+use bumpalo::Bump;
+use std::cell::Cell;
+
+pub(crate) struct BumpFrame {
+    pub bump: Bump,
+    pub node: Cell<*const RenderReturn<'static>>,
+}
+
+impl BumpFrame {
+    pub(crate) fn new(capacity: usize) -> Self {
+        let bump = Bump::with_capacity(capacity);
+        Self {
+            bump,
+            node: Cell::new(std::ptr::null()),
+        }
+    }
+
+    /// Creates a new lifetime out of thin air
+    pub(crate) unsafe fn try_load_node<'b>(&self) -> Option<&'b RenderReturn<'b>> {
+        let node = self.node.get();
+
+        if node.is_null() {
+            return None;
+        }
+
+        unsafe { std::mem::transmute(&*node) }
+    }
+}

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

@@ -0,0 +1,401 @@
+use std::cell::Cell;
+use std::rc::Rc;
+
+use crate::innerlude::{VComponent, VText};
+use crate::mutations::Mutation;
+use crate::mutations::Mutation::*;
+use crate::nodes::VNode;
+use crate::nodes::{DynamicNode, TemplateNode};
+use crate::virtual_dom::VirtualDom;
+use crate::{AttributeValue, ElementId, RenderReturn, ScopeId, SuspenseContext};
+
+impl<'b> VirtualDom {
+    /// Create a new template [`VNode`] and write it to the [`Mutations`] buffer.
+    ///
+    /// This method pushes the ScopeID to the internal scopestack and returns the number of nodes created.
+    pub(crate) fn create_scope(&mut self, scope: ScopeId, template: &'b VNode<'b>) -> usize {
+        self.scope_stack.push(scope);
+        let out = self.create(template);
+        self.scope_stack.pop();
+
+        out
+    }
+
+    /// Create this template and write its mutations
+    pub(crate) fn create(&mut self, template: &'b VNode<'b>) -> usize {
+        // The best renderers will have templates prehydrated and registered
+        // Just in case, let's create the template using instructions anyways
+        if !self.templates.contains_key(&template.template.name) {
+            self.register_template(template);
+        }
+
+        // Walk the roots, creating nodes and assigning IDs
+        // todo: adjust dynamic nodes to be in the order of roots and then leaves (ie BFS)
+        let mut dynamic_attrs = template.template.attr_paths.iter().enumerate().peekable();
+        let mut dynamic_nodes = template.template.node_paths.iter().enumerate().peekable();
+
+        let cur_scope = self.scope_stack.last().copied().unwrap();
+
+        let mut on_stack = 0;
+        for (root_idx, root) in template.template.roots.iter().enumerate() {
+            // We might need to generate an ID for the root node
+            on_stack += match root {
+                TemplateNode::DynamicText { id } | TemplateNode::Dynamic { id } => {
+                    match &template.dynamic_nodes[*id] {
+                        // a dynamic text node doesn't replace a template node, instead we create it on the fly
+                        DynamicNode::Text(VText { id: slot, value }) => {
+                            let id = self.next_element(template, template.template.node_paths[*id]);
+                            slot.set(id);
+
+                            // Safety: we promise not to re-alias this text later on after committing it to the mutation
+                            let unbounded_text = unsafe { std::mem::transmute(*value) };
+                            self.mutations.push(CreateTextNode {
+                                value: unbounded_text,
+                                id,
+                            });
+
+                            1
+                        }
+
+                        DynamicNode::Placeholder(slot) => {
+                            let id = self.next_element(template, template.template.node_paths[*id]);
+                            slot.set(id);
+                            self.mutations.push(CreatePlaceholder { id });
+                            1
+                        }
+
+                        DynamicNode::Fragment(_) | DynamicNode::Component { .. } => {
+                            self.create_dynamic_node(template, &template.dynamic_nodes[*id], *id)
+                        }
+                    }
+                }
+
+                TemplateNode::Element { .. } | TemplateNode::Text { .. } => {
+                    let this_id = self.next_root(template, root_idx);
+
+                    template.root_ids[root_idx].set(this_id);
+                    self.mutations.push(LoadTemplate {
+                        name: template.template.name,
+                        index: root_idx,
+                        id: this_id,
+                    });
+
+                    // we're on top of a node that has a dynamic attribute for a descendant
+                    // Set that attribute now before the stack gets in a weird state
+                    while let Some((mut attr_id, path)) =
+                        dynamic_attrs.next_if(|(_, p)| p[0] == root_idx as u8)
+                    {
+                        // if attribute is on a root node, then we've already created the element
+                        // Else, it's deep in the template and we should create a new id for it
+                        let id = match path.len() {
+                            1 => this_id,
+                            _ => {
+                                let id = self
+                                    .next_element(template, template.template.attr_paths[attr_id]);
+                                self.mutations.push(Mutation::AssignId {
+                                    path: &path[1..],
+                                    id,
+                                });
+                                id
+                            }
+                        };
+
+                        loop {
+                            let attribute = template.dynamic_attrs.get(attr_id).unwrap();
+                            attribute.mounted_element.set(id);
+
+                            // Safety: we promise not to re-alias this text later on after committing it to the mutation
+                            let unbounded_name = unsafe { std::mem::transmute(attribute.name) };
+
+                            match &attribute.value {
+                                AttributeValue::Text(value) => {
+                                    // Safety: we promise not to re-alias this text later on after committing it to the mutation
+                                    let unbounded_value = unsafe { std::mem::transmute(*value) };
+
+                                    self.mutations.push(SetAttribute {
+                                        name: unbounded_name,
+                                        value: unbounded_value,
+                                        ns: attribute.namespace,
+                                        id,
+                                    })
+                                }
+                                AttributeValue::Bool(value) => {
+                                    self.mutations.push(SetBoolAttribute {
+                                        name: unbounded_name,
+                                        value: *value,
+                                        id,
+                                    })
+                                }
+                                AttributeValue::Listener(_) => {
+                                    self.mutations.push(NewEventListener {
+                                        // all listeners start with "on"
+                                        name: &unbounded_name[2..],
+                                        scope: cur_scope,
+                                        id,
+                                    })
+                                }
+                                AttributeValue::Float(_) => todo!(),
+                                AttributeValue::Int(_) => todo!(),
+                                AttributeValue::Any(_) => todo!(),
+                                AttributeValue::None => todo!(),
+                            }
+
+                            // Only push the dynamic attributes forward if they match the current path (same element)
+                            match dynamic_attrs.next_if(|(_, p)| *p == path) {
+                                Some((next_attr_id, _)) => attr_id = next_attr_id,
+                                None => break,
+                            }
+                        }
+                    }
+
+                    // We're on top of a node that has a dynamic child for a descendant
+                    // Skip any node that's a root
+                    let mut start = None;
+                    let mut end = None;
+
+                    // Collect all the dynamic nodes below this root
+                    // We assign the start and end of the range of dynamic nodes since they area ordered in terms of tree path
+                    //
+                    // [0]
+                    // [1, 1]     <---|
+                    // [1, 1, 1]  <---| these are the range of dynamic nodes below root 1
+                    // [1, 1, 2]  <---|
+                    // [2]
+                    //
+                    // We collect each range and then create them and replace the placeholder in the template
+                    while let Some((idx, p)) =
+                        dynamic_nodes.next_if(|(_, p)| p[0] == root_idx as u8)
+                    {
+                        if p.len() == 1 {
+                            continue;
+                        }
+
+                        if start.is_none() {
+                            start = Some(idx);
+                        }
+
+                        end = Some(idx);
+                    }
+
+                    //
+                    if let (Some(start), Some(end)) = (start, end) {
+                        for idx in start..=end {
+                            let node = &template.dynamic_nodes[idx];
+                            let m = self.create_dynamic_node(template, node, idx);
+                            if m > 0 {
+                                self.mutations.push(ReplacePlaceholder {
+                                    m,
+                                    path: &template.template.node_paths[idx][1..],
+                                });
+                            }
+                        }
+                    }
+
+                    // elements create only one node :-)
+                    1
+                }
+            };
+        }
+
+        on_stack
+    }
+
+    /// Insert a new template into the VirtualDom's template registry
+    fn register_template(&mut self, template: &'b VNode<'b>) {
+        // First, make sure we mark the template as seen, regardless if we process it
+        self.templates
+            .insert(template.template.name, template.template);
+
+        // If it's all dynamic nodes, then we don't need to register it
+        // Quickly run through and see if it's all just dynamic nodes
+        let dynamic_roots = template
+            .template
+            .roots
+            .iter()
+            .filter(|root| {
+                matches!(
+                    root,
+                    TemplateNode::Dynamic { .. } | TemplateNode::DynamicText { .. }
+                )
+            })
+            .count();
+
+        if dynamic_roots == template.template.roots.len() {
+            return;
+        }
+
+        self.mutations.templates.push(template.template);
+    }
+
+    pub(crate) fn create_dynamic_node(
+        &mut self,
+        template: &'b VNode<'b>,
+        node: &'b DynamicNode<'b>,
+        idx: usize,
+    ) -> usize {
+        use DynamicNode::*;
+        match node {
+            Text(text) => self.create_dynamic_text(template, text, idx),
+            Fragment(frag) => self.create_fragment(frag),
+            Placeholder(frag) => self.create_placeholder(frag, template, idx),
+            Component(component) => self.create_component_node(template, component, idx),
+        }
+    }
+
+    fn create_dynamic_text(
+        &mut self,
+        template: &'b VNode<'b>,
+        text: &'b VText<'b>,
+        idx: usize,
+    ) -> usize {
+        // Allocate a dynamic element reference for this text node
+        let new_id = self.next_element(template, template.template.node_paths[idx]);
+
+        // Make sure the text node is assigned to the correct element
+        text.id.set(new_id);
+
+        // Safety: we promise not to re-alias this text later on after committing it to the mutation
+        let value = unsafe { std::mem::transmute(text.value) };
+
+        // Add the mutation to the list
+        self.mutations.push(HydrateText {
+            id: new_id,
+            path: &template.template.node_paths[idx][1..],
+            value,
+        });
+
+        // Since we're hydrating an existing node, we don't create any new nodes
+        0
+    }
+
+    pub(crate) fn create_placeholder(
+        &mut self,
+        slot: &Cell<ElementId>,
+        template: &'b VNode<'b>,
+        idx: usize,
+    ) -> usize {
+        // Allocate a dynamic element reference for this text node
+        let id = self.next_element(template, template.template.node_paths[idx]);
+
+        // Make sure the text node is assigned to the correct element
+        slot.set(id);
+
+        // Assign the ID to the existing node in the template
+        self.mutations.push(AssignId {
+            path: &template.template.node_paths[idx][1..],
+            id,
+        });
+
+        // Since the placeholder is already in the DOM, we don't create any new nodes
+        0
+    }
+
+    pub(crate) fn create_fragment(&mut self, nodes: &'b [VNode<'b>]) -> usize {
+        nodes.iter().fold(0, |acc, child| acc + self.create(child))
+    }
+
+    pub(super) fn create_component_node(
+        &mut self,
+        template: &'b VNode<'b>,
+        component: &'b VComponent<'b>,
+        idx: usize,
+    ) -> usize {
+        let props = component
+            .props
+            .replace(None)
+            .expect("Props to always exist when a component is being created");
+
+        let unbounded_props = unsafe { std::mem::transmute(props) };
+
+        let scope = self.new_scope(unbounded_props, component.name);
+        let scope = scope.id;
+        component.scope.set(Some(scope));
+
+        let return_nodes = unsafe { self.run_scope(scope).extend_lifetime_ref() };
+
+        use RenderReturn::*;
+
+        match return_nodes {
+            Sync(Ok(t)) => self.mount_component(scope, template, t, idx),
+            Sync(Err(_e)) => todo!("Propogate error upwards"),
+            Async(_) => self.mount_component_placeholder(template, idx, scope),
+        }
+    }
+
+    fn mount_component(
+        &mut self,
+        scope: ScopeId,
+        parent: &'b VNode<'b>,
+        new: &'b VNode<'b>,
+        idx: usize,
+    ) -> usize {
+        // Keep track of how many mutations are in the buffer in case we need to split them out if a suspense boundary
+        // is encountered
+        let mutations_to_this_point = self.mutations.edits.len();
+
+        // Create the component's root element
+        let created = self.create_scope(scope, new);
+
+        // If there are no suspense leaves below us, then just don't bother checking anything suspense related
+        if self.collected_leaves.is_empty() {
+            return created;
+        }
+
+        // If running the scope has collected some leaves and *this* component is a boundary, then handle the suspense
+        let boundary = match self.scopes[scope.0].has_context::<Rc<SuspenseContext>>() {
+            Some(boundary) => boundary,
+            _ => return created,
+        };
+
+        // Since this is a boundary, use its placeholder within the template as the placeholder for the suspense tree
+        let new_id = self.next_element(new, parent.template.node_paths[idx]);
+
+        // Now connect everything to the boundary
+        self.scopes[scope.0].placeholder.set(Some(new_id));
+
+        // This involves breaking off the mutations to this point, and then creating a new placeholder for the boundary
+        // Note that we break off dynamic mutations only - since static mutations aren't rendered immediately
+        let split_off = unsafe {
+            std::mem::transmute::<Vec<Mutation>, Vec<Mutation>>(
+                self.mutations.edits.split_off(mutations_to_this_point),
+            )
+        };
+        boundary.mutations.borrow_mut().edits.extend(split_off);
+        boundary.created_on_stack.set(created);
+        boundary
+            .waiting_on
+            .borrow_mut()
+            .extend(self.collected_leaves.drain(..));
+
+        // Now assign the placeholder in the DOM
+        self.mutations.push(AssignId {
+            id: new_id,
+            path: &parent.template.node_paths[idx][1..],
+        });
+
+        0
+    }
+
+    /// Take the rendered nodes from a component and handle them if they were async
+    ///
+    /// IE simply assign an ID to the placeholder
+    fn mount_component_placeholder(
+        &mut self,
+        template: &VNode,
+        idx: usize,
+        scope: ScopeId,
+    ) -> usize {
+        let new_id = self.next_element(template, template.template.node_paths[idx]);
+
+        // Set the placeholder of the scope
+        self.scopes[scope.0].placeholder.set(Some(new_id));
+
+        // Since the placeholder is already in the DOM, we don't create any new nodes
+        self.mutations.push(AssignId {
+            id: new_id,
+            path: &template.template.node_paths[idx][1..],
+        });
+
+        0
+    }
+}

+ 538 - 1115
packages/core/src/diff.rs

@@ -1,859 +1,390 @@
-#![warn(clippy::pedantic)]
-#![allow(clippy::cast_possible_truncation)]
-
-//! This module contains the stateful [`DiffState`] and all methods to diff [`VNode`]s, their properties, and their children.
-//!
-//! The [`DiffState`] calculates the diffs between the old and new frames, updates the new nodes, and generates a set
-//! of mutations for the renderer to apply.
-//!
-//! ## Notice:
-//!
-//! The inspiration and code for this module was originally taken from Dodrio (@fitzgen) and then modified to support
-//! Components, Fragments, Suspense, `SubTree` memoization, incremental diffing, cancellation, pausing, priority
-//! scheduling, and additional batching operations.
-//!
-//! ## Implementation Details:
-//!
-//! ### IDs for elements
-//! --------------------
-//! All nodes are addressed by their IDs.
-//! We don't necessarily require that DOM changes happen instantly during the diffing process, so the implementor may choose
-//! to batch nodes if it is more performant for their application. The element IDs are indices into the internal element
-//! array. The expectation is that implementors will use the ID as an index into a Vec of real nodes, allowing for passive
-//! garbage collection as the [`crate::VirtualDom`] replaces old nodes.
-//!
-//! When new vnodes are created through `cx.render`, they won't know which real node they correspond to. During diffing,
-//! we always make sure to copy over the ID. If we don't do this properly, the [`ElementId`] will be populated incorrectly
-//! and brick the user's page.
-//!
-//! ### Fragment Support
-//! --------------------
-//! Fragments (nodes without a parent) are supported through a combination of "replace with" and anchor vnodes. Fragments
-//! can be particularly challenging when they are empty, so the anchor node lets us "reserve" a spot for the empty
-//! fragment to be replaced with when it is no longer empty. This is guaranteed by logic in the [`crate::innerlude::NodeFactory`] - it is
-//! impossible to craft a fragment with 0 elements - they must always have at least a single placeholder element. Adding
-//! "dummy" nodes _is_ inefficient, but it makes our diffing algorithm faster and the implementation is completely up to
-//! the platform.
-//!
-//! Other implementations either don't support fragments or use a "child + sibling" pattern to represent them. Our code is
-//! vastly simpler and more performant when we can just create a placeholder element while the fragment has no children.
-//!
-//! ### Suspense
-//! ------------
-//! Dioxus implements Suspense slightly differently than React. In React, each fiber is manually progressed until it runs
-//! into a promise-like value. React will then work on the next "ready" fiber, checking back on the previous fiber once
-//! it has finished its new work. In Dioxus, we use a similar approach, but try to completely render the tree before
-//! switching sub-fibers. Instead, each future is submitted into a futures-queue and the node is manually loaded later on.
-//! Due to the frequent calls to [`crate::virtual_dom::VirtualDom::work_with_deadline`] we can get the pure "fetch-as-you-render" behavior of React Fiber.
-//!
-//! We're able to use this approach because we use placeholder nodes - futures that aren't ready still get submitted to
-//! DOM, but as a placeholder.
-//!
-//! Right now, the "suspense" queue is intertwined with hooks. In the future, we should allow any future to drive attributes
-//! and contents, without the need for a `use_suspense` hook. In the interim, this is the quickest way to get Suspense working.
-//!
-//! ## Subtree Memoization
-//! -----------------------
-//! We also employ "subtree memoization" which saves us from having to check trees which hold no dynamic content. We can
-//! detect if a subtree is "static" by checking if its children are "static". Since we dive into the tree depth-first, the
-//! calls to "create" propagate this information upwards. Structures like the one below are entirely static:
-//! ```rust, ignore
-//! rsx!( div { class: "hello world", "this node is entirely static" } )
-//! ```
-//! Because the subtrees won't be diffed, their "real node" data will be stale (invalid), so it's up to the reconciler to
-//! track nodes created in a scope and clean up all relevant data. Support for this is currently WIP and depends on comp-time
-//! hashing of the subtree from the rsx! macro. We do a very limited form of static analysis via static string pointers as
-//! a way of short-circuiting the most expensive checks.
-//!
-//! ## Bloom Filter and Heuristics
-//! ------------------------------
-//! For all components, we employ some basic heuristics to speed up allocations and pre-size bump arenas. The heuristics are
-//! currently very rough, but will get better as time goes on. The information currently tracked includes the size of a
-//! bump arena after first render, the number of hooks, and the number of nodes in the tree.
-//!
-//! ## Garbage Collection
-//! ---------------------
-//! Dioxus uses a passive garbage collection system to clean up old nodes once the work has been completed. This garbage
-//! collection is done internally once the main diffing work is complete. After the "garbage" is collected, Dioxus will then
-//! start to re-use old keys for new nodes. This results in a passive memory management system that is very efficient.
-//!
-//! The IDs used by the key/map are just an index into a Vec. This means that Dioxus will drive the key allocation strategy
-//! so the client only needs to maintain a simple list of nodes. By default, Dioxus will not manually clean up old nodes
-//! for the client. As new nodes are created, old nodes will be over-written.
-//!
-//! ## Further Reading and Thoughts
-//! ----------------------------
-//! There are more ways of increasing diff performance here that are currently not implemented.
-//! - Strong memoization of subtrees.
-//! - Guided diffing.
-//! - Certain web-dom-specific optimizations.
-//!
-//! 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 std::cell::Cell;
 
 use crate::{
-    dynamic_template_context::TemplateContext,
-    innerlude::{
-        AnyProps, ElementId, Mutations, ScopeArena, ScopeId, VComponent, VElement, VFragment,
-        VNode, VPlaceholder, VText,
-    },
-    template::{
-        Template, TemplateAttribute, TemplateElement, TemplateNode, TemplateNodeId,
-        TemplateNodeType, TemplateValue, TextTemplateSegment, VTemplateRef,
-    },
-    Attribute, TemplateAttributeValue,
+    arena::ElementId,
+    innerlude::{DirtyScope, VComponent, VText},
+    mutations::Mutation,
+    nodes::RenderReturn,
+    nodes::{DynamicNode, VNode},
+    scopes::ScopeId,
+    virtual_dom::VirtualDom,
+    AttributeValue, TemplateNode,
 };
-use bumpalo::Bump;
-use rustc_hash::{FxHashMap, FxHashSet};
-use smallvec::{smallvec, SmallVec};
-
-pub(crate) struct DiffState<'bump> {
-    pub(crate) scopes: &'bump ScopeArena,
-    pub(crate) mutations: Mutations<'bump>,
-    pub(crate) force_diff: bool,
-    pub(crate) scope_stack: SmallVec<[ScopeId; 5]>,
-}
 
-impl<'b> DiffState<'b> {
-    pub fn new(scopes: &'b ScopeArena) -> Self {
-        Self {
-            scopes,
-            mutations: Mutations::new(),
-            force_diff: false,
-            scope_stack: smallvec![],
-        }
-    }
+use fxhash::{FxHashMap, FxHashSet};
+use DynamicNode::*;
 
-    pub fn diff_scope(&mut self, parent: ElementId, scopeid: ScopeId) {
-        let (old, new) = (self.scopes.wip_head(scopeid), self.scopes.fin_head(scopeid));
+impl<'b> VirtualDom {
+    pub(super) fn diff_scope(&mut self, scope: ScopeId) {
+        let scope_state = &mut self.scopes[scope.0];
 
-        self.scope_stack.push(scopeid);
-        {
-            self.diff_node(parent, old, new);
+        self.scope_stack.push(scope);
+        unsafe {
+            // Load the old and new bump arenas
+            let old = scope_state
+                .previous_frame()
+                .try_load_node()
+                .expect("Call rebuild before diffing");
+
+            let new = scope_state
+                .current_frame()
+                .try_load_node()
+                .expect("Call rebuild before diffing");
+
+            use RenderReturn::{Async, Sync};
+
+            match (old, new) {
+                (Sync(Ok(l)), Sync(Ok(r))) => self.diff_node(l, r),
+
+                // Err cases
+                (Sync(Ok(l)), Sync(Err(e))) => self.diff_ok_to_err(l, e),
+                (Sync(Err(e)), Sync(Ok(r))) => self.diff_err_to_ok(e, r),
+                (Sync(Err(_eo)), Sync(Err(_en))) => { /* nothing */ }
+
+                // Async
+                (Sync(Ok(_l)), Async(_)) => todo!(),
+                (Sync(Err(_e)), Async(_)) => todo!(),
+                (Async(_), Sync(Ok(_r))) => todo!(),
+                (Async(_), Sync(Err(_e))) => { /* nothing */ }
+                (Async(_), Async(_)) => { /* nothing */ }
+            };
         }
         self.scope_stack.pop();
-
-        self.mutations.mark_dirty_scope(scopeid);
     }
 
-    pub fn diff_node(
-        &mut self,
-        parent: ElementId,
-        old_node: &'b VNode<'b>,
-        new_node: &'b VNode<'b>,
-    ) {
-        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);
-            }
+    fn diff_ok_to_err(&mut self, _l: &'b VNode<'b>, _e: &anyhow::Error) {}
+    fn diff_err_to_ok(&mut self, _e: &anyhow::Error, _l: &'b VNode<'b>) {}
 
-            (Placeholder(old), Placeholder(new)) => {
-                self.diff_placeholder_nodes(old, new, old_node, new_node);
-            }
-
-            (Element(old), Element(new)) => {
-                self.diff_element_nodes(old, new, old_node, new_node);
-            }
-
-            (Component(old), Component(new)) => {
-                self.diff_component_nodes(parent, old, new, old_node, new_node);
-            }
-
-            (Fragment(old), Fragment(new)) => {
-                self.diff_fragment_nodes(parent, old, new);
-            }
+    fn diff_node(&mut self, left_template: &'b VNode<'b>, right_template: &'b VNode<'b>) {
+        if left_template.template.name != right_template.template.name {
+            return self.light_diff_templates(left_template, right_template);
+        }
 
-            (TemplateRef(old), TemplateRef(new)) => {
-                self.diff_template_ref_nodes(parent, old, new, old_node, new_node);
+        for (left_attr, right_attr) in left_template
+            .dynamic_attrs
+            .iter()
+            .zip(right_template.dynamic_attrs.iter())
+        {
+            // Move over the ID from the old to the new
+            right_attr
+                .mounted_element
+                .set(left_attr.mounted_element.get());
+
+            // We want to make sure anything listener that gets pulled is valid
+            if let AttributeValue::Listener(_) = right_attr.value {
+                self.update_template(left_attr.mounted_element.get(), right_template);
+            }
+
+            if left_attr.value != right_attr.value || left_attr.volatile {
+                // todo: add more types of attribute values
+                match right_attr.value {
+                    AttributeValue::Text(text) => {
+                        let name = unsafe { std::mem::transmute(left_attr.name) };
+                        let value = unsafe { std::mem::transmute(text) };
+                        self.mutations.push(Mutation::SetAttribute {
+                            id: left_attr.mounted_element.get(),
+                            ns: right_attr.namespace,
+                            name,
+                            value,
+                        });
+                    }
+                    // todo: more types of attribute values
+                    _ => todo!("other attribute types"),
+                }
             }
-
-            (
-                Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_) | TemplateRef(_),
-                Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_) | TemplateRef(_),
-            ) => self.replace_node(parent, old_node, new_node),
         }
-    }
 
-    pub fn create_node(&mut self, parent: ElementId, node: &'b VNode<'b>, nodes: &mut Vec<u64>) {
-        match node {
-            VNode::Text(vtext) => self.create_text_node(vtext, node, nodes),
-            VNode::Placeholder(anchor) => self.create_anchor_node(anchor, node, nodes),
-            VNode::Element(element) => self.create_element_node(parent, element, node, nodes),
-            VNode::Fragment(frag) => self.create_fragment_node(parent, frag, nodes),
-            VNode::Component(component) => self.create_component_node(parent, component, nodes),
-            VNode::TemplateRef(temp) => self.create_template_ref_node(parent, temp, node, nodes),
+        for (idx, (left_node, right_node)) in left_template
+            .dynamic_nodes
+            .iter()
+            .zip(right_template.dynamic_nodes.iter())
+            .enumerate()
+        {
+            match (left_node, right_node) {
+                (Text(left), Text(right)) => self.diff_vtext(left, right),
+                (Fragment(left), Fragment(right)) => self.diff_non_empty_fragment(left, right),
+                (Placeholder(left), Placeholder(right)) => {
+                    right.set(left.get());
+                }
+                (Component(left), Component(right)) => {
+                    self.diff_vcomponent(left, right, right_template, idx)
+                }
+                (Placeholder(left), Fragment(right)) => {
+                    self.replace_placeholder_with_nodes(left, right)
+                }
+                (Fragment(left), Placeholder(right)) => {
+                    self.replace_nodes_with_placeholder(left, right)
+                }
+                _ => todo!(),
+            };
         }
-    }
-
-    fn create_text_node(&mut self, text: &'b VText<'b>, node: &'b VNode<'b>, nodes: &mut Vec<u64>) {
-        let real_id = self.scopes.reserve_node(node);
-        text.id.set(Some(real_id));
-        self.mutations
-            .create_text_node(text.text, Some(real_id.as_u64()));
-        nodes.push(real_id.0 as u64);
-    }
-
-    fn create_anchor_node(
-        &mut self,
-        anchor: &'b VPlaceholder,
-        node: &'b VNode<'b>,
-        nodes: &mut Vec<u64>,
-    ) {
-        let real_id = self.scopes.reserve_node(node);
-        anchor.id.set(Some(real_id));
-        self.mutations.create_placeholder(Some(real_id.as_u64()));
-        nodes.push(real_id.0 as u64);
-    }
-
-    fn create_element_node(
-        &mut self,
-        parent: ElementId,
-        element: &'b VElement<'b>,
-        node: &'b VNode<'b>,
-        nodes: &mut Vec<u64>,
-    ) {
-        let VElement {
-            tag: tag_name,
-            listeners,
-            attributes,
-            children,
-            namespace,
-            id: dom_id,
-            parent: parent_id,
-            ..
-        } = &element;
-
-        parent_id.set(Some(parent));
-
-        let real_id = self.scopes.reserve_node(node);
-
-        dom_id.set(Some(real_id));
 
+        // Make sure the roots get transferred over
+        for (left, right) in left_template
+            .root_ids
+            .iter()
+            .zip(right_template.root_ids.iter())
         {
-            self.mutations
-                .create_element(tag_name, *namespace, Some(real_id.as_u64()), 0);
-
-            let cur_scope_id = self.current_scope();
-
-            for listener in listeners.iter() {
-                listener.mounted_node.set(Some(real_id));
-                self.mutations.new_event_listener(listener, cur_scope_id);
-            }
-
-            for attr in attributes.iter() {
-                self.mutations.set_attribute(attr, Some(real_id.as_u64()));
-            }
-
-            if !children.is_empty() {
-                self.create_and_append_children(real_id, children);
-            }
+            right.set(left.get());
         }
-
-        nodes.push(real_id.0 as u64);
     }
 
-    fn create_fragment_node(
-        &mut self,
-        parent: ElementId,
-        frag: &'b VFragment<'b>,
-        nodes: &mut Vec<u64>,
-    ) {
-        self.create_children(parent, frag.children, nodes);
+    fn replace_placeholder_with_nodes(&mut self, l: &'b Cell<ElementId>, r: &'b [VNode<'b>]) {
+        let m = self.create_children(r);
+        let id = l.get();
+        self.mutations.push(Mutation::ReplaceWith { id, m });
+        self.reclaim(id);
     }
 
-    fn create_component_node(
-        &mut self,
-        parent: ElementId,
-        vcomponent: &'b VComponent<'b>,
-        nodes: &mut Vec<u64>,
-    ) {
-        let parent_idx = self.current_scope();
-
-        // the component might already exist - if it does, we need to reuse it
-        // this makes figure out when to drop the component more complicated
-        let new_idx = if let Some(idx) = vcomponent.scope.get() {
-            assert!(self.scopes.get_scope(idx).is_some());
-            idx
-        } else {
-            // Insert a new scope into our component list
-            let props: Box<dyn AnyProps + 'b> = vcomponent.props.borrow_mut().take().unwrap();
-            let props: Box<dyn AnyProps + 'static> = unsafe { std::mem::transmute(props) };
-            self.scopes
-                .new_with_key(vcomponent.user_fc, props, Some(parent_idx), parent)
-        };
+    fn replace_nodes_with_placeholder(&mut self, l: &'b [VNode<'b>], r: &'b Cell<ElementId>) {
+        // Remove the old nodes, except for one
+        self.remove_nodes(&l[1..]);
 
-        // Actually initialize the caller's slot with the right address
-        vcomponent.scope.set(Some(new_idx));
+        // Now create the new one
+        let first = self.replace_inner(&l[0]);
 
-        log::trace!(
-            "created component \"{}\", id: {:?} parent {:?}",
-            vcomponent.fn_name,
-            new_idx,
-            parent_idx,
-        );
-
-        // if vcomponent.can_memoize {
-        //     // todo: implement promotion logic. save us from boxing props that we don't need
-        // } else {
-        //     // track this component internally so we know the right drop order
-        // }
-
-        self.enter_scope(new_idx);
-
-        // Run the scope for one iteration to initialize it
-        self.scopes.run_scope(new_idx);
-        self.mutations.mark_dirty_scope(new_idx);
-
-        // Take the node that was just generated from running the component
-        let nextnode = self.scopes.fin_head(new_idx);
-        self.create_node(parent, nextnode, nodes);
-
-        self.leave_scope();
-    }
-
-    pub(crate) fn create_template_ref_node(
-        &mut self,
-        parent: ElementId,
-        new: &'b VTemplateRef<'b>,
-        _node: &'b VNode<'b>,
-        nodes: &mut Vec<u64>,
-    ) {
-        let (id, just_created) = {
-            let mut resolver = self.scopes.template_resolver.borrow_mut();
-            resolver.get_or_create_client_id(&new.template_id, self.scopes)
-        };
-
-        let template = {
-            let templates = self.scopes.templates.borrow();
-            templates.get(&new.template_id).unwrap().clone()
-        };
-        let template = template.borrow();
-
-        if just_created {
-            self.register_template(&template, id);
-        }
-
-        new.template_ref_id
-            .set(Some(self.scopes.reserve_template_ref(new)));
-
-        let template_ref_id = new.template_ref_id.get().unwrap();
-
-        let root_nodes = template.root_nodes();
-        nodes.extend(root_nodes.iter().map(|node_id| {
-            let real_id = self.scopes.reserve_template_node(template_ref_id, *node_id);
-            new.set_node_id(*node_id, real_id);
-            real_id.as_u64()
-        }));
+        // Create the placeholder first, ensuring we get a dedicated ID for the placeholder
+        let placeholder = self.next_element(&l[0], &[]);
+        r.set(placeholder);
+        self.mutations
+            .push(Mutation::CreatePlaceholder { id: placeholder });
 
-        self.mutations.clone_node_children(
-            Some(id.as_u64()),
-            nodes[(nodes.len() - root_nodes.len())..].to_vec(),
-        );
+        self.mutations
+            .push(Mutation::ReplaceWith { id: first, m: 1 });
 
-        new.hydrate(parent, &template, self);
+        self.try_reclaim(first);
     }
 
-    pub(crate) fn diff_text_nodes(
+    fn diff_vcomponent(
         &mut self,
-        old: &'b VText<'b>,
-        new: &'b VText<'b>,
-        _old_node: &'b VNode<'b>,
-        new_node: &'b VNode<'b>,
+        left: &'b VComponent<'b>,
+        right: &'b VComponent<'b>,
+        right_template: &'b VNode<'b>,
+        idx: usize,
     ) {
-        if std::ptr::eq(old, new) {
+        // Replace components that have different render fns
+        if left.render_fn != right.render_fn {
+            let created = self.create_component_node(right_template, right, idx);
+            let head = unsafe {
+                self.scopes[left.scope.get().unwrap().0]
+                    .root_node()
+                    .extend_lifetime_ref()
+            };
+            let id = match head {
+                RenderReturn::Sync(Ok(node)) => self.replace_inner(node),
+                _ => todo!(),
+            };
+            self.mutations
+                .push(Mutation::ReplaceWith { id, m: created });
+            self.drop_scope(left.scope.get().unwrap());
             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),
-        };
+        // Make sure the new vcomponent has the right scopeid associated to it
+        let scope_id = left.scope.get().unwrap();
+        right.scope.set(Some(scope_id));
 
-        if old.text != new.text {
-            self.mutations.set_text(new.text, Some(root.as_u64()));
-        }
+        // copy out the box for both
+        let old = self.scopes[scope_id.0].props.as_ref();
+        let new = right.props.replace(None).unwrap();
 
-        self.scopes.update_node(new_node, root);
-
-        new.id.set(Some(root));
-    }
-
-    pub(crate) fn diff_placeholder_nodes(
-        &mut self,
-        old: &'b VPlaceholder,
-        new: &'b VPlaceholder,
-        _old_node: &'b VNode<'b>,
-        new_node: &'b VNode<'b>,
-    ) {
-        if std::ptr::eq(old, new) {
+        // If the props are static, then we try to memoize by setting the new with the old
+        // The target scopestate still has the reference to the old props, so there's no need to update anything
+        // This also implicitly drops the new props since they're not used
+        if left.static_props && unsafe { old.as_ref().unwrap().memoize(new.as_ref()) } {
             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),
-        };
+        // First, move over the props from the old to the new, dropping old props in the process
+        self.scopes[scope_id.0].props = unsafe { std::mem::transmute(new) };
 
-        self.scopes.update_node(new_node, root);
-        new.id.set(Some(root));
+        // Now run the component and diff it
+        self.run_scope(scope_id);
+        self.diff_scope(scope_id);
     }
 
-    fn diff_element_nodes(
-        &mut self,
-        old: &'b VElement<'b>,
-        new: &'b VElement<'b>,
-        old_node: &'b VNode<'b>,
-        new_node: &'b VNode<'b>,
-    ) {
-        if std::ptr::eq(old, new) {
-            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),
-        };
-
-        // If the element type is completely different, the element needs to be re-rendered completely
-        // This is an optimization React makes due to how users structure their code
-        //
-        // This case is rather rare (typically only in non-keyed lists)
-        if new.tag != old.tag || new.namespace != old.namespace {
-            self.replace_node(root, old_node, new_node);
-            return;
+    /// Lightly diff the two templates, checking only their roots.
+    ///
+    /// The goal here is to preserve any existing component state that might exist. This is to preserve some React-like
+    /// behavior where the component state is preserved when the component is re-rendered.
+    ///
+    /// This is implemented by iterating each root, checking if the component is the same, if it is, then diff it.
+    ///
+    /// We then pass the new template through "create" which should be smart enough to skip roots.
+    ///
+    /// Currently, we only handle the case where the roots are the same component list. If there's any sort of deviation,
+    /// IE more nodes, less nodes, different nodes, or expressions, then we just replace the whole thing.
+    ///
+    /// This is mostly implemented to help solve the issue where the same component is rendered under two different
+    /// conditions:
+    ///
+    /// ```rust, ignore
+    /// if enabled {
+    ///     rsx!{ Component { enabled_sign: "abc" } }
+    /// } else {
+    ///     rsx!{ Component { enabled_sign: "xyz" } }
+    /// }
+    /// ```
+    ///
+    /// However, we should not that it's explicit in the docs that this is not a guarantee. If you need to preserve state,
+    /// then you should be passing in separate props instead.
+    ///
+    /// ```rust, ignore
+    /// let props = if enabled {
+    ///     ComponentProps { enabled_sign: "abc" }
+    /// } else {
+    ///     ComponentProps { enabled_sign: "xyz" }
+    /// };
+    ///
+    /// rsx! {
+    ///     Component { ..props }
+    /// }
+    /// ```
+    fn light_diff_templates(&mut self, left: &'b VNode<'b>, right: &'b VNode<'b>) {
+        match matching_components(left, right) {
+            None => self.replace(left, right),
+            Some(components) => components
+                .into_iter()
+                .enumerate()
+                .for_each(|(idx, (l, r))| self.diff_vcomponent(l, r, right, idx)),
         }
+    }
 
-        self.scopes.update_node(new_node, root);
-
-        new.id.set(Some(root));
-        new.parent.set(old.parent.get());
-
-        // todo: attributes currently rely on the element on top of the stack, but in theory, we only need the id of the
-        // element to modify its attributes.
-        // it would result in fewer instructions if we just set the id directly.
-        // it would also clean up this code some, but that's not very important anyways
+    /// Diff the two text nodes
+    ///
+    /// This just moves the ID of the old node over to the new node, and then sets the text of the new node if it's
+    /// different.
+    fn diff_vtext(&mut self, left: &'b VText<'b>, right: &'b VText<'b>) {
+        let id = left.id.get();
 
-        // Diff Attributes
-        //
-        // It's extraordinarily rare to have the number/order of attributes change
-        // In these cases, we just completely erase the old set and make a new set
-        //
-        // 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.is_static && old_attr.value != new_attr.value
-                    || new_attr.attribute.volatile
-                {
-                    self.mutations.set_attribute(new_attr, Some(root.as_u64()));
-                }
-            }
-        } else {
-            for attribute in old.attributes {
-                self.mutations
-                    .remove_attribute(attribute, Some(root.as_u64()));
-            }
-            for attribute in new.attributes {
-                self.mutations.set_attribute(attribute, Some(root.as_u64()));
-            }
+        right.id.set(id);
+        if left.value != right.value {
+            let value = unsafe { std::mem::transmute(right.value) };
+            self.mutations.push(Mutation::SetText { id, value });
         }
+    }
 
-        // Diff listeners
-        //
-        // It's extraordinarily rare to have the number/order of listeners change
-        // In the cases where the listeners change, we completely wipe the data attributes and add new ones
-        //
-        // We also need to make sure that all listeners are properly attached to the parent scope (fix_listener)
-        //
-        // TODO: take a more efficient path than this
-        let cur_scope_id = self.current_scope();
-
-        if old.listeners.len() == new.listeners.len() {
-            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, Some(root.as_u64()));
-                    self.mutations.new_event_listener(new_l, cur_scope_id);
+    /// Remove all the top-level nodes, returning the firstmost root ElementId
+    ///
+    /// All IDs will be garbage collected
+    fn replace_inner(&mut self, node: &'b VNode<'b>) -> ElementId {
+        let id = match node.dynamic_root(0) {
+            None => node.root_ids[0].get(),
+            Some(Text(t)) => t.id.get(),
+            Some(Placeholder(e)) => e.get(),
+            Some(Fragment(nodes)) => {
+                let id = self.replace_inner(&nodes[0]);
+                self.remove_nodes(&nodes[1..]);
+                id
+            }
+            Some(Component(comp)) => {
+                let scope = comp.scope.get().unwrap();
+                match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } {
+                    RenderReturn::Sync(Ok(t)) => self.replace_inner(t),
+                    _ => todo!("cannot handle nonstandard nodes"),
                 }
             }
-        } else {
-            for listener in old.listeners {
-                self.mutations
-                    .remove_event_listener(listener.event, Some(root.as_u64()));
-            }
-            for listener in new.listeners {
-                listener.mounted_node.set(Some(root));
-                self.mutations.new_event_listener(listener, cur_scope_id);
-            }
-        }
-
-        match (old.children.len(), new.children.len()) {
-            (0, 0) => {}
-            (0, _) => {
-                let mut created = Vec::new();
-                self.create_children(root, new.children, &mut created);
-                self.mutations.append_children(Some(root.as_u64()), created);
-            }
-            (_, _) => self.diff_children(root, old.children, new.children),
         };
-    }
 
-    fn diff_component_nodes(
-        &mut self,
-        parent: ElementId,
-        old: &'b VComponent<'b>,
-        new: &'b VComponent<'b>,
-        old_node: &'b VNode<'b>,
-        new_node: &'b VNode<'b>,
-    ) {
-        let scope_addr = old
-            .scope
-            .get()
-            .expect("existing component nodes should have a scope");
-
-        if std::ptr::eq(old, new) {
-            return;
+        // Just remove the rest from the dom
+        for (idx, _) in node.template.roots.iter().enumerate().skip(1) {
+            self.remove_root_node(node, idx);
         }
 
-        // Make sure we're dealing with the same component (by function pointer)
-        if old.user_fc == new.user_fc {
-            self.enter_scope(scope_addr);
-            {
-                // Make sure the new component vnode is referencing the right scope id
-                new.scope.set(Some(scope_addr));
-
-                // make sure the component's caller function is up to date
-                let scope = self
-                    .scopes
-                    .get_scope(scope_addr)
-                    .unwrap_or_else(|| panic!("could not find {:?}", scope_addr));
-
-                // take the new props out regardless
-                // when memoizing, push to the existing scope if memoization happens
-                let new_props = new
-                    .props
-                    .borrow_mut()
-                    .take()
-                    .expect("new component props should exist");
-
-                let should_diff = {
-                    if old.can_memoize {
-                        // safety: we trust the implementation of "memoize"
-                        let props_are_the_same = unsafe {
-                            let new_ref = new_props.as_ref();
-                            scope.props.borrow().as_ref().unwrap().memoize(new_ref)
-                        };
-                        !props_are_the_same || self.force_diff
-                    } else {
-                        true
-                    }
-                };
-
-                if should_diff {
-                    let _old_props = scope
-                        .props
-                        .replace(unsafe { std::mem::transmute(Some(new_props)) });
+        // Garabge collect all of the nodes since this gets used in replace
+        self.clean_up_node(node);
 
-                    // this should auto drop the previous props
-                    self.scopes.run_scope(scope_addr);
-                    self.mutations.mark_dirty_scope(scope_addr);
-
-                    self.diff_node(
-                        parent,
-                        self.scopes.wip_head(scope_addr),
-                        self.scopes.fin_head(scope_addr),
-                    );
-                } else {
-                    // memoization has taken place
-                    drop(new_props);
-                };
-            }
-            self.leave_scope();
-        } else {
-            self.replace_node(parent, old_node, new_node);
-        }
-    }
-
-    fn diff_fragment_nodes(
-        &mut self,
-        parent: ElementId,
-        old: &'b VFragment<'b>,
-        new: &'b VFragment<'b>,
-    ) {
-        if std::ptr::eq(old, new) {
-            return;
-        }
-
-        // This is the case where options or direct vnodes might be used.
-        // In this case, it's faster to just skip ahead to their diff
-        if old.children.len() == 1 && new.children.len() == 1 {
-            if !std::ptr::eq(old, new) {
-                self.diff_node(parent, &old.children[0], &new.children[0]);
-            }
-            return;
-        }
-
-        debug_assert!(!old.children.is_empty());
-        debug_assert!(!new.children.is_empty());
-
-        self.diff_children(parent, old.children, new.children);
+        id
     }
 
-    #[allow(clippy::too_many_lines)]
-    fn diff_template_ref_nodes(
-        &mut self,
-        parent: ElementId,
-        old: &'b VTemplateRef<'b>,
-        new: &'b VTemplateRef<'b>,
-        old_node: &'b VNode<'b>,
-        new_node: &'b VNode<'b>,
-    ) {
-        fn diff_attributes<'b, Nodes, Attributes, V, Children, Listeners, TextSegments, Text>(
-            nodes: &Nodes,
-            ctx: (
-                &mut DiffState<'b>,
-                &'b Bump,
-                &'b 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 (diff_state, 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,
+    /// Clean up the node, not generating mutations
+    ///
+    /// Simply walks through the dynamic nodes
+    fn clean_up_node(&mut self, node: &'b VNode<'b>) {
+        for (idx, dyn_node) in node.dynamic_nodes.iter().enumerate() {
+            // Roots are cleaned up automatically?
+            if node.template.node_paths[idx].len() == 1 {
+                continue;
+            }
+
+            match dyn_node {
+                Component(comp) => {
+                    let scope = comp.scope.get().unwrap();
+                    match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } {
+                        RenderReturn::Sync(Ok(t)) => self.clean_up_node(t),
+                        _ => todo!("cannot handle nonstandard nodes"),
                     };
-                    let real_id = new.get_node_id(*node_id);
-                    diff_state
-                        .mutations
-                        .set_attribute(scope_bump.alloc(attribute), Some(real_id.as_u64()));
-                } else {
-                    panic!("expected element node");
                 }
-            }
-        }
-
-        fn set_attribute<'b, Attributes, V, Children, Listeners, TextSegments, Text>(
-            node: &TemplateNode<Attributes, V, Children, Listeners, TextSegments, Text>,
-            ctx: (&mut DiffState<'b>, &'b Bump, &'b VTemplateRef<'b>, usize),
-        ) where
-            Attributes: AsRef<[TemplateAttribute<V>]>,
-            V: TemplateValue,
-            Children: AsRef<[TemplateNodeId]>,
-            Listeners: AsRef<[usize]>,
-            TextSegments: AsRef<[TextTemplateSegment<Text>]>,
-            Text: AsRef<str>,
-        {
-            let (diff_state, 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,
-                };
-                let real_id = new.get_node_id(node.id);
-                diff_state
-                    .mutations
-                    .set_attribute(scope_bump.alloc(attribute), Some(real_id.as_u64()));
-            } else {
-                panic!("expected element node");
-            }
+                Text(t) => self.reclaim(t.id.get()),
+                Placeholder(t) => self.reclaim(t.get()),
+                Fragment(nodes) => nodes.iter().for_each(|node| self.clean_up_node(node)),
+            };
         }
 
-        fn diff_text<'b, Attributes, V, Children, Listeners, TextSegments, Text>(
-            node: &TemplateNode<Attributes, V, Children, Listeners, TextSegments, Text>,
-            ctx: (
-                &mut DiffState<'b>,
-                &'b VTemplateRef<'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, new, dynamic_context) = ctx;
-            if let TemplateNodeType::Text(text) = &node.node_type {
-                let text = dynamic_context.resolve_text(text);
-                let real_id = new.get_node_id(node.id);
-                diff.mutations.set_text(
-                    diff.current_scope_bump().alloc(text),
-                    Some(real_id.as_u64()),
-                );
-            } else {
-                panic!("expected text node");
+        // we clean up nodes with dynamic attributes, provided the node is unique and not a root node
+        let mut id = None;
+        for (idx, attr) in node.dynamic_attrs.iter().enumerate() {
+            // We'll clean up the root nodes either way, so don't worry
+            if node.template.attr_paths[idx].len() == 1 {
+                continue;
             }
-        }
-
-        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(parent, old_node, new_node);
-            return;
-        }
-
-        if let Some(template_ref_id) = old.template_ref_id.get() {
-            self.scopes.update_template_ref(template_ref_id, new);
-            new.template_ref_id.set(Some(template_ref_id));
-        } else {
-            new.template_ref_id
-                .set(Some(self.scopes.reserve_template_ref(new)));
-        }
-        new.parent.set(Some(parent));
-        new.node_ids.replace(old.node_ids.take());
 
-        let scope_bump = &self.current_scope_bump();
+            let next_id = attr.mounted_element.get();
 
-        let template = {
-            let templates = self.scopes.templates.borrow();
-            templates.get(&new.template_id).unwrap().clone()
-        };
-        let template = template.borrow();
-
-        // 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,
-                    (self, scope_bump, new, &template, idx),
-                );
+            if id == Some(next_id) {
+                continue;
             }
-        }
 
-        // set all volatile attributes
-        for (id, idx) in template.volatile_attributes() {
-            template.with_node(
-                id,
-                set_attribute,
-                set_attribute,
-                (self, scope_bump, new, idx),
-            );
-        }
+            id = Some(next_id);
 
-        // diff dynmaic nodes
-        for (old_node, new_node) in old
-            .dynamic_context
-            .nodes
-            .iter()
-            .zip(new.dynamic_context.nodes.iter())
-        {
-            self.diff_node(parent, old_node, new_node);
+            self.reclaim(next_id);
         }
+    }
 
-        // 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);
-                }
+    fn remove_root_node(&mut self, node: &'b VNode<'b>, idx: usize) {
+        match node.dynamic_root(idx) {
+            Some(Text(i)) => {
+                let id = i.id.get();
+                self.mutations.push(Mutation::Remove { id });
+                self.reclaim(id);
             }
-        }
-        for node_id in dirty_text_nodes {
-            template.with_node(
-                node_id,
-                diff_text,
-                diff_text,
-                (self, new, &new.dynamic_context),
-            );
-        }
+            Some(Placeholder(e)) => {
+                let id = e.get();
+                self.mutations.push(Mutation::Remove { id });
+                self.reclaim(id);
+            }
+            Some(Fragment(nodes)) => self.remove_nodes(nodes),
+            Some(Component(comp)) => {
+                let scope = comp.scope.get().unwrap();
+                match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } {
+                    RenderReturn::Sync(Ok(t)) => self.remove_node(t),
+                    _ => todo!("cannot handle nonstandard nodes"),
+                };
+            }
+            None => {
+                let id = node.root_ids[idx].get();
+                self.mutations.push(Mutation::Remove { id });
+                self.reclaim(id);
+            }
+        };
     }
 
-    // Diff the given set of old and new children.
-    //
-    // The parent must be on top of the change list stack when this function is
-    // entered:
-    //
-    //     [... parent]
-    //
-    // the change list stack is in the same state when this function returns.
-    //
-    // If old no anchors are provided, then it's assumed that we can freely append to the parent.
-    //
-    // Remember, non-empty lists does not mean that there are real elements, just that there are virtual elements.
-    //
-    // Fragment nodes cannot generate empty children lists, so we can assume that when a list is empty, it belongs only
-    // to an element, and appending makes sense.
-    fn diff_children(&mut self, parent: ElementId, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
-        if std::ptr::eq(old, new) {
-            return;
-        }
-
-        // Remember, fragments can never be empty (they always have a single child)
-        match (old, new) {
-            ([], []) => {}
-            ([], _) => self.create_and_append_children(parent, new),
-            (_, []) => self.remove_nodes(old, true),
-            _ => {
-                let new_is_keyed = new[0].key().is_some();
-                let old_is_keyed = old[0].key().is_some();
-
-                debug_assert!(
-                    new.iter().all(|n| n.key().is_some() == new_is_keyed),
-                    "all siblings must be keyed or all siblings must be non-keyed"
-                );
-                debug_assert!(
-                    old.iter().all(|o| o.key().is_some() == old_is_keyed),
-                    "all siblings must be keyed or all siblings must be non-keyed"
-                );
+    fn diff_non_empty_fragment(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
+        let new_is_keyed = new[0].key.is_some();
+        let old_is_keyed = old[0].key.is_some();
+        debug_assert!(
+            new.iter().all(|n| n.key.is_some() == new_is_keyed),
+            "all siblings must be keyed or all siblings must be non-keyed"
+        );
+        debug_assert!(
+            old.iter().all(|o| o.key.is_some() == old_is_keyed),
+            "all siblings must be keyed or all siblings must be non-keyed"
+        );
 
-                if new_is_keyed && old_is_keyed {
-                    self.diff_keyed_children(parent, old, new);
-                } else {
-                    self.diff_non_keyed_children(parent, old, new);
-                }
-            }
+        if new_is_keyed && old_is_keyed {
+            self.diff_keyed_children(old, new);
+        } else {
+            self.diff_non_keyed_children(old, new);
         }
     }
 
@@ -865,12 +396,7 @@ impl<'b> DiffState<'b> {
     //     [... parent]
     //
     // the change list stack is in the same state when this function returns.
-    fn diff_non_keyed_children(
-        &mut self,
-        parent: ElementId,
-        old: &'b [VNode<'b>],
-        new: &'b [VNode<'b>],
-    ) {
+    fn diff_non_keyed_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
         use std::cmp::Ordering;
 
         // Handled these cases in `diff_children` before calling this function.
@@ -878,15 +404,13 @@ impl<'b> DiffState<'b> {
         debug_assert!(!old.is_empty());
 
         match old.len().cmp(&new.len()) {
-            Ordering::Greater => self.remove_nodes(&old[new.len()..], true),
-            Ordering::Less => {
-                self.create_and_insert_after(parent, &new[old.len()..], old.last().unwrap());
-            }
+            Ordering::Greater => self.remove_nodes(&old[new.len()..]),
+            Ordering::Less => self.create_and_insert_after(&new[old.len()..], old.last().unwrap()),
             Ordering::Equal => {}
         }
 
         for (new, old) in new.iter().zip(old.iter()) {
-            self.diff_node(parent, old, new);
+            self.diff_node(old, new);
         }
     }
 
@@ -906,18 +430,13 @@ impl<'b> DiffState<'b> {
     // https://github.com/infernojs/inferno/blob/36fd96/packages/inferno/src/DOM/patching.ts#L530-L739
     //
     // The stack is empty upon entry.
-    fn diff_keyed_children(
-        &mut self,
-        parent: ElementId,
-        old: &'b [VNode<'b>],
-        new: &'b [VNode<'b>],
-    ) {
+    fn diff_keyed_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
         if cfg!(debug_assertions) {
-            let mut keys = rustc_hash::FxHashSet::default();
+            let mut keys = fxhash::FxHashSet::default();
             let mut assert_unique_keys = |children: &'b [VNode<'b>]| {
                 keys.clear();
                 for child in children {
-                    let key = child.key();
+                    let key = child.key;
                     debug_assert!(
                         key.is_some(),
                         "if any sibling is keyed, all siblings must be keyed"
@@ -939,7 +458,7 @@ impl<'b> DiffState<'b> {
         //
         // `shared_prefix_count` is the count of how many nodes at the start of
         // `new` and `old` share the same keys.
-        let (left_offset, right_offset) = match self.diff_keyed_ends(parent, old, new) {
+        let (left_offset, right_offset) = match self.diff_keyed_ends(old, new) {
             Some(count) => count,
             None => return,
         };
@@ -958,25 +477,25 @@ impl<'b> DiffState<'b> {
 
         if new_middle.is_empty() {
             // remove the old elements
-            self.remove_nodes(old_middle, true);
+            self.remove_nodes(old_middle);
         } else if old_middle.is_empty() {
             // there were no old elements, so just create the new elements
             // we need to find the right "foothold" though - we shouldn't use the "append" at all
             if left_offset == 0 {
                 // insert at the beginning of the old list
                 let foothold = &old[old.len() - right_offset];
-                self.create_and_insert_before(parent, new_middle, foothold);
+                self.create_and_insert_before(new_middle, foothold);
             } else if right_offset == 0 {
                 // insert at the end  the old list
                 let foothold = old.last().unwrap();
-                self.create_and_insert_after(parent, new_middle, foothold);
+                self.create_and_insert_after(new_middle, foothold);
             } else {
                 // inserting in the middle
                 let foothold = &old[left_offset - 1];
-                self.create_and_insert_after(parent, new_middle, foothold);
+                self.create_and_insert_after(new_middle, foothold);
             }
         } else {
-            self.diff_keyed_middle(parent, old_middle, new_middle);
+            self.diff_keyed_middle(old_middle, new_middle);
         }
     }
 
@@ -987,7 +506,6 @@ impl<'b> DiffState<'b> {
     /// If there is no offset, then this function returns None and the diffing is complete.
     fn diff_keyed_ends(
         &mut self,
-        parent: ElementId,
         old: &'b [VNode<'b>],
         new: &'b [VNode<'b>],
     ) -> Option<(usize, usize)> {
@@ -995,24 +513,24 @@ impl<'b> DiffState<'b> {
 
         for (old, new) in old.iter().zip(new.iter()) {
             // abort early if we finally run into nodes with different keys
-            if old.key() != new.key() {
+            if old.key != new.key {
                 break;
             }
-            self.diff_node(parent, old, new);
+            self.diff_node(old, new);
             left_offset += 1;
         }
 
         // If that was all of the old children, then create and append the remaining
         // new children and we're finished.
         if left_offset == old.len() {
-            self.create_and_insert_after(parent, &new[left_offset..], old.last().unwrap());
+            self.create_and_insert_after(&new[left_offset..], old.last().unwrap());
             return None;
         }
 
         // And if that was all of the new children, then remove all of the remaining
         // old children and we're finished.
         if left_offset == new.len() {
-            self.remove_nodes(&old[left_offset..], true);
+            self.remove_nodes(&old[left_offset..]);
             return None;
         }
 
@@ -1020,10 +538,10 @@ impl<'b> DiffState<'b> {
         let mut right_offset = 0;
         for (old, new) in old.iter().rev().zip(new.iter().rev()) {
             // abort early if we finally run into nodes with different keys
-            if old.key() != new.key() {
+            if old.key != new.key {
                 break;
             }
-            self.diff_node(parent, old, new);
+            self.diff_node(old, new);
             right_offset += 1;
         }
 
@@ -1044,7 +562,7 @@ impl<'b> DiffState<'b> {
     //
     // Upon exit from this function, it will be restored to that same self.
     #[allow(clippy::too_many_lines)]
-    fn diff_keyed_middle(&mut self, parent: ElementId, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
+    fn diff_keyed_middle(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
         /*
         1. Map the old keys into a numerical ordering based on indices.
         2. Create a map of old key to its index
@@ -1066,11 +584,10 @@ impl<'b> DiffState<'b> {
         8. Finally, generate instructions to remove any old children.
         9. Generate instructions to finally diff children that are the same between both
         */
-
         // 0. Debug sanity checks
         // Should have already diffed the shared-key prefixes and suffixes.
-        debug_assert_ne!(new.first().map(VNode::key), old.first().map(VNode::key));
-        debug_assert_ne!(new.last().map(VNode::key), old.last().map(VNode::key));
+        debug_assert_ne!(new.first().map(|i| i.key), old.first().map(|i| i.key));
+        debug_assert_ne!(new.last().map(|i| i.key), old.last().map(|i| i.key));
 
         // 1. Map the old keys into a numerical ordering based on indices.
         // 2. Create a map of old key to its index
@@ -1078,7 +595,7 @@ impl<'b> DiffState<'b> {
         let old_key_to_old_index = old
             .iter()
             .enumerate()
-            .map(|(i, o)| (o.key().unwrap(), i))
+            .map(|(i, o)| (o.key.unwrap(), i))
             .collect::<FxHashMap<_, _>>();
 
         let mut shared_keys = FxHashSet::default();
@@ -1087,7 +604,7 @@ impl<'b> DiffState<'b> {
         let new_index_to_old_index = new
             .iter()
             .map(|node| {
-                let key = node.key().unwrap();
+                let key = node.key.unwrap();
                 if let Some(&index) = old_key_to_old_index.get(&key) {
                     shared_keys.insert(key);
                     index
@@ -1100,15 +617,15 @@ impl<'b> DiffState<'b> {
         // If none of the old keys are reused by the new children, then we remove all the remaining old children and
         // create the new children afresh.
         if shared_keys.is_empty() {
-            if let Some(first_old) = old.get(0) {
-                self.remove_nodes(&old[1..], true);
-                let mut nodes_created = Vec::new();
-                self.create_children(parent, new, &mut nodes_created);
-                self.replace_inner(first_old, nodes_created);
+            if old.get(0).is_some() {
+                self.remove_nodes(&old[1..]);
+                self.replace_many(&old[0], new);
             } else {
                 // I think this is wrong - why are we appending?
                 // only valid of the if there are no trailing elements
-                self.create_and_append_children(parent, new);
+                // self.create_and_append_children(new);
+
+                todo!("we should never be appending - just creating N");
             }
             return;
         }
@@ -1116,9 +633,9 @@ impl<'b> DiffState<'b> {
         // remove any old children that are not shared
         // todo: make this an iterator
         for child in old {
-            let key = child.key().unwrap();
+            let key = child.key.unwrap();
             if !shared_keys.contains(&key) {
-                self.remove_nodes([child], true);
+                self.remove_node(child);
             }
         }
 
@@ -1146,10 +663,10 @@ impl<'b> DiffState<'b> {
         }
 
         for idx in &lis_sequence {
-            self.diff_node(parent, &old[new_index_to_old_index[*idx]], &new[*idx]);
+            self.diff_node(&old[new_index_to_old_index[*idx]], &new[*idx]);
         }
 
-        let mut nodes_created = Vec::new();
+        let mut nodes_created = 0;
 
         // add mount instruction for the first items not covered by the lis
         let last = *lis_sequence.last().unwrap();
@@ -1158,16 +675,19 @@ impl<'b> DiffState<'b> {
                 let new_idx = idx + last + 1;
                 let old_index = new_index_to_old_index[new_idx];
                 if old_index == u32::MAX as usize {
-                    self.create_node(parent, new_node, &mut nodes_created);
+                    nodes_created += self.create(new_node);
                 } else {
-                    self.diff_node(parent, &old[old_index], new_node);
-                    self.get_all_real_nodes(new_node, &mut nodes_created);
+                    self.diff_node(&old[old_index], new_node);
+                    nodes_created += self.push_all_real_nodes(new_node);
                 }
             }
 
-            let last = Some(self.find_last_element(&new[last]).unwrap().as_u64());
-            self.mutations.insert_after(last, nodes_created);
-            nodes_created = Vec::new();
+            let id = self.find_last_element(&new[last]);
+            self.mutations.push(Mutation::InsertAfter {
+                id,
+                m: nodes_created,
+            });
+            nodes_created = 0;
         }
 
         // for each spacing, generate a mount instruction
@@ -1179,17 +699,20 @@ impl<'b> DiffState<'b> {
                     let new_idx = idx + next + 1;
                     let old_index = new_index_to_old_index[new_idx];
                     if old_index == u32::MAX as usize {
-                        self.create_node(parent, new_node, &mut nodes_created);
+                        nodes_created += self.create(new_node);
                     } else {
-                        self.diff_node(parent, &old[old_index], new_node);
-                        self.get_all_real_nodes(new_node, &mut nodes_created);
+                        self.diff_node(&old[old_index], new_node);
+                        nodes_created += self.push_all_real_nodes(new_node);
                     }
                 }
 
-                let first = Some(self.find_first_element(&new[last]).unwrap().as_u64());
-                self.mutations.insert_before(first, nodes_created);
+                let id = self.find_first_element(&new[last]);
+                self.mutations.push(Mutation::InsertBefore {
+                    id,
+                    m: nodes_created,
+                });
 
-                nodes_created = Vec::new();
+                nodes_created = 0;
             }
             last = *next;
         }
@@ -1200,327 +723,227 @@ impl<'b> DiffState<'b> {
             for (idx, new_node) in new[..first_lis].iter().enumerate() {
                 let old_index = new_index_to_old_index[idx];
                 if old_index == u32::MAX as usize {
-                    self.create_node(parent, new_node, &mut nodes_created);
+                    nodes_created += self.create(new_node);
                 } else {
-                    self.diff_node(parent, &old[old_index], new_node);
-                    self.get_all_real_nodes(new_node, &mut nodes_created);
+                    self.diff_node(&old[old_index], new_node);
+                    nodes_created += self.push_all_real_nodes(new_node);
                 }
             }
 
-            let first = Some(self.find_first_element(&new[first_lis]).unwrap().as_u64());
-            self.mutations.insert_before(first, nodes_created);
+            let id = self.find_first_element(&new[first_lis]);
+            self.mutations.push(Mutation::InsertBefore {
+                id,
+                m: nodes_created,
+            });
         }
     }
 
-    pub fn replace_node(&mut self, parent: ElementId, old: &'b VNode<'b>, new: &'b VNode<'b>) {
-        let mut nodes_vec = Vec::new();
-        self.create_node(parent, new, &mut nodes_vec);
-        self.replace_inner(old, nodes_vec);
+    /// Remove these nodes from the dom
+    /// Wont generate mutations for the inner nodes
+    fn remove_nodes(&mut self, nodes: &'b [VNode<'b>]) {
+        // note that we iterate in reverse to unlink lists of nodes in their rough index order
+        nodes.iter().rev().for_each(|node| self.remove_node(node));
     }
 
-    fn replace_inner(&mut self, old: &'b VNode<'b>, nodes_created: Vec<u64>) {
-        match old {
-            VNode::Element(el) => {
-                let id = old
-                    .try_mounted_id()
-                    .unwrap_or_else(|| panic!("broke on {:?}", old));
-
-                self.mutations
-                    .replace_with(Some(id.as_u64()), nodes_created);
-                self.remove_nodes(el.children, false);
-                self.scopes.collect_garbage(id);
-            }
-
-            VNode::Text(_) | VNode::Placeholder(_) => {
-                let id = old
-                    .try_mounted_id()
-                    .unwrap_or_else(|| panic!("broke on {:?}", old));
-
-                self.mutations
-                    .replace_with(Some(id.as_u64()), nodes_created);
-                self.scopes.collect_garbage(id);
-            }
-
-            VNode::Fragment(f) => {
-                self.replace_inner(&f.children[0], nodes_created);
-                self.remove_nodes(f.children.iter().skip(1), true);
-            }
-
-            VNode::Component(c) => {
-                log::trace!("Replacing component {:?}", old);
-                let scope_id = c.scope.get().unwrap();
-                let node = self.scopes.fin_head(scope_id);
-
-                self.enter_scope(scope_id);
-                {
-                    self.replace_inner(node, nodes_created);
-
-                    log::trace!("Replacing component x2 {:?}", old);
-
-                    let scope = self.scopes.get_scope(scope_id).unwrap();
-                    c.scope.set(None);
-                    let props = scope.props.take().unwrap();
-                    c.props.borrow_mut().replace(props);
-                    self.scopes.try_remove(scope_id);
-                }
-                self.leave_scope();
-            }
-
-            VNode::TemplateRef(template_ref) => {
-                let templates = self.scopes.templates.borrow();
-                let template = templates.get(&template_ref.template_id).unwrap().borrow();
-                let mut root_iter = template.root_nodes().iter();
-                let first_real_id = template_ref.get_node_id(*root_iter.next().unwrap());
-                self.mutations
-                    .replace_with(Some(first_real_id.as_u64()), nodes_created);
-                for id in root_iter {
-                    let real_id = template_ref.get_node_id(*id);
-                    self.mutations.remove(Some(real_id.as_u64()));
-                }
+    fn remove_node(&mut self, node: &'b VNode<'b>) {
+        for (idx, _) in node.template.roots.iter().enumerate() {
+            let id = match node.dynamic_root(idx) {
+                Some(Text(t)) => t.id.get(),
+                Some(Placeholder(t)) => t.get(),
+                Some(Fragment(t)) => return self.remove_nodes(t),
+                Some(Component(comp)) => return self.remove_component(comp.scope.get().unwrap()),
+                None => node.root_ids[idx].get(),
+            };
 
-                self.remove_nodes(template_ref.dynamic_context.nodes, true);
+            self.mutations.push(Mutation::Remove { id })
+        }
 
-                if let Some(id) = template_ref.template_ref_id.get() {
-                    self.scopes.template_refs.borrow_mut().remove(id.0);
-                }
+        self.clean_up_node(node);
 
-                for id in template_ref.node_ids.borrow().iter() {
-                    if let Some(id) = id.get() {
-                        self.scopes.collect_garbage(*id);
-                    }
-                }
+        for root in node.root_ids {
+            let id = root.get();
+            if id.0 != 0 {
+                self.reclaim(id);
             }
         }
     }
 
-    pub fn remove_nodes(&mut self, nodes: impl IntoIterator<Item = &'b VNode<'b>>, gen_muts: bool) {
-        for node in nodes {
-            match node {
-                VNode::Text(t) => {
-                    // this check exists because our null node will be removed but does not have an ID
-                    if let Some(id) = t.id.get() {
-                        self.scopes.collect_garbage(id);
-                        t.id.set(None);
-
-                        if gen_muts {
-                            self.mutations.remove(Some(id.as_u64()));
-                        }
-                    }
-                }
-                VNode::Placeholder(a) => {
-                    let id = a.id.get().unwrap();
-                    self.scopes.collect_garbage(id);
-                    a.id.set(None);
-
-                    if gen_muts {
-                        self.mutations.remove(Some(id.as_u64()));
-                    }
-                }
-                VNode::Element(e) => {
-                    let id = e.id.get().unwrap();
+    fn remove_component(&mut self, scope_id: ScopeId) {
+        let height = self.scopes[scope_id.0].height;
+        self.dirty_scopes.remove(&DirtyScope {
+            height,
+            id: scope_id,
+        });
 
-                    if gen_muts {
-                        self.mutations.remove(Some(id.as_u64()));
-                    }
+        // I promise, since we're descending down the tree, this is safe
+        match unsafe { self.scopes[scope_id.0].root_node().extend_lifetime_ref() } {
+            RenderReturn::Sync(Ok(t)) => self.remove_node(t),
+            _ => todo!("cannot handle nonstandard nodes"),
+        }
+    }
 
-                    self.scopes.collect_garbage(id);
-                    e.id.set(None);
+    /// Push all the real nodes on the stack
+    fn push_all_real_nodes(&mut self, node: &'b VNode<'b>) -> usize {
+        let mut onstack = 0;
 
-                    self.remove_nodes(e.children, false);
+        for (idx, _) in node.template.roots.iter().enumerate() {
+            match node.dynamic_root(idx) {
+                Some(Text(t)) => {
+                    self.mutations.push(Mutation::PushRoot { id: t.id.get() });
+                    onstack += 1;
                 }
-
-                VNode::Fragment(f) => {
-                    self.remove_nodes(f.children, gen_muts);
+                Some(Placeholder(t)) => {
+                    self.mutations.push(Mutation::PushRoot { id: t.get() });
+                    onstack += 1;
                 }
-
-                VNode::Component(c) => {
-                    self.enter_scope(c.scope.get().unwrap());
-                    {
-                        let scope_id = c.scope.get().unwrap();
-                        let root = self.scopes.root_node(scope_id);
-                        self.remove_nodes([root], gen_muts);
-
-                        let scope = self.scopes.get_scope(scope_id).unwrap();
-                        c.scope.set(None);
-
-                        let props = scope.props.take().unwrap();
-                        c.props.borrow_mut().replace(props);
-                        self.scopes.try_remove(scope_id);
+                Some(Fragment(nodes)) => {
+                    for node in *nodes {
+                        onstack += self.push_all_real_nodes(node);
                     }
-                    self.leave_scope();
                 }
-
-                VNode::TemplateRef(template_ref) => {
-                    let templates = self.scopes.templates.borrow();
-                    let template = templates.get(&template_ref.template_id).unwrap().borrow();
-                    if gen_muts {
-                        for id in template.root_nodes() {
-                            let real_id = template_ref.get_node_id(*id);
-                            self.mutations.remove(Some(real_id.as_u64()));
-                        }
-                    }
-
-                    self.remove_nodes(template_ref.dynamic_context.nodes, gen_muts);
-
-                    if let Some(id) = template_ref.template_ref_id.get() {
-                        self.scopes.template_refs.borrow_mut().remove(id.0);
-                    }
-
-                    for id in template_ref.node_ids.borrow().iter() {
-                        if let Some(id) = id.get() {
-                            self.scopes.collect_garbage(*id);
+                Some(Component(comp)) => {
+                    let scope = comp.scope.get().unwrap();
+                    onstack +=
+                        match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } {
+                            RenderReturn::Sync(Ok(node)) => self.push_all_real_nodes(node),
+                            _ => todo!(),
                         }
-                    }
                 }
-            }
+                None => {
+                    self.mutations.push(Mutation::PushRoot {
+                        id: node.root_ids[idx].get(),
+                    });
+                    onstack += 1;
+                }
+            };
         }
-    }
 
-    fn create_children(
-        &mut self,
-        parent: ElementId,
-        nodes: &'b [VNode<'b>],
-        nodes_vec: &mut Vec<u64>,
-    ) {
-        nodes_vec.reserve(nodes.len());
-        for node in nodes {
-            self.create_node(parent, node, nodes_vec);
-        }
+        onstack
     }
 
-    fn create_and_append_children(&mut self, parent: ElementId, nodes: &'b [VNode<'b>]) {
-        let mut nodes_vec = Vec::with_capacity(nodes.len());
-        self.create_children(parent, nodes, &mut nodes_vec);
-        self.mutations
-            .append_children(Some(parent.as_u64()), nodes_vec);
+    fn create_children(&mut self, nodes: &'b [VNode<'b>]) -> usize {
+        nodes.iter().fold(0, |acc, child| acc + self.create(child))
     }
 
-    fn create_and_insert_after(
-        &mut self,
-        parent: ElementId,
-        nodes: &'b [VNode<'b>],
-        after: &'b VNode<'b>,
-    ) {
-        let mut nodes_vec = Vec::with_capacity(nodes.len());
-        self.create_children(parent, nodes, &mut nodes_vec);
-        let last = self.find_last_element(after).unwrap();
-        self.mutations.insert_after(Some(last.as_u64()), nodes_vec);
+    fn create_and_insert_before(&mut self, new: &'b [VNode<'b>], before: &'b VNode<'b>) {
+        let m = self.create_children(new);
+        let id = self.find_first_element(before);
+        self.mutations.push(Mutation::InsertBefore { id, m })
     }
 
-    fn create_and_insert_before(
-        &mut self,
-        parent: ElementId,
-        nodes: &'b [VNode<'b>],
-        before: &'b VNode<'b>,
-    ) {
-        let mut nodes_vec = Vec::with_capacity(nodes.len());
-        self.create_children(parent, nodes, &mut nodes_vec);
-        let first = self.find_first_element(before).unwrap();
-        self.mutations
-            .insert_before(Some(first.as_u64()), nodes_vec);
-    }
-
-    pub fn current_scope(&self) -> ScopeId {
-        self.scope_stack.last().copied().expect("no current scope")
-    }
-
-    fn enter_scope(&mut self, scope: ScopeId) {
-        self.scope_stack.push(scope);
-    }
-
-    fn leave_scope(&mut self) {
-        self.scope_stack.pop();
+    fn create_and_insert_after(&mut self, new: &'b [VNode<'b>], after: &'b VNode<'b>) {
+        let m = self.create_children(new);
+        let id = self.find_last_element(after);
+        self.mutations.push(Mutation::InsertAfter { id, m })
     }
 
-    fn find_last_element(&mut self, vnode: &'b VNode<'b>) -> Option<ElementId> {
-        let mut search_node = Some(vnode);
-        loop {
-            match &search_node.take().unwrap() {
-                VNode::Text(t) => break t.id.get(),
-                VNode::Element(t) => break t.id.get(),
-                VNode::Placeholder(t) => break t.id.get(),
-                VNode::Fragment(frag) => search_node = frag.children.last(),
-                VNode::Component(el) => {
-                    let scope_id = el.scope.get().unwrap();
-                    search_node = Some(self.scopes.root_node(scope_id));
-                }
-                VNode::TemplateRef(t) => {
-                    let templates = self.scopes.templates.borrow();
-                    let template = templates.get(&t.template_id).unwrap();
-                    let template = template.borrow();
-                    break template.root_nodes().last().map(|id| t.get_node_id(*id));
+    fn find_first_element(&self, node: &'b VNode<'b>) -> ElementId {
+        match node.dynamic_root(0) {
+            None => node.root_ids[0].get(),
+            Some(Text(t)) => t.id.get(),
+            Some(Fragment(t)) => self.find_first_element(&t[0]),
+            Some(Placeholder(t)) => t.get(),
+            Some(Component(comp)) => {
+                let scope = comp.scope.get().unwrap();
+                match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } {
+                    RenderReturn::Sync(Ok(t)) => self.find_first_element(t),
+                    _ => todo!("cannot handle nonstandard nodes"),
                 }
             }
         }
     }
 
-    fn find_first_element(&mut self, vnode: &'b VNode<'b>) -> Option<ElementId> {
-        let mut search_node = Some(vnode);
-        loop {
-            match &search_node.take().expect("search node to have an ID") {
-                VNode::Text(t) => break t.id.get(),
-                VNode::Element(t) => break t.id.get(),
-                VNode::Placeholder(t) => break t.id.get(),
-                VNode::Fragment(frag) => search_node = Some(&frag.children[0]),
-                VNode::Component(el) => {
-                    let scope = el.scope.get().expect("element to have a scope assigned");
-                    search_node = Some(self.scopes.root_node(scope));
-                }
-                VNode::TemplateRef(t) => {
-                    let templates = self.scopes.templates.borrow();
-                    let template = templates.get(&t.template_id).unwrap();
-                    let template = template.borrow();
-                    break template.root_nodes().first().map(|id| t.get_node_id(*id));
+    fn find_last_element(&self, node: &'b VNode<'b>) -> ElementId {
+        match node.dynamic_root(node.template.roots.len() - 1) {
+            None => node.root_ids.last().unwrap().get(),
+            Some(Text(t)) => t.id.get(),
+            Some(Fragment(t)) => self.find_last_element(t.last().unwrap()),
+            Some(Placeholder(t)) => t.get(),
+            Some(Component(comp)) => {
+                let scope = comp.scope.get().unwrap();
+                match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } {
+                    RenderReturn::Sync(Ok(t)) => self.find_last_element(t),
+                    _ => todo!("cannot handle nonstandard nodes"),
                 }
             }
         }
     }
 
-    // recursively push all the nodes of a tree onto the stack and return how many are there
-    fn get_all_real_nodes(&mut self, node: &'b VNode<'b>, nodes: &mut Vec<u64>) {
-        match node {
-            VNode::Text(_) | VNode::Placeholder(_) | VNode::Element(_) => {
-                nodes.push(node.mounted_id().0 as u64);
-            }
+    fn replace(&mut self, left: &'b VNode<'b>, right: &'b VNode<'b>) {
+        let first = self.find_first_element(left);
+        let id = self.replace_inner(left);
+        let created = self.create(right);
+        self.mutations.push(Mutation::ReplaceWith {
+            id: first,
+            m: created,
+        });
+        self.try_reclaim(id);
+    }
 
-            VNode::TemplateRef(template_ref) => {
-                let templates = self.scopes.templates.borrow();
-                let template = templates.get(&template_ref.template_id).unwrap();
-                let template = template.borrow();
-                nodes.extend(
-                    template
-                        .root_nodes()
-                        .iter()
-                        .map(|id| template_ref.get_node_id(*id).as_u64()),
-                );
-            }
+    fn replace_many(&mut self, left: &'b VNode<'b>, right: &'b [VNode<'b>]) {
+        let first = self.find_first_element(left);
+        let id = self.replace_inner(left);
+        let created = self.create_children(right);
+        self.mutations.push(Mutation::ReplaceWith {
+            id: first,
+            m: created,
+        });
+        self.try_reclaim(id);
+    }
+}
 
-            VNode::Fragment(frag) => {
-                nodes.reserve(frag.children.len());
-                for child in frag.children {
-                    self.get_all_real_nodes(child, nodes);
-                }
-            }
+fn matching_components<'a>(
+    left: &'a VNode<'a>,
+    right: &'a VNode<'a>,
+) -> Option<Vec<(&'a VComponent<'a>, &'a VComponent<'a>)>> {
+    if left.template.roots.len() != right.template.roots.len() {
+        return None;
+    }
+
+    // run through the components, ensuring they're the same
+    left.template
+        .roots
+        .iter()
+        .zip(right.template.roots.iter())
+        .map(|(l, r)| {
+            let (l, r) = match (l, r) {
+                (TemplateNode::Dynamic { id: l }, TemplateNode::Dynamic { id: r }) => (l, r),
+                _ => return None,
+            };
 
-            VNode::Component(c) => {
-                let scope_id = c.scope.get().unwrap();
-                let root = self.scopes.root_node(scope_id);
-                self.get_all_real_nodes(root, nodes);
-            }
-        }
-    }
+            let (l, r) = match (&left.dynamic_nodes[*l], &right.dynamic_nodes[*r]) {
+                (Component(l), Component(r)) => (l, r),
+                _ => return None,
+            };
 
-    pub(crate) fn current_scope_bump(&self) -> &'b Bump {
-        &self
-            .scopes
-            .get_scope(self.current_scope())
-            .unwrap()
-            .fin_frame()
-            .bump
+            Some((l, r))
+        })
+        .collect()
+}
+
+/// We can apply various optimizations to dynamic nodes that are the single child of their parent.
+///
+/// IE
+///  - for text - we can use SetTextContent
+///  - for clearning children we can use RemoveChildren
+///  - for appending children we can use AppendChildren
+#[allow(dead_code)]
+fn is_dyn_node_only_child(node: &VNode, idx: usize) -> bool {
+    let path = node.template.node_paths[idx];
+
+    // use a loop to index every static node's children until the path has run out
+    // only break if the last path index is a dynamic node
+    let mut static_node = &node.template.roots[path[0] as usize];
+
+    for i in 1..path.len() - 1 {
+        match static_node {
+            TemplateNode::Element { children, .. } => static_node = &children[path[i] as usize],
+            _ => return false,
+        }
     }
 
-    pub fn register_template(&mut self, template: &Template, id: ElementId) {
-        let bump = &self.scopes.template_bump;
-        template.create(&mut self.mutations, bump, id);
+    match static_node {
+        TemplateNode::Element { children, .. } => children.len() == 1,
+        _ => false,
     }
 }

+ 19 - 0
packages/core/src/dirty_scope.rs

@@ -0,0 +1,19 @@
+use crate::ScopeId;
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct DirtyScope {
+    pub height: u32,
+    pub id: ScopeId,
+}
+
+impl PartialOrd for DirtyScope {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        Some(self.height.cmp(&other.height))
+    }
+}
+
+impl Ord for DirtyScope {
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        self.height.cmp(&other.height)
+    }
+}

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

@@ -1,190 +0,0 @@
-use std::{fmt::Write, marker::PhantomData, ops::Deref};
-
-use once_cell::sync::Lazy;
-
-use crate::{
-    template::{TemplateNodeId, TextTemplateSegment},
-    AttributeValue, Listener, TextTemplate, 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, Eq)]
-#[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,
-        }
-    }
-}
-
-/// 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: &TextTemplate<TextSegments, Text>,
-    ) -> String
-    where
-        TextSegments: AsRef<[TextTemplateSegment<Text>]>,
-        Text: AsRef<str>,
-    {
-        let mut result = String::with_capacity(text.min_size);
-        self.resolve_text_into(text, &mut result);
-        result
-    }
-
-    /// Resolve text and writes the result
-    pub fn resolve_text_into<TextSegments, Text>(
-        &self,
-        text: &TextTemplate<TextSegments, Text>,
-        result: &mut impl Write,
-    ) where
-        TextSegments: AsRef<[TextTemplateSegment<Text>]>,
-        Text: AsRef<str>,
-    {
-        for seg in text.segments.as_ref() {
-            match seg {
-                TextTemplateSegment::Static(s) => {
-                    let _ = result.write_str(s.as_ref());
-                }
-                TextTemplateSegment::Dynamic(idx) => {
-                    let _ = result.write_str(self.text_segments[*idx]);
-                }
-            }
-        }
-    }
-
-    /// 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]
-    }
-}

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

@@ -0,0 +1,19 @@
+use std::cell::RefCell;
+
+use crate::ScopeId;
+
+/// A boundary that will capture any errors from child components
+#[allow(dead_code)]
+pub struct ErrorBoundary {
+    error: RefCell<Option<(anyhow::Error, ScopeId)>>,
+    id: ScopeId,
+}
+
+impl ErrorBoundary {
+    pub fn new(id: ScopeId) -> Self {
+        Self {
+            error: RefCell::new(None),
+            id,
+        }
+    }
+}

+ 127 - 144
packages/core/src/events.rs

@@ -1,182 +1,165 @@
-//! Internal and external event system
-//!
-//!
-//! This is all kinda WIP, but the bones are there.
+use std::{
+    cell::{Cell, RefCell},
+    rc::Rc,
+};
 
-use crate::{ElementId, ScopeId};
-use std::{any::Any, cell::Cell, fmt::Debug, rc::Rc, sync::Arc};
-
-pub(crate) struct BubbleState {
-    pub canceled: Cell<bool>,
-}
-
-impl BubbleState {
-    pub fn new() -> Self {
-        Self {
-            canceled: Cell::new(false),
-        }
-    }
-}
-
-/// User Events are events that are shuttled from the renderer into the [`VirtualDom`] through the scheduler channel.
-///
-/// These events will be passed to the appropriate Element given by `mounted_dom_id` and then bubbled up through the tree
-/// where each listener is checked and fired if the event name matches.
+/// A wrapper around some generic data that handles the event's state
 ///
-/// It is the expectation that the event name matches the corresponding event listener, otherwise Dioxus will panic in
-/// attempting to downcast the event data.
 ///
-/// Because Event Data is sent across threads, it must be `Send + Sync`. We are hoping to lift the `Sync` restriction but
-/// `Send` will not be lifted. The entire `UserEvent` must also be `Send + Sync` due to its use in the scheduler channel.
+/// Prevent this event from continuing to bubble up the tree to parent elements.
 ///
 /// # Example
+///
 /// ```rust, ignore
-/// fn App(cx: Scope) -> Element {
-///     render!(div {
-///         onclick: move |_| println!("Clicked!")
-///     })
-/// }
+/// rsx! {
+///     button {
+///         onclick: move |evt: Event<MouseData>| {
+///             evt.cancel_bubble();
 ///
-/// let mut dom = VirtualDom::new(App);
-/// let mut scheduler = dom.get_scheduler_channel();
-/// scheduler.unbounded_send(SchedulerMsg::UiEvent(
-///     UserEvent {
-///         scope_id: None,
-///         priority: EventPriority::Medium,
-///         name: "click",
-///         element: Some(ElementId(0)),
-///         data: Arc::new(ClickEvent { .. })
+///         }
 ///     }
-/// )).unwrap();
+/// }
 /// ```
-#[derive(Debug, Clone)]
-pub struct UserEvent {
-    /// The originator of the event trigger if available
-    pub scope_id: Option<ScopeId>,
-
-    /// The priority of the event to be scheduled around ongoing work
-    pub priority: EventPriority,
-
-    /// The optional real node associated with the trigger
-    pub element: Option<ElementId>,
-
-    /// The event type IE "onclick" or "onmouseover"
-    pub name: &'static str,
-
-    /// If the event is bubbles up through the vdom
-    pub bubbles: bool,
-
-    /// The event data to be passed onto the event handler
-    pub data: Arc<dyn Any + Send + Sync>,
+pub struct Event<T: 'static + ?Sized> {
+    /// The data associated with this event
+    pub data: Rc<T>,
+    pub(crate) propogates: Rc<Cell<bool>>,
 }
 
-/// Priority of Event Triggers.
-///
-/// Internally, Dioxus will abort work that's taking too long if new, more important work arrives. Unlike React, Dioxus
-/// won't be afraid to pause work or flush changes to the Real Dom. This is called "cooperative scheduling". Some Renderers
-/// implement this form of scheduling internally, however Dioxus will perform its own scheduling as well.
-///
-/// The ultimate goal of the scheduler is to manage latency of changes, prioritizing "flashier" changes over "subtler" changes.
-///
-/// React has a 5-tier priority system. However, they break things into "Continuous" and "Discrete" priority. For now,
-/// we keep it simple, and just use a 3-tier priority system.
-///
-/// - `NoPriority` = 0
-/// - `LowPriority` = 1
-/// - `NormalPriority` = 2
-/// - `UserBlocking` = 3
-/// - `HighPriority` = 4
-/// - `ImmediatePriority` = 5
-///
-/// We still have a concept of discrete vs continuous though - discrete events won't be batched, but continuous events will.
-/// This means that multiple "scroll" events will be processed in a single frame, but multiple "click" events will be
-/// flushed before proceeding. Multiple discrete events is highly unlikely, though.
-#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
-pub enum EventPriority {
-    /// Work that must be completed during the EventHandler phase.
+impl<T> Event<T> {
+    /// Prevent this event from continuing to bubble up the tree to parent elements.
     ///
-    /// Currently this is reserved for controlled inputs.
-    Immediate = 3,
-
-    /// "High Priority" work will not interrupt other high priority work, but will interrupt medium and low priority work.
-    ///
-    /// This is typically reserved for things like user interaction.
+    /// # Example
     ///
-    /// React calls these "discrete" events, but with an extra category of "user-blocking" (Immediate).
-    High = 2,
+    /// ```rust, ignore
+    /// rsx! {
+    ///     button {
+    ///         onclick: move |evt: Event<MouseData>| {
+    ///             evt.cancel_bubble();
+    ///         }
+    ///     }
+    /// }
+    /// ```
+    #[deprecated = "use stop_propogation instead"]
+    pub fn cancel_bubble(&self) {
+        self.propogates.set(false);
+    }
 
-    /// "Medium priority" work is generated by page events not triggered by the user. These types of events are less important
-    /// than "High Priority" events and will take precedence over low priority events.
+    /// Prevent this event from continuing to bubble up the tree to parent elements.
     ///
-    /// This is typically reserved for VirtualEvents that are not related to keyboard or mouse input.
+    /// # Example
     ///
-    /// React calls these "continuous" events (e.g. mouse move, mouse wheel, touch move, etc).
-    Medium = 1,
+    /// ```rust, ignore
+    /// rsx! {
+    ///     button {
+    ///         onclick: move |evt: Event<MouseData>| {
+    ///             evt.cancel_bubble();
+    ///         }
+    ///     }
+    /// }
+    /// ```
+    pub fn stop_propogation(&self) {
+        self.propogates.set(false);
+    }
 
-    /// "Low Priority" work will always be preempted unless the work is significantly delayed, in which case it will be
-    /// advanced to the front of the work queue until completed.
-    ///
-    /// The primary user of Low Priority work is the asynchronous work system (Suspense).
+    /// Get a reference to the inner data from this event
     ///
-    /// This is considered "idle" work or "background" work.
-    Low = 0,
+    /// ```rust, ignore
+    /// rsx! {
+    ///     button {
+    ///         onclick: move |evt: Event<MouseData>| {
+    ///             let data = evt.inner.clone();
+    ///             cx.spawn(async move {
+    ///                 println!("{:?}", data);
+    ///             });
+    ///         }
+    ///     }
+    /// }
+    /// ```
+    pub fn inner(&self) -> &Rc<T> {
+        &self.data
+    }
 }
 
-/// The internal Dioxus type that carries any event data to the relevant handler.
-
-pub struct AnyEvent {
-    pub(crate) bubble_state: Rc<BubbleState>,
-    pub(crate) data: Arc<dyn Any + Send + Sync>,
+impl<T: ?Sized> Clone for Event<T> {
+    fn clone(&self) -> Self {
+        Self {
+            propogates: self.propogates.clone(),
+            data: self.data.clone(),
+        }
+    }
 }
 
-impl AnyEvent {
-    /// Convert this [`AnyEvent`] into a specific [`UiEvent`] with [`EventData`].
-    ///
-    /// ```rust, ignore
-    /// let evt: FormEvent = evvt.downcast().unwrap();
-    /// ```
-    #[must_use]
-    pub fn downcast<T: Send + Sync + 'static>(self) -> Option<UiEvent<T>> {
-        let AnyEvent { data, bubble_state } = self;
+impl<T> std::ops::Deref for Event<T> {
+    type Target = Rc<T>;
+    fn deref(&self) -> &Self::Target {
+        &self.data
+    }
+}
 
-        data.downcast::<T>()
-            .ok()
-            .map(|data| UiEvent { data, bubble_state })
+impl<T: std::fmt::Debug> std::fmt::Debug for Event<T> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("UiEvent")
+            .field("bubble_state", &self.propogates)
+            .field("data", &self.data)
+            .finish()
     }
 }
 
-/// A [`UiEvent`] is a type that wraps various [`EventData`].
+/// The callback type generated by the `rsx!` macro when an `on` field is specified for components.
 ///
-/// You should prefer to use the name of the event directly, rather than
-/// the [`UiEvent`]<T> generic type.
+/// This makes it possible to pass `move |evt| {}` style closures into components as property fields.
 ///
-/// For the HTML crate, this would include `MouseEvent`, `FormEvent` etc.
-pub struct UiEvent<T> {
-    /// The internal data of the event
-    /// This is wrapped in an Arc so that it can be sent across threads
-    pub data: Arc<T>,
-
-    #[allow(unused)]
-    bubble_state: Rc<BubbleState>,
+///
+/// # Example
+///
+/// ```rust, ignore
+/// rsx!{
+///     MyComponent { onclick: move |evt| log::info!("clicked") }
+/// }
+///
+/// #[derive(Props)]
+/// struct MyProps<'a> {
+///     onclick: EventHandler<'a, MouseEvent>,
+/// }
+///
+/// fn MyComponent(cx: Scope<'a, MyProps<'a>>) -> Element {
+///     cx.render(rsx!{
+///         button {
+///             onclick: move |evt| cx.props.onclick.call(evt),
+///         }
+///     })
+/// }
+///
+/// ```
+pub struct EventHandler<'bump, T = ()> {
+    pub(super) callback: RefCell<Option<ExternalListenerCallback<'bump, T>>>,
 }
 
-impl<T: Debug> std::fmt::Debug for UiEvent<T> {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.debug_struct("UiEvent").field("data", &self.data).finish()
+impl<T> Default for EventHandler<'_, T> {
+    fn default() -> Self {
+        Self {
+            callback: Default::default(),
+        }
     }
 }
 
-impl<T> std::ops::Deref for UiEvent<T> {
-    type Target = T;
+type ExternalListenerCallback<'bump, T> = bumpalo::boxed::Box<'bump, dyn FnMut(T) + 'bump>;
 
-    fn deref(&self) -> &Self::Target {
-        self.data.as_ref()
+impl<T> EventHandler<'_, T> {
+    /// Call this event handler with the appropriate event type
+    ///
+    /// This borrows the event using a RefCell. Recursively calling a listener will cause a panic.
+    pub fn call(&self, event: T) {
+        if let Some(callback) = self.callback.borrow_mut().as_mut() {
+            callback(event);
+        }
     }
-}
 
-impl<T> UiEvent<T> {
-    /// Prevent this event from bubbling up the tree.
-    pub fn cancel_bubble(&self) {
-        self.bubble_state.canceled.set(true);
+    /// Forcibly drop the internal handler callback, releasing memory
+    ///
+    /// This will force any future calls to "call" to not doing anything
+    pub fn release(&self) {
+        self.callback.replace(None);
     }
 }

+ 103 - 0
packages/core/src/fragment.rs

@@ -0,0 +1,103 @@
+use crate::innerlude::*;
+
+/// Create inline fragments using Component syntax.
+///
+/// ## Details
+///
+/// Fragments capture a series of children without rendering extra nodes.
+///
+/// Creating fragments explicitly with the Fragment component is particularly useful when rendering lists or tables and
+/// a key is needed to identify each item.
+///
+/// ## Example
+///
+/// ```rust, ignore
+/// rsx!{
+///     Fragment { key: "abc" }
+/// }
+/// ```
+///
+/// ## Usage
+///
+/// Fragments are incredibly useful when necessary, but *do* add cost in the diffing phase.
+/// Try to avoid highly nested fragments if you can. Unlike React, there is no protection against infinitely nested fragments.
+///
+/// This function defines a dedicated `Fragment` component that can be used to create inline fragments in the RSX macro.
+///
+/// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it.
+#[allow(non_upper_case_globals, non_snake_case)]
+pub fn Fragment<'a>(cx: Scope<'a, FragmentProps<'a>>) -> Element {
+    let children = cx.props.0.as_ref().map_err(|e| anyhow::anyhow!("{}", e))?;
+    Ok(VNode {
+        key: children.key,
+        parent: children.parent,
+        template: children.template,
+        root_ids: children.root_ids,
+        dynamic_nodes: children.dynamic_nodes,
+        dynamic_attrs: children.dynamic_attrs,
+    })
+}
+
+pub struct FragmentProps<'a>(Element<'a>);
+pub struct FragmentBuilder<'a, const BUILT: bool>(Element<'a>);
+impl<'a> FragmentBuilder<'a, false> {
+    pub fn children(self, children: Element<'a>) -> FragmentBuilder<'a, true> {
+        FragmentBuilder(children)
+    }
+}
+impl<'a, const A: bool> FragmentBuilder<'a, A> {
+    pub fn build(self) -> FragmentProps<'a> {
+        FragmentProps(self.0)
+    }
+}
+
+/// Access the children elements passed into the component
+///
+/// This enables patterns where a component is passed children from its parent.
+///
+/// ## Details
+///
+/// Unlike React, Dioxus allows *only* lists of children to be passed from parent to child - not arbitrary functions
+/// or classes. If you want to generate nodes instead of accepting them as a list, consider declaring a closure
+/// on the props that takes Context.
+///
+/// If a parent passes children into a component, the child will always re-render when the parent re-renders. In other
+/// words, a component cannot be automatically memoized if it borrows nodes from its parent, even if the component's
+/// props are valid for the static lifetime.
+///
+/// ## Example
+///
+/// ```rust, ignore
+/// fn App(cx: Scope) -> Element {
+///     cx.render(rsx!{
+///         CustomCard {
+///             h1 {}
+///             p {}
+///         }
+///     })
+/// }
+///
+/// #[derive(PartialEq, Props)]
+/// struct CardProps {
+///     children: Element
+/// }
+///
+/// fn CustomCard(cx: Scope<CardProps>) -> Element {
+///     cx.render(rsx!{
+///         div {
+///             h1 {"Title card"}
+///             {cx.props.children}
+///         }
+///     })
+/// }
+/// ```
+impl<'a> Properties for FragmentProps<'a> {
+    type Builder = FragmentBuilder<'a, false>;
+    const IS_STATIC: bool = false;
+    fn builder() -> Self::Builder {
+        FragmentBuilder(VNode::empty())
+    }
+    unsafe fn memoize(&self, _other: &Self) -> bool {
+        false
+    }
+}

+ 19 - 91
packages/core/src/lazynodes.rs

@@ -3,7 +3,7 @@
 //! This module provides support for a type called `LazyNodes` which is a micro-heap located on the stack to make calls
 //! to `rsx!` more efficient.
 //!
-//! To support returning rsx! from branches in match statements, we need to use dynamic dispatch on [`NodeFactory`] closures.
+//! To support returning rsx! from branches in match statements, we need to use dynamic dispatch on [`ScopeState`] closures.
 //!
 //! This can be done either through boxing directly, or by using dynamic-sized-types and a custom allocator. In our case,
 //! we build a tiny alloactor in the stack and allocate the closure into that.
@@ -11,13 +11,13 @@
 //! The logic for this was borrowed from <https://docs.rs/stack_dst/0.6.1/stack_dst/>. Unfortunately, this crate does not
 //! support non-static closures, so we've implemented the core logic of `ValueA` in this module.
 
-use crate::innerlude::{NodeFactory, VNode};
+use crate::{innerlude::VNode, ScopeState};
 use std::mem;
 
 /// A concrete type provider for closures that build [`VNode`] structures.
 ///
 /// This struct wraps lazy structs that build [`VNode`] trees Normally, we cannot perform a blanket implementation over
-/// closures, but if we wrap the closure in a concrete type, we can maintain separate implementations of [`IntoVNode`].
+/// closures, but if we wrap the closure in a concrete type, we can use it for different branches in matching.
 ///
 ///
 /// ```rust, ignore
@@ -31,7 +31,7 @@ type StackHeapSize = [usize; 16];
 
 enum StackNodeStorage<'a, 'b> {
     Stack(LazyStack),
-    Heap(Box<dyn FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>> + 'b>),
+    Heap(Box<dyn FnMut(Option<&'a ScopeState>) -> Option<VNode<'a>> + 'b>),
 }
 
 impl<'a, 'b> LazyNodes<'a, 'b> {
@@ -40,11 +40,11 @@ impl<'a, 'b> LazyNodes<'a, 'b> {
     /// If the closure cannot fit into the stack allocation (16 bytes), then it
     /// is placed on the heap. Most closures will fit into the stack, and is
     /// the most optimal way to use the creation function.
-    pub fn new(val: impl FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b) -> Self {
+    pub fn new(val: impl FnOnce(&'a ScopeState) -> VNode<'a> + 'b) -> Self {
         // there's no way to call FnOnce without a box, so we need to store it in a slot and use static dispatch
         let mut slot = Some(val);
 
-        let val = move |fac: Option<NodeFactory<'a>>| {
+        let val = move |fac: Option<&'a ScopeState>| {
             fac.map(
                 slot.take()
                     .expect("LazyNodes closure to be called only once"),
@@ -65,13 +65,13 @@ impl<'a, 'b> LazyNodes<'a, 'b> {
     /// Create a new [`LazyNodes`] closure, but force it onto the heap.
     pub fn new_boxed<F>(inner: F) -> Self
     where
-        F: FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b,
+        F: FnOnce(&'a ScopeState) -> VNode<'a> + 'b,
     {
         // there's no way to call FnOnce without a box, so we need to store it in a slot and use static dispatch
         let mut slot = Some(inner);
 
         Self {
-            inner: StackNodeStorage::Heap(Box::new(move |fac: Option<NodeFactory<'a>>| {
+            inner: StackNodeStorage::Heap(Box::new(move |fac: Option<&'a ScopeState>| {
                 fac.map(
                     slot.take()
                         .expect("LazyNodes closure to be called only once"),
@@ -82,9 +82,9 @@ impl<'a, 'b> LazyNodes<'a, 'b> {
 
     unsafe fn new_inner<F>(val: F) -> Self
     where
-        F: FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>> + 'b,
+        F: FnMut(Option<&'a ScopeState>) -> Option<VNode<'a>> + 'b,
     {
-        let mut ptr: *const _ = &val as &dyn FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>>;
+        let mut ptr: *const _ = &val as &dyn FnMut(Option<&'a ScopeState>) -> Option<VNode<'a>>;
 
         assert_eq!(
             ptr as *const u8, &val as *const _ as *const u8,
@@ -160,12 +160,10 @@ impl<'a, 'b> LazyNodes<'a, 'b> {
     /// ```rust, ignore
     /// let f = LazyNodes::new(move |f| f.element("div", [], [], [] None));
     ///
-    /// let fac = NodeFactory::new(&cx);
-    ///
     /// let node = f.call(cac);
     /// ```
     #[must_use]
-    pub fn call(self, f: NodeFactory<'a>) -> VNode<'a> {
+    pub fn call(self, f: &'a ScopeState) -> VNode<'a> {
         match self.inner {
             StackNodeStorage::Heap(mut lazy) => {
                 lazy(Some(f)).expect("Closure should not be called twice")
@@ -182,18 +180,18 @@ struct LazyStack {
 }
 
 impl LazyStack {
-    fn call<'a>(&mut self, f: NodeFactory<'a>) -> VNode<'a> {
+    fn call<'a>(&mut self, f: &'a ScopeState) -> VNode<'a> {
         let LazyStack { buf, .. } = self;
         let data = buf.as_ref();
 
         let info_size =
-            mem::size_of::<*mut dyn FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>>>()
+            mem::size_of::<*mut dyn FnMut(Option<&'a ScopeState>) -> Option<VNode<'a>>>()
                 / mem::size_of::<usize>()
                 - 1;
 
         let info_ofs = data.len() - info_size;
 
-        let g: *mut dyn FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>> =
+        let g: *mut dyn FnMut(Option<&'a ScopeState>) -> Option<VNode<'a>> =
             unsafe { make_fat_ptr(data[..].as_ptr() as usize, &data[info_ofs..]) };
 
         self.dropped = true;
@@ -208,14 +206,14 @@ impl Drop for LazyStack {
             let LazyStack { buf, .. } = self;
             let data = buf.as_ref();
 
-            let info_size = mem::size_of::<
-                *mut dyn FnMut(Option<NodeFactory<'_>>) -> Option<VNode<'_>>,
-            >() / mem::size_of::<usize>()
-                - 1;
+            let info_size =
+                mem::size_of::<*mut dyn FnMut(Option<&ScopeState>) -> Option<VNode<'_>>>()
+                    / mem::size_of::<usize>()
+                    - 1;
 
             let info_ofs = data.len() - info_size;
 
-            let g: *mut dyn FnMut(Option<NodeFactory<'_>>) -> Option<VNode<'_>> =
+            let g: *mut dyn FnMut(Option<&ScopeState>) -> Option<VNode<'_>> =
                 unsafe { make_fat_ptr(data[..].as_ptr() as usize, &data[info_ofs..]) };
 
             self.dropped = true;
@@ -250,73 +248,3 @@ unsafe fn make_fat_ptr<T: ?Sized>(data_ptr: usize, meta_vals: &[usize]) -> *mut
 fn round_to_words(len: usize) -> usize {
     (len + mem::size_of::<usize>() - 1) / mem::size_of::<usize>()
 }
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::innerlude::{Element, Scope, VirtualDom};
-
-    #[test]
-    fn it_works() {
-        fn app(cx: Scope<()>) -> Element {
-            cx.render(LazyNodes::new(|f| f.text(format_args!("hello world!"))))
-        }
-
-        let mut dom = VirtualDom::new(app);
-        dom.rebuild();
-
-        let g = dom.base_scope().root_node();
-        dbg!(g);
-    }
-
-    #[test]
-    fn it_drops() {
-        use std::rc::Rc;
-
-        struct AppProps {
-            inner: Rc<i32>,
-        }
-
-        fn app(cx: Scope<AppProps>) -> Element {
-            struct DropInner {
-                id: i32,
-            }
-            impl Drop for DropInner {
-                fn drop(&mut self) {
-                    eprintln!("dropping inner");
-                }
-            }
-
-            let caller = {
-                let it = (0..10).map(|i| {
-                    let val = cx.props.inner.clone();
-                    LazyNodes::new(move |f| {
-                        eprintln!("hell closure");
-                        let inner = DropInner { id: i };
-                        f.text(format_args!("hello world {:?}, {:?}", inner.id, val))
-                    })
-                });
-
-                LazyNodes::new(|f| {
-                    eprintln!("main closure");
-                    f.fragment_from_iter(it)
-                })
-            };
-
-            cx.render(caller)
-        }
-
-        let inner = Rc::new(0);
-        let mut dom = VirtualDom::new_with_props(
-            app,
-            AppProps {
-                inner: inner.clone(),
-            },
-        );
-        dom.rebuild();
-
-        drop(dom);
-
-        assert_eq!(Rc::strong_count(&inner), 1);
-    }
-}

+ 34 - 83
packages/core/src/lib.rs

@@ -1,37 +1,43 @@
-#![allow(non_snake_case)]
 #![doc = include_str!("../README.md")]
-#![deny(missing_docs)]
+#![warn(missing_docs)]
 
-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;
+mod any_props;
+mod arena;
+mod bump_frame;
+mod create;
+mod diff;
+mod dirty_scope;
+mod error_boundary;
+mod events;
+mod fragment;
+mod lazynodes;
+mod mutations;
+mod nodes;
+mod properties;
+mod scheduler;
+mod scope_arena;
+mod scopes;
+mod virtual_dom;
 
 pub(crate) mod innerlude {
-    pub use crate::arbitrary_value::*;
-    pub use crate::dynamic_template_context::*;
+    pub use crate::arena::*;
+    pub use crate::dirty_scope::*;
+    pub use crate::error_boundary::*;
     pub use crate::events::*;
+    pub use crate::fragment::*;
     pub use crate::lazynodes::*;
     pub use crate::mutations::*;
+    pub use crate::nodes::RenderReturn;
     pub use crate::nodes::*;
     pub use crate::properties::*;
+    pub use crate::scheduler::*;
     pub use crate::scopes::*;
-    pub use crate::template::*;
-    pub use crate::util::*;
     pub use crate::virtual_dom::*;
 
-    /// An [`Element`] is a possibly-none [`VNode`] created by calling `render` on [`Scope`] or [`ScopeState`].
+    /// An [`Element`] is a possibly-errored [`VNode`] created by calling `render` on [`Scope`] or [`ScopeState`].
     ///
-    /// Any [`None`] [`Element`] will automatically be coerced into a placeholder [`VNode`] with the [`VNode::Placeholder`] variant.
-    pub type Element<'a> = Option<VNode<'a>>;
+    /// An Errored [`Element`] will propagate the error to the nearest error boundary.
+    pub type Element<'a> = Result<VNode<'a>, anyhow::Error>;
 
     /// A [`Component`] is a function that takes a [`Scope`] and returns an [`Element`].
     ///
@@ -61,44 +67,23 @@ pub(crate) mod innerlude {
     /// )
     /// ```
     pub type Component<P = ()> = fn(Scope<P>) -> Element;
-
-    /// A list of attributes
-    pub type Attributes<'a> = Option<&'a [Attribute<'a>]>;
 }
 
 pub use crate::innerlude::{
-    AnyEvent, ArbitraryAttributeValue, Attribute, AttributeDiscription, AttributeValue,
-    CodeLocation, Component, DioxusElement, DomEdit, DynamicNodeMapping, Element, ElementId,
-    ElementIdIterator, EventHandler, EventPriority, IntoAttributeValue, IntoVNode, LazyNodes,
-    Listener, Mutations, NodeFactory, OwnedAttributeValue, PathSeg, Properties, RendererTemplateId,
-    SchedulerMsg, Scope, ScopeId, ScopeState, StaticCodeLocation, StaticDynamicNodeMapping,
-    StaticPathSeg, StaticTemplateNode, StaticTemplateNodes, StaticTraverse, TaskId, Template,
-    TemplateAttribute, TemplateAttributeValue, TemplateContext, TemplateElement, TemplateId,
-    TemplateNode, TemplateNodeId, TemplateNodeType, TemplateValue, TextTemplate,
-    TextTemplateSegment, UiEvent, UpdateOp, UserEvent, VComponent, VElement, VFragment, VNode,
-    VPlaceholder, VText, VirtualDom,
-};
-#[cfg(any(feature = "hot-reload", debug_assertions))]
-pub use crate::innerlude::{
-    OwnedCodeLocation, OwnedDynamicNodeMapping, OwnedPathSeg, OwnedTemplateNode,
-    OwnedTemplateNodes, OwnedTraverse, SetTemplateMsg,
+    fc_to_builder, Attribute, AttributeValue, Component, DynamicNode, Element, ElementId, Event,
+    Fragment, IntoDynNode, LazyNodes, Mutation, Mutations, Properties, RenderReturn, Scope,
+    ScopeId, ScopeState, Scoped, SuspenseContext, TaskId, Template, TemplateAttribute,
+    TemplateNode, VComponent, VNode, VText, VirtualDom,
 };
 
 /// 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, AttributeDiscription, AttributeValue, Attributes, CodeLocation, Component,
-        DioxusElement, Element, EventHandler, Fragment, IntoAttributeValue, LazyNodes,
-        LazyStaticVec, NodeFactory, Properties, Scope, ScopeId, ScopeState, StaticAttributeValue,
-        StaticCodeLocation, StaticDynamicNodeMapping, StaticPathSeg, StaticTemplate,
-        StaticTemplateNodes, StaticTraverse, Template, TemplateAttribute, TemplateAttributeValue,
-        TemplateContext, TemplateElement, TemplateId, TemplateNode, TemplateNodeId,
-        TemplateNodeType, TextTemplate, TextTemplateSegment, UpdateOp, VNode, VirtualDom,
+        fc_to_builder, Element, Event, EventHandler, Fragment, LazyNodes, Properties, Scope,
+        ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute, TemplateNode, VNode,
+        VirtualDom,
     };
 }
 
@@ -106,38 +91,4 @@ pub mod exports {
     //! Important dependencies that are used by the rest of the library
     //! 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
-pub(crate) mod unsafe_utils {
-    use crate::VNode;
-
-    pub(crate) unsafe fn extend_vnode<'a, 'b>(node: &'a VNode<'a>) -> &'b VNode<'b> {
-        std::mem::transmute(node)
-    }
-}
-
-#[macro_export]
-/// A helper macro for using hooks in async environements.
-///
-/// # Usage
-///
-///
-/// ```ignore
-/// let (data) = use_ref(&cx, || {});
-///
-/// let handle_thing = move |_| {
-///     to_owned![data]
-///     cx.spawn(async move {
-///         // do stuff
-///     });
-/// }
-/// ```
-macro_rules! to_owned {
-    ($($es:ident),+$(,)?) => {$(
-        #[allow(unused_mut)]
-        let mut $es = $es.to_owned();
-    )*}
 }

+ 186 - 333
packages/core/src/mutations.rs

@@ -1,407 +1,260 @@
-//! Instructions returned by the VirtualDOM on how to modify the Real DOM.
-//!
-//! This module contains an internal API to generate these instructions.
-//!
-//! Beware that changing code in this module will break compatibility with
-//! interpreters for these types of DomEdits.
+use fxhash::FxHashSet;
 
-use crate::innerlude::*;
-use std::{any::Any, fmt::Debug};
+use crate::{arena::ElementId, ScopeId, Template};
 
-/// ## Mutations
+/// A container for all the relevant steps to modify the Real DOM
 ///
-/// This method returns "mutations" - IE the necessary changes to get the RealDOM to match the VirtualDOM. It also
-/// includes a list of NodeRefs that need to be applied and effects that need to be triggered after the RealDOM has
-/// applied the edits.
+/// This object provides a bunch of important information for a renderer to use patch the Real Dom with the state of the
+/// VirtualDom. This includes the scopes that were modified, the templates that were discovered, and a list of changes
+/// in the form of a [`Mutation`].
+///
+/// These changes are specific to one subtree, so to patch multiple subtrees, you'd need to handle each set separately.
+///
+/// Templates, however, apply to all subtrees, not just target subtree.
 ///
 /// Mutations are the only link between the RealDOM and the VirtualDOM.
+#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
+#[derive(Debug, Default)]
+#[must_use = "not handling edits can lead to visual inconsistencies in UI"]
 pub struct Mutations<'a> {
-    /// The list of edits that need to be applied for the RealDOM to match the VirtualDOM.
-    pub edits: Vec<DomEdit<'a>>,
+    /// The ID of the subtree that these edits are targetting
+    pub subtree: usize,
 
     /// The list of Scopes that were diffed, created, and removed during the Diff process.
     pub dirty_scopes: FxHashSet<ScopeId>,
 
-    /// The list of nodes to connect to the RealDOM.
-    pub refs: Vec<NodeRefMutation<'a>>,
+    /// Any templates encountered while diffing the DOM.
+    ///
+    /// These must be loaded into a cache before applying the edits
+    pub templates: Vec<Template<'a>>,
+
+    /// Any mutations required to patch the renderer to match the layout of the VirtualDom
+    pub edits: Vec<Mutation<'a>>,
 }
 
-impl Debug for Mutations<'_> {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.debug_struct("Mutations")
-            .field("edits", &self.edits)
-            .field("noderefs", &self.refs)
-            .finish()
+impl<'a> Mutations<'a> {
+    /// Rewrites IDs to just be "template", so you can compare the mutations
+    ///
+    /// Used really only for testing
+    pub fn santize(mut self) -> Self {
+        for edit in self.edits.iter_mut() {
+            if let Mutation::LoadTemplate { name, .. } = edit {
+                *name = "template"
+            }
+        }
+
+        self
+    }
+
+    /// Push a new mutation into the dom_edits list
+    pub(crate) fn push(&mut self, mutation: Mutation<'static>) {
+        self.edits.push(mutation)
     }
 }
 
-/// A `DomEdit` represents a serialized form of the VirtualDom's trait-based API. This allows streaming edits across the
-/// network or through FFI boundaries.
-#[derive(Debug, PartialEq)]
+/// A `Mutation` represents a single instruction for the renderer to use to modify the UI tree to match the state
+/// of the Dioxus VirtualDom.
+///
+/// These edits can be serialized and sent over the network or through any interface
 #[cfg_attr(
     feature = "serialize",
     derive(serde::Serialize, serde::Deserialize),
     serde(tag = "type")
 )]
-pub enum DomEdit<'bump> {
-    /// Pop the topmost node from our stack and append them to the node
-    /// at the top of the stack.
+#[derive(Debug, PartialEq, Eq)]
+pub enum Mutation<'a> {
+    /// Add these m children to the target element
     AppendChildren {
-        /// The parent to append nodes to.
-        root: Option<u64>,
-
-        /// The ids of the children to append.
-        children: Vec<u64>,
-    },
-
-    /// Replace a given (single) node with a handful of nodes currently on the stack.
-    ReplaceWith {
-        /// The ID of the node to be replaced.
-        root: Option<u64>,
-
-        /// The ids of the nodes to replace the root with.
-        nodes: Vec<u64>,
-    },
-
-    /// Insert a number of nodes after a given node.
-    InsertAfter {
-        /// The ID of the node to insert after.
-        root: Option<u64>,
+        /// The ID of the element being mounted to
+        id: ElementId,
 
-        /// The ids of the nodes to insert after the target node.
-        nodes: Vec<u64>,
+        /// The number of nodes on the stack
+        m: usize,
     },
 
-    /// Insert a number of nodes before a given node.
-    InsertBefore {
-        /// The ID of the node to insert before.
-        root: Option<u64>,
-
-        /// The ids of the nodes to insert before the target node.
-        nodes: Vec<u64>,
+    /// Assign the element at the given path the target ElementId.
+    ///
+    /// The path is in the form of a list of indices based on children. Templates cannot have more than 255 children per
+    /// element, hence the use of a single byte.
+    ///
+    ///
+    AssignId {
+        /// The path of the child of the topmost node on the stack
+        ///
+        /// A path of `[]` represents the topmost node. A path of `[0]` represents the first child.
+        /// `[0,1,2]` represents 1st child's 2nd child's 3rd child.
+        path: &'static [u8],
+
+        /// The ID we're assigning to this element/placeholder.
+        ///
+        /// This will be used later to modify the element or replace it with another element.
+        id: ElementId,
     },
 
-    /// Remove a particular node from the DOM
-    Remove {
-        /// The ID of the node to remove.
-        root: Option<u64>,
+    /// Create an placeholder int he DOM that we will use later.
+    ///
+    /// Dioxus currently requires the use of placeholders to maintain a re-entrance point for things like list diffing
+    CreatePlaceholder {
+        /// The ID we're assigning to this element/placeholder.
+        ///
+        /// This will be used later to modify the element or replace it with another element.
+        id: ElementId,
     },
 
-    /// Create a new purely-text node
+    /// Create a node specifically for text with the given value
     CreateTextNode {
-        /// The ID the new node should have.
-        root: Option<u64>,
+        /// The text content of this text node
+        value: &'a str,
 
-        /// The textcontent of the node
-        text: &'bump str,
+        /// The ID we're assigning to this specific text nodes
+        ///
+        /// This will be used later to modify the element or replace it with another element.
+        id: ElementId,
     },
 
-    /// Create a new purely-element node
-    CreateElement {
-        /// The ID the new node should have.
-        root: Option<u64>,
-
-        /// The tagname of the node
-        tag: &'bump str,
-
-        /// The number of children nodes that will follow this message.
-        children: u32,
+    /// Hydrate an existing text node at the given path with the given text.
+    ///
+    /// Assign this text node the given ID since we will likely need to modify this text at a later point
+    HydrateText {
+        /// The path of the child of the topmost node on the stack
+        ///
+        /// A path of `[]` represents the topmost node. A path of `[0]` represents the first child.
+        /// `[0,1,2]` represents 1st child's 2nd child's 3rd child.
+        path: &'static [u8],
+
+        /// The value of the textnode that we want to set the placeholder with
+        value: &'a str,
+
+        /// The ID we're assigning to this specific text nodes
+        ///
+        /// This will be used later to modify the element or replace it with another element.
+        id: ElementId,
     },
 
-    /// Create a new purely-comment node with a given namespace
-    CreateElementNs {
-        /// The ID the new node should have.
-        root: Option<u64>,
-
-        /// The namespace of the node
-        tag: &'bump str,
+    /// Load and clone an existing node from a template saved under that specific name
+    ///
+    /// Dioxus guarantees that the renderer will have already been provided the template.
+    /// When the template is picked up in the template list, it should be saved under its "name" - here, the name
+    LoadTemplate {
+        /// The "name" of the template. When paired with `rsx!`, this is autogenerated
+        name: &'static str,
 
-        /// The namespace of the node (like `SVG`)
-        ns: &'static str,
+        /// Which root are we loading from the template?
+        ///
+        /// The template is stored as a list of nodes. This index represents the position of that root
+        index: usize,
 
-        /// The number of children nodes that will follow this message.
-        children: u32,
+        /// The ID we're assigning to this element being loaded from the template
+        ///
+        /// This will be used later to move the element around in lists
+        id: ElementId,
     },
 
-    /// Create a new placeholder node.
-    /// In most implementations, this will either be a hidden div or a comment node.
-    CreatePlaceholder {
-        /// The ID the new node should have.
-        root: Option<u64>,
-    },
+    /// Replace the target element (given by its ID) with the topmost m nodes on the stack
+    ReplaceWith {
+        /// The ID of the node we're going to replace with
+        id: ElementId,
 
-    /// Create a new Event Listener.
-    NewEventListener {
-        /// The name of the event to listen for.
-        event_name: &'static str,
+        /// The number of nodes on the stack to use to replace
+        m: usize,
+    },
 
-        /// The ID of the node to attach the listener to.
-        scope: ScopeId,
+    /// Replace an existing element in the template at the given path with the m nodes on the stack
+    ReplacePlaceholder {
+        /// The path of the child of the topmost node on the stack
+        ///
+        /// A path of `[]` represents the topmost node. A path of `[0]` represents the first child.
+        /// `[0,1,2]` represents 1st child's 2nd child's 3rd child.
+        path: &'static [u8],
 
-        /// The ID of the node to attach the listener to.
-        root: Option<u64>,
+        /// The number of nodes on the stack to use to replace
+        m: usize,
     },
 
-    /// Remove an existing Event Listener.
-    RemoveEventListener {
-        /// The ID of the node to remove.
-        root: Option<u64>,
+    /// Insert a number of nodes after a given node.
+    InsertAfter {
+        /// The ID of the node to insert after.
+        id: ElementId,
 
-        /// The name of the event to remove.
-        event: &'static str,
+        /// The ids of the nodes to insert after the target node.
+        m: usize,
     },
 
-    /// Set the textcontent of a node.
-    SetText {
-        /// The ID of the node to set the textcontent of.
-        root: Option<u64>,
+    /// Insert a number of nodes before a given node.
+    InsertBefore {
+        /// The ID of the node to insert before.
+        id: ElementId,
 
-        /// The textcontent of the node
-        text: &'bump str,
+        /// The ids of the nodes to insert before the target node.
+        m: usize,
     },
 
     /// Set the value of a node's attribute.
     SetAttribute {
-        /// The ID of the node to set the attribute of.
-        root: Option<u64>,
-
         /// The name of the attribute to set.
-        field: &'static str,
-
+        name: &'a str,
         /// The value of the attribute.
-        value: AttributeValue<'bump>,
+        value: &'a str,
+
+        /// The ID of the node to set the attribute of.
+        id: ElementId,
 
-        // value: &'bump str,
         /// The (optional) namespace of the attribute.
         /// For instance, "style" is in the "style" namespace.
-        ns: Option<&'bump str>,
+        ns: Option<&'a str>,
     },
 
-    /// Remove an attribute from a node.
-    RemoveAttribute {
-        /// The ID of the node to remove.
-        root: Option<u64>,
+    /// Set the value of a node's attribute.
+    SetBoolAttribute {
+        /// The name of the attribute to set.
+        name: &'a str,
 
-        /// The name of the attribute to remove.
-        name: &'static str,
+        /// The value of the attribute.
+        value: bool,
 
-        /// The namespace of the attribute.
-        ns: Option<&'bump str>,
+        /// The ID of the node to set the attribute of.
+        id: ElementId,
     },
 
-    /// Clones a node.
-    CloneNode {
-        /// The ID of the node to clone.
-        id: Option<u64>,
+    /// Set the textcontent of a node.
+    SetText {
+        /// The textcontent of the node
+        value: &'a str,
 
-        /// The ID of the new node.
-        new_id: u64,
+        /// The ID of the node to set the textcontent of.
+        id: ElementId,
     },
 
-    /// Clones the children of a node. (allows cloning fragments)
-    CloneNodeChildren {
-        /// The ID of the node to clone.
-        id: Option<u64>,
-
-        /// The ID of the new node.
-        new_ids: Vec<u64>,
-    },
+    /// Create a new Event Listener.
+    NewEventListener {
+        /// The name of the event to listen for.
+        name: &'a str,
 
-    /// Navigates to the last node to the first child of the current node.
-    FirstChild {},
+        /// The ID of the node to attach the listener to.
+        scope: ScopeId,
 
-    /// Navigates to the last node to the last child of the current node.
-    NextSibling {},
+        /// The ID of the node to attach the listener to.
+        id: ElementId,
+    },
 
-    /// Navigates to the last node to the parent of the current node.
-    ParentNode {},
+    /// Remove an existing Event Listener.
+    RemoveEventListener {
+        /// The name of the event to remove.
+        name: &'a str,
 
-    /// Stores the last node with a new id.
-    StoreWithId {
-        /// The ID of the node to store.
-        id: u64,
+        /// The ID of the node to remove.
+        id: ElementId,
     },
 
-    /// Manually set the last node.
-    SetLastNode {
-        /// The ID to set the last node to.
-        id: u64,
+    /// Remove a particular node from the DOM
+    Remove {
+        /// The ID of the node to remove.
+        id: ElementId,
     },
-}
-
-use rustc_hash::FxHashSet;
-use DomEdit::*;
-
-#[allow(unused)]
-impl<'a> Mutations<'a> {
-    pub(crate) fn new() -> Self {
-        Self {
-            edits: Vec::new(),
-            refs: Vec::new(),
-            dirty_scopes: Default::default(),
-        }
-    }
-
-    pub(crate) fn replace_with(&mut self, root: Option<u64>, nodes: Vec<u64>) {
-        self.edits.push(ReplaceWith { nodes, root });
-    }
 
-    pub(crate) fn insert_after(&mut self, root: Option<u64>, nodes: Vec<u64>) {
-        self.edits.push(InsertAfter { nodes, root });
-    }
-
-    pub(crate) fn insert_before(&mut self, root: Option<u64>, nodes: Vec<u64>) {
-        self.edits.push(InsertBefore { nodes, root });
-    }
-
-    pub(crate) fn append_children(&mut self, root: Option<u64>, children: Vec<u64>) {
-        self.edits.push(AppendChildren { root, children });
-    }
-
-    // Remove Nodes from the dom
-    pub(crate) fn remove(&mut self, id: Option<u64>) {
-        self.edits.push(Remove { root: id });
-    }
-
-    // Create
-    pub(crate) fn create_text_node(&mut self, text: &'a str, id: Option<u64>) {
-        self.edits.push(CreateTextNode { text, root: id });
-    }
-
-    pub(crate) fn create_element(
-        &mut self,
-        tag: &'static str,
-        ns: Option<&'static str>,
-        id: Option<u64>,
-        children: u32,
-    ) {
-        match ns {
-            Some(ns) => self.edits.push(CreateElementNs {
-                root: id,
-                ns,
-                tag,
-                children,
-            }),
-            None => self.edits.push(CreateElement {
-                root: id,
-                tag,
-                children,
-            }),
-        }
-    }
-
-    // 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: Option<u64>) {
-        self.edits.push(CreatePlaceholder { root: id });
-    }
-
-    // events
-    pub(crate) fn new_event_listener(&mut self, listener: &Listener, scope: ScopeId) {
-        let Listener {
-            event,
-            mounted_node,
-            ..
-        } = listener;
-
-        let element_id = Some(mounted_node.get().unwrap().into());
-
-        self.edits.push(NewEventListener {
-            scope,
-            event_name: event,
-            root: element_id,
-        });
-    }
-
-    pub(crate) fn remove_event_listener(&mut self, event: &'static str, root: Option<u64>) {
-        self.edits.push(RemoveEventListener { event, root });
-    }
-
-    // modify
-    pub(crate) fn set_text(&mut self, text: &'a str, root: Option<u64>) {
-        self.edits.push(SetText { text, root });
-    }
-
-    pub(crate) fn set_attribute(&mut self, attribute: &'a Attribute<'a>, root: Option<u64>) {
-        let Attribute {
-            value, attribute, ..
-        } = attribute;
-
-        self.edits.push(SetAttribute {
-            field: attribute.name,
-            value: value.clone(),
-            ns: attribute.namespace,
-            root,
-        });
-    }
-
-    pub(crate) fn remove_attribute(&mut self, attribute: &Attribute, root: Option<u64>) {
-        let Attribute { attribute, .. } = attribute;
-
-        self.edits.push(RemoveAttribute {
-            name: attribute.name,
-            ns: attribute.namespace,
-            root,
-        });
-    }
-
-    pub(crate) fn mark_dirty_scope(&mut self, scope: ScopeId) {
-        self.dirty_scopes.insert(scope);
-    }
-
-    pub(crate) fn clone_node(&mut self, id: Option<u64>, new_id: u64) {
-        self.edits.push(CloneNode { id, new_id });
-    }
-
-    pub(crate) fn clone_node_children(&mut self, id: Option<u64>, new_ids: Vec<u64>) {
-        self.edits.push(CloneNodeChildren { id, new_ids });
-    }
-
-    pub(crate) fn first_child(&mut self) {
-        self.edits.push(FirstChild {});
-    }
-
-    pub(crate) fn next_sibling(&mut self) {
-        self.edits.push(NextSibling {});
-    }
-
-    pub(crate) fn parent_node(&mut self) {
-        self.edits.push(ParentNode {});
-    }
-
-    pub(crate) fn store_with_id(&mut self, id: u64) {
-        self.edits.push(StoreWithId { id });
-    }
-
-    pub(crate) fn set_last_node(&mut self, id: u64) {
-        self.edits.push(SetLastNode { id });
-    }
-}
-
-// refs are only assigned once
-pub struct NodeRefMutation<'a> {
-    pub element: &'a mut Option<once_cell::sync::OnceCell<Box<dyn Any>>>,
-    pub element_id: ElementId,
-}
-
-impl<'a> std::fmt::Debug for NodeRefMutation<'a> {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.debug_struct("NodeRefMutation")
-            .field("element_id", &self.element_id)
-            .finish()
-    }
-}
-
-impl<'a> NodeRefMutation<'a> {
-    pub fn downcast_ref<T: 'static>(&self) -> Option<&T> {
-        self.element
-            .as_ref()
-            .and_then(|f| f.get())
-            .and_then(|f| f.downcast_ref::<T>())
-    }
-    pub fn downcast_mut<T: 'static>(&mut self) -> Option<&mut T> {
-        self.element
-            .as_mut()
-            .and_then(|f| f.get_mut())
-            .and_then(|f| f.downcast_mut::<T>())
-    }
+    /// Push the given root node onto our stack.
+    PushRoot {
+        /// The ID of the root node to push.
+        id: ElementId,
+    },
 }

+ 446 - 811
packages/core/src/nodes.rs

@@ -1,949 +1,584 @@
-//! Virtual Node Support
-//!
-//! 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::{
-    dynamic_template_context::TemplateContext,
-    innerlude::{
-        AttributeValue, ComponentPtr, Element, IntoAttributeValue, Properties, Scope, ScopeId,
-        ScopeState, Template, TemplateId,
-    },
-    lazynodes::LazyNodes,
-    template::VTemplateRef,
-    AnyEvent, Component,
+    any_props::AnyProps, arena::ElementId, Element, Event, LazyNodes, ScopeId, ScopeState,
 };
-use bumpalo::{boxed::Box as BumpBox, Bump};
+use bumpalo::boxed::Box as BumpBox;
+use bumpalo::Bump;
 use std::{
+    any::{Any, TypeId},
     cell::{Cell, RefCell},
-    fmt::{Arguments, Debug, Formatter},
-    rc::Rc,
+    fmt::Arguments,
+    future::Future,
 };
 
-/// A composable "VirtualNode" to declare a User Interface in the Dioxus VirtualDOM.
+pub type TemplateId = &'static str;
+
+/// The actual state of the component's most recent computation
 ///
-/// VNodes are designed to be lightweight and used with with a bump allocator. To create a VNode, you can use either of:
+/// Because Dioxus accepts components in the form of `async fn(Scope) -> Result<VNode>`, we need to support both
+/// sync and async versions.
 ///
-/// - the `rsx!` macro
-/// - the [`NodeFactory`] API
-pub enum VNode<'src> {
-    /// Text VNodes are simply bump-allocated (or static) string slices
-    ///
-    /// # Example
-    ///
-    /// ```rust, ignore
-    /// let mut vdom = VirtualDom::new();
-    /// let node = vdom.render_vnode(rsx!( "hello" ));
-    ///
-    /// if let VNode::Text(vtext) = node {
-    ///     assert_eq!(vtext.text, "hello");
-    ///     assert_eq!(vtext.dom_id.get(), None);
-    ///     assert_eq!(vtext.is_static, true);
-    /// }
-    /// ```
-    Text(&'src VText<'src>),
-
-    /// Element VNodes are VNodes that may contain attributes, listeners, a key, a tag, and children.
-    ///
-    /// # Example
-    ///
-    /// ```rust, ignore
-    /// let mut vdom = VirtualDom::new();
-    ///
-    /// let node = vdom.render_vnode(rsx!{
-    ///     div {
-    ///         key: "a",
-    ///         onclick: |e| log::info!("clicked"),
-    ///         hidden: "true",
-    ///         style: { background_color: "red" },
-    ///         "hello"
-    ///     }
-    /// });
-    ///
-    /// if let VNode::Element(velement) = node {
-    ///     assert_eq!(velement.tag_name, "div");
-    ///     assert_eq!(velement.namespace, None);
-    ///     assert_eq!(velement.key, Some("a"));
-    /// }
-    /// ```
-    Element(&'src VElement<'src>),
-
-    /// Fragment nodes may contain many VNodes without a single root.
-    ///
-    /// # Example
-    ///
-    /// ```rust, ignore
-    /// rsx!{
-    ///     a {}
-    ///     link {}
-    ///     style {}
-    ///     "asd"
-    ///     Example {}
-    /// }
-    /// ```
-    Fragment(&'src VFragment<'src>),
-
-    /// Component nodes represent a mounted component with props, children, and a key.
-    ///
-    /// # Example
-    ///
-    /// ```rust, ignore
-    /// fn Example(cx: Scope) -> Element {
-    ///     ...
-    /// }
-    ///
-    /// let mut vdom = VirtualDom::new();
-    ///
-    /// let node = vdom.render_vnode(rsx!( Example {} ));
-    ///
-    /// if let VNode::Component(vcomp) = node {
-    ///     assert_eq!(vcomp.user_fc, Example as *const ());
-    /// }
-    /// ```
-    Component(&'src VComponent<'src>),
+/// Dioxus will do its best to immediately resolve any async components into a regular Element, but as an implementor
+/// you might need to handle the case where there's no node immediately ready.
+pub enum RenderReturn<'a> {
+    /// A currently-available element
+    Sync(Element<'a>),
 
-    /// Placeholders are a type of placeholder VNode used when fragments don't contain any children.
-    ///
-    /// Placeholders cannot be directly constructed via public APIs.
-    ///
-    /// # Example
-    ///
-    /// ```rust, ignore
-    /// let mut vdom = VirtualDom::new();
-    ///
-    /// let node = vdom.render_vnode(rsx!( Fragment {} ));
-    ///
-    /// if let VNode::Fragment(frag) = node {
-    ///     let root = &frag.children[0];
-    ///     assert_eq!(root, VNode::Anchor);
-    /// }
-    /// ```
-    Placeholder(&'src VPlaceholder),
-
-    /// Templetes ase generated by the rsx macro to eleminate diffing static nodes.
-    TemplateRef(&'src VTemplateRef<'src>),
-}
-
-impl<'src> VNode<'src> {
-    /// Get the VNode's "key" used in the keyed diffing algorithm.
-    pub fn key(&self) -> Option<&'src str> {
-        match &self {
-            VNode::Element(el) => el.key,
-            VNode::Component(c) => c.key,
-            VNode::Fragment(f) => f.key,
-            VNode::Text(_t) => None,
-            VNode::Placeholder(_f) => None,
-            VNode::TemplateRef(t) => t.dynamic_context.key,
-        }
-    }
+    /// An ongoing future that will resolve to a [`Element`]
+    Async(BumpBox<'a, dyn Future<Output = Element<'a>> + 'a>),
+}
 
-    /// Get the ElementID of the mounted VNode.
+/// A reference to a template along with any context needed to hydrate it
+///
+/// The dynamic parts of the template are stored separately from the static parts. This allows faster diffing by skipping
+/// static parts of the template.
+#[derive(Debug, Clone)]
+pub struct VNode<'a> {
+    /// The key given to the root of this template.
     ///
-    /// Panics if the mounted ID is None or if the VNode is not represented by a single Element.
-    pub fn mounted_id(&self) -> ElementId {
-        self.try_mounted_id().unwrap()
-    }
+    /// In fragments, this is the key of the first child. In other cases, it is the key of the root.
+    pub key: Option<&'a str>,
 
-    /// Try to get the ElementID of the mounted VNode.
-    ///
-    /// Returns None if the VNode is not mounted, or if the VNode cannot be presented by a mounted ID (Fragment/Component)
-    pub fn try_mounted_id(&self) -> Option<ElementId> {
-        match &self {
-            VNode::Text(el) => el.id.get(),
-            VNode::Element(el) => el.id.get(),
-            VNode::Placeholder(el) => el.id.get(),
-            VNode::Fragment(_) => None,
-            VNode::Component(_) => None,
-            VNode::TemplateRef(_) => None,
-        }
+    /// When rendered, this template will be linked to its parent manually
+    pub parent: Option<ElementId>,
+
+    /// The static nodes and static descriptor of the template
+    pub template: Template<'static>,
+
+    /// The IDs for the roots of this template - to be used when moving the template around and removing it from
+    /// the actual Dom
+    pub root_ids: &'a [Cell<ElementId>],
+
+    /// The dynamic parts of the template
+    pub dynamic_nodes: &'a [DynamicNode<'a>],
+
+    /// The dynamic parts of the template
+    pub dynamic_attrs: &'a [Attribute<'a>],
+}
+
+impl<'a> VNode<'a> {
+    /// Create a template with no nodes that will be skipped over during diffing
+    pub fn empty() -> Element<'a> {
+        Ok(VNode {
+            key: None,
+            parent: None,
+            root_ids: &[],
+            dynamic_nodes: &[],
+            dynamic_attrs: &[],
+            template: Template {
+                name: "dioxus-empty",
+                roots: &[],
+                node_paths: &[],
+                attr_paths: &[],
+            },
+        })
     }
 
-    // Create an "owned" version of the vnode.
-    pub(crate) fn decouple(&self) -> VNode<'src> {
-        match *self {
-            VNode::Text(t) => VNode::Text(t),
-            VNode::Element(e) => VNode::Element(e),
-            VNode::Component(c) => VNode::Component(c),
-            VNode::Placeholder(a) => VNode::Placeholder(a),
-            VNode::Fragment(f) => VNode::Fragment(f),
-            VNode::TemplateRef(t) => VNode::TemplateRef(t),
+    /// Load a dynamic root at the given index
+    ///
+    /// Returns [`None`] if the root is actually a static node (Element/Text)
+    pub fn dynamic_root(&self, idx: usize) -> Option<&'a DynamicNode<'a>> {
+        match &self.template.roots[idx] {
+            TemplateNode::Element { .. } | TemplateNode::Text { text: _ } => None,
+            TemplateNode::Dynamic { id } | TemplateNode::DynamicText { id } => {
+                Some(&self.dynamic_nodes[*id])
+            }
         }
     }
-}
 
-impl Debug for VNode<'_> {
-    fn fmt(&self, s: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
-        match &self {
-            VNode::Element(el) => s
-                .debug_struct("VNode::Element")
-                .field("name", &el.tag)
-                .field("key", &el.key)
-                .field("attrs", &el.attributes)
-                .field("children", &el.children)
-                .field("id", &el.id)
-                .finish(),
-            VNode::Text(t) => s
-                .debug_struct("VNode::Text")
-                .field("text", &t.text)
-                .field("id", &t.id)
-                .finish(),
-            VNode::Placeholder(t) => s
-                .debug_struct("VNode::Placholder")
-                .field("id", &t.id)
-                .finish(),
-            VNode::Fragment(frag) => s
-                .debug_struct("VNode::Fragment")
-                .field("children", &frag.children)
-                .finish(),
-            VNode::Component(comp) => s
-                .debug_struct("VNode::Component")
-                .field("name", &comp.fn_name)
-                .field("fnptr", &comp.user_fc)
-                .field("key", &comp.key)
-                .field("scope", &comp.scope)
-                .finish(),
-            VNode::TemplateRef(temp) => s
-                .debug_struct("VNode::TemplateRef")
-                .field("template_id", &temp.template_id)
-                .finish(),
+    pub(crate) fn clear_listeners(&self) {
+        for attr in self.dynamic_attrs {
+            if let AttributeValue::Listener(l) = &attr.value {
+                l.borrow_mut().take();
+            }
         }
     }
 }
 
-/// An Element's unique identifier.
+/// A static layout of a UI tree that describes a set of dynamic and static nodes.
 ///
-/// `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 {
-        write!(f, "{}", self.0)
-    }
-}
-
-impl ElementId {
-    /// Convert the ElementId to a `u64`.
-    pub fn as_u64(&self) -> u64 {
-        (*self).into()
-    }
-}
+/// This is the core innovation in Dioxus. Most UIs are made of static nodes, yet participate in diffing like any
+/// dynamic node. This struct can be created at compile time. It promises that its name is unique, allow Dioxus to use
+/// its static description of the UI to skip immediately to the dynamic nodes during diffing.
+///
+/// For this to work properly, the [`Template::name`] *must* be unique across your entire project. This can be done via variety of
+/// ways, with the suggested approach being the unique code location (file, line, col, etc).
+#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
+#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq, PartialOrd, Ord)]
+pub struct Template<'a> {
+    /// The name of the template. This must be unique across your entire program for template diffing to work properly
+    ///
+    /// If two templates have the same name, it's likely that Dioxus will panic when diffing.
+    pub name: &'a str,
 
-impl From<ElementId> for u64 {
-    fn from(el: ElementId) -> u64 {
-        el.0 as u64
-    }
-}
+    /// The list of template nodes that make up the template
+    ///
+    /// Unlike react, calls to `rsx!` can have multiple roots. This list supports that paradigm.
+    pub roots: &'a [TemplateNode<'a>],
 
-fn empty_cell<T>() -> Cell<Option<T>> {
-    Cell::new(None)
-}
+    /// The paths of each node relative to the root of the template.
+    ///
+    /// These will be one segment shorter than the path sent to the renderer since those paths are relative to the
+    /// topmost element, not the `roots` field.
+    pub node_paths: &'a [&'a [u8]],
 
-/// A placeholder node only generated when Fragments don't have any children.
-pub struct VPlaceholder {
-    /// The [`ElementId`] of the placeholder.
-    pub id: Cell<Option<ElementId>>,
+    /// The paths of each dynamic attribute relative to the root of the template
+    ///
+    /// These will be one segment shorter than the path sent to the renderer since those paths are relative to the
+    /// topmost element, not the `roots` field.
+    pub attr_paths: &'a [&'a [u8]],
 }
 
-/// A bump-allocated string slice and metadata.
-pub struct VText<'src> {
-    /// The [`ElementId`] of the VText.
-    pub id: Cell<Option<ElementId>>,
-
-    /// The text of the VText.
-    pub text: &'src str,
+/// A statically known node in a layout.
+///
+/// This can be created at compile time, saving the VirtualDom time when diffing the tree
+#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq, PartialOrd, Ord)]
+#[cfg_attr(feature = "serialize", derive(serde::Serialize), serde(tag = "type"))]
+pub enum TemplateNode<'a> {
+    /// An statically known element in the dom.
+    ///
+    /// In HTML this would be something like `<div id="123"> </div>`
+    Element {
+        /// The name of the element
+        ///
+        /// IE for a div, it would be the string "div"
+        tag: &'a str,
+
+        /// The namespace of the element
+        ///
+        /// In HTML, this would be a valid URI that defines a namespace for all elements below it
+        /// SVG is an example of this namespace
+        namespace: Option<&'a str>,
+
+        /// A list of possibly dynamic attribues for this element
+        ///
+        /// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"`.
+        attrs: &'a [TemplateAttribute<'a>],
+
+        /// A list of template nodes that define another set of template nodes
+        children: &'a [TemplateNode<'a>],
+    },
 
-    /// An indiciation if this VText can be ignored during diffing
-    /// Is usually only when there are no strings to be formatted (so the text is &'static str)
-    pub is_static: bool,
-}
+    /// This template node is just a piece of static text
+    Text {
+        /// The actual text
+        text: &'a str,
+    },
 
-/// A list of VNodes with no single root.
-pub struct VFragment<'src> {
-    /// The key of the fragment to be used during keyed diffing.
-    pub key: Option<&'src str>,
+    /// This template node is unknown, and needs to be created at runtime.
+    Dynamic {
+        /// The index of the dynamic node in the VNode's dynamic_nodes list
+        id: usize,
+    },
 
-    /// Fragments can never have zero children. Enforced by NodeFactory.
+    /// This template node is known to be some text, but needs to be created at runtime
     ///
-    /// You *can* make a fragment with no children, but it's not a valid fragment and your VDom will panic.
-    pub children: &'src [VNode<'src>],
+    /// This is separate from the pure Dynamic variant for various optimizations
+    DynamicText {
+        /// The index of the dynamic node in the VNode's dynamic_nodes list
+        id: usize,
+    },
 }
 
-/// An element like a "div" with children, listeners, and attributes.
-pub struct VElement<'a> {
-    /// The [`ElementId`] of the VText.
-    pub id: Cell<Option<ElementId>>,
+/// A node created at runtime
+///
+/// This node's index in the DynamicNode list on VNode should match its repsective `Dynamic` index
+#[derive(Debug)]
+pub enum DynamicNode<'a> {
+    /// A component node
+    ///
+    /// Most of the time, Dioxus will actually know which component this is as compile time, but the props and
+    /// assigned scope are dynamic.
+    ///
+    /// The actual VComponent can be dynamic between two VNodes, though, allowing implementations to swap
+    /// the render function at runtime
+    Component(VComponent<'a>),
 
-    /// The key of the element to be used during keyed diffing.
-    pub key: Option<&'a str>,
+    /// A text node
+    Text(VText<'a>),
 
-    /// The tag name of the element.
+    /// A placeholder
     ///
-    /// IE "div"
-    pub tag: &'static str,
-
-    /// The namespace of the VElement
+    /// Used by suspense when a node isn't ready and by fragments that don't render anything
     ///
-    /// IE "svg"
-    pub namespace: Option<&'static str>,
+    /// In code, this is just an ElementId whose initial value is set to 0 upon creation
+    Placeholder(Cell<ElementId>),
 
-    /// The parent of the Element (if any).
+    /// A list of VNodes.
     ///
-    /// Used when bubbling events
-    pub parent: Cell<Option<ElementId>>,
-
-    /// The Listeners of the VElement.
-    pub listeners: &'a [Listener<'a>],
-
-    /// The attributes of the VElement.
-    pub attributes: &'a [Attribute<'a>],
-
-    /// The children of the VElement.
-    pub children: &'a [VNode<'a>],
-}
-
-impl Debug for VElement<'_> {
-    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
-        f.debug_struct("VElement")
-            .field("tag_name", &self.tag)
-            .field("namespace", &self.namespace)
-            .field("key", &self.key)
-            .field("id", &self.id)
-            .field("parent", &self.parent)
-            .field("listeners", &self.listeners.len())
-            .field("attributes", &self.attributes)
-            .field("children", &self.children)
-            .finish()
+    /// Note that this is not a list of dynamic nodes. These must be VNodes and created through conditional rendering
+    /// or iterators.
+    Fragment(&'a [VNode<'a>]),
+}
+
+impl Default for DynamicNode<'_> {
+    fn default() -> Self {
+        Self::Placeholder(Default::default())
     }
 }
 
-/// A trait for any generic Dioxus Element.
-///
-/// This trait provides the ability to use custom elements in the `rsx!` macro.
-///
-/// ```rust, ignore
-/// struct my_element;
-///
-/// impl DioxusElement for my_element {
-///     const TAG_NAME: "my_element";
-///     const NAME_SPACE: None;
-/// }
-///
-/// let _ = rsx!{
-///     my_element {}
-/// };
-/// ```
-pub trait DioxusElement {
-    /// The tag name of the element.
-    const TAG_NAME: &'static str;
+/// An instance of a child component
+pub struct VComponent<'a> {
+    /// The name of this component
+    pub name: &'static str,
 
-    /// The namespace of the element.
-    const NAME_SPACE: Option<&'static str>;
+    /// Are the props valid for the 'static lifetime?
+    ///
+    /// Internally, this is used as a guarantee. Externally, this might be incorrect, so don't count on it.
+    ///
+    /// This flag is assumed by the [`crate::Properties`] trait which is unsafe to implement
+    pub static_props: bool,
 
-    /// The tag name of the element.
-    #[inline]
-    fn tag_name(&self) -> &'static str {
-        Self::TAG_NAME
-    }
+    /// The assigned Scope for this component
+    pub scope: Cell<Option<ScopeId>>,
+
+    /// The function pointer of the component, known at compile time
+    ///
+    /// It is possible that components get folded at comppile time, so these shouldn't be really used as a key
+    pub render_fn: *const (),
 
-    /// The namespace of the element.
-    #[inline]
-    fn namespace(&self) -> Option<&'static str> {
-        Self::NAME_SPACE
+    pub(crate) props: Cell<Option<Box<dyn AnyProps<'a> + 'a>>>,
+}
+
+impl<'a> std::fmt::Debug for VComponent<'a> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("VComponent")
+            .field("name", &self.name)
+            .field("static_props", &self.static_props)
+            .field("scope", &self.scope)
+            .finish()
     }
 }
 
-type StaticStr = &'static str;
+/// An instance of some text, mounted to the DOM
+#[derive(Debug)]
+pub struct VText<'a> {
+    /// The actual text itself
+    pub value: &'a str,
 
-/// A discription of the attribute
-#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq)]
-#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
+    /// The ID of this node in the real DOM
+    pub id: Cell<ElementId>,
+}
+
+/// An attribute of the TemplateNode, created at compile time
+#[derive(Debug, PartialEq, Hash, Eq, PartialOrd, Ord)]
 #[cfg_attr(
-    all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
-    derive(serde::Deserialize)
+    feature = "serialize",
+    derive(serde::Serialize, serde::Deserialize),
+    serde(tag = "type")
 )]
-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,
+pub enum TemplateAttribute<'a> {
+    /// This attribute is entirely known at compile time, enabling
+    Static {
+        /// The name of this attribute.
+        ///
+        /// For example, the `href` attribute in `href="https://example.com"`, would have the name "href"
+        name: &'a str,
+
+        /// The value of this attribute, known at compile time
+        ///
+        /// Currently this only accepts &str, so values, even if they're known at compile time, are not known
+        value: &'a str,
+
+        /// The namespace of this attribute. Does not exist in the HTML spec
+        namespace: Option<&'a str>,
+    },
 
-    /// The namespace of the attribute.
+    /// The attribute in this position is actually determined dynamically at runtime
     ///
-    /// 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,
+    /// This is the index into the dynamic_attributes field on the container VNode
+    Dynamic {
+        /// The index
+        id: usize,
+    },
 }
 
-/// An attribute on a DOM node, such as `id="my-thing"` or
-/// `href="https://example.com"`.
-#[derive(Clone, Debug)]
+/// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"`
+#[derive(Debug)]
 pub struct Attribute<'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,
+    /// The name of the attribute.
+    pub name: &'a str,
 
-    /// The value of the attribute.
+    /// The value of the attribute
     pub value: AttributeValue<'a>,
-}
-
-/// An event listener.
-/// IE onclick, onkeydown, etc
-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>>,
 
-    /// The type of event to listen for.
+    /// The namespace of the attribute.
     ///
-    /// IE "click" - whatever the renderer needs to attach the listener by name.
-    pub event: &'static str,
-
-    /// The actual callback that the user specified
-    pub(crate) callback: InternalHandler<'bump>,
-}
-
-pub type InternalHandler<'bump> = &'bump RefCell<Option<InternalListenerCallback<'bump>>>;
-type InternalListenerCallback<'bump> = BumpBox<'bump, dyn FnMut(AnyEvent) + 'bump>;
-type ExternalListenerCallback<'bump, T> = BumpBox<'bump, dyn FnMut(T) + 'bump>;
-
-/// The callback type generated by the `rsx!` macro when an `on` field is specified for components.
-///
-/// This makes it possible to pass `move |evt| {}` style closures into components as property fields.
-///
-///
-/// # Example
-///
-/// ```rust, ignore
-///
-/// rsx!{
-///     MyComponent { onclick: move |evt| log::info!("clicked"), }
-/// }
-///
-/// #[derive(Props)]
-/// struct MyProps<'a> {
-///     onclick: EventHandler<'a, MouseEvent>,
-/// }
-///
-/// fn MyComponent(cx: Scope<'a, MyProps<'a>>) -> Element {
-///     cx.render(rsx!{
-///         button {
-///             onclick: move |evt| cx.props.onclick.call(evt),
-///         }
-///     })
-/// }
-///
-/// ```
-pub struct EventHandler<'bump, T = ()> {
-    /// The (optional) callback that the user specified
-    /// Uses a `RefCell` to allow for interior mutability, and FnMut closures.
-    pub callback: RefCell<Option<ExternalListenerCallback<'bump, T>>>,
-}
-
-impl<'a, T> Default for EventHandler<'a, T> {
-    fn default() -> Self {
-        Self {
-            callback: RefCell::new(None),
-        }
-    }
-}
+    /// Doesn’t exist in the html spec. Used in Dioxus to denote “style” tags and other attribute groups.
+    pub namespace: Option<&'static str>,
 
-impl<T> EventHandler<'_, T> {
-    /// Call this event handler with the appropriate event type
-    pub fn call(&self, event: T) {
-        log::trace!("calling event handler");
-        if let Some(callback) = self.callback.borrow_mut().as_mut() {
-            callback(event);
-        }
-        log::trace!("done");
-    }
+    /// The element in the DOM that this attribute belongs to
+    pub mounted_element: Cell<ElementId>,
 
-    /// Forcibly drop the internal handler callback, releasing memory
-    pub fn release(&self) {
-        self.callback.replace(None);
-    }
+    /// An indication of we should always try and set the attribute. Used in controlled components to ensure changes are propagated
+    pub volatile: bool,
 }
 
-/// Virtual Components for custom user-defined components
-/// Only supports the functional syntax
-pub struct VComponent<'src> {
-    /// The key of the component to be used during keyed diffing.
-    pub key: Option<&'src str>,
+/// Any of the built-in values that the Dioxus VirtualDom supports as dynamic attributes on elements
+///
+/// These are built-in to be faster during the diffing process. To use a custom value, use the [`AttributeValue::Any`]
+/// variant.
+pub enum AttributeValue<'a> {
+    /// Text attribute
+    Text(&'a str),
 
-    /// The ID of the component.
-    /// Will not be assigned until after the component has been initialized.
-    pub scope: Cell<Option<ScopeId>>,
+    /// A float
+    Float(f64),
 
-    /// An indication if the component is static (can be memozied)
-    pub can_memoize: bool,
+    /// Signed integer
+    Int(i64),
 
-    /// The function pointer to the component's render function.
-    pub user_fc: ComponentPtr,
+    /// Boolean
+    Bool(bool),
 
-    /// The actual name of the component.
-    pub fn_name: &'static str,
+    /// A listener, like "onclick"
+    Listener(RefCell<Option<ListenerCb<'a>>>),
 
-    /// The props of the component.
-    pub props: RefCell<Option<Box<dyn AnyProps + 'src>>>,
-}
+    /// An arbitrary value that implements PartialEq and is static
+    Any(BumpBox<'a, dyn AnyValue>),
 
-pub(crate) struct VComponentProps<P> {
-    pub render_fn: Component<P>,
-    pub memo: unsafe fn(&P, &P) -> bool,
-    pub props: P,
+    /// A "none" value, resulting in the removal of an attribute from the dom
+    None,
 }
 
-pub trait AnyProps {
-    fn as_ptr(&self) -> *const ();
-    fn render<'a>(&'a self, bump: &'a ScopeState) -> Element<'a>;
-    unsafe fn memoize(&self, other: &dyn AnyProps) -> bool;
-}
+type ListenerCb<'a> = BumpBox<'a, dyn FnMut(Event<dyn Any>) + 'a>;
 
-impl<P> AnyProps for VComponentProps<P> {
-    fn as_ptr(&self) -> *const () {
-        &self.props as *const _ as *const ()
+impl<'a> std::fmt::Debug for AttributeValue<'a> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Self::Text(arg0) => f.debug_tuple("Text").field(arg0).finish(),
+            Self::Float(arg0) => f.debug_tuple("Float").field(arg0).finish(),
+            Self::Int(arg0) => f.debug_tuple("Int").field(arg0).finish(),
+            Self::Bool(arg0) => f.debug_tuple("Bool").field(arg0).finish(),
+            Self::Listener(_) => f.debug_tuple("Listener").finish(),
+            Self::Any(_) => f.debug_tuple("Any").finish(),
+            Self::None => write!(f, "None"),
+        }
     }
+}
 
-    // Safety:
-    // this will downcast the other ptr as our swallowed type!
-    // you *must* make this check *before* calling this method
-    // if your functions are not the same, then you will downcast a pointer into a different type (UB)
-    unsafe fn memoize(&self, other: &dyn AnyProps) -> bool {
-        let real_other: &P = &*(other.as_ptr() as *const _ as *const P);
-        let real_us: &P = &*(self.as_ptr() as *const _ as *const P);
-        (self.memo)(real_us, real_other)
+impl<'a> PartialEq for AttributeValue<'a> {
+    fn eq(&self, other: &Self) -> bool {
+        match (self, other) {
+            (Self::Text(l0), Self::Text(r0)) => l0 == r0,
+            (Self::Float(l0), Self::Float(r0)) => l0 == r0,
+            (Self::Int(l0), Self::Int(r0)) => l0 == r0,
+            (Self::Bool(l0), Self::Bool(r0)) => l0 == r0,
+            (Self::Listener(_), Self::Listener(_)) => true,
+            (Self::Any(l0), Self::Any(r0)) => l0.any_cmp(r0.as_ref()),
+            _ => core::mem::discriminant(self) == core::mem::discriminant(other),
+        }
     }
+}
 
-    fn render<'a>(&'a self, scope: &'a ScopeState) -> Element<'a> {
-        let props = unsafe { std::mem::transmute::<&P, &P>(&self.props) };
-        (self.render_fn)(Scope { scope, props })
-    }
+#[doc(hidden)]
+pub trait AnyValue {
+    fn any_cmp(&self, other: &dyn AnyValue) -> bool;
+    fn our_typeid(&self) -> TypeId;
 }
 
-/// This struct provides an ergonomic API to quickly build VNodes.
-///
-/// NodeFactory is used to build VNodes in the component's memory space.
-/// This struct adds metadata to the final VNode about listeners, attributes, and children
-#[derive(Copy, Clone)]
-pub struct NodeFactory<'a> {
-    pub(crate) scope: &'a ScopeState,
-    pub(crate) bump: &'a Bump,
-}
-
-impl<'a> NodeFactory<'a> {
-    /// Create a new [`NodeFactory`] from a [`Scope`] or [`ScopeState`]
-    pub fn new(scope: &'a ScopeState) -> NodeFactory<'a> {
-        NodeFactory {
-            scope,
-            bump: &scope.wip_frame().bump,
+impl<T: PartialEq + Any> AnyValue for T {
+    fn any_cmp(&self, other: &dyn AnyValue) -> bool {
+        if self.type_id() != other.our_typeid() {
+            return false;
         }
-    }
-
-    /// Get the custom allocator for this component
-    #[inline]
-    pub fn bump(&self) -> &'a bumpalo::Bump {
-        self.bump
-    }
 
-    /// Directly pass in text blocks without the need to use the format_args macro.
-    pub fn static_text(&self, text: &'static str) -> VNode<'a> {
-        VNode::Text(self.bump.alloc(VText {
-            id: empty_cell(),
-            text,
-            is_static: true,
-        }))
+        self == unsafe { &*(other as *const _ as *const T) }
     }
 
-    /// Parses a lazy text Arguments and returns a string and a flag indicating if the text is 'static
-    ///
-    /// Text that's static may be pointer compared, making it cheaper to diff
-    pub fn raw_text(&self, args: Arguments) -> (&'a str, bool) {
-        match args.as_str() {
-            Some(static_str) => (static_str, true),
-            None => {
-                use bumpalo::core_alloc::fmt::Write;
-                let mut str_buf = bumpalo::collections::String::new_in(self.bump);
-                str_buf.write_fmt(args).unwrap();
-                (str_buf.into_bump_str(), false)
-            }
-        }
+    fn our_typeid(&self) -> TypeId {
+        self.type_id()
     }
+}
 
-    /// Create some text that's allocated along with the other vnodes
-    ///
-    pub fn text(&self, args: Arguments) -> VNode<'a> {
-        let (text, is_static) = self.raw_text(args);
-
-        VNode::Text(self.bump.alloc(VText {
-            text,
-            is_static,
-            id: empty_cell(),
-        }))
-    }
-
-    /// Create a new [`VNode::Element`]
-    pub fn element(
-        &self,
-        el: impl DioxusElement,
-        listeners: &'a [Listener<'a>],
-        attributes: &'a [Attribute<'a>],
-        children: &'a [VNode<'a>],
-        key: Option<Arguments>,
-    ) -> VNode<'a> {
-        self.raw_element(
-            el.tag_name(),
-            el.namespace(),
-            listeners,
-            attributes,
-            children,
-            key,
-        )
-    }
-
-    /// Create a new [`VNode::Element`] without the trait bound
-    ///
-    /// IE pass in "div" instead of `div`
-    pub fn raw_element(
-        &self,
-        tag_name: &'static str,
-        namespace: Option<&'static str>,
-        listeners: &'a [Listener<'a>],
-        attributes: &'a [Attribute<'a>],
-        children: &'a [VNode<'a>],
-        key: Option<Arguments>,
-    ) -> VNode<'a> {
-        let key = key.map(|f| self.raw_text(f).0);
-
-        let mut items = self.scope.items.borrow_mut();
-        for listener in listeners {
-            let long_listener = unsafe { std::mem::transmute(listener) };
-            items.listeners.push(long_listener);
-        }
-
-        VNode::Element(self.bump.alloc(VElement {
-            tag: tag_name,
-            key,
-            namespace,
-            listeners,
-            attributes,
-            children,
-            id: empty_cell(),
-            parent: empty_cell(),
-        }))
-    }
-
-    /// Create a new [`Attribute`] from a attribute discrimination and a value
-    pub fn attr_disciption(
-        &self,
-        discription: AttributeDiscription,
-        val: impl IntoAttributeValue<'a>,
-    ) -> Attribute<'a> {
-        Attribute {
-            attribute: discription,
-            is_static: false,
-            value: val.into_value(self.bump),
-        }
-    }
+#[doc(hidden)]
+pub trait ComponentReturn<'a, A = ()> {
+    fn into_return(self, cx: &'a ScopeState) -> RenderReturn<'a>;
+}
 
-    /// Create a new [`Attribute`]
-    pub fn attr(
-        &self,
-        name: &'static str,
-        val: impl IntoAttributeValue<'a>,
-        namespace: Option<&'static str>,
-        is_volatile: bool,
-    ) -> Attribute<'a> {
-        Attribute {
-            attribute: AttributeDiscription {
-                name,
-                namespace,
-                volatile: is_volatile,
-            },
-            is_static: false,
-            value: val.into_value(self.bump),
-        }
+impl<'a> ComponentReturn<'a> for Element<'a> {
+    fn into_return(self, _cx: &ScopeState) -> RenderReturn<'a> {
+        RenderReturn::Sync(self)
     }
+}
 
-    /// Create a new [`Attribute`] using non-arguments
-    pub fn custom_attr(
-        &self,
-        name: &'static str,
-        value: AttributeValue<'a>,
-        namespace: Option<&'static str>,
-        is_volatile: bool,
-        is_static: bool,
-    ) -> Attribute<'a> {
-        Attribute {
-            attribute: AttributeDiscription {
-                name,
-                namespace,
-                volatile: is_volatile,
-            },
-            is_static,
-            value,
-        }
+#[doc(hidden)]
+pub struct AsyncMarker;
+impl<'a, F> ComponentReturn<'a, AsyncMarker> for F
+where
+    F: Future<Output = Element<'a>> + 'a,
+{
+    fn into_return(self, cx: &'a ScopeState) -> RenderReturn<'a> {
+        let f: &mut dyn Future<Output = Element<'a>> = cx.bump().alloc(self);
+        RenderReturn::Async(unsafe { BumpBox::from_raw(f) })
     }
+}
 
-    /// Create a new [`VNode::Component`]
-    pub fn component<P>(
-        &self,
-        component: fn(Scope<'a, P>) -> Element,
-        props: P,
-        key: Option<Arguments>,
-        fn_name: &'static str,
-    ) -> VNode<'a>
-    where
-        P: Properties + 'a,
-    {
-        let vcomp = self.bump.alloc(VComponent {
-            key: key.map(|f| self.raw_text(f).0),
-            scope: Default::default(),
-            can_memoize: P::IS_STATIC,
-            user_fc: component as ComponentPtr,
-            fn_name,
-            props: RefCell::new(Some(Box::new(VComponentProps {
-                props,
-                memo: P::memoize, // smuggle the memoization function across borders
-
-                // i'm sorry but I just need to bludgeon the lifetimes into place here
-                // this is safe because we're managing all lifetimes to originate from previous calls
-                // the intricacies of Rust's lifetime system make it difficult to properly express
-                // the transformation from this specific lifetime to the for<'a> lifetime
-                render_fn: unsafe { std::mem::transmute(component) },
-            }))),
-        });
-
-        if !P::IS_STATIC {
-            let vcomp = &*vcomp;
-            let vcomp = unsafe { std::mem::transmute(vcomp) };
-            self.scope.items.borrow_mut().borrowed_props.push(vcomp);
-        }
-
-        VNode::Component(vcomp)
+impl<'a> RenderReturn<'a> {
+    pub(crate) unsafe fn extend_lifetime_ref<'c>(&self) -> &'c RenderReturn<'c> {
+        unsafe { std::mem::transmute(self) }
     }
-
-    /// Create a new [`Listener`]
-    pub fn listener(self, event: &'static str, callback: InternalHandler<'a>) -> Listener<'a> {
-        Listener {
-            event,
-            mounted_node: Cell::new(None),
-            callback,
-        }
+    pub(crate) unsafe fn extend_lifetime<'c>(self) -> RenderReturn<'c> {
+        unsafe { std::mem::transmute(self) }
     }
+}
 
-    /// Create a new [`VNode::Fragment`] from a root of the rsx! call
-    pub fn fragment_root<'b, 'c>(
-        self,
-        node_iter: impl IntoIterator<Item = impl IntoVNode<'a> + 'c> + 'b,
-    ) -> VNode<'a> {
-        let mut nodes = bumpalo::collections::Vec::new_in(self.bump);
-
-        for node in node_iter {
-            nodes.push(node.into_vnode(self));
-        }
+/// A trait that allows various items to be converted into a dynamic node for the rsx macro
+pub trait IntoDynNode<'a, A = ()> {
+    /// Consume this item along with a scopestate and produce a DynamicNode
+    ///
+    /// You can use the bump alloactor of the scopestate to creat the dynamic node
+    fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a>;
+}
 
-        if nodes.is_empty() {
-            VNode::Placeholder(self.bump.alloc(VPlaceholder { id: empty_cell() }))
-        } else {
-            VNode::Fragment(self.bump.alloc(VFragment {
-                children: nodes.into_bump_slice(),
-                key: None,
-            }))
-        }
+impl<'a> IntoDynNode<'a> for () {
+    fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
+        DynamicNode::default()
     }
-
-    /// Create a new [`VNode::Fragment`] from any iterator
-    pub fn fragment_from_iter<'c, I, J>(
-        self,
-        node_iter: impl IntoVNode<'a, I, J> + 'c,
-    ) -> VNode<'a> {
-        node_iter.into_vnode(self)
+}
+impl<'a> IntoDynNode<'a> for VNode<'a> {
+    fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
+        DynamicNode::Fragment(_cx.bump().alloc([self]))
     }
+}
 
-    /// Create a new [`VNode`] from any iterator of children
-    pub fn create_children(
-        self,
-        node_iter: impl IntoIterator<Item = impl IntoVNode<'a>>,
-    ) -> Element<'a> {
-        let mut nodes = bumpalo::collections::Vec::new_in(self.bump);
-
-        for node in node_iter {
-            nodes.push(node.into_vnode(self));
-        }
-
-        if nodes.is_empty() {
-            Some(VNode::Placeholder(
-                self.bump.alloc(VPlaceholder { id: empty_cell() }),
-            ))
-        } else {
-            let children = nodes.into_bump_slice();
-
-            Some(VNode::Fragment(self.bump.alloc(VFragment {
-                children,
-                key: None,
-            })))
+// An element that's an error is currently lost into the ether
+impl<'a> IntoDynNode<'a> for Element<'a> {
+    fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
+        match self {
+            Ok(val) => val.into_vnode(_cx),
+            _ => DynamicNode::default(),
         }
     }
+}
 
-    /// Create a new [`EventHandler`] from an [`FnMut`]
-    pub fn event_handler<T>(self, f: impl FnMut(T) + 'a) -> EventHandler<'a, T> {
-        let handler: &mut dyn FnMut(T) = self.bump.alloc(f);
-        let caller = unsafe { BumpBox::from_raw(handler as *mut dyn FnMut(T)) };
-        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)));
+impl<'a, T: IntoDynNode<'a>> IntoDynNode<'a> for Option<T> {
+    fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
+        match self {
+            Some(val) => val.into_vnode(_cx),
+            None => DynamicNode::default(),
         }
-        VNode::TemplateRef(self.bump.alloc(VTemplateRef {
-            dynamic_context,
-            template_id: id,
-            node_ids: RefCell::new(Vec::new()),
-            parent: Cell::new(None),
-            template_ref_id: Cell::new(None),
-        }))
     }
 }
 
-impl Debug for NodeFactory<'_> {
-    fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        Ok(())
+impl<'a> IntoDynNode<'a> for &Element<'a> {
+    fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
+        match self.as_ref() {
+            Ok(val) => val.clone().into_vnode(_cx),
+            _ => DynamicNode::default(),
+        }
     }
 }
 
-/// Trait implementations for use in the rsx! and html! macros.
-///
-/// ## Details
-///
-/// This section provides convenience methods and trait implementations for converting common structs into a format accepted
-/// by the macros.
-///
-/// All dynamic content in the macros must flow in through `fragment_from_iter`. Everything else must be statically layed out.
-/// We pipe basically everything through `fragment_from_iter`, so we expect a very specific type:
-/// ```rust, ignore
-/// impl IntoIterator<Item = impl IntoVNode<'a>>
-/// ```
-///
-/// As such, all node creation must go through the factory, which is only available in the component context.
-/// These strict requirements make it possible to manage lifetimes and state.
-pub trait IntoVNode<'a, I = (), J = ()> {
-    /// Convert this into a [`VNode`], using the [`NodeFactory`] as a source of allocation
-    fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a>;
+impl<'a, 'b> IntoDynNode<'a> for LazyNodes<'a, 'b> {
+    fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a> {
+        DynamicNode::Fragment(cx.bump().alloc([self.call(cx)]))
+    }
 }
 
-// TODO: do we even need this? It almost seems better not to
-// // For the case where a rendered VNode is passed into the rsx! macro through curly braces
-impl<'a> IntoVNode<'a> for VNode<'a> {
-    fn into_vnode(self, _: NodeFactory<'a>) -> VNode<'a> {
-        self
+impl<'a> IntoDynNode<'_> for &'a str {
+    fn into_vnode(self, cx: &ScopeState) -> DynamicNode {
+        cx.text_node(format_args!("{}", self))
     }
 }
 
-// Conveniently, we also support "null" (nothing) passed in
-impl IntoVNode<'_> for () {
-    fn into_vnode(self, cx: NodeFactory) -> VNode {
-        VNode::Placeholder(cx.bump.alloc(VPlaceholder { id: empty_cell() }))
+impl IntoDynNode<'_> for String {
+    fn into_vnode(self, cx: &ScopeState) -> DynamicNode {
+        cx.text_node(format_args!("{}", self))
     }
 }
 
-impl<'a, 'b> IntoVNode<'a> for LazyNodes<'a, 'b> {
-    fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
-        self.call(cx)
+impl<'b> IntoDynNode<'b> for Arguments<'_> {
+    fn into_vnode(self, cx: &'b ScopeState) -> DynamicNode<'b> {
+        cx.text_node(self)
     }
 }
 
-impl<'b> IntoVNode<'_> for &'b str {
-    fn into_vnode(self, cx: NodeFactory) -> VNode {
-        cx.text(format_args!("{}", self))
+impl<'a> IntoDynNode<'a> for &'a VNode<'a> {
+    fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
+        DynamicNode::Fragment(_cx.bump().alloc([VNode {
+            parent: self.parent,
+            template: self.template,
+            root_ids: self.root_ids,
+            key: self.key,
+            dynamic_nodes: self.dynamic_nodes,
+            dynamic_attrs: self.dynamic_attrs,
+        }]))
     }
 }
 
-impl IntoVNode<'_> for String {
-    fn into_vnode(self, cx: NodeFactory) -> VNode {
-        cx.text(format_args!("{}", self))
+pub trait IntoTemplate<'a> {
+    fn into_template(self, _cx: &'a ScopeState) -> VNode<'a>;
+}
+impl<'a> IntoTemplate<'a> for VNode<'a> {
+    fn into_template(self, _cx: &'a ScopeState) -> VNode<'a> {
+        self
     }
 }
-
-impl IntoVNode<'_> for Arguments<'_> {
-    fn into_vnode(self, cx: NodeFactory) -> VNode {
-        cx.text(self)
+impl<'a> IntoTemplate<'a> for Element<'a> {
+    fn into_template(self, _cx: &'a ScopeState) -> VNode<'a> {
+        match self {
+            Ok(val) => val.into_template(_cx),
+            _ => VNode::empty().unwrap(),
+        }
     }
 }
-
-impl<'a> IntoVNode<'a> for &VNode<'a> {
-    fn into_vnode(self, _cx: NodeFactory<'a>) -> VNode<'a> {
-        // borrowed nodes are strange
-        self.decouple()
+impl<'a, 'b> IntoTemplate<'a> for LazyNodes<'a, 'b> {
+    fn into_template(self, cx: &'a ScopeState) -> VNode<'a> {
+        self.call(cx)
     }
 }
 
 // Note that we're using the E as a generic but this is never crafted anyways.
+#[doc(hidden)]
 pub struct FromNodeIterator;
-impl<'a, T, I, E> IntoVNode<'a, FromNodeIterator, E> for T
+impl<'a, T, I> IntoDynNode<'a, FromNodeIterator> for T
 where
-    T: IntoIterator<Item = I>,
-    I: IntoVNode<'a, E>,
+    T: Iterator<Item = I>,
+    I: IntoTemplate<'a>,
 {
-    fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
-        let mut nodes = bumpalo::collections::Vec::new_in(cx.bump);
+    fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a> {
+        let mut nodes = bumpalo::collections::Vec::new_in(cx.bump());
+
+        nodes.extend(self.into_iter().map(|node| node.into_template(cx)));
 
-        for node in self {
-            nodes.push(node.into_vnode(cx));
+        match nodes.into_bump_slice() {
+            children if children.is_empty() => DynamicNode::default(),
+            children => DynamicNode::Fragment(children),
         }
+    }
+}
 
-        if nodes.is_empty() {
-            VNode::Placeholder(cx.bump.alloc(VPlaceholder { id: empty_cell() }))
-        } else {
-            let children = nodes.into_bump_slice();
-
-            if cfg!(debug_assertions)
-                && children.len() > 1
-                && children.last().unwrap().key().is_none()
-            {
-                // todo: make the backtrace prettier or remove it altogether
-                log::error!(
-                    r#"
-                Warning: Each child in an array or iterator should have a unique "key" prop.
-                Not providing a key will lead to poor performance with lists.
-                See docs.rs/dioxus for more information.
-                -------------
-                {:?}
-                "#,
-                    backtrace::Backtrace::new()
-                );
-            }
+/// A value that can be converted into an attribute value
+pub trait IntoAttributeValue<'a> {
+    /// Convert into an attribute value
+    fn into_value(self, bump: &'a Bump) -> AttributeValue<'a>;
+}
 
-            VNode::Fragment(cx.bump.alloc(VFragment {
-                children,
-                key: None,
-            }))
-        }
+impl<'a> IntoAttributeValue<'a> for &'a str {
+    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
+        AttributeValue::Text(self)
+    }
+}
+impl<'a> IntoAttributeValue<'a> for f64 {
+    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
+        AttributeValue::Float(self)
+    }
+}
+impl<'a> IntoAttributeValue<'a> for i64 {
+    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
+        AttributeValue::Int(self)
+    }
+}
+impl<'a> IntoAttributeValue<'a> for bool {
+    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
+        AttributeValue::Bool(self)
+    }
+}
+impl<'a> IntoAttributeValue<'a> for Arguments<'_> {
+    fn into_value(self, bump: &'a Bump) -> AttributeValue<'a> {
+        use bumpalo::core_alloc::fmt::Write;
+        let mut str_buf = bumpalo::collections::String::new_in(bump);
+        str_buf.write_fmt(self).unwrap();
+        AttributeValue::Text(str_buf.into_bump_str())
     }
 }

+ 1 - 96
packages/core/src/properties.rs

@@ -1,100 +1,5 @@
 use crate::innerlude::*;
 
-pub struct FragmentProps<'a>(Element<'a>);
-pub struct FragmentBuilder<'a, const BUILT: bool>(Element<'a>);
-impl<'a> FragmentBuilder<'a, false> {
-    pub fn children(self, children: Element<'a>) -> FragmentBuilder<'a, true> {
-        FragmentBuilder(children)
-    }
-}
-impl<'a, const A: bool> FragmentBuilder<'a, A> {
-    pub fn build(self) -> FragmentProps<'a> {
-        FragmentProps(self.0)
-    }
-}
-
-/// Access the children elements passed into the component
-///
-/// This enables patterns where a component is passed children from its parent.
-///
-/// ## Details
-///
-/// Unlike React, Dioxus allows *only* lists of children to be passed from parent to child - not arbitrary functions
-/// or classes. If you want to generate nodes instead of accepting them as a list, consider declaring a closure
-/// on the props that takes Context.
-///
-/// If a parent passes children into a component, the child will always re-render when the parent re-renders. In other
-/// words, a component cannot be automatically memoized if it borrows nodes from its parent, even if the component's
-/// props are valid for the static lifetime.
-///
-/// ## Example
-///
-/// ```rust, ignore
-/// fn App(cx: Scope) -> Element {
-///     cx.render(rsx!{
-///         CustomCard {
-///             h1 {}
-///             p {}
-///         }
-///     })
-/// }
-///
-/// #[derive(PartialEq, Props)]
-/// struct CardProps {
-///     children: Element
-/// }
-///
-/// fn CustomCard(cx: Scope<CardProps>) -> Element {
-///     cx.render(rsx!{
-///         div {
-///             h1 {"Title card"}
-///             {cx.props.children}
-///         }
-///     })
-/// }
-/// ```
-impl<'a> Properties for FragmentProps<'a> {
-    type Builder = FragmentBuilder<'a, false>;
-    const IS_STATIC: bool = false;
-    fn builder() -> Self::Builder {
-        FragmentBuilder(None)
-    }
-    unsafe fn memoize(&self, _other: &Self) -> bool {
-        false
-    }
-}
-
-/// Create inline fragments using Component syntax.
-///
-/// ## Details
-///
-/// Fragments capture a series of children without rendering extra nodes.
-///
-/// Creating fragments explicitly with the Fragment component is particularly useful when rendering lists or tables and
-/// a key is needed to identify each item.
-///
-/// ## Example
-///
-/// ```rust, ignore
-/// rsx!{
-///     Fragment { key: "abc" }
-/// }
-/// ```
-///
-/// ## Usage
-///
-/// Fragments are incredibly useful when necessary, but *do* add cost in the diffing phase.
-/// Try to avoid highly nested fragments if you can. Unlike React, there is no protection against infinitely nested fragments.
-///
-/// This function defines a dedicated `Fragment` component that can be used to create inline fragments in the RSX macro.
-///
-/// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it.
-#[allow(non_upper_case_globals, non_snake_case)]
-pub fn Fragment<'a>(cx: Scope<'a, FragmentProps<'a>>) -> Element {
-    let i = cx.props.0.as_ref().map(|f| f.decouple());
-    cx.render(LazyNodes::new(|f| f.fragment_from_iter(i)))
-}
-
 /// Every "Props" used for a component must implement the `Properties` trait. This trait gives some hints to Dioxus
 /// on how to memoize the props and some additional optimizations that can be made. We strongly encourage using the
 /// derive macro to implement the `Properties` trait automatically as guarantee that your memoization strategy is safe.
@@ -165,6 +70,6 @@ impl EmptyBuilder {
 
 /// This utility function launches the builder method so rsx! and html! macros can use the typed-builder pattern
 /// to initialize a component's props.
-pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Scope<'a, T>) -> Element) -> T::Builder {
+pub fn fc_to_builder<'a, A, T: Properties + 'a>(_: fn(Scope<'a, T>) -> A) -> T::Builder {
     T::builder()
 }

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác