rerender-lens 0.3.0 → 0.5.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 +118 -0
- package/README.md +192 -353
- package/dist/{budget-COBu7jBU.d.cts → budget-BnJlYwrV.d.ts} +20 -10
- package/dist/{budget-LkNjGtRc.d.ts → budget-CRswn-nC.d.cts} +20 -10
- package/dist/cli.cjs +248 -31
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +248 -31
- package/dist/cli.js.map +1 -1
- package/dist/{devtools-BHGUUo-p.d.ts → devtools-BYWat7nQ.d.ts} +17 -3
- package/dist/{devtools-DmhWiWcN.d.cts → devtools-DJi_6AfJ.d.cts} +17 -3
- package/dist/index.cjs +635 -109
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +131 -8
- package/dist/index.d.ts +131 -8
- package/dist/index.js +620 -110
- package/dist/index.js.map +1 -1
- package/dist/jest-setup.cjs +1673 -0
- package/dist/jest-setup.cjs.map +1 -0
- package/dist/jest-setup.d.cts +8 -0
- package/dist/jest-setup.d.ts +8 -0
- package/dist/jest-setup.js +1671 -0
- package/dist/jest-setup.js.map +1 -0
- package/dist/jest.cjs +1760 -0
- package/dist/jest.cjs.map +1 -0
- package/dist/jest.d.cts +23 -0
- package/dist/jest.d.ts +23 -0
- package/dist/jest.js +1751 -0
- package/dist/jest.js.map +1 -0
- package/dist/{notifiers-BjjGSHpp.d.ts → notifiers-DwKlvv4H.d.cts} +8 -4
- package/dist/{notifiers-BGQtWKfX.d.cts → notifiers-FNzKHs0a.d.ts} +8 -4
- package/dist/playwright.cjs +235 -30
- package/dist/playwright.cjs.map +1 -1
- package/dist/playwright.d.cts +8 -6
- package/dist/playwright.d.ts +8 -6
- package/dist/playwright.js +235 -31
- package/dist/playwright.js.map +1 -1
- package/dist/relay.cjs +10 -2
- package/dist/relay.cjs.map +1 -1
- package/dist/relay.js +10 -2
- package/dist/relay.js.map +1 -1
- package/dist/rerender-lens.iife.js +1100 -337
- package/dist/runner-report-B8Sc3FZO.d.cts +46 -0
- package/dist/runner-report-CW8X5RwK.d.ts +46 -0
- package/dist/setup.cjs +308 -95
- package/dist/setup.cjs.map +1 -1
- package/dist/setup.js +308 -95
- package/dist/setup.js.map +1 -1
- package/dist/{types-BzUEVkxJ.d.cts → types-DW5-N2XH.d.cts} +55 -2
- package/dist/{types-BzUEVkxJ.d.ts → types-DW5-N2XH.d.ts} +55 -2
- package/dist/vite.d.cts +1 -1
- package/dist/vite.d.ts +1 -1
- package/dist/vitest-setup.cjs +452 -87
- package/dist/vitest-setup.cjs.map +1 -1
- package/dist/vitest-setup.d.cts +4 -4
- package/dist/vitest-setup.d.ts +4 -4
- package/dist/vitest-setup.js +452 -87
- package/dist/vitest-setup.js.map +1 -1
- package/dist/vitest.cjs +515 -123
- package/dist/vitest.cjs.map +1 -1
- package/dist/vitest.d.cts +21 -39
- package/dist/vitest.d.ts +21 -39
- package/dist/vitest.js +516 -124
- package/dist/vitest.js.map +1 -1
- package/package.json +41 -5
- package/panel/panel.css +1 -34
- package/panel/panel.js +865 -713
package/dist/index.js
CHANGED
|
@@ -26,10 +26,49 @@ function remember(seen, a, b) {
|
|
|
26
26
|
set.add(b);
|
|
27
27
|
return false;
|
|
28
28
|
}
|
|
29
|
+
var scope = null;
|
|
30
|
+
var DEFAULT_DIFF_BUDGET = 5e4;
|
|
31
|
+
function beginDiffScope(budget = 2e5) {
|
|
32
|
+
const previous = scope;
|
|
33
|
+
scope = { cache: /* @__PURE__ */ new WeakMap(), remaining: budget, exhausted: false };
|
|
34
|
+
return () => {
|
|
35
|
+
scope = previous;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function diffBudgetExhausted() {
|
|
39
|
+
return scope?.exhausted === true;
|
|
40
|
+
}
|
|
29
41
|
function deepEqual(a, b, seen = /* @__PURE__ */ new Map()) {
|
|
30
42
|
if (Object.is(a, b)) return true;
|
|
31
43
|
if (typeof a !== typeof b) return false;
|
|
32
44
|
if (typeof a !== "object" || a === null || b === null) return false;
|
|
45
|
+
if (!scope) {
|
|
46
|
+
const end = beginDiffScope(DEFAULT_DIFF_BUDGET);
|
|
47
|
+
try {
|
|
48
|
+
return deepEqual(a, b, seen);
|
|
49
|
+
} finally {
|
|
50
|
+
end();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const sc = scope;
|
|
54
|
+
const objA = a;
|
|
55
|
+
const objB = b;
|
|
56
|
+
const hit = sc.cache.get(objA)?.get(objB);
|
|
57
|
+
if (hit !== void 0) return hit;
|
|
58
|
+
if (--sc.remaining < 0) {
|
|
59
|
+
sc.exhausted = true;
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
const result = deepEqualObjects(objA, objB, seen);
|
|
63
|
+
let m = sc.cache.get(objA);
|
|
64
|
+
if (!m) {
|
|
65
|
+
m = /* @__PURE__ */ new WeakMap();
|
|
66
|
+
sc.cache.set(objA, m);
|
|
67
|
+
}
|
|
68
|
+
m.set(objB, result);
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
function deepEqualObjects(a, b, seen) {
|
|
33
72
|
const objA = a;
|
|
34
73
|
const objB = b;
|
|
35
74
|
if (remember(seen, objA, objB)) return true;
|
|
@@ -58,6 +97,10 @@ function deepEqual(a, b, seen = /* @__PURE__ */ new Map()) {
|
|
|
58
97
|
}
|
|
59
98
|
if (a instanceof Set) {
|
|
60
99
|
if (!(b instanceof Set) || a.size !== b.size) return false;
|
|
100
|
+
if (a.size > 50) {
|
|
101
|
+
for (const v of a) if (!b.has(v)) return false;
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
61
104
|
outer: for (const v of a) {
|
|
62
105
|
if (b.has(v)) continue;
|
|
63
106
|
for (const w of b) if (deepEqual(v, w, seen)) continue outer;
|
|
@@ -73,14 +116,16 @@ function deepEqual(a, b, seen = /* @__PURE__ */ new Map()) {
|
|
|
73
116
|
const ka = Object.keys(a);
|
|
74
117
|
const kb = Object.keys(b);
|
|
75
118
|
if (ka.length !== kb.length) return false;
|
|
119
|
+
const rb = b;
|
|
76
120
|
for (const k of ka) {
|
|
77
121
|
if (!Object.prototype.hasOwnProperty.call(b, k)) return false;
|
|
78
|
-
if (!deepEqual(a[k],
|
|
122
|
+
if (!deepEqual(a[k], rb[k], seen)) return false;
|
|
79
123
|
}
|
|
80
124
|
return true;
|
|
81
125
|
}
|
|
82
126
|
return false;
|
|
83
127
|
}
|
|
128
|
+
var AVOIDABLE_KINDS = /* @__PURE__ */ new Set(["deep-equal", "function", "element"]);
|
|
84
129
|
function classify(prev, next) {
|
|
85
130
|
if (typeof prev === "function" && typeof next === "function") {
|
|
86
131
|
return prev.name === next.name && prev.toString() === next.toString() ? "function" : "different";
|
|
@@ -117,30 +162,47 @@ function diffRecords(prev, next, basePath = "") {
|
|
|
117
162
|
if (Object.is(a, b)) continue;
|
|
118
163
|
const kind = classify(a, b);
|
|
119
164
|
if (kind === "different") {
|
|
120
|
-
changes.push({ path: firstDifferentPath(a, b, path), kind, prev: a, next: b });
|
|
165
|
+
changes.push({ path: diffBudgetExhausted() ? path : firstDifferentPath(a, b, path) ?? path, kind, prev: a, next: b });
|
|
121
166
|
} else {
|
|
122
167
|
changes.push({ path, kind, prev: a, next: b });
|
|
123
168
|
}
|
|
124
169
|
}
|
|
125
170
|
return changes;
|
|
126
171
|
}
|
|
127
|
-
function firstDifferentPath(a, b, path, depth = 0) {
|
|
128
|
-
if (depth > 8) return path;
|
|
172
|
+
function firstDifferentPath(a, b, path = "", depth = 0) {
|
|
173
|
+
if (depth > 8) return path || "(value)";
|
|
129
174
|
if (Array.isArray(a) && Array.isArray(b)) {
|
|
130
|
-
if (a.length !== b.length) return `${path}.length
|
|
175
|
+
if (a.length !== b.length) return path ? `${path}.length` : "length";
|
|
131
176
|
for (let i = 0; i < a.length; i++) {
|
|
132
177
|
if (!deepEqual(a[i], b[i])) return firstDifferentPath(a[i], b[i], joinPath(path, i), depth + 1);
|
|
133
178
|
}
|
|
134
|
-
return
|
|
179
|
+
return null;
|
|
135
180
|
}
|
|
136
181
|
if (isPlainObject(a) && isPlainObject(b)) {
|
|
137
182
|
const keys = /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)]);
|
|
138
183
|
for (const k of keys) {
|
|
139
184
|
if (!deepEqual(a[k], b[k])) return firstDifferentPath(a[k], b[k], joinPath(path, k), depth + 1);
|
|
140
185
|
}
|
|
141
|
-
return
|
|
186
|
+
return null;
|
|
142
187
|
}
|
|
143
|
-
return path;
|
|
188
|
+
return deepEqual(a, b) ? null : path || "(value)";
|
|
189
|
+
}
|
|
190
|
+
function diffLeaves(a, b, limit = 20, path = "", out = []) {
|
|
191
|
+
if (out.length >= limit || Object.is(a, b)) return out;
|
|
192
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
193
|
+
const n = Math.max(a.length, b.length);
|
|
194
|
+
for (let i = 0; i < n && out.length < limit; i++) diffLeaves(a[i], b[i], limit, joinPath(path, i), out);
|
|
195
|
+
return out;
|
|
196
|
+
}
|
|
197
|
+
if (isPlainObject(a) && isPlainObject(b)) {
|
|
198
|
+
for (const k of /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)])) {
|
|
199
|
+
if (out.length >= limit) break;
|
|
200
|
+
diffLeaves(a[k], b[k], limit, joinPath(path, k), out);
|
|
201
|
+
}
|
|
202
|
+
return out;
|
|
203
|
+
}
|
|
204
|
+
if (!deepEqual(a, b)) out.push({ path: path || "(value)", prev: a, next: b });
|
|
205
|
+
return out;
|
|
144
206
|
}
|
|
145
207
|
|
|
146
208
|
// src/report.ts
|
|
@@ -158,10 +220,12 @@ function storeAdvice(custom) {
|
|
|
158
220
|
}
|
|
159
221
|
var isGenuine = (c) => c.kind === "different" || c.kind === "added" || c.kind === "removed";
|
|
160
222
|
var isChildren = (change) => change.path === "children";
|
|
223
|
+
var isRefObject = (change) => change.path === "ref" && change.kind === "deep-equal";
|
|
161
224
|
function fixFor(change) {
|
|
162
225
|
if (isChildren(change) && (change.kind === "element" || change.kind === "deep-equal")) {
|
|
163
226
|
return `lift the children out of the parent's render: memoize them with useMemo, hoist static elements to module scope, or render them from a component that does not re-render`;
|
|
164
227
|
}
|
|
228
|
+
if (isRefObject(change)) return `create the ref once with useRef (or createRef outside the render) and pass the same object every time`;
|
|
165
229
|
switch (change.kind) {
|
|
166
230
|
case "function":
|
|
167
231
|
return `wrap it in useCallback (or hoist it out of the parent's render)`;
|
|
@@ -177,6 +241,7 @@ function describe(change) {
|
|
|
177
241
|
if (isChildren(change) && (change.kind === "element" || change.kind === "deep-equal")) {
|
|
178
242
|
return `children are new React elements with the same types and props on every render of the parent`;
|
|
179
243
|
}
|
|
244
|
+
if (isRefObject(change)) return `prop "ref" is a new ref object on every render (React re-attaches it each time)`;
|
|
180
245
|
switch (change.kind) {
|
|
181
246
|
case "deep-equal":
|
|
182
247
|
return `prop "${change.path}" is a new reference but deep-equal to the previous value`;
|
|
@@ -204,11 +269,21 @@ function buildReport(input) {
|
|
|
204
269
|
if (causes.length === 0) trigger = "parent";
|
|
205
270
|
else if (causes.length === 1) trigger = causes[0];
|
|
206
271
|
else trigger = "mixed";
|
|
207
|
-
const
|
|
272
|
+
const nothingChanged = input.propChanges.length === 0 && stateChanges.length === 0 && hookChanges.length === 0;
|
|
273
|
+
const cacheHit = input.compiled === true && trigger === "parent" && input.propChanges.length === 0;
|
|
274
|
+
const revealed = input.revealed === true && trigger === "parent";
|
|
275
|
+
const avoidable = trigger === "parent" && !cacheHit && !revealed;
|
|
208
276
|
const reasons = [];
|
|
209
|
-
|
|
277
|
+
const memoFix = input.classComponent ? `Make "${input.component}" extend PureComponent (or implement shouldComponentUpdate)` : `Wrap "${input.component}" in React.memo (or extend PureComponent)`;
|
|
278
|
+
if (cacheHit) {
|
|
279
|
+
reasons.push(`compiled by React Compiler: its output is memoized and the inputs did not change, so this render is cheap; nothing to fix.`);
|
|
280
|
+
} else if (revealed) {
|
|
281
|
+
reasons.push(
|
|
282
|
+
`re-rendered because the Suspense boundary above it switched from its fallback to content: React re-renders the content it kept hidden while the fallback was shown. A one-off; nothing to fix.`
|
|
283
|
+
);
|
|
284
|
+
} else if (avoidable && nothingChanged) {
|
|
210
285
|
const who = input.parent ? `<${input.parent.name}> re-rendered (${describeTrigger(input.parent.trigger)})` : "its parent re-rendered";
|
|
211
|
-
reasons.push(`re-rendered with identical props because ${who}.
|
|
286
|
+
reasons.push(`re-rendered with identical props because ${who}. ${memoFix}.`);
|
|
212
287
|
} else if (avoidable && input.parent) {
|
|
213
288
|
reasons.push(`caused by <${input.parent.name}> re-rendering (${describeTrigger(input.parent.trigger)}).`);
|
|
214
289
|
}
|
|
@@ -217,9 +292,9 @@ function buildReport(input) {
|
|
|
217
292
|
reasons.push(fix ? `${describe(c)}: ${fix}.` : `${describe(c)}.`);
|
|
218
293
|
}
|
|
219
294
|
const memoized = input.memoized !== false;
|
|
220
|
-
if (avoidable && !memoized && input.propChanges.length > 0) {
|
|
295
|
+
if (avoidable && !memoized && !input.compiled && input.propChanges.length > 0) {
|
|
221
296
|
reasons.push(
|
|
222
|
-
`"${input.component}" is not memoized, so fixing the props alone will not stop this re-render: also wrap it in React.memo (or extend PureComponent).`
|
|
297
|
+
`"${input.component}" is not memoized, so fixing the props alone will not stop this re-render: also ${input.classComponent ? "extend PureComponent (or implement shouldComponentUpdate)" : "wrap it in React.memo (or extend PureComponent)"}.`
|
|
223
298
|
);
|
|
224
299
|
}
|
|
225
300
|
for (const c of stateChanges) {
|
|
@@ -231,6 +306,8 @@ function buildReport(input) {
|
|
|
231
306
|
const where = c.provider && c.provider.component ? ` (provided by <${c.provider.component}>)` : "";
|
|
232
307
|
const keys = c.changedKeys && typeof c.totalKeys === "number" && c.changedKeys.length > 0 && c.changedKeys.length < c.totalKeys ? `: only ${c.changedKeys.map((k) => `"${k}"`).join(", ")} of ${c.totalKeys} keys changed, yet every consumer re-renders. Split the context or memoize the slices consumers read` : "";
|
|
233
308
|
reasons.push(`${c.path} changed${where}${keys}.`);
|
|
309
|
+
} else if (c.hook === "useDeferredValue" && isGenuine(c)) {
|
|
310
|
+
reasons.push(`${hookLabel(c)} caught up with its latest value: this is the deferred second render React schedules after the urgent one; nothing to fix.`);
|
|
234
311
|
} else if (isGenuine(c)) reasons.push(`${hookLabel(c)} changed.`);
|
|
235
312
|
else if (isStateHook(c))
|
|
236
313
|
reasons.push(`${hookLabel(c)} was set to a value deep-equal to the current one (new reference, same contents): reuse the existing object or bail out before calling the setter.`);
|
|
@@ -261,6 +338,7 @@ function buildReport(input) {
|
|
|
261
338
|
reasons,
|
|
262
339
|
time: now()
|
|
263
340
|
};
|
|
341
|
+
if (input.compiled) report.compiled = true;
|
|
264
342
|
if (input.selfDuration !== void 0) report.selfDuration = input.selfDuration;
|
|
265
343
|
if (input.treeDuration !== void 0) report.treeDuration = input.treeDuration;
|
|
266
344
|
if (input.commitPriority) report.commitPriority = input.commitPriority;
|
|
@@ -298,10 +376,10 @@ var KIND_LABEL = {
|
|
|
298
376
|
};
|
|
299
377
|
function summarize(report) {
|
|
300
378
|
const counts2 = /* @__PURE__ */ new Map();
|
|
301
|
-
for (const c of [...report.propChanges, ...report.stateChanges, ...report.hookChanges]) {
|
|
379
|
+
for (const c of [...report.propChanges || [], ...report.stateChanges || [], ...report.hookChanges || []]) {
|
|
302
380
|
counts2.set(c.kind, (counts2.get(c.kind) ?? 0) + 1);
|
|
303
381
|
}
|
|
304
|
-
const parts = [...counts2].map(([kind, n]) => `${n} ${KIND_LABEL[kind]}`);
|
|
382
|
+
const parts = [...counts2].map(([kind, n]) => `${n} ${KIND_LABEL[kind] || kind}`);
|
|
305
383
|
if (parts.length === 0) parts.push("no changes");
|
|
306
384
|
return parts.join(", ");
|
|
307
385
|
}
|
|
@@ -328,7 +406,7 @@ function getState() {
|
|
|
328
406
|
const g = globalThis;
|
|
329
407
|
let s = g[KEY];
|
|
330
408
|
if (!s) {
|
|
331
|
-
s = { options: {}, enabled: false, printed: /* @__PURE__ */ new Map(), detach: null, nextInstanceId: 1, nextCommitId: 1, warnedOnce: /* @__PURE__ */ new Set(), scheduled: 0, commits: 0, overheadMs: 0, maxCommitMs: 0 };
|
|
409
|
+
s = { options: {}, enabled: false, printed: /* @__PURE__ */ new Map(), detach: null, nextInstanceId: 1, nextCommitId: 1, warnedOnce: /* @__PURE__ */ new Set(), scheduled: 0, commits: 0, overheadMs: 0, maxCommitMs: 0, truncated: 0 };
|
|
332
410
|
g[KEY] = s;
|
|
333
411
|
}
|
|
334
412
|
return s;
|
|
@@ -358,12 +436,33 @@ function dispatch(report, override) {
|
|
|
358
436
|
try {
|
|
359
437
|
options.notifier(report);
|
|
360
438
|
} catch (err) {
|
|
361
|
-
(
|
|
439
|
+
warnOnce("notifier", `notifier threw: ${String(err)}`);
|
|
362
440
|
}
|
|
363
441
|
}
|
|
364
442
|
}
|
|
365
443
|
|
|
366
444
|
// src/hookNames.ts
|
|
445
|
+
var NODE_COUNT = {
|
|
446
|
+
useState: 1,
|
|
447
|
+
useReducer: 1,
|
|
448
|
+
useRef: 1,
|
|
449
|
+
useMemo: 1,
|
|
450
|
+
useCallback: 1,
|
|
451
|
+
useEffect: 1,
|
|
452
|
+
useLayoutEffect: 1,
|
|
453
|
+
useInsertionEffect: 1,
|
|
454
|
+
useImperativeHandle: 1,
|
|
455
|
+
useSyncExternalStore: 2,
|
|
456
|
+
useTransition: 2,
|
|
457
|
+
useDeferredValue: 1,
|
|
458
|
+
useId: 1,
|
|
459
|
+
useOptimistic: 1,
|
|
460
|
+
useActionState: 3,
|
|
461
|
+
useFormState: 3,
|
|
462
|
+
useEffectEvent: 1,
|
|
463
|
+
useCacheRefresh: 1
|
|
464
|
+
};
|
|
465
|
+
var BUILTIN_HOOKS = /* @__PURE__ */ new Set([...Object.keys(NODE_COUNT), "useContext", "useDebugValue", "use"]);
|
|
367
466
|
var noop = () => {
|
|
368
467
|
};
|
|
369
468
|
function customHooksFromStack(stack, componentName, marker) {
|
|
@@ -378,7 +477,7 @@ function customHooksFromStack(stack, componentName, marker) {
|
|
|
378
477
|
continue;
|
|
379
478
|
}
|
|
380
479
|
if (name === componentName || name.endsWith("." + componentName)) break;
|
|
381
|
-
if (/^use[A-Z0-9_]/.test(name)) names.push(name);
|
|
480
|
+
if (/^use[A-Z0-9_]/.test(name) && !BUILTIN_HOOKS.has(name)) names.push(name);
|
|
382
481
|
if (names.length > 8) break;
|
|
383
482
|
}
|
|
384
483
|
return names;
|
|
@@ -678,6 +777,11 @@ function isMemoizedFiber(fiber) {
|
|
|
678
777
|
}
|
|
679
778
|
return false;
|
|
680
779
|
}
|
|
780
|
+
function isCompiledFiber(fiber) {
|
|
781
|
+
if (fiber.tag === ClassComponent) return false;
|
|
782
|
+
const cache2 = fiber.updateQueue?.memoCache;
|
|
783
|
+
return !!cache2 && Array.isArray(cache2.data);
|
|
784
|
+
}
|
|
681
785
|
function durationsOf(fiber) {
|
|
682
786
|
if (typeof fiber.actualDuration !== "number") return null;
|
|
683
787
|
const tree = fiber.actualDuration;
|
|
@@ -701,11 +805,14 @@ var counts = /* @__PURE__ */ new WeakMap();
|
|
|
701
805
|
var ids = /* @__PURE__ */ new WeakMap();
|
|
702
806
|
var fibersById = /* @__PURE__ */ new Map();
|
|
703
807
|
var hasWeakRef = typeof WeakRef === "function";
|
|
808
|
+
var pruneAt = 5e3;
|
|
704
809
|
function remember2(id, fiber) {
|
|
705
810
|
if (!hasWeakRef) return;
|
|
811
|
+
if (fibersById.get(id)?.deref() === fiber) return;
|
|
706
812
|
fibersById.set(id, new WeakRef(fiber));
|
|
707
|
-
if (fibersById.size >
|
|
813
|
+
if (fibersById.size > pruneAt) {
|
|
708
814
|
for (const [k, ref] of fibersById) if (!ref.deref()) fibersById.delete(k);
|
|
815
|
+
pruneAt = Math.max(5e3, fibersById.size * 2);
|
|
709
816
|
}
|
|
710
817
|
}
|
|
711
818
|
function instanceIdOf(fiber) {
|
|
@@ -760,7 +867,7 @@ function nearestComponent(fiber) {
|
|
|
760
867
|
}
|
|
761
868
|
var INTERNAL_FRAME = /react-dom|react_jsx|jsx-(dev-)?runtime|\/react\/|node_modules\/react|react-stack-bottom-frame|react_stack_bottom_frame|scheduler/;
|
|
762
869
|
function parseStackLocation(stack) {
|
|
763
|
-
for (const line of stack.split("\n")) {
|
|
870
|
+
for (const line of stack.split("\n", 24)) {
|
|
764
871
|
const m = /(?:at\s+(?:.*?\s+)?\(?|@)?((?:https?|file|webpack|vite|blob):[^\s()]+?):(\d+):(\d+)\)?\s*$/.exec(line.trim());
|
|
765
872
|
if (!m || !m[1] || INTERNAL_FRAME.test(m[1])) continue;
|
|
766
873
|
return { fileName: m[1], lineNumber: Number(m[2]), columnNumber: Number(m[3]) };
|
|
@@ -776,9 +883,16 @@ function sourceOf(fiber) {
|
|
|
776
883
|
return out;
|
|
777
884
|
}
|
|
778
885
|
const st = fiber._debugStack;
|
|
779
|
-
|
|
780
|
-
|
|
886
|
+
if (st && typeof st === "object") {
|
|
887
|
+
if (sourceCache.has(st)) return sourceCache.get(st);
|
|
888
|
+
const text = typeof st.stack === "string" ? st.stack : null;
|
|
889
|
+
const out = text ? parseStackLocation(text) : void 0;
|
|
890
|
+
sourceCache.set(st, out);
|
|
891
|
+
return out;
|
|
892
|
+
}
|
|
893
|
+
return typeof st === "string" ? parseStackLocation(st) : void 0;
|
|
781
894
|
}
|
|
895
|
+
var sourceCache = /* @__PURE__ */ new WeakMap();
|
|
782
896
|
function bumpCount(fiber) {
|
|
783
897
|
const prev = counts.get(fiber) ?? (fiber.alternate ? counts.get(fiber.alternate) : void 0) ?? 0;
|
|
784
898
|
const n = prev + 1;
|
|
@@ -846,7 +960,8 @@ function diffHooks(fiber, alt) {
|
|
|
846
960
|
const labels = hookLabelsFor(fiber, b);
|
|
847
961
|
let i = 0;
|
|
848
962
|
while (a && b) {
|
|
849
|
-
|
|
963
|
+
const deferred = labels?.[i] === "useDeferredValue";
|
|
964
|
+
if ((deferred || isStateNode(b) || isStoreNode(b)) && !Object.is(a.memoizedState, b.memoizedState)) {
|
|
850
965
|
const hook = labels?.[i] ?? hookLabel2(b);
|
|
851
966
|
out.push({
|
|
852
967
|
path: `${hook}#${i}`,
|
|
@@ -880,6 +995,10 @@ function diffContexts(fiber, alt) {
|
|
|
880
995
|
let a = alt.dependencies?.firstContext ?? null;
|
|
881
996
|
let b = fiber.dependencies?.firstContext ?? null;
|
|
882
997
|
let i = 0;
|
|
998
|
+
if (b && !("memoizedValue" in b)) {
|
|
999
|
+
warnOnce("contextValues", "this React does not record context values on the fiber (React < 18): context changes are reported as plain parent re-renders.");
|
|
1000
|
+
return out;
|
|
1001
|
+
}
|
|
883
1002
|
while (a && b) {
|
|
884
1003
|
if (a.context === b.context && !Object.is(a.memoizedValue, b.memoizedValue)) {
|
|
885
1004
|
const name = b.context.displayName ?? "Context";
|
|
@@ -912,10 +1031,26 @@ function diffContexts(fiber, alt) {
|
|
|
912
1031
|
return out;
|
|
913
1032
|
}
|
|
914
1033
|
var isGenuine2 = (c) => c.kind === "different" || c.kind === "added" || c.kind === "removed";
|
|
1034
|
+
var isRefObject2 = (v) => {
|
|
1035
|
+
if (typeof v !== "object" || v === null) return false;
|
|
1036
|
+
const keys = Object.keys(v);
|
|
1037
|
+
return keys.length === 1 && keys[0] === "current";
|
|
1038
|
+
};
|
|
1039
|
+
function diffProps(prev, next) {
|
|
1040
|
+
if (prev === next) return [];
|
|
1041
|
+
const pr = prev.ref;
|
|
1042
|
+
const nr = next.ref;
|
|
1043
|
+
if (pr !== nr && isRefObject2(pr) && isRefObject2(nr)) {
|
|
1044
|
+
const { ref: _p, ...restPrev } = prev;
|
|
1045
|
+
const { ref: _n, ...restNext } = next;
|
|
1046
|
+
return [{ path: "ref", kind: "deep-equal", prev: pr, next: nr }, ...diffRecords(restPrev, restNext)];
|
|
1047
|
+
}
|
|
1048
|
+
return diffRecords(prev, next);
|
|
1049
|
+
}
|
|
915
1050
|
function analyze(fiber, alt, trackHooks) {
|
|
916
1051
|
const prevProps = alt.memoizedProps ?? {};
|
|
917
1052
|
const nextProps = fiber.memoizedProps ?? {};
|
|
918
|
-
const propChanges =
|
|
1053
|
+
const propChanges = diffProps(prevProps, nextProps);
|
|
919
1054
|
let stateChanges = [];
|
|
920
1055
|
let hookChanges = [];
|
|
921
1056
|
if (fiber.tag === ClassComponent) {
|
|
@@ -928,7 +1063,7 @@ function analyze(fiber, alt, trackHooks) {
|
|
|
928
1063
|
} else if (trackHooks) {
|
|
929
1064
|
hookChanges = [...diffHooks(fiber, alt), ...diffContexts(fiber, alt)];
|
|
930
1065
|
}
|
|
931
|
-
const isStateHook = (h) => h.hook !== "useContext" && h.hook !== "useSyncExternalStore";
|
|
1066
|
+
const isStateHook = (h) => h.hook !== "useContext" && h.hook !== "useSyncExternalStore" && h.hook !== "useDeferredValue";
|
|
932
1067
|
const causes = [];
|
|
933
1068
|
if (propChanges.some(isGenuine2)) causes.push("props");
|
|
934
1069
|
if (stateChanges.some(isGenuine2) || hookChanges.some((h) => isStateHook(h) && isGenuine2(h))) causes.push("state");
|
|
@@ -966,6 +1101,26 @@ function updatersOf(root) {
|
|
|
966
1101
|
}
|
|
967
1102
|
var lastCommit = null;
|
|
968
1103
|
var EFFECT_LOOP_WINDOW_MS = 50;
|
|
1104
|
+
var MAX_REPORTS_PER_COMMIT = 200;
|
|
1105
|
+
var COMMIT_TIME_BUDGET_MS = 25;
|
|
1106
|
+
function isDeferredCatchUp(fiber) {
|
|
1107
|
+
const types = fiber._debugHookTypes;
|
|
1108
|
+
if (!types || !types.includes("useDeferredValue") || !fiber.alternate) return false;
|
|
1109
|
+
const first = fiber.memoizedState;
|
|
1110
|
+
if (!isHookList(first)) return false;
|
|
1111
|
+
const labels = hookLabelsFor(fiber, first);
|
|
1112
|
+
if (!labels) return false;
|
|
1113
|
+
let a = fiber.alternate.memoizedState;
|
|
1114
|
+
let b = first;
|
|
1115
|
+
for (let i = 0; a && b; a = a.next, b = b.next, i++) {
|
|
1116
|
+
if (labels[i] === "useDeferredValue" && !Object.is(a.memoizedState, b.memoizedState)) return true;
|
|
1117
|
+
}
|
|
1118
|
+
return false;
|
|
1119
|
+
}
|
|
1120
|
+
function isUnder(fiber, boundaries) {
|
|
1121
|
+
for (let f = fiber.return; f && f.tag !== HostRoot; f = f.return) if (f.tag === SuspenseComponent && boundaries.includes(f)) return true;
|
|
1122
|
+
return false;
|
|
1123
|
+
}
|
|
969
1124
|
function onCommit(root, commitPriority) {
|
|
970
1125
|
const s = getState();
|
|
971
1126
|
if (!s.enabled) return;
|
|
@@ -975,7 +1130,7 @@ function onCommit(root, commitPriority) {
|
|
|
975
1130
|
const rendered = [];
|
|
976
1131
|
const renderedNames = /* @__PURE__ */ new Set();
|
|
977
1132
|
let hot = false;
|
|
978
|
-
let
|
|
1133
|
+
let resolvedBoundaries = null;
|
|
979
1134
|
const stack = [root.current];
|
|
980
1135
|
while (stack.length) {
|
|
981
1136
|
const fiber = stack.pop();
|
|
@@ -985,7 +1140,7 @@ function onCommit(root, commitPriority) {
|
|
|
985
1140
|
renderedNames.add(fiberName(fiber));
|
|
986
1141
|
if (shouldTrack(fiberType(fiber), o)) rendered.push(fiber);
|
|
987
1142
|
}
|
|
988
|
-
if (fiber.tag === SuspenseComponent && alt && alt.memoizedState !== null && fiber.memoizedState === null)
|
|
1143
|
+
if (fiber.tag === SuspenseComponent && alt && alt.memoizedState !== null && fiber.memoizedState === null) (resolvedBoundaries ?? (resolvedBoundaries = [])).push(fiber);
|
|
989
1144
|
if (!alt || fiber.child !== alt.child) {
|
|
990
1145
|
for (let c = fiber.child; c; c = c.sibling) stack.push(c);
|
|
991
1146
|
}
|
|
@@ -996,10 +1151,11 @@ function onCommit(root, commitPriority) {
|
|
|
996
1151
|
const updaters = updatersOf(root);
|
|
997
1152
|
let commitCause;
|
|
998
1153
|
let afterCommit;
|
|
999
|
-
|
|
1154
|
+
const inputDriven = commitPriority === "immediate" || commitPriority === "user-blocking";
|
|
1155
|
+
if (!inputDriven && lastCommit && at - lastCommit.at < EFFECT_LOOP_WINDOW_MS && updaters.some((u) => lastCommit.rendered.has(u)) && !rendered.some(isDeferredCatchUp)) {
|
|
1000
1156
|
commitCause = "effect-after-commit";
|
|
1001
1157
|
afterCommit = lastCommit.id;
|
|
1002
|
-
} else if (
|
|
1158
|
+
} else if (resolvedBoundaries) commitCause = "suspense-resolved";
|
|
1003
1159
|
if (rendered.length === 0) {
|
|
1004
1160
|
lastCommit = { id: s.nextCommitId, at, rendered: renderedNames };
|
|
1005
1161
|
return;
|
|
@@ -1008,47 +1164,62 @@ function onCommit(root, commitPriority) {
|
|
|
1008
1164
|
lastCommit = { id: commitId, at, rendered: renderedNames };
|
|
1009
1165
|
const dispatcherRef = o.resolveHookNames ? getDispatcherRef() : null;
|
|
1010
1166
|
const parentCache = /* @__PURE__ */ new Map();
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1167
|
+
const endDiffScope = beginDiffScope();
|
|
1168
|
+
const deadline = at + COMMIT_TIME_BUDGET_MS;
|
|
1169
|
+
try {
|
|
1170
|
+
for (let i = 0; i < rendered.length; i++) {
|
|
1171
|
+
if (i >= MAX_REPORTS_PER_COMMIT || (i & 15) === 15 && nowMs() > deadline) {
|
|
1172
|
+
s.truncated += rendered.length - i;
|
|
1173
|
+
warnOnce("truncated", `a commit rendered ${rendered.length} tracked components; only ${i} were reported (see info().truncated). Narrow \`include\` or turn off includeState.`);
|
|
1174
|
+
break;
|
|
1175
|
+
}
|
|
1176
|
+
const fiber = rendered[i];
|
|
1177
|
+
const alt = fiber.alternate;
|
|
1178
|
+
const a = analyze(fiber, alt, trackHooks);
|
|
1179
|
+
const durations = durationsOf(fiber);
|
|
1180
|
+
let hookState = o.includeState !== false && trackHooks ? snapshotHooks(fiber) : void 0;
|
|
1181
|
+
if (dispatcherRef && fiber.tag !== ClassComponent && (a.hookChanges.length || hookState && hookState.length)) {
|
|
1182
|
+
const names = resolveHookNames(fiber, dispatcherRef);
|
|
1183
|
+
if (names.size) {
|
|
1184
|
+
for (const c of a.hookChanges) if (c.hook !== "useContext" && names.has(c.index)) c.custom = names.get(c.index);
|
|
1185
|
+
if (hookState) hookState = hookState.map((h) => names.has(h.index) ? { ...h, custom: names.get(h.index) } : h);
|
|
1186
|
+
}
|
|
1021
1187
|
}
|
|
1188
|
+
const report = buildReport({
|
|
1189
|
+
component: fiberName(fiber),
|
|
1190
|
+
instanceId: instanceId(fiber),
|
|
1191
|
+
renderCount: bumpCount(fiber),
|
|
1192
|
+
prevProps: alt.memoizedProps ?? {},
|
|
1193
|
+
nextProps: fiber.memoizedProps ?? {},
|
|
1194
|
+
propChanges: a.propChanges,
|
|
1195
|
+
stateChanges: a.stateChanges,
|
|
1196
|
+
hookChanges: a.hookChanges,
|
|
1197
|
+
...o.includeState !== false ? {
|
|
1198
|
+
hookState,
|
|
1199
|
+
contexts: trackHooks ? snapshotContexts(fiber) : void 0,
|
|
1200
|
+
state: fiber.tag === ClassComponent && fiber.memoizedState && typeof fiber.memoizedState === "object" ? fiber.memoizedState : void 0
|
|
1201
|
+
} : {},
|
|
1202
|
+
updaters,
|
|
1203
|
+
commitCause,
|
|
1204
|
+
afterCommit,
|
|
1205
|
+
key: fiber.key === null || fiber.key === void 0 ? null : String(fiber.key),
|
|
1206
|
+
parent: a.trigger === "parent" ? nearestRenderedAncestor(fiber, parentCache, trackHooks) : null,
|
|
1207
|
+
owner: ownerName(fiber),
|
|
1208
|
+
path: componentPath(fiber),
|
|
1209
|
+
memoized: isMemoizedFiber(fiber),
|
|
1210
|
+
compiled: isCompiledFiber(fiber),
|
|
1211
|
+
classComponent: fiber.tag === ClassComponent,
|
|
1212
|
+
revealed: resolvedBoundaries !== null && a.trigger === "parent" && isUnder(fiber, resolvedBoundaries),
|
|
1213
|
+
selfDuration: durations ? durations.self : void 0,
|
|
1214
|
+
treeDuration: durations ? durations.tree : void 0,
|
|
1215
|
+
commitId,
|
|
1216
|
+
commitPriority,
|
|
1217
|
+
source: sourceOf(fiber)
|
|
1218
|
+
});
|
|
1219
|
+
dispatch(report);
|
|
1022
1220
|
}
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
instanceId: instanceId(fiber),
|
|
1026
|
-
renderCount: bumpCount(fiber),
|
|
1027
|
-
prevProps: alt.memoizedProps ?? {},
|
|
1028
|
-
nextProps: fiber.memoizedProps ?? {},
|
|
1029
|
-
propChanges: a.propChanges,
|
|
1030
|
-
stateChanges: a.stateChanges,
|
|
1031
|
-
hookChanges: a.hookChanges,
|
|
1032
|
-
...o.includeState !== false ? {
|
|
1033
|
-
hookState,
|
|
1034
|
-
contexts: trackHooks ? snapshotContexts(fiber) : void 0,
|
|
1035
|
-
state: fiber.tag === ClassComponent && fiber.memoizedState && typeof fiber.memoizedState === "object" ? fiber.memoizedState : void 0
|
|
1036
|
-
} : {},
|
|
1037
|
-
updaters,
|
|
1038
|
-
commitCause,
|
|
1039
|
-
afterCommit,
|
|
1040
|
-
key: fiber.key === null || fiber.key === void 0 ? null : String(fiber.key),
|
|
1041
|
-
parent: a.trigger === "parent" ? nearestRenderedAncestor(fiber, parentCache, trackHooks) : null,
|
|
1042
|
-
owner: ownerName(fiber),
|
|
1043
|
-
path: componentPath(fiber),
|
|
1044
|
-
memoized: isMemoizedFiber(fiber),
|
|
1045
|
-
selfDuration: durations ? durations.self : void 0,
|
|
1046
|
-
treeDuration: durations ? durations.tree : void 0,
|
|
1047
|
-
commitId,
|
|
1048
|
-
commitPriority,
|
|
1049
|
-
source: sourceOf(fiber)
|
|
1050
|
-
});
|
|
1051
|
-
dispatch(report);
|
|
1221
|
+
} finally {
|
|
1222
|
+
endDiffScope();
|
|
1052
1223
|
}
|
|
1053
1224
|
}
|
|
1054
1225
|
|
|
@@ -1150,38 +1321,270 @@ function useWhyRerender(name, values, options) {
|
|
|
1150
1321
|
});
|
|
1151
1322
|
}
|
|
1152
1323
|
|
|
1324
|
+
// src/causes.ts
|
|
1325
|
+
var isAncestorReport = (anc, r) => anc.path.length < r.path.length && r.path[anc.path.length] === anc.component && anc.path.every((p, i) => r.path[i] === p);
|
|
1326
|
+
var bump = (m, key, n = 1) => void m.set(key, (m.get(key) || 0) + n);
|
|
1327
|
+
function indexByComponent(reports) {
|
|
1328
|
+
const index = /* @__PURE__ */ new Map();
|
|
1329
|
+
for (const r of reports) {
|
|
1330
|
+
const list = index.get(r.component);
|
|
1331
|
+
if (list) list.push(r);
|
|
1332
|
+
else index.set(r.component, [r]);
|
|
1333
|
+
}
|
|
1334
|
+
return index;
|
|
1335
|
+
}
|
|
1336
|
+
function rootCauseOf(r, commitReports, index = indexByComponent(commitReports)) {
|
|
1337
|
+
let cur = r;
|
|
1338
|
+
const seen = /* @__PURE__ */ new Set([r]);
|
|
1339
|
+
while (cur.trigger === "parent" && cur.parent) {
|
|
1340
|
+
const parent = cur.parent;
|
|
1341
|
+
const candidates = index.get(parent.name);
|
|
1342
|
+
const p = candidates && candidates.find((x) => isAncestorReport(x, cur));
|
|
1343
|
+
if (!p || seen.has(p)) return { name: parent.name, trigger: parent.trigger, report: null };
|
|
1344
|
+
seen.add(p);
|
|
1345
|
+
cur = p;
|
|
1346
|
+
}
|
|
1347
|
+
return cur === r ? null : { name: cur.component, trigger: cur.trigger, report: cur };
|
|
1348
|
+
}
|
|
1349
|
+
function rootCausesOf(reports) {
|
|
1350
|
+
const roots = /* @__PURE__ */ new Map();
|
|
1351
|
+
const rootByReport = /* @__PURE__ */ new Map();
|
|
1352
|
+
const index = indexByComponent(reports);
|
|
1353
|
+
let avoidable = 0;
|
|
1354
|
+
let wasted = 0;
|
|
1355
|
+
for (const r of reports) {
|
|
1356
|
+
if (!r.avoidable) continue;
|
|
1357
|
+
avoidable++;
|
|
1358
|
+
if (typeof r.selfDuration === "number") wasted += r.selfDuration;
|
|
1359
|
+
const root = rootCauseOf(r, reports, index);
|
|
1360
|
+
const name = root ? root.name : r.parent && r.parent.name || "(unknown)";
|
|
1361
|
+
const trigger = root ? root.trigger : r.parent && r.parent.trigger || "parent";
|
|
1362
|
+
rootByReport.set(r, name);
|
|
1363
|
+
let agg = roots.get(name);
|
|
1364
|
+
if (!agg) {
|
|
1365
|
+
agg = { name, trigger, count: 0, components: /* @__PURE__ */ new Map() };
|
|
1366
|
+
roots.set(name, agg);
|
|
1367
|
+
}
|
|
1368
|
+
agg.count++;
|
|
1369
|
+
bump(agg.components, r.component);
|
|
1370
|
+
}
|
|
1371
|
+
return { roots: [...roots.values()].sort((a, b) => b.count - a.count), rootByReport, avoidable, wasted };
|
|
1372
|
+
}
|
|
1373
|
+
function analyzeCommit(reports) {
|
|
1374
|
+
const first = reports[0];
|
|
1375
|
+
return {
|
|
1376
|
+
id: first && typeof first.commitId === "number" ? first.commitId : 0,
|
|
1377
|
+
total: reports.length,
|
|
1378
|
+
...rootCausesOf(reports),
|
|
1379
|
+
contexts: contextAttribution(reports),
|
|
1380
|
+
reports
|
|
1381
|
+
};
|
|
1382
|
+
}
|
|
1383
|
+
function contextAttribution(reports) {
|
|
1384
|
+
const byCtx = /* @__PURE__ */ new Map();
|
|
1385
|
+
for (const r of reports) {
|
|
1386
|
+
for (const c of r.hookChanges || []) {
|
|
1387
|
+
if (c.hook !== "useContext" && !/^useContext/.test(c.path)) continue;
|
|
1388
|
+
const m = /useContext\((.*)\)/.exec(c.path);
|
|
1389
|
+
const name = m && m[1] ? m[1] : c.path;
|
|
1390
|
+
let agg = byCtx.get(name);
|
|
1391
|
+
if (!agg) {
|
|
1392
|
+
agg = { name, consumers: 0, avoidable: 0, components: /* @__PURE__ */ new Map(), commits: /* @__PURE__ */ new Set(), providers: /* @__PURE__ */ new Map(), changedKeys: /* @__PURE__ */ new Set(), totalKeys: 0 };
|
|
1393
|
+
byCtx.set(name, agg);
|
|
1394
|
+
}
|
|
1395
|
+
agg.consumers++;
|
|
1396
|
+
if (AVOIDABLE_KINDS.has(c.kind)) agg.avoidable++;
|
|
1397
|
+
bump(agg.components, r.component);
|
|
1398
|
+
agg.commits.add(typeof r.commitId === "number" ? r.commitId : 0);
|
|
1399
|
+
if (c.provider && c.provider.component) bump(agg.providers, c.provider.component);
|
|
1400
|
+
if (c.changedKeys) for (const k of c.changedKeys) agg.changedKeys.add(k);
|
|
1401
|
+
if (typeof c.totalKeys === "number") agg.totalKeys = Math.max(agg.totalKeys, c.totalKeys);
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
return [...byCtx.values()].sort((a, b) => b.consumers - a.consumers);
|
|
1405
|
+
}
|
|
1406
|
+
function cascadeTree(reports) {
|
|
1407
|
+
const root = { name: "", children: /* @__PURE__ */ new Map(), report: null };
|
|
1408
|
+
for (const r of reports) {
|
|
1409
|
+
let node = root;
|
|
1410
|
+
for (const seg of r.path.concat([r.component])) {
|
|
1411
|
+
let next = node.children.get(seg);
|
|
1412
|
+
if (!next) {
|
|
1413
|
+
next = { name: seg, children: /* @__PURE__ */ new Map(), report: null };
|
|
1414
|
+
node.children.set(seg, next);
|
|
1415
|
+
}
|
|
1416
|
+
node = next;
|
|
1417
|
+
}
|
|
1418
|
+
if (!node.report || r.avoidable) node.report = r;
|
|
1419
|
+
node.count = (node.count || 0) + 1;
|
|
1420
|
+
if (r.avoidable) node.avoidable = (node.avoidable || 0) + 1;
|
|
1421
|
+
}
|
|
1422
|
+
return root;
|
|
1423
|
+
}
|
|
1424
|
+
function rootCauseSummary(name, commits, analyze2) {
|
|
1425
|
+
const analyzeFn = analyze2 || ((_, reports) => analyzeCommit(reports));
|
|
1426
|
+
const out = { name, trigger: "parent", commits: [], total: 0, components: /* @__PURE__ */ new Map(), affected: [] };
|
|
1427
|
+
for (const [key, reports] of commits) {
|
|
1428
|
+
const analysis = analyzeFn(key, reports);
|
|
1429
|
+
const root = analysis.roots.find((x) => x.name === name);
|
|
1430
|
+
if (!root) continue;
|
|
1431
|
+
out.trigger = root.trigger;
|
|
1432
|
+
out.commits.push({ key, analysis, count: root.count, components: root.components });
|
|
1433
|
+
out.total += root.count;
|
|
1434
|
+
for (const [c, n] of root.components) bump(out.components, c, n);
|
|
1435
|
+
for (const r of reports) if (r.avoidable && analysis.rootByReport.get(r) === name) out.affected.push(r);
|
|
1436
|
+
}
|
|
1437
|
+
out.commits.reverse();
|
|
1438
|
+
return out;
|
|
1439
|
+
}
|
|
1440
|
+
function groupByCommit(reports) {
|
|
1441
|
+
const out = /* @__PURE__ */ new Map();
|
|
1442
|
+
for (const r of reports) {
|
|
1443
|
+
if (typeof r.commitId !== "number" || r.commitId <= 0) continue;
|
|
1444
|
+
const list = out.get(r.commitId);
|
|
1445
|
+
if (list) list.push(r);
|
|
1446
|
+
else out.set(r.commitId, [r]);
|
|
1447
|
+
}
|
|
1448
|
+
return out;
|
|
1449
|
+
}
|
|
1450
|
+
function rankRootCauses(reports) {
|
|
1451
|
+
const byName = /* @__PURE__ */ new Map();
|
|
1452
|
+
for (const commit of groupByCommit(reports).values()) {
|
|
1453
|
+
for (const root of rootCausesOf(commit).roots) {
|
|
1454
|
+
let agg = byName.get(root.name);
|
|
1455
|
+
if (!agg) {
|
|
1456
|
+
agg = { name: root.name, trigger: root.trigger, commits: 0, count: 0, components: /* @__PURE__ */ new Map() };
|
|
1457
|
+
byName.set(root.name, agg);
|
|
1458
|
+
}
|
|
1459
|
+
agg.commits++;
|
|
1460
|
+
agg.count += root.count;
|
|
1461
|
+
for (const [c, n] of root.components) bump(agg.components, c, n);
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
return [...byName.values()].sort((a, b) => b.count - a.count || b.commits - a.commits || a.name.localeCompare(b.name));
|
|
1465
|
+
}
|
|
1466
|
+
var plural = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`;
|
|
1467
|
+
function formatRootCauses(reports, limit = 5) {
|
|
1468
|
+
const ranked = rankRootCauses(reports);
|
|
1469
|
+
const lines = ranked.slice(0, limit).map((root, i) => `${String(i + 1).padStart(2)}. <${root.name}> (${root.trigger}) started ${plural(root.commits, "commit")}, ${plural(root.count, "avoidable re-render")} (${[...root.components].map(([c, n]) => `<${c}>${n > 1 ? " x" + n : ""}`).join(", ")})`);
|
|
1470
|
+
if (ranked.length > limit) lines.push(` \u2026 ${ranked.length - limit} more`);
|
|
1471
|
+
return lines.join("\n");
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1153
1474
|
// src/fixes.ts
|
|
1154
|
-
var
|
|
1475
|
+
var MAX_FIX_REPORTS = 50;
|
|
1155
1476
|
var rootOf = (path) => path.split(/[.[]/)[0] || path;
|
|
1477
|
+
var identifier = (name) => /^[A-Za-z_$][\w$]*$/.test(name) ? name : "value";
|
|
1478
|
+
function shortValue(v, max = 60) {
|
|
1479
|
+
let s;
|
|
1480
|
+
try {
|
|
1481
|
+
s = JSON.stringify(v);
|
|
1482
|
+
} catch {
|
|
1483
|
+
s = String(v);
|
|
1484
|
+
}
|
|
1485
|
+
if (s === void 0) s = String(v);
|
|
1486
|
+
return s.length > max ? s.slice(0, max - 3) + "..." : s;
|
|
1487
|
+
}
|
|
1156
1488
|
function fixesFor(r) {
|
|
1157
1489
|
const out = [];
|
|
1158
|
-
const
|
|
1159
|
-
const
|
|
1490
|
+
const ownerName2 = r.owner || r.parent && r.parent.name || null;
|
|
1491
|
+
const propChanges = r.propChanges || [];
|
|
1492
|
+
const changes = [...propChanges, ...r.stateChanges || [], ...r.hookChanges || []];
|
|
1160
1493
|
if (r.avoidable && (changes.length === 0 || r.memoized === false)) {
|
|
1494
|
+
const identical = changes.length === 0;
|
|
1161
1495
|
out.push({
|
|
1162
1496
|
kind: "memo",
|
|
1163
1497
|
owner: r.component,
|
|
1164
1498
|
target: r.component,
|
|
1165
1499
|
prop: null,
|
|
1166
1500
|
label: `Wrap <${r.component}> in React.memo`,
|
|
1167
|
-
detail:
|
|
1501
|
+
detail: identical ? `<${r.component}> re-rendered with identical props because <${r.parent && r.parent.name || "its parent"}> re-rendered.` : `<${r.component}> is not memoized: fixing its props alone will not stop the re-render.`,
|
|
1502
|
+
snippet: `// ${r.component}
|
|
1503
|
+
import { memo } from 'react';
|
|
1504
|
+
|
|
1505
|
+
export const ${r.component} = memo(function ${r.component}(props) {
|
|
1506
|
+
// ...
|
|
1507
|
+
});
|
|
1508
|
+
// class components: extend PureComponent instead`
|
|
1168
1509
|
});
|
|
1169
1510
|
}
|
|
1170
|
-
for (const c of
|
|
1171
|
-
if (!
|
|
1511
|
+
for (const c of propChanges) {
|
|
1512
|
+
if (!AVOIDABLE_KINDS.has(c.kind)) continue;
|
|
1513
|
+
const owner = ownerName2 || "?";
|
|
1172
1514
|
const root = rootOf(c.path);
|
|
1515
|
+
const id = identifier(root);
|
|
1173
1516
|
if (root === "children" && (c.kind === "element" || c.kind === "deep-equal")) {
|
|
1174
|
-
out.push({
|
|
1517
|
+
out.push({
|
|
1518
|
+
kind: "children",
|
|
1519
|
+
owner,
|
|
1520
|
+
target: r.component,
|
|
1521
|
+
prop: "children",
|
|
1522
|
+
label: `memoize children of <${r.component}> in <${owner}>`,
|
|
1523
|
+
detail: `<${owner}> re-creates the children of <${r.component}> on every render; they have the same types and props each time.`,
|
|
1524
|
+
snippet: `// ${owner}
|
|
1525
|
+
import { useMemo } from 'react';
|
|
1526
|
+
|
|
1527
|
+
const children = useMemo(() => (
|
|
1528
|
+
<>{/* the same elements */}</>
|
|
1529
|
+
), [/* deps */]);
|
|
1530
|
+
|
|
1531
|
+
<${r.component}>{children}</${r.component}>
|
|
1532
|
+
|
|
1533
|
+
// static children: hoist them to module scope
|
|
1534
|
+
const STATIC = <em>hi</em>;`
|
|
1535
|
+
});
|
|
1175
1536
|
} else if (c.kind === "function") {
|
|
1176
|
-
out.push({
|
|
1537
|
+
out.push({
|
|
1538
|
+
kind: "useCallback",
|
|
1539
|
+
owner,
|
|
1540
|
+
target: r.component,
|
|
1541
|
+
prop: root,
|
|
1542
|
+
label: `useCallback(${root}) in <${owner}>`,
|
|
1543
|
+
detail: `prop "${c.path}" of <${r.component}> is a new function on every render of <${owner}>.`,
|
|
1544
|
+
snippet: `// ${owner}
|
|
1545
|
+
import { useCallback } from 'react';
|
|
1546
|
+
|
|
1547
|
+
const ${id} = useCallback((/* args */) => {
|
|
1548
|
+
// ...
|
|
1549
|
+
}, [/* deps */]);
|
|
1550
|
+
|
|
1551
|
+
<${r.component} ${root}={${id}} />`
|
|
1552
|
+
});
|
|
1177
1553
|
} else if (c.kind === "element") {
|
|
1178
|
-
out.push({
|
|
1554
|
+
out.push({
|
|
1555
|
+
kind: "useMemoElement",
|
|
1556
|
+
owner,
|
|
1557
|
+
target: r.component,
|
|
1558
|
+
prop: root,
|
|
1559
|
+
label: `memoize element prop ${root} in <${owner}>`,
|
|
1560
|
+
detail: `prop "${c.path}" of <${r.component}> is a new element with the same type and props on every render of <${owner}>.`,
|
|
1561
|
+
snippet: `// ${owner}
|
|
1562
|
+
import { useMemo } from 'react';
|
|
1563
|
+
|
|
1564
|
+
const ${id} = useMemo(() => ${shortValue(c.next, 40)}, [/* deps */]);
|
|
1565
|
+
// or pass it as children from a component that does not re-render`
|
|
1566
|
+
});
|
|
1179
1567
|
} else {
|
|
1180
|
-
|
|
1568
|
+
const isArray = Array.isArray(c.next);
|
|
1569
|
+
out.push({
|
|
1570
|
+
kind: "useMemo",
|
|
1571
|
+
owner,
|
|
1572
|
+
target: r.component,
|
|
1573
|
+
prop: root,
|
|
1574
|
+
label: `useMemo(${root}) in <${owner}>`,
|
|
1575
|
+
detail: `prop "${c.path}" of <${r.component}> is a new ${isArray ? "array" : "object"} with the same contents on every render of <${owner}>.`,
|
|
1576
|
+
snippet: `// ${owner}
|
|
1577
|
+
import { useMemo } from 'react';
|
|
1578
|
+
|
|
1579
|
+
const ${id} = useMemo(() => (${shortValue(c.next, 80)}), [/* deps */]);
|
|
1580
|
+
|
|
1581
|
+
// or, when it never changes, hoist it to module scope:
|
|
1582
|
+
const ${id.toUpperCase()} = ${shortValue(c.next, 80)};`
|
|
1583
|
+
});
|
|
1181
1584
|
}
|
|
1182
1585
|
}
|
|
1183
|
-
for (const c of [...r.stateChanges, ...r.hookChanges]) {
|
|
1184
|
-
const isContext = c.hook === "useContext";
|
|
1586
|
+
for (const c of [...r.stateChanges || [], ...r.hookChanges || []]) {
|
|
1587
|
+
const isContext = c.hook === "useContext" || /^useContext/.test(c.path);
|
|
1185
1588
|
const ctxName = isContext ? (/useContext\((.*)\)/.exec(c.path) || [])[1] || "Context" : "";
|
|
1186
1589
|
const providerOwner = isContext && c.provider && c.provider.component ? c.provider.component : null;
|
|
1187
1590
|
if (isContext && c.kind === "different" && c.changedKeys && typeof c.totalKeys === "number" && c.changedKeys.length > 0 && c.changedKeys.length < c.totalKeys) {
|
|
@@ -1191,20 +1594,63 @@ function fixesFor(r) {
|
|
|
1191
1594
|
target: r.component,
|
|
1192
1595
|
prop: ctxName,
|
|
1193
1596
|
label: `split ${ctxName}${providerOwner ? ` in <${providerOwner}>` : ""}: only ${c.changedKeys.join(", ")} changed`,
|
|
1194
|
-
detail: `${c.changedKeys.
|
|
1597
|
+
detail: `${c.changedKeys.map((k) => `"${k}"`).join(", ")} of ${c.totalKeys} keys changed in ${ctxName}, yet every consumer (like <${r.component}>) re-rendered. Consumers that read the other keys re-render for nothing.`,
|
|
1598
|
+
snippet: `// ${providerOwner || "Provider"}
|
|
1599
|
+
// one context per independently-changing slice
|
|
1600
|
+
const ${identifier(ctxName)}Static = createContext(...);
|
|
1601
|
+
const ${identifier(ctxName)}${c.changedKeys.map((k) => k[0].toUpperCase() + k.slice(1)).join("")} = createContext(...);
|
|
1602
|
+
|
|
1603
|
+
// or keep one context and let consumers select a slice:
|
|
1604
|
+
const ${c.changedKeys[0]} = useContextSelector(${ctxName}, (v) => v.${c.changedKeys[0]});`
|
|
1195
1605
|
});
|
|
1196
1606
|
continue;
|
|
1197
1607
|
}
|
|
1198
|
-
if (!
|
|
1608
|
+
if (!AVOIDABLE_KINDS.has(c.kind)) continue;
|
|
1199
1609
|
if (isContext) {
|
|
1200
|
-
out.push({
|
|
1610
|
+
out.push({
|
|
1611
|
+
kind: "contextValue",
|
|
1612
|
+
owner: providerOwner || `${ctxName}.Provider`,
|
|
1613
|
+
target: r.component,
|
|
1614
|
+
prop: ctxName,
|
|
1615
|
+
label: `memoize the ${ctxName} provider value${providerOwner ? ` in <${providerOwner}>` : ""}`,
|
|
1616
|
+
detail: `<${r.component}> re-rendered because ${ctxName} produced a new value that is deep-equal to the previous one${providerOwner ? ` (Provider rendered by <${providerOwner}>)` : ""}.`,
|
|
1617
|
+
snippet: `// ${providerOwner || `where <${ctxName}.Provider> is rendered`}
|
|
1618
|
+
const value = useMemo(() => ({ /* ... */ }), [/* deps */]);
|
|
1619
|
+
<${ctxName}.Provider value={value}>`
|
|
1620
|
+
});
|
|
1201
1621
|
} else if (c.hook === "useSyncExternalStore") {
|
|
1202
1622
|
const chain = c.custom || [];
|
|
1203
1623
|
const redux = chain.some((n) => /^use(App)?Selector$/.test(n));
|
|
1204
1624
|
const zustand = !redux && chain.some((n) => /^use[A-Z]\w*Store$/.test(n) || n === "useStore" || n === "useBoundStore");
|
|
1205
|
-
|
|
1625
|
+
const via = chain.length ? ` via ${chain.join(" \u203A ")}` : "";
|
|
1626
|
+
out.push({
|
|
1627
|
+
kind: "storeSnapshot",
|
|
1628
|
+
owner: r.component,
|
|
1629
|
+
target: r.component,
|
|
1630
|
+
prop: c.path,
|
|
1631
|
+
label: redux ? `memoize the selector in <${r.component}>` : zustand ? `useShallow in <${r.component}>` : `stable getSnapshot in <${r.component}>`,
|
|
1632
|
+
detail: redux ? `the selector${via} returns a new object on every call, so the component re-renders on every store change.` : zustand ? `the store selector${via} returns a new object on every call, so the component re-renders on every store change.` : `${c.path}${via} returned a new reference with the same contents; getSnapshot must return a cached value.`,
|
|
1633
|
+
snippet: redux ? `// ${r.component}
|
|
1634
|
+
import { shallowEqual } from 'react-redux';
|
|
1635
|
+
const slice = useSelector(selectSlice, shallowEqual);
|
|
1636
|
+
// or memoize: const selectSlice = createSelector([selectA, selectB], (a, b) => ({ a, b }));` : zustand ? `// ${r.component}
|
|
1637
|
+
import { useShallow } from 'zustand/react/shallow';
|
|
1638
|
+
const { a, b } = useStore(useShallow((s) => ({ a: s.a, b: s.b })));
|
|
1639
|
+
// or select a primitive: const a = useStore((s) => s.a);` : `// ${r.component}
|
|
1640
|
+
// getSnapshot must return the same reference while the data is unchanged
|
|
1641
|
+
const snapshot = useSyncExternalStore(subscribe, store.getSnapshot /* cached */);`
|
|
1642
|
+
});
|
|
1206
1643
|
} else {
|
|
1207
|
-
out.push({
|
|
1644
|
+
out.push({
|
|
1645
|
+
kind: "bailout",
|
|
1646
|
+
owner: r.component,
|
|
1647
|
+
target: r.component,
|
|
1648
|
+
prop: c.path,
|
|
1649
|
+
label: `bail out before setting ${c.path} in <${r.component}>`,
|
|
1650
|
+
detail: `${c.path} was set to a value deep-equal to the current one (new reference).`,
|
|
1651
|
+
snippet: `// ${r.component}
|
|
1652
|
+
setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
1653
|
+
});
|
|
1208
1654
|
}
|
|
1209
1655
|
}
|
|
1210
1656
|
return out;
|
|
@@ -1218,11 +1664,12 @@ function rankFixes(reports) {
|
|
|
1218
1664
|
const key = fixKey(f);
|
|
1219
1665
|
let agg = byKey.get(key);
|
|
1220
1666
|
if (!agg) {
|
|
1221
|
-
agg = { ...f, key, count: 0, components: {} };
|
|
1667
|
+
agg = { ...f, key, count: 0, components: {}, reports: [] };
|
|
1222
1668
|
byKey.set(key, agg);
|
|
1223
1669
|
}
|
|
1224
1670
|
agg.count++;
|
|
1225
1671
|
agg.components[r.component] = (agg.components[r.component] || 0) + 1;
|
|
1672
|
+
if (agg.reports.length < MAX_FIX_REPORTS) agg.reports.push(r);
|
|
1226
1673
|
}
|
|
1227
1674
|
}
|
|
1228
1675
|
return [...byKey.values()].sort((a, b) => b.count - a.count || a.label.localeCompare(b.label));
|
|
@@ -1232,6 +1679,8 @@ function formatFixes(reports, limit = 10) {
|
|
|
1232
1679
|
if (!ranked.length) return "No avoidable re-renders.";
|
|
1233
1680
|
const lines = ranked.slice(0, limit).map((f, i) => `${String(i + 1).padStart(2)}. ${f.label} (removes ${f.count}: ${Object.entries(f.components).map(([c, n]) => `<${c}>${n > 1 ? " x" + n : ""}`).join(", ")})`);
|
|
1234
1681
|
if (ranked.length > limit) lines.push(` \u2026 ${ranked.length - limit} more`);
|
|
1682
|
+
const roots = formatRootCauses(reports, 5);
|
|
1683
|
+
if (roots) lines.push("", "Root causes:", roots);
|
|
1235
1684
|
return lines.join("\n");
|
|
1236
1685
|
}
|
|
1237
1686
|
|
|
@@ -1288,6 +1737,8 @@ function compareSummaries(before, after) {
|
|
|
1288
1737
|
regressions: rows.filter((r) => r.delta > 0)
|
|
1289
1738
|
};
|
|
1290
1739
|
}
|
|
1740
|
+
var summarizeSession = (session, reports) => summarizeReports(reports, session);
|
|
1741
|
+
var compareSessions = compareSummaries;
|
|
1291
1742
|
function formatComparison(c) {
|
|
1292
1743
|
const w = Math.max(14, ...c.rows.map((r) => r.component.length + 2));
|
|
1293
1744
|
const line = (label, b, a, d) => `${label.padEnd(w)} ${String(b).padStart(8)} ${String(a).padStart(8)} ${d.padStart(8)}`;
|
|
@@ -1442,13 +1893,24 @@ function highlight(target) {
|
|
|
1442
1893
|
for (const s of l.sticky) s.remove();
|
|
1443
1894
|
l.sticky = [];
|
|
1444
1895
|
if (!target) return;
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
if (rect.width === 0 && rect.height === 0) continue;
|
|
1896
|
+
const frag = document.createDocumentFragment();
|
|
1897
|
+
for (const rect of measure(target.nodes)) {
|
|
1448
1898
|
const b = box(rect, "#1a73e8", "rgba(26, 115, 232, 0.12)", target.label);
|
|
1449
|
-
|
|
1899
|
+
frag.appendChild(b);
|
|
1450
1900
|
l.sticky.push(b);
|
|
1451
1901
|
}
|
|
1902
|
+
l.root.appendChild(frag);
|
|
1903
|
+
}
|
|
1904
|
+
var MAX_BOXES = 100;
|
|
1905
|
+
function measure(nodes) {
|
|
1906
|
+
const rects = [];
|
|
1907
|
+
for (const node of nodes) {
|
|
1908
|
+
if (rects.length >= MAX_BOXES) break;
|
|
1909
|
+
const rect = node.getBoundingClientRect();
|
|
1910
|
+
if (rect.width === 0 && rect.height === 0) continue;
|
|
1911
|
+
rects.push(rect);
|
|
1912
|
+
}
|
|
1913
|
+
return rects;
|
|
1452
1914
|
}
|
|
1453
1915
|
function clearHighlight() {
|
|
1454
1916
|
highlight(null);
|
|
@@ -1456,27 +1918,34 @@ function clearHighlight() {
|
|
|
1456
1918
|
function flash(target, duration = 500) {
|
|
1457
1919
|
const l = ensureLayer();
|
|
1458
1920
|
if (!l) return;
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1921
|
+
const frag = document.createDocumentFragment();
|
|
1922
|
+
const boxes = [];
|
|
1923
|
+
for (const rect of measure(target.nodes)) {
|
|
1462
1924
|
const b = box(rect, "#d93025", "rgba(217, 48, 37, 0.15)");
|
|
1463
1925
|
b.style.transition = `opacity ${duration}ms ease-out`;
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
b.style.opacity = "0";
|
|
1467
|
-
});
|
|
1468
|
-
setTimeout(() => b.remove(), duration + 50);
|
|
1926
|
+
frag.appendChild(b);
|
|
1927
|
+
boxes.push(b);
|
|
1469
1928
|
}
|
|
1929
|
+
if (!boxes.length) return;
|
|
1930
|
+
l.root.appendChild(frag);
|
|
1931
|
+
requestAnimationFrame(() => {
|
|
1932
|
+
for (const b of boxes) b.style.opacity = "0";
|
|
1933
|
+
});
|
|
1934
|
+
setTimeout(() => {
|
|
1935
|
+
for (const b of boxes) b.remove();
|
|
1936
|
+
}, duration + 50);
|
|
1470
1937
|
}
|
|
1471
1938
|
|
|
1472
1939
|
// src/version.ts
|
|
1473
|
-
var VERSION = "0.
|
|
1940
|
+
var VERSION = "0.5.0" ;
|
|
1474
1941
|
|
|
1475
1942
|
// src/devtools.ts
|
|
1476
1943
|
var DEVTOOLS_MARKER = "__rerenderLens";
|
|
1477
1944
|
var PROTOCOL_VERSION = 2;
|
|
1478
1945
|
var DEFAULT_CHANNEL = "rerender-lens";
|
|
1479
|
-
|
|
1946
|
+
var SERIALIZE_MAX_ENTRIES = 100;
|
|
1947
|
+
var SERIALIZE_MAX_NODES = 2e4;
|
|
1948
|
+
function serialize(value, maxDepth = 4, seen = /* @__PURE__ */ new WeakSet(), depth = 0, budget = { nodes: SERIALIZE_MAX_NODES }) {
|
|
1480
1949
|
if (value === null || value === void 0) return value;
|
|
1481
1950
|
const t = typeof value;
|
|
1482
1951
|
if (t === "string" || t === "boolean") return value;
|
|
@@ -1487,24 +1956,56 @@ function serialize(value, maxDepth = 6, seen = /* @__PURE__ */ new WeakSet(), de
|
|
|
1487
1956
|
const obj = value;
|
|
1488
1957
|
if (seen.has(obj)) return "[Circular]";
|
|
1489
1958
|
if (depth >= maxDepth) return "[\u2026]";
|
|
1959
|
+
if (--budget.nodes < 0) return "[\u2026]";
|
|
1960
|
+
const next = (v) => serialize(v, maxDepth, seen, depth + 1, budget);
|
|
1490
1961
|
seen.add(obj);
|
|
1491
1962
|
try {
|
|
1492
1963
|
if (isReactElement(obj)) {
|
|
1493
1964
|
const out2 = { $type: "element", name: getDisplayName(obj.type) };
|
|
1494
1965
|
if (obj.key !== null && obj.key !== void 0) out2.key = String(obj.key);
|
|
1495
|
-
out2.props =
|
|
1966
|
+
out2.props = next(obj.props);
|
|
1496
1967
|
return out2;
|
|
1497
1968
|
}
|
|
1498
1969
|
if (obj instanceof Date) return { $type: "Date", value: obj.toISOString() };
|
|
1499
1970
|
if (obj instanceof RegExp) return { $type: "RegExp", value: String(obj) };
|
|
1971
|
+
if (ArrayBuffer.isView(obj) || obj instanceof ArrayBuffer) {
|
|
1972
|
+
const bin = obj;
|
|
1973
|
+
return { $type: bin.constructor?.name || "ArrayBuffer", length: typeof bin.length === "number" ? bin.length : bin.byteLength };
|
|
1974
|
+
}
|
|
1975
|
+
if (obj instanceof Promise) return "[Promise]";
|
|
1976
|
+
if (typeof Node !== "undefined" && obj instanceof Node) return typeof Element !== "undefined" && obj instanceof Element ? `<${obj.tagName.toLowerCase()}>` : `[${obj.nodeName}]`;
|
|
1977
|
+
if (obj === globalThis) return "[Window]";
|
|
1500
1978
|
if (obj instanceof Map) {
|
|
1501
|
-
|
|
1979
|
+
const entries = [];
|
|
1980
|
+
for (const [k, v] of obj) {
|
|
1981
|
+
if (entries.length >= SERIALIZE_MAX_ENTRIES) {
|
|
1982
|
+
entries.push(["\u2026", `+${obj.size - SERIALIZE_MAX_ENTRIES} more`]);
|
|
1983
|
+
break;
|
|
1984
|
+
}
|
|
1985
|
+
entries.push([next(k), next(v)]);
|
|
1986
|
+
}
|
|
1987
|
+
return { $type: "Map", entries };
|
|
1988
|
+
}
|
|
1989
|
+
if (obj instanceof Set) {
|
|
1990
|
+
const values = [];
|
|
1991
|
+
for (const v of obj) {
|
|
1992
|
+
if (values.length >= SERIALIZE_MAX_ENTRIES) {
|
|
1993
|
+
values.push(`\u2026+${obj.size - SERIALIZE_MAX_ENTRIES} more`);
|
|
1994
|
+
break;
|
|
1995
|
+
}
|
|
1996
|
+
values.push(next(v));
|
|
1997
|
+
}
|
|
1998
|
+
return { $type: "Set", values };
|
|
1999
|
+
}
|
|
2000
|
+
if (Array.isArray(obj)) {
|
|
2001
|
+
const out2 = obj.slice(0, SERIALIZE_MAX_ENTRIES).map(next);
|
|
2002
|
+
if (obj.length > SERIALIZE_MAX_ENTRIES) out2.push(`\u2026+${obj.length - SERIALIZE_MAX_ENTRIES} more`);
|
|
2003
|
+
return out2;
|
|
1502
2004
|
}
|
|
1503
|
-
if (obj instanceof Set) return { $type: "Set", values: [...obj].map((v) => serialize(v, maxDepth, seen, depth + 1)) };
|
|
1504
|
-
if (Array.isArray(obj)) return obj.map((v) => serialize(v, maxDepth, seen, depth + 1));
|
|
1505
|
-
if (typeof Element !== "undefined" && obj instanceof Element) return `<${obj.tagName.toLowerCase()}>`;
|
|
1506
2005
|
const out = {};
|
|
1507
|
-
|
|
2006
|
+
const keys = Object.keys(obj);
|
|
2007
|
+
for (const k of keys.slice(0, SERIALIZE_MAX_ENTRIES)) out[k] = next(obj[k]);
|
|
2008
|
+
if (keys.length > SERIALIZE_MAX_ENTRIES) out["\u2026"] = `+${keys.length - SERIALIZE_MAX_ENTRIES} more`;
|
|
1508
2009
|
const proto = Object.getPrototypeOf(obj);
|
|
1509
2010
|
if (proto && proto !== Object.prototype && proto.constructor?.name) out.$type = proto.constructor.name;
|
|
1510
2011
|
return out;
|
|
@@ -1544,13 +2045,20 @@ function deserializeOptions(o) {
|
|
|
1544
2045
|
}
|
|
1545
2046
|
function createDevtoolsNotifier(options = {}) {
|
|
1546
2047
|
const bufferSize = options.bufferSize ?? 300;
|
|
1547
|
-
const maxDepth = options.maxDepth ??
|
|
2048
|
+
const maxDepth = options.maxDepth ?? 4;
|
|
1548
2049
|
const target = options.target ?? (typeof window !== "undefined" ? window : void 0);
|
|
1549
2050
|
const buffer = [];
|
|
1550
2051
|
let seq = 0;
|
|
1551
2052
|
let flashOn = options.flashAvoidable ?? false;
|
|
2053
|
+
let live = !target || typeof target.addEventListener !== "function";
|
|
2054
|
+
if (!live) {
|
|
2055
|
+
target.addEventListener("message", (event) => {
|
|
2056
|
+
const data = event.data;
|
|
2057
|
+
if ((event.source === target || !event.source) && data && data.__rerenderLensReady === true) live = true;
|
|
2058
|
+
});
|
|
2059
|
+
}
|
|
1552
2060
|
const post = (type, payload) => {
|
|
1553
|
-
if (!target) return;
|
|
2061
|
+
if (!target || type === "report" && !live) return;
|
|
1554
2062
|
const msg = { [DEVTOOLS_MARKER]: true, version: PROTOCOL_VERSION, type, payload };
|
|
1555
2063
|
target.postMessage(msg, "*");
|
|
1556
2064
|
};
|
|
@@ -1567,10 +2075,12 @@ function createDevtoolsNotifier(options = {}) {
|
|
|
1567
2075
|
injected: typeof window !== "undefined" && typeof window.__RERENDER_LENS_INJECTED__ === "string",
|
|
1568
2076
|
scheduled: getState().scheduled,
|
|
1569
2077
|
commits: getState().commits,
|
|
1570
|
-
overhead: { totalMs: getState().overheadMs, maxCommitMs: getState().maxCommitMs }
|
|
2078
|
+
overhead: { totalMs: getState().overheadMs, maxCommitMs: getState().maxCommitMs },
|
|
2079
|
+
truncated: getState().truncated
|
|
1571
2080
|
});
|
|
1572
2081
|
const bridge = {
|
|
1573
2082
|
replay: () => {
|
|
2083
|
+
live = true;
|
|
1574
2084
|
post("hello", info());
|
|
1575
2085
|
for (const e of buffer) post("report", e.payload);
|
|
1576
2086
|
},
|
|
@@ -1759,6 +2269,6 @@ function createDevtoolsNotifier(options = {}) {
|
|
|
1759
2269
|
};
|
|
1760
2270
|
}
|
|
1761
2271
|
|
|
1762
|
-
export { DEFAULT_CHANNEL, DEVTOOLS_MARKER, MARKER, PROTOCOL_VERSION, VERSION, assertWithinBudget, avoidableCounts, buildReport, checkBudget, classify, combineNotifiers, compareSummaries, configure, createCollector, createDevtoolsNotifier, customHooksFromStack, deepEqual, deserializeOptions, diffRecords, disable, ensureDevtoolsHook, fixKey, fixesFor, formatComparison, formatFixes, getDisplayName, getRenderers, hookLabel, init, isEnabled, isProductionReact, parseExport, printReport, rankFixes, resolveHookNames, serialize, serializeOptions, shouldTrack, storeAdvice, summarize, summarizeReports, toBudget, track, useWhyRerender };
|
|
2272
|
+
export { AVOIDABLE_KINDS, DEFAULT_CHANNEL, DEVTOOLS_MARKER, KIND_LABEL, MARKER, PROTOCOL_VERSION, VERSION, analyzeCommit, assertWithinBudget, avoidableCounts, buildReport, cascadeTree, checkBudget, classify, combineNotifiers, compareSessions, compareSummaries, configure, contextAttribution, createCollector, createDevtoolsNotifier, customHooksFromStack, deepEqual, deserializeOptions, diffLeaves, diffRecords, disable, ensureDevtoolsHook, firstDifferentPath, fixKey, fixesFor, formatComparison, formatFixes, formatRootCauses, getDisplayName, getRenderers, groupByCommit, hookLabel, indexByComponent, init, isEnabled, isProductionReact, parseExport, printReport, rankFixes, rankRootCauses, resolveHookNames, rootCauseOf, rootCauseSummary, serialize, serializeOptions, shortValue, shouldTrack, storeAdvice, summarize, summarizeReports, summarizeSession, toBudget, track, useWhyRerender };
|
|
1763
2273
|
//# sourceMappingURL=index.js.map
|
|
1764
2274
|
//# sourceMappingURL=index.js.map
|