rerender-lens 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -1,5 +1,3 @@
1
- import * as ReactNS from 'react';
2
-
3
1
  /** Why a single prop/state/hook value differs between two renders. */
4
2
  type ChangeKind =
5
3
  /** New reference, but deep-equal to the previous value. Avoidable. */
@@ -34,15 +32,43 @@ type RenderTrigger =
34
32
  /** More than one of the above. */
35
33
  | 'mixed';
36
34
  interface HookChange extends Change {
37
- /** `useState`, `useReducer`, `useContext`, ... */
35
+ /** `useState`, `useReducer`, `useContext`, `useSyncExternalStore`, or `state` when the exact hook is unknown. */
38
36
  hook: string;
39
37
  /** Position of the hook in call order (0-based). */
40
38
  index: number;
39
+ /** `useContext` only: the component that renders the nearest matching Provider, and its ancestry. */
40
+ provider?: {
41
+ component: string | null;
42
+ path: string[];
43
+ };
44
+ /** `useContext` only, object values: top-level keys whose value changed (shallow), and how many keys the value has. */
45
+ changedKeys?: string[];
46
+ totalKeys?: number;
47
+ }
48
+ /** Scheduler priority of the commit, as React reports it to the DevTools hook (transitions run at `normal`). */
49
+ type CommitPriority = 'immediate' | 'user-blocking' | 'normal' | 'low' | 'idle';
50
+ interface ParentInfo {
51
+ /** Display name of the nearest ancestor component that also rendered in this commit. */
52
+ name: string;
53
+ /** Why that ancestor rendered. */
54
+ trigger: RenderTrigger;
55
+ }
56
+ /** Where a component's element was created (React <= 18: `_debugSource`; React 19: parsed from `_debugStack`). */
57
+ interface SourceLocation {
58
+ fileName: string;
59
+ lineNumber?: number;
60
+ columnNumber?: number;
41
61
  }
42
62
  interface RenderReport {
43
63
  /** Display name of the tracked component. */
44
64
  component: string;
45
- /** Monotonic per-component render count (1 = first update, mount is never reported). */
65
+ /** Stable id of this component instance (fiber) across its lifetime. */
66
+ instanceId: number;
67
+ /** Monotonic id of the React commit that produced this report. Reports from one commit share it. 0 for `useWhyRerender`. */
68
+ commitId: number;
69
+ /** Priority React assigned to the commit: `immediate` for discrete input (clicks, keys), `user-blocking` for continuous input, `normal` for transitions and async updates. */
70
+ commitPriority?: CommitPriority;
71
+ /** Monotonic per-instance update count (1 = first update; mount is never reported). */
46
72
  renderCount: number;
47
73
  trigger: RenderTrigger;
48
74
  /** True when the re-render produced no genuine change in props, state, or hooks. */
@@ -54,8 +80,22 @@ interface RenderReport {
54
80
  propChanges: Change[];
55
81
  /** Class components only. */
56
82
  stateChanges: Change[];
57
- /** Function components only, when `trackHooks` is on. */
83
+ /** Function components: state hooks and contexts that changed. */
58
84
  hookChanges: HookChange[];
85
+ /** Nearest ancestor that rendered in the same commit, or null when the update started here. */
86
+ parent: ParentInfo | null;
87
+ /** Component that created this element (dev builds only). */
88
+ owner: string | null;
89
+ /** Component ancestry from the root down to this component, display names only. */
90
+ path: string[];
91
+ /** True for `React.memo` components and `PureComponent` classes: props alone decide whether they re-render. */
92
+ memoized: boolean;
93
+ /** Time spent in this component's own render (children excluded), in ms, when React exposes it (dev/profiling builds). */
94
+ selfDuration?: number;
95
+ /** Time spent rendering this component and everything below it that rendered in the same commit, in ms. */
96
+ treeDuration?: number;
97
+ /** Source location of the element that rendered this component, when React exposes it (dev builds). */
98
+ source?: SourceLocation;
59
99
  /** Human-readable explanations and suggested fixes. */
60
100
  reasons: string[];
61
101
  /** `performance.now()` (or `Date.now()`) when the report was produced. */
@@ -72,7 +112,7 @@ interface Options {
72
112
  include?: ComponentMatcher[];
73
113
  /** Components never to track, even when marked. */
74
114
  exclude?: ComponentMatcher[];
75
- /** Capture `useState`/`useReducer`/`useContext` values and diff them. Default true. */
115
+ /** Diff hook state and contexts of function components. Default true. */
76
116
  trackHooks?: boolean;
77
117
  /** Report re-renders caused by genuine changes too, not only avoidable ones. Default false. */
78
118
  logAll?: boolean;
@@ -84,27 +124,26 @@ interface Options {
84
124
  collapse?: boolean;
85
125
  /** Console-like sink used for printing. Default `console`. */
86
126
  console?: Pick<Console, 'log' | 'group' | 'groupCollapsed' | 'groupEnd' | 'warn'>;
127
+ /** Skip commits caused by Fast Refresh / hot module replacement. Default true. */
128
+ ignoreHotReload?: boolean;
129
+ /** Stop printing a component after this many reports (0 = unlimited). The notifier still receives them. Default 0. */
130
+ maxReportsPerComponent?: number;
87
131
  }
88
- /**
89
- * The React object to patch. Structural so that both `import React from 'react'`
90
- * and `import * as React from 'react'` type-check.
91
- */
92
- type ReactLike = Pick<typeof ReactNS, 'createElement' | 'memo' | 'forwardRef' | 'useRef'> & Partial<Pick<typeof ReactNS, 'useState' | 'useReducer' | 'useContext' | 'useSyncExternalStore'>>;
93
132
  /** Marker static: `MyComponent.rerenderLens = true` opts a component in. */
94
133
  declare const MARKER: "rerenderLens";
95
134
 
96
- type AnyType = any;
97
135
  declare function getDisplayName(type: unknown): string;
98
- /** Map an element type to its tracked wrapper (or return it unchanged). */
99
- declare function resolveType(type: AnyType): AnyType;
136
+ /** Decide whether a component type is tracked under the given options. */
137
+ declare function shouldTrack(type: unknown, o: Options): boolean;
100
138
  /**
101
- * Patch `React.createElement` (and the state hooks) so tracked components report
102
- * their re-renders. Returns a function that undoes the patch.
139
+ * Start reporting re-renders of tracked components. Observes React commits
140
+ * through the DevTools global hook; nothing in React is patched or wrapped.
141
+ * Returns a function that stops reporting.
103
142
  */
104
- declare function init(R: ReactLike, options?: Options): () => void;
105
- /** Update options at runtime. Include/exclude changes apply to elements created afterwards. */
143
+ declare function init(options?: Options): () => void;
144
+ /** Merge options at runtime. Takes effect on the next commit. */
106
145
  declare function configure(options: Options): void;
107
- /** Restore the original React functions. Already-created wrappers keep working but stop reporting. */
146
+ /** Stop reporting and restore the DevTools hook callback. */
108
147
  declare function disable(): void;
109
148
  declare function isEnabled(): boolean;
110
149
  /**
@@ -113,6 +152,86 @@ declare function isEnabled(): boolean;
113
152
  */
114
153
  declare function track<T>(component: T, name?: string): T;
115
154
 
155
+ /**
156
+ * Fiber-tree inspection. Runs after every React commit via the DevTools global
157
+ * hook, exactly like React DevTools itself. Element types are never touched, so
158
+ * Fast Refresh, memo comparators and component identity all stay intact.
159
+ */
160
+
161
+ interface ContextDependency {
162
+ context: {
163
+ displayName?: string;
164
+ _currentValue?: unknown;
165
+ };
166
+ memoizedValue: unknown;
167
+ next: ContextDependency | null;
168
+ }
169
+ interface Fiber {
170
+ tag: number;
171
+ key: unknown;
172
+ type: unknown;
173
+ elementType: unknown;
174
+ stateNode: unknown;
175
+ memoizedProps: Record<string, unknown> | null;
176
+ memoizedState: unknown;
177
+ alternate: Fiber | null;
178
+ child: Fiber | null;
179
+ sibling: Fiber | null;
180
+ return: Fiber | null;
181
+ flags?: number;
182
+ /** React 16 name for `flags`. */
183
+ effectTag?: number;
184
+ dependencies?: {
185
+ firstContext: ContextDependency | null;
186
+ } | null;
187
+ _debugOwner?: {
188
+ type?: unknown;
189
+ name?: string;
190
+ } | null;
191
+ _debugHookTypes?: string[] | null;
192
+ /** React <= 18 with the JSX dev transform. */
193
+ _debugSource?: {
194
+ fileName?: string;
195
+ lineNumber?: number;
196
+ columnNumber?: number;
197
+ } | null;
198
+ /** React 19: an Error captured where the element was created. */
199
+ _debugStack?: {
200
+ stack?: string;
201
+ } | string | null;
202
+ actualDuration?: number;
203
+ }
204
+ interface FiberRoot {
205
+ current: Fiber;
206
+ }
207
+ /** What `react-dom` passes to `hook.inject`. */
208
+ interface RendererInfo {
209
+ version?: string;
210
+ /** 1 = development build, 0 = production build. */
211
+ bundleType?: number;
212
+ rendererPackageName?: string;
213
+ }
214
+ interface DevtoolsHook {
215
+ renderers: Map<number, unknown>;
216
+ supportsFiber: boolean;
217
+ inject(renderer: unknown): number;
218
+ onCommitFiberRoot?: (id: number, root: FiberRoot, priority?: unknown, didError?: boolean) => void;
219
+ onCommitFiberUnmount?: (id: number, fiber: Fiber) => void;
220
+ onScheduleFiberRoot?: (id: number, root: FiberRoot, children: unknown) => void;
221
+ onPostCommitFiberRoot?: (id: number, root: FiberRoot) => void;
222
+ isDisabled?: boolean;
223
+ }
224
+ /**
225
+ * Make sure `window.__REACT_DEVTOOLS_GLOBAL_HOOK__` exists. React DOM only looks
226
+ * for it once, when the `react-dom` module is evaluated, so this must run before
227
+ * that unless the React DevTools extension or Fast Refresh already created it.
228
+ */
229
+ declare function ensureDevtoolsHook(): DevtoolsHook;
230
+ /** The renderers React registered on the hook (version and dev/prod bundle type). */
231
+ declare function getRenderers(): RendererInfo[];
232
+ /** True when every registered React renderer is a production build (names minified, no hook labels). */
233
+ declare function isProductionReact(): boolean;
234
+
116
235
  type HookOptions = Pick<Options, 'notifier' | 'silent' | 'console' | 'collapse' | 'logAll'>;
117
236
  /**
118
237
  * Track one component from the inside, without patching React:
@@ -147,12 +266,23 @@ declare function diffRecords(prev: Record<string, unknown> | undefined | null, n
147
266
 
148
267
  interface BuildInput {
149
268
  component: string;
269
+ instanceId?: number;
150
270
  renderCount: number;
151
271
  prevProps: Record<string, unknown>;
152
272
  nextProps: Record<string, unknown>;
153
273
  propChanges: Change[];
154
274
  stateChanges?: Change[];
155
275
  hookChanges?: HookChange[];
276
+ parent?: ParentInfo | null;
277
+ owner?: string | null;
278
+ path?: string[];
279
+ /** Default true: `useWhyRerender` and the console output assume the component decides on props alone. */
280
+ memoized?: boolean;
281
+ selfDuration?: number;
282
+ treeDuration?: number;
283
+ commitId?: number;
284
+ commitPriority?: CommitPriority;
285
+ source?: SourceLocation;
156
286
  }
157
287
  declare function buildReport(input: BuildInput): RenderReport;
158
288
  declare function summarize(report: RenderReport): string;
@@ -175,13 +305,53 @@ declare function createCollector(): Collector;
175
305
  declare function combineNotifiers(...notifiers: Array<Notifier | undefined | null | false>): Notifier;
176
306
 
177
307
  declare const DEVTOOLS_MARKER: "__rerenderLens";
178
- declare const PROTOCOL_VERSION = 1;
308
+ /** Bumped when the message shape or bridge API changes in a way the panel must know about. */
309
+ declare const PROTOCOL_VERSION = 2;
179
310
  interface DevtoolsMessage {
180
311
  [DEVTOOLS_MARKER]: true;
181
312
  version: number;
182
313
  type: 'report' | 'clear' | 'hello';
183
314
  payload?: unknown;
184
315
  }
316
+ /** Payload of a `hello` message. */
317
+ interface HelloPayload {
318
+ /** Buffered reports available for `replay()` / `pull()`. */
319
+ count: number;
320
+ /** Library version. */
321
+ library: string;
322
+ protocol: number;
323
+ react: RendererInfo[];
324
+ /** True when every React renderer on the page is a production build. */
325
+ production: boolean;
326
+ enabled: boolean;
327
+ options: SerializableOptions;
328
+ /** Who created this bridge: the page's own `init` call, or the extension's injected copy. */
329
+ source: 'page' | 'extension';
330
+ /** True when the extension injected a copy of the library into this page (whether or not it is the active one). */
331
+ injected: boolean;
332
+ /** Roots React scheduled (dev builds) and commits observed since `init`; the gap is work that never committed. */
333
+ scheduled: number;
334
+ commits: number;
335
+ }
336
+ declare global {
337
+ interface Window {
338
+ /** Set by the extension's inject script: version of the injected library. */
339
+ __RERENDER_LENS_INJECTED__?: string;
340
+ }
341
+ }
342
+ /** `Options` with matchers as strings (`"Name"` or `"/regex/flags"`) and no functions. */
343
+ interface SerializableOptions {
344
+ trackAllMemoized?: boolean;
345
+ trackAllComponents?: boolean;
346
+ include?: string[];
347
+ exclude?: string[];
348
+ trackHooks?: boolean;
349
+ logAll?: boolean;
350
+ silent?: boolean;
351
+ collapse?: boolean;
352
+ ignoreHotReload?: boolean;
353
+ maxReportsPerComponent?: number;
354
+ }
185
355
  interface DevtoolsNotifierOptions {
186
356
  /** How many reports to keep for `replay()`. Default 300. */
187
357
  bufferSize?: number;
@@ -189,6 +359,25 @@ interface DevtoolsNotifierOptions {
189
359
  target?: Pick<Window, 'postMessage'>;
190
360
  /** Max serialization depth. Default 6. */
191
361
  maxDepth?: number;
362
+ /** Flash the DOM of components that re-rendered avoidably. Default false; toggle later with `flashAvoidable`. */
363
+ flashAvoidable?: boolean;
364
+ /** Reported in `info()`. The extension's inject script passes `'extension'`. Default `'page'`. */
365
+ source?: 'page' | 'extension';
366
+ }
367
+ interface InspectResult {
368
+ component: string;
369
+ instanceId: number | null;
370
+ tracked: boolean;
371
+ path: string[];
372
+ /** Serialized reports for this instance, oldest first. */
373
+ reports: unknown[];
374
+ }
375
+ interface PullResult {
376
+ /** Sequence number of the newest report included; pass it back as `since`. */
377
+ seq: number;
378
+ reports: unknown[];
379
+ /** True when the caller missed reports that fell out of the buffer. */
380
+ dropped: boolean;
192
381
  }
193
382
  interface DevtoolsBridge {
194
383
  /** Re-post every buffered report (a panel that opened late calls this). */
@@ -196,6 +385,21 @@ interface DevtoolsBridge {
196
385
  clear(): void;
197
386
  readonly size: number;
198
387
  readonly version: number;
388
+ /** Library version. */
389
+ readonly library: string;
390
+ /** Everything the panel shows in its status line. */
391
+ info(): HelloPayload;
392
+ /** Reports newer than `since`. Lets a panel poll without any content script. */
393
+ pull(since?: number): PullResult;
394
+ /** Merge options at runtime; matchers are strings (`"/^Grid/"` becomes a RegExp). */
395
+ configure(options: SerializableOptions): SerializableOptions;
396
+ getOptions(): SerializableOptions;
397
+ /** Outline the DOM of an instance in the page; `null` clears. */
398
+ highlight(instanceId: number | null): boolean;
399
+ /** Turn the avoidable-render flash on or off. */
400
+ flashAvoidable(on: boolean): void;
401
+ /** Re-render details for a DOM node (DevTools' `$0`). */
402
+ inspect(node: unknown): InspectResult | null;
199
403
  }
200
404
  declare global {
201
405
  interface Window {
@@ -204,10 +408,16 @@ declare global {
204
408
  }
205
409
  /** Convert a report into a structured-clone-safe value (functions, elements, cycles removed). */
206
410
  declare function serialize(value: unknown, maxDepth?: number, seen?: WeakSet<object>, depth?: number): unknown;
411
+ /** The current options in a JSON-safe form (function matchers and custom consoles are dropped). */
412
+ declare function serializeOptions(o: Options): SerializableOptions;
413
+ declare function deserializeOptions(o: SerializableOptions): Options;
207
414
  /**
208
415
  * A notifier that posts every report on `window` for a DevTools extension to pick up.
209
- * Also installs `window.__RERENDER_LENS_DEVTOOLS__` with `replay()` / `clear()`.
416
+ * Also installs `window.__RERENDER_LENS_DEVTOOLS__` with `replay()` / `clear()` / `pull()` and friends.
210
417
  */
211
418
  declare function createDevtoolsNotifier(options?: DevtoolsNotifierOptions): Notifier;
212
419
 
213
- export { type Change, type ChangeKind, type Collector, type ComponentMatcher, DEVTOOLS_MARKER, type DevtoolsBridge, type DevtoolsMessage, type DevtoolsNotifierOptions, type HookChange, MARKER, type Notifier, type Options, PROTOCOL_VERSION, type RenderReport, type RenderTrigger, buildReport, classify, combineNotifiers, configure, createCollector, createDevtoolsNotifier, deepEqual, diffRecords, disable, getDisplayName, init, isEnabled, printReport, resolveType, serialize, summarize, track, useWhyRerender };
420
+ /** Library version, injected by the build; "dev" when running from source. */
421
+ declare const VERSION: string;
422
+
423
+ export { type Change, type ChangeKind, type Collector, type ComponentMatcher, DEVTOOLS_MARKER, type DevtoolsBridge, type DevtoolsMessage, type DevtoolsNotifierOptions, type HelloPayload, type HookChange, type InspectResult, MARKER, type Notifier, type Options, PROTOCOL_VERSION, type ParentInfo, type PullResult, type RenderReport, type RenderTrigger, type RendererInfo, type SerializableOptions, type SourceLocation, VERSION, buildReport, classify, combineNotifiers, configure, createCollector, createDevtoolsNotifier, deepEqual, deserializeOptions, diffRecords, disable, ensureDevtoolsHook, getDisplayName, getRenderers, init, isEnabled, isProductionReact, printReport, serialize, serializeOptions, shouldTrack, summarize, track, useWhyRerender };