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.
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,7 +161,7 @@ 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), kind, prev: a, next: b });
121
165
  } else {
122
166
  changes.push({ path, kind, prev: a, next: b });
123
167
  }
@@ -328,7 +372,7 @@ function getState() {
328
372
  const g = globalThis;
329
373
  let s = g[KEY];
330
374
  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 };
375
+ 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
376
  g[KEY] = s;
333
377
  }
334
378
  return s;
@@ -358,7 +402,7 @@ function dispatch(report, override) {
358
402
  try {
359
403
  options.notifier(report);
360
404
  } catch (err) {
361
- (options.console ?? console).warn("[rerender-lens] notifier threw", err);
405
+ warnOnce("notifier", `notifier threw: ${String(err)}`);
362
406
  }
363
407
  }
364
408
  }
@@ -701,11 +745,14 @@ var counts = /* @__PURE__ */ new WeakMap();
701
745
  var ids = /* @__PURE__ */ new WeakMap();
702
746
  var fibersById = /* @__PURE__ */ new Map();
703
747
  var hasWeakRef = typeof WeakRef === "function";
748
+ var pruneAt = 5e3;
704
749
  function remember2(id, fiber) {
705
750
  if (!hasWeakRef) return;
751
+ if (fibersById.get(id)?.deref() === fiber) return;
706
752
  fibersById.set(id, new WeakRef(fiber));
707
- if (fibersById.size > 5e3) {
753
+ if (fibersById.size > pruneAt) {
708
754
  for (const [k, ref] of fibersById) if (!ref.deref()) fibersById.delete(k);
755
+ pruneAt = Math.max(5e3, fibersById.size * 2);
709
756
  }
710
757
  }
711
758
  function instanceIdOf(fiber) {
@@ -760,7 +807,7 @@ function nearestComponent(fiber) {
760
807
  }
761
808
  var INTERNAL_FRAME = /react-dom|react_jsx|jsx-(dev-)?runtime|\/react\/|node_modules\/react|react-stack-bottom-frame|react_stack_bottom_frame|scheduler/;
762
809
  function parseStackLocation(stack) {
763
- for (const line of stack.split("\n")) {
810
+ for (const line of stack.split("\n", 24)) {
764
811
  const m = /(?:at\s+(?:.*?\s+)?\(?|@)?((?:https?|file|webpack|vite|blob):[^\s()]+?):(\d+):(\d+)\)?\s*$/.exec(line.trim());
765
812
  if (!m || !m[1] || INTERNAL_FRAME.test(m[1])) continue;
766
813
  return { fileName: m[1], lineNumber: Number(m[2]), columnNumber: Number(m[3]) };
@@ -776,9 +823,16 @@ function sourceOf(fiber) {
776
823
  return out;
777
824
  }
778
825
  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;
826
+ if (st && typeof st === "object") {
827
+ if (sourceCache.has(st)) return sourceCache.get(st);
828
+ const text = typeof st.stack === "string" ? st.stack : null;
829
+ const out = text ? parseStackLocation(text) : void 0;
830
+ sourceCache.set(st, out);
831
+ return out;
832
+ }
833
+ return typeof st === "string" ? parseStackLocation(st) : void 0;
781
834
  }
835
+ var sourceCache = /* @__PURE__ */ new WeakMap();
782
836
  function bumpCount(fiber) {
783
837
  const prev = counts.get(fiber) ?? (fiber.alternate ? counts.get(fiber.alternate) : void 0) ?? 0;
784
838
  const n = prev + 1;
@@ -966,6 +1020,8 @@ function updatersOf(root) {
966
1020
  }
967
1021
  var lastCommit = null;
968
1022
  var EFFECT_LOOP_WINDOW_MS = 50;
1023
+ var MAX_REPORTS_PER_COMMIT = 200;
1024
+ var COMMIT_TIME_BUDGET_MS = 25;
969
1025
  function onCommit(root, commitPriority) {
970
1026
  const s = getState();
971
1027
  if (!s.enabled) return;
@@ -996,7 +1052,8 @@ function onCommit(root, commitPriority) {
996
1052
  const updaters = updatersOf(root);
997
1053
  let commitCause;
998
1054
  let afterCommit;
999
- if (lastCommit && at - lastCommit.at < EFFECT_LOOP_WINDOW_MS && updaters.some((u) => lastCommit.rendered.has(u))) {
1055
+ const inputDriven = commitPriority === "immediate" || commitPriority === "user-blocking";
1056
+ if (!inputDriven && lastCommit && at - lastCommit.at < EFFECT_LOOP_WINDOW_MS && updaters.some((u) => lastCommit.rendered.has(u))) {
1000
1057
  commitCause = "effect-after-commit";
1001
1058
  afterCommit = lastCommit.id;
1002
1059
  } else if (suspenseResolved) commitCause = "suspense-resolved";
@@ -1008,47 +1065,59 @@ function onCommit(root, commitPriority) {
1008
1065
  lastCommit = { id: commitId, at, rendered: renderedNames };
1009
1066
  const dispatcherRef = o.resolveHookNames ? getDispatcherRef() : null;
1010
1067
  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);
1068
+ const endDiffScope = beginDiffScope();
1069
+ const deadline = at + COMMIT_TIME_BUDGET_MS;
1070
+ try {
1071
+ for (let i = 0; i < rendered.length; i++) {
1072
+ if (i >= MAX_REPORTS_PER_COMMIT || (i & 15) === 15 && nowMs() > deadline) {
1073
+ s.truncated += rendered.length - i;
1074
+ warnOnce("truncated", `a commit rendered ${rendered.length} tracked components; only ${i} were reported (see info().truncated). Narrow \`include\` or turn off includeState.`);
1075
+ break;
1021
1076
  }
1077
+ const fiber = rendered[i];
1078
+ const alt = fiber.alternate;
1079
+ const a = analyze(fiber, alt, trackHooks);
1080
+ const durations = durationsOf(fiber);
1081
+ let hookState = o.includeState !== false && trackHooks ? snapshotHooks(fiber) : void 0;
1082
+ if (dispatcherRef && fiber.tag !== ClassComponent && (a.hookChanges.length || hookState && hookState.length)) {
1083
+ const names = resolveHookNames(fiber, dispatcherRef);
1084
+ if (names.size) {
1085
+ for (const c of a.hookChanges) if (c.hook !== "useContext" && names.has(c.index)) c.custom = names.get(c.index);
1086
+ if (hookState) hookState = hookState.map((h) => names.has(h.index) ? { ...h, custom: names.get(h.index) } : h);
1087
+ }
1088
+ }
1089
+ const report = buildReport({
1090
+ component: fiberName(fiber),
1091
+ instanceId: instanceId(fiber),
1092
+ renderCount: bumpCount(fiber),
1093
+ prevProps: alt.memoizedProps ?? {},
1094
+ nextProps: fiber.memoizedProps ?? {},
1095
+ propChanges: a.propChanges,
1096
+ stateChanges: a.stateChanges,
1097
+ hookChanges: a.hookChanges,
1098
+ ...o.includeState !== false ? {
1099
+ hookState,
1100
+ contexts: trackHooks ? snapshotContexts(fiber) : void 0,
1101
+ state: fiber.tag === ClassComponent && fiber.memoizedState && typeof fiber.memoizedState === "object" ? fiber.memoizedState : void 0
1102
+ } : {},
1103
+ updaters,
1104
+ commitCause,
1105
+ afterCommit,
1106
+ key: fiber.key === null || fiber.key === void 0 ? null : String(fiber.key),
1107
+ parent: a.trigger === "parent" ? nearestRenderedAncestor(fiber, parentCache, trackHooks) : null,
1108
+ owner: ownerName(fiber),
1109
+ path: componentPath(fiber),
1110
+ memoized: isMemoizedFiber(fiber),
1111
+ selfDuration: durations ? durations.self : void 0,
1112
+ treeDuration: durations ? durations.tree : void 0,
1113
+ commitId,
1114
+ commitPriority,
1115
+ source: sourceOf(fiber)
1116
+ });
1117
+ dispatch(report);
1022
1118
  }
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);
1119
+ } finally {
1120
+ endDiffScope();
1052
1121
  }
1053
1122
  }
1054
1123
 
@@ -1177,13 +1246,24 @@ function highlight(target) {
1177
1246
  for (const s of l.sticky) s.remove();
1178
1247
  l.sticky = [];
1179
1248
  if (!target) return;
1180
- for (const node of target.nodes) {
1181
- const rect = node.getBoundingClientRect();
1182
- if (rect.width === 0 && rect.height === 0) continue;
1249
+ const frag = document.createDocumentFragment();
1250
+ for (const rect of measure(target.nodes)) {
1183
1251
  const b = box(rect, "#1a73e8", "rgba(26, 115, 232, 0.12)", target.label);
1184
- l.root.appendChild(b);
1252
+ frag.appendChild(b);
1185
1253
  l.sticky.push(b);
1186
1254
  }
1255
+ l.root.appendChild(frag);
1256
+ }
1257
+ var MAX_BOXES = 100;
1258
+ function measure(nodes) {
1259
+ const rects = [];
1260
+ for (const node of nodes) {
1261
+ if (rects.length >= MAX_BOXES) break;
1262
+ const rect = node.getBoundingClientRect();
1263
+ if (rect.width === 0 && rect.height === 0) continue;
1264
+ rects.push(rect);
1265
+ }
1266
+ return rects;
1187
1267
  }
1188
1268
  function clearHighlight() {
1189
1269
  highlight(null);
@@ -1191,27 +1271,34 @@ function clearHighlight() {
1191
1271
  function flash(target, duration = 500) {
1192
1272
  const l = ensureLayer();
1193
1273
  if (!l) return;
1194
- for (const node of target.nodes) {
1195
- const rect = node.getBoundingClientRect();
1196
- if (rect.width === 0 && rect.height === 0) continue;
1274
+ const frag = document.createDocumentFragment();
1275
+ const boxes = [];
1276
+ for (const rect of measure(target.nodes)) {
1197
1277
  const b = box(rect, "#d93025", "rgba(217, 48, 37, 0.15)");
1198
1278
  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);
1279
+ frag.appendChild(b);
1280
+ boxes.push(b);
1204
1281
  }
1282
+ if (!boxes.length) return;
1283
+ l.root.appendChild(frag);
1284
+ requestAnimationFrame(() => {
1285
+ for (const b of boxes) b.style.opacity = "0";
1286
+ });
1287
+ setTimeout(() => {
1288
+ for (const b of boxes) b.remove();
1289
+ }, duration + 50);
1205
1290
  }
1206
1291
 
1207
1292
  // src/version.ts
1208
- var VERSION = "0.3.0" ;
1293
+ var VERSION = "0.4.0" ;
1209
1294
 
1210
1295
  // src/devtools.ts
1211
1296
  var DEVTOOLS_MARKER = "__rerenderLens";
1212
1297
  var PROTOCOL_VERSION = 2;
1213
1298
  var DEFAULT_CHANNEL = "rerender-lens";
1214
- function serialize(value, maxDepth = 6, seen = /* @__PURE__ */ new WeakSet(), depth = 0) {
1299
+ var SERIALIZE_MAX_ENTRIES = 100;
1300
+ var SERIALIZE_MAX_NODES = 2e4;
1301
+ function serialize(value, maxDepth = 4, seen = /* @__PURE__ */ new WeakSet(), depth = 0, budget = { nodes: SERIALIZE_MAX_NODES }) {
1215
1302
  if (value === null || value === void 0) return value;
1216
1303
  const t = typeof value;
1217
1304
  if (t === "string" || t === "boolean") return value;
@@ -1222,24 +1309,56 @@ function serialize(value, maxDepth = 6, seen = /* @__PURE__ */ new WeakSet(), de
1222
1309
  const obj = value;
1223
1310
  if (seen.has(obj)) return "[Circular]";
1224
1311
  if (depth >= maxDepth) return "[\u2026]";
1312
+ if (--budget.nodes < 0) return "[\u2026]";
1313
+ const next = (v) => serialize(v, maxDepth, seen, depth + 1, budget);
1225
1314
  seen.add(obj);
1226
1315
  try {
1227
1316
  if (isReactElement(obj)) {
1228
1317
  const out2 = { $type: "element", name: getDisplayName(obj.type) };
1229
1318
  if (obj.key !== null && obj.key !== void 0) out2.key = String(obj.key);
1230
- out2.props = serialize(obj.props, maxDepth, seen, depth + 1);
1319
+ out2.props = next(obj.props);
1231
1320
  return out2;
1232
1321
  }
1233
1322
  if (obj instanceof Date) return { $type: "Date", value: obj.toISOString() };
1234
1323
  if (obj instanceof RegExp) return { $type: "RegExp", value: String(obj) };
1324
+ if (ArrayBuffer.isView(obj) || obj instanceof ArrayBuffer) {
1325
+ const bin = obj;
1326
+ return { $type: bin.constructor?.name || "ArrayBuffer", length: typeof bin.length === "number" ? bin.length : bin.byteLength };
1327
+ }
1328
+ if (obj instanceof Promise) return "[Promise]";
1329
+ if (typeof Node !== "undefined" && obj instanceof Node) return typeof Element !== "undefined" && obj instanceof Element ? `<${obj.tagName.toLowerCase()}>` : `[${obj.nodeName}]`;
1330
+ if (obj === globalThis) return "[Window]";
1235
1331
  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)]) };
1332
+ const entries = [];
1333
+ for (const [k, v] of obj) {
1334
+ if (entries.length >= SERIALIZE_MAX_ENTRIES) {
1335
+ entries.push(["\u2026", `+${obj.size - SERIALIZE_MAX_ENTRIES} more`]);
1336
+ break;
1337
+ }
1338
+ entries.push([next(k), next(v)]);
1339
+ }
1340
+ return { $type: "Map", entries };
1341
+ }
1342
+ if (obj instanceof Set) {
1343
+ const values = [];
1344
+ for (const v of obj) {
1345
+ if (values.length >= SERIALIZE_MAX_ENTRIES) {
1346
+ values.push(`\u2026+${obj.size - SERIALIZE_MAX_ENTRIES} more`);
1347
+ break;
1348
+ }
1349
+ values.push(next(v));
1350
+ }
1351
+ return { $type: "Set", values };
1352
+ }
1353
+ if (Array.isArray(obj)) {
1354
+ const out2 = obj.slice(0, SERIALIZE_MAX_ENTRIES).map(next);
1355
+ if (obj.length > SERIALIZE_MAX_ENTRIES) out2.push(`\u2026+${obj.length - SERIALIZE_MAX_ENTRIES} more`);
1356
+ return out2;
1237
1357
  }
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
1358
  const out = {};
1242
- for (const k of Object.keys(obj)) out[k] = serialize(obj[k], maxDepth, seen, depth + 1);
1359
+ const keys = Object.keys(obj);
1360
+ for (const k of keys.slice(0, SERIALIZE_MAX_ENTRIES)) out[k] = next(obj[k]);
1361
+ if (keys.length > SERIALIZE_MAX_ENTRIES) out["\u2026"] = `+${keys.length - SERIALIZE_MAX_ENTRIES} more`;
1243
1362
  const proto = Object.getPrototypeOf(obj);
1244
1363
  if (proto && proto !== Object.prototype && proto.constructor?.name) out.$type = proto.constructor.name;
1245
1364
  return out;
@@ -1279,13 +1398,20 @@ function deserializeOptions(o) {
1279
1398
  }
1280
1399
  function createDevtoolsNotifier(options = {}) {
1281
1400
  const bufferSize = options.bufferSize ?? 300;
1282
- const maxDepth = options.maxDepth ?? 6;
1401
+ const maxDepth = options.maxDepth ?? 4;
1283
1402
  const target = options.target ?? (typeof window !== "undefined" ? window : void 0);
1284
1403
  const buffer = [];
1285
1404
  let seq = 0;
1286
1405
  let flashOn = options.flashAvoidable ?? false;
1406
+ let live = !target || typeof target.addEventListener !== "function";
1407
+ if (!live) {
1408
+ target.addEventListener("message", (event) => {
1409
+ const data = event.data;
1410
+ if ((event.source === target || !event.source) && data && data.__rerenderLensReady === true) live = true;
1411
+ });
1412
+ }
1287
1413
  const post = (type, payload) => {
1288
- if (!target) return;
1414
+ if (!target || type === "report" && !live) return;
1289
1415
  const msg = { [DEVTOOLS_MARKER]: true, version: PROTOCOL_VERSION, type, payload };
1290
1416
  target.postMessage(msg, "*");
1291
1417
  };
@@ -1302,10 +1428,12 @@ function createDevtoolsNotifier(options = {}) {
1302
1428
  injected: typeof window !== "undefined" && typeof window.__RERENDER_LENS_INJECTED__ === "string",
1303
1429
  scheduled: getState().scheduled,
1304
1430
  commits: getState().commits,
1305
- overhead: { totalMs: getState().overheadMs, maxCommitMs: getState().maxCommitMs }
1431
+ overhead: { totalMs: getState().overheadMs, maxCommitMs: getState().maxCommitMs },
1432
+ truncated: getState().truncated
1306
1433
  });
1307
1434
  const bridge = {
1308
1435
  replay: () => {
1436
+ live = true;
1309
1437
  post("hello", info());
1310
1438
  for (const e of buffer) post("report", e.payload);
1311
1439
  },