react-debug-updates 0.1.1 → 0.1.2

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
@@ -79,6 +79,7 @@ Returns a `RenderLogger` handle, or `null` if the DevTools hook is not available
79
79
  | `flushInterval` | `number` | `250` | Milliseconds between overlay flush cycles |
80
80
  | `animationDuration` | `number` | `1200` | Overlay fade-out animation duration (ms) |
81
81
  | `showLabels` | `boolean` | `true` | Show text labels (name, count, duration, cause) above overlays |
82
+ | `opacity` | `number` | `0.3` | Peak opacity of overlay highlights (0–1) |
82
83
 
83
84
  ### `RenderLogger`
84
85
 
@@ -205,8 +205,8 @@ function ensureStylesheet(win) {
205
205
  style.textContent = `
206
206
  @keyframes ${ANIMATION_NAME} {
207
207
  0% { opacity: 0; }
208
- 8% { opacity: 1; }
209
- 40% { opacity: 1; }
208
+ 8% { opacity: var(--rdu-opacity, 1); }
209
+ 40% { opacity: var(--rdu-opacity, 1); }
210
210
  100% { opacity: 0; }
211
211
  }
212
212
  .${ANIMATION_NAME}-box {
@@ -296,7 +296,8 @@ function heatColor(count, alpha) {
296
296
  const HIGHLIGHT_DEFAULTS = {
297
297
  flushInterval: 250,
298
298
  animationDuration: 1200,
299
- showLabels: true
299
+ showLabels: true,
300
+ opacity: 0.3
300
301
  };
301
302
  function createBatcher(options) {
302
303
  let pending = [];
@@ -334,7 +335,7 @@ function createBatcher(options) {
334
335
  toShow.push({ coalesced, rect });
335
336
  }
336
337
  }
337
- for (let i = 0; i < toShow.length; i++) {
338
+ for (let i = toShow.length - 1; i >= 0; i--) {
338
339
  const { coalesced, rect } = toShow[i];
339
340
  const element = acquireOverlay(coalesced.ownerWindow);
340
341
  if (!element) continue;
@@ -348,6 +349,7 @@ function createBatcher(options) {
348
349
  style.height = `${rect.height}px`;
349
350
  style.backgroundColor = fillColor;
350
351
  style.border = `1.5px solid ${borderColor}`;
352
+ style.setProperty("--rdu-opacity", String(options.opacity));
351
353
  style.animation = `${OVERLAY_ANIMATION_NAME} ${options.animationDuration}ms ease-out forwards`;
352
354
  const label = element.firstElementChild;
353
355
  if (options.showLabels) {
@@ -1 +1 @@
1
- {"version":3,"file":"react-debug-updates.cjs","sources":["../src/causes.ts","../src/fiber.ts","../src/format.ts","../src/overlay.ts","../src/batcher.ts","../src/logger.ts"],"sourcesContent":["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 { Fiber, PendingEntry } from \"./types.js\";\nimport { detectCauses } from \"./causes.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\nconst COMPONENT_TAGS = new Set<number>([\n FiberTag.FunctionComponent,\n FiberTag.ClassComponent,\n FiberTag.ForwardRef,\n FiberTag.SimpleMemoComponent,\n FiberTag.MemoComponent,\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// Collect pending entries from a fiber tree\n// ────────────────────────────────────────────\n\nexport function collectPending(\n root: Fiber,\n mode: \"self-triggered\" | \"all\",\n trackCauses: boolean,\n): PendingEntry[] {\n const entries: PendingEntry[] = [];\n const selfTriggeredOnly = mode === \"self-triggered\";\n\n function walk(fiber: Fiber) {\n if (\n COMPONENT_TAGS.has(fiber.tag) &&\n fiber.flags & PerformedWork &&\n fiber.alternate !== null &&\n (!selfTriggeredOnly || isSelfTriggered(fiber))\n ) {\n const name = getComponentName(fiber);\n if (name) {\n entries.push({\n component: name,\n path: getFiberPath(fiber),\n duration: fiber.actualDuration ?? 0,\n domNode: findNearestDOMNode(fiber),\n causes: trackCauses ? detectCauses(fiber) : [],\n });\n }\n }\n if (fiber.child) walk(fiber.child);\n if (fiber.sibling) walk(fiber.sibling);\n }\n\n walk(root);\n return entries;\n}\n","import type { 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// 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: 1; }\n 40% { 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 { HighlightOptions, PendingEntry } 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// Batched flush\n// ────────────────────────────────────────────\n//\n// Commit path just pushes lightweight refs (no DOM reads, no formatting).\n// Flush (setInterval): batched rect reads → batched DOM writes.\n\nexport const HIGHLIGHT_DEFAULTS: Required<HighlightOptions> = {\n flushInterval: 250,\n animationDuration: 1200,\n showLabels: true,\n};\n\ninterface CoalescedEntry {\n count: number;\n totalDuration: number;\n component: string;\n domNode: HTMLElement;\n ownerWindow: Window;\n causeSummary: string;\n}\n\nexport function createBatcher(options: Required<HighlightOptions>) {\n let pending: PendingEntry[] = [];\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 } 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 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 // 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.animation = `${OVERLAY_ANIMATION_NAME} ${options.animationDuration}ms ease-out forwards`;\n\n const label = element.firstElementChild as HTMLElement;\n if (options.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: PendingEntry) {\n pending.push(entry);\n if (!timer) {\n timer = setInterval(flush, options.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 LoggerOptions,\n RenderEntry,\n RenderLogger,\n} from \"./types.js\";\nimport { collectPending } from \"./fiber.js\";\nimport { formatCausesConsole } from \"./format.js\";\nimport { HIGHLIGHT_DEFAULTS, createBatcher } from \"./batcher.js\";\nimport { disposeAllOverlays } from \"./overlay.js\";\n\n/**\n * Attach a render logger to React's DevTools hook.\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 * Returns a `RenderLogger` handle, or `null` if the DevTools hook is not found.\n */\nexport function attachRenderLogger(\n options: LoggerOptions = {},\n): RenderLogger | null {\n const {\n silent = false,\n bufferSize = 500,\n filter,\n highlight = false,\n mode = \"self-triggered\",\n showCauses = false,\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 highlightOptions = highlight\n ? {\n ...HIGHLIGHT_DEFAULTS,\n ...(typeof highlight === \"object\" ? highlight : {}),\n }\n : null;\n\n const batcher = highlightOptions ? createBatcher(highlightOptions) : 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 const pendingEntries = collectPending(root.current, mode, showCauses);\n\n for (let i = 0; i < pendingEntries.length; i++) {\n const pendingEntry = pendingEntries[i];\n\n const entry: RenderEntry = {\n component: pendingEntry.component,\n path: pendingEntry.path,\n duration: pendingEntry.duration,\n timestamp: performance.now(),\n causes: pendingEntry.causes,\n };\n\n if (filter && !filter(entry)) continue;\n\n if (entries.length >= bufferSize) entries.shift();\n entries.push(entry);\n\n batcher?.push(pendingEntry);\n }\n\n // Console output\n if (!silent && pendingEntries.length > 0) {\n console.groupCollapsed(\n `%c⚛ ${pendingEntries.length} re-render${pendingEntries.length > 1 ? \"s\" : \"\"}`,\n \"color: #61dafb; font-weight: bold\",\n );\n\n for (let i = 0; i < pendingEntries.length; i++) {\n const pendingEntry = pendingEntries[i];\n const durationText =\n pendingEntry.duration > 0\n ? ` (${pendingEntry.duration.toFixed(2)}ms)`\n : \"\";\n console.log(\n `%c${pendingEntry.component}%c ${pendingEntry.path}${durationText}`,\n \"color: #e8e82e; font-weight: bold\",\n \"color: #888\",\n );\n\n if (showCauses && pendingEntry.causes.length > 0) {\n const lines = formatCausesConsole(pendingEntry.causes);\n for (const line of lines) {\n console.log(`%c${line}`, \"color: #aaa\");\n }\n }\n }\n\n console.groupEnd();\n }\n };\n\n const disconnect = () => {\n hook.onCommitFiberRoot = previousOnCommit;\n batcher?.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, disconnect };\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"react-debug-updates.cjs","sources":["../src/causes.ts","../src/fiber.ts","../src/format.ts","../src/overlay.ts","../src/batcher.ts","../src/logger.ts"],"sourcesContent":["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 { Fiber, PendingEntry } from \"./types.js\";\nimport { detectCauses } from \"./causes.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\nconst COMPONENT_TAGS = new Set<number>([\n FiberTag.FunctionComponent,\n FiberTag.ClassComponent,\n FiberTag.ForwardRef,\n FiberTag.SimpleMemoComponent,\n FiberTag.MemoComponent,\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// Collect pending entries from a fiber tree\n// ────────────────────────────────────────────\n\nexport function collectPending(\n root: Fiber,\n mode: \"self-triggered\" | \"all\",\n trackCauses: boolean,\n): PendingEntry[] {\n const entries: PendingEntry[] = [];\n const selfTriggeredOnly = mode === \"self-triggered\";\n\n function walk(fiber: Fiber) {\n if (\n COMPONENT_TAGS.has(fiber.tag) &&\n fiber.flags & PerformedWork &&\n fiber.alternate !== null &&\n (!selfTriggeredOnly || isSelfTriggered(fiber))\n ) {\n const name = getComponentName(fiber);\n if (name) {\n entries.push({\n component: name,\n path: getFiberPath(fiber),\n duration: fiber.actualDuration ?? 0,\n domNode: findNearestDOMNode(fiber),\n causes: trackCauses ? detectCauses(fiber) : [],\n });\n }\n }\n if (fiber.child) walk(fiber.child);\n if (fiber.sibling) walk(fiber.sibling);\n }\n\n walk(root);\n return entries;\n}\n","import type { 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// 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 { HighlightOptions, PendingEntry } 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// Batched flush\n// ────────────────────────────────────────────\n//\n// Commit path just pushes lightweight refs (no DOM reads, no formatting).\n// Flush (setInterval): batched rect reads → batched DOM writes.\n\nexport const HIGHLIGHT_DEFAULTS: Required<HighlightOptions> = {\n flushInterval: 250,\n animationDuration: 1200,\n showLabels: true,\n opacity: 0.3,\n};\n\ninterface CoalescedEntry {\n count: number;\n totalDuration: number;\n component: string;\n domNode: HTMLElement;\n ownerWindow: Window;\n causeSummary: string;\n}\n\nexport function createBatcher(options: Required<HighlightOptions>) {\n let pending: PendingEntry[] = [];\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 } 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 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 // Write phase: position overlays (reverse order so parents render on top of children)\n for (let i = toShow.length - 1; i >= 0; 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(options.opacity));\n style.animation = `${OVERLAY_ANIMATION_NAME} ${options.animationDuration}ms ease-out forwards`;\n\n const label = element.firstElementChild as HTMLElement;\n if (options.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: PendingEntry) {\n pending.push(entry);\n if (!timer) {\n timer = setInterval(flush, options.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 LoggerOptions,\n RenderEntry,\n RenderLogger,\n} from \"./types.js\";\nimport { collectPending } from \"./fiber.js\";\nimport { formatCausesConsole } from \"./format.js\";\nimport { HIGHLIGHT_DEFAULTS, createBatcher } from \"./batcher.js\";\nimport { disposeAllOverlays } from \"./overlay.js\";\n\n/**\n * Attach a render logger to React's DevTools hook.\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 * Returns a `RenderLogger` handle, or `null` if the DevTools hook is not found.\n */\nexport function attachRenderLogger(\n options: LoggerOptions = {},\n): RenderLogger | null {\n const {\n silent = false,\n bufferSize = 500,\n filter,\n highlight = false,\n mode = \"self-triggered\",\n showCauses = false,\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 highlightOptions = highlight\n ? {\n ...HIGHLIGHT_DEFAULTS,\n ...(typeof highlight === \"object\" ? highlight : {}),\n }\n : null;\n\n const batcher = highlightOptions ? createBatcher(highlightOptions) : 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 const pendingEntries = collectPending(root.current, mode, showCauses);\n\n for (let i = 0; i < pendingEntries.length; i++) {\n const pendingEntry = pendingEntries[i];\n\n const entry: RenderEntry = {\n component: pendingEntry.component,\n path: pendingEntry.path,\n duration: pendingEntry.duration,\n timestamp: performance.now(),\n causes: pendingEntry.causes,\n };\n\n if (filter && !filter(entry)) continue;\n\n if (entries.length >= bufferSize) entries.shift();\n entries.push(entry);\n\n batcher?.push(pendingEntry);\n }\n\n // Console output\n if (!silent && pendingEntries.length > 0) {\n console.groupCollapsed(\n `%c⚛ ${pendingEntries.length} re-render${pendingEntries.length > 1 ? \"s\" : \"\"}`,\n \"color: #61dafb; font-weight: bold\",\n );\n\n for (let i = 0; i < pendingEntries.length; i++) {\n const pendingEntry = pendingEntries[i];\n const durationText =\n pendingEntry.duration > 0\n ? ` (${pendingEntry.duration.toFixed(2)}ms)`\n : \"\";\n console.log(\n `%c${pendingEntry.component}%c ${pendingEntry.path}${durationText}`,\n \"color: #e8e82e; font-weight: bold\",\n \"color: #888\",\n );\n\n if (showCauses && pendingEntry.causes.length > 0) {\n const lines = formatCausesConsole(pendingEntry.causes);\n for (const line of lines) {\n console.log(`%c${line}`, \"color: #aaa\");\n }\n }\n }\n\n console.groupEnd();\n }\n };\n\n const disconnect = () => {\n hook.onCommitFiberRoot = previousOnCommit;\n batcher?.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, disconnect };\n}\n"],"names":[],"mappings":"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"}
@@ -203,8 +203,8 @@ function ensureStylesheet(win) {
203
203
  style.textContent = `
204
204
  @keyframes ${ANIMATION_NAME} {
205
205
  0% { opacity: 0; }
206
- 8% { opacity: 1; }
207
- 40% { opacity: 1; }
206
+ 8% { opacity: var(--rdu-opacity, 1); }
207
+ 40% { opacity: var(--rdu-opacity, 1); }
208
208
  100% { opacity: 0; }
209
209
  }
210
210
  .${ANIMATION_NAME}-box {
@@ -294,7 +294,8 @@ function heatColor(count, alpha) {
294
294
  const HIGHLIGHT_DEFAULTS = {
295
295
  flushInterval: 250,
296
296
  animationDuration: 1200,
297
- showLabels: true
297
+ showLabels: true,
298
+ opacity: 0.3
298
299
  };
299
300
  function createBatcher(options) {
300
301
  let pending = [];
@@ -332,7 +333,7 @@ function createBatcher(options) {
332
333
  toShow.push({ coalesced, rect });
333
334
  }
334
335
  }
335
- for (let i = 0; i < toShow.length; i++) {
336
+ for (let i = toShow.length - 1; i >= 0; i--) {
336
337
  const { coalesced, rect } = toShow[i];
337
338
  const element = acquireOverlay(coalesced.ownerWindow);
338
339
  if (!element) continue;
@@ -346,6 +347,7 @@ function createBatcher(options) {
346
347
  style.height = `${rect.height}px`;
347
348
  style.backgroundColor = fillColor;
348
349
  style.border = `1.5px solid ${borderColor}`;
350
+ style.setProperty("--rdu-opacity", String(options.opacity));
349
351
  style.animation = `${OVERLAY_ANIMATION_NAME} ${options.animationDuration}ms ease-out forwards`;
350
352
  const label = element.firstElementChild;
351
353
  if (options.showLabels) {
@@ -1 +1 @@
1
- {"version":3,"file":"react-debug-updates.js","sources":["../src/causes.ts","../src/fiber.ts","../src/format.ts","../src/overlay.ts","../src/batcher.ts","../src/logger.ts"],"sourcesContent":["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 { Fiber, PendingEntry } from \"./types.js\";\nimport { detectCauses } from \"./causes.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\nconst COMPONENT_TAGS = new Set<number>([\n FiberTag.FunctionComponent,\n FiberTag.ClassComponent,\n FiberTag.ForwardRef,\n FiberTag.SimpleMemoComponent,\n FiberTag.MemoComponent,\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// Collect pending entries from a fiber tree\n// ────────────────────────────────────────────\n\nexport function collectPending(\n root: Fiber,\n mode: \"self-triggered\" | \"all\",\n trackCauses: boolean,\n): PendingEntry[] {\n const entries: PendingEntry[] = [];\n const selfTriggeredOnly = mode === \"self-triggered\";\n\n function walk(fiber: Fiber) {\n if (\n COMPONENT_TAGS.has(fiber.tag) &&\n fiber.flags & PerformedWork &&\n fiber.alternate !== null &&\n (!selfTriggeredOnly || isSelfTriggered(fiber))\n ) {\n const name = getComponentName(fiber);\n if (name) {\n entries.push({\n component: name,\n path: getFiberPath(fiber),\n duration: fiber.actualDuration ?? 0,\n domNode: findNearestDOMNode(fiber),\n causes: trackCauses ? detectCauses(fiber) : [],\n });\n }\n }\n if (fiber.child) walk(fiber.child);\n if (fiber.sibling) walk(fiber.sibling);\n }\n\n walk(root);\n return entries;\n}\n","import type { 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// 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: 1; }\n 40% { 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 { HighlightOptions, PendingEntry } 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// Batched flush\n// ────────────────────────────────────────────\n//\n// Commit path just pushes lightweight refs (no DOM reads, no formatting).\n// Flush (setInterval): batched rect reads → batched DOM writes.\n\nexport const HIGHLIGHT_DEFAULTS: Required<HighlightOptions> = {\n flushInterval: 250,\n animationDuration: 1200,\n showLabels: true,\n};\n\ninterface CoalescedEntry {\n count: number;\n totalDuration: number;\n component: string;\n domNode: HTMLElement;\n ownerWindow: Window;\n causeSummary: string;\n}\n\nexport function createBatcher(options: Required<HighlightOptions>) {\n let pending: PendingEntry[] = [];\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 } 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 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 // 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.animation = `${OVERLAY_ANIMATION_NAME} ${options.animationDuration}ms ease-out forwards`;\n\n const label = element.firstElementChild as HTMLElement;\n if (options.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: PendingEntry) {\n pending.push(entry);\n if (!timer) {\n timer = setInterval(flush, options.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 LoggerOptions,\n RenderEntry,\n RenderLogger,\n} from \"./types.js\";\nimport { collectPending } from \"./fiber.js\";\nimport { formatCausesConsole } from \"./format.js\";\nimport { HIGHLIGHT_DEFAULTS, createBatcher } from \"./batcher.js\";\nimport { disposeAllOverlays } from \"./overlay.js\";\n\n/**\n * Attach a render logger to React's DevTools hook.\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 * Returns a `RenderLogger` handle, or `null` if the DevTools hook is not found.\n */\nexport function attachRenderLogger(\n options: LoggerOptions = {},\n): RenderLogger | null {\n const {\n silent = false,\n bufferSize = 500,\n filter,\n highlight = false,\n mode = \"self-triggered\",\n showCauses = false,\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 highlightOptions = highlight\n ? {\n ...HIGHLIGHT_DEFAULTS,\n ...(typeof highlight === \"object\" ? highlight : {}),\n }\n : null;\n\n const batcher = highlightOptions ? createBatcher(highlightOptions) : 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 const pendingEntries = collectPending(root.current, mode, showCauses);\n\n for (let i = 0; i < pendingEntries.length; i++) {\n const pendingEntry = pendingEntries[i];\n\n const entry: RenderEntry = {\n component: pendingEntry.component,\n path: pendingEntry.path,\n duration: pendingEntry.duration,\n timestamp: performance.now(),\n causes: pendingEntry.causes,\n };\n\n if (filter && !filter(entry)) continue;\n\n if (entries.length >= bufferSize) entries.shift();\n entries.push(entry);\n\n batcher?.push(pendingEntry);\n }\n\n // Console output\n if (!silent && pendingEntries.length > 0) {\n console.groupCollapsed(\n `%c⚛ ${pendingEntries.length} re-render${pendingEntries.length > 1 ? \"s\" : \"\"}`,\n \"color: #61dafb; font-weight: bold\",\n );\n\n for (let i = 0; i < pendingEntries.length; i++) {\n const pendingEntry = pendingEntries[i];\n const durationText =\n pendingEntry.duration > 0\n ? ` (${pendingEntry.duration.toFixed(2)}ms)`\n : \"\";\n console.log(\n `%c${pendingEntry.component}%c ${pendingEntry.path}${durationText}`,\n \"color: #e8e82e; font-weight: bold\",\n \"color: #888\",\n );\n\n if (showCauses && pendingEntry.causes.length > 0) {\n const lines = formatCausesConsole(pendingEntry.causes);\n for (const line of lines) {\n console.log(`%c${line}`, \"color: #aaa\");\n }\n }\n }\n\n console.groupEnd();\n }\n };\n\n const disconnect = () => {\n hook.onCommitFiberRoot = previousOnCommit;\n batcher?.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, disconnect };\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"react-debug-updates.js","sources":["../src/causes.ts","../src/fiber.ts","../src/format.ts","../src/overlay.ts","../src/batcher.ts","../src/logger.ts"],"sourcesContent":["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 { Fiber, PendingEntry } from \"./types.js\";\nimport { detectCauses } from \"./causes.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\nconst COMPONENT_TAGS = new Set<number>([\n FiberTag.FunctionComponent,\n FiberTag.ClassComponent,\n FiberTag.ForwardRef,\n FiberTag.SimpleMemoComponent,\n FiberTag.MemoComponent,\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// Collect pending entries from a fiber tree\n// ────────────────────────────────────────────\n\nexport function collectPending(\n root: Fiber,\n mode: \"self-triggered\" | \"all\",\n trackCauses: boolean,\n): PendingEntry[] {\n const entries: PendingEntry[] = [];\n const selfTriggeredOnly = mode === \"self-triggered\";\n\n function walk(fiber: Fiber) {\n if (\n COMPONENT_TAGS.has(fiber.tag) &&\n fiber.flags & PerformedWork &&\n fiber.alternate !== null &&\n (!selfTriggeredOnly || isSelfTriggered(fiber))\n ) {\n const name = getComponentName(fiber);\n if (name) {\n entries.push({\n component: name,\n path: getFiberPath(fiber),\n duration: fiber.actualDuration ?? 0,\n domNode: findNearestDOMNode(fiber),\n causes: trackCauses ? detectCauses(fiber) : [],\n });\n }\n }\n if (fiber.child) walk(fiber.child);\n if (fiber.sibling) walk(fiber.sibling);\n }\n\n walk(root);\n return entries;\n}\n","import type { 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// 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 { HighlightOptions, PendingEntry } 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// Batched flush\n// ────────────────────────────────────────────\n//\n// Commit path just pushes lightweight refs (no DOM reads, no formatting).\n// Flush (setInterval): batched rect reads → batched DOM writes.\n\nexport const HIGHLIGHT_DEFAULTS: Required<HighlightOptions> = {\n flushInterval: 250,\n animationDuration: 1200,\n showLabels: true,\n opacity: 0.3,\n};\n\ninterface CoalescedEntry {\n count: number;\n totalDuration: number;\n component: string;\n domNode: HTMLElement;\n ownerWindow: Window;\n causeSummary: string;\n}\n\nexport function createBatcher(options: Required<HighlightOptions>) {\n let pending: PendingEntry[] = [];\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 } 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 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 // Write phase: position overlays (reverse order so parents render on top of children)\n for (let i = toShow.length - 1; i >= 0; 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(options.opacity));\n style.animation = `${OVERLAY_ANIMATION_NAME} ${options.animationDuration}ms ease-out forwards`;\n\n const label = element.firstElementChild as HTMLElement;\n if (options.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: PendingEntry) {\n pending.push(entry);\n if (!timer) {\n timer = setInterval(flush, options.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 LoggerOptions,\n RenderEntry,\n RenderLogger,\n} from \"./types.js\";\nimport { collectPending } from \"./fiber.js\";\nimport { formatCausesConsole } from \"./format.js\";\nimport { HIGHLIGHT_DEFAULTS, createBatcher } from \"./batcher.js\";\nimport { disposeAllOverlays } from \"./overlay.js\";\n\n/**\n * Attach a render logger to React's DevTools hook.\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 * Returns a `RenderLogger` handle, or `null` if the DevTools hook is not found.\n */\nexport function attachRenderLogger(\n options: LoggerOptions = {},\n): RenderLogger | null {\n const {\n silent = false,\n bufferSize = 500,\n filter,\n highlight = false,\n mode = \"self-triggered\",\n showCauses = false,\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 highlightOptions = highlight\n ? {\n ...HIGHLIGHT_DEFAULTS,\n ...(typeof highlight === \"object\" ? highlight : {}),\n }\n : null;\n\n const batcher = highlightOptions ? createBatcher(highlightOptions) : 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 const pendingEntries = collectPending(root.current, mode, showCauses);\n\n for (let i = 0; i < pendingEntries.length; i++) {\n const pendingEntry = pendingEntries[i];\n\n const entry: RenderEntry = {\n component: pendingEntry.component,\n path: pendingEntry.path,\n duration: pendingEntry.duration,\n timestamp: performance.now(),\n causes: pendingEntry.causes,\n };\n\n if (filter && !filter(entry)) continue;\n\n if (entries.length >= bufferSize) entries.shift();\n entries.push(entry);\n\n batcher?.push(pendingEntry);\n }\n\n // Console output\n if (!silent && pendingEntries.length > 0) {\n console.groupCollapsed(\n `%c⚛ ${pendingEntries.length} re-render${pendingEntries.length > 1 ? \"s\" : \"\"}`,\n \"color: #61dafb; font-weight: bold\",\n );\n\n for (let i = 0; i < pendingEntries.length; i++) {\n const pendingEntry = pendingEntries[i];\n const durationText =\n pendingEntry.duration > 0\n ? ` (${pendingEntry.duration.toFixed(2)}ms)`\n : \"\";\n console.log(\n `%c${pendingEntry.component}%c ${pendingEntry.path}${durationText}`,\n \"color: #e8e82e; font-weight: bold\",\n \"color: #888\",\n );\n\n if (showCauses && pendingEntry.causes.length > 0) {\n const lines = formatCausesConsole(pendingEntry.causes);\n for (const line of lines) {\n console.log(`%c${line}`, \"color: #aaa\");\n }\n }\n }\n\n console.groupEnd();\n }\n };\n\n const disconnect = () => {\n hook.onCommitFiberRoot = previousOnCommit;\n batcher?.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, disconnect };\n}\n"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -51,6 +51,8 @@ export interface HighlightOptions {
51
51
  animationDuration?: number;
52
52
  /** Show text labels (component name, count, duration, cause) above overlays. Default: true */
53
53
  showLabels?: boolean;
54
+ /** Peak opacity of overlay highlights (0–1). Default: 0.3 */
55
+ opacity?: number;
54
56
  }
55
57
  export interface RenderEntry {
56
58
  component: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-debug-updates",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
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",
@@ -25,7 +25,8 @@
25
25
  "scripts": {
26
26
  "build": "vite build && tsc --emitDeclarationOnly",
27
27
  "typecheck": "tsc --noEmit",
28
- "prepublishOnly": "yarn build"
28
+ "prepublishOnly": "yarn build",
29
+ "release": "npm version patch && npm publish --access public && git push && git push --tags"
29
30
  },
30
31
  "keywords": [
31
32
  "react",