This commit is contained in:
Evan Almloff 2022-06-16 13:20:41 -05:00
commit 11d045deeb
11 changed files with 361 additions and 18 deletions

View file

@ -2,7 +2,7 @@
name = "dioxus-cli" name = "dioxus-cli"
version = "0.1.4" version = "0.1.4"
authors = ["Jonathan Kelley"] authors = ["Jonathan Kelley"]
edition = "2018" edition = "2021"
description = "CLI tool for developing, testing, and publishing Dioxus apps" description = "CLI tool for developing, testing, and publishing Dioxus apps"
license = "MIT/Apache-2.0" license = "MIT/Apache-2.0"
@ -39,11 +39,15 @@ hyper = "0.14.17"
axum = { version = "0.5.1", features = ["ws", "headers"] } axum = { version = "0.5.1", features = ["ws", "headers"] }
tower-http = { version = "0.2.2", features = ["fs", "trace"] } tower-http = { version = "0.2.2", features = ["fs", "trace"] }
headers = "0.3.7" headers = "0.3.7"
walkdir = "2"
# tools download # tools download
dirs = "4.0.0" dirs = "4.0.0"
reqwest = { version = "0.11", features = ["rustls-tls", "stream", "trust-dns"] } reqwest = { version = "0.11", features = ["rustls-tls", "stream", "trust-dns"] }
flate2 = "1.0.22" flate2 = "1.0.22"
tar = "0.4.38" tar = "0.4.38"
zip = "0.6.2"
tower = "0.4.12" tower = "0.4.12"
syn = { version = "1.0" } syn = { version = "1.0" }

View file

@ -1,5 +1,5 @@
version = "Two" version = "Two"
edition = "2018" edition = "2021"
imports_granularity = "Crate" imports_granularity = "Crate"
#use_small_heuristics = "Max" #use_small_heuristics = "Max"

View file

@ -43,3 +43,18 @@ script = []
# use binaryen.wasm-opt for output Wasm file # use binaryen.wasm-opt for output Wasm file
# binaryen just will trigger in `web` platform # binaryen just will trigger in `web` platform
binaryen = { wasm_opt = true } 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

View file

@ -1,6 +1,7 @@
use crate::{ use crate::{
config::{CrateConfig, ExecutableType}, config::{CrateConfig, ExecutableType},
error::{Error, Result}, error::{Error, Result},
tools::Tool,
DioxusConfig, DioxusConfig,
}; };
use std::{ use std::{
@ -29,6 +30,9 @@ pub fn build(config: &CrateConfig) -> Result<()> {
.. ..
} = config; } = config;
// start to build the assets
let ignore_files = build_assets(config)?;
let t_start = std::time::Instant::now(); let t_start = std::time::Instant::now();
// [1] Build the .wasm module // [1] Build the .wasm module
@ -44,6 +48,21 @@ pub fn build(config: &CrateConfig) -> Result<()> {
if config.release { if config.release {
cmd.arg("--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 { match executable {
ExecutableType::Binary(name) => cmd.arg("--bin").arg(name), ExecutableType::Binary(name) => cmd.arg("--bin").arg(name),
@ -154,6 +173,13 @@ pub fn build(config: &CrateConfig) -> Result<()> {
log::warn!("Error copying dir: {}", _e); 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<()> { pub fn build_desktop(config: &CrateConfig, is_serve: bool) -> Result<()> {
log::info!("🚅 Running build [Desktop] command..."); log::info!("🚅 Running build [Desktop] command...");
let ignore_files = build_assets(config)?;
let mut cmd = Command::new("cargo"); let mut cmd = Command::new("cargo");
cmd.current_dir(&config.crate_dir) cmd.current_dir(&config.crate_dir)
.arg("build") .arg("build")
@ -175,6 +203,21 @@ pub fn build_desktop(config: &CrateConfig, is_serve: bool) -> Result<()> {
if config.release { if config.release {
cmd.arg("--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 { match &config.executable {
crate::ExecutableType::Binary(name) => cmd.arg("--bin").arg(name), 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); 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) 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<Vec<PathBuf>> {
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}; // use binary_install::{Cache, Download};
// /// Attempts to find `wasm-opt` in `PATH` locally, or failing that downloads a // /// Attempts to find `wasm-opt` in `PATH` locally, or failing that downloads a

View file

@ -14,11 +14,16 @@ impl Build {
// change the release state. // change the release state.
crate_config.with_release(self.build.release); crate_config.with_release(self.build.release);
crate_config.with_verbose(self.build.verbose);
if self.build.example.is_some() { if self.build.example.is_some() {
crate_config.as_example(self.build.example.unwrap()); 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(|| { let platform = self.build.platform.unwrap_or_else(|| {
crate_config crate_config
.dioxus_config .dioxus_config

View file

@ -12,13 +12,26 @@ pub struct ConfigOptsBuild {
#[serde(default)] #[serde(default)]
pub release: bool, pub release: bool,
// Use verbose output [default: false]
#[clap(long)]
#[serde(default)]
pub verbose: bool,
/// Build a example [default: ""] /// Build a example [default: ""]
#[clap(long)] #[clap(long)]
pub example: Option<String>, pub example: Option<String>,
/// Build with custom profile
#[clap(long)]
pub profile: Option<String>,
/// Build platform: support Web & Desktop [default: "default_platform"] /// Build platform: support Web & Desktop [default: "default_platform"]
#[clap(long)] #[clap(long)]
pub platform: Option<String>, pub platform: Option<String>,
/// Space separated list of features to activate
#[clap(long)]
pub features: Option<Vec<String>>,
} }
#[derive(Clone, Debug, Default, Deserialize, Parser)] #[derive(Clone, Debug, Default, Deserialize, Parser)]
@ -36,6 +49,15 @@ pub struct ConfigOptsServe {
#[serde(default)] #[serde(default)]
pub release: bool, pub release: bool,
// Use verbose output [default: false]
#[clap(long)]
#[serde(default)]
pub verbose: bool,
/// Build with custom profile
#[clap(long)]
pub profile: Option<String>,
/// Build platform: support Web & Desktop [default: "default_platform"] /// Build platform: support Web & Desktop [default: "default_platform"]
#[clap(long)] #[clap(long)]
pub platform: Option<String>, pub platform: Option<String>,
@ -44,6 +66,10 @@ pub struct ConfigOptsServe {
#[clap(long)] #[clap(long)]
#[serde(default)] #[serde(default)]
pub hot_reload: bool, pub hot_reload: bool,
/// Space separated list of features to activate
#[clap(long)]
pub features: Option<Vec<String>>,
} }
/// Ensure the given value for `--public-url` is formatted correctly. /// Ensure the given value for `--public-url` is formatted correctly.

View file

@ -18,14 +18,18 @@ impl Serve {
let mut crate_config = crate::CrateConfig::new()?; let mut crate_config = crate::CrateConfig::new()?;
// change the relase state. // change the relase state.
crate_config crate_config.with_hot_reload(self.serve.hot_reload);
.with_release(self.serve.release) crate_config.with_release(self.serve.release);
.with_hot_reload(self.serve.hot_reload); crate_config.with_verbose(self.serve.verbose);
if self.serve.example.is_some() { if self.serve.example.is_some() {
crate_config.as_example(self.serve.example.unwrap()); 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(|| { let platform = self.serve.platform.unwrap_or_else(|| {
crate_config crate_config
.dioxus_config .dioxus_config

View file

@ -20,14 +20,18 @@ impl Tool {
Tool::List {} => { Tool::List {} => {
for item in tools::tool_list() { for item in tools::tool_list() {
if tools::Tool::from_str(item).unwrap().is_installed() { if tools::Tool::from_str(item).unwrap().is_installed() {
println!("{item} [installed]"); println!("- {item} [installed]");
} else { } else {
println!("{item}"); println!("- {item}");
} }
} }
} }
Tool::AppPath {} => { 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 } => { Tool::Add { name } => {
let tool_list = tools::tool_list(); let tool_list = tools::tool_list();
@ -58,7 +62,6 @@ impl Tool {
log::info!("Tool {name} install successfully!"); log::info!("Tool {name} install successfully!");
} }
} }
Ok(()) Ok(())
} }
} }

View file

@ -115,6 +115,9 @@ pub struct CrateConfig {
pub dioxus_config: DioxusConfig, pub dioxus_config: DioxusConfig,
pub release: bool, pub release: bool,
pub hot_reload: bool, pub hot_reload: bool,
pub verbose: bool,
pub custom_profile: Option<String>,
pub features: Option<Vec<String>>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -164,6 +167,9 @@ impl CrateConfig {
let release = false; let release = false;
let hot_reload = false; let hot_reload = false;
let verbose = false;
let custom_profile = None;
let features = None;
Ok(Self { Ok(Self {
out_dir, out_dir,
@ -176,6 +182,9 @@ impl CrateConfig {
release, release,
dioxus_config, dioxus_config,
hot_reload, hot_reload,
custom_profile,
features,
verbose,
}) })
} }
@ -194,6 +203,21 @@ impl CrateConfig {
self 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<String>) -> &mut Self {
self.features = Some(features);
self
}
// pub fn with_build_options(&mut self, options: &BuildOptions) { // pub fn with_build_options(&mut self, options: &BuildOptions) {
// if let Some(name) = &options.example { // if let Some(name) = &options.example {
// self.as_example(name.clone()); // self.as_example(name.clone());

View file

@ -189,7 +189,7 @@ pub async fn startup_hot_reload(config: CrateConfig) -> Result<()> {
let file_service_config = config.clone(); let file_service_config = config.clone();
let file_service = ServiceBuilder::new() let file_service = ServiceBuilder::new()
.and_then( .and_then(
|response: Response<ServeFileSystemResponseBody>| async move { move |response: Response<ServeFileSystemResponseBody>| async move {
let response = if file_service_config let response = if file_service_config
.dioxus_config .dioxus_config
.web .web
@ -300,7 +300,7 @@ pub async fn startup_default(config: CrateConfig) -> Result<()> {
let file_service_config = config.clone(); let file_service_config = config.clone();
let file_service = ServiceBuilder::new() let file_service = ServiceBuilder::new()
.and_then( .and_then(
|response: Response<ServeFileSystemResponseBody>| async move { move |response: Response<ServeFileSystemResponseBody>| async move {
let response = if file_service_config let response = if file_service_config
.dioxus_config .dioxus_config
.web .web

View file

@ -1,6 +1,6 @@
use std::{ use std::{
fs::{create_dir_all, File}, fs::{create_dir_all, File},
path::PathBuf, path::{Path, PathBuf},
process::Command, process::Command,
}; };
@ -13,10 +13,11 @@ use tokio::io::AsyncWriteExt;
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub enum Tool { pub enum Tool {
Binaryen, Binaryen,
Sass,
} }
pub fn tool_list() -> Vec<&'static str> { pub fn tool_list() -> Vec<&'static str> {
vec!["binaryen"] vec!["binaryen", "sass"]
} }
pub fn app_path() -> PathBuf { pub fn app_path() -> PathBuf {
@ -52,6 +53,7 @@ impl Tool {
pub fn from_str(name: &str) -> Option<Self> { pub fn from_str(name: &str) -> Option<Self> {
match name { match name {
"binaryen" => Some(Self::Binaryen), "binaryen" => Some(Self::Binaryen),
"sass" => Some(Self::Sass),
_ => None, _ => None,
} }
} }
@ -60,6 +62,7 @@ impl Tool {
pub fn name(&self) -> &str { pub fn name(&self) -> &str {
match self { match self {
Self::Binaryen => "binaryen", Self::Binaryen => "binaryen",
Self::Sass => "sass",
} }
} }
@ -67,6 +70,7 @@ impl Tool {
pub fn bin_path(&self) -> &str { pub fn bin_path(&self) -> &str {
match self { match self {
Self::Binaryen => "bin", Self::Binaryen => "bin",
Self::Sass => ".",
} }
} }
@ -84,6 +88,17 @@ impl Tool {
panic!("unsupported platformm"); 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() 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 { pub fn extension(&self) -> &str {
match self { match self {
Self::Binaryen => "tar.gz", 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 chunk = chunk_res.context("error reading chunk from download")?;
let _ = file.write(chunk.as_ref()).await; let _ = file.write(chunk.as_ref()).await;
} }
// log::info!("temp file path: {:?}", temp_out);
Ok(temp_out) Ok(temp_out)
} }
@ -143,7 +172,7 @@ impl Tool {
let dir_name = if self == &Tool::Binaryen { let dir_name = if self == &Tool::Binaryen {
"binaryen-version_105" "binaryen-version_105"
} else { } else {
"" "dart-sass"
}; };
if self.extension() == "tar.gz" { if self.extension() == "tar.gz" {
@ -151,7 +180,10 @@ impl Tool {
let tar = GzDecoder::new(tar_gz); let tar = GzDecoder::new(tar_gz);
let mut archive = Archive::new(tar); let mut archive = Archive::new(tar);
archive.unpack(&tool_path)?; 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()))?; std::fs::rename(tool_path.join(dir_name), tool_path.join(self.name()))?;
} }
@ -169,6 +201,13 @@ impl Tool {
command.to_string() 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() { if !bin_path.join(&command_file).is_file() {
@ -185,3 +224,32 @@ impl Tool {
Ok(output.stdout) 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(())
}