From 9877dd7ed8928a2c5d08b546a54c93b26204c287 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 2 Apr 2023 16:18:15 -0500 Subject: [PATCH] parse and inject pre rendered content to work with trunk --- packages/server/src/adapters/axum_adapter.rs | 48 ++++++++-- packages/server/src/adapters/salvo_adapter.rs | 36 +++++++- packages/server/src/adapters/warp_adapter.rs | 2 +- packages/server/src/render.rs | 39 +------- packages/server/src/serve.rs | 92 ++++++++++++------- 5 files changed, 132 insertions(+), 85 deletions(-) diff --git a/packages/server/src/adapters/axum_adapter.rs b/packages/server/src/adapters/axum_adapter.rs index 8fb98b6ec..75c736731 100644 --- a/packages/server/src/adapters/axum_adapter.rs +++ b/packages/server/src/adapters/axum_adapter.rs @@ -70,23 +70,51 @@ where } fn serve_dioxus_application( - self, + mut self, server_fn_route: &'static str, cfg: impl Into>, ) -> Self { - use tower_http::services::ServeDir; + use tower_http::services::{ServeDir, ServeFile}; let cfg = cfg.into(); - // Serve the dist folder and the index.html file - let serve_dir = ServeDir::new(cfg.assets_path); - - self.register_server_fns(server_fn_route) - .nest_service("/assets", serve_dir) - .route_service( - "/", - get(render_handler).with_state((cfg, SSRState::default())), + // Serve all files in dist folder except index.html + let dir = std::fs::read_dir(cfg.assets_path).unwrap_or_else(|e| { + panic!( + "Couldn't read assets directory at {:?}: {}", + &cfg.assets_path, e ) + }); + + for entry in dir.flatten() { + let path = entry.path(); + if path.ends_with("index.html") { + continue; + } + let route = path + .strip_prefix(&cfg.assets_path) + .unwrap() + .iter() + .map(|segment| { + segment.to_str().unwrap_or_else(|| { + panic!("Failed to convert path segment {:?} to string", segment) + }) + }) + .collect::>() + .join("/"); + let route = format!("/{}", route); + if path.is_dir() { + self = self.nest_service(&route, ServeDir::new(path)); + } else { + self = self.nest_service(&route, ServeFile::new(path)); + } + } + + // Add server functions and render index.html + self.register_server_fns(server_fn_route).route( + "/", + get(render_handler).with_state((cfg, SSRState::default())), + ) } } diff --git a/packages/server/src/adapters/salvo_adapter.rs b/packages/server/src/adapters/salvo_adapter.rs index 0d8a27b22..f3bbd5f43 100644 --- a/packages/server/src/adapters/salvo_adapter.rs +++ b/packages/server/src/adapters/salvo_adapter.rs @@ -50,24 +50,50 @@ impl DioxusRouterExt for Router { } fn register_server_fns(self, server_fn_route: &'static str) -> Self { - self.register_server_fns_with_handler(|| ServerFnHandler { + self.register_server_fns_with_handler(server_fn_route, |func| ServerFnHandler { server_context: DioxusServerContext::default(), function: func, }) } fn serve_dioxus_application( - self, + mut self, server_fn_route: &'static str, cfg: impl Into>, ) -> Self { let cfg = cfg.into(); - // Serve the dist folder and the index.html file - let serve_dir = StaticDir::new([cfg.assets_path]); + + // Serve all files in dist folder except index.html + let dir = std::fs::read_dir(cfg.assets_path).unwrap_or_else(|e| { + panic!( + "Couldn't read assets directory at {:?}: {}", + &cfg.assets_path, e + ) + }); + + for entry in dir.flatten() { + let path = entry.path(); + if path.ends_with("index.html") { + continue; + } + let serve_dir = StaticDir::new([path.clone()]); + let route = path + .strip_prefix(&cfg.assets_path) + .unwrap() + .iter() + .map(|segment| { + segment.to_str().unwrap_or_else(|| { + panic!("Failed to convert path segment {:?} to string", segment) + }) + }) + .collect::>() + .join("/"); + let route = format!("/{}/<**path>", route); + self = self.push(Router::with_path(route).get(serve_dir)) + } self.register_server_fns(server_fn_route) .push(Router::with_path("/").get(SSRHandler { cfg })) - .push(Router::with_path("assets/<**path>").get(serve_dir)) } } diff --git a/packages/server/src/adapters/warp_adapter.rs b/packages/server/src/adapters/warp_adapter.rs index 00ebb38fa..82d1c382f 100644 --- a/packages/server/src/adapters/warp_adapter.rs +++ b/packages/server/src/adapters/warp_adapter.rs @@ -69,7 +69,7 @@ pub fn serve_dioxus_application( .and(warp::get()) .and(with_ssr_state()) .map(move |renderer: SSRState| warp::reply::html(renderer.render(&cfg)))) - .or(warp::path("assets").and(serve_dir)) + .or(serve_dir) .boxed() } diff --git a/packages/server/src/render.rs b/packages/server/src/render.rs index bf8c12854..f6582d6a7 100644 --- a/packages/server/src/render.rs +++ b/packages/server/src/render.rs @@ -1,4 +1,3 @@ -use std::fmt::Write; use std::sync::Arc; use dioxus_core::VirtualDom; @@ -47,12 +46,7 @@ impl Default for SSRState { impl SSRState { pub fn render(&self, cfg: &ServeConfig

) -> String { let ServeConfig { - app, - application_name, - base_path, - head, - props, - .. + app, props, index, .. } = cfg; let mut vdom = VirtualDom::new_with_props(*app, props.clone()); @@ -63,38 +57,11 @@ impl SSRState { let mut html = String::new(); - let result = write!( - &mut html, - r#" - - - {head} - -

"# - ); - - if let Err(err) = result { - eprintln!("Failed to write to html: {}", err); - } + html += &index.pre_main; let _ = renderer.render_to(&mut html, &vdom); - if let Err(err) = write!( - &mut html, - r#"
- - - "# - ) { - eprintln!("Failed to write to html: {}", err); - } + html += &index.post_main; html } diff --git a/packages/server/src/serve.rs b/packages/server/src/serve.rs index ed059f47f..6284a6f1b 100644 --- a/packages/server/src/serve.rs +++ b/packages/server/src/serve.rs @@ -1,12 +1,15 @@ +use std::fs::File; +use std::io::Read; +use std::path::PathBuf; + use dioxus_core::Component; #[derive(Clone)] pub struct ServeConfigBuilder { pub(crate) app: Component

, pub(crate) props: P, - pub(crate) application_name: Option<&'static str>, - pub(crate) base_path: Option<&'static str>, - pub(crate) head: Option<&'static str>, + pub(crate) root_id: Option<&'static str>, + pub(crate) index_path: Option<&'static str>, pub(crate) assets_path: Option<&'static str>, } @@ -16,32 +19,25 @@ impl ServeConfigBuilder

{ Self { app, props, - application_name: None, - base_path: None, - head: None, + root_id: None, + index_path: None, assets_path: None, } } - /// Set the application name matching the name in the Dioxus.toml file used to build the application - pub fn application_name(mut self, application_name: &'static str) -> Self { - self.application_name = Some(application_name); + /// 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); self } - /// Set the path the WASM application will be served under - pub fn base_path(mut self, base_path: &'static str) -> Self { - self.base_path = Some(base_path); + /// Set the id of the root element in the index.html file to place the prerendered content into. (defaults to main) + pub fn root_id(mut self, root_id: &'static str) -> Self { + self.root_id = Some(root_id); self } - /// Set the head content to be included in the HTML document served - pub fn head(mut self, head: &'static str) -> Self { - self.head = Some(head); - self - } - - /// Set the path of the assets folder generated by the Dioxus CLI. (defaults to dist/assets) + /// Set the path of the assets folder generated by the Dioxus CLI. (defaults to dist) pub fn assets_path(mut self, assets_path: &'static str) -> Self { self.assets_path = Some(assets_path); self @@ -49,31 +45,61 @@ impl ServeConfigBuilder

{ /// Build the ServeConfig pub fn build(self) -> ServeConfig

{ - let base_path = self.base_path.unwrap_or("."); - let application_name = self.application_name.unwrap_or("dioxus"); + let assets_path = self.assets_path.unwrap_or("dist"); + + let index_path = self + .index_path + .map(PathBuf::from) + .unwrap_or_else(|| format!("{assets_path}/index.html").into()); + + let root_id = self.root_id.unwrap_or("main"); + + let index = load_index_html(index_path, root_id); + ServeConfig { app: self.app, props: self.props, - application_name, - base_path, - head: self.head.map(String::from).unwrap_or(format!(r#"Dioxus Application - - - - - "#)), - assets_path: self.assets_path.unwrap_or("dist/assets"), + index, + assets_path, } } } +fn load_index_html(path: PathBuf, root_id: &'static str) -> IndexHtml { + let mut file = File::open(path).expect("Failed to find index.html. Make sure the index_path is set correctly and the WASM application has been built."); + + let mut contents = String::new(); + file.read_to_string(&mut contents) + .expect("Failed to read index.html"); + + let (pre_main, post_main) = contents.split_once(&format!("id=\"{root_id}\"")).unwrap_or_else(|| panic!("Failed to find id=\"{root_id}\" in index.html. The id is used to inject the application into the page.")); + + let post_main = post_main.split_once('>').unwrap_or_else(|| { + panic!("Failed to find closing > after id=\"{root_id}\" in index.html.") + }); + + let (pre_main, post_main) = ( + pre_main.to_string() + &format!("id=\"{root_id}\"") + post_main.0 + ">", + post_main.1.to_string(), + ); + + IndexHtml { + pre_main, + post_main, + } +} + +#[derive(Clone)] +pub(crate) struct IndexHtml { + pub(crate) pre_main: String, + pub(crate) post_main: String, +} + #[derive(Clone)] pub struct ServeConfig { pub(crate) app: Component

, pub(crate) props: P, - pub(crate) application_name: &'static str, - pub(crate) base_path: &'static str, - pub(crate) head: String, + pub(crate) index: IndexHtml, pub(crate) assets_path: &'static str, }