router/fullstack/ssr intigration
This commit is contained in:
1 parent
495fd22bfd
commit
1704ee0068
21 files changed
+507
-203
No files matched your search
@@ -30,13 +30,13 @@ salvo = { version = "0.37.7", optional = true, features = ["serve-static", "ws"]
|
|||||||
serde = "1.0.159"
|
serde = "1.0.159"
|
||||||
|
|
||||||
# Dioxus + SSR
|
# Dioxus + SSR
|
||||||
dioxus-core = { path = "../core", version = "^0.3.0" }
|
dioxus = { path = "../dioxus", version = "^0.3.0" }
|
||||||
dioxus-ssr = { path = "../ssr", version = "^0.3.0", optional = true }
|
dioxus-ssr = { path = "../ssr", version = "^0.3.0", optional = true }
|
||||||
hyper = { version = "0.14.25", optional = true }
|
hyper = { version = "0.14.25", optional = true }
|
||||||
http = { version = "0.2.9", optional = true }
|
http = { version = "0.2.9", optional = true }
|
||||||
|
|
||||||
# Router Intigration
|
# Router Intigration
|
||||||
dioxus-router = { path = "../router", version = "^0.3.0", optional = true }
|
dioxus-router = { path = "../router", version = "^0.3.0", features = ["ssr"], optional = true }
|
||||||
|
|
||||||
log = "0.4.17"
|
log = "0.4.17"
|
||||||
once_cell = "1.17.1"
|
once_cell = "1.17.1"
|
||||||
@@ -59,7 +59,7 @@ dioxus-hot-reload = { path = "../hot-reload" }
|
|||||||
web-sys = { version = "0.3.61", features = ["Window", "Document", "Element", "HtmlDocument", "Storage", "console"] }
|
web-sys = { version = "0.3.61", features = ["Window", "Document", "Element", "HtmlDocument", "Storage", "console"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["hot-reload", "default-tls"]
|
default = ["hot-reload", "default-tls", "router"]
|
||||||
router = ["dioxus-router"]
|
router = ["dioxus-router"]
|
||||||
hot-reload = ["serde_json", "tokio-stream", "futures-util"]
|
hot-reload = ["serde_json", "tokio-stream", "futures-util"]
|
||||||
warp = ["dep:warp", "http-body", "ssr"]
|
warp = ["dep:warp", "http-body", "ssr"]
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ use serde::{Deserialize, Serialize};
|
|||||||
fn main() {
|
fn main() {
|
||||||
#[cfg(feature = "web")]
|
#[cfg(feature = "web")]
|
||||||
dioxus_web::launch_with_props(
|
dioxus_web::launch_with_props(
|
||||||
App,
|
Router,
|
||||||
AppProps { route: None },
|
Default::default(),
|
||||||
dioxus_web::Config::new().hydrate(true),
|
dioxus_web::Config::new().hydrate(true),
|
||||||
);
|
);
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
@@ -33,11 +33,6 @@ fn main() {
|
|||||||
true
|
true
|
||||||
}));
|
}));
|
||||||
|
|
||||||
use axum::extract::State;
|
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
PostServerData::register().unwrap();
|
|
||||||
GetServerData::register().unwrap();
|
|
||||||
tokio::runtime::Runtime::new()
|
tokio::runtime::Runtime::new()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.block_on(async move {
|
.block_on(async move {
|
||||||
@@ -46,28 +41,12 @@ fn main() {
|
|||||||
axum::Server::bind(&addr)
|
axum::Server::bind(&addr)
|
||||||
.serve(
|
.serve(
|
||||||
axum::Router::new()
|
axum::Router::new()
|
||||||
// Serve the dist/assets folder with the javascript and WASM files created by the CLI
|
.serve_dioxus_application(
|
||||||
.serve_static_assets("./dist")
|
"",
|
||||||
// Register server functions
|
ServeConfigBuilder::new_with_router(
|
||||||
.register_server_fns("")
|
dioxus_fullstack::prelude::FullstackRouterConfig::<Route>::default()).incremental(IncrementalRendererConfig::default())
|
||||||
// Connect to the hot reload server
|
.build(),
|
||||||
.connect_hot_reload()
|
|
||||||
// If the path is unknown, render the application
|
|
||||||
.fallback(
|
|
||||||
move |uri: http::uri::Uri, State(ssr_state): State<SSRState>| {
|
|
||||||
let rendered = ssr_state.render(
|
|
||||||
&ServeConfigBuilder::new(
|
|
||||||
App,
|
|
||||||
AppProps {
|
|
||||||
route: Route::from_str(&uri.to_string()).ok(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.build(),
|
|
||||||
);
|
|
||||||
async move { axum::body::Full::from(rendered) }
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
.with_state(SSRState::default())
|
|
||||||
.into_make_service(),
|
.into_make_service(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -87,10 +66,7 @@ enum Route {
|
|||||||
#[inline_props]
|
#[inline_props]
|
||||||
fn Blog(cx: Scope) -> Element {
|
fn Blog(cx: Scope) -> Element {
|
||||||
render! {
|
render! {
|
||||||
Link {
|
Link { target: Route::Home {}, "Go to counter" }
|
||||||
target: Route::Home {},
|
|
||||||
"Go to counter"
|
|
||||||
}
|
|
||||||
table {
|
table {
|
||||||
tbody {
|
tbody {
|
||||||
for _ in 0..100 {
|
for _ in 0..100 {
|
||||||
@@ -105,38 +81,14 @@ fn Blog(cx: Scope) -> Element {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Props, PartialEq, Serialize, Deserialize)]
|
|
||||||
struct AppProps {
|
|
||||||
route: Option<Route>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn App(cx: Scope<AppProps>) -> Element {
|
|
||||||
#[cfg(feature = "ssr")]
|
|
||||||
let initial_route = cx.props.route.clone().unwrap_or(Route::Home {});
|
|
||||||
cx.render(rsx! {
|
|
||||||
Router {
|
|
||||||
config: move || RouterConfig::default().history({
|
|
||||||
#[cfg(feature = "ssr")]
|
|
||||||
let history = MemoryHistory::with_initial_path(initial_route);
|
|
||||||
#[cfg(feature = "web")]
|
|
||||||
let history = WebHistory::default();
|
|
||||||
history
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline_props]
|
#[inline_props]
|
||||||
fn Home(cx: Scope) -> Element {
|
fn Home(cx: Scope) -> Element {
|
||||||
let mut count = use_state(cx, || 0);
|
let mut count = use_state(cx, || 0);
|
||||||
let text = use_state(cx, || "...".to_string());
|
let text = use_state(cx, || "...".to_string());
|
||||||
|
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
Link {
|
Link { target: Route::Blog {}, "Go to blog" }
|
||||||
target: Route::Blog {}
|
div {
|
||||||
"Go to blog"
|
|
||||||
}
|
|
||||||
div{
|
|
||||||
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!" }
|
||||||
|
|||||||
@@ -63,7 +63,6 @@ use axum::{
|
|||||||
routing::{get, post},
|
routing::{get, post},
|
||||||
Router,
|
Router,
|
||||||
};
|
};
|
||||||
use dioxus_core::VirtualDom;
|
|
||||||
use server_fn::{Encoding, Payload, ServerFunctionRegistry};
|
use server_fn::{Encoding, Payload, ServerFunctionRegistry};
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -311,15 +310,13 @@ where
|
|||||||
cfg: impl Into<ServeConfig<P>>,
|
cfg: impl Into<ServeConfig<P>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let cfg = cfg.into();
|
let cfg = cfg.into();
|
||||||
|
let ssr_state = SSRState::new(&cfg);
|
||||||
|
|
||||||
// Add server functions and render index.html
|
// Add server functions and render index.html
|
||||||
self.serve_static_assets(cfg.assets_path)
|
self.serve_static_assets(cfg.assets_path)
|
||||||
.route(
|
|
||||||
"/",
|
|
||||||
get(render_handler).with_state((cfg, SSRState::default())),
|
|
||||||
)
|
|
||||||
.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)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn connect_hot_reload(self) -> Self {
|
fn connect_hot_reload(self) -> Self {
|
||||||
@@ -358,13 +355,18 @@ async fn render_handler<P: Clone + serde::Serialize + Send + Sync + 'static>(
|
|||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
let (parts, _) = request.into_parts();
|
let (parts, _) = request.into_parts();
|
||||||
let parts: Arc<RequestParts> = Arc::new(parts.into());
|
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 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);
|
match ssr_state.render(url, &cfg, |vdom| {
|
||||||
Full::from(rendered)
|
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.
|
/// 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)
|
self.serve_static_assets(cfg.assets_path)
|
||||||
.connect_hot_reload()
|
.connect_hot_reload()
|
||||||
.register_server_fns(server_fn_path)
|
.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 {
|
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()
|
connect_hot_reload()
|
||||||
.or(register_server_fns(server_fn_route))
|
.or(register_server_fns(server_fn_route))
|
||||||
.or(warp::path::end().and(render_ssr(cfg)))
|
|
||||||
.or(serve_dir)
|
.or(serve_dir)
|
||||||
|
.or(render_ssr(cfg))
|
||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use dioxus_core::Template;
|
use dioxus::prelude::Template;
|
||||||
use tokio::sync::{
|
use tokio::sync::{
|
||||||
watch::{channel, Receiver},
|
watch::{channel, Receiver},
|
||||||
RwLock,
|
RwLock,
|
||||||
@@ -9,7 +9,8 @@ use tokio::sync::{
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct HotReloadState {
|
pub struct HotReloadState {
|
||||||
// The cache of all templates that have been modified since the last time we checked
|
// 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
|
// The channel to send messages to the hot reload thread
|
||||||
pub(crate) message_receiver: Receiver<Option<Template<'static>>>,
|
pub(crate) message_receiver: Receiver<Option<Template<'static>>>,
|
||||||
}
|
}
|
||||||
|
|||||||
Whitespace-only changes.
@@ -10,8 +10,6 @@ mod props_html;
|
|||||||
mod adapters;
|
mod adapters;
|
||||||
#[cfg(all(debug_assertions, feature = "hot-reload", feature = "ssr"))]
|
#[cfg(all(debug_assertions, feature = "hot-reload", feature = "ssr"))]
|
||||||
mod hot_reload;
|
mod hot_reload;
|
||||||
#[cfg(feature = "router")]
|
|
||||||
mod incremental;
|
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
mod render;
|
mod render;
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
@@ -31,6 +29,8 @@ pub mod prelude {
|
|||||||
pub use crate::props_html::deserialize_props::get_root_props_from_document;
|
pub use crate::props_html::deserialize_props::get_root_props_from_document;
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
pub use crate::render::SSRState;
|
pub use crate::render::SSRState;
|
||||||
|
#[cfg(all(feature = "router", feature = "ssr"))]
|
||||||
|
pub use crate::serve_config::FullstackRouterConfig;
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
pub use crate::serve_config::{ServeConfig, ServeConfigBuilder};
|
pub use crate::serve_config::{ServeConfig, ServeConfigBuilder};
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
@@ -40,5 +40,7 @@ pub mod prelude {
|
|||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
pub use crate::server_fn::{ServerFnTraitObj, ServerFunction};
|
pub use crate::server_fn::{ServerFnTraitObj, ServerFunction};
|
||||||
pub use dioxus_server_macro::*;
|
pub use dioxus_server_macro::*;
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
|
pub use dioxus_ssr::incremental::IncrementalRendererConfig;
|
||||||
pub use server_fn::{self, ServerFn as _, ServerFnError};
|
pub use server_fn::{self, ServerFn as _, ServerFnError};
|
||||||
}
|
}
|
||||||
@@ -2,53 +2,93 @@
|
|||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use dioxus_core::VirtualDom;
|
use dioxus::prelude::VirtualDom;
|
||||||
use dioxus_ssr::Renderer;
|
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.
|
/// State used in server side rendering. This utilizes a pool of [`dioxus_ssr::Renderer`]s to cache static templates between renders.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SSRState {
|
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
|
// 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>>,
|
renderers: Arc<SsrRendererPool>,
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for SSRState {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
renderers: Arc::new(object_pool::Pool::new(10, pre_renderer)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SSRState {
|
impl SSRState {
|
||||||
/// Render the application to HTML.
|
pub(crate) fn new<P: Clone>(cfg: &ServeConfig<P>) -> Self {
|
||||||
pub fn render<P: 'static + Clone + serde::Serialize>(&self, cfg: &ServeConfig<P>) -> String {
|
if cfg.incremental.is_some() {
|
||||||
let ServeConfig { app, props, .. } = cfg;
|
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());
|
Self {
|
||||||
|
renderers: Arc::new(SsrRendererPool::Renderer(object_pool::Pool::new(
|
||||||
let _ = vdom.rebuild();
|
10,
|
||||||
|
pre_renderer,
|
||||||
self.render_vdom(&vdom, cfg)
|
))),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Render a VirtualDom to HTML.
|
/// Render the application to HTML.
|
||||||
pub fn render_vdom<P: 'static + Clone + serde::Serialize>(
|
pub fn render<P: 'static + Clone + serde::Serialize>(
|
||||||
&self,
|
&self,
|
||||||
vdom: &VirtualDom,
|
route: String,
|
||||||
cfg: &ServeConfig<P>,
|
cfg: &ServeConfig<P>,
|
||||||
) -> String {
|
modify_vdom: impl FnOnce(&mut VirtualDom),
|
||||||
let ServeConfig { index, .. } = cfg;
|
) -> 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();
|
let mut html = String::new();
|
||||||
|
|
||||||
html += &index.pre_main;
|
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
|
// serialize the props
|
||||||
let _ = crate::props_html::serialize_props::encode_in_element(&cfg.props, &mut html);
|
let _ = crate::props_html::serialize_props::encode_in_element(&cfg.props, &mut html);
|
||||||
@@ -89,12 +129,22 @@ impl SSRState {
|
|||||||
|
|
||||||
html += &index.post_main;
|
html += &index.post_main;
|
||||||
|
|
||||||
html
|
Ok(html)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pre_renderer() -> Renderer {
|
fn pre_renderer() -> Renderer {
|
||||||
let mut renderer = Renderer::default();
|
let mut renderer = Renderer::default();
|
||||||
renderer.pre_render = true;
|
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
|
renderer
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
|
#![allow(non_snake_case)]
|
||||||
//! Configeration for how to serve a Dioxus application
|
//! Configeration for how to serve a Dioxus application
|
||||||
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::path::PathBuf;
|
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`].
|
/// 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)]
|
#[derive(Clone)]
|
||||||
@@ -14,6 +15,164 @@ pub struct ServeConfigBuilder<P: Clone> {
|
|||||||
pub(crate) root_id: Option<&'static str>,
|
pub(crate) root_id: Option<&'static str>,
|
||||||
pub(crate) index_path: Option<&'static str>,
|
pub(crate) index_path: Option<&'static str>,
|
||||||
pub(crate) assets_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> {
|
impl<P: Clone> ServeConfigBuilder<P> {
|
||||||
@@ -25,9 +184,21 @@ impl<P: Clone> ServeConfigBuilder<P> {
|
|||||||
root_id: None,
|
root_id: None,
|
||||||
index_path: None,
|
index_path: None,
|
||||||
assets_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)
|
/// 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 {
|
pub fn index_path(mut self, index_path: &'static str) -> Self {
|
||||||
self.index_path = Some(index_path);
|
self.index_path = Some(index_path);
|
||||||
@@ -64,6 +235,8 @@ impl<P: Clone> ServeConfigBuilder<P> {
|
|||||||
props: self.props,
|
props: self.props,
|
||||||
index,
|
index,
|
||||||
assets_path,
|
assets_path,
|
||||||
|
#[cfg(feature = "router")]
|
||||||
|
incremental: self.incremental,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,6 +279,12 @@ pub struct ServeConfig<P: Clone> {
|
|||||||
pub(crate) props: P,
|
pub(crate) props: P,
|
||||||
pub(crate) index: IndexHtml,
|
pub(crate) index: IndexHtml,
|
||||||
pub(crate) assets_path: &'static str,
|
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> {
|
impl<P: Clone> From<ServeConfigBuilder<P>> for ServeConfig<P> {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use dioxus_core::ScopeState;
|
use dioxus::prelude::ScopeState;
|
||||||
|
|
||||||
/// A trait for an object that contains a server context
|
/// A trait for an object that contains a server context
|
||||||
pub trait HasServerContext {
|
pub trait HasServerContext {
|
||||||
|
|||||||
@@ -24,13 +24,10 @@ web-sys = { version = "0.3.60", optional = true, features = ["ScrollRestoration"
|
|||||||
js-sys = { version = "0.3.63", optional = true }
|
js-sys = { version = "0.3.63", optional = true }
|
||||||
gloo-utils = { version = "0.1.6", optional = true }
|
gloo-utils = { version = "0.1.6", optional = true }
|
||||||
dioxus-ssr = { path = "../ssr", optional = true }
|
dioxus-ssr = { path = "../ssr", optional = true }
|
||||||
lru = { version = "0.10.0", optional = true }
|
|
||||||
radix_trie = { version = "0.2.1", optional = true }
|
|
||||||
rustc-hash = "1.1.0"
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["web", "ssr"]
|
default = ["web", "ssr"]
|
||||||
ssr = ["dioxus-ssr", "lru", "radix_trie"]
|
ssr = ["dioxus-ssr"]
|
||||||
wasm_test = []
|
wasm_test = []
|
||||||
serde = ["dep:serde", "gloo-utils/serde"]
|
serde = ["dep:serde", "gloo-utils/serde"]
|
||||||
web = ["gloo", "web-sys", "wasm-bindgen", "gloo-utils", "js-sys"]
|
web = ["gloo", "web-sys", "wasm-bindgen", "gloo-utils", "js-sys"]
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use dioxus::prelude::*;
|
|||||||
use dioxus_router::prelude::*;
|
use dioxus_router::prelude::*;
|
||||||
|
|
||||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||||
use dioxus_router::ssr::{DefaultRenderer, IncrementalRenderer};
|
use dioxus_router::incremental::{DefaultRenderer, IncrementalRenderer};
|
||||||
use dioxus_ssr::Renderer;
|
use dioxus_ssr::Renderer;
|
||||||
|
|
||||||
pub fn criterion_benchmark(c: &mut Criterion) {
|
pub fn criterion_benchmark(c: &mut Criterion) {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use std::time::Duration;
|
|||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use dioxus_router::prelude::*;
|
use dioxus_router::prelude::*;
|
||||||
|
|
||||||
use dioxus_router::ssr::{DefaultRenderer, IncrementalRendererConfig};
|
use dioxus_ssr::incremental::{DefaultRenderer, IncrementalRendererConfig};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut renderer = IncrementalRendererConfig::new(DefaultRenderer {
|
let mut renderer = IncrementalRendererConfig::new(DefaultRenderer {
|
||||||
|
|||||||
@@ -66,6 +66,31 @@ where
|
|||||||
config: RouterConfigFactory<R>,
|
config: RouterConfigFactory<R>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "serde"))]
|
||||||
|
impl<R: Routable> Default for GenericRouterProps<R>
|
||||||
|
where
|
||||||
|
<R as FromStr>::Err: std::fmt::Display,
|
||||||
|
{
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
config: RouterConfigFactory::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
|
impl<R: Routable> Default for GenericRouterProps<R>
|
||||||
|
where
|
||||||
|
<R as FromStr>::Err: std::fmt::Display,
|
||||||
|
R: serde::Serialize + serde::de::DeserializeOwned,
|
||||||
|
{
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
config: RouterConfigFactory::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "serde"))]
|
#[cfg(not(feature = "serde"))]
|
||||||
impl<R: Routable> PartialEq for GenericRouterProps<R>
|
impl<R: Routable> PartialEq for GenericRouterProps<R>
|
||||||
where
|
where
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
//! Exentsions to the incremental renderer to support pre-caching static routes.
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_ssr::incremental::{IncrementalRenderer, IncrementalRendererError, RenderHTML};
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
trait IncrementalRendererRouterExt {
|
||||||
|
/// Pre-cache all static routes.
|
||||||
|
fn pre_cache_static_routes<Rt>(&mut self) -> Result<(), IncrementalRendererError>
|
||||||
|
where
|
||||||
|
Rt: Routable,
|
||||||
|
<Rt as FromStr>::Err: std::fmt::Display;
|
||||||
|
|
||||||
|
/// Render a route to a writer.
|
||||||
|
fn render_route<Rt, W, F: FnOnce(&mut VirtualDom)>(
|
||||||
|
&mut self,
|
||||||
|
route: Rt,
|
||||||
|
writer: &mut W,
|
||||||
|
modify_vdom: F,
|
||||||
|
) -> Result<(), IncrementalRendererError>
|
||||||
|
where
|
||||||
|
Rt: Routable,
|
||||||
|
<Rt as FromStr>::Err: std::fmt::Display,
|
||||||
|
W: std::io::Write;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: RenderHTML> IncrementalRendererRouterExt for IncrementalRenderer<R> {
|
||||||
|
fn pre_cache_static_routes<Rt>(&mut self) -> Result<(), IncrementalRendererError>
|
||||||
|
where
|
||||||
|
Rt: Routable,
|
||||||
|
<Rt as FromStr>::Err: std::fmt::Display,
|
||||||
|
{
|
||||||
|
for route in Rt::SITE_MAP
|
||||||
|
.iter()
|
||||||
|
.flat_map(|seg| seg.flatten().into_iter())
|
||||||
|
{
|
||||||
|
// check if this is a static segment
|
||||||
|
let mut is_static = true;
|
||||||
|
let mut full_path = String::new();
|
||||||
|
for segment in &route {
|
||||||
|
match segment {
|
||||||
|
SegmentType::Static(s) => {
|
||||||
|
full_path += "/";
|
||||||
|
full_path += s;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// skip routes with any dynamic segments
|
||||||
|
is_static = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_static {
|
||||||
|
match Rt::from_str(&full_path) {
|
||||||
|
Ok(route) => {
|
||||||
|
self.render_route(route, &mut std::io::sink(), |_| {})?;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Error pre-caching static route: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_route<Rt, W, F: FnOnce(&mut VirtualDom)>(
|
||||||
|
&mut self,
|
||||||
|
route: Rt,
|
||||||
|
writer: &mut W,
|
||||||
|
modify_vdom: F,
|
||||||
|
) -> Result<(), IncrementalRendererError>
|
||||||
|
where
|
||||||
|
Rt: Routable,
|
||||||
|
<Rt as FromStr>::Err: std::fmt::Display,
|
||||||
|
W: std::io::Write,
|
||||||
|
{
|
||||||
|
#[inline_props]
|
||||||
|
fn RenderPath<R>(cx: Scope, path: R) -> Element
|
||||||
|
where
|
||||||
|
R: Routable,
|
||||||
|
<R as FromStr>::Err: std::fmt::Display,
|
||||||
|
{
|
||||||
|
let path = path.clone();
|
||||||
|
render! {
|
||||||
|
GenericRouter::<R> {
|
||||||
|
config: || RouterConfig::default().history(MemoryHistory::with_initial_path(path))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.render(
|
||||||
|
route.to_string(),
|
||||||
|
RenderPath,
|
||||||
|
RenderPathProps { path: route },
|
||||||
|
writer,
|
||||||
|
modify_vdom,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ pub mod navigation;
|
|||||||
pub mod routable;
|
pub mod routable;
|
||||||
|
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
pub mod ssr;
|
pub mod incremental;
|
||||||
|
|
||||||
/// Components interacting with the router.
|
/// Components interacting with the router.
|
||||||
pub mod components {
|
pub mod components {
|
||||||
@@ -62,6 +62,9 @@ pub mod prelude {
|
|||||||
pub use crate::router_cfg::RouterConfig;
|
pub use crate::router_cfg::RouterConfig;
|
||||||
pub use dioxus_router_macro::Routable;
|
pub use dioxus_router_macro::Routable;
|
||||||
|
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
|
pub use dioxus_ssr::incremental::IncrementalRendererConfig;
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
/// A component with props used in the macro
|
/// A component with props used in the macro
|
||||||
pub trait HasProps {
|
pub trait HasProps {
|
||||||
|
|||||||
@@ -94,9 +94,9 @@ impl<I: std::iter::FromIterator<String>> FromRouteSegments for I {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Something that can be:
|
/// Something that can be:
|
||||||
/// 1) Converted from a route
|
/// 1. Converted from a route
|
||||||
/// 2) Converted to a route
|
/// 2. Converted to a route
|
||||||
/// 3) Rendered as a component
|
/// 3. Rendered as a component
|
||||||
///
|
///
|
||||||
/// This trait can be derived using the `#[derive(Routable)]` macro
|
/// This trait can be derived using the `#[derive(Routable)]` macro
|
||||||
pub trait Routable: std::fmt::Display + std::str::FromStr + Clone + 'static {
|
pub trait Routable: std::fmt::Display + std::str::FromStr + Clone + 'static {
|
||||||
|
|||||||
@@ -15,11 +15,13 @@ keywords = ["dom", "ui", "gui", "react", "ssr"]
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
dioxus-core = { path = "../core", version = "^0.3.0", features = ["serialize"] }
|
dioxus-core = { path = "../core", version = "^0.3.0", features = ["serialize"] }
|
||||||
askama_escape = "0.10.3"
|
askama_escape = "0.10.3"
|
||||||
|
thiserror = "1.0.23"
|
||||||
|
rustc-hash = "1.1.0"
|
||||||
|
lru = "0.10.0"
|
||||||
|
log = "0.4.13"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
dioxus = { path = "../dioxus", version = "0.3.0" }
|
dioxus = { path = "../dioxus", version = "0.3.0" }
|
||||||
thiserror = "1.0.23"
|
|
||||||
log = "0.4.13"
|
|
||||||
fern = { version = "0.6.0", features = ["colored"] }
|
fern = { version = "0.6.0", features = ["colored"] }
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
argh = "0.1.4"
|
argh = "0.1.4"
|
||||||
|
|||||||
@@ -2,15 +2,13 @@
|
|||||||
|
|
||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
use crate::prelude::*;
|
use dioxus_core::{Element, Scope, VirtualDom};
|
||||||
use dioxus::prelude::*;
|
|
||||||
use rustc_hash::FxHasher;
|
use rustc_hash::FxHasher;
|
||||||
use std::{
|
use std::{
|
||||||
hash::BuildHasherDefault,
|
hash::BuildHasherDefault,
|
||||||
io::Write,
|
io::Write,
|
||||||
num::NonZeroUsize,
|
num::NonZeroUsize,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
str::FromStr,
|
|
||||||
time::{Duration, SystemTime},
|
time::{Duration, SystemTime},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -62,6 +60,7 @@ impl RenderHTML for DefaultRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A configuration for the incremental renderer.
|
/// A configuration for the incremental renderer.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct IncrementalRendererConfig<R: RenderHTML> {
|
pub struct IncrementalRendererConfig<R: RenderHTML> {
|
||||||
static_dir: PathBuf,
|
static_dir: PathBuf,
|
||||||
memory_cache_limit: usize,
|
memory_cache_limit: usize,
|
||||||
@@ -69,9 +68,9 @@ pub struct IncrementalRendererConfig<R: RenderHTML> {
|
|||||||
render: R,
|
render: R,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for IncrementalRendererConfig<DefaultRenderer> {
|
impl<R: RenderHTML + Default> Default for IncrementalRendererConfig<R> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new(DefaultRenderer::default())
|
Self::new(R::default())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,7 +111,7 @@ impl<R: RenderHTML> IncrementalRendererConfig<R> {
|
|||||||
.map(|limit| lru::LruCache::with_hasher(limit, Default::default())),
|
.map(|limit| lru::LruCache::with_hasher(limit, Default::default())),
|
||||||
invalidate_after: self.invalidate_after,
|
invalidate_after: self.invalidate_after,
|
||||||
render: self.render,
|
render: self.render,
|
||||||
ssr_renderer: dioxus_ssr::Renderer::new(),
|
ssr_renderer: crate::Renderer::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -120,45 +119,74 @@ impl<R: RenderHTML> IncrementalRendererConfig<R> {
|
|||||||
/// An incremental renderer.
|
/// An incremental renderer.
|
||||||
pub struct IncrementalRenderer<R: RenderHTML> {
|
pub struct IncrementalRenderer<R: RenderHTML> {
|
||||||
static_dir: PathBuf,
|
static_dir: PathBuf,
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
memory_cache:
|
memory_cache:
|
||||||
Option<lru::LruCache<String, (SystemTime, Vec<u8>), BuildHasherDefault<FxHasher>>>,
|
Option<lru::LruCache<String, (SystemTime, Vec<u8>), BuildHasherDefault<FxHasher>>>,
|
||||||
invalidate_after: Option<Duration>,
|
invalidate_after: Option<Duration>,
|
||||||
ssr_renderer: dioxus_ssr::Renderer,
|
ssr_renderer: crate::Renderer,
|
||||||
render: R,
|
render: R,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: RenderHTML> IncrementalRenderer<R> {
|
impl<R: RenderHTML> IncrementalRenderer<R> {
|
||||||
|
/// Get the inner renderer.
|
||||||
|
pub fn renderer(&self) -> &crate::Renderer {
|
||||||
|
&self.ssr_renderer
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the inner renderer mutably.
|
||||||
|
pub fn renderer_mut(&mut self) -> &mut crate::Renderer {
|
||||||
|
&mut self.ssr_renderer
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a new incremental renderer builder.
|
/// Create a new incremental renderer builder.
|
||||||
pub fn builder(renderer: R) -> IncrementalRendererConfig<R> {
|
pub fn builder(renderer: R) -> IncrementalRendererConfig<R> {
|
||||||
IncrementalRendererConfig::new(renderer)
|
IncrementalRendererConfig::new(renderer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Remove a route from the cache.
|
||||||
|
pub fn invalidate(&mut self, route: &str) {
|
||||||
|
if let Some(cache) = &mut self.memory_cache {
|
||||||
|
cache.pop(route);
|
||||||
|
}
|
||||||
|
if let Some(path) = self.find_file(route) {
|
||||||
|
let _ = std::fs::remove_file(path.full_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove all routes from the cache.
|
||||||
|
pub fn invalidate_all(&mut self) {
|
||||||
|
if let Some(cache) = &mut self.memory_cache {
|
||||||
|
cache.clear();
|
||||||
|
}
|
||||||
|
// clear the static directory
|
||||||
|
let _ = std::fs::remove_dir_all(&self.static_dir);
|
||||||
|
}
|
||||||
|
|
||||||
fn track_timestamps(&self) -> bool {
|
fn track_timestamps(&self) -> bool {
|
||||||
self.invalidate_after.is_some()
|
self.invalidate_after.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_and_cache<Rt>(
|
fn render_and_cache<P: 'static>(
|
||||||
&mut self,
|
&mut self,
|
||||||
route: Rt,
|
route: String,
|
||||||
|
comp: fn(Scope<P>) -> Element,
|
||||||
|
props: P,
|
||||||
output: &mut impl Write,
|
output: &mut impl Write,
|
||||||
) -> Result<(), IncrementalRendererError>
|
modify_vdom: impl FnOnce(&mut VirtualDom),
|
||||||
where
|
) -> Result<(), IncrementalRendererError> {
|
||||||
Rt: Routable,
|
let mut vdom = VirtualDom::new_with_props(comp, props);
|
||||||
<Rt as FromStr>::Err: std::fmt::Display,
|
modify_vdom(&mut vdom);
|
||||||
{
|
|
||||||
let route_str = route.to_string();
|
|
||||||
let mut vdom = VirtualDom::new_with_props(RenderPath, RenderPathProps { path: route });
|
|
||||||
let _ = vdom.rebuild();
|
let _ = vdom.rebuild();
|
||||||
|
|
||||||
let mut html_buffer = WriteBuffer { buffer: Vec::new() };
|
let mut html_buffer = WriteBuffer { buffer: Vec::new() };
|
||||||
self.render.render_before_body(&mut html_buffer)?;
|
self.render.render_before_body(&mut html_buffer)?;
|
||||||
self.ssr_renderer.render_to(&mut html_buffer, &mut vdom)?;
|
self.ssr_renderer.render_to(&mut html_buffer, &vdom)?;
|
||||||
self.render.render_after_body(&mut html_buffer)?;
|
self.render.render_after_body(&mut html_buffer)?;
|
||||||
let html_buffer = html_buffer.buffer;
|
let html_buffer = html_buffer.buffer;
|
||||||
|
|
||||||
output.write_all(&html_buffer)?;
|
output.write_all(&html_buffer)?;
|
||||||
|
|
||||||
self.add_to_cache(route_str, html_buffer)
|
self.add_to_cache(route, html_buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_to_cache(
|
fn add_to_cache(
|
||||||
@@ -181,7 +209,7 @@ impl<R: RenderHTML> IncrementalRenderer<R> {
|
|||||||
|
|
||||||
fn add_to_memory_cache(&mut self, route: String, html: Vec<u8>) {
|
fn add_to_memory_cache(&mut self, route: String, html: Vec<u8>) {
|
||||||
if let Some(cache) = self.memory_cache.as_mut() {
|
if let Some(cache) = self.memory_cache.as_mut() {
|
||||||
cache.put(route.to_string(), (SystemTime::now(), html));
|
cache.put(route, (SystemTime::now(), html));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,25 +256,39 @@ impl<R: RenderHTML> IncrementalRenderer<R> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Render a route or get it from cache.
|
/// Render a route or get it from cache.
|
||||||
pub fn render<Rt>(
|
pub fn render<P: 'static>(
|
||||||
&mut self,
|
&mut self,
|
||||||
route: Rt,
|
route: String,
|
||||||
|
component: fn(Scope<P>) -> Element,
|
||||||
|
props: P,
|
||||||
output: &mut impl Write,
|
output: &mut impl Write,
|
||||||
) -> Result<(), IncrementalRendererError>
|
modify_vdom: impl FnOnce(&mut VirtualDom),
|
||||||
where
|
) -> Result<(), IncrementalRendererError> {
|
||||||
Rt: Routable,
|
|
||||||
<Rt as FromStr>::Err: std::fmt::Display,
|
|
||||||
{
|
|
||||||
// check if this route is cached
|
// check if this route is cached
|
||||||
if !self.search_cache(route.to_string(), output)? {
|
if !self.search_cache(route.to_string(), output)? {
|
||||||
// if not, create it
|
// if not, create it
|
||||||
self.render_and_cache(route, output)?;
|
self.render_and_cache(route, component, props, output, modify_vdom)?;
|
||||||
log::trace!("cache miss");
|
log::trace!("cache miss");
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Render a route or get it from cache to a string.
|
||||||
|
pub fn render_to_string<P: 'static>(
|
||||||
|
&mut self,
|
||||||
|
route: String,
|
||||||
|
component: fn(Scope<P>) -> Element,
|
||||||
|
props: P,
|
||||||
|
output: &mut String,
|
||||||
|
modify_vdom: impl FnOnce(&mut VirtualDom),
|
||||||
|
) -> Result<(), IncrementalRendererError> {
|
||||||
|
unsafe {
|
||||||
|
// SAFETY: The renderer will only write utf8 to the buffer
|
||||||
|
self.render(route, component, props, output.as_mut_vec(), modify_vdom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn find_file(&self, route: &str) -> Option<ValidCachedPath> {
|
fn find_file(&self, route: &str) -> Option<ValidCachedPath> {
|
||||||
let mut file_path = self.static_dir.clone();
|
let mut file_path = self.static_dir.clone();
|
||||||
for segment in route.split('/') {
|
for segment in route.split('/') {
|
||||||
@@ -300,48 +342,6 @@ impl<R: RenderHTML> IncrementalRenderer<R> {
|
|||||||
file_path.set_extension("html");
|
file_path.set_extension("html");
|
||||||
file_path
|
file_path
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pre-cache all static routes.
|
|
||||||
pub fn pre_cache_static_routes<Rt>(&mut self) -> Result<(), IncrementalRendererError>
|
|
||||||
where
|
|
||||||
Rt: Routable,
|
|
||||||
<Rt as FromStr>::Err: std::fmt::Display,
|
|
||||||
{
|
|
||||||
for route in Rt::SITE_MAP
|
|
||||||
.iter()
|
|
||||||
.flat_map(|seg| seg.flatten().into_iter())
|
|
||||||
{
|
|
||||||
// check if this is a static segment
|
|
||||||
let mut is_static = true;
|
|
||||||
let mut full_path = String::new();
|
|
||||||
for segment in &route {
|
|
||||||
match segment {
|
|
||||||
SegmentType::Static(s) => {
|
|
||||||
full_path += "/";
|
|
||||||
full_path += s;
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
// skip routes with any dynamic segments
|
|
||||||
is_static = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if is_static {
|
|
||||||
match Rt::from_str(&full_path) {
|
|
||||||
Ok(route) => {
|
|
||||||
let _ = self.render(route, &mut std::io::sink())?;
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
log::error!("Error pre-caching static route: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct WriteBuffer {
|
struct WriteBuffer {
|
||||||
@@ -375,7 +375,7 @@ impl ValidCachedPath {
|
|||||||
if value.extension() != Some(std::ffi::OsStr::new("html")) {
|
if value.extension() != Some(std::ffi::OsStr::new("html")) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let timestamp = decode_timestamp(&value.file_stem()?.to_str()?)?;
|
let timestamp = decode_timestamp(value.file_stem()?.to_str()?)?;
|
||||||
let full_path = value;
|
let full_path = value;
|
||||||
Some(Self {
|
Some(Self {
|
||||||
full_path,
|
full_path,
|
||||||
@@ -398,20 +398,6 @@ fn timestamp() -> String {
|
|||||||
format!("{:x}", timestamp)
|
format!("{:x}", timestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline_props]
|
|
||||||
fn RenderPath<R>(cx: Scope, path: R) -> Element
|
|
||||||
where
|
|
||||||
R: Routable,
|
|
||||||
<R as FromStr>::Err: std::fmt::Display,
|
|
||||||
{
|
|
||||||
let path = path.clone();
|
|
||||||
render! {
|
|
||||||
GenericRouter::<R> {
|
|
||||||
config: || RouterConfig::default().history(MemoryHistory::with_initial_path(path))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An error that can occur while rendering a route or retrieving a cached route.
|
/// An error that can occur while rendering a route or retrieving a cached route.
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum IncrementalRendererError {
|
pub enum IncrementalRendererError {
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
mod cache;
|
mod cache;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
|
pub mod incremental;
|
||||||
pub mod renderer;
|
pub mod renderer;
|
||||||
pub mod template;
|
pub mod template;
|
||||||
use dioxus_core::{Element, LazyNodes, Scope, VirtualDom};
|
use dioxus_core::{Element, LazyNodes, Scope, VirtualDom};
|
||||||
|
|||||||
Reference in new issue
Block a user