lib.rs 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. use std::{
  2. io::{BufRead, BufReader, Write},
  3. path::PathBuf,
  4. str::FromStr,
  5. sync::{Arc, Mutex},
  6. };
  7. use dioxus_core::Template;
  8. use dioxus_rsx::{
  9. hot_reload::{FileMap, UpdateResult},
  10. HotReloadingContext,
  11. };
  12. use interprocess::local_socket::{LocalSocketListener, LocalSocketStream};
  13. use notify::{RecommendedWatcher, RecursiveMode, Watcher};
  14. #[cfg(debug_assertions)]
  15. pub use dioxus_html::HtmlCtx;
  16. /// Initialize the hot reloading listener on the given path
  17. pub fn init<Ctx: HotReloadingContext + Send + 'static>(
  18. root_path: &'static str,
  19. listening_paths: &'static [&'static str],
  20. log: bool,
  21. ) {
  22. if let Ok(crate_dir) = PathBuf::from_str(root_path) {
  23. let temp_file = std::env::temp_dir().join("@dioxusin");
  24. let channels = Arc::new(Mutex::new(Vec::new()));
  25. let file_map = Arc::new(Mutex::new(FileMap::<Ctx>::new(crate_dir.clone())));
  26. if let Ok(local_socket_stream) = LocalSocketListener::bind(temp_file.as_path()) {
  27. // listen for connections
  28. std::thread::spawn({
  29. let file_map = file_map.clone();
  30. let channels = channels.clone();
  31. move || {
  32. for connection in local_socket_stream.incoming() {
  33. if let Ok(mut connection) = connection {
  34. // send any templates than have changed before the socket connected
  35. let templates: Vec<_> = {
  36. file_map
  37. .lock()
  38. .unwrap()
  39. .map
  40. .values()
  41. .filter_map(|(_, template_slot)| *template_slot)
  42. .collect()
  43. };
  44. for template in templates {
  45. if !send_template(template, &mut connection) {
  46. continue;
  47. }
  48. }
  49. channels.lock().unwrap().push(connection);
  50. if log {
  51. println!("Connected to hot reloading 🚀");
  52. }
  53. }
  54. }
  55. }
  56. });
  57. // watch for changes
  58. std::thread::spawn(move || {
  59. let mut last_update_time = chrono::Local::now().timestamp();
  60. let (tx, rx) = std::sync::mpsc::channel();
  61. let mut watcher = RecommendedWatcher::new(tx, notify::Config::default()).unwrap();
  62. for path in listening_paths {
  63. match PathBuf::from_str(path) {
  64. Ok(path) => {
  65. let full_path = crate_dir.join(path);
  66. if let Err(err) = watcher.watch(&full_path, RecursiveMode::Recursive) {
  67. if log {
  68. println!(
  69. "hot reloading failed to start watching {full_path:?}:\n{err:?}",
  70. );
  71. }
  72. }
  73. }
  74. Err(err) => {
  75. if log {
  76. println!("hot reloading failed to create path:\n{:?}", err);
  77. }
  78. }
  79. }
  80. }
  81. for evt in rx {
  82. // Give time for the change to take effect before reading the file
  83. std::thread::sleep(std::time::Duration::from_millis(100));
  84. if chrono::Local::now().timestamp() > last_update_time {
  85. if let Ok(evt) = evt {
  86. let mut channels = channels.lock().unwrap();
  87. for path in &evt.paths {
  88. // skip non rust files
  89. if path.extension().and_then(|p| p.to_str()) != Some("rs") {
  90. continue;
  91. }
  92. // find changes to the rsx in the file
  93. match file_map
  94. .lock()
  95. .unwrap()
  96. .update_rsx(&path, crate_dir.as_path())
  97. {
  98. UpdateResult::UpdatedRsx(msgs) => {
  99. for msg in msgs {
  100. let mut i = 0;
  101. while i < channels.len() {
  102. let channel = &mut channels[i];
  103. if send_template(msg, channel) {
  104. i += 1;
  105. } else {
  106. channels.remove(i);
  107. }
  108. }
  109. }
  110. }
  111. UpdateResult::NeedsRebuild => {
  112. if log {
  113. println!(
  114. "Rebuild needed... shutting down hot reloading"
  115. );
  116. }
  117. return;
  118. }
  119. }
  120. }
  121. }
  122. last_update_time = chrono::Local::now().timestamp();
  123. }
  124. }
  125. });
  126. }
  127. }
  128. }
  129. fn send_template(template: Template<'static>, channel: &mut impl Write) -> bool {
  130. if let Ok(msg) = serde_json::to_string(&template) {
  131. if channel.write_all(msg.as_bytes()).is_err() {
  132. return false;
  133. }
  134. if channel.write_all(&[b'\n']).is_err() {
  135. return false;
  136. }
  137. true
  138. } else {
  139. false
  140. }
  141. }
  142. /// Connect to the hot reloading listener. The callback provided will be called every time a template change is detected
  143. pub fn connect(mut f: impl FnMut(Template<'static>) + Send + 'static) {
  144. std::thread::spawn(move || {
  145. let temp_file = std::env::temp_dir().join("@dioxusin");
  146. if let Ok(socket) = LocalSocketStream::connect(temp_file.as_path()) {
  147. let mut buf_reader = BufReader::new(socket);
  148. loop {
  149. let mut buf = String::new();
  150. match buf_reader.read_line(&mut buf) {
  151. Ok(_) => {
  152. let template: Template<'static> =
  153. serde_json::from_str(Box::leak(buf.into_boxed_str())).unwrap();
  154. f(template);
  155. }
  156. Err(err) => {
  157. if err.kind() != std::io::ErrorKind::WouldBlock {
  158. break;
  159. }
  160. }
  161. }
  162. }
  163. }
  164. });
  165. }
  166. /// Start the hot reloading server
  167. ///
  168. /// Pass any number of paths to listen for changes on relative to the crate root as strings.
  169. /// If no paths are passed, it will listen on the src and examples folders.
  170. #[macro_export]
  171. macro_rules! hot_reload_init {
  172. ($(@ $ctx:ident)? $($t: ident)*) => {
  173. #[cfg(debug_assertions)]
  174. dioxus_hot_reload::init::<hot_reload_init!(@ctx: $($ctx)?)>(core::env!("CARGO_MANIFEST_DIR"), &["src", "examples"], hot_reload_init!(@log: $($t)*))
  175. };
  176. ($(@ $ctx:ident)? $($paths: literal),* $(,)? $($t: ident)*) => {
  177. #[cfg(debug_assertions)]
  178. dioxus_hot_reload::init::<hot_reload_init!(@ctx: $($ctx)?)>(core::env!("CARGO_MANIFEST_DIR"), &[$($paths),*], hot_reload_init!(@log: $($t)*))
  179. };
  180. (@log:) => {
  181. false
  182. };
  183. (@log: enable logging) => {
  184. true
  185. };
  186. (@log: disable logging) => {
  187. false
  188. };
  189. (@ctx: $ctx: ident) => {
  190. $ctx
  191. };
  192. (@ctx: ) => {
  193. dioxus_hot_reload::HtmlCtx
  194. };
  195. }