allow passing root prop data from the server to the client
This commit is contained in:
1 parent
0726a7b08f
commit
03a2824660
12 files changed
+230
-21
No files matched your search
@@ -44,10 +44,15 @@ anymap = "0.12.1"
|
|||||||
serde_json = { version = "1.0.95", optional = true }
|
serde_json = { version = "1.0.95", optional = true }
|
||||||
tokio-stream = { version = "0.1.12", features = ["sync"], optional = true }
|
tokio-stream = { version = "0.1.12", features = ["sync"], optional = true }
|
||||||
futures-util = { version = "0.3.28", optional = true }
|
futures-util = { version = "0.3.28", optional = true }
|
||||||
|
postcard = { version = "1.0.4", features = ["use-std"] }
|
||||||
|
yazi = "0.1.5"
|
||||||
|
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
dioxus-hot-reload = { path = "../hot-reload" }
|
dioxus-hot-reload = { path = "../hot-reload" }
|
||||||
|
|
||||||
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
|
web-sys = { version = "0.3.61", features = ["Window", "Document", "Element", "HtmlDocument", "Storage", "console"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["hot-reload"]
|
default = ["hot-reload"]
|
||||||
hot-reload = ["serde_json", "tokio-stream", "futures-util"]
|
hot-reload = ["serde_json", "tokio-stream", "futures-util"]
|
||||||
|
|||||||
@@ -8,10 +8,15 @@
|
|||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use dioxus_server::prelude::*;
|
use dioxus_server::prelude::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
#[cfg(feature = "web")]
|
#[cfg(feature = "web")]
|
||||||
dioxus_web::launch_cfg(app, dioxus_web::Config::new().hydrate(true));
|
dioxus_web::launch_with_props(
|
||||||
|
app,
|
||||||
|
get_props_from_document().unwrap_or_default(),
|
||||||
|
dioxus_web::Config::new().hydrate(true),
|
||||||
|
);
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
{
|
{
|
||||||
PostServerData::register().unwrap();
|
PostServerData::register().unwrap();
|
||||||
@@ -23,7 +28,10 @@ fn main() {
|
|||||||
axum::Server::bind(&addr)
|
axum::Server::bind(&addr)
|
||||||
.serve(
|
.serve(
|
||||||
axum::Router::new()
|
axum::Router::new()
|
||||||
.serve_dioxus_application("", ServeConfigBuilder::new(app, ()))
|
.serve_dioxus_application(
|
||||||
|
"",
|
||||||
|
ServeConfigBuilder::new(app, AppProps { count: 12345 }).build(),
|
||||||
|
)
|
||||||
.into_make_service(),
|
.into_make_service(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -32,8 +40,13 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn app(cx: Scope) -> Element {
|
#[derive(Props, PartialEq, Debug, Default, Serialize, Deserialize, Clone)]
|
||||||
let mut count = use_state(cx, || 0);
|
struct AppProps {
|
||||||
|
count: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app(cx: Scope<AppProps>) -> Element {
|
||||||
|
let mut count = use_state(cx, || cx.props.count);
|
||||||
let text = use_state(cx, || "...".to_string());
|
let text = use_state(cx, || "...".to_string());
|
||||||
|
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
|
|||||||
@@ -8,10 +8,15 @@
|
|||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use dioxus_server::prelude::*;
|
use dioxus_server::prelude::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
#[cfg(feature = "web")]
|
#[cfg(feature = "web")]
|
||||||
dioxus_web::launch_cfg(app, dioxus_web::Config::new().hydrate(true));
|
dioxus_web::launch_with_props(
|
||||||
|
app,
|
||||||
|
get_props_from_document().unwrap_or_default(),
|
||||||
|
dioxus_web::Config::new().hydrate(true),
|
||||||
|
);
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
{
|
{
|
||||||
use salvo::prelude::*;
|
use salvo::prelude::*;
|
||||||
@@ -20,8 +25,10 @@ fn main() {
|
|||||||
tokio::runtime::Runtime::new()
|
tokio::runtime::Runtime::new()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.block_on(async move {
|
.block_on(async move {
|
||||||
let router =
|
let router = Router::new().serve_dioxus_application(
|
||||||
Router::new().serve_dioxus_application("", ServeConfigBuilder::new(app, ()));
|
"",
|
||||||
|
ServeConfigBuilder::new(app, AppProps { count: 12345 }),
|
||||||
|
);
|
||||||
Server::new(TcpListener::bind("127.0.0.1:8080"))
|
Server::new(TcpListener::bind("127.0.0.1:8080"))
|
||||||
.serve(router)
|
.serve(router)
|
||||||
.await;
|
.await;
|
||||||
@@ -29,8 +36,13 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn app(cx: Scope) -> Element {
|
#[derive(Props, PartialEq, Debug, Default, Serialize, Deserialize, Clone)]
|
||||||
let mut count = use_state(cx, || 0);
|
struct AppProps {
|
||||||
|
count: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app(cx: Scope<AppProps>) -> Element {
|
||||||
|
let mut count = use_state(cx, || cx.props.count);
|
||||||
let text = use_state(cx, || "...".to_string());
|
let text = use_state(cx, || "...".to_string());
|
||||||
|
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
|
|||||||
@@ -8,10 +8,15 @@
|
|||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use dioxus_server::prelude::*;
|
use dioxus_server::prelude::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
#[cfg(feature = "web")]
|
#[cfg(feature = "web")]
|
||||||
dioxus_web::launch_cfg(app, dioxus_web::Config::new().hydrate(true));
|
dioxus_web::launch_with_props(
|
||||||
|
app,
|
||||||
|
get_props_from_document().unwrap_or_default(),
|
||||||
|
dioxus_web::Config::new().hydrate(true),
|
||||||
|
);
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
{
|
{
|
||||||
PostServerData::register().unwrap();
|
PostServerData::register().unwrap();
|
||||||
@@ -19,14 +24,22 @@ fn main() {
|
|||||||
tokio::runtime::Runtime::new()
|
tokio::runtime::Runtime::new()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.block_on(async move {
|
.block_on(async move {
|
||||||
let routes = serve_dioxus_application("", ServeConfigBuilder::new(app, ()));
|
let routes = serve_dioxus_application(
|
||||||
|
"",
|
||||||
|
ServeConfigBuilder::new(app, AppProps { count: 12345 }),
|
||||||
|
);
|
||||||
warp::serve(routes).run(([127, 0, 0, 1], 8080)).await;
|
warp::serve(routes).run(([127, 0, 0, 1], 8080)).await;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn app(cx: Scope) -> Element {
|
#[derive(Props, PartialEq, Debug, Default, Serialize, Deserialize, Clone)]
|
||||||
let mut count = use_state(cx, || 0);
|
struct AppProps {
|
||||||
|
count: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app(cx: Scope<AppProps>) -> Element {
|
||||||
|
let mut count = use_state(cx, || cx.props.count);
|
||||||
let text = use_state(cx, || "...".to_string());
|
let text = use_state(cx, || "...".to_string());
|
||||||
|
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ pub trait DioxusRouterExt<S> {
|
|||||||
/// todo!()
|
/// todo!()
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
fn serve_dioxus_application<P: Clone + Send + Sync + 'static>(
|
fn serve_dioxus_application<P: Clone + serde::Serialize + Send + Sync + 'static>(
|
||||||
self,
|
self,
|
||||||
server_fn_route: &'static str,
|
server_fn_route: &'static str,
|
||||||
cfg: impl Into<ServeConfig<P>>,
|
cfg: impl Into<ServeConfig<P>>,
|
||||||
@@ -229,7 +229,7 @@ where
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serve_dioxus_application<P: Clone + Send + Sync + 'static>(
|
fn serve_dioxus_application<P: Clone + serde::Serialize + Send + Sync + 'static>(
|
||||||
mut self,
|
mut self,
|
||||||
server_fn_route: &'static str,
|
server_fn_route: &'static str,
|
||||||
cfg: impl Into<ServeConfig<P>>,
|
cfg: impl Into<ServeConfig<P>>,
|
||||||
@@ -294,7 +294,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn render_handler<P: Clone + Send + Sync + 'static>(
|
async fn render_handler<P: Clone + serde::Serialize + Send + Sync + 'static>(
|
||||||
State((cfg, ssr_state)): State<(ServeConfig<P>, SSRState)>,
|
State((cfg, ssr_state)): State<(ServeConfig<P>, SSRState)>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
let rendered = ssr_state.render(&cfg);
|
let rendered = ssr_state.render(&cfg);
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ pub trait DioxusRouterExt {
|
|||||||
///
|
///
|
||||||
/// fn app(cx: Scope) -> Element {todo!()}
|
/// fn app(cx: Scope) -> Element {todo!()}
|
||||||
/// ```
|
/// ```
|
||||||
fn serve_dioxus_application<P: Clone + Send + Sync + 'static>(
|
fn serve_dioxus_application<P: Clone + serde::Serialize + Send + Sync + 'static>(
|
||||||
self,
|
self,
|
||||||
server_fn_path: &'static str,
|
server_fn_path: &'static str,
|
||||||
cfg: impl Into<ServeConfig<P>>,
|
cfg: impl Into<ServeConfig<P>>,
|
||||||
@@ -212,7 +212,7 @@ impl DioxusRouterExt for Router {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serve_dioxus_application<P: Clone + Send + Sync + 'static>(
|
fn serve_dioxus_application<P: Clone + serde::Serialize + Send + Sync + 'static>(
|
||||||
mut self,
|
mut self,
|
||||||
server_fn_route: &'static str,
|
server_fn_route: &'static str,
|
||||||
cfg: impl Into<ServeConfig<P>>,
|
cfg: impl Into<ServeConfig<P>>,
|
||||||
@@ -269,7 +269,7 @@ struct SSRHandler<P: Clone> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<P: Clone + Send + Sync + 'static> Handler for SSRHandler<P> {
|
impl<P: Clone + serde::Serialize + Send + Sync + 'static> Handler for SSRHandler<P> {
|
||||||
async fn handle(
|
async fn handle(
|
||||||
&self,
|
&self,
|
||||||
_req: &mut Request,
|
_req: &mut Request,
|
||||||
|
|||||||
@@ -189,7 +189,7 @@ pub fn register_server_fns(server_fn_route: &'static str) -> BoxedFilter<(impl R
|
|||||||
/// todo!()
|
/// todo!()
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn serve_dioxus_application<P: Clone + Send + Sync + 'static>(
|
pub fn serve_dioxus_application<P: Clone + serde::Serialize + Send + Sync + 'static>(
|
||||||
server_fn_route: &'static str,
|
server_fn_route: &'static str,
|
||||||
cfg: impl Into<ServeConfig<P>>,
|
cfg: impl Into<ServeConfig<P>>,
|
||||||
) -> BoxedFilter<(impl Reply,)> {
|
) -> BoxedFilter<(impl Reply,)> {
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
pub use adapters::*;
|
pub use adapters::*;
|
||||||
|
|
||||||
|
mod props_html;
|
||||||
|
|
||||||
mod adapters;
|
mod adapters;
|
||||||
#[cfg(all(debug_assertions, feature = "hot-reload", feature = "ssr"))]
|
#[cfg(all(debug_assertions, feature = "hot-reload", feature = "ssr"))]
|
||||||
mod hot_reload;
|
mod hot_reload;
|
||||||
@@ -23,6 +25,8 @@ pub mod prelude {
|
|||||||
pub use crate::adapters::salvo_adapter::*;
|
pub use crate::adapters::salvo_adapter::*;
|
||||||
#[cfg(feature = "warp")]
|
#[cfg(feature = "warp")]
|
||||||
pub use crate::adapters::warp_adapter::*;
|
pub use crate::adapters::warp_adapter::*;
|
||||||
|
#[cfg(not(feature = "ssr"))]
|
||||||
|
pub use crate::props_html::deserialize_props::get_props_from_document;
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
pub use crate::render::SSRState;
|
pub use crate::render::SSRState;
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
use serde::de::DeserializeOwned;
|
||||||
|
|
||||||
|
use super::u16_from_char;
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
pub(crate) fn serde_from_string<T: DeserializeOwned>(string: &str) -> Option<T> {
|
||||||
|
let decompressed = string
|
||||||
|
.chars()
|
||||||
|
.flat_map(|c| {
|
||||||
|
let u = u16_from_char(c);
|
||||||
|
let u1 = (u >> 8) as u8;
|
||||||
|
let u2 = (u & 0xFF) as u8;
|
||||||
|
[u1, u2].into_iter()
|
||||||
|
})
|
||||||
|
.collect::<Vec<u8>>();
|
||||||
|
let (decompressed, _) = yazi::decompress(&decompressed, yazi::Format::Zlib).unwrap();
|
||||||
|
|
||||||
|
postcard::from_bytes(&decompressed).ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "ssr"))]
|
||||||
|
/// Get the props from the document. This is only available in the browser.
|
||||||
|
pub fn get_props_from_document<T: DeserializeOwned>() -> Option<T> {
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
{
|
||||||
|
None
|
||||||
|
}
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
{
|
||||||
|
let attribute = web_sys::window()?
|
||||||
|
.document()?
|
||||||
|
.get_element_by_id("dioxus-storage")?
|
||||||
|
.get_attribute("data-serialized")?;
|
||||||
|
|
||||||
|
serde_from_string(&attribute)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
pub(crate) mod deserialize_props;
|
||||||
|
|
||||||
|
pub(crate) mod serialize_props;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn serialized_and_deserializes() {
|
||||||
|
use postcard::to_allocvec;
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)]
|
||||||
|
struct Data {
|
||||||
|
a: u32,
|
||||||
|
b: String,
|
||||||
|
bytes: Vec<u8>,
|
||||||
|
nested: Nested,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)]
|
||||||
|
struct Nested {
|
||||||
|
a: u32,
|
||||||
|
b: u16,
|
||||||
|
c: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
for x in 0..10usize {
|
||||||
|
for y in 0..10 {
|
||||||
|
let mut as_string = String::new();
|
||||||
|
let data = vec![
|
||||||
|
Data {
|
||||||
|
a: x as u32,
|
||||||
|
b: "hello".to_string(),
|
||||||
|
bytes: vec![0; x],
|
||||||
|
nested: Nested {
|
||||||
|
a: 1,
|
||||||
|
b: x as u16,
|
||||||
|
c: 3
|
||||||
|
},
|
||||||
|
};
|
||||||
|
y
|
||||||
|
];
|
||||||
|
serialize_props::serde_to_writable(&data, &mut as_string).unwrap();
|
||||||
|
|
||||||
|
println!("{}", as_string);
|
||||||
|
println!(
|
||||||
|
"original size: {}",
|
||||||
|
std::mem::size_of::<Data>() * data.len()
|
||||||
|
);
|
||||||
|
println!("serialized size: {}", to_allocvec(&data).unwrap().len());
|
||||||
|
println!("compressed size: {}", as_string.len());
|
||||||
|
|
||||||
|
let decoded: Vec<Data> = deserialize_props::serde_from_string(&as_string).unwrap();
|
||||||
|
assert_eq!(data, decoded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn encodes_and_decodes_bytes() {
|
||||||
|
for i in 0..(u16::MAX) {
|
||||||
|
let c = u16_to_char(i);
|
||||||
|
let i2 = u16_from_char(c);
|
||||||
|
assert_eq!(i, i2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
pub(crate) fn u16_to_char(u: u16) -> char {
|
||||||
|
let u = u as u32;
|
||||||
|
let mapped = if u <= 0xD7FF {
|
||||||
|
u
|
||||||
|
} else {
|
||||||
|
0xE000 + (u - 0xD7FF)
|
||||||
|
};
|
||||||
|
char::from_u32(mapped).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
pub(crate) fn u16_from_char(c: char) -> u16 {
|
||||||
|
let c = c as u32;
|
||||||
|
let mapped = if c <= 0xD7FF {
|
||||||
|
c
|
||||||
|
} else {
|
||||||
|
0xD7FF + (c - 0xE000)
|
||||||
|
};
|
||||||
|
mapped as u16
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use super::u16_to_char;
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
pub(crate) fn serde_to_writable<T: Serialize>(
|
||||||
|
value: &T,
|
||||||
|
mut write_to: impl std::fmt::Write,
|
||||||
|
) -> std::fmt::Result {
|
||||||
|
let serialized = postcard::to_allocvec(value).unwrap();
|
||||||
|
let compressed = yazi::compress(
|
||||||
|
&serialized,
|
||||||
|
yazi::Format::Zlib,
|
||||||
|
yazi::CompressionLevel::BestSize,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
for array in compressed.chunks(2) {
|
||||||
|
let w = if array.len() == 2 {
|
||||||
|
[array[0], array[1]]
|
||||||
|
} else {
|
||||||
|
[array[0], 0]
|
||||||
|
};
|
||||||
|
write_to.write_char(u16_to_char((w[0] as u16) << 8 | (w[1] as u16)))?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
|
/// 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<T: Serialize>(
|
||||||
|
data: T,
|
||||||
|
mut write_to: impl std::fmt::Write,
|
||||||
|
) -> std::fmt::Result {
|
||||||
|
write_to.write_str(r#"<meta hidden="true" id="dioxus-storage" data-serialized=""#)?;
|
||||||
|
serde_to_writable(&data, &mut write_to)?;
|
||||||
|
write_to.write_str(r#"" />"#)
|
||||||
|
}
|
||||||
@@ -24,7 +24,7 @@ impl Default for SSRState {
|
|||||||
|
|
||||||
impl SSRState {
|
impl SSRState {
|
||||||
/// Render the application to HTML.
|
/// Render the application to HTML.
|
||||||
pub fn render<P: 'static + Clone>(&self, cfg: &ServeConfig<P>) -> String {
|
pub fn render<P: 'static + Clone + serde::Serialize>(&self, cfg: &ServeConfig<P>) -> String {
|
||||||
let ServeConfig {
|
let ServeConfig {
|
||||||
app, props, index, ..
|
app, props, index, ..
|
||||||
} = cfg;
|
} = cfg;
|
||||||
@@ -41,6 +41,9 @@ impl SSRState {
|
|||||||
|
|
||||||
let _ = renderer.render_to(&mut html, &vdom);
|
let _ = renderer.render_to(&mut html, &vdom);
|
||||||
|
|
||||||
|
// serialize the props
|
||||||
|
let _ = crate::props_html::serialize_props::encode_in_element(&cfg.props, &mut html);
|
||||||
|
|
||||||
html += &index.post_main;
|
html += &index.post_main;
|
||||||
|
|
||||||
html
|
html
|
||||||
|
|||||||
Reference in new issue
Block a user