react-dev-profiler 1.0.0 → 1.0.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/LICENSE +1 -1
- package/README.md +2 -2
- package/dist/index.cjs +189 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +189 -31
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/index.css +0 -179
- package/dist/index.css.map +0 -1
package/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c)
|
|
3
|
+
Copyright (c) 2026 Frederic Denis (billywild87)
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
package/README.md
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Real-time React performance monitoring for development. Zero overhead in production.
|
|
4
4
|
|
|
5
|
-

|
|
6
|
-

|
|
5
|
+
[](https://www.npmjs.com/package/react-dev-profiler)
|
|
6
|
+
[](./LICENSE)
|
|
7
7
|
|
|
8
8
|
## What it does
|
|
9
9
|
|
package/dist/index.cjs
CHANGED
|
@@ -27,9 +27,6 @@ module.exports = __toCommonJS(index_exports);
|
|
|
27
27
|
// src/DevProfiler.tsx
|
|
28
28
|
var import_react4 = require("react");
|
|
29
29
|
|
|
30
|
-
// src/DevProfiler.module.css
|
|
31
|
-
var DevProfiler_default = {};
|
|
32
|
-
|
|
33
30
|
// src/types.ts
|
|
34
31
|
var HISTORY_SIZE = 60;
|
|
35
32
|
var INITIAL_PROFILER = {
|
|
@@ -63,6 +60,156 @@ var __DEV__ = typeof process !== "undefined" ? process.env.NODE_ENV !== "product
|
|
|
63
60
|
|
|
64
61
|
// src/hooks.ts
|
|
65
62
|
var import_react = require("react");
|
|
63
|
+
|
|
64
|
+
// src/styles.ts
|
|
65
|
+
var s = {
|
|
66
|
+
wrapper: {
|
|
67
|
+
display: "contents"
|
|
68
|
+
},
|
|
69
|
+
panel: {
|
|
70
|
+
position: "fixed",
|
|
71
|
+
zIndex: 99999,
|
|
72
|
+
background: "rgba(8, 8, 8, 0.95)",
|
|
73
|
+
border: "1px solid #222",
|
|
74
|
+
borderRadius: 10,
|
|
75
|
+
padding: "10px 14px",
|
|
76
|
+
minWidth: 220,
|
|
77
|
+
boxShadow: "0 8px 32px rgba(0, 0, 0, 0.7)",
|
|
78
|
+
fontFamily: "'SF Mono', 'Fira Code', monospace",
|
|
79
|
+
backdropFilter: "blur(12px)",
|
|
80
|
+
userSelect: "none",
|
|
81
|
+
color: "#ccc",
|
|
82
|
+
fontSize: 11
|
|
83
|
+
},
|
|
84
|
+
panelHeader: {
|
|
85
|
+
display: "flex",
|
|
86
|
+
justifyContent: "space-between",
|
|
87
|
+
alignItems: "center",
|
|
88
|
+
marginBottom: 8,
|
|
89
|
+
cursor: "grab",
|
|
90
|
+
touchAction: "none"
|
|
91
|
+
},
|
|
92
|
+
panelTitle: {
|
|
93
|
+
color: "#666",
|
|
94
|
+
fontSize: 9,
|
|
95
|
+
fontWeight: 700,
|
|
96
|
+
letterSpacing: 1.5,
|
|
97
|
+
textTransform: "uppercase",
|
|
98
|
+
display: "flex",
|
|
99
|
+
alignItems: "center",
|
|
100
|
+
gap: 6
|
|
101
|
+
},
|
|
102
|
+
instanceBadge: {
|
|
103
|
+
background: "#222",
|
|
104
|
+
color: "#888",
|
|
105
|
+
fontSize: 8,
|
|
106
|
+
padding: "1px 5px",
|
|
107
|
+
borderRadius: 4,
|
|
108
|
+
letterSpacing: 0.5,
|
|
109
|
+
textTransform: "none"
|
|
110
|
+
},
|
|
111
|
+
headerActions: {
|
|
112
|
+
display: "flex",
|
|
113
|
+
alignItems: "center",
|
|
114
|
+
gap: 8
|
|
115
|
+
},
|
|
116
|
+
iconBtn: {
|
|
117
|
+
background: "none",
|
|
118
|
+
border: "none",
|
|
119
|
+
color: "#444",
|
|
120
|
+
cursor: "pointer",
|
|
121
|
+
padding: 0,
|
|
122
|
+
lineHeight: 1,
|
|
123
|
+
display: "flex",
|
|
124
|
+
alignItems: "center",
|
|
125
|
+
transition: "color 0.15s"
|
|
126
|
+
},
|
|
127
|
+
iconBtnActive: {
|
|
128
|
+
color: "#4ade80"
|
|
129
|
+
},
|
|
130
|
+
closeBtn: {
|
|
131
|
+
background: "none",
|
|
132
|
+
border: "none",
|
|
133
|
+
color: "#444",
|
|
134
|
+
cursor: "pointer",
|
|
135
|
+
fontSize: 13,
|
|
136
|
+
padding: 0,
|
|
137
|
+
lineHeight: 1
|
|
138
|
+
},
|
|
139
|
+
body: {
|
|
140
|
+
display: "flex",
|
|
141
|
+
flexDirection: "column",
|
|
142
|
+
gap: 5
|
|
143
|
+
},
|
|
144
|
+
section: {
|
|
145
|
+
color: "#444",
|
|
146
|
+
fontSize: 8,
|
|
147
|
+
fontWeight: 600,
|
|
148
|
+
letterSpacing: 1,
|
|
149
|
+
textTransform: "uppercase",
|
|
150
|
+
marginTop: 2
|
|
151
|
+
},
|
|
152
|
+
separator: {
|
|
153
|
+
height: 1,
|
|
154
|
+
background: "#1a1a1a",
|
|
155
|
+
margin: "2px 0"
|
|
156
|
+
},
|
|
157
|
+
row: {
|
|
158
|
+
display: "flex",
|
|
159
|
+
justifyContent: "space-between",
|
|
160
|
+
alignItems: "center",
|
|
161
|
+
padding: "1px 0"
|
|
162
|
+
},
|
|
163
|
+
rowLabel: {
|
|
164
|
+
color: "#555",
|
|
165
|
+
fontSize: 10
|
|
166
|
+
},
|
|
167
|
+
rowValue: {
|
|
168
|
+
fontSize: 11,
|
|
169
|
+
fontWeight: 600
|
|
170
|
+
},
|
|
171
|
+
miniRow: {
|
|
172
|
+
display: "flex",
|
|
173
|
+
justifyContent: "space-between",
|
|
174
|
+
color: "#444",
|
|
175
|
+
fontSize: 9,
|
|
176
|
+
marginTop: -2,
|
|
177
|
+
marginBottom: 2
|
|
178
|
+
},
|
|
179
|
+
graphWrap: {
|
|
180
|
+
marginTop: 4
|
|
181
|
+
},
|
|
182
|
+
toggleBtn: {
|
|
183
|
+
position: "fixed",
|
|
184
|
+
zIndex: 99998,
|
|
185
|
+
minWidth: 28,
|
|
186
|
+
height: 24,
|
|
187
|
+
borderRadius: 7,
|
|
188
|
+
border: "1px solid #2a2a2a",
|
|
189
|
+
background: "rgba(20, 20, 20, 0.9)",
|
|
190
|
+
cursor: "pointer",
|
|
191
|
+
display: "flex",
|
|
192
|
+
alignItems: "center",
|
|
193
|
+
justifyContent: "center",
|
|
194
|
+
padding: "0 6px",
|
|
195
|
+
backdropFilter: "blur(8px)"
|
|
196
|
+
},
|
|
197
|
+
toggleFps: {
|
|
198
|
+
fontFamily: "'SF Mono', 'Fira Code', monospace",
|
|
199
|
+
fontSize: 10,
|
|
200
|
+
fontWeight: 700,
|
|
201
|
+
letterSpacing: -0.5
|
|
202
|
+
},
|
|
203
|
+
footer: {
|
|
204
|
+
marginTop: 8,
|
|
205
|
+
color: "#333",
|
|
206
|
+
fontSize: 8,
|
|
207
|
+
textAlign: "center"
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
var FLASH_OUTLINE = "2px solid rgba(99, 102, 241, 0.8)";
|
|
211
|
+
|
|
212
|
+
// src/hooks.ts
|
|
66
213
|
function useAnchorPosition(ref, position = "bottom-left") {
|
|
67
214
|
const [pos, setPos] = (0, import_react.useState)({ top: 0, left: 0 });
|
|
68
215
|
(0, import_react.useEffect)(() => {
|
|
@@ -180,8 +327,12 @@ function useRenderFlash(wrapperRef, open) {
|
|
|
180
327
|
mutationCount.current++;
|
|
181
328
|
if (!wrapperRef.current || mutationCount.current <= 1) return;
|
|
182
329
|
const el = wrapperRef.current;
|
|
183
|
-
el.
|
|
184
|
-
|
|
330
|
+
el.style.outline = FLASH_OUTLINE;
|
|
331
|
+
el.style.outlineOffset = "-2px";
|
|
332
|
+
setTimeout(() => {
|
|
333
|
+
el.style.outline = "";
|
|
334
|
+
el.style.outlineOffset = "";
|
|
335
|
+
}, 150);
|
|
185
336
|
});
|
|
186
337
|
observer.observe(wrapperRef.current, { childList: true, subtree: true });
|
|
187
338
|
return () => observer.disconnect();
|
|
@@ -215,7 +366,7 @@ function FrameTimeGraph({ history }) {
|
|
|
215
366
|
const w = 140;
|
|
216
367
|
const h = 32;
|
|
217
368
|
const barW = Math.max(1, w / HISTORY_SIZE - 0.5);
|
|
218
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
369
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: s.graphWrap, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: w, height: h, style: { display: "block" }, children: [
|
|
219
370
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { width: w, height: h, rx: 3, fill: "#111" }),
|
|
220
371
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
221
372
|
"line",
|
|
@@ -248,16 +399,17 @@ function FrameTimeGraph({ history }) {
|
|
|
248
399
|
] }) });
|
|
249
400
|
}
|
|
250
401
|
function StatRow({ label, value, sub, color = "#4ade80" }) {
|
|
251
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
252
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
402
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: s.row, children: [
|
|
403
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: s.rowLabel, children: label }),
|
|
253
404
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
|
|
254
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
405
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { ...s.rowValue, color }, children: value }),
|
|
255
406
|
sub && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "#444", fontSize: 9, marginLeft: 4 }, children: sub })
|
|
256
407
|
] })
|
|
257
408
|
] });
|
|
258
409
|
}
|
|
259
410
|
function getPanelStyle(pos, offset, position) {
|
|
260
411
|
const style = {
|
|
412
|
+
...s.panel,
|
|
261
413
|
transform: `translate(${offset.x}px, ${offset.y}px)`
|
|
262
414
|
};
|
|
263
415
|
if (position.startsWith("bottom")) {
|
|
@@ -369,23 +521,24 @@ function DevStatsPanel({
|
|
|
369
521
|
const fps = stats.frameTime > 0 ? Math.round(1e3 / stats.frameTime) : 0;
|
|
370
522
|
const memoGain = stats.profiler.baseDuration > 0 ? Math.round((1 - stats.profiler.actualDuration / stats.profiler.baseDuration) * 100) : 0;
|
|
371
523
|
const p99Color = stats.frameTimeP99 > 33 ? "#ef4444" : stats.frameTimeP99 > 16.67 ? "#f59e0b" : "#4ade80";
|
|
524
|
+
const exportStyle = exported ? { ...s.iconBtn, ...s.iconBtnActive } : s.iconBtn;
|
|
372
525
|
return (0, import_react_dom.createPortal)(
|
|
373
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
526
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: getPanelStyle(pos, offset, position), children: [
|
|
374
527
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
375
528
|
"div",
|
|
376
529
|
{
|
|
377
|
-
|
|
530
|
+
style: s.panelHeader,
|
|
378
531
|
...dragHandlers,
|
|
379
532
|
children: [
|
|
380
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
533
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: s.panelTitle, children: [
|
|
381
534
|
"Dev Profiler",
|
|
382
|
-
instanceCount > 1 && instanceId && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
535
|
+
instanceCount > 1 && instanceId && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: s.instanceBadge, children: instanceId })
|
|
383
536
|
] }),
|
|
384
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
537
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: s.headerActions, children: [
|
|
385
538
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
386
539
|
"button",
|
|
387
540
|
{
|
|
388
|
-
|
|
541
|
+
style: exportStyle,
|
|
389
542
|
onClick: handleExport,
|
|
390
543
|
title: exported ? "Copied!" : "Copy stats to clipboard",
|
|
391
544
|
children: exported ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { width: "10", height: "10", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.75.75 0 0 1 1.06-1.06L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0z" }) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: "10", height: "10", viewBox: "0 0 16 16", fill: "currentColor", children: [
|
|
@@ -394,17 +547,17 @@ function DevStatsPanel({
|
|
|
394
547
|
] })
|
|
395
548
|
}
|
|
396
549
|
),
|
|
397
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
398
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
550
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: s.iconBtn, onClick: handleReset, title: "Reset counters", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { width: "10", height: "10", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M14 1a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1h-4a1 1 0 0 1 0-2h1.6A6 6 0 0 0 2.07 7.5a1 1 0 1 1-1.97-.36A8 8 0 0 1 13 3.35V2a1 1 0 0 1 1-1zM2 15a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1h4a1 1 0 0 1 0 2H4.4A6 6 0 0 0 13.93 8.5a1 1 0 1 1 1.97.36A8 8 0 0 1 3 12.65V14a1 1 0 0 1-1 1z" }) }) }),
|
|
551
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: s.closeBtn, onClick: onClose, children: "\u2715" })
|
|
399
552
|
] })
|
|
400
553
|
]
|
|
401
554
|
}
|
|
402
555
|
),
|
|
403
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
404
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
556
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: s.body, children: [
|
|
557
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: s.section, children: "Rendering" }),
|
|
405
558
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatRow, { label: "Frame time", value: `${stats.frameTime.toFixed(1)}ms`, sub: `${fps} fps`, color: ftColor }),
|
|
406
559
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FrameTimeGraph, { history: stats.frameTimeHistory }),
|
|
407
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
560
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: s.miniRow, children: [
|
|
408
561
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
|
|
409
562
|
"min ",
|
|
410
563
|
stats.frameTimeMin.toFixed(1)
|
|
@@ -420,23 +573,23 @@ function DevStatsPanel({
|
|
|
420
573
|
] }),
|
|
421
574
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatRow, { label: "Renders/s", value: String(stats.rendersPerSecond), color: rpsColor }),
|
|
422
575
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatRow, { label: "Long tasks", value: String(stats.longTasks), color: stats.longTasks > 0 ? "#f59e0b" : "#4ade80" }),
|
|
423
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
424
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
576
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: s.separator }),
|
|
577
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: s.section, children: "React Profiler" }),
|
|
425
578
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatRow, { label: "Phase", value: stats.profiler.phase, color: "#888" }),
|
|
426
579
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatRow, { label: "Render", value: `${stats.profiler.actualDuration.toFixed(2)}ms`, color: actualColor }),
|
|
427
580
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatRow, { label: "Base (no memo)", value: `${stats.profiler.baseDuration.toFixed(2)}ms`, color: "#888" }),
|
|
428
581
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatRow, { label: "Memo gain", value: `${memoGain}%`, color: memoGain > 50 ? "#4ade80" : memoGain > 20 ? "#f59e0b" : "#ef4444" }),
|
|
429
582
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatRow, { label: "Commits", value: String(stats.profiler.commitCount) }),
|
|
430
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
431
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
583
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: s.separator }),
|
|
584
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: s.section, children: "DOM" }),
|
|
432
585
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatRow, { label: "Nodes", value: stats.domNodes.toLocaleString() }),
|
|
433
586
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatRow, { label: "Mutations", value: String(stats.domMutations) }),
|
|
434
587
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatRow, { label: "Size", value: stats.dimensions, color: "#888" }),
|
|
435
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
436
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
588
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: s.separator }),
|
|
589
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: s.section, children: "Memory" }),
|
|
437
590
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatRow, { label: "JS Heap", value: stats.memory > 0 ? `${stats.memory} MB` : "N/A" })
|
|
438
591
|
] }),
|
|
439
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
592
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: s.footer, children: "Ctrl+I to toggle" })
|
|
440
593
|
] }),
|
|
441
594
|
document.body
|
|
442
595
|
);
|
|
@@ -447,7 +600,7 @@ var import_react3 = require("react");
|
|
|
447
600
|
var import_react_dom2 = require("react-dom");
|
|
448
601
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
449
602
|
function getButtonStyle(pos, position) {
|
|
450
|
-
const style = {};
|
|
603
|
+
const style = { ...s.toggleBtn };
|
|
451
604
|
if (position.startsWith("bottom")) {
|
|
452
605
|
style.bottom = window.innerHeight - pos.top + 8;
|
|
453
606
|
} else {
|
|
@@ -491,11 +644,10 @@ function ToggleButton({
|
|
|
491
644
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
492
645
|
"button",
|
|
493
646
|
{
|
|
494
|
-
className: DevProfiler_default.toggleBtn,
|
|
495
647
|
onClick,
|
|
496
648
|
title: "Dev Profiler (Ctrl+I)",
|
|
497
649
|
style: getButtonStyle(pos, position),
|
|
498
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", {
|
|
650
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { ...s.toggleFps, color: fpsColor }, children: fps })
|
|
499
651
|
}
|
|
500
652
|
),
|
|
501
653
|
document.body
|
|
@@ -558,7 +710,7 @@ function DevProfiler({
|
|
|
558
710
|
return () => window.removeEventListener(TOGGLE_EVENT, handler);
|
|
559
711
|
}, []);
|
|
560
712
|
if (!__DEV__) return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children });
|
|
561
|
-
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { ref: wrapperRef,
|
|
713
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { ref: wrapperRef, style: { display: "contents" }, children: [
|
|
562
714
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Profiler, { id: "DevProfiler", onRender, children }),
|
|
563
715
|
!open && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ToggleButton, { targetRef: wrapperRef, onClick: toggle, position }),
|
|
564
716
|
open && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
@@ -594,6 +746,12 @@ function DevProfiler({
|
|
|
594
746
|
* @author Frederic Denis (billywild87) — https://github.com/billywild87
|
|
595
747
|
* @license MIT
|
|
596
748
|
*/
|
|
749
|
+
/**
|
|
750
|
+
* @module react-dev-profiler
|
|
751
|
+
* @description Inline styles for the profiler UI — no external CSS needed.
|
|
752
|
+
* @author Frederic Denis (billywild87) — https://github.com/billywild87
|
|
753
|
+
* @license MIT
|
|
754
|
+
*/
|
|
597
755
|
/**
|
|
598
756
|
* @module react-dev-profiler
|
|
599
757
|
* @description Custom hooks that power the profiler's data collection.
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/DevProfiler.tsx","../src/DevProfiler.module.css","../src/types.ts","../src/env.ts","../src/hooks.ts","../src/DevStatsPanel.tsx","../src/ToggleButton.tsx"],"sourcesContent":["/**\n * react-dev-profiler\n * Real-time React performance monitoring for development.\n *\n * @author Frederic Denis (billywild87) — https://github.com/billywild87\n * @license MIT\n */\n\nexport { DevProfiler } from './DevProfiler'\nexport type { DevStats, ReactProfilerData, PanelPosition } from './types'\n","/**\n * @module react-dev-profiler\n * @description Main wrapper component — wraps your app and enables profiling.\n * @author Frederic Denis (billywild87) — https://github.com/billywild87\n * @license MIT\n */\n\nimport { Profiler, useRef, useState, useEffect, useCallback, type ReactNode } from 'react'\nimport styles from './DevProfiler.module.css'\nimport { type ReactProfilerData, type PanelPosition, INITIAL_PROFILER } from './types'\nimport { __DEV__ } from './env'\nimport { useDomTracker, useRenderRate, useRenderFlash, useLongTasks } from './hooks'\nimport { DevStatsPanel } from './DevStatsPanel'\nimport { ToggleButton } from './ToggleButton'\n\n/*\n * Global keyboard shortcut: Ctrl+I (or Cmd+I) to toggle the profiler.\n * Registered once and kept alive across HMR reloads via a window flag.\n */\nconst TOGGLE_EVENT = 'devprofiler:toggle'\nconst BOUND_KEY = '__devprofiler_bound'\n\nif (typeof window !== 'undefined' && __DEV__ && !(window as any)[BOUND_KEY]) {\n (window as any)[BOUND_KEY] = true\n window.addEventListener('keydown', (e) => {\n if ((e.ctrlKey || e.metaKey) && e.key === 'i') {\n e.preventDefault()\n window.dispatchEvent(new CustomEvent(TOGGLE_EVENT))\n }\n })\n}\n\n/* Module-level registry to track active profiler instances. */\nlet instanceCounter = 0\nconst activeInstances = new Set<string>()\n\n/**\n * Wrap your application (or any subtree) with `<DevProfiler>` to enable\n * real-time performance monitoring during development.\n *\n * In production builds the profiler is completely stripped — children\n * are rendered as-is with zero overhead.\n *\n * @example\n * ```tsx\n * import { DevProfiler } from 'react-dev-profiler'\n *\n * function App() {\n * return (\n * <DevProfiler>\n * <YourApp />\n * </DevProfiler>\n * )\n * }\n * ```\n */\nexport function DevProfiler({\n children,\n position = 'bottom-left',\n id,\n}: {\n children: ReactNode\n /** Where to anchor the panel. @default 'bottom-left' */\n position?: PanelPosition\n /** Optional identifier — shown in the panel when multiple instances are active. */\n id?: string\n}) {\n const wrapperRef = useRef<HTMLDivElement>(null)\n const [open, setOpen] = useState(false)\n const toggle = useCallback(() => setOpen(prev => !prev), [])\n\n // Generate a stable instance ID (user-provided or auto-incremented).\n const instanceId = useRef(id ?? `profiler-${++instanceCounter}`).current\n\n useEffect(() => {\n activeInstances.add(instanceId)\n return () => { activeInstances.delete(instanceId) }\n }, [instanceId])\n\n const domTracker = useDomTracker(wrapperRef, open)\n const renderRate = useRenderRate()\n const longTasks = useLongTasks(open)\n useRenderFlash(wrapperRef, open)\n\n const profilerData = useRef<ReactProfilerData>({ ...INITIAL_PROFILER })\n const onRender = useCallback((_id: string, phase: string, actualDuration: number, baseDuration: number) => {\n profilerData.current = {\n phase,\n actualDuration,\n baseDuration,\n commitCount: profilerData.current.commitCount + 1,\n }\n renderRate.tick()\n }, [renderRate])\n\n const handleReset = useCallback(() => {\n domTracker.reset()\n renderRate.reset()\n longTasks.reset()\n profilerData.current = { ...INITIAL_PROFILER }\n }, [domTracker, renderRate, longTasks])\n\n useEffect(() => {\n const handler = () => setOpen(prev => !prev)\n window.addEventListener(TOGGLE_EVENT, handler)\n return () => window.removeEventListener(TOGGLE_EVENT, handler)\n }, [])\n\n if (!__DEV__) return <>{children}</>\n\n return (\n <div ref={wrapperRef} className={styles.wrapper}>\n <Profiler id=\"DevProfiler\" onRender={onRender}>\n {children}\n </Profiler>\n {!open && <ToggleButton targetRef={wrapperRef} onClick={toggle} position={position} />}\n {open && (\n <DevStatsPanel\n targetRef={wrapperRef}\n domTracker={domTracker}\n renderRate={renderRate}\n longTasks={longTasks}\n profilerData={profilerData}\n onClose={toggle}\n onReset={handleReset}\n position={position}\n instanceId={instanceId}\n instanceCount={activeInstances.size}\n />\n )}\n </div>\n )\n}\n","/**\n * react-dev-profiler — Styles\n * Dark, minimal overlay theme with glassmorphism.\n * Author: Frederic Denis (billywild87)\n */\n\n/* Wrapper — takes up the full space of the profiled subtree */\n.wrapper {\n height: 100%;\n width: 100%;\n}\n\n/* Panel — floating overlay anchored near the profiled element */\n.panel {\n position: fixed;\n z-index: 99999;\n background: rgba(8, 8, 8, 0.95);\n border: 1px solid #222;\n border-radius: 10px;\n padding: 10px 14px;\n min-width: 220px;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.7);\n font-family: 'SF Mono', 'Fira Code', monospace;\n backdrop-filter: blur(12px);\n user-select: none;\n}\n\n/* Draggable header */\n.panelHeader {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 8px;\n cursor: grab;\n touch-action: none;\n}\n\n.panelHeader:active {\n cursor: grabbing;\n}\n\n.panelTitle {\n color: #666;\n font-size: 9px;\n font-weight: 700;\n letter-spacing: 1.5px;\n text-transform: uppercase;\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.instanceBadge {\n background: #222;\n color: #888;\n font-size: 8px;\n padding: 1px 5px;\n border-radius: 4px;\n letter-spacing: 0.5px;\n text-transform: none;\n}\n\n.headerActions {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n/* Export button */\n.exportBtn {\n background: none;\n border: none;\n color: #444;\n cursor: pointer;\n padding: 0;\n line-height: 1;\n display: flex;\n align-items: center;\n transition: color 0.15s;\n}\n\n.exportBtn:hover {\n color: #4ade80;\n}\n\n.exportBtnActive {\n color: #4ade80;\n}\n\n/* Reset button */\n.resetBtn {\n background: none;\n border: none;\n color: #444;\n cursor: pointer;\n padding: 0;\n line-height: 1;\n display: flex;\n align-items: center;\n}\n\n.resetBtn:hover {\n color: #4ade80;\n}\n\n.closeBtn {\n background: none;\n border: none;\n color: #444;\n cursor: pointer;\n font-size: 13px;\n padding: 0;\n line-height: 1;\n}\n\n.closeBtn:hover {\n color: #888;\n}\n\n.body {\n display: flex;\n flex-direction: column;\n gap: 5px;\n}\n\n/* Stat rows */\n.section {\n color: #444;\n font-size: 8px;\n font-weight: 600;\n letter-spacing: 1px;\n text-transform: uppercase;\n margin-top: 2px;\n}\n\n.separator {\n height: 1px;\n background: #1a1a1a;\n margin: 2px 0;\n}\n\n.row {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 1px 0;\n}\n\n.rowLabel {\n color: #555;\n font-size: 10px;\n}\n\n.rowValue {\n font-size: 11px;\n font-weight: 600;\n}\n\n.miniRow {\n display: flex;\n justify-content: space-between;\n color: #444;\n font-size: 9px;\n margin-top: -2px;\n margin-bottom: 2px;\n}\n\n/* Frame time graph */\n.graphWrap {\n margin-top: 4px;\n}\n\n/* Toggle button — small floating FPS pill */\n.toggleBtn {\n position: fixed;\n z-index: 99998;\n min-width: 28px;\n height: 24px;\n border-radius: 7px;\n border: 1px solid #2a2a2a;\n background: rgba(20, 20, 20, 0.9);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0 6px;\n backdrop-filter: blur(8px);\n}\n\n.toggleBtn:hover {\n border-color: #333;\n}\n\n.toggleFps {\n font-family: 'SF Mono', 'Fira Code', monospace;\n font-size: 10px;\n font-weight: 700;\n letter-spacing: -0.5px;\n}\n\n/* Footer hint */\n.footer {\n margin-top: 8px;\n color: #333;\n font-size: 8px;\n text-align: center;\n}\n\n/* DOM mutation flash — brief outline pulse */\n.flash {\n outline: 2px solid rgba(99, 102, 241, 0.8);\n outline-offset: -2px;\n}\n","/**\n * @module react-dev-profiler\n * @description Type definitions and constants for the profiler.\n * @author Frederic Denis (billywild87) — https://github.com/billywild87\n * @license MIT\n */\n\n/** Data captured from React's built-in <Profiler> callback. */\nexport type ReactProfilerData = {\n phase: string\n actualDuration: number\n baseDuration: number\n commitCount: number\n}\n\n/** Aggregated stats displayed in the profiler panel. */\nexport type DevStats = {\n domMutations: number\n domNodes: number\n frameTime: number\n frameTimeMin: number\n frameTimeMax: number\n frameTimeP99: number\n frameTimeHistory: number[]\n longTasks: number\n rendersPerSecond: number\n memory: number\n dimensions: string\n profiler: ReactProfilerData\n}\n\n/** Where the panel anchors relative to the profiled element. */\nexport type PanelPosition = 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right'\n\n/** How many frames we keep in the rolling history graph. */\nexport const HISTORY_SIZE = 60\n\nexport const INITIAL_PROFILER: ReactProfilerData = {\n phase: 'mount',\n actualDuration: 0,\n baseDuration: 0,\n commitCount: 0,\n}\n\nexport const INITIAL_STATS: DevStats = {\n domMutations: 0,\n domNodes: 0,\n frameTime: 0,\n frameTimeMin: 0,\n frameTimeMax: 0,\n frameTimeP99: 0,\n frameTimeHistory: [],\n longTasks: 0,\n rendersPerSecond: 0,\n memory: 0,\n dimensions: '–',\n profiler: INITIAL_PROFILER,\n}\n\n/** Returns the value at a given percentile from a pre-sorted array. */\nexport function percentile(sorted: number[], p: number): number {\n if (sorted.length === 0) return 0\n const idx = Math.ceil((p / 100) * sorted.length) - 1\n return sorted[Math.max(0, idx)]\n}\n","/**\n * @module react-dev-profiler\n * @description Environment detection — works with Vite, webpack, Next.js, and any bundler.\n * @author Frederic Denis (billywild87) — https://github.com/billywild87\n * @license MIT\n */\n\n/**\n * True when running in a development environment.\n * Relies on `process.env.NODE_ENV` which all major bundlers replace at build time,\n * enabling dead-code elimination in production.\n */\nexport const __DEV__: boolean =\n typeof process !== 'undefined'\n ? process.env.NODE_ENV !== 'production'\n : true\n","/**\n * @module react-dev-profiler\n * @description Custom hooks that power the profiler's data collection.\n * @author Frederic Denis (billywild87) — https://github.com/billywild87\n * @license MIT\n */\n\nimport { useState, useEffect, useCallback, useRef } from 'react'\nimport styles from './DevProfiler.module.css'\nimport type { PanelPosition } from './types'\n\n/**\n * Tracks a corner position of a referenced element using ResizeObserver.\n * Used to anchor the panel and toggle button near the profiled component.\n */\nexport function useAnchorPosition(\n ref: React.RefObject<HTMLDivElement | null>,\n position: PanelPosition = 'bottom-left'\n) {\n const [pos, setPos] = useState({ top: 0, left: 0 })\n\n useEffect(() => {\n if (!ref.current) return\n const update = () => {\n if (!ref.current) return\n const rect = ref.current.getBoundingClientRect()\n const margin = 8\n switch (position) {\n case 'top-left':\n setPos({ top: rect.top + margin, left: rect.left + margin }); break\n case 'top-right':\n setPos({ top: rect.top + margin, left: rect.right - margin }); break\n case 'bottom-right':\n setPos({ top: rect.bottom - margin, left: rect.right - margin }); break\n case 'bottom-left':\n default:\n setPos({ top: rect.bottom - margin, left: rect.left + margin }); break\n }\n }\n const observer = new ResizeObserver(update)\n observer.observe(ref.current)\n observer.observe(document.documentElement)\n return () => observer.disconnect()\n }, [ref, position])\n\n return pos\n}\n\n/**\n * Makes an element draggable via Pointer Events.\n * Returns an offset to apply as CSS transform and event handlers for the drag handle.\n */\nexport function useDraggable() {\n const [offset, setOffset] = useState({ x: 0, y: 0 })\n const dragging = useRef(false)\n const start = useRef({ x: 0, y: 0 })\n\n const onPointerDown = useCallback((e: React.PointerEvent) => {\n dragging.current = true\n start.current = { x: e.clientX - offset.x, y: e.clientY - offset.y }\n ;(e.target as HTMLElement).setPointerCapture(e.pointerId)\n }, [offset])\n\n const onPointerMove = useCallback((e: React.PointerEvent) => {\n if (!dragging.current) return\n setOffset({\n x: e.clientX - start.current.x,\n y: e.clientY - start.current.y,\n })\n }, [])\n\n const onPointerUp = useCallback(() => {\n dragging.current = false\n }, [])\n\n return { offset, handlers: { onPointerDown, onPointerMove, onPointerUp } }\n}\n\n/**\n * Counts real DOM mutations via MutationObserver (ignoring class-only changes).\n * The node count is cached and only recalculated when the DOM actually changes,\n * so we don't pay the cost of querySelectorAll('*') every frame.\n */\nexport function useDomTracker(wrapperRef: React.RefObject<HTMLDivElement | null>, enabled: boolean) {\n const mutations = useRef(0)\n const nodeCount = useRef(0)\n const dirty = useRef(true)\n\n useEffect(() => {\n if (!enabled || !wrapperRef.current) return\n const observer = new MutationObserver((records) => {\n let realMutation = false\n for (const record of records) {\n if (record.type === 'attributes' && record.attributeName === 'class') continue\n realMutation = true\n }\n if (realMutation) {\n mutations.current++\n dirty.current = true\n }\n })\n observer.observe(wrapperRef.current, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: ['style'],\n })\n return () => observer.disconnect()\n }, [wrapperRef, enabled])\n\n const getNodeCount = useCallback(() => {\n if (dirty.current && wrapperRef.current) {\n nodeCount.current = wrapperRef.current.querySelectorAll('*').length\n dirty.current = false\n }\n return nodeCount.current\n }, [wrapperRef])\n\n const reset = useCallback(() => {\n mutations.current = 0\n dirty.current = true\n }, [])\n\n return { mutations, getNodeCount, reset }\n}\n\n/**\n * Measures how many React renders happen per second.\n * Call `tick()` from the <Profiler> onRender callback; the hook\n * snapshots the count every second and resets it.\n */\nexport function useRenderRate() {\n const renderCount = useRef(0)\n const rendersPerSecond = useRef(0)\n\n const tick = useCallback(() => {\n renderCount.current++\n }, [])\n\n useEffect(() => {\n const id = setInterval(() => {\n rendersPerSecond.current = renderCount.current\n renderCount.current = 0\n }, 1000)\n return () => clearInterval(id)\n }, [])\n\n const reset = useCallback(() => {\n renderCount.current = 0\n rendersPerSecond.current = 0\n }, [])\n\n return { rendersPerSecond, tick, reset }\n}\n\n/**\n * Flashes a subtle outline on the profiled element whenever the DOM mutates.\n * Skips the very first mutation (the initial mount) to avoid a flash on load.\n */\nexport function useRenderFlash(wrapperRef: React.RefObject<HTMLDivElement | null>, open: boolean) {\n const mutationCount = useRef(0)\n\n useEffect(() => {\n if (!open || !wrapperRef.current) return\n const observer = new MutationObserver(() => {\n mutationCount.current++\n if (!wrapperRef.current || mutationCount.current <= 1) return\n const el = wrapperRef.current\n el.classList.add(styles.flash)\n setTimeout(() => el.classList.remove(styles.flash), 150)\n })\n observer.observe(wrapperRef.current, { childList: true, subtree: true })\n return () => observer.disconnect()\n }, [wrapperRef, open])\n}\n\n/**\n * Detects browser \"long tasks\" (>50 ms) using PerformanceObserver.\n * Falls back silently in browsers that don't support the longtask entry type.\n */\nexport function useLongTasks(enabled: boolean) {\n const count = useRef(0)\n\n useEffect(() => {\n if (!enabled || typeof PerformanceObserver === 'undefined') return\n try {\n const observer = new PerformanceObserver((list) => {\n count.current += list.getEntries().length\n })\n observer.observe({ entryTypes: ['longtask'] })\n return () => observer.disconnect()\n } catch {\n // longtask not supported in this browser\n }\n }, [enabled])\n\n const reset = useCallback(() => { count.current = 0 }, [])\n\n return { count, reset }\n}\n","/**\n * @module react-dev-profiler\n * @description The main stats panel — renders all performance metrics in a floating overlay.\n * @author Frederic Denis (billywild87) — https://github.com/billywild87\n * @license MIT\n */\n\nimport { useState, useEffect, useCallback, useRef, type CSSProperties } from 'react'\nimport { createPortal } from 'react-dom'\nimport styles from './DevProfiler.module.css'\nimport { useAnchorPosition, useDraggable, useDomTracker, useRenderRate, useLongTasks } from './hooks'\nimport { type ReactProfilerData, type PanelPosition, type DevStats, HISTORY_SIZE, INITIAL_STATS, percentile } from './types'\n\n/** Rolling bar chart of frame times (last 60 samples). */\nfunction FrameTimeGraph({ history }: { history: number[] }) {\n const max = Math.max(33, ...history)\n const w = 140\n const h = 32\n const barW = Math.max(1, w / HISTORY_SIZE - 0.5)\n\n return (\n <div className={styles.graphWrap}>\n <svg width={w} height={h} style={{ display: 'block' }}>\n <rect width={w} height={h} rx={3} fill=\"#111\" />\n {/* 60 fps guideline */}\n <line x1={0} y1={h - (16.67 / max) * h} x2={w} y2={h - (16.67 / max) * h}\n stroke=\"#1a3a1a\" strokeWidth={1} />\n {/* 30 fps guideline */}\n <line x1={0} y1={h - (33 / max) * h} x2={w} y2={h - (33 / max) * h}\n stroke=\"#3a1a1a\" strokeWidth={1} />\n {history.map((ms, i) => {\n const x = (i / HISTORY_SIZE) * w\n const barH = Math.min((ms / max) * h, h)\n const color = ms > 33 ? '#ef4444' : ms > 16.67 ? '#f59e0b' : '#4ade80'\n return <rect key={i} x={x} y={h - barH} width={barW} height={barH} fill={color} opacity={0.8} rx={0.5} />\n })}\n </svg>\n </div>\n )\n}\n\n/** Single label → value row used throughout the panel. */\nfunction StatRow({ label, value, sub, color = '#4ade80' }: { label: string, value: string, sub?: string, color?: string }) {\n return (\n <div className={styles.row}>\n <span className={styles.rowLabel}>{label}</span>\n <span>\n <span className={styles.rowValue} style={{ color }}>{value}</span>\n {sub && <span style={{ color: '#444', fontSize: 9, marginLeft: 4 }}>{sub}</span>}\n </span>\n </div>\n )\n}\n\n/** Computes fixed-position styles based on the chosen panel position + drag offset. */\nfunction getPanelStyle(\n pos: { top: number; left: number },\n offset: { x: number; y: number },\n position: PanelPosition,\n): CSSProperties {\n const style: CSSProperties = {\n transform: `translate(${offset.x}px, ${offset.y}px)`,\n }\n if (position.startsWith('bottom')) {\n style.bottom = window.innerHeight - pos.top\n } else {\n style.top = pos.top\n }\n if (position.endsWith('right')) {\n style.right = window.innerWidth - pos.left\n } else {\n style.left = pos.left\n }\n return style\n}\n\n/** Floating stats panel portaled to document.body. */\nexport function DevStatsPanel({\n targetRef,\n domTracker,\n renderRate,\n longTasks,\n profilerData,\n onClose,\n onReset,\n position = 'bottom-left',\n instanceId,\n instanceCount = 1,\n}: {\n targetRef: React.RefObject<HTMLDivElement | null>\n domTracker: ReturnType<typeof useDomTracker>\n renderRate: ReturnType<typeof useRenderRate>\n longTasks: ReturnType<typeof useLongTasks>\n profilerData: React.RefObject<ReactProfilerData>\n onClose: () => void\n onReset: () => void\n position?: PanelPosition\n instanceId?: string\n instanceCount?: number\n}) {\n const [stats, setStats] = useState<DevStats>(INITIAL_STATS)\n const [exported, setExported] = useState(false)\n const lastFrame = useRef(performance.now())\n const frameTimeHistory = useRef<number[]>([])\n const allFrameTimes = useRef<number[]>([])\n const pos = useAnchorPosition(targetRef, position)\n const { offset, handlers: dragHandlers } = useDraggable()\n\n // Collects frame timing data every rAF, then snapshots stats once per second.\n useEffect(() => {\n let animId: number\n let frameCount = 0\n let frameTotalMs = 0\n let lastSecond = performance.now()\n\n const tick = () => {\n const now = performance.now()\n const delta = now - lastFrame.current\n lastFrame.current = now\n frameCount++\n frameTotalMs += delta\n\n if (now - lastSecond >= 1000) {\n const avgFrameTime = frameTotalMs / frameCount\n\n const hist = frameTimeHistory.current\n if (hist.length >= HISTORY_SIZE) hist.shift()\n hist.push(avgFrameTime)\n\n allFrameTimes.current.push(avgFrameTime)\n const sorted = [...allFrameTimes.current].sort((a, b) => a - b)\n\n const el = targetRef.current\n const dims = el ? `${el.offsetWidth} x ${el.offsetHeight}` : '–'\n\n // Chrome-only: performance.memory exposes JS heap usage\n const perf = performance as any\n const mem = perf.memory ? Math.round(perf.memory.usedJSHeapSize / 1024 / 1024) : 0\n\n setStats({\n domMutations: domTracker.mutations.current,\n domNodes: domTracker.getNodeCount(),\n frameTime: avgFrameTime,\n frameTimeMin: sorted[0] ?? 0,\n frameTimeMax: sorted[sorted.length - 1] ?? 0,\n frameTimeP99: percentile(sorted, 99),\n frameTimeHistory: [...hist],\n longTasks: longTasks.count.current,\n rendersPerSecond: renderRate.rendersPerSecond.current,\n memory: mem,\n dimensions: dims,\n profiler: { ...profilerData.current },\n })\n\n frameCount = 0\n frameTotalMs = 0\n lastSecond = now\n }\n animId = requestAnimationFrame(tick)\n }\n animId = requestAnimationFrame(tick)\n return () => cancelAnimationFrame(animId)\n }, [targetRef, domTracker, renderRate, longTasks])\n\n const handleReset = useCallback(() => {\n frameTimeHistory.current = []\n allFrameTimes.current = []\n setStats(INITIAL_STATS)\n onReset()\n }, [onReset])\n\n // Copy current stats as JSON to clipboard.\n const handleExport = useCallback(() => {\n const payload = { timestamp: new Date().toISOString(), ...stats }\n const json = JSON.stringify(payload, null, 2)\n try {\n navigator.clipboard.writeText(json).then(() => {\n setExported(true)\n setTimeout(() => setExported(false), 1200)\n })\n } catch {\n // Fallback for non-secure contexts\n const ta = document.createElement('textarea')\n ta.value = json\n ta.style.position = 'fixed'\n ta.style.opacity = '0'\n document.body.appendChild(ta)\n ta.select()\n document.execCommand('copy')\n document.body.removeChild(ta)\n setExported(true)\n setTimeout(() => setExported(false), 1200)\n }\n }, [stats])\n\n // Color thresholds: green = good, amber = warning, red = bad\n const ftColor = stats.frameTime > 33 ? '#ef4444' : stats.frameTime > 16.67 ? '#f59e0b' : '#4ade80'\n const rpsColor = stats.rendersPerSecond > 30 ? '#ef4444' : stats.rendersPerSecond > 10 ? '#f59e0b' : '#4ade80'\n const actualColor = stats.profiler.actualDuration > 16 ? '#ef4444' : stats.profiler.actualDuration > 8 ? '#f59e0b' : '#4ade80'\n const fps = stats.frameTime > 0 ? Math.round(1000 / stats.frameTime) : 0\n const memoGain = stats.profiler.baseDuration > 0\n ? Math.round((1 - stats.profiler.actualDuration / stats.profiler.baseDuration) * 100)\n : 0\n const p99Color = stats.frameTimeP99 > 33 ? '#ef4444' : stats.frameTimeP99 > 16.67 ? '#f59e0b' : '#4ade80'\n\n return createPortal(\n <div className={styles.panel} style={getPanelStyle(pos, offset, position)}>\n <div\n className={styles.panelHeader}\n {...dragHandlers}\n >\n <span className={styles.panelTitle}>\n Dev Profiler\n {instanceCount > 1 && instanceId && (\n <span className={styles.instanceBadge}>{instanceId}</span>\n )}\n </span>\n <div className={styles.headerActions}>\n <button\n className={`${styles.exportBtn} ${exported ? styles.exportBtnActive : ''}`}\n onClick={handleExport}\n title={exported ? 'Copied!' : 'Copy stats to clipboard'}\n >\n {exported ? (\n <svg width=\"10\" height=\"10\" viewBox=\"0 0 16 16\" fill=\"currentColor\">\n <path d=\"M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.75.75 0 0 1 1.06-1.06L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0z\"/>\n </svg>\n ) : (\n <svg width=\"10\" height=\"10\" viewBox=\"0 0 16 16\" fill=\"currentColor\">\n <path d=\"M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25z\"/>\n <path d=\"M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25z\"/>\n </svg>\n )}\n </button>\n <button className={styles.resetBtn} onClick={handleReset} title=\"Reset counters\">\n <svg width=\"10\" height=\"10\" viewBox=\"0 0 16 16\" fill=\"currentColor\">\n <path d=\"M14 1a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1h-4a1 1 0 0 1 0-2h1.6A6 6 0 0 0 2.07 7.5a1 1 0 1 1-1.97-.36A8 8 0 0 1 13 3.35V2a1 1 0 0 1 1-1zM2 15a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1h4a1 1 0 0 1 0 2H4.4A6 6 0 0 0 13.93 8.5a1 1 0 1 1 1.97.36A8 8 0 0 1 3 12.65V14a1 1 0 0 1-1 1z\"/>\n </svg>\n </button>\n <button className={styles.closeBtn} onClick={onClose}>✕</button>\n </div>\n </div>\n\n <div className={styles.body}>\n <span className={styles.section}>Rendering</span>\n <StatRow label=\"Frame time\" value={`${stats.frameTime.toFixed(1)}ms`} sub={`${fps} fps`} color={ftColor} />\n <FrameTimeGraph history={stats.frameTimeHistory} />\n <div className={styles.miniRow}>\n <span>min {stats.frameTimeMin.toFixed(1)}</span>\n <span>max {stats.frameTimeMax.toFixed(1)}</span>\n <span style={{ color: p99Color }}>p99 {stats.frameTimeP99.toFixed(1)}</span>\n </div>\n <StatRow label=\"Renders/s\" value={String(stats.rendersPerSecond)} color={rpsColor} />\n <StatRow label=\"Long tasks\" value={String(stats.longTasks)} color={stats.longTasks > 0 ? '#f59e0b' : '#4ade80'} />\n\n <div className={styles.separator} />\n <span className={styles.section}>React Profiler</span>\n <StatRow label=\"Phase\" value={stats.profiler.phase} color=\"#888\" />\n <StatRow label=\"Render\" value={`${stats.profiler.actualDuration.toFixed(2)}ms`} color={actualColor} />\n <StatRow label=\"Base (no memo)\" value={`${stats.profiler.baseDuration.toFixed(2)}ms`} color=\"#888\" />\n <StatRow label=\"Memo gain\" value={`${memoGain}%`} color={memoGain > 50 ? '#4ade80' : memoGain > 20 ? '#f59e0b' : '#ef4444'} />\n <StatRow label=\"Commits\" value={String(stats.profiler.commitCount)} />\n\n <div className={styles.separator} />\n <span className={styles.section}>DOM</span>\n <StatRow label=\"Nodes\" value={stats.domNodes.toLocaleString()} />\n <StatRow label=\"Mutations\" value={String(stats.domMutations)} />\n <StatRow label=\"Size\" value={stats.dimensions} color=\"#888\" />\n\n <div className={styles.separator} />\n <span className={styles.section}>Memory</span>\n <StatRow label=\"JS Heap\" value={stats.memory > 0 ? `${stats.memory} MB` : 'N/A'} />\n </div>\n\n <div className={styles.footer}>Ctrl+I to toggle</div>\n </div>,\n document.body\n )\n}\n","/**\n * @module react-dev-profiler\n * @description Minimal floating button that shows the current FPS at a glance.\n * @author Frederic Denis (billywild87) — https://github.com/billywild87\n * @license MIT\n */\n\nimport { useState, useEffect, useRef, type CSSProperties } from 'react'\nimport { createPortal } from 'react-dom'\nimport styles from './DevProfiler.module.css'\nimport { useAnchorPosition } from './hooks'\nimport type { PanelPosition } from './types'\n\n/** Computes fixed-position styles based on the chosen panel position. */\nfunction getButtonStyle(pos: { top: number; left: number }, position: PanelPosition): CSSProperties {\n const style: CSSProperties = {}\n if (position.startsWith('bottom')) {\n style.bottom = window.innerHeight - pos.top + 8\n } else {\n style.top = pos.top + 8\n }\n if (position.endsWith('right')) {\n style.right = window.innerWidth - pos.left\n } else {\n style.left = pos.left\n }\n return style\n}\n\n/** Small floating pill showing live FPS — click to open the full panel. */\nexport function ToggleButton({\n targetRef,\n onClick,\n position = 'bottom-left',\n}: {\n targetRef: React.RefObject<HTMLDivElement | null>\n onClick: () => void\n position?: PanelPosition\n}) {\n const pos = useAnchorPosition(targetRef, position)\n const [fps, setFps] = useState(0)\n const lastFrame = useRef(performance.now())\n\n useEffect(() => {\n let animId: number\n let count = 0\n let lastSecond = performance.now()\n\n const tick = () => {\n const now = performance.now()\n lastFrame.current = now\n count++\n if (now - lastSecond >= 1000) {\n setFps(count)\n count = 0\n lastSecond = now\n }\n animId = requestAnimationFrame(tick)\n }\n animId = requestAnimationFrame(tick)\n return () => cancelAnimationFrame(animId)\n }, [])\n\n const fpsColor = fps < 30 ? '#ef4444' : fps < 55 ? '#f59e0b' : '#4ade80'\n\n return createPortal(\n <button\n className={styles.toggleBtn}\n onClick={onClick}\n title=\"Dev Profiler (Ctrl+I)\"\n style={getButtonStyle(pos, position)}\n >\n <span className={styles.toggleFps} style={{ color: fpsColor }}>{fps}</span>\n </button>,\n document.body\n )\n}\n"],"mappings":"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","names":["import_react","import_react","import_react","import_react_dom","import_jsx_runtime","import_jsx_runtime"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/DevProfiler.tsx","../src/types.ts","../src/env.ts","../src/hooks.ts","../src/styles.ts","../src/DevStatsPanel.tsx","../src/ToggleButton.tsx"],"sourcesContent":["/**\n * react-dev-profiler\n * Real-time React performance monitoring for development.\n *\n * @author Frederic Denis (billywild87) — https://github.com/billywild87\n * @license MIT\n */\n\nexport { DevProfiler } from './DevProfiler'\nexport type { DevStats, ReactProfilerData, PanelPosition } from './types'\n","/**\n * @module react-dev-profiler\n * @description Main wrapper component — wraps your app and enables profiling.\n * @author Frederic Denis (billywild87) — https://github.com/billywild87\n * @license MIT\n */\n\nimport { Profiler, useRef, useState, useEffect, useCallback, type ReactNode } from 'react'\nimport { type ReactProfilerData, type PanelPosition, INITIAL_PROFILER } from './types'\nimport { __DEV__ } from './env'\nimport { useDomTracker, useRenderRate, useRenderFlash, useLongTasks } from './hooks'\nimport { DevStatsPanel } from './DevStatsPanel'\nimport { ToggleButton } from './ToggleButton'\n\n/*\n * Global keyboard shortcut: Ctrl+I (or Cmd+I) to toggle the profiler.\n * Registered once and kept alive across HMR reloads via a window flag.\n */\nconst TOGGLE_EVENT = 'devprofiler:toggle'\nconst BOUND_KEY = '__devprofiler_bound'\n\nif (typeof window !== 'undefined' && __DEV__ && !(window as any)[BOUND_KEY]) {\n (window as any)[BOUND_KEY] = true\n window.addEventListener('keydown', (e) => {\n if ((e.ctrlKey || e.metaKey) && e.key === 'i') {\n e.preventDefault()\n window.dispatchEvent(new CustomEvent(TOGGLE_EVENT))\n }\n })\n}\n\n/* Module-level registry to track active profiler instances. */\nlet instanceCounter = 0\nconst activeInstances = new Set<string>()\n\n/**\n * Wrap your application (or any subtree) with `<DevProfiler>` to enable\n * real-time performance monitoring during development.\n *\n * In production builds the profiler is completely stripped — children\n * are rendered as-is with zero overhead.\n *\n * @example\n * ```tsx\n * import { DevProfiler } from 'react-dev-profiler'\n *\n * function App() {\n * return (\n * <DevProfiler>\n * <YourApp />\n * </DevProfiler>\n * )\n * }\n * ```\n */\nexport function DevProfiler({\n children,\n position = 'bottom-left',\n id,\n}: {\n children: ReactNode\n /** Where to anchor the panel. @default 'bottom-left' */\n position?: PanelPosition\n /** Optional identifier — shown in the panel when multiple instances are active. */\n id?: string\n}) {\n const wrapperRef = useRef<HTMLDivElement>(null)\n const [open, setOpen] = useState(false)\n const toggle = useCallback(() => setOpen(prev => !prev), [])\n\n // Generate a stable instance ID (user-provided or auto-incremented).\n const instanceId = useRef(id ?? `profiler-${++instanceCounter}`).current\n\n useEffect(() => {\n activeInstances.add(instanceId)\n return () => { activeInstances.delete(instanceId) }\n }, [instanceId])\n\n const domTracker = useDomTracker(wrapperRef, open)\n const renderRate = useRenderRate()\n const longTasks = useLongTasks(open)\n useRenderFlash(wrapperRef, open)\n\n const profilerData = useRef<ReactProfilerData>({ ...INITIAL_PROFILER })\n const onRender = useCallback((_id: string, phase: string, actualDuration: number, baseDuration: number) => {\n profilerData.current = {\n phase,\n actualDuration,\n baseDuration,\n commitCount: profilerData.current.commitCount + 1,\n }\n renderRate.tick()\n }, [renderRate])\n\n const handleReset = useCallback(() => {\n domTracker.reset()\n renderRate.reset()\n longTasks.reset()\n profilerData.current = { ...INITIAL_PROFILER }\n }, [domTracker, renderRate, longTasks])\n\n useEffect(() => {\n const handler = () => setOpen(prev => !prev)\n window.addEventListener(TOGGLE_EVENT, handler)\n return () => window.removeEventListener(TOGGLE_EVENT, handler)\n }, [])\n\n if (!__DEV__) return <>{children}</>\n\n return (\n <div ref={wrapperRef} style={{ display: 'contents' }}>\n <Profiler id=\"DevProfiler\" onRender={onRender}>\n {children}\n </Profiler>\n {!open && <ToggleButton targetRef={wrapperRef} onClick={toggle} position={position} />}\n {open && (\n <DevStatsPanel\n targetRef={wrapperRef}\n domTracker={domTracker}\n renderRate={renderRate}\n longTasks={longTasks}\n profilerData={profilerData}\n onClose={toggle}\n onReset={handleReset}\n position={position}\n instanceId={instanceId}\n instanceCount={activeInstances.size}\n />\n )}\n </div>\n )\n}\n","/**\n * @module react-dev-profiler\n * @description Type definitions and constants for the profiler.\n * @author Frederic Denis (billywild87) — https://github.com/billywild87\n * @license MIT\n */\n\n/** Data captured from React's built-in <Profiler> callback. */\nexport type ReactProfilerData = {\n phase: string\n actualDuration: number\n baseDuration: number\n commitCount: number\n}\n\n/** Aggregated stats displayed in the profiler panel. */\nexport type DevStats = {\n domMutations: number\n domNodes: number\n frameTime: number\n frameTimeMin: number\n frameTimeMax: number\n frameTimeP99: number\n frameTimeHistory: number[]\n longTasks: number\n rendersPerSecond: number\n memory: number\n dimensions: string\n profiler: ReactProfilerData\n}\n\n/** Where the panel anchors relative to the profiled element. */\nexport type PanelPosition = 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right'\n\n/** How many frames we keep in the rolling history graph. */\nexport const HISTORY_SIZE = 60\n\nexport const INITIAL_PROFILER: ReactProfilerData = {\n phase: 'mount',\n actualDuration: 0,\n baseDuration: 0,\n commitCount: 0,\n}\n\nexport const INITIAL_STATS: DevStats = {\n domMutations: 0,\n domNodes: 0,\n frameTime: 0,\n frameTimeMin: 0,\n frameTimeMax: 0,\n frameTimeP99: 0,\n frameTimeHistory: [],\n longTasks: 0,\n rendersPerSecond: 0,\n memory: 0,\n dimensions: '–',\n profiler: INITIAL_PROFILER,\n}\n\n/** Returns the value at a given percentile from a pre-sorted array. */\nexport function percentile(sorted: number[], p: number): number {\n if (sorted.length === 0) return 0\n const idx = Math.ceil((p / 100) * sorted.length) - 1\n return sorted[Math.max(0, idx)]\n}\n","/**\n * @module react-dev-profiler\n * @description Environment detection — works with Vite, webpack, Next.js, and any bundler.\n * @author Frederic Denis (billywild87) — https://github.com/billywild87\n * @license MIT\n */\n\n/**\n * True when running in a development environment.\n * Relies on `process.env.NODE_ENV` which all major bundlers replace at build time,\n * enabling dead-code elimination in production.\n */\nexport const __DEV__: boolean =\n typeof process !== 'undefined'\n ? process.env.NODE_ENV !== 'production'\n : true\n","/**\n * @module react-dev-profiler\n * @description Custom hooks that power the profiler's data collection.\n * @author Frederic Denis (billywild87) — https://github.com/billywild87\n * @license MIT\n */\n\nimport { useState, useEffect, useCallback, useRef } from 'react'\nimport { FLASH_OUTLINE } from './styles'\nimport type { PanelPosition } from './types'\n\n/**\n * Tracks a corner position of a referenced element using ResizeObserver.\n * Used to anchor the panel and toggle button near the profiled component.\n */\nexport function useAnchorPosition(\n ref: React.RefObject<HTMLDivElement | null>,\n position: PanelPosition = 'bottom-left'\n) {\n const [pos, setPos] = useState({ top: 0, left: 0 })\n\n useEffect(() => {\n if (!ref.current) return\n const update = () => {\n if (!ref.current) return\n const rect = ref.current.getBoundingClientRect()\n const margin = 8\n switch (position) {\n case 'top-left':\n setPos({ top: rect.top + margin, left: rect.left + margin }); break\n case 'top-right':\n setPos({ top: rect.top + margin, left: rect.right - margin }); break\n case 'bottom-right':\n setPos({ top: rect.bottom - margin, left: rect.right - margin }); break\n case 'bottom-left':\n default:\n setPos({ top: rect.bottom - margin, left: rect.left + margin }); break\n }\n }\n const observer = new ResizeObserver(update)\n observer.observe(ref.current)\n observer.observe(document.documentElement)\n return () => observer.disconnect()\n }, [ref, position])\n\n return pos\n}\n\n/**\n * Makes an element draggable via Pointer Events.\n * Returns an offset to apply as CSS transform and event handlers for the drag handle.\n */\nexport function useDraggable() {\n const [offset, setOffset] = useState({ x: 0, y: 0 })\n const dragging = useRef(false)\n const start = useRef({ x: 0, y: 0 })\n\n const onPointerDown = useCallback((e: React.PointerEvent) => {\n dragging.current = true\n start.current = { x: e.clientX - offset.x, y: e.clientY - offset.y }\n ;(e.target as HTMLElement).setPointerCapture(e.pointerId)\n }, [offset])\n\n const onPointerMove = useCallback((e: React.PointerEvent) => {\n if (!dragging.current) return\n setOffset({\n x: e.clientX - start.current.x,\n y: e.clientY - start.current.y,\n })\n }, [])\n\n const onPointerUp = useCallback(() => {\n dragging.current = false\n }, [])\n\n return { offset, handlers: { onPointerDown, onPointerMove, onPointerUp } }\n}\n\n/**\n * Counts real DOM mutations via MutationObserver (ignoring class-only changes).\n * The node count is cached and only recalculated when the DOM actually changes,\n * so we don't pay the cost of querySelectorAll('*') every frame.\n */\nexport function useDomTracker(wrapperRef: React.RefObject<HTMLDivElement | null>, enabled: boolean) {\n const mutations = useRef(0)\n const nodeCount = useRef(0)\n const dirty = useRef(true)\n\n useEffect(() => {\n if (!enabled || !wrapperRef.current) return\n const observer = new MutationObserver((records) => {\n let realMutation = false\n for (const record of records) {\n if (record.type === 'attributes' && record.attributeName === 'class') continue\n realMutation = true\n }\n if (realMutation) {\n mutations.current++\n dirty.current = true\n }\n })\n observer.observe(wrapperRef.current, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: ['style'],\n })\n return () => observer.disconnect()\n }, [wrapperRef, enabled])\n\n const getNodeCount = useCallback(() => {\n if (dirty.current && wrapperRef.current) {\n nodeCount.current = wrapperRef.current.querySelectorAll('*').length\n dirty.current = false\n }\n return nodeCount.current\n }, [wrapperRef])\n\n const reset = useCallback(() => {\n mutations.current = 0\n dirty.current = true\n }, [])\n\n return { mutations, getNodeCount, reset }\n}\n\n/**\n * Measures how many React renders happen per second.\n * Call `tick()` from the <Profiler> onRender callback; the hook\n * snapshots the count every second and resets it.\n */\nexport function useRenderRate() {\n const renderCount = useRef(0)\n const rendersPerSecond = useRef(0)\n\n const tick = useCallback(() => {\n renderCount.current++\n }, [])\n\n useEffect(() => {\n const id = setInterval(() => {\n rendersPerSecond.current = renderCount.current\n renderCount.current = 0\n }, 1000)\n return () => clearInterval(id)\n }, [])\n\n const reset = useCallback(() => {\n renderCount.current = 0\n rendersPerSecond.current = 0\n }, [])\n\n return { rendersPerSecond, tick, reset }\n}\n\n/**\n * Flashes a subtle outline on the profiled element whenever the DOM mutates.\n * Skips the very first mutation (the initial mount) to avoid a flash on load.\n */\nexport function useRenderFlash(wrapperRef: React.RefObject<HTMLDivElement | null>, open: boolean) {\n const mutationCount = useRef(0)\n\n useEffect(() => {\n if (!open || !wrapperRef.current) return\n const observer = new MutationObserver(() => {\n mutationCount.current++\n if (!wrapperRef.current || mutationCount.current <= 1) return\n const el = wrapperRef.current\n el.style.outline = FLASH_OUTLINE\n el.style.outlineOffset = '-2px'\n setTimeout(() => {\n el.style.outline = ''\n el.style.outlineOffset = ''\n }, 150)\n })\n observer.observe(wrapperRef.current, { childList: true, subtree: true })\n return () => observer.disconnect()\n }, [wrapperRef, open])\n}\n\n/**\n * Detects browser \"long tasks\" (>50 ms) using PerformanceObserver.\n * Falls back silently in browsers that don't support the longtask entry type.\n */\nexport function useLongTasks(enabled: boolean) {\n const count = useRef(0)\n\n useEffect(() => {\n if (!enabled || typeof PerformanceObserver === 'undefined') return\n try {\n const observer = new PerformanceObserver((list) => {\n count.current += list.getEntries().length\n })\n observer.observe({ entryTypes: ['longtask'] })\n return () => observer.disconnect()\n } catch {\n // longtask not supported in this browser\n }\n }, [enabled])\n\n const reset = useCallback(() => { count.current = 0 }, [])\n\n return { count, reset }\n}\n","/**\n * @module react-dev-profiler\n * @description Inline styles for the profiler UI — no external CSS needed.\n * @author Frederic Denis (billywild87) — https://github.com/billywild87\n * @license MIT\n */\n\nimport type { CSSProperties } from 'react'\n\nexport const s = {\n wrapper: {\n display: 'contents',\n } satisfies CSSProperties,\n\n panel: {\n position: 'fixed',\n zIndex: 99999,\n background: 'rgba(8, 8, 8, 0.95)',\n border: '1px solid #222',\n borderRadius: 10,\n padding: '10px 14px',\n minWidth: 220,\n boxShadow: '0 8px 32px rgba(0, 0, 0, 0.7)',\n fontFamily: \"'SF Mono', 'Fira Code', monospace\",\n backdropFilter: 'blur(12px)',\n userSelect: 'none',\n color: '#ccc',\n fontSize: 11,\n } satisfies CSSProperties,\n\n panelHeader: {\n display: 'flex',\n justifyContent: 'space-between',\n alignItems: 'center',\n marginBottom: 8,\n cursor: 'grab',\n touchAction: 'none',\n } satisfies CSSProperties,\n\n panelTitle: {\n color: '#666',\n fontSize: 9,\n fontWeight: 700,\n letterSpacing: 1.5,\n textTransform: 'uppercase',\n display: 'flex',\n alignItems: 'center',\n gap: 6,\n } satisfies CSSProperties,\n\n instanceBadge: {\n background: '#222',\n color: '#888',\n fontSize: 8,\n padding: '1px 5px',\n borderRadius: 4,\n letterSpacing: 0.5,\n textTransform: 'none',\n } satisfies CSSProperties,\n\n headerActions: {\n display: 'flex',\n alignItems: 'center',\n gap: 8,\n } satisfies CSSProperties,\n\n iconBtn: {\n background: 'none',\n border: 'none',\n color: '#444',\n cursor: 'pointer',\n padding: 0,\n lineHeight: 1,\n display: 'flex',\n alignItems: 'center',\n transition: 'color 0.15s',\n } satisfies CSSProperties,\n\n iconBtnActive: {\n color: '#4ade80',\n } satisfies CSSProperties,\n\n closeBtn: {\n background: 'none',\n border: 'none',\n color: '#444',\n cursor: 'pointer',\n fontSize: 13,\n padding: 0,\n lineHeight: 1,\n } satisfies CSSProperties,\n\n body: {\n display: 'flex',\n flexDirection: 'column',\n gap: 5,\n } satisfies CSSProperties,\n\n section: {\n color: '#444',\n fontSize: 8,\n fontWeight: 600,\n letterSpacing: 1,\n textTransform: 'uppercase',\n marginTop: 2,\n } satisfies CSSProperties,\n\n separator: {\n height: 1,\n background: '#1a1a1a',\n margin: '2px 0',\n } satisfies CSSProperties,\n\n row: {\n display: 'flex',\n justifyContent: 'space-between',\n alignItems: 'center',\n padding: '1px 0',\n } satisfies CSSProperties,\n\n rowLabel: {\n color: '#555',\n fontSize: 10,\n } satisfies CSSProperties,\n\n rowValue: {\n fontSize: 11,\n fontWeight: 600,\n } satisfies CSSProperties,\n\n miniRow: {\n display: 'flex',\n justifyContent: 'space-between',\n color: '#444',\n fontSize: 9,\n marginTop: -2,\n marginBottom: 2,\n } satisfies CSSProperties,\n\n graphWrap: {\n marginTop: 4,\n } satisfies CSSProperties,\n\n toggleBtn: {\n position: 'fixed',\n zIndex: 99998,\n minWidth: 28,\n height: 24,\n borderRadius: 7,\n border: '1px solid #2a2a2a',\n background: 'rgba(20, 20, 20, 0.9)',\n cursor: 'pointer',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n padding: '0 6px',\n backdropFilter: 'blur(8px)',\n } satisfies CSSProperties,\n\n toggleFps: {\n fontFamily: \"'SF Mono', 'Fira Code', monospace\",\n fontSize: 10,\n fontWeight: 700,\n letterSpacing: -0.5,\n } satisfies CSSProperties,\n\n footer: {\n marginTop: 8,\n color: '#333',\n fontSize: 8,\n textAlign: 'center',\n } satisfies CSSProperties,\n}\n\n/** Flash outline class name applied directly via classList. */\nexport const FLASH_OUTLINE = '2px solid rgba(99, 102, 241, 0.8)'\n","/**\n * @module react-dev-profiler\n * @description The main stats panel — renders all performance metrics in a floating overlay.\n * @author Frederic Denis (billywild87) — https://github.com/billywild87\n * @license MIT\n */\n\nimport { useState, useEffect, useCallback, useRef, type CSSProperties } from 'react'\nimport { createPortal } from 'react-dom'\nimport { s } from './styles'\nimport { useAnchorPosition, useDraggable, useDomTracker, useRenderRate, useLongTasks } from './hooks'\nimport { type ReactProfilerData, type PanelPosition, type DevStats, HISTORY_SIZE, INITIAL_STATS, percentile } from './types'\n\n/** Rolling bar chart of frame times (last 60 samples). */\nfunction FrameTimeGraph({ history }: { history: number[] }) {\n const max = Math.max(33, ...history)\n const w = 140\n const h = 32\n const barW = Math.max(1, w / HISTORY_SIZE - 0.5)\n\n return (\n <div style={s.graphWrap}>\n <svg width={w} height={h} style={{ display: 'block' }}>\n <rect width={w} height={h} rx={3} fill=\"#111\" />\n {/* 60 fps guideline */}\n <line x1={0} y1={h - (16.67 / max) * h} x2={w} y2={h - (16.67 / max) * h}\n stroke=\"#1a3a1a\" strokeWidth={1} />\n {/* 30 fps guideline */}\n <line x1={0} y1={h - (33 / max) * h} x2={w} y2={h - (33 / max) * h}\n stroke=\"#3a1a1a\" strokeWidth={1} />\n {history.map((ms, i) => {\n const x = (i / HISTORY_SIZE) * w\n const barH = Math.min((ms / max) * h, h)\n const color = ms > 33 ? '#ef4444' : ms > 16.67 ? '#f59e0b' : '#4ade80'\n return <rect key={i} x={x} y={h - barH} width={barW} height={barH} fill={color} opacity={0.8} rx={0.5} />\n })}\n </svg>\n </div>\n )\n}\n\n/** Single label → value row used throughout the panel. */\nfunction StatRow({ label, value, sub, color = '#4ade80' }: { label: string, value: string, sub?: string, color?: string }) {\n return (\n <div style={s.row}>\n <span style={s.rowLabel}>{label}</span>\n <span>\n <span style={{ ...s.rowValue, color }}>{value}</span>\n {sub && <span style={{ color: '#444', fontSize: 9, marginLeft: 4 }}>{sub}</span>}\n </span>\n </div>\n )\n}\n\n/** Computes fixed-position styles based on the chosen panel position + drag offset. */\nfunction getPanelStyle(\n pos: { top: number; left: number },\n offset: { x: number; y: number },\n position: PanelPosition,\n): CSSProperties {\n const style: CSSProperties = {\n ...s.panel,\n transform: `translate(${offset.x}px, ${offset.y}px)`,\n }\n if (position.startsWith('bottom')) {\n style.bottom = window.innerHeight - pos.top\n } else {\n style.top = pos.top\n }\n if (position.endsWith('right')) {\n style.right = window.innerWidth - pos.left\n } else {\n style.left = pos.left\n }\n return style\n}\n\n/** Floating stats panel portaled to document.body. */\nexport function DevStatsPanel({\n targetRef,\n domTracker,\n renderRate,\n longTasks,\n profilerData,\n onClose,\n onReset,\n position = 'bottom-left',\n instanceId,\n instanceCount = 1,\n}: {\n targetRef: React.RefObject<HTMLDivElement | null>\n domTracker: ReturnType<typeof useDomTracker>\n renderRate: ReturnType<typeof useRenderRate>\n longTasks: ReturnType<typeof useLongTasks>\n profilerData: React.RefObject<ReactProfilerData>\n onClose: () => void\n onReset: () => void\n position?: PanelPosition\n instanceId?: string\n instanceCount?: number\n}) {\n const [stats, setStats] = useState<DevStats>(INITIAL_STATS)\n const [exported, setExported] = useState(false)\n const lastFrame = useRef(performance.now())\n const frameTimeHistory = useRef<number[]>([])\n const allFrameTimes = useRef<number[]>([])\n const pos = useAnchorPosition(targetRef, position)\n const { offset, handlers: dragHandlers } = useDraggable()\n\n // Collects frame timing data every rAF, then snapshots stats once per second.\n useEffect(() => {\n let animId: number\n let frameCount = 0\n let frameTotalMs = 0\n let lastSecond = performance.now()\n\n const tick = () => {\n const now = performance.now()\n const delta = now - lastFrame.current\n lastFrame.current = now\n frameCount++\n frameTotalMs += delta\n\n if (now - lastSecond >= 1000) {\n const avgFrameTime = frameTotalMs / frameCount\n\n const hist = frameTimeHistory.current\n if (hist.length >= HISTORY_SIZE) hist.shift()\n hist.push(avgFrameTime)\n\n allFrameTimes.current.push(avgFrameTime)\n const sorted = [...allFrameTimes.current].sort((a, b) => a - b)\n\n const el = targetRef.current\n const dims = el ? `${el.offsetWidth} x ${el.offsetHeight}` : '–'\n\n // Chrome-only: performance.memory exposes JS heap usage\n const perf = performance as any\n const mem = perf.memory ? Math.round(perf.memory.usedJSHeapSize / 1024 / 1024) : 0\n\n setStats({\n domMutations: domTracker.mutations.current,\n domNodes: domTracker.getNodeCount(),\n frameTime: avgFrameTime,\n frameTimeMin: sorted[0] ?? 0,\n frameTimeMax: sorted[sorted.length - 1] ?? 0,\n frameTimeP99: percentile(sorted, 99),\n frameTimeHistory: [...hist],\n longTasks: longTasks.count.current,\n rendersPerSecond: renderRate.rendersPerSecond.current,\n memory: mem,\n dimensions: dims,\n profiler: { ...profilerData.current },\n })\n\n frameCount = 0\n frameTotalMs = 0\n lastSecond = now\n }\n animId = requestAnimationFrame(tick)\n }\n animId = requestAnimationFrame(tick)\n return () => cancelAnimationFrame(animId)\n }, [targetRef, domTracker, renderRate, longTasks])\n\n const handleReset = useCallback(() => {\n frameTimeHistory.current = []\n allFrameTimes.current = []\n setStats(INITIAL_STATS)\n onReset()\n }, [onReset])\n\n // Copy current stats as JSON to clipboard.\n const handleExport = useCallback(() => {\n const payload = { timestamp: new Date().toISOString(), ...stats }\n const json = JSON.stringify(payload, null, 2)\n try {\n navigator.clipboard.writeText(json).then(() => {\n setExported(true)\n setTimeout(() => setExported(false), 1200)\n })\n } catch {\n // Fallback for non-secure contexts\n const ta = document.createElement('textarea')\n ta.value = json\n ta.style.position = 'fixed'\n ta.style.opacity = '0'\n document.body.appendChild(ta)\n ta.select()\n document.execCommand('copy')\n document.body.removeChild(ta)\n setExported(true)\n setTimeout(() => setExported(false), 1200)\n }\n }, [stats])\n\n // Color thresholds: green = good, amber = warning, red = bad\n const ftColor = stats.frameTime > 33 ? '#ef4444' : stats.frameTime > 16.67 ? '#f59e0b' : '#4ade80'\n const rpsColor = stats.rendersPerSecond > 30 ? '#ef4444' : stats.rendersPerSecond > 10 ? '#f59e0b' : '#4ade80'\n const actualColor = stats.profiler.actualDuration > 16 ? '#ef4444' : stats.profiler.actualDuration > 8 ? '#f59e0b' : '#4ade80'\n const fps = stats.frameTime > 0 ? Math.round(1000 / stats.frameTime) : 0\n const memoGain = stats.profiler.baseDuration > 0\n ? Math.round((1 - stats.profiler.actualDuration / stats.profiler.baseDuration) * 100)\n : 0\n const p99Color = stats.frameTimeP99 > 33 ? '#ef4444' : stats.frameTimeP99 > 16.67 ? '#f59e0b' : '#4ade80'\n\n const exportStyle: CSSProperties = exported\n ? { ...s.iconBtn, ...s.iconBtnActive }\n : s.iconBtn\n\n return createPortal(\n <div style={getPanelStyle(pos, offset, position)}>\n <div\n style={s.panelHeader}\n {...dragHandlers}\n >\n <span style={s.panelTitle}>\n Dev Profiler\n {instanceCount > 1 && instanceId && (\n <span style={s.instanceBadge}>{instanceId}</span>\n )}\n </span>\n <div style={s.headerActions}>\n <button\n style={exportStyle}\n onClick={handleExport}\n title={exported ? 'Copied!' : 'Copy stats to clipboard'}\n >\n {exported ? (\n <svg width=\"10\" height=\"10\" viewBox=\"0 0 16 16\" fill=\"currentColor\">\n <path d=\"M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.75.75 0 0 1 1.06-1.06L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0z\"/>\n </svg>\n ) : (\n <svg width=\"10\" height=\"10\" viewBox=\"0 0 16 16\" fill=\"currentColor\">\n <path d=\"M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25z\"/>\n <path d=\"M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25z\"/>\n </svg>\n )}\n </button>\n <button style={s.iconBtn} onClick={handleReset} title=\"Reset counters\">\n <svg width=\"10\" height=\"10\" viewBox=\"0 0 16 16\" fill=\"currentColor\">\n <path d=\"M14 1a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1h-4a1 1 0 0 1 0-2h1.6A6 6 0 0 0 2.07 7.5a1 1 0 1 1-1.97-.36A8 8 0 0 1 13 3.35V2a1 1 0 0 1 1-1zM2 15a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1h4a1 1 0 0 1 0 2H4.4A6 6 0 0 0 13.93 8.5a1 1 0 1 1 1.97.36A8 8 0 0 1 3 12.65V14a1 1 0 0 1-1 1z\"/>\n </svg>\n </button>\n <button style={s.closeBtn} onClick={onClose}>✕</button>\n </div>\n </div>\n\n <div style={s.body}>\n <span style={s.section}>Rendering</span>\n <StatRow label=\"Frame time\" value={`${stats.frameTime.toFixed(1)}ms`} sub={`${fps} fps`} color={ftColor} />\n <FrameTimeGraph history={stats.frameTimeHistory} />\n <div style={s.miniRow}>\n <span>min {stats.frameTimeMin.toFixed(1)}</span>\n <span>max {stats.frameTimeMax.toFixed(1)}</span>\n <span style={{ color: p99Color }}>p99 {stats.frameTimeP99.toFixed(1)}</span>\n </div>\n <StatRow label=\"Renders/s\" value={String(stats.rendersPerSecond)} color={rpsColor} />\n <StatRow label=\"Long tasks\" value={String(stats.longTasks)} color={stats.longTasks > 0 ? '#f59e0b' : '#4ade80'} />\n\n <div style={s.separator} />\n <span style={s.section}>React Profiler</span>\n <StatRow label=\"Phase\" value={stats.profiler.phase} color=\"#888\" />\n <StatRow label=\"Render\" value={`${stats.profiler.actualDuration.toFixed(2)}ms`} color={actualColor} />\n <StatRow label=\"Base (no memo)\" value={`${stats.profiler.baseDuration.toFixed(2)}ms`} color=\"#888\" />\n <StatRow label=\"Memo gain\" value={`${memoGain}%`} color={memoGain > 50 ? '#4ade80' : memoGain > 20 ? '#f59e0b' : '#ef4444'} />\n <StatRow label=\"Commits\" value={String(stats.profiler.commitCount)} />\n\n <div style={s.separator} />\n <span style={s.section}>DOM</span>\n <StatRow label=\"Nodes\" value={stats.domNodes.toLocaleString()} />\n <StatRow label=\"Mutations\" value={String(stats.domMutations)} />\n <StatRow label=\"Size\" value={stats.dimensions} color=\"#888\" />\n\n <div style={s.separator} />\n <span style={s.section}>Memory</span>\n <StatRow label=\"JS Heap\" value={stats.memory > 0 ? `${stats.memory} MB` : 'N/A'} />\n </div>\n\n <div style={s.footer}>Ctrl+I to toggle</div>\n </div>,\n document.body\n )\n}\n","/**\n * @module react-dev-profiler\n * @description Minimal floating button that shows the current FPS at a glance.\n * @author Frederic Denis (billywild87) — https://github.com/billywild87\n * @license MIT\n */\n\nimport { useState, useEffect, useRef, type CSSProperties } from 'react'\nimport { createPortal } from 'react-dom'\nimport { s } from './styles'\nimport { useAnchorPosition } from './hooks'\nimport type { PanelPosition } from './types'\n\n/** Computes fixed-position styles based on the chosen panel position. */\nfunction getButtonStyle(pos: { top: number; left: number }, position: PanelPosition): CSSProperties {\n const style: CSSProperties = { ...s.toggleBtn }\n if (position.startsWith('bottom')) {\n style.bottom = window.innerHeight - pos.top + 8\n } else {\n style.top = pos.top + 8\n }\n if (position.endsWith('right')) {\n style.right = window.innerWidth - pos.left\n } else {\n style.left = pos.left\n }\n return style\n}\n\n/** Small floating pill showing live FPS — click to open the full panel. */\nexport function ToggleButton({\n targetRef,\n onClick,\n position = 'bottom-left',\n}: {\n targetRef: React.RefObject<HTMLDivElement | null>\n onClick: () => void\n position?: PanelPosition\n}) {\n const pos = useAnchorPosition(targetRef, position)\n const [fps, setFps] = useState(0)\n const lastFrame = useRef(performance.now())\n\n useEffect(() => {\n let animId: number\n let count = 0\n let lastSecond = performance.now()\n\n const tick = () => {\n const now = performance.now()\n lastFrame.current = now\n count++\n if (now - lastSecond >= 1000) {\n setFps(count)\n count = 0\n lastSecond = now\n }\n animId = requestAnimationFrame(tick)\n }\n animId = requestAnimationFrame(tick)\n return () => cancelAnimationFrame(animId)\n }, [])\n\n const fpsColor = fps < 30 ? '#ef4444' : fps < 55 ? '#f59e0b' : '#4ade80'\n\n return createPortal(\n <button\n onClick={onClick}\n title=\"Dev Profiler (Ctrl+I)\"\n style={getButtonStyle(pos, position)}\n >\n <span style={{ ...s.toggleFps, color: fpsColor }}>{fps}</span>\n </button>,\n document.body\n )\n}\n"],"mappings":"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","names":["import_react","import_react","import_react","import_react_dom","import_jsx_runtime","import_jsx_runtime"]}
|