diff --git a/examples/nested_list/src/item.rs b/examples/nested_list/src/item.rs index fa939ba9f50..94cf53f3dd4 100644 --- a/examples/nested_list/src/item.rs +++ b/examples/nested_list/src/item.rs @@ -60,7 +60,7 @@ impl ListItem { html! {
- { self.props.children.render() } + { self.props.children.clone() }
} } diff --git a/yew-functional/src/use_context_hook.rs b/yew-functional/src/use_context_hook.rs index 10dd6834e7a..2ae46b77eb2 100644 --- a/yew-functional/src/use_context_hook.rs +++ b/yew-functional/src/use_context_hook.rs @@ -4,7 +4,8 @@ use std::any::TypeId; use std::cell::RefCell; use std::rc::{Rc, Weak}; use std::{iter, mem}; -use yew::html::{AnyScope, Renderable, Scope}; +use yew::html; +use yew::html::{AnyScope, Scope}; use yew::{Children, Component, ComponentLink, Html, Properties}; type ConsumerCallback = Box)>; @@ -83,7 +84,7 @@ impl Component for ContextProvider { } fn view(&self) -> Html { - self.children.render() + html! { <>{ self.children.clone() } } } } diff --git a/yew-functional/tests/use_context_hook.rs b/yew-functional/tests/use_context_hook.rs index acb35f6ebe5..289288be725 100644 --- a/yew-functional/tests/use_context_hook.rs +++ b/yew-functional/tests/use_context_hook.rs @@ -6,7 +6,7 @@ wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); extern crate yew; -use yew::{html, App, Children, Html, Properties, Renderable}; +use yew::{html, App, Children, Html, Properties}; use yew_functional::{ use_context, use_effect, use_ref, use_state, ContextProvider, FunctionComponent, FunctionProvider, @@ -198,7 +198,7 @@ fn use_context_update_works() {
{ format!("total: {}", counter.borrow()) }
- { props.children.render() } + { props.children.clone() } }; } diff --git a/yew-macro/src/html_tree/html_block.rs b/yew-macro/src/html_tree/html_block.rs index 8b7e5d393ac..1e56b77ed09 100644 --- a/yew-macro/src/html_tree/html_block.rs +++ b/yew-macro/src/html_tree/html_block.rs @@ -1,5 +1,6 @@ use super::html_iterable::HtmlIterable; use super::html_node::HtmlNode; +use super::ToNodeIterator; use crate::PeekValue; use proc_macro2::Delimiter; use quote::{quote, quote_spanned, ToTokens}; @@ -49,3 +50,15 @@ impl ToTokens for HtmlBlock { tokens.extend(quote_spanned! {brace.span=> #new_tokens}); } } + +impl ToNodeIterator for HtmlBlock { + fn to_node_iterator_stream(&self) -> Option { + let HtmlBlock { content, brace } = self; + let new_tokens = match content { + BlockContent::Iterable(iterable) => iterable.to_node_iterator_stream(), + BlockContent::Node(node) => node.to_node_iterator_stream(), + }?; + + Some(quote_spanned! {brace.span=> #new_tokens}) + } +} diff --git a/yew-macro/src/html_tree/html_component.rs b/yew-macro/src/html_tree/html_component.rs index 8e59e44c59a..70f0bfb08a1 100644 --- a/yew-macro/src/html_tree/html_component.rs +++ b/yew-macro/src/html_tree/html_component.rs @@ -1,6 +1,6 @@ +use super::HtmlChildrenTree; use super::HtmlProp; use super::HtmlPropSuffix; -use super::HtmlTreeNested; use crate::PeekValue; use boolinator::Boolinator; use proc_macro2::Span; @@ -19,7 +19,7 @@ use syn::{ pub struct HtmlComponent { ty: Type, props: Props, - children: Vec, + children: HtmlChildrenTree, } impl PeekValue<()> for HtmlComponent { @@ -48,11 +48,11 @@ impl Parse for HtmlComponent { return Ok(HtmlComponent { ty: open.ty, props: open.props, - children: Vec::new(), + children: HtmlChildrenTree::new(), }); } - let mut children: Vec = vec![]; + let mut children = HtmlChildrenTree::new(); loop { if input.is_empty() { return Err(syn::Error::new_spanned( @@ -66,7 +66,7 @@ impl Parse for HtmlComponent { } } - children.push(input.parse()?); + children.parse_child(input)?; } input.parse::()?; @@ -110,11 +110,7 @@ impl ToTokens for HtmlComponent { let set_children = if !children.is_empty() { quote! { - .children(::yew::html::ChildrenRenderer::new({ - let mut v = ::std::vec::Vec::new(); - #(v.extend(::yew::utils::NodeSeq::from(#children));)* - v - })) + .children(::yew::html::ChildrenRenderer::new(#children)) } } else { quote! {} @@ -157,7 +153,7 @@ impl ToTokens for HtmlComponent { let key = if let Some(key) = props.key() { quote_spanned! { key.span()=> Some(#key) } } else { - quote! {None } + quote! {None} }; tokens.extend(quote! {{ diff --git a/yew-macro/src/html_tree/html_iterable.rs b/yew-macro/src/html_tree/html_iterable.rs index 955a78016ac..22a99fbba50 100644 --- a/yew-macro/src/html_tree/html_iterable.rs +++ b/yew-macro/src/html_tree/html_iterable.rs @@ -1,3 +1,4 @@ +use super::ToNodeIterator; use crate::PeekValue; use boolinator::Boolinator; use proc_macro2::TokenStream; @@ -39,14 +40,21 @@ impl Parse for HtmlIterable { impl ToTokens for HtmlIterable { fn to_tokens(&self, tokens: &mut TokenStream) { let expr = &self.0; - let new_tokens = quote_spanned! {expr.span()=> { - let mut __yew_vlist = ::yew::virtual_dom::VList::default(); - for __yew_node in #expr { - __yew_vlist.add_child(__yew_node.into()); - } - ::yew::virtual_dom::VNode::from(__yew_vlist) - }}; + let new_tokens = quote_spanned! {expr.span()=> + ::std::iter::Iterator::collect::<::yew::virtual_dom::VNode>(::std::iter::IntoIterator::into_iter(#expr)) + }; tokens.extend(new_tokens); } } + +impl ToNodeIterator for HtmlIterable { + fn to_node_iterator_stream(&self) -> Option { + let Self(expr) = self; + // #expr can return anything that implements IntoIterator> + // so we generate some extra code to turn it into IntoIterator + Some(quote_spanned! {expr.span()=> + ::std::iter::Iterator::map(::std::iter::IntoIterator::into_iter(#expr), |n| n.into()) + }) + } +} diff --git a/yew-macro/src/html_tree/html_list.rs b/yew-macro/src/html_tree/html_list.rs index e534294ae1d..2e266ed4491 100644 --- a/yew-macro/src/html_tree/html_list.rs +++ b/yew-macro/src/html_tree/html_list.rs @@ -1,4 +1,4 @@ -use super::HtmlTree; +use super::HtmlChildrenTree; use crate::html_tree::{HtmlProp, HtmlPropSuffix}; use crate::PeekValue; use boolinator::Boolinator; @@ -10,8 +10,17 @@ use syn::spanned::Spanned; use syn::{Expr, Token}; pub struct HtmlList { - pub children: Vec, - pub key: Option, + children: HtmlChildrenTree, + key: Option, +} + +impl HtmlList { + pub fn empty() -> Self { + Self { + children: HtmlChildrenTree::new(), + key: None, + } + } } impl PeekValue<()> for HtmlList { @@ -42,9 +51,9 @@ impl Parse for HtmlList { )); } - let mut children: Vec = vec![]; + let mut children = HtmlChildrenTree::new(); while HtmlListClose::peek(input.cursor()).is_none() { - children.push(input.parse()?); + children.parse_child(input)?; } input.parse::()?; @@ -64,23 +73,9 @@ impl ToTokens for HtmlList { } else { quote! {None} }; - let children_tokens = if !children.is_empty() { - quote! { - let mut v = ::std::vec::Vec::new(); - #(v.extend(::yew::utils::NodeSeq::from(#children));)* - v - } - } else { - quote! { - ::std::vec::Vec::new() - } - }; tokens.extend(quote! { ::yew::virtual_dom::VNode::VList( - ::yew::virtual_dom::VList::new_with_children({ - #children_tokens - }, - #key) + ::yew::virtual_dom::VList::new_with_children(#children, #key) ) }); } diff --git a/yew-macro/src/html_tree/html_node.rs b/yew-macro/src/html_tree/html_node.rs index e20d47f79f3..a9b9a9dc0fa 100644 --- a/yew-macro/src/html_tree/html_node.rs +++ b/yew-macro/src/html_tree/html_node.rs @@ -1,13 +1,16 @@ +use super::ToNodeIterator; use crate::PeekValue; use proc_macro2::TokenStream; use quote::{quote, quote_spanned, ToTokens}; use syn::buffer::Cursor; use syn::parse::{Parse, ParseStream, Result}; use syn::spanned::Spanned; -use syn::Expr; -use syn::Lit; +use syn::{Expr, Lit}; -pub struct HtmlNode(Node); +pub enum HtmlNode { + Literal(Box), + Expression(Box), +} impl Parse for HtmlNode { fn parse(input: ParseStream) -> Result { @@ -17,12 +20,12 @@ impl Parse for HtmlNode { Lit::Str(_) | Lit::Char(_) | Lit::Int(_) | Lit::Float(_) | Lit::Bool(_) => {} _ => return Err(syn::Error::new(lit.span(), "unsupported type")), } - Node::Literal(Box::new(lit)) + HtmlNode::Literal(Box::new(lit)) } else { - Node::Expression(Box::new(input.parse()?)) + HtmlNode::Expression(Box::new(input.parse()?)) }; - Ok(HtmlNode(node)) + Ok(node) } } @@ -40,22 +43,21 @@ impl PeekValue<()> for HtmlNode { impl ToTokens for HtmlNode { fn to_tokens(&self, tokens: &mut TokenStream) { - self.0.to_tokens(tokens); + tokens.extend(match &self { + HtmlNode::Literal(lit) => quote! {#lit}, + HtmlNode::Expression(expr) => quote_spanned! {expr.span()=> {#expr}}, + }); } } -impl ToTokens for Node { - fn to_tokens(&self, tokens: &mut TokenStream) { - let node_token = match &self { - Node::Literal(lit) => quote! {#lit}, - Node::Expression(expr) => quote_spanned! {expr.span()=> {#expr} }, - }; - - tokens.extend(node_token); +impl ToNodeIterator for HtmlNode { + fn to_node_iterator_stream(&self) -> Option { + match self { + HtmlNode::Literal(_) => None, + HtmlNode::Expression(expr) => { + // NodeSeq turns both Into and Vec> into IntoIterator + Some(quote_spanned! {expr.span()=> ::yew::utils::NodeSeq::from(#expr)}) + } + } } } - -enum Node { - Literal(Box), - Expression(Box), -} diff --git a/yew-macro/src/html_tree/html_tag/mod.rs b/yew-macro/src/html_tree/html_tag/mod.rs index 50898820b0e..d1442bf1c37 100644 --- a/yew-macro/src/html_tree/html_tag/mod.rs +++ b/yew-macro/src/html_tree/html_tag/mod.rs @@ -1,9 +1,9 @@ mod tag_attributes; +use super::HtmlChildrenTree; use super::HtmlDashedName; use super::HtmlProp as TagAttribute; use super::HtmlPropSuffix as TagSuffix; -use super::HtmlTree; use crate::{non_capitalized_ascii, Peek, PeekValue}; use boolinator::Boolinator; use proc_macro2::{Delimiter, Span}; @@ -18,7 +18,7 @@ use tag_attributes::{ClassesForm, TagAttributes}; pub struct HtmlTag { tag_name: TagName, attributes: TagAttributes, - children: Vec, + children: HtmlChildrenTree, } impl PeekValue<()> for HtmlTag { @@ -47,7 +47,7 @@ impl Parse for HtmlTag { return Ok(HtmlTag { tag_name: open.tag_name, attributes: open.attributes, - children: Vec::new(), + children: HtmlChildrenTree::new(), }); } @@ -66,7 +66,7 @@ impl Parse for HtmlTag { } let open_key = open.tag_name.get_key(); - let mut children: Vec = vec![]; + let mut children = HtmlChildrenTree::new(); loop { if input.is_empty() { return Err(syn::Error::new_spanned( @@ -80,7 +80,7 @@ impl Parse for HtmlTag { } } - children.push(input.parse()?); + children.parse_child(input)?; } input.parse::()?; @@ -244,7 +244,7 @@ impl ToTokens for HtmlTag { #(#set_key)* #vtag.add_attributes(vec![#(#attr_pairs),*]); #vtag.add_listeners(vec![#(::std::rc::Rc::new(#listeners)),*]); - #vtag.add_children(vec![#(#children),*]); + #vtag.add_children(#children); #dyn_tag_runtime_checks ::yew::virtual_dom::VNode::from(#vtag) }}); diff --git a/yew-macro/src/html_tree/mod.rs b/yew-macro/src/html_tree/mod.rs index 76c886bec79..1c99b7eb53d 100644 --- a/yew-macro/src/html_tree/mod.rs +++ b/yew-macro/src/html_tree/mod.rs @@ -1,11 +1,11 @@ -pub mod html_block; -pub mod html_component; -pub mod html_dashed_name; -pub mod html_iterable; -pub mod html_list; -pub mod html_node; -pub mod html_prop; -pub mod html_tag; +mod html_block; +mod html_component; +mod html_dashed_name; +mod html_iterable; +mod html_list; +mod html_node; +mod html_prop; +mod html_tag; use crate::PeekValue; use html_block::HtmlBlock; @@ -17,7 +17,7 @@ use html_node::HtmlNode; use html_prop::HtmlProp; use html_prop::HtmlPropSuffix; use html_tag::HtmlTag; -use proc_macro2::TokenStream; +use proc_macro2::{Ident, Span, TokenStream}; use quote::{quote, ToTokens}; use syn::buffer::Cursor; use syn::parse::{Parse, ParseStream, Result}; @@ -33,43 +33,11 @@ pub enum HtmlType { pub enum HtmlTree { Block(Box), Component(Box), - Iterable(Box), List(Box), Tag(Box), - Node(Box), Empty, } -pub struct HtmlRoot(HtmlTree); -impl Parse for HtmlRoot { - fn parse(input: ParseStream) -> Result { - let html_root = if HtmlTree::peek(input.cursor()).is_some() { - HtmlRoot(input.parse()?) - } else if HtmlIterable::peek(input.cursor()).is_some() { - HtmlRoot(HtmlTree::Iterable(Box::new(input.parse()?))) - } else { - HtmlRoot(HtmlTree::Node(Box::new(input.parse()?))) - }; - - if !input.is_empty() { - let stream: TokenStream = input.parse()?; - Err(syn::Error::new_spanned( - stream, - "only one root html element is allowed (hint: you can wrap multiple html elements in a fragment `<>`)", - )) - } else { - Ok(html_root) - } - } -} - -impl ToTokens for HtmlRoot { - fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - let HtmlRoot(html_tree) = self; - html_tree.to_tokens(tokens); - } -} - impl Parse for HtmlTree { fn parse(input: ParseStream) -> Result { let html_type = HtmlTree::peek(input.cursor()) @@ -104,54 +72,149 @@ impl PeekValue for HtmlTree { } impl ToTokens for HtmlTree { - fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - let node = self.token_stream(); - tokens.extend(quote! { - ::yew::virtual_dom::VNode::from(#node) - }); + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + HtmlTree::Empty => HtmlList::empty().to_tokens(tokens), + HtmlTree::Component(comp) => comp.to_tokens(tokens), + HtmlTree::Tag(tag) => tag.to_tokens(tokens), + HtmlTree::List(list) => list.to_tokens(tokens), + HtmlTree::Block(block) => block.to_tokens(tokens), + } + } +} + +pub enum HtmlRoot { + Tree(HtmlTree), + Iterable(Box), + Node(Box), +} + +impl Parse for HtmlRoot { + fn parse(input: ParseStream) -> Result { + let html_root = if HtmlTree::peek(input.cursor()).is_some() { + Self::Tree(input.parse()?) + } else if HtmlIterable::peek(input.cursor()).is_some() { + Self::Iterable(Box::new(input.parse()?)) + } else { + Self::Node(Box::new(input.parse()?)) + }; + + if !input.is_empty() { + let stream: TokenStream = input.parse()?; + Err(syn::Error::new_spanned( + stream, + "only one root html element is allowed (hint: you can wrap multiple html elements in a fragment `<>`)", + )) + } else { + Ok(html_root) + } } } -impl HtmlTree { - fn token_stream(&self) -> proc_macro2::TokenStream { +impl ToTokens for HtmlRoot { + fn to_tokens(&self, tokens: &mut TokenStream) { match self { - HtmlTree::Empty => HtmlList { - children: Vec::new(), - key: None, - } - .into_token_stream(), - HtmlTree::Component(comp) => comp.into_token_stream(), - HtmlTree::Tag(tag) => tag.into_token_stream(), - HtmlTree::List(list) => list.into_token_stream(), - HtmlTree::Node(node) => node.into_token_stream(), - HtmlTree::Iterable(iterable) => iterable.into_token_stream(), - HtmlTree::Block(block) => block.into_token_stream(), + Self::Tree(tree) => tree.to_tokens(tokens), + Self::Node(node) => node.to_tokens(tokens), + Self::Iterable(iterable) => iterable.to_tokens(tokens), } } } -pub struct HtmlRootNested(HtmlTreeNested); -impl Parse for HtmlRootNested { +/// Same as HtmlRoot but always returns a VNode. +pub struct HtmlRootVNode(HtmlRoot); +impl Parse for HtmlRootVNode { fn parse(input: ParseStream) -> Result { - Ok(HtmlRootNested(HtmlTreeNested::parse(input)?)) + input.parse().map(Self) + } +} +impl ToTokens for HtmlRootVNode { + fn to_tokens(&self, tokens: &mut TokenStream) { + let new_tokens = self.0.to_token_stream(); + tokens.extend(quote! { + ::yew::virtual_dom::VNode::from(#new_tokens) + }); } } -impl ToTokens for HtmlRootNested { - fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - self.0.to_tokens(tokens); +/// This trait represents a type that can be unfolded into multiple html nodes. +pub trait ToNodeIterator { + /// Generate a token stream which produces a value that implements IntoIterator where T is inferred by the compiler. + /// The easiest way to achieve this is to call `.into()` on each element. + /// If the resulting iterator only ever yields a single item this function should return None instead. + fn to_node_iterator_stream(&self) -> Option; +} + +impl ToNodeIterator for HtmlTree { + fn to_node_iterator_stream(&self) -> Option { + match self { + HtmlTree::Block(block) => block.to_node_iterator_stream(), + // everthing else is just a single node. + _ => None, + } } } -pub struct HtmlTreeNested(HtmlTree); -impl Parse for HtmlTreeNested { - fn parse(input: ParseStream) -> Result { - Ok(HtmlTreeNested(HtmlTree::parse(input)?)) +struct HtmlChildrenTree(Vec); + +impl HtmlChildrenTree { + pub fn new() -> Self { + Self(Vec::new()) + } + + pub fn parse_child(&mut self, input: ParseStream) -> Result<()> { + self.0.push(input.parse()?); + Ok(()) + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + // Check if each child represents a single node. + // This is the case when no expressions are used. + fn only_single_node_children(&self) -> bool { + self.0 + .iter() + .map(ToNodeIterator::to_node_iterator_stream) + .all(|s| s.is_none()) + } + + pub fn to_build_vec_token_stream(&self) -> TokenStream { + let Self(children) = self; + + if self.only_single_node_children() { + // optimize for the common case where all children are single nodes (only using literal html). + return quote! { + vec![#((#children).into()),*] + }; + } + + let vec_ident = Ident::new("__yew_v", Span::call_site()); + let add_children_streams = children.iter().map(|child| { + if let Some(node_iterator_stream) = child.to_node_iterator_stream() { + quote! { + #vec_ident.extend(#node_iterator_stream); + } + } else { + quote! { + #vec_ident.push((#child).into()); + } + } + }); + + quote! { + { + let mut #vec_ident = ::std::vec::Vec::new(); + #(#add_children_streams)* + #vec_ident + } + } } } -impl ToTokens for HtmlTreeNested { - fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - tokens.extend(self.0.token_stream()); +impl ToTokens for HtmlChildrenTree { + fn to_tokens(&self, tokens: &mut TokenStream) { + tokens.extend(self.to_build_vec_token_stream()); } } diff --git a/yew-macro/src/lib.rs b/yew-macro/src/lib.rs index 894e9a20a1f..fe06c09cdcf 100644 --- a/yew-macro/src/lib.rs +++ b/yew-macro/src/lib.rs @@ -66,7 +66,7 @@ mod derive_props; mod html_tree; use derive_props::DerivePropsInput; -use html_tree::{HtmlRoot, HtmlRootNested}; +use html_tree::{HtmlRoot, HtmlRootVNode}; use proc_macro::TokenStream; use proc_macro_hack::proc_macro_hack; use quote::{quote, ToTokens}; @@ -99,12 +99,12 @@ pub fn derive_props(input: TokenStream) -> TokenStream { #[proc_macro_hack] pub fn html_nested(input: TokenStream) -> TokenStream { - let root = parse_macro_input!(input as HtmlRootNested); + let root = parse_macro_input!(input as HtmlRoot); TokenStream::from(quote! {#root}) } #[proc_macro_hack] pub fn html(input: TokenStream) -> TokenStream { - let root = parse_macro_input!(input as HtmlRoot); + let root = parse_macro_input!(input as HtmlRootVNode); TokenStream::from(quote! {#root}) } diff --git a/yew-macro/tests/macro/html-block-fail.stderr b/yew-macro/tests/macro/html-block-fail.stderr index c5d845d6b38..a2a029f239b 100644 --- a/yew-macro/tests/macro/html-block-fail.stderr +++ b/yew-macro/tests/macro/html-block-fail.stderr @@ -8,6 +8,8 @@ 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 `std::string::ToString` for `()` = note: required because of the requirements on the impl of `std::convert::From<()>` for `yew::virtual_dom::vnode::VNode` + = note: required because of the requirements on the impl of `std::convert::Into` for `()` + = note: required because of the requirements on the impl of `std::convert::From<()>` for `yew::utils::NodeSeq<(), yew::virtual_dom::vnode::VNode>` = note: required by `std::convert::From::from` = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) @@ -21,6 +23,8 @@ 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 `std::string::ToString` for `()` = note: required because of the requirements on the impl of `std::convert::From<()>` for `yew::virtual_dom::vnode::VNode` + = note: required because of the requirements on the impl of `std::convert::Into` for `()` + = note: required because of the requirements on the impl of `std::convert::From<()>` for `yew::utils::NodeSeq<(), yew::virtual_dom::vnode::VNode>` = note: required by `std::convert::From::from` = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/yew-macro/tests/macro/html-component-fail.rs b/yew-macro/tests/macro/html-component-fail.rs index fd7e2ba0969..5ad55b2713b 100644 --- a/yew-macro/tests/macro/html-component-fail.rs +++ b/yew-macro/tests/macro/html-component-fail.rs @@ -116,6 +116,11 @@ fn compile_fail() { html! { > }; html! { >>> }; + + html_nested! { + { 1 } + { 2 } + }; } fn main() {} diff --git a/yew-macro/tests/macro/html-component-fail.stderr b/yew-macro/tests/macro/html-component-fail.stderr index 0e56447c5dc..50aaa638298 100644 --- a/yew-macro/tests/macro/html-component-fail.stderr +++ b/yew-macro/tests/macro/html-component-fail.stderr @@ -190,6 +190,14 @@ error: this closing tag has no corresponding opening tag | = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) +error: only one root html element is allowed (hint: you can wrap multiple html elements in a fragment `<>`) + --> $DIR/html-component-fail.rs:122:9 + | +122 | { 2 } + | ^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + error[E0425]: cannot find value `blah` in this scope --> $DIR/html-component-fail.rs:91:25 | @@ -346,7 +354,6 @@ error[E0277]: the trait bound `yew::virtual_dom::vcomp::VChild: std::conv | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::convert::From<&str>` is not implemented for `yew::virtual_dom::vcomp::VChild` | = note: required because of the requirements on the impl of `std::convert::Into>` for `&str` - = note: required because of the requirements on the impl of `std::iter::IntoIterator` for `yew::utils::NodeSeq<&str, yew::virtual_dom::vcomp::VChild>` = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `yew::virtual_dom::vcomp::VChild: std::convert::From` is not satisfied @@ -356,7 +363,6 @@ error[E0277]: the trait bound `yew::virtual_dom::vcomp::VChild: std::conv | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::convert::From` is not implemented for `yew::virtual_dom::vcomp::VChild` | = note: required because of the requirements on the impl of `std::convert::Into>` for `yew::virtual_dom::vnode::VNode` - = note: required because of the requirements on the impl of `std::iter::IntoIterator` for `yew::utils::NodeSeq>` = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `yew::virtual_dom::vcomp::VChild: std::convert::From` is not satisfied @@ -366,5 +372,4 @@ error[E0277]: the trait bound `yew::virtual_dom::vcomp::VChild: std::conv | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::convert::From` is not implemented for `yew::virtual_dom::vcomp::VChild` | = note: required because of the requirements on the impl of `std::convert::Into>` for `yew::virtual_dom::vnode::VNode` - = note: required because of the requirements on the impl of `std::iter::IntoIterator` for `yew::utils::NodeSeq>` = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/yew-macro/tests/macro/html-component-pass.rs b/yew-macro/tests/macro/html-component-pass.rs index 39998d71db4..7b8b0d7fc88 100644 --- a/yew-macro/tests/macro/html-component-pass.rs +++ b/yew-macro/tests/macro/html-component-pass.rs @@ -300,9 +300,23 @@ fn compile_pass() { }; + let children = vec![ + html_nested! { }, + html_nested! { }, + ]; + html! { + + { children } + + }; + let variants = || -> Vec { vec![ - ChildrenVariants::Child(VChild::new(ChildProperties::default(), NodeRef::default(), None)), + ChildrenVariants::Child(VChild::new( + ChildProperties::default(), + NodeRef::default(), + None, + )), ChildrenVariants::AltChild(VChild::new((), NodeRef::default(), None)), ] }; @@ -340,6 +354,8 @@ fn compile_pass() { >>>> }; + + html_nested! { 1 }; } fn main() {} diff --git a/yew-macro/tests/macro/html-iterable-fail.rs b/yew-macro/tests/macro/html-iterable-fail.rs index bdd3062642e..97312a08ca6 100644 --- a/yew-macro/tests/macro/html-iterable-fail.rs +++ b/yew-macro/tests/macro/html-iterable-fail.rs @@ -11,6 +11,13 @@ fn compile_fail() { let empty = Vec::<()>::new(); html! { for empty.iter() }; + + html! { + <> +
+ { for () } + + }; } fn main() {} diff --git a/yew-macro/tests/macro/html-iterable-fail.stderr b/yew-macro/tests/macro/html-iterable-fail.stderr index 9a91575233c..ae7425308b9 100644 --- a/yew-macro/tests/macro/html-iterable-fail.stderr +++ b/yew-macro/tests/macro/html-iterable-fail.stderr @@ -37,6 +37,8 @@ error[E0277]: `()` doesn't implement `std::fmt::Display` = note: required because of the requirements on the impl of `std::string::ToString` for `()` = note: required because of the requirements on the impl of `std::convert::From<()>` for `yew::virtual_dom::vnode::VNode` = note: required because of the requirements on the impl of `std::convert::Into` for `()` + = note: required because of the requirements on the impl of `std::iter::FromIterator<()>` for `yew::virtual_dom::vnode::VNode` + = note: required by `std::iter::Iterator::collect` = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: `()` doesn't implement `std::fmt::Display` @@ -50,6 +52,8 @@ error[E0277]: `()` doesn't implement `std::fmt::Display` = note: required because of the requirements on the impl of `std::string::ToString` for `()` = note: required because of the requirements on the impl of `std::convert::From<()>` for `yew::virtual_dom::vnode::VNode` = note: required because of the requirements on the impl of `std::convert::Into` for `()` + = note: required because of the requirements on the impl of `std::iter::FromIterator<()>` for `yew::virtual_dom::vnode::VNode` + = note: required by `std::iter::Iterator::collect` = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: `()` doesn't implement `std::fmt::Display` @@ -64,4 +68,16 @@ error[E0277]: `()` doesn't implement `std::fmt::Display` = note: required because of the requirements on the impl of `std::string::ToString` for `&()` = note: required because of the requirements on the impl of `std::convert::From<&()>` for `yew::virtual_dom::vnode::VNode` = note: required because of the requirements on the impl of `std::convert::Into` for `&()` + = note: required because of the requirements on the impl of `std::iter::FromIterator<&()>` for `yew::virtual_dom::vnode::VNode` + = note: required by `std::iter::Iterator::collect` + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: `()` is not an iterator + --> $DIR/html-iterable-fail.rs:18:19 + | +18 | { for () } + | ^^ `()` is not an iterator + | + = help: the trait `std::iter::Iterator` is not implemented for `()` + = note: required by `std::iter::IntoIterator::into_iter` = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/yew-macro/tests/macro/html-list-pass.rs b/yew-macro/tests/macro/html-list-pass.rs index 70b862dad51..f0c61edcf26 100644 --- a/yew-macro/tests/macro/html-list-pass.rs +++ b/yew-macro/tests/macro/html-list-pass.rs @@ -13,6 +13,12 @@ fn compile_pass() { }; + + let children = vec![ + html! { { "Hello" } }, + html! { { "World" } }, + ]; + html! { <>{children} }; } fn main() {} diff --git a/yew-macro/tests/macro/html-tag-pass.rs b/yew-macro/tests/macro/html-tag-pass.rs index c36990a3ead..77ae4aef624 100644 --- a/yew-macro/tests/macro/html-tag-pass.rs +++ b/yew-macro/tests/macro/html-tag-pass.rs @@ -60,6 +60,12 @@ fn compile_pass() { }/>
}; + + let children = vec![ + html! { { "Hello" } }, + html! { { "World" } }, + ]; + html! {
{children}
}; } fn main() {} diff --git a/yew/src/html/mod.rs b/yew/src/html/mod.rs index 0156f5cd2ef..d06a6f665ad 100644 --- a/yew/src/html/mod.rs +++ b/yew/src/html/mod.rs @@ -11,7 +11,7 @@ pub(crate) use scope::ComponentUpdate; pub use scope::{AnyScope, Scope}; use crate::callback::Callback; -use crate::virtual_dom::{VChild, VList, VNode}; +use crate::virtual_dom::{VChild, VNode}; use cfg_if::cfg_if; use cfg_match::cfg_match; use std::cell::RefCell; @@ -163,7 +163,7 @@ pub type Html = VNode; /// The Wrapper component must define a `children` property in order to wrap other elements. The /// children property can be used to render the wrapped elements. /// ``` -///# use yew::{Children, Html, Properties, Renderable, Component, ComponentLink, html}; +///# use yew::{Children, Html, Properties, Component, ComponentLink, html}; /// #[derive(Clone, Properties)] /// struct WrapperProps { /// children: Children, @@ -180,7 +180,7 @@ pub type Html = VNode; /// fn view(&self) -> Html { /// html! { ///
-/// { self.props.children.render() } +/// { self.props.children.clone() } ///
/// } /// } @@ -313,14 +313,11 @@ where self.children.len() } - /// Build children components and return `Vec` - pub fn to_vec(&self) -> Vec { - self.children.clone() - } - /// Render children components and return `Iterator` - pub fn iter(&self) -> impl Iterator { - self.to_vec().into_iter() + pub fn iter<'a>(&'a self) -> impl Iterator + 'a { + // clone each child lazily. + // This way `self.iter().next()` only has to clone a single node. + self.children.iter().cloned() } } @@ -338,12 +335,12 @@ impl fmt::Debug for ChildrenRenderer { } } -impl Renderable for ChildrenRenderer -where - T: Clone + Into, -{ - fn render(&self) -> Html { - VList::new_with_children(self.iter().map(|c| c.into()).collect(), None).into() +impl IntoIterator for ChildrenRenderer { + type Item = T; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.children.into_iter() } } diff --git a/yew/src/utils.rs b/yew/src/utils.rs index 1242c04fde8..e6232cb9da2 100644 --- a/yew/src/utils.rs +++ b/yew/src/utils.rs @@ -4,6 +4,7 @@ use anyhow::{anyhow, Error}; use cfg_if::cfg_if; use cfg_match::cfg_match; use std::marker::PhantomData; +use yew::html::ChildrenRenderer; cfg_if! { if #[cfg(feature = "std_web")] { use stdweb::web::{Document, Window}; @@ -86,7 +87,16 @@ impl, OUT> From> for NodeSeq { } } -impl, OUT> IntoIterator for NodeSeq { +impl, OUT> From> for NodeSeq { + fn from(val: ChildrenRenderer) -> Self { + Self( + val.into_iter().map(|x| x.into()).collect(), + PhantomData::default(), + ) + } +} + +impl IntoIterator for NodeSeq { type Item = OUT; type IntoIter = std::vec::IntoIter; diff --git a/yew/src/virtual_dom/vcomp.rs b/yew/src/virtual_dom/vcomp.rs index d3c6c739bd4..e8414937017 100644 --- a/yew/src/virtual_dom/vcomp.rs +++ b/yew/src/virtual_dom/vcomp.rs @@ -336,7 +336,7 @@ impl fmt::Debug for VChild { mod tests { use super::VChild; use crate::macros::Properties; - use crate::{html, Component, ComponentLink, Html, NodeRef, ShouldRender}; + use crate::{html, Children, Component, ComponentLink, Html, NodeRef, ShouldRender}; #[cfg(feature = "wasm_test")] use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure}; @@ -435,4 +435,107 @@ mod tests { assert_ne!(vchild1, vchild3); assert_ne!(vchild2, vchild3); } + + #[derive(Clone, Properties)] + pub struct ListProps { + pub children: Children, + } + pub struct List(ListProps); + impl Component for List { + type Message = (); + type Properties = ListProps; + + fn create(props: Self::Properties, _: ComponentLink) -> Self { + Self(props) + } + fn update(&mut self, _: Self::Message) -> ShouldRender { + unimplemented!(); + } + fn change(&mut self, _: Self::Properties) -> ShouldRender { + unimplemented!(); + } + fn view(&self) -> Html { + let item_iter = self.0.children.iter().map(|item| html! {
  • { item }
  • }); + html! { +
      { for item_iter }
    + } + } + } + + #[cfg(feature = "web_sys")] + use super::{AnyScope, Element}; + + #[cfg(feature = "web_sys")] + fn setup_parent() -> (AnyScope, Element) { + use crate::utils::document; + + let scope = AnyScope { + type_id: std::any::TypeId::of::<()>(), + parent: None, + state: std::rc::Rc::new(()), + }; + let parent = document().create_element("div").unwrap(); + + document().body().unwrap().append_child(&parent).unwrap(); + + (scope, parent) + } + + #[cfg(feature = "web_sys")] + fn get_html(mut node: Html, scope: &AnyScope, parent: &Element) -> String { + use super::VDiff; + + // clear parent + parent.set_inner_html(""); + + node.apply(&scope, &parent, None, None); + parent.inner_html() + } + + #[test] + #[cfg(feature = "web_sys")] + fn all_ways_of_passing_children_work() { + let (scope, parent) = setup_parent(); + + let children: Vec<_> = vec!["a", "b", "c"] + .drain(..) + .map(|text| html! {{ text }}) + .collect(); + let children_renderer = Children::new(children.clone()); + let expected_html = "\ +
      \ +
    • a
    • \ +
    • b
    • \ +
    • c
    • \ +
    "; + + let prop_method = html! { + + }; + assert_eq!(get_html(prop_method, &scope, &parent), expected_html); + + let children_renderer_method = html! { + + { children_renderer.clone() } + + }; + assert_eq!( + get_html(children_renderer_method, &scope, &parent), + expected_html + ); + + let direct_method = html! { + + { children.clone() } + + }; + assert_eq!(get_html(direct_method, &scope, &parent), expected_html); + + let for_method = html! { + + { for children } + + }; + assert_eq!(get_html(for_method, &scope, &parent), expected_html); + } } diff --git a/yew/src/virtual_dom/vlist.rs b/yew/src/virtual_dom/vlist.rs index 0f0fc6c7e94..6befc547959 100644 --- a/yew/src/virtual_dom/vlist.rs +++ b/yew/src/virtual_dom/vlist.rs @@ -65,6 +65,11 @@ impl VList { pub fn add_child(&mut self, child: VNode) { self.children.push(child); } + + /// Add multiple `VNode` children. + pub fn add_children(&mut self, children: impl IntoIterator) { + self.children.extend(children); + } } impl VDiff for VList { diff --git a/yew/src/virtual_dom/vtag.rs b/yew/src/virtual_dom/vtag.rs index 61cdb83265f..9f590b07fa6 100644 --- a/yew/src/virtual_dom/vtag.rs +++ b/yew/src/virtual_dom/vtag.rs @@ -118,10 +118,8 @@ impl VTag { } /// Add multiple `VNode` children. - pub fn add_children(&mut self, children: Vec) { - for child in children { - self.add_child(child); - } + pub fn add_children(&mut self, children: impl IntoIterator) { + self.children.add_children(children); } /// Sets `value` for an