mirror of
https://github.com/yewstack/yew.git
synced 2025-12-08 21:26:25 +00:00
Optimize VTag construction, memory footprint and patching (#1947)
* yew-macro: optimize VTag construction in html! macro * yew/vtag: decrease VTag memory footpting and construction args * yew,yew-macro: optimize VTag contruction, memory footprint and diffing * yew/vlist: revert to VTag boxing * yew-macro: add clippy allow for nightly rust * yew-macro: fix allow namespace * *: bump MSRV to 1.49.0 * yew/vnode: restore == for VTag comparison * yew/vtag: clean up reference casting * yew-macro/html_element: fix error span regression
This commit is contained in:
parent
2412a68bee
commit
d89f1ccc5c
2
.github/workflows/pull-request.yml
vendored
2
.github/workflows/pull-request.yml
vendored
@ -113,7 +113,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
toolchain:
|
||||
- 1.45.0 # MSRV
|
||||
- 1.49.0 # MSRV
|
||||
- stable
|
||||
|
||||
steps:
|
||||
|
||||
@ -36,7 +36,7 @@ run_task = { name = ["lint-flow"], fork = true }
|
||||
category = "Testing"
|
||||
description = "Run all tests"
|
||||
dependencies = ["tests-setup"]
|
||||
env = { CARGO_MAKE_WORKSPACE_SKIP_MEMBERS = ["**/examples/*"] }
|
||||
env = { CARGO_MAKE_WORKSPACE_SKIP_MEMBERS = ["**/examples/*", "**/packages/changelog"] }
|
||||
run_task = { name = ["test-flow", "doc-test-flow"], fork = true }
|
||||
|
||||
[tasks.benchmarks]
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
<a href="https://docs.rs/yew/"><img alt="API Docs" src="https://img.shields.io/badge/docs.rs-yew-green"/></a>
|
||||
<a href="https://discord.gg/VQck8X4"><img alt="Discord Chat" src="https://img.shields.io/discord/701068342760570933"/></a>
|
||||
<a href="https://gitlocalize.com/repo/4999/whole_project?utm_source=badge"> <img src="https://gitlocalize.com/repo/4999/whole_project/badge.svg" /> </a>
|
||||
<a href="https://blog.rust-lang.org/2020/07/16/Rust-1.45.0.html"><img alt="Rustc Version 1.45+" src="https://img.shields.io/badge/rustc-1.45%2B-lightgrey.svg"/></a>
|
||||
<a href="https://blog.rust-lang.org/2020/12/31/Rust-1.49.0.html"><img alt="Rustc Version 1.49.0+" src="https://img.shields.io/badge/rustc-1.49%2B-lightgrey.svg"/></a>
|
||||
</p>
|
||||
|
||||
<h4>
|
||||
|
||||
@ -53,9 +53,20 @@ pub fn render_markdown(src: &str) -> Html {
|
||||
pre.add_child(top.into());
|
||||
top = pre;
|
||||
} else if let Tag::Table(aligns) = tag {
|
||||
for r in top.children.iter_mut() {
|
||||
for r in top
|
||||
.children_mut()
|
||||
.iter_mut()
|
||||
.map(|ch| ch.iter_mut())
|
||||
.flatten()
|
||||
{
|
||||
if let VNode::VTag(ref mut vtag) = r {
|
||||
for (i, c) in vtag.children.iter_mut().enumerate() {
|
||||
for (i, c) in vtag
|
||||
.children_mut()
|
||||
.iter_mut()
|
||||
.map(|ch| ch.iter_mut())
|
||||
.flatten()
|
||||
.enumerate()
|
||||
{
|
||||
if let VNode::VTag(ref mut vtag) = c {
|
||||
match aligns[i] {
|
||||
Alignment::None => {}
|
||||
@ -68,7 +79,12 @@ pub fn render_markdown(src: &str) -> Html {
|
||||
}
|
||||
}
|
||||
} else if let Tag::TableHead = tag {
|
||||
for c in top.children.iter_mut() {
|
||||
for c in top
|
||||
.children_mut()
|
||||
.iter_mut()
|
||||
.map(|ch| ch.iter_mut())
|
||||
.flatten()
|
||||
{
|
||||
if let VNode::VTag(ref mut vtag) = c {
|
||||
// TODO
|
||||
// vtag.tag = "th".into();
|
||||
|
||||
@ -97,30 +97,10 @@ impl ToTokens for HtmlElement {
|
||||
children,
|
||||
} = self;
|
||||
|
||||
let name_sr = match &name {
|
||||
TagName::Lit(name) => name.stringify(),
|
||||
TagName::Expr(name) => {
|
||||
let expr = &name.expr;
|
||||
let vtag_name = Ident::new("__yew_vtag_name", expr.span());
|
||||
// this way we get a nice error message (with the correct span) when the expression doesn't return a valid value
|
||||
quote_spanned! {expr.span()=> {
|
||||
#[allow(unused_braces)]
|
||||
let mut #vtag_name = ::std::convert::Into::<::std::borrow::Cow::<'static, str>>::into(#expr);
|
||||
if !#vtag_name.is_ascii() {
|
||||
::std::panic!("a dynamic tag returned a tag name containing non ASCII characters: `{}`", #vtag_name);
|
||||
};
|
||||
// convert to lowercase because the runtime checks rely on it.
|
||||
#vtag_name.to_mut().make_ascii_lowercase();
|
||||
#vtag_name
|
||||
}}
|
||||
}
|
||||
};
|
||||
|
||||
let ElementProps {
|
||||
classes,
|
||||
attributes,
|
||||
booleans,
|
||||
kind,
|
||||
value,
|
||||
checked,
|
||||
node_ref,
|
||||
@ -128,44 +108,44 @@ impl ToTokens for HtmlElement {
|
||||
listeners,
|
||||
} = &props;
|
||||
|
||||
let vtag = Ident::new("__yew_vtag", name.span());
|
||||
|
||||
// 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.optimize_literals();
|
||||
quote! {
|
||||
#vtag.__macro_set_key(#value);
|
||||
}
|
||||
});
|
||||
let set_value = value.as_ref().map(|attr| {
|
||||
let value = attr.value.optimize_literals();
|
||||
quote! {
|
||||
#vtag.set_value(#value);
|
||||
}
|
||||
});
|
||||
let set_kind = kind.as_ref().map(|attr| {
|
||||
let value = attr.value.optimize_literals();
|
||||
quote! {
|
||||
#vtag.set_kind(#value);
|
||||
}
|
||||
});
|
||||
let set_checked = checked.as_ref().map(|attr| {
|
||||
let value = &attr.value;
|
||||
quote! {
|
||||
#vtag.set_checked(#value);
|
||||
}
|
||||
});
|
||||
let node_ref = node_ref
|
||||
.as_ref()
|
||||
.map(|attr| {
|
||||
let value = &attr.value;
|
||||
quote_spanned! {value.span()=>
|
||||
::yew::html::IntoPropValue::<::yew::html::NodeRef>
|
||||
::into_prop_value(#value)
|
||||
}
|
||||
})
|
||||
.unwrap_or(quote! { ::std::default::Default::default() });
|
||||
let key = key
|
||||
.as_ref()
|
||||
.map(|attr| {
|
||||
let value = attr.value.optimize_literals();
|
||||
quote_spanned! {value.span()=>
|
||||
::std::option::Option::Some(
|
||||
::std::convert::Into::<::yew::virtual_dom::Key>::into(#value)
|
||||
)
|
||||
}
|
||||
})
|
||||
.unwrap_or(quote! { ::std::option::Option::None });
|
||||
let value = value
|
||||
.as_ref()
|
||||
.map(wrap_attr_prop)
|
||||
.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 });
|
||||
|
||||
// other attributes
|
||||
|
||||
let set_attributes = {
|
||||
let attributes = {
|
||||
let normal_attrs = attributes.iter().map(|Prop { label, value, .. }| {
|
||||
let key = label.to_lit_str();
|
||||
let value = value.optimize_literals();
|
||||
@ -224,12 +204,12 @@ impl ToTokens for HtmlElement {
|
||||
|
||||
let attrs = normal_attrs.chain(boolean_attrs).chain(class_attr);
|
||||
quote! {
|
||||
#vtag.attributes = ::yew::virtual_dom::Attributes::Vec(::std::vec![#(#attrs),*]);
|
||||
::yew::virtual_dom::Attributes::Vec(::std::vec![#(#attrs),*])
|
||||
}
|
||||
};
|
||||
|
||||
let set_listeners = if listeners.is_empty() {
|
||||
None
|
||||
let listeners = if listeners.is_empty() {
|
||||
quote! { ::std::vec![] }
|
||||
} else {
|
||||
let listeners_it = listeners.iter().map(|Prop { label, value, .. }| {
|
||||
let name = &label.name;
|
||||
@ -238,75 +218,175 @@ impl ToTokens for HtmlElement {
|
||||
}
|
||||
});
|
||||
|
||||
Some(quote! {
|
||||
#vtag.__macro_set_listeners(::std::vec![#(#listeners_it),*]);
|
||||
})
|
||||
quote! { ::std::vec![#(#listeners_it),*].into_iter().flatten().collect() }
|
||||
};
|
||||
|
||||
let add_children = if children.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(quote! {
|
||||
#[allow(clippy::redundant_clone, unused_braces)]
|
||||
#vtag.add_children(#children);
|
||||
})
|
||||
let child_list = quote! {
|
||||
::yew::virtual_dom::VList{
|
||||
key: ::std::option::Option::None,
|
||||
children: #children,
|
||||
}
|
||||
};
|
||||
|
||||
// These are the runtime-checks exclusive to dynamic tags.
|
||||
// For literal tags this is already done at compile-time.
|
||||
let dyn_tag_runtime_checks = if matches!(&name, TagName::Expr(_)) {
|
||||
// when Span::source_file Span::start get stabilised or yew-macro introduces a nightly feature flag
|
||||
// we should expand the panic message to contain the exact location of the dynamic tag.
|
||||
Some(quote! {
|
||||
// check void element
|
||||
if !#vtag.children.is_empty() {
|
||||
match #vtag.tag() {
|
||||
"area" | "base" | "br" | "col" | "embed" | "hr" | "img" | "input" | "link"
|
||||
| "meta" | "param" | "source" | "track" | "wbr" => {
|
||||
::std::panic!("a dynamic tag tried to create a `<{0}>` tag with children. `<{0}>` is a void element which can't have any children.", #vtag.tag());
|
||||
tokens.extend(match &name {
|
||||
TagName::Lit(name) => {
|
||||
let name_span = name.span();
|
||||
let name = name.to_ascii_lowercase_string();
|
||||
match &*name {
|
||||
"input" => {
|
||||
quote_spanned! {name_span=>
|
||||
#[allow(clippy::redundant_clone, unused_braces)]
|
||||
::std::convert::Into::<::yew::virtual_dom::VNode>::into(
|
||||
::yew::virtual_dom::VTag::__new_input(
|
||||
#value,
|
||||
#checked,
|
||||
#node_ref,
|
||||
#key,
|
||||
#attributes,
|
||||
#listeners,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
"textarea" => {
|
||||
quote_spanned! {name_span=>
|
||||
#[allow(clippy::redundant_clone, unused_braces)]
|
||||
::std::convert::Into::<::yew::virtual_dom::VNode>::into(
|
||||
::yew::virtual_dom::VTag::__new_textarea(
|
||||
#value,
|
||||
#node_ref,
|
||||
#key,
|
||||
#attributes,
|
||||
#listeners,
|
||||
),
|
||||
)
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
};
|
||||
|
||||
// handle special attribute value
|
||||
match #vtag.tag() {
|
||||
"input" | "textarea" => {}
|
||||
_ => {
|
||||
let __yew_v = #vtag.value.take();
|
||||
#vtag.__macro_push_attr(::yew::virtual_dom::PositionalAttr::new("value", __yew_v));
|
||||
quote_spanned! {name_span=>
|
||||
#[allow(clippy::redundant_clone, unused_braces)]
|
||||
::std::convert::Into::<::yew::virtual_dom::VNode>::into(
|
||||
::yew::virtual_dom::VTag::__new_other(
|
||||
::std::borrow::Cow::<'static, str>::Borrowed(#name),
|
||||
#node_ref,
|
||||
#key,
|
||||
#attributes,
|
||||
#listeners,
|
||||
#child_list,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
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
|
||||
#set_listeners
|
||||
|
||||
#add_children
|
||||
|
||||
#dyn_tag_runtime_checks
|
||||
{
|
||||
use ::std::convert::From;
|
||||
::yew::virtual_dom::VNode::from(#vtag)
|
||||
}
|
||||
}
|
||||
TagName::Expr(name) => {
|
||||
#[allow(unused_braces)]
|
||||
let vtag = Ident::new("__yew_vtag", name.span());
|
||||
let expr = &name.expr;
|
||||
let vtag_name = Ident::new("__yew_vtag_name", expr.span());
|
||||
|
||||
// handle special attribute value
|
||||
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),
|
||||
);
|
||||
}}
|
||||
});
|
||||
|
||||
// this way we get a nice error message (with the correct span) when the expression
|
||||
// doesn't return a valid value
|
||||
quote_spanned! {expr.span()=> {
|
||||
let mut #vtag_name = ::std::convert::Into::<
|
||||
::std::borrow::Cow::<'static, str>
|
||||
>::into(#expr);
|
||||
if !#vtag_name.is_ascii() {
|
||||
::std::panic!(
|
||||
"a dynamic tag returned a tag name containing non ASCII characters: `{}`",
|
||||
#vtag_name,
|
||||
);
|
||||
}
|
||||
// convert to lowercase because the runtime checks rely on it.
|
||||
#vtag_name.to_mut().make_ascii_lowercase();
|
||||
|
||||
#[allow(clippy::redundant_clone, unused_braces, clippy::let_and_return)]
|
||||
let mut #vtag = match ::std::convert::AsRef::<str>::as_ref(&#vtag_name) {
|
||||
"input" => {
|
||||
::yew::virtual_dom::VTag::__new_textarea(
|
||||
#value,
|
||||
#node_ref,
|
||||
#key,
|
||||
#attributes,
|
||||
#listeners,
|
||||
)
|
||||
}
|
||||
"textarea" => {
|
||||
::yew::virtual_dom::VTag::__new_textarea(
|
||||
#value,
|
||||
#node_ref,
|
||||
#key,
|
||||
#attributes,
|
||||
#listeners,
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
let mut __yew_vtag = ::yew::virtual_dom::VTag::__new_other(
|
||||
#vtag_name,
|
||||
#node_ref,
|
||||
#key,
|
||||
#attributes,
|
||||
#listeners,
|
||||
#child_list,
|
||||
);
|
||||
|
||||
#handle_value_attr
|
||||
|
||||
__yew_vtag
|
||||
}
|
||||
};
|
||||
|
||||
// These are the runtime-checks exclusive to dynamic tags.
|
||||
// For literal tags this is already done at compile-time.
|
||||
//
|
||||
// When Span::source_file Span::start get stabilised or yew-macro introduces a
|
||||
// nightly feature flag we should expand the panic message to contain the exact
|
||||
// location of the dynamic tag.
|
||||
//
|
||||
// check void element
|
||||
if !#vtag.children().is_empty() {
|
||||
match #vtag.tag() {
|
||||
"area" | "base" | "br" | "col" | "embed" | "hr" | "img" | "input"
|
||||
| "link" | "meta" | "param" | "source" | "track" | "wbr"
|
||||
=> {
|
||||
::std::panic!(
|
||||
"a dynamic tag tried to create a `<{0}>` tag with children. `<{0}>` is a void element which can't have any children.",
|
||||
#vtag.tag(),
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
::std::convert::Into::<::yew::virtual_dom::VNode>::into(#vtag)
|
||||
}}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn wrap_attr_prop(prop: &Prop) -> TokenStream {
|
||||
let value = prop.value.optimize_literals();
|
||||
quote_spanned! {value.span()=>
|
||||
::yew::html::IntoPropValue::<
|
||||
::std::option::Option<
|
||||
::yew::virtual_dom::AttrValue
|
||||
>
|
||||
>
|
||||
::into_prop_value(#value)
|
||||
}
|
||||
}
|
||||
|
||||
struct DynamicName {
|
||||
at: Token![@],
|
||||
expr: Option<Block>,
|
||||
|
||||
@ -23,7 +23,6 @@ pub struct ElementProps {
|
||||
pub classes: Option<ClassesForm>,
|
||||
pub booleans: Vec<Prop>,
|
||||
pub value: Option<Prop>,
|
||||
pub kind: Option<Prop>,
|
||||
pub checked: Option<Prop>,
|
||||
pub node_ref: Option<Prop>,
|
||||
pub key: Option<Prop>,
|
||||
@ -46,7 +45,6 @@ impl Parse for ElementProps {
|
||||
.pop("class")
|
||||
.map(|prop| ClassesForm::from_expr(prop.value));
|
||||
let value = props.pop("value");
|
||||
let kind = props.pop("type");
|
||||
let checked = props.pop("checked");
|
||||
|
||||
let SpecialProps { node_ref, key } = props.special;
|
||||
@ -58,7 +56,6 @@ impl Parse for ElementProps {
|
||||
checked,
|
||||
booleans: booleans.into_vec(),
|
||||
value,
|
||||
kind,
|
||||
node_ref,
|
||||
key,
|
||||
})
|
||||
|
||||
@ -185,12 +185,19 @@ error[E0277]: the trait bound `(): IntoPropValue<Option<Cow<'static, str>>>` is
|
||||
|
|
||||
43 | html! { <input type=() /> };
|
||||
| ^^ the trait `IntoPropValue<Option<Cow<'static, str>>>` is not implemented for `()`
|
||||
|
|
||||
::: $WORKSPACE/packages/yew/src/virtual_dom/mod.rs
|
||||
|
|
||||
| pub fn new(key: &'static str, value: impl IntoPropValue<Option<AttrValue>>) -> Self {
|
||||
| -------------------------------- required by this bound in `PositionalAttr::new`
|
||||
|
||||
error[E0277]: the trait bound `(): IntoPropValue<Option<Cow<'static, str>>>` is not satisfied
|
||||
--> $DIR/element-fail.rs:44:26
|
||||
|
|
||||
44 | html! { <input value=() /> };
|
||||
| ^^ the trait `IntoPropValue<Option<Cow<'static, str>>>` is not implemented for `()`
|
||||
|
|
||||
= note: required by `into_prop_value`
|
||||
|
||||
error[E0277]: the trait bound `(): IntoPropValue<Option<Cow<'static, str>>>` is not satisfied
|
||||
--> $DIR/element-fail.rs:45:21
|
||||
@ -309,20 +316,25 @@ error[E0277]: the trait bound `Option<{integer}>: IntoPropValue<Option<yew::Call
|
||||
<Option<&'static str> as IntoPropValue<Option<String>>>
|
||||
<Option<String> as IntoPropValue<Option<Cow<'static, str>>>>
|
||||
|
||||
error[E0308]: mismatched types
|
||||
error[E0277]: the trait bound `(): IntoPropValue<yew::NodeRef>` is not satisfied
|
||||
--> $DIR/element-fail.rs:56:24
|
||||
|
|
||||
56 | html! { <input ref=() /> };
|
||||
| ^^ expected struct `yew::NodeRef`, found `()`
|
||||
| ^^ the trait `IntoPropValue<yew::NodeRef>` is not implemented for `()`
|
||||
|
|
||||
= note: required by `into_prop_value`
|
||||
|
||||
error[E0308]: mismatched types
|
||||
error[E0277]: the trait bound `Option<yew::NodeRef>: IntoPropValue<yew::NodeRef>` is not satisfied
|
||||
--> $DIR/element-fail.rs:57:24
|
||||
|
|
||||
57 | html! { <input ref=Some(NodeRef::default()) /> };
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `yew::NodeRef`, found enum `Option`
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IntoPropValue<yew::NodeRef>` is not implemented for `Option<yew::NodeRef>`
|
||||
|
|
||||
= note: expected struct `yew::NodeRef`
|
||||
found enum `Option<yew::NodeRef>`
|
||||
= help: the following implementations were found:
|
||||
<Option<&'static str> as IntoPropValue<Option<Cow<'static, str>>>>
|
||||
<Option<&'static str> as IntoPropValue<Option<String>>>
|
||||
<Option<String> as IntoPropValue<Option<Cow<'static, str>>>>
|
||||
= note: required by `into_prop_value`
|
||||
|
||||
error[E0277]: the trait bound `Cow<'static, str>: From<{integer}>` is not satisfied
|
||||
--> $DIR/element-fail.rs:71:15
|
||||
|
||||
@ -21,6 +21,7 @@ pub trait IntoPropValue<T> {
|
||||
}
|
||||
|
||||
impl<T> IntoPropValue<T> for T {
|
||||
#[inline]
|
||||
fn into_prop_value(self) -> T {
|
||||
self
|
||||
}
|
||||
@ -29,12 +30,14 @@ impl<T> IntoPropValue<T> for &T
|
||||
where
|
||||
T: ImplicitClone,
|
||||
{
|
||||
#[inline]
|
||||
fn into_prop_value(self) -> T {
|
||||
self.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoPropValue<Option<T>> for T {
|
||||
#[inline]
|
||||
fn into_prop_value(self) -> Option<T> {
|
||||
Some(self)
|
||||
}
|
||||
@ -43,6 +46,7 @@ impl<T> IntoPropValue<Option<T>> for &T
|
||||
where
|
||||
T: ImplicitClone,
|
||||
{
|
||||
#[inline]
|
||||
fn into_prop_value(self) -> Option<T> {
|
||||
Some(self.clone())
|
||||
}
|
||||
@ -52,6 +56,7 @@ macro_rules! impl_into_prop {
|
||||
(|$value:ident: $from_ty:ty| -> $to_ty:ty { $conversion:expr }) => {
|
||||
// implement V -> T
|
||||
impl IntoPropValue<$to_ty> for $from_ty {
|
||||
#[inline]
|
||||
fn into_prop_value(self) -> $to_ty {
|
||||
let $value = self;
|
||||
$conversion
|
||||
@ -59,6 +64,7 @@ macro_rules! impl_into_prop {
|
||||
}
|
||||
// implement V -> Option<T>
|
||||
impl IntoPropValue<Option<$to_ty>> for $from_ty {
|
||||
#[inline]
|
||||
fn into_prop_value(self) -> Option<$to_ty> {
|
||||
let $value = self;
|
||||
Some({ $conversion })
|
||||
@ -66,6 +72,7 @@ macro_rules! impl_into_prop {
|
||||
}
|
||||
// implement Option<V> -> Option<T>
|
||||
impl IntoPropValue<Option<$to_ty>> for Option<$from_ty> {
|
||||
#[inline]
|
||||
fn into_prop_value(self) -> Option<$to_ty> {
|
||||
self.map(IntoPropValue::into_prop_value)
|
||||
}
|
||||
|
||||
@ -26,6 +26,7 @@ macro_rules! impl_action {
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[inline]
|
||||
pub fn __macro_new(callback: impl IntoPropValue<Option<Callback<Event>>>) -> Option<Rc<dyn Listener>> {
|
||||
let callback = callback.into_prop_value()?;
|
||||
Some(Rc::new(Self::new(callback)))
|
||||
|
||||
@ -16,7 +16,7 @@ pub mod vtext;
|
||||
use crate::html::{AnyScope, IntoPropValue, NodeRef};
|
||||
use gloo::events::EventListener;
|
||||
use indexmap::IndexMap;
|
||||
use std::{borrow::Cow, collections::HashMap, fmt, hint::unreachable_unchecked, iter, mem, rc::Rc};
|
||||
use std::{borrow::Cow, collections::HashMap, fmt, hint::unreachable_unchecked, iter, mem};
|
||||
use web_sys::{Element, Node};
|
||||
|
||||
#[doc(inline)]
|
||||
@ -47,23 +47,34 @@ impl fmt::Debug for dyn Listener {
|
||||
}
|
||||
}
|
||||
|
||||
/// A list of event listeners.
|
||||
type Listeners = Vec<Rc<dyn Listener>>;
|
||||
|
||||
/// Attribute value
|
||||
pub type AttrValue = Cow<'static, str>;
|
||||
|
||||
/// Applies contained changes to DOM [Element]
|
||||
trait Apply {
|
||||
/// [Element] type to apply the changes to
|
||||
type Element;
|
||||
|
||||
/// Apply contained values to [Element] with no ancestor
|
||||
fn apply(&mut self, el: &Self::Element);
|
||||
|
||||
/// Apply diff between [self] and `ancestor` to [Element].
|
||||
fn apply_diff(&mut self, el: &Self::Element, ancestor: Self);
|
||||
}
|
||||
|
||||
/// Key-value tuple which makes up an item of the [`Attributes::Vec`] variant.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct PositionalAttr(pub &'static str, pub Option<AttrValue>);
|
||||
impl PositionalAttr {
|
||||
/// Create a positional attribute
|
||||
#[inline]
|
||||
pub fn new(key: &'static str, value: impl IntoPropValue<Option<AttrValue>>) -> Self {
|
||||
Self(key, value.into_prop_value())
|
||||
}
|
||||
|
||||
/// Create a boolean attribute.
|
||||
/// `present` controls whether the attribute is added
|
||||
#[inline]
|
||||
pub fn new_boolean(key: &'static str, present: bool) -> Self {
|
||||
let value = if present {
|
||||
Some(Cow::Borrowed(key))
|
||||
@ -74,15 +85,18 @@ impl PositionalAttr {
|
||||
}
|
||||
|
||||
/// Create a placeholder for removed attributes
|
||||
#[inline]
|
||||
pub fn new_placeholder(key: &'static str) -> Self {
|
||||
Self(key, None)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn transpose(self) -> Option<(&'static str, AttrValue)> {
|
||||
let Self(key, value) = self;
|
||||
value.map(|v| (key, v))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn transposed<'a>(&'a self) -> Option<(&'static str, &'a AttrValue)> {
|
||||
let Self(key, value) = self;
|
||||
value.as_ref().map(|v| (*key, v))
|
||||
@ -298,6 +312,46 @@ impl Attributes {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_attribute(el: &Element, key: &str, value: &str) {
|
||||
el.set_attribute(&key, &value)
|
||||
.expect("invalid attribute key")
|
||||
}
|
||||
}
|
||||
|
||||
impl Apply for Attributes {
|
||||
type Element = Element;
|
||||
|
||||
fn apply(&mut self, el: &Element) {
|
||||
match self {
|
||||
Self::Vec(v) => {
|
||||
for attr in v.iter() {
|
||||
if let Some(v) = &attr.1 {
|
||||
Self::set_attribute(el, &attr.0, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
Self::IndexMap(m) => {
|
||||
for (k, v) in m.iter() {
|
||||
Self::set_attribute(el, k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_diff(&mut self, el: &Element, ancestor: Self) {
|
||||
for change in Self::diff(self, &ancestor) {
|
||||
match change {
|
||||
Patch::Add(key, value) | Patch::Replace(key, value) => {
|
||||
Self::set_attribute(el, key, value);
|
||||
}
|
||||
Patch::Remove(key) => {
|
||||
el.remove_attribute(&key)
|
||||
.expect("could not remove attribute");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<PositionalAttr>> for Attributes {
|
||||
|
||||
@ -39,8 +39,7 @@ impl VNode {
|
||||
pub(crate) fn first_node(&self) -> Node {
|
||||
match self {
|
||||
VNode::VTag(vtag) => vtag
|
||||
.reference
|
||||
.as_ref()
|
||||
.reference()
|
||||
.expect("VTag is not mounted")
|
||||
.clone()
|
||||
.into(),
|
||||
@ -133,24 +132,28 @@ impl Default for VNode {
|
||||
}
|
||||
|
||||
impl From<VText> for VNode {
|
||||
#[inline]
|
||||
fn from(vtext: VText) -> Self {
|
||||
VNode::VText(vtext)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<VList> for VNode {
|
||||
#[inline]
|
||||
fn from(vlist: VList) -> Self {
|
||||
VNode::VList(vlist)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<VTag> for VNode {
|
||||
#[inline]
|
||||
fn from(vtag: VTag) -> Self {
|
||||
VNode::VTag(Box::new(vtag))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<VComp> for VNode {
|
||||
#[inline]
|
||||
fn from(vcomp: VComp) -> Self {
|
||||
VNode::VComp(vcomp)
|
||||
}
|
||||
@ -173,11 +176,10 @@ impl<T: ToString> From<T> for VNode {
|
||||
|
||||
impl<A: Into<VNode>> FromIterator<A> for VNode {
|
||||
fn from_iter<T: IntoIterator<Item = A>>(iter: T) -> Self {
|
||||
let vlist = iter.into_iter().fold(VList::default(), |mut acc, x| {
|
||||
acc.add_child(x.into());
|
||||
acc
|
||||
});
|
||||
VNode::VList(vlist)
|
||||
VNode::VList(VList {
|
||||
key: None,
|
||||
children: iter.into_iter().map(|n| n.into()).collect(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -14,7 +14,7 @@ Your local development environment will need a couple of tools to compile, build
|
||||
To install Rust follow the [official instructions](https://www.rust-lang.org/tools/install).
|
||||
|
||||
:::important
|
||||
The minimum supported Rust version (MSRV) for Yew is `1.45.0`. Older versions can cause unexpected issues accompanied by incomprehensible error messages.
|
||||
The minimum supported Rust version (MSRV) for Yew is `1.49.0`. Older versions can cause unexpected issues accompanied by incomprehensible error messages.
|
||||
You can check your toolchain version using `rustup show` (under "active toolchain") or alternatively `rustc --version`. To update your toolchain, run `rustup update`.
|
||||
:::
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@ You also need to install the `wasm32-unknown-unknown` target to compile Rust to
|
||||
If you're using rustup, you just need to run `rustup target add wasm32-unknown-unknown`.
|
||||
|
||||
:::important
|
||||
The minimum supported Rust version (MSRV) for Yew is `1.45.0`. Older versions can cause unexpected issues accompanied by incomprehensible error messages.
|
||||
The minimum supported Rust version (MSRV) for Yew is `1.49.0`. Older versions can cause unexpected issues accompanied by incomprehensible error messages.
|
||||
You can check your toolchain version using `rustup show` (under "active toolchain") or alternatively `rustc --version`. To update your toolchain, run `rustup update`.
|
||||
:::
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user