Suspense boundaries/out of order streaming/anyhow like error handling (#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 <jkelleyrtp@gmail.com>
This commit is contained in:
Evan AlmloffandJonathan Kelley authored and GitHub committed 2024-07-01 20:50:36 -07:00
1 parent ffa36a67c3
commit 022e4ad203
175 files changed
+7872 -4010

No files matched your search

@@ -66,7 +66,7 @@ pub fn GoBackButton(props: HistoryButtonProps) -> Element {
error!("{msg}, will be inactive");
#[cfg(debug_assertions)]
panic!("{}", msg);
return None;
return VNode::empty();
}
};
@@ -137,7 +137,7 @@ pub fn GoForwardButton(props: HistoryButtonProps) -> Element {
error!("{msg}, will be inactive");
#[cfg(debug_assertions)]
panic!("{}", msg);
return None;
return VNode::empty();
}
};
+1 -1
View File
@@ -222,7 +222,7 @@ pub fn Link(props: LinkProps) -> Element {
error!("{msg}, will be inactive");
#[cfg(debug_assertions)]
panic!("{}", msg);
return None;
return VNode::empty();
}
};
+11 -51
View File
@@ -1,6 +1,6 @@
use dioxus_lib::prelude::*;
use std::{cell::RefCell, rc::Rc, str::FromStr};
use std::str::FromStr;
use crate::{
prelude::{provide_router_context, Outlet},
@@ -8,66 +8,30 @@ use crate::{
router_cfg::RouterConfig,
};
/// The config for [`Router`].
#[derive(Clone)]
pub struct RouterConfigFactory<R: Routable> {
#[allow(clippy::type_complexity)]
config: Rc<RefCell<Option<Box<dyn FnOnce() -> RouterConfig<R>>>>>,
}
impl<R: Routable> Default for RouterConfigFactory<R>
where
<R as FromStr>::Err: std::fmt::Display,
{
fn default() -> Self {
Self::from(RouterConfig::default)
}
}
impl<R: Routable, F: FnOnce() -> RouterConfig<R> + 'static> From<F> for RouterConfigFactory<R> {
fn from(value: F) -> Self {
Self {
config: Rc::new(RefCell::new(Some(Box::new(value)))),
}
}
}
/// The props for [`Router`].
#[derive(Props)]
pub struct RouterProps<R: Routable>
where
<R as FromStr>::Err: std::fmt::Display,
{
pub struct RouterProps<R: Clone + 'static> {
#[props(default, into)]
config: RouterConfigFactory<R>,
config: Callback<(), RouterConfig<R>>,
}
impl<T: Routable> Clone for RouterProps<T>
where
<T as FromStr>::Err: std::fmt::Display,
{
impl<T: Clone> Clone for RouterProps<T> {
fn clone(&self) -> Self {
Self {
config: self.config.clone(),
}
*self
}
}
impl<R: Routable> Default for RouterProps<R>
where
<R as FromStr>::Err: std::fmt::Display,
{
impl<T: Clone> Copy for RouterProps<T> {}
impl<R: Clone + 'static> Default for RouterProps<R> {
fn default() -> Self {
Self {
config: RouterConfigFactory::default(),
config: Callback::new(|_| RouterConfig::default()),
}
}
}
impl<R: Routable> PartialEq for RouterProps<R>
where
<R as FromStr>::Err: std::fmt::Display,
{
impl<R: Clone> PartialEq for RouterProps<R> {
fn eq(&self, _: &Self) -> bool {
// prevent the router from re-rendering when the initial url or config changes
true
@@ -83,11 +47,7 @@ where
use_hook(|| {
provide_router_context(RouterContext::new(
(props
.config
.config
.take()
.expect("use_context_provider ran twice"))(),
props.config.call(()),
schedule_update_any(),
));
+4 -4
View File
@@ -41,11 +41,11 @@ impl<R> OutletContext<R> {
});
if let Some(error) = router.render_error() {
if current_level == 0 {
return Some(error);
return if current_level == 0 {
error
} else {
return None;
}
VNode::empty()
};
}
router.current::<R>().render(current_level)
+2 -2
View File
@@ -316,12 +316,12 @@ impl RouterContext {
self.inner.read().site_map
}
pub(crate) fn render_error(&self) -> Element {
pub(crate) fn render_error(&self) -> Option<Element> {
let inner_read = self.inner.write_unchecked();
inner_read
.unresolved_error
.as_ref()
.and_then(|_| (inner_read.failure_external_navigation)())
.map(|_| (inner_read.failure_external_navigation)())
}
fn change_route(&self) -> Option<ExternalNavigationFailure> {
+2 -2
View File
@@ -21,9 +21,9 @@ where
/// # use dioxus_router::prelude::*;
/// # use dioxus::prelude::*;
/// # #[component]
/// # fn Index() -> Element { None }
/// # fn Index() -> Element { VNode::empty() }
/// # #[component]
/// # fn OtherPage() -> Element { None }
/// # fn OtherPage() -> Element { VNode::empty() }
/// #[derive(Clone, Routable, Debug, PartialEq)]
/// enum Route {
/// #[route("/")]
+14 -14
View File
@@ -54,9 +54,9 @@ pub trait HistoryProvider<R: Routable> {
/// # use dioxus_router::prelude::*;
/// # use dioxus::prelude::*;
/// # #[component]
/// # fn Index() -> Element { None }
/// # fn Index() -> Element { VNode::empty() }
/// # #[component]
/// # fn OtherPage() -> Element { None }
/// # fn OtherPage() -> Element { VNode::empty() }
/// #[derive(Clone, Routable, Debug, PartialEq)]
/// enum Route {
/// #[route("/")]
@@ -92,8 +92,8 @@ pub trait HistoryProvider<R: Routable> {
/// # use dioxus_router::prelude::*;
/// # use dioxus::prelude::*;
/// # #[component]
/// # fn Index() -> Element { None }
/// # fn Other() -> Element { None }
/// # fn Index() -> Element { VNode::empty() }
/// # fn Other() -> Element { VNode::empty() }
/// #[derive(Clone, Routable, Debug, PartialEq)]
/// enum Route {
/// #[route("/")]
@@ -121,9 +121,9 @@ pub trait HistoryProvider<R: Routable> {
/// # use dioxus_router::prelude::*;
/// # use dioxus::prelude::*;
/// # #[component]
/// # fn Index() -> Element { None }
/// # fn Index() -> Element { VNode::empty() }
/// # #[component]
/// # fn OtherPage() -> Element { None }
/// # fn OtherPage() -> Element { VNode::empty() }
/// #[derive(Clone, Routable, Debug, PartialEq)]
/// enum Route {
/// #[route("/")]
@@ -153,9 +153,9 @@ pub trait HistoryProvider<R: Routable> {
/// # use dioxus_router::prelude::*;
/// # use dioxus::prelude::*;
/// # #[component]
/// # fn Index() -> Element { None }
/// # fn Index() -> Element { VNode::empty() }
/// # #[component]
/// # fn OtherPage() -> Element { None }
/// # fn OtherPage() -> Element { VNode::empty() }
/// #[derive(Clone, Routable, Debug, PartialEq)]
/// enum Route {
/// #[route("/")]
@@ -186,9 +186,9 @@ pub trait HistoryProvider<R: Routable> {
/// # use dioxus_router::prelude::*;
/// # use dioxus::prelude::*;
/// # #[component]
/// # fn Index() -> Element { None }
/// # fn Index() -> Element { VNode::empty() }
/// # #[component]
/// # fn OtherPage() -> Element { None }
/// # fn OtherPage() -> Element { VNode::empty() }
/// #[derive(Clone, Routable, Debug, PartialEq)]
/// enum Route {
/// #[route("/")]
@@ -219,9 +219,9 @@ pub trait HistoryProvider<R: Routable> {
/// # use dioxus_router::prelude::*;
/// # use dioxus::prelude::*;
/// # #[component]
/// # fn Index() -> Element { None }
/// # fn Index() -> Element { VNode::empty() }
/// # #[component]
/// # fn OtherPage() -> Element { None }
/// # fn OtherPage() -> Element { VNode::empty() }
/// #[derive(Clone, Routable, Debug, PartialEq)]
/// enum Route {
/// #[route("/")]
@@ -248,9 +248,9 @@ pub trait HistoryProvider<R: Routable> {
/// # use dioxus_router::prelude::*;
/// # use dioxus::prelude::*;
/// # #[component]
/// # fn Index() -> Element { None }
/// # fn Index() -> Element { VNode::empty() }
/// # #[component]
/// # fn OtherPage() -> Element { None }
/// # fn OtherPage() -> Element { VNode::empty() }
/// #[derive(Clone, Routable, Debug, PartialEq)]
/// enum Route {
/// #[route("/")]
+5 -6
View File
@@ -15,12 +15,11 @@ use super::{
#[allow(dead_code)]
fn base_path() -> Option<&'static str> {
let base_path = dioxus_cli_config::CURRENT_CONFIG
.as_ref()
.ok()
.and_then(|c| c.dioxus_config.web.app.base_path.as_deref());
tracing::trace!("Using base_path from Dioxus.toml: {:?}", base_path);
base_path
tracing::trace!(
"Using base_path from Dioxus.toml: {:?}",
dioxus_cli_config::BASE_PATH
);
dioxus_cli_config::BASE_PATH
}
#[allow(clippy::extra_unused_type_parameters)]
-86
View File
@@ -1,86 +0,0 @@
//! Extensions to the incremental renderer to support pre-caching static routes.
use core::pin::Pin;
use std::future::Future;
use std::str::FromStr;
use crate::prelude::*;
use dioxus_lib::prelude::*;
/// Pre-cache all static routes.
pub async fn pre_cache_static_routes<Rt, R: WrapBody + Send + Sync>(
renderer: &mut IncrementalRenderer,
wrapper: &R,
) -> Result<(), IncrementalRendererError>
where
Rt: Routable,
<Rt as FromStr>::Err: std::fmt::Display,
{
for route in Rt::static_routes() {
render_route(
renderer,
route,
&mut tokio::io::sink(),
|vdom| {
Box::pin(async move {
vdom.rebuild_in_place();
vdom.wait_for_suspense().await;
})
},
wrapper,
)
.await?;
}
Ok(())
}
/// Render a route to a writer.
pub async fn render_route<
R: WrapBody + Send + Sync,
Rt,
W,
F: FnOnce(&mut VirtualDom) -> Pin<Box<dyn Future<Output = ()> + '_>>,
>(
renderer: &mut IncrementalRenderer,
route: Rt,
writer: &mut W,
modify_vdom: F,
wrapper: &R,
) -> Result<RenderFreshness, IncrementalRendererError>
where
Rt: Routable,
<Rt as FromStr>::Err: std::fmt::Display,
W: tokio::io::AsyncWrite + Unpin + Send,
{
#[derive(Clone)]
struct RootProps<Rt>(Rt);
impl<Rt> PartialEq for RootProps<Rt> {
fn eq(&self, _: &Self) -> bool {
true
}
}
fn RenderPath<R>(props: RootProps<R>) -> Element
where
R: Routable,
<R as FromStr>::Err: std::fmt::Display,
{
let path = props.0;
rsx! {
Router::<R> {
config: || RouterConfig::default().history(MemoryHistory::with_initial_path(path))
}
}
}
renderer
.render(
route.to_string(),
|| VirtualDom::new_with_props(RenderPath, RootProps(route)),
writer,
modify_vdom,
wrapper,
)
.await
}
-8
View File
@@ -8,9 +8,6 @@
pub mod navigation;
pub mod routable;
#[cfg(feature = "ssr")]
pub mod incremental;
/// Components interacting with the router.
pub mod components {
mod default_errors;
@@ -67,11 +64,6 @@ pub mod prelude {
pub use crate::router_cfg::RouterConfig;
pub use dioxus_router_macro::Routable;
#[cfg(feature = "ssr")]
pub use crate::incremental::*;
#[cfg(feature = "ssr")]
pub use dioxus_ssr::incremental::*;
#[doc(hidden)]
/// A component with props used in the macro
pub trait HasProps {
+4 -4
View File
@@ -607,9 +607,9 @@ pub trait Routable: FromStr + Display + Clone + 'static {
/// use dioxus::prelude::*;
///
/// #[component]
/// fn Home() -> Element { None }
/// fn Home() -> Element { VNode::empty() }
/// #[component]
/// fn About() -> Element { None }
/// fn About() -> Element { VNode::empty() }
///
/// #[derive(Routable, Clone, PartialEq, Debug)]
/// enum Route {
@@ -649,9 +649,9 @@ pub trait Routable: FromStr + Display + Clone + 'static {
/// use dioxus::prelude::*;
///
/// #[component]
/// fn Home() -> Element { None }
/// fn Home() -> Element { VNode::empty() }
/// #[component]
/// fn About() -> Element { None }
/// fn About() -> Element { VNode::empty() }
///
/// #[derive(Routable, Clone, PartialEq, Debug)]
/// enum Route {
+4 -7
View File
@@ -10,7 +10,7 @@ use std::sync::Arc;
/// # use dioxus::prelude::*;
/// # #[component]
/// # fn Index() -> Element {
/// # None
/// # VNode::empty()
/// # }
/// #[derive(Clone, Routable)]
/// enum Route {
@@ -19,17 +19,14 @@ use std::sync::Arc;
/// }
/// let cfg = RouterConfig::default().history(MemoryHistory::<Route>::default());
/// ```
pub struct RouterConfig<R: Routable> {
pub struct RouterConfig<R> {
pub(crate) failure_external_navigation: fn() -> Element,
pub(crate) history: Option<Box<dyn AnyHistoryProvider>>,
pub(crate) on_update: Option<RoutingCallback<R>>,
pub(crate) initial_route: Option<R>,
}
impl<R: Routable + Clone> Default for RouterConfig<R>
where
<R as std::str::FromStr>::Err: std::fmt::Display,
{
impl<R> Default for RouterConfig<R> {
fn default() -> Self {
Self {
failure_external_navigation: FailureExternalNavigation,
@@ -131,7 +128,7 @@ where
return Box::new(AnyHistoryProviderImplWrapper::new(
MemoryHistory::<R>::with_initial_path(
dioxus_fullstack::prelude::server_context()
.request_parts_blocking()
.request_parts()
.uri
.to_string()
.parse()