@tanstack/redact 0.0.13 → 0.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/internal.d.ts +23 -39
- package/dist/core/internal.js +7 -23
- package/dist/core/internal.js.map +3 -3
- package/dist/dom/dispatcher.d.ts +3 -1
- package/dist/dom/dispatcher.js +104 -97
- package/dist/dom/dispatcher.js.map +2 -2
- package/dist/dom/dom.js +1 -1
- package/dist/dom/dom.js.map +2 -2
- package/dist/dom/event-replay.js +7 -29
- package/dist/dom/event-replay.js.map +2 -2
- package/dist/dom/features/class/full.js +16 -16
- package/dist/dom/features/class/full.js.map +2 -2
- package/dist/dom/features/class/stub.js +7 -7
- package/dist/dom/features/class/stub.js.map +2 -2
- package/dist/dom/features/context/full.js +7 -7
- package/dist/dom/features/context/full.js.map +2 -2
- package/dist/dom/features/context/stub.js +3 -3
- package/dist/dom/features/context/stub.js.map +2 -2
- package/dist/dom/features/forward-ref/full.js +11 -11
- package/dist/dom/features/forward-ref/full.js.map +2 -2
- package/dist/dom/features/hydration/full.d.ts +26 -14
- package/dist/dom/features/hydration/full.js +412 -92
- package/dist/dom/features/hydration/full.js.map +2 -2
- package/dist/dom/features/hydration/stub.d.ts +13 -12
- package/dist/dom/features/hydration/stub.js +23 -8
- package/dist/dom/features/hydration/stub.js.map +2 -2
- package/dist/dom/features/lazy/full.js +3 -43
- package/dist/dom/features/lazy/full.js.map +2 -2
- package/dist/dom/features/lazy/stub.js +1 -1
- package/dist/dom/features/lazy/stub.js.map +2 -2
- package/dist/dom/features/memo/full.js +3 -3
- package/dist/dom/features/memo/full.js.map +2 -2
- package/dist/dom/features/memo/stub.js +1 -1
- package/dist/dom/features/memo/stub.js.map +2 -2
- package/dist/dom/features/portal/full.js +2 -2
- package/dist/dom/features/portal/full.js.map +2 -2
- package/dist/dom/features/suspense/full.js +151 -109
- package/dist/dom/features/suspense/full.js.map +2 -2
- package/dist/dom/reconcile.d.ts +3 -1
- package/dist/dom/reconcile.js +141 -129
- package/dist/dom/reconcile.js.map +2 -2
- package/dist/dom/root-internal.d.ts +9 -0
- package/dist/dom/root-internal.js +31 -0
- package/dist/dom/root-internal.js.map +7 -0
- package/dist/dom/root.d.ts +1 -1
- package/dist/dom/root.js +4 -136
- package/dist/dom/root.js.map +2 -2
- package/dist/dom/test-utils.js +1 -1
- package/dist/dom/test-utils.js.map +2 -2
- package/dist/react/children.js +4 -1
- package/dist/react/children.js.map +2 -2
- package/dist/react/class.js +8 -2
- package/dist/react/class.js.map +2 -2
- package/dist/react/context.js +4 -1
- package/dist/react/context.js.map +2 -2
- package/dist/react/index.js +1 -1
- package/dist/react/index.js.map +2 -2
- package/dist/react/shared-internals.d.ts +4 -7
- package/dist/react/shared-internals.js +4 -7
- package/dist/react/shared-internals.js.map +2 -2
- package/dist/server/stream.d.ts +5 -1
- package/dist/server/stream.js.map +2 -2
- package/dist/vite/index.js +19 -3
- package/dist/vite/index.js.map +2 -2
- package/package.json +1 -1
- package/src/core/internal.ts +30 -50
- package/src/dom/dispatcher.ts +114 -105
- package/src/dom/dom.ts +1 -1
- package/src/dom/event-replay.ts +14 -30
- package/src/dom/features/class/full.ts +16 -16
- package/src/dom/features/class/stub.ts +7 -7
- package/src/dom/features/context/full.ts +7 -7
- package/src/dom/features/context/stub.ts +3 -3
- package/src/dom/features/forward-ref/full.ts +11 -11
- package/src/dom/features/hydration/full.ts +516 -104
- package/src/dom/features/hydration/stub.ts +28 -14
- package/src/dom/features/lazy/full.ts +3 -56
- package/src/dom/features/lazy/stub.ts +1 -1
- package/src/dom/features/memo/full.ts +3 -3
- package/src/dom/features/memo/stub.ts +1 -1
- package/src/dom/features/portal/full.ts +2 -2
- package/src/dom/features/suspense/full.ts +176 -129
- package/src/dom/reconcile.ts +164 -170
- package/src/dom/root-internal.ts +40 -0
- package/src/dom/root.ts +5 -165
- package/src/dom/test-utils.ts +1 -1
- package/src/react/children.ts +4 -1
- package/src/react/class.ts +8 -2
- package/src/react/context.ts +4 -1
- package/src/react/index.ts +1 -1
- package/src/react/shared-internals.ts +8 -14
- package/src/server/stream.ts +9 -4
- package/src/vite/index.ts +34 -3
|
@@ -7,29 +7,24 @@ import type { Fiber, FiberRoot } from '../../../core'
|
|
|
7
7
|
// element matching, and the WeakMap of cursors per fiber are all stripped.
|
|
8
8
|
|
|
9
9
|
export class HydrationCursor {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
n: ChildNode | null = null
|
|
11
|
+
p: Node
|
|
12
|
+
e: ChildNode | null = null
|
|
13
13
|
constructor(parent?: Node, _s?: ChildNode | null, _e?: ChildNode | null) {
|
|
14
|
-
this.
|
|
14
|
+
this.p = parent as Node
|
|
15
15
|
}
|
|
16
|
-
|
|
16
|
+
take(): ChildNode | null {
|
|
17
17
|
return null
|
|
18
18
|
}
|
|
19
|
-
|
|
19
|
+
head(): ChildNode | null {
|
|
20
20
|
return null
|
|
21
21
|
}
|
|
22
|
-
|
|
23
|
-
return
|
|
22
|
+
has(): boolean {
|
|
23
|
+
return false
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
export
|
|
28
|
-
kind: 'pending' | 'resolved'
|
|
29
|
-
id: number
|
|
30
|
-
startMark: Comment
|
|
31
|
-
endMark: Comment
|
|
32
|
-
}
|
|
27
|
+
export type BoundaryInfo = [0 | 1, number, Comment, Comment]
|
|
33
28
|
|
|
34
29
|
export function beginHydration(_root: FiberRoot): void {
|
|
35
30
|
throw new Error(
|
|
@@ -70,3 +65,22 @@ export function findHostParent(fiber: Fiber): Fiber {
|
|
|
70
65
|
export function installHydrationScrollGuard(): void {}
|
|
71
66
|
|
|
72
67
|
export function drainReplayQueue(): void {}
|
|
68
|
+
|
|
69
|
+
export interface HydrationBailoutError extends Error {
|
|
70
|
+
f: Fiber | null
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function isHydrationBailout(_error: unknown): _error is HydrationBailoutError {
|
|
74
|
+
return false
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function abortHydration(cause: unknown, fiber: Fiber | null = null): never {
|
|
78
|
+
const error = (cause instanceof Error ? cause : new Error('Hydration mismatch.')) as HydrationBailoutError
|
|
79
|
+
;(error as any).f = fiber
|
|
80
|
+
throw error
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function hydrateRootImpl(): never {
|
|
84
|
+
beginHydration(null as any)
|
|
85
|
+
throw new Error('`hydrateRoot` requires the `hydration` feature.')
|
|
86
|
+
}
|
|
@@ -4,16 +4,10 @@ import {
|
|
|
4
4
|
registerRenderer,
|
|
5
5
|
registerTypeMatcher,
|
|
6
6
|
renderFiber,
|
|
7
|
-
scheduleUpdate,
|
|
8
7
|
isThenable,
|
|
9
8
|
handleSuspended,
|
|
10
|
-
|
|
9
|
+
deferHydration,
|
|
11
10
|
} from '../../reconcile'
|
|
12
|
-
import {
|
|
13
|
-
getHydrationCursor,
|
|
14
|
-
setHydrationCursor,
|
|
15
|
-
findHostParent as findHydrationHost,
|
|
16
|
-
} from '../hydration'
|
|
17
11
|
|
|
18
12
|
function renderLazy(fiber: Fiber, domParent: Node, anchor: Node | null): void {
|
|
19
13
|
const { _payload, _init } = fiber.type as any
|
|
@@ -22,54 +16,7 @@ function renderLazy(fiber: Fiber, domParent: Node, anchor: Node | null): void {
|
|
|
22
16
|
resolved = _init(_payload)
|
|
23
17
|
} catch (thenable: any) {
|
|
24
18
|
if (isThenable(thenable)) {
|
|
25
|
-
|
|
26
|
-
// Suspense boundary needs special handling: the SSR DOM for its
|
|
27
|
-
// resolved content is already in the page and our cursor is pointing
|
|
28
|
-
// at it. A normal `handleSuspended` would schedule the lazy's later
|
|
29
|
-
// re-render WITHOUT the hydration cursor — so when it eventually
|
|
30
|
-
// resolves, we'd create a fresh DOM copy next to the SSR one (visible
|
|
31
|
-
// as duplicate logos / buttons / sections inside the Suspense). Mirror
|
|
32
|
-
// the pattern in renderFunction: preserve the in-scope cursor on this
|
|
33
|
-
// fiber and flag it for deferred re-hydration, so rerenderFiber
|
|
34
|
-
// restores `root.hydrating = true` and the resolved render adopts the
|
|
35
|
-
// existing DOM instead of mounting a duplicate.
|
|
36
|
-
const root = getCurrentRoot()
|
|
37
|
-
if (root?.hydrating) {
|
|
38
|
-
const hostParent = findHydrationHost(fiber)
|
|
39
|
-
const inheritedCursor = getHydrationCursor(hostParent)
|
|
40
|
-
if (inheritedCursor) {
|
|
41
|
-
setHydrationCursor(fiber, inheritedCursor)
|
|
42
|
-
}
|
|
43
|
-
fiber.memoizedState = {
|
|
44
|
-
...(fiber.memoizedState ?? {}),
|
|
45
|
-
_pendingHydration: true,
|
|
46
|
-
}
|
|
47
|
-
// Mark the nearest ancestor Suspense as "awaiting hydration-resume"
|
|
48
|
-
// so a post-hydration re-render of that Suspense doesn't accidentally
|
|
49
|
-
// flip into suspended+pending and mount a fallback atop the SSR
|
|
50
|
-
// content. Match-by-tag-name works whether Suspense is the full
|
|
51
|
-
// feature or stubbed to Fragment (the walk just never finds one).
|
|
52
|
-
let sus: Fiber | null = fiber.parent
|
|
53
|
-
while (sus && sus.tag !== FiberTag.Suspense) sus = sus.parent
|
|
54
|
-
if (sus && sus.memoizedState) {
|
|
55
|
-
;(sus.memoizedState as any)._awaitingLazyHydration = true
|
|
56
|
-
}
|
|
57
|
-
thenable.then(
|
|
58
|
-
() => {
|
|
59
|
-
if (sus && sus.memoizedState) {
|
|
60
|
-
;(sus.memoizedState as any)._awaitingLazyHydration = false
|
|
61
|
-
}
|
|
62
|
-
scheduleUpdate(fiber)
|
|
63
|
-
},
|
|
64
|
-
() => {
|
|
65
|
-
if (sus && sus.memoizedState) {
|
|
66
|
-
;(sus.memoizedState as any)._awaitingLazyHydration = false
|
|
67
|
-
}
|
|
68
|
-
scheduleUpdate(fiber)
|
|
69
|
-
},
|
|
70
|
-
)
|
|
71
|
-
return
|
|
72
|
-
}
|
|
19
|
+
if (deferHydration(fiber, thenable)) return
|
|
73
20
|
handleSuspended(fiber, thenable)
|
|
74
21
|
// reconcileChildren would be called here if we were rendering children,
|
|
75
22
|
// but Lazy delegates and has no children of its own.
|
|
@@ -81,7 +28,7 @@ function renderLazy(fiber: Fiber, domParent: Node, anchor: Node | null): void {
|
|
|
81
28
|
const savedType = fiber.type
|
|
82
29
|
fiber.type = resolved
|
|
83
30
|
fiber.tag =
|
|
84
|
-
typeof resolved
|
|
31
|
+
typeof resolved == 'function'
|
|
85
32
|
? resolved.prototype?.isReactComponent
|
|
86
33
|
? FiberTag.Class
|
|
87
34
|
: FiberTag.Function
|
|
@@ -28,7 +28,7 @@ function renderLazyStub(fiber: Fiber, domParent: Node, anchor: Node | null): voi
|
|
|
28
28
|
const savedType = fiber.type
|
|
29
29
|
fiber.type = resolved
|
|
30
30
|
fiber.tag =
|
|
31
|
-
typeof resolved
|
|
31
|
+
typeof resolved == 'function'
|
|
32
32
|
? resolved.prototype?.isReactComponent
|
|
33
33
|
? FiberTag.Class
|
|
34
34
|
: FiberTag.Function
|
|
@@ -25,8 +25,8 @@ function shallowEqual(a: any, b: any): boolean {
|
|
|
25
25
|
|
|
26
26
|
function renderMemo(fiber: Fiber, domParent: Node, anchor: Node | null): void {
|
|
27
27
|
const { type, compare } = fiber.type as any
|
|
28
|
-
const props = fiber.
|
|
29
|
-
const prev = fiber.
|
|
28
|
+
const props = fiber.pp ?? {}
|
|
29
|
+
const prev = fiber.mp
|
|
30
30
|
|
|
31
31
|
// Memo's prop-equality gate guards PARENT-triggered rerenders. If this
|
|
32
32
|
// render is a STATE-triggered rerender of this exact fiber (hook update,
|
|
@@ -45,7 +45,7 @@ function renderMemo(fiber: Fiber, domParent: Node, anchor: Node | null): void {
|
|
|
45
45
|
// `forwardRef`. Without the marker-based branch we'd mistreat
|
|
46
46
|
// `memo(forwardRef(...))` as a Fragment and render nothing.
|
|
47
47
|
let innerTag: FiberTag
|
|
48
|
-
if (typeof type
|
|
48
|
+
if (typeof type == 'function') {
|
|
49
49
|
innerTag = type.prototype?.isReactComponent ? FiberTag.Class : FiberTag.Function
|
|
50
50
|
} else if (type && typeof type === 'object') {
|
|
51
51
|
const m = (type as any).$$typeof
|
|
@@ -14,7 +14,7 @@ function renderMemoStub(fiber: Fiber, domParent: Node, anchor: Node | null): voi
|
|
|
14
14
|
const { type } = fiber.type as any
|
|
15
15
|
|
|
16
16
|
let innerTag: FiberTag
|
|
17
|
-
if (typeof type
|
|
17
|
+
if (typeof type == 'function') {
|
|
18
18
|
innerTag = type.prototype?.isReactComponent ? FiberTag.Class : FiberTag.Function
|
|
19
19
|
} else if (type && typeof type === 'object') {
|
|
20
20
|
const m = (type as any).$$typeof
|
|
@@ -9,12 +9,12 @@ import {
|
|
|
9
9
|
} from '../../reconcile'
|
|
10
10
|
|
|
11
11
|
function renderPortal(fiber: Fiber, _domParent: Node, _anchor: Node | null): void {
|
|
12
|
-
const { children, container } = fiber.
|
|
12
|
+
const { children, container } = fiber.pp as {
|
|
13
13
|
children: ReactNode
|
|
14
14
|
container: Element
|
|
15
15
|
}
|
|
16
16
|
reconcileChildren(fiber, childrenToArray(children), container, null)
|
|
17
|
-
fiber.
|
|
17
|
+
fiber.mp = fiber.pp
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
registerElementMarker(REACT_PORTAL_TYPE)
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
runEffects,
|
|
15
15
|
getCurrentRoot,
|
|
16
16
|
withCurrentRoot,
|
|
17
|
+
discardPendingWork,
|
|
17
18
|
} from '../../reconcile'
|
|
18
19
|
import {
|
|
19
20
|
HydrationCursor,
|
|
@@ -21,16 +22,13 @@ import {
|
|
|
21
22
|
clearHydrationCursor,
|
|
22
23
|
advanceCursorPast,
|
|
23
24
|
tryConsumeBoundary,
|
|
25
|
+
isHydrationBailout,
|
|
24
26
|
} from '../hydration'
|
|
25
27
|
|
|
26
|
-
|
|
28
|
+
let suspendHandler: ((t: Promise<any>) => void) | null = null
|
|
27
29
|
|
|
28
30
|
function realHandleSuspended(fiber: Fiber, thenable: Promise<any>): void {
|
|
29
|
-
|
|
30
|
-
if (handler) {
|
|
31
|
-
handler(thenable)
|
|
32
|
-
return
|
|
33
|
-
}
|
|
31
|
+
if (suspendHandler) return suspendHandler(thenable)
|
|
34
32
|
// Fallback: schedule re-render when promise settles
|
|
35
33
|
thenable.then(
|
|
36
34
|
() => scheduleUpdate(fiber),
|
|
@@ -44,43 +42,34 @@ function realHandleSuspended(fiber: Fiber, thenable: Promise<any>): void {
|
|
|
44
42
|
// and component state survive across the suspension. The fallback is mounted
|
|
45
43
|
// alongside the hidden primary until the pending promise resolves.
|
|
46
44
|
//
|
|
47
|
-
// We track the hidden subtree DOM in `state.
|
|
45
|
+
// We track the hidden subtree DOM in `state.d` (root host nodes +
|
|
48
46
|
// their original `display` so we can restore it) and the fallback as a
|
|
49
|
-
// detached Fragment fiber in `state.
|
|
47
|
+
// detached Fragment fiber in `state.f` (deliberately kept OUT of
|
|
50
48
|
// `fiber.child` so reconciles against `props.children` don't trip on it).
|
|
51
49
|
// First-mount suspensions have no committed DOM worth preserving, so they
|
|
52
50
|
// keep the original unmount-and-render-fallback behavior.
|
|
53
51
|
interface SuspenseState {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
endMark?: Comment
|
|
60
|
-
realChildren?: any
|
|
61
|
-
_awaitingLazyHydration?: boolean
|
|
52
|
+
p?: Promise<any> | null
|
|
53
|
+
b?: Comment
|
|
54
|
+
e?: Comment
|
|
55
|
+
r?: any
|
|
56
|
+
a?: boolean
|
|
62
57
|
// Re-suspend preservation:
|
|
63
|
-
|
|
64
|
-
|
|
58
|
+
d?: Array<[HTMLElement, string]> | null
|
|
59
|
+
f?: Fiber | null
|
|
65
60
|
}
|
|
66
61
|
|
|
67
62
|
function renderSuspense(fiber: Fiber, domParent: Node, anchor: Node | null): void {
|
|
68
|
-
const props = fiber.
|
|
69
|
-
const state = (fiber.
|
|
70
|
-
suspended: false,
|
|
71
|
-
pending: null as Promise<any> | null,
|
|
72
|
-
hiddenDoms: null,
|
|
73
|
-
fallbackFiber: null,
|
|
74
|
-
}) as SuspenseState
|
|
63
|
+
const props = fiber.pp ?? {}
|
|
64
|
+
const state = (fiber.ms ??= {}) as SuspenseState
|
|
75
65
|
|
|
76
66
|
// Streaming hydration: if the next DOM node is a server-emitted boundary
|
|
77
67
|
// marker, route through the boundary-aware hydration path.
|
|
78
68
|
const root = getCurrentRoot()
|
|
79
|
-
if (root?.
|
|
80
|
-
const boundary = tryConsumeBoundary(fiber.parent)
|
|
69
|
+
if (root?.h && !state.b) {
|
|
70
|
+
const boundary = tryConsumeBoundary(fiber.parent!)
|
|
81
71
|
if (boundary) {
|
|
82
72
|
hydrateSuspenseBoundary(fiber, props, boundary, domParent, anchor)
|
|
83
|
-
state.hydrated = true
|
|
84
73
|
return
|
|
85
74
|
}
|
|
86
75
|
}
|
|
@@ -91,8 +80,8 @@ function renderSuspense(fiber: Fiber, domParent: Node, anchor: Node | null): voi
|
|
|
91
80
|
// yet. Until the Lazy's resume fires, skip our own tryChildren pass so
|
|
92
81
|
// an unrelated re-render can't accidentally flip us into the suspended
|
|
93
82
|
// path and mount a duplicate fallback on top of the SSR content.
|
|
94
|
-
if (state.
|
|
95
|
-
fiber.
|
|
83
|
+
if (state.a) {
|
|
84
|
+
fiber.mp = props
|
|
96
85
|
return
|
|
97
86
|
}
|
|
98
87
|
|
|
@@ -100,78 +89,62 @@ function renderSuspense(fiber: Fiber, domParent: Node, anchor: Node | null): voi
|
|
|
100
89
|
// re-attempt primary children (would re-throw and churn the tree). Just
|
|
101
90
|
// refresh the fallback in case its JSX changed, and wait for the pending
|
|
102
91
|
// promise to fire scheduleUpdate.
|
|
103
|
-
if (state.
|
|
104
|
-
state.
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
if (state.suspended && state.pending && !state.fallbackFiber) {
|
|
113
|
-
reconcileChildren(fiber, childrenToArray(props.fallback), domParent, anchor)
|
|
114
|
-
fiber.memoizedProps = props
|
|
92
|
+
if (state.p) {
|
|
93
|
+
if (state.f) {
|
|
94
|
+
state.f.pp = { children: props.fallback }
|
|
95
|
+
renderFiber(state.f, domParent, anchor)
|
|
96
|
+
} else {
|
|
97
|
+
// Initial-mount suspended path: no committed primary to preserve.
|
|
98
|
+
reconcileChildren(fiber, childrenToArray(props.fallback), domParent, anchor)
|
|
99
|
+
}
|
|
100
|
+
fiber.mp = props
|
|
115
101
|
return
|
|
116
102
|
}
|
|
117
103
|
|
|
118
104
|
// Snapshot whether we have an existing committed primary tree before
|
|
119
105
|
// attempting the new render. If the new attempt suspends and we did have a
|
|
120
106
|
// committed primary, we keep it (hidden) rather than destroying it.
|
|
121
|
-
const hadCommittedPrimary = fiber.
|
|
107
|
+
const hadCommittedPrimary = fiber.mp !== undefined && fiber.child !== null
|
|
122
108
|
|
|
123
|
-
const
|
|
124
|
-
let
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
suspendedThenable = thenable
|
|
129
|
-
})
|
|
109
|
+
const prevHandler = suspendHandler
|
|
110
|
+
let pendingThenable: any
|
|
111
|
+
suspendHandler = (thenable) => {
|
|
112
|
+
pendingThenable = thenable
|
|
113
|
+
}
|
|
130
114
|
try {
|
|
131
115
|
reconcileChildren(fiber, childrenToArray(props.children), domParent, anchor)
|
|
132
116
|
} finally {
|
|
133
|
-
|
|
134
|
-
void savedHandler
|
|
117
|
+
suspendHandler = prevHandler
|
|
135
118
|
}
|
|
136
119
|
|
|
137
|
-
if (
|
|
138
|
-
state.
|
|
139
|
-
state.pending = suspendedThenable
|
|
120
|
+
if (pendingThenable) {
|
|
121
|
+
state.p = pendingThenable
|
|
140
122
|
const onSettle = () => {
|
|
141
|
-
state.
|
|
142
|
-
state.pending = null
|
|
123
|
+
state.p = null
|
|
143
124
|
scheduleUpdate(fiber)
|
|
144
125
|
}
|
|
145
|
-
|
|
126
|
+
pendingThenable.then(onSettle, onSettle)
|
|
146
127
|
|
|
147
128
|
if (hadCommittedPrimary && fiber.child) {
|
|
148
129
|
// Hide the primary subtree's root host doms so the fallback is the only
|
|
149
130
|
// thing visible, but the underlying nodes (and their scroll/state/focus)
|
|
150
131
|
// survive. Save original `display` for the resume path.
|
|
151
|
-
const
|
|
132
|
+
const hidden: Array<[HTMLElement, string]> = []
|
|
152
133
|
let c: Fiber | null = fiber.child
|
|
153
134
|
while (c) {
|
|
154
|
-
|
|
135
|
+
hideRootHostDoms(c, hidden)
|
|
155
136
|
c = c.sibling
|
|
156
137
|
}
|
|
157
|
-
|
|
158
|
-
for (const d of hostDoms) {
|
|
159
|
-
if (d.nodeType === 1) {
|
|
160
|
-
const el = d as HTMLElement
|
|
161
|
-
hidden.push([el, el.style.display])
|
|
162
|
-
el.style.display = 'none'
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
state.hiddenDoms = hidden
|
|
138
|
+
state.d = hidden
|
|
166
139
|
|
|
167
140
|
// Mount fallback in a detached Fragment fiber. Kept off `fiber.child`
|
|
168
141
|
// so reconciles of primary don't see it as a stale match candidate.
|
|
169
|
-
if (!state.
|
|
170
|
-
state.
|
|
171
|
-
state.
|
|
142
|
+
if (!state.f) {
|
|
143
|
+
state.f = createFiber(FiberTag.Fragment, null, null)
|
|
144
|
+
state.f.parent = fiber
|
|
172
145
|
}
|
|
173
|
-
state.
|
|
174
|
-
renderFiber(state.
|
|
146
|
+
state.f.pp = { children: props.fallback }
|
|
147
|
+
renderFiber(state.f, domParent, anchor)
|
|
175
148
|
} else {
|
|
176
149
|
// First-mount suspension — nothing to preserve.
|
|
177
150
|
unmountAllChildren(fiber, domParent)
|
|
@@ -180,32 +153,36 @@ function renderSuspense(fiber: Fiber, domParent: Node, anchor: Node | null): voi
|
|
|
180
153
|
} else {
|
|
181
154
|
// Render succeeded. Clean up any preserved-suspend state from a prior
|
|
182
155
|
// suspension cycle: unhide primary, unmount the orphan fallback fiber.
|
|
183
|
-
if (state.
|
|
184
|
-
for (const [el, origDisplay] of state.
|
|
156
|
+
if (state.d) {
|
|
157
|
+
for (const [el, origDisplay] of state.d) {
|
|
185
158
|
el.style.display = origDisplay
|
|
186
159
|
}
|
|
187
|
-
state.
|
|
160
|
+
state.d = null
|
|
188
161
|
}
|
|
189
|
-
if (state.
|
|
190
|
-
unmountFiber(state.
|
|
191
|
-
state.
|
|
162
|
+
if (state.f) {
|
|
163
|
+
unmountFiber(state.f, domParent)
|
|
164
|
+
state.f = null
|
|
192
165
|
}
|
|
193
166
|
}
|
|
194
|
-
fiber.
|
|
167
|
+
fiber.mp = props
|
|
195
168
|
}
|
|
196
169
|
|
|
197
170
|
// Walk a fiber subtree collecting host/text DOM nodes that sit at the root
|
|
198
171
|
// of the subtree (do not descend through their children — display:none on
|
|
199
172
|
// the root hides the whole element). Used by the hide-on-suspend path.
|
|
200
|
-
function
|
|
201
|
-
if (fiber.tag === FiberTag.Host
|
|
202
|
-
|
|
173
|
+
function hideRootHostDoms(fiber: Fiber, out: Array<[HTMLElement, string]>): void {
|
|
174
|
+
if (fiber.tag === FiberTag.Host) {
|
|
175
|
+
const el = fiber.dom as HTMLElement | null
|
|
176
|
+
if (el) {
|
|
177
|
+
out.push([el, el.style.display])
|
|
178
|
+
el.style.display = 'none'
|
|
179
|
+
}
|
|
203
180
|
return
|
|
204
181
|
}
|
|
205
182
|
if (fiber.tag === FiberTag.Portal) return
|
|
206
183
|
let c = fiber.child
|
|
207
184
|
while (c) {
|
|
208
|
-
|
|
185
|
+
hideRootHostDoms(c, out)
|
|
209
186
|
c = c.sibling
|
|
210
187
|
}
|
|
211
188
|
}
|
|
@@ -213,82 +190,152 @@ function collectRootHostDoms(fiber: Fiber, out: Node[]): void {
|
|
|
213
190
|
function hydrateSuspenseBoundary(
|
|
214
191
|
fiber: Fiber,
|
|
215
192
|
props: any,
|
|
216
|
-
boundary:
|
|
193
|
+
boundary: [0 | 1, number, Comment, Comment],
|
|
217
194
|
domParent: Node,
|
|
218
195
|
anchor: Node | null,
|
|
219
196
|
): void {
|
|
220
|
-
const
|
|
197
|
+
const [pendingBoundary, id, startMark, endMark] = boundary
|
|
221
198
|
// Record the boundary shape so we can re-hydrate on reveal.
|
|
222
|
-
fiber.
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
boundaryId: id,
|
|
227
|
-
startMark,
|
|
228
|
-
endMark,
|
|
229
|
-
realChildren: props.children,
|
|
199
|
+
fiber.ms = {
|
|
200
|
+
b: startMark,
|
|
201
|
+
e: endMark,
|
|
202
|
+
r: props.children,
|
|
230
203
|
}
|
|
231
204
|
|
|
232
|
-
if (
|
|
205
|
+
if (!pendingBoundary) {
|
|
233
206
|
// Real DOM is inline between startMark and endMark. Hydrate into it.
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
207
|
+
const parent = startMark.parentNode!
|
|
208
|
+
try {
|
|
209
|
+
setHydrationCursor(fiber, new HydrationCursor(parent, startMark.nextSibling, endMark))
|
|
210
|
+
reconcileChildren(fiber, childrenToArray(props.children), domParent, anchor)
|
|
211
|
+
advanceCursorPast(fiber.parent!, endMark)
|
|
212
|
+
} catch (e) {
|
|
213
|
+
if (!isHydrationBailout(e)) {
|
|
214
|
+
clearBoundaryRange(startMark, endMark)
|
|
215
|
+
unmountAllChildren(fiber, parent)
|
|
216
|
+
throw e
|
|
217
|
+
}
|
|
218
|
+
recoverBoundaryHydration(fiber, props.children, parent, startMark, endMark)
|
|
219
|
+
} finally {
|
|
220
|
+
clearHydrationCursor(fiber)
|
|
221
|
+
}
|
|
222
|
+
fiber.mp = props
|
|
240
223
|
return
|
|
241
224
|
}
|
|
242
225
|
|
|
243
226
|
// Pending: fallback DOM lives inside <div id="B:ID">. Hydrate the fallback
|
|
244
227
|
// React subtree against that div's children.
|
|
245
|
-
const bDiv =
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
228
|
+
const bDiv = document.getElementById(`B:${id}`)
|
|
229
|
+
try {
|
|
230
|
+
if (bDiv) {
|
|
231
|
+
setHydrationCursor(fiber, new HydrationCursor(bDiv))
|
|
232
|
+
reconcileChildren(fiber, childrenToArray(props.fallback), domParent, anchor)
|
|
233
|
+
} else {
|
|
234
|
+
// Couldn't find fallback container — render fresh (non-adopting)
|
|
235
|
+
reconcileChildren(fiber, childrenToArray(props.fallback), domParent, anchor)
|
|
236
|
+
}
|
|
237
|
+
} catch (e) {
|
|
238
|
+
if (!isHydrationBailout(e) || !bDiv) throw e
|
|
239
|
+
recoverFallbackHydration(fiber, props.fallback, bDiv)
|
|
240
|
+
} finally {
|
|
250
241
|
clearHydrationCursor(fiber)
|
|
251
|
-
} else {
|
|
252
|
-
// Couldn't find fallback container — render fresh (non-adopting)
|
|
253
|
-
reconcileChildren(fiber, childrenToArray(props.fallback), domParent, anchor)
|
|
254
242
|
}
|
|
255
243
|
advanceCursorPast(fiber.parent!, endMark)
|
|
256
244
|
|
|
257
245
|
// Register for server-streamed reveal (HTML chunks + $RC calls).
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
246
|
+
;(globalThis as any).$RH?.(id, () => rehydrateBoundary(fiber))
|
|
247
|
+
// If the inline runtime isn't present, nothing external will mark us dy.
|
|
248
|
+
|
|
249
|
+
fiber.mp = props
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function recoverBoundaryHydration(
|
|
253
|
+
fiber: Fiber,
|
|
254
|
+
children: any,
|
|
255
|
+
parent: Node,
|
|
256
|
+
startMark: Comment,
|
|
257
|
+
endMark: Comment,
|
|
258
|
+
): void {
|
|
259
|
+
const root = findRoot(fiber)!
|
|
260
|
+
const prevHydrating = root.h
|
|
261
|
+
discardPendingWork(root)
|
|
262
|
+
clearBoundaryRange(startMark, endMark)
|
|
263
|
+
unmountAllChildren(fiber, parent)
|
|
264
|
+
root.h = false
|
|
265
|
+
try {
|
|
266
|
+
reconcileChildren(fiber, childrenToArray(children), parent, endMark)
|
|
267
|
+
advanceCursorPast(fiber.parent!, endMark)
|
|
268
|
+
} catch (clientError) {
|
|
269
|
+
clearBoundaryRange(startMark, endMark)
|
|
270
|
+
unmountAllChildren(fiber, parent)
|
|
271
|
+
throw clientError
|
|
272
|
+
} finally {
|
|
273
|
+
root.h = prevHydrating
|
|
261
274
|
}
|
|
262
|
-
|
|
275
|
+
}
|
|
263
276
|
|
|
264
|
-
|
|
277
|
+
function recoverFallbackHydration(fiber: Fiber, fallback: any, parent: HTMLElement): void {
|
|
278
|
+
const root = findRoot(fiber)!
|
|
279
|
+
const prevHydrating = root.h
|
|
280
|
+
discardPendingWork(root)
|
|
281
|
+
parent.textContent = ''
|
|
282
|
+
unmountAllChildren(fiber, parent)
|
|
283
|
+
root.h = false
|
|
284
|
+
try {
|
|
285
|
+
reconcileChildren(fiber, childrenToArray(fallback), parent, null)
|
|
286
|
+
} catch (clientError) {
|
|
287
|
+
parent.textContent = ''
|
|
288
|
+
unmountAllChildren(fiber, parent)
|
|
289
|
+
throw clientError
|
|
290
|
+
} finally {
|
|
291
|
+
root.h = prevHydrating
|
|
292
|
+
}
|
|
265
293
|
}
|
|
266
294
|
|
|
267
295
|
function rehydrateBoundary(fiber: Fiber): void {
|
|
268
|
-
const state = fiber.
|
|
269
|
-
if (!state || !state.
|
|
296
|
+
const state = fiber.ms
|
|
297
|
+
if (!state || !state.b || !state.e) return
|
|
270
298
|
|
|
271
299
|
const root = findRoot(fiber)
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
if (!parent) return
|
|
300
|
+
const parent = state.b.parentNode as Node
|
|
301
|
+
if (!root || !parent) return
|
|
275
302
|
|
|
276
303
|
// Unmount existing fallback subtree. Its DOM has already been removed by $RC
|
|
277
|
-
// (or at least its container); unmounting here cleans up fibers +
|
|
304
|
+
// (or at least its container); unmounting here cleans up fibers + fx.
|
|
278
305
|
withCurrentRoot(root, () => {
|
|
279
|
-
|
|
306
|
+
const prevHydrating = root.h
|
|
307
|
+
try {
|
|
308
|
+
unmountAllChildren(fiber, parent)
|
|
280
309
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
310
|
+
// Re-hydrate with real children against the now-real DOM range.
|
|
311
|
+
root.h = true
|
|
312
|
+
setHydrationCursor(fiber, new HydrationCursor(parent, state.b.nextSibling, state.e))
|
|
313
|
+
reconcileChildren(fiber, childrenToArray(state.r), parent, null)
|
|
314
|
+
} catch (e) {
|
|
315
|
+
if (!isHydrationBailout(e)) {
|
|
316
|
+
clearBoundaryRange(state.b, state.e)
|
|
317
|
+
unmountAllChildren(fiber, parent)
|
|
318
|
+
throw e
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
recoverBoundaryHydration(fiber, state.r, parent, state.b, state.e)
|
|
322
|
+
} finally {
|
|
323
|
+
root.h = prevHydrating
|
|
324
|
+
clearHydrationCursor(fiber)
|
|
325
|
+
}
|
|
288
326
|
runEffects(root)
|
|
289
327
|
})
|
|
290
328
|
}
|
|
291
329
|
|
|
330
|
+
function clearBoundaryRange(startMark: Comment, endMark: Comment): void {
|
|
331
|
+
let node = startMark.nextSibling
|
|
332
|
+
while (node && node !== endMark) {
|
|
333
|
+
const next = node.nextSibling
|
|
334
|
+
node.parentNode?.removeChild(node)
|
|
335
|
+
node = next
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
292
339
|
registerTypeMatcher((type) => (type === REACT_SUSPENSE_TYPE ? FiberTag.Suspense : null))
|
|
293
340
|
registerRenderer(FiberTag.Suspense, renderSuspense)
|
|
294
341
|
installCapability('handleSuspended', realHandleSuspended)
|