Hotreloading of for/if/body, formatted strings, literals, component props, nested rsx, light CLI rewrite, cli TUI (#2258)
Hotreload the contents of for loops, if chains, component bodies, props, attributes, and any literals discovered in rsx! Add a TUI renderer to the CLI. Improve the CLI build system to be async and parallel. Refactor RSX to allow partial expansion of expressions. Merge autofmt implementations for consistency. Merge the representation of elements and components under the hood. Add a diagnostics system for rsx for improved error messages. Drop interprocess and move to websockets for communication between the CLI and the server. Assign IDs to nodes and attributes in a stable way to be used in non compiler contexts. Add hotreloading to any body of component/for loop/if chain/etc. --------- Co-authored-by: Evan Almloff <evanalmloff@gmail.com> Co-authored-by: Liam Mitchell <liamkarlmitchell@gmail.com>
This commit is contained in:
181 files changed
+12755
-10806
No files matched your search
@@ -1,115 +0,0 @@
|
||||
//! Utilities for working with cargo and rust files
|
||||
use std::error::Error;
|
||||
use std::{
|
||||
env,
|
||||
fmt::{Display, Formatter},
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
str,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CargoError {
|
||||
msg: String,
|
||||
}
|
||||
|
||||
impl CargoError {
|
||||
pub fn new(msg: String) -> Self {
|
||||
Self { msg }
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for CargoError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "CargoError: {}", self.msg)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for CargoError {}
|
||||
|
||||
/// How many parent folders are searched for a `Cargo.toml`
|
||||
const MAX_ANCESTORS: u32 = 10;
|
||||
|
||||
/// Some fields parsed from `cargo metadata` command
|
||||
pub struct Metadata {
|
||||
pub workspace_root: PathBuf,
|
||||
pub target_directory: PathBuf,
|
||||
}
|
||||
|
||||
/// Returns the root of the crate that the command is run from
|
||||
///
|
||||
/// If the command is run from the workspace root, this will return the top-level Cargo.toml
|
||||
pub fn crate_root() -> Result<PathBuf, CargoError> {
|
||||
// From the current directory we work our way up, looking for `Cargo.toml`
|
||||
env::current_dir()
|
||||
.ok()
|
||||
.and_then(|mut wd| {
|
||||
for _ in 0..MAX_ANCESTORS {
|
||||
if contains_manifest(&wd) {
|
||||
return Some(wd);
|
||||
}
|
||||
if !wd.pop() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
CargoError::new("Failed to find directory containing Cargo.toml".to_string())
|
||||
})
|
||||
}
|
||||
|
||||
/// Checks if the directory contains `Cargo.toml`
|
||||
fn contains_manifest(path: &Path) -> bool {
|
||||
fs::read_dir(path)
|
||||
.map(|entries| {
|
||||
entries
|
||||
.filter_map(Result::ok)
|
||||
.any(|ent| &ent.file_name() == "Cargo.toml")
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
impl Metadata {
|
||||
/// Returns the struct filled from `cargo metadata` output
|
||||
/// TODO @Jon, find a different way that doesn't rely on the cargo metadata command (it's slow)
|
||||
pub fn get() -> Result<Self, CargoError> {
|
||||
let output = Command::new("cargo")
|
||||
.args(["metadata"])
|
||||
.output()
|
||||
.map_err(|_| CargoError::new("Manifset".to_string()))?;
|
||||
|
||||
if !output.status.success() {
|
||||
let mut msg = str::from_utf8(&output.stderr).unwrap().trim();
|
||||
if msg.starts_with("error: ") {
|
||||
msg = &msg[7..];
|
||||
}
|
||||
|
||||
return Err(CargoError::new(msg.to_string()));
|
||||
}
|
||||
|
||||
let stdout = str::from_utf8(&output.stdout).unwrap();
|
||||
if let Some(line) = stdout.lines().next() {
|
||||
let meta: serde_json::Value = serde_json::from_str(line)
|
||||
.map_err(|_| CargoError::new("InvalidOutput".to_string()))?;
|
||||
|
||||
let workspace_root = meta["workspace_root"]
|
||||
.as_str()
|
||||
.ok_or_else(|| CargoError::new("InvalidOutput".to_string()))?
|
||||
.into();
|
||||
|
||||
let target_directory = meta["target_directory"]
|
||||
.as_str()
|
||||
.ok_or_else(|| CargoError::new("InvalidOutput".to_string()))?
|
||||
.into();
|
||||
|
||||
return Ok(Self {
|
||||
workspace_root,
|
||||
target_directory,
|
||||
});
|
||||
}
|
||||
|
||||
Err(CargoError::new("InvalidOutput".to_string()))
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,19 @@
|
||||
use crate::BundleConfig;
|
||||
use crate::CargoError;
|
||||
use core::fmt::{Display, Formatter};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Display;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Debug)]
|
||||
#[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
|
||||
@@ -29,6 +32,56 @@ pub enum Platform {
|
||||
StaticGeneration,
|
||||
}
|
||||
|
||||
/// 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),
|
||||
_ => 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",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DioxusConfig {
|
||||
pub application: ApplicationConfig,
|
||||
@@ -37,165 +90,6 @@ pub struct DioxusConfig {
|
||||
|
||||
#[serde(default)]
|
||||
pub bundle: BundleConfig,
|
||||
|
||||
#[cfg(feature = "cli")]
|
||||
#[serde(default = "default_plugin")]
|
||||
pub plugin: toml::Value,
|
||||
|
||||
#[cfg(feature = "cli")]
|
||||
pub cli_settings: Option<crate::CliSettings>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "cli")]
|
||||
fn default_plugin() -> toml::Value {
|
||||
toml::Value::Boolean(true)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LoadDioxusConfigError {
|
||||
location: String,
|
||||
error: String,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for LoadDioxusConfigError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{} {}", self.location, self.error)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for LoadDioxusConfigError {}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum CrateConfigError {
|
||||
Cargo(CargoError),
|
||||
Io(std::io::Error),
|
||||
#[cfg(feature = "cli")]
|
||||
Toml(toml::de::Error),
|
||||
LoadDioxusConfig(LoadDioxusConfigError),
|
||||
}
|
||||
|
||||
impl From<CargoError> for CrateConfigError {
|
||||
fn from(err: CargoError) -> Self {
|
||||
Self::Cargo(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for CrateConfigError {
|
||||
fn from(err: std::io::Error) -> Self {
|
||||
Self::Io(err)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "cli")]
|
||||
impl From<toml::de::Error> for CrateConfigError {
|
||||
fn from(err: toml::de::Error) -> Self {
|
||||
Self::Toml(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LoadDioxusConfigError> for CrateConfigError {
|
||||
fn from(err: LoadDioxusConfigError) -> Self {
|
||||
Self::LoadDioxusConfig(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for CrateConfigError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Cargo(err) => write!(f, "{}", err),
|
||||
Self::Io(err) => write!(f, "{}", err),
|
||||
#[cfg(feature = "cli")]
|
||||
Self::Toml(err) => write!(f, "{}", err),
|
||||
Self::LoadDioxusConfig(err) => write!(f, "{}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for CrateConfigError {}
|
||||
|
||||
impl DioxusConfig {
|
||||
#[cfg(feature = "cli")]
|
||||
/// Load the dioxus config from a path
|
||||
#[tracing::instrument]
|
||||
pub fn load(bin: Option<PathBuf>) -> Result<Option<DioxusConfig>, CrateConfigError> {
|
||||
use crate::CliSettings;
|
||||
|
||||
let crate_dir = crate::cargo::crate_root();
|
||||
|
||||
let crate_dir = match crate_dir {
|
||||
Ok(dir) => {
|
||||
if let Some(bin) = bin {
|
||||
dir.join(bin)
|
||||
} else {
|
||||
dir
|
||||
}
|
||||
}
|
||||
Err(_) => return Ok(None),
|
||||
};
|
||||
let crate_dir = crate_dir.as_path();
|
||||
|
||||
let Some(dioxus_conf_file) = acquire_dioxus_toml(crate_dir) else {
|
||||
tracing::warn!(?crate_dir, "no dioxus config found for");
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let dioxus_conf_file = dioxus_conf_file.as_path();
|
||||
let cfg = toml::from_str::<DioxusConfig>(&std::fs::read_to_string(dioxus_conf_file)?)
|
||||
.map_err(|err| {
|
||||
let error_location = dioxus_conf_file
|
||||
.strip_prefix(crate_dir)
|
||||
.unwrap_or(dioxus_conf_file)
|
||||
.display();
|
||||
CrateConfigError::LoadDioxusConfig(LoadDioxusConfigError {
|
||||
location: error_location.to_string(),
|
||||
error: err.to_string(),
|
||||
})
|
||||
})
|
||||
.map(Some);
|
||||
match cfg {
|
||||
Ok(Some(mut cfg)) => {
|
||||
let name = cfg.application.name.clone();
|
||||
if cfg.bundle.identifier.is_none() {
|
||||
cfg.bundle.identifier = Some(format!("io.github.{name}"));
|
||||
}
|
||||
if cfg.bundle.publisher.is_none() {
|
||||
cfg.bundle.publisher = Some(name);
|
||||
}
|
||||
|
||||
// Handle Cli Settings
|
||||
if cfg.cli_settings.is_none() {
|
||||
cfg.cli_settings = Some(CliSettings::default());
|
||||
}
|
||||
|
||||
let cli_settings = cfg.cli_settings.as_mut().unwrap();
|
||||
// If the project-level settings doesn't exist, let's grab it from global.
|
||||
if let Some(global_cli_settings) = crate::CliSettings::from_global() {
|
||||
if cli_settings.always_hot_reload.is_none() {
|
||||
cli_settings.always_hot_reload = global_cli_settings.always_hot_reload;
|
||||
}
|
||||
if cli_settings.always_open_browser.is_none() {
|
||||
cli_settings.always_open_browser = global_cli_settings.always_open_browser;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some(cfg))
|
||||
}
|
||||
cfg => cfg,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "cli")]
|
||||
#[tracing::instrument]
|
||||
fn acquire_dioxus_toml(dir: &std::path::Path) -> Option<PathBuf> {
|
||||
use tracing::trace;
|
||||
|
||||
["Dioxus.toml", "dioxus.toml"]
|
||||
.into_iter()
|
||||
.map(|file| dir.join(file))
|
||||
.inspect(|path| trace!("checking [{path:?}]"))
|
||||
.find(|path| path.is_file())
|
||||
}
|
||||
|
||||
impl Default for DioxusConfig {
|
||||
@@ -208,9 +102,6 @@ impl Default for DioxusConfig {
|
||||
out_dir: out_dir_default(),
|
||||
asset_dir: asset_dir_default(),
|
||||
|
||||
#[cfg(feature = "cli")]
|
||||
tools: Default::default(),
|
||||
|
||||
sub_package: None,
|
||||
},
|
||||
web: WebConfig {
|
||||
@@ -242,11 +133,6 @@ impl Default for DioxusConfig {
|
||||
publisher: Some(name),
|
||||
..Default::default()
|
||||
},
|
||||
#[cfg(feature = "cli")]
|
||||
plugin: toml::Value::Table(toml::map::Map::new()),
|
||||
|
||||
#[cfg(feature = "cli")]
|
||||
cli_settings: Some(crate::CliSettings::default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -265,10 +151,6 @@ pub struct ApplicationConfig {
|
||||
#[serde(default = "asset_dir_default")]
|
||||
pub asset_dir: PathBuf,
|
||||
|
||||
#[cfg(feature = "cli")]
|
||||
#[serde(default)]
|
||||
pub tools: std::collections::HashMap<String, toml::Value>,
|
||||
|
||||
#[serde(default)]
|
||||
pub sub_package: Option<String>,
|
||||
}
|
||||
@@ -362,6 +244,16 @@ pub struct WebAppConfig {
|
||||
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 {
|
||||
@@ -429,228 +321,6 @@ pub struct WebHttpsConfig {
|
||||
pub cert_path: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CrateConfig {
|
||||
pub crate_dir: PathBuf,
|
||||
pub workspace_dir: PathBuf,
|
||||
pub target_dir: PathBuf,
|
||||
#[cfg(feature = "cli")]
|
||||
pub manifest: cargo_toml::Manifest<cargo_toml::Value>,
|
||||
pub executable: ExecutableType,
|
||||
pub dioxus_config: DioxusConfig,
|
||||
pub release: bool,
|
||||
pub hot_reload: bool,
|
||||
pub cross_origin_policy: bool,
|
||||
pub verbose: bool,
|
||||
pub custom_profile: Option<String>,
|
||||
pub features: Option<Vec<String>>,
|
||||
pub target: Option<String>,
|
||||
pub cargo_args: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum ExecutableType {
|
||||
Binary(String),
|
||||
Lib(String),
|
||||
Example(String),
|
||||
}
|
||||
|
||||
impl ExecutableType {
|
||||
/// Get the name of the executable if it is a binary or an example.
|
||||
pub fn executable(&self) -> Option<&str> {
|
||||
match self {
|
||||
Self::Binary(bin) | Self::Example(bin) => Some(bin),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CrateConfig {
|
||||
#[cfg(feature = "cli")]
|
||||
pub fn new(bin: Option<PathBuf>) -> Result<Self, CrateConfigError> {
|
||||
let dioxus_config = DioxusConfig::load(bin.clone())?.unwrap_or_default();
|
||||
|
||||
let crate_root = crate::crate_root()?;
|
||||
|
||||
let crate_dir = if let Some(package) = &dioxus_config.application.sub_package {
|
||||
crate_root.join(package)
|
||||
} else if let Some(bin) = bin {
|
||||
crate_root.join(bin)
|
||||
} else {
|
||||
crate_root
|
||||
};
|
||||
|
||||
let meta = crate::Metadata::get()?;
|
||||
let workspace_dir = meta.workspace_root;
|
||||
let target_dir = meta.target_directory;
|
||||
|
||||
let cargo_def = &crate_dir.join("Cargo.toml");
|
||||
|
||||
let manifest = cargo_toml::Manifest::from_path(cargo_def).unwrap();
|
||||
|
||||
let mut output_filename = String::from("dioxus_app");
|
||||
if let Some(package) = &manifest.package.as_ref() {
|
||||
output_filename = match &package.default_run {
|
||||
Some(default_run_target) => default_run_target.to_owned(),
|
||||
None => manifest
|
||||
.bin
|
||||
.iter()
|
||||
.find(|b| {
|
||||
#[allow(clippy::useless_asref)]
|
||||
let matching_bin =
|
||||
b.name == manifest.package.as_ref().map(|pkg| pkg.name.clone());
|
||||
matching_bin
|
||||
})
|
||||
.or(manifest
|
||||
.bin
|
||||
.iter()
|
||||
.find(|b| b.path == Some("src/main.rs".to_owned())))
|
||||
.or(manifest.bin.first())
|
||||
.or(manifest.lib.as_ref())
|
||||
.and_then(|prod| prod.name.clone())
|
||||
.unwrap_or(String::from("dioxus_app")),
|
||||
};
|
||||
}
|
||||
|
||||
let executable = ExecutableType::Binary(output_filename);
|
||||
|
||||
let release = false;
|
||||
let hot_reload = false;
|
||||
let cross_origin_policy = false;
|
||||
let verbose = false;
|
||||
let custom_profile = None;
|
||||
let features = None;
|
||||
let target = None;
|
||||
let cargo_args = vec![];
|
||||
|
||||
Ok(Self {
|
||||
crate_dir,
|
||||
workspace_dir,
|
||||
target_dir,
|
||||
#[cfg(feature = "cli")]
|
||||
manifest,
|
||||
executable,
|
||||
dioxus_config,
|
||||
release,
|
||||
hot_reload,
|
||||
cross_origin_policy,
|
||||
verbose,
|
||||
custom_profile,
|
||||
features,
|
||||
target,
|
||||
cargo_args,
|
||||
})
|
||||
}
|
||||
|
||||
/// Compose an asset directory. Represents the typical "public" directory
|
||||
/// with publicly available resources (configurable in the `Dioxus.toml`).
|
||||
pub fn asset_dir(&self) -> PathBuf {
|
||||
self.crate_dir
|
||||
.join(&self.dioxus_config.application.asset_dir)
|
||||
}
|
||||
|
||||
/// Compose an out directory. Represents the typical "dist" directory that
|
||||
/// is "distributed" after building an application (configurable in the
|
||||
/// `Dioxus.toml`).
|
||||
pub fn out_dir(&self) -> PathBuf {
|
||||
self.crate_dir.join(&self.dioxus_config.application.out_dir)
|
||||
}
|
||||
|
||||
/// Compose an out directory for the fullstack platform. See `out_dir()`
|
||||
/// method.
|
||||
pub fn fullstack_out_dir(&self) -> PathBuf {
|
||||
self.crate_dir.join(".dioxus")
|
||||
}
|
||||
|
||||
/// Compose a target directory for the server (fullstack-only?).
|
||||
pub fn server_target_dir(&self) -> PathBuf {
|
||||
self.fullstack_out_dir().join("ssr")
|
||||
}
|
||||
|
||||
/// Compose a target directory for the client (fullstack-only?).
|
||||
pub fn client_target_dir(&self) -> PathBuf {
|
||||
self.fullstack_out_dir().join("web")
|
||||
}
|
||||
|
||||
pub fn as_example(&mut self, example_name: String) -> &mut Self {
|
||||
self.executable = ExecutableType::Example(example_name);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_release(&mut self, release: bool) -> &mut Self {
|
||||
self.release = release;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_hot_reload(&mut self, hot_reload: bool) -> &mut Self {
|
||||
self.hot_reload = hot_reload;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_cross_origin_policy(&mut self, cross_origin_policy: bool) -> &mut Self {
|
||||
self.cross_origin_policy = cross_origin_policy;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_verbose(&mut self, verbose: bool) -> &mut Self {
|
||||
self.verbose = verbose;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_profile(&mut self, profile: String) -> &mut Self {
|
||||
self.custom_profile = Some(profile);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_features(&mut self, features: Vec<String>) -> &mut Self {
|
||||
self.features = Some(features);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_target(&mut self, target: String) -> &mut Self {
|
||||
self.target = Some(target);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_cargo_args(&mut self, cargo_args: Vec<String>) -> &mut Self {
|
||||
self.cargo_args = cargo_args;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_features(&mut self, feature: Vec<String>) -> &mut Self {
|
||||
if let Some(features) = &mut self.features {
|
||||
features.extend(feature);
|
||||
} else {
|
||||
self.features = Some(feature);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(feature = "cli")]
|
||||
pub fn extend_with_platform(&mut self, platform: Platform) -> &mut Self {
|
||||
let manifest = &self.manifest;
|
||||
let features = match platform {
|
||||
Platform::Web if manifest.features.contains_key("web") => {
|
||||
vec!["web".to_string()]
|
||||
}
|
||||
Platform::Desktop if manifest.features.contains_key("desktop") => {
|
||||
vec!["desktop".to_string()]
|
||||
}
|
||||
_ => {
|
||||
// fullstack has its own feature insertion - we use a different featureset for the client and server
|
||||
vec![]
|
||||
}
|
||||
};
|
||||
self.add_features(features);
|
||||
self
|
||||
}
|
||||
|
||||
/// Check if assets should be pre_compressed. This will only be true in release mode if the user has enabled pre_compress in the web config.
|
||||
pub fn should_pre_compress_web_assets(&self) -> bool {
|
||||
self.dioxus_config.web.pre_compress && self.release
|
||||
}
|
||||
}
|
||||
|
||||
fn true_bool() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
@@ -8,37 +8,21 @@ pub use config::*;
|
||||
mod bundle;
|
||||
pub use bundle::*;
|
||||
|
||||
mod cargo;
|
||||
pub use cargo::*;
|
||||
#[cfg(feature = "cli")]
|
||||
mod serve;
|
||||
#[cfg(feature = "cli")]
|
||||
pub use serve::*;
|
||||
|
||||
#[cfg(feature = "cli")]
|
||||
mod settings;
|
||||
|
||||
#[cfg(feature = "cli")]
|
||||
pub use settings::*;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub mod __private {
|
||||
use crate::CrateConfig;
|
||||
use crate::DioxusConfig;
|
||||
|
||||
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: &CrateConfig) -> CrateConfigDropGuard {
|
||||
pub fn save_config(config: &DioxusConfig) -> CrateConfigDropGuard {
|
||||
std::env::set_var(CONFIG_ENV, serde_json::to_string(config).unwrap());
|
||||
std::env::set_var(
|
||||
CONFIG_BASE_PATH_ENV,
|
||||
config
|
||||
.dioxus_config
|
||||
.web
|
||||
.app
|
||||
.base_path
|
||||
.clone()
|
||||
.unwrap_or_default(),
|
||||
config.web.app.base_path.clone().unwrap_or_default(),
|
||||
);
|
||||
CrateConfigDropGuard
|
||||
}
|
||||
@@ -53,7 +37,7 @@ pub mod __private {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "cli")]
|
||||
#[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";
|
||||
@@ -74,7 +58,7 @@ 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::CrateConfig, DioxusCLINotUsed>,
|
||||
Result<crate::config::DioxusConfig, DioxusCLINotUsed>,
|
||||
> = once_cell::sync::Lazy::new(|| {
|
||||
CURRENT_CONFIG_JSON
|
||||
.and_then(|config| serde_json::from_str(config).ok())
|
||||
|
||||
@@ -1,35 +1,98 @@
|
||||
#![allow(unused)] // lots of configs...
|
||||
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4};
|
||||
|
||||
#[cfg(feature = "read-from-args")]
|
||||
use clap::Parser;
|
||||
|
||||
/// Arguments for the serve command
|
||||
#[derive(Clone, Debug, Parser, serde::Serialize, serde::Deserialize)]
|
||||
pub struct ServeArguments {
|
||||
/// 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)]
|
||||
pub addr: Option<std::net::IpAddr>,
|
||||
#[clap(long, default_value_t = default_address())]
|
||||
pub addr: std::net::IpAddr,
|
||||
}
|
||||
|
||||
impl Default for ServeArguments {
|
||||
#[cfg(feature = "read-from-args")]
|
||||
impl Default for AddressArguments {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
port: default_port(),
|
||||
addr: None,
|
||||
addr: default_address(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ServeArguments {
|
||||
#[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))
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
use crate::CrateConfigError;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
fs,
|
||||
io::{Error, ErrorKind},
|
||||
path::PathBuf,
|
||||
};
|
||||
use tracing::{debug, error, warn};
|
||||
|
||||
const GLOBAL_SETTINGS_FILE_NAME: &str = "dioxus/settings.toml";
|
||||
|
||||
/// Describes cli settings from project or global level.
|
||||
/// The order of priority goes:
|
||||
/// 1. CLI Flags/Arguments
|
||||
/// 2. Project-level Settings
|
||||
/// 3. Global-level settings.
|
||||
///
|
||||
/// This allows users to control the cli settings with ease.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct CliSettings {
|
||||
/// Describes whether hot reload should always be on.
|
||||
pub always_hot_reload: Option<bool>,
|
||||
/// Describes whether the CLI should always open the browser for Web targets.
|
||||
pub always_open_browser: Option<bool>,
|
||||
}
|
||||
|
||||
impl CliSettings {
|
||||
/// Get the current settings structure from global.
|
||||
pub fn from_global() -> Option<Self> {
|
||||
let Some(path) = dirs::data_local_dir() else {
|
||||
warn!("failed to get local data directory, some config keys may be missing");
|
||||
return None;
|
||||
};
|
||||
|
||||
let path = path.join(GLOBAL_SETTINGS_FILE_NAME);
|
||||
let Some(data) = fs::read_to_string(path).ok() else {
|
||||
// We use a debug here because we expect the file to not exist.
|
||||
debug!("failed to read `{}` config file", GLOBAL_SETTINGS_FILE_NAME);
|
||||
return None;
|
||||
};
|
||||
|
||||
let data = toml::from_str::<CliSettings>(&data).ok();
|
||||
if data.is_none() {
|
||||
warn!(
|
||||
"failed to parse `{}` config file",
|
||||
GLOBAL_SETTINGS_FILE_NAME
|
||||
);
|
||||
}
|
||||
|
||||
data
|
||||
}
|
||||
|
||||
/// Save the current structure to the global settings toml.
|
||||
/// This does not save to project-level settings.
|
||||
pub fn save(self) -> Result<Self, CrateConfigError> {
|
||||
let path = Self::get_settings_path().ok_or_else(|| {
|
||||
error!("failed to get settings path");
|
||||
CrateConfigError::Io(Error::new(
|
||||
ErrorKind::NotFound,
|
||||
"failed to get settings path",
|
||||
))
|
||||
})?;
|
||||
|
||||
let data = toml::to_string_pretty(&self).map_err(|e| {
|
||||
error!(?self, "failed to parse config into toml");
|
||||
CrateConfigError::Io(Error::new(ErrorKind::Other, e.to_string()))
|
||||
})?;
|
||||
|
||||
let result = fs::write(path.clone(), data.clone());
|
||||
if let Err(e) = result {
|
||||
error!(?data, ?path, "failed to save global cli settings");
|
||||
return Err(CrateConfigError::Io(e));
|
||||
}
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Get the path to the settings toml file.
|
||||
pub fn get_settings_path() -> Option<PathBuf> {
|
||||
let Some(path) = dirs::data_local_dir() else {
|
||||
warn!("failed to get local data directory, some config keys may be missing");
|
||||
return None;
|
||||
};
|
||||
|
||||
Some(path.join(GLOBAL_SETTINGS_FILE_NAME))
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user