Fix ssg race condition (#3521)
* Fix server crash logging and opening fullstack apps * Fix race condition during SSG * move route prerendering into a separate stage * Fix static site generation with dx serve * only proxy the fullstack server if ssg is disabled * test nested-suspense with ssg in playwright
This commit is contained in:
1 parent
343c48f5ad
commit
ecc419df0a
18 files changed
+370
-214
No files matched your search
@@ -0,0 +1,114 @@
|
||||
// This test is used by playwright configured in the root of the repo
|
||||
// Tests:
|
||||
// - SEO without JS
|
||||
// - Streaming hydration
|
||||
// - Suspense
|
||||
// - Server functions
|
||||
//
|
||||
// Without Javascript, content may not load into the right location, but it should still be somewhere in the html even if it is invisible
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub fn app() -> Element {
|
||||
rsx! {
|
||||
SuspenseBoundary {
|
||||
fallback: move |_| rsx! {},
|
||||
document::Style {
|
||||
href: asset!("/assets/style.css")
|
||||
}
|
||||
LoadTitle {}
|
||||
}
|
||||
MessageWithLoader { id: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn MessageWithLoader(id: usize) -> Element {
|
||||
rsx! {
|
||||
SuspenseBoundary {
|
||||
fallback: move |_| rsx! {
|
||||
"Loading {id}..."
|
||||
},
|
||||
Message { id }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn LoadTitle() -> Element {
|
||||
let title = use_server_future(move || server_content(0))?()
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
rsx! {
|
||||
"title loaded"
|
||||
document::Title { "{title.title}" }
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Message(id: usize) -> Element {
|
||||
let message = use_server_future(move || server_content(id))?()
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
rsx! {
|
||||
h2 {
|
||||
id: "title-{id}",
|
||||
"{message.title}"
|
||||
}
|
||||
p {
|
||||
id: "body-{id}",
|
||||
"{message.body}"
|
||||
}
|
||||
div {
|
||||
id: "children-{id}",
|
||||
padding: "10px",
|
||||
for child in message.children {
|
||||
MessageWithLoader { id: child }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct Content {
|
||||
title: String,
|
||||
body: String,
|
||||
children: Vec<usize>,
|
||||
}
|
||||
|
||||
#[server]
|
||||
async fn server_content(id: usize) -> Result<Content, ServerFnError> {
|
||||
let content_tree = [
|
||||
Content {
|
||||
title: "The robot says hello world".to_string(),
|
||||
body: "The robot becomes sentient and says hello world".to_string(),
|
||||
children: vec![1, 2, 3],
|
||||
},
|
||||
Content {
|
||||
title: "The world says hello back".to_string(),
|
||||
body: "In a stunning turn of events, the world collectively unites and says hello back"
|
||||
.to_string(),
|
||||
children: vec![4],
|
||||
},
|
||||
Content {
|
||||
title: "Goodbye Robot".to_string(),
|
||||
body: "The robot says goodbye".to_string(),
|
||||
children: vec![],
|
||||
},
|
||||
Content {
|
||||
title: "Goodbye World".to_string(),
|
||||
body: "The world says goodbye".to_string(),
|
||||
children: vec![],
|
||||
},
|
||||
Content {
|
||||
title: "Hello World".to_string(),
|
||||
body: "The world says hello again".to_string(),
|
||||
children: vec![],
|
||||
},
|
||||
];
|
||||
tokio::time::sleep(std::time::Duration::from_millis(1000)).await;
|
||||
Ok(content_tree[id].clone())
|
||||
}
|
||||
@@ -1,123 +1,12 @@
|
||||
// This test is used by playwright configured in the root of the repo
|
||||
// Tests:
|
||||
// - SEO without JS
|
||||
// - Streaming hydration
|
||||
// - Suspense
|
||||
// - Server functions
|
||||
//
|
||||
// Without Javascript, content may not load into the right location, but it should still be somewhere in the html even if it is invisible
|
||||
|
||||
#![allow(non_snake_case)]
|
||||
use dioxus::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use nested_suspense::app;
|
||||
|
||||
fn main() {
|
||||
LaunchBuilder::new()
|
||||
.with_cfg(server_only! {
|
||||
dioxus::fullstack::ServeConfig::builder().enable_out_of_order_streaming()
|
||||
ServeConfig::builder()
|
||||
.enable_out_of_order_streaming()
|
||||
})
|
||||
.launch(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
rsx! {
|
||||
SuspenseBoundary {
|
||||
fallback: move |_| rsx! {},
|
||||
document::Style {
|
||||
href: asset!("/assets/style.css")
|
||||
}
|
||||
LoadTitle {}
|
||||
}
|
||||
MessageWithLoader { id: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn MessageWithLoader(id: usize) -> Element {
|
||||
rsx! {
|
||||
SuspenseBoundary {
|
||||
fallback: move |_| rsx! {
|
||||
"Loading {id}..."
|
||||
},
|
||||
Message { id }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn LoadTitle() -> Element {
|
||||
let title = use_server_future(move || server_content(0))?()
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
rsx! {
|
||||
"title loaded"
|
||||
document::Title { "{title.title}" }
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Message(id: usize) -> Element {
|
||||
let message = use_server_future(move || server_content(id))?()
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
rsx! {
|
||||
h2 {
|
||||
id: "title-{id}",
|
||||
"{message.title}"
|
||||
}
|
||||
p {
|
||||
id: "body-{id}",
|
||||
"{message.body}"
|
||||
}
|
||||
div {
|
||||
id: "children-{id}",
|
||||
padding: "10px",
|
||||
for child in message.children {
|
||||
MessageWithLoader { id: child }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct Content {
|
||||
title: String,
|
||||
body: String,
|
||||
children: Vec<usize>,
|
||||
}
|
||||
|
||||
#[server]
|
||||
async fn server_content(id: usize) -> Result<Content, ServerFnError> {
|
||||
let content_tree = [
|
||||
Content {
|
||||
title: "The robot says hello world".to_string(),
|
||||
body: "The robot becomes sentient and says hello world".to_string(),
|
||||
children: vec![1, 2, 3],
|
||||
},
|
||||
Content {
|
||||
title: "The world says hello back".to_string(),
|
||||
body: "In a stunning turn of events, the world collectively unites and says hello back"
|
||||
.to_string(),
|
||||
children: vec![4],
|
||||
},
|
||||
Content {
|
||||
title: "Goodbye Robot".to_string(),
|
||||
body: "The robot says goodbye".to_string(),
|
||||
children: vec![],
|
||||
},
|
||||
Content {
|
||||
title: "Goodbye World".to_string(),
|
||||
body: "The world says goodbye".to_string(),
|
||||
children: vec![],
|
||||
},
|
||||
Content {
|
||||
title: "Hello World".to_string(),
|
||||
body: "The world says hello again".to_string(),
|
||||
children: vec![],
|
||||
},
|
||||
];
|
||||
tokio::time::sleep(std::time::Duration::from_millis(1000)).await;
|
||||
Ok(content_tree[id].clone())
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#![allow(non_snake_case)]
|
||||
use dioxus::prelude::*;
|
||||
use nested_suspense::app;
|
||||
|
||||
fn main() {
|
||||
dioxus::LaunchBuilder::new()
|
||||
.with_cfg(server_only! {
|
||||
ServeConfig::builder()
|
||||
.incremental(
|
||||
IncrementalRendererConfig::new()
|
||||
.static_dir(
|
||||
std::env::current_exe()
|
||||
.unwrap()
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join("public")
|
||||
)
|
||||
)
|
||||
.enable_out_of_order_streaming()
|
||||
})
|
||||
.launch(app);
|
||||
}
|
||||
|
||||
#[server(endpoint = "static_routes")]
|
||||
async fn static_routes() -> Result<Vec<String>, ServerFnError> {
|
||||
Ok(vec!["/".to_string()])
|
||||
}
|
||||
Reference in new issue
Block a user