more fullstack cleanups: loaders serialize, ServeConfig only (#4785)
* some opinionated cleanups in dioxus-server * add serialize to loader * add `result` to Resource * clippy * fix * writable memos, resources, `onmount`, `.pending`, and `.value()` on actions * fix compile issues * fix typo
This commit is contained in:
1 parent
a3e40a5ea2
commit
7102bc3b6a
33 files changed
+2085
-672
No files matched your search
@@ -3,37 +3,51 @@
|
||||
|
||||
use dioxus_core::LaunchConfig;
|
||||
use std::any::Any;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::IncrementalRendererConfig;
|
||||
use crate::{IncrementalRendererConfig, IndexHtml};
|
||||
|
||||
#[allow(unused)]
|
||||
pub(crate) type ContextProviders = Arc<Vec<Box<dyn Fn() -> Box<dyn Any> + Send + Sync + 'static>>>;
|
||||
|
||||
/// 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, Default)]
|
||||
pub struct ServeConfigBuilder {
|
||||
pub struct ServeConfig {
|
||||
pub(crate) root_id: Option<&'static str>,
|
||||
pub(crate) index_html: Option<String>,
|
||||
pub(crate) index_path: Option<PathBuf>,
|
||||
pub(crate) index: IndexHtml,
|
||||
pub(crate) index_html_override: Option<String>,
|
||||
pub(crate) incremental: Option<IncrementalRendererConfig>,
|
||||
pub(crate) context_providers: Vec<Arc<dyn Fn() -> Box<dyn Any> + Send + Sync + 'static>>,
|
||||
pub(crate) streaming_mode: StreamingMode,
|
||||
}
|
||||
|
||||
impl LaunchConfig for ServeConfigBuilder {}
|
||||
/// The streaming mode to use while rendering the page
|
||||
#[derive(Clone, Copy, Default, PartialEq)]
|
||||
pub enum StreamingMode {
|
||||
/// Streaming is disabled; all server futures should be resolved before hydrating the page on the client
|
||||
#[default]
|
||||
Disabled,
|
||||
|
||||
impl ServeConfigBuilder {
|
||||
/// Create a new ServeConfigBuilder with incremental static generation disabled and the default index.html settings
|
||||
/// Out of order streaming is enabled; server futures are resolved out of order and streamed to the client
|
||||
/// as they resolve
|
||||
OutOfOrder,
|
||||
}
|
||||
|
||||
impl LaunchConfig for ServeConfig {}
|
||||
|
||||
impl ServeConfig {
|
||||
/// Create a new ServeConfig with incremental static generation disabled and the default index.html settings.
|
||||
pub fn builder() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
|
||||
/// Create a new ServeConfig with incremental static generation disabled and the default index.html settings
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
root_id: None,
|
||||
index_html: None,
|
||||
index_path: None,
|
||||
incremental: None,
|
||||
index: IndexHtml::default(),
|
||||
index_html_override: None,
|
||||
context_providers: Default::default(),
|
||||
streaming_mode: StreamingMode::default(),
|
||||
}
|
||||
@@ -49,7 +63,7 @@ impl ServeConfigBuilder {
|
||||
/// // Finally, launch the app with the config
|
||||
/// LaunchBuilder::new()
|
||||
/// // Only set the server config if the server feature is enabled
|
||||
/// .with_cfg(server_only!(dioxus_server::ServeConfigBuilder::default().incremental(dioxus_server::IncrementalRendererConfig::default())))
|
||||
/// .with_cfg(server_only!(dioxus_server::ServeConfig::default().incremental(dioxus_server::IncrementalRendererConfig::default())))
|
||||
/// .launch(app);
|
||||
/// ```
|
||||
pub fn incremental(mut self, cfg: IncrementalRendererConfig) -> Self {
|
||||
@@ -57,15 +71,9 @@ impl ServeConfigBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the contents of the index.html file to be served. (precedence over index_path)
|
||||
/// Set the contents of the index.html file to be served.
|
||||
pub fn index_html(mut self, index_html: String) -> Self {
|
||||
self.index_html = Some(index_html);
|
||||
self
|
||||
}
|
||||
|
||||
/// 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: PathBuf) -> Self {
|
||||
self.index_path = Some(index_path);
|
||||
self.index_html_override = Some(index_html);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -96,7 +104,7 @@ impl ServeConfigBuilder {
|
||||
/// LaunchBuilder::new()
|
||||
/// // Only set the server config if the server feature is enabled
|
||||
/// .with_cfg(server_only! {
|
||||
/// dioxus_server::ServeConfigBuilder::default().root_id("app")
|
||||
/// dioxus_server::ServeConfig::default().root_id("app")
|
||||
/// })
|
||||
/// // You also need to set the root id in your web config
|
||||
/// .with_cfg(web! {
|
||||
@@ -134,12 +142,11 @@ impl ServeConfigBuilder {
|
||||
///
|
||||
/// #[cfg(feature = "server")]
|
||||
/// dioxus_server::serve(|| async move {
|
||||
/// use dioxus_server::{axum, ServeConfigBuilder, DioxusRouterExt};
|
||||
/// use dioxus_server::{axum, ServeConfig, DioxusRouterExt};
|
||||
///
|
||||
/// let config = ServeConfigBuilder::default()
|
||||
/// let config = ServeConfig::default()
|
||||
/// // You can provide context to your whole app on the server (including server functions) with the `context_provider` method on the launch builder
|
||||
/// .context_providers(Arc::new(vec![Box::new(|| Box::new(1234u32) as Box<dyn Any>) as Box<dyn Fn() -> Box<dyn Any> + Send + Sync>]))
|
||||
/// .build()?;
|
||||
/// .context_providers(Arc::new(vec![Box::new(|| Box::new(1234u32) as Box<dyn Any>) as Box<dyn Fn() -> Box<dyn Any> + Send + Sync>]));
|
||||
///
|
||||
/// Ok(
|
||||
/// axum::Router::new()
|
||||
@@ -192,12 +199,11 @@ impl ServeConfigBuilder {
|
||||
///
|
||||
/// #[cfg(feature = "server")]
|
||||
/// dioxus_server::serve(|| async move {
|
||||
/// use dioxus_server::{axum, ServeConfigBuilder, DioxusRouterExt};
|
||||
/// use dioxus_server::{axum, ServeConfig, DioxusRouterExt};
|
||||
///
|
||||
/// let config = ServeConfigBuilder::default()
|
||||
/// let config = ServeConfig::default()
|
||||
/// // You can provide context to your whole app on the server (including server functions) with the `context_provider` method on the launch builder
|
||||
/// .context_provider(|| 1234u32)
|
||||
/// .build()?;
|
||||
/// .context_provider(|| 1234u32);
|
||||
///
|
||||
/// Ok(
|
||||
/// axum::Router::new()
|
||||
@@ -243,12 +249,12 @@ impl ServeConfigBuilder {
|
||||
/// // Run a custom server with axum on the server
|
||||
/// #[cfg(feature = "server")]
|
||||
/// dioxus_server::serve(|| async move {
|
||||
/// use dioxus_server::{axum, ServeConfigBuilder, DioxusRouterExt};
|
||||
/// use dioxus_server::{axum, ServeConfig, DioxusRouterExt};
|
||||
///
|
||||
/// let config = ServeConfig::default()
|
||||
/// // You can provide context to your whole app on the server (including server functions) with the `context_provider` method on the launch builder
|
||||
/// .context(1234u32);
|
||||
///
|
||||
/// let config = ServeConfigBuilder::default()
|
||||
/// .context(1234u32)
|
||||
/// .build()?;
|
||||
//
|
||||
/// Ok(
|
||||
/// axum::Router::new()
|
||||
/// .serve_dioxus_application(config, app)
|
||||
@@ -307,188 +313,4 @@ impl ServeConfigBuilder {
|
||||
self.streaming_mode = StreamingMode::OutOfOrder;
|
||||
self
|
||||
}
|
||||
|
||||
/// Build the ServeConfig. This may fail if the index.html file is not found.
|
||||
///
|
||||
/// ## WASM compatibility
|
||||
/// In the context of WASM the file system normally can't be read so this always requires
|
||||
/// the `index_html` field to be set.
|
||||
pub fn build(self) -> Result<ServeConfig, UnableToLoadIndex> {
|
||||
let root_id = self.root_id.unwrap_or("main");
|
||||
|
||||
let index_html = match self.index_html {
|
||||
Some(index) => index,
|
||||
None => {
|
||||
// The CLI always bundles static assets into the exe/public directory
|
||||
let public_path = public_path();
|
||||
|
||||
let index_path = self
|
||||
.index_path
|
||||
.unwrap_or_else(|| public_path.join("index.html"));
|
||||
load_index_path(index_path).unwrap_or_else(|_| {
|
||||
r#"
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head> </head>
|
||||
<body>
|
||||
<div id="main"></div>
|
||||
</body>
|
||||
</html>
|
||||
"#
|
||||
.to_string()
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
let index = load_index_html(index_html, root_id);
|
||||
let context_providers = Arc::new(
|
||||
self.context_providers
|
||||
.into_iter()
|
||||
.map(|f| {
|
||||
Box::new(move || f()) as Box<dyn Fn() -> Box<dyn std::any::Any> + Send + Sync>
|
||||
})
|
||||
.collect(),
|
||||
);
|
||||
|
||||
Ok(ServeConfig {
|
||||
index,
|
||||
incremental: self.incremental,
|
||||
context_providers,
|
||||
streaming_mode: self.streaming_mode,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryInto<ServeConfig> for ServeConfigBuilder {
|
||||
type Error = UnableToLoadIndex;
|
||||
|
||||
fn try_into(self) -> Result<ServeConfig, Self::Error> {
|
||||
self.build()
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the path to the public assets directory to serve static files from
|
||||
pub(crate) fn public_path() -> PathBuf {
|
||||
if let Ok(path) = std::env::var("DIOXUS_PUBLIC_PATH") {
|
||||
return PathBuf::from(path);
|
||||
}
|
||||
|
||||
// The CLI always bundles static assets into the exe/public directory
|
||||
std::env::current_exe()
|
||||
.expect("Failed to get current executable path")
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join("public")
|
||||
}
|
||||
|
||||
/// An error that can occur when loading the index.html file
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UnableToLoadIndex(PathBuf);
|
||||
|
||||
impl std::fmt::Display for UnableToLoadIndex {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Failed to find index.html. Make sure the index_path is set correctly and the WASM application has been built. Tried to open file at path: {:?}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for UnableToLoadIndex {}
|
||||
|
||||
fn load_index_path(path: PathBuf) -> Result<String, UnableToLoadIndex> {
|
||||
let mut file = File::open(&path).map_err(|_| UnableToLoadIndex(path))?;
|
||||
|
||||
let mut contents = String::new();
|
||||
file.read_to_string(&mut contents)
|
||||
.expect("Failed to read index.html");
|
||||
Ok(contents)
|
||||
}
|
||||
|
||||
fn load_index_html(contents: String, root_id: &'static str) -> IndexHtml {
|
||||
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(),
|
||||
);
|
||||
|
||||
let (head, close_head) = pre_main.split_once("</head>").unwrap_or_else(|| {
|
||||
panic!("Failed to find closing </head> tag after id=\"{root_id}\" in index.html.")
|
||||
});
|
||||
let (head, close_head) = (head.to_string(), "</head>".to_string() + close_head);
|
||||
|
||||
let (post_main, after_closing_body_tag) =
|
||||
post_main.split_once("</body>").unwrap_or_else(|| {
|
||||
panic!("Failed to find closing </body> tag after id=\"{root_id}\" in index.html.")
|
||||
});
|
||||
|
||||
// Strip out the head if it exists
|
||||
let mut head_before_title = String::new();
|
||||
let mut head_after_title = head;
|
||||
let mut title = String::new();
|
||||
if let Some((new_head_before_title, new_title)) = head_after_title.split_once("<title>") {
|
||||
let (new_title, new_head_after_title) = new_title
|
||||
.split_once("</title>")
|
||||
.expect("Failed to find closing </title> tag after <title> in index.html.");
|
||||
title = format!("<title>{new_title}</title>");
|
||||
head_before_title = new_head_before_title.to_string();
|
||||
head_after_title = new_head_after_title.to_string();
|
||||
}
|
||||
|
||||
IndexHtml {
|
||||
head_before_title,
|
||||
head_after_title,
|
||||
title,
|
||||
close_head,
|
||||
post_main: post_main.to_string(),
|
||||
after_closing_body_tag: "</body>".to_string() + after_closing_body_tag,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct IndexHtml {
|
||||
pub(crate) head_before_title: String,
|
||||
pub(crate) head_after_title: String,
|
||||
pub(crate) title: String,
|
||||
pub(crate) close_head: String,
|
||||
pub(crate) post_main: String,
|
||||
pub(crate) after_closing_body_tag: String,
|
||||
}
|
||||
|
||||
/// The streaming mode to use while rendering the page
|
||||
#[derive(Clone, Copy, Default, PartialEq)]
|
||||
pub enum StreamingMode {
|
||||
/// Streaming is disabled; all server futures should be resolved before hydrating the page on the client
|
||||
#[default]
|
||||
Disabled,
|
||||
/// Out of order streaming is enabled; server futures are resolved out of order and streamed to the client
|
||||
/// as they resolve
|
||||
OutOfOrder,
|
||||
}
|
||||
|
||||
/// 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`].
|
||||
/// See [`ServeConfigBuilder`] to create a ServeConfig
|
||||
#[derive(Clone)]
|
||||
pub struct ServeConfig {
|
||||
pub(crate) index: IndexHtml,
|
||||
pub(crate) incremental: Option<IncrementalRendererConfig>,
|
||||
pub(crate) context_providers: ContextProviders,
|
||||
pub(crate) streaming_mode: StreamingMode,
|
||||
}
|
||||
|
||||
impl LaunchConfig for ServeConfig {}
|
||||
|
||||
impl ServeConfig {
|
||||
/// Create a new ServeConfig
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub fn new() -> Result<Self, UnableToLoadIndex> {
|
||||
ServeConfigBuilder::new().build()
|
||||
}
|
||||
|
||||
/// Create a new builder for a ServeConfig
|
||||
pub fn builder() -> ServeConfigBuilder {
|
||||
ServeConfigBuilder::new()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
use anyhow::Context;
|
||||
|
||||
/// An `IndexHtml` represents the contents of an `index.html` file used to serve a web application.
|
||||
///
|
||||
/// This defines the static portion of your web application, typically generated by a tool like `dx`
|
||||
/// in conjunction with wasm-bindgen.
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct IndexHtml {
|
||||
pub(crate) head_before_title: String,
|
||||
pub(crate) head_after_title: String,
|
||||
pub(crate) title: String,
|
||||
pub(crate) close_head: String,
|
||||
pub(crate) post_main: String,
|
||||
pub(crate) after_closing_body_tag: String,
|
||||
}
|
||||
|
||||
impl IndexHtml {
|
||||
/// Create a new `IndexHtml` from the raw contents of an `index.html` file.
|
||||
///
|
||||
/// This function will parse the `index.html` and split it into sections for easier manipulation.
|
||||
/// The `root_id` parameter specifies the id of the main application container (e.g., "main")
|
||||
/// which your app will render into.
|
||||
pub(crate) fn new(contents: &str, root_id: &'static str) -> Result<IndexHtml, anyhow::Error> {
|
||||
let (pre_main, post_main) = contents.split_once(&format!("id=\"{root_id}\""))
|
||||
.with_context(|| format!("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('>').with_context(|| {
|
||||
format!("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(),
|
||||
);
|
||||
|
||||
let (head, close_head) = pre_main.split_once("</head>").with_context(|| {
|
||||
format!("Failed to find closing </head> tag after id=\"{root_id}\" in index.html.")
|
||||
})?;
|
||||
|
||||
let (head, close_head) = (head.to_string(), "</head>".to_string() + close_head);
|
||||
|
||||
let (post_main, after_closing_body_tag) =
|
||||
post_main.split_once("</body>").with_context(|| {
|
||||
format!("Failed to find closing </body> tag after id=\"{root_id}\" in index.html.")
|
||||
})?;
|
||||
|
||||
// Strip out the head if it exists
|
||||
let mut head_before_title = String::new();
|
||||
let mut head_after_title = head;
|
||||
let mut title = String::new();
|
||||
if let Some((new_head_before_title, new_title)) = head_after_title.split_once("<title>") {
|
||||
let (new_title, new_head_after_title) = new_title
|
||||
.split_once("</title>")
|
||||
.context("Failed to find closing </title> tag after <title> in index.html.")?;
|
||||
title = format!("<title>{new_title}</title>");
|
||||
head_before_title = new_head_before_title.to_string();
|
||||
head_after_title = new_head_after_title.to_string();
|
||||
}
|
||||
|
||||
Ok(IndexHtml {
|
||||
head_before_title,
|
||||
head_after_title,
|
||||
title,
|
||||
close_head,
|
||||
post_main: post_main.to_string(),
|
||||
after_closing_body_tag: "</body>".to_string() + after_closing_body_tag,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for IndexHtml {
|
||||
fn default() -> Self {
|
||||
const DEFAULT: &str = r#"<!DOCTYPE html>
|
||||
<html>
|
||||
<head> </head>
|
||||
<body>
|
||||
<div id="main"></div>
|
||||
</body>
|
||||
</html>"#;
|
||||
|
||||
Self::new(DEFAULT, "main").expect("Failed to load default index.html")
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
//! A launch function that creates an axum router for the LaunchBuilder
|
||||
|
||||
use crate::{server::DioxusRouterExt, RenderHandleState, ServeConfig, ServeConfigBuilder};
|
||||
use crate::IndexHtml;
|
||||
use crate::{server::DioxusRouterExt, RenderHandleState, ServeConfig};
|
||||
use anyhow::Context;
|
||||
use axum::{
|
||||
body::Body,
|
||||
extract::{Request, State},
|
||||
response::IntoResponse,
|
||||
routing::IntoMakeService,
|
||||
Router,
|
||||
};
|
||||
use dioxus_cli_config::base_path;
|
||||
use dioxus_core::Element;
|
||||
@@ -19,11 +21,11 @@ use hyper_util::{
|
||||
rt::{TokioExecutor, TokioIo},
|
||||
service::TowerToHyperService,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use std::{any::Any, collections::HashMap, net::SocketAddr, prelude::rust_2024::Future};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_util::task::LocalPoolHandle;
|
||||
use tower::Service;
|
||||
use tower::ServiceExt as _;
|
||||
use tower::{Service, ServiceExt as _};
|
||||
|
||||
type ContextList = Vec<Box<dyn Fn() -> Box<dyn Any> + Send + Sync>>;
|
||||
|
||||
@@ -55,34 +57,32 @@ async fn serve_server(
|
||||
|
||||
dioxus_devtools::connect(move |msg| _ = devtools_tx.unbounded_send(msg));
|
||||
|
||||
let platform_config = platform_config
|
||||
let mut cfg = platform_config
|
||||
.into_iter()
|
||||
.find_map(|cfg| {
|
||||
cfg.downcast::<ServeConfig>()
|
||||
.map(|cfg| Result::Ok(*cfg))
|
||||
.or_else(|cfg| {
|
||||
cfg.downcast::<ServeConfigBuilder>()
|
||||
.map(|builder| builder.build())
|
||||
})
|
||||
.ok()
|
||||
})
|
||||
.find_map(|cfg| cfg.downcast::<ServeConfig>().ok().map(|b| *b))
|
||||
.unwrap_or_else(ServeConfig::new);
|
||||
|
||||
// Extend the config's context providers with the context providers from the launch builder
|
||||
let cfg = platform_config
|
||||
.map(|mut cfg| {
|
||||
let mut contexts = contexts;
|
||||
let cfg_context_providers = cfg.context_providers.clone();
|
||||
for i in 0..cfg_context_providers.len() {
|
||||
contexts.push(Box::new({
|
||||
let cfg_context_providers = cfg_context_providers.clone();
|
||||
move || (cfg_context_providers[i])()
|
||||
}));
|
||||
}
|
||||
cfg.context_providers = std::sync::Arc::new(contexts);
|
||||
cfg
|
||||
})
|
||||
.unwrap();
|
||||
for ctx in contexts {
|
||||
let arced = Arc::new(ctx) as Arc<dyn Fn() -> Box<dyn Any> + Send + Sync>;
|
||||
cfg.context_providers.push(arced);
|
||||
}
|
||||
|
||||
// Create the IndexHtml object if the user has specified an override
|
||||
if let Some(raw_index_html) = cfg.index_html_override.as_ref() {
|
||||
cfg.index = IndexHtml::new(raw_index_html, cfg.root_id.unwrap_or("main"))
|
||||
.expect("Failed to parse custom index.html");
|
||||
} else if !cfg!(target_arch = "wasm32") {
|
||||
let public_path = crate::public_path();
|
||||
let index_html_path = public_path.join("index.html");
|
||||
|
||||
if index_html_path.exists() {
|
||||
let index_html = std::fs::read_to_string(index_html_path)
|
||||
.expect("Failed to read index.html from public directory");
|
||||
cfg.index = IndexHtml::new(&index_html, cfg.root_id.unwrap_or("main"))
|
||||
.expect("Failed to parse index.html from public directory");
|
||||
}
|
||||
}
|
||||
|
||||
// Get the address the server should run on. If the CLI is running, the CLI proxies fullstack into the main address
|
||||
// and we use the generated address the CLI gives us
|
||||
@@ -90,7 +90,7 @@ async fn serve_server(
|
||||
|
||||
// Create the router and register the server functions under the basepath.
|
||||
let router = apply_base_path(
|
||||
axum::Router::new().serve_dioxus_application(cfg.clone(), original_root),
|
||||
Router::new().serve_dioxus_application(cfg.clone(), original_root),
|
||||
original_root,
|
||||
cfg.clone(),
|
||||
base_path().map(|s| s.to_string()),
|
||||
@@ -101,6 +101,7 @@ async fn serve_server(
|
||||
.map(usize::from)
|
||||
.unwrap_or(1),
|
||||
);
|
||||
|
||||
let mut make_service = router.into_make_service();
|
||||
|
||||
let listener = tokio::net::TcpListener::bind(address).await.unwrap();
|
||||
@@ -136,7 +137,7 @@ async fn serve_server(
|
||||
let tcp_stream = TokioIo::new(tcp_stream);
|
||||
|
||||
std::future::poll_fn(|cx| {
|
||||
<IntoMakeService<axum::Router> as tower::Service<Request>>::poll_ready(
|
||||
<IntoMakeService<Router> as tower::Service<Request>>::poll_ready(
|
||||
&mut make_service,
|
||||
cx,
|
||||
)
|
||||
@@ -189,8 +190,8 @@ async fn serve_server(
|
||||
|
||||
unsafe { dioxus_devtools::subsecond::apply_patch(table).unwrap() };
|
||||
|
||||
let mut new_router = axum::Router::new().serve_static_assets();
|
||||
let new_cfg = ServeConfig::new().unwrap();
|
||||
let mut new_router = Router::new().serve_static_assets();
|
||||
let new_cfg = ServeConfig::new();
|
||||
|
||||
let server_fn_iter = ServerFunction::collect();
|
||||
|
||||
@@ -250,58 +251,69 @@ async fn serve_server(
|
||||
}
|
||||
|
||||
fn apply_base_path(
|
||||
mut router: axum::Router,
|
||||
root: fn() -> Result<VNode, RenderError>,
|
||||
mut router: Router,
|
||||
root: fn() -> Element,
|
||||
cfg: ServeConfig,
|
||||
base_path: Option<String>,
|
||||
) -> axum::Router {
|
||||
) -> Router {
|
||||
if let Some(base_path) = base_path {
|
||||
let base_path = base_path.trim_matches('/');
|
||||
|
||||
// If there is a base path, nest the router under it and serve the root route manually
|
||||
// Nesting a route in axum only serves /base_path or /base_path/ not both
|
||||
router = axum::Router::new().nest(&format!("/{base_path}/"), router);
|
||||
|
||||
async fn root_render_handler(
|
||||
state: State<RenderHandleState>,
|
||||
mut request: Request<Body>,
|
||||
) -> impl IntoResponse {
|
||||
// The root of the base path always looks like the root from dioxus fullstack
|
||||
*request.uri_mut() = "/".parse().unwrap();
|
||||
RenderHandleState::render_handler(state, request).await
|
||||
}
|
||||
|
||||
router = router.route(
|
||||
router = Router::new().nest(&format!("/{base_path}/"), router).route(
|
||||
&format!("/{base_path}"),
|
||||
axum::routing::method_routing::get(root_render_handler)
|
||||
.with_state(RenderHandleState::new(cfg, root)),
|
||||
axum::routing::method_routing::get(
|
||||
|state: State<RenderHandleState>, mut request: Request<Body>| async move {
|
||||
// The root of the base path always looks like the root from dioxus fullstack
|
||||
*request.uri_mut() = "/".parse().unwrap();
|
||||
RenderHandleState::render_handler(state, request).await
|
||||
},
|
||||
)
|
||||
.with_state(RenderHandleState::new(cfg, root)),
|
||||
)
|
||||
}
|
||||
|
||||
router
|
||||
}
|
||||
|
||||
pub fn serve<F>(mut serve_it: impl FnMut() -> F)
|
||||
/// Serve a fullstack dioxus application with a custom axum router.
|
||||
///
|
||||
/// This function sets up an async runtime, enables the default dioxus logger, runs the provided initializer,
|
||||
/// and then starts an axum server with the returned router.
|
||||
///
|
||||
/// The axum router will be bound to the address specified by the `IP` and `PORT` environment variables,
|
||||
/// defaulting to `127.0.0.1:8080` if not set.
|
||||
pub fn serve<F>(mut serve_it: impl FnMut() -> F) -> !
|
||||
where
|
||||
F: Future<Output = Result<axum::Router, anyhow::Error>>,
|
||||
F: Future<Output = Result<Router, anyhow::Error>>,
|
||||
{
|
||||
dioxus_logger::initialize_default();
|
||||
|
||||
tokio::runtime::Runtime::new()
|
||||
.unwrap()
|
||||
.block_on(async move {
|
||||
let router = serve_it().await.unwrap();
|
||||
let app_future = async move {
|
||||
let router = serve_it().await.expect("Failed to create axum router");
|
||||
let address = dioxus_cli_config::fullstack_address_or_localhost();
|
||||
let listener = tokio::net::TcpListener::bind(address)
|
||||
.await
|
||||
.with_context(|| format!("Failed to bind app to given address: {address}"))
|
||||
.unwrap();
|
||||
tracing::trace!("Listening on {address}");
|
||||
axum::serve::serve(listener, router)
|
||||
.await
|
||||
.expect("Failed to serve axum app");
|
||||
};
|
||||
|
||||
let address = dioxus_cli_config::fullstack_address_or_localhost();
|
||||
let listener = tokio::net::TcpListener::bind(address).await.unwrap();
|
||||
if let Ok(handle) = tokio::runtime::Handle::try_current() {
|
||||
handle.block_on(app_future);
|
||||
} else {
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
.block_on(app_future);
|
||||
}
|
||||
|
||||
tracing::trace!("Listening on {address}");
|
||||
|
||||
axum::serve::serve(listener, router.into_make_service())
|
||||
.await
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
// unreachable!("Serving a fullstack app should never return")
|
||||
unreachable!("Serving a fullstack app should never return")
|
||||
}
|
||||
|
||||
/// Create a router that serves the dioxus application at the appropriate base path.
|
||||
@@ -315,10 +327,10 @@ where
|
||||
/// - Hot-reloading
|
||||
/// - Async Runtime
|
||||
/// - Logging
|
||||
pub fn router(app: fn() -> Element) -> axum::Router {
|
||||
let cfg = ServeConfig::new().unwrap();
|
||||
pub fn router(app: fn() -> Element) -> Router {
|
||||
let cfg = ServeConfig::new();
|
||||
apply_base_path(
|
||||
axum::Router::new().serve_dioxus_application(cfg.clone(), app),
|
||||
Router::new().serve_dioxus_application(cfg.clone(), app),
|
||||
app,
|
||||
cfg,
|
||||
base_path().map(|s| s.to_string()),
|
||||
|
||||
@@ -11,9 +11,8 @@ pub use bytes::Bytes;
|
||||
pub use dioxus_fullstack_core::{ServerFnError, ServerFnResult};
|
||||
|
||||
pub use axum;
|
||||
pub(crate) use config::*;
|
||||
pub use config::ServeConfig;
|
||||
pub use config::*;
|
||||
pub use config::{ServeConfig, ServeConfigBuilder};
|
||||
pub use document::ServerDocument;
|
||||
pub use http;
|
||||
pub use inventory;
|
||||
@@ -46,3 +45,6 @@ pub use serverfn::*;
|
||||
|
||||
pub mod isrg;
|
||||
pub use isrg::*;
|
||||
|
||||
mod index_html;
|
||||
pub(crate) use index_html::IndexHtml;
|
||||
@@ -2,20 +2,17 @@ use crate::{
|
||||
ssr::{SSRError, SsrRendererPool},
|
||||
ServeConfig, ServerFunction,
|
||||
};
|
||||
use crate::{IncrementalRendererError, RenderFreshness};
|
||||
use axum::{
|
||||
body,
|
||||
body::Body,
|
||||
extract::State,
|
||||
http::{Request, Response, StatusCode},
|
||||
http::{Request, StatusCode},
|
||||
response::IntoResponse,
|
||||
response::Response,
|
||||
routing::*,
|
||||
};
|
||||
use dioxus_core::{Element, VirtualDom};
|
||||
use dioxus_fullstack_core::HttpError;
|
||||
use futures::Stream;
|
||||
use http::{header::*, request::Parts};
|
||||
use std::path::Path;
|
||||
use http::header::*;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use tower::util::MapResponse;
|
||||
use tower::ServiceExt;
|
||||
@@ -46,10 +43,9 @@ use tower_http::services::fs::ServeFileSystemResponseBody;
|
||||
/// // Note you can use `register_server_functions_with_context`
|
||||
/// // to inject the context into server functions running outside
|
||||
/// // of an SSR render context.
|
||||
/// .fallback(get(render_handler)
|
||||
/// .with_state(RenderHandleState::new(ServeConfig::new().unwrap(), app))
|
||||
/// )
|
||||
/// .into_make_service();
|
||||
/// .fallback(get(render_handler))
|
||||
/// .with_state(RenderHandleState::new(ServeConfig::new(), app));
|
||||
///
|
||||
/// let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
|
||||
/// axum::serve(listener, router).await.unwrap();
|
||||
/// }
|
||||
@@ -103,7 +99,7 @@ pub trait DioxusRouterExt<S>: DioxusRouterFnExt<S> {
|
||||
/// let addr = dioxus::cli_config::fullstack_address_or_localhost();
|
||||
/// let router = axum::Router::new()
|
||||
/// // Server side render the application, serve static assets, and register server functions
|
||||
/// .serve_dioxus_application(ServeConfig::new().unwrap(), app)
|
||||
/// .serve_dioxus_application(ServeConfig::new(), app)
|
||||
/// .into_make_service();
|
||||
/// let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
|
||||
/// axum::serve(listener, router).await.unwrap();
|
||||
@@ -124,7 +120,7 @@ where
|
||||
S: Send + Sync + Clone + 'static,
|
||||
{
|
||||
fn serve_static_assets(self) -> Self {
|
||||
let public_path = crate::public_path();
|
||||
let public_path = public_path();
|
||||
|
||||
if !public_path.exists() {
|
||||
return self;
|
||||
@@ -166,6 +162,7 @@ pub trait DioxusRouterFnExt<S> {
|
||||
fn register_server_functions(self) -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Serves a Dioxus application without static assets.
|
||||
/// Sets up server function routes and rendering endpoints only.
|
||||
///
|
||||
@@ -179,7 +176,7 @@ pub trait DioxusRouterFnExt<S> {
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let router = axum::Router::new()
|
||||
/// .serve_api_application(ServeConfig::new().unwrap(), app)
|
||||
/// .serve_api_application(ServeConfig::new(), app)
|
||||
/// .into_make_service();
|
||||
/// // ...
|
||||
/// }
|
||||
@@ -195,8 +192,14 @@ pub trait DioxusRouterFnExt<S> {
|
||||
|
||||
impl<S: Send + Sync + Clone + 'static> DioxusRouterFnExt<S> for Router<S> {
|
||||
fn register_server_functions(mut self) -> Self {
|
||||
for f in ServerFunction::collect() {
|
||||
self = f.register_server_fn_on_router(self);
|
||||
for func in ServerFunction::collect() {
|
||||
tracing::info!(
|
||||
"Registering server function: {} {}",
|
||||
func.method(),
|
||||
func.path()
|
||||
);
|
||||
|
||||
self = func.register_server_fn_on_router(self);
|
||||
}
|
||||
self
|
||||
}
|
||||
@@ -229,7 +232,8 @@ impl RenderHandleState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new [`RenderHandleState`] with a custom [`VirtualDom`] factory. This method can be used to pass context into the root component of your application.
|
||||
/// Create a new [`RenderHandleState`] with a custom [`VirtualDom`] factory. This method can be
|
||||
/// used to pass context into the root component of your application.
|
||||
pub fn new_with_virtual_dom_factory(
|
||||
config: ServeConfig,
|
||||
build_virtual_dom: impl Fn() -> VirtualDom + Send + Sync + 'static,
|
||||
@@ -272,36 +276,36 @@ impl RenderHandleState {
|
||||
/// // Note you can use `register_server_functions_with_context`
|
||||
/// // to inject the context into server functions running outside
|
||||
/// // of an SSR render context.
|
||||
/// .fallback(get(render_handler)
|
||||
/// .with_state(RenderHandleState::new(ServeConfig::new().unwrap(), app))
|
||||
/// )
|
||||
/// .fallback(get(render_handler))
|
||||
/// .with_state(RenderHandleState::new(ServeConfig::new(), app))
|
||||
/// .into_make_service();
|
||||
///
|
||||
/// let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
|
||||
/// axum::serve(listener, router).await.unwrap();
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn render_handler(
|
||||
State(state): State<Self>,
|
||||
request: Request<Body>,
|
||||
) -> impl IntoResponse {
|
||||
let cfg = &state.config;
|
||||
let build_virtual_dom = {
|
||||
let build_virtual_dom = state.build_virtual_dom.clone();
|
||||
let context_providers = state.config.context_providers.clone();
|
||||
move || {
|
||||
let mut vdom = build_virtual_dom();
|
||||
for state in context_providers.as_slice() {
|
||||
vdom.insert_any_root_context(state());
|
||||
}
|
||||
vdom
|
||||
}
|
||||
};
|
||||
|
||||
pub async fn render_handler(State(state): State<Self>, request: Request<Body>) -> Response {
|
||||
let (parts, _) = request.into_parts();
|
||||
|
||||
match state.render(parts, cfg, build_virtual_dom).await {
|
||||
let response = state
|
||||
.renderers
|
||||
.clone()
|
||||
.render_to(parts, &state.config, {
|
||||
let build_virtual_dom = state.build_virtual_dom.clone();
|
||||
let context_providers = state.config.context_providers.clone();
|
||||
move || {
|
||||
let mut vdom = build_virtual_dom();
|
||||
for state in context_providers.as_slice() {
|
||||
vdom.insert_any_root_context(state());
|
||||
}
|
||||
vdom
|
||||
}
|
||||
})
|
||||
.await;
|
||||
|
||||
match response {
|
||||
Ok((status, freshness, rx)) => {
|
||||
let mut response = axum::response::Response::builder()
|
||||
let mut response = Response::builder()
|
||||
.status(status.status)
|
||||
.header(CONTENT_TYPE, "text/html; charset=utf-8")
|
||||
.body(Body::from_stream(rx))
|
||||
@@ -309,15 +313,20 @@ impl RenderHandleState {
|
||||
|
||||
freshness.write(response.headers_mut());
|
||||
|
||||
Result::<http::Response<axum::body::Body>, StatusCode>::Ok(response)
|
||||
response
|
||||
}
|
||||
|
||||
Err(SSRError::Incremental(e)) => {
|
||||
tracing::error!("Failed to render page: {}", e);
|
||||
Ok(Self::err_to_axum_response(e).into_response())
|
||||
|
||||
Response::builder()
|
||||
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.body(e.to_string())
|
||||
.unwrap()
|
||||
.into_response()
|
||||
}
|
||||
|
||||
Err(SSRError::HttpError { status, message }) => Ok(Response::builder()
|
||||
Err(SSRError::HttpError { status, message }) => Response::builder()
|
||||
.status(status)
|
||||
.body(Body::from(message.unwrap_or_else(|| {
|
||||
status
|
||||
@@ -325,43 +334,26 @@ impl RenderHandleState {
|
||||
.unwrap_or("An unknown error occurred")
|
||||
.to_string()
|
||||
})))
|
||||
.unwrap()),
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
fn err_to_axum_response<E: std::fmt::Display>(e: E) -> Response<axum::body::Body> {
|
||||
Response::builder()
|
||||
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.body(body::Body::new(format!("Error: {}", e)))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Render the application to HTML.
|
||||
pub async fn render<'a>(
|
||||
&'a self,
|
||||
parts: Parts,
|
||||
cfg: &'a ServeConfig,
|
||||
virtual_dom_factory: impl FnOnce() -> VirtualDom + Send + Sync + 'static,
|
||||
) -> Result<
|
||||
(
|
||||
HttpError,
|
||||
RenderFreshness,
|
||||
impl Stream<Item = Result<String, IncrementalRendererError>>,
|
||||
),
|
||||
SSRError,
|
||||
> {
|
||||
self.renderers
|
||||
.clone()
|
||||
.render_to(parts, cfg, virtual_dom_factory)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
fn serve_dir_cached<S>(
|
||||
mut router: Router<S>,
|
||||
public_path: &std::path::Path,
|
||||
directory: &std::path::Path,
|
||||
) -> Router<S>
|
||||
/// Get the path to the public assets directory to serve static files from
|
||||
pub(crate) fn public_path() -> PathBuf {
|
||||
if let Ok(path) = std::env::var("DIOXUS_PUBLIC_PATH") {
|
||||
return PathBuf::from(path);
|
||||
}
|
||||
|
||||
// The CLI always bundles static assets into the exe/public directory
|
||||
std::env::current_exe()
|
||||
.expect("Failed to get current executable path")
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join("public")
|
||||
}
|
||||
|
||||
fn serve_dir_cached<S>(mut router: Router<S>, public_path: &Path, directory: &Path) -> Router<S>
|
||||
where
|
||||
S: Send + Sync + Clone + 'static,
|
||||
{
|
||||
@@ -376,8 +368,16 @@ where
|
||||
if path == public_path.join("index.html") {
|
||||
continue;
|
||||
}
|
||||
let route = path.strip_prefix(public_path).unwrap();
|
||||
let route = path_components_to_route_lossy(route);
|
||||
|
||||
let route = format!(
|
||||
"/{}",
|
||||
path.strip_prefix(public_path)
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|segment| segment.to_string_lossy())
|
||||
.collect::<Vec<_>>()
|
||||
.join("/")
|
||||
);
|
||||
|
||||
if path.is_dir() {
|
||||
router = serve_dir_cached(router, public_path, &path);
|
||||
@@ -397,15 +397,6 @@ where
|
||||
router
|
||||
}
|
||||
|
||||
fn path_components_to_route_lossy(path: &Path) -> String {
|
||||
let route = path
|
||||
.iter()
|
||||
.map(|segment| segment.to_string_lossy())
|
||||
.collect::<Vec<_>>()
|
||||
.join("/");
|
||||
format!("/{}", route)
|
||||
}
|
||||
|
||||
type MappedAxumService<S> = MapResponse<
|
||||
S,
|
||||
fn(Response<ServeFileSystemResponseBody>) -> Response<ServeFileSystemResponseBody>,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use axum::body::Body;
|
||||
use axum::handler::Handler;
|
||||
use axum::routing::MethodRouter;
|
||||
use axum::Router;
|
||||
use dashmap::DashMap;
|
||||
@@ -19,10 +18,6 @@ pub struct ServerFunction<Caller = ()> {
|
||||
_phantom: PhantomData<Caller>,
|
||||
}
|
||||
|
||||
pub struct MakeRequest<T> {
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl ServerFunction {
|
||||
/// Create a new server function object.
|
||||
pub const fn new(
|
||||
@@ -52,56 +47,14 @@ impl ServerFunction {
|
||||
inventory::iter::<ServerFunction>().collect()
|
||||
}
|
||||
|
||||
pub fn handler(&self) -> fn() -> MethodRouter<DioxusServerState> {
|
||||
self.handler
|
||||
}
|
||||
|
||||
pub fn register_server_fn_on_router<S>(&'static self, router: Router<S>) -> Router<S>
|
||||
where
|
||||
S: Send + Sync + Clone + 'static,
|
||||
{
|
||||
tracing::info!(
|
||||
"Registering server function: {} {}",
|
||||
self.method(),
|
||||
self.path()
|
||||
);
|
||||
use http::method::Method;
|
||||
let path = self.path();
|
||||
let method = self.method();
|
||||
let handler = move |req| self.handle_server_fns_inner(req);
|
||||
match method {
|
||||
Method::GET => router.route(path, axum::routing::get(handler)),
|
||||
Method::POST => router.route(path, axum::routing::post(handler)),
|
||||
Method::PUT => router.route(path, axum::routing::put(handler)),
|
||||
Method::DELETE => router.route(path, axum::routing::delete(handler)),
|
||||
Method::PATCH => router.route(path, axum::routing::patch(handler)),
|
||||
Method::HEAD => router.route(path, axum::routing::head(handler)),
|
||||
Method::OPTIONS => router.route(path, axum::routing::options(handler)),
|
||||
Method::CONNECT => router.route(path, axum::routing::connect(handler)),
|
||||
Method::TRACE => router.route(path, axum::routing::trace(handler)),
|
||||
_ => unimplemented!("Unsupported server function method: {}", method),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle_server_fns_inner(&self, req: http::Request<Body>) -> http::Response<Body> {
|
||||
// todo: jon
|
||||
// - bring back middleware to serverfns. the new layer system is fine but isn't a full replacement
|
||||
//
|
||||
// use axum::body;
|
||||
// use axum::extract::State;
|
||||
// use axum::routing::*;
|
||||
// use axum::{
|
||||
// body::Body,
|
||||
// http::{Request, Response, StatusCode},
|
||||
// response::IntoResponse,
|
||||
// };
|
||||
// use http::header::*;
|
||||
|
||||
// let (parts, body) = req.into_parts();
|
||||
// let req = Request::from_parts(parts.clone(), body);
|
||||
|
||||
// // Create the server context with info from the request
|
||||
// let server_context = DioxusServerContext::new(parts);
|
||||
|
||||
// // Provide additional context from the render state
|
||||
// server_context.add_server_context(&additional_context);
|
||||
|
||||
// // store Accepts and Referrer in case we need them for redirect (below)
|
||||
// let referrer = req.headers().get(REFERER).cloned();
|
||||
// let accepts_html = req
|
||||
@@ -111,13 +64,6 @@ impl ServerFunction {
|
||||
// .map(|v| v.contains("text/html"))
|
||||
// .unwrap_or(false);
|
||||
|
||||
// actually run the server fn (which may use the server context)
|
||||
// let fut = crate::with_server_context(server_context.clone(), || service.run(req));
|
||||
// let fut = crate::with_server_context(server_context.clone(), || service.run(req));
|
||||
|
||||
// let res = ProvideServerContext::new(fut, server_context.clone()).await;
|
||||
// let mut res = res.res;
|
||||
|
||||
// // it it accepts text/html (i.e., is a plain form post) and doesn't already have a
|
||||
// // Location set, then redirect to Referer
|
||||
// if accepts_html {
|
||||
@@ -130,13 +76,10 @@ impl ServerFunction {
|
||||
// }
|
||||
// }
|
||||
|
||||
// // apply the response parts from the server context to the response
|
||||
// server_context.send_response(&mut res);
|
||||
|
||||
let mthd: MethodRouter<DioxusServerState> =
|
||||
(self.handler)().with_state(DioxusServerState {});
|
||||
|
||||
mthd.call(req, DioxusServerState {}).await
|
||||
router.route(
|
||||
self.path(),
|
||||
((self.handler)()).with_state(DioxusServerState {}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -667,8 +667,6 @@ impl SsrRendererPool {
|
||||
to: &mut R,
|
||||
virtual_dom: &VirtualDom,
|
||||
) -> Result<(), IncrementalRendererError> {
|
||||
let ServeConfig { index, .. } = cfg;
|
||||
|
||||
let title = {
|
||||
let document: Option<Rc<ServerDocument>> =
|
||||
virtual_dom.in_scope(ScopeId::ROOT, dioxus_core::try_consume_context);
|
||||
@@ -676,13 +674,13 @@ impl SsrRendererPool {
|
||||
document.and_then(|document| document.title())
|
||||
};
|
||||
|
||||
to.write_str(&index.head_before_title)?;
|
||||
to.write_str(&cfg.index.head_before_title)?;
|
||||
if let Some(title) = title {
|
||||
to.write_str(&title)?;
|
||||
} else {
|
||||
to.write_str(&index.title)?;
|
||||
to.write_str(&cfg.index.title)?;
|
||||
}
|
||||
to.write_str(&index.head_after_title)?;
|
||||
to.write_str(&cfg.index.head_after_title)?;
|
||||
|
||||
let document =
|
||||
virtual_dom.in_scope(ScopeId::ROOT, try_consume_context::<Rc<ServerDocument>>);
|
||||
@@ -704,9 +702,7 @@ impl SsrRendererPool {
|
||||
cfg: &ServeConfig,
|
||||
to: &mut R,
|
||||
) -> Result<(), IncrementalRendererError> {
|
||||
let ServeConfig { index, .. } = cfg;
|
||||
|
||||
to.write_str(&index.close_head)?;
|
||||
to.write_str(&cfg.index.close_head)?;
|
||||
|
||||
// // #[cfg(feature = "document")]
|
||||
// {
|
||||
@@ -723,8 +719,6 @@ impl SsrRendererPool {
|
||||
to: &mut R,
|
||||
virtual_dom: &VirtualDom,
|
||||
) -> Result<(), IncrementalRendererError> {
|
||||
let ServeConfig { index, .. } = cfg;
|
||||
|
||||
// Collect the initial server data from the root node. For most apps, no use_server_futures will be resolved initially, so this will be full on `None`s.
|
||||
// Sending down those Nones are still important to tell the client not to run the use_server_futures that are already running on the backend
|
||||
let resolved_data = SsrRendererPool::serialize_server_data(virtual_dom, ScopeId::ROOT);
|
||||
@@ -749,7 +743,7 @@ impl SsrRendererPool {
|
||||
)?;
|
||||
}
|
||||
write!(to, r#"</script>"#,)?;
|
||||
to.write_str(&index.post_main)?;
|
||||
to.write_str(&cfg.index.post_main)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -759,9 +753,7 @@ impl SsrRendererPool {
|
||||
cfg: &ServeConfig,
|
||||
to: &mut R,
|
||||
) -> Result<(), IncrementalRendererError> {
|
||||
let ServeConfig { index, .. } = cfg;
|
||||
|
||||
to.write_str(&index.after_closing_body_tag)?;
|
||||
to.write_str(&cfg.index.after_closing_body_tag)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user