rerender-lens 0.4.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.
Files changed (66) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.md +192 -362
  3. package/dist/{budget-COBu7jBU.d.cts → budget-BnJlYwrV.d.ts} +20 -10
  4. package/dist/{budget-LkNjGtRc.d.ts → budget-CRswn-nC.d.cts} +20 -10
  5. package/dist/cli.cjs +248 -31
  6. package/dist/cli.cjs.map +1 -1
  7. package/dist/cli.js +248 -31
  8. package/dist/cli.js.map +1 -1
  9. package/dist/{devtools-CZebzpF6.d.ts → devtools-BYWat7nQ.d.ts} +7 -1
  10. package/dist/{devtools-BkCct3cJ.d.cts → devtools-DJi_6AfJ.d.cts} +7 -1
  11. package/dist/index.cjs +439 -41
  12. package/dist/index.cjs.map +1 -1
  13. package/dist/index.d.cts +131 -8
  14. package/dist/index.d.ts +131 -8
  15. package/dist/index.js +424 -42
  16. package/dist/index.js.map +1 -1
  17. package/dist/jest-setup.cjs +1673 -0
  18. package/dist/jest-setup.cjs.map +1 -0
  19. package/dist/jest-setup.d.cts +8 -0
  20. package/dist/jest-setup.d.ts +8 -0
  21. package/dist/jest-setup.js +1671 -0
  22. package/dist/jest-setup.js.map +1 -0
  23. package/dist/jest.cjs +1760 -0
  24. package/dist/jest.cjs.map +1 -0
  25. package/dist/jest.d.cts +23 -0
  26. package/dist/jest.d.ts +23 -0
  27. package/dist/jest.js +1751 -0
  28. package/dist/jest.js.map +1 -0
  29. package/dist/{notifiers-BjjGSHpp.d.ts → notifiers-DwKlvv4H.d.cts} +8 -4
  30. package/dist/{notifiers-BGQtWKfX.d.cts → notifiers-FNzKHs0a.d.ts} +8 -4
  31. package/dist/playwright.cjs +230 -28
  32. package/dist/playwright.cjs.map +1 -1
  33. package/dist/playwright.d.cts +4 -4
  34. package/dist/playwright.d.ts +4 -4
  35. package/dist/playwright.js +230 -28
  36. package/dist/playwright.js.map +1 -1
  37. package/dist/relay.cjs +10 -2
  38. package/dist/relay.cjs.map +1 -1
  39. package/dist/relay.js +10 -2
  40. package/dist/relay.js.map +1 -1
  41. package/dist/rerender-lens.iife.js +356 -54
  42. package/dist/runner-report-B8Sc3FZO.d.cts +46 -0
  43. package/dist/runner-report-CW8X5RwK.d.ts +46 -0
  44. package/dist/setup.cjs +112 -27
  45. package/dist/setup.cjs.map +1 -1
  46. package/dist/setup.js +112 -27
  47. package/dist/setup.js.map +1 -1
  48. package/dist/{types-BzUEVkxJ.d.cts → types-DW5-N2XH.d.cts} +55 -2
  49. package/dist/{types-BzUEVkxJ.d.ts → types-DW5-N2XH.d.ts} +55 -2
  50. package/dist/vite.d.cts +1 -1
  51. package/dist/vite.d.ts +1 -1
  52. package/dist/vitest-setup.cjs +337 -41
  53. package/dist/vitest-setup.cjs.map +1 -1
  54. package/dist/vitest-setup.d.cts +4 -4
  55. package/dist/vitest-setup.d.ts +4 -4
  56. package/dist/vitest-setup.js +337 -41
  57. package/dist/vitest-setup.js.map +1 -1
  58. package/dist/vitest.cjs +400 -77
  59. package/dist/vitest.cjs.map +1 -1
  60. package/dist/vitest.d.cts +21 -39
  61. package/dist/vitest.d.ts +21 -39
  62. package/dist/vitest.js +401 -78
  63. package/dist/vitest.js.map +1 -1
  64. package/package.json +40 -4
  65. package/panel/panel.css +1 -0
  66. package/panel/panel.js +499 -309
package/dist/index.js CHANGED
@@ -125,6 +125,7 @@ function deepEqualObjects(a, b, seen) {
125
125
  }
126
126
  return false;
127
127
  }
128
+ var AVOIDABLE_KINDS = /* @__PURE__ */ new Set(["deep-equal", "function", "element"]);
128
129
  function classify(prev, next) {
129
130
  if (typeof prev === "function" && typeof next === "function") {
130
131
  return prev.name === next.name && prev.toString() === next.toString() ? "function" : "different";
@@ -161,30 +162,47 @@ function diffRecords(prev, next, basePath = "") {
161
162
  if (Object.is(a, b)) continue;
162
163
  const kind = classify(a, b);
163
164
  if (kind === "different") {
164
- changes.push({ path: diffBudgetExhausted() ? 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 });
165
166
  } else {
166
167
  changes.push({ path, kind, prev: a, next: b });
167
168
  }
168
169
  }
169
170
  return changes;
170
171
  }
171
- function firstDifferentPath(a, b, path, depth = 0) {
172
- if (depth > 8) return path;
172
+ function firstDifferentPath(a, b, path = "", depth = 0) {
173
+ if (depth > 8) return path || "(value)";
173
174
  if (Array.isArray(a) && Array.isArray(b)) {
174
- if (a.length !== b.length) return `${path}.length`;
175
+ if (a.length !== b.length) return path ? `${path}.length` : "length";
175
176
  for (let i = 0; i < a.length; i++) {
176
177
  if (!deepEqual(a[i], b[i])) return firstDifferentPath(a[i], b[i], joinPath(path, i), depth + 1);
177
178
  }
178
- return path;
179
+ return null;
179
180
  }
180
181
  if (isPlainObject(a) && isPlainObject(b)) {
181
182
  const keys = /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)]);
182
183
  for (const k of keys) {
183
184
  if (!deepEqual(a[k], b[k])) return firstDifferentPath(a[k], b[k], joinPath(path, k), depth + 1);
184
185
  }
185
- return path;
186
+ return null;
186
187
  }
187
- 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;
188
206
  }
189
207
 
190
208
  // src/report.ts
@@ -202,10 +220,12 @@ function storeAdvice(custom) {
202
220
  }
203
221
  var isGenuine = (c) => c.kind === "different" || c.kind === "added" || c.kind === "removed";
204
222
  var isChildren = (change) => change.path === "children";
223
+ var isRefObject = (change) => change.path === "ref" && change.kind === "deep-equal";
205
224
  function fixFor(change) {
206
225
  if (isChildren(change) && (change.kind === "element" || change.kind === "deep-equal")) {
207
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`;
208
227
  }
228
+ if (isRefObject(change)) return `create the ref once with useRef (or createRef outside the render) and pass the same object every time`;
209
229
  switch (change.kind) {
210
230
  case "function":
211
231
  return `wrap it in useCallback (or hoist it out of the parent's render)`;
@@ -221,6 +241,7 @@ function describe(change) {
221
241
  if (isChildren(change) && (change.kind === "element" || change.kind === "deep-equal")) {
222
242
  return `children are new React elements with the same types and props on every render of the parent`;
223
243
  }
244
+ if (isRefObject(change)) return `prop "ref" is a new ref object on every render (React re-attaches it each time)`;
224
245
  switch (change.kind) {
225
246
  case "deep-equal":
226
247
  return `prop "${change.path}" is a new reference but deep-equal to the previous value`;
@@ -248,11 +269,21 @@ function buildReport(input) {
248
269
  if (causes.length === 0) trigger = "parent";
249
270
  else if (causes.length === 1) trigger = causes[0];
250
271
  else trigger = "mixed";
251
- const avoidable = trigger === "parent";
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;
252
276
  const reasons = [];
253
- if (avoidable && input.propChanges.length === 0 && stateChanges.length === 0 && hookChanges.length === 0) {
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) {
254
285
  const who = input.parent ? `<${input.parent.name}> re-rendered (${describeTrigger(input.parent.trigger)})` : "its parent re-rendered";
255
- reasons.push(`re-rendered with identical props because ${who}. Wrap "${input.component}" in React.memo (or extend PureComponent).`);
286
+ reasons.push(`re-rendered with identical props because ${who}. ${memoFix}.`);
256
287
  } else if (avoidable && input.parent) {
257
288
  reasons.push(`caused by <${input.parent.name}> re-rendering (${describeTrigger(input.parent.trigger)}).`);
258
289
  }
@@ -261,9 +292,9 @@ function buildReport(input) {
261
292
  reasons.push(fix ? `${describe(c)}: ${fix}.` : `${describe(c)}.`);
262
293
  }
263
294
  const memoized = input.memoized !== false;
264
- if (avoidable && !memoized && input.propChanges.length > 0) {
295
+ if (avoidable && !memoized && !input.compiled && input.propChanges.length > 0) {
265
296
  reasons.push(
266
- `"${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)"}.`
267
298
  );
268
299
  }
269
300
  for (const c of stateChanges) {
@@ -275,6 +306,8 @@ function buildReport(input) {
275
306
  const where = c.provider && c.provider.component ? ` (provided by <${c.provider.component}>)` : "";
276
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` : "";
277
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.`);
278
311
  } else if (isGenuine(c)) reasons.push(`${hookLabel(c)} changed.`);
279
312
  else if (isStateHook(c))
280
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.`);
@@ -305,6 +338,7 @@ function buildReport(input) {
305
338
  reasons,
306
339
  time: now()
307
340
  };
341
+ if (input.compiled) report.compiled = true;
308
342
  if (input.selfDuration !== void 0) report.selfDuration = input.selfDuration;
309
343
  if (input.treeDuration !== void 0) report.treeDuration = input.treeDuration;
310
344
  if (input.commitPriority) report.commitPriority = input.commitPriority;
@@ -342,10 +376,10 @@ var KIND_LABEL = {
342
376
  };
343
377
  function summarize(report) {
344
378
  const counts2 = /* @__PURE__ */ new Map();
345
- for (const c of [...report.propChanges, ...report.stateChanges, ...report.hookChanges]) {
379
+ for (const c of [...report.propChanges || [], ...report.stateChanges || [], ...report.hookChanges || []]) {
346
380
  counts2.set(c.kind, (counts2.get(c.kind) ?? 0) + 1);
347
381
  }
348
- const parts = [...counts2].map(([kind, n]) => `${n} ${KIND_LABEL[kind]}`);
382
+ const parts = [...counts2].map(([kind, n]) => `${n} ${KIND_LABEL[kind] || kind}`);
349
383
  if (parts.length === 0) parts.push("no changes");
350
384
  return parts.join(", ");
351
385
  }
@@ -408,6 +442,27 @@ function dispatch(report, override) {
408
442
  }
409
443
 
410
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"]);
411
466
  var noop = () => {
412
467
  };
413
468
  function customHooksFromStack(stack, componentName, marker) {
@@ -422,7 +477,7 @@ function customHooksFromStack(stack, componentName, marker) {
422
477
  continue;
423
478
  }
424
479
  if (name === componentName || name.endsWith("." + componentName)) break;
425
- 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);
426
481
  if (names.length > 8) break;
427
482
  }
428
483
  return names;
@@ -722,6 +777,11 @@ function isMemoizedFiber(fiber) {
722
777
  }
723
778
  return false;
724
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
+ }
725
785
  function durationsOf(fiber) {
726
786
  if (typeof fiber.actualDuration !== "number") return null;
727
787
  const tree = fiber.actualDuration;
@@ -900,7 +960,8 @@ function diffHooks(fiber, alt) {
900
960
  const labels = hookLabelsFor(fiber, b);
901
961
  let i = 0;
902
962
  while (a && b) {
903
- if ((isStateNode(b) || isStoreNode(b)) && !Object.is(a.memoizedState, b.memoizedState)) {
963
+ const deferred = labels?.[i] === "useDeferredValue";
964
+ if ((deferred || isStateNode(b) || isStoreNode(b)) && !Object.is(a.memoizedState, b.memoizedState)) {
904
965
  const hook = labels?.[i] ?? hookLabel2(b);
905
966
  out.push({
906
967
  path: `${hook}#${i}`,
@@ -934,6 +995,10 @@ function diffContexts(fiber, alt) {
934
995
  let a = alt.dependencies?.firstContext ?? null;
935
996
  let b = fiber.dependencies?.firstContext ?? null;
936
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
+ }
937
1002
  while (a && b) {
938
1003
  if (a.context === b.context && !Object.is(a.memoizedValue, b.memoizedValue)) {
939
1004
  const name = b.context.displayName ?? "Context";
@@ -966,10 +1031,26 @@ function diffContexts(fiber, alt) {
966
1031
  return out;
967
1032
  }
968
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
+ }
969
1050
  function analyze(fiber, alt, trackHooks) {
970
1051
  const prevProps = alt.memoizedProps ?? {};
971
1052
  const nextProps = fiber.memoizedProps ?? {};
972
- const propChanges = prevProps === nextProps ? [] : diffRecords(prevProps, nextProps);
1053
+ const propChanges = diffProps(prevProps, nextProps);
973
1054
  let stateChanges = [];
974
1055
  let hookChanges = [];
975
1056
  if (fiber.tag === ClassComponent) {
@@ -982,7 +1063,7 @@ function analyze(fiber, alt, trackHooks) {
982
1063
  } else if (trackHooks) {
983
1064
  hookChanges = [...diffHooks(fiber, alt), ...diffContexts(fiber, alt)];
984
1065
  }
985
- const isStateHook = (h) => h.hook !== "useContext" && h.hook !== "useSyncExternalStore";
1066
+ const isStateHook = (h) => h.hook !== "useContext" && h.hook !== "useSyncExternalStore" && h.hook !== "useDeferredValue";
986
1067
  const causes = [];
987
1068
  if (propChanges.some(isGenuine2)) causes.push("props");
988
1069
  if (stateChanges.some(isGenuine2) || hookChanges.some((h) => isStateHook(h) && isGenuine2(h))) causes.push("state");
@@ -1022,6 +1103,24 @@ var lastCommit = null;
1022
1103
  var EFFECT_LOOP_WINDOW_MS = 50;
1023
1104
  var MAX_REPORTS_PER_COMMIT = 200;
1024
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
+ }
1025
1124
  function onCommit(root, commitPriority) {
1026
1125
  const s = getState();
1027
1126
  if (!s.enabled) return;
@@ -1031,7 +1130,7 @@ function onCommit(root, commitPriority) {
1031
1130
  const rendered = [];
1032
1131
  const renderedNames = /* @__PURE__ */ new Set();
1033
1132
  let hot = false;
1034
- let suspenseResolved = false;
1133
+ let resolvedBoundaries = null;
1035
1134
  const stack = [root.current];
1036
1135
  while (stack.length) {
1037
1136
  const fiber = stack.pop();
@@ -1041,7 +1140,7 @@ function onCommit(root, commitPriority) {
1041
1140
  renderedNames.add(fiberName(fiber));
1042
1141
  if (shouldTrack(fiberType(fiber), o)) rendered.push(fiber);
1043
1142
  }
1044
- if (fiber.tag === SuspenseComponent && alt && alt.memoizedState !== null && fiber.memoizedState === null) suspenseResolved = true;
1143
+ if (fiber.tag === SuspenseComponent && alt && alt.memoizedState !== null && fiber.memoizedState === null) (resolvedBoundaries ?? (resolvedBoundaries = [])).push(fiber);
1045
1144
  if (!alt || fiber.child !== alt.child) {
1046
1145
  for (let c = fiber.child; c; c = c.sibling) stack.push(c);
1047
1146
  }
@@ -1053,10 +1152,10 @@ function onCommit(root, commitPriority) {
1053
1152
  let commitCause;
1054
1153
  let afterCommit;
1055
1154
  const inputDriven = commitPriority === "immediate" || commitPriority === "user-blocking";
1056
- if (!inputDriven && lastCommit && at - lastCommit.at < EFFECT_LOOP_WINDOW_MS && updaters.some((u) => lastCommit.rendered.has(u))) {
1155
+ if (!inputDriven && lastCommit && at - lastCommit.at < EFFECT_LOOP_WINDOW_MS && updaters.some((u) => lastCommit.rendered.has(u)) && !rendered.some(isDeferredCatchUp)) {
1057
1156
  commitCause = "effect-after-commit";
1058
1157
  afterCommit = lastCommit.id;
1059
- } else if (suspenseResolved) commitCause = "suspense-resolved";
1158
+ } else if (resolvedBoundaries) commitCause = "suspense-resolved";
1060
1159
  if (rendered.length === 0) {
1061
1160
  lastCommit = { id: s.nextCommitId, at, rendered: renderedNames };
1062
1161
  return;
@@ -1108,6 +1207,9 @@ function onCommit(root, commitPriority) {
1108
1207
  owner: ownerName(fiber),
1109
1208
  path: componentPath(fiber),
1110
1209
  memoized: isMemoizedFiber(fiber),
1210
+ compiled: isCompiledFiber(fiber),
1211
+ classComponent: fiber.tag === ClassComponent,
1212
+ revealed: resolvedBoundaries !== null && a.trigger === "parent" && isUnder(fiber, resolvedBoundaries),
1111
1213
  selfDuration: durations ? durations.self : void 0,
1112
1214
  treeDuration: durations ? durations.tree : void 0,
1113
1215
  commitId,
@@ -1219,38 +1321,270 @@ function useWhyRerender(name, values, options) {
1219
1321
  });
1220
1322
  }
1221
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
+
1222
1474
  // src/fixes.ts
1223
- var AVOIDABLE = /* @__PURE__ */ new Set(["deep-equal", "function", "element"]);
1475
+ var MAX_FIX_REPORTS = 50;
1224
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
+ }
1225
1488
  function fixesFor(r) {
1226
1489
  const out = [];
1227
- const owner = r.owner || r.parent && r.parent.name || "?";
1228
- const changes = [...r.propChanges, ...r.stateChanges, ...r.hookChanges];
1490
+ const ownerName2 = r.owner || r.parent && r.parent.name || null;
1491
+ const propChanges = r.propChanges || [];
1492
+ const changes = [...propChanges, ...r.stateChanges || [], ...r.hookChanges || []];
1229
1493
  if (r.avoidable && (changes.length === 0 || r.memoized === false)) {
1494
+ const identical = changes.length === 0;
1230
1495
  out.push({
1231
1496
  kind: "memo",
1232
1497
  owner: r.component,
1233
1498
  target: r.component,
1234
1499
  prop: null,
1235
1500
  label: `Wrap <${r.component}> in React.memo`,
1236
- detail: changes.length === 0 ? `<${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.`
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`
1237
1509
  });
1238
1510
  }
1239
- for (const c of r.propChanges) {
1240
- if (!AVOIDABLE.has(c.kind)) continue;
1511
+ for (const c of propChanges) {
1512
+ if (!AVOIDABLE_KINDS.has(c.kind)) continue;
1513
+ const owner = ownerName2 || "?";
1241
1514
  const root = rootOf(c.path);
1515
+ const id = identifier(root);
1242
1516
  if (root === "children" && (c.kind === "element" || c.kind === "deep-equal")) {
1243
- out.push({ kind: "children", owner, target: r.component, prop: "children", label: `memoize children of <${r.component}> in <${owner}>`, detail: `<${owner}> re-creates the children of <${r.component}> on every render.` });
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
+ });
1244
1536
  } else if (c.kind === "function") {
1245
- out.push({ kind: "useCallback", owner, target: r.component, prop: root, label: `useCallback(${root}) in <${owner}>`, detail: `prop "${c.path}" of <${r.component}> is a new function on every render of <${owner}>.` });
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
+ });
1246
1553
  } else if (c.kind === "element") {
1247
- out.push({ kind: "useMemoElement", owner, target: r.component, prop: root, label: `memoize element prop ${root} in <${owner}>`, detail: `prop "${c.path}" of <${r.component}> is a new element with the same type and props on every render of <${owner}>.` });
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
+ });
1248
1567
  } else {
1249
- out.push({ kind: "useMemo", owner, target: r.component, prop: root, label: `useMemo(${root}) in <${owner}>`, detail: `prop "${c.path}" of <${r.component}> is a new ${Array.isArray(c.next) ? "array" : "object"} with the same contents on every render of <${owner}>.` });
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
+ });
1250
1584
  }
1251
1585
  }
1252
- for (const c of [...r.stateChanges, ...r.hookChanges]) {
1253
- const isContext = c.hook === "useContext";
1586
+ for (const c of [...r.stateChanges || [], ...r.hookChanges || []]) {
1587
+ const isContext = c.hook === "useContext" || /^useContext/.test(c.path);
1254
1588
  const ctxName = isContext ? (/useContext\((.*)\)/.exec(c.path) || [])[1] || "Context" : "";
1255
1589
  const providerOwner = isContext && c.provider && c.provider.component ? c.provider.component : null;
1256
1590
  if (isContext && c.kind === "different" && c.changedKeys && typeof c.totalKeys === "number" && c.changedKeys.length > 0 && c.changedKeys.length < c.totalKeys) {
@@ -1260,20 +1594,63 @@ function fixesFor(r) {
1260
1594
  target: r.component,
1261
1595
  prop: ctxName,
1262
1596
  label: `split ${ctxName}${providerOwner ? ` in <${providerOwner}>` : ""}: only ${c.changedKeys.join(", ")} changed`,
1263
- detail: `${c.changedKeys.length} of ${c.totalKeys} keys changed in ${ctxName}, yet every consumer re-rendered.`
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]});`
1264
1605
  });
1265
1606
  continue;
1266
1607
  }
1267
- if (!AVOIDABLE.has(c.kind)) continue;
1608
+ if (!AVOIDABLE_KINDS.has(c.kind)) continue;
1268
1609
  if (isContext) {
1269
- out.push({ kind: "contextValue", owner: providerOwner || `${ctxName}.Provider`, target: r.component, prop: ctxName, label: `memoize the ${ctxName} provider value${providerOwner ? ` in <${providerOwner}>` : ""}`, detail: `${ctxName} produced a new value that is deep-equal to the previous one.` });
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
+ });
1270
1621
  } else if (c.hook === "useSyncExternalStore") {
1271
1622
  const chain = c.custom || [];
1272
1623
  const redux = chain.some((n) => /^use(App)?Selector$/.test(n));
1273
1624
  const zustand = !redux && chain.some((n) => /^use[A-Z]\w*Store$/.test(n) || n === "useStore" || n === "useBoundStore");
1274
- out.push({ kind: "storeSnapshot", owner: r.component, target: r.component, prop: c.path, label: redux ? `memoize the selector in <${r.component}>` : zustand ? `useShallow in <${r.component}>` : `stable getSnapshot in <${r.component}>`, detail: storeAdvice(c.custom) });
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
+ });
1275
1643
  } else {
1276
- out.push({ kind: "bailout", owner: r.component, target: r.component, prop: c.path, label: `bail out before setting ${c.path} in <${r.component}>`, detail: `${c.path} was set to a value deep-equal to the current one (new reference).` });
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
+ });
1277
1654
  }
1278
1655
  }
1279
1656
  return out;
@@ -1287,11 +1664,12 @@ function rankFixes(reports) {
1287
1664
  const key = fixKey(f);
1288
1665
  let agg = byKey.get(key);
1289
1666
  if (!agg) {
1290
- agg = { ...f, key, count: 0, components: {} };
1667
+ agg = { ...f, key, count: 0, components: {}, reports: [] };
1291
1668
  byKey.set(key, agg);
1292
1669
  }
1293
1670
  agg.count++;
1294
1671
  agg.components[r.component] = (agg.components[r.component] || 0) + 1;
1672
+ if (agg.reports.length < MAX_FIX_REPORTS) agg.reports.push(r);
1295
1673
  }
1296
1674
  }
1297
1675
  return [...byKey.values()].sort((a, b) => b.count - a.count || a.label.localeCompare(b.label));
@@ -1301,6 +1679,8 @@ function formatFixes(reports, limit = 10) {
1301
1679
  if (!ranked.length) return "No avoidable re-renders.";
1302
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(", ")})`);
1303
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);
1304
1684
  return lines.join("\n");
1305
1685
  }
1306
1686
 
@@ -1357,6 +1737,8 @@ function compareSummaries(before, after) {
1357
1737
  regressions: rows.filter((r) => r.delta > 0)
1358
1738
  };
1359
1739
  }
1740
+ var summarizeSession = (session, reports) => summarizeReports(reports, session);
1741
+ var compareSessions = compareSummaries;
1360
1742
  function formatComparison(c) {
1361
1743
  const w = Math.max(14, ...c.rows.map((r) => r.component.length + 2));
1362
1744
  const line = (label, b, a, d) => `${label.padEnd(w)} ${String(b).padStart(8)} ${String(a).padStart(8)} ${d.padStart(8)}`;
@@ -1555,7 +1937,7 @@ function flash(target, duration = 500) {
1555
1937
  }
1556
1938
 
1557
1939
  // src/version.ts
1558
- var VERSION = "0.4.0" ;
1940
+ var VERSION = "0.5.0" ;
1559
1941
 
1560
1942
  // src/devtools.ts
1561
1943
  var DEVTOOLS_MARKER = "__rerenderLens";
@@ -1887,6 +2269,6 @@ function createDevtoolsNotifier(options = {}) {
1887
2269
  };
1888
2270
  }
1889
2271
 
1890
- 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 };
1891
2273
  //# sourceMappingURL=index.js.map
1892
2274
  //# sourceMappingURL=index.js.map