lib.rs 7.4 KB

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