react-render-detective 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -3
- package/dist/babel.cjs +9 -0
- package/dist/babel.cjs.map +1 -0
- package/dist/babel.d.cts +38 -0
- package/dist/babel.d.ts +38 -0
- package/dist/babel.js +3 -0
- package/dist/babel.js.map +1 -0
- package/dist/{chunk-SKLDIIL6.cjs → chunk-6YPJQ7M5.cjs} +8 -4
- package/dist/chunk-6YPJQ7M5.cjs.map +1 -0
- package/dist/chunk-BPNLNIHU.cjs +162 -0
- package/dist/chunk-BPNLNIHU.cjs.map +1 -0
- package/dist/{chunk-D4G4D5AC.js → chunk-LSIXHWQD.js} +7 -4
- package/dist/chunk-LSIXHWQD.js.map +1 -0
- package/dist/{chunk-27IWHWSM.js → chunk-MSISKPST.js} +8 -4
- package/dist/chunk-MSISKPST.js.map +1 -0
- package/dist/chunk-PSCFSJ65.js +160 -0
- package/dist/chunk-PSCFSJ65.js.map +1 -0
- package/dist/{chunk-U3SAWFCT.cjs → chunk-QQ4VARSD.cjs} +7 -4
- package/dist/chunk-QQ4VARSD.cjs.map +1 -0
- package/dist/core.cjs +18 -18
- package/dist/core.d.cts +5 -3
- package/dist/core.d.ts +5 -3
- package/dist/core.js +1 -1
- package/dist/index.cjs +41 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -2
- package/dist/index.d.ts +14 -2
- package/dist/index.js +13 -7
- package/dist/index.js.map +1 -1
- package/dist/overlay.cjs +6 -6
- package/dist/overlay.cjs.map +1 -1
- package/dist/overlay.js +3 -3
- package/dist/overlay.js.map +1 -1
- package/dist/{types-CY61yfhK.d.cts → types-BWVaQPHb.d.cts} +7 -0
- package/dist/{types-CY61yfhK.d.ts → types-BWVaQPHb.d.ts} +7 -0
- package/dist/vite.cjs +48 -0
- package/dist/vite.cjs.map +1 -0
- package/dist/vite.d.cts +19 -0
- package/dist/vite.d.ts +19 -0
- package/dist/vite.js +43 -0
- package/dist/vite.js.map +1 -0
- package/package.json +46 -157
- package/dist/chunk-27IWHWSM.js.map +0 -1
- package/dist/chunk-D4G4D5AC.js.map +0 -1
- package/dist/chunk-SKLDIIL6.cjs.map +0 -1
- package/dist/chunk-U3SAWFCT.cjs.map +0 -1
|
@@ -44,7 +44,8 @@ function explainEvents(component, events) {
|
|
|
44
44
|
if (topProp && topProp.share >= 0.5) {
|
|
45
45
|
headline = `${pct(topProp.share)} of updates followed \`${topProp.key}\` changing by reference while its contents stayed the same.`;
|
|
46
46
|
const via = latest.parent?.name;
|
|
47
|
-
|
|
47
|
+
const viaSource = latest.parent?.source ? ` (${latest.parent.source})` : "";
|
|
48
|
+
nextStep = `Trace \`${topProp.key}\` back from ${via ? `${via}${viaSource}, which passes it to ${component}` : component}, and stabilise it where it is created${topProp.valueType === "function" ? " (useCallback, or hoist it out of the component)" : " (useMemo, or pass the primitive fields you use)"}.`;
|
|
48
49
|
confidence = "high";
|
|
49
50
|
} else if (top && top.reason === "parent" && top.share >= 0.5) {
|
|
50
51
|
headline = `${pct(top.share)} of updates were parent propagation with identical props.`;
|
|
@@ -79,6 +80,8 @@ function explainEvents(component, events) {
|
|
|
79
80
|
}
|
|
80
81
|
return {
|
|
81
82
|
component,
|
|
83
|
+
source: latest.component.source,
|
|
84
|
+
parent: latest.parent ? { name: latest.parent.name, source: latest.parent.source } : void 0,
|
|
82
85
|
renders: mine.length,
|
|
83
86
|
mounts: mine.length - updates.length,
|
|
84
87
|
breakdown,
|
|
@@ -95,7 +98,7 @@ function explainEvents(component, events) {
|
|
|
95
98
|
}
|
|
96
99
|
function formatExplanation(e) {
|
|
97
100
|
const lines = [
|
|
98
|
-
`${e.component}`,
|
|
101
|
+
e.source ? `${e.component} ${e.source}` : `${e.component}`,
|
|
99
102
|
"",
|
|
100
103
|
`${e.renders} recorded render${e.renders === 1 ? "" : "s"}${e.mounts > 0 ? ` (${e.mounts} mount${e.mounts === 1 ? "" : "s"})` : ""}`,
|
|
101
104
|
"",
|
|
@@ -132,5 +135,5 @@ var fmt = (ms) => `${ms.toFixed(1)}ms`;
|
|
|
132
135
|
|
|
133
136
|
exports.explainEvents = explainEvents;
|
|
134
137
|
exports.formatExplanation = formatExplanation;
|
|
135
|
-
//# sourceMappingURL=chunk-
|
|
136
|
-
//# sourceMappingURL=chunk-
|
|
138
|
+
//# sourceMappingURL=chunk-QQ4VARSD.cjs.map
|
|
139
|
+
//# sourceMappingURL=chunk-QQ4VARSD.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/explain.ts"],"names":[],"mappings":"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","file":"chunk-QQ4VARSD.cjs","sourcesContent":["import type { Confidence, RenderEvent, RenderReason } from \"./types.js\";\n\nexport interface Explanation {\n component: string;\n /** `File.tsx:12:2`, when the build plugin instrumented this component. */\n source?: string;\n /** Where the component's props come from, and its source if known. */\n parent?: { name: string; source?: string };\n renders: number;\n /** Mounts are excluded from every share below — they cannot be optimised away. */\n mounts: number;\n /** Reason → share of renders, descending. */\n breakdown: Array<{ reason: RenderReason; count: number; share: number }>;\n /** Props that most often changed by reference only, descending. */\n unstableProps: Array<{ key: string; count: number; share: number; valueType: string }>;\n averageSelfDuration: number;\n totalSelfDuration: number;\n potentiallyAvoidableRenders: number;\n estimatedAvoidableTime: number;\n devReplays: number;\n headline: string;\n nextStep: string;\n confidence: Confidence;\n}\n\n/**\n * The flagship \"explain this render\" answer, aggregated across the recorded\n * history of one component. Pure — no React, no console.\n */\nexport function explainEvents(component: string, events: RenderEvent[]): Explanation | undefined {\n const mine = events.filter((e) => e.component.name === component);\n if (mine.length === 0) return undefined;\n\n /*\n * Shares are measured against *updates*, not against every recorded render.\n * Mounts happen once per instance and are not something you can optimise\n * away, so counting them in the denominator quietly deflates the share of\n * whatever is actually driving the re-renders — on a 20-row list, a prop\n * responsible for every single update reads as \"33%\".\n */\n const updates = mine.filter((e) => e.phase !== \"mount\");\n const denominator = updates.length || mine.length;\n\n const reasonCounts = new Map<RenderReason, number>();\n const unstable = new Map<string, { count: number; valueType: string }>();\n let totalSelf = 0;\n let avoidable = 0;\n let avoidableTime = 0;\n let replays = 0;\n let propsWithNewValues = 0;\n let propsReferenceOnly = 0;\n\n for (const e of mine) {\n reasonCounts.set(e.diagnosis.reason, (reasonCounts.get(e.diagnosis.reason) ?? 0) + 1);\n totalSelf += e.timings.selfDuration;\n if (e.devReplay) replays++;\n if (e.diagnosis.potentiallyAvoidable) {\n avoidable++;\n avoidableTime += e.timings.selfDuration;\n }\n if (e.diagnosis.reason === \"props\") {\n if (e.changedProps.some((c) => c.kind !== \"reference\")) propsWithNewValues++;\n else propsReferenceOnly++;\n }\n for (const c of e.changedProps) {\n if (c.kind !== \"reference\") continue;\n const entry = unstable.get(c.key) ?? { count: 0, valueType: c.valueType };\n entry.count++;\n unstable.set(c.key, entry);\n }\n }\n\n const breakdown = [...reasonCounts.entries()]\n .map(([reason, count]) => ({ reason, count, share: count / mine.length }))\n .sort((a, b) => b.count - a.count);\n\n const unstableProps = [...unstable.entries()]\n .map(([key, v]) => ({ key, count: v.count, share: v.count / denominator, valueType: v.valueType }))\n .sort((a, b) => b.count - a.count);\n\n // Ranked over updates, for the same reason the shares are.\n const top = [...reasonCounts.entries()]\n .filter(([reason]) => reason !== \"mount\")\n .map(([reason, count]) => ({ reason, count, share: count / denominator }))\n .sort((a, b) => b.count - a.count)[0];\n const topProp = unstableProps[0];\n const latest = mine[mine.length - 1] as RenderEvent;\n\n let headline: string;\n let nextStep: string;\n let confidence: Confidence = \"medium\";\n\n if (topProp && topProp.share >= 0.5) {\n headline = `${pct(topProp.share)} of updates followed \\`${topProp.key}\\` changing by reference while its contents stayed the same.`;\n // `parent` is the nearest instrumented ancestor — where the prop arrives\n // from, which is not necessarily where it is created. Say the former.\n const via = latest.parent?.name;\n const viaSource = latest.parent?.source ? ` (${latest.parent.source})` : \"\";\n nextStep =\n `Trace \\`${topProp.key}\\` back from ${via ? `${via}${viaSource}, which passes it to ${component}` : component}, ` +\n `and stabilise it where it is created${\n topProp.valueType === \"function\" ? \" (useCallback, or hoist it out of the component)\" : \" (useMemo, or pass the primitive fields you use)\"\n }.`;\n confidence = \"high\";\n } else if (top && top.reason === \"parent\" && top.share >= 0.5) {\n headline = `${pct(top.share)} of updates were parent propagation with identical props.`;\n nextStep = `Measure ${component} with React.memo(). It is only worth it if ${fmt(totalSelf / mine.length)} per render matters here.`;\n confidence = \"high\";\n } else if (top && top.reason === \"context\") {\n headline = `${pct(top.share)} of updates followed a tracked context update.`;\n nextStep = \"Check whether the provider's value is stable, and whether this component needs the whole context value.\";\n confidence = \"medium\";\n } else if (top && (top.reason === \"state\" || top.reason === \"state-or-external\")) {\n headline = `${pct(top.share)} of updates came from inside the component — state or an external store.`;\n nextStep =\n top.reason === \"state\"\n ? \"These are real state updates. Check whether every update needs to change state.\"\n : \"Name the state with useTrackedState to see which value drives these renders.\";\n confidence = top.reason === \"state\" ? \"high\" : \"medium\";\n } else if (top && top.reason === \"props\") {\n // Do not call a bucket \"new values\" without checking that it holds any.\n if (propsReferenceOnly > propsWithNewValues) {\n headline = `${pct(propsReferenceOnly / denominator)} of updates were prop changes where only the reference changed — the contents were identical.`;\n nextStep = `Stabilise the props listed above in whichever component renders ${component}.`;\n confidence = \"high\";\n } else if (propsReferenceOnly > 0) {\n headline =\n `${pct(propsWithNewValues / denominator)} of updates carried genuinely new prop values, ` +\n `and ${pct(propsReferenceOnly / denominator)} changed by reference only.`;\n nextStep = \"The reference-only ones are the avoidable half — start there.\";\n confidence = \"high\";\n } else {\n headline = `${pct(top.share)} of updates were driven by props with genuinely new values.`;\n nextStep = \"These look like legitimate data changes. Look at render cost rather than render count.\";\n confidence = \"high\";\n }\n } else {\n headline = \"Cause could not be determined reliably.\";\n nextStep = \"Instrument the parent, or track the context this component consumes, to narrow it down.\";\n confidence = \"low\";\n }\n\n return {\n component,\n source: latest.component.source,\n parent: latest.parent ? { name: latest.parent.name, source: latest.parent.source } : undefined,\n renders: mine.length,\n mounts: mine.length - updates.length,\n breakdown,\n unstableProps,\n averageSelfDuration: totalSelf / mine.length,\n totalSelfDuration: totalSelf,\n potentiallyAvoidableRenders: avoidable,\n estimatedAvoidableTime: avoidableTime,\n devReplays: replays,\n headline,\n nextStep,\n confidence,\n };\n}\n\nexport function formatExplanation(e: Explanation): string {\n const lines: string[] = [\n e.source ? `${e.component} ${e.source}` : `${e.component}`,\n \"\",\n `${e.renders} recorded render${e.renders === 1 ? \"\" : \"s\"}${e.mounts > 0 ? ` (${e.mounts} mount${e.mounts === 1 ? \"\" : \"s\"})` : \"\"}`,\n \"\",\n \"Why?\",\n ` ${e.headline}`,\n \"\",\n \"Breakdown (share of all renders)\",\n ...e.breakdown.map((b) => ` ${b.reason.padEnd(18)} ${String(b.count).padStart(5)} ${pct(b.share)}`),\n ];\n\n if (e.unstableProps.length > 0) {\n lines.push(\"\", \"Reference-only prop changes (share of updates)\");\n for (const p of e.unstableProps) {\n lines.push(` ${p.key.padEnd(18)} ${String(p.count).padStart(5)} ${pct(p.share)} (${p.valueType})`);\n }\n }\n\n lines.push(\n \"\",\n \"Cost\",\n ` average ${fmt(e.averageSelfDuration)}`,\n ` total ${fmt(e.totalSelfDuration)}`,\n ` potentially avoidable ${e.potentiallyAvoidableRenders} render(s), ~${fmt(e.estimatedAvoidableTime)}`,\n );\n\n if (e.devReplays > 0) {\n lines.push(\n \"\",\n ` ${e.devReplays} development replay(s) observed (StrictMode double-invoke or discarded attempts) — excluded from the counts above.`,\n );\n }\n\n lines.push(\"\", \"Next step\", ` ${e.nextStep}`, \"\", `Confidence: ${e.confidence}`);\n return lines.join(\"\\n\");\n}\n\nconst pct = (n: number): string => `${Math.round(n * 100)}%`;\nconst fmt = (ms: number): string => `${ms.toFixed(1)}ms`;\n"]}
|
package/dist/core.cjs
CHANGED
|
@@ -1,76 +1,76 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunk6YPJQ7M5_cjs = require('./chunk-6YPJQ7M5.cjs');
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
Object.defineProperty(exports, "Detective", {
|
|
8
8
|
enumerable: true,
|
|
9
|
-
get: function () { return
|
|
9
|
+
get: function () { return chunk6YPJQ7M5_cjs.Detective; }
|
|
10
10
|
});
|
|
11
11
|
Object.defineProperty(exports, "RingBuffer", {
|
|
12
12
|
enumerable: true,
|
|
13
|
-
get: function () { return
|
|
13
|
+
get: function () { return chunk6YPJQ7M5_cjs.RingBuffer; }
|
|
14
14
|
});
|
|
15
15
|
Object.defineProperty(exports, "defaultConfig", {
|
|
16
16
|
enumerable: true,
|
|
17
|
-
get: function () { return
|
|
17
|
+
get: function () { return chunk6YPJQ7M5_cjs.defaultConfig; }
|
|
18
18
|
});
|
|
19
19
|
Object.defineProperty(exports, "detectDev", {
|
|
20
20
|
enumerable: true,
|
|
21
|
-
get: function () { return
|
|
21
|
+
get: function () { return chunk6YPJQ7M5_cjs.detectDev; }
|
|
22
22
|
});
|
|
23
23
|
Object.defineProperty(exports, "diagnose", {
|
|
24
24
|
enumerable: true,
|
|
25
|
-
get: function () { return
|
|
25
|
+
get: function () { return chunk6YPJQ7M5_cjs.diagnose; }
|
|
26
26
|
});
|
|
27
27
|
Object.defineProperty(exports, "diffProps", {
|
|
28
28
|
enumerable: true,
|
|
29
|
-
get: function () { return
|
|
29
|
+
get: function () { return chunk6YPJQ7M5_cjs.diffProps; }
|
|
30
30
|
});
|
|
31
31
|
Object.defineProperty(exports, "formatInspected", {
|
|
32
32
|
enumerable: true,
|
|
33
|
-
get: function () { return
|
|
33
|
+
get: function () { return chunk6YPJQ7M5_cjs.formatInspected; }
|
|
34
34
|
});
|
|
35
35
|
Object.defineProperty(exports, "getDetective", {
|
|
36
36
|
enumerable: true,
|
|
37
|
-
get: function () { return
|
|
37
|
+
get: function () { return chunk6YPJQ7M5_cjs.getDetective; }
|
|
38
38
|
});
|
|
39
39
|
Object.defineProperty(exports, "inspect", {
|
|
40
40
|
enumerable: true,
|
|
41
|
-
get: function () { return
|
|
41
|
+
get: function () { return chunk6YPJQ7M5_cjs.inspect; }
|
|
42
42
|
});
|
|
43
43
|
Object.defineProperty(exports, "isPlainObject", {
|
|
44
44
|
enumerable: true,
|
|
45
|
-
get: function () { return
|
|
45
|
+
get: function () { return chunk6YPJQ7M5_cjs.isPlainObject; }
|
|
46
46
|
});
|
|
47
47
|
Object.defineProperty(exports, "isReactElement", {
|
|
48
48
|
enumerable: true,
|
|
49
|
-
get: function () { return
|
|
49
|
+
get: function () { return chunk6YPJQ7M5_cjs.isReactElement; }
|
|
50
50
|
});
|
|
51
51
|
Object.defineProperty(exports, "matches", {
|
|
52
52
|
enumerable: true,
|
|
53
|
-
get: function () { return
|
|
53
|
+
get: function () { return chunk6YPJQ7M5_cjs.matches; }
|
|
54
54
|
});
|
|
55
55
|
Object.defineProperty(exports, "mergeConfig", {
|
|
56
56
|
enumerable: true,
|
|
57
|
-
get: function () { return
|
|
57
|
+
get: function () { return chunk6YPJQ7M5_cjs.mergeConfig; }
|
|
58
58
|
});
|
|
59
59
|
Object.defineProperty(exports, "severityFor", {
|
|
60
60
|
enumerable: true,
|
|
61
|
-
get: function () { return
|
|
61
|
+
get: function () { return chunk6YPJQ7M5_cjs.severityFor; }
|
|
62
62
|
});
|
|
63
63
|
Object.defineProperty(exports, "shallowEqual", {
|
|
64
64
|
enumerable: true,
|
|
65
|
-
get: function () { return
|
|
65
|
+
get: function () { return chunk6YPJQ7M5_cjs.shallowEqual; }
|
|
66
66
|
});
|
|
67
67
|
Object.defineProperty(exports, "shouldInstrument", {
|
|
68
68
|
enumerable: true,
|
|
69
|
-
get: function () { return
|
|
69
|
+
get: function () { return chunk6YPJQ7M5_cjs.shouldInstrument; }
|
|
70
70
|
});
|
|
71
71
|
Object.defineProperty(exports, "valueType", {
|
|
72
72
|
enumerable: true,
|
|
73
|
-
get: function () { return
|
|
73
|
+
get: function () { return chunk6YPJQ7M5_cjs.valueType; }
|
|
74
74
|
});
|
|
75
75
|
//# sourceMappingURL=core.cjs.map
|
|
76
76
|
//# sourceMappingURL=core.cjs.map
|
package/dist/core.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { D as DetectiveConfig, T as TrackedStateChange, R as RenderReason, a as DetectiveOptions, b as RenderEvent, c as RenderPhase, C as ContextChange, d as ComponentStats, A as AppStats, P as PropChange, e as Thresholds, f as Diagnosis, I as Inspected, g as InspectionLimits, h as PropValueType } from './types-
|
|
2
|
-
export { i as ComponentInfo, j as Confidence, M as Mode, k as PropChangeKind, l as RenderTimings } from './types-
|
|
1
|
+
import { D as DetectiveConfig, T as TrackedStateChange, R as RenderReason, a as DetectiveOptions, b as RenderEvent, c as RenderPhase, C as ContextChange, d as ComponentStats, A as AppStats, P as PropChange, e as Thresholds, f as Diagnosis, I as Inspected, g as InspectionLimits, h as PropValueType } from './types-BWVaQPHb.cjs';
|
|
2
|
+
export { i as ComponentInfo, j as Confidence, M as Mode, k as PropChangeKind, l as RenderTimings } from './types-BWVaQPHb.cjs';
|
|
3
3
|
|
|
4
4
|
/** What an instrumentation site reports at commit time. */
|
|
5
5
|
interface CommitInput {
|
|
@@ -13,6 +13,8 @@ interface CommitInput {
|
|
|
13
13
|
interface NodeRecord {
|
|
14
14
|
id: string;
|
|
15
15
|
name: string;
|
|
16
|
+
/** `File.tsx:12:2` when the build plugin instrumented this component. */
|
|
17
|
+
source?: string;
|
|
16
18
|
/** Nearest instrumented ancestor, held directly so diagnosis never needs the registry. */
|
|
17
19
|
parent?: NodeRecord;
|
|
18
20
|
depth: number;
|
|
@@ -81,7 +83,7 @@ declare class Detective {
|
|
|
81
83
|
* discards one of the two, and only the surviving fiber's effect attaches —
|
|
82
84
|
* so discarded nodes are simply garbage collected.
|
|
83
85
|
*/
|
|
84
|
-
createNode(name: string, parent: NodeRecord | undefined): NodeRecord | undefined;
|
|
86
|
+
createNode(name: string, parent: NodeRecord | undefined, source?: string): NodeRecord | undefined;
|
|
85
87
|
attach(node: NodeRecord): void;
|
|
86
88
|
detach(node: NodeRecord): void;
|
|
87
89
|
/** Hot path. Must stay allocation-free and O(1). */
|
package/dist/core.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { D as DetectiveConfig, T as TrackedStateChange, R as RenderReason, a as DetectiveOptions, b as RenderEvent, c as RenderPhase, C as ContextChange, d as ComponentStats, A as AppStats, P as PropChange, e as Thresholds, f as Diagnosis, I as Inspected, g as InspectionLimits, h as PropValueType } from './types-
|
|
2
|
-
export { i as ComponentInfo, j as Confidence, M as Mode, k as PropChangeKind, l as RenderTimings } from './types-
|
|
1
|
+
import { D as DetectiveConfig, T as TrackedStateChange, R as RenderReason, a as DetectiveOptions, b as RenderEvent, c as RenderPhase, C as ContextChange, d as ComponentStats, A as AppStats, P as PropChange, e as Thresholds, f as Diagnosis, I as Inspected, g as InspectionLimits, h as PropValueType } from './types-BWVaQPHb.js';
|
|
2
|
+
export { i as ComponentInfo, j as Confidence, M as Mode, k as PropChangeKind, l as RenderTimings } from './types-BWVaQPHb.js';
|
|
3
3
|
|
|
4
4
|
/** What an instrumentation site reports at commit time. */
|
|
5
5
|
interface CommitInput {
|
|
@@ -13,6 +13,8 @@ interface CommitInput {
|
|
|
13
13
|
interface NodeRecord {
|
|
14
14
|
id: string;
|
|
15
15
|
name: string;
|
|
16
|
+
/** `File.tsx:12:2` when the build plugin instrumented this component. */
|
|
17
|
+
source?: string;
|
|
16
18
|
/** Nearest instrumented ancestor, held directly so diagnosis never needs the registry. */
|
|
17
19
|
parent?: NodeRecord;
|
|
18
20
|
depth: number;
|
|
@@ -81,7 +83,7 @@ declare class Detective {
|
|
|
81
83
|
* discards one of the two, and only the surviving fiber's effect attaches —
|
|
82
84
|
* so discarded nodes are simply garbage collected.
|
|
83
85
|
*/
|
|
84
|
-
createNode(name: string, parent: NodeRecord | undefined): NodeRecord | undefined;
|
|
86
|
+
createNode(name: string, parent: NodeRecord | undefined, source?: string): NodeRecord | undefined;
|
|
85
87
|
attach(node: NodeRecord): void;
|
|
86
88
|
detach(node: NodeRecord): void;
|
|
87
89
|
/** Hot path. Must stay allocation-free and O(1). */
|
package/dist/core.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { Detective, RingBuffer, defaultConfig, detectDev, diagnose, diffProps, formatInspected, getDetective, inspect, isPlainObject, isReactElement, matches, mergeConfig, severityFor, shallowEqual, shouldInstrument, valueType } from './chunk-
|
|
1
|
+
export { Detective, RingBuffer, defaultConfig, detectDev, diagnose, diffProps, formatInspected, getDetective, inspect, isPlainObject, isReactElement, matches, mergeConfig, severityFor, shallowEqual, shouldInstrument, valueType } from './chunk-MSISKPST.js';
|
|
2
2
|
//# sourceMappingURL=core.js.map
|
|
3
3
|
//# sourceMappingURL=core.js.map
|
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
var
|
|
3
|
+
var chunkQQ4VARSD_cjs = require('./chunk-QQ4VARSD.cjs');
|
|
4
|
+
var chunk6YPJQ7M5_cjs = require('./chunk-6YPJQ7M5.cjs');
|
|
5
5
|
var React = require('react');
|
|
6
6
|
var jsxRuntime = require('react/jsx-runtime');
|
|
7
7
|
|
|
@@ -65,8 +65,11 @@ function printVerbose(event) {
|
|
|
65
65
|
group.call(console, header);
|
|
66
66
|
try {
|
|
67
67
|
console.log(`Cause: ${label(event)} (confidence: ${diagnosis.confidence})`);
|
|
68
|
+
if (event.component.source) console.log(`Source: ${event.component.source}`);
|
|
68
69
|
if (event.parent) {
|
|
69
|
-
console.log(
|
|
70
|
+
console.log(
|
|
71
|
+
`Parent: ${event.parent.name}${event.parent.source ? ` (${event.parent.source})` : ""}${event.parentRendered ? " \u2014 re-rendered in this commit" : " \u2014 did not re-render"}`
|
|
72
|
+
);
|
|
70
73
|
}
|
|
71
74
|
console.log(
|
|
72
75
|
`Cost: self ${timings.selfDuration.toFixed(1)}ms \xB7 subtree ${timings.subtreeDuration.toFixed(1)}ms` + (timings.accountedDescendantDuration > 0 ? ` (${timings.accountedDescendantDuration.toFixed(1)}ms in instrumented children)` : "")
|
|
@@ -81,7 +84,7 @@ function printVerbose(event) {
|
|
|
81
84
|
}
|
|
82
85
|
if (event.trackedState.length > 0) {
|
|
83
86
|
console.log(
|
|
84
|
-
`State: ${event.trackedState.map((s) => `${s.name}: ${
|
|
87
|
+
`State: ${event.trackedState.map((s) => `${s.name}: ${chunk6YPJQ7M5_cjs.formatInspected(s.previous)} \u2192 ${chunk6YPJQ7M5_cjs.formatInspected(s.current)}`).join(", ")}`
|
|
85
88
|
);
|
|
86
89
|
}
|
|
87
90
|
if (event.contextChanges.length > 0) {
|
|
@@ -98,11 +101,11 @@ function printVerbose(event) {
|
|
|
98
101
|
}
|
|
99
102
|
function propLine(c) {
|
|
100
103
|
const head = `${c.key} (${c.valueType}) \u2014 ${describeKind(c)}`;
|
|
101
|
-
if (c.kind === "added") return `${head}: ${
|
|
102
|
-
if (c.kind === "removed") return `${head}: was ${
|
|
104
|
+
if (c.kind === "added") return `${head}: ${chunk6YPJQ7M5_cjs.formatInspected(c.current)}`;
|
|
105
|
+
if (c.kind === "removed") return `${head}: was ${chunk6YPJQ7M5_cjs.formatInspected(c.previous)}`;
|
|
103
106
|
return `${head}
|
|
104
|
-
previous: ${
|
|
105
|
-
current: ${
|
|
107
|
+
previous: ${chunk6YPJQ7M5_cjs.formatInspected(c.previous)}
|
|
108
|
+
current: ${chunk6YPJQ7M5_cjs.formatInspected(c.current)}`;
|
|
106
109
|
}
|
|
107
110
|
function describeKind(c) {
|
|
108
111
|
switch (c.kind) {
|
|
@@ -137,11 +140,11 @@ function label(event) {
|
|
|
137
140
|
}
|
|
138
141
|
var AncestryContext = React.createContext(void 0);
|
|
139
142
|
AncestryContext.displayName = "RenderDetectiveAncestry";
|
|
140
|
-
function useInstrumentedNode(name, props) {
|
|
141
|
-
const detective =
|
|
143
|
+
function useInstrumentedNode(name, props, source) {
|
|
144
|
+
const detective = chunk6YPJQ7M5_cjs.getDetective();
|
|
142
145
|
const parent = React.useContext(AncestryContext);
|
|
143
146
|
const [node] = React.useState(
|
|
144
|
-
() => detective.enabled ? detective.createNode(name, parent) : void 0
|
|
147
|
+
() => detective.enabled ? detective.createNode(name, parent, source) : void 0
|
|
145
148
|
);
|
|
146
149
|
React.useLayoutEffect(() => {
|
|
147
150
|
if (!node) return;
|
|
@@ -184,13 +187,16 @@ function componentName(component, fallback = "Anonymous") {
|
|
|
184
187
|
if (c.type) return componentName(c.type, fallback);
|
|
185
188
|
return fallback;
|
|
186
189
|
}
|
|
190
|
+
var IS_WRAPPER = /* @__PURE__ */ Symbol.for("react-render-detective.wrapper");
|
|
187
191
|
function withRenderDetective(Component, options = {}) {
|
|
192
|
+
if (Component[IS_WRAPPER]) return Component;
|
|
188
193
|
const name = options.name ?? componentName(Component);
|
|
189
194
|
function RenderDetected(props) {
|
|
190
|
-
const { node, onRender } = useInstrumentedNode(name, props);
|
|
195
|
+
const { node, onRender } = useInstrumentedNode(name, props, options.source);
|
|
191
196
|
return renderInstrumented(node, onRender, /* @__PURE__ */ jsxRuntime.jsx(Component, { ...props }));
|
|
192
197
|
}
|
|
193
198
|
RenderDetected.displayName = `RenderDetective(${name})`;
|
|
199
|
+
RenderDetected[IS_WRAPPER] = true;
|
|
194
200
|
return RenderDetected;
|
|
195
201
|
}
|
|
196
202
|
|
|
@@ -200,7 +206,7 @@ function RenderDetective({ name, children }) {
|
|
|
200
206
|
return renderInstrumented(node, onRender, children);
|
|
201
207
|
}
|
|
202
208
|
function useRenderDiagnostics(name, props) {
|
|
203
|
-
const detective =
|
|
209
|
+
const detective = chunk6YPJQ7M5_cjs.getDetective();
|
|
204
210
|
const parent = React.useContext(AncestryContext);
|
|
205
211
|
const [node] = React.useState(() => {
|
|
206
212
|
if (!detective.enabled) return void 0;
|
|
@@ -235,7 +241,7 @@ function useRenderDiagnostics(name, props) {
|
|
|
235
241
|
}
|
|
236
242
|
var useOwnerId = typeof React__namespace.useId === "function" ? React__namespace.useId : () => "";
|
|
237
243
|
function useTrackedState(name, initial) {
|
|
238
|
-
const detective =
|
|
244
|
+
const detective = chunk6YPJQ7M5_cjs.getDetective();
|
|
239
245
|
const node = React.useContext(AncestryContext);
|
|
240
246
|
const ownerId = useOwnerId();
|
|
241
247
|
const [state, setState] = React.useState(initial);
|
|
@@ -245,8 +251,8 @@ function useTrackedState(name, initial) {
|
|
|
245
251
|
if (node && owns && !Object.is(previous.current, state)) {
|
|
246
252
|
detective.recordStateChange(node, {
|
|
247
253
|
name,
|
|
248
|
-
previous:
|
|
249
|
-
current:
|
|
254
|
+
previous: chunk6YPJQ7M5_cjs.inspect(previous.current, detective.config.inspection),
|
|
255
|
+
current: chunk6YPJQ7M5_cjs.inspect(state, detective.config.inspection)
|
|
250
256
|
});
|
|
251
257
|
previous.current = state;
|
|
252
258
|
} else if (!owns) {
|
|
@@ -255,7 +261,7 @@ function useTrackedState(name, initial) {
|
|
|
255
261
|
return [state, setState];
|
|
256
262
|
}
|
|
257
263
|
function useTrackedEffect(name, effect, deps) {
|
|
258
|
-
const detective =
|
|
264
|
+
const detective = chunk6YPJQ7M5_cjs.getDetective();
|
|
259
265
|
const previous = React.useRef(void 0);
|
|
260
266
|
const changed = React.useRef([]);
|
|
261
267
|
if (detective.enabled) {
|
|
@@ -271,7 +277,7 @@ function useTrackedEffect(name, effect, deps) {
|
|
|
271
277
|
}, deps);
|
|
272
278
|
}
|
|
273
279
|
function useTrackedContextValue(contextName, value) {
|
|
274
|
-
const detective =
|
|
280
|
+
const detective = chunk6YPJQ7M5_cjs.getDetective();
|
|
275
281
|
const previous = React.useRef(void 0);
|
|
276
282
|
const first = React.useRef(true);
|
|
277
283
|
if (detective.enabled) {
|
|
@@ -279,8 +285,8 @@ function useTrackedContextValue(contextName, value) {
|
|
|
279
285
|
first.current = false;
|
|
280
286
|
} else if (!Object.is(previous.current, value)) {
|
|
281
287
|
const prev = previous.current;
|
|
282
|
-
const equal =
|
|
283
|
-
const changedKeys = isRecord(prev) && isRecord(value) ?
|
|
288
|
+
const equal = chunk6YPJQ7M5_cjs.shallowEqual(prev, value, detective.config);
|
|
289
|
+
const changedKeys = isRecord(prev) && isRecord(value) ? chunk6YPJQ7M5_cjs.diffProps(prev, value, detective.config).changed.map((c) => c.key) : [];
|
|
284
290
|
detective.recordContextChange({
|
|
285
291
|
contextName,
|
|
286
292
|
changedKeys,
|
|
@@ -299,7 +305,7 @@ function isRecord(v) {
|
|
|
299
305
|
// src/index.ts
|
|
300
306
|
var REPORTER = /* @__PURE__ */ Symbol.for("react-render-detective.reporter");
|
|
301
307
|
function init(options = {}) {
|
|
302
|
-
const detective =
|
|
308
|
+
const detective = chunk6YPJQ7M5_cjs.getDetective();
|
|
303
309
|
detective.init(options);
|
|
304
310
|
const g = globalThis;
|
|
305
311
|
g[REPORTER]?.();
|
|
@@ -313,41 +319,41 @@ function init(options = {}) {
|
|
|
313
319
|
}
|
|
314
320
|
}
|
|
315
321
|
function configure(options) {
|
|
316
|
-
|
|
322
|
+
chunk6YPJQ7M5_cjs.getDetective().configure(options);
|
|
317
323
|
}
|
|
318
324
|
function getConfig() {
|
|
319
|
-
return
|
|
325
|
+
return chunk6YPJQ7M5_cjs.getDetective().config;
|
|
320
326
|
}
|
|
321
327
|
function isEnabled() {
|
|
322
|
-
return
|
|
328
|
+
return chunk6YPJQ7M5_cjs.getDetective().enabled;
|
|
323
329
|
}
|
|
324
330
|
function getEvents() {
|
|
325
|
-
return
|
|
331
|
+
return chunk6YPJQ7M5_cjs.getDetective().getEvents();
|
|
326
332
|
}
|
|
327
333
|
function getStats() {
|
|
328
|
-
return
|
|
334
|
+
return chunk6YPJQ7M5_cjs.getDetective().getStats();
|
|
329
335
|
}
|
|
330
336
|
function getComponentStats(name) {
|
|
331
|
-
return
|
|
337
|
+
return chunk6YPJQ7M5_cjs.getDetective().getComponentStats(name);
|
|
332
338
|
}
|
|
333
339
|
function subscribe(listener) {
|
|
334
|
-
return
|
|
340
|
+
return chunk6YPJQ7M5_cjs.getDetective().subscribe(listener);
|
|
335
341
|
}
|
|
336
342
|
function clear() {
|
|
337
|
-
|
|
343
|
+
chunk6YPJQ7M5_cjs.getDetective().clear();
|
|
338
344
|
}
|
|
339
345
|
function reset() {
|
|
340
346
|
const g = globalThis;
|
|
341
347
|
g[REPORTER]?.();
|
|
342
348
|
g[REPORTER] = void 0;
|
|
343
|
-
|
|
349
|
+
chunk6YPJQ7M5_cjs.getDetective().reset();
|
|
344
350
|
}
|
|
345
351
|
function explain(componentName2) {
|
|
346
|
-
const explanation =
|
|
347
|
-
return explanation ?
|
|
352
|
+
const explanation = chunkQQ4VARSD_cjs.explainEvents(componentName2, getEvents());
|
|
353
|
+
return explanation ? chunkQQ4VARSD_cjs.formatExplanation(explanation) : void 0;
|
|
348
354
|
}
|
|
349
355
|
function explainStructured(componentName2) {
|
|
350
|
-
return
|
|
356
|
+
return chunkQQ4VARSD_cjs.explainEvents(componentName2, getEvents());
|
|
351
357
|
}
|
|
352
358
|
function printStats() {
|
|
353
359
|
const s = getStats();
|
|
@@ -391,11 +397,11 @@ var ReactRenderDetective = {
|
|
|
391
397
|
|
|
392
398
|
Object.defineProperty(exports, "explainEvents", {
|
|
393
399
|
enumerable: true,
|
|
394
|
-
get: function () { return
|
|
400
|
+
get: function () { return chunkQQ4VARSD_cjs.explainEvents; }
|
|
395
401
|
});
|
|
396
402
|
Object.defineProperty(exports, "formatExplanation", {
|
|
397
403
|
enumerable: true,
|
|
398
|
-
get: function () { return
|
|
404
|
+
get: function () { return chunkQQ4VARSD_cjs.formatExplanation; }
|
|
399
405
|
});
|
|
400
406
|
exports.ReactRenderDetective = ReactRenderDetective;
|
|
401
407
|
exports.RenderDetective = RenderDetective;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/console/reporter.ts","../src/react/ancestry.ts","../src/react/instrument.tsx","../src/react/withRenderDetective.tsx","../src/react/RenderDetective.tsx","../src/react/hooks.ts","../src/index.ts"],"names":["formatInspected","createContext","getDetective","useContext","useState","useLayoutEffect","useCallback","jsx","Profiler","useRef","useEffect","React","inspect","shallowEqual","diffProps","componentName","explainEvents","formatExplanation"],"mappings":"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","file":"index.cjs","sourcesContent":["import { formatInspected } from \"../core/inspect.js\";\nimport type { Detective } from \"../core/store.js\";\nimport type { PropChange, RenderEvent } from \"../core/types.js\";\n\nconst ICON: Record<string, string> = {\n normal: \"·\",\n monitor: \"•\",\n slow: \"▲\",\n \"very-slow\": \"▲▲\",\n critical: \"■\",\n};\n\n/** Attaches console output. Returns an unsubscribe so init() stays idempotent. */\nexport function attachConsoleReporter(detective: Detective): () => void {\n return detective.subscribe((event) => {\n const mode = detective.config.mode;\n if (mode === \"silent\") return;\n try {\n if (mode === \"verbose\") printVerbose(event);\n else printConcise(event, detective.config.slowRenderThreshold);\n } catch {\n /* ignore */\n }\n });\n}\n\nfunction printConcise(event: RenderEvent, slowThreshold: number): void {\n const { diagnosis, timings, component } = event;\n const changed = event.changedProps.map((c) => c.key).join(\", \");\n const parts = [\n `[RRD] ${component.name} #${event.renderNumber}`,\n `Reason: ${label(event)}`,\n changed ? `Changed: ${changed}` : undefined,\n timings.subtreeDuration > 0 ? `Duration: ${timings.selfDuration.toFixed(1)}ms` : undefined,\n ].filter(Boolean);\n\n const line = `${ICON[diagnosis.severity] ?? \"·\"} ${parts.join(\" \")}`;\n const slow = timings.selfDuration >= slowThreshold;\n if (slow) console.warn(line);\n else console.log(line);\n}\n\nfunction printVerbose(event: RenderEvent): void {\n const { diagnosis, timings } = event;\n const header = `[RRD] ${event.component.name} #${event.renderNumber} — ${diagnosis.summary}`;\n\n const group = diagnosis.severity === \"normal\" ? console.groupCollapsed : console.group;\n group.call(console, header);\n try {\n console.log(`Cause: ${label(event)} (confidence: ${diagnosis.confidence})`);\n if (event.parent) {\n console.log(`Parent: ${event.parent.name}${event.parentRendered ? \" (re-rendered in this commit)\" : \" (did not re-render)\"}`);\n }\n console.log(\n `Cost: self ${timings.selfDuration.toFixed(1)}ms · subtree ${timings.subtreeDuration.toFixed(1)}ms` +\n (timings.accountedDescendantDuration > 0\n ? ` (${timings.accountedDescendantDuration.toFixed(1)}ms in instrumented children)`\n : \"\"),\n );\n\n if (event.changedProps.length > 0) {\n console.groupCollapsed(`Props changed (${event.changedProps.length})`);\n for (const c of event.changedProps) console.log(propLine(c));\n console.groupEnd();\n }\n if (event.unchangedProps.length > 0) {\n console.log(`Props same: ${event.unchangedProps.join(\", \")}`);\n }\n if (event.trackedState.length > 0) {\n console.log(\n `State: ${event.trackedState\n .map((s) => `${s.name}: ${formatInspected(s.previous)} → ${formatInspected(s.current)}`)\n .join(\", \")}`,\n );\n }\n if (event.contextChanges.length > 0) {\n console.log(`Context: ${event.contextChanges.map((c) => c.contextName).join(\", \")} changed in this commit`);\n }\n\n console.groupCollapsed(\"Evidence\");\n for (const line of diagnosis.evidence) console.log(`• ${line}`);\n console.groupEnd();\n\n if (diagnosis.suggestion) console.log(`Next step: ${diagnosis.suggestion}`);\n if (diagnosis.potentiallyAvoidable) console.log(\"Flag: potentially avoidable render\");\n } finally {\n console.groupEnd();\n }\n}\n\nfunction propLine(c: PropChange): string {\n const head = `${c.key} (${c.valueType}) — ${describeKind(c)}`;\n if (c.kind === \"added\") return `${head}: ${formatInspected(c.current)}`;\n if (c.kind === \"removed\") return `${head}: was ${formatInspected(c.previous)}`;\n return `${head}\\n previous: ${formatInspected(c.previous)}\\n current: ${formatInspected(c.current)}`;\n}\n\nfunction describeKind(c: PropChange): string {\n switch (c.kind) {\n case \"added\":\n return \"added\";\n case \"removed\":\n return \"removed\";\n case \"value\":\n return \"value changed\";\n case \"reference\":\n if (c.valueType === \"function\") return c.sourceEqual ? \"new function, identical source\" : \"new function reference\";\n return c.shallowEqual === true ? \"reference changed, contents identical\" : \"reference changed\";\n }\n}\n\nfunction label(event: RenderEvent): string {\n switch (event.diagnosis.reason) {\n case \"mount\":\n return \"mount\";\n case \"props\":\n return event.changedProps.every((c) => c.kind === \"reference\") ? \"prop changed (reference only)\" : \"prop changed\";\n case \"parent\":\n return \"parent rendered\";\n case \"context\":\n return \"context update\";\n case \"state\":\n return \"state changed\";\n case \"state-or-external\":\n return \"state or external store\";\n case \"unknown\":\n return \"undetermined\";\n }\n}\n","import { createContext } from \"react\";\nimport type { NodeRecord } from \"../core/store.js\";\n\n/**\n * Carries the nearest instrumented ancestor down the tree.\n *\n * The value is the node object, created once per mounted instance and **never\n * replaced**. That is deliberate: a context value that changed per render would\n * force every instrumented descendant to re-render, defeating `React.memo` and\n * changing the behaviour of the very app we are measuring.\n */\nexport const AncestryContext = createContext<NodeRecord | undefined>(undefined);\nAncestryContext.displayName = \"RenderDetectiveAncestry\";\n","import { Profiler, useCallback, useContext, useLayoutEffect, useState } from \"react\";\nimport type { ProfilerOnRenderCallback, ReactNode } from \"react\";\nimport { getDetective } from \"../core/store.js\";\nimport type { NodeRecord } from \"../core/store.js\";\nimport type { RenderPhase } from \"../core/types.js\";\nimport { AncestryContext } from \"./ancestry.js\";\n\nexport interface TrackOptions {\n /** Overrides the inferred component name. Required for anonymous components. */\n name?: string;\n}\n\n/**\n * Registers the current component, records this render attempt, and returns the\n * node plus a Profiler callback. All of it is a no-op when disabled.\n */\nexport function useInstrumentedNode(\n name: string,\n props: Record<string, unknown>,\n): { node: NodeRecord | undefined; onRender: ProfilerOnRenderCallback } {\n const detective = getDetective();\n const parent = useContext(AncestryContext);\n\n // The active/inactive decision is frozen per instance so the element tree\n // shape never changes underneath a mounted component.\n const [node] = useState<NodeRecord | undefined>(() =>\n detective.enabled ? detective.createNode(name, parent) : undefined,\n );\n\n useLayoutEffect(() => {\n if (!node) return;\n detective.attach(node);\n return () => detective.detach(node);\n }, [detective, node]);\n\n if (node) {\n try {\n detective.recordAttempt(node, props);\n } catch {\n /* never break the app being debugged (§46) */\n }\n }\n\n const onRender = useCallback<ProfilerOnRenderCallback>(\n (_id, phase, actualDuration, baseDuration, startTime, commitTime) => {\n if (!node) return;\n try {\n detective.recordCommit(node, {\n phase: phase as RenderPhase,\n subtreeDuration: actualDuration,\n baseDuration,\n startTime,\n commitTime,\n });\n } catch {\n /* ignore */\n }\n },\n [detective, node],\n );\n\n return { node, onRender };\n}\n\nexport function renderInstrumented(\n node: NodeRecord | undefined,\n onRender: ProfilerOnRenderCallback,\n children: ReactNode,\n): ReactNode {\n if (!node) return children;\n return (\n <Profiler id={node.id} onRender={onRender}>\n <AncestryContext.Provider value={node}>{children}</AncestryContext.Provider>\n </Profiler>\n );\n}\n\nexport function componentName(component: unknown, fallback = \"Anonymous\"): string {\n const c = component as { displayName?: string; name?: string; type?: unknown } | null;\n if (!c) return fallback;\n if (typeof c.displayName === \"string\" && c.displayName) return c.displayName;\n if (typeof c.name === \"string\" && c.name) return c.name;\n // React.memo / forwardRef wrappers keep the real component on `.type`.\n if (c.type) return componentName(c.type, fallback);\n return fallback;\n}\n","import type { ComponentType } from \"react\";\nimport { componentName, renderInstrumented, useInstrumentedNode } from \"./instrument.js\";\nimport type { TrackOptions } from \"./instrument.js\";\n\n/**\n * Wraps a component with full instrumentation: render counting, prop diffing,\n * parent attribution and Profiler timings.\n *\n * This is the most accurate integration mode — the wrapper's render function\n * runs exactly when the wrapped component re-renders, and it receives the same\n * props, so every diagnosis has first-hand evidence.\n *\n * Ref note: on React 19 `ref` is an ordinary prop and passes through the spread.\n * On React 18 and below, wrap with `forwardRef` yourself if the component needs\n * a ref.\n */\nexport function withRenderDetective<P extends object>(\n Component: ComponentType<P>,\n options: TrackOptions = {},\n): ComponentType<P> {\n const name = options.name ?? componentName(Component);\n\n function RenderDetected(props: P) {\n const { node, onRender } = useInstrumentedNode(name, props as Record<string, unknown>);\n return renderInstrumented(node, onRender, <Component {...props} />);\n }\n\n RenderDetected.displayName = `RenderDetective(${name})`;\n return RenderDetected;\n}\n","import type { ReactNode } from \"react\";\nimport { renderInstrumented, useInstrumentedNode } from \"./instrument.js\";\n\nexport interface RenderDetectiveProps {\n name: string;\n children: ReactNode;\n}\n\n/**\n * Zero-refactor integration: drop it around a subtree.\n *\n * <RenderDetective name=\"UserProfile\"><UserProfile /></RenderDetective>\n *\n * Accuracy note: this boundary only sees the `children` **element**, not the\n * wrapped component's props. It therefore reports timings, parent propagation\n * and children-element churn accurately, but it cannot attribute a render to an\n * individual prop. Use `withRenderDetective` when you want per-prop diagnosis.\n */\nexport function RenderDetective({ name, children }: RenderDetectiveProps): ReactNode {\n const { node, onRender } = useInstrumentedNode(name, { children });\n return renderInstrumented(node, onRender, children);\n}\n","import * as React from \"react\";\nimport { useContext, useEffect, useRef, useState } from \"react\";\nimport type { Dispatch, SetStateAction } from \"react\";\nimport { getDetective } from \"../core/store.js\";\nimport { inspect } from \"../core/inspect.js\";\nimport { diffProps, shallowEqual } from \"../core/compare.js\";\nimport type { RenderEvent } from \"../core/types.js\";\nimport { AncestryContext } from \"./ancestry.js\";\n\n/**\n * In-component diagnostics without wrapping.\n *\n * Limitation, by design: a hook cannot install a `<Profiler>` around its own\n * component, so **no duration is available in this mode** — timings are\n * reported as 0. Everything else (render count, prop diffing, parent\n * attribution) is as accurate as the HOC.\n */\nexport function useRenderDiagnostics(name: string, props?: Record<string, unknown>): RenderEvent | undefined {\n const detective = getDetective();\n const parent = useContext(AncestryContext);\n const [node] = useState(() => {\n if (!detective.enabled) return undefined;\n const created = detective.createNode(name, parent);\n if (created) detective.hookModeNodes.add(created);\n return created;\n });\n const last = useRef<RenderEvent | undefined>(undefined);\n\n useEffect(() => {\n if (!node) return;\n detective.attach(node);\n const unsubscribe = detective.subscribe((event) => {\n if (event.component.id === node.id) last.current = event;\n });\n return () => {\n unsubscribe();\n detective.detach(node);\n };\n }, [detective, node]);\n\n if (node) {\n const isMount = !node.seenCommit;\n detective.recordAttempt(node, props ?? {});\n // No Profiler in hook mode: enqueue with a sentinel commit time, which the\n // flush pass resolves to the next real commit that follows this render.\n detective.recordCommit(node, {\n phase: isMount ? \"mount\" : \"update\",\n subtreeDuration: 0,\n baseDuration: 0,\n startTime: 0,\n commitTime: -1,\n });\n }\n\n return last.current;\n}\n\n/**\n * React 18+. Used only to identify *which* component under an instrumented node\n * owns the tracked state; `useId` is stable across StrictMode's double render,\n * which a `useRef` token would not be. Older React simply loses the guard.\n */\nconst useOwnerId: () => string =\n typeof (React as { useId?: () => string }).useId === \"function\"\n ? (React as unknown as { useId: () => string }).useId\n : () => \"\";\n\n/**\n * `useState` with a label. The only reliable way to attribute a render to a\n * specific piece of local state without React internals.\n *\n * **Call this inside a component that is itself instrumented** (wrapped with\n * `withRenderDetective`, or using `useRenderDiagnostics`). A hook cannot see\n * which component called it, only the nearest instrumented ancestor — so state\n * named in an uninstrumented descendant would be reported as the ancestor's.\n * The first caller under a given node claims it and later callers are ignored,\n * which stops a descendant from stealing the attribution, but it cannot rescue\n * the case where the ancestor never tracks state of its own.\n */\nexport function useTrackedState<S>(name: string, initial: S | (() => S)): [S, Dispatch<SetStateAction<S>>] {\n const detective = getDetective();\n const node = useContext(AncestryContext);\n const ownerId = useOwnerId();\n const [state, setState] = useState<S>(initial);\n const previous = useRef<S>(state);\n\n if (node && node.stateOwner === undefined) node.stateOwner = ownerId;\n const owns = !node || node.stateOwner === ownerId;\n\n if (node && owns && !Object.is(previous.current, state)) {\n detective.recordStateChange(node, {\n name,\n previous: inspect(previous.current, detective.config.inspection),\n current: inspect(state, detective.config.inspection),\n });\n previous.current = state;\n } else if (!owns) {\n previous.current = state;\n }\n\n return [state, setState];\n}\n\n/**\n * `useEffect` that reports which of **its own declared** dependencies changed.\n *\n * This does not analyse whether the dependency list is correct — that needs\n * static analysis, not runtime observation (see docs/FEASIBILITY.md §9).\n */\nexport function useTrackedEffect(\n name: string,\n effect: () => void | (() => void),\n deps: unknown[],\n): void {\n const detective = getDetective();\n const previous = useRef<unknown[] | undefined>(undefined);\n const changed = useRef<number[]>([]);\n\n if (detective.enabled) {\n const prev = previous.current;\n changed.current = prev\n ? deps.map((d, i) => (Object.is(d, prev[i]) ? -1 : i)).filter((i) => i >= 0)\n : [];\n previous.current = deps;\n }\n\n useEffect(() => {\n if (detective.enabled && detective.config.mode !== \"silent\" && changed.current.length > 0) {\n // eslint-disable-next-line no-console\n console.debug(`[RRD] effect ${name} ran — deps changed at index ${changed.current.join(\", \")}`);\n }\n return effect();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, deps);\n}\n\n/**\n * Reports when a context value changes, from inside the provider.\n * Returns the value untouched.\n */\nexport function useTrackedContextValue<T>(contextName: string, value: T): T {\n const detective = getDetective();\n const previous = useRef<T | undefined>(undefined);\n const first = useRef(true);\n\n if (detective.enabled) {\n if (first.current) {\n first.current = false;\n } else if (!Object.is(previous.current, value)) {\n const prev = previous.current;\n const equal = shallowEqual(prev, value, detective.config);\n const changedKeys =\n isRecord(prev) && isRecord(value)\n ? diffProps(prev, value, detective.config).changed.map((c) => c.key)\n : [];\n detective.recordContextChange({\n contextName,\n changedKeys,\n referenceOnly: equal === true,\n commitTime: -1,\n });\n }\n previous.current = value;\n }\n\n return value;\n}\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}\n","import { attachConsoleReporter } from \"./console/reporter.js\";\nimport { explainEvents, formatExplanation } from \"./core/explain.js\";\nimport { getDetective } from \"./core/store.js\";\nimport type {\n AppStats,\n ComponentStats,\n DetectiveConfig,\n DetectiveOptions,\n RenderEvent,\n} from \"./core/types.js\";\n\nconst REPORTER = Symbol.for(\"react-render-detective.reporter\");\ntype Global = typeof globalThis & { [REPORTER]?: () => void };\n\n/**\n * Enable diagnostics. Safe to call repeatedly — Fast Refresh, duplicate module\n * copies and re-entrant setup all reconfigure the single instance rather than\n * stacking listeners (§47).\n */\nexport function init(options: DetectiveOptions = {}): void {\n const detective = getDetective();\n detective.init(options);\n\n const g = globalThis as Global;\n g[REPORTER]?.();\n g[REPORTER] = undefined;\n\n if (detective.enabled && detective.config.mode !== \"silent\") {\n g[REPORTER] = attachConsoleReporter(detective);\n } else if (!detective.enabled) {\n // Never fail silently: a disabled detective looks identical to a broken one.\n console.info(\n \"[RRD] Diagnostics are disabled\" +\n (detective.config.enabled === false ? \" (enabled: false, or a production build was detected).\" : \".\"),\n );\n }\n}\n\n/** Update configuration without re-initialising. */\nexport function configure(options: DetectiveOptions): void {\n getDetective().configure(options);\n}\n\nexport function getConfig(): Readonly<DetectiveConfig> {\n return getDetective().config;\n}\n\nexport function isEnabled(): boolean {\n return getDetective().enabled;\n}\n\n/** Oldest → newest, bounded by `maxEvents`. */\nexport function getEvents(): RenderEvent[] {\n return getDetective().getEvents();\n}\n\nexport function getStats(): AppStats {\n return getDetective().getStats();\n}\n\nexport function getComponentStats(name?: string): ComponentStats[] {\n return getDetective().getComponentStats(name);\n}\n\n/** Live event stream. Returns an unsubscribe function. */\nexport function subscribe(listener: (event: RenderEvent) => void): () => void {\n return getDetective().subscribe(listener);\n}\n\n/** Clears recorded events and statistics; instrumentation stays attached. */\nexport function clear(): void {\n getDetective().clear();\n}\n\n/** Full teardown, including configuration. Mainly for tests and HMR disposal. */\nexport function reset(): void {\n const g = globalThis as Global;\n g[REPORTER]?.();\n g[REPORTER] = undefined;\n getDetective().reset();\n}\n\n/**\n * The flagship answer: aggregate every recorded render of one component into a\n * single, human explanation. Returns `undefined` if nothing was recorded.\n */\nexport function explain(componentName: string): string | undefined {\n const explanation = explainEvents(componentName, getEvents());\n return explanation ? formatExplanation(explanation) : undefined;\n}\n\n/** Structured form of `explain`, for building UIs on top. */\nexport function explainStructured(componentName: string) {\n return explainEvents(componentName, getEvents());\n}\n\n/** Prints the application-level dashboard (§53). */\nexport function printStats(): void {\n const s = getStats();\n const lines = [\n \"React Render Detective\",\n \"\",\n `Components ${s.components}`,\n `Total renders ${s.totalRenders}`,\n `Total render time ${s.totalRenderTime.toFixed(1)}ms`,\n `Slow renders ${s.slowRenders}`,\n `Potentially avoidable ${s.potentiallyAvoidableRenders}`,\n ];\n if (s.devReplays > 0) {\n lines.push(`Development replays ${s.devReplays} (StrictMode / discarded — not counted above)`);\n }\n if (s.mostExpensive.length > 0) {\n lines.push(\"\", \"Top by cumulative render time\");\n for (const [i, c] of s.mostExpensive.slice(0, 5).entries()) {\n lines.push(\n `${String(i + 1).padStart(2)}. ${c.name.padEnd(22)} ${String(c.renderCount).padStart(6)} renders ${c.totalSelfDuration\n .toFixed(1)\n .padStart(8)}ms (avg ${c.averageSelfDuration.toFixed(1)}ms, p95 ${c.p95SelfDuration.toFixed(1)}ms)`,\n );\n }\n }\n console.log(lines.join(\"\\n\"));\n}\n\n/** Namespaced form, matching the documented `ReactRenderDetective.init()` usage. */\nexport const ReactRenderDetective = {\n init,\n configure,\n getConfig,\n isEnabled,\n getEvents,\n getStats,\n getComponentStats,\n subscribe,\n clear,\n reset,\n explain,\n explainStructured,\n printStats,\n};\n\nexport { withRenderDetective } from \"./react/withRenderDetective.js\";\nexport { RenderDetective } from \"./react/RenderDetective.js\";\nexport type { RenderDetectiveProps } from \"./react/RenderDetective.js\";\nexport {\n useRenderDiagnostics,\n useTrackedState,\n useTrackedEffect,\n useTrackedContextValue,\n} from \"./react/hooks.js\";\nexport type { TrackOptions } from \"./react/instrument.js\";\nexport { explainEvents, formatExplanation } from \"./core/explain.js\";\nexport type { Explanation } from \"./core/explain.js\";\nexport type * from \"./core/types.js\";\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/console/reporter.ts","../src/react/ancestry.ts","../src/react/instrument.tsx","../src/react/withRenderDetective.tsx","../src/react/RenderDetective.tsx","../src/react/hooks.ts","../src/index.ts"],"names":["formatInspected","createContext","getDetective","useContext","useState","useLayoutEffect","useCallback","jsx","Profiler","useRef","useEffect","React","inspect","shallowEqual","diffProps","componentName","explainEvents","formatExplanation"],"mappings":"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","file":"index.cjs","sourcesContent":["import { formatInspected } from \"../core/inspect.js\";\nimport type { Detective } from \"../core/store.js\";\nimport type { PropChange, RenderEvent } from \"../core/types.js\";\n\nconst ICON: Record<string, string> = {\n normal: \"·\",\n monitor: \"•\",\n slow: \"▲\",\n \"very-slow\": \"▲▲\",\n critical: \"■\",\n};\n\n/** Attaches console output. Returns an unsubscribe so init() stays idempotent. */\nexport function attachConsoleReporter(detective: Detective): () => void {\n return detective.subscribe((event) => {\n const mode = detective.config.mode;\n if (mode === \"silent\") return;\n try {\n if (mode === \"verbose\") printVerbose(event);\n else printConcise(event, detective.config.slowRenderThreshold);\n } catch {\n /* ignore */\n }\n });\n}\n\nfunction printConcise(event: RenderEvent, slowThreshold: number): void {\n const { diagnosis, timings, component } = event;\n const changed = event.changedProps.map((c) => c.key).join(\", \");\n const parts = [\n `[RRD] ${component.name} #${event.renderNumber}`,\n `Reason: ${label(event)}`,\n changed ? `Changed: ${changed}` : undefined,\n timings.subtreeDuration > 0 ? `Duration: ${timings.selfDuration.toFixed(1)}ms` : undefined,\n ].filter(Boolean);\n\n const line = `${ICON[diagnosis.severity] ?? \"·\"} ${parts.join(\" \")}`;\n const slow = timings.selfDuration >= slowThreshold;\n if (slow) console.warn(line);\n else console.log(line);\n}\n\nfunction printVerbose(event: RenderEvent): void {\n const { diagnosis, timings } = event;\n const header = `[RRD] ${event.component.name} #${event.renderNumber} — ${diagnosis.summary}`;\n\n const group = diagnosis.severity === \"normal\" ? console.groupCollapsed : console.group;\n group.call(console, header);\n try {\n console.log(`Cause: ${label(event)} (confidence: ${diagnosis.confidence})`);\n if (event.component.source) console.log(`Source: ${event.component.source}`);\n if (event.parent) {\n console.log(\n `Parent: ${event.parent.name}${event.parent.source ? ` (${event.parent.source})` : \"\"}` +\n `${event.parentRendered ? \" — re-rendered in this commit\" : \" — did not re-render\"}`,\n );\n }\n console.log(\n `Cost: self ${timings.selfDuration.toFixed(1)}ms · subtree ${timings.subtreeDuration.toFixed(1)}ms` +\n (timings.accountedDescendantDuration > 0\n ? ` (${timings.accountedDescendantDuration.toFixed(1)}ms in instrumented children)`\n : \"\"),\n );\n\n if (event.changedProps.length > 0) {\n console.groupCollapsed(`Props changed (${event.changedProps.length})`);\n for (const c of event.changedProps) console.log(propLine(c));\n console.groupEnd();\n }\n if (event.unchangedProps.length > 0) {\n console.log(`Props same: ${event.unchangedProps.join(\", \")}`);\n }\n if (event.trackedState.length > 0) {\n console.log(\n `State: ${event.trackedState\n .map((s) => `${s.name}: ${formatInspected(s.previous)} → ${formatInspected(s.current)}`)\n .join(\", \")}`,\n );\n }\n if (event.contextChanges.length > 0) {\n console.log(`Context: ${event.contextChanges.map((c) => c.contextName).join(\", \")} changed in this commit`);\n }\n\n console.groupCollapsed(\"Evidence\");\n for (const line of diagnosis.evidence) console.log(`• ${line}`);\n console.groupEnd();\n\n if (diagnosis.suggestion) console.log(`Next step: ${diagnosis.suggestion}`);\n if (diagnosis.potentiallyAvoidable) console.log(\"Flag: potentially avoidable render\");\n } finally {\n console.groupEnd();\n }\n}\n\nfunction propLine(c: PropChange): string {\n const head = `${c.key} (${c.valueType}) — ${describeKind(c)}`;\n if (c.kind === \"added\") return `${head}: ${formatInspected(c.current)}`;\n if (c.kind === \"removed\") return `${head}: was ${formatInspected(c.previous)}`;\n return `${head}\\n previous: ${formatInspected(c.previous)}\\n current: ${formatInspected(c.current)}`;\n}\n\nfunction describeKind(c: PropChange): string {\n switch (c.kind) {\n case \"added\":\n return \"added\";\n case \"removed\":\n return \"removed\";\n case \"value\":\n return \"value changed\";\n case \"reference\":\n if (c.valueType === \"function\") return c.sourceEqual ? \"new function, identical source\" : \"new function reference\";\n return c.shallowEqual === true ? \"reference changed, contents identical\" : \"reference changed\";\n }\n}\n\nfunction label(event: RenderEvent): string {\n switch (event.diagnosis.reason) {\n case \"mount\":\n return \"mount\";\n case \"props\":\n return event.changedProps.every((c) => c.kind === \"reference\") ? \"prop changed (reference only)\" : \"prop changed\";\n case \"parent\":\n return \"parent rendered\";\n case \"context\":\n return \"context update\";\n case \"state\":\n return \"state changed\";\n case \"state-or-external\":\n return \"state or external store\";\n case \"unknown\":\n return \"undetermined\";\n }\n}\n","import { createContext } from \"react\";\nimport type { NodeRecord } from \"../core/store.js\";\n\n/**\n * Carries the nearest instrumented ancestor down the tree.\n *\n * The value is the node object, created once per mounted instance and **never\n * replaced**. That is deliberate: a context value that changed per render would\n * force every instrumented descendant to re-render, defeating `React.memo` and\n * changing the behaviour of the very app we are measuring.\n */\nexport const AncestryContext = createContext<NodeRecord | undefined>(undefined);\nAncestryContext.displayName = \"RenderDetectiveAncestry\";\n","import { Profiler, useCallback, useContext, useLayoutEffect, useState } from \"react\";\nimport type { ProfilerOnRenderCallback, ReactNode } from \"react\";\nimport { getDetective } from \"../core/store.js\";\nimport type { NodeRecord } from \"../core/store.js\";\nimport type { RenderPhase } from \"../core/types.js\";\nimport { AncestryContext } from \"./ancestry.js\";\n\nexport interface TrackOptions {\n /** Overrides the inferred component name. Required for anonymous components. */\n name?: string;\n /**\n * `path/to/File.tsx:12:2`. Supplied automatically by the build plugin; there\n * is no runtime way to obtain it.\n */\n source?: string;\n}\n\n/**\n * Registers the current component, records this render attempt, and returns the\n * node plus a Profiler callback. All of it is a no-op when disabled.\n */\nexport function useInstrumentedNode(\n name: string,\n props: Record<string, unknown>,\n source?: string,\n): { node: NodeRecord | undefined; onRender: ProfilerOnRenderCallback } {\n const detective = getDetective();\n const parent = useContext(AncestryContext);\n\n // The active/inactive decision is frozen per instance so the element tree\n // shape never changes underneath a mounted component.\n const [node] = useState<NodeRecord | undefined>(() =>\n detective.enabled ? detective.createNode(name, parent, source) : undefined,\n );\n\n useLayoutEffect(() => {\n if (!node) return;\n detective.attach(node);\n return () => detective.detach(node);\n }, [detective, node]);\n\n if (node) {\n try {\n detective.recordAttempt(node, props);\n } catch {\n /* never break the app being debugged (§46) */\n }\n }\n\n const onRender = useCallback<ProfilerOnRenderCallback>(\n (_id, phase, actualDuration, baseDuration, startTime, commitTime) => {\n if (!node) return;\n try {\n detective.recordCommit(node, {\n phase: phase as RenderPhase,\n subtreeDuration: actualDuration,\n baseDuration,\n startTime,\n commitTime,\n });\n } catch {\n /* ignore */\n }\n },\n [detective, node],\n );\n\n return { node, onRender };\n}\n\nexport function renderInstrumented(\n node: NodeRecord | undefined,\n onRender: ProfilerOnRenderCallback,\n children: ReactNode,\n): ReactNode {\n if (!node) return children;\n return (\n <Profiler id={node.id} onRender={onRender}>\n <AncestryContext.Provider value={node}>{children}</AncestryContext.Provider>\n </Profiler>\n );\n}\n\nexport function componentName(component: unknown, fallback = \"Anonymous\"): string {\n const c = component as { displayName?: string; name?: string; type?: unknown } | null;\n if (!c) return fallback;\n if (typeof c.displayName === \"string\" && c.displayName) return c.displayName;\n if (typeof c.name === \"string\" && c.name) return c.name;\n // React.memo / forwardRef wrappers keep the real component on `.type`.\n if (c.type) return componentName(c.type, fallback);\n return fallback;\n}\n","import type { ComponentType } from \"react\";\n\n/** Marks a component as one of ours, so it is never wrapped twice. */\nconst IS_WRAPPER = Symbol.for(\"react-render-detective.wrapper\");\nimport { componentName, renderInstrumented, useInstrumentedNode } from \"./instrument.js\";\nimport type { TrackOptions } from \"./instrument.js\";\n\n/**\n * Wraps a component with full instrumentation: render counting, prop diffing,\n * parent attribution and Profiler timings.\n *\n * This is the most accurate integration mode — the wrapper's render function\n * runs exactly when the wrapped component re-renders, and it receives the same\n * props, so every diagnosis has first-hand evidence.\n *\n * Ref note: on React 19 `ref` is an ordinary prop and passes through the spread.\n * On React 18 and below, wrap with `forwardRef` yourself if the component needs\n * a ref.\n */\nexport function withRenderDetective<P extends object>(\n Component: ComponentType<P>,\n options: TrackOptions = {},\n): ComponentType<P> {\n /*\n * Wrapping a wrapper would make it render itself. That is unreachable through\n * careful hand-wrapping but trivially reachable once a build plugin is\n * wrapping everything, and the failure mode is a stack overflow before first\n * paint — so return the existing wrapper instead.\n */\n if ((Component as { [IS_WRAPPER]?: boolean })[IS_WRAPPER]) return Component;\n\n const name = options.name ?? componentName(Component);\n\n function RenderDetected(props: P) {\n const { node, onRender } = useInstrumentedNode(name, props as Record<string, unknown>, options.source);\n return renderInstrumented(node, onRender, <Component {...props} />);\n }\n\n RenderDetected.displayName = `RenderDetective(${name})`;\n (RenderDetected as { [IS_WRAPPER]?: boolean })[IS_WRAPPER] = true;\n return RenderDetected;\n}\n","import type { ReactNode } from \"react\";\nimport { renderInstrumented, useInstrumentedNode } from \"./instrument.js\";\n\nexport interface RenderDetectiveProps {\n name: string;\n children: ReactNode;\n}\n\n/**\n * Zero-refactor integration: drop it around a subtree.\n *\n * <RenderDetective name=\"UserProfile\"><UserProfile /></RenderDetective>\n *\n * Accuracy note: this boundary only sees the `children` **element**, not the\n * wrapped component's props. It therefore reports timings, parent propagation\n * and children-element churn accurately, but it cannot attribute a render to an\n * individual prop. Use `withRenderDetective` when you want per-prop diagnosis.\n */\nexport function RenderDetective({ name, children }: RenderDetectiveProps): ReactNode {\n const { node, onRender } = useInstrumentedNode(name, { children });\n return renderInstrumented(node, onRender, children);\n}\n","import * as React from \"react\";\nimport { useContext, useEffect, useRef, useState } from \"react\";\nimport type { Dispatch, SetStateAction } from \"react\";\nimport { getDetective } from \"../core/store.js\";\nimport { inspect } from \"../core/inspect.js\";\nimport { diffProps, shallowEqual } from \"../core/compare.js\";\nimport type { RenderEvent } from \"../core/types.js\";\nimport { AncestryContext } from \"./ancestry.js\";\n\n/**\n * In-component diagnostics without wrapping.\n *\n * Limitation, by design: a hook cannot install a `<Profiler>` around its own\n * component, so **no duration is available in this mode** — timings are\n * reported as 0. Everything else (render count, prop diffing, parent\n * attribution) is as accurate as the HOC.\n */\nexport function useRenderDiagnostics(name: string, props?: Record<string, unknown>): RenderEvent | undefined {\n const detective = getDetective();\n const parent = useContext(AncestryContext);\n const [node] = useState(() => {\n if (!detective.enabled) return undefined;\n const created = detective.createNode(name, parent);\n if (created) detective.hookModeNodes.add(created);\n return created;\n });\n const last = useRef<RenderEvent | undefined>(undefined);\n\n useEffect(() => {\n if (!node) return;\n detective.attach(node);\n const unsubscribe = detective.subscribe((event) => {\n if (event.component.id === node.id) last.current = event;\n });\n return () => {\n unsubscribe();\n detective.detach(node);\n };\n }, [detective, node]);\n\n if (node) {\n const isMount = !node.seenCommit;\n detective.recordAttempt(node, props ?? {});\n // No Profiler in hook mode: enqueue with a sentinel commit time, which the\n // flush pass resolves to the next real commit that follows this render.\n detective.recordCommit(node, {\n phase: isMount ? \"mount\" : \"update\",\n subtreeDuration: 0,\n baseDuration: 0,\n startTime: 0,\n commitTime: -1,\n });\n }\n\n return last.current;\n}\n\n/**\n * React 18+. Used only to identify *which* component under an instrumented node\n * owns the tracked state; `useId` is stable across StrictMode's double render,\n * which a `useRef` token would not be. Older React simply loses the guard.\n */\nconst useOwnerId: () => string =\n typeof (React as { useId?: () => string }).useId === \"function\"\n ? (React as unknown as { useId: () => string }).useId\n : () => \"\";\n\n/**\n * `useState` with a label. The only reliable way to attribute a render to a\n * specific piece of local state without React internals.\n *\n * **Call this inside a component that is itself instrumented** (wrapped with\n * `withRenderDetective`, or using `useRenderDiagnostics`). A hook cannot see\n * which component called it, only the nearest instrumented ancestor — so state\n * named in an uninstrumented descendant would be reported as the ancestor's.\n * The first caller under a given node claims it and later callers are ignored,\n * which stops a descendant from stealing the attribution, but it cannot rescue\n * the case where the ancestor never tracks state of its own.\n */\nexport function useTrackedState<S>(name: string, initial: S | (() => S)): [S, Dispatch<SetStateAction<S>>] {\n const detective = getDetective();\n const node = useContext(AncestryContext);\n const ownerId = useOwnerId();\n const [state, setState] = useState<S>(initial);\n const previous = useRef<S>(state);\n\n if (node && node.stateOwner === undefined) node.stateOwner = ownerId;\n const owns = !node || node.stateOwner === ownerId;\n\n if (node && owns && !Object.is(previous.current, state)) {\n detective.recordStateChange(node, {\n name,\n previous: inspect(previous.current, detective.config.inspection),\n current: inspect(state, detective.config.inspection),\n });\n previous.current = state;\n } else if (!owns) {\n previous.current = state;\n }\n\n return [state, setState];\n}\n\n/**\n * `useEffect` that reports which of **its own declared** dependencies changed.\n *\n * This does not analyse whether the dependency list is correct — that needs\n * static analysis, not runtime observation (see docs/FEASIBILITY.md §9).\n */\nexport function useTrackedEffect(\n name: string,\n effect: () => void | (() => void),\n deps: unknown[],\n): void {\n const detective = getDetective();\n const previous = useRef<unknown[] | undefined>(undefined);\n const changed = useRef<number[]>([]);\n\n if (detective.enabled) {\n const prev = previous.current;\n changed.current = prev\n ? deps.map((d, i) => (Object.is(d, prev[i]) ? -1 : i)).filter((i) => i >= 0)\n : [];\n previous.current = deps;\n }\n\n useEffect(() => {\n if (detective.enabled && detective.config.mode !== \"silent\" && changed.current.length > 0) {\n // eslint-disable-next-line no-console\n console.debug(`[RRD] effect ${name} ran — deps changed at index ${changed.current.join(\", \")}`);\n }\n return effect();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, deps);\n}\n\n/**\n * Reports when a context value changes, from inside the provider.\n * Returns the value untouched.\n */\nexport function useTrackedContextValue<T>(contextName: string, value: T): T {\n const detective = getDetective();\n const previous = useRef<T | undefined>(undefined);\n const first = useRef(true);\n\n if (detective.enabled) {\n if (first.current) {\n first.current = false;\n } else if (!Object.is(previous.current, value)) {\n const prev = previous.current;\n const equal = shallowEqual(prev, value, detective.config);\n const changedKeys =\n isRecord(prev) && isRecord(value)\n ? diffProps(prev, value, detective.config).changed.map((c) => c.key)\n : [];\n detective.recordContextChange({\n contextName,\n changedKeys,\n referenceOnly: equal === true,\n commitTime: -1,\n });\n }\n previous.current = value;\n }\n\n return value;\n}\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}\n","import { attachConsoleReporter } from \"./console/reporter.js\";\nimport { explainEvents, formatExplanation } from \"./core/explain.js\";\nimport { getDetective } from \"./core/store.js\";\nimport type {\n AppStats,\n ComponentStats,\n DetectiveConfig,\n DetectiveOptions,\n RenderEvent,\n} from \"./core/types.js\";\n\nconst REPORTER = Symbol.for(\"react-render-detective.reporter\");\ntype Global = typeof globalThis & { [REPORTER]?: () => void };\n\n/**\n * Enable diagnostics. Safe to call repeatedly — Fast Refresh, duplicate module\n * copies and re-entrant setup all reconfigure the single instance rather than\n * stacking listeners (§47).\n */\nexport function init(options: DetectiveOptions = {}): void {\n const detective = getDetective();\n detective.init(options);\n\n const g = globalThis as Global;\n g[REPORTER]?.();\n g[REPORTER] = undefined;\n\n if (detective.enabled && detective.config.mode !== \"silent\") {\n g[REPORTER] = attachConsoleReporter(detective);\n } else if (!detective.enabled) {\n // Never fail silently: a disabled detective looks identical to a broken one.\n console.info(\n \"[RRD] Diagnostics are disabled\" +\n (detective.config.enabled === false ? \" (enabled: false, or a production build was detected).\" : \".\"),\n );\n }\n}\n\n/** Update configuration without re-initialising. */\nexport function configure(options: DetectiveOptions): void {\n getDetective().configure(options);\n}\n\nexport function getConfig(): Readonly<DetectiveConfig> {\n return getDetective().config;\n}\n\nexport function isEnabled(): boolean {\n return getDetective().enabled;\n}\n\n/** Oldest → newest, bounded by `maxEvents`. */\nexport function getEvents(): RenderEvent[] {\n return getDetective().getEvents();\n}\n\nexport function getStats(): AppStats {\n return getDetective().getStats();\n}\n\nexport function getComponentStats(name?: string): ComponentStats[] {\n return getDetective().getComponentStats(name);\n}\n\n/** Live event stream. Returns an unsubscribe function. */\nexport function subscribe(listener: (event: RenderEvent) => void): () => void {\n return getDetective().subscribe(listener);\n}\n\n/** Clears recorded events and statistics; instrumentation stays attached. */\nexport function clear(): void {\n getDetective().clear();\n}\n\n/** Full teardown, including configuration. Mainly for tests and HMR disposal. */\nexport function reset(): void {\n const g = globalThis as Global;\n g[REPORTER]?.();\n g[REPORTER] = undefined;\n getDetective().reset();\n}\n\n/**\n * The flagship answer: aggregate every recorded render of one component into a\n * single, human explanation. Returns `undefined` if nothing was recorded.\n */\nexport function explain(componentName: string): string | undefined {\n const explanation = explainEvents(componentName, getEvents());\n return explanation ? formatExplanation(explanation) : undefined;\n}\n\n/** Structured form of `explain`, for building UIs on top. */\nexport function explainStructured(componentName: string) {\n return explainEvents(componentName, getEvents());\n}\n\n/** Prints the application-level dashboard (§53). */\nexport function printStats(): void {\n const s = getStats();\n const lines = [\n \"React Render Detective\",\n \"\",\n `Components ${s.components}`,\n `Total renders ${s.totalRenders}`,\n `Total render time ${s.totalRenderTime.toFixed(1)}ms`,\n `Slow renders ${s.slowRenders}`,\n `Potentially avoidable ${s.potentiallyAvoidableRenders}`,\n ];\n if (s.devReplays > 0) {\n lines.push(`Development replays ${s.devReplays} (StrictMode / discarded — not counted above)`);\n }\n if (s.mostExpensive.length > 0) {\n lines.push(\"\", \"Top by cumulative render time\");\n for (const [i, c] of s.mostExpensive.slice(0, 5).entries()) {\n lines.push(\n `${String(i + 1).padStart(2)}. ${c.name.padEnd(22)} ${String(c.renderCount).padStart(6)} renders ${c.totalSelfDuration\n .toFixed(1)\n .padStart(8)}ms (avg ${c.averageSelfDuration.toFixed(1)}ms, p95 ${c.p95SelfDuration.toFixed(1)}ms)`,\n );\n }\n }\n console.log(lines.join(\"\\n\"));\n}\n\n/** Namespaced form, matching the documented `ReactRenderDetective.init()` usage. */\nexport const ReactRenderDetective = {\n init,\n configure,\n getConfig,\n isEnabled,\n getEvents,\n getStats,\n getComponentStats,\n subscribe,\n clear,\n reset,\n explain,\n explainStructured,\n printStats,\n};\n\nexport { withRenderDetective } from \"./react/withRenderDetective.js\";\nexport { RenderDetective } from \"./react/RenderDetective.js\";\nexport type { RenderDetectiveProps } from \"./react/RenderDetective.js\";\nexport {\n useRenderDiagnostics,\n useTrackedState,\n useTrackedEffect,\n useTrackedContextValue,\n} from \"./react/hooks.js\";\nexport type { TrackOptions } from \"./react/instrument.js\";\nexport { explainEvents, formatExplanation } from \"./core/explain.js\";\nexport type { Explanation } from \"./core/explain.js\";\nexport type * from \"./core/types.js\";\n"]}
|