From 694bef0d93c9371c6de36340462cd43383cd0509 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 18 Jan 2024 10:47:10 -0600 Subject: [PATCH] fix hydration --- packages/fullstack/src/config.rs | 2 +- packages/fullstack/src/launch.rs | 1 + .../interpreter/src/sledgehammer_bindings.rs | 4 -- packages/web/src/cfg.rs | 2 - packages/web/src/launch.rs | 15 +++-- packages/web/src/lib.rs | 10 +-- packages/web/src/mutations.rs | 2 +- packages/web/src/rehydrate.rs | 61 +++++++++++++++++++ 8 files changed, 76 insertions(+), 21 deletions(-) diff --git a/packages/fullstack/src/config.rs b/packages/fullstack/src/config.rs index 8107b1ace..fe9efe149 100644 --- a/packages/fullstack/src/config.rs +++ b/packages/fullstack/src/config.rs @@ -111,7 +111,7 @@ impl Config { #[cfg(not(feature = "ssr"))] { let cfg = self.web_cfg.hydrate(true); - dioxus_web::run( + dioxus_web::launch::launch_virtual_dom( // TODO: this should pull the props from the document build_virtual_dom(), cfg, diff --git a/packages/fullstack/src/launch.rs b/packages/fullstack/src/launch.rs index 21092163d..2c77c1e3f 100644 --- a/packages/fullstack/src/launch.rs +++ b/packages/fullstack/src/launch.rs @@ -1,5 +1,6 @@ //! This module contains the `launch` function, which is the main entry point for dioxus fullstack +use core::panic; use std::any::Any; use dioxus_lib::prelude::{Element, VirtualDom}; diff --git a/packages/interpreter/src/sledgehammer_bindings.rs b/packages/interpreter/src/sledgehammer_bindings.rs index 23831f3e6..2e50112e2 100644 --- a/packages/interpreter/src/sledgehammer_bindings.rs +++ b/packages/interpreter/src/sledgehammer_bindings.rs @@ -87,7 +87,6 @@ mod js { templates[tmpl_id] = nodes; } export function hydrate(ids) { - console.log("hydrating", ids); const hydrateNodes = document.querySelectorAll('[data-node-hydration]'); for (let i = 0; i < hydrateNodes.length; i++) { const hydrateNode = hydrateNodes[i]; @@ -95,7 +94,6 @@ mod js { const split = hydration.split(','); const id = ids[parseInt(split[0])]; nodes[id] = hydrateNode; - console.log("hydrating node", hydrateNode, id); if (split.length > 1) { hydrateNode.listening = split.length - 1; hydrateNode.setAttribute('data-dioxus-id', id); @@ -104,7 +102,6 @@ mod js { const split2 = listener.split(':'); const event_name = split2[0]; const bubbles = split2[1] === '1'; - console.log("hydrating listener", event_name, bubbles); listeners.create(event_name, hydrateNode, bubbles); } } @@ -118,7 +115,6 @@ mod js { const id = currentNode.textContent; const split = id.split('node-id'); if (split.length > 1) { - console.log("hydrating text", currentNode.nextSibling, id); nodes[ids[parseInt(split[1])]] = currentNode.nextSibling; } currentNode = treeWalker.nextNode(); diff --git a/packages/web/src/cfg.rs b/packages/web/src/cfg.rs index afdf7e2e2..3f9961378 100644 --- a/packages/web/src/cfg.rs +++ b/packages/web/src/cfg.rs @@ -8,7 +8,6 @@ /// 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) default_panic_hook: bool, @@ -17,7 +16,6 @@ pub struct Config { impl Default for Config { fn default() -> Self { Self { - #[cfg(feature = "hydrate")] hydrate: false, rootname: "main".to_string(), default_panic_hook: true, diff --git a/packages/web/src/launch.rs b/packages/web/src/launch.rs index 0561a701d..760bcf87b 100644 --- a/packages/web/src/launch.rs +++ b/packages/web/src/launch.rs @@ -12,11 +12,18 @@ pub fn launch( contexts: Vec Box>>, platform_config: Config, ) { + let mut vdom = VirtualDom::new(root); + for context in contexts { + vdom.insert_any_root_context(context()); + } + launch_virtual_dom(vdom, platform_config); +} + +/// Launch the web application with a prebuild virtual dom +/// +/// For a builder API, see `LaunchBuilder` defined in the `dioxus` crate. +pub fn launch_virtual_dom(vdom: VirtualDom, platform_config: Config) { wasm_bindgen_futures::spawn_local(async move { - let mut vdom = VirtualDom::new(root); - for context in contexts { - vdom.insert_any_root_context(context()); - } crate::run(vdom, platform_config).await; }); } diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index bb459f198..7e365d89b 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -121,10 +121,7 @@ pub async fn run(virtual_dom: VirtualDom, web_config: Config) { let (tx, mut rx) = futures_channel::mpsc::unbounded(); - #[cfg(feature = "hydrate")] let should_hydrate = web_config.hydrate; - #[cfg(not(feature = "hydrate"))] - let should_hydrate = false; let mut websys_dom = dom::WebsysDom::new(web_config, tx); @@ -133,13 +130,8 @@ pub async fn run(virtual_dom: VirtualDom, web_config: Config) { 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 + dom.rebuild(&mut crate::rehydrate::OnlyWriteTemplates(&mut websys_dom)); - { - dom.rebuild(&mut websys_dom); - websys_dom.flush_edits(); - } if let Err(err) = websys_dom.rehydrate(&dom) { tracing::error!("Rehydration failed. {:?}", err); tracing::error!("Rebuild DOM into element from scratch"); diff --git a/packages/web/src/mutations.rs b/packages/web/src/mutations.rs index 3a5ac4334..feadb2995 100644 --- a/packages/web/src/mutations.rs +++ b/packages/web/src/mutations.rs @@ -12,7 +12,7 @@ use wasm_bindgen::JsCast; use wasm_bindgen::JsValue; impl WebsysDom { - fn create_template_node(&self, v: &TemplateNode) -> web_sys::Node { + pub(crate) fn create_template_node(&self, v: &TemplateNode) -> web_sys::Node { use TemplateNode::*; match v { Element { diff --git a/packages/web/src/rehydrate.rs b/packages/web/src/rehydrate.rs index 9ed427cfa..bf6adead7 100644 --- a/packages/web/src/rehydrate.rs +++ b/packages/web/src/rehydrate.rs @@ -1,6 +1,9 @@ use crate::dom::WebsysDom; +use dioxus_core::prelude::*; use dioxus_core::AttributeValue; +use dioxus_core::WriteMutations; use dioxus_core::{DynamicNode, ElementId, ScopeState, TemplateNode, VNode, VirtualDom}; +use dioxus_interpreter_js::save_template; #[derive(Debug)] pub enum RehydrationError { @@ -149,3 +152,61 @@ impl WebsysDom { Ok(()) } } + +/// During rehydration, we don't want to actually write anything to the DOM, but we do need to store any templates that were created. This struct is used to only write templates to the DOM. +pub(crate) struct OnlyWriteTemplates<'a>(pub &'a mut WebsysDom); + +impl WriteMutations for OnlyWriteTemplates<'_> { + fn register_template(&mut self, template: Template) { + let mut roots = vec![]; + + for root in template.roots { + roots.push(self.0.create_template_node(root)) + } + + self.0 + .templates + .insert(template.name.to_owned(), self.0.max_template_id); + save_template(roots, self.0.max_template_id); + self.0.max_template_id += 1 + } + + fn append_children(&mut self, _: ElementId, _: usize) {} + + fn assign_node_id(&mut self, _: &'static [u8], _: ElementId) {} + + fn create_placeholder(&mut self, _: ElementId) {} + + fn create_text_node(&mut self, _: &str, _: ElementId) {} + + fn hydrate_text_node(&mut self, _: &'static [u8], _: &str, _: ElementId) {} + + fn load_template(&mut self, _: &'static str, _: usize, _: ElementId) {} + + fn replace_node_with(&mut self, _: ElementId, _: usize) {} + + fn replace_placeholder_with_nodes(&mut self, _: &'static [u8], _: usize) {} + + fn insert_nodes_after(&mut self, _: ElementId, _: usize) {} + + fn insert_nodes_before(&mut self, _: ElementId, _: usize) {} + + fn set_attribute( + &mut self, + _: &'static str, + _: Option<&'static str>, + _: &AttributeValue, + _: ElementId, + ) { + } + + fn set_node_text(&mut self, _: &str, _: ElementId) {} + + fn create_event_listener(&mut self, _: &'static str, _: ElementId) {} + + fn remove_event_listener(&mut self, _: &'static str, _: ElementId) {} + + fn remove_node(&mut self, _: ElementId) {} + + fn push_root(&mut self, _: ElementId) {} +}