fix the fullstack platform with the new launch API

This commit is contained in:
Evan Almloff committed 2024-01-18 10:19:39 -06:00
1 parent cb5a43c120
commit a323962a22
7 files changed
+281 -255

No files matched your search

+16 -1
View File
@@ -11,7 +11,7 @@ use tao::event::{Event, StartCause, WindowEvent};
///
/// This will block the main thread, and *must* be spawned on the main thread. This function does not assume any runtime
/// and is equivalent to calling launch_with_props with the tokio feature disabled.
pub fn launch_with_props_blocking(virtual_dom: VirtualDom, desktop_config: Config) {
pub fn launch_virtual_dom_blocking(virtual_dom: VirtualDom, desktop_config: Config) {
let (event_loop, mut app) = App::new(desktop_config, virtual_dom);
event_loop.run(move |window_event, _, control_flow| {
@@ -48,6 +48,21 @@ pub fn launch_with_props_blocking(virtual_dom: VirtualDom, desktop_config: Confi
})
}
/// Launches the WebView and runs the event loop, with configuration and root props.
pub fn launch_virtual_dom(virtual_dom: VirtualDom, desktop_config: Config) {
#[cfg(feature = "tokio")]
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap()
.block_on(tokio::task::unconstrained(async move {
launch_virtual_dom_blocking(virtual_dom, desktop_config)
}));
#[cfg(not(feature = "tokio"))]
launch_virtual_dom_blocking(config, desktop_config)
}
/// Launches the WebView and runs the event loop, with configuration and root props.
pub fn launch(
root: fn() -> Element,
+18 -16
View File
@@ -63,7 +63,8 @@ use axum::{
routing::{get, post},
Router,
};
use dioxus_lib::prelude::dioxus_core::{AnyProps, CrossPlatformConfig};
use dioxus_lib::prelude::dioxus_core::AnyProps;
use dioxus_lib::prelude::VirtualDom;
use server_fn::{Encoding, ServerFunctionRegistry};
use std::sync::Arc;
use std::sync::RwLock;
@@ -216,11 +217,11 @@ pub trait DioxusRouterExt<S> {
/// todo!()
/// }
/// ```
fn serve_dioxus_application<P: AnyProps + Clone + Send + Sync + 'static>(
fn serve_dioxus_application(
self,
server_fn_route: &'static str,
cfg: impl Into<ServeConfig>,
dioxus_config: CrossPlatformConfig<P>,
build_virtual_dom: impl Fn() -> VirtualDom + Send + Sync + 'static,
) -> Self;
}
@@ -315,11 +316,11 @@ where
self
}
fn serve_dioxus_application<P: AnyProps + Clone + Send + Sync + 'static>(
fn serve_dioxus_application(
self,
server_fn_route: &'static str,
cfg: impl Into<ServeConfig>,
dioxus_config: CrossPlatformConfig<P>,
build_virtual_dom: impl Fn() -> VirtualDom + Send + Sync + 'static,
) -> Self {
let cfg = cfg.into();
let ssr_state = SSRState::new(&cfg);
@@ -328,7 +329,7 @@ where
self.serve_static_assets(cfg.assets_path)
.connect_hot_reload()
.register_server_fns(server_fn_route)
.fallback(get(render_handler).with_state((cfg, dioxus_config, ssr_state)))
.fallback(get(render_handler).with_state((cfg, Arc::new(build_virtual_dom), ssr_state)))
}
fn connect_hot_reload(self) -> Self {
@@ -418,15 +419,12 @@ fn apply_request_parts_to_response<B>(
/// .unwrap();
/// }
/// ```
pub async fn render_handler_with_context<
P: AnyProps + Clone + Send + Sync + 'static,
F: FnMut(&mut DioxusServerContext),
>(
State((mut inject_context, cfg, ssr_state, dioxus_config)): State<(
pub async fn render_handler_with_context<F: FnMut(&mut DioxusServerContext)>(
State((mut inject_context, cfg, ssr_state, virtual_dom_factory)): State<(
F,
ServeConfig,
SSRState,
CrossPlatformConfig<P>,
Arc<dyn Fn() -> VirtualDom + Send + Sync>,
)>,
request: Request<Body>,
) -> impl IntoResponse {
@@ -437,7 +435,7 @@ pub async fn render_handler_with_context<
inject_context(&mut server_context);
match ssr_state
.render(url, &cfg, dioxus_config, &server_context)
.render(url, &cfg, move || virtual_dom_factory(), &server_context)
.await
{
Ok(rendered) => {
@@ -456,12 +454,16 @@ pub async fn render_handler_with_context<
}
/// SSR renderer handler for Axum
pub async fn render_handler<P: AnyProps + Clone + Send + Sync + 'static>(
State((cfg, dioxus_config, ssr_state)): State<(ServeConfig, CrossPlatformConfig<P>, SSRState)>,
pub async fn render_handler(
State((cfg, virtual_dom_factory, ssr_state)): State<(
ServeConfig,
Arc<dyn Fn() -> VirtualDom + Send + Sync>,
SSRState,
)>,
request: Request<Body>,
) -> impl IntoResponse {
render_handler_with_context(
State((|_: &mut _| (), cfg, ssr_state, dioxus_config)),
State((|_: &mut _| (), cfg, ssr_state, virtual_dom_factory)),
request,
)
.await
+206
View File
@@ -0,0 +1,206 @@
//! Launch helper macros for fullstack apps
#![allow(unused)]
use crate::prelude::*;
use dioxus_lib::prelude::{dioxus_core::AnyProps, *};
use std::sync::Arc;
/// Settings for a fullstack app.
pub struct Config {
#[cfg(feature = "ssr")]
server_fn_route: &'static str,
#[cfg(feature = "ssr")]
server_cfg: ServeConfigBuilder,
#[cfg(feature = "ssr")]
addr: std::net::SocketAddr,
#[cfg(feature = "web")]
web_cfg: dioxus_web::Config,
#[cfg(feature = "desktop")]
desktop_cfg: dioxus_desktop::Config,
}
#[allow(clippy::derivable_impls)]
impl Default for Config {
fn default() -> Self {
Self {
#[cfg(feature = "ssr")]
server_fn_route: "",
#[cfg(feature = "ssr")]
addr: std::net::SocketAddr::from(([127, 0, 0, 1], 8080)),
#[cfg(feature = "ssr")]
server_cfg: ServeConfigBuilder::new(),
#[cfg(feature = "web")]
web_cfg: dioxus_web::Config::default(),
#[cfg(feature = "desktop")]
desktop_cfg: dioxus_desktop::Config::default(),
}
}
}
impl Config {
/// Create a new config for a fullstack app.
pub fn new() -> Self {
Self::default()
}
/// Set the address to serve the app on.
#[cfg(feature = "ssr")]
pub fn addr(self, addr: impl Into<std::net::SocketAddr>) -> Self {
let addr = addr.into();
Self { addr, ..self }
}
/// Set the route to the server functions.
#[cfg(feature = "ssr")]
pub fn server_fn_route(self, server_fn_route: &'static str) -> Self {
Self {
server_fn_route,
..self
}
}
/// Set the incremental renderer config.
#[cfg(feature = "ssr")]
pub fn incremental(self, cfg: IncrementalRendererConfig) -> Self {
Self {
server_cfg: self.server_cfg.incremental(cfg),
..self
}
}
/// Set the server config.
#[cfg(feature = "ssr")]
pub fn server_cfg(self, server_cfg: ServeConfigBuilder) -> Self {
Self { server_cfg, ..self }
}
/// Set the web config.
#[cfg(feature = "web")]
pub fn web_cfg(self, web_cfg: dioxus_web::Config) -> Self {
Self { web_cfg, ..self }
}
/// Set the desktop config.
#[cfg(feature = "desktop")]
pub fn desktop_cfg(self, desktop_cfg: dioxus_desktop::Config) -> Self {
Self {
desktop_cfg,
..self
}
}
/// Launch the app.
pub fn launch(self, build_virtual_dom: impl Fn() -> VirtualDom + Send + Sync + 'static) {
#[cfg(feature = "ssr")]
tokio::runtime::Runtime::new()
.unwrap()
.block_on(async move {
self.launch_server(build_virtual_dom).await;
});
#[cfg(not(feature = "ssr"))]
{
#[cfg(feature = "web")]
self.launch_web(build_virtual_dom);
#[cfg(feature = "desktop")]
self.launch_desktop(build_virtual_dom);
}
}
#[cfg(feature = "web")]
/// Launch the web application
pub fn launch_web(self, build_virtual_dom: impl Fn() -> VirtualDom + Send + Sync + 'static) {
#[cfg(not(feature = "ssr"))]
{
let cfg = self.web_cfg.hydrate(true);
dioxus_web::run(
// TODO: this should pull the props from the document
build_virtual_dom(),
cfg,
);
}
}
#[cfg(feature = "desktop")]
/// Launch the web application
pub fn launch_desktop<P: AnyProps>(self, build_virtual_dom: impl Fn() -> VirtualDom + 'static) {
let cfg = self.desktop_cfg;
dioxus_desktop::launch::launch_with_props_blocking(self.component, self.props, cfg);
}
#[cfg(feature = "ssr")]
/// Launch a server application
pub async fn launch_server(
self,
build_virtual_dom: impl Fn() -> VirtualDom + Send + Sync + 'static,
) {
let addr = self.addr;
println!("Listening on {}", addr);
let cfg = self.server_cfg.build();
let server_fn_route = self.server_fn_route;
#[cfg(all(feature = "axum", not(feature = "warp"), not(feature = "salvo")))]
{
use crate::adapters::axum_adapter::{render_handler, DioxusRouterExt};
use axum::routing::get;
use tower::ServiceBuilder;
let ssr_state = SSRState::new(&cfg);
let router = axum::Router::new().register_server_fns(server_fn_route);
#[cfg(not(feature = "desktop"))]
let router = router
.serve_static_assets(cfg.assets_path)
.connect_hot_reload()
.fallback(get(render_handler).with_state((
cfg,
Arc::new(build_virtual_dom),
ssr_state,
)));
let router = router
.layer(
ServiceBuilder::new()
.layer(tower_http::compression::CompressionLayer::new().gzip(true)),
)
.into_make_service();
axum::Server::bind(&addr).serve(router).await.unwrap();
}
#[cfg(all(feature = "warp", not(feature = "axum"), not(feature = "salvo")))]
{
use warp::Filter;
// First register the server functions
let router = register_server_fns(server_fn_route);
#[cfg(not(feature = "desktop"))]
let router = {
// Serve the dist folder and the index.html file
let serve_dir = warp::fs::dir(cfg.assets_path);
router
.or(connect_hot_reload())
// Then the index route
.or(warp::path::end().and(render_ssr(cfg.clone())))
// Then the static assets
.or(serve_dir)
// Then all other routes
.or(render_ssr(cfg))
};
warp::serve(router.boxed().with(warp::filters::compression::gzip()))
.run(addr)
.await;
}
#[cfg(all(feature = "salvo", not(feature = "axum"), not(feature = "warp")))]
{
use crate::adapters::salvo_adapter::{DioxusRouterExt, SSRHandler};
use salvo::conn::Listener;
let router = salvo::Router::new().register_server_fns(server_fn_route);
#[cfg(not(feature = "desktop"))]
let router = router
.serve_static_assets(cfg.assets_path)
.connect_hot_reload()
.push(salvo::Router::with_path("/<**any_path>").get(SSRHandler::new(cfg)));
let router = router.hoop(
salvo::compression::Compression::new()
.enable_gzip(salvo::prelude::CompressionLevel::Default),
);
salvo::Server::new(salvo::conn::tcp::TcpListener::new(addr).bind().await)
.serve(router)
.await;
}
}
}
+29 -226
View File
@@ -1,232 +1,35 @@
//! Launch helper macros for fullstack apps
#![allow(unused)]
use crate::prelude::*;
use dioxus_lib::prelude::{
dioxus_core::{AnyProps, CrossPlatformConfig},
*,
};
//! This module contains the `launch` function, which is the main entry point for dioxus fullstack
pub mod launch {
use dioxus_lib::prelude::{dioxus_core::BoxedContext, Element, VirtualDom};
use std::any::Any;
pub type Config = crate::Config;
use dioxus_lib::prelude::{Element, VirtualDom};
pub fn launch(
root: fn() -> Element,
contexts: Vec<Box<dyn Fn() -> Box<dyn Any> + Send>>,
platform_config: 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(dom);
#[cfg(feature = "desktop")]
platform_config.launch_desktop(dom);
}
}
}
/// Settings for a fullstack app.
pub struct Config {
#[cfg(feature = "ssr")]
server_fn_route: &'static str,
#[cfg(feature = "ssr")]
server_cfg: ServeConfigBuilder,
#[cfg(feature = "ssr")]
addr: std::net::SocketAddr,
#[cfg(feature = "web")]
web_cfg: dioxus_web::Config,
#[cfg(feature = "desktop")]
desktop_cfg: dioxus_desktop::Config,
}
#[allow(clippy::derivable_impls)]
impl Default for Config {
fn default() -> Self {
Self {
#[cfg(feature = "ssr")]
server_fn_route: "",
#[cfg(feature = "ssr")]
addr: std::net::SocketAddr::from(([127, 0, 0, 1], 8080)),
#[cfg(feature = "ssr")]
server_cfg: ServeConfigBuilder::new(),
#[cfg(feature = "web")]
web_cfg: dioxus_web::Config::default(),
#[cfg(feature = "desktop")]
desktop_cfg: dioxus_desktop::Config::default(),
}
}
}
impl Config {
/// Create a new config for a fullstack app.
pub fn new() -> Self {
Self::default()
}
/// Set the address to serve the app on.
#[cfg(feature = "ssr")]
pub fn addr(self, addr: impl Into<std::net::SocketAddr>) -> Self {
let addr = addr.into();
Self { addr, ..self }
}
/// Set the route to the server functions.
#[cfg(feature = "ssr")]
pub fn server_fn_route(self, server_fn_route: &'static str) -> Self {
Self {
server_fn_route,
..self
}
}
/// Set the incremental renderer config.
#[cfg(feature = "ssr")]
pub fn incremental(self, cfg: IncrementalRendererConfig) -> Self {
Self {
server_cfg: self.server_cfg.incremental(cfg),
..self
}
}
/// Set the server config.
#[cfg(feature = "ssr")]
pub fn server_cfg(self, server_cfg: ServeConfigBuilder) -> Self {
Self { server_cfg, ..self }
}
/// Set the web config.
#[cfg(feature = "web")]
pub fn web_cfg(self, web_cfg: dioxus_web::Config) -> Self {
Self { web_cfg, ..self }
}
/// Set the desktop config.
#[cfg(feature = "desktop")]
pub fn desktop_cfg(self, desktop_cfg: dioxus_desktop::Config) -> Self {
Self {
desktop_cfg,
..self
}
}
/// Launch the app.
pub fn launch<P: AnyProps + Clone + Send + Sync>(self, dioxus_config: CrossPlatformConfig<P>) {
#[cfg(feature = "ssr")]
tokio::runtime::Runtime::new()
.unwrap()
.block_on(async move {
self.launch_server(dioxus_config).await;
});
#[cfg(not(feature = "ssr"))]
{
#[cfg(feature = "web")]
self.launch_web(dioxus_config);
#[cfg(feature = "desktop")]
self.launch_desktop(dioxus_config);
}
}
#[cfg(feature = "web")]
/// Launch the web application
pub fn launch_web<P: AnyProps>(self, dioxus_config: CrossPlatformConfig<P>) {
use dioxus_lib::prelude::dioxus_core::{CrossPlatformConfig, PlatformBuilder};
#[cfg(not(feature = "ssr"))]
{
let cfg = self.web_cfg.hydrate(true);
dioxus_web::WebPlatform::launch(
// TODO: this should pull the props from the document
dioxus_config,
cfg,
);
}
}
#[cfg(feature = "desktop")]
/// Launch the web application
pub fn launch_desktop<P: AnyProps>(self, dioxus_config: CrossPlatformConfig<P>) {
let cfg = self.desktop_cfg;
dioxus_desktop::launch_with_props(self.component, self.props, cfg);
}
#[cfg(feature = "ssr")]
/// Launch a server application
pub async fn launch_server<P: AnyProps + Send + Sync + Clone>(
self,
dioxus_config: CrossPlatformConfig<P>,
) {
let addr = self.addr;
println!("Listening on {}", addr);
let cfg = self.server_cfg.build();
let server_fn_route = self.server_fn_route;
#[cfg(all(feature = "axum", not(feature = "warp"), not(feature = "salvo")))]
{
use crate::adapters::axum_adapter::{render_handler, DioxusRouterExt};
use axum::routing::get;
use tower::ServiceBuilder;
let ssr_state = SSRState::new(&cfg);
let router = axum::Router::new().register_server_fns(server_fn_route);
#[cfg(not(feature = "desktop"))]
let router = router
.serve_static_assets(cfg.assets_path)
.connect_hot_reload()
.fallback(get(render_handler).with_state((cfg, dioxus_config, ssr_state)));
let router = router
.layer(
ServiceBuilder::new()
.layer(tower_http::compression::CompressionLayer::new().gzip(true)),
)
.into_make_service();
axum::Server::bind(&addr).serve(router).await.unwrap();
}
#[cfg(all(feature = "warp", not(feature = "axum"), not(feature = "salvo")))]
{
use warp::Filter;
// First register the server functions
let router = register_server_fns(server_fn_route);
#[cfg(not(feature = "desktop"))]
let router = {
// Serve the dist folder and the index.html file
let serve_dir = warp::fs::dir(cfg.assets_path);
router
.or(connect_hot_reload())
// Then the index route
.or(warp::path::end().and(render_ssr(cfg.clone())))
// Then the static assets
.or(serve_dir)
// Then all other routes
.or(render_ssr(cfg))
};
warp::serve(router.boxed().with(warp::filters::compression::gzip()))
.run(addr)
.await;
}
#[cfg(all(feature = "salvo", not(feature = "axum"), not(feature = "warp")))]
{
use crate::adapters::salvo_adapter::{DioxusRouterExt, SSRHandler};
use salvo::conn::Listener;
let router = salvo::Router::new().register_server_fns(server_fn_route);
#[cfg(not(feature = "desktop"))]
let router = router
.serve_static_assets(cfg.assets_path)
.connect_hot_reload()
.push(salvo::Router::with_path("/<**any_path>").get(SSRHandler::new(cfg)));
let router = router.hoop(
salvo::compression::Compression::new()
.enable_gzip(salvo::prelude::CompressionLevel::Default),
);
salvo::Server::new(salvo::conn::tcp::TcpListener::new(addr).bind().await)
.serve(router)
.await;
pub use crate::Config;
/// Launch a fullstack app with the given root component, contexts, and config.
pub fn launch(
root: fn() -> Element,
contexts: Vec<Box<dyn Fn() -> Box<dyn Any> + Send + Sync>>,
platform_config: Config,
) {
let virtual_dom_factory = move || {
let mut vdom = VirtualDom::new(root);
for context in &contexts {
vdom.insert_any_root_context(context());
}
vdom
};
#[cfg(feature = "ssr")]
tokio::runtime::Runtime::new()
.unwrap()
.block_on(async move {
platform_config.launch_server(virtual_dom_factory).await;
});
#[cfg(not(feature = "ssr"))]
{
#[cfg(feature = "web")]
platform_config.launch_web(virtual_dom_factory);
#[cfg(feature = "desktop")]
platform_config.launch_desktop(virtual_dom_factory);
}
}
+3 -2
View File
@@ -11,11 +11,12 @@ mod html_storage;
mod adapters;
#[cfg(feature = "ssr")]
pub use adapters::*;
mod config;
mod hooks;
#[cfg(all(debug_assertions, feature = "hot-reload", feature = "ssr"))]
mod hot_reload;
mod launch;
pub use launch::*;
pub mod launch;
pub use config::*;
#[cfg(feature = "ssr")]
mod layer;
#[cfg(feature = "ssr")]
+7 -8
View File
@@ -1,6 +1,5 @@
//! 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::server_context::SERVER_CONTEXT;
use dioxus_lib::prelude::VirtualDom;
@@ -22,11 +21,11 @@ enum SsrRendererPool {
}
impl SsrRendererPool {
async fn render_to<P: AnyProps + Clone + Send + Sync + 'static>(
async fn render_to(
&self,
cfg: &ServeConfig,
route: String,
dioxus_config: CrossPlatformConfig<P>,
virtual_dom_factory: impl FnOnce() -> VirtualDom + Send + Sync + 'static,
server_context: &DioxusServerContext,
) -> Result<(RenderFreshness, String), dioxus_ssr::incremental::IncrementalRendererError> {
let wrapper = FullstackRenderer {
@@ -45,7 +44,7 @@ impl SsrRendererPool {
tokio::runtime::Runtime::new()
.expect("couldn't spawn runtime")
.block_on(async move {
let mut vdom = dioxus_config.build_vdom();
let mut vdom = virtual_dom_factory();
vdom.in_runtime(|| {
// Make sure the evaluator is initialized
dioxus_ssr::eval::init_eval();
@@ -112,7 +111,7 @@ impl SsrRendererPool {
match renderer
.render(
route,
dioxus_config,
virtual_dom_factory,
&mut *to,
|vdom| {
Box::pin(async move {
@@ -192,11 +191,11 @@ impl SSRState {
}
/// Render the application to HTML.
pub fn render<'a, P: AnyProps + Clone + Send + Sync>(
pub fn render<'a>(
&'a self,
route: String,
cfg: &'a ServeConfig,
dioxus_config: CrossPlatformConfig<P>,
virtual_dom_factory: impl FnOnce() -> VirtualDom + Send + Sync + 'static,
server_context: &'a DioxusServerContext,
) -> impl std::future::Future<
Output = Result<RenderResponse, dioxus_ssr::incremental::IncrementalRendererError>,
@@ -207,7 +206,7 @@ impl SSRState {
let (freshness, html) = self
.renderers
.render_to(cfg, route, dioxus_config, server_context)
.render_to(cfg, route, virtual_dom_factory, server_context)
.await?;
Ok(RenderResponse { html, freshness })
+2 -2
View File
@@ -170,7 +170,7 @@ impl IncrementalRenderer {
pub async fn render<R: WrapBody + Send + Sync>(
&mut self,
route: String,
virtual_dom: VirtualDom,
virtual_dom_factory: impl FnOnce() -> VirtualDom,
output: &mut (impl AsyncWrite + Unpin + std::marker::Send),
rebuild_with: impl FnOnce(&mut VirtualDom) -> Pin<Box<dyn Future<Output = ()> + '_>>,
renderer: &R,
@@ -181,7 +181,7 @@ impl IncrementalRenderer {
} else {
// if not, create it
let freshness = self
.render_and_cache(route, virtual_dom, output, rebuild_with, renderer)
.render_and_cache(route, virtual_dom_factory(), output, rebuild_with, renderer)
.await?;
tracing::trace!("cache miss");
Ok(freshness)