initial async routing work (to support bundle splitting)

This commit is contained in:
Greg Johnston 2024-05-05 15:21:06 -04:00
parent cfba7a2797
commit a7b1152910
11 changed files with 355 additions and 163 deletions

View file

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

View file

@ -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::<BrowserUrl>();
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::<BrowserUrl>();
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(),
}
}
}

View file

@ -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<Defs, Fal, R> {
pub(crate) struct FlatRoutesView<Loc, Defs, Fal, R> {
pub location: Option<Loc>,
pub routes: Routes<Defs, R>,
pub path: ArcMemo<String>,
pub fallback: Fal,
@ -45,13 +49,14 @@ pub(crate) struct FlatRoutesView<Defs, Fal, R> {
pub params: ArcRwSignal<ParamsMap>,
}
impl<Defs, Fal, R> FlatRoutesView<Defs, Fal, R>
impl<Loc, Defs, Fal, R> FlatRoutesView<Loc, Defs, Fal, R>
where
Loc: LocationProvider,
Defs: MatchNestedRoutes<R>,
Fal: Render<R>,
R: Renderer + 'static,
{
pub fn choose(
pub async fn choose(
self,
) -> Either<Fal, <Defs::Match as MatchInterface<R>>::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::<ParamsMap>();
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::<ParamsMap>();
params.set(new_params);
let (view, child) = matched.into_view_and_child();
#[cfg(debug_assertions)]
if child.is_some() {
panic!(
"<FlatRoutes> should not be used with nested \
routes."
);
#[cfg(debug_assertions)]
if child.is_some() {
panic!(
"<FlatRoutes> should not be used with nested \
routes."
);
}
EitherFuture::Right {
inner: ScopedFuture::new(view.choose()),
}
}
let view = view.choose();
Either::Right(view)
}
}
})
})
.await
}
}
impl<Defs, Fal, R> Render<R> for FlatRoutesView<Defs, Fal, R>
impl<Loc, Defs, Fal, R> Render<R> for FlatRoutesView<Loc, Defs, Fal, R>
where
Defs: MatchNestedRoutes<R>,
Fal: Render<R>,
Loc: LocationProvider,
Defs: MatchNestedRoutes<R> + 'static,
Fal: Render<R> + 'static,
R: Renderer + 'static,
{
type State = <Either<Fal, <Defs::Match as MatchInterface<R>>::View> as Render<R>>::State;
type State = Rc<
RefCell<
// TODO loading indicator
<EitherOf3<(), Fal, <Defs::Match as MatchInterface<R>>::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<Defs, Fal, R> AddAnyAttr<R> for FlatRoutesView<Defs, Fal, R>
impl<Loc, Defs, Fal, R> AddAnyAttr<R> for FlatRoutesView<Loc, Defs, Fal, R>
where
Defs: MatchNestedRoutes<R> + Send,
Fal: RenderHtml<R>,
Loc: LocationProvider + Send,
Defs: MatchNestedRoutes<R> + Send + 'static,
Fal: RenderHtml<R> + 'static,
R: Renderer + 'static,
{
type Output<SomeNewAttr: leptos::attr::Attribute<R>> =
FlatRoutesView<Defs, Fal, R>;
FlatRoutesView<Loc, Defs, Fal, R>;
fn add_any_attr<NewAttr: leptos::attr::Attribute<R>>(
self,
@ -126,10 +192,11 @@ where
}
}
impl<Defs, Fal, R> RenderHtml<R> for FlatRoutesView<Defs, Fal, R>
impl<Loc, Defs, Fal, R> RenderHtml<R> for FlatRoutesView<Loc, Defs, Fal, R>
where
Defs: MatchNestedRoutes<R> + Send,
Fal: RenderHtml<R>,
Loc: LocationProvider + Send,
Defs: MatchNestedRoutes<R> + Send + 'static,
Fal: RenderHtml<R> + '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::<OUT_OF_ORDER>(buf, position);
todo!()
// self.choose()
// .to_html_async_with_buf::<OUT_OF_ORDER>(buf, position);
}
fn hydrate<const FROM_SERVER: bool>(
@ -210,6 +279,7 @@ where
cursor: &Cursor<R>,
position: &PositionState,
) -> Self::State {
self.choose().hydrate::<FROM_SERVER>(cursor, position)
todo!()
// self.choose().hydrate::<FROM_SERVER>(cursor, position)
}
}

View file

@ -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<Url>,
pending_navigation: Arc<Mutex<Option<oneshot::Sender<()>>>>,
}
impl fmt::Debug for BrowserUrl {
@ -49,7 +59,11 @@ impl LocationProvider for BrowserUrl {
fn new() -> Result<Self, JsValue> {
let url = ArcRwSignal::new(Self::current()?);
Ok(Self { url })
let pending_navigation = Default::default();
Ok(Self {
url,
pending_navigation,
})
}
fn as_url(&self) -> &ArcRwSignal<Url> {
@ -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();

View file

@ -114,7 +114,7 @@ impl Default for LocationChange {
}
}
pub trait LocationProvider: Sized {
pub trait LocationProvider: Clone + 'static {
type Error: Debug;
fn new() -> Result<Self, Self::Error>;
@ -126,6 +126,10 @@ pub trait LocationProvider: Sized {
/// Sets up any global event listeners or other initialization needed.
fn init(&self, base: Option<Cow<'static, str>>);
/// 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);

View file

@ -1,4 +1,5 @@
use either_of::*;
use std::future::Future;
use tachys::{renderer::Renderer, view::Render};
pub trait ChooseView<R>
@ -6,21 +7,22 @@ where
Self: Send + 'static,
R: Renderer + 'static,
{
type Output: Render<R> + Send;
type Output;
fn choose(self) -> Self::Output;
fn choose(self) -> impl Future<Output = Self::Output>;
}
impl<F, View, R> ChooseView<R> for F
impl<F, ViewFut, R> ChooseView<R> for F
where
F: Fn() -> View + Send + 'static,
View: Render<R> + Send,
F: Fn() -> ViewFut + Send + 'static,
ViewFut: Future,
ViewFut::Output: Render<R> + 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<A, B, Rndr> ChooseView<Rndr> for Either<A, B>
@ -41,10 +43,10 @@ where
{
type Output = Either<A::Output, B::Output>;
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),)*
}
}
}

View file

@ -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<ParamsIter, Child, ViewFn, View, Rndr> MatchInterface<Rndr>
impl<ParamsIter, Child, ViewFn, ViewFut, Rndr> MatchInterface<Rndr>
for NestedMatch<ParamsIter, Child, ViewFn>
where
Rndr: Renderer + 'static,
Child: MatchInterface<Rndr> + MatchParams + 'static,
ViewFn: Fn() -> View + Send + 'static,
View: Render<Rndr> + RenderHtml<Rndr> + Send + 'static,
ViewFn: Fn() -> ViewFut + Send + 'static,
ViewFut: Future,
ViewFut::Output: Render<Rndr> + RenderHtml<Rndr> + 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<Segments, Children, Data, ViewFn, View, Rndr> MatchNestedRoutes<Rndr>
impl<Segments, Children, Data, ViewFn, ViewFut, Rndr> MatchNestedRoutes<Rndr>
for NestedRoute<Segments, Children, Data, ViewFn, Rndr>
where
Self: 'static,
@ -159,11 +160,12 @@ where
Children::Match: MatchParams,
Children: 'static,
<Children::Match as MatchParams>::Params: Clone,
ViewFn: Fn() -> View + Send + Clone + 'static,
View: Render<Rndr> + RenderHtml<Rndr> + Send + 'static,
ViewFn: Fn() -> ViewFut + Send + Clone + 'static,
ViewFut: Future,
ViewFut::Output: Render<Rndr> + RenderHtml<Rndr> + Send + 'static,
{
type Data = Data;
type View = View;
type View = ViewFut::Output;
type Match = NestedMatch<iter::Chain<
<Segments::ParamsIter as IntoIterator>::IntoIter,
Either<iter::Empty::<

View file

@ -1,25 +1,30 @@
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, EitherOf3};
use futures::future::join_all;
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},
};
use std::{
borrow::Cow,
cell::RefCell,
future::Future,
iter,
marker::PhantomData,
mem,
pin::Pin,
rc::Rc,
sync::{
mpsc::{self, Receiver, Sender},
Arc, Mutex,
@ -32,7 +37,7 @@ use tachys::{
view::{
add_attr::AddAnyAttr,
any_view::{AnyView, AnyViewState, IntoAny},
either::EitherState,
either::{EitherOf3State, EitherState},
Mountable, Position, PositionState, Render, RenderHtml,
},
};
@ -41,7 +46,8 @@ pub struct Outlet<R> {
rndr: PhantomData<R>,
}
pub(crate) struct NestedRoutesView<Defs, Fal, R> {
pub(crate) struct NestedRoutesView<Loc, Defs, Fal, R> {
pub location: Option<Loc>,
pub routes: Routes<Defs, R>,
pub outer_owner: Owner,
pub url: ArcRwSignal<Url>,
@ -62,15 +68,18 @@ where
path: ArcMemo<String>,
search_params: ArcMemo<ParamsMap>,
outlets: Vec<RouteContext<R>>,
view: EitherState<Fal::State, AnyViewState<R>, R>,
// TODO loading fallback
view: Rc<RefCell<EitherOf3State<(), Fal, AnyView<R>, R>>>,
}
impl<Defs, Fal, R> Render<R> for NestedRoutesView<Defs, Fal, R>
impl<Loc, Defs, Fal, R> Render<R> for NestedRoutesView<Loc, Defs, Fal, R>
where
Loc: LocationProvider,
Defs: MatchNestedRoutes<R>,
Fal: Render<R>,
Fal: Render<R> + 'static,
R: Renderer + 'static,
{
// TODO support fallback while loading
type State = NestedRouteViewState<Fal, R>;
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::<Fal, AnyView<R>>::Left(self.fallback)
.rebuild(&mut state.view);
EitherOf3::<(), Fal, AnyView<R>>::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::<Fal, AnyView<R>>::Right(
EitherOf3::<(), Fal, AnyView<R>>::C(
Outlet(OutletProps::builder().build()).into_any(),
)
.rebuild(&mut state.view);
.rebuild(&mut *state.view.borrow_mut());
})
}
}
@ -141,14 +186,15 @@ where
}
}
impl<Defs, Fal, R> AddAnyAttr<R> for NestedRoutesView<Defs, Fal, R>
impl<Loc, Defs, Fal, R> AddAnyAttr<R> for NestedRoutesView<Loc, Defs, Fal, R>
where
Loc: LocationProvider + Send,
Defs: MatchNestedRoutes<R> + Send,
Fal: RenderHtml<R>,
Fal: RenderHtml<R> + 'static,
R: Renderer + 'static,
{
type Output<SomeNewAttr: leptos::attr::Attribute<R>> =
NestedRoutesView<Defs, Fal, R>;
NestedRoutesView<Loc, Defs, Fal, R>;
fn add_any_attr<NewAttr: leptos::attr::Attribute<R>>(
self,
@ -161,10 +207,11 @@ where
}
}
impl<Defs, Fal, R> RenderHtml<R> for NestedRoutesView<Defs, Fal, R>
impl<Loc, Defs, Fal, R> RenderHtml<R> for NestedRoutesView<Loc, Defs, Fal, R>
where
Loc: LocationProvider + Send,
Defs: MatchNestedRoutes<R> + Send,
Fal: RenderHtml<R>,
Fal: RenderHtml<R> + '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::<FROM_SERVER>(cursor, position);
.hydrate::<FROM_SERVER>(cursor, position),
));
NestedRouteViewState {
outlets,
@ -378,6 +445,7 @@ where
fn build_nested_route(
self,
base: Option<Oco<'static, str>>,
loaders: &mut Vec<Pin<Box<dyn Future<Output = ArcTrigger>>>>,
outlets: &mut Vec<RouteContext<R>>,
parent: &Owner,
);
@ -386,6 +454,7 @@ where
self,
base: Option<Oco<'static, str>>,
items: &mut usize,
loaders: &mut Vec<Pin<Box<dyn Future<Output = ArcTrigger>>>>,
outlets: &mut Vec<RouteContext<R>>,
parent: &Owner,
);
@ -399,6 +468,7 @@ where
fn build_nested_route(
self,
base: Option<Oco<'static, str>>,
loaders: &mut Vec<Pin<Box<dyn Future<Output = ArcTrigger>>>>,
outlets: &mut Vec<RouteContext<R>>,
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<Oco<'static, str>>,
items: &mut usize,
loaders: &mut Vec<Pin<Box<dyn Future<Output = ArcTrigger>>>>,
outlets: &mut Vec<RouteContext<R>>,
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())
}
}

View file

@ -305,31 +305,3 @@ where
self.write().insert_before_this(parent, child)
}
}
impl<Rndr, Fal, Output> Mountable<Rndr>
for Rc<RefCell<EitherState<Fal, Output, Rndr>>>
where
Fal: Mountable<Rndr>,
Output: Mountable<Rndr>,
Rndr: Renderer,
{
fn unmount(&mut self) {
self.borrow_mut().unmount();
}
fn mount(
&mut self,
parent: &<Rndr as Renderer>::Element,
marker: Option<&<Rndr as Renderer>::Node>,
) {
self.borrow_mut().mount(parent, marker);
}
fn insert_before_this(
&self,
parent: &<Rndr as Renderer>::Element,
child: &mut dyn Mountable<Rndr>,
) -> bool {
self.borrow_mut().insert_before_this(parent, child)
}
}

View file

@ -425,8 +425,8 @@ macro_rules! tuples {
$($ty: Render<Rndr>,)*
Rndr: Renderer
{
state: [<EitherOf $num>]<$($ty::State,)*>,
marker: Rndr::Placeholder,
pub state: [<EitherOf $num>]<$($ty::State,)*>,
pub marker: Rndr::Placeholder,
}
impl<$($ty,)* Rndr> Mountable<Rndr> for [<EitherOf $num State>]<$($ty,)* Rndr>

View file

@ -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<T, R> Mountable<R> for Rc<RefCell<T>>
where
T: Mountable<R>,
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: &<R as Renderer>::Element,
child: &mut dyn Mountable<R>,
) -> bool {
self.borrow().insert_before_this(parent, child)
}
}
/// Allows data to be added to a static template.
pub trait ToTemplate {
const TEMPLATE: &'static str = "";