lib.rs 9.2 KB

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