ソースを参照

feat: add `init` command

mrxiaozhuox 3 年 前
コミット
0e2c05be66

+ 2 - 3
README.md

@@ -10,7 +10,7 @@
 
 # About
 
-dioxus-studio (inspired by wasm-pack and webpack) is a tool to help get dioxus projects off the ground. It handles all the build, development, bundling, and publishing to make web development simple.
+dioxus-cli (inspired by wasm-pack and webpack) is a tool to help get dioxus projects off the ground. It handles all the build, development, bundling, and publishing to make web development simple.
 
 
 ## Installation
@@ -19,5 +19,4 @@ dioxus-studio (inspired by wasm-pack and webpack) is a tool to help get dioxus p
 $ cargo install dioxus-cli
 ```
 
-Now, `dioxus` is in your path.
-
+Now, `dioxus` is in your path.

+ 2 - 6
src/builder.rs

@@ -2,10 +2,7 @@ use crate::{
     config::{CrateConfig, ExecutableType},
     error::{Error, Result},
 };
-use std::{
-    io::Write,
-    process::Command,
-};
+use std::{io::Write, process::Command};
 use wasm_bindgen_cli_support::Bindgen;
 
 pub fn build(config: &CrateConfig) -> Result<()> {
@@ -36,8 +33,7 @@ pub fn build(config: &CrateConfig) -> Result<()> {
         .arg("--target")
         .arg("wasm32-unknown-unknown")
         .stdout(std::process::Stdio::inherit())
-        .stderr(std::process::Stdio::inherit())
-    ;
+        .stderr(std::process::Stdio::inherit());
 
     if config.release {
         cmd.arg("--release");

+ 61 - 0
src/cli/init/mod.rs

@@ -0,0 +1,61 @@
+use std::{process::{Command, Stdio}, fs::File, path::PathBuf, io::Write};
+
+use structopt::StructOpt;
+
+use crate::{error::{Error, Result}, cargo};
+
+/// Build the Rust WASM app and all of its assets.
+#[derive(Clone, Debug, StructOpt)]
+#[structopt(name = "init")]
+pub struct Init {
+
+    /// Init project path
+    #[structopt(default_value = ".")]
+    path: String,
+
+    /// Template path
+    #[structopt(default_value = "default", long)]
+    template: String,
+
+}
+
+impl Init {
+    pub fn init(self) -> Result<()> {
+        
+        log::info!("🔧 Start to init a new project '{}'.", self.path);
+
+        let project_path = PathBuf::from(&self.path);
+
+        let output = Command::new("cargo")
+            .arg("init")
+            .arg(&self.path)
+            .arg("--bin")
+            .stdout(Stdio::piped())
+            .stderr(Stdio::piped())
+            .output()?
+        ;
+
+        if !output.status.success() {
+            return Err(Error::CargoError("Cargo init failed".into()));
+        }
+
+        // get the template code
+        let template_str = match self.template {
+            _ => include_str!("../../template/default.rs"),
+        };
+
+        let main_rs_file = project_path.join("src").join("main.rs");
+        if !main_rs_file.is_file() {
+            return Err(Error::FailedToWrite);
+        }
+
+        let mut file = File::create(main_rs_file)?;
+        file.write_all(&template_str.as_bytes())?;
+
+        // log::info!("🎯 Project initialization completed.");
+
+        
+
+        Ok(())
+    }
+}

+ 6 - 2
src/cli/mod.rs

@@ -5,6 +5,7 @@ pub mod cfg;
 pub mod clean;
 pub mod serve;
 pub mod translate;
+pub mod init;
 
 /// Build, bundle, & ship your Dioxus app.
 ///
@@ -27,12 +28,15 @@ pub struct Cli {
 
 #[derive(StructOpt)]
 pub enum Commands {
-    // /// Build the Rust WASM app and all of its assets.
+    /// Build the Rust WASM app and all of its assets.
     Build(build::Build),
     /// Translate some source file into Dioxus code.
     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),
+    /// Init a new project for Dioxus.
+    Init(init::Init),
+
     // /// Clean output artifacts.
     // Clean(clean::Clean),
 

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

@@ -1,145 +0,0 @@
-// 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 }
-//     }
-// }

+ 0 - 0
src/cli/serve/events.rs


+ 0 - 3
src/cli/serve/mod.rs

@@ -2,8 +2,6 @@ use crate::{cfg::ConfigOptsServe, server};
 use std::io::Write;
 use structopt::StructOpt;
 
-mod develop;
-
 /// Run the WASM project on dev-server
 #[derive(Clone, Debug, StructOpt)]
 #[structopt(name = "serve")]
@@ -14,7 +12,6 @@ pub struct Serve {
 
 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);

+ 1 - 0
src/cli/translate/to_component.rs

@@ -4,6 +4,7 @@
 //! - [ ] Extracts svgs
 //! - [ ] Attempts to extract lists
 
+#[allow(unused_imports)]
 use std::{
     fmt::{Display, Formatter},
     io::Write,

+ 1 - 3
src/lib.rs

@@ -15,6 +15,4 @@ pub mod error;
 pub use error::*;
 
 pub mod logging;
-pub use logging::*;
-
-pub mod watch;
+pub use logging::*;

+ 15 - 3
src/main.rs

@@ -8,11 +8,15 @@ async fn main() -> Result<()> {
 
     match args.action {
         Commands::Translate(opts) => {
-            opts.translate()?;
+            if let Err(e) = opts.translate() {
+                log::error!("translate error: {}", e);
+            }
         }
 
         Commands::Build(opts) => {
-            opts.build()?;
+            if let Err(e) = opts.build() {
+                log::error!("build error: {}", e);
+            }
         }
 
         // Commands::Clean(_) => {
@@ -23,7 +27,15 @@ async fn main() -> Result<()> {
         //     //
         // }
         Commands::Serve(opts) => {
-            opts.serve().await?;
+            if let Err(e) = opts.serve().await {
+                log::error!("serve error: {}", e);
+            }
+        }
+
+        Commands::Init(opts) => {
+            if let Err(e) = opts.init() {
+                log::error!("init error: {}", e);
+            }
         }
     }
 

+ 0 - 0
src/err.html → src/server/err.html


+ 38 - 26
src/server/mod.rs

@@ -1,34 +1,40 @@
 use axum::{
-    extract::{
-        ws::Message,
-        Extension, TypedHeader, WebSocketUpgrade,
-    },
+    extract::{ws::Message, Extension, TypedHeader, WebSocketUpgrade},
     http::StatusCode,
     response::IntoResponse,
     routing::{get, get_service},
     AddExtensionLayer, Router,
 };
-use notify::{watcher, Watcher, DebouncedEvent};
+use notify::{watcher, DebouncedEvent, Watcher};
 use std::sync::{mpsc::channel, Arc, Mutex};
 use std::time::Duration;
 use tower_http::services::ServeDir;
 
-use crate::{CrateConfig, builder};
+use crate::{builder, CrateConfig};
 
 struct WsRelodState {
     update: bool,
 }
 
-impl WsRelodState { fn change(&mut self) { self.update = !self.update; } }
+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();
 
+    // file watcher: check file change
     let mut watcher = watcher(tx, Duration::from_secs(2)).unwrap();
-    watcher.watch(config.crate_dir.join("src").clone(), notify::RecursiveMode::Recursive).unwrap();
+    watcher
+        .watch(
+            config.crate_dir.join("src").clone(),
+            notify::RecursiveMode::Recursive,
+        )
+        .unwrap();
 
     let ws_reload_state = Arc::new(Mutex::new(WsRelodState { update: false }));
 
@@ -38,37 +44,40 @@ pub async fn startup(config: CrateConfig) -> anyhow::Result<()> {
         loop {
             if let Ok(v) = rx.recv() {
                 match v {
-                    DebouncedEvent::Create(_) | DebouncedEvent::Write(_) |
-                    DebouncedEvent::Remove(_) | DebouncedEvent::Rename(_, _) => {
+                    DebouncedEvent::Create(_)
+                    | DebouncedEvent::Write(_)
+                    | DebouncedEvent::Remove(_)
+                    | DebouncedEvent::Rename(_, _) => {
                         if let Ok(_) = builder::build(&watcher_conf) {
+                            // change the websocket reload state to true;
+                            // the page will auto-reload.
                             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),
-                    )
-                },
-            ),
-        )
+        .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()));
 
+    // start serve dev-server at 0.0.0.0:8080
     let port = "8080";
+    log::info!("📡 Dev-Server is started at: http://127.0.0.1:{}/", port);
     axum::Server::bind(&format!("0.0.0.0:{}", port).parse().unwrap())
         .serve(app.into_make_service())
         .await?;
-
     Ok(())
 }
 
@@ -80,7 +89,10 @@ async fn ws_handler(
     ws.on_upgrade(|mut socket| async move {
         loop {
             if state.lock().unwrap().update {
-                socket.send(Message::Text(String::from("reload"))).await.unwrap();
+                socket
+                    .send(Message::Text(String::from("reload")))
+                    .await
+                    .unwrap();
                 state.lock().unwrap().change();
             }
         }

+ 9 - 0
src/template/config.toml

@@ -0,0 +1,9 @@
+[package]
+name = "{project}"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+dioxus = { version = "0.1.7", features = ["web"] }

+ 16 - 0
src/template/default.rs

@@ -0,0 +1,16 @@
+use dioxus::prelude::*;
+
+fn main() {
+    dioxus::desktop::launch(app);
+}
+
+fn app(cx: Scope) -> Element {
+    cx.render(rsx! (
+        div {
+            style: "text-align: center;",
+            h2 { "🌗🚀 Dioxus" }
+            p { strong { "Frontend that scales." } }
+            p { "Dioxus is a portable, performant, and ergonomic framework for building cross-platform user interfaces in Rust." }
+        }
+    ))
+}

+ 0 - 1
src/watch.rs

@@ -1 +0,0 @@
-