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