@flotrace/runtime-core 2.2.4 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +94 -0
- package/babel-plugin.d.ts +36 -0
- package/babel-plugin.js +302 -0
- package/dist/chunk-5LSFLPGP.mjs +276 -0
- package/dist/index.d.mts +435 -17
- package/dist/index.d.ts +435 -17
- package/dist/index.js +801 -120
- package/dist/index.mjs +585 -120
- package/dist/jsx-dev-runtime.d.mts +64 -0
- package/dist/jsx-dev-runtime.d.ts +64 -0
- package/dist/jsx-dev-runtime.js +179 -0
- package/dist/jsx-dev-runtime.mjs +46 -0
- package/dist/jsx-runtime.d.mts +1 -0
- package/dist/jsx-runtime.d.ts +1 -0
- package/dist/jsx-runtime.js +34 -0
- package/dist/jsx-runtime.mjs +7 -0
- package/package.json +18 -1
package/dist/index.js
CHANGED
|
@@ -31,13 +31,22 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
DEFAULT_CONFIG: () => DEFAULT_CONFIG,
|
|
34
|
+
FLOTRACE_SOURCE: () => FLOTRACE_SOURCE,
|
|
34
35
|
FloTraceWebSocketClient: () => FloTraceWebSocketClient,
|
|
36
|
+
JSX_RUNTIME_ACTIVE_KEY: () => JSX_RUNTIME_ACTIVE_KEY,
|
|
35
37
|
buildAncestorChain: () => buildAncestorChain,
|
|
38
|
+
clearCallSiteRenders: () => clearCallSiteRenders,
|
|
36
39
|
clearFetchOriginTags: () => clearFetchOriginTags,
|
|
40
|
+
computeCallSiteId: () => computeCallSiteId,
|
|
41
|
+
computeCallSiteMetricsPayload: () => computeCallSiteMetricsPayload,
|
|
42
|
+
describeFiberType: () => describeFiberType,
|
|
43
|
+
detectInlineLiterals: () => detectInlineLiterals,
|
|
37
44
|
detectServerComponent: () => detectServerComponent,
|
|
38
45
|
detectWebFramework: () => detectWebFramework,
|
|
39
46
|
disposeWebSocketClient: () => disposeWebSocketClient,
|
|
40
47
|
findFetchOrigin: () => findFetchOrigin,
|
|
48
|
+
getCallSiteRenderRate: () => getCallSiteRenderRate,
|
|
49
|
+
getCallSiteRenders: () => getCallSiteRenders,
|
|
41
50
|
getChangedKeys: () => getChangedKeys,
|
|
42
51
|
getComponentNameFromFiber: () => getComponentNameFromFiber,
|
|
43
52
|
getCurrentRenderingFiber: () => getCurrentRenderingFiber,
|
|
@@ -60,9 +69,16 @@ __export(index_exports, {
|
|
|
60
69
|
installTanStackQueryTracker: () => installTanStackQueryTracker,
|
|
61
70
|
installTimelineTracker: () => installTimelineTracker,
|
|
62
71
|
installZustandTracker: () => installZustandTracker,
|
|
72
|
+
isJsxRuntimeActive: () => isJsxRuntimeActive,
|
|
63
73
|
isReduxStore: () => isReduxStore,
|
|
64
74
|
isTanStackQueryClient: () => isTanStackQueryClient,
|
|
75
|
+
isUserComponent: () => isUserComponent,
|
|
76
|
+
logTreeSnapshot: () => logTreeSnapshot,
|
|
77
|
+
logTreeSummary: () => logTreeSummary,
|
|
78
|
+
markJsxRuntimeActive: () => markJsxRuntimeActive,
|
|
65
79
|
maybeEmitNextjsContext: () => maybeEmitNextjsContext,
|
|
80
|
+
normalizeJsxSourcePath: () => normalizeJsxSourcePath,
|
|
81
|
+
recordCallSiteRender: () => recordCallSiteRender,
|
|
66
82
|
recordTimelineEvent: () => recordTimelineEvent,
|
|
67
83
|
requestFullSnapshot: () => requestFullSnapshot,
|
|
68
84
|
requestTreeSnapshot: () => requestTreeSnapshot,
|
|
@@ -70,6 +86,8 @@ __export(index_exports, {
|
|
|
70
86
|
resolveValueTrace: () => resolveValueTrace,
|
|
71
87
|
serializeProps: () => serializeProps,
|
|
72
88
|
serializeValue: () => serializeValue,
|
|
89
|
+
setDuplicateKeyEmitter: () => setDuplicateKeyEmitter,
|
|
90
|
+
setFiberDebug: () => setFiberDebug,
|
|
73
91
|
tagFetchData: () => tagFetchData,
|
|
74
92
|
uninstallFiberTreeWalker: () => uninstallFiberTreeWalker,
|
|
75
93
|
uninstallReduxTracker: () => uninstallReduxTracker,
|
|
@@ -103,6 +121,223 @@ var DEFAULT_CONFIG = {
|
|
|
103
121
|
getAppUrl: void 0
|
|
104
122
|
};
|
|
105
123
|
|
|
124
|
+
// src/jsxRuntimeUtils.ts
|
|
125
|
+
var FLOTRACE_SOURCE = /* @__PURE__ */ Symbol.for("flotrace.source");
|
|
126
|
+
var JSX_RUNTIME_ACTIVE_KEY = /* @__PURE__ */ Symbol.for("flotrace.jsx-runtime-active");
|
|
127
|
+
function normalizeJsxSourcePath(fileName) {
|
|
128
|
+
let p = fileName;
|
|
129
|
+
if (p.startsWith("file://")) p = p.slice("file://".length);
|
|
130
|
+
if (p.startsWith("webpack-internal:///./")) p = p.slice("webpack-internal:///./".length);
|
|
131
|
+
if (p.startsWith("[project]/")) p = p.slice("[project]/".length);
|
|
132
|
+
if (p.startsWith("./")) p = p.slice(2);
|
|
133
|
+
if (/^\/[a-zA-Z]:[\\/]/.test(p)) p = p.slice(1);
|
|
134
|
+
if (/^[a-zA-Z]:[\\/]/.test(p)) p = p[0].toLowerCase() + p.slice(1);
|
|
135
|
+
return p;
|
|
136
|
+
}
|
|
137
|
+
function normalizeStackFramePath(rawPath) {
|
|
138
|
+
let p = rawPath;
|
|
139
|
+
const queryIdx = p.indexOf("?");
|
|
140
|
+
if (queryIdx !== -1) p = p.slice(0, queryIdx);
|
|
141
|
+
const httpMatch = p.match(/^https?:\/\/[^/]+(\/.+)$/);
|
|
142
|
+
if (httpMatch) {
|
|
143
|
+
p = httpMatch[1];
|
|
144
|
+
if (p.startsWith("/")) p = p.slice(1);
|
|
145
|
+
}
|
|
146
|
+
return normalizeJsxSourcePath(p);
|
|
147
|
+
}
|
|
148
|
+
function isJsBundlePath(rawPath) {
|
|
149
|
+
if (/\bindex\.bundle\b/.test(rawPath)) return true;
|
|
150
|
+
if (/\.bundle(\?|$|\.js(\?|$))/.test(rawPath)) return true;
|
|
151
|
+
if (/\?platform=(ios|android|web|native)\b/.test(rawPath)) return true;
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
function parseFirstNonReactFrame(stack) {
|
|
155
|
+
const lines = stack.split("\n");
|
|
156
|
+
for (const line of lines) {
|
|
157
|
+
const parened = line.match(/\(([^)]+):(\d+):(\d+)\)/);
|
|
158
|
+
const hermes = line.match(/@([^\s]+):(\d+):(\d+)$/);
|
|
159
|
+
const match = parened ?? hermes;
|
|
160
|
+
if (!match) continue;
|
|
161
|
+
const path = match[1];
|
|
162
|
+
if (path.includes("react-dom")) continue;
|
|
163
|
+
if (path.includes("react-native/Libraries")) continue;
|
|
164
|
+
if (path.includes("/react/cjs/")) continue;
|
|
165
|
+
if (path.includes("/scheduler/")) continue;
|
|
166
|
+
if (isJsBundlePath(path)) continue;
|
|
167
|
+
return {
|
|
168
|
+
fileName: normalizeStackFramePath(path),
|
|
169
|
+
lineNumber: Number(match[2]),
|
|
170
|
+
columnNumber: Number(match[3])
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
function computeCallSiteId(source) {
|
|
176
|
+
const normPath = normalizeJsxSourcePath(source.fileName);
|
|
177
|
+
const key = `${normPath}:${source.lineNumber}:${source.columnNumber}`;
|
|
178
|
+
let hash = 2166136261;
|
|
179
|
+
for (let i = 0; i < key.length; i++) {
|
|
180
|
+
hash ^= key.charCodeAt(i);
|
|
181
|
+
hash = Math.imul(hash, 16777619);
|
|
182
|
+
}
|
|
183
|
+
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
184
|
+
}
|
|
185
|
+
var FLOTRACE_SRC_ATTR = "data-flotrace-src";
|
|
186
|
+
function parseDataFlotraceSrc(value) {
|
|
187
|
+
let obj;
|
|
188
|
+
try {
|
|
189
|
+
obj = JSON.parse(value);
|
|
190
|
+
} catch {
|
|
191
|
+
return void 0;
|
|
192
|
+
}
|
|
193
|
+
if (!obj || typeof obj !== "object") return void 0;
|
|
194
|
+
const o = obj;
|
|
195
|
+
if (typeof o.f !== "string" || typeof o.l !== "number" || typeof o.c !== "number") {
|
|
196
|
+
return void 0;
|
|
197
|
+
}
|
|
198
|
+
const fileName = o.f;
|
|
199
|
+
const lineNumber = o.l;
|
|
200
|
+
const columnNumber = o.c;
|
|
201
|
+
return {
|
|
202
|
+
fileName,
|
|
203
|
+
lineNumber,
|
|
204
|
+
columnNumber,
|
|
205
|
+
callSiteId: computeCallSiteId({ fileName, lineNumber, columnNumber })
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
function readJsxSourceFromFiber(fiber) {
|
|
209
|
+
const props = fiber.memoizedProps;
|
|
210
|
+
if (props) {
|
|
211
|
+
const symRaw = props[FLOTRACE_SOURCE];
|
|
212
|
+
if (symRaw && typeof symRaw === "object") {
|
|
213
|
+
const obj = symRaw;
|
|
214
|
+
if (typeof obj.fileName === "string" && typeof obj.lineNumber === "number" && typeof obj.columnNumber === "number" && typeof obj.callSiteId === "string") {
|
|
215
|
+
return symRaw;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
const strRaw = props[FLOTRACE_SRC_ATTR];
|
|
219
|
+
if (typeof strRaw === "string") {
|
|
220
|
+
const parsed = parseDataFlotraceSrc(strRaw);
|
|
221
|
+
if (parsed) return parsed;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
const type = fiber.type;
|
|
225
|
+
if (type !== null && (typeof type === "function" || typeof type === "object")) {
|
|
226
|
+
const typeAttr = type[FLOTRACE_SRC_ATTR];
|
|
227
|
+
if (typeof typeAttr === "string") {
|
|
228
|
+
const parsed = parseDataFlotraceSrc(typeAttr);
|
|
229
|
+
if (parsed) return parsed;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return void 0;
|
|
233
|
+
}
|
|
234
|
+
function isUserComponent(fiber) {
|
|
235
|
+
const jsxSrc = readJsxSourceFromFiber({
|
|
236
|
+
memoizedProps: fiber.memoizedProps ?? null,
|
|
237
|
+
type: fiber.type
|
|
238
|
+
});
|
|
239
|
+
if (jsxSrc && !jsxSrc.fileName.includes("node_modules")) return true;
|
|
240
|
+
const debugSourcePath = fiber._debugSource?.fileName;
|
|
241
|
+
if (typeof debugSourcePath === "string" && !debugSourcePath.includes("node_modules")) {
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
const stack = fiber._debugStack?.stack;
|
|
245
|
+
if (typeof stack === "string") {
|
|
246
|
+
const frame = parseFirstNonReactFrame(stack);
|
|
247
|
+
if (frame && !frame.fileName.includes("node_modules")) return true;
|
|
248
|
+
}
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
var callSiteRenders = /* @__PURE__ */ new Map();
|
|
252
|
+
var RING_BUFFER_MAX = 60;
|
|
253
|
+
function recordCallSiteRender(callSiteId, now2 = performance.now()) {
|
|
254
|
+
const arr = callSiteRenders.get(callSiteId);
|
|
255
|
+
if (arr === void 0) {
|
|
256
|
+
callSiteRenders.set(callSiteId, [now2]);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
arr.push(now2);
|
|
260
|
+
if (arr.length > RING_BUFFER_MAX) arr.shift();
|
|
261
|
+
}
|
|
262
|
+
function getCallSiteRenders(callSiteId) {
|
|
263
|
+
return callSiteRenders.get(callSiteId) ?? [];
|
|
264
|
+
}
|
|
265
|
+
function getCallSiteRenderRate(callSiteId, windowMs = 5e3, now2 = performance.now()) {
|
|
266
|
+
const arr = callSiteRenders.get(callSiteId);
|
|
267
|
+
if (!arr || arr.length === 0) return 0;
|
|
268
|
+
const cutoff = now2 - windowMs;
|
|
269
|
+
let count = 0;
|
|
270
|
+
for (let i = arr.length - 1; i >= 0; i--) {
|
|
271
|
+
if (arr[i] >= cutoff) count++;
|
|
272
|
+
else break;
|
|
273
|
+
}
|
|
274
|
+
return count / windowMs * 1e3;
|
|
275
|
+
}
|
|
276
|
+
function clearCallSiteRenders() {
|
|
277
|
+
callSiteRenders.clear();
|
|
278
|
+
}
|
|
279
|
+
function computeCallSiteMetricsPayload(windowMs = 5e3, now2 = performance.now()) {
|
|
280
|
+
let out = null;
|
|
281
|
+
const cutoff = now2 - windowMs;
|
|
282
|
+
for (const [callSiteId, arr] of callSiteRenders) {
|
|
283
|
+
if (arr.length === 0) continue;
|
|
284
|
+
let count = 0;
|
|
285
|
+
for (let i = arr.length - 1; i >= 0; i--) {
|
|
286
|
+
if (arr[i] >= cutoff) count++;
|
|
287
|
+
else break;
|
|
288
|
+
}
|
|
289
|
+
if (count > 0) {
|
|
290
|
+
(out ?? (out = {}))[callSiteId] = count / windowMs * 1e3;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return out;
|
|
294
|
+
}
|
|
295
|
+
var duplicateKeyEmitter = null;
|
|
296
|
+
function setDuplicateKeyEmitter(emitter) {
|
|
297
|
+
duplicateKeyEmitter = emitter;
|
|
298
|
+
}
|
|
299
|
+
function markJsxRuntimeActive() {
|
|
300
|
+
globalThis[JSX_RUNTIME_ACTIVE_KEY] = true;
|
|
301
|
+
}
|
|
302
|
+
function isJsxRuntimeActive() {
|
|
303
|
+
return globalThis[JSX_RUNTIME_ACTIVE_KEY] === true;
|
|
304
|
+
}
|
|
305
|
+
var KNOWN_REACT_PROPS = /* @__PURE__ */ new Set(["key", "ref", "children", "className"]);
|
|
306
|
+
var REACT_ELEMENT_TYPEOF_LEGACY = /* @__PURE__ */ Symbol.for("react.element");
|
|
307
|
+
var REACT_ELEMENT_TYPEOF_R19 = /* @__PURE__ */ Symbol.for("react.transitional.element");
|
|
308
|
+
function isReactElement(v) {
|
|
309
|
+
const typeOf = v.$$typeof;
|
|
310
|
+
return typeOf === REACT_ELEMENT_TYPEOF_LEGACY || typeOf === REACT_ELEMENT_TYPEOF_R19;
|
|
311
|
+
}
|
|
312
|
+
function detectInlineLiterals(props) {
|
|
313
|
+
let out;
|
|
314
|
+
for (const k in props) {
|
|
315
|
+
if (KNOWN_REACT_PROPS.has(k)) continue;
|
|
316
|
+
const v = props[k];
|
|
317
|
+
if (typeof v === "function") {
|
|
318
|
+
if (!v.name) {
|
|
319
|
+
(out ?? (out = {}))[k] = "fn";
|
|
320
|
+
}
|
|
321
|
+
} else if (Array.isArray(v)) {
|
|
322
|
+
if (v.length > 0) {
|
|
323
|
+
(out ?? (out = {}))[k] = "arr";
|
|
324
|
+
}
|
|
325
|
+
} else if (v !== null && typeof v === "object" && // Skip elements processed by our own runtime (marker present).
|
|
326
|
+
!(FLOTRACE_SOURCE in v) && // Skip React elements processed by ANY runtime — `$$typeof` is set on
|
|
327
|
+
// every element regardless of which jsx-runtime created it, so this
|
|
328
|
+
// catches mixed-runtime codebases. Without this guard, an inline
|
|
329
|
+
// `<Outer child={<Inner/>} />` would false-positive on `child` when
|
|
330
|
+
// Inner went through a different jsxImportSource.
|
|
331
|
+
!isReactElement(v)) {
|
|
332
|
+
const proto = Object.getPrototypeOf(v);
|
|
333
|
+
if (proto === Object.prototype || proto === null) {
|
|
334
|
+
(out ?? (out = {}))[k] = "obj";
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return out;
|
|
339
|
+
}
|
|
340
|
+
|
|
106
341
|
// src/serializer.ts
|
|
107
342
|
var MAX_DEPTH = 5;
|
|
108
343
|
var MAX_STRING_LENGTH = 500;
|
|
@@ -211,11 +446,7 @@ function serializeValue(value, depth = 0, seen = /* @__PURE__ */ new WeakSet())
|
|
|
211
446
|
for (let i = 0; i < Math.min(keys.length, MAX_OBJECT_KEYS); i++) {
|
|
212
447
|
const key = keys[i];
|
|
213
448
|
try {
|
|
214
|
-
result[key] = serializeValue(
|
|
215
|
-
value[key],
|
|
216
|
-
depth + 1,
|
|
217
|
-
seen
|
|
218
|
-
);
|
|
449
|
+
result[key] = serializeValue(value[key], depth + 1, seen);
|
|
219
450
|
} catch {
|
|
220
451
|
result[key] = { __type: "truncated", originalType: "error" };
|
|
221
452
|
}
|
|
@@ -316,7 +547,12 @@ var _FloTraceWebSocketClient = class _FloTraceWebSocketClient {
|
|
|
316
547
|
frameworkName: this.config.frameworkName,
|
|
317
548
|
frameworkVersion: this.config.frameworkVersion,
|
|
318
549
|
reactNativeVersion: this.config.reactNativeVersion,
|
|
319
|
-
runtimeVersion: this.config.runtimeVersion
|
|
550
|
+
runtimeVersion: this.config.runtimeVersion,
|
|
551
|
+
// P5: JSX runtime adoption signal — read at WS-open time so
|
|
552
|
+
// multiple fibers have already rendered by the moment we report.
|
|
553
|
+
// `isJsxRuntimeActive` reads `globalThis[Symbol.for('flotrace.jsx-runtime-active')]`,
|
|
554
|
+
// which the dev jsx-runtime sets on first jsxDEV call.
|
|
555
|
+
jsxRuntimeActive: isJsxRuntimeActive()
|
|
320
556
|
});
|
|
321
557
|
this.flush();
|
|
322
558
|
};
|
|
@@ -642,26 +878,31 @@ function classifyFromDebugLabel(state, index, effects, effectIdx, debugLabel) {
|
|
|
642
878
|
const ms = state.memoizedState;
|
|
643
879
|
const normalizedLabel = debugLabel.toLowerCase().replace(/\s/g, "");
|
|
644
880
|
const labelMap = {
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
881
|
+
usestate: "useState",
|
|
882
|
+
usereducer: "useReducer",
|
|
883
|
+
useref: "useRef",
|
|
884
|
+
usememo: "useMemo",
|
|
885
|
+
usecallback: "useCallback",
|
|
886
|
+
useeffect: "useEffect",
|
|
887
|
+
uselayouteffect: "useLayoutEffect",
|
|
888
|
+
useinsertioneffect: "useInsertionEffect",
|
|
889
|
+
usecontext: "useContext",
|
|
890
|
+
useimperativehandle: "useImperativeHandle",
|
|
891
|
+
usedebugvalue: "useDebugValue",
|
|
892
|
+
usetransition: "useTransition",
|
|
893
|
+
usedeferredvalue: "useDeferredValue",
|
|
894
|
+
useid: "useId",
|
|
895
|
+
usesyncexternalstore: "useSyncExternalStore",
|
|
896
|
+
useoptimistic: "useOptimistic",
|
|
897
|
+
useformstatus: "useFormStatus"
|
|
662
898
|
};
|
|
663
899
|
const hookType = labelMap[normalizedLabel] ?? "unknown";
|
|
664
|
-
const base = {
|
|
900
|
+
const base = {
|
|
901
|
+
index,
|
|
902
|
+
type: hookType,
|
|
903
|
+
value: serializeValue(ms, 0, /* @__PURE__ */ new WeakSet()),
|
|
904
|
+
debugLabel
|
|
905
|
+
};
|
|
665
906
|
if (hookType === "useEffect" || hookType === "useLayoutEffect" || hookType === "useInsertionEffect") {
|
|
666
907
|
if (effectIdx < effects.length) {
|
|
667
908
|
const effect = effects[effectIdx];
|
|
@@ -1128,7 +1369,13 @@ var ClassComponent = 1;
|
|
|
1128
1369
|
var ForwardRef = 11;
|
|
1129
1370
|
var MemoComponent = 14;
|
|
1130
1371
|
var SimpleMemoComponent = 15;
|
|
1131
|
-
var USER_TAGS = /* @__PURE__ */ new Set([
|
|
1372
|
+
var USER_TAGS = /* @__PURE__ */ new Set([
|
|
1373
|
+
FunctionComponent,
|
|
1374
|
+
ClassComponent,
|
|
1375
|
+
ForwardRef,
|
|
1376
|
+
MemoComponent,
|
|
1377
|
+
SimpleMemoComponent
|
|
1378
|
+
]);
|
|
1132
1379
|
function isMemoizedFiber(fiber) {
|
|
1133
1380
|
return fiber.tag === MemoComponent || fiber.tag === SimpleMemoComponent;
|
|
1134
1381
|
}
|
|
@@ -1212,13 +1459,15 @@ function buildCascadeTree(rootFiber, triggers) {
|
|
|
1212
1459
|
triggerByName.set(t.componentName, t);
|
|
1213
1460
|
}
|
|
1214
1461
|
}
|
|
1215
|
-
const stack = [
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1462
|
+
const stack = [
|
|
1463
|
+
{
|
|
1464
|
+
fiber: rootFiber,
|
|
1465
|
+
depth: 0,
|
|
1466
|
+
parentRerendered: false,
|
|
1467
|
+
parentNode: null,
|
|
1468
|
+
isRoot: true
|
|
1469
|
+
}
|
|
1470
|
+
];
|
|
1222
1471
|
while (stack.length > 0) {
|
|
1223
1472
|
const entry = stack.pop();
|
|
1224
1473
|
const { fiber, depth, parentRerendered, parentNode, isRoot } = entry;
|
|
@@ -1229,7 +1478,13 @@ function buildCascadeTree(rootFiber, triggers) {
|
|
|
1229
1478
|
if (isNewMount && !didRender) {
|
|
1230
1479
|
let child2 = fiber.child;
|
|
1231
1480
|
while (child2) {
|
|
1232
|
-
stack.push({
|
|
1481
|
+
stack.push({
|
|
1482
|
+
fiber: child2,
|
|
1483
|
+
depth: depth + 1,
|
|
1484
|
+
parentRerendered: false,
|
|
1485
|
+
parentNode,
|
|
1486
|
+
isRoot: false
|
|
1487
|
+
});
|
|
1233
1488
|
child2 = child2.sibling;
|
|
1234
1489
|
}
|
|
1235
1490
|
continue;
|
|
@@ -1237,7 +1492,13 @@ function buildCascadeTree(rootFiber, triggers) {
|
|
|
1237
1492
|
if (!USER_TAGS.has(fiber.tag)) {
|
|
1238
1493
|
let child2 = fiber.child;
|
|
1239
1494
|
while (child2) {
|
|
1240
|
-
stack.push({
|
|
1495
|
+
stack.push({
|
|
1496
|
+
fiber: child2,
|
|
1497
|
+
depth: depth + 1,
|
|
1498
|
+
parentRerendered: didRender || parentRerendered,
|
|
1499
|
+
parentNode,
|
|
1500
|
+
isRoot: false
|
|
1501
|
+
});
|
|
1241
1502
|
child2 = child2.sibling;
|
|
1242
1503
|
}
|
|
1243
1504
|
continue;
|
|
@@ -1246,7 +1507,13 @@ function buildCascadeTree(rootFiber, triggers) {
|
|
|
1246
1507
|
if (reason === null) {
|
|
1247
1508
|
let child2 = fiber.child;
|
|
1248
1509
|
while (child2) {
|
|
1249
|
-
stack.push({
|
|
1510
|
+
stack.push({
|
|
1511
|
+
fiber: child2,
|
|
1512
|
+
depth: depth + 1,
|
|
1513
|
+
parentRerendered: false,
|
|
1514
|
+
parentNode,
|
|
1515
|
+
isRoot: false
|
|
1516
|
+
});
|
|
1250
1517
|
child2 = child2.sibling;
|
|
1251
1518
|
}
|
|
1252
1519
|
continue;
|
|
@@ -1272,7 +1539,11 @@ function buildCascadeTree(rootFiber, triggers) {
|
|
|
1272
1539
|
triggerId,
|
|
1273
1540
|
children: [],
|
|
1274
1541
|
depth,
|
|
1275
|
-
isMemoized: isMemoizedFiber(fiber)
|
|
1542
|
+
isMemoized: isMemoizedFiber(fiber),
|
|
1543
|
+
// P6: JSX-runtime attribution — read from fiber.memoizedProps directly.
|
|
1544
|
+
// Same source the walker uses, so cascade nodes align with LiveTreeNode
|
|
1545
|
+
// attribution for the same user component.
|
|
1546
|
+
jsxSource: readJsxSourceFromFiber(fiber)
|
|
1276
1547
|
};
|
|
1277
1548
|
totalComponents++;
|
|
1278
1549
|
if (reason === "parent-cascade") {
|
|
@@ -1305,7 +1576,10 @@ function analyzeCascade(root, triggers) {
|
|
|
1305
1576
|
try {
|
|
1306
1577
|
const finishedLanes = getFinishedLanes(root);
|
|
1307
1578
|
const lane = classifyLanes(finishedLanes);
|
|
1308
|
-
const { rootCauses, totalComponents, avoidableCount, avoidableDuration } = buildCascadeTree(
|
|
1579
|
+
const { rootCauses, totalComponents, avoidableCount, avoidableDuration } = buildCascadeTree(
|
|
1580
|
+
root.current,
|
|
1581
|
+
triggers
|
|
1582
|
+
);
|
|
1309
1583
|
if (totalComponents === 0) return null;
|
|
1310
1584
|
const totalDuration = rootCauses.reduce((sum, n) => sum + n.subtreeDuration, 0);
|
|
1311
1585
|
const triggerIds = triggers.map((t) => t.triggerId);
|
|
@@ -1387,7 +1661,8 @@ function shouldFlagRename(value) {
|
|
|
1387
1661
|
if (value === null || value === void 0) return false;
|
|
1388
1662
|
if (typeof value !== "object") return false;
|
|
1389
1663
|
if (Array.isArray(value) && value.length === 0) return false;
|
|
1390
|
-
if (!Array.isArray(value) && Object.keys(value).length === 0)
|
|
1664
|
+
if (!Array.isArray(value) && Object.keys(value).length === 0)
|
|
1665
|
+
return false;
|
|
1391
1666
|
return true;
|
|
1392
1667
|
}
|
|
1393
1668
|
function computePropIntersectionRatio(nodeProps, childrenProps) {
|
|
@@ -1535,7 +1810,11 @@ function runAnalysis(tree, fiberRefMap2) {
|
|
|
1535
1810
|
}
|
|
1536
1811
|
}
|
|
1537
1812
|
for (const sourceId of sourceNodeIds) {
|
|
1538
|
-
|
|
1813
|
+
const firstEdge = outEdges.get(sourceId)?.[0];
|
|
1814
|
+
if (!firstEdge) continue;
|
|
1815
|
+
const sourcePropName = firstEdge.propKey;
|
|
1816
|
+
const allPaths = [];
|
|
1817
|
+
const dfs = (currentId, currentPropKey, currentPath, visited) => {
|
|
1539
1818
|
if (visited.has(currentId)) return;
|
|
1540
1819
|
visited.add(currentId);
|
|
1541
1820
|
const outgoing = outEdges.get(currentId);
|
|
@@ -1548,7 +1827,7 @@ function runAnalysis(tree, fiberRefMap2) {
|
|
|
1548
1827
|
}
|
|
1549
1828
|
for (const edge of outgoing) {
|
|
1550
1829
|
const isRename = edge.propKey !== edge.childPropKey;
|
|
1551
|
-
|
|
1830
|
+
dfs(
|
|
1552
1831
|
edge.childNodeId,
|
|
1553
1832
|
edge.childPropKey,
|
|
1554
1833
|
[...currentPath, { nodeId: edge.childNodeId, propKey: edge.childPropKey, isRename }],
|
|
@@ -1557,12 +1836,7 @@ function runAnalysis(tree, fiberRefMap2) {
|
|
|
1557
1836
|
}
|
|
1558
1837
|
visited.delete(currentId);
|
|
1559
1838
|
};
|
|
1560
|
-
|
|
1561
|
-
const firstEdge = outEdges.get(sourceId)?.[0];
|
|
1562
|
-
if (!firstEdge) continue;
|
|
1563
|
-
const sourcePropName = firstEdge.propKey;
|
|
1564
|
-
const allPaths = [];
|
|
1565
|
-
dfs2(
|
|
1839
|
+
dfs(
|
|
1566
1840
|
sourceId,
|
|
1567
1841
|
sourcePropName,
|
|
1568
1842
|
[{ nodeId: sourceId, propKey: sourcePropName, isRename: false }],
|
|
@@ -1596,13 +1870,24 @@ function runAnalysis(tree, fiberRefMap2) {
|
|
|
1596
1870
|
propKey: p.propKey,
|
|
1597
1871
|
role,
|
|
1598
1872
|
hookCount: hookCounts.get(p.nodeId) ?? 0,
|
|
1599
|
-
hasContextHook: contextFlags.get(p.nodeId) ?? false
|
|
1873
|
+
hasContextHook: contextFlags.get(p.nodeId) ?? false,
|
|
1874
|
+
// P6: propagate JSX-runtime attribution from the tree node so the
|
|
1875
|
+
// drill-chain detail can render `(file:line)` per step + click-
|
|
1876
|
+
// to-IDE on each component along the chain. Undefined when the
|
|
1877
|
+
// user hasn't opted into the JSX runtime.
|
|
1878
|
+
jsxSource: n?.jsxSource
|
|
1600
1879
|
};
|
|
1601
1880
|
});
|
|
1602
1881
|
const passthroughCount = chainNodes.filter((n) => n.role === "passthrough").length;
|
|
1603
1882
|
const sourceNode = nodeMap.get(sourceId);
|
|
1604
1883
|
const renames = path.flatMap(
|
|
1605
|
-
(p, idx) => p.isRename ? [
|
|
1884
|
+
(p, idx) => p.isRename ? [
|
|
1885
|
+
{
|
|
1886
|
+
atNodeId: p.nodeId,
|
|
1887
|
+
fromKey: idx > 0 ? path[idx - 1].propKey : sourcePropName,
|
|
1888
|
+
toKey: p.propKey
|
|
1889
|
+
}
|
|
1890
|
+
] : []
|
|
1606
1891
|
);
|
|
1607
1892
|
chains.push({
|
|
1608
1893
|
chainId: makeChainId(sourceId, fp, consumerNodeId),
|
|
@@ -1697,10 +1982,7 @@ var SERVER_COMPONENT_PATTERNS = [
|
|
|
1697
1982
|
/[\\/]app[\\/].+[\\/]error\.[jt]sx?$/
|
|
1698
1983
|
// Next.js error UI
|
|
1699
1984
|
];
|
|
1700
|
-
var SERVER_REFERENCE_PATTERNS = [
|
|
1701
|
-
/_ServerReference$/,
|
|
1702
|
-
/^RSC_/
|
|
1703
|
-
];
|
|
1985
|
+
var SERVER_REFERENCE_PATTERNS = [/_ServerReference$/, /^RSC_/];
|
|
1704
1986
|
var detectionEmitted = false;
|
|
1705
1987
|
function maybeEmitNextjsContext(client2) {
|
|
1706
1988
|
if (detectionEmitted) return;
|
|
@@ -1794,7 +2076,9 @@ function scanActionStateChanges(fiberRefMap2, client2) {
|
|
|
1794
2076
|
for (const [nodeId, fiber] of fiberRefMap2) {
|
|
1795
2077
|
const entries = extractActionEntries(fiber);
|
|
1796
2078
|
if (!entries) continue;
|
|
1797
|
-
const snapshot = JSON.stringify(
|
|
2079
|
+
const snapshot = JSON.stringify(
|
|
2080
|
+
entries.map((e) => ({ i: e.hookIndex, p: e.isPending, s: e.state }))
|
|
2081
|
+
);
|
|
1798
2082
|
if (prevActionStateMap.get(nodeId) === snapshot) continue;
|
|
1799
2083
|
prevActionStateMap.set(nodeId, snapshot);
|
|
1800
2084
|
const componentName = nodeId.split("/").pop()?.replace(/-\d+$/, "") ?? "Unknown";
|
|
@@ -1904,7 +2188,8 @@ function tagFetchData(obj, requestId, depth = 0) {
|
|
|
1904
2188
|
const limit = Math.min(obj.length, FETCH_ORIGIN_TAG_ARRAY_LIMIT);
|
|
1905
2189
|
for (let i = 0; i < limit; i++) tagFetchData(obj[i], requestId, depth + 1);
|
|
1906
2190
|
} else {
|
|
1907
|
-
for (const val of Object.values(obj))
|
|
2191
|
+
for (const val of Object.values(obj))
|
|
2192
|
+
tagFetchData(val, requestId, depth + 1);
|
|
1908
2193
|
}
|
|
1909
2194
|
}
|
|
1910
2195
|
function hasActiveTags() {
|
|
@@ -1940,6 +2225,344 @@ function scanForOrigin(obj, depth, ignoreTTL) {
|
|
|
1940
2225
|
return void 0;
|
|
1941
2226
|
}
|
|
1942
2227
|
|
|
2228
|
+
// src/fiberDebugLogger.ts
|
|
2229
|
+
var MAX_FIBER_RECORDS = 500;
|
|
2230
|
+
var MAX_TREE_RECORDS = 50;
|
|
2231
|
+
var MAX_TOP_NAMES_PER_SNAPSHOT = 10;
|
|
2232
|
+
var MAX_CONTEXTS_PER_FIBER = 8;
|
|
2233
|
+
var totalFiberEvents = 0;
|
|
2234
|
+
var recordingStartedAt = null;
|
|
2235
|
+
var fiberRecords = /* @__PURE__ */ new Map();
|
|
2236
|
+
var treeRecords = [];
|
|
2237
|
+
function isRecording() {
|
|
2238
|
+
return Boolean(globalThis.__FT_DEBUG);
|
|
2239
|
+
}
|
|
2240
|
+
function setFiberDebug(enabled) {
|
|
2241
|
+
globalThis.__FT_DEBUG = enabled;
|
|
2242
|
+
if (enabled) {
|
|
2243
|
+
console.info(
|
|
2244
|
+
"%c[FT debug]%c recording started \u2014 call %c__ft.dump()%c to view, %c__ft.clear()%c to reset, %c__ft.download()%c to export",
|
|
2245
|
+
"background:#1e293b;color:#7dd3fc;padding:1px 6px;border-radius:3px;font-weight:600;",
|
|
2246
|
+
"color:#94a3b8;",
|
|
2247
|
+
"color:#a78bfa;font-weight:600;",
|
|
2248
|
+
"color:#94a3b8;",
|
|
2249
|
+
"color:#a78bfa;font-weight:600;",
|
|
2250
|
+
"color:#94a3b8;",
|
|
2251
|
+
"color:#a78bfa;font-weight:600;",
|
|
2252
|
+
"color:#94a3b8;"
|
|
2253
|
+
);
|
|
2254
|
+
}
|
|
2255
|
+
}
|
|
2256
|
+
function isFiberFunctionType(t) {
|
|
2257
|
+
return typeof t === "function";
|
|
2258
|
+
}
|
|
2259
|
+
function isFiberObjectType(t) {
|
|
2260
|
+
return typeof t === "object" && t !== null;
|
|
2261
|
+
}
|
|
2262
|
+
function isClassComponentType(t) {
|
|
2263
|
+
if (typeof t !== "function") return false;
|
|
2264
|
+
const proto = t.prototype;
|
|
2265
|
+
return typeof proto?.isReactComponent !== "undefined";
|
|
2266
|
+
}
|
|
2267
|
+
function describeFiberType(fiber) {
|
|
2268
|
+
const type = fiber.type;
|
|
2269
|
+
if (typeof type === "string") {
|
|
2270
|
+
return {
|
|
2271
|
+
kind: "host",
|
|
2272
|
+
name: type,
|
|
2273
|
+
displayName: void 0,
|
|
2274
|
+
resolved: type,
|
|
2275
|
+
looksMinified: false
|
|
2276
|
+
};
|
|
2277
|
+
}
|
|
2278
|
+
if (isFiberFunctionType(type)) {
|
|
2279
|
+
const isClass = isClassComponentType(type);
|
|
2280
|
+
const resolved = type.displayName ?? type.name ?? "Anonymous";
|
|
2281
|
+
return {
|
|
2282
|
+
kind: isClass ? "class" : "function",
|
|
2283
|
+
name: type.name,
|
|
2284
|
+
displayName: type.displayName,
|
|
2285
|
+
resolved,
|
|
2286
|
+
looksMinified: looksMinified(resolved)
|
|
2287
|
+
};
|
|
2288
|
+
}
|
|
2289
|
+
if (isFiberObjectType(type)) {
|
|
2290
|
+
if (type.render) {
|
|
2291
|
+
const resolved2 = type.render.displayName ?? type.render.name ?? "ForwardRef";
|
|
2292
|
+
return {
|
|
2293
|
+
kind: "forwardRef",
|
|
2294
|
+
name: type.render.name,
|
|
2295
|
+
displayName: type.render.displayName,
|
|
2296
|
+
resolved: resolved2,
|
|
2297
|
+
looksMinified: looksMinified(resolved2)
|
|
2298
|
+
};
|
|
2299
|
+
}
|
|
2300
|
+
if (type.type) {
|
|
2301
|
+
const resolved2 = type.type.displayName ?? type.type.name ?? "Memo";
|
|
2302
|
+
return {
|
|
2303
|
+
kind: "memo",
|
|
2304
|
+
name: type.type.name,
|
|
2305
|
+
displayName: type.type.displayName,
|
|
2306
|
+
resolved: resolved2,
|
|
2307
|
+
looksMinified: looksMinified(resolved2)
|
|
2308
|
+
};
|
|
2309
|
+
}
|
|
2310
|
+
const resolved = type.displayName ?? type.name ?? "Unknown";
|
|
2311
|
+
return {
|
|
2312
|
+
kind: "unknown",
|
|
2313
|
+
name: type.name,
|
|
2314
|
+
displayName: type.displayName,
|
|
2315
|
+
resolved,
|
|
2316
|
+
looksMinified: looksMinified(resolved)
|
|
2317
|
+
};
|
|
2318
|
+
}
|
|
2319
|
+
return {
|
|
2320
|
+
kind: "unknown",
|
|
2321
|
+
name: void 0,
|
|
2322
|
+
displayName: void 0,
|
|
2323
|
+
resolved: "Unknown",
|
|
2324
|
+
looksMinified: false
|
|
2325
|
+
};
|
|
2326
|
+
}
|
|
2327
|
+
function looksMinified(name) {
|
|
2328
|
+
return /^[a-z_$][a-z0-9_$]?$/i.test(name);
|
|
2329
|
+
}
|
|
2330
|
+
function logFiberType(fiber, context) {
|
|
2331
|
+
if (!isRecording()) return;
|
|
2332
|
+
const info = describeFiberType(fiber);
|
|
2333
|
+
const key = info.resolved;
|
|
2334
|
+
const now2 = Date.now();
|
|
2335
|
+
if (recordingStartedAt === null) recordingStartedAt = now2;
|
|
2336
|
+
totalFiberEvents += 1;
|
|
2337
|
+
const existing = fiberRecords.get(key);
|
|
2338
|
+
if (existing) {
|
|
2339
|
+
existing.count += 1;
|
|
2340
|
+
existing.lastSeen = now2;
|
|
2341
|
+
if (context && existing.contexts.size < MAX_CONTEXTS_PER_FIBER) {
|
|
2342
|
+
existing.contexts.add(context);
|
|
2343
|
+
}
|
|
2344
|
+
if (existing.exampleKey === void 0 && typeof fiber.key === "string") {
|
|
2345
|
+
existing.exampleKey = fiber.key;
|
|
2346
|
+
}
|
|
2347
|
+
return;
|
|
2348
|
+
}
|
|
2349
|
+
if (fiberRecords.size >= MAX_FIBER_RECORDS) {
|
|
2350
|
+
evictOldestFiber();
|
|
2351
|
+
}
|
|
2352
|
+
fiberRecords.set(key, {
|
|
2353
|
+
name: info.resolved,
|
|
2354
|
+
rawName: info.name,
|
|
2355
|
+
rawDisplayName: info.displayName,
|
|
2356
|
+
kind: info.kind,
|
|
2357
|
+
fiberTag: fiber.tag,
|
|
2358
|
+
looksMinified: info.looksMinified,
|
|
2359
|
+
count: 1,
|
|
2360
|
+
contexts: new Set(context ? [context] : []),
|
|
2361
|
+
firstSeen: now2,
|
|
2362
|
+
lastSeen: now2,
|
|
2363
|
+
source: fiber._debugSource ?? void 0,
|
|
2364
|
+
exampleKey: typeof fiber.key === "string" ? fiber.key : void 0
|
|
2365
|
+
});
|
|
2366
|
+
}
|
|
2367
|
+
function evictOldestFiber() {
|
|
2368
|
+
let oldestKey;
|
|
2369
|
+
let oldestTime = Infinity;
|
|
2370
|
+
for (const [key, rec] of fiberRecords) {
|
|
2371
|
+
if (rec.lastSeen < oldestTime) {
|
|
2372
|
+
oldestTime = rec.lastSeen;
|
|
2373
|
+
oldestKey = key;
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2376
|
+
if (oldestKey) fiberRecords.delete(oldestKey);
|
|
2377
|
+
}
|
|
2378
|
+
function walkStats(node, depth, acc) {
|
|
2379
|
+
acc.total += 1;
|
|
2380
|
+
if (depth > acc.maxDepth) acc.maxDepth = depth;
|
|
2381
|
+
if (looksMinified(node.name)) acc.minifiedLike += 1;
|
|
2382
|
+
acc.byName.set(node.name, (acc.byName.get(node.name) ?? 0) + 1);
|
|
2383
|
+
for (const child of node.children) walkStats(child, depth + 1, acc);
|
|
2384
|
+
}
|
|
2385
|
+
function logTreeSnapshot(tree, context) {
|
|
2386
|
+
if (!isRecording() || !tree) return;
|
|
2387
|
+
const stats = { total: 0, minifiedLike: 0, byName: /* @__PURE__ */ new Map(), maxDepth: 0 };
|
|
2388
|
+
walkStats(tree, 0, stats);
|
|
2389
|
+
const topNames = [...stats.byName.entries()].sort((a, b) => b[1] - a[1]).slice(0, MAX_TOP_NAMES_PER_SNAPSHOT).map(([n, c]) => `${n}:${c}`).join(",");
|
|
2390
|
+
treeRecords.push({
|
|
2391
|
+
ts: Date.now(),
|
|
2392
|
+
ctx: context ?? "",
|
|
2393
|
+
rootName: tree.name,
|
|
2394
|
+
totalNodes: stats.total,
|
|
2395
|
+
maxDepth: stats.maxDepth,
|
|
2396
|
+
minifiedLike: stats.minifiedLike,
|
|
2397
|
+
topNames
|
|
2398
|
+
});
|
|
2399
|
+
if (treeRecords.length > MAX_TREE_RECORDS) treeRecords.shift();
|
|
2400
|
+
}
|
|
2401
|
+
var logTreeSummary = logTreeSnapshot;
|
|
2402
|
+
function installConsoleApi() {
|
|
2403
|
+
if (globalThis.__ft) return;
|
|
2404
|
+
const api = {
|
|
2405
|
+
dump() {
|
|
2406
|
+
const fiberRows = serializeFiberRecords();
|
|
2407
|
+
const snapRows = serializeTreeRecords();
|
|
2408
|
+
const minifiedCount = fiberRows.filter((r) => r.looksMinified).length;
|
|
2409
|
+
const elapsedSec = recordingStartedAt === null ? 0 : (Date.now() - recordingStartedAt) / 1e3;
|
|
2410
|
+
const summary = [
|
|
2411
|
+
{
|
|
2412
|
+
metric: "uniqueComponents",
|
|
2413
|
+
value: fiberRecords.size
|
|
2414
|
+
},
|
|
2415
|
+
{
|
|
2416
|
+
metric: "totalFiberEvents",
|
|
2417
|
+
value: totalFiberEvents
|
|
2418
|
+
},
|
|
2419
|
+
{
|
|
2420
|
+
metric: "minifiedLike",
|
|
2421
|
+
value: `${minifiedCount} / ${fiberRecords.size}`
|
|
2422
|
+
},
|
|
2423
|
+
{
|
|
2424
|
+
metric: "snapshots",
|
|
2425
|
+
value: treeRecords.length
|
|
2426
|
+
},
|
|
2427
|
+
{
|
|
2428
|
+
metric: "recordingSec",
|
|
2429
|
+
value: elapsedSec.toFixed(1)
|
|
2430
|
+
}
|
|
2431
|
+
];
|
|
2432
|
+
console.groupCollapsed(
|
|
2433
|
+
`%c[FT debug] dump%c \u2014 ${fiberRecords.size} components, ${totalFiberEvents} events, ${treeRecords.length} snapshots`,
|
|
2434
|
+
"background:#1e293b;color:#7dd3fc;padding:1px 6px;border-radius:3px;font-weight:600;",
|
|
2435
|
+
"color:#94a3b8;"
|
|
2436
|
+
);
|
|
2437
|
+
console.log("Summary:");
|
|
2438
|
+
console.table(summary, ["metric", "value"]);
|
|
2439
|
+
console.log("Fibers \u2014 every observed component (sorted by call count):");
|
|
2440
|
+
console.table(fiberRows, [
|
|
2441
|
+
"name",
|
|
2442
|
+
"rawName",
|
|
2443
|
+
"rawDisplayName",
|
|
2444
|
+
"kind",
|
|
2445
|
+
"fiberTag",
|
|
2446
|
+
"count",
|
|
2447
|
+
"looksMinified",
|
|
2448
|
+
"exampleKey",
|
|
2449
|
+
"contexts",
|
|
2450
|
+
"file",
|
|
2451
|
+
"line",
|
|
2452
|
+
"firstSeenAt",
|
|
2453
|
+
"lastSeenAt",
|
|
2454
|
+
"lastAgoSec"
|
|
2455
|
+
]);
|
|
2456
|
+
console.log("Tree snapshots \u2014 newest last:");
|
|
2457
|
+
console.table(snapRows, [
|
|
2458
|
+
"ts",
|
|
2459
|
+
"ctx",
|
|
2460
|
+
"rootName",
|
|
2461
|
+
"totalNodes",
|
|
2462
|
+
"maxDepth",
|
|
2463
|
+
"minifiedLike",
|
|
2464
|
+
"topNames"
|
|
2465
|
+
]);
|
|
2466
|
+
console.groupEnd();
|
|
2467
|
+
},
|
|
2468
|
+
fibers() {
|
|
2469
|
+
console.table(serializeFiberRecords(), [
|
|
2470
|
+
"name",
|
|
2471
|
+
"rawName",
|
|
2472
|
+
"rawDisplayName",
|
|
2473
|
+
"kind",
|
|
2474
|
+
"fiberTag",
|
|
2475
|
+
"count",
|
|
2476
|
+
"looksMinified",
|
|
2477
|
+
"exampleKey",
|
|
2478
|
+
"contexts",
|
|
2479
|
+
"file",
|
|
2480
|
+
"line",
|
|
2481
|
+
"firstSeenAt",
|
|
2482
|
+
"lastSeenAt",
|
|
2483
|
+
"lastAgoSec"
|
|
2484
|
+
]);
|
|
2485
|
+
},
|
|
2486
|
+
snapshots() {
|
|
2487
|
+
console.table(serializeTreeRecords());
|
|
2488
|
+
},
|
|
2489
|
+
tail(n = 20) {
|
|
2490
|
+
console.table(treeRecords.slice(-n));
|
|
2491
|
+
},
|
|
2492
|
+
clear() {
|
|
2493
|
+
fiberRecords.clear();
|
|
2494
|
+
treeRecords.length = 0;
|
|
2495
|
+
console.info("[FT debug] cleared");
|
|
2496
|
+
},
|
|
2497
|
+
size() {
|
|
2498
|
+
return { fibers: fiberRecords.size, snapshots: treeRecords.length };
|
|
2499
|
+
},
|
|
2500
|
+
export() {
|
|
2501
|
+
return { fibers: serializeFiberRecords(), snapshots: treeRecords.slice() };
|
|
2502
|
+
},
|
|
2503
|
+
download(filename) {
|
|
2504
|
+
const data = api.export();
|
|
2505
|
+
const json = JSON.stringify(data, null, 2);
|
|
2506
|
+
const docRef = globalThis.document;
|
|
2507
|
+
const URLRef = globalThis.URL;
|
|
2508
|
+
if (!docRef || !URLRef || typeof URLRef.createObjectURL !== "function") {
|
|
2509
|
+
console.warn(
|
|
2510
|
+
"[FT debug] download() requires a browser environment \u2014 printing JSON instead"
|
|
2511
|
+
);
|
|
2512
|
+
console.log(json);
|
|
2513
|
+
return;
|
|
2514
|
+
}
|
|
2515
|
+
const blob = new Blob([json], { type: "application/json" });
|
|
2516
|
+
const url = URLRef.createObjectURL(blob);
|
|
2517
|
+
const a = docRef.createElement("a");
|
|
2518
|
+
a.href = url;
|
|
2519
|
+
a.download = filename ?? `flotrace-debug-${Date.now()}.json`;
|
|
2520
|
+
a.click();
|
|
2521
|
+
URLRef.revokeObjectURL(url);
|
|
2522
|
+
}
|
|
2523
|
+
};
|
|
2524
|
+
globalThis.__ft = api;
|
|
2525
|
+
}
|
|
2526
|
+
installConsoleApi();
|
|
2527
|
+
function formatTime(ms) {
|
|
2528
|
+
const d = new Date(ms);
|
|
2529
|
+
const hh = String(d.getHours()).padStart(2, "0");
|
|
2530
|
+
const mm = String(d.getMinutes()).padStart(2, "0");
|
|
2531
|
+
const ss = String(d.getSeconds()).padStart(2, "0");
|
|
2532
|
+
const mss = String(d.getMilliseconds()).padStart(3, "0");
|
|
2533
|
+
return `${hh}:${mm}:${ss}.${mss}`;
|
|
2534
|
+
}
|
|
2535
|
+
function serializeFiberRecords() {
|
|
2536
|
+
const now2 = Date.now();
|
|
2537
|
+
return [...fiberRecords.values()].sort((a, b) => b.count - a.count).map((r) => ({
|
|
2538
|
+
name: r.name,
|
|
2539
|
+
rawName: r.rawName ?? "",
|
|
2540
|
+
rawDisplayName: r.rawDisplayName ?? "",
|
|
2541
|
+
kind: r.kind,
|
|
2542
|
+
fiberTag: r.fiberTag ?? "",
|
|
2543
|
+
count: r.count,
|
|
2544
|
+
looksMinified: r.looksMinified,
|
|
2545
|
+
exampleKey: r.exampleKey ?? "",
|
|
2546
|
+
contexts: [...r.contexts].join(","),
|
|
2547
|
+
file: r.source?.fileName ?? "",
|
|
2548
|
+
line: r.source?.lineNumber ?? "",
|
|
2549
|
+
firstSeenAt: formatTime(r.firstSeen),
|
|
2550
|
+
lastSeenAt: formatTime(r.lastSeen),
|
|
2551
|
+
lastAgoSec: ((now2 - r.lastSeen) / 1e3).toFixed(1)
|
|
2552
|
+
}));
|
|
2553
|
+
}
|
|
2554
|
+
function serializeTreeRecords() {
|
|
2555
|
+
return treeRecords.map((t) => ({
|
|
2556
|
+
ts: formatTime(t.ts),
|
|
2557
|
+
ctx: t.ctx,
|
|
2558
|
+
rootName: t.rootName,
|
|
2559
|
+
totalNodes: t.totalNodes,
|
|
2560
|
+
maxDepth: t.maxDepth,
|
|
2561
|
+
minifiedLike: t.minifiedLike,
|
|
2562
|
+
topNames: t.topNames
|
|
2563
|
+
}));
|
|
2564
|
+
}
|
|
2565
|
+
|
|
1943
2566
|
// src/fiberTreeWalker.ts
|
|
1944
2567
|
var FIBER_TAGS = {
|
|
1945
2568
|
FunctionComponent: 0,
|
|
@@ -2082,6 +2705,7 @@ function debugLog(...args) {
|
|
|
2082
2705
|
}
|
|
2083
2706
|
var fiberRefMap = /* @__PURE__ */ new Map();
|
|
2084
2707
|
function getComponentName2(fiber) {
|
|
2708
|
+
logFiberType(fiber, "getName");
|
|
2085
2709
|
const type = fiber.type;
|
|
2086
2710
|
if (!type) return "Unknown";
|
|
2087
2711
|
if (typeof type === "function") {
|
|
@@ -2102,7 +2726,7 @@ function getComponentName2(fiber) {
|
|
|
2102
2726
|
}
|
|
2103
2727
|
return "Unknown";
|
|
2104
2728
|
}
|
|
2105
|
-
function
|
|
2729
|
+
function isLikelyUserComponent(fiber) {
|
|
2106
2730
|
if (!USER_COMPONENT_TAGS.has(fiber.tag)) return false;
|
|
2107
2731
|
const name = getComponentName2(fiber);
|
|
2108
2732
|
if (name === "Anonymous" || name === "Unknown" || name === "ForwardRef" || name === "Memo")
|
|
@@ -2206,15 +2830,28 @@ var FRAMEWORK_PATH_PATTERNS = [
|
|
|
2206
2830
|
/react-hook-form/,
|
|
2207
2831
|
/formik/
|
|
2208
2832
|
];
|
|
2209
|
-
function
|
|
2210
|
-
|
|
2833
|
+
function resolveEffectiveSourceLocation(fiber) {
|
|
2834
|
+
const jsxSrc = readJsxSourceFromFiber(fiber);
|
|
2835
|
+
if (jsxSrc) {
|
|
2836
|
+
return {
|
|
2837
|
+
fileName: jsxSrc.fileName,
|
|
2838
|
+
lineNumber: jsxSrc.lineNumber,
|
|
2839
|
+
columnNumber: jsxSrc.columnNumber
|
|
2840
|
+
};
|
|
2841
|
+
}
|
|
2842
|
+
if (fiber._debugSource?.fileName) {
|
|
2843
|
+
return {
|
|
2844
|
+
fileName: fiber._debugSource.fileName,
|
|
2845
|
+
lineNumber: fiber._debugSource.lineNumber
|
|
2846
|
+
};
|
|
2847
|
+
}
|
|
2211
2848
|
const ownerHit = walkAncestors(
|
|
2212
2849
|
fiber._debugOwner ?? null,
|
|
2213
2850
|
3,
|
|
2214
2851
|
(f) => f._debugOwner ?? null,
|
|
2215
2852
|
(cur) => cur._debugSource?.fileName ?? void 0
|
|
2216
2853
|
);
|
|
2217
|
-
if (ownerHit) return ownerHit;
|
|
2854
|
+
if (ownerHit) return { fileName: ownerHit };
|
|
2218
2855
|
const stack = fiber._debugStack?.stack;
|
|
2219
2856
|
if (typeof stack === "string") {
|
|
2220
2857
|
const parsed = parseFirstNonReactFrame(stack);
|
|
@@ -2222,6 +2859,18 @@ function resolveEffectiveSourcePath(fiber) {
|
|
|
2222
2859
|
}
|
|
2223
2860
|
return null;
|
|
2224
2861
|
}
|
|
2862
|
+
function resolveEffectiveSourcePath(fiber) {
|
|
2863
|
+
return resolveEffectiveSourceLocation(fiber)?.fileName ?? null;
|
|
2864
|
+
}
|
|
2865
|
+
function resolveSourceConfidence(fiber, isFramework, isLibrary, precomputedJsxSource) {
|
|
2866
|
+
if (isUserComponent(fiber)) return "exact";
|
|
2867
|
+
if (isFramework || isLibrary) return "package";
|
|
2868
|
+
const jsxSrc = precomputedJsxSource ?? readJsxSourceFromFiber(fiber);
|
|
2869
|
+
if (jsxSrc) return "exact";
|
|
2870
|
+
if (fiber._debugSource?.fileName) return "exact";
|
|
2871
|
+
if (resolveEffectiveSourcePath(fiber)) return "inferred";
|
|
2872
|
+
return "unknown";
|
|
2873
|
+
}
|
|
2225
2874
|
var STOP_WALK = /* @__PURE__ */ Symbol("stop-walk");
|
|
2226
2875
|
function walkAncestors(start, maxHops, next, visit) {
|
|
2227
2876
|
let cur = start;
|
|
@@ -2232,22 +2881,8 @@ function walkAncestors(start, maxHops, next, visit) {
|
|
|
2232
2881
|
}
|
|
2233
2882
|
return void 0;
|
|
2234
2883
|
}
|
|
2235
|
-
function parseFirstNonReactFrame(stack) {
|
|
2236
|
-
const lines = stack.split("\n");
|
|
2237
|
-
for (const line of lines) {
|
|
2238
|
-
const parened = line.match(/\(([^)]+):\d+:\d+\)/);
|
|
2239
|
-
const hermes = line.match(/@([^\s]+):\d+:\d+$/);
|
|
2240
|
-
const path = parened?.[1] ?? hermes?.[1];
|
|
2241
|
-
if (!path) continue;
|
|
2242
|
-
if (path.includes("react-dom")) continue;
|
|
2243
|
-
if (path.includes("react-native/Libraries")) continue;
|
|
2244
|
-
if (path.includes("/react/cjs/")) continue;
|
|
2245
|
-
if (path.includes("/scheduler/")) continue;
|
|
2246
|
-
return path;
|
|
2247
|
-
}
|
|
2248
|
-
return null;
|
|
2249
|
-
}
|
|
2250
2884
|
function isFrameworkComponent(fiber, name) {
|
|
2885
|
+
if (isUserComponent(fiber)) return false;
|
|
2251
2886
|
if (walkerFilterConfig.frameworkNames.has(name)) return true;
|
|
2252
2887
|
for (const pattern of walkerFilterConfig.frameworkNamePatterns) {
|
|
2253
2888
|
if (pattern.test(name)) return true;
|
|
@@ -2287,6 +2922,7 @@ var KNOWN_LIBRARY_NAMES = /* @__PURE__ */ new Map([
|
|
|
2287
2922
|
["HeroIcon", "heroicons"]
|
|
2288
2923
|
]);
|
|
2289
2924
|
function detectLibraryName(fiber, name) {
|
|
2925
|
+
if (isUserComponent(fiber)) return void 0;
|
|
2290
2926
|
if (name.includes(".")) {
|
|
2291
2927
|
return name.split(".")[0].toLowerCase();
|
|
2292
2928
|
}
|
|
@@ -2357,7 +2993,7 @@ function resolveEffectiveReactKey(fiber) {
|
|
|
2357
2993
|
6,
|
|
2358
2994
|
(f) => f.return ?? null,
|
|
2359
2995
|
(cur) => {
|
|
2360
|
-
if (
|
|
2996
|
+
if (isLikelyUserComponent(cur) && !isFrameworkComponent(cur, getComponentName2(cur))) {
|
|
2361
2997
|
return STOP_WALK;
|
|
2362
2998
|
}
|
|
2363
2999
|
return typeof cur.key === "string" ? cur.key : void 0;
|
|
@@ -2373,7 +3009,7 @@ function walkFiber(fiber, parentId, sharedNameCountMap, depth = 0, inSuspenseFal
|
|
|
2373
3009
|
while (current) {
|
|
2374
3010
|
try {
|
|
2375
3011
|
const tag = current.tag;
|
|
2376
|
-
if (
|
|
3012
|
+
if (isLikelyUserComponent(current)) {
|
|
2377
3013
|
const name = getComponentName2(current);
|
|
2378
3014
|
const nameCount = nameCountMap.get(name) || 0;
|
|
2379
3015
|
nameCountMap.set(name, nameCount + 1);
|
|
@@ -2388,13 +3024,7 @@ function walkFiber(fiber, parentId, sharedNameCountMap, depth = 0, inSuspenseFal
|
|
|
2388
3024
|
{ reason: renderReason },
|
|
2389
3025
|
current.actualDuration
|
|
2390
3026
|
);
|
|
2391
|
-
const children = walkFiber(
|
|
2392
|
-
current.child,
|
|
2393
|
-
nodeId,
|
|
2394
|
-
void 0,
|
|
2395
|
-
depth + 1,
|
|
2396
|
-
inSuspenseFallback
|
|
2397
|
-
);
|
|
3027
|
+
const children = walkFiber(current.child, nodeId, void 0, depth + 1, inSuspenseFallback);
|
|
2398
3028
|
const truncatedChildren = children.length > MAX_CHILDREN_PER_NODE ? children.slice(0, MAX_CHILDREN_PER_NODE) : children;
|
|
2399
3029
|
const framework = isFrameworkComponent(current, name) || void 0;
|
|
2400
3030
|
const queryHashes = detectQueryObserverHashes(current);
|
|
@@ -2402,6 +3032,15 @@ function walkFiber(fiber, parentId, sharedNameCountMap, depth = 0, inSuspenseFal
|
|
|
2402
3032
|
const compilerStatus = detectCompilerStatus(current);
|
|
2403
3033
|
const isServerComponent = detectServerComponent(current) || void 0;
|
|
2404
3034
|
const libraryName = framework ? void 0 : detectLibraryName(current, name);
|
|
3035
|
+
const jsxSource = readJsxSourceFromFiber(current);
|
|
3036
|
+
const sourceConfidence = resolveSourceConfidence(
|
|
3037
|
+
current,
|
|
3038
|
+
framework === true,
|
|
3039
|
+
libraryName !== void 0,
|
|
3040
|
+
jsxSource
|
|
3041
|
+
);
|
|
3042
|
+
const needsStackFallback = (jsxSource?.fileName ?? current._debugSource?.fileName) === void 0 || (jsxSource?.lineNumber ?? current._debugSource?.lineNumber) === void 0;
|
|
3043
|
+
const stackLocation = needsStackFallback ? resolveEffectiveSourceLocation(current) : null;
|
|
2405
3044
|
const node = {
|
|
2406
3045
|
id: nodeId,
|
|
2407
3046
|
name,
|
|
@@ -2410,8 +3049,8 @@ function walkFiber(fiber, parentId, sharedNameCountMap, depth = 0, inSuspenseFal
|
|
|
2410
3049
|
renderPhase,
|
|
2411
3050
|
renderReason,
|
|
2412
3051
|
renderDuration: current.actualDuration,
|
|
2413
|
-
filePath: current._debugSource?.fileName,
|
|
2414
|
-
lineNumber: current._debugSource?.lineNumber,
|
|
3052
|
+
filePath: jsxSource?.fileName ?? current._debugSource?.fileName ?? stackLocation?.fileName,
|
|
3053
|
+
lineNumber: jsxSource?.lineNumber ?? current._debugSource?.lineNumber ?? stackLocation?.lineNumber,
|
|
2415
3054
|
isFramework: framework,
|
|
2416
3055
|
reactKey: resolveEffectiveReactKey(current),
|
|
2417
3056
|
queryHashes,
|
|
@@ -2422,7 +3061,9 @@ function walkFiber(fiber, parentId, sharedNameCountMap, depth = 0, inSuspenseFal
|
|
|
2422
3061
|
compilerStatus,
|
|
2423
3062
|
isServerComponent,
|
|
2424
3063
|
isLibrary: libraryName !== void 0 ? true : void 0,
|
|
2425
|
-
libraryName
|
|
3064
|
+
libraryName,
|
|
3065
|
+
jsxSource,
|
|
3066
|
+
sourceConfidence
|
|
2426
3067
|
};
|
|
2427
3068
|
if (!walkerOptions.pruneSubtree?.(node)) {
|
|
2428
3069
|
nodes.push(node);
|
|
@@ -2496,11 +3137,7 @@ function buildTreeFromFiberRoot(root) {
|
|
|
2496
3137
|
}
|
|
2497
3138
|
fiberRefMap.clear();
|
|
2498
3139
|
const topLevelNodes = walkFiber(rootFiber.child, "");
|
|
2499
|
-
debugLog(
|
|
2500
|
-
"[FloTrace] walkFiber found",
|
|
2501
|
-
topLevelNodes.length,
|
|
2502
|
-
"top-level nodes"
|
|
2503
|
-
);
|
|
3140
|
+
debugLog("[FloTrace] walkFiber found", topLevelNodes.length, "top-level nodes");
|
|
2504
3141
|
if (topLevelNodes.length === 1) {
|
|
2505
3142
|
return topLevelNodes[0];
|
|
2506
3143
|
}
|
|
@@ -2567,9 +3204,7 @@ function findFiberRootFromDOM() {
|
|
|
2567
3204
|
}
|
|
2568
3205
|
}
|
|
2569
3206
|
}
|
|
2570
|
-
console.warn(
|
|
2571
|
-
"[FloTrace] Could not find React fiber root from any DOM element"
|
|
2572
|
-
);
|
|
3207
|
+
console.warn("[FloTrace] Could not find React fiber root from any DOM element");
|
|
2573
3208
|
return null;
|
|
2574
3209
|
} catch (error) {
|
|
2575
3210
|
console.error("[FloTrace] Error finding fiber root from DOM:", error);
|
|
@@ -2631,9 +3266,7 @@ function executeSnapshot(root) {
|
|
|
2631
3266
|
adaptSnapshotInterval(nodeCount);
|
|
2632
3267
|
const client2 = getWebSocketClient();
|
|
2633
3268
|
if (!client2.connected) {
|
|
2634
|
-
console.warn(
|
|
2635
|
-
"[FloTrace] WebSocket not connected, cannot send tree snapshot"
|
|
2636
|
-
);
|
|
3269
|
+
console.warn("[FloTrace] WebSocket not connected, cannot send tree snapshot");
|
|
2637
3270
|
return;
|
|
2638
3271
|
}
|
|
2639
3272
|
const currentFlatTree = flattenTree2(tree);
|
|
@@ -2649,6 +3282,7 @@ function executeSnapshot(root) {
|
|
|
2649
3282
|
"nextInterval:",
|
|
2650
3283
|
snapshotIntervalMs + "ms"
|
|
2651
3284
|
);
|
|
3285
|
+
logTreeSnapshot(tree, `send seq=${snapshotCounter}`);
|
|
2652
3286
|
client2.sendImmediate({
|
|
2653
3287
|
type: "runtime:treeSnapshot",
|
|
2654
3288
|
tree,
|
|
@@ -2669,6 +3303,7 @@ function executeSnapshot(root) {
|
|
|
2669
3303
|
"updated:",
|
|
2670
3304
|
diff.updated.length
|
|
2671
3305
|
);
|
|
3306
|
+
logTreeSummary(tree, `diff seq=${diffSeq}`);
|
|
2672
3307
|
client2.sendImmediate({
|
|
2673
3308
|
type: "runtime:treeDiff",
|
|
2674
3309
|
seq: diffSeq,
|
|
@@ -2809,9 +3444,7 @@ function installFiberTreeWalker(options = {}) {
|
|
|
2809
3444
|
return () => uninstallFiberTreeWalker();
|
|
2810
3445
|
}
|
|
2811
3446
|
if (typeof window === "undefined") {
|
|
2812
|
-
console.warn(
|
|
2813
|
-
"[FloTrace] Not in browser environment, cannot install fiber tree walker"
|
|
2814
|
-
);
|
|
3447
|
+
console.warn("[FloTrace] Not in browser environment, cannot install fiber tree walker");
|
|
2815
3448
|
return () => {
|
|
2816
3449
|
};
|
|
2817
3450
|
}
|
|
@@ -2838,10 +3471,7 @@ function installFiberTreeWalker(options = {}) {
|
|
|
2838
3471
|
try {
|
|
2839
3472
|
originalOnCommitFiberRoot(rendererID, root, priority);
|
|
2840
3473
|
} catch (error) {
|
|
2841
|
-
console.error(
|
|
2842
|
-
"[FloTrace] Error in original onCommitFiberRoot:",
|
|
2843
|
-
error
|
|
2844
|
-
);
|
|
3474
|
+
console.error("[FloTrace] Error in original onCommitFiberRoot:", error);
|
|
2845
3475
|
}
|
|
2846
3476
|
}
|
|
2847
3477
|
if (hookedRendererID === null) {
|
|
@@ -2867,9 +3497,7 @@ function installFiberTreeWalker(options = {}) {
|
|
|
2867
3497
|
scheduleSnapshot(root);
|
|
2868
3498
|
};
|
|
2869
3499
|
activeStrategy = "devtools";
|
|
2870
|
-
console.log(
|
|
2871
|
-
"[FloTrace] Fiber tree walker installed (DevTools hook strategy)"
|
|
2872
|
-
);
|
|
3500
|
+
console.log("[FloTrace] Fiber tree walker installed (DevTools hook strategy)");
|
|
2873
3501
|
setTimeout(() => {
|
|
2874
3502
|
try {
|
|
2875
3503
|
const root = findFiberRootFromDOM();
|
|
@@ -2882,9 +3510,7 @@ function installFiberTreeWalker(options = {}) {
|
|
|
2882
3510
|
}, 100);
|
|
2883
3511
|
} else {
|
|
2884
3512
|
activeStrategy = "dom";
|
|
2885
|
-
console.log(
|
|
2886
|
-
"[FloTrace] Fiber tree walker installed (DOM fallback strategy)"
|
|
2887
|
-
);
|
|
3513
|
+
console.log("[FloTrace] Fiber tree walker installed (DOM fallback strategy)");
|
|
2888
3514
|
setTimeout(() => {
|
|
2889
3515
|
try {
|
|
2890
3516
|
const root = findFiberRootFromDOM();
|
|
@@ -3162,12 +3788,18 @@ function installZustandTracker(stores, client2) {
|
|
|
3162
3788
|
try {
|
|
3163
3789
|
scheduleStoreUpdate(storeName, prevState, newState, client2);
|
|
3164
3790
|
} catch (error) {
|
|
3165
|
-
console.error(
|
|
3791
|
+
console.error(
|
|
3792
|
+
`[FloTrace] Error in Zustand subscribe callback for "${storeName}":`,
|
|
3793
|
+
error
|
|
3794
|
+
);
|
|
3166
3795
|
}
|
|
3167
3796
|
});
|
|
3168
3797
|
activeUnsubscribers.push(unsubscribe);
|
|
3169
3798
|
} catch (error) {
|
|
3170
|
-
console.error(
|
|
3799
|
+
console.error(
|
|
3800
|
+
`[FloTrace] Failed to install tracker for Zustand store "${storeName}":`,
|
|
3801
|
+
error
|
|
3802
|
+
);
|
|
3171
3803
|
}
|
|
3172
3804
|
}
|
|
3173
3805
|
}
|
|
@@ -3210,10 +3842,13 @@ function scheduleStoreUpdate(storeName, prevState, newState, client2) {
|
|
|
3210
3842
|
if (changedKeys.length === 0) return;
|
|
3211
3843
|
const existing = debounceTimers.get(storeName);
|
|
3212
3844
|
if (existing) clearTimeout(existing);
|
|
3213
|
-
debounceTimers.set(
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3845
|
+
debounceTimers.set(
|
|
3846
|
+
storeName,
|
|
3847
|
+
setTimeout(() => {
|
|
3848
|
+
debounceTimers.delete(storeName);
|
|
3849
|
+
sendStoreUpdate(storeName, newState, changedKeys, client2);
|
|
3850
|
+
}, DEBOUNCE_MS)
|
|
3851
|
+
);
|
|
3217
3852
|
}
|
|
3218
3853
|
function sendStoreUpdate(storeName, state, changedKeys, client2) {
|
|
3219
3854
|
try {
|
|
@@ -3786,7 +4421,14 @@ function findMatchingPathInObject(target, targetFp, container, currentPath, dept
|
|
|
3786
4421
|
const child = container[i];
|
|
3787
4422
|
const directMatch = valuesMatch(target, targetFp, child, cache);
|
|
3788
4423
|
if (directMatch) return { path: [...currentPath, String(i)], confidence: directMatch };
|
|
3789
|
-
const nested = findMatchingPathInObject(
|
|
4424
|
+
const nested = findMatchingPathInObject(
|
|
4425
|
+
target,
|
|
4426
|
+
targetFp,
|
|
4427
|
+
child,
|
|
4428
|
+
[...currentPath, String(i)],
|
|
4429
|
+
depth + 1,
|
|
4430
|
+
cache
|
|
4431
|
+
);
|
|
3790
4432
|
if (nested) return nested;
|
|
3791
4433
|
}
|
|
3792
4434
|
} else {
|
|
@@ -3794,7 +4436,14 @@ function findMatchingPathInObject(target, targetFp, container, currentPath, dept
|
|
|
3794
4436
|
const child = container[key];
|
|
3795
4437
|
const directMatch = valuesMatch(target, targetFp, child, cache);
|
|
3796
4438
|
if (directMatch) return { path: [...currentPath, key], confidence: directMatch };
|
|
3797
|
-
const nested = findMatchingPathInObject(
|
|
4439
|
+
const nested = findMatchingPathInObject(
|
|
4440
|
+
target,
|
|
4441
|
+
targetFp,
|
|
4442
|
+
child,
|
|
4443
|
+
[...currentPath, key],
|
|
4444
|
+
depth + 1,
|
|
4445
|
+
cache
|
|
4446
|
+
);
|
|
3798
4447
|
if (nested) return nested;
|
|
3799
4448
|
}
|
|
3800
4449
|
}
|
|
@@ -3899,7 +4548,12 @@ function resolveValueTrace(input) {
|
|
|
3899
4548
|
nodeId: input.nodeId,
|
|
3900
4549
|
componentName: rootComponentName,
|
|
3901
4550
|
propPath: input.propPath,
|
|
3902
|
-
confidence: "exact"
|
|
4551
|
+
confidence: "exact",
|
|
4552
|
+
// P6: `fiber.memoizedProps[FLOTRACE_SOURCE]` is the JSX call site where
|
|
4553
|
+
// this fiber was created — i.e., the `<Consumer .../>` JSX in the
|
|
4554
|
+
// PARENT's source file. That's exactly the "drilled from `Parent.tsx:42`"
|
|
4555
|
+
// attribution the user wants on the leaf prop step.
|
|
4556
|
+
callSiteOfParentJsx: readJsxSourceFromFiber(fiber)
|
|
3903
4557
|
});
|
|
3904
4558
|
} else if (input.hookPath) {
|
|
3905
4559
|
steps.push({
|
|
@@ -3939,7 +4593,12 @@ function resolveValueTrace(input) {
|
|
|
3939
4593
|
nodeId: ancestorNodeId,
|
|
3940
4594
|
componentName: ancestorName,
|
|
3941
4595
|
propPath: trailingSubPath.length > 0 ? [...matchPath, ...trailingSubPath] : matchPath,
|
|
3942
|
-
confidence: matchConfidence
|
|
4596
|
+
confidence: matchConfidence,
|
|
4597
|
+
// P6: attribute the parent JSX call site that drilled this
|
|
4598
|
+
// prop. When the user opted into the JSX runtime AND this
|
|
4599
|
+
// ancestor's fiber went through jsxDEV, the consumer sees
|
|
4600
|
+
// "drilled from `Parent.tsx:42:8`" with a click-to-IDE link.
|
|
4601
|
+
callSiteOfParentJsx: readJsxSourceFromFiber(current)
|
|
3943
4602
|
});
|
|
3944
4603
|
}
|
|
3945
4604
|
} else {
|
|
@@ -3988,7 +4647,11 @@ function resolveValueTrace(input) {
|
|
|
3988
4647
|
const contextMatch = findContextMatch(fiber, rootValue, rootFp, fiberToNodeId, fpCache);
|
|
3989
4648
|
if (contextMatch) {
|
|
3990
4649
|
steps.push(contextMatch.step);
|
|
3991
|
-
const providerStoreMatch = findStoreMatch(
|
|
4650
|
+
const providerStoreMatch = findStoreMatch(
|
|
4651
|
+
contextMatch.providerValue,
|
|
4652
|
+
cachedFp(contextMatch.providerValue, fpCache),
|
|
4653
|
+
fpCache
|
|
4654
|
+
);
|
|
3992
4655
|
if (providerStoreMatch) {
|
|
3993
4656
|
steps.push({
|
|
3994
4657
|
kind: "store",
|
|
@@ -4182,13 +4845,22 @@ function detectWebFramework() {
|
|
|
4182
4845
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4183
4846
|
0 && (module.exports = {
|
|
4184
4847
|
DEFAULT_CONFIG,
|
|
4848
|
+
FLOTRACE_SOURCE,
|
|
4185
4849
|
FloTraceWebSocketClient,
|
|
4850
|
+
JSX_RUNTIME_ACTIVE_KEY,
|
|
4186
4851
|
buildAncestorChain,
|
|
4852
|
+
clearCallSiteRenders,
|
|
4187
4853
|
clearFetchOriginTags,
|
|
4854
|
+
computeCallSiteId,
|
|
4855
|
+
computeCallSiteMetricsPayload,
|
|
4856
|
+
describeFiberType,
|
|
4857
|
+
detectInlineLiterals,
|
|
4188
4858
|
detectServerComponent,
|
|
4189
4859
|
detectWebFramework,
|
|
4190
4860
|
disposeWebSocketClient,
|
|
4191
4861
|
findFetchOrigin,
|
|
4862
|
+
getCallSiteRenderRate,
|
|
4863
|
+
getCallSiteRenders,
|
|
4192
4864
|
getChangedKeys,
|
|
4193
4865
|
getComponentNameFromFiber,
|
|
4194
4866
|
getCurrentRenderingFiber,
|
|
@@ -4211,9 +4883,16 @@ function detectWebFramework() {
|
|
|
4211
4883
|
installTanStackQueryTracker,
|
|
4212
4884
|
installTimelineTracker,
|
|
4213
4885
|
installZustandTracker,
|
|
4886
|
+
isJsxRuntimeActive,
|
|
4214
4887
|
isReduxStore,
|
|
4215
4888
|
isTanStackQueryClient,
|
|
4889
|
+
isUserComponent,
|
|
4890
|
+
logTreeSnapshot,
|
|
4891
|
+
logTreeSummary,
|
|
4892
|
+
markJsxRuntimeActive,
|
|
4216
4893
|
maybeEmitNextjsContext,
|
|
4894
|
+
normalizeJsxSourcePath,
|
|
4895
|
+
recordCallSiteRender,
|
|
4217
4896
|
recordTimelineEvent,
|
|
4218
4897
|
requestFullSnapshot,
|
|
4219
4898
|
requestTreeSnapshot,
|
|
@@ -4221,6 +4900,8 @@ function detectWebFramework() {
|
|
|
4221
4900
|
resolveValueTrace,
|
|
4222
4901
|
serializeProps,
|
|
4223
4902
|
serializeValue,
|
|
4903
|
+
setDuplicateKeyEmitter,
|
|
4904
|
+
setFiberDebug,
|
|
4224
4905
|
tagFetchData,
|
|
4225
4906
|
uninstallFiberTreeWalker,
|
|
4226
4907
|
uninstallReduxTracker,
|