mrxiaozhuox 3 роки тому
батько
коміт
5e710f6693
11 змінених файлів з 334 додано та 175 видалено
  1. 5 2
      Cargo.toml
  2. 4 7
      src/builder.rs
  3. 2 3
      src/cli/build/mod.rs
  4. 20 0
      src/cli/cfg.rs
  5. 0 1
      src/cli/mod.rs
  6. 145 145
      src/cli/serve/develop.rs
  7. 30 3
      src/cli/serve/mod.rs
  8. 2 1
      src/lib.rs
  9. 11 13
      src/main.rs
  10. 87 0
      src/server/mod.rs
  11. 28 0
      src/server/serve.html

+ 5 - 2
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"

+ 4 - 7
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#"
 <html>
@@ -132,7 +129,7 @@ fn gen_page(module: &str) -> String {
     <!-- Note the usage of `type=module` here as this is an ES6 module -->
     <script type="module">
       import init from "{}";
-      init("./wasm/module_bg.wasm");
+      init("./assets/module_bg.wasm");
     </script>
     <div id="dioxusroot"> </div>
   </body>

+ 2 - 3
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(())
     }
-}
+}

+ 20 - 0
src/cli/cfg.rs

@@ -67,6 +67,26 @@ pub struct ConfigOptsBuild {
     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.
 pub fn parse_public_url(val: &str) -> String {
     let prefix = if !val.starts_with('/') { "/" } else { "" };

+ 0 - 1
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),
 

+ 145 - 145
src/cli/serve/develop.rs

@@ -1,145 +1,145 @@
-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;
-
-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);
-
-    let out_dir = cfg.out_dir.clone();
-
-    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()));
-
-    match server.race(watcher).await {
-        Err(e) => log::warn!("Error running development server, {:?}", e),
-        _ => {}
-    }
-
-    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);
-
-    // 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()?;
-
-    // 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),
-    }
-
-    '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;
-            }
-        }
-
-        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()?;
-
-    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();
-
-    app.at("/")
-        .get(|req: tide::Request<ServerState>| 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);
-
-                    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);
-
-    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,
-}
-
-type ErrStatus = Arc<AtomicBool>;
-
-impl ServerState {
-    fn new(serv_path: PathBuf, is_err: ErrStatus) -> Self {
-        Self { serv_path, is_err }
-    }
-}
+// 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;
+
+// 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);
+
+//     let out_dir = cfg.out_dir.clone();
+
+//     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()));
+
+//     match server.race(watcher).await {
+//         Err(e) => log::warn!("Error running development server, {:?}", e),
+//         _ => {}
+//     }
+
+//     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);
+
+//     // 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()?;
+
+//     // 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),
+//     }
+
+//     '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;
+//             }
+//         }
+
+//         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()?;
+
+//     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();
+
+//     app.at("/")
+//         .get(|req: tide::Request<ServerState>| 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);
+
+//                     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);
+
+//     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,
+// }
+
+// type ErrStatus = Arc<AtomicBool>;
+
+// impl ServerState {
+//     fn new(serv_path: PathBuf, is_err: ErrStatus) -> Self {
+//         Self { serv_path, is_err }
+//     }
+// }

+ 30 - 3
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(())
+    }
+}

+ 2 - 1
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;
+pub mod watch;

+ 11 - 13
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(())

+ 87 - 0
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<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();
+            }
+        }
+    })
+}

+ 28 - 0
src/server/serve.html

@@ -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>