diff --git a/Cargo.toml b/Cargo.toml index 21e2dad7e..c869267b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" [[bin]] 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..4f6824780 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 @@ -154,6 +158,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 +177,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") @@ -250,6 +263,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 +359,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/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/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(()) +}