config.rs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576
  1. use crate::BundleConfig;
  2. use crate::CargoError;
  3. use core::fmt::{Display, Formatter};
  4. use serde::{Deserialize, Serialize};
  5. use std::path::PathBuf;
  6. #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Debug)]
  7. #[cfg_attr(feature = "cli", derive(clap::ValueEnum))]
  8. pub enum Platform {
  9. /// Targeting the web platform using WASM
  10. #[cfg_attr(feature = "cli", clap(name = "web"))]
  11. #[serde(rename = "web")]
  12. Web,
  13. /// Targeting the desktop platform using Tao/Wry-based webview
  14. #[cfg_attr(feature = "cli", clap(name = "desktop"))]
  15. #[serde(rename = "desktop")]
  16. Desktop,
  17. /// Targeting the server platform using Axum and Dioxus-Fullstack
  18. #[cfg_attr(feature = "cli", clap(name = "fullstack"))]
  19. #[serde(rename = "fullstack")]
  20. Fullstack,
  21. }
  22. #[derive(Debug, Clone, Serialize, Deserialize)]
  23. pub struct DioxusConfig {
  24. pub application: ApplicationConfig,
  25. pub web: WebConfig,
  26. #[serde(default)]
  27. pub bundle: BundleConfig,
  28. #[cfg(feature = "cli")]
  29. #[serde(default = "default_plugin")]
  30. pub plugin: toml::Value,
  31. }
  32. #[cfg(feature = "cli")]
  33. fn default_plugin() -> toml::Value {
  34. toml::Value::Boolean(true)
  35. }
  36. #[derive(Debug, Clone, Serialize, Deserialize)]
  37. pub struct LoadDioxusConfigError {
  38. location: String,
  39. error: String,
  40. }
  41. impl std::fmt::Display for LoadDioxusConfigError {
  42. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  43. write!(f, "{} {}", self.location, self.error)
  44. }
  45. }
  46. impl std::error::Error for LoadDioxusConfigError {}
  47. #[derive(Debug)]
  48. pub enum CrateConfigError {
  49. Cargo(CargoError),
  50. Io(std::io::Error),
  51. #[cfg(feature = "cli")]
  52. Toml(toml::de::Error),
  53. LoadDioxusConfig(LoadDioxusConfigError),
  54. }
  55. impl From<CargoError> for CrateConfigError {
  56. fn from(err: CargoError) -> Self {
  57. Self::Cargo(err)
  58. }
  59. }
  60. impl From<std::io::Error> for CrateConfigError {
  61. fn from(err: std::io::Error) -> Self {
  62. Self::Io(err)
  63. }
  64. }
  65. #[cfg(feature = "cli")]
  66. impl From<toml::de::Error> for CrateConfigError {
  67. fn from(err: toml::de::Error) -> Self {
  68. Self::Toml(err)
  69. }
  70. }
  71. impl From<LoadDioxusConfigError> for CrateConfigError {
  72. fn from(err: LoadDioxusConfigError) -> Self {
  73. Self::LoadDioxusConfig(err)
  74. }
  75. }
  76. impl Display for CrateConfigError {
  77. fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
  78. match self {
  79. Self::Cargo(err) => write!(f, "{}", err),
  80. Self::Io(err) => write!(f, "{}", err),
  81. #[cfg(feature = "cli")]
  82. Self::Toml(err) => write!(f, "{}", err),
  83. Self::LoadDioxusConfig(err) => write!(f, "{}", err),
  84. }
  85. }
  86. }
  87. impl std::error::Error for CrateConfigError {}
  88. impl DioxusConfig {
  89. #[cfg(feature = "cli")]
  90. /// Load the dioxus config from a path
  91. pub fn load(bin: Option<PathBuf>) -> Result<Option<DioxusConfig>, CrateConfigError> {
  92. let crate_dir = crate::cargo::crate_root();
  93. let crate_dir = match crate_dir {
  94. Ok(dir) => {
  95. if let Some(bin) = bin {
  96. dir.join(bin)
  97. } else {
  98. dir
  99. }
  100. }
  101. Err(_) => return Ok(None),
  102. };
  103. let crate_dir = crate_dir.as_path();
  104. let Some(dioxus_conf_file) = acquire_dioxus_toml(crate_dir) else {
  105. return Ok(None);
  106. };
  107. let dioxus_conf_file = dioxus_conf_file.as_path();
  108. let cfg = toml::from_str::<DioxusConfig>(&std::fs::read_to_string(dioxus_conf_file)?)
  109. .map_err(|err| {
  110. let error_location = dioxus_conf_file
  111. .strip_prefix(crate_dir)
  112. .unwrap_or(dioxus_conf_file)
  113. .display();
  114. CrateConfigError::LoadDioxusConfig(LoadDioxusConfigError {
  115. location: error_location.to_string(),
  116. error: err.to_string(),
  117. })
  118. })
  119. .map(Some);
  120. match cfg {
  121. Ok(Some(mut cfg)) => {
  122. let name = cfg.application.name.clone();
  123. if cfg.bundle.identifier.is_none() {
  124. cfg.bundle.identifier = Some(format!("io.github.{name}"));
  125. }
  126. if cfg.bundle.publisher.is_none() {
  127. cfg.bundle.publisher = Some(name);
  128. }
  129. Ok(Some(cfg))
  130. }
  131. cfg => cfg,
  132. }
  133. }
  134. }
  135. #[cfg(feature = "cli")]
  136. fn acquire_dioxus_toml(dir: &std::path::Path) -> Option<PathBuf> {
  137. // prefer uppercase
  138. let uppercase_conf = dir.join("Dioxus.toml");
  139. if uppercase_conf.is_file() {
  140. return Some(uppercase_conf);
  141. }
  142. // lowercase is fine too
  143. let lowercase_conf = dir.join("dioxus.toml");
  144. if lowercase_conf.is_file() {
  145. return Some(lowercase_conf);
  146. }
  147. None
  148. }
  149. impl Default for DioxusConfig {
  150. fn default() -> Self {
  151. let name = default_name();
  152. Self {
  153. application: ApplicationConfig {
  154. name: name.clone(),
  155. default_platform: default_platform(),
  156. out_dir: out_dir_default(),
  157. asset_dir: asset_dir_default(),
  158. hot_reload: hot_reload_default(),
  159. #[cfg(feature = "cli")]
  160. tools: Default::default(),
  161. sub_package: None,
  162. },
  163. web: WebConfig {
  164. app: WebAppConfig {
  165. title: default_title(),
  166. base_path: None,
  167. },
  168. proxy: vec![],
  169. watcher: Default::default(),
  170. resource: WebResourceConfig {
  171. dev: WebDevResourceConfig {
  172. style: vec![],
  173. script: vec![],
  174. },
  175. style: Some(vec![]),
  176. script: Some(vec![]),
  177. },
  178. https: WebHttpsConfig {
  179. enabled: None,
  180. mkcert: None,
  181. key_path: None,
  182. cert_path: None,
  183. },
  184. },
  185. bundle: BundleConfig {
  186. identifier: Some(format!("io.github.{name}")),
  187. publisher: Some(name),
  188. ..Default::default()
  189. },
  190. #[cfg(feature = "cli")]
  191. plugin: toml::Value::Table(toml::map::Map::new()),
  192. }
  193. }
  194. }
  195. #[derive(Debug, Clone, Serialize, Deserialize)]
  196. pub struct ApplicationConfig {
  197. #[serde(default = "default_name")]
  198. pub name: String,
  199. #[serde(default = "default_platform")]
  200. pub default_platform: Platform,
  201. #[serde(default = "out_dir_default")]
  202. pub out_dir: PathBuf,
  203. #[serde(default = "asset_dir_default")]
  204. pub asset_dir: PathBuf,
  205. #[serde(default = "hot_reload_default")]
  206. pub hot_reload: bool,
  207. #[cfg(feature = "cli")]
  208. #[serde(default)]
  209. pub tools: std::collections::HashMap<String, toml::Value>,
  210. #[serde(default)]
  211. pub sub_package: Option<String>,
  212. }
  213. fn default_name() -> String {
  214. "name".into()
  215. }
  216. fn default_platform() -> Platform {
  217. Platform::Web
  218. }
  219. fn hot_reload_default() -> bool {
  220. true
  221. }
  222. fn asset_dir_default() -> PathBuf {
  223. PathBuf::from("public")
  224. }
  225. fn out_dir_default() -> PathBuf {
  226. PathBuf::from("dist")
  227. }
  228. #[derive(Debug, Clone, Serialize, Deserialize)]
  229. pub struct WebConfig {
  230. #[serde(default)]
  231. pub app: WebAppConfig,
  232. #[serde(default)]
  233. pub proxy: Vec<WebProxyConfig>,
  234. #[serde(default)]
  235. pub watcher: WebWatcherConfig,
  236. #[serde(default)]
  237. pub resource: WebResourceConfig,
  238. #[serde(default)]
  239. pub https: WebHttpsConfig,
  240. }
  241. #[derive(Debug, Clone, Serialize, Deserialize)]
  242. pub struct WebAppConfig {
  243. #[serde(default = "default_title")]
  244. pub title: String,
  245. pub base_path: Option<String>,
  246. }
  247. impl Default for WebAppConfig {
  248. fn default() -> Self {
  249. Self {
  250. title: default_title(),
  251. base_path: None,
  252. }
  253. }
  254. }
  255. fn default_title() -> String {
  256. "dioxus | ⛺".into()
  257. }
  258. #[derive(Debug, Clone, Serialize, Deserialize)]
  259. pub struct WebProxyConfig {
  260. pub backend: String,
  261. }
  262. #[derive(Debug, Clone, Serialize, Deserialize)]
  263. pub struct WebWatcherConfig {
  264. #[serde(default = "watch_path_default")]
  265. pub watch_path: Vec<PathBuf>,
  266. #[serde(default)]
  267. pub reload_html: bool,
  268. #[serde(default = "true_bool")]
  269. pub index_on_404: bool,
  270. }
  271. impl Default for WebWatcherConfig {
  272. fn default() -> Self {
  273. Self {
  274. watch_path: watch_path_default(),
  275. reload_html: false,
  276. index_on_404: true,
  277. }
  278. }
  279. }
  280. fn watch_path_default() -> Vec<PathBuf> {
  281. vec![PathBuf::from("src"), PathBuf::from("examples")]
  282. }
  283. #[derive(Default, Debug, Clone, Serialize, Deserialize)]
  284. pub struct WebResourceConfig {
  285. pub dev: WebDevResourceConfig,
  286. pub style: Option<Vec<PathBuf>>,
  287. pub script: Option<Vec<PathBuf>>,
  288. }
  289. #[derive(Default, Debug, Clone, Serialize, Deserialize)]
  290. pub struct WebDevResourceConfig {
  291. #[serde(default)]
  292. pub style: Vec<PathBuf>,
  293. #[serde(default)]
  294. pub script: Vec<PathBuf>,
  295. }
  296. #[derive(Debug, Default, Clone, Serialize, Deserialize)]
  297. pub struct WebHttpsConfig {
  298. pub enabled: Option<bool>,
  299. pub mkcert: Option<bool>,
  300. pub key_path: Option<String>,
  301. pub cert_path: Option<String>,
  302. }
  303. #[derive(Debug, Clone, Serialize, Deserialize)]
  304. pub struct CrateConfig {
  305. pub crate_dir: PathBuf,
  306. pub workspace_dir: PathBuf,
  307. pub target_dir: PathBuf,
  308. #[cfg(feature = "cli")]
  309. pub manifest: cargo_toml::Manifest<cargo_toml::Value>,
  310. pub executable: ExecutableType,
  311. pub dioxus_config: DioxusConfig,
  312. pub release: bool,
  313. pub hot_reload: bool,
  314. pub cross_origin_policy: bool,
  315. pub verbose: bool,
  316. pub custom_profile: Option<String>,
  317. pub features: Option<Vec<String>>,
  318. pub target: Option<String>,
  319. pub cargo_args: Vec<String>,
  320. }
  321. #[derive(Debug, Clone, Serialize, Deserialize)]
  322. pub enum ExecutableType {
  323. Binary(String),
  324. Lib(String),
  325. Example(String),
  326. }
  327. impl ExecutableType {
  328. /// Get the name of the executable if it is a binary or an example.
  329. pub fn executable(&self) -> Option<&str> {
  330. match self {
  331. Self::Binary(bin) | Self::Example(bin) => Some(bin),
  332. _ => None,
  333. }
  334. }
  335. }
  336. impl CrateConfig {
  337. #[cfg(feature = "cli")]
  338. pub fn new(bin: Option<PathBuf>) -> Result<Self, CrateConfigError> {
  339. let dioxus_config = DioxusConfig::load(bin.clone())?.unwrap_or_default();
  340. let crate_root = crate::crate_root()?;
  341. let crate_dir = if let Some(package) = &dioxus_config.application.sub_package {
  342. crate_root.join(package)
  343. } else if let Some(bin) = bin {
  344. crate_root.join(bin)
  345. } else {
  346. crate_root
  347. };
  348. let meta = crate::Metadata::get()?;
  349. let workspace_dir = meta.workspace_root;
  350. let target_dir = meta.target_directory;
  351. let cargo_def = &crate_dir.join("Cargo.toml");
  352. let manifest = cargo_toml::Manifest::from_path(cargo_def).unwrap();
  353. let mut output_filename = String::from("dioxus_app");
  354. if let Some(package) = &manifest.package.as_ref() {
  355. output_filename = match &package.default_run {
  356. Some(default_run_target) => default_run_target.to_owned(),
  357. None => manifest
  358. .bin
  359. .iter()
  360. .find(|b| {
  361. #[allow(clippy::useless_asref)]
  362. let matching_bin =
  363. b.name == manifest.package.as_ref().map(|pkg| pkg.name.clone());
  364. matching_bin
  365. })
  366. .or(manifest
  367. .bin
  368. .iter()
  369. .find(|b| b.path == Some("src/main.rs".to_owned())))
  370. .or(manifest.bin.first())
  371. .or(manifest.lib.as_ref())
  372. .and_then(|prod| prod.name.clone())
  373. .unwrap_or(String::from("dioxus_app")),
  374. };
  375. }
  376. let executable = ExecutableType::Binary(output_filename);
  377. let release = false;
  378. let hot_reload = false;
  379. let cross_origin_policy = false;
  380. let verbose = false;
  381. let custom_profile = None;
  382. let features = None;
  383. let target = None;
  384. let cargo_args = vec![];
  385. Ok(Self {
  386. crate_dir,
  387. workspace_dir,
  388. target_dir,
  389. #[cfg(feature = "cli")]
  390. manifest,
  391. executable,
  392. dioxus_config,
  393. release,
  394. hot_reload,
  395. cross_origin_policy,
  396. verbose,
  397. custom_profile,
  398. features,
  399. target,
  400. cargo_args,
  401. })
  402. }
  403. /// Compose an asset directory. Represents the typical "public" directory
  404. /// with publicly available resources (configurable in the `Dioxus.toml`).
  405. pub fn asset_dir(&self) -> PathBuf {
  406. self.crate_dir
  407. .join(&self.dioxus_config.application.asset_dir)
  408. }
  409. /// Compose an out directory. Represents the typical "dist" directory that
  410. /// is "distributed" after building an application (configurable in the
  411. /// `Dioxus.toml`).
  412. pub fn out_dir(&self) -> PathBuf {
  413. self.crate_dir.join(&self.dioxus_config.application.out_dir)
  414. }
  415. /// Compose an out directory for the fullstack platform. See `out_dir()`
  416. /// method.
  417. pub fn fullstack_out_dir(&self) -> PathBuf {
  418. self.crate_dir.join(".dioxus")
  419. }
  420. /// Compose a target directory for the server (fullstack-only?).
  421. pub fn server_target_dir(&self) -> PathBuf {
  422. self.fullstack_out_dir().join("ssr")
  423. }
  424. /// Compose a target directory for the client (fullstack-only?).
  425. pub fn client_target_dir(&self) -> PathBuf {
  426. self.fullstack_out_dir().join("web")
  427. }
  428. pub fn as_example(&mut self, example_name: String) -> &mut Self {
  429. self.executable = ExecutableType::Example(example_name);
  430. self
  431. }
  432. pub fn with_release(&mut self, release: bool) -> &mut Self {
  433. self.release = release;
  434. self
  435. }
  436. pub fn with_hot_reload(&mut self, hot_reload: bool) -> &mut Self {
  437. self.hot_reload = hot_reload;
  438. self
  439. }
  440. pub fn with_cross_origin_policy(&mut self, cross_origin_policy: bool) -> &mut Self {
  441. self.cross_origin_policy = cross_origin_policy;
  442. self
  443. }
  444. pub fn with_verbose(&mut self, verbose: bool) -> &mut Self {
  445. self.verbose = verbose;
  446. self
  447. }
  448. pub fn set_profile(&mut self, profile: String) -> &mut Self {
  449. self.custom_profile = Some(profile);
  450. self
  451. }
  452. pub fn set_features(&mut self, features: Vec<String>) -> &mut Self {
  453. self.features = Some(features);
  454. self
  455. }
  456. pub fn set_target(&mut self, target: String) -> &mut Self {
  457. self.target = Some(target);
  458. self
  459. }
  460. pub fn set_cargo_args(&mut self, cargo_args: Vec<String>) -> &mut Self {
  461. self.cargo_args = cargo_args;
  462. self
  463. }
  464. pub fn add_features(&mut self, feature: Vec<String>) -> &mut Self {
  465. if let Some(features) = &mut self.features {
  466. features.extend(feature);
  467. } else {
  468. self.features = Some(feature);
  469. }
  470. self
  471. }
  472. #[cfg(feature = "cli")]
  473. pub fn extend_with_platform(&mut self, platform: Platform) -> &mut Self {
  474. let manifest = &self.manifest;
  475. let features = match platform {
  476. Platform::Web if manifest.features.contains_key("web") => {
  477. vec!["web".to_string()]
  478. }
  479. Platform::Desktop if manifest.features.contains_key("desktop") => {
  480. vec!["desktop".to_string()]
  481. }
  482. _ => {
  483. // fullstack has its own feature insertion - we use a different featureset for the client and server
  484. vec![]
  485. }
  486. };
  487. self.add_features(features);
  488. self
  489. }
  490. }
  491. fn true_bool() -> bool {
  492. true
  493. }