Use comments instead of element markers for hydration -- fixes issue #320

This commit is contained in:
Greg Johnston 2023-01-16 09:21:28 -05:00
parent 61ca6465df
commit d049d2f36b
4 changed files with 56 additions and 45 deletions

View file

@ -39,6 +39,7 @@ features = [
"Range",
"Text",
"HtmlCollection",
"TreeWalker",
# Events we cast to in leptos_macro -- added here so we don't force users to import them
"AnimationEvent",

View file

@ -1,21 +1,50 @@
use cfg_if::cfg_if;
use std::{cell::RefCell, fmt::Display};
#[cfg(all(target_arch = "wasm32", feature = "web"))]
use once_cell::unsync::Lazy as LazyCell;
cfg_if! {
if #[cfg(all(target_arch = "wasm32", feature = "web"))] {
use once_cell::unsync::Lazy as LazyCell;
use std::collections::HashMap;
use wasm_bindgen::JsCast;
// We can tell if we start in hydration mode by checking to see if the
// id "_0-0-0" is present in the DOM. If it is, we know we are hydrating from
// the server, if not, we are starting off in CSR
#[cfg(all(target_arch = "wasm32", feature = "web"))]
thread_local! {
static IS_HYDRATING: RefCell<LazyCell<bool>> = RefCell::new(LazyCell::new(|| {
#[cfg(debug_assertions)]
return crate::document().get_element_by_id("_0-0-0").is_some()
|| crate::document().get_element_by_id("_0-0-0o").is_some();
// We can tell if we start in hydration mode by checking to see if the
// id "_0-0-0" is present in the DOM. If it is, we know we are hydrating from
// the server, if not, we are starting off in CSR
thread_local! {
static HYDRATION_COMMENTS: LazyCell<HashMap<String, web_sys::Comment>> = LazyCell::new(|| {
let document = crate::document();
let body = document.body().unwrap();
let walker = document
.create_tree_walker_with_what_to_show(&body, 128)
.unwrap();
let mut map = HashMap::new();
while let Ok(Some(node)) = walker.next_node() {
if let Some(content) = node.text_content() {
if let Some(hk) = content.strip_prefix("hk=") {
if let Some(hk) = hk.split("|").next() {
map.insert(hk.into(), node.unchecked_into());
}
}
}
}
map
});
#[cfg(not(debug_assertions))]
return crate::document().get_element_by_id("_0-0-0").is_some();
}));
static IS_HYDRATING: RefCell<LazyCell<bool>> = RefCell::new(LazyCell::new(|| {
#[cfg(debug_assertions)]
return crate::document().get_element_by_id("_0-0-0").is_some()
|| crate::document().get_element_by_id("_0-0-0o").is_some()
|| HYDRATION_COMMENTS.with(|comments| comments.get("_0-0-0o").is_some());
#[cfg(not(debug_assertions))]
return crate::document().get_element_by_id("_0-0-0").is_some();
}));
}
pub(crate) fn get_marker(id: &str) -> Option<web_sys::Comment> {
HYDRATION_COMMENTS.with(|comments| comments.get(id).cloned())
}
}
}
/// A stable identifer within the server-rendering or hydration process.

View file

@ -303,7 +303,7 @@ impl Comment {
if HydrationCtx::is_hydrating() {
let id = HydrationCtx::to_string(id, closing);
if let Some(marker) = document().get_element_by_id(&id) {
if let Some(marker) = hydration::get_marker(&id) {
marker.before_with_node_1(&node).unwrap();
marker.remove();

View file

@ -16,7 +16,7 @@ use std::borrow::Cow;
/// <p>"Hello, world!"</p>
/// });
/// // static HTML includes some hydration info
/// assert_eq!(html, "<style>[leptos]{display:none;}</style><p id=\"_0-1\">Hello, world!</p>");
/// assert_eq!(html, "<p id=\"_0-1\">Hello, world!</p>");
/// # }}
/// ```
pub fn render_to_string<F, N>(f: F) -> String
@ -33,13 +33,7 @@ where
runtime.dispose();
#[cfg(debug_assertions)]
{
format!("<style>[leptos]{{display:none;}}</style>{html}")
}
#[cfg(not(debug_assertions))]
format!("<style>l-m{{display:none;}}</style>{html}")
html.into()
}
/// Renders a function to a stream of HTML strings.
@ -122,16 +116,6 @@ pub fn render_to_stream_with_prefix_undisposed(
let pending_resources = serde_json::to_string(&resources).unwrap();
let prefix = prefix(cx);
let shell = {
#[cfg(debug_assertions)]
{
format!("<style>[leptos]{{display:none;}}</style>{shell}")
}
#[cfg(not(debug_assertions))]
format!("<style>l-m{{display:none;}}</style>{shell}")
};
(
shell,
prefix,
@ -218,7 +202,7 @@ impl View {
};
cfg_if! {
if #[cfg(debug_assertions)] {
format!(r#"<leptos-{name}-start leptos id="{}"></leptos-{name}-start>{}<leptos-{name}-end leptos id="{}"></leptos-{name}-end>"#,
format!(r#"<!--hk={}|leptos-{name}-start-->{}<!--hk={}|leptos-{name}-end-->"#,
HydrationCtx::to_string(&node.id, false),
content(),
HydrationCtx::to_string(&node.id, true),
@ -226,7 +210,7 @@ impl View {
).into()
} else {
format!(
r#"{}<l-m id="{}"></l-m>"#,
r#"{}<!--hk={}-->"#,
content(),
HydrationCtx::to_string(&node.id, true)
).into()
@ -243,14 +227,14 @@ impl View {
#[cfg(debug_assertions)]
{
format!(
"<leptos-unit leptos id={}></leptos-unit>",
"<!--hk={}|leptos-unit-->",
HydrationCtx::to_string(&u.id, true)
)
.into()
}
#[cfg(not(debug_assertions))]
format!("<l-m id={}></l-m>", HydrationCtx::to_string(&u.id, true))
format!("<!--hk={}-->", HydrationCtx::to_string(&u.id, true))
.into()
}) as Box<dyn FnOnce() -> Cow<'static, str>>,
),
@ -286,7 +270,6 @@ impl View {
}
CoreComponent::Each(node) => {
let children = node.children.take();
(
node.id,
"each",
@ -303,10 +286,8 @@ impl View {
#[cfg(debug_assertions)]
{
format!(
"<leptos-each-item-start leptos \
id=\"{}\"></\
leptos-each-item-start>{}<leptos-each-item-end \
leptos id=\"{}\"></leptos-each-item-end>",
"<!--hk={}|leptos-each-item-start-->{}\
<!--hk={}|leptos-each-item-end-->",
HydrationCtx::to_string(&id, false),
content(),
HydrationCtx::to_string(&id, true),
@ -315,7 +296,7 @@ impl View {
#[cfg(not(debug_assertions))]
format!(
"{}<l-m id=\"{}\"></l-m>",
"{}<!--hk={}-->",
content(),
HydrationCtx::to_string(&id, true)
)
@ -331,7 +312,7 @@ impl View {
cfg_if! {
if #[cfg(debug_assertions)] {
format!(
r#"<leptos-{name}-start leptos id="{}"></leptos-{name}-start>{}<leptos-{name}-end leptos id="{}"></leptos-{name}-end>"#,
r#"<!--hk={}|leptos-{name}-start-->{}<!--hk={}|leptos-{name}-end-->"#,
HydrationCtx::to_string(&id, false),
content(),
HydrationCtx::to_string(&id, true),
@ -340,7 +321,7 @@ impl View {
let _ = name;
format!(
r#"{}<l-m id="{}"></l-m>"#,
r#"{}<!--hk={}-->"#,
content(),
HydrationCtx::to_string(&id, true)
).into()