diff --git a/packages/yew-macro/src/html_tree/html_element.rs b/packages/yew-macro/src/html_tree/html_element.rs
index 1bd46e093..e4c205799 100644
--- a/packages/yew-macro/src/html_tree/html_element.rs
+++ b/packages/yew-macro/src/html_tree/html_element.rs
@@ -132,61 +132,73 @@ impl ToTokens for HtmlElement {
// attributes with special treatment
- let set_node_ref = node_ref.as_ref().map(|attr| {
- let value = &attr.value;
- quote! {
- #vtag.node_ref = #value;
- }
- });
- let set_key = key.as_ref().map(|attr| {
- let value = &attr.value;
- quote! {
- #vtag.key = ::std::option::Option::Some(::std::convert::Into::<::yew::virtual_dom::Key>::into(#value));
- }
- });
- let set_value = value.as_ref().map(|attr| {
- let value = &attr.value;
- if attr.question_mark.is_some() {
- quote_spanned! {value.span()=>
- if let ::std::option::Option::Some(__yew_v) = ::std::option::Option::as_ref(&(#value)) {
- #vtag.set_value(__yew_v);
- };
+ let node_ref = node_ref
+ .as_ref()
+ .map(|attr| {
+ let value = &attr.value;
+ quote! { #value }
+ })
+ .unwrap_or(quote! { ::std::default::Default::default() });
+ let key = key
+ .as_ref()
+ .map(|attr| {
+ let value = &attr.value;
+ quote! {
+ ::std::option::Option::Some
+ (::std::convert::Into::<::yew::virtual_dom::Key>::into(#value))
}
- } else {
- quote_spanned! {value.span()=>
- #vtag.set_value(&(#value));
+ })
+ .unwrap_or(quote! { ::std::option::Option::None });
+ let value = value
+ .as_ref()
+ .map(|attr| {
+ let value = &attr.value;
+ if attr.question_mark.is_some() {
+ quote_spanned! {value.span()=>
+ ::std::option::Option::as_ref(&(#value))
+ .map(::std::string::ToString::to_string)
+ }
+ } else {
+ quote_spanned! {value.span()=>
+ ::std::option::Option::Some(::std::string::ToString::to_string(&(#value)))
+ }
}
- }
- });
- let set_kind = kind.as_ref().map(|attr| {
- let value = &attr.value;
- if attr.question_mark.is_some() {
- let sr = stringify::stringify_option_at_runtime(value);
- quote_spanned! {value.span()=>
- if let ::std::option::Option::Some(__yew_v) = #sr {
- #vtag.set_kind(__yew_v);
- };
+ })
+ .unwrap_or(quote! { ::std::option::Option::None });
+ let kind = kind
+ .as_ref()
+ .map(|attr| {
+ let value = &attr.value;
+ if attr.question_mark.is_some() {
+ let sr = stringify::stringify_option_at_runtime(value);
+ quote_spanned! {value.span()=>
+ ::std::option::Option::map(
+ #sr,
+ |v| ::std::convert::Into::<::std::borrow::Cow<'static, str>>::into(v),
+ )
+ }
+ } else {
+ let sr = value.stringify();
+ quote_spanned! {value.span()=>
+ ::std::option::Option::Some
+ (::std::convert::Into::<::std::borrow::Cow<'static, str>>::into(#sr))
+ }
}
- } else {
- let sr = value.stringify();
- quote_spanned! {value.span()=>
- #vtag.set_kind(#sr);
- }
- }
- });
- let set_checked = checked.as_ref().map(|attr| {
- let value = &attr.value;
- quote_spanned! {value.span()=>
- #vtag.set_checked(#value);
- }
- });
+ })
+ .unwrap_or(quote! { ::std::option::Option::None });
+ let checked = checked
+ .as_ref()
+ .map(|attr| {
+ let value = &attr.value;
+ quote_spanned! {value.span()=> #value}
+ })
+ .unwrap_or(quote! { false });
// normal attributes
- let set_attributes = if attributes.is_empty() {
- None
- } else {
- let attrs = attributes.iter().map(
+ let mut attributes = attributes
+ .iter()
+ .map(
|Prop {
label,
question_mark,
@@ -206,33 +218,22 @@ impl ToTokens for HtmlElement {
}
}
},
- );
- Some(quote! {
- #vtag.attributes = ::yew::virtual_dom::Attributes::Vec(::std::vec![#(#attrs),*]);
- })
- };
+ )
+ .collect::>();
- let push_booleans = if booleans.is_empty() {
- None
- } else {
- let tokens = booleans
- .iter()
- .map(|Prop { label, value, .. }| {
- let label_str = label.to_lit_str();
- let sr = label.stringify();
- quote_spanned! {value.span()=> {
- if #value {
- #vtag.__macro_push_attribute(#label_str, #sr);
- } else {
- #vtag.__macro_push_attribute_placeholder(#label_str);
- };
- }}
- })
- .collect::();
- Some(tokens)
- };
+ attributes.extend(booleans.iter().map(|Prop { label, value, .. }| {
+ let label_str = label.to_lit_str();
+ let sr = label.stringify();
+ quote_spanned! {value.span()=> {
+ if #value {
+ ::yew::virtual_dom::PositionalAttr::new(#label_str, #sr)
+ } else {
+ ::yew::virtual_dom::PositionalAttr::new_placeholder(#label_str)
+ }
+ }}
+ }));
- let push_classes = match classes {
+ match classes {
Some(ClassesForm::Tuple(classes)) => {
let span = classes.span();
let classes: Vec<_> = classes.elems.iter().collect();
@@ -250,49 +251,47 @@ impl ToTokens for HtmlElement {
};
};
- Some(quote! {
+ attributes.push(quote! {{
let mut __yew_classes = ::yew::html::Classes::with_capacity(#n);
#(__yew_classes.push(#classes);)*
#deprecation_warning
if !__yew_classes.is_empty() {
- #vtag.__macro_push_attribute("class", #sr);
+ ::yew::virtual_dom::PositionalAttr::new("class", #sr)
} else {
- #vtag.__macro_push_attribute_placeholder("class");
- };
- })
+ ::yew::virtual_dom::PositionalAttr::new_placeholder("class")
+ }
+ }});
}
Some(ClassesForm::Single(classes)) => match classes.try_into_lit() {
Some(lit) => {
- if lit.value().is_empty() {
- None
- } else {
+ if !lit.value().is_empty() {
let sr = lit.stringify();
- Some(quote! {
- #vtag.__macro_push_attribute("class", #sr);
- })
+ attributes.push(quote! {
+ ::yew::virtual_dom::PositionalAttr::new("class", #sr)
+ });
}
}
None => {
let sr = stringify::stringify_at_runtime(quote! { __yew_classes });
- Some(quote! {
+ attributes.push(quote! {{
let __yew_classes = ::std::convert::Into::<::yew::html::Classes>::into(#classes);
if !__yew_classes.is_empty() {
- #vtag.__macro_push_attribute("class", #sr);
+ ::yew::virtual_dom::PositionalAttr::new("class", #sr)
} else {
- #vtag.__macro_push_attribute_placeholder("class");
- };
- })
+ ::yew::virtual_dom::PositionalAttr::new_placeholder("class")
+ }
+ }});
}
},
- None => None,
+ None => (),
};
- let add_listeners = if listeners.is_empty() {
- None
+ let listeners = if listeners.is_empty() {
+ quote! { ::std::vec![] }
} else if listeners.iter().any(|attr| attr.question_mark.is_some()) {
- let add_listeners = listeners
+ let listeners = listeners
.iter()
.map(
|Prop {
@@ -307,41 +306,29 @@ impl ToTokens for HtmlElement {
let ident = Ident::new("__yew_listener", name.span());
let listener = to_wrapped_listener(name, &ident);
quote_spanned! {value.span()=>
- let #ident = ::std::option::Option::map(#value, |#ident| {
- #listener
- });
- if let ::std::option::Option::Some(#ident) = #ident {
- #vtag.add_listener(#ident);
- };
+ ::std::option::Option::map(#value, |#ident| #listener)
}
} else {
let listener = to_wrapped_listener(name, value);
- quote_spanned! {value.span()=>
- #vtag.add_listener(#listener);
- }
+ quote_spanned! {value.span()=> Some(#listener)}
}
},
)
- .collect();
+ .collect::>();
+ quote! {{
+ use ::std::iter::{Iterator, IntoIterator};
- Some(add_listeners)
+ ::std::vec![#(#listeners),*]
+ .into_iter()
+ .filter_map(|l| l)
+ .collect::<::std::vec::Vec<::std::rc::Rc>>()
+ }}
} else {
let listeners_it = listeners
.iter()
- .map(|Prop { label, value, .. }| to_wrapped_listener(&label.name, value));
-
- Some(quote! {
- #vtag.add_listeners(::std::vec![#(#listeners_it),*]);
- })
- };
-
- let add_children = if children.is_empty() {
- None
- } else {
- Some(quote! {
- #[allow(clippy::redundant_clone, unused_braces)]
- #vtag.add_children(#children);
- })
+ .map(|Prop { label, value, .. }| to_wrapped_listener(&label.name, value))
+ .collect::>();
+ quote! { ::std::vec![#(#listeners_it),*] }
};
// These are the runtime-checks exclusive to dynamic tags.
@@ -380,21 +367,21 @@ impl ToTokens for HtmlElement {
tokens.extend(quote_spanned! {name.span()=>
{
- #[allow(unused_braces)]
- let mut #vtag = ::yew::virtual_dom::VTag::new(#name_sr);
-
- #set_node_ref
- #set_key
- #set_value
- #set_kind
- #set_checked
-
- #set_attributes
- #push_booleans
- #push_classes
-
- #add_listeners
- #add_children
+ #[allow(clippy::redundant_clone, unused_braces)]
+ let mut #vtag = ::yew::virtual_dom::VTag::__new_complete(
+ #name_sr,
+ #node_ref,
+ #key,
+ #value,
+ #kind,
+ #checked,
+ ::yew::virtual_dom::Attributes::Vec(::std::vec![#(#attributes),*]),
+ #listeners,
+ ::yew::virtual_dom::VList{
+ key: ::std::option::Option::None,
+ children: #children,
+ },
+ );
#dyn_tag_runtime_checks
#[allow(unused_braces)]
@@ -408,7 +395,7 @@ fn to_wrapped_listener(name: &Ident, value: impl ToTokens) -> TokenStream {
quote_spanned! {value.span()=>
::std::rc::Rc::new(::yew::html::#name::Wrapper::new(
<::yew::virtual_dom::VTag as ::yew::virtual_dom::Transformer<_, _>>::transform(#value),
- ))
+ )) as ::std::rc::Rc::
}
}
diff --git a/packages/yew-macro/tests/html_macro/element-fail.stderr b/packages/yew-macro/tests/html_macro/element-fail.stderr
index e84c55c2a..879a819d2 100644
--- a/packages/yew-macro/tests/html_macro/element-fail.stderr
+++ b/packages/yew-macro/tests/html_macro/element-fail.stderr
@@ -200,6 +200,7 @@ error[E0277]: `()` doesn't implement `std::fmt::Display`
= 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 by `to_string`
error[E0277]: `()` doesn't implement `std::fmt::Display`
--> $DIR/element-fail.rs:30:21
diff --git a/packages/yew/Cargo.toml b/packages/yew/Cargo.toml
index 560d12a0f..cf6188842 100644
--- a/packages/yew/Cargo.toml
+++ b/packages/yew/Cargo.toml
@@ -28,7 +28,7 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
slab = "0.4"
thiserror = "1"
-wasm-bindgen = "0.2.60"
+wasm-bindgen = "0.2.74"
wasm-bindgen-futures = "0.4"
yew-macro = { version = "0.17.0", path = "../yew-macro" }
@@ -107,7 +107,7 @@ rustversion = "1.0"
serde_derive = "1"
ssri = "6.0.0"
trybuild = "1.0"
-wasm-bindgen-test = "0.3.4"
+wasm-bindgen-test = "0.3.24"
[features]
default = ["agent"]
diff --git a/packages/yew/src/virtual_dom/vtag.rs b/packages/yew/src/virtual_dom/vtag.rs
index 42ca67c24..df18c8528 100644
--- a/packages/yew/src/virtual_dom/vtag.rs
+++ b/packages/yew/src/virtual_dom/vtag.rs
@@ -103,23 +103,56 @@ impl Clone for VTag {
impl VTag {
/// Creates a new `VTag` instance with `tag` name (cannot be changed later in DOM).
pub fn new(tag: impl Into>) -> Self {
+ Self::__new_complete(
+ tag,
+ Default::default(),
+ Default::default(),
+ None,
+ None,
+ Default::default(),
+ Default::default(),
+ Default::default(),
+ Default::default(),
+ )
+ }
+
+ /// Creates a new `VTag` instance with `tag` name (cannot be changed later in DOM).
+ ///
+ /// Unlike `new()`, this sets all the public fields of `VTag` in one call. This allows the
+ /// compiler to inline property and child list construction in the html! macro. This enables
+ /// higher instruction parallelism by reducing data dependency and avoids `memcpy` of Vtag
+ /// fields amd child `VTag`s.
+ #[doc(hidden)]
+ #[allow(clippy::too_many_arguments)]
+ pub fn __new_complete(
+ tag: impl Into>,
+ node_ref: NodeRef,
+ key: Option,
+ value: Option,
+ kind: Option>,
+ checked: bool,
+ // at bottom for more readable macro-expanded coded
+ attributes: Attributes,
+ listeners: Listeners,
+ children: VList,
+ ) -> Self {
let tag: Cow<'static, str> = tag.into();
let element_type = ElementType::from_tag(&tag);
VTag {
tag,
element_type,
reference: None,
- attributes: Attributes::new(),
- listeners: Vec::new(),
+ attributes,
+ listeners,
captured: Vec::new(),
- children: VList::new(),
- node_ref: NodeRef::default(),
- key: None,
- value: None,
- kind: None,
+ children,
+ node_ref,
+ key,
+ value,
+ kind,
// In HTML node `checked` attribute sets `defaultChecked` parameter,
// but we use own field to control real `checked` parameter
- checked: false,
+ checked,
}
}