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