diff --git a/Cargo.toml b/Cargo.toml
index 3a8b061ec..cd1dec98b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -21,8 +21,6 @@ cargo_toml = "0.10.0"
futures = "0.3.12"
notify = "4.0.17"
html_parser = "0.6.2"
-tui = { version = "0.16.0", features = ["crossterm"] }
-crossterm = "0.22.1"
binary-install = "0.0.2"
convert_case = "0.5.0"
structopt = "0.3.25"
@@ -30,6 +28,11 @@ cargo_metadata = "0.14.1"
tokio = { version = "1.15.0", features = ["full"] }
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]]
path = "src/main.rs"
name = "dioxus"
diff --git a/src/builder.rs b/src/builder.rs
index b5bae9bf9..89451e163 100644
--- a/src/builder.rs
+++ b/src/builder.rs
@@ -37,7 +37,6 @@ pub fn build(config: &CrateConfig) -> Result<()> {
.arg("wasm32-unknown-unknown")
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped());
- ;
if config.release {
cmd.arg("--release");
@@ -54,7 +53,7 @@ pub fn build(config: &CrateConfig) -> Result<()> {
let output = child.wait()?;
if output.success() {
- log::info!("Build complete! {:?}", reason);
+ log::info!("Build complete!");
} else {
log::error!("Build failed!");
let mut reason = String::new();
@@ -63,7 +62,7 @@ pub fn build(config: &CrateConfig) -> Result<()> {
}
// [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
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
// TODO: support names via options
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();
if static_dir.is_dir() {
@@ -118,7 +115,7 @@ pub fn build(config: &CrateConfig) -> Result<()> {
Ok(())
}
-fn gen_page(module: &str) -> String {
+pub fn gen_page(module: &str) -> String {
format!(
r#"
@@ -132,7 +129,7 @@ fn gen_page(module: &str) -> String {
diff --git a/src/cli/build/mod.rs b/src/cli/build/mod.rs
index f4b69d5d7..58d797485 100644
--- a/src/cli/build/mod.rs
+++ b/src/cli/build/mod.rs
@@ -13,8 +13,7 @@ pub struct Build {
impl Build {
pub fn build(self) -> anyhow::Result<()> {
-
- let mut crate_config = crate::CrateConfig::new()?;
+ let mut crate_config = crate::CrateConfig::new()?;
// change the relase state.
crate_config.with_release(self.build.release);
@@ -23,4 +22,4 @@ impl Build {
Ok(())
}
-}
\ No newline at end of file
+}
diff --git a/src/cli/cfg.rs b/src/cli/cfg.rs
index feab0e61f..5736ebc19 100644
--- a/src/cli/cfg.rs
+++ b/src/cli/cfg.rs
@@ -67,6 +67,26 @@ pub struct ConfigOptsBuild {
pub pattern_params: Option>,
}
+#[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,
+
+ /// 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,
+
+ /// The public URL from which assets are to be served [default: /]
+ #[structopt(long, parse(from_str=parse_public_url))]
+ pub public_url: Option,
+}
+
/// Ensure the given value for `--public-url` is formatted correctly.
pub fn parse_public_url(val: &str) -> String {
let prefix = if !val.starts_with('/') { "/" } else { "" };
diff --git a/src/cli/mod.rs b/src/cli/mod.rs
index fa3bd2c77..32492459a 100644
--- a/src/cli/mod.rs
+++ b/src/cli/mod.rs
@@ -35,7 +35,6 @@ pub enum Commands {
Translate(translate::Translate),
// /// Build, watch & serve the Rust WASM app and all of its assets.
Serve(serve::Serve),
-
// /// Clean output artifacts.
// Clean(clean::Clean),
diff --git a/src/cli/serve/develop.rs b/src/cli/serve/develop.rs
index dd386b14e..27aba99ee 100644
--- a/src/cli/serve/develop.rs
+++ b/src/cli/serve/develop.rs
@@ -1,145 +1,145 @@
-use crate::{cli::DevelopOptions, config::CrateConfig, error::Result};
-use async_std::prelude::FutureExt;
+// use crate::{cli::DevelopOptions, config::CrateConfig, error::Result};
+// use async_std::prelude::FutureExt;
-use log::info;
-use notify::{RecommendedWatcher, RecursiveMode, Watcher};
-use std::path::PathBuf;
-use std::sync::atomic::AtomicBool;
-use std::sync::Arc;
-use tide::http::mime::HTML;
-use tide::http::Mime;
+// use log::info;
+// use notify::{RecommendedWatcher, RecursiveMode, Watcher};
+// use std::path::PathBuf;
+// use std::sync::atomic::AtomicBool;
+// use std::sync::Arc;
+// use tide::http::mime::HTML;
+// use tide::http::Mime;
-pub struct DevelopState {
- //
- reload_on_change: bool,
-}
+// pub struct DevelopState {
+// //
+// reload_on_change: bool,
+// }
-pub async fn develop(options: DevelopOptions) -> Result<()> {
- //
- log::info!("Starting development server 🚀");
- let mut cfg = CrateConfig::new()?;
- cfg.with_develop_options(&options);
+// pub async fn develop(options: DevelopOptions) -> Result<()> {
+// //
+// log::info!("Starting development server 🚀");
+// let mut cfg = CrateConfig::new()?;
+// 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
- // This lets the task progress while we handle file updates
- 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()));
+// // Spawn the server onto a seperate task
+// // This lets the task progress while we handle file updates
+// 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()));
- match server.race(watcher).await {
- Err(e) => log::warn!("Error running development server, {:?}", e),
- _ => {}
- }
+// match server.race(watcher).await {
+// Err(e) => log::warn!("Error running development server, {:?}", e),
+// _ => {}
+// }
- Ok(())
-}
+// Ok(())
+// }
-async fn watch_directory(config: CrateConfig, is_err: ErrStatus) -> Result<()> {
- // Create a channel to receive the events.
- let (watcher_tx, watcher_rx) = async_std::channel::bounded(100);
+// async fn watch_directory(config: CrateConfig, is_err: ErrStatus) -> Result<()> {
+// // Create a channel to receive the events.
+// let (watcher_tx, watcher_rx) = async_std::channel::bounded(100);
- // Automatically select the best implementation for your platform.
- // You can also access each implementation directly e.g. INotifyWatcher.
- let mut watcher: RecommendedWatcher = Watcher::new(move |res| {
- async_std::task::block_on(watcher_tx.send(res));
- // send an event
- let _ = async_std::task::block_on(watcher_tx.send(res));
- })
- .expect("failed to make watcher");
+// // Automatically select the best implementation for your platform.
+// // You can also access each implementation directly e.g. INotifyWatcher.
+// let mut watcher: RecommendedWatcher = Watcher::new(move |res| {
+// async_std::task::block_on(watcher_tx.send(res));
+// // send an event
+// let _ = async_std::task::block_on(watcher_tx.send(res));
+// })
+// .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
- // below will be monitored for changes.
- watcher
- .watch(&src_dir.join("src"), RecursiveMode::Recursive)
- .expect("Failed to watch dir");
+// // Add a path to be watched. All files and directories at that path and
+// // below will be monitored for changes.
+// watcher
+// .watch(&src_dir.join("src"), RecursiveMode::Recursive)
+// .expect("Failed to watch dir");
- match watcher.watch(&src_dir.join("examples"), RecursiveMode::Recursive) {
- Ok(_) => {}
- Err(e) => log::warn!("Failed to watch examples dir, {:?}", e),
- }
+// match watcher.watch(&src_dir.join("examples"), RecursiveMode::Recursive) {
+// Ok(_) => {}
+// Err(e) => log::warn!("Failed to watch examples dir, {:?}", e),
+// }
- 'run: loop {
- match crate::builder::build(&config) {
- Ok(_) => {
- is_err.store(false, std::sync::atomic::Ordering::Relaxed);
- async_std::task::sleep(std::time::Duration::from_millis(500)).await;
- }
- Err(err) => is_err.store(true, std::sync::atomic::Ordering::Relaxed),
- };
+// 'run: loop {
+// match crate::builder::build(&config) {
+// Ok(_) => {
+// is_err.store(false, std::sync::atomic::Ordering::Relaxed);
+// async_std::task::sleep(std::time::Duration::from_millis(500)).await;
+// }
+// Err(err) => is_err.store(true, std::sync::atomic::Ordering::Relaxed),
+// };
- let mut msg = None;
- loop {
- let new_msg = watcher_rx.recv().await.unwrap().unwrap();
- if !watcher_rx.is_empty() {
- msg = Some(new_msg);
- break;
- }
- }
+// let mut msg = None;
+// loop {
+// let new_msg = watcher_rx.recv().await.unwrap().unwrap();
+// if !watcher_rx.is_empty() {
+// msg = Some(new_msg);
+// break;
+// }
+// }
- info!("File updated, rebuilding app");
- }
- Ok(())
-}
+// info!("File updated, rebuilding app");
+// }
+// Ok(())
+// }
-async fn launch_server(outdir: PathBuf, is_err: ErrStatus) -> Result<()> {
- let _crate_dir = crate::cargo::crate_root()?;
- let _workspace_dir = crate::cargo::workspace_root()?;
+// async fn launch_server(outdir: PathBuf, is_err: ErrStatus) -> Result<()> {
+// let _crate_dir = crate::cargo::crate_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());
- log::info!("Serving {}", file_path);
- let p = outdir.display().to_string();
+// let file_path = format!("{}/index.html", outdir.display());
+// log::info!("Serving {}", file_path);
+// let p = outdir.display().to_string();
- app.at("/")
- .get(|req: tide::Request| async move {
- log::info!("Connected to development server");
- let state = req.state();
+// app.at("/")
+// .get(|req: tide::Request| async move {
+// log::info!("Connected to development server");
+// let state = req.state();
- match state.is_err.load(std::sync::atomic::Ordering::Relaxed) {
- true => {
- //
- let mut resp =
- tide::Body::from_string(format!(include_str!("../err.html"), err = "_"));
- resp.set_mime(HTML);
+// match state.is_err.load(std::sync::atomic::Ordering::Relaxed) {
+// true => {
+// //
+// let mut resp =
+// tide::Body::from_string(format!(include_str!("../err.html"), err = "_"));
+// resp.set_mime(HTML);
- Ok(resp)
- }
- false => {
- Ok(tide::Body::from_file(state.serv_path.clone().join("index.html")).await?)
- }
- }
- })
- .serve_dir(p)?;
- // .serve_file(file_path)
- // .unwrap();
+// Ok(resp)
+// }
+// false => {
+// Ok(tide::Body::from_file(state.serv_path.clone().join("index.html")).await?)
+// }
+// }
+// })
+// .serve_dir(p)?;
+// // .serve_file(file_path)
+// // .unwrap();
- let port = "8080";
- let serve_addr = format!("127.0.0.1:{}", port);
+// let port = "8080";
+// let serve_addr = format!("127.0.0.1:{}", port);
- info!("App available at http://{}/", serve_addr);
- app.listen(serve_addr).await?;
- Ok(())
-}
+// info!("App available at http://{}/", serve_addr);
+// app.listen(serve_addr).await?;
+// Ok(())
+// }
-/// https://github.com/http-rs/tide/blob/main/examples/state.rs
-/// Tide seems to prefer using state instead of injecting into the app closure
-/// The app closure needs to be static and
-#[derive(Clone)]
-struct ServerState {
- serv_path: PathBuf,
- is_err: ErrStatus,
-}
+// /// https://github.com/http-rs/tide/blob/main/examples/state.rs
+// /// Tide seems to prefer using state instead of injecting into the app closure
+// /// The app closure needs to be static and
+// #[derive(Clone)]
+// struct ServerState {
+// serv_path: PathBuf,
+// is_err: ErrStatus,
+// }
-type ErrStatus = Arc;
+// type ErrStatus = Arc;
-impl ServerState {
- fn new(serv_path: PathBuf, is_err: ErrStatus) -> Self {
- Self { serv_path, is_err }
- }
-}
+// impl ServerState {
+// fn new(serv_path: PathBuf, is_err: ErrStatus) -> Self {
+// Self { serv_path, is_err }
+// }
+// }
diff --git a/src/cli/serve/mod.rs b/src/cli/serve/mod.rs
index 9695870c0..36c050bd3 100644
--- a/src/cli/serve/mod.rs
+++ b/src/cli/serve/mod.rs
@@ -1,9 +1,36 @@
-use crate::cfg::ConfigOptsBuild;
+use crate::{cfg::ConfigOptsServe, server};
use anyhow::Result;
use std::path::PathBuf;
+use std::io::Write;
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)]
#[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(())
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index ea8393484..d4d3d7953 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,4 +1,5 @@
pub mod builder;
+pub mod server;
pub use builder::*;
pub mod cargo;
@@ -16,4 +17,4 @@ pub use error::*;
pub mod logging;
pub use logging::*;
-pub mod watch;
\ No newline at end of file
+pub mod watch;
diff --git a/src/main.rs b/src/main.rs
index 2ec44eb97..a58794783 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -3,30 +3,28 @@ use structopt::StructOpt;
#[tokio::main]
async fn main() -> Result<()> {
-
let args = Cli::from_args();
set_up_logging();
match args.action {
Commands::Translate(opts) => {
opts.translate();
- }
-
+ }
+
Commands::Build(opts) => {
opts.build();
}
- // Commands::Clean(_) => {
- // //
- // }
+ // Commands::Clean(_) => {
+ // //
+ // }
- // Commands::Config(_) => {
- // //
- // }
-
- Commands::Serve(_) => {
- //
- }
+ // Commands::Config(_) => {
+ // //
+ // }
+ Commands::Serve(opts) => {
+ opts.serve().await;
+ }
}
Ok(())
diff --git a/src/server/mod.rs b/src/server/mod.rs
new file mode 100644
index 000000000..2e937dbb8
--- /dev/null
+++ b/src/server/mod.rs
@@ -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>,
+ Extension(state): Extension>>,
+) -> 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();
+ }
+ }
+ })
+}
diff --git a/src/server/serve.html b/src/server/serve.html
new file mode 100644
index 000000000..98997403b
--- /dev/null
+++ b/src/server/serve.html
@@ -0,0 +1,28 @@
+
+
+
+
+ Dioxus-CLI Dev Server
+
+
+
+
+
+
+
+
+
+