feat: add dev-server
This commit is contained in:
1 parent
281a075c6c
commit
5e710f6693
11 files changed
+307
-148
No files matched your search
+5
-2
@@ -21,8 +21,6 @@ cargo_toml = "0.10.0"
|
|||||||
futures = "0.3.12"
|
futures = "0.3.12"
|
||||||
notify = "4.0.17"
|
notify = "4.0.17"
|
||||||
html_parser = "0.6.2"
|
html_parser = "0.6.2"
|
||||||
tui = { version = "0.16.0", features = ["crossterm"] }
|
|
||||||
crossterm = "0.22.1"
|
|
||||||
binary-install = "0.0.2"
|
binary-install = "0.0.2"
|
||||||
convert_case = "0.5.0"
|
convert_case = "0.5.0"
|
||||||
structopt = "0.3.25"
|
structopt = "0.3.25"
|
||||||
@@ -30,6 +28,11 @@ cargo_metadata = "0.14.1"
|
|||||||
tokio = { version = "1.15.0", features = ["full"] }
|
tokio = { version = "1.15.0", features = ["full"] }
|
||||||
atty = "0.2.14"
|
atty = "0.2.14"
|
||||||
|
|
||||||
|
axum = { version = "0.4.4", features = ["ws", "headers"] }
|
||||||
|
tower-http = { version = "0.2.0", features = ["fs", "trace"] }
|
||||||
|
headers = "0.3"
|
||||||
|
# hyper = { version = "0.14.11", features = ["full"] }
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
name = "dioxus"
|
name = "dioxus"
|
||||||
+4
-7
@@ -37,7 +37,6 @@ pub fn build(config: &CrateConfig) -> Result<()> {
|
|||||||
.arg("wasm32-unknown-unknown")
|
.arg("wasm32-unknown-unknown")
|
||||||
.stdout(std::process::Stdio::piped())
|
.stdout(std::process::Stdio::piped())
|
||||||
.stderr(std::process::Stdio::piped());
|
.stderr(std::process::Stdio::piped());
|
||||||
;
|
|
||||||
|
|
||||||
if config.release {
|
if config.release {
|
||||||
cmd.arg("--release");
|
cmd.arg("--release");
|
||||||
@@ -54,7 +53,7 @@ pub fn build(config: &CrateConfig) -> Result<()> {
|
|||||||
let output = child.wait()?;
|
let output = child.wait()?;
|
||||||
|
|
||||||
if output.success() {
|
if output.success() {
|
||||||
log::info!("Build complete! {:?}", reason);
|
log::info!("Build complete!");
|
||||||
} else {
|
} else {
|
||||||
log::error!("Build failed!");
|
log::error!("Build failed!");
|
||||||
let mut reason = String::new();
|
let mut reason = String::new();
|
||||||
@@ -63,7 +62,7 @@ pub fn build(config: &CrateConfig) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// [2] Establish the output directory structure
|
// [2] Establish the output directory structure
|
||||||
let bindgen_outdir = out_dir.join("wasm");
|
let bindgen_outdir = out_dir.join("assets");
|
||||||
|
|
||||||
// [3] Bindgen the final binary for use easy linking
|
// [3] Bindgen the final binary for use easy linking
|
||||||
let mut bindgen_builder = Bindgen::new();
|
let mut bindgen_builder = Bindgen::new();
|
||||||
@@ -100,8 +99,6 @@ pub fn build(config: &CrateConfig) -> Result<()> {
|
|||||||
// [5] Generate the html file with the module name
|
// [5] Generate the html file with the module name
|
||||||
// TODO: support names via options
|
// TODO: support names via options
|
||||||
log::info!("Writing to '{:#?}' directory...", out_dir);
|
log::info!("Writing to '{:#?}' directory...", out_dir);
|
||||||
let mut file = std::fs::File::create(out_dir.join("index.html"))?;
|
|
||||||
file.write_all(gen_page("./wasm/module.js").as_str().as_bytes())?;
|
|
||||||
|
|
||||||
let copy_options = fs_extra::dir::CopyOptions::new();
|
let copy_options = fs_extra::dir::CopyOptions::new();
|
||||||
if static_dir.is_dir() {
|
if static_dir.is_dir() {
|
||||||
@@ -118,7 +115,7 @@ pub fn build(config: &CrateConfig) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gen_page(module: &str) -> String {
|
pub fn gen_page(module: &str) -> String {
|
||||||
format!(
|
format!(
|
||||||
r#"
|
r#"
|
||||||
<html>
|
<html>
|
||||||
@@ -132,7 +129,7 @@ fn gen_page(module: &str) -> String {
|
|||||||
<!-- Note the usage of `type=module` here as this is an ES6 module -->
|
<!-- Note the usage of `type=module` here as this is an ES6 module -->
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import init from "{}";
|
import init from "{}";
|
||||||
init("./wasm/module_bg.wasm");
|
init("./assets/module_bg.wasm");
|
||||||
</script>
|
</script>
|
||||||
<div id="dioxusroot"> </div>
|
<div id="dioxusroot"> </div>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -13,8 +13,7 @@ pub struct Build {
|
|||||||
|
|
||||||
impl Build {
|
impl Build {
|
||||||
pub fn build(self) -> anyhow::Result<()> {
|
pub fn build(self) -> anyhow::Result<()> {
|
||||||
|
let mut crate_config = crate::CrateConfig::new()?;
|
||||||
let mut crate_config = crate::CrateConfig::new()?;
|
|
||||||
|
|
||||||
// change the relase state.
|
// change the relase state.
|
||||||
crate_config.with_release(self.build.release);
|
crate_config.with_release(self.build.release);
|
||||||
@@ -23,4 +22,4 @@ impl Build {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,6 +67,26 @@ pub struct ConfigOptsBuild {
|
|||||||
pub pattern_params: Option<HashMap<String, String>>,
|
pub pattern_params: Option<HashMap<String, String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, Deserialize, StructOpt)]
|
||||||
|
pub struct ConfigOptsServe {
|
||||||
|
/// The index HTML file to drive the bundling process [default: index.html]
|
||||||
|
#[structopt(parse(from_os_str))]
|
||||||
|
pub target: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// Build in release mode [default: false]
|
||||||
|
#[structopt(long)]
|
||||||
|
#[serde(default)]
|
||||||
|
pub release: bool,
|
||||||
|
|
||||||
|
/// The output dir for all final assets [default: dist]
|
||||||
|
#[structopt(short, long, parse(from_os_str))]
|
||||||
|
pub dist: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// The public URL from which assets are to be served [default: /]
|
||||||
|
#[structopt(long, parse(from_str=parse_public_url))]
|
||||||
|
pub public_url: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
/// Ensure the given value for `--public-url` is formatted correctly.
|
/// Ensure the given value for `--public-url` is formatted correctly.
|
||||||
pub fn parse_public_url(val: &str) -> String {
|
pub fn parse_public_url(val: &str) -> String {
|
||||||
let prefix = if !val.starts_with('/') { "/" } else { "" };
|
let prefix = if !val.starts_with('/') { "/" } else { "" };
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ pub enum Commands {
|
|||||||
Translate(translate::Translate),
|
Translate(translate::Translate),
|
||||||
// /// Build, watch & serve the Rust WASM app and all of its assets.
|
// /// Build, watch & serve the Rust WASM app and all of its assets.
|
||||||
Serve(serve::Serve),
|
Serve(serve::Serve),
|
||||||
|
|
||||||
// /// Clean output artifacts.
|
// /// Clean output artifacts.
|
||||||
// Clean(clean::Clean),
|
// Clean(clean::Clean),
|
||||||
|
|
||||||
|
|||||||
+118
-118
@@ -1,145 +1,145 @@
|
|||||||
use crate::{cli::DevelopOptions, config::CrateConfig, error::Result};
|
// use crate::{cli::DevelopOptions, config::CrateConfig, error::Result};
|
||||||
use async_std::prelude::FutureExt;
|
// use async_std::prelude::FutureExt;
|
||||||
|
|
||||||
use log::info;
|
// use log::info;
|
||||||
use notify::{RecommendedWatcher, RecursiveMode, Watcher};
|
// use notify::{RecommendedWatcher, RecursiveMode, Watcher};
|
||||||
use std::path::PathBuf;
|
// use std::path::PathBuf;
|
||||||
use std::sync::atomic::AtomicBool;
|
// use std::sync::atomic::AtomicBool;
|
||||||
use std::sync::Arc;
|
// use std::sync::Arc;
|
||||||
use tide::http::mime::HTML;
|
// use tide::http::mime::HTML;
|
||||||
use tide::http::Mime;
|
// use tide::http::Mime;
|
||||||
|
|
||||||
pub struct DevelopState {
|
// pub struct DevelopState {
|
||||||
//
|
// //
|
||||||
reload_on_change: bool,
|
// reload_on_change: bool,
|
||||||
}
|
// }
|
||||||
|
|
||||||
pub async fn develop(options: DevelopOptions) -> Result<()> {
|
// pub async fn develop(options: DevelopOptions) -> Result<()> {
|
||||||
//
|
// //
|
||||||
log::info!("Starting development server 🚀");
|
// log::info!("Starting development server 🚀");
|
||||||
let mut cfg = CrateConfig::new()?;
|
// let mut cfg = CrateConfig::new()?;
|
||||||
cfg.with_develop_options(&options);
|
// cfg.with_develop_options(&options);
|
||||||
|
|
||||||
let out_dir = cfg.out_dir.clone();
|
// let out_dir = cfg.out_dir.clone();
|
||||||
|
|
||||||
let is_err = Arc::new(AtomicBool::new(false));
|
// let is_err = Arc::new(AtomicBool::new(false));
|
||||||
|
|
||||||
// Spawn the server onto a seperate task
|
// // Spawn the server onto a seperate task
|
||||||
// This lets the task progress while we handle file updates
|
// // This lets the task progress while we handle file updates
|
||||||
let server = async_std::task::spawn(launch_server(out_dir, is_err.clone()));
|
// let server = async_std::task::spawn(launch_server(out_dir, is_err.clone()));
|
||||||
let watcher = async_std::task::spawn(watch_directory(cfg.clone(), is_err.clone()));
|
// let watcher = async_std::task::spawn(watch_directory(cfg.clone(), is_err.clone()));
|
||||||
|
|
||||||
match server.race(watcher).await {
|
// match server.race(watcher).await {
|
||||||
Err(e) => log::warn!("Error running development server, {:?}", e),
|
// Err(e) => log::warn!("Error running development server, {:?}", e),
|
||||||
_ => {}
|
// _ => {}
|
||||||
}
|
// }
|
||||||
|
|
||||||
Ok(())
|
// Ok(())
|
||||||
}
|
// }
|
||||||
|
|
||||||
async fn watch_directory(config: CrateConfig, is_err: ErrStatus) -> Result<()> {
|
// async fn watch_directory(config: CrateConfig, is_err: ErrStatus) -> Result<()> {
|
||||||
// Create a channel to receive the events.
|
// // Create a channel to receive the events.
|
||||||
let (watcher_tx, watcher_rx) = async_std::channel::bounded(100);
|
// let (watcher_tx, watcher_rx) = async_std::channel::bounded(100);
|
||||||
|
|
||||||
// Automatically select the best implementation for your platform.
|
// // Automatically select the best implementation for your platform.
|
||||||
// You can also access each implementation directly e.g. INotifyWatcher.
|
// // You can also access each implementation directly e.g. INotifyWatcher.
|
||||||
let mut watcher: RecommendedWatcher = Watcher::new(move |res| {
|
// let mut watcher: RecommendedWatcher = Watcher::new(move |res| {
|
||||||
async_std::task::block_on(watcher_tx.send(res));
|
// async_std::task::block_on(watcher_tx.send(res));
|
||||||
// send an event
|
// // send an event
|
||||||
let _ = async_std::task::block_on(watcher_tx.send(res));
|
// let _ = async_std::task::block_on(watcher_tx.send(res));
|
||||||
})
|
// })
|
||||||
.expect("failed to make watcher");
|
// .expect("failed to make watcher");
|
||||||
|
|
||||||
let src_dir = crate::cargo::crate_root()?;
|
// let src_dir = crate::cargo::crate_root()?;
|
||||||
|
|
||||||
// Add a path to be watched. All files and directories at that path and
|
// // Add a path to be watched. All files and directories at that path and
|
||||||
// below will be monitored for changes.
|
// // below will be monitored for changes.
|
||||||
watcher
|
// watcher
|
||||||
.watch(&src_dir.join("src"), RecursiveMode::Recursive)
|
// .watch(&src_dir.join("src"), RecursiveMode::Recursive)
|
||||||
.expect("Failed to watch dir");
|
// .expect("Failed to watch dir");
|
||||||
|
|
||||||
match watcher.watch(&src_dir.join("examples"), RecursiveMode::Recursive) {
|
// match watcher.watch(&src_dir.join("examples"), RecursiveMode::Recursive) {
|
||||||
Ok(_) => {}
|
// Ok(_) => {}
|
||||||
Err(e) => log::warn!("Failed to watch examples dir, {:?}", e),
|
// Err(e) => log::warn!("Failed to watch examples dir, {:?}", e),
|
||||||
}
|
// }
|
||||||
|
|
||||||
'run: loop {
|
// 'run: loop {
|
||||||
match crate::builder::build(&config) {
|
// match crate::builder::build(&config) {
|
||||||
Ok(_) => {
|
// Ok(_) => {
|
||||||
is_err.store(false, std::sync::atomic::Ordering::Relaxed);
|
// is_err.store(false, std::sync::atomic::Ordering::Relaxed);
|
||||||
async_std::task::sleep(std::time::Duration::from_millis(500)).await;
|
// async_std::task::sleep(std::time::Duration::from_millis(500)).await;
|
||||||
}
|
// }
|
||||||
Err(err) => is_err.store(true, std::sync::atomic::Ordering::Relaxed),
|
// Err(err) => is_err.store(true, std::sync::atomic::Ordering::Relaxed),
|
||||||
};
|
// };
|
||||||
|
|
||||||
let mut msg = None;
|
// let mut msg = None;
|
||||||
loop {
|
// loop {
|
||||||
let new_msg = watcher_rx.recv().await.unwrap().unwrap();
|
// let new_msg = watcher_rx.recv().await.unwrap().unwrap();
|
||||||
if !watcher_rx.is_empty() {
|
// if !watcher_rx.is_empty() {
|
||||||
msg = Some(new_msg);
|
// msg = Some(new_msg);
|
||||||
break;
|
// break;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
info!("File updated, rebuilding app");
|
// info!("File updated, rebuilding app");
|
||||||
}
|
// }
|
||||||
Ok(())
|
// Ok(())
|
||||||
}
|
// }
|
||||||
|
|
||||||
async fn launch_server(outdir: PathBuf, is_err: ErrStatus) -> Result<()> {
|
// async fn launch_server(outdir: PathBuf, is_err: ErrStatus) -> Result<()> {
|
||||||
let _crate_dir = crate::cargo::crate_root()?;
|
// let _crate_dir = crate::cargo::crate_root()?;
|
||||||
let _workspace_dir = crate::cargo::workspace_root()?;
|
// let _workspace_dir = crate::cargo::workspace_root()?;
|
||||||
|
|
||||||
let mut app = tide::with_state(ServerState::new(outdir.to_owned(), is_err));
|
// let mut app = tide::with_state(ServerState::new(outdir.to_owned(), is_err));
|
||||||
|
|
||||||
let file_path = format!("{}/index.html", outdir.display());
|
// let file_path = format!("{}/index.html", outdir.display());
|
||||||
log::info!("Serving {}", file_path);
|
// log::info!("Serving {}", file_path);
|
||||||
let p = outdir.display().to_string();
|
// let p = outdir.display().to_string();
|
||||||
|
|
||||||
app.at("/")
|
// app.at("/")
|
||||||
.get(|req: tide::Request<ServerState>| async move {
|
// .get(|req: tide::Request<ServerState>| async move {
|
||||||
log::info!("Connected to development server");
|
// log::info!("Connected to development server");
|
||||||
let state = req.state();
|
// let state = req.state();
|
||||||
|
|
||||||
match state.is_err.load(std::sync::atomic::Ordering::Relaxed) {
|
// match state.is_err.load(std::sync::atomic::Ordering::Relaxed) {
|
||||||
true => {
|
// true => {
|
||||||
//
|
// //
|
||||||
let mut resp =
|
// let mut resp =
|
||||||
tide::Body::from_string(format!(include_str!("../err.html"), err = "_"));
|
// tide::Body::from_string(format!(include_str!("../err.html"), err = "_"));
|
||||||
resp.set_mime(HTML);
|
// resp.set_mime(HTML);
|
||||||
|
|
||||||
Ok(resp)
|
// Ok(resp)
|
||||||
}
|
// }
|
||||||
false => {
|
// false => {
|
||||||
Ok(tide::Body::from_file(state.serv_path.clone().join("index.html")).await?)
|
// Ok(tide::Body::from_file(state.serv_path.clone().join("index.html")).await?)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
})
|
// })
|
||||||
.serve_dir(p)?;
|
// .serve_dir(p)?;
|
||||||
// .serve_file(file_path)
|
// // .serve_file(file_path)
|
||||||
// .unwrap();
|
// // .unwrap();
|
||||||
|
|
||||||
let port = "8080";
|
// let port = "8080";
|
||||||
let serve_addr = format!("127.0.0.1:{}", port);
|
// let serve_addr = format!("127.0.0.1:{}", port);
|
||||||
|
|
||||||
info!("App available at http://{}/", serve_addr);
|
// info!("App available at http://{}/", serve_addr);
|
||||||
app.listen(serve_addr).await?;
|
// app.listen(serve_addr).await?;
|
||||||
Ok(())
|
// Ok(())
|
||||||
}
|
// }
|
||||||
|
|
||||||
/// https://github.com/http-rs/tide/blob/main/examples/state.rs
|
// /// https://github.com/http-rs/tide/blob/main/examples/state.rs
|
||||||
/// Tide seems to prefer using state instead of injecting into the app closure
|
// /// Tide seems to prefer using state instead of injecting into the app closure
|
||||||
/// The app closure needs to be static and
|
// /// The app closure needs to be static and
|
||||||
#[derive(Clone)]
|
// #[derive(Clone)]
|
||||||
struct ServerState {
|
// struct ServerState {
|
||||||
serv_path: PathBuf,
|
// serv_path: PathBuf,
|
||||||
is_err: ErrStatus,
|
// is_err: ErrStatus,
|
||||||
}
|
// }
|
||||||
|
|
||||||
type ErrStatus = Arc<AtomicBool>;
|
// type ErrStatus = Arc<AtomicBool>;
|
||||||
|
|
||||||
impl ServerState {
|
// impl ServerState {
|
||||||
fn new(serv_path: PathBuf, is_err: ErrStatus) -> Self {
|
// fn new(serv_path: PathBuf, is_err: ErrStatus) -> Self {
|
||||||
Self { serv_path, is_err }
|
// Self { serv_path, is_err }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
+30
-3
@@ -1,9 +1,36 @@
|
|||||||
use crate::cfg::ConfigOptsBuild;
|
use crate::{cfg::ConfigOptsServe, server};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::io::Write;
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
|
|
||||||
/// Build the Rust WASM app and all of its assets.
|
mod develop;
|
||||||
|
|
||||||
|
/// Run the WASM project on dev-server
|
||||||
#[derive(Clone, Debug, StructOpt)]
|
#[derive(Clone, Debug, StructOpt)]
|
||||||
#[structopt(name = "serve")]
|
#[structopt(name = "serve")]
|
||||||
pub struct Serve {}
|
pub struct Serve {
|
||||||
|
#[structopt(flatten)]
|
||||||
|
pub serve: ConfigOptsServe,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serve {
|
||||||
|
pub async fn serve(self) -> anyhow::Result<()> {
|
||||||
|
|
||||||
|
let mut crate_config = crate::CrateConfig::new()?;
|
||||||
|
// change the relase state.
|
||||||
|
crate_config.with_release(self.serve.release);
|
||||||
|
|
||||||
|
crate::builder::build(&crate_config).expect("build failed");
|
||||||
|
|
||||||
|
let serve_html = String::from(include_str!("../../server/serve.html"));
|
||||||
|
|
||||||
|
let mut file = std::fs::File::create(crate_config.out_dir.join("index.html"))?;
|
||||||
|
file.write_all(serve_html.as_bytes())?;
|
||||||
|
|
||||||
|
// start the develop server
|
||||||
|
server::startup(crate_config.clone()).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-1
@@ -1,4 +1,5 @@
|
|||||||
pub mod builder;
|
pub mod builder;
|
||||||
|
pub mod server;
|
||||||
pub use builder::*;
|
pub use builder::*;
|
||||||
|
|
||||||
pub mod cargo;
|
pub mod cargo;
|
||||||
@@ -16,4 +17,4 @@ pub use error::*;
|
|||||||
pub mod logging;
|
pub mod logging;
|
||||||
pub use logging::*;
|
pub use logging::*;
|
||||||
|
|
||||||
pub mod watch;
|
pub mod watch;
|
||||||
+11
-13
@@ -3,30 +3,28 @@ use structopt::StructOpt;
|
|||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
|
|
||||||
let args = Cli::from_args();
|
let args = Cli::from_args();
|
||||||
set_up_logging();
|
set_up_logging();
|
||||||
|
|
||||||
match args.action {
|
match args.action {
|
||||||
Commands::Translate(opts) => {
|
Commands::Translate(opts) => {
|
||||||
opts.translate();
|
opts.translate();
|
||||||
}
|
}
|
||||||
|
|
||||||
Commands::Build(opts) => {
|
Commands::Build(opts) => {
|
||||||
opts.build();
|
opts.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Commands::Clean(_) => {
|
// Commands::Clean(_) => {
|
||||||
// //
|
// //
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// Commands::Config(_) => {
|
// Commands::Config(_) => {
|
||||||
// //
|
// //
|
||||||
// }
|
// }
|
||||||
|
Commands::Serve(opts) => {
|
||||||
Commands::Serve(_) => {
|
opts.serve().await;
|
||||||
//
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
use axum::{
|
||||||
|
extract::{
|
||||||
|
ws::{Message, WebSocket},
|
||||||
|
Extension, TypedHeader, WebSocketUpgrade,
|
||||||
|
},
|
||||||
|
http::StatusCode,
|
||||||
|
response::IntoResponse,
|
||||||
|
routing::{get, get_service},
|
||||||
|
AddExtensionLayer, Router,
|
||||||
|
};
|
||||||
|
use notify::{watcher, Watcher, DebouncedEvent};
|
||||||
|
use std::sync::{mpsc::channel, Arc, Mutex};
|
||||||
|
use std::time::Duration;
|
||||||
|
use tower_http::services::ServeDir;
|
||||||
|
|
||||||
|
use crate::{CrateConfig, builder};
|
||||||
|
|
||||||
|
struct WsRelodState {
|
||||||
|
update: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WsRelodState { fn change(&mut self) { self.update = !self.update } }
|
||||||
|
|
||||||
|
pub async fn startup(config: CrateConfig) -> anyhow::Result<()> {
|
||||||
|
|
||||||
|
log::info!("Starting development server 🚀");
|
||||||
|
|
||||||
|
let (tx, rx) = channel();
|
||||||
|
|
||||||
|
let mut watcher = watcher(tx, Duration::from_secs(2)).unwrap();
|
||||||
|
watcher.watch(config.crate_dir.join("src").clone(), notify::RecursiveMode::Recursive).unwrap();
|
||||||
|
|
||||||
|
let ws_reload_state = Arc::new(Mutex::new(WsRelodState { update: false }));
|
||||||
|
|
||||||
|
let watcher_conf = config.clone();
|
||||||
|
let watcher_ws_state = ws_reload_state.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
loop {
|
||||||
|
if let Ok(v) = rx.recv() {
|
||||||
|
match v {
|
||||||
|
DebouncedEvent::Create(_) | DebouncedEvent::Write(_) |
|
||||||
|
DebouncedEvent::Remove(_) | DebouncedEvent::Rename(_, _) => {
|
||||||
|
builder::build(&watcher_conf).unwrap();
|
||||||
|
watcher_ws_state.lock().unwrap().change();
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let app = Router::new()
|
||||||
|
.route("/ws", get(ws_handler))
|
||||||
|
.fallback(
|
||||||
|
get_service(ServeDir::new(config.out_dir)).handle_error(
|
||||||
|
|error: std::io::Error| async move {
|
||||||
|
(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
format!("Unhandled internal error: {}", error),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.layer(AddExtensionLayer::new(ws_reload_state.clone()));
|
||||||
|
|
||||||
|
let port = "8080";
|
||||||
|
axum::Server::bind(&format!("0.0.0.0:{}", port).parse().unwrap())
|
||||||
|
.serve(app.into_make_service())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn ws_handler(
|
||||||
|
ws: WebSocketUpgrade,
|
||||||
|
_: Option<TypedHeader<headers::UserAgent>>,
|
||||||
|
Extension(state): Extension<Arc<Mutex<WsRelodState>>>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
ws.on_upgrade(|mut socket| async move {
|
||||||
|
loop {
|
||||||
|
if state.lock().unwrap().update {
|
||||||
|
socket.send(Message::Text(String::from("reload"))).await.unwrap();
|
||||||
|
state.lock().unwrap().change();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<title>Dioxus-CLI Dev Server</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="main"></div>
|
||||||
|
|
||||||
|
<!-- Note the usage of `type=module` here as this is an ES6 module -->
|
||||||
|
<script type="module">
|
||||||
|
import init from "./assets/module.js";
|
||||||
|
init("./assets/module_bg.wasm");
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const socket = new WebSocket("ws://localhost:8080/ws");
|
||||||
|
|
||||||
|
socket.addEventListener("message", function (event) {
|
||||||
|
console.log(event);
|
||||||
|
if (event.data === "reload") {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in new issue
Block a user