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