Quellcode durchsuchen

Merge branch 'upstream' into simplify-native-core

Evan Almloff vor 2 Jahren
Ursprung
Commit
5087429cff

+ 2 - 1
packages/core/src/lib.rs

@@ -73,7 +73,8 @@ pub use crate::innerlude::{
     fc_to_builder, AnyValue, Attribute, AttributeValue, BorrowedAttributeValue, CapturedError,
     Component, DynamicNode, Element, ElementId, Event, Fragment, IntoDynNode, LazyNodes, Mutation,
     Mutations, Properties, RenderReturn, Scope, ScopeId, ScopeState, Scoped, SuspenseContext,
-    TaskId, Template, TemplateAttribute, TemplateNode, VComponent, VNode, VText, VirtualDom,
+    TaskId, Template, TemplateAttribute, TemplateNode, VComponent, VNode, VPlaceholder, VText,
+    VirtualDom,
 };
 
 /// The purpose of this module is to alleviate imports of many common types

+ 4 - 2
packages/core/src/scopes.rs

@@ -477,8 +477,10 @@ impl<'src> ScopeState {
         let mut props = self.borrowed_props.borrow_mut();
         for node in element.dynamic_nodes {
             if let DynamicNode::Component(comp) = node {
-                let unbounded = unsafe { std::mem::transmute(comp as *const VComponent) };
-                props.push(unbounded);
+                if !comp.static_props {
+                    let unbounded = unsafe { std::mem::transmute(comp as *const VComponent) };
+                    props.push(unbounded);
+                }
             }
         }
 

+ 3 - 0
packages/hooks/src/lib.rs

@@ -44,3 +44,6 @@ pub use useeffect::*;
 
 mod usecallback;
 pub use usecallback::*;
+
+mod usememo;
+pub use usememo::*;

+ 37 - 0
packages/hooks/src/usememo.rs

@@ -0,0 +1,37 @@
+use dioxus_core::ScopeState;
+
+use crate::UseFutureDep;
+
+/// A hook that provides a callback that executes after the hooks have been applied
+///
+/// Whenever the hooks dependencies change, the callback will be re-evaluated.
+///
+/// - dependencies: a tuple of references to values that are PartialEq + Clone
+///
+/// ## Examples
+///
+/// ```rust, ignore
+///
+/// #[inline_props]
+/// fn app(cx: Scope, name: &str) -> Element {
+///     use_memo(cx, (name,), |(name,)| {
+///         expensive_computation(name);
+///     }))
+/// }
+/// ```
+pub fn use_memo<T, D>(cx: &ScopeState, dependencies: D, callback: impl FnOnce(D::Out) -> T) -> &T
+where
+    T: 'static,
+    D: UseFutureDep,
+{
+    let value = cx.use_hook(|| None);
+
+    let dependancies_vec = cx.use_hook(Vec::new);
+
+    if dependencies.clone().apply(dependancies_vec) || value.is_none() {
+        // Create the new value
+        *value = Some(callback(dependencies.out()));
+    }
+
+    value.as_ref().unwrap()
+}

+ 2 - 2
packages/liveview/Cargo.toml

@@ -18,7 +18,7 @@ futures-util = { version = "0.3.25", default-features = false, features = [
     "sink",
 ] }
 futures-channel = { version = "0.3.25", features = ["sink"] }
-tokio = { version = "1.23.0", features = ["time"] }
+tokio = { version = "1.22.0", features = ["time"] }
 tokio-stream = { version = "0.1.11", features = ["net"] }
 tokio-util = { version = "0.7.4", features = ["rt"] }
 serde = { version = "1.0.151", features = ["derive"] }
@@ -44,7 +44,7 @@ salvo = { version = "0.37.7", optional = true, features = ["ws"] }
 
 [dev-dependencies]
 pretty_env_logger = { version = "0.4.0" }
-tokio = { version = "1.23.0", features = ["full"] }
+tokio = { version = "1.22.0", features = ["full"] }
 dioxus = { path = "../dioxus", version = "0.3.0" }
 warp = "0.3.3"
 axum = { version = "0.6.1", features = ["ws"] }

+ 1 - 1
packages/web/Cargo.toml

@@ -88,4 +88,4 @@ dioxus = { path = "../dioxus", version = "0.3.0" }
 wasm-bindgen-test = "0.3.29"
 dioxus-ssr = { path = "../ssr", version = "0.3.0"}
 wasm-logger = "0.2.0"
-dioxus-web = { path = "." }
+dioxus-web = { path = ".", features = ["hydrate"] }

+ 3 - 0
packages/web/src/cfg.rs

@@ -8,6 +8,7 @@
 /// dioxus_web::launch(App, Config::new().hydrate(true).root_name("myroot"))
 /// ```
 pub struct Config {
+    #[cfg(feature = "hydrate")]
     pub(crate) hydrate: bool,
     pub(crate) rootname: String,
     pub(crate) cached_strings: Vec<String>,
@@ -17,6 +18,7 @@ pub struct Config {
 impl Default for Config {
     fn default() -> Self {
         Self {
+            #[cfg(feature = "hydrate")]
             hydrate: false,
             rootname: "main".to_string(),
             cached_strings: Vec::new(),
@@ -33,6 +35,7 @@ impl Config {
         Self::default()
     }
 
+    #[cfg(feature = "hydrate")]
     /// Enable SSR hydration
     ///
     /// This enables Dioxus to pick up work from a pre-renderd HTML file. Hydration will completely skip over any async

+ 8 - 2
packages/web/src/dom.rs

@@ -22,9 +22,11 @@ use crate::Config;
 
 pub struct WebsysDom {
     document: Document,
+    #[allow(dead_code)]
+    pub(crate) root: Element,
     templates: FxHashMap<String, u32>,
     max_template_id: u32,
-    interpreter: Channel,
+    pub(crate) interpreter: Channel,
 }
 
 pub struct UiEvent {
@@ -72,10 +74,14 @@ impl WebsysDom {
                 }
             }));
 
-        dioxus_interpreter_js::initilize(root.unchecked_into(), handler.as_ref().unchecked_ref());
+        dioxus_interpreter_js::initilize(
+            root.clone().unchecked_into(),
+            handler.as_ref().unchecked_ref(),
+        );
         handler.forget();
         Self {
             document,
+            root,
             interpreter,
             templates: FxHashMap::default(),
             max_template_id: 0,

+ 30 - 32
packages/web/src/lib.rs

@@ -62,6 +62,8 @@ mod cache;
 mod cfg;
 mod dom;
 mod hot_reload;
+#[cfg(feature = "hydrate")]
+mod rehydrate;
 mod util;
 
 // Currently disabled since it actually slows down immediate rendering
@@ -179,17 +181,40 @@ pub async fn run_with_props<T: 'static>(root: fn(Scope<T>) -> Element, root_prop
         wasm_bindgen::intern(s);
     }
 
-    let _should_hydrate = cfg.hydrate;
-
     let (tx, mut rx) = futures_channel::mpsc::unbounded();
 
+    #[cfg(feature = "hydrate")]
+    let should_hydrate = cfg.hydrate;
+    #[cfg(not(feature = "hydrate"))]
+    let should_hydrate = false;
+
     let mut websys_dom = dom::WebsysDom::new(cfg, tx);
 
     log::info!("rebuilding app");
 
-    // if should_hydrate {
-    // } else {
-    {
+    if should_hydrate {
+        #[cfg(feature = "hydrate")]
+        {
+            // todo: we need to split rebuild and initialize into two phases
+            // it's a waste to produce edits just to get the vdom loaded
+
+            let templates = dom.rebuild().templates;
+            websys_dom.load_templates(&templates);
+
+            if let Err(err) = websys_dom.rehydrate(&dom) {
+                log::error!(
+                    "Rehydration failed {:?}. Rebuild DOM into element from scratch",
+                    &err
+                );
+                websys_dom.root.set_text_content(None);
+
+                let edits = dom.rebuild();
+
+                websys_dom.load_templates(&edits.templates);
+                websys_dom.apply_edits(edits.edits);
+            }
+        }
+    } else {
         let edits = dom.rebuild();
 
         websys_dom.load_templates(&edits.templates);
@@ -249,30 +274,3 @@ pub async fn run_with_props<T: 'static>(root: fn(Scope<T>) -> Element, root_prop
         websys_dom.apply_edits(edits.edits);
     }
 }
-
-// if should_hydrate {
-//     // todo: we need to split rebuild and initialize into two phases
-//     // 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",
-//             &err
-//         );
-
-//         websys_dom.root.set_text_content(None);
-
-//         // errrrr we should split rebuild into two phases
-//         // one that initializes things and one that produces edits
-//         let edits = dom.rebuild();
-
-//         websys_dom.apply_edits(edits.edits);
-//     }
-// } else {
-//     let edits = dom.rebuild();
-//     websys_dom.apply_edits(edits.template_mutations);
-//     websys_dom.apply_edits(edits.edits);
-// }

+ 235 - 131
packages/web/src/rehydrate.rs

@@ -1,9 +1,13 @@
 use crate::dom::WebsysDom;
-use dioxus_core::{VNode, VirtualDom};
+use dioxus_core::{
+    AttributeValue, DynamicNode, ElementId, ScopeState, TemplateNode, VNode, VPlaceholder, VText,
+    VirtualDom,
+};
+use dioxus_html::event_bubbles;
 use wasm_bindgen::JsCast;
-use web_sys::{Comment, Element, Node, Text};
+use web_sys::{Comment, Node};
 
-#[derive(Debug)]
+#[derive(Debug, Copy, Clone)]
 pub enum RehydrationError {
     NodeTypeMismatch,
     NodeNotFound,
@@ -11,163 +15,263 @@ pub enum RehydrationError {
 }
 use RehydrationError::*;
 
+fn set_node(hydrated: &mut Vec<bool>, id: ElementId, node: Node) {
+    let idx = id.0;
+    if idx >= hydrated.len() {
+        hydrated.resize(idx + 1, false);
+    }
+    if !hydrated[idx] {
+        dioxus_interpreter_js::set_node(idx as u32, node);
+        hydrated[idx] = true;
+    }
+}
+
 impl WebsysDom {
     // we're streaming in patches, but the nodes already exist
     // so we're just going to write the correct IDs to the node and load them in
     pub fn rehydrate(&mut self, dom: &VirtualDom) -> Result<(), RehydrationError> {
-        let root = self
+        let mut root = self
             .root
             .clone()
             .dyn_into::<Node>()
-            .map_err(|_| NodeTypeMismatch)?;
+            .map_err(|_| NodeTypeMismatch)?
+            .first_child()
+            .ok_or(NodeNotFound);
 
         let root_scope = dom.base_scope();
-        let root_node = root_scope.root_node();
-
-        let mut nodes = vec![root];
-        let mut counter = vec![0];
-
-        let mut last_node_was_text = false;
-
-        todo!()
-        // // Recursively rehydrate the dom from the VirtualDom
-        // self.rehydrate_single(
-        //     &mut nodes,
-        //     &mut counter,
-        //     dom,
-        //     root_node,
-        //     &mut last_node_was_text,
-        // )
+
+        let mut hydrated = vec![true];
+
+        let mut last_node_was_static_text = false;
+
+        // Recursively rehydrate the dom from the VirtualDom
+        self.rehydrate_scope(
+            root_scope,
+            &mut root,
+            &mut hydrated,
+            dom,
+            &mut last_node_was_static_text,
+        )?;
+
+        self.interpreter.flush();
+        Ok(())
     }
 
-    fn rehydrate_single(
+    fn rehydrate_scope(
         &mut self,
-        nodes: &mut Vec<Node>,
-        place: &mut Vec<u32>,
+        scope: &ScopeState,
+        current_child: &mut Result<Node, RehydrationError>,
+        hydrated: &mut Vec<bool>,
         dom: &VirtualDom,
-        node: &VNode,
-        last_node_was_text: &mut bool,
+        last_node_was_static_text: &mut bool,
     ) -> Result<(), RehydrationError> {
-        match node {
-            VNode::Text(t) => {
-                let node_id = t.id.get().ok_or(VNodeNotInitialized)?;
+        let vnode = match scope.root_node() {
+            dioxus_core::RenderReturn::Ready(ready) => ready,
+            _ => return Err(VNodeNotInitialized),
+        };
+        self.rehydrate_vnode(
+            current_child,
+            hydrated,
+            dom,
+            vnode,
+            last_node_was_static_text,
+        )
+    }
 
-                let cur_place = place.last_mut().unwrap();
+    fn rehydrate_vnode(
+        &mut self,
+        current_child: &mut Result<Node, RehydrationError>,
+        hydrated: &mut Vec<bool>,
+        dom: &VirtualDom,
+        vnode: &VNode,
+        last_node_was_static_text: &mut bool,
+    ) -> Result<(), RehydrationError> {
+        for (i, root) in vnode.template.get().roots.iter().enumerate() {
+            // make sure we set the root node ids even if the node is not dynamic
+            set_node(
+                hydrated,
+                vnode.root_ids.get(i).ok_or(VNodeNotInitialized)?,
+                current_child.clone()?,
+            );
+
+            self.rehydrate_template_node(
+                current_child,
+                hydrated,
+                dom,
+                vnode,
+                root,
+                last_node_was_static_text,
+            )?;
+        }
+        Ok(())
+    }
 
-                // skip over the comment element
-                if *last_node_was_text {
-                    if cfg!(debug_assertions) {
-                        let node = nodes.last().unwrap().child_nodes().get(*cur_place).unwrap();
-                        let node_text = node.dyn_into::<Comment>().unwrap();
-                        assert_eq!(node_text.data(), "spacer");
+    fn rehydrate_template_node(
+        &mut self,
+        current_child: &mut Result<Node, RehydrationError>,
+        hydrated: &mut Vec<bool>,
+        dom: &VirtualDom,
+        vnode: &VNode,
+        node: &TemplateNode,
+        last_node_was_static_text: &mut bool,
+    ) -> Result<(), RehydrationError> {
+        match node {
+            TemplateNode::Element {
+                children, attrs, ..
+            } => {
+                let mut mounted_id = None;
+                for attr in *attrs {
+                    if let dioxus_core::TemplateAttribute::Dynamic { id } = attr {
+                        let attribute = &vnode.dynamic_attrs[*id];
+                        let value = &attribute.value;
+                        let id = attribute.mounted_element.get();
+                        mounted_id = Some(id);
+                        let name = attribute.name;
+                        if let AttributeValue::Listener(_) = value {
+                            self.interpreter.new_event_listener(
+                                &name[2..],
+                                id.0 as u32,
+                                event_bubbles(name) as u8,
+                            );
+                        }
                     }
-                    *cur_place += 1;
                 }
-
-                let node = nodes
-                    .last()
-                    .unwrap()
-                    .child_nodes()
-                    .get(*cur_place)
-                    .ok_or(NodeNotFound)?;
-
-                let _text_el = node.dyn_ref::<Text>().ok_or(NodeTypeMismatch)?;
-
-                // in debug we make sure the text is the same
-                if cfg!(debug_assertions) {
-                    let contents = _text_el.node_value().unwrap();
-                    assert_eq!(t.text, contents);
+                if let Some(id) = mounted_id {
+                    set_node(hydrated, id, current_child.clone()?);
                 }
-
-                *last_node_was_text = true;
-
-                self.interpreter.SetNode(node_id.0, node);
-
-                *cur_place += 1;
-            }
-
-            VNode::Element(vel) => {
-                let node_id = vel.id.get().ok_or(VNodeNotInitialized)?;
-
-                let cur_place = place.last_mut().unwrap();
-
-                let node = nodes.last().unwrap().child_nodes().get(*cur_place).unwrap();
-
-                self.interpreter.SetNode(node_id.0, node.clone());
-
-                *cur_place += 1;
-
-                nodes.push(node.clone());
-
-                place.push(0);
-
-                // we cant have the last node be text
-                let mut last_node_was_text = false;
-                for child in vel.children {
-                    self.rehydrate_single(nodes, place, dom, child, &mut last_node_was_text)?;
+                if !children.is_empty() {
+                    let mut children_current_child = current_child
+                        .as_mut()
+                        .map_err(|e| *e)?
+                        .first_child()
+                        .ok_or(NodeNotFound)?
+                        .dyn_into::<Node>()
+                        .map_err(|_| NodeTypeMismatch);
+                    for child in *children {
+                        self.rehydrate_template_node(
+                            &mut children_current_child,
+                            hydrated,
+                            dom,
+                            vnode,
+                            child,
+                            last_node_was_static_text,
+                        )?;
+                    }
                 }
-
-                for listener in vel.listeners {
-                    let id = listener.mounted_node.get().unwrap();
-                    self.interpreter.NewEventListener(
-                        listener.event,
-                        Some(id.as_u64()),
-                        self.handler.as_ref().unchecked_ref(),
-                        event_bubbles(listener.event),
-                    );
+                *current_child = current_child
+                    .as_mut()
+                    .map_err(|e| *e)?
+                    .next_sibling()
+                    .ok_or(NodeNotFound);
+                *last_node_was_static_text = false;
+            }
+            TemplateNode::Text { .. } => {
+                // if the last node was static text, it got merged with this one
+                if !*last_node_was_static_text {
+                    *current_child = current_child
+                        .as_mut()
+                        .map_err(|e| *e)?
+                        .next_sibling()
+                        .ok_or(NodeNotFound);
                 }
+                *last_node_was_static_text = true;
+            }
+            TemplateNode::Dynamic { id } | TemplateNode::DynamicText { id } => {
+                self.rehydrate_dynamic_node(
+                    current_child,
+                    hydrated,
+                    dom,
+                    &vnode.dynamic_nodes[*id],
+                    last_node_was_static_text,
+                )?;
+            }
+        }
+        Ok(())
+    }
 
-                if !vel.listeners.is_empty() {
-                    use smallstr::SmallString;
-                    use std::fmt::Write;
-
-                    // 8 digits is enough, yes?
-                    // 12 million nodes in one page?
-                    let mut s: SmallString<[u8; 8]> = smallstr::SmallString::new();
-                    write!(s, "{}", node_id).unwrap();
-
-                    node.dyn_ref::<Element>()
-                        .unwrap()
-                        .set_attribute("dioxus-id", s.as_str())
-                        .unwrap();
+    fn rehydrate_dynamic_node(
+        &mut self,
+        current_child: &mut Result<Node, RehydrationError>,
+        hydrated: &mut Vec<bool>,
+        dom: &VirtualDom,
+        dynamic: &DynamicNode,
+        last_node_was_static_text: &mut bool,
+    ) -> Result<(), RehydrationError> {
+        match dynamic {
+            dioxus_core::DynamicNode::Text(VText { id, .. }) => {
+                // skip comment separator before node
+                if cfg!(debug_assertions) {
+                    assert!(current_child
+                        .as_mut()
+                        .map_err(|e| *e)?
+                        .has_type::<Comment>());
                 }
-
-                place.pop();
-                nodes.pop();
-
+                *current_child = current_child
+                    .as_mut()
+                    .map_err(|e| *e)?
+                    .next_sibling()
+                    .ok_or(NodeNotFound);
+
+                set_node(
+                    hydrated,
+                    id.get().ok_or(VNodeNotInitialized)?,
+                    current_child.clone()?,
+                );
+                *current_child = current_child
+                    .as_mut()
+                    .map_err(|e| *e)?
+                    .next_sibling()
+                    .ok_or(NodeNotFound);
+
+                // skip comment separator after node
                 if cfg!(debug_assertions) {
-                    let el = node.dyn_ref::<Element>().unwrap();
-                    let name = el.tag_name().to_lowercase();
-                    assert_eq!(name, vel.tag);
+                    assert!(current_child
+                        .as_mut()
+                        .map_err(|e| *e)?
+                        .has_type::<Comment>());
                 }
-            }
-
-            VNode::Placeholder(el) => {
-                let node_id = el.id.get().ok_or(VNodeNotInitialized)?;
-
-                let cur_place = place.last_mut().unwrap();
-                let node = nodes.last().unwrap().child_nodes().get(*cur_place).unwrap();
+                *current_child = current_child
+                    .as_mut()
+                    .map_err(|e| *e)?
+                    .next_sibling()
+                    .ok_or(NodeNotFound);
 
-                self.interpreter.SetNode(node_id.0, node);
-
-                // self.nodes[node_id.0] = Some(node);
-
-                *cur_place += 1;
+                *last_node_was_static_text = false;
             }
-
-            VNode::Fragment(el) => {
-                for el in el.children {
-                    self.rehydrate_single(nodes, place, dom, el, last_node_was_text)?;
-                }
+            dioxus_core::DynamicNode::Placeholder(VPlaceholder { id, .. }) => {
+                set_node(
+                    hydrated,
+                    id.get().ok_or(VNodeNotInitialized)?,
+                    current_child.clone()?,
+                );
+                *current_child = current_child
+                    .as_mut()
+                    .map_err(|e| *e)?
+                    .next_sibling()
+                    .ok_or(NodeNotFound);
+                *last_node_was_static_text = false;
             }
-
-            VNode::Component(el) => {
-                let scope = dom.get_scope(el.scope.get().unwrap()).unwrap();
-                let node = scope.root_node();
-                todo!()
-                // self.rehydrate_single(nodes, place, dom, node, last_node_was_text)?;
+            dioxus_core::DynamicNode::Component(comp) => {
+                let scope = comp.scope.get().ok_or(VNodeNotInitialized)?;
+                self.rehydrate_scope(
+                    dom.get_scope(scope).unwrap(),
+                    current_child,
+                    hydrated,
+                    dom,
+                    last_node_was_static_text,
+                )?;
+            }
+            dioxus_core::DynamicNode::Fragment(fragment) => {
+                for vnode in *fragment {
+                    self.rehydrate_vnode(
+                        current_child,
+                        hydrated,
+                        dom,
+                        vnode,
+                        last_node_was_static_text,
+                    )?;
+                }
             }
-            VNode::TemplateRef(_) => todo!(),
         }
         Ok(())
     }