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.
Files changed (66) hide show
  1. package/CHANGELOG.md +118 -0
  2. package/README.md +192 -353
  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-BHGUUo-p.d.ts → devtools-BYWat7nQ.d.ts} +17 -3
  10. package/dist/{devtools-DmhWiWcN.d.cts → devtools-DJi_6AfJ.d.cts} +17 -3
  11. package/dist/index.cjs +635 -109
  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 +620 -110
  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 +235 -30
  32. package/dist/playwright.cjs.map +1 -1
  33. package/dist/playwright.d.cts +8 -6
  34. package/dist/playwright.d.ts +8 -6
  35. package/dist/playwright.js +235 -31
  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 +1100 -337
  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 +308 -95
  45. package/dist/setup.cjs.map +1 -1
  46. package/dist/setup.js +308 -95
  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 +452 -87
  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 +452 -87
  57. package/dist/vitest-setup.js.map +1 -1
  58. package/dist/vitest.cjs +515 -123
  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 +516 -124
  63. package/dist/vitest.js.map +1 -1
  64. package/package.json +41 -5
  65. package/panel/panel.css +1 -34
  66. package/panel/panel.js +865 -713
package/dist/setup.cjs CHANGED
@@ -26,10 +26,49 @@ function remember(seen, a, b) {
26
26
  set.add(b);
27
27
  return false;
28
28
  }
29
+ var scope = null;
30
+ var DEFAULT_DIFF_BUDGET = 5e4;
31
+ function beginDiffScope(budget = 2e5) {
32
+ const previous = scope;
33
+ scope = { cache: /* @__PURE__ */ new WeakMap(), remaining: budget, exhausted: false };
34
+ return () => {
35
+ scope = previous;
36
+ };
37
+ }
38
+ function diffBudgetExhausted() {
39
+ return scope?.exhausted === true;
40
+ }
29
41
  function deepEqual(a, b, seen = /* @__PURE__ */ new Map()) {
30
42
  if (Object.is(a, b)) return true;
31
43
  if (typeof a !== typeof b) return false;
32
44
  if (typeof a !== "object" || a === null || b === null) return false;
45
+ if (!scope) {
46
+ const end = beginDiffScope(DEFAULT_DIFF_BUDGET);
47
+ try {
48
+ return deepEqual(a, b, seen);
49
+ } finally {
50
+ end();
51
+ }
52
+ }
53
+ const sc = scope;
54
+ const objA = a;
55
+ const objB = b;
56
+ const hit = sc.cache.get(objA)?.get(objB);
57
+ if (hit !== void 0) return hit;
58
+ if (--sc.remaining < 0) {
59
+ sc.exhausted = true;
60
+ return false;
61
+ }
62
+ const result = deepEqualObjects(objA, objB, seen);
63
+ let m = sc.cache.get(objA);
64
+ if (!m) {
65
+ m = /* @__PURE__ */ new WeakMap();
66
+ sc.cache.set(objA, m);
67
+ }
68
+ m.set(objB, result);
69
+ return result;
70
+ }
71
+ function deepEqualObjects(a, b, seen) {
33
72
  const objA = a;
34
73
  const objB = b;
35
74
  if (remember(seen, objA, objB)) return true;
@@ -58,6 +97,10 @@ function deepEqual(a, b, seen = /* @__PURE__ */ new Map()) {
58
97
  }
59
98
  if (a instanceof Set) {
60
99
  if (!(b instanceof Set) || a.size !== b.size) return false;
100
+ if (a.size > 50) {
101
+ for (const v of a) if (!b.has(v)) return false;
102
+ return true;
103
+ }
61
104
  outer: for (const v of a) {
62
105
  if (b.has(v)) continue;
63
106
  for (const w of b) if (deepEqual(v, w, seen)) continue outer;
@@ -73,9 +116,10 @@ function deepEqual(a, b, seen = /* @__PURE__ */ new Map()) {
73
116
  const ka = Object.keys(a);
74
117
  const kb = Object.keys(b);
75
118
  if (ka.length !== kb.length) return false;
119
+ const rb = b;
76
120
  for (const k of ka) {
77
121
  if (!Object.prototype.hasOwnProperty.call(b, k)) return false;
78
- if (!deepEqual(a[k], b[k], seen)) return false;
122
+ if (!deepEqual(a[k], rb[k], seen)) return false;
79
123
  }
80
124
  return true;
81
125
  }
@@ -117,30 +161,30 @@ function diffRecords(prev, next, basePath = "") {
117
161
  if (Object.is(a, b)) continue;
118
162
  const kind = classify(a, b);
119
163
  if (kind === "different") {
120
- changes.push({ path: firstDifferentPath(a, b, path), kind, prev: a, next: b });
164
+ changes.push({ path: diffBudgetExhausted() ? path : firstDifferentPath(a, b, path) ?? path, kind, prev: a, next: b });
121
165
  } else {
122
166
  changes.push({ path, kind, prev: a, next: b });
123
167
  }
124
168
  }
125
169
  return changes;
126
170
  }
127
- function firstDifferentPath(a, b, path, depth = 0) {
128
- if (depth > 8) return path;
171
+ function firstDifferentPath(a, b, path = "", depth = 0) {
172
+ if (depth > 8) return path || "(value)";
129
173
  if (Array.isArray(a) && Array.isArray(b)) {
130
- if (a.length !== b.length) return `${path}.length`;
174
+ if (a.length !== b.length) return path ? `${path}.length` : "length";
131
175
  for (let i = 0; i < a.length; i++) {
132
176
  if (!deepEqual(a[i], b[i])) return firstDifferentPath(a[i], b[i], joinPath(path, i), depth + 1);
133
177
  }
134
- return path;
178
+ return null;
135
179
  }
136
180
  if (isPlainObject(a) && isPlainObject(b)) {
137
181
  const keys = /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)]);
138
182
  for (const k of keys) {
139
183
  if (!deepEqual(a[k], b[k])) return firstDifferentPath(a[k], b[k], joinPath(path, k), depth + 1);
140
184
  }
141
- return path;
185
+ return null;
142
186
  }
143
- return path;
187
+ return deepEqual(a, b) ? null : path || "(value)";
144
188
  }
145
189
 
146
190
  // src/report.ts
@@ -158,10 +202,12 @@ function storeAdvice(custom) {
158
202
  }
159
203
  var isGenuine = (c) => c.kind === "different" || c.kind === "added" || c.kind === "removed";
160
204
  var isChildren = (change) => change.path === "children";
205
+ var isRefObject = (change) => change.path === "ref" && change.kind === "deep-equal";
161
206
  function fixFor(change) {
162
207
  if (isChildren(change) && (change.kind === "element" || change.kind === "deep-equal")) {
163
208
  return `lift the children out of the parent's render: memoize them with useMemo, hoist static elements to module scope, or render them from a component that does not re-render`;
164
209
  }
210
+ if (isRefObject(change)) return `create the ref once with useRef (or createRef outside the render) and pass the same object every time`;
165
211
  switch (change.kind) {
166
212
  case "function":
167
213
  return `wrap it in useCallback (or hoist it out of the parent's render)`;
@@ -177,6 +223,7 @@ function describe(change) {
177
223
  if (isChildren(change) && (change.kind === "element" || change.kind === "deep-equal")) {
178
224
  return `children are new React elements with the same types and props on every render of the parent`;
179
225
  }
226
+ if (isRefObject(change)) return `prop "ref" is a new ref object on every render (React re-attaches it each time)`;
180
227
  switch (change.kind) {
181
228
  case "deep-equal":
182
229
  return `prop "${change.path}" is a new reference but deep-equal to the previous value`;
@@ -204,11 +251,21 @@ function buildReport(input) {
204
251
  if (causes.length === 0) trigger = "parent";
205
252
  else if (causes.length === 1) trigger = causes[0];
206
253
  else trigger = "mixed";
207
- const avoidable = trigger === "parent";
254
+ const nothingChanged = input.propChanges.length === 0 && stateChanges.length === 0 && hookChanges.length === 0;
255
+ const cacheHit = input.compiled === true && trigger === "parent" && input.propChanges.length === 0;
256
+ const revealed = input.revealed === true && trigger === "parent";
257
+ const avoidable = trigger === "parent" && !cacheHit && !revealed;
208
258
  const reasons = [];
209
- if (avoidable && input.propChanges.length === 0 && stateChanges.length === 0 && hookChanges.length === 0) {
259
+ const memoFix = input.classComponent ? `Make "${input.component}" extend PureComponent (or implement shouldComponentUpdate)` : `Wrap "${input.component}" in React.memo (or extend PureComponent)`;
260
+ if (cacheHit) {
261
+ reasons.push(`compiled by React Compiler: its output is memoized and the inputs did not change, so this render is cheap; nothing to fix.`);
262
+ } else if (revealed) {
263
+ reasons.push(
264
+ `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.`
265
+ );
266
+ } else if (avoidable && nothingChanged) {
210
267
  const who = input.parent ? `<${input.parent.name}> re-rendered (${describeTrigger(input.parent.trigger)})` : "its parent re-rendered";
211
- reasons.push(`re-rendered with identical props because ${who}. Wrap "${input.component}" in React.memo (or extend PureComponent).`);
268
+ reasons.push(`re-rendered with identical props because ${who}. ${memoFix}.`);
212
269
  } else if (avoidable && input.parent) {
213
270
  reasons.push(`caused by <${input.parent.name}> re-rendering (${describeTrigger(input.parent.trigger)}).`);
214
271
  }
@@ -217,9 +274,9 @@ function buildReport(input) {
217
274
  reasons.push(fix ? `${describe(c)}: ${fix}.` : `${describe(c)}.`);
218
275
  }
219
276
  const memoized = input.memoized !== false;
220
- if (avoidable && !memoized && input.propChanges.length > 0) {
277
+ if (avoidable && !memoized && !input.compiled && input.propChanges.length > 0) {
221
278
  reasons.push(
222
- `"${input.component}" is not memoized, so fixing the props alone will not stop this re-render: also wrap it in React.memo (or extend PureComponent).`
279
+ `"${input.component}" is not memoized, so fixing the props alone will not stop this re-render: also ${input.classComponent ? "extend PureComponent (or implement shouldComponentUpdate)" : "wrap it in React.memo (or extend PureComponent)"}.`
223
280
  );
224
281
  }
225
282
  for (const c of stateChanges) {
@@ -231,6 +288,8 @@ function buildReport(input) {
231
288
  const where = c.provider && c.provider.component ? ` (provided by <${c.provider.component}>)` : "";
232
289
  const keys = c.changedKeys && typeof c.totalKeys === "number" && c.changedKeys.length > 0 && c.changedKeys.length < c.totalKeys ? `: only ${c.changedKeys.map((k) => `"${k}"`).join(", ")} of ${c.totalKeys} keys changed, yet every consumer re-renders. Split the context or memoize the slices consumers read` : "";
233
290
  reasons.push(`${c.path} changed${where}${keys}.`);
291
+ } else if (c.hook === "useDeferredValue" && isGenuine(c)) {
292
+ reasons.push(`${hookLabel(c)} caught up with its latest value: this is the deferred second render React schedules after the urgent one; nothing to fix.`);
234
293
  } else if (isGenuine(c)) reasons.push(`${hookLabel(c)} changed.`);
235
294
  else if (isStateHook(c))
236
295
  reasons.push(`${hookLabel(c)} was set to a value deep-equal to the current one (new reference, same contents): reuse the existing object or bail out before calling the setter.`);
@@ -261,6 +320,7 @@ function buildReport(input) {
261
320
  reasons,
262
321
  time: now()
263
322
  };
323
+ if (input.compiled) report.compiled = true;
264
324
  if (input.selfDuration !== void 0) report.selfDuration = input.selfDuration;
265
325
  if (input.treeDuration !== void 0) report.treeDuration = input.treeDuration;
266
326
  if (input.commitPriority) report.commitPriority = input.commitPriority;
@@ -298,10 +358,10 @@ var KIND_LABEL = {
298
358
  };
299
359
  function summarize(report) {
300
360
  const counts2 = /* @__PURE__ */ new Map();
301
- for (const c of [...report.propChanges, ...report.stateChanges, ...report.hookChanges]) {
361
+ for (const c of [...report.propChanges || [], ...report.stateChanges || [], ...report.hookChanges || []]) {
302
362
  counts2.set(c.kind, (counts2.get(c.kind) ?? 0) + 1);
303
363
  }
304
- const parts = [...counts2].map(([kind, n]) => `${n} ${KIND_LABEL[kind]}`);
364
+ const parts = [...counts2].map(([kind, n]) => `${n} ${KIND_LABEL[kind] || kind}`);
305
365
  if (parts.length === 0) parts.push("no changes");
306
366
  return parts.join(", ");
307
367
  }
@@ -328,7 +388,7 @@ function getState() {
328
388
  const g = globalThis;
329
389
  let s = g[KEY];
330
390
  if (!s) {
331
- s = { options: {}, enabled: false, printed: /* @__PURE__ */ new Map(), detach: null, nextInstanceId: 1, nextCommitId: 1, warnedOnce: /* @__PURE__ */ new Set(), scheduled: 0, commits: 0, overheadMs: 0, maxCommitMs: 0 };
391
+ s = { options: {}, enabled: false, printed: /* @__PURE__ */ new Map(), detach: null, nextInstanceId: 1, nextCommitId: 1, warnedOnce: /* @__PURE__ */ new Set(), scheduled: 0, commits: 0, overheadMs: 0, maxCommitMs: 0, truncated: 0 };
332
392
  g[KEY] = s;
333
393
  }
334
394
  return s;
@@ -358,12 +418,33 @@ function dispatch(report, override) {
358
418
  try {
359
419
  options.notifier(report);
360
420
  } catch (err) {
361
- (options.console ?? console).warn("[rerender-lens] notifier threw", err);
421
+ warnOnce("notifier", `notifier threw: ${String(err)}`);
362
422
  }
363
423
  }
364
424
  }
365
425
 
366
426
  // src/hookNames.ts
427
+ var NODE_COUNT = {
428
+ useState: 1,
429
+ useReducer: 1,
430
+ useRef: 1,
431
+ useMemo: 1,
432
+ useCallback: 1,
433
+ useEffect: 1,
434
+ useLayoutEffect: 1,
435
+ useInsertionEffect: 1,
436
+ useImperativeHandle: 1,
437
+ useSyncExternalStore: 2,
438
+ useTransition: 2,
439
+ useDeferredValue: 1,
440
+ useId: 1,
441
+ useOptimistic: 1,
442
+ useActionState: 3,
443
+ useFormState: 3,
444
+ useEffectEvent: 1,
445
+ useCacheRefresh: 1
446
+ };
447
+ var BUILTIN_HOOKS = /* @__PURE__ */ new Set([...Object.keys(NODE_COUNT), "useContext", "useDebugValue", "use"]);
367
448
  var noop = () => {
368
449
  };
369
450
  function customHooksFromStack(stack, componentName, marker) {
@@ -378,7 +459,7 @@ function customHooksFromStack(stack, componentName, marker) {
378
459
  continue;
379
460
  }
380
461
  if (name === componentName || name.endsWith("." + componentName)) break;
381
- if (/^use[A-Z0-9_]/.test(name)) names.push(name);
462
+ if (/^use[A-Z0-9_]/.test(name) && !BUILTIN_HOOKS.has(name)) names.push(name);
382
463
  if (names.length > 8) break;
383
464
  }
384
465
  return names;
@@ -678,6 +759,11 @@ function isMemoizedFiber(fiber) {
678
759
  }
679
760
  return false;
680
761
  }
762
+ function isCompiledFiber(fiber) {
763
+ if (fiber.tag === ClassComponent) return false;
764
+ const cache2 = fiber.updateQueue?.memoCache;
765
+ return !!cache2 && Array.isArray(cache2.data);
766
+ }
681
767
  function durationsOf(fiber) {
682
768
  if (typeof fiber.actualDuration !== "number") return null;
683
769
  const tree = fiber.actualDuration;
@@ -701,11 +787,14 @@ var counts = /* @__PURE__ */ new WeakMap();
701
787
  var ids = /* @__PURE__ */ new WeakMap();
702
788
  var fibersById = /* @__PURE__ */ new Map();
703
789
  var hasWeakRef = typeof WeakRef === "function";
790
+ var pruneAt = 5e3;
704
791
  function remember2(id, fiber) {
705
792
  if (!hasWeakRef) return;
793
+ if (fibersById.get(id)?.deref() === fiber) return;
706
794
  fibersById.set(id, new WeakRef(fiber));
707
- if (fibersById.size > 5e3) {
795
+ if (fibersById.size > pruneAt) {
708
796
  for (const [k, ref] of fibersById) if (!ref.deref()) fibersById.delete(k);
797
+ pruneAt = Math.max(5e3, fibersById.size * 2);
709
798
  }
710
799
  }
711
800
  function instanceIdOf(fiber) {
@@ -760,7 +849,7 @@ function nearestComponent(fiber) {
760
849
  }
761
850
  var INTERNAL_FRAME = /react-dom|react_jsx|jsx-(dev-)?runtime|\/react\/|node_modules\/react|react-stack-bottom-frame|react_stack_bottom_frame|scheduler/;
762
851
  function parseStackLocation(stack) {
763
- for (const line of stack.split("\n")) {
852
+ for (const line of stack.split("\n", 24)) {
764
853
  const m = /(?:at\s+(?:.*?\s+)?\(?|@)?((?:https?|file|webpack|vite|blob):[^\s()]+?):(\d+):(\d+)\)?\s*$/.exec(line.trim());
765
854
  if (!m || !m[1] || INTERNAL_FRAME.test(m[1])) continue;
766
855
  return { fileName: m[1], lineNumber: Number(m[2]), columnNumber: Number(m[3]) };
@@ -776,9 +865,16 @@ function sourceOf(fiber) {
776
865
  return out;
777
866
  }
778
867
  const st = fiber._debugStack;
779
- const text = typeof st === "string" ? st : st && typeof st.stack === "string" ? st.stack : null;
780
- return text ? parseStackLocation(text) : void 0;
868
+ if (st && typeof st === "object") {
869
+ if (sourceCache.has(st)) return sourceCache.get(st);
870
+ const text = typeof st.stack === "string" ? st.stack : null;
871
+ const out = text ? parseStackLocation(text) : void 0;
872
+ sourceCache.set(st, out);
873
+ return out;
874
+ }
875
+ return typeof st === "string" ? parseStackLocation(st) : void 0;
781
876
  }
877
+ var sourceCache = /* @__PURE__ */ new WeakMap();
782
878
  function bumpCount(fiber) {
783
879
  const prev = counts.get(fiber) ?? (fiber.alternate ? counts.get(fiber.alternate) : void 0) ?? 0;
784
880
  const n = prev + 1;
@@ -846,7 +942,8 @@ function diffHooks(fiber, alt) {
846
942
  const labels = hookLabelsFor(fiber, b);
847
943
  let i = 0;
848
944
  while (a && b) {
849
- if ((isStateNode(b) || isStoreNode(b)) && !Object.is(a.memoizedState, b.memoizedState)) {
945
+ const deferred = labels?.[i] === "useDeferredValue";
946
+ if ((deferred || isStateNode(b) || isStoreNode(b)) && !Object.is(a.memoizedState, b.memoizedState)) {
850
947
  const hook = labels?.[i] ?? hookLabel2(b);
851
948
  out.push({
852
949
  path: `${hook}#${i}`,
@@ -880,6 +977,10 @@ function diffContexts(fiber, alt) {
880
977
  let a = alt.dependencies?.firstContext ?? null;
881
978
  let b = fiber.dependencies?.firstContext ?? null;
882
979
  let i = 0;
980
+ if (b && !("memoizedValue" in b)) {
981
+ warnOnce("contextValues", "this React does not record context values on the fiber (React < 18): context changes are reported as plain parent re-renders.");
982
+ return out;
983
+ }
883
984
  while (a && b) {
884
985
  if (a.context === b.context && !Object.is(a.memoizedValue, b.memoizedValue)) {
885
986
  const name = b.context.displayName ?? "Context";
@@ -912,10 +1013,26 @@ function diffContexts(fiber, alt) {
912
1013
  return out;
913
1014
  }
914
1015
  var isGenuine2 = (c) => c.kind === "different" || c.kind === "added" || c.kind === "removed";
1016
+ var isRefObject2 = (v) => {
1017
+ if (typeof v !== "object" || v === null) return false;
1018
+ const keys = Object.keys(v);
1019
+ return keys.length === 1 && keys[0] === "current";
1020
+ };
1021
+ function diffProps(prev, next) {
1022
+ if (prev === next) return [];
1023
+ const pr = prev.ref;
1024
+ const nr = next.ref;
1025
+ if (pr !== nr && isRefObject2(pr) && isRefObject2(nr)) {
1026
+ const { ref: _p, ...restPrev } = prev;
1027
+ const { ref: _n, ...restNext } = next;
1028
+ return [{ path: "ref", kind: "deep-equal", prev: pr, next: nr }, ...diffRecords(restPrev, restNext)];
1029
+ }
1030
+ return diffRecords(prev, next);
1031
+ }
915
1032
  function analyze(fiber, alt, trackHooks) {
916
1033
  const prevProps = alt.memoizedProps ?? {};
917
1034
  const nextProps = fiber.memoizedProps ?? {};
918
- const propChanges = prevProps === nextProps ? [] : diffRecords(prevProps, nextProps);
1035
+ const propChanges = diffProps(prevProps, nextProps);
919
1036
  let stateChanges = [];
920
1037
  let hookChanges = [];
921
1038
  if (fiber.tag === ClassComponent) {
@@ -928,7 +1045,7 @@ function analyze(fiber, alt, trackHooks) {
928
1045
  } else if (trackHooks) {
929
1046
  hookChanges = [...diffHooks(fiber, alt), ...diffContexts(fiber, alt)];
930
1047
  }
931
- const isStateHook = (h) => h.hook !== "useContext" && h.hook !== "useSyncExternalStore";
1048
+ const isStateHook = (h) => h.hook !== "useContext" && h.hook !== "useSyncExternalStore" && h.hook !== "useDeferredValue";
932
1049
  const causes = [];
933
1050
  if (propChanges.some(isGenuine2)) causes.push("props");
934
1051
  if (stateChanges.some(isGenuine2) || hookChanges.some((h) => isStateHook(h) && isGenuine2(h))) causes.push("state");
@@ -966,6 +1083,26 @@ function updatersOf(root) {
966
1083
  }
967
1084
  var lastCommit = null;
968
1085
  var EFFECT_LOOP_WINDOW_MS = 50;
1086
+ var MAX_REPORTS_PER_COMMIT = 200;
1087
+ var COMMIT_TIME_BUDGET_MS = 25;
1088
+ function isDeferredCatchUp(fiber) {
1089
+ const types = fiber._debugHookTypes;
1090
+ if (!types || !types.includes("useDeferredValue") || !fiber.alternate) return false;
1091
+ const first = fiber.memoizedState;
1092
+ if (!isHookList(first)) return false;
1093
+ const labels = hookLabelsFor(fiber, first);
1094
+ if (!labels) return false;
1095
+ let a = fiber.alternate.memoizedState;
1096
+ let b = first;
1097
+ for (let i = 0; a && b; a = a.next, b = b.next, i++) {
1098
+ if (labels[i] === "useDeferredValue" && !Object.is(a.memoizedState, b.memoizedState)) return true;
1099
+ }
1100
+ return false;
1101
+ }
1102
+ function isUnder(fiber, boundaries) {
1103
+ for (let f = fiber.return; f && f.tag !== HostRoot; f = f.return) if (f.tag === SuspenseComponent && boundaries.includes(f)) return true;
1104
+ return false;
1105
+ }
969
1106
  function onCommit(root, commitPriority) {
970
1107
  const s = getState();
971
1108
  if (!s.enabled) return;
@@ -975,7 +1112,7 @@ function onCommit(root, commitPriority) {
975
1112
  const rendered = [];
976
1113
  const renderedNames = /* @__PURE__ */ new Set();
977
1114
  let hot = false;
978
- let suspenseResolved = false;
1115
+ let resolvedBoundaries = null;
979
1116
  const stack = [root.current];
980
1117
  while (stack.length) {
981
1118
  const fiber = stack.pop();
@@ -985,7 +1122,7 @@ function onCommit(root, commitPriority) {
985
1122
  renderedNames.add(fiberName(fiber));
986
1123
  if (shouldTrack(fiberType(fiber), o)) rendered.push(fiber);
987
1124
  }
988
- if (fiber.tag === SuspenseComponent && alt && alt.memoizedState !== null && fiber.memoizedState === null) suspenseResolved = true;
1125
+ if (fiber.tag === SuspenseComponent && alt && alt.memoizedState !== null && fiber.memoizedState === null) (resolvedBoundaries ?? (resolvedBoundaries = [])).push(fiber);
989
1126
  if (!alt || fiber.child !== alt.child) {
990
1127
  for (let c = fiber.child; c; c = c.sibling) stack.push(c);
991
1128
  }
@@ -996,10 +1133,11 @@ function onCommit(root, commitPriority) {
996
1133
  const updaters = updatersOf(root);
997
1134
  let commitCause;
998
1135
  let afterCommit;
999
- if (lastCommit && at - lastCommit.at < EFFECT_LOOP_WINDOW_MS && updaters.some((u) => lastCommit.rendered.has(u))) {
1136
+ const inputDriven = commitPriority === "immediate" || commitPriority === "user-blocking";
1137
+ if (!inputDriven && lastCommit && at - lastCommit.at < EFFECT_LOOP_WINDOW_MS && updaters.some((u) => lastCommit.rendered.has(u)) && !rendered.some(isDeferredCatchUp)) {
1000
1138
  commitCause = "effect-after-commit";
1001
1139
  afterCommit = lastCommit.id;
1002
- } else if (suspenseResolved) commitCause = "suspense-resolved";
1140
+ } else if (resolvedBoundaries) commitCause = "suspense-resolved";
1003
1141
  if (rendered.length === 0) {
1004
1142
  lastCommit = { id: s.nextCommitId, at, rendered: renderedNames };
1005
1143
  return;
@@ -1008,47 +1146,62 @@ function onCommit(root, commitPriority) {
1008
1146
  lastCommit = { id: commitId, at, rendered: renderedNames };
1009
1147
  const dispatcherRef = o.resolveHookNames ? getDispatcherRef() : null;
1010
1148
  const parentCache = /* @__PURE__ */ new Map();
1011
- for (const fiber of rendered) {
1012
- const alt = fiber.alternate;
1013
- const a = analyze(fiber, alt, trackHooks);
1014
- const durations = durationsOf(fiber);
1015
- let hookState = o.includeState !== false && trackHooks ? snapshotHooks(fiber) : void 0;
1016
- if (dispatcherRef && fiber.tag !== ClassComponent && (a.hookChanges.length || hookState && hookState.length)) {
1017
- const names = resolveHookNames(fiber, dispatcherRef);
1018
- if (names.size) {
1019
- for (const c of a.hookChanges) if (c.hook !== "useContext" && names.has(c.index)) c.custom = names.get(c.index);
1020
- if (hookState) hookState = hookState.map((h) => names.has(h.index) ? { ...h, custom: names.get(h.index) } : h);
1149
+ const endDiffScope = beginDiffScope();
1150
+ const deadline = at + COMMIT_TIME_BUDGET_MS;
1151
+ try {
1152
+ for (let i = 0; i < rendered.length; i++) {
1153
+ if (i >= MAX_REPORTS_PER_COMMIT || (i & 15) === 15 && nowMs() > deadline) {
1154
+ s.truncated += rendered.length - i;
1155
+ warnOnce("truncated", `a commit rendered ${rendered.length} tracked components; only ${i} were reported (see info().truncated). Narrow \`include\` or turn off includeState.`);
1156
+ break;
1157
+ }
1158
+ const fiber = rendered[i];
1159
+ const alt = fiber.alternate;
1160
+ const a = analyze(fiber, alt, trackHooks);
1161
+ const durations = durationsOf(fiber);
1162
+ let hookState = o.includeState !== false && trackHooks ? snapshotHooks(fiber) : void 0;
1163
+ if (dispatcherRef && fiber.tag !== ClassComponent && (a.hookChanges.length || hookState && hookState.length)) {
1164
+ const names = resolveHookNames(fiber, dispatcherRef);
1165
+ if (names.size) {
1166
+ for (const c of a.hookChanges) if (c.hook !== "useContext" && names.has(c.index)) c.custom = names.get(c.index);
1167
+ if (hookState) hookState = hookState.map((h) => names.has(h.index) ? { ...h, custom: names.get(h.index) } : h);
1168
+ }
1021
1169
  }
1170
+ const report = buildReport({
1171
+ component: fiberName(fiber),
1172
+ instanceId: instanceId(fiber),
1173
+ renderCount: bumpCount(fiber),
1174
+ prevProps: alt.memoizedProps ?? {},
1175
+ nextProps: fiber.memoizedProps ?? {},
1176
+ propChanges: a.propChanges,
1177
+ stateChanges: a.stateChanges,
1178
+ hookChanges: a.hookChanges,
1179
+ ...o.includeState !== false ? {
1180
+ hookState,
1181
+ contexts: trackHooks ? snapshotContexts(fiber) : void 0,
1182
+ state: fiber.tag === ClassComponent && fiber.memoizedState && typeof fiber.memoizedState === "object" ? fiber.memoizedState : void 0
1183
+ } : {},
1184
+ updaters,
1185
+ commitCause,
1186
+ afterCommit,
1187
+ key: fiber.key === null || fiber.key === void 0 ? null : String(fiber.key),
1188
+ parent: a.trigger === "parent" ? nearestRenderedAncestor(fiber, parentCache, trackHooks) : null,
1189
+ owner: ownerName(fiber),
1190
+ path: componentPath(fiber),
1191
+ memoized: isMemoizedFiber(fiber),
1192
+ compiled: isCompiledFiber(fiber),
1193
+ classComponent: fiber.tag === ClassComponent,
1194
+ revealed: resolvedBoundaries !== null && a.trigger === "parent" && isUnder(fiber, resolvedBoundaries),
1195
+ selfDuration: durations ? durations.self : void 0,
1196
+ treeDuration: durations ? durations.tree : void 0,
1197
+ commitId,
1198
+ commitPriority,
1199
+ source: sourceOf(fiber)
1200
+ });
1201
+ dispatch(report);
1022
1202
  }
1023
- const report = buildReport({
1024
- component: fiberName(fiber),
1025
- instanceId: instanceId(fiber),
1026
- renderCount: bumpCount(fiber),
1027
- prevProps: alt.memoizedProps ?? {},
1028
- nextProps: fiber.memoizedProps ?? {},
1029
- propChanges: a.propChanges,
1030
- stateChanges: a.stateChanges,
1031
- hookChanges: a.hookChanges,
1032
- ...o.includeState !== false ? {
1033
- hookState,
1034
- contexts: trackHooks ? snapshotContexts(fiber) : void 0,
1035
- state: fiber.tag === ClassComponent && fiber.memoizedState && typeof fiber.memoizedState === "object" ? fiber.memoizedState : void 0
1036
- } : {},
1037
- updaters,
1038
- commitCause,
1039
- afterCommit,
1040
- key: fiber.key === null || fiber.key === void 0 ? null : String(fiber.key),
1041
- parent: a.trigger === "parent" ? nearestRenderedAncestor(fiber, parentCache, trackHooks) : null,
1042
- owner: ownerName(fiber),
1043
- path: componentPath(fiber),
1044
- memoized: isMemoizedFiber(fiber),
1045
- selfDuration: durations ? durations.self : void 0,
1046
- treeDuration: durations ? durations.tree : void 0,
1047
- commitId,
1048
- commitPriority,
1049
- source: sourceOf(fiber)
1050
- });
1051
- dispatch(report);
1203
+ } finally {
1204
+ endDiffScope();
1052
1205
  }
1053
1206
  }
1054
1207
 
@@ -1177,13 +1330,24 @@ function highlight(target) {
1177
1330
  for (const s of l.sticky) s.remove();
1178
1331
  l.sticky = [];
1179
1332
  if (!target) return;
1180
- for (const node of target.nodes) {
1181
- const rect = node.getBoundingClientRect();
1182
- if (rect.width === 0 && rect.height === 0) continue;
1333
+ const frag = document.createDocumentFragment();
1334
+ for (const rect of measure(target.nodes)) {
1183
1335
  const b = box(rect, "#1a73e8", "rgba(26, 115, 232, 0.12)", target.label);
1184
- l.root.appendChild(b);
1336
+ frag.appendChild(b);
1185
1337
  l.sticky.push(b);
1186
1338
  }
1339
+ l.root.appendChild(frag);
1340
+ }
1341
+ var MAX_BOXES = 100;
1342
+ function measure(nodes) {
1343
+ const rects = [];
1344
+ for (const node of nodes) {
1345
+ if (rects.length >= MAX_BOXES) break;
1346
+ const rect = node.getBoundingClientRect();
1347
+ if (rect.width === 0 && rect.height === 0) continue;
1348
+ rects.push(rect);
1349
+ }
1350
+ return rects;
1187
1351
  }
1188
1352
  function clearHighlight() {
1189
1353
  highlight(null);
@@ -1191,27 +1355,34 @@ function clearHighlight() {
1191
1355
  function flash(target, duration = 500) {
1192
1356
  const l = ensureLayer();
1193
1357
  if (!l) return;
1194
- for (const node of target.nodes) {
1195
- const rect = node.getBoundingClientRect();
1196
- if (rect.width === 0 && rect.height === 0) continue;
1358
+ const frag = document.createDocumentFragment();
1359
+ const boxes = [];
1360
+ for (const rect of measure(target.nodes)) {
1197
1361
  const b = box(rect, "#d93025", "rgba(217, 48, 37, 0.15)");
1198
1362
  b.style.transition = `opacity ${duration}ms ease-out`;
1199
- l.root.appendChild(b);
1200
- requestAnimationFrame(() => {
1201
- b.style.opacity = "0";
1202
- });
1203
- setTimeout(() => b.remove(), duration + 50);
1363
+ frag.appendChild(b);
1364
+ boxes.push(b);
1204
1365
  }
1366
+ if (!boxes.length) return;
1367
+ l.root.appendChild(frag);
1368
+ requestAnimationFrame(() => {
1369
+ for (const b of boxes) b.style.opacity = "0";
1370
+ });
1371
+ setTimeout(() => {
1372
+ for (const b of boxes) b.remove();
1373
+ }, duration + 50);
1205
1374
  }
1206
1375
 
1207
1376
  // src/version.ts
1208
- var VERSION = "0.3.0" ;
1377
+ var VERSION = "0.5.0" ;
1209
1378
 
1210
1379
  // src/devtools.ts
1211
1380
  var DEVTOOLS_MARKER = "__rerenderLens";
1212
1381
  var PROTOCOL_VERSION = 2;
1213
1382
  var DEFAULT_CHANNEL = "rerender-lens";
1214
- function serialize(value, maxDepth = 6, seen = /* @__PURE__ */ new WeakSet(), depth = 0) {
1383
+ var SERIALIZE_MAX_ENTRIES = 100;
1384
+ var SERIALIZE_MAX_NODES = 2e4;
1385
+ function serialize(value, maxDepth = 4, seen = /* @__PURE__ */ new WeakSet(), depth = 0, budget = { nodes: SERIALIZE_MAX_NODES }) {
1215
1386
  if (value === null || value === void 0) return value;
1216
1387
  const t = typeof value;
1217
1388
  if (t === "string" || t === "boolean") return value;
@@ -1222,24 +1393,56 @@ function serialize(value, maxDepth = 6, seen = /* @__PURE__ */ new WeakSet(), de
1222
1393
  const obj = value;
1223
1394
  if (seen.has(obj)) return "[Circular]";
1224
1395
  if (depth >= maxDepth) return "[\u2026]";
1396
+ if (--budget.nodes < 0) return "[\u2026]";
1397
+ const next = (v) => serialize(v, maxDepth, seen, depth + 1, budget);
1225
1398
  seen.add(obj);
1226
1399
  try {
1227
1400
  if (isReactElement(obj)) {
1228
1401
  const out2 = { $type: "element", name: getDisplayName(obj.type) };
1229
1402
  if (obj.key !== null && obj.key !== void 0) out2.key = String(obj.key);
1230
- out2.props = serialize(obj.props, maxDepth, seen, depth + 1);
1403
+ out2.props = next(obj.props);
1231
1404
  return out2;
1232
1405
  }
1233
1406
  if (obj instanceof Date) return { $type: "Date", value: obj.toISOString() };
1234
1407
  if (obj instanceof RegExp) return { $type: "RegExp", value: String(obj) };
1408
+ if (ArrayBuffer.isView(obj) || obj instanceof ArrayBuffer) {
1409
+ const bin = obj;
1410
+ return { $type: bin.constructor?.name || "ArrayBuffer", length: typeof bin.length === "number" ? bin.length : bin.byteLength };
1411
+ }
1412
+ if (obj instanceof Promise) return "[Promise]";
1413
+ if (typeof Node !== "undefined" && obj instanceof Node) return typeof Element !== "undefined" && obj instanceof Element ? `<${obj.tagName.toLowerCase()}>` : `[${obj.nodeName}]`;
1414
+ if (obj === globalThis) return "[Window]";
1235
1415
  if (obj instanceof Map) {
1236
- return { $type: "Map", entries: [...obj].map(([k, v]) => [serialize(k, maxDepth, seen, depth + 1), serialize(v, maxDepth, seen, depth + 1)]) };
1416
+ const entries = [];
1417
+ for (const [k, v] of obj) {
1418
+ if (entries.length >= SERIALIZE_MAX_ENTRIES) {
1419
+ entries.push(["\u2026", `+${obj.size - SERIALIZE_MAX_ENTRIES} more`]);
1420
+ break;
1421
+ }
1422
+ entries.push([next(k), next(v)]);
1423
+ }
1424
+ return { $type: "Map", entries };
1425
+ }
1426
+ if (obj instanceof Set) {
1427
+ const values = [];
1428
+ for (const v of obj) {
1429
+ if (values.length >= SERIALIZE_MAX_ENTRIES) {
1430
+ values.push(`\u2026+${obj.size - SERIALIZE_MAX_ENTRIES} more`);
1431
+ break;
1432
+ }
1433
+ values.push(next(v));
1434
+ }
1435
+ return { $type: "Set", values };
1436
+ }
1437
+ if (Array.isArray(obj)) {
1438
+ const out2 = obj.slice(0, SERIALIZE_MAX_ENTRIES).map(next);
1439
+ if (obj.length > SERIALIZE_MAX_ENTRIES) out2.push(`\u2026+${obj.length - SERIALIZE_MAX_ENTRIES} more`);
1440
+ return out2;
1237
1441
  }
1238
- if (obj instanceof Set) return { $type: "Set", values: [...obj].map((v) => serialize(v, maxDepth, seen, depth + 1)) };
1239
- if (Array.isArray(obj)) return obj.map((v) => serialize(v, maxDepth, seen, depth + 1));
1240
- if (typeof Element !== "undefined" && obj instanceof Element) return `<${obj.tagName.toLowerCase()}>`;
1241
1442
  const out = {};
1242
- for (const k of Object.keys(obj)) out[k] = serialize(obj[k], maxDepth, seen, depth + 1);
1443
+ const keys = Object.keys(obj);
1444
+ for (const k of keys.slice(0, SERIALIZE_MAX_ENTRIES)) out[k] = next(obj[k]);
1445
+ if (keys.length > SERIALIZE_MAX_ENTRIES) out["\u2026"] = `+${keys.length - SERIALIZE_MAX_ENTRIES} more`;
1243
1446
  const proto = Object.getPrototypeOf(obj);
1244
1447
  if (proto && proto !== Object.prototype && proto.constructor?.name) out.$type = proto.constructor.name;
1245
1448
  return out;
@@ -1279,13 +1482,20 @@ function deserializeOptions(o) {
1279
1482
  }
1280
1483
  function createDevtoolsNotifier(options = {}) {
1281
1484
  const bufferSize = options.bufferSize ?? 300;
1282
- const maxDepth = options.maxDepth ?? 6;
1485
+ const maxDepth = options.maxDepth ?? 4;
1283
1486
  const target = options.target ?? (typeof window !== "undefined" ? window : void 0);
1284
1487
  const buffer = [];
1285
1488
  let seq = 0;
1286
1489
  let flashOn = options.flashAvoidable ?? false;
1490
+ let live = !target || typeof target.addEventListener !== "function";
1491
+ if (!live) {
1492
+ target.addEventListener("message", (event) => {
1493
+ const data = event.data;
1494
+ if ((event.source === target || !event.source) && data && data.__rerenderLensReady === true) live = true;
1495
+ });
1496
+ }
1287
1497
  const post = (type, payload) => {
1288
- if (!target) return;
1498
+ if (!target || type === "report" && !live) return;
1289
1499
  const msg = { [DEVTOOLS_MARKER]: true, version: PROTOCOL_VERSION, type, payload };
1290
1500
  target.postMessage(msg, "*");
1291
1501
  };
@@ -1302,10 +1512,12 @@ function createDevtoolsNotifier(options = {}) {
1302
1512
  injected: typeof window !== "undefined" && typeof window.__RERENDER_LENS_INJECTED__ === "string",
1303
1513
  scheduled: getState().scheduled,
1304
1514
  commits: getState().commits,
1305
- overhead: { totalMs: getState().overheadMs, maxCommitMs: getState().maxCommitMs }
1515
+ overhead: { totalMs: getState().overheadMs, maxCommitMs: getState().maxCommitMs },
1516
+ truncated: getState().truncated
1306
1517
  });
1307
1518
  const bridge = {
1308
1519
  replay: () => {
1520
+ live = true;
1309
1521
  post("hello", info());
1310
1522
  for (const e of buffer) post("report", e.payload);
1311
1523
  },
@@ -1495,15 +1707,16 @@ function createDevtoolsNotifier(options = {}) {
1495
1707
  }
1496
1708
 
1497
1709
  // src/setup.ts
1498
- var env = (key) => {
1710
+ var read = (get) => {
1499
1711
  try {
1500
- return typeof process !== "undefined" && process && process.env ? process.env[key] : void 0;
1712
+ return get();
1501
1713
  } catch {
1502
1714
  return void 0;
1503
1715
  }
1504
1716
  };
1505
- if (env("NODE_ENV") !== "production" && typeof window !== "undefined" && !window.__RERENDER_LENS_DEVTOOLS__) {
1506
- const relay = env("RERENDER_LENS_RELAY") || env("NEXT_PUBLIC_RERENDER_LENS_RELAY");
1717
+ var production = read(() => process.env.NODE_ENV) === "production";
1718
+ if (!production && typeof window !== "undefined" && !window.__RERENDER_LENS_DEVTOOLS__) {
1719
+ const relay = read(() => process.env.RERENDER_LENS_RELAY) || read(() => process.env.NEXT_PUBLIC_RERENDER_LENS_RELAY);
1507
1720
  init({ trackAllMemoized: true, notifier: createDevtoolsNotifier(relay ? { relay } : {}) });
1508
1721
  }
1509
1722
  //# sourceMappingURL=setup.cjs.map