fix: properly handle islands used in the body of other islands (closes #2725)

This commit is contained in:
Greg Johnston 2024-07-29 10:20:51 -04:00
parent 6c749f5e24
commit 24775fb59b
4 changed files with 62 additions and 14 deletions

View File

@ -25,11 +25,7 @@ pub fn AutoReload(
};
let script = include_str!("reload_script.js");
view! {
<script nonce=nonce>
{format!("{script}({reload_port:?}, {protocol})")}
</script>
}
view! { <script nonce=nonce>{format!("{script}({reload_port:?}, {protocol})")}</script> }
})
}
@ -49,6 +45,9 @@ pub fn HydrationScripts(
#[cfg(not(feature = "nonce"))]
let nonce = None::<String>;
let script = if islands {
if let Some(sc) = Owner::current_shared_context() {
sc.set_is_hydrating(false);
}
include_str!("./island_script.js")
} else {
include_str!("./hydration_script.js")
@ -56,7 +55,13 @@ pub fn HydrationScripts(
view! {
<link rel="modulepreload" href=format!("/{pkg_path}/{output_name}.js") nonce=nonce.clone()/>
<link rel="preload" href=format!("/{pkg_path}/{wasm_output_name}.wasm") r#as="fetch" r#type="application/wasm" crossorigin=nonce.clone().unwrap_or_default()/>
<link
rel="preload"
href=format!("/{pkg_path}/{wasm_output_name}.wasm")
r#as="fetch"
r#type="application/wasm"
crossorigin=nonce.clone().unwrap_or_default()
/>
<script type="module" nonce=nonce>
{format!("{script}({pkg_path:?}, {output_name:?}, {wasm_output_name:?})")}
</script>

View File

@ -181,7 +181,7 @@ where
/// Hydrates any islands that are currently present on the page.
#[cfg(feature = "hydrate")]
pub fn hydrate_islands() {
use hydration_context::HydrateSharedContext;
use hydration_context::{HydrateSharedContext, SharedContext};
use std::sync::Arc;
// use wasm-bindgen-futures to drive the reactive system
@ -193,7 +193,9 @@ pub fn hydrate_islands() {
FIRST_CALL.set(false);
// create a new reactive owner and use it as the root node to run the app
let owner = Owner::new_root(Some(Arc::new(HydrateSharedContext::new())));
let sc = HydrateSharedContext::new();
sc.set_is_hydrating(false); // islands mode starts in "not hydrating"
let owner = Owner::new_root(Some(Arc::new(sc)));
owner.set();
std::mem::forget(owner);
}

View File

@ -260,11 +260,21 @@ impl ToTokens for Model {
let component = if *is_island {
quote! {
{
::leptos::tachys::html::islands::Island::new(
#component_id,
#component
)
#island_serialized_props
if ::leptos::reactive_graph::owner::Owner::current_shared_context()
.map(|sc| sc.get_is_hydrating())
.unwrap_or(false) {
::leptos::either::Either::Left(
#component
)
} else {
::leptos::either::Either::Right(
::leptos::tachys::html::islands::Island::new(
#component_id,
#component
)
#island_serialized_props
)
}
}
}
} else {
@ -285,7 +295,15 @@ impl ToTokens for Model {
let wrapped_children = if is_island_with_children {
quote! {
use leptos::tachys::view::any_view::IntoAny;
let children = Box::new(|| ::leptos::tachys::html::islands::IslandChildren::new(children()).into_any());
let children = Box::new(|| {
let sc = ::leptos::reactive_graph::owner::Owner::current_shared_context().unwrap();
let prev = sc.get_is_hydrating();
let value = ::leptos::reactive_graph::owner::Owner::with_no_hydration(||
::leptos::tachys::html::islands::IslandChildren::new(children()).into_any()
);
sc.set_is_hydrating(prev);
value
});
}
} else {
quote! {}

View File

@ -274,6 +274,29 @@ impl Owner {
inner(Box::new(fun))
}
/// Runs the given function, after indicating that the current [`SharedContext`] should /// not handle data created in this function.
#[cfg(feature = "hydration")]
pub fn with_no_hydration<T>(fun: impl FnOnce() -> T + 'static) -> T {
fn inner<T>(fun: Box<dyn FnOnce() -> T>) -> T {
let sc = OWNER.with_borrow(|o| {
o.as_ref()
.and_then(|current| current.shared_context.clone())
});
match sc {
None => fun(),
Some(sc) => {
let prev = sc.get_is_hydrating();
sc.set_is_hydrating(false);
let value = fun();
sc.set_is_hydrating(prev);
value
}
}
}
inner(Box::new(fun))
}
}
/// Registers a function to be run the next time the current owner is cleaned up.