dioxus_web/
lib.rs

1#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
2#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
3#![deny(missing_docs)]
4
5//! Dioxus WebSys
6//!
7//! ## Overview
8//! ------------
9//! This crate implements a renderer of the Dioxus Virtual DOM for the web browser using WebSys. This web render for
10//! Dioxus is one of the more advanced renderers, supporting:
11//! - idle work
12//! - animations
13//! - jank-free rendering
14//! - controlled components
15//! - hydration
16//! - and more.
17//!
18//! The actual implementation is farily thin, with the heavy lifting happening inside the Dioxus Core crate.
19//!
20//! To purview the examples, check of the root Dioxus crate - the examples in this crate are mostly meant to provide
21//! validation of websys-specific features and not the general use of Dioxus.
22
23pub use crate::cfg::Config;
24use crate::hydration::SuspenseMessage;
25use dioxus_core::VirtualDom;
26use dom::WebsysDom;
27use futures_util::{pin_mut, select, FutureExt, StreamExt};
28
29mod cfg;
30mod dom;
31
32mod events;
33pub mod launch;
34mod mutations;
35pub use events::*;
36
37#[cfg(feature = "document")]
38mod document;
39#[cfg(feature = "file_engine")]
40mod file_engine;
41#[cfg(feature = "document")]
42mod history;
43#[cfg(feature = "document")]
44pub use document::WebDocument;
45#[cfg(feature = "file_engine")]
46pub use file_engine::*;
47
48#[cfg(all(feature = "devtools", debug_assertions))]
49mod devtools;
50
51mod hydration;
52#[allow(unused)]
53pub use hydration::*;
54
55/// Runs the app as a future that can be scheduled around the main thread.
56///
57/// Polls futures internal to the VirtualDOM, hence the async nature of this function.
58///
59/// # Example
60///
61/// ```ignore, rust
62/// let app_fut = dioxus_web::run_with_props(App, RootProps { name: String::from("foo") });
63/// wasm_bindgen_futures::spawn_local(app_fut);
64/// ```
65pub async fn run(mut virtual_dom: VirtualDom, web_config: Config) -> ! {
66    #[cfg(feature = "document")]
67    virtual_dom.in_runtime(document::init_document);
68
69    let runtime = virtual_dom.runtime();
70
71    #[cfg(all(feature = "devtools", debug_assertions))]
72    let mut hotreload_rx = devtools::init(runtime.clone());
73
74    let should_hydrate = web_config.hydrate;
75
76    let mut websys_dom = WebsysDom::new(web_config, runtime);
77
78    let mut hydration_receiver: Option<futures_channel::mpsc::UnboundedReceiver<SuspenseMessage>> =
79        None;
80
81    if should_hydrate {
82        #[cfg(feature = "hydrate")]
83        {
84            websys_dom.skip_mutations = true;
85            // Get the initial hydration data from the client
86            #[wasm_bindgen::prelude::wasm_bindgen(inline_js = r#"
87                export function get_initial_hydration_data() {
88                    const decoded = atob(window.initial_dioxus_hydration_data);
89                    return Uint8Array.from(decoded, (c) => c.charCodeAt(0))
90                }
91                export function get_initial_hydration_debug_types() {
92                    return window.initial_dioxus_hydration_debug_types;
93                }
94                export function get_initial_hydration_debug_locations() {
95                    return window.initial_dioxus_hydration_debug_locations;
96                }
97            "#)]
98            extern "C" {
99                fn get_initial_hydration_data() -> js_sys::Uint8Array;
100                fn get_initial_hydration_debug_types() -> Option<Vec<String>>;
101                fn get_initial_hydration_debug_locations() -> Option<Vec<String>>;
102            }
103            let hydration_data = get_initial_hydration_data().to_vec();
104
105            // If we are running in debug mode, also get the debug types and locations
106            #[cfg(debug_assertions)]
107            let debug_types = get_initial_hydration_debug_types();
108            #[cfg(not(debug_assertions))]
109            let debug_types = None;
110            #[cfg(debug_assertions)]
111            let debug_locations = get_initial_hydration_debug_locations();
112            #[cfg(not(debug_assertions))]
113            let debug_locations = None;
114
115            let server_data =
116                HTMLDataCursor::from_serialized(&hydration_data, debug_types, debug_locations);
117            // If the server serialized an error into the root suspense boundary, throw it into the root scope
118            if let Some(error) = server_data.error() {
119                virtual_dom.in_runtime(|| dioxus_core::ScopeId::APP.throw_error(error));
120            }
121            with_server_data(server_data, || {
122                virtual_dom.rebuild(&mut websys_dom);
123            });
124            websys_dom.skip_mutations = false;
125
126            let rx = websys_dom.rehydrate(&virtual_dom).unwrap();
127            hydration_receiver = Some(rx);
128
129            #[cfg(feature = "mounted")]
130            {
131                // Flush any mounted events that were queued up while hydrating
132                websys_dom.flush_queued_mounted_events();
133            }
134        }
135        #[cfg(not(feature = "hydrate"))]
136        {
137            panic!("Hydration is not enabled. Please enable the `hydrate` feature.");
138        }
139    } else {
140        virtual_dom.rebuild(&mut websys_dom);
141
142        websys_dom.flush_edits();
143    }
144
145    loop {
146        // if virtual dom has nothing, wait for it to have something before requesting idle time
147        // if there is work then this future resolves immediately.
148        #[cfg(all(feature = "devtools", debug_assertions))]
149        let template;
150        #[allow(unused)]
151        let mut hydration_work: Option<SuspenseMessage> = None;
152
153        {
154            let work = virtual_dom.wait_for_work().fuse();
155            pin_mut!(work);
156
157            let mut hydration_receiver_iter = futures_util::stream::iter(&mut hydration_receiver)
158                .fuse()
159                .flatten();
160            let mut rx_hydration = hydration_receiver_iter.select_next_some();
161
162            #[cfg(all(feature = "devtools", debug_assertions))]
163            #[allow(unused)]
164            {
165                let mut devtools_next = hotreload_rx.select_next_some();
166                select! {
167                    _ = work => {
168                        template = None;
169                    },
170                    new_template = devtools_next => {
171                        template = Some(new_template);
172                    },
173                    hydration_data = rx_hydration => {
174                        template = None;
175                        #[cfg(feature = "hydrate")]
176                        {
177                            hydration_work = Some(hydration_data);
178                        }
179                    },
180                }
181            }
182
183            #[cfg(not(all(feature = "devtools", debug_assertions)))]
184            #[allow(unused)]
185            {
186                select! {
187                    _ = work => {},
188                    hyd = rx_hydration => {
189                        #[cfg(feature = "hydrate")]
190                        {
191                            hydration_work = Some(hyd);
192                        }
193                    }
194                }
195            }
196        }
197
198        #[cfg(all(feature = "devtools", debug_assertions))]
199        if let Some(hr_msg) = template {
200            // Replace all templates
201            dioxus_devtools::apply_changes(&virtual_dom, &hr_msg);
202
203            if !hr_msg.assets.is_empty() {
204                crate::devtools::invalidate_browser_asset_cache();
205            }
206        }
207
208        #[cfg(feature = "hydrate")]
209        if let Some(hydration_data) = hydration_work {
210            websys_dom.rehydrate_streaming(hydration_data, &mut virtual_dom);
211        }
212
213        // Todo: This is currently disabled because it has a negative impact on response times for events but it could be re-enabled for tasks
214        // Jank free rendering
215        //
216        // 1. wait for the browser to give us "idle" time
217        // 2. During idle time, diff the dom
218        // 3. Stop diffing if the deadline is exceeded
219        // 4. Wait for the animation frame to patch the dom
220
221        // wait for the mainthread to schedule us in
222        // let deadline = work_loop.wait_for_idle_time().await;
223
224        // run the virtualdom work phase until the frame deadline is reached
225        virtual_dom.render_immediate(&mut websys_dom);
226
227        // wait for the animation frame to fire so we can apply our changes
228        // work_loop.wait_for_raf().await;
229
230        websys_dom.flush_edits();
231    }
232}