react-debug-updates 0.1.6 → 0.1.9
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/README.md +37 -28
- package/dist/fiber.d.ts +2 -2
- package/dist/format.d.ts +3 -1
- package/dist/highlights.d.ts +5 -0
- package/dist/index.d.ts +2 -2
- package/dist/monitor.d.ts +14 -0
- package/dist/react-debug-updates.cjs +130 -129
- package/dist/react-debug-updates.cjs.map +1 -1
- package/dist/react-debug-updates.js +130 -129
- package/dist/react-debug-updates.js.map +1 -1
- package/dist/types.d.ts +24 -15
- package/package.json +1 -1
- package/dist/batcher.d.ts +0 -6
- package/dist/logger.d.ts +0 -11
package/README.md
CHANGED
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
# react-debug-updates
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
See exactly which React components re-render, how often, how long they take, and *why* — all without modifying your components.
|
|
4
4
|
|
|
5
5
|
 
|
|
6
6
|
|
|
7
7
|
<img src="demo.gif" alt="demo" width="852" height="476" />
|
|
8
8
|
|
|
9
|
+
## Why?
|
|
10
|
+
|
|
11
|
+
I was working on an Electron app and spent hours trying to get the official React DevTools to work with it. DevTools' Electron integration is fragile, poorly documented, and breaks between versions. I just needed to see which components were re-rendering so I could fix performance issues.
|
|
12
|
+
|
|
13
|
+
So I wrote this — a plug-and-play one-liner that gives you visual highlight overlays and console logging for React re-renders. No browser extension, no Electron hacks, no configuration. Works in any React web environment — browsers, Electron, iframes.
|
|
14
|
+
|
|
9
15
|
## How it works
|
|
10
16
|
|
|
11
|
-
Hooks into `__REACT_DEVTOOLS_GLOBAL_HOOK__` to intercept every React commit.
|
|
17
|
+
Hooks into `__REACT_DEVTOOLS_GLOBAL_HOOK__` to intercept every React commit. Uses the same fiber tree diffing approach as React DevTools to detect which components actually re-rendered. No React DevTools extension required. No wrappers, no HOCs, no code changes — just call `monitor()` and you get:
|
|
12
18
|
|
|
13
|
-
- **Console logging** — grouped, color-coded re-render reports with component tree paths and render durations
|
|
14
19
|
- **Visual overlays** — highlight boxes on re-rendered DOM nodes with a heat-map color scale (blue → red as render count increases)
|
|
20
|
+
- **Console logging** — grouped, color-coded re-render reports with component tree paths and render durations
|
|
15
21
|
- **Cause detection** — pinpoint *which* `useState`, `useReducer`, `useSyncExternalStore`, or `useContext` hook triggered each re-render, with previous→next values
|
|
16
22
|
|
|
17
23
|
## Install
|
|
@@ -26,30 +32,38 @@ pnpm add react-debug-updates
|
|
|
26
32
|
|
|
27
33
|
## Quick start
|
|
28
34
|
|
|
29
|
-
Import and call `
|
|
35
|
+
Import and call `monitor` **before** React renders anything — ideally at the very top of your entry point. This ensures the hook is in place before the first commit.
|
|
30
36
|
|
|
31
37
|
```ts
|
|
32
|
-
import {
|
|
38
|
+
import { monitor } from "react-debug-updates";
|
|
33
39
|
|
|
34
|
-
//
|
|
35
|
-
const
|
|
36
|
-
highlight: true,
|
|
37
|
-
showCauses: true,
|
|
38
|
-
});
|
|
40
|
+
// One-liner — overlays + console logging out of the box
|
|
41
|
+
const updates = monitor();
|
|
39
42
|
|
|
40
43
|
// Later, to clean up:
|
|
41
|
-
|
|
44
|
+
updates?.stop();
|
|
42
45
|
```
|
|
43
46
|
|
|
44
47
|
### Dev-only guard
|
|
45
48
|
|
|
46
49
|
```ts
|
|
47
50
|
if (process.env.NODE_ENV === "development") {
|
|
48
|
-
const {
|
|
49
|
-
|
|
51
|
+
const { monitor } = await import("react-debug-updates");
|
|
52
|
+
monitor();
|
|
50
53
|
}
|
|
51
54
|
```
|
|
52
55
|
|
|
56
|
+
### With options
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
monitor({
|
|
60
|
+
showCauses: true,
|
|
61
|
+
opacity: 0.5,
|
|
62
|
+
showLabels: false,
|
|
63
|
+
silent: true, // overlays only, no console output
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
53
67
|
## Requirements
|
|
54
68
|
|
|
55
69
|
- A **React dev build** (which automatically creates `__REACT_DEVTOOLS_GLOBAL_HOOK__`) — no browser extension needed
|
|
@@ -57,36 +71,31 @@ if (process.env.NODE_ENV === "development") {
|
|
|
57
71
|
|
|
58
72
|
## API
|
|
59
73
|
|
|
60
|
-
### `
|
|
74
|
+
### `monitor(options?): UpdateMonitor | null`
|
|
61
75
|
|
|
62
|
-
Returns
|
|
76
|
+
Returns an `UpdateMonitor` handle, or `null` if the DevTools hook is not available.
|
|
63
77
|
|
|
64
78
|
#### Options
|
|
65
79
|
|
|
66
80
|
| Option | Type | Default | Description |
|
|
67
81
|
| --- | --- | --- | --- |
|
|
68
|
-
| `silent` | `boolean` | `false` | Suppress console output |
|
|
69
82
|
| `mode` | `"self-triggered" \| "all"` | `"self-triggered"` | `"self-triggered"` tracks only components whose own state changed. `"all"` includes children swept by parent updates |
|
|
70
|
-
| `bufferSize` | `number` | `500` | Max entries kept in the ring buffer |
|
|
71
|
-
| `filter` | `(entry: RenderEntry) => boolean` | — | Return `false` to skip an entry |
|
|
72
|
-
| `highlight` | `boolean \| HighlightOptions` | `false` | Enable visual overlay highlights |
|
|
73
83
|
| `showCauses` | `boolean` | `false` | Detect and display why each component re-rendered |
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
| Option | Type | Default | Description |
|
|
78
|
-
| --- | --- | --- | --- |
|
|
79
|
-
| `flushInterval` | `number` | `250` | Milliseconds between overlay flush cycles |
|
|
80
|
-
| `animationDuration` | `number` | `1200` | Overlay fade-out animation duration (ms) |
|
|
84
|
+
| `silent` | `boolean` | `false` | Suppress console output |
|
|
85
|
+
| `overlay` | `boolean` | `true` | Enable visual highlight overlays |
|
|
81
86
|
| `showLabels` | `boolean` | `true` | Show text labels (name, count, duration, cause) above overlays |
|
|
82
87
|
| `opacity` | `number` | `0.3` | Peak opacity of overlay highlights (0–1) |
|
|
88
|
+
| `flushInterval` | `number` | `250` | Milliseconds between overlay flush cycles |
|
|
89
|
+
| `animationDuration` | `number` | `1200` | Overlay fade-out animation duration (ms) |
|
|
90
|
+
| `bufferSize` | `number` | `500` | Max entries kept in the ring buffer |
|
|
91
|
+
| `filter` | `(entry: RenderEntry) => boolean` | — | Return `false` to skip an entry |
|
|
83
92
|
|
|
84
|
-
### `
|
|
93
|
+
### `UpdateMonitor`
|
|
85
94
|
|
|
86
95
|
| Property | Type | Description |
|
|
87
96
|
| --- | --- | --- |
|
|
88
97
|
| `entries` | `RenderEntry[]` | Ring buffer of recorded re-render entries |
|
|
89
|
-
| `
|
|
98
|
+
| `stop` | `() => void` | Unhook from React and remove all overlays |
|
|
90
99
|
|
|
91
100
|
### `RenderEntry`
|
|
92
101
|
|
package/dist/fiber.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Fiber,
|
|
1
|
+
import type { Fiber, DetectedRender } from "./types.js";
|
|
2
2
|
export declare const FiberTag: {
|
|
3
3
|
readonly FunctionComponent: 0;
|
|
4
4
|
readonly ClassComponent: 1;
|
|
@@ -10,4 +10,4 @@ export declare const FiberTag: {
|
|
|
10
10
|
export declare function getComponentName(fiber: Fiber): string | null;
|
|
11
11
|
export declare function findNearestDOMNode(fiber: Fiber): HTMLElement | null;
|
|
12
12
|
export declare function getFiberPath(fiber: Fiber, maxDepth?: number): string;
|
|
13
|
-
export declare function
|
|
13
|
+
export declare function detectRenders(root: Fiber, mode: "self-triggered" | "all"): DetectedRender[];
|
package/dist/format.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import type { UpdateCause } from "./types.js";
|
|
1
|
+
import type { HighlightEntry, UpdateCause } from "./types.js";
|
|
2
2
|
export declare function formatValue(value: unknown, maxLength?: number): string;
|
|
3
3
|
/** Compact summary for overlay labels. */
|
|
4
4
|
export declare function formatCausesShort(causes: UpdateCause[]): string;
|
|
5
5
|
/** Detailed lines for console output. */
|
|
6
6
|
export declare function formatCausesConsole(causes: UpdateCause[]): string[];
|
|
7
|
+
/** Log re-renders as a collapsed console group. */
|
|
8
|
+
export declare function logRerendersToConsole(entries: HighlightEntry[], showCauses: boolean): void;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export type {
|
|
1
|
+
export { monitor } from "./monitor.js";
|
|
2
|
+
export type { MonitorOptions, UpdateMonitor, RenderEntry, UpdateCause, } from "./types.js";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { MonitorOptions, UpdateMonitor } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Start monitoring React re-renders.
|
|
4
|
+
*
|
|
5
|
+
* Hooks into `__REACT_DEVTOOLS_GLOBAL_HOOK__.onCommitFiberRoot` to intercept
|
|
6
|
+
* every React commit. Records re-render entries, optionally logs them to the
|
|
7
|
+
* console, and optionally shows visual highlight overlays on re-rendered DOM nodes.
|
|
8
|
+
*
|
|
9
|
+
* Call this **before** React renders anything — ideally at the very top of
|
|
10
|
+
* your entry point.
|
|
11
|
+
*
|
|
12
|
+
* Returns an `UpdateMonitor` handle, or `null` if the DevTools hook is not found.
|
|
13
|
+
*/
|
|
14
|
+
export declare function monitor(options?: MonitorOptions): UpdateMonitor | null;
|
|
@@ -1,64 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
-
const STATE_HOOKS = /* @__PURE__ */ new Set([
|
|
4
|
-
"useState",
|
|
5
|
-
"useReducer",
|
|
6
|
-
"useSyncExternalStore"
|
|
7
|
-
]);
|
|
8
|
-
const HOOKS_WITHOUT_NODE = /* @__PURE__ */ new Set(["useContext"]);
|
|
9
|
-
function detectCauses(fiber) {
|
|
10
|
-
const alternate = fiber.alternate;
|
|
11
|
-
if (!alternate) return [];
|
|
12
|
-
const causes = [];
|
|
13
|
-
if (fiber.memoizedProps !== alternate.memoizedProps) {
|
|
14
|
-
causes.push({ kind: "props" });
|
|
15
|
-
}
|
|
16
|
-
if (fiber.tag === FiberTag.ClassComponent) {
|
|
17
|
-
if (fiber.memoizedState !== alternate.memoizedState) {
|
|
18
|
-
causes.push({ kind: "class-state" });
|
|
19
|
-
}
|
|
20
|
-
return causes;
|
|
21
|
-
}
|
|
22
|
-
const hookTypes = fiber._debugHookTypes;
|
|
23
|
-
if (!hookTypes) {
|
|
24
|
-
if (fiber.memoizedState !== alternate.memoizedState) {
|
|
25
|
-
causes.push({ kind: "unknown" });
|
|
26
|
-
}
|
|
27
|
-
return causes;
|
|
28
|
-
}
|
|
29
|
-
let currentNode = fiber.memoizedState;
|
|
30
|
-
let previousNode = alternate.memoizedState;
|
|
31
|
-
let hasContextHook = false;
|
|
32
|
-
let anyStateHookChanged = false;
|
|
33
|
-
for (let i = 0; i < hookTypes.length; i++) {
|
|
34
|
-
const type = hookTypes[i];
|
|
35
|
-
if (HOOKS_WITHOUT_NODE.has(type)) {
|
|
36
|
-
if (type === "useContext") hasContextHook = true;
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
if (STATE_HOOKS.has(type) && currentNode && previousNode) {
|
|
40
|
-
if (!Object.is(currentNode.memoizedState, previousNode.memoizedState)) {
|
|
41
|
-
anyStateHookChanged = true;
|
|
42
|
-
causes.push({
|
|
43
|
-
kind: "hook",
|
|
44
|
-
hookIndex: i,
|
|
45
|
-
hookType: type,
|
|
46
|
-
previousValue: previousNode.memoizedState,
|
|
47
|
-
nextValue: currentNode.memoizedState
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
currentNode = (currentNode == null ? void 0 : currentNode.next) ?? null;
|
|
52
|
-
previousNode = (previousNode == null ? void 0 : previousNode.next) ?? null;
|
|
53
|
-
}
|
|
54
|
-
if (hasContextHook && !anyStateHookChanged && fiber.memoizedProps === alternate.memoizedProps) {
|
|
55
|
-
causes.push({ kind: "hook", hookType: "useContext" });
|
|
56
|
-
}
|
|
57
|
-
if (causes.length === 0 && fiber.memoizedProps === alternate.memoizedProps) {
|
|
58
|
-
causes.push({ kind: "unknown" });
|
|
59
|
-
}
|
|
60
|
-
return causes;
|
|
61
|
-
}
|
|
62
3
|
const FiberTag = {
|
|
63
4
|
FunctionComponent: 0,
|
|
64
5
|
ClassComponent: 1,
|
|
@@ -113,25 +54,15 @@ function isSelfTriggered(fiber) {
|
|
|
113
54
|
function didFiberRender(nextFiber) {
|
|
114
55
|
return (nextFiber.flags & PerformedWork) === PerformedWork;
|
|
115
56
|
}
|
|
116
|
-
function
|
|
117
|
-
const
|
|
57
|
+
function detectRenders(root, mode) {
|
|
58
|
+
const results = [];
|
|
118
59
|
const selfTriggeredOnly = mode === "self-triggered";
|
|
119
60
|
const previousRoot = root.alternate;
|
|
120
|
-
if (!previousRoot) return
|
|
61
|
+
if (!previousRoot) return results;
|
|
121
62
|
function walk(nextFiber, previousFiber, depth) {
|
|
122
63
|
if (COMPONENT_TAGS.has(nextFiber.tag) && previousFiber !== null && previousFiber !== nextFiber && // same object → bailed-out subtree
|
|
123
64
|
didFiberRender(nextFiber) && (!selfTriggeredOnly || isSelfTriggered(nextFiber))) {
|
|
124
|
-
|
|
125
|
-
if (name) {
|
|
126
|
-
entries.push({
|
|
127
|
-
component: name,
|
|
128
|
-
path: getFiberPath(nextFiber),
|
|
129
|
-
duration: nextFiber.actualDuration ?? 0,
|
|
130
|
-
depth,
|
|
131
|
-
domNode: findNearestDOMNode(nextFiber),
|
|
132
|
-
causes: trackCauses ? detectCauses(nextFiber) : []
|
|
133
|
-
});
|
|
134
|
-
}
|
|
65
|
+
results.push({ fiber: nextFiber, depth });
|
|
135
66
|
}
|
|
136
67
|
let nextChild = nextFiber.child;
|
|
137
68
|
let previousChildAtSameIndex = (previousFiber == null ? void 0 : previousFiber.child) ?? null;
|
|
@@ -148,7 +79,66 @@ function collectPending(root, mode, trackCauses) {
|
|
|
148
79
|
}
|
|
149
80
|
}
|
|
150
81
|
walk(root, previousRoot, 0);
|
|
151
|
-
return
|
|
82
|
+
return results;
|
|
83
|
+
}
|
|
84
|
+
const STATE_HOOKS = /* @__PURE__ */ new Set([
|
|
85
|
+
"useState",
|
|
86
|
+
"useReducer",
|
|
87
|
+
"useSyncExternalStore"
|
|
88
|
+
]);
|
|
89
|
+
const HOOKS_WITHOUT_NODE = /* @__PURE__ */ new Set(["useContext"]);
|
|
90
|
+
function detectCauses(fiber) {
|
|
91
|
+
const alternate = fiber.alternate;
|
|
92
|
+
if (!alternate) return [];
|
|
93
|
+
const causes = [];
|
|
94
|
+
if (fiber.memoizedProps !== alternate.memoizedProps) {
|
|
95
|
+
causes.push({ kind: "props" });
|
|
96
|
+
}
|
|
97
|
+
if (fiber.tag === FiberTag.ClassComponent) {
|
|
98
|
+
if (fiber.memoizedState !== alternate.memoizedState) {
|
|
99
|
+
causes.push({ kind: "class-state" });
|
|
100
|
+
}
|
|
101
|
+
return causes;
|
|
102
|
+
}
|
|
103
|
+
const hookTypes = fiber._debugHookTypes;
|
|
104
|
+
if (!hookTypes) {
|
|
105
|
+
if (fiber.memoizedState !== alternate.memoizedState) {
|
|
106
|
+
causes.push({ kind: "unknown" });
|
|
107
|
+
}
|
|
108
|
+
return causes;
|
|
109
|
+
}
|
|
110
|
+
let currentNode = fiber.memoizedState;
|
|
111
|
+
let previousNode = alternate.memoizedState;
|
|
112
|
+
let hasContextHook = false;
|
|
113
|
+
let anyStateHookChanged = false;
|
|
114
|
+
for (let i = 0; i < hookTypes.length; i++) {
|
|
115
|
+
const type = hookTypes[i];
|
|
116
|
+
if (HOOKS_WITHOUT_NODE.has(type)) {
|
|
117
|
+
if (type === "useContext") hasContextHook = true;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (STATE_HOOKS.has(type) && currentNode && previousNode) {
|
|
121
|
+
if (!Object.is(currentNode.memoizedState, previousNode.memoizedState)) {
|
|
122
|
+
anyStateHookChanged = true;
|
|
123
|
+
causes.push({
|
|
124
|
+
kind: "hook",
|
|
125
|
+
hookIndex: i,
|
|
126
|
+
hookType: type,
|
|
127
|
+
previousValue: previousNode.memoizedState,
|
|
128
|
+
nextValue: currentNode.memoizedState
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
currentNode = (currentNode == null ? void 0 : currentNode.next) ?? null;
|
|
133
|
+
previousNode = (previousNode == null ? void 0 : previousNode.next) ?? null;
|
|
134
|
+
}
|
|
135
|
+
if (hasContextHook && !anyStateHookChanged && fiber.memoizedProps === alternate.memoizedProps) {
|
|
136
|
+
causes.push({ kind: "hook", hookType: "useContext" });
|
|
137
|
+
}
|
|
138
|
+
if (causes.length === 0 && fiber.memoizedProps === alternate.memoizedProps) {
|
|
139
|
+
causes.push({ kind: "unknown" });
|
|
140
|
+
}
|
|
141
|
+
return causes;
|
|
152
142
|
}
|
|
153
143
|
function formatValue(value, maxLength = 50) {
|
|
154
144
|
var _a;
|
|
@@ -212,6 +202,29 @@ function formatCausesConsole(causes) {
|
|
|
212
202
|
}
|
|
213
203
|
return lines;
|
|
214
204
|
}
|
|
205
|
+
function logRerendersToConsole(entries, showCauses) {
|
|
206
|
+
if (entries.length === 0) return;
|
|
207
|
+
console.groupCollapsed(
|
|
208
|
+
`%c⚛ ${entries.length} re-render${entries.length > 1 ? "s" : ""}`,
|
|
209
|
+
"color: #61dafb; font-weight: bold"
|
|
210
|
+
);
|
|
211
|
+
for (let i = 0; i < entries.length; i++) {
|
|
212
|
+
const entry = entries[i];
|
|
213
|
+
const durationText = entry.duration > 0 ? ` (${entry.duration.toFixed(2)}ms)` : "";
|
|
214
|
+
console.log(
|
|
215
|
+
`%c${entry.component}%c ${entry.path}${durationText}`,
|
|
216
|
+
"color: #e8e82e; font-weight: bold",
|
|
217
|
+
"color: #888"
|
|
218
|
+
);
|
|
219
|
+
if (showCauses && entry.causes.length > 0) {
|
|
220
|
+
const lines = formatCausesConsole(entry.causes);
|
|
221
|
+
for (const line of lines) {
|
|
222
|
+
console.log(`%c${line}`, "color: #aaa");
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
console.groupEnd();
|
|
227
|
+
}
|
|
215
228
|
const ANIMATION_NAME = "__rdu-fade";
|
|
216
229
|
const injectedWindows = /* @__PURE__ */ new WeakSet();
|
|
217
230
|
function ensureStylesheet(win) {
|
|
@@ -309,13 +322,7 @@ function heatColor(count, alpha) {
|
|
|
309
322
|
const lightness = 55 - normalizedCount * 10;
|
|
310
323
|
return `hsla(${hue}, ${saturation}%, ${lightness}%, ${alpha})`;
|
|
311
324
|
}
|
|
312
|
-
|
|
313
|
-
flushInterval: 250,
|
|
314
|
-
animationDuration: 1200,
|
|
315
|
-
showLabels: true,
|
|
316
|
-
opacity: 0.3
|
|
317
|
-
};
|
|
318
|
-
function createBatcher(options) {
|
|
325
|
+
function createHighlighter(config) {
|
|
319
326
|
let pending = [];
|
|
320
327
|
let timer = null;
|
|
321
328
|
function flush() {
|
|
@@ -368,10 +375,10 @@ function createBatcher(options) {
|
|
|
368
375
|
style.height = `${rect.height}px`;
|
|
369
376
|
style.backgroundColor = fillColor;
|
|
370
377
|
style.border = `1.5px solid ${borderColor}`;
|
|
371
|
-
style.setProperty("--rdu-opacity", String(
|
|
372
|
-
style.animation = `${OVERLAY_ANIMATION_NAME} ${
|
|
378
|
+
style.setProperty("--rdu-opacity", String(config.opacity));
|
|
379
|
+
style.animation = `${OVERLAY_ANIMATION_NAME} ${config.animationDuration}ms ease-out forwards`;
|
|
373
380
|
const label = element.firstElementChild;
|
|
374
|
-
if (
|
|
381
|
+
if (config.showLabels) {
|
|
375
382
|
const countText = coalesced.count > 1 ? ` ×${coalesced.count}` : "";
|
|
376
383
|
const durationText = coalesced.totalDuration > 0 ? ` ${coalesced.totalDuration.toFixed(1)}ms` : "";
|
|
377
384
|
const causeText = coalesced.causeSummary ? ` (${coalesced.causeSummary})` : "";
|
|
@@ -386,7 +393,7 @@ function createBatcher(options) {
|
|
|
386
393
|
function push(entry) {
|
|
387
394
|
pending.push(entry);
|
|
388
395
|
if (!timer) {
|
|
389
|
-
timer = setInterval(flush,
|
|
396
|
+
timer = setInterval(flush, config.flushInterval);
|
|
390
397
|
}
|
|
391
398
|
}
|
|
392
399
|
function dispose() {
|
|
@@ -398,14 +405,18 @@ function createBatcher(options) {
|
|
|
398
405
|
}
|
|
399
406
|
return { push, dispose };
|
|
400
407
|
}
|
|
401
|
-
function
|
|
408
|
+
function monitor(options = {}) {
|
|
402
409
|
const {
|
|
403
410
|
silent = false,
|
|
404
411
|
bufferSize = 500,
|
|
405
412
|
filter,
|
|
406
|
-
|
|
413
|
+
overlay = true,
|
|
407
414
|
mode = "self-triggered",
|
|
408
|
-
showCauses = false
|
|
415
|
+
showCauses = false,
|
|
416
|
+
flushInterval = 250,
|
|
417
|
+
animationDuration = 1200,
|
|
418
|
+
showLabels = true,
|
|
419
|
+
opacity = 0.3
|
|
409
420
|
} = options;
|
|
410
421
|
const hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
|
411
422
|
if (!hook) {
|
|
@@ -414,56 +425,46 @@ function attachRenderLogger(options = {}) {
|
|
|
414
425
|
);
|
|
415
426
|
return null;
|
|
416
427
|
}
|
|
417
|
-
const
|
|
418
|
-
...HIGHLIGHT_DEFAULTS,
|
|
419
|
-
...typeof highlight === "object" ? highlight : {}
|
|
420
|
-
} : null;
|
|
421
|
-
const batcher = highlightOptions ? createBatcher(highlightOptions) : null;
|
|
428
|
+
const highlighter = overlay ? createHighlighter({ flushInterval, animationDuration, showLabels, opacity }) : null;
|
|
422
429
|
const entries = [];
|
|
423
430
|
const previousOnCommit = hook.onCommitFiberRoot.bind(hook);
|
|
424
431
|
hook.onCommitFiberRoot = (rendererID, root, priorityLevel) => {
|
|
425
432
|
previousOnCommit(rendererID, root, priorityLevel);
|
|
426
|
-
const
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
+
const detectedRenders = detectRenders(root.current, mode);
|
|
434
|
+
if (detectedRenders.length === 0) return;
|
|
435
|
+
const highlightEntries = [];
|
|
436
|
+
for (let i = 0; i < detectedRenders.length; i++) {
|
|
437
|
+
const { fiber, depth } = detectedRenders[i];
|
|
438
|
+
const name = getComponentName(fiber);
|
|
439
|
+
if (!name) continue;
|
|
440
|
+
const highlightEntry = {
|
|
441
|
+
component: name,
|
|
442
|
+
path: getFiberPath(fiber),
|
|
443
|
+
duration: fiber.actualDuration ?? 0,
|
|
444
|
+
depth,
|
|
445
|
+
domNode: findNearestDOMNode(fiber),
|
|
446
|
+
causes: showCauses ? detectCauses(fiber) : []
|
|
447
|
+
};
|
|
448
|
+
const renderEntry = {
|
|
449
|
+
component: highlightEntry.component,
|
|
450
|
+
path: highlightEntry.path,
|
|
451
|
+
duration: highlightEntry.duration,
|
|
433
452
|
timestamp: performance.now(),
|
|
434
|
-
causes:
|
|
453
|
+
causes: highlightEntry.causes
|
|
435
454
|
};
|
|
436
|
-
if (filter && !filter(
|
|
455
|
+
if (filter && !filter(renderEntry)) continue;
|
|
437
456
|
if (entries.length >= bufferSize) entries.shift();
|
|
438
|
-
entries.push(
|
|
439
|
-
|
|
457
|
+
entries.push(renderEntry);
|
|
458
|
+
highlightEntries.push(highlightEntry);
|
|
459
|
+
highlighter == null ? void 0 : highlighter.push(highlightEntry);
|
|
440
460
|
}
|
|
441
|
-
if (!silent
|
|
442
|
-
|
|
443
|
-
`%c⚛ ${pendingEntries.length} re-render${pendingEntries.length > 1 ? "s" : ""}`,
|
|
444
|
-
"color: #61dafb; font-weight: bold"
|
|
445
|
-
);
|
|
446
|
-
for (let i = 0; i < pendingEntries.length; i++) {
|
|
447
|
-
const pendingEntry = pendingEntries[i];
|
|
448
|
-
const durationText = pendingEntry.duration > 0 ? ` (${pendingEntry.duration.toFixed(2)}ms)` : "";
|
|
449
|
-
console.log(
|
|
450
|
-
`%c${pendingEntry.component}%c ${pendingEntry.path}${durationText}`,
|
|
451
|
-
"color: #e8e82e; font-weight: bold",
|
|
452
|
-
"color: #888"
|
|
453
|
-
);
|
|
454
|
-
if (showCauses && pendingEntry.causes.length > 0) {
|
|
455
|
-
const lines = formatCausesConsole(pendingEntry.causes);
|
|
456
|
-
for (const line of lines) {
|
|
457
|
-
console.log(`%c${line}`, "color: #aaa");
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
console.groupEnd();
|
|
461
|
+
if (!silent) {
|
|
462
|
+
logRerendersToConsole(highlightEntries, showCauses);
|
|
462
463
|
}
|
|
463
464
|
};
|
|
464
|
-
const
|
|
465
|
+
const stop = () => {
|
|
465
466
|
hook.onCommitFiberRoot = previousOnCommit;
|
|
466
|
-
|
|
467
|
+
highlighter == null ? void 0 : highlighter.dispose();
|
|
467
468
|
disposeAllOverlays();
|
|
468
469
|
};
|
|
469
470
|
if (!silent) {
|
|
@@ -472,7 +473,7 @@ function attachRenderLogger(options = {}) {
|
|
|
472
473
|
"color: #61dafb; font-weight: bold"
|
|
473
474
|
);
|
|
474
475
|
}
|
|
475
|
-
return { entries,
|
|
476
|
+
return { entries, stop };
|
|
476
477
|
}
|
|
477
|
-
exports.
|
|
478
|
+
exports.monitor = monitor;
|
|
478
479
|
//# sourceMappingURL=react-debug-updates.cjs.map
|