From 3195ab4ffcb0b680ff3a491e106da5f3f2472ec0 Mon Sep 17 00:00:00 2001 From: Greg Johnston Date: Sun, 18 Dec 2022 07:38:51 -0500 Subject: [PATCH] Get `Suspense`/`Transition` hydration working --- leptos_core/src/suspense.rs | 15 +++-- leptos_core/src/transition.rs | 15 +++-- leptos_dom/src/components.rs | 12 ++-- leptos_dom/src/components/dyn_child.rs | 8 +-- leptos_dom/src/components/each.rs | 16 ++--- leptos_dom/src/components/fragment.rs | 17 ++++-- leptos_dom/src/components/unit.rs | 6 +- leptos_dom/src/html.rs | 27 +++++---- leptos_dom/src/hydration.rs | 83 ++++++++++++++------------ leptos_dom/src/lib.rs | 9 +-- leptos_dom/src/ssr.rs | 18 +++--- leptos_macro/src/view.rs | 30 ++++++---- router/src/components/routes.rs | 2 +- 13 files changed, 144 insertions(+), 114 deletions(-) diff --git a/leptos_core/src/suspense.rs b/leptos_core/src/suspense.rs index b4870e55b..1477d0136 100644 --- a/leptos_core/src/suspense.rs +++ b/leptos_core/src/suspense.rs @@ -93,13 +93,14 @@ where { use leptos_dom::DynChild; - let cached_id = HydrationCtx::peak(); - let _space_for_inner = HydrationCtx::id(); + let cached_id = HydrationCtx::peek(); DynChild::new(move || { if context.ready() { leptos_dom::warn!(" ready and continuing from {}", cached_id); - HydrationCtx::continue_from(cached_id); + let mut id_to_replace = cached_id.clone(); + id_to_replace.offset -= 1; + HydrationCtx::continue_from(id_to_replace); child(cx).into_view(cx) } else { @@ -123,7 +124,7 @@ where use leptos_dom::DynChild; let orig_child = Rc::clone(&orig_child); - let current_id = HydrationCtx::peak(); + let current_id = HydrationCtx::peek(); DynChild::new(move || { @@ -138,7 +139,9 @@ where // show the fallback, but also prepare to stream HTML else { let orig_child = Rc::clone(&orig_child); - cx.register_suspense(context, ¤t_id.to_string(), move || { + let mut id_to_replace = current_id.clone(); + id_to_replace.offset += 1; + cx.register_suspense(context, &id_to_replace.to_string(), move || { orig_child(cx) .into_view(cx) .render_to_string(cx) @@ -146,7 +149,7 @@ where }); // return the fallback for now, wrapped in fragment identifer - HydrationCtx::continue_from(current_id); + HydrationCtx::continue_from(current_id.clone()); fallback().into_view(cx) } }; diff --git a/leptos_core/src/transition.rs b/leptos_core/src/transition.rs index 2c0b04de1..3f47fb35f 100644 --- a/leptos_core/src/transition.rs +++ b/leptos_core/src/transition.rs @@ -109,13 +109,14 @@ where use std::cell::RefCell; let prev_child = RefCell::new(None); - let cached_id = HydrationCtx::peak(); - let _space_for_inner = HydrationCtx::id(); + let cached_id = HydrationCtx::peek(); DynChild::new(move || { if context.ready() { leptos_dom::warn!(" ready and continuing from {}", cached_id); - HydrationCtx::continue_from(cached_id); + let mut id_to_replace = cached_id.clone(); + id_to_replace.offset -= 1; + HydrationCtx::continue_from(id_to_replace.clone()); let current_child = child(cx).into_view(cx); *prev_child.borrow_mut() = Some(current_child.clone()); @@ -152,7 +153,7 @@ where E: IntoView, { let orig_child = Rc::clone(&orig_child); - let current_id = HydrationCtx::peak(); + let current_id = HydrationCtx::peek(); DynChild::new(move || { @@ -167,7 +168,9 @@ where // show the fallback, but also prepare to stream HTML else { let orig_child = Rc::clone(&orig_child); - cx.register_suspense(context, ¤t_id.to_string(), move || { + let mut id_to_replace = current_id.clone(); + id_to_replace.offset += 1; + cx.register_suspense(context, &id_to_replace.to_string(), move || { orig_child(cx) .into_view(cx) .render_to_string(cx) @@ -175,7 +178,7 @@ where }); // return the fallback for now, wrapped in fragment identifer - HydrationCtx::continue_from(current_id); + HydrationCtx::continue_from(current_id.clone()); fallback().into_view(cx) } }; diff --git a/leptos_dom/src/components.rs b/leptos_dom/src/components.rs index abefe6849..b79bb3843 100644 --- a/leptos_dom/src/components.rs +++ b/leptos_dom/src/components.rs @@ -3,7 +3,7 @@ mod each; mod fragment; mod unit; -use crate::{hydration::HydrationCtx, Comment, IntoView, View}; +use crate::{hydration::{HydrationCtx, HydrationKey}, Comment, IntoView, View}; #[cfg(all(target_arch = "wasm32", feature = "web"))] use crate::{mount_child, MountKind, Mountable}; pub use dyn_child::*; @@ -51,7 +51,7 @@ pub struct ComponentRepr { pub children: Vec, closing: Comment, #[cfg(not(all(target_arch = "wasm32", feature = "web")))] - pub(crate) id: usize, + pub(crate) id: HydrationKey, } impl fmt::Debug for ComponentRepr { @@ -125,17 +125,17 @@ impl IntoView for ComponentRepr { impl ComponentRepr { /// Creates a new [`Component`]. pub fn new(name: impl Into>) -> Self { - Self::new_with_id(name, HydrationCtx::id()) + Self::new_with_id(name, HydrationCtx::next_component()) } /// Creates a new [`Component`] with the given hydration ID. - pub fn new_with_id(name: impl Into>, id: usize) -> Self { + pub fn new_with_id(name: impl Into>, id: HydrationKey) -> Self { let name = name.into(); let markers = ( - Comment::new(Cow::Owned(format!("")), id, true), + Comment::new(Cow::Owned(format!("")), &id, true), #[cfg(debug_assertions)] - Comment::new(Cow::Owned(format!("<{name}>")), id, false), + Comment::new(Cow::Owned(format!("<{name}>")), &id, false), ); #[cfg(all(target_arch = "wasm32", feature = "web"))] diff --git a/leptos_dom/src/components/dyn_child.rs b/leptos_dom/src/components/dyn_child.rs index 9f5a924d2..a19e31f6f 100644 --- a/leptos_dom/src/components/dyn_child.rs +++ b/leptos_dom/src/components/dyn_child.rs @@ -1,4 +1,4 @@ -use crate::{hydration::HydrationCtx, Comment, IntoView, View}; +use crate::{hydration::{HydrationCtx, HydrationKey}, Comment, IntoView, View}; #[cfg(all(target_arch = "wasm32", feature = "web"))] use crate::{mount_child, unmount_child, MountKind, Mountable}; use leptos_reactive::Scope; @@ -18,7 +18,7 @@ pub struct DynChildRepr { pub(crate) child: Rc>>>, closing: Comment, #[cfg(not(all(target_arch = "wasm32", feature = "web")))] - pub(crate) id: usize, + pub(crate) id: HydrationKey, } impl fmt::Debug for DynChildRepr { @@ -65,9 +65,9 @@ impl DynChildRepr { let id = HydrationCtx::id(); let markers = ( - Comment::new(Cow::Borrowed(""), id, true), + Comment::new(Cow::Borrowed(""), &id, true), #[cfg(debug_assertions)] - Comment::new(Cow::Borrowed(""), id, false), + Comment::new(Cow::Borrowed(""), &id, false), ); #[cfg(all(target_arch = "wasm32", feature = "web"))] diff --git a/leptos_dom/src/components/each.rs b/leptos_dom/src/components/each.rs index c3fd03009..168d058ed 100644 --- a/leptos_dom/src/components/each.rs +++ b/leptos_dom/src/components/each.rs @@ -1,4 +1,4 @@ -use crate::{hydration::HydrationCtx, Comment, CoreComponent, IntoView, View}; +use crate::{hydration::{HydrationCtx, HydrationKey}, Comment, CoreComponent, IntoView, View}; #[cfg(all(target_arch = "wasm32", feature = "web"))] use crate::{mount_child, MountKind, Mountable, RANGE}; #[cfg(all(target_arch = "wasm32", feature = "web"))] @@ -50,7 +50,7 @@ pub struct EachRepr { pub(crate) children: Rc>>>, closing: Comment, #[cfg(not(all(target_arch = "wasm32", feature = "web")))] - pub(crate) id: usize, + pub(crate) id: HydrationKey, } impl fmt::Debug for EachRepr { @@ -74,9 +74,9 @@ impl Default for EachRepr { let id = HydrationCtx::id(); let markers = ( - Comment::new(Cow::Borrowed(""), id, true), + Comment::new(Cow::Borrowed(""), &id, true), #[cfg(debug_assertions)] - Comment::new(Cow::Borrowed(""), id, false), + Comment::new(Cow::Borrowed(""), &id, false), ); #[cfg(all(target_arch = "wasm32", feature = "web"))] @@ -147,7 +147,7 @@ pub(crate) struct EachItem { pub(crate) child: View, closing: Comment, #[cfg(not(all(target_arch = "wasm32", feature = "web")))] - pub(crate) id: usize, + pub(crate) id: HydrationKey, } impl fmt::Debug for EachItem { @@ -169,9 +169,9 @@ impl EachItem { let id = HydrationCtx::id(); let markers = ( - Comment::new(Cow::Borrowed(""), id, true), + Comment::new(Cow::Borrowed(""), &id, true), #[cfg(debug_assertions)] - Comment::new(Cow::Borrowed(""), id, false), + Comment::new(Cow::Borrowed(""), &id, false), ); #[cfg(all(target_arch = "wasm32", feature = "web"))] @@ -691,7 +691,7 @@ where /// /// #[derive(Copy, Clone, Debug, PartialEq, Eq)] /// struct Counter { -/// id: usize, +/// id: HydrationKey, /// count: RwSignal /// } /// diff --git a/leptos_dom/src/components/fragment.rs b/leptos_dom/src/components/fragment.rs index 37bda37c1..2681b8367 100644 --- a/leptos_dom/src/components/fragment.rs +++ b/leptos_dom/src/components/fragment.rs @@ -1,6 +1,6 @@ use leptos_reactive::Scope; -use crate::{ComponentRepr, IntoView, View, HydrationCtx}; +use crate::{ComponentRepr, IntoView, View, HydrationCtx, hydration::HydrationKey}; /// Trait for converting any iterable into a [`Fragment`]. pub trait IntoFragment { @@ -21,7 +21,7 @@ where /// Represents a group of [`views`](View). #[derive(Debug, Clone)] pub struct Fragment { - id: usize, + id: HydrationKey, nodes: Vec } @@ -43,8 +43,13 @@ impl Fragment { Self::new_with_id(HydrationCtx::id(), nodes) } + /// Creates a new [`Fragment`] from a function that returns [`Vec`]. + pub fn lazy(nodes: impl Fn() -> Vec) -> Self { + Self::new_with_id(HydrationCtx::id(), nodes()) + } + /// Creates a new [`Fragment`] with the given hydration ID from a [`Vec`]. - pub fn new_with_id(id: usize, nodes: Vec) -> Self { + pub fn new_with_id(id: HydrationKey, nodes: Vec) -> Self { Self { id, nodes @@ -57,15 +62,15 @@ impl Fragment { } /// Returns the fragment's hydration ID. - pub fn id(&self) -> usize { - self.id + pub fn id(&self) -> &HydrationKey { + &self.id } } impl IntoView for Fragment { #[cfg_attr(debug_assertions, instrument(level = "trace", name = "", skip_all, fields(children = self.nodes.len())))] fn into_view(self, cx: leptos_reactive::Scope) -> View { - let mut frag = ComponentRepr::new_with_id("", self.id); + let mut frag = ComponentRepr::new_with_id("", self.id.clone()); frag.children = self.nodes; diff --git a/leptos_dom/src/components/unit.rs b/leptos_dom/src/components/unit.rs index a1b256fa1..4fdaeb1e9 100644 --- a/leptos_dom/src/components/unit.rs +++ b/leptos_dom/src/components/unit.rs @@ -2,7 +2,7 @@ use std::fmt; #[cfg(all(target_arch = "wasm32", feature = "web"))] use crate::Mountable; -use crate::{hydration::HydrationCtx, Comment, CoreComponent, IntoView, View}; +use crate::{hydration::{HydrationCtx, HydrationKey}, Comment, CoreComponent, IntoView, View}; #[cfg(all(target_arch = "wasm32", feature = "web"))] use wasm_bindgen::JsCast; @@ -11,7 +11,7 @@ use wasm_bindgen::JsCast; pub struct UnitRepr { comment: Comment, #[cfg(not(all(target_arch = "wasm32", feature = "web")))] - pub(crate) id: usize, + pub(crate) id: HydrationKey, } impl fmt::Debug for UnitRepr { @@ -25,7 +25,7 @@ impl Default for UnitRepr { let id = HydrationCtx::id(); Self { - comment: Comment::new("<() />", id, true), + comment: Comment::new("<() />", &id, true), #[cfg(not(all(target_arch = "wasm32", feature = "web")))] id, } diff --git a/leptos_dom/src/html.rs b/leptos_dom/src/html.rs index 0b6849b83..511accdbd 100644 --- a/leptos_dom/src/html.rs +++ b/leptos_dom/src/html.rs @@ -1,3 +1,4 @@ +use crate::hydration::HydrationKey; #[cfg(all(target_arch = "wasm32", feature = "web"))] use crate::events::*; #[cfg(all(target_arch = "wasm32", feature = "web"))] @@ -70,7 +71,7 @@ pub trait IntoElement: IntoElementBounds { /// A unique `id` that should be generated for each new instance of /// this element, and be consitant for both SSR and CSR. #[cfg(not(all(target_arch = "wasm32", feature = "web")))] - fn hydration_id(&self) -> usize; + fn hydration_id(&self) -> &HydrationKey; } /// Trait for converting any type which impl [`AsRef`] @@ -115,7 +116,7 @@ pub struct AnyElement { pub(crate) element: web_sys::HtmlElement, pub(crate) is_void: bool, #[cfg(not(all(target_arch = "wasm32", feature = "web")))] - pub(crate) id: usize, + pub(crate) id: HydrationKey, } impl std::ops::Deref for AnyElement { @@ -145,8 +146,8 @@ impl IntoElement for AnyElement { } #[cfg(not(all(target_arch = "wasm32", feature = "web")))] - fn hydration_id(&self) -> usize { - self.id + fn hydration_id(&self) -> &HydrationKey { + &self.id } } @@ -157,7 +158,7 @@ pub struct Custom { #[cfg(all(target_arch = "wasm32", feature = "web"))] element: web_sys::HtmlElement, #[cfg(not(all(target_arch = "wasm32", feature = "web")))] - id: usize, + id: HydrationKey, } #[cfg(all(target_arch = "wasm32", feature = "web"))] @@ -180,8 +181,8 @@ impl IntoElement for Custom { } #[cfg(not(all(target_arch = "wasm32", feature = "web")))] - fn hydration_id(&self) -> usize { - self.id + fn hydration_id(&self) -> &HydrationKey { + &self.id } } @@ -293,7 +294,7 @@ impl HtmlElement { element: AnyElement { name: element.name(), is_void: element.is_void(), - id: element.hydration_id(), + id: element.hydration_id().clone(), }, } } @@ -571,7 +572,7 @@ impl IntoView for HtmlElement { .. } = self; - let id = element.hydration_id(); + let id = element.hydration_id().clone(); let mut element = Element::new(element); let children = children; @@ -611,7 +612,7 @@ pub fn custom(cx: Scope, el: El) -> HtmlElement { #[cfg(all(target_arch = "wasm32", feature = "web"))] element: el.get_element().clone(), #[cfg(not(all(target_arch = "wasm32", feature = "web")))] - id: el.hydration_id(), + id: el.hydration_id().clone(), }, ) } @@ -644,7 +645,7 @@ macro_rules! generate_html_tags { #[cfg(all(target_arch = "wasm32", feature = "web"))] element: web_sys::HtmlElement, #[cfg(not(all(target_arch = "wasm32", feature = "web")))] - id: usize, + id: HydrationKey, } impl Default for [<$tag:camel $($trailing_)?>] { @@ -732,8 +733,8 @@ macro_rules! generate_html_tags { } #[cfg(not(all(target_arch = "wasm32", feature = "web")))] - fn hydration_id(&self) -> usize { - self.id + fn hydration_id(&self) -> &HydrationKey { + &self.id } generate_html_tags! { @void $($void)? } diff --git a/leptos_dom/src/hydration.rs b/leptos_dom/src/hydration.rs index 5b5c92ef3..736c22ccf 100644 --- a/leptos_dom/src/hydration.rs +++ b/leptos_dom/src/hydration.rs @@ -1,4 +1,6 @@ use leptos_reactive::Scope; +use std::fmt::Display; +use std::cell::RefCell; #[cfg(all(target_arch = "wasm32", feature = "web"))] use std::cell::LazyCell; @@ -10,53 +12,72 @@ use std::cell::LazyCell; #[thread_local] static mut IS_HYDRATING: LazyCell = LazyCell::new(|| { #[cfg(debug_assertions)] - return crate::document().get_element_by_id("_0").is_some() - || crate::document().get_element_by_id("_0o").is_some(); + return crate::document().get_element_by_id("_0-0-0").is_some() + || crate::document().get_element_by_id("_0-0-0o").is_some(); #[cfg(not(debug_assertions))] - return crate::document().get_element_by_id("_0").is_some(); + return crate::document().get_element_by_id("_0-0-0").is_some(); }); -#[thread_local] -static mut ID: usize = 0; +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct HydrationKey { + pub previous: String, + pub offset: usize +} -#[thread_local] -static mut FORCE_HK: bool = false; +impl Display for HydrationKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}{}", self.previous, self.offset) + } +} + +impl Default for HydrationKey { + fn default() -> Self { + Self { previous: "0-".to_string(), offset: 0 } + } +} + +thread_local!(static ID: RefCell = Default::default()); /// Control and utility methods for hydration. pub struct HydrationCtx; impl HydrationCtx { /// Get the next `id` without incrementing it. - pub fn peak() -> usize { - unsafe { ID } + pub fn peek() -> HydrationKey { + ID.with(|id| id.borrow().clone()) } /// Increments the current hydration `id` and returns it - pub fn id() -> usize { - unsafe { - let id = ID; - - // Prevent panics on long-running debug builds - ID = ID.wrapping_add(1); - - id - } + pub fn id() -> HydrationKey { + ID.with(|id| { + let mut id = id.borrow_mut(); + id.offset = id.offset.wrapping_add(1); + id.clone() + }) } - pub(crate) fn current_id() -> usize { - unsafe { ID } + /// Resets the hydration `id` for the next component, and returns it + pub fn next_component() -> HydrationKey { + ID.with(|id| { + let mut id = id.borrow_mut(); + let offset = id.offset; + id.previous.push_str(&offset.to_string()); + id.previous.push('-'); + id.offset = 0; + id.clone() + }) } #[cfg(not(all(target_arch = "wasm32", feature = "web")))] pub(crate) fn reset_id() { - unsafe { ID = 0 }; + ID.with(|id| *id.borrow_mut() = Default::default()); } /// Resums hydration from the provided `id`. Usefull for /// `Suspense` and other fancy things. - pub fn continue_from(id: usize) { - unsafe { ID = id } + pub fn continue_from(id: HydrationKey) { + ID.with(|i| *i.borrow_mut() = id); } #[cfg(all(target_arch = "wasm32", feature = "web"))] @@ -71,7 +92,7 @@ impl HydrationCtx { unsafe { *IS_HYDRATING } } - pub(crate) fn to_string(id: usize, closing: bool) -> String { + pub(crate) fn to_string(id: &HydrationKey, closing: bool) -> String { #[cfg(debug_assertions)] return format!("_{id}{}", if closing { 'c' } else { 'o' }); @@ -82,18 +103,4 @@ impl HydrationCtx { format!("_{id}") } } - - /// All components and elements created after this method is - /// called with use `leptos-hk` for their hydration `id`, - /// instead of `id`. - pub fn start_force_hk() { - unsafe { FORCE_HK = true } - } - - /// All components and elements created after this method is - /// called with use `id` by default for their hydration `id`, - /// instead of `leptos-hk`. - pub fn stop_force_hk() { - unsafe { FORCE_HK = false } - } } diff --git a/leptos_dom/src/lib.rs b/leptos_dom/src/lib.rs index abe5f5f97..e4d3ebec6 100644 --- a/leptos_dom/src/lib.rs +++ b/leptos_dom/src/lib.rs @@ -25,6 +25,7 @@ pub use events::typed as ev; pub use helpers::*; pub use html::*; pub use hydration::HydrationCtx; +use hydration::HydrationKey; pub use js_sys; use leptos_reactive::Scope; pub use logging::*; @@ -150,7 +151,7 @@ cfg_if! { attrs: SmallVec<[(Cow<'static, str>, Cow<'static, str>); 4]>, children: Vec, prerendered: Option>, - id: usize, + id: HydrationKey, } impl fmt::Debug for Element { @@ -245,7 +246,7 @@ impl Element { is_void: el.is_void(), attrs: Default::default(), children: Default::default(), - id: el.hydration_id(), + id: el.hydration_id().clone(), prerendered: None } } @@ -263,7 +264,7 @@ impl Element { is_void: el.is_void(), attrs: Default::default(), children: Default::default(), - id: el.hydration_id(), + id: el.hydration_id().clone(), prerendered: Some(html.into()), } } @@ -279,7 +280,7 @@ struct Comment { impl Comment { fn new( content: impl Into>, - id: usize, + id: &HydrationKey, closing: bool, ) -> Self { let content = content.into(); diff --git a/leptos_dom/src/ssr.rs b/leptos_dom/src/ssr.rs index 2474c9765..cad17101c 100644 --- a/leptos_dom/src/ssr.rs +++ b/leptos_dom/src/ssr.rs @@ -182,9 +182,9 @@ impl View { cfg_if! { if #[cfg(debug_assertions)] { format!(r#"{}"#, - HydrationCtx::to_string(node.id, false), + HydrationCtx::to_string(&node.id, false), content(), - HydrationCtx::to_string(node.id, true) + HydrationCtx::to_string(&node.id, true) ).into() } else { format!( @@ -198,11 +198,11 @@ impl View { View::CoreComponent(node) => { let (id, wrap, content) = match node { CoreComponent::Unit(u) => ( - u.id, + u.id.clone(), false, Box::new(move || format!( "", - HydrationCtx::to_string(u.id, true) + HydrationCtx::to_string(&u.id, true) ) .into()) as Box Cow<'static, str>>, ), @@ -251,9 +251,9 @@ impl View { return format!( "{}", - HydrationCtx::to_string(id, false), + HydrationCtx::to_string(&id, false), content(), - HydrationCtx::to_string(id, true), + HydrationCtx::to_string(&id, true), ); #[cfg(not(debug_assertions))] @@ -275,15 +275,15 @@ impl View { if #[cfg(debug_assertions)] { format!( r#"{}"#, - HydrationCtx::to_string(id, false), + HydrationCtx::to_string(&id, false), content(), - HydrationCtx::to_string(id, true), + HydrationCtx::to_string(&id, true), ).into() } else { format!( r#"{}"#, content(), - HydrationCtx::to_string(id, true) + HydrationCtx::to_string(&id, true) ).into() } } diff --git a/leptos_macro/src/view.rs b/leptos_macro/src/view.rs index 1bfc26c0d..63cb1b43d 100644 --- a/leptos_macro/src/view.rs +++ b/leptos_macro/src/view.rs @@ -155,7 +155,7 @@ pub(crate) fn render_view(cx: &Ident, nodes: &[Node], mode: Mode) -> TokenStream } else if nodes.len() == 1 { node_to_tokens(cx, &nodes[0]) } else { - fragment_to_tokens(cx, Span::call_site(), nodes) + fragment_to_tokens(cx, Span::call_site(), nodes, false) } } } @@ -475,7 +475,7 @@ fn set_class_attribute_ssr( } } -fn fragment_to_tokens(cx: &Ident, span: Span, nodes: &[Node]) -> TokenStream { +fn fragment_to_tokens(cx: &Ident, span: Span, nodes: &[Node], lazy: bool) -> TokenStream { let nodes = nodes.iter().map(|node| { let node = node_to_tokens(cx, node); @@ -483,18 +483,28 @@ fn fragment_to_tokens(cx: &Ident, span: Span, nodes: &[Node]) -> TokenStream { #node.into_view(#cx) } }); - quote! { - { - leptos::Fragment::new(vec![ - #(#nodes),* - ]) + if lazy { + quote! { + { + leptos::Fragment::lazy(|| vec![ + #(#nodes),* + ]) + } + } + } else { + quote! { + { + leptos::Fragment::new(vec![ + #(#nodes),* + ]) + } } } } fn node_to_tokens(cx: &Ident, node: &Node) -> TokenStream { match node { - Node::Fragment(fragment) => fragment_to_tokens(cx, Span::call_site(), &fragment.children), + Node::Fragment(fragment) => fragment_to_tokens(cx, Span::call_site(), &fragment.children, false), Node::Comment(_) | Node::Doctype(_) => quote! {}, Node::Text(node) => { let value = node.value.as_ref(); @@ -533,7 +543,7 @@ fn element_to_tokens(cx: &Ident, node: &NodeElement) -> TokenStream { let children = node.children.iter().map(|node| { let child = match node { Node::Fragment(fragment) => { - fragment_to_tokens(cx, Span::call_site(), &fragment.children) + fragment_to_tokens(cx, Span::call_site(), &fragment.children, false) } Node::Text(node) => { let span = node.value.span(); @@ -644,7 +654,7 @@ fn component_to_tokens(cx: &Ident, node: &NodeElement) -> TokenStream { let children = if node.children.is_empty() { quote! {} } else { - let children = fragment_to_tokens(cx, span, &node.children); + let children = fragment_to_tokens(cx, span, &node.children, true); quote! { .children(Box::new(move |#cx| #children)) } }; diff --git a/router/src/components/routes.rs b/router/src/components/routes.rs index f6964f693..473b5e023 100644 --- a/router/src/components/routes.rs +++ b/router/src/components/routes.rs @@ -200,7 +200,7 @@ pub fn Routes( }); Fragment::new_with_id( - frag.id(), + frag.id().clone(), vec![(move || root.get()).into_view(cx)] ) }