web_dev_server.rs 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. use axum::{
  2. extract::{ws::WebSocket, WebSocketUpgrade},
  3. http::{header::CONTENT_TYPE, Request, StatusCode},
  4. response::IntoResponse,
  5. routing::get,
  6. Router, Server,
  7. };
  8. use html5ever::{namespace_url, ns, LocalName, QualName};
  9. use kuchiki::{traits::TendrilSink, NodeRef};
  10. use notify::RecursiveMode;
  11. use notify_debouncer_mini::new_debouncer;
  12. use std::{
  13. net::SocketAddr,
  14. path::{Path, PathBuf},
  15. str::FromStr,
  16. sync::{mpsc::sync_channel, Arc},
  17. thread,
  18. time::Duration,
  19. };
  20. use tauri_utils::mime_type::MimeType;
  21. use tokio::sync::broadcast::{channel, Sender};
  22. const AUTO_RELOAD_SCRIPT: &str = include_str!("./auto-reload.js");
  23. pub const SERVER_URL: &str = "http://127.0.0.1:1430";
  24. struct State {
  25. serve_dir: PathBuf,
  26. tx: Sender<()>,
  27. }
  28. pub fn start_dev_server<P: AsRef<Path>>(path: P) {
  29. let serve_dir = path.as_ref().to_path_buf();
  30. std::thread::spawn(move || {
  31. tokio::runtime::Builder::new_current_thread()
  32. .enable_io()
  33. .build()
  34. .unwrap()
  35. .block_on(async move {
  36. let (tx, _) = channel(1);
  37. let tokio_tx = tx.clone();
  38. let serve_dir_ = serve_dir.clone();
  39. thread::spawn(move || {
  40. let (tx, rx) = sync_channel(1);
  41. let mut watcher = new_debouncer(Duration::from_secs(1), None, move |r| {
  42. if let Ok(events) = r {
  43. tx.send(events).unwrap()
  44. }
  45. })
  46. .unwrap();
  47. watcher
  48. .watcher()
  49. .watch(&serve_dir_, RecursiveMode::Recursive)
  50. .unwrap();
  51. loop {
  52. if rx.recv().is_ok() {
  53. let _ = tokio_tx.send(());
  54. }
  55. }
  56. });
  57. let state = Arc::new(State { serve_dir, tx });
  58. let router = Router::new()
  59. .fallback(
  60. Router::new().nest(
  61. "/",
  62. get({
  63. let state = state.clone();
  64. move |req| handler(req, state)
  65. })
  66. .handle_error(|_error| async move { StatusCode::INTERNAL_SERVER_ERROR }),
  67. ),
  68. )
  69. .route(
  70. "/_tauri-cli/ws",
  71. get(move |ws: WebSocketUpgrade| async move {
  72. ws.on_upgrade(|socket| async move { ws_handler(socket, state).await })
  73. }),
  74. );
  75. Server::bind(&SocketAddr::from_str(SERVER_URL.split('/').nth(2).unwrap()).unwrap())
  76. .serve(router.into_make_service())
  77. .await
  78. .unwrap();
  79. })
  80. });
  81. }
  82. async fn handler<T>(req: Request<T>, state: Arc<State>) -> impl IntoResponse {
  83. let uri = req.uri().to_string();
  84. let uri = if uri == "/" {
  85. &uri
  86. } else {
  87. uri.strip_prefix('/').unwrap_or(&uri)
  88. };
  89. let file = std::fs::read(state.serve_dir.join(uri))
  90. .or_else(|_| std::fs::read(state.serve_dir.join(format!("{}.html", &uri))))
  91. .or_else(|_| std::fs::read(state.serve_dir.join(format!("{}/index.html", &uri))))
  92. .or_else(|_| std::fs::read(state.serve_dir.join("index.html")));
  93. file
  94. .map(|mut f| {
  95. let mime_type = MimeType::parse(&f, uri);
  96. if mime_type == MimeType::Html.to_string() {
  97. let mut document = kuchiki::parse_html().one(String::from_utf8_lossy(&f).into_owned());
  98. fn with_html_head<F: FnOnce(&NodeRef)>(document: &mut NodeRef, f: F) {
  99. if let Ok(ref node) = document.select_first("head") {
  100. f(node.as_node())
  101. } else {
  102. let node = NodeRef::new_element(
  103. QualName::new(None, ns!(html), LocalName::from("head")),
  104. None,
  105. );
  106. f(&node);
  107. document.prepend(node)
  108. }
  109. }
  110. with_html_head(&mut document, |head| {
  111. let script_el =
  112. NodeRef::new_element(QualName::new(None, ns!(html), "script".into()), None);
  113. script_el.append(NodeRef::new_text(AUTO_RELOAD_SCRIPT));
  114. head.prepend(script_el);
  115. });
  116. f = document.to_string().as_bytes().to_vec();
  117. }
  118. (StatusCode::OK, [(CONTENT_TYPE, mime_type)], f)
  119. })
  120. .unwrap_or_else(|_| {
  121. (
  122. StatusCode::NOT_FOUND,
  123. [(CONTENT_TYPE, "text/plain".into())],
  124. vec![],
  125. )
  126. })
  127. }
  128. async fn ws_handler(mut ws: WebSocket, state: Arc<State>) {
  129. let mut rx = state.tx.subscribe();
  130. while tokio::select! {
  131. _ = ws.recv() => return,
  132. fs_reload_event = rx.recv() => fs_reload_event.is_ok(),
  133. } {
  134. let ws_send = ws.send(axum::extract::ws::Message::Text(
  135. r#"{"reload": true}"#.to_owned(),
  136. ));
  137. if ws_send.await.is_err() {
  138. break;
  139. }
  140. }
  141. }