diff --git a/packages/yew-macro/src/html_tree/html_element.rs b/packages/yew-macro/src/html_tree/html_element.rs
index 24d0b7ac894..663f72797ce 100644
--- a/packages/yew-macro/src/html_tree/html_element.rs
+++ b/packages/yew-macro/src/html_tree/html_element.rs
@@ -1,6 +1,6 @@
use super::{HtmlChildrenTree, HtmlDashedName, TagTokens};
use crate::props::{ClassesForm, ElementProps, Prop};
-use crate::stringify::Stringify;
+use crate::stringify::{Stringify, Value};
use crate::{non_capitalized_ascii, Peek, PeekValue};
use boolinator::Boolinator;
use proc_macro2::{Delimiter, TokenStream};
@@ -8,7 +8,7 @@ use quote::{quote, quote_spanned, ToTokens};
use syn::buffer::Cursor;
use syn::parse::{Parse, ParseStream};
use syn::spanned::Spanned;
-use syn::{Block, Ident, Token};
+use syn::{Block, Expr, Ident, Lit, LitStr, Token};
pub struct HtmlElement {
name: TagName,
@@ -147,17 +147,35 @@ impl ToTokens for HtmlElement {
let attributes = {
let normal_attrs = attributes.iter().map(|Prop { label, value, .. }| {
- let key = label.to_lit_str();
- let value = value.optimize_literals();
- quote! {
- ::yew::virtual_dom::PositionalAttr::new(#key, #value)
- }
+ (label.to_lit_str(), value.optimize_literals_tagged())
});
- let boolean_attrs = booleans.iter().map(|Prop { label, value, .. }| {
+ let boolean_attrs = booleans.iter().filter_map(|Prop { label, value, .. }| {
let key = label.to_lit_str();
- quote! {
- ::yew::virtual_dom::PositionalAttr::new_boolean(#key, #value)
- }
+ Some((
+ key.clone(),
+ match value {
+ Expr::Lit(e) => match &e.lit {
+ Lit::Bool(b) => Value::Static(if b.value {
+ quote! { #key }
+ } else {
+ return None;
+ }),
+ _ => Value::Dynamic(quote_spanned! {value.span()=> {
+ ::yew::utils::__ensure_type::(#value);
+ #key
+ }}),
+ },
+ expr => Value::Dynamic(quote_spanned! {expr.span()=>
+ if #expr {
+ ::std::option::Option::Some(
+ ::std::borrow::Cow::<'static, str>::Borrowed(#key)
+ )
+ } else {
+ None
+ }
+ }),
+ },
+ ))
});
let class_attr = classes.as_ref().and_then(|classes| match classes {
ClassesForm::Tuple(classes) => {
@@ -176,36 +194,78 @@ impl ToTokens for HtmlElement {
};
};
- Some(quote! {
- {
- let mut __yew_classes = ::yew::html::Classes::with_capacity(#n);
- #(__yew_classes.push(#classes);)*
+ Some((
+ LitStr::new("class", span),
+ Value::Dynamic(quote! {
+ {
+ #deprecation_warning
- #deprecation_warning
-
- ::yew::virtual_dom::PositionalAttr::new("class", __yew_classes)
- }
- })
+ let mut __yew_classes = ::yew::html::Classes::with_capacity(#n);
+ #(__yew_classes.push(#classes);)*
+ __yew_classes
+ }
+ }),
+ ))
}
- ClassesForm::Single(classes) => match classes.try_into_lit() {
- Some(lit) => {
- if lit.value().is_empty() {
- None
- } else {
- let sr = lit.stringify();
- Some(quote! { ::yew::virtual_dom::PositionalAttr::new("class", #sr) })
+ ClassesForm::Single(classes) => {
+ match classes.try_into_lit() {
+ Some(lit) => {
+ if lit.value().is_empty() {
+ None
+ } else {
+ Some((
+ LitStr::new("class", lit.span()),
+ Value::Static(quote! { #lit }),
+ ))
+ }
+ }
+ None => {
+ Some((
+ LitStr::new("class", classes.span()),
+ Value::Dynamic(quote! {
+ ::std::convert::Into::<::yew::html::Classes>::into(#classes)
+ }),
+ ))
}
- }
- None => {
- Some(quote! { ::yew::virtual_dom::PositionalAttr::new("class", ::std::convert::Into::<::yew::html::Classes>::into(#classes)) })
}
}
});
- let attrs = normal_attrs.chain(boolean_attrs).chain(class_attr);
- quote! {
- ::yew::virtual_dom::Attributes::Vec(::std::vec![#(#attrs),*])
+ /// Try to turn attribute list into a `::yew::virtual_dom::Attributes::Static`
+ fn try_into_static(src: &[(LitStr, Value)]) -> Option {
+ let mut kv = Vec::with_capacity(src.len());
+ for (k, v) in src.iter() {
+ let v = match v {
+ Value::Static(v) => quote! { #v },
+ Value::Dynamic(_) => return None,
+ };
+ kv.push(quote! { [ #k, #v ] });
+ }
+
+ Some(quote! { ::yew::virtual_dom::Attributes::Static(&[#(#kv),*]) })
}
+
+ let attrs = normal_attrs
+ .chain(boolean_attrs)
+ .chain(class_attr)
+ .collect::>();
+ try_into_static(&attrs).unwrap_or_else(|| {
+ let keys = attrs.iter().map(|(k, _)| quote! { #k });
+ let values = attrs.iter().map(|(_, v)| {
+ quote_spanned! {v.span()=>
+ ::yew::html::IntoPropValue::<
+ ::std::option::Option::<::yew::virtual_dom::AttrValue>
+ >
+ ::into_prop_value(#v)
+ }
+ });
+ quote! {
+ ::yew::virtual_dom::Attributes::Dynamic{
+ keys: &[#(#keys),*],
+ values: ::std::boxed::Box::new([#(#values),*]),
+ }
+ }
+ })
};
let listeners = if listeners.is_empty() {
@@ -291,9 +351,7 @@ impl ToTokens for HtmlElement {
let handle_value_attr = props.value.as_ref().map(|prop| {
let v = prop.value.optimize_literals();
quote_spanned! {v.span()=> {
- __yew_vtag.__macro_push_attr(
- ::yew::virtual_dom::PositionalAttr::new("value", #v),
- );
+ __yew_vtag.__macro_push_attr("value", #v);
}}
});
diff --git a/packages/yew-macro/src/stringify.rs b/packages/yew-macro/src/stringify.rs
index 291d9147b9b..fa6a7b6b4ec 100644
--- a/packages/yew-macro/src/stringify.rs
+++ b/packages/yew-macro/src/stringify.rs
@@ -21,13 +21,21 @@ pub trait Stringify {
/// Optimize literals to `&'static str`, otherwise keep the value as is.
fn optimize_literals(&self) -> TokenStream
+ where
+ Self: ToTokens,
+ {
+ self.optimize_literals_tagged().to_token_stream()
+ }
+
+ /// Like `optimize_literals` but tags static or dynamic strings with [Value]
+ fn optimize_literals_tagged(&self) -> Value
where
Self: ToTokens,
{
if let Some(lit) = self.try_into_lit() {
- lit.to_token_stream()
+ Value::Static(lit.to_token_stream())
} else {
- self.to_token_stream()
+ Value::Dynamic(self.to_token_stream())
}
}
}
@@ -41,6 +49,21 @@ impl Stringify for &T {
}
}
+/// A stringified value that can be either static (known at compile time) or dynamic (known only at
+/// runtime)
+pub enum Value {
+ Static(TokenStream),
+ Dynamic(TokenStream),
+}
+
+impl ToTokens for Value {
+ fn to_tokens(&self, tokens: &mut TokenStream) {
+ tokens.extend(match self {
+ Value::Static(tt) | Value::Dynamic(tt) => tt.clone(),
+ });
+ }
+}
+
impl Stringify for LitStr {
fn try_into_lit(&self) -> Option {
Some(self.clone())
diff --git a/packages/yew-macro/tests/classes_macro/classes-fail.stderr b/packages/yew-macro/tests/classes_macro/classes-fail.stderr
index df4ac0aac3a..4ae13819a9a 100644
--- a/packages/yew-macro/tests/classes_macro/classes-fail.stderr
+++ b/packages/yew-macro/tests/classes_macro/classes-fail.stderr
@@ -51,9 +51,9 @@ error[E0277]: the trait bound `Classes: From<{integer}>` is not satisfied
>
and 4 others
= note: required because of the requirements on the impl of `Into` for `{integer}`
- = note: required because of the requirements on the impl of `From>` for `Classes`
+ = note: required because of the requirements on the impl of `From>` for `Classes`
= note: 1 redundant requirements hidden
- = note: required because of the requirements on the impl of `Into` for `std::vec::Vec<{integer}>`
+ = note: required because of the requirements on the impl of `Into` for `Vec<{integer}>`
error[E0277]: the trait bound `Classes: From<{integer}>` is not satisfied
--> $DIR/classes-fail.rs:13:14
diff --git a/packages/yew-macro/tests/html_macro/element-fail.stderr b/packages/yew-macro/tests/html_macro/element-fail.stderr
index f6f00fcaaa1..9a5e031565f 100644
--- a/packages/yew-macro/tests/html_macro/element-fail.stderr
+++ b/packages/yew-macro/tests/html_macro/element-fail.stderr
@@ -177,10 +177,7 @@ error[E0277]: the trait bound `(): IntoPropValue };
| ^^^^^^^^^^^ the trait `IntoPropValue