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) /// 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 /// 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 pulldown_cmark::{Alignment, CodeBlockKind, Event, Options, Parser, Tag};
use yew::virtual_dom::{VNode, VTag, VText}; use yew::virtual_dom::{VNode, VTag, VText};
use yew::{html, Classes, Html}; use yew::{html, Classes, Html};
@ -56,16 +58,22 @@ pub fn render_markdown(src: &str) -> Html {
if let Some(top_children) = top.children_mut() { if let Some(top_children) = top.children_mut() {
for r in top_children.to_vlist_mut().iter_mut() { for r in top_children.to_vlist_mut().iter_mut() {
if let VNode::VTag(ref mut vtag) = r { 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 for (i, c) in
vtag_children.to_vlist_mut().iter_mut().enumerate() vtag_children.to_vlist_mut().iter_mut().enumerate()
{ {
if let VNode::VTag(ref mut vtag) = c { if let VNode::VTag(ref mut vtag) = c {
match aligns[i] { match aligns[i] {
Alignment::None => {} Alignment::None => {}
Alignment::Left => add_class(vtag, "text-left"), Alignment::Left => {
Alignment::Center => add_class(vtag, "text-center"), add_class(Rc::make_mut(vtag), "text-left")
Alignment::Right => add_class(vtag, "text-right"), }
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 { if let VNode::VTag(ref mut vtag) = c {
// TODO // TODO
// vtag.tag = "th".into(); // 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 { if elems.len() == 1 {
VNode::VTag(Box::new(elems.pop().unwrap())) VNode::VTag(Rc::new(elems.pop().unwrap()))
} else { } else {
html! { html! {
<div>{ for elems.into_iter() }</div> <div>{ for elems.into_iter() }</div>

View File

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

View File

@ -78,9 +78,9 @@ impl ToTokens for HtmlList {
}; };
tokens.extend(quote_spanned! {spanned.span()=> 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) ::yew::virtual_dom::VList::with_children(#children, #key)
) ))
}); });
} }
} }

View File

@ -123,7 +123,7 @@ impl ToTokens for HtmlTree {
lint::lint_all(self); lint::lint_all(self);
match self { match self {
HtmlTree::Empty => tokens.extend(quote! { 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::Component(comp) => comp.to_tokens(tokens),
HtmlTree::Element(tag) => tag.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()=> 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) ::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 { impl ::std::convert::Into<::yew::virtual_dom::VNode> for ChildrenVariants {
fn into(self) -> ::yew::virtual_dom::VNode { fn into(self) -> ::yew::virtual_dom::VNode {
match self { 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, ::yew::virtual_dom::VComp,
>::into(comp)), >::into(comp))),
Self::AltChild(comp) => ::yew::virtual_dom::VNode::VComp(::std::convert::Into::< Self::AltChild(comp) => ::yew::virtual_dom::VNode::VComp(::std::rc::Rc::new(::std::convert::Into::<
::yew::virtual_dom::VComp, ::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 { impl ::std::convert::Into<::yew::virtual_dom::VNode> for ChildrenVariants {
fn into(self) -> ::yew::virtual_dom::VNode { fn into(self) -> ::yew::virtual_dom::VNode {
match self { 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, ::yew::virtual_dom::VComp,
>::into(comp)), >::into(comp))),
Self::AltChild(comp) => ::yew::virtual_dom::VNode::VComp(::std::convert::Into::< Self::AltChild(comp) => ::yew::virtual_dom::VNode::VComp(::std::rc::Rc::new(::std::convert::Into::<
::yew::virtual_dom::VComp, ::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 and $N others
= note: this error originates in the macro `html` (in Nightly builds, run with -Z macro-backtrace for more info) = 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 --> tests/html_macro/element-fail.rs:77:15
| |
77 | html! { <@{55}></@> }; 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>`: = help: the following other types implement trait `From<T>`:
<Cow<'a, CStr> as From<&'a CStr>> <implicit_clone::unsync::IString as From<&'static str>>
<Cow<'a, CStr> as From<&'a CString>> <implicit_clone::unsync::IString as From<Cow<'static, str>>>
<Cow<'a, CStr> as From<CString>> <implicit_clone::unsync::IString as From<Rc<str>>>
<Cow<'a, OsStr> as From<&'a OsStr>> <implicit_clone::unsync::IString as From<String>>
<Cow<'a, OsStr> as From<&'a OsString>> = note: required because of the requirements on the impl of `Into<implicit_clone::unsync::IString>` for `{integer}`
<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}`

View File

@ -4,13 +4,13 @@ use std::cmp::Ordering;
use std::collections::HashSet; use std::collections::HashSet;
use std::hash::Hash; use std::hash::Hash;
use std::ops::Deref; use std::ops::Deref;
use std::rc::Rc;
use web_sys::Element; use web_sys::Element;
use super::{test_log, BNode, BSubtree, DomSlot}; use super::{test_log, BNode, BSubtree, DomSlot};
use crate::dom_bundle::{Reconcilable, ReconcileTarget}; use crate::dom_bundle::{Reconcilable, ReconcileTarget};
use crate::html::AnyScope; use crate::html::AnyScope;
use crate::utils::RcExt;
use crate::virtual_dom::{Key, VList, VNode, VText}; use crate::virtual_dom::{Key, VList, VNode, VText};
/// This struct represents a mounted [VList] /// This struct represents a mounted [VList]
@ -30,10 +30,8 @@ impl VList {
let children = self let children = self
.children .children
.map(Rc::try_unwrap) .map(RcExt::unwrap_or_clone)
.unwrap_or_else(|| Ok(Vec::new())) .unwrap_or_default();
// Rc::unwrap_or_clone is not stable yet.
.unwrap_or_else(|m| m.to_vec());
(self.key, fully_keyed, children) (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 super::{BComp, BList, BPortal, BRaw, BSubtree, BSuspense, BTag, BText, DomSlot};
use crate::dom_bundle::{Reconcilable, ReconcileTarget}; use crate::dom_bundle::{Reconcilable, ReconcileTarget};
use crate::html::AnyScope; use crate::html::AnyScope;
use crate::utils::RcExt;
use crate::virtual_dom::{Key, VNode}; use crate::virtual_dom::{Key, VNode};
/// The bundle implementation to [VNode]. /// The bundle implementation to [VNode].
@ -95,7 +96,8 @@ impl Reconcilable for VNode {
) -> (DomSlot, Self::Bundle) { ) -> (DomSlot, Self::Bundle) {
match self { match self {
VNode::VTag(vtag) => { 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()) (node_ref, tag.into())
} }
VNode::VText(vtext) => { VNode::VText(vtext) => {
@ -103,11 +105,13 @@ impl Reconcilable for VNode {
(node_ref, text.into()) (node_ref, text.into())
} }
VNode::VComp(vcomp) => { 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()) (node_ref, comp.into())
} }
VNode::VList(vlist) => { 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()) (node_ref, list.into())
} }
VNode::VRef(node) => { VNode::VRef(node) => {
@ -115,11 +119,13 @@ impl Reconcilable for VNode {
(DomSlot::at(node.clone()), BNode::Ref(node)) (DomSlot::at(node.clone()), BNode::Ref(node))
} }
VNode::VPortal(vportal) => { 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()) (node_ref, portal.into())
} }
VNode::VSuspense(vsuspsense) => { 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()) (node_ref, suspsense.into())
} }
VNode::VRaw(vraw) => { VNode::VRaw(vraw) => {
@ -149,20 +155,46 @@ impl Reconcilable for VNode {
bundle: &mut BNode, bundle: &mut BNode,
) -> DomSlot { ) -> DomSlot {
match self { 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::VText(vtext) => vtext.reconcile_node(root, parent_scope, parent, slot, bundle),
VNode::VComp(vcomp) => vcomp.reconcile_node(root, parent_scope, parent, slot, bundle), VNode::VComp(vcomp) => RcExt::unwrap_or_clone(vcomp).reconcile_node(
VNode::VList(vlist) => vlist.reconcile_node(root, parent_scope, parent, slot, bundle), 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 { VNode::VRef(node) => match bundle {
BNode::Ref(ref n) if &node == n => DomSlot::at(node), BNode::Ref(ref n) if &node == n => DomSlot::at(node),
_ => VNode::VRef(node).replace(root, parent_scope, parent, slot, bundle), _ => VNode::VRef(node).replace(root, parent_scope, parent, slot, bundle),
}, },
VNode::VPortal(vportal) => { VNode::VPortal(vportal) => RcExt::unwrap_or_clone(vportal).reconcile_node(
vportal.reconcile_node(root, parent_scope, parent, slot, bundle) root,
} parent_scope,
VNode::VSuspense(vsuspsense) => { parent,
vsuspsense.reconcile_node(root, parent_scope, parent, slot, bundle) 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), VNode::VRaw(vraw) => vraw.reconcile_node(root, parent_scope, parent, slot, bundle),
} }
} }
@ -246,10 +278,16 @@ mod feat_hydration {
fragment: &mut Fragment, fragment: &mut Fragment,
) -> Self::Bundle { ) -> Self::Bundle {
match self { 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::VText(vtext) => vtext.hydrate(root, parent_scope, parent, fragment).into(),
VNode::VComp(vcomp) => vcomp.hydrate(root, parent_scope, parent, fragment).into(), VNode::VComp(vcomp) => RcExt::unwrap_or_clone(vcomp)
VNode::VList(vlist) => vlist.hydrate(root, parent_scope, parent, fragment).into(), .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. // You cannot hydrate a VRef.
VNode::VRef(_) => { VNode::VRef(_) => {
panic!( panic!(
@ -264,7 +302,7 @@ mod feat_hydration {
use_effect." use_effect."
) )
} }
VNode::VSuspense(vsuspense) => vsuspense VNode::VSuspense(vsuspense) => RcExt::unwrap_or_clone(vsuspense)
.hydrate(root, parent_scope, parent, fragment) .hydrate(root, parent_scope, parent, fragment)
.into(), .into(),
VNode::VRaw(vraw) => vraw.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 { mod layout_tests {
extern crate self as yew; extern crate self as yew;
use std::rc::Rc;
use gloo::utils::document; use gloo::utils::document;
use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure}; use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
use web_sys::HtmlInputElement; use web_sys::HtmlInputElement;
@ -151,10 +153,10 @@ mod layout_tests {
<div> <div>
{VNode::VRef(first_target.clone().into())} {VNode::VRef(first_target.clone().into())}
{VNode::VRef(second_target.clone().into())} {VNode::VRef(second_target.clone().into())}
{VNode::VPortal(VPortal::new( {VNode::VPortal(Rc::new(VPortal::new(
html! { {"PORTAL"} }, html! { {"PORTAL"} },
first_target.clone(), first_target.clone(),
))} )))}
{"AFTER"} {"AFTER"}
</div> </div>
}, },
@ -166,10 +168,10 @@ mod layout_tests {
<div> <div>
{VNode::VRef(first_target.clone().into())} {VNode::VRef(first_target.clone().into())}
{VNode::VRef(second_target.clone().into())} {VNode::VRef(second_target.clone().into())}
{VNode::VPortal(VPortal::new( {VNode::VPortal(Rc::new(VPortal::new(
html! { {"PORTAL"} }, html! { {"PORTAL"} },
second_target.clone(), second_target.clone(),
))} )))}
{"AFTER"} {"AFTER"}
</div> </div>
}, },
@ -181,10 +183,10 @@ mod layout_tests {
<div> <div>
{VNode::VRef(first_target.clone().into())} {VNode::VRef(first_target.clone().into())}
{VNode::VRef(second_target.clone().into())} {VNode::VRef(second_target.clone().into())}
{VNode::VPortal(VPortal::new( {VNode::VPortal(Rc::new(VPortal::new(
html! { <> {"PORTAL"} <b /> </> }, html! { <> {"PORTAL"} <b /> </> },
second_target.clone(), second_target.clone(),
))} )))}
{"AFTER"} {"AFTER"}
</div> </div>
}, },
@ -207,11 +209,11 @@ mod layout_tests {
node: html! { node: html! {
<div> <div>
{VNode::VRef(target_with_child.clone().into())} {VNode::VRef(target_with_child.clone().into())}
{VNode::VPortal(VPortal::new_before( {VNode::VPortal(Rc::new(VPortal::new_before(
html! { {"PORTAL"} }, html! { {"PORTAL"} },
target_with_child.clone(), target_with_child.clone(),
Some(target_child.clone().into()), Some(target_child.clone().into()),
))} )))}
</div> </div>
}, },
expected: "<div><i>PORTAL<s></s></i></div>", expected: "<div><i>PORTAL<s></s></i></div>",

View File

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

View File

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

View File

@ -1,6 +1,7 @@
//! Component children module //! Component children module
use std::fmt; use std::fmt;
use std::rc::Rc;
use crate::html::Html; use crate::html::Html;
use crate::virtual_dom::{VChild, VComp, VList, VNode}; 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)] #[inline(always)]
fn to_html(&self) -> Html { fn to_html(&self) -> Html {
Html::VList(VList::with_children( Html::VList(Rc::new(VList::with_children(
self.iter().map(ToHtml::to_html).collect(), self.iter().map(ToHtml::to_html).collect(),
None, None,
)) )))
} }
#[inline(always)] #[inline(always)]
fn into_html(self) -> Html { 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(), self.into_iter().map(ToHtml::into_html).collect(),
None, None,
)) )))
} }
} }
@ -81,7 +81,7 @@ impl ToHtml for Vec<VNode> {
#[inline(always)] #[inline(always)]
fn into_html(self) -> Html { 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)] #[inline(always)]
fn into_html(self) -> Html { fn into_html(self) -> Html {
Html::VList(self) Html::VList(Rc::new(self))
} }
} }
@ -132,7 +132,7 @@ where
#[inline(always)] #[inline(always)]
fn into_html(self) -> Html { 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 /// ## Relevant examples
/// - [Portals](https://github.com/yewstack/yew/tree/master/examples/portals) /// - [Portals](https://github.com/yewstack/yew/tree/master/examples/portals)
pub fn create_portal(child: Html, host: Element) -> Html { 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(), 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 std::rc::Rc;
use crate::html::ImplicitClone;
/// The [Listener] trait is an universal implementation of an event listener /// The [Listener] trait is an universal implementation of an event listener
/// which is used to bind Rust-listener to JS-listener (DOM). /// which is used to bind Rust-listener to JS-listener (DOM).
pub trait Listener { pub trait Listener {
@ -168,6 +170,8 @@ pub enum Listeners {
Pending(Box<[Option<Rc<dyn Listener>>]>), Pending(Box<[Option<Rc<dyn Listener>>]>),
} }
impl ImplicitClone for Listeners {}
impl PartialEq for Listeners { impl PartialEq for Listeners {
fn eq(&self, rhs: &Self) -> bool { fn eq(&self, rhs: &Self) -> bool {
use Listeners::*; use Listeners::*;

View File

@ -22,6 +22,7 @@ pub mod vtag;
pub mod vtext; pub mod vtext;
use std::hint::unreachable_unchecked; use std::hint::unreachable_unchecked;
use std::rc::Rc;
use indexmap::IndexMap; use indexmap::IndexMap;
@ -204,7 +205,7 @@ pub enum Attributes {
/// IndexMap is used to provide runtime attribute deduplication in cases where the html! macro /// IndexMap is used to provide runtime attribute deduplication in cases where the html! macro
/// was not used to guarantee it. /// was not used to guarantee it.
IndexMap(IndexMap<AttrValue, (AttrValue, ApplyAttributeAs)>), IndexMap(Rc<IndexMap<AttrValue, (AttrValue, ApplyAttributeAs)>>),
} }
impl Attributes { impl Attributes {
@ -233,7 +234,7 @@ impl Attributes {
macro_rules! unpack { macro_rules! unpack {
() => { () => {
match self { match self {
Self::IndexMap(m) => m, Self::IndexMap(m) => Rc::make_mut(m),
// SAFETY: unreachable because we set self to the `IndexMap` variant above. // SAFETY: unreachable because we set self to the `IndexMap` variant above.
_ => unsafe { unreachable_unchecked() }, _ => unsafe { unreachable_unchecked() },
} }
@ -241,23 +242,23 @@ impl Attributes {
} }
match self { match self {
Self::IndexMap(m) => m, Self::IndexMap(m) => Rc::make_mut(m),
Self::Static(arr) => { Self::Static(arr) => {
*self = Self::IndexMap( *self = Self::IndexMap(Rc::new(
arr.iter() arr.iter()
.map(|(k, v, ty)| ((*k).into(), ((*v).into(), *ty))) .map(|(k, v, ty)| ((*k).into(), ((*v).into(), *ty)))
.collect(), .collect(),
); ));
unpack!() unpack!()
} }
Self::Dynamic { keys, values } => { Self::Dynamic { keys, values } => {
*self = Self::IndexMap( *self = Self::IndexMap(Rc::new(
std::mem::take(values) std::mem::take(values)
.iter_mut() .iter_mut()
.zip(keys.iter()) .zip(keys.iter())
.filter_map(|(v, k)| v.take().map(|v| (AttrValue::from(*k), v))) .filter_map(|(v, k)| v.take().map(|v| (AttrValue::from(*k), v)))
.collect(), .collect(),
); ));
unpack!() unpack!()
} }
} }
@ -270,7 +271,7 @@ impl From<IndexMap<AttrValue, AttrValue>> for Attributes {
.into_iter() .into_iter()
.map(|(k, v)| (k, (v, ApplyAttributeAs::Attribute))) .map(|(k, v)| (k, (v, ApplyAttributeAs::Attribute)))
.collect(); .collect();
Self::IndexMap(v) Self::IndexMap(Rc::new(v))
} }
} }
@ -280,7 +281,7 @@ impl From<IndexMap<&'static str, AttrValue>> for Attributes {
.into_iter() .into_iter()
.map(|(k, v)| (AttrValue::Static(k), (v, ApplyAttributeAs::Attribute))) .map(|(k, v)| (AttrValue::Static(k), (v, ApplyAttributeAs::Attribute)))
.collect(); .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 std::rc::Rc;
use super::{Key, VNode}; use super::{Key, VNode};
use crate::html::ImplicitClone;
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
enum FullyKeyedState { enum FullyKeyedState {
@ -23,6 +24,8 @@ pub struct VList {
pub key: Option<Key>, pub key: Option<Key>,
} }
impl ImplicitClone for VList {}
impl PartialEq for VList { impl PartialEq for VList {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.key == other.key && self.children == other.children self.key == other.key && self.children == other.children

View File

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

View File

@ -8,10 +8,10 @@ use super::VNode;
pub struct VPortal { pub struct VPortal {
/// The element under which the content is inserted. /// The element under which the content is inserted.
pub host: Element, 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>, pub inner_sibling: Option<Node>,
/// The inserted node /// The inserted node
pub node: Box<VNode>, pub node: VNode,
} }
impl VPortal { impl VPortal {
@ -20,7 +20,7 @@ impl VPortal {
Self { Self {
host, host,
inner_sibling: None, inner_sibling: None,
node: Box::new(content), node: content,
} }
} }
@ -31,7 +31,7 @@ impl VPortal {
Self { Self {
host, host,
inner_sibling, inner_sibling,
node: Box::new(content), node: content,
} }
} }
} }

View File

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

View File

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

View File

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

View File

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