rerender-lens 0.3.0 → 0.4.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.
@@ -28,10 +28,49 @@ function remember(seen, a, b) {
28
28
  set.add(b);
29
29
  return false;
30
30
  }
31
+ var scope = null;
32
+ var DEFAULT_DIFF_BUDGET = 5e4;
33
+ function beginDiffScope(budget = 2e5) {
34
+ const previous = scope;
35
+ scope = { cache: /* @__PURE__ */ new WeakMap(), remaining: budget, exhausted: false };
36
+ return () => {
37
+ scope = previous;
38
+ };
39
+ }
40
+ function diffBudgetExhausted() {
41
+ return scope?.exhausted === true;
42
+ }
31
43
  function deepEqual(a, b, seen = /* @__PURE__ */ new Map()) {
32
44
  if (Object.is(a, b)) return true;
33
45
  if (typeof a !== typeof b) return false;
34
46
  if (typeof a !== "object" || a === null || b === null) return false;
47
+ if (!scope) {
48
+ const end = beginDiffScope(DEFAULT_DIFF_BUDGET);
49
+ try {
50
+ return deepEqual(a, b, seen);
51
+ } finally {
52
+ end();
53
+ }
54
+ }
55
+ const sc = scope;
56
+ const objA = a;
57
+ const objB = b;
58
+ const hit = sc.cache.get(objA)?.get(objB);
59
+ if (hit !== void 0) return hit;
60
+ if (--sc.remaining < 0) {
61
+ sc.exhausted = true;
62
+ return false;
63
+ }
64
+ const result = deepEqualObjects(objA, objB, seen);
65
+ let m = sc.cache.get(objA);
66
+ if (!m) {
67
+ m = /* @__PURE__ */ new WeakMap();
68
+ sc.cache.set(objA, m);
69
+ }
70
+ m.set(objB, result);
71
+ return result;
72
+ }
73
+ function deepEqualObjects(a, b, seen) {
35
74
  const objA = a;
36
75
  const objB = b;
37
76
  if (remember(seen, objA, objB)) return true;
@@ -60,6 +99,10 @@ function deepEqual(a, b, seen = /* @__PURE__ */ new Map()) {
60
99
  }
61
100
  if (a instanceof Set) {
62
101
  if (!(b instanceof Set) || a.size !== b.size) return false;
102
+ if (a.size > 50) {
103
+ for (const v of a) if (!b.has(v)) return false;
104
+ return true;
105
+ }
63
106
  outer: for (const v of a) {
64
107
  if (b.has(v)) continue;
65
108
  for (const w of b) if (deepEqual(v, w, seen)) continue outer;
@@ -75,9 +118,10 @@ function deepEqual(a, b, seen = /* @__PURE__ */ new Map()) {
75
118
  const ka = Object.keys(a);
76
119
  const kb = Object.keys(b);
77
120
  if (ka.length !== kb.length) return false;
121
+ const rb = b;
78
122
  for (const k of ka) {
79
123
  if (!Object.prototype.hasOwnProperty.call(b, k)) return false;
80
- if (!deepEqual(a[k], b[k], seen)) return false;
124
+ if (!deepEqual(a[k], rb[k], seen)) return false;
81
125
  }
82
126
  return true;
83
127
  }
@@ -119,7 +163,7 @@ function diffRecords(prev, next, basePath = "") {
119
163
  if (Object.is(a, b)) continue;
120
164
  const kind = classify(a, b);
121
165
  if (kind === "different") {
122
- changes.push({ path: firstDifferentPath(a, b, path), kind, prev: a, next: b });
166
+ changes.push({ path: diffBudgetExhausted() ? path : firstDifferentPath(a, b, path), kind, prev: a, next: b });
123
167
  } else {
124
168
  changes.push({ path, kind, prev: a, next: b });
125
169
  }
@@ -330,7 +374,7 @@ function getState() {
330
374
  const g = globalThis;
331
375
  let s = g[KEY];
332
376
  if (!s) {
333
- s = { options: {}, enabled: false, printed: /* @__PURE__ */ new Map(), detach: null, nextInstanceId: 1, nextCommitId: 1, warnedOnce: /* @__PURE__ */ new Set(), scheduled: 0, commits: 0, overheadMs: 0, maxCommitMs: 0 };
377
+ s = { options: {}, enabled: false, printed: /* @__PURE__ */ new Map(), detach: null, nextInstanceId: 1, nextCommitId: 1, warnedOnce: /* @__PURE__ */ new Set(), scheduled: 0, commits: 0, overheadMs: 0, maxCommitMs: 0, truncated: 0 };
334
378
  g[KEY] = s;
335
379
  }
336
380
  return s;
@@ -360,7 +404,7 @@ function dispatch(report, override) {
360
404
  try {
361
405
  options.notifier(report);
362
406
  } catch (err) {
363
- (options.console ?? console).warn("[rerender-lens] notifier threw", err);
407
+ warnOnce("notifier", `notifier threw: ${String(err)}`);
364
408
  }
365
409
  }
366
410
  }
@@ -748,11 +792,14 @@ var counts = /* @__PURE__ */ new WeakMap();
748
792
  var ids = /* @__PURE__ */ new WeakMap();
749
793
  var fibersById = /* @__PURE__ */ new Map();
750
794
  var hasWeakRef = typeof WeakRef === "function";
795
+ var pruneAt = 5e3;
751
796
  function remember2(id, fiber) {
752
797
  if (!hasWeakRef) return;
798
+ if (fibersById.get(id)?.deref() === fiber) return;
753
799
  fibersById.set(id, new WeakRef(fiber));
754
- if (fibersById.size > 5e3) {
800
+ if (fibersById.size > pruneAt) {
755
801
  for (const [k, ref] of fibersById) if (!ref.deref()) fibersById.delete(k);
802
+ pruneAt = Math.max(5e3, fibersById.size * 2);
756
803
  }
757
804
  }
758
805
  function instanceIdOf(fiber) {
@@ -781,7 +828,7 @@ function nearestComponent(fiber) {
781
828
  }
782
829
  var INTERNAL_FRAME = /react-dom|react_jsx|jsx-(dev-)?runtime|\/react\/|node_modules\/react|react-stack-bottom-frame|react_stack_bottom_frame|scheduler/;
783
830
  function parseStackLocation(stack) {
784
- for (const line of stack.split("\n")) {
831
+ for (const line of stack.split("\n", 24)) {
785
832
  const m = /(?:at\s+(?:.*?\s+)?\(?|@)?((?:https?|file|webpack|vite|blob):[^\s()]+?):(\d+):(\d+)\)?\s*$/.exec(line.trim());
786
833
  if (!m || !m[1] || INTERNAL_FRAME.test(m[1])) continue;
787
834
  return { fileName: m[1], lineNumber: Number(m[2]), columnNumber: Number(m[3]) };
@@ -797,9 +844,16 @@ function sourceOf(fiber) {
797
844
  return out;
798
845
  }
799
846
  const st = fiber._debugStack;
800
- const text = typeof st === "string" ? st : st && typeof st.stack === "string" ? st.stack : null;
801
- return text ? parseStackLocation(text) : void 0;
847
+ if (st && typeof st === "object") {
848
+ if (sourceCache.has(st)) return sourceCache.get(st);
849
+ const text = typeof st.stack === "string" ? st.stack : null;
850
+ const out = text ? parseStackLocation(text) : void 0;
851
+ sourceCache.set(st, out);
852
+ return out;
853
+ }
854
+ return typeof st === "string" ? parseStackLocation(st) : void 0;
802
855
  }
856
+ var sourceCache = /* @__PURE__ */ new WeakMap();
803
857
  function bumpCount(fiber) {
804
858
  const prev = counts.get(fiber) ?? (fiber.alternate ? counts.get(fiber.alternate) : void 0) ?? 0;
805
859
  const n = prev + 1;
@@ -987,6 +1041,8 @@ function updatersOf(root) {
987
1041
  }
988
1042
  var lastCommit = null;
989
1043
  var EFFECT_LOOP_WINDOW_MS = 50;
1044
+ var MAX_REPORTS_PER_COMMIT = 200;
1045
+ var COMMIT_TIME_BUDGET_MS = 25;
990
1046
  function onCommit(root, commitPriority) {
991
1047
  const s = getState();
992
1048
  if (!s.enabled) return;
@@ -1017,7 +1073,8 @@ function onCommit(root, commitPriority) {
1017
1073
  const updaters = updatersOf(root);
1018
1074
  let commitCause;
1019
1075
  let afterCommit;
1020
- if (lastCommit && at - lastCommit.at < EFFECT_LOOP_WINDOW_MS && updaters.some((u) => lastCommit.rendered.has(u))) {
1076
+ const inputDriven = commitPriority === "immediate" || commitPriority === "user-blocking";
1077
+ if (!inputDriven && lastCommit && at - lastCommit.at < EFFECT_LOOP_WINDOW_MS && updaters.some((u) => lastCommit.rendered.has(u))) {
1021
1078
  commitCause = "effect-after-commit";
1022
1079
  afterCommit = lastCommit.id;
1023
1080
  } else if (suspenseResolved) commitCause = "suspense-resolved";
@@ -1029,47 +1086,59 @@ function onCommit(root, commitPriority) {
1029
1086
  lastCommit = { id: commitId, at, rendered: renderedNames };
1030
1087
  const dispatcherRef = o.resolveHookNames ? getDispatcherRef() : null;
1031
1088
  const parentCache = /* @__PURE__ */ new Map();
1032
- for (const fiber of rendered) {
1033
- const alt = fiber.alternate;
1034
- const a = analyze(fiber, alt, trackHooks);
1035
- const durations = durationsOf(fiber);
1036
- let hookState = o.includeState !== false && trackHooks ? snapshotHooks(fiber) : void 0;
1037
- if (dispatcherRef && fiber.tag !== ClassComponent && (a.hookChanges.length || hookState && hookState.length)) {
1038
- const names = resolveHookNames(fiber, dispatcherRef);
1039
- if (names.size) {
1040
- for (const c of a.hookChanges) if (c.hook !== "useContext" && names.has(c.index)) c.custom = names.get(c.index);
1041
- if (hookState) hookState = hookState.map((h) => names.has(h.index) ? { ...h, custom: names.get(h.index) } : h);
1089
+ const endDiffScope = beginDiffScope();
1090
+ const deadline = at + COMMIT_TIME_BUDGET_MS;
1091
+ try {
1092
+ for (let i = 0; i < rendered.length; i++) {
1093
+ if (i >= MAX_REPORTS_PER_COMMIT || (i & 15) === 15 && nowMs() > deadline) {
1094
+ s.truncated += rendered.length - i;
1095
+ warnOnce("truncated", `a commit rendered ${rendered.length} tracked components; only ${i} were reported (see info().truncated). Narrow \`include\` or turn off includeState.`);
1096
+ break;
1097
+ }
1098
+ const fiber = rendered[i];
1099
+ const alt = fiber.alternate;
1100
+ const a = analyze(fiber, alt, trackHooks);
1101
+ const durations = durationsOf(fiber);
1102
+ let hookState = o.includeState !== false && trackHooks ? snapshotHooks(fiber) : void 0;
1103
+ if (dispatcherRef && fiber.tag !== ClassComponent && (a.hookChanges.length || hookState && hookState.length)) {
1104
+ const names = resolveHookNames(fiber, dispatcherRef);
1105
+ if (names.size) {
1106
+ for (const c of a.hookChanges) if (c.hook !== "useContext" && names.has(c.index)) c.custom = names.get(c.index);
1107
+ if (hookState) hookState = hookState.map((h) => names.has(h.index) ? { ...h, custom: names.get(h.index) } : h);
1108
+ }
1042
1109
  }
1110
+ const report = buildReport({
1111
+ component: fiberName(fiber),
1112
+ instanceId: instanceId(fiber),
1113
+ renderCount: bumpCount(fiber),
1114
+ prevProps: alt.memoizedProps ?? {},
1115
+ nextProps: fiber.memoizedProps ?? {},
1116
+ propChanges: a.propChanges,
1117
+ stateChanges: a.stateChanges,
1118
+ hookChanges: a.hookChanges,
1119
+ ...o.includeState !== false ? {
1120
+ hookState,
1121
+ contexts: trackHooks ? snapshotContexts(fiber) : void 0,
1122
+ state: fiber.tag === ClassComponent && fiber.memoizedState && typeof fiber.memoizedState === "object" ? fiber.memoizedState : void 0
1123
+ } : {},
1124
+ updaters,
1125
+ commitCause,
1126
+ afterCommit,
1127
+ key: fiber.key === null || fiber.key === void 0 ? null : String(fiber.key),
1128
+ parent: a.trigger === "parent" ? nearestRenderedAncestor(fiber, parentCache, trackHooks) : null,
1129
+ owner: ownerName(fiber),
1130
+ path: componentPath(fiber),
1131
+ memoized: isMemoizedFiber(fiber),
1132
+ selfDuration: durations ? durations.self : void 0,
1133
+ treeDuration: durations ? durations.tree : void 0,
1134
+ commitId,
1135
+ commitPriority,
1136
+ source: sourceOf(fiber)
1137
+ });
1138
+ dispatch(report);
1043
1139
  }
1044
- const report = buildReport({
1045
- component: fiberName(fiber),
1046
- instanceId: instanceId(fiber),
1047
- renderCount: bumpCount(fiber),
1048
- prevProps: alt.memoizedProps ?? {},
1049
- nextProps: fiber.memoizedProps ?? {},
1050
- propChanges: a.propChanges,
1051
- stateChanges: a.stateChanges,
1052
- hookChanges: a.hookChanges,
1053
- ...o.includeState !== false ? {
1054
- hookState,
1055
- contexts: trackHooks ? snapshotContexts(fiber) : void 0,
1056
- state: fiber.tag === ClassComponent && fiber.memoizedState && typeof fiber.memoizedState === "object" ? fiber.memoizedState : void 0
1057
- } : {},
1058
- updaters,
1059
- commitCause,
1060
- afterCommit,
1061
- key: fiber.key === null || fiber.key === void 0 ? null : String(fiber.key),
1062
- parent: a.trigger === "parent" ? nearestRenderedAncestor(fiber, parentCache, trackHooks) : null,
1063
- owner: ownerName(fiber),
1064
- path: componentPath(fiber),
1065
- memoized: isMemoizedFiber(fiber),
1066
- selfDuration: durations ? durations.self : void 0,
1067
- treeDuration: durations ? durations.tree : void 0,
1068
- commitId,
1069
- commitPriority,
1070
- source: sourceOf(fiber)
1071
- });
1072
- dispatch(report);
1140
+ } finally {
1141
+ endDiffScope();
1073
1142
  }
1074
1143
  }
1075
1144