@tanstack/redact 0.0.14 → 0.0.16
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 +417 -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 -111
- 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/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 +519 -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 -131
- 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/dom/reconcile.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
2
|
FiberTag,
|
|
3
|
-
FiberFlag,
|
|
4
3
|
createFiber,
|
|
5
4
|
REACT_ELEMENT_TYPE,
|
|
6
5
|
REACT_LEGACY_ELEMENT_TYPE,
|
|
@@ -23,13 +22,11 @@ import { makeDispatcher } from './dispatcher'
|
|
|
23
22
|
import {
|
|
24
23
|
adoptHostDom,
|
|
25
24
|
adoptTextDom,
|
|
26
|
-
tryConsumeBoundary,
|
|
27
|
-
advanceCursorPast,
|
|
28
25
|
setHydrationCursor,
|
|
29
26
|
getHydrationCursor,
|
|
30
27
|
clearHydrationCursor,
|
|
31
|
-
HydrationCursor,
|
|
32
28
|
findHostParent as findHydrationHost,
|
|
29
|
+
abortHydration,
|
|
33
30
|
} from './features/hydration'
|
|
34
31
|
|
|
35
32
|
// ---------------------------------------------------------------------------
|
|
@@ -52,19 +49,19 @@ const pendingRoots = new Set<FiberRoot>()
|
|
|
52
49
|
let forceRerenderingFiber: Fiber | null = null
|
|
53
50
|
|
|
54
51
|
export function scheduleUpdate(fiber: Fiber): void {
|
|
55
|
-
// Drop updates scheduled on already-
|
|
52
|
+
// Drop updates scheduled on already-um fibers. Subscribers (router,
|
|
56
53
|
// query, any external store) can fire after unmount if their cleanup was
|
|
57
54
|
// missed, and letting those reach rerenderFiber mounts zombie DOM into the
|
|
58
55
|
// old .parent's DOM (which stays reachable via the stale pointer).
|
|
59
|
-
if (fiber.
|
|
56
|
+
if (fiber.um) return
|
|
60
57
|
const root = findRoot(fiber)
|
|
61
58
|
if (!root) return
|
|
62
|
-
root.
|
|
63
|
-
fiber.
|
|
59
|
+
root.p.add(fiber)
|
|
60
|
+
fiber.dy = true
|
|
64
61
|
pendingRoots.add(root)
|
|
65
62
|
if (isBatching) return
|
|
66
|
-
if (!root.
|
|
67
|
-
root.
|
|
63
|
+
if (!root.s) {
|
|
64
|
+
root.s = true
|
|
68
65
|
queueMicrotask(flushPending)
|
|
69
66
|
}
|
|
70
67
|
}
|
|
@@ -98,23 +95,26 @@ function flushPending(): void {
|
|
|
98
95
|
let guard = 0
|
|
99
96
|
while (pendingRoots.size > 0) {
|
|
100
97
|
if (++guard > 50) {
|
|
101
|
-
|
|
98
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
99
|
+
throw new Error('flushPending exceeded 50 iterations — suspected infinite update loop.')
|
|
100
|
+
}
|
|
101
|
+
throw new Error()
|
|
102
102
|
}
|
|
103
103
|
const roots = [...pendingRoots]
|
|
104
104
|
pendingRoots.clear()
|
|
105
105
|
for (const root of roots) {
|
|
106
|
-
root.
|
|
106
|
+
root.s = false
|
|
107
107
|
// Render each pending fiber from shallowest first so an ancestor's
|
|
108
108
|
// cascade reaches descendants before we try to render them directly.
|
|
109
|
-
// Descendants rendered via cascade still have `
|
|
109
|
+
// Descendants rendered via cascade still have `dy=true` (only
|
|
110
110
|
// rerenderFiber clears it); when we later reach them in this loop,
|
|
111
|
-
// rerenderFiber's own `if (!
|
|
112
|
-
// previously filtered descendants of
|
|
111
|
+
// rerenderFiber's own `if (!dy) return` is our short-circuit. We
|
|
112
|
+
// previously filtered descendants of dy ancestors here, but that
|
|
113
113
|
// loses updates whenever an ancestor's render doesn't actually reach
|
|
114
114
|
// the descendant — e.g. React.memo bailing on equal props. Keep all
|
|
115
|
-
//
|
|
116
|
-
const pending = [...root.
|
|
117
|
-
root.
|
|
115
|
+
// dy fibers and let rerenderFiber de-dupe via its dy check.
|
|
116
|
+
const pending = [...root.p]
|
|
117
|
+
root.p.clear()
|
|
118
118
|
pending.sort((a, b) => fiberDepth(a) - fiberDepth(b))
|
|
119
119
|
for (const fiber of pending) {
|
|
120
120
|
rerenderFiber(fiber, root)
|
|
@@ -127,6 +127,12 @@ function flushPending(): void {
|
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
+
export function discardPendingWork(root: FiberRoot): void {
|
|
131
|
+
root.p.clear()
|
|
132
|
+
root.s = false
|
|
133
|
+
pendingRoots.delete(root)
|
|
134
|
+
}
|
|
135
|
+
|
|
130
136
|
function fiberDepth(fiber: Fiber): number {
|
|
131
137
|
let d = 0
|
|
132
138
|
let p: Fiber | null = fiber.parent
|
|
@@ -151,13 +157,13 @@ export function findRoot(fiber: Fiber): FiberRoot | null {
|
|
|
151
157
|
// ---------------------------------------------------------------------------
|
|
152
158
|
|
|
153
159
|
export function renderRoot(root: FiberRoot, children: ReactNode): void {
|
|
154
|
-
const rootFiber = root.
|
|
155
|
-
rootFiber.
|
|
160
|
+
const rootFiber = root.r
|
|
161
|
+
rootFiber.pp = { children }
|
|
156
162
|
currentRoot = root
|
|
157
163
|
try {
|
|
158
|
-
reconcileChildren(rootFiber, childrenToArray(children), root.
|
|
159
|
-
rootFiber.
|
|
160
|
-
rootFiber.
|
|
164
|
+
reconcileChildren(rootFiber, childrenToArray(children), root.c as Node, null)
|
|
165
|
+
rootFiber.mp = rootFiber.pp
|
|
166
|
+
rootFiber.dy = false
|
|
161
167
|
} finally {
|
|
162
168
|
currentRoot = null
|
|
163
169
|
}
|
|
@@ -165,29 +171,29 @@ export function renderRoot(root: FiberRoot, children: ReactNode): void {
|
|
|
165
171
|
}
|
|
166
172
|
|
|
167
173
|
function rerenderFiber(fiber: Fiber, root: FiberRoot): void {
|
|
168
|
-
if (!fiber.
|
|
169
|
-
// Skip fibers that were
|
|
174
|
+
if (!fiber.dy) return
|
|
175
|
+
// Skip fibers that were um between scheduling and flush. Without this,
|
|
170
176
|
// the flush loop re-enters a zombie fiber whose .parent is still set; its
|
|
171
177
|
// render mounts fresh DOM into the old parent's still-attached DOM (since
|
|
172
178
|
// unmountFiber only clears fiber.child, not fiber.parent). Visible as route
|
|
173
179
|
// content from a previous location staying on screen after nav, because a
|
|
174
|
-
// pending rerender on the old route's LibraryLandingPage (
|
|
180
|
+
// pending rerender on the old route's LibraryLandingPage (um during
|
|
175
181
|
// Outlet's shallow-first render) still fires from root.pending.
|
|
176
|
-
if (fiber.
|
|
182
|
+
if (fiber.um) return
|
|
177
183
|
// Clear BEFORE rendering so a scheduleUpdate() triggered mid-render (e.g.
|
|
178
|
-
// error boundary catching a descendant throw) marks us
|
|
184
|
+
// error boundary catching a descendant throw) marks us dy for the next
|
|
179
185
|
// flush iteration instead of being wiped out when render() completes.
|
|
180
|
-
fiber.
|
|
186
|
+
fiber.dy = false
|
|
181
187
|
currentRoot = root
|
|
182
188
|
// If this rerender is resuming a hydration that was deferred by a suspension,
|
|
183
189
|
// re-activate hydration mode for its duration so descendants adopt DOM
|
|
184
190
|
// instead of re-creating it.
|
|
185
191
|
const resumeHydration =
|
|
186
|
-
fiber.
|
|
187
|
-
const prevHydrating = root.
|
|
192
|
+
fiber.ms && (fiber.ms as any).p === true
|
|
193
|
+
const prevHydrating = root.h
|
|
188
194
|
if (resumeHydration) {
|
|
189
|
-
delete (fiber.
|
|
190
|
-
root.
|
|
195
|
+
delete (fiber.ms as any).p
|
|
196
|
+
root.h = true
|
|
191
197
|
}
|
|
192
198
|
const prevForcing = forceRerenderingFiber
|
|
193
199
|
forceRerenderingFiber = fiber
|
|
@@ -196,7 +202,7 @@ function rerenderFiber(fiber: Fiber, root: FiberRoot): void {
|
|
|
196
202
|
} finally {
|
|
197
203
|
forceRerenderingFiber = prevForcing
|
|
198
204
|
if (resumeHydration) {
|
|
199
|
-
root.
|
|
205
|
+
root.h = prevHydrating
|
|
200
206
|
// Deferred hydration completed — detach the preserved cursor so future
|
|
201
207
|
// updates (post-hydration state changes) don't try to adopt stale DOM.
|
|
202
208
|
clearHydrationCursor(fiber)
|
|
@@ -269,7 +275,7 @@ function pushChildren(node: ReactNode, out: NormalizedChild[]): void {
|
|
|
269
275
|
}
|
|
270
276
|
|
|
271
277
|
function isIterable(obj: any): boolean {
|
|
272
|
-
return obj != null && typeof obj
|
|
278
|
+
return obj != null && typeof obj[Symbol.iterator] == 'function'
|
|
273
279
|
}
|
|
274
280
|
|
|
275
281
|
function getKeyOf(child: NormalizedChild, index: number): string {
|
|
@@ -297,7 +303,7 @@ function fiberFromChild(child: NormalizedChild, parent: Fiber): Fiber {
|
|
|
297
303
|
if (!child) return createFiber(FiberTag.Fragment, null, null)
|
|
298
304
|
if (isTextChild(child)) {
|
|
299
305
|
const f = createFiber(FiberTag.Text, null, null)
|
|
300
|
-
f.
|
|
306
|
+
f.pp = child
|
|
301
307
|
f.parent = parent
|
|
302
308
|
return f
|
|
303
309
|
}
|
|
@@ -317,13 +323,13 @@ function fiberFromChild(child: NormalizedChild, parent: Fiber): Fiber {
|
|
|
317
323
|
if (matched !== null) break
|
|
318
324
|
}
|
|
319
325
|
if (matched !== null) tag = matched
|
|
320
|
-
else if (typeof type
|
|
326
|
+
else if (typeof type == 'function') {
|
|
321
327
|
tag = type.prototype && type.prototype.isReactComponent ? FiberTag.Class : FiberTag.Function
|
|
322
328
|
}
|
|
323
329
|
}
|
|
324
330
|
const f = createFiber(tag, type, child.key ?? null)
|
|
325
331
|
f.ref = (child as any).ref ?? null
|
|
326
|
-
f.
|
|
332
|
+
f.pp = child.props
|
|
327
333
|
f.parent = parent
|
|
328
334
|
return f
|
|
329
335
|
}
|
|
@@ -346,9 +352,9 @@ export function reconcileChildren(
|
|
|
346
352
|
// Fast path: unkeyed positional steady-state. Walk the existing sibling
|
|
347
353
|
// chain and newChildren in lockstep, validating AND committing in one pass.
|
|
348
354
|
// On any divergence we fall back to the slow path, which rebuilds the
|
|
349
|
-
// sibling chain anyway — partial
|
|
355
|
+
// sibling chain anyway — partial pp writes are idempotent.
|
|
350
356
|
// Skips the Map / Set / existing-array allocation entirely.
|
|
351
|
-
if (!currentRoot?.
|
|
357
|
+
if (!currentRoot?.h) {
|
|
352
358
|
let f: Fiber | null = parent.child
|
|
353
359
|
let ok = true
|
|
354
360
|
for (let i = 0; i < newChildren.length; i++) {
|
|
@@ -356,11 +362,11 @@ export function reconcileChildren(
|
|
|
356
362
|
if (child == null || !f || f.key != null) { ok = false; break }
|
|
357
363
|
if (typeof child === 'string') {
|
|
358
364
|
if (f.tag !== FiberTag.Text) { ok = false; break }
|
|
359
|
-
f.
|
|
365
|
+
f.pp = child
|
|
360
366
|
} else {
|
|
361
367
|
if ((child as ReactElement).key != null) { ok = false; break }
|
|
362
368
|
if (f.type !== (child as ReactElement).type) { ok = false; break }
|
|
363
|
-
f.
|
|
369
|
+
f.pp = (child as ReactElement).props
|
|
364
370
|
f.ref = (child as any).ref ?? null
|
|
365
371
|
}
|
|
366
372
|
f = f.sibling
|
|
@@ -395,7 +401,7 @@ export function reconcileChildren(
|
|
|
395
401
|
// budget > 0: more new than old remain → treat slot as an INSERTION: keep
|
|
396
402
|
// the old cursor and create a fresh fiber for new[i].
|
|
397
403
|
// budget < 0: more old than new remain → treat slot as a DELETION: advance
|
|
398
|
-
// the old cursor past the mismatched fiber (it'll be
|
|
404
|
+
// the old cursor past the mismatched fiber (it'll be um
|
|
399
405
|
// in the unclaimed pass) and retry.
|
|
400
406
|
// budget == 0: equal remaining → treat as REPLACE by preferring delete
|
|
401
407
|
// until budget flips positive or we hit a match.
|
|
@@ -453,7 +459,7 @@ export function reconcileChildren(
|
|
|
453
459
|
break
|
|
454
460
|
}
|
|
455
461
|
// Deletion (or replace-as-delete-first): advance past cand. It remains
|
|
456
|
-
// unclaimed and will be
|
|
462
|
+
// unclaimed and will be um at the end.
|
|
457
463
|
existingIdx++
|
|
458
464
|
budget++
|
|
459
465
|
}
|
|
@@ -467,10 +473,10 @@ export function reconcileChildren(
|
|
|
467
473
|
claimed.add(match)
|
|
468
474
|
fiber = match
|
|
469
475
|
if (isTextChild(child!)) {
|
|
470
|
-
fiber.
|
|
476
|
+
fiber.pp = child
|
|
471
477
|
} else {
|
|
472
478
|
fiber.type = (child as ReactElement).type
|
|
473
|
-
fiber.
|
|
479
|
+
fiber.pp = (child as ReactElement).props
|
|
474
480
|
fiber.ref = (child as any).ref ?? null
|
|
475
481
|
}
|
|
476
482
|
} else {
|
|
@@ -490,7 +496,7 @@ export function reconcileChildren(
|
|
|
490
496
|
// forward with the correct per-child anchor. During hydration the cursor
|
|
491
497
|
// walks DOM forward and each renderFiber adopts the next existing node,
|
|
492
498
|
// so per-child anchors are moot — fall back to the parent's anchor.
|
|
493
|
-
const hydrating = !!currentRoot?.
|
|
499
|
+
const hydrating = !!currentRoot?.h
|
|
494
500
|
for (let f: Fiber | null = parent.child; f; f = f.sibling) {
|
|
495
501
|
let a = anchor
|
|
496
502
|
if (!hydrating) {
|
|
@@ -542,10 +548,8 @@ export function reconcileChildren(
|
|
|
542
548
|
// on each re-render causes stylesheet flash and re-download. Head element
|
|
543
549
|
// ordering is semantically fluid; the browser doesn't care about exact
|
|
544
550
|
// order within <head>.
|
|
545
|
-
const parentIsHead =
|
|
546
|
-
|
|
547
|
-
(domParent as Element).tagName.toLowerCase() === 'head'
|
|
548
|
-
if (structurallyChanged && !currentRoot?.hydrating && !parentIsHead) {
|
|
551
|
+
const parentIsHead = (domParent as Element).nodeName === 'HEAD'
|
|
552
|
+
if (structurallyChanged && !currentRoot?.h && !parentIsHead) {
|
|
549
553
|
placeChildrenInOrder(parent, domParent, anchor)
|
|
550
554
|
}
|
|
551
555
|
}
|
|
@@ -710,11 +714,11 @@ export function renderFiber(fiber: Fiber, domParent: Node, anchor: Node | null):
|
|
|
710
714
|
}
|
|
711
715
|
|
|
712
716
|
function renderText(fiber: Fiber, domParent: Node, anchor: Node | null): void {
|
|
713
|
-
const text = fiber.
|
|
717
|
+
const text = fiber.pp as string
|
|
714
718
|
// Identity-unchanged fast path: skip the native Text.data write entirely.
|
|
715
|
-
if (fiber.dom && fiber.
|
|
719
|
+
if (fiber.dom && fiber.mp === text) return
|
|
716
720
|
if (!fiber.dom) {
|
|
717
|
-
const hydrated = currentRoot?.
|
|
721
|
+
const hydrated = currentRoot?.h ? adoptTextDom(fiber, fiber.parent!, text) : false
|
|
718
722
|
if (!hydrated) {
|
|
719
723
|
fiber.dom = document.createTextNode(text)
|
|
720
724
|
insertInto(domParent, fiber.dom, anchor)
|
|
@@ -724,13 +728,13 @@ function renderText(fiber: Fiber, domParent: Node, anchor: Node | null): void {
|
|
|
724
728
|
// hydration — `.data !== text` here is guaranteed, so write directly.
|
|
725
729
|
;(fiber.dom as Text).data = text
|
|
726
730
|
}
|
|
727
|
-
fiber.
|
|
728
|
-
//
|
|
731
|
+
fiber.mp = text
|
|
732
|
+
// dy cleared at rerender start; leaving true lets mid-render schedule persist
|
|
729
733
|
}
|
|
730
734
|
|
|
731
735
|
function renderHost(fiber: Fiber, domParent: Node, anchor: Node | null): void {
|
|
732
|
-
const props = fiber.
|
|
733
|
-
const prev = fiber.
|
|
736
|
+
const props = fiber.pp ?? {}
|
|
737
|
+
const prev = fiber.mp ?? {}
|
|
734
738
|
const type = fiber.type as string
|
|
735
739
|
const isSvg = type === 'svg' || (domParent as Element).namespaceURI === 'http://www.w3.org/2000/svg'
|
|
736
740
|
|
|
@@ -744,7 +748,7 @@ function renderHost(fiber: Fiber, domParent: Node, anchor: Node | null): void {
|
|
|
744
748
|
: undefined
|
|
745
749
|
|
|
746
750
|
if (!fiber.dom) {
|
|
747
|
-
const hydrated = currentRoot?.
|
|
751
|
+
const hydrated = currentRoot?.h ? adoptHostDom(fiber, fiber.parent!) : false
|
|
748
752
|
if (!hydrated) {
|
|
749
753
|
fiber.dom = createHostNode(type, isSvg)
|
|
750
754
|
// Two passes so form-control attributes (notably <input type>) are in
|
|
@@ -806,23 +810,22 @@ function renderHost(fiber: Fiber, domParent: Node, anchor: Node | null): void {
|
|
|
806
810
|
// structural mismatch (server produced more than client wants). Report.
|
|
807
811
|
// <head>/<html> are position-insensitive — leftover here is normal
|
|
808
812
|
// (Vite dev-style injections, SSR-only scripts, etc.).
|
|
809
|
-
if (currentRoot?.
|
|
813
|
+
if (currentRoot?.h) {
|
|
810
814
|
const parentTag = (fiber.type as string).toLowerCase()
|
|
811
|
-
|
|
815
|
+
const hasOpaqueHydrationChildren =
|
|
816
|
+
props.dangerouslySetInnerHTML != null ||
|
|
817
|
+
(parentTag === 'textarea' && (props.value != null || props.defaultValue != null))
|
|
818
|
+
if (parentTag !== 'head' && parentTag !== 'html' && !hasOpaqueHydrationChildren) {
|
|
812
819
|
const cursor = getHydrationCursor(fiber)
|
|
813
820
|
if (cursor) {
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
new Error(
|
|
820
|
-
`Hydration mismatch: server rendered ${leftover.length} extra ` +
|
|
821
|
-
`${leftover.length === 1 ? 'node' : 'nodes'} inside <${parentTag}> ` +
|
|
822
|
-
`that the client tree did not.`,
|
|
823
|
-
),
|
|
821
|
+
if (cursor.has()) {
|
|
822
|
+
const error = new Error(
|
|
823
|
+
process.env.NODE_ENV !== 'production'
|
|
824
|
+
? `Hydration mismatch: server rendered extra nodes inside <${parentTag}>.`
|
|
825
|
+
: 'Hydration mismatch.',
|
|
824
826
|
)
|
|
825
|
-
|
|
827
|
+
if (currentRoot.re) currentRoot.re(error)
|
|
828
|
+
abortHydration(error, fiber)
|
|
826
829
|
}
|
|
827
830
|
}
|
|
828
831
|
}
|
|
@@ -841,63 +844,28 @@ function renderHost(fiber: Fiber, domParent: Node, anchor: Node | null): void {
|
|
|
841
844
|
}
|
|
842
845
|
}
|
|
843
846
|
|
|
844
|
-
fiber.
|
|
845
|
-
//
|
|
847
|
+
fiber.mp = props
|
|
848
|
+
// dy cleared at rerender start; leaving true lets mid-render schedule persist
|
|
846
849
|
}
|
|
847
850
|
|
|
848
851
|
function renderFunction(fiber: Fiber, domParent: Node, anchor: Node | null): void {
|
|
849
852
|
const prevDispatcher = ReactSharedInternals.H
|
|
850
|
-
const prevFiber = ReactSharedInternals.
|
|
851
|
-
const prevHook = ReactSharedInternals.
|
|
852
|
-
const prevIndex = ReactSharedInternals.
|
|
853
|
+
const prevFiber = ReactSharedInternals.F
|
|
854
|
+
const prevHook = ReactSharedInternals.K
|
|
855
|
+
const prevIndex = ReactSharedInternals.I
|
|
853
856
|
|
|
854
857
|
ReactSharedInternals.H = makeDispatcher()
|
|
855
|
-
ReactSharedInternals.
|
|
856
|
-
ReactSharedInternals.
|
|
857
|
-
ReactSharedInternals.
|
|
858
|
+
ReactSharedInternals.F = fiber
|
|
859
|
+
ReactSharedInternals.K = null
|
|
860
|
+
ReactSharedInternals.I = 0
|
|
858
861
|
|
|
859
862
|
let rendered: ReactNode
|
|
860
863
|
let deferredForHydration = false
|
|
861
864
|
try {
|
|
862
|
-
rendered = (fiber.type as Function)(fiber.
|
|
865
|
+
rendered = (fiber.type as Function)(fiber.pp ?? {})
|
|
863
866
|
} catch (e: any) {
|
|
864
867
|
if (isThenable(e)) {
|
|
865
|
-
if (
|
|
866
|
-
// Suspension during initial hydration. Leave the existing DOM alone
|
|
867
|
-
// and preserve the in-scope hydration cursor on THIS fiber so it
|
|
868
|
-
// survives the synchronous endHydration() that fires when the initial
|
|
869
|
-
// hydrateRoot() call returns. When the promise settles, the fiber
|
|
870
|
-
// re-renders (see rerenderFiber) with hydration re-activated and its
|
|
871
|
-
// descendants adopt DOM instead of creating new nodes.
|
|
872
|
-
const hostParent = findHydrationHost(fiber)
|
|
873
|
-
const inheritedCursor = getHydrationCursor(hostParent)
|
|
874
|
-
if (inheritedCursor) {
|
|
875
|
-
setHydrationCursor(fiber, inheritedCursor)
|
|
876
|
-
}
|
|
877
|
-
fiber.memoizedState = {
|
|
878
|
-
...(fiber.memoizedState ?? {}),
|
|
879
|
-
_pendingHydration: true,
|
|
880
|
-
}
|
|
881
|
-
// Mirror renderLazy's guard: mark the nearest Suspense ancestor as
|
|
882
|
-
// awaiting hydration-resume, so any re-render of that Suspense (e.g.
|
|
883
|
-
// rehydrateBoundary fired by $RC, or an unrelated state update from a
|
|
884
|
-
// sibling) doesn't re-enter `tryChildren`, re-throw, and flip Suspense
|
|
885
|
-
// into its suspended+pending path — which would unmount our deferred
|
|
886
|
-
// subtree and remount a fallback on top of the SSR content. By
|
|
887
|
-
// pinning the Suspense to a "hydration-suspended" no-op until our
|
|
888
|
-
// resume fires, the deferred re-render owns the adoption pass.
|
|
889
|
-
let sus: Fiber | null = fiber.parent
|
|
890
|
-
while (sus && sus.tag !== FiberTag.Suspense) sus = sus.parent
|
|
891
|
-
if (sus && sus.memoizedState) {
|
|
892
|
-
;(sus.memoizedState as any)._awaitingLazyHydration = true
|
|
893
|
-
}
|
|
894
|
-
const clearAwait = () => {
|
|
895
|
-
if (sus && sus.memoizedState) {
|
|
896
|
-
;(sus.memoizedState as any)._awaitingLazyHydration = false
|
|
897
|
-
}
|
|
898
|
-
scheduleUpdate(fiber)
|
|
899
|
-
}
|
|
900
|
-
e.then(clearAwait, clearAwait)
|
|
868
|
+
if (deferHydration(fiber, e)) {
|
|
901
869
|
deferredForHydration = true
|
|
902
870
|
} else {
|
|
903
871
|
CAPABILITIES.handleSuspended(fiber, e)
|
|
@@ -909,16 +877,16 @@ function renderFunction(fiber: Fiber, domParent: Node, anchor: Node | null): voi
|
|
|
909
877
|
}
|
|
910
878
|
} finally {
|
|
911
879
|
ReactSharedInternals.H = prevDispatcher
|
|
912
|
-
ReactSharedInternals.
|
|
913
|
-
ReactSharedInternals.
|
|
914
|
-
ReactSharedInternals.
|
|
880
|
+
ReactSharedInternals.F = prevFiber
|
|
881
|
+
ReactSharedInternals.K = prevHook
|
|
882
|
+
ReactSharedInternals.I = prevIndex
|
|
915
883
|
}
|
|
916
884
|
|
|
917
885
|
if (deferredForHydration) return
|
|
918
886
|
|
|
919
887
|
reconcileChildren(fiber, childrenToArray(rendered), domParent, anchor)
|
|
920
|
-
fiber.
|
|
921
|
-
//
|
|
888
|
+
fiber.mp = fiber.pp
|
|
889
|
+
// dy cleared at rerender start; leaving true lets mid-render schedule persist
|
|
922
890
|
}
|
|
923
891
|
|
|
924
892
|
function hasAncestorHydrationCursor(_fiber: Fiber): boolean {
|
|
@@ -927,11 +895,32 @@ function hasAncestorHydrationCursor(_fiber: Fiber): boolean {
|
|
|
927
895
|
return false
|
|
928
896
|
}
|
|
929
897
|
|
|
898
|
+
export function deferHydration(fiber: Fiber, thenable: Promise<any>): boolean {
|
|
899
|
+
if (!currentRoot?.h) return false
|
|
900
|
+
const hostParent = findHydrationHost(fiber)
|
|
901
|
+
const inheritedCursor = getHydrationCursor(hostParent)
|
|
902
|
+
if (inheritedCursor) setHydrationCursor(fiber, inheritedCursor)
|
|
903
|
+
;((fiber.ms ??= {}) as any).p = true
|
|
904
|
+
let sus: Fiber | null = fiber.parent
|
|
905
|
+
while (sus && sus.tag !== FiberTag.Suspense) sus = sus.parent
|
|
906
|
+
if (sus && sus.ms) {
|
|
907
|
+
;(sus.ms as any).a = true
|
|
908
|
+
}
|
|
909
|
+
const clearAwait = () => {
|
|
910
|
+
if (sus && sus.ms) {
|
|
911
|
+
;(sus.ms as any).a = false
|
|
912
|
+
}
|
|
913
|
+
scheduleUpdate(fiber)
|
|
914
|
+
}
|
|
915
|
+
thenable.then(clearAwait, clearAwait)
|
|
916
|
+
return true
|
|
917
|
+
}
|
|
918
|
+
|
|
930
919
|
function renderFragment(fiber: Fiber, domParent: Node, anchor: Node | null): void {
|
|
931
|
-
const props = fiber.
|
|
920
|
+
const props = fiber.pp ?? {}
|
|
932
921
|
reconcileChildren(fiber, childrenToArray(props.children), domParent, anchor)
|
|
933
|
-
fiber.
|
|
934
|
-
//
|
|
922
|
+
fiber.mp = props
|
|
923
|
+
// dy cleared at rerender start; leaving true lets mid-render schedule persist
|
|
935
924
|
}
|
|
936
925
|
|
|
937
926
|
// ---------------------------------------------------------------------------
|
|
@@ -981,12 +970,15 @@ export function handleSuspended(fiber: Fiber, thenable: Promise<any>): void {
|
|
|
981
970
|
}
|
|
982
971
|
|
|
983
972
|
export function handleErrorInRender(fiber: Fiber, err: any): void {
|
|
973
|
+
if (currentRoot?.h) {
|
|
974
|
+
abortHydration(err, fiber)
|
|
975
|
+
}
|
|
984
976
|
// Bubble to nearest class boundary with getDerivedStateFromError / componentDidCatch
|
|
985
977
|
let f: Fiber | null = fiber.parent
|
|
986
978
|
while (f) {
|
|
987
979
|
if (f.tag === FiberTag.Class) {
|
|
988
980
|
const Ctor = f.type as any
|
|
989
|
-
const instance = f.
|
|
981
|
+
const instance = f.sn
|
|
990
982
|
if (Ctor.getDerivedStateFromError) {
|
|
991
983
|
const update = Ctor.getDerivedStateFromError(err)
|
|
992
984
|
instance.state = { ...instance.state, ...update }
|
|
@@ -1002,12 +994,12 @@ export function handleErrorInRender(fiber: Fiber, err: any): void {
|
|
|
1002
994
|
f = f.parent
|
|
1003
995
|
}
|
|
1004
996
|
// No boundary — report to root
|
|
1005
|
-
if (currentRoot?.
|
|
997
|
+
if (currentRoot?.ue) currentRoot.ue(err)
|
|
1006
998
|
else throw err
|
|
1007
999
|
}
|
|
1008
1000
|
|
|
1009
1001
|
export function isThenable(x: any): x is Promise<any> {
|
|
1010
|
-
return x != null && typeof x.then
|
|
1002
|
+
return x != null && typeof x.then == 'function'
|
|
1011
1003
|
}
|
|
1012
1004
|
|
|
1013
1005
|
// ---------------------------------------------------------------------------
|
|
@@ -1015,7 +1007,7 @@ export function isThenable(x: any): x is Promise<any> {
|
|
|
1015
1007
|
// ---------------------------------------------------------------------------
|
|
1016
1008
|
|
|
1017
1009
|
export function unmountFiber(fiber: Fiber, domParent: Node): void {
|
|
1018
|
-
fiber.
|
|
1010
|
+
fiber.um = true
|
|
1019
1011
|
// Recurse first
|
|
1020
1012
|
let c = fiber.child
|
|
1021
1013
|
while (c) {
|
|
@@ -1025,27 +1017,27 @@ export function unmountFiber(fiber: Fiber, domParent: Node): void {
|
|
|
1025
1017
|
}
|
|
1026
1018
|
fiber.child = null
|
|
1027
1019
|
|
|
1028
|
-
// Run
|
|
1029
|
-
if (fiber.
|
|
1030
|
-
for (const cleanup of fiber.
|
|
1020
|
+
// Run cu (fx + layout fx)
|
|
1021
|
+
if (fiber.cu) {
|
|
1022
|
+
for (const cleanup of fiber.cu) {
|
|
1031
1023
|
try {
|
|
1032
1024
|
cleanup()
|
|
1033
1025
|
} catch (e) {
|
|
1034
|
-
if (currentRoot?.
|
|
1026
|
+
if (currentRoot?.re) currentRoot.re(e)
|
|
1035
1027
|
}
|
|
1036
1028
|
}
|
|
1037
|
-
fiber.
|
|
1029
|
+
fiber.cu = null
|
|
1038
1030
|
}
|
|
1039
1031
|
|
|
1040
|
-
if (fiber.tag === FiberTag.Class && fiber.
|
|
1032
|
+
if (fiber.tag === FiberTag.Class && fiber.sn?.componentWillUnmount) {
|
|
1041
1033
|
try {
|
|
1042
|
-
fiber.
|
|
1034
|
+
fiber.sn.componentWillUnmount()
|
|
1043
1035
|
} catch (e) {
|
|
1044
|
-
if (currentRoot?.
|
|
1036
|
+
if (currentRoot?.re) currentRoot.re(e)
|
|
1045
1037
|
}
|
|
1046
|
-
fiber.
|
|
1047
|
-
fiber.
|
|
1048
|
-
fiber.
|
|
1038
|
+
fiber.sn._fiber = null
|
|
1039
|
+
fiber.sn._enqueueUpdate = null
|
|
1040
|
+
fiber.sn._forceUpdate = null
|
|
1049
1041
|
}
|
|
1050
1042
|
|
|
1051
1043
|
// Detach ref
|
|
@@ -1105,7 +1097,7 @@ function getHostParent(fiber: Fiber): Node {
|
|
|
1105
1097
|
while (p) {
|
|
1106
1098
|
if (p.tag === FiberTag.Host) return p.dom!
|
|
1107
1099
|
if (p.tag === FiberTag.Root)
|
|
1108
|
-
return (p.
|
|
1100
|
+
return (p.sn as Node) || (p.dom as Node) || (p.root?.c as Node)
|
|
1109
1101
|
if (p.tag === FiberTag.Portal) {
|
|
1110
1102
|
// Portal renders its children into the `container` prop, not into any
|
|
1111
1103
|
// DOM element the portal fiber "owns". Read the container from the
|
|
@@ -1113,12 +1105,15 @@ function getHostParent(fiber: Fiber): Node {
|
|
|
1113
1105
|
// (e.g. a Floating-UI-positioned popper in a Radix Portal) finds its
|
|
1114
1106
|
// host parent — otherwise getHostParent returns undefined and the
|
|
1115
1107
|
// next renderHost crashes reading `.namespaceURI` on undefined.
|
|
1116
|
-
const props = (p.
|
|
1117
|
-
return (props?.container as Node) || (p.
|
|
1108
|
+
const props = (p.pp ?? p.mp) as { container?: Element } | null
|
|
1109
|
+
return (props?.container as Node) || (p.sn as Node) || (p.dom as Node) || (p.root?.c as Node)
|
|
1118
1110
|
}
|
|
1119
1111
|
p = p.parent
|
|
1120
1112
|
}
|
|
1121
|
-
|
|
1113
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
1114
|
+
throw new Error('No host parent found.')
|
|
1115
|
+
}
|
|
1116
|
+
throw new Error()
|
|
1122
1117
|
}
|
|
1123
1118
|
|
|
1124
1119
|
function getAnchor(fiber: Fiber): Node | null {
|
|
@@ -1174,17 +1169,17 @@ function defaultReadContext(_fiber: Fiber, ctx: any): any {
|
|
|
1174
1169
|
// ---------------------------------------------------------------------------
|
|
1175
1170
|
|
|
1176
1171
|
function attachRef(fiber: Fiber, value: any): void {
|
|
1177
|
-
const ref = fiber.ref ?? (fiber.
|
|
1172
|
+
const ref = fiber.ref ?? (fiber.pp?.ref ?? null)
|
|
1178
1173
|
if (!ref) return
|
|
1179
|
-
if (typeof ref
|
|
1174
|
+
if (typeof ref == 'function') {
|
|
1180
1175
|
// Match React's commit-phase semantics: callback refs run after render
|
|
1181
1176
|
// (during the layout/commit phase), not during render. Calling them
|
|
1182
1177
|
// synchronously here breaks libraries that assert no event handlers run
|
|
1183
1178
|
// during render (e.g. base-ui's useStableCallback trampoline).
|
|
1184
1179
|
scheduleLifecycle(fiber, () => {
|
|
1185
1180
|
const cleanup = ref(value)
|
|
1186
|
-
fiber.
|
|
1187
|
-
fiber.
|
|
1181
|
+
fiber.cu ||= []
|
|
1182
|
+
fiber.cu.push(typeof cleanup == 'function' ? cleanup : () => ref(null))
|
|
1188
1183
|
})
|
|
1189
1184
|
} else {
|
|
1190
1185
|
ref.current = value
|
|
@@ -1192,13 +1187,13 @@ function attachRef(fiber: Fiber, value: any): void {
|
|
|
1192
1187
|
}
|
|
1193
1188
|
|
|
1194
1189
|
function syncRefIfChanged(fiber: Fiber, value: any): void {
|
|
1195
|
-
const ref = fiber.ref ?? (fiber.
|
|
1190
|
+
const ref = fiber.ref ?? (fiber.pp?.ref ?? null)
|
|
1196
1191
|
if (!ref) return
|
|
1197
1192
|
if (typeof ref === 'object' && ref.current !== value) ref.current = value
|
|
1198
1193
|
}
|
|
1199
1194
|
|
|
1200
1195
|
function detachRef(ref: any): void {
|
|
1201
|
-
// Function refs are handled via fiber.
|
|
1196
|
+
// Function refs are handled via fiber.cu (queued in attachRef during
|
|
1202
1197
|
// the commit phase): the cleanup either invokes the user-returned cleanup
|
|
1203
1198
|
// fn or calls ref(null). Calling ref(null) here would double-fire it.
|
|
1204
1199
|
if (ref && typeof ref === 'object') {
|
|
@@ -1210,56 +1205,55 @@ function detachRef(ref: any): void {
|
|
|
1210
1205
|
// Effects
|
|
1211
1206
|
// ---------------------------------------------------------------------------
|
|
1212
1207
|
|
|
1213
|
-
const pendingEffects: Array<
|
|
1214
|
-
const pendingLayoutEffects: Array<
|
|
1215
|
-
const pendingLifecycles: Array<
|
|
1208
|
+
const pendingEffects: Array<[Fiber, Effect]> = []
|
|
1209
|
+
const pendingLayoutEffects: Array<[Fiber, Effect]> = []
|
|
1210
|
+
const pendingLifecycles: Array<() => void> = []
|
|
1216
1211
|
|
|
1217
1212
|
export function enqueueEffect(fiber: Fiber, effect: Effect): void {
|
|
1218
|
-
if (effect.
|
|
1219
|
-
pendingLayoutEffects.push(
|
|
1213
|
+
if (effect.t) {
|
|
1214
|
+
pendingLayoutEffects.push([fiber, effect])
|
|
1220
1215
|
} else {
|
|
1221
|
-
pendingEffects.push(
|
|
1216
|
+
pendingEffects.push([fiber, effect])
|
|
1222
1217
|
}
|
|
1223
1218
|
}
|
|
1224
1219
|
|
|
1225
|
-
export function scheduleLifecycle(
|
|
1226
|
-
pendingLifecycles.push(
|
|
1220
|
+
export function scheduleLifecycle(_fiber: Fiber, fn: () => void): void {
|
|
1221
|
+
pendingLifecycles.push(fn)
|
|
1227
1222
|
}
|
|
1228
1223
|
|
|
1229
1224
|
export function runEffects(root: FiberRoot): void {
|
|
1230
|
-
// Layout
|
|
1225
|
+
// Layout fx synchronously
|
|
1231
1226
|
while (pendingLayoutEffects.length) {
|
|
1232
|
-
const
|
|
1227
|
+
const [fiber, effect] = pendingLayoutEffects.shift()!
|
|
1233
1228
|
runEffect(fiber, effect, root)
|
|
1234
1229
|
}
|
|
1235
1230
|
// Then lifecycles
|
|
1236
1231
|
while (pendingLifecycles.length) {
|
|
1237
|
-
const
|
|
1232
|
+
const fn = pendingLifecycles.shift()!
|
|
1238
1233
|
try {
|
|
1239
1234
|
fn()
|
|
1240
1235
|
} catch (e) {
|
|
1241
|
-
if (root.
|
|
1236
|
+
if (root.ce) root.ce(e)
|
|
1242
1237
|
}
|
|
1243
1238
|
}
|
|
1244
|
-
// Passive
|
|
1239
|
+
// Passive fx on microtask
|
|
1245
1240
|
if (pendingEffects.length) {
|
|
1246
1241
|
const batch = pendingEffects.splice(0)
|
|
1247
1242
|
queueMicrotask(() => {
|
|
1248
|
-
for (const
|
|
1243
|
+
for (const [fiber, effect] of batch) runEffect(fiber, effect, root)
|
|
1249
1244
|
})
|
|
1250
1245
|
}
|
|
1251
1246
|
}
|
|
1252
1247
|
|
|
1253
1248
|
function runEffect(fiber: Fiber, effect: Effect, root: FiberRoot): void {
|
|
1254
1249
|
try {
|
|
1255
|
-
const cleanup = effect.
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
fiber.
|
|
1259
|
-
fiber.cleanups.push(effect.destroy)
|
|
1250
|
+
const cleanup = effect.c()
|
|
1251
|
+
if (typeof cleanup == 'function') {
|
|
1252
|
+
fiber.cu ||= []
|
|
1253
|
+
fiber.cu.push(cleanup)
|
|
1260
1254
|
}
|
|
1261
1255
|
} catch (e) {
|
|
1262
|
-
if (root.
|
|
1256
|
+
if (root.ce) root.ce(e)
|
|
1263
1257
|
}
|
|
1264
1258
|
}
|
|
1265
1259
|
|