rerender-lens 0.2.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/CHANGELOG.md +110 -1
- package/README.md +159 -9
- package/dist/budget-COBu7jBU.d.cts +64 -0
- package/dist/budget-LkNjGtRc.d.ts +64 -0
- package/dist/cli.cjs +458 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +4 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.js +452 -0
- package/dist/cli.js.map +1 -0
- package/dist/devtools-BkCct3cJ.d.cts +252 -0
- package/dist/devtools-CZebzpF6.d.ts +252 -0
- package/dist/index.cjs +832 -75
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +40 -336
- package/dist/index.d.ts +40 -336
- package/dist/index.js +816 -76
- package/dist/index.js.map +1 -1
- package/dist/notifiers-BGQtWKfX.d.cts +90 -0
- package/dist/notifiers-BjjGSHpp.d.ts +90 -0
- package/dist/playwright.cjs +199 -0
- package/dist/playwright.cjs.map +1 -0
- package/dist/playwright.d.cts +37 -0
- package/dist/playwright.d.ts +37 -0
- package/dist/playwright.js +186 -0
- package/dist/playwright.js.map +1 -0
- package/dist/relay.cjs +166 -0
- package/dist/relay.cjs.map +1 -0
- package/dist/relay.d.cts +41 -0
- package/dist/relay.d.ts +41 -0
- package/dist/relay.js +161 -0
- package/dist/relay.js.map +1 -0
- package/dist/rerender-lens.iife.js +2178 -0
- package/dist/setup.cjs +1638 -0
- package/dist/setup.cjs.map +1 -0
- package/dist/setup.d.cts +2 -0
- package/dist/setup.d.ts +2 -0
- package/dist/setup.js +1636 -0
- package/dist/setup.js.map +1 -0
- package/dist/types-BzUEVkxJ.d.cts +177 -0
- package/dist/types-BzUEVkxJ.d.ts +177 -0
- package/dist/vite.cjs +113 -0
- package/dist/vite.cjs.map +1 -0
- package/dist/vite.d.cts +84 -0
- package/dist/vite.d.ts +84 -0
- package/dist/vite.js +103 -0
- package/dist/vite.js.map +1 -0
- package/dist/vitest-setup.cjs +1368 -0
- package/dist/vitest-setup.cjs.map +1 -0
- package/dist/vitest-setup.d.cts +8 -0
- package/dist/vitest-setup.d.ts +8 -0
- package/dist/vitest-setup.js +1366 -0
- package/dist/vitest-setup.js.map +1 -0
- package/dist/vitest.cjs +1429 -0
- package/dist/vitest.cjs.map +1 -0
- package/dist/vitest.d.cts +52 -0
- package/dist/vitest.d.ts +52 -0
- package/dist/vitest.js +1420 -0
- package/dist/vitest.js.map +1 -0
- package/package.json +84 -4
- package/panel/panel.css +384 -0
- package/panel/panel.html +12 -0
- package/panel/panel.js +3287 -0
package/dist/index.js
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],
|
|
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
|
}
|
|
@@ -145,6 +189,17 @@ function firstDifferentPath(a, b, path, depth = 0) {
|
|
|
145
189
|
|
|
146
190
|
// src/report.ts
|
|
147
191
|
var now = () => typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
|
|
192
|
+
var hookLabel = (c) => c.custom && c.custom.length ? `${c.custom.join(" \u203A ")} \u203A ${c.hook}#${c.index}` : `${c.hook} #${c.index}`;
|
|
193
|
+
function storeAdvice(custom) {
|
|
194
|
+
const chain = custom ?? [];
|
|
195
|
+
if (chain.some((n) => /^useSelector$/.test(n)))
|
|
196
|
+
return "the Redux selector returns a new object on every call: return a stored slice, pass shallowEqual as the equality function, or memoize it with createSelector";
|
|
197
|
+
if (chain.some((n) => /^useAppSelector$/.test(n)))
|
|
198
|
+
return "the selector returns a new object on every call: return a stored slice, pass shallowEqual, or memoize it with createSelector";
|
|
199
|
+
if (chain.some((n) => /^use[A-Z]\w*Store$/.test(n) || n === "useStore" || n === "useBoundStore"))
|
|
200
|
+
return "the store selector returns a new object on every call: select a primitive, or wrap the selector with useShallow (Zustand) / an equality function";
|
|
201
|
+
return "getSnapshot returns a new reference with the same contents: cache the snapshot in the store and return the same object while the data is unchanged";
|
|
202
|
+
}
|
|
148
203
|
var isGenuine = (c) => c.kind === "different" || c.kind === "added" || c.kind === "removed";
|
|
149
204
|
var isChildren = (change) => change.path === "children";
|
|
150
205
|
function fixFor(change) {
|
|
@@ -220,10 +275,17 @@ function buildReport(input) {
|
|
|
220
275
|
const where = c.provider && c.provider.component ? ` (provided by <${c.provider.component}>)` : "";
|
|
221
276
|
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` : "";
|
|
222
277
|
reasons.push(`${c.path} changed${where}${keys}.`);
|
|
223
|
-
} else if (isGenuine(c)) reasons.push(`${c
|
|
278
|
+
} else if (isGenuine(c)) reasons.push(`${hookLabel(c)} changed.`);
|
|
224
279
|
else if (isStateHook(c))
|
|
225
|
-
reasons.push(`${c
|
|
226
|
-
else reasons.push(`${c
|
|
280
|
+
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.`);
|
|
281
|
+
else if (c.hook === "useSyncExternalStore") reasons.push(`${hookLabel(c)} returned a new reference that is deep-equal to the previous value: ${storeAdvice(c.custom)}.`);
|
|
282
|
+
else reasons.push(`${hookLabel(c)} returned a new reference that is deep-equal to the previous value: memoize the context/store value where it is produced.`);
|
|
283
|
+
}
|
|
284
|
+
if (input.commitCause === "effect-after-commit") {
|
|
285
|
+
const who = input.updaters && input.updaters.length ? input.updaters.map((u) => `<${u}>`).join(", ") : "a component that rendered in it";
|
|
286
|
+
reasons.push(`this commit was scheduled right after commit #${input.afterCommit ?? "?"} by ${who}: an effect there set state (effect \u2192 setState loop). Derive the value during render or compute it before setting state.`);
|
|
287
|
+
} else if (input.commitCause === "suspense-resolved") {
|
|
288
|
+
reasons.push("this commit shows content that was suspended (a Suspense boundary resolved).");
|
|
227
289
|
}
|
|
228
290
|
const report = {
|
|
229
291
|
component: input.component,
|
|
@@ -247,6 +309,13 @@ function buildReport(input) {
|
|
|
247
309
|
if (input.treeDuration !== void 0) report.treeDuration = input.treeDuration;
|
|
248
310
|
if (input.commitPriority) report.commitPriority = input.commitPriority;
|
|
249
311
|
if (input.source) report.source = input.source;
|
|
312
|
+
if (input.hookState) report.hookState = input.hookState;
|
|
313
|
+
if (input.contexts) report.contexts = input.contexts;
|
|
314
|
+
if (input.state) report.state = input.state;
|
|
315
|
+
if (input.updaters && input.updaters.length) report.updaters = input.updaters;
|
|
316
|
+
if (input.commitCause) report.commitCause = input.commitCause;
|
|
317
|
+
if (input.afterCommit !== void 0) report.afterCommit = input.afterCommit;
|
|
318
|
+
if (input.key !== void 0) report.key = input.key;
|
|
250
319
|
return report;
|
|
251
320
|
}
|
|
252
321
|
function describeTrigger(t) {
|
|
@@ -291,6 +360,9 @@ function printReport(report, options) {
|
|
|
291
360
|
c.log(`${ch.path} (${KIND_LABEL[ch.kind]})`, { prev: ch.prev, next: ch.next });
|
|
292
361
|
}
|
|
293
362
|
c.log("props", report.props);
|
|
363
|
+
if (report.hookState && report.hookState.length) c.log("hooks", Object.fromEntries(report.hookState.map((h) => [h.path, h.value])));
|
|
364
|
+
if (report.contexts && report.contexts.length) c.log("contexts", Object.fromEntries(report.contexts.map((x) => [x.name, x.value])));
|
|
365
|
+
if (report.state) c.log("state", report.state);
|
|
294
366
|
c.groupEnd();
|
|
295
367
|
}
|
|
296
368
|
|
|
@@ -300,7 +372,7 @@ function getState() {
|
|
|
300
372
|
const g = globalThis;
|
|
301
373
|
let s = g[KEY];
|
|
302
374
|
if (!s) {
|
|
303
|
-
s = { options: {}, enabled: false, printed: /* @__PURE__ */ new Map(), detach: null, nextInstanceId: 1, nextCommitId: 1, warnedOnce: /* @__PURE__ */ new Set(), scheduled: 0, commits: 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 };
|
|
304
376
|
g[KEY] = s;
|
|
305
377
|
}
|
|
306
378
|
return s;
|
|
@@ -330,11 +402,176 @@ function dispatch(report, override) {
|
|
|
330
402
|
try {
|
|
331
403
|
options.notifier(report);
|
|
332
404
|
} catch (err) {
|
|
333
|
-
(
|
|
405
|
+
warnOnce("notifier", `notifier threw: ${String(err)}`);
|
|
334
406
|
}
|
|
335
407
|
}
|
|
336
408
|
}
|
|
337
409
|
|
|
410
|
+
// src/hookNames.ts
|
|
411
|
+
var noop = () => {
|
|
412
|
+
};
|
|
413
|
+
function customHooksFromStack(stack, componentName, marker) {
|
|
414
|
+
const names = [];
|
|
415
|
+
let past = false;
|
|
416
|
+
for (const line of stack.split("\n")) {
|
|
417
|
+
const m = /^\s*at\s+(?:async\s+)?([^\s(]+)|^([^@\s]+)@/.exec(line);
|
|
418
|
+
const name = m ? (m[1] || m[2] || "").replace(/^Object\./, "").replace(/^Proxy\./, "") : "";
|
|
419
|
+
if (!name) continue;
|
|
420
|
+
if (!past) {
|
|
421
|
+
if (name.includes(marker)) past = true;
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
if (name === componentName || name.endsWith("." + componentName)) break;
|
|
425
|
+
if (/^use[A-Z0-9_]/.test(name)) names.push(name);
|
|
426
|
+
if (names.length > 8) break;
|
|
427
|
+
}
|
|
428
|
+
return names;
|
|
429
|
+
}
|
|
430
|
+
var cache = /* @__PURE__ */ new WeakMap();
|
|
431
|
+
function makeDispatcher(fiber, componentName, out, marker) {
|
|
432
|
+
let node = fiber.memoizedState;
|
|
433
|
+
let index = 0;
|
|
434
|
+
const take = (count) => {
|
|
435
|
+
const first = node;
|
|
436
|
+
for (let i = 0; i < count && node; i++) node = node.next;
|
|
437
|
+
index += count;
|
|
438
|
+
return first;
|
|
439
|
+
};
|
|
440
|
+
const record = (primitive, count) => {
|
|
441
|
+
const stack = new Error().stack || "";
|
|
442
|
+
const chain = customHooksFromStack(stack, componentName, marker);
|
|
443
|
+
const start = index;
|
|
444
|
+
const first = take(count);
|
|
445
|
+
if (chain.length) for (let i = 0; i < count; i++) out.set(start + i, chain);
|
|
446
|
+
return first;
|
|
447
|
+
};
|
|
448
|
+
const memoized = (n) => n ? n.memoizedState : void 0;
|
|
449
|
+
const dispatcher = {};
|
|
450
|
+
const define = (name, impl) => {
|
|
451
|
+
const fn = { [marker + name]: (...a) => impl(...a) }[marker + name];
|
|
452
|
+
dispatcher[name] = fn;
|
|
453
|
+
};
|
|
454
|
+
define("useState", () => {
|
|
455
|
+
const n = record("useState", 1);
|
|
456
|
+
return [memoized(n), noop];
|
|
457
|
+
});
|
|
458
|
+
define("useReducer", () => {
|
|
459
|
+
const n = record("useReducer", 1);
|
|
460
|
+
return [memoized(n), noop];
|
|
461
|
+
});
|
|
462
|
+
define("useRef", (init2) => {
|
|
463
|
+
const n = record("useRef", 1);
|
|
464
|
+
return n ? n.memoizedState : { current: init2 };
|
|
465
|
+
});
|
|
466
|
+
define("useMemo", (fn) => {
|
|
467
|
+
const n = record("useMemo", 1);
|
|
468
|
+
const s = memoized(n);
|
|
469
|
+
return Array.isArray(s) ? s[0] : typeof fn === "function" ? fn() : void 0;
|
|
470
|
+
});
|
|
471
|
+
define("useCallback", (fn) => {
|
|
472
|
+
const n = record("useCallback", 1);
|
|
473
|
+
const s = memoized(n);
|
|
474
|
+
return Array.isArray(s) ? s[0] : fn;
|
|
475
|
+
});
|
|
476
|
+
for (const effect of ["useEffect", "useLayoutEffect", "useInsertionEffect", "useImperativeHandle"]) define(effect, () => void record(effect, 1));
|
|
477
|
+
define("useContext", (ctx) => {
|
|
478
|
+
record("useContext", 0);
|
|
479
|
+
const c = ctx;
|
|
480
|
+
return c ? c._currentValue : void 0;
|
|
481
|
+
});
|
|
482
|
+
define("useDebugValue", () => void record("useDebugValue", 0));
|
|
483
|
+
define("useSyncExternalStore", (_subscribe, getSnapshot) => {
|
|
484
|
+
const n = record("useSyncExternalStore", 2);
|
|
485
|
+
return n ? n.memoizedState : typeof getSnapshot === "function" ? getSnapshot() : void 0;
|
|
486
|
+
});
|
|
487
|
+
define("useTransition", () => {
|
|
488
|
+
record("useTransition", 2);
|
|
489
|
+
return [false, noop];
|
|
490
|
+
});
|
|
491
|
+
define("useDeferredValue", (v) => {
|
|
492
|
+
const n = record("useDeferredValue", 1);
|
|
493
|
+
return n ? n.memoizedState : v;
|
|
494
|
+
});
|
|
495
|
+
define("useId", () => {
|
|
496
|
+
const n = record("useId", 1);
|
|
497
|
+
return n ? n.memoizedState : "id";
|
|
498
|
+
});
|
|
499
|
+
define("useOptimistic", (v) => {
|
|
500
|
+
const n = record("useOptimistic", 1);
|
|
501
|
+
return [n ? n.memoizedState : v, noop];
|
|
502
|
+
});
|
|
503
|
+
for (const action of ["useActionState", "useFormState"]) {
|
|
504
|
+
define(action, (_fn, init2) => {
|
|
505
|
+
const n = record(action, 3);
|
|
506
|
+
return [n ? n.memoizedState : init2, noop, false];
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
define("useEffectEvent", (fn) => {
|
|
510
|
+
record("useEffectEvent", 1);
|
|
511
|
+
return fn;
|
|
512
|
+
});
|
|
513
|
+
define("useCacheRefresh", () => {
|
|
514
|
+
record("useCacheRefresh", 1);
|
|
515
|
+
return noop;
|
|
516
|
+
});
|
|
517
|
+
define("useHostTransitionStatus", () => ({ pending: false, data: null, method: null, action: null }));
|
|
518
|
+
define("useMemoCache", (size) => new Array(Number(size) || 0).fill(/* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")));
|
|
519
|
+
define("use", (usable) => {
|
|
520
|
+
record("use", 0);
|
|
521
|
+
const u = usable;
|
|
522
|
+
if (u && typeof u.then === "function") {
|
|
523
|
+
if (u.status === "fulfilled") return u.value;
|
|
524
|
+
throw new Error("rerender-lens: cannot replay use() on a pending promise");
|
|
525
|
+
}
|
|
526
|
+
return u ? u._currentValue : void 0;
|
|
527
|
+
});
|
|
528
|
+
define("readContext", (ctx) => ctx?._currentValue);
|
|
529
|
+
return { dispatcher, nodesConsumed: () => index };
|
|
530
|
+
}
|
|
531
|
+
function renderFunctionOf(type) {
|
|
532
|
+
let t = type;
|
|
533
|
+
while (t && typeof t === "object" && t.type) t = t.type;
|
|
534
|
+
if (t && typeof t === "object" && typeof t.render === "function") return { fn: t.render, name: t.render.name || t.displayName || "Component" };
|
|
535
|
+
if (typeof t === "function") return { fn: t, name: t.name || "Component" };
|
|
536
|
+
return { fn: null, name: "Component" };
|
|
537
|
+
}
|
|
538
|
+
function resolveHookNames(fiber, ref) {
|
|
539
|
+
const type = fiber.type;
|
|
540
|
+
if (!type || !ref) return /* @__PURE__ */ new Map();
|
|
541
|
+
const cached = cache.get(type);
|
|
542
|
+
if (cached) return cached.byNode;
|
|
543
|
+
const result = { byNode: /* @__PURE__ */ new Map(), ok: false };
|
|
544
|
+
cache.set(type, result);
|
|
545
|
+
const { fn, name } = renderFunctionOf(type);
|
|
546
|
+
if (!fn) return result.byNode;
|
|
547
|
+
const useH = "H" in ref;
|
|
548
|
+
const marker = "__rl_";
|
|
549
|
+
const previous = useH ? ref.H : ref.current;
|
|
550
|
+
const { dispatcher, nodesConsumed } = makeDispatcher(fiber, name, result.byNode, marker);
|
|
551
|
+
const proxy = new Proxy(dispatcher, {
|
|
552
|
+
get(target, key) {
|
|
553
|
+
if (key in target) return target[key];
|
|
554
|
+
return () => void 0;
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
let total = 0;
|
|
558
|
+
for (let n = fiber.memoizedState; n && typeof n === "object" && "next" in n; n = n.next) total++;
|
|
559
|
+
try {
|
|
560
|
+
if (useH) ref.H = proxy;
|
|
561
|
+
else ref.current = proxy;
|
|
562
|
+
const secondArg = fiber.tag === 11 ? fiber.stateNode?.ref ?? null : void 0;
|
|
563
|
+
fn(fiber.memoizedProps ?? {}, secondArg);
|
|
564
|
+
result.ok = nodesConsumed() === total;
|
|
565
|
+
if (!result.ok) result.byNode.clear();
|
|
566
|
+
} catch {
|
|
567
|
+
result.byNode.clear();
|
|
568
|
+
} finally {
|
|
569
|
+
if (useH) ref.H = previous;
|
|
570
|
+
else ref.current = previous;
|
|
571
|
+
}
|
|
572
|
+
return result.byNode;
|
|
573
|
+
}
|
|
574
|
+
|
|
338
575
|
// src/fiber.ts
|
|
339
576
|
var FunctionComponent = 0;
|
|
340
577
|
var ClassComponent = 1;
|
|
@@ -342,10 +579,12 @@ var HostRoot = 3;
|
|
|
342
579
|
var HostComponent = 5;
|
|
343
580
|
var ContextProvider = 10;
|
|
344
581
|
var ForwardRef = 11;
|
|
582
|
+
var SuspenseComponent = 13;
|
|
345
583
|
var MemoComponent = 14;
|
|
346
584
|
var SimpleMemoComponent = 15;
|
|
347
585
|
var PerformedWork = 1;
|
|
348
586
|
var HOOK = "__REACT_DEVTOOLS_GLOBAL_HOOK__";
|
|
587
|
+
var nowMs = () => typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
|
|
349
588
|
function ensureDevtoolsHook() {
|
|
350
589
|
const g = globalThis;
|
|
351
590
|
let hook = g[HOOK];
|
|
@@ -374,7 +613,9 @@ function ensureDevtoolsHook() {
|
|
|
374
613
|
return hook;
|
|
375
614
|
}
|
|
376
615
|
var capturedRenderers = /* @__PURE__ */ new Map();
|
|
616
|
+
var dispatcherRefs = /* @__PURE__ */ new Map();
|
|
377
617
|
var INJECT_WRAPPED = /* @__PURE__ */ Symbol.for("rerender-lens.injectWrapped");
|
|
618
|
+
var ATTACHED = /* @__PURE__ */ Symbol.for("rerender-lens.attached");
|
|
378
619
|
function pickRenderer(r) {
|
|
379
620
|
const info = r ?? {};
|
|
380
621
|
return { version: info.version, bundleType: info.bundleType, rendererPackageName: info.rendererPackageName };
|
|
@@ -385,20 +626,45 @@ function wrapInject(hook) {
|
|
|
385
626
|
const original = hook.inject;
|
|
386
627
|
hook.inject = function(renderer) {
|
|
387
628
|
const id = original.call(this, renderer);
|
|
388
|
-
|
|
629
|
+
const key = typeof id === "number" ? id : capturedRenderers.size + 1;
|
|
630
|
+
capturedRenderers.set(key, pickRenderer(renderer));
|
|
631
|
+
const ref = renderer?.currentDispatcherRef;
|
|
632
|
+
if (ref && typeof ref === "object") dispatcherRefs.set(key, ref);
|
|
389
633
|
return id;
|
|
390
634
|
};
|
|
391
635
|
h[INJECT_WRAPPED] = true;
|
|
392
636
|
}
|
|
637
|
+
function getDispatcherRef() {
|
|
638
|
+
for (const ref of dispatcherRefs.values()) return ref;
|
|
639
|
+
const g = globalThis;
|
|
640
|
+
const hook = g[HOOK];
|
|
641
|
+
let found = null;
|
|
642
|
+
if (hook && hook.renderers && typeof hook.renderers.forEach === "function") {
|
|
643
|
+
hook.renderers.forEach((r) => {
|
|
644
|
+
const ref = r?.currentDispatcherRef;
|
|
645
|
+
if (!found && ref && typeof ref === "object") found = ref;
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
return found;
|
|
649
|
+
}
|
|
393
650
|
function attach() {
|
|
394
651
|
const hook = ensureDevtoolsHook();
|
|
395
652
|
wrapInject(hook);
|
|
653
|
+
if (hook[ATTACHED]) return () => {
|
|
654
|
+
};
|
|
655
|
+
hook[ATTACHED] = true;
|
|
396
656
|
const previous = hook.onCommitFiberRoot;
|
|
397
657
|
const patched = function(id, root, priority, didError) {
|
|
658
|
+
const started = nowMs();
|
|
398
659
|
try {
|
|
399
660
|
onCommit(root, priorityLabel(priority));
|
|
400
661
|
} catch (err) {
|
|
401
662
|
warnOnce("commit", `failed to inspect a commit: ${String(err)}`);
|
|
663
|
+
} finally {
|
|
664
|
+
const spent = nowMs() - started;
|
|
665
|
+
const s = getState();
|
|
666
|
+
s.overheadMs += spent;
|
|
667
|
+
if (spent > s.maxCommitMs) s.maxCommitMs = spent;
|
|
402
668
|
}
|
|
403
669
|
if (typeof previous === "function") return previous.call(this, id, root, priority, didError);
|
|
404
670
|
};
|
|
@@ -410,6 +676,7 @@ function attach() {
|
|
|
410
676
|
};
|
|
411
677
|
hook.onScheduleFiberRoot = patchedSchedule;
|
|
412
678
|
return () => {
|
|
679
|
+
hook[ATTACHED] = false;
|
|
413
680
|
if (hook.onCommitFiberRoot === patched) hook.onCommitFiberRoot = previous;
|
|
414
681
|
if (hook.onScheduleFiberRoot === patchedSchedule) hook.onScheduleFiberRoot = previousSchedule;
|
|
415
682
|
};
|
|
@@ -478,11 +745,14 @@ var counts = /* @__PURE__ */ new WeakMap();
|
|
|
478
745
|
var ids = /* @__PURE__ */ new WeakMap();
|
|
479
746
|
var fibersById = /* @__PURE__ */ new Map();
|
|
480
747
|
var hasWeakRef = typeof WeakRef === "function";
|
|
748
|
+
var pruneAt = 5e3;
|
|
481
749
|
function remember2(id, fiber) {
|
|
482
750
|
if (!hasWeakRef) return;
|
|
751
|
+
if (fibersById.get(id)?.deref() === fiber) return;
|
|
483
752
|
fibersById.set(id, new WeakRef(fiber));
|
|
484
|
-
if (fibersById.size >
|
|
753
|
+
if (fibersById.size > pruneAt) {
|
|
485
754
|
for (const [k, ref] of fibersById) if (!ref.deref()) fibersById.delete(k);
|
|
755
|
+
pruneAt = Math.max(5e3, fibersById.size * 2);
|
|
486
756
|
}
|
|
487
757
|
}
|
|
488
758
|
function instanceIdOf(fiber) {
|
|
@@ -537,7 +807,7 @@ function nearestComponent(fiber) {
|
|
|
537
807
|
}
|
|
538
808
|
var INTERNAL_FRAME = /react-dom|react_jsx|jsx-(dev-)?runtime|\/react\/|node_modules\/react|react-stack-bottom-frame|react_stack_bottom_frame|scheduler/;
|
|
539
809
|
function parseStackLocation(stack) {
|
|
540
|
-
for (const line of stack.split("\n")) {
|
|
810
|
+
for (const line of stack.split("\n", 24)) {
|
|
541
811
|
const m = /(?:at\s+(?:.*?\s+)?\(?|@)?((?:https?|file|webpack|vite|blob):[^\s()]+?):(\d+):(\d+)\)?\s*$/.exec(line.trim());
|
|
542
812
|
if (!m || !m[1] || INTERNAL_FRAME.test(m[1])) continue;
|
|
543
813
|
return { fileName: m[1], lineNumber: Number(m[2]), columnNumber: Number(m[3]) };
|
|
@@ -553,9 +823,16 @@ function sourceOf(fiber) {
|
|
|
553
823
|
return out;
|
|
554
824
|
}
|
|
555
825
|
const st = fiber._debugStack;
|
|
556
|
-
|
|
557
|
-
|
|
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;
|
|
558
834
|
}
|
|
835
|
+
var sourceCache = /* @__PURE__ */ new WeakMap();
|
|
559
836
|
function bumpCount(fiber) {
|
|
560
837
|
const prev = counts.get(fiber) ?? (fiber.alternate ? counts.get(fiber.alternate) : void 0) ?? 0;
|
|
561
838
|
const n = prev + 1;
|
|
@@ -579,7 +856,7 @@ function ownerName(fiber) {
|
|
|
579
856
|
}
|
|
580
857
|
var isStateNode = (n) => !!n.queue && typeof n.queue.lastRenderedReducer === "function";
|
|
581
858
|
var isStoreNode = (n) => !!n.queue && typeof n.queue.getSnapshot === "function";
|
|
582
|
-
function
|
|
859
|
+
function hookLabel2(node) {
|
|
583
860
|
if (isStoreNode(node)) return "useSyncExternalStore";
|
|
584
861
|
if (isStateNode(node)) {
|
|
585
862
|
const r = node.queue.lastRenderedReducer;
|
|
@@ -588,20 +865,43 @@ function hookLabel(node) {
|
|
|
588
865
|
return "state";
|
|
589
866
|
}
|
|
590
867
|
var NODELESS_HOOKS = /* @__PURE__ */ new Set(["useContext", "useDebugValue", "use"]);
|
|
868
|
+
function hookLabelsFor(fiber, first) {
|
|
869
|
+
const types = fiber._debugHookTypes?.filter((t) => !NODELESS_HOOKS.has(t));
|
|
870
|
+
let nodeCount = 0;
|
|
871
|
+
for (let n = first; n; n = n.next) nodeCount++;
|
|
872
|
+
return types && types.length === nodeCount ? types : null;
|
|
873
|
+
}
|
|
874
|
+
var isHookList = (v) => !!v && typeof v === "object" && "next" in v;
|
|
875
|
+
function snapshotHooks(fiber) {
|
|
876
|
+
const out = [];
|
|
877
|
+
if (fiber.tag === ClassComponent) return out;
|
|
878
|
+
const first = fiber.memoizedState;
|
|
879
|
+
if (!isHookList(first)) return out;
|
|
880
|
+
const labels = hookLabelsFor(fiber, first);
|
|
881
|
+
let i = 0;
|
|
882
|
+
for (let n = first; n; n = n.next, i++) {
|
|
883
|
+
if (!isStateNode(n) && !isStoreNode(n)) continue;
|
|
884
|
+
const hook = labels?.[i] ?? hookLabel2(n);
|
|
885
|
+
out.push({ path: `${hook}#${i}`, hook, index: i, value: n.memoizedState });
|
|
886
|
+
}
|
|
887
|
+
return out;
|
|
888
|
+
}
|
|
889
|
+
function snapshotContexts(fiber) {
|
|
890
|
+
const out = [];
|
|
891
|
+
for (let d = fiber.dependencies?.firstContext ?? null; d; d = d.next) out.push({ name: d.context.displayName ?? "Context", value: d.memoizedValue });
|
|
892
|
+
return out;
|
|
893
|
+
}
|
|
591
894
|
function diffHooks(fiber, alt) {
|
|
592
895
|
const out = [];
|
|
593
896
|
if (fiber.tag === ClassComponent) return out;
|
|
594
897
|
let a = alt.memoizedState;
|
|
595
898
|
let b = fiber.memoizedState;
|
|
596
899
|
if (!a || !b || typeof b !== "object" || !("next" in b)) return out;
|
|
597
|
-
const
|
|
598
|
-
let nodeCount = 0;
|
|
599
|
-
for (let n = b; n; n = n.next) nodeCount++;
|
|
600
|
-
const labels = types && types.length === nodeCount ? types : null;
|
|
900
|
+
const labels = hookLabelsFor(fiber, b);
|
|
601
901
|
let i = 0;
|
|
602
902
|
while (a && b) {
|
|
603
903
|
if ((isStateNode(b) || isStoreNode(b)) && !Object.is(a.memoizedState, b.memoizedState)) {
|
|
604
|
-
const hook = labels?.[i] ??
|
|
904
|
+
const hook = labels?.[i] ?? hookLabel2(b);
|
|
605
905
|
out.push({
|
|
606
906
|
path: `${hook}#${i}`,
|
|
607
907
|
hook,
|
|
@@ -690,14 +990,14 @@ function analyze(fiber, alt, trackHooks) {
|
|
|
690
990
|
const trigger = causes.length === 0 ? "parent" : causes.length === 1 ? causes[0] : "mixed";
|
|
691
991
|
return { propChanges, stateChanges, hookChanges, trigger };
|
|
692
992
|
}
|
|
693
|
-
function nearestRenderedAncestor(fiber,
|
|
993
|
+
function nearestRenderedAncestor(fiber, cache2, trackHooks) {
|
|
694
994
|
let f = fiber.return;
|
|
695
995
|
while (f && f.tag !== HostRoot) {
|
|
696
996
|
if (isComponentTag(f.tag) && f.alternate && didRender(f)) {
|
|
697
|
-
let info =
|
|
997
|
+
let info = cache2.get(f);
|
|
698
998
|
if (info === void 0) {
|
|
699
999
|
info = { name: fiberName(f), trigger: analyze(f, f.alternate, trackHooks).trigger };
|
|
700
|
-
|
|
1000
|
+
cache2.set(f, info);
|
|
701
1001
|
}
|
|
702
1002
|
return info;
|
|
703
1003
|
}
|
|
@@ -705,6 +1005,23 @@ function nearestRenderedAncestor(fiber, cache, trackHooks) {
|
|
|
705
1005
|
}
|
|
706
1006
|
return null;
|
|
707
1007
|
}
|
|
1008
|
+
function updatersOf(root) {
|
|
1009
|
+
const set = root.memoizedUpdaters;
|
|
1010
|
+
if (!set || typeof set.forEach !== "function") return [];
|
|
1011
|
+
const names = [];
|
|
1012
|
+
set.forEach((f) => {
|
|
1013
|
+
const comp = nearestComponent(f);
|
|
1014
|
+
if (comp) {
|
|
1015
|
+
const name = fiberName(comp);
|
|
1016
|
+
if (!names.includes(name)) names.push(name);
|
|
1017
|
+
}
|
|
1018
|
+
});
|
|
1019
|
+
return names;
|
|
1020
|
+
}
|
|
1021
|
+
var lastCommit = null;
|
|
1022
|
+
var EFFECT_LOOP_WINDOW_MS = 50;
|
|
1023
|
+
var MAX_REPORTS_PER_COMMIT = 200;
|
|
1024
|
+
var COMMIT_TIME_BUDGET_MS = 25;
|
|
708
1025
|
function onCommit(root, commitPriority) {
|
|
709
1026
|
const s = getState();
|
|
710
1027
|
if (!s.enabled) return;
|
|
@@ -712,48 +1029,95 @@ function onCommit(root, commitPriority) {
|
|
|
712
1029
|
const o = s.options;
|
|
713
1030
|
const trackHooks = o.trackHooks !== false;
|
|
714
1031
|
const rendered = [];
|
|
1032
|
+
const renderedNames = /* @__PURE__ */ new Set();
|
|
715
1033
|
let hot = false;
|
|
1034
|
+
let suspenseResolved = false;
|
|
716
1035
|
const stack = [root.current];
|
|
717
1036
|
while (stack.length) {
|
|
718
1037
|
const fiber = stack.pop();
|
|
719
1038
|
const alt = fiber.alternate;
|
|
720
1039
|
if (alt && isComponentTag(fiber.tag) && didRender(fiber)) {
|
|
721
1040
|
if (isHotSwapped(fiber, alt)) hot = true;
|
|
1041
|
+
renderedNames.add(fiberName(fiber));
|
|
722
1042
|
if (shouldTrack(fiberType(fiber), o)) rendered.push(fiber);
|
|
723
1043
|
}
|
|
1044
|
+
if (fiber.tag === SuspenseComponent && alt && alt.memoizedState !== null && fiber.memoizedState === null) suspenseResolved = true;
|
|
724
1045
|
if (!alt || fiber.child !== alt.child) {
|
|
725
1046
|
for (let c = fiber.child; c; c = c.sibling) stack.push(c);
|
|
726
1047
|
}
|
|
727
1048
|
}
|
|
728
1049
|
if (hot && o.ignoreHotReload !== false) return;
|
|
729
1050
|
rendered.reverse();
|
|
730
|
-
|
|
1051
|
+
const at = nowMs();
|
|
1052
|
+
const updaters = updatersOf(root);
|
|
1053
|
+
let commitCause;
|
|
1054
|
+
let afterCommit;
|
|
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))) {
|
|
1057
|
+
commitCause = "effect-after-commit";
|
|
1058
|
+
afterCommit = lastCommit.id;
|
|
1059
|
+
} else if (suspenseResolved) commitCause = "suspense-resolved";
|
|
1060
|
+
if (rendered.length === 0) {
|
|
1061
|
+
lastCommit = { id: s.nextCommitId, at, rendered: renderedNames };
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
731
1064
|
const commitId = s.nextCommitId++;
|
|
1065
|
+
lastCommit = { id: commitId, at, rendered: renderedNames };
|
|
1066
|
+
const dispatcherRef = o.resolveHookNames ? getDispatcherRef() : null;
|
|
732
1067
|
const parentCache = /* @__PURE__ */ new Map();
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
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;
|
|
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);
|
|
1118
|
+
}
|
|
1119
|
+
} finally {
|
|
1120
|
+
endDiffScope();
|
|
757
1121
|
}
|
|
758
1122
|
}
|
|
759
1123
|
|
|
@@ -855,6 +1219,201 @@ function useWhyRerender(name, values, options) {
|
|
|
855
1219
|
});
|
|
856
1220
|
}
|
|
857
1221
|
|
|
1222
|
+
// src/fixes.ts
|
|
1223
|
+
var AVOIDABLE = /* @__PURE__ */ new Set(["deep-equal", "function", "element"]);
|
|
1224
|
+
var rootOf = (path) => path.split(/[.[]/)[0] || path;
|
|
1225
|
+
function fixesFor(r) {
|
|
1226
|
+
const out = [];
|
|
1227
|
+
const owner = r.owner || r.parent && r.parent.name || "?";
|
|
1228
|
+
const changes = [...r.propChanges, ...r.stateChanges, ...r.hookChanges];
|
|
1229
|
+
if (r.avoidable && (changes.length === 0 || r.memoized === false)) {
|
|
1230
|
+
out.push({
|
|
1231
|
+
kind: "memo",
|
|
1232
|
+
owner: r.component,
|
|
1233
|
+
target: r.component,
|
|
1234
|
+
prop: null,
|
|
1235
|
+
label: `Wrap <${r.component}> in React.memo`,
|
|
1236
|
+
detail: changes.length === 0 ? `<${r.component}> re-rendered with identical props because <${r.parent && r.parent.name || "its parent"}> re-rendered.` : `<${r.component}> is not memoized: fixing its props alone will not stop the re-render.`
|
|
1237
|
+
});
|
|
1238
|
+
}
|
|
1239
|
+
for (const c of r.propChanges) {
|
|
1240
|
+
if (!AVOIDABLE.has(c.kind)) continue;
|
|
1241
|
+
const root = rootOf(c.path);
|
|
1242
|
+
if (root === "children" && (c.kind === "element" || c.kind === "deep-equal")) {
|
|
1243
|
+
out.push({ kind: "children", owner, target: r.component, prop: "children", label: `memoize children of <${r.component}> in <${owner}>`, detail: `<${owner}> re-creates the children of <${r.component}> on every render.` });
|
|
1244
|
+
} else if (c.kind === "function") {
|
|
1245
|
+
out.push({ kind: "useCallback", owner, target: r.component, prop: root, label: `useCallback(${root}) in <${owner}>`, detail: `prop "${c.path}" of <${r.component}> is a new function on every render of <${owner}>.` });
|
|
1246
|
+
} else if (c.kind === "element") {
|
|
1247
|
+
out.push({ kind: "useMemoElement", owner, target: r.component, prop: root, label: `memoize element prop ${root} in <${owner}>`, detail: `prop "${c.path}" of <${r.component}> is a new element with the same type and props on every render of <${owner}>.` });
|
|
1248
|
+
} else {
|
|
1249
|
+
out.push({ kind: "useMemo", owner, target: r.component, prop: root, label: `useMemo(${root}) in <${owner}>`, detail: `prop "${c.path}" of <${r.component}> is a new ${Array.isArray(c.next) ? "array" : "object"} with the same contents on every render of <${owner}>.` });
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
for (const c of [...r.stateChanges, ...r.hookChanges]) {
|
|
1253
|
+
const isContext = c.hook === "useContext";
|
|
1254
|
+
const ctxName = isContext ? (/useContext\((.*)\)/.exec(c.path) || [])[1] || "Context" : "";
|
|
1255
|
+
const providerOwner = isContext && c.provider && c.provider.component ? c.provider.component : null;
|
|
1256
|
+
if (isContext && c.kind === "different" && c.changedKeys && typeof c.totalKeys === "number" && c.changedKeys.length > 0 && c.changedKeys.length < c.totalKeys) {
|
|
1257
|
+
out.push({
|
|
1258
|
+
kind: "splitContext",
|
|
1259
|
+
owner: providerOwner || `${ctxName}.Provider`,
|
|
1260
|
+
target: r.component,
|
|
1261
|
+
prop: ctxName,
|
|
1262
|
+
label: `split ${ctxName}${providerOwner ? ` in <${providerOwner}>` : ""}: only ${c.changedKeys.join(", ")} changed`,
|
|
1263
|
+
detail: `${c.changedKeys.length} of ${c.totalKeys} keys changed in ${ctxName}, yet every consumer re-rendered.`
|
|
1264
|
+
});
|
|
1265
|
+
continue;
|
|
1266
|
+
}
|
|
1267
|
+
if (!AVOIDABLE.has(c.kind)) continue;
|
|
1268
|
+
if (isContext) {
|
|
1269
|
+
out.push({ kind: "contextValue", owner: providerOwner || `${ctxName}.Provider`, target: r.component, prop: ctxName, label: `memoize the ${ctxName} provider value${providerOwner ? ` in <${providerOwner}>` : ""}`, detail: `${ctxName} produced a new value that is deep-equal to the previous one.` });
|
|
1270
|
+
} else if (c.hook === "useSyncExternalStore") {
|
|
1271
|
+
const chain = c.custom || [];
|
|
1272
|
+
const redux = chain.some((n) => /^use(App)?Selector$/.test(n));
|
|
1273
|
+
const zustand = !redux && chain.some((n) => /^use[A-Z]\w*Store$/.test(n) || n === "useStore" || n === "useBoundStore");
|
|
1274
|
+
out.push({ kind: "storeSnapshot", owner: r.component, target: r.component, prop: c.path, label: redux ? `memoize the selector in <${r.component}>` : zustand ? `useShallow in <${r.component}>` : `stable getSnapshot in <${r.component}>`, detail: storeAdvice(c.custom) });
|
|
1275
|
+
} else {
|
|
1276
|
+
out.push({ kind: "bailout", owner: r.component, target: r.component, prop: c.path, label: `bail out before setting ${c.path} in <${r.component}>`, detail: `${c.path} was set to a value deep-equal to the current one (new reference).` });
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
return out;
|
|
1280
|
+
}
|
|
1281
|
+
var fixKey = (f) => `${f.kind}|${f.owner}|${f.prop || f.target}`;
|
|
1282
|
+
function rankFixes(reports) {
|
|
1283
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
1284
|
+
for (const r of reports) {
|
|
1285
|
+
if (!r.avoidable) continue;
|
|
1286
|
+
for (const f of fixesFor(r)) {
|
|
1287
|
+
const key = fixKey(f);
|
|
1288
|
+
let agg = byKey.get(key);
|
|
1289
|
+
if (!agg) {
|
|
1290
|
+
agg = { ...f, key, count: 0, components: {} };
|
|
1291
|
+
byKey.set(key, agg);
|
|
1292
|
+
}
|
|
1293
|
+
agg.count++;
|
|
1294
|
+
agg.components[r.component] = (agg.components[r.component] || 0) + 1;
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
return [...byKey.values()].sort((a, b) => b.count - a.count || a.label.localeCompare(b.label));
|
|
1298
|
+
}
|
|
1299
|
+
function formatFixes(reports, limit = 10) {
|
|
1300
|
+
const ranked = rankFixes(reports);
|
|
1301
|
+
if (!ranked.length) return "No avoidable re-renders.";
|
|
1302
|
+
const lines = ranked.slice(0, limit).map((f, i) => `${String(i + 1).padStart(2)}. ${f.label} (removes ${f.count}: ${Object.entries(f.components).map(([c, n]) => `<${c}>${n > 1 ? " x" + n : ""}`).join(", ")})`);
|
|
1303
|
+
if (ranked.length > limit) lines.push(` \u2026 ${ranked.length - limit} more`);
|
|
1304
|
+
return lines.join("\n");
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
// src/sessions.ts
|
|
1308
|
+
function summarizeReports(reports, meta = {}) {
|
|
1309
|
+
var _a;
|
|
1310
|
+
const byComponent = {};
|
|
1311
|
+
let avoidable = 0;
|
|
1312
|
+
let wasted = 0;
|
|
1313
|
+
for (const r of reports) {
|
|
1314
|
+
const c = byComponent[_a = r.component] || (byComponent[_a] = { total: 0, avoidable: 0, wasted: 0 });
|
|
1315
|
+
c.total++;
|
|
1316
|
+
if (r.avoidable) {
|
|
1317
|
+
c.avoidable++;
|
|
1318
|
+
avoidable++;
|
|
1319
|
+
if (typeof r.selfDuration === "number") {
|
|
1320
|
+
c.wasted += r.selfDuration;
|
|
1321
|
+
wasted += r.selfDuration;
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
return {
|
|
1326
|
+
id: meta.id ?? `s${Date.now().toString(36)}`,
|
|
1327
|
+
name: meta.name ?? "session",
|
|
1328
|
+
startedAt: meta.startedAt ?? (reports[0]?.time ?? 0),
|
|
1329
|
+
endedAt: meta.endedAt ?? (reports[reports.length - 1]?.time ?? null),
|
|
1330
|
+
total: reports.length,
|
|
1331
|
+
avoidable,
|
|
1332
|
+
wasted,
|
|
1333
|
+
byComponent,
|
|
1334
|
+
fixes: rankFixes(reports).map((f) => ({ key: f.key, label: f.label, count: f.count }))
|
|
1335
|
+
};
|
|
1336
|
+
}
|
|
1337
|
+
function compareSummaries(before, after) {
|
|
1338
|
+
const names = /* @__PURE__ */ new Set([...Object.keys(before.byComponent), ...Object.keys(after.byComponent)]);
|
|
1339
|
+
const rows = [];
|
|
1340
|
+
for (const component of names) {
|
|
1341
|
+
const b = before.byComponent[component]?.avoidable || 0;
|
|
1342
|
+
const a = after.byComponent[component]?.avoidable || 0;
|
|
1343
|
+
if (b || a) rows.push({ component, before: b, after: a, delta: a - b });
|
|
1344
|
+
}
|
|
1345
|
+
rows.sort((x, y) => x.delta - y.delta || y.before - x.before || x.component.localeCompare(y.component));
|
|
1346
|
+
const afterKeys = new Set(after.fixes.map((f) => f.key));
|
|
1347
|
+
const beforeKeys = new Set(before.fixes.map((f) => f.key));
|
|
1348
|
+
return {
|
|
1349
|
+
before,
|
|
1350
|
+
after,
|
|
1351
|
+
rows,
|
|
1352
|
+
total: { before: before.total, after: after.total, delta: after.total - before.total },
|
|
1353
|
+
avoidable: { before: before.avoidable, after: after.avoidable, delta: after.avoidable - before.avoidable },
|
|
1354
|
+
wasted: { before: before.wasted, after: after.wasted, delta: after.wasted - before.wasted },
|
|
1355
|
+
resolvedFixes: before.fixes.filter((f) => !afterKeys.has(f.key)),
|
|
1356
|
+
newFixes: after.fixes.filter((f) => !beforeKeys.has(f.key)),
|
|
1357
|
+
regressions: rows.filter((r) => r.delta > 0)
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1360
|
+
function formatComparison(c) {
|
|
1361
|
+
const w = Math.max(14, ...c.rows.map((r) => r.component.length + 2));
|
|
1362
|
+
const line = (label, b, a, d) => `${label.padEnd(w)} ${String(b).padStart(8)} ${String(a).padStart(8)} ${d.padStart(8)}`;
|
|
1363
|
+
const delta = (n, suffix = "") => n === 0 ? "\xB10" : `${n > 0 ? "+" : ""}${Number.isInteger(n) ? n : n.toFixed(1)}${suffix}`;
|
|
1364
|
+
const out = [line("avoidable", c.before.name.slice(0, 8), c.after.name.slice(0, 8), "\u0394"), line("all components", c.avoidable.before, c.avoidable.after, delta(c.avoidable.delta))];
|
|
1365
|
+
if (c.wasted.before || c.wasted.after) out.push(line("wasted ms", c.wasted.before.toFixed(1), c.wasted.after.toFixed(1), delta(Number(c.wasted.delta.toFixed(1)))));
|
|
1366
|
+
for (const r of c.rows) out.push(line(`<${r.component}>`, r.before, r.after, delta(r.delta)));
|
|
1367
|
+
if (c.resolvedFixes.length) out.push("", "no longer needed:", ...c.resolvedFixes.map((f) => ` - ${f.label}`));
|
|
1368
|
+
if (c.newFixes.length) out.push("", "new:", ...c.newFixes.map((f) => ` - ${f.label}`));
|
|
1369
|
+
return out.join("\n");
|
|
1370
|
+
}
|
|
1371
|
+
function parseExport(data) {
|
|
1372
|
+
if (Array.isArray(data)) return { reports: data, sessions: [], summary: null };
|
|
1373
|
+
if (data && typeof data === "object") {
|
|
1374
|
+
const d = data;
|
|
1375
|
+
if (Array.isArray(d.reports)) return { reports: d.reports, sessions: Array.isArray(d.sessions) ? d.sessions : [], summary: null };
|
|
1376
|
+
if (d.byComponent && typeof d.byComponent === "object") return { reports: [], sessions: [], summary: data };
|
|
1377
|
+
}
|
|
1378
|
+
throw new Error("not a rerender-lens export or session summary");
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
// src/budget.ts
|
|
1382
|
+
function avoidableCounts(reports) {
|
|
1383
|
+
const counts2 = {};
|
|
1384
|
+
for (const r of reports) if (r.avoidable) counts2[r.component] = (counts2[r.component] || 0) + 1;
|
|
1385
|
+
return counts2;
|
|
1386
|
+
}
|
|
1387
|
+
function checkBudget(reports, budget) {
|
|
1388
|
+
const b = typeof budget === "number" ? { "*": budget } : budget;
|
|
1389
|
+
const fallback = typeof b["*"] === "number" ? b["*"] : 0;
|
|
1390
|
+
const counts2 = avoidableCounts(reports);
|
|
1391
|
+
const violations = [];
|
|
1392
|
+
const slack = [];
|
|
1393
|
+
const names = /* @__PURE__ */ new Set([...Object.keys(counts2), ...Object.keys(b).filter((k) => k !== "*")]);
|
|
1394
|
+
for (const component of names) {
|
|
1395
|
+
const allowed = typeof b[component] === "number" ? b[component] : fallback;
|
|
1396
|
+
const avoidable = counts2[component] || 0;
|
|
1397
|
+
if (avoidable > allowed) violations.push({ component, avoidable, allowed });
|
|
1398
|
+
else if (avoidable < allowed) slack.push({ component, avoidable, allowed });
|
|
1399
|
+
}
|
|
1400
|
+
violations.sort((x, y) => y.avoidable - y.allowed - (x.avoidable - x.allowed));
|
|
1401
|
+
return { ok: violations.length === 0, violations, slack, counts: counts2 };
|
|
1402
|
+
}
|
|
1403
|
+
function toBudget(reports) {
|
|
1404
|
+
return { "*": 0, ...avoidableCounts(reports) };
|
|
1405
|
+
}
|
|
1406
|
+
function assertWithinBudget(reports, budget) {
|
|
1407
|
+
const result = checkBudget(reports, budget);
|
|
1408
|
+
if (result.ok) return result;
|
|
1409
|
+
const lines = result.violations.map((v) => ` <${v.component}>: ${v.avoidable} avoidable re-render${v.avoidable === 1 ? "" : "s"} (budget ${v.allowed})`);
|
|
1410
|
+
throw new Error(`Re-render budget exceeded:
|
|
1411
|
+
${lines.join("\n")}
|
|
1412
|
+
|
|
1413
|
+
Fixes, most impact first:
|
|
1414
|
+
${formatFixes(reports)}`);
|
|
1415
|
+
}
|
|
1416
|
+
|
|
858
1417
|
// src/notifiers.ts
|
|
859
1418
|
function createCollector() {
|
|
860
1419
|
const reports = [];
|
|
@@ -875,8 +1434,14 @@ function createCollector() {
|
|
|
875
1434
|
const lines = bad.map((r) => ` <${r.component}> render #${r.renderCount}:
|
|
876
1435
|
${r.reasons.map((x) => ` - ${x}`).join("\n")}`);
|
|
877
1436
|
throw new Error(`${bad.length} avoidable re-render${bad.length === 1 ? "" : "s"} detected:
|
|
878
|
-
${lines.join("\n")}
|
|
879
|
-
|
|
1437
|
+
${lines.join("\n")}
|
|
1438
|
+
|
|
1439
|
+
Fixes, most impact first:
|
|
1440
|
+
${formatFixes(reports)}`);
|
|
1441
|
+
},
|
|
1442
|
+
fixes: () => rankFixes(reports),
|
|
1443
|
+
summary: (name) => summarizeReports(reports, name ? { name } : {}),
|
|
1444
|
+
assertWithinBudget: (budget) => assertWithinBudget(reports, budget)
|
|
880
1445
|
};
|
|
881
1446
|
}
|
|
882
1447
|
function combineNotifiers(...notifiers) {
|
|
@@ -946,13 +1511,24 @@ function highlight(target) {
|
|
|
946
1511
|
for (const s of l.sticky) s.remove();
|
|
947
1512
|
l.sticky = [];
|
|
948
1513
|
if (!target) return;
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
if (rect.width === 0 && rect.height === 0) continue;
|
|
1514
|
+
const frag = document.createDocumentFragment();
|
|
1515
|
+
for (const rect of measure(target.nodes)) {
|
|
952
1516
|
const b = box(rect, "#1a73e8", "rgba(26, 115, 232, 0.12)", target.label);
|
|
953
|
-
|
|
1517
|
+
frag.appendChild(b);
|
|
954
1518
|
l.sticky.push(b);
|
|
955
1519
|
}
|
|
1520
|
+
l.root.appendChild(frag);
|
|
1521
|
+
}
|
|
1522
|
+
var MAX_BOXES = 100;
|
|
1523
|
+
function measure(nodes) {
|
|
1524
|
+
const rects = [];
|
|
1525
|
+
for (const node of nodes) {
|
|
1526
|
+
if (rects.length >= MAX_BOXES) break;
|
|
1527
|
+
const rect = node.getBoundingClientRect();
|
|
1528
|
+
if (rect.width === 0 && rect.height === 0) continue;
|
|
1529
|
+
rects.push(rect);
|
|
1530
|
+
}
|
|
1531
|
+
return rects;
|
|
956
1532
|
}
|
|
957
1533
|
function clearHighlight() {
|
|
958
1534
|
highlight(null);
|
|
@@ -960,26 +1536,34 @@ function clearHighlight() {
|
|
|
960
1536
|
function flash(target, duration = 500) {
|
|
961
1537
|
const l = ensureLayer();
|
|
962
1538
|
if (!l) return;
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
1539
|
+
const frag = document.createDocumentFragment();
|
|
1540
|
+
const boxes = [];
|
|
1541
|
+
for (const rect of measure(target.nodes)) {
|
|
966
1542
|
const b = box(rect, "#d93025", "rgba(217, 48, 37, 0.15)");
|
|
967
1543
|
b.style.transition = `opacity ${duration}ms ease-out`;
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
b.style.opacity = "0";
|
|
971
|
-
});
|
|
972
|
-
setTimeout(() => b.remove(), duration + 50);
|
|
1544
|
+
frag.appendChild(b);
|
|
1545
|
+
boxes.push(b);
|
|
973
1546
|
}
|
|
1547
|
+
if (!boxes.length) return;
|
|
1548
|
+
l.root.appendChild(frag);
|
|
1549
|
+
requestAnimationFrame(() => {
|
|
1550
|
+
for (const b of boxes) b.style.opacity = "0";
|
|
1551
|
+
});
|
|
1552
|
+
setTimeout(() => {
|
|
1553
|
+
for (const b of boxes) b.remove();
|
|
1554
|
+
}, duration + 50);
|
|
974
1555
|
}
|
|
975
1556
|
|
|
976
1557
|
// src/version.ts
|
|
977
|
-
var VERSION = "0.
|
|
1558
|
+
var VERSION = "0.4.0" ;
|
|
978
1559
|
|
|
979
1560
|
// src/devtools.ts
|
|
980
1561
|
var DEVTOOLS_MARKER = "__rerenderLens";
|
|
981
1562
|
var PROTOCOL_VERSION = 2;
|
|
982
|
-
|
|
1563
|
+
var DEFAULT_CHANNEL = "rerender-lens";
|
|
1564
|
+
var SERIALIZE_MAX_ENTRIES = 100;
|
|
1565
|
+
var SERIALIZE_MAX_NODES = 2e4;
|
|
1566
|
+
function serialize(value, maxDepth = 4, seen = /* @__PURE__ */ new WeakSet(), depth = 0, budget = { nodes: SERIALIZE_MAX_NODES }) {
|
|
983
1567
|
if (value === null || value === void 0) return value;
|
|
984
1568
|
const t = typeof value;
|
|
985
1569
|
if (t === "string" || t === "boolean") return value;
|
|
@@ -990,24 +1574,56 @@ function serialize(value, maxDepth = 6, seen = /* @__PURE__ */ new WeakSet(), de
|
|
|
990
1574
|
const obj = value;
|
|
991
1575
|
if (seen.has(obj)) return "[Circular]";
|
|
992
1576
|
if (depth >= maxDepth) return "[\u2026]";
|
|
1577
|
+
if (--budget.nodes < 0) return "[\u2026]";
|
|
1578
|
+
const next = (v) => serialize(v, maxDepth, seen, depth + 1, budget);
|
|
993
1579
|
seen.add(obj);
|
|
994
1580
|
try {
|
|
995
1581
|
if (isReactElement(obj)) {
|
|
996
1582
|
const out2 = { $type: "element", name: getDisplayName(obj.type) };
|
|
997
1583
|
if (obj.key !== null && obj.key !== void 0) out2.key = String(obj.key);
|
|
998
|
-
out2.props =
|
|
1584
|
+
out2.props = next(obj.props);
|
|
999
1585
|
return out2;
|
|
1000
1586
|
}
|
|
1001
1587
|
if (obj instanceof Date) return { $type: "Date", value: obj.toISOString() };
|
|
1002
1588
|
if (obj instanceof RegExp) return { $type: "RegExp", value: String(obj) };
|
|
1589
|
+
if (ArrayBuffer.isView(obj) || obj instanceof ArrayBuffer) {
|
|
1590
|
+
const bin = obj;
|
|
1591
|
+
return { $type: bin.constructor?.name || "ArrayBuffer", length: typeof bin.length === "number" ? bin.length : bin.byteLength };
|
|
1592
|
+
}
|
|
1593
|
+
if (obj instanceof Promise) return "[Promise]";
|
|
1594
|
+
if (typeof Node !== "undefined" && obj instanceof Node) return typeof Element !== "undefined" && obj instanceof Element ? `<${obj.tagName.toLowerCase()}>` : `[${obj.nodeName}]`;
|
|
1595
|
+
if (obj === globalThis) return "[Window]";
|
|
1003
1596
|
if (obj instanceof Map) {
|
|
1004
|
-
|
|
1597
|
+
const entries = [];
|
|
1598
|
+
for (const [k, v] of obj) {
|
|
1599
|
+
if (entries.length >= SERIALIZE_MAX_ENTRIES) {
|
|
1600
|
+
entries.push(["\u2026", `+${obj.size - SERIALIZE_MAX_ENTRIES} more`]);
|
|
1601
|
+
break;
|
|
1602
|
+
}
|
|
1603
|
+
entries.push([next(k), next(v)]);
|
|
1604
|
+
}
|
|
1605
|
+
return { $type: "Map", entries };
|
|
1606
|
+
}
|
|
1607
|
+
if (obj instanceof Set) {
|
|
1608
|
+
const values = [];
|
|
1609
|
+
for (const v of obj) {
|
|
1610
|
+
if (values.length >= SERIALIZE_MAX_ENTRIES) {
|
|
1611
|
+
values.push(`\u2026+${obj.size - SERIALIZE_MAX_ENTRIES} more`);
|
|
1612
|
+
break;
|
|
1613
|
+
}
|
|
1614
|
+
values.push(next(v));
|
|
1615
|
+
}
|
|
1616
|
+
return { $type: "Set", values };
|
|
1617
|
+
}
|
|
1618
|
+
if (Array.isArray(obj)) {
|
|
1619
|
+
const out2 = obj.slice(0, SERIALIZE_MAX_ENTRIES).map(next);
|
|
1620
|
+
if (obj.length > SERIALIZE_MAX_ENTRIES) out2.push(`\u2026+${obj.length - SERIALIZE_MAX_ENTRIES} more`);
|
|
1621
|
+
return out2;
|
|
1005
1622
|
}
|
|
1006
|
-
if (obj instanceof Set) return { $type: "Set", values: [...obj].map((v) => serialize(v, maxDepth, seen, depth + 1)) };
|
|
1007
|
-
if (Array.isArray(obj)) return obj.map((v) => serialize(v, maxDepth, seen, depth + 1));
|
|
1008
|
-
if (typeof Element !== "undefined" && obj instanceof Element) return `<${obj.tagName.toLowerCase()}>`;
|
|
1009
1623
|
const out = {};
|
|
1010
|
-
|
|
1624
|
+
const keys = Object.keys(obj);
|
|
1625
|
+
for (const k of keys.slice(0, SERIALIZE_MAX_ENTRIES)) out[k] = next(obj[k]);
|
|
1626
|
+
if (keys.length > SERIALIZE_MAX_ENTRIES) out["\u2026"] = `+${keys.length - SERIALIZE_MAX_ENTRIES} more`;
|
|
1011
1627
|
const proto = Object.getPrototypeOf(obj);
|
|
1012
1628
|
if (proto && proto !== Object.prototype && proto.constructor?.name) out.$type = proto.constructor.name;
|
|
1013
1629
|
return out;
|
|
@@ -1029,7 +1645,7 @@ var stringToMatcher = (s) => {
|
|
|
1029
1645
|
};
|
|
1030
1646
|
function serializeOptions(o) {
|
|
1031
1647
|
const out = {};
|
|
1032
|
-
const bools = ["trackAllMemoized", "trackAllComponents", "trackHooks", "logAll", "silent", "collapse", "ignoreHotReload"];
|
|
1648
|
+
const bools = ["trackAllMemoized", "trackAllComponents", "trackHooks", "includeState", "resolveHookNames", "logAll", "silent", "collapse", "ignoreHotReload"];
|
|
1033
1649
|
for (const k of bools) if (typeof o[k] === "boolean") out[k] = o[k];
|
|
1034
1650
|
if (typeof o.maxReportsPerComponent === "number") out.maxReportsPerComponent = o.maxReportsPerComponent;
|
|
1035
1651
|
if (o.include) out.include = o.include.map(matcherToString).filter((x) => x !== null);
|
|
@@ -1038,7 +1654,7 @@ function serializeOptions(o) {
|
|
|
1038
1654
|
}
|
|
1039
1655
|
function deserializeOptions(o) {
|
|
1040
1656
|
const out = {};
|
|
1041
|
-
const bools = ["trackAllMemoized", "trackAllComponents", "trackHooks", "logAll", "silent", "collapse", "ignoreHotReload"];
|
|
1657
|
+
const bools = ["trackAllMemoized", "trackAllComponents", "trackHooks", "includeState", "resolveHookNames", "logAll", "silent", "collapse", "ignoreHotReload"];
|
|
1042
1658
|
for (const k of bools) if (typeof o[k] === "boolean") out[k] = o[k];
|
|
1043
1659
|
if (typeof o.maxReportsPerComponent === "number") out.maxReportsPerComponent = o.maxReportsPerComponent;
|
|
1044
1660
|
if (Array.isArray(o.include)) out.include = o.include.filter((s) => typeof s === "string" && s).map(stringToMatcher);
|
|
@@ -1047,13 +1663,20 @@ function deserializeOptions(o) {
|
|
|
1047
1663
|
}
|
|
1048
1664
|
function createDevtoolsNotifier(options = {}) {
|
|
1049
1665
|
const bufferSize = options.bufferSize ?? 300;
|
|
1050
|
-
const maxDepth = options.maxDepth ??
|
|
1666
|
+
const maxDepth = options.maxDepth ?? 4;
|
|
1051
1667
|
const target = options.target ?? (typeof window !== "undefined" ? window : void 0);
|
|
1052
1668
|
const buffer = [];
|
|
1053
1669
|
let seq = 0;
|
|
1054
1670
|
let flashOn = options.flashAvoidable ?? false;
|
|
1671
|
+
let live = !target || typeof target.addEventListener !== "function";
|
|
1672
|
+
if (!live) {
|
|
1673
|
+
target.addEventListener("message", (event) => {
|
|
1674
|
+
const data = event.data;
|
|
1675
|
+
if ((event.source === target || !event.source) && data && data.__rerenderLensReady === true) live = true;
|
|
1676
|
+
});
|
|
1677
|
+
}
|
|
1055
1678
|
const post = (type, payload) => {
|
|
1056
|
-
if (!target) return;
|
|
1679
|
+
if (!target || type === "report" && !live) return;
|
|
1057
1680
|
const msg = { [DEVTOOLS_MARKER]: true, version: PROTOCOL_VERSION, type, payload };
|
|
1058
1681
|
target.postMessage(msg, "*");
|
|
1059
1682
|
};
|
|
@@ -1069,10 +1692,13 @@ function createDevtoolsNotifier(options = {}) {
|
|
|
1069
1692
|
source,
|
|
1070
1693
|
injected: typeof window !== "undefined" && typeof window.__RERENDER_LENS_INJECTED__ === "string",
|
|
1071
1694
|
scheduled: getState().scheduled,
|
|
1072
|
-
commits: getState().commits
|
|
1695
|
+
commits: getState().commits,
|
|
1696
|
+
overhead: { totalMs: getState().overheadMs, maxCommitMs: getState().maxCommitMs },
|
|
1697
|
+
truncated: getState().truncated
|
|
1073
1698
|
});
|
|
1074
1699
|
const bridge = {
|
|
1075
1700
|
replay: () => {
|
|
1701
|
+
live = true;
|
|
1076
1702
|
post("hello", info());
|
|
1077
1703
|
for (const e of buffer) post("report", e.payload);
|
|
1078
1704
|
},
|
|
@@ -1134,12 +1760,126 @@ function createDevtoolsNotifier(options = {}) {
|
|
|
1134
1760
|
}
|
|
1135
1761
|
};
|
|
1136
1762
|
if (typeof window !== "undefined") window.__RERENDER_LENS_DEVTOOLS__ = bridge;
|
|
1137
|
-
|
|
1763
|
+
const runCommand = (data) => {
|
|
1764
|
+
const reply = { __rerenderLensReply: true, id: data.id };
|
|
1765
|
+
try {
|
|
1766
|
+
switch (data.cmd) {
|
|
1767
|
+
case "info":
|
|
1768
|
+
reply.result = info();
|
|
1769
|
+
break;
|
|
1770
|
+
case "pull":
|
|
1771
|
+
reply.result = bridge.pull(typeof data.arg === "number" ? data.arg : 0);
|
|
1772
|
+
break;
|
|
1773
|
+
case "replay":
|
|
1774
|
+
bridge.replay();
|
|
1775
|
+
reply.result = true;
|
|
1776
|
+
break;
|
|
1777
|
+
case "clear":
|
|
1778
|
+
bridge.clear();
|
|
1779
|
+
reply.result = true;
|
|
1780
|
+
break;
|
|
1781
|
+
case "configure":
|
|
1782
|
+
reply.result = bridge.configure(data.arg ?? {});
|
|
1783
|
+
break;
|
|
1784
|
+
case "highlight":
|
|
1785
|
+
reply.result = bridge.highlight(typeof data.arg === "number" ? data.arg : null);
|
|
1786
|
+
break;
|
|
1787
|
+
case "flash":
|
|
1788
|
+
bridge.flashAvoidable(!!data.arg);
|
|
1789
|
+
reply.result = true;
|
|
1790
|
+
break;
|
|
1791
|
+
default:
|
|
1792
|
+
reply.error = `unknown command ${String(data.cmd)}`;
|
|
1793
|
+
}
|
|
1794
|
+
} catch (e) {
|
|
1795
|
+
reply.error = String(e.message || e);
|
|
1796
|
+
}
|
|
1797
|
+
return reply;
|
|
1798
|
+
};
|
|
1799
|
+
const isCommand = (data) => !!data && typeof data === "object" && data.__rerenderLensCmd === true && typeof data.id === "string";
|
|
1800
|
+
const envelope = (type, payload) => ({ [DEVTOOLS_MARKER]: true, version: PROTOCOL_VERSION, type, payload });
|
|
1801
|
+
const sinks = [];
|
|
1802
|
+
if (options.channel && typeof BroadcastChannel === "function") {
|
|
1803
|
+
const name = typeof options.channel === "string" ? options.channel : DEFAULT_CHANNEL;
|
|
1804
|
+
try {
|
|
1805
|
+
const channel = new BroadcastChannel(name);
|
|
1806
|
+
channel.onmessage = (event) => {
|
|
1807
|
+
if (isCommand(event.data)) channel.postMessage(runCommand(event.data));
|
|
1808
|
+
};
|
|
1809
|
+
sinks.push((message) => {
|
|
1810
|
+
try {
|
|
1811
|
+
channel.postMessage(message);
|
|
1812
|
+
} catch {
|
|
1813
|
+
}
|
|
1814
|
+
});
|
|
1815
|
+
} catch {
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
const relayUrl = options.relay ?? (typeof window !== "undefined" ? window.__RERENDER_LENS_RELAY__ : void 0);
|
|
1819
|
+
const ES = options.eventSource ?? (typeof EventSource === "function" ? EventSource : null);
|
|
1820
|
+
if (relayUrl && ES && typeof fetch === "function") {
|
|
1821
|
+
const base = relayUrl.replace(/\/$/, "");
|
|
1822
|
+
let queue = [];
|
|
1823
|
+
let scheduled = false;
|
|
1824
|
+
const flush = () => {
|
|
1825
|
+
scheduled = false;
|
|
1826
|
+
const batch = queue;
|
|
1827
|
+
queue = [];
|
|
1828
|
+
fetch(`${base}/message`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(batch), keepalive: true }).catch(() => {
|
|
1829
|
+
});
|
|
1830
|
+
};
|
|
1831
|
+
const relaySend = (message) => {
|
|
1832
|
+
queue.push(message);
|
|
1833
|
+
if (!scheduled) {
|
|
1834
|
+
scheduled = true;
|
|
1835
|
+
setTimeout(flush, 0);
|
|
1836
|
+
}
|
|
1837
|
+
};
|
|
1838
|
+
try {
|
|
1839
|
+
const stream = new ES(`${base}/events?role=app`);
|
|
1840
|
+
stream.onmessage = (event) => {
|
|
1841
|
+
let parsed;
|
|
1842
|
+
try {
|
|
1843
|
+
parsed = JSON.parse(event.data);
|
|
1844
|
+
} catch {
|
|
1845
|
+
return;
|
|
1846
|
+
}
|
|
1847
|
+
for (const m of Array.isArray(parsed) ? parsed : [parsed]) if (isCommand(m)) relaySend(runCommand(m));
|
|
1848
|
+
};
|
|
1849
|
+
stream.onerror = () => {
|
|
1850
|
+
};
|
|
1851
|
+
sinks.push(relaySend);
|
|
1852
|
+
} catch {
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
const postAll = (type, payload) => {
|
|
1856
|
+
post(type, payload);
|
|
1857
|
+
if (sinks.length) {
|
|
1858
|
+
const message = envelope(type, payload);
|
|
1859
|
+
for (const sink of sinks) sink(message);
|
|
1860
|
+
}
|
|
1861
|
+
};
|
|
1862
|
+
if (sinks.length) {
|
|
1863
|
+
const replay = bridge.replay;
|
|
1864
|
+
const clear = bridge.clear;
|
|
1865
|
+
bridge.replay = () => {
|
|
1866
|
+
for (const sink of sinks) {
|
|
1867
|
+
sink(envelope("hello", info()));
|
|
1868
|
+
for (const e of buffer) sink(envelope("report", e.payload));
|
|
1869
|
+
}
|
|
1870
|
+
replay();
|
|
1871
|
+
};
|
|
1872
|
+
bridge.clear = () => {
|
|
1873
|
+
clear();
|
|
1874
|
+
for (const sink of sinks) sink(envelope("clear"));
|
|
1875
|
+
};
|
|
1876
|
+
}
|
|
1877
|
+
postAll("hello", info());
|
|
1138
1878
|
return (report) => {
|
|
1139
1879
|
const payload = serialize(report, maxDepth);
|
|
1140
1880
|
buffer.push({ seq: ++seq, instanceId: report.instanceId, payload });
|
|
1141
1881
|
if (buffer.length > bufferSize) buffer.splice(0, buffer.length - bufferSize);
|
|
1142
|
-
|
|
1882
|
+
postAll("report", payload);
|
|
1143
1883
|
if (flashOn && report.avoidable && report.instanceId) {
|
|
1144
1884
|
const fiber = fiberById(report.instanceId);
|
|
1145
1885
|
if (fiber) flash({ nodes: hostNodesOf(fiber), label: report.component });
|
|
@@ -1147,6 +1887,6 @@ function createDevtoolsNotifier(options = {}) {
|
|
|
1147
1887
|
};
|
|
1148
1888
|
}
|
|
1149
1889
|
|
|
1150
|
-
export { DEVTOOLS_MARKER, MARKER, PROTOCOL_VERSION, VERSION, buildReport, classify, combineNotifiers, configure, createCollector, createDevtoolsNotifier, deepEqual, deserializeOptions, diffRecords, disable, ensureDevtoolsHook, getDisplayName, getRenderers, init, isEnabled, isProductionReact, printReport, serialize, serializeOptions, shouldTrack, summarize, track, useWhyRerender };
|
|
1890
|
+
export { DEFAULT_CHANNEL, DEVTOOLS_MARKER, MARKER, PROTOCOL_VERSION, VERSION, assertWithinBudget, avoidableCounts, buildReport, checkBudget, classify, combineNotifiers, compareSummaries, configure, createCollector, createDevtoolsNotifier, customHooksFromStack, deepEqual, deserializeOptions, diffRecords, disable, ensureDevtoolsHook, fixKey, fixesFor, formatComparison, formatFixes, getDisplayName, getRenderers, hookLabel, init, isEnabled, isProductionReact, parseExport, printReport, rankFixes, resolveHookNames, serialize, serializeOptions, shouldTrack, storeAdvice, summarize, summarizeReports, toBudget, track, useWhyRerender };
|
|
1151
1891
|
//# sourceMappingURL=index.js.map
|
|
1152
1892
|
//# sourceMappingURL=index.js.map
|