From d0205a8ea3c9061fee2b1044ed107d1c98e811a6 Mon Sep 17 00:00:00 2001 From: Dillen Meijboom Date: Tue, 25 Apr 2023 23:48:04 +0200 Subject: [PATCH] feat: implement hydration for vraw (#3245) --- packages/yew/src/dom_bundle/bnode.rs | 4 +- packages/yew/src/dom_bundle/braw.rs | 28 ++++++++++++++ packages/yew/src/virtual_dom/mod.rs | 7 ++++ packages/yew/src/virtual_dom/vraw.rs | 13 ++++++- packages/yew/tests/hydration.rs | 57 +++++++++++++++++++++++++++- 5 files changed, 104 insertions(+), 5 deletions(-) diff --git a/packages/yew/src/dom_bundle/bnode.rs b/packages/yew/src/dom_bundle/bnode.rs index 6646cf78f..d6fdbe5aa 100644 --- a/packages/yew/src/dom_bundle/bnode.rs +++ b/packages/yew/src/dom_bundle/bnode.rs @@ -267,9 +267,7 @@ mod feat_hydration { VNode::VSuspense(vsuspense) => vsuspense .hydrate(root, parent_scope, parent, fragment) .into(), - VNode::VRaw(_) => { - panic!("VRaw is not hydratable (raw HTML string cannot be hydrated)") - } + VNode::VRaw(vraw) => vraw.hydrate(root, parent_scope, parent, fragment).into(), } } } diff --git a/packages/yew/src/dom_bundle/braw.rs b/packages/yew/src/dom_bundle/braw.rs index 18452c885..0d122a87c 100644 --- a/packages/yew/src/dom_bundle/braw.rs +++ b/packages/yew/src/dom_bundle/braw.rs @@ -125,6 +125,34 @@ impl Reconcilable for VRaw { } } +#[cfg(feature = "hydration")] +mod feat_hydration { + use super::*; + use crate::dom_bundle::{Fragment, Hydratable}; + use crate::virtual_dom::Collectable; + + impl Hydratable for VRaw { + fn hydrate( + self, + _root: &BSubtree, + _parent_scope: &AnyScope, + parent: &Element, + fragment: &mut Fragment, + ) -> Self::Bundle { + let collectable = Collectable::Raw; + let fallback_fragment = Fragment::collect_between(fragment, &collectable, parent); + + let Self { html } = self; + + BRaw { + children_count: fallback_fragment.len(), + reference: fallback_fragment.iter().next().cloned(), + html, + } + } + } +} + #[cfg(target_arch = "wasm32")] #[cfg(test)] mod tests { diff --git a/packages/yew/src/virtual_dom/mod.rs b/packages/yew/src/virtual_dom/mod.rs index 3fa5a0fc6..c90a175c9 100644 --- a/packages/yew/src/virtual_dom/mod.rs +++ b/packages/yew/src/virtual_dom/mod.rs @@ -64,6 +64,7 @@ mod feat_ssr_hydration { /// This indicates a kind that can be collected from fragment to be processed at a later time pub enum Collectable { Component(ComponentName), + Raw, Suspense, } @@ -79,6 +80,7 @@ mod feat_ssr_hydration { pub fn open_start_mark(&self) -> &'static str { match self { Self::Component(_) => "<[", + Self::Raw => "<#", Self::Suspense => " &'static str { match self { Self::Component(_) => " " " &'static str { match self { Self::Component(_) => "]>", + Self::Raw => ">", Self::Suspense => ">", } } @@ -104,6 +108,7 @@ mod feat_ssr_hydration { Self::Component(m) => format!("Component({m})").into(), #[cfg(not(debug_assertions))] Self::Component(_) => "Component".into(), + Self::Raw => "Raw".into(), Self::Suspense => "Suspense".into(), } } @@ -130,6 +135,7 @@ mod feat_ssr { Self::Component(type_name) => { let _ = w.write_str(type_name); } + Self::Raw => {} Self::Suspense => {} } @@ -146,6 +152,7 @@ mod feat_ssr { Self::Component(type_name) => { let _ = w.write_str(type_name); } + Self::Raw => {} Self::Suspense => {} } diff --git a/packages/yew/src/virtual_dom/vraw.rs b/packages/yew/src/virtual_dom/vraw.rs index f710e0d91..366e5248f 100644 --- a/packages/yew/src/virtual_dom/vraw.rs +++ b/packages/yew/src/virtual_dom/vraw.rs @@ -19,15 +19,26 @@ mod feat_ssr { use super::*; use crate::html::AnyScope; use crate::platform::fmt::BufWriter; + use crate::virtual_dom::Collectable; impl VRaw { pub(crate) async fn render_into_stream( &self, w: &mut BufWriter, _parent_scope: &AnyScope, - _hydratable: bool, + hydratable: bool, ) { + let collectable = Collectable::Raw; + + if hydratable { + collectable.write_open_tag(w); + } + let _ = w.write_str(self.html.as_ref()); + + if hydratable { + collectable.write_close_tag(w); + } } } } diff --git a/packages/yew/tests/hydration.rs b/packages/yew/tests/hydration.rs index 1ab95b0e2..ecaa11eb1 100644 --- a/packages/yew/tests/hydration.rs +++ b/packages/yew/tests/hydration.rs @@ -15,7 +15,8 @@ use web_sys::{HtmlElement, HtmlTextAreaElement}; use yew::platform::time::sleep; use yew::prelude::*; use yew::suspense::{use_future, Suspension, SuspensionResult}; -use yew::{Renderer, ServerRenderer}; +use yew::virtual_dom::VNode; +use yew::{function_component, Renderer, ServerRenderer}; wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); @@ -94,6 +95,60 @@ async fn hydration_works() { ); } +#[wasm_bindgen_test] +async fn hydration_with_raw() { + #[function_component(Content)] + fn content() -> Html { + let vnode = VNode::from_html_unchecked("

Hello World

".into()); + + html! { +
+ {vnode} +
+ } + } + + #[function_component(App)] + fn app() -> Html { + html! { +
+ +
+ } + } + + let s = ServerRenderer::::new().render().await; + + gloo::utils::document() + .query_selector("#output") + .unwrap() + .unwrap() + .set_inner_html(&s); + + sleep(Duration::from_millis(10)).await; + + Renderer::::with_root(gloo::utils::document().get_element_by_id("output").unwrap()) + .hydrate(); + + let result = obtain_result(); + + // still hydrating, during hydration, the server rendered result is shown. + assert_eq!( + result.as_str(), + r#"

Hello World

"# + ); + + sleep(Duration::from_millis(50)).await; + + let result = obtain_result(); + + // hydrated. + assert_eq!( + result.as_str(), + r#"

Hello World

"# + ); +} + #[wasm_bindgen_test] async fn hydration_with_suspense() { #[derive(PartialEq)]