Fix hydration of suspense fragments that contain text nodes (#3287)

* Fix hydration of suspense fragments that contain text nodes

* fix release builds

* disable link deduplication

* restore meta tag deduplication

* Add a test for the failed hydration case

* make changes to the document trait non-breaking

* remove info log from server future hook

* restore default implementation - this was also a breaking change

* fix semver breakage

* clean up server_future

* Cleanup unused code

* fix clippy

---------

Co-authored-by: Jonathan Kelley <jkelleyrtp@gmail.com>
This commit is contained in:
Evan AlmloffandJonathan Kelley authored and GitHub committed 2025-01-07 19:47:23 -08:00
1 parent 57d637184f
commit d41ab3a756
33 files changed
+644 -222

No files matched your search

+19 -5
View File
@@ -31,7 +31,7 @@ export class BaseInterpreter {
// sledgehammer is generating this...
m: any;
constructor() { }
constructor() {}
initialize(root: HTMLElement, handler: EventListener | null = null) {
this.global = {};
@@ -45,7 +45,7 @@ export class BaseInterpreter {
this.handler = handler;
// make sure to set the root element's ID so it still registers events
root.setAttribute('data-dioxus-id', "0");
root.setAttribute("data-dioxus-id", "0");
}
handleResizeEvent(entry: ResizeObserverEntry) {
@@ -241,6 +241,18 @@ export class BaseInterpreter {
NodeFilter.SHOW_COMMENT
);
let nextSibling = under.nextSibling;
// Continue to the next node. Returns false if we should stop traversing because we've reached the end of the children
// of the root
let continueToNextNode = () => {
// stop traversing if there are no more nodes
if (!treeWalker.nextNode()) {
return false;
}
// stop traversing if we have reached the next sibling of the root node
return treeWalker.currentNode !== nextSibling;
};
while (treeWalker.currentNode) {
const currentNode = treeWalker.currentNode as ChildNode;
if (currentNode.nodeType === Node.COMMENT_NODE) {
@@ -251,7 +263,7 @@ export class BaseInterpreter {
if (placeholderSplit.length > 1) {
this.nodes[ids[parseInt(placeholderSplit[1])]] = currentNode;
if (!treeWalker.nextNode()) {
if (!continueToNextNode()) {
break;
}
continue;
@@ -284,7 +296,9 @@ export class BaseInterpreter {
}
treeWalker.currentNode = commentAfterText;
this.nodes[ids[parseInt(textNodeSplit[1])]] = textNode;
let exit = !treeWalker.nextNode();
// Stop traversing if we started on a comment node (which has no children)
// or the next sibling stops the walk
let exit = currentNode === under || !continueToNextNode();
// remove the comment node after the text node
commentAfterText.remove();
if (exit) {
@@ -293,7 +307,7 @@ export class BaseInterpreter {
continue;
}
}
if (!treeWalker.nextNode()) {
if (!continueToNextNode()) {
break;
}
}
+9 -4
View File
@@ -1,11 +1,16 @@
import "./hydrate_types";
import { HydrationCallback } from "./hydrate_types";
export function register_rehydrate_chunk_for_streaming(
callback: (id: number[], data: Uint8Array) => void
export function register_rehydrate_chunk_for_streaming(callback: HydrationCallback): void {
return register_rehydrate_chunk_for_streaming_debug(callback);
}
export function register_rehydrate_chunk_for_streaming_debug(
callback: HydrationCallback
): void {
window.hydration_callback = callback;
for (let i = 0; i < window.hydrate_queue.length; i++) {
const [id, data] = window.hydrate_queue[i];
window.hydration_callback(id, data);
const [id, data, debug_types, debug_locations] = window.hydrate_queue[i];
window.hydration_callback(id, data, debug_types, debug_locations);
}
}
+12 -3
View File
@@ -1,8 +1,17 @@
export {};
export { };
export type HydrationCallback = (
id: number[],
data: Uint8Array,
debug_types: string[] | null,
debug_locations: string[] | null
) => void;
declare global {
interface Window {
hydrate_queue: [number[], Uint8Array][];
hydration_callback: null | ((id: number[], data: Uint8Array) => void);
hydrate_queue: [number[], Uint8Array, string[] | null, string[] | null][];
hydration_callback:
| null
| HydrationCallback;
}
}
@@ -4,13 +4,18 @@ import "./hydrate_types";
window.hydrate_queue = [];
// @ts-ignore
window.dx_hydrate = (id: number[], data: string) => {
window.dx_hydrate = (
id: number[],
data: string,
debug_types: string[] | null,
debug_locations: string[] | null
) => {
// First convert the base64 encoded string to a Uint8Array
const decoded = atob(data);
const bytes = Uint8Array.from(decoded, (c) => c.charCodeAt(0));
if (window.hydration_callback) {
window.hydration_callback(id, bytes);
window.hydration_callback(id, bytes, debug_types, debug_locations);
} else {
window.hydrate_queue.push([id, bytes]);
window.hydrate_queue.push([id, bytes, debug_types, debug_locations]);
}
};