From 9bb10c65a3cbabef127f6af5c0d1eadab1d01531 Mon Sep 17 00:00:00 2001 From: mrxiaozhuox Date: Sun, 23 Jan 2022 17:35:59 +0800 Subject: [PATCH] feat: add `Dioxus.toml` --- src/assets/autoreload.js | 25 +++++++++++++ src/assets/index.html | 18 +++++++++ src/builder.rs | 81 ++++++++++++++++++++++++++-------------- src/cli/build/mod.rs | 7 +--- src/cli/cfg.rs | 16 -------- src/cli/init/mod.rs | 27 ++++++++++---- src/cli/serve/mod.rs | 28 ++++++++++---- src/config.rs | 78 ++++++++++++++++++++++++++++++-------- src/server/err.html | 19 ---------- src/server/mod.rs | 54 ++++++++++++++++----------- src/server/serve.html | 39 ------------------- src/template/config.toml | 43 +++++++++++++++++---- src/template/default.rs | 4 +- 13 files changed, 269 insertions(+), 170 deletions(-) create mode 100644 src/assets/autoreload.js create mode 100644 src/assets/index.html delete mode 100644 src/server/err.html delete mode 100644 src/server/serve.html diff --git a/src/assets/autoreload.js b/src/assets/autoreload.js new file mode 100644 index 000000000..ff4444085 --- /dev/null +++ b/src/assets/autoreload.js @@ -0,0 +1,25 @@ +(function () { + var protocol = window.location.protocol === "https:" ? "wss:" : "ws:"; + + var url = protocol + "//" + window.location.host + "/_dioxus/ws"; + + var poll_interval = 2000; + + 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") { + window.location.reload(); + } + }; + + ws.onclose = reload_upon_connect; +})(); \ No newline at end of file diff --git a/src/assets/index.html b/src/assets/index.html new file mode 100644 index 000000000..12e1e5678 --- /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 58ee085fc..381ef8b28 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -1,11 +1,12 @@ use crate::{ config::{CrateConfig, ExecutableType}, error::{Error, Result}, + DioxusConfig, }; -use std::{io::Write, path::PathBuf, process::Command}; +use std::process::Command; use wasm_bindgen_cli_support::Bindgen; -pub fn build(config: &CrateConfig, dist: PathBuf) -> Result<()> { +pub fn build(config: &CrateConfig) -> Result<()> { /* [1] Build the project with cargo, generating a wasm32-unknown-unknown target (is there a more specific, better target to leverage?) [2] Generate the appropriate build folders @@ -15,15 +16,15 @@ pub fn build(config: &CrateConfig, dist: PathBuf) -> Result<()> { */ let CrateConfig { + out_dir, crate_dir, target_dir, static_dir, executable, + dioxus_config, .. } = config; - let out_dir = crate_dir.join(dist); - let t_start = std::time::Instant::now(); // [1] Build the .wasm module @@ -53,7 +54,7 @@ pub fn build(config: &CrateConfig, dist: PathBuf) -> Result<()> { 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("assets"); @@ -83,7 +84,7 @@ pub fn build(config: &CrateConfig, dist: PathBuf) -> Result<()> { .keep_debug(true) .remove_name_section(false) .remove_producers_section(false) - .out_name("module") + .out_name(&dioxus_config.application.name) .generate(&bindgen_outdir)?; let copy_options = fs_extra::dir::CopyOptions::new(); @@ -101,28 +102,52 @@ pub fn build(config: &CrateConfig, dist: PathBuf) -> Result<()> { Ok(()) } -pub fn gen_page(module: &str) -> String { - format!( - r#" - - - - - - -
-
- - -
- - -"#, - module - ) +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(vec![]); + let mut script_list = resouces.script.unwrap_or(vec![]); + + if serve { + let mut dev_style = resouces.dev.style.clone().unwrap_or(vec![]); + let mut dev_script = resouces.dev.script.clone().unwrap_or(vec![]); + 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("dioxus | β›Ί".into()); + + html.replace("{app_title}", &title) } // use binary_install::{Cache, Download}; diff --git a/src/cli/build/mod.rs b/src/cli/build/mod.rs index 760c2fd8e..fc329a305 100644 --- a/src/cli/build/mod.rs +++ b/src/cli/build/mod.rs @@ -1,5 +1,3 @@ -use std::path::PathBuf; - use crate::cfg::ConfigOptsBuild; use structopt::StructOpt; @@ -18,10 +16,7 @@ impl Build { // change the relase state. crate_config.with_release(self.build.release); - crate::builder::build( - &crate_config, - self.build.dist.unwrap_or(PathBuf::from("dist")), - )?; + crate::builder::build(&crate_config)?; Ok(()) } diff --git a/src/cli/cfg.rs b/src/cli/cfg.rs index 2c5f42a26..ab68ce4f9 100644 --- a/src/cli/cfg.rs +++ b/src/cli/cfg.rs @@ -16,14 +16,6 @@ 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, - - /// The public URL from which assets are to be served [default: /] - #[structopt(long, parse(from_str=parse_public_url))] - pub public_url: Option, - /// Optional pattern for the app loader script [default: None] /// /// Patterns should include the sequences `{base}`, `{wasm}`, and `{js}` in order to @@ -74,14 +66,6 @@ pub struct ConfigOptsServe { #[structopt(long)] #[serde(default)] pub release: bool, - - /// The output dir for all final assets [default: dist] - #[structopt(short, long, parse(from_os_str))] - pub dist: Option, - - /// The public URL from which assets are to be served [default: /] - #[structopt(long, parse(from_str=parse_public_url))] - pub public_url: Option, } /// Ensure the given value for `--public-url` is formatted correctly. diff --git a/src/cli/init/mod.rs b/src/cli/init/mod.rs index dde0ccca3..52dcf4c14 100644 --- a/src/cli/init/mod.rs +++ b/src/cli/init/mod.rs @@ -13,9 +13,9 @@ use crate::error::{Error, Result}; #[derive(Clone, Debug, StructOpt)] #[structopt(name = "init")] pub struct Init { - /// Init project path + /// Init project name #[structopt(default_value = ".")] - path: String, + name: String, /// Template path #[structopt(default_value = "default", long)] @@ -24,18 +24,23 @@ pub struct Init { impl Init { pub fn init(self) -> Result<()> { - log::info!("πŸ”§ Start to init a new project '{}'.", self.path); + if self.name.contains(".") { + log::error!("❗Unsupported project name."); + return Ok(()); + } - let project_path = PathBuf::from(&self.path); + log::info!("πŸ”§ Start to init a new project '{}'.", self.name); + + let project_path = PathBuf::from(&self.name); if project_path.join("Cargo.toml").is_file() { - log::warn!("Path '{}' is initialized.", self.path); + log::warn!("Folder '{}' is initialized.", self.name); return Ok(()); } let output = Command::new("cargo") .arg("init") - .arg(&self.path) + .arg(&format!("./{}", self.name)) .arg("--bin") .stdout(Stdio::piped()) .stderr(Stdio::piped()) @@ -58,6 +63,11 @@ impl Init { let mut file = File::create(main_rs_file)?; file.write_all(&template_str.as_bytes())?; + let mut file = File::create(project_path.join("Dioxus.toml"))?; + let dioxus_conf = String::from(include_str!("../../template/config.toml")) + .replace("{project-name}", &self.name); + file.write_all(dioxus_conf.as_bytes())?; + // log::info!("🎯 Project initialization completed."); if !Command::new("cargo") @@ -72,8 +82,9 @@ impl Init { file.write_all("dioxus = { version = \"0.1.7\", features = [\"web\"] }".as_bytes())?; } - log::info!("\nπŸ’‘ Project initialized:"); - log::info!("🎯> cd {}", self.path); + println!(""); + log::info!("πŸ’‘ Project initialized:"); + log::info!("🎯> cd ./{}", self.name); log::info!("🎯> dioxus serve"); Ok(()) diff --git a/src/cli/serve/mod.rs b/src/cli/serve/mod.rs index 586d49b7c..7d580f7fb 100644 --- a/src/cli/serve/mod.rs +++ b/src/cli/serve/mod.rs @@ -1,4 +1,4 @@ -use crate::{cfg::ConfigOptsServe, server}; +use crate::{cfg::ConfigOptsServe, gen_page, server}; use std::{io::Write, path::PathBuf}; use structopt::StructOpt; @@ -16,19 +16,31 @@ impl Serve { // change the relase state. crate_config.with_release(self.serve.release); - let dist_path = self.serve.dist.clone().unwrap_or(PathBuf::from("dist")); + crate::builder::build(&crate_config).expect("build failed"); - crate::builder::build(&crate_config, dist_path.clone()).expect("build failed"); + let serve_html = gen_page(&crate_config.dioxus_config, true); - let serve_html = String::from(include_str!("../../server/serve.html")); - - let mut file = - std::fs::File::create(crate_config.crate_dir.join(dist_path).join("index.html"))?; + let mut file = std::fs::File::create( + crate_config + .crate_dir + .join( + crate_config + .dioxus_config + .web + .app + .out_dir + .clone() + .unwrap_or(PathBuf::from("dist")), + ) + .join("index.html"), + )?; file.write_all(serve_html.as_bytes())?; // start the develop server - server::startup(crate_config.clone(), &self.serve).await?; + server::startup(crate_config.clone()).await?; Ok(()) } + + pub fn regen_page() {} } diff --git a/src/config.rs b/src/config.rs index 9d38160f2..39a3d2038 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,43 +1,76 @@ use crate::error::Result; use serde::{Deserialize, Serialize}; -use std::path::PathBuf; - +use std::{fs::File, io::Read, path::PathBuf}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DioxusConfig { - application: ApplicationConfig, - web: WebConfig, + 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() { + return Err(crate::error::Error::CargoError( + "Dioxus.toml not found".into(), + )); + } + + 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)?; + + match toml::from_str::(&meta_str) { + Ok(v) => return Ok(v), + Err(e) => { + log::error!("{}", e); + return Err(crate::error::Error::Unique( + "Dioxus.toml parse failed".into(), + )); + } + } + } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ApplicationConfig { - name: String, - platform: Vec, + pub name: String, + pub platforms: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct WebConfig { - app: WebAppConfing, - watcher: WebWatcherConfing, - resource: WebResourceConfing, + pub app: WebAppConfing, + pub watcher: WebWatcherConfing, + pub resource: WebResourceConfing, } - #[derive(Debug, Clone, Serialize, Deserialize)] pub struct WebAppConfing { - + pub title: Option, + pub out_dir: Option, + pub static_dir: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct WebWatcherConfing { - + pub watch_path: 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 { @@ -48,6 +81,7 @@ pub struct CrateConfig { pub static_dir: PathBuf, pub manifest: cargo_toml::Manifest, pub executable: ExecutableType, + pub dioxus_config: DioxusConfig, pub release: bool, } @@ -60,12 +94,23 @@ 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.web.app.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 static_dir = match dioxus_config.web.app.static_dir { + Some(ref v) => crate_dir.join(v), + None => crate_dir.join("static"), + }; let manifest = cargo_toml::Manifest::from_path(&cargo_def).unwrap(); @@ -96,6 +141,7 @@ impl CrateConfig { manifest, executable, release, + dioxus_config, }) } @@ -124,4 +170,4 @@ impl CrateConfig { // self.release = options.release; // self.out_dir = tempfile::Builder::new().tempdir().expect("").into_path(); // } -} \ No newline at end of file +} diff --git a/src/server/err.html b/src/server/err.html deleted file mode 100644 index 8fdbf9bae..000000000 --- a/src/server/err.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - -
-

- Sorry, but building your application failed. -

-

- Here's the error: -

- - {err} - -
- - - diff --git a/src/server/mod.rs b/src/server/mod.rs index 6b91a2e71..4d33f524e 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -13,7 +13,7 @@ use std::{ }; use tower_http::services::ServeDir; -use crate::{builder, CrateConfig}; +use crate::{builder, crate_root, CrateConfig}; struct WsRelodState { update: bool, @@ -25,42 +25,54 @@ impl WsRelodState { } } -pub async fn startup( - config: CrateConfig, - opts: &crate::cfg::ConfigOptsServe, -) -> anyhow::Result<()> { +pub async fn startup(config: CrateConfig) -> anyhow::Result<()> { log::info!("πŸš€ Starting development server..."); let (tx, rx) = channel(); - let dist_path = opts.dist.clone().unwrap_or(PathBuf::from("dist")); + let dist_path = config.out_dir.clone(); // file watcher: check file change - let mut watcher = watcher(tx, Duration::from_secs(2)).unwrap(); + let mut watcher = watcher(tx, Duration::from_secs(1)).unwrap(); watcher - .watch( - config.crate_dir.join("src").clone(), - notify::RecursiveMode::Recursive, - ) + .watch(config.crate_dir.clone(), notify::RecursiveMode::Recursive) .unwrap(); let ws_reload_state = Arc::new(Mutex::new(WsRelodState { update: false })); let watcher_conf = config.clone(); let watcher_ws_state = ws_reload_state.clone(); - let watcher_dist_path = dist_path.clone(); tokio::spawn(async move { + let allow_watch_path = watcher_conf + .dioxus_config + .web + .watcher + .watch_path + .clone() + .unwrap_or(vec![PathBuf::from("src")]); + let crate_dir = watcher_conf.crate_dir.clone(); loop { if let Ok(v) = rx.recv() { match v { - DebouncedEvent::Create(_) - | DebouncedEvent::Write(_) - | DebouncedEvent::Remove(_) - | DebouncedEvent::Rename(_, _) => { - if let Ok(_) = builder::build(&watcher_conf, watcher_dist_path.clone()) { - // change the websocket reload state to true; - // the page will auto-reload. - watcher_ws_state.lock().unwrap().change(); + DebouncedEvent::Create(e) + | DebouncedEvent::Write(e) + | DebouncedEvent::Remove(e) + | DebouncedEvent::Rename(e, _) => { + let mut reload = false; + for path in &allow_watch_path { + let temp = crate_dir.clone().join(path); + if e.starts_with(temp) { + reload = true; + break; + } + } + + if reload { + if let Ok(_) = builder::build(&watcher_conf) { + // change the websocket reload state to true; + // the page will auto-reload. + watcher_ws_state.lock().unwrap().change(); + } } } _ => {} @@ -70,7 +82,7 @@ pub async fn startup( }); let app = Router::new() - .route("/ws", get(ws_handler)) + .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 { diff --git a/src/server/serve.html b/src/server/serve.html deleted file mode 100644 index b5268bd68..000000000 --- a/src/server/serve.html +++ /dev/null @@ -1,39 +0,0 @@ - - - - - Dioxus-CLI Dev Server - - -
- - - - - - - diff --git a/src/template/config.toml b/src/template/config.toml index 9644babc1..ea1efbd4b 100644 --- a/src/template/config.toml +++ b/src/template/config.toml @@ -1,9 +1,38 @@ -[package] -name = "{project}" -version = "0.1.0" -edition = "2021" +[application] -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +# App (Project) Name +name = "{project-name}" -[dependencies] -dioxus = { version = "0.1.7", features = ["web"] } \ No newline at end of file +# Dioxus App Support Platform +# desktop, web, mobile, ssr +platforms = ["web"] + +[web.app] + +# HTML title tag content +title = "dioxus | β›Ί" + +# Web `build` & `serve` dist path +out_dir = "dist" + +# resource (static) file folder +static_dir = "static" + +[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/template/default.rs b/src/template/default.rs index ac1614fc4..47b4a2c1d 100644 --- a/src/template/default.rs +++ b/src/template/default.rs @@ -8,8 +8,8 @@ fn app(cx: Scope) -> Element { cx.render(rsx! ( div { style: "text-align: center;", - h2 { "πŸŒ—πŸš€ Dioxus" } - p { strong { "Frontend that scales." } } + h1 { "πŸŒ— Dioxus πŸš€" } + h3 { "Frontend that scales." } p { "Dioxus is a portable, performant, and ergonomic framework for building cross-platform user interfaces in Rust." } } ))