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:tokio",
|
||||
"leptos/ssr",
|
||||
"leptos_meta/ssr",
|
||||
"dep:leptos_axum",
|
||||
"routing/ssr",
|
||||
]
|
||||
|
||||
[profile.wasm-release]
|
||||
|
@ -60,7 +62,7 @@ style-file = "style/main.scss"
|
|||
# Optional. Env: LEPTOS_ASSETS_DIR.
|
||||
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.
|
||||
site-addr = "127.0.0.1:3000"
|
||||
site-addr = "127.0.0.1:3007"
|
||||
# The port to use for automatic reload monitoring
|
||||
reload-port = 3001
|
||||
# [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 routing::{
|
||||
components::{Route, Router, Routes},
|
||||
components::{FlatRoutes, Route, Router},
|
||||
hooks::use_params,
|
||||
params::Params,
|
||||
ParamSegment, SsrMode, StaticSegment,
|
||||
|
@ -24,11 +24,10 @@ pub fn App() -> impl IntoView {
|
|||
view! {
|
||||
<Stylesheet id="leptos" href="/pkg/ssr_modes.css"/>
|
||||
<Title text="Welcome to Leptos"/>
|
||||
|
||||
<Meta name="color-scheme" content="dark light"/>
|
||||
<Router>
|
||||
<main>
|
||||
// TODO should fallback be on Routes or Router?
|
||||
<Routes fallback>
|
||||
<FlatRoutes fallback>
|
||||
// We’ll load the home page with out-of-order streaming and <Suspense/>
|
||||
<Route path=StaticSegment("") view=HomePage/>
|
||||
|
||||
|
@ -44,7 +43,7 @@ pub fn App() -> impl IntoView {
|
|||
view=Post
|
||||
ssr=SsrMode::InOrder
|
||||
/>
|
||||
</Routes>
|
||||
</FlatRoutes>
|
||||
</main>
|
||||
</Router>
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -68,6 +68,7 @@ rustls = ["leptos_server/rustls", "server_fn/rustls"]
|
|||
ssr = [
|
||||
"leptos_macro/ssr",
|
||||
"leptos_reactive/ssr",
|
||||
"leptos_server/ssr",
|
||||
"server_fn/ssr",
|
||||
"hydration",
|
||||
"tachys/ssr",
|
||||
|
|
|
@ -39,6 +39,7 @@ base64 = { version = "0.22", optional = true }
|
|||
leptos = { path = "../leptos" }
|
||||
|
||||
[features]
|
||||
ssr = []
|
||||
default-tls = ["server_fn/default-tls"]
|
||||
rustls = ["server_fn/rustls"]
|
||||
hydration = ["reactive_graph/hydration", "dep:serde", "dep:serde_json"]
|
||||
|
|
|
@ -187,6 +187,7 @@ where
|
|||
source.add_subscriber(data.to_any_subscriber());
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
if let Some(shared_context) = shared_context {
|
||||
let value = data.clone();
|
||||
let ready_fut = data.ready();
|
||||
|
@ -408,7 +409,7 @@ where
|
|||
T: Send + Sync + 'static,
|
||||
Fut: Future<Output = T> + Send + 'static,
|
||||
{
|
||||
let ArcResource { ser, data } =
|
||||
let ArcResource { data, .. } =
|
||||
ArcResource::new_with_encoding(source, fetcher);
|
||||
Resource {
|
||||
ser: PhantomData,
|
||||
|
|
|
@ -24,6 +24,7 @@ features = ["HtmlLinkElement", "HtmlMetaElement", "HtmlTitleElement"]
|
|||
|
||||
[features]
|
||||
default = []
|
||||
ssr = []
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
rustdoc-args = ["--generate-link-to-definition"]
|
||||
|
|
|
@ -288,6 +288,7 @@ where
|
|||
{
|
||||
let mut el = Some(el);
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
if let Some(cx) = use_context::<ServerMetaContext>() {
|
||||
let mut inner = cx.inner.write().or_poisoned();
|
||||
el.take()
|
||||
|
@ -458,78 +459,3 @@ impl RenderHtml<Dom> for MetaTagsView {
|
|||
) -> 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]
|
||||
tracing = ["dep:tracing"]
|
||||
ssr = []
|
||||
nightly = []
|
||||
|
|
|
@ -6,7 +6,8 @@ use crate::{
|
|||
},
|
||||
navigate::{NavigateOptions, UseNavigate},
|
||||
resolve_path::resolve_path,
|
||||
MatchNestedRoutes, NestedRoute, NestedRoutesView, Routes, SsrMode,
|
||||
FlatRoutesView, MatchNestedRoutes, NestedRoute, NestedRoutesView, Routes,
|
||||
SsrMode,
|
||||
};
|
||||
use leptos::{
|
||||
children::{ToChildren, TypedChildren},
|
||||
|
@ -70,19 +71,20 @@ pub fn Router<Chil>(
|
|||
where
|
||||
Chil: IntoView,
|
||||
{
|
||||
let current_url = if Owner::current_shared_context()
|
||||
.map(|sc| sc.is_browser())
|
||||
.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 {
|
||||
#[cfg(feature = "ssr")]
|
||||
let current_url = {
|
||||
let req = use_context::<RequestUrl>().expect("no RequestUrl provided");
|
||||
let parsed = req.parse().expect("could not parse RequestUrl");
|
||||
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
|
||||
let state = ArcRwSignal::new(State::new(None));
|
||||
let location = Location::new(current_url.read_only(), state.read_only());
|
||||
|
@ -220,7 +222,7 @@ where
|
|||
move |_| url.read().search_params().clone()
|
||||
});
|
||||
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 {
|
||||
routes: routes.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]
|
||||
pub fn Route<Segments, View, ViewFn>(
|
||||
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))]
|
||||
|
||||
pub mod components;
|
||||
mod flat_router;
|
||||
mod generate_route_list;
|
||||
pub mod hooks;
|
||||
pub mod link;
|
||||
|
@ -16,6 +17,7 @@ pub mod params;
|
|||
mod ssr_mode;
|
||||
mod static_route;
|
||||
|
||||
pub use flat_router::*;
|
||||
pub use generate_route_list::*;
|
||||
pub use matching::*;
|
||||
pub use method::*;
|
||||
|
|
|
@ -26,13 +26,13 @@ use std::{
|
|||
},
|
||||
};
|
||||
use tachys::{
|
||||
hydration::Cursor, view::PositionState,
|
||||
hydration::Cursor,
|
||||
renderer::Renderer,
|
||||
ssr::StreamBuilder,
|
||||
view::{
|
||||
any_view::{AnyView, AnyViewState, IntoAny},
|
||||
either::EitherState,
|
||||
Mountable, Position, Render, RenderHtml,
|
||||
Mountable, Position, PositionState, Render, RenderHtml,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -182,8 +182,6 @@ where
|
|||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
println!("routes = {routes:#?}");
|
||||
|
||||
// add fallback
|
||||
// TODO fix: causes overlapping route issues on Axum
|
||||
/*routes.push(RouteListing::new(
|
||||
|
@ -203,31 +201,31 @@ where
|
|||
|
||||
RouteList::register(RouteList::from(routes));
|
||||
} else {
|
||||
let NestedRoutesView {
|
||||
routes,
|
||||
outer_owner,
|
||||
url,
|
||||
path,
|
||||
search_params,
|
||||
fallback,
|
||||
base,
|
||||
..
|
||||
} = self;
|
||||
let NestedRoutesView {
|
||||
routes,
|
||||
outer_owner,
|
||||
url,
|
||||
path,
|
||||
search_params,
|
||||
fallback,
|
||||
base,
|
||||
..
|
||||
} = self;
|
||||
|
||||
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(),
|
||||
)
|
||||
})
|
||||
}
|
||||
};
|
||||
view.to_html_with_buf(buf, position);
|
||||
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(),
|
||||
)
|
||||
})
|
||||
}
|
||||
};
|
||||
view.to_html_with_buf(buf, position);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -401,9 +401,9 @@ where
|
|||
// hydrate children
|
||||
position.set(Position::FirstChild);
|
||||
let children = self.children.hydrate::<FROM_SERVER>(cursor, position);
|
||||
cursor.set(el.as_ref().clone());
|
||||
|
||||
// go to next sibling
|
||||
cursor.set(el.as_ref().clone());
|
||||
position.set(Position::NextChild);
|
||||
|
||||
ElementState {
|
||||
|
|
|
@ -32,10 +32,6 @@ where
|
|||
#[allow(clippy::type_complexity)]
|
||||
hydrate_from_server:
|
||||
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>
|
||||
|
@ -195,25 +191,7 @@ where
|
|||
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,
|
||||
value: Box<dyn Any>,
|
||||
state: &mut AnyViewState<R>| {
|
||||
|
@ -250,8 +228,6 @@ where
|
|||
to_html_async_ooo,
|
||||
#[cfg(feature = "hydrate")]
|
||||
hydrate_from_server,
|
||||
#[cfg(feature = "hydrate")]
|
||||
hydrate_from_template,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -331,7 +307,10 @@ where
|
|||
if FROM_SERVER {
|
||||
(self.hydrate_from_server)(self.value, cursor, position)
|
||||
} else {
|
||||
(self.hydrate_from_template)(self.value, cursor, position)
|
||||
panic!(
|
||||
"hydrating AnyView from inside a ViewTemplate is not \
|
||||
supported."
|
||||
);
|
||||
}
|
||||
#[cfg(not(feature = "hydrate"))]
|
||||
{
|
||||
|
|
|
@ -507,7 +507,9 @@ macro_rules! tuples {
|
|||
position: &PositionState,
|
||||
) -> Self::State {
|
||||
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);
|
||||
|
|
Loading…
Reference in New Issue