lib.rs 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. //! Dioxus WebSys
  2. //!
  3. //! ## Overview
  4. //! ------------
  5. //! This crate implements a renderer of the Dioxus Virtual DOM for the web browser using WebSys. This web render for
  6. //! Dioxus is one of the more advanced renderers, supporting:
  7. //! - idle work
  8. //! - animations
  9. //! - jank-free rendering
  10. //! - noderefs
  11. //! - controlled components
  12. //! - re-hydration
  13. //! - and more.
  14. //!
  15. //! The actual implementation is farily thin, with the heavy lifting happening inside the Dioxus Core crate.
  16. //!
  17. //! To purview the examples, check of the root Dioxus crate - the examples in this crate are mostly meant to provide
  18. //! validation of websys-specific features and not the general use of Dioxus.
  19. //!
  20. //! ## RequestAnimationFrame and RequestIdleCallback
  21. //! ------------------------------------------------
  22. //! React implements "jank free rendering" by deliberately not blocking the browser's main thread. For large diffs, long
  23. //! running work, and integration with things like React-Three-Fiber, it's extremeley important to avoid blocking the
  24. //! main thread.
  25. //!
  26. //! React solves this problem by breaking up the rendering process into a "diff" phase and a "render" phase. In Dioxus,
  27. //! the diff phase is non-blocking, using "yield_now" to allow the browser to process other events. When the diff phase
  28. //! is finally complete, the VirtualDOM will return a set of "Mutations" for this crate to apply.
  29. //!
  30. //! Here, we schedule the "diff" phase during the browser's idle period, achieved by calling RequestIdleCallback and then
  31. //! setting a timeout from the that completes when the idleperiod is over. Then, we call requestAnimationFrame
  32. //!
  33. //! From Google's guide on rAF and rIC:
  34. //! -----------------------------------
  35. //!
  36. //! If the callback is fired at the end of the frame, it will be scheduled to go after the current frame has been committed,
  37. //! which means that style changes will have been applied, and, importantly, layout calculated. If we make DOM changes inside
  38. //! of the idle callback, those layout calculations will be invalidated. If there are any kind of layout reads in the next
  39. //! frame, e.g. getBoundingClientRect, clientWidth, etc, the browser will have to perform a Forced Synchronous Layout,
  40. //! which is a potential performance bottleneck.
  41. //!
  42. //! Another reason not trigger DOM changes in the idle callback is that the time impact of changing the DOM is unpredictable,
  43. //! and as such we could easily go past the deadline the browser provided.
  44. //!
  45. //! The best practice is to only make DOM changes inside of a requestAnimationFrame callback, since it is scheduled by the
  46. //! browser with that type of work in mind. That means that our code will need to use a document fragment, which can then
  47. //! be appended in the next requestAnimationFrame callback. If you are using a VDOM library, you would use requestIdleCallback
  48. //! to make changes, but you would apply the DOM patches in the next requestAnimationFrame callback, not the idle callback.
  49. //!
  50. //! Essentially:
  51. //! ------------
  52. //! - Do the VDOM work during the idlecallback
  53. //! - Do DOM work in the next requestAnimationFrame callback
  54. use std::rc::Rc;
  55. pub use crate::cfg::WebConfig;
  56. use crate::dom::load_document;
  57. use dioxus::prelude::Properties;
  58. use dioxus::virtual_dom::VirtualDom;
  59. pub use dioxus_core as dioxus;
  60. use dioxus_core::prelude::FC;
  61. mod cache;
  62. mod cfg;
  63. mod dom;
  64. mod nodeslab;
  65. mod ric_raf;
  66. /// Launches the VirtualDOM from the specified component function.
  67. ///
  68. /// This method will block the thread with `spawn_local`
  69. ///
  70. /// # Example
  71. ///
  72. ///
  73. ///
  74. pub fn launch<F>(root: FC<()>, config: F)
  75. where
  76. F: FnOnce(WebConfig) -> WebConfig,
  77. {
  78. launch_with_props(root, (), config)
  79. }
  80. /// Launches the VirtualDOM from the specified component function and props.
  81. ///
  82. /// This method will block the thread with `spawn_local`
  83. ///
  84. /// # Example
  85. ///
  86. ///
  87. pub fn launch_with_props<T, F>(root: FC<T>, root_props: T, config: F)
  88. where
  89. T: Properties + 'static,
  90. F: FnOnce(WebConfig) -> WebConfig,
  91. {
  92. let config = config(WebConfig::default());
  93. let fut = run_with_props(root, root_props, config);
  94. wasm_bindgen_futures::spawn_local(fut);
  95. }
  96. /// This method is the primary entrypoint for Websys Dioxus apps. Will panic if an error occurs while rendering.
  97. /// See DioxusErrors for more information on how these errors could occour.
  98. ///
  99. /// # Example
  100. ///
  101. /// ```ignore
  102. /// fn main() {
  103. /// wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example));
  104. /// }
  105. /// ```
  106. ///
  107. /// Run the app to completion, panicing if any error occurs while rendering.
  108. /// Pairs well with the wasm_bindgen async handler
  109. pub async fn run_with_props<T: Properties + 'static>(root: FC<T>, root_props: T, cfg: WebConfig) {
  110. let mut dom = VirtualDom::new_with_props(root, root_props);
  111. let hydrating = cfg.hydrate;
  112. let root_el = load_document().get_element_by_id(&cfg.rootname).unwrap();
  113. let tasks = dom.get_event_sender();
  114. let sender_callback = Rc::new(move |event| tasks.unbounded_send(event).unwrap());
  115. let mut websys_dom = dom::WebsysDom::new(root_el, cfg, sender_callback);
  116. let mut mutations = dom.rebuild();
  117. // hydrating is simply running the dom for a single render. If the page is already written, then the corresponding
  118. // ElementIds should already line up because the web_sys dom has already loaded elements with the DioxusID into memory
  119. if !hydrating {
  120. websys_dom.process_edits(&mut mutations.edits);
  121. }
  122. let work_loop = ric_raf::RafLoop::new();
  123. loop {
  124. // if virtualdom has nothing, wait for it to have something before requesting idle time
  125. if !dom.has_work() {
  126. dom.wait_for_any_work().await;
  127. }
  128. let deadline = work_loop.wait_for_idle_time().await;
  129. if let Some(mut mutations) = dom.run_with_deadline(deadline).await {
  130. work_loop.wait_for_raf().await;
  131. websys_dom.process_edits(&mut mutations.edits);
  132. }
  133. }
  134. }