react-dev-insight 1.0.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/README.md ADDED
@@ -0,0 +1,282 @@
1
+ <div align="center">
2
+ <img src="https://raw.githubusercontent.com/santhoshkumar7300/react-dev-insight/main/assets/main-demo.gif" alt="React Dev Insight Demo" width="100%" />
3
+
4
+ <br/>
5
+
6
+ <h1>✨ React Dev Insight</h1>
7
+
8
+ <p>
9
+ <strong>A lightning-fast, zero-overhead developer experience plugin for React.</strong>
10
+ </p>
11
+
12
+ <p>
13
+ <a href="https://www.npmjs.com/package/react-dev-insight"><img src="https://img.shields.io/npm/v/react-dev-insight?style=flat-square&color=2dd4bf" alt="NPM Version" /></a>
14
+ <a href="https://www.npmjs.com/package/react-dev-insight"><img src="https://img.shields.io/npm/dm/react-dev-insight?style=flat-square&color=6366f1" alt="NPM Downloads" /></a>
15
+ <a href="https://github.com/santhoshkumar7300/react-dev-insight/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/react-dev-insight?style=flat-square&color=ec4899" alt="License" /></a>
16
+ <img src="https://img.shields.io/badge/TypeScript-Ready-blue?style=flat-square&logo=typescript&logoColor=white" alt="TypeScript Support" />
17
+ </p>
18
+ </div>
19
+
20
+ <br/>
21
+
22
+ > **Stop guessing why your React app feels slow.**
23
+ >
24
+ > `react-dev-insight` is a lightweight, strictly development-only plugin that actively visualizes component rendering behavior right in your browser. Framed within a stunning modern glassmorphism UI, it transforms performance debugging from a terminal-diving chore into an intuitive, visual joy.
25
+
26
+ ---
27
+
28
+ <br/>
29
+
30
+ ## 🌟 Why React Dev Insight?
31
+
32
+ React performance debugging usually means reading through console logs or clicking through complex flame graphs. We built **React Dev Insight** to bring the insights directly to your UI.
33
+
34
+ It runs passively while you build, instantly flagging anomalies, tracking render counts, and drawing the exact physical prop-drilling paths right on your screen.
35
+
36
+ <br/>
37
+
38
+ ### Core Features
39
+
40
+ - **💥 Visual Render Highlights:** See exactly _what_ renders and _when_ it renders with sleek, animated glowing boundaries.
41
+ - **� The Cascade Trace (NEW):** Visually map out parent-to-child render cascading via glowing SVG bezier curves in real-time. Literally watch the chain reaction.
42
+ - **🔍 Prop Diff Tooltips:** Hover over a highlight to instantly see the exact prop mutations that triggered the cycle.
43
+ - **🚨 Thrash Detection:** Automatically flags components caught in frequent render loops (e.g., 3+ renders in <500ms) with a warning glow.
44
+ - **🎛️ Glassmorphism Dashboard:** A beautiful, draggable floating control panel summarizing global render metrics and top offenders.
45
+ - **🚀 Zero Production Bloat:** Aggressively tree-shakeable. In `NODE_ENV === 'production'`, it compiles down to absolutely nothing. Zero DOM bloat, zero overhead.
46
+
47
+ <br/>
48
+
49
+ ---
50
+
51
+ <br/>
52
+
53
+ ## 📸 See It In Action
54
+
55
+ <div align="center">
56
+ <table>
57
+ <tr>
58
+ <td align="center">
59
+ <strong>Re-render Highlights</strong><br/><br/>
60
+ <img src="https://raw.githubusercontent.com/santhoshkumar7300/react-dev-insight/main/assets/rerender.gif" alt="Re-render Highlight" width="400"/>
61
+ </td>
62
+ <td align="center">
63
+ <strong>Render Badges</strong><br/><br/>
64
+ <img src="https://raw.githubusercontent.com/santhoshkumar7300/react-dev-insight/main/assets/badge.gif" alt="Render Badge" width="400"/>
65
+ </td>
66
+ </tr>
67
+ <tr>
68
+ <td align="center">
69
+ <br/><strong>Prop Diff Tooltip</strong><br/><br/>
70
+ <img src="https://raw.githubusercontent.com/santhoshkumar7300/react-dev-insight/main/assets/tooltip.gif" alt="Prop Diff Tooltip" width="400"/>
71
+ </td>
72
+ <td align="center">
73
+ <br/><strong>Mini Dashboard</strong><br/><br/>
74
+ <img src="https://raw.githubusercontent.com/santhoshkumar7300/react-dev-insight/main/assets/dashboard.gif" alt="Mini Dashboard" width="400"/>
75
+ </td>
76
+ </tr>
77
+ </table>
78
+ </div>
79
+
80
+ <br/>
81
+
82
+ ---
83
+
84
+ <br/>
85
+
86
+ ## 📦 Installation
87
+
88
+ ```bash
89
+ npm install react-dev-insight --save-dev
90
+ ```
91
+
92
+ _(Also supports `yarn add -D` and `pnpm add -D`)_
93
+
94
+ <br/>
95
+
96
+ ---
97
+
98
+ <br/>
99
+
100
+ ## 🛠️ Usage
101
+
102
+ ### 1. The Global Provider
103
+
104
+ Wrap your application root inside the `DevInsightProvider`.
105
+
106
+ _Note: In production builds, this provider returns your `children` directly, causing zero performance or DOM overhead._
107
+
108
+ ```tsx
109
+ import { DevInsightProvider } from "react-dev-insight";
110
+
111
+ function App() {
112
+ return (
113
+ <DevInsightProvider>
114
+ <YourApp />
115
+ </DevInsightProvider>
116
+ );
117
+ }
118
+ ```
119
+
120
+ ### 2. Tracking Components
121
+
122
+ **Method A: `withDevInsight` HOC (Easiest)**
123
+
124
+ Wrap any component to instantly track its render behavior.
125
+
126
+ ```tsx
127
+ import React from "react";
128
+ import { withDevInsight } from "react-dev-insight";
129
+
130
+ const DashboardWidget = ({ data }) => {
131
+ return (
132
+ <div className="card">
133
+ <h3>{data.title}</h3>
134
+ </div>
135
+ );
136
+ };
137
+
138
+ // Optionally pass a custom display name for the dashboard leaderboard
139
+ export default withDevInsight(DashboardWidget, "DashboardWidget");
140
+ ```
141
+
142
+ **Method B: `useRenderTracker` Hook (Advanced)**
143
+
144
+ For precise control, attach the returned `ref` to the outermost DOM element of your component.
145
+
146
+ ```tsx
147
+ import React from "react";
148
+ import { useRenderTracker } from "react-dev-insight";
149
+
150
+ export const SettingsPanel = (props) => {
151
+ // Pass the component name, and the props to diff against
152
+ const ref = useRenderTracker<HTMLDivElement>("SettingsPanel", props);
153
+
154
+ return (
155
+ <div ref={ref} className="settings-container">
156
+ <h1>Settings</h1>
157
+ </div>
158
+ );
159
+ };
160
+ ```
161
+
162
+ <br/>
163
+
164
+ ---
165
+
166
+ <br/>
167
+
168
+ ## 🧠 Performance Philosophy
169
+
170
+ **"Do no harm."**
171
+
172
+ Debugging tools shouldn't distort the very performance benchmarks they are trying to measure. `react-dev-insight` uses a completely decoupled architecture to ensure your app stays fast:
173
+
174
+ 1. **O(1) Interception:** It intercepts props and state using ultra-fast shallow diffing and stores them in a memory-safe `WeakMap` to prevent memory leaks.
175
+ 2. **Asynchronous Rendering:** The visual overlays are rendered in a topmost React `<Portal>` bound directly to `document.body`. We **never** pollute your application's DOM hierarchy with wrapper `div`s that break Grid or Flexbox layouts.
176
+ 3. **Pure CSS Animations:** The glowing highlights and bounding boxes use hardware-accelerated CSS `transform` and `opacity` properties, offloading the work to the GPU and preventing main-thread blocking.
177
+
178
+ <br/>
179
+
180
+ ---
181
+
182
+ <br/>
183
+
184
+ ## 🆚 Comparisons
185
+
186
+ ### vs. `why-did-you-render`
187
+
188
+ `why-did-you-render` is fantastic, but it forces you to dig through terminal logs to understand what happened. **React Dev Insight is a visual-first tool.** It brings the insights directly to your UI, making it instantly obvious which parts of your screen are thrashing.
189
+
190
+ ### vs. React DevTools Profiler
191
+
192
+ The official Profiler is unmatched for deep-dive microscopic analysis. However, it requires you to actively record, click through flame graphs, and parse milliseconds. **React Dev Insight is passive and ambient.** Leave it running while you build, and it intuitively flags anomalies in real-time.
193
+
194
+ <br/>
195
+
196
+ ---
197
+
198
+ <br/>
199
+
200
+ ## 📚 API Reference
201
+
202
+ ### `<DevInsightProvider />`
203
+
204
+ The outermost wrapper required to mount the visual overlay portal.
205
+
206
+ - **Props:** `{ children: ReactNode }`
207
+
208
+ ### `withDevInsight(Component, [name])`
209
+
210
+ A Higher-Order Component to track a specific React component.
211
+
212
+ - **`Component`**: The React component to wrap.
213
+ - **`name`** _(Optional)_: A string to identify the component in the dashboard. Defaults to the component's display name.
214
+
215
+ ### `useRenderTracker(name, props)`
216
+
217
+ A hook for fine-grained tracking control.
218
+
219
+ - **`name`**: String identifier for the component.
220
+ - **`props`**: The component's props object (used for diffing).
221
+ - **Returns**: A `React.RefObject` that _must_ be attached to the component's root DOM node.
222
+
223
+ <br/>
224
+
225
+ ---
226
+
227
+ <br/>
228
+
229
+ ## 🗺️ Roadmap
230
+
231
+ - [ ] Support for tracking pure Context Consumer updates (bypassing props).
232
+ - [ ] Integration with Next.js App Router RSCs (Client Boundaries).
233
+ - [ ] Customizable theme injection (Dark/Light mode overriding).
234
+ - [ ] Export render data to JSON format.
235
+
236
+ <br/>
237
+
238
+ ---
239
+
240
+ <br/>
241
+
242
+ ## 🤝 Contributing
243
+
244
+ We welcome community contributions! Please read our [Contributing Guidelines](CONTRIBUTING.md) to get started.
245
+
246
+ 1. Fork the repository.
247
+ 2. Create a feature branch (`git checkout -b feature/amazing-feature`).
248
+ 3. Commit your changes (`git commit -m 'feat: added something amazing'`).
249
+ 4. Push to the branch (`git push origin feature/amazing-feature`).
250
+ 5. Open a Pull Request.
251
+
252
+ <br/>
253
+
254
+ ---
255
+
256
+ <br/>
257
+
258
+ ## ❓ FAQ
259
+
260
+ **Q: Will this slow down my production app?**
261
+ A: **No.** The entire library is wrapped in strict `process.env.NODE_ENV !== 'production'` checks. When you run your production build, modern bundlers (Webpack, Vite, Rollup) will completely dead-code-eliminate the package.
262
+
263
+ **Q: Why does the highlight box look misaligned?**
264
+ A: Ensure you are attaching the `useRenderTracker` ref to the _outermost_ DOM element of your component. Text nodes or React Fragments without a physical bounding box cannot be traced visually.
265
+
266
+ **Q: Does it support React 18 Concurrent Mode?**
267
+ A: Yes! It includes specific debouncing logic to ignore Strict Mode double-invocations so your render counts remain perfectly accurate.
268
+
269
+ <br/>
270
+
271
+ ---
272
+
273
+ <br/>
274
+
275
+ ## 📄 License
276
+
277
+ Distributed under the MIT License. See `LICENSE` for more information.
278
+
279
+ <div align="center">
280
+ <br/>
281
+ Built with ❤️ for the React ecosystem.
282
+ </div>
@@ -0,0 +1,110 @@
1
+ // src/core/renderStore.ts
2
+ var renderStore = /* @__PURE__ */ new WeakMap();
3
+ function getRenderData(key) {
4
+ if (!renderStore.has(key)) {
5
+ renderStore.set(key, {
6
+ count: 0,
7
+ lastRenderTime: 0,
8
+ renderTimings: [],
9
+ prevProps: {},
10
+ changedProps: {},
11
+ isClient: typeof window !== "undefined",
12
+ elementRef: null,
13
+ cause: "mount",
14
+ isFrequent: false,
15
+ sourceRef: null
16
+ });
17
+ }
18
+ return renderStore.get(key);
19
+ }
20
+ function updateRenderData(key, data) {
21
+ const existing = getRenderData(key);
22
+ renderStore.set(key, { ...existing, ...data });
23
+ }
24
+
25
+ // src/core/diff.ts
26
+ function shallowDiff(prevProps, nextProps) {
27
+ const changes = {};
28
+ const prevKeys = Object.keys(prevProps);
29
+ const nextKeys = Object.keys(nextProps);
30
+ const allKeys = /* @__PURE__ */ new Set([...prevKeys, ...nextKeys]);
31
+ for (const key of allKeys) {
32
+ if (key === "children") continue;
33
+ const oldVal = prevProps[key];
34
+ const newVal = nextProps[key];
35
+ if (!Object.is(oldVal, newVal)) {
36
+ changes[key] = { oldVal, newVal };
37
+ }
38
+ }
39
+ return changes;
40
+ }
41
+
42
+ // src/core/tracker.ts
43
+ var listeners = /* @__PURE__ */ new Set();
44
+ function subscribeToReRenders(listener) {
45
+ listeners.add(listener);
46
+ return () => {
47
+ listeners.delete(listener);
48
+ };
49
+ }
50
+ var notifyTimeouts = /* @__PURE__ */ new WeakMap();
51
+ function notifyListeners(ref, options, elementRef) {
52
+ const data = getRenderData(ref);
53
+ const eventData = {
54
+ name: options.componentName,
55
+ count: data.count,
56
+ changedProps: data.changedProps,
57
+ timestamp: data.lastRenderTime,
58
+ elementRef,
59
+ cause: data.cause,
60
+ isFrequent: data.isFrequent,
61
+ sourceRef: data.sourceRef
62
+ };
63
+ if (notifyTimeouts.has(ref)) {
64
+ clearTimeout(notifyTimeouts.get(ref));
65
+ }
66
+ const timeout = setTimeout(() => {
67
+ listeners.forEach((listener) => listener(ref, eventData));
68
+ }, 16);
69
+ notifyTimeouts.set(ref, timeout);
70
+ }
71
+ var lastTrackedRender = null;
72
+ function trackRender(ref, props, options) {
73
+ if (process.env.NODE_ENV !== "development") return;
74
+ const data = getRenderData(ref);
75
+ const now = performance.now();
76
+ const changedProps = shallowDiff(data.prevProps, props);
77
+ const hasPropChanges = Object.keys(changedProps).length > 0;
78
+ if (data.count > 0 && !hasPropChanges && now - data.lastRenderTime < 10) {
79
+ return;
80
+ }
81
+ let cause = "mount";
82
+ if (data.count > 0) {
83
+ cause = hasPropChanges ? "props" : "state/context";
84
+ }
85
+ let sourceRef = null;
86
+ if (cause === "props" && lastTrackedRender && now - lastTrackedRender.timestamp < 10 && lastTrackedRender.ref !== ref) {
87
+ sourceRef = lastTrackedRender.ref;
88
+ }
89
+ lastTrackedRender = { ref, timestamp: now };
90
+ const newTimings = [...data.renderTimings, now].slice(-10);
91
+ let isFrequent = false;
92
+ if (newTimings.length >= 3) {
93
+ const timeSinceThirdLast = now - newTimings[newTimings.length - 3];
94
+ isFrequent = timeSinceThirdLast < 500;
95
+ }
96
+ updateRenderData(ref, {
97
+ count: data.count + 1,
98
+ lastRenderTime: now,
99
+ renderTimings: newTimings,
100
+ prevProps: { ...props },
101
+ changedProps,
102
+ cause,
103
+ isFrequent,
104
+ sourceRef
105
+ });
106
+ }
107
+
108
+ export { getRenderData, notifyListeners, renderStore, shallowDiff, subscribeToReRenders, trackRender, updateRenderData };
109
+ //# sourceMappingURL=chunk-HH72HK3H.mjs.map
110
+ //# sourceMappingURL=chunk-HH72HK3H.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/renderStore.ts","../src/core/diff.ts","../src/core/tracker.ts"],"names":[],"mappings":"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","file":"chunk-HH72HK3H.mjs","sourcesContent":["export type RenderCause = \"mount\" | \"props\" | \"state/context\" | \"unknown\";\n\nexport interface RenderData {\n count: number;\n lastRenderTime: number;\n renderTimings: number[];\n prevProps: Record<string, any>;\n changedProps: Record<string, { oldVal: any; newVal: any }>;\n isClient: boolean;\n elementRef: HTMLElement | null;\n cause: RenderCause;\n isFrequent: boolean;\n sourceRef: object | null;\n}\n\n// Global WeakMap to store render data per component instance without memory leaks.\n// Keys are the component's internal unique object reference or an instance id.\nexport const renderStore = new WeakMap<object, RenderData>();\n\nexport function getRenderData(key: object): RenderData {\n if (!renderStore.has(key)) {\n renderStore.set(key, {\n count: 0,\n lastRenderTime: 0,\n renderTimings: [],\n prevProps: {},\n changedProps: {},\n isClient: typeof window !== \"undefined\",\n elementRef: null,\n cause: \"mount\",\n isFrequent: false,\n sourceRef: null,\n });\n }\n return renderStore.get(key)!;\n}\n\nexport function updateRenderData(key: object, data: Partial<RenderData>) {\n const existing = getRenderData(key);\n renderStore.set(key, { ...existing, ...data });\n}\n","export function shallowDiff(\n prevProps: Record<string, any>,\n nextProps: Record<string, any>,\n): Record<string, { oldVal: any; newVal: any }> {\n const changes: Record<string, { oldVal: any; newVal: any }> = {};\n\n const prevKeys = Object.keys(prevProps);\n const nextKeys = Object.keys(nextProps);\n\n const allKeys = new Set([...prevKeys, ...nextKeys]);\n\n for (const key of allKeys) {\n if (key === \"children\") continue;\n\n const oldVal = prevProps[key];\n const newVal = nextProps[key];\n\n // Performance-safe exact match check (Object.is handles NaN and -0 properly)\n if (!Object.is(oldVal, newVal)) {\n // Don't flag function reference changes as heavily since they often change (e.g., inline arrow functions)\n // but they are technically changed. We record them.\n changes[key] = { oldVal, newVal };\n }\n }\n\n return changes;\n}\n","import {\n getRenderData,\n renderStore,\n updateRenderData,\n RenderCause,\n} from \"./renderStore\";\nimport { shallowDiff } from \"./diff\";\n\nexport interface TrackerOptions {\n componentName: string;\n}\n\n/**\n * Listener function that receives render metadata broadcasts.\n */\nexport type RenderListener = (\n componentRef: object,\n data: {\n name: string;\n count: number;\n changedProps: Record<string, { oldVal: any; newVal: any }>;\n timestamp: number;\n elementRef: HTMLElement | null;\n cause: RenderCause;\n isFrequent: boolean;\n sourceRef: object | null;\n },\n) => void;\n\nconst listeners = new Set<RenderListener>();\n\n/**\n * Subscribes to global render events emitted by the tracker.\n * @returns An unsubscribe function.\n */\nexport function subscribeToReRenders(listener: RenderListener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n}\n\n// Map to handle debouncing of notifications\nconst notifyTimeouts = new WeakMap<object, ReturnType<typeof setTimeout>>();\n\n/**\n * Debounces and notifies all subscribed listeners of a component's render event.\n */\nexport function notifyListeners(\n ref: object,\n options: TrackerOptions,\n elementRef: HTMLElement | null,\n) {\n const data = getRenderData(ref);\n const eventData = {\n name: options.componentName,\n count: data.count,\n changedProps: data.changedProps,\n timestamp: data.lastRenderTime,\n elementRef,\n cause: data.cause,\n isFrequent: data.isFrequent,\n sourceRef: data.sourceRef,\n };\n\n // Debouncing the broadcast slightly to avoid overwhelming the overlay\n // especially in React 18 concurrent features.\n if (notifyTimeouts.has(ref)) {\n clearTimeout(notifyTimeouts.get(ref)!);\n }\n\n const timeout = setTimeout(() => {\n listeners.forEach((listener) => listener(ref, eventData));\n }, 16); // ~1 frame\n\n notifyTimeouts.set(ref, timeout);\n}\n\n// Track the immediately preceding render in the current Javascript execution macrotask\n// This allows us to map out the render \"cascade\" tree (e.g. Parent -> Child)\nlet lastTrackedRender: { ref: object; timestamp: number } | null = null;\n\n/**\n * Synchronously tracks a component render, calculates prop diffs, and computes the cause.\n * Includes protection against React 18 Strict Mode double-invocations.\n */\nexport function trackRender(\n ref: object,\n props: Record<string, any>,\n options: TrackerOptions,\n) {\n if (process.env.NODE_ENV !== \"development\") return;\n\n const data = getRenderData(ref);\n const now = performance.now();\n\n const changedProps = shallowDiff(data.prevProps, props);\n const hasPropChanges = Object.keys(changedProps).length > 0;\n\n // Mitigation for React 18 Strict Mode double-renders:\n // If no props changed and the render happened within 10ms of the last one,\n // it is almost certainly a strict-mode double invocation. Ignore it.\n if (data.count > 0 && !hasPropChanges && now - data.lastRenderTime < 10) {\n return;\n }\n\n let cause: RenderCause = \"mount\";\n if (data.count > 0) {\n cause = hasPropChanges ? \"props\" : \"state/context\";\n }\n\n // Check for rendering cascade (rendered within 5ms of another component)\n let sourceRef: object | null = null;\n if (\n cause === \"props\" &&\n lastTrackedRender &&\n now - lastTrackedRender.timestamp < 10 &&\n lastTrackedRender.ref !== ref\n ) {\n sourceRef = lastTrackedRender.ref;\n }\n\n lastTrackedRender = { ref, timestamp: now };\n\n const newTimings = [...data.renderTimings, now].slice(-10); // Keep last 10\n\n // Frequent render check: 3 renders within 500ms\n let isFrequent = false;\n if (newTimings.length >= 3) {\n const timeSinceThirdLast = now - newTimings[newTimings.length - 3];\n isFrequent = timeSinceThirdLast < 500;\n }\n\n // Update state for this component instance\n updateRenderData(ref, {\n count: data.count + 1,\n lastRenderTime: now,\n renderTimings: newTimings,\n prevProps: { ...props },\n changedProps,\n cause,\n isFrequent,\n sourceRef,\n });\n}\n"]}