launch.rs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  1. //! Launch helper macros for fullstack apps
  2. #![allow(clippy::new_without_default)]
  3. #![allow(unused)]
  4. use dioxus_config_macro::*;
  5. use std::any::Any;
  6. use crate::prelude::*;
  7. /// A builder for a fullstack app.
  8. #[must_use]
  9. pub struct LaunchBuilder<Cfg: 'static = (), ContextFn: ?Sized = ValidContext> {
  10. launch_fn: LaunchFn<Cfg, ContextFn>,
  11. contexts: Vec<Box<ContextFn>>,
  12. platform_config: Option<Cfg>,
  13. }
  14. pub type LaunchFn<Cfg, Context> = fn(fn() -> Element, Vec<Box<Context>>, Cfg);
  15. #[cfg(any(
  16. feature = "fullstack",
  17. feature = "static-generation",
  18. feature = "liveview"
  19. ))]
  20. type ValidContext = SendContext;
  21. #[cfg(not(any(
  22. feature = "fullstack",
  23. feature = "static-generation",
  24. feature = "liveview"
  25. )))]
  26. type ValidContext = UnsendContext;
  27. type SendContext = dyn Fn() -> Box<dyn Any + Send + Sync> + Send + Sync + 'static;
  28. type UnsendContext = dyn Fn() -> Box<dyn Any> + 'static;
  29. impl LaunchBuilder {
  30. /// Create a new builder for your application. This will create a launch configuration for the current platform based on the features enabled on the `dioxus` crate.
  31. // If you aren't using a third party renderer and this is not a docs.rs build, generate a warning about no renderer being enabled
  32. #[cfg_attr(
  33. all(not(any(
  34. docsrs,
  35. feature = "third-party-renderer",
  36. feature = "liveview",
  37. feature = "desktop",
  38. feature = "mobile",
  39. feature = "web",
  40. feature = "fullstack",
  41. feature = "static-generation"
  42. ))),
  43. deprecated(
  44. note = "No renderer is enabled. You must enable a renderer feature on the dioxus crate before calling the launch function.\nAdd `web`, `desktop`, `mobile`, `fullstack`, or `static-generation` to the `features` of dioxus field in your Cargo.toml.\n# Example\n```toml\n# ...\n[dependencies]\ndioxus = { version = \"0.5.0\", features = [\"web\"] }\n# ...\n```"
  45. )
  46. )]
  47. pub fn new() -> LaunchBuilder<current_platform::Config, ValidContext> {
  48. LaunchBuilder {
  49. launch_fn: |root, contexts, cfg| current_platform::launch(root, contexts, cfg),
  50. contexts: Vec::new(),
  51. platform_config: None,
  52. }
  53. }
  54. /// Launch your web application.
  55. #[cfg(feature = "web")]
  56. #[cfg_attr(docsrs, doc(cfg(feature = "web")))]
  57. pub fn web() -> LaunchBuilder<dioxus_web::Config, UnsendContext> {
  58. LaunchBuilder {
  59. launch_fn: dioxus_web::launch::launch,
  60. contexts: Vec::new(),
  61. platform_config: None,
  62. }
  63. }
  64. /// Launch your desktop application.
  65. #[cfg(feature = "desktop")]
  66. #[cfg_attr(docsrs, doc(cfg(feature = "desktop")))]
  67. pub fn desktop() -> LaunchBuilder<dioxus_desktop::Config, UnsendContext> {
  68. LaunchBuilder {
  69. launch_fn: |root, contexts, cfg| dioxus_desktop::launch::launch(root, contexts, cfg),
  70. contexts: Vec::new(),
  71. platform_config: None,
  72. }
  73. }
  74. /// Launch your fullstack application.
  75. #[cfg(feature = "fullstack")]
  76. #[cfg_attr(docsrs, doc(cfg(feature = "fullstack")))]
  77. pub fn fullstack() -> LaunchBuilder<dioxus_fullstack::Config, SendContext> {
  78. LaunchBuilder {
  79. launch_fn: |root, contexts, cfg| dioxus_fullstack::launch::launch(root, contexts, cfg),
  80. contexts: Vec::new(),
  81. platform_config: None,
  82. }
  83. }
  84. /// Launch your static site generation application.
  85. #[cfg(feature = "static-generation")]
  86. #[cfg_attr(docsrs, doc(cfg(feature = "static-generation")))]
  87. pub fn static_generation() -> LaunchBuilder<dioxus_static_site_generation::Config, SendContext>
  88. {
  89. LaunchBuilder {
  90. launch_fn: |root, contexts, cfg| {
  91. dioxus_static_site_generation::launch::launch(root, contexts, cfg)
  92. },
  93. contexts: Vec::new(),
  94. platform_config: None,
  95. }
  96. }
  97. /// Launch your fullstack application.
  98. #[cfg(feature = "mobile")]
  99. #[cfg_attr(docsrs, doc(cfg(feature = "mobile")))]
  100. pub fn mobile() -> LaunchBuilder<dioxus_mobile::Config, UnsendContext> {
  101. LaunchBuilder {
  102. launch_fn: |root, contexts, cfg| dioxus_mobile::launch::launch(root, contexts, cfg),
  103. contexts: Vec::new(),
  104. platform_config: None,
  105. }
  106. }
  107. /// Provide a custom launch function for your application.
  108. ///
  109. /// Useful for third party renderers to tap into the launch builder API without having to reimplement it.
  110. pub fn custom<Cfg, List>(launch_fn: LaunchFn<Cfg, List>) -> LaunchBuilder<Cfg, List> {
  111. LaunchBuilder {
  112. launch_fn,
  113. contexts: vec![],
  114. platform_config: None,
  115. }
  116. }
  117. }
  118. // Fullstack platform builder
  119. impl<Cfg> LaunchBuilder<Cfg, UnsendContext> {
  120. /// Inject state into the root component's context that is created on the thread that the app is launched on.
  121. pub fn with_context_provider(mut self, state: impl Fn() -> Box<dyn Any> + 'static) -> Self {
  122. self.contexts.push(Box::new(state) as Box<UnsendContext>);
  123. self
  124. }
  125. /// Inject state into the root component's context.
  126. pub fn with_context(mut self, state: impl Any + Clone + 'static) -> Self {
  127. self.contexts
  128. .push(Box::new(move || Box::new(state.clone())));
  129. self
  130. }
  131. }
  132. impl<Cfg> LaunchBuilder<Cfg, SendContext> {
  133. /// Inject state into the root component's context that is created on the thread that the app is launched on.
  134. pub fn with_context_provider(
  135. mut self,
  136. state: impl Fn() -> Box<dyn Any + Send + Sync> + Send + Sync + 'static,
  137. ) -> Self {
  138. self.contexts.push(Box::new(state) as Box<SendContext>);
  139. self
  140. }
  141. /// Inject state into the root component's context.
  142. pub fn with_context(mut self, state: impl Any + Clone + Send + Sync + 'static) -> Self {
  143. self.contexts
  144. .push(Box::new(move || Box::new(state.clone())));
  145. self
  146. }
  147. }
  148. /// A trait for converting a type into a platform-specific config:
  149. /// - A unit value will be converted into `None`
  150. /// - Any config will be converted into `Some(config)`
  151. /// - If the config is for another platform, it will be converted into `None`
  152. pub trait TryIntoConfig<Config = Self> {
  153. fn into_config(self, config: &mut Option<Config>);
  154. }
  155. // A config can always be converted into itself
  156. impl<Cfg> TryIntoConfig<Cfg> for Cfg {
  157. fn into_config(self, config: &mut Option<Cfg>) {
  158. *config = Some(self);
  159. }
  160. }
  161. // The unit type can be converted into the current platform config.
  162. // This makes it possible to use the `desktop!`, `web!`, etc macros with the launch API.
  163. #[cfg(any(
  164. feature = "liveview",
  165. feature = "desktop",
  166. feature = "mobile",
  167. feature = "web",
  168. feature = "fullstack"
  169. ))]
  170. impl TryIntoConfig<current_platform::Config> for () {
  171. fn into_config(self, config: &mut Option<current_platform::Config>) {}
  172. }
  173. impl<Cfg: Default + 'static, ContextFn: ?Sized> LaunchBuilder<Cfg, ContextFn> {
  174. /// Provide a platform-specific config to the builder.
  175. pub fn with_cfg(mut self, config: impl TryIntoConfig<Cfg>) -> Self {
  176. config.into_config(&mut self.platform_config);
  177. self
  178. }
  179. // Static generation is the only platform that may exit. We can't use the `!` type here
  180. #[cfg(any(feature = "static-generation", feature = "web"))]
  181. /// Launch your application.
  182. pub fn launch(self, app: fn() -> Element) {
  183. let cfg = self.platform_config.unwrap_or_default();
  184. (self.launch_fn)(app, self.contexts, cfg)
  185. }
  186. #[cfg(not(any(feature = "static-generation", feature = "web")))]
  187. /// Launch your application.
  188. pub fn launch(self, app: fn() -> Element) -> ! {
  189. let cfg = self.platform_config.unwrap_or_default();
  190. (self.launch_fn)(app, self.contexts, cfg);
  191. unreachable!("Launching an application will never exit")
  192. }
  193. }
  194. /// Re-export the platform we expect the user wants
  195. ///
  196. /// If multiple platforms are enabled, we use this priority (from highest to lowest):
  197. /// - `fullstack`
  198. /// - `desktop`
  199. /// - `mobile`
  200. /// - `static-generation`
  201. /// - `web`
  202. /// - `liveview`
  203. mod current_platform {
  204. macro_rules! if_else_cfg {
  205. (if $attr:meta { $($then:item)* } else { $($else:item)* }) => {
  206. $(
  207. #[cfg($attr)]
  208. $then
  209. )*
  210. $(
  211. #[cfg(not($attr))]
  212. $else
  213. )*
  214. };
  215. }
  216. use crate::prelude::TryIntoConfig;
  217. #[cfg(feature = "fullstack")]
  218. pub use dioxus_fullstack::launch::*;
  219. #[cfg(all(feature = "fullstack", feature = "axum"))]
  220. impl TryIntoConfig<crate::launch::current_platform::Config>
  221. for ::dioxus_fullstack::prelude::ServeConfigBuilder
  222. {
  223. fn into_config(self, config: &mut Option<crate::launch::current_platform::Config>) {
  224. match config {
  225. Some(config) => config.set_server_cfg(self),
  226. None => {
  227. *config =
  228. Some(crate::launch::current_platform::Config::new().with_server_cfg(self))
  229. }
  230. }
  231. }
  232. }
  233. #[cfg(any(feature = "desktop", feature = "mobile"))]
  234. if_else_cfg! {
  235. if not(feature = "fullstack") {
  236. #[cfg(feature = "desktop")]
  237. pub use dioxus_desktop::launch::*;
  238. #[cfg(not(feature = "desktop"))]
  239. pub use dioxus_mobile::launch::*;
  240. } else {
  241. if_else_cfg! {
  242. if feature = "desktop" {
  243. impl TryIntoConfig<crate::launch::current_platform::Config> for ::dioxus_desktop::Config {
  244. fn into_config(self, config: &mut Option<crate::launch::current_platform::Config>) {
  245. match config {
  246. Some(config) => config.set_desktop_config(self),
  247. None => *config = Some(crate::launch::current_platform::Config::new().with_desktop_config(self)),
  248. }
  249. }
  250. }
  251. } else {
  252. impl TryIntoConfig<crate::launch::current_platform::Config> for ::dioxus_mobile::Config {
  253. fn into_config(self, config: &mut Option<crate::launch::current_platform::Config>) {
  254. match config {
  255. Some(config) => config.set_mobile_cfg(self),
  256. None => *config = Some(crate::launch::current_platform::Config::new().with_mobile_cfg(self)),
  257. }
  258. }
  259. }
  260. }
  261. }
  262. }
  263. }
  264. #[cfg(feature = "static-generation")]
  265. if_else_cfg! {
  266. if all(not(feature = "fullstack"), not(feature = "desktop"), not(feature = "mobile")) {
  267. pub use dioxus_static_site_generation::launch::*;
  268. } else {
  269. impl TryIntoConfig<crate::launch::current_platform::Config> for ::dioxus_static_site_generation::Config {
  270. fn into_config(self, config: &mut Option<crate::launch::current_platform::Config>) {}
  271. }
  272. }
  273. }
  274. #[cfg(feature = "web")]
  275. if_else_cfg! {
  276. if not(any(feature = "desktop", feature = "mobile", feature = "fullstack", feature = "static-generation")) {
  277. pub use dioxus_web::launch::*;
  278. } else {
  279. if_else_cfg! {
  280. if feature = "fullstack" {
  281. impl TryIntoConfig<crate::launch::current_platform::Config> for ::dioxus_web::Config {
  282. fn into_config(self, config: &mut Option<crate::launch::current_platform::Config>) {
  283. match config {
  284. Some(config) => config.set_web_config(self),
  285. None => *config = Some(crate::launch::current_platform::Config::new().with_web_config(self)),
  286. }
  287. }
  288. }
  289. } else {
  290. impl TryIntoConfig<crate::launch::current_platform::Config> for ::dioxus_web::Config {
  291. fn into_config(self, config: &mut Option<crate::launch::current_platform::Config>) {}
  292. }
  293. }
  294. }
  295. }
  296. }
  297. #[cfg(feature = "liveview")]
  298. if_else_cfg! {
  299. if
  300. not(any(
  301. feature = "web",
  302. feature = "desktop",
  303. feature = "mobile",
  304. feature = "fullstack",
  305. feature = "static-generation"
  306. ))
  307. {
  308. pub use dioxus_liveview::launch::*;
  309. } else {
  310. impl<R: ::dioxus_liveview::LiveviewRouter> TryIntoConfig<crate::launch::current_platform::Config> for ::dioxus_liveview::Config<R> {
  311. fn into_config(self, config: &mut Option<crate::launch::current_platform::Config>) {}
  312. }
  313. }
  314. }
  315. #[cfg(not(any(
  316. feature = "liveview",
  317. feature = "desktop",
  318. feature = "mobile",
  319. feature = "web",
  320. feature = "fullstack",
  321. feature = "static-generation"
  322. )))]
  323. pub type Config = ();
  324. #[cfg(not(any(
  325. feature = "liveview",
  326. feature = "desktop",
  327. feature = "mobile",
  328. feature = "web",
  329. feature = "fullstack",
  330. feature = "static-generation"
  331. )))]
  332. pub fn launch(
  333. root: fn() -> dioxus_core::Element,
  334. contexts: Vec<Box<super::ValidContext>>,
  335. platform_config: (),
  336. ) -> ! {
  337. #[cfg(feature = "third-party-renderer")]
  338. panic!("No first party renderer feature enabled. It looks like you are trying to use a third party renderer. You will need to use the launch function from the third party renderer crate.");
  339. panic!("No platform feature enabled. Please enable one of the following features: liveview, desktop, mobile, web, tui, fullstack to use the launch API.")
  340. }
  341. }
  342. // ! is unstable, so we can't name the type with an alias. Instead we need to generate different variants of items with macros
  343. macro_rules! impl_launch {
  344. ($($return_type:tt),*) => {
  345. /// Launch your application without any additional configuration. See [`LaunchBuilder`] for more options.
  346. // If you aren't using a third party renderer and this is not a docs.rs build, generate a warning about no renderer being enabled
  347. #[cfg_attr(
  348. all(not(any(
  349. docsrs,
  350. feature = "third-party-renderer",
  351. feature = "liveview",
  352. feature = "desktop",
  353. feature = "mobile",
  354. feature = "web",
  355. feature = "fullstack",
  356. feature = "static-generation"
  357. ))),
  358. deprecated(
  359. note = "No renderer is enabled. You must enable a renderer feature on the dioxus crate before calling the launch function.\nAdd `web`, `desktop`, `mobile`, `fullstack`, or `static-generation` to the `features` of dioxus field in your Cargo.toml.\n# Example\n```toml\n# ...\n[dependencies]\ndioxus = { version = \"0.5.0\", features = [\"web\"] }\n# ...\n```"
  360. )
  361. )]
  362. pub fn launch(app: fn() -> Element) -> $($return_type)* {
  363. #[allow(deprecated)]
  364. LaunchBuilder::new().launch(app)
  365. }
  366. };
  367. }
  368. // Static generation is the only platform that may exit. We can't use the `!` type here
  369. #[cfg(any(feature = "static-generation", feature = "web"))]
  370. impl_launch!(());
  371. #[cfg(not(any(feature = "static-generation", feature = "web")))]
  372. impl_launch!(!);
  373. #[cfg(feature = "web")]
  374. #[cfg_attr(docsrs, doc(cfg(feature = "web")))]
  375. /// Launch your web application without any additional configuration. See [`LaunchBuilder`] for more options.
  376. pub fn launch_web(app: fn() -> Element) {
  377. LaunchBuilder::web().launch(app)
  378. }
  379. #[cfg(feature = "desktop")]
  380. #[cfg_attr(docsrs, doc(cfg(feature = "desktop")))]
  381. /// Launch your desktop application without any additional configuration. See [`LaunchBuilder`] for more options.
  382. pub fn launch_desktop(app: fn() -> Element) {
  383. LaunchBuilder::desktop().launch(app)
  384. }
  385. #[cfg(feature = "fullstack")]
  386. #[cfg_attr(docsrs, doc(cfg(feature = "fullstack")))]
  387. /// Launch your fullstack application without any additional configuration. See [`LaunchBuilder`] for more options.
  388. pub fn launch_fullstack(app: fn() -> Element) {
  389. LaunchBuilder::fullstack().launch(app)
  390. }
  391. #[cfg(feature = "mobile")]
  392. #[cfg_attr(docsrs, doc(cfg(feature = "mobile")))]
  393. /// Launch your mobile application without any additional configuration. See [`LaunchBuilder`] for more options.
  394. pub fn launch_mobile(app: fn() -> Element) {
  395. LaunchBuilder::mobile().launch(app)
  396. }