Explorar o código

wip: memoize dom in the prescence of identical components

Jonathan Kelley %!s(int64=3) %!d(string=hai) anos
pai
achega
cb2782b

+ 2 - 2
Cargo.toml

@@ -20,7 +20,7 @@ dioxus-web = { path = "./packages/web", version = "^0.0.3", optional = true }
 dioxus-desktop = { path = "./packages/desktop", version = "^0.1.3", optional = true }
 dioxus-ssr = { path = "./packages/ssr", version = "0.1.1", optional = true }
 
-# dioxus-router = { path = "./packages/router", optional = true }
+dioxus-router = { path = "./packages/router", optional = true }
 dioxus-mobile = { path = "./packages/mobile", version = "^0.0.2", optional = true }
 # dioxus-liveview = { path = "./packages/liveview", optional = true }
 
@@ -33,7 +33,7 @@ html = ["dioxus-html"]
 ssr = ["dioxus-ssr"]
 web = ["dioxus-web"]
 desktop = ["dioxus-desktop"]
-# router = ["dioxus-router"]
+router = ["dioxus-router"]
 
 # "dioxus-router/web"
 # "dioxus-router/desktop"

+ 20 - 10
packages/core-macro/src/rsx/component.rs

@@ -19,7 +19,7 @@ use quote::{quote, ToTokens, TokenStreamExt};
 use syn::{
     ext::IdentExt,
     parse::{Parse, ParseBuffer, ParseStream},
-    token, Expr, Ident, Result, Token,
+    token, Expr, Ident, LitStr, Result, Token,
 };
 
 pub struct Component {
@@ -135,8 +135,6 @@ impl ToTokens for Component {
             None => quote! { None },
         };
 
-        // #children
-
         tokens.append_all(quote! {
             __cx.component(
                 #name,
@@ -155,7 +153,7 @@ pub struct ComponentField {
 
 enum ContentField {
     ManExpr(Expr),
-
+    Formatted(LitStr),
     OnHandlerRaw(Expr),
 }
 
@@ -163,6 +161,9 @@ impl ToTokens for ContentField {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
         match self {
             ContentField::ManExpr(e) => e.to_tokens(tokens),
+            ContentField::Formatted(s) => tokens.append_all(quote! {
+                __cx.raw_text(format_args_f!(#s)).0
+            }),
             ContentField::OnHandlerRaw(e) => tokens.append_all(quote! {
                 __cx.bump().alloc(#e)
             }),
@@ -175,13 +176,22 @@ impl Parse for ComponentField {
         let name = Ident::parse_any(input)?;
         input.parse::<Token![:]>()?;
 
-        let name_str = name.to_string();
-        let content = if name_str.starts_with("on") {
-            ContentField::OnHandlerRaw(input.parse()?)
-        } else {
-            ContentField::ManExpr(input.parse::<Expr>()?)
-        };
+        if name.to_string().starts_with("on") {
+            let content = ContentField::OnHandlerRaw(input.parse()?);
+            return Ok(Self { name, content });
+        }
+
+        if name.to_string() == "key" {
+            let content = ContentField::ManExpr(input.parse()?);
+            return Ok(Self { name, content });
+        }
+
+        if input.peek(LitStr) && input.peek2(Token![,]) {
+            let content = ContentField::Formatted(input.parse()?);
+            return Ok(Self { name, content });
+        }
 
+        let content = ContentField::ManExpr(input.parse()?);
         Ok(Self { name, content })
     }
 }

+ 1 - 1
packages/core-macro/src/rsx/element.rs

@@ -46,7 +46,7 @@ impl Parse for Element {
 
                 content.parse::<Token![:]>()?;
 
-                if content.peek(LitStr) {
+                if content.peek(LitStr) && content.peek2(Token![,]) {
                     let value = content.parse::<LitStr>()?;
                     attributes.push(ElementAttrNamed {
                         el_name: el_name.clone(),

+ 12 - 16
packages/core/src/diff.rs

@@ -390,10 +390,7 @@ impl<'bump> DiffState<'bump> {
         self.stack.add_child_count(1);
 
         if let Some(cur_scope_id) = self.stack.current_scope() {
-            let scope = self.scopes.get_scope(cur_scope_id).unwrap();
-
             for listener in *listeners {
-                self.attach_listener_to_scope(listener, scope);
                 listener.mounted_node.set(Some(real_id));
                 self.mutations.new_event_listener(listener, cur_scope_id);
             }
@@ -446,9 +443,9 @@ impl<'bump> DiffState<'bump> {
             }
             false => {
                 // track this component internally so we know the right drop order
-                let cur_scope = self.scopes.get_scope(parent_idx).unwrap();
-                let extended = unsafe { std::mem::transmute(vcomponent) };
-                cur_scope.items.borrow_mut().borrowed_props.push(extended);
+                // let cur_scope = self.scopes.get_scope(parent_idx).unwrap();
+                // let extended = unsafe { std::mem::transmute(vcomponent) };
+                // cur_scope.items.borrow_mut().borrowed_props.push(extended);
             }
         }
 
@@ -580,8 +577,6 @@ impl<'bump> DiffState<'bump> {
         //
         // TODO: take a more efficient path than this
         if let Some(cur_scope_id) = self.stack.current_scope() {
-            let scope = self.scopes.get_scope(cur_scope_id).unwrap();
-
             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 {
@@ -590,7 +585,6 @@ impl<'bump> DiffState<'bump> {
                         self.mutations.new_event_listener(new_l, cur_scope_id);
                     }
                     new_l.mounted_node.set(old_l.mounted_node.get());
-                    self.attach_listener_to_scope(new_l, scope);
                 }
             } else {
                 for listener in old.listeners {
@@ -600,7 +594,6 @@ impl<'bump> DiffState<'bump> {
                 for listener in new.listeners {
                     listener.mounted_node.set(Some(root));
                     self.mutations.new_event_listener(listener, cur_scope_id);
-                    self.attach_listener_to_scope(listener, scope);
                 }
             }
         }
@@ -687,6 +680,12 @@ impl<'bump> DiffState<'bump> {
         new: &'bump VComponent<'bump>,
     ) {
         let scope_addr = old.scope.get().unwrap();
+        log::debug!("diff_component_nodes: {:?}", scope_addr);
+
+        if std::ptr::eq(old, new) {
+            log::debug!("skipping component diff - component is the sames");
+            return;
+        }
 
         // Make sure we're dealing with the same component (by function pointer)
         if old.user_fc == new.user_fc {
@@ -728,11 +727,14 @@ impl<'bump> DiffState<'bump> {
 
                 // this should auto drop the previous props
                 self.scopes.run_scope(scope_addr);
+                self.mutations.dirty_scopes.insert(scope_addr);
+
                 self.diff_node(
                     self.scopes.wip_head(scope_addr),
                     self.scopes.fin_head(scope_addr),
                 );
             } else {
+                log::debug!("memoized");
                 // memoization has taken place
                 drop(new_props);
             };
@@ -1293,10 +1295,4 @@ impl<'bump> DiffState<'bump> {
             }
         }
     }
-
-    /// Adds a listener closure to a scope during diff.
-    fn attach_listener_to_scope(&mut self, listener: &'bump Listener<'bump>, scope: &ScopeState) {
-        let long_listener = unsafe { std::mem::transmute(listener) };
-        scope.items.borrow_mut().listeners.push(long_listener)
-    }
 }

+ 40 - 40
packages/core/src/lazynodes.rs

@@ -244,15 +244,15 @@ fn round_to_words(len: usize) -> usize {
 fn it_works() {
     let bump = bumpalo::Bump::new();
 
-    let factory = NodeFactory { bump: &bump };
+    // let factory = NodeFactory { bump: &bump };
 
-    let caller = LazyNodes::new_some(|f| {
-        //
-        f.text(format_args!("hello world!"))
-    });
-    let g = caller.call(factory);
+    // let caller = LazyNodes::new_some(|f| {
+    //     //
+    //     f.text(format_args!("hello world!"))
+    // });
+    // let g = caller.call(factory);
 
-    dbg!(g);
+    // dbg!(g);
 }
 
 #[test]
@@ -260,37 +260,37 @@ fn it_drops() {
     use std::rc::Rc;
     let bump = bumpalo::Bump::new();
 
-    let factory = NodeFactory { bump: &bump };
-
-    struct DropInner {
-        id: i32,
-    }
-    impl Drop for DropInner {
-        fn drop(&mut self) {
-            log::debug!("dropping inner");
-        }
-    }
-    let val = Rc::new(10);
-
-    let caller = {
-        let it = (0..10)
-            .map(|i| {
-                let val = val.clone();
-
-                LazyNodes::new_some(move |f| {
-                    log::debug!("hell closure");
-                    let inner = DropInner { id: i };
-                    f.text(format_args!("hello world {:?}, {:?}", inner.id, val))
-                })
-            })
-            .collect::<Vec<_>>();
-
-        LazyNodes::new_some(|f| {
-            log::debug!("main closure");
-            f.fragment_from_iter(it)
-        })
-    };
-
-    let _ = caller.call(factory);
-    assert_eq!(Rc::strong_count(&val), 1);
+    // let factory = NodeFactory { bump: &bump };
+
+    // struct DropInner {
+    //     id: i32,
+    // }
+    // impl Drop for DropInner {
+    //     fn drop(&mut self) {
+    //         log::debug!("dropping inner");
+    //     }
+    // }
+    // let val = Rc::new(10);
+
+    // let caller = {
+    //     let it = (0..10)
+    //         .map(|i| {
+    //             let val = val.clone();
+
+    //             LazyNodes::new_some(move |f| {
+    //                 log::debug!("hell closure");
+    //                 let inner = DropInner { id: i };
+    //                 f.text(format_args!("hello world {:?}, {:?}", inner.id, val))
+    //             })
+    //         })
+    //         .collect::<Vec<_>>();
+
+    //     LazyNodes::new_some(|f| {
+    //         log::debug!("main closure");
+    //         f.fragment_from_iter(it)
+    //     })
+    // };
+
+    // let _ = caller.call(factory);
+    // assert_eq!(Rc::strong_count(&val), 1);
 }

+ 27 - 6
packages/core/src/nodes.rs

@@ -395,12 +395,16 @@ impl<P> AnyProps for VComponentProps<P> {
 /// This struct adds metadata to the final VNode about listeners, attributes, and children
 #[derive(Copy, Clone)]
 pub struct NodeFactory<'a> {
+    pub(crate) scope: &'a ScopeState,
     pub(crate) bump: &'a Bump,
 }
 
 impl<'a> NodeFactory<'a> {
-    pub fn new(bump: &'a Bump) -> NodeFactory<'a> {
-        NodeFactory { bump }
+    pub fn new(scope: &'a ScopeState) -> NodeFactory<'a> {
+        NodeFactory {
+            scope,
+            bump: &scope.wip_frame().bump,
+        }
     }
 
     #[inline]
@@ -473,6 +477,12 @@ impl<'a> NodeFactory<'a> {
     ) -> VNode<'a> {
         let key = key.map(|f| self.raw_text(f).0);
 
+        let mut items = self.scope.items.borrow_mut();
+        for listener in listeners {
+            let long_listener = unsafe { std::mem::transmute(listener) };
+            items.listeners.push(long_listener);
+        }
+
         VNode::Element(self.bump.alloc(VElement {
             tag: tag_name,
             key,
@@ -511,7 +521,7 @@ impl<'a> NodeFactory<'a> {
     where
         P: Properties + 'a,
     {
-        VNode::Component(self.bump.alloc(VComponent {
+        let vcomp = self.bump.alloc(VComponent {
             key: key.map(|f| self.raw_text(f).0),
             scope: Default::default(),
             can_memoize: P::IS_STATIC,
@@ -528,7 +538,15 @@ impl<'a> NodeFactory<'a> {
                 // the transformation from this specific lifetime to the for<'a> lifetime
                 render_fn: unsafe { std::mem::transmute(component) },
             }))),
-        }))
+        });
+
+        if !P::IS_STATIC {
+            let vcomp = &*vcomp;
+            let vcomp = unsafe { std::mem::transmute(vcomp) };
+            self.scope.items.borrow_mut().borrowed_props.push(vcomp);
+        }
+
+        VNode::Component(vcomp)
     }
 
     pub fn listener(self, event: &'static str, callback: EventHandler<'a>) -> Listener<'a> {
@@ -730,8 +748,11 @@ impl IntoVNode<'_> for Arguments<'_> {
 
 impl<'a> IntoVNode<'a> for &Option<VNode<'a>> {
     fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
-        let r = self.as_ref().map(|f| f.decouple());
-        cx.fragment_from_iter(r)
+        self.as_ref()
+            .map(|f| f.decouple())
+            .unwrap_or(VNode::Placeholder(
+                cx.bump.alloc(VPlaceholder { id: empty_cell() }),
+            ))
     }
 }
 

+ 14 - 2
packages/core/src/scopes.rs

@@ -143,6 +143,7 @@ impl ScopeArena {
 
     // Removes a scope and its descendents from the arena
     pub fn try_remove(&self, id: ScopeId) -> Option<()> {
+        log::debug!("removing scope {:?}", id);
         self.ensure_drop_safety(id);
 
         // Safety:
@@ -188,6 +189,8 @@ impl ScopeArena {
     /// This also makes sure that drop order is consistent and predictable. All resources that rely on being dropped will
     /// be dropped.
     pub(crate) fn ensure_drop_safety(&self, scope_id: ScopeId) {
+        log::debug!("Ensuring drop safety for scope {:?}", scope_id);
+
         if let Some(scope) = self.get_scope(scope_id) {
             let mut items = scope.items.borrow_mut();
 
@@ -217,6 +220,7 @@ impl ScopeArena {
         // Cycle to the next frame and then reset it
         // This breaks any latent references, invalidating every pointer referencing into it.
         // Remove all the outdated listeners
+        log::debug!("Running scope {:?}", id);
         self.ensure_drop_safety(id);
 
         // todo: we *know* that this is aliased by the contents of the scope itself
@@ -278,14 +282,19 @@ impl ScopeArena {
         let nodes = self.nodes.borrow();
         let mut cur_el = Some(element);
 
+        log::debug!("calling listener {:?}, {:?}", event, element);
         let state = Rc::new(BubbleState::new());
 
         while let Some(id) = cur_el.take() {
             if let Some(el) = nodes.get(id.0) {
+                log::debug!("Found valid receiver element");
+
                 let real_el = unsafe { &**el };
                 if let VNode::Element(real_el) = real_el {
                     for listener in real_el.listeners.borrow().iter() {
                         if listener.event == event.name {
+                            log::debug!("Found valid receiver event");
+
                             if state.canceled.get() {
                                 // stop bubbling if canceled
                                 break;
@@ -651,12 +660,14 @@ impl ScopeState {
     ///     rsx!(cx, div { "hello {state.0}" })
     /// }
     /// ```
-    pub fn provide_context<T: 'static>(&self, value: T) {
+    pub fn provide_context<T: 'static>(&self, value: T) -> Rc<T> {
+        let value = Rc::new(value);
         self.shared_contexts
             .borrow_mut()
-            .insert(TypeId::of::<T>(), Rc::new(value))
+            .insert(TypeId::of::<T>(), value.clone())
             .map(|f| f.downcast::<T>().ok())
             .flatten();
+        value
     }
 
     /// Try to retrieve a SharedState with type T from the any parent Scope.
@@ -713,6 +724,7 @@ impl ScopeState {
     ///```
     pub fn render<'src>(&'src self, rsx: LazyNodes<'src, '_>) -> Option<VNode<'src>> {
         Some(rsx.call(NodeFactory {
+            scope: self,
             bump: &self.wip_frame().bump,
         }))
     }

+ 4 - 1
packages/core/src/virtual_dom.rs

@@ -531,6 +531,9 @@ impl VirtualDom {
         diff_state.stack.element_stack.push(ElementId(0));
         diff_state.stack.scope_stack.push(scope_id);
         diff_state.work(|| false);
+        self.dirty_scopes.clear();
+        assert!(self.dirty_scopes.is_empty());
+
         diff_state.mutations
     }
 
@@ -599,7 +602,7 @@ impl VirtualDom {
     pub fn render_vnodes<'a>(&'a self, lazy_nodes: LazyNodes<'a, '_>) -> &'a VNode<'a> {
         let scope = self.scopes.get_scope(ScopeId(0)).unwrap();
         let frame = scope.wip_frame();
-        let factory = NodeFactory { bump: &frame.bump };
+        let factory = NodeFactory::new(scope);
         let node = lazy_nodes.call(factory);
         frame.bump.alloc(node)
     }

+ 29 - 1
packages/core/tests/miri_stress.rs

@@ -97,7 +97,7 @@ fn memo_works_properly() {
 
         cx.render(rsx!(
             div { "Hello, world! {name}" }
-            child(na: "asdfg".to_string())
+            child(na: "asdfg".to_string() )
         ))
     }
 
@@ -285,3 +285,31 @@ fn basic() {
     dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
     dom.work_with_deadline(|| false);
 }
+
+#[test]
+fn leak_thru_children() {
+    fn app(cx: Scope) -> Element {
+        cx.render(rsx! {
+            Child {
+                name: "asd".to_string(),
+            }
+        });
+        cx.render(rsx! {
+            div {}
+        })
+    }
+
+    #[inline_props]
+    fn Child(cx: Scope, name: String) -> Element {
+        rsx!(cx, div { "child {name}" })
+    }
+
+    let mut dom = new_dom(app, ());
+    let _ = dom.rebuild();
+
+    dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
+    dom.work_with_deadline(|| false);
+
+    dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
+    dom.work_with_deadline(|| false);
+}

+ 5 - 4
packages/hooks/src/usestate/owned.rs

@@ -12,8 +12,8 @@ pub struct UseStateOwned<T: 'static> {
 }
 
 impl<T> UseStateOwned<T> {
-    pub fn get(&self) -> Ref<T> {
-        Ref::map(self.wip.borrow(), |x| x.as_ref().unwrap())
+    pub fn get(&self) -> Ref<Option<T>> {
+        self.wip.borrow()
     }
 
     pub fn set(&self, new_val: T) {
@@ -21,8 +21,9 @@ impl<T> UseStateOwned<T> {
         (self.update_callback)();
     }
 
-    pub fn modify(&self) -> RefMut<T> {
-        RefMut::map(self.wip.borrow_mut(), |x| x.as_mut().unwrap())
+    pub fn modify(&self) -> RefMut<Option<T>> {
+        (self.update_callback)();
+        self.wip.borrow_mut()
     }
 }
 

+ 5 - 0
packages/html/src/elements.rs

@@ -1100,6 +1100,11 @@ impl label {
         cx.attr("for", val, None, false)
     }
 }
+impl a {
+    pub fn prevent_default<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
+        cx.attr("dioxus-prevent-default", val, None, false)
+    }
+}
 
 builder_constructors! {
     // SVG components

+ 3 - 0
packages/router/.vscode/settings.json

@@ -0,0 +1,3 @@
+{
+    "rust-analyzer.cargo.allFeatures": true
+}

+ 4 - 2
packages/router/Cargo.toml

@@ -19,6 +19,7 @@ serde = "1"
 url = "2.2.2"
 serde_urlencoded = "0.7"
 
+# for wasm
 web-sys = { version = "0.3", features = [
     "Attr",
     "Document",
@@ -33,11 +34,12 @@ web-sys = { version = "0.3", features = [
 wasm-bindgen = { version = "0.2", optional = true }
 js-sys = { version = "0.3", optional = true }
 gloo = { version = "0.5", optional = true }
+log = "0.4.14"
 
 
 [features]
-default = ["derive"]
-web = ["web-sys"]
+default = ["derive", "web"]
+web = ["web-sys", "gloo", "js-sys", "wasm-bindgen"]
 desktop = []
 mobile = []
 derive = []

+ 17 - 28
packages/router/README.md

@@ -1,40 +1,29 @@
-# Router hook for Dioxus apps
+# Routing for Dioxus App
 
-Dioxus-router provides a use_router hook that returns a different value depending on the route.
-The router is generic over any value, however it makes sense to return a different set of VNodes
-and feed them into the App's return VNodes.
-
-Using the router should feel similar to tide's routing framework where an "address" book is assembled at the head of the app.
-
-Here's an example of how to use the router hook:
+DioxusRouter adds React-Router style routing to your Dioxus apps. Works in browser, SSR, and natively.
 
 ```rust
-#[derive(Clone, PartialEq, Serialize, Deserialize, Routable)]
-enum AppRoute {
-    Home, 
-    Posts,
-    NotFound
+fn app() {
+    cx.render(rsx! {
+        Routes {
+            Route { to: "/", Component {} },
+            Route { to: "/blog", Blog {} },
+            Route { to: "/blog/:id", BlogPost {} },
+        }
+    })
 }
+```
 
-static App: Component = |cx| {
-    let route = use_router(cx, AppRoute::parse);
-    
-    match route {
-        AppRoute::Home => rsx!(cx, Home {})
-        AppRoute::Posts => rsx!(cx, Posts {})
-        AppRoute::Notfound => rsx!(cx, Notfound {})
-    }
-};
+Then, in your route, you can choose to parse the Route any way you want through `use_route`.
+```rust
+let id: usize = use_route(&cx).path("id")?;
+
+let state: CustomState = use_route(&cx).parse()?;
 ```
 
 Adding links into your app:
-
 ```rust
-static Leaf: Component = |cx| {
-    rsx!(cx, div { 
-        Link { to: AppRoute::Home } 
-    })
-}
+Link { to: "id/{id}" }
 ```
 
 Currently, the router is only supported in a web environment, but we plan to add 1st-party support via the context API when new renderers are available.

+ 30 - 21
packages/router/examples/simple.rs

@@ -11,31 +11,40 @@ fn main() {
 }
 
 static APP: Component = |cx| {
-    #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
-    enum Route {
-        Home,
-        About,
-        NotFound,
-    }
-    impl Default for Route {
-        fn default() -> Self {
-            Route::Home
+    cx.render(rsx! {
+        Router {
+            onchange: move |route| log::info!("route changed to {}", route),
+            Route { to: "/", Home {} }
+            Route { to: "blog"
+                Route { to: "/", BlogList {} }
+                Route { to: ":id", BlogPost {} }
+            }
         }
-    }
+    })
+};
 
-    let route = use_router(&cx, |c| {});
+fn Home(cx: Scope) -> Element {
+    cx.render(rsx! {
+        div {
+            h1 { "Home" }
+        }
+    })
+}
 
+fn BlogList(cx: Scope) -> Element {
     cx.render(rsx! {
         div {
-            {match route {
-                Route::Home => rsx!(h1 { "Home" }),
-                Route::About => rsx!(h1 { "About" }),
-                Route::NotFound => rsx!(h1 { "NotFound" }),
-            }}
-            nav {
-                Link { to: Route::Home, href: "/" }
-                Link { to: Route::About, href: "/about" }
-            }
+
         }
     })
-};
+}
+
+fn BlogPost(cx: Scope) -> Element {
+    let id = use_route(&cx).segment::<usize>("id")?;
+
+    cx.render(rsx! {
+        div {
+
+        }
+    })
+}

+ 18 - 7
packages/router/src/link.rs → packages/router/src/components/link.rs

@@ -1,4 +1,4 @@
-use crate::{Routable, RouterService};
+use crate::RouterService;
 use dioxus::Attribute;
 use dioxus_core as dioxus;
 use dioxus_core::prelude::*;
@@ -6,8 +6,8 @@ use dioxus_core_macro::{format_args_f, rsx, Props};
 use dioxus_html as dioxus_elements;
 
 #[derive(Props)]
-pub struct LinkProps<'a, R: Routable> {
-    to: R,
+pub struct LinkProps<'a> {
+    to: &'a str,
 
     /// The url that gets pushed to the history stack
     ///
@@ -28,6 +28,9 @@ pub struct LinkProps<'a, R: Routable> {
     #[props(default, setter(strip_option))]
     class: Option<&'a str>,
 
+    #[props(default, setter(strip_option))]
+    id: Option<&'a str>,
+
     children: Element<'a>,
 
     #[props(default)]
@@ -35,13 +38,21 @@ pub struct LinkProps<'a, R: Routable> {
 }
 
 #[allow(non_snake_case)]
-pub fn Link<'a, R: Routable>(cx: Scope<'a, LinkProps<'a, R>>) -> Element {
-    let service = cx.consume_context::<RouterService<R>>()?;
+pub fn Link<'a>(cx: Scope<'a, LinkProps<'a>>) -> Element {
+    log::debug!("rendering link {:?}", cx.scope_id());
+    let service = cx.consume_context::<RouterService>()?;
     cx.render(rsx! {
         a {
-            href: "#",
+            href: "{cx.props.to}",
             class: format_args!("{}", cx.props.class.unwrap_or("")),
-            onclick: move |_| service.push_route(cx.props.to.clone()),
+            id: format_args!("{}", cx.props.id.unwrap_or("")),
+
+            prevent_default: "onclick",
+            onclick: move |_| {
+                log::debug!("clicked");
+                service.push_route(cx.props.to.clone());
+            },
+
             &cx.props.children
         }
     })

+ 58 - 0
packages/router/src/components/route.rs

@@ -0,0 +1,58 @@
+use dioxus_core::Element;
+
+use dioxus_core as dioxus;
+use dioxus_core::prelude::*;
+use dioxus_core_macro::Props;
+use dioxus_core_macro::*;
+use dioxus_html as dioxus_elements;
+
+use crate::{RouteContext, RouterService};
+
+#[derive(Props)]
+pub struct RouteProps<'a> {
+    to: &'a str,
+    children: Element<'a>,
+}
+
+pub fn Route<'a>(cx: Scope<'a, RouteProps<'a>>) -> Element {
+    // now we want to submit
+    let router_root = cx
+        .use_hook(|_| cx.consume_context::<RouterService>())
+        .as_ref()?;
+
+    cx.use_hook(|_| {
+        // create a bigger, better, longer route if one above us exists
+        let total_route = match cx.consume_context::<RouteContext>() {
+            Some(ctx) => format!("{}", ctx.total_route.clone()),
+            None => format!("{}", cx.props.to.clone()),
+        };
+
+        // provide our route context
+        let route_context = cx.provide_context(RouteContext {
+            declared_route: cx.props.to.to_string(),
+            total_route,
+        });
+
+        // submit our rout
+        router_root.register_total_route(route_context.total_route.clone(), cx.scope_id());
+
+        Some(RouteInner {})
+    });
+
+    log::debug!("rendering route {:?}", cx.scope_id());
+
+    if router_root.should_render(cx.scope_id()) {
+        log::debug!("route {:?} produced nodes", cx.scope_id());
+        cx.render(rsx!(&cx.props.children))
+    } else {
+        None
+    }
+}
+
+struct RouteInner {}
+
+impl Drop for RouteInner {
+    fn drop(&mut self) {
+        // todo!()
+    }
+}

+ 29 - 0
packages/router/src/components/router.rs

@@ -0,0 +1,29 @@
+use dioxus_core::Element;
+
+use dioxus_core as dioxus;
+use dioxus_core::prelude::*;
+use dioxus_core_macro::*;
+use dioxus_html as dioxus_elements;
+
+use crate::RouterService;
+
+#[derive(Props)]
+pub struct RouterProps<'a> {
+    children: Element<'a>,
+
+    #[props(default, setter(strip_option))]
+    onchange: Option<&'a Fn(&'a str)>,
+}
+
+pub fn Router<'a>(cx: Scope<'a, RouterProps<'a>>) -> Element {
+    let p = cx.use_hook(|_| {
+        let update = cx.schedule_update_any();
+        cx.provide_context(RouterService::new(update, cx.scope_id()))
+    });
+
+    log::debug!("rendering router {:?}", cx.scope_id());
+
+    cx.render(rsx!(
+        div { &cx.props.children }
+    ))
+}

+ 30 - 0
packages/router/src/hooks/use_route.rs

@@ -0,0 +1,30 @@
+use dioxus_core::ScopeState;
+
+pub struct UseRoute<'a> {
+    cur_route: String,
+    cx: &'a ScopeState,
+}
+
+impl<'a> UseRoute<'a> {
+    /// Parse the query part of the URL
+    pub fn param<T>(&self, param: &str) -> Option<&T> {
+        todo!()
+    }
+
+    pub fn nth_segment(&self, n: usize) -> Option<&str> {
+        todo!()
+    }
+
+    pub fn last_segment(&self) -> Option<&str> {
+        todo!()
+    }
+
+    /// Parse the segments of the URL, using named parameters (defined in your router)
+    pub fn segment<T>(&self, name: &str) -> Option<&T> {
+        todo!()
+    }
+}
+
+pub fn use_route(cx: &ScopeState) -> UseRoute {
+    todo!()
+}

+ 20 - 25
packages/router/src/lib.rs

@@ -7,17 +7,6 @@
 //!
 //! ```rust
 //! fn app(cx: Scope) -> Element {
-//!     let route = use_router(&cx, |svc, path| {
-//!         match path {
-//!             "/about" => Route::About,
-//!             _ => Route::Home,
-//!         }
-//!     });
-//!
-//!     match route {
-//!         Route::Home => rsx!(cx, h1 { "Home" }),
-//!         Route::About => rsx!(cx, h1 { "About" }),
-//!     }
 //! }
 //!
 //!
@@ -35,22 +24,28 @@
 //!
 //!
 
-mod link;
+mod hooks {
+    mod use_route;
+    pub use use_route::*;
+}
+pub use hooks::*;
+
+mod components {
+    mod router;
+    pub use router::*;
+
+    mod route;
+    pub use route::*;
+
+    mod link;
+    pub use link::*;
+}
+pub use components::*;
+
 mod platform;
+mod routecontext;
 mod service;
-mod userouter;
 mod utils;
 
-pub use link::*;
-use serde::{de::DeserializeOwned, Deserialize, Serialize};
+pub use routecontext::*;
 pub use service::*;
-pub use userouter::*;
-
-pub trait Routable:
-    'static + Send + Clone + PartialEq + Serialize + DeserializeOwned + Default
-{
-}
-impl<T> Routable for T where
-    T: 'static + Send + Clone + PartialEq + Serialize + DeserializeOwned + Default
-{
-}

+ 0 - 7
packages/router/src/platform.rs

@@ -1,7 +0,0 @@
-pub fn parse_current_route() -> String {
-    todo!()
-}
-
-pub(crate) fn get_base_route() -> String {
-    todo!()
-}

+ 11 - 0
packages/router/src/platform/mod.rs

@@ -0,0 +1,11 @@
+use url::Url;
+
+pub trait RouterProvider {
+    fn get_current_route(&self) -> String;
+    fn subscribe_to_route_changes(&self, callback: Box<dyn Fn(String)>);
+}
+
+enum RouteChange {
+    LinkTo(String),
+    Back(String),
+}

+ 7 - 0
packages/router/src/routecontext.rs

@@ -0,0 +1,7 @@
+pub struct RouteContext {
+    // "/name/:id"
+    pub declared_route: String,
+
+    // "app/name/:id"
+    pub total_route: String,
+}

+ 68 - 11
packages/router/src/service.rs

@@ -1,18 +1,75 @@
-use crate::Routable;
-use std::{cell::RefCell, rc::Rc};
+use gloo::history::{BrowserHistory, History, HistoryListener};
+use std::{cell::RefCell, collections::HashMap, rc::Rc};
 
-pub struct RouterService<R: Routable> {
-    pub(crate) regen_route: Rc<dyn Fn()>,
-    pub(crate) pending_routes: RefCell<Vec<R>>,
+use dioxus_core::ScopeId;
+
+pub struct RouterService {
+    pub(crate) regen_route: Rc<dyn Fn(ScopeId)>,
+    history: RefCell<BrowserHistory>,
+    registerd_routes: RefCell<RouteSlot>,
+    slots: RefCell<HashMap<ScopeId, String>>,
+    cur_root: RefCell<String>,
+    listener: HistoryListener,
+}
+
+enum RouteSlot {
+    Routes {
+        // the partial route
+        partial: String,
+
+        // the total route
+        total: String,
+
+        // Connections to other routs
+        rest: Vec<RouteSlot>,
+    },
 }
 
-impl<R: Routable> RouterService<R> {
-    pub fn current_path(&self) -> &str {
-        todo!()
+impl RouterService {
+    pub fn new(regen_route: Rc<dyn Fn(ScopeId)>, root_scope: ScopeId) -> Self {
+        let history = BrowserHistory::default();
+        let location = history.location();
+        let path = location.path();
+
+        let regen = Rc::clone(&regen_route);
+        let listener = history.listen(move || {
+            regen(root_scope);
+        });
+
+        Self {
+            registerd_routes: RefCell::new(RouteSlot::Routes {
+                partial: String::from("/"),
+                total: String::from("/"),
+                rest: Vec::new(),
+            }),
+            history: RefCell::new(history),
+            regen_route,
+            slots: Default::default(),
+            cur_root: RefCell::new(path.to_string()),
+            listener,
+        }
     }
-    pub fn push_route(&self, route: R) {
-        self.pending_routes.borrow_mut().push(route);
-        (self.regen_route)();
+
+    pub fn push_route(&self, route: &str) {
+        self.history.borrow_mut().push(route);
+    }
+
+    pub fn register_total_route(&self, route: String, scope: ScopeId) {
+        self.slots.borrow_mut().insert(scope, route);
+    }
+
+    pub fn should_render(&self, scope: ScopeId) -> bool {
+        let location = self.history.borrow().location();
+        let path = location.path();
+
+        let roots = self.slots.borrow();
+
+        let root = roots.get(&scope);
+
+        match root {
+            Some(r) => r == path,
+            None => false,
+        }
     }
 }
 

+ 0 - 33
packages/router/src/userouter.rs

@@ -1,33 +0,0 @@
-use std::{cell::RefCell, rc::Rc};
-
-use crate::{Routable, RouterCfg, RouterService};
-use dioxus_core::ScopeState;
-
-/// Initialize the app's router service and provide access to `Link` components
-pub fn use_router<'a, R: Routable>(cx: &'a ScopeState, _cfg: impl FnOnce(&mut RouterCfg)) -> &'a R {
-    let router = cx.use_hook(|_| {
-        let svc: RouterService<R> = RouterService {
-            regen_route: cx.schedule_update(),
-            pending_routes: RefCell::new(Vec::new()),
-        };
-        let first_path = R::default();
-        cx.provide_context(svc);
-        UseRouterInner {
-            svc: cx.consume_context::<RouterService<R>>().unwrap(),
-            history: vec![first_path],
-        }
-    });
-
-    let mut pending_routes = router.svc.pending_routes.borrow_mut();
-
-    for route in pending_routes.drain(..) {
-        router.history.push(route);
-    }
-
-    router.history.last().unwrap()
-}
-
-struct UseRouterInner<R: Routable> {
-    svc: Rc<RouterService<R>>,
-    history: Vec<R>,
-}

+ 15 - 1
packages/web/src/dom.rs

@@ -274,7 +274,21 @@ impl WebsysDom {
                 // "Result" cannot be received from JS
                 // Instead, we just build and immediately execute a closure that returns result
                 match decode_trigger(event) {
-                    Ok(synthetic_event) => trigger.as_ref()(SchedulerMsg::Event(synthetic_event)),
+                    Ok(synthetic_event) => {
+                        let target = event.target().unwrap();
+                        if let Some(node) = target.dyn_ref::<HtmlElement>() {
+                            if let Some(name) = node.get_attribute("dioxus-prevent-default") {
+                                if name == synthetic_event.name
+                                    || name.trim_start_matches("on") == synthetic_event.name
+                                {
+                                    log::debug!("Preventing default");
+                                    event.prevent_default();
+                                }
+                            }
+                        }
+
+                        trigger.as_ref()(SchedulerMsg::Event(synthetic_event))
+                    }
                     Err(e) => log::error!("Error decoding Dioxus event attribute. {:#?}", e),
                 };
             });

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

@@ -157,6 +157,7 @@ pub async fn run_with_props<T: 'static + Send>(root: Component<T>, root_props: T
 
     let mut websys_dom = dom::WebsysDom::new(root_el, cfg, sender_callback);
 
+    log::debug!("rebuilding app");
     let mut mutations = dom.rebuild();
 
     // hydrating is simply running the dom for a single render. If the page is already written, then the corresponding
@@ -168,10 +169,13 @@ pub async fn run_with_props<T: 'static + Send>(root: Component<T>, root_props: T
     let work_loop = ric_raf::RafLoop::new();
 
     loop {
+        log::debug!("waiting for work");
         // if virtualdom has nothing, wait for it to have something before requesting idle time
         // if there is work then this future resolves immediately.
         dom.wait_for_work().await;
 
+        log::debug!("working..");
+
         // wait for the mainthread to schedule us in
         let mut deadline = work_loop.wait_for_idle_time().await;