mirror of
https://github.com/yewstack/yew.git
synced 2025-12-08 21:26:25 +00:00
Optimize vtag construction (#1867)
* yew-macro: optimize VTag construction in html! macro * vtag: remove inlining suggestions * vtag: clean up doc comment
This commit is contained in:
parent
97f67fa77d
commit
7cac4d4756
@ -132,61 +132,73 @@ impl ToTokens for HtmlElement {
|
|||||||
|
|
||||||
// attributes with special treatment
|
// attributes with special treatment
|
||||||
|
|
||||||
let set_node_ref = node_ref.as_ref().map(|attr| {
|
let node_ref = node_ref
|
||||||
let value = &attr.value;
|
.as_ref()
|
||||||
quote! {
|
.map(|attr| {
|
||||||
#vtag.node_ref = #value;
|
let value = &attr.value;
|
||||||
}
|
quote! { #value }
|
||||||
});
|
})
|
||||||
let set_key = key.as_ref().map(|attr| {
|
.unwrap_or(quote! { ::std::default::Default::default() });
|
||||||
let value = &attr.value;
|
let key = key
|
||||||
quote! {
|
.as_ref()
|
||||||
#vtag.key = ::std::option::Option::Some(::std::convert::Into::<::yew::virtual_dom::Key>::into(#value));
|
.map(|attr| {
|
||||||
}
|
let value = &attr.value;
|
||||||
});
|
quote! {
|
||||||
let set_value = value.as_ref().map(|attr| {
|
::std::option::Option::Some
|
||||||
let value = &attr.value;
|
(::std::convert::Into::<::yew::virtual_dom::Key>::into(#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);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
} else {
|
})
|
||||||
quote_spanned! {value.span()=>
|
.unwrap_or(quote! { ::std::option::Option::None });
|
||||||
#vtag.set_value(&(#value));
|
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)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
});
|
.unwrap_or(quote! { ::std::option::Option::None });
|
||||||
let set_kind = kind.as_ref().map(|attr| {
|
let kind = kind
|
||||||
let value = &attr.value;
|
.as_ref()
|
||||||
if attr.question_mark.is_some() {
|
.map(|attr| {
|
||||||
let sr = stringify::stringify_option_at_runtime(value);
|
let value = &attr.value;
|
||||||
quote_spanned! {value.span()=>
|
if attr.question_mark.is_some() {
|
||||||
if let ::std::option::Option::Some(__yew_v) = #sr {
|
let sr = stringify::stringify_option_at_runtime(value);
|
||||||
#vtag.set_kind(__yew_v);
|
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();
|
.unwrap_or(quote! { ::std::option::Option::None });
|
||||||
quote_spanned! {value.span()=>
|
let checked = checked
|
||||||
#vtag.set_kind(#sr);
|
.as_ref()
|
||||||
}
|
.map(|attr| {
|
||||||
}
|
let value = &attr.value;
|
||||||
});
|
quote_spanned! {value.span()=> #value}
|
||||||
let set_checked = checked.as_ref().map(|attr| {
|
})
|
||||||
let value = &attr.value;
|
.unwrap_or(quote! { false });
|
||||||
quote_spanned! {value.span()=>
|
|
||||||
#vtag.set_checked(#value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// normal attributes
|
// normal attributes
|
||||||
|
|
||||||
let set_attributes = if attributes.is_empty() {
|
let mut attributes = attributes
|
||||||
None
|
.iter()
|
||||||
} else {
|
.map(
|
||||||
let attrs = attributes.iter().map(
|
|
||||||
|Prop {
|
|Prop {
|
||||||
label,
|
label,
|
||||||
question_mark,
|
question_mark,
|
||||||
@ -206,33 +218,22 @@ impl ToTokens for HtmlElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
)
|
||||||
Some(quote! {
|
.collect::<Vec<_>>();
|
||||||
#vtag.attributes = ::yew::virtual_dom::Attributes::Vec(::std::vec![#(#attrs),*]);
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
let push_booleans = if booleans.is_empty() {
|
attributes.extend(booleans.iter().map(|Prop { label, value, .. }| {
|
||||||
None
|
let label_str = label.to_lit_str();
|
||||||
} else {
|
let sr = label.stringify();
|
||||||
let tokens = booleans
|
quote_spanned! {value.span()=> {
|
||||||
.iter()
|
if #value {
|
||||||
.map(|Prop { label, value, .. }| {
|
::yew::virtual_dom::PositionalAttr::new(#label_str, #sr)
|
||||||
let label_str = label.to_lit_str();
|
} else {
|
||||||
let sr = label.stringify();
|
::yew::virtual_dom::PositionalAttr::new_placeholder(#label_str)
|
||||||
quote_spanned! {value.span()=> {
|
}
|
||||||
if #value {
|
}}
|
||||||
#vtag.__macro_push_attribute(#label_str, #sr);
|
}));
|
||||||
} else {
|
|
||||||
#vtag.__macro_push_attribute_placeholder(#label_str);
|
|
||||||
};
|
|
||||||
}}
|
|
||||||
})
|
|
||||||
.collect::<TokenStream>();
|
|
||||||
Some(tokens)
|
|
||||||
};
|
|
||||||
|
|
||||||
let push_classes = match classes {
|
match classes {
|
||||||
Some(ClassesForm::Tuple(classes)) => {
|
Some(ClassesForm::Tuple(classes)) => {
|
||||||
let span = classes.span();
|
let span = classes.span();
|
||||||
let classes: Vec<_> = classes.elems.iter().collect();
|
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);
|
let mut __yew_classes = ::yew::html::Classes::with_capacity(#n);
|
||||||
#(__yew_classes.push(#classes);)*
|
#(__yew_classes.push(#classes);)*
|
||||||
|
|
||||||
#deprecation_warning
|
#deprecation_warning
|
||||||
|
|
||||||
if !__yew_classes.is_empty() {
|
if !__yew_classes.is_empty() {
|
||||||
#vtag.__macro_push_attribute("class", #sr);
|
::yew::virtual_dom::PositionalAttr::new("class", #sr)
|
||||||
} else {
|
} else {
|
||||||
#vtag.__macro_push_attribute_placeholder("class");
|
::yew::virtual_dom::PositionalAttr::new_placeholder("class")
|
||||||
};
|
}
|
||||||
})
|
}});
|
||||||
}
|
}
|
||||||
Some(ClassesForm::Single(classes)) => match classes.try_into_lit() {
|
Some(ClassesForm::Single(classes)) => match classes.try_into_lit() {
|
||||||
Some(lit) => {
|
Some(lit) => {
|
||||||
if lit.value().is_empty() {
|
if !lit.value().is_empty() {
|
||||||
None
|
|
||||||
} else {
|
|
||||||
let sr = lit.stringify();
|
let sr = lit.stringify();
|
||||||
Some(quote! {
|
attributes.push(quote! {
|
||||||
#vtag.__macro_push_attribute("class", #sr);
|
::yew::virtual_dom::PositionalAttr::new("class", #sr)
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let sr = stringify::stringify_at_runtime(quote! { __yew_classes });
|
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);
|
let __yew_classes = ::std::convert::Into::<::yew::html::Classes>::into(#classes);
|
||||||
if !__yew_classes.is_empty() {
|
if !__yew_classes.is_empty() {
|
||||||
#vtag.__macro_push_attribute("class", #sr);
|
::yew::virtual_dom::PositionalAttr::new("class", #sr)
|
||||||
} else {
|
} else {
|
||||||
#vtag.__macro_push_attribute_placeholder("class");
|
::yew::virtual_dom::PositionalAttr::new_placeholder("class")
|
||||||
};
|
}
|
||||||
})
|
}});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
None => None,
|
None => (),
|
||||||
};
|
};
|
||||||
|
|
||||||
let add_listeners = if listeners.is_empty() {
|
let listeners = if listeners.is_empty() {
|
||||||
None
|
quote! { ::std::vec![] }
|
||||||
} else if listeners.iter().any(|attr| attr.question_mark.is_some()) {
|
} else if listeners.iter().any(|attr| attr.question_mark.is_some()) {
|
||||||
let add_listeners = listeners
|
let listeners = listeners
|
||||||
.iter()
|
.iter()
|
||||||
.map(
|
.map(
|
||||||
|Prop {
|
|Prop {
|
||||||
@ -307,41 +306,29 @@ impl ToTokens for HtmlElement {
|
|||||||
let ident = Ident::new("__yew_listener", name.span());
|
let ident = Ident::new("__yew_listener", name.span());
|
||||||
let listener = to_wrapped_listener(name, &ident);
|
let listener = to_wrapped_listener(name, &ident);
|
||||||
quote_spanned! {value.span()=>
|
quote_spanned! {value.span()=>
|
||||||
let #ident = ::std::option::Option::map(#value, |#ident| {
|
::std::option::Option::map(#value, |#ident| #listener)
|
||||||
#listener
|
|
||||||
});
|
|
||||||
if let ::std::option::Option::Some(#ident) = #ident {
|
|
||||||
#vtag.add_listener(#ident);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let listener = to_wrapped_listener(name, value);
|
let listener = to_wrapped_listener(name, value);
|
||||||
quote_spanned! {value.span()=>
|
quote_spanned! {value.span()=> Some(#listener)}
|
||||||
#vtag.add_listener(#listener);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.collect();
|
.collect::<Vec<TokenStream>>();
|
||||||
|
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<dyn ::yew::virtual_dom::Listener>>>()
|
||||||
|
}}
|
||||||
} else {
|
} else {
|
||||||
let listeners_it = listeners
|
let listeners_it = listeners
|
||||||
.iter()
|
.iter()
|
||||||
.map(|Prop { label, value, .. }| to_wrapped_listener(&label.name, value));
|
.map(|Prop { label, value, .. }| to_wrapped_listener(&label.name, value))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
Some(quote! {
|
quote! { ::std::vec![#(#listeners_it),*] }
|
||||||
#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);
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// These are the runtime-checks exclusive to dynamic tags.
|
// These are the runtime-checks exclusive to dynamic tags.
|
||||||
@ -380,21 +367,21 @@ impl ToTokens for HtmlElement {
|
|||||||
|
|
||||||
tokens.extend(quote_spanned! {name.span()=>
|
tokens.extend(quote_spanned! {name.span()=>
|
||||||
{
|
{
|
||||||
#[allow(unused_braces)]
|
#[allow(clippy::redundant_clone, unused_braces)]
|
||||||
let mut #vtag = ::yew::virtual_dom::VTag::new(#name_sr);
|
let mut #vtag = ::yew::virtual_dom::VTag::__new_complete(
|
||||||
|
#name_sr,
|
||||||
#set_node_ref
|
#node_ref,
|
||||||
#set_key
|
#key,
|
||||||
#set_value
|
#value,
|
||||||
#set_kind
|
#kind,
|
||||||
#set_checked
|
#checked,
|
||||||
|
::yew::virtual_dom::Attributes::Vec(::std::vec![#(#attributes),*]),
|
||||||
#set_attributes
|
#listeners,
|
||||||
#push_booleans
|
::yew::virtual_dom::VList{
|
||||||
#push_classes
|
key: ::std::option::Option::None,
|
||||||
|
children: #children,
|
||||||
#add_listeners
|
},
|
||||||
#add_children
|
);
|
||||||
|
|
||||||
#dyn_tag_runtime_checks
|
#dyn_tag_runtime_checks
|
||||||
#[allow(unused_braces)]
|
#[allow(unused_braces)]
|
||||||
@ -408,7 +395,7 @@ fn to_wrapped_listener(name: &Ident, value: impl ToTokens) -> TokenStream {
|
|||||||
quote_spanned! {value.span()=>
|
quote_spanned! {value.span()=>
|
||||||
::std::rc::Rc::new(::yew::html::#name::Wrapper::new(
|
::std::rc::Rc::new(::yew::html::#name::Wrapper::new(
|
||||||
<::yew::virtual_dom::VTag as ::yew::virtual_dom::Transformer<_, _>>::transform(#value),
|
<::yew::virtual_dom::VTag as ::yew::virtual_dom::Transformer<_, _>>::transform(#value),
|
||||||
))
|
)) as ::std::rc::Rc::<dyn ::yew::virtual_dom::Listener>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -200,6 +200,7 @@ error[E0277]: `()` doesn't implement `std::fmt::Display`
|
|||||||
= help: the trait `std::fmt::Display` is not implemented for `()`
|
= 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: 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 because of the requirements on the impl of `ToString` for `()`
|
||||||
|
= note: required by `to_string`
|
||||||
|
|
||||||
error[E0277]: `()` doesn't implement `std::fmt::Display`
|
error[E0277]: `()` doesn't implement `std::fmt::Display`
|
||||||
--> $DIR/element-fail.rs:30:21
|
--> $DIR/element-fail.rs:30:21
|
||||||
|
|||||||
@ -28,7 +28,7 @@ serde = { version = "1.0", features = ["derive"] }
|
|||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
slab = "0.4"
|
slab = "0.4"
|
||||||
thiserror = "1"
|
thiserror = "1"
|
||||||
wasm-bindgen = "0.2.60"
|
wasm-bindgen = "0.2.74"
|
||||||
wasm-bindgen-futures = "0.4"
|
wasm-bindgen-futures = "0.4"
|
||||||
yew-macro = { version = "0.17.0", path = "../yew-macro" }
|
yew-macro = { version = "0.17.0", path = "../yew-macro" }
|
||||||
|
|
||||||
@ -107,7 +107,7 @@ rustversion = "1.0"
|
|||||||
serde_derive = "1"
|
serde_derive = "1"
|
||||||
ssri = "6.0.0"
|
ssri = "6.0.0"
|
||||||
trybuild = "1.0"
|
trybuild = "1.0"
|
||||||
wasm-bindgen-test = "0.3.4"
|
wasm-bindgen-test = "0.3.24"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["agent"]
|
default = ["agent"]
|
||||||
|
|||||||
@ -103,23 +103,56 @@ impl Clone for VTag {
|
|||||||
impl VTag {
|
impl VTag {
|
||||||
/// Creates a new `VTag` instance with `tag` name (cannot be changed later in DOM).
|
/// Creates a new `VTag` instance with `tag` name (cannot be changed later in DOM).
|
||||||
pub fn new(tag: impl Into<Cow<'static, str>>) -> Self {
|
pub fn new(tag: impl Into<Cow<'static, str>>) -> 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<Cow<'static, str>>,
|
||||||
|
node_ref: NodeRef,
|
||||||
|
key: Option<Key>,
|
||||||
|
value: Option<String>,
|
||||||
|
kind: Option<Cow<'static, str>>,
|
||||||
|
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 tag: Cow<'static, str> = tag.into();
|
||||||
let element_type = ElementType::from_tag(&tag);
|
let element_type = ElementType::from_tag(&tag);
|
||||||
VTag {
|
VTag {
|
||||||
tag,
|
tag,
|
||||||
element_type,
|
element_type,
|
||||||
reference: None,
|
reference: None,
|
||||||
attributes: Attributes::new(),
|
attributes,
|
||||||
listeners: Vec::new(),
|
listeners,
|
||||||
captured: Vec::new(),
|
captured: Vec::new(),
|
||||||
children: VList::new(),
|
children,
|
||||||
node_ref: NodeRef::default(),
|
node_ref,
|
||||||
key: None,
|
key,
|
||||||
value: None,
|
value,
|
||||||
kind: None,
|
kind,
|
||||||
// In HTML node `checked` attribute sets `defaultChecked` parameter,
|
// In HTML node `checked` attribute sets `defaultChecked` parameter,
|
||||||
// but we use own field to control real `checked` parameter
|
// but we use own field to control real `checked` parameter
|
||||||
checked: false,
|
checked,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user