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