mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-27 14:40:44 +00:00
parse and inject pre rendered content to work with trunk
This commit is contained in:
parent
f96425e425
commit
9877dd7ed8
5 changed files with 132 additions and 85 deletions
|
@ -70,20 +70,48 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serve_dioxus_application<P: Clone + Send + Sync + 'static>(
|
fn serve_dioxus_application<P: Clone + Send + Sync + 'static>(
|
||||||
self,
|
mut self,
|
||||||
server_fn_route: &'static str,
|
server_fn_route: &'static str,
|
||||||
cfg: impl Into<ServeConfig<P>>,
|
cfg: impl Into<ServeConfig<P>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
use tower_http::services::ServeDir;
|
use tower_http::services::{ServeDir, ServeFile};
|
||||||
|
|
||||||
let cfg = cfg.into();
|
let cfg = cfg.into();
|
||||||
|
|
||||||
// Serve the dist folder and the index.html file
|
// Serve all files in dist folder except index.html
|
||||||
let serve_dir = ServeDir::new(cfg.assets_path);
|
let dir = std::fs::read_dir(cfg.assets_path).unwrap_or_else(|e| {
|
||||||
|
panic!(
|
||||||
|
"Couldn't read assets directory at {:?}: {}",
|
||||||
|
&cfg.assets_path, e
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
self.register_server_fns(server_fn_route)
|
for entry in dir.flatten() {
|
||||||
.nest_service("/assets", serve_dir)
|
let path = entry.path();
|
||||||
.route_service(
|
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::<Vec<_>>()
|
||||||
|
.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())),
|
get(render_handler).with_state((cfg, SSRState::default())),
|
||||||
)
|
)
|
||||||
|
|
|
@ -50,24 +50,50 @@ impl DioxusRouterExt for Router {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register_server_fns(self, server_fn_route: &'static str) -> Self {
|
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(),
|
server_context: DioxusServerContext::default(),
|
||||||
function: func,
|
function: func,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serve_dioxus_application<P: Clone + Send + Sync + 'static>(
|
fn serve_dioxus_application<P: Clone + Send + Sync + 'static>(
|
||||||
self,
|
mut self,
|
||||||
server_fn_route: &'static str,
|
server_fn_route: &'static str,
|
||||||
cfg: impl Into<ServeConfig<P>>,
|
cfg: impl Into<ServeConfig<P>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let cfg = cfg.into();
|
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::<Vec<_>>()
|
||||||
|
.join("/");
|
||||||
|
let route = format!("/{}/<**path>", route);
|
||||||
|
self = self.push(Router::with_path(route).get(serve_dir))
|
||||||
|
}
|
||||||
|
|
||||||
self.register_server_fns(server_fn_route)
|
self.register_server_fns(server_fn_route)
|
||||||
.push(Router::with_path("/").get(SSRHandler { cfg }))
|
.push(Router::with_path("/").get(SSRHandler { cfg }))
|
||||||
.push(Router::with_path("assets/<**path>").get(serve_dir))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -69,7 +69,7 @@ pub fn serve_dioxus_application<P: Clone + Send + Sync + 'static>(
|
||||||
.and(warp::get())
|
.and(warp::get())
|
||||||
.and(with_ssr_state())
|
.and(with_ssr_state())
|
||||||
.map(move |renderer: SSRState| warp::reply::html(renderer.render(&cfg))))
|
.map(move |renderer: SSRState| warp::reply::html(renderer.render(&cfg))))
|
||||||
.or(warp::path("assets").and(serve_dir))
|
.or(serve_dir)
|
||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
use std::fmt::Write;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use dioxus_core::VirtualDom;
|
use dioxus_core::VirtualDom;
|
||||||
|
@ -47,12 +46,7 @@ impl Default for SSRState {
|
||||||
impl SSRState {
|
impl SSRState {
|
||||||
pub fn render<P: 'static + Clone>(&self, cfg: &ServeConfig<P>) -> String {
|
pub fn render<P: 'static + Clone>(&self, cfg: &ServeConfig<P>) -> String {
|
||||||
let ServeConfig {
|
let ServeConfig {
|
||||||
app,
|
app, props, index, ..
|
||||||
application_name,
|
|
||||||
base_path,
|
|
||||||
head,
|
|
||||||
props,
|
|
||||||
..
|
|
||||||
} = cfg;
|
} = cfg;
|
||||||
|
|
||||||
let mut vdom = VirtualDom::new_with_props(*app, props.clone());
|
let mut vdom = VirtualDom::new_with_props(*app, props.clone());
|
||||||
|
@ -63,38 +57,11 @@ impl SSRState {
|
||||||
|
|
||||||
let mut html = String::new();
|
let mut html = String::new();
|
||||||
|
|
||||||
let result = write!(
|
html += &index.pre_main;
|
||||||
&mut html,
|
|
||||||
r#"
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>{head}
|
|
||||||
</head><body>
|
|
||||||
<div id="main">"#
|
|
||||||
);
|
|
||||||
|
|
||||||
if let Err(err) = result {
|
|
||||||
eprintln!("Failed to write to html: {}", err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let _ = renderer.render_to(&mut html, &vdom);
|
let _ = renderer.render_to(&mut html, &vdom);
|
||||||
|
|
||||||
if let Err(err) = write!(
|
html += &index.post_main;
|
||||||
&mut html,
|
|
||||||
r#"</div>
|
|
||||||
<script type="module">
|
|
||||||
import init from "/{base_path}/assets/dioxus/{application_name}.js";
|
|
||||||
init("/{base_path}/assets/dioxus/{application_name}_bg.wasm").then(wasm => {{
|
|
||||||
if (wasm.__wbindgen_start == undefined) {{
|
|
||||||
wasm.main();
|
|
||||||
}}
|
|
||||||
}});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>"#
|
|
||||||
) {
|
|
||||||
eprintln!("Failed to write to html: {}", err);
|
|
||||||
}
|
|
||||||
|
|
||||||
html
|
html
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::Read;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use dioxus_core::Component;
|
use dioxus_core::Component;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ServeConfigBuilder<P: Clone> {
|
pub struct ServeConfigBuilder<P: Clone> {
|
||||||
pub(crate) app: Component<P>,
|
pub(crate) app: Component<P>,
|
||||||
pub(crate) props: P,
|
pub(crate) props: P,
|
||||||
pub(crate) application_name: Option<&'static str>,
|
pub(crate) root_id: Option<&'static str>,
|
||||||
pub(crate) base_path: Option<&'static str>,
|
pub(crate) index_path: Option<&'static str>,
|
||||||
pub(crate) head: Option<&'static str>,
|
|
||||||
pub(crate) assets_path: Option<&'static str>,
|
pub(crate) assets_path: Option<&'static str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,32 +19,25 @@ impl<P: Clone> ServeConfigBuilder<P> {
|
||||||
Self {
|
Self {
|
||||||
app,
|
app,
|
||||||
props,
|
props,
|
||||||
application_name: None,
|
root_id: None,
|
||||||
base_path: None,
|
index_path: None,
|
||||||
head: None,
|
|
||||||
assets_path: None,
|
assets_path: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the application name matching the name in the Dioxus.toml file used to build the application
|
/// Set the path of the index.html file to be served. (defaults to {assets_path}/index.html)
|
||||||
pub fn application_name(mut self, application_name: &'static str) -> Self {
|
pub fn index_path(mut self, index_path: &'static str) -> Self {
|
||||||
self.application_name = Some(application_name);
|
self.index_path = Some(index_path);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the path the WASM application will be served under
|
/// Set the id of the root element in the index.html file to place the prerendered content into. (defaults to main)
|
||||||
pub fn base_path(mut self, base_path: &'static str) -> Self {
|
pub fn root_id(mut self, root_id: &'static str) -> Self {
|
||||||
self.base_path = Some(base_path);
|
self.root_id = Some(root_id);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the head content to be included in the HTML document served
|
/// Set the path of the assets folder generated by the Dioxus CLI. (defaults to dist)
|
||||||
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)
|
|
||||||
pub fn assets_path(mut self, assets_path: &'static str) -> Self {
|
pub fn assets_path(mut self, assets_path: &'static str) -> Self {
|
||||||
self.assets_path = Some(assets_path);
|
self.assets_path = Some(assets_path);
|
||||||
self
|
self
|
||||||
|
@ -49,31 +45,61 @@ impl<P: Clone> ServeConfigBuilder<P> {
|
||||||
|
|
||||||
/// Build the ServeConfig
|
/// Build the ServeConfig
|
||||||
pub fn build(self) -> ServeConfig<P> {
|
pub fn build(self) -> ServeConfig<P> {
|
||||||
let base_path = self.base_path.unwrap_or(".");
|
let assets_path = self.assets_path.unwrap_or("dist");
|
||||||
let application_name = self.application_name.unwrap_or("dioxus");
|
|
||||||
|
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 {
|
ServeConfig {
|
||||||
app: self.app,
|
app: self.app,
|
||||||
props: self.props,
|
props: self.props,
|
||||||
application_name,
|
index,
|
||||||
base_path,
|
assets_path,
|
||||||
head: self.head.map(String::from).unwrap_or(format!(r#"<title>Dioxus Application</title>
|
|
||||||
<link rel="preload" href="/{base_path}/assets/dioxus/{application_name}_bg.wasm" as="fetch" type="application/wasm" crossorigin="" />
|
|
||||||
<link rel="modulepreload" href="/{base_path}/assets/dioxus/{application_name}.js" />
|
|
||||||
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<meta charset="UTF-8" />"#)),
|
|
||||||
assets_path: self.assets_path.unwrap_or("dist/assets"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)]
|
#[derive(Clone)]
|
||||||
pub struct ServeConfig<P: Clone> {
|
pub struct ServeConfig<P: Clone> {
|
||||||
pub(crate) app: Component<P>,
|
pub(crate) app: Component<P>,
|
||||||
pub(crate) props: P,
|
pub(crate) props: P,
|
||||||
pub(crate) application_name: &'static str,
|
pub(crate) index: IndexHtml,
|
||||||
pub(crate) base_path: &'static str,
|
|
||||||
pub(crate) head: String,
|
|
||||||
pub(crate) assets_path: &'static str,
|
pub(crate) assets_path: &'static str,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue