mirror of https://github.com/yewstack/yew
Optimise vtag (#1447)
* yew/vtag: remove needless indirection
* yew/vtag: reduce VTag memory footprint
* Revert "yew/vtag: remove needless indirection"
This reverts commit 7c59c61e0e
.
* yew/vtag,yew-macro: optimise attribute setting and memory usage
* yew/vtag,yew-macro: reduce string memory footprint and use static strings more
* yew,yew-macro: opportunistically use static memory for VText
* yew/vtag: use String instead of Box<str>
* yew-macro: remove one extra iteration
* yew/vtag: remove API extension for textarea
* yew/vtag: remove extra calls
* yew-macro: preconstruct StringRef for class literals
* yew-macro: construct non-dynamic VTags in-place
* yew-macro: Insert class and href into attrs in-place
* *: run stable checks
* yew/vtag: use key-pair vector for attributes
* yew/macro,yew: use trait associated methods with paths, where possible
* Update yew/src/virtual_dom/vtag.rs
Co-authored-by: Teymour Aldridge <42674621+teymour-aldridge@users.noreply.github.com>
* Update yew/src/virtual_dom/vtag.rs
Co-authored-by: Teymour Aldridge <42674621+teymour-aldridge@users.noreply.github.com>
* Update yew/src/virtual_dom/vtag.rs
Co-authored-by: Teymour Aldridge <42674621+teymour-aldridge@users.noreply.github.com>
* Update yew/src/virtual_dom/vtag.rs
Co-authored-by: Teymour Aldridge <42674621+teymour-aldridge@users.noreply.github.com>
* Update yew/src/virtual_dom/vtag.rs
Co-authored-by: Teymour Aldridge <42674621+teymour-aldridge@users.noreply.github.com>
* Update yew/src/virtual_dom/vtag.rs
Co-authored-by: Teymour Aldridge <42674621+teymour-aldridge@users.noreply.github.com>
* yew/vtag: comment clarification
as suggested by @teymour-aldridge
* yew/vtag: fix added test failing
* yew/vlist: revert removing key
* yew/vtag,yew-macro: stash VTagInner changes for later
* yew/vtag: restore diff_attributes() generating a patch list + add bechmarks
* yew: fix becnhmarks running with std_web
* yew: remove Href
* yew/vtag: fix comment changes
* examples: fix trait impl
* yew: swap Stringref with Cow
* examples: remove redundant clone
* ci: fix stable check
* yew/VText: remove needless constructor
* *: remove needless trait full paths
* yew/benchmarks: add IndexMap attribute diff becnhmark
* yew-macro: fix stderr regressions
* yew: convert Attributes to enum
with variants optimised for both the html! macro and the VTag API
* yew/benchmarks: move feature guard
* Update examples/common/src/markdown.rs
Co-authored-by: Justin Starry <justin.m.starry@gmail.com>
* Update examples/common/src/markdown.rs
Co-authored-by: Justin Starry <justin.m.starry@gmail.com>
* Update examples/common/src/markdown.rs
Co-authored-by: Justin Starry <justin.m.starry@gmail.com>
* Update examples/common/src/markdown.rs
Co-authored-by: Justin Starry <justin.m.starry@gmail.com>
* Update examples/common/src/markdown.rs
Co-authored-by: Justin Starry <justin.m.starry@gmail.com>
* examples: remove unused import
* Apply suggestions from code review
Co-authored-by: Justin Starry <justin.m.starry@gmail.com>
* yew-macro: rebuild stderr
* yew-macro: accept Into<Cow> for dynamic tags
* yew-macro: remove unneeded {} wrapping
* yew: revert doc comment
* yew/vtag: clean up attribute type conversion
* yew-macro: remove now supported literal failures
Co-authored-by: Teymour Aldridge <42674621+teymour-aldridge@users.noreply.github.com>
Co-authored-by: Justin Starry <justin.starry@icloud.com>
Co-authored-by: Justin Starry <justin.m.starry@gmail.com>
This commit is contained in:
parent
1e3f4e54d3
commit
2b584ca37b
|
@ -0,0 +1,18 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail # https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/
|
||||
|
||||
# SETUP
|
||||
|
||||
test_flags=("--headless" "--firefox")
|
||||
test_features="wasm_test"
|
||||
|
||||
echo "running benchmarks with flags: ${test_flags[*]} and features: ${test_features}"
|
||||
|
||||
# TESTS
|
||||
|
||||
set -x
|
||||
|
||||
(cd yew &&
|
||||
wasm-pack test "${test_flags[@]}" --release \
|
||||
-- --features "${test_features}" bench
|
||||
)
|
|
@ -13,12 +13,13 @@ use yew::{html, Html};
|
|||
fn add_class(vtag: &mut VTag, class: &str) {
|
||||
let mut classes: Classes = vtag
|
||||
.attributes
|
||||
.get("class")
|
||||
.map(AsRef::as_ref)
|
||||
.iter()
|
||||
.find(|(k, _)| k == &"class")
|
||||
.map(|(_, v)| AsRef::as_ref(v))
|
||||
.unwrap_or("")
|
||||
.into();
|
||||
classes.push(class);
|
||||
vtag.add_attribute("class", &classes);
|
||||
vtag.add_attribute("class", classes.to_string());
|
||||
}
|
||||
|
||||
/// Renders a string of Markdown to HTML with the default options (footnotes
|
||||
|
@ -72,7 +73,7 @@ pub fn render_markdown(src: &str) -> Html {
|
|||
if let VNode::VTag(ref mut vtag) = c {
|
||||
// TODO
|
||||
// vtag.tag = "th".into();
|
||||
vtag.add_attribute("scope", &"col");
|
||||
vtag.add_attribute("scope", "col");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -84,7 +85,7 @@ pub fn render_markdown(src: &str) -> Html {
|
|||
}
|
||||
Event::Text(text) => add_child!(VText::new(text.to_string()).into()),
|
||||
Event::Rule => add_child!(VTag::new("hr").into()),
|
||||
Event::SoftBreak => add_child!(VText::new("\n".to_string()).into()),
|
||||
Event::SoftBreak => add_child!(VText::new("\n").into()),
|
||||
Event::HardBreak => add_child!(VTag::new("br").into()),
|
||||
_ => println!("Unknown event: {:#?}", ev),
|
||||
}
|
||||
|
@ -109,7 +110,7 @@ fn make_tag(t: Tag) -> VTag {
|
|||
}
|
||||
Tag::BlockQuote => {
|
||||
let mut el = VTag::new("blockquote");
|
||||
el.add_attribute("class", &"blockquote");
|
||||
el.add_attribute("class", "blockquote");
|
||||
el
|
||||
}
|
||||
Tag::CodeBlock(code_block_kind) => {
|
||||
|
@ -121,10 +122,10 @@ fn make_tag(t: Tag) -> VTag {
|
|||
// highlighting support by locating the language classes and applying dom transforms
|
||||
// on their contents.
|
||||
match lang.as_ref() {
|
||||
"html" => el.add_attribute("class", &"html-language"),
|
||||
"rust" => el.add_attribute("class", &"rust-language"),
|
||||
"java" => el.add_attribute("class", &"java-language"),
|
||||
"c" => el.add_attribute("class", &"c-language"),
|
||||
"html" => el.add_attribute("class", "html-language"),
|
||||
"rust" => el.add_attribute("class", "rust-language"),
|
||||
"java" => el.add_attribute("class", "java-language"),
|
||||
"c" => el.add_attribute("class", "c-language"),
|
||||
_ => {} // Add your own language highlighting support
|
||||
};
|
||||
}
|
||||
|
@ -135,13 +136,13 @@ fn make_tag(t: Tag) -> VTag {
|
|||
Tag::List(Some(1)) => VTag::new("ol"),
|
||||
Tag::List(Some(ref start)) => {
|
||||
let mut el = VTag::new("ol");
|
||||
el.add_attribute("start", start);
|
||||
el.add_attribute("start", start.to_string());
|
||||
el
|
||||
}
|
||||
Tag::Item => VTag::new("li"),
|
||||
Tag::Table(_) => {
|
||||
let mut el = VTag::new("table");
|
||||
el.add_attribute("class", &"table");
|
||||
el.add_attribute("class", "table");
|
||||
el
|
||||
}
|
||||
Tag::TableHead => VTag::new("th"),
|
||||
|
@ -149,36 +150,36 @@ fn make_tag(t: Tag) -> VTag {
|
|||
Tag::TableCell => VTag::new("td"),
|
||||
Tag::Emphasis => {
|
||||
let mut el = VTag::new("span");
|
||||
el.add_attribute("class", &"font-italic");
|
||||
el.add_attribute("class", "font-italic");
|
||||
el
|
||||
}
|
||||
Tag::Strong => {
|
||||
let mut el = VTag::new("span");
|
||||
el.add_attribute("class", &"font-weight-bold");
|
||||
el.add_attribute("class", "font-weight-bold");
|
||||
el
|
||||
}
|
||||
Tag::Link(_link_type, ref href, ref title) => {
|
||||
let mut el = VTag::new("a");
|
||||
el.add_attribute("href", href);
|
||||
el.add_attribute("href", href.to_string());
|
||||
let title = title.clone().into_string();
|
||||
if title != "" {
|
||||
el.add_attribute("title", &title);
|
||||
el.add_attribute("title", title);
|
||||
}
|
||||
el
|
||||
}
|
||||
Tag::Image(_link_type, ref src, ref title) => {
|
||||
let mut el = VTag::new("img");
|
||||
el.add_attribute("src", src);
|
||||
el.add_attribute("src", src.to_string());
|
||||
let title = title.clone().into_string();
|
||||
if title != "" {
|
||||
el.add_attribute("title", &title);
|
||||
el.add_attribute("title", title);
|
||||
}
|
||||
el
|
||||
}
|
||||
Tag::FootnoteDefinition(ref _footnote_id) => VTag::new("span"), // Footnotes are not rendered as anything special
|
||||
Tag::Strikethrough => {
|
||||
let mut el = VTag::new("span");
|
||||
el.add_attribute("class", &"text-decoration-strikethrough");
|
||||
el.add_attribute("class", "text-decoration-strikethrough");
|
||||
el
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#![recursion_limit = "512"]
|
||||
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::borrow::Cow;
|
||||
use strum::IntoEnumIterator;
|
||||
use strum_macros::{EnumIter, ToString};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
@ -8,7 +9,7 @@ use yew::events::KeyboardEvent;
|
|||
use yew::format::Json;
|
||||
use yew::services::storage::{Area, StorageService};
|
||||
use yew::web_sys::HtmlInputElement as InputElement;
|
||||
use yew::{html, Component, ComponentLink, Href, Html, InputData, NodeRef, ShouldRender};
|
||||
use yew::{html, Component, ComponentLink, Html, InputData, NodeRef, ShouldRender};
|
||||
|
||||
const KEY: &str = "yew.todomvc.self";
|
||||
|
||||
|
@ -190,11 +191,16 @@ impl Component for Model {
|
|||
|
||||
impl Model {
|
||||
fn view_filter(&self, filter: Filter) -> Html {
|
||||
let cls = if self.state.filter == filter {
|
||||
"selected"
|
||||
} else {
|
||||
"not-selected"
|
||||
};
|
||||
let flt = filter.clone();
|
||||
html! {
|
||||
<li>
|
||||
<a class=if self.state.filter == flt { "selected" } else { "not-selected" }
|
||||
href=&flt
|
||||
<a class=cls
|
||||
href=filter
|
||||
onclick=self.link.callback(move |_| Msg::SetFilter(flt.clone()))>
|
||||
{ filter }
|
||||
</a>
|
||||
|
@ -272,8 +278,8 @@ pub enum Filter {
|
|||
Completed,
|
||||
}
|
||||
|
||||
impl<'a> Into<Href> for &'a Filter {
|
||||
fn into(self) -> Href {
|
||||
impl<'a> Into<Cow<'static, str>> for &'a Filter {
|
||||
fn into(self) -> Cow<'static, str> {
|
||||
match *self {
|
||||
Filter::All => "#/".into(),
|
||||
Filter::Active => "#/active".into(),
|
||||
|
|
|
@ -137,7 +137,7 @@ where
|
|||
let view_option = |value: &T| {
|
||||
let flag = selected == Some(value);
|
||||
html! {
|
||||
<option value=value selected=flag>{ value.to_string() }</option>
|
||||
<option value=value.to_string() selected=flag>{ value.to_string() }</option>
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -36,9 +36,9 @@ impl<COMP: Component> VTagProducer<COMP> {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn attribute(mut self, name: String, value: String) -> Self {
|
||||
pub fn attribute(mut self, name: &'static str, value: String) -> Self {
|
||||
let effect = Effect::new(move |mut vtag: VTag, _scope: &ScopeHolder<COMP>| {
|
||||
vtag.add_attribute(&name, &value);
|
||||
vtag.add_attribute(name, value);
|
||||
vtag
|
||||
});
|
||||
self.effects.push(effect);
|
||||
|
@ -56,7 +56,7 @@ impl<COMP: Component> VTagProducer<COMP> {
|
|||
|
||||
pub fn classes(mut self, classes: Classes) -> Self {
|
||||
let effect = Effect::new(move |mut vtag: VTag, _scope: &ScopeHolder<COMP>| {
|
||||
vtag.add_attribute("class", &classes.to_string());
|
||||
vtag.add_attribute("class", classes.to_string());
|
||||
vtag
|
||||
});
|
||||
self.effects.push(effect);
|
||||
|
|
|
@ -44,7 +44,10 @@ impl PeekValue<()> for HtmlNode {
|
|||
impl ToTokens for HtmlNode {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
tokens.extend(match &self {
|
||||
HtmlNode::Literal(lit) => quote! {#lit},
|
||||
HtmlNode::Literal(lit) => {
|
||||
let sr = crate::stringify::Constructor::from(lit.as_ref());
|
||||
quote! { ::yew::virtual_dom::VText::new(#sr) }
|
||||
}
|
||||
HtmlNode::Expression(expr) => quote_spanned! {expr.span()=> #expr},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ use super::HtmlChildrenTree;
|
|||
use super::HtmlDashedName;
|
||||
use super::HtmlProp as TagAttribute;
|
||||
use super::HtmlPropSuffix as TagSuffix;
|
||||
use crate::stringify;
|
||||
use crate::{non_capitalized_ascii, Peek, PeekValue};
|
||||
use boolinator::Boolinator;
|
||||
use proc_macro2::{Delimiter, Span};
|
||||
|
@ -104,7 +105,7 @@ impl ToTokens for HtmlTag {
|
|||
let name = match &tag_name {
|
||||
TagName::Lit(name) => {
|
||||
let name_str = name.to_string();
|
||||
quote! {#name_str}
|
||||
quote! { ::std::borrow::Cow::<'static, str>::Borrowed(#name_str) }
|
||||
}
|
||||
TagName::Expr(name) => {
|
||||
let expr = &name.expr;
|
||||
|
@ -112,7 +113,7 @@ impl ToTokens for HtmlTag {
|
|||
// 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::borrow::Cow::<'static, str>::from(#expr);
|
||||
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);
|
||||
}
|
||||
|
@ -132,50 +133,72 @@ impl ToTokens for HtmlTag {
|
|||
checked,
|
||||
node_ref,
|
||||
key,
|
||||
href,
|
||||
listeners,
|
||||
} = &attributes;
|
||||
|
||||
let vtag = Ident::new("__yew_vtag", tag_name.span());
|
||||
let attr_pairs = attributes.iter().map(|TagAttribute { label, value }| {
|
||||
let label_str = label.to_string();
|
||||
quote_spanned! {value.span()=> (#label_str.to_owned(), (#value).to_string()) }
|
||||
});
|
||||
let mut attr_pairs: Vec<_> = attributes
|
||||
.iter()
|
||||
.map(|TagAttribute { label, value }| {
|
||||
let label_str = label.to_string();
|
||||
let sr = stringify::Constructor::from(value);
|
||||
quote! { (#label_str, #sr) }
|
||||
})
|
||||
.collect();
|
||||
let set_booleans = booleans.iter().map(|TagAttribute { label, value }| {
|
||||
let label_str = label.to_string();
|
||||
// We use `set_boolean_attribute` instead of inlining an if statement to avoid
|
||||
// the `suspicious_else_formatting` clippy warning.
|
||||
quote_spanned! {value.span()=> #vtag.set_boolean_attribute(&#label_str, #value); }
|
||||
quote_spanned! {value.span()=> {
|
||||
#[allow(clippy::suspicious_else_formatting)]
|
||||
if #value {
|
||||
#vtag.push_attribute(
|
||||
#label_str,
|
||||
::std::borrow::Cow::<'static, str>::Borrowed(#label_str),
|
||||
);
|
||||
}
|
||||
}}
|
||||
});
|
||||
let set_kind = kind.iter().map(|kind| {
|
||||
quote_spanned! {kind.span()=> #vtag.set_kind(&(#kind)); }
|
||||
let sr = stringify::Constructor::from(kind);
|
||||
quote_spanned! {kind.span()=> #vtag.set_kind(#sr); }
|
||||
});
|
||||
let set_value = value.iter().map(|value| {
|
||||
quote_spanned! {value.span()=> #vtag.set_value(&(#value)); }
|
||||
});
|
||||
let add_href = href.iter().map(|href| {
|
||||
quote_spanned! {href.span()=>
|
||||
let __yew_href: ::yew::html::Href = (#href).into();
|
||||
#vtag.add_attribute("href", &__yew_href);
|
||||
}
|
||||
});
|
||||
let set_checked = checked.iter().map(|checked| {
|
||||
quote_spanned! {checked.span()=> #vtag.set_checked(#checked); }
|
||||
});
|
||||
let set_classes = classes.iter().map(|classes_form| match classes_form {
|
||||
ClassesForm::Tuple(classes) => quote! {
|
||||
let __yew_classes = ::yew::virtual_dom::Classes::default()#(.extend(#classes))*;
|
||||
|
||||
let set_classes = match classes {
|
||||
Some(ClassesForm::Tuple(classes)) => Some(quote! {
|
||||
let __yew_classes
|
||||
= ::yew::virtual_dom::Classes::default()#(.extend(#classes))*;
|
||||
if !__yew_classes.is_empty() {
|
||||
#vtag.add_attribute("class", &__yew_classes);
|
||||
#vtag.push_attribute("class", __yew_classes.to_string());
|
||||
}
|
||||
},
|
||||
ClassesForm::Single(classes) => quote! {
|
||||
let __yew_classes = ::std::convert::Into::<::yew::virtual_dom::Classes>::into(#classes);
|
||||
if !__yew_classes.is_empty() {
|
||||
#vtag.add_attribute("class", &__yew_classes);
|
||||
}),
|
||||
Some(ClassesForm::Single(classes)) => match stringify::try_stringify_expr(classes) {
|
||||
Some(s) => {
|
||||
if !s.is_empty() {
|
||||
let sr = stringify::Constructor::from(s);
|
||||
attr_pairs.push(quote! { ("class", #sr) });
|
||||
}
|
||||
None
|
||||
}
|
||||
None => Some(quote! {
|
||||
let __yew_classes
|
||||
= ::std::convert::Into::<::yew::virtual_dom::Classes>::into(#classes);
|
||||
if !__yew_classes.is_empty() {
|
||||
#vtag.push_attribute(
|
||||
"class",
|
||||
::std::string::ToString::to_string(&__yew_classes),
|
||||
);
|
||||
}
|
||||
}),
|
||||
},
|
||||
});
|
||||
None => None,
|
||||
};
|
||||
let set_classes_it = set_classes.iter();
|
||||
|
||||
let set_node_ref = node_ref.iter().map(|node_ref| {
|
||||
quote! {
|
||||
#vtag.node_ref = #node_ref;
|
||||
|
@ -186,16 +209,18 @@ impl ToTokens for HtmlTag {
|
|||
#vtag.key = Some(::yew::virtual_dom::Key::from(#key));
|
||||
}
|
||||
});
|
||||
let listeners = listeners.iter().map(|listener| {
|
||||
let name = &listener.label.name;
|
||||
let callback = &listener.value;
|
||||
let listeners: Vec<_> = listeners
|
||||
.iter()
|
||||
.map(|listener| {
|
||||
let name = &listener.label.name;
|
||||
let callback = &listener.value;
|
||||
|
||||
quote_spanned! {name.span()=> ::yew::html::#name::Wrapper::new(
|
||||
<::yew::virtual_dom::VTag as ::yew::virtual_dom::Transformer<_, _>>::transform(
|
||||
#callback
|
||||
)
|
||||
)}
|
||||
});
|
||||
quote_spanned! {name.span()=> ::yew::html::#name::Wrapper::new(
|
||||
<::yew::virtual_dom::VTag as ::yew::virtual_dom::Transformer<_, _>>
|
||||
::transform(#callback),
|
||||
)}
|
||||
})
|
||||
.collect();
|
||||
|
||||
// These are the runtime-checks exclusive to dynamic tags.
|
||||
// For literal tags this is already done at compile-time.
|
||||
|
@ -219,7 +244,7 @@ impl ToTokens for HtmlTag {
|
|||
"input" | "textarea" => {}
|
||||
_ => {
|
||||
if let ::std::option::Option::Some(value) = #vtag.value.take() {
|
||||
#vtag.attributes.insert("value".to_string(), value);
|
||||
#vtag.push_attribute("value", value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -228,21 +253,37 @@ impl ToTokens for HtmlTag {
|
|||
None
|
||||
};
|
||||
|
||||
// Constant ifs - easy for the compiler to optimise out
|
||||
// Attribute setting ordered to reduce reallocation on collection expansion
|
||||
let has_attrs = !attr_pairs.is_empty();
|
||||
let has_listeners = !listeners.is_empty();
|
||||
let has_children = !children.is_empty();
|
||||
tokens.extend(quote! {{
|
||||
#[allow(unused_braces)]
|
||||
let mut #vtag = ::yew::virtual_dom::VTag::new(#name);
|
||||
#(#set_kind)*
|
||||
#(#set_value)*
|
||||
#(#add_href)*
|
||||
#(#set_checked)*
|
||||
#(#set_booleans)*
|
||||
#(#set_classes)*
|
||||
#(#set_node_ref)*
|
||||
#(#set_key)*
|
||||
#[allow(redundant_clone, unused_braces)]
|
||||
#vtag.add_attributes(vec![#(#attr_pairs),*]);
|
||||
#vtag.add_listeners(vec![#(::std::rc::Rc::new(#listeners)),*]);
|
||||
#vtag.add_children(#children);
|
||||
#(#set_kind)*
|
||||
|
||||
#[allow(clippy::suspicious_else_formatting)]
|
||||
if #has_attrs {
|
||||
#vtag.attributes = ::yew::virtual_dom::Attributes::Vec(vec![#(#attr_pairs),*]);
|
||||
}
|
||||
#(#set_booleans)*
|
||||
#(#set_classes_it)*
|
||||
#(#set_checked)*
|
||||
#(#set_value)*
|
||||
|
||||
if #has_listeners {
|
||||
#vtag.add_listeners(vec![#(::std::rc::Rc::new(#listeners)),*]);
|
||||
}
|
||||
if #has_children {
|
||||
#[allow(redundant_clone, unused_braces)]
|
||||
#vtag.add_children(#children);
|
||||
}
|
||||
|
||||
#dyn_tag_runtime_checks
|
||||
#[allow(unused_braces)]
|
||||
::yew::virtual_dom::VNode::from(#vtag)
|
||||
}});
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@ pub struct TagAttributes {
|
|||
pub checked: Option<Expr>,
|
||||
pub node_ref: Option<Expr>,
|
||||
pub key: Option<Expr>,
|
||||
pub href: Option<Expr>,
|
||||
}
|
||||
|
||||
pub enum ClassesForm {
|
||||
|
@ -316,8 +315,6 @@ impl Parse for TagAttributes {
|
|||
let node_ref = TagAttributes::remove_attr(&mut attributes, "ref");
|
||||
let key = TagAttributes::remove_attr(&mut attributes, "key");
|
||||
|
||||
let href = TagAttributes::remove_attr(&mut attributes, "href");
|
||||
|
||||
Ok(TagAttributes {
|
||||
attributes,
|
||||
classes,
|
||||
|
@ -327,7 +324,6 @@ impl Parse for TagAttributes {
|
|||
value,
|
||||
kind,
|
||||
node_ref,
|
||||
href,
|
||||
key,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -131,9 +131,10 @@ impl Parse for HtmlRootVNode {
|
|||
impl ToTokens for HtmlRootVNode {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let new_tokens = self.0.to_token_stream();
|
||||
tokens.extend(quote! {
|
||||
tokens.extend(quote! {{
|
||||
#[allow(unused_braces)]
|
||||
::yew::virtual_dom::VNode::from(#new_tokens)
|
||||
});
|
||||
}});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -59,6 +59,7 @@
|
|||
|
||||
mod derive_props;
|
||||
mod html_tree;
|
||||
mod stringify;
|
||||
|
||||
use derive_props::DerivePropsInput;
|
||||
use html_tree::{HtmlRoot, HtmlRootVNode};
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
use proc_macro2::TokenStream;
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::{Expr, Lit};
|
||||
|
||||
/// Attempt converting expression to str, if it's a literal
|
||||
pub fn try_stringify_expr(src: &Expr) -> Option<String> {
|
||||
match src {
|
||||
Expr::Lit(l) => try_stringify_lit(&l.lit),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt converting literal to str literal
|
||||
fn try_stringify_lit(src: &Lit) -> Option<String> {
|
||||
match src {
|
||||
Lit::Str(v) => Some(v.value()),
|
||||
Lit::Char(v) => Some(v.value().to_string()),
|
||||
Lit::Int(v) => Some(v.base10_digits().to_string()),
|
||||
Lit::Float(v) => Some(v.base10_digits().to_string()),
|
||||
Lit::Bool(v) => Some(v.value.to_string()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts literals and expressions to Cow<'static, str> construction calls
|
||||
pub struct Constructor(TokenStream);
|
||||
|
||||
macro_rules! stringify_at_runtime {
|
||||
($src:expr) => {{
|
||||
let src = $src;
|
||||
Self(quote! {
|
||||
::std::borrow::Cow::<'static, str>::Owned(
|
||||
::std::string::ToString::to_string(&#src),
|
||||
)
|
||||
})
|
||||
}};
|
||||
}
|
||||
|
||||
impl From<&Expr> for Constructor {
|
||||
fn from(src: &Expr) -> Self {
|
||||
match try_stringify_expr(src) {
|
||||
Some(s) => Self::from(s),
|
||||
None => stringify_at_runtime!(src),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Lit> for Constructor {
|
||||
fn from(src: &Lit) -> Self {
|
||||
match try_stringify_lit(src) {
|
||||
Some(s) => Self::from(s),
|
||||
None => stringify_at_runtime!(src),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Constructor {
|
||||
fn from(src: String) -> Self {
|
||||
Self(quote! { ::std::borrow::Cow::<'static, str>::Borrowed(#src) })
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for Constructor {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
tokens.extend(std::iter::once(self.0.clone()));
|
||||
}
|
||||
}
|
|
@ -285,13 +285,13 @@ error[E0599]: no method named `build` found for struct `ChildContainerProperties
|
|||
candidate #1: `proc_macro::bridge::server::TokenStreamBuilder`
|
||||
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0277]: the trait bound `yew::virtual_dom::vcomp::VChild<Child>: std::convert::From<&str>` is not satisfied
|
||||
error[E0277]: the trait bound `yew::virtual_dom::vcomp::VChild<Child>: std::convert::From<yew::virtual_dom::vtext::VText>` is not satisfied
|
||||
--> $DIR/html-component-fail.rs:113:5
|
||||
|
|
||||
113 | html! { <ChildContainer>{ "Not allowed" }</ChildContainer> };
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::convert::From<&str>` is not implemented for `yew::virtual_dom::vcomp::VChild<Child>`
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::convert::From<yew::virtual_dom::vtext::VText>` is not implemented for `yew::virtual_dom::vcomp::VChild<Child>`
|
||||
|
|
||||
= note: required because of the requirements on the impl of `std::convert::Into<yew::virtual_dom::vcomp::VChild<Child>>` for `&str`
|
||||
= note: required because of the requirements on the impl of `std::convert::Into<yew::virtual_dom::vcomp::VChild<Child>>` for `yew::virtual_dom::vtext::VText`
|
||||
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0277]: the trait bound `yew::virtual_dom::vcomp::VChild<Child>: std::convert::From<yew::virtual_dom::vnode::VNode>` is not satisfied
|
||||
|
|
|
@ -9,10 +9,8 @@ fn compile_fail() {
|
|||
// unsupported literals
|
||||
html! { b'a' };
|
||||
html! { b"str" };
|
||||
html! { 1111111111111111111111111111111111111111111111111111111111111111111111111111 };
|
||||
html! { <span>{ b'a' }</span> };
|
||||
html! { <span>{ b"str" }</span> };
|
||||
html! { <span>{ 1111111111111111111111111111111111111111111111111111111111111111111111111111 }</span> };
|
||||
|
||||
let not_node = || ();
|
||||
html! {
|
||||
|
|
|
@ -22,30 +22,18 @@ error: unsupported type
|
|||
11 | html! { b"str" };
|
||||
| ^^^^^^
|
||||
|
||||
error: integer literal is too large
|
||||
--> $DIR/html-node-fail.rs:12:14
|
||||
error: unsupported type
|
||||
--> $DIR/html-node-fail.rs:12:22
|
||||
|
|
||||
12 | html! { 1111111111111111111111111111111111111111111111111111111111111111111111111111 };
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
12 | html! { <span>{ b'a' }</span> };
|
||||
| ^^^^
|
||||
|
||||
error: unsupported type
|
||||
--> $DIR/html-node-fail.rs:13:22
|
||||
|
|
||||
13 | html! { <span>{ b'a' }</span> };
|
||||
| ^^^^
|
||||
|
||||
error: unsupported type
|
||||
--> $DIR/html-node-fail.rs:14:22
|
||||
|
|
||||
14 | html! { <span>{ b"str" }</span> };
|
||||
13 | html! { <span>{ b"str" }</span> };
|
||||
| ^^^^^^
|
||||
|
||||
error: integer literal is too large
|
||||
--> $DIR/html-node-fail.rs:15:22
|
||||
|
|
||||
15 | html! { <span>{ 1111111111111111111111111111111111111111111111111111111111111111111111111111 }</span> };
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error[E0425]: cannot find value `invalid` in this scope
|
||||
--> $DIR/html-node-fail.rs:7:13
|
||||
|
|
||||
|
@ -65,9 +53,9 @@ error[E0277]: `()` doesn't implement `std::fmt::Display`
|
|||
= note: required by `std::convert::From::from`
|
||||
|
||||
error[E0277]: `()` doesn't implement `std::fmt::Display`
|
||||
--> $DIR/html-node-fail.rs:19:9
|
||||
--> $DIR/html-node-fail.rs:17:9
|
||||
|
|
||||
19 | not_node()
|
||||
17 | not_node()
|
||||
| ^^^^^^^^^^ `()` cannot be formatted with the default formatter
|
||||
|
|
||||
= help: the trait `std::fmt::Display` is not implemented for `()`
|
||||
|
|
|
@ -155,14 +155,16 @@ error[E0308]: mismatched types
|
|||
| ^ expected `bool`, found integer
|
||||
|
||||
error[E0277]: `()` doesn't implement `std::fmt::Display`
|
||||
--> $DIR/html-tag-fail.rs:28:25
|
||||
--> $DIR/html-tag-fail.rs:28:5
|
||||
|
|
||||
28 | html! { <input type=() /> };
|
||||
| ^^ `()` cannot be formatted with the default formatter
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `()` cannot be formatted with the default formatter
|
||||
|
|
||||
= 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 `std::string::ToString` for `()`
|
||||
= note: required by `std::string::ToString::to_string`
|
||||
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0277]: `()` doesn't implement `std::fmt::Display`
|
||||
--> $DIR/html-tag-fail.rs:29:26
|
||||
|
@ -174,16 +176,17 @@ error[E0277]: `()` doesn't implement `std::fmt::Display`
|
|||
= 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 `std::string::ToString` for `()`
|
||||
|
||||
error[E0277]: the trait bound `yew::html::Href: std::convert::From<()>` is not satisfied
|
||||
--> $DIR/html-tag-fail.rs:30:21
|
||||
error[E0277]: `()` doesn't implement `std::fmt::Display`
|
||||
--> $DIR/html-tag-fail.rs:30:5
|
||||
|
|
||||
30 | html! { <a href=() /> };
|
||||
| ^^ the trait `std::convert::From<()>` is not implemented for `yew::html::Href`
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ `()` cannot be formatted with the default formatter
|
||||
|
|
||||
= help: the following implementations were found:
|
||||
<yew::html::Href as std::convert::From<&'a str>>
|
||||
<yew::html::Href as std::convert::From<std::string::String>>
|
||||
= note: required because of the requirements on the impl of `std::convert::Into<yew::html::Href>` 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: required because of the requirements on the impl of `std::string::ToString` for `()`
|
||||
= note: required by `std::string::ToString::to_string`
|
||||
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/html-tag-fail.rs:32:20
|
||||
|
@ -203,22 +206,17 @@ error[E0308]: mismatched types
|
|||
= note: expected enum `yew::callback::Callback<web_sys::features::gen_MouseEvent::MouseEvent>`
|
||||
found enum `yew::callback::Callback<std::string::String>`
|
||||
|
||||
error[E0599]: no method named `to_string` found for struct `NotToString` in the current scope
|
||||
--> $DIR/html-tag-fail.rs:35:27
|
||||
error[E0277]: `NotToString` doesn't implement `std::fmt::Display`
|
||||
--> $DIR/html-tag-fail.rs:35:5
|
||||
|
|
||||
3 | struct NotToString;
|
||||
| -------------------
|
||||
| |
|
||||
| method `to_string` not found for this
|
||||
| doesn't satisfy `NotToString: std::fmt::Display`
|
||||
| doesn't satisfy `NotToString: std::string::ToString`
|
||||
...
|
||||
35 | html! { <input string=NotToString /> };
|
||||
| ^^^^^^^^^^^ method not found in `NotToString`
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `NotToString` cannot be formatted with the default formatter
|
||||
|
|
||||
= note: the method `to_string` exists but the following trait bounds were not satisfied:
|
||||
`NotToString: std::fmt::Display`
|
||||
which is required by `NotToString: std::string::ToString`
|
||||
= help: the trait `std::fmt::Display` is not implemented for `NotToString`
|
||||
= 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 `std::string::ToString` for `NotToString`
|
||||
= note: required by `std::string::ToString::to_string`
|
||||
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/html-tag-fail.rs:37:24
|
||||
|
@ -238,4 +236,5 @@ error[E0277]: the trait bound `std::borrow::Cow<'static, str>: std::convert::Fro
|
|||
<std::borrow::Cow<'a, [T]> as std::convert::From<std::vec::Vec<T>>>
|
||||
<std::borrow::Cow<'a, std::ffi::CStr> as std::convert::From<&'a std::ffi::CStr>>
|
||||
and 11 others
|
||||
= note: required by `std::convert::From::from`
|
||||
= note: required because of the requirements on the impl of `std::convert::Into<std::borrow::Cow<'static, str>>` for `{integer}`
|
||||
= note: required by `std::convert::Into::into`
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
#![recursion_limit = "512"]
|
||||
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::borrow::Cow;
|
||||
use strum::IntoEnumIterator;
|
||||
use strum_macros::{EnumIter, ToString};
|
||||
use yew::events::IKeyboardEvent;
|
||||
use yew::format::Json;
|
||||
use yew::services::storage::{Area, StorageService};
|
||||
use yew::{html, Component, ComponentLink, Href, Html, InputData, KeyPressEvent, ShouldRender};
|
||||
use yew::{html, Component, ComponentLink, Html, InputData, KeyPressEvent, ShouldRender};
|
||||
|
||||
const KEY: &str = "yew.todomvc.self";
|
||||
|
||||
|
@ -168,11 +169,16 @@ impl Component for Model {
|
|||
|
||||
impl Model {
|
||||
fn view_filter(&self, filter: Filter) -> Html {
|
||||
let cls = if self.state.filter == filter {
|
||||
"selected"
|
||||
} else {
|
||||
"not-selected"
|
||||
};
|
||||
let flt = filter.clone();
|
||||
html! {
|
||||
<li>
|
||||
<a class=if self.state.filter == flt { "selected" } else { "not-selected" }
|
||||
href=&flt
|
||||
<a class=cls
|
||||
href=flt
|
||||
onclick=self.link.callback(move |_| Msg::SetFilter(flt.clone()))>
|
||||
{ filter }
|
||||
</a>
|
||||
|
@ -248,8 +254,8 @@ pub enum Filter {
|
|||
Completed,
|
||||
}
|
||||
|
||||
impl<'a> Into<Href> for &'a Filter {
|
||||
fn into(self) -> Href {
|
||||
impl<'a> Into<Cow<'static, str>> for &'a Filter {
|
||||
fn into(self) -> Cow<'static, str> {
|
||||
match *self {
|
||||
Filter::All => "#/".into(),
|
||||
Filter::Active => "#/active".into(),
|
||||
|
|
|
@ -110,6 +110,7 @@ wasm-bindgen = "0.2.60"
|
|||
wasm-bindgen-test = "0.3.4"
|
||||
base64 = "0.12.0"
|
||||
ssri = "6.0.0"
|
||||
easybench-wasm = "0.2.1"
|
||||
|
||||
[target.'cfg(target_os = "emscripten")'.dependencies]
|
||||
ryu = "1.0.2" # 1.0.1 breaks emscripten
|
||||
|
|
|
@ -500,32 +500,6 @@ impl EmptyBuilder {
|
|||
/// Link to component's scope for creating callbacks.
|
||||
pub type ComponentLink<COMP> = Scope<COMP>;
|
||||
|
||||
/// A bridging type for checking `href` attribute value.
|
||||
#[derive(Debug)]
|
||||
pub struct Href {
|
||||
link: String,
|
||||
}
|
||||
|
||||
impl From<String> for Href {
|
||||
fn from(link: String) -> Self {
|
||||
Href { link }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for Href {
|
||||
fn from(link: &'a str) -> Self {
|
||||
Href {
|
||||
link: link.to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for Href {
|
||||
fn to_string(&self) -> String {
|
||||
self.link.to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
@ -206,7 +206,7 @@ pub mod prelude {
|
|||
pub use crate::callback::Callback;
|
||||
pub use crate::events::*;
|
||||
pub use crate::html::{
|
||||
Children, ChildrenWithProps, Component, ComponentLink, Href, Html, NodeRef, Properties,
|
||||
Children, ChildrenWithProps, Component, ComponentLink, Html, NodeRef, Properties,
|
||||
Renderable, ShouldRender,
|
||||
};
|
||||
pub use crate::macros::*;
|
||||
|
|
|
@ -15,8 +15,8 @@ pub mod vtext;
|
|||
|
||||
use crate::html::{AnyScope, NodeRef};
|
||||
use cfg_if::cfg_if;
|
||||
use indexmap::set::IndexSet;
|
||||
use std::collections::HashMap;
|
||||
use indexmap::{IndexMap, IndexSet};
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
use std::rc::Rc;
|
||||
cfg_if! {
|
||||
|
@ -60,8 +60,77 @@ impl fmt::Debug for dyn Listener {
|
|||
/// A list of event listeners.
|
||||
type Listeners = Vec<Rc<dyn Listener>>;
|
||||
|
||||
/// A map of attributes.
|
||||
type Attributes = HashMap<String, String>;
|
||||
/// A collection of attributes for an element
|
||||
#[derive(PartialEq, Eq, Clone, Debug)]
|
||||
pub enum Attributes {
|
||||
/// A vector is ideal because most of the time the list will neither change
|
||||
/// length nor key order.
|
||||
Vec(Vec<(&'static str, Cow<'static, str>)>),
|
||||
|
||||
/// IndexMap is used to provide runtime attribute deduplication in cases where the html! macro
|
||||
/// was not used to guarantee it.
|
||||
IndexMap(IndexMap<&'static str, Cow<'static, str>>),
|
||||
}
|
||||
|
||||
impl Attributes {
|
||||
/// Construct a default Attributes instance
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Construct new IndexMap variant from Vec variant
|
||||
pub(crate) fn new_indexmap(v: Vec<(&'static str, Cow<'static, str>)>) -> Self {
|
||||
Self::IndexMap(v.into_iter().collect())
|
||||
}
|
||||
|
||||
/// Return iterator over attribute key-value pairs
|
||||
pub fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = (&'static str, &'a str)> + 'a> {
|
||||
macro_rules! pack {
|
||||
($src:expr) => {
|
||||
Box::new($src.iter().map(|(k, v)| (*k, v.as_ref())))
|
||||
};
|
||||
}
|
||||
|
||||
match self {
|
||||
Self::Vec(v) => pack!(v),
|
||||
Self::IndexMap(m) => pack!(m),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsMut<IndexMap<&'static str, Cow<'static, str>>> for Attributes {
|
||||
fn as_mut(&mut self) -> &mut IndexMap<&'static str, Cow<'static, str>> {
|
||||
match self {
|
||||
Self::IndexMap(m) => m,
|
||||
Self::Vec(v) => {
|
||||
*self = Self::new_indexmap(std::mem::take(v));
|
||||
self.as_mut()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_attrs_from {
|
||||
($($from:path => $variant:ident)*) => {
|
||||
$(
|
||||
impl From<$from> for Attributes {
|
||||
fn from(v: $from) -> Self {
|
||||
Self::$variant(v)
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
impl_attrs_from! {
|
||||
Vec<(&'static str, Cow<'static, str>)> => Vec
|
||||
IndexMap<&'static str, Cow<'static, str>> => IndexMap
|
||||
}
|
||||
|
||||
impl Default for Attributes {
|
||||
fn default() -> Self {
|
||||
Self::Vec(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
/// A set of classes.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
|
@ -343,7 +412,7 @@ mod layout_tests {
|
|||
let parent_node: Node = parent_element.clone().into();
|
||||
let end_node = document.create_text_node("END");
|
||||
parent_node.append_child(&end_node).unwrap();
|
||||
let mut empty_node: VNode = VText::new("".into()).into();
|
||||
let mut empty_node: VNode = VText::new("").into();
|
||||
|
||||
// Tests each layout independently
|
||||
let next_sibling = NodeRef::new(end_node.into());
|
||||
|
|
|
@ -95,7 +95,7 @@ impl VDiff for VList {
|
|||
// Without a placeholder the next element becomes first
|
||||
// and corrupts the order of rendering
|
||||
// We use empty text element to stake out a place
|
||||
let placeholder = VText::new("".into());
|
||||
let placeholder = VText::new("");
|
||||
self.children.push(placeholder.into());
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ use crate::html::{AnyScope, NodeRef};
|
|||
use crate::utils::document;
|
||||
use cfg_if::cfg_if;
|
||||
use cfg_match::cfg_match;
|
||||
use indexmap::IndexMap;
|
||||
use log::warn;
|
||||
use std::borrow::Cow;
|
||||
use std::cmp::PartialEq;
|
||||
|
@ -76,7 +77,7 @@ pub struct VTag {
|
|||
/// Contains
|
||||
/// [kind](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#Form_%3Cinput%3E_types)
|
||||
/// value of an `InputElement`.
|
||||
pub kind: Option<String>,
|
||||
pub kind: Option<Cow<'static, str>>,
|
||||
/// Represents `checked` attribute of
|
||||
/// [input](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-checked).
|
||||
/// It exists to override standard behavior of `checked` attribute, because
|
||||
|
@ -112,7 +113,7 @@ impl Clone for VTag {
|
|||
|
||||
impl VTag {
|
||||
/// Creates a new `VTag` instance with `tag` name (cannot be changed later in DOM).
|
||||
pub fn new<S: Into<Cow<'static, str>>>(tag: S) -> Self {
|
||||
pub fn new(tag: impl Into<Cow<'static, str>>) -> Self {
|
||||
let tag: Cow<'static, str> = tag.into();
|
||||
let element_type = ElementType::from_tag(&tag);
|
||||
VTag {
|
||||
|
@ -157,8 +158,8 @@ impl VTag {
|
|||
/// Sets `kind` property of an
|
||||
/// [InputElement](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input).
|
||||
/// Same as set `type` attribute.
|
||||
pub fn set_kind<T: ToString>(&mut self, value: &T) {
|
||||
self.kind = Some(value.to_string());
|
||||
pub fn set_kind(&mut self, value: impl Into<Cow<'static, str>>) {
|
||||
self.kind = Some(value.into());
|
||||
}
|
||||
|
||||
/// Sets `checked` property of an
|
||||
|
@ -168,34 +169,41 @@ impl VTag {
|
|||
self.checked = value;
|
||||
}
|
||||
|
||||
/// Adds attribute to a virtual node. Not every attribute works when
|
||||
/// it set as attribute. We use workarounds for:
|
||||
/// Pushes a key-value pair to the Vec variant of attributes.
|
||||
///
|
||||
/// It is the responsibility of the caller to ensure the attribute list does not contain
|
||||
/// duplicate keys.
|
||||
///
|
||||
/// Not every attribute works when it set as an attribute. We use workarounds for:
|
||||
/// `type/kind`, `value` and `checked`.
|
||||
///
|
||||
/// If this virtual node has this attribute present, the value is replaced.
|
||||
pub fn add_attribute<T: ToString>(&mut self, name: &str, value: &T) {
|
||||
self.attributes.insert(name.to_owned(), value.to_string());
|
||||
}
|
||||
|
||||
/// Sets a boolean attribute if `value` is true. Removes if `value` is false. The name
|
||||
/// of the attribute will be used as the value.
|
||||
///
|
||||
/// Example: `<button disabled="disabled">`
|
||||
pub fn set_boolean_attribute(&mut self, name: &str, value: bool) {
|
||||
if value {
|
||||
self.attributes.insert(name.to_owned(), name.to_owned());
|
||||
} else {
|
||||
self.attributes.remove(name);
|
||||
pub fn push_attribute(&mut self, key: &'static str, value: impl Into<Cow<'static, str>>) {
|
||||
if let Attributes::Vec(v) = &mut self.attributes {
|
||||
v.push((key, value.into()));
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds attributes to a virtual node. Not every attribute works when
|
||||
/// it set as attribute. We use workarounds for:
|
||||
/// Adds a key-value pair to attributes
|
||||
///
|
||||
/// Not every attribute works when it set as an attribute. We use workarounds for:
|
||||
/// `type/kind`, `value` and `checked`.
|
||||
pub fn add_attributes(&mut self, attrs: Vec<(String, String)>) {
|
||||
for (name, value) in attrs {
|
||||
self.attributes.insert(name, value);
|
||||
}
|
||||
pub fn add_attribute(&mut self, key: &'static str, value: impl Into<Cow<'static, str>>) {
|
||||
self.attributes_mut().insert(key, value.into());
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the IndexMap variant of attributes.
|
||||
///
|
||||
/// Not every attribute works when it set as an attribute. We use workarounds for:
|
||||
/// `type/kind`, `value` and `checked`.
|
||||
pub fn attributes_mut(&mut self) -> &mut IndexMap<&'static str, Cow<'static, str>> {
|
||||
self.attributes.as_mut()
|
||||
}
|
||||
|
||||
/// Sets attributes to a virtual node.
|
||||
///
|
||||
/// Not every attribute works when it set as an attribute. We use workarounds for:
|
||||
/// `type/kind`, `value` and `checked`.
|
||||
pub fn set_attributes(&mut self, attrs: impl Into<Attributes>) {
|
||||
self.attributes = attrs.into();
|
||||
}
|
||||
|
||||
/// Adds new listener to the node.
|
||||
|
@ -209,9 +217,7 @@ impl VTag {
|
|||
/// They are boxed because we want to keep them in a single list.
|
||||
/// Later `Listener::attach` will attach an actual listener to a DOM node.
|
||||
pub fn add_listeners(&mut self, listeners: Vec<Rc<dyn Listener>>) {
|
||||
for listener in listeners {
|
||||
self.listeners.push(listener);
|
||||
}
|
||||
self.listeners.extend(listeners);
|
||||
}
|
||||
|
||||
/// Every render it removes all listeners and attach it back later
|
||||
|
@ -261,36 +267,132 @@ impl VTag {
|
|||
}
|
||||
}
|
||||
|
||||
/// This handles patching of attributes when the keys are equal but
|
||||
/// the values are different.
|
||||
fn diff_attributes<'a>(
|
||||
&'a self,
|
||||
ancestor: &'a Option<Box<Self>>,
|
||||
) -> impl Iterator<Item = Patch<&'a str, &'a str>> + 'a {
|
||||
// Only change what is necessary.
|
||||
let to_add_or_replace =
|
||||
self.attributes.iter().filter_map(move |(key, value)| {
|
||||
match ancestor
|
||||
.as_ref()
|
||||
.and_then(|ancestor| ancestor.attributes.get(&**key))
|
||||
{
|
||||
None => Some(Patch::Add(&**key, &**value)),
|
||||
Some(ancestor_value) if value != ancestor_value => {
|
||||
Some(Patch::Replace(&**key, &**value))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
});
|
||||
let to_remove = ancestor
|
||||
.iter()
|
||||
.flat_map(|ancestor| ancestor.attributes.keys())
|
||||
.filter(move |key| !self.attributes.contains_key(&**key))
|
||||
.map(|key| Patch::Remove(&**key));
|
||||
/// Diffs attributes between the old and new lists.
|
||||
///
|
||||
/// This method optimises for the lists being the same length, having the
|
||||
/// same keys in the same order - most common case.
|
||||
fn diff_attributes_vectors<'a>(
|
||||
new: &'a [(&'static str, Cow<'static, str>)],
|
||||
old: &'a [(&'static str, Cow<'static, str>)],
|
||||
) -> Vec<Patch<&'static str, &'a str>> {
|
||||
let mut out = Vec::new();
|
||||
|
||||
to_add_or_replace.chain(to_remove)
|
||||
if new.len() != old.len() {
|
||||
diff_incompatible(&mut out, new, old);
|
||||
return out;
|
||||
}
|
||||
|
||||
for (i, (n, o)) in new.iter().zip(old).enumerate() {
|
||||
if n.0 != o.0 {
|
||||
diff_incompatible(&mut out, &new[i..], &old[i..]);
|
||||
break;
|
||||
}
|
||||
if n.1 != o.1 {
|
||||
out.push(Patch::Replace(n.0, &n.1));
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
|
||||
/// Diffs attribute lists that do not contain the same set of keys in
|
||||
/// the same order
|
||||
fn diff_incompatible<'a>(
|
||||
dst: &mut Vec<Patch<&'static str, &'a str>>,
|
||||
new: &'a [(&'static str, Cow<'static, str>)],
|
||||
old: &'a [(&'static str, Cow<'static, str>)],
|
||||
) {
|
||||
use std::collections::HashMap;
|
||||
|
||||
macro_rules! collect {
|
||||
($src:expr) => {
|
||||
$src.iter()
|
||||
.map(|(k, v)| (*k, v))
|
||||
.collect::<HashMap<&'static str, &Cow<'static, str>>>()
|
||||
};
|
||||
}
|
||||
|
||||
let new = collect!(new);
|
||||
let old = collect!(old);
|
||||
|
||||
for (k, n_v) in new.iter() {
|
||||
match old.get(k) {
|
||||
Some(o_v) => {
|
||||
if n_v != o_v {
|
||||
dst.push(Patch::Replace(k, n_v));
|
||||
}
|
||||
}
|
||||
None => dst.push(Patch::Add(k, n_v)),
|
||||
};
|
||||
}
|
||||
|
||||
for k in old.keys() {
|
||||
if !new.contains_key(k) {
|
||||
dst.push(Patch::Remove(k));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Similar to `diff_attributes` except there is only a single `kind`.
|
||||
/// Diffs attributes between the old and new IndexMaps.
|
||||
///
|
||||
/// This method must be used, if either the new or old node were created without using the html!
|
||||
/// macro, that provides compile-time attribute deduplication.
|
||||
fn diff_attributes_indexmaps<'a>(
|
||||
new: &'a IndexMap<&'static str, Cow<'static, str>>,
|
||||
old: &'a IndexMap<&'static str, Cow<'static, str>>,
|
||||
) -> Vec<Patch<&'static str, &'a str>> {
|
||||
use indexmap::map::Iter;
|
||||
use std::iter::Peekable;
|
||||
|
||||
let mut out = Vec::new();
|
||||
let mut new_iter = new.iter().peekable();
|
||||
let mut old_iter = old.iter().peekable();
|
||||
loop {
|
||||
if new_iter.peek().is_none() || old_iter.peek().is_none() {
|
||||
break;
|
||||
}
|
||||
match (new_iter.next(), old_iter.next()) {
|
||||
(Some(n), Some(o)) => {
|
||||
if n.0 != o.0 {
|
||||
break;
|
||||
}
|
||||
if n.1 != o.1 {
|
||||
out.push(Patch::Replace(*n.0, n.1.as_ref()));
|
||||
}
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
diff_incompatible(&mut out, new_iter, old_iter, &new, &old);
|
||||
return out;
|
||||
|
||||
fn diff_incompatible<'a>(
|
||||
dst: &mut Vec<Patch<&'static str, &'a str>>,
|
||||
new_keys: Peekable<Iter<'a, &'static str, Cow<'static, str>>>,
|
||||
old_keys: Peekable<Iter<'a, &'static str, Cow<'static, str>>>,
|
||||
new_map: &'a IndexMap<&'static str, Cow<'static, str>>,
|
||||
old_map: &'a IndexMap<&'static str, Cow<'static, str>>,
|
||||
) {
|
||||
for (k, n_v) in new_keys {
|
||||
match old_map.get(k) {
|
||||
Some(o_v) => {
|
||||
if n_v != o_v {
|
||||
dst.push(Patch::Replace(k, n_v));
|
||||
}
|
||||
}
|
||||
None => dst.push(Patch::Add(k, n_v)),
|
||||
};
|
||||
}
|
||||
|
||||
for (k, _) in old_keys {
|
||||
if !new_map.contains_key(k) {
|
||||
dst.push(Patch::Remove(k));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Compares new kind with ancestor and produces a patch to apply, if any
|
||||
fn diff_kind<'a>(&'a self, ancestor: &'a Option<Box<Self>>) -> Option<Patch<&'a str, ()>> {
|
||||
match (
|
||||
self.kind.as_ref(),
|
||||
|
@ -309,7 +411,7 @@ impl VTag {
|
|||
}
|
||||
}
|
||||
|
||||
/// Almost identical in spirit to `diff_kind`
|
||||
/// Compares new value with ancestor and produces a patch to apply, if any
|
||||
fn diff_value<'a>(&'a self, ancestor: &'a Option<Box<Self>>) -> Option<Patch<&'a str, ()>> {
|
||||
match (
|
||||
self.value.as_ref(),
|
||||
|
@ -328,14 +430,45 @@ impl VTag {
|
|||
}
|
||||
}
|
||||
|
||||
fn apply_diffs(&mut self, ancestor: &Option<Box<Self>>) {
|
||||
fn apply_diffs(&mut self, ancestor: &mut Option<Box<Self>>) {
|
||||
let element = self.reference.as_ref().expect("element expected");
|
||||
|
||||
// Update parameters
|
||||
let changes = self.diff_attributes(ancestor);
|
||||
|
||||
// apply attribute patches including an optional "class"-attribute patch
|
||||
for change in changes {
|
||||
// Apply attribute patches including an optional "class"-attribute patch.
|
||||
macro_rules! add_all {
|
||||
($src:expr) => {
|
||||
$src.iter()
|
||||
.map(|(k, v)| Patch::Add(*k, v.as_ref()))
|
||||
.collect()
|
||||
};
|
||||
}
|
||||
for change in match (
|
||||
&mut self.attributes,
|
||||
&mut ancestor.as_mut().map(|a| &mut a.attributes),
|
||||
) {
|
||||
(Attributes::Vec(new), Some(Attributes::Vec(old))) => {
|
||||
Self::diff_attributes_vectors(new, old)
|
||||
}
|
||||
(Attributes::IndexMap(new), Some(Attributes::IndexMap(old))) => {
|
||||
Self::diff_attributes_indexmaps(new, old)
|
||||
}
|
||||
(Attributes::Vec(new), None) => add_all!(new),
|
||||
(Attributes::IndexMap(new), None) => add_all!(new),
|
||||
(Attributes::Vec(new), Some(Attributes::IndexMap(old))) => {
|
||||
self.attributes = Attributes::new_indexmap(std::mem::take(new));
|
||||
match &self.attributes {
|
||||
Attributes::IndexMap(new) => Self::diff_attributes_indexmaps(new, old),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
(Attributes::IndexMap(new), Some(Attributes::Vec(old))) => {
|
||||
ancestor.as_mut().unwrap().attributes =
|
||||
Attributes::new_indexmap(std::mem::take(old));
|
||||
match &ancestor.as_ref().map(|a| &a.attributes) {
|
||||
Some(Attributes::IndexMap(old)) => Self::diff_attributes_indexmaps(new, old),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
} {
|
||||
match change {
|
||||
Patch::Add(key, value) | Patch::Replace(key, value) => {
|
||||
element
|
||||
|
@ -345,7 +478,8 @@ impl VTag {
|
|||
Patch::Remove(key) => {
|
||||
cfg_match! {
|
||||
feature = "std_web" => element.remove_attribute(&key),
|
||||
feature = "web_sys" => element.remove_attribute(&key).expect("could not remove attribute"),
|
||||
feature = "web_sys" => element.remove_attribute(&key)
|
||||
.expect("could not remove attribute"),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -431,7 +565,8 @@ impl VTag {
|
|||
}
|
||||
|
||||
fn create_element(&self, parent: &Element) -> Element {
|
||||
if self.tag == "svg"
|
||||
let tag = self.tag();
|
||||
if tag == "svg"
|
||||
|| parent
|
||||
.namespace_uri()
|
||||
.map_or(false, |ns| ns == SVG_NAMESPACE)
|
||||
|
@ -441,11 +576,11 @@ impl VTag {
|
|||
feature = "web_sys" => Some(SVG_NAMESPACE),
|
||||
};
|
||||
document()
|
||||
.create_element_ns(namespace, &self.tag)
|
||||
.create_element_ns(namespace, tag)
|
||||
.expect("can't create namespaced element for vtag")
|
||||
} else {
|
||||
document()
|
||||
.create_element(&self.tag)
|
||||
.create_element(tag)
|
||||
.expect("can't create element for vtag")
|
||||
}
|
||||
}
|
||||
|
@ -480,7 +615,7 @@ impl VDiff for VTag {
|
|||
match ancestor {
|
||||
// If the ancestor is a tag of the same type, don't recreate, keep the
|
||||
// old tag and update its attributes and children.
|
||||
VNode::VTag(vtag) if self.tag == vtag.tag && self.key == vtag.key => Some(vtag),
|
||||
VNode::VTag(vtag) if self.tag() == vtag.tag() && self.key == vtag.key => Some(vtag),
|
||||
_ => {
|
||||
let element = self.create_element(parent);
|
||||
super::insert_node(&element, parent, Some(ancestor.first_node()));
|
||||
|
@ -503,7 +638,7 @@ impl VDiff for VTag {
|
|||
self.reference = Some(element);
|
||||
}
|
||||
|
||||
self.apply_diffs(&ancestor_tag);
|
||||
self.apply_diffs(&mut ancestor_tag);
|
||||
self.recreate_listeners(&mut ancestor_tag);
|
||||
|
||||
// Process children
|
||||
|
@ -713,8 +848,9 @@ mod tests {
|
|||
/// Returns the class attribute as str reference, or "" if the attribute is not set.
|
||||
fn get_class_str(vtag: &VTag) -> &str {
|
||||
vtag.attributes
|
||||
.get("class")
|
||||
.map(AsRef::as_ref)
|
||||
.iter()
|
||||
.find(|(k, _)| k == &"class")
|
||||
.map(|(_, v)| AsRef::as_ref(v))
|
||||
.unwrap_or("")
|
||||
}
|
||||
|
||||
|
@ -745,7 +881,7 @@ mod tests {
|
|||
#[test]
|
||||
fn supports_multiple_classes_string() {
|
||||
let a = html! {
|
||||
<div class="class-1 class-2 class-3"></div>
|
||||
<div class="class-1 class-2 class-3"></div>
|
||||
};
|
||||
|
||||
let b = html! {
|
||||
|
@ -836,26 +972,32 @@ mod tests {
|
|||
let d_arr = [""];
|
||||
let d = html! { <div class=&d_arr[..]></div> };
|
||||
|
||||
macro_rules! has_class {
|
||||
($vtag:expr) => {
|
||||
$vtag.attributes.iter().any(|(k, _)| k == "class")
|
||||
};
|
||||
}
|
||||
|
||||
if let VNode::VTag(vtag) = a {
|
||||
assert!(!vtag.attributes.contains_key("class"));
|
||||
assert!(!has_class!(vtag));
|
||||
} else {
|
||||
panic!("vtag expected");
|
||||
}
|
||||
|
||||
if let VNode::VTag(vtag) = b {
|
||||
assert!(!vtag.attributes.contains_key("class"));
|
||||
assert!(!has_class!(vtag));
|
||||
} else {
|
||||
panic!("vtag expected");
|
||||
}
|
||||
|
||||
if let VNode::VTag(vtag) = c {
|
||||
assert!(!vtag.attributes.contains_key("class"));
|
||||
assert!(!has_class!(vtag));
|
||||
} else {
|
||||
panic!("vtag expected");
|
||||
}
|
||||
|
||||
if let VNode::VTag(vtag) = d {
|
||||
assert!(!vtag.attributes.contains_key("class"));
|
||||
assert!(!vtag.attributes.iter().any(|(k, _)| k == "class"));
|
||||
} else {
|
||||
panic!("vtag expected");
|
||||
}
|
||||
|
@ -911,7 +1053,7 @@ mod tests {
|
|||
#[test]
|
||||
fn keeps_order_of_classes() {
|
||||
let a = html! {
|
||||
<div class="class-1 class-2 class-3",></div>
|
||||
<div class=vec!["class-1", "class-2", "class-3"],></div>
|
||||
};
|
||||
|
||||
if let VNode::VTag(vtag) = a {
|
||||
|
@ -997,10 +1139,12 @@ mod tests {
|
|||
</p>
|
||||
};
|
||||
if let VNode::VTag(vtag) = a {
|
||||
assert!(vtag.attributes.contains_key("aria-controls"));
|
||||
assert_eq!(
|
||||
vtag.attributes.get("aria-controls"),
|
||||
Some(&"it-works".into())
|
||||
vtag.attributes
|
||||
.iter()
|
||||
.find(|(k, _)| k == &"aria-controls")
|
||||
.map(|(_, v)| v),
|
||||
Some("it-works")
|
||||
);
|
||||
} else {
|
||||
panic!("vtag expected");
|
||||
|
@ -1364,7 +1508,7 @@ mod tests {
|
|||
elem.apply(&scope, &parent, NodeRef::default(), None);
|
||||
let vtag = assert_vtag(&mut elem);
|
||||
// make sure the new tag name is used internally
|
||||
assert_eq!(vtag.tag, "a");
|
||||
assert_eq!(vtag.tag(), "a");
|
||||
|
||||
#[cfg(feature = "web_sys")]
|
||||
// Element.tagName is always in the canonical upper-case form.
|
||||
|
@ -1378,17 +1522,19 @@ mod tests {
|
|||
};
|
||||
let div_vtag = assert_vtag(&mut div_el);
|
||||
assert!(div_vtag.value.is_none());
|
||||
assert_eq!(
|
||||
div_vtag.attributes.get("value").map(String::as_str),
|
||||
Some("Hello")
|
||||
);
|
||||
let v: Option<&str> = div_vtag
|
||||
.attributes
|
||||
.iter()
|
||||
.find(|(k, _)| k == &"value")
|
||||
.map(|(_, v)| AsRef::as_ref(v));
|
||||
assert_eq!(v, Some("Hello"));
|
||||
|
||||
let mut input_el = html! {
|
||||
<@{"input"} value="World"/>
|
||||
};
|
||||
let input_vtag = assert_vtag(&mut input_el);
|
||||
assert_eq!(input_vtag.value, Some("World".to_string()));
|
||||
assert!(!input_vtag.attributes.contains_key("value"));
|
||||
assert!(!input_vtag.attributes.iter().any(|(k, _)| k == "value"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -1519,3 +1665,180 @@ mod layout_tests {
|
|||
diff_layouts(vec![layout1, layout2, layout3, layout4]);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "web_sys", feature = "wasm_test"))]
|
||||
mod benchmarks {
|
||||
use super::{Patch, VTag};
|
||||
use easybench_wasm::bench_env_limit;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
|
||||
|
||||
wasm_bindgen_test_configure!(run_in_browser);
|
||||
|
||||
// In seconds
|
||||
const BENCHMARK_DURATION: f64 = 1.0;
|
||||
|
||||
fn diff_attributes_hashmap<'a>(
|
||||
new: &'a HashMap<&'static str, Cow<'static, str>>,
|
||||
old: &'a HashMap<&'static str, Cow<'static, str>>,
|
||||
) -> Vec<Patch<&'static str, &'a str>> {
|
||||
// Only change what is necessary.
|
||||
let to_add_or_replace = new
|
||||
.iter()
|
||||
.filter_map(move |(key, value)| match old.get(key) {
|
||||
None => Some(Patch::Add(&**key, &**value)),
|
||||
Some(ancestor_value) if value != ancestor_value => {
|
||||
Some(Patch::Replace(&**key, &**value))
|
||||
}
|
||||
_ => None,
|
||||
});
|
||||
let to_remove = old
|
||||
.keys()
|
||||
.filter(move |key| !new.contains_key(&**key))
|
||||
.map(|key| Patch::Remove(&**key));
|
||||
|
||||
to_add_or_replace.chain(to_remove).collect()
|
||||
}
|
||||
|
||||
macro_rules! bench_attrs {
|
||||
($name:ident, $args:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let env = $args;
|
||||
|
||||
macro_rules! build_map {
|
||||
($type:ident, $src:expr) => {
|
||||
&$src
|
||||
.into_iter()
|
||||
.collect::<$type<&'static str, Cow<'static, str>>>();
|
||||
};
|
||||
}
|
||||
|
||||
wasm_bindgen_test::console_log!(
|
||||
"{}: hashmaps: {}",
|
||||
stringify!($name),
|
||||
bench_env_limit(BENCHMARK_DURATION, env.clone(), |(a, b)| {
|
||||
format!(
|
||||
"{:?}",
|
||||
diff_attributes_hashmap(build_map!(HashMap, a), build_map!(HashMap, b))
|
||||
)
|
||||
})
|
||||
);
|
||||
wasm_bindgen_test::console_log!(
|
||||
"{}: indexmaps: {}",
|
||||
stringify!($name),
|
||||
bench_env_limit(BENCHMARK_DURATION, env.clone(), |(a, b)| {
|
||||
use indexmap::IndexMap;
|
||||
|
||||
format!(
|
||||
"{:?}",
|
||||
VTag::diff_attributes_indexmaps(
|
||||
build_map!(IndexMap, a),
|
||||
build_map!(IndexMap, b)
|
||||
)
|
||||
)
|
||||
})
|
||||
);
|
||||
wasm_bindgen_test::console_log!(
|
||||
"{}: vectors: {}",
|
||||
stringify!($name),
|
||||
bench_env_limit(BENCHMARK_DURATION, env.clone(), |(a, b)| format!(
|
||||
"{:?}",
|
||||
VTag::diff_attributes_vectors(&a, &b)
|
||||
))
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Fill vector wit more attributes
|
||||
fn extend_attrs(dst: &mut Vec<(&'static str, Cow<'static, str>)>) {
|
||||
dst.extend(vec![
|
||||
("oh", Cow::Borrowed("danny")),
|
||||
("boy", Cow::Borrowed("the")),
|
||||
("pipes", Cow::Borrowed("the")),
|
||||
("are", Cow::Borrowed("calling")),
|
||||
("from", Cow::Borrowed("glen")),
|
||||
("to", Cow::Borrowed("glen")),
|
||||
("and", Cow::Borrowed("down")),
|
||||
("the", Cow::Borrowed("mountain")),
|
||||
("side", Cow::Borrowed("")),
|
||||
]);
|
||||
}
|
||||
|
||||
bench_attrs! {
|
||||
bench_diff_attributes_same,
|
||||
{
|
||||
let mut old: Vec<(&'static str, Cow<'static, str>)> = vec![
|
||||
("disable", Cow::Borrowed("disable")),
|
||||
("style", Cow::Borrowed("display: none;")),
|
||||
("class", Cow::Borrowed("lass")),
|
||||
];
|
||||
extend_attrs(&mut old);
|
||||
(old.clone(), old)
|
||||
}
|
||||
}
|
||||
|
||||
bench_attrs! {
|
||||
bench_diff_attributes_append,
|
||||
{
|
||||
let mut old = vec![
|
||||
("disable", Cow::Borrowed("disable")),
|
||||
("style", Cow::Borrowed("display: none;")),
|
||||
("class", Cow::Borrowed("lass")),
|
||||
];
|
||||
extend_attrs(&mut old);
|
||||
let mut new = old.clone();
|
||||
new.push(("hidden", Cow::Borrowed("hidden")));
|
||||
(new, old)
|
||||
}
|
||||
}
|
||||
|
||||
bench_attrs! {
|
||||
bench_diff_attributes_change_first,
|
||||
{
|
||||
let mut old = vec![
|
||||
("disable", Cow::Borrowed("disable")),
|
||||
("style", Cow::Borrowed("display: none;")),
|
||||
("class", Cow::Borrowed("lass")),
|
||||
];
|
||||
extend_attrs(&mut old);
|
||||
let mut new = old.clone();
|
||||
new[0] = ("disable", Cow::Borrowed("enable"));
|
||||
(new, old)
|
||||
}
|
||||
}
|
||||
|
||||
bench_attrs! {
|
||||
bench_diff_attributes_change_middle,
|
||||
{
|
||||
let mut old = vec![
|
||||
("disable", Cow::Borrowed("disable")),
|
||||
("style", Cow::Borrowed("display: none;")),
|
||||
("class", Cow::Borrowed("lass")),
|
||||
];
|
||||
extend_attrs(&mut old);
|
||||
let mut new = old.clone();
|
||||
let mid = &mut new.get_mut(old.len()/2).unwrap();
|
||||
mid.1 = Cow::Borrowed("changed");
|
||||
(new, old)
|
||||
}
|
||||
}
|
||||
|
||||
bench_attrs! {
|
||||
bench_diff_attributes_change_last,
|
||||
{
|
||||
let mut old = vec![
|
||||
("disable", Cow::Borrowed("disable")),
|
||||
("style", Cow::Borrowed("display: none;")),
|
||||
("class", Cow::Borrowed("lass")),
|
||||
];
|
||||
extend_attrs(&mut old);
|
||||
let mut new = old.clone();
|
||||
let last = &mut new.get_mut(old.len()-1).unwrap();
|
||||
last.1 = Cow::Borrowed("changed");
|
||||
(new, old)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ use crate::html::{AnyScope, NodeRef};
|
|||
use crate::utils::document;
|
||||
use cfg_if::cfg_if;
|
||||
use log::warn;
|
||||
use std::borrow::Cow;
|
||||
use std::cmp::PartialEq;
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "std_web")] {
|
||||
|
@ -20,16 +21,16 @@ cfg_if! {
|
|||
#[derive(Clone, Debug)]
|
||||
pub struct VText {
|
||||
/// Contains a text of the node.
|
||||
pub text: String,
|
||||
pub text: Cow<'static, str>,
|
||||
/// A reference to the `TextNode`.
|
||||
pub reference: Option<TextNode>,
|
||||
}
|
||||
|
||||
impl VText {
|
||||
/// Creates new virtual text node with a content.
|
||||
pub fn new(text: String) -> Self {
|
||||
pub fn new(text: impl Into<Cow<'static, str>>) -> Self {
|
||||
VText {
|
||||
text,
|
||||
text: text.into(),
|
||||
reference: None,
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue