react-debug-updates 0.1.9 → 0.1.11

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 CHANGED
@@ -14,11 +14,11 @@ So I wrote this — a plug-and-play one-liner that gives you visual highlight ov
14
14
 
15
15
  ## How it works
16
16
 
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:
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 `startReactUpdatesMonitor()` and you get:
18
18
 
19
- - **Visual overlays** — highlight boxes on re-rendered DOM nodes with a heat-map color scale (blue → red as render count increases)
19
+ - **Visual highlights** — highlight boxes on re-rendered DOM nodes with a heat-map color scale (blue → red as render count increases)
20
20
  - **Console logging** — grouped, color-coded re-render reports with component tree paths and render durations
21
- - **Cause detection** — pinpoint *which* `useState`, `useReducer`, `useSyncExternalStore`, or `useContext` hook triggered each re-render, with previous→next values
21
+ - **Update reasons** — pinpoint *which* `useState`, `useReducer`, `useSyncExternalStore`, or `useContext` hook triggered each re-render, with previous→next values
22
22
 
23
23
  ## Install
24
24
 
@@ -32,80 +32,61 @@ pnpm add react-debug-updates
32
32
 
33
33
  ## Quick start
34
34
 
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.
35
+ Import and call `startReactUpdatesMonitor` **before** React renders anything — ideally at the very top of your entry point. This ensures the hook is in place before the first commit.
36
36
 
37
37
  ```ts
38
- import { monitor } from "react-debug-updates";
38
+ import { startReactUpdatesMonitor } from "react-debug-updates";
39
39
 
40
- // One-liner — overlays + console logging out of the box
41
- const updates = monitor();
40
+ // One-liner — visual highlights out of the box
41
+ const stop = startReactUpdatesMonitor();
42
42
 
43
43
  // Later, to clean up:
44
- updates?.stop();
44
+ stop();
45
45
  ```
46
46
 
47
47
  ### Dev-only guard
48
48
 
49
49
  ```ts
50
50
  if (process.env.NODE_ENV === "development") {
51
- const { monitor } = await import("react-debug-updates");
52
- monitor();
51
+ const { startReactUpdatesMonitor } = await import("react-debug-updates");
52
+ startReactUpdatesMonitor();
53
53
  }
54
54
  ```
55
55
 
56
56
  ### With options
57
57
 
58
58
  ```ts
59
- monitor({
60
- showCauses: true,
61
- opacity: 0.5,
62
- showLabels: false,
63
- silent: true, // overlays only, no console output
59
+ startReactUpdatesMonitor({
60
+ reasonOfUpdate: true,
61
+ logToConsole: true,
62
+ highlightOpacity: 0.5,
63
+ highlightShowLabels: false,
64
64
  });
65
65
  ```
66
66
 
67
67
  ## Requirements
68
68
 
69
69
  - A **React dev build** (which automatically creates `__REACT_DEVTOOLS_GLOBAL_HOOK__`) — no browser extension needed
70
- - For `showCauses` and render durations: React must be in **dev mode** (provides `_debugHookTypes` and `actualDuration` on fibers)
70
+ - For `reasonOfUpdate` and render durations: React must be in **dev mode** (provides `_debugHookTypes` and `actualDuration` on fibers)
71
71
 
72
72
  ## API
73
73
 
74
- ### `monitor(options?): UpdateMonitor | null`
74
+ ### `startReactUpdatesMonitor(options?): () => void`
75
75
 
76
- Returns an `UpdateMonitor` handle, or `null` if the DevTools hook is not available.
76
+ Returns a `stop` function to unhook from React and remove all overlays. Throws if called in a non-browser environment (e.g. SSR).
77
77
 
78
78
  #### Options
79
79
 
80
80
  | Option | Type | Default | Description |
81
81
  | --- | --- | --- | --- |
82
82
  | `mode` | `"self-triggered" \| "all"` | `"self-triggered"` | `"self-triggered"` tracks only components whose own state changed. `"all"` includes children swept by parent updates |
83
- | `showCauses` | `boolean` | `false` | Detect and display why each component re-rendered |
84
- | `silent` | `boolean` | `false` | Suppress console output |
85
- | `overlay` | `boolean` | `true` | Enable visual highlight overlays |
86
- | `showLabels` | `boolean` | `true` | Show text labels (name, count, duration, cause) above overlays |
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 |
92
-
93
- ### `UpdateMonitor`
94
-
95
- | Property | Type | Description |
96
- | --- | --- | --- |
97
- | `entries` | `RenderEntry[]` | Ring buffer of recorded re-render entries |
98
- | `stop` | `() => void` | Unhook from React and remove all overlays |
99
-
100
- ### `RenderEntry`
101
-
102
- | Property | Type | Description |
103
- | --- | --- | --- |
104
- | `component` | `string` | Component display name |
105
- | `path` | `string` | Ancestor component path (e.g. `"App → Layout → Sidebar"`) |
106
- | `duration` | `number` | Render duration in ms (requires React dev mode) |
107
- | `timestamp` | `number` | `performance.now()` when the entry was recorded |
108
- | `causes` | `UpdateCause[]` | Why this component re-rendered (requires `showCauses`) |
83
+ | `reasonOfUpdate` | `boolean` | `false` | Detect and display why each component re-rendered |
84
+ | `logToConsole` | `boolean` | `false` | Log re-renders to the console |
85
+ | `highlight` | `boolean` | `true` | Enable visual highlight overlays |
86
+ | `highlightShowLabels` | `boolean` | `true` | Show text labels (name, count, duration, cause) above highlights |
87
+ | `highlightOpacity` | `number` | `0.3` | Peak opacity of highlight overlays (0–1) |
88
+ | `highlightFlushInterval` | `number` | `250` | Milliseconds between highlight flush cycles |
89
+ | `highlightAnimationDuration` | `number` | `1200` | Highlight fade-out animation duration (ms) |
109
90
 
110
91
  ### `UpdateCause`
111
92
 
@@ -129,11 +110,11 @@ Returns an `UpdateMonitor` handle, or `null` if the DevTools hook is not availab
129
110
  ↳ useContext changed
130
111
  ```
131
112
 
132
- ## Visual overlays
113
+ ## Visual highlights
133
114
 
134
115
  Re-rendered components get a highlight box that fades out. The color shifts from blue to red as the same node re-renders repeatedly within a flush window — making "hot" components visually obvious.
135
116
 
136
- Each overlay label shows: `ComponentName ×count duration (cause)`
117
+ Each highlight label shows: `ComponentName ×count duration (cause)`
137
118
 
138
119
  ## License
139
120
 
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export { monitor } from "./monitor.js";
2
- export type { MonitorOptions, UpdateMonitor, RenderEntry, UpdateCause, } from "./types.js";
1
+ export { startReactUpdatesMonitor } from "./monitor.js";
2
+ export type { MonitorOptions, UpdateCause } from "./types.js";
package/dist/monitor.d.ts CHANGED
@@ -1,14 +1,15 @@
1
- import type { MonitorOptions, UpdateMonitor } from "./types.js";
1
+ import type { MonitorOptions } from "./types.js";
2
2
  /**
3
3
  * Start monitoring React re-renders.
4
4
  *
5
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.
6
+ * every React commit. Shows visual highlight overlays on re-rendered DOM nodes
7
+ * and optionally logs re-renders to the console.
8
8
  *
9
9
  * Call this **before** React renders anything — ideally at the very top of
10
10
  * your entry point.
11
11
  *
12
- * Returns an `UpdateMonitor` handle, or `null` if the DevTools hook is not found.
12
+ * Returns a `stop` function to unhook and clean up. Throws if called
13
+ * in a non-browser environment (e.g. SSR).
13
14
  */
14
- export declare function monitor(options?: MonitorOptions): UpdateMonitor | null;
15
+ export declare function startReactUpdatesMonitor({ logToConsole, highlight, mode, reasonOfUpdate, highlightFlushInterval, highlightAnimationDuration, highlightShowLabels, highlightOpacity, }?: MonitorOptions): () => void;
@@ -405,28 +405,48 @@ function createHighlighter(config) {
405
405
  }
406
406
  return { push, dispose };
407
407
  }
408
- function monitor(options = {}) {
409
- const {
410
- silent = false,
411
- bufferSize = 500,
412
- filter,
413
- overlay = true,
414
- mode = "self-triggered",
415
- showCauses = false,
416
- flushInterval = 250,
417
- animationDuration = 1200,
418
- showLabels = true,
419
- opacity = 0.3
420
- } = options;
421
- const hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
422
- if (!hook) {
423
- console.warn(
424
- "[react-debug-updates] __REACT_DEVTOOLS_GLOBAL_HOOK__ not found. Make sure React DevTools or a dev build of React is active."
408
+ function ensureDevToolsHook(win) {
409
+ const global = win;
410
+ if (!global.__REACT_DEVTOOLS_GLOBAL_HOOK__) {
411
+ global.__REACT_DEVTOOLS_GLOBAL_HOOK__ = {
412
+ supportsFiber: true,
413
+ inject() {
414
+ return 1;
415
+ },
416
+ onCommitFiberRoot() {
417
+ },
418
+ onCommitFiberUnmount() {
419
+ },
420
+ onPostCommitFiberRoot() {
421
+ },
422
+ checkDCE() {
423
+ }
424
+ };
425
+ }
426
+ return global.__REACT_DEVTOOLS_GLOBAL_HOOK__;
427
+ }
428
+ function startReactUpdatesMonitor({
429
+ logToConsole = false,
430
+ highlight = true,
431
+ mode = "self-triggered",
432
+ reasonOfUpdate = false,
433
+ highlightFlushInterval = 250,
434
+ highlightAnimationDuration = 1200,
435
+ highlightShowLabels = true,
436
+ highlightOpacity = 0.3
437
+ } = {}) {
438
+ if (typeof window === "undefined") {
439
+ throw new Error(
440
+ "[react-debug-updates] Cannot start monitor in a non-browser environment. Guard the call with a `typeof window !== 'undefined'` check."
425
441
  );
426
- return null;
427
442
  }
428
- const highlighter = overlay ? createHighlighter({ flushInterval, animationDuration, showLabels, opacity }) : null;
429
- const entries = [];
443
+ const hook = ensureDevToolsHook(window);
444
+ const highlighter = highlight ? createHighlighter({
445
+ flushInterval: highlightFlushInterval,
446
+ animationDuration: highlightAnimationDuration,
447
+ showLabels: highlightShowLabels,
448
+ opacity: highlightOpacity
449
+ }) : null;
430
450
  const previousOnCommit = hook.onCommitFiberRoot.bind(hook);
431
451
  hook.onCommitFiberRoot = (rendererID, root, priorityLevel) => {
432
452
  previousOnCommit(rendererID, root, priorityLevel);
@@ -437,43 +457,32 @@ function monitor(options = {}) {
437
457
  const { fiber, depth } = detectedRenders[i];
438
458
  const name = getComponentName(fiber);
439
459
  if (!name) continue;
440
- const highlightEntry = {
460
+ const entry = {
441
461
  component: name,
442
462
  path: getFiberPath(fiber),
443
463
  duration: fiber.actualDuration ?? 0,
444
464
  depth,
445
465
  domNode: findNearestDOMNode(fiber),
446
- causes: showCauses ? detectCauses(fiber) : []
466
+ causes: reasonOfUpdate ? detectCauses(fiber) : []
447
467
  };
448
- const renderEntry = {
449
- component: highlightEntry.component,
450
- path: highlightEntry.path,
451
- duration: highlightEntry.duration,
452
- timestamp: performance.now(),
453
- causes: highlightEntry.causes
454
- };
455
- if (filter && !filter(renderEntry)) continue;
456
- if (entries.length >= bufferSize) entries.shift();
457
- entries.push(renderEntry);
458
- highlightEntries.push(highlightEntry);
459
- highlighter == null ? void 0 : highlighter.push(highlightEntry);
468
+ highlightEntries.push(entry);
469
+ highlighter == null ? void 0 : highlighter.push(entry);
460
470
  }
461
- if (!silent) {
462
- logRerendersToConsole(highlightEntries, showCauses);
471
+ if (logToConsole) {
472
+ logRerendersToConsole(highlightEntries, reasonOfUpdate);
463
473
  }
464
474
  };
465
- const stop = () => {
466
- hook.onCommitFiberRoot = previousOnCommit;
467
- highlighter == null ? void 0 : highlighter.dispose();
468
- disposeAllOverlays();
469
- };
470
- if (!silent) {
475
+ if (logToConsole) {
471
476
  console.log(
472
477
  "%c⚛ react-debug-updates attached",
473
478
  "color: #61dafb; font-weight: bold"
474
479
  );
475
480
  }
476
- return { entries, stop };
481
+ return () => {
482
+ hook.onCommitFiberRoot = previousOnCommit;
483
+ highlighter == null ? void 0 : highlighter.dispose();
484
+ disposeAllOverlays();
485
+ };
477
486
  }
478
- exports.monitor = monitor;
487
+ exports.startReactUpdatesMonitor = startReactUpdatesMonitor;
479
488
  //# sourceMappingURL=react-debug-updates.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"react-debug-updates.cjs","sources":["../src/fiber.ts","../src/causes.ts","../src/format.ts","../src/overlay.ts","../src/highlights.ts","../src/monitor.ts"],"sourcesContent":["import type { Fiber, DetectedRender } from \"./types.js\";\n\n// ────────────────────────────────────────────\n// Fiber tag constants\n// ────────────────────────────────────────────\n\nexport const FiberTag = {\n FunctionComponent: 0,\n ClassComponent: 1,\n HostComponent: 5,\n ForwardRef: 11,\n SimpleMemoComponent: 15,\n MemoComponent: 14,\n} as const;\n\n// Component tags we report as re-renders.\n// MemoComponent (14) is excluded to avoid double-reporting — it's a wrapper\n// fiber around the inner component. The inner component (FunctionComponent,\n// ForwardRef, or SimpleMemoComponent) has its own PerformedWork flag and\n// will be reported correctly on its own.\nconst COMPONENT_TAGS = new Set<number>([\n FiberTag.FunctionComponent,\n FiberTag.ClassComponent,\n FiberTag.ForwardRef,\n FiberTag.SimpleMemoComponent,\n]);\n\nconst PerformedWork = 0b0000001;\n\n// ────────────────────────────────────────────\n// Fiber tree helpers\n// ────────────────────────────────────────────\n\nexport function getComponentName(fiber: Fiber): string | null {\n const { type } = fiber;\n if (!type || typeof type === \"string\") return null;\n return type.displayName ?? type.name ?? null;\n}\n\nfunction isHTMLElement(value: unknown): value is HTMLElement {\n return (\n typeof value === \"object\" &&\n value !== null &&\n (value as Node).nodeType === 1 &&\n typeof (value as HTMLElement).getBoundingClientRect === \"function\"\n );\n}\n\nexport function findNearestDOMNode(fiber: Fiber): HTMLElement | null {\n if (fiber.tag === FiberTag.HostComponent && isHTMLElement(fiber.stateNode)) {\n return fiber.stateNode;\n }\n let child: Fiber | null = fiber.child;\n while (child) {\n const found = findNearestDOMNode(child);\n if (found) return found;\n child = child.sibling;\n }\n return null;\n}\n\nexport function getFiberPath(fiber: Fiber, maxDepth = 5): string {\n const parts: string[] = [];\n let current: Fiber | null = fiber.return;\n let depth = 0;\n while (current && depth < maxDepth) {\n const name = getComponentName(current);\n if (name) parts.unshift(name);\n current = current.return;\n depth++;\n }\n return parts.length ? parts.join(\" → \") : \"(root)\";\n}\n\n// ────────────────────────────────────────────\n// Self-triggered detection\n// ────────────────────────────────────────────\n\nfunction isSelfTriggered(fiber: Fiber): boolean {\n const alternate = fiber.alternate;\n if (!alternate) return false;\n return fiber.memoizedProps === alternate.memoizedProps;\n}\n\n// ────────────────────────────────────────────\n// didFiberRender — mirrors React DevTools\n// ────────────────────────────────────────────\n//\n// See: react-devtools-shared/src/backend/fiber/shared/DevToolsFiberChangeDetection.js\n//\n// For component fibers (function, class, memo, forwardRef), React sets the\n// PerformedWork flag (bit 0) only when user code actually executes.\n// createWorkInProgress resets flags to NoFlags, so PerformedWork on a\n// work-in-progress fiber is always fresh — never stale from a prior commit.\n//\n// This check must only be called AFTER confirming prevFiber !== nextFiber\n// (i.e. the fiber was actually processed, not a bailed-out subtree).\n\nfunction didFiberRender(nextFiber: Fiber): boolean {\n return (nextFiber.flags & PerformedWork) === PerformedWork;\n}\n\n// ────────────────────────────────────────────\n// Detect which components re-rendered in a commit\n// ────────────────────────────────────────────\n//\n// Mirrors React DevTools' updateFiberRecursively / updateChildrenRecursively.\n//\n// React double-buffers fibers. After a commit, root.current is the committed\n// tree and root.current.alternate is the previous tree. We walk both in\n// parallel. At each level, if prevChild and nextChild are the same object,\n// React bailed out that entire subtree (via cloneChildFibers) — we skip it.\n// Otherwise, we use nextChild.alternate as the previous fiber and check\n// didFiberRender (PerformedWork) to see if user code actually ran.\n//\n// Returns lightweight DetectedRender objects — just the fiber and its depth.\n// DOM lookup, cause detection, and formatting are the caller's responsibility.\n\nexport function detectRenders(\n root: Fiber,\n mode: \"self-triggered\" | \"all\",\n): DetectedRender[] {\n const results: DetectedRender[] = [];\n const selfTriggeredOnly = mode === \"self-triggered\";\n\n // The alternate of the committed root is the previous tree's root.\n // On initial mount this is null — nothing to report.\n const previousRoot = root.alternate;\n if (!previousRoot) return results;\n\n function walk(\n nextFiber: Fiber,\n previousFiber: Fiber | null,\n depth: number,\n ) {\n // ── Check this fiber ──\n if (\n COMPONENT_TAGS.has(nextFiber.tag) &&\n previousFiber !== null &&\n previousFiber !== nextFiber && // same object → bailed-out subtree\n didFiberRender(nextFiber) &&\n (!selfTriggeredOnly || isSelfTriggered(nextFiber))\n ) {\n results.push({ fiber: nextFiber, depth });\n }\n\n // ── Walk children, matching with previous tree ──\n let nextChild = nextFiber.child;\n let previousChildAtSameIndex = previousFiber?.child ?? null;\n\n while (nextChild) {\n let matchedPrevious: Fiber | null;\n\n if (previousChildAtSameIndex === nextChild) {\n // Same object identity — React shared this fiber via cloneChildFibers.\n // The entire subtree was bailed out; passing the same object as both\n // prev and next causes the prevFiber !== nextFiber guard to skip it.\n matchedPrevious = nextChild;\n } else {\n // Different object — this fiber was processed. The alternate is the\n // corresponding fiber from the previous tree.\n matchedPrevious = nextChild.alternate;\n }\n\n walk(nextChild, matchedPrevious, depth + 1);\n\n nextChild = nextChild.sibling;\n previousChildAtSameIndex = previousChildAtSameIndex?.sibling ?? null;\n }\n }\n\n walk(root, previousRoot, 0);\n return results;\n}\n","import type { Fiber, HookNode, UpdateCause } from \"./types.js\";\nimport { FiberTag } from \"./fiber.js\";\n\n// ────────────────────────────────────────────\n// Update cause detection\n// ────────────────────────────────────────────\n//\n// For function components, React stores hooks as a linked list on\n// fiber.memoizedState. In dev builds, _debugHookTypes lists hook names\n// in call order. We walk both in parallel, comparing memoizedState\n// on each node to find which hook's state actually changed.\n//\n// Caveat: useContext does NOT create a linked list node, so we skip it\n// when advancing the pointer, and detect it by elimination.\n\n/**\n * Hooks whose memoizedState changing can trigger a re-render.\n * We only report these — useEffect/useMemo/etc. don't cause re-renders.\n */\nconst STATE_HOOKS = new Set([\n \"useState\",\n \"useReducer\",\n \"useSyncExternalStore\",\n]);\n\n/**\n * Hooks that do NOT create a node in the memoizedState linked list.\n * Currently only useContext — every other hook allocates a node.\n */\nconst HOOKS_WITHOUT_NODE = new Set([\"useContext\"]);\n\nexport function detectCauses(fiber: Fiber): UpdateCause[] {\n const alternate = fiber.alternate;\n if (!alternate) return [];\n\n const causes: UpdateCause[] = [];\n\n // ── Props changed (parent re-rendered us with new props) ──\n if (fiber.memoizedProps !== alternate.memoizedProps) {\n causes.push({ kind: \"props\" });\n }\n\n // ── Class component ──\n if (fiber.tag === FiberTag.ClassComponent) {\n if (fiber.memoizedState !== alternate.memoizedState) {\n causes.push({ kind: \"class-state\" });\n }\n return causes;\n }\n\n // ── Function component hooks ──\n const hookTypes = fiber._debugHookTypes;\n\n if (!hookTypes) {\n // No debug info (prod build) — best effort\n if (fiber.memoizedState !== alternate.memoizedState) {\n causes.push({ kind: \"unknown\" });\n }\n return causes;\n }\n\n let currentNode = fiber.memoizedState as HookNode | null;\n let previousNode = alternate.memoizedState as HookNode | null;\n let hasContextHook = false;\n let anyStateHookChanged = false;\n\n for (let i = 0; i < hookTypes.length; i++) {\n const type = hookTypes[i];\n\n // useContext has no linked list node — skip pointer advance\n if (HOOKS_WITHOUT_NODE.has(type)) {\n if (type === \"useContext\") hasContextHook = true;\n continue;\n }\n\n if (STATE_HOOKS.has(type) && currentNode && previousNode) {\n if (!Object.is(currentNode.memoizedState, previousNode.memoizedState)) {\n anyStateHookChanged = true;\n causes.push({\n kind: \"hook\",\n hookIndex: i,\n hookType: type,\n previousValue: previousNode.memoizedState,\n nextValue: currentNode.memoizedState,\n });\n }\n }\n\n currentNode = currentNode?.next ?? null;\n previousNode = previousNode?.next ?? null;\n }\n\n // If no state hook changed but component is self-triggered and has\n // useContext → the context value must have changed.\n if (\n hasContextHook &&\n !anyStateHookChanged &&\n fiber.memoizedProps === alternate.memoizedProps\n ) {\n causes.push({ kind: \"hook\", hookType: \"useContext\" });\n }\n\n // If still nothing and self-triggered, mark unknown\n if (causes.length === 0 && fiber.memoizedProps === alternate.memoizedProps) {\n causes.push({ kind: \"unknown\" });\n }\n\n return causes;\n}\n","import type { HighlightEntry, UpdateCause } from \"./types.js\";\n\n// ────────────────────────────────────────────\n// Value formatting\n// ────────────────────────────────────────────\n\nexport function formatValue(value: unknown, maxLength = 50): string {\n if (value === null) return \"null\";\n if (value === undefined) return \"undefined\";\n if (typeof value === \"boolean\") return String(value);\n if (typeof value === \"number\") return String(value);\n if (typeof value === \"function\")\n return `ƒ ${(value as { name?: string }).name || \"anonymous\"}`;\n\n if (typeof value === \"string\") {\n const truncated =\n value.length > maxLength ? value.slice(0, maxLength) + \"…\" : value;\n return JSON.stringify(truncated);\n }\n\n if (Array.isArray(value)) return `Array(${value.length})`;\n\n if (typeof value === \"object\") {\n const name = (value as object).constructor?.name;\n if (name && name !== \"Object\") return name;\n const keys = Object.keys(value as object);\n if (keys.length <= 3) return `{ ${keys.join(\", \")} }`;\n return `{ ${keys.slice(0, 3).join(\", \")}, … }`;\n }\n\n return String(value);\n}\n\n// ────────────────────────────────────────────\n// Cause formatting\n// ────────────────────────────────────────────\n\n/** Compact summary for overlay labels. */\nexport function formatCausesShort(causes: UpdateCause[]): string {\n if (causes.length === 0) return \"\";\n\n const parts: string[] = [];\n for (const cause of causes) {\n if (cause.kind === \"props\") {\n parts.push(\"props\");\n } else if (cause.kind === \"class-state\") {\n parts.push(\"state\");\n } else if (cause.kind === \"hook\" && cause.hookType) {\n const indexSuffix = cause.hookIndex != null ? `[${cause.hookIndex}]` : \"\";\n parts.push(`${cause.hookType}${indexSuffix}`);\n } else {\n parts.push(\"?\");\n }\n }\n\n return parts.join(\", \");\n}\n\n/** Detailed lines for console output. */\nexport function formatCausesConsole(causes: UpdateCause[]): string[] {\n const lines: string[] = [];\n\n for (const cause of causes) {\n if (cause.kind === \"props\") {\n lines.push(\" ↳ props changed (parent re-rendered)\");\n } else if (cause.kind === \"class-state\") {\n lines.push(\" ↳ class state changed\");\n } else if (cause.kind === \"hook\" && cause.hookType) {\n const indexSuffix =\n cause.hookIndex != null ? `[${cause.hookIndex}]` : \"\";\n const name = `${cause.hookType}${indexSuffix}`;\n\n if (cause.previousValue !== undefined && cause.nextValue !== undefined) {\n lines.push(\n ` ↳ ${name}: ${formatValue(cause.previousValue)} → ${formatValue(cause.nextValue)}`,\n );\n } else {\n lines.push(` ↳ ${name} changed`);\n }\n } else {\n lines.push(\" ↳ unknown cause\");\n }\n }\n\n return lines;\n}\n\n// ────────────────────────────────────────────\n// Console output\n// ────────────────────────────────────────────\n\n/** Log re-renders as a collapsed console group. */\nexport function logRerendersToConsole(\n entries: HighlightEntry[],\n showCauses: boolean,\n) {\n if (entries.length === 0) return;\n\n console.groupCollapsed(\n `%c⚛ ${entries.length} re-render${entries.length > 1 ? \"s\" : \"\"}`,\n \"color: #61dafb; font-weight: bold\",\n );\n\n for (let i = 0; i < entries.length; i++) {\n const entry = entries[i];\n const durationText =\n entry.duration > 0 ? ` (${entry.duration.toFixed(2)}ms)` : \"\";\n console.log(\n `%c${entry.component}%c ${entry.path}${durationText}`,\n \"color: #e8e82e; font-weight: bold\",\n \"color: #888\",\n );\n\n if (showCauses && entry.causes.length > 0) {\n const lines = formatCausesConsole(entry.causes);\n for (const line of lines) {\n console.log(`%c${line}`, \"color: #aaa\");\n }\n }\n }\n\n console.groupEnd();\n}\n","// ────────────────────────────────────────────\n// Per-window overlay infrastructure\n// ────────────────────────────────────────────\n//\n// Performance:\n// - Overlay elements pooled per window, reused via animationend\n// - Single CSS @keyframes per window, no JS timers per element\n\nconst ANIMATION_NAME = \"__rdu-fade\";\n\nconst injectedWindows = new WeakSet<Window>();\n\nfunction ensureStylesheet(win: Window) {\n if (injectedWindows.has(win)) return;\n injectedWindows.add(win);\n\n const style = win.document.createElement(\"style\");\n style.textContent = `\n @keyframes ${ANIMATION_NAME} {\n 0% { opacity: 0; }\n 8% { opacity: var(--rdu-opacity, 1); }\n 40% { opacity: var(--rdu-opacity, 1); }\n 100% { opacity: 0; }\n }\n .${ANIMATION_NAME}-box {\n position: fixed;\n pointer-events: none;\n box-sizing: border-box;\n border-radius: 3px;\n opacity: 0;\n will-change: opacity;\n }\n .${ANIMATION_NAME}-label {\n position: absolute;\n top: -18px;\n left: -1px;\n font: 10px/16px ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, monospace;\n padding: 0 4px;\n color: #fff;\n border-radius: 2px 2px 0 0;\n white-space: nowrap;\n pointer-events: none;\n }\n `;\n win.document.head.appendChild(style);\n}\n\n// ────────────────────────────────────────────\n// Overlay root container\n// ────────────────────────────────────────────\n\nconst overlayRoots = new WeakMap<Window, HTMLDivElement>();\n\nfunction getOverlayRoot(win: Window): HTMLDivElement | null {\n if (win.closed) return null;\n\n const existing = overlayRoots.get(win);\n if (existing?.isConnected) return existing;\n\n ensureStylesheet(win);\n\n const root = win.document.createElement(\"div\");\n root.id = \"__react-debug-updates\";\n Object.assign(root.style, {\n position: \"fixed\",\n top: \"0\",\n left: \"0\",\n width: \"0\",\n height: \"0\",\n overflow: \"visible\",\n pointerEvents: \"none\",\n zIndex: \"2147483647\",\n } satisfies Partial<CSSStyleDeclaration>);\n win.document.body.appendChild(root);\n\n overlayRoots.set(win, root);\n return root;\n}\n\n// ────────────────────────────────────────────\n// Element pool\n// ────────────────────────────────────────────\n\nconst pools = new WeakMap<Window, HTMLDivElement[]>();\n\nexport function acquireOverlay(win: Window): HTMLDivElement | null {\n const root = getOverlayRoot(win);\n if (!root) return null;\n\n let pool = pools.get(win);\n if (!pool) {\n pool = [];\n pools.set(win, pool);\n }\n\n const document = win.document;\n let element = pool.pop();\n\n if (!element) {\n element = document.createElement(\"div\");\n element.className = `${ANIMATION_NAME}-box`;\n\n const label = document.createElement(\"span\");\n label.className = `${ANIMATION_NAME}-label`;\n element.appendChild(label);\n\n element.addEventListener(\"animationend\", () => {\n element!.style.animation = \"none\";\n element!.remove();\n pool!.push(element!);\n });\n }\n\n root.appendChild(element);\n return element;\n}\n\nexport function disposeAllOverlays() {\n const mainRoot = overlayRoots.get(window);\n mainRoot?.remove();\n overlayRoots.delete(window);\n pools.delete(window);\n}\n\nexport const OVERLAY_ANIMATION_NAME = ANIMATION_NAME;\n","import type { OverlayConfig, HighlightEntry } from \"./types.js\";\nimport { formatCausesShort } from \"./format.js\";\nimport { acquireOverlay, OVERLAY_ANIMATION_NAME } from \"./overlay.js\";\n\n// ────────────────────────────────────────────\n// Heat color\n// ────────────────────────────────────────────\n\nfunction heatColor(count: number, alpha: number): string {\n const normalizedCount = Math.min((count - 1) / 7, 1);\n const hue = 200 - normalizedCount * 200;\n const saturation = 85 + normalizedCount * 15;\n const lightness = 55 - normalizedCount * 10;\n return `hsla(${hue}, ${saturation}%, ${lightness}%, ${alpha})`;\n}\n\n// ────────────────────────────────────────────\n// Highlight scheduler\n// ────────────────────────────────────────────\n//\n// Commit path just pushes lightweight entries (no DOM reads, no formatting).\n// Flush (setInterval): batched rect reads → batched DOM writes.\n\ninterface CoalescedEntry {\n count: number;\n totalDuration: number;\n component: string;\n /** Shallowest fiber depth among coalesced entries (for z-ordering). */\n depth: number;\n domNode: HTMLElement;\n ownerWindow: Window;\n causeSummary: string;\n}\n\nexport function createHighlighter(config: OverlayConfig) {\n let pending: HighlightEntry[] = [];\n let timer: ReturnType<typeof setInterval> | null = null;\n\n function flush() {\n if (pending.length === 0) return;\n\n const batch = pending;\n pending = [];\n\n // Coalesce by DOM node identity\n const map = new Map<HTMLElement, CoalescedEntry>();\n\n for (let i = 0; i < batch.length; i++) {\n const entry = batch[i];\n if (!entry.domNode) continue;\n\n const existing = map.get(entry.domNode);\n if (existing) {\n existing.count++;\n existing.totalDuration += entry.duration;\n existing.depth = Math.min(existing.depth, entry.depth);\n } else {\n const win = entry.domNode.ownerDocument?.defaultView;\n if (!win || win.closed) continue;\n map.set(entry.domNode, {\n count: 1,\n totalDuration: entry.duration,\n component: entry.component,\n depth: entry.depth,\n domNode: entry.domNode,\n ownerWindow: win,\n causeSummary: formatCausesShort(entry.causes),\n });\n }\n }\n\n // Read phase: batch all rect reads\n const toShow: Array<{ coalesced: CoalescedEntry; rect: DOMRect }> = [];\n for (const coalesced of map.values()) {\n const rect = coalesced.domNode.getBoundingClientRect();\n if (rect.width > 0 || rect.height > 0) {\n toShow.push({ coalesced, rect });\n }\n }\n\n // Sort deepest first so parents are appended last (render on top)\n toShow.sort((a, b) => b.coalesced.depth - a.coalesced.depth);\n\n // Write phase: position overlays\n for (let i = 0; i < toShow.length; i++) {\n const { coalesced, rect } = toShow[i];\n const element = acquireOverlay(coalesced.ownerWindow);\n if (!element) continue;\n\n const fillColor = heatColor(coalesced.count, 0.18);\n const borderColor = heatColor(coalesced.count, 0.75);\n const labelBackground = heatColor(coalesced.count, 0.9);\n\n const style = element.style;\n style.top = `${rect.top}px`;\n style.left = `${rect.left}px`;\n style.width = `${rect.width}px`;\n style.height = `${rect.height}px`;\n style.backgroundColor = fillColor;\n style.border = `1.5px solid ${borderColor}`;\n style.setProperty(\"--rdu-opacity\", String(config.opacity));\n style.animation = `${OVERLAY_ANIMATION_NAME} ${config.animationDuration}ms ease-out forwards`;\n\n const label = element.firstElementChild as HTMLElement;\n if (config.showLabels) {\n const countText = coalesced.count > 1 ? ` ×${coalesced.count}` : \"\";\n const durationText =\n coalesced.totalDuration > 0\n ? ` ${coalesced.totalDuration.toFixed(1)}ms`\n : \"\";\n const causeText = coalesced.causeSummary\n ? ` (${coalesced.causeSummary})`\n : \"\";\n label.textContent = `${coalesced.component}${countText}${durationText}${causeText}`;\n label.style.backgroundColor = labelBackground;\n } else {\n label.textContent = \"\";\n label.style.backgroundColor = \"transparent\";\n }\n }\n }\n\n function push(entry: HighlightEntry) {\n pending.push(entry);\n if (!timer) {\n timer = setInterval(flush, config.flushInterval);\n }\n }\n\n function dispose() {\n if (timer) {\n clearInterval(timer);\n timer = null;\n }\n pending = [];\n }\n\n return { push, dispose };\n}\n","import type {\n DevToolsHook,\n FiberRoot,\n HighlightEntry,\n MonitorOptions,\n RenderEntry,\n UpdateMonitor,\n} from \"./types.js\";\nimport {\n detectRenders,\n findNearestDOMNode,\n getComponentName,\n getFiberPath,\n} from \"./fiber.js\";\nimport { detectCauses } from \"./causes.js\";\nimport { logRerendersToConsole } from \"./format.js\";\nimport { createHighlighter } from \"./highlights.js\";\nimport { disposeAllOverlays } from \"./overlay.js\";\n\n/**\n * Start monitoring React re-renders.\n *\n * Hooks into `__REACT_DEVTOOLS_GLOBAL_HOOK__.onCommitFiberRoot` to intercept\n * every React commit. Records re-render entries, optionally logs them to the\n * console, and optionally shows visual highlight overlays on re-rendered DOM nodes.\n *\n * Call this **before** React renders anything — ideally at the very top of\n * your entry point.\n *\n * Returns an `UpdateMonitor` handle, or `null` if the DevTools hook is not found.\n */\nexport function monitor(options: MonitorOptions = {}): UpdateMonitor | null {\n const {\n silent = false,\n bufferSize = 500,\n filter,\n overlay = true,\n mode = \"self-triggered\",\n showCauses = false,\n flushInterval = 250,\n animationDuration = 1200,\n showLabels = true,\n opacity = 0.3,\n } = options;\n\n const hook = (\n window as unknown as { __REACT_DEVTOOLS_GLOBAL_HOOK__?: DevToolsHook }\n ).__REACT_DEVTOOLS_GLOBAL_HOOK__;\n\n if (!hook) {\n console.warn(\n \"[react-debug-updates] __REACT_DEVTOOLS_GLOBAL_HOOK__ not found. \" +\n \"Make sure React DevTools or a dev build of React is active.\",\n );\n return null;\n }\n\n const highlighter = overlay\n ? createHighlighter({ flushInterval, animationDuration, showLabels, opacity })\n : null;\n\n const entries: RenderEntry[] = [];\n const previousOnCommit = hook.onCommitFiberRoot.bind(hook);\n\n hook.onCommitFiberRoot = (\n rendererID: number,\n root: FiberRoot,\n priorityLevel?: unknown,\n ) => {\n previousOnCommit(rendererID, root, priorityLevel);\n\n // 1. Detect which component fibers actually re-rendered (pure fiber analysis)\n const detectedRenders = detectRenders(root.current, mode);\n if (detectedRenders.length === 0) return;\n\n // 2. Build full entries: resolve names, DOM nodes, causes\n const highlightEntries: HighlightEntry[] = [];\n\n for (let i = 0; i < detectedRenders.length; i++) {\n const { fiber, depth } = detectedRenders[i];\n const name = getComponentName(fiber);\n if (!name) continue;\n\n const highlightEntry: HighlightEntry = {\n component: name,\n path: getFiberPath(fiber),\n duration: fiber.actualDuration ?? 0,\n depth,\n domNode: findNearestDOMNode(fiber),\n causes: showCauses ? detectCauses(fiber) : [],\n };\n\n const renderEntry: RenderEntry = {\n component: highlightEntry.component,\n path: highlightEntry.path,\n duration: highlightEntry.duration,\n timestamp: performance.now(),\n causes: highlightEntry.causes,\n };\n\n if (filter && !filter(renderEntry)) continue;\n\n if (entries.length >= bufferSize) entries.shift();\n entries.push(renderEntry);\n\n highlightEntries.push(highlightEntry);\n highlighter?.push(highlightEntry);\n }\n\n // 3. Console output\n if (!silent) {\n logRerendersToConsole(highlightEntries, showCauses);\n }\n };\n\n const stop = () => {\n hook.onCommitFiberRoot = previousOnCommit;\n highlighter?.dispose();\n disposeAllOverlays();\n };\n\n if (!silent) {\n console.log(\n \"%c⚛ react-debug-updates attached\",\n \"color: #61dafb; font-weight: bold\",\n );\n }\n\n return { entries, stop };\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"react-debug-updates.cjs","sources":["../src/fiber.ts","../src/causes.ts","../src/format.ts","../src/overlay.ts","../src/highlights.ts","../src/monitor.ts"],"sourcesContent":["import type { Fiber, DetectedRender } from \"./types.js\";\n\n// ────────────────────────────────────────────\n// Fiber tag constants\n// ────────────────────────────────────────────\n\nexport const FiberTag = {\n FunctionComponent: 0,\n ClassComponent: 1,\n HostComponent: 5,\n ForwardRef: 11,\n SimpleMemoComponent: 15,\n MemoComponent: 14,\n} as const;\n\n// Component tags we report as re-renders.\n// MemoComponent (14) is excluded to avoid double-reporting — it's a wrapper\n// fiber around the inner component. The inner component (FunctionComponent,\n// ForwardRef, or SimpleMemoComponent) has its own PerformedWork flag and\n// will be reported correctly on its own.\nconst COMPONENT_TAGS = new Set<number>([\n FiberTag.FunctionComponent,\n FiberTag.ClassComponent,\n FiberTag.ForwardRef,\n FiberTag.SimpleMemoComponent,\n]);\n\nconst PerformedWork = 0b0000001;\n\n// ────────────────────────────────────────────\n// Fiber tree helpers\n// ────────────────────────────────────────────\n\nexport function getComponentName(fiber: Fiber): string | null {\n const { type } = fiber;\n if (!type || typeof type === \"string\") return null;\n return type.displayName ?? type.name ?? null;\n}\n\nfunction isHTMLElement(value: unknown): value is HTMLElement {\n return (\n typeof value === \"object\" &&\n value !== null &&\n (value as Node).nodeType === 1 &&\n typeof (value as HTMLElement).getBoundingClientRect === \"function\"\n );\n}\n\nexport function findNearestDOMNode(fiber: Fiber): HTMLElement | null {\n if (fiber.tag === FiberTag.HostComponent && isHTMLElement(fiber.stateNode)) {\n return fiber.stateNode;\n }\n let child: Fiber | null = fiber.child;\n while (child) {\n const found = findNearestDOMNode(child);\n if (found) return found;\n child = child.sibling;\n }\n return null;\n}\n\nexport function getFiberPath(fiber: Fiber, maxDepth = 5): string {\n const parts: string[] = [];\n let current: Fiber | null = fiber.return;\n let depth = 0;\n while (current && depth < maxDepth) {\n const name = getComponentName(current);\n if (name) parts.unshift(name);\n current = current.return;\n depth++;\n }\n return parts.length ? parts.join(\" → \") : \"(root)\";\n}\n\n// ────────────────────────────────────────────\n// Self-triggered detection\n// ────────────────────────────────────────────\n\nfunction isSelfTriggered(fiber: Fiber): boolean {\n const alternate = fiber.alternate;\n if (!alternate) return false;\n return fiber.memoizedProps === alternate.memoizedProps;\n}\n\n// ────────────────────────────────────────────\n// didFiberRender — mirrors React DevTools\n// ────────────────────────────────────────────\n//\n// See: react-devtools-shared/src/backend/fiber/shared/DevToolsFiberChangeDetection.js\n//\n// For component fibers (function, class, memo, forwardRef), React sets the\n// PerformedWork flag (bit 0) only when user code actually executes.\n// createWorkInProgress resets flags to NoFlags, so PerformedWork on a\n// work-in-progress fiber is always fresh — never stale from a prior commit.\n//\n// This check must only be called AFTER confirming prevFiber !== nextFiber\n// (i.e. the fiber was actually processed, not a bailed-out subtree).\n\nfunction didFiberRender(nextFiber: Fiber): boolean {\n return (nextFiber.flags & PerformedWork) === PerformedWork;\n}\n\n// ────────────────────────────────────────────\n// Detect which components re-rendered in a commit\n// ────────────────────────────────────────────\n//\n// Mirrors React DevTools' updateFiberRecursively / updateChildrenRecursively.\n//\n// React double-buffers fibers. After a commit, root.current is the committed\n// tree and root.current.alternate is the previous tree. We walk both in\n// parallel. At each level, if prevChild and nextChild are the same object,\n// React bailed out that entire subtree (via cloneChildFibers) — we skip it.\n// Otherwise, we use nextChild.alternate as the previous fiber and check\n// didFiberRender (PerformedWork) to see if user code actually ran.\n//\n// Returns lightweight DetectedRender objects — just the fiber and its depth.\n// DOM lookup, cause detection, and formatting are the caller's responsibility.\n\nexport function detectRenders(\n root: Fiber,\n mode: \"self-triggered\" | \"all\",\n): DetectedRender[] {\n const results: DetectedRender[] = [];\n const selfTriggeredOnly = mode === \"self-triggered\";\n\n // The alternate of the committed root is the previous tree's root.\n // On initial mount this is null — nothing to report.\n const previousRoot = root.alternate;\n if (!previousRoot) return results;\n\n function walk(\n nextFiber: Fiber,\n previousFiber: Fiber | null,\n depth: number,\n ) {\n // ── Check this fiber ──\n if (\n COMPONENT_TAGS.has(nextFiber.tag) &&\n previousFiber !== null &&\n previousFiber !== nextFiber && // same object → bailed-out subtree\n didFiberRender(nextFiber) &&\n (!selfTriggeredOnly || isSelfTriggered(nextFiber))\n ) {\n results.push({ fiber: nextFiber, depth });\n }\n\n // ── Walk children, matching with previous tree ──\n let nextChild = nextFiber.child;\n let previousChildAtSameIndex = previousFiber?.child ?? null;\n\n while (nextChild) {\n let matchedPrevious: Fiber | null;\n\n if (previousChildAtSameIndex === nextChild) {\n // Same object identity — React shared this fiber via cloneChildFibers.\n // The entire subtree was bailed out; passing the same object as both\n // prev and next causes the prevFiber !== nextFiber guard to skip it.\n matchedPrevious = nextChild;\n } else {\n // Different object — this fiber was processed. The alternate is the\n // corresponding fiber from the previous tree.\n matchedPrevious = nextChild.alternate;\n }\n\n walk(nextChild, matchedPrevious, depth + 1);\n\n nextChild = nextChild.sibling;\n previousChildAtSameIndex = previousChildAtSameIndex?.sibling ?? null;\n }\n }\n\n walk(root, previousRoot, 0);\n return results;\n}\n","import type { Fiber, HookNode, UpdateCause } from \"./types.js\";\nimport { FiberTag } from \"./fiber.js\";\n\n// ────────────────────────────────────────────\n// Update cause detection\n// ────────────────────────────────────────────\n//\n// For function components, React stores hooks as a linked list on\n// fiber.memoizedState. In dev builds, _debugHookTypes lists hook names\n// in call order. We walk both in parallel, comparing memoizedState\n// on each node to find which hook's state actually changed.\n//\n// Caveat: useContext does NOT create a linked list node, so we skip it\n// when advancing the pointer, and detect it by elimination.\n\n/**\n * Hooks whose memoizedState changing can trigger a re-render.\n * We only report these — useEffect/useMemo/etc. don't cause re-renders.\n */\nconst STATE_HOOKS = new Set([\n \"useState\",\n \"useReducer\",\n \"useSyncExternalStore\",\n]);\n\n/**\n * Hooks that do NOT create a node in the memoizedState linked list.\n * Currently only useContext — every other hook allocates a node.\n */\nconst HOOKS_WITHOUT_NODE = new Set([\"useContext\"]);\n\nexport function detectCauses(fiber: Fiber): UpdateCause[] {\n const alternate = fiber.alternate;\n if (!alternate) return [];\n\n const causes: UpdateCause[] = [];\n\n // ── Props changed (parent re-rendered us with new props) ──\n if (fiber.memoizedProps !== alternate.memoizedProps) {\n causes.push({ kind: \"props\" });\n }\n\n // ── Class component ──\n if (fiber.tag === FiberTag.ClassComponent) {\n if (fiber.memoizedState !== alternate.memoizedState) {\n causes.push({ kind: \"class-state\" });\n }\n return causes;\n }\n\n // ── Function component hooks ──\n const hookTypes = fiber._debugHookTypes;\n\n if (!hookTypes) {\n // No debug info (prod build) — best effort\n if (fiber.memoizedState !== alternate.memoizedState) {\n causes.push({ kind: \"unknown\" });\n }\n return causes;\n }\n\n let currentNode = fiber.memoizedState as HookNode | null;\n let previousNode = alternate.memoizedState as HookNode | null;\n let hasContextHook = false;\n let anyStateHookChanged = false;\n\n for (let i = 0; i < hookTypes.length; i++) {\n const type = hookTypes[i];\n\n // useContext has no linked list node — skip pointer advance\n if (HOOKS_WITHOUT_NODE.has(type)) {\n if (type === \"useContext\") hasContextHook = true;\n continue;\n }\n\n if (STATE_HOOKS.has(type) && currentNode && previousNode) {\n if (!Object.is(currentNode.memoizedState, previousNode.memoizedState)) {\n anyStateHookChanged = true;\n causes.push({\n kind: \"hook\",\n hookIndex: i,\n hookType: type,\n previousValue: previousNode.memoizedState,\n nextValue: currentNode.memoizedState,\n });\n }\n }\n\n currentNode = currentNode?.next ?? null;\n previousNode = previousNode?.next ?? null;\n }\n\n // If no state hook changed but component is self-triggered and has\n // useContext → the context value must have changed.\n if (\n hasContextHook &&\n !anyStateHookChanged &&\n fiber.memoizedProps === alternate.memoizedProps\n ) {\n causes.push({ kind: \"hook\", hookType: \"useContext\" });\n }\n\n // If still nothing and self-triggered, mark unknown\n if (causes.length === 0 && fiber.memoizedProps === alternate.memoizedProps) {\n causes.push({ kind: \"unknown\" });\n }\n\n return causes;\n}\n","import type { HighlightEntry, UpdateCause } from \"./types.js\";\n\n// ────────────────────────────────────────────\n// Value formatting\n// ────────────────────────────────────────────\n\nexport function formatValue(value: unknown, maxLength = 50): string {\n if (value === null) return \"null\";\n if (value === undefined) return \"undefined\";\n if (typeof value === \"boolean\") return String(value);\n if (typeof value === \"number\") return String(value);\n if (typeof value === \"function\")\n return `ƒ ${(value as { name?: string }).name || \"anonymous\"}`;\n\n if (typeof value === \"string\") {\n const truncated =\n value.length > maxLength ? value.slice(0, maxLength) + \"…\" : value;\n return JSON.stringify(truncated);\n }\n\n if (Array.isArray(value)) return `Array(${value.length})`;\n\n if (typeof value === \"object\") {\n const name = (value as object).constructor?.name;\n if (name && name !== \"Object\") return name;\n const keys = Object.keys(value as object);\n if (keys.length <= 3) return `{ ${keys.join(\", \")} }`;\n return `{ ${keys.slice(0, 3).join(\", \")}, … }`;\n }\n\n return String(value);\n}\n\n// ────────────────────────────────────────────\n// Cause formatting\n// ────────────────────────────────────────────\n\n/** Compact summary for overlay labels. */\nexport function formatCausesShort(causes: UpdateCause[]): string {\n if (causes.length === 0) return \"\";\n\n const parts: string[] = [];\n for (const cause of causes) {\n if (cause.kind === \"props\") {\n parts.push(\"props\");\n } else if (cause.kind === \"class-state\") {\n parts.push(\"state\");\n } else if (cause.kind === \"hook\" && cause.hookType) {\n const indexSuffix = cause.hookIndex != null ? `[${cause.hookIndex}]` : \"\";\n parts.push(`${cause.hookType}${indexSuffix}`);\n } else {\n parts.push(\"?\");\n }\n }\n\n return parts.join(\", \");\n}\n\n/** Detailed lines for console output. */\nexport function formatCausesConsole(causes: UpdateCause[]): string[] {\n const lines: string[] = [];\n\n for (const cause of causes) {\n if (cause.kind === \"props\") {\n lines.push(\" ↳ props changed (parent re-rendered)\");\n } else if (cause.kind === \"class-state\") {\n lines.push(\" ↳ class state changed\");\n } else if (cause.kind === \"hook\" && cause.hookType) {\n const indexSuffix =\n cause.hookIndex != null ? `[${cause.hookIndex}]` : \"\";\n const name = `${cause.hookType}${indexSuffix}`;\n\n if (cause.previousValue !== undefined && cause.nextValue !== undefined) {\n lines.push(\n ` ↳ ${name}: ${formatValue(cause.previousValue)} → ${formatValue(cause.nextValue)}`,\n );\n } else {\n lines.push(` ↳ ${name} changed`);\n }\n } else {\n lines.push(\" ↳ unknown cause\");\n }\n }\n\n return lines;\n}\n\n// ────────────────────────────────────────────\n// Console output\n// ────────────────────────────────────────────\n\n/** Log re-renders as a collapsed console group. */\nexport function logRerendersToConsole(\n entries: HighlightEntry[],\n showCauses: boolean,\n) {\n if (entries.length === 0) return;\n\n console.groupCollapsed(\n `%c⚛ ${entries.length} re-render${entries.length > 1 ? \"s\" : \"\"}`,\n \"color: #61dafb; font-weight: bold\",\n );\n\n for (let i = 0; i < entries.length; i++) {\n const entry = entries[i];\n const durationText =\n entry.duration > 0 ? ` (${entry.duration.toFixed(2)}ms)` : \"\";\n console.log(\n `%c${entry.component}%c ${entry.path}${durationText}`,\n \"color: #e8e82e; font-weight: bold\",\n \"color: #888\",\n );\n\n if (showCauses && entry.causes.length > 0) {\n const lines = formatCausesConsole(entry.causes);\n for (const line of lines) {\n console.log(`%c${line}`, \"color: #aaa\");\n }\n }\n }\n\n console.groupEnd();\n}\n","// ────────────────────────────────────────────\n// Per-window overlay infrastructure\n// ────────────────────────────────────────────\n//\n// Performance:\n// - Overlay elements pooled per window, reused via animationend\n// - Single CSS @keyframes per window, no JS timers per element\n\nconst ANIMATION_NAME = \"__rdu-fade\";\n\nconst injectedWindows = new WeakSet<Window>();\n\nfunction ensureStylesheet(win: Window) {\n if (injectedWindows.has(win)) return;\n injectedWindows.add(win);\n\n const style = win.document.createElement(\"style\");\n style.textContent = `\n @keyframes ${ANIMATION_NAME} {\n 0% { opacity: 0; }\n 8% { opacity: var(--rdu-opacity, 1); }\n 40% { opacity: var(--rdu-opacity, 1); }\n 100% { opacity: 0; }\n }\n .${ANIMATION_NAME}-box {\n position: fixed;\n pointer-events: none;\n box-sizing: border-box;\n border-radius: 3px;\n opacity: 0;\n will-change: opacity;\n }\n .${ANIMATION_NAME}-label {\n position: absolute;\n top: -18px;\n left: -1px;\n font: 10px/16px ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, monospace;\n padding: 0 4px;\n color: #fff;\n border-radius: 2px 2px 0 0;\n white-space: nowrap;\n pointer-events: none;\n }\n `;\n win.document.head.appendChild(style);\n}\n\n// ────────────────────────────────────────────\n// Overlay root container\n// ────────────────────────────────────────────\n\nconst overlayRoots = new WeakMap<Window, HTMLDivElement>();\n\nfunction getOverlayRoot(win: Window): HTMLDivElement | null {\n if (win.closed) return null;\n\n const existing = overlayRoots.get(win);\n if (existing?.isConnected) return existing;\n\n ensureStylesheet(win);\n\n const root = win.document.createElement(\"div\");\n root.id = \"__react-debug-updates\";\n Object.assign(root.style, {\n position: \"fixed\",\n top: \"0\",\n left: \"0\",\n width: \"0\",\n height: \"0\",\n overflow: \"visible\",\n pointerEvents: \"none\",\n zIndex: \"2147483647\",\n } satisfies Partial<CSSStyleDeclaration>);\n win.document.body.appendChild(root);\n\n overlayRoots.set(win, root);\n return root;\n}\n\n// ────────────────────────────────────────────\n// Element pool\n// ────────────────────────────────────────────\n\nconst pools = new WeakMap<Window, HTMLDivElement[]>();\n\nexport function acquireOverlay(win: Window): HTMLDivElement | null {\n const root = getOverlayRoot(win);\n if (!root) return null;\n\n let pool = pools.get(win);\n if (!pool) {\n pool = [];\n pools.set(win, pool);\n }\n\n const document = win.document;\n let element = pool.pop();\n\n if (!element) {\n element = document.createElement(\"div\");\n element.className = `${ANIMATION_NAME}-box`;\n\n const label = document.createElement(\"span\");\n label.className = `${ANIMATION_NAME}-label`;\n element.appendChild(label);\n\n element.addEventListener(\"animationend\", () => {\n element!.style.animation = \"none\";\n element!.remove();\n pool!.push(element!);\n });\n }\n\n root.appendChild(element);\n return element;\n}\n\nexport function disposeAllOverlays() {\n const mainRoot = overlayRoots.get(window);\n mainRoot?.remove();\n overlayRoots.delete(window);\n pools.delete(window);\n}\n\nexport const OVERLAY_ANIMATION_NAME = ANIMATION_NAME;\n","import type { OverlayConfig, HighlightEntry } from \"./types.js\";\nimport { formatCausesShort } from \"./format.js\";\nimport { acquireOverlay, OVERLAY_ANIMATION_NAME } from \"./overlay.js\";\n\n// ────────────────────────────────────────────\n// Heat color\n// ────────────────────────────────────────────\n\nfunction heatColor(count: number, alpha: number): string {\n const normalizedCount = Math.min((count - 1) / 7, 1);\n const hue = 200 - normalizedCount * 200;\n const saturation = 85 + normalizedCount * 15;\n const lightness = 55 - normalizedCount * 10;\n return `hsla(${hue}, ${saturation}%, ${lightness}%, ${alpha})`;\n}\n\n// ────────────────────────────────────────────\n// Highlight scheduler\n// ────────────────────────────────────────────\n//\n// Commit path just pushes lightweight entries (no DOM reads, no formatting).\n// Flush (setInterval): batched rect reads → batched DOM writes.\n\ninterface CoalescedEntry {\n count: number;\n totalDuration: number;\n component: string;\n /** Shallowest fiber depth among coalesced entries (for z-ordering). */\n depth: number;\n domNode: HTMLElement;\n ownerWindow: Window;\n causeSummary: string;\n}\n\nexport function createHighlighter(config: OverlayConfig) {\n let pending: HighlightEntry[] = [];\n let timer: ReturnType<typeof setInterval> | null = null;\n\n function flush() {\n if (pending.length === 0) return;\n\n const batch = pending;\n pending = [];\n\n // Coalesce by DOM node identity\n const map = new Map<HTMLElement, CoalescedEntry>();\n\n for (let i = 0; i < batch.length; i++) {\n const entry = batch[i];\n if (!entry.domNode) continue;\n\n const existing = map.get(entry.domNode);\n if (existing) {\n existing.count++;\n existing.totalDuration += entry.duration;\n existing.depth = Math.min(existing.depth, entry.depth);\n } else {\n const win = entry.domNode.ownerDocument?.defaultView;\n if (!win || win.closed) continue;\n map.set(entry.domNode, {\n count: 1,\n totalDuration: entry.duration,\n component: entry.component,\n depth: entry.depth,\n domNode: entry.domNode,\n ownerWindow: win,\n causeSummary: formatCausesShort(entry.causes),\n });\n }\n }\n\n // Read phase: batch all rect reads\n const toShow: Array<{ coalesced: CoalescedEntry; rect: DOMRect }> = [];\n for (const coalesced of map.values()) {\n const rect = coalesced.domNode.getBoundingClientRect();\n if (rect.width > 0 || rect.height > 0) {\n toShow.push({ coalesced, rect });\n }\n }\n\n // Sort deepest first so parents are appended last (render on top)\n toShow.sort((a, b) => b.coalesced.depth - a.coalesced.depth);\n\n // Write phase: position overlays\n for (let i = 0; i < toShow.length; i++) {\n const { coalesced, rect } = toShow[i];\n const element = acquireOverlay(coalesced.ownerWindow);\n if (!element) continue;\n\n const fillColor = heatColor(coalesced.count, 0.18);\n const borderColor = heatColor(coalesced.count, 0.75);\n const labelBackground = heatColor(coalesced.count, 0.9);\n\n const style = element.style;\n style.top = `${rect.top}px`;\n style.left = `${rect.left}px`;\n style.width = `${rect.width}px`;\n style.height = `${rect.height}px`;\n style.backgroundColor = fillColor;\n style.border = `1.5px solid ${borderColor}`;\n style.setProperty(\"--rdu-opacity\", String(config.opacity));\n style.animation = `${OVERLAY_ANIMATION_NAME} ${config.animationDuration}ms ease-out forwards`;\n\n const label = element.firstElementChild as HTMLElement;\n if (config.showLabels) {\n const countText = coalesced.count > 1 ? ` ×${coalesced.count}` : \"\";\n const durationText =\n coalesced.totalDuration > 0\n ? ` ${coalesced.totalDuration.toFixed(1)}ms`\n : \"\";\n const causeText = coalesced.causeSummary\n ? ` (${coalesced.causeSummary})`\n : \"\";\n label.textContent = `${coalesced.component}${countText}${durationText}${causeText}`;\n label.style.backgroundColor = labelBackground;\n } else {\n label.textContent = \"\";\n label.style.backgroundColor = \"transparent\";\n }\n }\n }\n\n function push(entry: HighlightEntry) {\n pending.push(entry);\n if (!timer) {\n timer = setInterval(flush, config.flushInterval);\n }\n }\n\n function dispose() {\n if (timer) {\n clearInterval(timer);\n timer = null;\n }\n pending = [];\n }\n\n return { push, dispose };\n}\n","import type {\n DevToolsHook,\n FiberRoot,\n HighlightEntry,\n MonitorOptions,\n} from \"./types.js\";\nimport {\n detectRenders,\n findNearestDOMNode,\n getComponentName,\n getFiberPath,\n} from \"./fiber.js\";\nimport { detectCauses } from \"./causes.js\";\nimport { logRerendersToConsole } from \"./format.js\";\nimport { createHighlighter } from \"./highlights.js\";\nimport { disposeAllOverlays } from \"./overlay.js\";\n\n/**\n * Ensure __REACT_DEVTOOLS_GLOBAL_HOOK__ exists on window.\n *\n * React reads this hook during initialization — if it's missing, React will\n * never connect. When this library is imported before React (the recommended\n * setup), we need to create a minimal hook so React can find and register with it.\n *\n * If a hook already exists (e.g. from the React DevTools extension), we leave\n * it untouched and hook into it.\n */\nfunction ensureDevToolsHook(win: Window): DevToolsHook {\n const global = win as unknown as {\n __REACT_DEVTOOLS_GLOBAL_HOOK__?: DevToolsHook;\n };\n\n if (!global.__REACT_DEVTOOLS_GLOBAL_HOOK__) {\n global.__REACT_DEVTOOLS_GLOBAL_HOOK__ = {\n supportsFiber: true,\n inject() {\n return 1;\n },\n onCommitFiberRoot() {},\n onCommitFiberUnmount() {},\n onPostCommitFiberRoot() {},\n checkDCE() {},\n } as DevToolsHook;\n }\n\n return global.__REACT_DEVTOOLS_GLOBAL_HOOK__;\n}\n\n/**\n * Start monitoring React re-renders.\n *\n * Hooks into `__REACT_DEVTOOLS_GLOBAL_HOOK__.onCommitFiberRoot` to intercept\n * every React commit. Shows visual highlight overlays on re-rendered DOM nodes\n * and optionally logs re-renders to the console.\n *\n * Call this **before** React renders anything — ideally at the very top of\n * your entry point.\n *\n * Returns a `stop` function to unhook and clean up. Throws if called\n * in a non-browser environment (e.g. SSR).\n */\nexport function startReactUpdatesMonitor({\n logToConsole = false,\n highlight = true,\n mode = \"self-triggered\",\n reasonOfUpdate = false,\n highlightFlushInterval = 250,\n highlightAnimationDuration = 1200,\n highlightShowLabels = true,\n highlightOpacity = 0.3,\n}: MonitorOptions = {}): () => void {\n if (typeof window === \"undefined\") {\n throw new Error(\n \"[react-debug-updates] Cannot start monitor in a non-browser environment. \" +\n \"Guard the call with a `typeof window !== 'undefined'` check.\",\n );\n }\n\n const hook = ensureDevToolsHook(window);\n\n const highlighter = highlight\n ? createHighlighter({\n flushInterval: highlightFlushInterval,\n animationDuration: highlightAnimationDuration,\n showLabels: highlightShowLabels,\n opacity: highlightOpacity,\n })\n : null;\n\n const previousOnCommit = hook.onCommitFiberRoot.bind(hook);\n\n hook.onCommitFiberRoot = (\n rendererID: number,\n root: FiberRoot,\n priorityLevel?: unknown,\n ) => {\n previousOnCommit(rendererID, root, priorityLevel);\n\n // 1. Detect which component fibers actually re-rendered (pure fiber analysis)\n const detectedRenders = detectRenders(root.current, mode);\n if (detectedRenders.length === 0) return;\n\n // 2. Build full entries: resolve names, DOM nodes, causes\n const highlightEntries: HighlightEntry[] = [];\n\n for (let i = 0; i < detectedRenders.length; i++) {\n const { fiber, depth } = detectedRenders[i];\n const name = getComponentName(fiber);\n if (!name) continue;\n\n const entry: HighlightEntry = {\n component: name,\n path: getFiberPath(fiber),\n duration: fiber.actualDuration ?? 0,\n depth,\n domNode: findNearestDOMNode(fiber),\n causes: reasonOfUpdate ? detectCauses(fiber) : [],\n };\n\n highlightEntries.push(entry);\n highlighter?.push(entry);\n }\n\n // 3. Console output\n if (logToConsole) {\n logRerendersToConsole(highlightEntries, reasonOfUpdate);\n }\n };\n\n if (logToConsole) {\n console.log(\n \"%c⚛ react-debug-updates attached\",\n \"color: #61dafb; font-weight: bold\",\n );\n }\n\n return () => {\n hook.onCommitFiberRoot = previousOnCommit;\n highlighter?.dispose();\n disposeAllOverlays();\n };\n}\n"],"names":[],"mappings":"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"}
@@ -403,28 +403,48 @@ function createHighlighter(config) {
403
403
  }
404
404
  return { push, dispose };
405
405
  }
406
- function monitor(options = {}) {
407
- const {
408
- silent = false,
409
- bufferSize = 500,
410
- filter,
411
- overlay = true,
412
- mode = "self-triggered",
413
- showCauses = false,
414
- flushInterval = 250,
415
- animationDuration = 1200,
416
- showLabels = true,
417
- opacity = 0.3
418
- } = options;
419
- const hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
420
- if (!hook) {
421
- console.warn(
422
- "[react-debug-updates] __REACT_DEVTOOLS_GLOBAL_HOOK__ not found. Make sure React DevTools or a dev build of React is active."
406
+ function ensureDevToolsHook(win) {
407
+ const global = win;
408
+ if (!global.__REACT_DEVTOOLS_GLOBAL_HOOK__) {
409
+ global.__REACT_DEVTOOLS_GLOBAL_HOOK__ = {
410
+ supportsFiber: true,
411
+ inject() {
412
+ return 1;
413
+ },
414
+ onCommitFiberRoot() {
415
+ },
416
+ onCommitFiberUnmount() {
417
+ },
418
+ onPostCommitFiberRoot() {
419
+ },
420
+ checkDCE() {
421
+ }
422
+ };
423
+ }
424
+ return global.__REACT_DEVTOOLS_GLOBAL_HOOK__;
425
+ }
426
+ function startReactUpdatesMonitor({
427
+ logToConsole = false,
428
+ highlight = true,
429
+ mode = "self-triggered",
430
+ reasonOfUpdate = false,
431
+ highlightFlushInterval = 250,
432
+ highlightAnimationDuration = 1200,
433
+ highlightShowLabels = true,
434
+ highlightOpacity = 0.3
435
+ } = {}) {
436
+ if (typeof window === "undefined") {
437
+ throw new Error(
438
+ "[react-debug-updates] Cannot start monitor in a non-browser environment. Guard the call with a `typeof window !== 'undefined'` check."
423
439
  );
424
- return null;
425
440
  }
426
- const highlighter = overlay ? createHighlighter({ flushInterval, animationDuration, showLabels, opacity }) : null;
427
- const entries = [];
441
+ const hook = ensureDevToolsHook(window);
442
+ const highlighter = highlight ? createHighlighter({
443
+ flushInterval: highlightFlushInterval,
444
+ animationDuration: highlightAnimationDuration,
445
+ showLabels: highlightShowLabels,
446
+ opacity: highlightOpacity
447
+ }) : null;
428
448
  const previousOnCommit = hook.onCommitFiberRoot.bind(hook);
429
449
  hook.onCommitFiberRoot = (rendererID, root, priorityLevel) => {
430
450
  previousOnCommit(rendererID, root, priorityLevel);
@@ -435,45 +455,34 @@ function monitor(options = {}) {
435
455
  const { fiber, depth } = detectedRenders[i];
436
456
  const name = getComponentName(fiber);
437
457
  if (!name) continue;
438
- const highlightEntry = {
458
+ const entry = {
439
459
  component: name,
440
460
  path: getFiberPath(fiber),
441
461
  duration: fiber.actualDuration ?? 0,
442
462
  depth,
443
463
  domNode: findNearestDOMNode(fiber),
444
- causes: showCauses ? detectCauses(fiber) : []
464
+ causes: reasonOfUpdate ? detectCauses(fiber) : []
445
465
  };
446
- const renderEntry = {
447
- component: highlightEntry.component,
448
- path: highlightEntry.path,
449
- duration: highlightEntry.duration,
450
- timestamp: performance.now(),
451
- causes: highlightEntry.causes
452
- };
453
- if (filter && !filter(renderEntry)) continue;
454
- if (entries.length >= bufferSize) entries.shift();
455
- entries.push(renderEntry);
456
- highlightEntries.push(highlightEntry);
457
- highlighter == null ? void 0 : highlighter.push(highlightEntry);
466
+ highlightEntries.push(entry);
467
+ highlighter == null ? void 0 : highlighter.push(entry);
458
468
  }
459
- if (!silent) {
460
- logRerendersToConsole(highlightEntries, showCauses);
469
+ if (logToConsole) {
470
+ logRerendersToConsole(highlightEntries, reasonOfUpdate);
461
471
  }
462
472
  };
463
- const stop = () => {
464
- hook.onCommitFiberRoot = previousOnCommit;
465
- highlighter == null ? void 0 : highlighter.dispose();
466
- disposeAllOverlays();
467
- };
468
- if (!silent) {
473
+ if (logToConsole) {
469
474
  console.log(
470
475
  "%c⚛ react-debug-updates attached",
471
476
  "color: #61dafb; font-weight: bold"
472
477
  );
473
478
  }
474
- return { entries, stop };
479
+ return () => {
480
+ hook.onCommitFiberRoot = previousOnCommit;
481
+ highlighter == null ? void 0 : highlighter.dispose();
482
+ disposeAllOverlays();
483
+ };
475
484
  }
476
485
  export {
477
- monitor
486
+ startReactUpdatesMonitor
478
487
  };
479
488
  //# sourceMappingURL=react-debug-updates.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"react-debug-updates.js","sources":["../src/fiber.ts","../src/causes.ts","../src/format.ts","../src/overlay.ts","../src/highlights.ts","../src/monitor.ts"],"sourcesContent":["import type { Fiber, DetectedRender } from \"./types.js\";\n\n// ────────────────────────────────────────────\n// Fiber tag constants\n// ────────────────────────────────────────────\n\nexport const FiberTag = {\n FunctionComponent: 0,\n ClassComponent: 1,\n HostComponent: 5,\n ForwardRef: 11,\n SimpleMemoComponent: 15,\n MemoComponent: 14,\n} as const;\n\n// Component tags we report as re-renders.\n// MemoComponent (14) is excluded to avoid double-reporting — it's a wrapper\n// fiber around the inner component. The inner component (FunctionComponent,\n// ForwardRef, or SimpleMemoComponent) has its own PerformedWork flag and\n// will be reported correctly on its own.\nconst COMPONENT_TAGS = new Set<number>([\n FiberTag.FunctionComponent,\n FiberTag.ClassComponent,\n FiberTag.ForwardRef,\n FiberTag.SimpleMemoComponent,\n]);\n\nconst PerformedWork = 0b0000001;\n\n// ────────────────────────────────────────────\n// Fiber tree helpers\n// ────────────────────────────────────────────\n\nexport function getComponentName(fiber: Fiber): string | null {\n const { type } = fiber;\n if (!type || typeof type === \"string\") return null;\n return type.displayName ?? type.name ?? null;\n}\n\nfunction isHTMLElement(value: unknown): value is HTMLElement {\n return (\n typeof value === \"object\" &&\n value !== null &&\n (value as Node).nodeType === 1 &&\n typeof (value as HTMLElement).getBoundingClientRect === \"function\"\n );\n}\n\nexport function findNearestDOMNode(fiber: Fiber): HTMLElement | null {\n if (fiber.tag === FiberTag.HostComponent && isHTMLElement(fiber.stateNode)) {\n return fiber.stateNode;\n }\n let child: Fiber | null = fiber.child;\n while (child) {\n const found = findNearestDOMNode(child);\n if (found) return found;\n child = child.sibling;\n }\n return null;\n}\n\nexport function getFiberPath(fiber: Fiber, maxDepth = 5): string {\n const parts: string[] = [];\n let current: Fiber | null = fiber.return;\n let depth = 0;\n while (current && depth < maxDepth) {\n const name = getComponentName(current);\n if (name) parts.unshift(name);\n current = current.return;\n depth++;\n }\n return parts.length ? parts.join(\" → \") : \"(root)\";\n}\n\n// ────────────────────────────────────────────\n// Self-triggered detection\n// ────────────────────────────────────────────\n\nfunction isSelfTriggered(fiber: Fiber): boolean {\n const alternate = fiber.alternate;\n if (!alternate) return false;\n return fiber.memoizedProps === alternate.memoizedProps;\n}\n\n// ────────────────────────────────────────────\n// didFiberRender — mirrors React DevTools\n// ────────────────────────────────────────────\n//\n// See: react-devtools-shared/src/backend/fiber/shared/DevToolsFiberChangeDetection.js\n//\n// For component fibers (function, class, memo, forwardRef), React sets the\n// PerformedWork flag (bit 0) only when user code actually executes.\n// createWorkInProgress resets flags to NoFlags, so PerformedWork on a\n// work-in-progress fiber is always fresh — never stale from a prior commit.\n//\n// This check must only be called AFTER confirming prevFiber !== nextFiber\n// (i.e. the fiber was actually processed, not a bailed-out subtree).\n\nfunction didFiberRender(nextFiber: Fiber): boolean {\n return (nextFiber.flags & PerformedWork) === PerformedWork;\n}\n\n// ────────────────────────────────────────────\n// Detect which components re-rendered in a commit\n// ────────────────────────────────────────────\n//\n// Mirrors React DevTools' updateFiberRecursively / updateChildrenRecursively.\n//\n// React double-buffers fibers. After a commit, root.current is the committed\n// tree and root.current.alternate is the previous tree. We walk both in\n// parallel. At each level, if prevChild and nextChild are the same object,\n// React bailed out that entire subtree (via cloneChildFibers) — we skip it.\n// Otherwise, we use nextChild.alternate as the previous fiber and check\n// didFiberRender (PerformedWork) to see if user code actually ran.\n//\n// Returns lightweight DetectedRender objects — just the fiber and its depth.\n// DOM lookup, cause detection, and formatting are the caller's responsibility.\n\nexport function detectRenders(\n root: Fiber,\n mode: \"self-triggered\" | \"all\",\n): DetectedRender[] {\n const results: DetectedRender[] = [];\n const selfTriggeredOnly = mode === \"self-triggered\";\n\n // The alternate of the committed root is the previous tree's root.\n // On initial mount this is null — nothing to report.\n const previousRoot = root.alternate;\n if (!previousRoot) return results;\n\n function walk(\n nextFiber: Fiber,\n previousFiber: Fiber | null,\n depth: number,\n ) {\n // ── Check this fiber ──\n if (\n COMPONENT_TAGS.has(nextFiber.tag) &&\n previousFiber !== null &&\n previousFiber !== nextFiber && // same object → bailed-out subtree\n didFiberRender(nextFiber) &&\n (!selfTriggeredOnly || isSelfTriggered(nextFiber))\n ) {\n results.push({ fiber: nextFiber, depth });\n }\n\n // ── Walk children, matching with previous tree ──\n let nextChild = nextFiber.child;\n let previousChildAtSameIndex = previousFiber?.child ?? null;\n\n while (nextChild) {\n let matchedPrevious: Fiber | null;\n\n if (previousChildAtSameIndex === nextChild) {\n // Same object identity — React shared this fiber via cloneChildFibers.\n // The entire subtree was bailed out; passing the same object as both\n // prev and next causes the prevFiber !== nextFiber guard to skip it.\n matchedPrevious = nextChild;\n } else {\n // Different object — this fiber was processed. The alternate is the\n // corresponding fiber from the previous tree.\n matchedPrevious = nextChild.alternate;\n }\n\n walk(nextChild, matchedPrevious, depth + 1);\n\n nextChild = nextChild.sibling;\n previousChildAtSameIndex = previousChildAtSameIndex?.sibling ?? null;\n }\n }\n\n walk(root, previousRoot, 0);\n return results;\n}\n","import type { Fiber, HookNode, UpdateCause } from \"./types.js\";\nimport { FiberTag } from \"./fiber.js\";\n\n// ────────────────────────────────────────────\n// Update cause detection\n// ────────────────────────────────────────────\n//\n// For function components, React stores hooks as a linked list on\n// fiber.memoizedState. In dev builds, _debugHookTypes lists hook names\n// in call order. We walk both in parallel, comparing memoizedState\n// on each node to find which hook's state actually changed.\n//\n// Caveat: useContext does NOT create a linked list node, so we skip it\n// when advancing the pointer, and detect it by elimination.\n\n/**\n * Hooks whose memoizedState changing can trigger a re-render.\n * We only report these — useEffect/useMemo/etc. don't cause re-renders.\n */\nconst STATE_HOOKS = new Set([\n \"useState\",\n \"useReducer\",\n \"useSyncExternalStore\",\n]);\n\n/**\n * Hooks that do NOT create a node in the memoizedState linked list.\n * Currently only useContext — every other hook allocates a node.\n */\nconst HOOKS_WITHOUT_NODE = new Set([\"useContext\"]);\n\nexport function detectCauses(fiber: Fiber): UpdateCause[] {\n const alternate = fiber.alternate;\n if (!alternate) return [];\n\n const causes: UpdateCause[] = [];\n\n // ── Props changed (parent re-rendered us with new props) ──\n if (fiber.memoizedProps !== alternate.memoizedProps) {\n causes.push({ kind: \"props\" });\n }\n\n // ── Class component ──\n if (fiber.tag === FiberTag.ClassComponent) {\n if (fiber.memoizedState !== alternate.memoizedState) {\n causes.push({ kind: \"class-state\" });\n }\n return causes;\n }\n\n // ── Function component hooks ──\n const hookTypes = fiber._debugHookTypes;\n\n if (!hookTypes) {\n // No debug info (prod build) — best effort\n if (fiber.memoizedState !== alternate.memoizedState) {\n causes.push({ kind: \"unknown\" });\n }\n return causes;\n }\n\n let currentNode = fiber.memoizedState as HookNode | null;\n let previousNode = alternate.memoizedState as HookNode | null;\n let hasContextHook = false;\n let anyStateHookChanged = false;\n\n for (let i = 0; i < hookTypes.length; i++) {\n const type = hookTypes[i];\n\n // useContext has no linked list node — skip pointer advance\n if (HOOKS_WITHOUT_NODE.has(type)) {\n if (type === \"useContext\") hasContextHook = true;\n continue;\n }\n\n if (STATE_HOOKS.has(type) && currentNode && previousNode) {\n if (!Object.is(currentNode.memoizedState, previousNode.memoizedState)) {\n anyStateHookChanged = true;\n causes.push({\n kind: \"hook\",\n hookIndex: i,\n hookType: type,\n previousValue: previousNode.memoizedState,\n nextValue: currentNode.memoizedState,\n });\n }\n }\n\n currentNode = currentNode?.next ?? null;\n previousNode = previousNode?.next ?? null;\n }\n\n // If no state hook changed but component is self-triggered and has\n // useContext → the context value must have changed.\n if (\n hasContextHook &&\n !anyStateHookChanged &&\n fiber.memoizedProps === alternate.memoizedProps\n ) {\n causes.push({ kind: \"hook\", hookType: \"useContext\" });\n }\n\n // If still nothing and self-triggered, mark unknown\n if (causes.length === 0 && fiber.memoizedProps === alternate.memoizedProps) {\n causes.push({ kind: \"unknown\" });\n }\n\n return causes;\n}\n","import type { HighlightEntry, UpdateCause } from \"./types.js\";\n\n// ────────────────────────────────────────────\n// Value formatting\n// ────────────────────────────────────────────\n\nexport function formatValue(value: unknown, maxLength = 50): string {\n if (value === null) return \"null\";\n if (value === undefined) return \"undefined\";\n if (typeof value === \"boolean\") return String(value);\n if (typeof value === \"number\") return String(value);\n if (typeof value === \"function\")\n return `ƒ ${(value as { name?: string }).name || \"anonymous\"}`;\n\n if (typeof value === \"string\") {\n const truncated =\n value.length > maxLength ? value.slice(0, maxLength) + \"…\" : value;\n return JSON.stringify(truncated);\n }\n\n if (Array.isArray(value)) return `Array(${value.length})`;\n\n if (typeof value === \"object\") {\n const name = (value as object).constructor?.name;\n if (name && name !== \"Object\") return name;\n const keys = Object.keys(value as object);\n if (keys.length <= 3) return `{ ${keys.join(\", \")} }`;\n return `{ ${keys.slice(0, 3).join(\", \")}, … }`;\n }\n\n return String(value);\n}\n\n// ────────────────────────────────────────────\n// Cause formatting\n// ────────────────────────────────────────────\n\n/** Compact summary for overlay labels. */\nexport function formatCausesShort(causes: UpdateCause[]): string {\n if (causes.length === 0) return \"\";\n\n const parts: string[] = [];\n for (const cause of causes) {\n if (cause.kind === \"props\") {\n parts.push(\"props\");\n } else if (cause.kind === \"class-state\") {\n parts.push(\"state\");\n } else if (cause.kind === \"hook\" && cause.hookType) {\n const indexSuffix = cause.hookIndex != null ? `[${cause.hookIndex}]` : \"\";\n parts.push(`${cause.hookType}${indexSuffix}`);\n } else {\n parts.push(\"?\");\n }\n }\n\n return parts.join(\", \");\n}\n\n/** Detailed lines for console output. */\nexport function formatCausesConsole(causes: UpdateCause[]): string[] {\n const lines: string[] = [];\n\n for (const cause of causes) {\n if (cause.kind === \"props\") {\n lines.push(\" ↳ props changed (parent re-rendered)\");\n } else if (cause.kind === \"class-state\") {\n lines.push(\" ↳ class state changed\");\n } else if (cause.kind === \"hook\" && cause.hookType) {\n const indexSuffix =\n cause.hookIndex != null ? `[${cause.hookIndex}]` : \"\";\n const name = `${cause.hookType}${indexSuffix}`;\n\n if (cause.previousValue !== undefined && cause.nextValue !== undefined) {\n lines.push(\n ` ↳ ${name}: ${formatValue(cause.previousValue)} → ${formatValue(cause.nextValue)}`,\n );\n } else {\n lines.push(` ↳ ${name} changed`);\n }\n } else {\n lines.push(\" ↳ unknown cause\");\n }\n }\n\n return lines;\n}\n\n// ────────────────────────────────────────────\n// Console output\n// ────────────────────────────────────────────\n\n/** Log re-renders as a collapsed console group. */\nexport function logRerendersToConsole(\n entries: HighlightEntry[],\n showCauses: boolean,\n) {\n if (entries.length === 0) return;\n\n console.groupCollapsed(\n `%c⚛ ${entries.length} re-render${entries.length > 1 ? \"s\" : \"\"}`,\n \"color: #61dafb; font-weight: bold\",\n );\n\n for (let i = 0; i < entries.length; i++) {\n const entry = entries[i];\n const durationText =\n entry.duration > 0 ? ` (${entry.duration.toFixed(2)}ms)` : \"\";\n console.log(\n `%c${entry.component}%c ${entry.path}${durationText}`,\n \"color: #e8e82e; font-weight: bold\",\n \"color: #888\",\n );\n\n if (showCauses && entry.causes.length > 0) {\n const lines = formatCausesConsole(entry.causes);\n for (const line of lines) {\n console.log(`%c${line}`, \"color: #aaa\");\n }\n }\n }\n\n console.groupEnd();\n}\n","// ────────────────────────────────────────────\n// Per-window overlay infrastructure\n// ────────────────────────────────────────────\n//\n// Performance:\n// - Overlay elements pooled per window, reused via animationend\n// - Single CSS @keyframes per window, no JS timers per element\n\nconst ANIMATION_NAME = \"__rdu-fade\";\n\nconst injectedWindows = new WeakSet<Window>();\n\nfunction ensureStylesheet(win: Window) {\n if (injectedWindows.has(win)) return;\n injectedWindows.add(win);\n\n const style = win.document.createElement(\"style\");\n style.textContent = `\n @keyframes ${ANIMATION_NAME} {\n 0% { opacity: 0; }\n 8% { opacity: var(--rdu-opacity, 1); }\n 40% { opacity: var(--rdu-opacity, 1); }\n 100% { opacity: 0; }\n }\n .${ANIMATION_NAME}-box {\n position: fixed;\n pointer-events: none;\n box-sizing: border-box;\n border-radius: 3px;\n opacity: 0;\n will-change: opacity;\n }\n .${ANIMATION_NAME}-label {\n position: absolute;\n top: -18px;\n left: -1px;\n font: 10px/16px ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, monospace;\n padding: 0 4px;\n color: #fff;\n border-radius: 2px 2px 0 0;\n white-space: nowrap;\n pointer-events: none;\n }\n `;\n win.document.head.appendChild(style);\n}\n\n// ────────────────────────────────────────────\n// Overlay root container\n// ────────────────────────────────────────────\n\nconst overlayRoots = new WeakMap<Window, HTMLDivElement>();\n\nfunction getOverlayRoot(win: Window): HTMLDivElement | null {\n if (win.closed) return null;\n\n const existing = overlayRoots.get(win);\n if (existing?.isConnected) return existing;\n\n ensureStylesheet(win);\n\n const root = win.document.createElement(\"div\");\n root.id = \"__react-debug-updates\";\n Object.assign(root.style, {\n position: \"fixed\",\n top: \"0\",\n left: \"0\",\n width: \"0\",\n height: \"0\",\n overflow: \"visible\",\n pointerEvents: \"none\",\n zIndex: \"2147483647\",\n } satisfies Partial<CSSStyleDeclaration>);\n win.document.body.appendChild(root);\n\n overlayRoots.set(win, root);\n return root;\n}\n\n// ────────────────────────────────────────────\n// Element pool\n// ────────────────────────────────────────────\n\nconst pools = new WeakMap<Window, HTMLDivElement[]>();\n\nexport function acquireOverlay(win: Window): HTMLDivElement | null {\n const root = getOverlayRoot(win);\n if (!root) return null;\n\n let pool = pools.get(win);\n if (!pool) {\n pool = [];\n pools.set(win, pool);\n }\n\n const document = win.document;\n let element = pool.pop();\n\n if (!element) {\n element = document.createElement(\"div\");\n element.className = `${ANIMATION_NAME}-box`;\n\n const label = document.createElement(\"span\");\n label.className = `${ANIMATION_NAME}-label`;\n element.appendChild(label);\n\n element.addEventListener(\"animationend\", () => {\n element!.style.animation = \"none\";\n element!.remove();\n pool!.push(element!);\n });\n }\n\n root.appendChild(element);\n return element;\n}\n\nexport function disposeAllOverlays() {\n const mainRoot = overlayRoots.get(window);\n mainRoot?.remove();\n overlayRoots.delete(window);\n pools.delete(window);\n}\n\nexport const OVERLAY_ANIMATION_NAME = ANIMATION_NAME;\n","import type { OverlayConfig, HighlightEntry } from \"./types.js\";\nimport { formatCausesShort } from \"./format.js\";\nimport { acquireOverlay, OVERLAY_ANIMATION_NAME } from \"./overlay.js\";\n\n// ────────────────────────────────────────────\n// Heat color\n// ────────────────────────────────────────────\n\nfunction heatColor(count: number, alpha: number): string {\n const normalizedCount = Math.min((count - 1) / 7, 1);\n const hue = 200 - normalizedCount * 200;\n const saturation = 85 + normalizedCount * 15;\n const lightness = 55 - normalizedCount * 10;\n return `hsla(${hue}, ${saturation}%, ${lightness}%, ${alpha})`;\n}\n\n// ────────────────────────────────────────────\n// Highlight scheduler\n// ────────────────────────────────────────────\n//\n// Commit path just pushes lightweight entries (no DOM reads, no formatting).\n// Flush (setInterval): batched rect reads → batched DOM writes.\n\ninterface CoalescedEntry {\n count: number;\n totalDuration: number;\n component: string;\n /** Shallowest fiber depth among coalesced entries (for z-ordering). */\n depth: number;\n domNode: HTMLElement;\n ownerWindow: Window;\n causeSummary: string;\n}\n\nexport function createHighlighter(config: OverlayConfig) {\n let pending: HighlightEntry[] = [];\n let timer: ReturnType<typeof setInterval> | null = null;\n\n function flush() {\n if (pending.length === 0) return;\n\n const batch = pending;\n pending = [];\n\n // Coalesce by DOM node identity\n const map = new Map<HTMLElement, CoalescedEntry>();\n\n for (let i = 0; i < batch.length; i++) {\n const entry = batch[i];\n if (!entry.domNode) continue;\n\n const existing = map.get(entry.domNode);\n if (existing) {\n existing.count++;\n existing.totalDuration += entry.duration;\n existing.depth = Math.min(existing.depth, entry.depth);\n } else {\n const win = entry.domNode.ownerDocument?.defaultView;\n if (!win || win.closed) continue;\n map.set(entry.domNode, {\n count: 1,\n totalDuration: entry.duration,\n component: entry.component,\n depth: entry.depth,\n domNode: entry.domNode,\n ownerWindow: win,\n causeSummary: formatCausesShort(entry.causes),\n });\n }\n }\n\n // Read phase: batch all rect reads\n const toShow: Array<{ coalesced: CoalescedEntry; rect: DOMRect }> = [];\n for (const coalesced of map.values()) {\n const rect = coalesced.domNode.getBoundingClientRect();\n if (rect.width > 0 || rect.height > 0) {\n toShow.push({ coalesced, rect });\n }\n }\n\n // Sort deepest first so parents are appended last (render on top)\n toShow.sort((a, b) => b.coalesced.depth - a.coalesced.depth);\n\n // Write phase: position overlays\n for (let i = 0; i < toShow.length; i++) {\n const { coalesced, rect } = toShow[i];\n const element = acquireOverlay(coalesced.ownerWindow);\n if (!element) continue;\n\n const fillColor = heatColor(coalesced.count, 0.18);\n const borderColor = heatColor(coalesced.count, 0.75);\n const labelBackground = heatColor(coalesced.count, 0.9);\n\n const style = element.style;\n style.top = `${rect.top}px`;\n style.left = `${rect.left}px`;\n style.width = `${rect.width}px`;\n style.height = `${rect.height}px`;\n style.backgroundColor = fillColor;\n style.border = `1.5px solid ${borderColor}`;\n style.setProperty(\"--rdu-opacity\", String(config.opacity));\n style.animation = `${OVERLAY_ANIMATION_NAME} ${config.animationDuration}ms ease-out forwards`;\n\n const label = element.firstElementChild as HTMLElement;\n if (config.showLabels) {\n const countText = coalesced.count > 1 ? ` ×${coalesced.count}` : \"\";\n const durationText =\n coalesced.totalDuration > 0\n ? ` ${coalesced.totalDuration.toFixed(1)}ms`\n : \"\";\n const causeText = coalesced.causeSummary\n ? ` (${coalesced.causeSummary})`\n : \"\";\n label.textContent = `${coalesced.component}${countText}${durationText}${causeText}`;\n label.style.backgroundColor = labelBackground;\n } else {\n label.textContent = \"\";\n label.style.backgroundColor = \"transparent\";\n }\n }\n }\n\n function push(entry: HighlightEntry) {\n pending.push(entry);\n if (!timer) {\n timer = setInterval(flush, config.flushInterval);\n }\n }\n\n function dispose() {\n if (timer) {\n clearInterval(timer);\n timer = null;\n }\n pending = [];\n }\n\n return { push, dispose };\n}\n","import type {\n DevToolsHook,\n FiberRoot,\n HighlightEntry,\n MonitorOptions,\n RenderEntry,\n UpdateMonitor,\n} from \"./types.js\";\nimport {\n detectRenders,\n findNearestDOMNode,\n getComponentName,\n getFiberPath,\n} from \"./fiber.js\";\nimport { detectCauses } from \"./causes.js\";\nimport { logRerendersToConsole } from \"./format.js\";\nimport { createHighlighter } from \"./highlights.js\";\nimport { disposeAllOverlays } from \"./overlay.js\";\n\n/**\n * Start monitoring React re-renders.\n *\n * Hooks into `__REACT_DEVTOOLS_GLOBAL_HOOK__.onCommitFiberRoot` to intercept\n * every React commit. Records re-render entries, optionally logs them to the\n * console, and optionally shows visual highlight overlays on re-rendered DOM nodes.\n *\n * Call this **before** React renders anything — ideally at the very top of\n * your entry point.\n *\n * Returns an `UpdateMonitor` handle, or `null` if the DevTools hook is not found.\n */\nexport function monitor(options: MonitorOptions = {}): UpdateMonitor | null {\n const {\n silent = false,\n bufferSize = 500,\n filter,\n overlay = true,\n mode = \"self-triggered\",\n showCauses = false,\n flushInterval = 250,\n animationDuration = 1200,\n showLabels = true,\n opacity = 0.3,\n } = options;\n\n const hook = (\n window as unknown as { __REACT_DEVTOOLS_GLOBAL_HOOK__?: DevToolsHook }\n ).__REACT_DEVTOOLS_GLOBAL_HOOK__;\n\n if (!hook) {\n console.warn(\n \"[react-debug-updates] __REACT_DEVTOOLS_GLOBAL_HOOK__ not found. \" +\n \"Make sure React DevTools or a dev build of React is active.\",\n );\n return null;\n }\n\n const highlighter = overlay\n ? createHighlighter({ flushInterval, animationDuration, showLabels, opacity })\n : null;\n\n const entries: RenderEntry[] = [];\n const previousOnCommit = hook.onCommitFiberRoot.bind(hook);\n\n hook.onCommitFiberRoot = (\n rendererID: number,\n root: FiberRoot,\n priorityLevel?: unknown,\n ) => {\n previousOnCommit(rendererID, root, priorityLevel);\n\n // 1. Detect which component fibers actually re-rendered (pure fiber analysis)\n const detectedRenders = detectRenders(root.current, mode);\n if (detectedRenders.length === 0) return;\n\n // 2. Build full entries: resolve names, DOM nodes, causes\n const highlightEntries: HighlightEntry[] = [];\n\n for (let i = 0; i < detectedRenders.length; i++) {\n const { fiber, depth } = detectedRenders[i];\n const name = getComponentName(fiber);\n if (!name) continue;\n\n const highlightEntry: HighlightEntry = {\n component: name,\n path: getFiberPath(fiber),\n duration: fiber.actualDuration ?? 0,\n depth,\n domNode: findNearestDOMNode(fiber),\n causes: showCauses ? detectCauses(fiber) : [],\n };\n\n const renderEntry: RenderEntry = {\n component: highlightEntry.component,\n path: highlightEntry.path,\n duration: highlightEntry.duration,\n timestamp: performance.now(),\n causes: highlightEntry.causes,\n };\n\n if (filter && !filter(renderEntry)) continue;\n\n if (entries.length >= bufferSize) entries.shift();\n entries.push(renderEntry);\n\n highlightEntries.push(highlightEntry);\n highlighter?.push(highlightEntry);\n }\n\n // 3. Console output\n if (!silent) {\n logRerendersToConsole(highlightEntries, showCauses);\n }\n };\n\n const stop = () => {\n hook.onCommitFiberRoot = previousOnCommit;\n highlighter?.dispose();\n disposeAllOverlays();\n };\n\n if (!silent) {\n console.log(\n \"%c⚛ react-debug-updates attached\",\n \"color: #61dafb; font-weight: bold\",\n );\n }\n\n return { entries, stop };\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"react-debug-updates.js","sources":["../src/fiber.ts","../src/causes.ts","../src/format.ts","../src/overlay.ts","../src/highlights.ts","../src/monitor.ts"],"sourcesContent":["import type { Fiber, DetectedRender } from \"./types.js\";\n\n// ────────────────────────────────────────────\n// Fiber tag constants\n// ────────────────────────────────────────────\n\nexport const FiberTag = {\n FunctionComponent: 0,\n ClassComponent: 1,\n HostComponent: 5,\n ForwardRef: 11,\n SimpleMemoComponent: 15,\n MemoComponent: 14,\n} as const;\n\n// Component tags we report as re-renders.\n// MemoComponent (14) is excluded to avoid double-reporting — it's a wrapper\n// fiber around the inner component. The inner component (FunctionComponent,\n// ForwardRef, or SimpleMemoComponent) has its own PerformedWork flag and\n// will be reported correctly on its own.\nconst COMPONENT_TAGS = new Set<number>([\n FiberTag.FunctionComponent,\n FiberTag.ClassComponent,\n FiberTag.ForwardRef,\n FiberTag.SimpleMemoComponent,\n]);\n\nconst PerformedWork = 0b0000001;\n\n// ────────────────────────────────────────────\n// Fiber tree helpers\n// ────────────────────────────────────────────\n\nexport function getComponentName(fiber: Fiber): string | null {\n const { type } = fiber;\n if (!type || typeof type === \"string\") return null;\n return type.displayName ?? type.name ?? null;\n}\n\nfunction isHTMLElement(value: unknown): value is HTMLElement {\n return (\n typeof value === \"object\" &&\n value !== null &&\n (value as Node).nodeType === 1 &&\n typeof (value as HTMLElement).getBoundingClientRect === \"function\"\n );\n}\n\nexport function findNearestDOMNode(fiber: Fiber): HTMLElement | null {\n if (fiber.tag === FiberTag.HostComponent && isHTMLElement(fiber.stateNode)) {\n return fiber.stateNode;\n }\n let child: Fiber | null = fiber.child;\n while (child) {\n const found = findNearestDOMNode(child);\n if (found) return found;\n child = child.sibling;\n }\n return null;\n}\n\nexport function getFiberPath(fiber: Fiber, maxDepth = 5): string {\n const parts: string[] = [];\n let current: Fiber | null = fiber.return;\n let depth = 0;\n while (current && depth < maxDepth) {\n const name = getComponentName(current);\n if (name) parts.unshift(name);\n current = current.return;\n depth++;\n }\n return parts.length ? parts.join(\" → \") : \"(root)\";\n}\n\n// ────────────────────────────────────────────\n// Self-triggered detection\n// ────────────────────────────────────────────\n\nfunction isSelfTriggered(fiber: Fiber): boolean {\n const alternate = fiber.alternate;\n if (!alternate) return false;\n return fiber.memoizedProps === alternate.memoizedProps;\n}\n\n// ────────────────────────────────────────────\n// didFiberRender — mirrors React DevTools\n// ────────────────────────────────────────────\n//\n// See: react-devtools-shared/src/backend/fiber/shared/DevToolsFiberChangeDetection.js\n//\n// For component fibers (function, class, memo, forwardRef), React sets the\n// PerformedWork flag (bit 0) only when user code actually executes.\n// createWorkInProgress resets flags to NoFlags, so PerformedWork on a\n// work-in-progress fiber is always fresh — never stale from a prior commit.\n//\n// This check must only be called AFTER confirming prevFiber !== nextFiber\n// (i.e. the fiber was actually processed, not a bailed-out subtree).\n\nfunction didFiberRender(nextFiber: Fiber): boolean {\n return (nextFiber.flags & PerformedWork) === PerformedWork;\n}\n\n// ────────────────────────────────────────────\n// Detect which components re-rendered in a commit\n// ────────────────────────────────────────────\n//\n// Mirrors React DevTools' updateFiberRecursively / updateChildrenRecursively.\n//\n// React double-buffers fibers. After a commit, root.current is the committed\n// tree and root.current.alternate is the previous tree. We walk both in\n// parallel. At each level, if prevChild and nextChild are the same object,\n// React bailed out that entire subtree (via cloneChildFibers) — we skip it.\n// Otherwise, we use nextChild.alternate as the previous fiber and check\n// didFiberRender (PerformedWork) to see if user code actually ran.\n//\n// Returns lightweight DetectedRender objects — just the fiber and its depth.\n// DOM lookup, cause detection, and formatting are the caller's responsibility.\n\nexport function detectRenders(\n root: Fiber,\n mode: \"self-triggered\" | \"all\",\n): DetectedRender[] {\n const results: DetectedRender[] = [];\n const selfTriggeredOnly = mode === \"self-triggered\";\n\n // The alternate of the committed root is the previous tree's root.\n // On initial mount this is null — nothing to report.\n const previousRoot = root.alternate;\n if (!previousRoot) return results;\n\n function walk(\n nextFiber: Fiber,\n previousFiber: Fiber | null,\n depth: number,\n ) {\n // ── Check this fiber ──\n if (\n COMPONENT_TAGS.has(nextFiber.tag) &&\n previousFiber !== null &&\n previousFiber !== nextFiber && // same object → bailed-out subtree\n didFiberRender(nextFiber) &&\n (!selfTriggeredOnly || isSelfTriggered(nextFiber))\n ) {\n results.push({ fiber: nextFiber, depth });\n }\n\n // ── Walk children, matching with previous tree ──\n let nextChild = nextFiber.child;\n let previousChildAtSameIndex = previousFiber?.child ?? null;\n\n while (nextChild) {\n let matchedPrevious: Fiber | null;\n\n if (previousChildAtSameIndex === nextChild) {\n // Same object identity — React shared this fiber via cloneChildFibers.\n // The entire subtree was bailed out; passing the same object as both\n // prev and next causes the prevFiber !== nextFiber guard to skip it.\n matchedPrevious = nextChild;\n } else {\n // Different object — this fiber was processed. The alternate is the\n // corresponding fiber from the previous tree.\n matchedPrevious = nextChild.alternate;\n }\n\n walk(nextChild, matchedPrevious, depth + 1);\n\n nextChild = nextChild.sibling;\n previousChildAtSameIndex = previousChildAtSameIndex?.sibling ?? null;\n }\n }\n\n walk(root, previousRoot, 0);\n return results;\n}\n","import type { Fiber, HookNode, UpdateCause } from \"./types.js\";\nimport { FiberTag } from \"./fiber.js\";\n\n// ────────────────────────────────────────────\n// Update cause detection\n// ────────────────────────────────────────────\n//\n// For function components, React stores hooks as a linked list on\n// fiber.memoizedState. In dev builds, _debugHookTypes lists hook names\n// in call order. We walk both in parallel, comparing memoizedState\n// on each node to find which hook's state actually changed.\n//\n// Caveat: useContext does NOT create a linked list node, so we skip it\n// when advancing the pointer, and detect it by elimination.\n\n/**\n * Hooks whose memoizedState changing can trigger a re-render.\n * We only report these — useEffect/useMemo/etc. don't cause re-renders.\n */\nconst STATE_HOOKS = new Set([\n \"useState\",\n \"useReducer\",\n \"useSyncExternalStore\",\n]);\n\n/**\n * Hooks that do NOT create a node in the memoizedState linked list.\n * Currently only useContext — every other hook allocates a node.\n */\nconst HOOKS_WITHOUT_NODE = new Set([\"useContext\"]);\n\nexport function detectCauses(fiber: Fiber): UpdateCause[] {\n const alternate = fiber.alternate;\n if (!alternate) return [];\n\n const causes: UpdateCause[] = [];\n\n // ── Props changed (parent re-rendered us with new props) ──\n if (fiber.memoizedProps !== alternate.memoizedProps) {\n causes.push({ kind: \"props\" });\n }\n\n // ── Class component ──\n if (fiber.tag === FiberTag.ClassComponent) {\n if (fiber.memoizedState !== alternate.memoizedState) {\n causes.push({ kind: \"class-state\" });\n }\n return causes;\n }\n\n // ── Function component hooks ──\n const hookTypes = fiber._debugHookTypes;\n\n if (!hookTypes) {\n // No debug info (prod build) — best effort\n if (fiber.memoizedState !== alternate.memoizedState) {\n causes.push({ kind: \"unknown\" });\n }\n return causes;\n }\n\n let currentNode = fiber.memoizedState as HookNode | null;\n let previousNode = alternate.memoizedState as HookNode | null;\n let hasContextHook = false;\n let anyStateHookChanged = false;\n\n for (let i = 0; i < hookTypes.length; i++) {\n const type = hookTypes[i];\n\n // useContext has no linked list node — skip pointer advance\n if (HOOKS_WITHOUT_NODE.has(type)) {\n if (type === \"useContext\") hasContextHook = true;\n continue;\n }\n\n if (STATE_HOOKS.has(type) && currentNode && previousNode) {\n if (!Object.is(currentNode.memoizedState, previousNode.memoizedState)) {\n anyStateHookChanged = true;\n causes.push({\n kind: \"hook\",\n hookIndex: i,\n hookType: type,\n previousValue: previousNode.memoizedState,\n nextValue: currentNode.memoizedState,\n });\n }\n }\n\n currentNode = currentNode?.next ?? null;\n previousNode = previousNode?.next ?? null;\n }\n\n // If no state hook changed but component is self-triggered and has\n // useContext → the context value must have changed.\n if (\n hasContextHook &&\n !anyStateHookChanged &&\n fiber.memoizedProps === alternate.memoizedProps\n ) {\n causes.push({ kind: \"hook\", hookType: \"useContext\" });\n }\n\n // If still nothing and self-triggered, mark unknown\n if (causes.length === 0 && fiber.memoizedProps === alternate.memoizedProps) {\n causes.push({ kind: \"unknown\" });\n }\n\n return causes;\n}\n","import type { HighlightEntry, UpdateCause } from \"./types.js\";\n\n// ────────────────────────────────────────────\n// Value formatting\n// ────────────────────────────────────────────\n\nexport function formatValue(value: unknown, maxLength = 50): string {\n if (value === null) return \"null\";\n if (value === undefined) return \"undefined\";\n if (typeof value === \"boolean\") return String(value);\n if (typeof value === \"number\") return String(value);\n if (typeof value === \"function\")\n return `ƒ ${(value as { name?: string }).name || \"anonymous\"}`;\n\n if (typeof value === \"string\") {\n const truncated =\n value.length > maxLength ? value.slice(0, maxLength) + \"…\" : value;\n return JSON.stringify(truncated);\n }\n\n if (Array.isArray(value)) return `Array(${value.length})`;\n\n if (typeof value === \"object\") {\n const name = (value as object).constructor?.name;\n if (name && name !== \"Object\") return name;\n const keys = Object.keys(value as object);\n if (keys.length <= 3) return `{ ${keys.join(\", \")} }`;\n return `{ ${keys.slice(0, 3).join(\", \")}, … }`;\n }\n\n return String(value);\n}\n\n// ────────────────────────────────────────────\n// Cause formatting\n// ────────────────────────────────────────────\n\n/** Compact summary for overlay labels. */\nexport function formatCausesShort(causes: UpdateCause[]): string {\n if (causes.length === 0) return \"\";\n\n const parts: string[] = [];\n for (const cause of causes) {\n if (cause.kind === \"props\") {\n parts.push(\"props\");\n } else if (cause.kind === \"class-state\") {\n parts.push(\"state\");\n } else if (cause.kind === \"hook\" && cause.hookType) {\n const indexSuffix = cause.hookIndex != null ? `[${cause.hookIndex}]` : \"\";\n parts.push(`${cause.hookType}${indexSuffix}`);\n } else {\n parts.push(\"?\");\n }\n }\n\n return parts.join(\", \");\n}\n\n/** Detailed lines for console output. */\nexport function formatCausesConsole(causes: UpdateCause[]): string[] {\n const lines: string[] = [];\n\n for (const cause of causes) {\n if (cause.kind === \"props\") {\n lines.push(\" ↳ props changed (parent re-rendered)\");\n } else if (cause.kind === \"class-state\") {\n lines.push(\" ↳ class state changed\");\n } else if (cause.kind === \"hook\" && cause.hookType) {\n const indexSuffix =\n cause.hookIndex != null ? `[${cause.hookIndex}]` : \"\";\n const name = `${cause.hookType}${indexSuffix}`;\n\n if (cause.previousValue !== undefined && cause.nextValue !== undefined) {\n lines.push(\n ` ↳ ${name}: ${formatValue(cause.previousValue)} → ${formatValue(cause.nextValue)}`,\n );\n } else {\n lines.push(` ↳ ${name} changed`);\n }\n } else {\n lines.push(\" ↳ unknown cause\");\n }\n }\n\n return lines;\n}\n\n// ────────────────────────────────────────────\n// Console output\n// ────────────────────────────────────────────\n\n/** Log re-renders as a collapsed console group. */\nexport function logRerendersToConsole(\n entries: HighlightEntry[],\n showCauses: boolean,\n) {\n if (entries.length === 0) return;\n\n console.groupCollapsed(\n `%c⚛ ${entries.length} re-render${entries.length > 1 ? \"s\" : \"\"}`,\n \"color: #61dafb; font-weight: bold\",\n );\n\n for (let i = 0; i < entries.length; i++) {\n const entry = entries[i];\n const durationText =\n entry.duration > 0 ? ` (${entry.duration.toFixed(2)}ms)` : \"\";\n console.log(\n `%c${entry.component}%c ${entry.path}${durationText}`,\n \"color: #e8e82e; font-weight: bold\",\n \"color: #888\",\n );\n\n if (showCauses && entry.causes.length > 0) {\n const lines = formatCausesConsole(entry.causes);\n for (const line of lines) {\n console.log(`%c${line}`, \"color: #aaa\");\n }\n }\n }\n\n console.groupEnd();\n}\n","// ────────────────────────────────────────────\n// Per-window overlay infrastructure\n// ────────────────────────────────────────────\n//\n// Performance:\n// - Overlay elements pooled per window, reused via animationend\n// - Single CSS @keyframes per window, no JS timers per element\n\nconst ANIMATION_NAME = \"__rdu-fade\";\n\nconst injectedWindows = new WeakSet<Window>();\n\nfunction ensureStylesheet(win: Window) {\n if (injectedWindows.has(win)) return;\n injectedWindows.add(win);\n\n const style = win.document.createElement(\"style\");\n style.textContent = `\n @keyframes ${ANIMATION_NAME} {\n 0% { opacity: 0; }\n 8% { opacity: var(--rdu-opacity, 1); }\n 40% { opacity: var(--rdu-opacity, 1); }\n 100% { opacity: 0; }\n }\n .${ANIMATION_NAME}-box {\n position: fixed;\n pointer-events: none;\n box-sizing: border-box;\n border-radius: 3px;\n opacity: 0;\n will-change: opacity;\n }\n .${ANIMATION_NAME}-label {\n position: absolute;\n top: -18px;\n left: -1px;\n font: 10px/16px ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, monospace;\n padding: 0 4px;\n color: #fff;\n border-radius: 2px 2px 0 0;\n white-space: nowrap;\n pointer-events: none;\n }\n `;\n win.document.head.appendChild(style);\n}\n\n// ────────────────────────────────────────────\n// Overlay root container\n// ────────────────────────────────────────────\n\nconst overlayRoots = new WeakMap<Window, HTMLDivElement>();\n\nfunction getOverlayRoot(win: Window): HTMLDivElement | null {\n if (win.closed) return null;\n\n const existing = overlayRoots.get(win);\n if (existing?.isConnected) return existing;\n\n ensureStylesheet(win);\n\n const root = win.document.createElement(\"div\");\n root.id = \"__react-debug-updates\";\n Object.assign(root.style, {\n position: \"fixed\",\n top: \"0\",\n left: \"0\",\n width: \"0\",\n height: \"0\",\n overflow: \"visible\",\n pointerEvents: \"none\",\n zIndex: \"2147483647\",\n } satisfies Partial<CSSStyleDeclaration>);\n win.document.body.appendChild(root);\n\n overlayRoots.set(win, root);\n return root;\n}\n\n// ────────────────────────────────────────────\n// Element pool\n// ────────────────────────────────────────────\n\nconst pools = new WeakMap<Window, HTMLDivElement[]>();\n\nexport function acquireOverlay(win: Window): HTMLDivElement | null {\n const root = getOverlayRoot(win);\n if (!root) return null;\n\n let pool = pools.get(win);\n if (!pool) {\n pool = [];\n pools.set(win, pool);\n }\n\n const document = win.document;\n let element = pool.pop();\n\n if (!element) {\n element = document.createElement(\"div\");\n element.className = `${ANIMATION_NAME}-box`;\n\n const label = document.createElement(\"span\");\n label.className = `${ANIMATION_NAME}-label`;\n element.appendChild(label);\n\n element.addEventListener(\"animationend\", () => {\n element!.style.animation = \"none\";\n element!.remove();\n pool!.push(element!);\n });\n }\n\n root.appendChild(element);\n return element;\n}\n\nexport function disposeAllOverlays() {\n const mainRoot = overlayRoots.get(window);\n mainRoot?.remove();\n overlayRoots.delete(window);\n pools.delete(window);\n}\n\nexport const OVERLAY_ANIMATION_NAME = ANIMATION_NAME;\n","import type { OverlayConfig, HighlightEntry } from \"./types.js\";\nimport { formatCausesShort } from \"./format.js\";\nimport { acquireOverlay, OVERLAY_ANIMATION_NAME } from \"./overlay.js\";\n\n// ────────────────────────────────────────────\n// Heat color\n// ────────────────────────────────────────────\n\nfunction heatColor(count: number, alpha: number): string {\n const normalizedCount = Math.min((count - 1) / 7, 1);\n const hue = 200 - normalizedCount * 200;\n const saturation = 85 + normalizedCount * 15;\n const lightness = 55 - normalizedCount * 10;\n return `hsla(${hue}, ${saturation}%, ${lightness}%, ${alpha})`;\n}\n\n// ────────────────────────────────────────────\n// Highlight scheduler\n// ────────────────────────────────────────────\n//\n// Commit path just pushes lightweight entries (no DOM reads, no formatting).\n// Flush (setInterval): batched rect reads → batched DOM writes.\n\ninterface CoalescedEntry {\n count: number;\n totalDuration: number;\n component: string;\n /** Shallowest fiber depth among coalesced entries (for z-ordering). */\n depth: number;\n domNode: HTMLElement;\n ownerWindow: Window;\n causeSummary: string;\n}\n\nexport function createHighlighter(config: OverlayConfig) {\n let pending: HighlightEntry[] = [];\n let timer: ReturnType<typeof setInterval> | null = null;\n\n function flush() {\n if (pending.length === 0) return;\n\n const batch = pending;\n pending = [];\n\n // Coalesce by DOM node identity\n const map = new Map<HTMLElement, CoalescedEntry>();\n\n for (let i = 0; i < batch.length; i++) {\n const entry = batch[i];\n if (!entry.domNode) continue;\n\n const existing = map.get(entry.domNode);\n if (existing) {\n existing.count++;\n existing.totalDuration += entry.duration;\n existing.depth = Math.min(existing.depth, entry.depth);\n } else {\n const win = entry.domNode.ownerDocument?.defaultView;\n if (!win || win.closed) continue;\n map.set(entry.domNode, {\n count: 1,\n totalDuration: entry.duration,\n component: entry.component,\n depth: entry.depth,\n domNode: entry.domNode,\n ownerWindow: win,\n causeSummary: formatCausesShort(entry.causes),\n });\n }\n }\n\n // Read phase: batch all rect reads\n const toShow: Array<{ coalesced: CoalescedEntry; rect: DOMRect }> = [];\n for (const coalesced of map.values()) {\n const rect = coalesced.domNode.getBoundingClientRect();\n if (rect.width > 0 || rect.height > 0) {\n toShow.push({ coalesced, rect });\n }\n }\n\n // Sort deepest first so parents are appended last (render on top)\n toShow.sort((a, b) => b.coalesced.depth - a.coalesced.depth);\n\n // Write phase: position overlays\n for (let i = 0; i < toShow.length; i++) {\n const { coalesced, rect } = toShow[i];\n const element = acquireOverlay(coalesced.ownerWindow);\n if (!element) continue;\n\n const fillColor = heatColor(coalesced.count, 0.18);\n const borderColor = heatColor(coalesced.count, 0.75);\n const labelBackground = heatColor(coalesced.count, 0.9);\n\n const style = element.style;\n style.top = `${rect.top}px`;\n style.left = `${rect.left}px`;\n style.width = `${rect.width}px`;\n style.height = `${rect.height}px`;\n style.backgroundColor = fillColor;\n style.border = `1.5px solid ${borderColor}`;\n style.setProperty(\"--rdu-opacity\", String(config.opacity));\n style.animation = `${OVERLAY_ANIMATION_NAME} ${config.animationDuration}ms ease-out forwards`;\n\n const label = element.firstElementChild as HTMLElement;\n if (config.showLabels) {\n const countText = coalesced.count > 1 ? ` ×${coalesced.count}` : \"\";\n const durationText =\n coalesced.totalDuration > 0\n ? ` ${coalesced.totalDuration.toFixed(1)}ms`\n : \"\";\n const causeText = coalesced.causeSummary\n ? ` (${coalesced.causeSummary})`\n : \"\";\n label.textContent = `${coalesced.component}${countText}${durationText}${causeText}`;\n label.style.backgroundColor = labelBackground;\n } else {\n label.textContent = \"\";\n label.style.backgroundColor = \"transparent\";\n }\n }\n }\n\n function push(entry: HighlightEntry) {\n pending.push(entry);\n if (!timer) {\n timer = setInterval(flush, config.flushInterval);\n }\n }\n\n function dispose() {\n if (timer) {\n clearInterval(timer);\n timer = null;\n }\n pending = [];\n }\n\n return { push, dispose };\n}\n","import type {\n DevToolsHook,\n FiberRoot,\n HighlightEntry,\n MonitorOptions,\n} from \"./types.js\";\nimport {\n detectRenders,\n findNearestDOMNode,\n getComponentName,\n getFiberPath,\n} from \"./fiber.js\";\nimport { detectCauses } from \"./causes.js\";\nimport { logRerendersToConsole } from \"./format.js\";\nimport { createHighlighter } from \"./highlights.js\";\nimport { disposeAllOverlays } from \"./overlay.js\";\n\n/**\n * Ensure __REACT_DEVTOOLS_GLOBAL_HOOK__ exists on window.\n *\n * React reads this hook during initialization — if it's missing, React will\n * never connect. When this library is imported before React (the recommended\n * setup), we need to create a minimal hook so React can find and register with it.\n *\n * If a hook already exists (e.g. from the React DevTools extension), we leave\n * it untouched and hook into it.\n */\nfunction ensureDevToolsHook(win: Window): DevToolsHook {\n const global = win as unknown as {\n __REACT_DEVTOOLS_GLOBAL_HOOK__?: DevToolsHook;\n };\n\n if (!global.__REACT_DEVTOOLS_GLOBAL_HOOK__) {\n global.__REACT_DEVTOOLS_GLOBAL_HOOK__ = {\n supportsFiber: true,\n inject() {\n return 1;\n },\n onCommitFiberRoot() {},\n onCommitFiberUnmount() {},\n onPostCommitFiberRoot() {},\n checkDCE() {},\n } as DevToolsHook;\n }\n\n return global.__REACT_DEVTOOLS_GLOBAL_HOOK__;\n}\n\n/**\n * Start monitoring React re-renders.\n *\n * Hooks into `__REACT_DEVTOOLS_GLOBAL_HOOK__.onCommitFiberRoot` to intercept\n * every React commit. Shows visual highlight overlays on re-rendered DOM nodes\n * and optionally logs re-renders to the console.\n *\n * Call this **before** React renders anything — ideally at the very top of\n * your entry point.\n *\n * Returns a `stop` function to unhook and clean up. Throws if called\n * in a non-browser environment (e.g. SSR).\n */\nexport function startReactUpdatesMonitor({\n logToConsole = false,\n highlight = true,\n mode = \"self-triggered\",\n reasonOfUpdate = false,\n highlightFlushInterval = 250,\n highlightAnimationDuration = 1200,\n highlightShowLabels = true,\n highlightOpacity = 0.3,\n}: MonitorOptions = {}): () => void {\n if (typeof window === \"undefined\") {\n throw new Error(\n \"[react-debug-updates] Cannot start monitor in a non-browser environment. \" +\n \"Guard the call with a `typeof window !== 'undefined'` check.\",\n );\n }\n\n const hook = ensureDevToolsHook(window);\n\n const highlighter = highlight\n ? createHighlighter({\n flushInterval: highlightFlushInterval,\n animationDuration: highlightAnimationDuration,\n showLabels: highlightShowLabels,\n opacity: highlightOpacity,\n })\n : null;\n\n const previousOnCommit = hook.onCommitFiberRoot.bind(hook);\n\n hook.onCommitFiberRoot = (\n rendererID: number,\n root: FiberRoot,\n priorityLevel?: unknown,\n ) => {\n previousOnCommit(rendererID, root, priorityLevel);\n\n // 1. Detect which component fibers actually re-rendered (pure fiber analysis)\n const detectedRenders = detectRenders(root.current, mode);\n if (detectedRenders.length === 0) return;\n\n // 2. Build full entries: resolve names, DOM nodes, causes\n const highlightEntries: HighlightEntry[] = [];\n\n for (let i = 0; i < detectedRenders.length; i++) {\n const { fiber, depth } = detectedRenders[i];\n const name = getComponentName(fiber);\n if (!name) continue;\n\n const entry: HighlightEntry = {\n component: name,\n path: getFiberPath(fiber),\n duration: fiber.actualDuration ?? 0,\n depth,\n domNode: findNearestDOMNode(fiber),\n causes: reasonOfUpdate ? detectCauses(fiber) : [],\n };\n\n highlightEntries.push(entry);\n highlighter?.push(entry);\n }\n\n // 3. Console output\n if (logToConsole) {\n logRerendersToConsole(highlightEntries, reasonOfUpdate);\n }\n };\n\n if (logToConsole) {\n console.log(\n \"%c⚛ react-debug-updates attached\",\n \"color: #61dafb; font-weight: bold\",\n );\n }\n\n return () => {\n hook.onCommitFiberRoot = previousOnCommit;\n highlighter?.dispose();\n disposeAllOverlays();\n };\n}\n"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -56,16 +56,7 @@ export interface OverlayConfig {
56
56
  showLabels: boolean;
57
57
  opacity: number;
58
58
  }
59
- export interface RenderEntry {
60
- component: string;
61
- path: string;
62
- duration: number;
63
- timestamp: number;
64
- causes: UpdateCause[];
65
- }
66
59
  export interface MonitorOptions {
67
- /** Suppress console output. Default: false */
68
- silent?: boolean;
69
60
  /**
70
61
  * Which re-renders to track.
71
62
  * - `"self-triggered"` — only components whose own state/hooks changed
@@ -74,10 +65,6 @@ export interface MonitorOptions {
74
65
  * Default: `"self-triggered"`
75
66
  */
76
67
  mode?: "self-triggered" | "all";
77
- /** Ring buffer size for stored entries. Default: 500 */
78
- bufferSize?: number;
79
- /** Filter — return `false` to skip an entry. */
80
- filter?: (entry: RenderEntry) => boolean;
81
68
  /**
82
69
  * Detect and display *why* each component re-rendered.
83
70
  *
@@ -85,21 +72,17 @@ export interface MonitorOptions {
85
72
  * with previous→next values for state hooks.
86
73
  * Requires a React dev build (`_debugHookTypes`). Default: `false`
87
74
  */
88
- showCauses?: boolean;
75
+ reasonOfUpdate?: boolean;
76
+ /** Log re-renders to the console. Default: false */
77
+ logToConsole?: boolean;
89
78
  /** Enable visual highlight overlays. Default: true */
90
- overlay?: boolean;
79
+ highlight?: boolean;
91
80
  /** Show text labels (component name, count, duration, cause) above overlays. Default: true */
92
- showLabels?: boolean;
93
- /** Peak opacity of overlay highlights (0–1). Default: 0.3 */
94
- opacity?: number;
95
- /** Time between overlay flush cycles (ms). Default: 250 */
96
- flushInterval?: number;
97
- /** Overlay fade-out animation duration (ms). Default: 1200 */
98
- animationDuration?: number;
99
- }
100
- export interface UpdateMonitor {
101
- /** Ring buffer of recorded re-render entries. */
102
- entries: RenderEntry[];
103
- /** Unhook from React and clean up overlays. */
104
- stop: () => void;
81
+ highlightShowLabels?: boolean;
82
+ /** Peak opacity of highlight overlays (0–1). Default: 0.3 */
83
+ highlightOpacity?: number;
84
+ /** Time between highlight flush cycles (ms). Default: 250 */
85
+ highlightFlushInterval?: number;
86
+ /** Highlight fade-out animation duration (ms). Default: 1200 */
87
+ highlightAnimationDuration?: number;
105
88
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-debug-updates",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "See why React components re-render — visual overlays, console logging, and hook-level cause detection with zero code changes",
5
5
  "type": "module",
6
6
  "main": "./dist/react-debug-updates.cjs",