lib.rs 8.2 KB

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