Skip to content

Commit 022e4ad

Browse files
ealmloffjkelleyrtp
andauthored
Suspense boundaries/out of order streaming/anyhow like error handling (DioxusLabs#2365)
* 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 * switch to a result for Element * fix formatting * fix hot reload doctest imports * fix axum imports * add show method to error context * implement retaining nodes during suspense * fix unterminated if statements * communicate between tasks and suspense boundaries * make suspense placeholders easier to use * implement IntoDynNode and IntoVNode for more wrappers * fix clippy examples * fix rsx tests * add streaming html utilities to the ssr package * unify hydration and non-hydration ssr cache * fix router with Result Element * don't run server doc tests * Fix hot reload websocket doc examples * simple apps working with fullstack streaming * fix preloading wasm * Report errors encountered while streaming * remove async from incremental renderer * document new VirtualDom suspense methods * make streaming work with incremental rendering * fix static site generation * document suspense structs * create closure type; allow async event handlers in props; allow shorthand event handlers * test forwarding event handlers with the shorthand syntax * fix clippy * fix imports in spawn async doctest * fix empty rsx * fix async result event handlers * fix mounting router in multiple places * Fix task dead cancel race condition * simplify diffing before adding suspense * fix binary size increase * fix attribute diffing * more diffing fixes * create minimal fullstack feature * smaller fullstack bundles * allow mounting nodes that are already created and creating nodes without mounting them * fix hot reload feature * fix replacing components * don't reclaim virtual nodes * client side suspense working! * fix CLI * slightly smaller fullstack builds * fix multiple suspended scopes * fix merge errors * yield back to tokio every few polls to fix suspending on many tasks at once * remove logs * document suspense boundary and update suspense example * fix ssg * make streaming optional * fix some router and core tests * fix suspense example * fix serialization with out of order server futures * add incremental streaming hackernews demo * fix hackernews demo * fix root hackernews redirect * fix formatting * add tests for suspense cases * slightly smaller binaries * slightly smaller * improve error handling docs * fix errors example link * fix doc tests * remove log file * fix ssr cache type inference * remove index.html * fix ssg render template * fix assigning ids on elements with dynamic attributes * add desktop feature to the workspace examples * remove router static generation example; ssg lives in the dioxus-static-generation package * add a test for effects during suspense * only run effects on mounted nodes * fix multiple suspense roots * fix node iterator * fix closures without arguments * fix dioxus-core readme doctest * remove suspense logs * fix scope stack * fix clippy * remove unused suspense boundary from hackernews * assert that launch never returns for better compiler errors * fix static generation launch function * fix web renderer * pass context providers into server functions * add an example for FromContext * clean up DioxusRouterExt * fix server function context * fix fullstack desktop example * forward CLI serve settings to fullstack * re-export serve config at the root of fullstack * forward env directly instead of using a guard * just set the port in the CLI for fullstack playwright tests * fix fullstack dioxus-cli-config feature * fix launch server merge conflicts * fix fullstack launch context * Merge branch 'main' into suspense-2.0 * fix fullstack html data * remove drop virtual dom feature * add a comment about only_write_templates binary size workaround * remove explicit dependencies from use_server_future * make ErrorContext and SuspenseContext more similar * Tweak: small tweaks to tomls to make diff smaller * only rerun components under suspense after the initial placeholders are sent to the client * add module docs for suspense * keep track of when suspense boundaries are resolved * start implementing JS out of order streaming * fix core tests * implement the server side of suspense with js * fix streaming ssr with nested suspense * move streaming ssr code into fullstack * revert minification changes * serialize server future data as the html streams * start loading scripts wasm immediately instead of defering the script * very basic nested suspense example working with minimal html updates * clean up some suspense/error docs * fix hydrating nested pending server futures * sort resolved boundaries by height * Fix disconnecting clients while streaming * fix static generation crate * don't insert extra divs when hydrating streamed chunks * wait to swap in the elements until they are hydrated * remove inline streaming script * hackernews partially working * fix spa mode * banish the open shadow dom * fix removing placeholder * set up streaming playwright test * run web playwright tests on 9999 to avoid port conflicts with other local servers * remove suspense nodes if the suspense boundary is replaced before the suspense resolves on the server * ignore hydration of removed suspense boundaries * use path based indexing to fix hydrating suspense after parent suspense with child is removed * re-export dioxus error * remove resolved suspense divs if the suspense boundary has been removed * Fix client side initialized server futures * ignore comment nodes while traversing nodes in core to avoid lists getting swapped out with suspense * Pass initial hydration data to the client * hide pre nodes * don't panic if reclaiming an element fails * fix scope stack when polling tasks * improve deserialization out of length message * Ok(VNode::placeholder()) -> VNode::empty() * fix typo in rsx usage * restore testing changes from suspense example * clean up some logs and comments * fix playwright tests * clean up more changes in core * clean up core tests * remove anymap dependency * clean up changes to hooks * clean up changes in the router, rsx, and web * revert changes to axum-hello-world * fix use_server_future * fix clippy in dioxus-core * check that the next or previous node exist before checking if we should ignore them * fix formatting * fix suspense playwright test * remove unused suspense code * add more suspense playwright tests * add more docs for error boundaries * fix suspense core tests * fix ErrorBoundary example * remove a bunch of debug logging in js * fix router failure_external_navigation * use absolute paths in the interpreter build.rs * strip '\r' while hashing ts files * add a wrapper with a default error boundary and suspense boundary * restore hot reloading * ignore non-ts files when hashing * sort ts files before hashing them * fix rsx tests * fix fullstack doc tests * fix core tests * fix axum auth example * update suspense hydration diagram * longer playwright build limit * tiny fixes - spelling, formatting * update diagram link * remove comment and template nodes for suspense placeholders * remove comment nodes as we hydrate text * simplify hackernews example * clean up hydrating text nodes * switch to a separate environment variable for the base path for smaller binaries * clean up file system html trait * fix form data * move streaming code into fullstack * implement serialize and deserialize for CapturedError * remove waits in the nested suspense playwright spec * force sequential fullstack builds for CI * longer nested suspense delay for CI * fix --force-sequential flag * wait to launch server until client build is done --------- Co-authored-by: Jonathan Kelley <[email protected]>
1 parent ffa36a6 commit 022e4ad

File tree

175 files changed

+7835
-3973
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

175 files changed

+7835
-3973
lines changed

Cargo.lock

Lines changed: 350 additions & 75 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ members = [
3535
"packages/fullstack/examples/axum-streaming",
3636
"packages/fullstack/examples/axum-desktop",
3737
"packages/fullstack/examples/axum-auth",
38+
"packages/fullstack/examples/hackernews",
3839
"packages/static-generation/examples/simple",
3940
"packages/static-generation/examples/router",
4041
"packages/static-generation/examples/github-pages",
@@ -46,6 +47,8 @@ members = [
4647
"packages/playwright-tests/liveview",
4748
"packages/playwright-tests/web",
4849
"packages/playwright-tests/fullstack",
50+
"packages/playwright-tests/suspense-carousel",
51+
"packages/playwright-tests/nested-suspense",
4952
]
5053
exclude = ["examples/mobile_demo", "examples/openid_connect_demo"]
5154

@@ -61,10 +64,10 @@ dioxus-core-macro = { path = "packages/core-macro", version = "0.5.0" }
6164
dioxus-config-macro = { path = "packages/config-macro", version = "0.5.0" }
6265
dioxus-router = { path = "packages/router", version = "0.5.0" }
6366
dioxus-router-macro = { path = "packages/router-macro", version = "0.5.0" }
64-
dioxus-html = { path = "packages/html", version = "0.5.0" }
67+
dioxus-html = { path = "packages/html", default-features = false, version = "0.5.0" }
6568
dioxus-html-internal-macro = { path = "packages/html-internal-macro", version = "0.5.0" }
6669
dioxus-hooks = { path = "packages/hooks", version = "0.5.0" }
67-
dioxus-web = { path = "packages/web", version = "0.5.0" }
70+
dioxus-web = { path = "packages/web", default-features = false, version = "0.5.0" }
6871
dioxus-ssr = { path = "packages/ssr", version = "0.5.0", default-features = false }
6972
dioxus-desktop = { path = "packages/desktop", version = "0.5.0", default-features = false }
7073
dioxus-mobile = { path = "packages/mobile", version = "0.5.0" }
@@ -118,6 +121,9 @@ axum_session_auth = "0.12.1"
118121
axum-extra = "0.9.2"
119122
reqwest = "0.11.24"
120123
owo-colors = "4.0.0"
124+
ciborium = "0.2.1"
125+
base64 = "0.21.0"
126+
once_cell = "1.17.1"
121127

122128
# speed up some macros by optimizing them
123129
[profile.dev.package.insta]
@@ -275,7 +281,7 @@ required-features = ["desktop"]
275281
doc-scrape-examples = true
276282

277283
[[example]]
278-
name = "error_handle"
284+
name = "errors"
279285
required-features = ["desktop"]
280286
doc-scrape-examples = true
281287

examples/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ cargo run --example hello_world
7474

7575
[disabled](./disabled.rs) - Disable buttons conditionally
7676

77-
[error_handle](./error_handle.rs) - Handle errors with early return
77+
[errors](./errors.rs) - Handle errors with early return
7878

7979
## Routing
8080

examples/error_handle.rs

Lines changed: 0 additions & 55 deletions
This file was deleted.

examples/errors.rs

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
//! This example showcases how to use the ErrorBoundary component to handle errors in your app.
2+
//!
3+
//! The ErrorBoundary component is a special component that can be used to catch panics and other errors that occur.
4+
//! By default, Dioxus will catch panics during rendering, async, and handlers, and bubble them up to the nearest
5+
//! error boundary. If no error boundary is present, it will be caught by the root error boundary and the app will
6+
//! render the error message as just a string.
7+
//!
8+
//! NOTE: In wasm, panics can currently not be caught by the error boundary. This is a limitation of WASM in rust.
9+
#![allow(non_snake_case)]
10+
11+
use dioxus::prelude::*;
12+
13+
fn main() {
14+
launch(|| rsx! { Router::<Route> {} });
15+
}
16+
17+
/// You can use an ErrorBoundary to catch errors in children and display a warning
18+
fn Simple() -> Element {
19+
rsx! {
20+
GoBackButton { "Home" }
21+
ErrorBoundary {
22+
handle_error: |error: ErrorContext| rsx! {
23+
h1 { "An error occurred" }
24+
pre { "{error:#?}" }
25+
},
26+
ParseNumber {}
27+
}
28+
}
29+
}
30+
31+
#[component]
32+
fn ParseNumber() -> Element {
33+
rsx! {
34+
h1 { "Error handler demo" }
35+
button {
36+
onclick: move |_| {
37+
// You can return a result from an event handler which lets you easily quit rendering early if something fails
38+
let data: i32 = "0.5".parse()?;
39+
40+
println!("parsed {data}");
41+
42+
Ok(())
43+
},
44+
"Click to throw an error"
45+
}
46+
}
47+
}
48+
49+
// You can provide additional context for the Error boundary to visualize
50+
fn Show() -> Element {
51+
rsx! {
52+
GoBackButton { "Home" }
53+
div {
54+
ErrorBoundary {
55+
handle_error: |errors: ErrorContext| {
56+
rsx! {
57+
for error in errors.errors() {
58+
if let Some(error) = error.show() {
59+
{error}
60+
} else {
61+
pre {
62+
color: "red",
63+
"{error}"
64+
}
65+
}
66+
}
67+
}
68+
},
69+
ParseNumberWithShow {}
70+
}
71+
}
72+
}
73+
}
74+
75+
#[component]
76+
fn ParseNumberWithShow() -> Element {
77+
rsx! {
78+
h1 { "Error handler demo" }
79+
button {
80+
onclick: move |_| {
81+
let request_data = "0.5";
82+
let data: i32 = request_data.parse()
83+
// You can attach rsx to results that can be displayed in the Error Boundary
84+
.show(|_| rsx!{
85+
div {
86+
background_color: "red",
87+
border: "black",
88+
border_width: "2px",
89+
border_radius: "5px",
90+
p { "Failed to parse data" }
91+
Link {
92+
to: Route::Home {},
93+
"Go back to the homepage"
94+
}
95+
}
96+
})?;
97+
98+
println!("parsed {data}");
99+
100+
Ok(())
101+
},
102+
"Click to throw an error"
103+
}
104+
}
105+
}
106+
107+
// On desktop, dioxus will catch panics in components and insert an error automatically
108+
fn Panic() -> Element {
109+
rsx! {
110+
GoBackButton { "Home" }
111+
ErrorBoundary {
112+
handle_error: |errors: ErrorContext| rsx! {
113+
h1 { "Another error occurred" }
114+
pre { "{errors:#?}" }
115+
},
116+
ComponentPanic {}
117+
}
118+
}
119+
}
120+
121+
#[component]
122+
fn ComponentPanic() -> Element {
123+
panic!("This component panics")
124+
}
125+
126+
#[derive(Routable, Clone, Debug, PartialEq)]
127+
enum Route {
128+
#[route("/")]
129+
Home {},
130+
#[route("/simple")]
131+
Simple {},
132+
#[route("/panic")]
133+
Panic {},
134+
#[route("/show")]
135+
Show {},
136+
}
137+
138+
fn Home() -> Element {
139+
rsx! {
140+
ul {
141+
li {
142+
Link {
143+
to: Route::Simple {},
144+
"Simple errors"
145+
}
146+
}
147+
li {
148+
Link {
149+
to: Route::Panic {},
150+
"Capture panics"
151+
}
152+
}
153+
li {
154+
Link {
155+
to: Route::Show {},
156+
"Show errors"
157+
}
158+
}
159+
}
160+
}
161+
}

examples/rsx_usage.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -170,13 +170,13 @@ fn app() -> Element {
170170

171171
// Can pass in props directly as an expression
172172
{
173-
let props = TallerProps {a: "hello", children: None };
173+
let props = TallerProps {a: "hello", children: VNode::empty() };
174174
rsx!(Taller { ..props })
175175
}
176176

177177
// Spreading can also be overridden manually
178178
Taller {
179-
..TallerProps { a: "ballin!", children: None },
179+
..TallerProps { a: "ballin!", children: VNode::empty() },
180180
a: "not ballin!"
181181
}
182182

@@ -193,8 +193,8 @@ fn app() -> Element {
193193
// Type inference can be used too
194194
TypedInput { initial: 10.0 }
195195

196-
// geneircs with the `inline_props` macro
197-
Label { text: "hello geneirc world!" }
196+
// generic with the `inline_props` macro
197+
Label { text: "hello generic world!" }
198198
Label { text: 99.9 }
199199

200200
// Lowercase components work too, as long as they are access using a path
@@ -283,7 +283,7 @@ where
283283
return rsx! { "{props}" };
284284
}
285285

286-
None
286+
VNode::empty()
287287
}
288288

289289
#[component]

examples/shorthand.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ fn app() -> Element {
2222
rsx! {
2323
div { class, id, {&children} }
2424
Component { a, b, c, children, onclick }
25-
Component { a, ..ComponentProps { a: 1, b: 2, c: 3, children: None, onclick: Default::default() } }
25+
Component { a, ..ComponentProps { a: 1, b: 2, c: 3, children: VNode::empty(), onclick: Default::default() } }
2626
}
2727
}
2828

examples/suspense.rs

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,14 @@ fn app() -> Element {
3939
}
4040

4141
h3 { "Illustrious Dog Photo" }
42-
Doggo {}
42+
SuspenseBoundary {
43+
fallback: move |suspense: SuspenseContext| suspense.suspense_placeholder().unwrap_or_else(|| rsx! {
44+
div {
45+
"Loading..."
46+
}
47+
}),
48+
Doggo {}
49+
}
4350
}
4451
}
4552
}
@@ -49,7 +56,7 @@ fn app() -> Element {
4956
/// actually renders the data.
5057
#[component]
5158
fn Doggo() -> Element {
52-
let mut fut = use_resource(move || async move {
59+
let mut resource = use_resource(move || async move {
5360
#[derive(serde::Deserialize)]
5461
struct DogApi {
5562
message: String,
@@ -62,12 +69,26 @@ fn Doggo() -> Element {
6269
.await
6370
});
6471

65-
match fut.read_unchecked().as_ref() {
66-
Some(Ok(resp)) => rsx! {
67-
button { onclick: move |_| fut.restart(), "Click to fetch another doggo" }
72+
// You can suspend the future and only continue rendering when it's ready
73+
let value = resource.suspend().with_loading_placeholder(|| {
74+
rsx! {
75+
div {
76+
"Loading doggos..."
77+
}
78+
}
79+
})?;
80+
81+
match value.read_unchecked().as_ref() {
82+
Ok(resp) => rsx! {
83+
button { onclick: move |_| resource.restart(), "Click to fetch another doggo" }
6884
div { img { max_width: "500px", max_height: "500px", src: "{resp.message}" } }
6985
},
70-
Some(Err(_)) => rsx! { div { "loading dogs failed" } },
71-
None => rsx! { div { "loading dogs..." } },
86+
Err(_) => rsx! {
87+
div { "loading dogs failed" }
88+
button {
89+
onclick: move |_| resource.restart(),
90+
"retry"
91+
}
92+
},
7293
}
7394
}

0 commit comments

Comments
 (0)