why-hydration 0.1.5 → 0.3.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 +116 -0
- package/README.md +155 -35
- package/dist/chunk-CER5NQRR.js +1333 -0
- package/dist/chunk-CER5NQRR.js.map +1 -0
- package/dist/{chunk-AS5DZZHI.js → chunk-KP4S6CRL.js} +560 -230
- package/dist/chunk-KP4S6CRL.js.map +1 -0
- package/dist/{chunk-AT6A77X3.cjs → chunk-QFHUKJ5K.cjs} +568 -229
- package/dist/chunk-QFHUKJ5K.cjs.map +1 -0
- package/dist/chunk-RWMRFTQ4.cjs +1356 -0
- package/dist/chunk-RWMRFTQ4.cjs.map +1 -0
- package/dist/index.cjs +25 -17
- package/dist/index.d.cts +37 -6
- package/dist/index.d.ts +37 -6
- package/dist/index.js +1 -1
- package/dist/next/index.cjs +4 -4
- package/dist/next/index.d.cts +1 -1
- package/dist/next/index.d.ts +1 -1
- package/dist/next/index.js +2 -2
- package/dist/react.cjs +4 -4
- package/dist/react.d.cts +24 -3
- package/dist/react.d.ts +24 -3
- package/dist/react.js +2 -2
- 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 +22 -4
- package/dist/chunk-3I6F4K4L.js +0 -642
- package/dist/chunk-3I6F4K4L.js.map +0 -1
- package/dist/chunk-AS5DZZHI.js.map +0 -1
- package/dist/chunk-AT6A77X3.cjs.map +0 -1
- package/dist/chunk-OFVFNPE6.cjs +0 -665
- package/dist/chunk-OFVFNPE6.cjs.map +0 -1
package/dist/chunk-OFVFNPE6.cjs
DELETED
|
@@ -1,665 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var chunkAT6A77X3_cjs = require('./chunk-AT6A77X3.cjs');
|
|
4
|
-
var chunkDXPTZB3Z_cjs = require('./chunk-DXPTZB3Z.cjs');
|
|
5
|
-
var React2 = require('react');
|
|
6
|
-
|
|
7
|
-
function _interopNamespace(e) {
|
|
8
|
-
if (e && e.__esModule) return e;
|
|
9
|
-
var n = Object.create(null);
|
|
10
|
-
if (e) {
|
|
11
|
-
Object.keys(e).forEach(function (k) {
|
|
12
|
-
if (k !== 'default') {
|
|
13
|
-
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
14
|
-
Object.defineProperty(n, k, d.get ? d : {
|
|
15
|
-
enumerable: true,
|
|
16
|
-
get: function () { return e[k]; }
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
n.default = e;
|
|
22
|
-
return Object.freeze(n);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
var React2__namespace = /*#__PURE__*/_interopNamespace(React2);
|
|
26
|
-
|
|
27
|
-
// src/react/console.ts
|
|
28
|
-
function installConsoleInterceptor(onMessage) {
|
|
29
|
-
if (typeof console === "undefined") return () => {
|
|
30
|
-
};
|
|
31
|
-
const original = console.error;
|
|
32
|
-
const patched = (...args) => {
|
|
33
|
-
try {
|
|
34
|
-
const message = chunkAT6A77X3_cjs.formatConsoleArgs(args);
|
|
35
|
-
if (message && chunkAT6A77X3_cjs.isHydrationMessage(message)) {
|
|
36
|
-
onMessage(message, args);
|
|
37
|
-
}
|
|
38
|
-
} catch {
|
|
39
|
-
}
|
|
40
|
-
original.apply(console, args);
|
|
41
|
-
};
|
|
42
|
-
console.error = patched;
|
|
43
|
-
return () => {
|
|
44
|
-
if (console.error === patched) {
|
|
45
|
-
console.error = original;
|
|
46
|
-
}
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
var BADGE = "color:#f59e0b;font-weight:bold";
|
|
50
|
-
var DIM = "color:#9ca3af";
|
|
51
|
-
function createConsoleReporter() {
|
|
52
|
-
const sink = (report) => {
|
|
53
|
-
const title = `%c\u2B21 why-hydration%c ${report.cause.category} \u2014 ${report.node.path}`;
|
|
54
|
-
console.group(title, BADGE, DIM);
|
|
55
|
-
console.log("%cServer:%c", "color:#fb7185", "", report.server ?? "(none)");
|
|
56
|
-
console.log("%cClient:%c", "color:#4ade80", "", report.client ?? "(none)");
|
|
57
|
-
if (report.component) {
|
|
58
|
-
console.log("%cComponent:%c", DIM, "", `<${report.component}>`);
|
|
59
|
-
}
|
|
60
|
-
if (report.location?.file) {
|
|
61
|
-
const loc = report.location.line ? `${report.location.file}:${report.location.line}` : report.location.file;
|
|
62
|
-
console.log("%cSource:%c", DIM, "", loc);
|
|
63
|
-
}
|
|
64
|
-
console.log("%cWhy:%c", DIM, "", report.cause.explanation);
|
|
65
|
-
console.log("%cFix:%c", "color:#86efac", "", report.cause.suggestion);
|
|
66
|
-
if (report.cause.docsUrl) {
|
|
67
|
-
console.log("%cDocs:%c", DIM, "", report.cause.docsUrl);
|
|
68
|
-
}
|
|
69
|
-
if (report.componentStack) {
|
|
70
|
-
console.log("%cComponent stack:%c", DIM, "", report.componentStack);
|
|
71
|
-
}
|
|
72
|
-
console.groupEnd();
|
|
73
|
-
};
|
|
74
|
-
return sink;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// src/react/capture.ts
|
|
78
|
-
var MAX_CAPTURED = 50;
|
|
79
|
-
var captured = /* @__PURE__ */ new Set();
|
|
80
|
-
var listeners = /* @__PURE__ */ new Set();
|
|
81
|
-
var uninstall = null;
|
|
82
|
-
function startCapture() {
|
|
83
|
-
if (uninstall || typeof window === "undefined") return;
|
|
84
|
-
uninstall = installConsoleInterceptor((message) => {
|
|
85
|
-
if (captured.has(message) || captured.size >= MAX_CAPTURED) return;
|
|
86
|
-
captured.add(message);
|
|
87
|
-
for (const listener of [...listeners]) listener(message);
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
function subscribeCapture(listener) {
|
|
91
|
-
startCapture();
|
|
92
|
-
listeners.add(listener);
|
|
93
|
-
for (const message of [...captured]) listener(message);
|
|
94
|
-
return () => {
|
|
95
|
-
if (!listeners.delete(listener)) return;
|
|
96
|
-
if (listeners.size === 0 && uninstall) {
|
|
97
|
-
uninstall();
|
|
98
|
-
uninstall = null;
|
|
99
|
-
}
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// src/react/overlay.ts
|
|
104
|
-
var CONTAINER_ID = "why-hydration-overlay";
|
|
105
|
-
var CATEGORY_LABELS = {
|
|
106
|
-
"non-deterministic-value": "Non-deterministic value",
|
|
107
|
-
"date-time": "Date / time",
|
|
108
|
-
"locale-format": "Locale formatting",
|
|
109
|
-
"browser-only-api": "Browser-only API",
|
|
110
|
-
"viewport-branching": "Viewport branching",
|
|
111
|
-
"invalid-html-nesting": "Invalid HTML nesting",
|
|
112
|
-
"whitespace-minification": "Whitespace / minification",
|
|
113
|
-
"third-party-dom-mutation": "Third-party DOM mutation",
|
|
114
|
-
"attribute-mismatch": "Attribute mismatch",
|
|
115
|
-
unknown: "Unknown"
|
|
116
|
-
};
|
|
117
|
-
var STYLES = `
|
|
118
|
-
:host {
|
|
119
|
-
all: initial;
|
|
120
|
-
/* The "all" shorthand deliberately excludes direction/unicode-bidi (CSS spec),
|
|
121
|
-
so a host page with <html dir="rtl"> would otherwise leak direction:rtl
|
|
122
|
-
into this shadow tree and flip flex/grid order + text alignment. Report
|
|
123
|
-
content is English, so the overlay always renders left-to-right,
|
|
124
|
-
independent of the host page's directionality. */
|
|
125
|
-
direction: ltr;
|
|
126
|
-
unicode-bidi: isolate;
|
|
127
|
-
}
|
|
128
|
-
* { box-sizing: border-box; }
|
|
129
|
-
.wh-panel {
|
|
130
|
-
position: fixed;
|
|
131
|
-
z-index: 2147483647;
|
|
132
|
-
width: min(420px, calc(100vw - 32px));
|
|
133
|
-
max-height: min(70vh, 640px);
|
|
134
|
-
display: flex;
|
|
135
|
-
flex-direction: column;
|
|
136
|
-
font: 13px/1.5 ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
|
|
137
|
-
color: #e5e7eb;
|
|
138
|
-
background: #0b0f17;
|
|
139
|
-
border: 1px solid #1f2937;
|
|
140
|
-
border-radius: 12px;
|
|
141
|
-
box-shadow: 0 12px 40px rgba(0,0,0,.5);
|
|
142
|
-
overflow: hidden;
|
|
143
|
-
direction: ltr;
|
|
144
|
-
text-align: left;
|
|
145
|
-
}
|
|
146
|
-
.wh-bottom-right { bottom: 16px; right: 16px; }
|
|
147
|
-
.wh-bottom-left { bottom: 16px; left: 16px; }
|
|
148
|
-
.wh-top-right { top: 16px; right: 16px; }
|
|
149
|
-
.wh-top-left { top: 16px; left: 16px; }
|
|
150
|
-
.wh-header {
|
|
151
|
-
display: flex; align-items: center; gap: 8px;
|
|
152
|
-
padding: 10px 12px;
|
|
153
|
-
background: linear-gradient(180deg, #151b26, #0d1220);
|
|
154
|
-
border-bottom: 1px solid #1f2937;
|
|
155
|
-
}
|
|
156
|
-
.wh-dot { width: 8px; height: 8px; border-radius: 50%; background: #f59e0b; flex: none; }
|
|
157
|
-
.wh-title { font-weight: 600; color: #f3f4f6; }
|
|
158
|
-
.wh-count {
|
|
159
|
-
margin-left: auto; font-size: 11px; color: #9ca3af;
|
|
160
|
-
background: #111827; border: 1px solid #1f2937; border-radius: 999px;
|
|
161
|
-
padding: 1px 8px;
|
|
162
|
-
}
|
|
163
|
-
.wh-btn {
|
|
164
|
-
appearance: none; border: 1px solid #1f2937; background: #111827;
|
|
165
|
-
color: #9ca3af; border-radius: 6px; cursor: pointer;
|
|
166
|
-
font-size: 12px; padding: 3px 8px;
|
|
167
|
-
}
|
|
168
|
-
.wh-btn:hover { color: #f3f4f6; border-color: #374151; }
|
|
169
|
-
.wh-list { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; padding: 8px; display: flex; flex-direction: column; gap: 8px; }
|
|
170
|
-
.wh-card { flex: 0 0 auto; border: 1px solid #1f2937; border-radius: 10px; background: #0f1521; overflow: hidden; }
|
|
171
|
-
.wh-card-head { display: flex; align-items: center; gap: 8px; padding: 8px 10px; }
|
|
172
|
-
.wh-cat {
|
|
173
|
-
font-weight: 600; color: #fbbf24; font-size: 12px;
|
|
174
|
-
}
|
|
175
|
-
.wh-conf { margin-left: auto; font-size: 11px; color: #6b7280; }
|
|
176
|
-
.wh-body { padding: 0 10px 10px; }
|
|
177
|
-
.wh-where { font-size: 11px; color: #9ca3af; word-break: break-all; margin-bottom: 4px; }
|
|
178
|
-
.wh-where code { color: #93c5fd; }
|
|
179
|
-
.wh-comp { color: #f3f4f6; font-weight: 600; }
|
|
180
|
-
.wh-loc { font-size: 11px; color: #7dd3fc; word-break: break-all; margin-bottom: 8px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
|
|
181
|
-
.wh-diff { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; margin-bottom: 8px; }
|
|
182
|
-
.wh-side { border-radius: 8px; padding: 6px 8px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; word-break: break-word; }
|
|
183
|
-
.wh-server { background: rgba(244,63,94,.08); border: 1px solid rgba(244,63,94,.35); }
|
|
184
|
-
.wh-client { background: rgba(34,197,94,.08); border: 1px solid rgba(34,197,94,.35); }
|
|
185
|
-
.wh-side .wh-label { display: block; font-family: inherit; font-size: 9px; text-transform: uppercase; letter-spacing: .04em; margin-bottom: 3px; }
|
|
186
|
-
.wh-server .wh-label { color: #fb7185; }
|
|
187
|
-
.wh-client .wh-label { color: #4ade80; }
|
|
188
|
-
.wh-explain { color: #cbd5e1; margin-bottom: 8px; }
|
|
189
|
-
.wh-fix { color: #e5e7eb; background: #111827; border-left: 3px solid #22c55e; border-radius: 4px; padding: 6px 8px; }
|
|
190
|
-
.wh-fix strong { color: #86efac; }
|
|
191
|
-
.wh-docs { display: inline-block; margin-top: 6px; color: #60a5fa; text-decoration: none; font-size: 11px; }
|
|
192
|
-
.wh-docs:hover { text-decoration: underline; }
|
|
193
|
-
.wh-empty-value { color: #6b7280; font-style: italic; }
|
|
194
|
-
.wh-hint {
|
|
195
|
-
display: none; align-items: center; gap: 8px;
|
|
196
|
-
padding: 7px 12px; font-size: 11px; color: #cbd5e1;
|
|
197
|
-
background: #0d1220; border-top: 1px solid #1f2937;
|
|
198
|
-
animation: wh-fade .2s ease;
|
|
199
|
-
}
|
|
200
|
-
.wh-hint.wh-show { display: flex; }
|
|
201
|
-
.wh-hint-text { flex: 1; }
|
|
202
|
-
.wh-hint-x {
|
|
203
|
-
appearance: none; border: 0; background: transparent; cursor: pointer;
|
|
204
|
-
color: #9ca3af; font-size: 14px; line-height: 1; padding: 2px 4px;
|
|
205
|
-
}
|
|
206
|
-
.wh-hint-x:hover { color: #f3f4f6; }
|
|
207
|
-
@keyframes wh-fade { from { opacity: 0 } to { opacity: 1 } }
|
|
208
|
-
@media (max-width: 420px) {
|
|
209
|
-
.wh-panel { width: auto; max-height: min(80vh, 640px); }
|
|
210
|
-
.wh-bottom-right, .wh-bottom-left { left: 8px; right: 8px; }
|
|
211
|
-
.wh-top-right, .wh-top-left { left: 8px; right: 8px; }
|
|
212
|
-
.wh-diff { grid-template-columns: 1fr; }
|
|
213
|
-
}
|
|
214
|
-
`;
|
|
215
|
-
function el(tag, className, text) {
|
|
216
|
-
const node = document.createElement(tag);
|
|
217
|
-
if (className) node.className = className;
|
|
218
|
-
if (text != null) node.textContent = text;
|
|
219
|
-
return node;
|
|
220
|
-
}
|
|
221
|
-
function shortenPath(file) {
|
|
222
|
-
const normalized = file.replace(/\\/g, "/");
|
|
223
|
-
const marker = normalized.match(/(?:^|\/)(?:src|app|pages|components)\//);
|
|
224
|
-
if (marker && marker.index != null) {
|
|
225
|
-
return normalized.slice(marker.index).replace(/^\//, "");
|
|
226
|
-
}
|
|
227
|
-
const parts = normalized.split("/");
|
|
228
|
-
return parts.slice(-2).join("/");
|
|
229
|
-
}
|
|
230
|
-
function renderValue(value) {
|
|
231
|
-
if (value == null || value === "") {
|
|
232
|
-
return el("span", "wh-empty-value", value === "" ? "(empty)" : "(none)");
|
|
233
|
-
}
|
|
234
|
-
const span = document.createElement("span");
|
|
235
|
-
span.textContent = value;
|
|
236
|
-
return span;
|
|
237
|
-
}
|
|
238
|
-
function renderCard(report) {
|
|
239
|
-
const card = el("div", "wh-card");
|
|
240
|
-
const head = el("div", "wh-card-head");
|
|
241
|
-
head.appendChild(
|
|
242
|
-
el("span", "wh-cat", CATEGORY_LABELS[report.cause.category] ?? "Mismatch")
|
|
243
|
-
);
|
|
244
|
-
head.appendChild(
|
|
245
|
-
el("span", "wh-conf", `${Math.round(report.cause.confidence * 100)}%`)
|
|
246
|
-
);
|
|
247
|
-
card.appendChild(head);
|
|
248
|
-
const body = el("div", "wh-body");
|
|
249
|
-
const where = el("div", "wh-where");
|
|
250
|
-
if (report.component) {
|
|
251
|
-
where.appendChild(el("strong", "wh-comp", `<${report.component}>`));
|
|
252
|
-
where.append(" \xB7 ");
|
|
253
|
-
} else if (report.node.tagName) {
|
|
254
|
-
where.append(`<${report.node.tagName.toLowerCase()}> \xB7 `);
|
|
255
|
-
}
|
|
256
|
-
const code = el("code");
|
|
257
|
-
code.textContent = report.node.path;
|
|
258
|
-
where.appendChild(code);
|
|
259
|
-
if (report.node.attribute) {
|
|
260
|
-
where.append(` \xB7 @${report.node.attribute}`);
|
|
261
|
-
}
|
|
262
|
-
body.appendChild(where);
|
|
263
|
-
if (report.location?.file) {
|
|
264
|
-
const loc = el("div", "wh-loc");
|
|
265
|
-
const file = shortenPath(report.location.file);
|
|
266
|
-
loc.textContent = report.location.line ? `${file}:${report.location.line}` : file;
|
|
267
|
-
body.appendChild(loc);
|
|
268
|
-
}
|
|
269
|
-
const diff = el("div", "wh-diff");
|
|
270
|
-
const server = el("div", "wh-side wh-server");
|
|
271
|
-
server.appendChild(el("span", "wh-label", "Server"));
|
|
272
|
-
server.appendChild(renderValue(report.server));
|
|
273
|
-
const client = el("div", "wh-side wh-client");
|
|
274
|
-
client.appendChild(el("span", "wh-label", "Client"));
|
|
275
|
-
client.appendChild(renderValue(report.client));
|
|
276
|
-
diff.appendChild(server);
|
|
277
|
-
diff.appendChild(client);
|
|
278
|
-
body.appendChild(diff);
|
|
279
|
-
body.appendChild(el("div", "wh-explain", report.cause.explanation));
|
|
280
|
-
const fix = el("div", "wh-fix");
|
|
281
|
-
const strong = el("strong");
|
|
282
|
-
strong.textContent = "Fix: ";
|
|
283
|
-
fix.appendChild(strong);
|
|
284
|
-
fix.append(report.cause.suggestion);
|
|
285
|
-
body.appendChild(fix);
|
|
286
|
-
if (report.cause.docsUrl && /^https?:\/\//i.test(report.cause.docsUrl)) {
|
|
287
|
-
const a = el("a", "wh-docs", "Learn more \u2192");
|
|
288
|
-
a.href = report.cause.docsUrl;
|
|
289
|
-
a.target = "_blank";
|
|
290
|
-
a.rel = "noreferrer noopener";
|
|
291
|
-
body.appendChild(a);
|
|
292
|
-
}
|
|
293
|
-
card.appendChild(body);
|
|
294
|
-
return card;
|
|
295
|
-
}
|
|
296
|
-
function createOverlay(options = {}) {
|
|
297
|
-
const position = options.position ?? "bottom-right";
|
|
298
|
-
let host = null;
|
|
299
|
-
let listEl = null;
|
|
300
|
-
let countEl = null;
|
|
301
|
-
let hintEl = null;
|
|
302
|
-
let hintTextEl = null;
|
|
303
|
-
let hintCheckTimer = null;
|
|
304
|
-
let hintDismissed = false;
|
|
305
|
-
let count = 0;
|
|
306
|
-
function ensureMounted() {
|
|
307
|
-
if (host) return;
|
|
308
|
-
document.getElementById(CONTAINER_ID)?.remove();
|
|
309
|
-
host = document.createElement("div");
|
|
310
|
-
host.id = CONTAINER_ID;
|
|
311
|
-
host.setAttribute("data-why-hydration", "overlay");
|
|
312
|
-
host.setAttribute("aria-hidden", "false");
|
|
313
|
-
host.setAttribute("dir", "ltr");
|
|
314
|
-
const shadow = host.attachShadow({ mode: "open" });
|
|
315
|
-
const style = document.createElement("style");
|
|
316
|
-
style.textContent = STYLES;
|
|
317
|
-
shadow.appendChild(style);
|
|
318
|
-
const panel = el("div", `wh-panel wh-${position}`);
|
|
319
|
-
panel.setAttribute("role", "dialog");
|
|
320
|
-
panel.setAttribute("aria-label", "Hydration mismatch diagnostics");
|
|
321
|
-
const header = el("div", "wh-header");
|
|
322
|
-
header.appendChild(el("span", "wh-dot"));
|
|
323
|
-
header.appendChild(el("span", "wh-title", "Hydration mismatch"));
|
|
324
|
-
countEl = el("span", "wh-count", "0");
|
|
325
|
-
header.appendChild(countEl);
|
|
326
|
-
const close = el("button", "wh-btn", "Dismiss");
|
|
327
|
-
close.setAttribute("aria-label", "Dismiss diagnostics overlay");
|
|
328
|
-
close.addEventListener("click", () => destroy());
|
|
329
|
-
header.appendChild(close);
|
|
330
|
-
panel.appendChild(header);
|
|
331
|
-
listEl = el("div", "wh-list");
|
|
332
|
-
panel.appendChild(listEl);
|
|
333
|
-
hintEl = el("div", "wh-hint");
|
|
334
|
-
hintTextEl = el("span", "wh-hint-text");
|
|
335
|
-
hintEl.appendChild(hintTextEl);
|
|
336
|
-
const hintX = el("button", "wh-hint-x", "\u2715");
|
|
337
|
-
hintX.setAttribute("aria-label", "Dismiss hint");
|
|
338
|
-
hintX.addEventListener("click", () => dismissHint());
|
|
339
|
-
hintEl.appendChild(hintX);
|
|
340
|
-
panel.appendChild(hintEl);
|
|
341
|
-
shadow.appendChild(panel);
|
|
342
|
-
document.body.appendChild(host);
|
|
343
|
-
host.addEventListener("keydown", (e) => {
|
|
344
|
-
if (e.key === "Escape") destroy();
|
|
345
|
-
});
|
|
346
|
-
}
|
|
347
|
-
function dismissHint() {
|
|
348
|
-
hintDismissed = true;
|
|
349
|
-
hintEl?.classList.remove("wh-show");
|
|
350
|
-
}
|
|
351
|
-
function updateHint() {
|
|
352
|
-
if (!hintEl || !hintTextEl || hintDismissed) return;
|
|
353
|
-
hintTextEl.textContent = `\u2193 ${count} issues \u2014 scroll to see all`;
|
|
354
|
-
if (count >= 4) hintEl.classList.add("wh-show");
|
|
355
|
-
}
|
|
356
|
-
function destroy() {
|
|
357
|
-
if (hintCheckTimer) clearTimeout(hintCheckTimer);
|
|
358
|
-
hintCheckTimer = null;
|
|
359
|
-
host?.remove();
|
|
360
|
-
host = null;
|
|
361
|
-
listEl = null;
|
|
362
|
-
countEl = null;
|
|
363
|
-
hintEl = null;
|
|
364
|
-
hintTextEl = null;
|
|
365
|
-
count = 0;
|
|
366
|
-
hintDismissed = false;
|
|
367
|
-
}
|
|
368
|
-
function push(report) {
|
|
369
|
-
ensureMounted();
|
|
370
|
-
count += 1;
|
|
371
|
-
if (countEl) countEl.textContent = String(count);
|
|
372
|
-
listEl?.appendChild(renderCard(report));
|
|
373
|
-
if (hintCheckTimer) clearTimeout(hintCheckTimer);
|
|
374
|
-
hintCheckTimer = setTimeout(updateHint, 400);
|
|
375
|
-
}
|
|
376
|
-
return { push, destroy };
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
// src/react/fiber.ts
|
|
380
|
-
function getFiber(node) {
|
|
381
|
-
for (const key in node) {
|
|
382
|
-
if (key.startsWith("__reactFiber$") || key.startsWith("__reactInternalInstance$")) {
|
|
383
|
-
return node[key] ?? null;
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
return null;
|
|
387
|
-
}
|
|
388
|
-
function componentName(type) {
|
|
389
|
-
if (!type || typeof type === "string") return void 0;
|
|
390
|
-
if (typeof type === "function") {
|
|
391
|
-
const fn = type;
|
|
392
|
-
return fn.displayName || fn.name || void 0;
|
|
393
|
-
}
|
|
394
|
-
if (typeof type === "object") {
|
|
395
|
-
const obj = type;
|
|
396
|
-
if (obj.displayName) return obj.displayName;
|
|
397
|
-
if (obj.render) return obj.render.displayName || obj.render.name;
|
|
398
|
-
if (obj.type) return componentName(obj.type);
|
|
399
|
-
}
|
|
400
|
-
return void 0;
|
|
401
|
-
}
|
|
402
|
-
function resolveReactSource(node) {
|
|
403
|
-
if (!node) return {};
|
|
404
|
-
try {
|
|
405
|
-
let fiber = getFiber(node);
|
|
406
|
-
let component;
|
|
407
|
-
let location;
|
|
408
|
-
let hops = 0;
|
|
409
|
-
while (fiber && hops < 80) {
|
|
410
|
-
if (!component) {
|
|
411
|
-
const name = componentName(fiber.type);
|
|
412
|
-
if (name && !/^[a-z]/.test(name)) component = name;
|
|
413
|
-
}
|
|
414
|
-
if (!location && fiber._debugSource?.fileName) {
|
|
415
|
-
location = {
|
|
416
|
-
file: fiber._debugSource.fileName,
|
|
417
|
-
line: fiber._debugSource.lineNumber,
|
|
418
|
-
column: fiber._debugSource.columnNumber
|
|
419
|
-
};
|
|
420
|
-
}
|
|
421
|
-
if (component && location) break;
|
|
422
|
-
fiber = fiber.return ?? null;
|
|
423
|
-
hops += 1;
|
|
424
|
-
}
|
|
425
|
-
const result = {};
|
|
426
|
-
if (component) result.component = component;
|
|
427
|
-
if (location) result.location = location;
|
|
428
|
-
return result;
|
|
429
|
-
} catch {
|
|
430
|
-
return {};
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
// src/react/controller.ts
|
|
435
|
-
var INSPECT_FALLBACK_MS = 50;
|
|
436
|
-
function buildIgnore(ignore) {
|
|
437
|
-
if (!ignore || ignore.length === 0) return void 0;
|
|
438
|
-
return (divergence) => {
|
|
439
|
-
const node = divergence.element ?? null;
|
|
440
|
-
for (const rule of ignore) {
|
|
441
|
-
if (typeof rule === "function") {
|
|
442
|
-
if (node && rule(node)) return true;
|
|
443
|
-
} else if (node) {
|
|
444
|
-
try {
|
|
445
|
-
if (node.matches(rule) || node.closest(rule)) return true;
|
|
446
|
-
} catch {
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
return false;
|
|
451
|
-
};
|
|
452
|
-
}
|
|
453
|
-
var InspectorController = class {
|
|
454
|
-
constructor(options = {}) {
|
|
455
|
-
this.cleanups = [];
|
|
456
|
-
this.started = false;
|
|
457
|
-
this.pendingContext = {};
|
|
458
|
-
this.messages = /* @__PURE__ */ new Set();
|
|
459
|
-
this.warnedRoots = /* @__PURE__ */ new Set();
|
|
460
|
-
this.settlingTimers = [];
|
|
461
|
-
this.inspectScheduled = false;
|
|
462
|
-
this.inspectTimer = null;
|
|
463
|
-
this.onRecoverableError = (error, info) => {
|
|
464
|
-
if (!chunkAT6A77X3_cjs.isDev) return;
|
|
465
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
466
|
-
this.rememberMessage(message);
|
|
467
|
-
this.mergeContext({
|
|
468
|
-
componentStack: info?.componentStack,
|
|
469
|
-
component: firstComponentFromStack(info?.componentStack),
|
|
470
|
-
reactMessage: message
|
|
471
|
-
});
|
|
472
|
-
this.scheduleInspect();
|
|
473
|
-
};
|
|
474
|
-
this.options = options;
|
|
475
|
-
this.collector = new chunkAT6A77X3_cjs.ReportCollector({
|
|
476
|
-
maxReports: options.maxReports,
|
|
477
|
-
extra: options.classify,
|
|
478
|
-
ignore: buildIgnore(options?.ignore)
|
|
479
|
-
});
|
|
480
|
-
}
|
|
481
|
-
start() {
|
|
482
|
-
if (this.started || !chunkAT6A77X3_cjs.isDev || typeof window === "undefined") return;
|
|
483
|
-
this.started = true;
|
|
484
|
-
if (this.options.onReport) {
|
|
485
|
-
this.cleanups.push(this.collector.addSink(this.options.onReport));
|
|
486
|
-
}
|
|
487
|
-
this.cleanups.push(this.collector.addSink(createConsoleReporter()));
|
|
488
|
-
if (this.options.overlay !== false) {
|
|
489
|
-
const overlayOpts = typeof this.options.overlay === "object" ? this.options.overlay : {};
|
|
490
|
-
const handle = createOverlay(overlayOpts);
|
|
491
|
-
this.cleanups.push(
|
|
492
|
-
this.collector.addSink(handle.push, { replay: true }),
|
|
493
|
-
() => handle.destroy()
|
|
494
|
-
);
|
|
495
|
-
}
|
|
496
|
-
this.cleanups.push(
|
|
497
|
-
subscribeCapture((message) => {
|
|
498
|
-
this.rememberMessage(message);
|
|
499
|
-
this.mergeContext({ reactMessage: message });
|
|
500
|
-
this.scheduleInspect();
|
|
501
|
-
})
|
|
502
|
-
);
|
|
503
|
-
this.scheduleSettlingInspections();
|
|
504
|
-
}
|
|
505
|
-
// React applies client values to mismatched subtrees via a client re-render a
|
|
506
|
-
// few hundred ms after the initial hydration commit. We diff once per frame
|
|
507
|
-
// and then across this short "settling window" to catch that, deduped. This
|
|
508
|
-
// is a bounded, one-shot window — NOT a standing observer — so DOM changes
|
|
509
|
-
// from later app state are never mistaken for hydration mismatches.
|
|
510
|
-
scheduleSettlingInspections() {
|
|
511
|
-
this.scheduleInspect();
|
|
512
|
-
for (const delay of [80, 250, 700, 1500]) {
|
|
513
|
-
const timer = setTimeout(() => this.inspectAllRoots(), delay);
|
|
514
|
-
this.settlingTimers.push(timer);
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
rememberMessage(message) {
|
|
518
|
-
if (this.messages.size >= MAX_CAPTURED && !this.messages.has(message)) {
|
|
519
|
-
return;
|
|
520
|
-
}
|
|
521
|
-
this.messages.add(message);
|
|
522
|
-
}
|
|
523
|
-
mergeContext(ctx) {
|
|
524
|
-
this.pendingContext = {
|
|
525
|
-
componentStack: ctx.componentStack ?? this.pendingContext.componentStack,
|
|
526
|
-
component: ctx.component ?? this.pendingContext.component,
|
|
527
|
-
location: ctx.location ?? this.pendingContext.location,
|
|
528
|
-
reactMessage: ctx.reactMessage ?? this.pendingContext.reactMessage
|
|
529
|
-
};
|
|
530
|
-
}
|
|
531
|
-
domContext() {
|
|
532
|
-
return {
|
|
533
|
-
componentStack: this.pendingContext.componentStack,
|
|
534
|
-
component: this.pendingContext.component,
|
|
535
|
-
location: this.pendingContext.location
|
|
536
|
-
};
|
|
537
|
-
}
|
|
538
|
-
stop() {
|
|
539
|
-
for (const timer of this.settlingTimers.splice(0)) clearTimeout(timer);
|
|
540
|
-
this.clearScheduledInspect();
|
|
541
|
-
for (const cleanup of this.cleanups.splice(0)) cleanup();
|
|
542
|
-
this.started = false;
|
|
543
|
-
}
|
|
544
|
-
getReports() {
|
|
545
|
-
return this.collector.getReports();
|
|
546
|
-
}
|
|
547
|
-
inspectNow(context = {}) {
|
|
548
|
-
this.mergeContext(context);
|
|
549
|
-
this.inspectAllRoots();
|
|
550
|
-
}
|
|
551
|
-
inspectAllRoots() {
|
|
552
|
-
if (!this.started || typeof document === "undefined" || this.collector.isFull) {
|
|
553
|
-
return;
|
|
554
|
-
}
|
|
555
|
-
const configured = this.options.roots;
|
|
556
|
-
const selectors = configured ?? snapshotSelectors();
|
|
557
|
-
const context = this.domContext();
|
|
558
|
-
const seen = /* @__PURE__ */ new Set();
|
|
559
|
-
for (const selector of selectors) {
|
|
560
|
-
let root = null;
|
|
561
|
-
try {
|
|
562
|
-
root = document.querySelector(selector);
|
|
563
|
-
} catch {
|
|
564
|
-
this.warnRoot(selector, `"${selector}" is not a valid CSS selector.`);
|
|
565
|
-
continue;
|
|
566
|
-
}
|
|
567
|
-
if (!root || seen.has(root)) continue;
|
|
568
|
-
seen.add(root);
|
|
569
|
-
if (configured && chunkDXPTZB3Z_cjs.getServerHtmlForRoot(root) == null) {
|
|
570
|
-
this.warnRoot(
|
|
571
|
-
selector,
|
|
572
|
-
`no server HTML was captured for "${selector}". Add it to the \`selectors\` prop of <HydrationSnapshotScript> so the server markup for that root is snapshotted.`
|
|
573
|
-
);
|
|
574
|
-
continue;
|
|
575
|
-
}
|
|
576
|
-
chunkAT6A77X3_cjs.inspectRoot(
|
|
577
|
-
root,
|
|
578
|
-
this.collector,
|
|
579
|
-
context,
|
|
580
|
-
(divergence) => resolveReactSource(divergence.element ?? null)
|
|
581
|
-
);
|
|
582
|
-
}
|
|
583
|
-
for (const message of this.messages) {
|
|
584
|
-
chunkAT6A77X3_cjs.reportFromMessage(message, this.collector, this.pendingContext);
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
warnRoot(selector, detail) {
|
|
588
|
-
if (this.warnedRoots.has(selector)) return;
|
|
589
|
-
this.warnedRoots.add(selector);
|
|
590
|
-
console.warn(`[why-hydration] Skipping root: ${detail}`);
|
|
591
|
-
}
|
|
592
|
-
scheduleInspect() {
|
|
593
|
-
if (this.inspectScheduled) return;
|
|
594
|
-
this.inspectScheduled = true;
|
|
595
|
-
const run = () => {
|
|
596
|
-
if (!this.inspectScheduled) return;
|
|
597
|
-
this.clearScheduledInspect();
|
|
598
|
-
this.inspectAllRoots();
|
|
599
|
-
};
|
|
600
|
-
if (typeof requestAnimationFrame === "function") requestAnimationFrame(run);
|
|
601
|
-
this.inspectTimer = setTimeout(run, INSPECT_FALLBACK_MS);
|
|
602
|
-
}
|
|
603
|
-
clearScheduledInspect() {
|
|
604
|
-
this.inspectScheduled = false;
|
|
605
|
-
if (this.inspectTimer != null) {
|
|
606
|
-
clearTimeout(this.inspectTimer);
|
|
607
|
-
this.inspectTimer = null;
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
};
|
|
611
|
-
function snapshotSelectors() {
|
|
612
|
-
const snapshot = chunkDXPTZB3Z_cjs.readSnapshot();
|
|
613
|
-
const keys = snapshot ? Object.keys(snapshot.roots) : [];
|
|
614
|
-
return keys.length > 0 ? keys : [...chunkDXPTZB3Z_cjs.DEFAULT_SNAPSHOT_SELECTORS];
|
|
615
|
-
}
|
|
616
|
-
function firstComponentFromStack(stack) {
|
|
617
|
-
if (!stack) return void 0;
|
|
618
|
-
const match = /\n?\s*(?:at|in)\s+([A-Za-z0-9_$]+)/.exec(stack);
|
|
619
|
-
return match?.[1];
|
|
620
|
-
}
|
|
621
|
-
function InspectorImpl(props) {
|
|
622
|
-
const { children, overlay, onReport, ignore, classify, maxReports } = props;
|
|
623
|
-
startCapture();
|
|
624
|
-
const optionsRef = React2__namespace.useRef({});
|
|
625
|
-
optionsRef.current = { overlay, onReport, ignore, classify, maxReports };
|
|
626
|
-
React2__namespace.useEffect(() => {
|
|
627
|
-
const controller = new InspectorController(optionsRef.current);
|
|
628
|
-
controller.start();
|
|
629
|
-
return () => controller.stop();
|
|
630
|
-
}, []);
|
|
631
|
-
return React2__namespace.createElement(React2__namespace.Fragment, null, children);
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
// src/react/index.tsx
|
|
635
|
-
function PassThrough(props) {
|
|
636
|
-
return React2__namespace.createElement(React2__namespace.Fragment, null, props.children);
|
|
637
|
-
}
|
|
638
|
-
var HydrationInspector = process.env.NODE_ENV !== "production" ? InspectorImpl : PassThrough;
|
|
639
|
-
function createHydrationInspector(options = {}) {
|
|
640
|
-
if (process.env.NODE_ENV === "production") {
|
|
641
|
-
return {
|
|
642
|
-
onRecoverableError: () => {
|
|
643
|
-
},
|
|
644
|
-
Provider: PassThrough
|
|
645
|
-
};
|
|
646
|
-
}
|
|
647
|
-
const controller = new InspectorController(options);
|
|
648
|
-
controller.start();
|
|
649
|
-
function Provider(props) {
|
|
650
|
-
React2__namespace.useEffect(() => {
|
|
651
|
-
controller.start();
|
|
652
|
-
return () => controller.stop();
|
|
653
|
-
}, []);
|
|
654
|
-
return React2__namespace.createElement(React2__namespace.Fragment, null, props.children);
|
|
655
|
-
}
|
|
656
|
-
return {
|
|
657
|
-
onRecoverableError: controller.onRecoverableError,
|
|
658
|
-
Provider
|
|
659
|
-
};
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
exports.HydrationInspector = HydrationInspector;
|
|
663
|
-
exports.createHydrationInspector = createHydrationInspector;
|
|
664
|
-
//# sourceMappingURL=chunk-OFVFNPE6.cjs.map
|
|
665
|
-
//# sourceMappingURL=chunk-OFVFNPE6.cjs.map
|