mod.rs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752
  1. use crate::{builder, plugin::PluginManager, serve::Serve, BuildResult, CrateConfig, Result};
  2. use axum::{
  3. body::{Full, HttpBody},
  4. extract::{ws::Message, Extension, TypedHeader, WebSocketUpgrade},
  5. http::{
  6. header::{HeaderName, HeaderValue},
  7. Method, Response, StatusCode,
  8. },
  9. response::IntoResponse,
  10. routing::{get, get_service},
  11. Router,
  12. };
  13. use cargo_metadata::diagnostic::Diagnostic;
  14. use colored::Colorize;
  15. use dioxus_core::Template;
  16. use dioxus_html::HtmlCtx;
  17. use dioxus_rsx::hot_reload::*;
  18. use notify::{RecommendedWatcher, Watcher};
  19. use std::{
  20. net::UdpSocket,
  21. path::PathBuf,
  22. process::Command,
  23. sync::{Arc, Mutex},
  24. };
  25. use tokio::sync::broadcast;
  26. use tower::ServiceBuilder;
  27. use tower_http::services::fs::{ServeDir, ServeFileSystemResponseBody};
  28. use tower_http::{
  29. cors::{Any, CorsLayer},
  30. ServiceBuilderExt,
  31. };
  32. mod proxy;
  33. pub struct BuildManager {
  34. config: CrateConfig,
  35. reload_tx: broadcast::Sender<()>,
  36. }
  37. impl BuildManager {
  38. fn rebuild(&self) -> Result<BuildResult> {
  39. log::info!("🪁 Rebuild project");
  40. let result = builder::build(&self.config, true)?;
  41. // change the websocket reload state to true;
  42. // the page will auto-reload.
  43. if self
  44. .config
  45. .dioxus_config
  46. .web
  47. .watcher
  48. .reload_html
  49. .unwrap_or(false)
  50. {
  51. let _ = Serve::regen_dev_page(&self.config);
  52. }
  53. let _ = self.reload_tx.send(());
  54. Ok(result)
  55. }
  56. }
  57. struct WsReloadState {
  58. update: broadcast::Sender<()>,
  59. }
  60. pub async fn startup(port: u16, config: CrateConfig, start_browser: bool) -> Result<()> {
  61. // ctrl-c shutdown checker
  62. let crate_config = config.clone();
  63. let _ = ctrlc::set_handler(move || {
  64. let _ = PluginManager::on_serve_shutdown(&crate_config);
  65. std::process::exit(0);
  66. });
  67. let ip = get_ip().unwrap_or(String::from("0.0.0.0"));
  68. if config.hot_reload {
  69. startup_hot_reload(ip, port, config, start_browser).await?
  70. } else {
  71. startup_default(ip, port, config, start_browser).await?
  72. }
  73. Ok(())
  74. }
  75. pub struct HotReloadState {
  76. pub messages: broadcast::Sender<Template<'static>>,
  77. pub build_manager: Arc<BuildManager>,
  78. pub file_map: Arc<Mutex<FileMap<HtmlCtx>>>,
  79. pub watcher_config: CrateConfig,
  80. }
  81. pub async fn hot_reload_handler(
  82. ws: WebSocketUpgrade,
  83. _: Option<TypedHeader<headers::UserAgent>>,
  84. Extension(state): Extension<Arc<HotReloadState>>,
  85. ) -> impl IntoResponse {
  86. ws.on_upgrade(|mut socket| async move {
  87. log::info!("🔥 Hot Reload WebSocket connected");
  88. {
  89. // update any rsx calls that changed before the websocket connected.
  90. {
  91. log::info!("🔮 Finding updates since last compile...");
  92. let templates: Vec<_> = {
  93. state
  94. .file_map
  95. .lock()
  96. .unwrap()
  97. .map
  98. .values()
  99. .filter_map(|(_, template_slot)| *template_slot)
  100. .collect()
  101. };
  102. for template in templates {
  103. if socket
  104. .send(Message::Text(serde_json::to_string(&template).unwrap()))
  105. .await
  106. .is_err()
  107. {
  108. return;
  109. }
  110. }
  111. }
  112. log::info!("finished");
  113. }
  114. let mut rx = state.messages.subscribe();
  115. loop {
  116. if let Ok(rsx) = rx.recv().await {
  117. if socket
  118. .send(Message::Text(serde_json::to_string(&rsx).unwrap()))
  119. .await
  120. .is_err()
  121. {
  122. break;
  123. };
  124. }
  125. }
  126. })
  127. }
  128. #[allow(unused_assignments)]
  129. pub async fn startup_hot_reload(
  130. ip: String,
  131. port: u16,
  132. config: CrateConfig,
  133. start_browser: bool,
  134. ) -> Result<()> {
  135. let first_build_result = crate::builder::build(&config, false)?;
  136. log::info!("🚀 Starting development server...");
  137. PluginManager::on_serve_start(&config)?;
  138. let dist_path = config.out_dir.clone();
  139. let (reload_tx, _) = broadcast::channel(100);
  140. let FileMapBuildResult { map, errors } =
  141. FileMap::<HtmlCtx>::create(config.crate_dir.clone()).unwrap();
  142. for err in errors {
  143. log::error!("{}", err);
  144. }
  145. let file_map = Arc::new(Mutex::new(map));
  146. let build_manager = Arc::new(BuildManager {
  147. config: config.clone(),
  148. reload_tx: reload_tx.clone(),
  149. });
  150. let hot_reload_tx = broadcast::channel(100).0;
  151. let hot_reload_state = Arc::new(HotReloadState {
  152. messages: hot_reload_tx.clone(),
  153. build_manager: build_manager.clone(),
  154. file_map: file_map.clone(),
  155. watcher_config: config.clone(),
  156. });
  157. let crate_dir = config.crate_dir.clone();
  158. let ws_reload_state = Arc::new(WsReloadState {
  159. update: reload_tx.clone(),
  160. });
  161. // file watcher: check file change
  162. let allow_watch_path = config
  163. .dioxus_config
  164. .web
  165. .watcher
  166. .watch_path
  167. .clone()
  168. .unwrap_or_else(|| vec![PathBuf::from("src")]);
  169. let watcher_config = config.clone();
  170. let watcher_ip = ip.clone();
  171. let mut last_update_time = chrono::Local::now().timestamp();
  172. let mut watcher = RecommendedWatcher::new(
  173. move |evt: notify::Result<notify::Event>| {
  174. let config = watcher_config.clone();
  175. // Give time for the change to take effect before reading the file
  176. std::thread::sleep(std::time::Duration::from_millis(100));
  177. if chrono::Local::now().timestamp() > last_update_time {
  178. if let Ok(evt) = evt {
  179. let mut messages: Vec<Template<'static>> = Vec::new();
  180. for path in evt.paths.clone() {
  181. // if this is not a rust file, rebuild the whole project
  182. if path.extension().and_then(|p| p.to_str()) != Some("rs") {
  183. match build_manager.rebuild() {
  184. Ok(res) => {
  185. print_console_info(
  186. &watcher_ip,
  187. port,
  188. &config,
  189. PrettierOptions {
  190. changed: evt.paths,
  191. warnings: res.warnings,
  192. elapsed_time: res.elapsed_time,
  193. },
  194. );
  195. }
  196. Err(err) => {
  197. log::error!("{}", err);
  198. }
  199. }
  200. return;
  201. }
  202. // find changes to the rsx in the file
  203. let mut map = file_map.lock().unwrap();
  204. match map.update_rsx(&path, &crate_dir) {
  205. Ok(UpdateResult::UpdatedRsx(msgs)) => {
  206. messages.extend(msgs);
  207. }
  208. Ok(UpdateResult::NeedsRebuild) => {
  209. match build_manager.rebuild() {
  210. Ok(res) => {
  211. print_console_info(
  212. &watcher_ip,
  213. port,
  214. &config,
  215. PrettierOptions {
  216. changed: evt.paths,
  217. warnings: res.warnings,
  218. elapsed_time: res.elapsed_time,
  219. },
  220. );
  221. }
  222. Err(err) => {
  223. log::error!("{}", err);
  224. }
  225. }
  226. return;
  227. }
  228. Err(err) => {
  229. log::error!("{}", err);
  230. }
  231. }
  232. }
  233. for msg in messages {
  234. let _ = hot_reload_tx.send(msg);
  235. }
  236. }
  237. last_update_time = chrono::Local::now().timestamp();
  238. }
  239. },
  240. notify::Config::default(),
  241. )
  242. .unwrap();
  243. for sub_path in allow_watch_path {
  244. if let Err(err) = watcher.watch(
  245. &config.crate_dir.join(&sub_path),
  246. notify::RecursiveMode::Recursive,
  247. ) {
  248. log::error!("error watching {sub_path:?}: \n{}", err);
  249. }
  250. }
  251. // start serve dev-server at 0.0.0.0:8080
  252. print_console_info(
  253. &ip,
  254. port,
  255. &config,
  256. PrettierOptions {
  257. changed: vec![],
  258. warnings: first_build_result.warnings,
  259. elapsed_time: first_build_result.elapsed_time,
  260. },
  261. );
  262. let cors = CorsLayer::new()
  263. // allow `GET` and `POST` when accessing the resource
  264. .allow_methods([Method::GET, Method::POST])
  265. // allow requests from any origin
  266. .allow_origin(Any)
  267. .allow_headers(Any);
  268. let (coep, coop) = if config.cross_origin_policy {
  269. (
  270. HeaderValue::from_static("require-corp"),
  271. HeaderValue::from_static("same-origin"),
  272. )
  273. } else {
  274. (
  275. HeaderValue::from_static("unsafe-none"),
  276. HeaderValue::from_static("unsafe-none"),
  277. )
  278. };
  279. let file_service_config = config.clone();
  280. let file_service = ServiceBuilder::new()
  281. .override_response_header(
  282. HeaderName::from_static("cross-origin-embedder-policy"),
  283. coep,
  284. )
  285. .override_response_header(HeaderName::from_static("cross-origin-opener-policy"), coop)
  286. .and_then(
  287. move |response: Response<ServeFileSystemResponseBody>| async move {
  288. let response = if file_service_config
  289. .dioxus_config
  290. .web
  291. .watcher
  292. .index_on_404
  293. .unwrap_or(false)
  294. && response.status() == StatusCode::NOT_FOUND
  295. {
  296. let body = Full::from(
  297. // TODO: Cache/memoize this.
  298. std::fs::read_to_string(
  299. file_service_config
  300. .crate_dir
  301. .join(file_service_config.out_dir)
  302. .join("index.html"),
  303. )
  304. .ok()
  305. .unwrap(),
  306. )
  307. .map_err(|err| match err {})
  308. .boxed();
  309. Response::builder()
  310. .status(StatusCode::OK)
  311. .body(body)
  312. .unwrap()
  313. } else {
  314. response.map(|body| body.boxed())
  315. };
  316. Ok(response)
  317. },
  318. )
  319. .service(ServeDir::new(config.crate_dir.join(&dist_path)));
  320. let mut router = Router::new().route("/_dioxus/ws", get(ws_handler));
  321. for proxy_config in config.dioxus_config.web.proxy.unwrap_or_default() {
  322. router = proxy::add_proxy(router, &proxy_config)?;
  323. }
  324. router = router.fallback(get_service(file_service).handle_error(
  325. |error: std::io::Error| async move {
  326. (
  327. StatusCode::INTERNAL_SERVER_ERROR,
  328. format!("Unhandled internal error: {}", error),
  329. )
  330. },
  331. ));
  332. let router = router
  333. .route("/_dioxus/hot_reload", get(hot_reload_handler))
  334. .layer(cors)
  335. .layer(Extension(ws_reload_state))
  336. .layer(Extension(hot_reload_state));
  337. let addr = format!("0.0.0.0:{}", port).parse().unwrap();
  338. let server = axum::Server::bind(&addr).serve(router.into_make_service());
  339. if start_browser {
  340. let _ = open::that(format!("http://{}", addr));
  341. }
  342. server.await?;
  343. Ok(())
  344. }
  345. pub async fn startup_default(
  346. ip: String,
  347. port: u16,
  348. config: CrateConfig,
  349. start_browser: bool,
  350. ) -> Result<()> {
  351. let first_build_result = crate::builder::build(&config, false)?;
  352. log::info!("🚀 Starting development server...");
  353. let dist_path = config.out_dir.clone();
  354. let (reload_tx, _) = broadcast::channel(100);
  355. let build_manager = BuildManager {
  356. config: config.clone(),
  357. reload_tx: reload_tx.clone(),
  358. };
  359. let ws_reload_state = Arc::new(WsReloadState {
  360. update: reload_tx.clone(),
  361. });
  362. let mut last_update_time = chrono::Local::now().timestamp();
  363. // file watcher: check file change
  364. let allow_watch_path = config
  365. .dioxus_config
  366. .web
  367. .watcher
  368. .watch_path
  369. .clone()
  370. .unwrap_or_else(|| vec![PathBuf::from("src")]);
  371. let watcher_config = config.clone();
  372. let watcher_ip = ip.clone();
  373. let mut watcher = notify::recommended_watcher(move |info: notify::Result<notify::Event>| {
  374. let config = watcher_config.clone();
  375. if let Ok(e) = info {
  376. if chrono::Local::now().timestamp() > last_update_time {
  377. match build_manager.rebuild() {
  378. Ok(res) => {
  379. last_update_time = chrono::Local::now().timestamp();
  380. print_console_info(
  381. &watcher_ip,
  382. port,
  383. &config,
  384. PrettierOptions {
  385. changed: e.paths.clone(),
  386. warnings: res.warnings,
  387. elapsed_time: res.elapsed_time,
  388. },
  389. );
  390. let _ = PluginManager::on_serve_rebuild(
  391. chrono::Local::now().timestamp(),
  392. e.paths,
  393. );
  394. }
  395. Err(e) => log::error!("{}", e),
  396. }
  397. }
  398. }
  399. })
  400. .unwrap();
  401. for sub_path in allow_watch_path {
  402. watcher
  403. .watch(
  404. &config.crate_dir.join(sub_path),
  405. notify::RecursiveMode::Recursive,
  406. )
  407. .unwrap();
  408. }
  409. // start serve dev-server at 0.0.0.0
  410. print_console_info(
  411. &ip,
  412. port,
  413. &config,
  414. PrettierOptions {
  415. changed: vec![],
  416. warnings: first_build_result.warnings,
  417. elapsed_time: first_build_result.elapsed_time,
  418. },
  419. );
  420. PluginManager::on_serve_start(&config)?;
  421. let cors = CorsLayer::new()
  422. // allow `GET` and `POST` when accessing the resource
  423. .allow_methods([Method::GET, Method::POST])
  424. // allow requests from any origin
  425. .allow_origin(Any)
  426. .allow_headers(Any);
  427. let (coep, coop) = if config.cross_origin_policy {
  428. (
  429. HeaderValue::from_static("require-corp"),
  430. HeaderValue::from_static("same-origin"),
  431. )
  432. } else {
  433. (
  434. HeaderValue::from_static("unsafe-none"),
  435. HeaderValue::from_static("unsafe-none"),
  436. )
  437. };
  438. let file_service_config = config.clone();
  439. let file_service = ServiceBuilder::new()
  440. .override_response_header(
  441. HeaderName::from_static("cross-origin-embedder-policy"),
  442. coep,
  443. )
  444. .override_response_header(HeaderName::from_static("cross-origin-opener-policy"), coop)
  445. .and_then(
  446. move |response: Response<ServeFileSystemResponseBody>| async move {
  447. let response = if file_service_config
  448. .dioxus_config
  449. .web
  450. .watcher
  451. .index_on_404
  452. .unwrap_or(false)
  453. && response.status() == StatusCode::NOT_FOUND
  454. {
  455. let body = Full::from(
  456. // TODO: Cache/memoize this.
  457. std::fs::read_to_string(
  458. file_service_config
  459. .crate_dir
  460. .join(file_service_config.out_dir)
  461. .join("index.html"),
  462. )
  463. .ok()
  464. .unwrap(),
  465. )
  466. .map_err(|err| match err {})
  467. .boxed();
  468. Response::builder()
  469. .status(StatusCode::OK)
  470. .body(body)
  471. .unwrap()
  472. } else {
  473. response.map(|body| body.boxed())
  474. };
  475. Ok(response)
  476. },
  477. )
  478. .service(ServeDir::new(config.crate_dir.join(&dist_path)));
  479. let mut router = Router::new().route("/_dioxus/ws", get(ws_handler));
  480. for proxy_config in config.dioxus_config.web.proxy.unwrap_or_default() {
  481. router = proxy::add_proxy(router, &proxy_config)?;
  482. }
  483. router = router
  484. .fallback(
  485. get_service(file_service).handle_error(|error: std::io::Error| async move {
  486. (
  487. StatusCode::INTERNAL_SERVER_ERROR,
  488. format!("Unhandled internal error: {}", error),
  489. )
  490. }),
  491. )
  492. .layer(cors)
  493. .layer(Extension(ws_reload_state));
  494. let addr = format!("0.0.0.0:{}", port).parse().unwrap();
  495. let server = axum::Server::bind(&addr).serve(router.into_make_service());
  496. if start_browser {
  497. let _ = open::that(format!("http://{}", addr));
  498. }
  499. server.await?;
  500. Ok(())
  501. }
  502. #[derive(Debug, Default)]
  503. pub struct PrettierOptions {
  504. changed: Vec<PathBuf>,
  505. warnings: Vec<Diagnostic>,
  506. elapsed_time: u128,
  507. }
  508. fn print_console_info(ip: &String, port: u16, config: &CrateConfig, options: PrettierOptions) {
  509. if let Ok(native_clearseq) = Command::new(if cfg!(target_os = "windows") {
  510. "cls"
  511. } else {
  512. "clear"
  513. })
  514. .output()
  515. {
  516. print!("{}", String::from_utf8_lossy(&native_clearseq.stdout));
  517. } else {
  518. // Try ANSI-Escape characters
  519. print!("\x1b[2J\x1b[H");
  520. }
  521. // for path in &changed {
  522. // let path = path
  523. // .strip_prefix(crate::crate_root().unwrap())
  524. // .unwrap()
  525. // .to_path_buf();
  526. // log::info!("Updated {}", format!("{}", path.to_str().unwrap()).green());
  527. // }
  528. let mut profile = if config.release { "Release" } else { "Debug" }.to_string();
  529. if config.custom_profile.is_some() {
  530. profile = config.custom_profile.as_ref().unwrap().to_string();
  531. }
  532. let hot_reload = if config.hot_reload { "RSX" } else { "Normal" };
  533. let crate_root = crate::cargo::crate_root().unwrap();
  534. let custom_html_file = if crate_root.join("index.html").is_file() {
  535. "Custom [index.html]"
  536. } else {
  537. "Default"
  538. };
  539. let url_rewrite = if config
  540. .dioxus_config
  541. .web
  542. .watcher
  543. .index_on_404
  544. .unwrap_or(false)
  545. {
  546. "True"
  547. } else {
  548. "False"
  549. };
  550. let proxies = config.dioxus_config.web.proxy.as_ref();
  551. if options.changed.is_empty() {
  552. println!(
  553. "{} @ v{} [{}] \n",
  554. "Dioxus".bold().green(),
  555. crate::DIOXUS_CLI_VERSION,
  556. chrono::Local::now().format("%H:%M:%S").to_string().dimmed()
  557. );
  558. } else {
  559. println!(
  560. "Project Reloaded: {}\n",
  561. format!(
  562. "Changed {} files. [{}]",
  563. options.changed.len(),
  564. chrono::Local::now().format("%H:%M:%S").to_string().dimmed()
  565. )
  566. .purple()
  567. .bold()
  568. );
  569. }
  570. println!(
  571. "\t> Local : {}",
  572. format!("http://localhost:{}/", port).blue()
  573. );
  574. println!(
  575. "\t> Network : {}",
  576. format!("http://{}:{}/", ip, port).blue()
  577. );
  578. println!("");
  579. println!("\t> Profile : {}", profile.green());
  580. println!("\t> Hot Reload : {}", hot_reload.cyan());
  581. if let Some(proxies) = proxies {
  582. if !proxies.is_empty() {
  583. println!("\t> Proxies :");
  584. for proxy in proxies {
  585. println!("\t\t- {}", proxy.backend.blue());
  586. }
  587. }
  588. }
  589. println!("\t> Index Template : {}", custom_html_file.green());
  590. println!("\t> URL Rewrite [index_on_404] : {}", url_rewrite.purple());
  591. println!("");
  592. println!(
  593. "\t> Build Time Use : {} millis",
  594. options.elapsed_time.to_string().green().bold()
  595. );
  596. println!("");
  597. if options.warnings.len() == 0 {
  598. log::info!("{}\n", "A perfect compilation!".green().bold());
  599. } else {
  600. log::warn!(
  601. "{}",
  602. format!(
  603. "There were {} warning messages during the build.",
  604. options.warnings.len() - 1
  605. )
  606. .yellow()
  607. .bold()
  608. );
  609. // for info in &options.warnings {
  610. // let message = info.message.clone();
  611. // if message == format!("{} warnings emitted", options.warnings.len() - 1) {
  612. // continue;
  613. // }
  614. // let mut console = String::new();
  615. // for span in &info.spans {
  616. // let file = &span.file_name;
  617. // let line = (span.line_start, span.line_end);
  618. // let line_str = if line.0 == line.1 {
  619. // line.0.to_string()
  620. // } else {
  621. // format!("{}~{}", line.0, line.1)
  622. // };
  623. // let code = span.text.clone();
  624. // let span_info = if code.len() == 1 {
  625. // let code = code.get(0).unwrap().text.trim().blue().bold().to_string();
  626. // format!(
  627. // "[{}: {}]: '{}' --> {}",
  628. // file,
  629. // line_str,
  630. // code,
  631. // message.yellow().bold()
  632. // )
  633. // } else {
  634. // let code = code
  635. // .iter()
  636. // .enumerate()
  637. // .map(|(_i, s)| format!("\t{}\n", s.text).blue().bold().to_string())
  638. // .collect::<String>();
  639. // format!("[{}: {}]:\n{}\n#:{}", file, line_str, code, message)
  640. // };
  641. // console = format!("{console}\n\t{span_info}");
  642. // }
  643. // println!("{console}");
  644. // }
  645. // println!(
  646. // "\n{}\n",
  647. // "Resolving all warnings will help your code run better!".yellow()
  648. // );
  649. }
  650. }
  651. fn get_ip() -> Option<String> {
  652. let socket = match UdpSocket::bind("0.0.0.0:0") {
  653. Ok(s) => s,
  654. Err(_) => return None,
  655. };
  656. match socket.connect("8.8.8.8:80") {
  657. Ok(()) => (),
  658. Err(_) => return None,
  659. };
  660. match socket.local_addr() {
  661. Ok(addr) => return Some(addr.ip().to_string()),
  662. Err(_) => return None,
  663. };
  664. }
  665. async fn ws_handler(
  666. ws: WebSocketUpgrade,
  667. _: Option<TypedHeader<headers::UserAgent>>,
  668. Extension(state): Extension<Arc<WsReloadState>>,
  669. ) -> impl IntoResponse {
  670. ws.on_upgrade(|mut socket| async move {
  671. let mut rx = state.update.subscribe();
  672. let reload_watcher = tokio::spawn(async move {
  673. loop {
  674. rx.recv().await.unwrap();
  675. // ignore the error
  676. if socket
  677. .send(Message::Text(String::from("reload")))
  678. .await
  679. .is_err()
  680. {
  681. break;
  682. }
  683. // flush the errors after recompling
  684. rx = rx.resubscribe();
  685. }
  686. });
  687. reload_watcher.await.unwrap();
  688. })
  689. }