Browse Source

optimizations for wasm size (#582)

* optimize for size

* fix tests

* revert log feature

* make backtrace not optional

* remove dev feature from web dev-deps
Demonthos 2 years ago
parent
commit
f21c8423eb

+ 5 - 6
packages/core/Cargo.toml

@@ -23,9 +23,6 @@ rustc-hash = "1.1.0"
 # Used in diffing
 # Used in diffing
 longest-increasing-subsequence = "0.1.0"
 longest-increasing-subsequence = "0.1.0"
 
 
-# internall used
-log = { version = "0.4" }
-
 futures-util = { version = "0.3", default-features = false }
 futures-util = { version = "0.3", default-features = false }
 
 
 smallvec = "1.6"
 smallvec = "1.6"
@@ -34,16 +31,18 @@ slab = "0.4"
 
 
 futures-channel = "0.3.21"
 futures-channel = "0.3.21"
 
 
+# internally used
+log = "0.4"
+
 # used for noderefs
 # used for noderefs
 once_cell = "1.8"
 once_cell = "1.8"
 
 
-indexmap = "1.7"
 
 
 # Serialize the Edits for use in Webview/Liveview instances
 # Serialize the Edits for use in Webview/Liveview instances
 serde = { version = "1", features = ["derive"], optional = true }
 serde = { version = "1", features = ["derive"], optional = true }
 
 
 # todo: I want to get rid of this
 # todo: I want to get rid of this
-backtrace = "0.3"
+backtrace = { version = "0.3" }
 
 
 # allows cloing trait objects
 # allows cloing trait objects
 dyn-clone = "1.0.9"
 dyn-clone = "1.0.9"
@@ -52,4 +51,4 @@ dyn-clone = "1.0.9"
 default = []
 default = []
 serialize = ["serde"]
 serialize = ["serde"]
 debug_vdom = []
 debug_vdom = []
-hot-reload = []
+hot-reload = []

+ 42 - 12
packages/core/src/virtual_dom.rs

@@ -2,13 +2,13 @@
 //!
 //!
 //! This module provides the primary mechanics to create a hook-based, concurrent VDOM for Rust.
 //! This module provides the primary mechanics to create a hook-based, concurrent VDOM for Rust.
 
 
+use std::{collections::VecDeque, iter::FromIterator, task::Poll};
+
 use crate::diff::DiffState;
 use crate::diff::DiffState;
 use crate::innerlude::*;
 use crate::innerlude::*;
 use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
 use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
 use futures_util::{future::poll_fn, StreamExt};
 use futures_util::{future::poll_fn, StreamExt};
-use indexmap::IndexSet;
 use rustc_hash::FxHashSet;
 use rustc_hash::FxHashSet;
-use std::{collections::VecDeque, iter::FromIterator, task::Poll};
 
 
 /// A virtual node system that progresses user events and diffs UI trees.
 /// A virtual node system that progresses user events and diffs UI trees.
 ///
 ///
@@ -107,7 +107,8 @@ pub struct VirtualDom {
     scopes: ScopeArena,
     scopes: ScopeArena,
 
 
     pending_messages: VecDeque<SchedulerMsg>,
     pending_messages: VecDeque<SchedulerMsg>,
-    dirty_scopes: IndexSet<ScopeId>,
+    dirty_scopes: Vec<ScopeId>,
+    removed_scopes: FxHashSet<ScopeId>,
 
 
     channel: (
     channel: (
         UnboundedSender<SchedulerMsg>,
         UnboundedSender<SchedulerMsg>,
@@ -238,8 +239,9 @@ impl VirtualDom {
             root: ElementId(0),
             root: ElementId(0),
             scopes,
             scopes,
             channel,
             channel,
-            dirty_scopes: IndexSet::from_iter([ScopeId(0)]),
+            dirty_scopes: Vec::from_iter([ScopeId(0)]),
             pending_messages: VecDeque::new(),
             pending_messages: VecDeque::new(),
+            removed_scopes: FxHashSet::default(),
         }
         }
     }
     }
 
 
@@ -396,11 +398,18 @@ impl VirtualDom {
                 }
                 }
             }
             }
             SchedulerMsg::Immediate(s) => {
             SchedulerMsg::Immediate(s) => {
-                self.dirty_scopes.insert(s);
+                self.mark_dirty_scope(s);
             }
             }
             SchedulerMsg::DirtyAll => {
             SchedulerMsg::DirtyAll => {
-                for id in self.scopes.scopes.borrow().keys() {
-                    self.dirty_scopes.insert(*id);
+                let dirty = self
+                    .scopes
+                    .scopes
+                    .borrow()
+                    .keys()
+                    .copied()
+                    .collect::<Vec<_>>();
+                for id in dirty {
+                    self.mark_dirty_scope(id);
                 }
                 }
             }
             }
             #[cfg(any(feature = "hot-reload", debug_assertions))]
             #[cfg(any(feature = "hot-reload", debug_assertions))]
@@ -474,6 +483,7 @@ impl VirtualDom {
     pub fn work_with_deadline(&mut self, mut deadline: impl FnMut() -> bool) -> Vec<Mutations> {
     pub fn work_with_deadline(&mut self, mut deadline: impl FnMut() -> bool) -> Vec<Mutations> {
         let mut committed_mutations = vec![];
         let mut committed_mutations = vec![];
         self.scopes.template_bump.reset();
         self.scopes.template_bump.reset();
+        self.removed_scopes.clear();
 
 
         while !self.dirty_scopes.is_empty() {
         while !self.dirty_scopes.is_empty() {
             let scopes = &self.scopes;
             let scopes = &self.scopes;
@@ -492,7 +502,10 @@ impl VirtualDom {
             });
             });
 
 
             if let Some(scopeid) = self.dirty_scopes.pop() {
             if let Some(scopeid) = self.dirty_scopes.pop() {
-                if !ran_scopes.contains(&scopeid) {
+                if scopes.get_scope(scopeid).is_some()
+                    && !self.removed_scopes.contains(&scopeid)
+                    && !ran_scopes.contains(&scopeid)
+                {
                     ran_scopes.insert(scopeid);
                     ran_scopes.insert(scopeid);
 
 
                     self.scopes.run_scope(scopeid);
                     self.scopes.run_scope(scopeid);
@@ -501,9 +514,8 @@ impl VirtualDom {
 
 
                     let DiffState { mutations, .. } = diff_state;
                     let DiffState { mutations, .. } = diff_state;
 
 
-                    for scope in &mutations.dirty_scopes {
-                        self.dirty_scopes.remove(scope);
-                    }
+                    self.removed_scopes
+                        .extend(mutations.dirty_scopes.iter().copied());
 
 
                     if !mutations.edits.is_empty() {
                     if !mutations.edits.is_empty() {
                         committed_mutations.push(mutations);
                         committed_mutations.push(mutations);
@@ -729,6 +741,23 @@ impl VirtualDom {
             })
             })
             .unwrap()
             .unwrap()
     }
     }
+
+    fn mark_dirty_scope(&mut self, scope_id: ScopeId) {
+        let scopes = &self.scopes;
+        if let Some(scope) = scopes.get_scope(scope_id) {
+            let height = scope.height;
+            let id = scope_id.0;
+            if let Err(index) = self.dirty_scopes.binary_search_by(|new| {
+                let scope = scopes.get_scope(*new).unwrap();
+                let new_height = scope.height;
+                let new_id = &scope.scope_id();
+                height.cmp(&new_height).then(new_id.0.cmp(&id))
+            }) {
+                self.dirty_scopes.insert(index, scope_id);
+                log::info!("mark_dirty_scope: {:?}", self.dirty_scopes);
+            }
+        }
+    }
 }
 }
 
 
 /*
 /*
@@ -736,7 +765,8 @@ Scopes and ScopeArenas are never dropped internally.
 An app will always occupy as much memory as its biggest form.
 An app will always occupy as much memory as its biggest form.
 
 
 This means we need to handle all specifics of drop *here*. It's easier
 This means we need to handle all specifics of drop *here*. It's easier
-to reason about centralizing all the drop logic in one spot rather than scattered in each module.
+to reason about centralizing all the drop
+logic in one spot rather than scattered in each module.
 
 
 Broadly speaking, we want to use the remove_nodes method to clean up *everything*
 Broadly speaking, we want to use the remove_nodes method to clean up *everything*
 This will drop listeners, borrowed props, and hooks for all components.
 This will drop listeners, borrowed props, and hooks for all components.

+ 1 - 1
packages/hooks/Cargo.toml

@@ -14,7 +14,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
 [dependencies]
 [dependencies]
 dioxus-core = { path = "../../packages/core", version = "^0.2.1" }
 dioxus-core = { path = "../../packages/core", version = "^0.2.1" }
 futures-channel = "0.3.21"
 futures-channel = "0.3.21"
-log = { version = "0.4" }
+log = "0.4"
 
 
 
 
 [dev-dependencies]
 [dev-dependencies]

+ 4 - 4
packages/web/Cargo.toml

@@ -20,7 +20,7 @@ dioxus-interpreter-js = { path = "../interpreter", version = "^0.2.1", features
 js-sys = "0.3.56"
 js-sys = "0.3.56"
 wasm-bindgen = { version = "0.2.79", features = ["enable-interning"] }
 wasm-bindgen = { version = "0.2.79", features = ["enable-interning"] }
 wasm-bindgen-futures = "0.4.29"
 wasm-bindgen-futures = "0.4.29"
-log = { version = "0.4.14" }
+log = "0.4.14"
 rustc-hash = "1.1.0"
 rustc-hash = "1.1.0"
 console_error_panic_hook = { version = "0.1.7", optional = true }
 console_error_panic_hook = { version = "0.1.7", optional = true }
 once_cell = "1.9.0"
 once_cell = "1.9.0"
@@ -28,7 +28,6 @@ anyhow = "1.0.53"
 gloo-timers = { version = "0.2.3", features = ["futures"] }
 gloo-timers = { version = "0.2.3", features = ["futures"] }
 futures-util = "0.3.19"
 futures-util = "0.3.19"
 smallstr = "0.2.0"
 smallstr = "0.2.0"
-serde-wasm-bindgen = "0.4.2"
 futures-channel = "0.3.21"
 futures-channel = "0.3.21"
 serde_json = { version = "1.0" }
 serde_json = { version = "1.0" }
 
 
@@ -78,13 +77,14 @@ features = [
 ]
 ]
 
 
 [features]
 [features]
-default = ["panic_hook"]
+default = ["panic_hook", "hydrate"]
 panic_hook = ["console_error_panic_hook"]
 panic_hook = ["console_error_panic_hook"]
 hot-reload = ["dioxus/hot-reload"]
 hot-reload = ["dioxus/hot-reload"]
-
+hydrate = []
 
 
 [dev-dependencies]
 [dev-dependencies]
 dioxus = { path = "../dioxus" }
 dioxus = { path = "../dioxus" }
 wasm-bindgen-test = "0.3.29"
 wasm-bindgen-test = "0.3.29"
 dioxus-ssr = { path = "../ssr" }
 dioxus-ssr = { path = "../ssr" }
 wasm-logger = "0.2.0"
 wasm-logger = "0.2.0"
+dioxus-web = { path = "." }

+ 4 - 4
packages/web/src/dom.rs

@@ -12,7 +12,7 @@ use dioxus_html::event_bubbles;
 use dioxus_interpreter_js::Interpreter;
 use dioxus_interpreter_js::Interpreter;
 use js_sys::Function;
 use js_sys::Function;
 use std::{any::Any, rc::Rc, sync::Arc};
 use std::{any::Any, rc::Rc, sync::Arc};
-use wasm_bindgen::{closure::Closure, JsCast};
+use wasm_bindgen::{closure::Closure, JsCast, JsValue};
 use web_sys::{Document, Element, Event, HtmlElement};
 use web_sys::{Document, Element, Event, HtmlElement};
 
 
 use crate::Config;
 use crate::Config;
@@ -148,11 +148,11 @@ impl WebsysDom {
                 }
                 }
 
 
                 DomEdit::CreateTextNode { text, root } => {
                 DomEdit::CreateTextNode { text, root } => {
-                    let text = serde_wasm_bindgen::to_value(text).unwrap();
+                    let text = JsValue::from_str(text);
                     self.interpreter.CreateTextNode(text, root)
                     self.interpreter.CreateTextNode(text, root)
                 }
                 }
                 DomEdit::SetText { root, text } => {
                 DomEdit::SetText { root, text } => {
-                    let text = serde_wasm_bindgen::to_value(text).unwrap();
+                    let text = JsValue::from_str(text);
                     self.interpreter.SetText(root, text)
                     self.interpreter.SetText(root, text)
                 }
                 }
                 DomEdit::SetAttribute {
                 DomEdit::SetAttribute {
@@ -161,7 +161,7 @@ impl WebsysDom {
                     value,
                     value,
                     ns,
                     ns,
                 } => {
                 } => {
-                    let value = serde_wasm_bindgen::to_value(&value).unwrap();
+                    let value = JsValue::from_str(&value.to_string());
                     self.interpreter.SetAttribute(root, field, value, ns)
                     self.interpreter.SetAttribute(root, field, value, ns)
                 }
                 }
                 DomEdit::CloneNode { id, new_id } => self.interpreter.CloneNode(id, new_id),
                 DomEdit::CloneNode { id, new_id } => self.interpreter.CloneNode(id, new_id),

+ 5 - 1
packages/web/src/lib.rs

@@ -67,6 +67,7 @@ mod cfg;
 mod dom;
 mod dom;
 #[cfg(any(feature = "hot-reload", debug_assertions))]
 #[cfg(any(feature = "hot-reload", debug_assertions))]
 mod hot_reload;
 mod hot_reload;
+#[cfg(feature = "hydrate")]
 mod rehydrate;
 mod rehydrate;
 // mod ric_raf;
 // mod ric_raf;
 mod util;
 mod util;
@@ -164,7 +165,8 @@ where
 pub async fn run_with_props<T: 'static + Send>(root: Component<T>, root_props: T, cfg: Config) {
 pub async fn run_with_props<T: 'static + Send>(root: Component<T>, root_props: T, cfg: Config) {
     let mut dom = VirtualDom::new_with_props(root, root_props);
     let mut dom = VirtualDom::new_with_props(root, root_props);
 
 
-    if cfg!(feature = "panic_hook") && cfg.default_panic_hook {
+    #[cfg(feature = "panic_hook")]
+    if cfg.default_panic_hook {
         console_error_panic_hook::set_once();
         console_error_panic_hook::set_once();
     }
     }
 
 
@@ -194,6 +196,8 @@ pub async fn run_with_props<T: 'static + Send>(root: Component<T>, root_props: T
         // it's a waste to produce edits just to get the vdom loaded
         // it's a waste to produce edits just to get the vdom loaded
         let _ = dom.rebuild();
         let _ = dom.rebuild();
 
 
+        #[cfg(feature = "hydrate")]
+        #[allow(unused_variables)]
         if let Err(err) = websys_dom.rehydrate(&dom) {
         if let Err(err) = websys_dom.rehydrate(&dom) {
             log::error!(
             log::error!(
                 "Rehydration failed {:?}. Rebuild DOM into element from scratch",
                 "Rehydration failed {:?}. Rebuild DOM into element from scratch",