Parcourir la source

Merge remote-tracking branch 'upstream/master' into optimize-templates-v3

Evan Almloff il y a 2 ans
Parent
commit
b79ad4f50e
68 fichiers modifiés avec 1042 ajouts et 1293 suppressions
  1. 7 0
      .github/dependabot.yml
  2. 2 0
      examples/all_css.rs
  3. 2 6
      examples/borrowed.rs
  4. 10 22
      examples/calculator.rs
  5. 2 2
      examples/callback.rs
  6. 1 1
      examples/custom_element.rs
  7. 1 1
      examples/nested_listeners.rs
  8. 4 4
      examples/rsx_compile_fail.rs
  9. 0 32
      examples/simple_todo.rs
  10. 2 2
      examples/ssr.rs
  11. 0 16
      examples/suspense2.rs
  12. 5 8
      examples/todomvc.rs
  13. 6 6
      examples/window_event.rs
  14. 98 101
      notes/README/ZH_CN.md
  15. 3 3
      packages/core/README.md
  16. 59 34
      packages/core/src/arena.rs
  17. 8 6
      packages/core/src/create.rs
  18. 28 30
      packages/core/src/diff.rs
  19. 2 2
      packages/core/src/fragment.rs
  20. 1 0
      packages/core/src/mutations.rs
  21. 34 8
      packages/core/src/nodes.rs
  22. 8 7
      packages/core/src/scheduler/wait.rs
  23. 6 38
      packages/core/src/scope_arena.rs
  24. 23 23
      packages/core/src/scopes.rs
  25. 9 10
      packages/core/src/virtual_dom.rs
  26. 3 3
      packages/core/tests/bubble_error.rs
  27. 4 4
      packages/core/tests/diff_keyed_list.rs
  28. 6 6
      packages/core/tests/diff_unkeyed_list.rs
  29. 5 5
      packages/core/tests/miri_full_app.rs
  30. 2 2
      packages/core/tests/miri_stress.rs
  31. 3 1
      packages/core/tests/safety.rs
  32. 6 5
      packages/core/tests/suspense.rs
  33. 3 2
      packages/desktop/Cargo.toml
  34. 15 12
      packages/desktop/src/controller.rs
  35. 89 82
      packages/desktop/src/desktop_context.rs
  36. 0 2
      packages/desktop/src/events.rs
  37. 12 6
      packages/desktop/src/hot_reload.rs
  38. 127 114
      packages/desktop/src/lib.rs
  39. 2 1
      packages/fermi/src/hooks/atom_root.rs
  40. 10 6
      packages/hooks/src/usecallback.rs
  41. 9 9
      packages/hooks/src/usecontext.rs
  42. 2 4
      packages/hooks/src/usefuture.rs
  43. 3 3
      packages/html/src/render_template.rs
  44. 5 2
      packages/interpreter/src/bindings.rs
  45. 61 17
      packages/interpreter/src/interpreter.js
  46. 10 5
      packages/native-core/src/real_dom.rs
  47. 3 5
      packages/router/src/components/link.rs
  48. 1 1
      packages/router/src/components/redirect.rs
  49. 5 9
      packages/router/src/components/route.rs
  50. 6 7
      packages/router/src/components/router.rs
  51. 6 6
      packages/router/src/hooks/use_route.rs
  52. 5 7
      packages/router/src/hooks/use_router.rs
  53. 15 18
      packages/router/src/service.rs
  54. 1 1
      packages/rsx/src/component.rs
  55. 1 4
      packages/rsx/src/ifmt.rs
  56. 11 9
      packages/rsx/src/lib.rs
  57. 1 1
      packages/rsx/src/node.rs
  58. 21 9
      packages/ssr/src/cache.rs
  59. 0 37
      packages/ssr/src/config.rs
  60. 0 50
      packages/ssr/src/helpers.rs
  61. 49 3
      packages/ssr/src/lib.rs
  62. 181 146
      packages/ssr/src/renderer.rs
  63. 0 139
      packages/ssr/src/template.rs
  64. 0 144
      packages/ssr/tests.old/renders.rs
  65. 30 38
      packages/ssr/tests/simple.rs
  66. 1 0
      packages/web/Cargo.toml
  67. 6 6
      packages/web/src/dom.rs
  68. 1 0
      packages/web/src/lib.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 - 0
examples/all_css.rs

@@ -406,6 +406,8 @@ fn app(cx: Scope) -> Element {
             wrap_through: "a",
             writing_mode: "a",
             z_index: "a",
+
+            "This example isn't quite useful yet"
         }
     })
 }

+ 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 }
     })
 }
 

+ 10 - 22
examples/calculator.rs

@@ -25,6 +25,7 @@ fn app(cx: Scope) -> Element {
         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()))),
                                 "="
                             }
                         }

+ 2 - 2
examples/callback.rs

@@ -5,7 +5,7 @@ fn main() {
 }
 
 fn app(cx: Scope) -> Element {
-    let login = use_callback!(cx, || move |evt: MouseEvent| async move {
+    let login = use_callback!(cx, move |_| async move {
         let res = reqwest::get("https://dog.ceo/api/breeds/list/all")
             .await
             .unwrap()
@@ -13,7 +13,7 @@ fn app(cx: Scope) -> Element {
             .await
             .unwrap();
 
-        println!("{}, ", res);
+        println!("{:#?}, ", res);
     });
 
     cx.render(rsx! {

+ 1 - 1
examples/custom_element.rs

@@ -10,7 +10,7 @@ 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);
 }

+ 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"
             }

+ 4 - 4
examples/rsx_compile_fail.rs

@@ -2,14 +2,14 @@
 //! It also proves that lifetimes work properly, especially when used with use_ref
 
 use dioxus::prelude::*;
-use dioxus_ssr::config::SsrConfig;
 
 fn main() {
     let mut vdom = VirtualDom::new(example);
-    vdom.rebuild();
+    _ = vdom.rebuild();
 
-    let out = dioxus_ssr::render_vdom_cfg(&vdom, SsrConfig::default().newline(true).indent(true));
-    println!("{}", out);
+    let mut renderer = dioxus_ssr::Renderer::new();
+    renderer.pretty = true;
+    renderer.render(&vdom);
 }
 
 fn example(cx: Scope) -> Element {

+ 0 - 32
examples/simple_todo.rs

@@ -1,32 +0,0 @@
-use dioxus::prelude::*;
-
-fn main() {
-    dioxus_desktop::launch(app);
-}
-
-fn app(cx: Scope) -> Element {
-    let mut idx = use_state(cx, || 0);
-    let onhover = |h| println!("go!");
-
-    cx.render(rsx! {
-        div {
-            button { onclick: move |_| idx += 1, "+" }
-            button { onclick: move |_| idx -= 1, "-" }
-            ul {
-                (0..**idx).map(|i| rsx! {
-                    Child { i: i, onhover: onhover }
-                })
-            }
-        }
-    })
-}
-
-#[inline_props]
-fn Child<'a>(cx: Scope<'a>, i: i32, onhover: EventHandler<'a, MouseEvent>) -> Element {
-    cx.render(rsx! {
-        li {
-            onmouseover: move |e| onhover.call(e),
-            "{i}"
-        }
-    })
-}

+ 2 - 2
examples/ssr.rs

@@ -5,7 +5,7 @@
 use std::fmt::Write;
 
 use dioxus::prelude::*;
-use dioxus_ssr::config::SsrConfig;
+use dioxus_ssr::config::Config;
 
 fn main() {
     // We can render VirtualDoms
@@ -26,7 +26,7 @@ fn main() {
     // We can configure the SSR rendering to add ids for rehydration
     println!(
         "{}",
-        dioxus_ssr::render_vdom_cfg(&vdom, SsrConfig::default().pre_render(true))
+        dioxus_ssr::render_vdom_cfg(&vdom, Config::default().pre_render(true))
     );
 
     // We can even render as a writer

+ 0 - 16
examples/suspense2.rs

@@ -1,16 +0,0 @@
-use dioxus::prelude::*;
-use dioxus_desktop::{Config, LogicalSize, WindowBuilder};
-
-fn main() {
-    dioxus_desktop::launch(|cx| cx.render(rsx! { async_app {} }));
-}
-
-async fn async_app(cx: Scope<'_>) -> Element {
-    tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
-
-    cx.render(rsx! {
-        div {
-            "hi!"
-        }
-    })
-}

+ 5 - 8
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
@@ -58,7 +58,6 @@ pub fn app(cx: Scope<()>) -> Element {
                         value: "{draft}",
                         autofocus: "true",
                         oninput: move |evt| {
-                            println!("calling oninput");
                             draft.set(evt.value.clone());
                         },
                         onkeydown: move |evt| {
@@ -117,15 +116,13 @@ 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];
     let completed = if todo.checked { "completed" } else { "" };
     let editing = if **is_editing { "editing" } else { "" };
 
-    println!("rendering todo entry");
-
     cx.render(rsx!{
         li {
             class: "{completed} {editing}",

+ 6 - 6
examples/window_event.rs

@@ -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"
                     }

+ 98 - 101
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,29 +34,23 @@
   </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 {
@@ -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 协议,没有任何附加条款或条件。

+ 3 - 3
packages/core/README.md

@@ -41,7 +41,7 @@ fn app(cx: Scope) -> Element {
 
 Then, we'll want to create a new VirtualDom using this app as the root component.
 
-```rust, ingore
+```rust, ignore
 let mut dom = VirtualDom::new(app);
 ```
 
@@ -65,9 +65,9 @@ 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.
 
-```rust
+```rust, ignore
 loop {
-    tokio::select! {
+    select! {
         evt = real_dom.event() => dom.handle_event("click", evt.data, evt.element, evt.bubbles),
         _ = dom.wait_for_work() => {}
     }

+ 59 - 34
packages/core/src/arena.rs

@@ -1,7 +1,4 @@
-use crate::{
-    nodes::RenderReturn, nodes::VNode, virtual_dom::VirtualDom, AttributeValue, DynamicNode,
-    ScopeId,
-};
+use crate::{nodes::RenderReturn, nodes::VNode, virtual_dom::VirtualDom, DynamicNode, ScopeId};
 use bumpalo::boxed::Box as BumpBox;
 
 /// An Element's unique identifier.
@@ -37,21 +34,20 @@ impl ElementRef {
 
 impl VirtualDom {
     pub(crate) fn next_element(&mut self, template: &VNode, path: &'static [u8]) -> ElementId {
-        let entry = self.elements.vacant_entry();
-        let id = entry.key();
-        entry.insert(ElementRef {
-            template: template as *const _ as *mut _,
-            path: ElementPath::Deep(path),
-        });
-        ElementId(id)
+        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: ElementPath::Root(path),
+            path,
         });
         ElementId(id)
     }
@@ -64,9 +60,9 @@ impl VirtualDom {
     pub(crate) fn try_reclaim(&mut self, el: ElementId) -> Option<ElementRef> {
         if el.0 == 0 {
             panic!(
-                "Invalid element set to 0 - {:#?}",
+                "Cannot reclaim the root element - {:#?}",
                 std::backtrace::Backtrace::force_capture()
-            )
+            );
         }
 
         self.elements.try_remove(el.0)
@@ -79,17 +75,15 @@ impl VirtualDom {
 
     // Drop a scope and all its children
     pub(crate) fn drop_scope(&mut self, id: ScopeId) {
-        let scope = self.scopes.get(id.0).unwrap();
-
-        if let Some(root) = scope.as_ref().try_root_node() {
-            let root = unsafe { root.extend_lifetime_ref() };
-            if let RenderReturn::Sync(Ok(node)) = root {
+        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)
             }
         }
 
-        let scope = self.scopes.get_mut(id.0).unwrap();
-        scope.props.take();
+        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
@@ -99,24 +93,55 @@ impl VirtualDom {
     }
 
     fn drop_scope_inner(&mut self, node: &VNode) {
-        for attr in node.dynamic_attrs {
-            if let AttributeValue::Listener(l) = &attr.value {
-                l.borrow_mut().take();
+        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 (idx, _) in node.template.roots.iter().enumerate() {
-            match node.dynamic_root(idx) {
-                Some(DynamicNode::Component(c)) => self.drop_scope(c.scope.get().unwrap()),
-                Some(DynamicNode::Fragment(nodes)) => {
-                    for node in *nodes {
-                        self.drop_scope_inner(node);
-                    }
-                }
-                _ => {}
+        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 {

+ 8 - 6
packages/core/src/create.rs

@@ -1,4 +1,5 @@
 use std::cell::Cell;
+use std::rc::Rc;
 
 use crate::innerlude::{VComponent, VText};
 use crate::mutations::Mutation;
@@ -42,7 +43,7 @@ impl<'b> VirtualDom {
         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) => {
+                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 }) => {
@@ -72,7 +73,7 @@ impl<'b> VirtualDom {
                     }
                 }
 
-                TemplateNode::Element { .. } | TemplateNode::Text(_) => {
+                TemplateNode::Element { .. } | TemplateNode::Text { .. } => {
                     let this_id = self.next_root(template, root_idx);
 
                     template.root_ids[root_idx].set(this_id);
@@ -213,7 +214,7 @@ impl<'b> VirtualDom {
         if template.template.roots.iter().all(|root| {
             matches!(
                 root,
-                TemplateNode::Dynamic(_) | TemplateNode::DynamicText(_)
+                TemplateNode::Dynamic { .. } | TemplateNode::DynamicText { .. }
             )
         }) {
             return;
@@ -302,7 +303,8 @@ impl<'b> VirtualDom {
 
         let unbounded_props = unsafe { std::mem::transmute(props) };
 
-        let scope = self.new_scope(unbounded_props).id;
+        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() };
@@ -336,8 +338,8 @@ impl<'b> VirtualDom {
         }
 
         // 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::<SuspenseContext>() {
-            Some(boundary) => unsafe { &*(boundary as *const SuspenseContext) },
+        let boundary = match self.scopes[scope.0].has_context::<Rc<SuspenseContext>>() {
+            Some(boundary) => boundary,
             _ => return created,
         };
 

+ 28 - 30
packages/core/src/diff.rs

@@ -25,40 +25,35 @@ impl<'b> VirtualDom {
                 .previous_frame()
                 .try_load_node()
                 .expect("Call rebuild before diffing");
+
             let new = scope_state
                 .current_frame()
                 .try_load_node()
                 .expect("Call rebuild before diffing");
-            self.diff_maybe_node(old, new);
-        }
-        self.scope_stack.pop();
-    }
 
-    fn diff_maybe_node(&mut self, old: &'b RenderReturn<'b>, new: &'b RenderReturn<'b>) {
-        use RenderReturn::{Async, Sync};
-        match (old, new) {
-            (Sync(Ok(l)), Sync(Ok(r))) => self.diff_node(l, r),
+            use RenderReturn::{Async, Sync};
 
-            // 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 */ }
+            match (old, new) {
+                (Sync(Ok(l)), Sync(Ok(r))) => self.diff_node(l, r),
 
-            // Async
-            (Sync(Ok(_l)), Async(_)) => todo!(),
-            (Sync(Err(_e)), Async(_)) => todo!(),
-            (Async(_), Sync(Ok(_r))) => todo!(),
-            (Async(_), Sync(Err(_e))) => { /* nothing */ }
-            (Async(_), Async(_)) => { /* nothing */ }
+                // 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();
     }
 
-    fn diff_ok_to_err(&mut self, _l: &'b VNode<'b>, _e: &anyhow::Error) {
-        todo!("Not yet handling error rollover")
-    }
-    fn diff_err_to_ok(&mut self, _e: &anyhow::Error, _l: &'b VNode<'b>) {
-        todo!("Not yet handling error rollover")
-    }
+    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>) {}
 
     fn diff_node(&mut self, left_template: &'b VNode<'b>, right_template: &'b VNode<'b>) {
         if !std::ptr::eq(left_template.template.name, right_template.template.name)
@@ -144,16 +139,18 @@ impl<'b> VirtualDom {
     }
 
     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..]);
+
+        // Now create the new one
+        let first = self.replace_inner(&l[0]);
+
         // 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 });
 
-        // Remove the old nodes, except for onea
-        let first = self.replace_inner(&l[0]);
-        self.remove_nodes(&l[1..]);
-
         self.mutations
             .push(Mutation::ReplaceWith { id: first, m: 1 });
 
@@ -746,7 +743,8 @@ impl<'b> VirtualDom {
     /// Remove these nodes from the dom
     /// Wont generate mutations for the inner nodes
     fn remove_nodes(&mut self, nodes: &'b [VNode<'b>]) {
-        nodes.iter().for_each(|node| self.remove_node(node));
+        // 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 remove_node(&mut self, node: &'b VNode<'b>) {
@@ -911,7 +909,7 @@ fn matching_components<'a>(
         .zip(right.template.roots.iter())
         .map(|(l, r)| {
             let (l, r) = match (l, r) {
-                (TemplateNode::Dynamic(l), TemplateNode::Dynamic(r)) => (l, r),
+                (TemplateNode::Dynamic { id: l }, TemplateNode::Dynamic { id: r }) => (l, r),
                 _ => return None,
             };
 

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

@@ -29,8 +29,8 @@ use crate::innerlude::*;
 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.clone(),
-        parent: children.parent.clone(),
+        key: children.key,
+        parent: children.parent,
         template: children.template,
         root_ids: children.root_ids,
         dynamic_nodes: children.dynamic_nodes,

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

@@ -13,6 +13,7 @@ use crate::{arena::ElementId, ScopeId, Template};
 /// 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> {

+ 34 - 8
packages/core/src/nodes.rs

@@ -78,12 +78,20 @@ impl<'a> VNode<'a> {
     /// Returns [`None`] if the root is actually a static node (Element/Text)
     pub fn dynamic_root(&self, idx: usize) -> Option<&'a DynamicNode<'a>> {
         match &self.template.roots[idx] {
-            TemplateNode::Element { .. } | TemplateNode::Text(_) => None,
-            TemplateNode::Dynamic(id) | TemplateNode::DynamicText(id) => {
+            TemplateNode::Element { .. } | TemplateNode::Text { text: _ } => None,
+            TemplateNode::Dynamic { id } | TemplateNode::DynamicText { id } => {
                 Some(&self.dynamic_nodes[*id])
             }
         }
     }
+
+    pub(crate) fn clear_listeners(&self) {
+        for attr in self.dynamic_attrs {
+            if let AttributeValue::Listener(l) = &attr.value {
+                l.borrow_mut().take();
+            }
+        }
+    }
 }
 
 /// A static layout of a UI tree that describes a set of dynamic and static nodes.
@@ -124,7 +132,7 @@ pub struct Template<'a> {
 ///
 /// This can be created at compile time, saving the VirtualDom time when diffing the tree
 #[derive(Debug, Clone, Copy, PartialEq, Hash, Eq, PartialOrd, Ord)]
-#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
+#[cfg_attr(feature = "serialize", derive(serde::Serialize), serde(tag = "type"))]
 pub enum TemplateNode<'a> {
     /// An statically known element in the dom.
     ///
@@ -151,15 +159,24 @@ pub enum TemplateNode<'a> {
     },
 
     /// This template node is just a piece of static text
-    Text(&'a str),
+    Text {
+        /// The actual text
+        text: &'a str,
+    },
 
     /// This template node is unknown, and needs to be created at runtime.
-    Dynamic(usize),
+    Dynamic {
+        /// The index of the dynamic node in the VNode's dynamic_nodes list
+        id: usize,
+    },
 
     /// This template node is known to be some text, but needs to be created at runtime
     ///
     /// This is separate from the pure Dynamic variant for various optimizations
-    DynamicText(usize),
+    DynamicText {
+        /// The index of the dynamic node in the VNode's dynamic_nodes list
+        id: usize,
+    },
 }
 
 /// A node created at runtime
@@ -244,7 +261,11 @@ pub struct VText<'a> {
 
 /// An attribute of the TemplateNode, created at compile time
 #[derive(Debug, PartialEq, Hash, Eq, PartialOrd, Ord)]
-#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
+#[cfg_attr(
+    feature = "serialize",
+    derive(serde::Serialize, serde::Deserialize),
+    serde(tag = "type")
+)]
 pub enum TemplateAttribute<'a> {
     /// This attribute is entirely known at compile time, enabling
     Static {
@@ -265,7 +286,10 @@ pub enum TemplateAttribute<'a> {
     /// The attribute in this position is actually determined dynamically at runtime
     ///
     /// This is the index into the dynamic_attributes field on the container VNode
-    Dynamic(usize),
+    Dynamic {
+        /// The index
+        id: usize,
+    },
 }
 
 /// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"`
@@ -416,6 +440,8 @@ impl<'a> IntoDynNode<'a> for VNode<'a> {
         DynamicNode::Fragment(_cx.bump().alloc([self]))
     }
 }
+
+// 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 {

+ 8 - 7
packages/core/src/scheduler/wait.rs

@@ -1,5 +1,8 @@
 use futures_util::FutureExt;
-use std::task::{Context, Poll};
+use std::{
+    rc::Rc,
+    task::{Context, Poll},
+};
 
 use crate::{
     innerlude::{Mutation, Mutations, SuspenseContext},
@@ -31,12 +34,10 @@ impl VirtualDom {
         }
     }
 
-    pub(crate) fn acquire_suspense_boundary<'a>(&self, id: ScopeId) -> &'a SuspenseContext {
-        let ct = self.scopes[id.0]
-            .consume_context::<SuspenseContext>()
-            .unwrap();
-
-        unsafe { &*(ct as *const SuspenseContext) }
+    pub(crate) fn acquire_suspense_boundary(&self, id: ScopeId) -> Rc<SuspenseContext> {
+        self.scopes[id.0]
+            .consume_context::<Rc<SuspenseContext>>()
+            .unwrap()
     }
 
     pub(crate) fn handle_suspense_wakeup(&mut self, id: SuspenseId) {

+ 6 - 38
packages/core/src/scope_arena.rs

@@ -7,7 +7,6 @@ use crate::{
     scheduler::RcWake,
     scopes::{ScopeId, ScopeState},
     virtual_dom::VirtualDom,
-    AttributeValue, DynamicNode, VNode,
 };
 use bumpalo::Bump;
 use futures_util::FutureExt;
@@ -19,7 +18,11 @@ use std::{
 };
 
 impl VirtualDom {
-    pub(super) fn new_scope(&mut self, props: Box<dyn AnyProps<'static>>) -> &ScopeState {
+    pub(super) fn new_scope(
+        &mut self,
+        props: Box<dyn AnyProps<'static>>,
+        name: &'static str,
+    ) -> &ScopeState {
         let parent = self.acquire_current_scope_raw();
         let entry = self.scopes.vacant_entry();
         let height = unsafe { parent.map(|f| (*f).height + 1).unwrap_or(0) };
@@ -30,6 +33,7 @@ impl VirtualDom {
             id,
             height,
             props: Some(props),
+            name,
             placeholder: Default::default(),
             node_arena_1: BumpFrame::new(0),
             node_arena_2: BumpFrame::new(0),
@@ -50,42 +54,6 @@ impl VirtualDom {
             .and_then(|id| self.scopes.get_mut(id.0).map(|f| f.as_mut() as *mut _))
     }
 
-    fn ensure_drop_safety(&self, scope: ScopeId) {
-        let scope = &self.scopes[scope.0];
-        let node = unsafe { scope.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) {
-        for attr in node.dynamic_attrs {
-            if let AttributeValue::Listener(l) = &attr.value {
-                l.borrow_mut().take();
-            }
-        }
-
-        for child in node.dynamic_nodes {
-            match child {
-                DynamicNode::Component(c) => {
-                    // Only descend if the props are borrowed
-                    if !c.static_props {
-                        self.ensure_drop_safety(c.scope.get().unwrap());
-                        c.props.set(None);
-                    }
-                }
-                DynamicNode::Fragment(f) => {
-                    for node in *f {
-                        self.ensure_drop_safety_inner(node);
-                    }
-                }
-                _ => {}
-            }
-        }
-    }
-
     pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> &RenderReturn {
         // Cycle to the next frame and then reset it
         // This breaks any latent references, invalidating every pointer referencing into it.

+ 23 - 23
packages/core/src/scopes.rs

@@ -69,6 +69,7 @@ pub struct ScopeId(pub usize);
 /// This struct exists to provide a common interface for all scopes without relying on generics.
 pub struct ScopeState {
     pub(crate) render_cnt: Cell<usize>,
+    pub(crate) name: &'static str,
 
     pub(crate) node_arena_1: BumpFrame,
     pub(crate) node_arena_2: BumpFrame,
@@ -116,6 +117,11 @@ impl<'src> ScopeState {
         }
     }
 
+    /// Get the name of this component
+    pub fn name(&self) -> &str {
+        self.name
+    }
+
     /// Get the current render since the inception of this component
     ///
     /// This can be used as a helpful diagnostic when debugging hooks/renders, etc
@@ -251,17 +257,18 @@ impl<'src> ScopeState {
     }
 
     /// Return any context of type T if it exists on this scope
-    pub fn has_context<T: 'static>(&self) -> Option<&T> {
-        let contextex = self.shared_contexts.borrow();
-        let val = contextex.get(&TypeId::of::<T>())?;
-        let as_concrete = val.downcast_ref::<T>()? as *const T;
-        Some(unsafe { &*as_concrete })
+    pub fn has_context<T: 'static + Clone>(&self) -> Option<T> {
+        self.shared_contexts
+            .borrow()
+            .get(&TypeId::of::<T>())?
+            .downcast_ref::<T>()
+            .cloned()
     }
 
     /// Try to retrieve a shared state with type `T` from any parent scope.
     ///
-    /// To release the borrow, use `cloned` if the context is clone.
-    pub fn consume_context<T: 'static>(&self) -> Option<&T> {
+    /// Clones the state if it exists.
+    pub fn consume_context<T: 'static + Clone>(&self) -> Option<T> {
         if let Some(this_ctx) = self.has_context() {
             return Some(this_ctx);
         }
@@ -271,8 +278,7 @@ impl<'src> ScopeState {
             // safety: all parent pointers are valid thanks to the bump arena
             let parent = unsafe { &*parent_ptr };
             if let Some(shared) = parent.shared_contexts.borrow().get(&TypeId::of::<T>()) {
-                let as_concrete = shared.downcast_ref::<T>()? as *const T;
-                return Some(unsafe { &*as_concrete });
+                return shared.downcast_ref::<T>().cloned();
             }
             search_parent = parent.parent;
         }
@@ -284,7 +290,7 @@ impl<'src> ScopeState {
     ///
     /// This is a "fundamental" operation and should only be called during initialization of a hook.
     ///
-    /// For a hook that provides the same functionality, use `use_provide_context` and `use_consume_context` instead.
+    /// For a hook that provides the same functionality, use `use_provide_context` and `use_context` instead.
     ///
     /// If a state is provided that already exists, the new value will not be inserted. Instead, this method will
     /// return the existing value. This behavior is chosen so shared values do not need to be `Clone`. This particular
@@ -305,20 +311,14 @@ impl<'src> ScopeState {
     ///     render!(div { "hello {state.0}" })
     /// }
     /// ```
-    pub fn provide_context<T: 'static>(&self, value: T) -> &T {
-        let mut contexts = self.shared_contexts.borrow_mut();
-
-        let any = match contexts.get(&TypeId::of::<T>()) {
-            Some(item) => item.downcast_ref::<T>().unwrap() as *const T,
-            None => {
-                let boxed = Box::new(value);
-                let boxed_ptr = boxed.as_ref() as *const T;
-                contexts.insert(TypeId::of::<T>(), boxed);
-                boxed_ptr
-            }
-        };
+    pub fn provide_context<T: 'static + Clone>(&self, value: T) -> T {
+        let value2 = value.clone();
+
+        self.shared_contexts
+            .borrow_mut()
+            .insert(TypeId::of::<T>(), Box::new(value));
 
-        unsafe { &*any }
+        value2
     }
 
     /// Pushes the future onto the poll queue to be polled after the component renders.

+ 9 - 10
packages/core/src/virtual_dom.rs

@@ -240,20 +240,19 @@ impl VirtualDom {
             mutations: Mutations::default(),
         };
 
-        let root = dom.new_scope(Box::new(VProps::new(
-            root,
-            |_, _| unreachable!(),
-            root_props,
-        )));
+        let root = dom.new_scope(
+            Box::new(VProps::new(root, |_, _| unreachable!(), root_props)),
+            "app",
+        );
 
         // The root component is always a suspense boundary for any async children
         // This could be unexpected, so we might rethink this behavior later
         //
         // We *could* just panic if the suspense boundary is not found
-        root.provide_context(SuspenseContext::new(ScopeId(0)));
+        root.provide_context(Rc::new(SuspenseContext::new(ScopeId(0))));
 
         // Unlike react, we provide a default error boundary that just renders the error as a string
-        root.provide_context(ErrorBoundary::new(ScopeId(0)));
+        root.provide_context(Rc::new(ErrorBoundary::new(ScopeId(0))));
 
         // the root element is always given element ID 0 since it's the container for the entire tree
         dom.elements.insert(ElementRef::null());
@@ -297,7 +296,7 @@ impl VirtualDom {
     /// currently suspended.
     pub fn is_scope_suspended(&self, id: ScopeId) -> bool {
         !self.scopes[id.0]
-            .consume_context::<SuspenseContext>()
+            .consume_context::<Rc<SuspenseContext>>()
             .unwrap()
             .waiting_on
             .borrow()
@@ -528,7 +527,7 @@ impl VirtualDom {
             // first, unload any complete suspense trees
             for finished_fiber in self.finished_fibers.drain(..) {
                 let scope = &mut self.scopes[finished_fiber.0];
-                let context = scope.has_context::<SuspenseContext>().unwrap();
+                let context = scope.has_context::<Rc<SuspenseContext>>().unwrap();
 
                 self.mutations
                     .templates
@@ -566,7 +565,7 @@ impl VirtualDom {
                 // No placeholder necessary since this is a diff
                 if !self.collected_leaves.is_empty() {
                     let mut boundary = self.scopes[dirty.id.0]
-                        .consume_context::<SuspenseContext>()
+                        .consume_context::<Rc<SuspenseContext>>()
                         .unwrap();
 
                     let boundary_mut = boundary.borrow_mut();

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

@@ -17,12 +17,12 @@ fn app(cx: Scope) -> Element {
 }
 
 #[test]
-fn it_goes() {
+fn bubbles_error() {
     let mut dom = VirtualDom::new(app);
 
-    let edits = dom.rebuild().santize();
+    let _edits = dom.rebuild().santize();
 
     dom.mark_dirty(ScopeId(0));
 
-    dom.render_immediate();
+    _ = dom.render_immediate();
 }

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

@@ -320,9 +320,9 @@ fn remove_list() {
     assert_eq!(
         dom.render_immediate().santize().edits,
         [
-            Remove { id: ElementId(3) },
+            Remove { id: ElementId(5) },
             Remove { id: ElementId(4) },
-            Remove { id: ElementId(5) }
+            Remove { id: ElementId(3) },
         ]
     );
 }
@@ -345,10 +345,10 @@ fn no_common_keys() {
     assert_eq!(
         dom.render_immediate().santize().edits,
         [
-            Remove { id: ElementId(2) },
             Remove { id: ElementId(3) },
-            LoadTemplate { name: "template", index: 0, id: ElementId(3) },
+            Remove { id: ElementId(2) },
             LoadTemplate { name: "template", index: 0, id: ElementId(2) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(3) },
             LoadTemplate { name: "template", index: 0, id: ElementId(4) },
             ReplaceWith { id: ElementId(1), m: 3 }
         ]

+ 6 - 6
packages/core/tests/diff_unkeyed_list.rs

@@ -357,11 +357,11 @@ fn remove_many() {
     assert_eq!(
         edits.edits,
         [
-            Remove { id: ElementId(1,) },
-            Remove { id: ElementId(5,) },
-            Remove { id: ElementId(7,) },
             Remove { id: ElementId(9,) },
-            CreatePlaceholder { id: ElementId(9,) },
+            Remove { id: ElementId(7,) },
+            Remove { id: ElementId(5,) },
+            Remove { id: ElementId(1,) },
+            CreatePlaceholder { id: ElementId(3,) },
             ReplaceWith { id: ElementId(2,), m: 1 },
         ]
     );
@@ -372,8 +372,8 @@ fn remove_many() {
         edits.edits,
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
-            HydrateText { path: &[0,], value: "hello 0", id: ElementId(10,) },
-            ReplaceWith { id: ElementId(9,), m: 1 },
+            HydrateText { path: &[0,], value: "hello 0", id: ElementId(1,) },
+            ReplaceWith { id: ElementId(3,), m: 1 },
         ]
     )
 }

+ 5 - 5
packages/core/tests/miri_full_app.rs

@@ -8,16 +8,16 @@ fn miri_rollover() {
 
     _ = dom.rebuild();
 
-    for x in 0..3 {
+    for _ in 0..3 {
         dom.handle_event("click", Rc::new(MouseData::default()), ElementId(2), true);
         dom.process_events();
-        dom.render_immediate();
+        _ = dom.render_immediate();
     }
 }
 
 fn app(cx: Scope) -> Element {
     let mut idx = use_state(cx, || 0);
-    let onhover = |h| println!("go!");
+    let onhover = |_| println!("go!");
 
     cx.render(rsx! {
         div {
@@ -31,7 +31,7 @@ fn app(cx: Scope) -> Element {
             button { onclick: move |_| idx -= 1, "-" }
             ul {
                 (0..**idx).map(|i| rsx! {
-                    Child { i: i, onhover: onhover }
+                    child_example { i: i, onhover: onhover }
                 })
             }
         }
@@ -39,7 +39,7 @@ fn app(cx: Scope) -> Element {
 }
 
 #[inline_props]
-fn Child<'a>(cx: Scope<'a>, i: i32, onhover: EventHandler<'a, MouseEvent>) -> Element {
+fn child_example<'a>(cx: Scope<'a>, i: i32, onhover: EventHandler<'a, MouseEvent>) -> Element {
     cx.render(rsx! {
         li {
             onmouseover: move |e| onhover.call(e),

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

@@ -97,8 +97,8 @@ fn memo_works_properly() {
 
     let mut dom = VirtualDom::new(app);
 
-    dom.rebuild();
-    todo!()
+    _ = dom.rebuild();
+    // todo!()
     // dom.hard_diff(ScopeId(0));
     // dom.hard_diff(ScopeId(0));
     // dom.hard_diff(ScopeId(0));

+ 3 - 1
packages/core/tests/safety.rs

@@ -1,5 +1,7 @@
 //! Tests related to safety of the library.
 
+use std::rc::Rc;
+
 use dioxus::prelude::*;
 use dioxus_core::SuspenseContext;
 
@@ -18,5 +20,5 @@ fn root_node_isnt_null() {
 
     // There should be a default suspense context
     // todo: there should also be a default error boundary
-    assert!(scope.has_context::<SuspenseContext>().is_some());
+    assert!(scope.has_context::<Rc<SuspenseContext>>().is_some());
 }

+ 6 - 5
packages/core/tests/suspense.rs

@@ -1,9 +1,9 @@
 use dioxus::core::ElementId;
 use dioxus::core::{Mutation::*, SuspenseContext};
 use dioxus::prelude::*;
-use dioxus_core::SuspenseContext;
 use std::future::IntoFuture;
-use std::{rc::Rc, time::Duration};
+use std::rc::Rc;
+use std::time::Duration;
 
 #[tokio::test]
 async fn it_works() {
@@ -51,10 +51,12 @@ fn app(cx: Scope) -> Element {
 }
 
 fn suspense_boundary(cx: Scope) -> Element {
-    cx.use_hook(|| cx.provide_context(Rc::new(SuspenseContext::new(cx.scope_id()))));
+    cx.use_hook(|| {
+        cx.provide_context(Rc::new(SuspenseContext::new(cx.scope_id())));
+    });
 
     // Ensure the right types are found
-    cx.has_context::<SuspenseContext>().unwrap();
+    cx.has_context::<Rc<SuspenseContext>>().unwrap();
 
     cx.render(rsx!(async_child {}))
 }
@@ -72,7 +74,6 @@ async fn async_text(cx: Scope<'_>) -> Element {
 
     let age = use_future!(cx, || async {
         tokio::time::sleep(std::time::Duration::from_secs(2)).await;
-        println!("long future completed");
         1234
     });
 

+ 3 - 2
packages/desktop/Cargo.toml

@@ -33,7 +33,7 @@ webbrowser = "0.8.0"
 infer = "0.11.0"
 dunce = "1.0.2"
 
-interprocess = { version = "1.1.1" }
+interprocess = { version = "1.1.1", optional = true}
 futures-util = "0.3.25"
 
 [target.'cfg(target_os = "ios")'.dependencies]
@@ -44,11 +44,12 @@ objc_id = "0.1.1"
 core-foundation = "0.9.3"
 
 [features]
-default = ["tokio_runtime"]
+default = ["tokio_runtime", "hot-reload"]
 tokio_runtime = ["tokio"]
 fullscreen = ["wry/fullscreen"]
 transparent = ["wry/transparent"]
 tray = ["wry/tray"]
+hot-reload = ["interprocess"]
 
 [dev-dependencies]
 dioxus-core-macro = { path = "../core-macro" }

+ 15 - 12
packages/desktop/src/controller.rs

@@ -1,7 +1,7 @@
 use crate::desktop_context::{DesktopContext, UserWindowEvent};
 use crate::events::{decode_event, EventMessage};
 use dioxus_core::*;
-use futures_channel::mpsc::UnboundedReceiver;
+use futures_channel::mpsc::{unbounded, UnboundedSender};
 use futures_util::StreamExt;
 #[cfg(target_os = "ios")]
 use objc::runtime::Object;
@@ -22,6 +22,9 @@ pub(super) struct DesktopController {
     pub(super) pending_edits: Arc<Mutex<Vec<String>>>,
     pub(super) quit_app_on_close: bool,
     pub(super) is_ready: Arc<AtomicBool>,
+    pub(super) proxy: EventLoopProxy<UserWindowEvent>,
+    pub(super) event_tx: UnboundedSender<serde_json::Value>,
+
     #[cfg(target_os = "ios")]
     pub(super) views: Vec<*mut Object>,
 }
@@ -33,9 +36,10 @@ impl DesktopController {
         root: Component<P>,
         props: P,
         proxy: EventLoopProxy<UserWindowEvent>,
-        mut event_rx: UnboundedReceiver<serde_json::Value>,
     ) -> Self {
         let edit_queue = Arc::new(Mutex::new(Vec::new()));
+        let (event_tx, mut event_rx) = unbounded();
+        let proxy2 = proxy.clone();
 
         let pending_edits = edit_queue.clone();
         let desktop_context_proxy = proxy.clone();
@@ -54,8 +58,7 @@ impl DesktopController {
                 {
                     let edits = dom.rebuild();
                     let mut queue = edit_queue.lock().unwrap();
-                    queue.push(serde_json::to_string(&edits.templates).unwrap());
-                    queue.push(serde_json::to_string(&edits.edits).unwrap());
+                    queue.push(serde_json::to_string(&edits).unwrap());
                     proxy.send_event(UserWindowEvent::EditsReady).unwrap();
                 }
 
@@ -77,12 +80,8 @@ impl DesktopController {
                         .render_with_deadline(tokio::time::sleep(Duration::from_millis(16)))
                         .await;
 
-                    {
-                        let mut queue = edit_queue.lock().unwrap();
-                        queue.push(serde_json::to_string(&muts.templates).unwrap());
-                        queue.push(serde_json::to_string(&muts.edits).unwrap());
-                        let _ = proxy.send_event(UserWindowEvent::EditsReady);
-                    }
+                    edit_queue.lock().unwrap().push(serde_json::to_string(&muts).unwrap());
+                    let _ = proxy.send_event(UserWindowEvent::EditsReady);
                 }
             })
         });
@@ -92,6 +91,8 @@ impl DesktopController {
             webviews: HashMap::new(),
             is_ready: Arc::new(AtomicBool::new(false)),
             quit_app_on_close: true,
+            proxy: proxy2,
+            event_tx,
             #[cfg(target_os = "ios")]
             views: vec![],
         }
@@ -116,12 +117,14 @@ impl DesktopController {
 
             let (_id, view) = self.webviews.iter_mut().next().unwrap();
 
-            println!("processing pending edits {:?}", new_queue.len());
-
             for edit in new_queue.drain(..) {
                 view.evaluate_script(&format!("window.interpreter.handleEdits({})", edit))
                     .unwrap();
             }
         }
     }
+
+    pub(crate) fn set_template(&self, _serialized_template: String) {
+        todo!("hot reloading currently WIP")
+    }
 }

+ 89 - 82
packages/desktop/src/desktop_context.rs

@@ -168,6 +168,11 @@ pub enum UserWindowEvent {
     DragWindow,
     FocusWindow,
 
+    /// Set a new Dioxus template for hot-reloading
+    ///
+    /// Is a no-op in release builds. Must fit the right format for templates
+    SetTemplate(String),
+
     Visible(bool),
     Minimize(bool),
     Maximize(bool),
@@ -195,99 +200,101 @@ pub enum UserWindowEvent {
     PopView,
 }
 
-pub(super) fn handler(
-    user_event: UserWindowEvent,
-    desktop: &mut DesktopController,
-    control_flow: &mut ControlFlow,
-) {
-    // currently dioxus-desktop supports a single window only,
-    // so we can grab the only webview from the map;
-    // on wayland it is possible that a user event is emitted
-    // before the webview is initialized. ignore the event.
-    let webview = if let Some(webview) = desktop.webviews.values().next() {
-        webview
-    } else {
-        return;
-    };
-    let window = webview.window();
-
-    println!("user_event: {:?}", user_event);
-
-    match user_event {
-        Initialize | EditsReady => desktop.try_load_ready_webviews(),
-        CloseWindow => *control_flow = ControlFlow::Exit,
-        DragWindow => {
-            // if the drag_window has any errors, we don't do anything
-            window.fullscreen().is_none().then(|| window.drag_window());
-        }
-        Visible(state) => window.set_visible(state),
-        Minimize(state) => window.set_minimized(state),
-        Maximize(state) => window.set_maximized(state),
-        MaximizeToggle => window.set_maximized(!window.is_maximized()),
-        Fullscreen(state) => {
-            if let Some(handle) = window.current_monitor() {
-                window.set_fullscreen(state.then_some(WryFullscreen::Borderless(Some(handle))));
+impl DesktopController {
+    pub(super) fn handle_event(
+        &mut self,
+        user_event: UserWindowEvent,
+        control_flow: &mut ControlFlow,
+    ) {
+        // currently dioxus-desktop supports a single window only,
+        // so we can grab the only webview from the map;
+        // on wayland it is possible that a user event is emitted
+        // before the webview is initialized. ignore the event.
+        let webview = if let Some(webview) = self.webviews.values().next() {
+            webview
+        } else {
+            return;
+        };
+
+        let window = webview.window();
+
+        match user_event {
+            Initialize | EditsReady => self.try_load_ready_webviews(),
+            SetTemplate(template) => self.set_template(template),
+            CloseWindow => *control_flow = ControlFlow::Exit,
+            DragWindow => {
+                // if the drag_window has any errors, we don't do anything
+                window.fullscreen().is_none().then(|| window.drag_window());
             }
-        }
-        FocusWindow => window.set_focus(),
-        Resizable(state) => window.set_resizable(state),
-        AlwaysOnTop(state) => window.set_always_on_top(state),
+            Visible(state) => window.set_visible(state),
+            Minimize(state) => window.set_minimized(state),
+            Maximize(state) => window.set_maximized(state),
+            MaximizeToggle => window.set_maximized(!window.is_maximized()),
+            Fullscreen(state) => {
+                if let Some(handle) = window.current_monitor() {
+                    window.set_fullscreen(state.then_some(WryFullscreen::Borderless(Some(handle))));
+                }
+            }
+            FocusWindow => window.set_focus(),
+            Resizable(state) => window.set_resizable(state),
+            AlwaysOnTop(state) => window.set_always_on_top(state),
 
-        CursorVisible(state) => window.set_cursor_visible(state),
-        CursorGrab(state) => {
-            let _ = window.set_cursor_grab(state);
-        }
+            CursorVisible(state) => window.set_cursor_visible(state),
+            CursorGrab(state) => {
+                let _ = window.set_cursor_grab(state);
+            }
 
-        SetTitle(content) => window.set_title(&content),
-        SetDecorations(state) => window.set_decorations(state),
+            SetTitle(content) => window.set_title(&content),
+            SetDecorations(state) => window.set_decorations(state),
 
-        SetZoomLevel(scale_factor) => webview.zoom(scale_factor),
+            SetZoomLevel(scale_factor) => webview.zoom(scale_factor),
 
-        Print => {
-            if let Err(e) = webview.print() {
-                // we can't panic this error.
-                log::warn!("Open print modal failed: {e}");
+            Print => {
+                if let Err(e) = webview.print() {
+                    // we can't panic this error.
+                    log::warn!("Open print modal failed: {e}");
+                }
+            }
+            DevTool => {
+                #[cfg(debug_assertions)]
+                webview.open_devtools();
+                #[cfg(not(debug_assertions))]
+                log::warn!("Devtools are disabled in release builds");
             }
-        }
-        DevTool => {
-            #[cfg(debug_assertions)]
-            webview.open_devtools();
-            #[cfg(not(debug_assertions))]
-            log::warn!("Devtools are disabled in release builds");
-        }
 
-        Eval(code) => {
-            if let Err(e) = webview.evaluate_script(code.as_str()) {
-                // we can't panic this error.
-                log::warn!("Eval script error: {e}");
+            Eval(code) => {
+                if let Err(e) = webview.evaluate_script(code.as_str()) {
+                    // we can't panic this error.
+                    log::warn!("Eval script error: {e}");
+                }
             }
-        }
 
-        #[cfg(target_os = "ios")]
-        PushView(view) => unsafe {
-            use objc::runtime::Object;
-            use objc::*;
-            assert!(is_main_thread());
-            let ui_view = window.ui_view() as *mut Object;
-            let ui_view_frame: *mut Object = msg_send![ui_view, frame];
-            let _: () = msg_send![view, setFrame: ui_view_frame];
-            let _: () = msg_send![view, setAutoresizingMask: 31];
-
-            let ui_view_controller = window.ui_view_controller() as *mut Object;
-            let _: () = msg_send![ui_view_controller, setView: view];
-            desktop.views.push(ui_view);
-        },
-
-        #[cfg(target_os = "ios")]
-        PopView => unsafe {
-            use objc::runtime::Object;
-            use objc::*;
-            assert!(is_main_thread());
-            if let Some(view) = desktop.views.pop() {
+            #[cfg(target_os = "ios")]
+            PushView(view) => unsafe {
+                use objc::runtime::Object;
+                use objc::*;
+                assert!(is_main_thread());
+                let ui_view = window.ui_view() as *mut Object;
+                let ui_view_frame: *mut Object = msg_send![ui_view, frame];
+                let _: () = msg_send![view, setFrame: ui_view_frame];
+                let _: () = msg_send![view, setAutoresizingMask: 31];
+
                 let ui_view_controller = window.ui_view_controller() as *mut Object;
                 let _: () = msg_send![ui_view_controller, setView: view];
-            }
-        },
+                self.views.push(ui_view);
+            },
+
+            #[cfg(target_os = "ios")]
+            PopView => unsafe {
+                use objc::runtime::Object;
+                use objc::*;
+                assert!(is_main_thread());
+                if let Some(view) = self.views.pop() {
+                    let ui_view_controller = window.ui_view_controller() as *mut Object;
+                    let _: () = msg_send![ui_view_controller, setView: view];
+                }
+            },
+        }
     }
 }
 

+ 0 - 2
packages/desktop/src/events.rs

@@ -1,6 +1,5 @@
 //! Convert a serialized event to an event trigger
 
-use dioxus_core::ElementId;
 use dioxus_html::events::*;
 use serde::{Deserialize, Serialize};
 use serde_json::from_value;
@@ -43,7 +42,6 @@ macro_rules! match_data {
     ) => {
         match $name {
             $( $($mname)|* => {
-                println!("casting to type {:?}", std::any::TypeId::of::<$tip>());
                 let val: $tip = from_value::<$tip>($m).ok()?;
                 Rc::new(val) as Rc<dyn Any>
             })*

+ 12 - 6
packages/desktop/src/hot_reload.rs

@@ -1,4 +1,7 @@
+#![allow(dead_code)]
+
 use dioxus_core::VirtualDom;
+
 use interprocess::local_socket::{LocalSocketListener, LocalSocketStream};
 use std::io::{BufRead, BufReader};
 use std::time::Duration;
@@ -10,18 +13,21 @@ fn handle_error(connection: std::io::Result<LocalSocketStream>) -> Option<LocalS
         .ok()
 }
 
-pub(crate) fn init(dom: &VirtualDom) {
+pub(crate) fn init(_dom: &VirtualDom) {
     let latest_in_connection: Arc<Mutex<Option<BufReader<LocalSocketStream>>>> =
         Arc::new(Mutex::new(None));
+
     let latest_in_connection_handle = latest_in_connection.clone();
 
     // connect to processes for incoming data
     std::thread::spawn(move || {
-        // if let Ok(listener) = LocalSocketListener::bind("@dioxusin") {
-        //     for conn in listener.incoming().filter_map(handle_error) {
-        //         *latest_in_connection_handle.lock().unwrap() = Some(BufReader::new(conn));
-        //     }
-        // }
+        let temp_file = std::env::temp_dir().join("@dioxusin");
+
+        if let Ok(listener) = LocalSocketListener::bind(temp_file) {
+            for conn in listener.incoming().filter_map(handle_error) {
+                *latest_in_connection_handle.lock().unwrap() = Some(BufReader::new(conn));
+            }
+        }
     });
 
     std::thread::spawn(move || {

+ 127 - 114
packages/desktop/src/lib.rs

@@ -8,13 +8,17 @@ mod controller;
 mod desktop_context;
 mod escape;
 mod events;
-#[cfg(any(feature = "hot-reload", debug_assertions))]
-mod hot_reload;
 mod protocol;
 
+#[cfg(all(feature = "hot-reload", debug_assertions))]
+mod hot_reload;
+
+use std::sync::atomic::AtomicBool;
+use std::sync::Arc;
+
 use desktop_context::UserWindowEvent;
 pub use desktop_context::{use_eval, use_window, DesktopContext};
-use futures_channel::mpsc::unbounded;
+use futures_channel::mpsc::UnboundedSender;
 pub use wry;
 pub use wry::application as tao;
 
@@ -100,15 +104,68 @@ pub fn launch_cfg(root: Component, config_builder: Config) {
 /// ```
 pub fn launch_with_props<P: 'static + Send>(root: Component<P>, props: P, mut cfg: Config) {
     let event_loop = EventLoop::with_user_event();
+    let mut desktop = DesktopController::new_on_tokio(root, props, event_loop.create_proxy());
 
-    let (event_tx, event_rx) = unbounded();
-    let mut desktop =
-        DesktopController::new_on_tokio(root, props, event_loop.create_proxy(), event_rx);
-    let proxy = event_loop.create_proxy();
+    event_loop.run(move |window_event, event_loop, control_flow| {
+        *control_flow = ControlFlow::Wait;
+
+        match window_event {
+            Event::NewEvents(StartCause::Init) => desktop.start(&mut cfg, event_loop),
+
+            Event::WindowEvent {
+                event, window_id, ..
+            } => match event {
+                WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
+                WindowEvent::Destroyed { .. } => desktop.close_window(window_id, control_flow),
+                _ => {}
+            },
+
+            Event::UserEvent(user_event) => desktop.handle_event(user_event, control_flow),
+            Event::MainEventsCleared => {}
+            Event::Resumed => {}
+            Event::Suspended => {}
+            Event::LoopDestroyed => {}
+            Event::RedrawRequested(_id) => {}
+            _ => {}
+        }
+    })
+}
+
+impl DesktopController {
+    fn start(
+        &mut self,
+        cfg: &mut Config,
+        event_loop: &tao::event_loop::EventLoopWindowTarget<UserWindowEvent>,
+    ) {
+        let webview = build_webview(
+            cfg,
+            event_loop,
+            self.is_ready.clone(),
+            self.proxy.clone(),
+            self.event_tx.clone(),
+        );
+
+        self.webviews.insert(webview.window().id(), webview);
+    }
+}
 
-    // We assume that if the icon is None, then the user just didnt set it
+fn build_webview(
+    cfg: &mut Config,
+    event_loop: &tao::event_loop::EventLoopWindowTarget<UserWindowEvent>,
+    is_ready: Arc<AtomicBool>,
+    proxy: tao::event_loop::EventLoopProxy<UserWindowEvent>,
+    event_tx: UnboundedSender<serde_json::Value>,
+) -> wry::webview::WebView {
+    let builder = cfg.window.clone();
+    let window = builder.build(event_loop).unwrap();
+    let file_handler = cfg.file_drop_handler.take();
+    let custom_head = cfg.custom_head.clone();
+    let resource_dir = cfg.resource_dir.clone();
+    let index_file = cfg.custom_index.clone();
+
+    // We assume that if the icon is None in cfg, then the user just didnt set it
     if cfg.window.window.window_icon.is_none() {
-        cfg.window = cfg.window.with_window_icon(Some(
+        window.set_window_icon(Some(
             tao::window::Icon::from_rgba(
                 include_bytes!("./assets/default_icon.bin").to_vec(),
                 460,
@@ -118,85 +175,62 @@ pub fn launch_with_props<P: 'static + Send>(root: Component<P>, props: P, mut cf
         ));
     }
 
-    event_loop.run(move |window_event, event_loop, control_flow| {
-        *control_flow = ControlFlow::Wait;
-
-        // println!("window event: {:?}", window_event);
-        match window_event {
-            Event::NewEvents(StartCause::Init) => {
-                let builder = cfg.window.clone();
-
-                let window = builder.build(event_loop).unwrap();
-                let window_id = window.id();
-
-                let (is_ready, _) = (desktop.is_ready.clone(), ());
-                // let (is_ready, sender) = (desktop.is_ready.clone(), desktop.sender.clone());
-
-                let proxy = proxy.clone();
-
-                let file_handler = cfg.file_drop_handler.take();
-                let custom_head = cfg.custom_head.clone();
-                let resource_dir = cfg.resource_dir.clone();
-                let index_file = cfg.custom_index.clone();
-                let event_tx = event_tx.clone();
-
-                let mut webview = WebViewBuilder::new(window)
-                    .unwrap()
-                    .with_transparent(cfg.window.window.transparent)
-                    .with_url("dioxus://index.html/")
-                    .unwrap()
-                    .with_ipc_handler(move |_window: &Window, payload: String| {
-                        parse_ipc_message(&payload)
-                            .map(|message| match message.method() {
-                                "user_event" => {
-                                    _ = event_tx.unbounded_send(message.params());
+    let mut webview = WebViewBuilder::new(window)
+        .unwrap()
+        .with_transparent(cfg.window.window.transparent)
+        .with_url("dioxus://index.html/")
+        .unwrap()
+        .with_ipc_handler(move |_window: &Window, payload: String| {
+            parse_ipc_message(&payload)
+                .map(|message| match message.method() {
+                    "user_event" => {
+                        _ = event_tx.unbounded_send(message.params());
+                    }
+                    "initialize" => {
+                        is_ready.store(true, std::sync::atomic::Ordering::Relaxed);
+                        let _ = proxy.send_event(UserWindowEvent::EditsReady);
+                    }
+                    "browser_open" => {
+                        let data = message.params();
+                        log::trace!("Open browser: {:?}", data);
+                        if let Some(temp) = data.as_object() {
+                            if temp.contains_key("href") {
+                                let url = temp.get("href").unwrap().as_str().unwrap();
+                                if let Err(e) = webbrowser::open(url) {
+                                    log::error!("Open Browser error: {:?}", e);
                                 }
-                                "initialize" => {
-                                    is_ready.store(true, std::sync::atomic::Ordering::Relaxed);
-                                    println!("initializing...");
-                                    let _ = proxy.send_event(UserWindowEvent::EditsReady);
-                                }
-                                "browser_open" => {
-                                    let data = message.params();
-                                    log::trace!("Open browser: {:?}", data);
-                                    if let Some(temp) = data.as_object() {
-                                        if temp.contains_key("href") {
-                                            let url = temp.get("href").unwrap().as_str().unwrap();
-                                            if let Err(e) = webbrowser::open(url) {
-                                                log::error!("Open Browser error: {:?}", e);
-                                            }
-                                        }
-                                    }
-                                }
-                                _ => (),
-                            })
-                            .unwrap_or_else(|| {
-                                log::warn!("invalid IPC message received");
-                            });
-                    })
-                    .with_custom_protocol(String::from("dioxus"), move |r| {
-                        protocol::desktop_handler(
-                            r,
-                            resource_dir.clone(),
-                            custom_head.clone(),
-                            index_file.clone(),
-                        )
-                    })
-                    .with_file_drop_handler(move |window, evet| {
-                        file_handler
-                            .as_ref()
-                            .map(|handler| handler(window, evet))
-                            .unwrap_or_default()
-                    });
-
-                for (name, handler) in cfg.protocols.drain(..) {
-                    webview = webview.with_custom_protocol(name, handler)
-                }
-
-                if cfg.disable_context_menu {
-                    // in release mode, we don't want to show the dev tool or reload menus
-                    webview = webview.with_initialization_script(
-                        r#"
+                            }
+                        }
+                    }
+                    _ => (),
+                })
+                .unwrap_or_else(|| {
+                    log::warn!("invalid IPC message received");
+                });
+        })
+        .with_custom_protocol(String::from("dioxus"), move |r| {
+            protocol::desktop_handler(
+                r,
+                resource_dir.clone(),
+                custom_head.clone(),
+                index_file.clone(),
+            )
+        })
+        .with_file_drop_handler(move |window, evet| {
+            file_handler
+                .as_ref()
+                .map(|handler| handler(window, evet))
+                .unwrap_or_default()
+        });
+
+    for (name, handler) in cfg.protocols.drain(..) {
+        webview = webview.with_custom_protocol(name, handler)
+    }
+
+    if cfg.disable_context_menu {
+        // in release mode, we don't want to show the dev tool or reload menus
+        webview = webview.with_initialization_script(
+            r#"
                         if (document.addEventListener) {
                         document.addEventListener('contextmenu', function(e) {
                             e.preventDefault();
@@ -207,32 +241,11 @@ pub fn launch_with_props<P: 'static + Send>(root: Component<P>, props: P, mut cf
                         });
                         }
                     "#,
-                    )
-                } else {
-                    // in debug, we are okay with the reload menu showing and dev tool
-                    webview = webview.with_devtools(true);
-                }
-
-                desktop.webviews.insert(window_id, webview.build().unwrap());
-            }
-
-            Event::WindowEvent {
-                event, window_id, ..
-            } => match event {
-                WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
-                WindowEvent::Destroyed { .. } => desktop.close_window(window_id, control_flow),
-                _ => {}
-            },
+        )
+    } else {
+        // in debug, we are okay with the reload menu showing and dev tool
+        webview = webview.with_devtools(true);
+    }
 
-            Event::UserEvent(user_event) => {
-                desktop_context::handler(user_event, &mut desktop, control_flow)
-            }
-            Event::MainEventsCleared => {}
-            Event::Resumed => {}
-            Event::Suspended => {}
-            Event::LoopDestroyed => {}
-            Event::RedrawRequested(_id) => {}
-            _ => {}
-        }
-    })
+    webview.build().unwrap()
 }

+ 2 - 1
packages/fermi/src/hooks/atom_root.rs

@@ -1,6 +1,7 @@
+use std::rc::Rc;
+
 use crate::AtomRoot;
 use dioxus_core::ScopeState;
-use std::rc::Rc;
 
 // Returns the atom root, initiaizing it at the root of the app if it does not exist.
 pub fn use_atom_root(cx: &ScopeState) -> &Rc<AtomRoot> {

+ 10 - 6
packages/hooks/src/usecallback.rs

@@ -1,6 +1,4 @@
-use std::rc::Rc;
-
-use dioxus_core::{prelude::EventHandler, ScopeState};
+use dioxus_core::ScopeState;
 use std::future::Future;
 
 #[macro_export]
@@ -19,8 +17,14 @@ macro_rules! use_callback {
             move || $($rest)*
         )
     };
+    ($cx:ident, $($rest:tt)*) => {
+        use_callback(
+            $cx,
+            move || $($rest)*
+        )
+    };
 }
-pub fn use_callback<'a, T, R, F>(cx: &'a ScopeState, make: impl FnOnce() -> R) -> impl FnMut(T) + 'a
+pub fn use_callback<T, R, F>(cx: &ScopeState, make: impl FnOnce() -> R) -> impl FnMut(T) + '_
 where
     R: FnMut(T) -> F + 'static,
     F: Future<Output = ()> + 'static,
@@ -30,8 +34,8 @@ where
     move |evt| cx.spawn(hook(evt))
 }
 
-fn it_works(cx: &ScopeState) {
-    let p = use_callback(cx, || {
+fn _it_works(cx: &ScopeState) {
+    let _p = use_callback(cx, || {
         |()| async {
             //
         }

+ 9 - 9
packages/hooks/src/usecontext.rs

@@ -1,17 +1,17 @@
 use dioxus_core::ScopeState;
 
-/// Consume some context in the tree
-pub fn use_context<T: 'static>(cx: &ScopeState) -> Option<&T> {
-    match *cx.use_hook(|| cx.consume_context::<T>().map(|t| t as *const T)) {
-        Some(res) => Some(unsafe { &*res }),
-        None => None,
-    }
+/// Consume some context in the tree, providing a sharable handle to the value
+pub fn use_context<T: 'static + Clone>(cx: &ScopeState) -> Option<&T> {
+    cx.use_hook(|| cx.consume_context::<T>()).as_ref()
 }
 
 /// Provide some context via the tree and return a reference to it
 ///
 /// Once the context has been provided, it is immutable. Mutations should be done via interior mutability.
-pub fn use_context_provider<T: 'static>(cx: &ScopeState, f: impl FnOnce() -> T) -> &T {
-    let ptr = *cx.use_hook(|| cx.provide_context(f()) as *const T);
-    unsafe { &*ptr }
+pub fn use_context_provider<T: 'static + Clone>(cx: &ScopeState, f: impl FnOnce() -> T) -> &T {
+    cx.use_hook(|| {
+        let val = f();
+        cx.provide_context(val.clone());
+        val
+    })
 }

+ 2 - 4
packages/hooks/src/usefuture.rs

@@ -66,9 +66,7 @@ where
             // if there's a waker, we dont re-render the component. Instead we just progress that future
             match waker.borrow().as_ref() {
                 Some(waker) => waker.wake_by_ref(),
-                None => {
-                    // schedule_update()
-                }
+                None => schedule_update(),
             }
         })));
     }
@@ -122,7 +120,7 @@ impl<T> UseFuture<T> {
     }
 
     // Manually set the value in the future slot without starting the future over
-    pub fn set(&self, new_value: T) {
+    pub fn set(&self, _new_value: T) {
         // self.slot.set(Some(new_value));
         // self.needs_regen.set(true);
         // (self.update)();

+ 3 - 3
packages/html/src/render_template.rs

@@ -33,9 +33,9 @@ fn render_template_node(node: &TemplateNode, out: &mut String) -> std::fmt::Resu
             }
             write!(out, "</{tag}>")?;
         }
-        TemplateNode::Text(t) => write!(out, "{t}")?,
-        TemplateNode::Dynamic(_) => write!(out, "<pre hidden />")?,
-        TemplateNode::DynamicText(t) => write!(out, "<!-- --> {t} <!-- -->")?,
+        TemplateNode::Text { text: t } => write!(out, "{t}")?,
+        TemplateNode::Dynamic { id: _ } => write!(out, "<pre hidden />")?,
+        TemplateNode::DynamicText { id: t } => write!(out, "<!-- --> {t} <!-- -->")?,
     };
     Ok(())
 }

+ 5 - 2
packages/interpreter/src/bindings.rs

@@ -12,11 +12,14 @@ extern "C" {
     pub fn new(arg: Element) -> Interpreter;
 
     #[wasm_bindgen(method)]
-    pub fn SaveTemplate(this: &Interpreter, nodes: Vec<Node>, name: &str);
+    pub fn SaveTemplate(this: &Interpreter, template: JsValue);
 
     #[wasm_bindgen(method)]
     pub fn MountToRoot(this: &Interpreter);
 
+    #[wasm_bindgen(method)]
+    pub fn AppendChildren(this: &Interpreter, m: u32);
+
     #[wasm_bindgen(method)]
     pub fn AssignId(this: &Interpreter, path: &[u8], id: u32);
 
@@ -58,8 +61,8 @@ extern "C" {
         this: &Interpreter,
         name: &str,
         id: u32,
-        handler: &Function,
         bubbles: bool,
+        handler: &Function,
     );
 
     #[wasm_bindgen(method)]

+ 61 - 17
packages/interpreter/src/interpreter.js

@@ -76,9 +76,6 @@ export class Interpreter {
   pop() {
     return this.stack.pop();
   }
-  SaveTemplate(nodes, name) {
-    this.templates[name] = nodes;
-  }
   MountToRoot() {
     this.AppendChildren(this.stack.length - 1);
   }
@@ -140,7 +137,7 @@ export class Interpreter {
     this.stack.push(el);
     this.nodes[root] = el;
   }
-  NewEventListener(event_name, root, handler, bubbles) {
+  NewEventListener(event_name, root, bubbles, handler) {
     const element = this.nodes[root];
     element.setAttribute("data-dioxus-id", `${root}`);
     this.listeners.create(event_name, element, handler, bubbles);
@@ -213,10 +210,56 @@ export class Interpreter {
     }
   }
   handleEdits(edits) {
-    for (let edit of edits) {
+    for (let template of edits.templates) {
+      this.SaveTemplate(template);
+    }
+
+    for (let edit of edits.edits) {
       this.handleEdit(edit);
     }
   }
+
+  SaveTemplate(template) {
+    let roots = [];
+    for (let root of template.roots) {
+      roots.push(this.MakeTemplateNode(root));
+    }
+    this.templates[template.name] = roots;
+  }
+
+  MakeTemplateNode(node) {
+    console.log("making template node", node);
+    switch (node.type) {
+      case "Text":
+        return document.createTextNode(node.text);
+      case "Dynamic":
+        let dyn = document.createElement("pre");
+        dyn.hidden = true;
+        return dyn;
+      case "DynamicText":
+        return document.createTextNode("placeholder");
+      case "Element":
+        let el;
+
+        if (node.namespace != null) {
+          el = document.createElementNS(node.namespace, node.tag);
+        } else {
+          el = document.createElement(node.tag);
+        }
+
+        for (let attr of node.attrs) {
+          if (attr.type == "Static") {
+            this.SetAttributeInner(el, attr.name, attr.value, attr.namespace);
+          }
+        }
+
+        for (let child of node.children) {
+          el.appendChild(this.MakeTemplateNode(child));
+        }
+
+        return el;
+    }
+  }
   AssignId(path, id) {
     this.nodes[id] = this.LoadChild(path);
   }
@@ -232,7 +275,16 @@ export class Interpreter {
   }
   HydrateText(path, value, id) {
     let node = this.LoadChild(path);
-    node.textContent = value;
+
+    if (node.nodeType == Node.TEXT_NODE) {
+      node.textContent = value;
+    } else {
+      // replace with a textnode
+      let text = document.createTextNode(value);
+      node.replaceWith(text);
+      node = text;
+    }
+
     this.nodes[id] = node;
   }
   ReplacePlaceholder(path, m) {
@@ -296,17 +348,12 @@ export class Interpreter {
         this.RemoveAttribute(edit.id, edit.name, edit.ns);
         break;
       case "RemoveEventListener":
-        this.RemoveEventListener(edit.id, edit.event_name);
+        this.RemoveEventListener(edit.id, edit.name);
         break;
       case "NewEventListener":
-        // console.log("creating listener! ", edit);
-
         // this handler is only provided on desktop implementations since this
         // method is not used by the web implementation
         let handler = (event) => {
-
-          console.log("event", event);
-
           let target = event.target;
           if (target != null) {
             let realId = target.getAttribute(`data-dioxus-id`);
@@ -387,17 +434,14 @@ export class Interpreter {
             }
             window.ipc.postMessage(
               serializeIpcMessage("user_event", {
-                event: edit.event_name,
+                event: edit.name,
                 mounted_dom_id: parseInt(realId),
                 contents: contents,
               })
             );
           }
         };
-
-        console.log("adding event listener", edit);
-        this.NewEventListener(edit.event_name, edit.id, handler, event_bubbles(edit.event_name));
-
+        this.NewEventListener(edit.name, edit.id, event_bubbles(edit.name), handler);
         break;
     }
   }

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

@@ -132,7 +132,7 @@ impl<S: State> RealDom<S> {
                                 },
                                 OwnedAttributeValue::Text(value.to_string()),
                             )),
-                            dioxus_core::TemplateAttribute::Dynamic(_) => None,
+                            dioxus_core::TemplateAttribute::Dynamic { .. } => None,
                         })
                         .collect(),
                     listeners: FxHashSet::default(),
@@ -144,17 +144,17 @@ impl<S: State> RealDom<S> {
                 }
                 node_id
             }
-            TemplateNode::Text(txt) => {
+            TemplateNode::Text { text } => {
                 let node_id = self.create_node(Node::new(NodeType::Text {
-                    text: txt.to_string(),
+                    text: text.to_string(),
                 }));
                 node_id
             }
-            TemplateNode::Dynamic(_) => {
+            TemplateNode::Dynamic { .. } => {
                 let node_id = self.create_node(Node::new(NodeType::Placeholder));
                 node_id
             }
-            TemplateNode::DynamicText(_) => {
+            TemplateNode::DynamicText { .. } => {
                 let node_id = self.create_node(Node::new(NodeType::Text {
                     text: String::new(),
                 }));
@@ -220,7 +220,12 @@ impl<S: State> RealDom<S> {
                     let node = self.tree.get_mut(node_id).unwrap();
                     if let NodeType::Text { text } = &mut node.node_data.node_type {
                         *text = value.to_string();
+                    } else {
+                        node.node_data.node_type = NodeType::Text {
+                            text: value.to_string(),
+                        };
                     }
+
                     mark_dirty(node_id, NodeMask::new().with_text(), &mut nodes_updated);
                 }
                 LoadTemplate { name, index, id } => {

+ 3 - 5
packages/router/src/components/link.rs

@@ -1,6 +1,4 @@
-use std::sync::Arc;
-
-use crate::{use_route, RouterCore};
+use crate::{use_route, RouterContext};
 use dioxus::prelude::*;
 
 /// Props for the [`Link`](struct.Link.html) component.
@@ -77,7 +75,7 @@ pub struct LinkProps<'a> {
 /// }
 /// ```
 pub fn Link<'a>(cx: Scope<'a, LinkProps<'a>>) -> Element {
-    let svc = cx.use_hook(|| cx.consume_context::<Arc<RouterCore>>());
+    let svc = use_context::<RouterContext>(cx);
 
     let LinkProps {
         to,
@@ -107,7 +105,7 @@ pub fn Link<'a>(cx: Scope<'a, LinkProps<'a>>) -> Element {
         }
     };
 
-    let route = use_route(&cx);
+    let route = use_route(cx);
     let url = route.url();
     let path = url.path();
     let active = path == cx.props.to;

+ 1 - 1
packages/router/src/components/redirect.rs

@@ -32,7 +32,7 @@ pub struct RedirectProps<'a> {
 ///
 /// It will replace the current route rather than pushing the current one to the stack.
 pub fn Redirect<'a>(cx: Scope<'a, RedirectProps<'a>>) -> Element {
-    let router = use_router(&cx);
+    let router = use_router(cx);
 
     let immediate_redirect = cx.use_hook(|| {
         if let Some(from) = cx.props.from {

+ 5 - 9
packages/router/src/components/route.rs

@@ -1,7 +1,5 @@
+use crate::{RouteContext, RouterContext};
 use dioxus::prelude::*;
-use std::sync::Arc;
-
-use crate::{RouteContext, RouterCore};
 
 /// Props for the [`Route`](struct.Route.html) component.
 #[derive(Props)]
@@ -27,15 +25,13 @@ pub struct RouteProps<'a> {
 /// )
 /// ```
 pub fn Route<'a>(cx: Scope<'a, RouteProps<'a>>) -> Element {
-    let router_root = cx
-        .use_hook(|| cx.consume_context::<Arc<RouterCore>>())
-        .as_ref()
-        .unwrap();
+    let router_root = use_context::<RouterContext>(cx).unwrap();
+    let root_context = use_context::<RouteContext>(cx);
 
     cx.use_hook(|| {
         // create a bigger, better, longer route if one above us exists
-        let total_route = match cx.consume_context::<RouteContext>() {
-            Some(ctx) => ctx.total_route,
+        let total_route = match root_context {
+            Some(ctx) => ctx.total_route.clone(),
             None => cx.props.to.to_string(),
         };
 

+ 6 - 7
packages/router/src/components/router.rs

@@ -1,6 +1,5 @@
-use crate::{cfg::RouterCfg, RouterCore};
+use crate::{cfg::RouterCfg, RouterContext, RouterService};
 use dioxus::prelude::*;
-use std::sync::Arc;
 
 /// The props for the [`Router`](fn.Router.html) component.
 #[derive(Props)]
@@ -21,7 +20,7 @@ pub struct RouterProps<'a> {
     ///
     /// This lets you easily implement redirects
     #[props(default)]
-    pub onchange: EventHandler<'a, Arc<RouterCore>>,
+    pub onchange: EventHandler<'a, RouterContext>,
 
     /// Set the active class of all Link components contained in this router.
     ///
@@ -40,15 +39,15 @@ pub struct RouterProps<'a> {
 /// Will fallback to HashRouter is BrowserRouter is not available, or through configuration.
 #[allow(non_snake_case)]
 pub fn Router<'a>(cx: Scope<'a, RouterProps<'a>>) -> Element {
-    let svc = cx.use_hook(|| {
-        cx.provide_context(RouterCore::new(
-            &cx,
+    let svc = use_context_provider(cx, || {
+        RouterService::new(
+            cx,
             RouterCfg {
                 base_url: cx.props.base_url.map(|s| s.to_string()),
                 active_class: cx.props.active_class.map(|s| s.to_string()),
                 initial_url: cx.props.initial_url.clone(),
             },
-        ))
+        )
     });
 
     // next time we run the rout_found will be filled

+ 6 - 6
packages/router/src/hooks/use_route.rs

@@ -1,4 +1,4 @@
-use crate::{ParsedRoute, RouteContext, RouterCore, RouterService};
+use crate::{ParsedRoute, RouteContext, RouterContext};
 use dioxus::core::{ScopeId, ScopeState};
 use std::{borrow::Cow, str::FromStr, sync::Arc};
 use url::Url;
@@ -7,11 +7,11 @@ use url::Url;
 /// context of a [`Router`]. If this function is called outside of a `Router`
 /// component it will panic.
 pub fn use_route(cx: &ScopeState) -> &UseRoute {
-    let handle = cx.use_hook(|| {
-        let router = cx
-            .consume_context::<RouterService>()
-            .expect("Cannot call use_route outside the scope of a Router component");
+    let router = cx
+        .consume_context::<RouterContext>()
+        .expect("Cannot call use_route outside the scope of a Router component");
 
+    let handle = cx.use_hook(|| {
         let route_context = cx.consume_context::<RouteContext>();
 
         router.subscribe_onchange(cx.scope_id());
@@ -115,7 +115,7 @@ impl UseRoute {
 // and reveal our cached version of UseRoute to the component.
 struct UseRouteListener {
     state: UseRoute,
-    router: Arc<RouterCore>,
+    router: RouterContext,
     scope: ScopeId,
 }
 

+ 5 - 7
packages/router/src/hooks/use_router.rs

@@ -1,10 +1,8 @@
-use crate::RouterService;
-use dioxus::core::ScopeState;
+use crate::RouterContext;
+use dioxus::{core::ScopeState, prelude::use_context};
 
 /// This hook provides access to the `RouterService` for the app.
-pub fn use_router(cx: &ScopeState) -> &RouterService {
-    cx.use_hook(|| {
-        cx.consume_context::<RouterService>()
-            .expect("Cannot call use_route outside the scope of a Router component")
-    })
+pub fn use_router(cx: &ScopeState) -> &RouterContext {
+    use_context::<RouterContext>(cx)
+        .expect("Cannot call use_route outside the scope of a Router component")
 }

+ 15 - 18
packages/router/src/service.rs

@@ -4,7 +4,7 @@
 use crate::cfg::RouterCfg;
 use dioxus::core::{ScopeId, ScopeState, VirtualDom};
 use std::any::Any;
-use std::sync::Weak;
+use std::rc::Weak;
 use std::{
     cell::{Cell, RefCell},
     collections::{HashMap, HashSet},
@@ -14,6 +14,9 @@ use std::{
 };
 use url::Url;
 
+/// A clonable handle to the router
+pub type RouterContext = Rc<RouterService>;
+
 /// An abstraction over the platform's history API.
 ///
 /// The history is denoted using web-like semantics, with forward slashes delmitiing
@@ -41,7 +44,7 @@ use url::Url;
 /// - On the web, this is a [`BrowserHistory`](https://docs.rs/gloo/0.3.0/gloo/history/struct.BrowserHistory.html).
 /// - On desktop, mobile, and SSR, this is just a Vec of Strings. Currently on
 ///   desktop, there is no way to tap into forward/back for the app unless explicitly set.
-pub struct RouterCore {
+pub struct RouterService {
     pub(crate) route_found: Cell<Option<ScopeId>>,
 
     pub(crate) stack: RefCell<Vec<Arc<ParsedRoute>>>,
@@ -61,9 +64,6 @@ pub struct RouterCore {
     pub(crate) cfg: RouterCfg,
 }
 
-/// A shared type for the RouterCore.
-pub type RouterService = Arc<RouterCore>;
-
 /// A route is a combination of window title, saved state, and a URL.
 #[derive(Debug, Clone)]
 pub struct ParsedRoute {
@@ -77,8 +77,8 @@ pub struct ParsedRoute {
     pub serialized_state: Option<String>,
 }
 
-impl RouterCore {
-    pub(crate) fn new(cx: &ScopeState, cfg: RouterCfg) -> Arc<Self> {
+impl RouterService {
+    pub(crate) fn new(cx: &ScopeState, cfg: RouterCfg) -> RouterContext {
         #[cfg(feature = "web")]
         let history = Box::new(web::new());
 
@@ -99,7 +99,7 @@ impl RouterCore {
             None => Arc::new(history.init_location()),
         };
 
-        let svc = Arc::new(Self {
+        let svc = Rc::new(Self {
             cfg,
             regen_any_route: cx.schedule_update_any(),
             router_id: cx.scope_id(),
@@ -111,7 +111,7 @@ impl RouterCore {
             history,
         });
 
-        svc.history.attach_listeners(Arc::downgrade(&svc));
+        svc.history.attach_listeners(Rc::downgrade(&svc));
 
         svc
     }
@@ -247,12 +247,9 @@ impl RouterCore {
 /// that owns the router.
 ///
 /// This might change in the future.
-pub fn get_router_from_vdom(
-    dom: &VirtualDom,
-    target_scope: Option<ScopeId>,
-) -> Option<Arc<RouterCore>> {
-    dom.get_scope(target_scope.unwrap_or(ScopeId(0)))
-        .and_then(|scope| scope.consume_context::<Arc<RouterCore>>())
+pub fn get_router_from_vdom(dom: &VirtualDom, target_scope: ScopeId) -> Option<RouterContext> {
+    dom.get_scope(target_scope)
+        .and_then(|scope| scope.consume_context::<RouterContext>())
 }
 
 fn clean_route(route: String) -> String {
@@ -319,7 +316,7 @@ pub(crate) trait RouterProvider {
     fn replace(&self, route: &ParsedRoute);
     fn native_location(&self) -> Box<dyn Any>;
     fn init_location(&self) -> ParsedRoute;
-    fn attach_listeners(&self, svc: Weak<RouterCore>);
+    fn attach_listeners(&self, svc: Weak<RouterService>);
 }
 
 #[cfg(not(feature = "web"))]
@@ -350,7 +347,7 @@ mod hash {
 
         fn replace(&self, _route: &ParsedRoute) {}
 
-        fn attach_listeners(&self, _svc: Weak<RouterCore>) {}
+        fn attach_listeners(&self, _svc: Weak<RouterService>) {}
     }
 }
 
@@ -418,7 +415,7 @@ mod web {
             }
         }
 
-        fn attach_listeners(&self, svc: std::sync::Weak<crate::RouterCore>) {
+        fn attach_listeners(&self, svc: std::sync::Weak<crate::RouterService>) {
             self._listener.set(Some(EventListener::new(
                 &web_sys::window().unwrap(),
                 "popstate",

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

@@ -60,7 +60,7 @@ impl Component {
         match self
             .fields
             .iter()
-            .find(|f| f.name.to_string() == "key")
+            .find(|f| f.name == "key")
             .map(|f| &f.content)
         {
             Some(ContentField::Formatted(fmt)) => Some(fmt),

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

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

+ 11 - 9
packages/rsx/src/lib.rs

@@ -180,10 +180,10 @@ impl<'a> DynamicContext<'a> {
                 // [0, 2]
                 // [0, 2, 1]
 
-                let static_attrs = el.attributes.iter().filter_map(|attr| match &attr.attr {
+                let static_attrs = el.attributes.iter().map(|attr| match &attr.attr {
                     ElementAttr::AttrText { name, value } if value.is_static() => {
                         let value = value.source.as_ref().unwrap();
-                        Some(quote! {
+                        quote! {
                             ::dioxus::core::TemplateAttribute::Static {
                                 name: dioxus_elements::#el_name::#name.0,
                                 namespace: dioxus_elements::#el_name::#name.1,
@@ -192,12 +192,12 @@ impl<'a> DynamicContext<'a> {
                                 // todo: we don't diff these so we never apply the volatile flag
                                 // volatile: dioxus_elements::#el_name::#name.2,
                             }
-                        })
+                        }
                     }
 
                     ElementAttr::CustomAttrText { name, value } if value.is_static() => {
                         let value = value.source.as_ref().unwrap();
-                        Some(quote! {
+                        quote! {
                             ::dioxus::core::TemplateAttribute::Static {
                                 name: dioxus_elements::#el_name::#name.0,
                                 namespace: dioxus_elements::#el_name::#name.1,
@@ -206,7 +206,7 @@ impl<'a> DynamicContext<'a> {
                                 // todo: we don't diff these so we never apply the volatile flag
                                 // volatile: dioxus_elements::#el_name::#name.2,
                             }
-                        })
+                        }
                     }
 
                     ElementAttr::AttrExpression { .. }
@@ -217,7 +217,7 @@ impl<'a> DynamicContext<'a> {
                         let ct = self.dynamic_attributes.len();
                         self.dynamic_attributes.push(attr);
                         self.attr_paths.push(self.current_path.clone());
-                        Some(quote! { ::dioxus::core::TemplateAttribute::Dynamic(#ct) })
+                        quote! { ::dioxus::core::TemplateAttribute::Dynamic { id: #ct } }
                     }
                 });
 
@@ -245,7 +245,7 @@ impl<'a> DynamicContext<'a> {
 
             BodyNode::Text(text) if text.is_static() => {
                 let text = text.source.as_ref().unwrap();
-                quote! { ::dioxus::core::TemplateNode::Text(#text) }
+                quote! { ::dioxus::core::TemplateNode::Text{ text: #text } }
             }
 
             BodyNode::RawExpr(_)
@@ -258,8 +258,10 @@ impl<'a> DynamicContext<'a> {
                 self.node_paths.push(self.current_path.clone());
 
                 match root {
-                    BodyNode::Text(_) => quote! { ::dioxus::core::TemplateNode::DynamicText(#ct) },
-                    _ => quote! { ::dioxus::core::TemplateNode::Dynamic(#ct) },
+                    BodyNode::Text(_) => {
+                        quote! { ::dioxus::core::TemplateNode::DynamicText { id: #ct } }
+                    }
+                    _ => quote! { ::dioxus::core::TemplateNode::Dynamic { id: #ct } },
                 }
             }
         }

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

@@ -141,7 +141,7 @@ impl ToTokens for BodyNode {
                     pat, expr, body, ..
                 } = exp;
 
-                let renderer = TemplateRenderer { roots: &body };
+                let renderer = TemplateRenderer { roots: body };
 
                 tokens.append_all(quote! {
                      __cx.make_node(

+ 21 - 9
packages/ssr/src/cache.rs

@@ -65,12 +65,12 @@ impl StringCache {
                         TemplateAttribute::Static { name, value, .. } => {
                             write!(chain, " {}=\"{}\"", name, value)?;
                         }
-                        TemplateAttribute::Dynamic(index) => {
+                        TemplateAttribute::Dynamic { id: index } => {
                             chain.segments.push(Segment::Attr(*index))
                         }
                     }
                 }
-                if children.len() == 0 && tag_is_self_closing(tag) {
+                if children.is_empty() && tag_is_self_closing(tag) {
                     write!(chain, "/>")?;
                 } else {
                     write!(chain, ">")?;
@@ -81,8 +81,8 @@ impl StringCache {
                 }
                 cur_path.pop();
             }
-            TemplateNode::Text(text) => write!(chain, "{}", text)?,
-            TemplateNode::Dynamic(idx) | TemplateNode::DynamicText(idx) => {
+            TemplateNode::Text { text } => write!(chain, "{}", text)?,
+            TemplateNode::Dynamic { id: idx } | TemplateNode::DynamicText { id: idx } => {
                 chain.segments.push(Segment::Node(*idx))
             }
         }
@@ -92,9 +92,21 @@ impl StringCache {
 }
 
 fn tag_is_self_closing(tag: &str) -> bool {
-    match tag {
-        "area" | "base" | "br" | "col" | "embed" | "hr" | "img" | "input" | "link" | "meta"
-        | "param" | "source" | "track" | "wbr" => true,
-        _ => false,
-    }
+    matches!(
+        tag,
+        "area"
+            | "base"
+            | "br"
+            | "col"
+            | "embed"
+            | "hr"
+            | "img"
+            | "input"
+            | "link"
+            | "meta"
+            | "param"
+            | "source"
+            | "track"
+            | "wbr"
+    )
 }

+ 0 - 37
packages/ssr/src/config.rs

@@ -1,38 +1 @@
-#[derive(Clone, Debug, Default)]
-pub struct SsrConfig {
-    /// currently not supported - control if we indent the HTML output
-    indent: bool,
 
-    /// Control if elements are written onto a new line
-    newline: bool,
-
-    /// Choose to write ElementIDs into elements so the page can be re-hydrated later on
-    pre_render: bool,
-
-    // Currently not implemented
-    // Don't proceed onto new components. Instead, put the name of the component.
-    // TODO: components don't have names :(
-    skip_components: bool,
-}
-
-impl SsrConfig {
-    pub fn indent(mut self, a: bool) -> Self {
-        self.indent = a;
-        self
-    }
-
-    pub fn newline(mut self, a: bool) -> Self {
-        self.newline = a;
-        self
-    }
-
-    pub fn pre_render(mut self, a: bool) -> Self {
-        self.pre_render = a;
-        self
-    }
-
-    pub fn skip_components(mut self, a: bool) -> Self {
-        self.skip_components = a;
-        self
-    }
-}

+ 0 - 50
packages/ssr/src/helpers.rs

@@ -1,50 +0,0 @@
-use std::fmt::Write;
-
-use dioxus_core::{LazyNodes, ScopeId, VirtualDom};
-
-use crate::config::SsrConfig;
-
-pub fn pre_render(dom: &VirtualDom) -> String {
-    todo!()
-}
-
-pub fn pre_render_to(dom: &VirtualDom, write: impl Write) {
-    todo!()
-}
-
-pub fn render_vdom(dom: &VirtualDom) -> String {
-    todo!()
-    // format!("{:}", TextRenderer::from_vdom(dom, SsrConfig::default()))
-}
-
-pub fn pre_render_vdom(dom: &VirtualDom) -> String {
-    todo!()
-    // format!(
-    //     "{:}",
-    //     TextRenderer::from_vdom(dom, SsrConfig::default().pre_render(true))
-    // )
-}
-
-pub fn render_vdom_cfg(dom: &VirtualDom, cfg: SsrConfig) -> String {
-    todo!()
-    // format!(
-    //     "{:}",
-    //     TextRenderer::from_vdom(dom, cfg(SsrConfig::default()))
-    // )
-}
-
-pub fn render_vdom_scope(vdom: &VirtualDom, scope: ScopeId) -> Option<String> {
-    todo!()
-    // Some(format!(
-    //     "{:}",
-    //     TextRenderer {
-    //         cfg: SsrConfig::default(),
-    //         root: vdom.get_scope(scope).unwrap().root_node(),
-    //         vdom: Some(vdom),
-    //     }
-    // ))
-}
-
-pub fn render_lazy<'a, 'b>(f: LazyNodes<'a, 'b>) -> String {
-    todo!()
-}

+ 49 - 3
packages/ssr/src/lib.rs

@@ -2,8 +2,54 @@
 
 mod cache;
 pub mod config;
-pub mod helpers;
 pub mod renderer;
 pub mod template;
-pub use helpers::*;
-pub use template::SsrRender;
+use dioxus_core::{Element, LazyNodes, Scope, VirtualDom};
+use std::cell::Cell;
+
+pub use crate::renderer::Renderer;
+
+/// A convenience function to render an `rsx!` call to a string
+///
+/// For advanced rendering, create a new `SsrRender`.
+pub fn render_lazy(f: LazyNodes<'_, '_>) -> String {
+    // We need to somehow get the lazy call into the virtualdom even with the lifetime
+    // Since the lazy lifetime is valid for this function, we can just transmute it to static temporarily
+    // This is okay since we're returning an owned value
+    struct RootProps<'a, 'b> {
+        caller: Cell<Option<LazyNodes<'a, 'b>>>,
+    }
+
+    fn lazy_app<'a>(cx: Scope<'a, RootProps<'static, 'static>>) -> Element<'a> {
+        let lazy = cx.props.caller.take().unwrap();
+        let lazy: LazyNodes = unsafe { std::mem::transmute(lazy) };
+        Ok(lazy.call(cx))
+    }
+
+    let props: RootProps = unsafe {
+        std::mem::transmute(RootProps {
+            caller: Cell::new(Some(f)),
+        })
+    };
+
+    let mut dom = VirtualDom::new_with_props(lazy_app, props);
+    _ = dom.rebuild();
+
+    Renderer::new().render(&dom)
+}
+
+/// A convenience function to render an existing VirtualDom to a string
+///
+/// We generally recommend creating a new `Renderer` to take advantage of template caching.
+pub fn render(dom: &VirtualDom) -> String {
+    Renderer::new().render(dom)
+}
+
+/// A convenience function to pre-render an existing VirtualDom to a string
+///
+/// We generally recommend creating a new `Renderer` to take advantage of template caching.
+pub fn pre_render(dom: &VirtualDom) -> String {
+    let mut renderer = Renderer::new();
+    renderer.pre_render = true;
+    renderer.render(dom)
+}

+ 181 - 146
packages/ssr/src/renderer.rs

@@ -1,146 +1,181 @@
-// use dioxus_core::VirtualDom;
-
-// use crate::config::SsrConfig;
-
-// pub struct SsrRenderer {
-//     vdom: VirtualDom,
-//     cfg: SsrConfig,
-// }
-
-// impl Default for SsrRenderer {
-//     fn default() -> Self {
-//         Self::new(SsrConfig::default())
-//     }
-// }
-
-// impl SsrRenderer {
-//     pub fn new(cfg: SsrConfig) -> Self {
-//         Self {
-//             vdom: VirtualDom::new(app),
-//             cfg,
-//         }
-//     }
-
-//     pub fn render_lazy<'a>(&'a mut self, f: LazyNodes<'a, '_>) -> String {
-//         let scope = self.vdom.base_scope();
-//         let root = f.call(scope);
-//         format!(
-//             "{:}",
-//             TextRenderer {
-//                 cfg: self.cfg.clone(),
-//                 root: &root,
-//                 vdom: Some(&self.vdom),
-//             }
-//         )
-//     }
-
-//     fn html_render(
-//         &self,
-//         node: &VNode,
-//         f: &mut impl Write,
-//         il: u16,
-//         last_node_was_text: &mut bool,
-//     ) -> std::fmt::Result {
-//         // match &node {
-//         //     VNode::Text(text) => {
-//         //         if *last_node_was_text {
-//         //             write!(f, "<!--spacer-->")?;
-//         //         }
-
-//         //         if self.cfg.indent {
-//         //             for _ in 0..il {
-//         //                 write!(f, "    ")?;
-//         //             }
-//         //         }
-
-//         //         *last_node_was_text = true;
-
-//         //         write!(f, "{}", text.text)?
-//         //     }
-//         //     VNode::Element(el) => {
-//         //         *last_node_was_text = false;
-
-//         //         if self.cfg.indent {
-//         //             for _ in 0..il {
-//         //                 write!(f, "    ")?;
-//         //             }
-//         //         }
-
-//         //         write!(f, "<{}", el.tag)?;
-
-//         //         let inner_html = render_attributes(el.attributes.iter(), f)?;
-
-//         //         match self.cfg.newline {
-//         //             true => writeln!(f, ">")?,
-//         //             false => write!(f, ">")?,
-//         //         }
-
-//         //         if let Some(inner_html) = inner_html {
-//         //             write!(f, "{}", inner_html)?;
-//         //         } else {
-//         //             let mut last_node_was_text = false;
-//         //             for child in el.children {
-//         //                 self.html_render(child, f, il + 1, &mut last_node_was_text)?;
-//         //             }
-//         //         }
-
-//         //         if self.cfg.newline {
-//         //             writeln!(f)?;
-//         //         }
-//         //         if self.cfg.indent {
-//         //             for _ in 0..il {
-//         //                 write!(f, "    ")?;
-//         //             }
-//         //         }
-
-//         //         write!(f, "</{}>", el.tag)?;
-//         //         if self.cfg.newline {
-//         //             writeln!(f)?;
-//         //         }
-//         //     }
-//         //     VNode::Fragment(frag) => match frag.children.len() {
-//         //         0 => {
-//         //             *last_node_was_text = false;
-//         //             if self.cfg.indent {
-//         //                 for _ in 0..il {
-//         //                     write!(f, "    ")?;
-//         //                 }
-//         //             }
-//         //             write!(f, "<!--placeholder-->")?;
-//         //         }
-//         //         _ => {
-//         //             for child in frag.children {
-//         //                 self.html_render(child, f, il + 1, last_node_was_text)?;
-//         //             }
-//         //         }
-//         //     },
-//         //     VNode::Component(vcomp) => {
-//         //         let idx = vcomp.scope.get().unwrap();
-
-//         //         if let (Some(vdom), false) = (self.vdom, self.cfg.skip_components) {
-//         //             let new_node = vdom.get_scope(idx).unwrap().root_node();
-//         //             self.html_render(new_node, f, il + 1, last_node_was_text)?;
-//         //         } else {
-//         //         }
-//         //     }
-//         //     VNode::Template(t) => {
-//         //         if let Some(vdom) = self.vdom {
-//         //             todo!()
-//         //         } else {
-//         //             panic!("Cannot render template without vdom");
-//         //         }
-//         //     }
-//         //     VNode::Placeholder(_) => {
-//         //         todo!()
-//         //     }
-//         // }
-//         Ok(())
-//     }
-// }
-
-// impl<'a: 'c, 'c> Display for SsrRenderer<'a, '_, 'c> {
-//     fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
-//         let mut last_node_was_text = false;
-//         self.html_render(self.root, f, 0, &mut last_node_was_text)
-//     }
-// }
+use super::cache::Segment;
+use crate::cache::StringCache;
+use dioxus_core::{prelude::*, AttributeValue, DynamicNode, RenderReturn};
+use std::collections::HashMap;
+use std::fmt::Write;
+use std::rc::Rc;
+
+/// A virtualdom renderer that caches the templates it has seen for faster rendering
+#[derive(Default)]
+pub struct Renderer {
+    /// should we do our best to prettify the output?
+    pub pretty: bool,
+
+    /// Control if elements are written onto a new line
+    pub newline: bool,
+
+    /// Should we sanitize text nodes? (escape HTML)
+    pub sanitize: bool,
+
+    /// Choose to write ElementIDs into elements so the page can be re-hydrated later on
+    pub pre_render: bool,
+
+    // Currently not implemented
+    // Don't proceed onto new components. Instead, put the name of the component.
+    pub skip_components: bool,
+
+    /// A cache of templates that have been rendered
+    template_cache: HashMap<&'static str, Rc<StringCache>>,
+}
+
+impl Renderer {
+    pub fn new() -> Self {
+        Self::default()
+    }
+
+    pub fn render(&mut self, dom: &VirtualDom) -> String {
+        let mut buf = String::new();
+        self.render_to(&mut buf, dom).unwrap();
+        buf
+    }
+
+    pub fn render_to(&mut self, buf: &mut impl Write, dom: &VirtualDom) -> std::fmt::Result {
+        self.render_scope(buf, dom, ScopeId(0))
+    }
+
+    pub fn render_scope(
+        &mut self,
+        buf: &mut impl Write,
+        dom: &VirtualDom,
+        scope: ScopeId,
+    ) -> std::fmt::Result {
+        // We should never ever run into async or errored nodes in SSR
+        // Error boundaries and suspense boundaries will convert these to sync
+        if let RenderReturn::Sync(Ok(node)) = dom.get_scope(scope).unwrap().root_node() {
+            self.render_template(buf, dom, node)?
+        };
+
+        Ok(())
+    }
+
+    fn render_template(
+        &mut self,
+        buf: &mut impl Write,
+        dom: &VirtualDom,
+        template: &VNode,
+    ) -> std::fmt::Result {
+        let entry = self
+            .template_cache
+            .entry(template.template.name)
+            .or_insert_with(|| Rc::new(StringCache::from_template(template).unwrap()))
+            .clone();
+
+        for segment in entry.segments.iter() {
+            match segment {
+                Segment::Attr(idx) => {
+                    let attr = &template.dynamic_attrs[*idx];
+                    match attr.value {
+                        AttributeValue::Text(value) => write!(buf, " {}=\"{}\"", attr.name, value)?,
+                        AttributeValue::Bool(value) => write!(buf, " {}={}", attr.name, value)?,
+                        _ => {}
+                    };
+                }
+                Segment::Node(idx) => match &template.dynamic_nodes[*idx] {
+                    DynamicNode::Component(node) => {
+                        if self.skip_components {
+                            write!(buf, "<{}><{}/>", node.name, node.name)?;
+                        } else {
+                            let id = node.scope.get().unwrap();
+                            let scope = dom.get_scope(id).unwrap();
+                            let node = scope.root_node();
+                            match node {
+                                RenderReturn::Sync(Ok(node)) => {
+                                    self.render_template(buf, dom, node)?
+                                }
+                                _ => todo!(
+                                    "generally, scopes should be sync, only if being traversed"
+                                ),
+                            }
+                        }
+                    }
+                    DynamicNode::Text(text) => {
+                        // in SSR, we are concerned that we can't hunt down the right text node since they might get merged
+                        if self.pre_render {
+                            write!(buf, "<!--#-->")?;
+                        }
+
+                        // todo: escape the text
+                        write!(buf, "{}", text.value)?;
+
+                        if self.pre_render {
+                            write!(buf, "<!--#-->")?;
+                        }
+                    }
+                    DynamicNode::Fragment(nodes) => {
+                        for child in *nodes {
+                            self.render_template(buf, dom, child)?;
+                        }
+                    }
+
+                    DynamicNode::Placeholder(_el) => {
+                        if self.pre_render {
+                            write!(buf, "<pre><pre/>")?;
+                        }
+                    }
+                },
+
+                Segment::PreRendered(contents) => write!(buf, "{}", contents)?,
+            }
+        }
+
+        Ok(())
+    }
+}
+
+#[test]
+fn to_string_works() {
+    use dioxus::prelude::*;
+
+    fn app(cx: Scope) -> Element {
+        let dynamic = 123;
+        let dyn2 = "</diiiiiiiiv>"; // todo: escape this
+
+        render! {
+            div { class: "asdasdasd", class: "asdasdasd", id: "id-{dynamic}",
+                "Hello world 1 -->" "{dynamic}" "<-- Hello world 2"
+                div { "nest 1" }
+                div {}
+                div { "nest 2" }
+                "{dyn2}"
+                (0..5).map(|i| rsx! { div { "finalize {i}" } })
+            }
+        }
+    }
+
+    let mut dom = VirtualDom::new(app);
+    _ = dom.rebuild();
+
+    let mut renderer = Renderer::new();
+
+    let out = renderer.render(&dom);
+
+    use Segment::*;
+    assert_eq!(
+        renderer.template_cache.iter().next().unwrap().1.segments,
+        vec![
+            PreRendered("<div class=\"asdasdasd\" class=\"asdasdasd\"".into(),),
+            Attr(0,),
+            PreRendered(">Hello world 1 -->".into(),),
+            Node(0,),
+            PreRendered("<-- Hello world 2<div>nest 1</div><div></div><div>nest 2</div>".into(),),
+            Node(1,),
+            Node(2,),
+            PreRendered("</div>".into(),),
+        ]
+    );
+
+    assert_eq!(
+        out,
+        "<div class=\"asdasdasd\" class=\"asdasdasd\" id=\"id-123\">Hello world 1 --><!--#-->123<!--/#--><-- Hello world 2<div>nest 1</div><div></div><div>nest 2</div><!--#--></diiiiiiiiv><!--/#--><div><!--#-->finalize 0<!--/#--></div><div><!--#-->finalize 1<!--/#--></div><div><!--#-->finalize 2<!--/#--></div><div><!--#-->finalize 3<!--/#--></div><div><!--#-->finalize 4<!--/#--></div></div>"
+    );
+}

+ 0 - 139
packages/ssr/src/template.rs

@@ -1,140 +1 @@
-use super::cache::Segment;
-use dioxus_core::{prelude::*, AttributeValue, DynamicNode, RenderReturn, VText};
-use std::collections::HashMap;
-use std::fmt::Write;
-use std::rc::Rc;
 
-use crate::cache::StringCache;
-
-/// A virtualdom renderer that caches the templates it has seen for faster rendering
-#[derive(Default)]
-pub struct SsrRender {
-    template_cache: HashMap<&'static str, Rc<StringCache>>,
-}
-
-impl SsrRender {
-    pub fn render_vdom(&mut self, dom: &VirtualDom) -> String {
-        let scope = dom.base_scope();
-        let root = scope.root_node();
-
-        let mut out = String::new();
-
-        match root {
-            RenderReturn::Sync(Ok(node)) => self.render_template(&mut out, dom, node).unwrap(),
-            _ => {}
-        };
-
-        out
-    }
-
-    fn render_template(
-        &mut self,
-        buf: &mut String,
-        dom: &VirtualDom,
-        template: &VNode,
-    ) -> std::fmt::Result {
-        let entry = self
-            .template_cache
-            .entry(template.template.name)
-            .or_insert_with(|| Rc::new(StringCache::from_template(template).unwrap()))
-            .clone();
-
-        for segment in entry.segments.iter() {
-            match segment {
-                Segment::Attr(idx) => {
-                    let attr = &template.dynamic_attrs[*idx];
-                    match attr.value {
-                        AttributeValue::Text(value) => write!(buf, " {}=\"{}\"", attr.name, value)?,
-                        AttributeValue::Bool(value) => write!(buf, " {}={}", attr.name, value)?,
-                        _ => {}
-                    };
-                }
-                Segment::Node(idx) => match &template.dynamic_nodes[*idx] {
-                    DynamicNode::Component(node) => {
-                        let id = node.scope.get().unwrap();
-                        let scope = dom.get_scope(id).unwrap();
-                        let node = scope.root_node();
-                        match node {
-                            RenderReturn::Sync(Ok(node)) => self.render_template(buf, dom, node)?,
-                            _ => todo!(),
-                        }
-                    }
-                    DynamicNode::Text(text) => {
-                        // in SSR, we are concerned that we can't hunt down the right text node since they might get merged
-                        // if !*inner {
-                        // write!(buf, "<!--#-->")?;
-                        // }
-
-                        // todo: escape the text
-                        write!(buf, "{}", text.value)?;
-
-                        // if !*inner {
-                        // write!(buf, "<!--/#-->")?;
-                        // }
-                    }
-                    DynamicNode::Fragment(nodes) => {
-                        for child in *nodes {
-                            self.render_template(buf, dom, child)?;
-                        }
-                    }
-
-                    DynamicNode::Placeholder(_el) => {
-                        // todo write a placeholder if in pre-render mode
-                        // write!(buf, "<!--placeholder-->")?;
-                    }
-                },
-
-                Segment::PreRendered(contents) => buf.push_str(contents),
-            }
-        }
-
-        Ok(())
-    }
-}
-
-#[test]
-fn to_string_works() {
-    use dioxus::prelude::*;
-
-    fn app(cx: Scope) -> Element {
-        let dynamic = 123;
-        let dyn2 = "</diiiiiiiiv>"; // todo: escape this
-
-        render! {
-            div { class: "asdasdasd", class: "asdasdasd", id: "id-{dynamic}",
-                "Hello world 1 -->" "{dynamic}" "<-- Hello world 2"
-                div { "nest 1" }
-                div {}
-                div { "nest 2" }
-                "{dyn2}"
-                (0..5).map(|i| rsx! { div { "finalize {i}" } })
-            }
-        }
-    }
-
-    let mut dom = VirtualDom::new(app);
-    dom.rebuild();
-
-    use Segment::*;
-
-    // assert_eq!(
-    //     StringCache::from_template(&dom.base_scope().root_node())
-    //         .unwrap()
-    //         .segments,
-    //     vec![
-    //         PreRendered("<div class=\"asdasdasd\" class=\"asdasdasd\"".into(),),
-    //         Attr(0,),
-    //         PreRendered(">Hello world 1 -->".into(),),
-    //         Node(0,),
-    //         PreRendered("<-- Hello world 2<div>nest 1</div><div></div><div>nest 2</div>".into(),),
-    //         Node(1,),
-    //         Node(2,),
-    //         PreRendered("</div>".into(),),
-    //     ]
-    // );
-
-    // assert_eq!(
-    //     SsrRender::default().render_vdom(&dom),
-    //     "<div class=\"asdasdasd\" class=\"asdasdasd\" id=\"id-123\">Hello world 1 --><!--#-->123<!--/#--><-- Hello world 2<div>nest 1</div><div></div><div>nest 2</div><!--#--></diiiiiiiiv><!--/#--><div><!--#-->finalize 0<!--/#--></div><div><!--#-->finalize 1<!--/#--></div><div><!--#-->finalize 2<!--/#--></div><div><!--#-->finalize 3<!--/#--></div><div><!--#-->finalize 4<!--/#--></div></div>"
-    // );
-}

+ 0 - 144
packages/ssr/tests.old/renders.rs

@@ -1,144 +0,0 @@
-use dioxus::prelude::*;
-use dioxus_ssr::{render_lazy, render_vdom, render_vdom_cfg, SsrConfig, SsrRenderer, TextRenderer};
-
-static SIMPLE_APP: Component = |cx| {
-    cx.render(rsx!(div {
-        "hello world!"
-    }))
-};
-
-static SLIGHTLY_MORE_COMPLEX: Component = |cx| {
-    cx.render(rsx! {
-        div { title: "About W3Schools",
-            (0..20).map(|f| rsx!{
-                div {
-                    title: "About W3Schools",
-                    style: "color:blue;text-align:center",
-                    class: "About W3Schools",
-                    p {
-                        title: "About W3Schools",
-                        "Hello world!: {f}"
-                    }
-                }
-            })
-        }
-    })
-};
-
-static NESTED_APP: Component = |cx| {
-    cx.render(rsx!(
-        div {
-            SIMPLE_APP {}
-        }
-    ))
-};
-static FRAGMENT_APP: Component = |cx| {
-    cx.render(rsx!(
-        div { "f1" }
-        div { "f2" }
-        div { "f3" }
-        div { "f4" }
-    ))
-};
-
-#[test]
-fn to_string_works() {
-    let mut dom = VirtualDom::new(SIMPLE_APP);
-    dom.rebuild();
-    dbg!(render_vdom(&dom));
-}
-
-#[test]
-fn hydration() {
-    let mut dom = VirtualDom::new(NESTED_APP);
-    dom.rebuild();
-    dbg!(render_vdom_cfg(&dom, |c| c.pre_render(true)));
-}
-
-#[test]
-fn nested() {
-    let mut dom = VirtualDom::new(NESTED_APP);
-    dom.rebuild();
-    dbg!(render_vdom(&dom));
-}
-
-#[test]
-fn fragment_app() {
-    let mut dom = VirtualDom::new(FRAGMENT_APP);
-    dom.rebuild();
-    dbg!(render_vdom(&dom));
-}
-
-#[test]
-fn write_to_file() {
-    use std::fs::File;
-    use std::io::Write;
-
-    let mut file = File::create("index.html").unwrap();
-
-    let mut dom = VirtualDom::new(SLIGHTLY_MORE_COMPLEX);
-    dom.rebuild();
-
-    file.write_fmt(format_args!(
-        "{}",
-        TextRenderer::from_vdom(&dom, SsrConfig::default())
-    ))
-    .unwrap();
-}
-
-#[test]
-fn styles() {
-    static STLYE_APP: Component = |cx| {
-        cx.render(rsx! {
-            div { color: "blue", font_size: "46px"  }
-        })
-    };
-
-    let mut dom = VirtualDom::new(STLYE_APP);
-    dom.rebuild();
-    dbg!(render_vdom(&dom));
-}
-
-#[test]
-fn lazy() {
-    let p1 = SsrRenderer::new(|c| c).render_lazy(rsx! {
-        div { "ello"  }
-    });
-
-    let p2 = render_lazy(rsx! {
-        div {
-            "ello"
-        }
-    });
-    assert_eq!(p1, p2);
-}
-
-#[test]
-fn big_lazy() {
-    let s = render_lazy(rsx! {
-        div {
-            div {
-                div {
-                    h1 { "ello world" }
-                    h1 { "ello world" }
-                    h1 { "ello world" }
-                    h1 { "ello world" }
-                    h1 { "ello world" }
-                }
-            }
-        }
-    });
-
-    dbg!(s);
-}
-
-#[test]
-fn inner_html() {
-    let s = render_lazy(rsx! {
-        div {
-            dangerous_inner_html: "<div> ack </div>"
-        }
-    });
-
-    dbg!(s);
-}

+ 30 - 38
packages/ssr/tests/simple.rs

@@ -7,66 +7,43 @@ fn simple() {
     }
 
     let mut dom = VirtualDom::new(app);
-    dom.rebuild();
+    _ = dom.rebuild();
+
+    assert_eq!(dioxus_ssr::render(&dom), "<div>hello!</div>");
 
     assert_eq!(
-        dioxus_ssr::SsrRender::default().render_vdom(&dom),
+        dioxus_ssr::render_lazy(rsx!( div {"hello!"} )),
         "<div>hello!</div>"
     );
 }
 
 #[test]
 fn lists() {
-    fn app(cx: Scope) -> Element {
-        render! {
+    assert_eq!(
+        dioxus_ssr::render_lazy(rsx! {
             ul {
                 (0..5).map(|i| rsx! {
                     li { "item {i}" }
                 })
             }
-        }
-    }
-
-    let mut dom = VirtualDom::new(app);
-    dom.rebuild();
-
-    assert_eq!(
-        dioxus_ssr::SsrRender::default().render_vdom(&dom),
+        }),
         "<ul><li>item 0</li><li>item 1</li><li>item 2</li><li>item 3</li><li>item 4</li></ul>"
     );
 }
 
 #[test]
 fn dynamic() {
-    fn app(cx: Scope) -> Element {
-        let dynamic = 123;
-
-        render! {
-            div { "Hello world 1 -->" "{dynamic}" "<-- Hello world 2" }
-        }
-    }
-
-    let mut dom = VirtualDom::new(app);
-    dom.rebuild();
-
+    let dynamic = 123;
     assert_eq!(
-        dioxus_ssr::SsrRender::default().render_vdom(&dom),
+        dioxus_ssr::render_lazy(rsx! {
+            div { "Hello world 1 -->" "{dynamic}" "<-- Hello world 2" }
+        }),
         "<div>Hello world 1 -->123<-- Hello world 2</div>"
     );
 }
 
 #[test]
 fn components() {
-    fn app(cx: Scope) -> Element {
-        render! {
-            div {
-                (0..5).map(|name| rsx! {
-                    my_component { name: name }
-                })
-            }
-        }
-    }
-
     #[inline_props]
     fn my_component(cx: Scope, name: i32) -> Element {
         render! {
@@ -74,11 +51,26 @@ fn components() {
         }
     }
 
-    let mut dom = VirtualDom::new(app);
-    dom.rebuild();
-
     assert_eq!(
-        dioxus_ssr::SsrRender::default().render_vdom(&dom),
+        dioxus_ssr::render_lazy(rsx! {
+            div {
+                (0..5).map(|name| rsx! {
+                    my_component { name: name }
+                })
+            }
+        }),
         "<div><div>component 0</div><div>component 1</div><div>component 2</div><div>component 3</div><div>component 4</div></div>"
     );
 }
+
+#[test]
+fn fragments() {
+    assert_eq!(
+        dioxus_ssr::render_lazy(rsx! {
+            div {
+                (0..5).map(|_| rsx! (()))
+            }
+        }),
+        "<div></div>"
+    );
+}

+ 1 - 0
packages/web/Cargo.toml

@@ -30,6 +30,7 @@ futures-util = "0.3.19"
 smallstr = "0.2.0"
 futures-channel = "0.3.21"
 serde_json = { version = "1.0" }
+serde-wasm-bindgen = "0.4.5"
 
 [dependencies.web-sys]
 version = "0.3.56"

+ 6 - 6
packages/web/src/dom.rs

@@ -11,7 +11,7 @@ use dioxus_core::{Mutation, Template, TemplateAttribute, TemplateNode};
 use dioxus_html::{event_bubbles, CompositionData, FormData};
 use dioxus_interpreter_js::{save_template, Channel};
 use futures_channel::mpsc;
-use rustc_hash::{FxHashMap, FxHashSet};
+use rustc_hash::FxHashMap;
 use std::{any::Any, rc::Rc};
 use wasm_bindgen::{closure::Closure, JsCast};
 use web_sys::{Document, Element, Event, HtmlElement};
@@ -106,15 +106,15 @@ impl WebsysDom {
                     }
                 }
                 for child in *children {
-                    el.append_child(&self.create_template_node(child));
+                    let _ = el.append_child(&self.create_template_node(child));
                 }
                 el.dyn_into().unwrap()
             }
-            Text(t) => self.document.create_text_node(t).dyn_into().unwrap(),
-            DynamicText(_) => self.document.create_text_node("p").dyn_into().unwrap(),
-            Dynamic(_) => {
+            Text { text } => self.document.create_text_node(text).dyn_into().unwrap(),
+            DynamicText { .. } => self.document.create_text_node("p").dyn_into().unwrap(),
+            Dynamic { .. } => {
                 let el = self.document.create_element("pre").unwrap();
-                el.toggle_attribute("hidden");
+                let _ = el.toggle_attribute("hidden");
                 el.dyn_into().unwrap()
             }
         }

+ 1 - 0
packages/web/src/lib.rs

@@ -211,6 +211,7 @@ pub async fn run_with_props<T: 'static>(root: fn(Scope<T>) -> Element, root_prop
         let mut res = {
             let work = dom.wait_for_work().fuse();
             pin_mut!(work);
+
             futures_util::select! {
                 _ = work => None,
                 new_template = hotreload_rx.next() => {