Get router working with SSR

This commit is contained in:
Greg Johnston 2022-09-25 22:10:37 -04:00
parent 0f110cd2a1
commit d8cfa973f1
5 changed files with 132 additions and 35 deletions

View file

@ -1,4 +1,4 @@
use std::{any::Any, borrow::Cow, rc::Rc};
use std::{any::Any, borrow::Cow, future::Future, pin::Pin, rc::Rc};
use leptos_core::IntoVec;
use leptos_dom::{Child, Element, IntoChild};
@ -72,14 +72,12 @@ impl RouteContext {
.unwrap_or_default()
});
let data = loader.map(|loader| (loader.data)(cx, params, location.clone()));
Some(Self {
inner: Rc::new(RouteContextInner {
cx,
base_path: base.to_string(),
child: Box::new(child),
data,
loader,
action,
path,
original_path: route.original_path.to_string(),
@ -97,12 +95,12 @@ impl RouteContext {
&self.inner.path
}
pub fn params(&self) -> ParamsMap {
self.inner.params.get()
pub fn params(&self) -> Memo<ParamsMap> {
self.inner.params
}
pub fn data(&self) -> &Option<Box<dyn Any>> {
&self.inner.data
pub fn loader(&self) -> &Option<Loader> {
&self.inner.loader
}
pub fn base(cx: Scope, path: &str, fallback: Option<fn() -> Element>) -> Self {
@ -111,7 +109,7 @@ impl RouteContext {
cx,
base_path: path.to_string(),
child: Box::new(|| None),
data: None,
loader: None,
action: None,
path: path.to_string(),
original_path: path.to_string(),
@ -138,7 +136,7 @@ pub(crate) struct RouteContextInner {
cx: Scope,
base_path: String,
pub(crate) child: Box<dyn Fn() -> Option<RouteContext>>,
pub(crate) data: Option<Box<dyn Any>>,
pub(crate) loader: Option<Loader>,
pub(crate) action: Option<Action>,
pub(crate) path: String,
pub(crate) original_path: String,

View file

@ -11,6 +11,8 @@ use leptos_reactive::{
};
use thiserror::Error;
use typed_builder::TypedBuilder;
#[cfg(not(feature = "ssr"))]
use wasm_bindgen::JsCast;
#[cfg(feature = "transition")]

View file

@ -1,49 +1,136 @@
use std::{any::Any, fmt::Debug, rc::Rc};
use std::{any::Any, fmt::Debug, future::Future, rc::Rc};
use leptos_reactive::{debug_warn, Memo, Scope};
use leptos_reactive::{create_resource, debug_warn, Memo, Resource, Scope};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use crate::{use_route, Location, ParamsMap};
use crate::{use_location, use_params_map, use_route, Location, ParamsMap, PinnedFuture, Url};
pub fn use_loader<T>(cx: Scope) -> T
// SSR and CSR both do the work in their own environment and return it as a resource
#[cfg(not(feature = "hydrate"))]
pub fn use_loader<T>(cx: Scope) -> Resource<(ParamsMap, Url), T>
where
T: Clone + Debug + 'static,
T: Clone + Debug + Serialize + DeserializeOwned + 'static,
{
let route = use_route(cx);
let data = match route.data().as_ref() {
Some(data) => data,
None => {
debug_warn!(
"(use_loader) could not find any data for route {}",
route.path()
);
panic!()
}
let params = use_params_map(cx);
let loader = route.loader().clone().unwrap_or_else(|| {
debug_warn!(
"use_loader() called on a route without a loader: {:?}",
route.path()
);
panic!()
});
let location = use_location(cx);
let url = move || Url {
origin: String::default(),
pathname: location.pathname.get(),
search: location.search.get(),
hash: location.hash.get(),
};
let data = match data.downcast_ref::<T>() {
Some(data) => data,
None => {
debug_warn!("(use_loader) could not downcast data to requested type");
panic!()
}
let loader = loader.data.clone();
create_resource(
cx,
move || (params.get(), url()),
move |(params, url)| {
let loader = loader.clone();
async move {
let any_data = (loader.clone())(cx, params, url).await;
any_data.downcast_ref::<T>().cloned().unwrap_or_else(|| {
debug_warn!(
"use_loader() could not downcast to {:?}",
std::any::type_name::<T>(),
);
panic!()
})
}
},
)
}
// In hydration mode, only run the loader on the server
#[cfg(feature = "hydrate")]
pub fn use_loader<T>(cx: Scope) -> Resource<(ParamsMap, Url), T>
where
T: Clone + Debug + Serialize + DeserializeOwned + 'static,
{
use wasm_bindgen::{JsCast, UnwrapThrowExt};
let route = use_route(cx);
let params = use_params_map(cx);
let location = use_location(cx);
let url = move || Url {
origin: String::default(),
pathname: location.pathname.get(),
search: location.search.get(),
hash: location.hash.get(),
};
data.clone()
create_resource(
cx,
move || (params.get(), url()),
move |(params, url)| async move {
let route = use_route(cx);
let mut opts = web_sys::RequestInit::new();
opts.method("GET");
let url = route.path();
let request = web_sys::Request::new_with_str_and_init(&url, &opts).unwrap_throw();
request
.headers()
.set("Accept", "application/json")
.unwrap_throw();
let window = web_sys::window().unwrap_throw();
let resp_value =
wasm_bindgen_futures::JsFuture::from(window.fetch_with_request(&request))
.await
.unwrap_throw();
let resp = resp_value.unchecked_into::<web_sys::Response>();
let text = wasm_bindgen_futures::JsFuture::from(resp.text().unwrap_throw())
.await
.unwrap_throw()
.as_string()
.unwrap_throw();
let decoded = window.atob(&text).unwrap_throw();
bincode::deserialize(&decoded.as_bytes()).unwrap_throw()
//serde_json::from_str(&json.as_string().unwrap_throw()).unwrap_throw()
},
)
}
#[derive(Clone)]
pub struct Loader {
pub(crate) data: Rc<dyn Fn(Scope, Memo<ParamsMap>, Location) -> Box<dyn Any>>,
#[cfg(not(feature = "hydrate"))]
pub(crate) data: Rc<dyn Fn(Scope, ParamsMap, Url) -> PinnedFuture<Box<dyn Any>>>,
}
impl<F, T> From<F> for Loader
impl<F, Fu, T> From<F> for Loader
where
F: Fn(Scope, Memo<ParamsMap>, Location) -> T + 'static,
F: Fn(Scope, ParamsMap, Url) -> Fu + 'static,
Fu: Future<Output = T> + 'static,
T: Any + 'static,
{
#[cfg(not(feature = "hydrate"))]
fn from(f: F) -> Self {
let wrapped_fn = move |cx, params, url| {
let res = f(cx, params, url);
Box::pin(async move { Box::new(res.await) as Box<dyn Any> })
as PinnedFuture<Box<dyn Any>>
};
Self {
data: Rc::new(move |cx, params, location| Box::new(f(cx, params, location))),
data: Rc::new(wrapped_fn),
}
}
#[cfg(feature = "hydrate")]
fn from(f: F) -> Self {
Self {}
}
}
impl std::fmt::Debug for Loader {

View file

@ -1,4 +1,6 @@
use leptos_reactive::{create_signal, use_context, ReadSignal, Scope};
#[cfg(not(feature = "ssr"))]
use wasm_bindgen::UnwrapThrowExt;
mod location;

View file

@ -1,9 +1,16 @@
#[cfg(not(feature = "ssr"))]
use wasm_bindgen::JsValue;
#[derive(Debug, Clone, Default, PartialEq)]
#[cfg(not(feature = "ssr"))]
pub struct State(pub Option<JsValue>);
#[derive(Debug, Clone, Default, PartialEq)]
#[cfg(feature = "ssr")]
pub struct State(pub Option<()>);
impl State {
#[cfg(not(feature = "ssr"))]
pub fn to_js_value(&self) -> JsValue {
match &self.0 {
Some(v) => v.clone(),
@ -12,6 +19,7 @@ impl State {
}
}
#[cfg(not(feature = "ssr"))]
impl<T> From<T> for State
where
T: Into<JsValue>,