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

+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> {}
}