From fba4a5d8db4b673029676ac17d77416ccc4b19a6 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sat, 27 Nov 2021 02:47:10 +0900 Subject: [PATCH 01/27] Make Html a Result. --- examples/futures/src/markdown.rs | 2 +- examples/inner_html/src/main.rs | 3 +- examples/nested_list/src/list.rs | 12 +- .../yew-macro/src/html_tree/html_iterable.rs | 18 ++- packages/yew-macro/src/html_tree/html_node.rs | 2 +- packages/yew-macro/src/html_tree/mod.rs | 2 +- packages/yew/Cargo.toml | 1 + packages/yew/src/html/component/children.rs | 3 +- packages/yew/src/html/component/lifecycle.rs | 3 +- packages/yew/src/html/error.rs | 12 ++ packages/yew/src/html/mod.rs | 19 ++- packages/yew/src/lib.rs | 4 +- packages/yew/src/utils/mod.rs | 123 ++++++++++++++---- 13 files changed, 162 insertions(+), 42 deletions(-) create mode 100644 packages/yew/src/html/error.rs diff --git a/examples/futures/src/markdown.rs b/examples/futures/src/markdown.rs index 307c8702069..e50ab03002f 100644 --- a/examples/futures/src/markdown.rs +++ b/examples/futures/src/markdown.rs @@ -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! {
{ for elems.into_iter() }
diff --git a/examples/inner_html/src/main.rs b/examples/inner_html/src/main.rs index 9ab6992e3ee..d557d957241 100644 --- a/examples/inner_html/src/main.rs +++ b/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"); @@ -21,7 +22,7 @@ impl Component for Model { // See console::log_1(&div); - Html::VRef(div.into()) + Ok(VNode::VRef(div.into())) } } diff --git a/examples/nested_list/src/list.rs b/examples/nested_list/src/list.rs index 62bb09fe1bd..decb26b49c0 100644 --- a/examples/nested_list/src/list.rs +++ b/examples/nested_list/src/list.rs @@ -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 { @@ -41,8 +41,8 @@ where } } -impl From for Html { - fn from(variant: ListVariant) -> Html { +impl From for VNode { + fn from(variant: ListVariant) -> VNode { match variant.props { Variants::Header(props) => { VComp::new::(props, NodeRef::default(), None).into() @@ -111,7 +111,7 @@ impl List { } fn view_items(children: &ChildrenRenderer) -> Html { - children + let children = children .iter() .filter(|c| matches!(&c.props, Variants::Item(props) if !props.hide)) .enumerate() @@ -123,6 +123,8 @@ impl List { } c }) - .collect::() + .collect::(); + + html! {<>{children}} } } diff --git a/packages/yew-macro/src/html_tree/html_iterable.rs b/packages/yew-macro/src/html_tree/html_iterable.rs index 700d28ab2ad..71fe1bf9ceb 100644 --- a/packages/yew-macro/src/html_tree/html_iterable.rs +++ b/packages/yew-macro/src/html_tree/html_iterable.rs @@ -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 __render_node in #expr { + nodes.push(::yew::utils::TryIntoRenderNode::try_into_render_node(__render_node)?.into_value()); + } + + ::std::iter::Iterator::collect::<::yew::virtual_dom::VNode>(::std::iter::IntoIterator::into_iter(nodes)) + } }; tokens.extend(new_tokens); @@ -55,7 +62,14 @@ impl ToNodeIterator for HtmlIterable { // #expr can return anything that implements IntoIterator> // 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 = Vec::new(); + for __render_node in #expr { + nodes.push(::yew::utils::TryIntoRenderNode::try_into_render_node(__render_node)?.into_value()); + } + + nodes.into_iter() + } }) } } diff --git a/packages/yew-macro/src/html_tree/html_node.rs b/packages/yew-macro/src/html_tree/html_node.rs index d8cd6e19987..ab6fdea81cd 100644 --- a/packages/yew-macro/src/html_tree/html_node.rs +++ b/packages/yew-macro/src/html_tree/html_node.rs @@ -60,7 +60,7 @@ impl ToNodeIterator for HtmlNode { HtmlNode::Expression(expr) => { // NodeSeq turns both Into and Vec> into IntoIterator 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)?}, ) } } diff --git a/packages/yew-macro/src/html_tree/mod.rs b/packages/yew-macro/src/html_tree/mod.rs index ad18325da78..cf8e65dac47 100644 --- a/packages/yew-macro/src/html_tree/mod.rs +++ b/packages/yew-macro/src/html_tree/mod.rs @@ -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) + (|| Ok(::yew::utils::TryIntoRenderNode::<_, ::yew::virtual_dom::VNode>::try_into_render_node(#new_tokens)?.into_value()))() }}); } } diff --git a/packages/yew/Cargo.toml b/packages/yew/Cargo.toml index ed51e07c92f..d9de79f8f78 100644 --- a/packages/yew/Cargo.toml +++ b/packages/yew/Cargo.toml @@ -25,6 +25,7 @@ slab = "0.4" wasm-bindgen = "0.2" wasm-bindgen-futures = "0.4" yew-macro = { version = "^0.18.0", path = "../yew-macro" } +thiserror = "1.0" scoped-tls-hkt = "0.1" diff --git a/packages/yew/src/html/component/children.rs b/packages/yew/src/html/component/children.rs index c0fbd1d858d..c27c3ee2007 100644 --- a/packages/yew/src/html/component/children.rs +++ b/packages/yew/src/html/component/children.rs @@ -1,6 +1,5 @@ //! Component children module -use crate::html::Html; use crate::virtual_dom::{VChild, VNode}; use std::fmt; @@ -58,7 +57,7 @@ use std::fmt; /// } /// } /// ``` -pub type Children = ChildrenRenderer; +pub type Children = ChildrenRenderer; /// A type used for accepting children elements in Component::Properties and accessing their props. /// diff --git a/packages/yew/src/html/component/lifecycle.rs b/packages/yew/src/html/component/lifecycle.rs index 49931d551c2..2ca6a4fed01 100644 --- a/packages/yew/src/html/component/lifecycle.rs +++ b/packages/yew/src/html/component/lifecycle.rs @@ -171,7 +171,8 @@ impl Runnable for RenderRunner { #[cfg(debug_assertions)] crate::virtual_dom::vcomp::log_event(state.vcomp_id, "render"); - let mut new_root = state.component.view(&state.context); + // TODO: Error? + let mut new_root = state.component.view(&state.context).unwrap(); std::mem::swap(&mut new_root, &mut state.root_node); let ancestor = Some(new_root); let new_root = &mut state.root_node; diff --git a/packages/yew/src/html/error.rs b/packages/yew/src/html/error.rs new file mode 100644 index 00000000000..a3ee39ad9e1 --- /dev/null +++ b/packages/yew/src/html/error.rs @@ -0,0 +1,12 @@ +use thiserror::Error; + +/// Render Error. +#[derive(Error, Debug, Clone)] +pub enum RenderError { + /// Component Rendering Suspended + #[error("component rendering is suspended.")] + Suspended, +} + +/// Render Result. +pub type RenderResult = std::result::Result; diff --git a/packages/yew/src/html/mod.rs b/packages/yew/src/html/mod.rs index 84999370621..430496fe5c3 100644 --- a/packages/yew/src/html/mod.rs +++ b/packages/yew/src/html/mod.rs @@ -3,11 +3,13 @@ mod classes; mod component; mod conversion; +mod error; mod listener; pub use classes::*; pub use component::*; pub use conversion::*; +pub use error::*; pub use listener::*; use crate::virtual_dom::{VNode, VPortal}; @@ -17,7 +19,20 @@ use wasm_bindgen::JsValue; use web_sys::{Element, Node}; /// A type which expected as a result of `view` function implementation. -pub type Html = VNode; +pub type Html = RenderResult; + +/// A trait to provide a default value for [`Html`]. +pub trait HtmlDefault { + /// Returns the “default value” for a type. + /// Default values are often some kind of initial value, identity value, or anything else that may make sense as a default. + fn default() -> Self; +} + +impl HtmlDefault for Html { + fn default() -> Self { + Ok(VNode::default()) + } +} /// Wrapped Node reference for later use in Component lifecycle methods. /// @@ -141,7 +156,7 @@ impl NodeRef { /// ## Relevant examples /// - [Portals](https://github.com/yewstack/yew/tree/master/examples/portals) pub fn create_portal(child: Html, host: Element) -> Html { - VNode::VPortal(VPortal::new(child, host)) + Ok(VNode::VPortal(VPortal::new(child?, host))) } #[cfg(test)] diff --git a/packages/yew/src/lib.rs b/packages/yew/src/lib.rs index eedf5adff42..2cf6c50b85a 100644 --- a/packages/yew/src/lib.rs +++ b/packages/yew/src/lib.rs @@ -387,8 +387,8 @@ pub mod prelude { pub use crate::context::ContextProvider; pub use crate::events::*; pub use crate::html::{ - create_portal, Children, ChildrenWithProps, Classes, Component, Context, Html, NodeRef, - Properties, + create_portal, Children, ChildrenWithProps, Classes, Component, Context, Html, HtmlDefault, + NodeRef, Properties, }; pub use crate::macros::{classes, html, html_nested}; diff --git a/packages/yew/src/utils/mod.rs b/packages/yew/src/utils/mod.rs index 29e9a8874f0..47c0bb10e75 100644 --- a/packages/yew/src/utils/mod.rs +++ b/packages/yew/src/utils/mod.rs @@ -2,50 +2,125 @@ use std::marker::PhantomData; use yew::html::ChildrenRenderer; +use yew::html::RenderResult; -/// Map IntoIterator> to Iterator -pub fn into_node_iter(it: IT) -> impl Iterator +/// A special type necessary for flattening components returned from nested html macros. +#[derive(Debug)] +pub struct RenderNode(O, PhantomData); + +impl RenderNode { + /// Returns the wrapped value. + pub fn into_value(self) -> O { + self.0 + } +} + +/// A special trait to convert to a `RenderResult`. +pub trait TryIntoRenderNode { + /// Performs the conversion. + fn try_into_render_node(self) -> RenderResult>; +} + +impl TryIntoRenderNode for I where - IT: IntoIterator, - T: Into, + I: Into, { - it.into_iter().map(|n| n.into()) + fn try_into_render_node(self) -> RenderResult> { + Ok(RenderNode(self.into(), PhantomData::default())) + } +} + +impl TryIntoRenderNode for RenderResult +where + I: Into, +{ + fn try_into_render_node(self) -> RenderResult> { + Ok(RenderNode(self?.into(), PhantomData::default())) + } } /// A special type necessary for flattening components returned from nested html macros. #[derive(Debug)] -pub struct NodeSeq(Vec, PhantomData); +pub struct NodeSeq(Vec, PhantomData); + +impl IntoIterator for NodeSeq { + type Item = O; + type IntoIter = std::vec::IntoIter; -impl, OUT> From for NodeSeq { - fn from(val: IN) -> Self { - Self(vec![val.into()], PhantomData::default()) + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() } } -impl, OUT> From> for NodeSeq { - fn from(val: Vec) -> Self { - Self( - val.into_iter().map(|x| x.into()).collect(), +/// A special trait to convert to a `NodeSeq`. +pub trait TryIntoNodeSeq { + /// Performs the conversion. + fn try_into_node_seq(self) -> RenderResult>; +} + +impl TryIntoNodeSeq for I +where + I: Into, +{ + fn try_into_node_seq(self) -> RenderResult> { + Ok(NodeSeq(vec![self.into()], PhantomData::default())) + } +} + +impl TryIntoNodeSeq for Vec +where + I: Into, +{ + fn try_into_node_seq(self) -> RenderResult> { + Ok(NodeSeq( + self.into_iter().map(|x| x.into()).collect(), PhantomData::default(), - ) + )) } } -impl, OUT> From> for NodeSeq { - fn from(val: ChildrenRenderer) -> Self { - Self( - val.into_iter().map(|x| x.into()).collect(), +impl TryIntoNodeSeq for ChildrenRenderer +where + I: Into, +{ + fn try_into_node_seq(self) -> RenderResult> { + Ok(NodeSeq( + self.into_iter().map(|x| x.into()).collect(), PhantomData::default(), - ) + )) } } -impl IntoIterator for NodeSeq { - type Item = OUT; - type IntoIter = std::vec::IntoIter; +impl TryIntoNodeSeq for RenderResult +where + I: Into, +{ + fn try_into_node_seq(self) -> RenderResult> { + Ok(NodeSeq(vec![self?.into()], PhantomData::default())) + } +} - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() +impl TryIntoNodeSeq for RenderResult> +where + I: Into, +{ + fn try_into_node_seq(self) -> RenderResult> { + Ok(NodeSeq( + self?.into_iter().map(|x| x.into()).collect(), + PhantomData::default(), + )) + } +} + +impl TryIntoNodeSeq for RenderResult> +where + I: Into, +{ + fn try_into_node_seq(self) -> RenderResult> { + Ok(NodeSeq( + self?.into_iter().map(|x| x.into()).collect(), + PhantomData::default(), + )) } } From 4b6c3112c52732c5c6d41b2f54af1c09cda211fb Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sat, 27 Nov 2021 19:21:28 +0900 Subject: [PATCH 02/27] Fix tests. --- .../yew-macro/src/html_tree/html_iterable.rs | 12 +- packages/yew-macro/src/html_tree/mod.rs | 2 +- .../bad-return-type-fail.stderr | 7 +- .../function_component_attr/generic-pass.rs | 6 +- .../tests/html_macro/block-fail.stderr | 17 +- .../yew-macro/tests/html_macro/block-pass.rs | 22 ++- .../tests/html_macro/component-fail.stderr | 4 +- .../tests/html_macro/component-pass.rs | 71 ++++--- .../tests/html_macro/dyn-element-pass.rs | 10 +- .../tests/html_macro/element-fail.stderr | 2 +- .../html_macro/generic-component-pass.rs | 26 +-- .../tests/html_macro/html-element-pass.rs | 13 +- .../tests/html_macro/html-if-pass.rs | 48 ++--- .../tests/html_macro/html-node-pass.rs | 26 +-- .../tests/html_macro/iterable-fail.stderr | 77 ++++---- .../tests/html_macro/iterable-pass.rs | 12 +- .../yew-macro/tests/html_macro/list-pass.rs | 16 +- .../tests/html_macro/node-fail.stderr | 8 +- .../yew-macro/tests/html_macro/node-pass.rs | 30 +-- .../yew-macro/tests/html_macro/svg-pass.rs | 9 +- packages/yew-macro/tests/html_macro_test.rs | 10 +- packages/yew/src/html/component/children.rs | 7 + packages/yew/src/html/error.rs | 2 +- packages/yew/src/lib.rs | 7 +- packages/yew/src/utils/mod.rs | 65 ++++++- packages/yew/src/virtual_dom/key.rs | 5 +- packages/yew/src/virtual_dom/vcomp.rs | 87 +++++---- packages/yew/src/virtual_dom/vlist.rs | 177 ++++++++++++------ packages/yew/src/virtual_dom/vportal.rs | 18 +- packages/yew/src/virtual_dom/vtag.rs | 49 +++-- packages/yew/src/virtual_dom/vtext.rs | 20 +- website/docs/concepts/components.md | 9 +- website/docs/concepts/components/children.md | 20 +- website/docs/concepts/html/elements.md | 11 +- website/docs/concepts/html/lists.md | 3 +- 35 files changed, 541 insertions(+), 367 deletions(-) diff --git a/packages/yew-macro/src/html_tree/html_iterable.rs b/packages/yew-macro/src/html_tree/html_iterable.rs index 71fe1bf9ceb..3bf1aa4a0fe 100644 --- a/packages/yew-macro/src/html_tree/html_iterable.rs +++ b/packages/yew-macro/src/html_tree/html_iterable.rs @@ -44,8 +44,8 @@ impl ToTokens for HtmlIterable { #[allow(unused_braces)] { let mut nodes: ::std::vec::Vec<::yew::virtual_dom::VNode> = ::std::vec::Vec::new(); - for __render_node in #expr { - nodes.push(::yew::utils::TryIntoRenderNode::try_into_render_node(__render_node)?.into_value()); + 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)) @@ -63,12 +63,12 @@ impl ToNodeIterator for HtmlIterable { // We use a util method to avoid clippy warnings and reduce generated code size Some(quote_spanned! {expr.span()=> { - let mut nodes = Vec::new(); - for __render_node in #expr { - nodes.push(::yew::utils::TryIntoRenderNode::try_into_render_node(__render_node)?.into_value()); + let mut nodes = ::std::vec::Vec::new(); + for __node in #expr { + nodes.push(::yew::utils::TryIntoNode::try_into_node(__node)?.into_value()); } - nodes.into_iter() + ::std::iter::IntoIterator::into_iter(nodes) } }) } diff --git a/packages/yew-macro/src/html_tree/mod.rs b/packages/yew-macro/src/html_tree/mod.rs index cf8e65dac47..854790a1122 100644 --- a/packages/yew-macro/src/html_tree/mod.rs +++ b/packages/yew-macro/src/html_tree/mod.rs @@ -182,7 +182,7 @@ impl ToTokens for HtmlRootVNode { let new_tokens = self.0.to_token_stream(); tokens.extend(quote! {{ #[allow(clippy::useless_conversion)] - (|| Ok(::yew::utils::TryIntoRenderNode::<_, ::yew::virtual_dom::VNode>::try_into_render_node(#new_tokens)?.into_value()))() + (|| ::yew::html::RenderResult::Ok(::yew::utils::TryIntoNode::<_, ::yew::virtual_dom::VNode>::try_into_node(#new_tokens)?.into_value()))() }}); } } diff --git a/packages/yew-macro/tests/function_component_attr/bad-return-type-fail.stderr b/packages/yew-macro/tests/function_component_attr/bad-return-type-fail.stderr index 0a5d227dd08..bcb7d314ad8 100644 --- a/packages/yew-macro/tests/function_component_attr/bad-return-type-fail.stderr +++ b/packages/yew-macro/tests/function_component_attr/bad-return-type-fail.stderr @@ -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` because of return type 13 | 1 - | ^ expected enum `VNode`, found integer + | ^ expected enum `std::result::Result`, found integer + | + = note: expected enum `std::result::Result` + found type `{integer}` diff --git a/packages/yew-macro/tests/function_component_attr/generic-pass.rs b/packages/yew-macro/tests/function_component_attr/generic-pass.rs index 29df3007d46..544ad92607d 100644 --- a/packages/yew-macro/tests/function_component_attr/generic-pass.rs +++ b/packages/yew-macro/tests/function_component_attr/generic-pass.rs @@ -68,10 +68,10 @@ fn const_generics() -> ::yew::Html { } fn compile_pass() { - ::yew::html! { a=10 /> }; - ::yew::html! { /> }; + (::yew::html! { a=10 /> }).unwrap(); + (::yew::html! { /> }).unwrap(); - ::yew::html! { /> }; + (::yew::html! { /> }).unwrap(); } fn main() {} diff --git a/packages/yew-macro/tests/html_macro/block-fail.stderr b/packages/yew-macro/tests/html_macro/block-fail.stderr index bf887658929..660fbd23b95 100644 --- a/packages/yew-macro/tests/html_macro/block-fail.stderr +++ b/packages/yew-macro/tests/html_macro/block-fail.stderr @@ -9,9 +9,8 @@ error[E0277]: `()` doesn't implement `std::fmt::Display` = note: required because of the requirements on the impl of `ToString` for `()` = note: required because of the requirements on the impl of `From<()>` for `VNode` = note: required because of the requirements on the impl of `Into` for `()` - = note: 2 redundant requirements hidden - = note: required because of the requirements on the impl of `Into>` for `()` - = note: required by `into` + = note: required because of the requirements on the impl of `TryIntoNodeSeq<(), VNode>` for `()` + = note: required by `try_into_node_seq` error[E0277]: `()` doesn't implement `std::fmt::Display` --> $DIR/block-fail.rs:12:16 @@ -24,9 +23,8 @@ error[E0277]: `()` doesn't implement `std::fmt::Display` = note: required because of the requirements on the impl of `ToString` for `()` = note: required because of the requirements on the impl of `From<()>` for `VNode` = note: required because of the requirements on the impl of `Into` for `()` - = note: 2 redundant requirements hidden - = note: required because of the requirements on the impl of `Into>` for `()` - = note: required by `into` + = note: required because of the requirements on the impl of `TryIntoNodeSeq<(), VNode>` for `()` + = note: required by `try_into_node_seq` error[E0277]: `()` doesn't implement `std::fmt::Display` --> $DIR/block-fail.rs:15:17 @@ -34,13 +32,10 @@ error[E0277]: `()` doesn't implement `std::fmt::Display` 15 | <>{ for (0..3).map(|_| not_tree()) } | ^^^^^^ `()` cannot be formatted with the default formatter | - ::: $WORKSPACE/packages/yew/src/utils/mod.rs - | - | T: Into, - | ------- required by this bound in `into_node_iter` - | = help: the trait `std::fmt::Display` is not implemented for `()` = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead = note: required because of the requirements on the impl of `ToString` for `()` = note: required because of the requirements on the impl of `From<()>` for `VNode` = note: required because of the requirements on the impl of `Into` for `()` + = note: required because of the requirements on the impl of `TryIntoNode<(), VNode>` for `()` + = note: required by `try_into_node` diff --git a/packages/yew-macro/tests/html_macro/block-pass.rs b/packages/yew-macro/tests/html_macro/block-pass.rs index d8f527e6e46..36ee52c5b6c 100644 --- a/packages/yew-macro/tests/html_macro/block-pass.rs +++ b/packages/yew-macro/tests/html_macro/block-pass.rs @@ -37,29 +37,31 @@ pub struct u8; pub struct usize; fn main() { - ::yew::html! { <>{ "Hi" } }; - ::yew::html! { <>{ ::std::format!("Hello") } }; - ::yew::html! { <>{ ::std::string::ToString::to_string("Hello") } }; + (::yew::html! { <>{ "Hi" } }).unwrap(); + (::yew::html! { <>{ ::std::format!("Hello") } }).unwrap(); + (::yew::html! { <>{ ::std::string::ToString::to_string("Hello") } }).unwrap(); let msg = "Hello"; - ::yew::html! {
{ msg }
}; + (::yew::html! {
{ msg }
}).unwrap(); let subview = ::yew::html! { "subview!" }; - ::yew::html! {
{ subview }
}; + (::yew::html! {
{ subview }
}).unwrap(); let subview = || ::yew::html! { "subview!" }; - ::yew::html! {
{ subview() }
}; + (::yew::html! {
{ subview() }
}).unwrap(); - ::yew::html! { + (::yew::html! {
    { for ::std::iter::Iterator::map(0..3, |num| { ::yew::html! { { num } }}) }
- }; + }) + .unwrap(); let item = |num| ::yew::html! {
  • { ::std::format!("item {}!", num) }
  • }; - ::yew::html! { + (::yew::html! {
      { for ::std::iter::Iterator::map(0..3, item) }
    - }; + }) + .unwrap(); } diff --git a/packages/yew-macro/tests/html_macro/component-fail.stderr b/packages/yew-macro/tests/html_macro/component-fail.stderr index 7b6506d6cc4..122dd6e5d3b 100644 --- a/packages/yew-macro/tests/html_macro/component-fail.stderr +++ b/packages/yew-macro/tests/html_macro/component-fail.stderr @@ -346,7 +346,7 @@ error[E0277]: the trait bound `{integer}: IntoPropValue` is not satisfie <&'static str as IntoPropValue> <&'static str as IntoPropValue>> <&'static str as IntoPropValue>> - and 12 others + and 13 others error[E0277]: the trait bound `{integer}: IntoPropValue` is not satisfied --> tests/html_macro/component-fail.rs:79:34 @@ -359,7 +359,7 @@ error[E0277]: the trait bound `{integer}: IntoPropValue` is not satisfie <&'static str as IntoPropValue> <&'static str as IntoPropValue>> <&'static str as IntoPropValue>> - and 12 others + and 13 others error[E0308]: mismatched types --> tests/html_macro/component-fail.rs:80:31 diff --git a/packages/yew-macro/tests/html_macro/component-pass.rs b/packages/yew-macro/tests/html_macro/component-pass.rs index 8caa8be6e7b..04882c71f6b 100644 --- a/packages/yew-macro/tests/html_macro/component-pass.rs +++ b/packages/yew-macro/tests/html_macro/component-pass.rs @@ -158,18 +158,19 @@ mod scoped { } fn compile_pass() { - ::yew::html! { }; + (::yew::html! { }).unwrap(); - ::yew::html! { + (::yew::html! { <> - }; + }) + .unwrap(); let props = <::Properties as ::std::default::Default>::default(); let node_ref = <::yew::NodeRef as ::std::default::Default>::default(); - ::yew::html! { + (::yew::html! { <> @@ -178,9 +179,9 @@ fn compile_pass() { ::Properties as ::std::default::Default>::default() /> - }; + }).unwrap(); - ::yew::html! { + (::yew::html! { <> @@ -193,47 +194,51 @@ fn compile_pass() { >::from("child"))} int=1 /> - }; + }).unwrap(); let name_expr = "child"; - ::yew::html! { + (::yew::html! { - }; + }) + .unwrap(); let string = "child"; let int = 1; - ::yew::html! { + (::yew::html! { - }; + }) + .unwrap(); - ::yew::html! { + (::yew::html! { <> as ::std::convert::From<_>>::from(|_| ()))} /> as ::std::convert::From<_>>::from(|_| ())} /> >} /> - }; + }).unwrap(); let node_ref = <::yew::NodeRef as ::std::default::Default>::default(); - ::yew::html! { + (::yew::html! { <> - }; + }) + .unwrap(); let int = 1; let node_ref = <::yew::NodeRef as ::std::default::Default>::default(); - ::yew::html! { + (::yew::html! { <> - }; + }) + .unwrap(); let props = <::Properties as ::std::default::Default>::default(); let child_props = <::Properties as ::std::default::Default>::default(); - ::yew::html! { + (::yew::html! { <> @@ -268,21 +273,23 @@ fn compile_pass() { - }; + }) + .unwrap(); - ::yew::html! { + (::yew::html! { <> - }; + }) + .unwrap(); - ::yew::html! { + (::yew::html! { @@ -298,23 +305,26 @@ fn compile_pass() { ) } - }; + }) + .unwrap(); let children = ::std::vec![ ::yew::html_nested! { }, ::yew::html_nested! { }, ]; - ::yew::html! { + (::yew::html! { { ::std::clone::Clone::clone(&children) } - }; + }) + .unwrap(); // https://github.com/yewstack/yew/issues/1527 - ::yew::html! { + (::yew::html! { { for children } - }; + }) + .unwrap(); let variants = || -> ::std::vec::Vec { ::std::vec![ @@ -331,7 +341,7 @@ fn compile_pass() { ] }; - ::yew::html! { + (::yew::html! { <> { ::std::iter::Iterator::collect::<::yew::virtual_dom::VNode>( @@ -358,7 +368,8 @@ fn compile_pass() { } - }; + }) + .unwrap(); ::yew::html_nested! { 1 }; } diff --git a/packages/yew-macro/tests/html_macro/dyn-element-pass.rs b/packages/yew-macro/tests/html_macro/dyn-element-pass.rs index e26369903ec..49d3fd3f67a 100644 --- a/packages/yew-macro/tests/html_macro/dyn-element-pass.rs +++ b/packages/yew-macro/tests/html_macro/dyn-element-pass.rs @@ -43,14 +43,15 @@ fn main() { move || ::std::option::Option::unwrap(::std::iter::Iterator::next(&mut it)) }; - ::yew::html! { + (::yew::html! { <@{ dyn_tag() }> <@{ next_extra_tag() } class="extra-a"/> <@{ next_extra_tag() } class="extra-b"/> - }; + }) + .unwrap(); - ::yew::html! { + (::yew::html! { <@{ let tag = dyn_tag(); if tag == "test" { @@ -59,5 +60,6 @@ fn main() { "a" } }/> - }; + }) + .unwrap(); } diff --git a/packages/yew-macro/tests/html_macro/element-fail.stderr b/packages/yew-macro/tests/html_macro/element-fail.stderr index 7ee8ea4e9fc..03d47b2c55d 100644 --- a/packages/yew-macro/tests/html_macro/element-fail.stderr +++ b/packages/yew-macro/tests/html_macro/element-fail.stderr @@ -202,7 +202,7 @@ error: the property value must be either a literal or enclosed in braces. Consid 92 | html! { }; | ^^^^^^^^^^^ -warning: use of deprecated function `compile_fail::deprecated_use_of_class`: the use of `(...)` with the attribute `class` is deprecated and will be removed in version 0.19. Use the `classes!` macro instead. +warning: use of deprecated function `compile_fail::{closure#20}::deprecated_use_of_class`: the use of `(...)` with the attribute `class` is deprecated and will be removed in version 0.19. Use the `classes!` macro instead. --> tests/html_macro/element-fail.rs:80:25 | 80 | html! {
    }; diff --git a/packages/yew-macro/tests/html_macro/generic-component-pass.rs b/packages/yew-macro/tests/html_macro/generic-component-pass.rs index 49d88cb56b8..3f622fa6e0b 100644 --- a/packages/yew-macro/tests/html_macro/generic-component-pass.rs +++ b/packages/yew-macro/tests/html_macro/generic-component-pass.rs @@ -76,22 +76,24 @@ where } fn compile_pass() { - ::yew::html! { /> }; - ::yew::html! { >> }; + (::yew::html! { /> }).unwrap(); + (::yew::html! { >> }).unwrap(); - ::yew::html! { > /> }; - ::yew::html! { >>>> }; + (::yew::html! { > /> }).unwrap(); + (::yew::html! { >>>> }).unwrap(); - ::yew::html! { /> }; - ::yew::html! { >> }; - ::yew::html! { /> }; - ::yew::html! { >> }; + (::yew::html! { /> }).unwrap(); + (::yew::html! { >> }) + .unwrap(); + (::yew::html! { /> }).unwrap(); + (::yew::html! { >> }) + .unwrap(); - ::yew::html! { /> }; - ::yew::html! { >> }; + (::yew::html! { /> }).unwrap(); + (::yew::html! { >> }).unwrap(); - ::yew::html! { /> }; - ::yew::html! { >> }; + (::yew::html! { /> }).unwrap(); + (::yew::html! { >> }).unwrap(); } fn main() {} diff --git a/packages/yew-macro/tests/html_macro/html-element-pass.rs b/packages/yew-macro/tests/html_macro/html-element-pass.rs index c85989c96c9..732a130e51d 100644 --- a/packages/yew-macro/tests/html_macro/html-element-pass.rs +++ b/packages/yew-macro/tests/html_macro/html-element-pass.rs @@ -46,9 +46,10 @@ fn compile_pass() { || <::std::string::String as ::std::convert::From<&::std::primitive::str>>::from("test"); let mut extra_tags_iter = ::std::iter::IntoIterator::into_iter(::std::vec!["a", "b"]); - let attr_val_none: ::std::option::Option<::yew::virtual_dom::AttrValue> = ::std::option::Option::None; + let attr_val_none: ::std::option::Option<::yew::virtual_dom::AttrValue> = + ::std::option::Option::None; - ::yew::html! { + (::yew::html! {
    @@ -106,17 +107,17 @@ fn compile_pass() { onblur={::std::option::Option::Some(<::yew::Callback<::yew::FocusEvent> as ::std::convert::From<_>>::from(|_| ()))} />
    - }; + }).unwrap(); let children = ::std::vec![ ::yew::html! { { "Hello" } }, ::yew::html! { { "World" } }, ]; - ::yew::html! {
    {children}
    }; + (::yew::html! {
    {children}
    }).unwrap(); // handle misleading angle brackets - ::yew::html! {
    ::default()}>
    }; - ::yew::html! { }; + (::yew::html! {
    ::default()}>
    }).unwrap(); + (::yew::html! {
    }).unwrap(); } fn main() {} diff --git a/packages/yew-macro/tests/html_macro/html-if-pass.rs b/packages/yew-macro/tests/html_macro/html-if-pass.rs index 4ad4a265715..80d84d4d156 100644 --- a/packages/yew-macro/tests/html_macro/html-if-pass.rs +++ b/packages/yew-macro/tests/html_macro/html-if-pass.rs @@ -1,35 +1,35 @@ use yew::prelude::*; fn compile_pass_lit() { - html! { if true {} }; - html! { if true {
    } }; - html! { if true {
    } }; - html! { if true { <>
    } }; - html! { if true { { html! {} } } }; - html! { if true { { { let _x = 42; html! {} } } } }; - html! { if true {} else {} }; - html! { if true {} else if true {} }; - html! { if true {} else if true {} else {} }; - html! { if let Some(text) = Some("text") { { text } } }; - html! { <>
    if true {}
    }; - html! {
    if true {}
    }; + (html! { if true {} }).unwrap(); + (html! { if true {
    } }).unwrap(); + (html! { if true {
    } }).unwrap(); + (html! { if true { <>
    } }).unwrap(); + (html! { if true { { html! {} } } }).unwrap(); + (html! { if true { { { let _x = 42; html! {} } } } }).unwrap(); + (html! { if true {} else {} }).unwrap(); + (html! { if true {} else if true {} }).unwrap(); + (html! { if true {} else if true {} else {} }).unwrap(); + (html! { if let Some(text) = Some("text") { { text } } }).unwrap(); + (html! { <>
    if true {}
    }).unwrap(); + (html! {
    if true {}
    }).unwrap(); } fn compile_pass_expr() { let condition = true; - html! { if condition {} }; - html! { if condition {
    } }; - html! { if condition {
    } }; - html! { if condition { <>
    } }; - html! { if condition { { html! {} } } }; - html! { if condition { { { let _x = 42; html! {} } } } }; - html! { if condition {} else {} }; - html! { if condition {} else if condition {} }; - html! { if condition {} else if condition {} else {} }; - html! { if let Some(text) = Some("text") { { text } } }; - html! { <>
    if condition {}
    }; - html! {
    if condition {}
    }; + (html! { if condition {} }).unwrap(); + (html! { if condition {
    } }).unwrap(); + (html! { if condition {
    } }).unwrap(); + (html! { if condition { <>
    } }).unwrap(); + (html! { if condition { { html! {} } } }).unwrap(); + (html! { if condition { { { let _x = 42; html! {} } } } }).unwrap(); + (html! { if condition {} else {} }).unwrap(); + (html! { if condition {} else if condition {} }).unwrap(); + (html! { if condition {} else if condition {} else {} }).unwrap(); + (html! { if let Some(text) = Some("text") { { text } } }).unwrap(); + (html! { <>
    if condition {}
    }).unwrap(); + (html! {
    if condition {}
    }).unwrap(); } fn main() {} diff --git a/packages/yew-macro/tests/html_macro/html-node-pass.rs b/packages/yew-macro/tests/html_macro/html-node-pass.rs index a8c9dd1f7b3..18c2655f1ab 100644 --- a/packages/yew-macro/tests/html_macro/html-node-pass.rs +++ b/packages/yew-macro/tests/html_macro/html-node-pass.rs @@ -37,23 +37,23 @@ pub struct u8; pub struct usize; fn compile_pass() { - ::yew::html! { "" }; - ::yew::html! { 'a' }; - ::yew::html! { "hello" }; - ::yew::html! { 42 }; - ::yew::html! { 1.234 }; + (::yew::html! { "" }).unwrap(); + (::yew::html! { 'a' }).unwrap(); + (::yew::html! { "hello" }).unwrap(); + (::yew::html! { 42 }).unwrap(); + (::yew::html! { 1.234 }).unwrap(); - ::yew::html! { { "" } }; - ::yew::html! { { 'a' } }; - ::yew::html! { { "hello" } }; - ::yew::html! { { 42 } }; - ::yew::html! { { 1.234 } }; + (::yew::html! { { "" } }).unwrap(); + (::yew::html! { { 'a' } }).unwrap(); + (::yew::html! { { "hello" } }).unwrap(); + (::yew::html! { { 42 } }).unwrap(); + (::yew::html! { { 1.234 } }).unwrap(); - ::yew::html! { ::std::format!("Hello") }; - ::yew::html! { {<::std::string::String as ::std::convert::From<&::std::primitive::str>>::from("Hello") } }; + (::yew::html! { ::std::format!("Hello") }).unwrap(); + (::yew::html! { {<::std::string::String as ::std::convert::From<&::std::primitive::str>>::from("Hello") } }).unwrap(); let msg = "Hello"; - ::yew::html! { msg }; + (::yew::html! { msg }).unwrap(); } fn main() {} diff --git a/packages/yew-macro/tests/html_macro/iterable-fail.stderr b/packages/yew-macro/tests/html_macro/iterable-fail.stderr index c3000e923c6..081e7e61e18 100644 --- a/packages/yew-macro/tests/html_macro/iterable-fail.stderr +++ b/packages/yew-macro/tests/html_macro/iterable-fail.stderr @@ -25,44 +25,47 @@ error[E0277]: `()` is not an iterator = note: required by `into_iter` error[E0277]: `()` doesn't implement `std::fmt::Display` - --> $DIR/iterable-fail.rs:7:17 - | -7 | html! { for Vec::<()>::new().into_iter() }; - | ^^^ `()` cannot be formatted with the default formatter - | - = help: the trait `std::fmt::Display` is not implemented for `()` - = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead - = note: required because of the requirements on the impl of `ToString` for `()` - = note: required because of the requirements on the impl of `From<()>` for `VNode` - = note: required because of the requirements on the impl of `Into` for `()` - = note: required because of the requirements on the impl of `FromIterator<()>` for `VNode` + --> $DIR/iterable-fail.rs:7:17 + | +7 | html! { for Vec::<()>::new().into_iter() }; + | ^^^ `()` cannot be formatted with the default formatter + | + = help: the trait `std::fmt::Display` is not implemented for `()` + = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead + = note: required because of the requirements on the impl of `ToString` for `()` + = note: required because of the requirements on the impl of `From<()>` for `VNode` + = note: required because of the requirements on the impl of `Into` for `()` + = note: required because of the requirements on the impl of `TryIntoNode<(), VNode>` for `()` + = note: required by `try_into_node` error[E0277]: `()` doesn't implement `std::fmt::Display` - --> $DIR/iterable-fail.rs:10:17 - | -10 | html! { for empty }; - | ^^^^^ `()` cannot be formatted with the default formatter - | - = help: the trait `std::fmt::Display` is not implemented for `()` - = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead - = note: required because of the requirements on the impl of `ToString` for `()` - = note: required because of the requirements on the impl of `From<()>` for `VNode` - = note: required because of the requirements on the impl of `Into` for `()` - = note: required because of the requirements on the impl of `FromIterator<()>` for `VNode` + --> $DIR/iterable-fail.rs:10:17 + | +10 | html! { for empty }; + | ^^^^^ `()` cannot be formatted with the default formatter + | + = help: the trait `std::fmt::Display` is not implemented for `()` + = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead + = note: required because of the requirements on the impl of `ToString` for `()` + = note: required because of the requirements on the impl of `From<()>` for `VNode` + = note: required because of the requirements on the impl of `Into` for `()` + = note: required because of the requirements on the impl of `TryIntoNode<(), VNode>` for `()` + = note: required by `try_into_node` error[E0277]: `()` doesn't implement `std::fmt::Display` - --> $DIR/iterable-fail.rs:13:17 - | -13 | html! { for empty.iter() }; - | ^^^^^ `()` cannot be formatted with the default formatter - | - = help: the trait `std::fmt::Display` is not implemented for `()` - = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead - = note: required because of the requirements on the impl of `std::fmt::Display` for `&()` - = note: required because of the requirements on the impl of `ToString` for `&()` - = note: required because of the requirements on the impl of `From<&()>` for `VNode` - = note: required because of the requirements on the impl of `Into` for `&()` - = note: required because of the requirements on the impl of `FromIterator<&()>` for `VNode` + --> $DIR/iterable-fail.rs:13:17 + | +13 | html! { for empty.iter() }; + | ^^^^^ `()` cannot be formatted with the default formatter + | + = help: the trait `std::fmt::Display` is not implemented for `()` + = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead + = note: required because of the requirements on the impl of `std::fmt::Display` for `&()` + = note: required because of the requirements on the impl of `ToString` for `&()` + = note: required because of the requirements on the impl of `From<&()>` for `VNode` + = note: required because of the requirements on the impl of `Into` for `&()` + = note: required because of the requirements on the impl of `TryIntoNode<&(), VNode>` for `&()` + = note: required by `try_into_node` error[E0277]: `()` is not an iterator --> $DIR/iterable-fail.rs:18:19 @@ -70,10 +73,6 @@ error[E0277]: `()` is not an iterator 18 | { for () } | ^^ `()` is not an iterator | - ::: $WORKSPACE/packages/yew/src/utils/mod.rs - | - | IT: IntoIterator, - | ---------------------- required by this bound in `into_node_iter` - | = help: the trait `Iterator` is not implemented for `()` = note: required because of the requirements on the impl of `IntoIterator` for `()` + = note: required by `into_iter` diff --git a/packages/yew-macro/tests/html_macro/iterable-pass.rs b/packages/yew-macro/tests/html_macro/iterable-pass.rs index 18eadf7b042..2cf0bf97e1b 100644 --- a/packages/yew-macro/tests/html_macro/iterable-pass.rs +++ b/packages/yew-macro/tests/html_macro/iterable-pass.rs @@ -45,13 +45,13 @@ fn empty_iter() -> impl ::std::iter::Iterator { } fn main() { - ::yew::html! { for empty_iter() }; - ::yew::html! { for { empty_iter() } }; + (::yew::html! { for empty_iter() }).unwrap(); + (::yew::html! { for { empty_iter() } }).unwrap(); let empty = empty_vec(); - ::yew::html! { for empty }; + (::yew::html! { for empty }).unwrap(); - ::yew::html! { for empty_vec() }; - ::yew::html! { for ::std::iter::IntoIterator::into_iter(empty_vec()) }; - ::yew::html! { for ::std::iter::Iterator::map(0..3, |num| { ::yew::html! { { num } } }) }; + (::yew::html! { for empty_vec() }).unwrap(); + (::yew::html! { for ::std::iter::IntoIterator::into_iter(empty_vec()) }).unwrap(); + ( ::yew::html! { for ::std::iter::Iterator::map(0..3, |num| { ::yew::html! { { num } } }) } ).unwrap(); } diff --git a/packages/yew-macro/tests/html_macro/list-pass.rs b/packages/yew-macro/tests/html_macro/list-pass.rs index 3cbe619eaeb..f2ed4df5d22 100644 --- a/packages/yew-macro/tests/html_macro/list-pass.rs +++ b/packages/yew-macro/tests/html_macro/list-pass.rs @@ -37,22 +37,24 @@ pub struct u8; pub struct usize; fn main() { - ::yew::html! {}; - ::yew::html! { <> }; - ::yew::html! { + (::yew::html! {}).unwrap(); + (::yew::html! { <> }).unwrap(); + (::yew::html! { <> <> <> - }; - ::yew::html! { + }) + .unwrap(); + (::yew::html! { - }; + }) + .unwrap(); let children = ::std::vec![ ::yew::html! { { "Hello" } }, ::yew::html! { { "World" } }, ]; - ::yew::html! { <>{ children } }; + (::yew::html! { <>{ children } }).unwrap(); } diff --git a/packages/yew-macro/tests/html_macro/node-fail.stderr b/packages/yew-macro/tests/html_macro/node-fail.stderr index 751f9caf70e..8f67cd7ad74 100644 --- a/packages/yew-macro/tests/html_macro/node-fail.stderr +++ b/packages/yew-macro/tests/html_macro/node-fail.stderr @@ -50,7 +50,9 @@ error[E0277]: `()` doesn't implement `std::fmt::Display` = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead = note: required because of the requirements on the impl of `ToString` for `()` = note: required because of the requirements on the impl of `From<()>` for `VNode` - = note: required by `from` + = note: required because of the requirements on the impl of `Into` for `()` + = note: required because of the requirements on the impl of `TryIntoNode<(), VNode>` for `()` + = note: required by `try_into_node` error[E0277]: `()` doesn't implement `std::fmt::Display` --> $DIR/node-fail.rs:17:9 @@ -62,4 +64,6 @@ error[E0277]: `()` doesn't implement `std::fmt::Display` = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead = note: required because of the requirements on the impl of `ToString` for `()` = note: required because of the requirements on the impl of `From<()>` for `VNode` - = note: required by `from` + = note: required because of the requirements on the impl of `Into` for `()` + = note: required because of the requirements on the impl of `TryIntoNode<(), VNode>` for `()` + = note: required by `try_into_node` diff --git a/packages/yew-macro/tests/html_macro/node-pass.rs b/packages/yew-macro/tests/html_macro/node-pass.rs index 1dfaea9c8a1..d40e70c6254 100644 --- a/packages/yew-macro/tests/html_macro/node-pass.rs +++ b/packages/yew-macro/tests/html_macro/node-pass.rs @@ -37,23 +37,23 @@ pub struct u8; pub struct usize; fn main() { - ::yew::html! { "" }; - ::yew::html! { 'a' }; - ::yew::html! { "hello" }; - ::yew::html! { "42" }; - ::yew::html! { "1.234" }; - ::yew::html! { "true" }; + (::yew::html! { "" }).unwrap(); + (::yew::html! { 'a' }).unwrap(); + (::yew::html! { "hello" }).unwrap(); + (::yew::html! { "42" }).unwrap(); + (::yew::html! { "1.234" }).unwrap(); + (::yew::html! { "true" }).unwrap(); - ::yew::html! { { "" } }; - ::yew::html! { { 'a' } }; - ::yew::html! { { "hello" } }; - ::yew::html! { { "42" } }; - ::yew::html! { { "1.234" } }; - ::yew::html! { { "true" } }; + (::yew::html! { { "" } }).unwrap(); + (::yew::html! { { 'a' } }).unwrap(); + (::yew::html! { { "hello" } }).unwrap(); + (::yew::html! { { "42" } }).unwrap(); + (::yew::html! { { "1.234" } }).unwrap(); + (::yew::html! { { "true" } }).unwrap(); - ::yew::html! { ::std::format!("Hello") }; - ::yew::html! { ::std::string::ToString::to_string("Hello") }; + (::yew::html! { ::std::format!("Hello") }).unwrap(); + (::yew::html! { ::std::string::ToString::to_string("Hello") }).unwrap(); let msg = "Hello"; - ::yew::html! { msg }; + (::yew::html! { msg }).unwrap(); } diff --git a/packages/yew-macro/tests/html_macro/svg-pass.rs b/packages/yew-macro/tests/html_macro/svg-pass.rs index eb1bb3a0963..bddfe1e398f 100644 --- a/packages/yew-macro/tests/html_macro/svg-pass.rs +++ b/packages/yew-macro/tests/html_macro/svg-pass.rs @@ -39,17 +39,18 @@ pub struct usize; fn main() { // Ensure Rust keywords can be used as element names. // See: #1771 - ::yew::html! { + (::yew::html! { {"Go to example.org"} - }; + }) + .unwrap(); // some general SVG - ::yew::html! { + ( ::yew::html! { @@ -65,5 +66,5 @@ fn main() { - }; + }).unwrap(); } diff --git a/packages/yew-macro/tests/html_macro_test.rs b/packages/yew-macro/tests/html_macro_test.rs index 92dca13b234..75b5843f75e 100644 --- a/packages/yew-macro/tests/html_macro_test.rs +++ b/packages/yew-macro/tests/html_macro_test.rs @@ -14,17 +14,19 @@ fn html_macro() { expected = "a dynamic tag tried to create a `
    ` tag with children. `
    ` is a void element which can't have any children." )] fn dynamic_tags_catch_void_elements() { - html! { + (html! { <@{"br"}> { "No children allowed" } - }; + }) + .unwrap(); } #[test] #[should_panic(expected = "a dynamic tag returned a tag name containing non ASCII characters: `❤`")] fn dynamic_tags_catch_non_ascii() { - html! { + (html! { <@{"❤"}/> - }; + }) + .unwrap(); } diff --git a/packages/yew/src/html/component/children.rs b/packages/yew/src/html/component/children.rs index c27c3ee2007..2e3abdfd6e8 100644 --- a/packages/yew/src/html/component/children.rs +++ b/packages/yew/src/html/component/children.rs @@ -1,5 +1,6 @@ //! Component children module +use crate::html::{Html, IntoPropValue}; use crate::virtual_dom::{VChild, VNode}; use std::fmt; @@ -59,6 +60,12 @@ use std::fmt; /// ``` pub type Children = ChildrenRenderer; +impl IntoPropValue for Html { + fn into_prop_value(self) -> Children { + Children::new(vec![self.expect("Children must not be an RenderError.")]) + } +} + /// A type used for accepting children elements in Component::Properties and accessing their props. /// /// # Example diff --git a/packages/yew/src/html/error.rs b/packages/yew/src/html/error.rs index a3ee39ad9e1..b3ff9d27203 100644 --- a/packages/yew/src/html/error.rs +++ b/packages/yew/src/html/error.rs @@ -1,7 +1,7 @@ use thiserror::Error; /// Render Error. -#[derive(Error, Debug, Clone)] +#[derive(Error, Debug, Clone, PartialEq)] pub enum RenderError { /// Component Rendering Suspended #[error("component rendering is suspended.")] diff --git a/packages/yew/src/lib.rs b/packages/yew/src/lib.rs index 2cf6c50b85a..e91a1b7be0d 100644 --- a/packages/yew/src/lib.rs +++ b/packages/yew/src/lib.rs @@ -126,7 +126,7 @@ pub use yew_macro::html; /// ``` /// # use yew::prelude::*; /// use yew::html::ChildrenRenderer; -/// use yew::virtual_dom::VChild; +/// use yew::virtual_dom::{VChild, VNode}; /// /// #[derive(Clone, Properties, PartialEq)] /// struct ListProps { @@ -157,10 +157,9 @@ pub use yew_macro::html; /// fn from(child: VChild) -> Self { Self } /// } /// -/// impl Into for ListItem { -/// fn into(self) -> Html { html! { } } +/// impl Into for ListItem { +/// fn into(self) -> VNode { (html! { }).unwrap() } /// } -/// /// // You can use `List` with nested `ListItem` components. /// // Using any other kind of element would result in a compile error. /// # fn test() -> Html { diff --git a/packages/yew/src/utils/mod.rs b/packages/yew/src/utils/mod.rs index 47c0bb10e75..fd8b1d19a4f 100644 --- a/packages/yew/src/utils/mod.rs +++ b/packages/yew/src/utils/mod.rs @@ -6,9 +6,9 @@ use yew::html::RenderResult; /// A special type necessary for flattening components returned from nested html macros. #[derive(Debug)] -pub struct RenderNode(O, PhantomData); +pub struct Node(O, PhantomData); -impl RenderNode { +impl Node { /// Returns the wrapped value. pub fn into_value(self) -> O { self.0 @@ -16,26 +16,26 @@ impl RenderNode { } /// A special trait to convert to a `RenderResult`. -pub trait TryIntoRenderNode { +pub trait TryIntoNode { /// Performs the conversion. - fn try_into_render_node(self) -> RenderResult>; + fn try_into_node(self) -> RenderResult>; } -impl TryIntoRenderNode for I +impl TryIntoNode for I where I: Into, { - fn try_into_render_node(self) -> RenderResult> { - Ok(RenderNode(self.into(), PhantomData::default())) + fn try_into_node(self) -> RenderResult> { + Ok(Node(self.into(), PhantomData::default())) } } -impl TryIntoRenderNode for RenderResult +impl TryIntoNode for RenderResult where I: Into, { - fn try_into_render_node(self) -> RenderResult> { - Ok(RenderNode(self?.into(), PhantomData::default())) + fn try_into_node(self) -> RenderResult> { + Ok(Node(self?.into(), PhantomData::default())) } } @@ -112,6 +112,21 @@ where } } +impl TryIntoNodeSeq for Vec> +where + I: Into, +{ + fn try_into_node_seq(self) -> RenderResult> { + let mut nodes = Vec::new(); + + for node in self { + nodes.push(node?.into()); + } + + Ok(NodeSeq(nodes, PhantomData::default())) + } +} + impl TryIntoNodeSeq for RenderResult> where I: Into, @@ -124,6 +139,36 @@ where } } +impl TryIntoNodeSeq for ChildrenRenderer> +where + I: Into, +{ + fn try_into_node_seq(self) -> RenderResult> { + let mut nodes = Vec::new(); + + for node in self { + nodes.push(node?.into()); + } + + Ok(NodeSeq(nodes, PhantomData::default())) + } +} + +impl TryIntoNodeSeq for RenderResult>> +where + I: Into, +{ + fn try_into_node_seq(self) -> RenderResult> { + let mut nodes = Vec::new(); + + for node in self? { + nodes.push(node?.into()); + } + + Ok(NodeSeq(nodes, PhantomData::default())) + } +} + /// Hack to force type mismatch compile errors in yew-macro. // // TODO: replace with `compile_error!`, when `type_name_of_val` is stabilised (https://github.com/rust-lang/rust/issues/66359). diff --git a/packages/yew/src/virtual_dom/key.rs b/packages/yew/src/virtual_dom/key.rs index e6ead5ca905..e2f81c561d6 100644 --- a/packages/yew/src/virtual_dom/key.rs +++ b/packages/yew/src/virtual_dom/key.rs @@ -80,7 +80,7 @@ mod test { #[test] fn all_key_conversions() { - html! { + (html! {

    ::from("rc")}>

    @@ -98,6 +98,7 @@ mod test {

    - }; + }) + .unwrap(); } } diff --git a/packages/yew/src/virtual_dom/vcomp.rs b/packages/yew/src/virtual_dom/vcomp.rs index c7b58b74912..06237292fa0 100644 --- a/packages/yew/src/virtual_dom/vcomp.rs +++ b/packages/yew/src/virtual_dom/vcomp.rs @@ -324,11 +324,11 @@ mod tests { let parent_scope: AnyScope = crate::html::Scope::::new(None).into(); let parent_element = document.create_element("div").unwrap(); - let mut ancestor = html! { }; + let mut ancestor = html! { }.unwrap(); ancestor.apply(&parent_scope, &parent_element, NodeRef::default(), None); for _ in 0..10000 { - let mut node = html! { }; + let mut node = html! { }.unwrap(); node.apply( &parent_scope, &parent_element, @@ -341,37 +341,42 @@ mod tests { #[test] fn set_properties_to_component() { - html! { + (html! { - }; + }) + .unwrap(); - html! { + (html! { - }; + }) + .unwrap(); - html! { + (html! { - }; + }) + .unwrap(); - html! { + (html! { - }; + }) + .unwrap(); let props = Props { field_1: 1, field_2: 1, }; - html! { + (html! { - }; + }) + .unwrap(); } #[test] fn set_component_key() { let test_key: Key = "test".to_string().into(); - let check_key = |vnode: VNode| { - assert_eq!(vnode.key().as_ref(), Some(&test_key)); + let check_key = |vnode: Html| { + assert_eq!(vnode.unwrap().key().as_ref(), Some(&test_key)); }; let props = Props { @@ -391,8 +396,11 @@ mod tests { fn set_component_node_ref() { let test_node: Node = document().create_text_node("test").into(); let test_node_ref = NodeRef::new(test_node); - let check_node_ref = |vnode: VNode| { - assert_eq!(vnode.unchecked_first_node(), test_node_ref.get().unwrap()); + let check_node_ref = |vnode: Html| { + assert_eq!( + vnode.unwrap().unchecked_first_node(), + test_node_ref.get().unwrap() + ); }; let props = Props { @@ -483,11 +491,11 @@ mod tests { (scope, parent) } - fn get_html(mut node: Html, scope: &AnyScope, parent: &Element) -> String { + fn get_html(node: Html, scope: &AnyScope, parent: &Element) -> String { // clear parent parent.set_inner_html(""); - node.apply(scope, parent, NodeRef::default(), None); + node.unwrap().apply(scope, parent, NodeRef::default(), None); parent.inner_html() } @@ -497,7 +505,7 @@ mod tests { let children: Vec<_> = vec!["a", "b", "c"] .drain(..) - .map(|text| html! {{ text }}) + .map(|text| html! {{ text }}.unwrap()) .collect(); let children_renderer = Children::new(children.clone()); let expected_html = "\ @@ -545,7 +553,7 @@ mod tests { document().body().unwrap().append_child(&parent).unwrap(); let node_ref = NodeRef::default(); - let mut elem: VNode = html! { }; + let mut elem: VNode = html! { }.unwrap(); elem.apply(&scope, &parent, NodeRef::default(), None); let parent_node = parent.deref(); assert_eq!(node_ref.get(), parent_node.first_child()); @@ -612,7 +620,8 @@ mod layout_tests { >> {"C"} > - }, + } + .unwrap(), expected: "C", }; @@ -622,7 +631,8 @@ mod layout_tests { > {"A"} > - }, + } + .unwrap(), expected: "A", }; @@ -633,7 +643,8 @@ mod layout_tests { >> {"B"} > - }, + } + .unwrap(), expected: "B", }; @@ -644,7 +655,8 @@ mod layout_tests { >{"A"}> {"B"} > - }, + } + .unwrap(), expected: "AB", }; @@ -659,7 +671,8 @@ mod layout_tests { {"B"} > - }, + } + .unwrap(), expected: "AB", }; @@ -675,7 +688,8 @@ mod layout_tests { {"C"} > - }, + } + .unwrap(), expected: "ABC", }; @@ -693,7 +707,8 @@ mod layout_tests { {"C"} > - }, + } + .unwrap(), expected: "ABC", }; @@ -713,7 +728,8 @@ mod layout_tests { {"C"} > - }, + } + .unwrap(), expected: "ABC", }; @@ -733,7 +749,8 @@ mod layout_tests { {"C"} > - }, + } + .unwrap(), expected: "ABC", }; @@ -753,7 +770,8 @@ mod layout_tests { {"C"} > - }, + } + .unwrap(), expected: "ABC", }; @@ -773,7 +791,8 @@ mod layout_tests { {"C"} > - }, + } + .unwrap(), expected: "ABC", }; @@ -807,7 +826,8 @@ mod layout_tests { >> <> > - }, + } + .unwrap(), expected: "ABC", }; @@ -855,7 +875,8 @@ mod layout_tests { { "world" } } - }, + } + .unwrap(), expected: "
    • helloworld
    ", }; diff --git a/packages/yew/src/virtual_dom/vlist.rs b/packages/yew/src/virtual_dom/vlist.rs index 001c42fde61..e02e9443902 100644 --- a/packages/yew/src/virtual_dom/vlist.rs +++ b/packages/yew/src/virtual_dom/vlist.rs @@ -392,7 +392,8 @@ mod layout_tests { {"e"} - }, + } + .unwrap(), expected: "abcde", }; @@ -406,7 +407,8 @@ mod layout_tests { {"e"} {"f"} - }, + } + .unwrap(), expected: "abef", }; @@ -419,7 +421,8 @@ mod layout_tests { {"b"} {"e"} - }, + } + .unwrap(), expected: "abe", }; @@ -435,7 +438,8 @@ mod layout_tests { {"b"} {"e"} - }, + } + .unwrap(), expected: "acdbe", }; @@ -529,7 +533,8 @@ mod layout_tests_keys { {VNode::VRef(vref_node)} - }, + } + .unwrap(), expected: "acd

    0

    foobar", }); @@ -543,7 +548,8 @@ mod layout_tests_keys {

    - }, + } + .unwrap(), expected: "

    ", }, TestLayout { @@ -556,7 +562,8 @@ mod layout_tests_keys {

    - }, + } + .unwrap(), expected: "

    ", }, ]); @@ -569,7 +576,8 @@ mod layout_tests_keys { - }, + } + .unwrap(), expected: "", }, TestLayout { @@ -579,7 +587,8 @@ mod layout_tests_keys {

    - }, + } + .unwrap(), expected: "

    ", }, ]); @@ -592,7 +601,8 @@ mod layout_tests_keys { - }, + } + .unwrap(), expected: "", }, TestLayout { @@ -603,7 +613,8 @@ mod layout_tests_keys {

    - }, + } + .unwrap(), expected: "

    ", }, ]); @@ -616,7 +627,8 @@ mod layout_tests_keys { - }, + } + .unwrap(), expected: "", }, TestLayout { @@ -627,7 +639,8 @@ mod layout_tests_keys { - }, + } + .unwrap(), expected: "

    ", }, ]); @@ -641,7 +654,8 @@ mod layout_tests_keys {

    - }, + } + .unwrap(), expected: "

    ", }, TestLayout { @@ -651,7 +665,8 @@ mod layout_tests_keys {

    - }, + } + .unwrap(), expected: "

    ", }, ]); @@ -665,7 +680,8 @@ mod layout_tests_keys {

    - }, + } + .unwrap(), expected: "

    ", }, TestLayout { @@ -675,7 +691,8 @@ mod layout_tests_keys { - }, + } + .unwrap(), expected: "", }, ]); @@ -689,7 +706,8 @@ mod layout_tests_keys {

    - }, + } + .unwrap(), expected: "

    ", }, TestLayout { @@ -700,7 +718,8 @@ mod layout_tests_keys {
    - }, + } + .unwrap(), expected: "", }, ]); @@ -715,7 +734,8 @@ mod layout_tests_keys {

    - }, + } + .unwrap(), expected: "

    ", }, TestLayout { @@ -727,7 +747,8 @@ mod layout_tests_keys {

    - }, + } + .unwrap(), expected: "

    ", }, ]); @@ -742,7 +763,8 @@ mod layout_tests_keys {

    - }, + } + .unwrap(), expected: "

    ", }, TestLayout { @@ -754,7 +776,8 @@ mod layout_tests_keys {

    - }, + } + .unwrap(), expected: "

    ", }, ]); @@ -769,7 +792,8 @@ mod layout_tests_keys {

    - }, + } + .unwrap(), expected: "

    ", }, TestLayout { @@ -781,7 +805,8 @@ mod layout_tests_keys { - }, + } + .unwrap(), expected: "

    ", }, ]); @@ -801,7 +826,8 @@ mod layout_tests_keys { - }, + } + .unwrap(), expected: "

    ", }, TestLayout { @@ -813,7 +839,8 @@ mod layout_tests_keys { - }, + } + .unwrap(), expected: "

    ", }, ]); @@ -829,7 +856,8 @@ mod layout_tests_keys { - }, + } + .unwrap(), expected: "

    ", }, TestLayout { @@ -842,7 +870,8 @@ mod layout_tests_keys { - }, + } + .unwrap(), expected: "

    ", }, ]); @@ -858,7 +887,8 @@ mod layout_tests_keys { - }, + } + .unwrap(), expected: "

    ", }, TestLayout { @@ -871,7 +901,8 @@ mod layout_tests_keys { - }, + } + .unwrap(), expected: "

    ", }, ]); @@ -894,7 +925,8 @@ mod layout_tests_keys { - }, + } + .unwrap(), expected: "

    ", }, TestLayout { @@ -907,7 +939,8 @@ mod layout_tests_keys { - }, + } + .unwrap(), expected: "

    ", }, ]); @@ -923,7 +956,8 @@ mod layout_tests_keys { - }, + } + .unwrap(), expected: "

    ", }, TestLayout { @@ -936,7 +970,8 @@ mod layout_tests_keys { - }, + } + .unwrap(), expected: "

    ", }, ]); @@ -952,7 +987,8 @@ mod layout_tests_keys { - }, + } + .unwrap(), expected: "

    ", }, TestLayout { @@ -965,7 +1001,8 @@ mod layout_tests_keys { - }, + } + .unwrap(), expected: "

    ", }, ]); @@ -981,7 +1018,8 @@ mod layout_tests_keys { - }, + } + .unwrap(), expected: "

    ", }, TestLayout { @@ -994,7 +1032,8 @@ mod layout_tests_keys { - }, + } + .unwrap(), expected: "

    ", }, ]); @@ -1013,7 +1052,8 @@ mod layout_tests_keys { - }, + } + .unwrap(), expected: "", }, TestLayout { @@ -1029,7 +1069,8 @@ mod layout_tests_keys { - }, + } + .unwrap(), expected: "", }, ]); @@ -1049,7 +1090,8 @@ mod layout_tests_keys { - }, + } + .unwrap(), expected: "

    ", }, TestLayout { @@ -1066,7 +1108,8 @@ mod layout_tests_keys { - }, + } + .unwrap(), expected: "

    ", }, ]); @@ -1079,7 +1122,8 @@ mod layout_tests_keys { - }, + } + .unwrap(), expected: "", }, TestLayout { @@ -1090,7 +1134,8 @@ mod layout_tests_keys { - }, + } + .unwrap(), expected: "

    0

    ", }, ]); @@ -1103,7 +1148,8 @@ mod layout_tests_keys { - }, + } + .unwrap(), expected: "", }, TestLayout { @@ -1114,7 +1160,8 @@ mod layout_tests_keys { - }, + } + .unwrap(), expected: "

    0

    ", }, ]); @@ -1127,7 +1174,8 @@ mod layout_tests_keys { - }, + } + .unwrap(), expected: "", }, TestLayout { @@ -1138,7 +1186,8 @@ mod layout_tests_keys { - }, + } + .unwrap(), expected: "

    0

    ", }, ]); @@ -1152,7 +1201,8 @@ mod layout_tests_keys { - }, + } + .unwrap(), expected: "

    1

    2

    3

    ", }, TestLayout { @@ -1163,7 +1213,8 @@ mod layout_tests_keys { - }, + } + .unwrap(), expected: "

    3

    2

    1

    ", }, ]); @@ -1177,7 +1228,8 @@ mod layout_tests_keys {

    {"21"}

    {"22"}

    {"31"}

    {"32"}

    - }, + } + .unwrap(), expected: "

    11

    12

    21

    22

    31

    32

    ", }, TestLayout { @@ -1188,7 +1240,8 @@ mod layout_tests_keys {

    {"21"}

    {"22"}

    {"11"}

    {"12"}

    - }, + } + .unwrap(), expected: "

    31

    32

    21

    22

    11

    12

    ", }, ]); @@ -1201,7 +1254,8 @@ mod layout_tests_keys {
    - }, + } + .unwrap(), expected: "

    1

    2

    ", }, TestLayout { @@ -1215,7 +1269,8 @@ mod layout_tests_keys {

    {"2"}

    - }, + } + .unwrap(), expected: "

    1

    2

    ", }, ]); @@ -1232,7 +1287,8 @@ mod layout_tests_keys {

    {"4"}

    {"6"}

    - }, + } + .unwrap(), expected: "

    1

    3

    5

    2

    4

    6

    ", }, TestLayout { @@ -1246,7 +1302,8 @@ mod layout_tests_keys { - }, + } + .unwrap(), expected: "

    6

    5

    4

    3

    2

    1

    ", }, ]); @@ -1260,7 +1317,8 @@ mod layout_tests_keys {

    {"2"}

    {"3"}

    - }, + } + .unwrap(), expected: "

    1

    2

    3

    ", }, TestLayout { @@ -1271,7 +1329,8 @@ mod layout_tests_keys { - }, + } + .unwrap(), expected: "

    3

    2

    1

    ", }, ]); diff --git a/packages/yew/src/virtual_dom/vportal.rs b/packages/yew/src/virtual_dom/vportal.rs index afde447db4c..ba3428e32ab 100644 --- a/packages/yew/src/virtual_dom/vportal.rs +++ b/packages/yew/src/virtual_dom/vportal.rs @@ -130,12 +130,13 @@ mod layout_tests { {VNode::VRef(first_target.clone().into())} {VNode::VRef(second_target.clone().into())} {VNode::VPortal(VPortal::new( - html! { {"PORTAL"} }, + html! { {"PORTAL"} }.unwrap(), first_target.clone(), ))} {"AFTER"}
    - }, + } + .unwrap(), expected: "
    PORTALAFTER
    ", }); layouts.push(TestLayout { @@ -145,12 +146,13 @@ mod layout_tests { {VNode::VRef(first_target.clone().into())} {VNode::VRef(second_target.clone().into())} {VNode::VPortal(VPortal::new( - html! { {"PORTAL"} }, + html! { {"PORTAL"} }.unwrap(), second_target.clone(), ))} {"AFTER"}
    - }, + } + .unwrap(), expected: "
    PORTALAFTER
    ", }); layouts.push(TestLayout { @@ -162,7 +164,8 @@ mod layout_tests { {"FOO"} {"AFTER"}
    - }, + } + .unwrap(), expected: "
    FOOAFTER
    ", }); layouts.push(TestLayout { @@ -171,12 +174,13 @@ mod layout_tests {
    {VNode::VRef(target_with_child.clone().into())} {VNode::VPortal(VPortal::new_before( - html! { {"PORTAL"} }, + html! { {"PORTAL"} }.unwrap(), target_with_child.clone(), Some(target_child.clone().into()), ))}
    - }, + } + .unwrap(), expected: "
    PORTAL
    ", }); diff --git a/packages/yew/src/virtual_dom/vtag.rs b/packages/yew/src/virtual_dom/vtag.rs index 9e6130ec03c..40e5900fc4e 100644 --- a/packages/yew/src/virtual_dom/vtag.rs +++ b/packages/yew/src/virtual_dom/vtag.rs @@ -797,9 +797,9 @@ mod tests { let namespace = Some(namespace); let svg_el = document.create_element_ns(namespace, "svg").unwrap(); - let mut g_node = html! { }; + let mut g_node = html! { }.unwrap(); let path_node = html! { }; - let mut svg_node = html! { {path_node} }; + let mut svg_node = html! { {path_node} }.unwrap(); let svg_tag = assert_vtag_mut(&mut svg_node); svg_tag.apply(&scope, &div_el, NodeRef::default(), None); @@ -892,7 +892,8 @@ mod tests {

    - }; + } + .unwrap(); if let VNode::VTag(vtag) = a { assert_eq!( vtag.attributes @@ -913,7 +914,7 @@ mod tests { document().body().unwrap().append_child(&parent).unwrap(); - let mut elem = html! {
    }; + let mut elem = html! {
    }.unwrap(); VDiff::apply(&mut elem, &scope, &parent, NodeRef::default(), None); let vtag = assert_vtag_mut(&mut elem); // test if the className has not been set @@ -926,7 +927,7 @@ mod tests { document().body().unwrap().append_child(&parent).unwrap(); - let mut elem = gen_html(); + let mut elem = gen_html().unwrap(); VDiff::apply(&mut elem, &scope, &parent, NodeRef::default(), None); let vtag = assert_vtag_mut(&mut elem); // test if the className has been set @@ -953,7 +954,7 @@ mod tests { let expected = "not_changed_value"; // Initial state - let mut elem = html! { }; + let mut elem = html! { }.unwrap(); VDiff::apply(&mut elem, &scope, &parent, NodeRef::default(), None); let vtag = if let VNode::VTag(vtag) = elem { vtag @@ -967,7 +968,7 @@ mod tests { input.unwrap().set_value("User input"); let ancestor = vtag; - let mut elem = html! { }; + let mut elem = html! { }.unwrap(); let vtag = assert_vtag_mut(&mut elem); // Sync happens here @@ -996,7 +997,7 @@ mod tests { document().body().unwrap().append_child(&parent).unwrap(); // Initial state - let mut elem = html! { }; + let mut elem = html! { }.unwrap(); VDiff::apply(&mut elem, &scope, &parent, NodeRef::default(), None); let vtag = if let VNode::VTag(vtag) = elem { vtag @@ -1010,7 +1011,7 @@ mod tests { input.unwrap().set_value("User input"); let ancestor = vtag; - let mut elem = html! { }; + let mut elem = html! { }.unwrap(); let vtag = assert_vtag_mut(&mut elem); // Value should not be refreshed @@ -1046,7 +1047,8 @@ mod tests { let mut builder = String::new(); builder.push('a'); builder - }/> }; + }/> } + .unwrap(); VDiff::apply(&mut elem, &scope, &parent, NodeRef::default(), None); let vtag = assert_vtag_mut(&mut elem); @@ -1061,7 +1063,8 @@ mod tests { fn dynamic_tags_handle_value_attribute() { let mut div_el = html! { <@{"div"} value="Hello"/> - }; + } + .unwrap(); let div_vtag = assert_vtag_mut(&mut div_el); assert!(div_vtag.value().is_none()); let v: Option<&str> = div_vtag @@ -1073,7 +1076,8 @@ mod tests { let mut input_el = html! { <@{"input"} value="World"/> - }; + } + .unwrap(); let input_vtag = assert_vtag_mut(&mut input_el); assert_eq!(input_vtag.value(), Some(&AttrValue::Static("World"))); assert!(!input_vtag.attributes.iter().any(|(k, _)| k == "value")); @@ -1083,7 +1087,8 @@ mod tests { fn dynamic_tags_handle_weird_capitalization() { let mut el = html! { <@{"tExTAREa"}/> - }; + } + .unwrap(); let vtag = assert_vtag_mut(&mut el); assert_eq!(vtag.tag(), "textarea"); } @@ -1096,7 +1101,7 @@ mod tests { document().body().unwrap().append_child(&parent).unwrap(); let node_ref = NodeRef::default(); - let mut elem: VNode = html! {
    }; + let mut elem: VNode = html! {
    }.unwrap(); assert_vtag_mut(&mut elem); elem.apply(&scope, &parent, NodeRef::default(), None); let parent_node = parent.deref(); @@ -1112,14 +1117,14 @@ mod tests { document().body().unwrap().append_child(&parent).unwrap(); let node_ref_a = NodeRef::default(); - let mut elem_a = html! {
    }; + let mut elem_a = html! {
    }.unwrap(); elem_a.apply(&scope, &parent, NodeRef::default(), None); // save the Node to check later that it has been reused. let node_a = node_ref_a.get().unwrap(); let node_ref_b = NodeRef::default(); - let mut elem_b = html! {
    }; + let mut elem_b = html! {
    }.unwrap(); elem_b.apply(&scope, &parent, NodeRef::default(), Some(elem_a)); let node_b = node_ref_b.get().unwrap(); @@ -1158,7 +1163,8 @@ mod layout_tests { {"b"} - }, + } + .unwrap(), expected: "
    • a
    • b
    ", }; @@ -1176,7 +1182,8 @@ mod layout_tests { {"d"} - }, + } + .unwrap(), expected: "
    • a
    • b
    • d
    ", }; @@ -1197,7 +1204,8 @@ mod layout_tests { {"d"} - }, + } + .unwrap(), expected: "
    • a
    • b
    • c
    • d
    ", }; @@ -1220,7 +1228,8 @@ mod layout_tests { - }, + } + .unwrap(), expected: "
    • a
    • b
    • c
    • d
    ", }; diff --git a/packages/yew/src/virtual_dom/vtext.rs b/packages/yew/src/virtual_dom/vtext.rs index c6f8aa658ab..cdfee1f80d5 100644 --- a/packages/yew/src/virtual_dom/vtext.rs +++ b/packages/yew/src/virtual_dom/vtext.rs @@ -106,13 +106,15 @@ mod test { #[test] fn text_as_root() { - html! { + (html! { "Text Node As Root" - }; + }) + .unwrap(); - html! { + (html! { { "Text Node As Root" } - }; + }) + .unwrap(); } } @@ -133,13 +135,13 @@ mod layout_tests { fn diff() { let layout1 = TestLayout { name: "1", - node: html! { "a" }, + node: html! { "a" }.unwrap(), expected: "a", }; let layout2 = TestLayout { name: "2", - node: html! { "b" }, + node: html! { "b" }.unwrap(), expected: "b", }; @@ -150,7 +152,8 @@ mod layout_tests { {"a"} {"b"} - }, + } + .unwrap(), expected: "ab", }; @@ -161,7 +164,8 @@ mod layout_tests { {"b"} {"a"} - }, + } + .unwrap(), expected: "ba", }; diff --git a/website/docs/concepts/components.md b/website/docs/concepts/components.md index 579751f2522..025a90bf948 100644 --- a/website/docs/concepts/components.md +++ b/website/docs/concepts/components.md @@ -19,8 +19,8 @@ in the lifecycle of a component. ### Create -When a component is created, it receives properties from its parent component and is stored within -the `Context` thats passed down to the `create` method. The properties can be used to +When a component is created, it receives properties from its parent component and is stored within +the `Context` thats passed down to the `create` method. The properties can be used to initialize the component's state and the "link" can be used to register callbacks or send messages to the component. ```rust @@ -195,7 +195,7 @@ impl Component for MyComponent { html! { // impl } - } + } } ``` @@ -221,7 +221,7 @@ the same component after every render when that update also requests the compone A simple example can be seen below: ```rust -use yew::{Context, Component, Html}; +use yew::{Context, Component, Html, HtmlDefault}; struct Comp; @@ -302,4 +302,3 @@ enum Msg { All component lifecycle methods take a context object. This object provides a reference to component's scope, which allows sending messages to a component and the props passed to the component. - diff --git a/website/docs/concepts/components/children.md b/website/docs/concepts/components/children.md index 4bdb93f202d..585e1955b75 100644 --- a/website/docs/concepts/components/children.md +++ b/website/docs/concepts/components/children.md @@ -4,7 +4,7 @@ title: "Children" ## General usage -_Most of the time,_ when allowing a component to have children, you don't care +_Most of the time,_ when allowing a component to have children, you don't care what type of children the component has. In such cases, the below example will suffice. @@ -99,8 +99,8 @@ for better ergonomics. If you don't want to use it, you can manually implement ```rust use yew::{ - html, html::ChildrenRenderer, virtual_dom::VChild, Component, - Context, Html, Properties, + html, html::ChildrenRenderer, virtual_dom::VChild, Component, + Context, Html, Properties, virtual_dom::VNode }; pub struct Primary; @@ -145,8 +145,8 @@ pub enum Item { // Now, we implement `Into` so that yew knows how to render `Item`. #[allow(clippy::from_over_into)] -impl Into for Item { - fn into(self) -> Html { +impl Into for Item { + fn into(self) -> VNode { match self { Self::Primary(child) => child.into(), Self::Secondary(child) => child.into(), @@ -181,12 +181,12 @@ impl Component for List { ``` ### Optional typed child -You can also have a single optional child component of a specific type too: +You can also have a single optional child component of a specific type too: ```rust use yew::{ - html, html_nested, virtual_dom::VChild, Component, - Context, Html, Properties + html, html_nested, virtual_dom::VChild, Component, + Context, Html, Properties, virtual_dom::VNode }; pub struct PageSideBar; @@ -225,14 +225,14 @@ impl Component for Page { fn view(&self, ctx: &Context) -> Html { html! {
    - { ctx.props().sidebar.clone().map(Html::from).unwrap_or_default() } + { Html::Ok(ctx.props().sidebar.clone().map(VNode::from).unwrap_or_default()) } // ... page content
    } } } -// The page component can be called either with the sidebar or without: +// The page component can be called either with the sidebar or without: pub fn render_page(with_sidebar: bool) -> Html { if with_sidebar { diff --git a/website/docs/concepts/html/elements.md b/website/docs/concepts/html/elements.md index ab0a71e0daf..977604d018b 100644 --- a/website/docs/concepts/html/elements.md +++ b/website/docs/concepts/html/elements.md @@ -8,12 +8,13 @@ description: "Both HTML and SVG elements are supported" There are many reasons why you might want to create or manage DOM nodes manually in Yew, such as when integrating with JS libraries that can cause conflicts with managed components. -Using `web-sys`, you can create DOM elements and convert them into a `Node` - which can then be +Using `web-sys`, you can create DOM elements and convert them into a `Node` - which can then be used as a `Html` value using `VRef`: ```rust use web_sys::{Element, Node}; use yew::{Component, Context, html, Html}; +use yew::virtual_dom::VNode; use gloo_utils::document; struct Comp; @@ -34,7 +35,7 @@ impl Component for Comp { // Convert Element into a Node let node: Node = div.into(); // Return that Node as a Html value - Html::VRef(node) + Ok(VNode::VRef(node)) } } ``` @@ -57,9 +58,9 @@ html! { }; ``` -## Boolean Attributes +## Boolean Attributes -Some content attributes (e.g checked, hidden, required) are called boolean attributes. In Yew, +Some content attributes (e.g checked, hidden, required) are called boolean attributes. In Yew, boolean attributes need to be set to a bool value: ```rust @@ -77,7 +78,7 @@ This will result in **HTML** that's functionally equivalent to this: ``` -Setting a boolean attribute to false is equivalent to not using the attribute at all; values from +Setting a boolean attribute to false is equivalent to not using the attribute at all; values from boolean expressions can be used: ```rust diff --git a/website/docs/concepts/html/lists.md b/website/docs/concepts/html/lists.md index c28003c7f1a..97da2d9ea7d 100644 --- a/website/docs/concepts/html/lists.md +++ b/website/docs/concepts/html/lists.md @@ -11,12 +11,13 @@ list that Yew can display. ```rust use yew::{html, Html}; +use yew::virtual_dom::VNode; let items = (1..=10).collect::>(); html! {
      - { items.iter().collect::() } + { items.iter().collect::() }
    }; ``` From 0f5f307f82a4a135c1ca47c84abf5ee72c29d03e Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sat, 27 Nov 2021 23:43:42 +0900 Subject: [PATCH 03/27] Implement Suspense. --- packages/yew/src/html/component/lifecycle.rs | 43 ++++++- packages/yew/src/html/component/scope.rs | 2 +- packages/yew/src/html/error.rs | 4 +- packages/yew/src/lib.rs | 1 + packages/yew/src/suspense/component.rs | 90 +++++++++++++ packages/yew/src/suspense/mod.rs | 7 ++ packages/yew/src/suspense/suspension.rs | 102 +++++++++++++++ packages/yew/src/virtual_dom/mod.rs | 4 + packages/yew/src/virtual_dom/vnode.rs | 22 +++- packages/yew/src/virtual_dom/vsuspense.rs | 125 +++++++++++++++++++ 10 files changed, 392 insertions(+), 8 deletions(-) create mode 100644 packages/yew/src/suspense/component.rs create mode 100644 packages/yew/src/suspense/mod.rs create mode 100644 packages/yew/src/suspense/suspension.rs create mode 100644 packages/yew/src/virtual_dom/vsuspense.rs diff --git a/packages/yew/src/html/component/lifecycle.rs b/packages/yew/src/html/component/lifecycle.rs index 2ca6a4fed01..9a64d660678 100644 --- a/packages/yew/src/html/component/lifecycle.rs +++ b/packages/yew/src/html/component/lifecycle.rs @@ -1,7 +1,9 @@ //! Component lifecycle module -use super::{Component, Scope}; +use super::{AnyScope, Component, Scope}; +use crate::html::RenderError; use crate::scheduler::{self, Runnable, Shared}; +use crate::suspense::{Suspense, Suspension}; use crate::virtual_dom::{VDiff, VNode}; use crate::{Context, NodeRef}; use std::rc::Rc; @@ -17,6 +19,8 @@ pub(crate) struct ComponentState { node_ref: NodeRef, has_rendered: bool, + suspension: Option, + // Used for debug logging #[cfg(debug_assertions)] pub(crate) vcomp_id: u64, @@ -47,6 +51,7 @@ impl ComponentState { parent, next_sibling, node_ref, + suspension: None, has_rendered: false, #[cfg(debug_assertions)] @@ -171,10 +176,38 @@ impl Runnable for RenderRunner { #[cfg(debug_assertions)] crate::virtual_dom::vcomp::log_event(state.vcomp_id, "render"); - // TODO: Error? - let mut new_root = state.component.view(&state.context).unwrap(); - std::mem::swap(&mut new_root, &mut state.root_node); - let ancestor = Some(new_root); + let old_root = match state.component.view(&state.context) { + Ok(m) => { + // Currently not suspended, we cancel any previous suspension and update + // normally. + let mut root = m; + std::mem::swap(&mut root, &mut state.root_node); + + if let Some(ref m) = state.suspension { + m.resume_by_ref(); + state.suspension = None; + } + + root + } + + Err(RenderError::Suspended(m)) => { + // Currently suspended, we re-use previous root node and send + // suspension to parent element. + let comp_scope = AnyScope::from(state.context.scope.clone()); + + let suspense_scope = comp_scope.find_parent_scope::().unwrap(); + let suspense = suspense_scope.get_component().unwrap(); + + state.suspension = Some(m.clone()); + + suspense.suspend(m); + + state.root_node.clone() + } + }; + + let ancestor = Some(old_root); let new_root = &mut state.root_node; let scope = state.context.scope.clone().into(); let next_sibling = state.next_sibling.clone(); diff --git a/packages/yew/src/html/component/scope.rs b/packages/yew/src/html/component/scope.rs index ef88d11ad1d..93afcf80449 100644 --- a/packages/yew/src/html/component/scope.rs +++ b/packages/yew/src/html/component/scope.rs @@ -93,7 +93,7 @@ impl AnyScope { } } - fn find_parent_scope(&self) -> Option> { + pub(crate) fn find_parent_scope(&self) -> Option> { let expected_type_id = TypeId::of::(); iter::successors(Some(self), |scope| scope.get_parent()) .filter(|scope| scope.get_type_id() == &expected_type_id) diff --git a/packages/yew/src/html/error.rs b/packages/yew/src/html/error.rs index b3ff9d27203..f96cac9003d 100644 --- a/packages/yew/src/html/error.rs +++ b/packages/yew/src/html/error.rs @@ -1,11 +1,13 @@ use thiserror::Error; +use crate::suspense::Suspension; + /// Render Error. #[derive(Error, Debug, Clone, PartialEq)] pub enum RenderError { /// Component Rendering Suspended #[error("component rendering is suspended.")] - Suspended, + Suspended(#[from] Suspension), } /// Render Result. diff --git a/packages/yew/src/lib.rs b/packages/yew/src/lib.rs index e91a1b7be0d..843f7c90bf7 100644 --- a/packages/yew/src/lib.rs +++ b/packages/yew/src/lib.rs @@ -261,6 +261,7 @@ pub mod context; pub mod functional; pub mod html; pub mod scheduler; +pub mod suspense; pub mod utils; pub mod virtual_dom; diff --git a/packages/yew/src/suspense/component.rs b/packages/yew/src/suspense/component.rs new file mode 100644 index 00000000000..30676f35595 --- /dev/null +++ b/packages/yew/src/suspense/component.rs @@ -0,0 +1,90 @@ +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; + +#[derive(Properties, PartialEq, Debug)] +pub struct SuspenseProps { + #[prop_or_default] + children: Children, + + #[prop_or_default] + fallback: Children, + + #[prop_or_default] + key: Option, +} + +#[derive(Debug)] +pub enum SuspenseMsg { + Suspend(Suspension), + Resume(Suspension), +} + +/// Suspend rendering and show a fallback UI until the underlying task completes. +#[derive(Debug)] +pub struct Suspense { + link: Scope, + suspensions: Vec, + detached_parent: Element, +} + +impl Component for Suspense { + type Properties = SuspenseProps; + type Message = SuspenseMsg; + + fn create(ctx: &Context) -> Self { + Self { + link: ctx.link().clone(), + suspensions: Vec::new(), + detached_parent: document().create_element("div").unwrap(), + } + } + + fn update(&mut self, _ctx: &Context, msg: Self::Message) -> bool { + match msg { + Self::Message::Suspend(m) => { + if m.resumed() { + return false; + } + + m.listen(self.link.callback(Self::Message::Resume)); + } + Self::Message::Resume(ref m) => { + self.suspensions.retain(|n| m != n); + } + } + + true + } + + fn view(&self, ctx: &Context) -> Html { + let children_vnode = VNode::from(VList::with_children( + ctx.props().children.clone().into_iter().collect(), + None, + )); + let fallback_vnode = VNode::from(VList::with_children( + ctx.props().fallback.clone().into_iter().collect(), + None, + )); + + let vsuspense = VSuspense::new( + children_vnode, + fallback_vnode, + self.detached_parent.clone(), + !self.suspensions.is_empty(), + ctx.props().key.clone(), + ); + + Ok(VNode::from(vsuspense)) + } +} + +impl Suspense { + pub(crate) fn suspend(&self, s: Suspension) { + self.link.send_message(SuspenseMsg::Suspend(s)); + } +} diff --git a/packages/yew/src/suspense/mod.rs b/packages/yew/src/suspense/mod.rs new file mode 100644 index 00000000000..174ec99f8b1 --- /dev/null +++ b/packages/yew/src/suspense/mod.rs @@ -0,0 +1,7 @@ +//! This module provides suspense support. + +mod component; +mod suspension; + +pub use component::Suspense; +pub use suspension::{Suspension, SuspensionHandle}; diff --git a/packages/yew/src/suspense/suspension.rs b/packages/yew/src/suspense/suspension.rs new file mode 100644 index 00000000000..f841cb176d1 --- /dev/null +++ b/packages/yew/src/suspense/suspension.rs @@ -0,0 +1,102 @@ +use std::cell::RefCell; +use std::rc::Rc; +use std::sync::atomic::{AtomicBool, Ordering}; + +use thiserror::Error; + +use crate::Callback; + +thread_local! { + static SUSPENSION_ID: RefCell = RefCell::default(); +} + +/// A Suspension. +/// +/// This type can be sent back as an `Err(_)` to suspend a component until the underlying task +/// completes. +#[derive(Error, Debug, Clone)] +#[error("suspend component rendering")] +pub struct Suspension { + id: usize, + listeners: Rc>>>, + + resumed: Rc, +} + +impl PartialEq for Suspension { + fn eq(&self, rhs: &Self) -> bool { + self.id == rhs.id + } +} + +impl Suspension { + /// Creates a Suspension. + pub fn new() -> (Self, SuspensionHandle) { + let id = SUSPENSION_ID.with(|m| { + let mut m = m.borrow_mut(); + *m += 1; + + *m + }); + + let self_ = Suspension { + id, + listeners: Rc::default(), + resumed: Rc::default(), + }; + + (self_.clone(), SuspensionHandle { inner: self_ }) + } + + pub(crate) fn resumed(&self) -> bool { + self.resumed.load(Ordering::Relaxed) + } + + pub(crate) fn listen(&self, cb: Callback) { + assert!( + !self.resumed.load(Ordering::Relaxed), + "You are attempting to add a callback after it's resumed." + ); + + let mut listeners = self.listeners.borrow_mut(); + + listeners.push(cb); + } + + pub(crate) fn resume_by_ref(&self) { + // The component can resume rendering by returning a non-suspended result after a state is + // updated, so we always need to check here. + if !self.resumed.load(Ordering::Relaxed) { + self.resumed.store(true, Ordering::Relaxed); + let listeners = self.listeners.borrow(); + + for listener in listeners.iter() { + listener.emit(self.clone()); + } + } + } +} + +/// A Suspension Handle. +/// +/// This type is used to control the corresponding [`Suspension`]. +/// +/// When the current struct is dropped or `resume` is called, it will resume rendering of current +/// component. +#[derive(Debug, PartialEq)] +pub struct SuspensionHandle { + inner: Suspension, +} + +impl SuspensionHandle { + /// Resumes component rendering. + pub fn resume(self) { + self.inner.resume_by_ref(); + } +} + +impl Drop for SuspensionHandle { + fn drop(&mut self) { + self.inner.resume_by_ref(); + } +} diff --git a/packages/yew/src/virtual_dom/mod.rs b/packages/yew/src/virtual_dom/mod.rs index e4ab84f044d..9871deb1d04 100644 --- a/packages/yew/src/virtual_dom/mod.rs +++ b/packages/yew/src/virtual_dom/mod.rs @@ -13,6 +13,8 @@ pub mod vnode; #[doc(hidden)] pub mod vportal; #[doc(hidden)] +pub mod vsuspense; +#[doc(hidden)] pub mod vtag; #[doc(hidden)] pub mod vtext; @@ -36,6 +38,8 @@ pub use self::vnode::VNode; #[doc(inline)] pub use self::vportal::VPortal; #[doc(inline)] +pub use self::vsuspense::VSuspense; +#[doc(inline)] pub use self::vtag::VTag; #[doc(inline)] pub use self::vtext::VText; diff --git a/packages/yew/src/virtual_dom/vnode.rs b/packages/yew/src/virtual_dom/vnode.rs index c1d19df7c0e..876d01bb034 100644 --- a/packages/yew/src/virtual_dom/vnode.rs +++ b/packages/yew/src/virtual_dom/vnode.rs @@ -1,6 +1,6 @@ //! This module contains the implementation of abstract virtual node. -use super::{Key, VChild, VComp, VDiff, VList, VPortal, VTag, VText}; +use super::{Key, VChild, VComp, VDiff, VList, VPortal, VSuspense, VTag, VText}; use crate::html::{AnyScope, Component, NodeRef}; use gloo::console; use std::cmp::PartialEq; @@ -25,6 +25,8 @@ pub enum VNode { VPortal(VPortal), /// A holder for any `Node` (necessary for replacing node). VRef(Node), + /// A suspendible document fragment. + VSuspense(VSuspense), } impl VNode { @@ -36,6 +38,7 @@ impl VNode { VNode::VTag(vtag) => vtag.key.clone(), VNode::VText(_) => None, VNode::VPortal(vportal) => vportal.node.key(), + VNode::VSuspense(vsuspense) => vsuspense.key.clone(), } } @@ -47,6 +50,7 @@ impl VNode { VNode::VRef(_) | VNode::VText(_) => false, VNode::VTag(vtag) => vtag.key.is_some(), VNode::VPortal(vportal) => vportal.node.has_key(), + VNode::VSuspense(vsuspense) => vsuspense.key.is_some(), } } @@ -63,6 +67,7 @@ impl VNode { VNode::VList(vlist) => vlist.get(0).and_then(VNode::first_node), VNode::VRef(node) => Some(node.clone()), VNode::VPortal(vportal) => vportal.next_sibling(), + VNode::VSuspense(vsuspense) => vsuspense.first_node(), } } @@ -94,6 +99,9 @@ impl VNode { .unchecked_first_node(), VNode::VRef(node) => node.clone(), VNode::VPortal(_) => panic!("portals have no first node, they are empty inside"), + VNode::VSuspense(vsuspense) => { + vsuspense.first_node().expect("VSuspense is not mounted") + } } } @@ -130,6 +138,7 @@ impl VDiff for VNode { } } VNode::VPortal(ref mut vportal) => vportal.detach(parent), + VNode::VSuspense(ref mut vsuspense) => vsuspense.detach(parent), } } @@ -166,6 +175,9 @@ impl VDiff for VNode { VNode::VPortal(ref mut vportal) => { vportal.apply(parent_scope, parent, next_sibling, ancestor) } + VNode::VSuspense(ref mut vsuspense) => { + vsuspense.apply(parent_scope, parent, next_sibling, ancestor) + } } } } @@ -204,6 +216,13 @@ impl From for VNode { } } +impl From for VNode { + #[inline] + fn from(vsuspense: VSuspense) -> Self { + VNode::VSuspense(vsuspense) + } +} + impl From> for VNode where COMP: Component, @@ -237,6 +256,7 @@ impl fmt::Debug for VNode { VNode::VList(ref vlist) => vlist.fmt(f), VNode::VRef(ref vref) => write!(f, "VRef ( \"{}\" )", crate::utils::print_node(vref)), VNode::VPortal(ref vportal) => vportal.fmt(f), + VNode::VSuspense(ref vsuspense) => vsuspense.fmt(f), } } } diff --git a/packages/yew/src/virtual_dom/vsuspense.rs b/packages/yew/src/virtual_dom/vsuspense.rs new file mode 100644 index 00000000000..ff476ecd0dc --- /dev/null +++ b/packages/yew/src/virtual_dom/vsuspense.rs @@ -0,0 +1,125 @@ +use super::{Key, VDiff, VNode}; +use crate::html::{AnyScope, NodeRef}; +use web_sys::{Element, Node}; + +/// This struct represents a suspendable DOM fragment. +#[derive(Clone, Debug, PartialEq)] +pub struct VSuspense { + /// Child nodes. + children: Box, + + /// Fallback nodes when suspended. + fallback: Box, + + /// The element to attach to when children is not attached to DOM + detached_parent: Element, + + /// Whether the current status is suspended. + suspended: bool, + + /// The Key. + pub(crate) key: Option, +} + +impl VSuspense { + pub(crate) fn new( + children: VNode, + fallback: VNode, + detached_parent: Element, + suspended: bool, + key: Option, + ) -> Self { + Self { + children: children.into(), + fallback: fallback.into(), + detached_parent, + suspended, + key, + } + } + + pub(crate) fn first_node(&self) -> Option { + if self.suspended { + self.fallback.first_node() + } else { + self.children.first_node() + } + } +} + +impl VDiff for VSuspense { + fn detach(&mut self, parent: &Element) { + if self.suspended { + self.fallback.detach(parent); + self.children.detach(&self.detached_parent); + } else { + self.children.detach(parent); + } + } + + fn apply( + &mut self, + parent_scope: &AnyScope, + parent: &Element, + next_sibling: NodeRef, + ancestor: Option, + ) -> NodeRef { + let (already_suspended, children_ancestor, fallback_ancestor) = match ancestor { + Some(VNode::VSuspense(mut m)) => { + if m.key != self.key { + m.detach(parent); + } else if self.detached_parent != m.detached_parent { + // we delete everything from the outdated parent. + m.detached_parent.set_inner_html(""); + } + + ( + m.suspended, + Some((*m.children).clone()), + Some((*m.fallback).clone()), + ) + } + Some(mut m) => { + m.detach(parent); + (false, None, None) + } + None => (false, None, None), + }; + + match (self.suspended, already_suspended) { + (true, true) => { + self.children.apply( + parent_scope, + &self.detached_parent, + NodeRef::default(), + children_ancestor, + ); + + self.fallback + .apply(parent_scope, parent, next_sibling, fallback_ancestor) + } + + (false, false) => { + self.children + .apply(parent_scope, parent, next_sibling, children_ancestor) + } + + (true, false) => { + self.children.apply( + parent_scope, + &self.detached_parent, + NodeRef::default(), + children_ancestor, + ); + self.fallback + .apply(parent_scope, parent, next_sibling, fallback_ancestor) + } + + (false, true) => { + self.fallback.detach(parent); + self.children + .apply(parent_scope, parent, next_sibling, children_ancestor) + } + } + } +} From 7f196835e108e16992b4ea1afe467dd11c6ddd1c Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sun, 28 Nov 2021 00:55:38 +0900 Subject: [PATCH 04/27] Schedule render when suspension is resumed. --- packages/yew/src/html/component/lifecycle.rs | 46 ++++++++++++++++---- packages/yew/src/virtual_dom/vsuspense.rs | 2 + 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/packages/yew/src/html/component/lifecycle.rs b/packages/yew/src/html/component/lifecycle.rs index 9a64d660678..cd843508627 100644 --- a/packages/yew/src/html/component/lifecycle.rs +++ b/packages/yew/src/html/component/lifecycle.rs @@ -5,6 +5,7 @@ use crate::html::RenderError; use crate::scheduler::{self, Runnable, Shared}; use crate::suspense::{Suspense, Suspension}; use crate::virtual_dom::{VDiff, VNode}; +use crate::Callback; use crate::{Context, NodeRef}; use std::rc::Rc; use web_sys::Element; @@ -194,14 +195,43 @@ impl Runnable for RenderRunner { Err(RenderError::Suspended(m)) => { // Currently suspended, we re-use previous root node and send // suspension to parent element. - let comp_scope = AnyScope::from(state.context.scope.clone()); - - let suspense_scope = comp_scope.find_parent_scope::().unwrap(); - let suspense = suspense_scope.get_component().unwrap(); - - state.suspension = Some(m.clone()); - - suspense.suspend(m); + let shared_state = self.state.clone(); + + if m.resumed() { + // schedule a render immediately if suspension is resumed. + + scheduler::push_component_render( + shared_state.as_ptr() as usize, + RenderRunner { + state: shared_state.clone(), + }, + RenderedRunner { + state: shared_state, + }, + ); + } else { + // We schedule a render after current suspension is resumed. + + let comp_scope = AnyScope::from(state.context.scope.clone()); + + let suspense_scope = comp_scope.find_parent_scope::().unwrap(); + let suspense = suspense_scope.get_component().unwrap(); + m.listen(Callback::once(move |_| { + scheduler::push_component_render( + shared_state.as_ptr() as usize, + RenderRunner { + state: shared_state.clone(), + }, + RenderedRunner { + state: shared_state, + }, + ); + })); + + state.suspension = Some(m.clone()); + + suspense.suspend(m); + } state.root_node.clone() } diff --git a/packages/yew/src/virtual_dom/vsuspense.rs b/packages/yew/src/virtual_dom/vsuspense.rs index ff476ecd0dc..c0c3bb1635d 100644 --- a/packages/yew/src/virtual_dom/vsuspense.rs +++ b/packages/yew/src/virtual_dom/vsuspense.rs @@ -86,6 +86,8 @@ impl VDiff for VSuspense { None => (false, None, None), }; + // When it's suspended, we render children into an element that is detached from the dom + // tree while rendering fallback UI into the original place where children resides in. match (self.suspended, already_suspended) { (true, true) => { self.children.apply( From 31332c67bf42d94ecde37a007bce7c899e13e3e4 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sun, 28 Nov 2021 17:57:34 +0900 Subject: [PATCH 05/27] Shift children into a detached node. --- Cargo.toml | 1 + examples/suspense/Cargo.toml | 21 ++++++++ examples/suspense/README.md | 10 ++++ examples/suspense/index.html | 11 ++++ examples/suspense/index.scss | 0 examples/suspense/src/main.rs | 55 ++++++++++++++++++++ examples/suspense/src/use_sleep.rs | 43 +++++++++++++++ packages/yew/Cargo.toml | 1 + packages/yew/src/html/component/lifecycle.rs | 48 ++++++++++++----- packages/yew/src/html/component/scope.rs | 8 +++ packages/yew/src/lib.rs | 1 + packages/yew/src/suspense/component.rs | 19 +++++-- packages/yew/src/suspense/mod.rs | 2 +- packages/yew/src/suspense/suspension.rs | 8 ++- packages/yew/src/virtual_dom/mod.rs | 6 +++ packages/yew/src/virtual_dom/vcomp.rs | 5 ++ packages/yew/src/virtual_dom/vlist.rs | 10 ++++ packages/yew/src/virtual_dom/vnode.rs | 21 ++++++++ packages/yew/src/virtual_dom/vportal.rs | 4 ++ packages/yew/src/virtual_dom/vsuspense.rs | 44 +++++++++++----- packages/yew/src/virtual_dom/vtag.rs | 12 +++++ packages/yew/src/virtual_dom/vtext.rs | 12 +++++ 22 files changed, 308 insertions(+), 34 deletions(-) create mode 100644 examples/suspense/Cargo.toml create mode 100644 examples/suspense/README.md create mode 100644 examples/suspense/index.html create mode 100644 examples/suspense/index.scss create mode 100644 examples/suspense/src/main.rs create mode 100644 examples/suspense/src/use_sleep.rs diff --git a/Cargo.toml b/Cargo.toml index 6b6177b5b9c..7d9485c079f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ members = [ "examples/two_apps", "examples/webgl", "examples/web_worker_fib", + "examples/suspense", # Release tools "packages/changelog", diff --git a/examples/suspense/Cargo.toml b/examples/suspense/Cargo.toml new file mode 100644 index 00000000000..e6ce038fdda --- /dev/null +++ b/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"] } + +[dependencies.web-sys] +version = "0.3" +features = [ + "HtmlTextAreaElement", +] diff --git a/examples/suspense/README.md b/examples/suspense/README.md new file mode 100644 index 00000000000..137b848a254 --- /dev/null +++ b/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 `` support. + +## Concepts + +This example shows that how `` works in Yew and how you can +create hooks that utilises suspense. diff --git a/examples/suspense/index.html b/examples/suspense/index.html new file mode 100644 index 00000000000..ec8ff43c363 --- /dev/null +++ b/examples/suspense/index.html @@ -0,0 +1,11 @@ + + + + + Yew Suspense Demo + + + + + + diff --git a/examples/suspense/index.scss b/examples/suspense/index.scss new file mode 100644 index 00000000000..e69de29bb2d diff --git a/examples/suspense/src/main.rs b/examples/suspense/src/main.rs new file mode 100644 index 00000000000..77f555a6542 --- /dev/null +++ b/examples/suspense/src/main.rs @@ -0,0 +1,55 @@ +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! {
    {"Please wait..."}
    } +} + +#[function_component(AppContent)] +fn app_content() -> Html { + let resleep = use_sleep()?; + + let value = use_state(|| "".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! { + <> + + + + } +} + +#[function_component(App)] +fn app() -> Html { + let fallback = html! {}; + + html! { + + + + } +} + +fn main() { + console_log::init_with_level(Level::Trace).expect("Failed to initialise Log!"); + yew::start_app::(); +} diff --git a/examples/suspense/src/use_sleep.rs b/examples/suspense/src/use_sleep.rs new file mode 100644 index 00000000000..5ef99755f42 --- /dev/null +++ b/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; + + handle.resume(); + }); + + Self { s } + } +} + +impl Reducible for SleepState { + type Action = (); + + fn reduce(self: Rc, _action: Self::Action) -> Rc { + Self::new().into() + } +} + +pub fn use_sleep() -> SuspensionResult> { + 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()) + } +} diff --git a/packages/yew/Cargo.toml b/packages/yew/Cargo.toml index d9de79f8f78..2ace6221ae9 100644 --- a/packages/yew/Cargo.toml +++ b/packages/yew/Cargo.toml @@ -26,6 +26,7 @@ wasm-bindgen = "0.2" wasm-bindgen-futures = "0.4" yew-macro = { version = "^0.18.0", path = "../yew-macro" } thiserror = "1.0" +log = "0.4" scoped-tls-hkt = "0.1" diff --git a/packages/yew/src/html/component/lifecycle.rs b/packages/yew/src/html/component/lifecycle.rs index cd843508627..4ae86a5bb90 100644 --- a/packages/yew/src/html/component/lifecycle.rs +++ b/packages/yew/src/html/component/lifecycle.rs @@ -96,6 +96,8 @@ pub(crate) enum UpdateEvent { MessageBatch(Vec), /// Wraps properties, node ref, and next sibling for a component. Properties(Rc, NodeRef, NodeRef), + /// Shift Scope. + Shift(Element, NodeRef), } pub(crate) struct UpdateRunner { @@ -126,6 +128,16 @@ impl Runnable for UpdateRunner { false } } + UpdateEvent::Shift(parent, next_sibling) => { + state + .root_node + .shift(&state.parent, &parent, next_sibling.clone()); + + state.parent = parent; + state.next_sibling = next_sibling; + + false + } }; #[cfg(debug_assertions)] @@ -177,19 +189,29 @@ impl Runnable for RenderRunner { #[cfg(debug_assertions)] crate::virtual_dom::vcomp::log_event(state.vcomp_id, "render"); - let old_root = match state.component.view(&state.context) { + match state.component.view(&state.context) { Ok(m) => { - // Currently not suspended, we cancel any previous suspension and update + // Currently not suspended, we remove any previous suspension and update // normally. let mut root = m; std::mem::swap(&mut root, &mut state.root_node); if let Some(ref m) = state.suspension { - m.resume_by_ref(); - state.suspension = None; + let comp_scope = AnyScope::from(state.context.scope.clone()); + + let suspense_scope = comp_scope.find_parent_scope::().unwrap(); + let suspense = suspense_scope.get_component().unwrap(); + + suspense.resume(m.clone()); } - root + let ancestor = Some(root); + let new_root = &mut state.root_node; + let scope = state.context.scope.clone().into(); + let next_sibling = state.next_sibling.clone(); + + let node = new_root.apply(&scope, &state.parent, next_sibling, ancestor); + state.node_ref.link(node); } Err(RenderError::Suspended(m)) => { @@ -216,6 +238,7 @@ impl Runnable for RenderRunner { let suspense_scope = comp_scope.find_parent_scope::().unwrap(); let suspense = suspense_scope.get_component().unwrap(); + m.listen(Callback::once(move |_| { scheduler::push_component_render( shared_state.as_ptr() as usize, @@ -228,21 +251,18 @@ impl Runnable for RenderRunner { ); })); + if let Some(ref last_m) = state.suspension { + if &m != last_m { + // We remove previous suspension from the suspense. + suspense.resume(last_m.clone()); + } + } state.suspension = Some(m.clone()); suspense.suspend(m); } - - state.root_node.clone() } }; - - let ancestor = Some(old_root); - let new_root = &mut state.root_node; - let scope = state.context.scope.clone().into(); - let next_sibling = state.next_sibling.clone(); - let node = new_root.apply(&scope, &state.parent, next_sibling, ancestor); - state.node_ref.link(node); } } } diff --git a/packages/yew/src/html/component/scope.rs b/packages/yew/src/html/component/scope.rs index 93afcf80449..6d5aaeed50d 100644 --- a/packages/yew/src/html/component/scope.rs +++ b/packages/yew/src/html/component/scope.rs @@ -119,6 +119,7 @@ pub(crate) trait Scoped { fn to_any(&self) -> AnyScope; fn root_vnode(&self) -> Option>; fn destroy(&mut self); + fn shift_node(&self, parent: Element, next_sibling: NodeRef); } impl Scoped for Scope { @@ -145,6 +146,13 @@ impl Scoped for Scope { // Not guaranteed to already have the scheduler started scheduler::start(); } + + fn shift_node(&self, parent: Element, next_sibling: NodeRef) { + scheduler::push_component_update(UpdateRunner { + state: self.state.clone(), + event: UpdateEvent::Shift(parent, next_sibling), + }); + } } /// A context which allows sending messages to a component. diff --git a/packages/yew/src/lib.rs b/packages/yew/src/lib.rs index 843f7c90bf7..1ce2fb11eda 100644 --- a/packages/yew/src/lib.rs +++ b/packages/yew/src/lib.rs @@ -391,6 +391,7 @@ pub mod prelude { NodeRef, Properties, }; pub use crate::macros::{classes, html, html_nested}; + pub use crate::suspense::Suspense; pub use crate::functional::*; } diff --git a/packages/yew/src/suspense/component.rs b/packages/yew/src/suspense/component.rs index 30676f35595..7fbeda4ec82 100644 --- a/packages/yew/src/suspense/component.rs +++ b/packages/yew/src/suspense/component.rs @@ -9,13 +9,13 @@ use super::Suspension; #[derive(Properties, PartialEq, Debug)] pub struct SuspenseProps { #[prop_or_default] - children: Children, + pub children: Children, #[prop_or_default] - fallback: Children, + pub fallback: Children, #[prop_or_default] - key: Option, + pub key: Option, } #[derive(Debug)] @@ -52,13 +52,18 @@ impl Component for Suspense { } m.listen(self.link.callback(Self::Message::Resume)); + + self.suspensions.push(m); + + true } Self::Message::Resume(ref m) => { + let suspensions_len = self.suspensions.len(); self.suspensions.retain(|n| m != n); + + suspensions_len != self.suspensions.len() } } - - true } fn view(&self, ctx: &Context) -> Html { @@ -87,4 +92,8 @@ impl Suspense { pub(crate) fn suspend(&self, s: Suspension) { self.link.send_message(SuspenseMsg::Suspend(s)); } + + pub(crate) fn resume(&self, s: Suspension) { + self.link.send_message(SuspenseMsg::Resume(s)); + } } diff --git a/packages/yew/src/suspense/mod.rs b/packages/yew/src/suspense/mod.rs index 174ec99f8b1..617c263775f 100644 --- a/packages/yew/src/suspense/mod.rs +++ b/packages/yew/src/suspense/mod.rs @@ -4,4 +4,4 @@ mod component; mod suspension; pub use component::Suspense; -pub use suspension::{Suspension, SuspensionHandle}; +pub use suspension::{Suspension, SuspensionHandle, SuspensionResult}; diff --git a/packages/yew/src/suspense/suspension.rs b/packages/yew/src/suspense/suspension.rs index f841cb176d1..f0d58f24a9e 100644 --- a/packages/yew/src/suspense/suspension.rs +++ b/packages/yew/src/suspense/suspension.rs @@ -48,7 +48,8 @@ impl Suspension { (self_.clone(), SuspensionHandle { inner: self_ }) } - pub(crate) fn resumed(&self) -> bool { + /// Returns `true` if the current suspension is already resumed. + pub fn resumed(&self) -> bool { self.resumed.load(Ordering::Relaxed) } @@ -63,7 +64,7 @@ impl Suspension { listeners.push(cb); } - pub(crate) fn resume_by_ref(&self) { + fn resume_by_ref(&self) { // The component can resume rendering by returning a non-suspended result after a state is // updated, so we always need to check here. if !self.resumed.load(Ordering::Relaxed) { @@ -77,6 +78,9 @@ impl Suspension { } } +/// A Suspension Result. +pub type SuspensionResult = std::result::Result; + /// A Suspension Handle. /// /// This type is used to control the corresponding [`Suspension`]. diff --git a/packages/yew/src/virtual_dom/mod.rs b/packages/yew/src/virtual_dom/mod.rs index 9871deb1d04..6ada48557dc 100644 --- a/packages/yew/src/virtual_dom/mod.rs +++ b/packages/yew/src/virtual_dom/mod.rs @@ -499,6 +499,12 @@ pub(crate) trait VDiff { /// Remove self from parent. fn detach(&mut self, parent: &Element); + /// Move elements from one parent to another parent. + /// This is currently only used by `VSuspense` to preserve component state without detaching + /// (which destroys component state). + /// Prefer `detach` then apply if possible. + fn shift(&self, previous_parent: &Element, next_parent: &Element, next_sibling: NodeRef); + /// Scoped diff apply to other tree. /// /// Virtual rendering for the node. It uses parent node and existing diff --git a/packages/yew/src/virtual_dom/vcomp.rs b/packages/yew/src/virtual_dom/vcomp.rs index 06237292fa0..a51305a2f29 100644 --- a/packages/yew/src/virtual_dom/vcomp.rs +++ b/packages/yew/src/virtual_dom/vcomp.rs @@ -225,6 +225,11 @@ impl VDiff for VComp { self.take_scope().destroy(); } + fn shift(&self, _previous_parent: &Element, next_parent: &Element, next_sibling: NodeRef) { + let scope = self.scope.as_ref().unwrap(); + scope.shift_node(next_parent.clone(), next_sibling); + } + fn apply( &mut self, parent_scope: &AnyScope, diff --git a/packages/yew/src/virtual_dom/vlist.rs b/packages/yew/src/virtual_dom/vlist.rs index e02e9443902..585e4c98f43 100644 --- a/packages/yew/src/virtual_dom/vlist.rs +++ b/packages/yew/src/virtual_dom/vlist.rs @@ -313,6 +313,16 @@ impl VDiff for VList { } } + fn shift(&self, previous_parent: &Element, next_parent: &Element, next_sibling: NodeRef) { + let mut last_node_ref = next_sibling; + + for node in self.children.iter().rev() { + node.shift(previous_parent, next_parent, last_node_ref); + last_node_ref = NodeRef::default(); + last_node_ref.set(node.first_node()); + } + } + fn apply( &mut self, parent_scope: &AnyScope, diff --git a/packages/yew/src/virtual_dom/vnode.rs b/packages/yew/src/virtual_dom/vnode.rs index 876d01bb034..6bd7041d536 100644 --- a/packages/yew/src/virtual_dom/vnode.rs +++ b/packages/yew/src/virtual_dom/vnode.rs @@ -142,6 +142,27 @@ impl VDiff for VNode { } } + fn shift(&self, previous_parent: &Element, next_parent: &Element, next_sibling: NodeRef) { + match *self { + VNode::VTag(ref vtag) => vtag.shift(previous_parent, next_parent, next_sibling), + VNode::VText(ref vtext) => vtext.shift(previous_parent, next_parent, next_sibling), + VNode::VComp(ref vcomp) => vcomp.shift(previous_parent, next_parent, next_sibling), + VNode::VList(ref vlist) => vlist.shift(previous_parent, next_parent, next_sibling), + VNode::VRef(ref node) => { + previous_parent.remove_child(node).unwrap(); + next_parent + .insert_before(node, next_sibling.get().as_ref()) + .unwrap(); + } + VNode::VPortal(ref vportal) => { + vportal.shift(previous_parent, next_parent, next_sibling) + } + VNode::VSuspense(ref vsuspense) => { + vsuspense.shift(previous_parent, next_parent, next_sibling) + } + } + } + fn apply( &mut self, parent_scope: &AnyScope, diff --git a/packages/yew/src/virtual_dom/vportal.rs b/packages/yew/src/virtual_dom/vportal.rs index ba3428e32ab..f99a3b240ed 100644 --- a/packages/yew/src/virtual_dom/vportal.rs +++ b/packages/yew/src/virtual_dom/vportal.rs @@ -22,6 +22,10 @@ impl VDiff for VPortal { self.sibling_ref.set(None); } + fn shift(&self, _previous_parent: &Element, _next_parent: &Element, _next_sibling: NodeRef) { + // portals have nothing in it's original place of DOM, we also do nothing. + } + fn apply( &mut self, parent_scope: &AnyScope, diff --git a/packages/yew/src/virtual_dom/vsuspense.rs b/packages/yew/src/virtual_dom/vsuspense.rs index c0c3bb1635d..5cf6d177510 100644 --- a/packages/yew/src/virtual_dom/vsuspense.rs +++ b/packages/yew/src/virtual_dom/vsuspense.rs @@ -57,6 +57,16 @@ impl VDiff for VSuspense { } } + fn shift(&self, previous_parent: &Element, next_parent: &Element, next_sibling: NodeRef) { + if self.suspended { + self.fallback + .shift(previous_parent, next_parent, next_sibling); + } else { + self.children + .shift(previous_parent, next_parent, next_sibling); + } + } + fn apply( &mut self, parent_scope: &AnyScope, @@ -66,18 +76,14 @@ impl VDiff for VSuspense { ) -> NodeRef { let (already_suspended, children_ancestor, fallback_ancestor) = match ancestor { Some(VNode::VSuspense(mut m)) => { - if m.key != self.key { + // We only preserve the child state if they are the same suspense. + if m.key != self.key || self.detached_parent != m.detached_parent { m.detach(parent); - } else if self.detached_parent != m.detached_parent { - // we delete everything from the outdated parent. - m.detached_parent.set_inner_html(""); - } - ( - m.suspended, - Some((*m.children).clone()), - Some((*m.fallback).clone()), - ) + (false, None, None) + } else { + (m.suspended, Some(*m.children), Some(*m.fallback)) + } } Some(mut m) => { m.detach(parent); @@ -107,18 +113,32 @@ impl VDiff for VSuspense { } (true, false) => { + children_ancestor.as_ref().unwrap().shift( + parent, + &self.detached_parent, + NodeRef::default(), + ); + self.children.apply( parent_scope, &self.detached_parent, NodeRef::default(), children_ancestor, ); + + // first render of fallback, ancestor needs to be None. self.fallback - .apply(parent_scope, parent, next_sibling, fallback_ancestor) + .apply(parent_scope, parent, next_sibling, None) } (false, true) => { - self.fallback.detach(parent); + fallback_ancestor.unwrap().detach(parent); + + children_ancestor.as_ref().unwrap().shift( + &self.detached_parent, + parent, + next_sibling.clone(), + ); self.children .apply(parent_scope, parent, next_sibling, children_ancestor) } diff --git a/packages/yew/src/virtual_dom/vtag.rs b/packages/yew/src/virtual_dom/vtag.rs index 40e5900fc4e..6094b83a200 100644 --- a/packages/yew/src/virtual_dom/vtag.rs +++ b/packages/yew/src/virtual_dom/vtag.rs @@ -479,6 +479,18 @@ impl VDiff for VTag { self.node_ref.set(None); } + fn shift(&self, previous_parent: &Element, next_parent: &Element, next_sibling: NodeRef) { + let node = self + .reference + .as_ref() + .expect("tried to shift not rendered VTag from DOM"); + + previous_parent.remove_child(node).unwrap(); + next_parent + .insert_before(node, next_sibling.get().as_ref()) + .unwrap(); + } + /// Renders virtual tag over DOM [Element], but it also compares this with an ancestor [VTag] /// to compute what to patch in the actual DOM nodes. fn apply( diff --git a/packages/yew/src/virtual_dom/vtext.rs b/packages/yew/src/virtual_dom/vtext.rs index cdfee1f80d5..c9ba9768a06 100644 --- a/packages/yew/src/virtual_dom/vtext.rs +++ b/packages/yew/src/virtual_dom/vtext.rs @@ -54,6 +54,18 @@ impl VDiff for VText { } } + fn shift(&self, previous_parent: &Element, next_parent: &Element, next_sibling: NodeRef) { + let node = self + .reference + .as_ref() + .expect("tried to shift not rendered VTag from DOM"); + + previous_parent.remove_child(node).unwrap(); + next_parent + .insert_before(node, next_sibling.get().as_ref()) + .unwrap(); + } + /// Renders virtual node over existing `TextNode`, but only if value of text has changed. fn apply( &mut self, From 9bef4de32a95acacd85bdb50a394aade4437f1ed Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sun, 28 Nov 2021 18:36:52 +0900 Subject: [PATCH 06/27] styled example. --- examples/suspense/index.scss | 62 +++++++++++++++++++++++++++++++++++ examples/suspense/src/main.rs | 25 +++++++++----- packages/yew/Cargo.toml | 1 - 3 files changed, 79 insertions(+), 9 deletions(-) diff --git a/examples/suspense/index.scss b/examples/suspense/index.scss index e69de29bb2d..127178778be 100644 --- a/examples/suspense/index.scss +++ b/examples/suspense/index.scss @@ -0,0 +1,62 @@ +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: 400px; + + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +textarea { + width: 300px; + height: 300px; + font-size: 15px; +} + +.action-area { + padding-top: 20px; + padding-bottom: 20px; +} + +button { + color: white; + height: 50px; + width: 300px; + font-size: 20px; + background-color: rgb(88, 164, 255); + border-radius: 5px; + border: none; +} diff --git a/examples/suspense/src/main.rs b/examples/suspense/src/main.rs index 77f555a6542..3bdb829333e 100644 --- a/examples/suspense/src/main.rs +++ b/examples/suspense/src/main.rs @@ -9,14 +9,16 @@ use use_sleep::use_sleep; #[function_component(PleaseWait)] fn please_wait() -> Html { - html! {
    {"Please wait..."}
    } + html! {
    {"Please wait 5 Seconds..."}
    } } #[function_component(AppContent)] fn app_content() -> Html { let resleep = use_sleep()?; - let value = use_state(|| "".to_string()); + let value = use_state(|| { + "I am writing a long story...\n\nYou can take a break at anytime!".to_string() + }); let on_text_input = { let value = value.clone(); @@ -31,10 +33,12 @@ fn app_content() -> Html { let on_take_a_break = Callback::from(move |_| (resleep.clone())()); html! { - <> +
    - - +
    + +
    +
    } } @@ -43,9 +47,14 @@ fn app() -> Html { let fallback = html! {}; html! { - - - +
    +
    +

    {"Yew Suspense Demo"}

    + + + +
    +
    } } diff --git a/packages/yew/Cargo.toml b/packages/yew/Cargo.toml index 2ace6221ae9..d9de79f8f78 100644 --- a/packages/yew/Cargo.toml +++ b/packages/yew/Cargo.toml @@ -26,7 +26,6 @@ wasm-bindgen = "0.2" wasm-bindgen-futures = "0.4" yew-macro = { version = "^0.18.0", path = "../yew-macro" } thiserror = "1.0" -log = "0.4" scoped-tls-hkt = "0.1" From aa766b765cefd02aab55e9f9aacb8fcc4422ff45 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sun, 28 Nov 2021 18:38:34 +0900 Subject: [PATCH 07/27] Update wording a little bit. --- examples/suspense/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/suspense/src/main.rs b/examples/suspense/src/main.rs index 3bdb829333e..46649e200a4 100644 --- a/examples/suspense/src/main.rs +++ b/examples/suspense/src/main.rs @@ -17,7 +17,7 @@ fn app_content() -> Html { let resleep = use_sleep()?; let value = use_state(|| { - "I am writing a long story...\n\nYou can take a break at anytime!".to_string() + "I am writing a long story...\n\nYou can take a break at anytime \nand your work will be preserved.".to_string() }); let on_text_input = { From 1fc0db9ee3b4791e9d085778b16e17fbcf46c176 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sun, 28 Nov 2021 18:45:26 +0900 Subject: [PATCH 08/27] Move hint to hint. --- examples/suspense/index.scss | 15 ++++++++++++--- examples/suspense/src/main.rs | 5 ++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/examples/suspense/index.scss b/examples/suspense/index.scss index 127178778be..5e3a5385e6a 100644 --- a/examples/suspense/index.scss +++ b/examples/suspense/index.scss @@ -32,7 +32,7 @@ html, body { .content-area { width: 350px; - height: 400px; + height: 500px; display: flex; flex-direction: column; @@ -47,8 +47,7 @@ textarea { } .action-area { - padding-top: 20px; - padding-bottom: 20px; + padding-top: 40px; } button { @@ -60,3 +59,13 @@ button { border-radius: 5px; border: none; } + +.hint { + padding-top: 20px; + + font-size: 12px; + + text-align: center; + + color: rgb(100, 100, 100); +} diff --git a/examples/suspense/src/main.rs b/examples/suspense/src/main.rs index 46649e200a4..34a9bdc3312 100644 --- a/examples/suspense/src/main.rs +++ b/examples/suspense/src/main.rs @@ -16,9 +16,7 @@ fn please_wait() -> Html { fn app_content() -> Html { let resleep = use_sleep()?; - let value = use_state(|| { - "I am writing a long story...\n\nYou can take a break at anytime \nand your work will be preserved.".to_string() - }); + let value = use_state(|| "I am writing a long story...".to_string()); let on_text_input = { let value = value.clone(); @@ -37,6 +35,7 @@ fn app_content() -> Html {
    +
    {"You can take a break at anytime"}
    {"and your work will be preserved."}
    } From 376413ad1ab585d5821779cdc5a676ee4625a4ff Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Tue, 30 Nov 2021 21:59:13 +0900 Subject: [PATCH 09/27] Add some tests. --- packages/yew/Cargo.toml | 1 + packages/yew/tests/suspense.rs | 401 +++++++++++++++++++++++++++++++++ 2 files changed, 402 insertions(+) create mode 100644 packages/yew/tests/suspense.rs diff --git a/packages/yew/Cargo.toml b/packages/yew/Cargo.toml index d9de79f8f78..56545133dfb 100644 --- a/packages/yew/Cargo.toml +++ b/packages/yew/Cargo.toml @@ -63,6 +63,7 @@ features = [ [dev-dependencies] easybench-wasm = "0.2" wasm-bindgen-test = "0.3" +gloo = { version = "0.4", features = ["futures"] } [features] doc_test = [] diff --git a/packages/yew/tests/suspense.rs b/packages/yew/tests/suspense.rs new file mode 100644 index 00000000000..ae5ddcd97e1 --- /dev/null +++ b/packages/yew/tests/suspense.rs @@ -0,0 +1,401 @@ +mod common; + +use common::obtain_result; +use wasm_bindgen_test::*; +use yew::prelude::*; + +wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); + +use std::rc::Rc; + +use gloo::timers::future::TimeoutFuture; +use wasm_bindgen::JsCast; +use wasm_bindgen_futures::spawn_local; +use web_sys::{HtmlElement, HtmlTextAreaElement}; +use yew::suspense::{Suspension, SuspensionResult}; + +#[wasm_bindgen_test] +async fn suspense_works() { + #[derive(PartialEq)] + pub struct SleepState { + s: Suspension, + } + + impl SleepState { + fn new() -> Self { + let (s, handle) = Suspension::new(); + + spawn_local(async move { + TimeoutFuture::new(50).await; + + handle.resume(); + }); + + Self { s } + } + } + + impl Reducible for SleepState { + type Action = (); + + fn reduce(self: Rc, _action: Self::Action) -> Rc { + Self::new().into() + } + } + + pub fn use_sleep() -> SuspensionResult> { + 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()) + } + } + + #[function_component(Content)] + fn content() -> Html { + let resleep = use_sleep()?; + + let value = use_state(|| 0); + + let on_increment = { + let value = value.clone(); + + Callback::from(move |_: MouseEvent| { + value.set(*value + 1); + }) + }; + + let on_take_a_break = Callback::from(move |_: MouseEvent| (resleep.clone())()); + + html! { +
    +
    {*value}
    + +
    + +
    +
    + } + } + + #[function_component(App)] + fn app() -> Html { + let fallback = html! {
    {"wait..."}
    }; + + html! { +
    + + + +
    + } + } + + yew::start_app_in_element::(gloo_utils::document().get_element_by_id("output").unwrap()); + + TimeoutFuture::new(10).await; + let result = obtain_result(); + assert_eq!(result.as_str(), "
    wait...
    "); + + TimeoutFuture::new(50).await; + + let result = obtain_result(); + assert_eq!( + result.as_str(), + r#"
    0
    "# + ); + + TimeoutFuture::new(10).await; + + gloo_utils::document() + .query_selector(".increase") + .unwrap() + .unwrap() + .dyn_into::() + .unwrap() + .click(); + + gloo_utils::document() + .query_selector(".increase") + .unwrap() + .unwrap() + .dyn_into::() + .unwrap() + .click(); + + let result = obtain_result(); + assert_eq!( + result.as_str(), + r#"
    2
    "# + ); + + gloo_utils::document() + .query_selector(".take-a-break") + .unwrap() + .unwrap() + .dyn_into::() + .unwrap() + .click(); + + TimeoutFuture::new(10).await; + let result = obtain_result(); + assert_eq!(result.as_str(), "
    wait...
    "); + + TimeoutFuture::new(50).await; + + let result = obtain_result(); + assert_eq!( + result.as_str(), + r#"
    2
    "# + ); +} + +#[wasm_bindgen_test] +async fn suspense_not_suspended_at_start() { + #[derive(PartialEq)] + pub struct SleepState { + s: Option, + } + + impl SleepState { + fn new() -> Self { + Self { s: None } + } + } + + impl Reducible for SleepState { + type Action = (); + + fn reduce(self: Rc, _action: Self::Action) -> Rc { + let (s, handle) = Suspension::new(); + + spawn_local(async move { + TimeoutFuture::new(50).await; + + handle.resume(); + }); + + Self { s: Some(s) }.into() + } + } + + pub fn use_sleep() -> SuspensionResult> { + let sleep_state = use_reducer(SleepState::new); + + let s = match sleep_state.s.clone() { + Some(m) => m, + None => return Ok(Rc::new(move || sleep_state.dispatch(()))), + }; + + if s.resumed() { + Ok(Rc::new(move || sleep_state.dispatch(()))) + } else { + Err(s.clone()) + } + } + + #[function_component(Content)] + fn 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! { +
    + +
    + +
    +
    + } + } + + #[function_component(App)] + fn app() -> Html { + let fallback = html! {
    {"wait..."}
    }; + + html! { +
    + + + +
    + } + } + + yew::start_app_in_element::(gloo_utils::document().get_element_by_id("output").unwrap()); + + TimeoutFuture::new(10).await; + + let result = obtain_result(); + assert_eq!( + result.as_str(), + r#"
    "# + ); + gloo_utils::document() + .query_selector(".take-a-break") + .unwrap() + .unwrap() + .dyn_into::() + .unwrap() + .click(); + + TimeoutFuture::new(10).await; + let result = obtain_result(); + assert_eq!(result.as_str(), "
    wait...
    "); + + TimeoutFuture::new(50).await; + + let result = obtain_result(); + assert_eq!( + result.as_str(), + r#"
    "# + ); +} + +#[wasm_bindgen_test] +async fn suspense_nested_suspense_works() { + #[derive(PartialEq)] + pub struct SleepState { + s: Suspension, + } + + impl SleepState { + fn new() -> Self { + let (s, handle) = Suspension::new(); + + spawn_local(async move { + TimeoutFuture::new(50).await; + + handle.resume(); + }); + + Self { s } + } + } + + impl Reducible for SleepState { + type Action = (); + + fn reduce(self: Rc, _action: Self::Action) -> Rc { + Self::new().into() + } + } + + pub fn use_sleep() -> SuspensionResult> { + 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()) + } + } + + #[function_component(InnerContent)] + fn inner_content() -> Html { + let resleep = use_sleep()?; + + let on_take_a_break = Callback::from(move |_: MouseEvent| (resleep.clone())()); + + html! { +
    +
    + +
    +
    + } + } + + #[function_component(Content)] + fn content() -> Html { + let resleep = use_sleep()?; + + let fallback = html! {
    {"wait...(inner)"}
    }; + + let on_take_a_break = Callback::from(move |_: MouseEvent| (resleep.clone())()); + + html! { +
    +
    + +
    + + + +
    + } + } + + #[function_component(App)] + fn app() -> Html { + let fallback = html! {
    {"wait...(outer)"}
    }; + + html! { +
    + + + +
    + } + } + + yew::start_app_in_element::(gloo_utils::document().get_element_by_id("output").unwrap()); + + TimeoutFuture::new(10).await; + let result = obtain_result(); + assert_eq!(result.as_str(), "
    wait...(outer)
    "); + + TimeoutFuture::new(50).await; + + let result = obtain_result(); + assert_eq!( + result.as_str(), + r#"
    wait...(inner)
    "# + ); + + TimeoutFuture::new(50).await; + + let result = obtain_result(); + assert_eq!( + result.as_str(), + r#"
    "# + ); + + gloo_utils::document() + .query_selector(".take-a-break2") + .unwrap() + .unwrap() + .dyn_into::() + .unwrap() + .click(); + + TimeoutFuture::new(10).await; + let result = obtain_result(); + assert_eq!( + result.as_str(), + r#"
    wait...(inner)
    "# + ); + + TimeoutFuture::new(50).await; + + let result = obtain_result(); + assert_eq!( + result.as_str(), + r#"
    "# + ); +} From 2e7f0bf875307bbd0881422bf778d97090b61991 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Tue, 30 Nov 2021 22:10:07 +0900 Subject: [PATCH 10/27] Fix clippy. --- packages/yew/tests/suspense.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/yew/tests/suspense.rs b/packages/yew/tests/suspense.rs index ae5ddcd97e1..be3d6b834aa 100644 --- a/packages/yew/tests/suspense.rs +++ b/packages/yew/tests/suspense.rs @@ -192,7 +192,7 @@ async fn suspense_not_suspended_at_start() { if s.resumed() { Ok(Rc::new(move || sleep_state.dispatch(()))) } else { - Err(s.clone()) + Err(s) } } From 77ab06aa0c8a45171c5c5be30c485cc33749e840 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Tue, 30 Nov 2021 22:55:01 +0900 Subject: [PATCH 11/27] Add docs. --- website/docs/concepts/suspense.md | 84 +++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 website/docs/concepts/suspense.md diff --git a/website/docs/concepts/suspense.md b/website/docs/concepts/suspense.md new file mode 100644 index 00000000000..8a324384fa7 --- /dev/null +++ b/website/docs/concepts/suspense.md @@ -0,0 +1,84 @@ +--- +title: "Suspense" +description: "Suspense for data fetching" +--- + +Suspense is a way to suspend component rendering whilst waiting a task +to complete and a fallback (placeholder) UI is shown in the meanwhile. + +It can be used to fetch data from server, wait for tasks to be completed +by an agent, or other background asynchronous task. + +Before suspense, data fetching usually happens after (Fetch-on-render) or before +component rendering (Fetch-then-render). + +### Render-as-You-Fetch + +Suspense enables a new approach that allows components to initiate data request +during the rendering process. When a component initiates a data request, +the rendering process will become suspended and a fallback UI will be +shown until the request is completed. + +The recommended way to use suspense is with hooks. + +```rust, ignore +use yew::prelude::*; + +#[function_component(Content)] +fn content() -> Html { + let user = use_user()?; + + html! {
    {"Hello, "}{&user.name}
    } +} + +#[function_component(App)] +fn app() -> Html { + let fallback = html! {
    {"Loading..."}
    }; + + html! { + + + + } +} +``` + +In the above example, the `use_user` hook will suspend the component +rendering while the user is loading and a `Loading...` placeholder will +be shown until `user` is loaded. + +To define a hook that suspends a component rendering, it needs to return +a `SuspensionResult`. When the component needs to be suspended, the +hook should return a `Err(Suspension)` and users should unwrap it with +`?` in which it will be converted into `Html`. + +```rust, ignore +use yew::prelude::*; +use yew::suspense::Suspension; + +struct User { + name: String, +} + +fn use_user() -> SuspensionResult { + match load_user() { + // If a user is loaded, then we return it as Ok(user). + Some(m) => Ok(m), + None => { + // When user is still loading, then we create a `Suspension` + // and call `SuspensionHandle::resume` when data loading + // completes, the component will be re-rendered + // automatically. + let (s, handle) = Suspension::new(); + on_load_user_complete(move || {handle.resume();}); + Err(s) + }, + } +} +``` + +### Use Suspense in Struct Components + +Whilst it's possible to suspend a struct component, it's behaviour is +not well defined and not recommended. You should consider using function +components instead when using ``. From 9afeef0966c5592841d5798ee1289237e0058a6f Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Tue, 30 Nov 2021 22:57:21 +0900 Subject: [PATCH 12/27] Add to sidebar. --- website/sidebars.js | 217 ++++++++++++++++++++++---------------------- 1 file changed, 109 insertions(+), 108 deletions(-) diff --git a/website/sidebars.js b/website/sidebars.js index 17078dd6f22..0f5f8f5df58 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -13,122 +13,123 @@ module.exports = { // By default, Docusaurus generates a sidebar from the docs folder structure // conceptsSidebar: [{type: 'autogenerated', dirName: '.'}], - // But you can create a sidebar manually - sidebar: [ + // But you can create a sidebar manually + sidebar: [ + { + type: "category", + label: "Getting Started", + items: [ { - type: 'category', - label: 'Getting Started', - items: [ - { - type: 'category', - label: 'Project Setup', - items: [ - 'getting-started/project-setup/introduction', - 'getting-started/project-setup/using-trunk', - 'getting-started/project-setup/using-wasm-pack', - ] - }, - "getting-started/build-a-sample-app", - "getting-started/examples", - "getting-started/starter-templates", - ], + type: "category", + label: "Project Setup", + items: [ + "getting-started/project-setup/introduction", + "getting-started/project-setup/using-trunk", + "getting-started/project-setup/using-wasm-pack", + ], }, + "getting-started/build-a-sample-app", + "getting-started/examples", + "getting-started/starter-templates", + ], + }, + { + type: "category", + label: "Concepts", + items: [ { - type: "category", - label: "Concepts", - items: [ - { - type: "category", - label: "wasm-bindgen", - items: [ - "concepts/wasm-bindgen/introduction", - "concepts/wasm-bindgen/web-sys", - ] - }, - { - type: "category", - label: "Components", - items: [ - "concepts/components/introduction", - "concepts/components/callbacks", - "concepts/components/scope", - "concepts/components/properties", - "concepts/components/children", - "concepts/components/refs" - ], - }, - { - type: "category", - label: "HTML", - items: [ - "concepts/html/introduction", - "concepts/html/components", - "concepts/html/elements", - "concepts/html/events", - "concepts/html/classes", - "concepts/html/fragments", - "concepts/html/lists", - "concepts/html/literals-and-expressions" - ] - }, - { - type: "category", - label: "Function Components", - items: [ - "concepts/function-components/introduction", - "concepts/function-components/attribute", - "concepts/function-components/pre-defined-hooks", - "concepts/function-components/custom-hooks", - ] - }, - "concepts/agents", - "concepts/contexts", - "concepts/router", - ] + type: "category", + label: "wasm-bindgen", + items: [ + "concepts/wasm-bindgen/introduction", + "concepts/wasm-bindgen/web-sys", + ], }, { - type: 'category', - label: 'Advanced topics', - items: [ - "advanced-topics/how-it-works", - "advanced-topics/optimizations", - "advanced-topics/portals", - ] + type: "category", + label: "Components", + items: [ + "concepts/components/introduction", + "concepts/components/callbacks", + "concepts/components/scope", + "concepts/components/properties", + "concepts/components/children", + "concepts/components/refs", + ], }, { - type: 'category', - label: 'More', - items: [ - "more/debugging", - "more/development-tips", - "more/external-libs", - "more/css", - "more/testing", - "more/roadmap", - "more/wasm-build-tools" - ] + type: "category", + label: "HTML", + items: [ + "concepts/html/introduction", + "concepts/html/components", + "concepts/html/elements", + "concepts/html/events", + "concepts/html/classes", + "concepts/html/fragments", + "concepts/html/lists", + "concepts/html/literals-and-expressions", + ], }, { - type: "category", - label: "Migration guides", - items: [ - { - type: "category", - label: "yew", - items: ["migration-guides/yew/from-0_18_0-to-0_19_0"], - }, - { - type: "category", - label: "yew-agent", - items: ["migration-guides/yew-agent/from-0_0_0-to-0_1_0"], - }, - { - type: "category", - label: "yew-router", - items: ["migration-guides/yew-router/from-0_15_0-to-0_16_0"], - }, - ], + type: "category", + label: "Function Components", + items: [ + "concepts/function-components/introduction", + "concepts/function-components/attribute", + "concepts/function-components/pre-defined-hooks", + "concepts/function-components/custom-hooks", + ], }, - "tutorial" - ], + "concepts/agents", + "concepts/contexts", + "concepts/router", + "concepts/suspense", + ], + }, + { + type: "category", + label: "Advanced topics", + items: [ + "advanced-topics/how-it-works", + "advanced-topics/optimizations", + "advanced-topics/portals", + ], + }, + { + type: "category", + label: "More", + items: [ + "more/debugging", + "more/development-tips", + "more/external-libs", + "more/css", + "more/testing", + "more/roadmap", + "more/wasm-build-tools", + ], + }, + { + type: "category", + label: "Migration guides", + items: [ + { + type: "category", + label: "yew", + items: ["migration-guides/yew/from-0_18_0-to-0_19_0"], + }, + { + type: "category", + label: "yew-agent", + items: ["migration-guides/yew-agent/from-0_0_0-to-0_1_0"], + }, + { + type: "category", + label: "yew-router", + items: ["migration-guides/yew-router/from-0_15_0-to-0_16_0"], + }, + ], + }, + "tutorial", + ], }; From 4d58a14258bfdc45959624bd2656a6b062577489 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Tue, 30 Nov 2021 23:08:25 +0900 Subject: [PATCH 13/27] Fix syntax highlight. --- website/docs/concepts/suspense.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/website/docs/concepts/suspense.md b/website/docs/concepts/suspense.md index 8a324384fa7..410ff0271e6 100644 --- a/website/docs/concepts/suspense.md +++ b/website/docs/concepts/suspense.md @@ -7,7 +7,7 @@ Suspense is a way to suspend component rendering whilst waiting a task to complete and a fallback (placeholder) UI is shown in the meanwhile. It can be used to fetch data from server, wait for tasks to be completed -by an agent, or other background asynchronous task. +by an agent, or perform other background asynchronous task. Before suspense, data fetching usually happens after (Fetch-on-render) or before component rendering (Fetch-then-render). @@ -21,7 +21,7 @@ shown until the request is completed. The recommended way to use suspense is with hooks. -```rust, ignore +```rust ,ignore use yew::prelude::*; #[function_component(Content)] @@ -44,7 +44,7 @@ fn app() -> Html { ``` In the above example, the `use_user` hook will suspend the component -rendering while the user is loading and a `Loading...` placeholder will +rendering while user information is loading and a `Loading...` placeholder will be shown until `user` is loaded. To define a hook that suspends a component rendering, it needs to return @@ -52,9 +52,9 @@ a `SuspensionResult`. When the component needs to be suspended, the hook should return a `Err(Suspension)` and users should unwrap it with `?` in which it will be converted into `Html`. -```rust, ignore +```rust ,ignore use yew::prelude::*; -use yew::suspense::Suspension; +use yew::suspense::{Suspension, SuspensionResult}; struct User { name: String, @@ -80,5 +80,5 @@ fn use_user() -> SuspensionResult { ### Use Suspense in Struct Components Whilst it's possible to suspend a struct component, it's behaviour is -not well defined and not recommended. You should consider using function +not well-defined and not recommended. You should consider using function components instead when using ``. From 8bcacf6a2f9cab5d002978d61700ec47885e3fe7 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Mon, 20 Dec 2021 20:00:38 +0900 Subject: [PATCH 14/27] Component -> BaseComponent. --- .../yew-macro/src/html_tree/html_component.rs | 2 +- packages/yew/src/app_handle.rs | 8 +-- packages/yew/src/functional/mod.rs | 11 +++- packages/yew/src/html/component/lifecycle.rs | 28 ++++---- packages/yew/src/html/component/mod.rs | 66 ++++++++++++++++++- packages/yew/src/html/component/scope.rs | 24 +++---- packages/yew/src/lib.rs | 14 ++-- packages/yew/src/virtual_dom/vcomp.rs | 22 +++---- packages/yew/src/virtual_dom/vnode.rs | 4 +- 9 files changed, 124 insertions(+), 55 deletions(-) diff --git a/packages/yew-macro/src/html_tree/html_component.rs b/packages/yew-macro/src/html_tree/html_component.rs index 1714d542fb1..53a97236fdf 100644 --- a/packages/yew-macro/src/html_tree/html_component.rs +++ b/packages/yew-macro/src/html_tree/html_component.rs @@ -92,7 +92,7 @@ impl ToTokens for HtmlComponent { children, } = self; - let props_ty = quote_spanned!(ty.span()=> <#ty as ::yew::html::Component>::Properties); + let props_ty = quote_spanned!(ty.span()=> <#ty as ::yew::html::BaseComponent>::Properties); let children_renderer = if children.is_empty() { None } else { diff --git a/packages/yew/src/app_handle.rs b/packages/yew/src/app_handle.rs index 8ac8cfcc8f8..04e3b79afd7 100644 --- a/packages/yew/src/app_handle.rs +++ b/packages/yew/src/app_handle.rs @@ -3,21 +3,21 @@ use std::ops::Deref; -use crate::html::{Component, NodeRef, Scope, Scoped}; +use crate::html::{BaseComponent, NodeRef, Scope, Scoped}; use gloo_utils::document; use std::rc::Rc; use web_sys::Element; /// An instance of an application. #[derive(Debug)] -pub struct AppHandle { +pub struct AppHandle { /// `Scope` holder pub(crate) scope: Scope, } impl AppHandle where - COMP: Component, + COMP: BaseComponent, { /// The main entry point of a Yew program which also allows passing properties. It works /// similarly to the `program` function in Elm. You should provide an initial model, `update` @@ -56,7 +56,7 @@ where impl Deref for AppHandle where - COMP: Component, + COMP: BaseComponent, { type Target = Scope; diff --git a/packages/yew/src/functional/mod.rs b/packages/yew/src/functional/mod.rs index aa3df703725..37825d87685 100644 --- a/packages/yew/src/functional/mod.rs +++ b/packages/yew/src/functional/mod.rs @@ -13,8 +13,8 @@ //! //! More details about function components and Hooks can be found on [Yew Docs](https://yew.rs/docs/next/concepts/function-components/introduction) -use crate::html::AnyScope; -use crate::{Component, Html, Properties}; +use crate::html::{AnyScope, BaseComponent}; +use crate::{Html, Properties}; use scoped_tls_hkt::scoped_thread_local; use std::cell::RefCell; use std::fmt; @@ -100,7 +100,7 @@ where } } -impl Component for FunctionComponent +impl BaseComponent for FunctionComponent where T: FunctionProvider, { @@ -137,6 +137,10 @@ where msg() } + fn changed(&mut self, _ctx: &Context) -> bool { + true + } + fn view(&self, ctx: &Context) -> Html { self.with_hook_state(|| T::run(&*ctx.props())) } @@ -192,6 +196,7 @@ pub struct HookUpdater { hook: Rc>, process_message: ProcessMessage, } + impl HookUpdater { /// Callback which runs the hook. pub fn callback(&self, cb: F) diff --git a/packages/yew/src/html/component/lifecycle.rs b/packages/yew/src/html/component/lifecycle.rs index 4ae86a5bb90..004480071f4 100644 --- a/packages/yew/src/html/component/lifecycle.rs +++ b/packages/yew/src/html/component/lifecycle.rs @@ -1,6 +1,6 @@ //! Component lifecycle module -use super::{AnyScope, Component, Scope}; +use super::{AnyScope, BaseComponent, Scope}; use crate::html::RenderError; use crate::scheduler::{self, Runnable, Shared}; use crate::suspense::{Suspense, Suspension}; @@ -10,7 +10,7 @@ use crate::{Context, NodeRef}; use std::rc::Rc; use web_sys::Element; -pub(crate) struct ComponentState { +pub(crate) struct ComponentState { pub(crate) component: Box, pub(crate) root_node: VNode, @@ -27,7 +27,7 @@ pub(crate) struct ComponentState { pub(crate) vcomp_id: u64, } -impl ComponentState { +impl ComponentState { pub(crate) fn new( parent: Element, next_sibling: NodeRef, @@ -61,7 +61,7 @@ impl ComponentState { } } -pub(crate) struct CreateRunner { +pub(crate) struct CreateRunner { pub(crate) parent: Element, pub(crate) next_sibling: NodeRef, pub(crate) placeholder: VNode, @@ -70,7 +70,7 @@ pub(crate) struct CreateRunner { pub(crate) scope: Scope, } -impl Runnable for CreateRunner { +impl Runnable for CreateRunner { fn run(self: Box) { let mut current_state = self.scope.state.borrow_mut(); if current_state.is_none() { @@ -89,7 +89,7 @@ impl Runnable for CreateRunner { } } -pub(crate) enum UpdateEvent { +pub(crate) enum UpdateEvent { /// Wraps messages for a component. Message(COMP::Message), /// Wraps batch of messages for a component. @@ -100,12 +100,12 @@ pub(crate) enum UpdateEvent { Shift(Element, NodeRef), } -pub(crate) struct UpdateRunner { +pub(crate) struct UpdateRunner { pub(crate) state: Shared>>, pub(crate) event: UpdateEvent, } -impl Runnable for UpdateRunner { +impl Runnable for UpdateRunner { fn run(self: Box) { if let Some(mut state) = self.state.borrow_mut().as_mut() { let schedule_render = match self.event { @@ -162,11 +162,11 @@ impl Runnable for UpdateRunner { } } -pub(crate) struct DestroyRunner { +pub(crate) struct DestroyRunner { pub(crate) state: Shared>>, } -impl Runnable for DestroyRunner { +impl Runnable for DestroyRunner { fn run(self: Box) { if let Some(mut state) = self.state.borrow_mut().take() { #[cfg(debug_assertions)] @@ -179,11 +179,11 @@ impl Runnable for DestroyRunner { } } -pub(crate) struct RenderRunner { +pub(crate) struct RenderRunner { pub(crate) state: Shared>>, } -impl Runnable for RenderRunner { +impl Runnable for RenderRunner { fn run(self: Box) { if let Some(state) = self.state.borrow_mut().as_mut() { #[cfg(debug_assertions)] @@ -267,11 +267,11 @@ impl Runnable for RenderRunner { } } -pub(crate) struct RenderedRunner { +pub(crate) struct RenderedRunner { pub(crate) state: Shared>>, } -impl Runnable for RenderedRunner { +impl Runnable for RenderedRunner { fn run(self: Box) { if let Some(state) = self.state.borrow_mut().as_mut() { #[cfg(debug_assertions)] diff --git a/packages/yew/src/html/component/mod.rs b/packages/yew/src/html/component/mod.rs index 6c405d4ee3b..124a5167943 100644 --- a/packages/yew/src/html/component/mod.rs +++ b/packages/yew/src/html/component/mod.rs @@ -15,12 +15,12 @@ use std::rc::Rc; /// The [`Component`]'s context. This contains component's [`Scope`] and and props and /// is passed to every lifecycle method. #[derive(Debug)] -pub struct Context { +pub struct Context { pub(crate) scope: Scope, pub(crate) props: Rc, } -impl Context { +impl Context { /// The component scope #[inline] pub fn link(&self) -> &Scope { @@ -34,6 +34,35 @@ impl Context { } } +/// The common base of both function components and struct components. +/// +/// If you are taken here by doc links, you might be looking for: [`Component`] +pub trait BaseComponent: Sized + 'static { + /// The Component's Message. + type Message: 'static; + + /// The Component's properties. + type Properties: Properties; + + /// Creates a component. + fn create(ctx: &Context) -> Self; + + /// Updates component's internal state. + fn update(&mut self, ctx: &Context, msg: Self::Message) -> bool; + + /// React to changes of component properties. + fn changed(&mut self, ctx: &Context) -> bool; + + /// Returns a component layout to be rendered. + fn view(&self, ctx: &Context) -> Html; + + /// Notified after a layout is rendered. + fn rendered(&mut self, ctx: &Context, first_render: bool); + + /// Notified before a component is destroyed. + fn destroy(&mut self, ctx: &Context); +} + /// Components are the basic building blocks of the UI in a Yew app. Each Component /// chooses how to display itself using received props and self-managed state. /// Components can be dynamic and interactive by declaring messages that are @@ -95,3 +124,36 @@ pub trait Component: Sized + 'static { #[allow(unused_variables)] fn destroy(&mut self, ctx: &Context) {} } + +impl BaseComponent for T +where + T: Sized + Component + 'static, +{ + type Message = ::Message; + + type Properties = ::Properties; + + fn create(ctx: &Context) -> Self { + Component::create(ctx) + } + + fn update(&mut self, ctx: &Context, msg: Self::Message) -> bool { + Component::update(self, ctx, msg) + } + + fn changed(&mut self, ctx: &Context) -> bool { + Component::changed(self, ctx) + } + + fn view(&self, ctx: &Context) -> Html { + Component::view(self, ctx) + } + + fn rendered(&mut self, ctx: &Context, first_render: bool) { + Component::rendered(self, ctx, first_render) + } + + fn destroy(&mut self, ctx: &Context) { + Component::destroy(self, ctx) + } +} diff --git a/packages/yew/src/html/component/scope.rs b/packages/yew/src/html/component/scope.rs index 6d5aaeed50d..c29001b8487 100644 --- a/packages/yew/src/html/component/scope.rs +++ b/packages/yew/src/html/component/scope.rs @@ -5,7 +5,7 @@ use super::{ ComponentState, CreateRunner, DestroyRunner, RenderRunner, RenderedRunner, UpdateEvent, UpdateRunner, }, - Component, + BaseComponent, }; use crate::callback::Callback; use crate::context::{ContextHandle, ContextProvider}; @@ -34,7 +34,7 @@ pub struct AnyScope { pub(crate) vcomp_id: u64, } -impl From> for AnyScope { +impl From> for AnyScope { fn from(scope: Scope) -> Self { AnyScope { type_id: TypeId::of::(), @@ -71,7 +71,7 @@ impl AnyScope { } /// Attempts to downcast into a typed scope - pub fn downcast(self) -> Scope { + pub fn downcast(self) -> Scope { let state = self .state .downcast::>>>() @@ -93,7 +93,7 @@ impl AnyScope { } } - pub(crate) fn find_parent_scope(&self) -> Option> { + pub(crate) fn find_parent_scope(&self) -> Option> { let expected_type_id = TypeId::of::(); iter::successors(Some(self), |scope| scope.get_parent()) .filter(|scope| scope.get_type_id() == &expected_type_id) @@ -122,7 +122,7 @@ pub(crate) trait Scoped { fn shift_node(&self, parent: Element, next_sibling: NodeRef); } -impl Scoped for Scope { +impl Scoped for Scope { fn to_any(&self) -> AnyScope { self.clone().into() } @@ -156,7 +156,7 @@ impl Scoped for Scope { } /// A context which allows sending messages to a component. -pub struct Scope { +pub struct Scope { parent: Option>, pub(crate) state: Shared>>, @@ -165,13 +165,13 @@ pub struct Scope { pub(crate) vcomp_id: u64, } -impl fmt::Debug for Scope { +impl fmt::Debug for Scope { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("Scope<_>") } } -impl Clone for Scope { +impl Clone for Scope { fn clone(&self) -> Self { Scope { parent: self.parent.clone(), @@ -183,7 +183,7 @@ impl Clone for Scope { } } -impl Scope { +impl Scope { /// Returns the parent scope pub fn get_parent(&self) -> Option<&AnyScope> { self.parent.as_deref() @@ -513,7 +513,7 @@ impl Scope { /// Defines a message type that can be sent to a component. /// Used for the return value of closure given to [Scope::batch_callback](struct.Scope.html#method.batch_callback). -pub trait SendAsMessage { +pub trait SendAsMessage { /// Sends the message to the given component's scope. /// See [Scope::batch_callback](struct.Scope.html#method.batch_callback). fn send(self, scope: &Scope); @@ -521,7 +521,7 @@ pub trait SendAsMessage { impl SendAsMessage for Option where - COMP: Component, + COMP: BaseComponent, { fn send(self, scope: &Scope) { if let Some(msg) = self { @@ -532,7 +532,7 @@ where impl SendAsMessage for Vec where - COMP: Component, + COMP: BaseComponent, { fn send(self, scope: &Scope) { scope.send_message_batch(self); diff --git a/packages/yew/src/lib.rs b/packages/yew/src/lib.rs index 1ce2fb11eda..6c813a1fe98 100644 --- a/packages/yew/src/lib.rs +++ b/packages/yew/src/lib.rs @@ -281,6 +281,8 @@ pub mod events { pub use crate::app_handle::AppHandle; use web_sys::Element; +use crate::html::BaseComponent; + thread_local! { static PANIC_HOOK_IS_SET: Cell = Cell::new(false); } @@ -303,7 +305,7 @@ fn set_default_panic_hook() { /// If you would like to pass props, use the `start_app_with_props_in_element` method. pub fn start_app_in_element(element: Element) -> AppHandle where - COMP: Component, + COMP: BaseComponent, COMP::Properties: Default, { start_app_with_props_in_element(element, COMP::Properties::default()) @@ -313,7 +315,7 @@ where /// Alias to start_app_in_element(Body) pub fn start_app() -> AppHandle where - COMP: Component, + COMP: BaseComponent, COMP::Properties: Default, { start_app_with_props(COMP::Properties::default()) @@ -326,7 +328,7 @@ where /// CSS classes of the body element. pub fn start_app_as_body() -> AppHandle where - COMP: Component, + COMP: BaseComponent, COMP::Properties: Default, { start_app_with_props_as_body(COMP::Properties::default()) @@ -339,7 +341,7 @@ pub fn start_app_with_props_in_element( props: COMP::Properties, ) -> AppHandle where - COMP: Component, + COMP: BaseComponent, { set_default_panic_hook(); AppHandle::::mount_with_props(element, Rc::new(props)) @@ -349,7 +351,7 @@ where /// This function does the same as `start_app(...)` but allows to start an Yew application with properties. pub fn start_app_with_props(props: COMP::Properties) -> AppHandle where - COMP: Component, + COMP: BaseComponent, { start_app_with_props_in_element( gloo_utils::document() @@ -367,7 +369,7 @@ where /// CSS classes of the body element. pub fn start_app_with_props_as_body(props: COMP::Properties) -> AppHandle where - COMP: Component, + COMP: BaseComponent, { set_default_panic_hook(); AppHandle::::mount_as_body_with_props(Rc::new(props)) diff --git a/packages/yew/src/virtual_dom/vcomp.rs b/packages/yew/src/virtual_dom/vcomp.rs index a51305a2f29..190bcb7af9d 100644 --- a/packages/yew/src/virtual_dom/vcomp.rs +++ b/packages/yew/src/virtual_dom/vcomp.rs @@ -1,7 +1,7 @@ //! This module contains the implementation of a virtual component (`VComp`). use super::{Key, VDiff, VNode}; -use crate::html::{AnyScope, Component, NodeRef, Scope, Scoped}; +use crate::html::{AnyScope, BaseComponent, NodeRef, Scope, Scoped}; use std::any::TypeId; use std::borrow::Borrow; use std::fmt; @@ -73,7 +73,7 @@ impl Clone for VComp { } /// A virtual child component. -pub struct VChild { +pub struct VChild { /// The component properties pub props: Rc, /// Reference to the mounted node @@ -81,7 +81,7 @@ pub struct VChild { key: Option, } -impl Clone for VChild { +impl Clone for VChild { fn clone(&self) -> Self { VChild { props: Rc::clone(&self.props), @@ -91,7 +91,7 @@ impl Clone for VChild { } } -impl PartialEq for VChild +impl PartialEq for VChild where COMP::Properties: PartialEq, { @@ -102,7 +102,7 @@ where impl VChild where - COMP: Component, + COMP: BaseComponent, { /// Creates a child component that can be accessed and modified by its parent. pub fn new(props: COMP::Properties, node_ref: NodeRef, key: Option) -> Self { @@ -116,7 +116,7 @@ where impl From> for VComp where - COMP: Component, + COMP: BaseComponent, { fn from(vchild: VChild) -> Self { VComp::new::(vchild.props, vchild.node_ref, vchild.key) @@ -127,7 +127,7 @@ impl VComp { /// Creates a new `VComp` instance. pub fn new(props: Rc, node_ref: NodeRef, key: Option) -> Self where - COMP: Component, + COMP: BaseComponent, { VComp { type_id: TypeId::of::(), @@ -183,17 +183,17 @@ trait Mountable { fn reuse(self: Box, node_ref: NodeRef, scope: &dyn Scoped, next_sibling: NodeRef); } -struct PropsWrapper { +struct PropsWrapper { props: Rc, } -impl PropsWrapper { +impl PropsWrapper { pub fn new(props: Rc) -> Self { Self { props } } } -impl Mountable for PropsWrapper { +impl Mountable for PropsWrapper { fn copy(&self) -> Box { let wrapper: PropsWrapper = PropsWrapper { props: Rc::clone(&self.props), @@ -277,7 +277,7 @@ impl fmt::Debug for VComp { } } -impl fmt::Debug for VChild { +impl fmt::Debug for VChild { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("VChild<_>") } diff --git a/packages/yew/src/virtual_dom/vnode.rs b/packages/yew/src/virtual_dom/vnode.rs index 6bd7041d536..f23a0ec006c 100644 --- a/packages/yew/src/virtual_dom/vnode.rs +++ b/packages/yew/src/virtual_dom/vnode.rs @@ -1,7 +1,7 @@ //! This module contains the implementation of abstract virtual node. use super::{Key, VChild, VComp, VDiff, VList, VPortal, VSuspense, VTag, VText}; -use crate::html::{AnyScope, Component, NodeRef}; +use crate::html::{AnyScope, BaseComponent, NodeRef}; use gloo::console; use std::cmp::PartialEq; use std::fmt; @@ -246,7 +246,7 @@ impl From for VNode { impl From> for VNode where - COMP: Component, + COMP: BaseComponent, { fn from(vchild: VChild) -> Self { VNode::VComp(VComp::from(vchild)) From 525478256ebad8eb8b28f8d99875842bd46eedaf Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Mon, 20 Dec 2021 20:16:33 +0900 Subject: [PATCH 15/27] Html -> VNode, HtmlResult = RenderResult. --- Cargo.toml | 2 +- examples/futures/Cargo.toml | 0 examples/futures/README.md | 0 examples/futures/index.html | 0 examples/futures/src/main.rs | 0 examples/futures/src/markdown.rs | 2 +- examples/inner_html/Cargo.toml | 0 examples/inner_html/README.md | 0 examples/inner_html/index.html | 0 examples/inner_html/src/document.html | 0 examples/inner_html/src/main.rs | 3 +- .../yew-macro/src/html_tree/html_iterable.rs | 18 +- packages/yew-macro/src/html_tree/html_node.rs | 2 +- packages/yew-macro/src/html_tree/mod.rs | 2 +- packages/yew-macro/tests/derive_props/pass.rs | 20 +++ .../bad-return-type-fail.stderr | 7 +- .../function_component_attr/generic-pass.rs | 6 +- .../with-defaulted-type-param-pass.rs | 25 +++ .../tests/html_macro/block-fail.stderr | 17 +- .../yew-macro/tests/html_macro/block-pass.rs | 22 ++- .../tests/html_macro/component-fail.stderr | 4 +- .../tests/html_macro/component-pass.rs | 74 ++++---- .../tests/html_macro/dyn-element-pass.rs | 10 +- .../tests/html_macro/element-fail.stderr | 2 +- .../html_macro/generic-component-pass.rs | 26 ++- .../tests/html_macro/html-element-pass.rs | 13 +- .../tests/html_macro/html-if-pass.rs | 48 ++--- .../tests/html_macro/html-node-pass.rs | 26 +-- .../tests/html_macro/iterable-fail.stderr | 77 ++++---- .../tests/html_macro/iterable-pass.rs | 12 +- .../yew-macro/tests/html_macro/list-pass.rs | 16 +- .../tests/html_macro/node-fail.stderr | 8 +- .../yew-macro/tests/html_macro/node-pass.rs | 30 ++-- .../yew-macro/tests/html_macro/svg-pass.rs | 9 +- packages/yew-macro/tests/html_macro_test.rs | 24 ++- .../yew-macro/tests/props_macro/props-pass.rs | 10 ++ packages/yew/src/functional/mod.rs | 6 +- packages/yew/src/html/component/children.rs | 7 - packages/yew/src/html/component/mod.rs | 8 +- packages/yew/src/html/mod.rs | 19 +- packages/yew/src/lib.rs | 2 +- packages/yew/src/suspense/component.rs | 2 +- packages/yew/src/utils/mod.rs | 168 +++--------------- 43 files changed, 314 insertions(+), 413 deletions(-) mode change 100644 => 100755 examples/futures/Cargo.toml mode change 100644 => 100755 examples/futures/README.md mode change 100644 => 100755 examples/futures/index.html mode change 100644 => 100755 examples/futures/src/main.rs mode change 100644 => 100755 examples/futures/src/markdown.rs mode change 100644 => 100755 examples/inner_html/Cargo.toml mode change 100644 => 100755 examples/inner_html/README.md mode change 100644 => 100755 examples/inner_html/index.html mode change 100644 => 100755 examples/inner_html/src/document.html mode change 100644 => 100755 examples/inner_html/src/main.rs create mode 100644 packages/yew-macro/tests/function_component_attr/with-defaulted-type-param-pass.rs diff --git a/Cargo.toml b/Cargo.toml index 7d9485c079f..5054a8a3973 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,7 @@ members = [ "examples/two_apps", "examples/webgl", "examples/web_worker_fib", - "examples/suspense", + # "examples/suspense", # Release tools "packages/changelog", diff --git a/examples/futures/Cargo.toml b/examples/futures/Cargo.toml old mode 100644 new mode 100755 diff --git a/examples/futures/README.md b/examples/futures/README.md old mode 100644 new mode 100755 diff --git a/examples/futures/index.html b/examples/futures/index.html old mode 100644 new mode 100755 diff --git a/examples/futures/src/main.rs b/examples/futures/src/main.rs old mode 100644 new mode 100755 diff --git a/examples/futures/src/markdown.rs b/examples/futures/src/markdown.rs old mode 100644 new mode 100755 index e50ab03002f..307c8702069 --- a/examples/futures/src/markdown.rs +++ b/examples/futures/src/markdown.rs @@ -107,7 +107,7 @@ pub fn render_markdown(src: &str) -> Html { } if elems.len() == 1 { - Ok(VNode::VTag(Box::new(elems.pop().unwrap()))) + VNode::VTag(Box::new(elems.pop().unwrap())) } else { html! {
    { for elems.into_iter() }
    diff --git a/examples/inner_html/Cargo.toml b/examples/inner_html/Cargo.toml old mode 100644 new mode 100755 diff --git a/examples/inner_html/README.md b/examples/inner_html/README.md old mode 100644 new mode 100755 diff --git a/examples/inner_html/index.html b/examples/inner_html/index.html old mode 100644 new mode 100755 diff --git a/examples/inner_html/src/document.html b/examples/inner_html/src/document.html old mode 100644 new mode 100755 diff --git a/examples/inner_html/src/main.rs b/examples/inner_html/src/main.rs old mode 100644 new mode 100755 index d557d957241..9ab6992e3ee --- a/examples/inner_html/src/main.rs +++ b/examples/inner_html/src/main.rs @@ -1,5 +1,4 @@ use web_sys::console; -use yew::virtual_dom::VNode; use yew::{Component, Context, Html}; const HTML: &str = include_str!("document.html"); @@ -22,7 +21,7 @@ impl Component for Model { // See console::log_1(&div); - Ok(VNode::VRef(div.into())) + Html::VRef(div.into()) } } diff --git a/packages/yew-macro/src/html_tree/html_iterable.rs b/packages/yew-macro/src/html_tree/html_iterable.rs index 3bf1aa4a0fe..700d28ab2ad 100644 --- a/packages/yew-macro/src/html_tree/html_iterable.rs +++ b/packages/yew-macro/src/html_tree/html_iterable.rs @@ -42,14 +42,7 @@ impl ToTokens for HtmlIterable { let expr = &self.0; let new_tokens = quote_spanned! {expr.span()=> #[allow(unused_braces)] - { - 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)) - } + ::std::iter::Iterator::collect::<::yew::virtual_dom::VNode>(::std::iter::IntoIterator::into_iter(#expr)) }; tokens.extend(new_tokens); @@ -62,14 +55,7 @@ impl ToNodeIterator for HtmlIterable { // #expr can return anything that implements IntoIterator> // We use a util method to avoid clippy warnings and reduce generated code size Some(quote_spanned! {expr.span()=> - { - 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) - } + ::yew::utils::into_node_iter(#expr) }) } } diff --git a/packages/yew-macro/src/html_tree/html_node.rs b/packages/yew-macro/src/html_tree/html_node.rs index ab6fdea81cd..d8cd6e19987 100644 --- a/packages/yew-macro/src/html_tree/html_node.rs +++ b/packages/yew-macro/src/html_tree/html_node.rs @@ -60,7 +60,7 @@ impl ToNodeIterator for HtmlNode { HtmlNode::Expression(expr) => { // NodeSeq turns both Into and Vec> into IntoIterator Some( - quote_spanned! {expr.span()=> ::yew::utils::TryIntoNodeSeq::try_into_node_seq(#expr)?}, + quote_spanned! {expr.span()=> ::std::convert::Into::<::yew::utils::NodeSeq<_, _>>::into(#expr)}, ) } } diff --git a/packages/yew-macro/src/html_tree/mod.rs b/packages/yew-macro/src/html_tree/mod.rs index 854790a1122..ad18325da78 100644 --- a/packages/yew-macro/src/html_tree/mod.rs +++ b/packages/yew-macro/src/html_tree/mod.rs @@ -182,7 +182,7 @@ impl ToTokens for HtmlRootVNode { let new_tokens = self.0.to_token_stream(); tokens.extend(quote! {{ #[allow(clippy::useless_conversion)] - (|| ::yew::html::RenderResult::Ok(::yew::utils::TryIntoNode::<_, ::yew::virtual_dom::VNode>::try_into_node(#new_tokens)?.into_value()))() + <::yew::virtual_dom::VNode as ::std::convert::From<_>>::from(#new_tokens) }}); } } diff --git a/packages/yew-macro/tests/derive_props/pass.rs b/packages/yew-macro/tests/derive_props/pass.rs index ce99ee6787d..f6b62410bb3 100644 --- a/packages/yew-macro/tests/derive_props/pass.rs +++ b/packages/yew-macro/tests/derive_props/pass.rs @@ -251,4 +251,24 @@ mod t12 { } } +#[deny(non_snake_case, dead_code)] +mod t13 { + #[derive(::std::cmp::PartialEq, ::yew::Properties)] + #[allow(non_snake_case)] // putting this on fields directly does not work, even in normal rust + struct Props { + #[allow(dead_code)] + create_message: ::std::option::Option, + NonSnakeCase: u32, + } +} + +mod raw_field_names { + #[derive(::yew::Properties, ::std::cmp::PartialEq)] + pub struct Props { + r#true: u32, + r#pointless_raw_name: u32, + } + +} + fn main() {} diff --git a/packages/yew-macro/tests/function_component_attr/bad-return-type-fail.stderr b/packages/yew-macro/tests/function_component_attr/bad-return-type-fail.stderr index bcb7d314ad8..0a5d227dd08 100644 --- a/packages/yew-macro/tests/function_component_attr/bad-return-type-fail.stderr +++ b/packages/yew-macro/tests/function_component_attr/bad-return-type-fail.stderr @@ -8,9 +8,6 @@ error[E0308]: mismatched types --> $DIR/bad-return-type-fail.rs:13:5 | 12 | fn comp(_props: &Props) -> u32 { - | --- expected `std::result::Result` because of return type + | --- expected `VNode` because of return type 13 | 1 - | ^ expected enum `std::result::Result`, found integer - | - = note: expected enum `std::result::Result` - found type `{integer}` + | ^ expected enum `VNode`, found integer diff --git a/packages/yew-macro/tests/function_component_attr/generic-pass.rs b/packages/yew-macro/tests/function_component_attr/generic-pass.rs index 544ad92607d..29df3007d46 100644 --- a/packages/yew-macro/tests/function_component_attr/generic-pass.rs +++ b/packages/yew-macro/tests/function_component_attr/generic-pass.rs @@ -68,10 +68,10 @@ fn const_generics() -> ::yew::Html { } fn compile_pass() { - (::yew::html! { a=10 /> }).unwrap(); - (::yew::html! { /> }).unwrap(); + ::yew::html! { a=10 /> }; + ::yew::html! { /> }; - (::yew::html! { /> }).unwrap(); + ::yew::html! { /> }; } fn main() {} diff --git a/packages/yew-macro/tests/function_component_attr/with-defaulted-type-param-pass.rs b/packages/yew-macro/tests/function_component_attr/with-defaulted-type-param-pass.rs new file mode 100644 index 00000000000..9ff37a91496 --- /dev/null +++ b/packages/yew-macro/tests/function_component_attr/with-defaulted-type-param-pass.rs @@ -0,0 +1,25 @@ +use yew::prelude::*; + +#[derive(Properties, Debug)] +pub struct CompProps { + #[prop_or_default] + _phantom: std::marker::PhantomData, +} + +impl PartialEq for CompProps { + fn eq(&self, _rhs: &Self) -> bool { + true + } +} + +#[function_component(Comp)] +pub fn comp(_props: &CompProps) -> Html { + todo!() +} + +#[function_component(App)] +pub fn app() -> Html { + html! { } // No generics here. +} + +fn main() {} diff --git a/packages/yew-macro/tests/html_macro/block-fail.stderr b/packages/yew-macro/tests/html_macro/block-fail.stderr index 660fbd23b95..bf887658929 100644 --- a/packages/yew-macro/tests/html_macro/block-fail.stderr +++ b/packages/yew-macro/tests/html_macro/block-fail.stderr @@ -9,8 +9,9 @@ error[E0277]: `()` doesn't implement `std::fmt::Display` = note: required because of the requirements on the impl of `ToString` for `()` = note: required because of the requirements on the impl of `From<()>` for `VNode` = note: required because of the requirements on the impl of `Into` for `()` - = note: required because of the requirements on the impl of `TryIntoNodeSeq<(), VNode>` for `()` - = note: required by `try_into_node_seq` + = note: 2 redundant requirements hidden + = note: required because of the requirements on the impl of `Into>` for `()` + = note: required by `into` error[E0277]: `()` doesn't implement `std::fmt::Display` --> $DIR/block-fail.rs:12:16 @@ -23,8 +24,9 @@ error[E0277]: `()` doesn't implement `std::fmt::Display` = note: required because of the requirements on the impl of `ToString` for `()` = note: required because of the requirements on the impl of `From<()>` for `VNode` = note: required because of the requirements on the impl of `Into` for `()` - = note: required because of the requirements on the impl of `TryIntoNodeSeq<(), VNode>` for `()` - = note: required by `try_into_node_seq` + = note: 2 redundant requirements hidden + = note: required because of the requirements on the impl of `Into>` for `()` + = note: required by `into` error[E0277]: `()` doesn't implement `std::fmt::Display` --> $DIR/block-fail.rs:15:17 @@ -32,10 +34,13 @@ error[E0277]: `()` doesn't implement `std::fmt::Display` 15 | <>{ for (0..3).map(|_| not_tree()) } | ^^^^^^ `()` cannot be formatted with the default formatter | + ::: $WORKSPACE/packages/yew/src/utils/mod.rs + | + | T: Into, + | ------- required by this bound in `into_node_iter` + | = help: the trait `std::fmt::Display` is not implemented for `()` = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead = note: required because of the requirements on the impl of `ToString` for `()` = note: required because of the requirements on the impl of `From<()>` for `VNode` = note: required because of the requirements on the impl of `Into` for `()` - = note: required because of the requirements on the impl of `TryIntoNode<(), VNode>` for `()` - = note: required by `try_into_node` diff --git a/packages/yew-macro/tests/html_macro/block-pass.rs b/packages/yew-macro/tests/html_macro/block-pass.rs index 36ee52c5b6c..d8f527e6e46 100644 --- a/packages/yew-macro/tests/html_macro/block-pass.rs +++ b/packages/yew-macro/tests/html_macro/block-pass.rs @@ -37,31 +37,29 @@ pub struct u8; pub struct usize; fn main() { - (::yew::html! { <>{ "Hi" } }).unwrap(); - (::yew::html! { <>{ ::std::format!("Hello") } }).unwrap(); - (::yew::html! { <>{ ::std::string::ToString::to_string("Hello") } }).unwrap(); + ::yew::html! { <>{ "Hi" } }; + ::yew::html! { <>{ ::std::format!("Hello") } }; + ::yew::html! { <>{ ::std::string::ToString::to_string("Hello") } }; let msg = "Hello"; - (::yew::html! {
    { msg }
    }).unwrap(); + ::yew::html! {
    { msg }
    }; let subview = ::yew::html! { "subview!" }; - (::yew::html! {
    { subview }
    }).unwrap(); + ::yew::html! {
    { subview }
    }; let subview = || ::yew::html! { "subview!" }; - (::yew::html! {
    { subview() }
    }).unwrap(); + ::yew::html! {
    { subview() }
    }; - (::yew::html! { + ::yew::html! {
      { for ::std::iter::Iterator::map(0..3, |num| { ::yew::html! { { num } }}) }
    - }) - .unwrap(); + }; let item = |num| ::yew::html! {
  • { ::std::format!("item {}!", num) }
  • }; - (::yew::html! { + ::yew::html! {
      { for ::std::iter::Iterator::map(0..3, item) }
    - }) - .unwrap(); + }; } diff --git a/packages/yew-macro/tests/html_macro/component-fail.stderr b/packages/yew-macro/tests/html_macro/component-fail.stderr index 122dd6e5d3b..7b6506d6cc4 100644 --- a/packages/yew-macro/tests/html_macro/component-fail.stderr +++ b/packages/yew-macro/tests/html_macro/component-fail.stderr @@ -346,7 +346,7 @@ error[E0277]: the trait bound `{integer}: IntoPropValue` is not satisfie <&'static str as IntoPropValue> <&'static str as IntoPropValue>> <&'static str as IntoPropValue>> - and 13 others + and 12 others error[E0277]: the trait bound `{integer}: IntoPropValue` is not satisfied --> tests/html_macro/component-fail.rs:79:34 @@ -359,7 +359,7 @@ error[E0277]: the trait bound `{integer}: IntoPropValue` is not satisfie <&'static str as IntoPropValue> <&'static str as IntoPropValue>> <&'static str as IntoPropValue>> - and 13 others + and 12 others error[E0308]: mismatched types --> tests/html_macro/component-fail.rs:80:31 diff --git a/packages/yew-macro/tests/html_macro/component-pass.rs b/packages/yew-macro/tests/html_macro/component-pass.rs index 04882c71f6b..3d43236802a 100644 --- a/packages/yew-macro/tests/html_macro/component-pass.rs +++ b/packages/yew-macro/tests/html_macro/component-pass.rs @@ -95,6 +95,8 @@ impl ::std::convert::Into<::yew::virtual_dom::VNode> for ChildrenVariants { pub struct ChildProperties { #[prop_or_default] pub string: ::std::string::String, + #[prop_or_default] + pub r#fn: ::std::primitive::i32, pub int: ::std::primitive::i32, #[prop_or_default] pub opt_str: ::std::option::Option<::std::string::String>, @@ -158,19 +160,19 @@ mod scoped { } fn compile_pass() { - (::yew::html! { }).unwrap(); + ::yew::html! { }; + ::yew::html! { }; - (::yew::html! { + ::yew::html! { <> - }) - .unwrap(); + }; let props = <::Properties as ::std::default::Default>::default(); let node_ref = <::yew::NodeRef as ::std::default::Default>::default(); - (::yew::html! { + ::yew::html! { <> @@ -179,9 +181,9 @@ fn compile_pass() { ::Properties as ::std::default::Default>::default() /> - }).unwrap(); + }; - (::yew::html! { + ::yew::html! { <> @@ -194,51 +196,47 @@ fn compile_pass() { >::from("child"))} int=1 /> - }).unwrap(); + }; let name_expr = "child"; - (::yew::html! { + ::yew::html! { - }) - .unwrap(); + }; let string = "child"; let int = 1; - (::yew::html! { + ::yew::html! { - }) - .unwrap(); + }; - (::yew::html! { + ::yew::html! { <> as ::std::convert::From<_>>::from(|_| ()))} /> as ::std::convert::From<_>>::from(|_| ())} /> >} /> - }).unwrap(); + }; let node_ref = <::yew::NodeRef as ::std::default::Default>::default(); - (::yew::html! { + ::yew::html! { <> - }) - .unwrap(); + }; let int = 1; let node_ref = <::yew::NodeRef as ::std::default::Default>::default(); - (::yew::html! { + ::yew::html! { <> - }) - .unwrap(); + }; let props = <::Properties as ::std::default::Default>::default(); let child_props = <::Properties as ::std::default::Default>::default(); - (::yew::html! { + ::yew::html! { <> @@ -273,23 +271,21 @@ fn compile_pass() { - }) - .unwrap(); + }; - (::yew::html! { + ::yew::html! { <> - }) - .unwrap(); + }; - (::yew::html! { + ::yew::html! { @@ -305,26 +301,23 @@ fn compile_pass() { ) } - }) - .unwrap(); + }; let children = ::std::vec![ ::yew::html_nested! { }, ::yew::html_nested! { }, ]; - (::yew::html! { + ::yew::html! { { ::std::clone::Clone::clone(&children) } - }) - .unwrap(); + }; // https://github.com/yewstack/yew/issues/1527 - (::yew::html! { + ::yew::html! { { for children } - }) - .unwrap(); + }; let variants = || -> ::std::vec::Vec { ::std::vec![ @@ -341,7 +334,7 @@ fn compile_pass() { ] }; - (::yew::html! { + ::yew::html! { <> { ::std::iter::Iterator::collect::<::yew::virtual_dom::VNode>( @@ -368,8 +361,7 @@ fn compile_pass() { }
    - }) - .unwrap(); + }; ::yew::html_nested! { 1 }; } diff --git a/packages/yew-macro/tests/html_macro/dyn-element-pass.rs b/packages/yew-macro/tests/html_macro/dyn-element-pass.rs index 49d3fd3f67a..e26369903ec 100644 --- a/packages/yew-macro/tests/html_macro/dyn-element-pass.rs +++ b/packages/yew-macro/tests/html_macro/dyn-element-pass.rs @@ -43,15 +43,14 @@ fn main() { move || ::std::option::Option::unwrap(::std::iter::Iterator::next(&mut it)) }; - (::yew::html! { + ::yew::html! { <@{ dyn_tag() }> <@{ next_extra_tag() } class="extra-a"/> <@{ next_extra_tag() } class="extra-b"/> - }) - .unwrap(); + }; - (::yew::html! { + ::yew::html! { <@{ let tag = dyn_tag(); if tag == "test" { @@ -60,6 +59,5 @@ fn main() { "a" } }/> - }) - .unwrap(); + }; } diff --git a/packages/yew-macro/tests/html_macro/element-fail.stderr b/packages/yew-macro/tests/html_macro/element-fail.stderr index 03d47b2c55d..7ee8ea4e9fc 100644 --- a/packages/yew-macro/tests/html_macro/element-fail.stderr +++ b/packages/yew-macro/tests/html_macro/element-fail.stderr @@ -202,7 +202,7 @@ error: the property value must be either a literal or enclosed in braces. Consid 92 | html! { }; | ^^^^^^^^^^^ -warning: use of deprecated function `compile_fail::{closure#20}::deprecated_use_of_class`: the use of `(...)` with the attribute `class` is deprecated and will be removed in version 0.19. Use the `classes!` macro instead. +warning: use of deprecated function `compile_fail::deprecated_use_of_class`: the use of `(...)` with the attribute `class` is deprecated and will be removed in version 0.19. Use the `classes!` macro instead. --> tests/html_macro/element-fail.rs:80:25 | 80 | html! {
    }; diff --git a/packages/yew-macro/tests/html_macro/generic-component-pass.rs b/packages/yew-macro/tests/html_macro/generic-component-pass.rs index 3f622fa6e0b..49d88cb56b8 100644 --- a/packages/yew-macro/tests/html_macro/generic-component-pass.rs +++ b/packages/yew-macro/tests/html_macro/generic-component-pass.rs @@ -76,24 +76,22 @@ where } fn compile_pass() { - (::yew::html! { /> }).unwrap(); - (::yew::html! { >> }).unwrap(); + ::yew::html! { /> }; + ::yew::html! { >> }; - (::yew::html! { > /> }).unwrap(); - (::yew::html! { >>>> }).unwrap(); + ::yew::html! { > /> }; + ::yew::html! { >>>> }; - (::yew::html! { /> }).unwrap(); - (::yew::html! { >> }) - .unwrap(); - (::yew::html! { /> }).unwrap(); - (::yew::html! { >> }) - .unwrap(); + ::yew::html! { /> }; + ::yew::html! { >> }; + ::yew::html! { /> }; + ::yew::html! { >> }; - (::yew::html! { /> }).unwrap(); - (::yew::html! { >> }).unwrap(); + ::yew::html! { /> }; + ::yew::html! { >> }; - (::yew::html! { /> }).unwrap(); - (::yew::html! { >> }).unwrap(); + ::yew::html! { /> }; + ::yew::html! { >> }; } fn main() {} diff --git a/packages/yew-macro/tests/html_macro/html-element-pass.rs b/packages/yew-macro/tests/html_macro/html-element-pass.rs index 732a130e51d..c85989c96c9 100644 --- a/packages/yew-macro/tests/html_macro/html-element-pass.rs +++ b/packages/yew-macro/tests/html_macro/html-element-pass.rs @@ -46,10 +46,9 @@ fn compile_pass() { || <::std::string::String as ::std::convert::From<&::std::primitive::str>>::from("test"); let mut extra_tags_iter = ::std::iter::IntoIterator::into_iter(::std::vec!["a", "b"]); - let attr_val_none: ::std::option::Option<::yew::virtual_dom::AttrValue> = - ::std::option::Option::None; + let attr_val_none: ::std::option::Option<::yew::virtual_dom::AttrValue> = ::std::option::Option::None; - (::yew::html! { + ::yew::html! {
    @@ -107,17 +106,17 @@ fn compile_pass() { onblur={::std::option::Option::Some(<::yew::Callback<::yew::FocusEvent> as ::std::convert::From<_>>::from(|_| ()))} />
    - }).unwrap(); + }; let children = ::std::vec![ ::yew::html! { { "Hello" } }, ::yew::html! { { "World" } }, ]; - (::yew::html! {
    {children}
    }).unwrap(); + ::yew::html! {
    {children}
    }; // handle misleading angle brackets - (::yew::html! {
    ::default()}>
    }).unwrap(); - (::yew::html! {
    }).unwrap(); + ::yew::html! {
    ::default()}>
    }; + ::yew::html! {
    }; } fn main() {} diff --git a/packages/yew-macro/tests/html_macro/html-if-pass.rs b/packages/yew-macro/tests/html_macro/html-if-pass.rs index 80d84d4d156..4ad4a265715 100644 --- a/packages/yew-macro/tests/html_macro/html-if-pass.rs +++ b/packages/yew-macro/tests/html_macro/html-if-pass.rs @@ -1,35 +1,35 @@ use yew::prelude::*; fn compile_pass_lit() { - (html! { if true {} }).unwrap(); - (html! { if true {
    } }).unwrap(); - (html! { if true {
    } }).unwrap(); - (html! { if true { <>
    } }).unwrap(); - (html! { if true { { html! {} } } }).unwrap(); - (html! { if true { { { let _x = 42; html! {} } } } }).unwrap(); - (html! { if true {} else {} }).unwrap(); - (html! { if true {} else if true {} }).unwrap(); - (html! { if true {} else if true {} else {} }).unwrap(); - (html! { if let Some(text) = Some("text") { { text } } }).unwrap(); - (html! { <>
    if true {}
    }).unwrap(); - (html! {
    if true {}
    }).unwrap(); + html! { if true {} }; + html! { if true {
    } }; + html! { if true {
    } }; + html! { if true { <>
    } }; + html! { if true { { html! {} } } }; + html! { if true { { { let _x = 42; html! {} } } } }; + html! { if true {} else {} }; + html! { if true {} else if true {} }; + html! { if true {} else if true {} else {} }; + html! { if let Some(text) = Some("text") { { text } } }; + html! { <>
    if true {}
    }; + html! {
    if true {}
    }; } fn compile_pass_expr() { let condition = true; - (html! { if condition {} }).unwrap(); - (html! { if condition {
    } }).unwrap(); - (html! { if condition {
    } }).unwrap(); - (html! { if condition { <>
    } }).unwrap(); - (html! { if condition { { html! {} } } }).unwrap(); - (html! { if condition { { { let _x = 42; html! {} } } } }).unwrap(); - (html! { if condition {} else {} }).unwrap(); - (html! { if condition {} else if condition {} }).unwrap(); - (html! { if condition {} else if condition {} else {} }).unwrap(); - (html! { if let Some(text) = Some("text") { { text } } }).unwrap(); - (html! { <>
    if condition {}
    }).unwrap(); - (html! {
    if condition {}
    }).unwrap(); + html! { if condition {} }; + html! { if condition {
    } }; + html! { if condition {
    } }; + html! { if condition { <>
    } }; + html! { if condition { { html! {} } } }; + html! { if condition { { { let _x = 42; html! {} } } } }; + html! { if condition {} else {} }; + html! { if condition {} else if condition {} }; + html! { if condition {} else if condition {} else {} }; + html! { if let Some(text) = Some("text") { { text } } }; + html! { <>
    if condition {}
    }; + html! {
    if condition {}
    }; } fn main() {} diff --git a/packages/yew-macro/tests/html_macro/html-node-pass.rs b/packages/yew-macro/tests/html_macro/html-node-pass.rs index 18c2655f1ab..a8c9dd1f7b3 100644 --- a/packages/yew-macro/tests/html_macro/html-node-pass.rs +++ b/packages/yew-macro/tests/html_macro/html-node-pass.rs @@ -37,23 +37,23 @@ pub struct u8; pub struct usize; fn compile_pass() { - (::yew::html! { "" }).unwrap(); - (::yew::html! { 'a' }).unwrap(); - (::yew::html! { "hello" }).unwrap(); - (::yew::html! { 42 }).unwrap(); - (::yew::html! { 1.234 }).unwrap(); + ::yew::html! { "" }; + ::yew::html! { 'a' }; + ::yew::html! { "hello" }; + ::yew::html! { 42 }; + ::yew::html! { 1.234 }; - (::yew::html! { { "" } }).unwrap(); - (::yew::html! { { 'a' } }).unwrap(); - (::yew::html! { { "hello" } }).unwrap(); - (::yew::html! { { 42 } }).unwrap(); - (::yew::html! { { 1.234 } }).unwrap(); + ::yew::html! { { "" } }; + ::yew::html! { { 'a' } }; + ::yew::html! { { "hello" } }; + ::yew::html! { { 42 } }; + ::yew::html! { { 1.234 } }; - (::yew::html! { ::std::format!("Hello") }).unwrap(); - (::yew::html! { {<::std::string::String as ::std::convert::From<&::std::primitive::str>>::from("Hello") } }).unwrap(); + ::yew::html! { ::std::format!("Hello") }; + ::yew::html! { {<::std::string::String as ::std::convert::From<&::std::primitive::str>>::from("Hello") } }; let msg = "Hello"; - (::yew::html! { msg }).unwrap(); + ::yew::html! { msg }; } fn main() {} diff --git a/packages/yew-macro/tests/html_macro/iterable-fail.stderr b/packages/yew-macro/tests/html_macro/iterable-fail.stderr index 081e7e61e18..c3000e923c6 100644 --- a/packages/yew-macro/tests/html_macro/iterable-fail.stderr +++ b/packages/yew-macro/tests/html_macro/iterable-fail.stderr @@ -25,47 +25,44 @@ error[E0277]: `()` is not an iterator = note: required by `into_iter` error[E0277]: `()` doesn't implement `std::fmt::Display` - --> $DIR/iterable-fail.rs:7:17 - | -7 | html! { for Vec::<()>::new().into_iter() }; - | ^^^ `()` cannot be formatted with the default formatter - | - = help: the trait `std::fmt::Display` is not implemented for `()` - = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead - = note: required because of the requirements on the impl of `ToString` for `()` - = note: required because of the requirements on the impl of `From<()>` for `VNode` - = note: required because of the requirements on the impl of `Into` for `()` - = note: required because of the requirements on the impl of `TryIntoNode<(), VNode>` for `()` - = note: required by `try_into_node` + --> $DIR/iterable-fail.rs:7:17 + | +7 | html! { for Vec::<()>::new().into_iter() }; + | ^^^ `()` cannot be formatted with the default formatter + | + = help: the trait `std::fmt::Display` is not implemented for `()` + = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead + = note: required because of the requirements on the impl of `ToString` for `()` + = note: required because of the requirements on the impl of `From<()>` for `VNode` + = note: required because of the requirements on the impl of `Into` for `()` + = note: required because of the requirements on the impl of `FromIterator<()>` for `VNode` error[E0277]: `()` doesn't implement `std::fmt::Display` - --> $DIR/iterable-fail.rs:10:17 - | -10 | html! { for empty }; - | ^^^^^ `()` cannot be formatted with the default formatter - | - = help: the trait `std::fmt::Display` is not implemented for `()` - = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead - = note: required because of the requirements on the impl of `ToString` for `()` - = note: required because of the requirements on the impl of `From<()>` for `VNode` - = note: required because of the requirements on the impl of `Into` for `()` - = note: required because of the requirements on the impl of `TryIntoNode<(), VNode>` for `()` - = note: required by `try_into_node` + --> $DIR/iterable-fail.rs:10:17 + | +10 | html! { for empty }; + | ^^^^^ `()` cannot be formatted with the default formatter + | + = help: the trait `std::fmt::Display` is not implemented for `()` + = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead + = note: required because of the requirements on the impl of `ToString` for `()` + = note: required because of the requirements on the impl of `From<()>` for `VNode` + = note: required because of the requirements on the impl of `Into` for `()` + = note: required because of the requirements on the impl of `FromIterator<()>` for `VNode` error[E0277]: `()` doesn't implement `std::fmt::Display` - --> $DIR/iterable-fail.rs:13:17 - | -13 | html! { for empty.iter() }; - | ^^^^^ `()` cannot be formatted with the default formatter - | - = help: the trait `std::fmt::Display` is not implemented for `()` - = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead - = note: required because of the requirements on the impl of `std::fmt::Display` for `&()` - = note: required because of the requirements on the impl of `ToString` for `&()` - = note: required because of the requirements on the impl of `From<&()>` for `VNode` - = note: required because of the requirements on the impl of `Into` for `&()` - = note: required because of the requirements on the impl of `TryIntoNode<&(), VNode>` for `&()` - = note: required by `try_into_node` + --> $DIR/iterable-fail.rs:13:17 + | +13 | html! { for empty.iter() }; + | ^^^^^ `()` cannot be formatted with the default formatter + | + = help: the trait `std::fmt::Display` is not implemented for `()` + = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead + = note: required because of the requirements on the impl of `std::fmt::Display` for `&()` + = note: required because of the requirements on the impl of `ToString` for `&()` + = note: required because of the requirements on the impl of `From<&()>` for `VNode` + = note: required because of the requirements on the impl of `Into` for `&()` + = note: required because of the requirements on the impl of `FromIterator<&()>` for `VNode` error[E0277]: `()` is not an iterator --> $DIR/iterable-fail.rs:18:19 @@ -73,6 +70,10 @@ error[E0277]: `()` is not an iterator 18 | { for () } | ^^ `()` is not an iterator | + ::: $WORKSPACE/packages/yew/src/utils/mod.rs + | + | IT: IntoIterator, + | ---------------------- required by this bound in `into_node_iter` + | = help: the trait `Iterator` is not implemented for `()` = note: required because of the requirements on the impl of `IntoIterator` for `()` - = note: required by `into_iter` diff --git a/packages/yew-macro/tests/html_macro/iterable-pass.rs b/packages/yew-macro/tests/html_macro/iterable-pass.rs index 2cf0bf97e1b..18eadf7b042 100644 --- a/packages/yew-macro/tests/html_macro/iterable-pass.rs +++ b/packages/yew-macro/tests/html_macro/iterable-pass.rs @@ -45,13 +45,13 @@ fn empty_iter() -> impl ::std::iter::Iterator { } fn main() { - (::yew::html! { for empty_iter() }).unwrap(); - (::yew::html! { for { empty_iter() } }).unwrap(); + ::yew::html! { for empty_iter() }; + ::yew::html! { for { empty_iter() } }; let empty = empty_vec(); - (::yew::html! { for empty }).unwrap(); + ::yew::html! { for empty }; - (::yew::html! { for empty_vec() }).unwrap(); - (::yew::html! { for ::std::iter::IntoIterator::into_iter(empty_vec()) }).unwrap(); - ( ::yew::html! { for ::std::iter::Iterator::map(0..3, |num| { ::yew::html! { { num } } }) } ).unwrap(); + ::yew::html! { for empty_vec() }; + ::yew::html! { for ::std::iter::IntoIterator::into_iter(empty_vec()) }; + ::yew::html! { for ::std::iter::Iterator::map(0..3, |num| { ::yew::html! { { num } } }) }; } diff --git a/packages/yew-macro/tests/html_macro/list-pass.rs b/packages/yew-macro/tests/html_macro/list-pass.rs index f2ed4df5d22..3cbe619eaeb 100644 --- a/packages/yew-macro/tests/html_macro/list-pass.rs +++ b/packages/yew-macro/tests/html_macro/list-pass.rs @@ -37,24 +37,22 @@ pub struct u8; pub struct usize; fn main() { - (::yew::html! {}).unwrap(); - (::yew::html! { <> }).unwrap(); - (::yew::html! { + ::yew::html! {}; + ::yew::html! { <> }; + ::yew::html! { <> <> <> - }) - .unwrap(); - (::yew::html! { + }; + ::yew::html! { - }) - .unwrap(); + }; let children = ::std::vec![ ::yew::html! { { "Hello" } }, ::yew::html! { { "World" } }, ]; - (::yew::html! { <>{ children } }).unwrap(); + ::yew::html! { <>{ children } }; } diff --git a/packages/yew-macro/tests/html_macro/node-fail.stderr b/packages/yew-macro/tests/html_macro/node-fail.stderr index 8f67cd7ad74..751f9caf70e 100644 --- a/packages/yew-macro/tests/html_macro/node-fail.stderr +++ b/packages/yew-macro/tests/html_macro/node-fail.stderr @@ -50,9 +50,7 @@ error[E0277]: `()` doesn't implement `std::fmt::Display` = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead = note: required because of the requirements on the impl of `ToString` for `()` = note: required because of the requirements on the impl of `From<()>` for `VNode` - = note: required because of the requirements on the impl of `Into` for `()` - = note: required because of the requirements on the impl of `TryIntoNode<(), VNode>` for `()` - = note: required by `try_into_node` + = note: required by `from` error[E0277]: `()` doesn't implement `std::fmt::Display` --> $DIR/node-fail.rs:17:9 @@ -64,6 +62,4 @@ error[E0277]: `()` doesn't implement `std::fmt::Display` = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead = note: required because of the requirements on the impl of `ToString` for `()` = note: required because of the requirements on the impl of `From<()>` for `VNode` - = note: required because of the requirements on the impl of `Into` for `()` - = note: required because of the requirements on the impl of `TryIntoNode<(), VNode>` for `()` - = note: required by `try_into_node` + = note: required by `from` diff --git a/packages/yew-macro/tests/html_macro/node-pass.rs b/packages/yew-macro/tests/html_macro/node-pass.rs index d40e70c6254..1dfaea9c8a1 100644 --- a/packages/yew-macro/tests/html_macro/node-pass.rs +++ b/packages/yew-macro/tests/html_macro/node-pass.rs @@ -37,23 +37,23 @@ pub struct u8; pub struct usize; fn main() { - (::yew::html! { "" }).unwrap(); - (::yew::html! { 'a' }).unwrap(); - (::yew::html! { "hello" }).unwrap(); - (::yew::html! { "42" }).unwrap(); - (::yew::html! { "1.234" }).unwrap(); - (::yew::html! { "true" }).unwrap(); + ::yew::html! { "" }; + ::yew::html! { 'a' }; + ::yew::html! { "hello" }; + ::yew::html! { "42" }; + ::yew::html! { "1.234" }; + ::yew::html! { "true" }; - (::yew::html! { { "" } }).unwrap(); - (::yew::html! { { 'a' } }).unwrap(); - (::yew::html! { { "hello" } }).unwrap(); - (::yew::html! { { "42" } }).unwrap(); - (::yew::html! { { "1.234" } }).unwrap(); - (::yew::html! { { "true" } }).unwrap(); + ::yew::html! { { "" } }; + ::yew::html! { { 'a' } }; + ::yew::html! { { "hello" } }; + ::yew::html! { { "42" } }; + ::yew::html! { { "1.234" } }; + ::yew::html! { { "true" } }; - (::yew::html! { ::std::format!("Hello") }).unwrap(); - (::yew::html! { ::std::string::ToString::to_string("Hello") }).unwrap(); + ::yew::html! { ::std::format!("Hello") }; + ::yew::html! { ::std::string::ToString::to_string("Hello") }; let msg = "Hello"; - (::yew::html! { msg }).unwrap(); + ::yew::html! { msg }; } diff --git a/packages/yew-macro/tests/html_macro/svg-pass.rs b/packages/yew-macro/tests/html_macro/svg-pass.rs index bddfe1e398f..eb1bb3a0963 100644 --- a/packages/yew-macro/tests/html_macro/svg-pass.rs +++ b/packages/yew-macro/tests/html_macro/svg-pass.rs @@ -39,18 +39,17 @@ pub struct usize; fn main() { // Ensure Rust keywords can be used as element names. // See: #1771 - (::yew::html! { + ::yew::html! { {"Go to example.org"} - }) - .unwrap(); + }; // some general SVG - ( ::yew::html! { + ::yew::html! { @@ -66,5 +65,5 @@ fn main() { - }).unwrap(); + }; } diff --git a/packages/yew-macro/tests/html_macro_test.rs b/packages/yew-macro/tests/html_macro_test.rs index 75b5843f75e..ca19982635c 100644 --- a/packages/yew-macro/tests/html_macro_test.rs +++ b/packages/yew-macro/tests/html_macro_test.rs @@ -1,4 +1,4 @@ -use yew::html; +use yew::{html, html_nested}; #[allow(dead_code)] #[rustversion::attr(stable(1.51), test)] @@ -14,19 +14,29 @@ fn html_macro() { expected = "a dynamic tag tried to create a `
    ` tag with children. `
    ` is a void element which can't have any children." )] fn dynamic_tags_catch_void_elements() { - (html! { + html! { <@{"br"}> { "No children allowed" } - }) - .unwrap(); + }; } #[test] #[should_panic(expected = "a dynamic tag returned a tag name containing non ASCII characters: `❤`")] fn dynamic_tags_catch_non_ascii() { - (html! { + html! { <@{"❤"}/> - }) - .unwrap(); + }; +} + +/// test that compilation on html elements pass +/// fixes: https://github.com/yewstack/yew/issues/2268 +#[test] +fn html_nested_macro_on_html_element() { + let _node = html_nested! { +
    + }; + let _node = html_nested! { + + }; } diff --git a/packages/yew-macro/tests/props_macro/props-pass.rs b/packages/yew-macro/tests/props_macro/props-pass.rs index 1dd62c89dca..d5b91abe0cd 100644 --- a/packages/yew-macro/tests/props_macro/props-pass.rs +++ b/packages/yew-macro/tests/props_macro/props-pass.rs @@ -43,10 +43,20 @@ struct Props { b: ::std::primitive::usize, } +#[derive(::yew::Properties, ::std::cmp::PartialEq)] +pub struct RawIdentProps { + r#true: ::std::primitive::usize, + #[prop_or_default] + r#pointless_raw_name: ::std::primitive::usize, +} + fn compile_pass() { ::yew::props!(Props { a: 5 }); let (a, b) = (3, 5); ::yew::props!(Props { a, b }); + ::yew::props!(RawIdentProps { r#true: 5 }); + let (r#true, r#pointless_raw_name) = (3, 5); + ::yew::props!(RawIdentProps { r#true, r#pointless_raw_name }); } fn main() {} diff --git a/packages/yew/src/functional/mod.rs b/packages/yew/src/functional/mod.rs index 37825d87685..338440c9a10 100644 --- a/packages/yew/src/functional/mod.rs +++ b/packages/yew/src/functional/mod.rs @@ -13,7 +13,7 @@ //! //! More details about function components and Hooks can be found on [Yew Docs](https://yew.rs/docs/next/concepts/function-components/introduction) -use crate::html::{AnyScope, BaseComponent}; +use crate::html::{AnyScope, BaseComponent, HtmlResult}; use crate::{Html, Properties}; use scoped_tls_hkt::scoped_thread_local; use std::cell::RefCell; @@ -141,8 +141,8 @@ where true } - fn view(&self, ctx: &Context) -> Html { - self.with_hook_state(|| T::run(&*ctx.props())) + fn view(&self, ctx: &Context) -> HtmlResult { + self.with_hook_state(|| T::run(&*ctx.props())).into() } fn rendered(&mut self, ctx: &Context, _first_render: bool) { diff --git a/packages/yew/src/html/component/children.rs b/packages/yew/src/html/component/children.rs index 2e3abdfd6e8..c27c3ee2007 100644 --- a/packages/yew/src/html/component/children.rs +++ b/packages/yew/src/html/component/children.rs @@ -1,6 +1,5 @@ //! Component children module -use crate::html::{Html, IntoPropValue}; use crate::virtual_dom::{VChild, VNode}; use std::fmt; @@ -60,12 +59,6 @@ use std::fmt; /// ``` pub type Children = ChildrenRenderer; -impl IntoPropValue for Html { - fn into_prop_value(self) -> Children { - Children::new(vec![self.expect("Children must not be an RenderError.")]) - } -} - /// A type used for accepting children elements in Component::Properties and accessing their props. /// /// # Example diff --git a/packages/yew/src/html/component/mod.rs b/packages/yew/src/html/component/mod.rs index 124a5167943..4a43faf363f 100644 --- a/packages/yew/src/html/component/mod.rs +++ b/packages/yew/src/html/component/mod.rs @@ -5,7 +5,7 @@ mod lifecycle; mod properties; mod scope; -use super::Html; +use super::{Html, HtmlResult}; pub use children::*; pub use properties::*; pub(crate) use scope::Scoped; @@ -54,7 +54,7 @@ pub trait BaseComponent: Sized + 'static { fn changed(&mut self, ctx: &Context) -> bool; /// Returns a component layout to be rendered. - fn view(&self, ctx: &Context) -> Html; + fn view(&self, ctx: &Context) -> HtmlResult; /// Notified after a layout is rendered. fn rendered(&mut self, ctx: &Context, first_render: bool); @@ -145,8 +145,8 @@ where Component::changed(self, ctx) } - fn view(&self, ctx: &Context) -> Html { - Component::view(self, ctx) + fn view(&self, ctx: &Context) -> HtmlResult { + Component::view(self, ctx).into() } fn rendered(&mut self, ctx: &Context, first_render: bool) { diff --git a/packages/yew/src/html/mod.rs b/packages/yew/src/html/mod.rs index 430496fe5c3..d17dc54b507 100644 --- a/packages/yew/src/html/mod.rs +++ b/packages/yew/src/html/mod.rs @@ -19,18 +19,15 @@ use wasm_bindgen::JsValue; use web_sys::{Element, Node}; /// A type which expected as a result of `view` function implementation. -pub type Html = RenderResult; +pub type Html = VNode; -/// A trait to provide a default value for [`Html`]. -pub trait HtmlDefault { - /// Returns the “default value” for a type. - /// Default values are often some kind of initial value, identity value, or anything else that may make sense as a default. - fn default() -> Self; -} +/// An enhanced type of `Html` returned in suspendible function components. +pub type HtmlResult = RenderResult; -impl HtmlDefault for Html { - fn default() -> Self { - Ok(VNode::default()) +impl Into for Html { + #[inline(always)] + fn into(self) -> HtmlResult { + Ok(self) } } @@ -156,7 +153,7 @@ impl NodeRef { /// ## Relevant examples /// - [Portals](https://github.com/yewstack/yew/tree/master/examples/portals) pub fn create_portal(child: Html, host: Element) -> Html { - Ok(VNode::VPortal(VPortal::new(child?, host))) + VNode::VPortal(VPortal::new(child, host)) } #[cfg(test)] diff --git a/packages/yew/src/lib.rs b/packages/yew/src/lib.rs index 6c813a1fe98..2d44be2d3c2 100644 --- a/packages/yew/src/lib.rs +++ b/packages/yew/src/lib.rs @@ -389,7 +389,7 @@ pub mod prelude { pub use crate::context::ContextProvider; pub use crate::events::*; pub use crate::html::{ - create_portal, Children, ChildrenWithProps, Classes, Component, Context, Html, HtmlDefault, + create_portal, Children, ChildrenWithProps, Classes, Component, Context, Html, HtmlResult, NodeRef, Properties, }; pub use crate::macros::{classes, html, html_nested}; diff --git a/packages/yew/src/suspense/component.rs b/packages/yew/src/suspense/component.rs index 7fbeda4ec82..ecc004aa6d4 100644 --- a/packages/yew/src/suspense/component.rs +++ b/packages/yew/src/suspense/component.rs @@ -84,7 +84,7 @@ impl Component for Suspense { ctx.props().key.clone(), ); - Ok(VNode::from(vsuspense)) + VNode::from(vsuspense) } } diff --git a/packages/yew/src/utils/mod.rs b/packages/yew/src/utils/mod.rs index fd8b1d19a4f..29e9a8874f0 100644 --- a/packages/yew/src/utils/mod.rs +++ b/packages/yew/src/utils/mod.rs @@ -2,170 +2,50 @@ use std::marker::PhantomData; use yew::html::ChildrenRenderer; -use yew::html::RenderResult; -/// A special type necessary for flattening components returned from nested html macros. -#[derive(Debug)] -pub struct Node(O, PhantomData); - -impl Node { - /// Returns the wrapped value. - pub fn into_value(self) -> O { - self.0 - } -} - -/// A special trait to convert to a `RenderResult`. -pub trait TryIntoNode { - /// Performs the conversion. - fn try_into_node(self) -> RenderResult>; -} - -impl TryIntoNode for I +/// Map IntoIterator> to Iterator +pub fn into_node_iter(it: IT) -> impl Iterator where - I: Into, + IT: IntoIterator, + T: Into, { - fn try_into_node(self) -> RenderResult> { - Ok(Node(self.into(), PhantomData::default())) - } -} - -impl TryIntoNode for RenderResult -where - I: Into, -{ - fn try_into_node(self) -> RenderResult> { - Ok(Node(self?.into(), PhantomData::default())) - } + it.into_iter().map(|n| n.into()) } /// A special type necessary for flattening components returned from nested html macros. #[derive(Debug)] -pub struct NodeSeq(Vec, PhantomData); - -impl IntoIterator for NodeSeq { - type Item = O; - type IntoIter = std::vec::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} - -/// A special trait to convert to a `NodeSeq`. -pub trait TryIntoNodeSeq { - /// Performs the conversion. - fn try_into_node_seq(self) -> RenderResult>; -} - -impl TryIntoNodeSeq for I -where - I: Into, -{ - fn try_into_node_seq(self) -> RenderResult> { - Ok(NodeSeq(vec![self.into()], PhantomData::default())) - } -} - -impl TryIntoNodeSeq for Vec -where - I: Into, -{ - fn try_into_node_seq(self) -> RenderResult> { - Ok(NodeSeq( - self.into_iter().map(|x| x.into()).collect(), - PhantomData::default(), - )) - } -} +pub struct NodeSeq(Vec, PhantomData); -impl TryIntoNodeSeq for ChildrenRenderer -where - I: Into, -{ - fn try_into_node_seq(self) -> RenderResult> { - Ok(NodeSeq( - self.into_iter().map(|x| x.into()).collect(), - PhantomData::default(), - )) - } -} - -impl TryIntoNodeSeq for RenderResult -where - I: Into, -{ - fn try_into_node_seq(self) -> RenderResult> { - Ok(NodeSeq(vec![self?.into()], PhantomData::default())) +impl, OUT> From for NodeSeq { + fn from(val: IN) -> Self { + Self(vec![val.into()], PhantomData::default()) } } -impl TryIntoNodeSeq for RenderResult> -where - I: Into, -{ - fn try_into_node_seq(self) -> RenderResult> { - Ok(NodeSeq( - self?.into_iter().map(|x| x.into()).collect(), +impl, OUT> From> for NodeSeq { + fn from(val: Vec) -> Self { + Self( + val.into_iter().map(|x| x.into()).collect(), PhantomData::default(), - )) + ) } } -impl TryIntoNodeSeq for Vec> -where - I: Into, -{ - fn try_into_node_seq(self) -> RenderResult> { - let mut nodes = Vec::new(); - - for node in self { - nodes.push(node?.into()); - } - - Ok(NodeSeq(nodes, PhantomData::default())) - } -} - -impl TryIntoNodeSeq for RenderResult> -where - I: Into, -{ - fn try_into_node_seq(self) -> RenderResult> { - Ok(NodeSeq( - self?.into_iter().map(|x| x.into()).collect(), +impl, OUT> From> for NodeSeq { + fn from(val: ChildrenRenderer) -> Self { + Self( + val.into_iter().map(|x| x.into()).collect(), PhantomData::default(), - )) - } -} - -impl TryIntoNodeSeq for ChildrenRenderer> -where - I: Into, -{ - fn try_into_node_seq(self) -> RenderResult> { - let mut nodes = Vec::new(); - - for node in self { - nodes.push(node?.into()); - } - - Ok(NodeSeq(nodes, PhantomData::default())) + ) } } -impl TryIntoNodeSeq for RenderResult>> -where - I: Into, -{ - fn try_into_node_seq(self) -> RenderResult> { - let mut nodes = Vec::new(); - - for node in self? { - nodes.push(node?.into()); - } +impl IntoIterator for NodeSeq { + type Item = OUT; + type IntoIter = std::vec::IntoIter; - Ok(NodeSeq(nodes, PhantomData::default())) + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() } } From a54c06295e13da8875cfe7df4b37ef2e078ad515 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Mon, 20 Dec 2021 20:49:29 +0900 Subject: [PATCH 16/27] Suspendible Function Component. --- Cargo.toml | 2 +- .../function_todomvc/src/components/entry.rs | 2 +- .../function_todomvc/src/components/filter.rs | 2 +- .../src/components/header_input.rs | 2 +- .../src/components/info_footer.rs | 2 +- examples/function_todomvc/src/main.rs | 2 +- examples/suspense/src/main.rs | 6 +- examples/todomvc/Cargo.toml | 0 examples/todomvc/README.md | 0 examples/todomvc/index.html | 0 examples/todomvc/src/main.rs | 0 examples/todomvc/src/state.rs | 0 packages/yew-macro/src/function_component.rs | 94 ++++++++++++++----- packages/yew/src/functional/mod.rs | 6 +- packages/yew/src/html/component/children.rs | 3 +- packages/yew/src/suspense/component.rs | 22 ++--- 16 files changed, 93 insertions(+), 50 deletions(-) mode change 100644 => 100755 examples/todomvc/Cargo.toml mode change 100644 => 100755 examples/todomvc/README.md mode change 100644 => 100755 examples/todomvc/index.html mode change 100644 => 100755 examples/todomvc/src/main.rs mode change 100644 => 100755 examples/todomvc/src/state.rs diff --git a/Cargo.toml b/Cargo.toml index 5054a8a3973..7d9485c079f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,7 @@ members = [ "examples/two_apps", "examples/webgl", "examples/web_worker_fib", - # "examples/suspense", + "examples/suspense", # Release tools "packages/changelog", diff --git a/examples/function_todomvc/src/components/entry.rs b/examples/function_todomvc/src/components/entry.rs index 79104a6af4d..a366d511508 100644 --- a/examples/function_todomvc/src/components/entry.rs +++ b/examples/function_todomvc/src/components/entry.rs @@ -2,7 +2,7 @@ use crate::hooks::use_bool_toggle::use_bool_toggle; use crate::state::Entry as Item; use web_sys::{HtmlInputElement, MouseEvent}; use yew::events::{Event, FocusEvent, KeyboardEvent}; -use yew::{function_component, html, Callback, Classes, Properties, TargetCast}; +use yew::prelude::*; #[derive(PartialEq, Properties, Clone)] pub struct EntryProps { diff --git a/examples/function_todomvc/src/components/filter.rs b/examples/function_todomvc/src/components/filter.rs index 469572ed9b0..ae421bb7c9c 100644 --- a/examples/function_todomvc/src/components/filter.rs +++ b/examples/function_todomvc/src/components/filter.rs @@ -1,5 +1,5 @@ use crate::state::Filter as FilterEnum; -use yew::{function_component, html, Callback, Properties}; +use yew::prelude::*; #[derive(PartialEq, Properties)] pub struct FilterProps { diff --git a/examples/function_todomvc/src/components/header_input.rs b/examples/function_todomvc/src/components/header_input.rs index d01061b6be2..2cecf3b94ba 100644 --- a/examples/function_todomvc/src/components/header_input.rs +++ b/examples/function_todomvc/src/components/header_input.rs @@ -1,6 +1,6 @@ use web_sys::HtmlInputElement; use yew::events::KeyboardEvent; -use yew::{function_component, html, Callback, Properties, TargetCast}; +use yew::prelude::*; #[derive(PartialEq, Properties, Clone)] pub struct HeaderInputProps { diff --git a/examples/function_todomvc/src/components/info_footer.rs b/examples/function_todomvc/src/components/info_footer.rs index 9c70c6a0074..6b5ac009066 100644 --- a/examples/function_todomvc/src/components/info_footer.rs +++ b/examples/function_todomvc/src/components/info_footer.rs @@ -1,4 +1,4 @@ -use yew::{function_component, html}; +use yew::prelude::*; #[function_component(InfoFooter)] pub fn info_footer() -> Html { diff --git a/examples/function_todomvc/src/main.rs b/examples/function_todomvc/src/main.rs index 644b138ca9d..434f35caea7 100644 --- a/examples/function_todomvc/src/main.rs +++ b/examples/function_todomvc/src/main.rs @@ -1,7 +1,7 @@ use gloo::storage::{LocalStorage, Storage}; use state::{Action, Filter, State}; use strum::IntoEnumIterator; -use yew::{classes, function_component, html, use_effect_with_deps, use_reducer, Callback}; +use yew::prelude::*; mod components; mod hooks; diff --git a/examples/suspense/src/main.rs b/examples/suspense/src/main.rs index 34a9bdc3312..0cc9d1d751f 100644 --- a/examples/suspense/src/main.rs +++ b/examples/suspense/src/main.rs @@ -13,7 +13,7 @@ fn please_wait() -> Html { } #[function_component(AppContent)] -fn app_content() -> Html { +fn app_content() -> HtmlResult { let resleep = use_sleep()?; let value = use_state(|| "I am writing a long story...".to_string()); @@ -30,7 +30,7 @@ fn app_content() -> Html { let on_take_a_break = Callback::from(move |_| (resleep.clone())()); - html! { + Ok(html! {
    @@ -38,7 +38,7 @@ fn app_content() -> Html {
    {"You can take a break at anytime"}
    {"and your work will be preserved."}
    - } + }) } #[function_component(App)] diff --git a/examples/todomvc/Cargo.toml b/examples/todomvc/Cargo.toml old mode 100644 new mode 100755 diff --git a/examples/todomvc/README.md b/examples/todomvc/README.md old mode 100644 new mode 100755 diff --git a/examples/todomvc/index.html b/examples/todomvc/index.html old mode 100644 new mode 100755 diff --git a/examples/todomvc/src/main.rs b/examples/todomvc/src/main.rs old mode 100644 new mode 100755 diff --git a/examples/todomvc/src/state.rs b/examples/todomvc/src/state.rs old mode 100644 new mode 100755 diff --git a/packages/yew-macro/src/function_component.rs b/packages/yew-macro/src/function_component.rs index fb6f7f146fb..12a947f52d1 100644 --- a/packages/yew-macro/src/function_component.rs +++ b/packages/yew-macro/src/function_component.rs @@ -1,20 +1,23 @@ -use proc_macro2::TokenStream; -use quote::{quote, quote_spanned, ToTokens}; +use proc_macro2::{Span, TokenStream}; +use quote::{quote, ToTokens}; // , quote_spanned use syn::parse::{Parse, ParseStream}; use syn::punctuated::Punctuated; -use syn::spanned::Spanned; +// use syn::spanned::Spanned; use syn::token::Comma; -use syn::{Attribute, Block, FnArg, Generics, Ident, Item, ItemFn, ReturnType, Type, Visibility}; +use syn::{Attribute, FnArg, Generics, Ident, Item, ItemFn, ReturnType, Type, Visibility}; // Block pub struct FunctionComponent { - block: Box, + // block: Box, props_type: Box, - arg: FnArg, + // arg: FnArg, generics: Generics, vis: Visibility, attrs: Vec, name: Ident, - return_type: Box, + // return_type: Box, + func: ItemFn, + + has_arg: bool, } impl Parse for FunctionComponent { @@ -27,8 +30,9 @@ impl Parse for FunctionComponent { attrs, vis, sig, - block, - } = func; + // block, + .. + } = func.clone(); if sig.generics.lifetimes().next().is_some() { return Err(syn::Error::new_spanned( @@ -58,20 +62,21 @@ impl Parse for FunctionComponent { )); } - let return_type = match sig.output { + let _return_type = match sig.output { ReturnType::Default => { return Err(syn::Error::new_spanned( sig, - "function components must return `yew::Html`", + "function components must return `yew::Html` or `yew::HtmlResult`", )) } ReturnType::Type(_, ty) => ty, }; let mut inputs = sig.inputs.into_iter(); - let arg: FnArg = inputs + let (arg, has_arg) = inputs .next() - .unwrap_or_else(|| syn::parse_quote! { _: &() }); + .map(|m| (m, true)) + .unwrap_or_else(|| (syn::parse_quote! { _: &() }, false)); let ty = match &arg { FnArg::Typed(arg) => match &*arg.ty { @@ -122,13 +127,15 @@ impl Parse for FunctionComponent { Ok(Self { props_type: ty, - block, - arg, + // block, + // arg, generics: sig.generics, vis, attrs, name: sig.ident, - return_type, + // return_type, + func, + has_arg, }) } item => Err(syn::Error::new_spanned( @@ -162,14 +169,16 @@ pub fn function_component_impl( let FunctionComponentName { component_name } = name; let FunctionComponent { - block, + // block, props_type, - arg, + // arg, generics, vis, attrs, name: function_name, - return_type, + // return_type, + func, + has_arg, } = component; let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); @@ -181,32 +190,65 @@ pub fn function_component_impl( )); } - let ret_type = quote_spanned!(return_type.span()=> ::yew::html::Html); + let mut provider_name_str = component_name.to_string(); + + if provider_name_str.ends_with("Internal") { + // When a component ends with Internal, it will become possessive. + // InternalsInternal instead of InternalInternal + provider_name_str.push_str("sInternal"); + } else { + provider_name_str.push_str("Internal"); + } + + let provider_name = Ident::new(&provider_name_str, Span::mixed_site()); + + // let ret_type = quote_spanned!(return_type.span()=> ::yew::html::Html); let phantom_generics = generics .type_params() .map(|ty_param| ty_param.ident.clone()) // create a new Punctuated sequence without any type bounds .collect::>(); + let run_impl = if has_arg { + let provider_props = Ident::new("props", Span::mixed_site()); + + quote! { + fn run(#provider_props: &Self::TProps) -> ::yew::html::HtmlResult { + #func + + #function_name(#provider_props).into() + } + } + } else { + let provider_props = Ident::new("_props", Span::mixed_site()); + + quote! { + fn run(#provider_props: &Self::TProps) -> ::yew::html::HtmlResult { + #func + + #function_name().into() + } + } + }; + let quoted = quote! { #[doc(hidden)] #[allow(non_camel_case_types)] #[allow(unused_parens)] - #vis struct #function_name #impl_generics { + #vis struct #provider_name #impl_generics { _marker: ::std::marker::PhantomData<(#phantom_generics)>, } - impl #impl_generics ::yew::functional::FunctionProvider for #function_name #ty_generics #where_clause { + #[automatically_derived] + impl #impl_generics ::yew::functional::FunctionProvider for #provider_name #ty_generics #where_clause { type TProps = #props_type; - fn run(#arg) -> #ret_type { - #block - } + #run_impl } #(#attrs)* #[allow(type_alias_bounds)] - #vis type #component_name #impl_generics = ::yew::functional::FunctionComponent<#function_name #ty_generics>; + #vis type #component_name #impl_generics = ::yew::functional::FunctionComponent<#provider_name #ty_generics>; }; Ok(quoted) diff --git a/packages/yew/src/functional/mod.rs b/packages/yew/src/functional/mod.rs index 338440c9a10..f93e69a4bd1 100644 --- a/packages/yew/src/functional/mod.rs +++ b/packages/yew/src/functional/mod.rs @@ -14,7 +14,7 @@ //! More details about function components and Hooks can be found on [Yew Docs](https://yew.rs/docs/next/concepts/function-components/introduction) use crate::html::{AnyScope, BaseComponent, HtmlResult}; -use crate::{Html, Properties}; +use crate::Properties; use scoped_tls_hkt::scoped_thread_local; use std::cell::RefCell; use std::fmt; @@ -73,7 +73,7 @@ pub trait FunctionProvider { /// Render the component. This function returns the [`Html`] to be rendered for the component. /// /// Equivalent of [`Component::view`]. - fn run(props: &Self::TProps) -> Html; + fn run(props: &Self::TProps) -> HtmlResult; } /// Wrapper that allows a struct implementing [`FunctionProvider`] to be consumed as a component. @@ -142,7 +142,7 @@ where } fn view(&self, ctx: &Context) -> HtmlResult { - self.with_hook_state(|| T::run(&*ctx.props())).into() + self.with_hook_state(|| T::run(&*ctx.props())) } fn rendered(&mut self, ctx: &Context, _first_render: bool) { diff --git a/packages/yew/src/html/component/children.rs b/packages/yew/src/html/component/children.rs index c27c3ee2007..c0fbd1d858d 100644 --- a/packages/yew/src/html/component/children.rs +++ b/packages/yew/src/html/component/children.rs @@ -1,5 +1,6 @@ //! Component children module +use crate::html::Html; use crate::virtual_dom::{VChild, VNode}; use std::fmt; @@ -57,7 +58,7 @@ use std::fmt; /// } /// } /// ``` -pub type Children = ChildrenRenderer; +pub type Children = ChildrenRenderer; /// A type used for accepting children elements in Component::Properties and accessing their props. /// diff --git a/packages/yew/src/suspense/component.rs b/packages/yew/src/suspense/component.rs index ecc004aa6d4..750f2d9f78b 100644 --- a/packages/yew/src/suspense/component.rs +++ b/packages/yew/src/suspense/component.rs @@ -6,13 +6,13 @@ use web_sys::Element; use super::Suspension; -#[derive(Properties, PartialEq, Debug)] +#[derive(Properties, PartialEq, Debug, Clone)] pub struct SuspenseProps { #[prop_or_default] pub children: Children, #[prop_or_default] - pub fallback: Children, + pub fallback: Html, #[prop_or_default] pub key: Option, @@ -67,21 +67,21 @@ impl Component for Suspense { } fn view(&self, ctx: &Context) -> Html { - let children_vnode = VNode::from(VList::with_children( - ctx.props().children.clone().into_iter().collect(), - None, - )); - let fallback_vnode = VNode::from(VList::with_children( - ctx.props().fallback.clone().into_iter().collect(), - None, - )); + let SuspenseProps { + children, + fallback: fallback_vnode, + key, + } = (*ctx.props()).clone(); + + let children_vnode = + VNode::from(VList::with_children(children.into_iter().collect(), None)); let vsuspense = VSuspense::new( children_vnode, fallback_vnode, self.detached_parent.clone(), !self.suspensions.is_empty(), - ctx.props().key.clone(), + key, ); VNode::from(vsuspense) From af4e2f5e71ba8485dda9cd915cc14680fe827069 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Mon, 20 Dec 2021 21:22:17 +0900 Subject: [PATCH 17/27] Add a method to create suspension from futures. --- packages/yew/src/html/mod.rs | 6 +- packages/yew/src/suspense/suspension.rs | 14 ++ packages/yew/src/virtual_dom/key.rs | 3 +- packages/yew/src/virtual_dom/vcomp.rs | 87 ++++----- packages/yew/src/virtual_dom/vlist.rs | 247 ++++++------------------ packages/yew/src/virtual_dom/vportal.rs | 18 +- packages/yew/src/virtual_dom/vtag.rs | 49 ++--- packages/yew/src/virtual_dom/vtext.rs | 36 +--- packages/yew/tests/mod.rs | 8 +- packages/yew/tests/suspense.rs | 24 +-- packages/yew/tests/use_context.rs | 60 +++--- packages/yew/tests/use_effect.rs | 34 ++-- packages/yew/tests/use_reducer.rs | 12 +- packages/yew/tests/use_ref.rs | 8 +- packages/yew/tests/use_state.rs | 20 +- website/docs/concepts/suspense.md | 4 +- 16 files changed, 222 insertions(+), 408 deletions(-) diff --git a/packages/yew/src/html/mod.rs b/packages/yew/src/html/mod.rs index d17dc54b507..87a289aff37 100644 --- a/packages/yew/src/html/mod.rs +++ b/packages/yew/src/html/mod.rs @@ -24,10 +24,10 @@ pub type Html = VNode; /// An enhanced type of `Html` returned in suspendible function components. pub type HtmlResult = RenderResult; -impl Into for Html { +impl From for HtmlResult { #[inline(always)] - fn into(self) -> HtmlResult { - Ok(self) + fn from(m: Html) -> Self { + Ok(m) } } diff --git a/packages/yew/src/suspense/suspension.rs b/packages/yew/src/suspense/suspension.rs index f0d58f24a9e..ed41d3a54c6 100644 --- a/packages/yew/src/suspense/suspension.rs +++ b/packages/yew/src/suspense/suspension.rs @@ -1,8 +1,10 @@ use std::cell::RefCell; +use std::future::Future; use std::rc::Rc; use std::sync::atomic::{AtomicBool, Ordering}; use thiserror::Error; +use wasm_bindgen_futures::spawn_local; use crate::Callback; @@ -48,6 +50,18 @@ impl Suspension { (self_.clone(), SuspensionHandle { inner: self_ }) } + /// Creates a Suspension that resumes when the [`Future`] resolves. + pub fn from_future(f: impl Future + 'static) -> Self { + let (self_, handle) = Self::new(); + + spawn_local(async move { + f.await; + handle.resume(); + }); + + self_ + } + /// Returns `true` if the current suspension is already resumed. pub fn resumed(&self) -> bool { self.resumed.load(Ordering::Relaxed) diff --git a/packages/yew/src/virtual_dom/key.rs b/packages/yew/src/virtual_dom/key.rs index e2f81c561d6..ce093c1b949 100644 --- a/packages/yew/src/virtual_dom/key.rs +++ b/packages/yew/src/virtual_dom/key.rs @@ -98,7 +98,6 @@ mod test {

    - }) - .unwrap(); + }); } } diff --git a/packages/yew/src/virtual_dom/vcomp.rs b/packages/yew/src/virtual_dom/vcomp.rs index 190bcb7af9d..a90df8b2738 100644 --- a/packages/yew/src/virtual_dom/vcomp.rs +++ b/packages/yew/src/virtual_dom/vcomp.rs @@ -329,11 +329,11 @@ mod tests { let parent_scope: AnyScope = crate::html::Scope::::new(None).into(); let parent_element = document.create_element("div").unwrap(); - let mut ancestor = html! { }.unwrap(); + let mut ancestor = html! { }; ancestor.apply(&parent_scope, &parent_element, NodeRef::default(), None); for _ in 0..10000 { - let mut node = html! { }.unwrap(); + let mut node = html! { }; node.apply( &parent_scope, &parent_element, @@ -346,42 +346,37 @@ mod tests { #[test] fn set_properties_to_component() { - (html! { + html! { - }) - .unwrap(); + }; - (html! { + html! { - }) - .unwrap(); + }; - (html! { + html! { - }) - .unwrap(); + }; - (html! { + html! { - }) - .unwrap(); + }; let props = Props { field_1: 1, field_2: 1, }; - (html! { + html! { - }) - .unwrap(); + }; } #[test] fn set_component_key() { let test_key: Key = "test".to_string().into(); - let check_key = |vnode: Html| { - assert_eq!(vnode.unwrap().key().as_ref(), Some(&test_key)); + let check_key = |vnode: VNode| { + assert_eq!(vnode.key().as_ref(), Some(&test_key)); }; let props = Props { @@ -401,11 +396,8 @@ mod tests { fn set_component_node_ref() { let test_node: Node = document().create_text_node("test").into(); let test_node_ref = NodeRef::new(test_node); - let check_node_ref = |vnode: Html| { - assert_eq!( - vnode.unwrap().unchecked_first_node(), - test_node_ref.get().unwrap() - ); + let check_node_ref = |vnode: VNode| { + assert_eq!(vnode.unchecked_first_node(), test_node_ref.get().unwrap()); }; let props = Props { @@ -496,11 +488,11 @@ mod tests { (scope, parent) } - fn get_html(node: Html, scope: &AnyScope, parent: &Element) -> String { + fn get_html(mut node: Html, scope: &AnyScope, parent: &Element) -> String { // clear parent parent.set_inner_html(""); - node.unwrap().apply(scope, parent, NodeRef::default(), None); + node.apply(scope, parent, NodeRef::default(), None); parent.inner_html() } @@ -510,7 +502,7 @@ mod tests { let children: Vec<_> = vec!["a", "b", "c"] .drain(..) - .map(|text| html! {{ text }}.unwrap()) + .map(|text| html! {{ text }}) .collect(); let children_renderer = Children::new(children.clone()); let expected_html = "\ @@ -558,7 +550,7 @@ mod tests { document().body().unwrap().append_child(&parent).unwrap(); let node_ref = NodeRef::default(); - let mut elem: VNode = html! { }.unwrap(); + let mut elem: VNode = html! { }; elem.apply(&scope, &parent, NodeRef::default(), None); let parent_node = parent.deref(); assert_eq!(node_ref.get(), parent_node.first_child()); @@ -625,8 +617,7 @@ mod layout_tests { >> {"C"} > - } - .unwrap(), + }, expected: "C", }; @@ -636,8 +627,7 @@ mod layout_tests { > {"A"} > - } - .unwrap(), + }, expected: "A", }; @@ -648,8 +638,7 @@ mod layout_tests { >> {"B"} > - } - .unwrap(), + }, expected: "B", }; @@ -660,8 +649,7 @@ mod layout_tests { >{"A"}> {"B"} > - } - .unwrap(), + }, expected: "AB", }; @@ -676,8 +664,7 @@ mod layout_tests { {"B"} > - } - .unwrap(), + }, expected: "AB", }; @@ -693,8 +680,7 @@ mod layout_tests { {"C"} > - } - .unwrap(), + }, expected: "ABC", }; @@ -712,8 +698,7 @@ mod layout_tests { {"C"} > - } - .unwrap(), + }, expected: "ABC", }; @@ -733,8 +718,7 @@ mod layout_tests { {"C"} > - } - .unwrap(), + }, expected: "ABC", }; @@ -754,8 +738,7 @@ mod layout_tests { {"C"} > - } - .unwrap(), + }, expected: "ABC", }; @@ -775,8 +758,7 @@ mod layout_tests { {"C"} > - } - .unwrap(), + }, expected: "ABC", }; @@ -796,8 +778,7 @@ mod layout_tests { {"C"} > - } - .unwrap(), + }, expected: "ABC", }; @@ -831,8 +812,7 @@ mod layout_tests { >> <> > - } - .unwrap(), + }, expected: "ABC", }; @@ -880,8 +860,7 @@ mod layout_tests { { "world" } } - } - .unwrap(), + }, expected: "
    • helloworld
    ", }; diff --git a/packages/yew/src/virtual_dom/vlist.rs b/packages/yew/src/virtual_dom/vlist.rs index 9ccdd3ffb21..17194c2c429 100644 --- a/packages/yew/src/virtual_dom/vlist.rs +++ b/packages/yew/src/virtual_dom/vlist.rs @@ -353,88 +353,6 @@ impl VDiff for VList { } } -#[cfg(test)] -mod layout_tests { - extern crate self as yew; - - use crate::html; - use crate::virtual_dom::layout_tests::{diff_layouts, TestLayout}; - - #[cfg(feature = "wasm_test")] - use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure}; - - #[cfg(feature = "wasm_test")] - wasm_bindgen_test_configure!(run_in_browser); - - #[test] - fn diff() { - let layout1 = TestLayout { - name: "1", - node: html! { - <> - {"a"} - {"b"} - <> - {"c"} - {"d"} - - {"e"} - - } - .unwrap(), - expected: "abcde", - }; - - let layout2 = TestLayout { - name: "2", - node: html! { - <> - {"a"} - {"b"} - <> - {"e"} - {"f"} - - } - .unwrap(), - expected: "abef", - }; - - let layout3 = TestLayout { - name: "3", - node: html! { - <> - {"a"} - <> - {"b"} - {"e"} - - } - .unwrap(), - expected: "abe", - }; - - let layout4 = TestLayout { - name: "4", - node: html! { - <> - {"a"} - <> - {"c"} - {"d"} - - {"b"} - {"e"} - - } - .unwrap(), - expected: "acdbe", - }; - - diff_layouts(vec![layout1, layout2, layout3, layout4]); - } -} - #[cfg(test)] mod layout_tests_keys { extern crate self as yew; @@ -521,8 +439,7 @@ mod layout_tests_keys { {VNode::VRef(vref_node)} - } - .unwrap(), + }, expected: "acd

    0

    foobar", }); @@ -536,8 +453,7 @@ mod layout_tests_keys {

    - } - .unwrap(), + }, expected: "

    ", }, TestLayout { @@ -550,8 +466,7 @@ mod layout_tests_keys {

    - } - .unwrap(), + }, expected: "

    ", }, ]); @@ -564,8 +479,7 @@ mod layout_tests_keys { - } - .unwrap(), + }, expected: "", }, TestLayout { @@ -575,8 +489,7 @@ mod layout_tests_keys {

    - } - .unwrap(), + }, expected: "

    ", }, ]); @@ -589,8 +502,7 @@ mod layout_tests_keys { - } - .unwrap(), + }, expected: "", }, TestLayout { @@ -601,8 +513,7 @@ mod layout_tests_keys {

    - } - .unwrap(), + }, expected: "

    ", }, ]); @@ -615,8 +526,7 @@ mod layout_tests_keys { - } - .unwrap(), + }, expected: "", }, TestLayout { @@ -627,8 +537,7 @@ mod layout_tests_keys { - } - .unwrap(), + }, expected: "

    ", }, ]); @@ -642,8 +551,7 @@ mod layout_tests_keys {

    - } - .unwrap(), + }, expected: "

    ", }, TestLayout { @@ -653,8 +561,7 @@ mod layout_tests_keys {

    - } - .unwrap(), + }, expected: "

    ", }, ]); @@ -668,8 +575,7 @@ mod layout_tests_keys {

    - } - .unwrap(), + }, expected: "

    ", }, TestLayout { @@ -679,8 +585,7 @@ mod layout_tests_keys { - } - .unwrap(), + }, expected: "", }, ]); @@ -694,8 +599,7 @@ mod layout_tests_keys {

    - } - .unwrap(), + }, expected: "

    ", }, TestLayout { @@ -706,8 +610,7 @@ mod layout_tests_keys {
    - } - .unwrap(), + }, expected: "", }, ]); @@ -722,8 +625,7 @@ mod layout_tests_keys {

    - } - .unwrap(), + }, expected: "

    ", }, TestLayout { @@ -735,8 +637,7 @@ mod layout_tests_keys {

    - } - .unwrap(), + }, expected: "

    ", }, ]); @@ -751,8 +652,7 @@ mod layout_tests_keys {

    - } - .unwrap(), + }, expected: "

    ", }, TestLayout { @@ -764,8 +664,7 @@ mod layout_tests_keys {

    - } - .unwrap(), + }, expected: "

    ", }, ]); @@ -780,8 +679,7 @@ mod layout_tests_keys {

    - } - .unwrap(), + }, expected: "

    ", }, TestLayout { @@ -793,8 +691,7 @@ mod layout_tests_keys { - } - .unwrap(), + }, expected: "

    ", }, ]); @@ -814,8 +711,7 @@ mod layout_tests_keys { - } - .unwrap(), + }, expected: "

    ", }, TestLayout { @@ -827,8 +723,7 @@ mod layout_tests_keys { - } - .unwrap(), + }, expected: "

    ", }, ]); @@ -844,8 +739,7 @@ mod layout_tests_keys { - } - .unwrap(), + }, expected: "

    ", }, TestLayout { @@ -858,8 +752,7 @@ mod layout_tests_keys { - } - .unwrap(), + }, expected: "

    ", }, ]); @@ -875,8 +768,7 @@ mod layout_tests_keys { - } - .unwrap(), + }, expected: "

    ", }, TestLayout { @@ -889,8 +781,7 @@ mod layout_tests_keys { - } - .unwrap(), + }, expected: "

    ", }, ]); @@ -913,8 +804,7 @@ mod layout_tests_keys { - } - .unwrap(), + }, expected: "

    ", }, TestLayout { @@ -927,8 +817,7 @@ mod layout_tests_keys { - } - .unwrap(), + }, expected: "

    ", }, ]); @@ -944,8 +833,7 @@ mod layout_tests_keys { - } - .unwrap(), + }, expected: "

    ", }, TestLayout { @@ -958,8 +846,7 @@ mod layout_tests_keys { - } - .unwrap(), + }, expected: "

    ", }, ]); @@ -975,8 +862,7 @@ mod layout_tests_keys { - } - .unwrap(), + }, expected: "

    ", }, TestLayout { @@ -989,8 +875,7 @@ mod layout_tests_keys { - } - .unwrap(), + }, expected: "

    ", }, ]); @@ -1006,8 +891,7 @@ mod layout_tests_keys { - } - .unwrap(), + }, expected: "

    ", }, TestLayout { @@ -1020,8 +904,7 @@ mod layout_tests_keys { - } - .unwrap(), + }, expected: "

    ", }, ]); @@ -1040,8 +923,7 @@ mod layout_tests_keys { - } - .unwrap(), + }, expected: "", }, TestLayout { @@ -1057,8 +939,7 @@ mod layout_tests_keys { - } - .unwrap(), + }, expected: "", }, ]); @@ -1078,8 +959,7 @@ mod layout_tests_keys { - } - .unwrap(), + }, expected: "

    ", }, TestLayout { @@ -1096,8 +976,7 @@ mod layout_tests_keys { - } - .unwrap(), + }, expected: "

    ", }, ]); @@ -1110,8 +989,7 @@ mod layout_tests_keys { - } - .unwrap(), + }, expected: "", }, TestLayout { @@ -1122,8 +1000,7 @@ mod layout_tests_keys { - } - .unwrap(), + }, expected: "

    0

    ", }, ]); @@ -1136,8 +1013,7 @@ mod layout_tests_keys { - } - .unwrap(), + }, expected: "", }, TestLayout { @@ -1148,8 +1024,7 @@ mod layout_tests_keys { - } - .unwrap(), + }, expected: "

    0

    ", }, ]); @@ -1162,8 +1037,7 @@ mod layout_tests_keys { - } - .unwrap(), + }, expected: "", }, TestLayout { @@ -1174,8 +1048,7 @@ mod layout_tests_keys { - } - .unwrap(), + }, expected: "

    0

    ", }, ]); @@ -1189,8 +1062,7 @@ mod layout_tests_keys { - } - .unwrap(), + }, expected: "

    1

    2

    3

    ", }, TestLayout { @@ -1201,8 +1073,7 @@ mod layout_tests_keys { - } - .unwrap(), + }, expected: "

    3

    2

    1

    ", }, ]); @@ -1216,8 +1087,7 @@ mod layout_tests_keys {

    {"21"}

    {"22"}

    {"31"}

    {"32"}

    - } - .unwrap(), + }, expected: "

    11

    12

    21

    22

    31

    32

    ", }, TestLayout { @@ -1228,8 +1098,7 @@ mod layout_tests_keys {

    {"21"}

    {"22"}

    {"11"}

    {"12"}

    - } - .unwrap(), + }, expected: "

    31

    32

    21

    22

    11

    12

    ", }, ]); @@ -1242,8 +1111,7 @@ mod layout_tests_keys {
    - } - .unwrap(), + }, expected: "

    1

    2

    ", }, TestLayout { @@ -1257,8 +1125,7 @@ mod layout_tests_keys {

    {"2"}

    - } - .unwrap(), + }, expected: "

    1

    2

    ", }, ]); @@ -1275,8 +1142,7 @@ mod layout_tests_keys {

    {"4"}

    {"6"}

    - } - .unwrap(), + }, expected: "

    1

    3

    5

    2

    4

    6

    ", }, TestLayout { @@ -1290,8 +1156,7 @@ mod layout_tests_keys { - } - .unwrap(), + }, expected: "

    6

    5

    4

    3

    2

    1

    ", }, ]); @@ -1305,8 +1170,7 @@ mod layout_tests_keys {

    {"2"}

    {"3"}

    - } - .unwrap(), + }, expected: "

    1

    2

    3

    ", }, TestLayout { @@ -1317,8 +1181,7 @@ mod layout_tests_keys { - } - .unwrap(), + }, expected: "

    3

    2

    1

    ", }, ]); diff --git a/packages/yew/src/virtual_dom/vportal.rs b/packages/yew/src/virtual_dom/vportal.rs index f99a3b240ed..5dc907537f6 100644 --- a/packages/yew/src/virtual_dom/vportal.rs +++ b/packages/yew/src/virtual_dom/vportal.rs @@ -134,13 +134,12 @@ mod layout_tests { {VNode::VRef(first_target.clone().into())} {VNode::VRef(second_target.clone().into())} {VNode::VPortal(VPortal::new( - html! { {"PORTAL"} }.unwrap(), + html! { {"PORTAL"} }, first_target.clone(), ))} {"AFTER"}
    - } - .unwrap(), + }, expected: "
    PORTALAFTER
    ", }); layouts.push(TestLayout { @@ -150,13 +149,12 @@ mod layout_tests { {VNode::VRef(first_target.clone().into())} {VNode::VRef(second_target.clone().into())} {VNode::VPortal(VPortal::new( - html! { {"PORTAL"} }.unwrap(), + html! { {"PORTAL"} }, second_target.clone(), ))} {"AFTER"}
    - } - .unwrap(), + }, expected: "
    PORTALAFTER
    ", }); layouts.push(TestLayout { @@ -168,8 +166,7 @@ mod layout_tests { {"FOO"} {"AFTER"}
    - } - .unwrap(), + }, expected: "
    FOOAFTER
    ", }); layouts.push(TestLayout { @@ -178,13 +175,12 @@ mod layout_tests {
    {VNode::VRef(target_with_child.clone().into())} {VNode::VPortal(VPortal::new_before( - html! { {"PORTAL"} }.unwrap(), + html! { {"PORTAL"} }, target_with_child.clone(), Some(target_child.clone().into()), ))}
    - } - .unwrap(), + }, expected: "
    PORTAL
    ", }); diff --git a/packages/yew/src/virtual_dom/vtag.rs b/packages/yew/src/virtual_dom/vtag.rs index 6094b83a200..d8e1f744bf5 100644 --- a/packages/yew/src/virtual_dom/vtag.rs +++ b/packages/yew/src/virtual_dom/vtag.rs @@ -809,9 +809,9 @@ mod tests { let namespace = Some(namespace); let svg_el = document.create_element_ns(namespace, "svg").unwrap(); - let mut g_node = html! { }.unwrap(); + let mut g_node = html! { }; let path_node = html! { }; - let mut svg_node = html! { {path_node} }.unwrap(); + let mut svg_node = html! { {path_node} }; let svg_tag = assert_vtag_mut(&mut svg_node); svg_tag.apply(&scope, &div_el, NodeRef::default(), None); @@ -904,8 +904,7 @@ mod tests {

    - } - .unwrap(); + }; if let VNode::VTag(vtag) = a { assert_eq!( vtag.attributes @@ -926,7 +925,7 @@ mod tests { document().body().unwrap().append_child(&parent).unwrap(); - let mut elem = html! {
    }.unwrap(); + let mut elem = html! {
    }; VDiff::apply(&mut elem, &scope, &parent, NodeRef::default(), None); let vtag = assert_vtag_mut(&mut elem); // test if the className has not been set @@ -939,7 +938,7 @@ mod tests { document().body().unwrap().append_child(&parent).unwrap(); - let mut elem = gen_html().unwrap(); + let mut elem = gen_html(); VDiff::apply(&mut elem, &scope, &parent, NodeRef::default(), None); let vtag = assert_vtag_mut(&mut elem); // test if the className has been set @@ -966,7 +965,7 @@ mod tests { let expected = "not_changed_value"; // Initial state - let mut elem = html! { }.unwrap(); + let mut elem = html! { }; VDiff::apply(&mut elem, &scope, &parent, NodeRef::default(), None); let vtag = if let VNode::VTag(vtag) = elem { vtag @@ -980,7 +979,7 @@ mod tests { input.unwrap().set_value("User input"); let ancestor = vtag; - let mut elem = html! { }.unwrap(); + let mut elem = html! { }; let vtag = assert_vtag_mut(&mut elem); // Sync happens here @@ -1009,7 +1008,7 @@ mod tests { document().body().unwrap().append_child(&parent).unwrap(); // Initial state - let mut elem = html! { }.unwrap(); + let mut elem = html! { }; VDiff::apply(&mut elem, &scope, &parent, NodeRef::default(), None); let vtag = if let VNode::VTag(vtag) = elem { vtag @@ -1023,7 +1022,7 @@ mod tests { input.unwrap().set_value("User input"); let ancestor = vtag; - let mut elem = html! { }.unwrap(); + let mut elem = html! { }; let vtag = assert_vtag_mut(&mut elem); // Value should not be refreshed @@ -1059,8 +1058,7 @@ mod tests { let mut builder = String::new(); builder.push('a'); builder - }/> } - .unwrap(); + }/> }; VDiff::apply(&mut elem, &scope, &parent, NodeRef::default(), None); let vtag = assert_vtag_mut(&mut elem); @@ -1075,8 +1073,7 @@ mod tests { fn dynamic_tags_handle_value_attribute() { let mut div_el = html! { <@{"div"} value="Hello"/> - } - .unwrap(); + }; let div_vtag = assert_vtag_mut(&mut div_el); assert!(div_vtag.value().is_none()); let v: Option<&str> = div_vtag @@ -1088,8 +1085,7 @@ mod tests { let mut input_el = html! { <@{"input"} value="World"/> - } - .unwrap(); + }; let input_vtag = assert_vtag_mut(&mut input_el); assert_eq!(input_vtag.value(), Some(&AttrValue::Static("World"))); assert!(!input_vtag.attributes.iter().any(|(k, _)| k == "value")); @@ -1099,8 +1095,7 @@ mod tests { fn dynamic_tags_handle_weird_capitalization() { let mut el = html! { <@{"tExTAREa"}/> - } - .unwrap(); + }; let vtag = assert_vtag_mut(&mut el); assert_eq!(vtag.tag(), "textarea"); } @@ -1113,7 +1108,7 @@ mod tests { document().body().unwrap().append_child(&parent).unwrap(); let node_ref = NodeRef::default(); - let mut elem: VNode = html! {
    }.unwrap(); + let mut elem: VNode = html! {
    }; assert_vtag_mut(&mut elem); elem.apply(&scope, &parent, NodeRef::default(), None); let parent_node = parent.deref(); @@ -1129,14 +1124,14 @@ mod tests { document().body().unwrap().append_child(&parent).unwrap(); let node_ref_a = NodeRef::default(); - let mut elem_a = html! {
    }.unwrap(); + let mut elem_a = html! {
    }; elem_a.apply(&scope, &parent, NodeRef::default(), None); // save the Node to check later that it has been reused. let node_a = node_ref_a.get().unwrap(); let node_ref_b = NodeRef::default(); - let mut elem_b = html! {
    }.unwrap(); + let mut elem_b = html! {
    }; elem_b.apply(&scope, &parent, NodeRef::default(), Some(elem_a)); let node_b = node_ref_b.get().unwrap(); @@ -1175,8 +1170,7 @@ mod layout_tests { {"b"} - } - .unwrap(), + }, expected: "
    • a
    • b
    ", }; @@ -1194,8 +1188,7 @@ mod layout_tests { {"d"} - } - .unwrap(), + }, expected: "
    • a
    • b
    • d
    ", }; @@ -1216,8 +1209,7 @@ mod layout_tests { {"d"} - } - .unwrap(), + }, expected: "
    • a
    • b
    • c
    • d
    ", }; @@ -1240,8 +1232,7 @@ mod layout_tests { - } - .unwrap(), + }, expected: "
    • a
    • b
    • c
    • d
    ", }; diff --git a/packages/yew/src/virtual_dom/vtext.rs b/packages/yew/src/virtual_dom/vtext.rs index c9ba9768a06..8d62d44ba8c 100644 --- a/packages/yew/src/virtual_dom/vtext.rs +++ b/packages/yew/src/virtual_dom/vtext.rs @@ -104,32 +104,6 @@ impl PartialEq for VText { } } -#[cfg(test)] -mod test { - extern crate self as yew; - - use crate::html; - - #[cfg(feature = "wasm_test")] - use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure}; - - #[cfg(feature = "wasm_test")] - wasm_bindgen_test_configure!(run_in_browser); - - #[test] - fn text_as_root() { - (html! { - "Text Node As Root" - }) - .unwrap(); - - (html! { - { "Text Node As Root" } - }) - .unwrap(); - } -} - #[cfg(test)] mod layout_tests { extern crate self as yew; @@ -147,13 +121,13 @@ mod layout_tests { fn diff() { let layout1 = TestLayout { name: "1", - node: html! { "a" }.unwrap(), + node: html! { "a" }, expected: "a", }; let layout2 = TestLayout { name: "2", - node: html! { "b" }.unwrap(), + node: html! { "b" }, expected: "b", }; @@ -164,8 +138,7 @@ mod layout_tests { {"a"} {"b"} - } - .unwrap(), + }, expected: "ab", }; @@ -176,8 +149,7 @@ mod layout_tests { {"b"} {"a"} - } - .unwrap(), + }, expected: "ba", }; diff --git a/packages/yew/tests/mod.rs b/packages/yew/tests/mod.rs index ef98943a984..a8a7adbd897 100644 --- a/packages/yew/tests/mod.rs +++ b/packages/yew/tests/mod.rs @@ -3,7 +3,7 @@ mod common; use common::obtain_result; use wasm_bindgen_test::*; use yew::functional::{FunctionComponent, FunctionProvider}; -use yew::{html, Html, Properties}; +use yew::{html, HtmlResult, Properties}; wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); @@ -17,13 +17,13 @@ fn props_are_passed() { impl FunctionProvider for PropsPassedFunction { type TProps = PropsPassedFunctionProps; - fn run(props: &Self::TProps) -> Html { + fn run(props: &Self::TProps) -> HtmlResult { assert_eq!(&props.value, "props"); - return html! { + return Ok(html! {
    {"done"}
    - }; + }); } } type PropsComponent = FunctionComponent; diff --git a/packages/yew/tests/suspense.rs b/packages/yew/tests/suspense.rs index be3d6b834aa..881c21c4785 100644 --- a/packages/yew/tests/suspense.rs +++ b/packages/yew/tests/suspense.rs @@ -54,7 +54,7 @@ async fn suspense_works() { } #[function_component(Content)] - fn content() -> Html { + fn content() -> HtmlResult { let resleep = use_sleep()?; let value = use_state(|| 0); @@ -69,7 +69,7 @@ async fn suspense_works() { let on_take_a_break = Callback::from(move |_: MouseEvent| (resleep.clone())()); - html! { + Ok(html! {
    {*value}
    @@ -77,7 +77,7 @@ async fn suspense_works() {
    - } + }) } #[function_component(App)] @@ -197,7 +197,7 @@ async fn suspense_not_suspended_at_start() { } #[function_component(Content)] - fn content() -> Html { + fn content() -> HtmlResult { let resleep = use_sleep()?; let value = use_state(|| "I am writing a long story...".to_string()); @@ -214,14 +214,14 @@ async fn suspense_not_suspended_at_start() { let on_take_a_break = Callback::from(move |_| (resleep.clone())()); - html! { + Ok(html! {
    - } + }) } #[function_component(App)] @@ -307,29 +307,29 @@ async fn suspense_nested_suspense_works() { } #[function_component(InnerContent)] - fn inner_content() -> Html { + fn inner_content() -> HtmlResult { let resleep = use_sleep()?; let on_take_a_break = Callback::from(move |_: MouseEvent| (resleep.clone())()); - html! { + Ok(html! {
    - } + }) } #[function_component(Content)] - fn content() -> Html { + fn content() -> HtmlResult { let resleep = use_sleep()?; let fallback = html! {
    {"wait...(inner)"}
    }; let on_take_a_break = Callback::from(move |_: MouseEvent| (resleep.clone())()); - html! { + Ok(html! {
    @@ -338,7 +338,7 @@ async fn suspense_nested_suspense_works() {
    - } + }) } #[function_component(App)] diff --git a/packages/yew/tests/use_context.rs b/packages/yew/tests/use_context.rs index 23e76df07d2..c347b3b1f9a 100644 --- a/packages/yew/tests/use_context.rs +++ b/packages/yew/tests/use_context.rs @@ -6,7 +6,7 @@ use wasm_bindgen_test::*; use yew::functional::{ use_context, use_effect, use_mut_ref, use_state, FunctionComponent, FunctionProvider, }; -use yew::{html, Children, ContextProvider, Html, Properties}; +use yew::{html, Children, ContextProvider, HtmlResult, Properties}; wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); @@ -23,24 +23,24 @@ fn use_context_scoping_works() { impl FunctionProvider for ExpectNoContextFunction { type TProps = (); - fn run(_props: &Self::TProps) -> Html { + fn run(_props: &Self::TProps) -> HtmlResult { if use_context::().is_some() { console_log!( "Context should be None here, but was {:?}!", use_context::().unwrap() ); }; - return html! { + return Ok(html! {
    - }; + }); } } impl FunctionProvider for UseContextFunctionOuter { type TProps = (); - fn run(_props: &Self::TProps) -> Html { + fn run(_props: &Self::TProps) -> HtmlResult { type ExampleContextProvider = ContextProvider; - return html! { + return Ok(html! {
    {"ignored"}
    @@ -58,17 +58,17 @@ fn use_context_scoping_works() {
    - }; + }); } } impl FunctionProvider for UseContextFunctionInner { type TProps = (); - fn run(_props: &Self::TProps) -> Html { + fn run(_props: &Self::TProps) -> HtmlResult { let context = use_context::(); - return html! { + return Ok(html! {
    { &context.unwrap().0 }
    - }; + }); } } @@ -90,11 +90,11 @@ fn use_context_works_with_multiple_types() { impl FunctionProvider for Test1Function { type TProps = (); - fn run(_props: &Self::TProps) -> Html { + fn run(_props: &Self::TProps) -> HtmlResult { assert_eq!(use_context::(), Some(ContextA(2))); assert_eq!(use_context::(), Some(ContextB(1))); - return html! {}; + return Ok(html! {}); } } type Test1 = FunctionComponent; @@ -103,11 +103,11 @@ fn use_context_works_with_multiple_types() { impl FunctionProvider for Test2Function { type TProps = (); - fn run(_props: &Self::TProps) -> Html { + fn run(_props: &Self::TProps) -> HtmlResult { assert_eq!(use_context::(), Some(ContextA(0))); assert_eq!(use_context::(), Some(ContextB(1))); - return html! {}; + return Ok(html! {}); } } type Test2 = FunctionComponent; @@ -116,11 +116,11 @@ fn use_context_works_with_multiple_types() { impl FunctionProvider for Test3Function { type TProps = (); - fn run(_props: &Self::TProps) -> Html { + fn run(_props: &Self::TProps) -> HtmlResult { assert_eq!(use_context::(), Some(ContextA(0))); assert_eq!(use_context::(), None); - return html! {}; + return Ok(html! {}); } } type Test3 = FunctionComponent; @@ -129,11 +129,11 @@ fn use_context_works_with_multiple_types() { impl FunctionProvider for Test4Function { type TProps = (); - fn run(_props: &Self::TProps) -> Html { + fn run(_props: &Self::TProps) -> HtmlResult { assert_eq!(use_context::(), None); assert_eq!(use_context::(), None); - return html! {}; + return Ok(html! {}); } } type Test4 = FunctionComponent; @@ -142,11 +142,11 @@ fn use_context_works_with_multiple_types() { impl FunctionProvider for TestFunction { type TProps = (); - fn run(_props: &Self::TProps) -> Html { + fn run(_props: &Self::TProps) -> HtmlResult { type ContextAProvider = ContextProvider; type ContextBProvider = ContextProvider; - return html! { + return Ok(html! {
    @@ -159,7 +159,7 @@ fn use_context_works_with_multiple_types() {
    - }; + }); } } type TestComponent = FunctionComponent; @@ -184,17 +184,17 @@ fn use_context_update_works() { impl FunctionProvider for RenderCounterFunction { type TProps = RenderCounterProps; - fn run(props: &Self::TProps) -> Html { + fn run(props: &Self::TProps) -> HtmlResult { let counter = use_mut_ref(|| 0); *counter.borrow_mut() += 1; - return html! { + return Ok(html! { <>
    { format!("total: {}", counter.borrow()) }
    { props.children.clone() } - }; + }); } } type RenderCounter = FunctionComponent; @@ -209,20 +209,20 @@ fn use_context_update_works() { impl FunctionProvider for ContextOutletFunction { type TProps = ContextOutletProps; - fn run(props: &Self::TProps) -> Html { + fn run(props: &Self::TProps) -> HtmlResult { let counter = use_mut_ref(|| 0); *counter.borrow_mut() += 1; let ctx = use_context::>().expect("context not passed down"); - return html! { + return Ok(html! { <>
    { format!("magic: {}\n", props.magic) }
    { format!("current: {}, total: {}", ctx.0, counter.borrow()) }
    - }; + }); } } type ContextOutlet = FunctionComponent; @@ -231,7 +231,7 @@ fn use_context_update_works() { impl FunctionProvider for TestFunction { type TProps = (); - fn run(_props: &Self::TProps) -> Html { + fn run(_props: &Self::TProps) -> HtmlResult { type MyContextProvider = ContextProvider>; let ctx = use_state(|| MyContext("hello".into())); @@ -263,14 +263,14 @@ fn use_context_update_works() { || {} }); } - return html! { + return Ok(html! { - }; + }); } } type TestComponent = FunctionComponent; diff --git a/packages/yew/tests/use_effect.rs b/packages/yew/tests/use_effect.rs index 301a210a614..c5bd876c1bb 100644 --- a/packages/yew/tests/use_effect.rs +++ b/packages/yew/tests/use_effect.rs @@ -7,7 +7,7 @@ use wasm_bindgen_test::*; use yew::functional::{ use_effect_with_deps, use_mut_ref, use_state, FunctionComponent, FunctionProvider, }; -use yew::{html, Html, Properties}; +use yew::{html, HtmlResult, Properties}; wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); @@ -39,7 +39,7 @@ fn use_effect_destroys_on_component_drop() { impl FunctionProvider for UseEffectFunction { type TProps = FunctionProps; - fn run(props: &Self::TProps) -> Html { + fn run(props: &Self::TProps) -> HtmlResult { let effect_called = props.effect_called.clone(); let destroy_called = props.destroy_called.clone(); use_effect_with_deps( @@ -50,23 +50,23 @@ fn use_effect_destroys_on_component_drop() { }, (), ); - return html! {}; + return Ok(html! {}); } } impl FunctionProvider for UseEffectWrapper { type TProps = WrapperProps; - fn run(props: &Self::TProps) -> Html { + fn run(props: &Self::TProps) -> HtmlResult { let show = use_state(|| true); if *show { let effect_called: Rc = { Rc::new(move || show.set(false)) }; - html! { + Ok(html! { - } + }) } else { - html! { + Ok(html! {
    { "EMPTY" }
    - } + }) } } } @@ -87,7 +87,7 @@ fn use_effect_works_many_times() { impl FunctionProvider for UseEffectFunction { type TProps = (); - fn run(_: &Self::TProps) -> Html { + fn run(_: &Self::TProps) -> HtmlResult { let counter = use_state(|| 0); let counter_clone = counter.clone(); @@ -101,13 +101,13 @@ fn use_effect_works_many_times() { *counter, ); - return html! { + return Ok(html! {
    { "The test result is" }
    { *counter }
    { "\n" }
    - }; + }); } } @@ -125,7 +125,7 @@ fn use_effect_works_once() { impl FunctionProvider for UseEffectFunction { type TProps = (); - fn run(_: &Self::TProps) -> Html { + fn run(_: &Self::TProps) -> HtmlResult { let counter = use_state(|| 0); let counter_clone = counter.clone(); @@ -137,13 +137,13 @@ fn use_effect_works_once() { (), ); - return html! { + return Ok(html! {
    { "The test result is" }
    { *counter }
    { "\n" }
    - }; + }); } } type UseEffectComponent = FunctionComponent; @@ -160,7 +160,7 @@ fn use_effect_refires_on_dependency_change() { impl FunctionProvider for UseEffectFunction { type TProps = (); - fn run(_: &Self::TProps) -> Html { + fn run(_: &Self::TProps) -> HtmlResult { let number_ref = use_mut_ref(|| 0); let number_ref_c = number_ref.clone(); let number_ref2 = use_mut_ref(|| 0); @@ -185,13 +185,13 @@ fn use_effect_refires_on_dependency_change() { }, arg, ); - return html! { + return Ok(html! {
    {"The test result is"}
    {*number_ref.borrow_mut().deref_mut()}{*number_ref2.borrow_mut().deref_mut()}
    {"\n"}
    - }; + }); } } type UseEffectComponent = FunctionComponent; diff --git a/packages/yew/tests/use_reducer.rs b/packages/yew/tests/use_reducer.rs index ff54ffe83be..ee430f72a5a 100644 --- a/packages/yew/tests/use_reducer.rs +++ b/packages/yew/tests/use_reducer.rs @@ -34,7 +34,7 @@ fn use_reducer_works() { struct UseReducerFunction {} impl FunctionProvider for UseReducerFunction { type TProps = (); - fn run(_: &Self::TProps) -> Html { + fn run(_: &Self::TProps) -> HtmlResult { let counter = use_reducer(|| CounterState { counter: 10 }); let counter_clone = counter.clone(); @@ -45,13 +45,13 @@ fn use_reducer_works() { }, (), ); - return html! { + return Ok(html! {
    {"The test result is"}
    {counter.counter}
    {"\n"}
    - }; + }); } } type UseReducerComponent = FunctionComponent; @@ -83,7 +83,7 @@ fn use_reducer_eq_works() { struct UseReducerFunction {} impl FunctionProvider for UseReducerFunction { type TProps = (); - fn run(_: &Self::TProps) -> Html { + fn run(_: &Self::TProps) -> HtmlResult { let content = use_reducer_eq(|| ContentState { content: HashSet::default(), }); @@ -104,7 +104,7 @@ fn use_reducer_eq_works() { let add_content_b = Callback::from(move |_| content.dispatch("B".to_string())); - return html! { + return Ok(html! { <>
    {"This component has been rendered: "}{render_count}{" Time(s)."} @@ -112,7 +112,7 @@ fn use_reducer_eq_works() { - }; + }); } } type UseReducerComponent = FunctionComponent; diff --git a/packages/yew/tests/use_ref.rs b/packages/yew/tests/use_ref.rs index 80c86c76394..1ce8e674f4b 100644 --- a/packages/yew/tests/use_ref.rs +++ b/packages/yew/tests/use_ref.rs @@ -4,7 +4,7 @@ use common::obtain_result; use std::ops::DerefMut; use wasm_bindgen_test::*; use yew::functional::{use_mut_ref, use_state, FunctionComponent, FunctionProvider}; -use yew::{html, Html}; +use yew::{html, HtmlResult}; wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); @@ -14,20 +14,20 @@ fn use_ref_works() { impl FunctionProvider for UseRefFunction { type TProps = (); - fn run(_: &Self::TProps) -> Html { + fn run(_: &Self::TProps) -> HtmlResult { let ref_example = use_mut_ref(|| 0); *ref_example.borrow_mut().deref_mut() += 1; let counter = use_state(|| 0); if *counter < 5 { counter.set(*counter + 1) } - return html! { + return Ok(html! {
    {"The test output is: "}
    {*ref_example.borrow_mut().deref_mut() > 4}
    {"\n"}
    - }; + }); } } type UseRefComponent = FunctionComponent; diff --git a/packages/yew/tests/use_state.rs b/packages/yew/tests/use_state.rs index 5175700e63f..a4762450b9e 100644 --- a/packages/yew/tests/use_state.rs +++ b/packages/yew/tests/use_state.rs @@ -5,7 +5,7 @@ use wasm_bindgen_test::*; use yew::functional::{ use_effect_with_deps, use_state, use_state_eq, FunctionComponent, FunctionProvider, }; -use yew::{html, Html}; +use yew::{html, HtmlResult}; wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); @@ -15,18 +15,18 @@ fn use_state_works() { impl FunctionProvider for UseStateFunction { type TProps = (); - fn run(_: &Self::TProps) -> Html { + fn run(_: &Self::TProps) -> HtmlResult { let counter = use_state(|| 0); if *counter < 5 { counter.set(*counter + 1) } - return html! { + return Ok(html! {
    {"Test Output: "}
    {*counter}
    {"\n"}
    - }; + }); } } type UseComponent = FunctionComponent; @@ -43,7 +43,7 @@ fn multiple_use_state_setters() { impl FunctionProvider for UseStateFunction { type TProps = (); - fn run(_: &Self::TProps) -> Html { + fn run(_: &Self::TProps) -> HtmlResult { let counter = use_state(|| 0); let counter_clone = counter.clone(); use_effect_with_deps( @@ -64,14 +64,14 @@ fn multiple_use_state_setters() { } }; another_scope(); - return html! { + return Ok(html! {
    { "Test Output: " } // expected output
    { *counter }
    { "\n" }
    - }; + }); } } type UseComponent = FunctionComponent; @@ -91,7 +91,7 @@ fn use_state_eq_works() { impl FunctionProvider for UseStateFunction { type TProps = (); - fn run(_: &Self::TProps) -> Html { + fn run(_: &Self::TProps) -> HtmlResult { // No race conditions will be caused since its only used in one place unsafe { RENDER_COUNT += 1; @@ -99,13 +99,13 @@ fn use_state_eq_works() { let counter = use_state_eq(|| 0); counter.set(1); - return html! { + return Ok(html! {
    {"Test Output: "}
    {*counter}
    {"\n"}
    - }; + }); } } type UseComponent = FunctionComponent; diff --git a/website/docs/concepts/suspense.md b/website/docs/concepts/suspense.md index 410ff0271e6..db98888affc 100644 --- a/website/docs/concepts/suspense.md +++ b/website/docs/concepts/suspense.md @@ -25,10 +25,10 @@ The recommended way to use suspense is with hooks. use yew::prelude::*; #[function_component(Content)] -fn content() -> Html { +fn content() -> HtmlResult { let user = use_user()?; - html! {
    {"Hello, "}{&user.name}
    } + Ok(html! {
    {"Hello, "}{&user.name}
    }) } #[function_component(App)] From cbf12be71e28be4816e6d6e98359e16e3821e910 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Mon, 20 Dec 2021 21:34:31 +0900 Subject: [PATCH 18/27] Revert extra changes. --- examples/nested_list/src/list.rs | 12 ++- packages/yew/src/virtual_dom/key.rs | 4 +- packages/yew/src/virtual_dom/vlist.rs | 78 +++++++++++++++++++ packages/yew/src/virtual_dom/vtext.rs | 24 ++++++ website/docs/concepts/components/children.mdx | 20 ++--- .../docs/concepts/components/introduction.mdx | 9 ++- website/docs/concepts/html/elements.mdx | 3 +- website/docs/concepts/html/lists.mdx | 3 +- 8 files changed, 126 insertions(+), 27 deletions(-) mode change 100644 => 100755 examples/nested_list/src/list.rs mode change 100644 => 100755 website/docs/concepts/components/children.mdx mode change 100644 => 100755 website/docs/concepts/components/introduction.mdx mode change 100644 => 100755 website/docs/concepts/html/elements.mdx mode change 100644 => 100755 website/docs/concepts/html/lists.mdx diff --git a/examples/nested_list/src/list.rs b/examples/nested_list/src/list.rs old mode 100644 new mode 100755 index 1704e779507..91e6db2acb2 --- a/examples/nested_list/src/list.rs +++ b/examples/nested_list/src/list.rs @@ -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, VNode}; +use yew::virtual_dom::{VChild, VComp}; #[derive(Clone, PartialEq)] pub enum Variants { @@ -41,8 +41,8 @@ where } } -impl From for VNode { - fn from(variant: ListVariant) -> VNode { +impl From for Html { + fn from(variant: ListVariant) -> Html { match variant.props { Variants::Header(props) => { VComp::new::(props, NodeRef::default(), None).into() @@ -113,7 +113,7 @@ impl List { } fn view_items(children: &ChildrenRenderer) -> Html { - let children = children + children .iter() .filter(|c| matches!(&c.props, Variants::Item(props) if !props.hide)) .enumerate() @@ -125,8 +125,6 @@ impl List { } c }) - .collect::(); - - html! {<>{children}} + .collect::() } } diff --git a/packages/yew/src/virtual_dom/key.rs b/packages/yew/src/virtual_dom/key.rs index ce093c1b949..e6ead5ca905 100644 --- a/packages/yew/src/virtual_dom/key.rs +++ b/packages/yew/src/virtual_dom/key.rs @@ -80,7 +80,7 @@ mod test { #[test] fn all_key_conversions() { - (html! { + html! {

    ::from("rc")}>

    @@ -98,6 +98,6 @@ mod test {

    - }); + }; } } diff --git a/packages/yew/src/virtual_dom/vlist.rs b/packages/yew/src/virtual_dom/vlist.rs index 17194c2c429..c0e6b94bc44 100644 --- a/packages/yew/src/virtual_dom/vlist.rs +++ b/packages/yew/src/virtual_dom/vlist.rs @@ -353,6 +353,84 @@ impl VDiff for VList { } } +#[cfg(test)] +mod layout_tests { + extern crate self as yew; + + use crate::html; + use crate::virtual_dom::layout_tests::{diff_layouts, TestLayout}; + + #[cfg(feature = "wasm_test")] + use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure}; + + #[cfg(feature = "wasm_test")] + wasm_bindgen_test_configure!(run_in_browser); + + #[test] + fn diff() { + let layout1 = TestLayout { + name: "1", + node: html! { + <> + {"a"} + {"b"} + <> + {"c"} + {"d"} + + {"e"} + + }, + expected: "abcde", + }; + + let layout2 = TestLayout { + name: "2", + node: html! { + <> + {"a"} + {"b"} + <> + {"e"} + {"f"} + + }, + expected: "abef", + }; + + let layout3 = TestLayout { + name: "3", + node: html! { + <> + {"a"} + <> + {"b"} + {"e"} + + }, + expected: "abe", + }; + + let layout4 = TestLayout { + name: "4", + node: html! { + <> + {"a"} + <> + {"c"} + {"d"} + + {"b"} + {"e"} + + }, + expected: "acdbe", + }; + + diff_layouts(vec![layout1, layout2, layout3, layout4]); + } +} + #[cfg(test)] mod layout_tests_keys { extern crate self as yew; diff --git a/packages/yew/src/virtual_dom/vtext.rs b/packages/yew/src/virtual_dom/vtext.rs index 8d62d44ba8c..4e045a7a03d 100644 --- a/packages/yew/src/virtual_dom/vtext.rs +++ b/packages/yew/src/virtual_dom/vtext.rs @@ -104,6 +104,30 @@ impl PartialEq for VText { } } +#[cfg(test)] +mod test { + extern crate self as yew; + + use crate::html; + + #[cfg(feature = "wasm_test")] + use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure}; + + #[cfg(feature = "wasm_test")] + wasm_bindgen_test_configure!(run_in_browser); + + #[test] + fn text_as_root() { + html! { + "Text Node As Root" + }; + + html! { + { "Text Node As Root" } + }; + } +} + #[cfg(test)] mod layout_tests { extern crate self as yew; diff --git a/website/docs/concepts/components/children.mdx b/website/docs/concepts/components/children.mdx old mode 100644 new mode 100755 index 585e1955b75..4bdb93f202d --- a/website/docs/concepts/components/children.mdx +++ b/website/docs/concepts/components/children.mdx @@ -4,7 +4,7 @@ title: "Children" ## General usage -_Most of the time,_ when allowing a component to have children, you don't care +_Most of the time,_ when allowing a component to have children, you don't care what type of children the component has. In such cases, the below example will suffice. @@ -99,8 +99,8 @@ for better ergonomics. If you don't want to use it, you can manually implement ```rust use yew::{ - html, html::ChildrenRenderer, virtual_dom::VChild, Component, - Context, Html, Properties, virtual_dom::VNode + html, html::ChildrenRenderer, virtual_dom::VChild, Component, + Context, Html, Properties, }; pub struct Primary; @@ -145,8 +145,8 @@ pub enum Item { // Now, we implement `Into` so that yew knows how to render `Item`. #[allow(clippy::from_over_into)] -impl Into for Item { - fn into(self) -> VNode { +impl Into for Item { + fn into(self) -> Html { match self { Self::Primary(child) => child.into(), Self::Secondary(child) => child.into(), @@ -181,12 +181,12 @@ impl Component for List { ``` ### Optional typed child -You can also have a single optional child component of a specific type too: +You can also have a single optional child component of a specific type too: ```rust use yew::{ - html, html_nested, virtual_dom::VChild, Component, - Context, Html, Properties, virtual_dom::VNode + html, html_nested, virtual_dom::VChild, Component, + Context, Html, Properties }; pub struct PageSideBar; @@ -225,14 +225,14 @@ impl Component for Page { fn view(&self, ctx: &Context) -> Html { html! {
    - { Html::Ok(ctx.props().sidebar.clone().map(VNode::from).unwrap_or_default()) } + { ctx.props().sidebar.clone().map(Html::from).unwrap_or_default() } // ... page content
    } } } -// The page component can be called either with the sidebar or without: +// The page component can be called either with the sidebar or without: pub fn render_page(with_sidebar: bool) -> Html { if with_sidebar { diff --git a/website/docs/concepts/components/introduction.mdx b/website/docs/concepts/components/introduction.mdx old mode 100644 new mode 100755 index 26c756b3fea..3c11cab0e43 --- a/website/docs/concepts/components/introduction.mdx +++ b/website/docs/concepts/components/introduction.mdx @@ -19,8 +19,8 @@ in the lifecycle of a component. ### Create -When a component is created, it receives properties from its parent component and is stored within -the `Context` thats passed down to the `create` method. The properties can be used to +When a component is created, it receives properties from its parent component and is stored within +the `Context` thats passed down to the `create` method. The properties can be used to initialize the component's state and the "link" can be used to register callbacks or send messages to the component. ```rust @@ -195,7 +195,7 @@ impl Component for MyComponent { html! { // impl } - } + } } ``` @@ -221,7 +221,7 @@ the same component after every render when that update also requests the compone A simple example can be seen below: ```rust -use yew::{Context, Component, Html, HtmlDefault}; +use yew::{Context, Component, Html}; struct Comp; @@ -302,3 +302,4 @@ enum Msg { All component lifecycle methods take a context object. This object provides a reference to component's scope, which allows sending messages to a component and the props passed to the component. + diff --git a/website/docs/concepts/html/elements.mdx b/website/docs/concepts/html/elements.mdx old mode 100644 new mode 100755 index 1ee1b21f2f7..f75279c732f --- a/website/docs/concepts/html/elements.mdx +++ b/website/docs/concepts/html/elements.mdx @@ -17,7 +17,6 @@ used as a `Html` value using `VRef`: ```rust use web_sys::{Element, Node}; use yew::{Component, Context, html, Html}; -use yew::virtual_dom::VNode; use gloo_utils::document; struct Comp; @@ -38,7 +37,7 @@ impl Component for Comp { // Convert Element into a Node let node: Node = div.into(); // Return that Node as a Html value - Ok(VNode::VRef(node)) + Html::VRef(node) } } ``` diff --git a/website/docs/concepts/html/lists.mdx b/website/docs/concepts/html/lists.mdx old mode 100644 new mode 100755 index 43b66a90343..8a104130f2c --- a/website/docs/concepts/html/lists.mdx +++ b/website/docs/concepts/html/lists.mdx @@ -17,13 +17,12 @@ list that Yew can display. ```rust use yew::{html, Html}; -use yew::virtual_dom::VNode; let items = (1..=10).collect::>(); html! {
      - { items.iter().collect::() } + { items.iter().collect::() }
    }; ``` From b664e5f3c66cd53a19cd4940539c848ec1091fad Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Mon, 20 Dec 2021 22:55:41 +0900 Subject: [PATCH 19/27] Fix tests. --- packages/yew-macro/src/function_component.rs | 296 +++++++++--------- .../bad-return-type-fail.stderr | 20 +- .../function_component_attr/generic-pass.rs | 19 +- .../generic-props-fail.stderr | 28 +- .../component-unimplemented-fail.stderr | 6 +- packages/yew/src/functional/hooks/use_ref.rs | 3 +- packages/yew/src/lib.rs | 6 +- packages/yew/tests/use_context.rs | 36 +-- packages/yew/tests/use_effect.rs | 14 +- packages/yew/tests/use_reducer.rs | 8 +- packages/yew/tests/use_ref.rs | 4 +- packages/yew/tests/use_state.rs | 8 +- website/docs/advanced-topics/portals.mdx | 2 +- .../function-components/attribute.mdx | 6 +- .../function-components/custom-hooks.mdx | 10 +- .../function-components/introduction.mdx | 2 +- .../function-components/pre-defined-hooks.mdx | 17 +- 17 files changed, 255 insertions(+), 230 deletions(-) diff --git a/packages/yew-macro/src/function_component.rs b/packages/yew-macro/src/function_component.rs index 061264cf873..12e77250085 100644 --- a/packages/yew-macro/src/function_component.rs +++ b/packages/yew-macro/src/function_component.rs @@ -1,148 +1,146 @@ use proc_macro2::{Span, TokenStream}; -use quote::{quote, ToTokens}; // , quote_spanned +use quote::{quote, ToTokens}; use syn::parse::{Parse, ParseStream}; use syn::punctuated::Punctuated; -// use syn::spanned::Spanned; -use syn::token::Comma; -use syn::{Attribute, FnArg, Generics, Ident, Item, ItemFn, ReturnType, Type, Visibility}; // Block +use syn::token::{Comma, Fn}; +use syn::{Attribute, Block, FnArg, Generics, Ident, Item, ItemFn, ReturnType, Type, Visibility}; +#[derive(Clone)] pub struct FunctionComponent { - // block: Box, + block: Box, props_type: Box, - // arg: FnArg, + arg: FnArg, generics: Generics, vis: Visibility, attrs: Vec, name: Ident, - // return_type: Box, - func: ItemFn, - - has_arg: bool, + return_type: Box, + fn_token: Fn, } impl Parse for FunctionComponent { fn parse(input: ParseStream) -> syn::Result { let parsed: Item = input.parse()?; - match parsed { - Item::Fn(func) => { - let ItemFn { - attrs, - vis, - sig, - // block, - .. - } = func.clone(); - - if sig.generics.lifetimes().next().is_some() { - return Err(syn::Error::new_spanned( - sig.generics, - "function components can't have generic lifetime parameters", - )); - } + let func = match parsed { + Item::Fn(m) => m, - if sig.asyncness.is_some() { - return Err(syn::Error::new_spanned( - sig.asyncness, - "function components can't be async", - )); - } + item => { + return Err(syn::Error::new_spanned( + item, + "`function_component` attribute can only be applied to functions", + )) + } + }; + + let ItemFn { + attrs, + vis, + sig, + block, + } = func; + + if sig.generics.lifetimes().next().is_some() { + return Err(syn::Error::new_spanned( + sig.generics, + "function components can't have generic lifetime parameters", + )); + } - if sig.constness.is_some() { - return Err(syn::Error::new_spanned( - sig.constness, - "const functions can't be function components", - )); - } + if sig.asyncness.is_some() { + return Err(syn::Error::new_spanned( + sig.asyncness, + "function components can't be async", + )); + } - if sig.abi.is_some() { - return Err(syn::Error::new_spanned( - sig.abi, - "extern functions can't be function components", - )); - } + if sig.constness.is_some() { + return Err(syn::Error::new_spanned( + sig.constness, + "const functions can't be function components", + )); + } - let _return_type = match sig.output { - ReturnType::Default => { + if sig.abi.is_some() { + return Err(syn::Error::new_spanned( + sig.abi, + "extern functions can't be function components", + )); + } + + let return_type = match sig.output { + ReturnType::Default => { + return Err(syn::Error::new_spanned( + sig, + "function components must return `yew::Html` or `yew::HtmlResult`", + )) + } + ReturnType::Type(_, ty) => ty, + }; + + let mut inputs = sig.inputs.into_iter(); + let arg = inputs + .next() + .unwrap_or_else(|| syn::parse_quote! { _: &() }); + + let ty = match &arg { + FnArg::Typed(arg) => match &*arg.ty { + Type::Reference(ty) => { + if ty.lifetime.is_some() { return Err(syn::Error::new_spanned( - sig, - "function components must return `yew::Html` or `yew::HtmlResult`", - )) + &ty.lifetime, + "reference must not have a lifetime", + )); } - ReturnType::Type(_, ty) => ty, - }; - - let mut inputs = sig.inputs.into_iter(); - let (arg, has_arg) = inputs - .next() - .map(|m| (m, true)) - .unwrap_or_else(|| (syn::parse_quote! { _: &() }, false)); - - let ty = match &arg { - FnArg::Typed(arg) => match &*arg.ty { - Type::Reference(ty) => { - if ty.lifetime.is_some() { - return Err(syn::Error::new_spanned( - &ty.lifetime, - "reference must not have a lifetime", - )); - } - - if ty.mutability.is_some() { - return Err(syn::Error::new_spanned( - &ty.mutability, - "reference must not be mutable", - )); - } - - ty.elem.clone() - } - ty => { - let msg = format!( - "expected a reference to a `Properties` type (try: `&{}`)", - ty.to_token_stream() - ); - return Err(syn::Error::new_spanned(ty, msg)); - } - }, - - FnArg::Receiver(_) => { + + if ty.mutability.is_some() { return Err(syn::Error::new_spanned( - arg, - "function components can't accept a receiver", + &ty.mutability, + "reference must not be mutable", )); } - }; - - // Checking after param parsing may make it a little inefficient - // but that's a requirement for better error messages in case of receivers - // `>0` because first one is already consumed. - if inputs.len() > 0 { - let params: TokenStream = inputs.map(|it| it.to_token_stream()).collect(); - return Err(syn::Error::new_spanned( - params, - "function components can accept at most one parameter for the props", - )); + + ty.elem.clone() + } + ty => { + let msg = format!( + "expected a reference to a `Properties` type (try: `&{}`)", + ty.to_token_stream() + ); + return Err(syn::Error::new_spanned(ty, msg)); } + }, - Ok(Self { - props_type: ty, - // block, - // arg, - generics: sig.generics, - vis, - attrs, - name: sig.ident, - // return_type, - func, - has_arg, - }) + FnArg::Receiver(_) => { + return Err(syn::Error::new_spanned( + arg, + "function components can't accept a receiver", + )); } - item => Err(syn::Error::new_spanned( - item, - "`function_component` attribute can only be applied to functions", - )), + }; + + // Checking after param parsing may make it a little inefficient + // but that's a requirement for better error messages in case of receivers + // `>0` because first one is already consumed. + if inputs.len() > 0 { + let params: TokenStream = inputs.map(|it| it.to_token_stream()).collect(); + return Err(syn::Error::new_spanned( + params, + "function components can accept at most one parameter for the props", + )); } + + Ok(Self { + props_type: ty, + block, + arg, + generics: sig.generics, + vis, + attrs, + name: sig.ident, + return_type, + fn_token: sig.fn_token, + }) } } @@ -162,23 +160,44 @@ impl Parse for FunctionComponentName { } } +fn print_fn(func_comp: FunctionComponent) -> TokenStream { + let FunctionComponent { + fn_token, + name, + attrs, + block, + return_type, + generics, + arg, + .. + } = func_comp; + + let (_impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + quote! { + #(#attrs)* + #fn_token #name #ty_generics (#arg) -> #return_type + #where_clause + { + #block + } + } +} + pub fn function_component_impl( name: FunctionComponentName, component: FunctionComponent, ) -> syn::Result { let FunctionComponentName { component_name } = name; + let func = print_fn(component.clone()); + let FunctionComponent { - // block, props_type, - // arg, generics, vis, - attrs, name: function_name, - // return_type, - func, - has_arg, + .. } = component; let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); @@ -202,40 +221,20 @@ pub fn function_component_impl( let provider_name = Ident::new(&provider_name_str, Span::mixed_site()); - // let ret_type = quote_spanned!(return_type.span()=> ::yew::html::Html); - let phantom_generics = generics .type_params() .map(|ty_param| ty_param.ident.clone()) // create a new Punctuated sequence without any type bounds .collect::>(); - let run_impl = if has_arg { - let provider_props = Ident::new("props", Span::mixed_site()); + let provider_props = Ident::new("props", Span::mixed_site()); - quote! { - fn run(#provider_props: &Self::TProps) -> ::yew::html::HtmlResult { - #func - - #function_name(#provider_props).into() - } - } - } else { - let provider_props = Ident::new("_props", Span::mixed_site()); - - quote! { - fn run(#provider_props: &Self::TProps) -> ::yew::html::HtmlResult { - #func - - #function_name().into() - } - } - }; + let fn_generics = ty_generics.as_turbofish(); let quoted = quote! { #[doc(hidden)] #[allow(non_camel_case_types)] #[allow(unused_parens)] - #vis struct #provider_name #generics { + #vis struct #provider_name #ty_generics { _marker: ::std::marker::PhantomData<(#phantom_generics)>, } @@ -243,10 +242,13 @@ pub fn function_component_impl( impl #impl_generics ::yew::functional::FunctionProvider for #provider_name #ty_generics #where_clause { type TProps = #props_type; - #run_impl + fn run(#provider_props: &Self::TProps) -> ::yew::html::HtmlResult { + #func + + ::std::convert::Into::<::yew::html::HtmlResult>::into(#function_name #fn_generics (#provider_props)) + } } - #(#attrs)* #[allow(type_alias_bounds)] #vis type #component_name #generics = ::yew::functional::FunctionComponent<#provider_name #ty_generics>; }; diff --git a/packages/yew-macro/tests/function_component_attr/bad-return-type-fail.stderr b/packages/yew-macro/tests/function_component_attr/bad-return-type-fail.stderr index 0a5d227dd08..99dc2388659 100644 --- a/packages/yew-macro/tests/function_component_attr/bad-return-type-fail.stderr +++ b/packages/yew-macro/tests/function_component_attr/bad-return-type-fail.stderr @@ -1,13 +1,17 @@ -error: function components must return `yew::Html` - --> $DIR/bad-return-type-fail.rs:9:1 +error: function components must return `yew::Html` or `yew::HtmlResult` + --> tests/function_component_attr/bad-return-type-fail.rs:9:1 | 9 | fn comp_1(_props: &Props) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^ -error[E0308]: mismatched types - --> $DIR/bad-return-type-fail.rs:13:5 +error[E0277]: the trait bound `std::result::Result: From` is not satisfied + --> tests/function_component_attr/bad-return-type-fail.rs:11:1 | -12 | fn comp(_props: &Props) -> u32 { - | --- expected `VNode` because of return type -13 | 1 - | ^ expected enum `VNode`, found integer +11 | #[function_component(Comp)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `From` is not implemented for `std::result::Result` + | + = help: the following implementations were found: + as From> + = note: required because of the requirements on the impl of `Into>` for `u32` + = note: required by `into` + = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/packages/yew-macro/tests/function_component_attr/generic-pass.rs b/packages/yew-macro/tests/function_component_attr/generic-pass.rs index 29df3007d46..3672a4475cb 100644 --- a/packages/yew-macro/tests/function_component_attr/generic-pass.rs +++ b/packages/yew-macro/tests/function_component_attr/generic-pass.rs @@ -58,20 +58,21 @@ fn comp1(_props: &()) -> ::yew::Html { } } -#[::yew::function_component(ConstGenerics)] -fn const_generics() -> ::yew::Html { - ::yew::html! { -
    - { N } -
    - } -} +// no longer possible? +// #[::yew::function_component(ConstGenerics)] +// fn const_generics() -> ::yew::Html { +// ::yew::html! { +//
    +// { N } +//
    +// } +// } fn compile_pass() { ::yew::html! { a=10 /> }; ::yew::html! { /> }; - ::yew::html! { /> }; + // ::yew::html! { /> }; } fn main() {} diff --git a/packages/yew-macro/tests/function_component_attr/generic-props-fail.stderr b/packages/yew-macro/tests/function_component_attr/generic-props-fail.stderr index eccd889da64..2eb39e029e6 100644 --- a/packages/yew-macro/tests/function_component_attr/generic-props-fail.stderr +++ b/packages/yew-macro/tests/function_component_attr/generic-props-fail.stderr @@ -16,27 +16,41 @@ error[E0599]: no method named `build` found for struct `PropsBuilder /> }; | ^^^^ method not found in `PropsBuilder` -error[E0277]: the trait bound `MissingTypeBounds: yew::Properties` is not satisfied +error[E0277]: the trait bound `FunctionComponent>: BaseComponent` is not satisfied --> tests/function_component_attr/generic-props-fail.rs:27:14 | 27 | html! { /> }; - | ^^^^ the trait `yew::Properties` is not implemented for `MissingTypeBounds` + | ^^^^ the trait `BaseComponent` is not implemented for `FunctionComponent>` | - = note: required because of the requirements on the impl of `FunctionProvider` for `comp` + = help: the following implementations were found: + as BaseComponent> -error[E0599]: the function or associated item `new` exists for struct `VChild>>`, but its trait bounds were not satisfied +error[E0599]: the function or associated item `new` exists for struct `VChild>>`, but its trait bounds were not satisfied --> tests/function_component_attr/generic-props-fail.rs:27:14 | 27 | html! { /> }; - | ^^^^ function or associated item cannot be called on `VChild>>` due to unsatisfied trait bounds + | ^^^^ function or associated item cannot be called on `VChild>>` due to unsatisfied trait bounds | ::: $WORKSPACE/packages/yew/src/functional/mod.rs | | pub struct FunctionComponent { - | ----------------------------------------------------------- doesn't satisfy `_: yew::Component` + | ----------------------------------------------------------- doesn't satisfy `_: BaseComponent` | = note: the following trait bounds were not satisfied: - `FunctionComponent>: yew::Component` + `FunctionComponent>: BaseComponent` + +error[E0277]: the trait bound `MissingTypeBounds: yew::Properties` is not satisfied + --> tests/function_component_attr/generic-props-fail.rs:27:14 + | +27 | html! { /> }; + | ^^^^ the trait `yew::Properties` is not implemented for `MissingTypeBounds` + | + ::: $WORKSPACE/packages/yew/src/functional/mod.rs + | + | pub struct FunctionComponent { + | ---------------- required by this bound in `FunctionComponent` + | + = note: required because of the requirements on the impl of `FunctionProvider` for `CompInternal` error[E0107]: missing generics for type alias `Comp` --> tests/function_component_attr/generic-props-fail.rs:30:14 diff --git a/packages/yew-macro/tests/html_macro/component-unimplemented-fail.stderr b/packages/yew-macro/tests/html_macro/component-unimplemented-fail.stderr index 055b47b4db9..4b062ceeda5 100644 --- a/packages/yew-macro/tests/html_macro/component-unimplemented-fail.stderr +++ b/packages/yew-macro/tests/html_macro/component-unimplemented-fail.stderr @@ -3,15 +3,17 @@ error[E0277]: the trait bound `Unimplemented: yew::Component` is not satisfied | 6 | html! { }; | ^^^^^^^^^^^^^ the trait `yew::Component` is not implemented for `Unimplemented` + | + = note: required because of the requirements on the impl of `BaseComponent` for `Unimplemented` error[E0599]: the function or associated item `new` exists for struct `VChild`, but its trait bounds were not satisfied --> tests/html_macro/component-unimplemented-fail.rs:6:14 | 3 | struct Unimplemented; - | --------------------- doesn't satisfy `Unimplemented: yew::Component` + | --------------------- doesn't satisfy `Unimplemented: BaseComponent` ... 6 | html! { }; | ^^^^^^^^^^^^^ function or associated item cannot be called on `VChild` due to unsatisfied trait bounds | = note: the following trait bounds were not satisfied: - `Unimplemented: yew::Component` + `Unimplemented: BaseComponent` diff --git a/packages/yew/src/functional/hooks/use_ref.rs b/packages/yew/src/functional/hooks/use_ref.rs index cf633d26045..d334a4bbee2 100644 --- a/packages/yew/src/functional/hooks/use_ref.rs +++ b/packages/yew/src/functional/hooks/use_ref.rs @@ -77,7 +77,8 @@ pub fn use_ref(initial_value: impl FnOnce() -> T) -> Rc { /// ```rust /// # use wasm_bindgen::{prelude::Closure, JsCast}; /// # use yew::{ -/// # function_component, html, use_effect_with_deps, use_node_ref +/// # function_component, html, use_effect_with_deps, use_node_ref, +/// # Html, /// # }; /// # use web_sys::{Event, HtmlElement}; /// diff --git a/packages/yew/src/lib.rs b/packages/yew/src/lib.rs index 2d44be2d3c2..32d3b381d36 100644 --- a/packages/yew/src/lib.rs +++ b/packages/yew/src/lib.rs @@ -126,7 +126,7 @@ pub use yew_macro::html; /// ``` /// # use yew::prelude::*; /// use yew::html::ChildrenRenderer; -/// use yew::virtual_dom::{VChild, VNode}; +/// use yew::virtual_dom::VChild; /// /// #[derive(Clone, Properties, PartialEq)] /// struct ListProps { @@ -157,8 +157,8 @@ pub use yew_macro::html; /// fn from(child: VChild) -> Self { Self } /// } /// -/// impl Into for ListItem { -/// fn into(self) -> VNode { (html! { }).unwrap() } +/// impl Into for ListItem { +/// fn into(self) -> Html { html! { } } /// } /// // You can use `List` with nested `ListItem` components. /// // Using any other kind of element would result in a compile error. diff --git a/packages/yew/tests/use_context.rs b/packages/yew/tests/use_context.rs index c347b3b1f9a..4fffc969e94 100644 --- a/packages/yew/tests/use_context.rs +++ b/packages/yew/tests/use_context.rs @@ -30,9 +30,9 @@ fn use_context_scoping_works() { use_context::().unwrap() ); }; - return Ok(html! { + Ok(html! {
    - }); + }) } } impl FunctionProvider for UseContextFunctionOuter { @@ -40,7 +40,7 @@ fn use_context_scoping_works() { fn run(_props: &Self::TProps) -> HtmlResult { type ExampleContextProvider = ContextProvider; - return Ok(html! { + Ok(html! {
    {"ignored"}
    @@ -58,7 +58,7 @@ fn use_context_scoping_works() {
    - }); + }) } } impl FunctionProvider for UseContextFunctionInner { @@ -66,9 +66,9 @@ fn use_context_scoping_works() { fn run(_props: &Self::TProps) -> HtmlResult { let context = use_context::(); - return Ok(html! { + Ok(html! {
    { &context.unwrap().0 }
    - }); + }) } } @@ -94,7 +94,7 @@ fn use_context_works_with_multiple_types() { assert_eq!(use_context::(), Some(ContextA(2))); assert_eq!(use_context::(), Some(ContextB(1))); - return Ok(html! {}); + Ok(html! {}) } } type Test1 = FunctionComponent; @@ -107,7 +107,7 @@ fn use_context_works_with_multiple_types() { assert_eq!(use_context::(), Some(ContextA(0))); assert_eq!(use_context::(), Some(ContextB(1))); - return Ok(html! {}); + Ok(html! {}) } } type Test2 = FunctionComponent; @@ -120,7 +120,7 @@ fn use_context_works_with_multiple_types() { assert_eq!(use_context::(), Some(ContextA(0))); assert_eq!(use_context::(), None); - return Ok(html! {}); + Ok(html! {}) } } type Test3 = FunctionComponent; @@ -133,7 +133,7 @@ fn use_context_works_with_multiple_types() { assert_eq!(use_context::(), None); assert_eq!(use_context::(), None); - return Ok(html! {}); + Ok(html! {}) } } type Test4 = FunctionComponent; @@ -146,7 +146,7 @@ fn use_context_works_with_multiple_types() { type ContextAProvider = ContextProvider; type ContextBProvider = ContextProvider; - return Ok(html! { + Ok(html! {
    @@ -159,7 +159,7 @@ fn use_context_works_with_multiple_types() {
    - }); + }) } } type TestComponent = FunctionComponent; @@ -187,14 +187,14 @@ fn use_context_update_works() { fn run(props: &Self::TProps) -> HtmlResult { let counter = use_mut_ref(|| 0); *counter.borrow_mut() += 1; - return Ok(html! { + Ok(html! { <>
    { format!("total: {}", counter.borrow()) }
    { props.children.clone() } - }); + }) } } type RenderCounter = FunctionComponent; @@ -215,14 +215,14 @@ fn use_context_update_works() { let ctx = use_context::>().expect("context not passed down"); - return Ok(html! { + Ok(html! { <>
    { format!("magic: {}\n", props.magic) }
    { format!("current: {}, total: {}", ctx.0, counter.borrow()) }
    - }); + }) } } type ContextOutlet = FunctionComponent; @@ -263,14 +263,14 @@ fn use_context_update_works() { || {} }); } - return Ok(html! { + Ok(html! { - }); + }) } } type TestComponent = FunctionComponent; diff --git a/packages/yew/tests/use_effect.rs b/packages/yew/tests/use_effect.rs index c5bd876c1bb..bf064a87064 100644 --- a/packages/yew/tests/use_effect.rs +++ b/packages/yew/tests/use_effect.rs @@ -50,7 +50,7 @@ fn use_effect_destroys_on_component_drop() { }, (), ); - return Ok(html! {}); + Ok(html! {}) } } impl FunctionProvider for UseEffectWrapper { @@ -101,13 +101,13 @@ fn use_effect_works_many_times() { *counter, ); - return Ok(html! { + Ok(html! {
    { "The test result is" }
    { *counter }
    { "\n" }
    - }); + }) } } @@ -137,13 +137,13 @@ fn use_effect_works_once() { (), ); - return Ok(html! { + Ok(html! {
    { "The test result is" }
    { *counter }
    { "\n" }
    - }); + }) } } type UseEffectComponent = FunctionComponent; @@ -185,13 +185,13 @@ fn use_effect_refires_on_dependency_change() { }, arg, ); - return Ok(html! { + Ok(html! {
    {"The test result is"}
    {*number_ref.borrow_mut().deref_mut()}{*number_ref2.borrow_mut().deref_mut()}
    {"\n"}
    - }); + }) } } type UseEffectComponent = FunctionComponent; diff --git a/packages/yew/tests/use_reducer.rs b/packages/yew/tests/use_reducer.rs index ee430f72a5a..de81203edd6 100644 --- a/packages/yew/tests/use_reducer.rs +++ b/packages/yew/tests/use_reducer.rs @@ -45,13 +45,13 @@ fn use_reducer_works() { }, (), ); - return Ok(html! { + Ok(html! {
    {"The test result is"}
    {counter.counter}
    {"\n"}
    - }); + }) } } type UseReducerComponent = FunctionComponent; @@ -104,7 +104,7 @@ fn use_reducer_eq_works() { let add_content_b = Callback::from(move |_| content.dispatch("B".to_string())); - return Ok(html! { + Ok(html! { <>
    {"This component has been rendered: "}{render_count}{" Time(s)."} @@ -112,7 +112,7 @@ fn use_reducer_eq_works() { - }); + }) } } type UseReducerComponent = FunctionComponent; diff --git a/packages/yew/tests/use_ref.rs b/packages/yew/tests/use_ref.rs index 1ce8e674f4b..c27a0faea61 100644 --- a/packages/yew/tests/use_ref.rs +++ b/packages/yew/tests/use_ref.rs @@ -21,13 +21,13 @@ fn use_ref_works() { if *counter < 5 { counter.set(*counter + 1) } - return Ok(html! { + Ok(html! {
    {"The test output is: "}
    {*ref_example.borrow_mut().deref_mut() > 4}
    {"\n"}
    - }); + }) } } type UseRefComponent = FunctionComponent; diff --git a/packages/yew/tests/use_state.rs b/packages/yew/tests/use_state.rs index a4762450b9e..3a8ea80bc3e 100644 --- a/packages/yew/tests/use_state.rs +++ b/packages/yew/tests/use_state.rs @@ -64,14 +64,14 @@ fn multiple_use_state_setters() { } }; another_scope(); - return Ok(html! { + Ok(html! {
    { "Test Output: " } // expected output
    { *counter }
    { "\n" }
    - }); + }) } } type UseComponent = FunctionComponent; @@ -99,13 +99,13 @@ fn use_state_eq_works() { let counter = use_state_eq(|| 0); counter.set(1); - return Ok(html! { + Ok(html! {
    {"Test Output: "}
    {*counter}
    {"\n"}
    - }); + }) } } type UseComponent = FunctionComponent; diff --git a/website/docs/advanced-topics/portals.mdx b/website/docs/advanced-topics/portals.mdx index f486c273231..7555ea17e0d 100644 --- a/website/docs/advanced-topics/portals.mdx +++ b/website/docs/advanced-topics/portals.mdx @@ -16,7 +16,7 @@ Typical uses of portals can include modal dialogs and hovercards, as well as mor Note that `yew::create_portal` is a rather low-level building block, on which other components should be built that provide the interface for your specific use case. As an example, here is a simple modal dialogue that renders its `children` into an element outside `yew`'s control, identified by the `id="modal_host"`. ```rust -use yew::{html, create_portal, function_component, Children, Properties}; +use yew::{html, create_portal, function_component, Children, Properties, Html}; #[derive(Properties, PartialEq)] pub struct ModalProps { diff --git a/website/docs/concepts/function-components/attribute.mdx b/website/docs/concepts/function-components/attribute.mdx index 96e87c313ee..6bb81f360ad 100644 --- a/website/docs/concepts/function-components/attribute.mdx +++ b/website/docs/concepts/function-components/attribute.mdx @@ -35,7 +35,7 @@ html! { ```rust -use yew::{function_component, html, Properties}; +use yew::{function_component, html, Properties, Html}; #[derive(Properties, PartialEq)] pub struct RenderedAtProps { @@ -57,7 +57,7 @@ pub fn rendered_at(props: &RenderedAtProps) -> Html { ```rust -use yew::{function_component, html, use_state, Callback}; +use yew::{function_component, html, use_state, Callback, Html}; #[function_component(App)] fn app() -> Html { @@ -89,7 +89,7 @@ The `#[function_component(_)]` attribute also works with generic functions for c ```rust title=my_generic_component.rs use std::fmt::Display; -use yew::{function_component, html, Properties}; +use yew::{function_component, html, Properties, Html}; #[derive(Properties, PartialEq)] pub struct Props diff --git a/website/docs/concepts/function-components/custom-hooks.mdx b/website/docs/concepts/function-components/custom-hooks.mdx index 2e38c61716a..96c5fa4aea6 100644 --- a/website/docs/concepts/function-components/custom-hooks.mdx +++ b/website/docs/concepts/function-components/custom-hooks.mdx @@ -5,11 +5,11 @@ description: "Defining your own Hooks " ## Defining custom Hooks -Component's stateful logic can be extracted into usable function by creating custom Hooks. +Component's stateful logic can be extracted into usable function by creating custom Hooks. Consider that we have a component which subscribes to an agent and displays the messages sent to it. ```rust -use yew::{function_component, html, use_effect, use_state, Callback}; +use yew::{function_component, html, use_effect, use_state, Callback, Html}; use yew_agent::Bridged; // EventBus is an implementation yew_agent::Agent use website_test::agents::EventBus; @@ -51,9 +51,9 @@ fn use_subscribe() -> Rc>> { } ``` -This is a simple hook which can be created by combining other hooks. For this example, we'll two pre-defined hooks. +This is a simple hook which can be created by combining other hooks. For this example, we'll two pre-defined hooks. We'll use `use_state` hook to store the `Vec` for messages, so they persist between component re-renders. -We'll also use `use_effect` to subscribe to the `EventBus` `Agent` so the subscription can be tied to component's lifecycle. +We'll also use `use_effect` to subscribe to the `EventBus` `Agent` so the subscription can be tied to component's lifecycle. ```rust use std::collections::HashSet; @@ -80,7 +80,7 @@ fn use_subscribe() -> Vec { } ``` -Although this approach works in almost all cases, it can't be used to write primitive hooks like the pre-defined hooks we've been using already +Although this approach works in almost all cases, it can't be used to write primitive hooks like the pre-defined hooks we've been using already ### Writing primitive hooks diff --git a/website/docs/concepts/function-components/introduction.mdx b/website/docs/concepts/function-components/introduction.mdx index 05d14fcd5af..b64a98d69a7 100644 --- a/website/docs/concepts/function-components/introduction.mdx +++ b/website/docs/concepts/function-components/introduction.mdx @@ -16,7 +16,7 @@ implement the `Component` trait. The easiest way to create a function component is to add the [`#[function_component]`](./../function-components/attribute.mdx) attribute to a function. ```rust -use yew::{function_component, html}; +use yew::{function_component, html, Html}; #[function_component(HelloWorld)] fn hello_world() -> Html { diff --git a/website/docs/concepts/function-components/pre-defined-hooks.mdx b/website/docs/concepts/function-components/pre-defined-hooks.mdx index 2703fcf28c8..a79860eba73 100644 --- a/website/docs/concepts/function-components/pre-defined-hooks.mdx +++ b/website/docs/concepts/function-components/pre-defined-hooks.mdx @@ -24,7 +24,7 @@ re-render when the state changes. ### Example ```rust -use yew::{Callback, function_component, html, use_state}; +use yew::{Callback, function_component, html, use_state, Html}; #[function_component(UseState)] fn state() -> Html { @@ -68,7 +68,7 @@ This hook requires the state object to implement `PartialEq`. `use_ref` is used for obtaining an immutable reference to a value. Its state persists across renders. -`use_ref` can be useful for keeping things in scope for the lifetime of the component, so long as +`use_ref` can be useful for keeping things in scope for the lifetime of the component, so long as you don't store a clone of the resulting `Rc` anywhere that outlives the component. If you need a mutable reference, consider using [`use_mut_ref`](#use_mut_ref). @@ -77,11 +77,11 @@ If you need the component to be re-rendered on state change, consider using [`us ```rust // EventBus is an implementation of yew_agent::Agent use website_test::agents::EventBus; -use yew::{function_component, html, use_ref, use_state, Callback}; +use yew::{function_component, html, use_ref, use_state, Callback, Html}; use yew_agent::Bridged; #[function_component(UseRef)] -fn ref_hook() -> Html { +fn ref_hook() -> Html { let greeting = use_state(|| "No one has greeted me yet!".to_owned()); { @@ -97,7 +97,7 @@ fn ref_hook() -> Html {
    } } -``` +``` ## `use_mut_ref` `use_mut_ref` is used for obtaining a mutable reference to a value. @@ -114,6 +114,7 @@ use yew::{ events::Event, function_component, html, use_mut_ref, use_state, Callback, TargetCast, + Html, }; #[function_component(UseMutRef)] @@ -162,7 +163,7 @@ DOM. use web_sys::HtmlInputElement; use yew::{ function_component, functional::*, html, - NodeRef + NodeRef, Html }; #[function_component(UseRef)] @@ -306,7 +307,7 @@ The destructor can be used to clean up the effects introduced and it can take ow ### Example ```rust -use yew::{Callback, function_component, html, use_effect, use_state}; +use yew::{Callback, function_component, html, use_effect, use_state, Html}; #[function_component(UseEffect)] fn effect() -> Html { @@ -358,7 +359,7 @@ use_effect_with_deps( ### Example ```rust -use yew::{ContextProvider, function_component, html, use_context, use_state}; +use yew::{ContextProvider, function_component, html, use_context, use_state, Html}; /// App theme From 066662d4dad71a04c72c327b00a9800130e41cfe Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Mon, 20 Dec 2021 23:17:26 +0900 Subject: [PATCH 20/27] Update documentation. --- packages/yew-macro/src/function_component.rs | 2 +- .../bad-return-type-fail.stderr | 3 +- packages/yew/src/lib.rs | 4 +- website/docs/concepts/suspense.md | 43 +++++++++++++++++-- 4 files changed, 44 insertions(+), 8 deletions(-) diff --git a/packages/yew-macro/src/function_component.rs b/packages/yew-macro/src/function_component.rs index 12e77250085..f80fa542ca6 100644 --- a/packages/yew-macro/src/function_component.rs +++ b/packages/yew-macro/src/function_component.rs @@ -245,7 +245,7 @@ pub fn function_component_impl( fn run(#provider_props: &Self::TProps) -> ::yew::html::HtmlResult { #func - ::std::convert::Into::<::yew::html::HtmlResult>::into(#function_name #fn_generics (#provider_props)) + <::yew::html::HtmlResult as ::std::convert::From<_>>::from(#function_name #fn_generics (#provider_props)) } } diff --git a/packages/yew-macro/tests/function_component_attr/bad-return-type-fail.stderr b/packages/yew-macro/tests/function_component_attr/bad-return-type-fail.stderr index 99dc2388659..1605c9e7389 100644 --- a/packages/yew-macro/tests/function_component_attr/bad-return-type-fail.stderr +++ b/packages/yew-macro/tests/function_component_attr/bad-return-type-fail.stderr @@ -12,6 +12,5 @@ error[E0277]: the trait bound `std::result::Result: From as From> - = note: required because of the requirements on the impl of `Into>` for `u32` - = note: required by `into` + = note: required by `from` = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/packages/yew/src/lib.rs b/packages/yew/src/lib.rs index 32d3b381d36..2e999ac81c3 100644 --- a/packages/yew/src/lib.rs +++ b/packages/yew/src/lib.rs @@ -389,8 +389,8 @@ pub mod prelude { pub use crate::context::ContextProvider; pub use crate::events::*; pub use crate::html::{ - create_portal, Children, ChildrenWithProps, Classes, Component, Context, Html, HtmlResult, - NodeRef, Properties, + create_portal, BaseComponent, Children, ChildrenWithProps, Classes, Component, Context, + Html, HtmlResult, NodeRef, Properties, }; pub use crate::macros::{classes, html, html_nested}; pub use crate::suspense::Suspense; diff --git a/website/docs/concepts/suspense.md b/website/docs/concepts/suspense.md index db98888affc..13978e8e57f 100644 --- a/website/docs/concepts/suspense.md +++ b/website/docs/concepts/suspense.md @@ -79,6 +79,43 @@ fn use_user() -> SuspensionResult { ### Use Suspense in Struct Components -Whilst it's possible to suspend a struct component, it's behaviour is -not well-defined and not recommended. You should consider using function -components instead when using ``. +It's not possible to suspend a struct component directly. However, you +can use a function component as a Higher-Order-Component to +achieve suspense-based data fetching. + +```rust, ignore +use yew::prelude::*; + +#[function_component(WithUser)] +fn with_user() -> HtmlResult +where T: BaseComponent +{ + let user = use_user()?; + + Ok(html! {}) +} + +#[derive(Debug, PartialEq, Properties)] +pub struct UserContentProps { + pub user: User, +} + +pub struct BaseUserContent; + +impl Component for BaseUserContent { + type Properties = UserContentProps; + type Message = (); + + fn create(ctx: &Context) -> Self { + Self + } + + fn view(&self, ctx: &Context) -> Html { + let name = ctx.props().user.name; + + html! {
    {"Hello, "}{name}{"!"}
    } + } +} + +pub type UserContent = WithUser; +``` From f097303a9e73d6f870211cbe54e58520b79ca865 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Mon, 20 Dec 2021 23:47:17 +0900 Subject: [PATCH 21/27] Switch to custom trait to make test reliable. --- packages/yew-macro/src/function_component.rs | 2 +- .../bad-return-type-fail.stderr | 8 +++---- packages/yew/src/html/component/mod.rs | 4 ++-- packages/yew/src/html/mod.rs | 22 ++++++++++++++++--- packages/yew/src/lib.rs | 1 + packages/yew/src/sealed.rs | 2 ++ 6 files changed, 28 insertions(+), 11 deletions(-) create mode 100644 packages/yew/src/sealed.rs diff --git a/packages/yew-macro/src/function_component.rs b/packages/yew-macro/src/function_component.rs index f80fa542ca6..aafb7f59941 100644 --- a/packages/yew-macro/src/function_component.rs +++ b/packages/yew-macro/src/function_component.rs @@ -245,7 +245,7 @@ pub fn function_component_impl( fn run(#provider_props: &Self::TProps) -> ::yew::html::HtmlResult { #func - <::yew::html::HtmlResult as ::std::convert::From<_>>::from(#function_name #fn_generics (#provider_props)) + ::yew::html::IntoHtmlResult::into_html_result(#function_name #fn_generics (#provider_props)) } } diff --git a/packages/yew-macro/tests/function_component_attr/bad-return-type-fail.stderr b/packages/yew-macro/tests/function_component_attr/bad-return-type-fail.stderr index 1605c9e7389..14bca2acab2 100644 --- a/packages/yew-macro/tests/function_component_attr/bad-return-type-fail.stderr +++ b/packages/yew-macro/tests/function_component_attr/bad-return-type-fail.stderr @@ -4,13 +4,11 @@ error: function components must return `yew::Html` or `yew::HtmlResult` 9 | fn comp_1(_props: &Props) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^ -error[E0277]: the trait bound `std::result::Result: From` is not satisfied +error[E0277]: the trait bound `u32: IntoHtmlResult` is not satisfied --> tests/function_component_attr/bad-return-type-fail.rs:11:1 | 11 | #[function_component(Comp)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `From` is not implemented for `std::result::Result` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IntoHtmlResult` is not implemented for `u32` | - = help: the following implementations were found: - as From> - = note: required by `from` + = note: required by `into_html_result` = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/packages/yew/src/html/component/mod.rs b/packages/yew/src/html/component/mod.rs index 4a43faf363f..8bf7b2198bf 100644 --- a/packages/yew/src/html/component/mod.rs +++ b/packages/yew/src/html/component/mod.rs @@ -5,7 +5,7 @@ mod lifecycle; mod properties; mod scope; -use super::{Html, HtmlResult}; +use super::{Html, HtmlResult, IntoHtmlResult}; pub use children::*; pub use properties::*; pub(crate) use scope::Scoped; @@ -146,7 +146,7 @@ where } fn view(&self, ctx: &Context) -> HtmlResult { - Component::view(self, ctx).into() + Component::view(self, ctx).into_html_result() } fn rendered(&mut self, ctx: &Context, first_render: bool) { diff --git a/packages/yew/src/html/mod.rs b/packages/yew/src/html/mod.rs index 87a289aff37..ae290b1e67f 100644 --- a/packages/yew/src/html/mod.rs +++ b/packages/yew/src/html/mod.rs @@ -12,6 +12,7 @@ pub use conversion::*; pub use error::*; pub use listener::*; +use crate::sealed::Sealed; use crate::virtual_dom::{VNode, VPortal}; use std::cell::RefCell; use std::rc::Rc; @@ -24,10 +25,25 @@ pub type Html = VNode; /// An enhanced type of `Html` returned in suspendible function components. pub type HtmlResult = RenderResult; -impl From for HtmlResult { +impl Sealed for HtmlResult {} +impl Sealed for Html {} + +/// A trait to translate into a [`HtmlResult`]. +pub trait IntoHtmlResult: Sealed { + /// Performs the conversion. + fn into_html_result(self) -> HtmlResult; +} + +impl IntoHtmlResult for HtmlResult { + #[inline(always)] + fn into_html_result(self) -> HtmlResult { + self + } +} +impl IntoHtmlResult for Html { #[inline(always)] - fn from(m: Html) -> Self { - Ok(m) + fn into_html_result(self) -> HtmlResult { + Ok(self) } } diff --git a/packages/yew/src/lib.rs b/packages/yew/src/lib.rs index 2e999ac81c3..2d73dbca8eb 100644 --- a/packages/yew/src/lib.rs +++ b/packages/yew/src/lib.rs @@ -261,6 +261,7 @@ pub mod context; pub mod functional; pub mod html; pub mod scheduler; +mod sealed; pub mod suspense; pub mod utils; pub mod virtual_dom; diff --git a/packages/yew/src/sealed.rs b/packages/yew/src/sealed.rs new file mode 100644 index 00000000000..8d8bbad8650 --- /dev/null +++ b/packages/yew/src/sealed.rs @@ -0,0 +1,2 @@ +/// Base traits for sealed traits. +pub trait Sealed {} From d8e97011bcbef8e08f4b034d519a1a3f257f0d0d Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Tue, 21 Dec 2021 18:50:08 +0900 Subject: [PATCH 22/27] Fix file permission. --- examples/futures/Cargo.toml | 0 examples/futures/README.md | 0 examples/futures/index.html | 0 examples/futures/src/main.rs | 0 examples/futures/src/markdown.rs | 0 examples/inner_html/Cargo.toml | 0 examples/inner_html/README.md | 0 examples/inner_html/index.html | 0 examples/inner_html/src/document.html | 0 examples/inner_html/src/main.rs | 0 examples/todomvc/README.md | 0 examples/todomvc/index.html | 0 examples/todomvc/src/main.rs | 0 examples/todomvc/src/state.rs | 0 website/docs/concepts/components/children.mdx | 0 website/docs/concepts/components/introduction.mdx | 0 16 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 examples/futures/Cargo.toml mode change 100755 => 100644 examples/futures/README.md mode change 100755 => 100644 examples/futures/index.html mode change 100755 => 100644 examples/futures/src/main.rs mode change 100755 => 100644 examples/futures/src/markdown.rs mode change 100755 => 100644 examples/inner_html/Cargo.toml mode change 100755 => 100644 examples/inner_html/README.md mode change 100755 => 100644 examples/inner_html/index.html mode change 100755 => 100644 examples/inner_html/src/document.html mode change 100755 => 100644 examples/inner_html/src/main.rs mode change 100755 => 100644 examples/todomvc/README.md mode change 100755 => 100644 examples/todomvc/index.html mode change 100755 => 100644 examples/todomvc/src/main.rs mode change 100755 => 100644 examples/todomvc/src/state.rs mode change 100755 => 100644 website/docs/concepts/components/children.mdx mode change 100755 => 100644 website/docs/concepts/components/introduction.mdx diff --git a/examples/futures/Cargo.toml b/examples/futures/Cargo.toml old mode 100755 new mode 100644 diff --git a/examples/futures/README.md b/examples/futures/README.md old mode 100755 new mode 100644 diff --git a/examples/futures/index.html b/examples/futures/index.html old mode 100755 new mode 100644 diff --git a/examples/futures/src/main.rs b/examples/futures/src/main.rs old mode 100755 new mode 100644 diff --git a/examples/futures/src/markdown.rs b/examples/futures/src/markdown.rs old mode 100755 new mode 100644 diff --git a/examples/inner_html/Cargo.toml b/examples/inner_html/Cargo.toml old mode 100755 new mode 100644 diff --git a/examples/inner_html/README.md b/examples/inner_html/README.md old mode 100755 new mode 100644 diff --git a/examples/inner_html/index.html b/examples/inner_html/index.html old mode 100755 new mode 100644 diff --git a/examples/inner_html/src/document.html b/examples/inner_html/src/document.html old mode 100755 new mode 100644 diff --git a/examples/inner_html/src/main.rs b/examples/inner_html/src/main.rs old mode 100755 new mode 100644 diff --git a/examples/todomvc/README.md b/examples/todomvc/README.md old mode 100755 new mode 100644 diff --git a/examples/todomvc/index.html b/examples/todomvc/index.html old mode 100755 new mode 100644 diff --git a/examples/todomvc/src/main.rs b/examples/todomvc/src/main.rs old mode 100755 new mode 100644 diff --git a/examples/todomvc/src/state.rs b/examples/todomvc/src/state.rs old mode 100755 new mode 100644 diff --git a/website/docs/concepts/components/children.mdx b/website/docs/concepts/components/children.mdx old mode 100755 new mode 100644 diff --git a/website/docs/concepts/components/introduction.mdx b/website/docs/concepts/components/introduction.mdx old mode 100755 new mode 100644 From 787de4f6a3bbcf709e0626cfe7f651cc9cec029b Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Tue, 21 Dec 2021 19:16:05 +0900 Subject: [PATCH 23/27] Fix docs. --- examples/suspense/Cargo.toml | 2 +- examples/suspense/src/use_sleep.rs | 12 ++--- packages/yew/src/functional/mod.rs | 6 +-- packages/yew/src/html/component/mod.rs | 5 +- packages/yew/src/html/component/scope.rs | 14 +++--- packages/yew/src/html/conversion.rs | 2 +- packages/yew/src/virtual_dom/mod.rs | 2 +- website/docs/concepts/suspense.md | 61 +++++++++++++++++++++++- 8 files changed, 81 insertions(+), 23 deletions(-) diff --git a/examples/suspense/Cargo.toml b/examples/suspense/Cargo.toml index e6ce038fdda..81aa577ae33 100644 --- a/examples/suspense/Cargo.toml +++ b/examples/suspense/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" [dependencies] yew = { path = "../../packages/yew" } -gloo-timers = { version = "0.2.1", features = ["futures"] } +gloo-timers = { version = "0.2.2", features = ["futures"] } wasm-bindgen-futures = "0.4" wasm-bindgen = "0.2" log = "0.4.14" diff --git a/examples/suspense/src/use_sleep.rs b/examples/suspense/src/use_sleep.rs index 5ef99755f42..e8e8ff648ce 100644 --- a/examples/suspense/src/use_sleep.rs +++ b/examples/suspense/src/use_sleep.rs @@ -1,7 +1,7 @@ use std::rc::Rc; +use std::time::Duration; -use gloo_timers::future::TimeoutFuture; -use wasm_bindgen_futures::spawn_local; +use gloo_timers::future::sleep; use yew::prelude::*; use yew::suspense::{Suspension, SuspensionResult}; @@ -12,12 +12,8 @@ pub struct SleepState { impl SleepState { fn new() -> Self { - let (s, handle) = Suspension::new(); - - spawn_local(async move { - TimeoutFuture::new(5_000).await; - - handle.resume(); + let s = Suspension::from_future(async { + sleep(Duration::from_secs(5)).await; }); Self { s } diff --git a/packages/yew/src/functional/mod.rs b/packages/yew/src/functional/mod.rs index f93e69a4bd1..cd0d449ccd1 100644 --- a/packages/yew/src/functional/mod.rs +++ b/packages/yew/src/functional/mod.rs @@ -1,6 +1,6 @@ //! Function components are a simplified version of normal components. //! They consist of a single function annotated with the attribute `#[function_component(_)]` -//! that receives props and determines what should be rendered by returning [`Html`]. +//! that receives props and determines what should be rendered by returning [`Html`](crate::Html). //! //! ```rust //! # use yew::prelude::*; @@ -70,9 +70,9 @@ pub trait FunctionProvider { /// Properties for the Function Component. type TProps: Properties + PartialEq; - /// Render the component. This function returns the [`Html`] to be rendered for the component. + /// Render the component. This function returns the [`Html`](crate::Html) to be rendered for the component. /// - /// Equivalent of [`Component::view`]. + /// Equivalent of [`Component::view`](crate::html::Component::view). fn run(props: &Self::TProps) -> HtmlResult; } diff --git a/packages/yew/src/html/component/mod.rs b/packages/yew/src/html/component/mod.rs index 8bf7b2198bf..43363148a68 100644 --- a/packages/yew/src/html/component/mod.rs +++ b/packages/yew/src/html/component/mod.rs @@ -36,7 +36,10 @@ impl Context { /// The common base of both function components and struct components. /// -/// If you are taken here by doc links, you might be looking for: [`Component`] +/// If you are taken here by doc links, you might be looking for [`Component`] or +/// [`#[function_component]`](crate::functional::function_component). +/// +/// We provide a blanket implementation of this trait for every member that implements [`Component`]. pub trait BaseComponent: Sized + 'static { /// The Component's Message. type Message: 'static; diff --git a/packages/yew/src/html/component/scope.rs b/packages/yew/src/html/component/scope.rs index c29001b8487..023594b1bb6 100644 --- a/packages/yew/src/html/component/scope.rs +++ b/packages/yew/src/html/component/scope.rs @@ -276,7 +276,7 @@ impl Scope { /// Send a message to the component. /// /// Please be aware that currently this method synchronously - /// schedules a call to the [Component](Component) interface. + /// schedules a call to the [Component](crate::html::Component) interface. pub fn send_message(&self, msg: T) where T: Into, @@ -291,7 +291,7 @@ impl Scope { /// function is called only once if needed. /// /// Please be aware that currently this method synchronously - /// schedules calls to the [Component](Component) interface. + /// schedules calls to the [Component](crate::html::Component) interface. pub fn send_message_batch(&self, messages: Vec) { // There is no reason to schedule empty batches. // This check is especially handy for the batch_callback method. @@ -306,7 +306,7 @@ impl Scope { /// component's update method when invoked. /// /// Please be aware that currently the result of this callback - /// synchronously schedules a call to the [Component](Component) + /// synchronously schedules a call to the [Component](crate::html::Component) /// interface. pub fn callback(&self, function: F) -> Callback where @@ -325,7 +325,7 @@ impl Scope { /// [addEventListener](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener). /// /// Please be aware that currently the result of this callback - /// synchronously schedules a call to the [Component](Component) + /// synchronously schedules a call to the [Component](crate::html::Component) /// interface. pub fn callback_with_passive( &self, @@ -352,7 +352,7 @@ impl Scope { /// /// Please be aware that currently the result of this callback /// will synchronously schedule calls to the - /// [Component](Component) interface. + /// [Component](crate::html::Component) interface. pub fn callback_once(&self, function: F) -> Callback where M: Into, @@ -380,7 +380,7 @@ impl Scope { /// /// Please be aware that currently the results of these callbacks /// will synchronously schedule calls to the - /// [Component](Component) interface. + /// [Component](crate::html::Component) interface. pub fn batch_callback(&self, function: F) -> Callback where F: Fn(IN) -> OUT + 'static, @@ -408,7 +408,7 @@ impl Scope { /// /// Please be aware that currently the results of these callbacks /// will synchronously schedule calls to the - /// [Component](Component) interface. + /// [Component](crate::html::Component) interface. pub fn batch_callback_once(&self, function: F) -> Callback where F: FnOnce(IN) -> OUT + 'static, diff --git a/packages/yew/src/html/conversion.rs b/packages/yew/src/html/conversion.rs index 61598677a4b..13795502c05 100644 --- a/packages/yew/src/html/conversion.rs +++ b/packages/yew/src/html/conversion.rs @@ -2,7 +2,7 @@ use super::{Component, NodeRef, Scope}; use crate::virtual_dom::AttrValue; use std::{borrow::Cow, rc::Rc}; -/// Marker trait for types that the [`html!`] macro may clone implicitly. +/// Marker trait for types that the [`html!`](macro@crate::html) macro may clone implicitly. pub trait ImplicitClone: Clone {} // this is only implemented because there's no way to avoid cloning this value diff --git a/packages/yew/src/virtual_dom/mod.rs b/packages/yew/src/virtual_dom/mod.rs index 6ada48557dc..0acfccacfd1 100644 --- a/packages/yew/src/virtual_dom/mod.rs +++ b/packages/yew/src/virtual_dom/mod.rs @@ -228,7 +228,7 @@ pub enum Attributes { /// Attribute keys. Includes both always set and optional attribute keys. keys: &'static [&'static str], - /// Attribute values. Matches [keys]. Optional attributes are designated by setting [None]. + /// Attribute values. Matches [keys](Attributes::Dynamic::keys). Optional attributes are designated by setting [None]. values: Box<[Option]>, }, diff --git a/website/docs/concepts/suspense.md b/website/docs/concepts/suspense.md index 13978e8e57f..a8ac666a65d 100644 --- a/website/docs/concepts/suspense.md +++ b/website/docs/concepts/suspense.md @@ -77,13 +77,68 @@ fn use_user() -> SuspensionResult { } ``` +# Complete Example + +```rust +use yew::prelude::*; +use yew::suspense::{Suspension, SuspensionResult}; + +#[derive(Debug)] +struct User { + name: String, +} + +fn load_user() -> Option { + todo!() // implementation omitted. +} + +fn on_load_user_complete(_fn: F) { + todo!() // implementation omitted. +} + +fn use_user() -> SuspensionResult { + match load_user() { + // If a user is loaded, then we return it as Ok(user). + Some(m) => Ok(m), + None => { + // When user is still loading, then we create a `Suspension` + // and call `SuspensionHandle::resume` when data loading + // completes, the component will be re-rendered + // automatically. + let (s, handle) = Suspension::new(); + on_load_user_complete(move || {handle.resume();}); + Err(s) + }, + } +} + +#[function_component(Content)] +fn content() -> HtmlResult { + let user = use_user()?; + + Ok(html! {
    {"Hello, "}{&user.name}
    }) +} + +#[function_component(App)] +fn app() -> Html { + let fallback = html! {
    {"Loading..."}
    }; + + html! { + + + + } +} +``` + + ### Use Suspense in Struct Components It's not possible to suspend a struct component directly. However, you can use a function component as a Higher-Order-Component to achieve suspense-based data fetching. -```rust, ignore +```rust ,ignore use yew::prelude::*; #[function_component(WithUser)] @@ -119,3 +174,7 @@ impl Component for BaseUserContent { pub type UserContent = WithUser; ``` + +## Relevant examples + +- [Suspense](https://github.com/yewstack/yew/tree/master/examples/suspense) From d8c3e853d759a43f47916aad0eaf5a8620d60b0a Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Tue, 21 Dec 2021 20:55:37 +0900 Subject: [PATCH 24/27] Remove log. --- examples/suspense/Cargo.toml | 2 -- examples/suspense/src/main.rs | 3 --- 2 files changed, 5 deletions(-) diff --git a/examples/suspense/Cargo.toml b/examples/suspense/Cargo.toml index 81aa577ae33..cf1ca96c164 100644 --- a/examples/suspense/Cargo.toml +++ b/examples/suspense/Cargo.toml @@ -11,8 +11,6 @@ yew = { path = "../../packages/yew" } gloo-timers = { version = "0.2.2", features = ["futures"] } wasm-bindgen-futures = "0.4" wasm-bindgen = "0.2" -log = "0.4.14" -console_log = { version = "0.2.0", features = ["color"] } [dependencies.web-sys] version = "0.3" diff --git a/examples/suspense/src/main.rs b/examples/suspense/src/main.rs index 0cc9d1d751f..31f0a3d3591 100644 --- a/examples/suspense/src/main.rs +++ b/examples/suspense/src/main.rs @@ -1,8 +1,6 @@ use web_sys::HtmlTextAreaElement; use yew::prelude::*; -use log::Level; - mod use_sleep; use use_sleep::use_sleep; @@ -58,6 +56,5 @@ fn app() -> Html { } fn main() { - console_log::init_with_level(Level::Trace).expect("Failed to initialise Log!"); yew::start_app::(); } From 7e6641865fde2b9ccc8287a06200fd06ffdb9c4e Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Wed, 22 Dec 2021 08:15:05 +0900 Subject: [PATCH 25/27] Fix file permission. --- examples/nested_list/src/list.rs | 0 examples/todomvc/Cargo.toml | 0 website/docs/concepts/html/elements.mdx | 0 website/docs/concepts/html/lists.mdx | 0 4 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 examples/nested_list/src/list.rs mode change 100755 => 100644 examples/todomvc/Cargo.toml mode change 100755 => 100644 website/docs/concepts/html/elements.mdx mode change 100755 => 100644 website/docs/concepts/html/lists.mdx diff --git a/examples/nested_list/src/list.rs b/examples/nested_list/src/list.rs old mode 100755 new mode 100644 diff --git a/examples/todomvc/Cargo.toml b/examples/todomvc/Cargo.toml old mode 100755 new mode 100644 diff --git a/website/docs/concepts/html/elements.mdx b/website/docs/concepts/html/elements.mdx old mode 100755 new mode 100644 diff --git a/website/docs/concepts/html/lists.mdx b/website/docs/concepts/html/lists.mdx old mode 100755 new mode 100644 From eb950d23f0cb030596e5e34a8d5156d122f5a8bd Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Fri, 24 Dec 2021 21:54:25 +0900 Subject: [PATCH 26/27] Fix component name error. --- .../tests/function_component_attr/bad-name-fail.stderr | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/yew-macro/tests/function_component_attr/bad-name-fail.stderr b/packages/yew-macro/tests/function_component_attr/bad-name-fail.stderr index 6c97843a4f2..1a6c4c90058 100644 --- a/packages/yew-macro/tests/function_component_attr/bad-name-fail.stderr +++ b/packages/yew-macro/tests/function_component_attr/bad-name-fail.stderr @@ -16,10 +16,8 @@ error: expected identifier 26 | #[function_component(124)] | ^^^ -warning: type `component` should have an upper camel case name +error: the component must not have the same name as the function --> tests/function_component_attr/bad-name-fail.rs:35:22 | 35 | #[function_component(component)] - | ^^^^^^^^^ help: convert the identifier to upper camel case (notice the capitalization): `Component` - | - = note: `#[warn(non_camel_case_types)]` on by default + | ^^^^^^^^^ From fae94d1b9c60293e2b749743b7b5cf8eab59fc9e Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Thu, 30 Dec 2021 20:16:04 +0900 Subject: [PATCH 27/27] Make Suspension a future. --- packages/yew/src/html/component/lifecycle.rs | 4 +-- packages/yew/src/suspense/suspension.rs | 30 ++++++++++++++++---- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/packages/yew/src/html/component/lifecycle.rs b/packages/yew/src/html/component/lifecycle.rs index 004480071f4..e1b7a5e4108 100644 --- a/packages/yew/src/html/component/lifecycle.rs +++ b/packages/yew/src/html/component/lifecycle.rs @@ -239,14 +239,14 @@ impl Runnable for RenderRunner { let suspense_scope = comp_scope.find_parent_scope::().unwrap(); let suspense = suspense_scope.get_component().unwrap(); - m.listen(Callback::once(move |_| { + m.listen(Callback::from(move |_| { scheduler::push_component_render( shared_state.as_ptr() as usize, RenderRunner { state: shared_state.clone(), }, RenderedRunner { - state: shared_state, + state: shared_state.clone(), }, ); })); diff --git a/packages/yew/src/suspense/suspension.rs b/packages/yew/src/suspense/suspension.rs index ed41d3a54c6..9430e8d6dd5 100644 --- a/packages/yew/src/suspense/suspension.rs +++ b/packages/yew/src/suspense/suspension.rs @@ -1,7 +1,9 @@ use std::cell::RefCell; use std::future::Future; +use std::pin::Pin; use std::rc::Rc; use std::sync::atomic::{AtomicBool, Ordering}; +use std::task::{Context, Poll}; use thiserror::Error; use wasm_bindgen_futures::spawn_local; @@ -67,11 +69,12 @@ impl Suspension { self.resumed.load(Ordering::Relaxed) } + /// Listens to a suspension and get notified when it resumes. pub(crate) fn listen(&self, cb: Callback) { - assert!( - !self.resumed.load(Ordering::Relaxed), - "You are attempting to add a callback after it's resumed." - ); + if self.resumed() { + cb.emit(self.clone()); + return; + } let mut listeners = self.listeners.borrow_mut(); @@ -81,7 +84,7 @@ impl Suspension { fn resume_by_ref(&self) { // The component can resume rendering by returning a non-suspended result after a state is // updated, so we always need to check here. - if !self.resumed.load(Ordering::Relaxed) { + if !self.resumed() { self.resumed.store(true, Ordering::Relaxed); let listeners = self.listeners.borrow(); @@ -92,6 +95,23 @@ impl Suspension { } } +impl Future for Suspension { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if self.resumed() { + return Poll::Ready(()); + } + + let waker = cx.waker().clone(); + self.listen(Callback::from(move |_| { + waker.clone().wake(); + })); + + Poll::Pending + } +} + /// A Suspension Result. pub type SuspensionResult = std::result::Result;