@buoy-gg/highlight-updates 7.0.20 → 7.0.22
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/lib/commonjs/highlight-updates/utils/CommitProfiler.js +170 -8
- package/lib/commonjs/highlight-updates/utils/HighlightUpdatesController.js +6 -1
- package/lib/commonjs/highlight-updates/utils/devtoolsComponentNames.js +46 -5
- package/lib/commonjs/sync/highlightUpdatesSyncAdapter.js +43 -1
- package/lib/module/highlight-updates/utils/CommitProfiler.js +171 -9
- package/lib/module/highlight-updates/utils/HighlightUpdatesController.js +7 -2
- package/lib/module/highlight-updates/utils/devtoolsComponentNames.js +44 -4
- package/lib/module/sync/highlightUpdatesSyncAdapter.js +43 -1
- package/lib/typescript/highlight-updates/utils/CommitProfiler.d.ts +12 -2
- package/lib/typescript/highlight-updates/utils/CommitProfiler.d.ts.map +1 -1
- package/lib/typescript/highlight-updates/utils/HighlightUpdatesController.d.ts.map +1 -1
- package/lib/typescript/highlight-updates/utils/devtoolsComponentNames.d.ts +34 -0
- package/lib/typescript/highlight-updates/utils/devtoolsComponentNames.d.ts.map +1 -1
- package/lib/typescript/sync/highlightUpdatesSyncAdapter.d.ts +8 -0
- package/lib/typescript/sync/highlightUpdatesSyncAdapter.d.ts.map +1 -1
- package/package.json +7 -7
|
@@ -45,9 +45,11 @@ const TOP_COMPONENTS_CAP = 20;
|
|
|
45
45
|
let hookInstalled = false;
|
|
46
46
|
let active = false;
|
|
47
47
|
let sawDurations = false;
|
|
48
|
+
let captureDetail = false;
|
|
48
49
|
let totalCommits = 0;
|
|
49
50
|
let totalRenders = 0;
|
|
50
51
|
let totalRenderMs = 0;
|
|
52
|
+
let totalCauses = {};
|
|
51
53
|
let buckets = new Map();
|
|
52
54
|
function getHook() {
|
|
53
55
|
if (typeof global === "undefined") return null;
|
|
@@ -119,13 +121,129 @@ function isFrameworkWrapperName(name) {
|
|
|
119
121
|
}
|
|
120
122
|
return false;
|
|
121
123
|
}
|
|
122
|
-
|
|
124
|
+
|
|
125
|
+
/** Bound on prop keys compared per render, so a fat props object can't stall a commit. */
|
|
126
|
+
const MAX_PROPS_COMPARED = 40;
|
|
127
|
+
/** Bound on distinct changed-prop key names remembered per component (detail tier). */
|
|
128
|
+
const MAX_CHANGED_KEYS = 5;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Are two values for the same prop key equivalent despite differing identity?
|
|
132
|
+
*
|
|
133
|
+
* Functions always count as equivalent: a fresh arrow per render is THE
|
|
134
|
+
* unstable-prop bug, and there's no way to compare behaviour anyway. Plain
|
|
135
|
+
* objects and arrays compare one level deep, which catches `style={{...}}`
|
|
136
|
+
* and `data={[...]}`. Anything else falls through to a real value change.
|
|
137
|
+
*/
|
|
138
|
+
function isEquivalentValue(a, b) {
|
|
139
|
+
if (Object.is(a, b)) return true;
|
|
140
|
+
const ta = typeof a;
|
|
141
|
+
if (ta !== typeof b) return false;
|
|
142
|
+
if (ta === "function") return true;
|
|
143
|
+
if (a === null || b === null || ta !== "object") return false;
|
|
144
|
+
const aIsArray = Array.isArray(a);
|
|
145
|
+
if (aIsArray !== Array.isArray(b)) return false;
|
|
146
|
+
if (aIsArray) {
|
|
147
|
+
if (a.length !== b.length) return false;
|
|
148
|
+
for (let i = 0; i < a.length; i++) if (!Object.is(a[i], b[i])) return false;
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
const keysA = Object.keys(a);
|
|
152
|
+
const keysB = Object.keys(b);
|
|
153
|
+
if (keysA.length !== keysB.length) return false;
|
|
154
|
+
for (let i = 0; i < keysA.length; i++) {
|
|
155
|
+
const k = keysA[i];
|
|
156
|
+
if (!Object.prototype.hasOwnProperty.call(b, k)) return false;
|
|
157
|
+
if (!Object.is(a[k], b[k])) return false;
|
|
158
|
+
}
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Why did this fiber render?
|
|
163
|
+
*
|
|
164
|
+
* Simpler than the overlay's RenderCauseDetector, and deliberately so: that
|
|
165
|
+
* one carries WeakMap fallbacks and fiber-swap detection because traceUpdates
|
|
166
|
+
* hands it fibers at an ambiguous moment. Here we're inside onCommitFiberRoot
|
|
167
|
+
* walking the committed tree, so `fiber` is current and `fiber.alternate` is
|
|
168
|
+
* previous by definition. Porting the swap logic would be cargo-cult.
|
|
169
|
+
*/
|
|
170
|
+
function detectCause(fiber, wantKeys) {
|
|
171
|
+
const prev = fiber.alternate;
|
|
172
|
+
if (!prev) return {
|
|
173
|
+
cause: "mount"
|
|
174
|
+
};
|
|
175
|
+
const nextProps = fiber.memoizedProps;
|
|
176
|
+
const prevProps = prev.memoizedProps;
|
|
177
|
+
if (nextProps !== prevProps && nextProps && prevProps) {
|
|
178
|
+
let sawChange = false;
|
|
179
|
+
let sawValueChange = false;
|
|
180
|
+
let changedKeys;
|
|
181
|
+
const keys = Object.keys(nextProps);
|
|
182
|
+
const limit = keys.length < MAX_PROPS_COMPARED ? keys.length : MAX_PROPS_COMPARED;
|
|
183
|
+
for (let i = 0; i < limit; i++) {
|
|
184
|
+
const k = keys[i];
|
|
185
|
+
const a = prevProps[k];
|
|
186
|
+
const b = nextProps[k];
|
|
187
|
+
if (Object.is(a, b)) continue;
|
|
188
|
+
sawChange = true;
|
|
189
|
+
if (wantKeys) {
|
|
190
|
+
if (!changedKeys) changedKeys = [];
|
|
191
|
+
if (changedKeys.length < MAX_CHANGED_KEYS) changedKeys.push(k);
|
|
192
|
+
}
|
|
193
|
+
if (!isEquivalentValue(a, b)) {
|
|
194
|
+
sawValueChange = true;
|
|
195
|
+
if (!wantKeys) break; // cause is settled; only keys need the full pass
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
// A removed key counts as a real change; cheap to detect via key count.
|
|
199
|
+
if (!sawValueChange && Object.keys(prevProps).length !== keys.length) {
|
|
200
|
+
sawChange = true;
|
|
201
|
+
sawValueChange = true;
|
|
202
|
+
}
|
|
203
|
+
if (sawChange) {
|
|
204
|
+
return {
|
|
205
|
+
cause: sawValueChange ? "props" : "propsUnstable",
|
|
206
|
+
changedKeys
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// No prop change of substance. Own state/hooks next.
|
|
212
|
+
if (prev.memoizedState !== fiber.memoizedState) return {
|
|
213
|
+
cause: "hooks"
|
|
214
|
+
};
|
|
215
|
+
return {
|
|
216
|
+
cause: "parent"
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Nearest ancestor that ALSO rendered this commit and is worth naming — i.e.
|
|
222
|
+
* skipping framework wrappers, which are never where a fix goes. Answers "who
|
|
223
|
+
* dragged this along", which is the actionable half of a cascade.
|
|
224
|
+
*/
|
|
225
|
+
function nearestRenderedAncestorName(fiber) {
|
|
226
|
+
for (let p = fiber.return; p != null; p = p.return) {
|
|
227
|
+
if (!isCompositeTag(p.tag)) continue;
|
|
228
|
+
if ((getFlags(p) & PerformedWork) === 0) continue;
|
|
229
|
+
const name = getComponentName(p);
|
|
230
|
+
if (isFrameworkWrapperName(name)) continue;
|
|
231
|
+
return name;
|
|
232
|
+
}
|
|
233
|
+
return undefined;
|
|
234
|
+
}
|
|
235
|
+
function recordRender(fiber, parentName) {
|
|
123
236
|
const name = getComponentName(fiber);
|
|
124
237
|
const selfMs = computeSelfMs(fiber);
|
|
125
238
|
if (selfMs !== null) sawDurations = true;
|
|
126
239
|
const ms = selfMs ?? 0;
|
|
127
240
|
totalRenders += 1;
|
|
128
241
|
totalRenderMs += ms;
|
|
242
|
+
const {
|
|
243
|
+
cause,
|
|
244
|
+
changedKeys
|
|
245
|
+
} = detectCause(fiber, captureDetail);
|
|
246
|
+
totalCauses[cause] = (totalCauses[cause] ?? 0) + 1;
|
|
129
247
|
|
|
130
248
|
// Framework wrappers count toward totals but never surface as a row.
|
|
131
249
|
if (isFrameworkWrapperName(name)) return;
|
|
@@ -136,13 +254,27 @@ function recordRender(fiber) {
|
|
|
136
254
|
bucket = {
|
|
137
255
|
renders: 0,
|
|
138
256
|
totalSelfMs: 0,
|
|
139
|
-
maxSelfMs: 0
|
|
257
|
+
maxSelfMs: 0,
|
|
258
|
+
causes: {}
|
|
140
259
|
};
|
|
141
260
|
buckets.set(name, bucket);
|
|
142
261
|
}
|
|
143
262
|
bucket.renders += 1;
|
|
144
263
|
bucket.totalSelfMs += ms;
|
|
145
264
|
if (ms > bucket.maxSelfMs) bucket.maxSelfMs = ms;
|
|
265
|
+
bucket.causes[cause] = (bucket.causes[cause] ?? 0) + 1;
|
|
266
|
+
if (captureDetail) {
|
|
267
|
+
if (changedKeys) {
|
|
268
|
+
if (!bucket.changedProps) bucket.changedProps = new Set();
|
|
269
|
+
for (let i = 0; i < changedKeys.length; i++) {
|
|
270
|
+
if (bucket.changedProps.size >= MAX_CHANGED_KEYS) break;
|
|
271
|
+
bucket.changedProps.add(changedKeys[i]);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
// First parent wins — a component rendered under several parents is rare,
|
|
275
|
+
// and the first is the one that showed up in the heaviest commit.
|
|
276
|
+
if (parentName && !bucket.parentName) bucket.parentName = parentName;
|
|
277
|
+
}
|
|
146
278
|
}
|
|
147
279
|
|
|
148
280
|
/**
|
|
@@ -161,13 +293,21 @@ function handleCommit(root) {
|
|
|
161
293
|
let skipSubtree = false;
|
|
162
294
|
const composite = isCompositeTag(fiber.tag);
|
|
163
295
|
if (composite) {
|
|
164
|
-
|
|
165
|
-
|
|
296
|
+
const compositeName = getComponentName(fiber);
|
|
297
|
+
// Only Buoy's render-tracking UI gets its SUBTREE pruned — walking it
|
|
298
|
+
// feeds its own renders back in and loops. Everything else that's merely
|
|
299
|
+
// devtools noise is skipped as a NODE and we keep descending: app
|
|
300
|
+
// components live underneath Buoy chrome, and pruning a subtree on a name
|
|
301
|
+
// match is what made a stray "Virtualized" prefix hide every FlatList in
|
|
302
|
+
// every app.
|
|
303
|
+
if ((0, _devtoolsComponentNames.isRenderLoopUnsafeName)(compositeName)) {
|
|
166
304
|
skipSubtree = true;
|
|
305
|
+
} else if ((0, _devtoolsComponentNames.isDevToolsComponentName)(compositeName)) {
|
|
306
|
+
// skip this node, keep walking its children
|
|
167
307
|
} else if ((getFlags(fiber) & PerformedWork) !== 0) {
|
|
168
308
|
// PerformedWork is the only trustworthy "rendered this commit"
|
|
169
309
|
// signal — actualDuration is stale on bailed-out fibers.
|
|
170
|
-
recordRender(fiber);
|
|
310
|
+
recordRender(fiber, captureDetail ? nearestRenderedAncestorName(fiber) : undefined);
|
|
171
311
|
}
|
|
172
312
|
}
|
|
173
313
|
if (!skipSubtree) {
|
|
@@ -230,14 +370,24 @@ function isCapturing() {
|
|
|
230
370
|
return active;
|
|
231
371
|
}
|
|
232
372
|
|
|
233
|
-
/**
|
|
234
|
-
|
|
373
|
+
/**
|
|
374
|
+
* Begin a capture window, discarding any previous unclaimed data.
|
|
375
|
+
*
|
|
376
|
+
* `detail` turns on the allocating tier — changed prop key names and parent
|
|
377
|
+
* component names. Causes themselves are always classified: it costs a shallow
|
|
378
|
+
* prop compare, against a `computeSelfMs` that already walks every direct
|
|
379
|
+
* child on every render, and the wasted-render total is the number most worth
|
|
380
|
+
* seeing without having to know to ask for it.
|
|
381
|
+
*/
|
|
382
|
+
function startCapture(opts) {
|
|
235
383
|
if (!ensureHookInstalled()) return;
|
|
236
384
|
buckets = new Map();
|
|
237
385
|
sawDurations = false;
|
|
386
|
+
captureDetail = opts?.detail === true;
|
|
238
387
|
totalCommits = 0;
|
|
239
388
|
totalRenders = 0;
|
|
240
389
|
totalRenderMs = 0;
|
|
390
|
+
totalCauses = {};
|
|
241
391
|
active = true;
|
|
242
392
|
}
|
|
243
393
|
|
|
@@ -253,19 +403,31 @@ function stopCapture() {
|
|
|
253
403
|
renders: b.renders,
|
|
254
404
|
totalMs: round2(b.totalSelfMs),
|
|
255
405
|
maxMs: round2(b.maxSelfMs),
|
|
256
|
-
avgMs: b.renders > 0 ? round2(b.totalSelfMs / b.renders) : 0
|
|
406
|
+
avgMs: b.renders > 0 ? round2(b.totalSelfMs / b.renders) : 0,
|
|
407
|
+
causes: b.causes,
|
|
408
|
+
...(b.changedProps && b.changedProps.size > 0 ? {
|
|
409
|
+
changedProps: Array.from(b.changedProps)
|
|
410
|
+
} : null),
|
|
411
|
+
...(b.parentName ? {
|
|
412
|
+
parentName: b.parentName
|
|
413
|
+
} : null)
|
|
257
414
|
}));
|
|
258
415
|
const summary = {
|
|
259
416
|
totalCommits,
|
|
260
417
|
totalRenders,
|
|
261
418
|
capturedDurations: sawDurations,
|
|
262
419
|
totalRenderMs: round2(totalRenderMs),
|
|
420
|
+
// Parent cascades and identity-only prop churn are both removable without
|
|
421
|
+
// changing behaviour — together they're the recoverable share.
|
|
422
|
+
wastedRenders: (totalCauses.parent ?? 0) + (totalCauses.propsUnstable ?? 0),
|
|
423
|
+
causes: totalCauses,
|
|
263
424
|
topComponents
|
|
264
425
|
};
|
|
265
426
|
buckets = new Map();
|
|
266
427
|
totalCommits = 0;
|
|
267
428
|
totalRenders = 0;
|
|
268
429
|
totalRenderMs = 0;
|
|
430
|
+
totalCauses = {};
|
|
269
431
|
return summary;
|
|
270
432
|
}
|
|
271
433
|
function round2(n) {
|
|
@@ -555,7 +555,12 @@ function isOurOverlayNode(stateNode) {
|
|
|
555
555
|
|
|
556
556
|
// Only check other dev tools if excludeDevTools is enabled
|
|
557
557
|
if (excludeDevTools) {
|
|
558
|
-
|
|
558
|
+
// Two sets, same meaning: COMPONENTS is the original list, EXACT
|
|
559
|
+
// holds the names that used to be matched by over-broad prefixes
|
|
560
|
+
// (the "Virtualized" that hid every FlatList, the "Dial" that hid
|
|
561
|
+
// every Dialog). Both paths must consult both, or Buoy's own chrome
|
|
562
|
+
// starts flashing highlight boxes in one of them and not the other.
|
|
563
|
+
if (_devtoolsComponentNames.OTHER_DEV_TOOLS_COMPONENTS.has(name) || _devtoolsComponentNames.OTHER_DEV_TOOLS_EXACT.has(name)) {
|
|
559
564
|
result = true;
|
|
560
565
|
break;
|
|
561
566
|
}
|
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.OTHER_DEV_TOOLS_PREFIXES = exports.OTHER_DEV_TOOLS_COMPONENTS = exports.HIGHLIGHT_OVERLAY_PREFIXES = exports.HIGHLIGHT_OVERLAY_COMPONENTS = void 0;
|
|
6
|
+
exports.OTHER_DEV_TOOLS_PREFIXES = exports.OTHER_DEV_TOOLS_EXACT = exports.OTHER_DEV_TOOLS_COMPONENTS = exports.HIGHLIGHT_OVERLAY_PREFIXES = exports.HIGHLIGHT_OVERLAY_COMPONENTS = void 0;
|
|
7
7
|
exports.isCapturingBuoyToolRenders = isCapturingBuoyToolRenders;
|
|
8
8
|
exports.isDevToolsComponentName = isDevToolsComponentName;
|
|
9
9
|
exports.isJsTopComponentName = isJsTopComponentName;
|
|
10
10
|
exports.isPerfMonitorComponentName = isPerfMonitorComponentName;
|
|
11
|
+
exports.isRenderLoopUnsafeName = isRenderLoopUnsafeName;
|
|
11
12
|
exports.setCaptureBuoyToolRenders = setCaptureBuoyToolRenders;
|
|
12
13
|
// ============================================================================
|
|
13
14
|
// Buoy devtools component-name sets, shared by HighlightUpdatesController's
|
|
@@ -66,16 +67,37 @@ const OTHER_DEV_TOOLS_COMPONENTS = exports.OTHER_DEV_TOOLS_COMPONENTS = new Set(
|
|
|
66
67
|
// separately). The modal/table re-render on every 250ms snapshot tick.
|
|
67
68
|
"TaskTable", "TaskRow", "BusySparkline"]);
|
|
68
69
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
/**
|
|
71
|
+
* Other dev-tools prefixes — noise reduction, toggleable via excludeDevTools.
|
|
72
|
+
*
|
|
73
|
+
* EVERY ENTRY HERE MUST BE BUOY-NAMESPACED. These are prefix matches, and a
|
|
74
|
+
* match hides the component from render reports. Generic English words do not
|
|
75
|
+
* belong in this list, because app components share them.
|
|
76
|
+
*
|
|
77
|
+
* This list used to contain `Virtualized`, meant for Buoy's own
|
|
78
|
+
* VirtualizedDataExplorer. React Native's FlatList renders a `VirtualizedList`,
|
|
79
|
+
* so the prefix matched — and because CommitProfiler pruned the whole subtree
|
|
80
|
+
* on a match, Buoy could not see inside ANY FlatList or SectionList in ANY
|
|
81
|
+
* app. Lists are the number-one source of render problems in React Native, and
|
|
82
|
+
* the render tools were blind to all of them. `Dial` was the same trap:
|
|
83
|
+
* "Dialog".startsWith("Dial") is true, so every Dialog vanished too.
|
|
84
|
+
*
|
|
85
|
+
* Names that are generic on their own now live in OTHER_DEV_TOOLS_COMPONENTS
|
|
86
|
+
* as exact matches instead.
|
|
87
|
+
*/
|
|
88
|
+
const OTHER_DEV_TOOLS_PREFIXES = exports.OTHER_DEV_TOOLS_PREFIXES = ["JsModal", "ImageOverlay",
|
|
73
89
|
// Perf-monitor prefixes (safe: user code is Perf*, buoy code is
|
|
74
90
|
// PerfMonitor* / PerfHud*)
|
|
75
91
|
"PerfMonitor", "PerfHud",
|
|
76
92
|
// JS TOP (js-top package)
|
|
77
93
|
"JsTop"];
|
|
78
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Buoy component names that were previously matched by over-broad prefixes.
|
|
97
|
+
* Exact matches only — see the warning above.
|
|
98
|
+
*/
|
|
99
|
+
const OTHER_DEV_TOOLS_EXACT = exports.OTHER_DEV_TOOLS_EXACT = new Set(["VirtualizedDataExplorer", "TreeDiffView", "TreeDiffRow", "DataViewer", "DiffView", "DiffSummary", "FloatingDevTools", "FloatingMenu", "FloatingBubble", "MinimizedDock", "DialMenu", "DialItem", "ExpandableSection", "ChevronIcon", "GlitchText", "SettingsModal", "SettingsRow", "OnboardingFlow", "DevToolsMenu"]);
|
|
100
|
+
|
|
79
101
|
// ── Bench "profile our own tools" mode ──────────────────────────────────
|
|
80
102
|
// When enabled (by a dedicated benchmark screen, e.g. the example app's
|
|
81
103
|
// /tool-bench page), render capture INCLUDES Buoy's tool UI so we can
|
|
@@ -140,8 +162,27 @@ function isDevToolsComponentName(name) {
|
|
|
140
162
|
return isPerfMonitorComponentName(name) || isJsTopComponentName(name);
|
|
141
163
|
}
|
|
142
164
|
if (OTHER_DEV_TOOLS_COMPONENTS.has(name)) return true;
|
|
165
|
+
if (OTHER_DEV_TOOLS_EXACT.has(name)) return true;
|
|
143
166
|
for (let i = 0; i < OTHER_DEV_TOOLS_PREFIXES.length; i++) {
|
|
144
167
|
if (name.startsWith(OTHER_DEV_TOOLS_PREFIXES[i])) return true;
|
|
145
168
|
}
|
|
146
169
|
return false;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Is this name one whose SUBTREE must be skipped, not merely the node?
|
|
174
|
+
*
|
|
175
|
+
* Only the always-excluded group qualifies: walking Buoy's own render-tracking
|
|
176
|
+
* UI feeds its own renders back in and loops. Everything else in
|
|
177
|
+
* `isDevToolsComponentName` is noise reduction — hide the row, but keep
|
|
178
|
+
* walking, because app components live underneath Buoy chrome (a tool modal
|
|
179
|
+
* renders the host app behind it) and pruning on a NAME is how one bad prefix
|
|
180
|
+
* cost us every FlatList in every app.
|
|
181
|
+
*/
|
|
182
|
+
function isRenderLoopUnsafeName(name) {
|
|
183
|
+
if (HIGHLIGHT_OVERLAY_COMPONENTS.has(name)) return true;
|
|
184
|
+
for (let i = 0; i < HIGHLIGHT_OVERLAY_PREFIXES.length; i++) {
|
|
185
|
+
if (name.startsWith(HIGHLIGHT_OVERLAY_PREFIXES[i])) return true;
|
|
186
|
+
}
|
|
187
|
+
return false;
|
|
147
188
|
}
|
|
@@ -7,6 +7,7 @@ exports.highlightUpdatesSyncAdapter = void 0;
|
|
|
7
7
|
var _HighlightUpdatesController = _interopRequireDefault(require("../highlight-updates/utils/HighlightUpdatesController"));
|
|
8
8
|
var _RenderTracker = require("../highlight-updates/utils/RenderTracker");
|
|
9
9
|
var _screenElements = require("../highlight-updates/utils/screenElements");
|
|
10
|
+
var _CommitProfiler = require("../highlight-updates/utils/CommitProfiler");
|
|
10
11
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
11
12
|
function ensureInitialized() {
|
|
12
13
|
if (!_HighlightUpdatesController.default.isInitialized()) {
|
|
@@ -65,6 +66,41 @@ function jsonSafeRender(render) {
|
|
|
65
66
|
}
|
|
66
67
|
}
|
|
67
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Measurement window for automated callers (MCP `measure_renders`).
|
|
71
|
+
*
|
|
72
|
+
* Backed by CommitProfiler, NOT the RenderTracker that feeds the overlay, and
|
|
73
|
+
* the difference is the whole point. RenderTracker tracks HOST VIEWS: ask it
|
|
74
|
+
* why `DealRow` re-rendered and it answers about the native `View` inside it —
|
|
75
|
+
* "props changed: accessibilityState, accessibilityValue" — which is true and
|
|
76
|
+
* useless. CommitProfiler walks COMPOSITE fibers, so it answers about the
|
|
77
|
+
* component you named: which of ITS props changed, whether they changed by
|
|
78
|
+
* value or only by identity, and which parent dragged it along.
|
|
79
|
+
*
|
|
80
|
+
* It also touches none of the user's overlay state — no enabling, no clearing
|
|
81
|
+
* counters someone was collecting, nothing drawn on screen. A measurement is
|
|
82
|
+
* invisible.
|
|
83
|
+
*/
|
|
84
|
+
function beginMeasurement() {
|
|
85
|
+
if (!_CommitProfiler.CommitProfiler.isSupported()) {
|
|
86
|
+
return {
|
|
87
|
+
ok: false,
|
|
88
|
+
reason: "render capture needs a dev build with the React DevTools hook available"
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
_CommitProfiler.CommitProfiler.startCapture({
|
|
92
|
+
detail: true
|
|
93
|
+
});
|
|
94
|
+
return {
|
|
95
|
+
ok: true
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function endMeasurement() {
|
|
99
|
+
return {
|
|
100
|
+
summary: _CommitProfiler.CommitProfiler.stopCapture()
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
68
104
|
/**
|
|
69
105
|
* Sync adapter for the highlight-updates tool, consumed by
|
|
70
106
|
* @buoy-gg/external-sync's `useExternalSync` (structurally matches its
|
|
@@ -81,7 +117,9 @@ function jsonSafeRender(render) {
|
|
|
81
117
|
* stay on the device. Dashboards fetch them via `getRenderDetail`.
|
|
82
118
|
*/
|
|
83
119
|
const highlightUpdatesSyncAdapter = exports.highlightUpdatesSyncAdapter = {
|
|
84
|
-
|
|
120
|
+
// v3: beginMeasurement / endMeasurement — a CommitProfiler capture window
|
|
121
|
+
// for automated callers, with per-component causes. Invisible to the user.
|
|
122
|
+
version: 3,
|
|
85
123
|
getSnapshot: () => ({
|
|
86
124
|
enabled: _HighlightUpdatesController.default.isEnabled(),
|
|
87
125
|
frozen: _HighlightUpdatesController.default.getFrozen(),
|
|
@@ -103,6 +141,10 @@ const highlightUpdatesSyncAdapter = exports.highlightUpdatesSyncAdapter = {
|
|
|
103
141
|
};
|
|
104
142
|
},
|
|
105
143
|
actions: {
|
|
144
|
+
// Capture window for automated callers (MCP `measure_renders`). Pairs:
|
|
145
|
+
// begin -> drive the UI -> end. `end` always restores.
|
|
146
|
+
beginMeasurement: () => beginMeasurement(),
|
|
147
|
+
endMeasurement: () => endMeasurement(),
|
|
106
148
|
/**
|
|
107
149
|
* On-demand detail: one component's real renderHistory + lastRenderCause
|
|
108
150
|
* (including hookChanges). Wire-form snapshots omit those.
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* registry (see the registration in this package's index.tsx).
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
import { isDevToolsComponentName } from "./devtoolsComponentNames";
|
|
21
|
+
import { isDevToolsComponentName, isRenderLoopUnsafeName } from "./devtoolsComponentNames";
|
|
22
22
|
|
|
23
23
|
// React work tags for components that render user code.
|
|
24
24
|
const FunctionComponent = 0;
|
|
@@ -42,9 +42,11 @@ const TOP_COMPONENTS_CAP = 20;
|
|
|
42
42
|
let hookInstalled = false;
|
|
43
43
|
let active = false;
|
|
44
44
|
let sawDurations = false;
|
|
45
|
+
let captureDetail = false;
|
|
45
46
|
let totalCommits = 0;
|
|
46
47
|
let totalRenders = 0;
|
|
47
48
|
let totalRenderMs = 0;
|
|
49
|
+
let totalCauses = {};
|
|
48
50
|
let buckets = new Map();
|
|
49
51
|
function getHook() {
|
|
50
52
|
if (typeof global === "undefined") return null;
|
|
@@ -116,13 +118,129 @@ function isFrameworkWrapperName(name) {
|
|
|
116
118
|
}
|
|
117
119
|
return false;
|
|
118
120
|
}
|
|
119
|
-
|
|
121
|
+
|
|
122
|
+
/** Bound on prop keys compared per render, so a fat props object can't stall a commit. */
|
|
123
|
+
const MAX_PROPS_COMPARED = 40;
|
|
124
|
+
/** Bound on distinct changed-prop key names remembered per component (detail tier). */
|
|
125
|
+
const MAX_CHANGED_KEYS = 5;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Are two values for the same prop key equivalent despite differing identity?
|
|
129
|
+
*
|
|
130
|
+
* Functions always count as equivalent: a fresh arrow per render is THE
|
|
131
|
+
* unstable-prop bug, and there's no way to compare behaviour anyway. Plain
|
|
132
|
+
* objects and arrays compare one level deep, which catches `style={{...}}`
|
|
133
|
+
* and `data={[...]}`. Anything else falls through to a real value change.
|
|
134
|
+
*/
|
|
135
|
+
function isEquivalentValue(a, b) {
|
|
136
|
+
if (Object.is(a, b)) return true;
|
|
137
|
+
const ta = typeof a;
|
|
138
|
+
if (ta !== typeof b) return false;
|
|
139
|
+
if (ta === "function") return true;
|
|
140
|
+
if (a === null || b === null || ta !== "object") return false;
|
|
141
|
+
const aIsArray = Array.isArray(a);
|
|
142
|
+
if (aIsArray !== Array.isArray(b)) return false;
|
|
143
|
+
if (aIsArray) {
|
|
144
|
+
if (a.length !== b.length) return false;
|
|
145
|
+
for (let i = 0; i < a.length; i++) if (!Object.is(a[i], b[i])) return false;
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
const keysA = Object.keys(a);
|
|
149
|
+
const keysB = Object.keys(b);
|
|
150
|
+
if (keysA.length !== keysB.length) return false;
|
|
151
|
+
for (let i = 0; i < keysA.length; i++) {
|
|
152
|
+
const k = keysA[i];
|
|
153
|
+
if (!Object.prototype.hasOwnProperty.call(b, k)) return false;
|
|
154
|
+
if (!Object.is(a[k], b[k])) return false;
|
|
155
|
+
}
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Why did this fiber render?
|
|
160
|
+
*
|
|
161
|
+
* Simpler than the overlay's RenderCauseDetector, and deliberately so: that
|
|
162
|
+
* one carries WeakMap fallbacks and fiber-swap detection because traceUpdates
|
|
163
|
+
* hands it fibers at an ambiguous moment. Here we're inside onCommitFiberRoot
|
|
164
|
+
* walking the committed tree, so `fiber` is current and `fiber.alternate` is
|
|
165
|
+
* previous by definition. Porting the swap logic would be cargo-cult.
|
|
166
|
+
*/
|
|
167
|
+
function detectCause(fiber, wantKeys) {
|
|
168
|
+
const prev = fiber.alternate;
|
|
169
|
+
if (!prev) return {
|
|
170
|
+
cause: "mount"
|
|
171
|
+
};
|
|
172
|
+
const nextProps = fiber.memoizedProps;
|
|
173
|
+
const prevProps = prev.memoizedProps;
|
|
174
|
+
if (nextProps !== prevProps && nextProps && prevProps) {
|
|
175
|
+
let sawChange = false;
|
|
176
|
+
let sawValueChange = false;
|
|
177
|
+
let changedKeys;
|
|
178
|
+
const keys = Object.keys(nextProps);
|
|
179
|
+
const limit = keys.length < MAX_PROPS_COMPARED ? keys.length : MAX_PROPS_COMPARED;
|
|
180
|
+
for (let i = 0; i < limit; i++) {
|
|
181
|
+
const k = keys[i];
|
|
182
|
+
const a = prevProps[k];
|
|
183
|
+
const b = nextProps[k];
|
|
184
|
+
if (Object.is(a, b)) continue;
|
|
185
|
+
sawChange = true;
|
|
186
|
+
if (wantKeys) {
|
|
187
|
+
if (!changedKeys) changedKeys = [];
|
|
188
|
+
if (changedKeys.length < MAX_CHANGED_KEYS) changedKeys.push(k);
|
|
189
|
+
}
|
|
190
|
+
if (!isEquivalentValue(a, b)) {
|
|
191
|
+
sawValueChange = true;
|
|
192
|
+
if (!wantKeys) break; // cause is settled; only keys need the full pass
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// A removed key counts as a real change; cheap to detect via key count.
|
|
196
|
+
if (!sawValueChange && Object.keys(prevProps).length !== keys.length) {
|
|
197
|
+
sawChange = true;
|
|
198
|
+
sawValueChange = true;
|
|
199
|
+
}
|
|
200
|
+
if (sawChange) {
|
|
201
|
+
return {
|
|
202
|
+
cause: sawValueChange ? "props" : "propsUnstable",
|
|
203
|
+
changedKeys
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// No prop change of substance. Own state/hooks next.
|
|
209
|
+
if (prev.memoizedState !== fiber.memoizedState) return {
|
|
210
|
+
cause: "hooks"
|
|
211
|
+
};
|
|
212
|
+
return {
|
|
213
|
+
cause: "parent"
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Nearest ancestor that ALSO rendered this commit and is worth naming — i.e.
|
|
219
|
+
* skipping framework wrappers, which are never where a fix goes. Answers "who
|
|
220
|
+
* dragged this along", which is the actionable half of a cascade.
|
|
221
|
+
*/
|
|
222
|
+
function nearestRenderedAncestorName(fiber) {
|
|
223
|
+
for (let p = fiber.return; p != null; p = p.return) {
|
|
224
|
+
if (!isCompositeTag(p.tag)) continue;
|
|
225
|
+
if ((getFlags(p) & PerformedWork) === 0) continue;
|
|
226
|
+
const name = getComponentName(p);
|
|
227
|
+
if (isFrameworkWrapperName(name)) continue;
|
|
228
|
+
return name;
|
|
229
|
+
}
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
232
|
+
function recordRender(fiber, parentName) {
|
|
120
233
|
const name = getComponentName(fiber);
|
|
121
234
|
const selfMs = computeSelfMs(fiber);
|
|
122
235
|
if (selfMs !== null) sawDurations = true;
|
|
123
236
|
const ms = selfMs ?? 0;
|
|
124
237
|
totalRenders += 1;
|
|
125
238
|
totalRenderMs += ms;
|
|
239
|
+
const {
|
|
240
|
+
cause,
|
|
241
|
+
changedKeys
|
|
242
|
+
} = detectCause(fiber, captureDetail);
|
|
243
|
+
totalCauses[cause] = (totalCauses[cause] ?? 0) + 1;
|
|
126
244
|
|
|
127
245
|
// Framework wrappers count toward totals but never surface as a row.
|
|
128
246
|
if (isFrameworkWrapperName(name)) return;
|
|
@@ -133,13 +251,27 @@ function recordRender(fiber) {
|
|
|
133
251
|
bucket = {
|
|
134
252
|
renders: 0,
|
|
135
253
|
totalSelfMs: 0,
|
|
136
|
-
maxSelfMs: 0
|
|
254
|
+
maxSelfMs: 0,
|
|
255
|
+
causes: {}
|
|
137
256
|
};
|
|
138
257
|
buckets.set(name, bucket);
|
|
139
258
|
}
|
|
140
259
|
bucket.renders += 1;
|
|
141
260
|
bucket.totalSelfMs += ms;
|
|
142
261
|
if (ms > bucket.maxSelfMs) bucket.maxSelfMs = ms;
|
|
262
|
+
bucket.causes[cause] = (bucket.causes[cause] ?? 0) + 1;
|
|
263
|
+
if (captureDetail) {
|
|
264
|
+
if (changedKeys) {
|
|
265
|
+
if (!bucket.changedProps) bucket.changedProps = new Set();
|
|
266
|
+
for (let i = 0; i < changedKeys.length; i++) {
|
|
267
|
+
if (bucket.changedProps.size >= MAX_CHANGED_KEYS) break;
|
|
268
|
+
bucket.changedProps.add(changedKeys[i]);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
// First parent wins — a component rendered under several parents is rare,
|
|
272
|
+
// and the first is the one that showed up in the heaviest commit.
|
|
273
|
+
if (parentName && !bucket.parentName) bucket.parentName = parentName;
|
|
274
|
+
}
|
|
143
275
|
}
|
|
144
276
|
|
|
145
277
|
/**
|
|
@@ -158,13 +290,21 @@ function handleCommit(root) {
|
|
|
158
290
|
let skipSubtree = false;
|
|
159
291
|
const composite = isCompositeTag(fiber.tag);
|
|
160
292
|
if (composite) {
|
|
161
|
-
|
|
162
|
-
|
|
293
|
+
const compositeName = getComponentName(fiber);
|
|
294
|
+
// Only Buoy's render-tracking UI gets its SUBTREE pruned — walking it
|
|
295
|
+
// feeds its own renders back in and loops. Everything else that's merely
|
|
296
|
+
// devtools noise is skipped as a NODE and we keep descending: app
|
|
297
|
+
// components live underneath Buoy chrome, and pruning a subtree on a name
|
|
298
|
+
// match is what made a stray "Virtualized" prefix hide every FlatList in
|
|
299
|
+
// every app.
|
|
300
|
+
if (isRenderLoopUnsafeName(compositeName)) {
|
|
163
301
|
skipSubtree = true;
|
|
302
|
+
} else if (isDevToolsComponentName(compositeName)) {
|
|
303
|
+
// skip this node, keep walking its children
|
|
164
304
|
} else if ((getFlags(fiber) & PerformedWork) !== 0) {
|
|
165
305
|
// PerformedWork is the only trustworthy "rendered this commit"
|
|
166
306
|
// signal — actualDuration is stale on bailed-out fibers.
|
|
167
|
-
recordRender(fiber);
|
|
307
|
+
recordRender(fiber, captureDetail ? nearestRenderedAncestorName(fiber) : undefined);
|
|
168
308
|
}
|
|
169
309
|
}
|
|
170
310
|
if (!skipSubtree) {
|
|
@@ -227,14 +367,24 @@ function isCapturing() {
|
|
|
227
367
|
return active;
|
|
228
368
|
}
|
|
229
369
|
|
|
230
|
-
/**
|
|
231
|
-
|
|
370
|
+
/**
|
|
371
|
+
* Begin a capture window, discarding any previous unclaimed data.
|
|
372
|
+
*
|
|
373
|
+
* `detail` turns on the allocating tier — changed prop key names and parent
|
|
374
|
+
* component names. Causes themselves are always classified: it costs a shallow
|
|
375
|
+
* prop compare, against a `computeSelfMs` that already walks every direct
|
|
376
|
+
* child on every render, and the wasted-render total is the number most worth
|
|
377
|
+
* seeing without having to know to ask for it.
|
|
378
|
+
*/
|
|
379
|
+
function startCapture(opts) {
|
|
232
380
|
if (!ensureHookInstalled()) return;
|
|
233
381
|
buckets = new Map();
|
|
234
382
|
sawDurations = false;
|
|
383
|
+
captureDetail = opts?.detail === true;
|
|
235
384
|
totalCommits = 0;
|
|
236
385
|
totalRenders = 0;
|
|
237
386
|
totalRenderMs = 0;
|
|
387
|
+
totalCauses = {};
|
|
238
388
|
active = true;
|
|
239
389
|
}
|
|
240
390
|
|
|
@@ -250,19 +400,31 @@ function stopCapture() {
|
|
|
250
400
|
renders: b.renders,
|
|
251
401
|
totalMs: round2(b.totalSelfMs),
|
|
252
402
|
maxMs: round2(b.maxSelfMs),
|
|
253
|
-
avgMs: b.renders > 0 ? round2(b.totalSelfMs / b.renders) : 0
|
|
403
|
+
avgMs: b.renders > 0 ? round2(b.totalSelfMs / b.renders) : 0,
|
|
404
|
+
causes: b.causes,
|
|
405
|
+
...(b.changedProps && b.changedProps.size > 0 ? {
|
|
406
|
+
changedProps: Array.from(b.changedProps)
|
|
407
|
+
} : null),
|
|
408
|
+
...(b.parentName ? {
|
|
409
|
+
parentName: b.parentName
|
|
410
|
+
} : null)
|
|
254
411
|
}));
|
|
255
412
|
const summary = {
|
|
256
413
|
totalCommits,
|
|
257
414
|
totalRenders,
|
|
258
415
|
capturedDurations: sawDurations,
|
|
259
416
|
totalRenderMs: round2(totalRenderMs),
|
|
417
|
+
// Parent cascades and identity-only prop churn are both removable without
|
|
418
|
+
// changing behaviour — together they're the recoverable share.
|
|
419
|
+
wastedRenders: (totalCauses.parent ?? 0) + (totalCauses.propsUnstable ?? 0),
|
|
420
|
+
causes: totalCauses,
|
|
260
421
|
topComponents
|
|
261
422
|
};
|
|
262
423
|
buckets = new Map();
|
|
263
424
|
totalCommits = 0;
|
|
264
425
|
totalRenders = 0;
|
|
265
426
|
totalRenderMs = 0;
|
|
427
|
+
totalCauses = {};
|
|
266
428
|
return summary;
|
|
267
429
|
}
|
|
268
430
|
function round2(n) {
|
|
@@ -33,7 +33,7 @@ import { RenderTracker } from "./RenderTracker";
|
|
|
33
33
|
import { PerformanceLogger, markEventReceived } from "./PerformanceLogger";
|
|
34
34
|
import { detectRenderCause, clearRenderCauseState } from "./RenderCauseDetector";
|
|
35
35
|
import { notifySubscriberCountChange } from "@buoy-gg/shared-ui";
|
|
36
|
-
import { HIGHLIGHT_OVERLAY_COMPONENTS, HIGHLIGHT_OVERLAY_PREFIXES, OTHER_DEV_TOOLS_COMPONENTS, OTHER_DEV_TOOLS_PREFIXES, isCapturingBuoyToolRenders, isPerfMonitorComponentName } from "./devtoolsComponentNames";
|
|
36
|
+
import { HIGHLIGHT_OVERLAY_COMPONENTS, HIGHLIGHT_OVERLAY_PREFIXES, OTHER_DEV_TOOLS_COMPONENTS, OTHER_DEV_TOOLS_EXACT, OTHER_DEV_TOOLS_PREFIXES, isCapturingBuoyToolRenders, isPerfMonitorComponentName } from "./devtoolsComponentNames";
|
|
37
37
|
// State
|
|
38
38
|
let globalEnabled = false; // User-controlled toggle for visual highlights
|
|
39
39
|
// When true, tracking + measurement + storage all run as normal, but the
|
|
@@ -551,7 +551,12 @@ function isOurOverlayNode(stateNode) {
|
|
|
551
551
|
|
|
552
552
|
// Only check other dev tools if excludeDevTools is enabled
|
|
553
553
|
if (excludeDevTools) {
|
|
554
|
-
|
|
554
|
+
// Two sets, same meaning: COMPONENTS is the original list, EXACT
|
|
555
|
+
// holds the names that used to be matched by over-broad prefixes
|
|
556
|
+
// (the "Virtualized" that hid every FlatList, the "Dial" that hid
|
|
557
|
+
// every Dialog). Both paths must consult both, or Buoy's own chrome
|
|
558
|
+
// starts flashing highlight boxes in one of them and not the other.
|
|
559
|
+
if (OTHER_DEV_TOOLS_COMPONENTS.has(name) || OTHER_DEV_TOOLS_EXACT.has(name)) {
|
|
555
560
|
result = true;
|
|
556
561
|
break;
|
|
557
562
|
}
|
|
@@ -57,16 +57,37 @@ export const OTHER_DEV_TOOLS_COMPONENTS = new Set([
|
|
|
57
57
|
// separately). The modal/table re-render on every 250ms snapshot tick.
|
|
58
58
|
"TaskTable", "TaskRow", "BusySparkline"]);
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
/**
|
|
61
|
+
* Other dev-tools prefixes — noise reduction, toggleable via excludeDevTools.
|
|
62
|
+
*
|
|
63
|
+
* EVERY ENTRY HERE MUST BE BUOY-NAMESPACED. These are prefix matches, and a
|
|
64
|
+
* match hides the component from render reports. Generic English words do not
|
|
65
|
+
* belong in this list, because app components share them.
|
|
66
|
+
*
|
|
67
|
+
* This list used to contain `Virtualized`, meant for Buoy's own
|
|
68
|
+
* VirtualizedDataExplorer. React Native's FlatList renders a `VirtualizedList`,
|
|
69
|
+
* so the prefix matched — and because CommitProfiler pruned the whole subtree
|
|
70
|
+
* on a match, Buoy could not see inside ANY FlatList or SectionList in ANY
|
|
71
|
+
* app. Lists are the number-one source of render problems in React Native, and
|
|
72
|
+
* the render tools were blind to all of them. `Dial` was the same trap:
|
|
73
|
+
* "Dialog".startsWith("Dial") is true, so every Dialog vanished too.
|
|
74
|
+
*
|
|
75
|
+
* Names that are generic on their own now live in OTHER_DEV_TOOLS_COMPONENTS
|
|
76
|
+
* as exact matches instead.
|
|
77
|
+
*/
|
|
78
|
+
export const OTHER_DEV_TOOLS_PREFIXES = ["JsModal", "ImageOverlay",
|
|
64
79
|
// Perf-monitor prefixes (safe: user code is Perf*, buoy code is
|
|
65
80
|
// PerfMonitor* / PerfHud*)
|
|
66
81
|
"PerfMonitor", "PerfHud",
|
|
67
82
|
// JS TOP (js-top package)
|
|
68
83
|
"JsTop"];
|
|
69
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Buoy component names that were previously matched by over-broad prefixes.
|
|
87
|
+
* Exact matches only — see the warning above.
|
|
88
|
+
*/
|
|
89
|
+
export const OTHER_DEV_TOOLS_EXACT = new Set(["VirtualizedDataExplorer", "TreeDiffView", "TreeDiffRow", "DataViewer", "DiffView", "DiffSummary", "FloatingDevTools", "FloatingMenu", "FloatingBubble", "MinimizedDock", "DialMenu", "DialItem", "ExpandableSection", "ChevronIcon", "GlitchText", "SettingsModal", "SettingsRow", "OnboardingFlow", "DevToolsMenu"]);
|
|
90
|
+
|
|
70
91
|
// ── Bench "profile our own tools" mode ──────────────────────────────────
|
|
71
92
|
// When enabled (by a dedicated benchmark screen, e.g. the example app's
|
|
72
93
|
// /tool-bench page), render capture INCLUDES Buoy's tool UI so we can
|
|
@@ -131,8 +152,27 @@ export function isDevToolsComponentName(name) {
|
|
|
131
152
|
return isPerfMonitorComponentName(name) || isJsTopComponentName(name);
|
|
132
153
|
}
|
|
133
154
|
if (OTHER_DEV_TOOLS_COMPONENTS.has(name)) return true;
|
|
155
|
+
if (OTHER_DEV_TOOLS_EXACT.has(name)) return true;
|
|
134
156
|
for (let i = 0; i < OTHER_DEV_TOOLS_PREFIXES.length; i++) {
|
|
135
157
|
if (name.startsWith(OTHER_DEV_TOOLS_PREFIXES[i])) return true;
|
|
136
158
|
}
|
|
137
159
|
return false;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Is this name one whose SUBTREE must be skipped, not merely the node?
|
|
164
|
+
*
|
|
165
|
+
* Only the always-excluded group qualifies: walking Buoy's own render-tracking
|
|
166
|
+
* UI feeds its own renders back in and loops. Everything else in
|
|
167
|
+
* `isDevToolsComponentName` is noise reduction — hide the row, but keep
|
|
168
|
+
* walking, because app components live underneath Buoy chrome (a tool modal
|
|
169
|
+
* renders the host app behind it) and pruning on a NAME is how one bad prefix
|
|
170
|
+
* cost us every FlatList in every app.
|
|
171
|
+
*/
|
|
172
|
+
export function isRenderLoopUnsafeName(name) {
|
|
173
|
+
if (HIGHLIGHT_OVERLAY_COMPONENTS.has(name)) return true;
|
|
174
|
+
for (let i = 0; i < HIGHLIGHT_OVERLAY_PREFIXES.length; i++) {
|
|
175
|
+
if (name.startsWith(HIGHLIGHT_OVERLAY_PREFIXES[i])) return true;
|
|
176
|
+
}
|
|
177
|
+
return false;
|
|
138
178
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import HighlightUpdatesController from "../highlight-updates/utils/HighlightUpdatesController";
|
|
4
4
|
import { RenderTracker } from "../highlight-updates/utils/RenderTracker";
|
|
5
5
|
import { describeScreen, tapElement } from "../highlight-updates/utils/screenElements";
|
|
6
|
+
import { CommitProfiler } from "../highlight-updates/utils/CommitProfiler";
|
|
6
7
|
function ensureInitialized() {
|
|
7
8
|
if (!HighlightUpdatesController.isInitialized()) {
|
|
8
9
|
HighlightUpdatesController.initialize();
|
|
@@ -60,6 +61,41 @@ function jsonSafeRender(render) {
|
|
|
60
61
|
}
|
|
61
62
|
}
|
|
62
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Measurement window for automated callers (MCP `measure_renders`).
|
|
66
|
+
*
|
|
67
|
+
* Backed by CommitProfiler, NOT the RenderTracker that feeds the overlay, and
|
|
68
|
+
* the difference is the whole point. RenderTracker tracks HOST VIEWS: ask it
|
|
69
|
+
* why `DealRow` re-rendered and it answers about the native `View` inside it —
|
|
70
|
+
* "props changed: accessibilityState, accessibilityValue" — which is true and
|
|
71
|
+
* useless. CommitProfiler walks COMPOSITE fibers, so it answers about the
|
|
72
|
+
* component you named: which of ITS props changed, whether they changed by
|
|
73
|
+
* value or only by identity, and which parent dragged it along.
|
|
74
|
+
*
|
|
75
|
+
* It also touches none of the user's overlay state — no enabling, no clearing
|
|
76
|
+
* counters someone was collecting, nothing drawn on screen. A measurement is
|
|
77
|
+
* invisible.
|
|
78
|
+
*/
|
|
79
|
+
function beginMeasurement() {
|
|
80
|
+
if (!CommitProfiler.isSupported()) {
|
|
81
|
+
return {
|
|
82
|
+
ok: false,
|
|
83
|
+
reason: "render capture needs a dev build with the React DevTools hook available"
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
CommitProfiler.startCapture({
|
|
87
|
+
detail: true
|
|
88
|
+
});
|
|
89
|
+
return {
|
|
90
|
+
ok: true
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function endMeasurement() {
|
|
94
|
+
return {
|
|
95
|
+
summary: CommitProfiler.stopCapture()
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
63
99
|
/**
|
|
64
100
|
* Sync adapter for the highlight-updates tool, consumed by
|
|
65
101
|
* @buoy-gg/external-sync's `useExternalSync` (structurally matches its
|
|
@@ -76,7 +112,9 @@ function jsonSafeRender(render) {
|
|
|
76
112
|
* stay on the device. Dashboards fetch them via `getRenderDetail`.
|
|
77
113
|
*/
|
|
78
114
|
export const highlightUpdatesSyncAdapter = {
|
|
79
|
-
|
|
115
|
+
// v3: beginMeasurement / endMeasurement — a CommitProfiler capture window
|
|
116
|
+
// for automated callers, with per-component causes. Invisible to the user.
|
|
117
|
+
version: 3,
|
|
80
118
|
getSnapshot: () => ({
|
|
81
119
|
enabled: HighlightUpdatesController.isEnabled(),
|
|
82
120
|
frozen: HighlightUpdatesController.getFrozen(),
|
|
@@ -98,6 +136,10 @@ export const highlightUpdatesSyncAdapter = {
|
|
|
98
136
|
};
|
|
99
137
|
},
|
|
100
138
|
actions: {
|
|
139
|
+
// Capture window for automated callers (MCP `measure_renders`). Pairs:
|
|
140
|
+
// begin -> drive the UI -> end. `end` always restores.
|
|
141
|
+
beginMeasurement: () => beginMeasurement(),
|
|
142
|
+
endMeasurement: () => endMeasurement(),
|
|
101
143
|
/**
|
|
102
144
|
* On-demand detail: one component's real renderHistory + lastRenderCause
|
|
103
145
|
* (including hookChanges). Wire-form snapshots omit those.
|
|
@@ -18,8 +18,18 @@
|
|
|
18
18
|
import type { RenderCaptureSummary } from "@buoy-gg/shared-ui";
|
|
19
19
|
declare function isSupported(): boolean;
|
|
20
20
|
declare function isCapturing(): boolean;
|
|
21
|
-
/**
|
|
22
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Begin a capture window, discarding any previous unclaimed data.
|
|
23
|
+
*
|
|
24
|
+
* `detail` turns on the allocating tier — changed prop key names and parent
|
|
25
|
+
* component names. Causes themselves are always classified: it costs a shallow
|
|
26
|
+
* prop compare, against a `computeSelfMs` that already walks every direct
|
|
27
|
+
* child on every render, and the wasted-render total is the number most worth
|
|
28
|
+
* seeing without having to know to ask for it.
|
|
29
|
+
*/
|
|
30
|
+
declare function startCapture(opts?: {
|
|
31
|
+
detail?: boolean;
|
|
32
|
+
}): void;
|
|
23
33
|
/** End the window and return the aggregate (null if never started). */
|
|
24
34
|
declare function stopCapture(): RenderCaptureSummary | null;
|
|
25
35
|
export declare const CommitProfiler: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CommitProfiler.d.ts","sourceRoot":"","sources":["../../../../src/highlight-updates/utils/CommitProfiler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EACV,oBAAoB,
|
|
1
|
+
{"version":3,"file":"CommitProfiler.d.ts","sourceRoot":"","sources":["../../../../src/highlight-updates/utils/CommitProfiler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EACV,oBAAoB,EAGrB,MAAM,oBAAoB,CAAC;AA4a5B,iBAAS,WAAW,IAAI,OAAO,CAI9B;AAED,iBAAS,WAAW,IAAI,OAAO,CAE9B;AAED;;;;;;;;GAQG;AACH,iBAAS,YAAY,CAAC,IAAI,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAUvD;AAED,uEAAuE;AACvE,iBAAS,WAAW,IAAI,oBAAoB,GAAG,IAAI,CA4ClD;AAMD,eAAO,MAAM,cAAc;;;;;CAK1B,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HighlightUpdatesController.d.ts","sourceRoot":"","sources":["../../../../src/highlight-updates/utils/HighlightUpdatesController.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;
|
|
1
|
+
{"version":3,"file":"HighlightUpdatesController.d.ts","sourceRoot":"","sources":["../../../../src/highlight-updates/utils/HighlightUpdatesController.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,30 @@
|
|
|
1
1
|
export declare const HIGHLIGHT_OVERLAY_COMPONENTS: Set<string>;
|
|
2
2
|
export declare const HIGHLIGHT_OVERLAY_PREFIXES: string[];
|
|
3
3
|
export declare const OTHER_DEV_TOOLS_COMPONENTS: Set<string>;
|
|
4
|
+
/**
|
|
5
|
+
* Other dev-tools prefixes — noise reduction, toggleable via excludeDevTools.
|
|
6
|
+
*
|
|
7
|
+
* EVERY ENTRY HERE MUST BE BUOY-NAMESPACED. These are prefix matches, and a
|
|
8
|
+
* match hides the component from render reports. Generic English words do not
|
|
9
|
+
* belong in this list, because app components share them.
|
|
10
|
+
*
|
|
11
|
+
* This list used to contain `Virtualized`, meant for Buoy's own
|
|
12
|
+
* VirtualizedDataExplorer. React Native's FlatList renders a `VirtualizedList`,
|
|
13
|
+
* so the prefix matched — and because CommitProfiler pruned the whole subtree
|
|
14
|
+
* on a match, Buoy could not see inside ANY FlatList or SectionList in ANY
|
|
15
|
+
* app. Lists are the number-one source of render problems in React Native, and
|
|
16
|
+
* the render tools were blind to all of them. `Dial` was the same trap:
|
|
17
|
+
* "Dialog".startsWith("Dial") is true, so every Dialog vanished too.
|
|
18
|
+
*
|
|
19
|
+
* Names that are generic on their own now live in OTHER_DEV_TOOLS_COMPONENTS
|
|
20
|
+
* as exact matches instead.
|
|
21
|
+
*/
|
|
4
22
|
export declare const OTHER_DEV_TOOLS_PREFIXES: string[];
|
|
23
|
+
/**
|
|
24
|
+
* Buoy component names that were previously matched by over-broad prefixes.
|
|
25
|
+
* Exact matches only — see the warning above.
|
|
26
|
+
*/
|
|
27
|
+
export declare const OTHER_DEV_TOOLS_EXACT: Set<string>;
|
|
5
28
|
export declare function setCaptureBuoyToolRenders(enabled: boolean): void;
|
|
6
29
|
export declare function isCapturingBuoyToolRenders(): boolean;
|
|
7
30
|
/**
|
|
@@ -22,4 +45,15 @@ export declare function isJsTopComponentName(name: string): boolean;
|
|
|
22
45
|
* RN's LogBox). Sets are checked first (O(1)), then prefixes.
|
|
23
46
|
*/
|
|
24
47
|
export declare function isDevToolsComponentName(name: string): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Is this name one whose SUBTREE must be skipped, not merely the node?
|
|
50
|
+
*
|
|
51
|
+
* Only the always-excluded group qualifies: walking Buoy's own render-tracking
|
|
52
|
+
* UI feeds its own renders back in and loops. Everything else in
|
|
53
|
+
* `isDevToolsComponentName` is noise reduction — hide the row, but keep
|
|
54
|
+
* walking, because app components live underneath Buoy chrome (a tool modal
|
|
55
|
+
* renders the host app behind it) and pruning on a NAME is how one bad prefix
|
|
56
|
+
* cost us every FlatList in every app.
|
|
57
|
+
*/
|
|
58
|
+
export declare function isRenderLoopUnsafeName(name: string): boolean;
|
|
25
59
|
//# sourceMappingURL=devtoolsComponentNames.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"devtoolsComponentNames.d.ts","sourceRoot":"","sources":["../../../../src/highlight-updates/utils/devtoolsComponentNames.ts"],"names":[],"mappings":"AAcA,eAAO,MAAM,4BAA4B,aA4BvC,CAAC;AAGH,eAAO,MAAM,0BAA0B,UAWtC,CAAC;AAGF,eAAO,MAAM,0BAA0B,aAwIrC,CAAC;
|
|
1
|
+
{"version":3,"file":"devtoolsComponentNames.d.ts","sourceRoot":"","sources":["../../../../src/highlight-updates/utils/devtoolsComponentNames.ts"],"names":[],"mappings":"AAcA,eAAO,MAAM,4BAA4B,aA4BvC,CAAC;AAGH,eAAO,MAAM,0BAA0B,UAWtC,CAAC;AAGF,eAAO,MAAM,0BAA0B,aAwIrC,CAAC;AAEH;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,wBAAwB,UASpC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,qBAAqB,aAoBhC,CAAC;AAcH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAEhE;AAED,wBAAgB,0BAA0B,IAAI,OAAO,CAEpD;AA8BD;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAMhE;AAUD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE1D;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAkB7D;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAM5D"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { TrackedRender } from "../highlight-updates/utils/RenderTracker";
|
|
2
|
+
import type { RenderCaptureSummary } from "@buoy-gg/shared-ui";
|
|
2
3
|
/**
|
|
3
4
|
* Sync adapter for the highlight-updates tool, consumed by
|
|
4
5
|
* @buoy-gg/external-sync's `useExternalSync` (structurally matches its
|
|
@@ -28,6 +29,13 @@ export declare const highlightUpdatesSyncAdapter: {
|
|
|
28
29
|
};
|
|
29
30
|
subscribe: (onChange: () => void) => () => void;
|
|
30
31
|
actions: {
|
|
32
|
+
beginMeasurement: () => {
|
|
33
|
+
ok: boolean;
|
|
34
|
+
reason?: string;
|
|
35
|
+
};
|
|
36
|
+
endMeasurement: () => {
|
|
37
|
+
summary: RenderCaptureSummary | null;
|
|
38
|
+
};
|
|
31
39
|
/**
|
|
32
40
|
* On-demand detail: one component's real renderHistory + lastRenderCause
|
|
33
41
|
* (including hookChanges). Wire-form snapshots omit those.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"highlightUpdatesSyncAdapter.d.ts","sourceRoot":"","sources":["../../../src/sync/highlightUpdatesSyncAdapter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAEV,aAAa,EACd,MAAM,0CAA0C,CAAC;
|
|
1
|
+
{"version":3,"file":"highlightUpdatesSyncAdapter.d.ts","sourceRoot":"","sources":["../../../src/sync/highlightUpdatesSyncAdapter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAEV,aAAa,EACd,MAAM,0CAA0C,CAAC;AAOlD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AA4F/D;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,2BAA2B;;;;;;;;;;;;0BAYhB,MAAM,IAAI;;;gBA3CC,OAAO;qBAAW,MAAM;;;qBAYrB,oBAAoB,GAAG,IAAI;;QAiD7D;;;WAGG;mCACwB,OAAO;;;;;;;;;;;;8BAcZ,OAAO;;;gCAYL,OAAO;mCASJ,OAAO;qCAQL,OAAO;;8BAad,OAAO;;CAEhC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@buoy-gg/highlight-updates",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.22",
|
|
4
4
|
"description": "Render highlighter for React Native — see every render with its cause: mount, state, props, or parent. Part of Buoy devtools.",
|
|
5
5
|
"main": "lib/commonjs/index",
|
|
6
6
|
"module": "lib/module/index",
|
|
@@ -26,14 +26,14 @@
|
|
|
26
26
|
],
|
|
27
27
|
"sideEffects": false,
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@buoy-gg/shared-ui": "^7.0.
|
|
30
|
-
"@buoy-gg/floating-tools-core": "^7.0.
|
|
29
|
+
"@buoy-gg/shared-ui": "^7.0.22",
|
|
30
|
+
"@buoy-gg/floating-tools-core": "^7.0.22"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
|
-
"@buoy-gg/license": "7.0.
|
|
33
|
+
"@buoy-gg/license": "7.0.22",
|
|
34
34
|
"react": "*",
|
|
35
35
|
"react-native": "*",
|
|
36
|
-
"@buoy-gg/core": "^7.0.
|
|
36
|
+
"@buoy-gg/core": "^7.0.22"
|
|
37
37
|
},
|
|
38
38
|
"peerDependenciesMeta": {
|
|
39
39
|
"@buoy-gg/core": {
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
"@types/react": "^19.1.0",
|
|
45
45
|
"@types/react-native": "^0.73.0",
|
|
46
46
|
"typescript": "~5.8.3",
|
|
47
|
-
"@buoy-gg/core": "^7.0.
|
|
48
|
-
"@buoy-gg/license": "^7.0.
|
|
47
|
+
"@buoy-gg/core": "^7.0.22",
|
|
48
|
+
"@buoy-gg/license": "^7.0.22"
|
|
49
49
|
},
|
|
50
50
|
"react-native-builder-bob": {
|
|
51
51
|
"source": "src",
|