Skip to content

Commit 50a9039

Browse files
author
liv
authored
Merge pull request #622 from axodotdev/feat/livereload
feat: add livereload to dev
2 parents 4cd4151 + d14cfdb commit 50a9039

4 files changed

Lines changed: 88 additions & 38 deletions

File tree

Cargo.lock

Lines changed: 17 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ thiserror = "1.0.37"
4040
tokio = { version = "1.20.1", features = ["full"] }
4141
toml = "0.5.9"
4242
tower-http = { version = "0.3.0", features = ["fs", "trace"] }
43+
tower-livereload = "0.8.0"
4344
tracing = "0.1"
4445
tracing-subscriber = { version = "0.3", features = ["fmt"] }
4546
url = "2.3.1"

src/commands/dev.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,12 @@ impl Dev {
124124
Build::new(self.project_root.clone(), self.config_path.clone()).run()?;
125125
}
126126

127-
// Spawn the serve process out into a separate thread so that we can loop through received events on this thread
128-
let _ = std::thread::spawn(move || Serve::new(self.port).run());
127+
let (ws_tx, ws_rx) = std::sync::mpsc::channel();
128+
// Spawn the serve process out into a separate thread so that we can loop through received
129+
// events on this thread.
130+
let _thread_handle = std::thread::spawn(move || {
131+
Serve::new(self.port).run_with_livereload(ws_rx).unwrap();
132+
});
129133
let addr = SocketAddr::from(([127, 0, 0, 1], self.port.unwrap_or(7979)));
130134
let msg = if config.build.path_prefix.is_some() {
131135
format!(
@@ -167,6 +171,12 @@ impl Dev {
167171
{
168172
eprintln!("{:?}", Report::new(e));
169173
continue;
174+
} else {
175+
// Reload page (this goes into the serve thread, which has spawned a subthread
176+
// to handle messages)
177+
ws_tx
178+
.send(())
179+
.expect("error when sending livereload message");
170180
}
171181
}
172182
}

src/commands/serve.rs

Lines changed: 58 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use camino::{Utf8Path, Utf8PathBuf};
22
use std::net::SocketAddr;
3+
use std::sync::mpsc::Receiver;
4+
use std::thread;
35

46
use oranda::config::Config;
57
use oranda::errors::*;
@@ -8,6 +10,7 @@ use axum::{http::StatusCode, routing::get_service, Router};
810

911
use clap::Parser;
1012
use tower_http::services::ServeDir;
13+
use tower_livereload::LiveReloadLayer;
1114

1215
#[derive(Debug, Default, Parser)]
1316
pub struct Serve {
@@ -23,19 +26,27 @@ impl Serve {
2326
}
2427

2528
pub fn run(&self) -> Result<()> {
26-
let workspace_config_path = &Utf8PathBuf::from("./oranda-workspace.json");
27-
let config = if workspace_config_path.exists() {
28-
Config::build(workspace_config_path)?
29+
let config = Self::build_config()?;
30+
if Utf8Path::new(&config.build.dist_dir).is_dir() {
31+
self.serve(&config.build.dist_dir, &config.build.path_prefix, None)?;
32+
Ok(())
2933
} else {
30-
Config::build(&Utf8PathBuf::from("./oranda.json"))?
31-
};
34+
Err(OrandaError::BuildNotFound {
35+
dist_dir: config.build.dist_dir.to_string(),
36+
})
37+
}
38+
}
39+
40+
pub fn run_with_livereload(&self, rx: Receiver<()>) -> Result<()> {
41+
let config = Self::build_config()?;
3242
if Utf8Path::new(&config.build.dist_dir).is_dir() {
33-
if let Some(prefix) = config.build.path_prefix {
34-
tracing::debug!("`path_prefix` configured: {}", &prefix);
35-
self.serve_prefix(&config.build.dist_dir, &prefix)?;
36-
} else {
37-
self.serve(&config.build.dist_dir)?;
38-
}
43+
let livereload = LiveReloadLayer::new();
44+
self.serve(
45+
&config.build.dist_dir,
46+
&config.build.path_prefix,
47+
Some((livereload, rx)),
48+
)?;
49+
3950
Ok(())
4051
} else {
4152
Err(OrandaError::BuildNotFound {
@@ -45,7 +56,12 @@ impl Serve {
4556
}
4657

4758
#[tokio::main]
48-
async fn serve(&self, dist_dir: &str) -> Result<()> {
59+
async fn serve(
60+
&self,
61+
dist_dir: &str,
62+
path_prefix: &Option<String>,
63+
livereload: Option<(LiveReloadLayer, Receiver<()>)>,
64+
) -> Result<()> {
4965
let serve_dir =
5066
get_service(ServeDir::new(dist_dir)).handle_error(|error: std::io::Error| async move {
5167
(
@@ -54,37 +70,45 @@ impl Serve {
5470
)
5571
});
5672

57-
let app = Router::new().nest_service("/", serve_dir);
58-
59-
let addr = SocketAddr::from(([127, 0, 0, 1], self.port));
60-
let msg = format!("Your project is available at: http://{}", addr);
61-
tracing::info!(success = true, "{}", &msg);
62-
axum::Server::bind(&addr)
63-
.serve(app.into_make_service())
64-
.await
65-
.expect("failed to start server");
66-
Ok(())
67-
}
73+
let prefix_route = if let Some(prefix) = path_prefix {
74+
format!("/{}", prefix)
75+
} else {
76+
"/".to_string()
77+
};
78+
let mut app = Router::new().nest_service(&prefix_route, serve_dir);
79+
if let Some(livereload) = livereload {
80+
let (livereload, rx) = livereload;
81+
let reloader = livereload.reloader();
82+
app = app.layer(livereload);
6883

69-
#[tokio::main]
70-
async fn serve_prefix(&self, dist_dir: &str, prefix: &str) -> Result<()> {
71-
let serve_dir =
72-
get_service(ServeDir::new(dist_dir)).handle_error(|error: std::io::Error| async move {
73-
(
74-
StatusCode::INTERNAL_SERVER_ERROR,
75-
format!("Unhandled internal error: {}", error),
76-
)
84+
// Because the server will later block this thread, spawn another thread to handle
85+
// reload request messages.
86+
thread::spawn(move || loop {
87+
rx.recv().expect("broken pipe");
88+
reloader.reload();
7789
});
78-
let prefix_route = format!("/{}", prefix);
79-
let app = Router::new().nest_service(&prefix_route, serve_dir);
90+
}
8091

8192
let addr = SocketAddr::from(([127, 0, 0, 1], self.port));
82-
let msg = format!("Your project is available at: http://{}/{}", addr, prefix);
93+
let msg = format!(
94+
"Your project is available at: http://{}/{}",
95+
addr,
96+
path_prefix.as_ref().unwrap_or(&String::new())
97+
);
8398
tracing::info!(success = true, "{}", &msg);
8499
axum::Server::bind(&addr)
85100
.serve(app.into_make_service())
86101
.await
87102
.expect("failed to start server");
88103
Ok(())
89104
}
105+
106+
fn build_config() -> Result<Config> {
107+
let workspace_config_path = &Utf8PathBuf::from("./oranda-workspace.json");
108+
if workspace_config_path.exists() {
109+
Config::build(workspace_config_path)
110+
} else {
111+
Config::build(&Utf8PathBuf::from("./oranda.json"))
112+
}
113+
}
90114
}

0 commit comments

Comments
 (0)