rerender-lens 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -1
- package/README.md +14 -5
- package/dist/{devtools-DmhWiWcN.d.cts → devtools-BkCct3cJ.d.cts} +10 -2
- package/dist/{devtools-BHGUUo-p.d.ts → devtools-CZebzpF6.d.ts} +10 -2
- package/dist/index.cjs +199 -71
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +199 -71
- package/dist/index.js.map +1 -1
- package/dist/playwright.cjs +5 -2
- package/dist/playwright.cjs.map +1 -1
- package/dist/playwright.d.cts +5 -3
- package/dist/playwright.d.ts +5 -3
- package/dist/playwright.js +5 -3
- package/dist/playwright.js.map +1 -1
- package/dist/rerender-lens.iife.js +768 -307
- package/dist/setup.cjs +199 -71
- package/dist/setup.cjs.map +1 -1
- package/dist/setup.js +199 -71
- package/dist/setup.js.map +1 -1
- package/dist/vitest-setup.cjs +117 -48
- package/dist/vitest-setup.cjs.map +1 -1
- package/dist/vitest-setup.js +117 -48
- package/dist/vitest-setup.js.map +1 -1
- package/dist/vitest.cjs +117 -48
- package/dist/vitest.cjs.map +1 -1
- package/dist/vitest.js +117 -48
- package/dist/vitest.js.map +1 -1
- package/package.json +2 -2
- package/panel/panel.css +0 -34
- package/panel/panel.js +400 -438
package/CHANGELOG.md
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 0.4.0
|
|
4
4
|
|
|
5
|
+
Highlights: the panel for any app with `npx rerender-lens panel`, Vitest and Playwright
|
|
6
|
+
integrations, bounded work per commit (large apps no longer freeze), a lighter panel, and the
|
|
7
|
+
injectable library split into three readable scripts. Details, newest first:
|
|
8
|
+
|
|
9
|
+
- **Scale test.** `examples/vite-react/scale.html?rows=N` renders N memoized cells sharing a
|
|
10
|
+
2,000-item context and a 4 MB typed-array prop; its readout shows the library's overhead and
|
|
11
|
+
the reports skipped by the cap. The e2e suite runs it with 3,000 rows. The panel shows the
|
|
12
|
+
skipped count in the status tooltip and as a banner.
|
|
5
13
|
- **`npx rerender-lens panel`.** A dependency-free relay (`rerender-lens/relay`) that serves the
|
|
6
14
|
panel and forwards messages between apps and panels over server-sent events and POST, so the
|
|
7
15
|
panel works for any app on any origin without the extension or Vite: Next.js, Webpack, a remote
|
|
@@ -18,6 +26,38 @@
|
|
|
18
26
|
`clearReports`, `expectWithinBudget`.
|
|
19
27
|
- A second copy of the library (another bundle on the same page) no longer wraps the hook
|
|
20
28
|
again, which doubled every report.
|
|
29
|
+
- **Bounded work per commit** (large apps froze the page). Deep equality is memoized across the
|
|
30
|
+
components of one commit and gives up on values too large to walk (reported as `different`);
|
|
31
|
+
large Sets compare by identity. Serialization caps every array/object/Map/Set at 100 entries
|
|
32
|
+
(`…+N more`), every report at 20k nodes, treats typed arrays, `ArrayBuffer`, DOM nodes and
|
|
33
|
+
promises as leaves, and defaults to depth 4. A commit reports at most 200 tracked components
|
|
34
|
+
and stops after 25 ms; the rest is counted in `info().truncated` with a one-time warning.
|
|
35
|
+
Source locations are cached per element, the instance registry prunes on growth only, and
|
|
36
|
+
highlight/flash measure all nodes before drawing (at most 100 boxes).
|
|
37
|
+
- Reports are posted on `window` only once something says it listens (the extension's content
|
|
38
|
+
script, or `replay()`), so a page that runs the library without an open panel pays no
|
|
39
|
+
structured clone per report and wakes none of its own `message` listeners. `hello` and
|
|
40
|
+
`clear` still post; the BroadcastChannel and relay paths are unchanged.
|
|
41
|
+
- The injected library (extension) now runs with `includeState: false`; Settings can turn the
|
|
42
|
+
hook/context/state snapshots on per origin. Effect-loop detection no longer flags commits
|
|
43
|
+
caused by discrete input (typing, dragging). The toolbar badge repaints at most every 100 ms.
|
|
44
|
+
- **The panel keeps up.** Summary totals are incremental; the Offenders/Commits/Fixes lists and
|
|
45
|
+
the "best fix" stat update at most every 250 / 500 ms once 200 reports are buffered (the
|
|
46
|
+
tree and the stream stay live); commit analysis is memoized per commit and root causes are
|
|
47
|
+
found by index; each commit keeps at most 500 reports; buffer eviction is one splice per
|
|
48
|
+
frame. The content script sends one batched port message per task instead of one per
|
|
49
|
+
report. The DevTools panel retries the connect handshake with backoff (up to ~90 s) when a
|
|
50
|
+
page loads the library late, and the polling fallback clears the panel once per reset
|
|
51
|
+
instead of on every poll when the page overruns its buffer.
|
|
52
|
+
- The injectable library ships as three vendor scripts (`rerender-lens.core.js`,
|
|
53
|
+
`rerender-lens.engine.js`, `rerender-lens.js`) built by `scripts/build-vendor.mjs`; the
|
|
54
|
+
Playwright helper still gets one concatenated bundle.
|
|
55
|
+
- Removed: per-component notes and mute, share links (`panel.html?report=…`), the Offenders
|
|
56
|
+
column chooser (*Places*, *Last seen*), and the windowed Offenders/Fixes variant above 200 rows.
|
|
57
|
+
Offenders and Fixes always render plain rows; the tree and the stream stay virtualized. The
|
|
58
|
+
panel also drops its protocol-1 fallbacks: protocol 2 is the only one it speaks.
|
|
59
|
+
- Fix: after a navigation the panel's polling fallback stops until the new page answers `info`,
|
|
60
|
+
instead of evaluating into pages without the library every 500 ms.
|
|
21
61
|
|
|
22
62
|
## 0.3.0
|
|
23
63
|
|
package/README.md
CHANGED
|
@@ -52,8 +52,9 @@ Then pick one of two modes:
|
|
|
52
52
|
|
|
53
53
|
- **Inject the library** (no app code): tick *Inject the library* in the toolbar popup or in the
|
|
54
54
|
panel's Settings and reload. The extension loads rerender-lens into the page before React,
|
|
55
|
-
tracking every `React.memo` / `PureComponent` by default
|
|
56
|
-
Settings; the
|
|
55
|
+
tracking every `React.memo` / `PureComponent` by default, without the hook/context/state
|
|
56
|
+
snapshots (*Include state* in Settings turns them on; they are the costliest part on big
|
|
57
|
+
apps). Change what is tracked from Settings; the choice is saved per origin.
|
|
57
58
|
- **The page runs the library**: install the package and pass the DevTools notifier (see below).
|
|
58
59
|
This is the way to go when you also want console output or want to commit the setup.
|
|
59
60
|
|
|
@@ -339,8 +340,12 @@ init({ trackAllMemoized: true, silent: true, notifier: createDevtoolsNotifier()
|
|
|
339
340
|
```
|
|
340
341
|
|
|
341
342
|
Each report is serialized (functions become `ƒ name`, elements `<Type>`, cycles cut) and posted
|
|
342
|
-
on `window` as `{ __rerenderLens: true, version: 2, type: 'report', payload }`.
|
|
343
|
-
|
|
343
|
+
on `window` as `{ __rerenderLens: true, version: 2, type: 'report', payload }`. Reports go on
|
|
344
|
+
`window` only after a listener announced itself (`{ __rerenderLensReady: true }`, which the
|
|
345
|
+
extension's content script posts) or after `replay()`, so a page nobody inspects pays no
|
|
346
|
+
structured clone per report. Values are bounded: 100 entries per array/object/Map/Set, depth 4
|
|
347
|
+
by default (`maxDepth`), 20k nodes per report. The last 300 reports are buffered. The bridge
|
|
348
|
+
at `window.__RERENDER_LENS_DEVTOOLS__` exposes:
|
|
344
349
|
|
|
345
350
|
| Method | |
|
|
346
351
|
| --- | --- |
|
|
@@ -392,7 +397,7 @@ deepEqual(a, b), diffRecords(prev, next), classify(prev, next)
|
|
|
392
397
|
| `trackAllComponents` | `false` | track everything (noisy) |
|
|
393
398
|
| `include` / `exclude` | | display-name matchers |
|
|
394
399
|
| `trackHooks` | `true` | diff hook state and contexts |
|
|
395
|
-
| `includeState` | `true` | put every hook, context and class state value on each report |
|
|
400
|
+
| `includeState` | `true` (`false` when injected by the extension) | put every hook, context and class state value on each report; the costliest option on large trees |
|
|
396
401
|
| `resolveHookNames` | `false` | label hooks with the custom hooks that own them (`useCart › useState#0`); re-runs each component type once |
|
|
397
402
|
| `logAll` | `false` | print non-avoidable reports too |
|
|
398
403
|
| `silent` | `false` | never print; notifier still runs |
|
|
@@ -423,6 +428,10 @@ deepEqual(a, b), diffRecords(prev, next), classify(prev, next)
|
|
|
423
428
|
|
|
424
429
|
## Example app
|
|
425
430
|
|
|
431
|
+
`examples/vite-react/scale.html?rows=3000` is the scale test: thousands of memoized cells that all
|
|
432
|
+
re-render avoidably, sharing one large context value and a typed-array prop. Its readout shows
|
|
433
|
+
the library's own cost per commit and how many reports the per-commit cap skipped.
|
|
434
|
+
|
|
426
435
|
`examples/vite-react` has three deliberate bugs. From the repo root:
|
|
427
436
|
|
|
428
437
|
```sh
|
|
@@ -115,6 +115,8 @@ interface HelloPayload {
|
|
|
115
115
|
totalMs: number;
|
|
116
116
|
maxCommitMs: number;
|
|
117
117
|
};
|
|
118
|
+
/** Reports skipped because a commit exceeded the per-commit cap or time budget. */
|
|
119
|
+
truncated: number;
|
|
118
120
|
}
|
|
119
121
|
/** Command sent by a panel over the BroadcastChannel; answered with a `ChannelReply` of the same id. */
|
|
120
122
|
interface ChannelCommand {
|
|
@@ -230,8 +232,14 @@ declare global {
|
|
|
230
232
|
__RERENDER_LENS_DEVTOOLS__?: DevtoolsBridge;
|
|
231
233
|
}
|
|
232
234
|
}
|
|
233
|
-
/**
|
|
234
|
-
|
|
235
|
+
/**
|
|
236
|
+
* Convert a report into a structured-clone-safe value (functions, elements, cycles removed).
|
|
237
|
+
* Depth, breadth and total size are bounded so a giant prop (a 100k-row list, an image buffer,
|
|
238
|
+
* a scene graph) costs a bounded amount of time and memory per report.
|
|
239
|
+
*/
|
|
240
|
+
declare function serialize(value: unknown, maxDepth?: number, seen?: WeakSet<object>, depth?: number, budget?: {
|
|
241
|
+
nodes: number;
|
|
242
|
+
}): unknown;
|
|
235
243
|
/** The current options in a JSON-safe form (function matchers and custom consoles are dropped). */
|
|
236
244
|
declare function serializeOptions(o: Options): SerializableOptions;
|
|
237
245
|
declare function deserializeOptions(o: SerializableOptions): Options;
|
|
@@ -115,6 +115,8 @@ interface HelloPayload {
|
|
|
115
115
|
totalMs: number;
|
|
116
116
|
maxCommitMs: number;
|
|
117
117
|
};
|
|
118
|
+
/** Reports skipped because a commit exceeded the per-commit cap or time budget. */
|
|
119
|
+
truncated: number;
|
|
118
120
|
}
|
|
119
121
|
/** Command sent by a panel over the BroadcastChannel; answered with a `ChannelReply` of the same id. */
|
|
120
122
|
interface ChannelCommand {
|
|
@@ -230,8 +232,14 @@ declare global {
|
|
|
230
232
|
__RERENDER_LENS_DEVTOOLS__?: DevtoolsBridge;
|
|
231
233
|
}
|
|
232
234
|
}
|
|
233
|
-
/**
|
|
234
|
-
|
|
235
|
+
/**
|
|
236
|
+
* Convert a report into a structured-clone-safe value (functions, elements, cycles removed).
|
|
237
|
+
* Depth, breadth and total size are bounded so a giant prop (a 100k-row list, an image buffer,
|
|
238
|
+
* a scene graph) costs a bounded amount of time and memory per report.
|
|
239
|
+
*/
|
|
240
|
+
declare function serialize(value: unknown, maxDepth?: number, seen?: WeakSet<object>, depth?: number, budget?: {
|
|
241
|
+
nodes: number;
|
|
242
|
+
}): unknown;
|
|
235
243
|
/** The current options in a JSON-safe form (function matchers and custom consoles are dropped). */
|
|
236
244
|
declare function serializeOptions(o: Options): SerializableOptions;
|
|
237
245
|
declare function deserializeOptions(o: SerializableOptions): Options;
|
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
|
}
|
|
@@ -330,7 +374,7 @@ function getState() {
|
|
|
330
374
|
const g = globalThis;
|
|
331
375
|
let s = g[KEY];
|
|
332
376
|
if (!s) {
|
|
333
|
-
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 };
|
|
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 };
|
|
334
378
|
g[KEY] = s;
|
|
335
379
|
}
|
|
336
380
|
return s;
|
|
@@ -360,7 +404,7 @@ function dispatch(report, override) {
|
|
|
360
404
|
try {
|
|
361
405
|
options.notifier(report);
|
|
362
406
|
} catch (err) {
|
|
363
|
-
(
|
|
407
|
+
warnOnce("notifier", `notifier threw: ${String(err)}`);
|
|
364
408
|
}
|
|
365
409
|
}
|
|
366
410
|
}
|
|
@@ -703,11 +747,14 @@ var counts = /* @__PURE__ */ new WeakMap();
|
|
|
703
747
|
var ids = /* @__PURE__ */ new WeakMap();
|
|
704
748
|
var fibersById = /* @__PURE__ */ new Map();
|
|
705
749
|
var hasWeakRef = typeof WeakRef === "function";
|
|
750
|
+
var pruneAt = 5e3;
|
|
706
751
|
function remember2(id, fiber) {
|
|
707
752
|
if (!hasWeakRef) return;
|
|
753
|
+
if (fibersById.get(id)?.deref() === fiber) return;
|
|
708
754
|
fibersById.set(id, new WeakRef(fiber));
|
|
709
|
-
if (fibersById.size >
|
|
755
|
+
if (fibersById.size > pruneAt) {
|
|
710
756
|
for (const [k, ref] of fibersById) if (!ref.deref()) fibersById.delete(k);
|
|
757
|
+
pruneAt = Math.max(5e3, fibersById.size * 2);
|
|
711
758
|
}
|
|
712
759
|
}
|
|
713
760
|
function instanceIdOf(fiber) {
|
|
@@ -762,7 +809,7 @@ function nearestComponent(fiber) {
|
|
|
762
809
|
}
|
|
763
810
|
var INTERNAL_FRAME = /react-dom|react_jsx|jsx-(dev-)?runtime|\/react\/|node_modules\/react|react-stack-bottom-frame|react_stack_bottom_frame|scheduler/;
|
|
764
811
|
function parseStackLocation(stack) {
|
|
765
|
-
for (const line of stack.split("\n")) {
|
|
812
|
+
for (const line of stack.split("\n", 24)) {
|
|
766
813
|
const m = /(?:at\s+(?:.*?\s+)?\(?|@)?((?:https?|file|webpack|vite|blob):[^\s()]+?):(\d+):(\d+)\)?\s*$/.exec(line.trim());
|
|
767
814
|
if (!m || !m[1] || INTERNAL_FRAME.test(m[1])) continue;
|
|
768
815
|
return { fileName: m[1], lineNumber: Number(m[2]), columnNumber: Number(m[3]) };
|
|
@@ -778,9 +825,16 @@ function sourceOf(fiber) {
|
|
|
778
825
|
return out;
|
|
779
826
|
}
|
|
780
827
|
const st = fiber._debugStack;
|
|
781
|
-
|
|
782
|
-
|
|
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;
|
|
783
836
|
}
|
|
837
|
+
var sourceCache = /* @__PURE__ */ new WeakMap();
|
|
784
838
|
function bumpCount(fiber) {
|
|
785
839
|
const prev = counts.get(fiber) ?? (fiber.alternate ? counts.get(fiber.alternate) : void 0) ?? 0;
|
|
786
840
|
const n = prev + 1;
|
|
@@ -968,6 +1022,8 @@ function updatersOf(root) {
|
|
|
968
1022
|
}
|
|
969
1023
|
var lastCommit = null;
|
|
970
1024
|
var EFFECT_LOOP_WINDOW_MS = 50;
|
|
1025
|
+
var MAX_REPORTS_PER_COMMIT = 200;
|
|
1026
|
+
var COMMIT_TIME_BUDGET_MS = 25;
|
|
971
1027
|
function onCommit(root, commitPriority) {
|
|
972
1028
|
const s = getState();
|
|
973
1029
|
if (!s.enabled) return;
|
|
@@ -998,7 +1054,8 @@ function onCommit(root, commitPriority) {
|
|
|
998
1054
|
const updaters = updatersOf(root);
|
|
999
1055
|
let commitCause;
|
|
1000
1056
|
let afterCommit;
|
|
1001
|
-
|
|
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))) {
|
|
1002
1059
|
commitCause = "effect-after-commit";
|
|
1003
1060
|
afterCommit = lastCommit.id;
|
|
1004
1061
|
} else if (suspenseResolved) commitCause = "suspense-resolved";
|
|
@@ -1010,47 +1067,59 @@ function onCommit(root, commitPriority) {
|
|
|
1010
1067
|
lastCommit = { id: commitId, at, rendered: renderedNames };
|
|
1011
1068
|
const dispatcherRef = o.resolveHookNames ? getDispatcherRef() : null;
|
|
1012
1069
|
const parentCache = /* @__PURE__ */ new Map();
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
for (const c of a.hookChanges) if (c.hook !== "useContext" && names.has(c.index)) c.custom = names.get(c.index);
|
|
1022
|
-
if (hookState) hookState = hookState.map((h) => names.has(h.index) ? { ...h, custom: names.get(h.index) } : h);
|
|
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;
|
|
1023
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);
|
|
1024
1120
|
}
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
instanceId: instanceId(fiber),
|
|
1028
|
-
renderCount: bumpCount(fiber),
|
|
1029
|
-
prevProps: alt.memoizedProps ?? {},
|
|
1030
|
-
nextProps: fiber.memoizedProps ?? {},
|
|
1031
|
-
propChanges: a.propChanges,
|
|
1032
|
-
stateChanges: a.stateChanges,
|
|
1033
|
-
hookChanges: a.hookChanges,
|
|
1034
|
-
...o.includeState !== false ? {
|
|
1035
|
-
hookState,
|
|
1036
|
-
contexts: trackHooks ? snapshotContexts(fiber) : void 0,
|
|
1037
|
-
state: fiber.tag === ClassComponent && fiber.memoizedState && typeof fiber.memoizedState === "object" ? fiber.memoizedState : void 0
|
|
1038
|
-
} : {},
|
|
1039
|
-
updaters,
|
|
1040
|
-
commitCause,
|
|
1041
|
-
afterCommit,
|
|
1042
|
-
key: fiber.key === null || fiber.key === void 0 ? null : String(fiber.key),
|
|
1043
|
-
parent: a.trigger === "parent" ? nearestRenderedAncestor(fiber, parentCache, trackHooks) : null,
|
|
1044
|
-
owner: ownerName(fiber),
|
|
1045
|
-
path: componentPath(fiber),
|
|
1046
|
-
memoized: isMemoizedFiber(fiber),
|
|
1047
|
-
selfDuration: durations ? durations.self : void 0,
|
|
1048
|
-
treeDuration: durations ? durations.tree : void 0,
|
|
1049
|
-
commitId,
|
|
1050
|
-
commitPriority,
|
|
1051
|
-
source: sourceOf(fiber)
|
|
1052
|
-
});
|
|
1053
|
-
dispatch(report);
|
|
1121
|
+
} finally {
|
|
1122
|
+
endDiffScope();
|
|
1054
1123
|
}
|
|
1055
1124
|
}
|
|
1056
1125
|
|
|
@@ -1444,13 +1513,24 @@ function highlight(target) {
|
|
|
1444
1513
|
for (const s of l.sticky) s.remove();
|
|
1445
1514
|
l.sticky = [];
|
|
1446
1515
|
if (!target) return;
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
if (rect.width === 0 && rect.height === 0) continue;
|
|
1516
|
+
const frag = document.createDocumentFragment();
|
|
1517
|
+
for (const rect of measure(target.nodes)) {
|
|
1450
1518
|
const b = box(rect, "#1a73e8", "rgba(26, 115, 232, 0.12)", target.label);
|
|
1451
|
-
|
|
1519
|
+
frag.appendChild(b);
|
|
1452
1520
|
l.sticky.push(b);
|
|
1453
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;
|
|
1454
1534
|
}
|
|
1455
1535
|
function clearHighlight() {
|
|
1456
1536
|
highlight(null);
|
|
@@ -1458,27 +1538,34 @@ function clearHighlight() {
|
|
|
1458
1538
|
function flash(target, duration = 500) {
|
|
1459
1539
|
const l = ensureLayer();
|
|
1460
1540
|
if (!l) return;
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1541
|
+
const frag = document.createDocumentFragment();
|
|
1542
|
+
const boxes = [];
|
|
1543
|
+
for (const rect of measure(target.nodes)) {
|
|
1464
1544
|
const b = box(rect, "#d93025", "rgba(217, 48, 37, 0.15)");
|
|
1465
1545
|
b.style.transition = `opacity ${duration}ms ease-out`;
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
b.style.opacity = "0";
|
|
1469
|
-
});
|
|
1470
|
-
setTimeout(() => b.remove(), duration + 50);
|
|
1546
|
+
frag.appendChild(b);
|
|
1547
|
+
boxes.push(b);
|
|
1471
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);
|
|
1472
1557
|
}
|
|
1473
1558
|
|
|
1474
1559
|
// src/version.ts
|
|
1475
|
-
var VERSION = "0.
|
|
1560
|
+
var VERSION = "0.4.0" ;
|
|
1476
1561
|
|
|
1477
1562
|
// src/devtools.ts
|
|
1478
1563
|
var DEVTOOLS_MARKER = "__rerenderLens";
|
|
1479
1564
|
var PROTOCOL_VERSION = 2;
|
|
1480
1565
|
var DEFAULT_CHANNEL = "rerender-lens";
|
|
1481
|
-
|
|
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 }) {
|
|
1482
1569
|
if (value === null || value === void 0) return value;
|
|
1483
1570
|
const t = typeof value;
|
|
1484
1571
|
if (t === "string" || t === "boolean") return value;
|
|
@@ -1489,24 +1576,56 @@ function serialize(value, maxDepth = 6, seen = /* @__PURE__ */ new WeakSet(), de
|
|
|
1489
1576
|
const obj = value;
|
|
1490
1577
|
if (seen.has(obj)) return "[Circular]";
|
|
1491
1578
|
if (depth >= maxDepth) return "[\u2026]";
|
|
1579
|
+
if (--budget.nodes < 0) return "[\u2026]";
|
|
1580
|
+
const next = (v) => serialize(v, maxDepth, seen, depth + 1, budget);
|
|
1492
1581
|
seen.add(obj);
|
|
1493
1582
|
try {
|
|
1494
1583
|
if (isReactElement(obj)) {
|
|
1495
1584
|
const out2 = { $type: "element", name: getDisplayName(obj.type) };
|
|
1496
1585
|
if (obj.key !== null && obj.key !== void 0) out2.key = String(obj.key);
|
|
1497
|
-
out2.props =
|
|
1586
|
+
out2.props = next(obj.props);
|
|
1498
1587
|
return out2;
|
|
1499
1588
|
}
|
|
1500
1589
|
if (obj instanceof Date) return { $type: "Date", value: obj.toISOString() };
|
|
1501
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]";
|
|
1502
1598
|
if (obj instanceof Map) {
|
|
1503
|
-
|
|
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;
|
|
1504
1624
|
}
|
|
1505
|
-
if (obj instanceof Set) return { $type: "Set", values: [...obj].map((v) => serialize(v, maxDepth, seen, depth + 1)) };
|
|
1506
|
-
if (Array.isArray(obj)) return obj.map((v) => serialize(v, maxDepth, seen, depth + 1));
|
|
1507
|
-
if (typeof Element !== "undefined" && obj instanceof Element) return `<${obj.tagName.toLowerCase()}>`;
|
|
1508
1625
|
const out = {};
|
|
1509
|
-
|
|
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`;
|
|
1510
1629
|
const proto = Object.getPrototypeOf(obj);
|
|
1511
1630
|
if (proto && proto !== Object.prototype && proto.constructor?.name) out.$type = proto.constructor.name;
|
|
1512
1631
|
return out;
|
|
@@ -1546,13 +1665,20 @@ function deserializeOptions(o) {
|
|
|
1546
1665
|
}
|
|
1547
1666
|
function createDevtoolsNotifier(options = {}) {
|
|
1548
1667
|
const bufferSize = options.bufferSize ?? 300;
|
|
1549
|
-
const maxDepth = options.maxDepth ??
|
|
1668
|
+
const maxDepth = options.maxDepth ?? 4;
|
|
1550
1669
|
const target = options.target ?? (typeof window !== "undefined" ? window : void 0);
|
|
1551
1670
|
const buffer = [];
|
|
1552
1671
|
let seq = 0;
|
|
1553
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
|
+
}
|
|
1554
1680
|
const post = (type, payload) => {
|
|
1555
|
-
if (!target) return;
|
|
1681
|
+
if (!target || type === "report" && !live) return;
|
|
1556
1682
|
const msg = { [DEVTOOLS_MARKER]: true, version: PROTOCOL_VERSION, type, payload };
|
|
1557
1683
|
target.postMessage(msg, "*");
|
|
1558
1684
|
};
|
|
@@ -1569,10 +1695,12 @@ function createDevtoolsNotifier(options = {}) {
|
|
|
1569
1695
|
injected: typeof window !== "undefined" && typeof window.__RERENDER_LENS_INJECTED__ === "string",
|
|
1570
1696
|
scheduled: getState().scheduled,
|
|
1571
1697
|
commits: getState().commits,
|
|
1572
|
-
overhead: { totalMs: getState().overheadMs, maxCommitMs: getState().maxCommitMs }
|
|
1698
|
+
overhead: { totalMs: getState().overheadMs, maxCommitMs: getState().maxCommitMs },
|
|
1699
|
+
truncated: getState().truncated
|
|
1573
1700
|
});
|
|
1574
1701
|
const bridge = {
|
|
1575
1702
|
replay: () => {
|
|
1703
|
+
live = true;
|
|
1576
1704
|
post("hello", info());
|
|
1577
1705
|
for (const e of buffer) post("report", e.payload);
|
|
1578
1706
|
},
|