Skip to content

Commit 97eecad

Browse files
committed
Omit conditional elements without an else branch when present in children
1 parent 2c4ea60 commit 97eecad

File tree

4 files changed

+164
-12
lines changed

4 files changed

+164
-12
lines changed

packages/yew-macro/src/html_tree/html_if.rs

+33-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use boolinator::Boolinator;
22
use proc_macro2::TokenStream;
3-
use quote::{quote_spanned, ToTokens};
3+
use quote::{quote, quote_spanned, ToTokens};
44
use syn::buffer::Cursor;
55
use syn::parse::{Parse, ParseStream};
66
use syn::spanned::Spanned;
@@ -139,3 +139,35 @@ impl ToTokens for HtmlRootBracedOrIf {
139139
}
140140
}
141141
}
142+
143+
pub struct HtmlIfIterItem<'a>(pub &'a HtmlIf);
144+
145+
impl<'a> ToNodeIterator for HtmlIfIterItem<'a> {
146+
fn to_node_iterator_stream(&self) -> Option<TokenStream> {
147+
let HtmlIf {
148+
if_token,
149+
cond,
150+
then_branch,
151+
else_branch,
152+
..
153+
} = &self.0;
154+
155+
if else_branch.is_none() {
156+
// This slightly awkward syntax is necessary to defeat stable's
157+
// [`clippy::manual_map`] lint.
158+
let new_tokens = quote_spanned! {if_token.span()=>
159+
if #cond {
160+
let _x = ::std::convert::Into::into(#then_branch);
161+
::std::option::Option::Some(_x)
162+
} else {
163+
::std::option::Option::None
164+
}
165+
};
166+
167+
Some(new_tokens)
168+
} else {
169+
let new_tokens = self.0.to_node_iterator_stream().unwrap();
170+
Some(quote! { ::std::option::Option::Some(#new_tokens) })
171+
}
172+
}
173+
}

packages/yew-macro/src/html_tree/mod.rs

+41-9
Original file line numberDiff line numberDiff line change
@@ -237,23 +237,55 @@ impl HtmlChildrenTree {
237237

238238
pub fn to_build_vec_token_stream(&self) -> TokenStream {
239239
let Self(children) = self;
240-
241240
if self.only_single_node_children() {
242241
// optimize for the common case where all children are single nodes (only using literal
243242
// html).
244-
let children_into = children
245-
.iter()
246-
.map(|child| quote_spanned! {child.span()=> ::std::convert::Into::into(#child) });
247-
return quote! {
248-
::std::vec![#(#children_into),*]
249-
};
243+
let children_into = children.iter().map(|child| {
244+
if let HtmlTree::If(if_clause) = child {
245+
html_if::HtmlIfIterItem(if_clause.as_ref())
246+
.to_node_iterator_stream()
247+
.unwrap()
248+
} else {
249+
quote_spanned! {child.span()=> ::std::option::Option::Some(::std::convert::Into::into(#child)) }
250+
}
251+
});
252+
253+
return quote! {{
254+
use ::std::iter::IntoIterator;
255+
use ::std::iter::Iterator;
256+
[#(#children_into),*].into_iter().filter(|x: &::std::option::Option<_>| x.is_some()).map(|x| x.unwrap()).collect::<::std::vec::Vec<_>>()
257+
}};
250258
}
251259

252260
let vec_ident = Ident::new("__yew_v", Span::mixed_site());
253261
let add_children_streams = children.iter().map(|child| {
254262
if let Some(node_iterator_stream) = child.to_node_iterator_stream() {
255-
quote! {
256-
::std::iter::Extend::extend(&mut #vec_ident, #node_iterator_stream);
263+
if let HtmlTree::If(if_clause) = child {
264+
let tokens = html_if::HtmlIfIterItem(if_clause.as_ref())
265+
.to_node_iterator_stream()
266+
.unwrap();
267+
268+
quote! {
269+
if let ::std::option::Option::Some(iter) = #tokens {
270+
::std::iter::Extend::extend(&mut #vec_ident, iter)
271+
} else {
272+
#vec_ident
273+
}
274+
}
275+
} else {
276+
quote! {
277+
::std::iter::Extend::extend(&mut #vec_ident, #node_iterator_stream);
278+
}
279+
}
280+
} else if let HtmlTree::If(if_clause) = child {
281+
let tokens = html_if::HtmlIfIterItem(if_clause.as_ref())
282+
.to_node_iterator_stream()
283+
.unwrap();
284+
285+
quote_spanned! {child.span()=>
286+
if let ::std::option::Option::Some(iter) = ::std::convert::Into::into(#tokens) {
287+
#vec_ident.push(iter)
288+
}
257289
}
258290
} else {
259291
quote_spanned! {child.span()=>

packages/yew/src/dom_bundle/btag/mod.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -1175,7 +1175,6 @@ mod tests_without_browser {
11751175
},
11761176
html! {
11771177
<div>
1178-
<></>
11791178
</div>
11801179
},
11811180
);
@@ -1265,7 +1264,7 @@ mod tests_without_browser {
12651264
}
12661265
</div>
12671266
},
1268-
html! { <div><></></div> },
1267+
html! { <div></div> },
12691268
);
12701269
}
12711270
}

packages/yew/tests/children.rs

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
#![cfg(not(target_arch = "wasm32"))]
2+
3+
use yew::prelude::*;
4+
5+
#[tokio::test]
6+
async fn conditional_children_are_omitted() {
7+
#[derive(Properties, PartialEq)]
8+
pub struct Props {
9+
#[prop_or_default]
10+
pub children: Children,
11+
}
12+
13+
#[function_component]
14+
pub fn Problem(p: &Props) -> Html {
15+
html! {
16+
<div class="container">
17+
{
18+
for p.children.iter().enumerate().map(|(i, x)| {
19+
html! {
20+
<div>
21+
<span>{x}</span>
22+
<span>{i}</span>
23+
</div>
24+
}
25+
})
26+
}
27+
</div>
28+
}
29+
}
30+
31+
#[function_component]
32+
fn App() -> Html {
33+
let b = false;
34+
html! {
35+
<Problem>
36+
<span>{ "A" }</span>
37+
<span>{ "B" }</span>
38+
if b {
39+
<span>{ "C" }</span>
40+
}
41+
<span>{ "D" }</span>
42+
</Problem>
43+
}
44+
}
45+
46+
let mut s = String::new();
47+
yew::ServerRenderer::<App>::new()
48+
.render_to_string(&mut s)
49+
.await;
50+
51+
assert_eq!(
52+
s,
53+
"<!--<[children::conditional_children_are_omitted::{{closure}}::App]>--><!\
54+
--<[children::conditional_children_are_omitted::{{closure}}::Problem]>--><div \
55+
class=\"container\"><div><span><span>A</span></span><span>0</span></\
56+
div><div><span><span>B</span></span><span>1</span></div><div><span><span>D</span></\
57+
span><span>2</span></div></div><!--</\
58+
[children::conditional_children_are_omitted::{{closure}}::Problem]>--><!--</\
59+
[children::conditional_children_are_omitted::{{closure}}::App]>-->"
60+
);
61+
}
62+
63+
#[tokio::test]
64+
async fn conditional_children_are_omitted_in_non_only_single_node_children_case() {
65+
#[function_component]
66+
fn App() -> Html {
67+
let b = true;
68+
let a = Some(());
69+
let chtml = html! {
70+
<div></div>
71+
};
72+
html! {
73+
<>
74+
{ chtml }
75+
if a.is_some() || b { <></> }
76+
</>
77+
}
78+
}
79+
80+
let mut s = String::new();
81+
yew::ServerRenderer::<App>::new()
82+
.render_to_string(&mut s)
83+
.await;
84+
85+
assert_eq!(
86+
s,
87+
"<!--<[children::conditional_children_are_omitted_in_non_only_single_node_children_case::{{closure}}::App]>--><div></div><!--</[children::conditional_children_are_omitted_in_non_only_single_node_children_case::{{closure}}::App]>-->"
88+
);
89+
}

0 commit comments

Comments
 (0)