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() {
>> Generic>>
>
};
+
+ 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! {
+
+ }
+ }
+ }
+
+ #[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 = "\
+ ";
+
+ 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