diff --git a/Cargo.toml b/Cargo.toml
index 455048c36..5bbfd7ac2 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"
@@ -12,23 +12,29 @@ 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"
+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"
-tui = { version = "0.16.0", default-features = false, features = ["crossterm"] }
-crossterm = "0.22.1"
binary-install = "0.0.2"
convert_case = "0.5.0"
structopt = "0.3.25"
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"] }
+headers = "0.3"
+# hyper = { version = "0.14.11", features = ["full"] }
[[bin]]
path = "src/main.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/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/assets/autoreload.js b/src/assets/autoreload.js
new file mode 100644
index 000000000..da62e5eae
--- /dev/null
+++ b/src/assets/autoreload.js
@@ -0,0 +1,23 @@
+(function () {
+ 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.onmessage = (ev) => {
+ if (ev.data == "reload") {
+ // alert("reload!!!");
+ window.location.reload();
+ }
+ };
+ ws.onclose = reload_upon_connect;
+})()
\ No newline at end of file
diff --git a/src/assets/dioxus.toml b/src/assets/dioxus.toml
new file mode 100644
index 000000000..717b070e2
--- /dev/null
+++ b/src/assets/dioxus.toml
@@ -0,0 +1,38 @@
+[application]
+
+# App (Project) Name
+name = "{{project-name}}"
+
+# Dioxus App Support Platform
+# desktop, web, mobile, ssr
+platforms = ["web"]
+
+# Web `build` & `serve` dist path
+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 = []
+
+# 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/assets/index.html b/src/assets/index.html
new file mode 100644
index 000000000..9934e490d
--- /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 b80c3dd36..257072a6a 100644
--- a/src/builder.rs
+++ b/src/builder.rs
@@ -1,9 +1,11 @@
use crate::{
config::{CrateConfig, ExecutableType},
error::{Error, Result},
+ DioxusConfig,
};
use std::{
- io::{Read, Write},
+ fs::{copy, create_dir_all, remove_dir_all},
+ path::PathBuf,
process::Command,
};
use wasm_bindgen_cli_support::Bindgen;
@@ -21,22 +23,23 @@ pub fn build(config: &CrateConfig) -> Result<()> {
out_dir,
crate_dir,
target_dir,
- static_dir,
+ public_dir,
executable,
+ dioxus_config,
..
} = config;
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")
.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,20 +51,16 @@ pub fn build(config: &CrateConfig) -> Result<()> {
ExecutableType::Example(name) => cmd.arg("--example").arg(name),
};
- let mut child = cmd.spawn()?;
- let output = child.wait()?;
+ let output = cmd.output()?;
- if output.success() {
- log::info!("Build complete!");
- } else {
+ if !output.status.success() {
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));
}
// [2] Establish the output directory structure
- let bindgen_outdir = out_dir.join("wasm");
+ let bindgen_outdir = out_dir.join("assets").join("dioxus");
// [3] Bindgen the final binary for use easy linking
let mut bindgen_builder = Bindgen::new();
@@ -89,53 +88,168 @@ pub fn build(config: &CrateConfig) -> Result<()> {
.keep_debug(true)
.remove_name_section(false)
.remove_producers_section(false)
- .out_name("module")
+ .out_name(&dioxus_config.application.name)
.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 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();
- match fs_extra::dir::copy(static_dir, out_dir, ©_options) {
- Ok(_) => {}
- Err(_e) => {
- log::warn!("Error copying dir");
+ 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);
+ }
+ }
+ }
}
}
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(())
}
-fn gen_page(module: &str) -> String {
- format!(
- r#"
-
-
-
-
-
-
-
-
-
-
-
-
-
-"#,
- module
- )
+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() {
+ return Err(Error::BuildFailed("Program build failed.".into()));
+ }
+
+ 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(release_type.to_string())
+ .join(name.to_string())
+ }
+ crate::ExecutableType::Example(name) => {
+ file_name = name.clone();
+ config
+ .target_dir
+ .join(release_type.to_string())
+ .join("examples")
+ .join(name.to_string())
+ }
+ };
+
+ let target_file = if cfg!(windows) {
+ res_path.set_extension("exe");
+ format!("{}.exe", &file_name)
+ } else {
+ file_name
+ };
+
+ 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_else(|| PathBuf::from("dist"))
+ .display()
+ );
+ }
+
+ Ok(())
+}
+
+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_default();
+ let mut script_list = resouces.script.unwrap_or_default();
+
+ if serve {
+ 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);
+ }
+
+ 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_else(|| "dioxus | βΊ".into());
+
+ html.replace("{app_title}", &title)
}
// use binary_install::{Cache, Download};
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/cargo.rs b/src/cargo.rs
index 3a911a81e..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, 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;
@@ -44,7 +49,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 +63,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 33a81a540..684cec58c 100644
--- a/src/cli/build/mod.rs
+++ b/src/cli/build/mod.rs
@@ -1,6 +1,6 @@
-use crate::cfg::ConfigOptsBuild;
-use anyhow::Result;
-use std::path::PathBuf;
+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.
@@ -11,4 +11,46 @@ 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);
+
+ if self.build.example.is_some() {
+ crate_config.as_example(self.build.example.unwrap());
+ }
+
+ 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."));
+ }
+ }
+
+ 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
+ .application
+ .out_dir
+ .clone()
+ .unwrap_or_else(|| PathBuf::from("dist")),
+ )
+ .join("index.html"),
+ )?;
+ file.write_all(temp.as_bytes())?;
+
+ Ok(())
+ }
+}
diff --git a/src/cli/cfg.rs b/src/cli/cfg.rs
index feab0e61f..ece00e07e 100644
--- a/src/cli/cfg.rs
+++ b/src/cli/cfg.rs
@@ -1,11 +1,7 @@
-use anyhow::Result;
use std::path::PathBuf;
use structopt::StructOpt;
-use serde::{Deserialize, Serialize};
-use std::collections::HashMap;
-use std::str::FromStr;
-use std::sync::Arc;
+use serde::Deserialize;
/// Config options for the build system.
#[derive(Clone, Debug, Default, Deserialize, StructOpt)]
@@ -19,52 +15,33 @@ 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,
+ /// Build a example [default: ""]
+ #[structopt(long)]
+ pub example: Option,
- /// The public URL from which assets are to be served [default: /]
- #[structopt(long, parse(from_str=parse_public_url))]
- pub public_url: Option,
+ /// Build platform: support Web & Desktop [default: "web"]
+ #[structopt(long, default_value = "web")]
+ pub platform: String,
+}
- /// 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)]
+#[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 a example [default: ""]
+ #[structopt(long)]
+ pub example: Option,
+
+ /// Build in release mode [default: false]
+ #[structopt(long)]
#[serde(default)]
- pub pattern_script: Option,
+ pub release: bool,
- /// 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, default_value = "web")]
+ pub platform: String,
}
/// Ensure the given value for `--public-url` is formatted correctly.
diff --git a/src/cli/clean/mod.rs b/src/cli/clean/mod.rs
index aeeedf523..62593b1ee 100644
--- a/src/cli/clean/mod.rs
+++ b/src/cli/clean/mod.rs
@@ -1,9 +1,40 @@
-use crate::cfg::ConfigOptsBuild;
-use anyhow::Result;
-use std::path::PathBuf;
+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
+ .application
+ .out_dir
+ .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))?;
+ }
+
+ Ok(())
+ }
+}
diff --git a/src/cli/config/mod.rs b/src/cli/config/mod.rs
index 616c9bc78..d5291a7d6 100644
--- a/src/cli/config/mod.rs
+++ b/src/cli/config/mod.rs
@@ -1,9 +1,43 @@
-use crate::cfg::ConfigOptsBuild;
-use anyhow::Result;
-use std::path::PathBuf;
+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, StructOpt)]
+#[derive(Clone, Debug, Deserialize, StructOpt)]
#[structopt(name = "config")]
-pub struct 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 {
+ 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
new file mode 100644
index 000000000..fa553c55a
--- /dev/null
+++ b/src/cli/create/mod.rs
@@ -0,0 +1,98 @@
+use std::{
+ fs::File,
+ io::{Read, Write},
+ path::PathBuf,
+ process::{Command, Stdio},
+};
+
+use regex::Regex;
+use serde::Deserialize;
+use structopt::StructOpt;
+
+use crate::{error::Result, Error};
+
+/// Build the Rust WASM app and all of its assets.
+#[derive(Clone, Debug, Default, Deserialize, StructOpt)]
+#[structopt(name = "create")]
+pub struct Create {
+ /// Init project name
+ #[structopt(default_value = ".")]
+ name: String,
+
+ /// Template path
+ #[structopt(default_value = "gh:dioxuslabs/dioxus-template", long)]
+ template: String,
+}
+
+impl Create {
+ pub fn create(self) -> Result<()> {
+ if Self::name_vaild_check(self.name.clone()) {
+ log::error!("βUnsupported project name.");
+ return Ok(());
+ }
+
+ let project_path = PathBuf::from(&self.name);
+
+ 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 create a new project '{}'.", self.name);
+
+ let output = Command::new("cargo")
+ .arg("generate")
+ .arg("--help")
+ .stdout(Stdio::piped())
+ .stderr(Stdio::piped())
+ .output()?;
+
+ if !output.status.success() {
+ 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."
+ )));
+ }
+ }
+
+ let generate_output = Command::new("cargo")
+ .arg("generate")
+ .arg(&self.template)
+ .arg("--name")
+ .arg(&self.name)
+ .stdout(Stdio::piped())
+ .stderr(Stdio::inherit())
+ .output()?;
+
+ if !generate_output.status.success() {
+ return Err(Error::Other(anyhow::anyhow!("Generate project failed.")));
+ }
+
+ 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:");
+ log::info!("π―> cd ./{}", self.name);
+ log::info!("π―> dioxus serve");
+
+ 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/mod.rs b/src/cli/mod.rs
index fe118cbb7..4a72e00ac 100644
--- a/src/cli/mod.rs
+++ b/src/cli/mod.rs
@@ -1,14 +1,12 @@
-use anyhow::{Context, Result};
-use std::path::PathBuf;
use structopt::StructOpt;
pub mod build;
pub mod cfg;
pub mod clean;
pub mod config;
+pub mod create;
pub mod serve;
pub mod translate;
-pub mod watch;
/// Build, bundle, & ship your Dioxus app.
///
@@ -31,16 +29,16 @@ pub struct Cli {
#[derive(StructOpt)]
pub enum Commands {
- // /// Build the Rust WASM app and all of its assets.
- // Build(build::Build),
+ /// 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.
- // Serve(serve::Serve),
-
- // /// Clean output artifacts.
- // Clean(clean::Clean),
-
- // /// Trunk config controls.
- // Config(config::Config),
+ /// Build, watch & serve the Rust WASM app and all of its assets.
+ Serve(serve::Serve),
+ /// Init a new project for Dioxus.
+ Create(create::Create),
+ /// Clean output artifacts.
+ Clean(clean::Clean),
+ /// Dioxus config file controls.
+ Config(config::Config),
}
diff --git a/src/cli/serve/develop.rs b/src/cli/serve/develop.rs
deleted file mode 100644
index dd386b14e..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/draw.rs b/src/cli/serve/draw.rs
deleted file mode 100644
index e69de29bb..000000000
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 9695870c0..8f26e0f30 100644
--- a/src/cli/serve/mod.rs
+++ b/src/cli/serve/mod.rs
@@ -1,9 +1,87 @@
-use crate::cfg::ConfigOptsBuild;
-use anyhow::Result;
-use std::path::PathBuf;
+use crate::{cfg::ConfigOptsServe, gen_page, server, CrateConfig};
+use std::{io::Write, path::PathBuf, process::{Command, Stdio}};
use structopt::StructOpt;
-/// Build the Rust WASM app and all of its assets.
+/// 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);
+
+ if self.serve.example.is_some() {
+ crate_config.as_example(self.serve.example.unwrap());
+ }
+
+ match self.serve.platform.as_str() {
+ "web" => {
+ crate::builder::build(&crate_config)?;
+ }
+ "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(),
+ )
+ .stdout(Stdio::inherit())
+ .output()?;
+ }
+ }
+ return Ok(());
+ }
+ _ => {
+ return Err(anyhow::anyhow!("Unsoppurt platform target."));
+ }
+ }
+
+ // 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(
+ crate_config
+ .crate_dir
+ .join(
+ crate_config
+ .dioxus_config
+ .application
+ .out_dir
+ .clone()
+ .unwrap_or_else(|| PathBuf::from("dist")),
+ )
+ .join("index.html"),
+ )?;
+ file.write_all(serve_html.as_bytes())?;
+
+ Ok(())
+ }
+}
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/translate/mod.rs b/src/cli/translate/mod.rs
index 5799cad02..81b5092ed 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 let Some(..) = temp {
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 b08161e9c..c83777ccb 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,
@@ -20,6 +21,7 @@ pub fn convert_html_to_component(html: &str) -> Result {
})
}
+#[allow(dead_code)]
pub struct ComponentRenderer {
dom: Dom,
icon_index: usize,
@@ -118,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/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/config.rs b/src/config.rs
index 47920c583..1151445ef 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -1,5 +1,99 @@
use crate::error::Result;
-use std::{io::Write, path::PathBuf, process::Command};
+use serde::{Deserialize, Serialize};
+use std::{fs::File, io::Read, path::PathBuf};
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct DioxusConfig {
+ 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() {
+ 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"))?;
+ let mut meta_str = String::new();
+ dioxus_conf_file.read_to_string(&mut meta_str)?;
+
+ toml::from_str::(&meta_str)
+ .map_err(|_| crate::Error::Unique("Dioxus.toml parse failed".into()))
+ }
+}
+
+impl Default for DioxusConfig {
+ fn default() -> Self {
+ Self {
+ application: ApplicationConfig {
+ name: "dioxus".into(),
+ platforms: vec![String::from("web")],
+ out_dir: Some(PathBuf::from("dist")),
+ },
+ web: WebConfig {
+ app: WebAppConfing {
+ title: Some("dioxus | βΊ".into()),
+ 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,
+ pub platforms: Vec,
+ pub out_dir: Option,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct WebConfig {
+ pub app: WebAppConfing,
+ pub watcher: WebWatcherConfing,
+ pub resource: WebResourceConfing,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct WebAppConfing {
+ pub title: 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)]
+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 {
@@ -7,9 +101,10 @@ 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,
pub release: bool,
}
@@ -22,28 +117,33 @@ 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.application.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 public_dir = match dioxus_config.web.app.public_dir {
+ Some(ref v) => crate_dir.join(v),
+ None => crate_dir.join("public"),
+ };
let manifest = cargo_toml::Manifest::from_path(&cargo_def).unwrap();
// 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)
- .package
- .as_ref()
- .and_then(|pkg| Some(pkg.name.replace("-", "_")))
- .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);
@@ -54,10 +154,11 @@ impl CrateConfig {
crate_dir,
workspace_dir,
target_dir,
- static_dir,
+ public_dir,
manifest,
executable,
release,
+ dioxus_config,
})
}
diff --git a/src/err.html b/src/err.html
deleted file mode 100644
index 8fdbf9bae..000000000
--- a/src/err.html
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
-
-
-
- Sorry, but building your application failed.
-
-
- Here's the error:
-
-
- {err}
-
-
-
-
-
diff --git a/src/lib.rs b/src/lib.rs
index 4cc4f2d75..395cbc8dc 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;
@@ -15,13 +16,3 @@ 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::*;
-}
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| {
diff --git a/src/main.rs b/src/main.rs
index 798c492dc..6b8c177db 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -8,22 +8,40 @@ async fn main() -> Result<()> {
match args.action {
Commands::Translate(opts) => {
- opts.translate();
- } // Commands::Build(_) => {
- // //
- // }
+ if let Err(e) = opts.translate() {
+ log::error!("translate error: {}", e);
+ }
+ }
- // Commands::Clean(_) => {
- // //
- // }
+ Commands::Build(opts) => {
+ if let Err(e) = opts.build() {
+ log::error!("build error: {}", e);
+ }
+ }
- // Commands::Config(_) => {
- // //
- // }
+ Commands::Clean(opts) => {
+ if let Err(e) = opts.clean() {
+ log::error!("clean error: {}", e);
+ }
+ }
- // Commands::Serve(_) => {
- // //
- // }
+ Commands::Serve(opts) => {
+ if let Err(e) = opts.serve().await {
+ log::error!("serve error: {}", e);
+ }
+ }
+
+ Commands::Create(opts) => {
+ if let Err(e) = opts.create() {
+ log::error!("create error: {}", e);
+ }
+ }
+
+ Commands::Config(opts) => {
+ if let Err(e) = opts.config() {
+ log::error!("config error: {}", e);
+ }
+ }
}
Ok(())
diff --git a/src/server/mod.rs b/src/server/mod.rs
new file mode 100644
index 000000000..880f25ceb
--- /dev/null
+++ b/src/server/mod.rs
@@ -0,0 +1,124 @@
+use axum::{
+ extract::{ws::Message, Extension, TypedHeader, WebSocketUpgrade},
+ http::StatusCode,
+ response::IntoResponse,
+ routing::{get, get_service},
+ AddExtensionLayer, Router,
+};
+use notify::{RecommendedWatcher, Watcher};
+
+use std::{path::PathBuf, sync::Arc};
+use tower_http::services::ServeDir;
+
+use crate::{builder, serve::Serve, CrateConfig};
+use tokio::sync::broadcast;
+
+struct WsRelodState {
+ update: broadcast::Sender,
+}
+
+pub async fn startup(config: CrateConfig) -> anyhow::Result<()> {
+ log::info!("π Starting development server...");
+
+ let dist_path = config.out_dir.clone();
+
+ let (reload_tx, _) = broadcast::channel(100);
+
+ let ws_reload_state = Arc::new(WsRelodState {
+ 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 |_: 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);
+ }
+ println!("watcher send reload");
+ reload_tx.send("reload".into()).unwrap();
+ last_update_time = chrono::Local::now().timestamp();
+ }
+ })
+ .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();
+ }
+
+ // 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(
+ 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(())
+}
+
+async fn ws_handler(
+ ws: WebSocketUpgrade,
+ _: Option>,
+ Extension(state): Extension>,
+) -> impl IntoResponse {
+ ws.on_upgrade(|mut socket| async move {
+ let mut rx = state.update.subscribe();
+ let reload_watcher = tokio::spawn(async move {
+ loop {
+ let v = rx.recv().await.unwrap();
+ if v == "reload" {
+ // ignore the error
+ if socket
+ .send(Message::Text(String::from("reload")))
+ .await
+ .is_err()
+ {
+ break;
+ }
+ }
+ }
+ });
+
+ reload_watcher.await.unwrap();
+ })
+}
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 @@
-