mod.rs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. use crate::{
  2. cfg::ConfigOptsServe,
  3. server::{
  4. output::{print_console_info, PrettierOptions},
  5. setup_file_watcher, Platform,
  6. },
  7. BuildResult, Result,
  8. };
  9. use dioxus_cli_config::{CrateConfig, ExecutableType};
  10. use dioxus_hot_reload::HotReloadMsg;
  11. use dioxus_html::HtmlCtx;
  12. use dioxus_rsx::hot_reload::*;
  13. use interprocess::local_socket::LocalSocketListener;
  14. use std::{
  15. fs::create_dir_all,
  16. process::{Child, Command},
  17. sync::{Arc, Mutex, RwLock},
  18. };
  19. use tokio::sync::broadcast::{self};
  20. #[cfg(feature = "plugin")]
  21. use crate::plugin::PluginManager;
  22. use super::HotReloadState;
  23. pub async fn startup(config: CrateConfig, serve: &ConfigOptsServe) -> Result<()> {
  24. startup_with_platform::<DesktopPlatform>(config, serve).await
  25. }
  26. pub(crate) async fn startup_with_platform<P: Platform + Send + 'static>(
  27. config: CrateConfig,
  28. serve_cfg: &ConfigOptsServe,
  29. ) -> Result<()> {
  30. set_ctrl_c(&config);
  31. let hot_reload_state = match config.hot_reload {
  32. true => {
  33. let FileMapBuildResult { map, errors } =
  34. FileMap::<HtmlCtx>::create(config.crate_dir.clone()).unwrap();
  35. for err in errors {
  36. log::error!("{}", err);
  37. }
  38. let file_map = Arc::new(Mutex::new(map));
  39. let hot_reload_tx = broadcast::channel(100).0;
  40. Some(HotReloadState {
  41. messages: hot_reload_tx.clone(),
  42. file_map: file_map.clone(),
  43. })
  44. }
  45. false => None,
  46. };
  47. serve::<P>(config, serve_cfg, hot_reload_state).await?;
  48. Ok(())
  49. }
  50. fn set_ctrl_c(config: &CrateConfig) {
  51. // ctrl-c shutdown checker
  52. let _crate_config = config.clone();
  53. let _ = ctrlc::set_handler(move || {
  54. #[cfg(feature = "plugin")]
  55. let _ = PluginManager::on_serve_shutdown(&_crate_config);
  56. std::process::exit(0);
  57. });
  58. }
  59. /// Start the server without hot reload
  60. async fn serve<P: Platform + Send + 'static>(
  61. config: CrateConfig,
  62. serve: &ConfigOptsServe,
  63. hot_reload_state: Option<HotReloadState>,
  64. ) -> Result<()> {
  65. let platform = RwLock::new(P::start(&config, serve)?);
  66. log::info!("🚀 Starting development server...");
  67. // We got to own watcher so that it exists for the duration of serve
  68. // Otherwise full reload won't work.
  69. let _watcher = setup_file_watcher(
  70. {
  71. let config = config.clone();
  72. move || platform.write().unwrap().rebuild(&config)
  73. },
  74. &config,
  75. None,
  76. hot_reload_state.clone(),
  77. )
  78. .await?;
  79. match hot_reload_state {
  80. Some(hot_reload_state) => {
  81. // The open interprocess sockets
  82. start_desktop_hot_reload(hot_reload_state).await?;
  83. }
  84. None => {
  85. std::future::pending::<()>().await;
  86. }
  87. }
  88. Ok(())
  89. }
  90. async fn start_desktop_hot_reload(hot_reload_state: HotReloadState) -> Result<()> {
  91. let metadata = cargo_metadata::MetadataCommand::new()
  92. .no_deps()
  93. .exec()
  94. .unwrap();
  95. let target_dir = metadata.target_directory.as_std_path();
  96. let _ = create_dir_all(target_dir); // `_all` is for good measure and future-proofness.
  97. let path = target_dir.join("dioxusin");
  98. clear_paths(&path);
  99. match LocalSocketListener::bind(path) {
  100. Ok(local_socket_stream) => {
  101. let aborted = Arc::new(Mutex::new(false));
  102. // States
  103. // The open interprocess sockets
  104. let channels = Arc::new(Mutex::new(Vec::new()));
  105. // listen for connections
  106. std::thread::spawn({
  107. let file_map = hot_reload_state.file_map.clone();
  108. let channels = channels.clone();
  109. let aborted = aborted.clone();
  110. move || {
  111. loop {
  112. //accept() will block the thread when local_socket_stream is in blocking mode (default)
  113. match local_socket_stream.accept() {
  114. Ok(mut connection) => {
  115. // send any templates than have changed before the socket connected
  116. let templates: Vec<_> = {
  117. file_map
  118. .lock()
  119. .unwrap()
  120. .map
  121. .values()
  122. .flat_map(|v| v.templates.values().copied())
  123. .collect()
  124. };
  125. for template in templates {
  126. if !send_msg(
  127. HotReloadMsg::UpdateTemplate(template),
  128. &mut connection,
  129. ) {
  130. continue;
  131. }
  132. }
  133. channels.lock().unwrap().push(connection);
  134. println!("Connected to hot reloading 🚀");
  135. }
  136. Err(err) => {
  137. let error_string = err.to_string();
  138. // Filter out any error messages about a operation that may block and an error message that triggers on some operating systems that says "Waiting for a process to open the other end of the pipe" without WouldBlock being set
  139. let display_error = err.kind() != std::io::ErrorKind::WouldBlock
  140. && !error_string.contains("Waiting for a process");
  141. if display_error {
  142. println!("Error connecting to hot reloading: {} (Hot reloading is a feature of the dioxus-cli. If you are not using the CLI, this error can be ignored)", err);
  143. }
  144. }
  145. }
  146. if *aborted.lock().unwrap() {
  147. break;
  148. }
  149. }
  150. }
  151. });
  152. let mut hot_reload_rx = hot_reload_state.messages.subscribe();
  153. while let Ok(msg) = hot_reload_rx.recv().await {
  154. let channels = &mut *channels.lock().unwrap();
  155. let mut i = 0;
  156. while i < channels.len() {
  157. let channel = &mut channels[i];
  158. if send_msg(msg.clone(), channel) {
  159. i += 1;
  160. } else {
  161. channels.remove(i);
  162. }
  163. }
  164. }
  165. }
  166. Err(error) => println!("failed to connect to hot reloading\n{error}"),
  167. }
  168. Ok(())
  169. }
  170. fn clear_paths(file_socket_path: &std::path::Path) {
  171. if cfg!(unix) {
  172. // On unix, if you force quit the application, it can leave the file socket open
  173. // This will cause the local socket listener to fail to open
  174. // We check if the file socket is already open from an old session and then delete it
  175. if file_socket_path.exists() {
  176. let _ = std::fs::remove_file(file_socket_path);
  177. }
  178. }
  179. }
  180. fn send_msg(msg: HotReloadMsg, channel: &mut impl std::io::Write) -> bool {
  181. if let Ok(msg) = serde_json::to_string(&msg) {
  182. if channel.write_all(msg.as_bytes()).is_err() {
  183. return false;
  184. }
  185. if channel.write_all(&[b'\n']).is_err() {
  186. return false;
  187. }
  188. true
  189. } else {
  190. false
  191. }
  192. }
  193. fn start_desktop(
  194. config: &CrateConfig,
  195. skip_assets: bool,
  196. rust_flags: Option<String>,
  197. ) -> Result<(RAIIChild, BuildResult)> {
  198. // Run the desktop application
  199. // Only used for the fullstack platform,
  200. let result = crate::builder::build_desktop(config, true, skip_assets, rust_flags)?;
  201. let active = "DIOXUS_ACTIVE";
  202. let child = RAIIChild(
  203. Command::new(
  204. result
  205. .executable
  206. .clone()
  207. .ok_or(anyhow::anyhow!("No executable found after desktop build"))?,
  208. )
  209. .env(active, "true")
  210. .spawn()?,
  211. );
  212. Ok((child, result))
  213. }
  214. pub(crate) struct DesktopPlatform {
  215. currently_running_child: RAIIChild,
  216. skip_assets: bool,
  217. }
  218. impl DesktopPlatform {
  219. /// `rust_flags` argument is added because it is used by the
  220. /// `DesktopPlatform`'s implementation of the `Platform::start()`.
  221. pub fn start_with_options(
  222. config: &CrateConfig,
  223. serve: &ConfigOptsServe,
  224. rust_flags: Option<String>,
  225. ) -> Result<Self> {
  226. let (child, first_build_result) = start_desktop(config, serve.skip_assets, rust_flags)?;
  227. log::info!("🚀 Starting development server...");
  228. // Print serve info
  229. print_console_info(
  230. config,
  231. PrettierOptions {
  232. changed: vec![],
  233. warnings: first_build_result.warnings,
  234. elapsed_time: first_build_result.elapsed_time,
  235. },
  236. None,
  237. );
  238. Ok(Self {
  239. currently_running_child: child,
  240. skip_assets: serve.skip_assets,
  241. })
  242. }
  243. /// `rust_flags` argument is added because it is used by the
  244. /// `DesktopPlatform`'s implementation of the `Platform::rebuild()`.
  245. pub fn rebuild_with_options(
  246. &mut self,
  247. config: &CrateConfig,
  248. rust_flags: Option<String>,
  249. ) -> Result<BuildResult> {
  250. self.currently_running_child.0.kill()?;
  251. let (child, result) = start_desktop(config, self.skip_assets, rust_flags)?;
  252. self.currently_running_child = child;
  253. Ok(result)
  254. }
  255. }
  256. impl Platform for DesktopPlatform {
  257. fn start(config: &CrateConfig, serve: &ConfigOptsServe) -> Result<Self> {
  258. // See `start_with_options()`'s docs for the explanation why the code
  259. // was moved there.
  260. // Since desktop platform doesn't use `rust_flags`, this argument is
  261. // explicitly set to `None`.
  262. DesktopPlatform::start_with_options(config, serve, None)
  263. }
  264. fn rebuild(&mut self, config: &CrateConfig) -> Result<BuildResult> {
  265. // See `rebuild_with_options()`'s docs for the explanation why the code
  266. // was moved there.
  267. // Since desktop platform doesn't use `rust_flags`, this argument is
  268. // explicitly set to `None`.
  269. DesktopPlatform::rebuild_with_options(self, config, None)
  270. }
  271. }
  272. struct RAIIChild(Child);
  273. impl Drop for RAIIChild {
  274. fn drop(&mut self) {
  275. let _ = self.0.kill();
  276. }
  277. }