why-hydration 0.1.4 → 0.2.0
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/CHANGELOG.md +108 -1
- package/README.md +220 -33
- package/dist/{chunk-WQLUD25W.js → chunk-3OAI2ZHY.js} +375 -118
- package/dist/chunk-3OAI2ZHY.js.map +1 -0
- package/dist/{chunk-OGQEPU7G.cjs → chunk-DXPTZB3Z.cjs} +44 -2
- package/dist/chunk-DXPTZB3Z.cjs.map +1 -0
- package/dist/{chunk-DSO337ME.cjs → chunk-KX6G7I4Y.cjs} +384 -118
- package/dist/chunk-KX6G7I4Y.cjs.map +1 -0
- package/dist/chunk-MK3TYHKM.js +966 -0
- package/dist/chunk-MK3TYHKM.js.map +1 -0
- package/dist/chunk-R7IWOK5M.cjs +989 -0
- package/dist/chunk-R7IWOK5M.cjs.map +1 -0
- package/dist/{chunk-FV3PEJQE.js → chunk-XIM33ZGB.js} +44 -2
- package/dist/chunk-XIM33ZGB.js.map +1 -0
- package/dist/index.cjs +32 -24
- package/dist/index.d.cts +43 -5
- package/dist/index.d.ts +43 -5
- package/dist/index.js +2 -2
- package/dist/next/index.cjs +5 -5
- package/dist/next/index.d.cts +1 -1
- package/dist/next/index.d.ts +1 -1
- package/dist/next/index.js +3 -3
- package/dist/next/script.cjs +2 -2
- package/dist/next/script.js +1 -1
- package/dist/react.cjs +5 -5
- package/dist/react.d.cts +23 -3
- package/dist/react.d.ts +23 -3
- package/dist/react.js +3 -3
- package/dist/{types-6wTih14e.d.cts → types-C14vcpBO.d.cts} +9 -0
- package/dist/{types-6wTih14e.d.ts → types-C14vcpBO.d.ts} +9 -0
- package/package.json +1 -1
- package/dist/chunk-3KULWJ7A.js +0 -569
- package/dist/chunk-3KULWJ7A.js.map +0 -1
- package/dist/chunk-5CBGYE7A.cjs +0 -592
- package/dist/chunk-5CBGYE7A.cjs.map +0 -1
- package/dist/chunk-DSO337ME.cjs.map +0 -1
- package/dist/chunk-FV3PEJQE.js.map +0 -1
- package/dist/chunk-OGQEPU7G.cjs.map +0 -1
- package/dist/chunk-WQLUD25W.js.map +0 -1
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkDXPTZB3Z_cjs = require('./chunk-DXPTZB3Z.cjs');
|
|
4
4
|
|
|
5
5
|
// src/core/env.ts
|
|
6
|
-
|
|
6
|
+
function readNodeEnv() {
|
|
7
|
+
try {
|
|
8
|
+
return process.env.NODE_ENV;
|
|
9
|
+
} catch {
|
|
10
|
+
return void 0;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
var isDev = readNodeEnv() !== "production";
|
|
7
14
|
|
|
8
15
|
// src/core/diff.ts
|
|
9
16
|
var IGNORED_ATTRIBUTES = /* @__PURE__ */ new Set(["data-reactroot"]);
|
|
10
17
|
function parseServerHtml(html, rootTagName) {
|
|
11
|
-
const
|
|
18
|
+
const doc = document.implementation.createHTMLDocument("");
|
|
19
|
+
const container = doc.createElement(rootTagName || "div");
|
|
12
20
|
container.innerHTML = html;
|
|
13
21
|
return container;
|
|
14
22
|
}
|
|
@@ -159,21 +167,21 @@ function diffAttributes(serverEl, clientEl, path, parentTag) {
|
|
|
159
167
|
}
|
|
160
168
|
return null;
|
|
161
169
|
}
|
|
162
|
-
function normalizeAttr(name,
|
|
163
|
-
if (
|
|
170
|
+
function normalizeAttr(name, value2) {
|
|
171
|
+
if (value2 == null) return null;
|
|
164
172
|
if (name === "class") {
|
|
165
|
-
return
|
|
173
|
+
return value2.trim().split(/\s+/).filter(Boolean).sort().join(" ");
|
|
166
174
|
}
|
|
167
175
|
if (name === "style") {
|
|
168
|
-
return normalizeStyle(
|
|
176
|
+
return normalizeStyle(value2);
|
|
169
177
|
}
|
|
170
|
-
return
|
|
178
|
+
return value2;
|
|
171
179
|
}
|
|
172
|
-
function normalizeStyle(
|
|
180
|
+
function normalizeStyle(value2) {
|
|
173
181
|
if (typeof document !== "undefined") {
|
|
174
182
|
try {
|
|
175
183
|
const el = document.createElement("div");
|
|
176
|
-
el.style.cssText =
|
|
184
|
+
el.style.cssText = value2;
|
|
177
185
|
const decls = [];
|
|
178
186
|
for (let i = 0; i < el.style.length; i++) {
|
|
179
187
|
const prop = el.style.item(i);
|
|
@@ -183,7 +191,7 @@ function normalizeStyle(value) {
|
|
|
183
191
|
} catch {
|
|
184
192
|
}
|
|
185
193
|
}
|
|
186
|
-
return
|
|
194
|
+
return value2.split(";").map((s) => s.trim()).filter(Boolean).sort().join(";");
|
|
187
195
|
}
|
|
188
196
|
function nodeKey(node) {
|
|
189
197
|
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
@@ -353,8 +361,26 @@ var RANDOM_DECIMAL_RE = /^0?\.\d{6,}$/;
|
|
|
353
361
|
var NANOID_RE = /^[A-Za-z0-9_-]{10,}$/;
|
|
354
362
|
var ARABIC_INDIC_DIGITS = /[٠-٩۰-۹]/;
|
|
355
363
|
var LATIN_DIGITS = /[0-9]/;
|
|
356
|
-
|
|
357
|
-
|
|
364
|
+
var BIDI_CONTROLS = /[\u200e\u200f\u061c\u2066-\u2069]/g;
|
|
365
|
+
function stripBidiControls(value2) {
|
|
366
|
+
return value2.replace(BIDI_CONTROLS, "");
|
|
367
|
+
}
|
|
368
|
+
function differsOnlyByBidiControls(a, b) {
|
|
369
|
+
const sa = stripBidiControls(a);
|
|
370
|
+
const sb = stripBidiControls(b);
|
|
371
|
+
return a !== b && sa === sb && sa.trim() !== "";
|
|
372
|
+
}
|
|
373
|
+
function normalizeNumerals(value2) {
|
|
374
|
+
return stripBidiControls(value2).replace(/[٠-٩۰-۹٫٬]/g, (ch) => {
|
|
375
|
+
if (ch === "\u066B") return ".";
|
|
376
|
+
if (ch === "\u066C") return ",";
|
|
377
|
+
const code2 = ch.charCodeAt(0);
|
|
378
|
+
const zero = code2 >= 1776 ? 1776 : 1632;
|
|
379
|
+
return String(code2 - zero);
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
function isRandomLike(value2) {
|
|
383
|
+
const v = value2.trim();
|
|
358
384
|
if (!v) return false;
|
|
359
385
|
if (UUID_RE.test(v)) return true;
|
|
360
386
|
if (REACT_ID_RE.test(v)) return true;
|
|
@@ -363,16 +389,26 @@ function isRandomLike(value) {
|
|
|
363
389
|
if (NANOID_RE.test(v) && /[A-Za-z]/.test(v) && /[0-9]/.test(v)) return true;
|
|
364
390
|
return false;
|
|
365
391
|
}
|
|
366
|
-
function looksLikeTime(
|
|
367
|
-
return /\b\d{1,2}:\d{2}(?::\d{2})?(?:\s?[AaPp][Mm])?\b/.test(
|
|
392
|
+
function looksLikeTime(value2) {
|
|
393
|
+
return /\b\d{1,2}:\d{2}(?::\d{2})?(?:\s?[AaPp][Mm])?\b/.test(
|
|
394
|
+
normalizeNumerals(value2).trim()
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
var ISO_DATE = /^\d{4}-\d{1,2}(?:-\d{1,2})?(?:[T ]\d{1,2}:\d{2}(?::\d{2})?)?/;
|
|
398
|
+
var NUMERIC_DATE = /^\d{1,4}[/.-]\d{1,2}[/.-]\d{1,4}$/;
|
|
399
|
+
var TIME_OF_DAY = /^\d{1,2}:\d{2}(?::\d{2})?(?:\s?[AaPp]\.?[Mm]\.?)?$/;
|
|
400
|
+
var MONTH_NAME = /\b(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*\b/i;
|
|
401
|
+
function looksLikeDate(v) {
|
|
402
|
+
return ISO_DATE.test(v) || NUMERIC_DATE.test(v) || TIME_OF_DAY.test(v) || MONTH_NAME.test(v) && /\d/.test(v);
|
|
368
403
|
}
|
|
369
|
-
function toTimestamp(
|
|
370
|
-
const v =
|
|
404
|
+
function toTimestamp(value2) {
|
|
405
|
+
const v = value2.trim();
|
|
371
406
|
if (!v) return null;
|
|
372
407
|
if (/^\d{10,13}$/.test(v)) {
|
|
373
408
|
const n = Number(v);
|
|
374
409
|
return v.length === 10 ? n * 1e3 : n;
|
|
375
410
|
}
|
|
411
|
+
if (!looksLikeDate(v)) return null;
|
|
376
412
|
const parsed = Date.parse(v);
|
|
377
413
|
if (!Number.isNaN(parsed)) return parsed;
|
|
378
414
|
if (looksLikeTime(v)) {
|
|
@@ -381,23 +417,23 @@ function toTimestamp(value) {
|
|
|
381
417
|
}
|
|
382
418
|
return null;
|
|
383
419
|
}
|
|
384
|
-
function hasArabicIndicDigits(
|
|
385
|
-
return ARABIC_INDIC_DIGITS.test(
|
|
420
|
+
function hasArabicIndicDigits(value2) {
|
|
421
|
+
return ARABIC_INDIC_DIGITS.test(value2);
|
|
386
422
|
}
|
|
387
|
-
function hasLatinDigits(
|
|
388
|
-
return LATIN_DIGITS.test(
|
|
423
|
+
function hasLatinDigits(value2) {
|
|
424
|
+
return LATIN_DIGITS.test(value2);
|
|
389
425
|
}
|
|
390
426
|
var NUMERIC_LIKE = /^[+-]?[\d.,\s\u00a0\u2009]+$/;
|
|
391
427
|
function isSameNumberDifferentSeparators(a, b) {
|
|
392
|
-
const at = a.trim();
|
|
393
|
-
const bt = b.trim();
|
|
428
|
+
const at = normalizeNumerals(a).trim();
|
|
429
|
+
const bt = normalizeNumerals(b).trim();
|
|
394
430
|
if (!NUMERIC_LIKE.test(at) || !NUMERIC_LIKE.test(bt)) return false;
|
|
395
431
|
const digitsOnly = (s) => s.replace(/\D/g, "");
|
|
396
432
|
const da = digitsOnly(at);
|
|
397
433
|
const db = digitsOnly(bt);
|
|
398
434
|
if (!da || da !== db) return false;
|
|
399
435
|
const hasSep = (s) => /[.,\s\u00a0\u2009]/.test(s);
|
|
400
|
-
return (hasSep(at) || hasSep(bt)) &&
|
|
436
|
+
return (hasSep(at) || hasSep(bt)) && a.trim() !== b.trim();
|
|
401
437
|
}
|
|
402
438
|
var CONTENT_ATTRIBUTES = /* @__PURE__ */ new Set([
|
|
403
439
|
"value",
|
|
@@ -421,9 +457,10 @@ function looksLikeThirdPartyNode(html, tagName) {
|
|
|
421
457
|
if (!h) return false;
|
|
422
458
|
return THIRD_PARTY_MARKERS.test(h) || h.includes("about:blank");
|
|
423
459
|
}
|
|
460
|
+
var DATE_PARTS_RE = /^(\d{1,4})[/.-](\d{1,2})[/.-](\d{1,4})$/;
|
|
424
461
|
function isSameDateDifferentOrder(a, b) {
|
|
425
|
-
const partsA = a.trim().match(
|
|
426
|
-
const partsB = b.trim().match(
|
|
462
|
+
const partsA = normalizeNumerals(a).trim().match(DATE_PARTS_RE);
|
|
463
|
+
const partsB = normalizeNumerals(b).trim().match(DATE_PARTS_RE);
|
|
427
464
|
if (!partsA || !partsB) return false;
|
|
428
465
|
const setA = [partsA[1], partsA[2], partsA[3]].sort().join("|");
|
|
429
466
|
const setB = [partsB[1], partsB[2], partsB[3]].sort().join("|");
|
|
@@ -489,6 +526,163 @@ function messageIndicatesInvalidNesting(message) {
|
|
|
489
526
|
return /validateDOMNesting/i.test(message) || /cannot (?:be a|contain).*(?:descendant|child)/i.test(message) || /cannot appear as a (?:child|descendant)/i.test(message);
|
|
490
527
|
}
|
|
491
528
|
|
|
529
|
+
// src/core/classify/messages.ts
|
|
530
|
+
var code = (text) => ({ code: text });
|
|
531
|
+
var value = (text) => ({ value: text });
|
|
532
|
+
function param(params, name) {
|
|
533
|
+
const v = params[name];
|
|
534
|
+
return typeof v === "string" ? v : "";
|
|
535
|
+
}
|
|
536
|
+
function paramList(params, name) {
|
|
537
|
+
const v = params[name];
|
|
538
|
+
return Array.isArray(v) ? v : [];
|
|
539
|
+
}
|
|
540
|
+
function valueList(items, separator) {
|
|
541
|
+
const out = [];
|
|
542
|
+
items.forEach((item, i) => {
|
|
543
|
+
if (i > 0) out.push(separator);
|
|
544
|
+
out.push(value(item));
|
|
545
|
+
});
|
|
546
|
+
return out;
|
|
547
|
+
}
|
|
548
|
+
function renderMessage(template, params = {}) {
|
|
549
|
+
if (typeof template === "function") return template(params);
|
|
550
|
+
const out = [];
|
|
551
|
+
template.split("`").forEach((part, i) => {
|
|
552
|
+
if (i % 2 === 1) {
|
|
553
|
+
out.push(
|
|
554
|
+
code(part.replace(/\{(\w+)\}/g, (_, n) => param(params, n)))
|
|
555
|
+
);
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
let last = 0;
|
|
559
|
+
for (const m of part.matchAll(/\{(\w+)\}/g)) {
|
|
560
|
+
const at = m.index ?? 0;
|
|
561
|
+
if (at > last) out.push(part.slice(last, at));
|
|
562
|
+
out.push(value(param(params, m[1])));
|
|
563
|
+
last = at + m[0].length;
|
|
564
|
+
}
|
|
565
|
+
if (last < part.length) out.push(part.slice(last));
|
|
566
|
+
});
|
|
567
|
+
return out.filter((s) => s !== "");
|
|
568
|
+
}
|
|
569
|
+
function plainText(segments) {
|
|
570
|
+
return segments.map(
|
|
571
|
+
(s) => typeof s === "string" ? s : "code" in s ? `\`${s.code}\`` : s.value
|
|
572
|
+
).join("");
|
|
573
|
+
}
|
|
574
|
+
function classDetail(params, words) {
|
|
575
|
+
const added = paramList(params, "added");
|
|
576
|
+
const removed = paramList(params, "removed");
|
|
577
|
+
if (!added.length && !removed.length) return [];
|
|
578
|
+
const out = [words.open];
|
|
579
|
+
if (added.length) {
|
|
580
|
+
out.push(words.added, ...valueList(added, words.listSeparator));
|
|
581
|
+
}
|
|
582
|
+
if (removed.length) {
|
|
583
|
+
if (added.length) out.push(words.partSeparator);
|
|
584
|
+
out.push(words.removed, ...valueList(removed, words.listSeparator));
|
|
585
|
+
}
|
|
586
|
+
out.push(words.close);
|
|
587
|
+
return out;
|
|
588
|
+
}
|
|
589
|
+
var EN_MESSAGES = {
|
|
590
|
+
"non-deterministic-value": {
|
|
591
|
+
explanation: "The server and client rendered different random-looking values (an id, token, or Math.random() output). Anything non-deterministic in render produces a different value on each side.",
|
|
592
|
+
suggestion: "Use React `useId()` for ids. For random values, generate them after mount (in `useEffect`) or pass a value down from the server so both sides agree. Never call `Math.random()`/`crypto` during render."
|
|
593
|
+
},
|
|
594
|
+
"date-time": {
|
|
595
|
+
explanation: "The values are dates/times that differ between server render and client render \u2014 the clock moved (or the timezone differs) between the two environments.",
|
|
596
|
+
suggestion: "Render the current time after mount, or pass a single server timestamp down and format it identically on both sides. Pin an explicit timezone when formatting."
|
|
597
|
+
},
|
|
598
|
+
"locale-format.bidi": {
|
|
599
|
+
explanation: "The values differ only by invisible bidirectional control marks (LRM/RLM/isolates). `Intl` adds these around numbers and dates in RTL locales, and different ICU versions \u2014 Node vs the browser \u2014 emit different ones for the same input.",
|
|
600
|
+
suggestion: "Format the value in one place and pass the string down, or pin the same locale and timezone on both sides. If the marks are harmless, add `suppressHydrationWarning` to the element."
|
|
601
|
+
},
|
|
602
|
+
"locale-format.script": {
|
|
603
|
+
explanation: "The same value was formatted with different digit scripts (Arabic-Indic \u0660\u0661\u0662 vs Latin 012). The server and client resolved to different locales.",
|
|
604
|
+
suggestion: "Pass an explicit `locale` (and timezone) to `Intl.NumberFormat` / `toLocaleString` on both server and client, or format the value after mount so only the client locale is ever used."
|
|
605
|
+
},
|
|
606
|
+
"locale-format.separators": {
|
|
607
|
+
explanation: "The same number was formatted with different grouping/decimal separators between server and client (e.g. 1,234.56 vs 1.234,56).",
|
|
608
|
+
suggestion: "Pass an explicit locale to `Intl.NumberFormat`/`toLocaleString` on both sides so the separators match."
|
|
609
|
+
},
|
|
610
|
+
"locale-format.date-order": {
|
|
611
|
+
explanation: "The same date was rendered in a different field order (MM/DD vs DD/MM) between server and client.",
|
|
612
|
+
suggestion: "Format dates with an explicit locale and timezone via `Intl` on both sides."
|
|
613
|
+
},
|
|
614
|
+
"browser-only-api": {
|
|
615
|
+
explanation: "The client rendered content the server left empty \u2014 the signature of reading a browser-only API (`window`, `document`, `localStorage`, `navigator`, `matchMedia`) during render.",
|
|
616
|
+
suggestion: "Gate browser-only reads behind a mounted flag or `useEffect`, or use `useSyncExternalStore` with a server snapshot so the first client render matches the server."
|
|
617
|
+
},
|
|
618
|
+
"viewport-branching": {
|
|
619
|
+
explanation: "A whole subtree was added, removed, or swapped between server and client \u2014 typically a JavaScript width/viewport check that branches the tree at first render.",
|
|
620
|
+
suggestion: "Render both branches and switch between them with CSS media queries at first paint instead of branching in JavaScript, or defer the JS-driven branch until after mount."
|
|
621
|
+
},
|
|
622
|
+
"invalid-html-nesting": {
|
|
623
|
+
explanation: "A node was moved or ejected because the markup is invalid HTML (e.g. a `<div>` inside a `<p>`, or nested `<a>`). The browser repairs the server DOM, so it no longer matches what React expects.",
|
|
624
|
+
suggestion: "Fix the markup validity: block elements cannot live inside `<p>`, anchors cannot nest, etc. Replace the invalid parent with a `<div>` or restructure the tree."
|
|
625
|
+
},
|
|
626
|
+
"whitespace-minification": {
|
|
627
|
+
explanation: "The mismatch is whitespace-only \u2014 the text is identical apart from spaces/newlines. An HTML minifier likely collapsed whitespace around the hydration root differently from React.",
|
|
628
|
+
suggestion: "Check your HTML minifier settings (e.g. `conservativeCollapse`) around the app root, or avoid minifying whitespace inside hydrated markup."
|
|
629
|
+
},
|
|
630
|
+
"third-party-dom-mutation.extension-attribute": {
|
|
631
|
+
explanation: "The attribute `{attribute}` was injected by a browser extension or third-party script (e.g. Grammarly, ColorZilla) before hydration, so the client DOM no longer matches the server.",
|
|
632
|
+
suggestion: "This is usually harmless. Add `suppressHydrationWarning` to the affected element, or defer third-party script init until after hydration."
|
|
633
|
+
},
|
|
634
|
+
"third-party-dom-mutation.root-attribute": {
|
|
635
|
+
explanation: "An attribute (`{attribute}`) appeared on a root element that the server never sent \u2014 a hallmark of an extension or early third-party script mutating the DOM.",
|
|
636
|
+
suggestion: "Add `suppressHydrationWarning` to the root element, or defer the third-party script until after hydration."
|
|
637
|
+
},
|
|
638
|
+
"third-party-dom-mutation.injected-node": {
|
|
639
|
+
explanation: "A {tag} was injected by a third-party script or browser extension (ads, consent, analytics, chat) after the server render. It is not part of your app's hydration, so this is usually harmless noise.",
|
|
640
|
+
suggestion: 'If React warns about it, add `suppressHydrationWarning` to the nearest server-rendered wrapper, or load the third-party script after hydration (e.g. Next.js `<Script strategy="afterInteractive">`).'
|
|
641
|
+
},
|
|
642
|
+
"attribute-mismatch.class": {
|
|
643
|
+
explanation: (p) => [
|
|
644
|
+
"The ",
|
|
645
|
+
code("class"),
|
|
646
|
+
" differs between server and client",
|
|
647
|
+
...classDetail(p, {
|
|
648
|
+
added: "added on client: ",
|
|
649
|
+
removed: "removed on client: ",
|
|
650
|
+
listSeparator: ", ",
|
|
651
|
+
partSeparator: "; ",
|
|
652
|
+
open: " (",
|
|
653
|
+
close: ")"
|
|
654
|
+
}),
|
|
655
|
+
". A class was applied conditionally on the client \u2014 commonly a viewport, media-query, theme, or feature-flag check that runs during the first render."
|
|
656
|
+
],
|
|
657
|
+
suggestion: "Render the same className on the server and the first client paint. Move client-only conditions into `useEffect`/a mounted flag, or drive the visual change with CSS media queries instead of a JS class toggle."
|
|
658
|
+
},
|
|
659
|
+
"attribute-mismatch.style": {
|
|
660
|
+
explanation: "The inline `style` differs between server and client \u2014 an inline style was computed from client-only state (viewport size, theme, scroll position) during render.",
|
|
661
|
+
suggestion: "Compute the style after mount (`useEffect`) so the first client render matches the server, or move it to a CSS class / media query."
|
|
662
|
+
},
|
|
663
|
+
"attribute-mismatch.generic": {
|
|
664
|
+
explanation: "The `{attribute}` attribute differs between server (`{server}`) and client (`{client}`) \u2014 its value was derived from something that differs between the server and the first client render.",
|
|
665
|
+
suggestion: "Make the attribute deterministic across server and client, or set it after mount so the first client render matches the server HTML."
|
|
666
|
+
},
|
|
667
|
+
unknown: {
|
|
668
|
+
explanation: "A hydration mismatch was detected but could not be matched to a known cause. Inspect the server vs client values above.",
|
|
669
|
+
suggestion: "Compare the server and client values. Common causes are non-deterministic values, dates/locales, and browser-only APIs used during render."
|
|
670
|
+
},
|
|
671
|
+
"unknown.no-location": {
|
|
672
|
+
explanation: "React reported that hydration failed but did not say which node differed, and the DOM diff found no difference to point at.",
|
|
673
|
+
suggestion: "Make sure `<HydrationSnapshotScript>` (or the manual snapshot script) is in `<head>` so the DOM diff can locate the node, and read React's full warning in the browser console."
|
|
674
|
+
}
|
|
675
|
+
};
|
|
676
|
+
function describe(id, params = {}, catalog = EN_MESSAGES) {
|
|
677
|
+
const entry = catalog[id];
|
|
678
|
+
return {
|
|
679
|
+
messageId: id,
|
|
680
|
+
...Object.keys(params).length ? { params } : {},
|
|
681
|
+
explanation: plainText(renderMessage(entry.explanation, params)),
|
|
682
|
+
suggestion: plainText(renderMessage(entry.suggestion, params))
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
|
|
492
686
|
// src/core/classify/rules.ts
|
|
493
687
|
var DOCS_BASE = "https://github.com/razan-aboushi/why-hydration#cause-";
|
|
494
688
|
function docs(category) {
|
|
@@ -511,8 +705,7 @@ var nonDeterministic = (d) => {
|
|
|
511
705
|
return {
|
|
512
706
|
category: "non-deterministic-value",
|
|
513
707
|
confidence: 0.9,
|
|
514
|
-
|
|
515
|
-
suggestion: "Use React `useId()` for ids. For random values, generate them after mount (in `useEffect`) or pass a value down from the server so both sides agree. Never call `Math.random()`/`crypto` during render.",
|
|
708
|
+
...describe("non-deterministic-value"),
|
|
516
709
|
docsUrl: docs("non-deterministic-value")
|
|
517
710
|
};
|
|
518
711
|
};
|
|
@@ -530,8 +723,7 @@ var dateTime = (d) => {
|
|
|
530
723
|
return {
|
|
531
724
|
category: "date-time",
|
|
532
725
|
confidence: smallDelta || bothTimes ? 0.85 : 0.7,
|
|
533
|
-
|
|
534
|
-
suggestion: "Render the current time after mount, or pass a single server timestamp down and format it identically on both sides. Pin an explicit timezone when formatting.",
|
|
726
|
+
...describe("date-time"),
|
|
535
727
|
docsUrl: docs("date-time")
|
|
536
728
|
};
|
|
537
729
|
};
|
|
@@ -539,13 +731,20 @@ var localeFormat = (d) => {
|
|
|
539
731
|
if (!isValueDivergence(d)) return null;
|
|
540
732
|
if (!bothDiffer(d)) return null;
|
|
541
733
|
const { server, client } = d;
|
|
734
|
+
if (differsOnlyByBidiControls(server, client)) {
|
|
735
|
+
return {
|
|
736
|
+
category: "locale-format",
|
|
737
|
+
confidence: 0.88,
|
|
738
|
+
...describe("locale-format.bidi"),
|
|
739
|
+
docsUrl: docs("locale-format")
|
|
740
|
+
};
|
|
741
|
+
}
|
|
542
742
|
const scriptMismatch = hasArabicIndicDigits(server) && hasLatinDigits(client) || hasLatinDigits(server) && hasArabicIndicDigits(client);
|
|
543
743
|
if (scriptMismatch) {
|
|
544
744
|
return {
|
|
545
745
|
category: "locale-format",
|
|
546
746
|
confidence: 0.92,
|
|
547
|
-
|
|
548
|
-
suggestion: "Pass an explicit `locale` (and timezone) to `Intl.NumberFormat` / `toLocaleString` on both server and client, or format the value after mount so only the client locale is ever used.",
|
|
747
|
+
...describe("locale-format.script"),
|
|
549
748
|
docsUrl: docs("locale-format")
|
|
550
749
|
};
|
|
551
750
|
}
|
|
@@ -553,8 +752,7 @@ var localeFormat = (d) => {
|
|
|
553
752
|
return {
|
|
554
753
|
category: "locale-format",
|
|
555
754
|
confidence: 0.82,
|
|
556
|
-
|
|
557
|
-
suggestion: "Pass an explicit locale to `Intl.NumberFormat`/`toLocaleString` on both sides so the separators match.",
|
|
755
|
+
...describe("locale-format.separators"),
|
|
558
756
|
docsUrl: docs("locale-format")
|
|
559
757
|
};
|
|
560
758
|
}
|
|
@@ -562,8 +760,7 @@ var localeFormat = (d) => {
|
|
|
562
760
|
return {
|
|
563
761
|
category: "locale-format",
|
|
564
762
|
confidence: 0.75,
|
|
565
|
-
|
|
566
|
-
suggestion: "Format dates with an explicit locale and timezone via `Intl` on both sides.",
|
|
763
|
+
...describe("locale-format.date-order"),
|
|
567
764
|
docsUrl: docs("locale-format")
|
|
568
765
|
};
|
|
569
766
|
}
|
|
@@ -577,8 +774,7 @@ var browserOnlyApi = (d) => {
|
|
|
577
774
|
return {
|
|
578
775
|
category: "browser-only-api",
|
|
579
776
|
confidence: 0.75,
|
|
580
|
-
|
|
581
|
-
suggestion: "Gate browser-only reads behind a mounted flag or `useEffect`, or use `useSyncExternalStore` with a server snapshot so the first client render matches the server.",
|
|
777
|
+
...describe("browser-only-api"),
|
|
582
778
|
docsUrl: docs("browser-only-api")
|
|
583
779
|
};
|
|
584
780
|
};
|
|
@@ -588,11 +784,11 @@ var viewportBranching = (d) => {
|
|
|
588
784
|
if (isInvalidNesting(d.parentTagName, d.tagName) || messageIndicatesInvalidNesting(d.reactMessage)) {
|
|
589
785
|
return null;
|
|
590
786
|
}
|
|
787
|
+
if (d.server == null && d.client == null && !d.tagName) return null;
|
|
591
788
|
return {
|
|
592
789
|
category: "viewport-branching",
|
|
593
790
|
confidence: 0.6,
|
|
594
|
-
|
|
595
|
-
suggestion: "Render both branches and switch between them with CSS media queries at first paint instead of branching in JavaScript, or defer the JS-driven branch until after mount.",
|
|
791
|
+
...describe("viewport-branching"),
|
|
596
792
|
docsUrl: docs("viewport-branching")
|
|
597
793
|
};
|
|
598
794
|
};
|
|
@@ -603,8 +799,7 @@ var invalidNesting = (d) => {
|
|
|
603
799
|
return {
|
|
604
800
|
category: "invalid-html-nesting",
|
|
605
801
|
confidence: byMessage ? 0.9 : 0.72,
|
|
606
|
-
|
|
607
|
-
suggestion: "Fix the markup validity: block elements cannot live inside `<p>`, anchors cannot nest, etc. Replace the invalid parent with a `<div>` or restructure the tree.",
|
|
802
|
+
...describe("invalid-html-nesting"),
|
|
608
803
|
docsUrl: docs("invalid-html-nesting")
|
|
609
804
|
};
|
|
610
805
|
};
|
|
@@ -617,8 +812,7 @@ var whitespaceMinification = (d) => {
|
|
|
617
812
|
return {
|
|
618
813
|
category: "whitespace-minification",
|
|
619
814
|
confidence: 0.7,
|
|
620
|
-
|
|
621
|
-
suggestion: "Check your HTML minifier settings (e.g. `conservativeCollapse`) around the app root, or avoid minifying whitespace inside hydrated markup.",
|
|
815
|
+
...describe("whitespace-minification"),
|
|
622
816
|
docsUrl: docs("whitespace-minification")
|
|
623
817
|
};
|
|
624
818
|
};
|
|
@@ -628,18 +822,21 @@ var thirdPartyDomMutation = (d) => {
|
|
|
628
822
|
return {
|
|
629
823
|
category: "third-party-dom-mutation",
|
|
630
824
|
confidence: 0.88,
|
|
631
|
-
|
|
632
|
-
|
|
825
|
+
...describe("third-party-dom-mutation.extension-attribute", {
|
|
826
|
+
attribute: d.attribute
|
|
827
|
+
}),
|
|
633
828
|
docsUrl: docs("third-party-dom-mutation")
|
|
634
829
|
};
|
|
635
830
|
}
|
|
636
|
-
const
|
|
831
|
+
const el = d.element;
|
|
832
|
+
const atRoot = d.server == null && el != null && /^(?:HTML|BODY)$/.test(el.tagName);
|
|
637
833
|
if (atRoot) {
|
|
638
834
|
return {
|
|
639
835
|
category: "third-party-dom-mutation",
|
|
640
836
|
confidence: 0.6,
|
|
641
|
-
|
|
642
|
-
|
|
837
|
+
...describe("third-party-dom-mutation.root-attribute", {
|
|
838
|
+
attribute: d.attribute
|
|
839
|
+
}),
|
|
643
840
|
docsUrl: docs("third-party-dom-mutation")
|
|
644
841
|
};
|
|
645
842
|
}
|
|
@@ -649,8 +846,9 @@ var thirdPartyDomMutation = (d) => {
|
|
|
649
846
|
return {
|
|
650
847
|
category: "third-party-dom-mutation",
|
|
651
848
|
confidence: 0.7,
|
|
652
|
-
|
|
653
|
-
|
|
849
|
+
...describe("third-party-dom-mutation.injected-node", {
|
|
850
|
+
tag: `<${tag}>`
|
|
851
|
+
}),
|
|
654
852
|
docsUrl: docs("third-party-dom-mutation")
|
|
655
853
|
};
|
|
656
854
|
}
|
|
@@ -666,15 +864,10 @@ var attributeMismatch = (d) => {
|
|
|
666
864
|
const clientSet = new Set(client.split(/\s+/).filter(Boolean));
|
|
667
865
|
const added = [...clientSet].filter((c) => !serverSet.has(c));
|
|
668
866
|
const removed = [...serverSet].filter((c) => !clientSet.has(c));
|
|
669
|
-
const parts = [];
|
|
670
|
-
if (added.length) parts.push(`added on client: ${added.join(", ")}`);
|
|
671
|
-
if (removed.length) parts.push(`removed on client: ${removed.join(", ")}`);
|
|
672
|
-
const detail = parts.length ? ` (${parts.join("; ")})` : "";
|
|
673
867
|
return {
|
|
674
868
|
category: "attribute-mismatch",
|
|
675
869
|
confidence: 0.8,
|
|
676
|
-
|
|
677
|
-
suggestion: "Render the same className on the server and the first client paint. Move client-only conditions into `useEffect`/a mounted flag, or drive the visual change with CSS media queries instead of a JS class toggle.",
|
|
870
|
+
...describe("attribute-mismatch.class", { added, removed }),
|
|
678
871
|
docsUrl: docs("attribute-mismatch")
|
|
679
872
|
};
|
|
680
873
|
}
|
|
@@ -682,16 +875,18 @@ var attributeMismatch = (d) => {
|
|
|
682
875
|
return {
|
|
683
876
|
category: "attribute-mismatch",
|
|
684
877
|
confidence: 0.75,
|
|
685
|
-
|
|
686
|
-
suggestion: "Compute the style after mount (`useEffect`) so the first client render matches the server, or move it to a CSS class / media query.",
|
|
878
|
+
...describe("attribute-mismatch.style"),
|
|
687
879
|
docsUrl: docs("attribute-mismatch")
|
|
688
880
|
};
|
|
689
881
|
}
|
|
690
882
|
return {
|
|
691
883
|
category: "attribute-mismatch",
|
|
692
884
|
confidence: 0.6,
|
|
693
|
-
|
|
694
|
-
|
|
885
|
+
...describe("attribute-mismatch.generic", {
|
|
886
|
+
attribute: d.attribute,
|
|
887
|
+
server,
|
|
888
|
+
client
|
|
889
|
+
}),
|
|
695
890
|
docsUrl: docs("attribute-mismatch")
|
|
696
891
|
};
|
|
697
892
|
};
|
|
@@ -713,13 +908,21 @@ var BUILT_IN_RULES = [
|
|
|
713
908
|
var UNKNOWN_CAUSE = {
|
|
714
909
|
category: "unknown",
|
|
715
910
|
confidence: 0,
|
|
716
|
-
|
|
717
|
-
|
|
911
|
+
...describe("unknown"),
|
|
912
|
+
docsUrl: docs("unknown")
|
|
913
|
+
};
|
|
914
|
+
var UNKNOWN_NO_LOCATION_CAUSE = {
|
|
915
|
+
category: "unknown",
|
|
916
|
+
confidence: 0,
|
|
917
|
+
...describe("unknown.no-location"),
|
|
718
918
|
docsUrl: docs("unknown")
|
|
719
919
|
};
|
|
720
920
|
|
|
721
921
|
// src/core/classify/index.ts
|
|
722
922
|
var CONFIDENCE_THRESHOLD = 0.5;
|
|
923
|
+
function isLocationless(d) {
|
|
924
|
+
return d.server == null && d.client == null && !d.tagName && !d.attribute;
|
|
925
|
+
}
|
|
723
926
|
function classify(divergence, options = {}) {
|
|
724
927
|
const threshold = options.threshold ?? CONFIDENCE_THRESHOLD;
|
|
725
928
|
const rules = [
|
|
@@ -737,7 +940,7 @@ function classify(divergence, options = {}) {
|
|
|
737
940
|
return result;
|
|
738
941
|
}
|
|
739
942
|
}
|
|
740
|
-
return UNKNOWN_CAUSE;
|
|
943
|
+
return isLocationless(divergence) ? UNKNOWN_NO_LOCATION_CAUSE : UNKNOWN_CAUSE;
|
|
741
944
|
}
|
|
742
945
|
|
|
743
946
|
// src/core/report.ts
|
|
@@ -746,9 +949,12 @@ function nextId() {
|
|
|
746
949
|
counter += 1;
|
|
747
950
|
return `wh_${Date.now().toString(36)}_${counter}`;
|
|
748
951
|
}
|
|
749
|
-
function truncate(
|
|
750
|
-
if (
|
|
751
|
-
|
|
952
|
+
function truncate(value2, max = 300) {
|
|
953
|
+
if (value2 == null) return null;
|
|
954
|
+
if (value2.length <= max) return value2;
|
|
955
|
+
const last = value2.charCodeAt(max - 1);
|
|
956
|
+
const end = last >= 55296 && last <= 56319 ? max - 1 : max;
|
|
957
|
+
return `${value2.slice(0, end)}\u2026`;
|
|
752
958
|
}
|
|
753
959
|
function buildReport(divergence, cause, context = {}) {
|
|
754
960
|
return {
|
|
@@ -786,34 +992,48 @@ var ReportCollector = class {
|
|
|
786
992
|
this.options = options;
|
|
787
993
|
this.maxReports = options.maxReports ?? 25;
|
|
788
994
|
}
|
|
789
|
-
|
|
995
|
+
/**
|
|
996
|
+
* Register a sink. Pass `replay` for sinks that render *state* (the overlay)
|
|
997
|
+
* rather than react to *events* (`onReport`, the console): they need the
|
|
998
|
+
* reports collected before they were attached, which matters when a sink is
|
|
999
|
+
* re-attached after a stop/start cycle.
|
|
1000
|
+
*/
|
|
1001
|
+
addSink(sink, options = {}) {
|
|
790
1002
|
this.sinks.add(sink);
|
|
1003
|
+
if (options.replay) {
|
|
1004
|
+
for (const report of this.reports) this.emit(sink, report);
|
|
1005
|
+
}
|
|
791
1006
|
return () => this.sinks.delete(sink);
|
|
792
1007
|
}
|
|
1008
|
+
emit(sink, report) {
|
|
1009
|
+
try {
|
|
1010
|
+
sink(report);
|
|
1011
|
+
} catch {
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
793
1014
|
getReports() {
|
|
794
1015
|
return this.reports;
|
|
795
1016
|
}
|
|
1017
|
+
/** The cause this collector would assign — same rules, same threshold. */
|
|
1018
|
+
classify(divergence) {
|
|
1019
|
+
return classify(divergence, {
|
|
1020
|
+
extra: this.options.extra,
|
|
1021
|
+
threshold: this.options.threshold
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
796
1024
|
get isFull() {
|
|
797
1025
|
return this.reports.length >= this.maxReports;
|
|
798
1026
|
}
|
|
799
1027
|
report(divergence, context = {}) {
|
|
800
1028
|
if (this.isFull) return null;
|
|
801
1029
|
if (this.options.ignore?.(divergence)) return null;
|
|
802
|
-
const cause = classify(divergence
|
|
803
|
-
extra: this.options.extra,
|
|
804
|
-
threshold: this.options.threshold
|
|
805
|
-
});
|
|
1030
|
+
const cause = this.classify(divergence);
|
|
806
1031
|
const report = buildReport(divergence, cause, context);
|
|
807
1032
|
const signature = signatureOf(report);
|
|
808
1033
|
if (this.seen.has(signature)) return null;
|
|
809
1034
|
this.seen.add(signature);
|
|
810
1035
|
this.reports.push(report);
|
|
811
|
-
for (const sink of this.sinks)
|
|
812
|
-
try {
|
|
813
|
-
sink(report);
|
|
814
|
-
} catch {
|
|
815
|
-
}
|
|
816
|
-
}
|
|
1036
|
+
for (const sink of this.sinks) this.emit(sink, report);
|
|
817
1037
|
return report;
|
|
818
1038
|
}
|
|
819
1039
|
reset() {
|
|
@@ -857,11 +1077,19 @@ function extractComponentFromMessage(message) {
|
|
|
857
1077
|
}
|
|
858
1078
|
return names.length ? names[names.length - 1] : void 0;
|
|
859
1079
|
}
|
|
1080
|
+
function diffLines(message) {
|
|
1081
|
+
const lines = message.split("\n");
|
|
1082
|
+
let link = -1;
|
|
1083
|
+
lines.forEach((line, i) => {
|
|
1084
|
+
if (/react\.dev\/link\/hydration-mismatch/.test(line)) link = i;
|
|
1085
|
+
});
|
|
1086
|
+
return link >= 0 ? lines.slice(link + 1) : lines;
|
|
1087
|
+
}
|
|
860
1088
|
var ATTR_RE = /^([\w:-]+)=(?:"([\s\S]*)"|\{([\s\S]*)\})$/;
|
|
861
1089
|
function parseModernDiff(message) {
|
|
862
1090
|
const plus = [];
|
|
863
1091
|
const minus = [];
|
|
864
|
-
for (const raw of message
|
|
1092
|
+
for (const raw of diffLines(message)) {
|
|
865
1093
|
const line = raw.trim();
|
|
866
1094
|
const p = /^\+\s+(.+)$/.exec(line);
|
|
867
1095
|
const mn = /^-\s+(.+)$/.exec(line);
|
|
@@ -987,45 +1215,47 @@ function parseHydrationMessage(message) {
|
|
|
987
1215
|
}
|
|
988
1216
|
function parseAllHydrationDivergences(message) {
|
|
989
1217
|
if (!isHydrationMessage(message)) return [];
|
|
1218
|
+
const fallback = () => {
|
|
1219
|
+
const single = parseHydrationMessage(message);
|
|
1220
|
+
return single ? [single] : [];
|
|
1221
|
+
};
|
|
990
1222
|
const plus = [];
|
|
991
1223
|
const minus = [];
|
|
992
|
-
for (const raw of message
|
|
1224
|
+
for (const raw of diffLines(message)) {
|
|
993
1225
|
const line = raw.trim();
|
|
994
1226
|
const p = /^\+\s+(.+)$/.exec(line);
|
|
995
1227
|
const mn = /^-\s+(.+)$/.exec(line);
|
|
996
1228
|
if (p && p[1]) plus.push(p[1].trim());
|
|
997
1229
|
else if (mn && mn[1]) minus.push(mn[1].trim());
|
|
998
1230
|
}
|
|
999
|
-
if (plus.length === 0 && minus.length === 0)
|
|
1000
|
-
const single = parseHydrationMessage(message);
|
|
1001
|
-
return single ? [single] : [];
|
|
1002
|
-
}
|
|
1231
|
+
if (plus.length === 0 && minus.length === 0) return fallback();
|
|
1003
1232
|
const out = [];
|
|
1233
|
+
const attrPlus = plus.filter((p) => ATTR_RE.test(p));
|
|
1004
1234
|
const attrMinus = minus.filter((m) => ATTR_RE.test(m));
|
|
1005
1235
|
const usedMinus = /* @__PURE__ */ new Set();
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
if (
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
server: parsed ? parsed[2] ?? parsed[3] ?? "" : "",
|
|
1024
|
-
client: clientValue,
|
|
1025
|
-
reactMessage: message
|
|
1026
|
-
});
|
|
1027
|
-
}
|
|
1236
|
+
const attribute = (name, server, client) => ({
|
|
1237
|
+
kind: "attribute",
|
|
1238
|
+
path: "body",
|
|
1239
|
+
attribute: name,
|
|
1240
|
+
server,
|
|
1241
|
+
client,
|
|
1242
|
+
reactMessage: message
|
|
1243
|
+
});
|
|
1244
|
+
for (const line of attrPlus) {
|
|
1245
|
+
const name = attrName(line);
|
|
1246
|
+
if (!name) continue;
|
|
1247
|
+
const idx = attrMinus.findIndex(
|
|
1248
|
+
(m, k) => !usedMinus.has(k) && attrName(m) === name
|
|
1249
|
+
);
|
|
1250
|
+
if (idx >= 0) usedMinus.add(idx);
|
|
1251
|
+
const server = idx >= 0 ? attrValue(attrMinus[idx]) : null;
|
|
1252
|
+
out.push(attribute(name, server, attrValue(line)));
|
|
1028
1253
|
}
|
|
1254
|
+
attrMinus.forEach((line, k) => {
|
|
1255
|
+
if (usedMinus.has(k)) return;
|
|
1256
|
+
const name = attrName(line);
|
|
1257
|
+
if (name) out.push(attribute(name, attrValue(line), null));
|
|
1258
|
+
});
|
|
1029
1259
|
const textPlus = plus.filter((p) => !ATTR_RE.test(p));
|
|
1030
1260
|
const textMinus = minus.filter((m) => !ATTR_RE.test(m));
|
|
1031
1261
|
const len = Math.max(textPlus.length, textMinus.length);
|
|
@@ -1033,22 +1263,32 @@ function parseAllHydrationDivergences(message) {
|
|
|
1033
1263
|
const client = textPlus[i] ?? null;
|
|
1034
1264
|
const server = textMinus[i] ?? null;
|
|
1035
1265
|
if (client !== null || server !== null) {
|
|
1036
|
-
out.push({
|
|
1266
|
+
out.push({
|
|
1267
|
+
kind: "text",
|
|
1268
|
+
path: "body",
|
|
1269
|
+
server,
|
|
1270
|
+
client,
|
|
1271
|
+
reactMessage: message
|
|
1272
|
+
});
|
|
1037
1273
|
}
|
|
1038
1274
|
}
|
|
1039
|
-
return out.length ? out :
|
|
1275
|
+
return out.length ? out : fallback();
|
|
1276
|
+
}
|
|
1277
|
+
function attrName(line) {
|
|
1278
|
+
return ATTR_RE.exec(line)?.[1];
|
|
1279
|
+
}
|
|
1280
|
+
function attrValue(line) {
|
|
1281
|
+
const parsed = ATTR_RE.exec(line);
|
|
1282
|
+
return parsed ? parsed[2] ?? parsed[3] ?? "" : "";
|
|
1040
1283
|
}
|
|
1041
1284
|
|
|
1042
1285
|
// src/core/inspect.ts
|
|
1043
1286
|
function inspectRoot(root, collector, context = {}, enrich) {
|
|
1044
|
-
const serverHtml =
|
|
1287
|
+
const serverHtml = chunkDXPTZB3Z_cjs.getServerHtmlForRoot(root);
|
|
1045
1288
|
if (serverHtml == null) return 0;
|
|
1046
|
-
const divergences =
|
|
1289
|
+
const divergences = collectDivergences(serverTreeFor(root, serverHtml), root);
|
|
1047
1290
|
let reported = 0;
|
|
1048
1291
|
for (const divergence of divergences) {
|
|
1049
|
-
if (context.reactMessage && !divergence.reactMessage) {
|
|
1050
|
-
divergence.reactMessage = context.reactMessage;
|
|
1051
|
-
}
|
|
1052
1292
|
const enriched = enrich ? enrich(divergence) : {};
|
|
1053
1293
|
if (collector.report(divergence, { ...context, ...enriched }) != null) {
|
|
1054
1294
|
reported += 1;
|
|
@@ -1056,12 +1296,29 @@ function inspectRoot(root, collector, context = {}, enrich) {
|
|
|
1056
1296
|
}
|
|
1057
1297
|
return reported;
|
|
1058
1298
|
}
|
|
1059
|
-
|
|
1060
|
-
|
|
1299
|
+
var serverTrees = /* @__PURE__ */ new WeakMap();
|
|
1300
|
+
function serverTreeFor(root, html) {
|
|
1301
|
+
const cached = serverTrees.get(root);
|
|
1302
|
+
if (cached && cached.html === html) return cached.tree;
|
|
1303
|
+
const tree = parseServerHtml(html, root.tagName);
|
|
1304
|
+
serverTrees.set(root, { html, tree });
|
|
1305
|
+
return tree;
|
|
1306
|
+
}
|
|
1307
|
+
function reportFromMessage(message, collector, context = {}, options = {}) {
|
|
1308
|
+
const mode = options.locationless ?? "include";
|
|
1309
|
+
const divergences = parseAllHydrationDivergences(message).filter((d) => {
|
|
1310
|
+
if (mode === "include") return true;
|
|
1311
|
+
const bare = isLocationless(d) && collector.classify(d).messageId === "unknown.no-location";
|
|
1312
|
+
return mode === "skip" ? !bare : bare;
|
|
1313
|
+
});
|
|
1061
1314
|
const component = extractComponentFromMessage(message) ?? context.component;
|
|
1062
1315
|
let reported = 0;
|
|
1063
1316
|
for (const divergence of divergences) {
|
|
1064
|
-
if (collector.report(divergence, {
|
|
1317
|
+
if (collector.report(divergence, {
|
|
1318
|
+
...context,
|
|
1319
|
+
component,
|
|
1320
|
+
reactMessage: message
|
|
1321
|
+
}) != null) {
|
|
1065
1322
|
reported += 1;
|
|
1066
1323
|
}
|
|
1067
1324
|
}
|
|
@@ -1070,19 +1327,28 @@ function reportFromMessage(message, collector, context = {}) {
|
|
|
1070
1327
|
|
|
1071
1328
|
exports.BUILT_IN_RULES = BUILT_IN_RULES;
|
|
1072
1329
|
exports.CONFIDENCE_THRESHOLD = CONFIDENCE_THRESHOLD;
|
|
1330
|
+
exports.EN_MESSAGES = EN_MESSAGES;
|
|
1073
1331
|
exports.ReportCollector = ReportCollector;
|
|
1074
1332
|
exports.UNKNOWN_CAUSE = UNKNOWN_CAUSE;
|
|
1333
|
+
exports.UNKNOWN_NO_LOCATION_CAUSE = UNKNOWN_NO_LOCATION_CAUSE;
|
|
1075
1334
|
exports.buildReport = buildReport;
|
|
1335
|
+
exports.classDetail = classDetail;
|
|
1076
1336
|
exports.classify = classify;
|
|
1337
|
+
exports.code = code;
|
|
1077
1338
|
exports.diffSnapshotAgainstDom = diffSnapshotAgainstDom;
|
|
1078
1339
|
exports.diffTrees = diffTrees;
|
|
1079
1340
|
exports.formatConsoleArgs = formatConsoleArgs;
|
|
1080
1341
|
exports.inspectRoot = inspectRoot;
|
|
1081
1342
|
exports.isDev = isDev;
|
|
1082
1343
|
exports.isHydrationMessage = isHydrationMessage;
|
|
1344
|
+
exports.isInternalComponent = isInternalComponent;
|
|
1345
|
+
exports.isLocationless = isLocationless;
|
|
1346
|
+
exports.param = param;
|
|
1083
1347
|
exports.parseHydrationMessage = parseHydrationMessage;
|
|
1084
1348
|
exports.parseServerHtml = parseServerHtml;
|
|
1349
|
+
exports.plainText = plainText;
|
|
1350
|
+
exports.renderMessage = renderMessage;
|
|
1085
1351
|
exports.reportFromMessage = reportFromMessage;
|
|
1086
1352
|
exports.signatureOf = signatureOf;
|
|
1087
|
-
//# sourceMappingURL=chunk-
|
|
1088
|
-
//# sourceMappingURL=chunk-
|
|
1353
|
+
//# sourceMappingURL=chunk-KX6G7I4Y.cjs.map
|
|
1354
|
+
//# sourceMappingURL=chunk-KX6G7I4Y.cjs.map
|