launch.rs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  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 + '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 fullstack application.
  85. #[cfg(feature = "mobile")]
  86. #[cfg_attr(docsrs, doc(cfg(feature = "mobile")))]
  87. pub fn mobile() -> LaunchBuilder<dioxus_mobile::Config, UnsendContext> {
  88. LaunchBuilder {
  89. launch_fn: |root, contexts, cfg| dioxus_mobile::launch::launch(root, contexts, cfg),
  90. contexts: Vec::new(),
  91. platform_config: None,
  92. }
  93. }
  94. /// Provide a custom launch function for your application.
  95. ///
  96. /// Useful for third party renderers to tap into the launch builder API without having to reimplement it.
  97. pub fn custom<Cfg, List>(launch_fn: LaunchFn<Cfg, List>) -> LaunchBuilder<Cfg, List> {
  98. LaunchBuilder {
  99. launch_fn,
  100. contexts: vec![],
  101. platform_config: None,
  102. }
  103. }
  104. }
  105. // Fullstack platform builder
  106. impl<Cfg> LaunchBuilder<Cfg, UnsendContext> {
  107. /// Inject state into the root component's context that is created on the thread that the app is launched on.
  108. pub fn with_context_provider(mut self, state: impl Fn() -> Box<dyn Any> + 'static) -> Self {
  109. self.contexts.push(Box::new(state) as Box<UnsendContext>);
  110. self
  111. }
  112. /// Inject state into the root component's context.
  113. pub fn with_context(mut self, state: impl Any + Clone + 'static) -> Self {
  114. self.contexts
  115. .push(Box::new(move || Box::new(state.clone())));
  116. self
  117. }
  118. }
  119. impl<Cfg> LaunchBuilder<Cfg, SendContext> {
  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(
  122. mut self,
  123. state: impl Fn() -> Box<dyn Any> + Send + Sync + 'static,
  124. ) -> Self {
  125. self.contexts.push(Box::new(state) as Box<SendContext>);
  126. self
  127. }
  128. /// Inject state into the root component's context.
  129. pub fn with_context(mut self, state: impl Any + Clone + Send + Sync + 'static) -> Self {
  130. self.contexts
  131. .push(Box::new(move || Box::new(state.clone())));
  132. self
  133. }
  134. }
  135. /// A trait for converting a type into a platform-specific config:
  136. /// - A unit value will be converted into `None`
  137. /// - Any config will be converted into `Some(config)`
  138. /// - If the config is for another platform, it will be converted into `None`
  139. pub trait TryIntoConfig<Config = Self> {
  140. fn into_config(self) -> Option<Config>;
  141. }
  142. // A config can always be converted into itself
  143. impl<Cfg> TryIntoConfig<Cfg> for Cfg {
  144. fn into_config(self) -> Option<Cfg> {
  145. Some(self)
  146. }
  147. }
  148. // The unit type can be converted into the current platform config.
  149. // This makes it possible to use the `desktop!`, `web!`, etc macros with the launch API.
  150. #[cfg(any(
  151. feature = "liveview",
  152. feature = "desktop",
  153. feature = "mobile",
  154. feature = "web",
  155. feature = "fullstack"
  156. ))]
  157. impl TryIntoConfig<current_platform::Config> for () {
  158. fn into_config(self) -> Option<current_platform::Config> {
  159. None
  160. }
  161. }
  162. impl<Cfg: Default + 'static, ContextFn: ?Sized> LaunchBuilder<Cfg, ContextFn> {
  163. /// Provide a platform-specific config to the builder.
  164. pub fn with_cfg(mut self, config: impl TryIntoConfig<Cfg>) -> Self {
  165. self.platform_config = self.platform_config.or(config.into_config());
  166. self
  167. }
  168. // Static generation is the only platform that may exit. We can't use the `!` type here
  169. #[cfg(any(feature = "static-generation", feature = "web"))]
  170. /// Launch your application.
  171. pub fn launch(self, app: fn() -> Element) {
  172. let cfg = self.platform_config.unwrap_or_default();
  173. (self.launch_fn)(app, self.contexts, cfg)
  174. }
  175. #[cfg(not(any(feature = "static-generation", feature = "web")))]
  176. /// Launch your application.
  177. pub fn launch(self, app: fn() -> Element) -> ! {
  178. let cfg = self.platform_config.unwrap_or_default();
  179. (self.launch_fn)(app, self.contexts, cfg);
  180. unreachable!("Launching an application will never exit")
  181. }
  182. }
  183. /// Re-export the platform we expect the user wants
  184. ///
  185. /// If multiple platforms are enabled, we use this priority (from highest to lowest):
  186. /// - `fullstack`
  187. /// - `desktop`
  188. /// - `mobile`
  189. /// - `static-generation`
  190. /// - `web`
  191. /// - `liveview`
  192. mod current_platform {
  193. macro_rules! if_else_cfg {
  194. (if $attr:meta { $($then:item)* } else { $($else:item)* }) => {
  195. $(
  196. #[cfg($attr)]
  197. $then
  198. )*
  199. $(
  200. #[cfg(not($attr))]
  201. $else
  202. )*
  203. };
  204. }
  205. use crate::prelude::TryIntoConfig;
  206. #[cfg(feature = "fullstack")]
  207. pub use dioxus_fullstack::launch::*;
  208. #[cfg(any(feature = "desktop", feature = "mobile"))]
  209. if_else_cfg! {
  210. if not(feature = "fullstack") {
  211. #[cfg(feature = "desktop")]
  212. pub use dioxus_desktop::launch::*;
  213. #[cfg(not(feature = "desktop"))]
  214. pub use dioxus_mobile::launch::*;
  215. } else {
  216. impl TryIntoConfig<crate::launch::current_platform::Config> for ::dioxus_desktop::Config {
  217. fn into_config(self) -> Option<crate::launch::current_platform::Config> {
  218. None
  219. }
  220. }
  221. }
  222. }
  223. #[cfg(feature = "static-generation")]
  224. if_else_cfg! {
  225. if all(not(feature = "fullstack"), not(feature = "desktop"), not(feature = "mobile")) {
  226. pub use dioxus_static_site_generation::launch::*;
  227. } else {
  228. impl TryIntoConfig<crate::launch::current_platform::Config> for ::dioxus_static_site_generation::Config {
  229. fn into_config(self) -> Option<crate::launch::current_platform::Config> {
  230. None
  231. }
  232. }
  233. }
  234. }
  235. #[cfg(feature = "web")]
  236. if_else_cfg! {
  237. if not(any(feature = "desktop", feature = "mobile", feature = "fullstack", feature = "static-generation")) {
  238. pub use dioxus_web::launch::*;
  239. } else {
  240. impl TryIntoConfig<crate::launch::current_platform::Config> for ::dioxus_web::Config {
  241. fn into_config(self) -> Option<crate::launch::current_platform::Config> {
  242. None
  243. }
  244. }
  245. }
  246. }
  247. #[cfg(feature = "liveview")]
  248. if_else_cfg! {
  249. if
  250. not(any(
  251. feature = "web",
  252. feature = "desktop",
  253. feature = "mobile",
  254. feature = "fullstack",
  255. feature = "static-generation"
  256. ))
  257. {
  258. pub use dioxus_liveview::launch::*;
  259. } else {
  260. impl<R: ::dioxus_liveview::LiveviewRouter> TryIntoConfig<crate::launch::current_platform::Config> for ::dioxus_liveview::Config<R> {
  261. fn into_config(self) -> Option<crate::launch::current_platform::Config> {
  262. None
  263. }
  264. }
  265. }
  266. }
  267. #[cfg(not(any(
  268. feature = "liveview",
  269. feature = "desktop",
  270. feature = "mobile",
  271. feature = "web",
  272. feature = "fullstack",
  273. feature = "static-generation"
  274. )))]
  275. pub type Config = ();
  276. #[cfg(not(any(
  277. feature = "liveview",
  278. feature = "desktop",
  279. feature = "mobile",
  280. feature = "web",
  281. feature = "fullstack",
  282. feature = "static-generation"
  283. )))]
  284. pub fn launch(
  285. root: fn() -> dioxus_core::Element,
  286. contexts: Vec<Box<super::ValidContext>>,
  287. platform_config: (),
  288. ) -> ! {
  289. #[cfg(feature = "third-party-renderer")]
  290. 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.");
  291. panic!("No platform feature enabled. Please enable one of the following features: liveview, desktop, mobile, web, tui, fullstack to use the launch API.")
  292. }
  293. }
  294. // ! is unstable, so we can't name the type with an alias. Instead we need to generate different variants of items with macros
  295. macro_rules! impl_launch {
  296. ($($return_type:tt),*) => {
  297. /// Launch your application without any additional configuration. See [`LaunchBuilder`] for more options.
  298. // 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
  299. #[cfg_attr(
  300. all(not(any(
  301. docsrs,
  302. feature = "third-party-renderer",
  303. feature = "liveview",
  304. feature = "desktop",
  305. feature = "mobile",
  306. feature = "web",
  307. feature = "fullstack",
  308. feature = "static-generation"
  309. ))),
  310. deprecated(
  311. 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```"
  312. )
  313. )]
  314. pub fn launch(app: fn() -> Element) -> $($return_type)* {
  315. #[allow(deprecated)]
  316. LaunchBuilder::new().launch(app)
  317. }
  318. };
  319. }
  320. // Static generation is the only platform that may exit. We can't use the `!` type here
  321. #[cfg(any(feature = "static-generation", feature = "web"))]
  322. impl_launch!(());
  323. #[cfg(not(any(feature = "static-generation", feature = "web")))]
  324. impl_launch!(!);
  325. #[cfg(feature = "web")]
  326. #[cfg_attr(docsrs, doc(cfg(feature = "web")))]
  327. /// Launch your web application without any additional configuration. See [`LaunchBuilder`] for more options.
  328. pub fn launch_web(app: fn() -> Element) {
  329. LaunchBuilder::web().launch(app)
  330. }
  331. #[cfg(feature = "desktop")]
  332. #[cfg_attr(docsrs, doc(cfg(feature = "desktop")))]
  333. /// Launch your desktop application without any additional configuration. See [`LaunchBuilder`] for more options.
  334. pub fn launch_desktop(app: fn() -> Element) {
  335. LaunchBuilder::desktop().launch(app)
  336. }
  337. #[cfg(feature = "fullstack")]
  338. #[cfg_attr(docsrs, doc(cfg(feature = "fullstack")))]
  339. /// Launch your fullstack application without any additional configuration. See [`LaunchBuilder`] for more options.
  340. pub fn launch_fullstack(app: fn() -> Element) {
  341. LaunchBuilder::fullstack().launch(app)
  342. }
  343. #[cfg(feature = "mobile")]
  344. #[cfg_attr(docsrs, doc(cfg(feature = "mobile")))]
  345. /// Launch your mobile application without any additional configuration. See [`LaunchBuilder`] for more options.
  346. pub fn launch_mobile(app: fn() -> Element) {
  347. LaunchBuilder::mobile().launch(app)
  348. }