瀏覽代碼

wip: making progress on diffing and hydration

Jonathan Kelley 3 年之前
父節點
當前提交
49856ccd68

+ 18 - 5
Cargo.toml

@@ -19,7 +19,7 @@ dioxus-mobile = { path = "./packages/mobile", optional = true }
 
 [features]
 # core
-default = ["core", "ssr", "desktop"]
+default = ["core", "ssr"]
 core = ["macro", "hooks", "html"]
 macro = ["dioxus-core-macro"]
 hooks = ["dioxus-hooks"]
@@ -37,18 +37,31 @@ mobile = ["dioxus-mobile"]
 
 
 [dev-dependencies]
-futures = "0.3.15"
+futures-util = "0.3.16"
 log = "0.4.14"
 num-format = "0.4.0"
 separator = "0.4.1"
 serde = { version = "1.0.126", features = ["derive"] }
-surf = "2.2.0"
+im-rc = "15.0.0"
+fxhash = "0.2.1"
+anyhow = "1.0.42"
+
+[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
+argh = "0.1.5"
 env_logger = "*"
 async-std = { version = "1.9.0", features = ["attributes"] }
-im-rc = "15.0.0"
 rand = { version = "0.8.4", features = ["small_rng"] }
-fxhash = "0.2.1"
+surf = {version = "2.2.0", git = "https://github.com/jkelleyrtp/surf/", branch = "jk/fix-the-wasm"}
+
+[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
 gloo-timers = "0.2.1"
+surf = {version = "2.2.0", default-features = false, features = ["wasm-client"], git = "https://github.com/jkelleyrtp/surf/", branch = "jk/fix-the-wasm"}
+
+
+[dependencies.getrandom]
+version = "0.2"
+features = ["js"]
+
 
 [workspace]
 members = [

+ 1 - 1
README.md

@@ -166,7 +166,7 @@ Dioxus is heavily inspired by React, but we want your transition to feel like an
 | Custom elements           | ✅      | ✅     | Define new element primitives                               |
 | Suspense                  | ✅      | ✅     | schedule future render from future/promise                  |
 | Integrated error handling | ✅      | ✅     | Gracefully handle errors with ? syntax                      |
-| Re-hydration              | 🛠      | ✅     | Pre-render to HTML to speed up first contentful paint       |
+| Re-hydration              |       | ✅     | Pre-render to HTML to speed up first contentful paint       |
 | Cooperative Scheduling    | 🛠      | ✅     | Prioritize important events over non-important events       |
 | Runs natively             | ✅      | ❓     | runs as a portable binary w/o a runtime (Node)              |
 | 1st class global state    | ✅      | ❓     | redux/recoil/mobx on top of context                         |

+ 4 - 2
examples/file_explorer.rs

@@ -12,8 +12,10 @@ use std::fs::{self, DirEntry};
 fn main() {
     env_logger::init();
     dioxus::desktop::launch(App, |c| {
-        c.with_resizable(false)
-            .with_inner_size(LogicalSize::new(800.0, 400.0))
+        c.with_window(|w| {
+            w.with_resizable(false)
+                .with_inner_size(LogicalSize::new(800.0, 400.0))
+        })
     })
     .unwrap();
 }

+ 33 - 0
examples/hydration.rs

@@ -0,0 +1,33 @@
+//! Example: realworld usage of hydration
+//! ------------------------------------
+//!
+//! This example shows how to pre-render a page using dioxus SSR and then how to rehydrate it on the client side.
+//!
+//! To accomplish hydration on the web, you'll want to set up a slightly more sophisticated build & bundle strategy. In
+//! the official docs, we have a guide for using DioxusStudio as a build tool with pre-rendering and hydration.
+//!
+//! In this example, we pre-render the page to HTML and then pass it into the desktop configuration. This serves as a
+//! proof-of-concept for the hydration feature, but you'll probably only want to use hydration for the web.
+
+use dioxus::prelude::*;
+use dioxus::ssr;
+
+fn main() {
+    let mut vdom = VirtualDom::launch_in_place(App);
+    let content = ssr::render_vdom(&vdom, |f| f.pre_render(true));
+
+    dioxus::desktop::launch(App, |c| c.with_prerendered(content)).unwrap();
+}
+
+static App: FC<()> = |cx| {
+    let mut val = use_state(cx, || 0);
+    cx.render(rsx! {
+        div {
+            h1 {"hello world. Count: {val}"}
+            button {
+                "click to increment"
+                onclick: move |_| val += 1
+            }
+        }
+    })
+};

+ 6 - 4
examples/model.rs

@@ -15,17 +15,19 @@
 //! the RefCell will panic and crash. You can use `try_get_mut` or `.modify` to avoid this problem, or just not hold two
 //! RefMuts at the same time.
 
+use dioxus::desktop::wry::application::dpi::LogicalSize;
 use dioxus::events::on::*;
 use dioxus::prelude::*;
-use dioxus_desktop::wry::application::dpi::LogicalSize;
 
 const STYLE: &str = include_str!("./assets/calculator.css");
 fn main() {
     env_logger::init();
     dioxus::desktop::launch(App, |cfg| {
-        cfg.with_title("Calculator Demo")
-            .with_resizable(false)
-            .with_inner_size(LogicalSize::new(320.0, 530.0))
+        cfg.with_window(|w| {
+            w.with_title("Calculator Demo")
+                .with_resizable(false)
+                .with_inner_size(LogicalSize::new(320.0, 530.0))
+        })
     })
     .expect("failed to launch dioxus app");
 }

+ 1 - 1
examples/reference/task.rs

@@ -30,8 +30,8 @@ pub static Example: FC<()> = |cx| {
 
     // Tasks are 'static, so we need to copy relevant items in
     let (async_count, dir) = (count.for_async(), *direction);
+    
     let (task, result) = use_task(cx, move || async move {
-        // Count infinitely!
         loop {
             gloo_timers::future::TimeoutFuture::new(250).await;
             *async_count.get_mut() += dir;

+ 1 - 1
examples/reference/tostring.rs

@@ -7,7 +7,7 @@ pub static Example: FC<()> = |cx| {
         // This is an easy/low hanging fruit to improve upon
         let mut dom = VirtualDom::new(SomeApp);
         dom.rebuild_in_place().unwrap();
-        ssr::render_vdom(&dom)
+        ssr::render_vdom(&dom, |c| c)
     });
 
     cx.render(rsx! {

+ 5 - 3
examples/ssr.rs

@@ -1,13 +1,15 @@
+#![allow(non_upper_case_globals)]
+
 use dioxus::prelude::*;
 use dioxus::ssr;
 
 fn main() {
     let mut vdom = VirtualDom::new(App);
-    vdom.rebuild_in_place();
-    println!("{}", ssr::render_vdom(&vdom));
+    vdom.rebuild_in_place().expect("Rebuilding failed");
+    println!("{}", ssr::render_vdom(&vdom, |c| c));
 }
 
-const App: FC<()> = |cx| {
+static App: FC<()> = |cx| {
     cx.render(rsx!(
         div {
             h1 { "Title" }

+ 6 - 4
examples/tailwind.rs

@@ -3,10 +3,12 @@ use dioxus::prelude::*;
 fn main() {
     use dioxus::desktop::wry::application::platform::macos::*;
     dioxus::desktop::launch(App, |c| {
-        c.with_fullsize_content_view(true)
-            .with_titlebar_buttons_hidden(false)
-            .with_titlebar_transparent(true)
-            .with_movable_by_window_background(true)
+        c.with_window(|w| {
+            w.with_fullsize_content_view(true)
+                .with_titlebar_buttons_hidden(false)
+                .with_titlebar_transparent(true)
+                .with_movable_by_window_background(true)
+        })
     });
 }
 

+ 3 - 7
examples/todomvc.rs

@@ -1,14 +1,10 @@
+#![allow(non_upper_case_globals, non_snake_case)]
 use dioxus::prelude::*;
 use im_rc::HashMap;
 use std::rc::Rc;
 
-fn main() {
-    #[cfg(feature = "desktop")]
-    // #[cfg(not(target_arch = "wasm32"))]
-    dioxus::desktop::launch(App, |c| c);
-
-    #[cfg(feature = "desktop")]
-    dioxus::web::launch(App, |c| c);
+fn main() -> anyhow::Result<()> {
+    dioxus::desktop::launch(App, |c| c)
 }
 
 #[derive(PartialEq)]

+ 3 - 2
examples/webview.rs

@@ -8,12 +8,13 @@
 //! into the native VDom instance.
 //!
 //! Currently, NodeRefs won't work properly, but all other event functionality will.
+#![allow(non_upper_case_globals, non_snake_case)]
 
 use dioxus::{events::on::MouseEvent, prelude::*};
 
-fn main() {
+fn main() -> anyhow::Result<()> {
     env_logger::init();
-    dioxus::desktop::launch(App, |c| c);
+    dioxus::desktop::launch(App, |c| c)
 }
 
 static App: FC<()> = |cx| {

+ 1 - 34
examples/webview_web.rs

@@ -1,3 +1,4 @@
+#![allow(non_upper_case_globals, non_snake_case)]
 //! Example: Webview Renderer
 //! -------------------------
 //!
@@ -11,53 +12,19 @@
 
 use dioxus::prelude::*;
 
-// #[cfg]
 fn main() {
-    // env_logger::init();
     dioxus::web::launch(App, |c| c);
 }
 
 static App: FC<()> = |cx| {
-    dbg!("rednering parent");
-    cx.render(rsx! {
-        div {
-            But {
-                h1 {"he"}
-            }
-            // But {
-            //     h1 {"llo"}
-            // }
-            // But {
-            //     h1 {"world"}
-            // }
-        }
-    })
-};
-
-static But: FC<()> = |cx| {
     let mut count = use_state(cx, || 0);
 
-    // let d = Dropper { name: "asd" };
-    // let handler = move |_| {
-    //     dbg!(d.name);
-    // };
-
     cx.render(rsx! {
         div {
             h1 { "Hifive counter: {count}" }
             {cx.children()}
             button { onclick: move |_| count += 1, "Up high!" }
             button { onclick: move |_| count -= 1, "Down low!" }
-            // button { onclick: {handler}, "Down low!" }
         }
     })
 };
-
-// struct Dropper {
-//     name: &'static str,
-// }
-// impl Drop for Dropper {
-//     fn drop(&mut self) {
-//         dbg!("dropped");
-//     }
-// }

+ 4 - 0
packages/core/Cargo.toml

@@ -40,6 +40,10 @@ smallvec = "1.6.1"
 slab = "0.4.3"
 
 
+[dev-dependencies]
+dioxus-html = { path = "../html" }
+
+
 [features]
 default = ["serialize"]
 serialize = ["serde"]

+ 2 - 4
packages/core/examples/alternative.rs

@@ -1,9 +1,7 @@
-fn main() {}
-
-use dioxus::*;
-use dioxus_core as dioxus;
 use dioxus_core::prelude::*;
 
+fn main() {}
+
 pub static Example: FC<()> = |cx| {
     let list = (0..10).map(|f| LazyNodes::new(move |f| todo!()));
 

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

@@ -1,4 +1,5 @@
 use std::cell::{RefCell, RefMut};
+use std::fmt::Display;
 use std::{cell::UnsafeCell, rc::Rc};
 
 use crate::heuristics::*;
@@ -17,6 +18,11 @@ pub struct ScopeId(pub usize);
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
 pub struct ElementId(pub usize);
+impl Display for ElementId {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}", self.0)
+    }
+}
 
 impl ElementId {
     pub fn as_u64(self) -> u64 {

+ 19 - 30
packages/core/src/context.rs

@@ -1,24 +1,24 @@
-use crate::innerlude::*;
+//! Public APIs for managing component state, tasks, and lifecycles.
 
-use futures_util::FutureExt;
-use std::{
-    any::{Any, TypeId},
-    cell::{Cell, RefCell},
-    future::Future,
-    ops::Deref,
-    rc::Rc,
-};
+use crate::innerlude::*;
+use std::{any::TypeId, ops::Deref, rc::Rc};
 
 /// Components in Dioxus use the "Context" object to interact with their lifecycle.
-/// This lets components schedule updates, integrate hooks, and expose their context via the context api.
 ///
-/// Properties passed down from the parent component are also directly accessible via the exposed "props" field.
+/// This lets components access props, schedule updates, integrate hooks, and expose shared state.
+///
+/// Note: all of these methods are *imperative* - they do not act as hooks! They are meant to be used by hooks
+/// to provide complex behavior. For instance, calling "add_shared_state" on every render is considered a leak. This method
+/// exists for the `use_provide_state` hook to provide a shared state object.
+///
+/// For the most part, the only method you should be using regularly is `render`.
+///
+/// ## Example
 ///
 /// ```ignore
 /// #[derive(Properties)]
 /// struct Props {
 ///     name: String
-///
 /// }
 ///
 /// fn example(cx: Context<Props>) -> VNode {
@@ -27,16 +27,6 @@ use std::{
 ///     }
 /// }
 /// ```
-///
-/// ## Available Methods:
-/// - render
-/// - use_hook
-/// - use_task
-/// - use_suspense
-/// - submit_task
-/// - children
-/// - use_effect
-///
 pub struct Context<'src, T> {
     pub props: &'src T,
     pub scope: &'src Scope,
@@ -123,7 +113,8 @@ impl<'src, P> Context<'src, P> {
         todo!()
     }
 
-    /// Get's this context's ScopeId
+    /// Get's this component's unique identifier.
+    ///
     pub fn get_scope_id(&self) -> ScopeId {
         self.scope.our_arena_idx.clone()
     }
@@ -148,11 +139,7 @@ impl<'src, P> Context<'src, P> {
         lazy_nodes: LazyNodes<'src, F>,
     ) -> DomTree<'src> {
         let scope_ref = self.scope;
-        // let listener_id = &scope_ref.listener_idx;
-        Some(lazy_nodes.into_vnode(NodeFactory {
-            scope: scope_ref,
-            // listener_id,
-        }))
+        Some(lazy_nodes.into_vnode(NodeFactory { scope: scope_ref }))
     }
 
     /// `submit_task` will submit the future to be polled.
@@ -177,11 +164,14 @@ impl<'src, P> Context<'src, P> {
     }
 
     /// Add a state globally accessible to child components via tree walking
-    pub fn add_shared_state<T: 'static>(self, val: T) -> Option<Rc<dyn Any>> {
+    pub fn add_shared_state<T: 'static>(self, val: T) {
         self.scope
             .shared_contexts
             .borrow_mut()
             .insert(TypeId::of::<T>(), Rc::new(val))
+            .map(|_| {
+                log::warn!("A shared state was replaced with itself. This is does not result in a panic, but is probably not what you are trying to do");
+            });
     }
 
     /// Walk the tree to find a shared state with the TypeId of the generic type
@@ -212,7 +202,6 @@ impl<'src, P> Context<'src, P> {
                     .clone()
                     .downcast::<T>()
                     .expect("Should not fail, already validated the type from the hashmap");
-
                 par = Some(rc);
                 break;
             } else {

+ 240 - 281
packages/core/src/diff.rs

@@ -55,7 +55,7 @@ use crate::{arena::SharedResources, innerlude::*};
 use fxhash::{FxHashMap, FxHashSet};
 use smallvec::{smallvec, SmallVec};
 
-use std::{any::Any, borrow::Borrow, cmp::Ordering};
+use std::{any::Any, cmp::Ordering, process::Child};
 
 /// Instead of having handles directly over nodes, Dioxus uses simple u32 as node IDs.
 /// The expectation is that the underlying renderer will mainain their Nodes in vec where the ids are the index. This allows
@@ -71,29 +71,18 @@ pub trait RealDom<'a> {
 
 pub struct DiffMachine<'real, 'bump> {
     pub real_dom: &'real dyn RealDom<'bump>,
+
     pub vdom: &'bump SharedResources,
+
     pub edits: DomEditor<'real, 'bump>,
 
     pub scheduled_garbage: Vec<&'bump VNode<'bump>>,
 
     pub cur_idxs: SmallVec<[ScopeId; 5]>,
-    pub diffed: FxHashSet<ScopeId>,
-    pub seen_nodes: FxHashSet<ScopeId>,
-}
 
-impl<'r, 'b> DiffMachine<'r, 'b> {
-    pub fn get_scope_mut(&mut self, id: &ScopeId) -> Option<&'b mut Scope> {
-        // ensure we haven't seen this scope before
-        // if we have, then we're trying to alias it, which is not allowed
-        debug_assert!(!self.seen_nodes.contains(id));
+    pub diffed: FxHashSet<ScopeId>,
 
-        unsafe { self.vdom.get_scope_mut(*id) }
-    }
-    pub fn get_scope(&mut self, id: &ScopeId) -> Option<&'b Scope> {
-        // ensure we haven't seen this scope before
-        // if we have, then we're trying to alias it, which is not allowed
-        unsafe { self.vdom.get_scope(*id) }
-    }
+    pub seen_nodes: FxHashSet<ScopeId>,
 }
 
 impl<'real, 'bump> DiffMachine<'real, 'bump> {
@@ -121,20 +110,17 @@ impl<'real, 'bump> DiffMachine<'real, 'bump> {
     //
     // each function call assumes the stack is fresh (empty).
     pub fn diff_node(&mut self, old_node: &'bump VNode<'bump>, new_node: &'bump VNode<'bump>) {
+        let root = old_node.dom_id.get();
+
         match (&old_node.kind, &new_node.kind) {
             // Handle the "sane" cases first.
             // The rsx and html macros strongly discourage dynamic lists not encapsulated by a "Fragment".
             // So the sane (and fast!) cases are where the virtual structure stays the same and is easily diffable.
             (VNodeKind::Text(old), VNodeKind::Text(new)) => {
-                // currently busted for components - need to fid
-                let root = old_node.dom_id.get().expect(&format!(
-                    "Should not be diffing old nodes that were never assigned, {:#?}",
-                    old_node
-                ));
+                let root = root.unwrap();
 
                 if old.text != new.text {
                     self.edits.push_root(root);
-                    log::debug!("Text has changed {}, {}", old.text, new.text);
                     self.edits.set_text(new.text);
                     self.edits.pop();
                 }
@@ -143,16 +129,12 @@ impl<'real, 'bump> DiffMachine<'real, 'bump> {
             }
 
             (VNodeKind::Element(old), VNodeKind::Element(new)) => {
-                // currently busted for components - need to fid
-                let root = old_node.dom_id.get().expect(&format!(
-                    "Should not be diffing old nodes that were never assigned, {:#?}",
-                    old_node
-                ));
+                let root = root.unwrap();
 
                 // If the element type is completely different, the element needs to be re-rendered completely
                 // This is an optimization React makes due to how users structure their code
                 //
-                // In Dioxus, this is less likely to occur unless through a fragment
+                // This case is rather rare (typically only in non-keyed lists)
                 if new.tag_name != old.tag_name || new.namespace != old.namespace {
                     self.edits.push_root(root);
                     let meta = self.create(new_node);
@@ -164,59 +146,105 @@ impl<'real, 'bump> DiffMachine<'real, 'bump> {
 
                 new_node.dom_id.set(Some(root));
 
-                // push it just in case
-                // TODO: remove this - it clogs up things and is inefficient
-                // self.edits.push_root(root);
-
+                // Don't push the root if we don't have to
                 let mut has_comitted = false;
-                self.edits.push_root(root);
-                // dbg!("diffing listeners");
-                self.diff_listeners(&mut has_comitted, old.listeners, new.listeners);
-                // dbg!("diffing attrs");
-                self.diff_attr(
-                    &mut has_comitted,
-                    old.attributes,
-                    new.attributes,
-                    new.namespace,
-                );
-                // dbg!("diffing childrne");
-                self.diff_children(&mut has_comitted, old.children, new.children);
-                self.edits.pop();
-                // if has_comitted {
-                //     self.edits.pop();
-                // }
+                let mut please_commit = |edits: &mut DomEditor| {
+                    if !has_comitted {
+                        has_comitted = true;
+                        edits.push_root(root);
+                    }
+                };
+
+                // Diff Attributes
+                //
+                // It's extraordinarily rare to have the number/order of attributes change
+                // In these cases, we just completely erase the old set and make a new set
+                //
+                // TODO: take a more efficient path than this
+                if old.attributes.len() == new.attributes.len() {
+                    for (old_attr, new_attr) in old.attributes.iter().zip(new.attributes.iter()) {
+                        if old_attr.value != new_attr.value {
+                            please_commit(&mut self.edits);
+                            self.edits.set_attribute(new_attr);
+                        }
+                    }
+                } else {
+                    // TODO: provide some sort of report on how "good" the diffing was
+                    please_commit(&mut self.edits);
+                    for attribute in old.attributes {
+                        self.edits.remove_attribute(attribute);
+                    }
+                    for attribute in new.attributes {
+                        self.edits.set_attribute(attribute)
+                    }
+                }
+
+                // Diff listeners
+                //
+                // It's extraordinarily rare to have the number/order of listeners change
+                // In the cases where the listeners change, we completely wipe the data attributes and add new ones
+                //
+                // TODO: take a more efficient path than this
+                if old.listeners.len() == new.listeners.len() {
+                    for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) {
+                        if old_l.event != new_l.event {
+                            please_commit(&mut self.edits);
+                            self.edits.remove_event_listener(old_l.event);
+                            self.edits.new_event_listener(new_l);
+                        }
+                        new_l.mounted_node.set(old_l.mounted_node.get());
+                    }
+                } else {
+                    please_commit(&mut self.edits);
+                    for listener in old.listeners {
+                        self.edits.remove_event_listener(listener.event);
+                    }
+                    for listener in new.listeners {
+                        listener.mounted_node.set(Some(root));
+                        self.edits.new_event_listener(listener);
+                    }
+                }
+
+                if has_comitted {
+                    self.edits.pop();
+                }
+
+                // Each child pushes its own root, so it doesn't need our current root
+                todo!();
+                // self.diff_children(old.children, new.children);
             }
 
             (VNodeKind::Component(old), VNodeKind::Component(new)) => {
-                log::warn!("diffing components? {:#?}", new.user_fc);
+                let scope_addr = old.ass_scope.get().unwrap();
+
+                // Make sure we're dealing with the same component (by function pointer)
                 if old.user_fc == new.user_fc {
-                    // Make sure we're dealing with the same component (by function pointer)
-                    self.cur_idxs.push(old.ass_scope.get().unwrap());
+                    //
+                    self.cur_idxs.push(scope_addr);
 
                     // Make sure the new component vnode is referencing the right scope id
-                    let scope_addr = old.ass_scope.get().unwrap();
                     new.ass_scope.set(Some(scope_addr));
 
                     // make sure the component's caller function is up to date
                     let scope = self.get_scope_mut(&scope_addr).unwrap();
 
-                    scope.caller = new.caller.clone();
-
-                    // ack - this doesn't work on its own!
-                    scope.update_children(ScopeChildren(new.children));
+                    scope
+                        .update_scope_dependencies(new.caller.clone(), ScopeChildren(new.children));
 
                     // React doesn't automatically memoize, but we do.
-                    let are_the_same = match old.comparator {
-                        Some(comparator) => comparator(new),
-                        None => false,
-                    };
-
-                    if !are_the_same {
-                        scope.run_scope().unwrap();
-                        self.diff_node(scope.frames.wip_head(), scope.frames.fin_head());
-                    } else {
-                        //
+                    let compare = old.comparator.unwrap();
+
+                    match compare(new) {
+                        true => {
+                            // the props are the same...
+                        }
+                        false => {
+                            // the props are different...
+                            scope.run_scope().unwrap();
+                            self.diff_node(scope.frames.wip_head(), scope.frames.fin_head());
+                        }
                     }
+
                     self.cur_idxs.pop();
 
                     self.seen_nodes.insert(scope_addr);
@@ -254,14 +282,7 @@ impl<'real, 'bump> DiffMachine<'real, 'bump> {
                     return;
                 }
 
-                // Diff using the approach where we're looking for added or removed nodes.
-                if old.children.len() != new.children.len() {}
-
-                // Diff where we think the elements are the same
-                if old.children.len() == new.children.len() {}
-
-                let mut has_comitted = false;
-                self.diff_children(&mut has_comitted, old.children, new.children);
+                self.diff_children(old, new, old, new_anchor)
             }
 
             // The strategy here is to pick the first possible node from the previous set and use that as our replace with root
@@ -329,25 +350,7 @@ impl<'real, 'bump> DiffMachine<'real, 'bump> {
             }
         }
     }
-}
 
-// When we create new nodes, we need to propagate some information back up the call chain.
-// This gives the caller some information on how to handle things like insertins, appending, and subtree discarding.
-pub struct CreateMeta {
-    pub is_static: bool,
-    pub added_to_stack: u32,
-}
-
-impl CreateMeta {
-    fn new(is_static: bool, added_to_tack: u32) -> Self {
-        Self {
-            is_static,
-            added_to_stack: added_to_tack,
-        }
-    }
-}
-
-impl<'real, 'bump> DiffMachine<'real, 'bump> {
     // Emit instructions to create the given virtual node.
     //
     // The change list stack may have any shape upon entering this function:
@@ -395,11 +398,10 @@ impl<'real, 'bump> DiffMachine<'real, 'bump> {
                 };
                 node.dom_id.set(Some(real_id));
 
-                listeners.iter().enumerate().for_each(|(idx, listener)| {
+                listeners.iter().for_each(|listener| {
                     log::info!("setting listener id to {:#?}", real_id);
                     listener.mounted_node.set(Some(real_id));
-                    self.edits
-                        .new_event_listener(listener.event, listener.scope, idx, real_id);
+                    self.edits.new_event_listener(listener);
 
                     // if the node has an event listener, then it must be visited ?
                     is_static = false;
@@ -407,8 +409,7 @@ impl<'real, 'bump> DiffMachine<'real, 'bump> {
 
                 for attr in *attributes {
                     is_static = is_static && attr.is_static;
-                    self.edits
-                        .set_attribute(&attr.name, &attr.value, *namespace);
+                    self.edits.set_attribute(attr);
                 }
 
                 // Fast path: if there is a single text child, it is faster to
@@ -526,9 +527,25 @@ impl<'real, 'bump> DiffMachine<'real, 'bump> {
             }
         }
     }
-}
 
-impl<'a, 'bump> DiffMachine<'a, 'bump> {
+    fn create_children(&mut self, children: &'bump [VNode<'bump>]) -> CreateMeta {
+        let mut is_static = true;
+        let mut added_to_stack = 0;
+
+        for child in children {
+            let child_meta = self.create(child);
+            is_static = is_static && child_meta.is_static;
+            added_to_stack += child_meta.added_to_stack;
+        }
+
+        CreateMeta {
+            is_static,
+            added_to_stack,
+        }
+    }
+
+    pub fn replace_vnode(&mut self, old_node: &'bump VNode<'bump>, new_node: &'bump VNode<'bump>) {}
+
     /// Destroy a scope and all of its descendents.
     ///
     /// Calling this will run the destuctors on all hooks in the tree.
@@ -561,133 +578,6 @@ impl<'a, 'bump> DiffMachine<'a, 'bump> {
         }
     }
 
-    // Diff event listeners between `old` and `new`.
-    //
-    // The listeners' node must be on top of the change list stack:
-    //
-    //     [... node]
-    //
-    // The change list stack is left unchanged.
-    fn diff_listeners(&mut self, committed: &mut bool, old: &[Listener<'_>], new: &[Listener<'_>]) {
-        if !old.is_empty() || !new.is_empty() {
-            // self.edits.commit_traversal();
-        }
-        // TODO
-        // what does "diffing listeners" even mean?
-
-        for (old_l, new_l) in old.iter().zip(new.iter()) {
-            log::info!(
-                "moving listener forward with event. old: {:#?}",
-                old_l.mounted_node.get()
-            );
-            new_l.mounted_node.set(old_l.mounted_node.get());
-        }
-        // 'outer1: for (_l_idx, new_l) in new.iter().enumerate() {
-        //     // go through each new listener
-        //     // find its corresponding partner in the old list
-        //     // if any characteristics changed, remove and then re-add
-
-        //     // if nothing changed, then just move on
-        //     let _event_type = new_l.event;
-
-        //     for old_l in old {
-        //         if new_l.event == old_l.event {
-        //             log::info!(
-        //                 "moving listener forward with event. old: {:#?}",
-        //                 old_l.mounted_node.get()
-        //             );
-        //             new_l.mounted_node.set(old_l.mounted_node.get());
-        //             // if new_l.id != old_l.id {
-        //             //     self.edits.remove_event_listener(event_type);
-        //             //     // TODO! we need to mess with events and assign them by ElementId
-        //             //     // self.edits
-        //             //     //     .update_event_listener(event_type, new_l.scope, new_l.id)
-        //             // }
-
-        //             continue 'outer1;
-        //         }
-        //     }
-
-        // self.edits
-        //     .new_event_listener(event_type, new_l.scope, new_l.id);
-        // }
-
-        // 'outer2: for old_l in old {
-        //     for new_l in new {
-        //         if new_l.event == old_l.event {
-        //             continue 'outer2;
-        //         }
-        //     }
-        //     self.edits.remove_event_listener(old_l.event);
-        // }
-    }
-
-    // Diff a node's attributes.
-    //
-    // The attributes' node must be on top of the change list stack:
-    //
-    //     [... node]
-    //
-    // The change list stack is left unchanged.
-    fn diff_attr(
-        &mut self,
-        committed: &mut bool,
-        old: &'bump [Attribute<'bump>],
-        new: &'bump [Attribute<'bump>],
-        namespace: Option<&'static str>,
-    ) {
-        for (old_attr, new_attr) in old.iter().zip(new.iter()) {
-            if old_attr.value != new_attr.value {
-                if !*committed {
-                    *committed = true;
-                    // self.edits.push_root();
-                }
-            }
-            // if old_attr.name == new_attr.name {
-            // }
-        }
-        // Do O(n^2) passes to add/update and remove attributes, since
-        // there are almost always very few attributes.
-        //
-        // The "fast" path is when the list of attributes name is identical and in the same order
-        // With the Rsx and Html macros, this will almost always be the case
-        // 'outer: for new_attr in new {
-        //     if new_attr.is_volatile {
-        //         // self.edits.commit_traversal();
-        //         self.edits
-        //             .set_attribute(new_attr.name, new_attr.value, namespace);
-        //     } else {
-        //         for old_attr in old {
-        //             if old_attr.name == new_attr.name {
-        //                 if old_attr.value != new_attr.value {
-        //                     // self.edits.commit_traversal();
-        //                     self.edits
-        //                         .set_attribute(new_attr.name, new_attr.value, namespace);
-        //                 }
-        //                 continue 'outer;
-        //             } else {
-        //                 // names are different, a varying order of attributes has arrived
-        //             }
-        //         }
-
-        //         // self.edits.commit_traversal();
-        //         self.edits
-        //             .set_attribute(new_attr.name, new_attr.value, namespace);
-        //     }
-        // }
-
-        // 'outer2: for old_attr in old {
-        //     for new_attr in new {
-        //         if old_attr.name == new_attr.name {
-        //             continue 'outer2;
-        //         }
-        //     }
-
-        //     // self.edits.commit_traversal();
-        //     self.edits.remove_attribute(old_attr.name);
-        // }
-    }
-
     // Diff the given set of old and new children.
     //
     // The parent must be on top of the change list stack when this function is
@@ -696,72 +586,98 @@ impl<'a, 'bump> DiffMachine<'a, 'bump> {
     //     [... parent]
     //
     // the change list stack is in the same state when this function returns.
+    //
+    // If old no anchors are provided, then it's assumed that we can freely append to the parent.
+    //
+    // Remember, non-empty lists does not mean that there are real elements, just that there are virtual elements.
     fn diff_children(
         &mut self,
-        committed: &mut bool,
         old: &'bump [VNode<'bump>],
         new: &'bump [VNode<'bump>],
+        old_anchor: &mut Option<ElementId>,
+        new_anchor: &mut Option<ElementId>,
     ) {
-        // if new.is_empty() {
-        //     if !old.is_empty() {
-        //         // self.edits.commit_traversal();
-        //         self.remove_all_children(old);
-        //     }
-        //     return;
-        // }
-
-        // if new.len() == 1 {
-        //     match (&old.first(), &new[0]) {
-        // (Some(VNodeKind::Text(old_vtext)), VNodeKind::Text(new_vtext))
-        //     if old_vtext.text == new_vtext.text =>
-        // {
-        //     // Don't take this fast path...
-        // }
+        const IS_EMPTY: bool = true;
+        const IS_NOT_EMPTY: bool = false;
+
+        match (old_anchor, new.is_empty()) {
+            // Both are empty, dealing only with potential anchors
+            (Some(_), IS_EMPTY) => {
+                *new_anchor = *old_anchor;
+                if old.len() > 0 {
+                    // clean up these virtual nodes (components, fragments, etc)
+                }
+            }
 
-        // (_, VNodeKind::Text(text)) => {
-        //     // self.edits.commit_traversal();
-        //     log::debug!("using optimized text set");
-        //     self.edits.set_text(text.text);
-        //     return;
-        // }
+            // Completely adding new nodes, removing any placeholder if it exists
+            (Some(anchor), IS_NOT_EMPTY) => match old_anchor {
+                // If there's anchor to work from, then we replace it with the new children
+                Some(anchor) => {
+                    self.edits.push_root(*anchor);
+                    let meta = self.create_children(new);
+                    if meta.added_to_stack > 0 {
+                        self.edits.replace_with(meta.added_to_stack)
+                    } else {
+                        // no items added to the stack... hmmmm....
+                        *new_anchor = *old_anchor;
+                    }
+                }
 
-        // todo: any more optimizations
-        //         (_, _) => {}
-        //     }
-        // }
+                // If there's no anchor to work with, we just straight up append them
+                None => {
+                    let meta = self.create_children(new);
+                    self.edits.append_children(meta.added_to_stack);
+                }
+            },
+
+            // Completely removing old nodes and putting an anchor in its place
+            // no anchor (old has nodes) and the new is empty
+            // remove all the old nodes
+            (None, IS_EMPTY) => {
+                // load the first real
+                if let Some(to_replace) = find_first_real_node(old, self.vdom) {
+                    //
+                    self.edits.push_root(to_replace.dom_id.get().unwrap());
+
+                    // Create the anchor
+                    let anchor_id = self.vdom.reserve_node();
+                    self.edits.create_placeholder(anchor_id);
+                    *new_anchor = Some(anchor_id);
+
+                    // Replace that node
+                    self.edits.replace_with(1);
+                } else {
+                    // no real nodes -
+                    *new_anchor = *old_anchor;
+                }
 
-        // if old.is_empty() {
-        //     if !new.is_empty() {
-        //         // self.edits.commit_traversal();
-        //         self.create_and_append_children(new);
-        //     }
-        //     return;
-        // }
+                // remove the rest
+                for child in &old[1..] {
+                    self.edits.push_root(child.element_id().unwrap());
+                    self.edits.remove();
+                }
+            }
 
-        // let new_is_keyed = new[0].key.is_some();
-        // let old_is_keyed = old[0].key.is_some();
+            (None, IS_NOT_EMPTY) => {
+                let new_is_keyed = new[0].key.is_some();
+                let old_is_keyed = old[0].key.is_some();
 
-        // debug_assert!(
-        //     new.iter().all(|n| n.key.is_some() == new_is_keyed),
-        //     "all siblings must be keyed or all siblings must be non-keyed"
-        // );
-        // debug_assert!(
-        //     old.iter().all(|o| o.key.is_some() == old_is_keyed),
-        //     "all siblings must be keyed or all siblings must be non-keyed"
-        // );
+                debug_assert!(
+                    new.iter().all(|n| n.key.is_some() == new_is_keyed),
+                    "all siblings must be keyed or all siblings must be non-keyed"
+                );
+                debug_assert!(
+                    old.iter().all(|o| o.key.is_some() == old_is_keyed),
+                    "all siblings must be keyed or all siblings must be non-keyed"
+                );
 
-        // if new_is_keyed && old_is_keyed {
-        //     // log::warn!("using the wrong approach");
-        //     self.diff_non_keyed_children(old, new);
-        //     // todo!("Not yet implemented a migration away from temporaries");
-        //     // let t = self.edits.next_temporary();
-        //     // self.diff_keyed_children(old, new);
-        //     // self.edits.set_next_temporary(t);
-        // } else {
-        //     // log::debug!("diffing non keyed children");
-        //     self.diff_non_keyed_children(old, new);
-        // }
-        self.diff_non_keyed_children(old, new);
+                if new_is_keyed && old_is_keyed {
+                    self.diff_keyed_children(old, new);
+                } else {
+                    self.diff_non_keyed_children(old, new);
+                }
+            }
+        }
     }
 
     // Diffing "keyed" children.
@@ -1280,6 +1196,35 @@ impl<'a, 'bump> DiffMachine<'a, 'bump> {
         todo!()
         // self.edits.remove_self_and_next_siblings();
     }
+
+    pub fn get_scope_mut(&mut self, id: &ScopeId) -> Option<&'bump mut Scope> {
+        // ensure we haven't seen this scope before
+        // if we have, then we're trying to alias it, which is not allowed
+        debug_assert!(!self.seen_nodes.contains(id));
+
+        unsafe { self.vdom.get_scope_mut(*id) }
+    }
+    pub fn get_scope(&mut self, id: &ScopeId) -> Option<&'bump Scope> {
+        // ensure we haven't seen this scope before
+        // if we have, then we're trying to alias it, which is not allowed
+        unsafe { self.vdom.get_scope(*id) }
+    }
+}
+
+// When we create new nodes, we need to propagate some information back up the call chain.
+// This gives the caller some information on how to handle things like insertins, appending, and subtree discarding.
+pub struct CreateMeta {
+    pub is_static: bool,
+    pub added_to_stack: u32,
+}
+
+impl CreateMeta {
+    fn new(is_static: bool, added_to_tack: u32) -> Self {
+        Self {
+            is_static,
+            added_to_stack: added_to_tack,
+        }
+    }
 }
 
 enum KeyedPrefixResult {
@@ -1291,6 +1236,20 @@ enum KeyedPrefixResult {
     MoreWorkToDo(usize),
 }
 
+fn find_first_real_node<'a>(
+    nodes: &'a [VNode<'a>],
+    scopes: &'a SharedResources,
+) -> Option<&'a VNode<'a>> {
+    for node in nodes {
+        let iter = RealChildIterator::new(node, scopes);
+        if let Some(node) = iter.next() {
+            return Some(node);
+        }
+    }
+
+    None
+}
+
 /// This iterator iterates through a list of virtual children and only returns real children (Elements or Text).
 ///
 /// This iterator is useful when it's important to load the next real root onto the top of the stack for operations like

+ 34 - 20
packages/core/src/editor.rs

@@ -5,7 +5,10 @@
 //!
 //!
 
-use crate::{innerlude::ScopeId, ElementId};
+use crate::{
+    innerlude::{Attribute, Listener, ScopeId},
+    ElementId,
+};
 
 /// The `DomEditor` provides an imperative interface for the Diffing algorithm to plan out its changes.
 ///
@@ -86,18 +89,20 @@ impl<'real, 'bump> DomEditor<'real, 'bump> {
     }
 
     // events
-    pub(crate) fn new_event_listener(
-        &mut self,
-        event: &'static str,
-        scope: ScopeId,
-        element_id: usize,
-        realnode: ElementId,
-    ) {
-        self.edits.push(NewEventListener {
+    pub(crate) fn new_event_listener(&mut self, listener: &Listener) {
+        let Listener {
+            event,
             scope,
+            mounted_node,
+            ..
+        } = listener;
+
+        let element_id = mounted_node.get().unwrap().as_u64();
+
+        self.edits.push(NewEventListener {
+            scope: scope.clone(),
             event_name: event,
-            element_id,
-            mounted_node_id: realnode.as_u64(),
+            mounted_node_id: element_id,
         });
     }
 
@@ -113,17 +118,27 @@ impl<'real, 'bump> DomEditor<'real, 'bump> {
     }
 
     #[inline]
-    pub(crate) fn set_attribute(
-        &mut self,
-        field: &'static str,
-        value: &'bump str,
-        ns: Option<&'static str>,
-    ) {
-        self.edits.push(SetAttribute { field, value, ns });
+    pub(crate) fn set_attribute(&mut self, attribute: &'bump Attribute) {
+        let Attribute {
+            name,
+            value,
+            is_static,
+            is_volatile,
+            namespace,
+        } = attribute;
+        // field: &'static str,
+        // value: &'bump str,
+        // ns: Option<&'static str>,
+        self.edits.push(SetAttribute {
+            field: name,
+            value,
+            ns: *namespace,
+        });
     }
 
     #[inline]
-    pub(crate) fn remove_attribute(&mut self, name: &'static str) {
+    pub(crate) fn remove_attribute(&mut self, attribute: &Attribute) {
+        let name = attribute.name;
         self.edits.push(RemoveAttribute { name });
     }
 }
@@ -169,7 +184,6 @@ pub enum DomEdit<'bump> {
         event_name: &'static str,
         scope: ScopeId,
         mounted_node_id: u64,
-        element_id: usize,
     },
     RemoveEventListener {
         event: &'static str,

+ 10 - 0
packages/core/src/nodes.rs

@@ -117,6 +117,16 @@ pub struct Listener<'bump> {
     pub(crate) callback: RefCell<Option<BumpBox<'bump, dyn FnMut(VirtualEvent) + 'bump>>>,
 }
 
+impl Listener<'_> {
+    // serialize the listener event stuff to a string
+    pub fn serialize(&self) {
+        //
+    }
+    pub fn deserialize() {
+        //
+    }
+}
+
 /// Virtual Components for custom user-defined components
 /// Only supports the functional syntax
 pub struct VComponent<'src> {

+ 3 - 6
packages/core/src/scope.rs

@@ -87,15 +87,12 @@ impl Scope {
         }
     }
 
-    pub(crate) fn update_caller<'creator_node>(&mut self, caller: Rc<WrappedCaller>) {
-        self.caller = caller;
-    }
-
-    pub(crate) fn update_children<'creator_node>(
+    pub(crate) fn update_scope_dependencies<'creator_node>(
         &mut self,
+        caller: Rc<WrappedCaller>,
         child_nodes: ScopeChildren,
-        // child_nodes: &'creator_node [VNode<'creator_node>],
     ) {
+        self.caller = caller;
         // let child_nodes = unsafe { std::mem::transmute(child_nodes) };
         let child_nodes = unsafe { child_nodes.extend_lifetime() };
         self.child_nodes = child_nodes;

+ 17 - 0
packages/core/tests/diffing.rs

@@ -0,0 +1,17 @@
+use dioxus::prelude::*;
+use dioxus_core as dioxus;
+use dioxus_html as dioxus_elements;
+
+#[test]
+fn diffing_works() {}
+
+#[test]
+fn html_and_rsx_generate_the_same_output() {
+    let old = rsx! {
+        div { "Hello world!" }
+    };
+
+    // let new = html! {
+    //     <div>"Hello world!"</div>
+    // };
+}

+ 1 - 1
packages/desktop/examples/test.rs

@@ -3,7 +3,7 @@ use dioxus_core::prelude::*;
 use dioxus_html as dioxus_elements;
 
 fn main() {
-    dioxus_desktop::launch(App, |f| f.with_maximized(true)).expect("Failed");
+    dioxus_desktop::launch(App, |f| f.with_window(|w| w.with_maximized(true))).expect("Failed");
 }
 
 static App: FC<()> = |cx| {

+ 45 - 0
packages/desktop/src/cfg.rs

@@ -0,0 +1,45 @@
+use std::ops::{Deref, DerefMut};
+
+use dioxus_core::DomEdit;
+use wry::{
+    application::{
+        error::OsError,
+        event_loop::EventLoopWindowTarget,
+        menu::MenuBar,
+        window::{Fullscreen, Icon, Window, WindowBuilder},
+    },
+    webview::{RpcRequest, RpcResponse},
+};
+
+pub struct DesktopConfig<'a> {
+    pub window: WindowBuilder,
+    pub(crate) manual_edits: Option<DomEdit<'a>>,
+    pub(crate) pre_rendered: Option<String>,
+}
+
+impl DesktopConfig<'_> {
+    /// Initializes a new `WindowBuilder` with default values.
+    #[inline]
+    pub fn new() -> Self {
+        Self {
+            window: Default::default(),
+            pre_rendered: None,
+            manual_edits: None,
+        }
+    }
+
+    pub fn with_prerendered(&mut self, content: String) -> &mut Self {
+        self.pre_rendered = Some(content);
+        self
+    }
+
+    pub fn with_window(&mut self, f: impl FnOnce(WindowBuilder) -> WindowBuilder) -> &mut Self {
+        // gots to do a swap because the window builder only takes itself as muy self
+        // I wish more people knew about returning &mut Self
+        let mut builder = WindowBuilder::default();
+        std::mem::swap(&mut self.window, &mut builder);
+        builder = f(builder);
+        std::mem::swap(&mut self.window, &mut builder);
+        self
+    }
+}

+ 179 - 168
packages/desktop/src/index.html

@@ -2,208 +2,219 @@
 <html>
 
 <head>
-    <script>
-        class Interpreter {
-            constructor(root) {
-                this.root = root;
-                this.stack = [root];
-                this.listeners = {
-                    "onclick": {}
-                };
-                this.lastNodeWasText = false;
-                this.nodes = [root, root, root, root];
-            }
 
-            top() {
-                return this.stack[this.stack.length - 1];
-            }
+</head>
 
-            pop() {
-                return this.stack.pop();
-            }
+<body>
+    <div id="_dioxusroot">
+    </div>
+</body>
+<script>
+    class Interpreter {
+        constructor(root) {
+            this.root = root;
+            this.stack = [root];
+            this.listeners = {
+                "onclick": {}
+            };
+            this.lastNodeWasText = false;
+            this.nodes = [root, root, root, root];
+        }
 
-            PushRoot(edit) {
-                const id = edit.id;
-                const node = this.nodes[id];
-                console.log("pushing root ", node, "with id", id);
-                this.stack.push(node);
-            }
+        top() {
+            return this.stack[this.stack.length - 1];
+        }
 
-            PopRoot(edit) {
-                this.stack.pop();
-            }
+        pop() {
+            return this.stack.pop();
+        }
 
-            AppendChildren(edit) {
-                let root = this.stack[this.stack.length - (edit.many + 1)];
-                for (let i = 0; i < edit.many; i++) {
-                    console.log("popping ", i, edit.many);
-                    let node = this.pop();
-                    root.appendChild(node);
-                }
-            }
+        PushRoot(edit) {
+            const id = edit.id;
+            const node = this.nodes[id];
+            console.log("pushing root ", node, "with id", id);
+            this.stack.push(node);
+        }
 
-            ReplaceWith(edit) {
+        PopRoot(edit) {
+            this.stack.pop();
+        }
 
-                let root = this.stack[this.stack.length - (edit.many + 1)];
-                let els = [];
+        AppendChildren(edit) {
+            let root = this.stack[this.stack.length - (edit.many + 1)];
+            for (let i = 0; i < edit.many; i++) {
+                console.log("popping ", i, edit.many);
+                let node = this.pop();
+                root.appendChild(node);
+            }
+        }
 
-                for (let i = 0; i < edit.many; i++) {
-                    els.push(this.pop());
-                }
+        ReplaceWith(edit) {
 
-                root.replaceWith(...els);
-            }
+            let root = this.stack[this.stack.length - (edit.many + 1)];
+            let els = [];
 
-            Remove(edit) {
-                const node = this.stack.pop();
-                node.remove();
+            for (let i = 0; i < edit.many; i++) {
+                els.push(this.pop());
             }
 
-            RemoveAllChildren(edit) {}
+            root.replaceWith(...els);
+        }
 
-            CreateTextNode(edit) {
-                const node = document.createTextNode(edit.text);
-                this.nodes[edit.id] = node;
-                this.stack.push(node);
-            }
+        Remove(edit) {
+            const node = this.stack.pop();
+            node.remove();
+        }
 
-            CreateElement(edit) {
-                const tagName = edit.tag;
-                const el = document.createElement(tagName);
-                this.nodes[edit.id] = el;
-                console.log(`creating element: `, edit);
-                this.stack.push(el);
-            }
+        RemoveAllChildren(edit) {}
 
-            CreateElementNs(edit) {
-                const tagName = edit.tag;
-                console.log(`creating namespaced element: `, edit);
-                this.stack.push(document.createElementNS(edit.ns, edit.tag));
-            }
+        CreateTextNode(edit) {
+            const node = document.createTextNode(edit.text);
+            this.nodes[edit.id] = node;
+            this.stack.push(node);
+        }
 
-            CreatePlaceholder(edit) {
-                const a = `this.stack.push(document.createElement(" pre"))`;
-                this.stack.push(document.createComment("vroot"));
-            }
+        CreateElement(edit) {
+            const tagName = edit.tag;
+            const el = document.createElement(tagName);
+            this.nodes[edit.id] = el;
+            console.log(`creating element: `, edit);
+            this.stack.push(el);
+        }
+
+        CreateElementNs(edit) {
+            const tagName = edit.tag;
+            console.log(`creating namespaced element: `, edit);
+            this.stack.push(document.createElementNS(edit.ns, edit.tag));
+        }
+
+        CreatePlaceholder(edit) {
+            const a = `this.stack.push(document.createElement(" pre"))`;
+            this.stack.push(document.createComment("vroot"));
+        }
 
-            NewEventListener(edit) {
-                const element_id = edit.element_id;
-                const event_name = edit.event_name;
-                const mounted_node_id = edit.mounted_node_id;
-                const scope = edit.scope;
-
-                const element = this.top();
-                element.setAttribute(`dioxus-event-${event_name}`, `${scope}.${mounted_node_id}`);
-
-                console.log("listener map is", this.listeners);
-                if (this.listeners[event_name] === undefined) {
-                    console.log("adding listener!");
-                    this.listeners[event_name] = "bla";
-                    this.root.addEventListener(event_name, (event) => {
-                        const target = event.target;
-                        const type = event.type;
-                        const val = target.getAttribute(`dioxus-event-${event_name}`);
-                        const fields = val.split(".");
-                        const scope_id = parseInt(fields[0]);
-                        const real_id = parseInt(fields[1]);
-
-                        console.log(`parsed event with scope_id ${scope_id} and real_id ${real_id}`);
-
-                        rpc.call('user_event', {
-                            event: event_name,
-                            scope: scope_id,
-                            mounted_dom_id: real_id,
-                        }).then((reply) => {
-                            console.log(reply);
-                            this.stack.push(this.root);
-
-                            for (let x = 0; x < reply.length; x++) {
-                                let edit = reply[x];
-                                console.log(edit);
-
-                                let f = this[edit.type];
-                                f.call(this, edit);
-                            }
-
-                            console.log("initiated");
-                        }).catch((err) => {
-                            console.log("failed to initiate", err);
-                        });
+        NewEventListener(edit) {
+            const element_id = edit.element_id;
+            const event_name = edit.event_name;
+            const mounted_node_id = edit.mounted_node_id;
+            const scope = edit.scope;
+
+            const element = this.top();
+            element.setAttribute(`dioxus-event-${event_name}`, `${scope}.${mounted_node_id}`);
+
+            console.log("listener map is", this.listeners);
+            if (this.listeners[event_name] === undefined) {
+                console.log("adding listener!");
+                this.listeners[event_name] = "bla";
+                this.root.addEventListener(event_name, (event) => {
+                    const target = event.target;
+                    const type = event.type;
+                    const val = target.getAttribute(`dioxus-event-${event_name}`);
+                    const fields = val.split(".");
+                    const scope_id = parseInt(fields[0]);
+                    const real_id = parseInt(fields[1]);
+
+                    console.log(`parsed event with scope_id ${scope_id} and real_id ${real_id}`);
+
+                    rpc.call('user_event', {
+                        event: event_name,
+                        scope: scope_id,
+                        mounted_dom_id: real_id,
+                    }).then((reply) => {
+                        console.log(reply);
+                        this.stack.push(this.root);
+
+                        let edits = reply.edits;
+
+                        for (let x = 0; x < edits.length; x++) {
+                            let edit = edits[x];
+                            console.log(edit);
+
+                            let f = this[edit.type];
+                            f.call(this, edit);
+                        }
+
+                        console.log("initiated");
+                    }).catch((err) => {
+                        console.log("failed to initiate", err);
                     });
-                }
+                });
             }
+        }
+
+        RemoveEventListener(edit) {}
 
-            RemoveEventListener(edit) {}
+        SetText(edit) {
+            this.top().textContent = edit.text;
+        }
 
-            SetText(edit) {
-                this.top().textContent = edit.text;
+        SetAttribute(edit) {
+            const name = edit.field;
+            const value = edit.value;
+            const ns = edit.ns;
+            const node = this.top(this.stack);
+            if (ns == "style") {
+                node.style[name] = value;
+            } else if (ns !== undefined) {
+                node.setAttributeNS(ns, name, value);
+            } else {
+                node.setAttribute(name, value);
+            }
+            if (name === "value") {
+                node.value = value;
+            }
+            if (name === "checked") {
+                node.checked = true;
             }
+            if (name === "selected") {
+                node.selected = true;
+            }
+        }
+        RemoveAttribute(edit) {
+            const name = edit.field;
+            const node = this.top(this.stack);
+            node.removeAttribute(name);
 
-            SetAttribute(edit) {
-                const name = edit.field;
-                const value = edit.value;
-                const ns = edit.ns;
-                const node = this.top(this.stack);
-                if (ns == "style") {
-                    node.style[name] = value;
-                } else if (ns !== undefined) {
-                    node.setAttributeNS(ns, name, value);
-                } else {
-                    node.setAttribute(name, value);
-                }
-                if (name === "value") {
-                    node.value = value;
-                }
-                if (name === "checked") {
-                    node.checked = true;
-                }
-                if (name === "selected") {
-                    node.selected = true;
-                }
+            if (name === "value") {
+                node.value = null;
+            }
+            if (name === "checked") {
+                node.checked = false;
             }
-            RemoveAttribute(edit) {
-                const name = edit.field;
-                const node = this.top(this.stack);
-                node.removeAttribute(name);
-
-                if (name === "value") {
-                    node.value = null;
-                }
-                if (name === "checked") {
-                    node.checked = false;
-                }
-                if (name === "selected") {
-                    node.selected = false;
-                }
+            if (name === "selected") {
+                node.selected = false;
             }
         }
+    }
 
 
 
 
-        async function initialize() {
-            const reply = await rpc.call('initiate');
-            const interpreter = new Interpreter(window.document.getElementById("_dioxusroot"));
-            console.log(reply);
+    async function initialize() {
+        const reply = await rpc.call('initiate');
+        let root = window.document.getElementById("_dioxusroot");
+        const interpreter = new Interpreter(root);
+        console.log(reply);
 
-            for (let x = 0; x < reply.length; x++) {
-                let edit = reply[x];
-                console.log(edit);
+        let pre_rendered = reply.pre_rendered;
+        if (pre_rendered !== undefined) {
+            root.innerHTML = pre_rendered;
+        }
 
-                let f = interpreter[edit.type];
-                f.call(interpreter, edit);
-            }
+        const edits = reply.edits;
+
+        for (let x = 0; x < edits.length; x++) {
+            let edit = edits[x];
+            console.log(edit);
 
-            console.log("stack completed: ", interpreter.stack);
+            let f = interpreter[edit.type];
+            f.call(interpreter, edit);
         }
-        console.log("initializing...");
-        initialize();
-    </script>
-</head>
 
-<body>
-    <div id="_dioxusroot">
-    </div>
-</body>
+        console.log("stack completed: ", interpreter.stack);
+    }
+    console.log("initializing...");
+    initialize();
+</script>
 
 </html>

+ 53 - 18
packages/desktop/src/lib.rs

@@ -3,6 +3,7 @@ use std::ops::{Deref, DerefMut};
 use std::sync::mpsc::channel;
 use std::sync::{Arc, RwLock};
 
+use cfg::DesktopConfig;
 use dioxus_core::*;
 pub use wry;
 
@@ -15,6 +16,7 @@ use wry::{
     webview::{RpcRequest, RpcResponse},
 };
 
+mod cfg;
 mod dom;
 mod escape;
 mod events;
@@ -24,14 +26,14 @@ static HTML_CONTENT: &'static str = include_str!("./index.html");
 
 pub fn launch(
     root: FC<()>,
-    builder: impl FnOnce(WindowBuilder) -> WindowBuilder,
+    builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>,
 ) -> anyhow::Result<()> {
     launch_with_props(root, (), builder)
 }
 pub fn launch_with_props<P: Properties + 'static>(
     root: FC<P>,
     props: P,
-    builder: impl FnOnce(WindowBuilder) -> WindowBuilder,
+    builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>,
 ) -> anyhow::Result<()> {
     WebviewRenderer::run(root, props, builder)
 }
@@ -46,11 +48,17 @@ enum RpcEvent<'a> {
     Initialize { edits: Vec<DomEdit<'a>> },
 }
 
+#[derive(Serialize)]
+struct Response<'a> {
+    pre_rendered: Option<String>,
+    edits: Vec<DomEdit<'a>>,
+}
+
 impl<T: Properties + 'static> WebviewRenderer<T> {
     pub fn run(
         root: FC<T>,
         props: T,
-        user_builder: impl FnOnce(WindowBuilder) -> WindowBuilder,
+        user_builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>,
     ) -> anyhow::Result<()> {
         Self::run_with_edits(root, props, user_builder, None)
     }
@@ -58,13 +66,22 @@ impl<T: Properties + 'static> WebviewRenderer<T> {
     pub fn run_with_edits(
         root: FC<T>,
         props: T,
-        user_builder: impl FnOnce(WindowBuilder) -> WindowBuilder,
+        user_builder: impl for<'a, 'b> FnOnce(&'a mut DesktopConfig<'b>) -> &'a mut DesktopConfig<'b>,
         redits: Option<Vec<DomEdit<'static>>>,
     ) -> anyhow::Result<()> {
         log::info!("hello edits");
         let event_loop = EventLoop::new();
 
-        let window = user_builder(WindowBuilder::new()).build(&event_loop)?;
+        let mut cfg = DesktopConfig::new();
+        user_builder(&mut cfg);
+
+        let DesktopConfig {
+            window,
+            manual_edits,
+            pre_rendered,
+        } = cfg;
+
+        let window = window.build(&event_loop)?;
 
         let vir = VirtualDom::new_with_props(root, props);
 
@@ -73,8 +90,6 @@ impl<T: Properties + 'static> WebviewRenderer<T> {
         // let registry = Arc::new(RwLock::new(Some(WebviewRegistry::new())));
 
         let webview = WebViewBuilder::new(window)?
-            // .with_visible(false)
-            // .with_transparent(true)
             .with_url(&format!("data:text/html,{}", HTML_CONTENT))?
             .with_rpc_handler(move |_window: &Window, mut req: RpcRequest| {
                 match req.method.as_str() {
@@ -87,17 +102,32 @@ impl<T: Properties + 'static> WebviewRenderer<T> {
 
                             // Create the thin wrapper around the registry to collect the edits into
                             let mut real = dom::WebviewDom::new();
-
-                            // Serialize the edit stream
-                            let edits = {
-                                let mut edits = Vec::new();
-                                lock.rebuild(&mut real, &mut edits).unwrap();
-                                serde_json::to_value(edits).unwrap()
+                            let pre = pre_rendered.clone();
+
+                            let response = match pre {
+                                Some(content) => {
+                                    lock.rebuild_in_place().unwrap();
+
+                                    Response {
+                                        edits: Vec::new(),
+                                        pre_rendered: Some(content),
+                                    }
+                                }
+                                None => {
+                                    //
+                                    let edits = {
+                                        let mut edits = Vec::new();
+                                        lock.rebuild(&mut real, &mut edits).unwrap();
+                                        edits
+                                    };
+                                    Response {
+                                        edits,
+                                        pre_rendered: None,
+                                    }
+                                }
                             };
 
-                            // Give back the registry into its slot
-                            // *reg_lock = Some(real.consume());
-                            edits
+                            serde_json::to_value(&response).unwrap()
                         };
 
                         // Return the edits into the webview runtime
@@ -128,13 +158,18 @@ impl<T: Properties + 'static> WebviewRenderer<T> {
                             lock.progress_with_event(&mut real, &mut edits)
                                 .await
                                 .expect("failed to progress");
-                            let edits = serde_json::to_value(edits).unwrap();
+
+                            let response = Response {
+                                edits,
+                                pre_rendered: None,
+                            };
+                            let response = serde_json::to_value(&response).unwrap();
 
                             // Give back the registry into its slot
                             // *reg_lock = Some(real.consume());
 
                             // Return the edits into the webview runtime
-                            Some(RpcResponse::new_result(req.id.take(), Some(edits)))
+                            Some(RpcResponse::new_result(req.id.take(), Some(response)))
                         });
 
                         response

+ 6 - 0
packages/ssr/README.md

@@ -16,3 +16,9 @@ vdom.rebuild_in_place();
 let text = dioxus_ssr::render_root(&vdom);
 assert_eq!(text, "<div>hello world!</div>")
 ```
+
+
+
+## Pre-rendering
+
+

+ 10 - 0
packages/ssr/examples/hydration.rs

@@ -0,0 +1,10 @@
+//! Example: realworld usage of hydration
+//!
+//!
+use dioxus::virtual_dom::VirtualDom;
+use dioxus_core as dioxus;
+use dioxus_core::prelude::*;
+use dioxus_hooks::use_state;
+use dioxus_html as dioxus_elements;
+
+fn main() {}

+ 1 - 1
packages/ssr/examples/tide.rs

@@ -22,7 +22,7 @@ async fn main() -> Result<(), std::io::Error> {
         let dom = VirtualDom::launch_with_props_in_place(Example, ExampleProps { initial_name });
 
         Ok(Response::builder(200)
-            .body(format!("{}", dioxus_ssr::render_vdom(&dom)))
+            .body(format!("{}", dioxus_ssr::render_vdom(&dom, |c| c)))
             .content_type(tide::http::mime::HTML)
             .build())
     });

+ 5 - 2
packages/ssr/examples/tofile.rs

@@ -15,8 +15,11 @@ fn main() {
     let mut dom = VirtualDom::new(App);
     dom.rebuild_in_place().expect("failed to run virtualdom");
 
-    file.write_fmt(format_args!("{}", TextRenderer::from_vdom(&dom)))
-        .unwrap();
+    file.write_fmt(format_args!(
+        "{}",
+        TextRenderer::from_vdom(&dom, Default::default())
+    ))
+    .unwrap();
 }
 
 pub static App: FC<()> = |cx| {

+ 112 - 59
packages/ssr/src/lib.rs

@@ -1,3 +1,7 @@
+//!
+//!
+//!
+//!
 //! This crate demonstrates how to implement a custom renderer for Dioxus VNodes via the `TextRenderer` renderer.
 //! The `TextRenderer` consumes a Dioxus Virtual DOM, progresses its event queue, and renders the VNodes to a String.
 //!
@@ -12,8 +16,11 @@ use dioxus_core::*;
 
 pub fn render_vnode(vnode: &VNode, string: &mut String) {}
 
-pub fn render_vdom(dom: &VirtualDom) -> String {
-    format!("{:}", TextRenderer::from_vdom(dom))
+pub fn render_vdom(dom: &VirtualDom, cfg: impl FnOnce(SsrConfig) -> SsrConfig) -> String {
+    format!(
+        "{:}",
+        TextRenderer::from_vdom(dom, cfg(SsrConfig::default()))
+    )
 }
 
 pub fn render_vdom_scope(vdom: &VirtualDom, scope: ScopeId) -> Option<String> {
@@ -27,29 +34,6 @@ pub fn render_vdom_scope(vdom: &VirtualDom, scope: ScopeId) -> Option<String> {
     ))
 }
 
-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,
-
-    // 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 Default for SsrConfig {
-    fn default() -> Self {
-        Self {
-            indent: false,
-
-            newline: false,
-            _skip_components: false,
-        }
-    }
-}
 /// A configurable text renderer for the Dioxus VirtualDOM.
 ///
 ///
@@ -60,7 +44,7 @@ impl Default for SsrConfig {
 ///
 /// ## Example
 /// ```ignore
-/// const App: FC<()> = |cx| cx.render(rsx!(div { "hello world" }));
+/// static App: FC<()> = |cx| cx.render(rsx!(div { "hello world" }));
 /// let mut vdom = VirtualDom::new(App);
 /// vdom.rebuild_in_place();
 ///
@@ -81,11 +65,11 @@ impl Display for TextRenderer<'_> {
 }
 
 impl<'a> TextRenderer<'a> {
-    pub fn from_vdom(vdom: &'a VirtualDom) -> Self {
+    pub fn from_vdom(vdom: &'a VirtualDom, cfg: SsrConfig) -> Self {
         Self {
+            cfg,
             root: vdom.base_scope().root(),
             vdom: Some(vdom),
-            cfg: SsrConfig::default(),
         }
     }
 
@@ -132,6 +116,21 @@ impl<'a> TextRenderer<'a> {
                     }
                 }
 
+                // we write the element's id as a data attribute
+                //
+                // when the page is loaded, the `querySelectorAll` will be used to collect all the nodes, and then add
+                // them interpreter's stack
+                match (self.cfg.pre_render, node.element_id()) {
+                    (true, Some(id)) => {
+                        write!(f, " dio_el=\"{}\"", id)?;
+                        //
+                        for listener in el.listeners {
+                            // write the listeners
+                        }
+                    }
+                    _ => {}
+                }
+
                 match self.cfg.newline {
                     true => write!(f, ">\n")?,
                     false => write!(f, ">")?,
@@ -162,9 +161,14 @@ impl<'a> TextRenderer<'a> {
             }
             VNodeKind::Component(vcomp) => {
                 let idx = vcomp.ass_scope.get().unwrap();
-                if let Some(vdom) = self.vdom {
-                    let new_node = vdom.get_scope(idx).unwrap().root();
-                    self.html_render(new_node, f, il + 1)?;
+                match (self.vdom, self.cfg.skip_components) {
+                    (Some(vdom), false) => {
+                        let new_node = vdom.get_scope(idx).unwrap().root();
+                        self.html_render(new_node, f, il + 1)?;
+                    }
+                    _ => {
+                        // render the component by name
+                    }
                 }
             }
             VNodeKind::Suspended { .. } => {
@@ -175,6 +179,52 @@ impl<'a> TextRenderer<'a> {
     }
 }
 
+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 Default for SsrConfig {
+    fn default() -> Self {
+        Self {
+            indent: false,
+            pre_render: false,
+            newline: false,
+            skip_components: false,
+        }
+    }
+}
+
+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
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -182,15 +232,14 @@ mod tests {
     use dioxus_core as dioxus;
     use dioxus_core::prelude::*;
     use dioxus_html as dioxus_elements;
-    use dioxus_html::GlobalAttributes;
 
-    const SIMPLE_APP: FC<()> = |cx| {
+    static SIMPLE_APP: FC<()> = |cx| {
         cx.render(rsx!(div {
             "hello world!"
         }))
     };
 
-    const SLIGHTLY_MORE_COMPLEX: FC<()> = |cx| {
+    static SLIGHTLY_MORE_COMPLEX: FC<()> = |cx| {
         cx.render(rsx! {
             div {
                 title: "About W3Schools"
@@ -209,14 +258,14 @@ mod tests {
         })
     };
 
-    const NESTED_APP: FC<()> = |cx| {
+    static NESTED_APP: FC<()> = |cx| {
         cx.render(rsx!(
             div {
                 SIMPLE_APP {}
             }
         ))
     };
-    const FRAGMENT_APP: FC<()> = |cx| {
+    static FRAGMENT_APP: FC<()> = |cx| {
         cx.render(rsx!(
             div { "f1" }
             div { "f2" }
@@ -229,21 +278,28 @@ mod tests {
     fn to_string_works() {
         let mut dom = VirtualDom::new(SIMPLE_APP);
         dom.rebuild_in_place().expect("failed to run virtualdom");
-        dbg!(render_vdom(&dom));
+        dbg!(render_vdom(&dom, |c| c));
+    }
+
+    #[test]
+    fn hydration() {
+        let mut dom = VirtualDom::new(NESTED_APP);
+        dom.rebuild_in_place().expect("failed to run virtualdom");
+        dbg!(render_vdom(&dom, |c| c.pre_render(true)));
     }
 
     #[test]
     fn nested() {
         let mut dom = VirtualDom::new(NESTED_APP);
         dom.rebuild_in_place().expect("failed to run virtualdom");
-        dbg!(render_vdom(&dom));
+        dbg!(render_vdom(&dom, |c| c));
     }
 
     #[test]
     fn fragment_app() {
         let mut dom = VirtualDom::new(FRAGMENT_APP);
         dom.rebuild_in_place().expect("failed to run virtualdom");
-        dbg!(render_vdom(&dom));
+        dbg!(render_vdom(&dom, |c| c));
     }
 
     #[test]
@@ -256,26 +312,23 @@ mod tests {
         let mut dom = VirtualDom::new(SLIGHTLY_MORE_COMPLEX);
         dom.rebuild_in_place().expect("failed to run virtualdom");
 
-        file.write_fmt(format_args!("{}", TextRenderer::from_vdom(&dom)))
-            .unwrap();
+        file.write_fmt(format_args!(
+            "{}",
+            TextRenderer::from_vdom(&dom, SsrConfig::default())
+        ))
+        .unwrap();
     }
 
-    // #[test]
-    // fn styles() {
-    //     const STLYE_APP: FC<()> = |cx| {
-    //         //
-    //         cx.render(rsx! {
-    //             div {
-    //                 style: {
-    //                     color: "blue",
-    //                     font_size: "46px"
-    //                 }
-    //             }
-    //         })
-    //     };
-
-    //     let mut dom = VirtualDom::new(STLYE_APP);
-    //     dom.rebuild_in_place().expect("failed to run virtualdom");
-    //     dbg!(render_vdom(&dom));
-    // }
+    #[test]
+    fn styles() {
+        static STLYE_APP: FC<()> = |cx| {
+            cx.render(rsx! {
+                div { style: { color: "blue", font_size: "46px" } }
+            })
+        };
+
+        let mut dom = VirtualDom::new(STLYE_APP);
+        dom.rebuild_in_place().expect("failed to run virtualdom");
+        dbg!(render_vdom(&dom, |c| c));
+    }
 }

+ 21 - 0
packages/web/src/cfg.rs

@@ -0,0 +1,21 @@
+pub struct WebConfig {
+    pub(crate) hydrate: bool,
+}
+impl Default for WebConfig {
+    fn default() -> Self {
+        Self { hydrate: false }
+    }
+}
+impl WebConfig {
+    /// Enable SSR hydration
+    ///
+    /// This enables Dioxus to pick up work from a pre-renderd HTML file. Hydration will completely skip over any async
+    /// work and suspended nodes.
+    ///
+    /// Dioxus will load up all the elements with the `dio_el` data attribute into memory when the page is loaded.
+    ///
+    pub fn hydrate(mut self, f: bool) -> Self {
+        self.hydrate = f;
+        self
+    }
+}

+ 54 - 39
packages/web/src/new.rs → packages/web/src/dom.rs

@@ -8,12 +8,19 @@ use fxhash::FxHashMap;
 use wasm_bindgen::{closure::Closure, JsCast};
 use web_sys::{
     window, Document, Element, Event, HtmlElement, HtmlInputElement, HtmlOptionElement, Node,
+    NodeList,
 };
 
+use crate::{nodeslab::NodeSlab, WebConfig};
+
 pub struct WebsysDom {
-    pub stack: Stack,
-    nodes: Vec<Node>,
+    stack: Stack,
+
+    /// A map from ElementID (index) to Node
+    nodes: NodeSlab,
+
     document: Document,
+
     root: Element,
 
     event_receiver: async_channel::Receiver<EventTrigger>,
@@ -33,11 +40,8 @@ pub struct WebsysDom {
     last_node_was_text: bool,
 }
 impl WebsysDom {
-    pub fn new(root: Element) -> Self {
-        let document = window()
-            .expect("must have access to the window")
-            .document()
-            .expect("must have access to the Document");
+    pub fn new(root: Element, cfg: WebConfig) -> Self {
+        let document = load_document();
 
         let (sender, receiver) = async_channel::unbounded::<EventTrigger>();
 
@@ -48,14 +52,36 @@ impl WebsysDom {
             });
         });
 
-        let mut nodes = Vec::with_capacity(1000);
+        let mut nodes = NodeSlab::new(2000);
+        let mut listeners = FxHashMap::default();
+
+        // re-hydrate the page - only supports one virtualdom per page
+        if cfg.hydrate {
+            // Load all the elements into the arena
+            let node_list: NodeList = document.query_selector_all("dio_el").unwrap();
+            let len = node_list.length() as usize;
 
-        // let root_id = nodes.insert(root.clone().dyn_into::<Node>().unwrap());
+            for x in 0..len {
+                let node: Node = node_list.get(x as u32).unwrap();
+                let el: &Element = node.dyn_ref::<Element>().unwrap();
+                let id: String = el.get_attribute("dio_el").unwrap();
+                let id = id.parse::<usize>().unwrap();
+
+                // this autoresizes the vector if needed
+                nodes[id] = Some(node);
+            }
+
+            // Load all the event listeners into our listener register
+        }
+
+        let mut stack = Stack::with_capacity(10);
+        let root_node = root.clone().dyn_into::<Node>().unwrap();
+        stack.push(root_node);
 
         Self {
-            stack: Stack::with_capacity(10),
+            stack,
             nodes,
-            listeners: FxHashMap::default(),
+            listeners,
             document,
             event_receiver: receiver,
             trigger: sender_callback,
@@ -97,19 +123,14 @@ impl WebsysDom {
         }
     }
     fn push(&mut self, root: u64) {
-        // let key = DefaultKey::from(KeyData::from_ffi(root));
         let key = root as usize;
-        let domnode = self.nodes.get_mut(key);
+        let domnode = &self.nodes[key];
 
         let real_node: Node = match domnode {
             Some(n) => n.clone(),
             None => todo!(),
         };
 
-        // let domnode = domnode.unwrap().as_mut().unwrap();
-        // .expect(&format!("Failed to pop know root: {:#?}", key))
-        // .unwrap();
-
         self.stack.push(real_node);
     }
     // drop the node off the stack
@@ -205,7 +226,7 @@ impl WebsysDom {
 
         let id = id as usize;
         self.stack.push(textnode.clone());
-        *self.nodes.get_mut(id).unwrap() = textnode;
+        self.nodes[id] = Some(textnode);
     }
 
     fn create_element(&mut self, tag: &str, ns: Option<&'static str>, id: u64) {
@@ -227,7 +248,7 @@ impl WebsysDom {
         let id = id as usize;
 
         self.stack.push(el.clone());
-        *self.nodes.get_mut(id).unwrap() = el;
+        self.nodes[id] = Some(el);
         // let nid = self.node_counter.?next();
         // let nid = self.nodes.insert(el).data().as_ffi();
         // log::debug!("Called [`create_element`]: {}, {:?}", tag, nid);
@@ -264,6 +285,9 @@ impl WebsysDom {
         )
         .unwrap();
 
+        el.set_attribute(&format!("dioxus-event"), &format!("{}", event))
+            .unwrap();
+
         // Register the callback to decode
 
         if let Some(entry) = self.listeners.get_mut(event) {
@@ -512,26 +536,6 @@ fn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent {
                 }
             }
             VirtualEvent::MouseEvent(MouseEvent(Rc::new(CustomMouseEvent(evt))))
-            // MouseEvent(Box::new(RawMouseEvent {
-            //                 alt_key: evt.alt_key(),
-            //                 button: evt.button() as i32,
-            //                 buttons: evt.buttons() as i32,
-            //                 client_x: evt.client_x(),
-            //                 client_y: evt.client_y(),
-            //                 ctrl_key: evt.ctrl_key(),
-            //                 meta_key: evt.meta_key(),
-            //                 page_x: evt.page_x(),
-            //                 page_y: evt.page_y(),
-            //                 screen_x: evt.screen_x(),
-            //                 screen_y: evt.screen_y(),
-            //                 shift_key: evt.shift_key(),
-            //                 get_modifier_state: GetModifierKey(Box::new(|f| {
-            //                     // evt.get_modifier_state(f)
-            //                     todo!("This is not yet implemented properly, sorry :(");
-            //                 })),
-            //             }))
-            // todo!()
-            // VirtualEvent::MouseEvent()
         }
 
         "pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture"
@@ -645,3 +649,14 @@ fn decode_trigger(event: &web_sys::Event) -> anyhow::Result<EventTrigger> {
         dioxus_core::events::EventPriority::High,
     ))
 }
+
+pub fn prepare_websys_dom() -> Element {
+    load_document().get_element_by_id("dioxusroot").unwrap()
+}
+
+pub fn load_document() -> Document {
+    web_sys::window()
+        .expect("should have access to the Window")
+        .document()
+        .expect("should have access to the Document")
+}

+ 54 - 33
packages/web/src/lib.rs

@@ -2,38 +2,66 @@
 //! --------------
 //! This crate implements a renderer of the Dioxus Virtual DOM for the web browser using Websys.
 
+pub use crate::cfg::WebConfig;
+use crate::dom::load_document;
 use dioxus::prelude::{Context, Properties, VNode};
 use dioxus::virtual_dom::VirtualDom;
 pub use dioxus_core as dioxus;
+use dioxus_core::error::Result;
 use dioxus_core::{events::EventTrigger, prelude::FC};
 use futures_util::{pin_mut, Stream, StreamExt};
 use fxhash::FxHashMap;
-use web_sys::{window, Document, Element, Event, Node};
+use js_sys::Iterator;
+use web_sys::{window, Document, Element, Event, Node, NodeList};
 
 mod cache;
-mod new;
+mod cfg;
+mod dom;
+mod nodeslab;
 
 /// Launches the VirtualDOM from the specified component function.
 ///
 /// This method will block the thread with `spawn_local`
+///
+/// # Example
+///
+///
+///
 pub fn launch<F>(root: FC<()>, config: F)
 where
-    F: FnOnce(()),
+    F: FnOnce(WebConfig) -> WebConfig,
 {
-    wasm_bindgen_futures::spawn_local(run(root))
+    launch_with_props(root, (), config)
 }
 
+/// Launches the VirtualDOM from the specified component function and props.
+///
+/// This method will block the thread with `spawn_local`
+///
+/// # Example
+///
+///
 pub fn launch_with_props<T, F>(root: FC<T>, root_props: T, config: F)
 where
     T: Properties + 'static,
-    F: FnOnce(()),
+    F: FnOnce(WebConfig) -> WebConfig,
 {
-    wasm_bindgen_futures::spawn_local(run_with_props(root, root_props))
+    let config = config(WebConfig::default());
+    let fut = run_with_props(root, root_props, config);
+
+    wasm_bindgen_futures::spawn_local(async {
+        match fut.await {
+            Ok(_) => log::error!("Your app completed running... somehow?"),
+            Err(e) => log::error!("Your app crashed! {}", e),
+        }
+    });
 }
 
 /// This method is the primary entrypoint for Websys Dioxus apps. Will panic if an error occurs while rendering.
 /// See DioxusErrors for more information on how these errors could occour.
 ///
+/// # Example
+///
 /// ```ignore
 /// fn main() {
 ///     wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example));
@@ -42,29 +70,19 @@ where
 ///
 /// Run the app to completion, panicing if any error occurs while rendering.
 /// Pairs well with the wasm_bindgen async handler
-pub async fn run(root: FC<()>) {
-    run_with_props(root, ()).await;
-}
-
-pub async fn run_with_props<T: Properties + 'static>(root: FC<T>, root_props: T) {
+pub async fn run_with_props<T: Properties + 'static>(
+    root: FC<T>,
+    root_props: T,
+    cfg: WebConfig,
+) -> Result<()> {
     let dom = VirtualDom::new_with_props(root, root_props);
-    event_loop(dom).await.expect("Event loop failed");
-}
-
-pub async fn event_loop(mut internal_dom: VirtualDom) -> dioxus_core::error::Result<()> {
-    use wasm_bindgen::JsCast;
 
-    let root = prepare_websys_dom();
-    let root_node = root.clone().dyn_into::<Node>().unwrap();
+    let root_el = load_document().get_element_by_id("dioxus_root").unwrap();
+    let mut websys_dom = dom::WebsysDom::new(root_el, cfg);
 
-    let mut websys_dom = crate::new::WebsysDom::new(root.clone());
-
-    websys_dom.stack.push(root_node.clone());
-    websys_dom.stack.push(root_node);
-
-    let mut edits = Vec::new();
-    internal_dom.rebuild(&mut websys_dom, &mut edits)?;
-    websys_dom.process_edits(&mut edits);
+    // let mut edits = Vec::new();
+    // internal_dom.rebuild(&mut websys_dom, &mut edits)?;
+    // websys_dom.process_edits(&mut edits);
 
     log::info!("Going into event loop");
     loop {
@@ -105,11 +123,14 @@ pub async fn event_loop(mut internal_dom: VirtualDom) -> dioxus_core::error::Res
     Ok(())
 }
 
-fn prepare_websys_dom() -> Element {
-    web_sys::window()
-        .expect("should have access to the Window")
-        .document()
-        .expect("should have access to the Document")
-        .get_element_by_id("dioxusroot")
-        .unwrap()
+fn iter_node_list() {}
+
+// struct NodeListIter {
+//     node_list: NodeList,
+// }
+// impl Iterator for NodeListIter {}
+
+struct HydrationNode {
+    id: usize,
+    node: Node,
 }

+ 41 - 0
packages/web/src/nodeslab.rs

@@ -0,0 +1,41 @@
+use std::ops::{Index, IndexMut};
+
+use web_sys::Node;
+
+pub struct NodeSlab {
+    nodes: Vec<Option<Node>>,
+}
+
+impl NodeSlab {
+    pub fn new(capacity: usize) -> NodeSlab {
+        NodeSlab {
+            nodes: Vec::with_capacity(capacity),
+        }
+    }
+
+    fn insert_and_extend(&mut self, node: Node, id: usize) {
+        if id > self.nodes.len() * 3 {
+            panic!("Trying to insert an element way too far out of bounds");
+        }
+
+        if id < self.nodes.len() {}
+    }
+}
+impl Index<usize> for NodeSlab {
+    type Output = Option<Node>;
+    fn index(&self, index: usize) -> &Self::Output {
+        &self.nodes[index]
+    }
+}
+
+impl IndexMut<usize> for NodeSlab {
+    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
+        if index >= self.nodes.len() * 3 {
+            panic!("Trying to mutate an element way too far out of bounds");
+        }
+        if index > self.nodes.len() {
+            self.nodes.resize_with(index, || None);
+        }
+        &mut self.nodes[index]
+    }
+}