make fullstack hello world compile

This commit is contained in:
Evan Almloff 2024-01-17 16:58:03 -06:00
parent 499e81fa82
commit ae3e167cfe
19 changed files with 288 additions and 155 deletions

2
Cargo.lock generated
View file

@ -787,7 +787,6 @@ name = "axum-hello-world"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"dioxus", "dioxus",
"dioxus-fullstack",
"reqwest", "reqwest",
"serde", "serde",
"simple_logger", "simple_logger",
@ -2363,6 +2362,7 @@ dependencies = [
"env_logger", "env_logger",
"futures-util", "futures-util",
"rand 0.8.5", "rand 0.8.5",
"serde",
"thiserror", "thiserror",
"tokio", "tokio",
"tracing", "tracing",

View file

@ -7,7 +7,7 @@ use proc_macro2::TokenStream as TokenStream2;
use quote::quote; use quote::quote;
#[proc_macro] #[proc_macro]
pub fn server(input: TokenStream) -> TokenStream { pub fn server_only(input: TokenStream) -> TokenStream {
if cfg!(any(feature = "ssr", feature = "liveview")) { if cfg!(any(feature = "ssr", feature = "liveview")) {
let input = TokenStream2::from(input); let input = TokenStream2::from(input);
quote! { quote! {

View file

@ -5,28 +5,16 @@ pub(crate) type BoxedAnyProps = Box<dyn AnyProps>;
/// A trait for a component that can be rendered. /// A trait for a component that can be rendered.
pub trait AnyProps: 'static { pub trait AnyProps: 'static {
/// Render the component with the internal props.
fn render(&self) -> RenderReturn; fn render(&self) -> RenderReturn;
/// Check if the props are the same as the type erased props of another component.
fn memoize(&self, other: &dyn Any) -> bool; fn memoize(&self, other: &dyn Any) -> bool;
/// Get the props as a type erased `dyn Any`.
fn props(&self) -> &dyn Any; fn props(&self) -> &dyn Any;
/// Duplicate this component into a new boxed component.
fn duplicate(&self) -> BoxedAnyProps; fn duplicate(&self) -> BoxedAnyProps;
} }
/// Create a new boxed props object.
pub(crate) fn new_any_props<F: ComponentFunction<P, M>, P: Clone + 'static, M: 'static>(
render_fn: F,
memo: fn(&P, &P) -> bool,
props: P,
name: &'static str,
) -> VProps<F, P, M> {
VProps {
render_fn,
memo,
props,
name,
phantom: std::marker::PhantomData,
}
}
/// A component along with the props the component uses to render. /// A component along with the props the component uses to render.
pub struct VProps<F: ComponentFunction<P, M>, P, M> { pub struct VProps<F: ComponentFunction<P, M>, P, M> {
render_fn: F, render_fn: F,
@ -36,6 +24,41 @@ pub struct VProps<F: ComponentFunction<P, M>, P, M> {
phantom: std::marker::PhantomData<M>, phantom: std::marker::PhantomData<M>,
} }
impl<F: ComponentFunction<P, M>, P: Clone, M> Clone for VProps<F, P, M> {
fn clone(&self) -> Self {
Self {
render_fn: self.render_fn.clone(),
memo: self.memo,
props: self.props.clone(),
name: self.name,
phantom: std::marker::PhantomData,
}
}
}
impl<F: ComponentFunction<P, M> + Clone, P: Clone + 'static, M: 'static> VProps<F, P, M> {
/// Create a [`VProps`] object.
pub fn new(
render_fn: F,
memo: fn(&P, &P) -> bool,
props: P,
name: &'static str,
) -> VProps<F, P, M> {
VProps {
render_fn,
memo,
props,
name,
phantom: std::marker::PhantomData,
}
}
/// Get the current props of the VProps object
pub fn props(&self) -> &P {
&self.props
}
}
impl<F: ComponentFunction<P, M> + Clone, P: Clone + 'static, M: 'static> AnyProps impl<F: ComponentFunction<P, M> + Clone, P: Clone + 'static, M: 'static> AnyProps
for VProps<F, P, M> for VProps<F, P, M>
{ {

View file

@ -1,7 +1,4 @@
use crate::{ use crate::{any_props::BoxedAnyProps, innerlude::ScopeState, VProps};
any_props::{new_any_props, BoxedAnyProps},
innerlude::ScopeState,
};
use crate::{arena::ElementId, Element, Event}; use crate::{arena::ElementId, Element, Event};
use crate::{ use crate::{
innerlude::{ElementRef, EventHandler, MountId}, innerlude::{ElementRef, EventHandler, MountId},
@ -529,7 +526,7 @@ impl VComponent {
P: Properties + 'static, P: Properties + 'static,
{ {
let render_fn = component.id(); let render_fn = component.id();
let props = Box::new(new_any_props( let props = Box::new(VProps::new(
component, component,
<P as Properties>::memoize, <P as Properties>::memoize,
props, props,

View file

@ -1,7 +1,7 @@
use std::any::Any; use std::any::Any;
use crate::{ use crate::{
any_props::{new_any_props, AnyProps, VProps}, any_props::{AnyProps, VProps},
properties::ComponentFunction, properties::ComponentFunction,
VirtualDom, VirtualDom,
}; };
@ -44,38 +44,37 @@ impl<T: Any + Clone> ClonableAny for T {
} }
/// The platform-independent part of the config needed to launch an application. /// The platform-independent part of the config needed to launch an application.
#[derive(Clone)]
pub struct CrossPlatformConfig<P: AnyProps> { pub struct CrossPlatformConfig<P: AnyProps> {
/// The root component function. /// The root component function.
component: P, props: P,
/// The contexts to provide to the root component. // /// The contexts to provide to the root component.
root_contexts: Vec<BoxedContext>, // root_contexts: Vec<BoxedContext>,
} }
impl<F: ComponentFunction<Props, M>, Props: Clone + 'static, M: 'static> impl<P: AnyProps> CrossPlatformConfig<P> {}
CrossPlatformConfig<VProps<F, Props, M>>
{
/// Create a new cross-platform config.
pub fn new(component: F, props: Props, root_contexts: Vec<BoxedContext>) -> Self {
CrossPlatformConfig {
component: new_any_props(component, |_, _| true, props, "root"),
root_contexts,
}
}
}
impl<P: AnyProps> CrossPlatformConfig<P> { impl<P: AnyProps> CrossPlatformConfig<P> {
/// Push a new context into the root component's context. /// Create a new cross-platform config.
pub fn push_context<T: Any + Clone + 'static>(&mut self, context: T) { pub fn new(props: P) -> Self {
self.root_contexts.push(BoxedContext::new(context)); CrossPlatformConfig {
props,
// root_contexts,
} }
}
// /// Push a new context into the root component's context.
// pub fn push_context<T: Any + Clone + 'static>(&mut self, context: T) {
// self.root_contexts.push(BoxedContext::new(context));
// }
/// Build a virtual dom from the config. /// Build a virtual dom from the config.
pub fn build_vdom(self) -> VirtualDom { pub fn build_vdom(self) -> VirtualDom {
let mut vdom = VirtualDom::new_with_component(self.component); let mut vdom = VirtualDom::new_with_component(self.props);
for context in self.root_contexts { // for context in self.root_contexts {
vdom.insert_boxed_root_context(context); // vdom.insert_boxed_root_context(context);
} // }
vdom vdom
} }

View file

@ -3,7 +3,7 @@
//! This module provides the primary mechanics to create a hook-based, concurrent VDOM for Rust. //! This module provides the primary mechanics to create a hook-based, concurrent VDOM for Rust.
use crate::{ use crate::{
any_props::{new_any_props, AnyProps}, any_props::AnyProps,
arena::ElementId, arena::ElementId,
innerlude::{ innerlude::{
DirtyScope, ElementRef, ErrorBoundary, NoOpMutations, SchedulerMsg, ScopeState, VNodeMount, DirtyScope, ElementRef, ErrorBoundary, NoOpMutations, SchedulerMsg, ScopeState, VNodeMount,
@ -13,7 +13,7 @@ use crate::{
nodes::{Template, TemplateId}, nodes::{Template, TemplateId},
runtime::{Runtime, RuntimeGuard}, runtime::{Runtime, RuntimeGuard},
scopes::ScopeId, scopes::ScopeId,
AttributeValue, BoxedContext, ComponentFunction, Element, Event, Mutations, Task, AttributeValue, BoxedContext, ComponentFunction, Element, Event, Mutations, Task, VProps,
}; };
use futures_util::{pin_mut, StreamExt}; use futures_util::{pin_mut, StreamExt};
use rustc_hash::{FxHashMap, FxHashSet}; use rustc_hash::{FxHashMap, FxHashSet};
@ -263,7 +263,7 @@ impl VirtualDom {
root: impl ComponentFunction<P, M>, root: impl ComponentFunction<P, M>,
root_props: P, root_props: P,
) -> Self { ) -> Self {
Self::new_with_component(new_any_props(root, |_, _| true, root_props, "root")) Self::new_with_component(VProps::new(root, |_, _| true, root_props, "root"))
} }
/// Create a new virtualdom and build it immediately /// Create a new virtualdom and build it immediately

View file

@ -25,6 +25,8 @@ dioxus-fullstack = { workspace = true, optional = true }
dioxus-liveview = { workspace = true, optional = true } dioxus-liveview = { workspace = true, optional = true }
# dioxus-tui = { workspace = true, optional = true } # dioxus-tui = { workspace = true, optional = true }
serde = { version = "1.0.136", optional = true }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies] [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
dioxus-hot-reload = { workspace = true, optional = true } dioxus-hot-reload = { workspace = true, optional = true }
@ -40,11 +42,14 @@ launch = ["dioxus-config-macro"]
router = ["dioxus-router"] router = ["dioxus-router"]
# Platforms # Platforms
fullstack = ["dioxus-fullstack", "dioxus-config-macro/fullstack"] fullstack = ["dioxus-fullstack", "dioxus-config-macro/fullstack", "serde"]
desktop = ["dioxus-desktop", "dioxus-fullstack?/desktop", "dioxus-config-macro/desktop"] desktop = ["dioxus-desktop", "dioxus-fullstack?/desktop", "dioxus-config-macro/desktop"]
web = ["dioxus-web", "dioxus-fullstack?/web", "dioxus-config-macro/web"] web = ["dioxus-web", "dioxus-fullstack?/web", "dioxus-config-macro/web"]
ssr = ["dioxus-fullstack?/ssr", "dioxus-config-macro/ssr"] ssr = ["dioxus-fullstack?/ssr", "dioxus-config-macro/ssr"]
liveview = ["dioxus-desktop", "dioxus-config-macro/liveview"] liveview = ["dioxus-desktop", "dioxus-config-macro/liveview"]
axum = ["dioxus-fullstack?/axum"]
salvo = ["dioxus-fullstack?/salvo"]
warp = ["dioxus-fullstack?/warp"]
# tui = ["dioxus-tui", "dioxus-config-macro/tui"] # tui = ["dioxus-tui", "dioxus-config-macro/tui"]
[dev-dependencies] [dev-dependencies]

View file

@ -13,18 +13,26 @@ pub struct LaunchBuilder<P: AnyProps, Platform: PlatformBuilder<P> = CurrentPlat
platform_config: Option<<Platform as PlatformBuilder<P>>::Config>, platform_config: Option<<Platform as PlatformBuilder<P>>::Config>,
} }
// Default platform builder #[cfg(feature = "fullstack")]
impl<F: ComponentFunction<Props, M>, Props: Clone + Default + 'static, M: 'static> // Fullstack platform builder
LaunchBuilder<VProps<F, Props, M>> impl<
F: ComponentFunction<Props, M> + Send + Sync,
Props: Clone + Send + Sync + 'static,
M: Send + Sync + 'static,
> LaunchBuilder<VProps<F, Props, M>>
{ {
/// 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. /// 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.
pub fn new(component: F) -> Self { pub fn new(component: F) -> Self
where
Props: Default,
{
Self { Self {
cross_platform_config: CrossPlatformConfig::new( cross_platform_config: CrossPlatformConfig::new(VProps::new(
component, component,
|_, _| true,
Default::default(), Default::default(),
Default::default(), "root",
), )),
platform_config: None, platform_config: None,
} }
} }
@ -32,19 +40,59 @@ impl<F: ComponentFunction<Props, M>, Props: Clone + Default + 'static, M: 'stati
/// Create a new builder for your application with some root props. This will create a launch configuration for the current platform based on the features enabled on the `dioxus` crate. /// Create a new builder for your application with some root props. This will create a launch configuration for the current platform based on the features enabled on the `dioxus` crate.
pub fn new_with_props(component: F, props: Props) -> Self { pub fn new_with_props(component: F, props: Props) -> Self {
Self { Self {
cross_platform_config: CrossPlatformConfig::new(component, props, Default::default()), cross_platform_config: CrossPlatformConfig::new(VProps::new(
component,
|_, _| true,
props,
"root",
)),
platform_config: None,
}
}
}
#[cfg(not(feature = "fullstack"))]
// Default platform builder
impl<F: ComponentFunction<Props, M>, Props: Clone + 'static, M: 'static>
LaunchBuilder<VProps<F, Props, M>>
{
/// 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.
pub fn new(component: F) -> Self
where
Props: Default,
{
Self {
cross_platform_config: CrossPlatformConfig::new(VProps::new(
component,
|_, _| true,
Default::default(),
"root",
)),
platform_config: None,
}
}
/// Create a new builder for your application with some root props. This will create a launch configuration for the current platform based on the features enabled on the `dioxus` crate.
pub fn new_with_props(component: F, props: Props) -> Self {
Self {
cross_platform_config: CrossPlatformConfig::new(VProps::new(
component,
|_, _| true,
props,
"root",
)),
platform_config: None, platform_config: None,
} }
} }
} }
impl<P: AnyProps, Platform: PlatformBuilder<P>> LaunchBuilder<P, Platform> { impl<P: AnyProps, Platform: PlatformBuilder<P>> LaunchBuilder<P, Platform> {
/// Inject state into the root component's context. // /// Inject state into the root component's context.
pub fn context(mut self, state: impl Any + Clone + 'static) -> Self { // pub fn context(mut self, state: impl Any + Clone + 'static) -> Self {
self.cross_platform_config // self.cross_platform_config
.push_context(BoxedContext::new(state)); // .push_context(BoxedContext::new(state));
self // self
} // }
/// Provide a platform-specific config to the builder. /// Provide a platform-specific config to the builder.
pub fn cfg( pub fn cfg(
@ -89,13 +137,37 @@ impl<P: AnyProps> LaunchBuilder<P, dioxus_desktop::DesktopPlatform> {
} }
} }
#[cfg(feature = "desktop")] #[cfg(feature = "fullstack")]
impl<P: AnyProps + Clone + Send + Sync> LaunchBuilder<P, dioxus_fullstack::FullstackPlatform> {
/// Launch your fullstack application.
pub fn launch_fullstack(self) {
dioxus_fullstack::FullstackPlatform::launch(
self.cross_platform_config,
self.platform_config.unwrap_or_default(),
);
}
}
#[cfg(feature = "fullstack")]
type CurrentPlatform = dioxus_fullstack::FullstackPlatform;
#[cfg(all(feature = "desktop", not(feature = "fullstack")))]
type CurrentPlatform = dioxus_desktop::DesktopPlatform; type CurrentPlatform = dioxus_desktop::DesktopPlatform;
#[cfg(all(feature = "web", not(feature = "desktop")))] #[cfg(all(feature = "web", not(any(feature = "desktop", feature = "fullstack"))))]
type CurrentPlatform = dioxus_web::WebPlatform; type CurrentPlatform = dioxus_web::WebPlatform;
#[cfg(not(any(feature = "desktop", feature = "web")))] #[cfg(not(any(feature = "desktop", feature = "web", feature = "fullstack")))]
type CurrentPlatform = (); type CurrentPlatform = ();
#[cfg(feature = "fullstack")]
/// Launch your application without any additional configuration. See [`LaunchBuilder`] for more options.
pub fn launch<Props, Marker>(component: impl ComponentFunction<Props, Marker> + Send + Sync)
where
Props: Default + Send + Sync + Clone + 'static,
Marker: Send + Sync + 'static,
{
LaunchBuilder::new(component).launch()
}
#[cfg(not(feature = "fullstack"))]
/// Launch your application without any additional configuration. See [`LaunchBuilder`] for more options. /// Launch your application without any additional configuration. See [`LaunchBuilder`] for more options.
pub fn launch<Props, Marker: 'static>(component: impl ComponentFunction<Props, Marker>) pub fn launch<Props, Marker: 'static>(component: impl ComponentFunction<Props, Marker>)
where where
@ -104,7 +176,7 @@ where
LaunchBuilder::new(component).launch() LaunchBuilder::new(component).launch()
} }
#[cfg(feature = "web")] #[cfg(all(feature = "web", not(feature = "fullstack")))]
/// Launch your web application without any additional configuration. See [`LaunchBuilder`] for more options. /// Launch your web application without any additional configuration. See [`LaunchBuilder`] for more options.
pub fn launch_web<Props, Marker: 'static>(component: impl ComponentFunction<Props, Marker>) pub fn launch_web<Props, Marker: 'static>(component: impl ComponentFunction<Props, Marker>)
where where
@ -113,7 +185,7 @@ where
LaunchBuilder::new(component).launch_web() LaunchBuilder::new(component).launch_web()
} }
#[cfg(feature = "desktop")] #[cfg(all(feature = "desktop", not(feature = "fullstack")))]
/// Launch your desktop application without any additional configuration. See [`LaunchBuilder`] for more options. /// Launch your desktop application without any additional configuration. See [`LaunchBuilder`] for more options.
pub fn launch_desktop<Props, Marker: 'static>(component: impl ComponentFunction<Props, Marker>) pub fn launch_desktop<Props, Marker: 'static>(component: impl ComponentFunction<Props, Marker>)
where where
@ -121,3 +193,14 @@ where
{ {
LaunchBuilder::new(component).launch_desktop() LaunchBuilder::new(component).launch_desktop()
} }
#[cfg(feature = "fullstack")]
/// Launch your fullstack application without any additional configuration. See [`LaunchBuilder`] for more options.
pub fn launch_fullstack<Props, Marker>(
component: impl ComponentFunction<Props, Marker> + Send + Sync,
) where
Props: Default + Send + Sync + Clone + 'static,
Marker: Send + Sync + 'static,
{
LaunchBuilder::new(component).launch_fullstack()
}

View file

@ -57,6 +57,9 @@ pub mod prelude {
pub use dioxus_hot_reload::{self, hot_reload_init}; pub use dioxus_hot_reload::{self, hot_reload_init};
pub use dioxus_core; pub use dioxus_core;
#[cfg(feature = "fullstack")]
pub use dioxus_fullstack::prelude::*;
} }
#[cfg(feature = "web")] #[cfg(feature = "web")]

View file

@ -7,8 +7,7 @@ publish = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
dioxus = { workspace = true } dioxus = { workspace = true, features = ["fullstack"]}
dioxus-fullstack = { workspace = true }
serde = "1.0.159" serde = "1.0.159"
simple_logger = "4.2.0" simple_logger = "4.2.0"
tracing-wasm = "0.2.1" tracing-wasm = "0.2.1"
@ -18,5 +17,5 @@ reqwest = "0.11.18"
[features] [features]
default = [] default = []
ssr = ["dioxus-fullstack/axum"] ssr = ["dioxus/axum"]
web = ["dioxus-fullstack/web"] web = ["dioxus/web"]

View file

@ -7,27 +7,17 @@
#![allow(non_snake_case, unused)] #![allow(non_snake_case, unused)]
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_fullstack::{
launch::{self, LaunchBuilder},
prelude::*,
};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Props, PartialEq, Debug, Default, Serialize, Deserialize, Clone)] fn app() -> Element {
struct AppProps { // let state = use_server_future(|| async move { get_server_data().await.unwrap() })?;
count: i32, // let state = state.value();
}
fn app(cx: Scope<AppProps>) -> Element {
let state =
use_server_future((), |()| async move { get_server_data().await.unwrap() })?.value();
let mut count = use_signal(|| 0); let mut count = use_signal(|| 0);
let text = use_signal(|| "...".to_string()); let text = use_signal(|| "...".to_string());
let eval = use_eval(cx);
rsx! { rsx! {
div { "Server state: {state}" } // div { "Server state: {state}" }
h1 { "High-Five counter: {count}" } h1 { "High-Five counter: {count}" }
button { onclick: move |_| count += 1, "Up high!" } button { onclick: move |_| count += 1, "Up high!" }
button { onclick: move |_| count -= 1, "Down low!" } button { onclick: move |_| count -= 1, "Down low!" }
@ -50,9 +40,7 @@ fn app(cx: Scope<AppProps>) -> Element {
#[server] #[server]
async fn post_server_data(data: String) -> Result<(), ServerFnError> { async fn post_server_data(data: String) -> Result<(), ServerFnError> {
let axum::extract::Host(host): axum::extract::Host = extract().await?;
println!("Server received: {}", data); println!("Server received: {}", data);
println!("{:?}", host);
Ok(()) Ok(())
} }
@ -68,5 +56,5 @@ fn main() {
#[cfg(feature = "ssr")] #[cfg(feature = "ssr")]
tracing_subscriber::fmt::init(); tracing_subscriber::fmt::init();
LaunchBuilder::new_with_props(app, AppProps { count: 0 }).launch() launch(app);
} }

View file

@ -63,6 +63,7 @@ use axum::{
routing::{get, post}, routing::{get, post},
Router, Router,
}; };
use dioxus_lib::prelude::dioxus_core::{AnyProps, CrossPlatformConfig};
use server_fn::{Encoding, ServerFunctionRegistry}; use server_fn::{Encoding, ServerFunctionRegistry};
use std::sync::Arc; use std::sync::Arc;
use std::sync::RwLock; use std::sync::RwLock;
@ -215,10 +216,11 @@ pub trait DioxusRouterExt<S> {
/// todo!() /// todo!()
/// } /// }
/// ``` /// ```
fn serve_dioxus_application<P: Clone + serde::Serialize + Send + Sync + 'static>( fn serve_dioxus_application<P: AnyProps + Clone + Send + Sync + 'static>(
self, self,
server_fn_route: &'static str, server_fn_route: &'static str,
cfg: impl Into<ServeConfig<P>>, cfg: impl Into<ServeConfig>,
dioxus_config: CrossPlatformConfig<P>,
) -> Self; ) -> Self;
} }
@ -313,10 +315,11 @@ where
self self
} }
fn serve_dioxus_application<P: Clone + serde::Serialize + Send + Sync + 'static>( fn serve_dioxus_application<P: AnyProps + Clone + Send + Sync + 'static>(
self, self,
server_fn_route: &'static str, server_fn_route: &'static str,
cfg: impl Into<ServeConfig<P>>, cfg: impl Into<ServeConfig>,
dioxus_config: CrossPlatformConfig<P>,
) -> Self { ) -> Self {
let cfg = cfg.into(); let cfg = cfg.into();
let ssr_state = SSRState::new(&cfg); let ssr_state = SSRState::new(&cfg);
@ -325,7 +328,7 @@ where
self.serve_static_assets(cfg.assets_path) self.serve_static_assets(cfg.assets_path)
.connect_hot_reload() .connect_hot_reload()
.register_server_fns(server_fn_route) .register_server_fns(server_fn_route)
.fallback(get(render_handler).with_state((cfg, ssr_state))) .fallback(get(render_handler).with_state((cfg, dioxus_config, ssr_state)))
} }
fn connect_hot_reload(self) -> Self { fn connect_hot_reload(self) -> Self {
@ -416,10 +419,15 @@ fn apply_request_parts_to_response<B>(
/// } /// }
/// ``` /// ```
pub async fn render_handler_with_context< pub async fn render_handler_with_context<
P: Clone + serde::Serialize + Send + Sync + 'static, P: AnyProps + Clone + Send + Sync + 'static,
F: FnMut(&mut DioxusServerContext), F: FnMut(&mut DioxusServerContext),
>( >(
State((mut inject_context, cfg, ssr_state)): State<(F, ServeConfig<P>, SSRState)>, State((mut inject_context, cfg, ssr_state, dioxus_config)): State<(
F,
ServeConfig,
SSRState,
CrossPlatformConfig<P>,
)>,
request: Request<Body>, request: Request<Body>,
) -> impl IntoResponse { ) -> impl IntoResponse {
let (parts, _) = request.into_parts(); let (parts, _) = request.into_parts();
@ -428,7 +436,10 @@ pub async fn render_handler_with_context<
let mut server_context = DioxusServerContext::new(parts.clone()); let mut server_context = DioxusServerContext::new(parts.clone());
inject_context(&mut server_context); inject_context(&mut server_context);
match ssr_state.render(url, &cfg, &server_context).await { match ssr_state
.render(url, &cfg, dioxus_config, &server_context)
.await
{
Ok(rendered) => { Ok(rendered) => {
let crate::render::RenderResponse { html, freshness } = rendered; let crate::render::RenderResponse { html, freshness } = rendered;
let mut response = axum::response::Html::from(html).into_response(); let mut response = axum::response::Html::from(html).into_response();
@ -445,11 +456,15 @@ pub async fn render_handler_with_context<
} }
/// SSR renderer handler for Axum /// SSR renderer handler for Axum
pub async fn render_handler<P: Clone + serde::Serialize + Send + Sync + 'static>( pub async fn render_handler<P: AnyProps + Clone + Send + Sync + 'static>(
State((cfg, ssr_state)): State<(ServeConfig<P>, SSRState)>, State((cfg, dioxus_config, ssr_state)): State<(ServeConfig, CrossPlatformConfig<P>, SSRState)>,
request: Request<Body>, request: Request<Body>,
) -> impl IntoResponse { ) -> impl IntoResponse {
render_handler_with_context(State((|_: &mut _| (), cfg, ssr_state)), request).await render_handler_with_context(
State((|_: &mut _| (), cfg, ssr_state, dioxus_config)),
request,
)
.await
} }
fn report_err<E: std::fmt::Display>(e: E) -> Response<BoxBody> { fn report_err<E: std::fmt::Display>(e: E) -> Response<BoxBody> {

View file

@ -1,17 +1,34 @@
//! Launch helper macros for fullstack apps //! Launch helper macros for fullstack apps
#![allow(unused)] #![allow(unused)]
use crate::prelude::*; use crate::prelude::*;
use dioxus_lib::prelude::{dioxus_core::AnyProps, *}; use dioxus_lib::prelude::{
dioxus_core::{AnyProps, CrossPlatformConfig},
*,
};
/// The desktop renderer platform /// The desktop renderer platform
pub struct FullstackPlatform; pub struct FullstackPlatform;
impl<Props: AnyProps + Send + Sync + 'static> dioxus_core::PlatformBuilder<Props> impl<Props: AnyProps + Clone + Send + Sync + 'static> dioxus_core::PlatformBuilder<Props>
for FullstackPlatform for FullstackPlatform
{ {
type Config = Config; type Config = Config;
fn launch(config: dioxus_core::CrossPlatformConfig<Props>, platform_config: Self::Config) {} fn launch(config: dioxus_core::CrossPlatformConfig<Props>, platform_config: Self::Config) {
#[cfg(feature = "ssr")]
tokio::runtime::Runtime::new()
.unwrap()
.block_on(async move {
platform_config.launch_server(config).await;
});
#[cfg(not(feature = "ssr"))]
{
#[cfg(feature = "web")]
platform_config.launch_web(config);
#[cfg(feature = "desktop")]
platform_config.launch_desktop(config);
}
}
} }
/// Settings for a fullstack app. /// Settings for a fullstack app.
@ -99,31 +116,33 @@ impl Config {
} }
/// Launch the app. /// Launch the app.
pub fn launch(self) { pub fn launch<P: AnyProps + Clone + Send + Sync>(self, dioxus_config: CrossPlatformConfig<P>) {
#[cfg(feature = "ssr")] #[cfg(feature = "ssr")]
tokio::runtime::Runtime::new() tokio::runtime::Runtime::new()
.unwrap() .unwrap()
.block_on(async move { .block_on(async move {
self.launch_server().await; self.launch_server(dioxus_config).await;
}); });
#[cfg(not(feature = "ssr"))] #[cfg(not(feature = "ssr"))]
{ {
#[cfg(feature = "web")] #[cfg(feature = "web")]
self.launch_web(); self.launch_web(dioxus_config);
#[cfg(feature = "desktop")] #[cfg(feature = "desktop")]
self.launch_desktop(); self.launch_desktop(dioxus_config);
} }
} }
#[cfg(feature = "web")] #[cfg(feature = "web")]
/// Launch the web application /// Launch the web application
pub fn launch_web(self) { pub fn launch_web<P: AnyProps>(self, dioxus_config: CrossPlatformConfig<P>) {
use dioxus_lib::prelude::dioxus_core::{CrossPlatformConfig, PlatformBuilder};
#[cfg(not(feature = "ssr"))] #[cfg(not(feature = "ssr"))]
{ {
let cfg = self.web_cfg.hydrate(true); let cfg = self.web_cfg.hydrate(true);
dioxus_web::launch_with_props( dioxus_web::WebPlatform::launch(
self.component, // TODO: this should pull the props from the document
get_root_props_from_document().unwrap(), dioxus_config,
cfg, cfg,
); );
} }
@ -131,14 +150,17 @@ impl Config {
#[cfg(feature = "desktop")] #[cfg(feature = "desktop")]
/// Launch the web application /// Launch the web application
pub fn launch_desktop(self) { pub fn launch_desktop<P: AnyProps>(self, dioxus_config: CrossPlatformConfig<P>) {
let cfg = self.desktop_cfg; let cfg = self.desktop_cfg;
dioxus_desktop::launch_with_props(self.component, self.props, cfg); dioxus_desktop::launch_with_props(self.component, self.props, cfg);
} }
#[cfg(feature = "ssr")] #[cfg(feature = "ssr")]
/// Launch a server application /// Launch a server application
pub async fn launch_server(self) { pub async fn launch_server<P: AnyProps + Send + Sync + Clone>(
self,
dioxus_config: CrossPlatformConfig<P>,
) {
let addr = self.addr; let addr = self.addr;
println!("Listening on {}", addr); println!("Listening on {}", addr);
let cfg = self.server_cfg.build(); let cfg = self.server_cfg.build();
@ -155,7 +177,7 @@ impl Config {
let router = router let router = router
.serve_static_assets(cfg.assets_path) .serve_static_assets(cfg.assets_path)
.connect_hot_reload() .connect_hot_reload()
.fallback(get(render_handler).with_state((cfg, ssr_state))); .fallback(get(render_handler).with_state((cfg, dioxus_config, ssr_state)));
let router = router let router = router
.layer( .layer(
ServiceBuilder::new() ServiceBuilder::new()

View file

@ -14,7 +14,8 @@ pub use adapters::*;
mod hooks; mod hooks;
#[cfg(all(debug_assertions, feature = "hot-reload", feature = "ssr"))] #[cfg(all(debug_assertions, feature = "hot-reload", feature = "ssr"))]
mod hot_reload; mod hot_reload;
pub mod launch; mod launch;
pub use launch::*;
#[cfg(feature = "ssr")] #[cfg(feature = "ssr")]
mod layer; mod layer;
#[cfg(feature = "ssr")] #[cfg(feature = "ssr")]

View file

@ -1,5 +1,6 @@
//! A shared pool of renderers for efficient server side rendering. //! A shared pool of renderers for efficient server side rendering.
use crate::render::dioxus_core::AnyProps;
use crate::render::dioxus_core::CrossPlatformConfig;
use crate::render::dioxus_core::NoOpMutations; use crate::render::dioxus_core::NoOpMutations;
use crate::server_context::SERVER_CONTEXT; use crate::server_context::SERVER_CONTEXT;
use dioxus_lib::prelude::VirtualDom; use dioxus_lib::prelude::VirtualDom;
@ -21,15 +22,15 @@ enum SsrRendererPool {
} }
impl SsrRendererPool { impl SsrRendererPool {
async fn render_to<P: Clone + Serialize + Send + Sync + 'static>( async fn render_to<P: AnyProps + Clone + Send + Sync + 'static>(
&self, &self,
cfg: &ServeConfig<P>, cfg: &ServeConfig,
route: String, route: String,
component: Component<P>, dioxus_config: CrossPlatformConfig<P>,
props: P,
server_context: &DioxusServerContext, server_context: &DioxusServerContext,
) -> Result<(RenderFreshness, String), dioxus_ssr::incremental::IncrementalRendererError> { ) -> Result<(RenderFreshness, String), dioxus_ssr::incremental::IncrementalRendererError> {
let wrapper = FullstackRenderer { let wrapper = FullstackRenderer {
serialized_props: None,
cfg: cfg.clone(), cfg: cfg.clone(),
server_context: server_context.clone(), server_context: server_context.clone(),
}; };
@ -44,7 +45,7 @@ impl SsrRendererPool {
tokio::runtime::Runtime::new() tokio::runtime::Runtime::new()
.expect("couldn't spawn runtime") .expect("couldn't spawn runtime")
.block_on(async move { .block_on(async move {
let mut vdom = VirtualDom::new_with_props(component, props); let mut vdom = dioxus_config.build_vdom();
vdom.in_runtime(|| { vdom.in_runtime(|| {
// Make sure the evaluator is initialized // Make sure the evaluator is initialized
dioxus_ssr::eval::init_eval(); dioxus_ssr::eval::init_eval();
@ -111,8 +112,7 @@ impl SsrRendererPool {
match renderer match renderer
.render( .render(
route, route,
component, dioxus_config,
props,
&mut *to, &mut *to,
|vdom| { |vdom| {
Box::pin(async move { Box::pin(async move {
@ -169,7 +169,7 @@ pub struct SSRState {
impl SSRState { impl SSRState {
/// Create a new [`SSRState`]. /// Create a new [`SSRState`].
pub fn new<P: Clone>(cfg: &ServeConfig<P>) -> Self { pub fn new(cfg: &ServeConfig) -> Self {
if cfg.incremental.is_some() { if cfg.incremental.is_some() {
return Self { return Self {
renderers: Arc::new(SsrRendererPool::Incremental(RwLock::new(vec![ renderers: Arc::new(SsrRendererPool::Incremental(RwLock::new(vec![
@ -192,13 +192,12 @@ impl SSRState {
} }
/// Render the application to HTML. /// Render the application to HTML.
pub fn render<'a, P: 'static + Clone + serde::Serialize + Send + Sync>( pub fn render<'a, P: AnyProps + Clone + Send + Sync>(
&'a self, &'a self,
route: String, route: String,
cfg: &'a ServeConfig<P>, cfg: &'a ServeConfig,
dioxus_config: CrossPlatformConfig<P>,
server_context: &'a DioxusServerContext, server_context: &'a DioxusServerContext,
app: Component<P>,
props: P,
) -> impl std::future::Future< ) -> impl std::future::Future<
Output = Result<RenderResponse, dioxus_ssr::incremental::IncrementalRendererError>, Output = Result<RenderResponse, dioxus_ssr::incremental::IncrementalRendererError>,
> + Send > + Send
@ -208,7 +207,7 @@ impl SSRState {
let (freshness, html) = self let (freshness, html) = self
.renderers .renderers
.render_to(cfg, route, app, props, server_context) .render_to(cfg, route, dioxus_config, server_context)
.await?; .await?;
Ok(RenderResponse { html, freshness }) Ok(RenderResponse { html, freshness })
@ -216,16 +215,13 @@ impl SSRState {
} }
} }
struct FullstackRenderer<P: Clone + Send + Sync + 'static> { struct FullstackRenderer {
component: Component<P>, serialized_props: Option<String>,
props: P,
cfg: ServeConfig, cfg: ServeConfig,
server_context: DioxusServerContext, server_context: DioxusServerContext,
} }
impl<P: Clone + Serialize + Send + Sync + 'static> dioxus_ssr::incremental::WrapBody impl dioxus_ssr::incremental::WrapBody for FullstackRenderer {
for FullstackRenderer<P>
{
fn render_before_body<R: std::io::Write>( fn render_before_body<R: std::io::Write>(
&self, &self,
to: &mut R, to: &mut R,
@ -242,9 +238,10 @@ impl<P: Clone + Serialize + Send + Sync + 'static> dioxus_ssr::incremental::Wrap
to: &mut R, to: &mut R,
) -> Result<(), dioxus_ssr::incremental::IncrementalRendererError> { ) -> Result<(), dioxus_ssr::incremental::IncrementalRendererError> {
// serialize the props // serialize the props
crate::html_storage::serialize::encode_props_in_element(&self.cfg.props, to).map_err( // TODO: restore props serialization
|err| dioxus_ssr::incremental::IncrementalRendererError::Other(Box::new(err)), // crate::html_storage::serialize::encode_props_in_element(&self.cfg.props, to).map_err(
)?; // |err| dioxus_ssr::incremental::IncrementalRendererError::Other(Box::new(err)),
// )?;
// serialize the server state // serialize the server state
crate::html_storage::serialize::encode_in_element( crate::html_storage::serialize::encode_in_element(
&*self.server_context.html_data().map_err(|_| { &*self.server_context.html_data().map_err(|_| {

View file

@ -98,9 +98,9 @@ pub fn server(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
} }
}; };
let server_fn_path: syn::Path = syn::parse_quote!(::dioxus_fullstack::prelude::server_fn); let server_fn_path: syn::Path = syn::parse_quote!(::dioxus::fullstack::prelude::server_fn);
let trait_obj_wrapper: syn::Type = let trait_obj_wrapper: syn::Type =
syn::parse_quote!(::dioxus_fullstack::prelude::ServerFnTraitObj); syn::parse_quote!(::dioxus::fullstack::prelude::ServerFnTraitObj);
let mut args: ServerFnArgs = match syn::parse(args) { let mut args: ServerFnArgs = match syn::parse(args) {
Ok(args) => args, Ok(args) => args,
Err(e) => return e.to_compile_error().into(), Err(e) => return e.to_compile_error().into(),
@ -125,7 +125,7 @@ pub fn server(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
#tokens #tokens
#[cfg(feature = "ssr")] #[cfg(feature = "ssr")]
#server_fn_path::inventory::submit! { #server_fn_path::inventory::submit! {
::dioxus_fullstack::prelude::ServerFnMiddleware { ::dioxus::fullstack::prelude::ServerFnMiddleware {
prefix: #struct_name::PREFIX, prefix: #struct_name::PREFIX,
url: #struct_name::URL, url: #struct_name::URL,
middleware: || vec![ middleware: || vec![

View file

@ -3,7 +3,7 @@
#![allow(non_snake_case)] #![allow(non_snake_case)]
use crate::fs_cache::ValidCachedPath; use crate::fs_cache::ValidCachedPath;
use dioxus_core::{Element, VirtualDom}; use dioxus_core::{AnyProps, CrossPlatformConfig, VirtualDom};
use rustc_hash::FxHasher; use rustc_hash::FxHasher;
use std::{ use std::{
future::Future, future::Future,
@ -69,18 +69,17 @@ impl IncrementalRenderer {
self.invalidate_after.is_some() self.invalidate_after.is_some()
} }
async fn render_and_cache<'a, P: Clone + 'static, R: WrapBody + Send + Sync>( async fn render_and_cache<'a, P: AnyProps + 'static, R: WrapBody + Send + Sync>(
&'a mut self, &'a mut self,
route: String, route: String,
comp: fn(P) -> Element, dioxus_config: CrossPlatformConfig<P>,
props: P,
output: &'a mut (impl AsyncWrite + Unpin + Send), output: &'a mut (impl AsyncWrite + Unpin + Send),
rebuild_with: impl FnOnce(&mut VirtualDom) -> Pin<Box<dyn Future<Output = ()> + '_>>, rebuild_with: impl FnOnce(&mut VirtualDom) -> Pin<Box<dyn Future<Output = ()> + '_>>,
renderer: &'a R, renderer: &'a R,
) -> Result<RenderFreshness, IncrementalRendererError> { ) -> Result<RenderFreshness, IncrementalRendererError> {
let mut html_buffer = WriteBuffer { buffer: Vec::new() }; let mut html_buffer = WriteBuffer { buffer: Vec::new() };
{ {
let mut vdom = VirtualDom::new_with_props(comp, props); let mut vdom = dioxus_config.build_vdom();
vdom.in_runtime(crate::eval::init_eval); vdom.in_runtime(crate::eval::init_eval);
rebuild_with(&mut vdom).await; rebuild_with(&mut vdom).await;
@ -168,11 +167,10 @@ impl IncrementalRenderer {
} }
/// Render a route or get it from cache. /// Render a route or get it from cache.
pub async fn render<P: Clone + 'static, R: WrapBody + Send + Sync>( pub async fn render<P: AnyProps, R: WrapBody + Send + Sync>(
&mut self, &mut self,
route: String, route: String,
component: fn(P) -> Element, dioxus_config: CrossPlatformConfig<P>,
props: P,
output: &mut (impl AsyncWrite + Unpin + std::marker::Send), output: &mut (impl AsyncWrite + Unpin + std::marker::Send),
rebuild_with: impl FnOnce(&mut VirtualDom) -> Pin<Box<dyn Future<Output = ()> + '_>>, rebuild_with: impl FnOnce(&mut VirtualDom) -> Pin<Box<dyn Future<Output = ()> + '_>>,
renderer: &R, renderer: &R,
@ -183,7 +181,7 @@ impl IncrementalRenderer {
} else { } else {
// if not, create it // if not, create it
let freshness = self let freshness = self
.render_and_cache(route, component, props, output, rebuild_with, renderer) .render_and_cache(route, dioxus_config, output, rebuild_with, renderer)
.await?; .await?;
tracing::trace!("cache miss"); tracing::trace!("cache miss");
Ok(freshness) Ok(freshness)

View file

@ -60,7 +60,7 @@ use std::rc::Rc;
pub use crate::cfg::Config; pub use crate::cfg::Config;
#[cfg(feature = "file_engine")] #[cfg(feature = "file_engine")]
pub use crate::file_engine::WebFileEngineExt; pub use crate::file_engine::WebFileEngineExt;
use dioxus_core::CrossPlatformConfig; use dioxus_core::{AnyProps, CrossPlatformConfig};
use futures_util::{ use futures_util::{
future::{select, Either}, future::{select, Either},
pin_mut, FutureExt, StreamExt, pin_mut, FutureExt, StreamExt,
@ -99,7 +99,10 @@ mod rehydrate;
/// wasm_bindgen_futures::spawn_local(app_fut); /// wasm_bindgen_futures::spawn_local(app_fut);
/// } /// }
/// ``` /// ```
pub async fn run_with_props(dioxus_config: CrossPlatformConfig, web_config: Config) { pub async fn run_with_props<P: AnyProps>(
dioxus_config: CrossPlatformConfig<P>,
web_config: Config,
) {
tracing::info!("Starting up"); tracing::info!("Starting up");
let mut dom = dioxus_config.build_vdom(); let mut dom = dioxus_config.build_vdom();
@ -123,7 +126,7 @@ pub async fn run_with_props(dioxus_config: CrossPlatformConfig, web_config: Conf
let (tx, mut rx) = futures_channel::mpsc::unbounded(); let (tx, mut rx) = futures_channel::mpsc::unbounded();
#[cfg(feature = "hydrate")] #[cfg(feature = "hydrate")]
let should_hydrate = cfg.hydrate; let should_hydrate = web_config.hydrate;
#[cfg(not(feature = "hydrate"))] #[cfg(not(feature = "hydrate"))]
let should_hydrate = false; let should_hydrate = false;

View file

@ -5,10 +5,10 @@ use crate::Config;
/// The web renderer platform /// The web renderer platform
pub struct WebPlatform; pub struct WebPlatform;
impl PlatformBuilder for WebPlatform { impl<P: AnyProps> PlatformBuilder<P> for WebPlatform {
type Config = Config; type Config = Config;
fn launch(config: CrossPlatformConfig, platform_config: Self::Config) { fn launch(config: CrossPlatformConfig<P>, platform_config: Self::Config) {
wasm_bindgen_futures::spawn_local(async move { wasm_bindgen_futures::spawn_local(async move {
crate::run_with_props(config, platform_config).await; crate::run_with_props(config, platform_config).await;
}); });