codify "platform" into resolution logic, server-fn/fullstack overhaul (#4596)

overhaul dioxus fullstack using a new implementation of server_fn

This PR is a complete rewrite of dioxus fullstack designed to improve compatibility with axum and to fix a number of compilation errors based on feature flag unification plumbing through macros.
This commit is contained in:
Jonathan Kelley authored and GitHub committed 2025-10-03 18:26:19 -07:00
1 parent 8421c3aef0
commit f2b1c6d97f
424 files changed
+17284 -6624

No files matched your search

+1 -1
View File
@@ -85,7 +85,7 @@ impl<F: ComponentFunction<P, M> + Clone, P: Clone + 'static, M: 'static> AnyProp
{
tracing::error!("Panic while rendering component `{_name}`: {err:?}");
}
Element::Err(CapturedPanic { error: err }.into())
Element::Err(CapturedPanic(err).into())
}
}
}
+11 -20
View File
@@ -9,8 +9,8 @@ use crate::{
ElementRef, MountId, ScopeOrder, SuspenseBoundaryProps, SuspenseBoundaryPropsWithOwner,
VComponent, WriteMutations,
},
nodes::{AsVNode, VNode},
scopes::ScopeId,
nodes::VNode,
scopes::{LastRenderedNode, ScopeId},
virtual_dom::VirtualDom,
Element, SuspenseContext,
};
@@ -50,10 +50,9 @@ impl VirtualDom {
// If it is suspended, we need to diff it but write the mutations nothing
// Note: It is important that we still diff the scope even if it is suspended, because the scope may render other child components which may change between renders
let mut render_to = to.filter(|_| self.runtime.scope_should_render(scope));
old.as_vnode()
.diff_node(new_real_nodes, self, render_to.as_deref_mut());
old.diff_node(new_real_nodes, self, render_to.as_deref_mut());
self.scopes[scope.0].last_rendered_node = Some(new_nodes);
self.scopes[scope.0].last_rendered_node = Some(LastRenderedNode::new(new_nodes));
if render_to.is_some() {
self.runtime.get_state(scope).unwrap().mount(&self.runtime);
@@ -69,7 +68,7 @@ impl VirtualDom {
&mut self,
to: Option<&mut M>,
scope: ScopeId,
new_nodes: Element,
new_nodes: LastRenderedNode,
parent: Option<ElementRef>,
) -> usize {
self.runtime.clone().with_scope_on_stack(scope, || {
@@ -79,9 +78,7 @@ impl VirtualDom {
let mut render_to = to.filter(|_| self.runtime.scope_should_render(scope));
// Create the node
let nodes = new_nodes
.as_vnode()
.create(self, parent, render_to.as_deref_mut());
let nodes = new_nodes.create(self, parent, render_to.as_deref_mut());
// Then set the new node as the last rendered node
self.scopes[scope.0].last_rendered_node = Some(new_nodes);
@@ -105,13 +102,8 @@ impl VirtualDom {
SuspenseContext::remove_suspended_nodes::<M>(self, scope_id, destroy_component_state);
// Remove the component from the dom
if let Some(node) = self.scopes[scope_id.0].last_rendered_node.as_ref() {
node.clone().as_vnode().remove_node_inner(
self,
to,
destroy_component_state,
replace_with,
)
if let Some(node) = self.scopes[scope_id.0].last_rendered_node.clone() {
node.remove_node_inner(self, to, destroy_component_state, replace_with)
};
if destroy_component_state {
@@ -210,16 +202,15 @@ impl VNode {
let new = dom.run_scope(scope_id);
// Then set the new node as the last rendered node
dom.scopes[scope_id.0].last_rendered_node = Some(new);
dom.scopes[scope_id.0].last_rendered_node = Some(LastRenderedNode::new(new));
}
let scope = ScopeId(dom.get_mounted_dyn_node(mount, idx));
let new_node = dom.scopes[scope.0]
.last_rendered_node
.as_ref()
.expect("Component to be mounted")
.clone();
.clone()
.expect("Component to be mounted");
dom.create_scope(to, scope, new_node, parent)
}
+41 -455
View File
@@ -1,18 +1,29 @@
use crate::{
global_context::current_scope_id, innerlude::provide_context, try_consume_context, use_hook,
Element, IntoDynNode, Properties, ReactiveContext, ScopeId, Subscribers, Template,
TemplateAttribute, TemplateNode, VNode,
innerlude::{provide_context, CapturedError},
try_consume_context, use_hook, Element, IntoDynNode, Properties, ReactiveContext, Subscribers,
Template, TemplateAttribute, TemplateNode, VNode,
};
use std::{
any::Any,
backtrace::Backtrace,
cell::{Ref, RefCell},
error::Error,
cell::RefCell,
fmt::{Debug, Display},
rc::Rc,
str::FromStr,
};
/// Return early with an error.
#[macro_export]
macro_rules! bail {
($msg:literal $(,)?) => {
return $crate::internal::Err($crate::internal::__anyhow!($msg).into())
};
($err:expr $(,)?) => {
return $crate::internal::Err($crate::internal::__anyhow!($err).into())
};
($fmt:expr, $($arg:tt)*) => {
return $crate::internal::Err($crate::internal::__anyhow!($fmt, $($arg)*).into())
};
}
/// A panic in a component that was caught by an error boundary.
///
/// <div class="warning">
@@ -20,12 +31,9 @@ use std::{
/// WASM currently does not support caching unwinds, so this struct will not be created in WASM.
///
/// </div>
pub struct CapturedPanic {
#[allow(dead_code)]
/// The error that was caught
pub error: Box<dyn Any + 'static>,
}
pub(crate) struct CapturedPanic(pub(crate) Box<dyn Any + Send + 'static>);
unsafe impl Sync for CapturedPanic {}
impl std::error::Error for CapturedPanic {}
impl Debug for CapturedPanic {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CapturedPanic").finish()
@@ -34,12 +42,10 @@ impl Debug for CapturedPanic {
impl Display for CapturedPanic {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("Encountered panic: {:?}", self.error))
f.write_fmt(format_args!("Encountered panic: {:?}", self.0))
}
}
impl Error for CapturedPanic {}
/// A context supplied by fullstack to create hydration compatible error boundaries. Generally, this
/// is not present and the default in memory error boundary is used. If fullstack is enabled, it will
/// provide its own factory that handles syncing errors to the hydration context
@@ -48,7 +54,7 @@ struct CreateErrorBoundary(fn() -> ErrorContext);
impl Default for CreateErrorBoundary {
fn default() -> Self {
Self(|| ErrorContext::new(Vec::new()))
Self(|| ErrorContext::new(None))
}
}
@@ -71,241 +77,57 @@ pub fn use_error_boundary_provider() -> ErrorContext {
use_hook(|| provide_context(create_error_boundary()))
}
/// A trait for any type that can be downcast to a concrete type and implements Debug. This is automatically implemented for all types that implement Any + Debug.
pub trait AnyError {
fn as_any(&self) -> &dyn Any;
fn as_error(&self) -> &dyn Error;
}
/// An wrapper error type for types that only implement Display. We use a inner type here to avoid overlapping implementations for DisplayError and impl Error
struct DisplayError(DisplayErrorInner);
impl<E: Display + 'static> From<E> for DisplayError {
fn from(e: E) -> Self {
Self(DisplayErrorInner(Box::new(e)))
}
}
struct DisplayErrorInner(Box<dyn Display>);
impl Display for DisplayErrorInner {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl Debug for DisplayErrorInner {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl Error for DisplayErrorInner {}
impl AnyError for DisplayError {
fn as_any(&self) -> &dyn Any {
&self.0 .0
}
fn as_error(&self) -> &dyn Error {
&self.0
}
}
/// Provides context methods to [`Result`] and [`Option`] types that are compatible with [`CapturedError`]
///
/// This trait is sealed and cannot be implemented outside of dioxus-core
pub trait Context<T, E>: private::Sealed {
/// Add a visual representation of the error that the [`ErrorBoundary`] may render
///
/// # Example
/// ```rust
/// # use dioxus::prelude::*;
/// fn Component() -> Element {
/// // You can bubble up errors with `?` inside components, and event handlers
/// // Along with the error itself, you can provide a way to display the error by calling `show`
/// let number = "1234".parse::<usize>().show(|error| rsx! {
/// div {
/// background_color: "red",
/// color: "white",
/// "Error parsing number: {error}"
/// }
/// })?;
/// unimplemented!()
/// }
/// ```
fn show(self, display_error: impl FnOnce(&E) -> Element) -> Result<T>;
/// Wrap the result additional context about the error that occurred.
///
/// # Example
/// ```rust
/// # use dioxus::prelude::*;
/// fn NumberParser() -> Element {
/// // You can bubble up errors with `?` inside components, and event handlers
/// // Along with the error itself, you can provide a way to display the error by calling `context`
/// let number = "-1234".parse::<usize>().context("Parsing number inside of the NumberParser")?;
/// unimplemented!()
/// }
/// ```
fn context<C: Display + 'static>(self, context: C) -> Result<T>;
/// Wrap the result with additional context about the error that occurred. The closure will only be run if the Result is an error.
///
/// # Example
/// ```rust
/// # use dioxus::prelude::*;
/// fn NumberParser() -> Element {
/// // You can bubble up errors with `?` inside components, and event handlers
/// // Along with the error itself, you can provide a way to display the error by calling `context`
/// let number = "-1234".parse::<usize>().with_context(|| format!("Timestamp: {:?}", std::time::Instant::now()))?;
/// unimplemented!()
/// }
/// ```
fn with_context<C: Display + 'static>(self, context: impl FnOnce() -> C) -> Result<T>;
}
impl<T, E> Context<T, E> for std::result::Result<T, E>
where
E: Error + 'static,
{
fn show(self, display_error: impl FnOnce(&E) -> Element) -> Result<T> {
// We don't use result mapping to avoid extra frames
match self {
std::result::Result::Ok(value) => Ok(value),
Err(error) => {
let render = display_error(&error).unwrap_or_default();
let mut error: CapturedError = error.into();
error.render = render;
Err(error)
}
}
}
fn context<C: Display + 'static>(self, context: C) -> Result<T> {
self.with_context(|| context)
}
fn with_context<C: Display + 'static>(self, context: impl FnOnce() -> C) -> Result<T> {
// We don't use result mapping to avoid extra frames
match self {
std::result::Result::Ok(value) => Ok(value),
Err(error) => {
let mut error: CapturedError = error.into();
error.context.push(Rc::new(AdditionalErrorContext {
backtrace: Backtrace::capture(),
context: Box::new(context()),
scope: current_scope_id().ok(),
}));
Err(error)
}
}
}
}
impl<T> Context<T, CapturedError> for Option<T> {
fn show(self, display_error: impl FnOnce(&CapturedError) -> Element) -> Result<T> {
// We don't use result mapping to avoid extra frames
match self {
Some(value) => Ok(value),
None => {
let mut error = CapturedError::from_display("Value was none");
let render = display_error(&error).unwrap_or_default();
error.render = render;
Err(error)
}
}
}
fn context<C: Display + 'static>(self, context: C) -> Result<T> {
self.with_context(|| context)
}
fn with_context<C: Display + 'static>(self, context: impl FnOnce() -> C) -> Result<T> {
// We don't use result mapping to avoid extra frames
match self {
Some(value) => Ok(value),
None => {
let error = CapturedError::from_display(context());
Err(error)
}
}
}
}
pub(crate) mod private {
use super::*;
pub trait Sealed {}
impl<T, E> Sealed for std::result::Result<T, E> where E: Error {}
impl<T> Sealed for Option<T> {}
}
impl<T: Any + Error> AnyError for T {
fn as_any(&self) -> &dyn Any {
self
}
fn as_error(&self) -> &dyn Error {
self
}
}
/// A context with information about suspended components
#[derive(Clone)]
pub struct ErrorContext {
errors: Rc<RefCell<Vec<CapturedError>>>,
error: Rc<RefCell<Option<CapturedError>>>,
subscribers: Subscribers,
}
impl Debug for ErrorContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ErrorContext")
.field("errors", &self.errors)
.field("error", &self.error)
.finish()
}
}
impl PartialEq for ErrorContext {
fn eq(&self, other: &Self) -> bool {
Rc::ptr_eq(&self.errors, &other.errors)
Rc::ptr_eq(&self.error, &other.error)
}
}
impl ErrorContext {
/// Create a new in memory error context. This will not be serialized to the hydration context if fullstack
/// is enabled
pub fn new(errors: Vec<CapturedError>) -> Self {
/// Create a new suspense boundary in a specific scope
pub fn new(error: Option<CapturedError>) -> Self {
Self {
errors: Rc::new(RefCell::new(errors)),
subscribers: Subscribers::default(),
error: Rc::new(RefCell::new(error)),
subscribers: Subscribers::new(),
}
}
/// Get all errors thrown from child components
pub fn errors(&self) -> Ref<'_, [CapturedError]> {
/// Get the current error, if any. If multiple components have errored, this will return the first
/// error that made it to this boundary.
pub fn error(&self) -> Option<CapturedError> {
// Subscribe to the current reactive context if one exists. This is usually
// the error boundary component that is rendering the errors
if let Some(rc) = ReactiveContext::current() {
self.subscribers.add(rc);
}
Ref::map(self.errors.borrow(), |errors| errors.as_slice())
}
/// Get the Element from the first error that can be shown
pub fn show(&self) -> Option<Element> {
self.errors.borrow().iter().find_map(|task| task.show())
self.error.borrow().clone()
}
/// Push an error into this Error Boundary
pub fn insert_error(&self, error: CapturedError) {
self.errors.borrow_mut().push(error);
self.error.borrow_mut().replace(error);
self.mark_dirty()
}
/// Clear all errors from this Error Boundary
pub fn clear_errors(&self) {
self.errors.borrow_mut().clear();
self.error.borrow_mut().take();
self.mark_dirty();
}
@@ -321,244 +143,6 @@ impl ErrorContext {
}
}
/// Errors can have additional context added as they bubble up the render tree
/// This context can be used to provide additional information to the user
struct AdditionalErrorContext {
backtrace: Backtrace,
context: Box<dyn Display>,
scope: Option<ScopeId>,
}
impl Debug for AdditionalErrorContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ErrorContext")
.field("backtrace", &self.backtrace)
.field("context", &self.context.to_string())
.finish()
}
}
impl Display for AdditionalErrorContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let AdditionalErrorContext {
backtrace,
context,
scope,
} = self;
write!(f, "{context} (from ")?;
if let Some(scope) = scope {
write!(f, "scope {scope:?} ")?;
}
write!(f, "at {backtrace:?})")
}
}
/// A type alias for a result that can be either a boxed error or a value
/// This is useful to avoid having to use `Result<T, CapturedError>` everywhere
pub type Result<T = ()> = std::result::Result<T, CapturedError>;
/// A helper function for an Ok result that can be either a boxed error or a value
/// This is useful to avoid having to use `Ok<T, CapturedError>` everywhere
#[allow(non_snake_case)]
pub fn Ok<T>(value: T) -> Result<T> {
Result::Ok(value)
}
#[derive(Clone)]
/// An instance of an error captured by a descendant component.
pub struct CapturedError {
/// The error captured by the error boundary
error: Rc<dyn AnyError + 'static>,
/// The backtrace of the error
backtrace: Rc<Backtrace>,
/// The scope that threw the error
scope: ScopeId,
/// An error message that can be displayed to the user
pub(crate) render: VNode,
/// Additional context that was added to the error
context: Vec<Rc<AdditionalErrorContext>>,
}
impl FromStr for CapturedError {
type Err = std::convert::Infallible;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
std::result::Result::Ok(Self::from_display(s.to_string()))
}
}
#[cfg(feature = "serialize")]
#[derive(serde::Serialize, serde::Deserialize)]
struct SerializedCapturedError {
error: String,
context: Vec<String>,
}
#[cfg(feature = "serialize")]
impl serde::Serialize for CapturedError {
fn serialize<S: serde::Serializer>(
&self,
serializer: S,
) -> std::result::Result<S::Ok, S::Error> {
let serialized = SerializedCapturedError {
error: self.error.as_error().to_string(),
context: self
.context
.iter()
.map(|context| context.to_string())
.collect(),
};
serialized.serialize(serializer)
}
}
#[cfg(feature = "serialize")]
impl<'de> serde::Deserialize<'de> for CapturedError {
fn deserialize<D: serde::Deserializer<'de>>(
deserializer: D,
) -> std::result::Result<Self, D::Error> {
let serialized = SerializedCapturedError::deserialize(deserializer)?;
let error = DisplayError::from(serialized.error);
let context = serialized
.context
.into_iter()
.map(|context| {
Rc::new(AdditionalErrorContext {
scope: None,
backtrace: Backtrace::disabled(),
context: Box::new(context),
})
})
.collect();
std::result::Result::Ok(Self {
error: Rc::new(error),
context,
backtrace: Rc::new(Backtrace::disabled()),
scope: ScopeId::ROOT,
render: VNode::placeholder(),
})
}
}
impl Debug for CapturedError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CapturedError")
.field("error", &self.error.as_error())
.field("backtrace", &self.backtrace)
.field("scope", &self.scope)
.finish()
}
}
impl<E: AnyError + 'static> From<E> for CapturedError {
fn from(error: E) -> Self {
Self {
error: Rc::new(error),
backtrace: Rc::new(Backtrace::capture()),
scope: current_scope_id()
.expect("Cannot create an error boundary outside of a component's scope."),
render: Default::default(),
context: Default::default(),
}
}
}
impl CapturedError {
/// Create a new captured error
pub fn new(error: impl AnyError + 'static) -> Self {
Self {
error: Rc::new(error),
backtrace: Rc::new(Backtrace::capture()),
scope: current_scope_id().unwrap_or(ScopeId::ROOT),
render: Default::default(),
context: Default::default(),
}
}
/// Create a new error from a type that only implements [`Display`]. If your type implements [`Error`], you can use [`CapturedError::from`] instead.
pub fn from_display(error: impl Display + 'static) -> Self {
Self {
error: Rc::new(DisplayError::from(error)),
backtrace: Rc::new(Backtrace::capture()),
scope: current_scope_id().unwrap_or(ScopeId::ROOT),
render: Default::default(),
context: Default::default(),
}
}
/// Mark the error as being thrown from a specific scope
pub fn with_origin(mut self, scope: ScopeId) -> Self {
self.scope = scope;
self
}
/// Get a VNode representation of the error if the error provides one
pub fn show(&self) -> Option<Element> {
if self.render == VNode::placeholder() {
None
} else {
Some(std::result::Result::Ok(self.render.clone()))
}
}
/// Create a deep clone of this error
pub(crate) fn deep_clone(&self) -> Self {
Self {
render: self.render.deep_clone(),
..self.clone()
}
}
}
impl PartialEq for CapturedError {
fn eq(&self, other: &Self) -> bool {
format!("{:?}", self) == format!("{:?}", other)
}
}
impl Display for CapturedError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!(
"Encountered error: {:?}\nIn scope: {:?}\nBacktrace: {}\nContext: ",
self.error.as_error(),
self.scope,
self.backtrace
))?;
for context in &*self.context {
f.write_fmt(format_args!("{}\n", context))?;
}
std::result::Result::Ok(())
}
}
impl CapturedError {
/// Downcast the error type into a concrete error type
pub fn downcast<T: 'static>(&self) -> Option<&T> {
self.error.as_any().downcast_ref::<T>()
}
}
pub(crate) fn throw_into(error: impl Into<CapturedError>, scope: ScopeId) {
let error = error.into();
if let Some(cx) = scope.consume_context::<ErrorContext>() {
cx.insert_error(error)
} else {
tracing::error!(
"Tried to throw an error into an error boundary, but failed to locate a boundary: {:?}",
error
)
}
}
#[allow(clippy::type_complexity)]
#[derive(Clone)]
pub struct ErrorHandler(Rc<dyn Fn(ErrorContext) -> Element>);
@@ -587,7 +171,7 @@ fn default_handler(errors: ErrorContext) -> Element {
None,
TEMPLATE,
Box::new([errors
.errors()
.error()
.iter()
.map(|e| {
static TEMPLATE: Template = Template {
@@ -628,6 +212,7 @@ impl ErrorBoundaryProps {
ErrorBoundaryPropsBuilder { fields: ((), ()) }
}
}
#[must_use]
#[doc(hidden)]
#[allow(dead_code, non_camel_case_types, non_snake_case)]
@@ -850,8 +435,9 @@ impl<
#[allow(non_upper_case_globals, non_snake_case)]
pub fn ErrorBoundary(props: ErrorBoundaryProps) -> Element {
let error_boundary = use_error_boundary_provider();
let errors = error_boundary.errors();
let has_errors = !errors.is_empty();
let errors = error_boundary.error();
let has_errors = errors.is_some();
// Drop errors before running user code that might borrow the error lock
drop(errors);
+21 -8
View File
@@ -1,4 +1,5 @@
use crate::{current_scope_id, properties::SuperFrom, runtime::RuntimeGuard, Runtime, ScopeId};
use futures_util::FutureExt;
use generational_box::GenerationalBox;
use std::{any::Any, cell::RefCell, marker::PhantomData, panic::Location, rc::Rc};
@@ -335,9 +336,15 @@ impl<Ret> SpawnIfAsync<(), Ret> for Ret {
pub struct AsyncMarker;
impl<F: std::future::Future<Output = ()> + 'static> SpawnIfAsync<AsyncMarker> for F {
fn spawn(self) {
crate::spawn(async move {
self.await;
});
// Quick poll once to deal with things like prevent_default in the same tick
let mut fut = Box::pin(self);
let res = fut.as_mut().now_or_never();
if res.is_none() {
crate::spawn(async move {
fut.await;
});
}
}
}
@@ -351,11 +358,17 @@ where
{
#[inline]
fn spawn(self) {
crate::spawn(async move {
if let Err(err) = self.await {
crate::throw_error(err)
}
});
// Quick poll once to deal with things like prevent_default in the same tick
let mut fut = Box::pin(self);
let res = fut.as_mut().now_or_never();
if res.is_none() {
crate::spawn(async move {
if let Err(err) = fut.await {
crate::throw_error(err)
}
});
}
}
}
+2 -3
View File
@@ -1,7 +1,6 @@
use crate::runtime::RuntimeError;
use crate::{innerlude::CapturedError, runtime::RuntimeError};
use crate::{
innerlude::SuspendedFuture, runtime::Runtime, CapturedError, Element, ScopeId, SuspenseContext,
Task,
innerlude::SuspendedFuture, runtime::Runtime, Element, ScopeId, SuspenseContext, Task,
};
use std::future::Future;
use std::rc::Rc;
+27 -13
View File
@@ -41,6 +41,14 @@ pub mod internal {
TemplateGlobalKey,
};
#[allow(non_snake_case)]
#[doc(hidden)]
pub fn Err<T, E>(e: E) -> Result<T, E> {
std::result::Result::Err(e)
}
pub use anyhow::__anyhow;
#[doc(hidden)]
pub use generational_box;
}
@@ -67,6 +75,11 @@ pub(crate) mod innerlude {
pub use crate::tasks::*;
pub use crate::virtual_dom::*;
pub use anyhow::anyhow;
pub use anyhow::Context as AnyhowContext;
pub use anyhow::Error;
pub use anyhow::Result;
/// An [`Element`] is a possibly-none [`VNode`] created by calling `render` on [`ScopeId`] or [`ScopeState`].
///
/// An Errored [`Element`] will propagate the error to the nearest error boundary.
@@ -77,21 +90,22 @@ pub(crate) mod innerlude {
}
pub use crate::innerlude::{
consume_context, consume_context_from_scope, current_owner, current_scope_id, fc_to_builder,
force_all_dirty, generation, has_context, needs_update, needs_update_any, parent_scope,
provide_context, provide_create_error_boundary, provide_root_context, queue_effect,
remove_future, schedule_update, schedule_update_any, spawn, spawn_forever, spawn_isomorphic,
suspend, suspense_context, throw_error, try_consume_context, use_after_render,
use_before_render, use_drop, use_error_boundary_provider, use_hook, use_hook_with_cleanup,
vdom_is_rendering, with_owner, AnyValue, Attribute, AttributeValue, Callback, CapturedError,
Component, ComponentFunction, Context, DynamicNode, Element, ElementId, ErrorBoundary,
ErrorContext, Event, EventHandler, Fragment, HasAttributes, IntoAttributeValue, IntoDynNode,
LaunchConfig, ListenerCallback, MarkerWrapper, Mutation, Mutations, NoOpMutations, Ok,
anyhow, consume_context, consume_context_from_scope, current_owner, current_scope_id,
fc_to_builder, force_all_dirty, generation, has_context, needs_update, needs_update_any,
parent_scope, provide_context, provide_create_error_boundary, provide_root_context,
queue_effect, remove_future, schedule_update, schedule_update_any, spawn, spawn_forever,
spawn_isomorphic, suspend, suspense_context, throw_error, try_consume_context,
use_after_render, use_before_render, use_drop, use_hook, use_hook_with_cleanup,
vdom_is_rendering, with_owner, AnyValue, AnyhowContext, Attribute, AttributeValue, Callback,
CapturedError, Component, ComponentFunction, DynamicNode, Element, ElementId, Error,
ErrorBoundary, ErrorContext, Event, EventHandler, Fragment, HasAttributes, IntoAttributeValue,
IntoDynNode, LaunchConfig, ListenerCallback, MarkerWrapper, Mutation, Mutations, NoOpMutations,
OptionStringFromMarker, Properties, ReactiveContext, RenderError, Result, Runtime,
RuntimeGuard, ScopeId, ScopeState, SpawnIfAsync, SubscriberList, Subscribers, SuperFrom,
SuperInto, SuspendedFuture, SuspenseBoundary, SuspenseBoundaryProps, SuspenseContext,
SuspenseExtension, Task, Template, TemplateAttribute, TemplateNode, VComponent, VNode,
VNodeInner, VPlaceholder, VText, VirtualDom, WriteMutations,
SuperInto, SuspendedFuture, SuspenseBoundary, SuspenseBoundaryProps, SuspenseContext, Task,
Template, TemplateAttribute, TemplateNode, VComponent, VNode, VNodeInner, VPlaceholder, VText,
VirtualDom, WriteMutations,
};
pub use anyhow::Ok;
pub use const_format;
+7 -56
View File
@@ -4,7 +4,7 @@ use crate::{
events::ListenerCallback,
innerlude::{ElementRef, MountId, ScopeState, VProps},
properties::ComponentFunction,
Element, Event, Properties, RenderError, ScopeId, VirtualDom,
Element, Event, Properties, ScopeId, VirtualDom,
};
use dioxus_core_types::DioxusFormattable;
use std::ops::Deref;
@@ -100,55 +100,6 @@ pub struct VNode {
pub(crate) mount: Cell<MountId>,
}
impl AsRef<VNode> for Element {
fn as_ref(&self) -> &VNode {
match self {
Element::Ok(node) => node,
Element::Err(RenderError::Aborted(err)) => &err.render,
Element::Err(RenderError::Suspended(fut)) => &fut.placeholder,
}
}
}
impl From<&Element> for VNode {
fn from(val: &Element) -> Self {
AsRef::as_ref(val).clone()
}
}
impl From<Element> for VNode {
fn from(val: Element) -> Self {
match val {
Element::Ok(node) => node,
Element::Err(RenderError::Aborted(err)) => err.render,
Element::Err(RenderError::Suspended(fut)) => fut.placeholder,
}
}
}
/// A tiny helper trait to get the vnode for a Element
pub(crate) trait AsVNode {
/// Get the vnode for this element
fn as_vnode(&self) -> &VNode;
/// Create a deep clone of this VNode
fn deep_clone(&self) -> Self;
}
impl AsVNode for Element {
fn as_vnode(&self) -> &VNode {
AsRef::as_ref(self)
}
fn deep_clone(&self) -> Self {
match self {
Ok(node) => Ok(node.deep_clone()),
Err(RenderError::Aborted(err)) => Err(RenderError::Aborted(err.deep_clone())),
Err(RenderError::Suspended(fut)) => Err(RenderError::Suspended(fut.deep_clone())),
}
}
}
impl Default for VNode {
fn default() -> Self {
Self::placeholder()
@@ -979,7 +930,7 @@ impl IntoVNode for Element {
fn into_vnode(self) -> VNode {
match self {
Ok(val) => val.into_vnode(),
_ => VNode::empty().unwrap(),
_ => VNode::default(),
}
}
}
@@ -987,7 +938,7 @@ impl IntoVNode for &Element {
fn into_vnode(self) -> VNode {
match self {
Ok(val) => val.into_vnode(),
_ => VNode::empty().unwrap(),
_ => VNode::default(),
}
}
}
@@ -995,7 +946,7 @@ impl IntoVNode for Option<VNode> {
fn into_vnode(self) -> VNode {
match self {
Some(val) => val.into_vnode(),
_ => VNode::empty().unwrap(),
_ => VNode::default(),
}
}
}
@@ -1003,7 +954,7 @@ impl IntoVNode for &Option<VNode> {
fn into_vnode(self) -> VNode {
match self.as_ref() {
Some(val) => val.clone().into_vnode(),
_ => VNode::empty().unwrap(),
_ => VNode::default(),
}
}
}
@@ -1011,7 +962,7 @@ impl IntoVNode for Option<Element> {
fn into_vnode(self) -> VNode {
match self {
Some(val) => val.into_vnode(),
_ => VNode::empty().unwrap(),
_ => VNode::default(),
}
}
}
@@ -1019,7 +970,7 @@ impl IntoVNode for &Option<Element> {
fn into_vnode(self) -> VNode {
match self.as_ref() {
Some(val) => val.clone().into_vnode(),
_ => VNode::empty().unwrap(),
_ => VNode::default(),
}
}
}
+1 -2
View File
@@ -108,8 +108,7 @@ impl ReactiveContext {
let id = scope.id;
let sender = runtime.sender.clone();
let update_scope = move || {
tracing::trace!("Marking scope {:?} as dirty", id);
sender.unbounded_send(SchedulerMsg::Immediate(id)).unwrap();
_ = sender.unbounded_send(SchedulerMsg::Immediate(id));
};
// Otherwise, create a new context at the current scope
+94 -12
View File
@@ -1,12 +1,18 @@
use std::fmt::{Debug, Display};
use std::{
fmt::{Debug, Display},
sync::Arc,
};
use crate::innerlude::*;
/// An error that can occur while rendering a component
#[derive(Clone, PartialEq, Debug)]
#[derive(Debug, Clone, PartialEq)]
pub enum RenderError {
/// The render function returned early
Aborted(CapturedError),
/// The render function returned early due to an error.
///
/// We captured the error, wrapped it in an Arc, and stored it here. You can no longer modify the error,
/// but you can cheaply pass it around.
Error(CapturedError),
/// The component was suspended
Suspended(SuspendedFuture),
@@ -26,27 +32,103 @@ impl Default for RenderError {
}
}
impl std::error::Error for RenderAbortedEarly {}
Self::Aborted(RenderAbortedEarly.into())
Self::Error(CapturedError(Arc::new(RenderAbortedEarly.into())))
}
}
impl Display for RenderError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Aborted(e) => write!(f, "Render aborted: {e}"),
Self::Error(e) => write!(f, "Render aborted: {e}"),
Self::Suspended(e) => write!(f, "Component suspended: {e:?}"),
}
}
}
impl<E: std::error::Error + 'static> From<E> for RenderError {
fn from(e: E) -> Self {
Self::Aborted(CapturedError::from(e))
impl From<CapturedError> for RenderError {
fn from(e: CapturedError) -> Self {
Self::Error(e)
}
}
impl From<CapturedError> for RenderError {
fn from(e: CapturedError) -> Self {
RenderError::Aborted(e)
impl<E: Into<Error>> From<E> for RenderError {
fn from(e: E) -> Self {
let anyhow_err = e.into();
if let Some(suspended) = anyhow_err.downcast_ref::<SuspendedFuture>() {
return Self::Suspended(suspended.clone());
}
if let Some(render_error) = anyhow_err.downcast_ref::<RenderError>() {
return render_error.clone();
}
Self::Error(CapturedError(Arc::new(anyhow_err)))
}
}
/// An `anyhow::Error` wrapped in an `Arc` so it can be cheaply cloned and passed around.
#[derive(Debug, Clone)]
pub struct CapturedError(pub Arc<Error>);
impl CapturedError {
/// Create a `CapturedError` from anything that implements `Display`.
pub fn from_display(t: impl Display) -> Self {
Self(Arc::new(anyhow::anyhow!(t.to_string())))
}
}
#[cfg(feature = "serialize")]
impl serde::Serialize for CapturedError {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.0.to_string())
}
}
#[cfg(feature = "serialize")]
impl<'de> serde::Deserialize<'de> for CapturedError {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Ok(Self::from_display(s))
}
}
impl std::ops::Deref for CapturedError {
type Target = Error;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<E: Into<Error>> From<E> for CapturedError {
fn from(e: E) -> Self {
Self(Arc::new(e.into()))
}
}
impl std::fmt::Display for CapturedError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl PartialEq for CapturedError {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0)
}
}
#[test]
fn assert_errs_can_downcast() {
fn assert_is_stderr_like<T: Send + Sync + Display + Debug>() {}
assert_is_stderr_like::<RenderError>();
assert_is_stderr_like::<CapturedError>();
}
+4 -5
View File
@@ -250,7 +250,7 @@ impl Runtime {
/// Pops a scope off the stack
pub(crate) fn pop() {
RUNTIMES.with(|stack| stack.borrow_mut().pop());
RUNTIMES.with(|stack| stack.borrow_mut().pop().unwrap());
}
/// Runs a function with the current runtime
@@ -273,11 +273,10 @@ impl Runtime {
/// Runs a function with the current scope
pub(crate) fn with_scope<R>(
scope: ScopeId,
f: impl FnOnce(&Scope) -> R,
callback: impl FnOnce(&Scope) -> R,
) -> Result<R, RuntimeError> {
Self::with(|rt| rt.get_state(scope).map(|sc| f(&sc)))
.ok()
.flatten()
Self::with(|rt| rt.get_state(scope).map(|scopestate| callback(&scopestate)))
.expect("Runtime should exist")
.ok_or(RuntimeError::new())
}
+10 -8
View File
@@ -1,13 +1,11 @@
use crate::{
any_props::{AnyProps, BoxedAnyProps},
innerlude::{throw_error, RenderError, ScopeOrder, ScopeState},
nodes::AsVNode,
scope_context::{Scope, SuspenseLocation},
scopes::ScopeId,
virtual_dom::VirtualDom,
ReactiveContext,
Element, ReactiveContext,
};
use crate::{Element, VNode};
impl VirtualDom {
pub(super) fn new_scope(
@@ -39,7 +37,6 @@ impl VirtualDom {
});
self.runtime.create_scope(scope_runtime);
tracing::trace!("created scope {id:?} with parent {parent_id:?}");
scope
}
@@ -79,7 +76,14 @@ impl VirtualDom {
// the component runs, it returns a clone of the last rsx that was returned from
// that resource. If we don't deep clone the VNode and the resource changes, then
// we could end up diffing two different versions of the same mounted node
let mut render_return = render_return.deep_clone();
let mut render_return = match render_return {
Ok(node) => Ok(node.deep_clone()),
Err(RenderError::Error(err)) => Err(RenderError::Error(err.clone())),
Err(RenderError::Suspended(fut)) => {
Err(RenderError::Suspended(fut.deep_clone()))
}
};
self.handle_element_return(&mut render_return, scope_id, &scope.state());
render_return
})
@@ -103,13 +107,12 @@ impl VirtualDom {
/// Insert any errors, or suspended tasks from an element return into the runtime
fn handle_element_return(&self, node: &mut Element, scope_id: ScopeId, scope_state: &Scope) {
match node {
Err(RenderError::Aborted(e)) => {
Err(RenderError::Error(e)) => {
tracing::error!(
"Error while rendering component `{}`:\n{e}",
scope_state.name
);
throw_error(e.clone());
e.render = VNode::placeholder();
}
Err(RenderError::Suspended(e)) => {
let task = e.task();
@@ -136,7 +139,6 @@ impl VirtualDom {
.suspended_tasks
.set(self.runtime.suspended_tasks.get() + 1);
}
e.placeholder = VNode::placeholder();
}
Ok(_) => {
// If the render was successful, we can move the render generation forward by one
+12 -6
View File
@@ -1,5 +1,5 @@
use crate::{
innerlude::{throw_into, CapturedError, SchedulerMsg, SuspenseContext},
innerlude::{CapturedError, SchedulerMsg, SuspenseContext},
runtime::RuntimeError,
Runtime, ScopeId, Task,
};
@@ -167,7 +167,7 @@ impl Scope {
pub fn schedule_update_any(&self) -> Arc<dyn Fn(ScopeId) + Send + Sync> {
let chan = self.sender();
Arc::new(move |id| {
chan.unbounded_send(SchedulerMsg::Immediate(id)).unwrap();
_ = chan.unbounded_send(SchedulerMsg::Immediate(id));
})
}
@@ -545,9 +545,7 @@ impl ScopeId {
/// Consume context from the current scope
pub fn consume_context<T: 'static + Clone>(self) -> Option<T> {
Runtime::with_scope(self, |cx| cx.consume_context::<T>())
.ok()
.flatten()
Runtime::with_scope(self, |cx| cx.consume_context::<T>()).expect("Runtime to exist")
}
/// Consume context from the current scope
@@ -651,7 +649,15 @@ impl ScopeId {
/// }
/// ```
pub fn throw_error(self, error: impl Into<CapturedError> + 'static) {
throw_into(error, self)
let error = error.into();
if let Some(cx) = self.consume_context::<crate::ErrorContext>() {
cx.insert_error(error)
} else {
tracing::error!(
"Tried to throw an error into an error boundary, but failed to locate a boundary: {:?}",
error
)
}
}
/// Get the suspense context the current scope is in
+41 -4
View File
@@ -1,6 +1,6 @@
use crate::{
any_props::BoxedAnyProps, nodes::AsVNode, reactive_context::ReactiveContext,
scope_context::Scope, Element, Runtime, VNode,
any_props::BoxedAnyProps, reactive_context::ReactiveContext, scope_context::Scope, Element,
RenderError, Runtime, VNode,
};
use std::{cell::Ref, rc::Rc};
@@ -76,11 +76,44 @@ pub struct ScopeState {
pub(crate) context_id: ScopeId,
/// The last node that has been rendered for this component. This node may not ben mounted
/// During suspense, this component can be rendered in the background multiple times
pub(crate) last_rendered_node: Option<Element>,
pub(crate) last_rendered_node: Option<LastRenderedNode>,
pub(crate) props: BoxedAnyProps,
pub(crate) reactive_context: ReactiveContext,
}
#[derive(Clone, PartialEq, Debug)]
pub enum LastRenderedNode {
Real(VNode),
Placeholder(VNode, RenderError),
}
impl std::ops::Deref for LastRenderedNode {
type Target = VNode;
fn deref(&self) -> &Self::Target {
match self {
LastRenderedNode::Real(vnode) => vnode,
LastRenderedNode::Placeholder(vnode, _err) => vnode,
}
}
}
impl LastRenderedNode {
pub fn new(node: Element) -> Self {
match node {
Ok(vnode) => LastRenderedNode::Real(vnode),
Err(err) => LastRenderedNode::Placeholder(VNode::placeholder(), err),
}
}
pub fn as_vnode(&self) -> &VNode {
match self {
LastRenderedNode::Real(vnode) => vnode,
LastRenderedNode::Placeholder(vnode, _err) => vnode,
}
}
}
impl Drop for ScopeState {
fn drop(&mut self) {
self.runtime.remove_scope(self.context_id);
@@ -104,7 +137,11 @@ impl ScopeState {
///
/// Returns [`None`] if the tree has not been built yet.
pub fn try_root_node(&self) -> Option<&VNode> {
self.last_rendered_node.as_ref().map(AsVNode::as_vnode)
match &self.last_rendered_node {
Some(LastRenderedNode::Real(vnode)) => Some(vnode),
Some(LastRenderedNode::Placeholder(vnode, _)) => Some(vnode),
None => None,
}
}
/// Returns the scope id of this [`ScopeState`].
+38 -45
View File
@@ -5,7 +5,7 @@ use crate::{innerlude::*, scope_context::SuspenseLocation};
pub struct SuspenseBoundaryProps {
fallback: Callback<SuspenseContext, Element>,
/// The children of the suspense boundary
children: Element,
children: LastRenderedNode,
}
impl Clone for SuspenseBoundaryProps {
@@ -205,7 +205,10 @@ impl<__children: SuspenseBoundaryPropsBuilder_Optional<Element>>
let fallback = fallback.0;
let children = SuspenseBoundaryPropsBuilder_Optional::into_value(children, VNode::empty);
SuspenseBoundaryPropsWithOwner {
inner: SuspenseBoundaryProps { fallback, children },
inner: SuspenseBoundaryProps {
fallback,
children: LastRenderedNode::new(children),
},
owner: self.owner,
}
}
@@ -229,13 +232,7 @@ impl ::core::cmp::PartialEq for SuspenseBoundaryProps {
/// fn App() -> Element {
/// rsx! {
/// SuspenseBoundary {
/// fallback: |context: SuspenseContext| rsx! {
/// if let Some(placeholder) = context.suspense_placeholder() {
/// {placeholder}
/// } else {
/// "Loading..."
/// }
/// },
/// fallback: |_| rsx! { "Loading..." },
/// Article {}
/// }
/// }
@@ -310,7 +307,7 @@ impl SuspenseBoundaryProps {
// First always render the children in the background. Rendering the children may cause this boundary to suspend
suspense_context.under_suspense_boundary(&dom.runtime(), || {
children.as_vnode().create(dom, parent, None::<&mut M>);
children.create(dom, parent, None::<&mut M>);
});
// Store the (now mounted) children back into the scope state
@@ -325,6 +322,7 @@ impl SuspenseBoundaryProps {
.suspense_context()
.unwrap()
.clone();
// If there are suspended futures, render the fallback
let nodes_created = if !suspense_context.suspended_futures().is_empty() {
let (node, nodes_created) =
@@ -337,9 +335,10 @@ impl SuspenseBoundaryProps {
scope_id,
)
.unwrap();
suspense_context.set_suspended_nodes(children.into());
let suspense_placeholder = props.fallback.call(suspense_context);
let nodes_created = suspense_placeholder.as_vnode().create(dom, parent, to);
suspense_context.set_suspended_nodes(children.as_vnode().clone());
let suspense_placeholder =
LastRenderedNode::new(props.fallback.call(suspense_context));
let nodes_created = suspense_placeholder.create(dom, parent, to);
(suspense_placeholder, nodes_created)
});
@@ -349,13 +348,11 @@ impl SuspenseBoundaryProps {
nodes_created
} else {
// Otherwise just render the children in the real dom
debug_assert!(children.as_vnode().mount.get().mounted());
debug_assert!(children.mount.get().mounted());
let nodes_created = suspense_context
.under_suspense_boundary(&dom.runtime(), || {
children.as_vnode().create(dom, parent, to)
});
.under_suspense_boundary(&dom.runtime(), || children.create(dom, parent, to));
let scope_state = &mut dom.scopes[scope_id.0];
scope_state.last_rendered_node = Some(children);
scope_state.last_rendered_node = children.into();
let suspense_context =
SuspenseContext::downcast_suspense_boundary_from_scope(&dom.runtime, scope_id)
.unwrap();
@@ -395,8 +392,8 @@ impl SuspenseBoundaryProps {
suspense_context.inner.suspended_tasks.borrow_mut().clear();
// Get the parent of the suspense boundary to later create children with the right parent
let currently_rendered = scope_state.last_rendered_node.as_ref().unwrap().clone();
let mount = currently_rendered.as_vnode().mount.get();
let currently_rendered = scope_state.last_rendered_node.clone().unwrap();
let mount = currently_rendered.mount.get();
let parent = {
let mounts = dom.runtime.mounts.borrow();
mounts
@@ -412,24 +409,24 @@ impl SuspenseBoundaryProps {
let suspense_context =
SuspenseContext::downcast_suspense_boundary_from_scope(&dom.runtime, scope_id)
.unwrap();
// Take the suspended nodes out of the suspense boundary so the children know that the boundary is not suspended while diffing
let suspended = suspense_context.take_suspended_nodes();
if let Some(node) = suspended {
node.remove_node(&mut *dom, None::<&mut M>, None);
}
// Replace the rendered nodes with resolved nodes
currently_rendered
.as_vnode()
.remove_node(&mut *dom, Some(to), Some(replace_with));
currently_rendered.remove_node(&mut *dom, Some(to), Some(replace_with));
// Switch to only writing templates
only_write_templates(to);
children.as_vnode().mount.take();
children.mount.take();
// First always render the children in the background. Rendering the children may cause this boundary to suspend
suspense_context.under_suspense_boundary(&dom.runtime(), || {
children.as_vnode().create(dom, parent, Some(to));
children.create(dom, parent, Some(to));
});
// Store the (now mounted) children back into the scope state
@@ -454,7 +451,7 @@ impl SuspenseBoundaryProps {
.unwrap()
.clone();
let last_rendered_node = scope.last_rendered_node.as_ref().unwrap().clone();
let last_rendered_node = scope.last_rendered_node.clone().unwrap();
let Self {
fallback, children, ..
@@ -467,19 +464,16 @@ impl SuspenseBoundaryProps {
// We already have suspended nodes that still need to be suspended
// Just diff the normal and suspended nodes
(Some(suspended_nodes), true) => {
let new_suspended_nodes: VNode = children.into();
let new_suspended_nodes: VNode = children.as_vnode().clone();
// Diff the placeholder nodes in the dom
let new_placeholder =
suspense_context.in_suspense_placeholder(&dom.runtime(), || {
let old_placeholder = last_rendered_node;
let new_placeholder = fallback.call(suspense_context.clone());
let new_placeholder =
LastRenderedNode::new(fallback.call(suspense_context.clone()));
old_placeholder.as_vnode().diff_node(
new_placeholder.as_vnode(),
dom,
to,
);
old_placeholder.diff_node(&new_placeholder, dom, to);
new_placeholder
});
@@ -504,20 +498,19 @@ impl SuspenseBoundaryProps {
let new_children = children;
suspense_context.under_suspense_boundary(&dom.runtime(), || {
old_children
.as_vnode()
.diff_node(new_children.as_vnode(), dom, to);
old_children.diff_node(&new_children, dom, to);
});
// Set the last rendered node to the new children
dom.scopes[scope_id.0].last_rendered_node = Some(new_children);
dom.scopes[scope_id.0].last_rendered_node = new_children.into();
}
// We have no suspended nodes, but we just became suspended. Move the children to the background
(None, true) => {
let old_children = last_rendered_node.as_vnode();
let new_children: VNode = children.into();
let old_children = last_rendered_node;
let new_children: VNode = children.as_vnode().clone();
let new_placeholder = fallback.call(suspense_context.clone());
let new_placeholder =
LastRenderedNode::new(fallback.call(suspense_context.clone()));
// Move the children to the background
let mount = old_children.mount.get();
@@ -525,7 +518,7 @@ impl SuspenseBoundaryProps {
suspense_context.in_suspense_placeholder(&dom.runtime(), || {
old_children.move_node_to_background(
std::slice::from_ref(new_placeholder.as_vnode()),
std::slice::from_ref(&new_placeholder),
parent,
dom,
to,
@@ -558,13 +551,13 @@ impl SuspenseBoundaryProps {
// First diff the two children nodes in the background
suspense_context.under_suspense_boundary(&dom.runtime(), || {
old_suspended_nodes.diff_node(new_children.as_vnode(), dom, None::<&mut M>);
old_suspended_nodes.diff_node(&new_children, dom, None::<&mut M>);
// Then replace the placeholder with the new children
let mount = old_placeholder.as_vnode().mount.get();
let mount = old_placeholder.mount.get();
let parent = dom.get_mounted_parent(mount);
old_placeholder.as_vnode().replace(
std::slice::from_ref(new_children.as_vnode()),
old_placeholder.replace(
std::slice::from_ref(&new_children),
parent,
dom,
to,
+10 -66
View File
@@ -37,50 +37,38 @@ use std::{
#[derive(Clone, PartialEq, Debug)]
pub struct SuspendedFuture {
origin: ScopeId,
task: Task,
pub(crate) placeholder: VNode,
task: TaskId,
}
impl SuspendedFuture {
/// Create a new suspended future
pub fn new(task: Task) -> Self {
Self {
task,
task: task.id,
origin: current_scope_id().unwrap_or_else(|e| panic!("{}", e)),
placeholder: VNode::placeholder(),
}
}
/// Get a placeholder to display while the future is suspended
pub fn suspense_placeholder(&self) -> Option<VNode> {
if self.placeholder == VNode::placeholder() {
None
} else {
Some(self.placeholder.clone())
}
}
/// Set a new placeholder the SuspenseBoundary may use to display while the future is suspended
pub fn with_placeholder(mut self, placeholder: VNode) -> Self {
self.placeholder = placeholder;
self
}
/// Get the task that was suspended
pub fn task(&self) -> Task {
self.task
Task::from_id(self.task)
}
/// Create a deep clone of this suspended future
pub(crate) fn deep_clone(&self) -> Self {
Self {
task: self.task,
placeholder: self.placeholder.deep_clone(),
origin: self.origin,
}
}
}
impl std::fmt::Display for SuspendedFuture {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "SuspendedFuture {{ task: {:?} }}", self.task)
}
}
/// A context with information about suspended components
#[derive(Debug, Clone)]
pub struct SuspenseContext {
@@ -165,7 +153,7 @@ impl SuspenseContext {
self.inner
.suspended_tasks
.borrow_mut()
.retain(|t| t.task != task);
.retain(|t| t.task != task.id);
self.inner.id.get().needs_update();
}
@@ -176,16 +164,6 @@ impl SuspenseContext {
})
}
/// Get the first suspended task with a loading placeholder
pub fn suspense_placeholder(&self) -> Option<Element> {
self.inner
.suspended_tasks
.borrow()
.iter()
.find_map(|task| task.suspense_placeholder())
.map(std::result::Result::Ok)
}
/// Run a closure after suspense is resolved
pub fn after_suspense_resolved(&self, callback: impl FnOnce() + 'static) {
let mut closures = self.inner.after_suspense_resolved.borrow_mut();
@@ -226,37 +204,3 @@ impl Debug for SuspenseBoundaryInner {
.finish()
}
}
/// Provides context methods to [`Result<T, RenderError>`] to show loading indicators for suspended results
///
/// This trait is sealed and cannot be implemented outside of dioxus-core
pub trait SuspenseExtension<T>: private::Sealed {
/// Add a loading indicator if the result is suspended
fn with_loading_placeholder(
self,
display_placeholder: impl FnOnce() -> Element,
) -> std::result::Result<T, RenderError>;
}
impl<T> SuspenseExtension<T> for std::result::Result<T, RenderError> {
fn with_loading_placeholder(
self,
display_placeholder: impl FnOnce() -> Element,
) -> std::result::Result<T, RenderError> {
if let Err(RenderError::Suspended(suspense)) = self {
Err(RenderError::Suspended(suspense.with_placeholder(
display_placeholder().unwrap_or_default(),
)))
} else {
self
}
}
}
pub(crate) mod private {
use super::*;
pub trait Sealed {}
impl<T> Sealed for std::result::Result<T, RenderError> {}
}
+4 -3
View File
@@ -20,11 +20,13 @@ use std::{pin::Pin, task::Poll};
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct Task {
pub(crate) id: slotmap::DefaultKey,
pub(crate) id: TaskId,
// We add a raw pointer to make this !Send + !Sync
unsend: PhantomData<*const ()>,
}
pub(crate) type TaskId = slotmap::DefaultKey;
impl Task {
/// Create a task from a raw id
pub(crate) const fn from_id(id: slotmap::DefaultKey) -> Self {
@@ -48,8 +50,6 @@ impl Task {
}
/// Drop the task immediately.
///
/// This does not abort the task, so you'll want to wrap it in an abort handle if that's important to you
pub fn cancel(self) {
remove_future(self);
}
@@ -382,6 +382,7 @@ impl TaskType {
#[derive(Debug)]
pub(crate) enum SchedulerMsg {
/// All components have been marked as dirty, requiring a full render
#[allow(unused)]
AllDirty,
/// Immediate updates from Components that mark them as dirty
+8 -1
View File
@@ -2,7 +2,6 @@
//!
//! This module provides the primary mechanics to create a hook-based, concurrent VDOM for Rust.
use crate::innerlude::Work;
use crate::properties::RootProps;
use crate::root_wrapper::RootScopeWrapper;
use crate::{
@@ -12,6 +11,7 @@ use crate::{
scopes::ScopeId,
ComponentFunction, Element, Mutations,
};
use crate::{innerlude::Work, scopes::LastRenderedNode};
use crate::{Task, VComponent};
use futures_util::StreamExt;
use slab::Slab;
@@ -351,6 +351,11 @@ impl VirtualDom {
f()
}
/// Run a closure inside a specific scope
pub fn in_scope<T>(&self, scope: ScopeId, f: impl FnOnce() -> T) -> T {
self.in_runtime(|| scope.in_runtime(f))
}
/// Build the virtualdom with a global context inserted into the base scope
///
/// This is useful for what is essentially dependency injection when building the app
@@ -578,6 +583,8 @@ impl VirtualDom {
.clone()
.while_rendering(|| self.run_scope(ScopeId::ROOT));
let new_nodes = LastRenderedNode::new(new_nodes);
self.scopes[ScopeId::ROOT.0].last_rendered_node = Some(new_nodes.clone());
// Rebuilding implies we append the created elements to the root