From 9bbf35aa9a1e55dc08064600d21c4a37279001f0 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 2 Jan 2025 09:38:08 -0600 Subject: [PATCH 1/2] Add helper methods to provide a single clone server only context --- packages/fullstack/src/serve_config.rs | 197 +++++++++++++++++++++++-- packages/fullstack/src/server/mod.rs | 6 +- 2 files changed, 185 insertions(+), 18 deletions(-) diff --git a/packages/fullstack/src/serve_config.rs b/packages/fullstack/src/serve_config.rs index 41dac595cb..c8dec4ba5d 100644 --- a/packages/fullstack/src/serve_config.rs +++ b/packages/fullstack/src/serve_config.rs @@ -1,13 +1,16 @@ //! Configuration for how to serve a Dioxus application #![allow(non_snake_case)] +use dioxus_lib::prelude::dioxus_core::LaunchConfig; +use std::any::Any; use std::fs::File; use std::io::Read; use std::path::PathBuf; +use std::sync::Arc; -use dioxus_lib::prelude::dioxus_core::LaunchConfig; - -use crate::server::ContextProviders; +#[allow(unused)] +pub(crate) type ContextProviders = + Arc Box + Send + Sync + 'static>>>; /// A ServeConfig is used to configure how to serve a Dioxus application. It contains information about how to serve static assets, and what content to render with [`dioxus-ssr`]. #[derive(Clone, Default)] @@ -16,7 +19,8 @@ pub struct ServeConfigBuilder { pub(crate) index_html: Option, pub(crate) index_path: Option, pub(crate) incremental: Option, - pub(crate) context_providers: ContextProviders, + pub(crate) context_providers: + Vec Box + Send + Sync + 'static>>, pub(crate) streaming_mode: StreamingMode, } @@ -109,20 +113,178 @@ impl ServeConfigBuilder { self } + /// Provide context to the root and server functions. You can use this context while rendering with [`consume_context`](dioxus_lib::prelude::consume_context) + /// or in server functions with [`FromContext`](crate::prelude::FromContext). + /// + /// + /// The context providers passed into this method will be called when the context type is requested which may happen many times in the lifecycle of the application. + /// + /// + /// Context will be forwarded from the LaunchBuilder if it is provided. + /// + /// ```rust, no_run + /// #![allow(non_snake_case)] + /// use dioxus::prelude::*; + /// use std::sync::Arc; + /// use std::any::Any; + /// + /// fn main() { + /// #[cfg(feature = "web")] + /// // Hydrate the application on the client + /// dioxus::launch(app); + /// #[cfg(feature = "server")] + /// { + /// tokio::runtime::Runtime::new() + /// .unwrap() + /// .block_on(async move { + /// let address = dioxus_cli_config::fullstack_address_or_localhost(); + /// let listener = tokio::net::TcpListener::bind(address) + /// .await + /// .unwrap(); + /// let config = ServeConfigBuilder::default() + /// // You can provide context to your whole app on the server (including server functions) with the `context_provider` method on the launch builder + /// .context_providers(Arc::new(vec![Box::new(|| Box::new(1234u32) as Box) as Box Box + Send + Sync>])); + /// axum::serve( + /// listener, + /// axum::Router::new() + /// .serve_dioxus_application(config, app) + /// .into_make_service(), + /// ) + /// .await + /// .unwrap(); + /// }); + /// } + /// } + /// + /// #[server] + /// async fn read_context() -> Result { + /// // You can extract values from the server context with the `extract` function + /// let FromContext(value) = extract().await?; + /// Ok(value) + /// } + /// + /// fn app() -> Element { + /// let future = use_resource(read_context); + /// rsx! { + /// h1 { "{future:?}" } + /// } + /// } + /// ``` + pub fn context_providers( + mut self, + state: Arc Box + Send + Sync + 'static>>>, + ) -> Self { + // This API should probably accept Vec Box + Send + Sync + 'static>> instead of Arc so we can + // continue adding to the context list after calling this method. Changing the type is a breaking change so it cannot + // be done until 0.7 is released. + let context_providers = (0..state.len()).map(|i| { + let state = state.clone(); + Arc::new(move || state[i]()) as Arc Box + Send + Sync> + }); + self.context_providers.extend(context_providers); + self + } + /// Provide context to the root and server functions. You can use this context /// while rendering with [`consume_context`](dioxus_lib::prelude::consume_context) or in server functions with [`FromContext`](crate::prelude::FromContext). /// + /// + /// The context providers passed into this method will be called when the context type is requested which may happen many times in the lifecycle of the application. + /// + /// /// Context will be forwarded from the LaunchBuilder if it is provided. /// /// ```rust, no_run + /// #![allow(non_snake_case)] /// use dioxus::prelude::*; /// - /// dioxus::LaunchBuilder::new() - /// // You can provide context to your whole app (including server functions) with the `with_context` method on the launch builder - /// .with_context(server_only! { - /// 1234567890u32 - /// }) - /// .launch(app); + /// fn main() { + /// #[cfg(feature = "web")] + /// // Hydrate the application on the client + /// dioxus::launch(app); + /// #[cfg(feature = "server")] + /// { + /// tokio::runtime::Runtime::new() + /// .unwrap() + /// .block_on(async move { + /// let address = dioxus_cli_config::fullstack_address_or_localhost(); + /// let listener = tokio::net::TcpListener::bind(address) + /// .await + /// .unwrap(); + /// let config = ServeConfigBuilder::default() + /// // You can provide context to your whole app on the server (including server functions) with the `context_provider` method on the launch builder + /// .context_provider(move || 1234u32); + /// axum::serve( + /// listener, + /// axum::Router::new() + /// .serve_dioxus_application(config, app) + /// .into_make_service(), + /// ) + /// .await + /// .unwrap(); + /// }); + /// } + /// } + /// + /// #[server] + /// async fn read_context() -> Result { + /// // You can extract values from the server context with the `extract` function + /// let FromContext(value) = extract().await?; + /// Ok(value) + /// } + /// + /// fn app() -> Element { + /// let future = use_resource(read_context); + /// rsx! { + /// h1 { "{future:?}" } + /// } + /// } + /// ``` + pub fn context_provider( + mut self, + state: impl Fn() -> C + Send + Sync + 'static, + ) -> Self { + self.context_providers + .push(Arc::new(move || Box::new(state()))); + self + } + + /// Provide context to the root and server functions. You can use this context while rendering with [`consume_context`](dioxus_lib::prelude::consume_context) + /// or in server functions with [`FromContext`](crate::prelude::FromContext). + /// + /// Context will be forwarded from the LaunchBuilder if it is provided. + /// + /// ```rust, no_run + /// #![allow(non_snake_case)] + /// use dioxus::prelude::*; + /// + /// fn main() { + /// #[cfg(feature = "web")] + /// // Hydrate the application on the client + /// dioxus::launch(app); + /// #[cfg(feature = "server")] + /// { + /// tokio::runtime::Runtime::new() + /// .unwrap() + /// .block_on(async move { + /// let address = dioxus_cli_config::fullstack_address_or_localhost(); + /// let listener = tokio::net::TcpListener::bind(address) + /// .await + /// .unwrap(); + /// let config = ServeConfigBuilder::default() + /// // You can provide context to your whole app on the server (including server functions) with the `context_provider` method on the launch builder + /// .context(1234u32); + /// axum::serve( + /// listener, + /// axum::Router::new() + /// .serve_dioxus_application(config, app) + /// .into_make_service(), + /// ) + /// .await + /// .unwrap(); + /// }); + /// } + /// } /// /// #[server] /// async fn read_context() -> Result { @@ -138,8 +300,9 @@ impl ServeConfigBuilder { /// } /// } /// ``` - pub fn context_providers(mut self, state: ContextProviders) -> Self { - self.context_providers = state; + pub fn context(mut self, state: impl Any + Clone + Send + Sync + 'static) -> Self { + self.context_providers + .push(Arc::new(move || Box::new(state.clone()))); self } @@ -195,11 +358,19 @@ impl ServeConfigBuilder { }; let index = load_index_html(index_html, root_id); + let context_providers = Arc::new( + self.context_providers + .into_iter() + .map(|f| { + Box::new(move || f()) as Box Box + Send + Sync> + }) + .collect(), + ); Ok(ServeConfig { index, incremental: self.incremental, - context_providers: self.context_providers, + context_providers, streaming_mode: self.streaming_mode, }) } diff --git a/packages/fullstack/src/server/mod.rs b/packages/fullstack/src/server/mod.rs index a53647deee..1e5d385099 100644 --- a/packages/fullstack/src/server/mod.rs +++ b/packages/fullstack/src/server/mod.rs @@ -57,10 +57,6 @@ pub mod launch; -#[allow(unused)] -pub(crate) type ContextProviders = - Arc Box + Send + Sync + 'static>>>; - use axum::routing::*; use axum::{ body::{self, Body}, @@ -73,7 +69,7 @@ use http::header::*; use std::sync::Arc; -use crate::prelude::*; +use crate::{prelude::*, ContextProviders}; /// A extension trait with utilities for integrating Dioxus with your Axum router. pub trait DioxusRouterExt { From eb3c0db26b1889da22ec4c20c5ea1e00ed87e5a8 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 29 Jan 2025 15:41:23 -0600 Subject: [PATCH 2/2] fix clippy --- packages/fullstack/src/serve_config.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/fullstack/src/serve_config.rs b/packages/fullstack/src/serve_config.rs index c8dec4ba5d..46642eaca6 100644 --- a/packages/fullstack/src/serve_config.rs +++ b/packages/fullstack/src/serve_config.rs @@ -8,7 +8,6 @@ use std::io::Read; use std::path::PathBuf; use std::sync::Arc; -#[allow(unused)] pub(crate) type ContextProviders = Arc Box + Send + Sync + 'static>>>; @@ -170,10 +169,7 @@ impl ServeConfigBuilder { /// } /// } /// ``` - pub fn context_providers( - mut self, - state: Arc Box + Send + Sync + 'static>>>, - ) -> Self { + pub fn context_providers(mut self, state: ContextProviders) -> Self { // This API should probably accept Vec Box + Send + Sync + 'static>> instead of Arc so we can // continue adding to the context list after calling this method. Changing the type is a breaking change so it cannot // be done until 0.7 is released.