//! # Virtual DOM Implementation for Rust //! //! This module provides the primary mechanics to create a hook-based, concurrent VDOM for Rust. use crate::{ any_props::AnyProps, arena::ElementId, innerlude::{ DirtyScope, ElementRef, ErrorBoundary, NoOpMutations, SchedulerMsg, ScopeState, VNodeMount, VProps, WriteMutations, }, nodes::RenderReturn, nodes::{Template, TemplateId}, runtime::{Runtime, RuntimeGuard}, scopes::ScopeId, AttributeValue, ComponentFunction, Element, Event, Mutations, }; use futures_util::StreamExt; use rustc_hash::{FxHashMap, FxHashSet}; use slab::Slab; use std::{any::Any, collections::BTreeSet, rc::Rc}; /// A virtual node system that progresses user events and diffs UI trees. /// /// ## Guide /// /// Components are defined as simple functions that take [`crate::properties::Properties`] and return an [`Element`]. /// /// ```rust /// # use dioxus::prelude::*; /// /// #[derive(Props, PartialEq, Clone)] /// struct AppProps { /// title: String /// } /// /// fn app(cx: AppProps) -> Element { /// rsx!( /// div {"hello, {cx.title}"} /// ) /// } /// ``` /// /// Components may be composed to make complex apps. /// /// ```rust /// # #![allow(unused)] /// # use dioxus::prelude::*; /// /// # #[derive(Props, PartialEq, Clone)] /// # struct AppProps { /// # title: String /// # } /// /// static ROUTES: &str = ""; /// /// #[component] /// fn app(cx: AppProps) -> Element { /// rsx!( /// NavBar { routes: ROUTES } /// Title { "{cx.title}" } /// Footer {} /// ) /// } /// /// #[component] /// fn NavBar( routes: &'static str) -> Element { /// rsx! { /// div { "Routes: {routes}" } /// } /// } /// /// #[component] /// fn Footer() -> Element { /// rsx! { div { "Footer" } } /// } /// /// #[component] /// fn Title( children: Element) -> Element { /// rsx! { /// div { id: "title", {children} } /// } /// } /// ``` /// /// To start an app, create a [`VirtualDom`] and call [`VirtualDom::rebuild`] to get the list of edits required to /// draw the UI. /// /// ```rust /// # use dioxus::prelude::*; /// # fn app() -> Element { rsx! { div {} } } /// /// let mut vdom = VirtualDom::new(app); /// let edits = vdom.rebuild_to_vec(); /// ``` /// /// To call listeners inside the VirtualDom, call [`VirtualDom::handle_event`] with the appropriate event data. /// /// ```rust, ignore /// vdom.handle_event(event); /// ``` /// /// While no events are ready, call [`VirtualDom::wait_for_work`] to poll any futures inside the VirtualDom. /// /// ```rust, ignore /// vdom.wait_for_work().await; /// ``` /// /// Once work is ready, call [`VirtualDom::render_with_deadline`] to compute the differences between the previous and /// current UI trees. This will return a [`Mutations`] object that contains Edits, Effects, and NodeRefs that need to be /// handled by the renderer. /// /// ```rust, ignore /// let mutations = vdom.work_with_deadline(tokio::time::sleep(Duration::from_millis(100))); /// /// for edit in mutations.edits { /// real_dom.apply(edit); /// } /// ``` /// /// To not wait for suspense while diffing the VirtualDom, call [`VirtualDom::render_immediate`] or pass an immediately /// ready future to [`VirtualDom::render_with_deadline`]. /// /// /// ## Building an event loop around Dioxus: /// /// Putting everything together, you can build an event loop around Dioxus by using the methods outlined above. /// ```rust, ignore /// #[component] /// fn app() -> Element { /// rsx! { /// div { "Hello World" } /// } /// } /// /// let dom = VirtualDom::new(app); /// /// real_dom.apply(dom.rebuild()); /// /// loop { /// select! { /// _ = dom.wait_for_work() => {} /// evt = real_dom.wait_for_event() => dom.handle_event(evt), /// } /// /// real_dom.apply(dom.render_immediate()); /// } /// ``` /// /// ## Waiting for suspense /// /// Because Dioxus supports suspense, you can use it for server-side rendering, static site generation, and other usecases /// where waiting on portions of the UI to finish rendering is important. To wait for suspense, use the /// [`VirtualDom::render_with_deadline`] method: /// /// ```rust, ignore /// let dom = VirtualDom::new(app); /// /// let deadline = tokio::time::sleep(Duration::from_millis(100)); /// let edits = dom.render_with_deadline(deadline).await; /// ``` /// /// ## Use with streaming /// /// If not all rendering is done by the deadline, it might be worthwhile to stream the rest later. To do this, we /// suggest rendering with a deadline, and then looping between [`VirtualDom::wait_for_work`] and render_immediate until /// no suspended work is left. /// /// ```rust, ignore /// let dom = VirtualDom::new(app); /// /// let deadline = tokio::time::sleep(Duration::from_millis(20)); /// let edits = dom.render_with_deadline(deadline).await; /// /// real_dom.apply(edits); /// /// while dom.has_suspended_work() { /// dom.wait_for_work().await; /// real_dom.apply(dom.render_immediate()); /// } /// ``` pub struct VirtualDom { pub(crate) scopes: Slab, pub(crate) dirty_scopes: BTreeSet, // Maps a template path to a map of byte indexes to templates pub(crate) templates: FxHashMap>, // Templates changes that are queued for the next render pub(crate) queued_templates: Vec