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.js CHANGED
@@ -24,10 +24,49 @@ function remember(seen, a, b) {
24
24
  set.add(b);
25
25
  return false;
26
26
  }
27
+ var scope = null;
28
+ var DEFAULT_DIFF_BUDGET = 5e4;
29
+ function beginDiffScope(budget = 2e5) {
30
+ const previous = scope;
31
+ scope = { cache: /* @__PURE__ */ new WeakMap(), remaining: budget, exhausted: false };
32
+ return () => {
33
+ scope = previous;
34
+ };
35
+ }
36
+ function diffBudgetExhausted() {
37
+ return scope?.exhausted === true;
38
+ }
27
39
  function deepEqual(a, b, seen = /* @__PURE__ */ new Map()) {
28
40
  if (Object.is(a, b)) return true;
29
41
  if (typeof a !== typeof b) return false;
30
42
  if (typeof a !== "object" || a === null || b === null) return false;
43
+ if (!scope) {
44
+ const end = beginDiffScope(DEFAULT_DIFF_BUDGET);
45
+ try {
46
+ return deepEqual(a, b, seen);
47
+ } finally {
48
+ end();
49
+ }
50
+ }
51
+ const sc = scope;
52
+ const objA = a;
53
+ const objB = b;
54
+ const hit = sc.cache.get(objA)?.get(objB);
55
+ if (hit !== void 0) return hit;
56
+ if (--sc.remaining < 0) {
57
+ sc.exhausted = true;
58
+ return false;
59
+ }
60
+ const result = deepEqualObjects(objA, objB, seen);
61
+ let m = sc.cache.get(objA);
62
+ if (!m) {
63
+ m = /* @__PURE__ */ new WeakMap();
64
+ sc.cache.set(objA, m);
65
+ }
66
+ m.set(objB, result);
67
+ return result;
68
+ }
69
+ function deepEqualObjects(a, b, seen) {
31
70
  const objA = a;
32
71
  const objB = b;
33
72
  if (remember(seen, objA, objB)) return true;
@@ -56,6 +95,10 @@ function deepEqual(a, b, seen = /* @__PURE__ */ new Map()) {
56
95
  }
57
96
  if (a instanceof Set) {
58
97
  if (!(b instanceof Set) || a.size !== b.size) return false;
98
+ if (a.size > 50) {
99
+ for (const v of a) if (!b.has(v)) return false;
100
+ return true;
101
+ }
59
102
  outer: for (const v of a) {
60
103
  if (b.has(v)) continue;
61
104
  for (const w of b) if (deepEqual(v, w, seen)) continue outer;
@@ -71,9 +114,10 @@ function deepEqual(a, b, seen = /* @__PURE__ */ new Map()) {
71
114
  const ka = Object.keys(a);
72
115
  const kb = Object.keys(b);
73
116
  if (ka.length !== kb.length) return false;
117
+ const rb = b;
74
118
  for (const k of ka) {
75
119
  if (!Object.prototype.hasOwnProperty.call(b, k)) return false;
76
- if (!deepEqual(a[k], b[k], seen)) return false;
120
+ if (!deepEqual(a[k], rb[k], seen)) return false;
77
121
  }
78
122
  return true;
79
123
  }
@@ -115,7 +159,7 @@ function diffRecords(prev, next, basePath = "") {
115
159
  if (Object.is(a, b)) continue;
116
160
  const kind = classify(a, b);
117
161
  if (kind === "different") {
118
- changes.push({ path: firstDifferentPath(a, b, path), kind, prev: a, next: b });
162
+ changes.push({ path: diffBudgetExhausted() ? path : firstDifferentPath(a, b, path), kind, prev: a, next: b });
119
163
  } else {
120
164
  changes.push({ path, kind, prev: a, next: b });
121
165
  }
@@ -326,7 +370,7 @@ function getState() {
326
370
  const g = globalThis;
327
371
  let s = g[KEY];
328
372
  if (!s) {
329
- 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 };
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 };
330
374
  g[KEY] = s;
331
375
  }
332
376
  return s;
@@ -356,7 +400,7 @@ function dispatch(report, override) {
356
400
  try {
357
401
  options.notifier(report);
358
402
  } catch (err) {
359
- (options.console ?? console).warn("[rerender-lens] notifier threw", err);
403
+ warnOnce("notifier", `notifier threw: ${String(err)}`);
360
404
  }
361
405
  }
362
406
  }
@@ -699,11 +743,14 @@ var counts = /* @__PURE__ */ new WeakMap();
699
743
  var ids = /* @__PURE__ */ new WeakMap();
700
744
  var fibersById = /* @__PURE__ */ new Map();
701
745
  var hasWeakRef = typeof WeakRef === "function";
746
+ var pruneAt = 5e3;
702
747
  function remember2(id, fiber) {
703
748
  if (!hasWeakRef) return;
749
+ if (fibersById.get(id)?.deref() === fiber) return;
704
750
  fibersById.set(id, new WeakRef(fiber));
705
- if (fibersById.size > 5e3) {
751
+ if (fibersById.size > pruneAt) {
706
752
  for (const [k, ref] of fibersById) if (!ref.deref()) fibersById.delete(k);
753
+ pruneAt = Math.max(5e3, fibersById.size * 2);
707
754
  }
708
755
  }
709
756
  function instanceIdOf(fiber) {
@@ -758,7 +805,7 @@ function nearestComponent(fiber) {
758
805
  }
759
806
  var INTERNAL_FRAME = /react-dom|react_jsx|jsx-(dev-)?runtime|\/react\/|node_modules\/react|react-stack-bottom-frame|react_stack_bottom_frame|scheduler/;
760
807
  function parseStackLocation(stack) {
761
- for (const line of stack.split("\n")) {
808
+ for (const line of stack.split("\n", 24)) {
762
809
  const m = /(?:at\s+(?:.*?\s+)?\(?|@)?((?:https?|file|webpack|vite|blob):[^\s()]+?):(\d+):(\d+)\)?\s*$/.exec(line.trim());
763
810
  if (!m || !m[1] || INTERNAL_FRAME.test(m[1])) continue;
764
811
  return { fileName: m[1], lineNumber: Number(m[2]), columnNumber: Number(m[3]) };
@@ -774,9 +821,16 @@ function sourceOf(fiber) {
774
821
  return out;
775
822
  }
776
823
  const st = fiber._debugStack;
777
- const text = typeof st === "string" ? st : st && typeof st.stack === "string" ? st.stack : null;
778
- return text ? parseStackLocation(text) : void 0;
824
+ if (st && typeof st === "object") {
825
+ if (sourceCache.has(st)) return sourceCache.get(st);
826
+ const text = typeof st.stack === "string" ? st.stack : null;
827
+ const out = text ? parseStackLocation(text) : void 0;
828
+ sourceCache.set(st, out);
829
+ return out;
830
+ }
831
+ return typeof st === "string" ? parseStackLocation(st) : void 0;
779
832
  }
833
+ var sourceCache = /* @__PURE__ */ new WeakMap();
780
834
  function bumpCount(fiber) {
781
835
  const prev = counts.get(fiber) ?? (fiber.alternate ? counts.get(fiber.alternate) : void 0) ?? 0;
782
836
  const n = prev + 1;
@@ -964,6 +1018,8 @@ function updatersOf(root) {
964
1018
  }
965
1019
  var lastCommit = null;
966
1020
  var EFFECT_LOOP_WINDOW_MS = 50;
1021
+ var MAX_REPORTS_PER_COMMIT = 200;
1022
+ var COMMIT_TIME_BUDGET_MS = 25;
967
1023
  function onCommit(root, commitPriority) {
968
1024
  const s = getState();
969
1025
  if (!s.enabled) return;
@@ -994,7 +1050,8 @@ function onCommit(root, commitPriority) {
994
1050
  const updaters = updatersOf(root);
995
1051
  let commitCause;
996
1052
  let afterCommit;
997
- if (lastCommit && at - lastCommit.at < EFFECT_LOOP_WINDOW_MS && updaters.some((u) => lastCommit.rendered.has(u))) {
1053
+ 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))) {
998
1055
  commitCause = "effect-after-commit";
999
1056
  afterCommit = lastCommit.id;
1000
1057
  } else if (suspenseResolved) commitCause = "suspense-resolved";
@@ -1006,47 +1063,59 @@ function onCommit(root, commitPriority) {
1006
1063
  lastCommit = { id: commitId, at, rendered: renderedNames };
1007
1064
  const dispatcherRef = o.resolveHookNames ? getDispatcherRef() : null;
1008
1065
  const parentCache = /* @__PURE__ */ new Map();
1009
- for (const fiber of rendered) {
1010
- const alt = fiber.alternate;
1011
- const a = analyze(fiber, alt, trackHooks);
1012
- const durations = durationsOf(fiber);
1013
- let hookState = o.includeState !== false && trackHooks ? snapshotHooks(fiber) : void 0;
1014
- if (dispatcherRef && fiber.tag !== ClassComponent && (a.hookChanges.length || hookState && hookState.length)) {
1015
- const names = resolveHookNames(fiber, dispatcherRef);
1016
- if (names.size) {
1017
- for (const c of a.hookChanges) if (c.hook !== "useContext" && names.has(c.index)) c.custom = names.get(c.index);
1018
- if (hookState) hookState = hookState.map((h) => names.has(h.index) ? { ...h, custom: names.get(h.index) } : h);
1066
+ const endDiffScope = beginDiffScope();
1067
+ const deadline = at + COMMIT_TIME_BUDGET_MS;
1068
+ try {
1069
+ for (let i = 0; i < rendered.length; i++) {
1070
+ if (i >= MAX_REPORTS_PER_COMMIT || (i & 15) === 15 && nowMs() > deadline) {
1071
+ s.truncated += rendered.length - i;
1072
+ warnOnce("truncated", `a commit rendered ${rendered.length} tracked components; only ${i} were reported (see info().truncated). Narrow \`include\` or turn off includeState.`);
1073
+ break;
1019
1074
  }
1075
+ const fiber = rendered[i];
1076
+ const alt = fiber.alternate;
1077
+ const a = analyze(fiber, alt, trackHooks);
1078
+ const durations = durationsOf(fiber);
1079
+ let hookState = o.includeState !== false && trackHooks ? snapshotHooks(fiber) : void 0;
1080
+ if (dispatcherRef && fiber.tag !== ClassComponent && (a.hookChanges.length || hookState && hookState.length)) {
1081
+ const names = resolveHookNames(fiber, dispatcherRef);
1082
+ if (names.size) {
1083
+ for (const c of a.hookChanges) if (c.hook !== "useContext" && names.has(c.index)) c.custom = names.get(c.index);
1084
+ if (hookState) hookState = hookState.map((h) => names.has(h.index) ? { ...h, custom: names.get(h.index) } : h);
1085
+ }
1086
+ }
1087
+ const report = buildReport({
1088
+ component: fiberName(fiber),
1089
+ instanceId: instanceId(fiber),
1090
+ renderCount: bumpCount(fiber),
1091
+ prevProps: alt.memoizedProps ?? {},
1092
+ nextProps: fiber.memoizedProps ?? {},
1093
+ propChanges: a.propChanges,
1094
+ stateChanges: a.stateChanges,
1095
+ hookChanges: a.hookChanges,
1096
+ ...o.includeState !== false ? {
1097
+ hookState,
1098
+ contexts: trackHooks ? snapshotContexts(fiber) : void 0,
1099
+ state: fiber.tag === ClassComponent && fiber.memoizedState && typeof fiber.memoizedState === "object" ? fiber.memoizedState : void 0
1100
+ } : {},
1101
+ updaters,
1102
+ commitCause,
1103
+ afterCommit,
1104
+ key: fiber.key === null || fiber.key === void 0 ? null : String(fiber.key),
1105
+ parent: a.trigger === "parent" ? nearestRenderedAncestor(fiber, parentCache, trackHooks) : null,
1106
+ owner: ownerName(fiber),
1107
+ path: componentPath(fiber),
1108
+ memoized: isMemoizedFiber(fiber),
1109
+ selfDuration: durations ? durations.self : void 0,
1110
+ treeDuration: durations ? durations.tree : void 0,
1111
+ commitId,
1112
+ commitPriority,
1113
+ source: sourceOf(fiber)
1114
+ });
1115
+ dispatch(report);
1020
1116
  }
1021
- const report = buildReport({
1022
- component: fiberName(fiber),
1023
- instanceId: instanceId(fiber),
1024
- renderCount: bumpCount(fiber),
1025
- prevProps: alt.memoizedProps ?? {},
1026
- nextProps: fiber.memoizedProps ?? {},
1027
- propChanges: a.propChanges,
1028
- stateChanges: a.stateChanges,
1029
- hookChanges: a.hookChanges,
1030
- ...o.includeState !== false ? {
1031
- hookState,
1032
- contexts: trackHooks ? snapshotContexts(fiber) : void 0,
1033
- state: fiber.tag === ClassComponent && fiber.memoizedState && typeof fiber.memoizedState === "object" ? fiber.memoizedState : void 0
1034
- } : {},
1035
- updaters,
1036
- commitCause,
1037
- afterCommit,
1038
- key: fiber.key === null || fiber.key === void 0 ? null : String(fiber.key),
1039
- parent: a.trigger === "parent" ? nearestRenderedAncestor(fiber, parentCache, trackHooks) : null,
1040
- owner: ownerName(fiber),
1041
- path: componentPath(fiber),
1042
- memoized: isMemoizedFiber(fiber),
1043
- selfDuration: durations ? durations.self : void 0,
1044
- treeDuration: durations ? durations.tree : void 0,
1045
- commitId,
1046
- commitPriority,
1047
- source: sourceOf(fiber)
1048
- });
1049
- dispatch(report);
1117
+ } finally {
1118
+ endDiffScope();
1050
1119
  }
1051
1120
  }
1052
1121
 
@@ -1175,13 +1244,24 @@ function highlight(target) {
1175
1244
  for (const s of l.sticky) s.remove();
1176
1245
  l.sticky = [];
1177
1246
  if (!target) return;
1178
- for (const node of target.nodes) {
1179
- const rect = node.getBoundingClientRect();
1180
- if (rect.width === 0 && rect.height === 0) continue;
1247
+ const frag = document.createDocumentFragment();
1248
+ for (const rect of measure(target.nodes)) {
1181
1249
  const b = box(rect, "#1a73e8", "rgba(26, 115, 232, 0.12)", target.label);
1182
- l.root.appendChild(b);
1250
+ frag.appendChild(b);
1183
1251
  l.sticky.push(b);
1184
1252
  }
1253
+ l.root.appendChild(frag);
1254
+ }
1255
+ var MAX_BOXES = 100;
1256
+ function measure(nodes) {
1257
+ const rects = [];
1258
+ for (const node of nodes) {
1259
+ if (rects.length >= MAX_BOXES) break;
1260
+ const rect = node.getBoundingClientRect();
1261
+ if (rect.width === 0 && rect.height === 0) continue;
1262
+ rects.push(rect);
1263
+ }
1264
+ return rects;
1185
1265
  }
1186
1266
  function clearHighlight() {
1187
1267
  highlight(null);
@@ -1189,27 +1269,34 @@ function clearHighlight() {
1189
1269
  function flash(target, duration = 500) {
1190
1270
  const l = ensureLayer();
1191
1271
  if (!l) return;
1192
- for (const node of target.nodes) {
1193
- const rect = node.getBoundingClientRect();
1194
- if (rect.width === 0 && rect.height === 0) continue;
1272
+ const frag = document.createDocumentFragment();
1273
+ const boxes = [];
1274
+ for (const rect of measure(target.nodes)) {
1195
1275
  const b = box(rect, "#d93025", "rgba(217, 48, 37, 0.15)");
1196
1276
  b.style.transition = `opacity ${duration}ms ease-out`;
1197
- l.root.appendChild(b);
1198
- requestAnimationFrame(() => {
1199
- b.style.opacity = "0";
1200
- });
1201
- setTimeout(() => b.remove(), duration + 50);
1277
+ frag.appendChild(b);
1278
+ boxes.push(b);
1202
1279
  }
1280
+ if (!boxes.length) return;
1281
+ l.root.appendChild(frag);
1282
+ requestAnimationFrame(() => {
1283
+ for (const b of boxes) b.style.opacity = "0";
1284
+ });
1285
+ setTimeout(() => {
1286
+ for (const b of boxes) b.remove();
1287
+ }, duration + 50);
1203
1288
  }
1204
1289
 
1205
1290
  // src/version.ts
1206
- var VERSION = "0.3.0" ;
1291
+ var VERSION = "0.4.0" ;
1207
1292
 
1208
1293
  // src/devtools.ts
1209
1294
  var DEVTOOLS_MARKER = "__rerenderLens";
1210
1295
  var PROTOCOL_VERSION = 2;
1211
1296
  var DEFAULT_CHANNEL = "rerender-lens";
1212
- function serialize(value, maxDepth = 6, seen = /* @__PURE__ */ new WeakSet(), depth = 0) {
1297
+ var SERIALIZE_MAX_ENTRIES = 100;
1298
+ var SERIALIZE_MAX_NODES = 2e4;
1299
+ function serialize(value, maxDepth = 4, seen = /* @__PURE__ */ new WeakSet(), depth = 0, budget = { nodes: SERIALIZE_MAX_NODES }) {
1213
1300
  if (value === null || value === void 0) return value;
1214
1301
  const t = typeof value;
1215
1302
  if (t === "string" || t === "boolean") return value;
@@ -1220,24 +1307,56 @@ function serialize(value, maxDepth = 6, seen = /* @__PURE__ */ new WeakSet(), de
1220
1307
  const obj = value;
1221
1308
  if (seen.has(obj)) return "[Circular]";
1222
1309
  if (depth >= maxDepth) return "[\u2026]";
1310
+ if (--budget.nodes < 0) return "[\u2026]";
1311
+ const next = (v) => serialize(v, maxDepth, seen, depth + 1, budget);
1223
1312
  seen.add(obj);
1224
1313
  try {
1225
1314
  if (isReactElement(obj)) {
1226
1315
  const out2 = { $type: "element", name: getDisplayName(obj.type) };
1227
1316
  if (obj.key !== null && obj.key !== void 0) out2.key = String(obj.key);
1228
- out2.props = serialize(obj.props, maxDepth, seen, depth + 1);
1317
+ out2.props = next(obj.props);
1229
1318
  return out2;
1230
1319
  }
1231
1320
  if (obj instanceof Date) return { $type: "Date", value: obj.toISOString() };
1232
1321
  if (obj instanceof RegExp) return { $type: "RegExp", value: String(obj) };
1322
+ if (ArrayBuffer.isView(obj) || obj instanceof ArrayBuffer) {
1323
+ const bin = obj;
1324
+ return { $type: bin.constructor?.name || "ArrayBuffer", length: typeof bin.length === "number" ? bin.length : bin.byteLength };
1325
+ }
1326
+ if (obj instanceof Promise) return "[Promise]";
1327
+ if (typeof Node !== "undefined" && obj instanceof Node) return typeof Element !== "undefined" && obj instanceof Element ? `<${obj.tagName.toLowerCase()}>` : `[${obj.nodeName}]`;
1328
+ if (obj === globalThis) return "[Window]";
1233
1329
  if (obj instanceof Map) {
1234
- return { $type: "Map", entries: [...obj].map(([k, v]) => [serialize(k, maxDepth, seen, depth + 1), serialize(v, maxDepth, seen, depth + 1)]) };
1330
+ const entries = [];
1331
+ for (const [k, v] of obj) {
1332
+ if (entries.length >= SERIALIZE_MAX_ENTRIES) {
1333
+ entries.push(["\u2026", `+${obj.size - SERIALIZE_MAX_ENTRIES} more`]);
1334
+ break;
1335
+ }
1336
+ entries.push([next(k), next(v)]);
1337
+ }
1338
+ return { $type: "Map", entries };
1339
+ }
1340
+ if (obj instanceof Set) {
1341
+ const values = [];
1342
+ for (const v of obj) {
1343
+ if (values.length >= SERIALIZE_MAX_ENTRIES) {
1344
+ values.push(`\u2026+${obj.size - SERIALIZE_MAX_ENTRIES} more`);
1345
+ break;
1346
+ }
1347
+ values.push(next(v));
1348
+ }
1349
+ return { $type: "Set", values };
1350
+ }
1351
+ if (Array.isArray(obj)) {
1352
+ const out2 = obj.slice(0, SERIALIZE_MAX_ENTRIES).map(next);
1353
+ if (obj.length > SERIALIZE_MAX_ENTRIES) out2.push(`\u2026+${obj.length - SERIALIZE_MAX_ENTRIES} more`);
1354
+ return out2;
1235
1355
  }
1236
- if (obj instanceof Set) return { $type: "Set", values: [...obj].map((v) => serialize(v, maxDepth, seen, depth + 1)) };
1237
- if (Array.isArray(obj)) return obj.map((v) => serialize(v, maxDepth, seen, depth + 1));
1238
- if (typeof Element !== "undefined" && obj instanceof Element) return `<${obj.tagName.toLowerCase()}>`;
1239
1356
  const out = {};
1240
- for (const k of Object.keys(obj)) out[k] = serialize(obj[k], maxDepth, seen, depth + 1);
1357
+ const keys = Object.keys(obj);
1358
+ for (const k of keys.slice(0, SERIALIZE_MAX_ENTRIES)) out[k] = next(obj[k]);
1359
+ if (keys.length > SERIALIZE_MAX_ENTRIES) out["\u2026"] = `+${keys.length - SERIALIZE_MAX_ENTRIES} more`;
1241
1360
  const proto = Object.getPrototypeOf(obj);
1242
1361
  if (proto && proto !== Object.prototype && proto.constructor?.name) out.$type = proto.constructor.name;
1243
1362
  return out;
@@ -1277,13 +1396,20 @@ function deserializeOptions(o) {
1277
1396
  }
1278
1397
  function createDevtoolsNotifier(options = {}) {
1279
1398
  const bufferSize = options.bufferSize ?? 300;
1280
- const maxDepth = options.maxDepth ?? 6;
1399
+ const maxDepth = options.maxDepth ?? 4;
1281
1400
  const target = options.target ?? (typeof window !== "undefined" ? window : void 0);
1282
1401
  const buffer = [];
1283
1402
  let seq = 0;
1284
1403
  let flashOn = options.flashAvoidable ?? false;
1404
+ let live = !target || typeof target.addEventListener !== "function";
1405
+ if (!live) {
1406
+ target.addEventListener("message", (event) => {
1407
+ const data = event.data;
1408
+ if ((event.source === target || !event.source) && data && data.__rerenderLensReady === true) live = true;
1409
+ });
1410
+ }
1285
1411
  const post = (type, payload) => {
1286
- if (!target) return;
1412
+ if (!target || type === "report" && !live) return;
1287
1413
  const msg = { [DEVTOOLS_MARKER]: true, version: PROTOCOL_VERSION, type, payload };
1288
1414
  target.postMessage(msg, "*");
1289
1415
  };
@@ -1300,10 +1426,12 @@ function createDevtoolsNotifier(options = {}) {
1300
1426
  injected: typeof window !== "undefined" && typeof window.__RERENDER_LENS_INJECTED__ === "string",
1301
1427
  scheduled: getState().scheduled,
1302
1428
  commits: getState().commits,
1303
- overhead: { totalMs: getState().overheadMs, maxCommitMs: getState().maxCommitMs }
1429
+ overhead: { totalMs: getState().overheadMs, maxCommitMs: getState().maxCommitMs },
1430
+ truncated: getState().truncated
1304
1431
  });
1305
1432
  const bridge = {
1306
1433
  replay: () => {
1434
+ live = true;
1307
1435
  post("hello", info());
1308
1436
  for (const e of buffer) post("report", e.payload);
1309
1437
  },