Fix the root error boundary during suspense in ssr (#4710)
* Fix the root error boundary during suspense in ssr * fix some typos
This commit is contained in:
1 parent
10912f4750
commit
ad40f81607
9 files changed
+49
-13
No files matched your search
@@ -2754,7 +2754,7 @@ impl BuildRequest {
|
|||||||
),
|
),
|
||||||
("OPENSSL_LIBS".to_string(), openssl_libs.into()),
|
("OPENSSL_LIBS".to_string(), openssl_libs.into()),
|
||||||
// Set the wry env vars - this is where wry will dump its kotlin files.
|
// Set the wry env vars - this is where wry will dump its kotlin files.
|
||||||
// Their setup is really annyoing and requires us to hardcode `dx` to specific versions of tao/wry.
|
// Their setup is really annoying and requires us to hardcode `dx` to specific versions of tao/wry.
|
||||||
(
|
(
|
||||||
"WRY_ANDROID_PACKAGE".to_string(),
|
"WRY_ANDROID_PACKAGE".to_string(),
|
||||||
"dev.dioxus.main".to_string().into(),
|
"dev.dioxus.main".to_string().into(),
|
||||||
@@ -3926,7 +3926,7 @@ __wbg_init({{module_or_path: "/{}/{wasm_path}"}}).then((wasm) => {{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the path to the wasm-bindgen output files. Either the direct file or the opitmized one depending on the build mode
|
/// Get the path to the wasm-bindgen output files. Either the direct file or the optimized one depending on the build mode
|
||||||
fn bundled_wasm_path(&self, assets: &AssetManifest) -> String {
|
fn bundled_wasm_path(&self, assets: &AssetManifest) -> String {
|
||||||
let wasm_bindgen_wasm_out = self.wasm_bindgen_wasm_output_file();
|
let wasm_bindgen_wasm_out = self.wasm_bindgen_wasm_output_file();
|
||||||
if self.should_bundle_to_asset() {
|
if self.should_bundle_to_asset() {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use std::borrow::Cow;
|
|||||||
|
|
||||||
/// Take this type and format it into a Cow<'static, str>
|
/// Take this type and format it into a Cow<'static, str>
|
||||||
///
|
///
|
||||||
/// This trait exists so libraries like manganis can implement this type for asssets without depending
|
/// This trait exists so libraries like manganis can implement this type for assets without depending
|
||||||
/// on dioxus-core, which can be heavy in proc macros and build scripts.
|
/// on dioxus-core, which can be heavy in proc macros and build scripts.
|
||||||
///
|
///
|
||||||
/// We don't want a blanket impl for T: Display because that might conflict for the other integral data
|
/// We don't want a blanket impl for T: Display because that might conflict for the other integral data
|
||||||
|
|||||||
@@ -795,7 +795,7 @@ impl VNode {
|
|||||||
fn write_attrs(
|
fn write_attrs(
|
||||||
&self,
|
&self,
|
||||||
mount: MountId,
|
mount: MountId,
|
||||||
dynamic_attrbiutes_iter: &mut Peekable<impl Iterator<Item = (usize, &'static [u8])>>,
|
dynamic_attributes_iter: &mut Peekable<impl Iterator<Item = (usize, &'static [u8])>>,
|
||||||
root_idx: u8,
|
root_idx: u8,
|
||||||
dom: &mut VirtualDom,
|
dom: &mut VirtualDom,
|
||||||
to: &mut impl WriteMutations,
|
to: &mut impl WriteMutations,
|
||||||
@@ -804,7 +804,7 @@ impl VNode {
|
|||||||
// Only take nodes that are under this root node
|
// Only take nodes that are under this root node
|
||||||
let from_root_node = |(_, path): &(usize, &[u8])| path.first() == Some(&root_idx);
|
let from_root_node = |(_, path): &(usize, &[u8])| path.first() == Some(&root_idx);
|
||||||
while let Some((attribute_idx, attribute_path)) =
|
while let Some((attribute_idx, attribute_path)) =
|
||||||
dynamic_attrbiutes_iter.next_if(from_root_node)
|
dynamic_attributes_iter.next_if(from_root_node)
|
||||||
{
|
{
|
||||||
let attribute = &self.dynamic_attrs[attribute_idx];
|
let attribute = &self.dynamic_attrs[attribute_idx];
|
||||||
|
|
||||||
|
|||||||
@@ -45,11 +45,17 @@ impl ScopeId {
|
|||||||
/// let my_persistent_state = Signal::new_in_scope(String::new(), ScopeId::APP);
|
/// let my_persistent_state = Signal::new_in_scope(String::new(), ScopeId::APP);
|
||||||
/// ```
|
/// ```
|
||||||
// ScopeId(0) is the root scope wrapper
|
// ScopeId(0) is the root scope wrapper
|
||||||
// ScopeId(1) is the default error boundary
|
// ScopeId(1) is the default suspense boundary
|
||||||
// ScopeId(2) is the default suspense boundary
|
// ScopeId(2) is the default error boundary
|
||||||
// ScopeId(3) is the users root scope
|
// ScopeId(3) is the users root scope
|
||||||
pub const APP: ScopeId = ScopeId(3);
|
pub const APP: ScopeId = ScopeId(3);
|
||||||
|
|
||||||
|
/// The ScopeId of the topmost error boundary in the tree.
|
||||||
|
pub const ROOT_ERROR_BOUNDARY: ScopeId = ScopeId(2);
|
||||||
|
|
||||||
|
/// The ScopeId of the topmost suspense boundary in the tree.
|
||||||
|
pub const ROOT_SUSPENSE_BOUNDARY: ScopeId = ScopeId(1);
|
||||||
|
|
||||||
/// The ScopeId of the topmost scope in the tree.
|
/// The ScopeId of the topmost scope in the tree.
|
||||||
/// This will be higher up in the tree than [`ScopeId::APP`] because dioxus inserts a default [`crate::SuspenseBoundary`] and [`crate::ErrorBoundary`] at the root of the tree.
|
/// This will be higher up in the tree than [`ScopeId::APP`] because dioxus inserts a default [`crate::SuspenseBoundary`] and [`crate::ErrorBoundary`] at the root of the tree.
|
||||||
// ScopeId(0) is the root scope wrapper
|
// ScopeId(0) is the root scope wrapper
|
||||||
|
|||||||
@@ -42,6 +42,19 @@ test("error route", async ({ request }) => {
|
|||||||
expect(response.status()).toBe(500);
|
expect(response.status()).toBe(500);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// The async error route should return 500
|
||||||
|
test("async error route", async ({ request }) => {
|
||||||
|
await waitForBuild(request);
|
||||||
|
const response = await request.get("http://localhost:8888/async-error");
|
||||||
|
|
||||||
|
expect(response.status()).toBe(500);
|
||||||
|
|
||||||
|
// Expect the response to contain the error message
|
||||||
|
const errorMessage = "Async error from a server function";
|
||||||
|
const text = await response.text();
|
||||||
|
expect(text).toContain(errorMessage);
|
||||||
|
});
|
||||||
|
|
||||||
// An unknown route should return 404
|
// An unknown route should return 404
|
||||||
test("unknown route", async ({ request }) => {
|
test("unknown route", async ({ request }) => {
|
||||||
await waitForBuild(request);
|
await waitForBuild(request);
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ enum Route {
|
|||||||
#[route("/error")]
|
#[route("/error")]
|
||||||
ThrowsError,
|
ThrowsError,
|
||||||
|
|
||||||
|
#[route("/async-error")]
|
||||||
|
ThrowsAsyncError,
|
||||||
|
|
||||||
#[route("/can-go-back")]
|
#[route("/can-go-back")]
|
||||||
HydrateCanGoBack,
|
HydrateCanGoBack,
|
||||||
}
|
}
|
||||||
@@ -52,6 +55,20 @@ fn ThrowsError() -> Element {
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
fn ThrowsAsyncError() -> Element {
|
||||||
|
#[server]
|
||||||
|
async fn error_after_delay() -> ServerFnResult<()> {
|
||||||
|
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
||||||
|
Err(ServerFnError::new("Async error from a server function"))
|
||||||
|
}
|
||||||
|
|
||||||
|
use_server_future(error_after_delay)?().unwrap()?;
|
||||||
|
rsx! {
|
||||||
|
"Hello, world!"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
fn Home() -> Element {
|
fn Home() -> Element {
|
||||||
let route: Route = use_route();
|
let route: Route = use_route();
|
||||||
|
|||||||
@@ -241,7 +241,7 @@ impl SsrRendererPool {
|
|||||||
|
|
||||||
// check if there are any errors
|
// check if there are any errors
|
||||||
let errors = virtual_dom.in_runtime(|| {
|
let errors = virtual_dom.in_runtime(|| {
|
||||||
let error_context: ErrorContext = ScopeId::APP
|
let error_context: ErrorContext = ScopeId::ROOT_ERROR_BOUNDARY
|
||||||
.consume_context()
|
.consume_context()
|
||||||
.expect("The root should be under an error boundary");
|
.expect("The root should be under an error boundary");
|
||||||
let errors = error_context.errors();
|
let errors = error_context.errors();
|
||||||
|
|||||||
@@ -499,7 +499,7 @@ pub unsafe fn apply_patch(mut table: JumpTable) -> Result<(), PatchError> {
|
|||||||
// On non-wasm platforms we can just use libloading and the known aslr offsets to load the library
|
// On non-wasm platforms we can just use libloading and the known aslr offsets to load the library
|
||||||
#[cfg(any(unix, windows))]
|
#[cfg(any(unix, windows))]
|
||||||
{
|
{
|
||||||
// on android we try to cirumvent permissions issues by copying the library to a memmap and then libloading that
|
// on android we try to circumvent permissions issues by copying the library to a memmap and then libloading that
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
let lib = Box::leak(Box::new(android_memmap_dlopen(&table.lib)?));
|
let lib = Box::leak(Box::new(android_memmap_dlopen(&table.lib)?));
|
||||||
|
|
||||||
|
|||||||
@@ -262,7 +262,7 @@ impl<'a> Splitter<'a> {
|
|||||||
self.clear_data_segments(&mut out, &unique_symbols);
|
self.clear_data_segments(&mut out, &unique_symbols);
|
||||||
|
|
||||||
// Clear out the element segments and then add in the initializers for the shared imports
|
// Clear out the element segments and then add in the initializers for the shared imports
|
||||||
self.create_ifunc_initialzers(&mut out, &unique_symbols);
|
self.create_ifunc_initializers(&mut out, &unique_symbols);
|
||||||
|
|
||||||
// Convert our split module's functions to real functions that call the indirect function
|
// Convert our split module's functions to real functions that call the indirect function
|
||||||
self.add_split_imports(
|
self.add_split_imports(
|
||||||
@@ -332,7 +332,7 @@ impl<'a> Splitter<'a> {
|
|||||||
self.clear_data_segments(&mut out, &unique_symbols);
|
self.clear_data_segments(&mut out, &unique_symbols);
|
||||||
|
|
||||||
// Clear out the element segments and then add in the initializers for the shared imports
|
// Clear out the element segments and then add in the initializers for the shared imports
|
||||||
self.create_ifunc_initialzers(&mut out, &unique_symbols);
|
self.create_ifunc_initializers(&mut out, &unique_symbols);
|
||||||
|
|
||||||
// We have to make sure our table matches that of the other tables even though we don't call them.
|
// We have to make sure our table matches that of the other tables even though we don't call them.
|
||||||
let ifunc_table_id = self.load_funcref_table(&mut out);
|
let ifunc_table_id = self.load_funcref_table(&mut out);
|
||||||
@@ -554,7 +554,7 @@ impl<'a> Splitter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Creates the jump points
|
/// Creates the jump points
|
||||||
fn create_ifunc_initialzers(&self, out: &mut Module, unique_symbols: &HashSet<Node>) {
|
fn create_ifunc_initializers(&self, out: &mut Module, unique_symbols: &HashSet<Node>) {
|
||||||
let ifunc_table = self.load_funcref_table(out);
|
let ifunc_table = self.load_funcref_table(out);
|
||||||
|
|
||||||
let mut initializers = HashMap::new();
|
let mut initializers = HashMap::new();
|
||||||
@@ -1009,7 +1009,7 @@ impl<'a> Splitter<'a> {
|
|||||||
let mut recovered_children = HashSet::new();
|
let mut recovered_children = HashSet::new();
|
||||||
for lost in lost_children {
|
for lost in lost_children {
|
||||||
match lost {
|
match lost {
|
||||||
// Functions need to be found - the wasm decsribe functions are usually completely dissolved
|
// Functions need to be found - the wasm describe functions are usually completely dissolved
|
||||||
Node::Function(id) => {
|
Node::Function(id) => {
|
||||||
let func = original.module.funcs.get(id);
|
let func = original.module.funcs.get(id);
|
||||||
let name = func.name.as_ref().unwrap();
|
let name = func.name.as_ref().unwrap();
|
||||||
|
|||||||
Reference in new issue
Block a user