//! # Virtual DOM Implementation for Rust //! //! This module provides the primary mechanics to create a hook-based, concurrent VDOM for Rust. use crate::{ any_props::{BoxedAnyProps, VProps}, arena::ElementId, innerlude::{ DirtyScope, ElementRef, ErrorBoundary, NoOpMutations, Scheduler, SchedulerMsg, WriteMutations, }, nodes::RenderReturn, nodes::{Template, TemplateId}, runtime::{Runtime, RuntimeGuard}, scopes::{ScopeId, ScopeState}, AttributeValue, Element, Event, }; use futures_util::{pin_mut, StreamExt}; use rustc_hash::{FxHashMap, FxHashSet}; use slab::Slab; use std::{any::Any, cell::Cell, collections::BTreeSet, future::Future, rc::Rc, sync::Arc}; /// A virtual node system that progresses user events and diffs UI trees. /// /// ## Guide /// /// Components are defined as simple functions that take [`Scope`] and return an [`Element`]. /// /// ```rust /// # use dioxus::prelude::*; /// /// #[derive(Props, PartialEq)] /// struct AppProps { /// title: String /// } /// /// fn App(cx: Scope) -> Element { /// cx.render(rsx!( /// div {"hello, {cx.props.title}"} /// )) /// } /// ``` /// /// Components may be composed to make complex apps. /// /// ```rust /// # #![allow(unused)] /// # use dioxus::prelude::*; /// /// # #[derive(Props, PartialEq)] /// # struct AppProps { /// # title: String /// # } /// /// static ROUTES: &str = ""; /// /// #[component] /// fn App(cx: Scope) -> Element { /// cx.render(rsx!( /// NavBar { routes: ROUTES } /// Title { "{cx.props.title}" } /// Footer {} /// )) /// } /// /// #[component] /// fn NavBar(cx: Scope, routes: &'static str) -> Element { /// cx.render(rsx! { /// div { "Routes: {routes}" } /// }) /// } /// /// #[component] /// fn Footer(cx: Scope) -> Element { /// cx.render(rsx! { div { "Footer" } }) /// } /// /// #[component] /// fn Title<'a>(cx: Scope<'a>, children: Element) -> Element { /// cx.render(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(cx: Scope) -> Element { cx.render(rsx! { div {} }) } /// /// let mut vdom = VirtualDom::new(App); /// let edits = vdom.rebuild(); /// ``` /// /// 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(cx: Scope) -> Element { /// cx.render(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 byteindexes to templates pub(crate) templates: FxHashMap>, // Templates changes that are queued for the next render pub(crate) queued_templates: Vec