xilem_web: Optimize element casting, and intern often used strings (e.g. "div") (#594)

Some micro-optimizations, e.g. avoids a js call `instanceof` (via
`dyn_into`/`dyn_ref`) when constructing elements.

Leads to a small speed increase when constructing/visiting elements
(roughly 2%) and more importantly a leaner wasm binary (around 5 %),
respectively tested with the js-framework-benchmark suite.
This commit is contained in:
Philipp Mildenberger 2024-09-17 20:49:15 +02:00 committed by GitHub
parent d758ae508e
commit 2ccd9d4712
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 19 additions and 20 deletions

View File

@ -179,3 +179,5 @@ features = [
[features]
default = ["hydration"]
hydration = []
# This interns some often used strings, such as element tags ("div" etc.), which slightly improves performance when creating elements at the cost of a bigger wasm binary
intern_strings = ["wasm-bindgen/enable-interning"]

View File

@ -127,17 +127,13 @@ impl Attributes {
for modifier in self.attribute_modifiers.iter().rev() {
match modifier {
AttributeModifier::Remove(name) => {
if self.updated_attributes.contains_key(name) {
self.updated_attributes.remove(name);
if self.updated_attributes.remove(name).is_some() {
remove_attribute(element, name);
// element.remove_attribute(name);
}
}
AttributeModifier::Set(name, value) => {
if self.updated_attributes.contains_key(name) {
self.updated_attributes.remove(name);
if self.updated_attributes.remove(name).is_some() {
set_attribute(element, name, &value.serialize());
// element.set_attribute(name, &value.serialize());
}
}
AttributeModifier::EndMarker(_) => (),

View File

@ -149,7 +149,7 @@ impl Classes {
// Svg elements do have issues with className, see https://developer.mozilla.org/en-US/docs/Web/API/Element/className
if element.dyn_ref::<web_sys::SvgElement>().is_some() {
element
.set_attribute("class", &self.class_name)
.set_attribute(wasm_bindgen::intern("class"), &self.class_name)
.unwrap_throw();
} else {
element.set_class_name(&self.class_name);

View File

@ -70,7 +70,10 @@ impl Pod<web_sys::Element, ElementProps> {
/// Creates a new Pod with [`web_sys::Element`] as element and `ElementProps` as its [`DomView::Props`](`crate::DomView::Props`)
pub fn new_element(children: Vec<AnyPod>, ns: &str, elem_name: &str) -> Self {
let element = document()
.create_element_ns(Some(ns), elem_name)
.create_element_ns(
Some(wasm_bindgen::intern(ns)),
wasm_bindgen::intern(elem_name),
)
.unwrap_throw();
for child in children.iter() {
@ -93,7 +96,7 @@ impl Pod<web_sys::Element, ElementProps> {
#[cfg(feature = "hydration")]
pub fn hydrate_element(children: Vec<AnyPod>, element: web_sys::Node) -> Self {
Self {
node: element.dyn_into().unwrap_throw(),
node: element.unchecked_into(),
props: ElementProps {
in_hydration: true,
attributes: None,

View File

@ -381,7 +381,7 @@ where
.replace_child(&new_element, element.node)
.unwrap_throw();
}
*element.node = new_element.dyn_into().unwrap_throw();
*element.node = new_element.unchecked_into();
}
rebuild_element(

View File

@ -74,7 +74,7 @@ fn create_event_listener<Event: JsCast + crate::Message>(
) -> Closure<dyn FnMut(web_sys::Event)> {
let thunk = ctx.message_thunk();
let callback = Closure::new(move |event: web_sys::Event| {
let event = event.dyn_into::<Event>().unwrap_throw();
let event = event.unchecked_into::<Event>();
thunk.push_message(event);
});
@ -545,7 +545,7 @@ where
ctx.with_id(ON_EVENT_VIEW_ID, |ctx| {
let thunk = ctx.message_thunk();
let callback = Closure::new(move |entries: js_sys::Array| {
let entry: web_sys::ResizeObserverEntry = entries.at(0).dyn_into().unwrap_throw();
let entry: web_sys::ResizeObserverEntry = entries.at(0).unchecked_into();
thunk.push_message(entry);
});

View File

@ -409,7 +409,7 @@ macro_rules! impl_dom_node_for_elements {
impl From<Pod<web_sys::Element, ElementProps>> for Pod<web_sys::$ty, ElementProps> {
fn from(value: Pod<web_sys::Element, ElementProps>) -> Self {
Self {
node: value.node.dyn_into().unwrap_throw(),
node: value.node.unchecked_into(),
props: value.props,
}
}

View File

@ -86,7 +86,7 @@ where
ctx.with_id(ViewId::new(0), |ctx| {
let (element, child_state) = self.child.build(ctx);
let thunk = ctx.message_thunk();
let el = element.as_ref().dyn_ref::<web_sys::Element>().unwrap();
let el = element.as_ref().unchecked_ref::<web_sys::Element>();
let el_clone = el.clone();
let down_closure = Closure::new(move |e: PointerEvent| {
thunk.push_message(PointerMsg::Down(PointerDetails::from_pointer_event(&e)));

View File

@ -183,14 +183,12 @@ impl Styles {
for modifier in self.style_modifiers.iter().rev() {
match modifier {
StyleModifier::Remove(name) => {
if self.updated_styles.contains_key(name) {
self.updated_styles.remove(name);
if self.updated_styles.remove(name).is_some() {
remove_style(element, name);
}
}
StyleModifier::Set(name, value) => {
if self.updated_styles.contains_key(name) {
self.updated_styles.remove(name);
if self.updated_styles.remove(name).is_some() {
set_style(element, name, value);
}
}

View File

@ -21,7 +21,7 @@ macro_rules! impl_string_view {
) -> (Self::OrphanElement, Self::OrphanViewState) {
#[cfg(feature = "hydration")]
let node = if ctx.is_hydrating() {
ctx.hydrate_node().unwrap().dyn_into().unwrap()
ctx.hydrate_node().unwrap().unchecked_into()
} else {
web_sys::Text::new_with_data(view).unwrap()
};
@ -81,7 +81,7 @@ macro_rules! impl_to_string_view {
) -> (Self::OrphanElement, Self::OrphanViewState) {
#[cfg(feature = "hydration")]
let node = if ctx.is_hydrating() {
ctx.hydrate_node().unwrap().dyn_into().unwrap()
ctx.hydrate_node().unwrap().unchecked_into()
} else {
web_sys::Text::new_with_data(&view.to_string()).unwrap()
};