diff --git a/Cargo.toml b/Cargo.toml index 7f1223520..f5afe80db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "dioxus-cli" version = "0.1.4" authors = ["Jonathan Kelley"] -edition = "2018" +edition = "2021" description = "CLI tool for developing, testing, and publishing Dioxus apps" license = "MIT/Apache-2.0" @@ -39,11 +39,15 @@ hyper = "0.14.17" axum = { version = "0.5.1", features = ["ws", "headers"] } tower-http = { version = "0.2.2", features = ["fs", "trace"] } headers = "0.3.7" + +walkdir = "2" + # tools download dirs = "4.0.0" reqwest = { version = "0.11", features = ["rustls-tls", "stream", "trust-dns"] } flate2 = "1.0.22" tar = "0.4.38" +zip = "0.6.2" tower = "0.4.12" syn = { version = "1.0" } diff --git a/rustfmt.toml b/rustfmt.toml index 9071fb4b5..39b98f125 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,8 +1,8 @@ version = "Two" -edition = "2018" +edition = "2021" imports_granularity = "Crate" #use_small_heuristics = "Max" #control_brace_style = "ClosingNextLine" normalize_comments = true -format_code_in_doc_comments = true \ No newline at end of file +format_code_in_doc_comments = true diff --git a/src/assets/dioxus.toml b/src/assets/dioxus.toml index b43d0d6df..a5e849bfb 100644 --- a/src/assets/dioxus.toml +++ b/src/assets/dioxus.toml @@ -42,4 +42,19 @@ script = [] # use binaryen.wasm-opt for output Wasm file # binaryen just will trigger in `web` platform -binaryen = { wasm_opt = true } \ No newline at end of file +binaryen = { wasm_opt = true } + +# use sass auto will auto check sass file and build it. +[application.tools.sass] + +# auto will check the assets dirs, and auto to transform all scss file to css file. +input = "*" + +# or you can specify some scss file -> css file +# input = [ +# # some sass file path +# # this file will translate to `/css/test.css` +# "/css/test.scss" +# ] + +source_map = true \ No newline at end of file diff --git a/src/builder.rs b/src/builder.rs index f7f68e2bc..02add84ad 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -1,6 +1,7 @@ use crate::{ config::{CrateConfig, ExecutableType}, error::{Error, Result}, + tools::Tool, DioxusConfig, }; use std::{ @@ -29,6 +30,9 @@ pub fn build(config: &CrateConfig) -> Result<()> { .. } = config; + // start to build the assets + let ignore_files = build_assets(config)?; + let t_start = std::time::Instant::now(); // [1] Build the .wasm module @@ -44,6 +48,21 @@ pub fn build(config: &CrateConfig) -> Result<()> { if config.release { cmd.arg("--release"); } + if config.verbose { + cmd.arg("--verbose"); + } + + if config.custom_profile.is_some() { + let custom_profile = config.custom_profile.as_ref().unwrap(); + cmd.arg("--profile"); + cmd.arg(custom_profile); + } + + if config.features.is_some() { + let features_str = config.features.as_ref().unwrap().join(" "); + cmd.arg("--features"); + cmd.arg(features_str); + } match executable { ExecutableType::Binary(name) => cmd.arg("--bin").arg(name), @@ -154,6 +173,13 @@ pub fn build(config: &CrateConfig) -> Result<()> { log::warn!("Error copying dir: {}", _e); } } + for ignore in &ignore_files { + let ignore = ignore.strip_prefix(&config.asset_dir).unwrap(); + let ignore = config.out_dir.join(ignore); + if ignore.is_file() { + std::fs::remove_file(ignore)?; + } + } } } } @@ -166,6 +192,8 @@ pub fn build(config: &CrateConfig) -> Result<()> { pub fn build_desktop(config: &CrateConfig, is_serve: bool) -> Result<()> { log::info!("🚅 Running build [Desktop] command..."); + let ignore_files = build_assets(config)?; + let mut cmd = Command::new("cargo"); cmd.current_dir(&config.crate_dir) .arg("build") @@ -175,6 +203,21 @@ pub fn build_desktop(config: &CrateConfig, is_serve: bool) -> Result<()> { if config.release { cmd.arg("--release"); } + if config.verbose { + cmd.arg("--verbose"); + } + + if config.custom_profile.is_some() { + let custom_profile = config.custom_profile.as_ref().unwrap(); + cmd.arg("--profile"); + cmd.arg(custom_profile); + } + + if config.features.is_some() { + let features_str = config.features.as_ref().unwrap().join(" "); + cmd.arg("--features"); + cmd.arg(features_str); + } match &config.executable { crate::ExecutableType::Binary(name) => cmd.arg("--bin").arg(name), @@ -250,6 +293,13 @@ pub fn build_desktop(config: &CrateConfig, is_serve: bool) -> Result<()> { log::warn!("Error copying dir: {}", e); } } + for ignore in &ignore_files { + let ignore = ignore.strip_prefix(&config.asset_dir).unwrap(); + let ignore = config.out_dir.join(ignore); + if ignore.is_file() { + std::fs::remove_file(ignore)?; + } + } } } } @@ -339,6 +389,150 @@ pub fn gen_page(config: &DioxusConfig, serve: bool) -> String { html.replace("{app_title}", &title) } +// this function will build some assets file +// like sass tool resources +// this function will return a array which file don't need copy to out_dir. +fn build_assets(config: &CrateConfig) -> Result> { + let mut result = vec![]; + + let dioxus_config = &config.dioxus_config; + let dioxus_tools = dioxus_config.application.tools.clone().unwrap_or_default(); + + // check sass tool state + let sass = Tool::Sass; + if sass.is_installed() && dioxus_tools.contains_key("sass") { + let sass_conf = dioxus_tools.get("sass").unwrap(); + if let Some(tab) = sass_conf.as_table() { + let source_map = tab.contains_key("source_map"); + let source_map = if source_map && tab.get("source_map").unwrap().is_bool() { + if tab.get("source_map").unwrap().as_bool().unwrap_or_default() { + "--source-map" + } else { + "--no-source-map" + } + } else { + "--source-map" + }; + + if tab.contains_key("input") { + if tab.get("input").unwrap().is_str() { + let file = tab.get("input").unwrap().as_str().unwrap().trim(); + + if file == "*" { + // if the sass open auto, we need auto-check the assets dir. + let asset_dir = config.asset_dir.clone(); + if asset_dir.is_dir() { + for entry in walkdir::WalkDir::new(&asset_dir) + .into_iter() + .filter_map(|e| e.ok()) + { + let temp = entry.path(); + if temp.is_file() { + let suffix = temp.extension(); + if suffix.is_none() { continue; } + let suffix = suffix.unwrap().to_str().unwrap(); + if suffix == "scss" || suffix == "sass" { + // if file suffix is `scss` / `sass` we need transform it. + let out_file = format!( + "{}.css", + temp.file_stem().unwrap().to_str().unwrap() + ); + let target_path = config + .out_dir + .join( + temp.strip_prefix(&asset_dir) + .unwrap() + .parent() + .unwrap(), + ) + .join(out_file); + let res = sass.call( + "sass", + vec![ + temp.to_str().unwrap(), + target_path.to_str().unwrap(), + source_map, + ], + ); + if res.is_ok() { + result.push(temp.to_path_buf()); + } + } + } + } + } + } else { + // just transform one file. + let relative_path = if &file[0..1] == "/" { + &file[1..file.len()] + } else { + file + }; + let path = config.asset_dir.join(relative_path); + let out_file = + format!("{}.css", path.file_stem().unwrap().to_str().unwrap()); + let target_path = config + .out_dir + .join(PathBuf::from(relative_path).parent().unwrap()) + .join(out_file); + if path.is_file() { + let res = sass.call( + "sass", + vec![ + path.to_str().unwrap(), + target_path.to_str().unwrap(), + source_map, + ], + ); + if res.is_ok() { + result.push(path); + } else { + log::error!("{:?}", res); + } + } + } + } else if tab.get("input").unwrap().is_array() { + // check files list. + let list = tab.get("input").unwrap().as_array().unwrap(); + for i in list { + if i.is_str() { + let path = i.as_str().unwrap(); + let relative_path = if &path[0..1] == "/" { + &path[1..path.len()] + } else { + path + }; + let path = config.asset_dir.join(relative_path); + let out_file = + format!("{}.css", path.file_stem().unwrap().to_str().unwrap()); + let target_path = config + .out_dir + .join(PathBuf::from(relative_path).parent().unwrap()) + .join(out_file); + if path.is_file() { + let res = sass.call( + "sass", + vec![ + path.to_str().unwrap(), + target_path.to_str().unwrap(), + source_map, + ], + ); + if res.is_ok() { + result.push(path); + } + } + } + } + } + } + } + } + // SASS END + + Ok(result) +} + // use binary_install::{Cache, Download}; // /// Attempts to find `wasm-opt` in `PATH` locally, or failing that downloads a diff --git a/src/cli/build/mod.rs b/src/cli/build/mod.rs index 4c043091f..661f17a5a 100644 --- a/src/cli/build/mod.rs +++ b/src/cli/build/mod.rs @@ -14,11 +14,16 @@ impl Build { // change the release state. crate_config.with_release(self.build.release); + crate_config.with_verbose(self.build.verbose); if self.build.example.is_some() { crate_config.as_example(self.build.example.unwrap()); } + if self.build.profile.is_some() { + crate_config.set_profile(self.build.profile.unwrap()); + } + let platform = self.build.platform.unwrap_or_else(|| { crate_config .dioxus_config diff --git a/src/cli/cfg.rs b/src/cli/cfg.rs index 3f284cbbf..73f9570d4 100644 --- a/src/cli/cfg.rs +++ b/src/cli/cfg.rs @@ -12,13 +12,26 @@ pub struct ConfigOptsBuild { #[serde(default)] pub release: bool, + // Use verbose output [default: false] + #[clap(long)] + #[serde(default)] + pub verbose: bool, + /// Build a example [default: ""] #[clap(long)] pub example: Option, + /// Build with custom profile + #[clap(long)] + pub profile: Option, + /// Build platform: support Web & Desktop [default: "default_platform"] #[clap(long)] pub platform: Option, + + /// Space separated list of features to activate + #[clap(long)] + pub features: Option>, } #[derive(Clone, Debug, Default, Deserialize, Parser)] @@ -36,6 +49,15 @@ pub struct ConfigOptsServe { #[serde(default)] pub release: bool, + // Use verbose output [default: false] + #[clap(long)] + #[serde(default)] + pub verbose: bool, + + /// Build with custom profile + #[clap(long)] + pub profile: Option, + /// Build platform: support Web & Desktop [default: "default_platform"] #[clap(long)] pub platform: Option, @@ -44,6 +66,10 @@ pub struct ConfigOptsServe { #[clap(long)] #[serde(default)] pub hot_reload: bool, + + /// Space separated list of features to activate + #[clap(long)] + pub features: 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 391e646d4..5511845cb 100644 --- a/src/cli/serve/mod.rs +++ b/src/cli/serve/mod.rs @@ -18,14 +18,18 @@ impl Serve { let mut crate_config = crate::CrateConfig::new()?; // change the relase state. - crate_config - .with_release(self.serve.release) - .with_hot_reload(self.serve.hot_reload); + crate_config.with_hot_reload(self.serve.hot_reload); + crate_config.with_release(self.serve.release); + crate_config.with_verbose(self.serve.verbose); if self.serve.example.is_some() { crate_config.as_example(self.serve.example.unwrap()); } + if self.serve.profile.is_some() { + crate_config.set_profile(self.serve.profile.unwrap()); + } + let platform = self.serve.platform.unwrap_or_else(|| { crate_config .dioxus_config diff --git a/src/cli/tool/mod.rs b/src/cli/tool/mod.rs index d5c4581aa..fde94b29a 100644 --- a/src/cli/tool/mod.rs +++ b/src/cli/tool/mod.rs @@ -20,14 +20,18 @@ impl Tool { Tool::List {} => { for item in tools::tool_list() { if tools::Tool::from_str(item).unwrap().is_installed() { - println!("{item} [installed]"); + println!("- {item} [installed]"); } else { - println!("{item}"); + println!("- {item}"); } } } Tool::AppPath {} => { - println!("{}", tools::tools_path().to_str().unwrap()); + if let Some(v) = tools::tools_path().to_str() { + println!("{}", v); + } else { + log::error!("Tools path get failed."); + } } Tool::Add { name } => { let tool_list = tools::tool_list(); @@ -58,7 +62,6 @@ impl Tool { log::info!("Tool {name} install successfully!"); } } - Ok(()) } } diff --git a/src/config.rs b/src/config.rs index aa2a03440..bd27a3624 100644 --- a/src/config.rs +++ b/src/config.rs @@ -115,6 +115,9 @@ pub struct CrateConfig { pub dioxus_config: DioxusConfig, pub release: bool, pub hot_reload: bool, + pub verbose: bool, + pub custom_profile: Option, + pub features: Option>, } #[derive(Debug, Clone)] @@ -164,6 +167,9 @@ impl CrateConfig { let release = false; let hot_reload = false; + let verbose = false; + let custom_profile = None; + let features = None; Ok(Self { out_dir, @@ -176,6 +182,9 @@ impl CrateConfig { release, dioxus_config, hot_reload, + custom_profile, + features, + verbose, }) } @@ -194,6 +203,21 @@ impl CrateConfig { self } + pub fn with_verbose(&mut self, verbose: bool) -> &mut Self { + self.verbose = verbose; + self + } + + pub fn set_profile(&mut self, profile: String) -> &mut Self { + self.custom_profile = Some(profile); + self + } + + pub fn set_features(&mut self, features: Vec) -> &mut Self { + self.features = Some(features); + self + } + // pub fn with_build_options(&mut self, options: &BuildOptions) { // if let Some(name) = &options.example { // self.as_example(name.clone()); diff --git a/src/server/mod.rs b/src/server/mod.rs index 5742c86ee..1eb159ae5 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -189,7 +189,7 @@ pub async fn startup_hot_reload(config: CrateConfig) -> Result<()> { let file_service_config = config.clone(); let file_service = ServiceBuilder::new() .and_then( - |response: Response| async move { + move |response: Response| async move { let response = if file_service_config .dioxus_config .web @@ -300,7 +300,7 @@ pub async fn startup_default(config: CrateConfig) -> Result<()> { let file_service_config = config.clone(); let file_service = ServiceBuilder::new() .and_then( - |response: Response| async move { + move |response: Response| async move { let response = if file_service_config .dioxus_config .web diff --git a/src/tools.rs b/src/tools.rs index 937063be0..2ab358d6b 100644 --- a/src/tools.rs +++ b/src/tools.rs @@ -1,6 +1,6 @@ use std::{ fs::{create_dir_all, File}, - path::PathBuf, + path::{Path, PathBuf}, process::Command, }; @@ -13,10 +13,11 @@ use tokio::io::AsyncWriteExt; #[derive(Debug, PartialEq, Eq)] pub enum Tool { Binaryen, + Sass, } pub fn tool_list() -> Vec<&'static str> { - vec!["binaryen"] + vec!["binaryen", "sass"] } pub fn app_path() -> PathBuf { @@ -52,6 +53,7 @@ impl Tool { pub fn from_str(name: &str) -> Option { match name { "binaryen" => Some(Self::Binaryen), + "sass" => Some(Self::Sass), _ => None, } } @@ -60,6 +62,7 @@ impl Tool { pub fn name(&self) -> &str { match self { Self::Binaryen => "binaryen", + Self::Sass => "sass", } } @@ -67,6 +70,7 @@ impl Tool { pub fn bin_path(&self) -> &str { match self { Self::Binaryen => "bin", + Self::Sass => ".", } } @@ -84,6 +88,17 @@ impl Tool { panic!("unsupported platformm"); } } + Self::Sass => { + if cfg!(target_os = "windows") { + "windows" + } else if cfg!(target_os = "macos") { + "macos" + } else if cfg!(target_os = "linux") { + "linux" + } else { + panic!("unsupported platformm"); + } + } } } @@ -96,6 +111,13 @@ impl Tool { target = self.target_platform() ) } + Self::Sass => { + format!( + "https://github.com/sass/dart-sass/releases/download/1.51.0/dart-sass-1.51.0-{target}-x64.{extension}", + target = self.target_platform(), + extension = self.extension() + ) + } } } @@ -103,6 +125,13 @@ impl Tool { pub fn extension(&self) -> &str { match self { Self::Binaryen => "tar.gz", + Self::Sass => { + if cfg!(target_os = "windows") { + "zip" + } else { + "tar.gz" + } + } } } @@ -131,7 +160,7 @@ impl Tool { let chunk = chunk_res.context("error reading chunk from download")?; let _ = file.write(chunk.as_ref()).await; } - + // log::info!("temp file path: {:?}", temp_out); Ok(temp_out) } @@ -143,7 +172,7 @@ impl Tool { let dir_name = if self == &Tool::Binaryen { "binaryen-version_105" } else { - "" + "dart-sass" }; if self.extension() == "tar.gz" { @@ -151,7 +180,10 @@ impl Tool { let tar = GzDecoder::new(tar_gz); let mut archive = Archive::new(tar); archive.unpack(&tool_path)?; - // println!("{:?} -> {:?}", tool_path.join(dir_name), tool_path.join(self.name())); + std::fs::rename(tool_path.join(dir_name), tool_path.join(self.name()))?; + } else if self.extension() == "zip" { + // decompress the `zip` file + extract_zip(&temp_path, &tool_path)?; std::fs::rename(tool_path.join(dir_name), tool_path.join(self.name()))?; } @@ -169,6 +201,13 @@ impl Tool { command.to_string() } } + Tool::Sass => { + if cfg!(target_os = "windows") { + format!("{}.bat", command) + } else { + command.to_string() + } + } }; if !bin_path.join(&command_file).is_file() { @@ -185,3 +224,32 @@ impl Tool { Ok(output.stdout) } } + +fn extract_zip(file: &Path, target: &Path) -> anyhow::Result<()> { + let zip_file = std::fs::File::open(&file)?; + let mut zip = zip::ZipArchive::new(zip_file)?; + + if !target.exists() { + let _ = std::fs::create_dir_all(target)?; + } + + for i in 0..zip.len() { + let mut file = zip.by_index(i)?; + if file.is_dir() { + // dir + let target = target.join(Path::new(&file.name().replace('\\', ""))); + let _ = std::fs::create_dir_all(target)?; + } else { + // file + let file_path = target.join(Path::new(file.name())); + let mut target_file = if !file_path.exists() { + std::fs::File::create(file_path)? + } else { + std::fs::File::open(file_path)? + }; + let _num = std::io::copy(&mut file, &mut target_file)?; + } + } + + Ok(()) +}