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

+2 -13
View File
@@ -43,8 +43,8 @@ wasm-bindgen-test = "0.3.33"
[features]
default = []
ssr = ["dioxus-ssr/incremental", "dep:tokio", "dioxus-fullstack?/server", "dep:http"]
liveview = ["dep:dioxus-liveview", "dep:tokio", "dep:serde", "dep:serde_json"]
ssr = []
liveview = ["dioxus-liveview", "dep:tokio", "dep:serde", "dep:serde_json"]
wasm_test = []
web = ["dep:gloo", "dep:web-sys", "dep:wasm-bindgen", "dep:gloo-utils", "dep:js-sys", "dioxus-router-macro/web"]
fullstack = ["dep:dioxus-fullstack"]
@@ -60,12 +60,6 @@ serde = { version = "1", features = ["derive"] }
[package.metadata.docs.rs]
cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"]
[[bench]]
name = "incremental"
required-features = ["ssr"]
harness = false
# Most of the examples live in the workspace. We include some here so that docs.rs can scrape our examples for better inline docs
[[example]]
name = "crm"
@@ -101,8 +95,3 @@ doc-scrape-examples = true
name = "simple_router"
path = "../../examples/simple_router.rs"
doc-scrape-examples = true
[[example]]
name = "static_generation"
required-features = ["ssr"]
doc-scrape-examples = true
-207
View File
@@ -1,207 +0,0 @@
#![allow(unused)]
use std::time::Duration;
use dioxus::prelude::*;
use dioxus_router::prelude::*;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use dioxus_ssr::Renderer;
pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("build 1000 routes", |b| {
let mut renderer = IncrementalRenderer::builder()
.static_dir("./static")
.invalidate_after(Duration::from_secs(10))
.build();
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(),
};
b.iter(|| {
tokio::runtime::Runtime::new().unwrap().block_on(async {
for id in 0..1000 {
render_route(
&mut renderer,
Route::Post { id },
&mut tokio::io::sink(),
|_| Box::pin(async move {}),
&wrapper,
)
.await
.unwrap();
}
})
})
});
c.bench_function("build 1000 routes no memory cache", |b| {
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(),
};
b.to_async(tokio::runtime::Runtime::new().unwrap())
.iter(|| async {
let mut renderer = IncrementalRenderer::builder()
.static_dir("./static")
.memory_cache_limit(0)
.invalidate_after(Duration::from_secs(10))
.build();
for id in 0..1000 {
render_route(
&mut renderer,
Route::Post { id },
&mut tokio::io::sink(),
|_| Box::pin(async move {}),
&wrapper,
)
.await
.unwrap();
}
})
});
c.bench_function("build 1000 routes no cache", |b| {
let mut renderer = Renderer::default();
b.iter(|| {
for id in 0..1000 {
let mut vdom = VirtualDom::new_with_props(
RenderPath,
RenderPathProps::builder().path(Route::Post { id }).build(),
);
vdom.rebuild_in_place();
struct Ignore;
impl std::fmt::Write for Ignore {
fn write_str(&mut self, s: &str) -> std::fmt::Result {
Ok(())
}
}
renderer.render_to(&mut Ignore, &vdom).unwrap();
}
})
});
c.bench_function("cache static", |b| {
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(),
};
b.to_async(tokio::runtime::Runtime::new().unwrap())
.iter(|| async {
let mut renderer = IncrementalRenderer::builder()
.static_dir("./static")
.build();
pre_cache_static_routes::<Route, _>(&mut renderer, &wrapper)
.await
.unwrap();
})
});
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);
#[component]
fn Blog() -> Element {
rsx! {
div {
"Blog"
}
}
}
#[component]
fn Post(id: usize) -> Element {
rsx! {
for _ in 0..id {
div {
"PostId: {id}"
}
}
}
}
#[component]
fn PostHome() -> Element {
rsx! {
div {
"Post"
}
}
}
#[component]
fn Home() -> Element {
rsx! {
div {
"Home"
}
}
}
#[rustfmt::skip]
#[derive(Clone, Debug, PartialEq, Routable)]
enum Route {
#[nest("/blog")]
#[route("/")]
Blog {},
#[route("/post/index")]
PostHome {},
#[route("/post/:id")]
Post {
id: usize,
},
#[end_nest]
#[route("/")]
Home {},
}
#[component]
fn RenderPath(path: Route) -> Element {
let path = path.clone();
rsx! {
Router::<Route> {
config: || RouterConfig::default().history(MemoryHistory::with_initial_path(path))
}
}
}
+1 -1
View File
@@ -160,7 +160,7 @@ fn Route3(dynamic: String) -> Element {
}
}
else {
None
VNode::empty()
}
}
Err(err) => {
@@ -1,105 +0,0 @@
use std::time::Duration;
use dioxus::prelude::*;
use dioxus_ssr::incremental::{DefaultRenderer, IncrementalRendererConfig};
#[tokio::main]
async fn main() {
let mut renderer = IncrementalRendererConfig::new()
.static_dir("./static")
.invalidate_after(Duration::from_secs(10))
.build();
println!(
"SITE MAP:\n{}",
Route::SITE_MAP
.iter()
.flat_map(|route| route.flatten().into_iter())
.map(|route| {
route
.iter()
.map(|segment| segment.to_string())
.collect::<Vec<_>>()
.join("")
})
.collect::<Vec<_>>()
.join("\n")
);
// This function is available if you enable the ssr feature
// on the dioxus::router crate.
pre_cache_static_routes::<Route, _>(
&mut renderer,
&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(),
},
)
.await
.unwrap();
}
#[component]
fn Blog() -> Element {
rsx! {
div {
"Blog"
}
}
}
#[component]
fn Post(id: usize) -> Element {
rsx! {
div {
"PostId: {id}"
}
}
}
#[component]
fn PostHome() -> Element {
rsx! {
div {
"Post"
}
}
}
#[component]
fn Home() -> Element {
rsx! {
div {
"Home"
}
}
}
#[rustfmt::skip]
#[derive(Clone, Debug, PartialEq, Routable)]
enum Route {
#[nest("/blog")]
#[route("/")]
Blog {},
#[route("/post/index")]
PostHome {},
#[route("/post/:id")]
Post {
id: usize,
},
#[end_nest]
#[route("/")]
Home {},
}
@@ -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()
+1 -1
View File
@@ -41,7 +41,7 @@ where
rsx! {
h1 { "App" }
Router::<R> {
config: || RouterConfig::default().history(MemoryHistory::default())
config: |_| RouterConfig::default().history(MemoryHistory::default())
}
}
}
+10 -6
View File
@@ -4,11 +4,16 @@ use dioxus::prelude::*;
use dioxus_router::prelude::*;
fn prepare(path: impl Into<String>) -> VirtualDom {
let mut vdom = VirtualDom::new_with_props(App, AppProps { path: path.into() });
let mut vdom = VirtualDom::new_with_props(
App,
AppProps {
path: path.into().parse().unwrap(),
},
);
vdom.rebuild_in_place();
return vdom;
#[derive(Routable, Clone)]
#[derive(Routable, Clone, PartialEq)]
#[rustfmt::skip]
enum Route {
#[route("/")]
@@ -30,13 +35,12 @@ fn prepare(path: impl Into<String>) -> VirtualDom {
}
#[component]
fn App(path: String) -> Element {
fn App(path: Route) -> Element {
rsx! {
h1 { "App" }
Router::<Route> {
config: {
let path = path.parse().unwrap();
move || RouterConfig::default().history(MemoryHistory::with_initial_path(path))
config: move |_| {
RouterConfig::default().history(MemoryHistory::with_initial_path(path.clone()))
}
}
}