Browse Source

feat: add `Dioxus.toml`

mrxiaozhuox 3 năm trước cách đây
mục cha
commit
9bb10c65a3

+ 25 - 0
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;
+})();

+ 18 - 0
src/assets/index.html

@@ -0,0 +1,18 @@
+<html>
+  <head>
+    <title>{app_title}</title>
+    <meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
+    <meta charset="UTF-8" />
+    {style_include}
+    
+  </head>
+  <body>
+    <div id="main"></div>
+    <!-- Note the usage of `type=module` here as this is an ES6 module -->
+    <script type="module">
+      import init from "./assets/{app_name}.js";
+      init("./assets/{app_name}_bg.wasm");
+    </script>
+    {script_include}
+  </body>
+</html>

+ 53 - 28
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#"
-<html>
-  <head>
-    <meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
-    <meta charset="UTF-8" />
-  </head>
-  <body>
-    <div id="main">
-    </div>
-    <!-- Note the usage of `type=module` here as this is an ES6 module -->
-    <script type="module">
-      import init from "{}";
-      init("./assets/module_bg.wasm");
-    </script>
-    <div id="dioxusroot"> </div>
-  </body>
-</html>
-"#,
-        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!(
+            "<link rel=\"stylesheet\" href=\"{}\">\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!(
+            "<script src=\"{}\"></script>\n",
+            &script.to_str().unwrap(),
+        ))
+    }
+
+    html = html.replace("{script_include}", &script_str);
+
+    if serve {
+        html += &format!(
+            "<script>{}</script>",
+            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};

+ 1 - 6
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(())
     }

+ 0 - 16
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<PathBuf>,
-
-    /// The public URL from which assets are to be served [default: /]
-    #[structopt(long, parse(from_str=parse_public_url))]
-    pub public_url: Option<String>,
-
     /// 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<PathBuf>,
-
-    /// The public URL from which assets are to be served [default: /]
-    #[structopt(long, parse(from_str=parse_public_url))]
-    pub public_url: Option<String>,
 }
 
 /// Ensure the given value for `--public-url` is formatted correctly.

+ 19 - 8
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(())

+ 22 - 10
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, dist_path.clone()).expect("build failed");
-
-        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"))?;
+        crate::builder::build(&crate_config).expect("build failed");
+
+        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
+                        .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() {}
 }

+ 62 - 16
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<DioxusConfig> {
+        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::<DioxusConfig>(&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<String>,
+    pub name: String,
+    pub platforms: Vec<String>,
 }
 
 #[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<String>,
+    pub out_dir: Option<PathBuf>,
+    pub static_dir: Option<PathBuf>,
 }
 
 #[derive(Debug, Clone, Serialize, Deserialize)]
 pub struct WebWatcherConfing {
-    
+    pub watch_path: Option<Vec<PathBuf>>,
 }
 
 #[derive(Debug, Clone, Serialize, Deserialize)]
 pub struct WebResourceConfing {
-    
+    pub dev: WebDevResourceConfing,
+    pub style: Option<Vec<PathBuf>>,
+    pub script: Option<Vec<PathBuf>>,
 }
 
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct WebDevResourceConfing {
+    pub style: Option<Vec<PathBuf>>,
+    pub script: Option<Vec<PathBuf>>,
+}
 
 #[derive(Debug, Clone)]
 pub struct CrateConfig {
@@ -48,6 +81,7 @@ pub struct CrateConfig {
     pub static_dir: PathBuf,
     pub manifest: cargo_toml::Manifest<cargo_toml::Value>,
     pub executable: ExecutableType,
+    pub dioxus_config: DioxusConfig,
     pub release: bool,
 }
 
@@ -60,12 +94,23 @@ pub enum ExecutableType {
 
 impl CrateConfig {
     pub fn new() -> Result<Self> {
+        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();
     // }
-}
+}

+ 0 - 19
src/server/err.html

@@ -1,19 +0,0 @@
-<html>
-
-<head></head>
-
-<body>
-    <div>
-        <h1>
-            Sorry, but building your application failed.
-        </h1>
-        <p>
-            Here's the error:
-        </p>
-        <code>
-            {err}
-        </code>
-    </div>
-</body>
-
-</html>

+ 33 - 21
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 {

+ 0 - 39
src/server/serve.html

@@ -1,39 +0,0 @@
-<html>
-  <head>
-    <meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
-    <meta charset="UTF-8" />
-    <title>Dioxus-CLI Dev Server</title>
-  </head>
-  <body>
-    <div id="main"></div>
-
-    <!-- Note the usage of `type=module` here as this is an ES6 module -->
-    <script type="module">
-      import init from "./assets/module.js";
-      init("./assets/module_bg.wasm");
-    </script>
-
-    <script>
-      (function () {
-        var protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
-        var url = protocol + "//" + window.location.host + "/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;
-      })();
-    </script>
-  </body>
-</html>

+ 36 - 7
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"] }
+# 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 = []

+ 2 - 2
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." }
         }
     ))