Skip to content

Commit 91baa21

Browse files
author
Dillen Meijboom
committed
feat: implement hydration for vraw
1 parent 998db3d commit 91baa21

File tree

5 files changed

+104
-5
lines changed

5 files changed

+104
-5
lines changed

packages/yew/src/dom_bundle/bnode.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -267,9 +267,7 @@ mod feat_hydration {
267267
VNode::VSuspense(vsuspense) => vsuspense
268268
.hydrate(root, parent_scope, parent, fragment)
269269
.into(),
270-
VNode::VRaw(_) => {
271-
panic!("VRaw is not hydratable (raw HTML string cannot be hydrated)")
272-
}
270+
VNode::VRaw(vraw) => vraw.hydrate(root, parent_scope, parent, fragment).into(),
273271
}
274272
}
275273
}

packages/yew/src/dom_bundle/braw.rs

+28
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,34 @@ impl Reconcilable for VRaw {
125125
}
126126
}
127127

128+
#[cfg(feature = "hydration")]
129+
mod feat_hydration {
130+
use super::*;
131+
use crate::dom_bundle::{Fragment, Hydratable};
132+
use crate::virtual_dom::Collectable;
133+
134+
impl Hydratable for VRaw {
135+
fn hydrate(
136+
self,
137+
_root: &BSubtree,
138+
_parent_scope: &AnyScope,
139+
parent: &Element,
140+
fragment: &mut Fragment,
141+
) -> Self::Bundle {
142+
let collectable = Collectable::Raw;
143+
let fallback_fragment = Fragment::collect_between(fragment, &collectable, parent);
144+
145+
let Self { html } = self;
146+
147+
BRaw {
148+
children_count: fallback_fragment.len(),
149+
reference: fallback_fragment.iter().next().cloned(),
150+
html,
151+
}
152+
}
153+
}
154+
}
155+
128156
#[cfg(target_arch = "wasm32")]
129157
#[cfg(test)]
130158
mod tests {

packages/yew/src/virtual_dom/mod.rs

+7
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ mod feat_ssr_hydration {
6464
/// This indicates a kind that can be collected from fragment to be processed at a later time
6565
pub enum Collectable {
6666
Component(ComponentName),
67+
Raw,
6768
Suspense,
6869
}
6970

@@ -79,20 +80,23 @@ mod feat_ssr_hydration {
7980
pub fn open_start_mark(&self) -> &'static str {
8081
match self {
8182
Self::Component(_) => "<[",
83+
Self::Raw => "<#",
8284
Self::Suspense => "<?",
8385
}
8486
}
8587

8688
pub fn close_start_mark(&self) -> &'static str {
8789
match self {
8890
Self::Component(_) => "</[",
91+
Self::Raw => "</#",
8992
Self::Suspense => "</?",
9093
}
9194
}
9295

9396
pub fn end_mark(&self) -> &'static str {
9497
match self {
9598
Self::Component(_) => "]>",
99+
Self::Raw => ">",
96100
Self::Suspense => ">",
97101
}
98102
}
@@ -104,6 +108,7 @@ mod feat_ssr_hydration {
104108
Self::Component(m) => format!("Component({m})").into(),
105109
#[cfg(not(debug_assertions))]
106110
Self::Component(_) => "Component".into(),
111+
Self::Raw => "Raw".into(),
107112
Self::Suspense => "Suspense".into(),
108113
}
109114
}
@@ -130,6 +135,7 @@ mod feat_ssr {
130135
Self::Component(type_name) => {
131136
let _ = w.write_str(type_name);
132137
}
138+
Self::Raw => {}
133139
Self::Suspense => {}
134140
}
135141

@@ -146,6 +152,7 @@ mod feat_ssr {
146152
Self::Component(type_name) => {
147153
let _ = w.write_str(type_name);
148154
}
155+
Self::Raw => {}
149156
Self::Suspense => {}
150157
}
151158

packages/yew/src/virtual_dom/vraw.rs

+12-1
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,26 @@ mod feat_ssr {
1919
use super::*;
2020
use crate::html::AnyScope;
2121
use crate::platform::fmt::BufWriter;
22+
use crate::virtual_dom::Collectable;
2223

2324
impl VRaw {
2425
pub(crate) async fn render_into_stream(
2526
&self,
2627
w: &mut BufWriter,
2728
_parent_scope: &AnyScope,
28-
_hydratable: bool,
29+
hydratable: bool,
2930
) {
31+
let collectable = Collectable::Raw;
32+
33+
if hydratable {
34+
collectable.write_open_tag(w);
35+
}
36+
3037
let _ = w.write_str(self.html.as_ref());
38+
39+
if hydratable {
40+
collectable.write_close_tag(w);
41+
}
3142
}
3243
}
3344
}

packages/yew/tests/hydration.rs

+56-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ use web_sys::{HtmlElement, HtmlTextAreaElement};
1515
use yew::platform::time::sleep;
1616
use yew::prelude::*;
1717
use yew::suspense::{use_future, Suspension, SuspensionResult};
18-
use yew::{Renderer, ServerRenderer};
18+
use yew::virtual_dom::VNode;
19+
use yew::{function_component, Renderer, ServerRenderer};
1920

2021
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
2122

@@ -94,6 +95,60 @@ async fn hydration_works() {
9495
);
9596
}
9697

98+
#[wasm_bindgen_test]
99+
async fn hydration_with_raw() {
100+
#[function_component(Content)]
101+
fn content() -> Html {
102+
let vnode = VNode::from_html_unchecked("<div><p>Hello World</p></div>".into());
103+
104+
html! {
105+
<div class="content-area">
106+
{vnode}
107+
</div>
108+
}
109+
}
110+
111+
#[function_component(App)]
112+
fn app() -> Html {
113+
html! {
114+
<div id="result">
115+
<Content />
116+
</div>
117+
}
118+
}
119+
120+
let s = ServerRenderer::<App>::new().render().await;
121+
122+
gloo::utils::document()
123+
.query_selector("#output")
124+
.unwrap()
125+
.unwrap()
126+
.set_inner_html(&s);
127+
128+
sleep(Duration::from_millis(10)).await;
129+
130+
Renderer::<App>::with_root(gloo::utils::document().get_element_by_id("output").unwrap())
131+
.hydrate();
132+
133+
let result = obtain_result();
134+
135+
// still hydrating, during hydration, the server rendered result is shown.
136+
assert_eq!(
137+
result.as_str(),
138+
r#"<!--<[hydration::hydration_with_raw::{{closure}}::Content]>--><div class="content-area"><!--<#>--><div><p>Hello World</p></div><!--</#>--></div><!--</[hydration::hydration_with_raw::{{closure}}::Content]>-->"#
139+
);
140+
141+
sleep(Duration::from_millis(50)).await;
142+
143+
let result = obtain_result();
144+
145+
// hydrated.
146+
assert_eq!(
147+
result.as_str(),
148+
r#"<div class="content-area"><div><p>Hello World</p></div></div>"#
149+
);
150+
}
151+
97152
#[wasm_bindgen_test]
98153
async fn hydration_with_suspense() {
99154
#[derive(PartialEq)]

0 commit comments

Comments
 (0)