router/fullstack/ssr intigration

This commit is contained in:
Evan Almloff committed 2023-06-22 16:51:48 -07:00
1 parent 495fd22bfd
commit 1704ee0068
21 files changed
+507 -203

No files matched your search

+12 -10
View File
@@ -63,7 +63,6 @@ use axum::{
routing::{get, post},
Router,
};
use dioxus_core::VirtualDom;
use server_fn::{Encoding, Payload, ServerFunctionRegistry};
use std::error::Error;
use std::sync::Arc;
@@ -311,15 +310,13 @@ where
cfg: impl Into<ServeConfig<P>>,
) -> Self {
let cfg = cfg.into();
let ssr_state = SSRState::new(&cfg);
// Add server functions and render index.html
self.serve_static_assets(cfg.assets_path)
.route(
"/",
get(render_handler).with_state((cfg, SSRState::default())),
)
.connect_hot_reload()
.register_server_fns(server_fn_route)
.fallback(get(render_handler).with_state((cfg, ssr_state)))
}
fn connect_hot_reload(self) -> Self {
@@ -358,13 +355,18 @@ async fn render_handler<P: Clone + serde::Serialize + Send + Sync + 'static>(
) -> impl IntoResponse {
let (parts, _) = request.into_parts();
let parts: Arc<RequestParts> = Arc::new(parts.into());
let url = parts.uri.path_and_query().unwrap().to_string();
let server_context = DioxusServerContext::new(parts);
let mut vdom =
VirtualDom::new_with_props(cfg.app, cfg.props.clone()).with_root_context(server_context);
let _ = vdom.rebuild();
let rendered = ssr_state.render_vdom(&vdom, &cfg);
Full::from(rendered)
match ssr_state.render(url, &cfg, |vdom| {
vdom.base_scope().provide_context(server_context);
}) {
Ok(html) => Full::from(html).into_response(),
Err(e) => {
log::error!("Failed to render page: {}", e);
report_err(e).into_response()
}
}
}
/// A default handler for server functions. It will deserialize the request, call the server function, and serialize the response.
@@ -282,7 +282,7 @@ impl DioxusRouterExt for Router {
self.serve_static_assets(cfg.assets_path)
.connect_hot_reload()
.register_server_fns(server_fn_path)
.push(Router::with_path("/").get(SSRHandler { cfg }))
.push(Router::with_path("/<**any_path>").get(SSRHandler { cfg }))
}
fn connect_hot_reload(self) -> Self {
@@ -169,8 +169,8 @@ pub fn serve_dioxus_application<P: Clone + serde::Serialize + Send + Sync + 'sta
connect_hot_reload()
.or(register_server_fns(server_fn_route))
.or(warp::path::end().and(render_ssr(cfg)))
.or(serve_dir)
.or(render_ssr(cfg))
.boxed()
}
+3 -2
View File
@@ -1,6 +1,6 @@
use std::sync::Arc;
use dioxus_core::Template;
use dioxus::prelude::Template;
use tokio::sync::{
watch::{channel, Receiver},
RwLock,
@@ -9,7 +9,8 @@ use tokio::sync::{
#[derive(Clone)]
pub struct HotReloadState {
// The cache of all templates that have been modified since the last time we checked
pub(crate) templates: Arc<RwLock<std::collections::HashSet<dioxus_core::Template<'static>>>>,
pub(crate) templates:
Arc<RwLock<std::collections::HashSet<dioxus::prelude::Template<'static>>>>,
// The channel to send messages to the hot reload thread
pub(crate) message_receiver: Receiver<Option<Template<'static>>>,
}
Whitespace-only changes.
+4 -2
View File
@@ -10,8 +10,6 @@ mod props_html;
mod adapters;
#[cfg(all(debug_assertions, feature = "hot-reload", feature = "ssr"))]
mod hot_reload;
#[cfg(feature = "router")]
mod incremental;
#[cfg(feature = "ssr")]
mod render;
#[cfg(feature = "ssr")]
@@ -31,6 +29,8 @@ pub mod prelude {
pub use crate::props_html::deserialize_props::get_root_props_from_document;
#[cfg(feature = "ssr")]
pub use crate::render::SSRState;
#[cfg(all(feature = "router", feature = "ssr"))]
pub use crate::serve_config::FullstackRouterConfig;
#[cfg(feature = "ssr")]
pub use crate::serve_config::{ServeConfig, ServeConfigBuilder};
#[cfg(feature = "ssr")]
@@ -40,5 +40,7 @@ pub mod prelude {
#[cfg(feature = "ssr")]
pub use crate::server_fn::{ServerFnTraitObj, ServerFunction};
pub use dioxus_server_macro::*;
#[cfg(feature = "ssr")]
pub use dioxus_ssr::incremental::IncrementalRendererConfig;
pub use server_fn::{self, ServerFn as _, ServerFnError};
}
+78 -28
View File
@@ -2,53 +2,93 @@
use std::sync::Arc;
use dioxus_core::VirtualDom;
use dioxus_ssr::Renderer;
use dioxus::prelude::VirtualDom;
use dioxus_ssr::{incremental::IncrementalRendererConfig, Renderer};
use crate::prelude::ServeConfig;
use crate::prelude::*;
use dioxus::prelude::*;
enum SsrRendererPool {
Renderer(object_pool::Pool<Renderer>),
Incremental(
object_pool::Pool<
dioxus_ssr::incremental::IncrementalRenderer<
crate::serve_config::EmptyIncrementalRenderTemplate,
>,
>,
),
}
impl SsrRendererPool {
fn render_to<P: Clone + 'static>(
&self,
cfg: &ServeConfig<P>,
route: String,
component: Component<P>,
props: P,
to: &mut String,
modify_vdom: impl FnOnce(&mut VirtualDom),
) -> Result<(), dioxus_ssr::incremental::IncrementalRendererError> {
match self {
Self::Renderer(pool) => {
let mut vdom = VirtualDom::new_with_props(component, props);
let _ = vdom.rebuild();
let mut renderer = pool.pull(pre_renderer);
renderer.render_to(to, &vdom)?;
}
Self::Incremental(pool) => {
let mut renderer = pool.pull(|| incremental_pre_renderer(cfg));
renderer.render_to_string(route, component, props, to, modify_vdom)?;
}
}
Ok(())
}
}
/// State used in server side rendering. This utilizes a pool of [`dioxus_ssr::Renderer`]s to cache static templates between renders.
#[derive(Clone)]
pub struct SSRState {
// We keep a pool of renderers to avoid re-creating them on every request. They are boxed to make them very cheap to move
renderers: Arc<object_pool::Pool<Renderer>>,
}
impl Default for SSRState {
fn default() -> Self {
Self {
renderers: Arc::new(object_pool::Pool::new(10, pre_renderer)),
}
}
renderers: Arc<SsrRendererPool>,
}
impl SSRState {
/// Render the application to HTML.
pub fn render<P: 'static + Clone + serde::Serialize>(&self, cfg: &ServeConfig<P>) -> String {
let ServeConfig { app, props, .. } = cfg;
pub(crate) fn new<P: Clone>(cfg: &ServeConfig<P>) -> Self {
if cfg.incremental.is_some() {
return Self {
renderers: Arc::new(SsrRendererPool::Incremental(object_pool::Pool::new(
10,
|| incremental_pre_renderer(cfg),
))),
};
}
let mut vdom = VirtualDom::new_with_props(*app, props.clone());
let _ = vdom.rebuild();
self.render_vdom(&vdom, cfg)
Self {
renderers: Arc::new(SsrRendererPool::Renderer(object_pool::Pool::new(
10,
pre_renderer,
))),
}
}
/// Render a VirtualDom to HTML.
pub fn render_vdom<P: 'static + Clone + serde::Serialize>(
/// Render the application to HTML.
pub fn render<P: 'static + Clone + serde::Serialize>(
&self,
vdom: &VirtualDom,
route: String,
cfg: &ServeConfig<P>,
) -> String {
let ServeConfig { index, .. } = cfg;
modify_vdom: impl FnOnce(&mut VirtualDom),
) -> Result<String, dioxus_ssr::incremental::IncrementalRendererError> {
let ServeConfig { app, props, .. } = cfg;
let mut renderer = self.renderers.pull(pre_renderer);
let ServeConfig { index, .. } = cfg;
let mut html = String::new();
html += &index.pre_main;
let _ = renderer.render_to(&mut html, vdom);
self.renderers
.render_to(cfg, route, *app, props.clone(), &mut html, modify_vdom)?;
// serialize the props
let _ = crate::props_html::serialize_props::encode_in_element(&cfg.props, &mut html);
@@ -89,12 +129,22 @@ impl SSRState {
html += &index.post_main;
html
Ok(html)
}
}
fn pre_renderer() -> Renderer {
let mut renderer = Renderer::default();
renderer.pre_render = true;
renderer.into()
}
fn incremental_pre_renderer<P: Clone>(
cfg: &ServeConfig<P>,
) -> dioxus_ssr::incremental::IncrementalRenderer<crate::serve_config::EmptyIncrementalRenderTemplate>
{
let builder: &IncrementalRendererConfig<_> = &*cfg.incremental.as_ref().unwrap();
let mut renderer = builder.clone().build();
renderer.renderer_mut().pre_render = true;
renderer
}
+180 -1
View File
@@ -1,10 +1,11 @@
#![allow(non_snake_case)]
//! Configeration for how to serve a Dioxus application
use std::fs::File;
use std::io::Read;
use std::path::PathBuf;
use dioxus_core::Component;
use dioxus::prelude::*;
/// 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)]
@@ -14,6 +15,164 @@ pub struct ServeConfigBuilder<P: Clone> {
pub(crate) root_id: Option<&'static str>,
pub(crate) index_path: Option<&'static str>,
pub(crate) assets_path: Option<&'static str>,
#[cfg(feature = "router")]
pub(crate) incremental: Option<
std::sync::Arc<
dioxus_ssr::incremental::IncrementalRendererConfig<EmptyIncrementalRenderTemplate>,
>,
>,
}
#[cfg(feature = "router")]
fn default_external_navigation_handler<R>() -> fn(Scope) -> Element
where
R: dioxus_router::prelude::Routable,
<R as std::str::FromStr>::Err: std::fmt::Display,
{
dioxus_router::prelude::FailureExternalNavigation::<R>
}
#[cfg(feature = "router")]
/// The configeration for the router
#[derive(Props, serde::Serialize, serde::Deserialize)]
pub struct FullstackRouterConfig<R>
where
R: dioxus_router::prelude::Routable,
<R as std::str::FromStr>::Err: std::fmt::Display,
{
#[serde(skip)]
#[serde(default = "default_external_navigation_handler::<R>")]
failure_external_navigation: fn(Scope) -> Element,
scroll_restoration: bool,
#[serde(skip)]
phantom: std::marker::PhantomData<R>,
}
#[cfg(feature = "router")]
impl<R> Clone for FullstackRouterConfig<R>
where
R: dioxus_router::prelude::Routable,
<R as std::str::FromStr>::Err: std::fmt::Display,
{
fn clone(&self) -> Self {
Self {
failure_external_navigation: self.failure_external_navigation,
scroll_restoration: self.scroll_restoration,
phantom: std::marker::PhantomData,
}
}
}
#[cfg(feature = "router")]
impl<R> Copy for FullstackRouterConfig<R>
where
R: dioxus_router::prelude::Routable,
<R as std::str::FromStr>::Err: std::fmt::Display,
{
}
#[cfg(feature = "router")]
impl<R> Default for FullstackRouterConfig<R>
where
R: dioxus_router::prelude::Routable,
<R as std::str::FromStr>::Err: std::fmt::Display,
{
fn default() -> Self {
Self {
failure_external_navigation: dioxus_router::prelude::FailureExternalNavigation::<R>,
scroll_restoration: true,
phantom: std::marker::PhantomData,
}
}
}
#[cfg(feature = "router")]
fn RouteWithCfg<R>(cx: Scope<FullstackRouterConfig<R>>) -> Element
where
R: dioxus_router::prelude::Routable,
<R as std::str::FromStr>::Err: std::fmt::Display,
{
use dioxus_router::prelude::RouterConfig;
#[cfg(feature = "ssr")]
let context: crate::prelude::DioxusServerContext = cx
.consume_context()
.expect("RouteWithCfg should be served by dioxus fullstack");
let cfg = *cx.props;
render! {
dioxus_router::prelude::GenericRouter::<R> {
config: move || {
RouterConfig::default()
.failure_external_navigation(cfg.failure_external_navigation)
.history({
#[cfg(feature = "ssr")]
let history = dioxus_router::prelude::MemoryHistory::with_initial_path(
context
.request_parts()
.uri
.to_string()
.parse()
.unwrap_or_else(|err| {
log::error!("Failed to parse uri: {}", err);
"/"
.parse()
.unwrap_or_else(|err| {
panic!("Failed to parse uri: {}", err);
})
}),
);
#[cfg(not(feature = "ssr"))]
let history = dioxus_router::prelude::WebHistory::new(
None,
cfg.scroll_restoration,
);
history
})
},
}
}
}
#[cfg(feature = "router")]
/// A template for incremental rendering that does nothing.
#[derive(Default, Clone)]
pub struct EmptyIncrementalRenderTemplate;
#[cfg(feature = "router")]
impl dioxus_ssr::incremental::RenderHTML for EmptyIncrementalRenderTemplate {
fn render_after_body<R: std::io::Write>(
&self,
_: &mut R,
) -> Result<(), dioxus_ssr::incremental::IncrementalRendererError> {
Ok(())
}
fn render_before_body<R: std::io::Write>(
&self,
_: &mut R,
) -> Result<(), dioxus_ssr::incremental::IncrementalRendererError> {
Ok(())
}
}
#[cfg(feature = "router")]
impl<R> ServeConfigBuilder<FullstackRouterConfig<R>>
where
R: dioxus_router::prelude::Routable,
<R as std::str::FromStr>::Err: std::fmt::Display,
{
/// Create a new ServeConfigBuilder to serve a router on the server.
pub fn new_with_router(cfg: FullstackRouterConfig<R>) -> Self {
Self {
app: RouteWithCfg::<R>,
props: cfg,
root_id: None,
index_path: None,
assets_path: None,
incremental: None,
}
}
}
impl<P: Clone> ServeConfigBuilder<P> {
@@ -25,9 +184,21 @@ impl<P: Clone> ServeConfigBuilder<P> {
root_id: None,
index_path: None,
assets_path: None,
#[cfg(feature = "router")]
incremental: None,
}
}
#[cfg(feature = "router")]
/// Enable incremental static generation
pub fn incremental(
mut self,
cfg: dioxus_ssr::incremental::IncrementalRendererConfig<EmptyIncrementalRenderTemplate>,
) -> Self {
self.incremental = Some(std::sync::Arc::new(cfg));
self
}
/// Set the path of the index.html file to be served. (defaults to {assets_path}/index.html)
pub fn index_path(mut self, index_path: &'static str) -> Self {
self.index_path = Some(index_path);
@@ -64,6 +235,8 @@ impl<P: Clone> ServeConfigBuilder<P> {
props: self.props,
index,
assets_path,
#[cfg(feature = "router")]
incremental: self.incremental,
}
}
}
@@ -106,6 +279,12 @@ pub struct ServeConfig<P: Clone> {
pub(crate) props: P,
pub(crate) index: IndexHtml,
pub(crate) assets_path: &'static str,
#[cfg(feature = "router")]
pub(crate) incremental: Option<
std::sync::Arc<
dioxus_ssr::incremental::IncrementalRendererConfig<EmptyIncrementalRenderTemplate>,
>,
>,
}
impl<P: Clone> From<ServeConfigBuilder<P>> for ServeConfig<P> {
+1 -1
View File
@@ -1,4 +1,4 @@
use dioxus_core::ScopeState;
use dioxus::prelude::ScopeState;
/// A trait for an object that contains a server context
pub trait HasServerContext {