mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 14:54:16 +00:00
Get router working with SSR
This commit is contained in:
parent
0f110cd2a1
commit
d8cfa973f1
5 changed files with 132 additions and 35 deletions
|
@ -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,
|
||||
|
|
|
@ -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")]
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
use leptos_reactive::{create_signal, use_context, ReadSignal, Scope};
|
||||
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
use wasm_bindgen::UnwrapThrowExt;
|
||||
|
||||
mod location;
|
||||
|
|
|
@ -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>,
|
||||
|
|
Loading…
Reference in a new issue