spawn hot reloading in a seperate thread

This commit is contained in:
Evan Almloff 2023-05-01 17:34:30 -05:00
parent 126ed2f9b8
commit 064bee4a9f
13 changed files with 106 additions and 65 deletions

View file

@ -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

View file

@ -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}"
})

View file

@ -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

View file

@ -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();

View file

@ -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

View file

@ -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();

View file

@ -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

View file

@ -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 |_| {

View file

@ -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() };

View file

@ -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;

View file

@ -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)
}
}

View file

@ -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
}

View file

@ -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>"#;