lib.rs 7.1 KB

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