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:
1 parent
8421c3aef0
commit
f2b1c6d97f
424 files changed
+17284
-6624
No files matched your search
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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;
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>();
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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`].
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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> {}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user