Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Server-side Rendering (without hydration) #2335

Merged
merged 31 commits into from
Jan 12, 2022
Merged
Changes from 1 commit
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
65af3a2
Basic render to html implementation.
futursolo Jan 6, 2022
667796d
Remove HtmlWriter.
futursolo Jan 6, 2022
820d3ac
Escape html content.
futursolo Jan 6, 2022
1b2d823
Add non-suspense tests.
futursolo Jan 6, 2022
5b3f5e3
Add Suspense tests.
futursolo Jan 6, 2022
c7c9d2d
Gated "ssr" feature.
futursolo Jan 6, 2022
2bab219
Add example.
futursolo Jan 6, 2022
90deefc
merge master into ssr
futursolo Jan 6, 2022
482865e
Fix tests.
futursolo Jan 6, 2022
10f4ac4
Fix docs.
futursolo Jan 6, 2022
77b48c0
Fix heading size.
futursolo Jan 6, 2022
476300a
Remove the unused YewRenderer.
futursolo Jan 6, 2022
95e1d39
Remove extra comment.
futursolo Jan 6, 2022
655afd4
unify naming.
futursolo Jan 6, 2022
26bebb5
Update docs.
futursolo Jan 7, 2022
61f44d1
Update docs.
futursolo Jan 7, 2022
826b431
Update docs.
futursolo Jan 7, 2022
ed138b1
Isolate spawn_local.
futursolo Jan 7, 2022
bd26db4
Add doc flags.
futursolo Jan 7, 2022
f003aab
Add ssr feature to docs.
futursolo Jan 7, 2022
e56fcef
Move ServerRenderer into their own file.
futursolo Jan 7, 2022
ebe8c68
Fix docs.
futursolo Jan 7, 2022
10d7295
Update features and docs.
futursolo Jan 8, 2022
8635d63
Fix example.
futursolo Jan 8, 2022
3599240
Adjust comment position.
futursolo Jan 8, 2022
39a3d06
Fix effects being wrongly called when a component is suspended.
futursolo Jan 9, 2022
49566a0
Fix clippy.
futursolo Jan 9, 2022
17fd3b9
Uuid & no double boxing.
futursolo Jan 11, 2022
c67de39
merge master into ssr
futursolo Jan 11, 2022
0f24ae6
Merge branch 'master' into ssr
futursolo Jan 11, 2022
3567689
Merge branch 'master' into fursolo-ssr
ranile Jan 12, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add Suspense tests.
futursolo committed Jan 6, 2022
commit 5b3f5e356e104289b422190a21e108bfcededc28
4 changes: 3 additions & 1 deletion packages/yew/src/html/component/lifecycle.rs
Original file line number Diff line number Diff line change
@@ -258,7 +258,9 @@ impl<COMP: BaseComponent> Runnable for RenderRunner<COMP> {

let comp_scope = AnyScope::from(state.context.scope.clone());

let suspense_scope = comp_scope.find_parent_scope::<Suspense>().unwrap();
let suspense_scope = comp_scope
.find_parent_scope::<Suspense>()
.expect("To suspend rendering, a <Suspense /> component is required.");
let suspense = suspense_scope.get_component().unwrap();

m.listen(Callback::from(move |_| {
12 changes: 9 additions & 3 deletions packages/yew/src/suspense/component.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use crate::html::{Children, Component, Context, Html, Properties, Scope};
use crate::virtual_dom::{Key, VList, VNode, VSuspense};

use gloo_utils::document;
use web_sys::Element;

use super::Suspension;
@@ -29,7 +28,7 @@ pub enum SuspenseMsg {
pub struct Suspense {
link: Scope<Self>,
suspensions: Vec<Suspension>,
detached_parent: Element,
detached_parent: Option<Element>,
}

impl Component for Suspense {
@@ -40,7 +39,14 @@ impl Component for Suspense {
Self {
link: ctx.link().clone(),
suspensions: Vec::new(),
detached_parent: document().create_element("div").unwrap(),

#[cfg(target_arch = "wasm32")]
detached_parent: web_sys::window()
.and_then(|m| m.document())
.and_then(|m| m.create_element("div").ok()),

#[cfg(not(target_arch = "wasm32"))]
detached_parent: None,
}
}

33 changes: 33 additions & 0 deletions packages/yew/src/virtual_dom/vlist.rs
Original file line number Diff line number Diff line change
@@ -1302,4 +1302,37 @@ mod ssr_tests {

assert_eq!(s, "<div>Hello world!</div>");
}

#[test]
async fn test_fragment() {
#[derive(PartialEq, Properties, Debug)]
struct ChildProps {
name: String,
}

#[function_component]
fn Child(props: &ChildProps) -> Html {
html! { <div>{"Hello, "}{&props.name}{"!"}</div> }
}

#[function_component]
fn Comp() -> Html {
html! {
<>
<Child name="Jane" />
<Child name="John" />
<Child name="Josh" />
</>
}
}

let renderer = YewServerRenderer::<Comp>::new();

let s = renderer.render_to_string().await;

assert_eq!(
s,
"<div>Hello, Jane!</div><div>Hello, John!</div><div>Hello, Josh!</div>"
);
}
}
113 changes: 106 additions & 7 deletions packages/yew/src/virtual_dom/vsuspense.rs
Original file line number Diff line number Diff line change
@@ -12,7 +12,7 @@ pub struct VSuspense {
fallback: Box<VNode>,

/// The element to attach to when children is not attached to DOM
detached_parent: Element,
detached_parent: Option<Element>,

/// Whether the current status is suspended.
suspended: bool,
@@ -25,7 +25,7 @@ impl VSuspense {
pub(crate) fn new(
children: VNode,
fallback: VNode,
detached_parent: Element,
detached_parent: Option<Element>,
suspended: bool,
key: Option<Key>,
) -> Self {
@@ -51,7 +51,9 @@ impl VDiff for VSuspense {
fn detach(&mut self, parent: &Element) {
if self.suspended {
self.fallback.detach(parent);
self.children.detach(&self.detached_parent);
if let Some(ref m) = self.detached_parent {
self.children.detach(m);
}
} else {
self.children.detach(parent);
}
@@ -74,6 +76,8 @@ impl VDiff for VSuspense {
next_sibling: NodeRef,
ancestor: Option<VNode>,
) -> NodeRef {
let detached_parent = self.detached_parent.as_ref().expect("no detached parent?");

let (already_suspended, children_ancestor, fallback_ancestor) = match ancestor {
Some(VNode::VSuspense(mut m)) => {
// We only preserve the child state if they are the same suspense.
@@ -98,7 +102,7 @@ impl VDiff for VSuspense {
(true, true) => {
self.children.apply(
parent_scope,
&self.detached_parent,
detached_parent,
NodeRef::default(),
children_ancestor,
);
@@ -115,13 +119,13 @@ impl VDiff for VSuspense {
(true, false) => {
children_ancestor.as_ref().unwrap().shift(
parent,
&self.detached_parent,
detached_parent,
NodeRef::default(),
);

self.children.apply(
parent_scope,
&self.detached_parent,
detached_parent,
NodeRef::default(),
children_ancestor,
);
@@ -135,7 +139,7 @@ impl VDiff for VSuspense {
fallback_ancestor.unwrap().detach(parent);

children_ancestor.as_ref().unwrap().shift(
&self.detached_parent,
detached_parent,
parent,
next_sibling.clone(),
);
@@ -156,3 +160,98 @@ mod feat_ssr {
}
}
}

#[cfg(all(test, not(target_arch = "wasm32")))]
mod ssr_tests {
use std::rc::Rc;
use std::time::Duration;

use tokio::task::{spawn_local, LocalSet};
use tokio::test;
use tokio::time::sleep;

use crate::prelude::*;
use crate::suspense::{Suspension, SuspensionResult};
use crate::YewServerRenderer;

#[test(flavor = "multi_thread", worker_threads = 2)]
async fn test_suspense() {
#[derive(PartialEq)]
pub struct SleepState {
s: Suspension,
}

impl SleepState {
fn new() -> Self {
let (s, handle) = Suspension::new();

// we use tokio spawn local here.
spawn_local(async move {
// we use tokio sleep here.
sleep(Duration::from_millis(50)).await;

handle.resume();
});

Self { s }
}
}

impl Reducible for SleepState {
type Action = ();

fn reduce(self: Rc<Self>, _action: Self::Action) -> Rc<Self> {
Self::new().into()
}
}

pub fn use_sleep() -> SuspensionResult<Rc<dyn Fn()>> {
let sleep_state = use_reducer(SleepState::new);

if sleep_state.s.resumed() {
Ok(Rc::new(move || sleep_state.dispatch(())))
} else {
Err(sleep_state.s.clone())
}
}

#[derive(PartialEq, Properties, Debug)]
struct ChildProps {
name: String,
}

#[function_component]
fn Child(props: &ChildProps) -> HtmlResult {
use_sleep()?;
Ok(html! { <div>{"Hello, "}{&props.name}{"!"}</div> })
}

#[function_component]
fn Comp() -> Html {
let fallback = html! {"loading..."};

html! {
<Suspense {fallback}>
<Child name="Jane" />
<Child name="John" />
<Child name="Josh" />
</Suspense>
}
}

let local = LocalSet::new();

let s = local
.run_until(async move {
let renderer = YewServerRenderer::<Comp>::new();

renderer.render_to_string().await
})
.await;

assert_eq!(
s,
"<div>Hello, Jane!</div><div>Hello, John!</div><div>Hello, Josh!</div>"
);
}
}