Pārlūkot izejas kodu

spawn hot reloading in a seperate thread

Evan Almloff 2 gadi atpakaļ
vecāks
revīzija
064bee4a9f

+ 1 - 7
packages/server/examples/axum-hello-world/Cargo.toml

@@ -13,15 +13,9 @@ dioxus-server = { path = "../../" }
 axum = { version = "0.6.12", optional = true }
 tokio = { version = "1.27.0", features = ["full"], optional = true }
 serde = "1.0.159"
+execute = "0.2.12"
 
 [features]
 default = ["web"]
 ssr = ["axum", "tokio", "dioxus-server/axum"]
 web = ["dioxus-web"]
-
-[profile.release]
-lto = true
-panic = "abort"
-opt-level = 3
-strip = true
-codegen-units = 1

+ 14 - 1
packages/server/examples/axum-hello-world/src/main.rs

@@ -19,6 +19,19 @@ fn main() {
     );
     #[cfg(feature = "ssr")]
     {
+        // Start hot reloading
+        hot_reload_init!(dioxus_hot_reload::Config::new().with_rebuild_callback(|| {
+            execute::shell("dioxus build --features web")
+                .spawn()
+                .unwrap()
+                .wait()
+                .unwrap();
+            execute::shell("cargo run --features ssr --no-default-features")
+                .spawn()
+                .unwrap();
+            true
+        }));
+
         PostServerData::register().unwrap();
         GetServerData::register().unwrap();
         tokio::runtime::Runtime::new()
@@ -65,7 +78,7 @@ fn app(cx: Scope<AppProps>) -> Element {
                     }
                 }
             },
-            "Run a server function"
+            "Run a server function! testing1234"
         }
         "Server said: {text}"
     })

+ 1 - 7
packages/server/examples/axum-router/Cargo.toml

@@ -16,15 +16,9 @@ tokio = { version = "1.27.0", features = ["full"], optional = true }
 serde = "1.0.159"
 tower-http = { version = "0.4.0", features = ["fs"], optional = true }
 http = { version = "0.2.9", optional = true }
+execute = "0.2.12"
 
 [features]
 default = ["web"]
 ssr = ["axum", "tokio", "dioxus-server/axum", "tower-http", "http"]
 web = ["dioxus-web", "dioxus-router/web"]
-
-[profile.release]
-lto = true
-panic = "abort"
-opt-level = 3
-strip = true
-codegen-units = 1

+ 13 - 0
packages/server/examples/axum-router/src/main.rs

@@ -20,6 +20,19 @@ fn main() {
     );
     #[cfg(feature = "ssr")]
     {
+        // Start hot reloading
+        hot_reload_init!(dioxus_hot_reload::Config::new().with_rebuild_callback(|| {
+            execute::shell("dioxus build --features web")
+                .spawn()
+                .unwrap()
+                .wait()
+                .unwrap();
+            execute::shell("cargo run --features ssr --no-default-features")
+                .spawn()
+                .unwrap();
+            true
+        }));
+
         use axum::extract::State;
         PostServerData::register().unwrap();
         GetServerData::register().unwrap();

+ 1 - 7
packages/server/examples/salvo-hello-world/Cargo.toml

@@ -13,15 +13,9 @@ dioxus-server = { path = "../../" }
 tokio = { version = "1.27.0", features = ["full"], optional = true }
 serde = "1.0.159"
 salvo = { version = "0.37.9", optional = true }
+execute = "0.2.12"
 
 [features]
 default = ["web"]
 ssr = ["salvo", "tokio", "dioxus-server/salvo"]
 web = ["dioxus-web"]
-
-[profile.release]
-lto = true
-panic = "abort"
-opt-level = 3
-strip = true
-codegen-units = 1

+ 13 - 0
packages/server/examples/salvo-hello-world/src/main.rs

@@ -19,6 +19,19 @@ fn main() {
     );
     #[cfg(feature = "ssr")]
     {
+        // Start hot reloading
+        hot_reload_init!(dioxus_hot_reload::Config::new().with_rebuild_callback(|| {
+            execute::shell("dioxus build --features web")
+                .spawn()
+                .unwrap()
+                .wait()
+                .unwrap();
+            execute::shell("cargo run --features ssr --no-default-features")
+                .spawn()
+                .unwrap();
+            true
+        }));
+
         use salvo::prelude::*;
         PostServerData::register().unwrap();
         GetServerData::register().unwrap();

+ 1 - 7
packages/server/examples/warp-hello-world/Cargo.toml

@@ -13,15 +13,9 @@ dioxus-server = { path = "../../" }
 tokio = { version = "1.27.0", features = ["full"], optional = true }
 serde = "1.0.159"
 warp = { version = "0.3.3", optional = true }
+execute = "0.2.12"
 
 [features]
 default = ["web"]
 ssr = ["warp", "tokio", "dioxus-server/warp"]
 web = ["dioxus-web"]
-
-[profile.release]
-lto = true
-panic = "abort"
-opt-level = 3
-strip = true
-codegen-units = 1

+ 14 - 1
packages/server/examples/warp-hello-world/src/main.rs

@@ -19,6 +19,19 @@ fn main() {
     );
     #[cfg(feature = "ssr")]
     {
+        // Start hot reloading
+        hot_reload_init!(dioxus_hot_reload::Config::new().with_rebuild_callback(|| {
+            execute::shell("dioxus build --features web")
+                .spawn()
+                .unwrap()
+                .wait()
+                .unwrap();
+            execute::shell("cargo run --features ssr --no-default-features")
+                .spawn()
+                .unwrap();
+            true
+        }));
+
         PostServerData::register().unwrap();
         GetServerData::register().unwrap();
         tokio::runtime::Runtime::new()
@@ -45,7 +58,7 @@ fn app(cx: Scope<AppProps>) -> Element {
 
     cx.render(rsx! {
         h1 { "High-Five counter: {count}" }
-        button { onclick: move |_| count += 1, "Up high!" }
+        button { onclick: move |_| count += 10, "Up high!" }
         button { onclick: move |_| count -= 1, "Down low!" }
         button {
             onclick: move |_| {

+ 7 - 11
packages/server/src/adapters/axum_adapter.rs

@@ -290,11 +290,7 @@ where
             self.nest(
                 "/_dioxus",
                 Router::new()
-                    .route(
-                        "/hot_reload",
-                        get(hot_reload_handler)
-                            .with_state(crate::hot_reload::HotReloadState::default()),
-                    )
+                    .route("/hot_reload", get(hot_reload_handler))
                     .route(
                         "/disconnect",
                         get(|ws: WebSocketUpgrade| async {
@@ -420,14 +416,13 @@ fn report_err<E: Error>(e: E) -> Response<BoxBody> {
 
 /// A handler for Dioxus web hot reload websocket. This will send the updated static parts of the RSX to the client when they change.
 #[cfg(all(debug_assertions, feature = "hot-reload", feature = "ssr"))]
-pub async fn hot_reload_handler(
-    ws: WebSocketUpgrade,
-    State(state): State<crate::hot_reload::HotReloadState>,
-) -> impl IntoResponse {
+pub async fn hot_reload_handler(ws: WebSocketUpgrade) -> impl IntoResponse {
     use axum::extract::ws::Message;
     use futures_util::StreamExt;
 
-    ws.on_upgrade(|mut socket| async move {
+    let state = crate::hot_reload::spawn_hot_reload().await;
+
+    ws.on_upgrade(move |mut socket| async move {
         println!("🔥 Hot Reload WebSocket connected");
         {
             // update any rsx calls that changed before the websocket connected.
@@ -448,7 +443,8 @@ pub async fn hot_reload_handler(
             println!("finished");
         }
 
-        let mut rx = tokio_stream::wrappers::WatchStream::from_changes(state.message_receiver);
+        let mut rx =
+            tokio_stream::wrappers::WatchStream::from_changes(state.message_receiver.clone());
         while let Some(change) = rx.next().await {
             if let Some(template) = change {
                 let template = { serde_json::to_string(&template).unwrap() };

+ 7 - 8
packages/server/src/adapters/salvo_adapter.rs

@@ -455,9 +455,7 @@ impl HotReloadHandler {
 /// A handler for Dioxus web hot reload websocket. This will send the updated static parts of the RSX to the client when they change.
 #[cfg(all(debug_assertions, feature = "hot-reload", feature = "ssr"))]
 #[derive(Default)]
-pub struct HotReloadHandler {
-    state: crate::hot_reload::HotReloadState,
-}
+pub struct HotReloadHandler;
 
 #[cfg(all(debug_assertions, feature = "hot-reload", feature = "ssr"))]
 #[handler]
@@ -471,10 +469,10 @@ impl HotReloadHandler {
         use salvo::ws::Message;
         use salvo::ws::WebSocketUpgrade;
 
-        let state = self.state.clone();
+        let state = crate::hot_reload::spawn_hot_reload().await;
 
         WebSocketUpgrade::new()
-            .upgrade(req, res, |mut websocket| async move {
+            .upgrade(req, res, move |mut websocket| async move {
                 use futures_util::StreamExt;
 
                 println!("🔥 Hot Reload WebSocket connected");
@@ -497,8 +495,9 @@ impl HotReloadHandler {
                     println!("finished");
                 }
 
-                let mut rx =
-                    tokio_stream::wrappers::WatchStream::from_changes(state.message_receiver);
+                let mut rx = tokio_stream::wrappers::WatchStream::from_changes(
+                    state.message_receiver.clone(),
+                );
                 while let Some(change) = rx.next().await {
                     if let Some(template) = change {
                         let template = { serde_json::to_string(&template).unwrap() };
@@ -518,7 +517,7 @@ async fn ignore_ws(req: &mut Request, res: &mut Response) -> Result<(), salvo::h
     use salvo::ws::WebSocketUpgrade;
     WebSocketUpgrade::new()
         .upgrade(req, res, |mut ws| async move {
-            let _ = dbg!(ws.send(salvo::ws::Message::text("connected")).await);
+            let _ = ws.send(salvo::ws::Message::text("connected")).await;
             while let Some(msg) = ws.recv().await {
                 if msg.is_err() {
                     return;

+ 15 - 12
packages/server/src/adapters/warp_adapter.rs

@@ -372,12 +372,10 @@ pub fn connect_hot_reload() -> impl Filter<Extract = (impl Reply,), Error = warp
         use futures_util::StreamExt;
         use warp::ws::Message;
 
-        let state = HotReloadState::default();
-
-        warp::path!("_dioxus" / "hot_reload")
+        let hot_reload = warp::path!("_dioxus" / "hot_reload")
+            .and(warp::any().then(|| crate::hot_reload::spawn_hot_reload()))
             .and(warp::ws())
-            .and(warp::any().map(move || state.clone()))
-            .map(move |ws: warp::ws::Ws, state: HotReloadState| {
+            .map(move |state: &'static HotReloadState, ws: warp::ws::Ws| {
                 #[cfg(all(debug_assertions, feature = "hot-reload", feature = "ssr"))]
                 ws.on_upgrade(move |mut websocket| {
                     async move {
@@ -404,7 +402,7 @@ pub fn connect_hot_reload() -> impl Filter<Extract = (impl Reply,), Error = warp
                         }
 
                         let mut rx = tokio_stream::wrappers::WatchStream::from_changes(
-                            state.message_receiver,
+                            state.message_receiver.clone(),
                         );
                         while let Some(change) = rx.next().await {
                             if let Some(template) = change {
@@ -416,9 +414,12 @@ pub fn connect_hot_reload() -> impl Filter<Extract = (impl Reply,), Error = warp
                         }
                     }
                 })
-            })
-            .or(warp::path!("_dioxus" / "disconnect").and(warp::ws()).map(
-                move |ws: warp::ws::Ws| {
+            });
+        let disconnect =
+            warp::path!("_dioxus" / "disconnect")
+                .and(warp::ws())
+                .map(move |ws: warp::ws::Ws| {
+                    println!("disconnect");
                     #[cfg(all(debug_assertions, feature = "hot-reload", feature = "ssr"))]
                     ws.on_upgrade(move |mut websocket| async move {
                         struct DisconnectOnDrop(Option<warp::ws::WebSocket>);
@@ -432,10 +433,12 @@ pub fn connect_hot_reload() -> impl Filter<Extract = (impl Reply,), Error = warp
                         let mut ws = DisconnectOnDrop(Some(websocket));
 
                         loop {
-                            ws.0.as_mut().unwrap().next().await;
+                            if ws.0.as_mut().unwrap().next().await.is_none() {
+                                break;
+                            }
                         }
                     })
-                },
-            ))
+                });
+        disconnect.or(hot_reload)
     }
 }

+ 15 - 0
packages/server/src/hot_reload.rs

@@ -44,3 +44,18 @@ impl Default for HotReloadState {
         }
     }
 }
+
+// Hot reloading can be expensive to start so we spawn a new thread
+static HOT_RELOAD_STATE: tokio::sync::OnceCell<HotReloadState> = tokio::sync::OnceCell::const_new();
+pub(crate) async fn spawn_hot_reload() -> &'static HotReloadState {
+    HOT_RELOAD_STATE
+        .get_or_init(|| async {
+            println!("spinning up hot reloading");
+            let r = tokio::task::spawn_blocking(HotReloadState::default)
+                .await
+                .unwrap();
+            println!("hot reloading ready");
+            r
+        })
+        .await
+}

+ 4 - 4
packages/server/src/render.rs

@@ -55,6 +55,7 @@ impl SSRState {
 
         #[cfg(all(debug_assertions, feature = "hot-reload"))]
         {
+            // In debug mode, we need to add a script to the page that will reload the page if the websocket disconnects to make full recompile hot reloads work
             let disconnect_js = r#"(function () {
     const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
     const url = protocol + '//' + window.location.host + '/_dioxus/disconnect';
@@ -77,10 +78,9 @@ impl SSRState {
 
     // on initial page load connect to the disconnect ws
     const ws = new WebSocket(url);
-    ws.onopen = () => {
-        // if we disconnect, start polling
-        ws.onclose = reload_upon_connect;
-    };
+    // if we disconnect, start polling
+    ws.onmessage = (m) => {console.log(m)};
+    ws.onclose = reload_upon_connect;
 })()"#;
 
             html += r#"<script>"#;