Fix `<Suspense/>` SSR
This commit is contained in:
parent
c6d30a710a
commit
01252b841d
|
@ -1,3 +1,5 @@
|
|||
use std::rc::Rc;
|
||||
|
||||
use leptos_dom::HydrationCtx;
|
||||
use leptos_dom::{Fragment, IntoView};
|
||||
use leptos_reactive::{provide_context, Scope, SuspenseContext};
|
||||
|
@ -75,7 +77,7 @@ where
|
|||
// provide this SuspenseContext to any resources below it
|
||||
provide_context(cx, context);
|
||||
|
||||
render_suspense(cx, context, props.fallback, props.children)
|
||||
render_suspense(cx, context, props.fallback, Rc::new(move |cx| (props.children)(cx)))
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "csr", feature = "hydrate"))]
|
||||
|
@ -83,35 +85,20 @@ fn render_suspense<'a, F, E>(
|
|||
cx: Scope,
|
||||
context: SuspenseContext,
|
||||
fallback: F,
|
||||
child: Box<dyn Fn(Scope) -> Fragment>,
|
||||
child: Rc<dyn Fn(Scope) -> Fragment>,
|
||||
) -> impl IntoView
|
||||
where
|
||||
F: Fn() -> E + 'static,
|
||||
E: IntoView,
|
||||
{
|
||||
use std::cell::RefCell;
|
||||
|
||||
use leptos_dom::DynChild;
|
||||
|
||||
let cached_id = RefCell::new(None);
|
||||
let cached_id = HydrationCtx::peak();
|
||||
|
||||
DynChild::new(move || {
|
||||
let mut cached_id_borrow = cached_id.borrow_mut();
|
||||
|
||||
let first_run = if cached_id_borrow.is_none() {
|
||||
*cached_id_borrow = Some(HydrationCtx::peak());
|
||||
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if context.ready() {
|
||||
leptos_dom::warn!("<Suspense/> ready");
|
||||
if let Some(id) = *cached_id_borrow {
|
||||
leptos_dom::warn!(" <Suspense/> continuing from {}", id);
|
||||
HydrationCtx::continue_from(id);
|
||||
}
|
||||
leptos_dom::warn!("<Suspense/> ready and continuing from {}", cached_id);
|
||||
HydrationCtx::continue_from(cached_id);
|
||||
|
||||
child(cx).into_view(cx)
|
||||
} else {
|
||||
|
@ -126,35 +113,47 @@ fn render_suspense<'a, F, E>(
|
|||
cx: Scope,
|
||||
context: SuspenseContext,
|
||||
fallback: F,
|
||||
orig_child: Box<dyn Fn(Scope) -> Fragment>,
|
||||
orig_child: Rc<dyn Fn(Scope) -> Fragment>,
|
||||
) -> impl IntoView
|
||||
where
|
||||
F: Fn() -> E + 'static,
|
||||
E: IntoView,
|
||||
{
|
||||
use leptos_dom::*;
|
||||
use std::cell::RefCell;
|
||||
|
||||
use leptos_dom::DynChild;
|
||||
|
||||
let orig_child = Rc::clone(&orig_child);
|
||||
let current_id = HydrationCtx::peak();
|
||||
|
||||
DynChild::new(move || {
|
||||
|
||||
let initial = {
|
||||
// run the child; we'll probably throw this away, but it will register resource reads
|
||||
let child = orig_child(cx).into_view(cx);
|
||||
|
||||
// no resources were read under this, so just return the child
|
||||
if context.pending_resources.get() == 0 {
|
||||
child
|
||||
}
|
||||
// show the fallback, but also prepare to stream HTML
|
||||
else {
|
||||
let key = cx.current_fragment_key();
|
||||
cx.register_suspense(context, &key, move || {
|
||||
orig_child(cx)
|
||||
.into_view(cx)
|
||||
.render_to_string(cx)
|
||||
.to_string()
|
||||
});
|
||||
|
||||
// return the fallback for now, wrapped in fragment identifer
|
||||
div(cx).id(key.to_string()).child(fallback).into_view(cx)
|
||||
}
|
||||
};
|
||||
initial
|
||||
let initial = {
|
||||
// no resources were read under this, so just return the child
|
||||
if context.pending_resources.get() == 0 {
|
||||
child.clone()
|
||||
}
|
||||
// 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 || {
|
||||
orig_child(cx)
|
||||
.into_view(cx)
|
||||
.render_to_string(cx)
|
||||
.to_string()
|
||||
});
|
||||
|
||||
// return the fallback for now, wrapped in fragment identifer
|
||||
println!("continuing from...");
|
||||
HydrationCtx::continue_from(current_id);
|
||||
fallback().into_view(cx)
|
||||
//Fragment::new(vec![fallback().into_view(cx)]).into_view(cx)
|
||||
//div(cx).attr("data-l-fragment", key.to_string()).child(fallback).into_view(cx)
|
||||
}
|
||||
};
|
||||
initial
|
||||
}).into_view(cx)
|
||||
}
|
||||
|
|
|
@ -112,9 +112,14 @@ pub fn render_to_stream_with_prefix(
|
|||
r#"
|
||||
<template id="{fragment_id}f">{html}</template>
|
||||
<script>
|
||||
var frag = document.getElementById("{fragment_id}");
|
||||
var start = document.getElementById("_{fragment_id}o");
|
||||
var end = document.getElementById("_{fragment_id}c");
|
||||
var range = new Range();
|
||||
range.setStartBefore(start.nextSibling);
|
||||
range.setEndAfter(end.previousSibling);
|
||||
range.deleteContents();
|
||||
var tpl = document.getElementById("{fragment_id}f");
|
||||
if(frag) frag.replaceWith(tpl.content.cloneNode(true));
|
||||
end.parentNode.insertBefore(tpl.content.cloneNode(true), end);
|
||||
</script>
|
||||
"#
|
||||
)
|
||||
|
@ -169,20 +174,23 @@ impl View {
|
|||
match self {
|
||||
View::Text(node) => node.content,
|
||||
View::Component(node) => {
|
||||
let content = node
|
||||
println!("rendering <{}/> with id {}", node.name, node.id);
|
||||
let content = || node
|
||||
.children
|
||||
.into_iter()
|
||||
.map(|node| node.render_to_string_helper())
|
||||
.join("");
|
||||
cfg_if! {
|
||||
if #[cfg(debug_assertions)] {
|
||||
format!(r#"<template id="{}"></template>{content}<template id="{}"></template>"#,
|
||||
format!(r#"<template id="{}"></template>{}<template id="{}"></template>"#,
|
||||
HydrationCtx::to_string(node.id, false),
|
||||
content(),
|
||||
HydrationCtx::to_string(node.id, true)
|
||||
).into()
|
||||
} else {
|
||||
format!(
|
||||
r#"{content}<template id="{}"></template>"#,
|
||||
r#"{}<template id="{}"></template>"#,
|
||||
content(),
|
||||
HydrationCtx::to_string(node.id, true)
|
||||
).into()
|
||||
}
|
||||
|
@ -193,18 +201,18 @@ impl View {
|
|||
CoreComponent::Unit(u) => (
|
||||
u.id,
|
||||
false,
|
||||
format!(
|
||||
Box::new(move || format!(
|
||||
"<template id={}></template>",
|
||||
HydrationCtx::to_string(u.id, true)
|
||||
)
|
||||
.into(),
|
||||
.into()) as Box<dyn FnOnce() -> Cow<'static, str>>,
|
||||
),
|
||||
CoreComponent::DynChild(node) => {
|
||||
let child = node.child.take();
|
||||
(
|
||||
node.id,
|
||||
true,
|
||||
if let Some(child) = *child {
|
||||
Box::new(move || if let Some(child) = *child {
|
||||
// On debug builds, `DynChild` has two marker nodes,
|
||||
// so there is no way for the text to be merged with
|
||||
// surrounding text when the browser parses the HTML,
|
||||
|
@ -223,7 +231,7 @@ impl View {
|
|||
}
|
||||
} else {
|
||||
"".into()
|
||||
},
|
||||
}) as Box<dyn FnOnce() -> Cow<'static, str>>
|
||||
)
|
||||
}
|
||||
CoreComponent::Each(node) => {
|
||||
|
@ -232,30 +240,33 @@ impl View {
|
|||
(
|
||||
node.id,
|
||||
true,
|
||||
children
|
||||
Box::new(move || children
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.map(|node| {
|
||||
let id = node.id;
|
||||
|
||||
let content = node.child.render_to_string_helper();
|
||||
let content = || node.child.render_to_string_helper();
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
return format!(
|
||||
"<template id=\"{}\"></template>{content}<template \
|
||||
"<template id=\"{}\"></template>{}<template \
|
||||
id=\"{}\"></template>",
|
||||
HydrationCtx::to_string(id, false),
|
||||
content(),
|
||||
HydrationCtx::to_string(id, true),
|
||||
);
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
return format!(
|
||||
"{content}<template id=\"{}c\"></template>",
|
||||
"{}<template id=\"{}c\"></template>",
|
||||
content(),
|
||||
HydrationCtx::to_string(id, true)
|
||||
);
|
||||
})
|
||||
.join("")
|
||||
.into(),
|
||||
.into()
|
||||
) as Box<dyn FnOnce() -> Cow<'static, str>>,
|
||||
)
|
||||
}
|
||||
};
|
||||
|
@ -264,19 +275,21 @@ impl View {
|
|||
cfg_if! {
|
||||
if #[cfg(debug_assertions)] {
|
||||
format!(
|
||||
r#"<template id="{}"></template>{content}<template id="{}"></template>"#,
|
||||
r#"<template id="{}"></template>{}<template id="{}"></template>"#,
|
||||
HydrationCtx::to_string(id, false),
|
||||
content(),
|
||||
HydrationCtx::to_string(id, true),
|
||||
).into()
|
||||
} else {
|
||||
format!(
|
||||
r#"{content}<template id="{}"></template>"#,
|
||||
r#"{}<template id="{}"></template>"#,
|
||||
content(),
|
||||
HydrationCtx::to_string(id, true)
|
||||
).into()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
content
|
||||
content()
|
||||
}
|
||||
}
|
||||
View::Element(el) => {
|
||||
|
|
Loading…
Reference in New Issue