Create a Static Site Generation platform; Deduplicate hot reloading code (#2226)
* create static site generation helpers in the router crate * work on integrating static site generation into fullstack * move ssg into a separate crate * integrate ssg with the launch builder * simplify ssg example * fix static_routes for child routes * move CLI hot reloading websocket code into dioxus-hot-reload * fix some unused imports * use the same hot reloading websocket code for fullstack * fix fullstack hot reloading * move cli hot reloading logic into the hot reload crate * ssg example working with dx serve * add more examples * fix clippy * fix formatting * fix hot reload doctest imports * fix axum imports * don't run server doc tests * Fix hot reload websocket doc examples
This commit is contained in:
1 parent
460b70e0f0
commit
245003a5d4
59 files changed
+1507
-791
No files matched your search
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "dioxus-fullstack"
|
||||
authors = ["Jonathan Kelley, Evan Almloff"]
|
||||
authors = ["Jonathan Kelley", "Evan Almloff"]
|
||||
version = { workspace = true }
|
||||
edition = "2021"
|
||||
description = "Fullstack Dioxus Utilities"
|
||||
@@ -62,7 +62,7 @@ dioxus-cli-config = { workspace = true, features = ["read-config"], optional = t
|
||||
tokio = { workspace = true, features = ["rt", "sync"], optional = true }
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
dioxus-hot-reload = { workspace = true }
|
||||
dioxus-hot-reload = { workspace = true, features = ["serve"] }
|
||||
tokio = { workspace = true, features = ["rt", "sync", "rt-multi-thread"], optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
@@ -70,13 +70,14 @@ dioxus = { workspace = true, features = ["fullstack"] }
|
||||
|
||||
[features]
|
||||
default = ["hot-reload"]
|
||||
hot-reload = ["serde_json"]
|
||||
hot-reload = ["serde_json", "dioxus-hot-reload/serve"]
|
||||
web = ["dioxus-web", "web-sys"]
|
||||
desktop = ["dioxus-desktop", "server_fn/reqwest", "dioxus_server_macro/reqwest"]
|
||||
mobile = ["dioxus-mobile"]
|
||||
default-tls = ["server_fn/default-tls"]
|
||||
rustls = ["server_fn/rustls"]
|
||||
axum = ["dep:axum", "tower-http", "server", "server_fn/axum", "dioxus_server_macro/axum"]
|
||||
static-site-generation = []
|
||||
server = [
|
||||
"server_fn/ssr",
|
||||
"dioxus_server_macro/server",
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
docs
|
||||
target
|
||||
static
|
||||
.dioxus
|
||||
@@ -1,20 +0,0 @@
|
||||
[package]
|
||||
name = "static-hydrated"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
dioxus-web = { workspace = true, features = ["hydrate"], optional = true }
|
||||
dioxus = { workspace = true }
|
||||
dioxus-fullstack = { workspace = true }
|
||||
dioxus-router = { workspace = true, features = ["fullstack"] }
|
||||
tokio = { workspace = true, features = ["full"], optional = true }
|
||||
serde = "1.0.159"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
server = ["tokio", "dioxus-fullstack/server"]
|
||||
web = ["dioxus-web"]
|
||||
@@ -1,46 +0,0 @@
|
||||
[application]
|
||||
|
||||
# App (Project) Name
|
||||
name = "Dioxus"
|
||||
|
||||
# Dioxus App Default Platform
|
||||
# desktop, web, mobile, ssr
|
||||
default_platform = "web"
|
||||
|
||||
# `build` & `serve` dist path
|
||||
out_dir = "docs"
|
||||
|
||||
# resource (public) file folder
|
||||
asset_dir = "public"
|
||||
|
||||
[web.app]
|
||||
|
||||
# HTML title tag content
|
||||
title = "dioxus | ⛺"
|
||||
|
||||
[web.watcher]
|
||||
|
||||
# when watcher trigger, regenerate the `index.html`
|
||||
reload_html = true
|
||||
|
||||
# which files or dirs will be watcher monitoring
|
||||
watch_path = ["src", "public"]
|
||||
|
||||
# include `assets` in web platform
|
||||
[web.resource]
|
||||
|
||||
# CSS style file
|
||||
style = ["tailwind.css"]
|
||||
|
||||
# Javascript code file
|
||||
script = []
|
||||
|
||||
[web.resource.dev]
|
||||
|
||||
# serve: [dev-server] only
|
||||
|
||||
# CSS style file
|
||||
style = []
|
||||
|
||||
# Javascript code file
|
||||
script = []
|
||||
@@ -1,90 +0,0 @@
|
||||
//! Run with:
|
||||
//!
|
||||
//! ```sh
|
||||
//! dx build --features web --release
|
||||
//! cargo run --features server
|
||||
//! ```
|
||||
|
||||
#![allow(unused)]
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_fullstack::{launch, prelude::*};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// Generate all routes and output them to the docs path
|
||||
#[cfg(feature = "server")]
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let wrapper = DefaultRenderer {
|
||||
before_body: r#"<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,
|
||||
initial-scale=1.0">
|
||||
<title>Dioxus Application</title>
|
||||
</head>
|
||||
<body>"#
|
||||
.to_string(),
|
||||
after_body: r#"</body>
|
||||
</html>"#
|
||||
.to_string(),
|
||||
};
|
||||
let mut renderer = IncrementalRenderer::builder().build();
|
||||
pre_cache_static_routes::<Route, _>(&mut renderer, &wrapper)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Hydrate the page
|
||||
#[cfg(not(feature = "server"))]
|
||||
fn main() {
|
||||
#[cfg(all(feature = "web", not(feature = "server")))]
|
||||
dioxus_web::launch_with_props(
|
||||
dioxus_fullstack::router::RouteWithCfg::<Route>,
|
||||
dioxus_fullstack::prelude::get_root_props_from_document()
|
||||
.expect("Failed to get root props from document"),
|
||||
dioxus_web::Config::default().hydrate(true),
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Clone, Routable, Debug, PartialEq, Serialize, Deserialize)]
|
||||
enum Route {
|
||||
#[route("/")]
|
||||
Home {},
|
||||
|
||||
#[route("/blog")]
|
||||
Blog,
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Blog() -> Element {
|
||||
rsx! {
|
||||
Link { to: Route::Home {}, "Go to counter" }
|
||||
table {
|
||||
tbody {
|
||||
for _ in 0..100 {
|
||||
tr {
|
||||
for _ in 0..100 {
|
||||
td { "hello world!" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Home() -> Element {
|
||||
let mut count = use_signal(|| 0);
|
||||
let text = use_signal(|| "...".to_string());
|
||||
|
||||
rsx! {
|
||||
Link { to: Route::Blog {}, "Go to blog" }
|
||||
div {
|
||||
h1 { "High-Five counter: {count}" }
|
||||
button { onclick: move |_| count += 1, "Up high!" }
|
||||
button { onclick: move |_| count -= 1, "Down low!" }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,9 +15,11 @@
|
||||
//! tokio::runtime::Runtime::new()
|
||||
//! .unwrap()
|
||||
//! .block_on(async move {
|
||||
//! let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 8080));
|
||||
//! axum::Server::bind(&addr)
|
||||
//! .serve(
|
||||
//! let listener = tokio::net::TcpListener::bind("127.0.0.01:8080")
|
||||
//! .await
|
||||
//! .unwrap();
|
||||
//! axum::serve(
|
||||
//! listener,
|
||||
//! axum::Router::new()
|
||||
//! // Server side render the application, serve static assets, and register server functions
|
||||
//! .serve_dioxus_application("", ServerConfig::new(app, ()))
|
||||
@@ -91,30 +93,6 @@ pub trait DioxusRouterExt<S> {
|
||||
/// ```
|
||||
fn register_server_fns(self) -> Self;
|
||||
|
||||
/// Register the web RSX hot reloading endpoint. This will enable hot reloading for your application in debug mode when you call [`dioxus_hot_reload::hot_reload_init`].
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// #![allow(non_snake_case)]
|
||||
/// use dioxus_fullstack::prelude::*;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// hot_reload_init!();
|
||||
/// let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 8080));
|
||||
/// axum::Server::bind(&addr)
|
||||
/// .serve(
|
||||
/// axum::Router::new()
|
||||
/// // Connect to hot reloading in debug mode
|
||||
/// .connect_hot_reload()
|
||||
/// .into_make_service(),
|
||||
/// )
|
||||
/// .await
|
||||
/// .unwrap();
|
||||
/// }
|
||||
/// ```
|
||||
fn connect_hot_reload(self) -> Self;
|
||||
|
||||
/// Serves the static WASM for your Dioxus application (except the generated index.html).
|
||||
///
|
||||
/// # Example
|
||||
@@ -151,7 +129,7 @@ pub trait DioxusRouterExt<S> {
|
||||
Self: Sized;
|
||||
|
||||
/// Serves the Dioxus application. This will serve a complete server side rendered application.
|
||||
/// This will serve static assets, server render the application, register server functions, and intigrate with hot reloading.
|
||||
/// This will serve static assets, server render the application, register server functions, and integrate with hot reloading.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
@@ -263,44 +241,22 @@ where
|
||||
let ssr_state = SSRState::new(&cfg);
|
||||
|
||||
// Add server functions and render index.html
|
||||
self.serve_static_assets(cfg.assets_path.clone())
|
||||
let mut server = self
|
||||
.serve_static_assets(cfg.assets_path.clone())
|
||||
.await
|
||||
.connect_hot_reload()
|
||||
.register_server_fns()
|
||||
.fallback(get(render_handler).with_state((
|
||||
cfg,
|
||||
Arc::new(build_virtual_dom),
|
||||
ssr_state,
|
||||
)))
|
||||
}
|
||||
}
|
||||
.register_server_fns();
|
||||
|
||||
fn connect_hot_reload(self) -> Self {
|
||||
#[cfg(all(debug_assertions, feature = "hot-reload"))]
|
||||
{
|
||||
self.nest(
|
||||
"/_dioxus",
|
||||
Router::new()
|
||||
.route(
|
||||
"/ws",
|
||||
get(|ws: axum::extract::WebSocketUpgrade| async {
|
||||
ws.on_upgrade(|mut ws| async move {
|
||||
use axum::extract::ws::Message;
|
||||
let _ = ws.send(Message::Text("connected".into())).await;
|
||||
loop {
|
||||
if ws.recv().await.is_none() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
}),
|
||||
)
|
||||
.route("/hot_reload", get(hot_reload_handler)),
|
||||
)
|
||||
}
|
||||
#[cfg(not(all(debug_assertions, feature = "hot-reload")))]
|
||||
{
|
||||
self
|
||||
#[cfg(all(feature = "hot-reload", debug_assertions))]
|
||||
{
|
||||
use dioxus_hot_reload::HotReloadRouterExt;
|
||||
server = server.forward_cli_hot_reloading();
|
||||
}
|
||||
|
||||
server.fallback(get(render_handler).with_state((
|
||||
cfg,
|
||||
Arc::new(build_virtual_dom),
|
||||
ssr_state,
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -424,51 +380,6 @@ fn report_err<E: std::fmt::Display>(e: E) -> Response<axum::body::Body> {
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// A handler for Dioxus web hot reload websocket. This will send the updated static parts of the RSX to the client when they change.
|
||||
#[cfg(all(debug_assertions, feature = "hot-reload"))]
|
||||
pub async fn hot_reload_handler(ws: axum::extract::WebSocketUpgrade) -> impl IntoResponse {
|
||||
use axum::extract::ws::Message;
|
||||
use futures_util::StreamExt;
|
||||
|
||||
let state = crate::hot_reload::spawn_hot_reload().await;
|
||||
|
||||
ws.on_upgrade(move |mut socket| async move {
|
||||
println!("🔥 Hot Reload WebSocket connected");
|
||||
{
|
||||
// update any rsx calls that changed before the websocket connected.
|
||||
{
|
||||
println!("🔮 Finding updates since last compile...");
|
||||
let templates_read = state.templates.read().await;
|
||||
|
||||
for template in &*templates_read {
|
||||
if socket
|
||||
.send(Message::Text(serde_json::to_string(&template).unwrap()))
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
println!("finished");
|
||||
}
|
||||
|
||||
let mut rx =
|
||||
tokio_stream::wrappers::WatchStream::from_changes(state.message_receiver.clone());
|
||||
|
||||
while let Some(change) = rx.next().await {
|
||||
if let Some(template) = change {
|
||||
let template = { serde_json::to_string(&template).unwrap() };
|
||||
if socket.send(Message::Text(template)).await.is_err() {
|
||||
break;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
println!("😳 Hot Reload WebSocket disconnected");
|
||||
})
|
||||
}
|
||||
|
||||
/// A handler for Dioxus server functions. This will run the server function and return the result.
|
||||
async fn handle_server_fns_inner(
|
||||
path: &str,
|
||||
|
||||
@@ -131,15 +131,21 @@ impl Config {
|
||||
let ssr_state = SSRState::new(&cfg);
|
||||
let router = axum::Router::new().register_server_fns();
|
||||
#[cfg(not(any(feature = "desktop", feature = "mobile")))]
|
||||
let router = router
|
||||
.serve_static_assets(cfg.assets_path.clone())
|
||||
.await
|
||||
.connect_hot_reload()
|
||||
.fallback(get(render_handler).with_state((
|
||||
let router = {
|
||||
let mut router = router.serve_static_assets(cfg.assets_path.clone()).await;
|
||||
|
||||
#[cfg(all(feature = "hot-reload", debug_assertions))]
|
||||
{
|
||||
use dioxus_hot_reload::HotReloadRouterExt;
|
||||
router = router.forward_cli_hot_reloading();
|
||||
}
|
||||
|
||||
router.fallback(get(render_handler).with_state((
|
||||
cfg,
|
||||
Arc::new(build_virtual_dom),
|
||||
ssr_state,
|
||||
)));
|
||||
)))
|
||||
};
|
||||
let router = router.into_make_service();
|
||||
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
|
||||
axum::serve(listener, router).await.unwrap();
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use dioxus_lib::prelude::Template;
|
||||
use tokio::sync::{
|
||||
watch::{channel, Receiver},
|
||||
RwLock,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct HotReloadState {
|
||||
// The cache of all templates that have been modified since the last time we checked
|
||||
pub(crate) templates: Arc<RwLock<std::collections::HashSet<dioxus_lib::prelude::Template>>>,
|
||||
// The channel to send messages to the hot reload thread
|
||||
pub(crate) message_receiver: Receiver<Option<Template>>,
|
||||
}
|
||||
|
||||
impl Default for HotReloadState {
|
||||
fn default() -> Self {
|
||||
let templates = Arc::new(RwLock::new(std::collections::HashSet::new()));
|
||||
let (tx, rx) = channel(None);
|
||||
|
||||
dioxus_hot_reload::connect({
|
||||
let templates = templates.clone();
|
||||
move |msg| match msg {
|
||||
dioxus_hot_reload::HotReloadMsg::UpdateTemplate(template) => {
|
||||
{
|
||||
let mut templates = templates.blocking_write();
|
||||
templates.insert(template);
|
||||
}
|
||||
|
||||
if let Err(err) = tx.send(Some(template)) {
|
||||
tracing::error!("Failed to send hot reload message: {}", err);
|
||||
}
|
||||
}
|
||||
dioxus_hot_reload::HotReloadMsg::Shutdown => {
|
||||
std::process::exit(0);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
});
|
||||
|
||||
Self {
|
||||
templates,
|
||||
message_receiver: rx,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Hot reloading can be expensive to start so we spawn a new thread
|
||||
static HOT_RELOAD_STATE: tokio::sync::OnceCell<HotReloadState> = tokio::sync::OnceCell::const_new();
|
||||
pub(crate) async fn spawn_hot_reload() -> &'static HotReloadState {
|
||||
HOT_RELOAD_STATE
|
||||
.get_or_init(|| async {
|
||||
println!("spinning up hot reloading");
|
||||
let r = tokio::task::spawn_blocking(HotReloadState::default)
|
||||
.await
|
||||
.unwrap();
|
||||
println!("hot reloading ready");
|
||||
r
|
||||
})
|
||||
.await
|
||||
}
|
||||
@@ -57,23 +57,3 @@ static SERVER_DATA: once_cell::sync::Lazy<Option<HTMLDataCursor>> =
|
||||
pub(crate) fn take_server_data<T: DeserializeOwned>() -> Option<T> {
|
||||
SERVER_DATA.as_ref()?.take()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "server"))]
|
||||
/// Get the props from the document. This is only available in the browser.
|
||||
///
|
||||
/// When dioxus-fullstack renders the page, it will serialize the root props and put them in the document. This function gets them from the document.
|
||||
pub fn get_root_props_from_document<T: DeserializeOwned>() -> Option<T> {
|
||||
#[cfg(all(feature = "web", target_arch = "wasm32"))]
|
||||
{
|
||||
let attribute = web_sys::window()?
|
||||
.document()?
|
||||
.get_element_by_id("dioxus-storage-props")?
|
||||
.get_attribute("data-serialized")?;
|
||||
|
||||
serde_from_bytes(attribute.as_bytes())
|
||||
}
|
||||
#[cfg(not(all(feature = "web", target_arch = "wasm32")))]
|
||||
{
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -14,19 +14,6 @@ pub(crate) fn serde_to_writable<T: Serialize>(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
/// Encode data into a element. This is inteded to be used in the server to send data to the client.
|
||||
pub(crate) fn encode_props_in_element<T: Serialize>(
|
||||
data: &T,
|
||||
write_to: &mut impl std::io::Write,
|
||||
) -> Result<(), ciborium::ser::Error<std::io::Error>> {
|
||||
write_to.write_all(
|
||||
r#"<meta hidden="true" id="dioxus-storage-props" data-serialized=""#.as_bytes(),
|
||||
)?;
|
||||
serde_to_writable(data, write_to)?;
|
||||
Ok(write_to.write_all(r#"" />"#.as_bytes())?)
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
/// 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(
|
||||
|
||||
@@ -8,21 +8,14 @@ pub use once_cell;
|
||||
|
||||
mod html_storage;
|
||||
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "axum")))]
|
||||
#[cfg(feature = "axum")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "axum")))]
|
||||
mod axum_adapter;
|
||||
|
||||
mod config;
|
||||
mod hooks;
|
||||
pub mod launch;
|
||||
|
||||
#[cfg(all(
|
||||
debug_assertions,
|
||||
feature = "hot-reload",
|
||||
feature = "server",
|
||||
not(target_arch = "wasm32")
|
||||
))]
|
||||
mod hot_reload;
|
||||
pub use config::*;
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
@@ -43,17 +36,9 @@ pub mod prelude {
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "axum")))]
|
||||
pub use crate::axum_adapter::*;
|
||||
|
||||
#[cfg(not(feature = "server"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(not(feature = "server"))))]
|
||||
pub use crate::html_storage::deserialize::get_root_props_from_document;
|
||||
|
||||
#[cfg(all(feature = "server", feature = "router"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(all(feature = "server", feature = "router"))))]
|
||||
pub use crate::render::pre_cache_static_routes_with_props;
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
|
||||
pub use crate::render::SSRState;
|
||||
pub use crate::render::{FullstackHTMLTemplate, SSRState};
|
||||
|
||||
#[cfg(feature = "router")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "router")))]
|
||||
@@ -70,23 +55,14 @@ pub mod prelude {
|
||||
#[cfg(feature = "server")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
|
||||
pub use crate::server_context::{
|
||||
extract, server_context, DioxusServerContext, FromServerContext, ProvideServerContext,
|
||||
extract, server_context, with_server_context, DioxusServerContext, FromServerContext,
|
||||
ProvideServerContext,
|
||||
};
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
|
||||
pub use dioxus_ssr::incremental::IncrementalRendererConfig;
|
||||
pub use dioxus_ssr::incremental::{IncrementalRenderer, IncrementalRendererConfig};
|
||||
|
||||
pub use dioxus_server_macro::*;
|
||||
pub use server_fn::{self, ServerFn as _, ServerFnError};
|
||||
}
|
||||
|
||||
// // Warn users about overlapping features
|
||||
// #[cfg(all(feature = "server", feature = "web", not(doc)))]
|
||||
// compile_error!("The `ssr` feature (enabled by `warp`, `axum`, or `salvo`) and `web` feature are overlapping. Please choose one or the other.");
|
||||
|
||||
// #[cfg(all(feature = "server", feature = "desktop", not(doc)))]
|
||||
// compile_error!("The `ssr` feature (enabled by `warp`, `axum`, or `salvo`) and `desktop` feature are overlapping. Please choose one or the other.");
|
||||
|
||||
// #[cfg(all(feature = "server", feature = "mobile", not(doc)))]
|
||||
// compile_error!("The `ssr` feature (enabled by `warp`, `axum`, or `salvo`) and `mobile` feature are overlapping. Please choose one or the other.");
|
||||
@@ -1,5 +1,5 @@
|
||||
//! A shared pool of renderers for efficient server side rendering.
|
||||
use crate::{render::dioxus_core::NoOpMutations, server_context::with_server_context};
|
||||
use crate::render::dioxus_core::NoOpMutations;
|
||||
use dioxus_ssr::{
|
||||
incremental::{RenderFreshness, WrapBody},
|
||||
Renderer,
|
||||
@@ -45,7 +45,7 @@ impl SsrRendererPool {
|
||||
virtual_dom_factory: impl FnOnce() -> VirtualDom + Send + Sync + 'static,
|
||||
server_context: &DioxusServerContext,
|
||||
) -> Result<(RenderFreshness, String), dioxus_ssr::incremental::IncrementalRendererError> {
|
||||
let wrapper = FullstackRenderer {
|
||||
let wrapper = FullstackHTMLTemplate {
|
||||
cfg: cfg.clone(),
|
||||
server_context: server_context.clone(),
|
||||
};
|
||||
@@ -210,12 +210,24 @@ impl SSRState {
|
||||
}
|
||||
}
|
||||
|
||||
struct FullstackRenderer {
|
||||
/// The template that wraps the body of the HTML for a fullstack page. This template contains the data needed to hydrate server functions that were run on the server.
|
||||
#[derive(Default)]
|
||||
pub struct FullstackHTMLTemplate {
|
||||
cfg: ServeConfig,
|
||||
server_context: DioxusServerContext,
|
||||
}
|
||||
|
||||
impl dioxus_ssr::incremental::WrapBody for FullstackRenderer {
|
||||
impl FullstackHTMLTemplate {
|
||||
/// Create a new [`FullstackHTMLTemplate`].
|
||||
pub fn new(cfg: &ServeConfig, server_context: &DioxusServerContext) -> Self {
|
||||
Self {
|
||||
cfg: cfg.clone(),
|
||||
server_context: server_context.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl dioxus_ssr::incremental::WrapBody for FullstackHTMLTemplate {
|
||||
fn render_before_body<R: std::io::Write>(
|
||||
&self,
|
||||
to: &mut R,
|
||||
@@ -231,11 +243,6 @@ impl dioxus_ssr::incremental::WrapBody for FullstackRenderer {
|
||||
&self,
|
||||
to: &mut R,
|
||||
) -> Result<(), dioxus_ssr::incremental::IncrementalRendererError> {
|
||||
// serialize the props
|
||||
// TODO: restore props serialization
|
||||
// crate::html_storage::serialize::encode_props_in_element(&self.cfg.props, to).map_err(
|
||||
// |err| dioxus_ssr::incremental::IncrementalRendererError::Other(Box::new(err)),
|
||||
// )?;
|
||||
// serialize the server state
|
||||
crate::html_storage::serialize::encode_in_element(
|
||||
&*self.server_context.html_data().map_err(|_| {
|
||||
@@ -263,38 +270,7 @@ impl dioxus_ssr::incremental::WrapBody for FullstackRenderer {
|
||||
#[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
|
||||
// This is copied from the Dioxus-CLI package at ../packages/cli
|
||||
let disconnect_js = r#"(function () {
|
||||
var protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
||||
var url = protocol + "//" + window.location.host + "/_dioxus/ws";
|
||||
var poll_interval = 8080;
|
||||
|
||||
var reload_upon_connect = (event) => {
|
||||
// Firefox will send a 1001 code when the connection is closed because the page is reloaded
|
||||
// Only firefox will trigger the onclose event when the page is reloaded manually: https://stackoverflow.com/questions/10965720/should-websocket-onclose-be-triggered-by-user-navigation-or-refresh
|
||||
// We should not reload the page in this case
|
||||
if (event.code === 1001) {
|
||||
return;
|
||||
}
|
||||
window.setTimeout(() => {
|
||||
var ws = new WebSocket(url);
|
||||
ws.onopen = () => window.location.reload();
|
||||
ws.onclose = reload_upon_connect;
|
||||
}, poll_interval);
|
||||
};
|
||||
|
||||
var ws = new WebSocket(url);
|
||||
|
||||
ws.onmessage = (ev) => {
|
||||
console.log("Received message: ", ev, ev.data);
|
||||
|
||||
if (ev.data == "reload") {
|
||||
window.location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
ws.onclose = reload_upon_connect;
|
||||
})();"#;
|
||||
let disconnect_js = dioxus_hot_reload::RECONNECT_SCRIPT;
|
||||
|
||||
to.write_all(r#"<script>"#.as_bytes())?;
|
||||
to.write_all(disconnect_js.as_bytes())?;
|
||||
|
||||
@@ -16,26 +16,6 @@ pub struct ServeConfigBuilder {
|
||||
Option<std::sync::Arc<dioxus_ssr::incremental::IncrementalRendererConfig>>,
|
||||
}
|
||||
|
||||
/// A template for incremental rendering that does nothing.
|
||||
#[derive(Default, Clone)]
|
||||
pub struct EmptyIncrementalRenderTemplate;
|
||||
|
||||
impl dioxus_ssr::incremental::WrapBody for EmptyIncrementalRenderTemplate {
|
||||
fn render_after_body<R: std::io::Write>(
|
||||
&self,
|
||||
_: &mut R,
|
||||
) -> Result<(), dioxus_ssr::incremental::IncrementalRendererError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn render_before_body<R: std::io::Write>(
|
||||
&self,
|
||||
_: &mut R,
|
||||
) -> Result<(), dioxus_ssr::incremental::IncrementalRendererError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ServeConfigBuilder {
|
||||
/// Create a new ServeConfigBuilder with the root component and props to render on the server.
|
||||
pub fn new() -> Self {
|
||||
@@ -151,6 +131,12 @@ pub struct ServeConfig {
|
||||
Option<std::sync::Arc<dioxus_ssr::incremental::IncrementalRendererConfig>>,
|
||||
}
|
||||
|
||||
impl Default for ServeConfig {
|
||||
fn default() -> Self {
|
||||
Self::builder().build()
|
||||
}
|
||||
}
|
||||
|
||||
impl ServeConfig {
|
||||
/// Create a new builder for a ServeConfig
|
||||
pub fn builder() -> ServeConfigBuilder {
|
||||
|
||||
@@ -159,7 +159,8 @@ pub async fn extract<E: FromServerContext<I>, I>() -> Result<E, E::Rejection> {
|
||||
E::from_request(&server_context()).await
|
||||
}
|
||||
|
||||
pub(crate) fn with_server_context<O>(context: DioxusServerContext, f: impl FnOnce() -> O) -> O {
|
||||
/// Run a function inside of the server context.
|
||||
pub fn with_server_context<O>(context: DioxusServerContext, f: impl FnOnce() -> O) -> O {
|
||||
// before polling the future, we need to set the context
|
||||
let prev_context = SERVER_CONTEXT.with(|ctx| ctx.replace(Box::new(context)));
|
||||
// poll the future, which may call server_context()
|
||||
|
||||
Reference in new issue
Block a user