SSR optimizations for binary size, and flat router
This commit is contained in:
parent
2934c295b5
commit
2470637b0b
|
@ -31,7 +31,9 @@ ssr = [
|
||||||
"dep:tower-http",
|
"dep:tower-http",
|
||||||
"dep:tokio",
|
"dep:tokio",
|
||||||
"leptos/ssr",
|
"leptos/ssr",
|
||||||
|
"leptos_meta/ssr",
|
||||||
"dep:leptos_axum",
|
"dep:leptos_axum",
|
||||||
|
"routing/ssr",
|
||||||
]
|
]
|
||||||
|
|
||||||
[profile.wasm-release]
|
[profile.wasm-release]
|
||||||
|
@ -60,7 +62,7 @@ style-file = "style/main.scss"
|
||||||
# Optional. Env: LEPTOS_ASSETS_DIR.
|
# Optional. Env: LEPTOS_ASSETS_DIR.
|
||||||
assets-dir = "assets"
|
assets-dir = "assets"
|
||||||
# The IP and port (ex: 127.0.0.1:3000) where the server serves the content. Use it in your server setup.
|
# The IP and port (ex: 127.0.0.1:3000) where the server serves the content. Use it in your server setup.
|
||||||
site-addr = "127.0.0.1:3000"
|
site-addr = "127.0.0.1:3007"
|
||||||
# The port to use for automatic reload monitoring
|
# The port to use for automatic reload monitoring
|
||||||
reload-port = 3001
|
reload-port = 3001
|
||||||
# [Optional] Command to use when running end2end tests. It will run in the end2end dir.
|
# [Optional] Command to use when running end2end tests. It will run in the end2end dir.
|
||||||
|
|
|
@ -7,7 +7,7 @@ use leptos::{
|
||||||
};
|
};
|
||||||
use leptos_meta::*;
|
use leptos_meta::*;
|
||||||
use routing::{
|
use routing::{
|
||||||
components::{Route, Router, Routes},
|
components::{FlatRoutes, Route, Router},
|
||||||
hooks::use_params,
|
hooks::use_params,
|
||||||
params::Params,
|
params::Params,
|
||||||
ParamSegment, SsrMode, StaticSegment,
|
ParamSegment, SsrMode, StaticSegment,
|
||||||
|
@ -24,11 +24,10 @@ pub fn App() -> impl IntoView {
|
||||||
view! {
|
view! {
|
||||||
<Stylesheet id="leptos" href="/pkg/ssr_modes.css"/>
|
<Stylesheet id="leptos" href="/pkg/ssr_modes.css"/>
|
||||||
<Title text="Welcome to Leptos"/>
|
<Title text="Welcome to Leptos"/>
|
||||||
|
<Meta name="color-scheme" content="dark light"/>
|
||||||
<Router>
|
<Router>
|
||||||
<main>
|
<main>
|
||||||
// TODO should fallback be on Routes or Router?
|
<FlatRoutes fallback>
|
||||||
<Routes fallback>
|
|
||||||
// We’ll load the home page with out-of-order streaming and <Suspense/>
|
// We’ll load the home page with out-of-order streaming and <Suspense/>
|
||||||
<Route path=StaticSegment("") view=HomePage/>
|
<Route path=StaticSegment("") view=HomePage/>
|
||||||
|
|
||||||
|
@ -44,7 +43,7 @@ pub fn App() -> impl IntoView {
|
||||||
view=Post
|
view=Post
|
||||||
ssr=SsrMode::InOrder
|
ssr=SsrMode::InOrder
|
||||||
/>
|
/>
|
||||||
</Routes>
|
</FlatRoutes>
|
||||||
</main>
|
</main>
|
||||||
</Router>
|
</Router>
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -68,6 +68,7 @@ rustls = ["leptos_server/rustls", "server_fn/rustls"]
|
||||||
ssr = [
|
ssr = [
|
||||||
"leptos_macro/ssr",
|
"leptos_macro/ssr",
|
||||||
"leptos_reactive/ssr",
|
"leptos_reactive/ssr",
|
||||||
|
"leptos_server/ssr",
|
||||||
"server_fn/ssr",
|
"server_fn/ssr",
|
||||||
"hydration",
|
"hydration",
|
||||||
"tachys/ssr",
|
"tachys/ssr",
|
||||||
|
|
|
@ -39,6 +39,7 @@ base64 = { version = "0.22", optional = true }
|
||||||
leptos = { path = "../leptos" }
|
leptos = { path = "../leptos" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
ssr = []
|
||||||
default-tls = ["server_fn/default-tls"]
|
default-tls = ["server_fn/default-tls"]
|
||||||
rustls = ["server_fn/rustls"]
|
rustls = ["server_fn/rustls"]
|
||||||
hydration = ["reactive_graph/hydration", "dep:serde", "dep:serde_json"]
|
hydration = ["reactive_graph/hydration", "dep:serde", "dep:serde_json"]
|
||||||
|
|
|
@ -187,6 +187,7 @@ where
|
||||||
source.add_subscriber(data.to_any_subscriber());
|
source.add_subscriber(data.to_any_subscriber());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
if let Some(shared_context) = shared_context {
|
if let Some(shared_context) = shared_context {
|
||||||
let value = data.clone();
|
let value = data.clone();
|
||||||
let ready_fut = data.ready();
|
let ready_fut = data.ready();
|
||||||
|
@ -408,7 +409,7 @@ where
|
||||||
T: Send + Sync + 'static,
|
T: Send + Sync + 'static,
|
||||||
Fut: Future<Output = T> + Send + 'static,
|
Fut: Future<Output = T> + Send + 'static,
|
||||||
{
|
{
|
||||||
let ArcResource { ser, data } =
|
let ArcResource { data, .. } =
|
||||||
ArcResource::new_with_encoding(source, fetcher);
|
ArcResource::new_with_encoding(source, fetcher);
|
||||||
Resource {
|
Resource {
|
||||||
ser: PhantomData,
|
ser: PhantomData,
|
||||||
|
|
|
@ -24,6 +24,7 @@ features = ["HtmlLinkElement", "HtmlMetaElement", "HtmlTitleElement"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
|
ssr = []
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
rustdoc-args = ["--generate-link-to-definition"]
|
rustdoc-args = ["--generate-link-to-definition"]
|
||||||
|
|
|
@ -288,6 +288,7 @@ where
|
||||||
{
|
{
|
||||||
let mut el = Some(el);
|
let mut el = Some(el);
|
||||||
|
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
if let Some(cx) = use_context::<ServerMetaContext>() {
|
if let Some(cx) = use_context::<ServerMetaContext>() {
|
||||||
let mut inner = cx.inner.write().or_poisoned();
|
let mut inner = cx.inner.write().or_poisoned();
|
||||||
el.take()
|
el.take()
|
||||||
|
@ -458,78 +459,3 @@ impl RenderHtml<Dom> for MetaTagsView {
|
||||||
) -> Self::State {
|
) -> Self::State {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MetaContext {
|
|
||||||
// TODO remove the below?
|
|
||||||
|
|
||||||
#[cfg(feature = "ssr")]
|
|
||||||
/// Converts the existing metadata tags into HTML that can be injected into the document head.
|
|
||||||
///
|
|
||||||
/// This should be called *after* the app’s component tree has been rendered into HTML, so that
|
|
||||||
/// components can set meta tags.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use leptos::*;
|
|
||||||
/// use leptos_meta::*;
|
|
||||||
///
|
|
||||||
/// # #[cfg(not(any(feature = "csr", feature = "hydrate")))] {
|
|
||||||
/// # let runtime = create_runtime();
|
|
||||||
/// provide_meta_context();
|
|
||||||
///
|
|
||||||
/// let app = view! {
|
|
||||||
/// <main>
|
|
||||||
/// <Title text="my title"/>
|
|
||||||
/// <Stylesheet href="/style.css"/>
|
|
||||||
/// <p>"Some text"</p>
|
|
||||||
/// </main>
|
|
||||||
/// };
|
|
||||||
///
|
|
||||||
/// // `app` contains only the body content w/ hydration stuff, not the meta tags
|
|
||||||
/// assert!(
|
|
||||||
/// !app.into_view().render_to_string().contains("my title")
|
|
||||||
/// );
|
|
||||||
/// // `MetaContext::dehydrate()` gives you HTML that should be in the `<head>`
|
|
||||||
/// assert!(use_head().dehydrate().contains("<title>my title</title>"));
|
|
||||||
/// # runtime.dispose();
|
|
||||||
/// # }
|
|
||||||
/// ```
|
|
||||||
pub fn dehydrate(&self) -> String {
|
|
||||||
let mut tags = String::new();
|
|
||||||
|
|
||||||
// Title
|
|
||||||
if let Some(title) = self.title.as_string() {
|
|
||||||
tags.push_str("<title>");
|
|
||||||
tags.push_str(&title);
|
|
||||||
tags.push_str("</title>");
|
|
||||||
}
|
|
||||||
tags.push_str(&self.tags.as_string());
|
|
||||||
|
|
||||||
tags
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extracts the metadata that should be used to close the `<head>` tag
|
|
||||||
/// and open the `<body>` tag. This is a helper function used in implementing
|
|
||||||
/// server-side HTML rendering across crates.
|
|
||||||
#[cfg(feature = "ssr")]
|
|
||||||
pub fn generate_head_metadata() -> String {
|
|
||||||
let (head, body) = generate_head_metadata_separated();
|
|
||||||
format!("{head}</head>{body}")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extracts the metadata that should be inserted at the beginning of the `<head>` tag
|
|
||||||
/// and on the opening `<body>` tag. This is a helper function used in implementing
|
|
||||||
/// server-side HTML rendering across crates.
|
|
||||||
#[cfg(feature = "ssr")]
|
|
||||||
pub fn generate_head_metadata_separated() -> (String, String) {
|
|
||||||
let meta = use_context::<MetaContext>();
|
|
||||||
let head = meta
|
|
||||||
.as_ref()
|
|
||||||
.map(|meta| meta.dehydrate())
|
|
||||||
.unwrap_or_default();
|
|
||||||
let body_meta = meta
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|meta| meta.body.as_string())
|
|
||||||
.unwrap_or_default();
|
|
||||||
(head, format!("<body{body_meta}>"))
|
|
||||||
}
|
|
||||||
|
|
|
@ -49,4 +49,5 @@ features = [
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
tracing = ["dep:tracing"]
|
tracing = ["dep:tracing"]
|
||||||
|
ssr = []
|
||||||
nightly = []
|
nightly = []
|
||||||
|
|
|
@ -6,7 +6,8 @@ use crate::{
|
||||||
},
|
},
|
||||||
navigate::{NavigateOptions, UseNavigate},
|
navigate::{NavigateOptions, UseNavigate},
|
||||||
resolve_path::resolve_path,
|
resolve_path::resolve_path,
|
||||||
MatchNestedRoutes, NestedRoute, NestedRoutesView, Routes, SsrMode,
|
FlatRoutesView, MatchNestedRoutes, NestedRoute, NestedRoutesView, Routes,
|
||||||
|
SsrMode,
|
||||||
};
|
};
|
||||||
use leptos::{
|
use leptos::{
|
||||||
children::{ToChildren, TypedChildren},
|
children::{ToChildren, TypedChildren},
|
||||||
|
@ -70,19 +71,20 @@ pub fn Router<Chil>(
|
||||||
where
|
where
|
||||||
Chil: IntoView,
|
Chil: IntoView,
|
||||||
{
|
{
|
||||||
let current_url = if Owner::current_shared_context()
|
#[cfg(feature = "ssr")]
|
||||||
.map(|sc| sc.is_browser())
|
let current_url = {
|
||||||
.unwrap_or(false)
|
|
||||||
{
|
|
||||||
let location = BrowserUrl::new().expect("could not access browser navigation"); // TODO options here
|
|
||||||
location.init(base.clone());
|
|
||||||
location.as_url().clone()
|
|
||||||
} else {
|
|
||||||
let req = use_context::<RequestUrl>().expect("no RequestUrl provided");
|
let req = use_context::<RequestUrl>().expect("no RequestUrl provided");
|
||||||
let parsed = req.parse().expect("could not parse RequestUrl");
|
let parsed = req.parse().expect("could not parse RequestUrl");
|
||||||
ArcRwSignal::new(parsed)
|
ArcRwSignal::new(parsed)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(not(feature = "ssr"))]
|
||||||
|
let current_url = {
|
||||||
|
let location =
|
||||||
|
BrowserUrl::new().expect("could not access browser navigation"); // TODO options here
|
||||||
|
location.init(base.clone());
|
||||||
|
location.as_url().clone()
|
||||||
|
};
|
||||||
// provide router context
|
// provide router context
|
||||||
let state = ArcRwSignal::new(State::new(None));
|
let state = ArcRwSignal::new(State::new(None));
|
||||||
let location = Location::new(current_url.read_only(), state.read_only());
|
let location = Location::new(current_url.read_only(), state.read_only());
|
||||||
|
@ -220,7 +222,7 @@ where
|
||||||
move |_| url.read().search_params().clone()
|
move |_| url.read().search_params().clone()
|
||||||
});
|
});
|
||||||
let outer_owner =
|
let outer_owner =
|
||||||
Owner::current().expect("creating Router, but no Owner was found");
|
Owner::current().expect("creating Routes, but no Owner was found");
|
||||||
move || NestedRoutesView {
|
move || NestedRoutesView {
|
||||||
routes: routes.clone(),
|
routes: routes.clone(),
|
||||||
outer_owner: outer_owner.clone(),
|
outer_owner: outer_owner.clone(),
|
||||||
|
@ -233,6 +235,46 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn FlatRoutes<Defs, FallbackFn, Fallback>(
|
||||||
|
fallback: FallbackFn,
|
||||||
|
children: RouteChildren<Defs>,
|
||||||
|
) -> impl IntoView
|
||||||
|
where
|
||||||
|
Defs: MatchNestedRoutes<Dom> + Clone + Send + 'static,
|
||||||
|
FallbackFn: Fn() -> Fallback + Send + 'static,
|
||||||
|
Fallback: IntoView + 'static,
|
||||||
|
{
|
||||||
|
use either_of::Either;
|
||||||
|
|
||||||
|
let RouterContext {
|
||||||
|
current_url, base, ..
|
||||||
|
} = use_context()
|
||||||
|
.expect("<FlatRoutes> should be used inside a <Router> component");
|
||||||
|
let base = base.map(|base| {
|
||||||
|
let mut base = Oco::from(base);
|
||||||
|
base.upgrade_inplace();
|
||||||
|
base
|
||||||
|
});
|
||||||
|
let routes = Routes::new(children.into_inner());
|
||||||
|
let path = ArcMemo::new({
|
||||||
|
let url = current_url.clone();
|
||||||
|
move |_| url.read().path().to_string()
|
||||||
|
});
|
||||||
|
let search_params = ArcMemo::new({
|
||||||
|
let url = current_url.clone();
|
||||||
|
move |_| url.read().search_params().clone()
|
||||||
|
});
|
||||||
|
let outer_owner =
|
||||||
|
Owner::current().expect("creating Router, but no Owner was found");
|
||||||
|
move || FlatRoutesView {
|
||||||
|
routes: routes.clone(),
|
||||||
|
path: path.clone(),
|
||||||
|
fallback: fallback(),
|
||||||
|
outer_owner: outer_owner.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Route<Segments, View, ViewFn>(
|
pub fn Route<Segments, View, ViewFn>(
|
||||||
path: Segments,
|
path: Segments,
|
||||||
|
|
|
@ -0,0 +1,189 @@
|
||||||
|
use crate::{
|
||||||
|
location::{Location, RequestUrl, Url},
|
||||||
|
matching::Routes,
|
||||||
|
params::ParamsMap,
|
||||||
|
resolve_path::resolve_path,
|
||||||
|
ChooseView, MatchInterface, MatchNestedRoutes, MatchParams, Method,
|
||||||
|
PathSegment, RouteList, RouteListing, RouteMatchId,
|
||||||
|
};
|
||||||
|
use either_of::Either;
|
||||||
|
use leptos::{component, oco::Oco, IntoView};
|
||||||
|
use or_poisoned::OrPoisoned;
|
||||||
|
use reactive_graph::{
|
||||||
|
computed::{ArcMemo, Memo},
|
||||||
|
owner::{provide_context, use_context, Owner},
|
||||||
|
signal::{ArcRwSignal, ArcTrigger},
|
||||||
|
traits::{Get, Read, ReadUntracked, Set, Track, Trigger},
|
||||||
|
};
|
||||||
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
|
iter,
|
||||||
|
marker::PhantomData,
|
||||||
|
mem,
|
||||||
|
sync::{
|
||||||
|
mpsc::{self, Receiver, Sender},
|
||||||
|
Arc, Mutex,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use tachys::{
|
||||||
|
hydration::Cursor,
|
||||||
|
renderer::Renderer,
|
||||||
|
ssr::StreamBuilder,
|
||||||
|
view::{
|
||||||
|
any_view::{AnyView, AnyViewState, IntoAny},
|
||||||
|
either::EitherState,
|
||||||
|
Mountable, Position, PositionState, Render, RenderHtml,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub(crate) struct FlatRoutesView<Defs, Fal, R> {
|
||||||
|
pub routes: Routes<Defs, R>,
|
||||||
|
pub path: ArcMemo<String>,
|
||||||
|
pub fallback: Fal,
|
||||||
|
pub outer_owner: Owner,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Defs, Fal, R> FlatRoutesView<Defs, Fal, R>
|
||||||
|
where
|
||||||
|
Defs: MatchNestedRoutes<R>,
|
||||||
|
Fal: Render<R>,
|
||||||
|
R: Renderer + 'static,
|
||||||
|
{
|
||||||
|
pub fn choose(
|
||||||
|
self,
|
||||||
|
) -> Either<Fal, <Defs::Match as MatchInterface<R>>::View> {
|
||||||
|
let FlatRoutesView {
|
||||||
|
routes,
|
||||||
|
path,
|
||||||
|
fallback,
|
||||||
|
outer_owner,
|
||||||
|
} = self;
|
||||||
|
|
||||||
|
outer_owner.with(|| {
|
||||||
|
let new_match = routes.match_route(&path.read());
|
||||||
|
match new_match {
|
||||||
|
None => Either::Left(fallback),
|
||||||
|
Some(matched) => {
|
||||||
|
let params = matched.to_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."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let view = view.choose();
|
||||||
|
Either::Right(view)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Defs, Fal, R> Render<R> for FlatRoutesView<Defs, Fal, R>
|
||||||
|
where
|
||||||
|
Defs: MatchNestedRoutes<R>,
|
||||||
|
Fal: Render<R>,
|
||||||
|
R: Renderer + 'static,
|
||||||
|
{
|
||||||
|
type State = <Either<Fal, <Defs::Match as MatchInterface<R>>::View> as Render<R>>::State;
|
||||||
|
|
||||||
|
fn build(self) -> Self::State {
|
||||||
|
self.choose().build()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rebuild(self, state: &mut Self::State) {
|
||||||
|
self.choose().rebuild(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Defs, Fal, R> RenderHtml<R> for FlatRoutesView<Defs, Fal, R>
|
||||||
|
where
|
||||||
|
Defs: MatchNestedRoutes<R> + Send,
|
||||||
|
Fal: RenderHtml<R>,
|
||||||
|
R: Renderer + 'static,
|
||||||
|
{
|
||||||
|
type AsyncOutput = Self;
|
||||||
|
|
||||||
|
const MIN_LENGTH: usize = <Either<
|
||||||
|
Fal,
|
||||||
|
<Defs::Match as MatchInterface<R>>::View,
|
||||||
|
> as RenderHtml<R>>::MIN_LENGTH;
|
||||||
|
|
||||||
|
async fn resolve(self) -> Self::AsyncOutput {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_html_with_buf(self, buf: &mut String, position: &mut Position) {
|
||||||
|
// if this is being run on the server for the first time, generating all possible routes
|
||||||
|
if RouteList::is_generating() {
|
||||||
|
// add routes
|
||||||
|
let (base, routes) = self.routes.generate_routes();
|
||||||
|
let mut routes = routes
|
||||||
|
.into_iter()
|
||||||
|
.map(|data| {
|
||||||
|
let path = base
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|base| {
|
||||||
|
iter::once(PathSegment::Static(
|
||||||
|
base.to_string().into(),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.chain(data.segments)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
RouteListing::new(
|
||||||
|
path,
|
||||||
|
data.ssr_mode,
|
||||||
|
// TODO methods
|
||||||
|
[Method::Get],
|
||||||
|
// TODO static data
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// add fallback
|
||||||
|
// TODO fix: causes overlapping route issues on Axum
|
||||||
|
/*routes.push(RouteListing::new(
|
||||||
|
[PathSegment::Static(
|
||||||
|
base.unwrap_or_default().to_string().into(),
|
||||||
|
)],
|
||||||
|
SsrMode::Async,
|
||||||
|
[
|
||||||
|
Method::Get,
|
||||||
|
Method::Post,
|
||||||
|
Method::Put,
|
||||||
|
Method::Patch,
|
||||||
|
Method::Delete,
|
||||||
|
],
|
||||||
|
None,
|
||||||
|
));*/
|
||||||
|
|
||||||
|
RouteList::register(RouteList::from(routes));
|
||||||
|
} else {
|
||||||
|
self.choose().to_html_with_buf(buf, position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
|
||||||
|
self,
|
||||||
|
buf: &mut StreamBuilder,
|
||||||
|
position: &mut Position,
|
||||||
|
) where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
self.choose()
|
||||||
|
.to_html_async_with_buf::<OUT_OF_ORDER>(buf, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hydrate<const FROM_SERVER: bool>(
|
||||||
|
self,
|
||||||
|
cursor: &Cursor<R>,
|
||||||
|
position: &PositionState,
|
||||||
|
) -> Self::State {
|
||||||
|
self.choose().hydrate::<FROM_SERVER>(cursor, position)
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,7 @@
|
||||||
#![cfg_attr(feature = "nightly", feature(negative_impls))]
|
#![cfg_attr(feature = "nightly", feature(negative_impls))]
|
||||||
|
|
||||||
pub mod components;
|
pub mod components;
|
||||||
|
mod flat_router;
|
||||||
mod generate_route_list;
|
mod generate_route_list;
|
||||||
pub mod hooks;
|
pub mod hooks;
|
||||||
pub mod link;
|
pub mod link;
|
||||||
|
@ -16,6 +17,7 @@ pub mod params;
|
||||||
mod ssr_mode;
|
mod ssr_mode;
|
||||||
mod static_route;
|
mod static_route;
|
||||||
|
|
||||||
|
pub use flat_router::*;
|
||||||
pub use generate_route_list::*;
|
pub use generate_route_list::*;
|
||||||
pub use matching::*;
|
pub use matching::*;
|
||||||
pub use method::*;
|
pub use method::*;
|
||||||
|
|
|
@ -26,13 +26,13 @@ use std::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use tachys::{
|
use tachys::{
|
||||||
hydration::Cursor, view::PositionState,
|
hydration::Cursor,
|
||||||
renderer::Renderer,
|
renderer::Renderer,
|
||||||
ssr::StreamBuilder,
|
ssr::StreamBuilder,
|
||||||
view::{
|
view::{
|
||||||
any_view::{AnyView, AnyViewState, IntoAny},
|
any_view::{AnyView, AnyViewState, IntoAny},
|
||||||
either::EitherState,
|
either::EitherState,
|
||||||
Mountable, Position, Render, RenderHtml,
|
Mountable, Position, PositionState, Render, RenderHtml,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -182,8 +182,6 @@ where
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
println!("routes = {routes:#?}");
|
|
||||||
|
|
||||||
// add fallback
|
// add fallback
|
||||||
// TODO fix: causes overlapping route issues on Axum
|
// TODO fix: causes overlapping route issues on Axum
|
||||||
/*routes.push(RouteListing::new(
|
/*routes.push(RouteListing::new(
|
||||||
|
@ -203,31 +201,31 @@ where
|
||||||
|
|
||||||
RouteList::register(RouteList::from(routes));
|
RouteList::register(RouteList::from(routes));
|
||||||
} else {
|
} else {
|
||||||
let NestedRoutesView {
|
let NestedRoutesView {
|
||||||
routes,
|
routes,
|
||||||
outer_owner,
|
outer_owner,
|
||||||
url,
|
url,
|
||||||
path,
|
path,
|
||||||
search_params,
|
search_params,
|
||||||
fallback,
|
fallback,
|
||||||
base,
|
base,
|
||||||
..
|
..
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
let mut outlets = Vec::new();
|
let mut outlets = Vec::new();
|
||||||
let new_match = routes.match_route(&path.read());
|
let new_match = routes.match_route(&path.read());
|
||||||
let view = match new_match {
|
let view = match new_match {
|
||||||
None => Either::Left(fallback),
|
None => Either::Left(fallback),
|
||||||
Some(route) => {
|
Some(route) => {
|
||||||
route.build_nested_route(base, &mut outlets, &outer_owner);
|
route.build_nested_route(base, &mut outlets, &outer_owner);
|
||||||
outer_owner.with(|| {
|
outer_owner.with(|| {
|
||||||
Either::Right(
|
Either::Right(
|
||||||
Outlet(OutletProps::builder().build()).into_any(),
|
Outlet(OutletProps::builder().build()).into_any(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
view.to_html_with_buf(buf, position);
|
view.to_html_with_buf(buf, position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -401,9 +401,9 @@ where
|
||||||
// hydrate children
|
// hydrate children
|
||||||
position.set(Position::FirstChild);
|
position.set(Position::FirstChild);
|
||||||
let children = self.children.hydrate::<FROM_SERVER>(cursor, position);
|
let children = self.children.hydrate::<FROM_SERVER>(cursor, position);
|
||||||
cursor.set(el.as_ref().clone());
|
|
||||||
|
|
||||||
// go to next sibling
|
// go to next sibling
|
||||||
|
cursor.set(el.as_ref().clone());
|
||||||
position.set(Position::NextChild);
|
position.set(Position::NextChild);
|
||||||
|
|
||||||
ElementState {
|
ElementState {
|
||||||
|
|
|
@ -32,10 +32,6 @@ where
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
hydrate_from_server:
|
hydrate_from_server:
|
||||||
fn(Box<dyn Any>, &Cursor<R>, &PositionState) -> AnyViewState<R>,
|
fn(Box<dyn Any>, &Cursor<R>, &PositionState) -> AnyViewState<R>,
|
||||||
#[cfg(feature = "hydrate")]
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
hydrate_from_template:
|
|
||||||
fn(Box<dyn Any>, &Cursor<R>, &PositionState) -> AnyViewState<R>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AnyViewState<R>
|
pub struct AnyViewState<R>
|
||||||
|
@ -195,25 +191,7 @@ where
|
||||||
insert_before_this: insert_before_this::<R, T>,
|
insert_before_this: insert_before_this::<R, T>,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
#[cfg(feature = "hydrate")]
|
|
||||||
let hydrate_from_template =
|
|
||||||
|value: Box<dyn Any>,
|
|
||||||
cursor: &Cursor<R>,
|
|
||||||
position: &PositionState| {
|
|
||||||
let value = value
|
|
||||||
.downcast::<T>()
|
|
||||||
.expect("AnyView::hydrate_from_server couldn't downcast");
|
|
||||||
let state = Box::new(value.hydrate::<true>(cursor, position));
|
|
||||||
|
|
||||||
AnyViewState {
|
|
||||||
type_id: TypeId::of::<T>(),
|
|
||||||
state,
|
|
||||||
rndr: PhantomData,
|
|
||||||
mount: mount_any::<R, T>,
|
|
||||||
unmount: unmount_any::<R, T>,
|
|
||||||
insert_before_this: insert_before_this::<R, T>,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let rebuild = |new_type_id: TypeId,
|
let rebuild = |new_type_id: TypeId,
|
||||||
value: Box<dyn Any>,
|
value: Box<dyn Any>,
|
||||||
state: &mut AnyViewState<R>| {
|
state: &mut AnyViewState<R>| {
|
||||||
|
@ -250,8 +228,6 @@ where
|
||||||
to_html_async_ooo,
|
to_html_async_ooo,
|
||||||
#[cfg(feature = "hydrate")]
|
#[cfg(feature = "hydrate")]
|
||||||
hydrate_from_server,
|
hydrate_from_server,
|
||||||
#[cfg(feature = "hydrate")]
|
|
||||||
hydrate_from_template,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -331,7 +307,10 @@ where
|
||||||
if FROM_SERVER {
|
if FROM_SERVER {
|
||||||
(self.hydrate_from_server)(self.value, cursor, position)
|
(self.hydrate_from_server)(self.value, cursor, position)
|
||||||
} else {
|
} else {
|
||||||
(self.hydrate_from_template)(self.value, cursor, position)
|
panic!(
|
||||||
|
"hydrating AnyView from inside a ViewTemplate is not \
|
||||||
|
supported."
|
||||||
|
);
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "hydrate"))]
|
#[cfg(not(feature = "hydrate"))]
|
||||||
{
|
{
|
||||||
|
|
|
@ -507,7 +507,9 @@ macro_rules! tuples {
|
||||||
position: &PositionState,
|
position: &PositionState,
|
||||||
) -> Self::State {
|
) -> Self::State {
|
||||||
let state = match self {
|
let state = match self {
|
||||||
$([<EitherOf $num>]::$ty(this) => [<EitherOf $num>]::$ty(this.hydrate::<FROM_SERVER>(cursor, position)),)*
|
$([<EitherOf $num>]::$ty(this) => {
|
||||||
|
[<EitherOf $num>]::$ty(this.hydrate::<FROM_SERVER>(cursor, position))
|
||||||
|
})*
|
||||||
};
|
};
|
||||||
|
|
||||||
let marker = cursor.next_placeholder(position);
|
let marker = cursor.next_placeholder(position);
|
||||||
|
|
Loading…
Reference in New Issue