Simplify cli-config, hotreload -> devtools (drop to 0 deps, fast compile times) (#2975)

* simplify cli-config crate
* clean up configs
* add devtools crate, update cargo imports
* fix serve addr, fix websocket proxy issue
* add comment about opt profiles
* rename hot-reload to devtools
This commit is contained in:
Jonathan Kelley 2024-09-17 17:18:23 -07:00 committed by GitHub
parent 8203ee571f
commit 3fb1f739d1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
58 changed files with 1131 additions and 1654 deletions

View file

@ -4,10 +4,11 @@
"editor.formatOnSave": false
},
// "rust-analyzer.check.workspace": true,
"rust-analyzer.check.workspace": false,
"rust-analyzer.check.features": "all",
// "rust-analyzer.check.workspace": false,
// "rust-analyzer.check.features": "all",
"rust-analyzer.cargo.features": "all",
"rust-analyzer.check.allTargets": true,
"rust-analyzer.check.features": "all",
// "rust-analyzer.check.allTargets": true,
// we don't want the formatter to kick in while we're working on dioxus itself
"dioxus.formatOnSave": "disabled",
}

273
Cargo.lock generated
View file

@ -1225,42 +1225,6 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "cached"
version = "0.44.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b195e4fbc4b6862bbd065b991a34750399c119797efff72492f28a5864de8700"
dependencies = [
"async-trait",
"cached_proc_macro",
"cached_proc_macro_types",
"futures",
"hashbrown 0.13.2",
"instant",
"once_cell",
"thiserror",
"tokio",
]
[[package]]
name = "cached_proc_macro"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b48814962d2fd604c50d2b9433c2a41a0ab567779ee2c02f7fba6eca1221f082"
dependencies = [
"cached_proc_macro_types",
"darling 0.14.4",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "cached_proc_macro_types"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0"
[[package]]
name = "cairo-rs"
version = "0.18.5"
@ -2247,38 +2211,14 @@ dependencies = [
"syn 2.0.77",
]
[[package]]
name = "darling"
version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850"
dependencies = [
"darling_core 0.14.4",
"darling_macro 0.14.4",
]
[[package]]
name = "darling"
version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989"
dependencies = [
"darling_core 0.20.10",
"darling_macro 0.20.10",
]
[[package]]
name = "darling_core"
version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim 0.10.0",
"syn 1.0.109",
"darling_core",
"darling_macro",
]
[[package]]
@ -2295,24 +2235,13 @@ dependencies = [
"syn 2.0.77",
]
[[package]]
name = "darling_macro"
version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e"
dependencies = [
"darling_core 0.14.4",
"quote",
"syn 1.0.109",
]
[[package]]
name = "darling_macro"
version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
dependencies = [
"darling_core 0.20.10",
"darling_core",
"quote",
"syn 2.0.77",
]
@ -2437,9 +2366,9 @@ dependencies = [
"dioxus-core",
"dioxus-core-macro",
"dioxus-desktop",
"dioxus-devtools",
"dioxus-fullstack",
"dioxus-hooks",
"dioxus-hot-reload",
"dioxus-html",
"dioxus-liveview",
"dioxus-mobile",
@ -2511,7 +2440,7 @@ dependencies = [
"dioxus-cli-config",
"dioxus-core",
"dioxus-core-types",
"dioxus-hot-reload",
"dioxus-devtools",
"dioxus-html",
"dioxus-rsx",
"dioxus-rsx-hotreload",
@ -2540,7 +2469,7 @@ dependencies = [
"ratatui",
"rayon",
"regex",
"reqwest 0.12.7",
"reqwest",
"rustls 0.23.13",
"serde",
"serde_json",
@ -2548,6 +2477,7 @@ dependencies = [
"syn 2.0.77",
"tar",
"tauri-bundler",
"tauri-utils",
"tempfile",
"thiserror",
"tokio",
@ -2568,19 +2498,6 @@ dependencies = [
[[package]]
name = "dioxus-cli-config"
version = "0.6.0-alpha.2"
dependencies = [
"built",
"cargo_toml",
"clap",
"dirs",
"once_cell",
"serde",
"serde_json",
"tauri-bundler",
"tauri-utils",
"toml 0.8.19",
"tracing",
]
[[package]]
name = "dioxus-config-macro"
@ -2605,7 +2522,7 @@ dependencies = [
"manganis",
"pretty_assertions",
"rand 0.8.5",
"reqwest 0.12.7",
"reqwest",
"rustc-hash 1.1.0",
"rustversion",
"serde",
@ -2649,8 +2566,8 @@ dependencies = [
"dioxus",
"dioxus-cli-config",
"dioxus-core",
"dioxus-devtools",
"dioxus-hooks",
"dioxus-hot-reload",
"dioxus-html",
"dioxus-interpreter-js",
"dioxus-signals",
@ -2667,7 +2584,7 @@ dependencies = [
"objc",
"objc_id",
"rand 0.8.5",
"reqwest 0.12.7",
"reqwest",
"rfd",
"rustc-hash 1.1.0",
"separator",
@ -2685,6 +2602,29 @@ dependencies = [
"wry",
]
[[package]]
name = "dioxus-devtools"
version = "0.6.0-alpha.2"
dependencies = [
"dioxus-core",
"dioxus-devtools-types",
"dioxus-signals",
"serde",
"serde_json",
"tokio",
"tracing",
"tungstenite 0.23.0",
"warnings",
]
[[package]]
name = "dioxus-devtools-types"
version = "0.6.0-alpha.2"
dependencies = [
"dioxus-core",
"serde",
]
[[package]]
name = "dioxus-examples"
version = "0.6.0-alpha.2"
@ -2700,7 +2640,7 @@ dependencies = [
"http-range",
"manganis",
"rand 0.8.5",
"reqwest 0.12.7",
"reqwest",
"separator",
"serde",
"serde_json",
@ -2730,11 +2670,10 @@ dependencies = [
"base64 0.22.1",
"bytes",
"ciborium",
"clap",
"dioxus",
"dioxus-cli-config",
"dioxus-desktop",
"dioxus-hot-reload",
"dioxus-devtools",
"dioxus-interpreter-js",
"dioxus-isrg",
"dioxus-lib",
@ -2776,7 +2715,7 @@ dependencies = [
"futures-channel",
"futures-util",
"generational-box",
"reqwest 0.12.7",
"reqwest",
"rustversion",
"slab",
"tokio",
@ -2785,31 +2724,6 @@ dependencies = [
"web-sys",
]
[[package]]
name = "dioxus-hot-reload"
version = "0.6.0-alpha.2"
dependencies = [
"chrono",
"dioxus-cli-config",
"dioxus-core",
"dioxus-html",
"dioxus-rsx",
"dioxus-rsx-hotreload",
"dioxus-signals",
"execute",
"futures-util",
"ignore",
"notify",
"once_cell",
"serde",
"serde_json",
"tokio",
"tokio-stream",
"tokio-tungstenite 0.23.1",
"tracing",
"warnings",
]
[[package]]
name = "dioxus-html"
version = "0.6.0-alpha.2"
@ -2905,7 +2819,7 @@ dependencies = [
"dioxus",
"dioxus-cli-config",
"dioxus-core",
"dioxus-hot-reload",
"dioxus-devtools",
"dioxus-html",
"dioxus-interpreter-js",
"futures-channel",
@ -3087,7 +3001,7 @@ dependencies = [
"once_cell",
"parking_lot",
"rand 0.8.5",
"reqwest 0.12.7",
"reqwest",
"rustc-hash 1.1.0",
"serde",
"simple_logger",
@ -3116,8 +3030,8 @@ dependencies = [
"criterion",
"dioxus",
"dioxus-cli-config",
"dioxus-devtools",
"dioxus-fullstack",
"dioxus-hot-reload",
"dioxus-isrg",
"dioxus-lib",
"dioxus-router",
@ -3147,7 +3061,7 @@ dependencies = [
"dioxus",
"dioxus-core",
"dioxus-core-types",
"dioxus-hot-reload",
"dioxus-devtools",
"dioxus-html",
"dioxus-interpreter-js",
"dioxus-signals",
@ -3301,10 +3215,9 @@ checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
name = "ecommerce-site"
version = "0.1.1"
dependencies = [
"cached",
"chrono",
"dioxus",
"reqwest 0.11.27",
"reqwest",
"serde",
]
@ -3374,7 +3287,7 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59c3b24c345d8c314966bdc1832f6c2635bfcce8e7cf363bd115987bba2ee242"
dependencies = [
"darling 0.20.10",
"darling",
"proc-macro2",
"quote",
"syn 2.0.77",
@ -3787,7 +3700,7 @@ version = "0.1.0"
dependencies = [
"chrono",
"dioxus",
"reqwest 0.12.7",
"reqwest",
"serde",
"tracing",
"tracing-subscriber",
@ -3799,7 +3712,7 @@ name = "fullstack-hello-world-example"
version = "0.1.0"
dependencies = [
"dioxus",
"reqwest 0.12.7",
"reqwest",
"serde",
"simple_logger",
"tracing",
@ -5242,19 +5155,6 @@ dependencies = [
"tokio-io-timeout",
]
[[package]]
name = "hyper-tls"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
dependencies = [
"bytes",
"hyper 0.14.30",
"native-tls",
"tokio",
"tokio-native-tls",
]
[[package]]
name = "hyper-tls"
version = "0.6.0"
@ -6236,7 +6136,7 @@ dependencies = [
"railwind",
"ravif",
"rayon",
"reqwest 0.12.7",
"reqwest",
"rustc-hash 1.1.0",
"serde",
"serde_json",
@ -8245,46 +8145,6 @@ dependencies = [
"bytecheck",
]
[[package]]
name = "reqwest"
version = "0.11.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62"
dependencies = [
"base64 0.21.7",
"bytes",
"encoding_rs",
"futures-core",
"futures-util",
"h2 0.3.26",
"http 0.2.12",
"http-body 0.4.6",
"hyper 0.14.30",
"hyper-tls 0.5.0",
"ipnet",
"js-sys",
"log",
"mime",
"native-tls",
"once_cell",
"percent-encoding",
"pin-project-lite",
"rustls-pemfile 1.0.4",
"serde",
"serde_json",
"serde_urlencoded",
"sync_wrapper 0.1.2",
"system-configuration 0.5.1",
"tokio",
"tokio-native-tls",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"winreg 0.50.0",
]
[[package]]
name = "reqwest"
version = "0.12.7"
@ -8303,7 +8163,7 @@ dependencies = [
"http-body-util",
"hyper 1.4.1",
"hyper-rustls",
"hyper-tls 0.6.0",
"hyper-tls",
"hyper-util",
"ipnet",
"js-sys",
@ -8322,7 +8182,7 @@ dependencies = [
"serde_json",
"serde_urlencoded",
"sync_wrapper 1.0.1",
"system-configuration 0.6.1",
"system-configuration",
"tokio",
"tokio-native-tls",
"tokio-rustls",
@ -8941,7 +8801,7 @@ version = "3.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350"
dependencies = [
"darling 0.20.10",
"darling",
"proc-macro2",
"quote",
"syn 2.0.77",
@ -8965,7 +8825,7 @@ dependencies = [
"inventory",
"js-sys",
"once_cell",
"reqwest 0.12.7",
"reqwest",
"send_wrapper",
"serde",
"serde_json",
@ -11214,17 +11074,6 @@ dependencies = [
"futures-core",
]
[[package]]
name = "system-configuration"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
dependencies = [
"bitflags 1.3.2",
"core-foundation 0.9.4",
"system-configuration-sys 0.5.0",
]
[[package]]
name = "system-configuration"
version = "0.6.1"
@ -11233,17 +11082,7 @@ checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
dependencies = [
"bitflags 2.6.0",
"core-foundation 0.9.4",
"system-configuration-sys 0.6.0",
]
[[package]]
name = "system-configuration-sys"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
dependencies = [
"core-foundation-sys",
"libc",
"system-configuration-sys",
]
[[package]]
@ -11378,7 +11217,7 @@ dependencies = [
"uuid",
"walkdir",
"windows-sys 0.48.0",
"winreg 0.51.0",
"winreg",
"zip",
]
@ -13247,16 +13086,6 @@ dependencies = [
"memchr",
]
[[package]]
name = "winreg"
version = "0.50.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
dependencies = [
"cfg-if",
"windows-sys 0.48.0",
]
[[package]]
name = "winreg"
version = "0.51.0"

View file

@ -28,7 +28,8 @@ members = [
"packages/rsx-rosetta",
"packages/generational-box",
"packages/signals",
"packages/hot-reload",
"packages/devtools",
"packages/devtools-types",
"packages/fullstack",
"packages/server-macro",
"packages/static-generation",
@ -91,9 +92,10 @@ dioxus-rsx = { path = "packages/rsx", version = "0.6.0-alpha.0" }
dioxus-rsx-hotreload = { path = "packages/rsx-hotreload", version = "0.6.0-alpha.0" }
dioxus-rsx-rosetta = { path = "packages/rsx-rosetta", version = "0.6.0-alpha.0" }
dioxus-signals = { path = "packages/signals", version = "0.6.0-alpha.0" }
dioxus-cli-config = { path = "packages/cli-config", version = "0.6.0-alpha.0", default-features = false}
dioxus-cli-config = { path = "packages/cli-config", version = "0.6.0-alpha.0" }
generational-box = { path = "packages/generational-box", version = "0.6.0-alpha.0" }
dioxus-hot-reload = { path = "packages/hot-reload", version = "0.6.0-alpha.0" }
dioxus-devtools = { path = "packages/devtools", version = "0.6.0-alpha.0" }
dioxus-devtools-types = { path = "packages/devtools-types", version = "0.6.0-alpha.0" }
dioxus-fullstack = { path = "packages/fullstack", version = "0.6.0-alpha.1" }
dioxus-static-site-generation = { path = "packages/static-generation", version = "0.6.0-alpha.0" }
dioxus_server_macro = { path = "packages/server-macro", version = "0.6.0-alpha.0", default-features = false }
@ -171,6 +173,7 @@ web-sys = { version = "0.3.56", default-features = false }
dirs = "5.0.1"
cargo-config2 = "0.1.26"
criterion = { version = "0.5" }
walrus = "*"
# desktop
wry = { version = "0.43.0", default-features = false }
@ -190,13 +193,9 @@ objc_id = "0.1.1"
[profile.dev.package.dioxus-core-macro]
opt-level = 3
# Enable a small amount of optimization in debug mode
[profile.cli-dev]
inherits = "dev"
opt-level = 1
# Enable high optimizations for dependencies (incl. Bevy), but not for our code:
[profile.cli-dev.package."*"]
# wasm bindgen is slooooooow, but it's because we're actually processing the wasm
# so, lets just bump up walrus to make it faster, no need for any special profiles
[profile.dev.package.walrus]
opt-level = 3
# Disable debug assertions to check the released path of core and other packages, but build without optimizations to keep build times quick

View file

@ -6,11 +6,15 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
cached = "0.44.0"
dioxus = { workspace = true, features = ["fullstack", "router"] }
reqwest = { version = "0.11.16", features = ["json"] }
serde = "1.0.160"
chrono = { version = "0.4.24", features = ["serde"] }
reqwest = { workspace = true, features = ["json"] }
serde = { workspace = true }
[target.'cfg(target_family = "wasm")'.dependencies]
chrono = { workspace = true, features = ["serde", "wasmbind"] }
[target.'cfg(not(target_family = "wasm"))'.dependencies]
chrono = { workspace = true, features = ["serde"] }
[features]
web = ["dioxus/web"]

View file

@ -55,7 +55,6 @@ impl Display for Sort {
}
// Cache up to 100 requests, invalidating them after 60 seconds
#[cached::proc_macro::cached(size = 100, time = 60, result = true)]
pub(crate) async fn fetch_user_carts(user_id: usize) -> Result<Vec<Cart>, reqwest::Error> {
reqwest::get(format!(
"https://fakestoreapi.com/carts/user/{user_id}?startdate=2019-12-10&enddate=2023-01-01"
@ -66,7 +65,6 @@ pub(crate) async fn fetch_user_carts(user_id: usize) -> Result<Vec<Cart>, reqwes
}
// Cache up to 100 requests, invalidating them after 60 seconds
#[cached::proc_macro::cached(size = 100, time = 60, result = true)]
pub(crate) async fn fetch_user(user_id: usize) -> dioxus::Result<Product> {
Ok(
reqwest::get(format!("https://fakestoreapi.com/users/{user_id}"))
@ -77,7 +75,6 @@ pub(crate) async fn fetch_user(user_id: usize) -> dioxus::Result<Product> {
}
// Cache up to 100 requests, invalidating them after 60 seconds
#[cached::proc_macro::cached(size = 100, time = 60, result = true)]
pub(crate) async fn fetch_product(product_id: usize) -> dioxus::Result<Product> {
Ok(
reqwest::get(format!("https://fakestoreapi.com/products/{product_id}"))
@ -88,7 +85,6 @@ pub(crate) async fn fetch_product(product_id: usize) -> dioxus::Result<Product>
}
// Cache up to 100 requests, invalidating them after 60 seconds
#[cached::proc_macro::cached(size = 100, time = 60, result = true)]
pub(crate) async fn fetch_products(count: usize, sort: Sort) -> dioxus::Result<Vec<Product>> {
Ok(reqwest::get(format!(
"https://fakestoreapi.com/products/?sort={sort}&limit={count}"

View file

@ -1,36 +1,6 @@
[package]
name = "dioxus-cli-config"
version = { workspace = true }
authors = ["Jonathan Kelley"]
edition = "2021"
description = "Configuration for the Dioxus CLI"
repository = "https://github.com/DioxusLabs/dioxus/"
license = "MIT OR Apache-2.0"
keywords = ["react", "gui", "cli", "dioxus", "wasm"]
version.workspace = true
[dependencies]
clap = { version = "4.2", features = ["derive"], optional = true }
serde = { version = "1.0.136", features = ["derive"] }
serde_json = "1.0.79"
toml = { workspace = true, optional = true }
cargo_toml = { workspace = true, optional = true }
once_cell = "1.18.0"
tracing = { workspace = true }
# bundling
tauri-bundler = { workspace = true, optional = true }
tauri-utils = { workspace = true, optional = true }
dirs = { workspace = true, optional = true }
[build-dependencies]
built = { version = "=0.7.4", features = ["git2"] }
[features]
default = ["read-config"]
cli = ["dep:tauri-bundler", "dep:tauri-utils", "read-from-args", "dep:toml", "dep:cargo_toml", "dep:dirs"]
read-config = []
read-from-args = ["dep:clap"]
[package.metadata.docs.rs]
cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"]

View file

@ -1,5 +1,7 @@
<div>
<h1>📦✨ Dioxus CLI Configuration</h1>
</div>
# dioxus-cli-config
The **dioxus-cli-config** contains the configuration for the **dioxus-cli**.
A crate that provides key/value names and types for configuring Dioxus applications at runtime.
This crate exists for us to very cleanly define the exact fields we want to pass down to Dioxus applications at runtime but without exposing the entire config object.
This leads to faster compile times, smaller binaries, and a clearer distinction between the config and the application.

View file

@ -1,3 +0,0 @@
fn main() {
built::write_built_file().expect("Failed to acquire build-time information");
}

View file

@ -1,260 +0,0 @@
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct BundleConfig {
pub identifier: Option<String>,
pub publisher: Option<String>,
pub icon: Option<Vec<String>>,
pub resources: Option<Vec<String>>,
pub copyright: Option<String>,
pub category: Option<String>,
pub short_description: Option<String>,
pub long_description: Option<String>,
pub external_bin: Option<Vec<String>>,
pub deb: Option<DebianSettings>,
pub macos: Option<MacOsSettings>,
pub windows: Option<WindowsSettings>,
}
#[cfg(feature = "cli")]
impl From<BundleConfig> for tauri_bundler::BundleSettings {
fn from(val: BundleConfig) -> Self {
tauri_bundler::BundleSettings {
identifier: val.identifier,
publisher: val.publisher,
icon: val.icon,
resources: val.resources,
copyright: val.copyright,
category: val.category.and_then(|c| c.parse().ok()),
short_description: val.short_description,
long_description: val.long_description,
external_bin: val.external_bin,
deb: val.deb.map(Into::into).unwrap_or_default(),
macos: val.macos.map(Into::into).unwrap_or_default(),
windows: val.windows.map(Into::into).unwrap_or_default(),
..Default::default()
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct DebianSettings {
pub depends: Option<Vec<String>>,
pub files: HashMap<PathBuf, PathBuf>,
pub nsis: Option<NsisSettings>,
}
#[cfg(feature = "cli")]
impl From<DebianSettings> for tauri_bundler::DebianSettings {
fn from(val: DebianSettings) -> Self {
tauri_bundler::DebianSettings {
depends: val.depends,
files: val.files,
desktop_template: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct WixSettings {
pub language: Vec<(String, Option<PathBuf>)>,
pub template: Option<PathBuf>,
pub fragment_paths: Vec<PathBuf>,
pub component_group_refs: Vec<String>,
pub component_refs: Vec<String>,
pub feature_group_refs: Vec<String>,
pub feature_refs: Vec<String>,
pub merge_refs: Vec<String>,
pub skip_webview_install: bool,
pub license: Option<PathBuf>,
pub enable_elevated_update_task: bool,
pub banner_path: Option<PathBuf>,
pub dialog_image_path: Option<PathBuf>,
pub fips_compliant: bool,
}
#[cfg(feature = "cli")]
impl From<WixSettings> for tauri_bundler::WixSettings {
fn from(val: WixSettings) -> Self {
tauri_bundler::WixSettings {
language: tauri_bundler::bundle::WixLanguage({
let mut languages: Vec<_> = val
.language
.iter()
.map(|l| {
(
l.0.clone(),
tauri_bundler::bundle::WixLanguageConfig {
locale_path: l.1.clone(),
},
)
})
.collect();
if languages.is_empty() {
languages.push(("en-US".into(), Default::default()));
}
languages
}),
template: val.template,
fragment_paths: val.fragment_paths,
component_group_refs: val.component_group_refs,
component_refs: val.component_refs,
feature_group_refs: val.feature_group_refs,
feature_refs: val.feature_refs,
merge_refs: val.merge_refs,
skip_webview_install: val.skip_webview_install,
license: val.license,
enable_elevated_update_task: val.enable_elevated_update_task,
banner_path: val.banner_path,
dialog_image_path: val.dialog_image_path,
fips_compliant: val.fips_compliant,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct MacOsSettings {
pub frameworks: Option<Vec<String>>,
pub minimum_system_version: Option<String>,
pub license: Option<String>,
pub exception_domain: Option<String>,
pub signing_identity: Option<String>,
pub provider_short_name: Option<String>,
pub entitlements: Option<String>,
pub info_plist_path: Option<PathBuf>,
}
#[cfg(feature = "cli")]
impl From<MacOsSettings> for tauri_bundler::MacOsSettings {
fn from(val: MacOsSettings) -> Self {
tauri_bundler::MacOsSettings {
frameworks: val.frameworks,
minimum_system_version: val.minimum_system_version,
license: val.license,
exception_domain: val.exception_domain,
signing_identity: val.signing_identity,
provider_short_name: val.provider_short_name,
entitlements: val.entitlements,
info_plist_path: val.info_plist_path,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WindowsSettings {
pub digest_algorithm: Option<String>,
pub certificate_thumbprint: Option<String>,
pub timestamp_url: Option<String>,
pub tsp: bool,
pub wix: Option<WixSettings>,
pub icon_path: Option<PathBuf>,
pub webview_install_mode: WebviewInstallMode,
pub webview_fixed_runtime_path: Option<PathBuf>,
pub allow_downgrades: bool,
pub nsis: Option<NsisSettings>,
}
#[cfg(feature = "cli")]
impl From<WindowsSettings> for tauri_bundler::WindowsSettings {
fn from(val: WindowsSettings) -> Self {
tauri_bundler::WindowsSettings {
digest_algorithm: val.digest_algorithm,
certificate_thumbprint: val.certificate_thumbprint,
timestamp_url: val.timestamp_url,
tsp: val.tsp,
wix: val.wix.map(Into::into),
icon_path: val.icon_path.unwrap_or("icons/icon.ico".into()),
webview_install_mode: val.webview_install_mode.into(),
webview_fixed_runtime_path: val.webview_fixed_runtime_path,
allow_downgrades: val.allow_downgrades,
nsis: val.nsis.map(Into::into),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NsisSettings {
pub template: Option<PathBuf>,
pub license: Option<PathBuf>,
pub header_image: Option<PathBuf>,
pub sidebar_image: Option<PathBuf>,
pub installer_icon: Option<PathBuf>,
pub install_mode: NSISInstallerMode,
pub languages: Option<Vec<String>>,
pub custom_language_files: Option<HashMap<String, PathBuf>>,
pub display_language_selector: bool,
}
#[cfg(feature = "cli")]
impl From<NsisSettings> for tauri_bundler::NsisSettings {
fn from(val: NsisSettings) -> Self {
tauri_bundler::NsisSettings {
license: val.license,
header_image: val.header_image,
sidebar_image: val.sidebar_image,
installer_icon: val.installer_icon,
install_mode: val.install_mode.into(),
languages: val.languages,
display_language_selector: val.display_language_selector,
custom_language_files: None,
template: None,
compression: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum NSISInstallerMode {
CurrentUser,
PerMachine,
Both,
}
#[cfg(feature = "cli")]
impl From<NSISInstallerMode> for tauri_utils::config::NSISInstallerMode {
fn from(val: NSISInstallerMode) -> Self {
match val {
NSISInstallerMode::CurrentUser => tauri_utils::config::NSISInstallerMode::CurrentUser,
NSISInstallerMode::PerMachine => tauri_utils::config::NSISInstallerMode::PerMachine,
NSISInstallerMode::Both => tauri_utils::config::NSISInstallerMode::Both,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum WebviewInstallMode {
Skip,
DownloadBootstrapper { silent: bool },
EmbedBootstrapper { silent: bool },
OfflineInstaller { silent: bool },
FixedRuntime { path: PathBuf },
}
#[cfg(feature = "cli")]
impl WebviewInstallMode {
fn into(self) -> tauri_utils::config::WebviewInstallMode {
match self {
Self::Skip => tauri_utils::config::WebviewInstallMode::Skip,
Self::DownloadBootstrapper { silent } => {
tauri_utils::config::WebviewInstallMode::DownloadBootstrapper { silent }
}
Self::EmbedBootstrapper { silent } => {
tauri_utils::config::WebviewInstallMode::EmbedBootstrapper { silent }
}
Self::OfflineInstaller { silent } => {
tauri_utils::config::WebviewInstallMode::OfflineInstaller { silent }
}
Self::FixedRuntime { path } => {
tauri_utils::config::WebviewInstallMode::FixedRuntime { path }
}
}
}
}
impl Default for WebviewInstallMode {
fn default() -> Self {
Self::OfflineInstaller { silent: false }
}
}

View file

@ -1,372 +0,0 @@
use crate::BundleConfig;
use serde::{Deserialize, Serialize};
use std::fmt::Display;
use std::path::PathBuf;
use std::str::FromStr;
#[derive(
Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Debug, Default,
)]
#[cfg_attr(feature = "cli", derive(clap::ValueEnum))]
#[non_exhaustive]
pub enum Platform {
/// Targeting the web platform using WASM
#[cfg_attr(feature = "cli", clap(name = "web"))]
#[serde(rename = "web")]
#[default]
Web,
/// Targeting the desktop platform using Tao/Wry-based webview
#[cfg_attr(feature = "cli", clap(name = "desktop"))]
#[serde(rename = "desktop")]
Desktop,
/// Targeting the server platform using Axum and Dioxus-Fullstack
#[cfg_attr(feature = "cli", clap(name = "fullstack"))]
#[serde(rename = "fullstack")]
Fullstack,
/// Targeting the static generation platform using SSR and Dioxus-Fullstack
#[cfg_attr(feature = "cli", clap(name = "static-generation"))]
#[serde(rename = "static-generation")]
StaticGeneration,
/// Targeting the static generation platform using SSR and Dioxus-Fullstack
#[cfg_attr(feature = "cli", clap(name = "liveview"))]
#[serde(rename = "liveview")]
Liveview,
}
/// An error that occurs when a platform is not recognized
pub struct UnknownPlatformError;
impl std::fmt::Display for UnknownPlatformError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Unknown platform")
}
}
impl FromStr for Platform {
type Err = UnknownPlatformError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"web" => Ok(Self::Web),
"desktop" => Ok(Self::Desktop),
"fullstack" => Ok(Self::Fullstack),
"static-generation" => Ok(Self::StaticGeneration),
"liveview" => Ok(Self::Liveview),
_ => Err(UnknownPlatformError),
}
}
}
impl Display for Platform {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let feature = self.feature_name();
f.write_str(feature)
}
}
impl Platform {
/// All platforms the dioxus CLI supports
pub const ALL: &'static [Self] = &[
Platform::Web,
Platform::Desktop,
Platform::Fullstack,
Platform::StaticGeneration,
];
/// Get the feature name for the platform in the dioxus crate
pub fn feature_name(&self) -> &str {
match self {
Platform::Web => "web",
Platform::Desktop => "desktop",
Platform::Fullstack => "fullstack",
Platform::StaticGeneration => "static-generation",
Platform::Liveview => "liveview",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DioxusConfig {
pub application: ApplicationConfig,
#[serde(default)]
pub web: WebConfig,
#[serde(default)]
pub desktop: DesktopConfig,
#[serde(default)]
pub bundle: BundleConfig,
}
impl Default for DioxusConfig {
fn default() -> Self {
let name = default_name();
Self {
application: ApplicationConfig {
name: name.clone(),
default_platform: default_platform(),
out_dir: out_dir_default(),
asset_dir: asset_dir_default(),
sub_package: None,
},
web: WebConfig {
app: WebAppConfig {
title: default_title(),
base_path: None,
},
proxy: vec![],
watcher: Default::default(),
resource: WebResourceConfig {
dev: WebDevResourceConfig {
style: vec![],
script: vec![],
},
style: Some(vec![]),
script: Some(vec![]),
},
https: WebHttpsConfig {
enabled: None,
mkcert: None,
key_path: None,
cert_path: None,
},
pre_compress: true,
wasm_opt: Default::default(),
},
desktop: DesktopConfig::default(),
bundle: BundleConfig {
identifier: Some(format!("io.github.{name}")),
publisher: Some(name),
..Default::default()
},
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApplicationConfig {
#[serde(default = "default_name")]
pub name: String,
#[serde(default = "default_platform")]
pub default_platform: Platform,
#[serde(default = "out_dir_default")]
pub out_dir: PathBuf,
#[serde(default = "asset_dir_default")]
pub asset_dir: PathBuf,
#[serde(default)]
pub sub_package: Option<String>,
}
fn default_name() -> String {
"my-cool-project".into()
}
fn default_platform() -> Platform {
Platform::Web
}
fn asset_dir_default() -> PathBuf {
PathBuf::from("public")
}
fn out_dir_default() -> PathBuf {
PathBuf::from("dist")
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WebConfig {
#[serde(default)]
pub app: WebAppConfig,
#[serde(default)]
pub proxy: Vec<WebProxyConfig>,
#[serde(default)]
pub watcher: WebWatcherConfig,
#[serde(default)]
pub resource: WebResourceConfig,
#[serde(default)]
pub https: WebHttpsConfig,
/// Whether to enable pre-compression of assets and wasm during a web build in release mode
#[serde(default = "true_bool")]
pub pre_compress: bool,
/// The wasm-opt configuration
#[serde(default)]
pub wasm_opt: WasmOptConfig,
}
impl Default for WebConfig {
fn default() -> Self {
Self {
pre_compress: true_bool(),
app: Default::default(),
https: Default::default(),
wasm_opt: Default::default(),
proxy: Default::default(),
watcher: Default::default(),
resource: Default::default(),
}
}
}
/// Represents configuration items for the desktop platform.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DesktopConfig {
/// Describes whether a debug-mode desktop app should be always-on-top.
#[serde(default)]
pub always_on_top: bool,
}
impl Default for DesktopConfig {
fn default() -> Self {
Self {
always_on_top: true,
}
}
}
/// The wasm-opt configuration
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct WasmOptConfig {
/// The wasm-opt level to use for release builds [default: s]
/// Options:
/// - z: optimize aggressively for size
/// - s: optimize for size
/// - 1: optimize for speed
/// - 2: optimize for more for speed
/// - 3: optimize for even more for speed
/// - 4: optimize aggressively for speed
#[serde(default)]
pub level: WasmOptLevel,
/// Keep debug symbols in the wasm file
#[serde(default = "false_bool")]
pub debug: bool,
}
/// The wasm-opt level to use for release web builds [default: 4]
#[derive(Default, Debug, Copy, Clone, Serialize, Deserialize)]
pub enum WasmOptLevel {
/// Optimize aggressively for size
#[serde(rename = "z")]
Z,
/// Optimize for size
#[serde(rename = "s")]
S,
/// Don't optimize
#[serde(rename = "0")]
Zero,
/// Optimize for speed
#[serde(rename = "1")]
One,
/// Optimize for more for speed
#[serde(rename = "2")]
Two,
/// Optimize for even more for speed
#[serde(rename = "3")]
Three,
/// Optimize aggressively for speed
#[serde(rename = "4")]
#[default]
Four,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WebAppConfig {
#[serde(default = "default_title")]
pub title: String,
pub base_path: Option<String>,
}
impl WebAppConfig {
/// Get the normalized base path for the application with `/` trimmed from both ends. If the base path is not set, this will return `.`.
pub fn base_path(&self) -> &str {
match &self.base_path {
Some(path) => path.trim_matches('/'),
None => ".",
}
}
}
impl Default for WebAppConfig {
fn default() -> Self {
Self {
title: default_title(),
base_path: None,
}
}
}
fn default_title() -> String {
"dioxus | ⛺".into()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WebProxyConfig {
pub backend: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WebWatcherConfig {
#[serde(default = "watch_path_default")]
pub watch_path: Vec<PathBuf>,
#[serde(default)]
pub reload_html: bool,
#[serde(default = "true_bool")]
pub index_on_404: bool,
}
impl Default for WebWatcherConfig {
fn default() -> Self {
Self {
watch_path: watch_path_default(),
reload_html: false,
index_on_404: true,
}
}
}
fn watch_path_default() -> Vec<PathBuf> {
vec![PathBuf::from("src"), PathBuf::from("examples")]
}
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct WebResourceConfig {
pub dev: WebDevResourceConfig,
pub style: Option<Vec<PathBuf>>,
pub script: Option<Vec<PathBuf>>,
}
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct WebDevResourceConfig {
#[serde(default)]
pub style: Vec<PathBuf>,
#[serde(default)]
pub script: Vec<PathBuf>,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct WebHttpsConfig {
pub enabled: Option<bool>,
pub mkcert: Option<bool>,
pub key_path: Option<String>,
pub cert_path: Option<String>,
}
fn true_bool() -> bool {
true
}
fn false_bool() -> bool {
false
}

View file

@ -1,99 +1,73 @@
#![doc = include_str!("../README.md")]
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
use std::{
net::{IpAddr, Ipv4Addr, SocketAddr},
path::PathBuf,
};
mod config;
pub use config::*;
pub const CLI_ENABLED_ENV: &str = "DIOXUS_CLI_ENABLED";
pub const SERVER_IP_ENV: &str = "IP";
pub const SERVER_PORT_ENV: &str = "PORT";
pub const DEVSERVER_RAW_ADDR_ENV: &str = "DIOXUS_DEVSERVER_ADDR";
pub const ALWAYS_ON_TOP_ENV: &str = "DIOXUS_ALWAYS_ON_TOP";
pub const ASSET_ROOT_ENV: &str = "DIOXUS_ASSET_ROOT";
pub const APP_TITLE_ENV: &str = "DIOXUS_APP_TITLE";
pub const OUT_DIR: &str = "DIOXUS_OUT_DIR";
mod bundle;
pub use bundle::*;
/// todo: this is not implemented but we're going to reserve this
///
/// technically this is only passed on "launch" so if you close the app, this will be lost
pub const IOS_DEVSERVER_ADDR_ENV: &str = "SIMCTL_CHILD_DIOXUS_DEVSERVER_ADDR";
mod serve;
pub use serve::*;
mod build_info;
#[doc(hidden)]
pub mod __private {
use crate::DioxusConfig;
pub(crate) const DIOXUS_CLI_VERSION: &str = "DIOXUS_CLI_VERSION";
pub(crate) const CONFIG_ENV: &str = "DIOXUS_CONFIG";
pub(crate) const CONFIG_BASE_PATH_ENV: &str = "DIOXUS_CONFIG_BASE_PATH";
pub fn save_config(config: &DioxusConfig, cli_version: &str) -> CrateConfigDropGuard {
std::env::set_var(CONFIG_ENV, serde_json::to_string(config).unwrap());
std::env::set_var(
CONFIG_BASE_PATH_ENV,
config.web.app.base_path.clone().unwrap_or_default(),
);
std::env::set_var(DIOXUS_CLI_VERSION, cli_version);
CrateConfigDropGuard
/// Get the address of the devserver for use over a raw socket
///
/// This is not a websocket! There's no protocol!
pub fn devserver_raw_addr() -> Option<SocketAddr> {
std::env::var(DEVSERVER_RAW_ADDR_ENV)
.map(|s| s.parse().ok())
.ok()
.flatten()
}
/// A guard that removes the config from the environment when dropped.
pub struct CrateConfigDropGuard;
impl Drop for CrateConfigDropGuard {
fn drop(&mut self) {
std::env::remove_var(CONFIG_ENV);
std::env::remove_var(CONFIG_BASE_PATH_ENV);
std::env::remove_var(DIOXUS_CLI_VERSION);
}
pub fn devserver_ws_endpoint() -> Option<String> {
let addr = devserver_raw_addr()?;
Some(format!("ws://{addr}/_dioxus"))
}
#[cfg(feature = "read-config")]
/// The environment variable that stores the CLIs serve configuration.
/// We use this to communicate between the CLI and the server for fullstack applications.
pub const SERVE_ENV: &str = "DIOXUS_SERVE_CONFIG";
pub fn server_ip() -> Option<IpAddr> {
std::env::var(SERVER_IP_ENV)
.ok()
.and_then(|s| s.parse().ok())
}
/// An error that occurs when the dioxus CLI was not used to build the application.
#[derive(Debug)]
pub struct DioxusCLINotUsed;
impl std::fmt::Display for DioxusCLINotUsed {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("dioxus CLI was not used to build the application")
}
pub fn server_port() -> Option<u16> {
std::env::var(SERVER_PORT_ENV)
.ok()
.and_then(|s| s.parse().ok())
}
impl std::error::Error for DioxusCLINotUsed {}
#[cfg(feature = "read-config")]
/// The current crate's configuration.
pub static CURRENT_CONFIG: once_cell::sync::Lazy<
Result<crate::config::DioxusConfig, DioxusCLINotUsed>,
> = once_cell::sync::Lazy::new(|| {
CURRENT_CONFIG_JSON
.ok_or_else(|| {
tracing::warn!("A library is trying to access the crate's configuration, but the dioxus CLI was not used to build the application.");
DioxusCLINotUsed
}).and_then(
|config|
match serde_json::from_str(config) {
Ok(config) => Ok(config),
Err(err) => {
let mut cli_version = crate::build_info::PKG_VERSION.to_string();
if let Some(hash) = crate::build_info::GIT_COMMIT_HASH_SHORT {
let hash = &hash.trim_start_matches('g')[..4];
cli_version.push_str(&format!("-{hash}"));
pub fn fullstack_address_or_localhost() -> SocketAddr {
let ip = server_ip().unwrap_or_else(|| IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
let port = server_port().unwrap_or(8080);
SocketAddr::new(ip, port)
}
let dioxus_version = std::option_env!("DIOXUS_CLI_VERSION").unwrap_or("unknown");
tracing::warn!("Failed to parse the CLI config file. This is likely caused by a mismatch between the version of the CLI and the dioxus version.\nCLI version: {cli_version}\nDioxus version: {dioxus_version}\nSerialization error: {err}");
Err(DioxusCLINotUsed)
pub fn app_title() -> Option<String> {
std::env::var(APP_TITLE_ENV).ok()
}
pub fn always_on_top() -> Option<bool> {
std::env::var(ALWAYS_ON_TOP_ENV)
.ok()
.and_then(|s| s.parse().ok())
}
)
});
#[cfg(feature = "read-config")]
/// The current crate's configuration.
pub const CURRENT_CONFIG_JSON: Option<&str> = std::option_env!("DIOXUS_CONFIG");
pub fn is_cli_enabled() -> bool {
std::env::var(CLI_ENABLED_ENV).is_ok()
}
#[cfg(feature = "read-config")]
/// The current crate's configuration.
pub const BASE_PATH: Option<&str> = std::option_env!("DIOXUS_CONFIG_BASE_PATH");
pub fn base_path() -> Option<PathBuf> {
std::env::var("DIOXUS_ASSET_ROOT").ok().map(PathBuf::from)
}
pub fn out_dir() -> Option<PathBuf> {
std::env::var(OUT_DIR).ok().map(PathBuf::from)
}

View file

@ -1,98 +0,0 @@
#![allow(unused)] // lots of configs...
use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4};
#[cfg(feature = "read-from-args")]
use clap::Parser;
/// The arguments for the address the server will run on
#[cfg(feature = "read-from-args")]
#[derive(Clone, Debug, Parser)]
pub struct AddressArguments {
/// The port the server will run on
#[clap(long)]
#[clap(default_value_t = default_port())]
pub port: u16,
/// The address the server will run on
#[clap(long, default_value_t = default_address())]
pub addr: std::net::IpAddr,
}
#[cfg(feature = "read-from-args")]
impl Default for AddressArguments {
fn default() -> Self {
Self {
port: default_port(),
addr: default_address(),
}
}
}
#[cfg(feature = "read-from-args")]
impl AddressArguments {
/// Get the address the server should run on
pub fn address(&self) -> SocketAddr {
SocketAddr::new(self.addr, self.port)
}
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct RuntimeCLIArguments {
/// The address hot reloading is running on
cli_address: SocketAddr,
/// The address the server should run on
server_socket: Option<SocketAddr>,
}
impl RuntimeCLIArguments {
/// Create a new RuntimeCLIArguments
pub fn new(cli_address: SocketAddr, server_socket: Option<SocketAddr>) -> Self {
Self {
cli_address,
server_socket,
}
}
/// Attempt to read the current serve settings from the CLI. This will only be set for the fullstack platform on recent versions of the CLI.
pub fn from_cli() -> Option<Self> {
std::env::var(crate::__private::SERVE_ENV)
.ok()
.and_then(|json| serde_json::from_str(&json).ok())
}
/// Get the address the server should run on
pub fn server_socket(&self) -> Option<SocketAddr> {
self.server_socket
}
/// Get the address the CLI is running on
pub fn cli_address(&self) -> SocketAddr {
self.cli_address
}
/// Get the address the proxied fullstack server should run on
#[cfg(feature = "read-from-args")]
pub fn fullstack_address(&self) -> AddressArguments {
let socket = self.server_socket.unwrap_or_else(|| {
SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, default_port()))
});
AddressArguments {
port: socket.port(),
addr: socket.ip(),
}
}
}
#[cfg(feature = "read-from-args")]
fn default_port() -> u16 {
8080
}
#[cfg(feature = "read-from-args")]
fn default_address() -> IpAddr {
IpAddr::V4(std::net::Ipv4Addr::new(127, 0, 0, 1))
}

View file

@ -16,7 +16,7 @@ thiserror = { workspace = true }
wasm-bindgen-cli-support = "0.2"
wasm-bindgen-shared = "0.2"
colored = "2.0.0"
dioxus-cli-config = { workspace = true, features = ["cli"], default-features = false }
dioxus-cli-config = { workspace = true }
# features
log = "0.4.14"
@ -78,6 +78,7 @@ toml_edit = "0.22.20"
# bundling
tauri-bundler = { workspace = true }
tauri-utils = { workspace = true }
# formatting
# syn = { workspace = true }
@ -95,7 +96,7 @@ dioxus-rsx-hotreload = { workspace = true }
dioxus-html = { workspace = true, features = ["hot-reload-context"] }
dioxus-core = { workspace = true, features = ["serialize"] }
dioxus-core-types = { workspace = true }
dioxus-hot-reload = { workspace = true, features = ["serve"] }
dioxus-devtools = { workspace = true }
ignore = "0.4.22"
env_logger = "0.11.3"
@ -119,9 +120,7 @@ built = { version = "=0.7.4", features = ["git2"] }
default = []
plugin = []
tokio-console = ["dep:console-subscriber"]
# when releasing dioxus, we want to enable wasm-opt
wasm-opt = ["dep:wasm-opt"]
# optimizations = ["dep:wasm-opt"]
[[bin]]
path = "src/main.rs"

View file

@ -10,11 +10,11 @@ use crate::builder::progress::CargoBuildResult;
use crate::builder::progress::Stage;
use crate::builder::progress::UpdateBuildProgress;
use crate::builder::progress::UpdateStage;
use crate::config::Platform;
use crate::link::LinkCommand;
use crate::Result;
use crate::TraceSrc;
use anyhow::Context;
use dioxus_cli_config::Platform;
use futures_channel::mpsc::UnboundedSender;
use manganis_cli_support::AssetManifest;
use manganis_cli_support::ManganisSupportGuard;
@ -115,10 +115,6 @@ impl BuildRequest {
let hash = &hash.trim_start_matches('g')[..4];
dioxus_version.push_str(&format!("-{hash}"));
}
let _guard = dioxus_cli_config::__private::save_config(
&self.dioxus_crate.dioxus_config,
&dioxus_version,
);
let _manganis_support = ManganisSupportGuard::default();
let _asset_guard =
AssetConfigDropGuard::new(self.dioxus_crate.dioxus_config.web.app.base_path.as_deref());

View file

@ -1,8 +1,8 @@
use crate::cli::serve::ServeArguments;
use crate::config::Platform;
use crate::dioxus_crate::DioxusCrate;
use crate::Result;
use crate::{build::Build, TraceSrc};
use dioxus_cli_config::{Platform, RuntimeCLIArguments};
use futures_util::stream::select_all;
use futures_util::StreamExt;
use std::net::SocketAddr;
@ -96,7 +96,6 @@ impl BuildRequest {
Platform::StaticGeneration | Platform::Fullstack => {
Self::new_fullstack(dioxus_crate.clone(), build_arguments, serve)?
}
_ => unimplemented!("Unknown platform: {platform:?}"),
})
}
@ -159,11 +158,16 @@ pub(crate) struct BuildResult {
impl BuildResult {
/// Open the executable if this is a native build
#[allow(clippy::too_many_arguments)]
pub fn open(
&self,
serve: &ServeArguments,
fullstack_address: Option<SocketAddr>,
workspace: &std::path::Path,
asset_root: &std::path::Path,
devserver_addr: SocketAddr,
app_title: String,
out_dir: PathBuf,
) -> std::io::Result<Option<Child>> {
match self.target_platform {
TargetPlatform::Web => {
@ -174,13 +178,7 @@ impl BuildResult {
tracing::info!(dx_src = ?TraceSrc::Dev, "Launching desktop app at {} 🎉", self.executable.display());
}
TargetPlatform::Server => {
if let Some(fullstack_address) = fullstack_address {
tracing::info!(
dx_src = ?TraceSrc::Dev,
"Launching fullstack server on http://{:?} 🎉",
fullstack_address
);
}
// shut this up for now - the web app will take priority in logging
}
TargetPlatform::Liveview => {
if let Some(fullstack_address) = fullstack_address {
@ -195,19 +193,36 @@ impl BuildResult {
tracing::info!(dx_src = ?TraceSrc::Dev, "Press [o] to open the app manually.");
let arguments = RuntimeCLIArguments::new(serve.address.address(), fullstack_address);
let executable = self.executable.canonicalize()?;
let mut cmd = Command::new(executable);
cmd
// When building the fullstack server, we need to forward the serve arguments (like port) to the fullstack server through env vars
.env(
dioxus_cli_config::__private::SERVE_ENV,
serde_json::to_string(&arguments).unwrap(),
)
.stderr(Stdio::piped())
// Set the env vars that the clients will expect
// These need to be stable within a release version (ie 0.6.0)
cmd.env(dioxus_cli_config::CLI_ENABLED_ENV, "true");
if let Some(addr) = fullstack_address {
cmd.env(dioxus_cli_config::SERVER_IP_ENV, addr.ip().to_string());
cmd.env(dioxus_cli_config::SERVER_PORT_ENV, addr.port().to_string());
}
cmd.env(
dioxus_cli_config::ALWAYS_ON_TOP_ENV,
serve.always_on_top.unwrap_or(true).to_string(),
);
cmd.env(
dioxus_cli_config::ASSET_ROOT_ENV,
asset_root.display().to_string(),
);
cmd.env(
dioxus_cli_config::DEVSERVER_RAW_ADDR_ENV,
devserver_addr.to_string(),
);
cmd.env(dioxus_cli_config::APP_TITLE_ENV, app_title);
cmd.env(dioxus_cli_config::OUT_DIR, out_dir.display().to_string());
cmd.stderr(Stdio::piped())
.stdout(Stdio::piped())
.kill_on_drop(true)
.current_dir(workspace);
Ok(Some(cmd.spawn()?))
}
}

View file

@ -132,11 +132,11 @@ impl BuildRequest {
// Only run wasm-opt if the feature is enabled
// Wasm-opt has an expensive build script that makes it annoying to keep enabled for iterative dev
#[cfg(feature = "wasm-opt")]
#[cfg(feature = "optimizations")]
{
// Run wasm-opt if this is a release build
if self.build_arguments.release {
use dioxus_cli_config::WasmOptLevel;
use crate::config::WasmOptLevel;
tracing::info!(dx_src = ?TraceSrc::Build, "Running optimization with wasm-opt...");
let mut options = match self.dioxus_crate.dioxus_config.web.wasm_opt.level {

View file

@ -1,7 +1,7 @@
use std::str::FromStr;
use crate::config::Platform;
use anyhow::Context;
use dioxus_cli_config::Platform;
use crate::{
builder::{BuildRequest, TargetPlatform},

View file

@ -1,7 +1,7 @@
use crate::config::AddressArguments;
use crate::{settings, tracer::CLILogControl, DioxusCrate};
use anyhow::Context;
use build::Build;
use dioxus_cli_config::AddressArguments;
use std::ops::Deref;
use super::*;

668
packages/cli/src/config.rs Normal file
View file

@ -0,0 +1,668 @@
use clap::Parser;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt::Display;
use std::net::{IpAddr, SocketAddr};
use std::path::PathBuf;
use std::str::FromStr;
#[derive(
Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Debug, Default,
)]
#[derive(clap::ValueEnum)]
#[non_exhaustive]
pub enum Platform {
/// Targeting the web platform using WASM
#[clap(name = "web")]
#[serde(rename = "web")]
#[default]
Web,
/// Targeting the desktop platform using Tao/Wry-based webview
#[clap(name = "desktop")]
#[serde(rename = "desktop")]
Desktop,
/// Targeting the server platform using Axum and Dioxus-Fullstack
#[clap(name = "fullstack")]
#[serde(rename = "fullstack")]
Fullstack,
/// Targeting the static generation platform using SSR and Dioxus-Fullstack
#[ clap(name = "static-generation")]
#[serde(rename = "static-generation")]
StaticGeneration,
/// Targeting the static generation platform using SSR and Dioxus-Fullstack
#[clap(name = "liveview")]
#[serde(rename = "liveview")]
Liveview,
}
/// An error that occurs when a platform is not recognized
pub struct UnknownPlatformError;
impl std::error::Error for UnknownPlatformError {}
impl std::fmt::Debug for UnknownPlatformError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Unknown platform")
}
}
impl std::fmt::Display for UnknownPlatformError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Unknown platform")
}
}
impl FromStr for Platform {
type Err = UnknownPlatformError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"web" => Ok(Self::Web),
"desktop" => Ok(Self::Desktop),
"fullstack" => Ok(Self::Fullstack),
"static-generation" => Ok(Self::StaticGeneration),
"liveview" => Ok(Self::Liveview),
_ => Err(UnknownPlatformError),
}
}
}
impl Display for Platform {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let feature = self.feature_name();
f.write_str(feature)
}
}
impl Platform {
/// All platforms the dioxus CLI supports
pub const ALL: &'static [Self] = &[
Platform::Web,
Platform::Desktop,
Platform::Fullstack,
Platform::StaticGeneration,
];
/// Get the feature name for the platform in the dioxus crate
pub fn feature_name(&self) -> &str {
match self {
Platform::Web => "web",
Platform::Desktop => "desktop",
Platform::Fullstack => "fullstack",
Platform::StaticGeneration => "static-generation",
Platform::Liveview => "liveview",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DioxusConfig {
pub application: ApplicationConfig,
#[serde(default)]
pub web: WebConfig,
#[serde(default)]
pub desktop: DesktopConfig,
#[serde(default)]
pub bundle: BundleConfig,
}
impl Default for DioxusConfig {
fn default() -> Self {
let name = default_name();
Self {
application: ApplicationConfig {
name: name.clone(),
default_platform: default_platform(),
out_dir: out_dir_default(),
asset_dir: asset_dir_default(),
sub_package: None,
},
web: WebConfig {
app: WebAppConfig {
title: default_title(),
base_path: None,
},
proxy: vec![],
watcher: Default::default(),
resource: WebResourceConfig {
dev: WebDevResourceConfig {
style: vec![],
script: vec![],
},
style: Some(vec![]),
script: Some(vec![]),
},
https: WebHttpsConfig {
enabled: None,
mkcert: None,
key_path: None,
cert_path: None,
},
pre_compress: true,
wasm_opt: Default::default(),
},
desktop: DesktopConfig::default(),
bundle: BundleConfig {
identifier: Some(format!("io.github.{name}")),
publisher: Some(name),
..Default::default()
},
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApplicationConfig {
#[serde(default = "default_name")]
pub name: String,
#[serde(default = "default_platform")]
pub default_platform: Platform,
#[serde(default = "out_dir_default")]
pub out_dir: PathBuf,
#[serde(default = "asset_dir_default")]
pub asset_dir: PathBuf,
#[serde(default)]
pub sub_package: Option<String>,
}
fn default_name() -> String {
"my-cool-project".into()
}
fn default_platform() -> Platform {
Platform::Web
}
fn asset_dir_default() -> PathBuf {
PathBuf::from("public")
}
fn out_dir_default() -> PathBuf {
PathBuf::from("dist")
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WebConfig {
#[serde(default)]
pub app: WebAppConfig,
#[serde(default)]
pub proxy: Vec<WebProxyConfig>,
#[serde(default)]
pub watcher: WebWatcherConfig,
#[serde(default)]
pub resource: WebResourceConfig,
#[serde(default)]
pub https: WebHttpsConfig,
/// Whether to enable pre-compression of assets and wasm during a web build in release mode
#[serde(default = "true_bool")]
pub pre_compress: bool,
/// The wasm-opt configuration
#[serde(default)]
pub wasm_opt: WasmOptConfig,
}
impl Default for WebConfig {
fn default() -> Self {
Self {
pre_compress: true_bool(),
app: Default::default(),
https: Default::default(),
wasm_opt: Default::default(),
proxy: Default::default(),
watcher: Default::default(),
resource: Default::default(),
}
}
}
/// Represents configuration items for the desktop platform.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DesktopConfig {
/// Describes whether a debug-mode desktop app should be always-on-top.
#[serde(default)]
pub always_on_top: bool,
}
impl Default for DesktopConfig {
fn default() -> Self {
Self {
always_on_top: true,
}
}
}
/// The wasm-opt configuration
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct WasmOptConfig {
/// The wasm-opt level to use for release builds [default: s]
/// Options:
/// - z: optimize aggressively for size
/// - s: optimize for size
/// - 1: optimize for speed
/// - 2: optimize for more for speed
/// - 3: optimize for even more for speed
/// - 4: optimize aggressively for speed
#[serde(default)]
pub level: WasmOptLevel,
/// Keep debug symbols in the wasm file
#[serde(default = "false_bool")]
pub debug: bool,
}
/// The wasm-opt level to use for release web builds [default: 4]
#[derive(Default, Debug, Copy, Clone, Serialize, Deserialize)]
pub enum WasmOptLevel {
/// Optimize aggressively for size
#[serde(rename = "z")]
Z,
/// Optimize for size
#[serde(rename = "s")]
S,
/// Don't optimize
#[serde(rename = "0")]
Zero,
/// Optimize for speed
#[serde(rename = "1")]
One,
/// Optimize for more for speed
#[serde(rename = "2")]
Two,
/// Optimize for even more for speed
#[serde(rename = "3")]
Three,
/// Optimize aggressively for speed
#[serde(rename = "4")]
#[default]
Four,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WebAppConfig {
#[serde(default = "default_title")]
pub title: String,
pub base_path: Option<String>,
}
impl WebAppConfig {
/// Get the normalized base path for the application with `/` trimmed from both ends. If the base path is not set, this will return `.`.
pub fn base_path(&self) -> &str {
match &self.base_path {
Some(path) => path.trim_matches('/'),
None => ".",
}
}
}
impl Default for WebAppConfig {
fn default() -> Self {
Self {
title: default_title(),
base_path: None,
}
}
}
fn default_title() -> String {
"dioxus | ⛺".into()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WebProxyConfig {
pub backend: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WebWatcherConfig {
#[serde(default = "watch_path_default")]
pub watch_path: Vec<PathBuf>,
#[serde(default)]
pub reload_html: bool,
#[serde(default = "true_bool")]
pub index_on_404: bool,
}
impl Default for WebWatcherConfig {
fn default() -> Self {
Self {
watch_path: watch_path_default(),
reload_html: false,
index_on_404: true,
}
}
}
fn watch_path_default() -> Vec<PathBuf> {
vec![PathBuf::from("src"), PathBuf::from("examples")]
}
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct WebResourceConfig {
pub dev: WebDevResourceConfig,
pub style: Option<Vec<PathBuf>>,
pub script: Option<Vec<PathBuf>>,
}
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct WebDevResourceConfig {
#[serde(default)]
pub style: Vec<PathBuf>,
#[serde(default)]
pub script: Vec<PathBuf>,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct WebHttpsConfig {
pub enabled: Option<bool>,
pub mkcert: Option<bool>,
pub key_path: Option<String>,
pub cert_path: Option<String>,
}
fn true_bool() -> bool {
true
}
fn false_bool() -> bool {
false
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct BundleConfig {
pub identifier: Option<String>,
pub publisher: Option<String>,
pub icon: Option<Vec<String>>,
pub resources: Option<Vec<String>>,
pub copyright: Option<String>,
pub category: Option<String>,
pub short_description: Option<String>,
pub long_description: Option<String>,
pub external_bin: Option<Vec<String>>,
pub deb: Option<DebianSettings>,
pub macos: Option<MacOsSettings>,
pub windows: Option<WindowsSettings>,
}
impl From<BundleConfig> for tauri_bundler::BundleSettings {
fn from(val: BundleConfig) -> Self {
tauri_bundler::BundleSettings {
identifier: val.identifier,
publisher: val.publisher,
icon: val.icon,
resources: val.resources,
copyright: val.copyright,
category: val.category.and_then(|c| c.parse().ok()),
short_description: val.short_description,
long_description: val.long_description,
external_bin: val.external_bin,
deb: val.deb.map(Into::into).unwrap_or_default(),
macos: val.macos.map(Into::into).unwrap_or_default(),
windows: val.windows.map(Into::into).unwrap_or_default(),
..Default::default()
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct DebianSettings {
pub depends: Option<Vec<String>>,
pub files: HashMap<PathBuf, PathBuf>,
pub nsis: Option<NsisSettings>,
}
impl From<DebianSettings> for tauri_bundler::DebianSettings {
fn from(val: DebianSettings) -> Self {
tauri_bundler::DebianSettings {
depends: val.depends,
files: val.files,
desktop_template: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct WixSettings {
pub language: Vec<(String, Option<PathBuf>)>,
pub template: Option<PathBuf>,
pub fragment_paths: Vec<PathBuf>,
pub component_group_refs: Vec<String>,
pub component_refs: Vec<String>,
pub feature_group_refs: Vec<String>,
pub feature_refs: Vec<String>,
pub merge_refs: Vec<String>,
pub skip_webview_install: bool,
pub license: Option<PathBuf>,
pub enable_elevated_update_task: bool,
pub banner_path: Option<PathBuf>,
pub dialog_image_path: Option<PathBuf>,
pub fips_compliant: bool,
}
impl From<WixSettings> for tauri_bundler::WixSettings {
fn from(val: WixSettings) -> Self {
tauri_bundler::WixSettings {
language: tauri_bundler::bundle::WixLanguage({
let mut languages: Vec<_> = val
.language
.iter()
.map(|l| {
(
l.0.clone(),
tauri_bundler::bundle::WixLanguageConfig {
locale_path: l.1.clone(),
},
)
})
.collect();
if languages.is_empty() {
languages.push(("en-US".into(), Default::default()));
}
languages
}),
template: val.template,
fragment_paths: val.fragment_paths,
component_group_refs: val.component_group_refs,
component_refs: val.component_refs,
feature_group_refs: val.feature_group_refs,
feature_refs: val.feature_refs,
merge_refs: val.merge_refs,
skip_webview_install: val.skip_webview_install,
license: val.license,
enable_elevated_update_task: val.enable_elevated_update_task,
banner_path: val.banner_path,
dialog_image_path: val.dialog_image_path,
fips_compliant: val.fips_compliant,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct MacOsSettings {
pub frameworks: Option<Vec<String>>,
pub minimum_system_version: Option<String>,
pub license: Option<String>,
pub exception_domain: Option<String>,
pub signing_identity: Option<String>,
pub provider_short_name: Option<String>,
pub entitlements: Option<String>,
pub info_plist_path: Option<PathBuf>,
}
impl From<MacOsSettings> for tauri_bundler::MacOsSettings {
fn from(val: MacOsSettings) -> Self {
tauri_bundler::MacOsSettings {
frameworks: val.frameworks,
minimum_system_version: val.minimum_system_version,
license: val.license,
exception_domain: val.exception_domain,
signing_identity: val.signing_identity,
provider_short_name: val.provider_short_name,
entitlements: val.entitlements,
info_plist_path: val.info_plist_path,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WindowsSettings {
pub digest_algorithm: Option<String>,
pub certificate_thumbprint: Option<String>,
pub timestamp_url: Option<String>,
pub tsp: bool,
pub wix: Option<WixSettings>,
pub icon_path: Option<PathBuf>,
pub webview_install_mode: WebviewInstallMode,
pub webview_fixed_runtime_path: Option<PathBuf>,
pub allow_downgrades: bool,
pub nsis: Option<NsisSettings>,
}
impl From<WindowsSettings> for tauri_bundler::WindowsSettings {
fn from(val: WindowsSettings) -> Self {
tauri_bundler::WindowsSettings {
digest_algorithm: val.digest_algorithm,
certificate_thumbprint: val.certificate_thumbprint,
timestamp_url: val.timestamp_url,
tsp: val.tsp,
wix: val.wix.map(Into::into),
icon_path: val.icon_path.unwrap_or("icons/icon.ico".into()),
webview_install_mode: val.webview_install_mode.into(),
webview_fixed_runtime_path: val.webview_fixed_runtime_path,
allow_downgrades: val.allow_downgrades,
nsis: val.nsis.map(Into::into),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NsisSettings {
pub template: Option<PathBuf>,
pub license: Option<PathBuf>,
pub header_image: Option<PathBuf>,
pub sidebar_image: Option<PathBuf>,
pub installer_icon: Option<PathBuf>,
pub install_mode: NSISInstallerMode,
pub languages: Option<Vec<String>>,
pub custom_language_files: Option<HashMap<String, PathBuf>>,
pub display_language_selector: bool,
}
impl From<NsisSettings> for tauri_bundler::NsisSettings {
fn from(val: NsisSettings) -> Self {
tauri_bundler::NsisSettings {
license: val.license,
header_image: val.header_image,
sidebar_image: val.sidebar_image,
installer_icon: val.installer_icon,
install_mode: val.install_mode.into(),
languages: val.languages,
display_language_selector: val.display_language_selector,
custom_language_files: None,
template: None,
compression: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum NSISInstallerMode {
CurrentUser,
PerMachine,
Both,
}
impl From<NSISInstallerMode> for tauri_utils::config::NSISInstallerMode {
fn from(val: NSISInstallerMode) -> Self {
match val {
NSISInstallerMode::CurrentUser => tauri_utils::config::NSISInstallerMode::CurrentUser,
NSISInstallerMode::PerMachine => tauri_utils::config::NSISInstallerMode::PerMachine,
NSISInstallerMode::Both => tauri_utils::config::NSISInstallerMode::Both,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum WebviewInstallMode {
Skip,
DownloadBootstrapper { silent: bool },
EmbedBootstrapper { silent: bool },
OfflineInstaller { silent: bool },
FixedRuntime { path: PathBuf },
}
impl WebviewInstallMode {
fn into(self) -> tauri_utils::config::WebviewInstallMode {
match self {
Self::Skip => tauri_utils::config::WebviewInstallMode::Skip,
Self::DownloadBootstrapper { silent } => {
tauri_utils::config::WebviewInstallMode::DownloadBootstrapper { silent }
}
Self::EmbedBootstrapper { silent } => {
tauri_utils::config::WebviewInstallMode::EmbedBootstrapper { silent }
}
Self::OfflineInstaller { silent } => {
tauri_utils::config::WebviewInstallMode::OfflineInstaller { silent }
}
Self::FixedRuntime { path } => {
tauri_utils::config::WebviewInstallMode::FixedRuntime { path }
}
}
}
}
impl Default for WebviewInstallMode {
fn default() -> Self {
Self::OfflineInstaller { silent: false }
}
}
/// The arguments for the address the server will run on
#[derive(Clone, Debug, Parser)]
pub struct AddressArguments {
/// The port the server will run on
#[clap(long)]
#[clap(default_value_t = default_port())]
pub port: u16,
/// The address the server will run on
#[clap(long, default_value_t = default_address())]
pub addr: std::net::IpAddr,
}
impl Default for AddressArguments {
fn default() -> Self {
Self {
port: default_port(),
addr: default_address(),
}
}
}
impl AddressArguments {
/// Get the address the server should run on
pub fn address(&self) -> SocketAddr {
SocketAddr::new(self.addr, self.port)
}
}
fn default_port() -> u16 {
8080
}
fn default_address() -> IpAddr {
IpAddr::V4(std::net::Ipv4Addr::new(127, 0, 0, 1))
}

View file

@ -1,6 +1,6 @@
use crate::build::TargetArgs;
use dioxus_cli_config::{DioxusConfig, Platform};
use krates::cm::Target;
use crate::config::{DioxusConfig, Platform};
use krates::{cm::Target, KrateDetails};
use krates::{cm::TargetKind, Cmd, Krates, NodeId};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
@ -84,16 +84,26 @@ fn find_main_package(package: Option<String>, krates: &Krates) -> Result<NodeId,
let kid = match package {
Some(package) => {
let mut workspace_members = krates.workspace_members();
workspace_members
.find_map(|node| {
let found = workspace_members.find_map(|node| {
if let krates::Node::Krate { id, krate, .. } = node {
if krate.name == package {
return Some(id);
}
}
None
})
.ok_or_else(|| CrateConfigError::PackageNotFound(package.clone()))?
});
if found.is_none() {
eprintln!("Could not find package {package} in the workspace. Did you forget to add it to the workspace?");
eprintln!("Packages in the workspace:");
for package in krates.workspace_members() {
if let krates::Node::Krate { krate, .. } = package {
eprintln!("{}", krate.name());
}
}
}
found.ok_or_else(|| CrateConfigError::PackageNotFound(package.clone()))?
}
None => {
// Otherwise find the package that is the closest parent of the current directory
@ -144,6 +154,7 @@ impl DioxusCrate {
cmd.features(target.features.clone());
let builder = krates::Builder::new();
let krates = builder.build(cmd, |_| {})?;
let package = find_main_package(target.package.clone(), &krates)?;
let dioxus_config = load_dioxus_config(&krates, package)?.unwrap_or_default();

View file

@ -5,6 +5,7 @@
pub mod assets;
pub mod builder;
pub mod cli;
pub mod config;
pub mod dioxus_crate;
pub mod dx_build_info;
pub mod error;

View file

@ -155,9 +155,24 @@ pub async fn serve_all(
builder.children.clear();
}
let asset_dir = dioxus_crate
.dioxus_config
.application
.asset_dir
.canonicalize()
.unwrap_or(std::path::PathBuf::from("./assets"));
// If we have a build result, open it
for build_result in results.iter() {
let child = build_result.open(&serve.server_arguments, server.fullstack_address(), &dioxus_crate.workspace_dir());
let child = build_result.open(
&serve.server_arguments,
server.fullstack_address(),
&dioxus_crate.workspace_dir(),
&asset_dir,
server.ip,
dioxus_crate.dioxus_config.application.name.clone(),
dioxus_crate.out_dir()
);
match child {
Ok(Some(child_proc)) => builder.children.push((build_result.target_platform, child_proc)),
Err(e) => {

View file

@ -1,3 +1,4 @@
use crate::config::{AddressArguments, Platform};
use crate::{
builder::{BuildResult, UpdateStage},
builder::{Stage, TargetPlatform, UpdateBuildProgress},
@ -18,8 +19,7 @@ use crossterm::{
tty::IsTty,
ExecutableCommand,
};
use dioxus_cli_config::{AddressArguments, Platform};
use dioxus_hot_reload::ClientMsg;
use dioxus_devtools::ClientMsg;
use futures_util::{future::select_all, Future, FutureExt, StreamExt};
use ratatui::{prelude::*, TerminalOptions, Viewport};
use std::{

View file

@ -1,6 +1,6 @@
use super::{BuildProgress, TraceMsg, TraceSrc};
use crate::config::Platform;
use ansi_to_tui::IntoText as _;
use dioxus_cli_config::Platform;
use ratatui::{
layout::{Alignment, Constraint, Direction, Layout, Rect},
style::{Color, Style, Stylize},

View file

@ -1,10 +1,10 @@
use crate::config::WebProxyConfig;
use crate::TraceSrc;
use crate::{Error, Result};
use dioxus_cli_config::WebProxyConfig;
use anyhow::{anyhow, Context};
use axum::body::Body as MyBody;
use axum::body::Body;
use axum::{body::Body as MyBody, response::IntoResponse};
use axum::{
http::StatusCode,
routing::{any, MethodRouter},
@ -97,7 +97,7 @@ pub(crate) fn proxy_to(
nocache: bool,
handle_error: fn(Error) -> Response<Body>,
) -> MethodRouter {
let client = ProxyClient::new(url);
let client = ProxyClient::new(url.clone());
any(move |mut req: Request<MyBody>| async move {
// Prevent request loops
@ -115,11 +115,54 @@ pub(crate) fn proxy_to(
"true".parse().expect("header value is valid"),
);
// We have to throw a redirect for ws connections since the upgrade handler will not be called
// Our _dioxus handler will override this in the default case
if req.uri().scheme().map(|f| f.as_str()) == Some("ws")
|| req.uri().scheme().map(|f| f.as_str()) == Some("wss")
{
let new_host = url.host().unwrap_or("localhost");
let proxied_uri = format!(
"{scheme}://{host}:{port}{path_and_query}",
scheme = req.uri().scheme_str().unwrap_or("ws"),
port = url.port().unwrap(),
host = new_host,
path_and_query = req
.uri()
.path_and_query()
.map(|f| f.to_string())
.unwrap_or_default()
);
tracing::info!(dx_src = ?TraceSrc::Dev, "Proxied websocket request {req:?} to {proxied_uri}");
return Ok(axum::response::Redirect::permanent(&proxied_uri).into_response());
}
if nocache {
crate::serve::insert_no_cache_headers(req.headers_mut());
}
client.send(req).await.map_err(handle_error)
let uri = req.uri().clone();
let res = client.send(req).await.map_err(handle_error);
match res {
Ok(res) => {
// log assets at a different log level
if uri.path().starts_with("/assets")
|| uri.path().starts_with("/_dioxus")
|| uri.path().starts_with("/public")
{
tracing::trace!(dx_src = ?TraceSrc::Dev, "[{}] {}", res.status().as_u16(), uri);
} else {
tracing::info!(dx_src = ?TraceSrc::Dev, "[{}] {}", res.status().as_u16(), uri);
}
Ok(res.into_response())
}
Err(err) => {
tracing::error!(dx_src = ?TraceSrc::Dev, "[{}] {}", err.status().as_u16(), uri);
Err(err)
}
}
})
}

View file

@ -1,3 +1,4 @@
use crate::config::{Platform, WebHttpsConfig};
use crate::serve::{next_or_pending, Serve};
use crate::{dioxus_crate::DioxusCrate, TraceSrc};
use crate::{Error, Result};
@ -18,8 +19,7 @@ use axum::{
Extension, Router,
};
use axum_server::tls_rustls::RustlsConfig;
use dioxus_cli_config::{Platform, WebHttpsConfig};
use dioxus_hot_reload::{DevserverMsg, HotReloadMsg};
use dioxus_devtools::{DevserverMsg, HotReloadMsg};
use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
use futures_util::stream;
use futures_util::{stream::FuturesUnordered, StreamExt};
@ -419,6 +419,7 @@ fn setup_router(
"/",
get(
|ws: WebSocketUpgrade, ext: Extension<UnboundedSender<WebSocket>>| async move {
tracing::info!("Incoming hotreload websocket request: {ws:?}");
ws.on_upgrade(move |socket| async move { _ = ext.0.unbounded_send(socket) })
},
),

View file

@ -5,7 +5,7 @@ use super::hot_reloading_file_map::HotreloadError;
use crate::serve::hot_reloading_file_map::FileMap;
use crate::TraceSrc;
use crate::{cli::serve::Serve, dioxus_crate::DioxusCrate};
use dioxus_hot_reload::HotReloadMsg;
use dioxus_devtools::HotReloadMsg;
use dioxus_html::HtmlCtx;
use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
use futures_util::StreamExt;

View file

@ -19,10 +19,10 @@ dioxus-html = { workspace = true, features = [
] }
dioxus-signals = { workspace = true, optional = true }
dioxus-interpreter-js = { workspace = true, features = ["binary-protocol", "serialize"] }
dioxus-cli-config = { workspace = true, features = ["read-config"] }
dioxus-cli-config = { workspace = true }
generational-box = { workspace = true }
# hotreload only works on desktop platforms.... mobile is still wip
dioxus-hot-reload = { workspace = true, optional = true, features = ["serve", "client"]}
dioxus-devtools = { workspace = true, optional = true }
serde = "1.0.136"
serde_json = "1.0.79"
@ -89,16 +89,15 @@ core-foundation = "0.9.3"
objc = "0.2.7"
[features]
default = ["tokio_runtime", "wry/objc-exception", "hot-reload", "devtools"]
default = ["tokio_runtime", "wry/objc-exception", "devtools"]
tokio_runtime = ["dep:tokio"]
fullscreen = ["wry/fullscreen"]
transparent = ["wry/transparent"]
devtools = ["wry/devtools"]
hot-reload = ["dep:dioxus-hot-reload", "dioxus-signals"]
devtools = ["wry/devtools", "dep:dioxus-devtools", "dioxus-signals"]
gnu = []
[package.metadata.docs.rs]
features = ["tokio_runtime", "hot-reload"]
features = ["tokio_runtime", "devtools"]
cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"]
default-features = false
targets = [

View file

@ -86,12 +86,7 @@ impl App {
app.set_menubar_receiver();
// Allow hotreloading to work - but only in debug mode
#[cfg(all(
feature = "hot-reload",
debug_assertions,
not(target_os = "android"),
not(target_os = "ios")
))]
#[cfg(all(feature = "devtools", debug_assertions))]
app.connect_hotreload();
#[cfg(debug_assertions)]
@ -140,25 +135,14 @@ impl App {
}
}
#[cfg(all(
feature = "hot-reload",
debug_assertions,
not(target_os = "android"),
not(target_os = "ios")
))]
#[cfg(all(feature = "devtools", debug_assertions))]
pub fn connect_hotreload(&self) {
if let Some(endpoint) = dioxus_cli_config::devserver_ws_endpoint() {
let proxy = self.shared.proxy.clone();
tokio::task::spawn(async move {
let Some(Ok(mut receiver)) = dioxus_hot_reload::NativeReceiver::create_from_cli().await
else {
return;
};
while let Some(Ok(msg)) = receiver.next().await {
dioxus_devtools::connect(endpoint, move |msg| {
_ = proxy.send_event(UserWindowEvent::HotReloadEvent(msg));
})
}
});
}
pub fn handle_new_window(&mut self) {
@ -276,19 +260,14 @@ impl App {
view.desktop_context.query.send(result);
}
#[cfg(all(
feature = "hot-reload",
debug_assertions,
not(target_os = "android"),
not(target_os = "ios")
))]
pub fn handle_hot_reload_msg(&mut self, msg: dioxus_hot_reload::DevserverMsg) {
use dioxus_hot_reload::DevserverMsg;
#[cfg(all(feature = "devtools", debug_assertions))]
pub fn handle_hot_reload_msg(&mut self, msg: dioxus_devtools::DevserverMsg) {
use dioxus_devtools::DevserverMsg;
match msg {
DevserverMsg::HotReload(hr_msg) => {
for webview in self.webviews.values_mut() {
dioxus_hot_reload::apply_changes(&mut webview.dom, &hr_msg);
dioxus_devtools::apply_changes(&webview.dom, &hr_msg);
webview.poll_vdom();
}

View file

@ -65,18 +65,12 @@ impl Config {
/// Initializes a new `WindowBuilder` with default values.
#[inline]
pub fn new() -> Self {
let dioxus_config = dioxus_cli_config::CURRENT_CONFIG.as_ref();
let mut window: WindowBuilder = WindowBuilder::new().with_title(
dioxus_config
.map(|c| c.application.name.clone())
.unwrap_or("Dioxus App".to_string()),
);
let mut window: WindowBuilder = WindowBuilder::new()
.with_title(dioxus_cli_config::app_title().unwrap_or_else(|| "Dioxus App".to_string()));
// During development we want the window to be on top so we can see it while we work
let always_on_top = dioxus_config
.map(|c| c.desktop.always_on_top)
.unwrap_or(true);
let always_on_top = dioxus_cli_config::always_on_top().unwrap_or(true);
if cfg!(debug_assertions) {
window = window.with_always_on_top(always_on_top);
}

View file

@ -19,12 +19,12 @@ pub enum UserWindowEvent {
/// Handle a hotreload event, basically telling us to update our templates
#[cfg(all(
feature = "hot-reload",
feature = "devtools",
debug_assertions,
not(target_os = "android"),
not(target_os = "ios")
))]
HotReloadEvent(dioxus_hot_reload::DevserverMsg),
HotReloadEvent(dioxus_devtools::DevserverMsg),
/// Create a new window
NewWindow,

View file

@ -41,12 +41,7 @@ pub fn launch_virtual_dom_blocking(virtual_dom: VirtualDom, desktop_config: Conf
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
UserWindowEvent::MudaMenuEvent(evnt) => app.handle_menu_event(evnt),
#[cfg(all(
feature = "hot-reload",
debug_assertions,
not(target_os = "android"),
not(target_os = "ios")
))]
#[cfg(all(feature = "devtools", debug_assertions))]
UserWindowEvent::HotReloadEvent(msg) => app.handle_hot_reload_msg(msg),
UserWindowEvent::Ipc { id, msg } => match msg.method() {

View file

@ -280,10 +280,10 @@ fn running_in_dev_mode() -> bool {
#[allow(unreachable_code)]
fn get_asset_root() -> Option<PathBuf> {
if running_in_dev_mode() {
return dioxus_cli_config::CURRENT_CONFIG
.as_ref()
.map(|c| c.application.out_dir.clone())
.ok();
// todo: we don't want to canonicalize assets like this, but it will take longer to migrate, so we'll do it later
// we should just be parsing paths the way they are instead of relative to the "asset" dir
// manganis will eventually just dump the raw path to us, but until then, we need to canonicalize here
return dioxus_cli_config::base_path();
}
#[cfg(target_os = "macos")]

View file

@ -381,7 +381,7 @@ impl WebviewInstance {
}
}
#[cfg(all(feature = "hot-reload", debug_assertions))]
#[cfg(all(feature = "devtools", debug_assertions))]
pub fn kick_stylsheets(&self) {
// run eval in the webview to kick the stylesheets by appending a query string
// we should do something less clunky than this

View file

@ -0,0 +1,8 @@
[package]
name = "dioxus-devtools-types"
edition = "2021"
version.workspace = true
[dependencies]
serde = { workspace = true, features = ["derive"] }
dioxus-core = { workspace = true }

View file

@ -2,18 +2,6 @@ use dioxus_core::internal::HotReloadTemplateWithLocation;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[cfg(feature = "client")]
mod client;
#[cfg(feature = "client")]
pub use client::*;
#[cfg(feature = "serve")]
mod ws_receiver;
#[cfg(feature = "serve")]
pub use ws_receiver::*;
/// A message the hot reloading server sends to the client
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub enum DevserverMsg {
@ -49,21 +37,5 @@ pub enum ClientMsg {
pub struct HotReloadMsg {
pub templates: Vec<HotReloadTemplateWithLocation>,
pub assets: Vec<PathBuf>,
/// A file changed that's not an asset or a rust file - best of luck!
pub unknown_files: Vec<PathBuf>,
}
#[test]
fn serialize_client_msg() {
let msg = ClientMsg::Log {
level: "info".to_string(),
messages: vec!["hello world".to_string()],
};
let json = serde_json::to_string(&msg).unwrap();
assert_eq!(
json,
r#"{"Log":{"level":"info","messages":["hello world"]}}"#
);
}

View file

@ -0,0 +1,31 @@
[package]
name = "dioxus-devtools"
authors = ["Jonathan Kelley", "Evan Almloff"]
version = { workspace = true }
edition = "2021"
license = "MIT OR Apache-2.0"
repository = "https://github.com/DioxusLabs/dioxus/"
homepage = "https://dioxuslabs.com/learn/0.4/migration/hot_reload"
description = "Hot reloading utilities for Dioxus"
keywords = ["dom", "ui", "gui", "react", "hot-reloading"]
[dependencies]
dioxus-signals = { workspace = true }
dioxus-core = { workspace = true, features = ["serialize"] }
dioxus-devtools-types = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
# hot reloading serve
tracing = { workspace = true }
warnings.workspace = true
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tungstenite = { version = "0.23.0" }
[dev-dependencies]
tokio = { workspace = true, features = ["full"] }
serde_json = "1.0.91"
[package.metadata.docs.rs]
cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"]

View file

@ -0,0 +1,59 @@
use dioxus_core::{ScopeId, VirtualDom};
pub use dioxus_devtools_types::*;
use dioxus_signals::Writable;
use warnings::Warning;
/// Applies template and literal changes to the VirtualDom
///
/// Assets need to be handled by the renderer.
pub fn apply_changes(dom: &VirtualDom, msg: &HotReloadMsg) {
dom.runtime().on_scope(ScopeId::ROOT, || {
let ctx = dioxus_signals::get_global_context();
for template in &msg.templates {
let id = &template.location;
let value = template.template.clone();
if let Some(mut signal) = ctx.get_signal_with_key(id) {
dioxus_signals::warnings::signal_read_and_write_in_reactive_scope::allow(|| {
dioxus_signals::warnings::signal_write_in_component_body::allow(|| {
signal.set(Some(value));
});
});
}
}
});
}
/// Connect to the devserver and handle its messages with a callback.
///
/// This doesn't use any form of security or protocol, so it's not safe to expose to the internet.
#[cfg(not(target_arch = "wasm32"))]
pub fn connect(endpoint: String, mut callback: impl FnMut(DevserverMsg) + Send + 'static) {
std::thread::spawn(move || {
let (mut websocket, _req) = match tungstenite::connect(endpoint.clone()) {
Ok((websocket, req)) => (websocket, req),
Err(err) => {
eprintln!(
"Failed to connect to devserver at {} because {}",
endpoint, err
);
return;
}
};
while let Ok(msg) = websocket.read() {
match msg {
tungstenite::Message::Text(text) => {
if let Ok(msg) = serde_json::from_str(&text) {
callback(msg);
} else {
eprintln!("Failed to parse message from devserver: {:?}", text);
}
}
msg => {
println!("Received a non-text message: {:?}", msg);
}
}
}
});
}

View file

@ -31,16 +31,16 @@ serde = { version = "1.0.136", optional = true }
axum = { workspace = true, optional = true }
[target.'cfg(not(any(target_arch = "wasm32", target_os = "ios", target_os = "android")))'.dependencies]
dioxus-hot-reload = { workspace = true, optional = true }
dioxus-devtools = { workspace = true, optional = true }
[features]
default = ["macro", "html", "hot-reload", "signals", "hooks", "launch", "mounted", "file_engine", "document", "asset"]
default = ["macro", "html", "devtools", "signals", "hooks", "launch", "mounted", "file_engine", "document", "asset"]
minimal = ["macro", "html", "signals", "hooks", "launch"]
signals = ["dep:dioxus-signals"]
macro = ["dep:dioxus-core-macro"]
html = ["dep:dioxus-html"]
hooks = ["dep:dioxus-hooks"]
hot-reload = ["dep:dioxus-hot-reload", "dioxus-web?/hot_reload", "dioxus-fullstack?/hot-reload"]
devtools = ["dep:dioxus-devtools", "dioxus-web?/devtools", "dioxus-fullstack?/devtools"]
mounted = ["dioxus-web?/mounted", "dioxus-html?/mounted"]
file_engine = ["dioxus-web?/file_engine"]
asset = ["dep:manganis", "dioxus-core/manganis"]

View file

@ -93,10 +93,10 @@ pub mod prelude {
#[cfg(all(
not(any(target_arch = "wasm32", target_os = "ios", target_os = "android")),
feature = "hot-reload"
feature = "devtools"
))]
#[cfg_attr(docsrs, doc(cfg(feature = "hot-reload")))]
pub use dioxus_hot_reload;
#[cfg_attr(docsrs, doc(cfg(feature = "devtools")))]
pub use dioxus_devtools;
pub use dioxus_core;

View file

@ -61,25 +61,24 @@ tower-layer = { version = "0.3.2", optional = true }
parking_lot = { version = "0.12.1", features = ["send_guard"], optional = true }
web-sys = { version = "0.3.61", optional = true, features = ["Window", "Document", "Element", "HtmlDocument", "Storage", "console"] }
dioxus-cli-config = { workspace = true, features = ["read-config"], optional = true }
clap = { version = "4.5.7", optional = true, features = ["derive"] }
dioxus-cli-config = { workspace = true, optional = true }
dioxus-devtools = { workspace = true, optional = true }
aws-lc-rs = { version = "1.8.1", optional = true }
[target.'cfg(target_arch = "wasm32")'.dependencies]
tokio = { workspace = true, features = ["rt", "sync"], optional = true }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
dioxus-hot-reload = { workspace = true, features = ["serve"] }
tokio = { workspace = true, features = ["rt", "sync", "rt-multi-thread"], optional = true }
[dev-dependencies]
dioxus = { workspace = true, features = ["fullstack"] }
[features]
default = ["hot-reload", "panic_hook", "document", "file_engine", "mounted"]
default = ["devtools", "panic_hook", "document", "file_engine", "mounted"]
panic_hook = ["dioxus-web?/panic_hook"]
hot-reload = ["dioxus-web?/hot_reload", "dioxus-hot-reload/serve"]
devtools = ["dioxus-web?/devtools", "dep:dioxus-devtools"]
mounted = ["dioxus-web?/mounted"]
file_engine = ["dioxus-web?/file_engine"]
document = ["dioxus-web?/document"]
@ -108,8 +107,6 @@ server = [
"dep:async-trait",
"dep:parking_lot",
"dioxus-interpreter-js",
"dep:clap",
"dioxus-cli-config/read-from-args",
]
aws-lc-rs = ["dep:aws-lc-rs"]

View file

@ -126,16 +126,9 @@ async fn launch_server(
build_virtual_dom: impl Fn() -> VirtualDom + Send + Sync + 'static,
context_providers: ContextProviders,
) {
use clap::Parser;
// Get the address the server should run on. If the CLI is running, the CLI proxies fullstack into the main address
// and we use the generated address the CLI gives us
let cli_args = dioxus_cli_config::RuntimeCLIArguments::from_cli();
let address = cli_args
.as_ref()
.map(|args| args.fullstack_address())
.unwrap_or_else(dioxus_cli_config::AddressArguments::parse)
.address();
let address = dioxus_cli_config::fullstack_address_or_localhost();
#[cfg(feature = "axum")]
{

View file

@ -1,52 +0,0 @@
[package]
name = "dioxus-hot-reload"
authors = ["Jonathan Kelley", "Evan Almloff"]
version = { workspace = true }
edition = "2021"
license = "MIT OR Apache-2.0"
repository = "https://github.com/DioxusLabs/dioxus/"
homepage = "https://dioxuslabs.com/learn/0.4/migration/hot_reload"
description = "Hot reloading utilities for Dioxus"
keywords = ["dom", "ui", "gui", "react", "hot-reloading"]
[dependencies]
dioxus-rsx = { workspace = true }
dioxus-rsx-hotreload = { workspace = true }
dioxus-core = { workspace = true, features = ["serialize"] }
dioxus-html = { workspace = true, optional = true }
dioxus-signals = { workspace = true, optional = true }
dioxus-cli-config = { workspace = true, optional = true, features = ["read-config"] }
notify = { workspace = true, optional = true }
chrono = { version = "0.4.24", default-features = false, features = ["clock"], optional = true }
serde_json = "1.0.91"
serde = { version = "1", features = ["derive"] }
execute = { version = "0.2.11", optional = true }
once_cell = { version = "1.17.0", optional = true }
ignore = { version = "0.4.19", optional = true }
# hot reloading serve
tokio-stream = { version = "0.1.12", features = ["sync"], optional = true }
futures-util = { workspace = true, features = ["async-await-macro"], optional = true }
tokio = { workspace = true, features = ["sync", "rt-multi-thread"], optional = true }
tracing = { workspace = true }
warnings.workspace = true
# use rustls on android
[target.'cfg(target_os = "android")'.dependencies]
tokio-tungstenite = { workspace = true, optional = true, features = ["rustls"] }
# use native tls on other platforms
[target.'cfg(not(target_os = "android"))'.dependencies]
tokio-tungstenite = { workspace = true, optional = true, features = ["native-tls"] }
[dev-dependencies]
tokio = { workspace = true, features = ["full"] }
[features]
default = ["dioxus-html"]
client = ["dep:dioxus-signals"]
serve = ["dep:tokio-stream", "dep:futures-util", "dep:tokio", "dep:tokio-tungstenite", "dep:dioxus-cli-config"]
[package.metadata.docs.rs]
cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"]

View file

@ -1,171 +0,0 @@
# `dioxus-hot-reload`: Hot Reloading Utilities for Dioxus
[![Crates.io][crates-badge]][crates-url]
[![MIT licensed][mit-badge]][mit-url]
[![Build Status][actions-badge]][actions-url]
[![Discord chat][discord-badge]][discord-url]
[crates-badge]: https://img.shields.io/crates/v/dioxus-hot-reload.svg
[crates-url]: https://crates.io/crates/dioxus-hot-reload
[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg
[mit-url]: https://github.com/dioxuslabs/dioxus/blob/main/LICENSE-MIT
[actions-badge]: https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg
[actions-url]: https://github.com/dioxuslabs/dioxus/actions?query=workflow%3ACI+branch%3Amaster
[discord-badge]: https://img.shields.io/discord/899851952891002890.svg?logo=discord&style=flat-square
[discord-url]: https://discord.gg/XgGxMSkvUM
[Website](https://dioxuslabs.com) |
[Guides](https://dioxuslabs.com/learn/0.5/) |
[API Docs](https://docs.rs/dioxus-hot-reload/latest/dioxus_hot_reload) |
[Chat](https://discord.gg/XgGxMSkvUM)
## Overview
Dioxus supports hot reloading for static parts of rsx macros. This enables changing the styling of your application without recompiling the rust code. This is useful for rapid iteration on the styling of your application.
Hot reloading could update the following change without recompiling:
```rust
rsx! {
div {
"Count: {count}",
}
}
```
=>
```rust
rsx! {
div {
color: "red",
font_size: "2em",
"Count: {count}",
}
}
```
But it could not update the following change:
```rust
rsx! {
div {
"Count: {count}",
}
}
```
=>
```rust
rsx! {
div {
"Count: {count*2}",
onclick: |_| println!("clicked"),
}
}
```
## Usage
> This crate implements hot reloading for native compilation targets not WASM. For hot reloading with the web renderer, see the [dioxus-cli](https://github.com/DioxusLabs/dioxus/tree/master/packages/cli) project.
Add this to the top of your main function on any renderer that supports hot reloading to start the hot reloading server:
```rust
fn main(){
hot_reload_init!();
// launch your application
}
```
By default the dev server watches on the root of the crate the macro is called in and ignores changes in the `/target` directory and any directories set in the `.gitignore` file in the root directory. To watch on custom paths pass call the `with_paths` function on the config builder:
```rust
fn main(){
hot_reload_init!(Config::new().with_paths(&["src", "examples", "assets"]));
// launch your application
}
```
By default the hot reloading server will output some logs in the console, to disable these logs call the `with_logging` function on the config builder:
```rust
fn main(){
hot_reload_init!(Config::new().with_logging(false));
// launch your application
}
```
To rebuild the application when the logic changes, you can use the `with_rebuild_command` function on the config builder. This command will be called when hot reloading fails to quickly update the rsx:
```rust
fn main(){
hot_reload_init!(Config::new().with_rebuild_command("cargo run"));
// launch your application
}
```
If you are using a namespace other than html, you can implement the [HotReloadingContext](https://docs.rs/dioxus-rsx/latest/dioxus_rsx/index.html#reexport.HotReloadingContext) trait to provide a mapping between the rust names of your elements/attributes and the resulting strings.
You can then provide the Context to the builder to make hot reloading work with your custom namespace:
```rust
fn main(){
// Note: Use default instead of new if you are using a custom namespace
hot_reload_init!(Config::<MyContext>::default());
// launch your application
}
```
## Implementing Hot Reloading for a Custom Renderer
To add hot reloading support to your custom renderer you can use the connect function. This will connect to the dev server you just need to provide a way to transfer `Template`s to the `VirtualDom`. Once you implement this your users can use the hot_reload_init function just like any other render.
```rust
async fn launch(app: Component) {
let mut vdom = VirtualDom::new(app);
// ...
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
dioxus_hot_reload::connect(move |msg| {
let _ = tx.send(msg);
});
loop {
tokio::select! {
Some(msg) = rx.recv() => {
match msg{
HotReloadMsg::Shutdown => {
// ... shutdown the application
}
HotReloadMsg::UpdateTemplate(template) => {
// update the template in the virtual dom
vdom.replace_template(template);
}
}
}
_ = vdom.wait_for_work() => {
// ...
}
}
let mutations = vdom.render_immediate();
// apply the mutations to the dom
}
}
```
## Contributing
- Report issues on our [issue tracker](https://github.com/dioxuslabs/dioxus/issues).
- Join the discord and ask questions!
## License
This project is licensed under the [MIT license].
[mit license]: https://github.com/dioxuslabs/dioxus/blob/main/LICENSE-MIT
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in Dioxus by you shall be licensed as MIT without any additional
terms or conditions.

View file

@ -1,25 +0,0 @@
use crate::HotReloadMsg;
use dioxus_core::{ScopeId, VirtualDom};
use dioxus_signals::Writable;
use warnings::Warning;
/// Applies template and literal changes to the VirtualDom
///
/// Assets need to be handled by the renderer.
pub fn apply_changes(dom: &mut VirtualDom, msg: &HotReloadMsg) {
dom.runtime().on_scope(ScopeId::ROOT, || {
let ctx = dioxus_signals::get_global_context();
for template in &msg.templates {
let id = &template.location;
let value = template.template.clone();
if let Some(mut signal) = ctx.get_signal_with_key(id) {
dioxus_signals::warnings::signal_read_and_write_in_reactive_scope::allow(|| {
dioxus_signals::warnings::signal_write_in_component_body::allow(|| {
signal.set(Some(value));
});
});
}
}
});
}

View file

@ -1,73 +0,0 @@
use crate::DevserverMsg;
use futures_util::{SinkExt, StreamExt};
use tokio::net::TcpStream;
use tokio_tungstenite::{
tungstenite::{Message, Result as TtResult},
MaybeTlsStream, WebSocketStream,
};
pub fn connect(mut callback: impl FnMut(DevserverMsg) + Send + 'static) {
tokio::spawn(async move {
let Some(Ok(mut recv)) = NativeReceiver::create_from_cli().await else {
return;
};
while let Some(msg) = recv.next().await {
match msg {
Ok(msg) => callback(msg),
Err(_e) => {}
}
}
});
}
/// A receiver for messages from the devserver
///
/// Calling `next` will watch the channel for the next valid message from the devserver
pub struct NativeReceiver {
socket: WebSocketStream<MaybeTlsStream<TcpStream>>,
}
impl NativeReceiver {
/// Connect to the devserver
async fn create(url: String) -> TtResult<Self> {
let (socket, _ws) = tokio_tungstenite::connect_async(&url).await?;
Ok(Self { socket })
}
/// Connect to the devserver with an address from the CLI. Returns None if the current application was not run with the CLI
pub async fn create_from_cli() -> Option<TtResult<Self>> {
let cli_args = dioxus_cli_config::RuntimeCLIArguments::from_cli()?;
let addr = cli_args.cli_address();
Some(Self::create(format!("ws://{addr}/_dioxus")).await)
}
/// Wait for the next message from the devserver
///
/// Returns None when the connection is closed or socket.next() returns None
pub async fn next(&mut self) -> Option<TtResult<DevserverMsg>> {
loop {
let res = self.socket.next().await?;
match res {
Ok(res) => match res {
Message::Text(text) => {
// let leaked: &'static str = Box::leak(text.into_boxed_str());
let msg = serde_json::from_str::<DevserverMsg>(&text);
if let Ok(msg) = msg {
return Some(Ok(msg));
}
}
// send a pong
Message::Ping(_) => {
let _ = self.socket.send(Message::Pong(vec![])).await;
}
Message::Close(_) => return None,
Message::Binary(_) => {}
Message::Pong(_) => {}
Message::Frame(_) => {}
},
Err(e) => return Some(Err(e)),
};
}
}
}

View file

@ -26,8 +26,8 @@ dioxus-html = { workspace = true, features = ["serialize", "document", "mounted"
rustc-hash = { workspace = true }
dioxus-core = { workspace = true, features = ["serialize"] }
dioxus-interpreter-js = { workspace = true, features = ["binary-protocol"] }
dioxus-hot-reload = { workspace = true, optional = true, features = ["serve", "client"] }
dioxus-cli-config = { workspace = true, features = ["read-config", "read-from-args"] }
dioxus-devtools = { workspace = true, optional = true }
dioxus-cli-config = { workspace = true }
generational-box = { workspace = true }
# axum
@ -41,10 +41,10 @@ tower = { workspace = true }
dioxus = { workspace = true }
[features]
default = ["hot-reload", "multi-thread"]
default = ["devtools", "multi-thread"]
axum = ["dep:axum"]
multi-thread = ["tokio/rt-multi-thread"]
hot-reload = ["dep:dioxus-hot-reload"]
devtools = ["dep:dioxus-devtools"]
[[example]]
name = "axum"

View file

@ -1,13 +1,9 @@
use dioxus_cli_config::{RuntimeCLIArguments, CURRENT_CONFIG};
use dioxus_core::VirtualDom;
use crate::LiveviewRouter;
pub(crate) fn app_title() -> String {
CURRENT_CONFIG
.as_ref()
.map(|c| c.web.app.title.clone())
.unwrap_or_else(|_| "Dioxus Liveview App".to_string())
dioxus_cli_config::app_title().unwrap_or_else(|| "Dioxus Liveview App".to_string())
}
/// A configuration for the LiveView server.
@ -19,15 +15,9 @@ pub struct Config<R: LiveviewRouter> {
impl<R: LiveviewRouter> Default for Config<R> {
fn default() -> Self {
let address = RuntimeCLIArguments::from_cli()
.map(|args| args.fullstack_address().address())
.unwrap_or(std::net::SocketAddr::V4(std::net::SocketAddrV4::new(
std::net::Ipv4Addr::new(127, 0, 0, 1),
8080,
)));
Self {
address: dioxus_cli_config::fullstack_address_or_localhost(),
router: R::create_default_liveview_router(),
address,
route: "/".to_string(),
}
}

View file

@ -6,7 +6,6 @@ use crate::{
LiveViewError,
};
use dioxus_core::prelude::*;
use dioxus_hot_reload::DevserverMsg;
use dioxus_html::{EventData, HtmlEvent, PlatformEventData};
use dioxus_interpreter_js::MutationState;
use futures_util::{pin_mut, SinkExt, StreamExt};
@ -117,10 +116,12 @@ impl<S> LiveViewSocket for S where
///
/// You might need to transform the error types of the web backend into the LiveView error type.
pub async fn run(mut vdom: VirtualDom, ws: impl LiveViewSocket) -> Result<(), LiveViewError> {
#[cfg(all(feature = "hot-reload", debug_assertions))]
#[cfg(all(feature = "devtools", debug_assertions))]
let mut hot_reload_rx = {
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
dioxus_hot_reload::connect(move |template| _ = tx.send(template));
if let Some(endpoint) = dioxus_cli_config::devserver_ws_endpoint() {
dioxus_devtools::connect(endpoint, move |template| _ = tx.send(template));
}
rx
};
@ -157,9 +158,9 @@ pub async fn run(mut vdom: VirtualDom, ws: impl LiveViewSocket) -> Result<(), Li
}
loop {
#[cfg(all(feature = "hot-reload", debug_assertions))]
#[cfg(all(feature = "devtools", debug_assertions))]
let hot_reload_wait = hot_reload_rx.recv();
#[cfg(not(all(feature = "hot-reload", debug_assertions)))]
#[cfg(not(all(feature = "devtools", debug_assertions)))]
let hot_reload_wait: std::future::Pending<Option<()>> = std::future::pending();
tokio::select! {
@ -213,22 +214,22 @@ pub async fn run(mut vdom: VirtualDom, ws: impl LiveViewSocket) -> Result<(), Li
}
Some(msg) = hot_reload_wait => {
#[cfg(all(feature = "hot-reload", debug_assertions))]
#[cfg(all(feature = "devtools", debug_assertions))]
match msg{
DevserverMsg::HotReload(msg)=> {
dioxus_hot_reload::apply_changes(&mut vdom, &msg);
dioxus_devtools::DevserverMsg::HotReload(msg)=> {
dioxus_devtools::apply_changes(&vdom, &msg);
}
DevserverMsg::Shutdown => {
dioxus_devtools::DevserverMsg::Shutdown => {
std::process::exit(0);
},
DevserverMsg::FullReloadCommand
| DevserverMsg::FullReloadStart
| DevserverMsg::FullReloadFailed => {
dioxus_devtools::DevserverMsg::FullReloadCommand
| dioxus_devtools::DevserverMsg::FullReloadStart
| dioxus_devtools::DevserverMsg::FullReloadFailed => {
// usually only web gets this message - what are we supposed to do?
// Maybe we could just binary patch ourselves in place without losing window state?
},
}
#[cfg(not(all(feature = "hot-reload", debug_assertions)))]
#[cfg(not(all(feature = "devtools", debug_assertions)))]
let () = msg;
}
}

View file

@ -29,7 +29,7 @@ dioxus-ssr = { workspace = true, optional = true }
http = { workspace = true, optional = true }
dioxus-fullstack = { workspace = true, optional = true }
tokio = { workspace = true, features = ["full"], optional = true }
dioxus-cli-config = { workspace = true, features = ["read-config"] }
dioxus-cli-config = { workspace = true }
rustversion = "1.0.17"
# you need to comment this out when publishing since cargo workspaces is not smart enough to wipe this when dropping

View file

@ -1,4 +1,7 @@
use std::sync::{Arc, Mutex};
use std::{
path::PathBuf,
sync::{Arc, Mutex},
};
use gloo::{console::error, events::EventListener, render::AnimationFrame};
@ -14,12 +17,12 @@ use super::{
};
#[allow(dead_code)]
fn base_path() -> Option<&'static str> {
fn base_path() -> Option<PathBuf> {
tracing::trace!(
"Using base_path from Dioxus.toml: {:?}",
dioxus_cli_config::BASE_PATH
"Using base_path from the CLI: {:?}",
dioxus_cli_config::base_path()
);
dioxus_cli_config::BASE_PATH
dioxus_cli_config::base_path()
}
#[allow(clippy::extra_unused_type_parameters)]
@ -97,7 +100,7 @@ impl<R: Routable> WebHistory<R> {
let prefix = prefix
// If there isn't a base path, try to grab one from the CLI
.or_else(|| base_path().map(|s| s.to_string()))
.or_else(|| base_path().map(|s| s.display().to_string()))
// Normalize the prefix to start and end with no slashes
.map(|prefix| prefix.trim_matches('/').to_string())
// If the prefix is empty, don't add it

View file

@ -1,8 +1,4 @@
{
"rust-analyzer.check.workspace": false,
// "rust-analyzer.check.extraArgs": [
// "--features",
// "hot_reload"
// ],
"rust-analyzer.cargo.features": "all"
}

View file

@ -18,8 +18,8 @@ dioxus-ssr = { workspace = true, optional = true }
dioxus-isrg = { workspace = true, optional = true }
axum = { workspace = true, features = ["ws", "macros"], optional = true }
tower-http = { workspace = true, features = ["fs"], optional = true }
dioxus-hot-reload = { workspace = true, features = ["serve"], optional = true }
dioxus-cli-config = { workspace = true, features = ["read-config"], optional = true }
dioxus-devtools = { workspace = true, optional = true }
dioxus-cli-config = { workspace = true, optional = true }
dioxus-web = { workspace = true, features = ["hydrate"], optional = true }
tokio = { workspace = true, optional = true }
http = { workspace = true, optional = true }
@ -32,7 +32,7 @@ criterion = { workspace = true }
[features]
default = []
server = ["dioxus-fullstack/server", "dioxus-router/ssr", "dep:dioxus-ssr", "dep:tokio", "dep:http", "dep:axum", "dep:tower-http", "dep:dioxus-hot-reload", "dep:dioxus-cli-config", "dep:tower", "dep:dioxus-isrg"]
server = ["dioxus-fullstack/server", "dioxus-router/ssr", "dep:dioxus-ssr", "dep:tokio", "dep:http", "dep:axum", "dep:tower-http", "dep:dioxus-devtools", "dep:dioxus-cli-config", "dep:tower", "dep:dioxus-isrg"]
web = ["dioxus-fullstack/web", "dioxus-router/web", "dep:dioxus-web"]
[package.metadata.docs.rs]

View file

@ -41,19 +41,12 @@ pub fn launch(
.unwrap();
// Serve the program if we are running with cargo
if std::env::var_os("CARGO").is_some() || std::env::var_os("DIOXUS_ACTIVE").is_some() {
if std::env::var_os("CARGO").is_some() || dioxus_cli_config::is_cli_enabled() {
// Get the address the server should run on. If the CLI is running, the CLI proxies static generation into the main address
// and we use the generated address the CLI gives us
let cli_args = dioxus_cli_config::RuntimeCLIArguments::from_cli();
let address = cli_args
.as_ref()
.map(|args| args.fullstack_address().address())
.unwrap_or_else(|| std::net::SocketAddr::from(([127, 0, 0, 1], 8080)));
let serve_address = dioxus_cli_config::fullstack_address_or_localhost();
// Point the user to the CLI address if the CLI is running or the fullstack address if not
let serve_address = cli_args
.map(|args| args.cli_address())
.unwrap_or_else(|| address);
println!(
"Serving static files from {} at http://{serve_address}",
path.display()
@ -77,7 +70,7 @@ pub fn launch(
})))
};
let listener = tokio::net::TcpListener::bind(address).await.unwrap();
let listener = tokio::net::TcpListener::bind(serve_address).await.unwrap();
axum::serve(listener, router.into_make_service())
.await
.unwrap();

View file

@ -65,10 +65,7 @@ pub async fn generate_static_site(
}
// Copy over the web output dir into the static output dir
let assets_path = dioxus_cli_config::CURRENT_CONFIG
.as_ref()
.map(|c| c.application.out_dir.clone())
.unwrap_or("./dist".into());
let assets_path = dioxus_cli_config::out_dir().unwrap_or("./dist".into());
let assets_path = assets_path.join("public");

View file

@ -13,7 +13,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
dioxus-core = { workspace = true }
dioxus-core-types = { workspace = true }
dioxus-html = { workspace = true, features = ["wasm-bind"] }
dioxus-hot-reload = { workspace = true, features = ["client"] }
dioxus-devtools = { workspace = true }
dioxus-signals = { workspace = true }
dioxus-interpreter-js = { workspace = true, features = [
"minimal_bindings",
@ -59,14 +59,14 @@ features = [
]
[features]
default = ["panic_hook", "mounted", "file_engine", "hot_reload", "document"]
default = ["panic_hook", "mounted", "file_engine", "devtools", "document"]
panic_hook = ["dep:console_error_panic_hook"]
hydrate = ["web-sys/Comment", "ciborium", "dep:serde"]
mounted = ["web-sys/Element", "dioxus-html/mounted"]
file_engine = [
"dioxus-html/file-engine",
]
hot_reload = ["web-sys/MessageEvent", "web-sys/WebSocket", "web-sys/Location", "dep:serde_json", "dep:serde", "dioxus-core/serialize"]
devtools = ["web-sys/MessageEvent", "web-sys/WebSocket", "web-sys/Location", "dep:serde_json", "dep:serde", "dioxus-core/serialize"]
document = ["dioxus-html/document", "dep:serde-wasm-bindgen", "dep:serde_json", "dep:serde"]
[dev-dependencies]

View file

@ -7,7 +7,7 @@ use std::fmt::Display;
use std::time::Duration;
use dioxus_core::ScopeId;
use dioxus_hot_reload::{DevserverMsg, HotReloadMsg};
use dioxus_devtools::{DevserverMsg, HotReloadMsg};
use dioxus_html::prelude::eval;
use futures_channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender};
use js_sys::JsString;

View file

@ -39,8 +39,8 @@ mod document;
#[cfg(feature = "document")]
pub use document::WebDocument;
#[cfg(all(feature = "hot_reload", debug_assertions))]
mod hot_reload;
#[cfg(all(feature = "devtools", debug_assertions))]
mod devtools;
mod hydration;
#[allow(unused)]
@ -67,8 +67,8 @@ pub async fn run(mut virtual_dom: VirtualDom, web_config: Config) -> ! {
console_error_panic_hook::set_once();
}
#[cfg(all(feature = "hot_reload", debug_assertions))]
let mut hotreload_rx = hot_reload::init();
#[cfg(all(feature = "devtools", debug_assertions))]
let mut hotreload_rx = devtools::init();
let runtime = virtual_dom.runtime();
@ -123,7 +123,7 @@ pub async fn run(mut virtual_dom: VirtualDom, web_config: Config) -> ! {
loop {
// if virtual dom has nothing, wait for it to have something before requesting idle time
// if there is work then this future resolves immediately.
#[cfg(all(feature = "hot_reload", debug_assertions))]
#[cfg(all(feature = "devtools", debug_assertions))]
let template;
#[allow(unused)]
let mut hydration_work: Option<SuspenseMessage> = None;
@ -137,15 +137,15 @@ pub async fn run(mut virtual_dom: VirtualDom, web_config: Config) -> ! {
.flatten();
let mut rx_hydration = hydration_receiver_iter.select_next_some();
#[cfg(all(feature = "hot_reload", debug_assertions))]
#[cfg(all(feature = "devtools", debug_assertions))]
#[allow(unused)]
{
let mut hot_reload_next = hotreload_rx.select_next_some();
let mut devtools_next = hotreload_rx.select_next_some();
select! {
_ = work => {
template = None;
},
new_template = hot_reload_next => {
new_template = devtools_next => {
template = Some(new_template);
},
hydration_data = rx_hydration => {
@ -158,7 +158,7 @@ pub async fn run(mut virtual_dom: VirtualDom, web_config: Config) -> ! {
}
}
#[cfg(not(all(feature = "hot_reload", debug_assertions)))]
#[cfg(not(all(feature = "devtools", debug_assertions)))]
#[allow(unused)]
{
select! {
@ -173,13 +173,13 @@ pub async fn run(mut virtual_dom: VirtualDom, web_config: Config) -> ! {
}
}
#[cfg(all(feature = "hot_reload", debug_assertions))]
#[cfg(all(feature = "devtools", debug_assertions))]
if let Some(hr_msg) = template {
// Replace all templates
dioxus_hot_reload::apply_changes(&mut virtual_dom, &hr_msg);
dioxus_devtools::apply_changes(&virtual_dom, &hr_msg);
if !hr_msg.assets.is_empty() {
crate::hot_reload::invalidate_browser_asset_cache();
crate::devtools::invalidate_browser_asset_cache();
}
}