make fullstack helpers compatable with prerendering
This commit is contained in:
1 parent
a7f7aad947
commit
f5c60eeb4c
7 files changed
+139
-93
No files matched your search
@@ -31,6 +31,8 @@ pub mod prelude {
|
|||||||
pub use crate::adapters::warp_adapter::*;
|
pub use crate::adapters::warp_adapter::*;
|
||||||
#[cfg(not(feature = "ssr"))]
|
#[cfg(not(feature = "ssr"))]
|
||||||
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(all(feature = "ssr", feature = "router"))]
|
||||||
|
pub use crate::render::pre_cache_static_routes_with_props;
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
pub use crate::render::SSRState;
|
pub use crate::render::SSRState;
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
|
|||||||
@@ -37,7 +37,8 @@ fn serialized_and_deserializes() {
|
|||||||
};
|
};
|
||||||
y
|
y
|
||||||
];
|
];
|
||||||
serialize_props::serde_to_writable(&data, &mut as_string).unwrap();
|
serialize_props::serde_to_writable(&data, &mut unsafe { as_string.as_bytes_mut() })
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
println!("{}", as_string);
|
println!("{}", as_string);
|
||||||
println!(
|
println!(
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ use base64::Engine;
|
|||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
pub(crate) fn serde_to_writable<T: Serialize>(
|
pub(crate) fn serde_to_writable<T: Serialize>(
|
||||||
value: &T,
|
value: &T,
|
||||||
mut write_to: impl std::fmt::Write,
|
write_to: &mut impl std::io::Write,
|
||||||
) -> std::fmt::Result {
|
) -> std::io::Result<()> {
|
||||||
let serialized = postcard::to_allocvec(value).unwrap();
|
let serialized = postcard::to_allocvec(value).unwrap();
|
||||||
let compressed = yazi::compress(
|
let compressed = yazi::compress(
|
||||||
&serialized,
|
&serialized,
|
||||||
@@ -15,7 +15,7 @@ pub(crate) fn serde_to_writable<T: Serialize>(
|
|||||||
yazi::CompressionLevel::BestSize,
|
yazi::CompressionLevel::BestSize,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
write_to.write_str(&STANDARD.encode(compressed));
|
write_to.write_all(&STANDARD.encode(compressed).as_bytes())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,9 +23,10 @@ pub(crate) fn serde_to_writable<T: Serialize>(
|
|||||||
/// Encode data into a element. This is inteded to be used in the server to send data to the client.
|
/// Encode data into a element. This is inteded to be used in the server to send data to the client.
|
||||||
pub(crate) fn encode_in_element<T: Serialize>(
|
pub(crate) fn encode_in_element<T: Serialize>(
|
||||||
data: T,
|
data: T,
|
||||||
mut write_to: impl std::fmt::Write,
|
write_to: &mut impl std::io::Write,
|
||||||
) -> std::fmt::Result {
|
) -> std::io::Result<()> {
|
||||||
write_to.write_str(r#"<meta hidden="true" id="dioxus-storage" data-serialized=""#)?;
|
write_to
|
||||||
serde_to_writable(&data, &mut write_to)?;
|
.write_all(r#"<meta hidden="true" id="dioxus-storage" data-serialized=""#.as_bytes())?;
|
||||||
write_to.write_str(r#"" />"#)
|
serde_to_writable(&data, write_to)?;
|
||||||
|
write_to.write_all(r#"" />"#.as_bytes())
|
||||||
}
|
}
|
||||||
@@ -1,29 +1,24 @@
|
|||||||
//! A shared pool of renderers for efficient server side rendering.
|
//! A shared pool of renderers for efficient server side rendering.
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::{fmt::Write, sync::Arc};
|
||||||
|
|
||||||
use dioxus::prelude::VirtualDom;
|
use dioxus::prelude::VirtualDom;
|
||||||
use dioxus_ssr::{
|
use dioxus_ssr::{
|
||||||
incremental::{IncrementalRendererConfig, RenderFreshness},
|
incremental::{IncrementalRendererConfig, RenderFreshness, WrapBody},
|
||||||
Renderer,
|
Renderer,
|
||||||
};
|
};
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
enum SsrRendererPool {
|
enum SsrRendererPool {
|
||||||
Renderer(object_pool::Pool<Renderer>),
|
Renderer(object_pool::Pool<Renderer>),
|
||||||
Incremental(
|
Incremental(object_pool::Pool<dioxus_ssr::incremental::IncrementalRenderer>),
|
||||||
object_pool::Pool<
|
|
||||||
dioxus_ssr::incremental::IncrementalRenderer<
|
|
||||||
crate::serve_config::EmptyIncrementalRenderTemplate,
|
|
||||||
>,
|
|
||||||
>,
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SsrRendererPool {
|
impl SsrRendererPool {
|
||||||
async fn render_to<P: Clone + 'static>(
|
async fn render_to<P: Clone + Serialize + Send + Sync + 'static>(
|
||||||
&self,
|
&self,
|
||||||
cfg: &ServeConfig<P>,
|
cfg: &ServeConfig<P>,
|
||||||
route: String,
|
route: String,
|
||||||
@@ -32,21 +27,28 @@ impl SsrRendererPool {
|
|||||||
to: &mut String,
|
to: &mut String,
|
||||||
modify_vdom: impl FnOnce(&mut VirtualDom),
|
modify_vdom: impl FnOnce(&mut VirtualDom),
|
||||||
) -> Result<RenderFreshness, dioxus_ssr::incremental::IncrementalRendererError> {
|
) -> Result<RenderFreshness, dioxus_ssr::incremental::IncrementalRendererError> {
|
||||||
|
let wrapper = FullstackRenderer { cfg };
|
||||||
match self {
|
match self {
|
||||||
Self::Renderer(pool) => {
|
Self::Renderer(pool) => {
|
||||||
let mut vdom = VirtualDom::new_with_props(component, props);
|
let mut vdom = VirtualDom::new_with_props(component, props);
|
||||||
modify_vdom(&mut vdom);
|
modify_vdom(&mut vdom);
|
||||||
|
|
||||||
let _ = vdom.rebuild();
|
let _ = vdom.rebuild();
|
||||||
|
|
||||||
let mut renderer = pool.pull(pre_renderer);
|
let mut renderer = pool.pull(pre_renderer);
|
||||||
|
|
||||||
|
// SAFETY: The fullstack renderer will only write UTF-8 to the buffer.
|
||||||
|
wrapper.render_before_body(unsafe { &mut to.as_bytes_mut() })?;
|
||||||
renderer.render_to(to, &vdom)?;
|
renderer.render_to(to, &vdom)?;
|
||||||
|
wrapper.render_after_body(unsafe { &mut to.as_bytes_mut() })?;
|
||||||
|
|
||||||
Ok(RenderFreshness::now(None))
|
Ok(RenderFreshness::now(None))
|
||||||
}
|
}
|
||||||
Self::Incremental(pool) => {
|
Self::Incremental(pool) => {
|
||||||
let mut renderer = pool.pull(|| incremental_pre_renderer(cfg));
|
let mut renderer =
|
||||||
|
pool.pull(|| incremental_pre_renderer(cfg.incremental.as_ref().unwrap()));
|
||||||
Ok(renderer
|
Ok(renderer
|
||||||
.render_to_string(route, component, props, to, modify_vdom)
|
.render_to_string(route, component, props, to, modify_vdom, &wrapper)
|
||||||
.await?)
|
.await?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -66,7 +68,7 @@ impl SSRState {
|
|||||||
return Self {
|
return Self {
|
||||||
renderers: Arc::new(SsrRendererPool::Incremental(object_pool::Pool::new(
|
renderers: Arc::new(SsrRendererPool::Incremental(object_pool::Pool::new(
|
||||||
10,
|
10,
|
||||||
|| incremental_pre_renderer(cfg),
|
|| incremental_pre_renderer(cfg.incremental.as_ref().unwrap()),
|
||||||
))),
|
))),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -90,26 +92,48 @@ impl SSRState {
|
|||||||
> + Send
|
> + Send
|
||||||
+ 'a {
|
+ 'a {
|
||||||
async move {
|
async move {
|
||||||
let ServeConfig { app, props, .. } = cfg;
|
|
||||||
|
|
||||||
let ServeConfig { index, .. } = cfg;
|
|
||||||
|
|
||||||
let mut html = String::new();
|
let mut html = String::new();
|
||||||
|
let ServeConfig { app, props, .. } = cfg;
|
||||||
html += &index.pre_main;
|
|
||||||
|
|
||||||
let freshness = self
|
let freshness = self
|
||||||
.renderers
|
.renderers
|
||||||
.render_to(cfg, route, *app, props.clone(), &mut html, modify_vdom)
|
.render_to(cfg, route, *app, props.clone(), &mut html, modify_vdom)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// serialize the props
|
Ok(RenderResponse { html, freshness })
|
||||||
let _ = crate::props_html::serialize_props::encode_in_element(&cfg.props, &mut html);
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(all(debug_assertions, feature = "hot-reload"))]
|
struct FullstackRenderer<'a, P: Clone + Send + Sync + 'static> {
|
||||||
{
|
cfg: &'a ServeConfig<P>,
|
||||||
// In debug mode, we need to add a script to the page that will reload the page if the websocket disconnects to make full recompile hot reloads work
|
}
|
||||||
let disconnect_js = r#"(function () {
|
|
||||||
|
impl<'a, P: Clone + Serialize + Send + Sync + 'static> dioxus_ssr::incremental::WrapBody
|
||||||
|
for FullstackRenderer<'a, P>
|
||||||
|
{
|
||||||
|
fn render_before_body<R: std::io::Write>(
|
||||||
|
&self,
|
||||||
|
to: &mut R,
|
||||||
|
) -> Result<(), dioxus_ssr::incremental::IncrementalRendererError> {
|
||||||
|
let ServeConfig { index, .. } = &self.cfg;
|
||||||
|
|
||||||
|
to.write_all(index.pre_main.as_bytes())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_after_body<R: std::io::Write>(
|
||||||
|
&self,
|
||||||
|
to: &mut R,
|
||||||
|
) -> Result<(), dioxus_ssr::incremental::IncrementalRendererError> {
|
||||||
|
// serialize the props
|
||||||
|
crate::props_html::serialize_props::encode_in_element(&self.cfg.props, to)?;
|
||||||
|
|
||||||
|
#[cfg(all(debug_assertions, feature = "hot-reload"))]
|
||||||
|
{
|
||||||
|
// In debug mode, we need to add a script to the page that will reload the page if the websocket disconnects to make full recompile hot reloads work
|
||||||
|
let disconnect_js = r#"(function () {
|
||||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||||
const url = protocol + '//' + window.location.host + '/_dioxus/disconnect';
|
const url = protocol + '//' + window.location.host + '/_dioxus/disconnect';
|
||||||
const poll_interval = 1000;
|
const poll_interval = 1000;
|
||||||
@@ -135,15 +159,16 @@ impl SSRState {
|
|||||||
ws.onclose = reload_upon_connect;
|
ws.onclose = reload_upon_connect;
|
||||||
})()"#;
|
})()"#;
|
||||||
|
|
||||||
html += r#"<script>"#;
|
to.write_all(r#"<script>"#.as_bytes())?;
|
||||||
html += disconnect_js;
|
to.write_all(disconnect_js.as_bytes())?;
|
||||||
html += r#"</script>"#;
|
to.write_all(r#"</script>"#.as_bytes())?;
|
||||||
}
|
|
||||||
|
|
||||||
html += &index.post_main;
|
|
||||||
|
|
||||||
Ok(RenderResponse { html, freshness })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let ServeConfig { index, .. } = &self.cfg;
|
||||||
|
|
||||||
|
to.write_all(index.post_main.as_bytes())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,12 +196,29 @@ fn pre_renderer() -> Renderer {
|
|||||||
renderer.into()
|
renderer.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn incremental_pre_renderer<P: Clone>(
|
fn incremental_pre_renderer(
|
||||||
cfg: &ServeConfig<P>,
|
cfg: &IncrementalRendererConfig,
|
||||||
) -> dioxus_ssr::incremental::IncrementalRenderer<crate::serve_config::EmptyIncrementalRenderTemplate>
|
) -> dioxus_ssr::incremental::IncrementalRenderer {
|
||||||
{
|
let mut renderer = cfg.clone().build();
|
||||||
let builder: &IncrementalRendererConfig<_> = &*cfg.incremental.as_ref().unwrap();
|
|
||||||
let mut renderer = builder.clone().build();
|
|
||||||
renderer.renderer_mut().pre_render = true;
|
renderer.renderer_mut().pre_render = true;
|
||||||
renderer
|
renderer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(all(feature = "ssr", feature = "router"))]
|
||||||
|
/// Pre-caches all static routes
|
||||||
|
pub async fn pre_cache_static_routes_with_props<Rt>(
|
||||||
|
cfg: &crate::prelude::ServeConfig<crate::router::FullstackRouterConfig<Rt>>,
|
||||||
|
) -> Result<(), dioxus_ssr::incremental::IncrementalRendererError>
|
||||||
|
where
|
||||||
|
Rt: dioxus_router::prelude::Routable + Send + Sync + Serialize,
|
||||||
|
<Rt as std::str::FromStr>::Err: std::fmt::Display,
|
||||||
|
{
|
||||||
|
let wrapper = FullstackRenderer { cfg };
|
||||||
|
let mut renderer = incremental_pre_renderer(
|
||||||
|
cfg.incremental
|
||||||
|
.as_ref()
|
||||||
|
.expect("incremental renderer config must be set to pre-cache static routes"),
|
||||||
|
);
|
||||||
|
|
||||||
|
dioxus_router::incremental::pre_cache_static_routes::<Rt, _>(&mut renderer, &wrapper).await
|
||||||
|
}
|
||||||
@@ -17,18 +17,15 @@ 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>,
|
||||||
pub(crate) incremental: Option<
|
pub(crate) incremental:
|
||||||
std::sync::Arc<
|
Option<std::sync::Arc<dioxus_ssr::incremental::IncrementalRendererConfig>>,
|
||||||
dioxus_ssr::incremental::IncrementalRendererConfig<EmptyIncrementalRenderTemplate>,
|
|
||||||
>,
|
|
||||||
>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A template for incremental rendering that does nothing.
|
/// A template for incremental rendering that does nothing.
|
||||||
#[derive(Default, Clone)]
|
#[derive(Default, Clone)]
|
||||||
pub struct EmptyIncrementalRenderTemplate;
|
pub struct EmptyIncrementalRenderTemplate;
|
||||||
|
|
||||||
impl dioxus_ssr::incremental::RenderHTML for EmptyIncrementalRenderTemplate {
|
impl dioxus_ssr::incremental::WrapBody for EmptyIncrementalRenderTemplate {
|
||||||
fn render_after_body<R: std::io::Write>(
|
fn render_after_body<R: std::io::Write>(
|
||||||
&self,
|
&self,
|
||||||
_: &mut R,
|
_: &mut R,
|
||||||
@@ -70,10 +67,7 @@ impl<P: Clone> ServeConfigBuilder<P> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Enable incremental static generation
|
/// Enable incremental static generation
|
||||||
pub fn incremental(
|
pub fn incremental(mut self, cfg: dioxus_ssr::incremental::IncrementalRendererConfig) -> Self {
|
||||||
mut self,
|
|
||||||
cfg: dioxus_ssr::incremental::IncrementalRendererConfig<EmptyIncrementalRenderTemplate>,
|
|
||||||
) -> Self {
|
|
||||||
self.incremental = Some(std::sync::Arc::new(cfg));
|
self.incremental = Some(std::sync::Arc::new(cfg));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@@ -157,11 +151,8 @@ 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,
|
||||||
pub(crate) incremental: Option<
|
pub(crate) incremental:
|
||||||
std::sync::Arc<
|
Option<std::sync::Arc<dioxus_ssr::incremental::IncrementalRendererConfig>>,
|
||||||
dioxus_ssr::incremental::IncrementalRendererConfig<EmptyIncrementalRenderTemplate>,
|
|
||||||
>,
|
|
||||||
>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P: Clone> From<ServeConfigBuilder<P>> for ServeConfig<P> {
|
impl<P: Clone> From<ServeConfigBuilder<P>> for ServeConfig<P> {
|
||||||
|
|||||||
@@ -3,14 +3,15 @@ use std::str::FromStr;
|
|||||||
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use dioxus_ssr::incremental::{
|
use dioxus_ssr::incremental::{
|
||||||
IncrementalRenderer, IncrementalRendererError, RenderFreshness, RenderHTML,
|
IncrementalRenderer, IncrementalRendererError, RenderFreshness, WrapBody,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// Pre-cache all static routes.
|
/// Pre-cache all static routes.
|
||||||
pub async fn pre_cache_static_routes<Rt, R: RenderHTML + Send>(
|
pub async fn pre_cache_static_routes<Rt, R: WrapBody + Send + Sync>(
|
||||||
renderer: &mut IncrementalRenderer<R>,
|
renderer: &mut IncrementalRenderer,
|
||||||
|
wrapper: &R,
|
||||||
) -> Result<(), IncrementalRendererError>
|
) -> Result<(), IncrementalRendererError>
|
||||||
where
|
where
|
||||||
Rt: Routable,
|
Rt: Routable,
|
||||||
@@ -41,7 +42,7 @@ where
|
|||||||
if is_static {
|
if is_static {
|
||||||
match Rt::from_str(&full_path) {
|
match Rt::from_str(&full_path) {
|
||||||
Ok(route) => {
|
Ok(route) => {
|
||||||
render_route(renderer, route, &mut tokio::io::sink(), |_| {}).await?;
|
render_route(renderer, route, &mut tokio::io::sink(), |_| {}, wrapper).await?;
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::info!("@ route: {}", full_path);
|
log::info!("@ route: {}", full_path);
|
||||||
@@ -55,11 +56,12 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Render a route to a writer.
|
/// Render a route to a writer.
|
||||||
pub async fn render_route<R: RenderHTML + Send, Rt, W, F: FnOnce(&mut VirtualDom)>(
|
pub async fn render_route<R: WrapBody + Send + Sync, Rt, W, F: FnOnce(&mut VirtualDom)>(
|
||||||
renderer: &mut IncrementalRenderer<R>,
|
renderer: &mut IncrementalRenderer,
|
||||||
route: Rt,
|
route: Rt,
|
||||||
writer: &mut W,
|
writer: &mut W,
|
||||||
modify_vdom: F,
|
modify_vdom: F,
|
||||||
|
wrapper: &R,
|
||||||
) -> Result<RenderFreshness, IncrementalRendererError>
|
) -> Result<RenderFreshness, IncrementalRendererError>
|
||||||
where
|
where
|
||||||
Rt: Routable,
|
Rt: Routable,
|
||||||
@@ -87,6 +89,7 @@ where
|
|||||||
RenderPathProps { path: route },
|
RenderPathProps { path: route },
|
||||||
writer,
|
writer,
|
||||||
modify_vdom,
|
modify_vdom,
|
||||||
|
wrapper,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
@@ -15,7 +15,7 @@ use std::{
|
|||||||
use tokio::io::{AsyncWrite, AsyncWriteExt, BufReader};
|
use tokio::io::{AsyncWrite, AsyncWriteExt, BufReader};
|
||||||
|
|
||||||
/// Something that can render a HTML page from a body.
|
/// Something that can render a HTML page from a body.
|
||||||
pub trait RenderHTML {
|
pub trait WrapBody {
|
||||||
/// Render the HTML before the body
|
/// Render the HTML before the body
|
||||||
fn render_before_body<R: Write>(&self, to: &mut R) -> Result<(), IncrementalRendererError>;
|
fn render_before_body<R: Write>(&self, to: &mut R) -> Result<(), IncrementalRendererError>;
|
||||||
/// Render the HTML after the body
|
/// Render the HTML after the body
|
||||||
@@ -49,7 +49,7 @@ impl Default for DefaultRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RenderHTML for DefaultRenderer {
|
impl WrapBody for DefaultRenderer {
|
||||||
fn render_before_body<R: Write>(&self, to: &mut R) -> Result<(), IncrementalRendererError> {
|
fn render_before_body<R: Write>(&self, to: &mut R) -> Result<(), IncrementalRendererError> {
|
||||||
to.write_all(self.before_body.as_bytes())?;
|
to.write_all(self.before_body.as_bytes())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -63,27 +63,25 @@ impl RenderHTML for DefaultRenderer {
|
|||||||
|
|
||||||
/// A configuration for the incremental renderer.
|
/// A configuration for the incremental renderer.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct IncrementalRendererConfig<R: RenderHTML> {
|
pub struct IncrementalRendererConfig {
|
||||||
static_dir: PathBuf,
|
static_dir: PathBuf,
|
||||||
memory_cache_limit: usize,
|
memory_cache_limit: usize,
|
||||||
invalidate_after: Option<Duration>,
|
invalidate_after: Option<Duration>,
|
||||||
render: R,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: RenderHTML + Default> Default for IncrementalRendererConfig<R> {
|
impl Default for IncrementalRendererConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new(R::default())
|
Self::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: RenderHTML> IncrementalRendererConfig<R> {
|
impl IncrementalRendererConfig {
|
||||||
/// Create a new incremental renderer configuration.
|
/// Create a new incremental renderer configuration.
|
||||||
pub fn new(render: R) -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
static_dir: PathBuf::from("./static"),
|
static_dir: PathBuf::from("./static"),
|
||||||
memory_cache_limit: 10000,
|
memory_cache_limit: 10000,
|
||||||
invalidate_after: None,
|
invalidate_after: None,
|
||||||
render,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,30 +104,28 @@ impl<R: RenderHTML> IncrementalRendererConfig<R> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Build the incremental renderer.
|
/// Build the incremental renderer.
|
||||||
pub fn build(self) -> IncrementalRenderer<R> {
|
pub fn build(self) -> IncrementalRenderer {
|
||||||
IncrementalRenderer {
|
IncrementalRenderer {
|
||||||
static_dir: self.static_dir,
|
static_dir: self.static_dir,
|
||||||
memory_cache: NonZeroUsize::new(self.memory_cache_limit)
|
memory_cache: NonZeroUsize::new(self.memory_cache_limit)
|
||||||
.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,
|
|
||||||
ssr_renderer: crate::Renderer::new(),
|
ssr_renderer: crate::Renderer::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An incremental renderer.
|
/// An incremental renderer.
|
||||||
pub struct IncrementalRenderer<R: RenderHTML> {
|
pub struct IncrementalRenderer {
|
||||||
static_dir: PathBuf,
|
static_dir: PathBuf,
|
||||||
#[allow(clippy::type_complexity)]
|
#[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: crate::Renderer,
|
ssr_renderer: crate::Renderer,
|
||||||
render: R,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: RenderHTML + std::marker::Send> IncrementalRenderer<R> {
|
impl IncrementalRenderer {
|
||||||
/// Get the inner renderer.
|
/// Get the inner renderer.
|
||||||
pub fn renderer(&self) -> &crate::Renderer {
|
pub fn renderer(&self) -> &crate::Renderer {
|
||||||
&self.ssr_renderer
|
&self.ssr_renderer
|
||||||
@@ -141,8 +137,8 @@ impl<R: RenderHTML + std::marker::Send> IncrementalRenderer<R> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new incremental renderer builder.
|
/// Create a new incremental renderer builder.
|
||||||
pub fn builder(renderer: R) -> IncrementalRendererConfig<R> {
|
pub fn builder() -> IncrementalRendererConfig {
|
||||||
IncrementalRendererConfig::new(renderer)
|
IncrementalRendererConfig::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove a route from the cache.
|
/// Remove a route from the cache.
|
||||||
@@ -168,13 +164,14 @@ impl<R: RenderHTML + std::marker::Send> IncrementalRenderer<R> {
|
|||||||
self.invalidate_after.is_some()
|
self.invalidate_after.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_and_cache<'a, P: 'static>(
|
fn render_and_cache<'a, P: 'static, R: WrapBody + Send + Sync>(
|
||||||
&'a mut self,
|
&'a mut self,
|
||||||
route: String,
|
route: String,
|
||||||
comp: fn(Scope<P>) -> Element,
|
comp: fn(Scope<P>) -> Element,
|
||||||
props: P,
|
props: P,
|
||||||
output: &'a mut (impl AsyncWrite + Unpin + Send),
|
output: &'a mut (impl AsyncWrite + Unpin + Send),
|
||||||
modify_vdom: impl FnOnce(&mut VirtualDom),
|
modify_vdom: impl FnOnce(&mut VirtualDom),
|
||||||
|
renderer: &'a R,
|
||||||
) -> impl std::future::Future<Output = Result<RenderFreshness, IncrementalRendererError>> + 'a + Send
|
) -> impl std::future::Future<Output = Result<RenderFreshness, IncrementalRendererError>> + 'a + Send
|
||||||
{
|
{
|
||||||
let mut html_buffer = WriteBuffer { buffer: Vec::new() };
|
let mut html_buffer = WriteBuffer { buffer: Vec::new() };
|
||||||
@@ -185,13 +182,13 @@ impl<R: RenderHTML + std::marker::Send> IncrementalRenderer<R> {
|
|||||||
modify_vdom(&mut vdom);
|
modify_vdom(&mut vdom);
|
||||||
let _ = vdom.rebuild();
|
let _ = vdom.rebuild();
|
||||||
|
|
||||||
result_1 = self.render.render_before_body(&mut *html_buffer);
|
result_1 = renderer.render_before_body(&mut *html_buffer);
|
||||||
result2 = self.ssr_renderer.render_to(&mut html_buffer, &vdom);
|
result2 = self.ssr_renderer.render_to(&mut html_buffer, &vdom);
|
||||||
}
|
}
|
||||||
async move {
|
async move {
|
||||||
result_1?;
|
result_1?;
|
||||||
result2?;
|
result2?;
|
||||||
self.render.render_after_body(&mut *html_buffer)?;
|
renderer.render_after_body(&mut *html_buffer)?;
|
||||||
let html_buffer = html_buffer.buffer;
|
let html_buffer = html_buffer.buffer;
|
||||||
|
|
||||||
output.write_all(&html_buffer).await?;
|
output.write_all(&html_buffer).await?;
|
||||||
@@ -273,13 +270,14 @@ impl<R: RenderHTML + std::marker::Send> IncrementalRenderer<R> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Render a route or get it from cache.
|
/// Render a route or get it from cache.
|
||||||
pub async fn render<P: 'static>(
|
pub async fn render<P: 'static, R: WrapBody + Send + Sync>(
|
||||||
&mut self,
|
&mut self,
|
||||||
route: String,
|
route: String,
|
||||||
component: fn(Scope<P>) -> Element,
|
component: fn(Scope<P>) -> Element,
|
||||||
props: P,
|
props: P,
|
||||||
output: &mut (impl AsyncWrite + Unpin + std::marker::Send),
|
output: &mut (impl AsyncWrite + Unpin + std::marker::Send),
|
||||||
modify_vdom: impl FnOnce(&mut VirtualDom),
|
modify_vdom: impl FnOnce(&mut VirtualDom),
|
||||||
|
renderer: &R,
|
||||||
) -> Result<RenderFreshness, IncrementalRendererError> {
|
) -> Result<RenderFreshness, IncrementalRendererError> {
|
||||||
// check if this route is cached
|
// check if this route is cached
|
||||||
if let Some(freshness) = self.search_cache(route.to_string(), output).await? {
|
if let Some(freshness) = self.search_cache(route.to_string(), output).await? {
|
||||||
@@ -287,7 +285,7 @@ impl<R: RenderHTML + std::marker::Send> IncrementalRenderer<R> {
|
|||||||
} else {
|
} else {
|
||||||
// if not, create it
|
// if not, create it
|
||||||
let freshness = self
|
let freshness = self
|
||||||
.render_and_cache(route, component, props, output, modify_vdom)
|
.render_and_cache(route, component, props, output, modify_vdom, renderer)
|
||||||
.await?;
|
.await?;
|
||||||
log::trace!("cache miss");
|
log::trace!("cache miss");
|
||||||
Ok(freshness)
|
Ok(freshness)
|
||||||
@@ -295,18 +293,26 @@ impl<R: RenderHTML + std::marker::Send> IncrementalRenderer<R> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Render a route or get it from cache to a string.
|
/// Render a route or get it from cache to a string.
|
||||||
pub async fn render_to_string<P: 'static>(
|
pub async fn render_to_string<P: 'static, R: WrapBody + Send + Sync>(
|
||||||
&mut self,
|
&mut self,
|
||||||
route: String,
|
route: String,
|
||||||
component: fn(Scope<P>) -> Element,
|
component: fn(Scope<P>) -> Element,
|
||||||
props: P,
|
props: P,
|
||||||
output: &mut String,
|
output: &mut String,
|
||||||
modify_vdom: impl FnOnce(&mut VirtualDom),
|
modify_vdom: impl FnOnce(&mut VirtualDom),
|
||||||
|
renderer: &R,
|
||||||
) -> Result<RenderFreshness, IncrementalRendererError> {
|
) -> Result<RenderFreshness, IncrementalRendererError> {
|
||||||
unsafe {
|
unsafe {
|
||||||
// SAFETY: The renderer will only write utf8 to the buffer
|
// SAFETY: The renderer will only write utf8 to the buffer
|
||||||
self.render(route, component, props, output.as_mut_vec(), modify_vdom)
|
self.render(
|
||||||
.await
|
route,
|
||||||
|
component,
|
||||||
|
props,
|
||||||
|
output.as_mut_vec(),
|
||||||
|
modify_vdom,
|
||||||
|
renderer,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user