Prechádzať zdrojové kódy

Merge branch 'master' into breaking

Jonathan Kelley 1 rok pred
rodič
commit
eff1dd6c90

+ 71 - 58
Cargo.lock

@@ -605,7 +605,7 @@ version = "0.5.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "41e7771d4ab6635cbd685ce8db215b29c78a468098126de77c57f3b2e6eb3757"
 dependencies = [
- "dirs 5.0.1",
+ "dirs",
  "git2",
  "terminal-prompt",
 ]
@@ -1075,7 +1075,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bf4918709cc4dd777ad2b6303ed03cb37f3ca0ccede8c1b0d28ac6db8f4710e0"
 dependencies = [
  "once_cell",
- "proc-macro-crate 2.0.1",
+ "proc-macro-crate 2.0.0",
  "proc-macro2",
  "quote",
  "syn 2.0.48",
@@ -1250,9 +1250,9 @@ dependencies = [
 
 [[package]]
 name = "cargo-generate"
-version = "0.18.5"
+version = "0.19.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8a2885ae054e000b117515ab33e91c10eca90c2788a7baec1b97ada1f1f51e57"
+checksum = "92c1b6f44358912a9538fa3b6ac8d3aa3f585444f9dc32f12ed85d1545a9df9f"
 dependencies = [
  "anyhow",
  "auth-git2",
@@ -1284,7 +1284,7 @@ dependencies = [
  "serde",
  "tempfile",
  "thiserror",
- "toml 0.8.2",
+ "toml 0.8.8",
  "walkdir",
 ]
 
@@ -1350,6 +1350,20 @@ dependencies = [
  "thiserror",
 ]
 
+[[package]]
+name = "cargo_metadata"
+version = "0.18.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037"
+dependencies = [
+ "camino",
+ "cargo-platform",
+ "semver",
+ "serde",
+ "serde_json",
+ "thiserror",
+]
+
 [[package]]
 name = "cargo_toml"
 version = "0.16.3"
@@ -1357,7 +1371,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e3f9629bc6c4388ea699781dc988c2b99766d7679b151c81990b4fa1208fafd3"
 dependencies = [
  "serde",
- "toml 0.8.2",
+ "toml 0.8.8",
+]
+
+[[package]]
+name = "cargo_toml"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "802b755090e39835a4b0440fb0bbee0df7495a8b337f63db21e616f7821c7e8c"
+dependencies = [
+ "serde",
+ "toml 0.8.8",
 ]
 
 [[package]]
@@ -2370,8 +2394,8 @@ dependencies = [
  "axum 0.5.17",
  "axum-server",
  "cargo-generate",
- "cargo_metadata 0.15.4",
- "cargo_toml",
+ "cargo_metadata 0.18.1",
+ "cargo_toml 0.18.0",
  "chrono",
  "clap 4.4.18",
  "colored 2.1.0",
@@ -2383,11 +2407,11 @@ dependencies = [
  "dioxus-hot-reload",
  "dioxus-html",
  "dioxus-rsx",
- "dirs 4.0.0",
+ "dirs",
  "fern",
  "flate2",
  "fs_extra",
- "futures",
+ "futures-util",
  "gitignore",
  "headers 0.3.9",
  "html_parser",
@@ -2406,14 +2430,13 @@ dependencies = [
  "serde",
  "serde_json",
  "subprocess",
- "syn 2.0.48",
  "tar",
  "tauri-bundler",
- "tauri-utils",
  "tempfile",
  "thiserror",
  "tokio",
- "toml_edit 0.19.15",
+ "toml 0.8.8",
+ "toml_edit 0.21.0",
  "tower",
  "tower-http 0.2.5",
  "walkdir",
@@ -2425,7 +2448,7 @@ dependencies = [
 name = "dioxus-cli-config"
 version = "0.4.1"
 dependencies = [
- "cargo_toml",
+ "cargo_toml 0.16.3",
  "clap 4.4.18",
  "once_cell",
  "serde",
@@ -2962,22 +2985,13 @@ dependencies = [
  "syn 2.0.48",
 ]
 
-[[package]]
-name = "dirs"
-version = "4.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059"
-dependencies = [
- "dirs-sys 0.3.7",
-]
-
 [[package]]
 name = "dirs"
 version = "5.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
 dependencies = [
- "dirs-sys 0.4.1",
+ "dirs-sys",
 ]
 
 [[package]]
@@ -2990,17 +3004,6 @@ dependencies = [
  "dirs-sys-next",
 ]
 
-[[package]]
-name = "dirs-sys"
-version = "0.3.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
-dependencies = [
- "libc",
- "redox_users",
- "winapi",
-]
-
 [[package]]
 name = "dirs-sys"
 version = "0.4.1"
@@ -3372,7 +3375,7 @@ dependencies = [
  "atomic 0.6.0",
  "pear",
  "serde",
- "toml 0.8.2",
+ "toml 0.8.8",
  "uncased",
  "version_check",
 ]
@@ -3943,9 +3946,9 @@ dependencies = [
 
 [[package]]
 name = "gix-config"
-version = "0.31.0"
+version = "0.32.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5cae98c6b4c66c09379bc35274b172587d6b0ac369a416c39128ad8c6454f9bb"
+checksum = "0341471d55d8676e98b88e121d7065dfa4c9c5acea4b6d6ecdd2846e85cce0c3"
 dependencies = [
  "bstr 1.9.0",
  "gix-config-value",
@@ -4045,9 +4048,9 @@ dependencies = [
 
 [[package]]
 name = "gix-object"
-version = "0.38.0"
+version = "0.39.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "740f2a44267f58770a1cb3a3d01d14e67b089c7136c48d4bddbb3cfd2bf86a51"
+checksum = "febf79c5825720c1c63fe974c7bbe695d0cb54aabad73f45671c60ce0e501e33"
 dependencies = [
  "bstr 1.9.0",
  "btoi",
@@ -4077,9 +4080,9 @@ dependencies = [
 
 [[package]]
 name = "gix-ref"
-version = "0.38.0"
+version = "0.39.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ec2f6d07ac88d2fb8007ee3fa3e801856fb9d82e7366ec0ca332eb2c9d74a52"
+checksum = "3b2069adc212cf7f3317ef55f6444abd06c50f28479dbbac5a86acf3b05cbbfe"
 dependencies = [
  "gix-actor",
  "gix-date",
@@ -4176,7 +4179,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc"
 dependencies = [
  "heck 0.4.1",
- "proc-macro-crate 2.0.1",
+ "proc-macro-crate 2.0.0",
  "proc-macro-error",
  "proc-macro2",
  "quote",
@@ -5892,7 +5895,7 @@ dependencies = [
  "anyhow",
  "cargo-lock 9.0.0",
  "cargo_metadata 0.17.0",
- "cargo_toml",
+ "cargo_toml 0.16.3",
  "image",
  "imagequant",
  "lightningcss",
@@ -6019,9 +6022,9 @@ checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
 
 [[package]]
 name = "memmap2"
-version = "0.7.1"
+version = "0.9.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f49388d20533534cd19360ad3d6a7dadc885944aa802ba3995040c5ec11288c6"
+checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322"
 dependencies = [
  "libc",
 ]
@@ -6605,9 +6608,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
 
 [[package]]
 name = "open"
-version = "4.2.0"
+version = "5.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3a083c0c7e5e4a8ec4176346cf61f67ac674e8bfb059d9226e1c54a96b377c12"
+checksum = "90878fb664448b54c4e592455ad02831e23a3f7e157374a8b95654731aac7349"
 dependencies = [
  "is-wsl",
  "libc",
@@ -7359,11 +7362,10 @@ dependencies = [
 
 [[package]]
 name = "proc-macro-crate"
-version = "2.0.1"
+version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97dc5fea232fc28d2f597b37c4876b348a40e33f3b02cc975c8d006d78d94b1a"
+checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8"
 dependencies = [
- "toml_datetime",
  "toml_edit 0.20.2",
 ]
 
@@ -8504,7 +8506,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5fa50756ef995d410bb1968b403a1eca169b54c3c0eb4be4bcb1da8c7591cb7d"
 dependencies = [
  "cruet",
- "proc-macro-crate 2.0.1",
+ "proc-macro-crate 2.0.0",
  "proc-macro2",
  "quote",
  "regex",
@@ -9597,7 +9599,7 @@ dependencies = [
  "cfg-expr 0.15.6",
  "heck 0.4.1",
  "pkg-config",
- "toml 0.8.2",
+ "toml 0.8.8",
  "version-compare",
 ]
 
@@ -10086,22 +10088,22 @@ dependencies = [
 
 [[package]]
 name = "toml"
-version = "0.8.2"
+version = "0.8.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d"
+checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35"
 dependencies = [
  "indexmap 2.1.0",
  "serde",
  "serde_spanned",
  "toml_datetime",
- "toml_edit 0.20.2",
+ "toml_edit 0.21.0",
 ]
 
 [[package]]
 name = "toml_datetime"
-version = "0.6.3"
+version = "0.6.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
+checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
 dependencies = [
  "serde",
 ]
@@ -10124,6 +10126,17 @@ name = "toml_edit"
 version = "0.20.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338"
+dependencies = [
+ "indexmap 2.1.0",
+ "toml_datetime",
+ "winnow",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03"
 dependencies = [
  "indexmap 2.1.0",
  "serde",

+ 1 - 1
README.md

@@ -42,7 +42,7 @@
     <span> | </span>
     <a href="https://dioxuslabs.com/learn/0.4/guide"> Guide </a>
     <span> | </span>
-    <a href="https://github.com/DioxusLabs/dioxus/blob/master/notes/README/ZH_CN.md"> 中文 </a>
+    <a href="https://github.com/DioxusLabs/dioxus/blob/master/translations/zh-cn/README.md"> 中文 </a>
     <span> | </span>
     <a href="https://github.com/DioxusLabs/dioxus/blob/master/translations/pt-br/README.md"> PT-BR </a>
     <span> | </span>

+ 18 - 11
packages/cli-config/src/config.rs

@@ -338,11 +338,9 @@ pub struct WebHttpsConfig {
 
 #[derive(Debug, Clone, Serialize, Deserialize)]
 pub struct CrateConfig {
-    pub out_dir: PathBuf,
     pub crate_dir: PathBuf,
     pub workspace_dir: PathBuf,
     pub target_dir: PathBuf,
-    pub asset_dir: PathBuf,
     #[cfg(feature = "cli")]
     pub manifest: cargo_toml::Manifest<cargo_toml::Value>,
     pub executable: ExecutableType,
@@ -383,12 +381,8 @@ impl CrateConfig {
         let workspace_dir = meta.workspace_root;
         let target_dir = meta.target_directory;
 
-        let out_dir = crate_dir.join(&dioxus_config.application.out_dir);
-
         let cargo_def = &crate_dir.join("Cargo.toml");
 
-        let asset_dir = crate_dir.join(&dioxus_config.application.asset_dir);
-
         let manifest = cargo_toml::Manifest::from_path(cargo_def).unwrap();
 
         let mut output_filename = String::from("dioxus_app");
@@ -414,6 +408,7 @@ impl CrateConfig {
 
         let release = false;
         let hot_reload = false;
+        let cross_origin_policy = false;
         let verbose = false;
         let custom_profile = None;
         let features = None;
@@ -421,26 +416,38 @@ impl CrateConfig {
         let cargo_args = vec![];
 
         Ok(Self {
-            out_dir,
             crate_dir,
             workspace_dir,
             target_dir,
-            asset_dir,
             #[cfg(feature = "cli")]
             manifest,
             executable,
-            release,
             dioxus_config,
+            release,
             hot_reload,
-            cross_origin_policy: false,
+            cross_origin_policy,
+            verbose,
             custom_profile,
             features,
-            verbose,
             target,
             cargo_args,
         })
     }
 
+    /// Compose an asset directory. Represents the typical "public" directory
+    /// with publicly available resources (configurable in the `Dioxus.toml`).
+    pub fn asset_dir(&self) -> PathBuf {
+        self.crate_dir
+            .join(&self.dioxus_config.application.asset_dir)
+    }
+
+    /// Compose an out directory. Represents the typical "dist" directory that
+    /// is "distributed" after building an application (configurable in the
+    /// `Dioxus.toml`).
+    pub fn out_dir(&self) -> PathBuf {
+        self.crate_dir.join(&self.dioxus_config.application.out_dir)
+    }
+
     pub fn as_example(&mut self, example_name: String) -> &mut Self {
         self.executable = ExecutableType::Example(example_name);
         self

+ 8 - 9
packages/cli/Cargo.toml

@@ -21,12 +21,13 @@ log = "0.4.14"
 fern = { version = "0.6.0", features = ["colored"] }
 serde = { version = "1.0.136", features = ["derive"] }
 serde_json = "1.0.79"
+toml = "0.8.8"
 fs_extra = "1.2.0"
-cargo_toml = "0.16.0"
-futures = "0.3.21"
+cargo_toml = "0.18.0"
+futures-util = { workspace = true }
 notify = { version = "5.0.0-pre.16", features = ["serde"] }
 html_parser  = { workspace = true }
-cargo_metadata = "0.15.0"
+cargo_metadata = "0.18.1"
 tokio = { version = "1.16.1", features = ["fs", "sync", "rt", "macros"] }
 atty = "0.2.14"
 chrono = "0.4.19"
@@ -44,7 +45,7 @@ headers = "0.3.7"
 walkdir = "2"
 
 # tools download
-dirs = "4.0.0"
+dirs = "5.0.1"
 reqwest = { version = "0.11", features = [
     "rustls-tls",
     "stream",
@@ -55,7 +56,6 @@ flate2 = "1.0.22"
 tar = "0.4.38"
 zip = "0.6.2"
 tower = "0.4.12"
-syn = { version = "2.0", features = ["full", "extra-traits"] }
 lazy_static = "1.4.0"
 
 # plugin packages
@@ -67,13 +67,12 @@ mlua = { version = "0.8.1", features = [
     "macros",
 ], optional = true }
 ctrlc = "3.2.3"
-open = "4.1.0"
-cargo-generate = "0.18"
-toml_edit = "0.19.11"
+open = "5.0.1"
+cargo-generate = "0.19.0"
+toml_edit = "0.21.0"
 
 # bundling
 tauri-bundler = { version = "=1.4.*", features = ["native-tls-vendored"] }
-tauri-utils = "=1.5.*"
 
 manganis-cli-support = { workspace = true, features = ["webp", "html"] }
 

+ 2 - 2
packages/cli/src/assets.rs

@@ -13,7 +13,7 @@ pub fn asset_manifest(crate_config: &CrateConfig) -> AssetManifest {
 
 /// Create a head file that contains all of the imports for assets that the user project uses
 pub fn create_assets_head(config: &CrateConfig, manifest: &AssetManifest) -> Result<()> {
-    let mut file = File::create(config.out_dir.join("__assets_head.html"))?;
+    let mut file = File::create(config.out_dir().join("__assets_head.html"))?;
     file.write_all(manifest.head().as_bytes())?;
     Ok(())
 }
@@ -29,7 +29,7 @@ pub(crate) fn process_assets(config: &CrateConfig, manifest: &AssetManifest) ->
             .clone()
             .unwrap_or_default(),
     );
-    let static_asset_output_dir = config.out_dir.join(static_asset_output_dir);
+    let static_asset_output_dir = config.out_dir().join(static_asset_output_dir);
 
     manifest.copy_static_assets_to(static_asset_output_dir)?;
 

+ 21 - 21
packages/cli/src/builder.rs

@@ -39,14 +39,14 @@ pub fn build(config: &CrateConfig, _: bool, skip_assets: bool) -> Result<BuildRe
     // [6] Link up the html page to the wasm module
 
     let CrateConfig {
-        out_dir,
         crate_dir,
         target_dir,
-        asset_dir,
         executable,
         dioxus_config,
         ..
     } = config;
+    let out_dir = config.out_dir();
+    let asset_dir = config.asset_dir();
 
     let _guard = WebAssetConfigDropGuard::new();
     let _manganis_support = ManganisSupportGuard::default();
@@ -251,15 +251,15 @@ pub fn build(config: &CrateConfig, _: bool, skip_assets: bool) -> Result<BuildRe
             if path.is_file() {
                 std::fs::copy(&path, out_dir.join(path.file_name().unwrap()))?;
             } else {
-                match fs_extra::dir::copy(&path, out_dir, &copy_options) {
+                match fs_extra::dir::copy(&path, &out_dir, &copy_options) {
                     Ok(_) => {}
                     Err(_e) => {
                         log::warn!("Error copying dir: {}", _e);
                     }
                 }
                 for ignore in &ignore_files {
-                    let ignore = ignore.strip_prefix(&config.asset_dir).unwrap();
-                    let ignore = config.out_dir.join(ignore);
+                    let ignore = ignore.strip_prefix(&config.asset_dir()).unwrap();
+                    let ignore = out_dir.join(ignore);
                     if ignore.is_file() {
                         std::fs::remove_file(ignore)?;
                     }
@@ -369,13 +369,13 @@ pub fn build_desktop(
         file_name
     };
 
-    if !config.out_dir.is_dir() {
-        create_dir_all(&config.out_dir)?;
+    if !config.out_dir().is_dir() {
+        create_dir_all(config.out_dir())?;
     }
-    copy(res_path, config.out_dir.join(target_file))?;
+    copy(res_path, config.out_dir().join(target_file))?;
 
     // this code will copy all public file to the output dir
-    if config.asset_dir.is_dir() {
+    if config.asset_dir().is_dir() {
         let copy_options = fs_extra::dir::CopyOptions {
             overwrite: true,
             skip_exist: false,
@@ -385,20 +385,20 @@ pub fn build_desktop(
             depth: 0,
         };
 
-        for entry in std::fs::read_dir(&config.asset_dir)? {
+        for entry in std::fs::read_dir(config.asset_dir())? {
             let path = entry?.path();
             if path.is_file() {
-                std::fs::copy(&path, &config.out_dir.join(path.file_name().unwrap()))?;
+                std::fs::copy(&path, &config.out_dir().join(path.file_name().unwrap()))?;
             } else {
-                match fs_extra::dir::copy(&path, &config.out_dir, &copy_options) {
+                match fs_extra::dir::copy(&path, &config.out_dir(), &copy_options) {
                     Ok(_) => {}
                     Err(e) => {
                         log::warn!("Error copying dir: {}", e);
                     }
                 }
                 for ignore in &ignore_files {
-                    let ignore = ignore.strip_prefix(&config.asset_dir).unwrap();
-                    let ignore = config.out_dir.join(ignore);
+                    let ignore = ignore.strip_prefix(&config.asset_dir()).unwrap();
+                    let ignore = config.out_dir().join(ignore);
                     if ignore.is_file() {
                         std::fs::remove_file(ignore)?;
                     }
@@ -443,7 +443,7 @@ fn prettier_build(cmd: subprocess::Exec) -> anyhow::Result<Vec<Diagnostic>> {
             .unwrap()
             .tick_chars("/|\\- "),
     );
-    pb.set_message("💼 Waiting to start build the project...");
+    pb.set_message("💼 Waiting to start building the project...");
 
     let stdout = cmd.detached().stream_stdout()?;
     let reader = std::io::BufReader::new(stdout);
@@ -635,7 +635,7 @@ fn build_assets(config: &CrateConfig) -> Result<Vec<PathBuf>> {
 
                     if file == "*" {
                         // if the sass open auto, we need auto-check the assets dir.
-                        let asset_dir = config.asset_dir.clone();
+                        let asset_dir = config.asset_dir().clone();
                         if asset_dir.is_dir() {
                             for entry in walkdir::WalkDir::new(&asset_dir)
                                 .into_iter()
@@ -655,7 +655,7 @@ fn build_assets(config: &CrateConfig) -> Result<Vec<PathBuf>> {
                                             temp.file_stem().unwrap().to_str().unwrap()
                                         );
                                         let target_path = config
-                                            .out_dir
+                                            .out_dir()
                                             .join(
                                                 temp.strip_prefix(&asset_dir)
                                                     .unwrap()
@@ -685,11 +685,11 @@ fn build_assets(config: &CrateConfig) -> Result<Vec<PathBuf>> {
                         } else {
                             file
                         };
-                        let path = config.asset_dir.join(relative_path);
+                        let path = config.asset_dir().join(relative_path);
                         let out_file =
                             format!("{}.css", path.file_stem().unwrap().to_str().unwrap());
                         let target_path = config
-                            .out_dir
+                            .out_dir()
                             .join(PathBuf::from(relative_path).parent().unwrap())
                             .join(out_file);
                         if path.is_file() {
@@ -719,11 +719,11 @@ fn build_assets(config: &CrateConfig) -> Result<Vec<PathBuf>> {
                             } else {
                                 path
                             };
-                            let path = config.asset_dir.join(relative_path);
+                            let path = config.asset_dir().join(relative_path);
                             let out_file =
                                 format!("{}.css", path.file_stem().unwrap().to_str().unwrap());
                             let target_path = config
-                                .out_dir
+                                .out_dir()
                                 .join(PathBuf::from(relative_path).parent().unwrap())
                                 .join(out_file);
                             if path.is_file() {

+ 1 - 1
packages/cli/src/cli/autoformat.rs

@@ -1,5 +1,5 @@
 use dioxus_autofmt::{IndentOptions, IndentType};
-use futures::{stream::FuturesUnordered, StreamExt};
+use futures_util::{stream::FuturesUnordered, StreamExt};
 use std::{fs, path::Path, process::exit};
 
 use super::*;

+ 2 - 2
packages/cli/src/cli/bundle.rs

@@ -87,7 +87,7 @@ impl Bundle {
         build_desktop(&crate_config, false, false)?;
 
         // copy the binary to the out dir
-        let package = crate_config.manifest.package.unwrap();
+        let package = crate_config.manifest.package.as_ref().unwrap();
 
         let mut name: PathBuf = match &crate_config.executable {
             ExecutableType::Binary(name)
@@ -149,7 +149,7 @@ impl Bundle {
         }
 
         let mut settings = SettingsBuilder::new()
-            .project_out_directory(crate_config.out_dir)
+            .project_out_directory(crate_config.out_dir())
             .package_settings(PackageSettings {
                 product_name: crate_config.dioxus_config.application.name.clone(),
                 version: package.version().to_string(),

+ 3 - 1
packages/cli/src/cli/cfg.rs

@@ -78,6 +78,7 @@ impl From<ConfigOptsServe> for ConfigOptsBuild {
 }
 
 #[derive(Clone, Debug, Default, Deserialize, Parser)]
+#[command(group = clap::ArgGroup::new("release-incompatible").multiple(true).conflicts_with("release"))]
 pub struct ConfigOptsServe {
     /// Port of dev server
     #[clap(long)]
@@ -116,8 +117,9 @@ pub struct ConfigOptsServe {
     #[clap(long, value_enum)]
     pub platform: Option<Platform>,
 
-    /// Build with hot reloading rsx [default: false]
+    /// Build with hot reloading rsx. Will not work with release builds. [default: false]
     #[clap(long)]
+    #[clap(group = "release-incompatible")]
     #[serde(default)]
     pub hot_reload: bool,
 

+ 1 - 1
packages/cli/src/cli/check.rs

@@ -1,4 +1,4 @@
-use futures::{stream::FuturesUnordered, StreamExt};
+use futures_util::{stream::FuturesUnordered, StreamExt};
 use std::{path::Path, process::exit};
 
 use super::*;

+ 26 - 24
packages/cli/src/main.rs

@@ -1,7 +1,7 @@
 use dioxus_cli_config::DioxusConfig;
 use std::path::PathBuf;
 
-use anyhow::anyhow;
+use anyhow::Context;
 use clap::Parser;
 use dioxus_cli::*;
 
@@ -19,24 +19,26 @@ fn get_bin(bin: Option<String>) -> Result<PathBuf> {
             .workspace_packages()
             .into_iter()
             .find(|p| p.name == bin)
-            .ok_or(format!("no such package: {}", bin))
-            .map_err(Error::CargoError)?
+            .ok_or(Error::CargoError(format!("no such package: {}", bin)))?
     } else {
         metadata
             .root_package()
-            .ok_or("no root package?".into())
-            .map_err(Error::CargoError)?
+            .ok_or(Error::CargoError("no root package?".to_string()))?
     };
 
     let crate_dir = package
         .manifest_path
         .parent()
-        .ok_or("couldn't take parent dir".into())
-        .map_err(Error::CargoError)?;
+        .ok_or(Error::CargoError("couldn't take parent dir".to_string()))?;
 
     Ok(crate_dir.into())
 }
 
+/// Simplifies error messages that use the same pattern.
+fn error_wrapper(message: &str) -> String {
+    format!("🚫 {message}:")
+}
+
 #[tokio::main]
 async fn main() -> anyhow::Result<()> {
     let args = Cli::parse();
@@ -46,35 +48,35 @@ async fn main() -> anyhow::Result<()> {
     match args.action {
         Translate(opts) => opts
             .translate()
-            .map_err(|e| anyhow!("🚫 Translation of HTML into RSX failed: {}", e)),
+            .context(error_wrapper("Translation of HTML into RSX failed")),
 
         Create(opts) => opts
             .create()
-            .map_err(|e| anyhow!("🚫 Creating new project failed: {}", e)),
+            .context(error_wrapper("Creating new project failed")),
 
         Init(opts) => opts
             .init()
-            .map_err(|e| anyhow!("🚫 Initialising a new project failed: {}", e)),
+            .context(error_wrapper("Initialising a new project failed")),
 
         Config(opts) => opts
             .config()
-            .map_err(|e| anyhow!("🚫 Configuring new project failed: {}", e)),
+            .context(error_wrapper("Configuring new project failed")),
 
         #[cfg(feature = "plugin")]
         Plugin(opts) => opts
             .plugin()
             .await
-            .map_err(|e| anyhow!("🚫 Error with plugin: {}", e)),
+            .context(error_wrapper("Error with plugin")),
 
         Autoformat(opts) => opts
             .autoformat()
             .await
-            .map_err(|e| anyhow!("🚫 Error autoformatting RSX: {}", e)),
+            .context(error_wrapper("Error autoformatting RSX")),
 
         Check(opts) => opts
             .check()
             .await
-            .map_err(|e| anyhow!("🚫 Error checking RSX: {}", e)),
+            .context(error_wrapper("Error checking RSX")),
 
         Version(opt) => {
             let version = opt.version();
@@ -85,33 +87,33 @@ async fn main() -> anyhow::Result<()> {
         action => {
             let bin = get_bin(args.bin)?;
             let _dioxus_config = DioxusConfig::load(Some(bin.clone()))
-                       .map_err(|e| anyhow!("Failed to load Dioxus config because: {e}"))?
-                       .unwrap_or_else(|| {
-                           log::info!("You appear to be creating a Dioxus project from scratch; we will use the default config");
-                           DioxusConfig::default()
-                    });
+                .context("Failed to load Dioxus config because")?
+                .unwrap_or_else(|| {
+                    log::info!("You appear to be creating a Dioxus project from scratch; we will use the default config");
+                    DioxusConfig::default()
+                });
 
             #[cfg(feature = "plugin")]
             PluginManager::init(_dioxus_config.plugin)
-                .map_err(|e| anyhow!("🚫 Plugin system initialization failed: {e}"))?;
+                .context(error_wrapper("Plugin system initialization failed"))?;
 
             match action {
                 Build(opts) => opts
                     .build(Some(bin.clone()), None)
-                    .map_err(|e| anyhow!("🚫 Building project failed: {}", e)),
+                    .context(error_wrapper("Building project failed")),
 
                 Clean(opts) => opts
                     .clean(Some(bin.clone()))
-                    .map_err(|e| anyhow!("🚫 Cleaning project failed: {}", e)),
+                    .context(error_wrapper("Cleaning project failed")),
 
                 Serve(opts) => opts
                     .serve(Some(bin.clone()))
                     .await
-                    .map_err(|e| anyhow!("🚫 Serving project failed: {}", e)),
+                    .context(error_wrapper("Serving project failed")),
 
                 Bundle(opts) => opts
                     .bundle(Some(bin.clone()))
-                    .map_err(|e| anyhow!("🚫 Bundling project failed: {}", e)),
+                    .context(error_wrapper("Bundling project failed")),
 
                 _ => unreachable!(),
             }

+ 4 - 4
packages/cli/src/plugin/mod.rs

@@ -181,8 +181,8 @@ impl PluginManager {
         let args = lua.create_table()?;
         args.set("name", crate_config.dioxus_config.application.name.clone())?;
         args.set("platform", platform)?;
-        args.set("out_dir", crate_config.out_dir.to_str().unwrap())?;
-        args.set("asset_dir", crate_config.asset_dir.to_str().unwrap())?;
+        args.set("out_dir", crate_config.out_dir().to_str().unwrap())?;
+        args.set("asset_dir", crate_config.asset_dir().to_str().unwrap())?;
 
         for i in 1..(manager.len()? as i32 + 1) {
             let info = manager.get::<i32, PluginInfo>(i)?;
@@ -205,8 +205,8 @@ impl PluginManager {
         let args = lua.create_table()?;
         args.set("name", crate_config.dioxus_config.application.name.clone())?;
         args.set("platform", platform)?;
-        args.set("out_dir", crate_config.out_dir.to_str().unwrap())?;
-        args.set("asset_dir", crate_config.asset_dir.to_str().unwrap())?;
+        args.set("out_dir", crate_config.out_dir().to_str().unwrap())?;
+        args.set("asset_dir", crate_config.asset_dir().to_str().unwrap())?;
 
         for i in 1..(manager.len()? as i32 + 1) {
             let info = manager.get::<i32, PluginInfo>(i)?;

+ 1 - 1
packages/cli/src/server/desktop/mod.rs

@@ -221,7 +221,7 @@ fn start_desktop(config: &CrateConfig, skip_assets: bool) -> Result<(RAIIChild,
         ExecutableType::Binary(name)
         | ExecutableType::Lib(name)
         | ExecutableType::Example(name) => {
-            let mut file = config.out_dir.join(name);
+            let mut file = config.out_dir().join(name);
             if cfg!(windows) {
                 file.set_extension("exe");
             }

+ 89 - 79
packages/cli/src/server/mod.rs

@@ -31,93 +31,103 @@ async fn setup_file_watcher<F: Fn() -> Result<BuildResult> + Send + 'static>(
     let mut watcher = notify::recommended_watcher(move |info: notify::Result<notify::Event>| {
         let config = watcher_config.clone();
         if let Ok(e) = info {
-            if chrono::Local::now().timestamp() > last_update_time {
-                let mut needs_full_rebuild;
-                if let Some(hot_reload) = &hot_reload {
-                    // find changes to the rsx in the file
-                    let mut rsx_file_map = hot_reload.file_map.lock().unwrap();
-                    let mut messages: Vec<Template> = Vec::new();
-
-                    // In hot reload mode, we only need to rebuild if non-rsx code is changed
-                    needs_full_rebuild = false;
-
-                    for path in &e.paths {
-                        // if this is not a rust file, rebuild the whole project
-                        if path.extension().and_then(|p| p.to_str()) != Some("rs") {
-                            needs_full_rebuild = true;
-                            break;
-                        }
-
-                        // Workaround for notify and vscode-like editor:
-                        // when edit & save a file in vscode, there will be two notifications,
-                        // the first one is a file with empty content.
-                        // filter the empty file notification to avoid false rebuild during hot-reload
-                        if let Ok(metadata) = fs::metadata(path) {
-                            if metadata.len() == 0 {
-                                continue;
+            match e.kind {
+                notify::EventKind::Create(_)
+                | notify::EventKind::Remove(_)
+                | notify::EventKind::Modify(_) => {
+                    if chrono::Local::now().timestamp() > last_update_time {
+                        let mut needs_full_rebuild;
+                        if let Some(hot_reload) = &hot_reload {
+                            // find changes to the rsx in the file
+                            let mut rsx_file_map = hot_reload.file_map.lock().unwrap();
+                            let mut messages: Vec<Template> = Vec::new();
+
+                            // In hot reload mode, we only need to rebuild if non-rsx code is changed
+                            needs_full_rebuild = false;
+
+                            for path in &e.paths {
+                                // if this is not a rust file, rebuild the whole project
+                                if path.extension().and_then(|p| p.to_str()) != Some("rs") {
+                                    needs_full_rebuild = true;
+                                    break;
+                                }
+
+                                // Workaround for notify and vscode-like editor:
+                                // when edit & save a file in vscode, there will be two notifications,
+                                // the first one is a file with empty content.
+                                // filter the empty file notification to avoid false rebuild during hot-reload
+                                if let Ok(metadata) = fs::metadata(path) {
+                                    if metadata.len() == 0 {
+                                        continue;
+                                    }
+                                }
+
+                                match rsx_file_map.update_rsx(path, &config.crate_dir) {
+                                    Ok(UpdateResult::UpdatedRsx(msgs)) => {
+                                        messages.extend(msgs);
+                                        needs_full_rebuild = false;
+                                    }
+                                    Ok(UpdateResult::NeedsRebuild) => {
+                                        needs_full_rebuild = true;
+                                    }
+                                    Err(err) => {
+                                        log::error!("{}", err);
+                                    }
+                                }
                             }
-                        }
 
-                        match rsx_file_map.update_rsx(path, &config.crate_dir) {
-                            Ok(UpdateResult::UpdatedRsx(msgs)) => {
-                                messages.extend(msgs);
-                                needs_full_rebuild = false;
+                            if needs_full_rebuild {
+                                // Reset the file map to the new state of the project
+                                let FileMapBuildResult {
+                                    map: new_file_map,
+                                    errors,
+                                } = FileMap::<HtmlCtx>::create(config.crate_dir.clone()).unwrap();
+
+                                for err in errors {
+                                    log::error!("{}", err);
+                                }
+
+                                *rsx_file_map = new_file_map;
+                            } else {
+                                for msg in messages {
+                                    let _ = hot_reload.messages.send(msg);
+                                }
                             }
-                            Ok(UpdateResult::NeedsRebuild) => {
-                                needs_full_rebuild = true;
-                            }
-                            Err(err) => {
-                                log::error!("{}", err);
-                            }
-                        }
-                    }
-
-                    if needs_full_rebuild {
-                        // Reset the file map to the new state of the project
-                        let FileMapBuildResult {
-                            map: new_file_map,
-                            errors,
-                        } = FileMap::<HtmlCtx>::create(config.crate_dir.clone()).unwrap();
-
-                        for err in errors {
-                            log::error!("{}", err);
-                        }
-
-                        *rsx_file_map = new_file_map;
-                    } else {
-                        for msg in messages {
-                            let _ = hot_reload.messages.send(msg);
+                        } else {
+                            needs_full_rebuild = true;
                         }
-                    }
-                } else {
-                    needs_full_rebuild = true;
-                }
 
-                if needs_full_rebuild {
-                    match build_with() {
-                        Ok(res) => {
-                            last_update_time = chrono::Local::now().timestamp();
-
-                            #[allow(clippy::redundant_clone)]
-                            print_console_info(
-                                &config,
-                                PrettierOptions {
-                                    changed: e.paths.clone(),
-                                    warnings: res.warnings,
-                                    elapsed_time: res.elapsed_time,
-                                },
-                                web_info.clone(),
-                            );
-
-                            #[cfg(feature = "plugin")]
-                            let _ = crate::plugin::PluginManager::on_serve_rebuild(
-                                chrono::Local::now().timestamp(),
-                                e.paths,
-                            );
+                        if needs_full_rebuild {
+                            match build_with() {
+                                Ok(res) => {
+                                    last_update_time = chrono::Local::now().timestamp();
+
+                                    #[allow(clippy::redundant_clone)]
+                                    print_console_info(
+                                        &config,
+                                        PrettierOptions {
+                                            changed: e.paths.clone(),
+                                            warnings: res.warnings,
+                                            elapsed_time: res.elapsed_time,
+                                        },
+                                        web_info.clone(),
+                                    );
+
+                                    #[cfg(feature = "plugin")]
+                                    let _ = PluginManager::on_serve_rebuild(
+                                        chrono::Local::now().timestamp(),
+                                        e.paths,
+                                    );
+                                }
+                                Err(e) => {
+                                    last_update_time = chrono::Local::now().timestamp();
+                                    log::error!("{:?}", e);
+                                }
+                            }
                         }
-                        Err(e) => log::error!("{}", e),
                     }
                 }
+                _ => {}
             }
         }
     })

+ 2 - 2
packages/cli/src/server/web/mod.rs

@@ -290,7 +290,7 @@ async fn setup_router(
                         std::fs::read_to_string(
                             file_service_config
                                 .crate_dir
-                                .join(file_service_config.out_dir)
+                                .join(file_service_config.out_dir())
                                 .join("index.html"),
                         )
                         .ok()
@@ -315,7 +315,7 @@ async fn setup_router(
                 Ok(response)
             },
         )
-        .service(ServeDir::new(config.crate_dir.join(&config.out_dir)));
+        .service(ServeDir::new(config.crate_dir.join(config.out_dir())));
 
     // Setup websocket
     let mut router = Router::new().route("/_dioxus/ws", get(ws_handler));

+ 1 - 1
packages/cli/src/tools.rs

@@ -7,7 +7,7 @@ use std::{
 
 use anyhow::Context;
 use flate2::read::GzDecoder;
-use futures::StreamExt;
+use futures_util::StreamExt;
 use tar::Archive;
 use tokio::io::AsyncWriteExt;
 

+ 120 - 1
packages/core/tests/diff_element.rs

@@ -1,6 +1,6 @@
 use dioxus::dioxus_core::Mutation::*;
 use dioxus::prelude::*;
-use dioxus_core::{ElementId, NoOpMutations};
+use dioxus_core::{AttributeValue, ElementId, NoOpMutations};
 
 #[test]
 fn text_diff() {
@@ -82,3 +82,122 @@ fn element_swap() {
         ]
     );
 }
+
+#[test]
+fn attribute_diff() {
+    fn app(cx: Scope) -> Element {
+        let gen = cx.generation();
+
+        // attributes have to be sorted by name
+        let attrs = match gen % 5 {
+            0 => cx.bump().alloc([Attribute::new(
+                "a",
+                AttributeValue::Text("hello"),
+                None,
+                false,
+            )]) as &[Attribute],
+            1 => cx.bump().alloc([
+                Attribute::new("a", AttributeValue::Text("hello"), None, false),
+                Attribute::new("b", AttributeValue::Text("hello"), None, false),
+                Attribute::new("c", AttributeValue::Text("hello"), None, false),
+            ]) as &[Attribute],
+            2 => cx.bump().alloc([
+                Attribute::new("c", AttributeValue::Text("hello"), None, false),
+                Attribute::new("d", AttributeValue::Text("hello"), None, false),
+                Attribute::new("e", AttributeValue::Text("hello"), None, false),
+            ]) as &[Attribute],
+            3 => cx.bump().alloc([Attribute::new(
+                "d",
+                AttributeValue::Text("world"),
+                None,
+                false,
+            )]) as &[Attribute],
+            _ => unreachable!(),
+        };
+
+        cx.render(rsx!(
+            div {
+                ..*attrs,
+                "hello"
+            }
+        ))
+    }
+
+    let mut vdom = VirtualDom::new(app);
+    _ = vdom.rebuild();
+
+    vdom.mark_dirty(ScopeId::ROOT);
+    assert_eq!(
+        vdom.render_immediate().santize().edits,
+        [
+            SetAttribute {
+                name: "b",
+                value: (&AttributeValue::Text("hello",)).into(),
+                id: ElementId(1,),
+                ns: None,
+            },
+            SetAttribute {
+                name: "c",
+                value: (&AttributeValue::Text("hello",)).into(),
+                id: ElementId(1,),
+                ns: None,
+            },
+        ]
+    );
+
+    vdom.mark_dirty(ScopeId::ROOT);
+    assert_eq!(
+        vdom.render_immediate().santize().edits,
+        [
+            SetAttribute {
+                name: "a",
+                value: (&AttributeValue::None).into(),
+                id: ElementId(1,),
+                ns: None,
+            },
+            SetAttribute {
+                name: "b",
+                value: (&AttributeValue::None).into(),
+                id: ElementId(1,),
+                ns: None,
+            },
+            SetAttribute {
+                name: "d",
+                value: (&AttributeValue::Text("hello",)).into(),
+                id: ElementId(1,),
+                ns: None,
+            },
+            SetAttribute {
+                name: "e",
+                value: (&AttributeValue::Text("hello",)).into(),
+                id: ElementId(1,),
+                ns: None,
+            },
+        ]
+    );
+
+    vdom.mark_dirty(ScopeId::ROOT);
+    assert_eq!(
+        vdom.render_immediate().santize().edits,
+        [
+            SetAttribute {
+                name: "c",
+                value: (&AttributeValue::None).into(),
+                id: ElementId(1,),
+                ns: None,
+            },
+            SetAttribute {
+                name: "d",
+                value: (&AttributeValue::Text("world",)).into(),
+                id: ElementId(1,),
+                ns: None,
+            },
+            SetAttribute {
+                name: "e",
+                value: (&AttributeValue::None).into(),
+                id: ElementId(1,),
+                ns: None,
+            },
+        ]
+    );
+}

+ 7 - 4
packages/desktop/headless_tests/events.rs

@@ -147,7 +147,7 @@ fn app() -> Element {
         ctrlKey: false,
         metaKey: false,
         shiftKey: false,
-        isComposing: false,
+        isComposing: true,
         which: 65,
         bubbles: true,
         })"#,
@@ -343,7 +343,8 @@ fn app() -> Element {
                     assert_eq!(event.data.code().to_string(), "KeyA");
                     assert_eq!(event.data.location(), Location::Standard);
                     assert!(event.data.is_auto_repeating());
-                    received_events.with_mut(|x| *x + 1);
+                    assert!(event.data.is_composing());
+                    received_events.with_mut(|x| *x + 1)
                 }
             }
             input {
@@ -355,7 +356,8 @@ fn app() -> Element {
                     assert_eq!(event.data.code().to_string(), "KeyA");
                     assert_eq!(event.data.location(), Location::Standard);
                     assert!(!event.data.is_auto_repeating());
-                    received_events.with_mut(|x| *x + 1);
+                    assert!(!event.data.is_composing());
+                    received_events.with_mut(|x| *x + 1)
                 }
             }
             input {
@@ -367,7 +369,8 @@ fn app() -> Element {
                     assert_eq!(event.data.code().to_string(), "KeyA");
                     assert_eq!(event.data.location(), Location::Standard);
                     assert!(!event.data.is_auto_repeating());
-                    received_events.with_mut(|x| *x + 1);
+                    assert!(!event.data.is_composing());
+                    received_events.with_mut(|x| *x + 1)
                 }
             }
             input {

+ 1 - 1
packages/desktop/src/menubar.rs

@@ -64,7 +64,7 @@ mod impl_ {
                 ])
                 .unwrap();
 
-            let edit_menu = Submenu::new("Window", true);
+            let edit_menu = Submenu::new("Edit", true);
             edit_menu
                 .append_items(&[
                     &PredefinedMenuItem::undo(None),

+ 22 - 1
packages/desktop/src/webview.rs

@@ -91,7 +91,28 @@ impl WebviewInstance {
             }
         };
 
-        let mut webview = WebViewBuilder::new(&window)
+        #[cfg(any(
+            target_os = "windows",
+            target_os = "macos",
+            target_os = "ios",
+            target_os = "android"
+        ))]
+        let mut webview = WebViewBuilder::new(&window);
+
+        #[cfg(not(any(
+            target_os = "windows",
+            target_os = "macos",
+            target_os = "ios",
+            target_os = "android"
+        )))]
+        let mut webview = {
+            use tao::platform::unix::WindowExtUnix;
+            use wry::WebViewBuilderExtUnix;
+            let vbox = window.default_vbox().unwrap();
+            WebViewBuilder::new_gtk(vbox)
+        };
+
+        webview = webview
             .with_transparent(cfg.window.window.transparent)
             .with_url("dioxus://index.html/")
             .unwrap()

+ 195 - 0
packages/html/src/elements.rs

@@ -1744,4 +1744,199 @@ builder_constructors! {
     r#use ["use", "http://www.w3.org/2000/svg"] {
         href: String DEFAULT,
     };
+
+    // MathML elements
+
+    /// Build a
+    /// [`<annotation>`](https://w3c.github.io/mathml-core/#dfn-annotation)
+    /// element.
+    annotation "http://www.w3.org/1998/Math/MathML" {
+            encoding: String DEFAULT,
+    };
+
+    /// Build a
+    /// [`<annotation-xml>`](https://w3c.github.io/mathml-core/#dfn-annotation-xml)
+    /// element.
+    annotationXml ["annotation-xml", "http://www.w3.org/1998/Math/MathML"] {
+            encoding: String DEFAULT,
+    };
+
+    /// Build a
+    /// [`<merror>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/merror)
+    /// element.
+    merror "http://www.w3.org/1998/Math/MathML" {};
+
+    /// Build a
+    /// [`<math>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/math)
+    /// element.
+    math "http://www.w3.org/1998/Math/MathML" {
+        display: String DEFAULT,
+    };
+
+    /// Build a
+    /// [`<mfrac>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mfrac)
+    /// element.
+    mfrac "http://www.w3.org/1998/Math/MathML" {
+        linethickness: usize DEFAULT,
+    };
+
+    /// Build a
+    /// [`<mi>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mi)
+    /// element.
+    mi "http://www.w3.org/1998/Math/MathML" {
+        mathvariant: String DEFAULT,
+    };
+
+    /// Build a
+    /// [`<mmultiscripts>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mmultiscripts)
+    /// element.
+    mmultiscripts "http://www.w3.org/1998/math/mathml" {};
+
+    /// Build a
+    /// [`<mn>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mn)
+    /// element.
+    mn "http://www.w3.org/1998/Math/MathML" {};
+
+    /// Build a
+    /// [`<mo>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mo)
+    /// element.
+    mo "http://www.w3.org/1998/Math/MathML" {
+        fence: Bool DEFAULT,
+        largeop: Bool DEFAULT,
+        lspace: usize DEFAULT,
+        maxsize: usize DEFAULT,
+        minsize: usize DEFAULT,
+        movablelimits: Bool DEFAULT,
+        rspace: usize DEFAULT,
+        separator: Bool DEFAULT,
+        stretchy: Bool DEFAULT,
+        symmetric: Bool DEFAULT,
+    };
+
+    /// Build a
+    /// [`<mover>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mover)
+    /// element.
+    mover "http://www.w3.org/1998/Math/MathML" {
+        accent: Bool DEFAULT,
+    };
+
+    /// Build a
+    /// [`<mpadded>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mpadded)
+    /// element.
+    mpadded "http://www.w3.org/1998/Math/MathML" {
+        depth: usize DEFAULT,
+        height: usize DEFAULT,
+        lspace: usize DEFAULT,
+        voffset: usize DEFAULT,
+        width: usize DEFAULT,
+    };
+
+    /// Build a
+    /// [`<mphantom>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mphantom)
+    /// element.
+    mphantom "http://www.w3.org/1998/Math/MathML" {};
+
+    /// Build a
+    /// [`<mprescripts>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mprescripts)
+    /// element.
+    mprescripts "http://www.w3.org/1998/Math/MathML" {};
+
+    /// Build a
+    /// [`<mroot>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mroot)
+    /// element.
+    mroot "http://www.w3.org/1998/Math/MathML" {};
+
+    /// Build a
+    /// [`<mrow>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mrow)
+    /// element.
+    mrow "http://www.w3.org/1998/Math/MathML" {
+
+    };
+
+    /// Build a
+    /// [`<ms>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/ms)
+    /// element.
+    ms "http://www.w3.org/1998/Math/MathML" {
+        lquote: String DEFAULT,
+        rquote: String DEFAULT,
+    };
+
+    /// Build a
+    /// [`<mspace>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mspace)
+    /// element.
+    mspace "http://www.w3.org/1998/Math/MathML" {
+        depth: usize DEFAULT,
+        height: usize DEFAULT,
+        width: usize DEFAULT,
+    };
+
+    /// Build a
+    /// [`<msqrt>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/msqrt)
+    /// element.
+    msqrt "http://www.w3.org/1998/Math/MathML" {};
+
+    /// Build a
+    /// [`<mstyle>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mstyle)
+    /// element.
+    mstyle "http://www.w3.org/1998/Math/MathML" {};
+
+    /// Build a
+    /// [`<msub>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/msub)
+    /// element.
+    msub "http://www.w3.org/1998/Math/MathML" {};
+
+    /// Build a
+    /// [`<msubsup>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/msubsup)
+    /// element.
+    msubsup "http://www.w3.org/1998/Math/MathML" {};
+
+    /// Build a
+    /// [`<msup>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/msup)
+    /// element.
+    msup "http://www.w3.org/1998/Math/MathML" {};
+
+    /// Build a
+    /// [`<mtable>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mtable)
+    /// element.
+    mtable "http://www.w3.org/1998/Math/MathML" {};
+
+    /// Build a
+    /// [`<mtd>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mtd)
+    /// element.
+    mtd "http://www.w3.org/1998/Math/MathML" {
+        columnspan: usize DEFAULT,
+        rowspan: usize DEFAULT,
+    };
+
+    /// Build a
+    /// [`<mtext>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mtext)
+    /// element.
+    mtext "http://www.w3.org/1998/Math/MathML" {};
+
+    /// Build a
+    /// [`<mtr>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mtr)
+    /// element.
+    mtr "http://www.w3.org/1998/Math/MathML" {};
+
+    /// Build a
+    /// [`<munder>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/munder)
+    /// element.
+    munder "http://www.w3.org/1998/Math/MathML" {
+        accentunder: Bool DEFAULT,
+    };
+
+    /// Build a
+    /// [`<munderover>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/munderover)
+    /// element.
+    munderover "http://www.w3.org/1998/Math/MathML" {
+        accent: Bool DEFAULT,
+        accentunder: Bool DEFAULT,
+    };
+
+    /// Build a
+    /// [`<semantics>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/semantics)
+    /// element.
+    semantics "http://www.w3.org/1998/Math/MathML" {
+        encoding: String DEFAULT,
+    };
 }

+ 18 - 0
packages/html/src/events/keyboard.rs

@@ -33,6 +33,7 @@ impl std::fmt::Debug for KeyboardData {
             .field("modifiers", &self.modifiers())
             .field("location", &self.location())
             .field("is_auto_repeating", &self.is_auto_repeating())
+            .field("is_composing", &self.is_composing())
             .finish()
     }
 }
@@ -44,6 +45,7 @@ impl PartialEq for KeyboardData {
             && self.modifiers() == other.modifiers()
             && self.location() == other.location()
             && self.is_auto_repeating() == other.is_auto_repeating()
+            && self.is_composing() == other.is_composing()
     }
 }
 
@@ -75,6 +77,11 @@ impl KeyboardData {
         self.inner.is_auto_repeating()
     }
 
+    /// Indicates whether the key is fired within a composition session.
+    pub fn is_composing(&self) -> bool {
+        self.inner.is_composing()
+    }
+
     /// Downcast this KeyboardData to a concrete type.
     pub fn downcast<T: 'static>(&self) -> Option<&T> {
         self.inner.as_any().downcast_ref::<T>()
@@ -92,6 +99,7 @@ impl ModifiersInteraction for KeyboardData {
 #[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)]
 pub struct SerializedKeyboardData {
     char_code: u32,
+    is_composing: bool,
     key: String,
     key_code: KeyCode,
     #[serde(deserialize_with = "resilient_deserialize_code")]
@@ -114,9 +122,11 @@ impl SerializedKeyboardData {
         location: Location,
         is_auto_repeating: bool,
         modifiers: Modifiers,
+        is_composing: bool,
     ) -> Self {
         Self {
             char_code: key.legacy_charcode(),
+            is_composing,
             key: key.to_string(),
             key_code: KeyCode::from_raw_code(
                 std::convert::TryInto::try_into(key.legacy_keycode())
@@ -144,6 +154,7 @@ impl From<&KeyboardData> for SerializedKeyboardData {
             data.location(),
             data.is_auto_repeating(),
             data.modifiers(),
+            data.is_composing(),
         )
     }
 }
@@ -166,6 +177,10 @@ impl HasKeyboardData for SerializedKeyboardData {
         self.repeat
     }
 
+    fn is_composing(&self) -> bool {
+        self.is_composing
+    }
+
     fn as_any(&self) -> &dyn std::any::Any {
         self
     }
@@ -236,6 +251,9 @@ pub trait HasKeyboardData: ModifiersInteraction + std::any::Any {
     /// `true` iff the key is being held down such that it is automatically repeating.
     fn is_auto_repeating(&self) -> bool;
 
+    /// Indicates whether the key is fired within a composition session.
+    fn is_composing(&self) -> bool;
+
     /// return self as Any
     fn as_any(&self) -> &dyn std::any::Any;
 }

+ 4 - 0
packages/html/src/web_sys_bind/events.rs

@@ -78,6 +78,10 @@ impl HasKeyboardData for KeyboardEvent {
         self.repeat()
     }
 
+    fn is_composing(&self) -> bool {
+        self.is_composing()
+    }
+
     fn as_any(&self) -> &dyn std::any::Any {
         self
     }

+ 2 - 0
packages/interpreter/src/interpreter.js

@@ -351,6 +351,7 @@ async function serialize_event(event) {
     case "keyup": {
       let {
         charCode,
+        isComposing,
         key,
         altKey,
         ctrlKey,
@@ -364,6 +365,7 @@ async function serialize_event(event) {
       } = event;
       return {
         char_code: charCode,
+        is_composing: isComposing,
         key: key,
         alt_key: altKey,
         ctrl_key: ctrlKey,

+ 2 - 0
packages/plasmo/src/hooks.rs

@@ -179,6 +179,7 @@ impl InnerInputState {
                         k.location(),
                         is_repeating,
                         k.modifiers(),
+                        k.is_composing(),
                     );
                 }
 
@@ -779,6 +780,7 @@ fn translate_key_event(event: crossterm::event::KeyEvent) -> Option<EventData> {
         Location::Standard,
         false,
         modifiers,
+        false,
     )))
 }
 

+ 2 - 2
packages/router-macro/src/lib.rs

@@ -84,7 +84,7 @@ mod segment;
 ///
 /// # `#[route("path", component)]`
 ///
-/// The `#[route]` attribute is used to define a route. It takes up to 3 parameters:
+/// The `#[route]` attribute is used to define a route. It takes up to 2 parameters:
 /// - `path`: The path to the enum variant (relative to the parent nest)
 /// - (optional) `component`: The component to render when the route is matched. If not specified, the name of the variant is used
 ///
@@ -162,7 +162,7 @@ mod segment;
 ///
 /// # `#[layout(component)]`
 ///
-/// The `#[layout]` attribute is used to define a layout. It takes 2 parameters:
+/// The `#[layout]` attribute is used to define a layout. It takes 1 parameters:
 /// - `component`: The component to render when the route is matched. If not specified, the name of the variant is used
 ///
 /// The layout component allows you to wrap all children of the layout in a component. The child routes are rendered in the Outlet of the layout component. The layout component must take all dynamic parameters of the nests it is nested in.

+ 20 - 3
packages/web/src/cfg.rs

@@ -9,7 +9,8 @@
 /// ```
 pub struct Config {
     pub(crate) hydrate: bool,
-    pub(crate) rootname: String,
+    pub(crate) root: ConfigRoot,
+    pub(crate) cached_strings: Vec<String>,
     pub(crate) default_panic_hook: bool,
 }
 
@@ -17,7 +18,8 @@ impl Default for Config {
     fn default() -> Self {
         Self {
             hydrate: false,
-            rootname: "main".to_string(),
+            root: ConfigRoot::RootName("main".to_string()),
+            cached_strings: Vec::new(),
             default_panic_hook: true,
         }
     }
@@ -46,8 +48,18 @@ impl Config {
     /// Set the name of the element that Dioxus will use as the root.
     ///
     /// This is akin to calling React.render() on the element with the specified name.
+    /// Note that this only works on the current document, i.e. `window.document`.
+    /// To use a different document (popup, iframe, ...) use [Self::rootelement] instead.
     pub fn rootname(mut self, name: impl Into<String>) -> Self {
-        self.rootname = name.into();
+        self.root = ConfigRoot::RootName(name.into());
+        self
+    }
+
+    /// Set the element that Dioxus will use as root.
+    ///
+    /// This is akin to calling React.render() on the given element.
+    pub fn rootelement(mut self, elem: web_sys::Element) -> Self {
+        self.root = ConfigRoot::RootElement(elem);
         self
     }
 
@@ -59,3 +71,8 @@ impl Config {
         self
     }
 }
+
+pub(crate) enum ConfigRoot {
+    RootName(String),
+    RootElement(web_sys::Element),
+}

+ 26 - 14
packages/web/src/dom.rs

@@ -36,24 +36,36 @@ pub struct UiEvent {
     pub data: PlatformEventData,
 }
 
+//fn get_document(elem: &web_sys::Element) ->
+
 impl WebsysDom {
     pub fn new(cfg: Config, event_channel: mpsc::UnboundedSender<UiEvent>) -> Self {
-        // eventually, we just want to let the interpreter do all the work of decoding events into our event type
-        // a match here in order to avoid some error during runtime browser test
-        let document = load_document();
-        let root = match document.get_element_by_id(&cfg.rootname) {
-            Some(root) => root,
-            None => {
-                web_sys::console::error_1(
-                    &format!(
-                        "element '#{}' not found. mounting to the body.",
-                        cfg.rootname
-                    )
-                    .into(),
-                );
-                document.create_element("body").ok().unwrap()
+        let (document, root) = match cfg.root {
+            crate::cfg::ConfigRoot::RootName(rootname) => {
+                // eventually, we just want to let the interpreter do all the work of decoding events into our event type
+                // a match here in order to avoid some error during runtime browser test
+                let document = load_document();
+                let root = match document.get_element_by_id(&rootname) {
+                    Some(root) => root,
+                    None => {
+                        web_sys::console::error_1(
+                            &format!("element '#{}' not found. mounting to the body.", rootname)
+                                .into(),
+                        );
+                        document.create_element("body").ok().unwrap()
+                    }
+                };
+                (document, root)
+            }
+            crate::cfg::ConfigRoot::RootElement(root) => {
+                let document = match root.owner_document() {
+                    Some(document) => document,
+                    None => load_document(),
+                };
+                (document, root)
             }
         };
+
         let interpreter = Channel::default();
 
         let handler: Closure<dyn FnMut(&Event)> = Closure::wrap(Box::new({

+ 15 - 15
notes/README/ZH_CN.md → translations/zh-cn/README.md

@@ -1,5 +1,5 @@
 <p align="center">
-  <img src="../header.svg">
+  <img src="../../notes/header.svg">
 </p>
 
 <div align="center">
@@ -66,17 +66,17 @@ fn app() -> Element {
 }
 ```
 
-Dioxus 可用于生成 网页前端、桌面应用、静态网站、移动端应用、TUI程序、等多类平台应用。
+Dioxus 可用于生成 网页前端、桌面应用、静态网站、移动端应用、TUI程序、 liveview程序等多类平台应用,Dioxus完全与渲染器无关,可以作用于任何渲染平台
 
 如果你能够熟悉使用 React 框架,那 Dioxus 对你来说将非常简单。
 
 ## 独特的特性:
-- 桌面程序完全基于本地环境运行(并非 Electron 的封装)
+- 只需不到10行代码就能原生运行桌面程序(并非 Electron 的封装)
 - 符合人体工程学的设计以及拥有强大的状态管理
 - 全面的内联文档 - 包含所有 HTML 元素、监听器 和 事件 指南。
-- 极快的运行效率和极高的内存效率
-- 智能项目热更新和高效的项目迭代
-- 一流的异步支持🔥
+- 极快的运行效率🔥🔥和极高的内存效率
+- 智能项目热重载以便快速迭代
+- 使用协程和Suspense来进行一流的异步支持
 - 更多内容请查看 [版本发布信息](https://dioxuslabs.com/blog/introducing-dioxus/).
 
 ## 已支持的平台
@@ -89,7 +89,7 @@ Dioxus 可用于生成 网页前端、桌面应用、静态网站、移动端应
           <li>使用 WebAssembly 直接对 DOM 进行渲染</li>
           <li>为 SSR 提供预渲染或作为客户端使用</li>
           <li>简单的 "Hello World" 仅仅 65kb, 媲美 React 框架</li>
-          <li>CLI 提供热更新支持,方便项目快速迭代</li>
+          <li>内置开发服务和热重载支持,方便项目快速迭代</li>
         </ul>
       </td>
     </tr>
@@ -98,7 +98,7 @@ Dioxus 可用于生成 网页前端、桌面应用、静态网站、移动端应
       <td>
         <ul>
           <li>使用 Webview 进行渲染 或 使用 WGPU 和 Skia(试验性的)</li>
-          <li>无多余配置,使用 `cargo build` 即可快速构建</li>
+          <li>无多余配置,简单的使用 `cargo build` 即可快速构建</li>
           <li>对原生系统的全面支持</li>
           <li>支持 Macos、Linux、Windows 等系统,极小的二进制文件</li>
         </ul>
@@ -118,9 +118,9 @@ Dioxus 可用于生成 网页前端、桌面应用、静态网站、移动端应
       <td><em>Liveview</em></td>
       <td>
         <ul>
-          <li>使用服务器渲染组件与应用程序</li>
+          <li>完全使用服务器渲染应用程序或单个组件</li>
           <li>与受欢迎的后端框架进行融合(Axum、Wrap)</li>
-          <li>及低的延迟</li>
+          <li>及低的延迟,可以同时支持10000个以上的终端程序</li>
         </ul>
       </td>
     </tr>
@@ -130,22 +130,22 @@ Dioxus 可用于生成 网页前端、桌面应用、静态网站、移动端应
         <ul>
           <li>在终端程序中渲染,类似于: <a href="https://github.com/vadimdemedes/ink"> ink.js</a></li>
           <li>支持 CSS 相关模型(类似于浏览器内的)</li>
-          <li>Built-in widgets like text input, buttons, and focus system</li>
+          <li>内置类似文字输入,按钮和焦点系统的小组件</li>
         </ul>
       </td>
     </tr>
   </table>
 </div>
 
-## Why Dioxus?
+## 为什么选择Dioxus?
 
 目前有非常多的应用开发选择,为什么偏偏要选择 Dioxus 呢?
 
-首先,Dioxus将开发者的经验放在首位。这反映在 Dioxus 特有的各种功能上。
+首先,Dioxus将开发者的体验放在首位。这体现在 Dioxus 特有的各种功能上。
 
 - 自动格式化 RSX 格式代码,并拥有 VSCode 插件作为支持。
 - 热加载基于 RSX 代码解析器,同时支持桌面程序和网页程序。
-- 强调文档的重要性,我们对所有 HTML 元素都提供文档支持。
+- 强调文档的重要性--我们的指南是完整的并且我们对所有 HTML 元素都提供文档支持。
 
 Dioxus 也是一个可扩展化的平台。
 
@@ -160,7 +160,7 @@ Dioxus 那么优秀,但什么时候它不适合我呢?
 
 ## 贡献代码
 - 在我们的 [问题追踪](https://github.com/dioxuslabs/dioxus/issues) 中汇报你遇到的问题。
-- 加入我们的 Discord 与我们交流。
+- [加入](https://discord.gg/XgGxMSkvUM)我们的 Discord 与我们交流。
 
 
 <a href="https://github.com/dioxuslabs/dioxus/graphs/contributors">