wasm-server 1.0.2

A simple WASM dev server with live reloading
use actix_files as fs;
use actix_send_websocket::WebSocket;
use actix_web::web::Data;
use actix_web::{middleware, web, App, HttpResponse, HttpServer, Responder, Result};
use notify::{watcher, RecursiveMode, Watcher};
use std::error::Error;
use std::fmt::Display;
use std::path::PathBuf;
use std::thread;
use std::time::Duration;

static UUID: &str = "69046294-8cb0-4f81-970d-b1c2437966cf";

#[derive(Debug)]
struct GeneralError(String);

impl Display for GeneralError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
        write!(f, "{:?}", self)
    }
}
impl Error for GeneralError {}

fn build(path: PathBuf) -> Result<(), GeneralError> {
    use wasm_pack::command::build::*;
    let build_options = BuildOptions {
        path: Some(path),
        target: Target::Web,
        dev: true,
        release: false,
        out_dir: "pkg".to_string(),
        out_name: Some("entry".to_string()),
        ..BuildOptions::default()
    };

    to_general_error(to_general_error(Build::try_from_opts(build_options))?.run())?;
    Ok(())
}
fn to_general_error<T>(msg: Result<T, impl std::fmt::Debug>) -> Result<T, GeneralError> {
    msg.map_err(|err| GeneralError(format!("{:?}", err)))
}

fn print_error<T>(msg: Result<T, impl std::fmt::Debug>) {
    match msg {
        Ok(_) => (),
        Err(e) => println!("{:?}", e),
    }
}

fn watch(build_finished: Chan, path: PathBuf) {
    let (tx, rx) = std::sync::mpsc::channel();
    let mut watcher = watcher(tx, Duration::from_secs(10)).unwrap();

    watcher
        .watch(path.join("src"), RecursiveMode::Recursive)
        .unwrap();

    while let Ok(_) = rx.recv() {
        match build(path.clone()) {
            Ok(()) => {
                print_error(build_finished.try_send(&Reload));
            }
            Err(e) => {
                println!("{:?}", e);
            }
        }
    }
}

async fn ws(ws: WebSocket, build_finished: Data<Chan>) -> impl Responder {
    let (_, res, t) = ws.into_parts();

    let mut a: Chan = build_finished.get_ref().clone();
    actix_web::rt::spawn(async move {
        while let Some(Reload) = a.recv().await {
            print_error(t.text("reload"));
        }
    });

    res
}

async fn index() -> HttpResponse {
    HttpResponse::Ok().content_type("text/html").body(
        "
        <!DOCTYPE html>
        <html lang='en'>
        <head>
            <meta charset='UTF-8' />
            <meta name='viewport' content='width=device-width, initial-scale=1.0' />
            <title>🔥 wasm server</title>
            <script>
            let timeout = 1000;
            const open = () => {
                const ws = new WebSocket('ws://localhost:8080/ws-69046294-8cb0-4f81-970d-b1c2437966cf');
                ws.onopen = () => {timeout = 1000;};
                ws.onmessage = (event) => window.location.reload(false);
                ws.onclose = () => setTimeout(open);
                ws.onerror = () => setTimeout(open, timeout);
                timeout *= 1.5;
            }
           
            open();
            </script>
            <script type='module'>
            import init from './entry.js';
            init().then((m) => m.main());
            </script>
        </head>
        <body></body>
        </html>
        ",
    )
}

#[derive(Copy, Clone)]
struct Reload;
type Chan = broadcaster::BroadcastChannel<
    Reload,
    futures_channel::mpsc::Sender<Reload>,
    futures_channel::mpsc::Receiver<Reload>,
>;

fn get_path() -> PathBuf {
    let mut args = std::env::args();
    args.next();
    match args.next() {
        Some(path) => {
            println!("path: {}", path);
            let mut p = PathBuf::new();
            p.push(path);
            p
        }
        None => std::env::current_dir().unwrap(),
    }
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let path = get_path();

    let chan = broadcaster::BroadcastChannel::with_cap(16);
    let path2 = path.clone();
    let chan2 = chan.clone();
    thread::Builder::new()
        .name("file watcher".to_string())
        .spawn(move || {
            watch(chan, path);
        })?;
    println!("running on http://127.0.0.1:8080");

    HttpServer::new(move || {
        App::new()
            .data(chan2.clone())
            .wrap(middleware::DefaultHeaders::new().header("Cache-Control", "no-cache"))
            .route("/", web::get().to(index))
            .route(format!("/ws-{}", UUID).as_str(), web::get().to(ws))
            .service(fs::Files::new("/", path2.join("pkg")))
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await?;

    Ok(())
}