Skip to content

Instantly share code, notes, and snippets.

@yurivish
Created January 7, 2026 21:39
Show Gist options
  • Select an option

  • Save yurivish/3d365fd6657d07c4ff6b277e1d089b84 to your computer and use it in GitHub Desktop.

Select an option

Save yurivish/3d365fd6657d07c4ff6b277e1d089b84 to your computer and use it in GitHub Desktop.
// Prototyping a live-reload workflow with auto-js-minification built-in. License is MIT: https://opensource.org/license/mit
use axum::{
Router,
extract::Path,
http::{StatusCode, header},
response::IntoResponse,
routing::get,
};
use clap::Parser;
use maud::{Markup, html};
use notify::Watcher;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
use tokio::net::TcpListener;
use tower::ServiceBuilder;
use tower_http::compression::CompressionLayer;
use tower_http::decompression::RequestDecompressionLayer;
use tower_http::services::{ServeDir, ServeFile};
use tower_livereload::LiveReloadLayer;
#[derive(Parser, Debug, Clone)]
#[command(author, version, about, long_about = None)]
struct Config {
/// Directory for serving static assets
#[arg(long, default_value = "src/assets")]
assets_dir: PathBuf,
}
fn compress_js(code: &str) -> Result<String, String> {
use oxc_allocator::Allocator;
use oxc_codegen::{Codegen, CodegenOptions};
use oxc_minifier::{MangleOptions, Minifier, MinifierOptions};
use oxc_parser::Parser;
use oxc_span::SourceType;
let allocator = Allocator::default();
let source_type = SourceType::ts();
let ret = Parser::new(&allocator, code, source_type).parse();
// Check for parse errors
if !ret.errors.is_empty() {
let error_messages: Vec<String> = ret.errors.iter().map(|e| format!("{:?}", e)).collect();
return Err(format!("Parse errors: {}", error_messages.join(", ")));
}
let mut program = ret.program;
let options = MinifierOptions {
mangle: Some(MangleOptions {
top_level: false,
..Default::default()
}),
..Default::default()
};
let minifier = Minifier::new(options);
minifier.minify(&allocator, &mut program);
let codegen_options = CodegenOptions {
minify: true,
..Default::default()
};
let result = Codegen::new().with_options(codegen_options).build(&program);
Ok(result.code)
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!(
"{:?}",
compress_js("import { x } from './foo.js'; const x: number = 1 + 1; console.log(x);")
);
if true {
return Ok(());
}
let config = Config::parse();
// The reload interval configures the SSE `retry` between disconnects
let livereload = LiveReloadLayer::new().reload_interval(Duration::from_millis(200));
let state = AppState { config };
let reloader = livereload.reloader();
let mut watcher = notify::recommended_watcher(move |res: Result<notify::Event, _>| {
if let Ok(event) = res {
// Reload, unless it's just a read.
if !matches!(event.kind, notify::EventKind::Access(_)) {
reloader.reload();
}
}
})
.expect("failed to initialize watcher");
watcher
.watch(&state.config.assets_dir, notify::RecursiveMode::Recursive)
.expect("failed to watch assets folder");
let listener = TcpListener::bind("0.0.0.0:8080")
.await
.expect("cannot construct tokio::net::TcpListener");
println!("Listening on {}", listener.local_addr()?);
let app = app(livereload, state);
axum::serve(listener, app)
.await
.expect("failed to run http server");
Ok(())
}
struct AppState {
config: Config,
}
fn app(livereload: LiveReloadLayer, state: AppState) -> Router {
let state = Arc::new(state);
let assets_dir = &state.config.assets_dir;
let index_path = assets_dir.join("index.html");
let router = Router::new()
.route("/about", get(about))
.route("/js/{*path}", get(serve_js))
.with_state(state.clone())
.route_service("/", ServeDir::new(assets_dir))
.fallback_service(ServeFile::new(index_path));
router.layer(
ServiceBuilder::new()
.layer(RequestDecompressionLayer::new())
.layer(CompressionLayer::new())
.layer(livereload),
)
}
async fn serve_js(Path(path): Path<String>) -> impl IntoResponse {
let source_path = format!("static/js/{path}");
match tokio::fs::read_to_string(&source_path).await {
Ok(source) => {
let minified = minify_js(&source); // your minification fn
(
StatusCode::OK,
[(header::CONTENT_TYPE, "application/javascript")],
minified,
)
}
Err(_) => (
StatusCode::NOT_FOUND,
[(header::CONTENT_TYPE, "text/plain")],
"Not found".to_string(),
),
}
}
async fn about() -> Markup {
html! {
h1 data-init="123" { "Hello, World!" }
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment