From a7b11529109704b8cd4051bb73c446adc52b4592 Mon Sep 17 00:00:00 2001 From: Greg Johnston Date: Sun, 5 May 2024 15:21:06 -0400 Subject: [PATCH] initial async routing work (to support bundle splitting) --- router/Cargo.toml | 1 + router/src/components.rs | 22 ++-- router/src/flat_router.rs | 160 +++++++++++++++++------- router/src/location/history.rs | 33 ++++- router/src/location/mod.rs | 6 +- router/src/matching/choose_view.rs | 30 ++--- router/src/matching/nested/mod.rs | 20 +-- router/src/nested_router.rs | 190 +++++++++++++++++++++-------- tachys/src/async_views/mod.rs | 28 ----- tachys/src/view/either.rs | 4 +- tachys/src/view/mod.rs | 24 +++- 11 files changed, 355 insertions(+), 163 deletions(-) diff --git a/router/Cargo.toml b/router/Cargo.toml index d55cf73eb..2e49d42d9 100644 --- a/router/Cargo.toml +++ b/router/Cargo.toml @@ -16,6 +16,7 @@ either_of = { workspace = true } or_poisoned = { workspace = true } reactive_graph = { workspace = true } tachys = { workspace = true, features = ["reactive_graph"] } +futures = "0.3" url = "2" js-sys = { version = "0.3" } wasm-bindgen = { version = "0.2" } diff --git a/router/src/components.rs b/router/src/components.rs index d8e999df4..bb568c8de 100644 --- a/router/src/components.rs +++ b/router/src/components.rs @@ -79,6 +79,7 @@ where let location = BrowserUrl::new().expect("could not access browser navigation"); // TODO options here location.init(base.clone()); + provide_context(location.clone()); location.as_url().clone() }; // provide router context @@ -199,6 +200,7 @@ where FallbackFn: Fn() -> Fallback + Send + 'static, Fallback: IntoView + 'static, { + let location = use_context::(); let RouterContext { current_url, base, .. } = use_context() @@ -220,6 +222,7 @@ where let outer_owner = Owner::current().expect("creating Routes, but no Owner was found"); move || NestedRoutesView { + location: location.clone(), routes: routes.clone(), outer_owner: outer_owner.clone(), url: current_url.clone(), @@ -241,8 +244,7 @@ where FallbackFn: Fn() -> Fallback + Send + 'static, Fallback: IntoView + 'static, { - use either_of::Either; - + let location = use_context::(); let RouterContext { current_url, base, .. } = use_context() @@ -264,12 +266,16 @@ where let outer_owner = Owner::current().expect("creating Router, but no Owner was found"); let params = ArcRwSignal::new(ParamsMap::new()); - move || FlatRoutesView { - routes: routes.clone(), - path: path.clone(), - fallback: fallback(), - outer_owner: outer_owner.clone(), - params: params.clone(), + move || { + path.track(); + FlatRoutesView { + location: location.clone(), + routes: routes.clone(), + path: path.clone(), + fallback: fallback(), + outer_owner: outer_owner.clone(), + params: params.clone(), + } } } diff --git a/router/src/flat_router.rs b/router/src/flat_router.rs index 47425b0a9..a5bdaecf8 100644 --- a/router/src/flat_router.rs +++ b/router/src/flat_router.rs @@ -1,25 +1,28 @@ use crate::{ - location::{Location, RequestUrl, Url}, + location::{Location, LocationProvider, RequestUrl, Url}, matching::Routes, params::ParamsMap, resolve_path::resolve_path, ChooseView, MatchInterface, MatchNestedRoutes, MatchParams, Method, PathSegment, RouteList, RouteListing, RouteMatchId, }; -use either_of::Either; +use any_spawner::Executor; +use either_of::{Either, EitherFuture, EitherOf3}; use leptos::{component, oco::Oco, IntoView}; use or_poisoned::OrPoisoned; use reactive_graph::{ - computed::{ArcMemo, Memo}, + computed::{ArcMemo, Memo, ScopedFuture}, owner::{provide_context, use_context, Owner}, signal::{ArcRwSignal, ArcTrigger}, - traits::{Get, Read, ReadUntracked, Set, Track, Trigger}, + traits::{Get, GetUntracked, Read, ReadUntracked, Set, Track, Trigger}, }; use std::{ borrow::Cow, + cell::RefCell, iter, marker::PhantomData, mem, + rc::Rc, sync::{ mpsc::{self, Receiver, Sender}, Arc, Mutex, @@ -37,7 +40,8 @@ use tachys::{ }, }; -pub(crate) struct FlatRoutesView { +pub(crate) struct FlatRoutesView { + pub location: Option, pub routes: Routes, pub path: ArcMemo, pub fallback: Fal, @@ -45,13 +49,14 @@ pub(crate) struct FlatRoutesView { pub params: ArcRwSignal, } -impl FlatRoutesView +impl FlatRoutesView where + Loc: LocationProvider, Defs: MatchNestedRoutes, Fal: Render, R: Renderer + 'static, { - pub fn choose( + pub async fn choose( self, ) -> Either>::View> { let FlatRoutesView { @@ -60,60 +65,121 @@ where fallback, outer_owner, params, + .. } = self; - outer_owner.with(|| { - provide_context(params.clone().read_only()); - let new_match = routes.match_route(&path.read()); - match new_match { - None => Either::Left(fallback), - Some(matched) => { - let new_params = - matched.to_params().into_iter().collect::(); - params.set(new_params); - let (view, child) = matched.into_view_and_child(); + outer_owner + .with(|| { + provide_context(params.clone().read_only()); + let new_match = routes.match_route(&path.read()); + match new_match { + None => EitherFuture::Left { + inner: async move { fallback }, + }, + Some(matched) => { + let new_params = matched + .to_params() + .into_iter() + .collect::(); + params.set(new_params); + let (view, child) = matched.into_view_and_child(); - #[cfg(debug_assertions)] - if child.is_some() { - panic!( - " should not be used with nested \ - routes." - ); + #[cfg(debug_assertions)] + if child.is_some() { + panic!( + " should not be used with nested \ + routes." + ); + } + + EitherFuture::Right { + inner: ScopedFuture::new(view.choose()), + } } - - let view = view.choose(); - Either::Right(view) } - } - }) + }) + .await } } -impl Render for FlatRoutesView +impl Render for FlatRoutesView where - Defs: MatchNestedRoutes, - Fal: Render, + Loc: LocationProvider, + Defs: MatchNestedRoutes + 'static, + Fal: Render + 'static, R: Renderer + 'static, { - type State = >::View> as Render>::State; + type State = Rc< + RefCell< + // TODO loading indicator + >::View> as Render< + R, + >>::State, + >, + >; fn build(self) -> Self::State { - self.choose().build() + let state = Rc::new(RefCell::new(EitherOf3::A(()).build())); + let spawned_path = self.path.get_untracked(); + let current_path = self.path.clone(); + let location = self.location.clone(); + let route = self.choose(); + Executor::spawn_local({ + let state = Rc::clone(&state); + async move { + let loaded_route = route.await; + // only update the route if it's still the current path + // i.e., if we've navigated away before this has loaded, do nothing + if &spawned_path == &*current_path.read_untracked() { + let new_view = match loaded_route { + Either::Left(i) => EitherOf3::B(i), + Either::Right(i) => EitherOf3::C(i), + }; + new_view.rebuild(&mut state.borrow_mut()); + if let Some(location) = location { + location.ready_to_complete(); + } + } + } + }); + state } fn rebuild(self, state: &mut Self::State) { - self.choose().rebuild(state); + let spawned_path = self.path.get_untracked(); + let current_path = self.path.clone(); + let location = self.location.clone(); + let route = self.choose(); + Executor::spawn_local({ + let state = Rc::clone(&*state); + async move { + let loaded_route = route.await; + // only update the route if it's still the current path + // i.e., if we've navigated away before this has loaded, do nothing + if &spawned_path == &*current_path.read_untracked() { + let new_view = match loaded_route { + Either::Left(i) => EitherOf3::B(i), + Either::Right(i) => EitherOf3::C(i), + }; + new_view.rebuild(&mut state.borrow_mut()); + if let Some(location) = location { + location.ready_to_complete(); + } + } + } + }); } } -impl AddAnyAttr for FlatRoutesView +impl AddAnyAttr for FlatRoutesView where - Defs: MatchNestedRoutes + Send, - Fal: RenderHtml, + Loc: LocationProvider + Send, + Defs: MatchNestedRoutes + Send + 'static, + Fal: RenderHtml + 'static, R: Renderer + 'static, { type Output> = - FlatRoutesView; + FlatRoutesView; fn add_any_attr>( self, @@ -126,10 +192,11 @@ where } } -impl RenderHtml for FlatRoutesView +impl RenderHtml for FlatRoutesView where - Defs: MatchNestedRoutes + Send, - Fal: RenderHtml, + Loc: LocationProvider + Send, + Defs: MatchNestedRoutes + Send + 'static, + Fal: RenderHtml + 'static, R: Renderer + 'static, { type AsyncOutput = Self; @@ -190,7 +257,8 @@ where RouteList::register(RouteList::from(routes)); } else { - self.choose().to_html_with_buf(buf, position); + todo!() + // self.choose().to_html_with_buf(buf, position); } } @@ -201,8 +269,9 @@ where ) where Self: Sized, { - self.choose() - .to_html_async_with_buf::(buf, position); + todo!() + // self.choose() + // .to_html_async_with_buf::(buf, position); } fn hydrate( @@ -210,6 +279,7 @@ where cursor: &Cursor, position: &PositionState, ) -> Self::State { - self.choose().hydrate::(cursor, position) + todo!() + // self.choose().hydrate::(cursor, position) } } diff --git a/router/src/location/history.rs b/router/src/location/history.rs index 9c8ad3a0a..1f5f904ec 100644 --- a/router/src/location/history.rs +++ b/router/src/location/history.rs @@ -3,9 +3,18 @@ use super::{ }; use crate::{navigate::UseNavigate, params::ParamsMap}; use core::fmt; +use futures::channel::oneshot; use js_sys::{try_iter, Array, JsString, Reflect}; +use or_poisoned::OrPoisoned; use reactive_graph::{signal::ArcRwSignal, traits::Set}; -use std::{borrow::Cow, boxed::Box, rc::Rc, string::String}; +use std::{ + borrow::Cow, + boxed::Box, + cell::RefCell, + rc::Rc, + string::String, + sync::{Arc, Mutex}, +}; use tachys::dom::{document, window}; use wasm_bindgen::{closure::Closure, JsCast, JsValue}; use web_sys::{Event, HtmlAnchorElement, MouseEvent, UrlSearchParams}; @@ -13,6 +22,7 @@ use web_sys::{Event, HtmlAnchorElement, MouseEvent, UrlSearchParams}; #[derive(Clone)] pub struct BrowserUrl { url: ArcRwSignal, + pending_navigation: Arc>>>, } impl fmt::Debug for BrowserUrl { @@ -49,7 +59,11 @@ impl LocationProvider for BrowserUrl { fn new() -> Result { let url = ArcRwSignal::new(Self::current()?); - Ok(Self { url }) + let pending_navigation = Default::default(); + Ok(Self { + url, + pending_navigation, + }) } fn as_url(&self) -> &ArcRwSignal { @@ -94,10 +108,17 @@ impl LocationProvider for BrowserUrl { let window = window(); let navigate = { let url = self.url.clone(); + let pending = Arc::clone(&self.pending_navigation); move |new_url, loc| { + let (tx, rx) = oneshot::channel::<()>(); + *pending.lock().or_poisoned() = Some(tx); url.set(new_url); async move { - Self::complete_navigation(&loc); + // if it has been canceled, ignore + // otherwise, complete navigation -- i.e., set URL in address bar + if rx.await.is_ok() { + Self::complete_navigation(&loc); + } } } }; @@ -146,6 +167,12 @@ impl LocationProvider for BrowserUrl { .expect("couldn't add `popstate` listener to `window`"); } + fn ready_to_complete(&self) { + if let Some(tx) = self.pending_navigation.lock().or_poisoned().take() { + tx.send(()); + } + } + fn complete_navigation(loc: &LocationChange) { let history = window().history().unwrap(); diff --git a/router/src/location/mod.rs b/router/src/location/mod.rs index 161d88cd3..d6ca48cdf 100644 --- a/router/src/location/mod.rs +++ b/router/src/location/mod.rs @@ -114,7 +114,7 @@ impl Default for LocationChange { } } -pub trait LocationProvider: Sized { +pub trait LocationProvider: Clone + 'static { type Error: Debug; fn new() -> Result; @@ -126,6 +126,10 @@ pub trait LocationProvider: Sized { /// Sets up any global event listeners or other initialization needed. fn init(&self, base: Option>); + /// Should be called after a navigation when all route components and data have been loaded and + /// the URL can be updated. + fn ready_to_complete(&self); + /// Update the browser's history to reflect a new location. fn complete_navigation(loc: &LocationChange); diff --git a/router/src/matching/choose_view.rs b/router/src/matching/choose_view.rs index 0cfddb43a..97d259e72 100644 --- a/router/src/matching/choose_view.rs +++ b/router/src/matching/choose_view.rs @@ -1,4 +1,5 @@ use either_of::*; +use std::future::Future; use tachys::{renderer::Renderer, view::Render}; pub trait ChooseView @@ -6,21 +7,22 @@ where Self: Send + 'static, R: Renderer + 'static, { - type Output: Render + Send; + type Output; - fn choose(self) -> Self::Output; + fn choose(self) -> impl Future; } -impl ChooseView for F +impl ChooseView for F where - F: Fn() -> View + Send + 'static, - View: Render + Send, + F: Fn() -> ViewFut + Send + 'static, + ViewFut: Future, + ViewFut::Output: Render + Send, R: Renderer + 'static, { - type Output = View; + type Output = ViewFut::Output; - fn choose(self) -> Self::Output { - self() + async fn choose(self) -> Self::Output { + self().await } } @@ -30,7 +32,7 @@ where { type Output = (); - fn choose(self) -> Self::Output {} + async fn choose(self) -> Self::Output {} } impl ChooseView for Either @@ -41,10 +43,10 @@ where { type Output = Either; - fn choose(self) -> Self::Output { + async fn choose(self) -> Self::Output { match self { - Either::Left(f) => Either::Left(f.choose()), - Either::Right(f) => Either::Right(f.choose()), + Either::Left(f) => Either::Left(f.choose().await), + Either::Right(f) => Either::Right(f.choose().await), } } } @@ -58,9 +60,9 @@ macro_rules! tuples { { type Output = $either<$($ty::Output,)*>; - fn choose(self ) -> Self::Output { + async fn choose(self ) -> Self::Output { match self { - $($either::$ty(f) => $either::$ty(f.choose()),)* + $($either::$ty(f) => $either::$ty(f.choose().await),)* } } } diff --git a/router/src/matching/nested/mod.rs b/router/src/matching/nested/mod.rs index 05b0435f3..4a1265b62 100644 --- a/router/src/matching/nested/mod.rs +++ b/router/src/matching/nested/mod.rs @@ -4,7 +4,7 @@ use super::{ }; use crate::{ChooseView, MatchParams, SsrMode, GeneratedRouteData}; use core::{fmt, iter}; -use std::{borrow::Cow, marker::PhantomData, sync::atomic::{AtomicU16, Ordering}}; +use std::{borrow::Cow, marker::PhantomData, sync::atomic::{AtomicU16, Ordering}, future::Future}; use either_of::Either; use tachys::{ renderer::Renderer, @@ -118,16 +118,17 @@ where } } -impl MatchInterface +impl MatchInterface for NestedMatch where Rndr: Renderer + 'static, Child: MatchInterface + MatchParams + 'static, - ViewFn: Fn() -> View + Send + 'static, - View: Render + RenderHtml + Send + 'static, + ViewFn: Fn() -> ViewFut + Send + 'static, + ViewFut: Future, + ViewFut::Output: Render + RenderHtml + Send + 'static, { type Child = Child; - type View = ViewFn::Output; + type View = ViewFut::Output; fn as_id(&self) -> RouteMatchId { self.id @@ -147,7 +148,7 @@ where } } -impl MatchNestedRoutes +impl MatchNestedRoutes for NestedRoute where Self: 'static, @@ -159,11 +160,12 @@ where Children::Match: MatchParams, Children: 'static, ::Params: Clone, - ViewFn: Fn() -> View + Send + Clone + 'static, - View: Render + RenderHtml + Send + 'static, + ViewFn: Fn() -> ViewFut + Send + Clone + 'static, + ViewFut: Future, + ViewFut::Output: Render + RenderHtml + Send + 'static, { type Data = Data; - type View = View; + type View = ViewFut::Output; type Match = NestedMatch::IntoIter, Either { rndr: PhantomData, } -pub(crate) struct NestedRoutesView { +pub(crate) struct NestedRoutesView { + pub location: Option, pub routes: Routes, pub outer_owner: Owner, pub url: ArcRwSignal, @@ -62,15 +68,18 @@ where path: ArcMemo, search_params: ArcMemo, outlets: Vec>, - view: EitherState, R>, + // TODO loading fallback + view: Rc, R>>>, } -impl Render for NestedRoutesView +impl Render for NestedRoutesView where + Loc: LocationProvider, Defs: MatchNestedRoutes, - Fal: Render, + Fal: Render + 'static, R: Renderer + 'static, { + // TODO support fallback while loading type State = NestedRouteViewState; fn build(self) -> Self::State { @@ -85,20 +94,41 @@ where .. } = self; + let mut loaders = Vec::new(); let mut outlets = Vec::new(); let new_match = routes.match_route(&path.read()); - let view = match new_match { - None => Either::Left(fallback), + + // start with an empty view because we'll be loading routes async + let view = EitherOf3::A(()).build(); + let view = Rc::new(RefCell::new(view)); + let matched_view = match new_match { + None => EitherOf3::B(fallback), Some(route) => { - route.build_nested_route(base, &mut outlets, &outer_owner); + route.build_nested_route( + base, + &mut loaders, + &mut outlets, + &outer_owner, + ); outer_owner.with(|| { - Either::Right( + EitherOf3::C( Outlet(OutletProps::builder().build()).into_any(), ) }) } - } - .build(); + }; + + Executor::spawn_local({ + let view = Rc::clone(&view); + let loaders = mem::take(&mut loaders); + async move { + let triggers = join_all(loaders).await; + for trigger in triggers { + trigger.trigger(); + } + matched_view.rebuild(&mut *view.borrow_mut()); + } + }); NestedRouteViewState { outlets, @@ -115,25 +145,40 @@ where match new_match { None => { - Either::>::Left(self.fallback) - .rebuild(&mut state.view); + EitherOf3::<(), Fal, AnyView>::B(self.fallback) + .rebuild(&mut state.view.borrow_mut()); state.outlets.clear(); } Some(route) => { + let mut loaders = Vec::new(); route.rebuild_nested_route( self.base, &mut 0, + &mut loaders, &mut state.outlets, &self.outer_owner, ); + // hmm... + let location = self.location.clone(); + Executor::spawn_local(async move { + let triggers = join_all(loaders).await; + // tell each one of the outlet triggers that it's ready + for trigger in triggers { + trigger.trigger(); + } + if let Some(loc) = location { + loc.ready_to_complete(); + } + }); + // if it was on the fallback, show the view instead - if matches!(state.view.state, Either::Left(_)) { + if matches!(state.view.borrow().state, EitherOf3::B(_)) { self.outer_owner.with(|| { - Either::>::Right( + EitherOf3::<(), Fal, AnyView>::C( Outlet(OutletProps::builder().build()).into_any(), ) - .rebuild(&mut state.view); + .rebuild(&mut *state.view.borrow_mut()); }) } } @@ -141,14 +186,15 @@ where } } -impl AddAnyAttr for NestedRoutesView +impl AddAnyAttr for NestedRoutesView where + Loc: LocationProvider + Send, Defs: MatchNestedRoutes + Send, - Fal: RenderHtml, + Fal: RenderHtml + 'static, R: Renderer + 'static, { type Output> = - NestedRoutesView; + NestedRoutesView; fn add_any_attr>( self, @@ -161,10 +207,11 @@ where } } -impl RenderHtml for NestedRoutesView +impl RenderHtml for NestedRoutesView where + Loc: LocationProvider + Send, Defs: MatchNestedRoutes + Send, - Fal: RenderHtml, + Fal: RenderHtml + 'static, R: Renderer + 'static, { type AsyncOutput = Self; @@ -238,7 +285,13 @@ where let view = match new_match { None => Either::Left(fallback), Some(route) => { - route.build_nested_route(base, &mut outlets, &outer_owner); + route.build_nested_route( + base, + // TODO loaders here + &mut Vec::new(), + &mut outlets, + &outer_owner, + ); outer_owner.with(|| { Either::Right( Outlet(OutletProps::builder().build()).into_any(), @@ -273,7 +326,13 @@ where let view = match new_match { None => Either::Left(fallback), Some(route) => { - route.build_nested_route(base, &mut outlets, &outer_owner); + route.build_nested_route( + base, + // TODO loaders + &mut Vec::new(), + &mut outlets, + &outer_owner, + ); outer_owner.with(|| { Either::Right( Outlet(OutletProps::builder().build()).into_any(), @@ -302,18 +361,26 @@ where let mut outlets = Vec::new(); let new_match = routes.match_route(&path.read()); - let view = match new_match { - None => Either::Left(fallback), - Some(route) => { - route.build_nested_route(base, &mut outlets, &outer_owner); - outer_owner.with(|| { - Either::Right( - Outlet(OutletProps::builder().build()).into_any(), - ) - }) + let view = Rc::new(RefCell::new( + match new_match { + None => EitherOf3::B(fallback), + Some(route) => { + route.build_nested_route( + base, + // TODO loaders in hydration + &mut Vec::new(), + &mut outlets, + &outer_owner, + ); + outer_owner.with(|| { + EitherOf3::C( + Outlet(OutletProps::builder().build()).into_any(), + ) + }) + } } - } - .hydrate::(cursor, position); + .hydrate::(cursor, position), + )); NestedRouteViewState { outlets, @@ -378,6 +445,7 @@ where fn build_nested_route( self, base: Option>, + loaders: &mut Vec>>>, outlets: &mut Vec>, parent: &Owner, ); @@ -386,6 +454,7 @@ where self, base: Option>, items: &mut usize, + loaders: &mut Vec>>>, outlets: &mut Vec>, parent: &Owner, ); @@ -399,6 +468,7 @@ where fn build_nested_route( self, base: Option>, + loaders: &mut Vec>>>, outlets: &mut Vec>, parent: &Owner, ) { @@ -428,7 +498,7 @@ where // add this outlet to the end of the outlet stack used for diffing let outlet = RouteContext { id: self.as_id(), - trigger, + trigger: trigger.clone(), params, owner: owner.clone(), matched: ArcRwSignal::new(self.as_matched().to_string()), @@ -441,9 +511,14 @@ where // send the initial view through the channel, and recurse through the children let (view, child) = self.into_view_and_child(); - tx.send(Box::new({ + loaders.push(Box::pin({ let owner = outlet.owner.clone(); - move || owner.with(|| view.choose().into_any()) + async move { + let view = + owner.with(|| ScopedFuture::new(view.choose())).await; + tx.send(Box::new(move || owner.with(|| view.into_any()))); + trigger + } })); // and share the outlet with the parent via context @@ -455,7 +530,7 @@ where // this is important because to build the view, we need access to the outlet // and the outlet will be returned from building this child if let Some(child) = child { - child.build_nested_route(base, outlets, &owner); + child.build_nested_route(base, loaders, outlets, &owner); } } @@ -463,6 +538,7 @@ where self, base: Option>, items: &mut usize, + loaders: &mut Vec>>>, outlets: &mut Vec>, parent: &Owner, ) { @@ -470,7 +546,7 @@ where match current { // if there's nothing currently in the routes at this point, build from here None => { - self.build_nested_route(base, outlets, parent); + self.build_nested_route(base, loaders, outlets, parent); } Some(current) => { // a unique ID for each route, which allows us to compare when we get new matches @@ -514,13 +590,21 @@ where // send the new view, with the new owner, through the channel to the Outlet, // and notify the trigger so that the reactive view inside the Outlet tracking // the trigger runs again - current.tx.send({ + loaders.push(Box::pin({ let owner = owner.clone(); - Box::new(move || { - owner.with(|| view.choose().into_any()) - }) - }); - current.trigger.trigger(); + let trigger = current.trigger.clone(); + let tx = current.tx.clone(); + async move { + let view = owner + .with(|| ScopedFuture::new(view.choose())) + .await; + tx.send(Box::new(move || { + owner.with(|| view.into_any()) + })); + drop(old_owner); + trigger + } + })); // remove all the items lower in the tree // if this match is different, all its children will also be different @@ -531,6 +615,7 @@ where let mut new_outlets = Vec::new(); child.build_nested_route( base, + loaders, &mut new_outlets, &owner, ); @@ -545,7 +630,9 @@ where if let Some(child) = child { let owner = current.owner.clone(); *items += 1; - child.rebuild_nested_route(base, items, outlets, &owner); + child.rebuild_nested_route( + base, items, loaders, outlets, &owner, + ); } } } @@ -597,7 +684,6 @@ where ); move || { trigger.track(); - - rx.try_recv().map(|view| view()).unwrap() + rx.try_recv().map(|view| view()) } } diff --git a/tachys/src/async_views/mod.rs b/tachys/src/async_views/mod.rs index 517b3f91b..e9f1ff322 100644 --- a/tachys/src/async_views/mod.rs +++ b/tachys/src/async_views/mod.rs @@ -305,31 +305,3 @@ where self.write().insert_before_this(parent, child) } } - -impl Mountable - for Rc>> -where - Fal: Mountable, - Output: Mountable, - Rndr: Renderer, -{ - fn unmount(&mut self) { - self.borrow_mut().unmount(); - } - - fn mount( - &mut self, - parent: &::Element, - marker: Option<&::Node>, - ) { - self.borrow_mut().mount(parent, marker); - } - - fn insert_before_this( - &self, - parent: &::Element, - child: &mut dyn Mountable, - ) -> bool { - self.borrow_mut().insert_before_this(parent, child) - } -} diff --git a/tachys/src/view/either.rs b/tachys/src/view/either.rs index 3b82ca76f..3833c423b 100644 --- a/tachys/src/view/either.rs +++ b/tachys/src/view/either.rs @@ -425,8 +425,8 @@ macro_rules! tuples { $($ty: Render,)* Rndr: Renderer { - state: []<$($ty::State,)*>, - marker: Rndr::Placeholder, + pub state: []<$($ty::State,)*>, + pub marker: Rndr::Placeholder, } impl<$($ty,)* Rndr> Mountable for []<$($ty,)* Rndr> diff --git a/tachys/src/view/mod.rs b/tachys/src/view/mod.rs index ba6b559cc..99238cc7f 100644 --- a/tachys/src/view/mod.rs +++ b/tachys/src/view/mod.rs @@ -1,7 +1,7 @@ use self::add_attr::AddAnyAttr; use crate::{hydration::Cursor, renderer::Renderer, ssr::StreamBuilder}; use parking_lot::RwLock; -use std::{future::Future, sync::Arc}; +use std::{cell::RefCell, future::Future, rc::Rc, sync::Arc}; pub mod add_attr; pub mod any_view; @@ -256,6 +256,28 @@ where } } +impl Mountable for Rc> +where + T: Mountable, + R: Renderer, +{ + fn unmount(&mut self) { + self.borrow_mut().unmount() + } + + fn mount(&mut self, parent: &R::Element, marker: Option<&R::Node>) { + self.borrow_mut().mount(parent, marker); + } + + fn insert_before_this( + &self, + parent: &::Element, + child: &mut dyn Mountable, + ) -> bool { + self.borrow().insert_before_this(parent, child) + } +} + /// Allows data to be added to a static template. pub trait ToTemplate { const TEMPLATE: &'static str = "";