diff --git a/packages/router/Cargo.toml b/packages/router/Cargo.toml
index fb92d0888..f0ec24757 100644
--- a/packages/router/Cargo.toml
+++ b/packages/router/Cargo.toml
@@ -23,9 +23,12 @@ wasm-bindgen = { version = "0.2.86", optional = true }
web-sys = { version = "0.3.60", optional = true, features = ["ScrollRestoration"] }
js-sys = { version = "0.3.63", optional = true }
gloo-utils = { version = "0.1.6", optional = true }
+dioxus-ssr = { path = "../ssr", optional = true }
+lru = { version = "0.10.0", optional = true }
[features]
-default = ["web"]
+default = ["web", "ssr"]
+ssr = ["dioxus-ssr", "lru"]
wasm_test = []
serde = ["dep:serde", "gloo-utils/serde"]
web = ["gloo", "web-sys", "wasm-bindgen", "gloo-utils", "js-sys"]
diff --git a/packages/router/examples/static_generation.rs b/packages/router/examples/static_generation.rs
index 249deb28b..f678080c6 100644
--- a/packages/router/examples/static_generation.rs
+++ b/packages/router/examples/static_generation.rs
@@ -2,74 +2,33 @@
use dioxus::prelude::*;
use dioxus_router::prelude::*;
-use std::io::prelude::*;
-use std::{path::PathBuf, str::FromStr};
+use dioxus_router::ssr::{DefaultRenderer, IncrementalRendererConfig};
fn main() {
- render_static_pages();
-}
+ let mut renderer = IncrementalRendererConfig::new(DefaultRenderer {
+ before_body: r#"
+
+
+
+
+ Dioxus Application
+
+ "#
+ .to_string(),
+ after_body: r#"
+ "#
+ .to_string(),
+ })
+ .static_dir("./static")
+ .memory_cache_limit(5)
+ .build();
-fn render_static_pages() {
- for route in Route::SITE_MAP
- .iter()
- .flat_map(|seg| seg.flatten().into_iter())
- {
- // check if this is a static segment
- let mut file_path = PathBuf::from("./");
- let mut full_path = String::new();
- let mut is_static = true;
- for segment in &route {
- match segment {
- SegmentType::Static(s) => {
- file_path.push(s);
- full_path += "/";
- full_path += s;
- }
- _ => {
- // skip routes with any dynamic segments
- is_static = false;
- break;
- }
- }
- }
+ renderer.pre_cache_static::();
- if is_static {
- let route = Route::from_str(&full_path).unwrap();
- let mut vdom = VirtualDom::new_with_props(RenderPath, RenderPathProps { path: route });
- let _ = vdom.rebuild();
-
- file_path.push("index.html");
- std::fs::create_dir_all(file_path.parent().unwrap()).unwrap();
- let mut file = std::fs::File::create(file_path).unwrap();
-
- let body = dioxus_ssr::render(&vdom);
- let html = format!(
- r#"
-
-
-
-
-
- {}
-
-
- {}
-
-
-"#,
- full_path, body
- );
- file.write_all(html.as_bytes()).unwrap();
- }
- }
-}
-
-#[inline_props]
-fn RenderPath(cx: Scope, path: Route) -> Element {
- let path = path.clone();
- render! {
- Router {
- config: || RouterConfig::default().history(MemoryHistory::with_initial_path(path))
+ for _ in 0..2 {
+ for id in 0..10 {
+ renderer.render(Route::Post { id });
}
}
}
@@ -84,7 +43,16 @@ fn Blog(cx: Scope) -> Element {
}
#[inline_props]
-fn Post(cx: Scope) -> Element {
+fn Post(cx: Scope, id: usize) -> Element {
+ render! {
+ div {
+ "PostId: {id}"
+ }
+ }
+}
+
+#[inline_props]
+fn PostHome(cx: Scope) -> Element {
render! {
div {
"Post"
@@ -107,8 +75,12 @@ enum Route {
#[nest("/blog")]
#[route("/")]
Blog {},
- #[route("/post")]
- Post {},
+ #[route("/post/index")]
+ PostHome {},
+ #[route("/post/:id")]
+ Post {
+ id: usize,
+ },
#[end_nest]
#[route("/")]
Home {},
diff --git a/packages/router/src/lib.rs b/packages/router/src/lib.rs
index 9db86db08..c145fad61 100644
--- a/packages/router/src/lib.rs
+++ b/packages/router/src/lib.rs
@@ -6,6 +6,9 @@
pub mod navigation;
pub mod routable;
+#[cfg(feature = "ssr")]
+pub mod ssr;
+
/// Components interacting with the router.
pub mod components {
mod default_errors;
diff --git a/packages/router/src/ssr.rs b/packages/router/src/ssr.rs
new file mode 100644
index 000000000..eb9ad648b
--- /dev/null
+++ b/packages/router/src/ssr.rs
@@ -0,0 +1,244 @@
+//! Incremental file based incremental rendering
+
+#![allow(non_snake_case)]
+
+use crate::prelude::*;
+use dioxus::prelude::*;
+use std::{
+ io::{Read, Write},
+ num::NonZeroUsize,
+ path::{Path, PathBuf},
+ str::FromStr,
+};
+
+/// Something that can render a HTML page from a body.
+pub trait RenderHTML {
+ /// Render a HTML page from a body.
+ fn render_html(&self, body: &str) -> String;
+}
+
+/// The default page renderer
+pub struct DefaultRenderer {
+ /// The HTML before the body.
+ pub before_body: String,
+ /// The HTML after the body.
+ pub after_body: String,
+}
+
+impl Default for DefaultRenderer {
+ fn default() -> Self {
+ let before = r#"
+
+
+
+
+ Dioxus Application
+
+ "#;
+ let after = r#"
+ "#;
+ Self {
+ before_body: before.to_string(),
+ after_body: after.to_string(),
+ }
+ }
+}
+
+impl RenderHTML for DefaultRenderer {
+ fn render_html(&self, body: &str) -> String {
+ format!("{}{}{}", self.before_body, body, self.after_body)
+ }
+}
+
+/// A configuration for the incremental renderer.
+pub struct IncrementalRendererConfig {
+ static_dir: PathBuf,
+ memory_cache_limit: usize,
+ render: R,
+}
+
+impl Default for IncrementalRendererConfig {
+ fn default() -> Self {
+ Self::new(DefaultRenderer::default())
+ }
+}
+
+impl IncrementalRendererConfig {
+ /// Create a new incremental renderer configuration.
+ pub fn new(render: R) -> Self {
+ Self {
+ static_dir: PathBuf::from("./static"),
+ memory_cache_limit: 100,
+ render,
+ }
+ }
+
+ /// Set the static directory.
+ pub fn static_dir>(mut self, static_dir: P) -> Self {
+ self.static_dir = static_dir.as_ref().to_path_buf();
+ self
+ }
+
+ /// Set the memory cache limit.
+ pub const fn memory_cache_limit(mut self, memory_cache_limit: usize) -> Self {
+ self.memory_cache_limit = memory_cache_limit;
+ self
+ }
+
+ /// Build the incremental renderer.
+ pub fn build(self) -> IncrementalRenderer {
+ IncrementalRenderer {
+ static_dir: self.static_dir,
+ memory_cache: NonZeroUsize::new(self.memory_cache_limit)
+ .map(|limit| lru::LruCache::new(limit)),
+ render: self.render,
+ }
+ }
+}
+
+/// An incremental renderer.
+pub struct IncrementalRenderer {
+ static_dir: PathBuf,
+ memory_cache: Option>,
+ render: R,
+}
+
+impl IncrementalRenderer {
+ /// Create a new incremental renderer builder.
+ pub fn builder(renderer: R) -> IncrementalRendererConfig {
+ IncrementalRendererConfig::new(renderer)
+ }
+
+ fn render_uncached