From 281a075c6c97c469d024d5645db13627663073ca Mon Sep 17 00:00:00 2001 From: mrxiaozhuox Date: Sat, 22 Jan 2022 10:02:52 +0800 Subject: [PATCH 01/27] fix: clean file --- Cargo.toml | 2 +- packages/rsx-translate/Cargo.toml | 8 -- packages/rsx-translate/src/lib.rs | 8 -- src/builder.rs | 14 ++- src/buildsystem/assets.rs | 4 - src/buildsystem/wasm.rs | 1 - src/cli/build/mod.rs | 14 ++- src/cli/config/mod.rs | 9 -- src/cli/mod.rs | 6 +- src/cli/serve/draw.rs | 0 src/cli/serve/studio.rs | 164 ------------------------------ src/cli/watch/mod.rs | 1 - src/lib.rs | 10 +- src/main.rs | 15 +-- 14 files changed, 35 insertions(+), 221 deletions(-) delete mode 100644 packages/rsx-translate/Cargo.toml delete mode 100644 packages/rsx-translate/src/lib.rs delete mode 100644 src/buildsystem/assets.rs delete mode 100644 src/buildsystem/wasm.rs delete mode 100644 src/cli/config/mod.rs delete mode 100644 src/cli/serve/draw.rs delete mode 100644 src/cli/serve/studio.rs delete mode 100644 src/cli/watch/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 238cec2e2..3a8b061ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ license = "MIT/Apache-2.0" thiserror = "1.0.23" log = "0.4.13" fern = { version = "0.6.0", features = ["colored"] } -wasm-bindgen-cli-support = "0.2.78" +wasm-bindgen-cli-support = "0.2.79" anyhow = "1.0.38" serde = { version = "1.0.133", features = ["derive"] } serde_json = "1" diff --git a/packages/rsx-translate/Cargo.toml b/packages/rsx-translate/Cargo.toml deleted file mode 100644 index 2af83c634..000000000 --- a/packages/rsx-translate/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "rsx-translate" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] diff --git a/packages/rsx-translate/src/lib.rs b/packages/rsx-translate/src/lib.rs deleted file mode 100644 index 1b4a90c93..000000000 --- a/packages/rsx-translate/src/lib.rs +++ /dev/null @@ -1,8 +0,0 @@ -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - let result = 2 + 2; - assert_eq!(result, 4); - } -} diff --git a/src/builder.rs b/src/builder.rs index b80c3dd36..b5bae9bf9 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -37,6 +37,7 @@ 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"); @@ -49,10 +50,11 @@ pub fn build(config: &CrateConfig) -> Result<()> { }; let mut child = cmd.spawn()?; + let output = child.wait()?; if output.success() { - log::info!("Build complete!"); + log::info!("Build complete! {:?}", reason); } else { log::error!("Build failed!"); let mut reason = String::new(); @@ -102,10 +104,12 @@ pub fn build(config: &CrateConfig) -> Result<()> { file.write_all(gen_page("./wasm/module.js").as_str().as_bytes())?; let copy_options = fs_extra::dir::CopyOptions::new(); - match fs_extra::dir::copy(static_dir, out_dir, ©_options) { - Ok(_) => {} - Err(_e) => { - log::warn!("Error copying dir"); + if static_dir.is_dir() { + match fs_extra::dir::copy(static_dir, out_dir, ©_options) { + Ok(_) => {} + Err(_e) => { + log::warn!("Error copying dir: {}", _e); + } } } diff --git a/src/buildsystem/assets.rs b/src/buildsystem/assets.rs deleted file mode 100644 index e35ac1005..000000000 --- a/src/buildsystem/assets.rs +++ /dev/null @@ -1,4 +0,0 @@ -/* - -todo: wasm splitting -*/ diff --git a/src/buildsystem/wasm.rs b/src/buildsystem/wasm.rs deleted file mode 100644 index 8b1378917..000000000 --- a/src/buildsystem/wasm.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/cli/build/mod.rs b/src/cli/build/mod.rs index 33a81a540..f4b69d5d7 100644 --- a/src/cli/build/mod.rs +++ b/src/cli/build/mod.rs @@ -11,4 +11,16 @@ pub struct Build { pub build: ConfigOptsBuild, } -pub async fn perform_build() {} +impl Build { + pub fn build(self) -> anyhow::Result<()> { + + let mut crate_config = crate::CrateConfig::new()?; + + // change the relase state. + crate_config.with_release(self.build.release); + + crate::builder::build(&crate_config); + + Ok(()) + } +} \ No newline at end of file diff --git a/src/cli/config/mod.rs b/src/cli/config/mod.rs deleted file mode 100644 index 616c9bc78..000000000 --- a/src/cli/config/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -use crate::cfg::ConfigOptsBuild; -use anyhow::Result; -use std::path::PathBuf; -use structopt::StructOpt; - -/// Build the Rust WASM app and all of its assets. -#[derive(Clone, Debug, StructOpt)] -#[structopt(name = "config")] -pub struct Config {} diff --git a/src/cli/mod.rs b/src/cli/mod.rs index fe118cbb7..fa3bd2c77 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -5,10 +5,8 @@ use structopt::StructOpt; pub mod build; pub mod cfg; pub mod clean; -pub mod config; pub mod serve; pub mod translate; -pub mod watch; /// Build, bundle, & ship your Dioxus app. /// @@ -32,11 +30,11 @@ pub struct Cli { #[derive(StructOpt)] pub enum Commands { // /// Build the Rust WASM app and all of its assets. - // Build(build::Build), + 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. - // Serve(serve::Serve), + Serve(serve::Serve), // /// Clean output artifacts. // Clean(clean::Clean), diff --git a/src/cli/serve/draw.rs b/src/cli/serve/draw.rs deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/cli/serve/studio.rs b/src/cli/serve/studio.rs deleted file mode 100644 index 60c330beb..000000000 --- a/src/cli/serve/studio.rs +++ /dev/null @@ -1,164 +0,0 @@ -//! It's better to store all the configuration in one spot - -use tui::{ - backend::{Backend, CrosstermBackend}, - layout::{Constraint, Direction, Layout, Rect}, - style::{Color, Modifier, Style}, - symbols, - text::{Span, Spans}, - widgets::canvas::{Canvas, Line, Map, MapResolution, Rectangle}, - widgets::{ - Axis, BarChart, Block, BorderType, Borders, Cell, Chart, Dataset, Gauge, LineGauge, List, - ListItem, Paragraph, Row, Sparkline, Table, Tabs, Wrap, - }, - Frame, Terminal, -}; - -use crate::*; -use std::{any::Any, io::Write, path::PathBuf, process::Command}; - -pub struct Cfg { - command: LaunchOptions, - headless: bool, - example: Option, - outdir: Option, - release: bool, - hydrate: Option, - template: Option, - translate_file: Option, - crate_config: Option, - should_quit: bool, -} - -pub async fn start(options: DevelopOptions) -> Result<()> { - let mut state = Cfg { - command: todo!(), - headless: todo!(), - example: todo!(), - outdir: todo!(), - release: todo!(), - hydrate: todo!(), - template: todo!(), - translate_file: todo!(), - crate_config: todo!(), - should_quit: false, - }; - - crossterm::terminal::enable_raw_mode()?; - - let backend = CrosstermBackend::new(std::io::stdout()); - let mut terminal = Terminal::new(backend).unwrap(); - - // Setup input handling - // let (tx, rx) = futures::channel::mpsc::unbounded(); - let tick_rate = std::time::Duration::from_millis(100); - - let mut prev_time = std::time::Instant::now(); - while !state.should_quit { - let next_time = prev_time + tick_rate; - let now = std::time::Instant::now(); - - let diff = next_time - std::time::Instant::now(); - } - - Ok(()) -} - -struct TuiStudio { - cfg: Cfg, - hook_idx: usize, - hooks: Vec>, -} -impl TuiStudio { - fn use_hook(&mut self, f: impl FnOnce() -> F) -> &mut F { - if self.hook_idx == self.hooks.len() { - self.hooks.push(Box::new(f())); - } - let idx = self.hook_idx; - self.hook_idx += 1; - let hook = self.hooks.get_mut(idx).unwrap(); - let r = hook.downcast_mut::().unwrap(); - r - } -} - -impl TuiStudio { - fn event_handler(&self, action: crossterm::event::Event) -> anyhow::Result<()> { - match action { - crossterm::event::Event::Key(_) => {} - crossterm::event::Event::Mouse(_) => {} - crossterm::event::Event::Resize(_, _) => {} - } - Ok(()) - } - - fn handle_key(&mut self, key: crossterm::event::KeyEvent) {} - - fn tick(&mut self) {} - - fn should_quit(&self) -> bool { - false - } - - fn render(&mut self, f: &mut tui::Frame) { - self.hook_idx = 0; - - // Wrapping block for a group - // Just draw the block and the group on the same area and build the group - // with at least a margin of 1 - let size = f.size(); - let block = Block::default() - .borders(Borders::ALL) - .title("Main block with round corners") - .border_type(BorderType::Rounded); - f.render_widget(block, size); - let chunks = Layout::default() - .direction(Direction::Vertical) - .margin(4) - .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) - .split(f.size()); - - let top_chunks = Layout::default() - .direction(Direction::Horizontal) - .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) - .split(chunks[0]); - let block = Block::default() - .title(vec![ - Span::styled("With", Style::default().fg(Color::Yellow)), - Span::from(" background"), - ]) - .style(Style::default().bg(Color::Green)); - f.render_widget(block, top_chunks[0]); - - let block = Block::default().title(Span::styled( - "Styled title", - Style::default() - .fg(Color::White) - .bg(Color::Red) - .add_modifier(Modifier::BOLD), - )); - f.render_widget(block, top_chunks[1]); - - let bottom_chunks = Layout::default() - .direction(Direction::Horizontal) - .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) - .split(chunks[1]); - let block = Block::default().title("With borders").borders(Borders::ALL); - f.render_widget(block, bottom_chunks[0]); - let block = Block::default() - .title("With styled borders and doubled borders") - .border_style(Style::default().fg(Color::Cyan)) - .borders(Borders::LEFT | Borders::RIGHT) - .border_type(BorderType::Double); - f.render_widget(block, bottom_chunks[1]); - } -} - -impl TuiStudio { - fn render_list(&mut self, f: &mut tui::Frame) { - let options = [ - "Bundle", "Develop", - // - ]; - } -} diff --git a/src/cli/watch/mod.rs b/src/cli/watch/mod.rs deleted file mode 100644 index 8b1378917..000000000 --- a/src/cli/watch/mod.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/lib.rs b/src/lib.rs index 4cc4f2d75..ea8393484 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,12 +16,4 @@ pub use error::*; pub mod logging; pub use logging::*; -pub mod watch; - -pub mod buildsystem { - pub mod wasm; - pub use wasm::*; - - pub mod assets; - pub use assets::*; -} +pub mod watch; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 798c492dc..2ec44eb97 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,15 +3,18 @@ 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(_) => { - // // - // } + } + + Commands::Build(opts) => { + opts.build(); + } // Commands::Clean(_) => { // // @@ -21,9 +24,9 @@ async fn main() -> Result<()> { // // // } - // Commands::Serve(_) => { - // // - // } + Commands::Serve(_) => { + // + } } Ok(()) From 5e710f6693edb536dcc6ab54261fdd9eff790cd2 Mon Sep 17 00:00:00 2001 From: mrxiaozhuox Date: Sat, 22 Jan 2022 21:23:30 +0800 Subject: [PATCH 02/27] feat: add `dev-server` --- Cargo.toml | 7 +- src/builder.rs | 11 +- src/cli/build/mod.rs | 5 +- src/cli/cfg.rs | 20 ++++ src/cli/mod.rs | 1 - src/cli/serve/develop.rs | 236 +++++++++++++++++++-------------------- src/cli/serve/mod.rs | 33 +++++- src/lib.rs | 3 +- src/main.rs | 24 ++-- src/server/mod.rs | 87 +++++++++++++++ src/server/serve.html | 28 +++++ 11 files changed, 307 insertions(+), 148 deletions(-) create mode 100644 src/server/mod.rs create mode 100644 src/server/serve.html 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 + + +
+ + + + + + + From d18a93f129132bd1ea59344fe757389eae603edc Mon Sep 17 00:00:00 2001 From: mrxiaozhuox Date: Sat, 22 Jan 2022 22:19:59 +0800 Subject: [PATCH 03/27] fix: fixed warn info --- src/builder.rs | 24 +++++++++++------------- src/cli/build/mod.rs | 4 +--- src/cli/cfg.rs | 5 +---- src/cli/clean/mod.rs | 3 --- src/cli/mod.rs | 2 -- src/cli/serve/mod.rs | 2 -- src/cli/translate/to_component.rs | 1 + src/config.rs | 2 +- src/main.rs | 6 +++--- src/server/mod.rs | 11 ++++++----- src/server/serve.html | 27 +++++++++++++++++++-------- 11 files changed, 43 insertions(+), 44 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 89451e163..101d31ee5 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -3,7 +3,7 @@ use crate::{ error::{Error, Result}, }; use std::{ - io::{Read, Write}, + io::Write, process::Command, }; use wasm_bindgen_cli_support::Bindgen; @@ -29,14 +29,15 @@ pub fn build(config: &CrateConfig) -> Result<()> { let t_start = std::time::Instant::now(); // [1] Build the .wasm module - log::info!("Running build commands..."); + log::info!("πŸš… Running build commands..."); let mut cmd = Command::new("cargo"); cmd.current_dir(&crate_dir) .arg("build") .arg("--target") .arg("wasm32-unknown-unknown") - .stdout(std::process::Stdio::piped()) - .stderr(std::process::Stdio::piped()); + .stdout(std::process::Stdio::inherit()) + .stderr(std::process::Stdio::inherit()) + ; if config.release { cmd.arg("--release"); @@ -48,16 +49,13 @@ pub fn build(config: &CrateConfig) -> Result<()> { ExecutableType::Example(name) => cmd.arg("--example").arg(name), }; - let mut child = cmd.spawn()?; + let output = cmd.output()?; - let output = child.wait()?; - - if output.success() { - log::info!("Build complete!"); + if output.status.success() { + std::io::stdout().write_all(&output.stdout).unwrap(); } else { log::error!("Build failed!"); - let mut reason = String::new(); - child.stderr.unwrap().read_to_string(&mut reason)?; + let reason = String::from_utf8_lossy(&output.stderr).to_string(); return Err(Error::BuildFailed(reason)); } @@ -98,8 +96,8 @@ 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); + // log::info!("Writing to '{:#?}' directory...", out_dir); let copy_options = fs_extra::dir::CopyOptions::new(); if static_dir.is_dir() { match fs_extra::dir::copy(static_dir, out_dir, ©_options) { @@ -111,7 +109,7 @@ pub fn build(config: &CrateConfig) -> Result<()> { } let t_end = std::time::Instant::now(); - log::info!("Done in {}ms! πŸŽ‰", (t_end - t_start).as_millis()); + log::info!("🏁 Done in {}ms!", (t_end - t_start).as_millis()); Ok(()) } diff --git a/src/cli/build/mod.rs b/src/cli/build/mod.rs index 58d797485..fc329a305 100644 --- a/src/cli/build/mod.rs +++ b/src/cli/build/mod.rs @@ -1,6 +1,4 @@ use crate::cfg::ConfigOptsBuild; -use anyhow::Result; -use std::path::PathBuf; use structopt::StructOpt; /// Build the Rust WASM app and all of its assets. @@ -18,7 +16,7 @@ impl Build { // change the relase state. crate_config.with_release(self.build.release); - crate::builder::build(&crate_config); + crate::builder::build(&crate_config)?; Ok(()) } diff --git a/src/cli/cfg.rs b/src/cli/cfg.rs index 5736ebc19..2c5f42a26 100644 --- a/src/cli/cfg.rs +++ b/src/cli/cfg.rs @@ -1,11 +1,8 @@ -use anyhow::Result; use std::path::PathBuf; use structopt::StructOpt; -use serde::{Deserialize, Serialize}; +use serde::Deserialize; use std::collections::HashMap; -use std::str::FromStr; -use std::sync::Arc; /// Config options for the build system. #[derive(Clone, Debug, Default, Deserialize, StructOpt)] diff --git a/src/cli/clean/mod.rs b/src/cli/clean/mod.rs index aeeedf523..1edcd053a 100644 --- a/src/cli/clean/mod.rs +++ b/src/cli/clean/mod.rs @@ -1,6 +1,3 @@ -use crate::cfg::ConfigOptsBuild; -use anyhow::Result; -use std::path::PathBuf; use structopt::StructOpt; /// Build the Rust WASM app and all of its assets. diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 32492459a..a739f9a01 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1,5 +1,3 @@ -use anyhow::{Context, Result}; -use std::path::PathBuf; use structopt::StructOpt; pub mod build; diff --git a/src/cli/serve/mod.rs b/src/cli/serve/mod.rs index 36c050bd3..f2ac2b69f 100644 --- a/src/cli/serve/mod.rs +++ b/src/cli/serve/mod.rs @@ -1,6 +1,4 @@ use crate::{cfg::ConfigOptsServe, server}; -use anyhow::Result; -use std::path::PathBuf; use std::io::Write; use structopt::StructOpt; diff --git a/src/cli/translate/to_component.rs b/src/cli/translate/to_component.rs index b08161e9c..d64854d37 100644 --- a/src/cli/translate/to_component.rs +++ b/src/cli/translate/to_component.rs @@ -20,6 +20,7 @@ pub fn convert_html_to_component(html: &str) -> Result { }) } +#[allow(dead_code)] pub struct ComponentRenderer { dom: Dom, icon_index: usize, diff --git a/src/config.rs b/src/config.rs index 47920c583..d17294b18 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,5 +1,5 @@ use crate::error::Result; -use std::{io::Write, path::PathBuf, process::Command}; +use std::path::PathBuf; #[derive(Debug, Clone)] pub struct CrateConfig { diff --git a/src/main.rs b/src/main.rs index a58794783..41fa5835b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,11 +8,11 @@ async fn main() -> Result<()> { match args.action { Commands::Translate(opts) => { - opts.translate(); + opts.translate()?; } Commands::Build(opts) => { - opts.build(); + opts.build()?; } // Commands::Clean(_) => { @@ -23,7 +23,7 @@ async fn main() -> Result<()> { // // // } Commands::Serve(opts) => { - opts.serve().await; + opts.serve().await?; } } diff --git a/src/server/mod.rs b/src/server/mod.rs index 2e937dbb8..6c6c08c1f 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,6 +1,6 @@ use axum::{ extract::{ - ws::{Message, WebSocket}, + ws::Message, Extension, TypedHeader, WebSocketUpgrade, }, http::StatusCode, @@ -19,11 +19,11 @@ 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 πŸš€"); + log::info!("πŸš€ Starting development server..."); let (tx, rx) = channel(); @@ -40,8 +40,9 @@ pub async fn startup(config: CrateConfig) -> anyhow::Result<()> { match v { DebouncedEvent::Create(_) | DebouncedEvent::Write(_) | DebouncedEvent::Remove(_) | DebouncedEvent::Rename(_, _) => { - builder::build(&watcher_conf).unwrap(); - watcher_ws_state.lock().unwrap().change(); + if let Ok(_) = builder::build(&watcher_conf) { + watcher_ws_state.lock().unwrap().change(); + } }, _ => {} } diff --git a/src/server/serve.html b/src/server/serve.html index 98997403b..b5268bd68 100644 --- a/src/server/serve.html +++ b/src/server/serve.html @@ -14,15 +14,26 @@ From 0e2c05be66fc994ad8ac2e03b34c0e48632c3296 Mon Sep 17 00:00:00 2001 From: mrxiaozhuox Date: Sun, 23 Jan 2022 01:29:36 +0800 Subject: [PATCH 04/27] feat: add `init` command --- README.md | 5 +- src/builder.rs | 8 +- src/cli/init/mod.rs | 61 +++++++++++++ src/cli/mod.rs | 8 +- src/cli/serve/develop.rs | 145 ------------------------------ src/cli/serve/events.rs | 0 src/cli/serve/mod.rs | 3 - src/cli/translate/to_component.rs | 1 + src/lib.rs | 4 +- src/main.rs | 18 +++- src/{ => server}/err.html | 0 src/server/mod.rs | 64 +++++++------ src/template/config.toml | 9 ++ src/template/default.rs | 16 ++++ src/watch.rs | 1 - 15 files changed, 151 insertions(+), 192 deletions(-) create mode 100644 src/cli/init/mod.rs delete mode 100644 src/cli/serve/develop.rs delete mode 100644 src/cli/serve/events.rs rename src/{ => server}/err.html (100%) create mode 100644 src/template/config.toml create mode 100644 src/template/default.rs delete mode 100644 src/watch.rs diff --git a/README.md b/README.md index 34be58eda..75e05b3cf 100644 --- a/README.md +++ b/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. \ No newline at end of file diff --git a/src/builder.rs b/src/builder.rs index 101d31ee5..24d9c3151 100644 --- a/src/builder.rs +++ b/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"); diff --git a/src/cli/init/mod.rs b/src/cli/init/mod.rs new file mode 100644 index 000000000..2325d9c31 --- /dev/null +++ b/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(()) + } +} \ No newline at end of file diff --git a/src/cli/mod.rs b/src/cli/mod.rs index a739f9a01..ddc55d5c4 100644 --- a/src/cli/mod.rs +++ b/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), diff --git a/src/cli/serve/develop.rs b/src/cli/serve/develop.rs deleted file mode 100644 index 27aba99ee..000000000 --- a/src/cli/serve/develop.rs +++ /dev/null @@ -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| 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; - -// impl ServerState { -// fn new(serv_path: PathBuf, is_err: ErrStatus) -> Self { -// Self { serv_path, is_err } -// } -// } diff --git a/src/cli/serve/events.rs b/src/cli/serve/events.rs deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/cli/serve/mod.rs b/src/cli/serve/mod.rs index f2ac2b69f..d6287f07d 100644 --- a/src/cli/serve/mod.rs +++ b/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); diff --git a/src/cli/translate/to_component.rs b/src/cli/translate/to_component.rs index d64854d37..972da7a25 100644 --- a/src/cli/translate/to_component.rs +++ b/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, diff --git a/src/lib.rs b/src/lib.rs index d4d3d7953..15c10a3b3 100644 --- a/src/lib.rs +++ b/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::*; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 41fa5835b..69636c6e3 100644 --- a/src/main.rs +++ b/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); + } } } diff --git a/src/err.html b/src/server/err.html similarity index 100% rename from src/err.html rename to src/server/err.html diff --git a/src/server/mod.rs b/src/server/mod.rs index 6c6c08c1f..7c0713c8d 100644 --- a/src/server/mod.rs +++ b/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(); } } diff --git a/src/template/config.toml b/src/template/config.toml new file mode 100644 index 000000000..4c6c2be9c --- /dev/null +++ b/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"] } diff --git a/src/template/default.rs b/src/template/default.rs new file mode 100644 index 000000000..c0fa412ea --- /dev/null +++ b/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." } + } + )) +} diff --git a/src/watch.rs b/src/watch.rs deleted file mode 100644 index 8b1378917..000000000 --- a/src/watch.rs +++ /dev/null @@ -1 +0,0 @@ - From 5395e7693c05caf5446a1cc6671de7e870366569 Mon Sep 17 00:00:00 2001 From: mrxiaozhuox Date: Sun, 23 Jan 2022 10:54:24 +0800 Subject: [PATCH 05/27] fix: `dist` setting --- src/builder.rs | 9 +++++---- src/cli/build/mod.rs | 7 ++++++- src/cli/init/mod.rs | 38 +++++++++++++++++++++++++++++--------- src/cli/mod.rs | 3 +-- src/cli/serve/mod.rs | 11 +++++++---- src/lib.rs | 2 +- src/server/mod.rs | 13 ++++++++++--- src/template/config.toml | 2 +- src/template/default.rs | 2 +- 9 files changed, 61 insertions(+), 26 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 24d9c3151..aad275ff2 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -2,10 +2,10 @@ use crate::{ config::{CrateConfig, ExecutableType}, error::{Error, Result}, }; -use std::{io::Write, process::Command}; +use std::{io::Write, path::PathBuf, process::Command}; use wasm_bindgen_cli_support::Bindgen; -pub fn build(config: &CrateConfig) -> Result<()> { +pub fn build(config: &CrateConfig, dist: PathBuf) -> Result<()> { /* [1] Build the project with cargo, generating a wasm32-unknown-unknown target (is there a more specific, better target to leverage?) [2] Generate the appropriate build folders @@ -15,7 +15,6 @@ pub fn build(config: &CrateConfig) -> Result<()> { */ let CrateConfig { - out_dir, crate_dir, target_dir, static_dir, @@ -23,10 +22,12 @@ pub fn build(config: &CrateConfig) -> Result<()> { .. } = config; + let out_dir = crate_dir.join(dist); + let t_start = std::time::Instant::now(); // [1] Build the .wasm module - log::info!("πŸš… Running build commands..."); + log::info!("πŸš… Running build command..."); let mut cmd = Command::new("cargo"); cmd.current_dir(&crate_dir) .arg("build") diff --git a/src/cli/build/mod.rs b/src/cli/build/mod.rs index fc329a305..760c2fd8e 100644 --- a/src/cli/build/mod.rs +++ b/src/cli/build/mod.rs @@ -1,3 +1,5 @@ +use std::path::PathBuf; + use crate::cfg::ConfigOptsBuild; use structopt::StructOpt; @@ -16,7 +18,10 @@ impl Build { // change the relase state. crate_config.with_release(self.build.release); - crate::builder::build(&crate_config)?; + crate::builder::build( + &crate_config, + self.build.dist.unwrap_or(PathBuf::from("dist")), + )?; Ok(()) } diff --git a/src/cli/init/mod.rs b/src/cli/init/mod.rs index 2325d9c31..dde0ccca3 100644 --- a/src/cli/init/mod.rs +++ b/src/cli/init/mod.rs @@ -1,14 +1,18 @@ -use std::{process::{Command, Stdio}, fs::File, path::PathBuf, io::Write}; +use std::{ + fs::{File, OpenOptions}, + io::Write, + path::PathBuf, + process::{Command, Stdio}, +}; use structopt::StructOpt; -use crate::{error::{Error, Result}, cargo}; +use crate::error::{Error, Result}; /// 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, @@ -16,24 +20,26 @@ pub struct Init { /// 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); + if project_path.join("Cargo.toml").is_file() { + log::warn!("Path '{}' is initialized.", self.path); + return Ok(()); + } + let output = Command::new("cargo") .arg("init") .arg(&self.path) .arg("--bin") .stdout(Stdio::piped()) .stderr(Stdio::piped()) - .output()? - ; + .output()?; if !output.status.success() { return Err(Error::CargoError("Cargo init failed".into())); @@ -54,8 +60,22 @@ impl Init { // log::info!("🎯 Project initialization completed."); - + if !Command::new("cargo") + .args(["add", "dioxus", "--features web"]) + .output()? + .status + .success() + { + let mut file = OpenOptions::new() + .append(true) + .open(project_path.join("Cargo.toml"))?; + file.write_all("dioxus = { version = \"0.1.7\", features = [\"web\"] }".as_bytes())?; + } + + log::info!("\nπŸ’‘ Project initialized:"); + log::info!("🎯> cd {}", self.path); + log::info!("🎯> dioxus serve"); Ok(()) } -} \ No newline at end of file +} diff --git a/src/cli/mod.rs b/src/cli/mod.rs index ddc55d5c4..93cbfef28 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -3,9 +3,9 @@ use structopt::StructOpt; pub mod build; pub mod cfg; pub mod clean; +pub mod init; pub mod serve; pub mod translate; -pub mod init; /// Build, bundle, & ship your Dioxus app. /// @@ -36,7 +36,6 @@ pub enum Commands { Serve(serve::Serve), /// Init a new project for Dioxus. Init(init::Init), - // /// Clean output artifacts. // Clean(clean::Clean), diff --git a/src/cli/serve/mod.rs b/src/cli/serve/mod.rs index d6287f07d..586d49b7c 100644 --- a/src/cli/serve/mod.rs +++ b/src/cli/serve/mod.rs @@ -1,5 +1,5 @@ use crate::{cfg::ConfigOptsServe, server}; -use std::io::Write; +use std::{io::Write, path::PathBuf}; use structopt::StructOpt; /// Run the WASM project on dev-server @@ -16,15 +16,18 @@ impl Serve { // change the relase state. crate_config.with_release(self.serve.release); - crate::builder::build(&crate_config).expect("build failed"); + let dist_path = self.serve.dist.clone().unwrap_or(PathBuf::from("dist")); + + crate::builder::build(&crate_config, dist_path.clone()).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"))?; + let mut file = + std::fs::File::create(crate_config.crate_dir.join(dist_path).join("index.html"))?; file.write_all(serve_html.as_bytes())?; // start the develop server - server::startup(crate_config.clone()).await?; + server::startup(crate_config.clone(), &self.serve).await?; Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index 15c10a3b3..395cbc8dc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,4 +15,4 @@ pub mod error; pub use error::*; pub mod logging; -pub use logging::*; \ No newline at end of file +pub use logging::*; diff --git a/src/server/mod.rs b/src/server/mod.rs index 7c0713c8d..4f04d3268 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -6,8 +6,11 @@ use axum::{ AddExtensionLayer, Router, }; use notify::{watcher, DebouncedEvent, Watcher}; -use std::sync::{mpsc::channel, Arc, Mutex}; use std::time::Duration; +use std::{ + path::PathBuf, + sync::{mpsc::channel, Arc, Mutex}, +}; use tower_http::services::ServeDir; use crate::{builder, CrateConfig}; @@ -22,7 +25,10 @@ impl WsRelodState { } } -pub async fn startup(config: CrateConfig) -> anyhow::Result<()> { +pub async fn startup( + config: CrateConfig, + opts: &crate::cfg::ConfigOptsServe, +) -> anyhow::Result<()> { log::info!("πŸš€ Starting development server..."); let (tx, rx) = channel(); @@ -40,6 +46,7 @@ pub async fn startup(config: CrateConfig) -> anyhow::Result<()> { let watcher_conf = config.clone(); let watcher_ws_state = ws_reload_state.clone(); + let dist_path = opts.dist.clone().unwrap_or(PathBuf::from("dist")); tokio::spawn(async move { loop { if let Ok(v) = rx.recv() { @@ -48,7 +55,7 @@ pub async fn startup(config: CrateConfig) -> anyhow::Result<()> { | DebouncedEvent::Write(_) | DebouncedEvent::Remove(_) | DebouncedEvent::Rename(_, _) => { - if let Ok(_) = builder::build(&watcher_conf) { + if let Ok(_) = builder::build(&watcher_conf, dist_path.clone()) { // change the websocket reload state to true; // the page will auto-reload. watcher_ws_state.lock().unwrap().change(); diff --git a/src/template/config.toml b/src/template/config.toml index 4c6c2be9c..9644babc1 100644 --- a/src/template/config.toml +++ b/src/template/config.toml @@ -6,4 +6,4 @@ 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"] } +dioxus = { version = "0.1.7", features = ["web"] } \ No newline at end of file diff --git a/src/template/default.rs b/src/template/default.rs index c0fa412ea..ac1614fc4 100644 --- a/src/template/default.rs +++ b/src/template/default.rs @@ -1,7 +1,7 @@ use dioxus::prelude::*; fn main() { - dioxus::desktop::launch(app); + dioxus::web::launch(app); } fn app(cx: Scope) -> Element { From 70adff7e443500e05ce8ff32a803971c4f816aff Mon Sep 17 00:00:00 2001 From: mrxiaozhuox Date: Sun, 23 Jan 2022 12:11:17 +0800 Subject: [PATCH 06/27] feat: add `dioxus.toml` support --- Cargo.toml | 1 + Dioxus.toml | 42 ++++++++++++++++++++++++++++++++++++++++++ src/builder.rs | 13 ++----------- src/config.rs | 40 +++++++++++++++++++++++++++++++++++++++- src/server/mod.rs | 24 ++++++++++++++---------- 5 files changed, 98 insertions(+), 22 deletions(-) create mode 100644 Dioxus.toml diff --git a/Cargo.toml b/Cargo.toml index cd1dec98b..8bb6fd3ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ wasm-bindgen-cli-support = "0.2.79" anyhow = "1.0.38" serde = { version = "1.0.133", features = ["derive"] } serde_json = "1" +toml = "0.5.8" fs_extra = "1.2.0" cargo_toml = "0.10.0" futures = "0.3.12" diff --git a/Dioxus.toml b/Dioxus.toml new file mode 100644 index 000000000..abd2d1ee4 --- /dev/null +++ b/Dioxus.toml @@ -0,0 +1,42 @@ +[application] + +# App (Project) Name +name = "dioxus-cli" + +# Dioxus App Support Platform +# desktop, web, mobile, ssr +platforms = ["web"] + +[web.app] + +# Web `build` & `serve` dist path +out_dir = "dist" + +# resource (static) file folder +static_dir = "static" + +[web.watcher] + + +watch_path = ["src"] + +# include `assets` in web platform +[web.resource] + +# CSS style file +style = [ + "./my-style.css" +] + +# Javascript code file +script = [ + "./app.js" +] + +[web.resource.dev] + +# Javascript code file +# serve: [dev-server] only +script = [ + "./debug.js" +] \ No newline at end of file diff --git a/src/builder.rs b/src/builder.rs index aad275ff2..58ee085fc 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -48,14 +48,12 @@ pub fn build(config: &CrateConfig, dist: PathBuf) -> Result<()> { let output = cmd.output()?; - if output.status.success() { - std::io::stdout().write_all(&output.stdout).unwrap(); - } else { + if !output.status.success() { log::error!("Build failed!"); let reason = String::from_utf8_lossy(&output.stderr).to_string(); return Err(Error::BuildFailed(reason)); } - + // [2] Establish the output directory structure let bindgen_outdir = out_dir.join("assets"); @@ -88,13 +86,6 @@ pub fn build(config: &CrateConfig, dist: PathBuf) -> Result<()> { .out_name("module") .generate(&bindgen_outdir)?; - // [4] - // TODO: wasm-opt - - // [5] Generate the html file with the module name - // TODO: support names via options - - // log::info!("Writing to '{:#?}' directory...", out_dir); let copy_options = fs_extra::dir::CopyOptions::new(); if static_dir.is_dir() { match fs_extra::dir::copy(static_dir, out_dir, ©_options) { diff --git a/src/config.rs b/src/config.rs index d17294b18..9d38160f2 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,44 @@ use crate::error::Result; +use serde::{Deserialize, Serialize}; use std::path::PathBuf; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DioxusConfig { + application: ApplicationConfig, + web: WebConfig, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ApplicationConfig { + name: String, + platform: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct WebConfig { + app: WebAppConfing, + watcher: WebWatcherConfing, + resource: WebResourceConfing, +} + + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct WebAppConfing { + +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct WebWatcherConfing { + +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct WebResourceConfing { + +} + + #[derive(Debug, Clone)] pub struct CrateConfig { pub out_dir: PathBuf, @@ -86,4 +124,4 @@ impl CrateConfig { // self.release = options.release; // self.out_dir = tempfile::Builder::new().tempdir().expect("").into_path(); // } -} +} \ No newline at end of file diff --git a/src/server/mod.rs b/src/server/mod.rs index 4f04d3268..6b91a2e71 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -33,6 +33,8 @@ pub async fn startup( let (tx, rx) = channel(); + let dist_path = opts.dist.clone().unwrap_or(PathBuf::from("dist")); + // file watcher: check file change let mut watcher = watcher(tx, Duration::from_secs(2)).unwrap(); watcher @@ -46,7 +48,7 @@ pub async fn startup( let watcher_conf = config.clone(); let watcher_ws_state = ws_reload_state.clone(); - let dist_path = opts.dist.clone().unwrap_or(PathBuf::from("dist")); + let watcher_dist_path = dist_path.clone(); tokio::spawn(async move { loop { if let Ok(v) = rx.recv() { @@ -55,7 +57,7 @@ pub async fn startup( | DebouncedEvent::Write(_) | DebouncedEvent::Remove(_) | DebouncedEvent::Rename(_, _) => { - if let Ok(_) = builder::build(&watcher_conf, dist_path.clone()) { + if let Ok(_) = builder::build(&watcher_conf, watcher_dist_path.clone()) { // change the websocket reload state to true; // the page will auto-reload. watcher_ws_state.lock().unwrap().change(); @@ -69,14 +71,16 @@ pub async fn startup( 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.crate_dir.join(&dist_path))).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 From 9bb10c65a3cbabef127f6af5c0d1eadab1d01531 Mon Sep 17 00:00:00 2001 From: mrxiaozhuox Date: Sun, 23 Jan 2022 17:35:59 +0800 Subject: [PATCH 07/27] feat: add `Dioxus.toml` --- src/assets/autoreload.js | 25 +++++++++++++ src/assets/index.html | 18 +++++++++ src/builder.rs | 81 ++++++++++++++++++++++++++-------------- src/cli/build/mod.rs | 7 +--- src/cli/cfg.rs | 16 -------- src/cli/init/mod.rs | 27 ++++++++++---- src/cli/serve/mod.rs | 28 ++++++++++---- src/config.rs | 78 ++++++++++++++++++++++++++++++-------- src/server/err.html | 19 ---------- src/server/mod.rs | 54 ++++++++++++++++----------- src/server/serve.html | 39 ------------------- src/template/config.toml | 43 +++++++++++++++++---- src/template/default.rs | 4 +- 13 files changed, 269 insertions(+), 170 deletions(-) create mode 100644 src/assets/autoreload.js create mode 100644 src/assets/index.html delete mode 100644 src/server/err.html delete mode 100644 src/server/serve.html diff --git a/src/assets/autoreload.js b/src/assets/autoreload.js new file mode 100644 index 000000000..ff4444085 --- /dev/null +++ b/src/assets/autoreload.js @@ -0,0 +1,25 @@ +(function () { + var protocol = window.location.protocol === "https:" ? "wss:" : "ws:"; + + var url = protocol + "//" + window.location.host + "/_dioxus/ws"; + + var poll_interval = 2000; + + var reload_upon_connect = () => { + window.setTimeout(() => { + var ws = new WebSocket(url); + ws.onopen = () => window.location.reload(); + ws.onclose = reload_upon_connect; + }, poll_interval); + }; + + var ws = new WebSocket(url); + + ws.onmessage = (ev) => { + if (ev.data == "reload") { + window.location.reload(); + } + }; + + ws.onclose = reload_upon_connect; +})(); \ No newline at end of file diff --git a/src/assets/index.html b/src/assets/index.html new file mode 100644 index 000000000..12e1e5678 --- /dev/null +++ b/src/assets/index.html @@ -0,0 +1,18 @@ + + + {app_title} + + + {style_include} + + + +
+ + + {script_include} + + diff --git a/src/builder.rs b/src/builder.rs index 58ee085fc..381ef8b28 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -1,11 +1,12 @@ use crate::{ config::{CrateConfig, ExecutableType}, error::{Error, Result}, + DioxusConfig, }; -use std::{io::Write, path::PathBuf, process::Command}; +use std::process::Command; use wasm_bindgen_cli_support::Bindgen; -pub fn build(config: &CrateConfig, dist: PathBuf) -> Result<()> { +pub fn build(config: &CrateConfig) -> Result<()> { /* [1] Build the project with cargo, generating a wasm32-unknown-unknown target (is there a more specific, better target to leverage?) [2] Generate the appropriate build folders @@ -15,15 +16,15 @@ pub fn build(config: &CrateConfig, dist: PathBuf) -> Result<()> { */ let CrateConfig { + out_dir, crate_dir, target_dir, static_dir, executable, + dioxus_config, .. } = config; - let out_dir = crate_dir.join(dist); - let t_start = std::time::Instant::now(); // [1] Build the .wasm module @@ -53,7 +54,7 @@ pub fn build(config: &CrateConfig, dist: PathBuf) -> Result<()> { let reason = String::from_utf8_lossy(&output.stderr).to_string(); return Err(Error::BuildFailed(reason)); } - + // [2] Establish the output directory structure let bindgen_outdir = out_dir.join("assets"); @@ -83,7 +84,7 @@ pub fn build(config: &CrateConfig, dist: PathBuf) -> Result<()> { .keep_debug(true) .remove_name_section(false) .remove_producers_section(false) - .out_name("module") + .out_name(&dioxus_config.application.name) .generate(&bindgen_outdir)?; let copy_options = fs_extra::dir::CopyOptions::new(); @@ -101,28 +102,52 @@ pub fn build(config: &CrateConfig, dist: PathBuf) -> Result<()> { Ok(()) } -pub fn gen_page(module: &str) -> String { - format!( - r#" - - - - - - -
-
- - -
- - -"#, - module - ) +pub fn gen_page(config: &DioxusConfig, serve: bool) -> String { + let mut html = String::from(include_str!("./assets/index.html")); + + let resouces = config.web.resource.clone(); + + let mut style_list = resouces.style.unwrap_or(vec![]); + let mut script_list = resouces.script.unwrap_or(vec![]); + + if serve { + let mut dev_style = resouces.dev.style.clone().unwrap_or(vec![]); + let mut dev_script = resouces.dev.script.clone().unwrap_or(vec![]); + style_list.append(&mut dev_style); + script_list.append(&mut dev_script); + } + + let mut style_str = String::new(); + for style in style_list { + style_str.push_str(&format!( + "\n", + &style.to_str().unwrap(), + )) + } + html = html.replace("{style_include}", &style_str); + + let mut script_str = String::new(); + for script in script_list { + script_str.push_str(&format!( + "\n", + &script.to_str().unwrap(), + )) + } + + html = html.replace("{script_include}", &script_str); + + if serve { + html += &format!( + "", + include_str!("./assets/autoreload.js") + ); + } + + html = html.replace("{app_name}", &config.application.name); + + let title = config.web.app.title.clone().unwrap_or("dioxus | β›Ί".into()); + + html.replace("{app_title}", &title) } // use binary_install::{Cache, Download}; diff --git a/src/cli/build/mod.rs b/src/cli/build/mod.rs index 760c2fd8e..fc329a305 100644 --- a/src/cli/build/mod.rs +++ b/src/cli/build/mod.rs @@ -1,5 +1,3 @@ -use std::path::PathBuf; - use crate::cfg::ConfigOptsBuild; use structopt::StructOpt; @@ -18,10 +16,7 @@ impl Build { // change the relase state. crate_config.with_release(self.build.release); - crate::builder::build( - &crate_config, - self.build.dist.unwrap_or(PathBuf::from("dist")), - )?; + crate::builder::build(&crate_config)?; Ok(()) } diff --git a/src/cli/cfg.rs b/src/cli/cfg.rs index 2c5f42a26..ab68ce4f9 100644 --- a/src/cli/cfg.rs +++ b/src/cli/cfg.rs @@ -16,14 +16,6 @@ pub struct ConfigOptsBuild { #[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, - /// Optional pattern for the app loader script [default: None] /// /// Patterns should include the sequences `{base}`, `{wasm}`, and `{js}` in order to @@ -74,14 +66,6 @@ pub struct ConfigOptsServe { #[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. diff --git a/src/cli/init/mod.rs b/src/cli/init/mod.rs index dde0ccca3..52dcf4c14 100644 --- a/src/cli/init/mod.rs +++ b/src/cli/init/mod.rs @@ -13,9 +13,9 @@ use crate::error::{Error, Result}; #[derive(Clone, Debug, StructOpt)] #[structopt(name = "init")] pub struct Init { - /// Init project path + /// Init project name #[structopt(default_value = ".")] - path: String, + name: String, /// Template path #[structopt(default_value = "default", long)] @@ -24,18 +24,23 @@ pub struct Init { impl Init { pub fn init(self) -> Result<()> { - log::info!("πŸ”§ Start to init a new project '{}'.", self.path); + if self.name.contains(".") { + log::error!("❗Unsupported project name."); + return Ok(()); + } - let project_path = PathBuf::from(&self.path); + log::info!("πŸ”§ Start to init a new project '{}'.", self.name); + + let project_path = PathBuf::from(&self.name); if project_path.join("Cargo.toml").is_file() { - log::warn!("Path '{}' is initialized.", self.path); + log::warn!("Folder '{}' is initialized.", self.name); return Ok(()); } let output = Command::new("cargo") .arg("init") - .arg(&self.path) + .arg(&format!("./{}", self.name)) .arg("--bin") .stdout(Stdio::piped()) .stderr(Stdio::piped()) @@ -58,6 +63,11 @@ impl Init { let mut file = File::create(main_rs_file)?; file.write_all(&template_str.as_bytes())?; + let mut file = File::create(project_path.join("Dioxus.toml"))?; + let dioxus_conf = String::from(include_str!("../../template/config.toml")) + .replace("{project-name}", &self.name); + file.write_all(dioxus_conf.as_bytes())?; + // log::info!("🎯 Project initialization completed."); if !Command::new("cargo") @@ -72,8 +82,9 @@ impl Init { file.write_all("dioxus = { version = \"0.1.7\", features = [\"web\"] }".as_bytes())?; } - log::info!("\nπŸ’‘ Project initialized:"); - log::info!("🎯> cd {}", self.path); + println!(""); + log::info!("πŸ’‘ Project initialized:"); + log::info!("🎯> cd ./{}", self.name); log::info!("🎯> dioxus serve"); Ok(()) diff --git a/src/cli/serve/mod.rs b/src/cli/serve/mod.rs index 586d49b7c..7d580f7fb 100644 --- a/src/cli/serve/mod.rs +++ b/src/cli/serve/mod.rs @@ -1,4 +1,4 @@ -use crate::{cfg::ConfigOptsServe, server}; +use crate::{cfg::ConfigOptsServe, gen_page, server}; use std::{io::Write, path::PathBuf}; use structopt::StructOpt; @@ -16,19 +16,31 @@ impl Serve { // change the relase state. crate_config.with_release(self.serve.release); - let dist_path = self.serve.dist.clone().unwrap_or(PathBuf::from("dist")); + crate::builder::build(&crate_config).expect("build failed"); - crate::builder::build(&crate_config, dist_path.clone()).expect("build failed"); + let serve_html = gen_page(&crate_config.dioxus_config, true); - let serve_html = String::from(include_str!("../../server/serve.html")); - - let mut file = - std::fs::File::create(crate_config.crate_dir.join(dist_path).join("index.html"))?; + let mut file = std::fs::File::create( + crate_config + .crate_dir + .join( + crate_config + .dioxus_config + .web + .app + .out_dir + .clone() + .unwrap_or(PathBuf::from("dist")), + ) + .join("index.html"), + )?; file.write_all(serve_html.as_bytes())?; // start the develop server - server::startup(crate_config.clone(), &self.serve).await?; + server::startup(crate_config.clone()).await?; Ok(()) } + + pub fn regen_page() {} } diff --git a/src/config.rs b/src/config.rs index 9d38160f2..39a3d2038 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,43 +1,76 @@ use crate::error::Result; use serde::{Deserialize, Serialize}; -use std::path::PathBuf; - +use std::{fs::File, io::Read, path::PathBuf}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DioxusConfig { - application: ApplicationConfig, - web: WebConfig, + pub application: ApplicationConfig, + pub web: WebConfig, +} + +impl DioxusConfig { + pub fn load() -> crate::error::Result { + let crate_dir = crate::cargo::crate_root()?; + + if !crate_dir.join("Dioxus.toml").is_file() { + return Err(crate::error::Error::CargoError( + "Dioxus.toml not found".into(), + )); + } + + let mut dioxus_conf_file = File::open(crate_dir.join("Dioxus.toml"))?; + let mut meta_str = String::new(); + dioxus_conf_file.read_to_string(&mut meta_str)?; + + match toml::from_str::(&meta_str) { + Ok(v) => return Ok(v), + Err(e) => { + log::error!("{}", e); + return Err(crate::error::Error::Unique( + "Dioxus.toml parse failed".into(), + )); + } + } + } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ApplicationConfig { - name: String, - platform: Vec, + pub name: String, + pub platforms: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct WebConfig { - app: WebAppConfing, - watcher: WebWatcherConfing, - resource: WebResourceConfing, + pub app: WebAppConfing, + pub watcher: WebWatcherConfing, + pub resource: WebResourceConfing, } - #[derive(Debug, Clone, Serialize, Deserialize)] pub struct WebAppConfing { - + pub title: Option, + pub out_dir: Option, + pub static_dir: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct WebWatcherConfing { - + pub watch_path: Option>, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct WebResourceConfing { - + pub dev: WebDevResourceConfing, + pub style: Option>, + pub script: Option>, } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct WebDevResourceConfing { + pub style: Option>, + pub script: Option>, +} #[derive(Debug, Clone)] pub struct CrateConfig { @@ -48,6 +81,7 @@ pub struct CrateConfig { pub static_dir: PathBuf, pub manifest: cargo_toml::Manifest, pub executable: ExecutableType, + pub dioxus_config: DioxusConfig, pub release: bool, } @@ -60,12 +94,23 @@ pub enum ExecutableType { impl CrateConfig { pub fn new() -> Result { + let dioxus_config = DioxusConfig::load()?; + let crate_dir = crate::cargo::crate_root()?; let workspace_dir = crate::cargo::workspace_root()?; let target_dir = workspace_dir.join("target"); - let out_dir = crate_dir.join("public"); + + let out_dir = match dioxus_config.web.app.out_dir { + Some(ref v) => crate_dir.join(v), + None => crate_dir.join("dist"), + }; + let cargo_def = &crate_dir.join("Cargo.toml"); - let static_dir = crate_dir.join("static"); + + let static_dir = match dioxus_config.web.app.static_dir { + Some(ref v) => crate_dir.join(v), + None => crate_dir.join("static"), + }; let manifest = cargo_toml::Manifest::from_path(&cargo_def).unwrap(); @@ -96,6 +141,7 @@ impl CrateConfig { manifest, executable, release, + dioxus_config, }) } @@ -124,4 +170,4 @@ impl CrateConfig { // self.release = options.release; // self.out_dir = tempfile::Builder::new().tempdir().expect("").into_path(); // } -} \ No newline at end of file +} diff --git a/src/server/err.html b/src/server/err.html deleted file mode 100644 index 8fdbf9bae..000000000 --- a/src/server/err.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - -
-

- Sorry, but building your application failed. -

-

- Here's the error: -

- - {err} - -
- - - diff --git a/src/server/mod.rs b/src/server/mod.rs index 6b91a2e71..4d33f524e 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -13,7 +13,7 @@ use std::{ }; use tower_http::services::ServeDir; -use crate::{builder, CrateConfig}; +use crate::{builder, crate_root, CrateConfig}; struct WsRelodState { update: bool, @@ -25,42 +25,54 @@ impl WsRelodState { } } -pub async fn startup( - config: CrateConfig, - opts: &crate::cfg::ConfigOptsServe, -) -> anyhow::Result<()> { +pub async fn startup(config: CrateConfig) -> anyhow::Result<()> { log::info!("πŸš€ Starting development server..."); let (tx, rx) = channel(); - let dist_path = opts.dist.clone().unwrap_or(PathBuf::from("dist")); + let dist_path = config.out_dir.clone(); // file watcher: check file change - let mut watcher = watcher(tx, Duration::from_secs(2)).unwrap(); + let mut watcher = watcher(tx, Duration::from_secs(1)).unwrap(); watcher - .watch( - config.crate_dir.join("src").clone(), - notify::RecursiveMode::Recursive, - ) + .watch(config.crate_dir.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(); - let watcher_dist_path = dist_path.clone(); tokio::spawn(async move { + let allow_watch_path = watcher_conf + .dioxus_config + .web + .watcher + .watch_path + .clone() + .unwrap_or(vec![PathBuf::from("src")]); + let crate_dir = watcher_conf.crate_dir.clone(); loop { if let Ok(v) = rx.recv() { match v { - DebouncedEvent::Create(_) - | DebouncedEvent::Write(_) - | DebouncedEvent::Remove(_) - | DebouncedEvent::Rename(_, _) => { - if let Ok(_) = builder::build(&watcher_conf, watcher_dist_path.clone()) { - // change the websocket reload state to true; - // the page will auto-reload. - watcher_ws_state.lock().unwrap().change(); + DebouncedEvent::Create(e) + | DebouncedEvent::Write(e) + | DebouncedEvent::Remove(e) + | DebouncedEvent::Rename(e, _) => { + let mut reload = false; + for path in &allow_watch_path { + let temp = crate_dir.clone().join(path); + if e.starts_with(temp) { + reload = true; + break; + } + } + + if reload { + 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(); + } } } _ => {} @@ -70,7 +82,7 @@ pub async fn startup( }); let app = Router::new() - .route("/ws", get(ws_handler)) + .route("/_dioxus/ws", get(ws_handler)) .fallback( get_service(ServeDir::new(config.crate_dir.join(&dist_path))).handle_error( |error: std::io::Error| async move { diff --git a/src/server/serve.html b/src/server/serve.html deleted file mode 100644 index b5268bd68..000000000 --- a/src/server/serve.html +++ /dev/null @@ -1,39 +0,0 @@ - - - - - Dioxus-CLI Dev Server - - -
- - - - - - - diff --git a/src/template/config.toml b/src/template/config.toml index 9644babc1..ea1efbd4b 100644 --- a/src/template/config.toml +++ b/src/template/config.toml @@ -1,9 +1,38 @@ -[package] -name = "{project}" -version = "0.1.0" -edition = "2021" +[application] -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +# App (Project) Name +name = "{project-name}" -[dependencies] -dioxus = { version = "0.1.7", features = ["web"] } \ No newline at end of file +# Dioxus App Support Platform +# desktop, web, mobile, ssr +platforms = ["web"] + +[web.app] + +# HTML title tag content +title = "dioxus | β›Ί" + +# Web `build` & `serve` dist path +out_dir = "dist" + +# resource (static) file folder +static_dir = "static" + +[web.watcher] + +watch_path = ["src"] + +# include `assets` in web platform +[web.resource] + +# CSS style file +style = [] + +# Javascript code file +script = [] + +[web.resource.dev] + +# Javascript code file +# serve: [dev-server] only +script = [] \ No newline at end of file diff --git a/src/template/default.rs b/src/template/default.rs index ac1614fc4..47b4a2c1d 100644 --- a/src/template/default.rs +++ b/src/template/default.rs @@ -8,8 +8,8 @@ fn app(cx: Scope) -> Element { cx.render(rsx! ( div { style: "text-align: center;", - h2 { "πŸŒ—πŸš€ Dioxus" } - p { strong { "Frontend that scales." } } + h1 { "πŸŒ— Dioxus πŸš€" } + h3 { "Frontend that scales." } p { "Dioxus is a portable, performant, and ergonomic framework for building cross-platform user interfaces in Rust." } } )) From 7343af2089ae0ff1f3da6b418d8168651a201832 Mon Sep 17 00:00:00 2001 From: mrxiaozhuox Date: Sun, 23 Jan 2022 22:50:23 +0800 Subject: [PATCH 08/27] feat: `clean` command --- src/assets/index.html | 4 ++-- src/builder.rs | 30 ++++++++++++++++++------- src/cli/build/mod.rs | 22 +++++++++++++++++- src/cli/clean/mod.rs | 35 +++++++++++++++++++++++++++++ src/cli/mod.rs | 3 +-- src/cli/serve/mod.rs | 17 +++++++++----- src/config.rs | 48 ++++++++++++++++++++++++++++++++-------- src/main.rs | 8 ++++--- src/server/mod.rs | 11 ++++++++- src/template/config.toml | 8 +++++-- 10 files changed, 152 insertions(+), 34 deletions(-) diff --git a/src/assets/index.html b/src/assets/index.html index 12e1e5678..9934e490d 100644 --- a/src/assets/index.html +++ b/src/assets/index.html @@ -10,8 +10,8 @@
{script_include} diff --git a/src/builder.rs b/src/builder.rs index 381ef8b28..0ecde96cd 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -19,7 +19,7 @@ pub fn build(config: &CrateConfig) -> Result<()> { out_dir, crate_dir, target_dir, - static_dir, + public_dir, executable, dioxus_config, .. @@ -56,7 +56,7 @@ pub fn build(config: &CrateConfig) -> Result<()> { } // [2] Establish the output directory structure - let bindgen_outdir = out_dir.join("assets"); + let bindgen_outdir = out_dir.join("assets").join("dioxus"); // [3] Bindgen the final binary for use easy linking let mut bindgen_builder = Bindgen::new(); @@ -87,12 +87,26 @@ pub fn build(config: &CrateConfig) -> Result<()> { .out_name(&dioxus_config.application.name) .generate(&bindgen_outdir)?; - let copy_options = fs_extra::dir::CopyOptions::new(); - if static_dir.is_dir() { - match fs_extra::dir::copy(static_dir, out_dir, ©_options) { - Ok(_) => {} - Err(_e) => { - log::warn!("Error copying dir: {}", _e); + let copy_options = fs_extra::dir::CopyOptions { + overwrite: true, + skip_exist: false, + buffer_size: 64000, + copy_inside: false, + content_only: false, + depth: 0, + }; + if public_dir.is_dir() { + for entry in std::fs::read_dir(&public_dir)? { + let path = entry?.path(); + if path.is_file() { + std::fs::copy(&path, out_dir.join(path.file_name().unwrap()))?; + } else { + match fs_extra::dir::copy(&path, out_dir, ©_options) { + Ok(_) => {} + Err(_e) => { + log::warn!("Error copying dir: {}", _e); + } + } } } } diff --git a/src/cli/build/mod.rs b/src/cli/build/mod.rs index fc329a305..e24a99822 100644 --- a/src/cli/build/mod.rs +++ b/src/cli/build/mod.rs @@ -1,4 +1,6 @@ -use crate::cfg::ConfigOptsBuild; +use std::{io::Write, path::PathBuf}; + +use crate::{cfg::ConfigOptsBuild, gen_page}; use structopt::StructOpt; /// Build the Rust WASM app and all of its assets. @@ -18,6 +20,24 @@ impl Build { crate::builder::build(&crate_config)?; + let temp = gen_page(&crate_config.dioxus_config, false); + + let mut file = std::fs::File::create( + crate_config + .crate_dir + .join( + crate_config + .dioxus_config + .web + .app + .out_dir + .clone() + .unwrap_or(PathBuf::from("dist")), + ) + .join("index.html"), + )?; + file.write_all(temp.as_bytes())?; + Ok(()) } } diff --git a/src/cli/clean/mod.rs b/src/cli/clean/mod.rs index 1edcd053a..4ab7653d2 100644 --- a/src/cli/clean/mod.rs +++ b/src/cli/clean/mod.rs @@ -1,6 +1,41 @@ +use std::{ + fs::remove_dir_all, + path::PathBuf, + process::{Command, Stdio}, +}; + use structopt::StructOpt; /// Build the Rust WASM app and all of its assets. #[derive(Clone, Debug, StructOpt)] #[structopt(name = "clean")] pub struct Clean {} + +impl Clean { + pub fn clean(self) -> anyhow::Result<()> { + let crate_config = crate::CrateConfig::new()?; + + let output = Command::new("cargo") + .arg("clean") + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output()?; + + if !output.status.success() { + log::error!("Cargo clean failed."); + return Ok(()); + } + + let out_dir = crate_config + .dioxus_config + .web + .app + .out_dir + .unwrap_or(PathBuf::from("dist")); + if crate_config.crate_dir.join(&out_dir).is_dir() { + remove_dir_all(crate_config.crate_dir.join(&out_dir))?; + } + + Ok(()) + } +} diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 93cbfef28..6224cea32 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -37,8 +37,7 @@ pub enum Commands { /// Init a new project for Dioxus. Init(init::Init), // /// Clean output artifacts. - // Clean(clean::Clean), - + Clean(clean::Clean), // /// Trunk config controls. // Config(config::Config), } diff --git a/src/cli/serve/mod.rs b/src/cli/serve/mod.rs index 7d580f7fb..19a7f28ff 100644 --- a/src/cli/serve/mod.rs +++ b/src/cli/serve/mod.rs @@ -1,4 +1,4 @@ -use crate::{cfg::ConfigOptsServe, gen_page, server}; +use crate::{cfg::ConfigOptsServe, gen_page, server, CrateConfig}; use std::{io::Write, path::PathBuf}; use structopt::StructOpt; @@ -18,6 +18,16 @@ impl Serve { crate::builder::build(&crate_config).expect("build failed"); + // generate dev-index page + Serve::regen_dev_page(&crate_config)?; + + // start the develop server + server::startup(crate_config.clone()).await?; + + Ok(()) + } + + pub fn regen_dev_page(crate_config: &CrateConfig) -> anyhow::Result<()> { let serve_html = gen_page(&crate_config.dioxus_config, true); let mut file = std::fs::File::create( @@ -36,11 +46,6 @@ impl Serve { )?; file.write_all(serve_html.as_bytes())?; - // start the develop server - server::startup(crate_config.clone()).await?; - Ok(()) } - - pub fn regen_page() {} } diff --git a/src/config.rs b/src/config.rs index 39a3d2038..403ad0e81 100644 --- a/src/config.rs +++ b/src/config.rs @@ -13,9 +13,8 @@ impl DioxusConfig { let crate_dir = crate::cargo::crate_root()?; if !crate_dir.join("Dioxus.toml").is_file() { - return Err(crate::error::Error::CargoError( - "Dioxus.toml not found".into(), - )); + log::warn!("Config file: `Dioxus.toml` not found."); + return Ok(Self::default()); } let mut dioxus_conf_file = File::open(crate_dir.join("Dioxus.toml"))?; @@ -34,6 +33,36 @@ impl DioxusConfig { } } +impl Default for DioxusConfig { + fn default() -> Self { + Self { + application: ApplicationConfig { + name: "dioxus".into(), + platforms: vec![String::from("web")], + }, + web: WebConfig { + app: WebAppConfing { + title: Some("dioxus | β›Ί".into()), + out_dir: Some(PathBuf::from("dist")), + public_dir: Some(PathBuf::from("public")), + }, + watcher: WebWatcherConfing { + watch_path: Some(vec![PathBuf::from("src")]), + reload_html: Some(false), + }, + resource: WebResourceConfing { + dev: WebDevResourceConfing { + style: Some(vec![]), + script: Some(vec![]), + }, + style: Some(vec![]), + script: Some(vec![]), + }, + }, + } + } +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ApplicationConfig { pub name: String, @@ -51,12 +80,13 @@ pub struct WebConfig { pub struct WebAppConfing { pub title: Option, pub out_dir: Option, - pub static_dir: Option, + pub public_dir: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct WebWatcherConfing { pub watch_path: Option>, + pub reload_html: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -78,7 +108,7 @@ pub struct CrateConfig { pub crate_dir: PathBuf, pub workspace_dir: PathBuf, pub target_dir: PathBuf, - pub static_dir: PathBuf, + pub public_dir: PathBuf, pub manifest: cargo_toml::Manifest, pub executable: ExecutableType, pub dioxus_config: DioxusConfig, @@ -107,9 +137,9 @@ impl CrateConfig { let cargo_def = &crate_dir.join("Cargo.toml"); - let static_dir = match dioxus_config.web.app.static_dir { + let public_dir = match dioxus_config.web.app.public_dir { Some(ref v) => crate_dir.join(v), - None => crate_dir.join("static"), + None => crate_dir.join("public"), }; let manifest = cargo_toml::Manifest::from_path(&cargo_def).unwrap(); @@ -124,7 +154,7 @@ impl CrateConfig { (&manifest) .package .as_ref() - .and_then(|pkg| Some(pkg.name.replace("-", "_"))) + .and_then(|pkg| Some(pkg.name.clone())) .clone() }) .expect("No lib found from cargo metadata"); @@ -137,7 +167,7 @@ impl CrateConfig { crate_dir, workspace_dir, target_dir, - static_dir, + public_dir, manifest, executable, release, diff --git a/src/main.rs b/src/main.rs index 69636c6e3..70f92cf95 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,9 +19,11 @@ async fn main() -> Result<()> { } } - // Commands::Clean(_) => { - // // - // } + Commands::Clean(opts) => { + if let Err(e) = opts.clean() { + log::error!("clean error: {}", e); + } + } // Commands::Config(_) => { // // diff --git a/src/server/mod.rs b/src/server/mod.rs index 4d33f524e..3151975d5 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -13,7 +13,7 @@ use std::{ }; use tower_http::services::ServeDir; -use crate::{builder, crate_root, CrateConfig}; +use crate::{builder, serve::Serve, CrateConfig}; struct WsRelodState { update: bool, @@ -71,6 +71,15 @@ pub async fn startup(config: CrateConfig) -> anyhow::Result<()> { if let Ok(_) = builder::build(&watcher_conf) { // change the websocket reload state to true; // the page will auto-reload. + if watcher_conf + .dioxus_config + .web + .watcher + .reload_html + .unwrap_or(false) + { + let _ = Serve::regen_dev_page(&watcher_conf); + } watcher_ws_state.lock().unwrap().change(); } } diff --git a/src/template/config.toml b/src/template/config.toml index ea1efbd4b..44f5baabd 100644 --- a/src/template/config.toml +++ b/src/template/config.toml @@ -15,11 +15,15 @@ title = "dioxus | β›Ί" # Web `build` & `serve` dist path out_dir = "dist" -# resource (static) file folder -static_dir = "static" +# resource (public) file folder +public_dir = "public" [web.watcher] +# when watcher trigger, regenerate the `index.html` +reload_html = true + +# which files or dirs will be watcher monitoring watch_path = ["src"] # include `assets` in web platform From bdcc06648b0275404b8626bcae6acf62b8af7af8 Mon Sep 17 00:00:00 2001 From: mrxiaozhuox Date: Mon, 24 Jan 2022 05:55:40 +0800 Subject: [PATCH 09/27] feat: init use `cargo-generate ` --- Cargo.toml | 3 +- Dioxus.toml | 2 +- src/cli/build/mod.rs | 4 ++ src/cli/cfg.rs | 8 ++++ src/cli/init/mod.rs | 97 ++++++++++++++++++++++------------------ src/cli/serve/mod.rs | 5 +++ src/template/config.toml | 42 ----------------- src/template/default.rs | 16 ------- 8 files changed, 74 insertions(+), 103 deletions(-) delete mode 100644 src/template/config.toml delete mode 100644 src/template/default.rs diff --git a/Cargo.toml b/Cargo.toml index 8bb6fd3ce..68dd3ee56 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dioxus-cli" -version = "0.1.1" +version = "0.1.2" authors = ["Jonathan Kelley"] edition = "2018" description = "CLI tool for developing, testing, and publishing Dioxus apps" @@ -28,6 +28,7 @@ structopt = "0.3.25" cargo_metadata = "0.14.1" tokio = { version = "1.15.0", features = ["full"] } atty = "0.2.14" +regex = "1.5.4" axum = { version = "0.4.4", features = ["ws", "headers"] } tower-http = { version = "0.2.0", features = ["fs", "trace"] } diff --git a/Dioxus.toml b/Dioxus.toml index abd2d1ee4..b4ba03f9d 100644 --- a/Dioxus.toml +++ b/Dioxus.toml @@ -13,7 +13,7 @@ platforms = ["web"] out_dir = "dist" # resource (static) file folder -static_dir = "static" +public_dir = "public" [web.watcher] diff --git a/src/cli/build/mod.rs b/src/cli/build/mod.rs index e24a99822..cbf82b339 100644 --- a/src/cli/build/mod.rs +++ b/src/cli/build/mod.rs @@ -18,6 +18,10 @@ impl Build { // change the relase state. crate_config.with_release(self.build.release); + if self.build.example.is_some() { + crate_config.as_example(self.build.example.unwrap()); + } + crate::builder::build(&crate_config)?; let temp = gen_page(&crate_config.dioxus_config, false); diff --git a/src/cli/cfg.rs b/src/cli/cfg.rs index ab68ce4f9..6a27c4890 100644 --- a/src/cli/cfg.rs +++ b/src/cli/cfg.rs @@ -16,6 +16,10 @@ pub struct ConfigOptsBuild { #[serde(default)] pub release: bool, + /// Build a example [default: ""] + #[structopt(long)] + pub example: Option, + /// Optional pattern for the app loader script [default: None] /// /// Patterns should include the sequences `{base}`, `{wasm}`, and `{js}` in order to @@ -62,6 +66,10 @@ pub struct ConfigOptsServe { #[structopt(parse(from_os_str))] pub target: Option, + /// Build a example [default: ""] + #[structopt(long)] + pub example: Option, + /// Build in release mode [default: false] #[structopt(long)] #[serde(default)] diff --git a/src/cli/init/mod.rs b/src/cli/init/mod.rs index 52dcf4c14..a19ee68fd 100644 --- a/src/cli/init/mod.rs +++ b/src/cli/init/mod.rs @@ -1,16 +1,18 @@ use std::{ - fs::{File, OpenOptions}, - io::Write, + fs::File, + io::{Read, Write}, path::PathBuf, process::{Command, Stdio}, }; +use regex::Regex; +use serde::Deserialize; use structopt::StructOpt; -use crate::error::{Error, Result}; +use crate::{error::Result, Error}; /// Build the Rust WASM app and all of its assets. -#[derive(Clone, Debug, StructOpt)] +#[derive(Clone, Debug, Default, Deserialize, StructOpt)] #[structopt(name = "init")] pub struct Init { /// Init project name @@ -18,69 +20,73 @@ pub struct Init { name: String, /// Template path - #[structopt(default_value = "default", long)] + #[structopt(default_value = "gh:dioxuslabs/dioxus-template", long)] template: String, + + /// Create a `lib` project, include a `hello-world` example. + #[structopt(long)] + #[serde(default)] + pub lib: bool, } impl Init { pub fn init(self) -> Result<()> { - if self.name.contains(".") { + if Self::name_vaild_check(self.name.clone()) { log::error!("❗Unsupported project name."); return Ok(()); } - log::info!("πŸ”§ Start to init a new project '{}'.", self.name); - let project_path = PathBuf::from(&self.name); - if project_path.join("Cargo.toml").is_file() { - log::warn!("Folder '{}' is initialized.", self.name); - return Ok(()); + if project_path.join("Dioxus.toml").is_file() || project_path.join("Cargo.toml").is_file() { + return Err(Error::Other(anyhow::anyhow!( + "🧨 Folder '{}' is initialized.", + &self.name + ))); } + log::info!("πŸ”§ Start to init a new project '{}'.", self.name); + let output = Command::new("cargo") - .arg("init") - .arg(&format!("./{}", self.name)) - .arg("--bin") + .arg("generate") + .arg("--help") .stdout(Stdio::piped()) .stderr(Stdio::piped()) .output()?; if !output.status.success() { - return Err(Error::CargoError("Cargo init failed".into())); + log::warn!("Tool is not installed: cargo-generate, try to install it."); + let install_output = Command::new("cargo") + .arg("install") + .arg("cargo-generate") + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .output()?; + if !install_output.status.success() { + return Err(Error::Other(anyhow::anyhow!( + "Try to install cargo-generate failed." + ))); + } } - // get the template code - let template_str = match self.template { - _ => include_str!("../../template/default.rs"), - }; + let generate_output = Command::new("cargo") + .arg("generate") + .arg(&self.template) + .arg("--name") + .arg(&self.name) + .stdout(Stdio::piped()) + .stderr(Stdio::inherit()) + .output()?; - let main_rs_file = project_path.join("src").join("main.rs"); - if !main_rs_file.is_file() { - return Err(Error::FailedToWrite); + if !generate_output.status.success() { + return Err(Error::Other(anyhow::anyhow!("Generate project failed."))); } - let mut file = File::create(main_rs_file)?; - file.write_all(&template_str.as_bytes())?; - - let mut file = File::create(project_path.join("Dioxus.toml"))?; - let dioxus_conf = String::from(include_str!("../../template/config.toml")) - .replace("{project-name}", &self.name); - file.write_all(dioxus_conf.as_bytes())?; - - // log::info!("🎯 Project initialization completed."); - - if !Command::new("cargo") - .args(["add", "dioxus", "--features web"]) - .output()? - .status - .success() - { - let mut file = OpenOptions::new() - .append(true) - .open(project_path.join("Cargo.toml"))?; - file.write_all("dioxus = { version = \"0.1.7\", features = [\"web\"] }".as_bytes())?; - } + let mut dioxus_file = File::open(project_path.join("Dioxus.toml"))?; + let mut meta_file = String::new(); + dioxus_file.read_to_string(&mut meta_file)?; + meta_file = meta_file.replace("{{project-name}}", &self.name); + File::create(project_path.join("Dioxus.toml"))?.write_all(meta_file.as_bytes())?; println!(""); log::info!("πŸ’‘ Project initialized:"); @@ -89,4 +95,9 @@ impl Init { Ok(()) } + + fn name_vaild_check(name: String) -> bool { + let r = Regex::new(r"^[a-zA-Z][a-zA-Z0-9\-_]$").unwrap(); + r.is_match(&name) + } } diff --git a/src/cli/serve/mod.rs b/src/cli/serve/mod.rs index 19a7f28ff..57716ac7e 100644 --- a/src/cli/serve/mod.rs +++ b/src/cli/serve/mod.rs @@ -13,9 +13,14 @@ 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); + if self.serve.example.is_some() { + crate_config.as_example(self.serve.example.unwrap()); + } + crate::builder::build(&crate_config).expect("build failed"); // generate dev-index page diff --git a/src/template/config.toml b/src/template/config.toml deleted file mode 100644 index 44f5baabd..000000000 --- a/src/template/config.toml +++ /dev/null @@ -1,42 +0,0 @@ -[application] - -# App (Project) Name -name = "{project-name}" - -# Dioxus App Support Platform -# desktop, web, mobile, ssr -platforms = ["web"] - -[web.app] - -# HTML title tag content -title = "dioxus | β›Ί" - -# Web `build` & `serve` dist path -out_dir = "dist" - -# resource (public) file folder -public_dir = "public" - -[web.watcher] - -# when watcher trigger, regenerate the `index.html` -reload_html = true - -# which files or dirs will be watcher monitoring -watch_path = ["src"] - -# include `assets` in web platform -[web.resource] - -# CSS style file -style = [] - -# Javascript code file -script = [] - -[web.resource.dev] - -# Javascript code file -# serve: [dev-server] only -script = [] \ No newline at end of file diff --git a/src/template/default.rs b/src/template/default.rs deleted file mode 100644 index 47b4a2c1d..000000000 --- a/src/template/default.rs +++ /dev/null @@ -1,16 +0,0 @@ -use dioxus::prelude::*; - -fn main() { - dioxus::web::launch(app); -} - -fn app(cx: Scope) -> Element { - cx.render(rsx! ( - div { - style: "text-align: center;", - h1 { "πŸŒ— Dioxus πŸš€" } - h3 { "Frontend that scales." } - p { "Dioxus is a portable, performant, and ergonomic framework for building cross-platform user interfaces in Rust." } - } - )) -} From 0ff4881d54f03115812ddeda48fa4687683be8c3 Mon Sep 17 00:00:00 2001 From: mrxiaozhuox Date: Mon, 24 Jan 2022 08:55:52 +0800 Subject: [PATCH 10/27] feat: support `desktop` build --- src/cli/build/mod.rs | 63 +++++++++++++++++++++++++++++++++++++++++++- src/cli/cfg.rs | 41 +++------------------------- 2 files changed, 65 insertions(+), 39 deletions(-) diff --git a/src/cli/build/mod.rs b/src/cli/build/mod.rs index cbf82b339..82060b436 100644 --- a/src/cli/build/mod.rs +++ b/src/cli/build/mod.rs @@ -1,6 +1,7 @@ -use std::{io::Write, path::PathBuf}; +use std::{io::Write, path::PathBuf, process::Command, fs::copy}; use crate::{cfg::ConfigOptsBuild, gen_page}; +use std::fs::remove_dir_all; use structopt::StructOpt; /// Build the Rust WASM app and all of its assets. @@ -22,6 +23,66 @@ impl Build { crate_config.as_example(self.build.example.unwrap()); } + if self.build.platform.is_some() { + if self.build.platform.unwrap().to_uppercase() == "DESKTOP" { + log::info!("πŸš… Running build command..."); + + let mut cmd = Command::new("cargo"); + cmd.current_dir(&crate_config.crate_dir) + .arg("build") + .stdout(std::process::Stdio::inherit()) + .stderr(std::process::Stdio::inherit()); + + if self.build.release { + cmd.arg("--release"); + } + + match &crate_config.executable { + crate::ExecutableType::Binary(name) => cmd.arg("--bin").arg(name), + crate::ExecutableType::Lib(name) => cmd.arg("--lib").arg(name), + crate::ExecutableType::Example(name) => cmd.arg("--example").arg(name), + }; + + let output = cmd.output()?; + + if output.status.success() { + if crate_config.out_dir.is_dir() { + + remove_dir_all(&crate_config.out_dir)?; + + let release_type = match crate_config.release { + true => "release", + false => "debug", + }; + + let mut res_path = match &crate_config.executable { + crate::ExecutableType::Binary(name) + | crate::ExecutableType::Lib(name) => crate_config + .target_dir + .join(format!("{}", release_type)) + .join(format!("{}", name)), + + crate::ExecutableType::Example(name) => crate_config + .target_dir + .join(format!("{}/examples", release_type)) + .join(format!("{}", name)), + }; + + let target_file; + if cfg!(windows) { + res_path.set_extension("exe"); + target_file = format!("{}.exe", &crate_config.dioxus_config.application.name); + } else { + target_file = crate_config.dioxus_config.application.name.clone(); + } + copy(res_path, &crate_config.out_dir.join(target_file))?; + } + } + + return Ok(()); + } + } + crate::builder::build(&crate_config)?; let temp = gen_page(&crate_config.dioxus_config, false); diff --git a/src/cli/cfg.rs b/src/cli/cfg.rs index 6a27c4890..7010d62e6 100644 --- a/src/cli/cfg.rs +++ b/src/cli/cfg.rs @@ -20,44 +20,9 @@ pub struct ConfigOptsBuild { #[structopt(long)] pub example: Option, - /// Optional pattern for the app loader script [default: None] - /// - /// Patterns should include the sequences `{base}`, `{wasm}`, and `{js}` in order to - /// properly load the application. Other sequences may be included corresponding - /// to key/value pairs provided in `pattern_params`. - /// - /// These values can only be provided via config file. - #[structopt(skip)] - #[serde(default)] - pub pattern_script: Option, - - /// Optional pattern for the app preload element [default: None] - /// - /// Patterns should include the sequences `{base}`, `{wasm}`, and `{js}` in order to - /// properly preload the application. Other sequences may be included corresponding - /// to key/value pairs provided in `pattern_params`. - /// - /// These values can only be provided via config file. - #[structopt(skip)] - #[serde(default)] - pub pattern_preload: Option, - - #[structopt(skip)] - #[serde(default)] - /// Optional replacement parameters corresponding to the patterns provided in - /// `pattern_script` and `pattern_preload`. - /// - /// When a pattern is being replaced with its corresponding value from this map, if the value is - /// prefixed with the symbol `@`, then the value is expected to be a file path, and the pattern - /// will be replaced with the contents of the target file. This allows insertion of some big JSON - /// state or even HTML files as a part of the `index.html` build. - /// - /// Trunk will automatically insert the `base`, `wasm` and `js` key/values into this map. In order - //// for the app to be loaded properly, the patterns `{base}`, `{wasm}` and `{js}` should be used - /// in `pattern_script` and `pattern_preload`. - /// - /// These values can only be provided via config file. - pub pattern_params: Option>, + /// Build platform: support Web & Desktop [default: "web"] + #[structopt(long)] + pub platform: Option, } #[derive(Clone, Debug, Default, Deserialize, StructOpt)] From bb69a040456db2e2a669ba4e3d80f2d2da44415f Mon Sep 17 00:00:00 2001 From: mrxiaozhuox Date: Mon, 24 Jan 2022 11:29:39 +0800 Subject: [PATCH 11/27] feat: commit code --- Dioxus.toml | 4 +-- src/cli/build/mod.rs | 76 ++++++++++++++++++++++++++++---------------- src/cli/cfg.rs | 1 - src/cli/clean/mod.rs | 3 +- src/cli/serve/mod.rs | 3 +- src/config.rs | 6 ++-- 6 files changed, 55 insertions(+), 38 deletions(-) diff --git a/Dioxus.toml b/Dioxus.toml index b4ba03f9d..86f52c349 100644 --- a/Dioxus.toml +++ b/Dioxus.toml @@ -7,11 +7,11 @@ name = "dioxus-cli" # desktop, web, mobile, ssr platforms = ["web"] -[web.app] - # Web `build` & `serve` dist path out_dir = "dist" +[web.app] + # resource (static) file folder public_dir = "public" diff --git a/src/cli/build/mod.rs b/src/cli/build/mod.rs index 82060b436..c1639e815 100644 --- a/src/cli/build/mod.rs +++ b/src/cli/build/mod.rs @@ -1,4 +1,9 @@ -use std::{io::Write, path::PathBuf, process::Command, fs::copy}; +use std::{ + fs::{copy, create_dir_all}, + io::Write, + path::PathBuf, + process::Command, +}; use crate::{cfg::ConfigOptsBuild, gen_page}; use std::fs::remove_dir_all; @@ -25,7 +30,7 @@ impl Build { if self.build.platform.is_some() { if self.build.platform.unwrap().to_uppercase() == "DESKTOP" { - log::info!("πŸš… Running build command..."); + log::info!("πŸš… Running build [Desktop] command..."); let mut cmd = Command::new("cargo"); cmd.current_dir(&crate_config.crate_dir) @@ -47,36 +52,52 @@ impl Build { if output.status.success() { if crate_config.out_dir.is_dir() { - remove_dir_all(&crate_config.out_dir)?; + } - let release_type = match crate_config.release { - true => "release", - false => "debug", - }; - - let mut res_path = match &crate_config.executable { - crate::ExecutableType::Binary(name) - | crate::ExecutableType::Lib(name) => crate_config + let release_type = match crate_config.release { + true => "release", + false => "debug", + }; + + let file_name: String; + let mut res_path = match &crate_config.executable { + crate::ExecutableType::Binary(name) | crate::ExecutableType::Lib(name) => { + file_name = name.clone(); + crate_config .target_dir .join(format!("{}", release_type)) - .join(format!("{}", name)), - - crate::ExecutableType::Example(name) => crate_config - .target_dir - .join(format!("{}/examples", release_type)) - .join(format!("{}", name)), - }; - - let target_file; - if cfg!(windows) { - res_path.set_extension("exe"); - target_file = format!("{}.exe", &crate_config.dioxus_config.application.name); - } else { - target_file = crate_config.dioxus_config.application.name.clone(); + .join(format!("{}", name)) } - copy(res_path, &crate_config.out_dir.join(target_file))?; + crate::ExecutableType::Example(name) => { + file_name = name.clone(); + crate_config + .target_dir + .join(format!("{}", release_type)) + .join("examples") + .join(format!("{}", name)) + } + }; + + let target_file; + if cfg!(windows) { + res_path.set_extension("exe"); + target_file = format!("{}.exe", &file_name); + } else { + target_file = file_name.clone(); } + create_dir_all(&crate_config.out_dir)?; + copy(res_path, &crate_config.out_dir.join(target_file))?; + + log::info!( + "πŸ› Build completed: [:{}]", + &crate_config + .dioxus_config + .application + .out_dir + .unwrap_or(PathBuf::from("dist")) + .display() + ); } return Ok(()); @@ -93,8 +114,7 @@ impl Build { .join( crate_config .dioxus_config - .web - .app + .application .out_dir .clone() .unwrap_or(PathBuf::from("dist")), diff --git a/src/cli/cfg.rs b/src/cli/cfg.rs index 7010d62e6..0734f362d 100644 --- a/src/cli/cfg.rs +++ b/src/cli/cfg.rs @@ -2,7 +2,6 @@ use std::path::PathBuf; use structopt::StructOpt; use serde::Deserialize; -use std::collections::HashMap; /// Config options for the build system. #[derive(Clone, Debug, Default, Deserialize, StructOpt)] diff --git a/src/cli/clean/mod.rs b/src/cli/clean/mod.rs index 4ab7653d2..7ba33b530 100644 --- a/src/cli/clean/mod.rs +++ b/src/cli/clean/mod.rs @@ -28,8 +28,7 @@ impl Clean { let out_dir = crate_config .dioxus_config - .web - .app + .application .out_dir .unwrap_or(PathBuf::from("dist")); if crate_config.crate_dir.join(&out_dir).is_dir() { diff --git a/src/cli/serve/mod.rs b/src/cli/serve/mod.rs index 57716ac7e..8b002e1fb 100644 --- a/src/cli/serve/mod.rs +++ b/src/cli/serve/mod.rs @@ -41,8 +41,7 @@ impl Serve { .join( crate_config .dioxus_config - .web - .app + .application .out_dir .clone() .unwrap_or(PathBuf::from("dist")), diff --git a/src/config.rs b/src/config.rs index 403ad0e81..ad1e25ea5 100644 --- a/src/config.rs +++ b/src/config.rs @@ -39,11 +39,11 @@ impl Default for DioxusConfig { application: ApplicationConfig { name: "dioxus".into(), platforms: vec![String::from("web")], + out_dir: Some(PathBuf::from("dist")), }, web: WebConfig { app: WebAppConfing { title: Some("dioxus | β›Ί".into()), - out_dir: Some(PathBuf::from("dist")), public_dir: Some(PathBuf::from("public")), }, watcher: WebWatcherConfing { @@ -67,6 +67,7 @@ impl Default for DioxusConfig { pub struct ApplicationConfig { pub name: String, pub platforms: Vec, + pub out_dir: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -79,7 +80,6 @@ pub struct WebConfig { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct WebAppConfing { pub title: Option, - pub out_dir: Option, pub public_dir: Option, } @@ -130,7 +130,7 @@ impl CrateConfig { let workspace_dir = crate::cargo::workspace_root()?; let target_dir = workspace_dir.join("target"); - let out_dir = match dioxus_config.web.app.out_dir { + let out_dir = match dioxus_config.application.out_dir { Some(ref v) => crate_dir.join(v), None => crate_dir.join("dist"), }; From d6957774026620c8649e92202e312432580c771c Mon Sep 17 00:00:00 2001 From: mrxiaozhuox Date: Mon, 24 Jan 2022 11:55:27 +0800 Subject: [PATCH 12/27] feat: support `desktop` --- src/builder.rs | 81 +++++++++++++++++++++++++++++++++++++++++++- src/cli/build/mod.rs | 80 ++----------------------------------------- src/cli/cfg.rs | 4 +++ src/cli/serve/mod.rs | 25 +++++++++++++- 4 files changed, 110 insertions(+), 80 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 0ecde96cd..415e56480 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -3,7 +3,11 @@ use crate::{ error::{Error, Result}, DioxusConfig, }; -use std::process::Command; +use std::{ + fs::{copy, create_dir_all, remove_dir_all}, + path::PathBuf, + process::Command, +}; use wasm_bindgen_cli_support::Bindgen; pub fn build(config: &CrateConfig) -> Result<()> { @@ -116,6 +120,81 @@ pub fn build(config: &CrateConfig) -> Result<()> { Ok(()) } +pub fn build_desktop(config: &CrateConfig) -> Result<()> { + log::info!("πŸš… Running build [Desktop] command..."); + + let mut cmd = Command::new("cargo"); + cmd.current_dir(&config.crate_dir) + .arg("build") + .stdout(std::process::Stdio::inherit()) + .stderr(std::process::Stdio::inherit()); + + if config.release { + cmd.arg("--release"); + } + + match &config.executable { + crate::ExecutableType::Binary(name) => cmd.arg("--bin").arg(name), + crate::ExecutableType::Lib(name) => cmd.arg("--lib").arg(name), + crate::ExecutableType::Example(name) => cmd.arg("--example").arg(name), + }; + + let output = cmd.output()?; + + if output.status.success() { + if config.out_dir.is_dir() { + remove_dir_all(&config.out_dir)?; + } + + let release_type = match config.release { + true => "release", + false => "debug", + }; + + let file_name: String; + let mut res_path = match &config.executable { + crate::ExecutableType::Binary(name) | crate::ExecutableType::Lib(name) => { + file_name = name.clone(); + config + .target_dir + .join(format!("{}", release_type)) + .join(format!("{}", name)) + } + crate::ExecutableType::Example(name) => { + file_name = name.clone(); + config + .target_dir + .join(format!("{}", release_type)) + .join("examples") + .join(format!("{}", name)) + } + }; + + let target_file; + if cfg!(windows) { + res_path.set_extension("exe"); + target_file = format!("{}.exe", &file_name); + } else { + target_file = file_name.clone(); + } + create_dir_all(&config.out_dir)?; + copy(res_path, &config.out_dir.join(target_file))?; + + log::info!( + "🚩 Build completed: [./{}]", + config + .dioxus_config + .application + .out_dir + .clone() + .unwrap_or(PathBuf::from("dist")) + .display() + ); + } + + Ok(()) +} + pub fn gen_page(config: &DioxusConfig, serve: bool) -> String { let mut html = String::from(include_str!("./assets/index.html")); diff --git a/src/cli/build/mod.rs b/src/cli/build/mod.rs index c1639e815..f018165ef 100644 --- a/src/cli/build/mod.rs +++ b/src/cli/build/mod.rs @@ -1,12 +1,6 @@ -use std::{ - fs::{copy, create_dir_all}, - io::Write, - path::PathBuf, - process::Command, -}; +use std::{io::Write, path::PathBuf}; use crate::{cfg::ConfigOptsBuild, gen_page}; -use std::fs::remove_dir_all; use structopt::StructOpt; /// Build the Rust WASM app and all of its assets. @@ -30,77 +24,7 @@ impl Build { if self.build.platform.is_some() { if self.build.platform.unwrap().to_uppercase() == "DESKTOP" { - log::info!("πŸš… Running build [Desktop] command..."); - - let mut cmd = Command::new("cargo"); - cmd.current_dir(&crate_config.crate_dir) - .arg("build") - .stdout(std::process::Stdio::inherit()) - .stderr(std::process::Stdio::inherit()); - - if self.build.release { - cmd.arg("--release"); - } - - match &crate_config.executable { - crate::ExecutableType::Binary(name) => cmd.arg("--bin").arg(name), - crate::ExecutableType::Lib(name) => cmd.arg("--lib").arg(name), - crate::ExecutableType::Example(name) => cmd.arg("--example").arg(name), - }; - - let output = cmd.output()?; - - if output.status.success() { - if crate_config.out_dir.is_dir() { - remove_dir_all(&crate_config.out_dir)?; - } - - let release_type = match crate_config.release { - true => "release", - false => "debug", - }; - - let file_name: String; - let mut res_path = match &crate_config.executable { - crate::ExecutableType::Binary(name) | crate::ExecutableType::Lib(name) => { - file_name = name.clone(); - crate_config - .target_dir - .join(format!("{}", release_type)) - .join(format!("{}", name)) - } - crate::ExecutableType::Example(name) => { - file_name = name.clone(); - crate_config - .target_dir - .join(format!("{}", release_type)) - .join("examples") - .join(format!("{}", name)) - } - }; - - let target_file; - if cfg!(windows) { - res_path.set_extension("exe"); - target_file = format!("{}.exe", &file_name); - } else { - target_file = file_name.clone(); - } - create_dir_all(&crate_config.out_dir)?; - copy(res_path, &crate_config.out_dir.join(target_file))?; - - log::info!( - "πŸ› Build completed: [:{}]", - &crate_config - .dioxus_config - .application - .out_dir - .unwrap_or(PathBuf::from("dist")) - .display() - ); - } - - return Ok(()); + crate::builder::build_desktop(&crate_config)?; } } diff --git a/src/cli/cfg.rs b/src/cli/cfg.rs index 0734f362d..454da71fe 100644 --- a/src/cli/cfg.rs +++ b/src/cli/cfg.rs @@ -38,6 +38,10 @@ pub struct ConfigOptsServe { #[structopt(long)] #[serde(default)] pub release: bool, + + /// Build platform: support Web & Desktop [default: "web"] + #[structopt(long)] + pub platform: Option, } /// Ensure the given value for `--public-url` is formatted correctly. diff --git a/src/cli/serve/mod.rs b/src/cli/serve/mod.rs index 8b002e1fb..ef413a63e 100644 --- a/src/cli/serve/mod.rs +++ b/src/cli/serve/mod.rs @@ -1,5 +1,5 @@ use crate::{cfg::ConfigOptsServe, gen_page, server, CrateConfig}; -use std::{io::Write, path::PathBuf}; +use std::{io::Write, path::PathBuf, process::Command}; use structopt::StructOpt; /// Run the WASM project on dev-server @@ -21,6 +21,29 @@ impl Serve { crate_config.as_example(self.serve.example.unwrap()); } + if self.serve.platform.is_some() { + if self.serve.platform.unwrap().to_uppercase() == "DESKTOP" { + crate::builder::build_desktop(&crate_config)?; + + match &crate_config.executable { + crate::ExecutableType::Binary(name) + | crate::ExecutableType::Lib(name) + | crate::ExecutableType::Example(name) => { + let mut file = crate_config.out_dir.join(name); + if cfg!(windows) { + file.set_extension("exe"); + } + Command::new(format!( + "{}", + crate_config.out_dir.join(file).to_str().unwrap() + )) + .output()?; + } + } + return Ok(()); + } + } + crate::builder::build(&crate_config).expect("build failed"); // generate dev-index page From 4cc7752adf17e41e153bcfa490ea14964e67b863 Mon Sep 17 00:00:00 2001 From: mrxiaozhuox Date: Mon, 24 Jan 2022 12:16:02 +0800 Subject: [PATCH 13/27] fix: change `init` to `create` --- src/cli/{init => create}/mod.rs | 10 +++++----- src/cli/mod.rs | 4 ++-- src/config.rs | 3 +-- src/main.rs | 6 +++--- 4 files changed, 11 insertions(+), 12 deletions(-) rename src/cli/{init => create}/mod.rs (94%) diff --git a/src/cli/init/mod.rs b/src/cli/create/mod.rs similarity index 94% rename from src/cli/init/mod.rs rename to src/cli/create/mod.rs index a19ee68fd..7cb2edbbe 100644 --- a/src/cli/init/mod.rs +++ b/src/cli/create/mod.rs @@ -13,8 +13,8 @@ use crate::{error::Result, Error}; /// Build the Rust WASM app and all of its assets. #[derive(Clone, Debug, Default, Deserialize, StructOpt)] -#[structopt(name = "init")] -pub struct Init { +#[structopt(name = "create")] +pub struct Create { /// Init project name #[structopt(default_value = ".")] name: String, @@ -29,8 +29,8 @@ pub struct Init { pub lib: bool, } -impl Init { - pub fn init(self) -> Result<()> { +impl Create { + pub fn create(self) -> Result<()> { if Self::name_vaild_check(self.name.clone()) { log::error!("❗Unsupported project name."); return Ok(()); @@ -45,7 +45,7 @@ impl Init { ))); } - log::info!("πŸ”§ Start to init a new project '{}'.", self.name); + log::info!("πŸ”§ Start to create a new project '{}'.", self.name); let output = Command::new("cargo") .arg("generate") diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 6224cea32..91d561463 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -3,7 +3,7 @@ use structopt::StructOpt; pub mod build; pub mod cfg; pub mod clean; -pub mod init; +pub mod create; pub mod serve; pub mod translate; @@ -35,7 +35,7 @@ pub enum Commands { /// Build, watch & serve the Rust WASM app and all of its assets. Serve(serve::Serve), /// Init a new project for Dioxus. - Init(init::Init), + Create(create::Create), // /// Clean output artifacts. Clean(clean::Clean), // /// Trunk config controls. diff --git a/src/config.rs b/src/config.rs index ad1e25ea5..fa52ddcbc 100644 --- a/src/config.rs +++ b/src/config.rs @@ -13,8 +13,7 @@ impl DioxusConfig { let crate_dir = crate::cargo::crate_root()?; if !crate_dir.join("Dioxus.toml").is_file() { - log::warn!("Config file: `Dioxus.toml` not found."); - return Ok(Self::default()); + return Err(crate::error::Error::Unique("Config file: `Dioxus.toml` not found.".into())); } let mut dioxus_conf_file = File::open(crate_dir.join("Dioxus.toml"))?; diff --git a/src/main.rs b/src/main.rs index 70f92cf95..20536048b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -34,9 +34,9 @@ async fn main() -> Result<()> { } } - Commands::Init(opts) => { - if let Err(e) = opts.init() { - log::error!("init error: {}", e); + Commands::Create(opts) => { + if let Err(e) = opts.create() { + log::error!("create error: {}", e); } } } From 66b832045ad436c4577990ef138407331d38d056 Mon Sep 17 00:00:00 2001 From: mrxiaozhuox Date: Mon, 24 Jan 2022 16:18:16 +0800 Subject: [PATCH 14/27] feat: add `config` command --- Dioxus.toml => src/assets/dioxus.toml | 18 +++++------ src/cli/config/mod.rs | 43 +++++++++++++++++++++++++++ src/cli/create/mod.rs | 5 ---- src/cli/mod.rs | 7 +++-- src/config.rs | 4 ++- src/main.rs | 6 ++++ 6 files changed, 63 insertions(+), 20 deletions(-) rename Dioxus.toml => src/assets/dioxus.toml (79%) create mode 100644 src/cli/config/mod.rs diff --git a/Dioxus.toml b/src/assets/dioxus.toml similarity index 79% rename from Dioxus.toml rename to src/assets/dioxus.toml index 86f52c349..717b070e2 100644 --- a/Dioxus.toml +++ b/src/assets/dioxus.toml @@ -1,7 +1,7 @@ [application] # App (Project) Name -name = "dioxus-cli" +name = "{{project-name}}" # Dioxus App Support Platform # desktop, web, mobile, ssr @@ -12,31 +12,27 @@ out_dir = "dist" [web.app] +# HTML title tag content +title = "dioxus | β›Ί" + # resource (static) file folder public_dir = "public" [web.watcher] - watch_path = ["src"] # include `assets` in web platform [web.resource] # CSS style file -style = [ - "./my-style.css" -] +style = [] # Javascript code file -script = [ - "./app.js" -] +script = [] [web.resource.dev] # Javascript code file # serve: [dev-server] only -script = [ - "./debug.js" -] \ No newline at end of file +script = [] \ No newline at end of file diff --git a/src/cli/config/mod.rs b/src/cli/config/mod.rs new file mode 100644 index 000000000..5e903dc3a --- /dev/null +++ b/src/cli/config/mod.rs @@ -0,0 +1,43 @@ +use std::{fs::File, io::Write}; + +use serde::Deserialize; +use structopt::StructOpt; + +/// Build the Rust WASM app and all of its assets. +#[derive(Clone, Debug, Deserialize, StructOpt)] +#[structopt(name = "config")] +pub enum Config { + /// Init `Dioxus.toml` for project/folder. + Init { + /// Init project name + name: String, + + /// Cover old config + #[structopt(long)] + #[serde(default)] + force: bool, + }, +} + +impl Config { + pub fn config(self) -> anyhow::Result<()> { + let crate_root = crate::cargo::crate_root()?; + match self { + Config::Init { name, force } => { + let conf_path = crate_root.join("Dioxus.toml"); + if conf_path.is_file() && force == false { + log::warn!( + "config file `Dioxus.toml` already exist, use `--force` to overwrite it." + ); + return Ok(()); + } + let mut file = File::create(conf_path)?; + let content = String::from(include_str!("../../assets/dioxus.toml")) + .replace("{{project-name}}", &name); + file.write_all(content.as_bytes())?; + log::info!("🚩 Init config file completed."); + } + } + Ok(()) + } +} diff --git a/src/cli/create/mod.rs b/src/cli/create/mod.rs index 7cb2edbbe..42364ddab 100644 --- a/src/cli/create/mod.rs +++ b/src/cli/create/mod.rs @@ -22,11 +22,6 @@ pub struct Create { /// Template path #[structopt(default_value = "gh:dioxuslabs/dioxus-template", long)] template: String, - - /// Create a `lib` project, include a `hello-world` example. - #[structopt(long)] - #[serde(default)] - pub lib: bool, } impl Create { diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 91d561463..4a72e00ac 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -3,6 +3,7 @@ use structopt::StructOpt; pub mod build; pub mod cfg; pub mod clean; +pub mod config; pub mod create; pub mod serve; pub mod translate; @@ -36,8 +37,8 @@ pub enum Commands { Serve(serve::Serve), /// Init a new project for Dioxus. Create(create::Create), - // /// Clean output artifacts. + /// Clean output artifacts. Clean(clean::Clean), - // /// Trunk config controls. - // Config(config::Config), + /// Dioxus config file controls. + Config(config::Config), } diff --git a/src/config.rs b/src/config.rs index fa52ddcbc..fb26b50bf 100644 --- a/src/config.rs +++ b/src/config.rs @@ -13,7 +13,9 @@ impl DioxusConfig { let crate_dir = crate::cargo::crate_root()?; if !crate_dir.join("Dioxus.toml").is_file() { - return Err(crate::error::Error::Unique("Config file: `Dioxus.toml` not found.".into())); + return Err(crate::error::Error::Unique( + "Config file: `Dioxus.toml` not found.".into(), + )); } let mut dioxus_conf_file = File::open(crate_dir.join("Dioxus.toml"))?; diff --git a/src/main.rs b/src/main.rs index 20536048b..1ef3a08f1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -39,6 +39,12 @@ async fn main() -> Result<()> { log::error!("create error: {}", e); } } + + Commands::Config(opts) => { + if let Err(e) = opts.config() { + log::error!("config error: {}", e); + } + } } Ok(()) From 84d1867654482640fa2cf31dd3eae10bf1bfa52d Mon Sep 17 00:00:00 2001 From: mrxiaozhuox Date: Tue, 25 Jan 2022 12:42:27 +0800 Subject: [PATCH 15/27] fix: use clippy fix --- src/builder.rs | 18 +++++++++--------- src/cli/build/mod.rs | 6 ++---- src/cli/config/mod.rs | 2 +- src/cli/create/mod.rs | 2 +- src/cli/serve/mod.rs | 29 ++++++++++++----------------- src/cli/translate/mod.rs | 4 ++-- src/cli/translate/to_component.rs | 2 +- src/config.rs | 15 +++++++-------- src/logging.rs | 2 +- 9 files changed, 36 insertions(+), 44 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 415e56480..c281f2b80 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -157,16 +157,16 @@ pub fn build_desktop(config: &CrateConfig) -> Result<()> { file_name = name.clone(); config .target_dir - .join(format!("{}", release_type)) - .join(format!("{}", name)) + .join(release_type.to_string()) + .join(name.to_string()) } crate::ExecutableType::Example(name) => { file_name = name.clone(); config .target_dir - .join(format!("{}", release_type)) + .join(release_type.to_string()) .join("examples") - .join(format!("{}", name)) + .join(name.to_string()) } }; @@ -175,7 +175,7 @@ pub fn build_desktop(config: &CrateConfig) -> Result<()> { res_path.set_extension("exe"); target_file = format!("{}.exe", &file_name); } else { - target_file = file_name.clone(); + target_file = file_name; } create_dir_all(&config.out_dir)?; copy(res_path, &config.out_dir.join(target_file))?; @@ -200,12 +200,12 @@ pub fn gen_page(config: &DioxusConfig, serve: bool) -> String { let resouces = config.web.resource.clone(); - let mut style_list = resouces.style.unwrap_or(vec![]); - let mut script_list = resouces.script.unwrap_or(vec![]); + let mut style_list = resouces.style.unwrap_or_default(); + let mut script_list = resouces.script.unwrap_or_default(); if serve { - let mut dev_style = resouces.dev.style.clone().unwrap_or(vec![]); - let mut dev_script = resouces.dev.script.clone().unwrap_or(vec![]); + let mut dev_style = resouces.dev.style.clone().unwrap_or_default(); + let mut dev_script = resouces.dev.script.unwrap_or_default(); style_list.append(&mut dev_style); script_list.append(&mut dev_script); } diff --git a/src/cli/build/mod.rs b/src/cli/build/mod.rs index f018165ef..318f6b7f5 100644 --- a/src/cli/build/mod.rs +++ b/src/cli/build/mod.rs @@ -22,10 +22,8 @@ impl Build { crate_config.as_example(self.build.example.unwrap()); } - if self.build.platform.is_some() { - if self.build.platform.unwrap().to_uppercase() == "DESKTOP" { - crate::builder::build_desktop(&crate_config)?; - } + if self.build.platform.is_some() && self.build.platform.unwrap().to_uppercase() == "DESKTOP" { + crate::builder::build_desktop(&crate_config)?; } crate::builder::build(&crate_config)?; diff --git a/src/cli/config/mod.rs b/src/cli/config/mod.rs index 5e903dc3a..d5291a7d6 100644 --- a/src/cli/config/mod.rs +++ b/src/cli/config/mod.rs @@ -25,7 +25,7 @@ impl Config { match self { Config::Init { name, force } => { let conf_path = crate_root.join("Dioxus.toml"); - if conf_path.is_file() && force == false { + if conf_path.is_file() && !force { log::warn!( "config file `Dioxus.toml` already exist, use `--force` to overwrite it." ); diff --git a/src/cli/create/mod.rs b/src/cli/create/mod.rs index 42364ddab..fa553c55a 100644 --- a/src/cli/create/mod.rs +++ b/src/cli/create/mod.rs @@ -83,7 +83,7 @@ impl Create { meta_file = meta_file.replace("{{project-name}}", &self.name); File::create(project_path.join("Dioxus.toml"))?.write_all(meta_file.as_bytes())?; - println!(""); + println!(); log::info!("πŸ’‘ Project initialized:"); log::info!("🎯> cd ./{}", self.name); log::info!("🎯> dioxus serve"); diff --git a/src/cli/serve/mod.rs b/src/cli/serve/mod.rs index ef413a63e..04795ab13 100644 --- a/src/cli/serve/mod.rs +++ b/src/cli/serve/mod.rs @@ -21,27 +21,22 @@ impl Serve { crate_config.as_example(self.serve.example.unwrap()); } - if self.serve.platform.is_some() { - if self.serve.platform.unwrap().to_uppercase() == "DESKTOP" { - crate::builder::build_desktop(&crate_config)?; + if self.serve.platform.is_some() && self.serve.platform.unwrap().to_uppercase() == "DESKTOP" { + crate::builder::build_desktop(&crate_config)?; - match &crate_config.executable { - crate::ExecutableType::Binary(name) - | crate::ExecutableType::Lib(name) - | crate::ExecutableType::Example(name) => { - let mut file = crate_config.out_dir.join(name); - if cfg!(windows) { - file.set_extension("exe"); - } - Command::new(format!( - "{}", - crate_config.out_dir.join(file).to_str().unwrap() - )) - .output()?; + match &crate_config.executable { + crate::ExecutableType::Binary(name) + | crate::ExecutableType::Lib(name) + | crate::ExecutableType::Example(name) => { + let mut file = crate_config.out_dir.join(name); + if cfg!(windows) { + file.set_extension("exe"); } + Command::new(crate_config.out_dir.join(file).to_str().unwrap().to_string()) + .output()?; } - return Ok(()); } + return Ok(()); } crate::builder::build(&crate_config).expect("build failed"); diff --git a/src/cli/translate/mod.rs b/src/cli/translate/mod.rs index 5799cad02..a62a1b4b3 100644 --- a/src/cli/translate/mod.rs +++ b/src/cli/translate/mod.rs @@ -48,7 +48,7 @@ impl Translate { exit(0); }) }); - if let None = temp { + if temp.is_none() { if let Some(s) = source { contents = s; } else { @@ -112,7 +112,7 @@ fn render_child(f: &mut impl Write, child: &Node, il: u32) -> std::fmt::Result { } write!(f, "\",")?; } - write!(f, "\n")?; + writeln!(f)?; // write the attributes if let Some(id) = &el.id { diff --git a/src/cli/translate/to_component.rs b/src/cli/translate/to_component.rs index 972da7a25..c83777ccb 100644 --- a/src/cli/translate/to_component.rs +++ b/src/cli/translate/to_component.rs @@ -120,7 +120,7 @@ fn render_element<'a>( } write!(f, "\",")?; } - write!(f, "\n")?; + writeln!(f)?; // write the attributes if let Some(id) = &el.id { diff --git a/src/config.rs b/src/config.rs index fb26b50bf..fa7e1bd1c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -23,12 +23,12 @@ impl DioxusConfig { dioxus_conf_file.read_to_string(&mut meta_str)?; match toml::from_str::(&meta_str) { - Ok(v) => return Ok(v), + Ok(v) => Ok(v), Err(e) => { log::error!("{}", e); - return Err(crate::error::Error::Unique( + Err(crate::error::Error::Unique( "Dioxus.toml parse failed".into(), - )); + )) } } } @@ -147,16 +147,15 @@ impl CrateConfig { // We just assume they're using a 'main.rs' // Anyway, we've already parsed the manifest, so it should be easy to change the type - let output_filename = (&manifest) + let output_filename = manifest .lib .as_ref() .and_then(|lib| lib.name.clone()) .or_else(|| { - (&manifest) + manifest .package - .as_ref() - .and_then(|pkg| Some(pkg.name.clone())) - .clone() + .as_ref().map(|pkg| pkg.name.clone()) + }) .expect("No lib found from cargo metadata"); let executable = ExecutableType::Binary(output_filename); diff --git a/src/logging.rs b/src/logging.rs index d30eeee47..48c2a2788 100644 --- a/src/logging.rs +++ b/src/logging.rs @@ -14,7 +14,7 @@ pub fn set_up_logging() { // configure colors for the name of the level. // since almost all of them are the same as the color for the whole line, we // just clone `colors_line` and overwrite our changes - let colors_level = colors_line.clone().info(Color::Green); + let colors_level = colors_line.info(Color::Green); // here we set up our fern Dispatch fern::Dispatch::new() .format(move |out, message, record| { From 1c2cf3330ee164a9492342ab1132f8d17b15931b Mon Sep 17 00:00:00 2001 From: mrxiaozhuox Date: Tue, 25 Jan 2022 13:52:33 +0800 Subject: [PATCH 16/27] fix: use clippy fix --- src/builder.rs | 4 ++-- src/cargo.rs | 6 +++--- src/cli/build/mod.rs | 2 +- src/cli/clean/mod.rs | 2 +- src/cli/serve/mod.rs | 2 +- src/cli/translate/mod.rs | 2 +- src/server/mod.rs | 28 +++++++++++++--------------- 7 files changed, 22 insertions(+), 24 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index c281f2b80..7d76f75cc 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -187,7 +187,7 @@ pub fn build_desktop(config: &CrateConfig) -> Result<()> { .application .out_dir .clone() - .unwrap_or(PathBuf::from("dist")) + .unwrap_or_else(|| PathBuf::from("dist")) .display() ); } @@ -238,7 +238,7 @@ pub fn gen_page(config: &DioxusConfig, serve: bool) -> String { html = html.replace("{app_name}", &config.application.name); - let title = config.web.app.title.clone().unwrap_or("dioxus | β›Ί".into()); + let title = config.web.app.title.clone().unwrap_or_else(|| "dioxus | β›Ί".into()); html.replace("{app_title}", &title) } diff --git a/src/cargo.rs b/src/cargo.rs index 3a911a81e..c268c5a3a 100644 --- a/src/cargo.rs +++ b/src/cargo.rs @@ -1,6 +1,6 @@ //! Utilities for working with cargo and rust files use crate::error::{Error, Result}; -use std::{env, fs, path::PathBuf, process::Command, str}; +use std::{env, fs, path::{PathBuf, Path}, process::Command, str}; /// How many parent folders are searched for a `Cargo.toml` const MAX_ANCESTORS: u32 = 10; @@ -44,7 +44,7 @@ pub fn workspace_root() -> Result { } let stdout = str::from_utf8(&output.stdout).unwrap(); - for line in stdout.lines() { + if let Some(line) = stdout.lines().next() { let meta: serde_json::Value = serde_json::from_str(line) .map_err(|_| Error::CargoError("InvalidOutput".to_string()))?; @@ -58,7 +58,7 @@ pub fn workspace_root() -> Result { } /// Checks if the directory contains `Cargo.toml` -fn contains_manifest(path: &PathBuf) -> bool { +fn contains_manifest(path: &Path) -> bool { fs::read_dir(path) .map(|entries| { entries diff --git a/src/cli/build/mod.rs b/src/cli/build/mod.rs index 318f6b7f5..27015420b 100644 --- a/src/cli/build/mod.rs +++ b/src/cli/build/mod.rs @@ -39,7 +39,7 @@ impl Build { .application .out_dir .clone() - .unwrap_or(PathBuf::from("dist")), + .unwrap_or_else(|| PathBuf::from("dist")), ) .join("index.html"), )?; diff --git a/src/cli/clean/mod.rs b/src/cli/clean/mod.rs index 7ba33b530..62593b1ee 100644 --- a/src/cli/clean/mod.rs +++ b/src/cli/clean/mod.rs @@ -30,7 +30,7 @@ impl Clean { .dioxus_config .application .out_dir - .unwrap_or(PathBuf::from("dist")); + .unwrap_or_else(|| PathBuf::from("dist")); if crate_config.crate_dir.join(&out_dir).is_dir() { remove_dir_all(crate_config.crate_dir.join(&out_dir))?; } diff --git a/src/cli/serve/mod.rs b/src/cli/serve/mod.rs index 04795ab13..a84d8136d 100644 --- a/src/cli/serve/mod.rs +++ b/src/cli/serve/mod.rs @@ -62,7 +62,7 @@ impl Serve { .application .out_dir .clone() - .unwrap_or(PathBuf::from("dist")), + .unwrap_or_else(|| PathBuf::from("dist")), ) .join("index.html"), )?; diff --git a/src/cli/translate/mod.rs b/src/cli/translate/mod.rs index a62a1b4b3..81b5092ed 100644 --- a/src/cli/translate/mod.rs +++ b/src/cli/translate/mod.rs @@ -48,7 +48,7 @@ impl Translate { exit(0); }) }); - if temp.is_none() { + if let Some(..) = temp { if let Some(s) = source { contents = s; } else { diff --git a/src/server/mod.rs b/src/server/mod.rs index 3151975d5..3092a01ec 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -49,7 +49,7 @@ pub async fn startup(config: CrateConfig) -> anyhow::Result<()> { .watcher .watch_path .clone() - .unwrap_or(vec![PathBuf::from("src")]); + .unwrap_or_else(|| vec![PathBuf::from("src")]); let crate_dir = watcher_conf.crate_dir.clone(); loop { if let Ok(v) = rx.recv() { @@ -67,21 +67,19 @@ pub async fn startup(config: CrateConfig) -> anyhow::Result<()> { } } - if reload { - if let Ok(_) = builder::build(&watcher_conf) { - // change the websocket reload state to true; - // the page will auto-reload. - if watcher_conf - .dioxus_config - .web - .watcher - .reload_html - .unwrap_or(false) - { - let _ = Serve::regen_dev_page(&watcher_conf); - } - watcher_ws_state.lock().unwrap().change(); + if reload && builder::build(&watcher_conf).is_ok() { + // change the websocket reload state to true; + // the page will auto-reload. + if watcher_conf + .dioxus_config + .web + .watcher + .reload_html + .unwrap_or(false) + { + let _ = Serve::regen_dev_page(&watcher_conf); } + watcher_ws_state.lock().unwrap().change(); } } _ => {} From 25e3ee848cadcd14ff291b39436266857037a11a Mon Sep 17 00:00:00 2001 From: mrxiaozhuox Date: Tue, 25 Jan 2022 17:09:06 +0800 Subject: [PATCH 17/27] fix: high CPU usage --- src/server/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/server/mod.rs b/src/server/mod.rs index 3092a01ec..364b7c572 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -118,6 +118,7 @@ async fn ws_handler( ) -> impl IntoResponse { ws.on_upgrade(|mut socket| async move { loop { + tokio::time::sleep(tokio::time::Duration::from_millis(1200)).await; if state.lock().unwrap().update { socket .send(Message::Text(String::from("reload"))) From b01b3f519f5a2a279cb60c479e7000d9819fed33 Mon Sep 17 00:00:00 2001 From: mrxiaozhuox Date: Tue, 25 Jan 2022 17:51:36 +0800 Subject: [PATCH 18/27] fix: commit code --- src/builder.rs | 17 ++++++++++------ src/cargo.rs | 7 ++++++- src/cli/build/mod.rs | 14 +++++++++---- src/cli/cfg.rs | 8 ++++---- src/cli/serve/mod.rs | 47 ++++++++++++++++++++++++++++---------------- src/config.rs | 12 +++-------- src/main.rs | 3 --- src/server/mod.rs | 6 +++--- 8 files changed, 67 insertions(+), 47 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 7d76f75cc..583c9bc8f 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -170,13 +170,13 @@ pub fn build_desktop(config: &CrateConfig) -> Result<()> { } }; - let target_file; - if cfg!(windows) { + let target_file = if cfg!(windows) { res_path.set_extension("exe"); - target_file = format!("{}.exe", &file_name); + format!("{}.exe", &file_name) } else { - target_file = file_name; - } + file_name + }; + create_dir_all(&config.out_dir)?; copy(res_path, &config.out_dir.join(target_file))?; @@ -238,7 +238,12 @@ pub fn gen_page(config: &DioxusConfig, serve: bool) -> String { html = html.replace("{app_name}", &config.application.name); - let title = config.web.app.title.clone().unwrap_or_else(|| "dioxus | β›Ί".into()); + let title = config + .web + .app + .title + .clone() + .unwrap_or_else(|| "dioxus | β›Ί".into()); html.replace("{app_title}", &title) } diff --git a/src/cargo.rs b/src/cargo.rs index c268c5a3a..67a3e48f1 100644 --- a/src/cargo.rs +++ b/src/cargo.rs @@ -1,6 +1,11 @@ //! Utilities for working with cargo and rust files use crate::error::{Error, Result}; -use std::{env, fs, path::{PathBuf, Path}, process::Command, str}; +use std::{ + env, fs, + path::{Path, PathBuf}, + process::Command, + str, +}; /// How many parent folders are searched for a `Cargo.toml` const MAX_ANCESTORS: u32 = 10; diff --git a/src/cli/build/mod.rs b/src/cli/build/mod.rs index 27015420b..684cec58c 100644 --- a/src/cli/build/mod.rs +++ b/src/cli/build/mod.rs @@ -22,12 +22,18 @@ impl Build { crate_config.as_example(self.build.example.unwrap()); } - if self.build.platform.is_some() && self.build.platform.unwrap().to_uppercase() == "DESKTOP" { - crate::builder::build_desktop(&crate_config)?; + match self.build.platform.as_str() { + "web" => { + crate::builder::build(&crate_config)?; + } + "desktop" => { + crate::builder::build_desktop(&crate_config)?; + } + _ => { + return Err(anyhow::anyhow!("Unsoppurt platform target.")); + } } - crate::builder::build(&crate_config)?; - let temp = gen_page(&crate_config.dioxus_config, false); let mut file = std::fs::File::create( diff --git a/src/cli/cfg.rs b/src/cli/cfg.rs index 454da71fe..ece00e07e 100644 --- a/src/cli/cfg.rs +++ b/src/cli/cfg.rs @@ -20,8 +20,8 @@ pub struct ConfigOptsBuild { pub example: Option, /// Build platform: support Web & Desktop [default: "web"] - #[structopt(long)] - pub platform: Option, + #[structopt(long, default_value = "web")] + pub platform: String, } #[derive(Clone, Debug, Default, Deserialize, StructOpt)] @@ -40,8 +40,8 @@ pub struct ConfigOptsServe { pub release: bool, /// Build platform: support Web & Desktop [default: "web"] - #[structopt(long)] - pub platform: Option, + #[structopt(long, default_value = "web")] + pub platform: String, } /// Ensure the given value for `--public-url` is formatted correctly. diff --git a/src/cli/serve/mod.rs b/src/cli/serve/mod.rs index a84d8136d..fd8f3c4f5 100644 --- a/src/cli/serve/mod.rs +++ b/src/cli/serve/mod.rs @@ -21,25 +21,38 @@ impl Serve { crate_config.as_example(self.serve.example.unwrap()); } - if self.serve.platform.is_some() && self.serve.platform.unwrap().to_uppercase() == "DESKTOP" { - crate::builder::build_desktop(&crate_config)?; - - match &crate_config.executable { - crate::ExecutableType::Binary(name) - | crate::ExecutableType::Lib(name) - | crate::ExecutableType::Example(name) => { - let mut file = crate_config.out_dir.join(name); - if cfg!(windows) { - file.set_extension("exe"); - } - Command::new(crate_config.out_dir.join(file).to_str().unwrap().to_string()) - .output()?; - } + match self.serve.platform.as_str() { + "web" => { + crate::builder::build(&crate_config)?; } - return Ok(()); - } + "desktop" => { + crate::builder::build_desktop(&crate_config)?; - crate::builder::build(&crate_config).expect("build failed"); + match &crate_config.executable { + crate::ExecutableType::Binary(name) + | crate::ExecutableType::Lib(name) + | crate::ExecutableType::Example(name) => { + let mut file = crate_config.out_dir.join(name); + if cfg!(windows) { + file.set_extension("exe"); + } + Command::new( + crate_config + .out_dir + .join(file) + .to_str() + .unwrap() + .to_string(), + ) + .output()?; + } + } + return Ok(()); + } + _ => { + return Err(anyhow::anyhow!("Unsoppurt platform target.")); + } + } // generate dev-index page Serve::regen_dev_page(&crate_config)?; diff --git a/src/config.rs b/src/config.rs index fa7e1bd1c..088955910 100644 --- a/src/config.rs +++ b/src/config.rs @@ -13,9 +13,8 @@ impl DioxusConfig { let crate_dir = crate::cargo::crate_root()?; if !crate_dir.join("Dioxus.toml").is_file() { - return Err(crate::error::Error::Unique( - "Config file: `Dioxus.toml` not found.".into(), - )); + log::warn!("Config file: `Dioxus.toml` not found; using default config."); + return Ok(DioxusConfig::default()); } let mut dioxus_conf_file = File::open(crate_dir.join("Dioxus.toml"))?; @@ -151,12 +150,7 @@ impl CrateConfig { .lib .as_ref() .and_then(|lib| lib.name.clone()) - .or_else(|| { - manifest - .package - .as_ref().map(|pkg| pkg.name.clone()) - - }) + .or_else(|| manifest.package.as_ref().map(|pkg| pkg.name.clone())) .expect("No lib found from cargo metadata"); let executable = ExecutableType::Binary(output_filename); diff --git a/src/main.rs b/src/main.rs index 1ef3a08f1..6b8c177db 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,9 +25,6 @@ async fn main() -> Result<()> { } } - // Commands::Config(_) => { - // // - // } Commands::Serve(opts) => { if let Err(e) = opts.serve().await { log::error!("serve error: {}", e); diff --git a/src/server/mod.rs b/src/server/mod.rs index 364b7c572..c7c9074ed 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -58,16 +58,16 @@ pub async fn startup(config: CrateConfig) -> anyhow::Result<()> { | DebouncedEvent::Write(e) | DebouncedEvent::Remove(e) | DebouncedEvent::Rename(e, _) => { - let mut reload = false; + let mut should_reload = false; for path in &allow_watch_path { let temp = crate_dir.clone().join(path); if e.starts_with(temp) { - reload = true; + should_reload = true; break; } } - if reload && builder::build(&watcher_conf).is_ok() { + if should_reload && builder::build(&watcher_conf).is_ok() { // change the websocket reload state to true; // the page will auto-reload. if watcher_conf From 2c0b0460c9da1f8b30e3c91cce070e669b23d908 Mon Sep 17 00:00:00 2001 From: mrxiaozhuox Date: Tue, 25 Jan 2022 18:01:33 +0800 Subject: [PATCH 19/27] fix: error wrap --- src/config.rs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/config.rs b/src/config.rs index 088955910..1151445ef 100644 --- a/src/config.rs +++ b/src/config.rs @@ -21,15 +21,8 @@ impl DioxusConfig { let mut meta_str = String::new(); dioxus_conf_file.read_to_string(&mut meta_str)?; - match toml::from_str::(&meta_str) { - Ok(v) => Ok(v), - Err(e) => { - log::error!("{}", e); - Err(crate::error::Error::Unique( - "Dioxus.toml parse failed".into(), - )) - } - } + toml::from_str::(&meta_str) + .map_err(|_| crate::Error::Unique("Dioxus.toml parse failed".into())) } } From 20977a49f0e0329dcdc77ccd01f4d5bb83fcf989 Mon Sep 17 00:00:00 2001 From: mrxiaozhuox Date: Wed, 26 Jan 2022 00:21:40 +0800 Subject: [PATCH 20/27] fix: high CPU usage --- src/assets/autoreload.js | 17 ++++------- src/server/mod.rs | 63 ++++++++++++++++++++-------------------- 2 files changed, 37 insertions(+), 43 deletions(-) diff --git a/src/assets/autoreload.js b/src/assets/autoreload.js index ff4444085..b2baa1907 100644 --- a/src/assets/autoreload.js +++ b/src/assets/autoreload.js @@ -5,21 +5,16 @@ var poll_interval = 2000; - var reload_upon_connect = () => { - window.setTimeout(() => { - var ws = new WebSocket(url); - ws.onopen = () => window.location.reload(); - ws.onclose = reload_upon_connect; - }, poll_interval); - }; - var ws = new WebSocket(url); - ws.onmessage = (ev) => { + ws.addEventListener("message", (ev) => { if (ev.data == "reload") { window.location.reload(); } - }; + }); + + ws.addEventListener("open", () => { + ws.send("init"); + }); - ws.onclose = reload_upon_connect; })(); \ No newline at end of file diff --git a/src/server/mod.rs b/src/server/mod.rs index c7c9074ed..24504498a 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -20,8 +20,8 @@ struct WsRelodState { } impl WsRelodState { - fn change(&mut self) { - self.update = !self.update; + fn change(&mut self, state: bool) { + self.update = state; } } @@ -34,40 +34,35 @@ pub async fn startup(config: CrateConfig) -> anyhow::Result<()> { // file watcher: check file change let mut watcher = watcher(tx, Duration::from_secs(1)).unwrap(); - watcher - .watch(config.crate_dir.clone(), notify::RecursiveMode::Recursive) - .unwrap(); + let allow_watch_path = config + .dioxus_config + .web + .watcher + .watch_path + .clone() + .unwrap_or_else(|| vec![PathBuf::from("src")]); + for sub_path in allow_watch_path { + watcher + .watch( + config.crate_dir.join(sub_path), + 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 { - let allow_watch_path = watcher_conf - .dioxus_config - .web - .watcher - .watch_path - .clone() - .unwrap_or_else(|| vec![PathBuf::from("src")]); - let crate_dir = watcher_conf.crate_dir.clone(); loop { if let Ok(v) = rx.recv() { match v { - DebouncedEvent::Create(e) - | DebouncedEvent::Write(e) - | DebouncedEvent::Remove(e) - | DebouncedEvent::Rename(e, _) => { - let mut should_reload = false; - for path in &allow_watch_path { - let temp = crate_dir.clone().join(path); - if e.starts_with(temp) { - should_reload = true; - break; - } - } - - if should_reload && builder::build(&watcher_conf).is_ok() { + DebouncedEvent::Create(_) + | DebouncedEvent::Write(_) + | DebouncedEvent::Remove(_) + | DebouncedEvent::Rename(_, _) => { + if builder::build(&watcher_conf).is_ok() { // change the websocket reload state to true; // the page will auto-reload. if watcher_conf @@ -79,7 +74,7 @@ pub async fn startup(config: CrateConfig) -> anyhow::Result<()> { { let _ = Serve::regen_dev_page(&watcher_conf); } - watcher_ws_state.lock().unwrap().change(); + watcher_ws_state.lock().unwrap().change(true); } } _ => {} @@ -118,13 +113,17 @@ async fn ws_handler( ) -> impl IntoResponse { ws.on_upgrade(|mut socket| async move { loop { - tokio::time::sleep(tokio::time::Duration::from_millis(1200)).await; + tokio::time::sleep(tokio::time::Duration::from_millis(1000)).await; if state.lock().unwrap().update { - socket + // ignore the error + if socket .send(Message::Text(String::from("reload"))) .await - .unwrap(); - state.lock().unwrap().change(); + .is_err() + { + return; + } + state.lock().unwrap().change(false); } } }) From 78980b2dbf62278bf939ece72c67f9ab35a39807 Mon Sep 17 00:00:00 2001 From: mrxiaozhuox Date: Wed, 26 Jan 2022 10:18:31 +0800 Subject: [PATCH 21/27] fix: use channel --- src/assets/autoreload.js | 4 ++++ src/server/mod.rs | 52 +++++++++++++++++++++------------------- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/src/assets/autoreload.js b/src/assets/autoreload.js index b2baa1907..89054664e 100644 --- a/src/assets/autoreload.js +++ b/src/assets/autoreload.js @@ -17,4 +17,8 @@ ws.send("init"); }); + ws.addEventListener("close", () => { + alert("Dev-Server Socket Closed."); + }); + })(); \ No newline at end of file diff --git a/src/server/mod.rs b/src/server/mod.rs index 24504498a..366ea41ab 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -9,31 +9,26 @@ use notify::{watcher, DebouncedEvent, Watcher}; use std::time::Duration; use std::{ path::PathBuf, - sync::{mpsc::channel, Arc, Mutex}, + sync::{mpsc::channel, Arc}, }; use tower_http::services::ServeDir; use crate::{builder, serve::Serve, CrateConfig}; +use tokio::sync::broadcast; struct WsRelodState { - update: bool, -} - -impl WsRelodState { - fn change(&mut self, state: bool) { - self.update = state; - } + update: broadcast::Sender, } pub async fn startup(config: CrateConfig) -> anyhow::Result<()> { log::info!("πŸš€ Starting development server..."); - let (tx, rx) = channel(); + let (watcher_tx, watcher_rx) = channel(); let dist_path = config.out_dir.clone(); // file watcher: check file change - let mut watcher = watcher(tx, Duration::from_secs(1)).unwrap(); + let mut watcher = watcher(watcher_tx, Duration::from_secs(1)).unwrap(); let allow_watch_path = config .dioxus_config .web @@ -50,13 +45,14 @@ pub async fn startup(config: CrateConfig) -> anyhow::Result<()> { .unwrap(); } - let ws_reload_state = Arc::new(Mutex::new(WsRelodState { update: false })); + let (reload_tx, _) = broadcast::channel(100); + let ws_reload_state = Arc::new(WsRelodState { update: reload_tx }); let watcher_conf = config.clone(); - let watcher_ws_state = ws_reload_state.clone(); + let watcher_ws_state = ws_reload_state.update.clone(); tokio::spawn(async move { loop { - if let Ok(v) = rx.recv() { + if let Ok(v) = watcher_rx.recv() { match v { DebouncedEvent::Create(_) | DebouncedEvent::Write(_) @@ -74,7 +70,8 @@ pub async fn startup(config: CrateConfig) -> anyhow::Result<()> { { let _ = Serve::regen_dev_page(&watcher_conf); } - watcher_ws_state.lock().unwrap().change(true); + println!("send reload!"); + watcher_ws_state.send("reload".into()).unwrap(); } } _ => {} @@ -109,21 +106,26 @@ pub async fn startup(config: CrateConfig) -> anyhow::Result<()> { async fn ws_handler( ws: WebSocketUpgrade, _: Option>, - Extension(state): Extension>>, + Extension(state): Extension>, ) -> impl IntoResponse { ws.on_upgrade(|mut socket| async move { + let mut rx = state.update.subscribe(); loop { - tokio::time::sleep(tokio::time::Duration::from_millis(1000)).await; - if state.lock().unwrap().update { - // ignore the error - if socket - .send(Message::Text(String::from("reload"))) - .await - .is_err() - { - return; + println!("wait"); + if let Ok(v) = rx.recv().await { + println!("trigger recv: {}", v); + if v == "reload" { + // ignore the error + if socket + .send(Message::Text(String::from("reload"))) + .await + .is_err() + { + return; + } } - state.lock().unwrap().change(false); + } else { + println!("SS"); } } }) From 7aeddba4270c404a1898382acd0b89b4b33096db Mon Sep 17 00:00:00 2001 From: mrxiaozhuox Date: Thu, 27 Jan 2022 01:03:17 +0800 Subject: [PATCH 22/27] fix: auto-connect `ws` server --- src/assets/autoreload.js | 40 +++++++------- src/server/mod.rs | 111 ++++++++++++++++++++------------------- 2 files changed, 77 insertions(+), 74 deletions(-) diff --git a/src/assets/autoreload.js b/src/assets/autoreload.js index 89054664e..a6bbac5da 100644 --- a/src/assets/autoreload.js +++ b/src/assets/autoreload.js @@ -1,24 +1,22 @@ (function () { - var protocol = window.location.protocol === "https:" ? "wss:" : "ws:"; - - var url = protocol + "//" + window.location.host + "/_dioxus/ws"; - - var poll_interval = 2000; + var protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; + var url = protocol + '//' + window.location.host + '/_dioxus/ws'; + var poll_interval = 8080; + var reload_upon_connect = () => { + window.setTimeout( + () => { + var ws = new WebSocket(url); + ws.onopen = () => window.location.reload(); + ws.onclose = reload_upon_connect; + }, + poll_interval); + }; var ws = new WebSocket(url); - - ws.addEventListener("message", (ev) => { - if (ev.data == "reload") { - window.location.reload(); - } - }); - - ws.addEventListener("open", () => { - ws.send("init"); - }); - - ws.addEventListener("close", () => { - alert("Dev-Server Socket Closed."); - }); - -})(); \ No newline at end of file + ws.onmessage = (ev) => { + if (ev.data == "reload") { + window.location.reload(); + } + }; + ws.onclose = reload_upon_connect; +})() \ No newline at end of file diff --git a/src/server/mod.rs b/src/server/mod.rs index 366ea41ab..7dd4151eb 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -36,6 +36,7 @@ pub async fn startup(config: CrateConfig) -> anyhow::Result<()> { .watch_path .clone() .unwrap_or_else(|| vec![PathBuf::from("src")]); + for sub_path in allow_watch_path { watcher .watch( @@ -46,60 +47,63 @@ pub async fn startup(config: CrateConfig) -> anyhow::Result<()> { } let (reload_tx, _) = broadcast::channel(100); - let ws_reload_state = Arc::new(WsRelodState { update: reload_tx }); + + let ws_reload_state = Arc::new(WsRelodState { + update: reload_tx.clone(), + }); let watcher_conf = config.clone(); - let watcher_ws_state = ws_reload_state.update.clone(); + tokio::spawn(async move { - loop { - if let Ok(v) = watcher_rx.recv() { - match v { - DebouncedEvent::Create(_) - | DebouncedEvent::Write(_) - | DebouncedEvent::Remove(_) - | DebouncedEvent::Rename(_, _) => { - if builder::build(&watcher_conf).is_ok() { - // change the websocket reload state to true; - // the page will auto-reload. - if watcher_conf - .dioxus_config - .web - .watcher - .reload_html - .unwrap_or(false) - { - let _ = Serve::regen_dev_page(&watcher_conf); - } - println!("send reload!"); - watcher_ws_state.send("reload".into()).unwrap(); + while let Ok(v) = watcher_rx.recv() { + match v { + DebouncedEvent::Create(_) + | DebouncedEvent::Write(_) + | DebouncedEvent::Remove(_) + | DebouncedEvent::Rename(_, _) => { + if builder::build(&watcher_conf).is_ok() { + // change the websocket reload state to true; + // the page will auto-reload. + if watcher_conf + .dioxus_config + .web + .watcher + .reload_html + .unwrap_or(false) + { + let _ = Serve::regen_dev_page(&watcher_conf); } + reload_tx.send("reload".into()).unwrap(); } - _ => {} } + _ => {} } } }); - let app = Router::new() - .route("/_dioxus/ws", get(ws_handler)) - .fallback( - get_service(ServeDir::new(config.crate_dir.join(&dist_path))).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()) + .serve( + Router::new() + .route("/_dioxus/ws", get(ws_handler)) + .fallback( + get_service(ServeDir::new(config.crate_dir.join(&dist_path))).handle_error( + |error: std::io::Error| async move { + ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Unhandled internal error: {}", error), + ) + }, + ), + ) + .layer(AddExtensionLayer::new(ws_reload_state)) + .into_make_service(), + ) .await?; + Ok(()) } @@ -109,23 +113,24 @@ async fn ws_handler( Extension(state): Extension>, ) -> impl IntoResponse { ws.on_upgrade(|mut socket| async move { + + log::info!("New websocket conenct."); + let mut rx = state.update.subscribe(); + loop { - println!("wait"); - if let Ok(v) = rx.recv().await { - println!("trigger recv: {}", v); - if v == "reload" { - // ignore the error - if socket - .send(Message::Text(String::from("reload"))) - .await - .is_err() - { - return; - } + let v = rx.recv().await.unwrap(); + println!("trigger recv: {}", v); + if v == "reload" { + // ignore the error + if socket + .send(Message::Text(String::from("reload"))) + .await + .is_err() + { + println!("send failed"); + return; } - } else { - println!("SS"); } } }) From b6172998609812a02cc9e68786e0cd283bc0e685 Mon Sep 17 00:00:00 2001 From: mrxiaozhuox Date: Thu, 27 Jan 2022 11:13:41 +0800 Subject: [PATCH 23/27] fix: auto-reload --- Cargo.toml | 3 +- src/assets/autoreload.js | 1 + src/server/mod.rs | 131 +++++++++++++++++++++------------------ 3 files changed, 73 insertions(+), 62 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 68dd3ee56..5f6443245 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,8 @@ toml = "0.5.8" fs_extra = "1.2.0" cargo_toml = "0.10.0" futures = "0.3.12" -notify = "4.0.17" +# notify = "4.0.17" +notify = { version = "5.0.0-pre.13", features = ["serde"] } html_parser = "0.6.2" binary-install = "0.0.2" convert_case = "0.5.0" diff --git a/src/assets/autoreload.js b/src/assets/autoreload.js index a6bbac5da..da62e5eae 100644 --- a/src/assets/autoreload.js +++ b/src/assets/autoreload.js @@ -15,6 +15,7 @@ var ws = new WebSocket(url); ws.onmessage = (ev) => { if (ev.data == "reload") { + // alert("reload!!!"); window.location.reload(); } }; diff --git a/src/server/mod.rs b/src/server/mod.rs index 7dd4151eb..45d7191d0 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -5,12 +5,9 @@ use axum::{ routing::{get, get_service}, AddExtensionLayer, Router, }; -use notify::{watcher, DebouncedEvent, Watcher}; -use std::time::Duration; -use std::{ - path::PathBuf, - sync::{mpsc::channel, Arc}, -}; +use notify::{RecommendedWatcher, Watcher}; + +use std::{path::PathBuf, sync::Arc}; use tower_http::services::ServeDir; use crate::{builder, serve::Serve, CrateConfig}; @@ -23,12 +20,33 @@ struct WsRelodState { pub async fn startup(config: CrateConfig) -> anyhow::Result<()> { log::info!("πŸš€ Starting development server..."); - let (watcher_tx, watcher_rx) = channel(); - let dist_path = config.out_dir.clone(); + let (reload_tx, _) = broadcast::channel(100); + + let ws_reload_state = Arc::new(WsRelodState { + update: reload_tx.clone(), + }); + // file watcher: check file change - let mut watcher = watcher(watcher_tx, Duration::from_secs(1)).unwrap(); + let watcher_conf = config.clone(); + let mut watcher = RecommendedWatcher::new(move |_| { + if builder::build(&watcher_conf).is_ok() { + // change the websocket reload state to true; + // the page will auto-reload. + if watcher_conf + .dioxus_config + .web + .watcher + .reload_html + .unwrap_or(false) + { + let _ = Serve::regen_dev_page(&watcher_conf); + } + reload_tx.send("reload".into()).unwrap(); + } + }) + .unwrap(); let allow_watch_path = config .dioxus_config .web @@ -40,46 +58,38 @@ pub async fn startup(config: CrateConfig) -> anyhow::Result<()> { for sub_path in allow_watch_path { watcher .watch( - config.crate_dir.join(sub_path), + &config.crate_dir.join(sub_path), notify::RecursiveMode::Recursive, ) .unwrap(); } - let (reload_tx, _) = broadcast::channel(100); - - let ws_reload_state = Arc::new(WsRelodState { - update: reload_tx.clone(), - }); - - let watcher_conf = config.clone(); - - tokio::spawn(async move { - while let Ok(v) = watcher_rx.recv() { - match v { - DebouncedEvent::Create(_) - | DebouncedEvent::Write(_) - | DebouncedEvent::Remove(_) - | DebouncedEvent::Rename(_, _) => { - if builder::build(&watcher_conf).is_ok() { - // change the websocket reload state to true; - // the page will auto-reload. - if watcher_conf - .dioxus_config - .web - .watcher - .reload_html - .unwrap_or(false) - { - let _ = Serve::regen_dev_page(&watcher_conf); - } - reload_tx.send("reload".into()).unwrap(); - } - } - _ => {} - } - } - }); + // tokio::spawn(async move { + // while let Ok(v) = watcher_rx.recv() { + // match v { + // DebouncedEvent::Create(_) + // | DebouncedEvent::Write(_) + // | DebouncedEvent::Remove(_) + // | DebouncedEvent::Rename(_, _) => { + // if builder::build(&watcher_conf).is_ok() { + // // change the websocket reload state to true; + // // the page will auto-reload. + // if watcher_conf + // .dioxus_config + // .web + // .watcher + // .reload_html + // .unwrap_or(false) + // { + // let _ = Serve::regen_dev_page(&watcher_conf); + // } + // reload_tx.send("reload".into()).unwrap(); + // } + // } + // _ => {} + // } + // } + // }); // start serve dev-server at 0.0.0.0:8080 let port = "8080"; @@ -113,25 +123,24 @@ async fn ws_handler( Extension(state): Extension>, ) -> impl IntoResponse { ws.on_upgrade(|mut socket| async move { - - log::info!("New websocket conenct."); - let mut rx = state.update.subscribe(); - - loop { - let v = rx.recv().await.unwrap(); - println!("trigger recv: {}", v); - if v == "reload" { - // ignore the error - if socket - .send(Message::Text(String::from("reload"))) - .await - .is_err() - { - println!("send failed"); - return; + let reload_watcher = tokio::spawn(async move { + loop { + let v = rx.recv().await.unwrap(); + println!("trigger recv: {}", v); + if v == "reload" { + // ignore the error + if socket + .send(Message::Text(String::from("reload"))) + .await + .is_err() + { + break; + } } } - } + }); + + reload_watcher.await.unwrap(); }) } From b8b0303904b3f01da5942355b5c61a5ea33c325c Mon Sep 17 00:00:00 2001 From: mrxiaozhuox Date: Thu, 27 Jan 2022 12:09:54 +0800 Subject: [PATCH 24/27] fix: dev server `auto-reload` --- Cargo.toml | 2 +- src/server/mod.rs | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5f6443245..5bbfd7ac2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,6 @@ toml = "0.5.8" fs_extra = "1.2.0" cargo_toml = "0.10.0" futures = "0.3.12" -# notify = "4.0.17" notify = { version = "5.0.0-pre.13", features = ["serde"] } html_parser = "0.6.2" binary-install = "0.0.2" @@ -30,6 +29,7 @@ cargo_metadata = "0.14.1" tokio = { version = "1.15.0", features = ["full"] } atty = "0.2.14" regex = "1.5.4" +chrono = "0.4.19" axum = { version = "0.4.4", features = ["ws", "headers"] } tower-http = { version = "0.2.0", features = ["fs", "trace"] } diff --git a/src/server/mod.rs b/src/server/mod.rs index 45d7191d0..b1684d980 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -28,10 +28,14 @@ pub async fn startup(config: CrateConfig) -> anyhow::Result<()> { update: reload_tx.clone(), }); + let mut last_update_time = chrono::Local::now().timestamp(); + // file watcher: check file change let watcher_conf = config.clone(); - let mut watcher = RecommendedWatcher::new(move |_| { - if builder::build(&watcher_conf).is_ok() { + let mut watcher = RecommendedWatcher::new(move | _ : notify::Result| { + if chrono::Local::now().timestamp() > last_update_time + && builder::build(&watcher_conf).is_ok() + { // change the websocket reload state to true; // the page will auto-reload. if watcher_conf @@ -43,7 +47,9 @@ pub async fn startup(config: CrateConfig) -> anyhow::Result<()> { { let _ = Serve::regen_dev_page(&watcher_conf); } + println!("watcher send reload"); reload_tx.send("reload".into()).unwrap(); + last_update_time = chrono::Local::now().timestamp(); } }) .unwrap(); @@ -127,7 +133,6 @@ async fn ws_handler( let reload_watcher = tokio::spawn(async move { loop { let v = rx.recv().await.unwrap(); - println!("trigger recv: {}", v); if v == "reload" { // ignore the error if socket From 23e09f0d9f0b3740cb9b9809e3efdd0f2652c21c Mon Sep 17 00:00:00 2001 From: mrxiaozhuox Date: Thu, 27 Jan 2022 14:11:18 +0800 Subject: [PATCH 25/27] fix: delete comment --- src/server/mod.rs | 29 +---------------------------- 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/src/server/mod.rs b/src/server/mod.rs index b1684d980..880f25ceb 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -32,7 +32,7 @@ pub async fn startup(config: CrateConfig) -> anyhow::Result<()> { // file watcher: check file change let watcher_conf = config.clone(); - let mut watcher = RecommendedWatcher::new(move | _ : notify::Result| { + let mut watcher = RecommendedWatcher::new(move |_: notify::Result| { if chrono::Local::now().timestamp() > last_update_time && builder::build(&watcher_conf).is_ok() { @@ -70,33 +70,6 @@ pub async fn startup(config: CrateConfig) -> anyhow::Result<()> { .unwrap(); } - // tokio::spawn(async move { - // while let Ok(v) = watcher_rx.recv() { - // match v { - // DebouncedEvent::Create(_) - // | DebouncedEvent::Write(_) - // | DebouncedEvent::Remove(_) - // | DebouncedEvent::Rename(_, _) => { - // if builder::build(&watcher_conf).is_ok() { - // // change the websocket reload state to true; - // // the page will auto-reload. - // if watcher_conf - // .dioxus_config - // .web - // .watcher - // .reload_html - // .unwrap_or(false) - // { - // let _ = Serve::regen_dev_page(&watcher_conf); - // } - // reload_tx.send("reload".into()).unwrap(); - // } - // } - // _ => {} - // } - // } - // }); - // 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); From 5d9a58b0faae85375b523ea3fa70ce29d58b141c Mon Sep 17 00:00:00 2001 From: mrxiaozhuox Date: Thu, 27 Jan 2022 23:06:09 +0800 Subject: [PATCH 26/27] fix: desktop build --- src/builder.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/builder.rs b/src/builder.rs index 583c9bc8f..257072a6a 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -141,6 +141,10 @@ pub fn build_desktop(config: &CrateConfig) -> Result<()> { let output = cmd.output()?; + if !output.status.success() { + return Err(Error::BuildFailed("Program build failed.".into())); + } + if output.status.success() { if config.out_dir.is_dir() { remove_dir_all(&config.out_dir)?; From efe956a1bfd097a2190a7db95286dafe1a2c09d9 Mon Sep 17 00:00:00 2001 From: mrxiaozhuox Date: Thu, 27 Jan 2022 23:56:44 +0800 Subject: [PATCH 27/27] fix: display log --- src/cli/serve/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cli/serve/mod.rs b/src/cli/serve/mod.rs index fd8f3c4f5..8f26e0f30 100644 --- a/src/cli/serve/mod.rs +++ b/src/cli/serve/mod.rs @@ -1,5 +1,5 @@ use crate::{cfg::ConfigOptsServe, gen_page, server, CrateConfig}; -use std::{io::Write, path::PathBuf, process::Command}; +use std::{io::Write, path::PathBuf, process::{Command, Stdio}}; use structopt::StructOpt; /// Run the WASM project on dev-server @@ -44,6 +44,7 @@ impl Serve { .unwrap() .to_string(), ) + .stdout(Stdio::inherit()) .output()?; } }