Make Html (VNode) cheap to clone (#3431)

* Make VNode cheap to clone

* Faster clone for list and portal

* Fixes hopefully good

* clippy

* more fixes hopefully good

* rustfmt

* More fixes

* more fixes...

* more fixes

* Update element-fail.stderr

* Macro fixes...

* CLEANUP

* Benchmark with divan

* WIP workflow

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* Use the 2 workflows approach, will fix after merge if not working

* CLEANUP

* can i push that change here pretty please

* Trigger CI
This commit is contained in:
Cecile Tonglet 2023-10-27 18:43:03 +02:00 committed by GitHub
parent aa63234419
commit 7f45af3a66
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 211 additions and 124 deletions

View File

@ -1,5 +1,7 @@
/// Original author of this code is [Nathan Ringo](https://github.com/remexre)
/// Source: https://github.com/acmumn/mentoring/blob/master/web-client/src/view/markdown.rs
use std::rc::Rc;
use pulldown_cmark::{Alignment, CodeBlockKind, Event, Options, Parser, Tag};
use yew::virtual_dom::{VNode, VTag, VText};
use yew::{html, Classes, Html};
@ -56,16 +58,22 @@ pub fn render_markdown(src: &str) -> Html {
if let Some(top_children) = top.children_mut() {
for r in top_children.to_vlist_mut().iter_mut() {
if let VNode::VTag(ref mut vtag) = r {
if let Some(vtag_children) = vtag.children_mut() {
if let Some(vtag_children) = Rc::make_mut(vtag).children_mut() {
for (i, c) in
vtag_children.to_vlist_mut().iter_mut().enumerate()
{
if let VNode::VTag(ref mut vtag) = c {
match aligns[i] {
Alignment::None => {}
Alignment::Left => add_class(vtag, "text-left"),
Alignment::Center => add_class(vtag, "text-center"),
Alignment::Right => add_class(vtag, "text-right"),
Alignment::Left => {
add_class(Rc::make_mut(vtag), "text-left")
}
Alignment::Center => {
add_class(Rc::make_mut(vtag), "text-center")
}
Alignment::Right => {
add_class(Rc::make_mut(vtag), "text-right")
}
}
}
}
@ -79,7 +87,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");
Rc::make_mut(vtag).add_attribute("scope", "col");
}
}
}
@ -99,7 +107,7 @@ pub fn render_markdown(src: &str) -> Html {
}
if elems.len() == 1 {
VNode::VTag(Box::new(elems.pop().unwrap()))
VNode::VTag(Rc::new(elems.pop().unwrap()))
} else {
html! {
<div>{ for elems.into_iter() }</div>

View File

@ -359,7 +359,7 @@ impl ToTokens for HtmlElement {
quote! {
::std::convert::Into::<::yew::virtual_dom::VNode>::into(
::yew::virtual_dom::VTag::__new_other(
::std::borrow::Cow::<'static, ::std::primitive::str>::Borrowed(#name),
::yew::virtual_dom::AttrValue::Static(#name),
#node_ref,
#key,
#attributes,
@ -416,7 +416,7 @@ impl ToTokens for HtmlElement {
// (note the extra braces). Hence the need for the `allow`.
// Anyways to remove the braces?
let mut #vtag_name = ::std::convert::Into::<
::std::borrow::Cow::<'static, ::std::primitive::str>
::yew::virtual_dom::AttrValue
>::into(#expr);
::std::debug_assert!(
#vtag_name.is_ascii(),

View File

@ -78,9 +78,9 @@ impl ToTokens for HtmlList {
};
tokens.extend(quote_spanned! {spanned.span()=>
::yew::virtual_dom::VNode::VList(
::yew::virtual_dom::VNode::VList(::std::rc::Rc::new(
::yew::virtual_dom::VList::with_children(#children, #key)
)
))
});
}
}

View File

@ -123,7 +123,7 @@ impl ToTokens for HtmlTree {
lint::lint_all(self);
match self {
HtmlTree::Empty => tokens.extend(quote! {
::yew::virtual_dom::VNode::VList(::yew::virtual_dom::VList::new())
<::yew::virtual_dom::VNode as ::std::default::Default>::default()
}),
HtmlTree::Component(comp) => comp.to_tokens(tokens),
HtmlTree::Element(tag) => tag.to_tokens(tokens),
@ -375,9 +375,9 @@ impl ToTokens for HtmlRootBraced {
tokens.extend(quote_spanned! {brace.span.span()=>
{
::yew::virtual_dom::VNode::VList(
::yew::virtual_dom::VNode::VList(::std::rc::Rc::new(
::yew::virtual_dom::VList::with_children(#children, ::std::option::Option::None)
)
))
}
});
}

View File

@ -83,12 +83,12 @@ impl ::std::convert::From<::yew::virtual_dom::VChild<AltChild>> for ChildrenVari
impl ::std::convert::Into<::yew::virtual_dom::VNode> for ChildrenVariants {
fn into(self) -> ::yew::virtual_dom::VNode {
match self {
Self::Child(comp) => ::yew::virtual_dom::VNode::VComp(::std::convert::Into::<
Self::Child(comp) => ::yew::virtual_dom::VNode::VComp(::std::rc::Rc::new(::std::convert::Into::<
::yew::virtual_dom::VComp,
>::into(comp)),
Self::AltChild(comp) => ::yew::virtual_dom::VNode::VComp(::std::convert::Into::<
>::into(comp))),
Self::AltChild(comp) => ::yew::virtual_dom::VNode::VComp(::std::rc::Rc::new(::std::convert::Into::<
::yew::virtual_dom::VComp,
>::into(comp)),
>::into(comp))),
}
}
}

View File

@ -82,12 +82,12 @@ impl ::std::convert::From<::yew::virtual_dom::VChild<AltChild>> for ChildrenVari
impl ::std::convert::Into<::yew::virtual_dom::VNode> for ChildrenVariants {
fn into(self) -> ::yew::virtual_dom::VNode {
match self {
Self::Child(comp) => ::yew::virtual_dom::VNode::VComp(::std::convert::Into::<
Self::Child(comp) => ::yew::virtual_dom::VNode::VComp(::std::rc::Rc::new(::std::convert::Into::<
::yew::virtual_dom::VComp,
>::into(comp)),
Self::AltChild(comp) => ::yew::virtual_dom::VNode::VComp(::std::convert::Into::<
>::into(comp))),
Self::AltChild(comp) => ::yew::virtual_dom::VNode::VComp(::std::rc::Rc::new(::std::convert::Into::<
::yew::virtual_dom::VComp,
>::into(comp)),
>::into(comp))),
}
}
}

View File

@ -703,20 +703,15 @@ error[E0277]: the trait bound `(): IntoPropValue<yew::NodeRef>` is not satisfied
and $N others
= note: this error originates in the macro `html` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0277]: the trait bound `Cow<'static, str>: From<{integer}>` is not satisfied
error[E0277]: the trait bound `implicit_clone::unsync::IString: From<{integer}>` is not satisfied
--> tests/html_macro/element-fail.rs:77:15
|
77 | html! { <@{55}></@> };
| ^^^^ the trait `From<{integer}>` is not implemented for `Cow<'static, str>`
| ^^^^ the trait `From<{integer}>` is not implemented for `implicit_clone::unsync::IString`
|
= help: the following other types implement trait `From<T>`:
<Cow<'a, CStr> as From<&'a CStr>>
<Cow<'a, CStr> as From<&'a CString>>
<Cow<'a, CStr> as From<CString>>
<Cow<'a, OsStr> as From<&'a OsStr>>
<Cow<'a, OsStr> as From<&'a OsString>>
<Cow<'a, OsStr> as From<OsString>>
<Cow<'a, Path> as From<&'a Path>>
<Cow<'a, Path> as From<&'a PathBuf>>
and $N others
= note: required because of the requirements on the impl of `Into<Cow<'static, str>>` for `{integer}`
<implicit_clone::unsync::IString as From<&'static str>>
<implicit_clone::unsync::IString as From<Cow<'static, str>>>
<implicit_clone::unsync::IString as From<Rc<str>>>
<implicit_clone::unsync::IString as From<String>>
= note: required because of the requirements on the impl of `Into<implicit_clone::unsync::IString>` for `{integer}`

View File

@ -4,13 +4,13 @@ use std::cmp::Ordering;
use std::collections::HashSet;
use std::hash::Hash;
use std::ops::Deref;
use std::rc::Rc;
use web_sys::Element;
use super::{test_log, BNode, BSubtree, DomSlot};
use crate::dom_bundle::{Reconcilable, ReconcileTarget};
use crate::html::AnyScope;
use crate::utils::RcExt;
use crate::virtual_dom::{Key, VList, VNode, VText};
/// This struct represents a mounted [VList]
@ -30,10 +30,8 @@ impl VList {
let children = self
.children
.map(Rc::try_unwrap)
.unwrap_or_else(|| Ok(Vec::new()))
// Rc::unwrap_or_clone is not stable yet.
.unwrap_or_else(|m| m.to_vec());
.map(RcExt::unwrap_or_clone)
.unwrap_or_default();
(self.key, fully_keyed, children)
}

View File

@ -7,6 +7,7 @@ use web_sys::{Element, Node};
use super::{BComp, BList, BPortal, BRaw, BSubtree, BSuspense, BTag, BText, DomSlot};
use crate::dom_bundle::{Reconcilable, ReconcileTarget};
use crate::html::AnyScope;
use crate::utils::RcExt;
use crate::virtual_dom::{Key, VNode};
/// The bundle implementation to [VNode].
@ -95,7 +96,8 @@ impl Reconcilable for VNode {
) -> (DomSlot, Self::Bundle) {
match self {
VNode::VTag(vtag) => {
let (node_ref, tag) = vtag.attach(root, parent_scope, parent, slot);
let (node_ref, tag) =
RcExt::unwrap_or_clone(vtag).attach(root, parent_scope, parent, slot);
(node_ref, tag.into())
}
VNode::VText(vtext) => {
@ -103,11 +105,13 @@ impl Reconcilable for VNode {
(node_ref, text.into())
}
VNode::VComp(vcomp) => {
let (node_ref, comp) = vcomp.attach(root, parent_scope, parent, slot);
let (node_ref, comp) =
RcExt::unwrap_or_clone(vcomp).attach(root, parent_scope, parent, slot);
(node_ref, comp.into())
}
VNode::VList(vlist) => {
let (node_ref, list) = vlist.attach(root, parent_scope, parent, slot);
let (node_ref, list) =
RcExt::unwrap_or_clone(vlist).attach(root, parent_scope, parent, slot);
(node_ref, list.into())
}
VNode::VRef(node) => {
@ -115,11 +119,13 @@ impl Reconcilable for VNode {
(DomSlot::at(node.clone()), BNode::Ref(node))
}
VNode::VPortal(vportal) => {
let (node_ref, portal) = vportal.attach(root, parent_scope, parent, slot);
let (node_ref, portal) =
RcExt::unwrap_or_clone(vportal).attach(root, parent_scope, parent, slot);
(node_ref, portal.into())
}
VNode::VSuspense(vsuspsense) => {
let (node_ref, suspsense) = vsuspsense.attach(root, parent_scope, parent, slot);
let (node_ref, suspsense) =
RcExt::unwrap_or_clone(vsuspsense).attach(root, parent_scope, parent, slot);
(node_ref, suspsense.into())
}
VNode::VRaw(vraw) => {
@ -149,20 +155,46 @@ impl Reconcilable for VNode {
bundle: &mut BNode,
) -> DomSlot {
match self {
VNode::VTag(vtag) => vtag.reconcile_node(root, parent_scope, parent, slot, bundle),
VNode::VTag(vtag) => RcExt::unwrap_or_clone(vtag).reconcile_node(
root,
parent_scope,
parent,
slot,
bundle,
),
VNode::VText(vtext) => vtext.reconcile_node(root, parent_scope, parent, slot, bundle),
VNode::VComp(vcomp) => vcomp.reconcile_node(root, parent_scope, parent, slot, bundle),
VNode::VList(vlist) => vlist.reconcile_node(root, parent_scope, parent, slot, bundle),
VNode::VComp(vcomp) => RcExt::unwrap_or_clone(vcomp).reconcile_node(
root,
parent_scope,
parent,
slot,
bundle,
),
VNode::VList(vlist) => RcExt::unwrap_or_clone(vlist).reconcile_node(
root,
parent_scope,
parent,
slot,
bundle,
),
VNode::VRef(node) => match bundle {
BNode::Ref(ref n) if &node == n => DomSlot::at(node),
_ => VNode::VRef(node).replace(root, parent_scope, parent, slot, bundle),
},
VNode::VPortal(vportal) => {
vportal.reconcile_node(root, parent_scope, parent, slot, bundle)
}
VNode::VSuspense(vsuspsense) => {
vsuspsense.reconcile_node(root, parent_scope, parent, slot, bundle)
}
VNode::VPortal(vportal) => RcExt::unwrap_or_clone(vportal).reconcile_node(
root,
parent_scope,
parent,
slot,
bundle,
),
VNode::VSuspense(vsuspsense) => RcExt::unwrap_or_clone(vsuspsense).reconcile_node(
root,
parent_scope,
parent,
slot,
bundle,
),
VNode::VRaw(vraw) => vraw.reconcile_node(root, parent_scope, parent, slot, bundle),
}
}
@ -246,10 +278,16 @@ mod feat_hydration {
fragment: &mut Fragment,
) -> Self::Bundle {
match self {
VNode::VTag(vtag) => vtag.hydrate(root, parent_scope, parent, fragment).into(),
VNode::VTag(vtag) => RcExt::unwrap_or_clone(vtag)
.hydrate(root, parent_scope, parent, fragment)
.into(),
VNode::VText(vtext) => vtext.hydrate(root, parent_scope, parent, fragment).into(),
VNode::VComp(vcomp) => vcomp.hydrate(root, parent_scope, parent, fragment).into(),
VNode::VList(vlist) => vlist.hydrate(root, parent_scope, parent, fragment).into(),
VNode::VComp(vcomp) => RcExt::unwrap_or_clone(vcomp)
.hydrate(root, parent_scope, parent, fragment)
.into(),
VNode::VList(vlist) => RcExt::unwrap_or_clone(vlist)
.hydrate(root, parent_scope, parent, fragment)
.into(),
// You cannot hydrate a VRef.
VNode::VRef(_) => {
panic!(
@ -264,7 +302,7 @@ mod feat_hydration {
use_effect."
)
}
VNode::VSuspense(vsuspense) => vsuspense
VNode::VSuspense(vsuspense) => RcExt::unwrap_or_clone(vsuspense)
.hydrate(root, parent_scope, parent, fragment)
.into(),
VNode::VRaw(vraw) => vraw.hydrate(root, parent_scope, parent, fragment).into(),

View File

@ -123,6 +123,8 @@ impl BPortal {
mod layout_tests {
extern crate self as yew;
use std::rc::Rc;
use gloo::utils::document;
use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
use web_sys::HtmlInputElement;
@ -151,10 +153,10 @@ mod layout_tests {
<div>
{VNode::VRef(first_target.clone().into())}
{VNode::VRef(second_target.clone().into())}
{VNode::VPortal(VPortal::new(
{VNode::VPortal(Rc::new(VPortal::new(
html! { {"PORTAL"} },
first_target.clone(),
))}
)))}
{"AFTER"}
</div>
},
@ -166,10 +168,10 @@ mod layout_tests {
<div>
{VNode::VRef(first_target.clone().into())}
{VNode::VRef(second_target.clone().into())}
{VNode::VPortal(VPortal::new(
{VNode::VPortal(Rc::new(VPortal::new(
html! { {"PORTAL"} },
second_target.clone(),
))}
)))}
{"AFTER"}
</div>
},
@ -181,10 +183,10 @@ mod layout_tests {
<div>
{VNode::VRef(first_target.clone().into())}
{VNode::VRef(second_target.clone().into())}
{VNode::VPortal(VPortal::new(
{VNode::VPortal(Rc::new(VPortal::new(
html! { <> {"PORTAL"} <b /> </> },
second_target.clone(),
))}
)))}
{"AFTER"}
</div>
},
@ -207,11 +209,11 @@ mod layout_tests {
node: html! {
<div>
{VNode::VRef(target_with_child.clone().into())}
{VNode::VPortal(VPortal::new_before(
{VNode::VPortal(Rc::new(VPortal::new_before(
html! { {"PORTAL"} },
target_with_child.clone(),
Some(target_child.clone().into()),
))}
)))}
</div>
},
expected: "<div><i>PORTAL<s></s></i></div>",

View File

@ -3,7 +3,6 @@
mod attributes;
mod listeners;
use std::borrow::Cow;
use std::cell::RefCell;
use std::collections::HashMap;
use std::hint::unreachable_unchecked;
@ -18,7 +17,7 @@ use web_sys::{Element, HtmlTextAreaElement as TextAreaElement};
use super::{BNode, BSubtree, DomSlot, Reconcilable, ReconcileTarget};
use crate::html::AnyScope;
use crate::virtual_dom::vtag::{InputFields, VTagInner, Value, MATHML_NAMESPACE, SVG_NAMESPACE};
use crate::virtual_dom::{Attributes, Key, VTag};
use crate::virtual_dom::{AttrValue, Attributes, Key, VTag};
use crate::NodeRef;
/// Applies contained changes to DOM [web_sys::Element]
@ -51,7 +50,7 @@ enum BTagInner {
/// Fields for all other kinds of [VTag]s
Other {
/// A tag of the element.
tag: Cow<'static, str>,
tag: AttrValue,
/// Child node.
child_bundle: BNode,
},
@ -407,6 +406,8 @@ mod feat_hydration {
#[cfg(target_arch = "wasm32")]
#[cfg(test)]
mod tests {
use std::rc::Rc;
use wasm_bindgen::JsCast;
use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
use web_sys::HtmlInputElement as InputElement;
@ -414,6 +415,7 @@ mod tests {
use super::*;
use crate::dom_bundle::utils::setup_parent;
use crate::dom_bundle::{BNode, Reconcilable, ReconcileTarget};
use crate::utils::RcExt;
use crate::virtual_dom::vtag::{HTML_NAMESPACE, SVG_NAMESPACE};
use crate::virtual_dom::{AttrValue, VNode, VTag};
use crate::{html, Html, NodeRef};
@ -564,7 +566,7 @@ mod tests {
fn assert_vtag(node: VNode) -> VTag {
if let VNode::VTag(vtag) = node {
return *vtag;
return RcExt::unwrap_or_clone(vtag);
}
panic!("should be vtag");
}
@ -971,7 +973,7 @@ mod tests {
vtag.add_attribute("disabled", "disabled");
vtag.add_attribute("tabindex", "0");
let elem = VNode::VTag(Box::new(vtag));
let elem = VNode::VTag(Rc::new(vtag));
let (_, mut elem) = elem.attach(&root, &scope, &parent, DomSlot::at_end());
@ -979,7 +981,7 @@ mod tests {
let mut vtag = VTag::new("div");
vtag.node_ref = test_ref.clone();
vtag.add_attribute("tabindex", "0");
let next_elem = VNode::VTag(Box::new(vtag));
let next_elem = VNode::VTag(Rc::new(vtag));
let elem_vtag = assert_vtag(next_elem);
// Sync happens here

View File

@ -6,6 +6,7 @@ use implicit_clone::ImplicitClone;
use indexmap::IndexSet;
use super::IntoPropValue;
use crate::utils::RcExt;
use crate::virtual_dom::AttrValue;
/// A set of classes, cheap to clone.
@ -150,10 +151,7 @@ impl IntoIterator for Classes {
#[inline]
fn into_iter(self) -> Self::IntoIter {
// NOTE: replace this by Rc::unwrap_or_clone() when it becomes stable
Rc::try_unwrap(self.set)
.unwrap_or_else(|rc| (*rc).clone())
.into_iter()
RcExt::unwrap_or_clone(self.set).into_iter()
}
}

View File

@ -1,6 +1,7 @@
//! Component children module
use std::fmt;
use std::rc::Rc;
use crate::html::Html;
use crate::virtual_dom::{VChild, VComp, VList, VNode};
@ -241,7 +242,7 @@ impl From<ChildrenRenderer<Html>> for Html {
}
}
Html::VList(val.into())
Html::VList(Rc::new(val.into()))
}
}

View File

@ -46,18 +46,18 @@ where
{
#[inline(always)]
fn to_html(&self) -> Html {
Html::VList(VList::with_children(
Html::VList(Rc::new(VList::with_children(
self.iter().map(ToHtml::to_html).collect(),
None,
))
)))
}
#[inline(always)]
fn into_html(self) -> Html {
Html::VList(VList::with_children(
Html::VList(Rc::new(VList::with_children(
self.into_iter().map(ToHtml::into_html).collect(),
None,
))
)))
}
}
@ -81,7 +81,7 @@ impl ToHtml for Vec<VNode> {
#[inline(always)]
fn into_html(self) -> Html {
Html::VList(VList::with_children(self, None))
Html::VList(Rc::new(VList::with_children(self, None)))
}
}
@ -105,7 +105,7 @@ impl ToHtml for VList {
#[inline(always)]
fn into_html(self) -> Html {
Html::VList(self)
Html::VList(Rc::new(self))
}
}
@ -132,7 +132,7 @@ where
#[inline(always)]
fn into_html(self) -> Html {
VNode::VComp(self.into())
VNode::VComp(Rc::new(self.into()))
}
}

View File

@ -142,5 +142,5 @@ mod feat_csr {
/// ## Relevant examples
/// - [Portals](https://github.com/yewstack/yew/tree/master/examples/portals)
pub fn create_portal(child: Html, host: Element) -> Html {
VNode::VPortal(VPortal::new(child, host))
VNode::VPortal(Rc::new(VPortal::new(child, host)))
}

View File

@ -64,3 +64,14 @@ pub fn print_node(n: &web_sys::Node) -> String {
None => n.text_content().unwrap_or_default(),
}
}
// NOTE: replace this by Rc::unwrap_or_clone() when it becomes stable
pub(crate) trait RcExt<T: Clone> {
fn unwrap_or_clone(this: Self) -> T;
}
impl<T: Clone> RcExt<T> for std::rc::Rc<T> {
fn unwrap_or_clone(this: Self) -> T {
std::rc::Rc::try_unwrap(this).unwrap_or_else(|rc| (*rc).clone())
}
}

View File

@ -1,5 +1,7 @@
use std::rc::Rc;
use crate::html::ImplicitClone;
/// The [Listener] trait is an universal implementation of an event listener
/// which is used to bind Rust-listener to JS-listener (DOM).
pub trait Listener {
@ -168,6 +170,8 @@ pub enum Listeners {
Pending(Box<[Option<Rc<dyn Listener>>]>),
}
impl ImplicitClone for Listeners {}
impl PartialEq for Listeners {
fn eq(&self, rhs: &Self) -> bool {
use Listeners::*;

View File

@ -22,6 +22,7 @@ pub mod vtag;
pub mod vtext;
use std::hint::unreachable_unchecked;
use std::rc::Rc;
use indexmap::IndexMap;
@ -204,7 +205,7 @@ pub enum Attributes {
/// IndexMap is used to provide runtime attribute deduplication in cases where the html! macro
/// was not used to guarantee it.
IndexMap(IndexMap<AttrValue, (AttrValue, ApplyAttributeAs)>),
IndexMap(Rc<IndexMap<AttrValue, (AttrValue, ApplyAttributeAs)>>),
}
impl Attributes {
@ -233,7 +234,7 @@ impl Attributes {
macro_rules! unpack {
() => {
match self {
Self::IndexMap(m) => m,
Self::IndexMap(m) => Rc::make_mut(m),
// SAFETY: unreachable because we set self to the `IndexMap` variant above.
_ => unsafe { unreachable_unchecked() },
}
@ -241,23 +242,23 @@ impl Attributes {
}
match self {
Self::IndexMap(m) => m,
Self::IndexMap(m) => Rc::make_mut(m),
Self::Static(arr) => {
*self = Self::IndexMap(
*self = Self::IndexMap(Rc::new(
arr.iter()
.map(|(k, v, ty)| ((*k).into(), ((*v).into(), *ty)))
.collect(),
);
));
unpack!()
}
Self::Dynamic { keys, values } => {
*self = Self::IndexMap(
*self = Self::IndexMap(Rc::new(
std::mem::take(values)
.iter_mut()
.zip(keys.iter())
.filter_map(|(v, k)| v.take().map(|v| (AttrValue::from(*k), v)))
.collect(),
);
));
unpack!()
}
}
@ -270,7 +271,7 @@ impl From<IndexMap<AttrValue, AttrValue>> for Attributes {
.into_iter()
.map(|(k, v)| (k, (v, ApplyAttributeAs::Attribute)))
.collect();
Self::IndexMap(v)
Self::IndexMap(Rc::new(v))
}
}
@ -280,7 +281,7 @@ impl From<IndexMap<&'static str, AttrValue>> for Attributes {
.into_iter()
.map(|(k, v)| (AttrValue::Static(k), (v, ApplyAttributeAs::Attribute)))
.collect();
Self::IndexMap(v)
Self::IndexMap(Rc::new(v))
}
}

View File

@ -3,6 +3,7 @@ use std::ops::{Deref, DerefMut};
use std::rc::Rc;
use super::{Key, VNode};
use crate::html::ImplicitClone;
#[derive(Clone, Copy, Debug, PartialEq)]
enum FullyKeyedState {
@ -23,6 +24,8 @@ pub struct VList {
pub key: Option<Key>,
}
impl ImplicitClone for VList {}
impl PartialEq for VList {
fn eq(&self, other: &Self) -> bool {
self.key == other.key && self.children == other.children

View File

@ -2,12 +2,13 @@
use std::cmp::PartialEq;
use std::iter::FromIterator;
use std::rc::Rc;
use std::{fmt, mem};
use web_sys::Node;
use super::{Key, VChild, VComp, VList, VPortal, VSuspense, VTag, VText};
use crate::html::BaseComponent;
use crate::html::{BaseComponent, ImplicitClone};
use crate::virtual_dom::VRaw;
use crate::AttrValue;
@ -16,25 +17,27 @@ use crate::AttrValue;
#[must_use = "html does not do anything unless returned to Yew for rendering."]
pub enum VNode {
/// A bind between `VTag` and `Element`.
VTag(Box<VTag>),
VTag(Rc<VTag>),
/// A bind between `VText` and `TextNode`.
VText(VText),
/// A bind between `VComp` and `Element`.
VComp(VComp),
VComp(Rc<VComp>),
/// A holder for a list of other nodes.
VList(VList),
VList(Rc<VList>),
/// A portal to another part of the document
VPortal(VPortal),
VPortal(Rc<VPortal>),
/// A holder for any `Node` (necessary for replacing node).
VRef(Node),
/// A suspendible document fragment.
VSuspense(VSuspense),
VSuspense(Rc<VSuspense>),
/// A raw HTML string, represented by [`AttrValue`](crate::AttrValue).
///
/// Also see: [`VNode::from_html_unchecked`]
VRaw(VRaw),
}
impl ImplicitClone for VNode {}
impl VNode {
pub fn key(&self) -> Option<&Key> {
match self {
@ -60,9 +63,10 @@ impl VNode {
pub fn to_vlist_mut(&mut self) -> &mut VList {
loop {
match *self {
Self::VList(ref mut m) => return m,
Self::VList(ref mut m) => return Rc::make_mut(m),
_ => {
*self = VNode::VList(VList::with_children(vec![mem::take(self)], None));
*self =
VNode::VList(Rc::new(VList::with_children(vec![mem::take(self)], None)));
}
}
}
@ -105,7 +109,7 @@ impl VNode {
impl Default for VNode {
fn default() -> Self {
VNode::VList(VList::default())
VNode::VList(Rc::new(VList::default()))
}
}
@ -119,35 +123,35 @@ impl From<VText> for VNode {
impl From<VList> for VNode {
#[inline]
fn from(vlist: VList) -> Self {
VNode::VList(vlist)
VNode::VList(Rc::new(vlist))
}
}
impl From<VTag> for VNode {
#[inline]
fn from(vtag: VTag) -> Self {
VNode::VTag(Box::new(vtag))
VNode::VTag(Rc::new(vtag))
}
}
impl From<VComp> for VNode {
#[inline]
fn from(vcomp: VComp) -> Self {
VNode::VComp(vcomp)
VNode::VComp(Rc::new(vcomp))
}
}
impl From<VSuspense> for VNode {
#[inline]
fn from(vsuspense: VSuspense) -> Self {
VNode::VSuspense(vsuspense)
VNode::VSuspense(Rc::new(vsuspense))
}
}
impl From<VPortal> for VNode {
#[inline]
fn from(vportal: VPortal) -> Self {
VNode::VPortal(vportal)
VNode::VPortal(Rc::new(vportal))
}
}
@ -156,7 +160,7 @@ where
COMP: BaseComponent,
{
fn from(vchild: VChild<COMP>) -> Self {
VNode::VComp(VComp::from(vchild))
VNode::VComp(Rc::new(VComp::from(vchild)))
}
}
@ -168,10 +172,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 {
VNode::VList(VList::with_children(
VNode::VList(Rc::new(VList::with_children(
iter.into_iter().map(|n| n.into()).collect(),
None,
))
)))
}
}

View File

@ -8,10 +8,10 @@ use super::VNode;
pub struct VPortal {
/// The element under which the content is inserted.
pub host: Element,
/// The next sibling after the inserted content. Most be a child of `host`.
/// The next sibling after the inserted content. Must be a child of `host`.
pub inner_sibling: Option<Node>,
/// The inserted node
pub node: Box<VNode>,
pub node: VNode,
}
impl VPortal {
@ -20,7 +20,7 @@ impl VPortal {
Self {
host,
inner_sibling: None,
node: Box::new(content),
node: content,
}
}
@ -31,7 +31,7 @@ impl VPortal {
Self {
host,
inner_sibling,
node: Box::new(content),
node: content,
}
}
}

View File

@ -1,3 +1,4 @@
use crate::html::ImplicitClone;
use crate::AttrValue;
/// A raw HTML string to be used in VDOM.
@ -6,6 +7,8 @@ pub struct VRaw {
pub html: AttrValue,
}
impl ImplicitClone for VRaw {}
impl From<AttrValue> for VRaw {
fn from(html: AttrValue) -> Self {
Self { html }

View File

@ -1,23 +1,26 @@
use super::{Key, VNode};
use crate::html::ImplicitClone;
/// This struct represents a suspendable DOM fragment.
#[derive(Clone, Debug, PartialEq)]
pub struct VSuspense {
/// Child nodes.
pub(crate) children: Box<VNode>,
pub(crate) children: VNode,
/// Fallback nodes when suspended.
pub(crate) fallback: Box<VNode>,
pub(crate) fallback: VNode,
/// Whether the current status is suspended.
pub(crate) suspended: bool,
/// The Key.
pub(crate) key: Option<Key>,
}
impl ImplicitClone for VSuspense {}
impl VSuspense {
pub fn new(children: VNode, fallback: VNode, suspended: bool, key: Option<Key>) -> Self {
Self {
children: children.into(),
fallback: fallback.into(),
children,
fallback,
suspended,
key,
}

View File

@ -1,6 +1,5 @@
//! This module contains the implementation of a virtual element node [VTag].
use std::borrow::Cow;
use std::cmp::PartialEq;
use std::marker::PhantomData;
use std::mem;
@ -10,7 +9,7 @@ use std::rc::Rc;
use web_sys::{HtmlInputElement as InputElement, HtmlTextAreaElement as TextAreaElement};
use super::{ApplyAttributeAs, AttrValue, Attributes, Key, Listener, Listeners, VNode};
use crate::html::{IntoPropValue, NodeRef};
use crate::html::{ImplicitClone, IntoPropValue, NodeRef};
/// SVG namespace string used for creating svg elements
pub const SVG_NAMESPACE: &str = "http://www.w3.org/2000/svg";
@ -22,9 +21,17 @@ pub const MATHML_NAMESPACE: &str = "http://www.w3.org/1998/Math/MathML";
pub const HTML_NAMESPACE: &str = "http://www.w3.org/1999/xhtml";
/// Value field corresponding to an [Element]'s `value` property
#[derive(Clone, Debug, Eq, PartialEq)]
#[derive(Debug, Eq, PartialEq)]
pub(crate) struct Value<T>(Option<AttrValue>, PhantomData<T>);
impl<T> Clone for Value<T> {
fn clone(&self) -> Self {
Self::new(self.0.clone())
}
}
impl<T> ImplicitClone for Value<T> {}
impl<T> Default for Value<T> {
fn default() -> Self {
Self::new(None)
@ -68,6 +75,8 @@ pub(crate) struct InputFields {
pub(crate) checked: Option<bool>,
}
impl ImplicitClone for InputFields {}
impl Deref for InputFields {
type Target = Value<InputElement>;
@ -111,12 +120,14 @@ pub(crate) enum VTagInner {
/// Fields for all other kinds of [VTag]s
Other {
/// A tag of the element.
tag: Cow<'static, str>,
tag: AttrValue,
/// children of the element.
children: VNode,
},
}
impl ImplicitClone for VTagInner {}
/// A type for a virtual
/// [Element](https://developer.mozilla.org/en-US/docs/Web/API/Element)
/// representation.
@ -133,10 +144,12 @@ pub struct VTag {
pub key: Option<Key>,
}
impl ImplicitClone for VTag {}
impl VTag {
/// Creates a new [VTag] instance with `tag` name (cannot be changed later in DOM).
pub fn new(tag: impl Into<Cow<'static, str>>) -> Self {
let tag: Cow<'static, str> = tag.into();
pub fn new(tag: impl Into<AttrValue>) -> Self {
let tag = tag.into();
Self::new_base(
match &*tag.to_ascii_lowercase() {
"input" => VTagInner::Input(Default::default()),
@ -226,7 +239,7 @@ impl VTag {
#[doc(hidden)]
#[allow(clippy::too_many_arguments)]
pub fn __new_other(
tag: Cow<'static, str>,
tag: AttrValue,
node_ref: NodeRef,
key: Option<Key>,
// at bottom for more readable macro-expanded coded

View File

@ -3,6 +3,7 @@
use std::cmp::PartialEq;
use super::AttrValue;
use crate::html::ImplicitClone;
/// A type for a virtual
/// [`TextNode`](https://developer.mozilla.org/en-US/docs/Web/API/Document/createTextNode)
@ -13,6 +14,8 @@ pub struct VText {
pub text: AttrValue,
}
impl ImplicitClone for VText {}
impl VText {
/// Creates new virtual text node with a content.
pub fn new(text: impl Into<AttrValue>) -> Self {