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

Suspense Support #2212

Merged
merged 35 commits into from Jan 5, 2022
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
fba4a5d
Make Html a Result.
futursolo Nov 26, 2021
4b6c311
Fix tests.
futursolo Nov 27, 2021
0f5f307
Implement Suspense.
futursolo Nov 27, 2021
7f19683
Schedule render when suspension is resumed.
futursolo Nov 27, 2021
31332c6
Shift children into a detached node.
futursolo Nov 28, 2021
0c4a59b
Merge branch 'master' into suspense
futursolo Nov 28, 2021
9bef4de
styled example.
futursolo Nov 28, 2021
aa766b7
Update wording a little bit.
futursolo Nov 28, 2021
1fc0db9
Move hint to hint.
futursolo Nov 28, 2021
376413a
Add some tests.
futursolo Nov 30, 2021
b11e503
Merge branch 'master' into suspense
futursolo Nov 30, 2021
2e7f0bf
Fix clippy.
futursolo Nov 30, 2021
77ab06a
Add docs.
futursolo Nov 30, 2021
9afeef0
Add to sidebar.
futursolo Nov 30, 2021
4d58a14
Fix syntax highlight.
futursolo Nov 30, 2021
9b22c1a
Merge branch 'master' into suspense
futursolo Dec 20, 2021
8bcacf6
Component -> BaseComponent.
futursolo Dec 20, 2021
5254782
Html -> VNode, HtmlResult = RenderResult<Html>.
futursolo Dec 20, 2021
a54c062
Suspendible Function Component.
futursolo Dec 20, 2021
af4e2f5
Add a method to create suspension from futures.
futursolo Dec 20, 2021
06a1954
Merge branch 'master' into suspense
futursolo Dec 20, 2021
cbf12be
Revert extra changes.
futursolo Dec 20, 2021
b664e5f
Fix tests.
futursolo Dec 20, 2021
066662d
Update documentation.
futursolo Dec 20, 2021
f097303
Switch to custom trait to make test reliable.
futursolo Dec 20, 2021
d8e9701
Fix file permission.
futursolo Dec 21, 2021
787de4f
Fix docs.
futursolo Dec 21, 2021
d8c3e85
Remove log.
futursolo Dec 21, 2021
7e66418
Fix file permission.
futursolo Dec 21, 2021
054f7fa
Resolve Conflict.
futursolo Dec 24, 2021
eb950d2
Fix component name error.
futursolo Dec 24, 2021
23c9a95
Merge branch 'master' into suspense
futursolo Dec 30, 2021
fae94d1
Make Suspension a future.
futursolo Dec 30, 2021
fcc8b68
Merge branch 'master' into suspense
futursolo Jan 2, 2022
5a376af
Merge branch 'yewstack:master' into suspense
futursolo Jan 5, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Expand Up @@ -34,6 +34,7 @@ members = [
"examples/two_apps",
"examples/webgl",
"examples/web_worker_fib",
"examples/suspense",

# Release tools
"packages/changelog",
Expand Down
2 changes: 1 addition & 1 deletion examples/futures/src/markdown.rs
Expand Up @@ -107,7 +107,7 @@ pub fn render_markdown(src: &str) -> Html {
}

if elems.len() == 1 {
VNode::VTag(Box::new(elems.pop().unwrap()))
Ok(VNode::VTag(Box::new(elems.pop().unwrap())))
} else {
html! {
<div>{ for elems.into_iter() }</div>
Expand Down
3 changes: 2 additions & 1 deletion examples/inner_html/src/main.rs
@@ -1,4 +1,5 @@
use web_sys::console;
use yew::virtual_dom::VNode;
use yew::{Component, Context, Html};

const HTML: &str = include_str!("document.html");
Expand All @@ -21,7 +22,7 @@ impl Component for Model {
// See <https://github.com/yewstack/yew/issues/1546>
console::log_1(&div);

Html::VRef(div.into())
Ok(VNode::VRef(div.into()))
}
}

Expand Down
12 changes: 7 additions & 5 deletions examples/nested_list/src/list.rs
Expand Up @@ -4,7 +4,7 @@ use crate::{Hovered, WeakComponentLink};
use std::rc::Rc;
use yew::html::{ChildrenRenderer, NodeRef};
use yew::prelude::*;
use yew::virtual_dom::{VChild, VComp};
use yew::virtual_dom::{VChild, VComp, VNode};

#[derive(Clone, PartialEq)]
pub enum Variants {
Expand Down Expand Up @@ -41,8 +41,8 @@ where
}
}

impl From<ListVariant> for Html {
fn from(variant: ListVariant) -> Html {
impl From<ListVariant> for VNode {
fn from(variant: ListVariant) -> VNode {
match variant.props {
Variants::Header(props) => {
VComp::new::<ListHeader>(props, NodeRef::default(), None).into()
Expand Down Expand Up @@ -113,7 +113,7 @@ impl List {
}

fn view_items(children: &ChildrenRenderer<ListVariant>) -> Html {
children
let children = children
.iter()
.filter(|c| matches!(&c.props, Variants::Item(props) if !props.hide))
.enumerate()
Expand All @@ -125,6 +125,8 @@ impl List {
}
c
})
.collect::<Html>()
.collect::<VNode>();

html! {<>{children}</>}
}
}
21 changes: 21 additions & 0 deletions examples/suspense/Cargo.toml
@@ -0,0 +1,21 @@
[package]
name = "suspense"
version = "0.1.0"
edition = "2018"
license = "MIT OR Apache-2.0"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
yew = { path = "../../packages/yew" }
gloo-timers = { version = "0.2.1", features = ["futures"] }
wasm-bindgen-futures = "0.4"
wasm-bindgen = "0.2"
log = "0.4.14"
console_log = { version = "0.2.0", features = ["color"] }
futursolo marked this conversation as resolved.
Show resolved Hide resolved

[dependencies.web-sys]
version = "0.3"
features = [
"HtmlTextAreaElement",
]
10 changes: 10 additions & 0 deletions examples/suspense/README.md
@@ -0,0 +1,10 @@
# Suspense Example

[![Demo](https://img.shields.io/website?label=demo&url=https%3A%2F%2Fexamples.yew.rs%2Fsuspense)](https://examples.yew.rs/suspense)

This is an example that demonstrates `<Suspense />` support.

## Concepts

This example shows that how `<Suspense />` works in Yew and how you can
create hooks that utilises suspense.
11 changes: 11 additions & 0 deletions examples/suspense/index.html
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Yew Suspense Demo</title>

<link data-trunk rel="sass" href="index.scss" />
</head>

<body></body>
</html>
71 changes: 71 additions & 0 deletions examples/suspense/index.scss
@@ -0,0 +1,71 @@
html, body {
font-family: sans-serif;

margin: 0;
padding: 0;

background-color: rgb(237, 244, 255);
}

.layout {
height: 100vh;
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}

.content {
height: 600px;
width: 600px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;

border-radius: 4px;
box-shadow: 0 0 5px 0 black;

background: white;
}

.content-area {
width: 350px;
height: 500px;

display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}

textarea {
width: 300px;
height: 300px;
font-size: 15px;
}

.action-area {
padding-top: 40px;
}

button {
color: white;
height: 50px;
width: 300px;
font-size: 20px;
background-color: rgb(88, 164, 255);
border-radius: 5px;
border: none;
}

.hint {
padding-top: 20px;

font-size: 12px;

text-align: center;

color: rgb(100, 100, 100);
}
63 changes: 63 additions & 0 deletions examples/suspense/src/main.rs
@@ -0,0 +1,63 @@
use web_sys::HtmlTextAreaElement;
use yew::prelude::*;

use log::Level;

mod use_sleep;

use use_sleep::use_sleep;

#[function_component(PleaseWait)]
fn please_wait() -> Html {
html! {<div class="content-area">{"Please wait 5 Seconds..."}</div>}
}

#[function_component(AppContent)]
fn app_content() -> Html {
let resleep = use_sleep()?;

let value = use_state(|| "I am writing a long story...".to_string());

let on_text_input = {
let value = value.clone();

Callback::from(move |e: InputEvent| {
let input: HtmlTextAreaElement = e.target_unchecked_into();

value.set(input.value());
})
};

let on_take_a_break = Callback::from(move |_| (resleep.clone())());

html! {
<div class="content-area">
<textarea value={value.to_string()} oninput={on_text_input}></textarea>
<div class="action-area">
<button onclick={on_take_a_break}>{"Take a break!"}</button>
<div class="hint">{"You can take a break at anytime"}<br />{"and your work will be preserved."}</div>
</div>
</div>
}
}

#[function_component(App)]
fn app() -> Html {
let fallback = html! {<PleaseWait />};

html! {
<div class="layout">
<div class="content">
<h1>{"Yew Suspense Demo"}</h1>
<Suspense fallback={fallback}>
<AppContent />
</Suspense>
</div>
</div>
}
}

fn main() {
console_log::init_with_level(Level::Trace).expect("Failed to initialise Log!");
yew::start_app::<App>();
}
43 changes: 43 additions & 0 deletions examples/suspense/src/use_sleep.rs
@@ -0,0 +1,43 @@
use std::rc::Rc;

use gloo_timers::future::TimeoutFuture;
use wasm_bindgen_futures::spawn_local;
use yew::prelude::*;
use yew::suspense::{Suspension, SuspensionResult};

#[derive(PartialEq)]
pub struct SleepState {
s: Suspension,
}

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

spawn_local(async move {
TimeoutFuture::new(5_000).await;
futursolo marked this conversation as resolved.
Show resolved Hide resolved

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())
}
}
18 changes: 16 additions & 2 deletions packages/yew-macro/src/html_tree/html_iterable.rs
Expand Up @@ -42,7 +42,14 @@ impl ToTokens for HtmlIterable {
let expr = &self.0;
let new_tokens = quote_spanned! {expr.span()=>
#[allow(unused_braces)]
::std::iter::Iterator::collect::<::yew::virtual_dom::VNode>(::std::iter::IntoIterator::into_iter(#expr))
{
let mut nodes: ::std::vec::Vec<::yew::virtual_dom::VNode> = ::std::vec::Vec::new();
for __node in #expr {
nodes.push(::yew::utils::TryIntoNode::try_into_node(__node)?.into_value());
}

::std::iter::Iterator::collect::<::yew::virtual_dom::VNode>(::std::iter::IntoIterator::into_iter(nodes))
}
};

tokens.extend(new_tokens);
Expand All @@ -55,7 +62,14 @@ impl ToNodeIterator for HtmlIterable {
// #expr can return anything that implements IntoIterator<Item=Into<T>>
// We use a util method to avoid clippy warnings and reduce generated code size
Some(quote_spanned! {expr.span()=>
::yew::utils::into_node_iter(#expr)
{
let mut nodes = ::std::vec::Vec::new();
for __node in #expr {
nodes.push(::yew::utils::TryIntoNode::try_into_node(__node)?.into_value());
}

::std::iter::IntoIterator::into_iter(nodes)
}
})
}
}
2 changes: 1 addition & 1 deletion packages/yew-macro/src/html_tree/html_node.rs
Expand Up @@ -60,7 +60,7 @@ impl ToNodeIterator for HtmlNode {
HtmlNode::Expression(expr) => {
// NodeSeq turns both Into<T> and Vec<Into<T>> into IntoIterator<Item = T>
Some(
quote_spanned! {expr.span()=> ::std::convert::Into::<::yew::utils::NodeSeq<_, _>>::into(#expr)},
quote_spanned! {expr.span()=> ::yew::utils::TryIntoNodeSeq::try_into_node_seq(#expr)?},
)
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/yew-macro/src/html_tree/mod.rs
Expand Up @@ -182,7 +182,7 @@ impl ToTokens for HtmlRootVNode {
let new_tokens = self.0.to_token_stream();
tokens.extend(quote! {{
#[allow(clippy::useless_conversion)]
<::yew::virtual_dom::VNode as ::std::convert::From<_>>::from(#new_tokens)
(|| ::yew::html::RenderResult::Ok(::yew::utils::TryIntoNode::<_, ::yew::virtual_dom::VNode>::try_into_node(#new_tokens)?.into_value()))()
}});
}
}
Expand Down
Expand Up @@ -8,6 +8,9 @@ error[E0308]: mismatched types
--> $DIR/bad-return-type-fail.rs:13:5
|
12 | fn comp(_props: &Props) -> u32 {
| --- expected `VNode` because of return type
| --- expected `std::result::Result<VNode, RenderError>` because of return type
13 | 1
| ^ expected enum `VNode`, found integer
| ^ expected enum `std::result::Result`, found integer
|
= note: expected enum `std::result::Result<VNode, RenderError>`
found type `{integer}`
Expand Up @@ -68,10 +68,10 @@ fn const_generics<const N: ::std::primitive::i32>() -> ::yew::Html {
}

fn compile_pass() {
::yew::html! { <Comp<Props> a=10 /> };
::yew::html! { <Comp1<::std::primitive::usize, ::std::primitive::usize> /> };
(::yew::html! { <Comp<Props> a=10 /> }).unwrap();
(::yew::html! { <Comp1<::std::primitive::usize, ::std::primitive::usize> /> }).unwrap();

::yew::html! { <ConstGenerics<10> /> };
(::yew::html! { <ConstGenerics<10> /> }).unwrap();
}

fn main() {}