react-render-detective 0.5.0 → 0.6.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/CHANGELOG.md +55 -0
- package/README.md +2 -2
- package/dist/babel.cjs +2 -2
- package/dist/babel.d.cts +11 -0
- package/dist/babel.d.ts +11 -0
- package/dist/babel.js +1 -1
- package/dist/{chunk-DI5STJM4.js → chunk-3TZNM7JO.js} +66 -3
- package/dist/chunk-3TZNM7JO.js.map +1 -0
- package/dist/{chunk-HOTY7J3X.js → chunk-D7M4UC3Y.js} +26 -2
- package/dist/chunk-D7M4UC3Y.js.map +1 -0
- package/dist/{chunk-PHYYA67T.cjs → chunk-EXDRRUUQ.cjs} +69 -2
- package/dist/chunk-EXDRRUUQ.cjs.map +1 -0
- package/dist/{chunk-OSPTERGK.cjs → chunk-LCGXVRJR.cjs} +66 -3
- package/dist/chunk-LCGXVRJR.cjs.map +1 -0
- package/dist/{chunk-MCIQMYNR.js → chunk-QVHAC7AD.js} +69 -2
- package/dist/chunk-QVHAC7AD.js.map +1 -0
- package/dist/{chunk-JD4IJ4MN.cjs → chunk-RGT44QQI.cjs} +26 -2
- package/dist/chunk-RGT44QQI.cjs.map +1 -0
- package/dist/core.cjs +18 -18
- package/dist/core.d.cts +13 -3
- package/dist/core.d.ts +13 -3
- package/dist/core.js +1 -1
- package/dist/index.cjs +72 -274
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +35 -115
- package/dist/index.d.ts +35 -115
- package/dist/index.js +41 -237
- package/dist/index.js.map +1 -1
- package/dist/interactions.cjs +252 -0
- package/dist/interactions.cjs.map +1 -0
- package/dist/interactions.d.cts +110 -0
- package/dist/interactions.d.ts +110 -0
- package/dist/interactions.js +241 -0
- package/dist/interactions.js.map +1 -0
- package/dist/overlay.cjs +5 -5
- package/dist/overlay.js +2 -2
- package/dist/testing.d.cts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/{types-gl13xwmN.d.cts → types-CRET8EhB.d.cts} +20 -2
- package/dist/{types-gl13xwmN.d.ts → types-CRET8EhB.d.ts} +20 -2
- package/dist/vite.cjs +2 -2
- package/dist/vite.js +1 -1
- package/package.json +6 -1
- package/dist/chunk-DI5STJM4.js.map +0 -1
- package/dist/chunk-HOTY7J3X.js.map +0 -1
- package/dist/chunk-JD4IJ4MN.cjs.map +0 -1
- package/dist/chunk-MCIQMYNR.js.map +0 -1
- package/dist/chunk-OSPTERGK.cjs.map +0 -1
- package/dist/chunk-PHYYA67T.cjs.map +0 -1
|
@@ -6,6 +6,7 @@ function explainEvents(component, events, lifecycle) {
|
|
|
6
6
|
const denominator = updates.length || mine.length;
|
|
7
7
|
const reasonCounts = /* @__PURE__ */ new Map();
|
|
8
8
|
const unstable = /* @__PURE__ */ new Map();
|
|
9
|
+
const unstableSelector = /* @__PURE__ */ new Map();
|
|
9
10
|
let totalSelf = 0;
|
|
10
11
|
let avoidable = 0;
|
|
11
12
|
let avoidableTime = 0;
|
|
@@ -24,6 +25,12 @@ function explainEvents(component, events, lifecycle) {
|
|
|
24
25
|
if (e.changedProps.some((c) => c.kind !== "reference")) propsWithNewValues++;
|
|
25
26
|
else propsReferenceOnly++;
|
|
26
27
|
}
|
|
28
|
+
for (const c of e.selectorChanges) {
|
|
29
|
+
if (!c.referenceOnly) continue;
|
|
30
|
+
const entry = unstableSelector.get(c.name) ?? { count: 0, source: c.source };
|
|
31
|
+
entry.count++;
|
|
32
|
+
unstableSelector.set(c.name, entry);
|
|
33
|
+
}
|
|
27
34
|
for (const c of e.changedProps) {
|
|
28
35
|
if (c.kind !== "reference") continue;
|
|
29
36
|
const entry = unstable.get(c.key) ?? { count: 0, valueType: c.valueType };
|
|
@@ -32,9 +39,11 @@ function explainEvents(component, events, lifecycle) {
|
|
|
32
39
|
}
|
|
33
40
|
}
|
|
34
41
|
const breakdown = [...reasonCounts.entries()].map(([reason, count]) => ({ reason, count, share: count / mine.length })).sort((a, b) => b.count - a.count);
|
|
42
|
+
const unstableSelectors = [...unstableSelector.entries()].map(([name, v]) => ({ name, source: v.source, count: v.count, share: v.count / denominator })).sort((a, b) => b.count - a.count);
|
|
35
43
|
const unstableProps = [...unstable.entries()].map(([key, v]) => ({ key, count: v.count, share: v.count / denominator, valueType: v.valueType })).sort((a, b) => b.count - a.count);
|
|
36
44
|
const top = [...reasonCounts.entries()].filter(([reason]) => reason !== "mount").map(([reason, count]) => ({ reason, count, share: count / denominator })).sort((a, b) => b.count - a.count)[0];
|
|
37
45
|
const topProp = unstableProps[0];
|
|
46
|
+
const topSelector = unstableSelectors[0];
|
|
38
47
|
const latest = mine[mine.length - 1];
|
|
39
48
|
let headline;
|
|
40
49
|
let nextStep;
|
|
@@ -45,6 +54,10 @@ function explainEvents(component, events, lifecycle) {
|
|
|
45
54
|
headline = `${component} was rebuilt ${remounts}\xD7 rather than re-rendered \u2014 its DOM and state were discarded each time.`;
|
|
46
55
|
nextStep = mountEvent?.diagnosis.suggestion ?? `Check whether ${component} is declared inside another component's render body, or receives a changing \`key\`.`;
|
|
47
56
|
confidence = mountEvent?.diagnosis.confidence ?? "medium";
|
|
57
|
+
} else if (topSelector && topSelector.share >= 0.5) {
|
|
58
|
+
headline = `${pct(topSelector.share)} of updates followed the \`${topSelector.name}\` selector returning a new reference with identical contents \u2014 so this re-renders on every store update, not only when its data changes.`;
|
|
59
|
+
nextStep = `Make \`${topSelector.name}\`${topSelector.source ? ` at ${topSelector.source}` : ""} return a stable value: select the raw slice and derive outside the selector, memoize with createSelector, or pass shallowEqual.`;
|
|
60
|
+
confidence = "high";
|
|
48
61
|
} else if (topProp && topProp.share >= 0.5) {
|
|
49
62
|
headline = `${pct(topProp.share)} of updates followed \`${topProp.key}\` changing by reference while its contents stayed the same.`;
|
|
50
63
|
const via = latest.parent?.name;
|
|
@@ -59,6 +72,10 @@ function explainEvents(component, events, lifecycle) {
|
|
|
59
72
|
headline = `${pct(top.share)} of updates followed a tracked context update.`;
|
|
60
73
|
nextStep = "Check whether the provider's value is stable, and whether this component needs the whole context value.";
|
|
61
74
|
confidence = "medium";
|
|
75
|
+
} else if (top && top.reason === "store") {
|
|
76
|
+
headline = `${pct(top.share)} of updates came from tracked store selectors whose values genuinely changed.`;
|
|
77
|
+
nextStep = "These are real data changes. Look at what each render costs rather than how many there are, or select less per component so fewer components wake on each store update.";
|
|
78
|
+
confidence = "high";
|
|
62
79
|
} else if (top && (top.reason === "state" || top.reason === "state-or-external")) {
|
|
63
80
|
headline = `${pct(top.share)} of updates came from inside the component \u2014 state or an external store.`;
|
|
64
81
|
nextStep = top.reason === "state" ? "These are real state updates. Check whether every update needs to change state." : "Name the state with useTrackedState to see which value drives these renders.";
|
|
@@ -92,6 +109,7 @@ function explainEvents(component, events, lifecycle) {
|
|
|
92
109
|
unstableProps,
|
|
93
110
|
averageSelfDuration: totalSelf / mine.length,
|
|
94
111
|
totalSelfDuration: totalSelf,
|
|
112
|
+
unstableSelectors,
|
|
95
113
|
potentiallyAvoidableRenders: avoidable,
|
|
96
114
|
estimatedAvoidableTime: avoidableTime,
|
|
97
115
|
devReplays: replays,
|
|
@@ -113,6 +131,12 @@ function formatExplanation(e) {
|
|
|
113
131
|
"Breakdown (share of all renders)",
|
|
114
132
|
...e.breakdown.map((b) => ` ${b.reason.padEnd(18)} ${String(b.count).padStart(5)} ${pct(b.share)}`)
|
|
115
133
|
];
|
|
134
|
+
if (e.unstableSelectors.length > 0) {
|
|
135
|
+
lines.push("", "Selectors returning a new reference each call (share of updates)");
|
|
136
|
+
for (const sel of e.unstableSelectors) {
|
|
137
|
+
lines.push(` ${sel.name.padEnd(24)} ${String(sel.count).padStart(5)} ${pct(sel.share)}${sel.source ? ` ${sel.source}` : ""}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
116
140
|
if (e.unstableProps.length > 0) {
|
|
117
141
|
lines.push("", "Reference-only prop changes (share of updates)");
|
|
118
142
|
for (const p of e.unstableProps) {
|
|
@@ -142,5 +166,5 @@ var pct = (n) => `${Math.round(n * 100)}%`;
|
|
|
142
166
|
var fmt = (ms) => `${ms.toFixed(1)}ms`;
|
|
143
167
|
|
|
144
168
|
export { explainEvents, formatExplanation };
|
|
145
|
-
//# sourceMappingURL=chunk-
|
|
146
|
-
//# sourceMappingURL=chunk-
|
|
169
|
+
//# sourceMappingURL=chunk-D7M4UC3Y.js.map
|
|
170
|
+
//# sourceMappingURL=chunk-D7M4UC3Y.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/explain.ts"],"names":[],"mappings":"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","file":"chunk-D7M4UC3Y.js","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 /** Store selectors that returned a new reference with identical contents. */\n unstableSelectors: Array<{ name: string; source?: string; count: number; share: number }>;\n averageSelfDuration: number;\n totalSelfDuration: number;\n potentiallyAvoidableRenders: number;\n estimatedAvoidableTime: number;\n devReplays: number;\n /** Times the component was rebuilt rather than re-rendered. */\n remounts: 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(\n component: string,\n events: RenderEvent[],\n lifecycle?: { remounts: number },\n): 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 const unstableSelector = new Map<string, { count: number; source?: 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.selectorChanges) {\n if (!c.referenceOnly) continue;\n const entry = unstableSelector.get(c.name) ?? { count: 0, source: c.source };\n entry.count++;\n unstableSelector.set(c.name, entry);\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 unstableSelectors = [...unstableSelector.entries()]\n .map(([name, v]) => ({ name, source: v.source, count: v.count, share: v.count / denominator }))\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 topSelector = unstableSelectors[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 const remounts = lifecycle?.remounts ?? 0;\n\n /*\n * Remounts outrank every render explanation. A component being rebuilt is\n * throwing away its DOM and its state, which costs more than any number of\n * re-renders — so if that is happening, it is the headline.\n */\n if (remounts >= 2) {\n const mountEvent = [...mine].reverse().find((e) => e.phase === \"mount\");\n headline = `${component} was rebuilt ${remounts}× rather than re-rendered — its DOM and state were discarded each time.`;\n nextStep =\n mountEvent?.diagnosis.suggestion ??\n `Check whether ${component} is declared inside another component's render body, or receives a changing \\`key\\`.`;\n confidence = mountEvent?.diagnosis.confidence ?? \"medium\";\n } else if (topSelector && topSelector.share >= 0.5) {\n /*\n * Ranked above props: a selector rebuilding its value re-renders the\n * component on *every* store update, so it is usually the larger cause.\n */\n headline =\n `${pct(topSelector.share)} of updates followed the \\`${topSelector.name}\\` selector returning a new reference ` +\n `with identical contents — so this re-renders on every store update, not only when its data changes.`;\n nextStep =\n `Make \\`${topSelector.name}\\`${topSelector.source ? ` at ${topSelector.source}` : \"\"} return a stable value: ` +\n `select the raw slice and derive outside the selector, memoize with createSelector, or pass shallowEqual.`;\n confidence = \"high\";\n } else 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 === \"store\") {\n headline = `${pct(top.share)} of updates came from tracked store selectors whose values genuinely changed.`;\n nextStep =\n \"These are real data changes. Look at what each render costs rather than how many there are, \" +\n \"or select less per component so fewer components wake on each store update.\";\n confidence = \"high\";\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 unstableSelectors,\n potentiallyAvoidableRenders: avoidable,\n estimatedAvoidableTime: avoidableTime,\n devReplays: replays,\n remounts,\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.unstableSelectors.length > 0) {\n lines.push(\"\", \"Selectors returning a new reference each call (share of updates)\");\n for (const sel of e.unstableSelectors) {\n lines.push(` ${sel.name.padEnd(24)} ${String(sel.count).padStart(5)} ${pct(sel.share)}${sel.source ? ` ${sel.source}` : \"\"}`);\n }\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 if (e.remounts > 0) {\n lines.push(\"\", `Rebuilt ${e.remounts}× (unmounted and mounted again — state and DOM discarded)`);\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"]}
|
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
var DEFAULT_IMPORT_SOURCE = "react-render-detective";
|
|
5
5
|
var SELF_PATTERN = /[/\\]react-render-detective[/\\](src|dist)[/\\]/;
|
|
6
6
|
var HOC_NAMES = /* @__PURE__ */ new Set(["memo", "forwardRef"]);
|
|
7
|
+
var DEFAULT_STORE_HOOKS = {
|
|
8
|
+
"react-redux": ["useSelector"]
|
|
9
|
+
};
|
|
7
10
|
function renderDetectiveBabelPlugin(api) {
|
|
8
11
|
const t = api.types;
|
|
9
12
|
const processed = /* @__PURE__ */ new WeakSet();
|
|
@@ -49,6 +52,33 @@ function renderDetectiveBabelPlugin(api) {
|
|
|
49
52
|
state.rrdLocal = local;
|
|
50
53
|
return local;
|
|
51
54
|
}
|
|
55
|
+
function ensureSelectorImport(path, state) {
|
|
56
|
+
if (state.rrdTrackSelectorLocal) return state.rrdTrackSelectorLocal;
|
|
57
|
+
const program = path.findParent((p) => p.isProgram());
|
|
58
|
+
const local = program.scope.generateUidIdentifier("rrdTrackSelector");
|
|
59
|
+
const source = state.opts.importSource ?? DEFAULT_IMPORT_SOURCE;
|
|
60
|
+
program.unshiftContainer(
|
|
61
|
+
"body",
|
|
62
|
+
t.importDeclaration([t.importSpecifier(local, t.identifier("trackSelector"))], t.stringLiteral(source))
|
|
63
|
+
);
|
|
64
|
+
state.rrdTrackSelectorLocal = local;
|
|
65
|
+
return local;
|
|
66
|
+
}
|
|
67
|
+
function labelForSelector(argument) {
|
|
68
|
+
if (!argument) return void 0;
|
|
69
|
+
if (!t.isArrowFunctionExpression(argument) && !t.isFunctionExpression(argument)) return void 0;
|
|
70
|
+
const body = t.isBlockStatement(argument.body) ? void 0 : argument.body;
|
|
71
|
+
if (!body || !t.isMemberExpression(body)) return void 0;
|
|
72
|
+
const parts = [];
|
|
73
|
+
let current = body;
|
|
74
|
+
while (t.isMemberExpression(current)) {
|
|
75
|
+
if (t.isIdentifier(current.property)) parts.unshift(current.property.name);
|
|
76
|
+
else if (t.isStringLiteral(current.property)) parts.unshift(current.property.value);
|
|
77
|
+
else return void 0;
|
|
78
|
+
current = current.object;
|
|
79
|
+
}
|
|
80
|
+
return t.isIdentifier(current) && parts.length > 0 ? parts.join(".") : void 0;
|
|
81
|
+
}
|
|
52
82
|
function locationOf(node, state) {
|
|
53
83
|
const line = node.loc?.start.line;
|
|
54
84
|
if (line === void 0 || !state.rrdRelativePath) return void 0;
|
|
@@ -91,6 +121,43 @@ function renderDetectiveBabelPlugin(api) {
|
|
|
91
121
|
if (skip) path.skip();
|
|
92
122
|
}
|
|
93
123
|
},
|
|
124
|
+
/** Records which local names are bound to a tracked store hook. */
|
|
125
|
+
ImportDeclaration(path, state) {
|
|
126
|
+
if (state.opts.trackStores === false) return;
|
|
127
|
+
const hooks = state.opts.storeHooks ?? DEFAULT_STORE_HOOKS;
|
|
128
|
+
const wanted = hooks[path.node.source.value];
|
|
129
|
+
if (!wanted) return;
|
|
130
|
+
for (const specifier of path.node.specifiers) {
|
|
131
|
+
if (!t.isImportSpecifier(specifier)) continue;
|
|
132
|
+
const imported = t.isIdentifier(specifier.imported) ? specifier.imported.name : specifier.imported.value;
|
|
133
|
+
if (wanted.includes(imported)) (state.rrdStoreHooks ?? (state.rrdStoreHooks = /* @__PURE__ */ new Set())).add(specifier.local.name);
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
/**
|
|
137
|
+
* `useSelector(fn)` → `_rrdTrackSelector(useSelector(fn), { name, source })`
|
|
138
|
+
*
|
|
139
|
+
* The call is wrapped rather than the import replaced, so every argument —
|
|
140
|
+
* including react-redux's equality function — reaches the real hook
|
|
141
|
+
* untouched. Changing those would change the app's behaviour.
|
|
142
|
+
*/
|
|
143
|
+
CallExpression(path, state) {
|
|
144
|
+
if (state.opts.trackStores === false) return;
|
|
145
|
+
if (!state.rrdStoreHooks || state.rrdStoreHooks.size === 0) return;
|
|
146
|
+
if (processed.has(path.node)) return;
|
|
147
|
+
const callee = path.node.callee;
|
|
148
|
+
if (!t.isIdentifier(callee) || !state.rrdStoreHooks.has(callee.name)) return;
|
|
149
|
+
if (!path.getFunctionParent()) return;
|
|
150
|
+
processed.add(path.node);
|
|
151
|
+
const source = locationOf(path.node, state);
|
|
152
|
+
const label = labelForSelector(path.node.arguments[0]);
|
|
153
|
+
const options = [];
|
|
154
|
+
if (label) options.push(t.objectProperty(t.identifier("name"), t.stringLiteral(label)));
|
|
155
|
+
if (source) options.push(t.objectProperty(t.identifier("source"), t.stringLiteral(source)));
|
|
156
|
+
state.rrdTouched = true;
|
|
157
|
+
path.replaceWith(
|
|
158
|
+
t.callExpression(ensureSelectorImport(path, state), [path.node, t.objectExpression(options)])
|
|
159
|
+
);
|
|
160
|
+
},
|
|
94
161
|
/** `function Foo() { return <div /> }` */
|
|
95
162
|
FunctionDeclaration(path, state) {
|
|
96
163
|
const id = path.node.id;
|
|
@@ -165,5 +232,5 @@ function componentNameFromFile(path) {
|
|
|
165
232
|
}
|
|
166
233
|
|
|
167
234
|
exports.renderDetectiveBabelPlugin = renderDetectiveBabelPlugin;
|
|
168
|
-
//# sourceMappingURL=chunk-
|
|
169
|
-
//# sourceMappingURL=chunk-
|
|
235
|
+
//# sourceMappingURL=chunk-EXDRRUUQ.cjs.map
|
|
236
|
+
//# sourceMappingURL=chunk-EXDRRUUQ.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/babel/index.ts"],"names":[],"mappings":"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","file":"chunk-EXDRRUUQ.cjs","sourcesContent":["/**\n * Babel plugin: instrument every React component in a file at build time.\n *\n * Why this exists: without it you only learn about components you already\n * suspected, because you have to wrap them by hand. With it, the tool reports\n * on the whole tree — and it can attach a real source location, which React\n * itself no longer exposes at runtime (`_debugSource` was removed in React 19).\n *\n * Build-time only. It must never run in a production build, and it removes\n * itself when `NODE_ENV` is production unless explicitly forced.\n */\nimport type { NodePath, PluginObject, PluginPass, types as BabelTypes } from \"@babel/core\";\n\nexport interface RenderDetectivePluginOptions {\n /** Defaults to `NODE_ENV !== \"production\"`. */\n enabled?: boolean;\n /** Only instrument files whose path matches. Empty = all files. */\n include?: Array<string | RegExp>;\n /** Skip files whose path matches. Applied after `include`. */\n exclude?: Array<string | RegExp>;\n /**\n * Skip files with no `\"use client\"` directive. Turn this on for the Next.js\n * app router: server components never render on the client, and wrapping one\n * in a hook-using HOC would break the build.\n */\n clientOnly?: boolean;\n /** Import specifier for the runtime. Overridable for testing and monorepos. */\n importSource?: string;\n /** Root used to make source locations relative. Defaults to `process.cwd()`. */\n root?: string;\n /**\n * Attribute external-store renders by wrapping store hooks at their call site.\n * Defaults to `true`. Without it, a Redux-driven render can only be reported\n * as \"state or an external store\".\n */\n trackStores?: boolean;\n /**\n * Which hooks to wrap, as `package` → hook names. Defaults to react-redux's\n * `useSelector`. Add your own store's hook here.\n */\n storeHooks?: Record<string, string[]>;\n}\n\ninterface State extends PluginPass {\n opts: RenderDetectivePluginOptions;\n rrdLocal?: BabelTypes.Identifier;\n rrdTrackSelectorLocal?: BabelTypes.Identifier;\n rrdTouched?: boolean;\n rrdRelativePath?: string;\n /** Local names bound to a tracked store hook in this file. */\n rrdStoreHooks?: Set<string>;\n}\n\nconst DEFAULT_IMPORT_SOURCE = \"react-render-detective\";\n\n/*\n * Never instrument the detective's own runtime. Its HOC returns a component\n * that renders the component it was given; instrumenting that makes it render\n * itself, and the app dies with a stack overflow before the first paint.\n *\n * node_modules covers the normal install. This covers a source alias — how the\n * bundled example consumes the package, and how anyone working on it locally\n * would too.\n */\nconst SELF_PATTERN = /[/\\\\]react-render-detective[/\\\\](src|dist)[/\\\\]/;\nconst HOC_NAMES = new Set([\"memo\", \"forwardRef\"]);\n\nconst DEFAULT_STORE_HOOKS: Record<string, string[]> = {\n \"react-redux\": [\"useSelector\"],\n};\n\nexport default function renderDetectiveBabelPlugin(\n api: { types: typeof BabelTypes; assertVersion?: (v: number | string) => void },\n): PluginObject {\n const t = api.types;\n /*\n * Guards against re-processing a node we just rewrote. Using `path.skip()`\n * for that was a mistake: it skips the entire subtree, so components declared\n * *inside* another component were never instrumented — exactly the case the\n * remount diagnosis exists to catch.\n */\n const processed = new WeakSet<BabelTypes.Node>();\n // Babel 7 and 8 both work: only `types`, `NodePath` and the visitor shape are\n // used, all stable across the major.\n api.assertVersion?.(\"^7.0.0-0 || ^8.0.0-0\");\n\n /**\n * A component is an uppercase-named function that returns JSX.\n *\n * No all-caps exclusion: it was rejecting `const A = () => <i />`, a perfectly\n * ordinary component, to guard against constants that in practice are values\n * rather than functions returning JSX — and the JSX check already covers that.\n */\n const isComponentName = (name: string | undefined | null): boolean => !!name && /^[A-Z]/.test(name);\n\n function returnsJsx(path: NodePath<BabelTypes.Function>): boolean {\n let found = false;\n const body = path.get(\"body\");\n\n // Concise arrow body: `() => <div />` or `() => cond ? <a/> : <b/>`\n if (!Array.isArray(body) && !body.isBlockStatement()) {\n const node = (body as NodePath).node;\n return containsJsx(node);\n }\n\n path.traverse({\n Function(inner) {\n inner.skip(); // JSX in a nested closure belongs to that closure\n },\n ReturnStatement(ret) {\n if (ret.node.argument && containsJsx(ret.node.argument)) found = true;\n },\n });\n return found;\n }\n\n function containsJsx(node: BabelTypes.Node | null | undefined): boolean {\n if (!node) return false;\n if (t.isJSXElement(node) || t.isJSXFragment(node)) return true;\n if (t.isConditionalExpression(node)) return containsJsx(node.consequent) || containsJsx(node.alternate);\n if (t.isLogicalExpression(node)) return containsJsx(node.left) || containsJsx(node.right);\n if (t.isParenthesizedExpression(node)) return containsJsx(node.expression);\n if (t.isCallExpression(node)) {\n // `memo(() => <div />)` and friends\n return node.arguments.some((a) => containsJsx(a as BabelTypes.Node));\n }\n return false;\n }\n\n function ensureImport(path: NodePath, state: State): BabelTypes.Identifier {\n if (state.rrdLocal) return state.rrdLocal;\n const program = path.findParent((p) => p.isProgram()) as NodePath<BabelTypes.Program>;\n const local = program.scope.generateUidIdentifier(\"rrdTrack\");\n const source = state.opts.importSource ?? DEFAULT_IMPORT_SOURCE;\n program.unshiftContainer(\n \"body\",\n t.importDeclaration([t.importSpecifier(local, t.identifier(\"withRenderDetective\"))], t.stringLiteral(source)),\n );\n state.rrdLocal = local;\n return local;\n }\n\n function ensureSelectorImport(path: NodePath, state: State): BabelTypes.Identifier {\n if (state.rrdTrackSelectorLocal) return state.rrdTrackSelectorLocal;\n const program = path.findParent((p) => p.isProgram()) as NodePath<BabelTypes.Program>;\n const local = program.scope.generateUidIdentifier(\"rrdTrackSelector\");\n const source = state.opts.importSource ?? DEFAULT_IMPORT_SOURCE;\n program.unshiftContainer(\n \"body\",\n t.importDeclaration([t.importSpecifier(local, t.identifier(\"trackSelector\"))], t.stringLiteral(source)),\n );\n state.rrdTrackSelectorLocal = local;\n return local;\n }\n\n /**\n * Derives a readable label from the selector expression.\n *\n * `state => state.flights.results` becomes `flights.results`, which is far\n * more useful in a diagnosis than a file and line. Anything less obvious\n * falls back to the call site.\n */\n function labelForSelector(argument: BabelTypes.Node | undefined): string | undefined {\n if (!argument) return undefined;\n if (!t.isArrowFunctionExpression(argument) && !t.isFunctionExpression(argument)) return undefined;\n const body = t.isBlockStatement(argument.body) ? undefined : argument.body;\n if (!body || !t.isMemberExpression(body)) return undefined;\n\n const parts: string[] = [];\n let current: BabelTypes.Node = body;\n while (t.isMemberExpression(current)) {\n if (t.isIdentifier(current.property)) parts.unshift(current.property.name);\n else if (t.isStringLiteral(current.property)) parts.unshift(current.property.value);\n else return undefined;\n current = current.object;\n }\n // Drop the state parameter itself: `state.a.b` → `a.b`.\n return t.isIdentifier(current) && parts.length > 0 ? parts.join(\".\") : undefined;\n }\n\n function locationOf(node: BabelTypes.Node, state: State): string | undefined {\n const line = node.loc?.start.line;\n if (line === undefined || !state.rrdRelativePath) return undefined;\n return `${state.rrdRelativePath}:${line}:${(node.loc?.start.column ?? 0) + 1}`;\n }\n\n /** `__rrdTrack(fn, { name, source })` */\n function wrap(\n fn: BabelTypes.Expression,\n name: string,\n node: BabelTypes.Node,\n path: NodePath,\n state: State,\n ): BabelTypes.CallExpression {\n const properties: BabelTypes.ObjectProperty[] = [\n t.objectProperty(t.identifier(\"name\"), t.stringLiteral(name)),\n ];\n const source = locationOf(node, state);\n if (source) properties.push(t.objectProperty(t.identifier(\"source\"), t.stringLiteral(source)));\n\n /*\n * Whether a component is declared inside another function is a *static*\n * fact, and the compiler is standing right here. The runtime previously\n * guessed at it by timing repeated definitions, which failed the moment a\n * user clicked more than five seconds apart.\n */\n if (path.getFunctionParent()) {\n properties.push(t.objectProperty(t.identifier(\"declaredInRender\"), t.booleanLiteral(true)));\n }\n state.rrdTouched = true;\n return t.callExpression(ensureImport(path, state), [fn, t.objectExpression(properties)]);\n }\n\n /** Already wrapped, by hand or by a previous pass. */\n function isAlreadyWrapped(path: NodePath): boolean {\n const parent = path.parentPath;\n if (!parent?.isCallExpression()) return false;\n const callee = parent.node.callee;\n if (t.isIdentifier(callee) && /rrdTrack|withRenderDetective/.test(callee.name)) return true;\n if (t.isMemberExpression(callee) && t.isIdentifier(callee.property, { name: \"withRenderDetective\" })) return true;\n return false;\n }\n\n return {\n name: \"react-render-detective\",\n\n pre(this: State, file: { opts: { filename?: string | null; root?: string | null } }) {\n const state = this;\n const filename = file.opts.filename ?? \"\";\n const root = state.opts.root ?? file.opts.root ?? process.cwd();\n state.rrdRelativePath = filename.startsWith(root) ? filename.slice(root.length).replace(/^[/\\\\]/, \"\") : filename;\n },\n\n visitor: {\n Program: {\n enter(path: NodePath<BabelTypes.Program>, state: State) {\n const enabled = state.opts.enabled ?? process.env.NODE_ENV !== \"production\";\n const filename = state.file.opts.filename ?? \"\";\n\n const skip =\n !enabled ||\n !filename ||\n /node_modules/.test(filename) ||\n SELF_PATTERN.test(filename) ||\n !matches(filename, state.opts.include, true) ||\n matches(filename, state.opts.exclude, false) ||\n (state.opts.clientOnly === true && !hasUseClient(path.node));\n\n if (skip) path.skip();\n },\n },\n\n /** Records which local names are bound to a tracked store hook. */\n ImportDeclaration(path: NodePath<BabelTypes.ImportDeclaration>, state: State) {\n if (state.opts.trackStores === false) return;\n const hooks = state.opts.storeHooks ?? DEFAULT_STORE_HOOKS;\n const wanted = hooks[path.node.source.value];\n if (!wanted) return;\n for (const specifier of path.node.specifiers) {\n if (!t.isImportSpecifier(specifier)) continue;\n const imported = t.isIdentifier(specifier.imported) ? specifier.imported.name : specifier.imported.value;\n if (wanted.includes(imported)) (state.rrdStoreHooks ??= new Set()).add(specifier.local.name);\n }\n },\n\n /**\n * `useSelector(fn)` → `_rrdTrackSelector(useSelector(fn), { name, source })`\n *\n * The call is wrapped rather than the import replaced, so every argument —\n * including react-redux's equality function — reaches the real hook\n * untouched. Changing those would change the app's behaviour.\n */\n CallExpression(path: NodePath<BabelTypes.CallExpression>, state: State) {\n if (state.opts.trackStores === false) return;\n if (!state.rrdStoreHooks || state.rrdStoreHooks.size === 0) return;\n if (processed.has(path.node)) return;\n\n const callee = path.node.callee;\n if (!t.isIdentifier(callee) || !state.rrdStoreHooks.has(callee.name)) return;\n // Not inside a component or hook body: nothing to attribute it to.\n if (!path.getFunctionParent()) return;\n\n processed.add(path.node);\n const source = locationOf(path.node, state);\n const label = labelForSelector(path.node.arguments[0] as BabelTypes.Node | undefined);\n\n const options: BabelTypes.ObjectProperty[] = [];\n if (label) options.push(t.objectProperty(t.identifier(\"name\"), t.stringLiteral(label)));\n if (source) options.push(t.objectProperty(t.identifier(\"source\"), t.stringLiteral(source)));\n\n state.rrdTouched = true;\n path.replaceWith(\n t.callExpression(ensureSelectorImport(path, state), [path.node, t.objectExpression(options)]),\n );\n },\n\n /** `function Foo() { return <div /> }` */\n FunctionDeclaration(path: NodePath<BabelTypes.FunctionDeclaration>, state: State) {\n const id = path.node.id;\n if (!id || !isComponentName(id.name)) return;\n if (!returnsJsx(path as NodePath<BabelTypes.Function>)) return;\n\n /*\n * Reassign rather than rewrite the declaration: function declarations\n * hoist, and components are routinely used above their definition. A\n * `const` would turn that into a temporal dead zone error at runtime.\n * ESM export bindings are live, so `export function Foo` still exports\n * the wrapper.\n */\n if (processed.has(path.node)) return;\n processed.add(path.node);\n\n const wrapped = wrap(t.cloneNode(id), id.name, path.node, path, state);\n const assignment = t.expressionStatement(t.assignmentExpression(\"=\", t.cloneNode(id), wrapped));\n (assignment as { _rrdGenerated?: boolean })._rrdGenerated = true;\n\n const statementParent = path.getStatementParent();\n if (path.parentPath.isExportDefaultDeclaration() || path.parentPath.isExportNamedDeclaration()) {\n statementParent?.insertAfter(assignment);\n } else {\n path.insertAfter(assignment);\n }\n },\n\n /** `const Foo = () => <div />` and `const Foo = memo(() => <div />)` */\n VariableDeclarator(path: NodePath<BabelTypes.VariableDeclarator>, state: State) {\n const id = path.node.id;\n const init = path.node.init;\n if (!t.isIdentifier(id) || !init || !isComponentName(id.name)) return;\n\n // Unwrap one level of memo()/forwardRef() so the instrumentation sits\n // inside them: memo compares props before our wrapper ever renders.\n let target: NodePath = path.get(\"init\") as NodePath;\n if (target.isCallExpression()) {\n const callee = target.node.callee;\n const isHoc =\n (t.isIdentifier(callee) && HOC_NAMES.has(callee.name)) ||\n (t.isMemberExpression(callee) && t.isIdentifier(callee.property) && HOC_NAMES.has(callee.property.name));\n if (!isHoc) return;\n const first = (target.get(\"arguments\") as NodePath[])[0];\n if (!first) return;\n target = first;\n }\n\n if (!target.isArrowFunctionExpression() && !target.isFunctionExpression()) return;\n if (isAlreadyWrapped(target)) return;\n if (!returnsJsx(target as NodePath<BabelTypes.Function>)) return;\n\n if (processed.has(path.node)) return;\n processed.add(path.node);\n target.replaceWith(wrap(target.node as BabelTypes.Expression, id.name, path.node, path, state));\n },\n\n /** `export default function () { return <div /> }` */\n ExportDefaultDeclaration(path: NodePath<BabelTypes.ExportDefaultDeclaration>, state: State) {\n const declaration = path.get(\"declaration\");\n if (!declaration.isFunctionDeclaration() || declaration.node.id) return; // named ones are handled above\n if (!returnsJsx(declaration as NodePath<BabelTypes.Function>)) return;\n\n const name = componentNameFromFile(state.rrdRelativePath ?? \"Default\");\n const expression = t.functionExpression(\n null,\n declaration.node.params as BabelTypes.Identifier[],\n declaration.node.body,\n declaration.node.generator,\n declaration.node.async,\n );\n if (processed.has(path.node)) return;\n processed.add(path.node);\n path.replaceWith(t.exportDefaultDeclaration(wrap(expression, name, declaration.node, path, state)));\n },\n },\n };\n}\n\nfunction matches(filename: string, patterns: Array<string | RegExp> | undefined, whenEmpty: boolean): boolean {\n if (!patterns || patterns.length === 0) return whenEmpty;\n return patterns.some((p) => (typeof p === \"string\" ? filename.includes(p) : p.test(filename)));\n}\n\nfunction hasUseClient(program: BabelTypes.Program): boolean {\n return program.directives.some((d) => d.value.value === \"use client\");\n}\n\n/** `src/components/UserCard.tsx` → `UserCard` */\nfunction componentNameFromFile(path: string): string {\n const base = path.split(/[/\\\\]/).pop() ?? \"Default\";\n const stem = base.replace(/\\.[jt]sx?$/, \"\");\n const cleaned = stem === \"index\" ? (path.split(/[/\\\\]/).slice(-2)[0] ?? \"Default\") : stem;\n const pascal = cleaned.replace(/(^|[-_.])(\\w)/g, (_, __, c: string) => c.toUpperCase());\n return /^[A-Z]/.test(pascal) ? pascal : `Component${pascal}`;\n}\n"]}
|
|
@@ -357,6 +357,35 @@ function classify(input) {
|
|
|
357
357
|
}
|
|
358
358
|
return make("mount", "high", `${input.componentName} mounted.`, ["First render of this instance."], false);
|
|
359
359
|
}
|
|
360
|
+
if (input.selectorChanges.length > 0) {
|
|
361
|
+
const changes = input.selectorChanges;
|
|
362
|
+
const unstable = changes.filter((c) => c.referenceOnly);
|
|
363
|
+
const names = changes.map((c) => c.name);
|
|
364
|
+
const evidence = changes.map(
|
|
365
|
+
(c) => `\`${c.name}\`${c.source ? ` (${c.source})` : ""}: ${formatInspected(c.previous)} \u2192 ${formatInspected(c.current)}` + (c.referenceOnly ? " \u2014 new reference, identical contents" : "")
|
|
366
|
+
);
|
|
367
|
+
if (unstable.length > 0) {
|
|
368
|
+
const worst = unstable[0];
|
|
369
|
+
evidence.push(
|
|
370
|
+
"useSelector compares with Object.is, so a selector that builds a new value each call re-renders the component on every store update \u2014 not only when its data changes."
|
|
371
|
+
);
|
|
372
|
+
return make(
|
|
373
|
+
"store",
|
|
374
|
+
"high",
|
|
375
|
+
`${input.componentName} rendered because ${unstable.map((c) => `\`${c.name}\``).join(", ")} returned a new reference with identical contents.`,
|
|
376
|
+
evidence,
|
|
377
|
+
true,
|
|
378
|
+
`Make \`${worst.name}\`${worst.source ? ` at ${worst.source}` : ""} return a stable value: select the raw slice and derive outside the selector, memoize it with createSelector, or pass an equality function such as shallowEqual.`
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
return make(
|
|
382
|
+
"store",
|
|
383
|
+
"high",
|
|
384
|
+
`${input.componentName} rendered because ${names.join(", ")} changed in the store.`,
|
|
385
|
+
[...evidence, "The values genuinely changed, so this render is doing real work."],
|
|
386
|
+
false
|
|
387
|
+
);
|
|
388
|
+
}
|
|
360
389
|
if (input.trackedState.length > 0) {
|
|
361
390
|
const names = input.trackedState.map((s) => s.name);
|
|
362
391
|
const evidence = input.trackedState.map(
|
|
@@ -439,7 +468,7 @@ function classify(input) {
|
|
|
439
468
|
[
|
|
440
469
|
"No new props came from above: the wrapper did not re-render.",
|
|
441
470
|
input.selfRenderProven ? "An instrumented child re-rendered from above in this commit, which proves this component produced it." : "No instrumented descendant rendered in this commit, so an uninstrumented descendant could in principle be the origin instead.",
|
|
442
|
-
"React does not expose hook state without private internals, so the exact source is not observable. Use useTrackedState to name
|
|
471
|
+
"React does not expose hook state without private internals, so the exact source is not observable. Use useTrackedState to name local state, and the build plugin's store tracking to name selectors."
|
|
443
472
|
],
|
|
444
473
|
false
|
|
445
474
|
);
|
|
@@ -606,12 +635,14 @@ var RELOAD_WINDOW_MS = 1500;
|
|
|
606
635
|
var INLINE_DEFINITION_WINDOW_MS = 5e3;
|
|
607
636
|
var timestamp = () => typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
|
|
608
637
|
var EMPTY_STATE = [];
|
|
638
|
+
var EMPTY_SELECTORS = [];
|
|
609
639
|
var DURATION_SAMPLE = 200;
|
|
610
640
|
var SWEEP_DELAY_MS = 250;
|
|
611
641
|
var emptyReasons = () => ({
|
|
612
642
|
mount: 0,
|
|
613
643
|
props: 0,
|
|
614
644
|
state: 0,
|
|
645
|
+
store: 0,
|
|
615
646
|
parent: 0,
|
|
616
647
|
context: 0,
|
|
617
648
|
"state-or-external": 0,
|
|
@@ -622,6 +653,9 @@ var Detective = class {
|
|
|
622
653
|
this.config = { ...defaultConfig };
|
|
623
654
|
this.nodes = /* @__PURE__ */ new Map();
|
|
624
655
|
this.listeners = /* @__PURE__ */ new Set();
|
|
656
|
+
/** Registered by optional entry points so they follow clear()/reset() without being imported here. */
|
|
657
|
+
this.clearHooks = /* @__PURE__ */ new Set();
|
|
658
|
+
this.resetHooks = /* @__PURE__ */ new Set();
|
|
625
659
|
this.pending = [];
|
|
626
660
|
this.contextChanges = [];
|
|
627
661
|
/** Orders renders and context updates so they can be matched without timers. */
|
|
@@ -661,6 +695,12 @@ var Detective = class {
|
|
|
661
695
|
get enabled() {
|
|
662
696
|
return this.config.enabled;
|
|
663
697
|
}
|
|
698
|
+
registerClearHook(hook) {
|
|
699
|
+
this.clearHooks.add(hook);
|
|
700
|
+
}
|
|
701
|
+
registerResetHook(hook) {
|
|
702
|
+
this.resetHooks.add(hook);
|
|
703
|
+
}
|
|
664
704
|
subscribe(listener) {
|
|
665
705
|
this.listeners.add(listener);
|
|
666
706
|
return () => {
|
|
@@ -685,6 +725,7 @@ var Detective = class {
|
|
|
685
725
|
sampled,
|
|
686
726
|
attempts: 0,
|
|
687
727
|
pendingState: [],
|
|
728
|
+
pendingSelectors: [],
|
|
688
729
|
seenCommit: false,
|
|
689
730
|
renderNumber: 0,
|
|
690
731
|
lastCommitTime: -1,
|
|
@@ -793,6 +834,9 @@ var Detective = class {
|
|
|
793
834
|
recordStateChange(node, change) {
|
|
794
835
|
if (node.pendingState.length < 16) node.pendingState.push(change);
|
|
795
836
|
}
|
|
837
|
+
recordSelectorChange(node, change) {
|
|
838
|
+
if (node.pendingSelectors.length < 16) node.pendingSelectors.push(change);
|
|
839
|
+
}
|
|
796
840
|
/**
|
|
797
841
|
* Hot path. Called from Profiler#onRender; only enqueues.
|
|
798
842
|
*
|
|
@@ -807,12 +851,14 @@ var Detective = class {
|
|
|
807
851
|
attempts: node.attempts,
|
|
808
852
|
props: node.pendingProps,
|
|
809
853
|
state: node.pendingState.length > 0 ? node.pendingState : EMPTY_STATE,
|
|
854
|
+
selectors: node.pendingSelectors.length > 0 ? node.pendingSelectors : EMPTY_SELECTORS,
|
|
810
855
|
seq: ++this.seq
|
|
811
856
|
});
|
|
812
857
|
node.seenCommit = true;
|
|
813
858
|
node.attempts = 0;
|
|
814
859
|
node.pendingProps = void 0;
|
|
815
860
|
if (node.pendingState.length > 0) node.pendingState = [];
|
|
861
|
+
if (node.pendingSelectors.length > 0) node.pendingSelectors = [];
|
|
816
862
|
node.lastCommitTime = commit.commitTime;
|
|
817
863
|
this.scheduleFlush();
|
|
818
864
|
}
|
|
@@ -924,6 +970,7 @@ var Detective = class {
|
|
|
924
970
|
const selfDuration = Math.max(0, rec.subtreeDuration - accounted);
|
|
925
971
|
const relevantContexts = contexts.filter((c) => c.commitTime === rec.commitTime);
|
|
926
972
|
const trackedState = rec.state;
|
|
973
|
+
const selectorChanges = rec.selectors;
|
|
927
974
|
node.renderNumber++;
|
|
928
975
|
const diagnosis = diagnose(
|
|
929
976
|
{
|
|
@@ -940,6 +987,7 @@ var Detective = class {
|
|
|
940
987
|
attempts: rec.attempts,
|
|
941
988
|
committed: true,
|
|
942
989
|
trackedState,
|
|
990
|
+
selectorChanges,
|
|
943
991
|
remounts: this.lifecycleOf(node.name).remounts,
|
|
944
992
|
inlineDefinitionSuspected: this.inlineDefinitionSuspected(node.name),
|
|
945
993
|
treeReloadSuspected: rec.phase === "mount" && this.reloadSuspectedAt(timestamp()),
|
|
@@ -975,6 +1023,7 @@ var Detective = class {
|
|
|
975
1023
|
selfOriginated: propsReevaluated === false,
|
|
976
1024
|
contextChanges: relevantContexts,
|
|
977
1025
|
trackedState,
|
|
1026
|
+
selectorChanges,
|
|
978
1027
|
committed: true,
|
|
979
1028
|
attempts: Math.max(1, rec.attempts),
|
|
980
1029
|
devReplay: rec.attempts > 1,
|
|
@@ -1049,6 +1098,12 @@ var Detective = class {
|
|
|
1049
1098
|
};
|
|
1050
1099
|
}
|
|
1051
1100
|
clear() {
|
|
1101
|
+
for (const hook of this.clearHooks) {
|
|
1102
|
+
try {
|
|
1103
|
+
hook();
|
|
1104
|
+
} catch {
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1052
1107
|
this.events.clear();
|
|
1053
1108
|
this.pending.length = 0;
|
|
1054
1109
|
this.contextChanges.length = 0;
|
|
@@ -1070,7 +1125,15 @@ var Detective = class {
|
|
|
1070
1125
|
}
|
|
1071
1126
|
/** Full teardown — used by tests and by HMR disposal. */
|
|
1072
1127
|
reset() {
|
|
1128
|
+
for (const hook of this.resetHooks) {
|
|
1129
|
+
try {
|
|
1130
|
+
hook();
|
|
1131
|
+
} catch {
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
this.resetHooks.clear();
|
|
1073
1135
|
this.clear();
|
|
1136
|
+
this.clearHooks.clear();
|
|
1074
1137
|
this.nodes.clear();
|
|
1075
1138
|
this.lifecycles.clear();
|
|
1076
1139
|
this.definitions.clear();
|
|
@@ -1159,5 +1222,5 @@ exports.severityFor = severityFor;
|
|
|
1159
1222
|
exports.shallowEqual = shallowEqual;
|
|
1160
1223
|
exports.shouldInstrument = shouldInstrument;
|
|
1161
1224
|
exports.valueType = valueType;
|
|
1162
|
-
//# sourceMappingURL=chunk-
|
|
1163
|
-
//# sourceMappingURL=chunk-
|
|
1225
|
+
//# sourceMappingURL=chunk-LCGXVRJR.cjs.map
|
|
1226
|
+
//# sourceMappingURL=chunk-LCGXVRJR.cjs.map
|