diff --git a/Cargo.toml b/Cargo.toml
index 2e4d21c2d..5f5ded9be 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -9,10 +9,12 @@ license = "MIT/Apache-2.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
+
# cli core
clap = { version = "3.0.14", features = ["derive"] }
thiserror = "1.0.30"
wasm-bindgen-cli-support = "0.2.79"
+
# features
log = "0.4.14"
fern = { version = "0.6.0", features = ["colored"] }
@@ -37,10 +39,13 @@ hyper = "0.14.17"
axum = { version = "0.4.5", features = ["ws", "headers"] }
tower-http = { version = "0.2.2", features = ["fs", "trace"] }
headers = "0.3.7"
+# tools download
+dirs = "4.0.0"
+reqwest = { version = "0.11", features = ["rustls-tls", "stream", "trust-dns"] }
+flate2 = "1.0.22"
+tar = "0.4.38"
tower = "0.4.12"
-# hyper = { version = "0.14.11", features = ["full"] }
-
[[bin]]
path = "src/main.rs"
name = "dioxus"
diff --git a/Dioxus.toml b/Dioxus.toml
new file mode 100644
index 000000000..c3e0dd8a0
--- /dev/null
+++ b/Dioxus.toml
@@ -0,0 +1,45 @@
+[application]
+
+# dioxus project name
+name = "dioxus-cli"
+
+# default platfrom
+# you can also use `dioxus serve/build --platform XXX` to use other platform
+# value: web | desktop
+default_platform = "desktop"
+
+# Web `build` & `serve` dist path
+out_dir = "dist"
+
+# resource (static) file folder
+asset_dir = "public"
+
+[web.app]
+
+# HTML title tag content
+title = "dioxus | ⛺"
+
+[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 = []
+
+[application.tools]
+
+# 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
diff --git a/src/assets/autoreload.js b/src/assets/autoreload.js
index da62e5eae..fffb3e26c 100644
--- a/src/assets/autoreload.js
+++ b/src/assets/autoreload.js
@@ -1,3 +1,6 @@
+// Dioxus-CLI
+// https://github.com/DioxusLabs/cli
+
(function () {
var protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
var url = protocol + '//' + window.location.host + '/_dioxus/ws';
@@ -15,7 +18,6 @@
var ws = new WebSocket(url);
ws.onmessage = (ev) => {
if (ev.data == "reload") {
- // alert("reload!!!");
window.location.reload();
}
};
diff --git a/src/assets/dioxus.toml b/src/assets/dioxus.toml
index d3f160242..ab85abe86 100644
--- a/src/assets/dioxus.toml
+++ b/src/assets/dioxus.toml
@@ -6,7 +6,7 @@ name = "{{project-name}}"
# default platfrom
# you can also use `dioxus serve/build --platform XXX` to use other platform
# value: web | desktop
-default_platform = "web"
+default_platform = "{{default-platform}}"
# Web `build` & `serve` dist path
out_dir = "dist"
@@ -36,4 +36,10 @@ script = []
# Javascript code file
# serve: [dev-server] only
-script = []
\ No newline at end of file
+script = []
+
+[application.tools]
+
+# use binaryen.wasm-opt for output Wasm file
+# binaryen just will trigger in `web` platform
+binaryen = { wasm_opt = [".PROJECT_OUT"] }
\ No newline at end of file
diff --git a/src/assets/index.html b/src/assets/index.html
index f907aa281..f57acbd34 100644
--- a/src/assets/index.html
+++ b/src/assets/index.html
@@ -1,14 +1,11 @@
-
{app_title}
{style_include}
-
-
@@ -22,5 +19,4 @@
{script_include}
-
\ No newline at end of file
diff --git a/src/builder.rs b/src/builder.rs
index bacfcd664..aaaa0278a 100644
--- a/src/builder.rs
+++ b/src/builder.rs
@@ -4,7 +4,8 @@ use crate::{
DioxusConfig,
};
use std::{
- fs::{copy, create_dir_all, remove_dir_all},
+ fs::{copy, create_dir_all, remove_dir_all, File},
+ io::Read,
panic,
path::PathBuf,
process::Command,
@@ -95,8 +96,43 @@ pub fn build(config: &CrateConfig) -> Result<()> {
});
if bindgen_result.is_err() {
log::error!("Bindgen build failed! \nThis is probably due to the Bindgen version, dioxus-cli using `0.2.79` Bindgen crate.");
+ return Ok(());
}
+ // check binaryen:wasm-opt tool
+ let dioxus_tools = dioxus_config.application.tools.clone().unwrap_or_default();
+ if dioxus_tools.contains_key("binaryen") {
+ let info = dioxus_tools.get("binaryen").unwrap();
+ let binaryen = crate::tools::Tool::Binaryen;
+
+ if binaryen.is_installed() {
+ if let Some(sub) = info.as_table() {
+ if sub.contains_key("wasm_opt")
+ && sub.get("wasm_opt").unwrap().as_bool().unwrap_or(false)
+ {
+ let target_file = out_dir
+ .join("assets")
+ .join("dioxus")
+ .join(format!("{}_bg.wasm", dioxus_config.application.name));
+ if target_file.is_file() {
+ binaryen.call(
+ "wasm-opt",
+ vec![
+ target_file.to_str().unwrap(),
+ "-o",
+ target_file.to_str().unwrap(),
+ ],
+ )?;
+ }
+ }
+ }
+ } else {
+ log::warn!(
+ "Binaryen tool not found, you can use `dioxus tool add binaryen` to install it."
+ );
+ }
+ }
+
// this code will copy all public file to the output dir
let copy_options = fs_extra::dir::CopyOptions {
overwrite: true,
@@ -237,7 +273,19 @@ pub fn build_desktop(config: &CrateConfig, is_serve: bool) -> Result<()> {
}
pub fn gen_page(config: &DioxusConfig, serve: bool) -> String {
- let mut html = String::from(include_str!("./assets/index.html"));
+ let crate_root = crate::cargo::crate_root().unwrap();
+ let custom_html_file = crate_root.join("index.html");
+ let mut html = if custom_html_file.is_file() {
+ let mut buf = String::new();
+ let mut file = File::open(custom_html_file).unwrap();
+ if file.read_to_string(&mut buf).is_ok() {
+ buf
+ } else {
+ String::from(include_str!("./assets/index.html"))
+ }
+ } else {
+ String::from(include_str!("./assets/index.html"))
+ };
let resouces = config.web.resource.clone();
diff --git a/src/cli/build/mod.rs b/src/cli/build/mod.rs
index 4c043091f..3b4d343af 100644
--- a/src/cli/build/mod.rs
+++ b/src/cli/build/mod.rs
@@ -12,6 +12,7 @@ impl Build {
pub fn build(self) -> Result<()> {
let mut crate_config = crate::CrateConfig::new()?;
+
// change the release state.
crate_config.with_release(self.build.release);
diff --git a/src/cli/config/mod.rs b/src/cli/config/mod.rs
index 6d672b7a5..fb1e046fb 100644
--- a/src/cli/config/mod.rs
+++ b/src/cli/config/mod.rs
@@ -13,14 +13,26 @@ pub enum Config {
#[clap(long)]
#[serde(default)]
force: bool,
+
+ /// Project default platform
+ #[clap(long, default_value = "web")]
+ platform: String,
},
+ /// Format print Dioxus config.
+ FormatPrint {},
+ /// Create a custom html file.
+ CustomHtml {}
}
impl Config {
pub fn config(self) -> Result<()> {
let crate_root = crate::cargo::crate_root()?;
match self {
- Config::Init { name, force } => {
+ Config::Init {
+ name,
+ force,
+ platform,
+ } => {
let conf_path = crate_root.join("Dioxus.toml");
if conf_path.is_file() && !force {
log::warn!(
@@ -30,10 +42,21 @@ impl Config {
}
let mut file = File::create(conf_path)?;
let content = String::from(include_str!("../../assets/dioxus.toml"))
- .replace("{{project-name}}", &name);
+ .replace("{{project-name}}", &name)
+ .replace("{{default-platform}}", &platform);
file.write_all(content.as_bytes())?;
log::info!("🚩 Init config file completed.");
}
+ Config::FormatPrint {} => {
+ println!("{:#?}", crate::CrateConfig::new()?.dioxus_config);
+ }
+ Config::CustomHtml {} => {
+ let html_path = crate_root.join("index.html");
+ let mut file = File::create(html_path)?;
+ let content = include_str!("../../assets/index.html");
+ file.write_all(content.as_bytes())?;
+ log::info!("🚩 Create custom html file done.");
+ }
}
Ok(())
}
diff --git a/src/cli/mod.rs b/src/cli/mod.rs
index 1b5ba0be7..2c91c94dd 100644
--- a/src/cli/mod.rs
+++ b/src/cli/mod.rs
@@ -5,6 +5,7 @@ pub mod config;
pub mod create;
pub mod serve;
pub mod translate;
+pub mod tool;
use crate::{
cfg::{ConfigOptsBuild, ConfigOptsServe},
@@ -55,4 +56,7 @@ pub enum Commands {
/// Dioxus config file controls.
#[clap(subcommand)]
Config(config::Config),
+ /// Install & Manage tools for Dioxus-cli.
+ #[clap(subcommand)]
+ Tool(tool::Tool),
}
diff --git a/src/cli/tool/mod.rs b/src/cli/tool/mod.rs
new file mode 100644
index 000000000..51e41e384
--- /dev/null
+++ b/src/cli/tool/mod.rs
@@ -0,0 +1,66 @@
+use crate::tools;
+
+use super::*;
+
+/// Build the Rust WASM app and all of its assets.
+#[derive(Clone, Debug, Deserialize, Subcommand)]
+#[clap(name = "tool")]
+pub enum Tool {
+ /// Return all dioxus-cli support tools.
+ List {},
+ /// Get default app install path.
+ AppPath {},
+ /// Install a new tool.
+ Add {
+ name: String
+ }
+}
+
+impl Tool {
+ pub async fn tool(self) -> Result<()> {
+ match self {
+ Tool::List {} => {
+ for item in tools::tool_list() {
+ if tools::Tool::from_str(item).unwrap().is_installed() {
+ println!("{item} [installed]");
+ } else {
+ println!("{item}");
+ }
+ }
+ }
+ Tool::AppPath {} => {
+ println!("{}", tools::tools_path().to_str().unwrap());
+ }
+ Tool::Add { name } => {
+ let tool_list = tools::tool_list();
+
+ if !tool_list.contains(&name.as_str()) {
+ log::error!("Tool {name} not found.");
+ return Ok(());
+ }
+ let target_tool = tools::Tool::from_str(&name).unwrap();
+
+ if target_tool.is_installed() {
+ log::warn!("Tool {name} is installed.");
+ return Ok(());
+ }
+
+ log::info!("Start to download tool package...");
+ if let Err(e) = target_tool.download_package().await {
+ log::error!("Tool download failed: {e}");
+ return Ok(());
+ }
+
+ log::info!("Start to install tool package...");
+ if let Err(e) = target_tool.install_package().await {
+ log::error!("Tool install failed: {e}");
+ return Ok(());
+ }
+
+ log::info!("Tool {name} install successfully!");
+ }
+ }
+
+ Ok(())
+ }
+}
diff --git a/src/config.rs b/src/config.rs
index 9e8b47f7a..0cdd0bd8b 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -1,6 +1,6 @@
use crate::error::Result;
use serde::{Deserialize, Serialize};
-use std::{fs::File, io::Read, path::PathBuf};
+use std::{fs::File, io::Read, path::PathBuf, collections::HashMap};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DioxusConfig {
@@ -34,6 +34,7 @@ impl Default for DioxusConfig {
default_platform: "web".to_string(),
out_dir: Some(PathBuf::from("dist")),
asset_dir: Some(PathBuf::from("public")),
+ tools: None,
sub_package: None,
},
web: WebConfig {
@@ -65,6 +66,7 @@ pub struct ApplicationConfig {
pub default_platform: String,
pub out_dir: Option,
pub asset_dir: Option,
+ pub tools: Option>,
pub sub_package: Option,
}
diff --git a/src/lib.rs b/src/lib.rs
index 395cbc8dc..8cd4b1f2d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,5 +1,7 @@
pub mod builder;
pub mod server;
+pub mod tools;
+
pub use builder::*;
pub mod cargo;
diff --git a/src/main.rs b/src/main.rs
index 97d4a3bb4..099ff74c0 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -42,6 +42,12 @@ async fn main() -> Result<()> {
log::error!("config error: {}", e);
}
}
+
+ Commands::Tool(opts) => {
+ if let Err(e) = opts.tool().await {
+ log::error!("tool error: {}", e);
+ }
+ }
}
Ok(())
diff --git a/src/server/mod.rs b/src/server/mod.rs
index 69d9799d2..1df13ca6a 100644
--- a/src/server/mod.rs
+++ b/src/server/mod.rs
@@ -35,23 +35,23 @@ pub async fn startup(config: CrateConfig) -> Result<()> {
// file watcher: check file change
let watcher_conf = config.clone();
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
- .dioxus_config
- .web
- .watcher
- .reload_html
- .unwrap_or(false)
- {
- let _ = Serve::regen_dev_page(&watcher_conf);
+ if chrono::Local::now().timestamp() > last_update_time {
+ log::info!("Start to rebuild project...");
+ 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);
+ }
+ let _ = reload_tx.send("reload".into());
+ last_update_time = chrono::Local::now().timestamp();
}
- println!("watcher send reload");
- reload_tx.send("reload".into()).unwrap();
- last_update_time = chrono::Local::now().timestamp();
}
})
.unwrap();
diff --git a/src/tools.rs b/src/tools.rs
new file mode 100644
index 000000000..937063be0
--- /dev/null
+++ b/src/tools.rs
@@ -0,0 +1,187 @@
+use std::{
+ fs::{create_dir_all, File},
+ path::PathBuf,
+ process::Command,
+};
+
+use anyhow::Context;
+use flate2::read::GzDecoder;
+use futures::StreamExt;
+use tar::Archive;
+use tokio::io::AsyncWriteExt;
+
+#[derive(Debug, PartialEq, Eq)]
+pub enum Tool {
+ Binaryen,
+}
+
+pub fn tool_list() -> Vec<&'static str> {
+ vec!["binaryen"]
+}
+
+pub fn app_path() -> PathBuf {
+ let data_local = dirs::data_local_dir().unwrap();
+ let dioxus_dir = data_local.join("dioxus");
+ if !dioxus_dir.is_dir() {
+ create_dir_all(&dioxus_dir).unwrap();
+ }
+ dioxus_dir
+}
+
+pub fn temp_path() -> PathBuf {
+ let app_path = app_path();
+ let temp_path = app_path.join("temp");
+ if !temp_path.is_dir() {
+ create_dir_all(&temp_path).unwrap();
+ }
+ temp_path
+}
+
+pub fn tools_path() -> PathBuf {
+ let app_path = app_path();
+ let temp_path = app_path.join("tools");
+ if !temp_path.is_dir() {
+ create_dir_all(&temp_path).unwrap();
+ }
+ temp_path
+}
+
+#[allow(clippy::should_implement_trait)]
+impl Tool {
+ /// from str to tool enum
+ pub fn from_str(name: &str) -> Option {
+ match name {
+ "binaryen" => Some(Self::Binaryen),
+ _ => None,
+ }
+ }
+
+ /// get current tool name str
+ pub fn name(&self) -> &str {
+ match self {
+ Self::Binaryen => "binaryen",
+ }
+ }
+
+ /// get tool bin dir path
+ pub fn bin_path(&self) -> &str {
+ match self {
+ Self::Binaryen => "bin",
+ }
+ }
+
+ /// get target platform
+ pub fn target_platform(&self) -> &str {
+ match self {
+ Self::Binaryen => {
+ if cfg!(target_os = "windows") {
+ "windows"
+ } else if cfg!(target_os = "macos") {
+ "macos"
+ } else if cfg!(target_os = "linux") {
+ "linux"
+ } else {
+ panic!("unsupported platformm");
+ }
+ }
+ }
+ }
+
+ /// get tool package download url
+ pub fn download_url(&self) -> String {
+ match self {
+ Self::Binaryen => {
+ format!(
+ "https://github.com/WebAssembly/binaryen/releases/download/version_105/binaryen-version_105-x86_64-{target}.tar.gz",
+ target = self.target_platform()
+ )
+ }
+ }
+ }
+
+ /// get package extension name
+ pub fn extension(&self) -> &str {
+ match self {
+ Self::Binaryen => "tar.gz",
+ }
+ }
+
+ /// check tool state
+ pub fn is_installed(&self) -> bool {
+ tools_path().join(self.name()).is_dir()
+ }
+
+ /// get download temp path
+ pub fn temp_out_path(&self) -> PathBuf {
+ temp_path().join(format!("{}-tool.tmp", self.name()))
+ }
+
+ /// start to download package
+ pub async fn download_package(&self) -> anyhow::Result {
+ let download_url = self.download_url();
+ let temp_out = self.temp_out_path();
+ let mut file = tokio::fs::File::create(&temp_out)
+ .await
+ .context("failed creating temporary output file")?;
+
+ let resp = reqwest::get(download_url).await.unwrap();
+
+ let mut res_bytes = resp.bytes_stream();
+ while let Some(chunk_res) = res_bytes.next().await {
+ let chunk = chunk_res.context("error reading chunk from download")?;
+ let _ = file.write(chunk.as_ref()).await;
+ }
+
+ Ok(temp_out)
+ }
+
+ /// start to install package
+ pub async fn install_package(&self) -> anyhow::Result<()> {
+ let temp_path = self.temp_out_path();
+ let tool_path = tools_path();
+
+ let dir_name = if self == &Tool::Binaryen {
+ "binaryen-version_105"
+ } else {
+ ""
+ };
+
+ if self.extension() == "tar.gz" {
+ let tar_gz = File::open(temp_path)?;
+ 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()))?;
+ }
+
+ Ok(())
+ }
+
+ pub fn call(&self, command: &str, args: Vec<&str>) -> anyhow::Result> {
+ let bin_path = tools_path().join(self.name()).join(self.bin_path());
+
+ let command_file = match self {
+ Tool::Binaryen => {
+ if cfg!(target_os = "windows") {
+ format!("{}.exe", command)
+ } else {
+ command.to_string()
+ }
+ }
+ };
+
+ if !bin_path.join(&command_file).is_file() {
+ return Err(anyhow::anyhow!("Command file not found."));
+ }
+
+ let mut command = Command::new(bin_path.join(&command_file).to_str().unwrap());
+
+ let output = command
+ .args(&args[..])
+ .stdout(std::process::Stdio::inherit())
+ .stderr(std::process::Stdio::inherit())
+ .output()?;
+ Ok(output.stdout)
+ }
+}