rerender-lens 0.4.0 → 0.7.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 (68) hide show
  1. package/CHANGELOG.md +127 -0
  2. package/README.md +208 -359
  3. package/dist/{budget-LkNjGtRc.d.ts → budget-CA1MS7lB.d.cts} +20 -10
  4. package/dist/{budget-COBu7jBU.d.cts → budget-ok1VDJ4t.d.ts} +20 -10
  5. package/dist/cli.cjs +322 -53
  6. package/dist/cli.cjs.map +1 -1
  7. package/dist/cli.js +322 -53
  8. package/dist/cli.js.map +1 -1
  9. package/dist/{devtools-BkCct3cJ.d.cts → devtools-Cl5n_52v.d.ts} +17 -2
  10. package/dist/{devtools-CZebzpF6.d.ts → devtools-UwQx2VYS.d.cts} +17 -2
  11. package/dist/index.cjs +537 -49
  12. package/dist/index.cjs.map +1 -1
  13. package/dist/index.d.cts +137 -8
  14. package/dist/index.d.ts +137 -8
  15. package/dist/index.js +521 -50
  16. package/dist/index.js.map +1 -1
  17. package/dist/jest-setup.cjs +1690 -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 +1688 -0
  22. package/dist/jest-setup.js.map +1 -0
  23. package/dist/jest.cjs +1777 -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 +1768 -0
  28. package/dist/jest.js.map +1 -0
  29. package/dist/{notifiers-BjjGSHpp.d.ts → notifiers-B5poAEvE.d.cts} +8 -4
  30. package/dist/{notifiers-BGQtWKfX.d.cts → notifiers-CqQW0Eum.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 +71 -19
  38. package/dist/relay.cjs.map +1 -1
  39. package/dist/relay.d.cts +25 -3
  40. package/dist/relay.d.ts +25 -3
  41. package/dist/relay.js +71 -19
  42. package/dist/relay.js.map +1 -1
  43. package/dist/rerender-lens.iife.js +457 -62
  44. package/dist/runner-report-Bvqoe89M.d.ts +46 -0
  45. package/dist/runner-report-CvpDg2zt.d.cts +46 -0
  46. package/dist/setup.cjs +209 -35
  47. package/dist/setup.cjs.map +1 -1
  48. package/dist/setup.js +209 -35
  49. package/dist/setup.js.map +1 -1
  50. package/dist/{types-BzUEVkxJ.d.cts → types-DwNN5cWe.d.cts} +82 -2
  51. package/dist/{types-BzUEVkxJ.d.ts → types-DwNN5cWe.d.ts} +82 -2
  52. package/dist/vite.d.cts +1 -1
  53. package/dist/vite.d.ts +1 -1
  54. package/dist/vitest-setup.cjs +357 -44
  55. package/dist/vitest-setup.cjs.map +1 -1
  56. package/dist/vitest-setup.d.cts +4 -4
  57. package/dist/vitest-setup.d.ts +4 -4
  58. package/dist/vitest-setup.js +357 -44
  59. package/dist/vitest-setup.js.map +1 -1
  60. package/dist/vitest.cjs +420 -80
  61. package/dist/vitest.cjs.map +1 -1
  62. package/dist/vitest.d.cts +21 -39
  63. package/dist/vitest.d.ts +21 -39
  64. package/dist/vitest.js +421 -81
  65. package/dist/vitest.js.map +1 -1
  66. package/package.json +41 -4
  67. package/panel/panel.css +49 -0
  68. package/panel/panel.js +871 -353
package/dist/setup.js CHANGED
@@ -159,30 +159,30 @@ function diffRecords(prev, next, basePath = "") {
159
159
  if (Object.is(a, b)) continue;
160
160
  const kind = classify(a, b);
161
161
  if (kind === "different") {
162
- changes.push({ path: diffBudgetExhausted() ? path : firstDifferentPath(a, b, path), kind, prev: a, next: b });
162
+ changes.push({ path: diffBudgetExhausted() ? path : firstDifferentPath(a, b, path) ?? path, kind, prev: a, next: b });
163
163
  } else {
164
164
  changes.push({ path, kind, prev: a, next: b });
165
165
  }
166
166
  }
167
167
  return changes;
168
168
  }
169
- function firstDifferentPath(a, b, path, depth = 0) {
170
- if (depth > 8) return path;
169
+ function firstDifferentPath(a, b, path = "", depth = 0) {
170
+ if (depth > 8) return path || "(value)";
171
171
  if (Array.isArray(a) && Array.isArray(b)) {
172
- if (a.length !== b.length) return `${path}.length`;
172
+ if (a.length !== b.length) return path ? `${path}.length` : "length";
173
173
  for (let i = 0; i < a.length; i++) {
174
174
  if (!deepEqual(a[i], b[i])) return firstDifferentPath(a[i], b[i], joinPath(path, i), depth + 1);
175
175
  }
176
- return path;
176
+ return null;
177
177
  }
178
178
  if (isPlainObject(a) && isPlainObject(b)) {
179
179
  const keys = /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)]);
180
180
  for (const k of keys) {
181
181
  if (!deepEqual(a[k], b[k])) return firstDifferentPath(a[k], b[k], joinPath(path, k), depth + 1);
182
182
  }
183
- return path;
183
+ return null;
184
184
  }
185
- return path;
185
+ return deepEqual(a, b) ? null : path || "(value)";
186
186
  }
187
187
 
188
188
  // src/report.ts
@@ -200,10 +200,12 @@ function storeAdvice(custom) {
200
200
  }
201
201
  var isGenuine = (c) => c.kind === "different" || c.kind === "added" || c.kind === "removed";
202
202
  var isChildren = (change) => change.path === "children";
203
+ var isRefObject = (change) => change.path === "ref" && change.kind === "deep-equal";
203
204
  function fixFor(change) {
204
205
  if (isChildren(change) && (change.kind === "element" || change.kind === "deep-equal")) {
205
206
  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`;
206
207
  }
208
+ if (isRefObject(change)) return `create the ref once with useRef (or createRef outside the render) and pass the same object every time`;
207
209
  switch (change.kind) {
208
210
  case "function":
209
211
  return `wrap it in useCallback (or hoist it out of the parent's render)`;
@@ -219,6 +221,7 @@ function describe(change) {
219
221
  if (isChildren(change) && (change.kind === "element" || change.kind === "deep-equal")) {
220
222
  return `children are new React elements with the same types and props on every render of the parent`;
221
223
  }
224
+ if (isRefObject(change)) return `prop "ref" is a new ref object on every render (React re-attaches it each time)`;
222
225
  switch (change.kind) {
223
226
  case "deep-equal":
224
227
  return `prop "${change.path}" is a new reference but deep-equal to the previous value`;
@@ -246,11 +249,21 @@ function buildReport(input) {
246
249
  if (causes.length === 0) trigger = "parent";
247
250
  else if (causes.length === 1) trigger = causes[0];
248
251
  else trigger = "mixed";
249
- const avoidable = trigger === "parent";
252
+ const nothingChanged = input.propChanges.length === 0 && stateChanges.length === 0 && hookChanges.length === 0;
253
+ const cacheHit = input.compiled === true && trigger === "parent" && input.propChanges.length === 0;
254
+ const revealed = input.revealed === true && trigger === "parent";
255
+ const avoidable = trigger === "parent" && !cacheHit && !revealed;
250
256
  const reasons = [];
251
- if (avoidable && input.propChanges.length === 0 && stateChanges.length === 0 && hookChanges.length === 0) {
257
+ const memoFix = input.classComponent ? `Make "${input.component}" extend PureComponent (or implement shouldComponentUpdate)` : `Wrap "${input.component}" in React.memo (or extend PureComponent)`;
258
+ if (cacheHit) {
259
+ reasons.push(`compiled by React Compiler: its output is memoized and the inputs did not change, so this render is cheap; nothing to fix.`);
260
+ } else if (revealed) {
261
+ reasons.push(
262
+ `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.`
263
+ );
264
+ } else if (avoidable && nothingChanged) {
252
265
  const who = input.parent ? `<${input.parent.name}> re-rendered (${describeTrigger(input.parent.trigger)})` : "its parent re-rendered";
253
- reasons.push(`re-rendered with identical props because ${who}. Wrap "${input.component}" in React.memo (or extend PureComponent).`);
266
+ reasons.push(`re-rendered with identical props because ${who}. ${memoFix}.`);
254
267
  } else if (avoidable && input.parent) {
255
268
  reasons.push(`caused by <${input.parent.name}> re-rendering (${describeTrigger(input.parent.trigger)}).`);
256
269
  }
@@ -259,9 +272,9 @@ function buildReport(input) {
259
272
  reasons.push(fix ? `${describe(c)}: ${fix}.` : `${describe(c)}.`);
260
273
  }
261
274
  const memoized = input.memoized !== false;
262
- if (avoidable && !memoized && input.propChanges.length > 0) {
275
+ if (avoidable && !memoized && !input.compiled && input.propChanges.length > 0) {
263
276
  reasons.push(
264
- `"${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).`
277
+ `"${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)"}.`
265
278
  );
266
279
  }
267
280
  for (const c of stateChanges) {
@@ -273,6 +286,8 @@ function buildReport(input) {
273
286
  const where = c.provider && c.provider.component ? ` (provided by <${c.provider.component}>)` : "";
274
287
  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` : "";
275
288
  reasons.push(`${c.path} changed${where}${keys}.`);
289
+ } else if (c.hook === "useDeferredValue" && isGenuine(c)) {
290
+ 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.`);
276
291
  } else if (isGenuine(c)) reasons.push(`${hookLabel(c)} changed.`);
277
292
  else if (isStateHook(c))
278
293
  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.`);
@@ -303,6 +318,7 @@ function buildReport(input) {
303
318
  reasons,
304
319
  time: now()
305
320
  };
321
+ if (input.compiled) report.compiled = true;
306
322
  if (input.selfDuration !== void 0) report.selfDuration = input.selfDuration;
307
323
  if (input.treeDuration !== void 0) report.treeDuration = input.treeDuration;
308
324
  if (input.commitPriority) report.commitPriority = input.commitPriority;
@@ -340,10 +356,10 @@ var KIND_LABEL = {
340
356
  };
341
357
  function summarize(report) {
342
358
  const counts2 = /* @__PURE__ */ new Map();
343
- for (const c of [...report.propChanges, ...report.stateChanges, ...report.hookChanges]) {
359
+ for (const c of [...report.propChanges || [], ...report.stateChanges || [], ...report.hookChanges || []]) {
344
360
  counts2.set(c.kind, (counts2.get(c.kind) ?? 0) + 1);
345
361
  }
346
- const parts = [...counts2].map(([kind, n]) => `${n} ${KIND_LABEL[kind]}`);
362
+ const parts = [...counts2].map(([kind, n]) => `${n} ${KIND_LABEL[kind] || kind}`);
347
363
  if (parts.length === 0) parts.push("no changes");
348
364
  return parts.join(", ");
349
365
  }
@@ -365,16 +381,27 @@ function printReport(report, options) {
365
381
  }
366
382
 
367
383
  // src/state.ts
384
+ var MAX_SEEN_COMPONENTS = 500;
368
385
  var KEY = /* @__PURE__ */ Symbol.for("rerender-lens.state");
369
386
  function getState() {
370
387
  const g = globalThis;
371
388
  let s = g[KEY];
372
389
  if (!s) {
373
- 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 };
390
+ 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, seen: /* @__PURE__ */ new Set(), seenTracked: /* @__PURE__ */ new Set(), seenOverflow: false };
374
391
  g[KEY] = s;
375
392
  }
376
393
  return s;
377
394
  }
395
+ function noteRendered(s, name, tracked) {
396
+ if (!s.seen.has(name)) {
397
+ if (s.seen.size >= MAX_SEEN_COMPONENTS) {
398
+ s.seenOverflow = true;
399
+ return;
400
+ }
401
+ s.seen.add(name);
402
+ }
403
+ if (tracked) s.seenTracked.add(name);
404
+ }
378
405
  function warnOnce(key, message) {
379
406
  const s = getState();
380
407
  if (s.warnedOnce.has(key)) return;
@@ -406,6 +433,27 @@ function dispatch(report, override) {
406
433
  }
407
434
 
408
435
  // src/hookNames.ts
436
+ var NODE_COUNT = {
437
+ useState: 1,
438
+ useReducer: 1,
439
+ useRef: 1,
440
+ useMemo: 1,
441
+ useCallback: 1,
442
+ useEffect: 1,
443
+ useLayoutEffect: 1,
444
+ useInsertionEffect: 1,
445
+ useImperativeHandle: 1,
446
+ useSyncExternalStore: 2,
447
+ useTransition: 2,
448
+ useDeferredValue: 1,
449
+ useId: 1,
450
+ useOptimistic: 1,
451
+ useActionState: 3,
452
+ useFormState: 3,
453
+ useEffectEvent: 1,
454
+ useCacheRefresh: 1
455
+ };
456
+ var BUILTIN_HOOKS = /* @__PURE__ */ new Set([...Object.keys(NODE_COUNT), "useContext", "useDebugValue", "use"]);
409
457
  var noop = () => {
410
458
  };
411
459
  function customHooksFromStack(stack, componentName, marker) {
@@ -420,7 +468,7 @@ function customHooksFromStack(stack, componentName, marker) {
420
468
  continue;
421
469
  }
422
470
  if (name === componentName || name.endsWith("." + componentName)) break;
423
- if (/^use[A-Z0-9_]/.test(name)) names.push(name);
471
+ if (/^use[A-Z0-9_]/.test(name) && !BUILTIN_HOOKS.has(name)) names.push(name);
424
472
  if (names.length > 8) break;
425
473
  }
426
474
  return names;
@@ -720,6 +768,11 @@ function isMemoizedFiber(fiber) {
720
768
  }
721
769
  return false;
722
770
  }
771
+ function isCompiledFiber(fiber) {
772
+ if (fiber.tag === ClassComponent) return false;
773
+ const cache2 = fiber.updateQueue?.memoCache;
774
+ return !!cache2 && Array.isArray(cache2.data);
775
+ }
723
776
  function durationsOf(fiber) {
724
777
  if (typeof fiber.actualDuration !== "number") return null;
725
778
  const tree = fiber.actualDuration;
@@ -898,7 +951,8 @@ function diffHooks(fiber, alt) {
898
951
  const labels = hookLabelsFor(fiber, b);
899
952
  let i = 0;
900
953
  while (a && b) {
901
- if ((isStateNode(b) || isStoreNode(b)) && !Object.is(a.memoizedState, b.memoizedState)) {
954
+ const deferred = labels?.[i] === "useDeferredValue";
955
+ if ((deferred || isStateNode(b) || isStoreNode(b)) && !Object.is(a.memoizedState, b.memoizedState)) {
902
956
  const hook = labels?.[i] ?? hookLabel2(b);
903
957
  out.push({
904
958
  path: `${hook}#${i}`,
@@ -932,6 +986,10 @@ function diffContexts(fiber, alt) {
932
986
  let a = alt.dependencies?.firstContext ?? null;
933
987
  let b = fiber.dependencies?.firstContext ?? null;
934
988
  let i = 0;
989
+ if (b && !("memoizedValue" in b)) {
990
+ warnOnce("contextValues", "this React does not record context values on the fiber (React < 18): context changes are reported as plain parent re-renders.");
991
+ return out;
992
+ }
935
993
  while (a && b) {
936
994
  if (a.context === b.context && !Object.is(a.memoizedValue, b.memoizedValue)) {
937
995
  const name = b.context.displayName ?? "Context";
@@ -964,10 +1022,26 @@ function diffContexts(fiber, alt) {
964
1022
  return out;
965
1023
  }
966
1024
  var isGenuine2 = (c) => c.kind === "different" || c.kind === "added" || c.kind === "removed";
1025
+ var isRefObject2 = (v) => {
1026
+ if (typeof v !== "object" || v === null) return false;
1027
+ const keys = Object.keys(v);
1028
+ return keys.length === 1 && keys[0] === "current";
1029
+ };
1030
+ function diffProps(prev, next) {
1031
+ if (prev === next) return [];
1032
+ const pr = prev.ref;
1033
+ const nr = next.ref;
1034
+ if (pr !== nr && isRefObject2(pr) && isRefObject2(nr)) {
1035
+ const { ref: _p, ...restPrev } = prev;
1036
+ const { ref: _n, ...restNext } = next;
1037
+ return [{ path: "ref", kind: "deep-equal", prev: pr, next: nr }, ...diffRecords(restPrev, restNext)];
1038
+ }
1039
+ return diffRecords(prev, next);
1040
+ }
967
1041
  function analyze(fiber, alt, trackHooks) {
968
1042
  const prevProps = alt.memoizedProps ?? {};
969
1043
  const nextProps = fiber.memoizedProps ?? {};
970
- const propChanges = prevProps === nextProps ? [] : diffRecords(prevProps, nextProps);
1044
+ const propChanges = diffProps(prevProps, nextProps);
971
1045
  let stateChanges = [];
972
1046
  let hookChanges = [];
973
1047
  if (fiber.tag === ClassComponent) {
@@ -980,7 +1054,7 @@ function analyze(fiber, alt, trackHooks) {
980
1054
  } else if (trackHooks) {
981
1055
  hookChanges = [...diffHooks(fiber, alt), ...diffContexts(fiber, alt)];
982
1056
  }
983
- const isStateHook = (h) => h.hook !== "useContext" && h.hook !== "useSyncExternalStore";
1057
+ const isStateHook = (h) => h.hook !== "useContext" && h.hook !== "useSyncExternalStore" && h.hook !== "useDeferredValue";
984
1058
  const causes = [];
985
1059
  if (propChanges.some(isGenuine2)) causes.push("props");
986
1060
  if (stateChanges.some(isGenuine2) || hookChanges.some((h) => isStateHook(h) && isGenuine2(h))) causes.push("state");
@@ -1020,6 +1094,24 @@ var lastCommit = null;
1020
1094
  var EFFECT_LOOP_WINDOW_MS = 50;
1021
1095
  var MAX_REPORTS_PER_COMMIT = 200;
1022
1096
  var COMMIT_TIME_BUDGET_MS = 25;
1097
+ function isDeferredCatchUp(fiber) {
1098
+ const types = fiber._debugHookTypes;
1099
+ if (!types || !types.includes("useDeferredValue") || !fiber.alternate) return false;
1100
+ const first = fiber.memoizedState;
1101
+ if (!isHookList(first)) return false;
1102
+ const labels = hookLabelsFor(fiber, first);
1103
+ if (!labels) return false;
1104
+ let a = fiber.alternate.memoizedState;
1105
+ let b = first;
1106
+ for (let i = 0; a && b; a = a.next, b = b.next, i++) {
1107
+ if (labels[i] === "useDeferredValue" && !Object.is(a.memoizedState, b.memoizedState)) return true;
1108
+ }
1109
+ return false;
1110
+ }
1111
+ function isUnder(fiber, boundaries) {
1112
+ for (let f = fiber.return; f && f.tag !== HostRoot; f = f.return) if (f.tag === SuspenseComponent && boundaries.includes(f)) return true;
1113
+ return false;
1114
+ }
1023
1115
  function onCommit(root, commitPriority) {
1024
1116
  const s = getState();
1025
1117
  if (!s.enabled) return;
@@ -1029,17 +1121,20 @@ function onCommit(root, commitPriority) {
1029
1121
  const rendered = [];
1030
1122
  const renderedNames = /* @__PURE__ */ new Set();
1031
1123
  let hot = false;
1032
- let suspenseResolved = false;
1124
+ let resolvedBoundaries = null;
1033
1125
  const stack = [root.current];
1034
1126
  while (stack.length) {
1035
1127
  const fiber = stack.pop();
1036
1128
  const alt = fiber.alternate;
1037
1129
  if (alt && isComponentTag(fiber.tag) && didRender(fiber)) {
1038
1130
  if (isHotSwapped(fiber, alt)) hot = true;
1039
- renderedNames.add(fiberName(fiber));
1040
- if (shouldTrack(fiberType(fiber), o)) rendered.push(fiber);
1131
+ const name = fiberName(fiber);
1132
+ renderedNames.add(name);
1133
+ const tracked = shouldTrack(fiberType(fiber), o);
1134
+ noteRendered(s, name, tracked);
1135
+ if (tracked) rendered.push(fiber);
1041
1136
  }
1042
- if (fiber.tag === SuspenseComponent && alt && alt.memoizedState !== null && fiber.memoizedState === null) suspenseResolved = true;
1137
+ if (fiber.tag === SuspenseComponent && alt && alt.memoizedState !== null && fiber.memoizedState === null) (resolvedBoundaries ?? (resolvedBoundaries = [])).push(fiber);
1043
1138
  if (!alt || fiber.child !== alt.child) {
1044
1139
  for (let c = fiber.child; c; c = c.sibling) stack.push(c);
1045
1140
  }
@@ -1051,10 +1146,10 @@ function onCommit(root, commitPriority) {
1051
1146
  let commitCause;
1052
1147
  let afterCommit;
1053
1148
  const inputDriven = commitPriority === "immediate" || commitPriority === "user-blocking";
1054
- if (!inputDriven && lastCommit && at - lastCommit.at < EFFECT_LOOP_WINDOW_MS && updaters.some((u) => lastCommit.rendered.has(u))) {
1149
+ if (!inputDriven && lastCommit && at - lastCommit.at < EFFECT_LOOP_WINDOW_MS && updaters.some((u) => lastCommit.rendered.has(u)) && !rendered.some(isDeferredCatchUp)) {
1055
1150
  commitCause = "effect-after-commit";
1056
1151
  afterCommit = lastCommit.id;
1057
- } else if (suspenseResolved) commitCause = "suspense-resolved";
1152
+ } else if (resolvedBoundaries) commitCause = "suspense-resolved";
1058
1153
  if (rendered.length === 0) {
1059
1154
  lastCommit = { id: s.nextCommitId, at, rendered: renderedNames };
1060
1155
  return;
@@ -1106,6 +1201,9 @@ function onCommit(root, commitPriority) {
1106
1201
  owner: ownerName(fiber),
1107
1202
  path: componentPath(fiber),
1108
1203
  memoized: isMemoizedFiber(fiber),
1204
+ compiled: isCompiledFiber(fiber),
1205
+ classComponent: fiber.tag === ClassComponent,
1206
+ revealed: resolvedBoundaries !== null && a.trigger === "parent" && isUnder(fiber, resolvedBoundaries),
1109
1207
  selfDuration: durations ? durations.self : void 0,
1110
1208
  treeDuration: durations ? durations.tree : void 0,
1111
1209
  commitId,
@@ -1159,10 +1257,53 @@ function shouldTrack(type, o) {
1159
1257
  if (o.trackAllMemoized) return isMemo(type) || isPureClass(type);
1160
1258
  return false;
1161
1259
  }
1260
+ var ANONYMOUS = "Anonymous";
1261
+ function explainTracking(type, o) {
1262
+ if (typeof type === "string")
1263
+ return { tracked: false, name: type, memoized: false, reason: `<${type}> is a DOM element, not a React component; only components are tracked.` };
1264
+ if (!isComponentLike(type))
1265
+ return { tracked: false, name: ANONYMOUS, memoized: false, reason: "This is not a React component type, so it is never tracked." };
1266
+ const name = getDisplayName(type);
1267
+ const memo = isMemo(type);
1268
+ const memoized = memo || isPureClass(type);
1269
+ if (o.exclude?.some((m) => matches(m, name)))
1270
+ return { tracked: false, name, memoized, reason: `<${name}> matches "exclude", which wins over every other rule.`, fix: `Remove "${name}" from the exclude list.` };
1271
+ if (hasMarker(type)) return { tracked: true, name, memoized, reason: `<${name}> is marked with track() (the ${MARKER} static).` };
1272
+ if (o.include?.some((m) => matches(m, name))) return { tracked: true, name, memoized, reason: `<${name}> matches "include".` };
1273
+ if (o.trackAllComponents) return { tracked: true, name, memoized, reason: '"Track every component" is on, so every component is tracked.' };
1274
+ if (o.trackAllMemoized && memoized)
1275
+ return { tracked: true, name, memoized, reason: `<${name}> is ${memo ? "a React.memo component" : "a PureComponent"}, and "Track every React.memo and PureComponent" is on.` };
1276
+ if (name === ANONYMOUS)
1277
+ return {
1278
+ tracked: false,
1279
+ name,
1280
+ memoized,
1281
+ reason: 'This component has no display name, so "include" and "exclude" cannot address it.',
1282
+ fix: "Give the function a name, set displayName, or mark it with track(fn, 'Name')."
1283
+ };
1284
+ if (o.trackAllMemoized)
1285
+ return {
1286
+ tracked: false,
1287
+ name,
1288
+ memoized,
1289
+ reason: `"Track every React.memo and PureComponent" only covers React.memo components and PureComponent classes, and <${name}> is neither.`,
1290
+ fix: `Add "${name}" to include, wrap it in React.memo, or turn on "Track every component".`
1291
+ };
1292
+ return {
1293
+ tracked: false,
1294
+ name,
1295
+ memoized,
1296
+ reason: `Nothing selects <${name}>: it is not marked with track(), no "include" pattern matches it, and neither "track every" option is on.`,
1297
+ fix: `Add "${name}" to include, or turn on "Track every React.memo and PureComponent" / "Track every component".`
1298
+ };
1299
+ }
1162
1300
  function init(options = {}) {
1163
1301
  const s = getState();
1164
1302
  s.options = { ...options };
1165
1303
  s.printed.clear();
1304
+ s.seen.clear();
1305
+ s.seenTracked.clear();
1306
+ s.seenOverflow = false;
1166
1307
  if (!s.enabled) {
1167
1308
  s.detach = attach();
1168
1309
  s.enabled = true;
@@ -1288,7 +1429,7 @@ function flash(target, duration = 500) {
1288
1429
  }
1289
1430
 
1290
1431
  // src/version.ts
1291
- var VERSION = "0.4.0" ;
1432
+ var VERSION = "0.7.0" ;
1292
1433
 
1293
1434
  // src/devtools.ts
1294
1435
  var DEVTOOLS_MARKER = "__rerenderLens";
@@ -1414,6 +1555,18 @@ function createDevtoolsNotifier(options = {}) {
1414
1555
  target.postMessage(msg, "*");
1415
1556
  };
1416
1557
  const source = options.source ?? "page";
1558
+ const trackingSummary = () => {
1559
+ const s = getState();
1560
+ const o = s.options;
1561
+ return {
1562
+ mode: o.trackAllComponents ? "all" : o.trackAllMemoized ? "memoized" : "marked",
1563
+ include: (o.include ?? []).map(matcherToString).filter((x) => x !== null),
1564
+ exclude: (o.exclude ?? []).map(matcherToString).filter((x) => x !== null),
1565
+ renderedCount: s.seen.size,
1566
+ trackedCount: s.seenTracked.size,
1567
+ overflow: s.seenOverflow
1568
+ };
1569
+ };
1417
1570
  const info = () => ({
1418
1571
  count: buffer.length,
1419
1572
  library: VERSION,
@@ -1427,7 +1580,8 @@ function createDevtoolsNotifier(options = {}) {
1427
1580
  scheduled: getState().scheduled,
1428
1581
  commits: getState().commits,
1429
1582
  overhead: { totalMs: getState().overheadMs, maxCommitMs: getState().maxCommitMs },
1430
- truncated: getState().truncated
1583
+ truncated: getState().truncated,
1584
+ tracking: trackingSummary()
1431
1585
  });
1432
1586
  const bridge = {
1433
1587
  replay: () => {
@@ -1473,6 +1627,10 @@ function createDevtoolsNotifier(options = {}) {
1473
1627
  flashAvoidable: (on) => {
1474
1628
  flashOn = !!on;
1475
1629
  },
1630
+ explain: (target2) => {
1631
+ const comp = typeof target2 === "number" ? fiberById(target2) : nearestComponent(fiberForNode(target2));
1632
+ return comp ? explainTracking(fiberType(comp), getState().options) : null;
1633
+ },
1476
1634
  inspect: (node) => {
1477
1635
  const comp = nearestComponent(fiberForNode(node));
1478
1636
  if (!comp) return null;
@@ -1487,6 +1645,7 @@ function createDevtoolsNotifier(options = {}) {
1487
1645
  component: fiberName(comp),
1488
1646
  instanceId: id,
1489
1647
  tracked: shouldTrack(fiberType(comp), getState().options),
1648
+ tracking: explainTracking(fiberType(comp), getState().options),
1490
1649
  path,
1491
1650
  reports: id === null ? [] : buffer.filter((e) => e.instanceId === id).slice(-20).map((e) => e.payload)
1492
1651
  };
@@ -1521,6 +1680,9 @@ function createDevtoolsNotifier(options = {}) {
1521
1680
  bridge.flashAvoidable(!!data.arg);
1522
1681
  reply.result = true;
1523
1682
  break;
1683
+ case "explain":
1684
+ reply.result = bridge.explain(data.arg);
1685
+ break;
1524
1686
  default:
1525
1687
  reply.error = `unknown command ${String(data.cmd)}`;
1526
1688
  }
@@ -1529,6 +1691,7 @@ function createDevtoolsNotifier(options = {}) {
1529
1691
  }
1530
1692
  return reply;
1531
1693
  };
1694
+ const isRelayGreeting = (data) => !!data && typeof data === "object" && data.__rerenderLensRelay === true && typeof data.app === "string";
1532
1695
  const isCommand = (data) => !!data && typeof data === "object" && data.__rerenderLensCmd === true && typeof data.id === "string";
1533
1696
  const envelope = (type, payload) => ({ [DEVTOOLS_MARKER]: true, version: PROTOCOL_VERSION, type, payload });
1534
1697
  const sinks = [];
@@ -1551,14 +1714,20 @@ function createDevtoolsNotifier(options = {}) {
1551
1714
  const relayUrl = options.relay ?? (typeof window !== "undefined" ? window.__RERENDER_LENS_RELAY__ : void 0);
1552
1715
  const ES = options.eventSource ?? (typeof EventSource === "function" ? EventSource : null);
1553
1716
  if (relayUrl && ES && typeof fetch === "function") {
1554
- const base = relayUrl.replace(/\/$/, "");
1717
+ const [rawBase, relayQuery = ""] = relayUrl.split("?");
1718
+ const base = (rawBase ?? "").replace(/\/$/, "");
1719
+ const relayToken = new URLSearchParams(relayQuery).get("token");
1720
+ const auth = relayToken ? `token=${encodeURIComponent(relayToken)}` : "";
1555
1721
  let queue = [];
1556
1722
  let scheduled = false;
1723
+ let appId = null;
1557
1724
  const flush = () => {
1558
1725
  scheduled = false;
1559
1726
  const batch = queue;
1560
1727
  queue = [];
1561
- fetch(`${base}/message`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(batch), keepalive: true }).catch(() => {
1728
+ const params = [appId ? `app=${encodeURIComponent(appId)}` : "", auth].filter(Boolean).join("&");
1729
+ const to = `${base}/message${params ? `?${params}` : ""}`;
1730
+ fetch(to, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(batch), keepalive: true }).catch(() => {
1562
1731
  });
1563
1732
  };
1564
1733
  const relaySend = (message) => {
@@ -1569,7 +1738,8 @@ function createDevtoolsNotifier(options = {}) {
1569
1738
  }
1570
1739
  };
1571
1740
  try {
1572
- const stream = new ES(`${base}/events?role=app`);
1741
+ const label = typeof location !== "undefined" ? `${location.host}${location.pathname}`.slice(0, 120) : "";
1742
+ const stream = new ES(`${base}/events?role=app${label ? `&label=${encodeURIComponent(label)}` : ""}${auth ? `&${auth}` : ""}`);
1573
1743
  stream.onmessage = (event) => {
1574
1744
  let parsed;
1575
1745
  try {
@@ -1577,7 +1747,10 @@ function createDevtoolsNotifier(options = {}) {
1577
1747
  } catch {
1578
1748
  return;
1579
1749
  }
1580
- for (const m of Array.isArray(parsed) ? parsed : [parsed]) if (isCommand(m)) relaySend(runCommand(m));
1750
+ for (const m of Array.isArray(parsed) ? parsed : [parsed]) {
1751
+ if (isRelayGreeting(m)) appId = m.app;
1752
+ else if (isCommand(m)) relaySend(runCommand(m));
1753
+ }
1581
1754
  };
1582
1755
  stream.onerror = () => {
1583
1756
  };
@@ -1621,15 +1794,16 @@ function createDevtoolsNotifier(options = {}) {
1621
1794
  }
1622
1795
 
1623
1796
  // src/setup.ts
1624
- var env = (key) => {
1797
+ var read = (get) => {
1625
1798
  try {
1626
- return typeof process !== "undefined" && process && process.env ? process.env[key] : void 0;
1799
+ return get();
1627
1800
  } catch {
1628
1801
  return void 0;
1629
1802
  }
1630
1803
  };
1631
- if (env("NODE_ENV") !== "production" && typeof window !== "undefined" && !window.__RERENDER_LENS_DEVTOOLS__) {
1632
- const relay = env("RERENDER_LENS_RELAY") || env("NEXT_PUBLIC_RERENDER_LENS_RELAY");
1804
+ var production = read(() => process.env.NODE_ENV) === "production";
1805
+ if (!production && typeof window !== "undefined" && !window.__RERENDER_LENS_DEVTOOLS__) {
1806
+ const relay = read(() => process.env.RERENDER_LENS_RELAY) || read(() => process.env.NEXT_PUBLIC_RERENDER_LENS_RELAY);
1633
1807
  init({ trackAllMemoized: true, notifier: createDevtoolsNotifier(relay ? { relay } : {}) });
1634
1808
  }
1635
1809
  //# sourceMappingURL=setup.js.map