wip: memoize dom in the prescence of identical components

This commit is contained in:
Jonathan Kelley committed 2022-01-05 00:27:22 -05:00
1 parent 7cf15ee4e8
commit cb2782b4bb
27 files changed
+474 -219

No files matched your search

+2 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)
}
}
+35 -35
View File
@@ -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 };
// 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);
// 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();
// 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(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)
})
};
// 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 _ = caller.call(factory);
// assert_eq!(Rc::strong_count(&val), 1);
}
+27 -6
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1,3 @@
{
"rust-analyzer.cargo.allFeatures": true
}
+4 -2
View File
@@ -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 = []
+21 -32
View File
@@ -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
}
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 {})
}
};
```
Adding links into your app:
```rust
static Leaf: Component = |cx| {
rsx!(cx, div {
Link { to: AppRoute::Home }
fn app() {
cx.render(rsx! {
Routes {
Route { to: "/", Component {} },
Route { to: "/blog", Blog {} },
Route { to: "/blog/:id", BlogPost {} },
}
})
}
```
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
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.
+32 -23
View File
@@ -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
}
}
let route = use_router(&cx, |c| {});
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" }
Router {
onchange: move |route| log::info!("route changed to {}", route),
Route { to: "/", Home {} }
Route { to: "blog"
Route { to: "/", BlogList {} }
Route { to: ":id", BlogPost {} }
}
}
})
};
fn Home(cx: Scope) -> Element {
cx.render(rsx! {
div {
h1 { "Home" }
}
})
}
fn BlogList(cx: Scope) -> Element {
cx.render(rsx! {
div {
}
})
}
fn BlogPost(cx: Scope) -> Element {
let id = use_route(&cx).segment::<usize>("id")?;
cx.render(rsx! {
div {
}
})
}
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
{
}
-7
View File
@@ -1,7 +0,0 @@
pub fn parse_current_route() -> String {
todo!()
}
pub(crate) fn get_base_route() -> String {
todo!()
}
+11
View File
@@ -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
View File
@@ -0,0 +1,7 @@
pub struct RouteContext {
// "/name/:id"
pub declared_route: String,
// "app/name/:id"
pub total_route: String,
}
+68 -11
View File
@@ -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,
}
impl<R: Routable> RouterService<R> {
pub fn current_path(&self) -> &str {
todo!()
enum RouteSlot {
Routes {
// the partial route
partial: String,
// the total route
total: String,
// Connections to other routs
rest: Vec<RouteSlot>,
},
}
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,
}
}
}
-33
View File
@@ -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
View File
@@ -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
View File
@@ -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;