@tanstack/redact 0.0.14 → 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/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
|
@@ -1,78 +1,62 @@
|
|
|
1
1
|
// packages/redact/src/dom/features/hydration/full.ts
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import {
|
|
3
|
+
FiberTag,
|
|
4
|
+
REACT_ELEMENT_TYPE
|
|
5
|
+
} from "../../../core";
|
|
6
|
+
import { createHostNode, setProp } from "../../dom";
|
|
5
7
|
import { drainReplayQueue } from "../../event-replay";
|
|
6
|
-
|
|
8
|
+
import { discardPendingWork, findRoot, flushSyncWork, renderRoot } from "../../reconcile";
|
|
9
|
+
import { attachRootFiber, createFiberRoot } from "../../root-internal";
|
|
7
10
|
function installHydrationScrollGuard() {
|
|
8
11
|
if (typeof window === "undefined") return;
|
|
9
12
|
const w = window;
|
|
10
|
-
if (w.
|
|
11
|
-
|
|
12
|
-
const guardStartedAt =
|
|
13
|
+
if (w._r) return;
|
|
14
|
+
const time = Date.now;
|
|
15
|
+
const guardStartedAt = w._r = time();
|
|
13
16
|
let lastUserScrollAt = 0;
|
|
14
|
-
let programmatic =
|
|
15
|
-
w.
|
|
16
|
-
window.addEventListener(
|
|
17
|
+
let programmatic = false;
|
|
18
|
+
w.addEventListener(
|
|
17
19
|
"scroll",
|
|
18
20
|
() => {
|
|
19
|
-
if (programmatic
|
|
20
|
-
lastUserScrollAt =
|
|
21
|
-
w.__redactScrollLog.push({ t: Math.round(lastUserScrollAt), ev: "user-scroll", y: window.scrollY });
|
|
21
|
+
if (!programmatic) {
|
|
22
|
+
lastUserScrollAt = time();
|
|
22
23
|
}
|
|
23
24
|
},
|
|
24
25
|
{ capture: true, passive: true }
|
|
25
26
|
);
|
|
26
|
-
const origScrollTo =
|
|
27
|
-
|
|
28
|
-
const now =
|
|
29
|
-
|
|
30
|
-
const userScrolledRecently = lastUserScrollAt > 0 && now - lastUserScrollAt < 1500;
|
|
31
|
-
if (inGuardWindow && userScrolledRecently) {
|
|
32
|
-
w.__redactScrollLog.push({
|
|
33
|
-
t: Math.round(now),
|
|
34
|
-
ev: "suppressed",
|
|
35
|
-
args: JSON.stringify(args).slice(0, 80),
|
|
36
|
-
tSinceHydrate: Math.round(now - guardStartedAt),
|
|
37
|
-
tSinceUserScroll: Math.round(now - lastUserScrollAt)
|
|
38
|
-
});
|
|
27
|
+
const origScrollTo = w.scrollTo;
|
|
28
|
+
w.scrollTo = function(...args) {
|
|
29
|
+
const now = time();
|
|
30
|
+
if (now - guardStartedAt < 3e3 && now - lastUserScrollAt < 1500) {
|
|
39
31
|
return;
|
|
40
32
|
}
|
|
41
|
-
|
|
42
|
-
t: Math.round(now),
|
|
43
|
-
ev: "allowed",
|
|
44
|
-
args: JSON.stringify(args).slice(0, 80),
|
|
45
|
-
tSinceHydrate: Math.round(now - guardStartedAt),
|
|
46
|
-
inGuard: inGuardWindow,
|
|
47
|
-
userScrolled: userScrolledRecently
|
|
48
|
-
});
|
|
49
|
-
programmatic++;
|
|
33
|
+
programmatic = true;
|
|
50
34
|
try {
|
|
51
|
-
return origScrollTo.apply(
|
|
35
|
+
return origScrollTo.apply(w, args);
|
|
52
36
|
} finally {
|
|
53
37
|
queueMicrotask(() => {
|
|
54
|
-
programmatic =
|
|
38
|
+
programmatic = false;
|
|
55
39
|
});
|
|
56
40
|
}
|
|
57
41
|
};
|
|
58
42
|
}
|
|
59
43
|
var HydrationCursor = class {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
44
|
+
n;
|
|
45
|
+
p;
|
|
46
|
+
e;
|
|
63
47
|
constructor(parent, start = null, endBefore = null) {
|
|
64
|
-
this.
|
|
65
|
-
this.
|
|
66
|
-
this.
|
|
48
|
+
this.p = parent;
|
|
49
|
+
this.n = start ?? parent.firstChild;
|
|
50
|
+
this.e = endBefore;
|
|
67
51
|
}
|
|
68
|
-
|
|
69
|
-
while (this.
|
|
70
|
-
const n = this.
|
|
52
|
+
take() {
|
|
53
|
+
while (this.n && this.n !== this.e) {
|
|
54
|
+
const n = this.n;
|
|
71
55
|
if (n.nodeType !== 1 && n.nodeType !== 3) {
|
|
72
|
-
this.
|
|
56
|
+
this.n = n.nextSibling;
|
|
73
57
|
continue;
|
|
74
58
|
}
|
|
75
|
-
this.
|
|
59
|
+
this.n = n.nextSibling;
|
|
76
60
|
return n;
|
|
77
61
|
}
|
|
78
62
|
return null;
|
|
@@ -84,10 +68,10 @@ var HydrationCursor = class {
|
|
|
84
68
|
* name/property for meta, src for script). Non-matching nodes stay in
|
|
85
69
|
* place so the SSR'd stylesheet/script order is preserved.
|
|
86
70
|
*/
|
|
87
|
-
|
|
71
|
+
head(tag, props) {
|
|
88
72
|
const target = tag.toLowerCase();
|
|
89
|
-
const keyAttrs = HEAD_KEY_ATTRS[target]
|
|
90
|
-
let scan = this.
|
|
73
|
+
const keyAttrs = HEAD_KEY_ATTRS[target];
|
|
74
|
+
let scan = this.p.firstChild;
|
|
91
75
|
while (scan) {
|
|
92
76
|
if (scan.nodeType === 1 && scan.tagName.toLowerCase() === target && headAttrsMatch(scan, props, keyAttrs)) {
|
|
93
77
|
CLAIMED.add(scan);
|
|
@@ -97,58 +81,130 @@ var HydrationCursor = class {
|
|
|
97
81
|
}
|
|
98
82
|
return null;
|
|
99
83
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
out.push(n);
|
|
84
|
+
has() {
|
|
85
|
+
let n = this.n;
|
|
86
|
+
while (n && n !== this.e) {
|
|
87
|
+
if (n.nodeType === 1 || n.nodeType === 3) return true;
|
|
105
88
|
n = n.nextSibling;
|
|
106
89
|
}
|
|
107
|
-
return
|
|
90
|
+
return false;
|
|
108
91
|
}
|
|
109
92
|
};
|
|
110
93
|
var hydrationCursors = /* @__PURE__ */ new WeakMap();
|
|
94
|
+
var PROD_HYDRATION_ERROR = "Hydration mismatch.";
|
|
95
|
+
function isHydrationBailout(error) {
|
|
96
|
+
return !!error && error.f !== void 0;
|
|
97
|
+
}
|
|
98
|
+
function abortHydration(cause, fiber = null) {
|
|
99
|
+
const error = cause instanceof Error ? cause : new Error(PROD_HYDRATION_ERROR);
|
|
100
|
+
error.f = fiber;
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
function hydrateRootImpl(container, initialChildren, options) {
|
|
104
|
+
const target = container;
|
|
105
|
+
const isDocument = container.nodeType === 9;
|
|
106
|
+
const body = isDocument ? target.body : null;
|
|
107
|
+
const root = createFiberRoot(target, options);
|
|
108
|
+
installHydrationScrollGuard();
|
|
109
|
+
const normalizedInitialChildren = isDocument ? normalizeDocumentChildren(initialChildren) : initialChildren;
|
|
110
|
+
let documentBodyFallback = false;
|
|
111
|
+
let hydrationError = null;
|
|
112
|
+
beginHydration(root);
|
|
113
|
+
try {
|
|
114
|
+
flushSyncWork(() => {
|
|
115
|
+
renderRoot(root, normalizedInitialChildren);
|
|
116
|
+
});
|
|
117
|
+
} catch (e) {
|
|
118
|
+
hydrationError = e;
|
|
119
|
+
}
|
|
120
|
+
endHydration(root);
|
|
121
|
+
if (hydrationError) {
|
|
122
|
+
if (!isHydrationBailout(hydrationError)) {
|
|
123
|
+
throw hydrationError;
|
|
124
|
+
}
|
|
125
|
+
let recoveryContainer = target;
|
|
126
|
+
let recoveryChildren = normalizedInitialChildren;
|
|
127
|
+
const hostRecovery = getRecoverableHostChildren(hydrationError);
|
|
128
|
+
if (hostRecovery) {
|
|
129
|
+
recoveryContainer = hostRecovery[0];
|
|
130
|
+
recoveryChildren = hostRecovery[1];
|
|
131
|
+
} else if (body) {
|
|
132
|
+
const bodyChildren = getRecoverableDocumentBodyChildren(hydrationError);
|
|
133
|
+
if (bodyChildren != null) {
|
|
134
|
+
documentBodyFallback = true;
|
|
135
|
+
recoveryContainer = body;
|
|
136
|
+
recoveryChildren = bodyChildren;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
resetAfterHydrationFailure(root, recoveryContainer);
|
|
140
|
+
try {
|
|
141
|
+
flushSyncWork(() => {
|
|
142
|
+
renderRoot(root, recoveryChildren);
|
|
143
|
+
});
|
|
144
|
+
} catch (clientError) {
|
|
145
|
+
resetAfterHydrationFailure(root, recoveryContainer);
|
|
146
|
+
throw clientError;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
drainReplayQueue();
|
|
150
|
+
return {
|
|
151
|
+
render(children) {
|
|
152
|
+
flushSyncWork(() => {
|
|
153
|
+
const normalized = isDocument ? normalizeDocumentChildren(children) : children;
|
|
154
|
+
renderRoot(
|
|
155
|
+
root,
|
|
156
|
+
documentBodyFallback ? getStaticDocumentBodyChildren(normalized) ?? normalized : normalized
|
|
157
|
+
);
|
|
158
|
+
});
|
|
159
|
+
},
|
|
160
|
+
unmount() {
|
|
161
|
+
flushSyncWork(() => {
|
|
162
|
+
renderRoot(root, null);
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
}
|
|
111
167
|
var HEAD_KEY_ATTRS = {
|
|
112
168
|
link: ["rel", "href", "sizes", "type"],
|
|
113
169
|
meta: ["name", "property", "charset", "http-equiv"],
|
|
114
|
-
script: ["src", "type"]
|
|
115
|
-
style: [],
|
|
116
|
-
title: []
|
|
170
|
+
script: ["src", "type"]
|
|
117
171
|
};
|
|
118
172
|
var DOCUMENT_HEAD_TAGS = /* @__PURE__ */ new Set(["base", "link", "meta", "script", "style", "title"]);
|
|
119
173
|
var CLAIMED = /* @__PURE__ */ new WeakSet();
|
|
120
174
|
function headAttrsMatch(el, props, keys) {
|
|
121
175
|
if (CLAIMED.has(el)) return false;
|
|
122
|
-
if (keys
|
|
176
|
+
if (!keys) return true;
|
|
177
|
+
let matched = false;
|
|
123
178
|
for (const k of keys) {
|
|
124
179
|
const propVal = props[k] ?? (k === "http-equiv" ? props.httpEquiv : void 0);
|
|
125
180
|
const elVal = el.getAttribute(k);
|
|
126
181
|
if (propVal == null && elVal == null) continue;
|
|
182
|
+
matched = true;
|
|
127
183
|
if (propVal == null || elVal == null) continue;
|
|
128
184
|
if (String(propVal) !== elVal) return false;
|
|
129
185
|
}
|
|
130
|
-
return
|
|
186
|
+
return matched;
|
|
131
187
|
}
|
|
132
188
|
function beginHydration(root) {
|
|
133
|
-
root.
|
|
134
|
-
hydrationCursors.set(root.
|
|
189
|
+
root.h = true;
|
|
190
|
+
hydrationCursors.set(root.r, new HydrationCursor(root.c));
|
|
135
191
|
}
|
|
136
192
|
function endHydration(root) {
|
|
137
|
-
root.
|
|
138
|
-
hydrationCursors.delete(root.
|
|
193
|
+
root.h = false;
|
|
194
|
+
hydrationCursors.delete(root.r);
|
|
139
195
|
}
|
|
140
196
|
function tryConsumeBoundary(parent) {
|
|
141
197
|
const cursor = hydrationCursors.get(findHostParent(parent));
|
|
142
198
|
if (!cursor) return null;
|
|
143
|
-
const peek = cursor.
|
|
199
|
+
const peek = cursor.n;
|
|
144
200
|
if (!peek || peek.nodeType !== 8) return null;
|
|
145
201
|
const data = peek.data;
|
|
146
202
|
const m = /^(\$\??)(\d+)$/.exec(data);
|
|
147
203
|
if (!m) return null;
|
|
148
|
-
const kind = m[1] === "$?" ?
|
|
204
|
+
const kind = m[1] === "$?" ? 1 : 0;
|
|
149
205
|
const id = Number(m[2]);
|
|
150
206
|
const startMark = peek;
|
|
151
|
-
cursor.
|
|
207
|
+
cursor.n = startMark.nextSibling;
|
|
152
208
|
let endMark = null;
|
|
153
209
|
let scan = startMark.nextSibling;
|
|
154
210
|
while (scan) {
|
|
@@ -159,12 +215,12 @@ function tryConsumeBoundary(parent) {
|
|
|
159
215
|
scan = scan.nextSibling;
|
|
160
216
|
}
|
|
161
217
|
if (!endMark) return null;
|
|
162
|
-
return
|
|
218
|
+
return [kind, id, startMark, endMark];
|
|
163
219
|
}
|
|
164
220
|
function advanceCursorPast(parent, node) {
|
|
165
221
|
const cursor = hydrationCursors.get(findHostParent(parent));
|
|
166
222
|
if (!cursor) return;
|
|
167
|
-
cursor.
|
|
223
|
+
cursor.n = node.nextSibling;
|
|
168
224
|
}
|
|
169
225
|
function getHydrationCursor(hostFiber) {
|
|
170
226
|
return hydrationCursors.get(hostFiber);
|
|
@@ -180,20 +236,20 @@ function adoptHostDom(fiber, parent) {
|
|
|
180
236
|
const cursor = hydrationCursors.get(hostParent);
|
|
181
237
|
if (!cursor) return false;
|
|
182
238
|
const tag = fiber.type.toLowerCase();
|
|
183
|
-
const documentHeadParent =
|
|
184
|
-
const parentEl = cursor.
|
|
239
|
+
const documentHeadParent = cursor.p.nodeType === 9 && DOCUMENT_HEAD_TAGS.has(tag) ? cursor.p.head : null;
|
|
240
|
+
const parentEl = cursor.p;
|
|
185
241
|
const parentTag = parentEl.nodeType === 1 ? parentEl.tagName.toLowerCase() : "";
|
|
186
242
|
const isHeadish = parentTag === "head" || parentTag === "html" || !!documentHeadParent;
|
|
187
243
|
let candidate;
|
|
188
244
|
if (documentHeadParent) {
|
|
189
|
-
candidate = new HydrationCursor(documentHeadParent).
|
|
245
|
+
candidate = new HydrationCursor(documentHeadParent).head(
|
|
190
246
|
tag,
|
|
191
|
-
fiber.
|
|
247
|
+
fiber.pp ?? {}
|
|
192
248
|
);
|
|
193
249
|
} else if (isHeadish) {
|
|
194
|
-
candidate = cursor.
|
|
250
|
+
candidate = cursor.head(tag, fiber.pp ?? {});
|
|
195
251
|
} else {
|
|
196
|
-
candidate = cursor.
|
|
252
|
+
candidate = cursor.take();
|
|
197
253
|
}
|
|
198
254
|
if (!candidate) {
|
|
199
255
|
if (!isHeadish) onMismatch(fiber, null);
|
|
@@ -204,30 +260,32 @@ function adoptHostDom(fiber, parent) {
|
|
|
204
260
|
return false;
|
|
205
261
|
}
|
|
206
262
|
fiber.dom = candidate;
|
|
207
|
-
const props = fiber.
|
|
263
|
+
const props = fiber.pp ?? {};
|
|
208
264
|
const isSvg = tag === "svg" || candidate.namespaceURI === "http://www.w3.org/2000/svg" && tag !== "foreignobject";
|
|
265
|
+
validateHydrationProps(fiber, candidate, props, tag, isSvg);
|
|
209
266
|
for (const k in props) {
|
|
210
267
|
if (k === "children") continue;
|
|
211
|
-
if (k[0] === "o" && k[1] === "n" && typeof props[k]
|
|
268
|
+
if (k[0] === "o" && k[1] === "n" && typeof props[k] == "function") {
|
|
212
269
|
setProp(candidate, k, props[k], void 0, isSvg);
|
|
213
270
|
}
|
|
214
271
|
}
|
|
215
272
|
hydrationCursors.set(fiber, new HydrationCursor(candidate));
|
|
216
273
|
return true;
|
|
217
274
|
}
|
|
218
|
-
function getDocumentHeadParent(parent, tag) {
|
|
219
|
-
if (parent.nodeType !== 9 || !DOCUMENT_HEAD_TAGS.has(tag)) return null;
|
|
220
|
-
return parent.head;
|
|
221
|
-
}
|
|
222
275
|
function adoptTextDom(fiber, parent, text) {
|
|
223
276
|
const cursor = hydrationCursors.get(findHostParent(parent));
|
|
224
277
|
if (!cursor) return false;
|
|
225
|
-
const candidate = cursor.
|
|
226
|
-
if (!candidate)
|
|
278
|
+
const candidate = cursor.take();
|
|
279
|
+
if (!candidate) onMismatch(fiber, null);
|
|
227
280
|
if (candidate.nodeType === 3) {
|
|
228
281
|
if (candidate.data !== text) {
|
|
229
|
-
|
|
230
|
-
|
|
282
|
+
if (true) {
|
|
283
|
+
failHydration(
|
|
284
|
+
fiber,
|
|
285
|
+
new Error(`Hydration text mismatch: expected "${text}" but found "${candidate.data}".`)
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
failHydration(fiber);
|
|
231
289
|
}
|
|
232
290
|
fiber.dom = candidate;
|
|
233
291
|
return true;
|
|
@@ -244,21 +302,281 @@ function findHostParent(fiber) {
|
|
|
244
302
|
}
|
|
245
303
|
f = f.parent;
|
|
246
304
|
}
|
|
247
|
-
|
|
305
|
+
if (true) {
|
|
306
|
+
throw new Error("No host parent found");
|
|
307
|
+
}
|
|
308
|
+
throw new Error();
|
|
248
309
|
}
|
|
249
310
|
function onMismatch(fiber, actualNode) {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
311
|
+
if (true) {
|
|
312
|
+
failHydration(
|
|
313
|
+
fiber,
|
|
253
314
|
new Error(
|
|
254
315
|
`Hydration mismatch: expected <${fiber.type ?? "text"}> but found ${actualNode ? actualNode.nodeType === 1 ? actualNode.tagName : "text" : "nothing"}.`
|
|
255
316
|
)
|
|
256
317
|
);
|
|
257
318
|
}
|
|
258
|
-
|
|
319
|
+
failHydration(fiber);
|
|
320
|
+
}
|
|
321
|
+
function failHydration(fiber, error = new Error(PROD_HYDRATION_ERROR)) {
|
|
322
|
+
const root = findRoot(fiber);
|
|
323
|
+
if (root?.re) {
|
|
324
|
+
root.re(error);
|
|
325
|
+
}
|
|
326
|
+
abortHydration(error, findHostRecoveryParent(fiber) ?? fiber);
|
|
327
|
+
}
|
|
328
|
+
function findHostRecoveryParent(fiber) {
|
|
329
|
+
if (fiber.tag === FiberTag.Text) {
|
|
330
|
+
let directHost = fiber.parent;
|
|
331
|
+
while (directHost && (directHost.tag !== FiberTag.Host || !directHost.dom)) {
|
|
332
|
+
directHost = directHost.parent;
|
|
333
|
+
}
|
|
334
|
+
if (!directHost) return null;
|
|
335
|
+
let hasEvent;
|
|
336
|
+
const props = directHost.pp ?? directHost.mp;
|
|
337
|
+
if (props) {
|
|
338
|
+
for (const k in props) {
|
|
339
|
+
if (k[0] === "o" && k[1] === "n" && typeof props[k] == "function") {
|
|
340
|
+
hasEvent = true;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return findNearestSafeHostAboveComposite(
|
|
345
|
+
hasEvent ? directHost.parent : directHost.parent?.tag === FiberTag.Host ? directHost.parent : directHost
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
return findNearestSafeHostAboveComposite(fiber.parent);
|
|
349
|
+
}
|
|
350
|
+
function findNearestSafeHostAboveComposite(fiber) {
|
|
351
|
+
let f = fiber;
|
|
352
|
+
while (f) {
|
|
353
|
+
if (f.tag === FiberTag.Host && f.dom) {
|
|
354
|
+
return isSafeHostRecoveryElement(f) ? f : null;
|
|
355
|
+
}
|
|
356
|
+
f = f.parent;
|
|
357
|
+
}
|
|
358
|
+
return null;
|
|
359
|
+
}
|
|
360
|
+
function isSafeHostRecoveryElement(fiber) {
|
|
361
|
+
const tag = fiber.type.toLowerCase();
|
|
362
|
+
return tag !== "html" && tag !== "head" && tag !== "body";
|
|
363
|
+
}
|
|
364
|
+
function resetAfterHydrationFailure(root, container) {
|
|
365
|
+
discardPendingWork(root);
|
|
366
|
+
clearHydrationContainer(container);
|
|
367
|
+
attachRootFiber(root, container);
|
|
368
|
+
root.h = false;
|
|
369
|
+
}
|
|
370
|
+
function clearHydrationContainer(container) {
|
|
371
|
+
if (container.nodeType === 9) {
|
|
372
|
+
let node = container.firstChild;
|
|
373
|
+
while (node) {
|
|
374
|
+
const next = node.nextSibling;
|
|
375
|
+
if (node.nodeType !== 10) {
|
|
376
|
+
container.removeChild(node);
|
|
377
|
+
}
|
|
378
|
+
node = next;
|
|
379
|
+
}
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
;
|
|
383
|
+
container.textContent = "";
|
|
384
|
+
}
|
|
385
|
+
function getRecoverableHostChildren(error) {
|
|
386
|
+
const host = error.f;
|
|
387
|
+
if (!host || host.tag !== FiberTag.Host || !host.dom || !isSafeHostRecoveryElement(host) || !findNearestSafeHostAboveComposite(host.parent)) {
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
return [host.dom, (host.pp ?? host.mp)?.children ?? null];
|
|
391
|
+
}
|
|
392
|
+
function getRecoverableDocumentBodyChildren(error) {
|
|
393
|
+
const bodyFiber = findBodyAncestor(error.f);
|
|
394
|
+
if (!bodyFiber) return null;
|
|
395
|
+
return (bodyFiber.pp ?? bodyFiber.mp)?.children ?? null;
|
|
396
|
+
}
|
|
397
|
+
function findBodyAncestor(fiber) {
|
|
398
|
+
let f = fiber;
|
|
399
|
+
while (f) {
|
|
400
|
+
if (f.tag === FiberTag.Host && f.type === "body") {
|
|
401
|
+
return f === fiber ? null : f;
|
|
402
|
+
}
|
|
403
|
+
f = f.parent;
|
|
404
|
+
}
|
|
405
|
+
return null;
|
|
406
|
+
}
|
|
407
|
+
function getStaticDocumentBodyChildren(children) {
|
|
408
|
+
const list = toChildArray(children);
|
|
409
|
+
const html = list.find((child) => isHostElement(child, "html"));
|
|
410
|
+
if (!html) return null;
|
|
411
|
+
const htmlChildren = toChildArray(html.props?.children);
|
|
412
|
+
const body = htmlChildren.find((child) => isHostElement(child, "body"));
|
|
413
|
+
return body ? body.props?.children ?? null : null;
|
|
414
|
+
}
|
|
415
|
+
function normalizeDocumentChildren(children) {
|
|
416
|
+
const list = toChildArray(children);
|
|
417
|
+
const htmlIndex = list.findIndex((child) => isHostElement(child, "html"));
|
|
418
|
+
if (htmlIndex === -1) return children;
|
|
419
|
+
const headNodes = list.filter(isHeadElement);
|
|
420
|
+
if (headNodes.length === 0) return children;
|
|
421
|
+
const htmlElement = list[htmlIndex];
|
|
422
|
+
const normalizedHtml = hoistIntoHtmlHead(htmlElement, headNodes);
|
|
423
|
+
return list.filter((child, index) => index === htmlIndex || !isHeadElement(child)).map((child) => child === htmlElement ? normalizedHtml : child);
|
|
424
|
+
}
|
|
425
|
+
function hoistIntoHtmlHead(htmlElement, headNodes) {
|
|
426
|
+
const htmlChildren = toChildArray(htmlElement.props?.children);
|
|
427
|
+
const headIndex = htmlChildren.findIndex((child) => isHostElement(child, "head"));
|
|
428
|
+
let nextChildren;
|
|
429
|
+
if (headIndex === -1) {
|
|
430
|
+
nextChildren = [
|
|
431
|
+
createHostElement("head", { children: headNodes }),
|
|
432
|
+
...htmlChildren
|
|
433
|
+
];
|
|
434
|
+
} else {
|
|
435
|
+
const headElement = htmlChildren[headIndex];
|
|
436
|
+
const existingHeadChildren = toChildArray(headElement.props?.children);
|
|
437
|
+
const nextHead = {
|
|
438
|
+
...headElement,
|
|
439
|
+
props: {
|
|
440
|
+
...headElement.props,
|
|
441
|
+
children: [...headNodes, ...existingHeadChildren]
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
nextChildren = htmlChildren.map((child, index) => index === headIndex ? nextHead : child);
|
|
445
|
+
}
|
|
446
|
+
return {
|
|
447
|
+
...htmlElement,
|
|
448
|
+
props: {
|
|
449
|
+
...htmlElement.props,
|
|
450
|
+
children: nextChildren
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
function toChildArray(children) {
|
|
455
|
+
if (children == null || typeof children === "boolean") return [];
|
|
456
|
+
if (Array.isArray(children)) return children;
|
|
457
|
+
if (isReactElement(children)) return [children];
|
|
458
|
+
if (typeof children !== "string" && isIterable(children)) return Array.from(children);
|
|
459
|
+
return [children];
|
|
460
|
+
}
|
|
461
|
+
function isHeadElement(value) {
|
|
462
|
+
return isReactElement(value) && typeof value.type === "string" && DOCUMENT_HEAD_TAGS.has(value.type);
|
|
463
|
+
}
|
|
464
|
+
function isHostElement(value, tag) {
|
|
465
|
+
return isReactElement(value) && value.type === tag;
|
|
466
|
+
}
|
|
467
|
+
function isReactElement(value) {
|
|
468
|
+
return !!value && typeof value === "object" && value.$$typeof === REACT_ELEMENT_TYPE;
|
|
469
|
+
}
|
|
470
|
+
function isIterable(value) {
|
|
471
|
+
return !!value && typeof value[Symbol.iterator] == "function";
|
|
472
|
+
}
|
|
473
|
+
function createHostElement(type, props) {
|
|
474
|
+
return {
|
|
475
|
+
$$typeof: REACT_ELEMENT_TYPE,
|
|
476
|
+
type,
|
|
477
|
+
key: null,
|
|
478
|
+
ref: null,
|
|
479
|
+
props
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
function validateHydrationProps(fiber, el, props, tag, isSvg) {
|
|
483
|
+
if (props.suppressHydrationWarning) return;
|
|
484
|
+
let expected;
|
|
485
|
+
for (const k in props) {
|
|
486
|
+
const value = props[k];
|
|
487
|
+
if (k === "children" || k === "key" || k === "ref" || k === "suppressHydrationWarning" || k === "suppressContentEditableWarning" || k[0] === "o" && k[1] === "n" && typeof value == "function") continue;
|
|
488
|
+
if (k === "dangerouslySetInnerHTML") {
|
|
489
|
+
const probe = document.createElement("div");
|
|
490
|
+
probe.innerHTML = value?.__html ?? "";
|
|
491
|
+
if (el.innerHTML !== probe.innerHTML) {
|
|
492
|
+
if (true) {
|
|
493
|
+
failHydration(
|
|
494
|
+
fiber,
|
|
495
|
+
new Error(`Hydration HTML mismatch inside <${tag}>.`)
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
failHydration(fiber);
|
|
499
|
+
}
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
if (k === "value" || k === "defaultValue") {
|
|
503
|
+
if (tag === "select") continue;
|
|
504
|
+
if (tag === "input" || tag === "textarea") {
|
|
505
|
+
if (k === "value" || props.value == null) {
|
|
506
|
+
if (value != null && el.value !== "" + value) {
|
|
507
|
+
if (true) {
|
|
508
|
+
failHydration(fiber, new Error(`Hydration ${tag} value mismatch on <${tag}>.`));
|
|
509
|
+
}
|
|
510
|
+
failHydration(fiber);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
continue;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
if (tag === "input" && (k === "checked" || k === "defaultChecked")) {
|
|
517
|
+
if (k === "checked" || props.checked == null) {
|
|
518
|
+
if (value != null && el.checked !== !!value) {
|
|
519
|
+
if (true) {
|
|
520
|
+
failHydration(fiber, new Error(`Hydration checked mismatch on <input>.`));
|
|
521
|
+
}
|
|
522
|
+
failHydration(fiber);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
if (tag === "option" && k === "selected") {
|
|
528
|
+
if (value != null && el.selected !== !!value) {
|
|
529
|
+
if (true) {
|
|
530
|
+
failHydration(fiber, new Error(`Hydration selected mismatch on <option>.`));
|
|
531
|
+
}
|
|
532
|
+
failHydration(fiber);
|
|
533
|
+
}
|
|
534
|
+
continue;
|
|
535
|
+
}
|
|
536
|
+
if (k === "style") {
|
|
537
|
+
expected ??= createHostNode(tag, isSvg);
|
|
538
|
+
setProp(expected, k, value, void 0, isSvg);
|
|
539
|
+
if (el.style.cssText !== expected.style.cssText) {
|
|
540
|
+
if (true) {
|
|
541
|
+
failHydration(
|
|
542
|
+
fiber,
|
|
543
|
+
new Error(`Hydration style mismatch on <${tag}>.`)
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
failHydration(fiber);
|
|
547
|
+
}
|
|
548
|
+
continue;
|
|
549
|
+
}
|
|
550
|
+
const stringifiedBoolean = k.startsWith("aria-") || k.startsWith("data-");
|
|
551
|
+
const attr = k === "className" ? "class" : k === "htmlFor" ? "for" : stringifiedBoolean ? k : k.toLowerCase();
|
|
552
|
+
let expectedValue;
|
|
553
|
+
if (value == null || value === false && !stringifiedBoolean) {
|
|
554
|
+
expectedValue = null;
|
|
555
|
+
} else {
|
|
556
|
+
expected ??= createHostNode(tag, isSvg);
|
|
557
|
+
setProp(expected, k, value, void 0, isSvg);
|
|
558
|
+
expectedValue = expected.getAttribute(attr);
|
|
559
|
+
}
|
|
560
|
+
const actualValue = el.getAttribute(attr);
|
|
561
|
+
if (expectedValue !== actualValue) {
|
|
562
|
+
if (true) {
|
|
563
|
+
failHydration(
|
|
564
|
+
fiber,
|
|
565
|
+
new Error(
|
|
566
|
+
`Hydration attribute mismatch on <${tag}> for "${attr}": expected ${formatHydrationValue(expectedValue)} but found ${formatHydrationValue(actualValue)}.`
|
|
567
|
+
)
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
failHydration(fiber);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
function formatHydrationValue(value) {
|
|
575
|
+
return value == null ? "nothing" : JSON.stringify(value);
|
|
259
576
|
}
|
|
260
577
|
export {
|
|
261
578
|
HydrationCursor,
|
|
579
|
+
abortHydration,
|
|
262
580
|
adoptHostDom,
|
|
263
581
|
adoptTextDom,
|
|
264
582
|
advanceCursorPast,
|
|
@@ -268,7 +586,9 @@ export {
|
|
|
268
586
|
endHydration,
|
|
269
587
|
findHostParent,
|
|
270
588
|
getHydrationCursor,
|
|
589
|
+
hydrateRootImpl,
|
|
271
590
|
installHydrationScrollGuard,
|
|
591
|
+
isHydrationBailout,
|
|
272
592
|
setHydrationCursor,
|
|
273
593
|
tryConsumeBoundary
|
|
274
594
|
};
|