@buoy-gg/highlight-updates 2.1.9 → 2.1.11
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/HighlightUpdatesOverlay.js +285 -1
- package/lib/commonjs/highlight-updates/components/HighlightFilterView.js +1371 -1
- package/lib/commonjs/highlight-updates/components/HighlightUpdatesModal.js +591 -1
- package/lib/commonjs/highlight-updates/components/IdentifierBadge.js +267 -1
- package/lib/commonjs/highlight-updates/components/IsolatedRenderList.js +178 -1
- package/lib/commonjs/highlight-updates/components/ModalHeaderContent.js +303 -1
- package/lib/commonjs/highlight-updates/components/RenderCauseBadge.js +500 -1
- package/lib/commonjs/highlight-updates/components/RenderDetailView.js +830 -1
- package/lib/commonjs/highlight-updates/components/RenderHistoryViewer.js +894 -1
- package/lib/commonjs/highlight-updates/components/RenderListItem.js +220 -1
- package/lib/commonjs/highlight-updates/components/StatsDisplay.js +70 -1
- package/lib/commonjs/highlight-updates/components/index.js +97 -1
- package/lib/commonjs/highlight-updates/utils/HighlightUpdatesController.js +1435 -1
- package/lib/commonjs/highlight-updates/utils/PerformanceLogger.js +359 -1
- package/lib/commonjs/highlight-updates/utils/ProfilerInterceptor.js +371 -1
- package/lib/commonjs/highlight-updates/utils/RenderCauseDetector.js +1828 -1
- package/lib/commonjs/highlight-updates/utils/RenderTracker.js +903 -1
- package/lib/commonjs/highlight-updates/utils/ViewTypeMapper.js +264 -1
- package/lib/commonjs/highlight-updates/utils/renderExportFormatter.js +58 -1
- package/lib/commonjs/index.js +311 -1
- package/lib/commonjs/preset.js +278 -1
- package/lib/module/highlight-updates/HighlightUpdatesOverlay.js +278 -1
- package/lib/module/highlight-updates/components/HighlightFilterView.js +1365 -1
- package/lib/module/highlight-updates/components/HighlightUpdatesModal.js +585 -1
- package/lib/module/highlight-updates/components/IdentifierBadge.js +259 -1
- package/lib/module/highlight-updates/components/IsolatedRenderList.js +174 -1
- package/lib/module/highlight-updates/components/ModalHeaderContent.js +298 -1
- package/lib/module/highlight-updates/components/RenderCauseBadge.js +491 -1
- package/lib/module/highlight-updates/components/RenderDetailView.js +826 -1
- package/lib/module/highlight-updates/components/RenderHistoryViewer.js +888 -1
- package/lib/module/highlight-updates/components/RenderListItem.js +215 -1
- package/lib/module/highlight-updates/components/StatsDisplay.js +67 -1
- package/lib/module/highlight-updates/components/index.js +16 -1
- package/lib/module/highlight-updates/utils/HighlightUpdatesController.js +1431 -1
- package/lib/module/highlight-updates/utils/PerformanceLogger.js +353 -1
- package/lib/module/highlight-updates/utils/ProfilerInterceptor.js +358 -1
- package/lib/module/highlight-updates/utils/RenderCauseDetector.js +1818 -1
- package/lib/module/highlight-updates/utils/RenderTracker.js +900 -1
- package/lib/module/highlight-updates/utils/ViewTypeMapper.js +255 -1
- package/lib/module/highlight-updates/utils/renderExportFormatter.js +54 -1
- package/lib/module/index.js +71 -1
- package/lib/module/preset.js +272 -1
- package/lib/typescript/highlight-updates/HighlightUpdatesOverlay.d.ts.map +1 -0
- package/lib/typescript/highlight-updates/components/HighlightFilterView.d.ts.map +1 -0
- package/lib/typescript/highlight-updates/components/HighlightUpdatesModal.d.ts.map +1 -0
- package/lib/typescript/highlight-updates/components/IdentifierBadge.d.ts.map +1 -0
- package/lib/typescript/highlight-updates/components/IsolatedRenderList.d.ts.map +1 -0
- package/lib/typescript/highlight-updates/components/ModalHeaderContent.d.ts.map +1 -0
- package/lib/typescript/highlight-updates/components/RenderCauseBadge.d.ts.map +1 -0
- package/lib/typescript/highlight-updates/components/RenderDetailView.d.ts.map +1 -0
- package/lib/typescript/highlight-updates/components/RenderHistoryViewer.d.ts.map +1 -0
- package/lib/typescript/highlight-updates/components/RenderListItem.d.ts.map +1 -0
- package/lib/typescript/highlight-updates/components/StatsDisplay.d.ts.map +1 -0
- package/lib/typescript/highlight-updates/components/index.d.ts.map +1 -0
- package/lib/typescript/highlight-updates/utils/HighlightUpdatesController.d.ts.map +1 -0
- package/lib/typescript/highlight-updates/utils/PerformanceLogger.d.ts.map +1 -0
- package/lib/typescript/highlight-updates/utils/ProfilerInterceptor.d.ts.map +1 -0
- package/lib/typescript/highlight-updates/utils/RenderCauseDetector.d.ts.map +1 -0
- package/lib/typescript/highlight-updates/utils/RenderTracker.d.ts.map +1 -0
- package/lib/typescript/highlight-updates/utils/ViewTypeMapper.d.ts.map +1 -0
- package/lib/typescript/highlight-updates/utils/renderExportFormatter.d.ts.map +1 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/preset.d.ts.map +1 -0
- package/package.json +16 -16
- package/LICENSE +0 -58
|
@@ -1 +1,358 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* ProfilerInterceptor
|
|
3
|
+
*
|
|
4
|
+
* Swizzles React DevTools internals to capture and log exactly what the
|
|
5
|
+
* profiler detects when "Highlight updates when components render" is enabled.
|
|
6
|
+
*
|
|
7
|
+
* This allows us to compare profiler detection with our own implementation
|
|
8
|
+
* to ensure 100% accuracy.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* import { installProfilerInterceptor } from './ProfilerInterceptor';
|
|
12
|
+
* installProfilerInterceptor();
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
"use strict";
|
|
16
|
+
|
|
17
|
+
// Store original functions for cleanup
|
|
18
|
+
let originalHookEmit = null;
|
|
19
|
+
let originalAgentEmit = null;
|
|
20
|
+
let isInstalled = false;
|
|
21
|
+
|
|
22
|
+
// Controls whether logging is active (toggled by enable/disable)
|
|
23
|
+
let loggingEnabled = false;
|
|
24
|
+
|
|
25
|
+
// Callback for our comparison function
|
|
26
|
+
let comparisonCallback = null;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Get a readable description of a stateNode for logging
|
|
30
|
+
*/
|
|
31
|
+
function describeNode(node) {
|
|
32
|
+
if (!node) return {
|
|
33
|
+
type: "null"
|
|
34
|
+
};
|
|
35
|
+
const n = node;
|
|
36
|
+
|
|
37
|
+
// Try to identify the node type and extract useful info
|
|
38
|
+
const info = {};
|
|
39
|
+
|
|
40
|
+
// Check for Fabric canonical structure
|
|
41
|
+
if (n.canonical) {
|
|
42
|
+
info.type = "Fabric";
|
|
43
|
+
const canonical = n.canonical;
|
|
44
|
+
if (canonical.publicInstance) {
|
|
45
|
+
const pub = canonical.publicInstance;
|
|
46
|
+
info.hasMeasure = typeof pub.measure === "function";
|
|
47
|
+
info.nativeTag = pub.__nativeTag ?? pub._nativeTag ?? "unknown";
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// Check for legacy structure with direct measure
|
|
51
|
+
else if (typeof n.measure === "function") {
|
|
52
|
+
info.type = "Legacy";
|
|
53
|
+
info.hasMeasure = true;
|
|
54
|
+
info.nativeTag = n.__nativeTag ?? n._nativeTag ?? n.nativeTag ?? "unknown";
|
|
55
|
+
}
|
|
56
|
+
// Unknown structure
|
|
57
|
+
else {
|
|
58
|
+
info.type = "Unknown";
|
|
59
|
+
info.keys = Object.keys(n).slice(0, 10); // First 10 keys for debugging
|
|
60
|
+
}
|
|
61
|
+
return info;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Swizzle hook.emit to intercept all events including 'traceUpdates'
|
|
66
|
+
*
|
|
67
|
+
* This captures the raw data from the renderer before any processing.
|
|
68
|
+
* The 'traceUpdates' event contains a Set of stateNodes.
|
|
69
|
+
*
|
|
70
|
+
* Reference: backend.js line 13566
|
|
71
|
+
* hook.emit('traceUpdates', traceUpdatesForNodes)
|
|
72
|
+
*/
|
|
73
|
+
function swizzleHookEmit() {
|
|
74
|
+
const hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
|
75
|
+
if (!hook || typeof hook.emit !== "function") {
|
|
76
|
+
// Silent - hook not available is expected in some environments
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (originalHookEmit) {
|
|
80
|
+
// Already swizzled - no need to log
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
originalHookEmit = hook.emit.bind(hook);
|
|
84
|
+
hook.emit = function (event, ...args) {
|
|
85
|
+
// Intercept traceUpdates events - only log when logging is enabled
|
|
86
|
+
if (event === "traceUpdates" && loggingEnabled) {
|
|
87
|
+
const nodes = args[0];
|
|
88
|
+
|
|
89
|
+
// Only log if there are actual nodes (skip 0 node events)
|
|
90
|
+
if (nodes.size > 0) {
|
|
91
|
+
console.log(`[PROFILER] traceUpdates: ${nodes.size} nodes`, Array.from(nodes).map((node, index) => ({
|
|
92
|
+
index,
|
|
93
|
+
...describeNode(node)
|
|
94
|
+
})));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Call comparison callback if set (even for 0 nodes)
|
|
98
|
+
if (comparisonCallback) {
|
|
99
|
+
comparisonCallback(nodes);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Call original emit
|
|
104
|
+
return originalHookEmit(event, ...args);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// Swizzled successfully - no need to log in normal operation
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Swizzle agent.emit to intercept 'drawTraceUpdates' events
|
|
112
|
+
*
|
|
113
|
+
* This captures data after the TraceUpdates module has processed it,
|
|
114
|
+
* including color assignments based on render count.
|
|
115
|
+
*
|
|
116
|
+
* Reference: backend.js line 6380
|
|
117
|
+
* agent.emit('drawTraceUpdates', nodesToDraw)
|
|
118
|
+
*/
|
|
119
|
+
function swizzleAgentEmit() {
|
|
120
|
+
const hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
|
121
|
+
const agent = hook?.reactDevtoolsAgent;
|
|
122
|
+
if (!agent || typeof agent.emit !== "function") {
|
|
123
|
+
// Silent - agent not available is expected when DevTools not connected
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (originalAgentEmit) {
|
|
127
|
+
// Already swizzled - no need to log
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
originalAgentEmit = agent.emit.bind(agent);
|
|
131
|
+
agent.emit = function (event, ...args) {
|
|
132
|
+
// Only log when logging is enabled and there are actual nodes
|
|
133
|
+
if (loggingEnabled && event === "drawTraceUpdates") {
|
|
134
|
+
const nodesToDraw = args[0];
|
|
135
|
+
if (nodesToDraw.length > 0) {
|
|
136
|
+
console.log(`[PROFILER] drawTraceUpdates: ${nodesToDraw.length} nodes`, nodesToDraw.map((item, index) => ({
|
|
137
|
+
index,
|
|
138
|
+
color: item.color,
|
|
139
|
+
...describeNode(item.node)
|
|
140
|
+
})));
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Call original emit
|
|
145
|
+
return originalAgentEmit(event, ...args);
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
// Swizzled successfully - no need to log in normal operation
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Log information about available renderer interfaces.
|
|
153
|
+
* Only call this manually for debugging - not called during normal operation.
|
|
154
|
+
* @internal
|
|
155
|
+
*/
|
|
156
|
+
export function logRendererInterfaces() {
|
|
157
|
+
const hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
|
158
|
+
if (!hook?.rendererInterfaces) {
|
|
159
|
+
console.log("[ProfilerInterceptor] No rendererInterfaces available");
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const info = [];
|
|
163
|
+
hook.rendererInterfaces.forEach((iface, id) => {
|
|
164
|
+
const methods = Object.keys(iface).filter(key => typeof iface[key] === "function");
|
|
165
|
+
info.push({
|
|
166
|
+
id,
|
|
167
|
+
hasSetTraceUpdatesEnabled: typeof iface.setTraceUpdatesEnabled === "function",
|
|
168
|
+
methods: methods.slice(0, 20) // First 20 methods
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
console.log(`[ProfilerInterceptor] Found ${hook.rendererInterfaces.size} renderer interface(s)`, info);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Log hook structure for debugging.
|
|
176
|
+
* Only call this manually for debugging - not called during normal operation.
|
|
177
|
+
* @internal
|
|
178
|
+
*/
|
|
179
|
+
export function logHookStructure() {
|
|
180
|
+
const hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
|
181
|
+
if (!hook) {
|
|
182
|
+
console.log("[ProfilerInterceptor] No hook available");
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const structure = {};
|
|
186
|
+
for (const key in hook) {
|
|
187
|
+
const value = hook[key];
|
|
188
|
+
if (typeof value === "function") {
|
|
189
|
+
structure[key] = "function";
|
|
190
|
+
} else if (value instanceof Map) {
|
|
191
|
+
structure[key] = `Map(${value.size})`;
|
|
192
|
+
} else if (value instanceof Set) {
|
|
193
|
+
structure[key] = `Set(${value.size})`;
|
|
194
|
+
} else if (typeof value === "object" && value !== null) {
|
|
195
|
+
structure[key] = "object";
|
|
196
|
+
} else {
|
|
197
|
+
structure[key] = typeof value;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
console.log("[ProfilerInterceptor] Hook structure:", structure);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Enable trace updates on all renderer interfaces
|
|
205
|
+
*
|
|
206
|
+
* This is what DevTools does when you check "Highlight updates when components render"
|
|
207
|
+
*
|
|
208
|
+
* Reference: backend.js line 15487-15489
|
|
209
|
+
* function setTraceUpdatesEnabled(isEnabled) {
|
|
210
|
+
* traceUpdatesEnabled = isEnabled;
|
|
211
|
+
* }
|
|
212
|
+
*/
|
|
213
|
+
function enableTracingOnAllRenderers() {
|
|
214
|
+
const hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
|
215
|
+
if (!hook?.rendererInterfaces) {
|
|
216
|
+
// Silent - no rendererInterfaces available
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
hook.rendererInterfaces.forEach(iface => {
|
|
220
|
+
if (typeof iface.setTraceUpdatesEnabled === "function") {
|
|
221
|
+
try {
|
|
222
|
+
iface.setTraceUpdatesEnabled(true);
|
|
223
|
+
} catch {
|
|
224
|
+
// Silent - error enabling tracing on this renderer
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Disable trace updates on all renderer interfaces
|
|
232
|
+
*/
|
|
233
|
+
function disableTracingOnAllRenderers() {
|
|
234
|
+
const hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
|
235
|
+
if (!hook?.rendererInterfaces) return;
|
|
236
|
+
hook.rendererInterfaces.forEach(iface => {
|
|
237
|
+
if (typeof iface.setTraceUpdatesEnabled === "function") {
|
|
238
|
+
try {
|
|
239
|
+
iface.setTraceUpdatesEnabled(false);
|
|
240
|
+
} catch {
|
|
241
|
+
// Silent - error disabling tracing on this renderer
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Set a callback to receive profiler nodes for comparison
|
|
249
|
+
*/
|
|
250
|
+
export function setComparisonCallback(callback) {
|
|
251
|
+
comparisonCallback = callback;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Install all profiler interception hooks
|
|
256
|
+
*
|
|
257
|
+
* Call this early in your app initialization to capture all events.
|
|
258
|
+
* Installation is silent - no console logs in normal operation.
|
|
259
|
+
*/
|
|
260
|
+
export function installProfilerInterceptor() {
|
|
261
|
+
if (typeof __DEV__ !== "undefined" && !__DEV__) {
|
|
262
|
+
// Silent in production - nothing to install
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
if (isInstalled) {
|
|
266
|
+
// Already installed - no action needed
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Swizzle emit functions to intercept events (silent operation)
|
|
271
|
+
swizzleHookEmit();
|
|
272
|
+
swizzleAgentEmit();
|
|
273
|
+
|
|
274
|
+
// NOTE: We don't enable tracing here - it will be enabled when
|
|
275
|
+
// enableProfilerLogging() is called (when user toggles on)
|
|
276
|
+
|
|
277
|
+
// Note: We don't need hook.sub since we already intercept via swizzled hook.emit
|
|
278
|
+
|
|
279
|
+
isInstalled = true;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Uninstall profiler interception hooks and restore original functions
|
|
284
|
+
*/
|
|
285
|
+
export function uninstallProfilerInterceptor() {
|
|
286
|
+
if (!isInstalled) return;
|
|
287
|
+
const hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
|
288
|
+
|
|
289
|
+
// Restore hook.emit
|
|
290
|
+
if (originalHookEmit && hook) {
|
|
291
|
+
hook.emit = originalHookEmit;
|
|
292
|
+
originalHookEmit = null;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Restore agent.emit
|
|
296
|
+
if (originalAgentEmit && hook?.reactDevtoolsAgent) {
|
|
297
|
+
hook.reactDevtoolsAgent.emit = originalAgentEmit;
|
|
298
|
+
originalAgentEmit = null;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Disable tracing
|
|
302
|
+
disableTracingOnAllRenderers();
|
|
303
|
+
comparisonCallback = null;
|
|
304
|
+
isInstalled = false;
|
|
305
|
+
// Silent uninstall - no logging in normal operation
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Check if interceptor is currently installed
|
|
310
|
+
*/
|
|
311
|
+
export function isInterceptorInstalled() {
|
|
312
|
+
return isInstalled;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Enable profiler logging - starts logging all profiler events
|
|
317
|
+
*
|
|
318
|
+
* NOTE: This only enables LOGGING of events. It does NOT enable tracing.
|
|
319
|
+
* The profiler must be enabled separately (e.g., via Chrome DevTools checkbox)
|
|
320
|
+
* for events to be emitted. This allows us to compare what the real profiler
|
|
321
|
+
* detects without our code interfering.
|
|
322
|
+
*/
|
|
323
|
+
export function enableProfilerLogging() {
|
|
324
|
+
loggingEnabled = true;
|
|
325
|
+
// Don't enable tracing here - let Chrome DevTools control that
|
|
326
|
+
// enableTracingOnAllRenderers();
|
|
327
|
+
// Silent enable - no logging needed
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Disable profiler logging - stops logging profiler events
|
|
332
|
+
* NOTE: Does NOT disable tracing - the profiler continues to work,
|
|
333
|
+
* we just stop logging its events
|
|
334
|
+
*/
|
|
335
|
+
export function disableProfilerLogging() {
|
|
336
|
+
loggingEnabled = false;
|
|
337
|
+
// Don't disable tracing - let the profiler continue to work
|
|
338
|
+
// disableTracingOnAllRenderers();
|
|
339
|
+
// Silent disable - no logging needed
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Check if logging is currently enabled
|
|
344
|
+
*/
|
|
345
|
+
export function isLoggingEnabled() {
|
|
346
|
+
return loggingEnabled;
|
|
347
|
+
}
|
|
348
|
+
export default {
|
|
349
|
+
install: installProfilerInterceptor,
|
|
350
|
+
uninstall: uninstallProfilerInterceptor,
|
|
351
|
+
isInstalled: isInterceptorInstalled,
|
|
352
|
+
setComparisonCallback,
|
|
353
|
+
enableTracing: enableTracingOnAllRenderers,
|
|
354
|
+
disableTracing: disableTracingOnAllRenderers,
|
|
355
|
+
enableLogging: enableProfilerLogging,
|
|
356
|
+
disableLogging: disableProfilerLogging,
|
|
357
|
+
isLoggingEnabled
|
|
358
|
+
};
|