فهرست منبع

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 سال پیش
والد
کامیت
f21c8423eb
6فایلهای تغییر یافته به همراه61 افزوده شده و 28 حذف شده
  1. 5 6
      packages/core/Cargo.toml
  2. 42 12
      packages/core/src/virtual_dom.rs
  3. 1 1
      packages/hooks/Cargo.toml
  4. 4 4
      packages/web/Cargo.toml
  5. 4 4
      packages/web/src/dom.rs
  6. 5 1
      packages/web/src/lib.rs

+ 5 - 6
packages/core/Cargo.toml

@@ -23,9 +23,6 @@ rustc-hash = "1.1.0"
 # Used in diffing
 longest-increasing-subsequence = "0.1.0"
 
-# internall used
-log = { version = "0.4" }
-
 futures-util = { version = "0.3", default-features = false }
 
 smallvec = "1.6"
@@ -34,16 +31,18 @@ slab = "0.4"
 
 futures-channel = "0.3.21"
 
+# internally used
+log = "0.4"
+
 # used for noderefs
 once_cell = "1.8"
 
-indexmap = "1.7"
 
 # Serialize the Edits for use in Webview/Liveview instances
 serde = { version = "1", features = ["derive"], optional = true }
 
 # todo: I want to get rid of this
-backtrace = "0.3"
+backtrace = { version = "0.3" }
 
 # allows cloing trait objects
 dyn-clone = "1.0.9"
@@ -52,4 +51,4 @@ dyn-clone = "1.0.9"
 default = []
 serialize = ["serde"]
 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.
 
+use std::{collections::VecDeque, iter::FromIterator, task::Poll};
+
 use crate::diff::DiffState;
 use crate::innerlude::*;
 use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
 use futures_util::{future::poll_fn, StreamExt};
-use indexmap::IndexSet;
 use rustc_hash::FxHashSet;
-use std::{collections::VecDeque, iter::FromIterator, task::Poll};
 
 /// A virtual node system that progresses user events and diffs UI trees.
 ///
@@ -107,7 +107,8 @@ pub struct VirtualDom {
     scopes: ScopeArena,
 
     pending_messages: VecDeque<SchedulerMsg>,
-    dirty_scopes: IndexSet<ScopeId>,
+    dirty_scopes: Vec<ScopeId>,
+    removed_scopes: FxHashSet<ScopeId>,
 
     channel: (
         UnboundedSender<SchedulerMsg>,
@@ -238,8 +239,9 @@ impl VirtualDom {
             root: ElementId(0),
             scopes,
             channel,
-            dirty_scopes: IndexSet::from_iter([ScopeId(0)]),
+            dirty_scopes: Vec::from_iter([ScopeId(0)]),
             pending_messages: VecDeque::new(),
+            removed_scopes: FxHashSet::default(),
         }
     }
 
@@ -396,11 +398,18 @@ impl VirtualDom {
                 }
             }
             SchedulerMsg::Immediate(s) => {
-                self.dirty_scopes.insert(s);
+                self.mark_dirty_scope(s);
             }
             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))]
@@ -474,6 +483,7 @@ impl VirtualDom {
     pub fn work_with_deadline(&mut self, mut deadline: impl FnMut() -> bool) -> Vec<Mutations> {
         let mut committed_mutations = vec![];
         self.scopes.template_bump.reset();
+        self.removed_scopes.clear();
 
         while !self.dirty_scopes.is_empty() {
             let scopes = &self.scopes;
@@ -492,7 +502,10 @@ impl VirtualDom {
             });
 
             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);
 
                     self.scopes.run_scope(scopeid);
@@ -501,9 +514,8 @@ impl VirtualDom {
 
                     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() {
                         committed_mutations.push(mutations);
@@ -729,6 +741,23 @@ impl VirtualDom {
             })
             .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.
 
 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*
 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]
 dioxus-core = { path = "../../packages/core", version = "^0.2.1" }
 futures-channel = "0.3.21"
-log = { version = "0.4" }
+log = "0.4"
 
 
 [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"
 wasm-bindgen = { version = "0.2.79", features = ["enable-interning"] }
 wasm-bindgen-futures = "0.4.29"
-log = { version = "0.4.14" }
+log = "0.4.14"
 rustc-hash = "1.1.0"
 console_error_panic_hook = { version = "0.1.7", optional = true }
 once_cell = "1.9.0"
@@ -28,7 +28,6 @@ anyhow = "1.0.53"
 gloo-timers = { version = "0.2.3", features = ["futures"] }
 futures-util = "0.3.19"
 smallstr = "0.2.0"
-serde-wasm-bindgen = "0.4.2"
 futures-channel = "0.3.21"
 serde_json = { version = "1.0" }
 
@@ -78,13 +77,14 @@ features = [
 ]
 
 [features]
-default = ["panic_hook"]
+default = ["panic_hook", "hydrate"]
 panic_hook = ["console_error_panic_hook"]
 hot-reload = ["dioxus/hot-reload"]
-
+hydrate = []
 
 [dev-dependencies]
 dioxus = { path = "../dioxus" }
 wasm-bindgen-test = "0.3.29"
 dioxus-ssr = { path = "../ssr" }
 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 js_sys::Function;
 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 crate::Config;
@@ -148,11 +148,11 @@ impl WebsysDom {
                 }
 
                 DomEdit::CreateTextNode { text, root } => {
-                    let text = serde_wasm_bindgen::to_value(text).unwrap();
+                    let text = JsValue::from_str(text);
                     self.interpreter.CreateTextNode(text, root)
                 }
                 DomEdit::SetText { root, text } => {
-                    let text = serde_wasm_bindgen::to_value(text).unwrap();
+                    let text = JsValue::from_str(text);
                     self.interpreter.SetText(root, text)
                 }
                 DomEdit::SetAttribute {
@@ -161,7 +161,7 @@ impl WebsysDom {
                     value,
                     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)
                 }
                 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;
 #[cfg(any(feature = "hot-reload", debug_assertions))]
 mod hot_reload;
+#[cfg(feature = "hydrate")]
 mod rehydrate;
 // mod ric_raf;
 mod util;
@@ -164,7 +165,8 @@ where
 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);
 
-    if cfg!(feature = "panic_hook") && cfg.default_panic_hook {
+    #[cfg(feature = "panic_hook")]
+    if cfg.default_panic_hook {
         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
         let _ = dom.rebuild();
 
+        #[cfg(feature = "hydrate")]
+        #[allow(unused_variables)]
         if let Err(err) = websys_dom.rehydrate(&dom) {
             log::error!(
                 "Rehydration failed {:?}. Rebuild DOM into element from scratch",