Skip to content

feat: develoment mode with hot-reloading #36

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 22 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
9af9c49
feat: add nix shell
fahdfady Feb 13, 2025
48f1903
feat: add a simple watcher
fahdfady Feb 16, 2025
16142af
add dev to cli
fahdfady Feb 17, 2025
73c4d33
feat: add dev to cli
fahdfady Feb 18, 2025
32f8823
fix watching events nad logging them
fahdfady Feb 18, 2025
1a32395
feat: add crate "metassr-watcher
fahdfady Feb 19, 2025
ceb3712
add a dummy rebuilder module
fahdfady Feb 19, 2025
02d95c4
logging out rebuilds
fahdfady Feb 19, 2025
96891e9
fix typo in path binding
fahdfady Feb 20, 2025
cedb5ea
feat: dummy single-page rebuilder implementation
fahdfady Feb 20, 2025
058e782
integrate rebuilder in dev server mode
fahdfady Feb 20, 2025
0c3422d
add watching for both src and static directories
fahdfady Feb 20, 2025
1b22a4b
abstract the start server in dev server mode
fahdfady Feb 20, 2025
9f4df0e
feat(devserver): add file change handling
fahdfady Feb 20, 2025
1cf2b79
add debug output for rebuild paths and dev command
fahdfady Feb 23, 2025
77225ff
fix(rebuilder): correct error handling for irrelevant events
fahdfady Feb 23, 2025
8cf3172
fix(main cli): use current directory for server path in dev mode
fahdfady Feb 23, 2025
20623bd
fix(rebuilder): start building on changes
fahdfady Feb 23, 2025
dc587fb
fix(rebuilder): update output directory handling and improve logging
fahdfady Feb 23, 2025
9107fe9
metassr-server(deps): add tokio-tungstenite, futures-util, serde
fahdfady Feb 26, 2025
b63d0c3
feat: implement LiveReload, the server-side of websocket in devmode
fahdfady Feb 26, 2025
5f31a39
feat(notworking): implement LiveReload, the client-side of websocket …
fahdfady Feb 26, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
217 changes: 180 additions & 37 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ tower-service = "0.3.3"
metassr-create = { path = "crates/metassr-create" }
metassr-bundler = { path = "crates/metassr-bundler" }
metassr-fs-analyzer = { path = "crates/metassr-fs-analyzer" }
metassr-watcher = { path = "crates/metassr-watcher" }

[workspace]
members = [
Expand All @@ -50,6 +51,7 @@ members = [
"crates/metassr-create",
"crates/metassr-bundler",
"crates/metassr-fs-analyzer",
"crates/metassr-watcher",
]

[[bin]]
Expand Down
7 changes: 7 additions & 0 deletions crates/metassr-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,19 @@ edition = "2021"
anyhow = "1.0.82"
axum = "0.7.5"
chrono = "0.4.38"
metacall = "0.4.1"
metassr-build = { path = "../metassr-build" }
metassr-fs-analyzer = { path = "../metassr-fs-analyzer" }
metassr-utils = { path = "../metassr-utils" }
metassr-watcher = { path = "../metassr-watcher" }
serde_json = "1.0.122"
tokio = { version = "1.36.0", features = ["full"] }
tokio-tungstenite = "0.26.2"
futures-util = "0.3.31"
serde = "1.0.218"
tower-http = { version = "0.5.2", features = ["trace", "fs"] }
tower-layer = "0.3.3"
tower-service = "0.3.3"
tracing = "0.1.40"
notify = { version = "8.0.0", features = ["serde"] }
walkdir = "2"
72 changes: 60 additions & 12 deletions crates/metassr-server/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// crates/metassr-server/src/lib.rs
mod fallback;
mod handler;
mod layers;
pub mod live_reload;
pub mod rebuilder;
mod router;

use fallback::Fallback;
Expand All @@ -9,11 +12,25 @@ use layers::tracing::{LayerSetup, TracingLayer, TracingLayerOptions};

use anyhow::Result;
use axum::{http::StatusCode, response::Redirect, Router};
use axum::{response::IntoResponse, routing::get};
use live_reload::LiveReloadServer;
use rebuilder::Rebuilder;
use router::RouterMut;
use std::path::{Path, PathBuf};
use std::{
path::{Path, PathBuf},
sync::Arc,
};
use tokio::net::TcpListener;
use tower_http::services::ServeDir;
use tower_http::services::ServeFile;
use tracing::info;

#[derive(Debug, Clone, Copy)]
pub enum ServerMode {
Development,
Production,
}

#[derive(Debug, Clone, Copy)]
pub enum RunningType {
SSG,
Expand All @@ -25,6 +42,7 @@ pub struct ServerConfigs {
pub _enable_http_logging: bool,
pub root_path: PathBuf,
pub running_type: RunningType,
pub mode: ServerMode,
}

pub struct Server {
Expand All @@ -36,7 +54,7 @@ impl Server {
Self { configs }
}

pub async fn run(&self) -> Result<()> {
pub async fn run(&self, rebuilder: Option<Arc<Rebuilder>>) -> Result<()> {
let listener =
tokio::net::TcpListener::bind(format!("0.0.0.0:{}", self.configs.port)).await?;

Expand All @@ -47,11 +65,35 @@ impl Server {
self.configs.root_path.to_str().unwrap()
));

let mut app = RouterMut::from(
Router::new()
.nest_service("/static", ServeDir::new(&static_dir))
.nest_service("/dist", ServeDir::new(&dist_dir)),
);
let mut base_router: Router<()> = Router::new()
.nest_service("/static", ServeDir::new(&static_dir))
.nest_service("/dist", ServeDir::new(&dist_dir));

if let ServerMode::Development = self.configs.mode {
let live_reload_script = include_str!("scripts/live-reload.js");
base_router = base_router.route(
"/livereload/script.js", //todo check this path
get(|| async {
axum::response::Response::builder()
.header("Content-Type", "application/javascript")
.body(live_reload_script.to_string())
.unwrap()
}),
);
// Start the WebSocket server for live reload
let ws_listener = TcpListener::bind("127.0.0.1:3001").await?;
println!("0000000000000 {:?}", ws_listener);
if let Some(rebuilder) = rebuilder {
tokio::spawn(async move {
while let Ok((stream, addr)) = ws_listener.accept().await {
let live_reload = LiveReloadServer::new(rebuilder.subscribe());
tokio::spawn(live_reload.handle_connection(stream, addr));
}
});
}
}

let mut app: RouterMut<()> = RouterMut::from(base_router);

match self.configs.running_type {
RunningType::SSG => {
Expand All @@ -65,24 +107,30 @@ impl Server {
.to_html(),
)
};
app.fallback(fallback)
app.fallback(fallback);
}
RunningType::SSR => app.fallback(|| async { Redirect::to("/_notfound") }),
}

PagesHandler::new(&mut app, &dist_dir, self.configs.running_type)?.build()?;

// **Setting up layers**

// Tracing layer
TracingLayer::setup(
TracingLayerOptions {
enable_http_logging: self.configs._enable_http_logging,
// mode: self.configs.mode,
},
&mut app,
);

info!("Listening on http://{}", listener.local_addr()?);
info!(
"Listening on http://{} in {} mode",
listener.local_addr()?,
match self.configs.mode {
ServerMode::Development => "development",
ServerMode::Production => "production",
}
);

axum::serve(listener, app.app()).await?;
Ok(())
}
Expand Down
65 changes: 65 additions & 0 deletions crates/metassr-server/src/live_reload.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// crates/metassr-server/src/live_reload.rs
use crate::rebuilder::RebuildType;
use futures_util::{SinkExt, StreamExt};
use serde::Serialize;
use std::net::SocketAddr;
use tokio_tungstenite::tungstenite::Message;

use tokio::{net::TcpStream, sync::broadcast};
// use tokio_tungstenite::accept_async;

#[derive(Debug, Serialize)]
struct LiveReloadMessage {
type_: String,
path: Option<String>,
}

pub struct LiveReloadServer {
receiver: broadcast::Receiver<RebuildType>,
}

impl LiveReloadServer {
pub fn new(receiver: broadcast::Receiver<RebuildType>) -> Self {
Self { receiver }
}

pub async fn handle_connection(mut self, stream: TcpStream, addr: std::net::SocketAddr) {
let ws_stream = tokio_tungstenite::accept_async(stream)
.await
.expect("Error during websocket handshake");

tracing::info!("New LiveReload connection from: {}", addr);

let (mut ws_sender, mut ws_receiver) = ws_stream.split();

while let Ok(rebuild_type) = self.receiver.recv().await {
let message: LiveReloadMessage = match rebuild_type {
RebuildType::Page(ref path) => LiveReloadMessage {
type_: "page".to_string(),
path: Some(path.to_string_lossy().to_string()),
},
RebuildType::Layout => LiveReloadMessage {
type_: "layout".to_string(),
path: None,
},
RebuildType::Component => LiveReloadMessage {
type_: "component".to_string(),
path: None,
},
RebuildType::Style => LiveReloadMessage {
type_: "style".to_string(),
path: None,
},
RebuildType::Static => LiveReloadMessage {
type_: "static".to_string(),
path: None,
},
};
let message_json = serde_json::to_string(&message).unwrap();
if let Err(e) = ws_sender.send(Message::Text(message_json.into())).await {
tracing::error!("Failed to send LiveReload message: {}", e);
break;
}
}
}
}
Loading