mod.rs 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. use crate::{
  2. cfg::ConfigOptsServe,
  3. server::{
  4. output::{print_console_info, PrettierOptions},
  5. setup_file_watcher, setup_file_watcher_hot_reload,
  6. },
  7. BuildResult, CrateConfig, Result,
  8. };
  9. use dioxus_hot_reload::HotReloadMsg;
  10. use dioxus_html::HtmlCtx;
  11. use dioxus_rsx::hot_reload::*;
  12. use interprocess_docfix::local_socket::LocalSocketListener;
  13. use std::{
  14. process::{Child, Command},
  15. sync::{Arc, Mutex, RwLock},
  16. };
  17. use tokio::sync::broadcast::{self};
  18. #[cfg(feature = "plugin")]
  19. use plugin::PluginManager;
  20. use super::Platform;
  21. pub async fn startup(config: CrateConfig, serve: &ConfigOptsServe) -> Result<()> {
  22. startup_with_platform::<DesktopPlatform>(config, serve).await
  23. }
  24. pub(crate) async fn startup_with_platform<P: Platform + Send + 'static>(
  25. config: CrateConfig,
  26. serve: &ConfigOptsServe,
  27. ) -> Result<()> {
  28. // ctrl-c shutdown checker
  29. let _crate_config = config.clone();
  30. let _ = ctrlc::set_handler(move || {
  31. #[cfg(feature = "plugin")]
  32. let _ = PluginManager::on_serve_shutdown(&_crate_config);
  33. std::process::exit(0);
  34. });
  35. match config.hot_reload {
  36. true => serve_hot_reload::<P>(config, serve).await?,
  37. false => serve_default::<P>(config, serve).await?,
  38. }
  39. Ok(())
  40. }
  41. /// Start the server without hot reload
  42. async fn serve_default<P: Platform + Send + 'static>(
  43. config: CrateConfig,
  44. serve: &ConfigOptsServe,
  45. ) -> Result<()> {
  46. let platform = RwLock::new(P::start(&config, serve)?);
  47. log::info!("🚀 Starting development server...");
  48. // We got to own watcher so that it exists for the duration of serve
  49. // Otherwise full reload won't work.
  50. let _watcher = setup_file_watcher(
  51. {
  52. let config = config.clone();
  53. move || platform.write().unwrap().rebuild(&config)
  54. },
  55. &config,
  56. None,
  57. )
  58. .await?;
  59. std::future::pending::<()>().await;
  60. Ok(())
  61. }
  62. /// Start dx serve with hot reload
  63. async fn serve_hot_reload<P: Platform + Send + 'static>(
  64. config: CrateConfig,
  65. serve: &ConfigOptsServe,
  66. ) -> Result<()> {
  67. let platform = RwLock::new(P::start(&config, serve)?);
  68. // Setup hot reload
  69. let FileMapBuildResult { map, errors } =
  70. FileMap::<HtmlCtx>::create(config.crate_dir.clone()).unwrap();
  71. for err in errors {
  72. log::error!("{}", err);
  73. }
  74. let file_map = Arc::new(Mutex::new(map));
  75. let (hot_reload_tx, mut hot_reload_rx) = broadcast::channel(100);
  76. // States
  77. // The open interprocess sockets
  78. let channels = Arc::new(Mutex::new(Vec::new()));
  79. // Setup file watcher
  80. // We got to own watcher so that it exists for the duration of serve
  81. // Otherwise hot reload won't work.
  82. let _watcher = setup_file_watcher_hot_reload(
  83. &config,
  84. hot_reload_tx,
  85. file_map.clone(),
  86. {
  87. let config = config.clone();
  88. let channels = channels.clone();
  89. move || {
  90. for channel in &mut *channels.lock().unwrap() {
  91. send_msg(HotReloadMsg::Shutdown, channel);
  92. }
  93. platform.write().unwrap().rebuild(&config)
  94. }
  95. },
  96. None,
  97. )
  98. .await?;
  99. clear_paths();
  100. match LocalSocketListener::bind("@dioxusin") {
  101. Ok(local_socket_stream) => {
  102. let aborted = Arc::new(Mutex::new(false));
  103. // listen for connections
  104. std::thread::spawn({
  105. let file_map = file_map.clone();
  106. let channels = channels.clone();
  107. let aborted = aborted.clone();
  108. let _ = local_socket_stream.set_nonblocking(true);
  109. move || {
  110. loop {
  111. if let Ok(mut connection) = local_socket_stream.accept() {
  112. // send any templates than have changed before the socket connected
  113. let templates: Vec<_> = {
  114. file_map
  115. .lock()
  116. .unwrap()
  117. .map
  118. .values()
  119. .filter_map(|(_, template_slot)| *template_slot)
  120. .collect()
  121. };
  122. for template in templates {
  123. if !send_msg(
  124. HotReloadMsg::UpdateTemplate(template),
  125. &mut connection,
  126. ) {
  127. continue;
  128. }
  129. }
  130. channels.lock().unwrap().push(connection);
  131. println!("Connected to hot reloading 🚀");
  132. }
  133. if *aborted.lock().unwrap() {
  134. break;
  135. }
  136. }
  137. }
  138. });
  139. while let Ok(template) = hot_reload_rx.recv().await {
  140. let channels = &mut *channels.lock().unwrap();
  141. let mut i = 0;
  142. while i < channels.len() {
  143. let channel = &mut channels[i];
  144. if send_msg(HotReloadMsg::UpdateTemplate(template), channel) {
  145. i += 1;
  146. } else {
  147. channels.remove(i);
  148. }
  149. }
  150. }
  151. }
  152. Err(error) => println!("failed to connect to hot reloading\n{error}"),
  153. }
  154. Ok(())
  155. }
  156. fn clear_paths() {
  157. if cfg!(target_os = "macos") {
  158. // On unix, if you force quit the application, it can leave the file socket open
  159. // This will cause the local socket listener to fail to open
  160. // We check if the file socket is already open from an old session and then delete it
  161. let paths = ["./dioxusin", "./@dioxusin"];
  162. for path in paths {
  163. let path = std::path::PathBuf::from(path);
  164. if path.exists() {
  165. let _ = std::fs::remove_file(path);
  166. }
  167. }
  168. }
  169. }
  170. fn send_msg(msg: HotReloadMsg, channel: &mut impl std::io::Write) -> bool {
  171. if let Ok(msg) = serde_json::to_string(&msg) {
  172. if channel.write_all(msg.as_bytes()).is_err() {
  173. return false;
  174. }
  175. if channel.write_all(&[b'\n']).is_err() {
  176. return false;
  177. }
  178. true
  179. } else {
  180. false
  181. }
  182. }
  183. fn start_desktop(config: &CrateConfig, skip_assets: bool) -> Result<(Child, BuildResult)> {
  184. // Run the desktop application
  185. let result = crate::builder::build_desktop(config, true, skip_assets)?;
  186. match &config.executable {
  187. crate::ExecutableType::Binary(name)
  188. | crate::ExecutableType::Lib(name)
  189. | crate::ExecutableType::Example(name) => {
  190. let mut file = config.out_dir.join(name);
  191. if cfg!(windows) {
  192. file.set_extension("exe");
  193. }
  194. let active = "DIOXUS_ACTIVE";
  195. let child = Command::new(file.to_str().unwrap())
  196. .env(active, "true")
  197. .spawn()?;
  198. Ok((child, result))
  199. }
  200. }
  201. }
  202. pub(crate) struct DesktopPlatform {
  203. currently_running_child: Child,
  204. skip_assets: bool,
  205. }
  206. impl Platform for DesktopPlatform {
  207. fn start(config: &CrateConfig, serve: &ConfigOptsServe) -> Result<Self> {
  208. let (child, first_build_result) = start_desktop(config, serve.skip_assets)?;
  209. log::info!("🚀 Starting development server...");
  210. // Print serve info
  211. print_console_info(
  212. config,
  213. PrettierOptions {
  214. changed: vec![],
  215. warnings: first_build_result.warnings,
  216. elapsed_time: first_build_result.elapsed_time,
  217. },
  218. None,
  219. );
  220. Ok(Self {
  221. currently_running_child: child,
  222. skip_assets: serve.skip_assets,
  223. })
  224. }
  225. fn rebuild(&mut self, config: &CrateConfig) -> Result<BuildResult> {
  226. self.currently_running_child.kill()?;
  227. let (child, result) = start_desktop(config, self.skip_assets)?;
  228. self.currently_running_child = child;
  229. Ok(result)
  230. }
  231. }