@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.mjs
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FLOTRACE_SOURCE,
|
|
3
|
+
JSX_RUNTIME_ACTIVE_KEY,
|
|
4
|
+
clearCallSiteRenders,
|
|
5
|
+
computeCallSiteId,
|
|
6
|
+
computeCallSiteMetricsPayload,
|
|
7
|
+
detectInlineLiterals,
|
|
8
|
+
getCallSiteRenderRate,
|
|
9
|
+
getCallSiteRenders,
|
|
10
|
+
isJsxRuntimeActive,
|
|
11
|
+
isUserComponent,
|
|
12
|
+
markJsxRuntimeActive,
|
|
13
|
+
normalizeJsxSourcePath,
|
|
14
|
+
parseFirstNonReactFrame,
|
|
15
|
+
readJsxSourceFromFiber,
|
|
16
|
+
recordCallSiteRender,
|
|
17
|
+
setDuplicateKeyEmitter
|
|
18
|
+
} from "./chunk-5LSFLPGP.mjs";
|
|
19
|
+
|
|
1
20
|
// src/types.ts
|
|
2
21
|
var DEFAULT_CONFIG = {
|
|
3
22
|
port: 3457,
|
|
@@ -129,11 +148,7 @@ function serializeValue(value, depth = 0, seen = /* @__PURE__ */ new WeakSet())
|
|
|
129
148
|
for (let i = 0; i < Math.min(keys.length, MAX_OBJECT_KEYS); i++) {
|
|
130
149
|
const key = keys[i];
|
|
131
150
|
try {
|
|
132
|
-
result[key] = serializeValue(
|
|
133
|
-
value[key],
|
|
134
|
-
depth + 1,
|
|
135
|
-
seen
|
|
136
|
-
);
|
|
151
|
+
result[key] = serializeValue(value[key], depth + 1, seen);
|
|
137
152
|
} catch {
|
|
138
153
|
result[key] = { __type: "truncated", originalType: "error" };
|
|
139
154
|
}
|
|
@@ -234,7 +249,12 @@ var _FloTraceWebSocketClient = class _FloTraceWebSocketClient {
|
|
|
234
249
|
frameworkName: this.config.frameworkName,
|
|
235
250
|
frameworkVersion: this.config.frameworkVersion,
|
|
236
251
|
reactNativeVersion: this.config.reactNativeVersion,
|
|
237
|
-
runtimeVersion: this.config.runtimeVersion
|
|
252
|
+
runtimeVersion: this.config.runtimeVersion,
|
|
253
|
+
// P5: JSX runtime adoption signal — read at WS-open time so
|
|
254
|
+
// multiple fibers have already rendered by the moment we report.
|
|
255
|
+
// `isJsxRuntimeActive` reads `globalThis[Symbol.for('flotrace.jsx-runtime-active')]`,
|
|
256
|
+
// which the dev jsx-runtime sets on first jsxDEV call.
|
|
257
|
+
jsxRuntimeActive: isJsxRuntimeActive()
|
|
238
258
|
});
|
|
239
259
|
this.flush();
|
|
240
260
|
};
|
|
@@ -560,26 +580,31 @@ function classifyFromDebugLabel(state, index, effects, effectIdx, debugLabel) {
|
|
|
560
580
|
const ms = state.memoizedState;
|
|
561
581
|
const normalizedLabel = debugLabel.toLowerCase().replace(/\s/g, "");
|
|
562
582
|
const labelMap = {
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
583
|
+
usestate: "useState",
|
|
584
|
+
usereducer: "useReducer",
|
|
585
|
+
useref: "useRef",
|
|
586
|
+
usememo: "useMemo",
|
|
587
|
+
usecallback: "useCallback",
|
|
588
|
+
useeffect: "useEffect",
|
|
589
|
+
uselayouteffect: "useLayoutEffect",
|
|
590
|
+
useinsertioneffect: "useInsertionEffect",
|
|
591
|
+
usecontext: "useContext",
|
|
592
|
+
useimperativehandle: "useImperativeHandle",
|
|
593
|
+
usedebugvalue: "useDebugValue",
|
|
594
|
+
usetransition: "useTransition",
|
|
595
|
+
usedeferredvalue: "useDeferredValue",
|
|
596
|
+
useid: "useId",
|
|
597
|
+
usesyncexternalstore: "useSyncExternalStore",
|
|
598
|
+
useoptimistic: "useOptimistic",
|
|
599
|
+
useformstatus: "useFormStatus"
|
|
580
600
|
};
|
|
581
601
|
const hookType = labelMap[normalizedLabel] ?? "unknown";
|
|
582
|
-
const base = {
|
|
602
|
+
const base = {
|
|
603
|
+
index,
|
|
604
|
+
type: hookType,
|
|
605
|
+
value: serializeValue(ms, 0, /* @__PURE__ */ new WeakSet()),
|
|
606
|
+
debugLabel
|
|
607
|
+
};
|
|
583
608
|
if (hookType === "useEffect" || hookType === "useLayoutEffect" || hookType === "useInsertionEffect") {
|
|
584
609
|
if (effectIdx < effects.length) {
|
|
585
610
|
const effect = effects[effectIdx];
|
|
@@ -1046,7 +1071,13 @@ var ClassComponent = 1;
|
|
|
1046
1071
|
var ForwardRef = 11;
|
|
1047
1072
|
var MemoComponent = 14;
|
|
1048
1073
|
var SimpleMemoComponent = 15;
|
|
1049
|
-
var USER_TAGS = /* @__PURE__ */ new Set([
|
|
1074
|
+
var USER_TAGS = /* @__PURE__ */ new Set([
|
|
1075
|
+
FunctionComponent,
|
|
1076
|
+
ClassComponent,
|
|
1077
|
+
ForwardRef,
|
|
1078
|
+
MemoComponent,
|
|
1079
|
+
SimpleMemoComponent
|
|
1080
|
+
]);
|
|
1050
1081
|
function isMemoizedFiber(fiber) {
|
|
1051
1082
|
return fiber.tag === MemoComponent || fiber.tag === SimpleMemoComponent;
|
|
1052
1083
|
}
|
|
@@ -1130,13 +1161,15 @@ function buildCascadeTree(rootFiber, triggers) {
|
|
|
1130
1161
|
triggerByName.set(t.componentName, t);
|
|
1131
1162
|
}
|
|
1132
1163
|
}
|
|
1133
|
-
const stack = [
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1164
|
+
const stack = [
|
|
1165
|
+
{
|
|
1166
|
+
fiber: rootFiber,
|
|
1167
|
+
depth: 0,
|
|
1168
|
+
parentRerendered: false,
|
|
1169
|
+
parentNode: null,
|
|
1170
|
+
isRoot: true
|
|
1171
|
+
}
|
|
1172
|
+
];
|
|
1140
1173
|
while (stack.length > 0) {
|
|
1141
1174
|
const entry = stack.pop();
|
|
1142
1175
|
const { fiber, depth, parentRerendered, parentNode, isRoot } = entry;
|
|
@@ -1147,7 +1180,13 @@ function buildCascadeTree(rootFiber, triggers) {
|
|
|
1147
1180
|
if (isNewMount && !didRender) {
|
|
1148
1181
|
let child2 = fiber.child;
|
|
1149
1182
|
while (child2) {
|
|
1150
|
-
stack.push({
|
|
1183
|
+
stack.push({
|
|
1184
|
+
fiber: child2,
|
|
1185
|
+
depth: depth + 1,
|
|
1186
|
+
parentRerendered: false,
|
|
1187
|
+
parentNode,
|
|
1188
|
+
isRoot: false
|
|
1189
|
+
});
|
|
1151
1190
|
child2 = child2.sibling;
|
|
1152
1191
|
}
|
|
1153
1192
|
continue;
|
|
@@ -1155,7 +1194,13 @@ function buildCascadeTree(rootFiber, triggers) {
|
|
|
1155
1194
|
if (!USER_TAGS.has(fiber.tag)) {
|
|
1156
1195
|
let child2 = fiber.child;
|
|
1157
1196
|
while (child2) {
|
|
1158
|
-
stack.push({
|
|
1197
|
+
stack.push({
|
|
1198
|
+
fiber: child2,
|
|
1199
|
+
depth: depth + 1,
|
|
1200
|
+
parentRerendered: didRender || parentRerendered,
|
|
1201
|
+
parentNode,
|
|
1202
|
+
isRoot: false
|
|
1203
|
+
});
|
|
1159
1204
|
child2 = child2.sibling;
|
|
1160
1205
|
}
|
|
1161
1206
|
continue;
|
|
@@ -1164,7 +1209,13 @@ function buildCascadeTree(rootFiber, triggers) {
|
|
|
1164
1209
|
if (reason === null) {
|
|
1165
1210
|
let child2 = fiber.child;
|
|
1166
1211
|
while (child2) {
|
|
1167
|
-
stack.push({
|
|
1212
|
+
stack.push({
|
|
1213
|
+
fiber: child2,
|
|
1214
|
+
depth: depth + 1,
|
|
1215
|
+
parentRerendered: false,
|
|
1216
|
+
parentNode,
|
|
1217
|
+
isRoot: false
|
|
1218
|
+
});
|
|
1168
1219
|
child2 = child2.sibling;
|
|
1169
1220
|
}
|
|
1170
1221
|
continue;
|
|
@@ -1190,7 +1241,11 @@ function buildCascadeTree(rootFiber, triggers) {
|
|
|
1190
1241
|
triggerId,
|
|
1191
1242
|
children: [],
|
|
1192
1243
|
depth,
|
|
1193
|
-
isMemoized: isMemoizedFiber(fiber)
|
|
1244
|
+
isMemoized: isMemoizedFiber(fiber),
|
|
1245
|
+
// P6: JSX-runtime attribution — read from fiber.memoizedProps directly.
|
|
1246
|
+
// Same source the walker uses, so cascade nodes align with LiveTreeNode
|
|
1247
|
+
// attribution for the same user component.
|
|
1248
|
+
jsxSource: readJsxSourceFromFiber(fiber)
|
|
1194
1249
|
};
|
|
1195
1250
|
totalComponents++;
|
|
1196
1251
|
if (reason === "parent-cascade") {
|
|
@@ -1223,7 +1278,10 @@ function analyzeCascade(root, triggers) {
|
|
|
1223
1278
|
try {
|
|
1224
1279
|
const finishedLanes = getFinishedLanes(root);
|
|
1225
1280
|
const lane = classifyLanes(finishedLanes);
|
|
1226
|
-
const { rootCauses, totalComponents, avoidableCount, avoidableDuration } = buildCascadeTree(
|
|
1281
|
+
const { rootCauses, totalComponents, avoidableCount, avoidableDuration } = buildCascadeTree(
|
|
1282
|
+
root.current,
|
|
1283
|
+
triggers
|
|
1284
|
+
);
|
|
1227
1285
|
if (totalComponents === 0) return null;
|
|
1228
1286
|
const totalDuration = rootCauses.reduce((sum, n) => sum + n.subtreeDuration, 0);
|
|
1229
1287
|
const triggerIds = triggers.map((t) => t.triggerId);
|
|
@@ -1305,7 +1363,8 @@ function shouldFlagRename(value) {
|
|
|
1305
1363
|
if (value === null || value === void 0) return false;
|
|
1306
1364
|
if (typeof value !== "object") return false;
|
|
1307
1365
|
if (Array.isArray(value) && value.length === 0) return false;
|
|
1308
|
-
if (!Array.isArray(value) && Object.keys(value).length === 0)
|
|
1366
|
+
if (!Array.isArray(value) && Object.keys(value).length === 0)
|
|
1367
|
+
return false;
|
|
1309
1368
|
return true;
|
|
1310
1369
|
}
|
|
1311
1370
|
function computePropIntersectionRatio(nodeProps, childrenProps) {
|
|
@@ -1453,7 +1512,11 @@ function runAnalysis(tree, fiberRefMap2) {
|
|
|
1453
1512
|
}
|
|
1454
1513
|
}
|
|
1455
1514
|
for (const sourceId of sourceNodeIds) {
|
|
1456
|
-
|
|
1515
|
+
const firstEdge = outEdges.get(sourceId)?.[0];
|
|
1516
|
+
if (!firstEdge) continue;
|
|
1517
|
+
const sourcePropName = firstEdge.propKey;
|
|
1518
|
+
const allPaths = [];
|
|
1519
|
+
const dfs = (currentId, currentPropKey, currentPath, visited) => {
|
|
1457
1520
|
if (visited.has(currentId)) return;
|
|
1458
1521
|
visited.add(currentId);
|
|
1459
1522
|
const outgoing = outEdges.get(currentId);
|
|
@@ -1466,7 +1529,7 @@ function runAnalysis(tree, fiberRefMap2) {
|
|
|
1466
1529
|
}
|
|
1467
1530
|
for (const edge of outgoing) {
|
|
1468
1531
|
const isRename = edge.propKey !== edge.childPropKey;
|
|
1469
|
-
|
|
1532
|
+
dfs(
|
|
1470
1533
|
edge.childNodeId,
|
|
1471
1534
|
edge.childPropKey,
|
|
1472
1535
|
[...currentPath, { nodeId: edge.childNodeId, propKey: edge.childPropKey, isRename }],
|
|
@@ -1475,12 +1538,7 @@ function runAnalysis(tree, fiberRefMap2) {
|
|
|
1475
1538
|
}
|
|
1476
1539
|
visited.delete(currentId);
|
|
1477
1540
|
};
|
|
1478
|
-
|
|
1479
|
-
const firstEdge = outEdges.get(sourceId)?.[0];
|
|
1480
|
-
if (!firstEdge) continue;
|
|
1481
|
-
const sourcePropName = firstEdge.propKey;
|
|
1482
|
-
const allPaths = [];
|
|
1483
|
-
dfs2(
|
|
1541
|
+
dfs(
|
|
1484
1542
|
sourceId,
|
|
1485
1543
|
sourcePropName,
|
|
1486
1544
|
[{ nodeId: sourceId, propKey: sourcePropName, isRename: false }],
|
|
@@ -1514,13 +1572,24 @@ function runAnalysis(tree, fiberRefMap2) {
|
|
|
1514
1572
|
propKey: p.propKey,
|
|
1515
1573
|
role,
|
|
1516
1574
|
hookCount: hookCounts.get(p.nodeId) ?? 0,
|
|
1517
|
-
hasContextHook: contextFlags.get(p.nodeId) ?? false
|
|
1575
|
+
hasContextHook: contextFlags.get(p.nodeId) ?? false,
|
|
1576
|
+
// P6: propagate JSX-runtime attribution from the tree node so the
|
|
1577
|
+
// drill-chain detail can render `(file:line)` per step + click-
|
|
1578
|
+
// to-IDE on each component along the chain. Undefined when the
|
|
1579
|
+
// user hasn't opted into the JSX runtime.
|
|
1580
|
+
jsxSource: n?.jsxSource
|
|
1518
1581
|
};
|
|
1519
1582
|
});
|
|
1520
1583
|
const passthroughCount = chainNodes.filter((n) => n.role === "passthrough").length;
|
|
1521
1584
|
const sourceNode = nodeMap.get(sourceId);
|
|
1522
1585
|
const renames = path.flatMap(
|
|
1523
|
-
(p, idx) => p.isRename ? [
|
|
1586
|
+
(p, idx) => p.isRename ? [
|
|
1587
|
+
{
|
|
1588
|
+
atNodeId: p.nodeId,
|
|
1589
|
+
fromKey: idx > 0 ? path[idx - 1].propKey : sourcePropName,
|
|
1590
|
+
toKey: p.propKey
|
|
1591
|
+
}
|
|
1592
|
+
] : []
|
|
1524
1593
|
);
|
|
1525
1594
|
chains.push({
|
|
1526
1595
|
chainId: makeChainId(sourceId, fp, consumerNodeId),
|
|
@@ -1615,10 +1684,7 @@ var SERVER_COMPONENT_PATTERNS = [
|
|
|
1615
1684
|
/[\\/]app[\\/].+[\\/]error\.[jt]sx?$/
|
|
1616
1685
|
// Next.js error UI
|
|
1617
1686
|
];
|
|
1618
|
-
var SERVER_REFERENCE_PATTERNS = [
|
|
1619
|
-
/_ServerReference$/,
|
|
1620
|
-
/^RSC_/
|
|
1621
|
-
];
|
|
1687
|
+
var SERVER_REFERENCE_PATTERNS = [/_ServerReference$/, /^RSC_/];
|
|
1622
1688
|
var detectionEmitted = false;
|
|
1623
1689
|
function maybeEmitNextjsContext(client2) {
|
|
1624
1690
|
if (detectionEmitted) return;
|
|
@@ -1712,7 +1778,9 @@ function scanActionStateChanges(fiberRefMap2, client2) {
|
|
|
1712
1778
|
for (const [nodeId, fiber] of fiberRefMap2) {
|
|
1713
1779
|
const entries = extractActionEntries(fiber);
|
|
1714
1780
|
if (!entries) continue;
|
|
1715
|
-
const snapshot = JSON.stringify(
|
|
1781
|
+
const snapshot = JSON.stringify(
|
|
1782
|
+
entries.map((e) => ({ i: e.hookIndex, p: e.isPending, s: e.state }))
|
|
1783
|
+
);
|
|
1716
1784
|
if (prevActionStateMap.get(nodeId) === snapshot) continue;
|
|
1717
1785
|
prevActionStateMap.set(nodeId, snapshot);
|
|
1718
1786
|
const componentName = nodeId.split("/").pop()?.replace(/-\d+$/, "") ?? "Unknown";
|
|
@@ -1822,7 +1890,8 @@ function tagFetchData(obj, requestId, depth = 0) {
|
|
|
1822
1890
|
const limit = Math.min(obj.length, FETCH_ORIGIN_TAG_ARRAY_LIMIT);
|
|
1823
1891
|
for (let i = 0; i < limit; i++) tagFetchData(obj[i], requestId, depth + 1);
|
|
1824
1892
|
} else {
|
|
1825
|
-
for (const val of Object.values(obj))
|
|
1893
|
+
for (const val of Object.values(obj))
|
|
1894
|
+
tagFetchData(val, requestId, depth + 1);
|
|
1826
1895
|
}
|
|
1827
1896
|
}
|
|
1828
1897
|
function hasActiveTags() {
|
|
@@ -1858,6 +1927,344 @@ function scanForOrigin(obj, depth, ignoreTTL) {
|
|
|
1858
1927
|
return void 0;
|
|
1859
1928
|
}
|
|
1860
1929
|
|
|
1930
|
+
// src/fiberDebugLogger.ts
|
|
1931
|
+
var MAX_FIBER_RECORDS = 500;
|
|
1932
|
+
var MAX_TREE_RECORDS = 50;
|
|
1933
|
+
var MAX_TOP_NAMES_PER_SNAPSHOT = 10;
|
|
1934
|
+
var MAX_CONTEXTS_PER_FIBER = 8;
|
|
1935
|
+
var totalFiberEvents = 0;
|
|
1936
|
+
var recordingStartedAt = null;
|
|
1937
|
+
var fiberRecords = /* @__PURE__ */ new Map();
|
|
1938
|
+
var treeRecords = [];
|
|
1939
|
+
function isRecording() {
|
|
1940
|
+
return Boolean(globalThis.__FT_DEBUG);
|
|
1941
|
+
}
|
|
1942
|
+
function setFiberDebug(enabled) {
|
|
1943
|
+
globalThis.__FT_DEBUG = enabled;
|
|
1944
|
+
if (enabled) {
|
|
1945
|
+
console.info(
|
|
1946
|
+
"%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",
|
|
1947
|
+
"background:#1e293b;color:#7dd3fc;padding:1px 6px;border-radius:3px;font-weight:600;",
|
|
1948
|
+
"color:#94a3b8;",
|
|
1949
|
+
"color:#a78bfa;font-weight:600;",
|
|
1950
|
+
"color:#94a3b8;",
|
|
1951
|
+
"color:#a78bfa;font-weight:600;",
|
|
1952
|
+
"color:#94a3b8;",
|
|
1953
|
+
"color:#a78bfa;font-weight:600;",
|
|
1954
|
+
"color:#94a3b8;"
|
|
1955
|
+
);
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
function isFiberFunctionType(t) {
|
|
1959
|
+
return typeof t === "function";
|
|
1960
|
+
}
|
|
1961
|
+
function isFiberObjectType(t) {
|
|
1962
|
+
return typeof t === "object" && t !== null;
|
|
1963
|
+
}
|
|
1964
|
+
function isClassComponentType(t) {
|
|
1965
|
+
if (typeof t !== "function") return false;
|
|
1966
|
+
const proto = t.prototype;
|
|
1967
|
+
return typeof proto?.isReactComponent !== "undefined";
|
|
1968
|
+
}
|
|
1969
|
+
function describeFiberType(fiber) {
|
|
1970
|
+
const type = fiber.type;
|
|
1971
|
+
if (typeof type === "string") {
|
|
1972
|
+
return {
|
|
1973
|
+
kind: "host",
|
|
1974
|
+
name: type,
|
|
1975
|
+
displayName: void 0,
|
|
1976
|
+
resolved: type,
|
|
1977
|
+
looksMinified: false
|
|
1978
|
+
};
|
|
1979
|
+
}
|
|
1980
|
+
if (isFiberFunctionType(type)) {
|
|
1981
|
+
const isClass = isClassComponentType(type);
|
|
1982
|
+
const resolved = type.displayName ?? type.name ?? "Anonymous";
|
|
1983
|
+
return {
|
|
1984
|
+
kind: isClass ? "class" : "function",
|
|
1985
|
+
name: type.name,
|
|
1986
|
+
displayName: type.displayName,
|
|
1987
|
+
resolved,
|
|
1988
|
+
looksMinified: looksMinified(resolved)
|
|
1989
|
+
};
|
|
1990
|
+
}
|
|
1991
|
+
if (isFiberObjectType(type)) {
|
|
1992
|
+
if (type.render) {
|
|
1993
|
+
const resolved2 = type.render.displayName ?? type.render.name ?? "ForwardRef";
|
|
1994
|
+
return {
|
|
1995
|
+
kind: "forwardRef",
|
|
1996
|
+
name: type.render.name,
|
|
1997
|
+
displayName: type.render.displayName,
|
|
1998
|
+
resolved: resolved2,
|
|
1999
|
+
looksMinified: looksMinified(resolved2)
|
|
2000
|
+
};
|
|
2001
|
+
}
|
|
2002
|
+
if (type.type) {
|
|
2003
|
+
const resolved2 = type.type.displayName ?? type.type.name ?? "Memo";
|
|
2004
|
+
return {
|
|
2005
|
+
kind: "memo",
|
|
2006
|
+
name: type.type.name,
|
|
2007
|
+
displayName: type.type.displayName,
|
|
2008
|
+
resolved: resolved2,
|
|
2009
|
+
looksMinified: looksMinified(resolved2)
|
|
2010
|
+
};
|
|
2011
|
+
}
|
|
2012
|
+
const resolved = type.displayName ?? type.name ?? "Unknown";
|
|
2013
|
+
return {
|
|
2014
|
+
kind: "unknown",
|
|
2015
|
+
name: type.name,
|
|
2016
|
+
displayName: type.displayName,
|
|
2017
|
+
resolved,
|
|
2018
|
+
looksMinified: looksMinified(resolved)
|
|
2019
|
+
};
|
|
2020
|
+
}
|
|
2021
|
+
return {
|
|
2022
|
+
kind: "unknown",
|
|
2023
|
+
name: void 0,
|
|
2024
|
+
displayName: void 0,
|
|
2025
|
+
resolved: "Unknown",
|
|
2026
|
+
looksMinified: false
|
|
2027
|
+
};
|
|
2028
|
+
}
|
|
2029
|
+
function looksMinified(name) {
|
|
2030
|
+
return /^[a-z_$][a-z0-9_$]?$/i.test(name);
|
|
2031
|
+
}
|
|
2032
|
+
function logFiberType(fiber, context) {
|
|
2033
|
+
if (!isRecording()) return;
|
|
2034
|
+
const info = describeFiberType(fiber);
|
|
2035
|
+
const key = info.resolved;
|
|
2036
|
+
const now2 = Date.now();
|
|
2037
|
+
if (recordingStartedAt === null) recordingStartedAt = now2;
|
|
2038
|
+
totalFiberEvents += 1;
|
|
2039
|
+
const existing = fiberRecords.get(key);
|
|
2040
|
+
if (existing) {
|
|
2041
|
+
existing.count += 1;
|
|
2042
|
+
existing.lastSeen = now2;
|
|
2043
|
+
if (context && existing.contexts.size < MAX_CONTEXTS_PER_FIBER) {
|
|
2044
|
+
existing.contexts.add(context);
|
|
2045
|
+
}
|
|
2046
|
+
if (existing.exampleKey === void 0 && typeof fiber.key === "string") {
|
|
2047
|
+
existing.exampleKey = fiber.key;
|
|
2048
|
+
}
|
|
2049
|
+
return;
|
|
2050
|
+
}
|
|
2051
|
+
if (fiberRecords.size >= MAX_FIBER_RECORDS) {
|
|
2052
|
+
evictOldestFiber();
|
|
2053
|
+
}
|
|
2054
|
+
fiberRecords.set(key, {
|
|
2055
|
+
name: info.resolved,
|
|
2056
|
+
rawName: info.name,
|
|
2057
|
+
rawDisplayName: info.displayName,
|
|
2058
|
+
kind: info.kind,
|
|
2059
|
+
fiberTag: fiber.tag,
|
|
2060
|
+
looksMinified: info.looksMinified,
|
|
2061
|
+
count: 1,
|
|
2062
|
+
contexts: new Set(context ? [context] : []),
|
|
2063
|
+
firstSeen: now2,
|
|
2064
|
+
lastSeen: now2,
|
|
2065
|
+
source: fiber._debugSource ?? void 0,
|
|
2066
|
+
exampleKey: typeof fiber.key === "string" ? fiber.key : void 0
|
|
2067
|
+
});
|
|
2068
|
+
}
|
|
2069
|
+
function evictOldestFiber() {
|
|
2070
|
+
let oldestKey;
|
|
2071
|
+
let oldestTime = Infinity;
|
|
2072
|
+
for (const [key, rec] of fiberRecords) {
|
|
2073
|
+
if (rec.lastSeen < oldestTime) {
|
|
2074
|
+
oldestTime = rec.lastSeen;
|
|
2075
|
+
oldestKey = key;
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
if (oldestKey) fiberRecords.delete(oldestKey);
|
|
2079
|
+
}
|
|
2080
|
+
function walkStats(node, depth, acc) {
|
|
2081
|
+
acc.total += 1;
|
|
2082
|
+
if (depth > acc.maxDepth) acc.maxDepth = depth;
|
|
2083
|
+
if (looksMinified(node.name)) acc.minifiedLike += 1;
|
|
2084
|
+
acc.byName.set(node.name, (acc.byName.get(node.name) ?? 0) + 1);
|
|
2085
|
+
for (const child of node.children) walkStats(child, depth + 1, acc);
|
|
2086
|
+
}
|
|
2087
|
+
function logTreeSnapshot(tree, context) {
|
|
2088
|
+
if (!isRecording() || !tree) return;
|
|
2089
|
+
const stats = { total: 0, minifiedLike: 0, byName: /* @__PURE__ */ new Map(), maxDepth: 0 };
|
|
2090
|
+
walkStats(tree, 0, stats);
|
|
2091
|
+
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(",");
|
|
2092
|
+
treeRecords.push({
|
|
2093
|
+
ts: Date.now(),
|
|
2094
|
+
ctx: context ?? "",
|
|
2095
|
+
rootName: tree.name,
|
|
2096
|
+
totalNodes: stats.total,
|
|
2097
|
+
maxDepth: stats.maxDepth,
|
|
2098
|
+
minifiedLike: stats.minifiedLike,
|
|
2099
|
+
topNames
|
|
2100
|
+
});
|
|
2101
|
+
if (treeRecords.length > MAX_TREE_RECORDS) treeRecords.shift();
|
|
2102
|
+
}
|
|
2103
|
+
var logTreeSummary = logTreeSnapshot;
|
|
2104
|
+
function installConsoleApi() {
|
|
2105
|
+
if (globalThis.__ft) return;
|
|
2106
|
+
const api = {
|
|
2107
|
+
dump() {
|
|
2108
|
+
const fiberRows = serializeFiberRecords();
|
|
2109
|
+
const snapRows = serializeTreeRecords();
|
|
2110
|
+
const minifiedCount = fiberRows.filter((r) => r.looksMinified).length;
|
|
2111
|
+
const elapsedSec = recordingStartedAt === null ? 0 : (Date.now() - recordingStartedAt) / 1e3;
|
|
2112
|
+
const summary = [
|
|
2113
|
+
{
|
|
2114
|
+
metric: "uniqueComponents",
|
|
2115
|
+
value: fiberRecords.size
|
|
2116
|
+
},
|
|
2117
|
+
{
|
|
2118
|
+
metric: "totalFiberEvents",
|
|
2119
|
+
value: totalFiberEvents
|
|
2120
|
+
},
|
|
2121
|
+
{
|
|
2122
|
+
metric: "minifiedLike",
|
|
2123
|
+
value: `${minifiedCount} / ${fiberRecords.size}`
|
|
2124
|
+
},
|
|
2125
|
+
{
|
|
2126
|
+
metric: "snapshots",
|
|
2127
|
+
value: treeRecords.length
|
|
2128
|
+
},
|
|
2129
|
+
{
|
|
2130
|
+
metric: "recordingSec",
|
|
2131
|
+
value: elapsedSec.toFixed(1)
|
|
2132
|
+
}
|
|
2133
|
+
];
|
|
2134
|
+
console.groupCollapsed(
|
|
2135
|
+
`%c[FT debug] dump%c \u2014 ${fiberRecords.size} components, ${totalFiberEvents} events, ${treeRecords.length} snapshots`,
|
|
2136
|
+
"background:#1e293b;color:#7dd3fc;padding:1px 6px;border-radius:3px;font-weight:600;",
|
|
2137
|
+
"color:#94a3b8;"
|
|
2138
|
+
);
|
|
2139
|
+
console.log("Summary:");
|
|
2140
|
+
console.table(summary, ["metric", "value"]);
|
|
2141
|
+
console.log("Fibers \u2014 every observed component (sorted by call count):");
|
|
2142
|
+
console.table(fiberRows, [
|
|
2143
|
+
"name",
|
|
2144
|
+
"rawName",
|
|
2145
|
+
"rawDisplayName",
|
|
2146
|
+
"kind",
|
|
2147
|
+
"fiberTag",
|
|
2148
|
+
"count",
|
|
2149
|
+
"looksMinified",
|
|
2150
|
+
"exampleKey",
|
|
2151
|
+
"contexts",
|
|
2152
|
+
"file",
|
|
2153
|
+
"line",
|
|
2154
|
+
"firstSeenAt",
|
|
2155
|
+
"lastSeenAt",
|
|
2156
|
+
"lastAgoSec"
|
|
2157
|
+
]);
|
|
2158
|
+
console.log("Tree snapshots \u2014 newest last:");
|
|
2159
|
+
console.table(snapRows, [
|
|
2160
|
+
"ts",
|
|
2161
|
+
"ctx",
|
|
2162
|
+
"rootName",
|
|
2163
|
+
"totalNodes",
|
|
2164
|
+
"maxDepth",
|
|
2165
|
+
"minifiedLike",
|
|
2166
|
+
"topNames"
|
|
2167
|
+
]);
|
|
2168
|
+
console.groupEnd();
|
|
2169
|
+
},
|
|
2170
|
+
fibers() {
|
|
2171
|
+
console.table(serializeFiberRecords(), [
|
|
2172
|
+
"name",
|
|
2173
|
+
"rawName",
|
|
2174
|
+
"rawDisplayName",
|
|
2175
|
+
"kind",
|
|
2176
|
+
"fiberTag",
|
|
2177
|
+
"count",
|
|
2178
|
+
"looksMinified",
|
|
2179
|
+
"exampleKey",
|
|
2180
|
+
"contexts",
|
|
2181
|
+
"file",
|
|
2182
|
+
"line",
|
|
2183
|
+
"firstSeenAt",
|
|
2184
|
+
"lastSeenAt",
|
|
2185
|
+
"lastAgoSec"
|
|
2186
|
+
]);
|
|
2187
|
+
},
|
|
2188
|
+
snapshots() {
|
|
2189
|
+
console.table(serializeTreeRecords());
|
|
2190
|
+
},
|
|
2191
|
+
tail(n = 20) {
|
|
2192
|
+
console.table(treeRecords.slice(-n));
|
|
2193
|
+
},
|
|
2194
|
+
clear() {
|
|
2195
|
+
fiberRecords.clear();
|
|
2196
|
+
treeRecords.length = 0;
|
|
2197
|
+
console.info("[FT debug] cleared");
|
|
2198
|
+
},
|
|
2199
|
+
size() {
|
|
2200
|
+
return { fibers: fiberRecords.size, snapshots: treeRecords.length };
|
|
2201
|
+
},
|
|
2202
|
+
export() {
|
|
2203
|
+
return { fibers: serializeFiberRecords(), snapshots: treeRecords.slice() };
|
|
2204
|
+
},
|
|
2205
|
+
download(filename) {
|
|
2206
|
+
const data = api.export();
|
|
2207
|
+
const json = JSON.stringify(data, null, 2);
|
|
2208
|
+
const docRef = globalThis.document;
|
|
2209
|
+
const URLRef = globalThis.URL;
|
|
2210
|
+
if (!docRef || !URLRef || typeof URLRef.createObjectURL !== "function") {
|
|
2211
|
+
console.warn(
|
|
2212
|
+
"[FT debug] download() requires a browser environment \u2014 printing JSON instead"
|
|
2213
|
+
);
|
|
2214
|
+
console.log(json);
|
|
2215
|
+
return;
|
|
2216
|
+
}
|
|
2217
|
+
const blob = new Blob([json], { type: "application/json" });
|
|
2218
|
+
const url = URLRef.createObjectURL(blob);
|
|
2219
|
+
const a = docRef.createElement("a");
|
|
2220
|
+
a.href = url;
|
|
2221
|
+
a.download = filename ?? `flotrace-debug-${Date.now()}.json`;
|
|
2222
|
+
a.click();
|
|
2223
|
+
URLRef.revokeObjectURL(url);
|
|
2224
|
+
}
|
|
2225
|
+
};
|
|
2226
|
+
globalThis.__ft = api;
|
|
2227
|
+
}
|
|
2228
|
+
installConsoleApi();
|
|
2229
|
+
function formatTime(ms) {
|
|
2230
|
+
const d = new Date(ms);
|
|
2231
|
+
const hh = String(d.getHours()).padStart(2, "0");
|
|
2232
|
+
const mm = String(d.getMinutes()).padStart(2, "0");
|
|
2233
|
+
const ss = String(d.getSeconds()).padStart(2, "0");
|
|
2234
|
+
const mss = String(d.getMilliseconds()).padStart(3, "0");
|
|
2235
|
+
return `${hh}:${mm}:${ss}.${mss}`;
|
|
2236
|
+
}
|
|
2237
|
+
function serializeFiberRecords() {
|
|
2238
|
+
const now2 = Date.now();
|
|
2239
|
+
return [...fiberRecords.values()].sort((a, b) => b.count - a.count).map((r) => ({
|
|
2240
|
+
name: r.name,
|
|
2241
|
+
rawName: r.rawName ?? "",
|
|
2242
|
+
rawDisplayName: r.rawDisplayName ?? "",
|
|
2243
|
+
kind: r.kind,
|
|
2244
|
+
fiberTag: r.fiberTag ?? "",
|
|
2245
|
+
count: r.count,
|
|
2246
|
+
looksMinified: r.looksMinified,
|
|
2247
|
+
exampleKey: r.exampleKey ?? "",
|
|
2248
|
+
contexts: [...r.contexts].join(","),
|
|
2249
|
+
file: r.source?.fileName ?? "",
|
|
2250
|
+
line: r.source?.lineNumber ?? "",
|
|
2251
|
+
firstSeenAt: formatTime(r.firstSeen),
|
|
2252
|
+
lastSeenAt: formatTime(r.lastSeen),
|
|
2253
|
+
lastAgoSec: ((now2 - r.lastSeen) / 1e3).toFixed(1)
|
|
2254
|
+
}));
|
|
2255
|
+
}
|
|
2256
|
+
function serializeTreeRecords() {
|
|
2257
|
+
return treeRecords.map((t) => ({
|
|
2258
|
+
ts: formatTime(t.ts),
|
|
2259
|
+
ctx: t.ctx,
|
|
2260
|
+
rootName: t.rootName,
|
|
2261
|
+
totalNodes: t.totalNodes,
|
|
2262
|
+
maxDepth: t.maxDepth,
|
|
2263
|
+
minifiedLike: t.minifiedLike,
|
|
2264
|
+
topNames: t.topNames
|
|
2265
|
+
}));
|
|
2266
|
+
}
|
|
2267
|
+
|
|
1861
2268
|
// src/fiberTreeWalker.ts
|
|
1862
2269
|
var FIBER_TAGS = {
|
|
1863
2270
|
FunctionComponent: 0,
|
|
@@ -2000,6 +2407,7 @@ function debugLog(...args) {
|
|
|
2000
2407
|
}
|
|
2001
2408
|
var fiberRefMap = /* @__PURE__ */ new Map();
|
|
2002
2409
|
function getComponentName2(fiber) {
|
|
2410
|
+
logFiberType(fiber, "getName");
|
|
2003
2411
|
const type = fiber.type;
|
|
2004
2412
|
if (!type) return "Unknown";
|
|
2005
2413
|
if (typeof type === "function") {
|
|
@@ -2020,7 +2428,7 @@ function getComponentName2(fiber) {
|
|
|
2020
2428
|
}
|
|
2021
2429
|
return "Unknown";
|
|
2022
2430
|
}
|
|
2023
|
-
function
|
|
2431
|
+
function isLikelyUserComponent(fiber) {
|
|
2024
2432
|
if (!USER_COMPONENT_TAGS.has(fiber.tag)) return false;
|
|
2025
2433
|
const name = getComponentName2(fiber);
|
|
2026
2434
|
if (name === "Anonymous" || name === "Unknown" || name === "ForwardRef" || name === "Memo")
|
|
@@ -2124,15 +2532,28 @@ var FRAMEWORK_PATH_PATTERNS = [
|
|
|
2124
2532
|
/react-hook-form/,
|
|
2125
2533
|
/formik/
|
|
2126
2534
|
];
|
|
2127
|
-
function
|
|
2128
|
-
|
|
2535
|
+
function resolveEffectiveSourceLocation(fiber) {
|
|
2536
|
+
const jsxSrc = readJsxSourceFromFiber(fiber);
|
|
2537
|
+
if (jsxSrc) {
|
|
2538
|
+
return {
|
|
2539
|
+
fileName: jsxSrc.fileName,
|
|
2540
|
+
lineNumber: jsxSrc.lineNumber,
|
|
2541
|
+
columnNumber: jsxSrc.columnNumber
|
|
2542
|
+
};
|
|
2543
|
+
}
|
|
2544
|
+
if (fiber._debugSource?.fileName) {
|
|
2545
|
+
return {
|
|
2546
|
+
fileName: fiber._debugSource.fileName,
|
|
2547
|
+
lineNumber: fiber._debugSource.lineNumber
|
|
2548
|
+
};
|
|
2549
|
+
}
|
|
2129
2550
|
const ownerHit = walkAncestors(
|
|
2130
2551
|
fiber._debugOwner ?? null,
|
|
2131
2552
|
3,
|
|
2132
2553
|
(f) => f._debugOwner ?? null,
|
|
2133
2554
|
(cur) => cur._debugSource?.fileName ?? void 0
|
|
2134
2555
|
);
|
|
2135
|
-
if (ownerHit) return ownerHit;
|
|
2556
|
+
if (ownerHit) return { fileName: ownerHit };
|
|
2136
2557
|
const stack = fiber._debugStack?.stack;
|
|
2137
2558
|
if (typeof stack === "string") {
|
|
2138
2559
|
const parsed = parseFirstNonReactFrame(stack);
|
|
@@ -2140,6 +2561,18 @@ function resolveEffectiveSourcePath(fiber) {
|
|
|
2140
2561
|
}
|
|
2141
2562
|
return null;
|
|
2142
2563
|
}
|
|
2564
|
+
function resolveEffectiveSourcePath(fiber) {
|
|
2565
|
+
return resolveEffectiveSourceLocation(fiber)?.fileName ?? null;
|
|
2566
|
+
}
|
|
2567
|
+
function resolveSourceConfidence(fiber, isFramework, isLibrary, precomputedJsxSource) {
|
|
2568
|
+
if (isUserComponent(fiber)) return "exact";
|
|
2569
|
+
if (isFramework || isLibrary) return "package";
|
|
2570
|
+
const jsxSrc = precomputedJsxSource ?? readJsxSourceFromFiber(fiber);
|
|
2571
|
+
if (jsxSrc) return "exact";
|
|
2572
|
+
if (fiber._debugSource?.fileName) return "exact";
|
|
2573
|
+
if (resolveEffectiveSourcePath(fiber)) return "inferred";
|
|
2574
|
+
return "unknown";
|
|
2575
|
+
}
|
|
2143
2576
|
var STOP_WALK = /* @__PURE__ */ Symbol("stop-walk");
|
|
2144
2577
|
function walkAncestors(start, maxHops, next, visit) {
|
|
2145
2578
|
let cur = start;
|
|
@@ -2150,22 +2583,8 @@ function walkAncestors(start, maxHops, next, visit) {
|
|
|
2150
2583
|
}
|
|
2151
2584
|
return void 0;
|
|
2152
2585
|
}
|
|
2153
|
-
function parseFirstNonReactFrame(stack) {
|
|
2154
|
-
const lines = stack.split("\n");
|
|
2155
|
-
for (const line of lines) {
|
|
2156
|
-
const parened = line.match(/\(([^)]+):\d+:\d+\)/);
|
|
2157
|
-
const hermes = line.match(/@([^\s]+):\d+:\d+$/);
|
|
2158
|
-
const path = parened?.[1] ?? hermes?.[1];
|
|
2159
|
-
if (!path) continue;
|
|
2160
|
-
if (path.includes("react-dom")) continue;
|
|
2161
|
-
if (path.includes("react-native/Libraries")) continue;
|
|
2162
|
-
if (path.includes("/react/cjs/")) continue;
|
|
2163
|
-
if (path.includes("/scheduler/")) continue;
|
|
2164
|
-
return path;
|
|
2165
|
-
}
|
|
2166
|
-
return null;
|
|
2167
|
-
}
|
|
2168
2586
|
function isFrameworkComponent(fiber, name) {
|
|
2587
|
+
if (isUserComponent(fiber)) return false;
|
|
2169
2588
|
if (walkerFilterConfig.frameworkNames.has(name)) return true;
|
|
2170
2589
|
for (const pattern of walkerFilterConfig.frameworkNamePatterns) {
|
|
2171
2590
|
if (pattern.test(name)) return true;
|
|
@@ -2205,6 +2624,7 @@ var KNOWN_LIBRARY_NAMES = /* @__PURE__ */ new Map([
|
|
|
2205
2624
|
["HeroIcon", "heroicons"]
|
|
2206
2625
|
]);
|
|
2207
2626
|
function detectLibraryName(fiber, name) {
|
|
2627
|
+
if (isUserComponent(fiber)) return void 0;
|
|
2208
2628
|
if (name.includes(".")) {
|
|
2209
2629
|
return name.split(".")[0].toLowerCase();
|
|
2210
2630
|
}
|
|
@@ -2275,7 +2695,7 @@ function resolveEffectiveReactKey(fiber) {
|
|
|
2275
2695
|
6,
|
|
2276
2696
|
(f) => f.return ?? null,
|
|
2277
2697
|
(cur) => {
|
|
2278
|
-
if (
|
|
2698
|
+
if (isLikelyUserComponent(cur) && !isFrameworkComponent(cur, getComponentName2(cur))) {
|
|
2279
2699
|
return STOP_WALK;
|
|
2280
2700
|
}
|
|
2281
2701
|
return typeof cur.key === "string" ? cur.key : void 0;
|
|
@@ -2291,7 +2711,7 @@ function walkFiber(fiber, parentId, sharedNameCountMap, depth = 0, inSuspenseFal
|
|
|
2291
2711
|
while (current) {
|
|
2292
2712
|
try {
|
|
2293
2713
|
const tag = current.tag;
|
|
2294
|
-
if (
|
|
2714
|
+
if (isLikelyUserComponent(current)) {
|
|
2295
2715
|
const name = getComponentName2(current);
|
|
2296
2716
|
const nameCount = nameCountMap.get(name) || 0;
|
|
2297
2717
|
nameCountMap.set(name, nameCount + 1);
|
|
@@ -2306,13 +2726,7 @@ function walkFiber(fiber, parentId, sharedNameCountMap, depth = 0, inSuspenseFal
|
|
|
2306
2726
|
{ reason: renderReason },
|
|
2307
2727
|
current.actualDuration
|
|
2308
2728
|
);
|
|
2309
|
-
const children = walkFiber(
|
|
2310
|
-
current.child,
|
|
2311
|
-
nodeId,
|
|
2312
|
-
void 0,
|
|
2313
|
-
depth + 1,
|
|
2314
|
-
inSuspenseFallback
|
|
2315
|
-
);
|
|
2729
|
+
const children = walkFiber(current.child, nodeId, void 0, depth + 1, inSuspenseFallback);
|
|
2316
2730
|
const truncatedChildren = children.length > MAX_CHILDREN_PER_NODE ? children.slice(0, MAX_CHILDREN_PER_NODE) : children;
|
|
2317
2731
|
const framework = isFrameworkComponent(current, name) || void 0;
|
|
2318
2732
|
const queryHashes = detectQueryObserverHashes(current);
|
|
@@ -2320,6 +2734,15 @@ function walkFiber(fiber, parentId, sharedNameCountMap, depth = 0, inSuspenseFal
|
|
|
2320
2734
|
const compilerStatus = detectCompilerStatus(current);
|
|
2321
2735
|
const isServerComponent = detectServerComponent(current) || void 0;
|
|
2322
2736
|
const libraryName = framework ? void 0 : detectLibraryName(current, name);
|
|
2737
|
+
const jsxSource = readJsxSourceFromFiber(current);
|
|
2738
|
+
const sourceConfidence = resolveSourceConfidence(
|
|
2739
|
+
current,
|
|
2740
|
+
framework === true,
|
|
2741
|
+
libraryName !== void 0,
|
|
2742
|
+
jsxSource
|
|
2743
|
+
);
|
|
2744
|
+
const needsStackFallback = (jsxSource?.fileName ?? current._debugSource?.fileName) === void 0 || (jsxSource?.lineNumber ?? current._debugSource?.lineNumber) === void 0;
|
|
2745
|
+
const stackLocation = needsStackFallback ? resolveEffectiveSourceLocation(current) : null;
|
|
2323
2746
|
const node = {
|
|
2324
2747
|
id: nodeId,
|
|
2325
2748
|
name,
|
|
@@ -2328,8 +2751,8 @@ function walkFiber(fiber, parentId, sharedNameCountMap, depth = 0, inSuspenseFal
|
|
|
2328
2751
|
renderPhase,
|
|
2329
2752
|
renderReason,
|
|
2330
2753
|
renderDuration: current.actualDuration,
|
|
2331
|
-
filePath: current._debugSource?.fileName,
|
|
2332
|
-
lineNumber: current._debugSource?.lineNumber,
|
|
2754
|
+
filePath: jsxSource?.fileName ?? current._debugSource?.fileName ?? stackLocation?.fileName,
|
|
2755
|
+
lineNumber: jsxSource?.lineNumber ?? current._debugSource?.lineNumber ?? stackLocation?.lineNumber,
|
|
2333
2756
|
isFramework: framework,
|
|
2334
2757
|
reactKey: resolveEffectiveReactKey(current),
|
|
2335
2758
|
queryHashes,
|
|
@@ -2340,7 +2763,9 @@ function walkFiber(fiber, parentId, sharedNameCountMap, depth = 0, inSuspenseFal
|
|
|
2340
2763
|
compilerStatus,
|
|
2341
2764
|
isServerComponent,
|
|
2342
2765
|
isLibrary: libraryName !== void 0 ? true : void 0,
|
|
2343
|
-
libraryName
|
|
2766
|
+
libraryName,
|
|
2767
|
+
jsxSource,
|
|
2768
|
+
sourceConfidence
|
|
2344
2769
|
};
|
|
2345
2770
|
if (!walkerOptions.pruneSubtree?.(node)) {
|
|
2346
2771
|
nodes.push(node);
|
|
@@ -2414,11 +2839,7 @@ function buildTreeFromFiberRoot(root) {
|
|
|
2414
2839
|
}
|
|
2415
2840
|
fiberRefMap.clear();
|
|
2416
2841
|
const topLevelNodes = walkFiber(rootFiber.child, "");
|
|
2417
|
-
debugLog(
|
|
2418
|
-
"[FloTrace] walkFiber found",
|
|
2419
|
-
topLevelNodes.length,
|
|
2420
|
-
"top-level nodes"
|
|
2421
|
-
);
|
|
2842
|
+
debugLog("[FloTrace] walkFiber found", topLevelNodes.length, "top-level nodes");
|
|
2422
2843
|
if (topLevelNodes.length === 1) {
|
|
2423
2844
|
return topLevelNodes[0];
|
|
2424
2845
|
}
|
|
@@ -2485,9 +2906,7 @@ function findFiberRootFromDOM() {
|
|
|
2485
2906
|
}
|
|
2486
2907
|
}
|
|
2487
2908
|
}
|
|
2488
|
-
console.warn(
|
|
2489
|
-
"[FloTrace] Could not find React fiber root from any DOM element"
|
|
2490
|
-
);
|
|
2909
|
+
console.warn("[FloTrace] Could not find React fiber root from any DOM element");
|
|
2491
2910
|
return null;
|
|
2492
2911
|
} catch (error) {
|
|
2493
2912
|
console.error("[FloTrace] Error finding fiber root from DOM:", error);
|
|
@@ -2549,9 +2968,7 @@ function executeSnapshot(root) {
|
|
|
2549
2968
|
adaptSnapshotInterval(nodeCount);
|
|
2550
2969
|
const client2 = getWebSocketClient();
|
|
2551
2970
|
if (!client2.connected) {
|
|
2552
|
-
console.warn(
|
|
2553
|
-
"[FloTrace] WebSocket not connected, cannot send tree snapshot"
|
|
2554
|
-
);
|
|
2971
|
+
console.warn("[FloTrace] WebSocket not connected, cannot send tree snapshot");
|
|
2555
2972
|
return;
|
|
2556
2973
|
}
|
|
2557
2974
|
const currentFlatTree = flattenTree2(tree);
|
|
@@ -2567,6 +2984,7 @@ function executeSnapshot(root) {
|
|
|
2567
2984
|
"nextInterval:",
|
|
2568
2985
|
snapshotIntervalMs + "ms"
|
|
2569
2986
|
);
|
|
2987
|
+
logTreeSnapshot(tree, `send seq=${snapshotCounter}`);
|
|
2570
2988
|
client2.sendImmediate({
|
|
2571
2989
|
type: "runtime:treeSnapshot",
|
|
2572
2990
|
tree,
|
|
@@ -2587,6 +3005,7 @@ function executeSnapshot(root) {
|
|
|
2587
3005
|
"updated:",
|
|
2588
3006
|
diff.updated.length
|
|
2589
3007
|
);
|
|
3008
|
+
logTreeSummary(tree, `diff seq=${diffSeq}`);
|
|
2590
3009
|
client2.sendImmediate({
|
|
2591
3010
|
type: "runtime:treeDiff",
|
|
2592
3011
|
seq: diffSeq,
|
|
@@ -2727,9 +3146,7 @@ function installFiberTreeWalker(options = {}) {
|
|
|
2727
3146
|
return () => uninstallFiberTreeWalker();
|
|
2728
3147
|
}
|
|
2729
3148
|
if (typeof window === "undefined") {
|
|
2730
|
-
console.warn(
|
|
2731
|
-
"[FloTrace] Not in browser environment, cannot install fiber tree walker"
|
|
2732
|
-
);
|
|
3149
|
+
console.warn("[FloTrace] Not in browser environment, cannot install fiber tree walker");
|
|
2733
3150
|
return () => {
|
|
2734
3151
|
};
|
|
2735
3152
|
}
|
|
@@ -2756,10 +3173,7 @@ function installFiberTreeWalker(options = {}) {
|
|
|
2756
3173
|
try {
|
|
2757
3174
|
originalOnCommitFiberRoot(rendererID, root, priority);
|
|
2758
3175
|
} catch (error) {
|
|
2759
|
-
console.error(
|
|
2760
|
-
"[FloTrace] Error in original onCommitFiberRoot:",
|
|
2761
|
-
error
|
|
2762
|
-
);
|
|
3176
|
+
console.error("[FloTrace] Error in original onCommitFiberRoot:", error);
|
|
2763
3177
|
}
|
|
2764
3178
|
}
|
|
2765
3179
|
if (hookedRendererID === null) {
|
|
@@ -2785,9 +3199,7 @@ function installFiberTreeWalker(options = {}) {
|
|
|
2785
3199
|
scheduleSnapshot(root);
|
|
2786
3200
|
};
|
|
2787
3201
|
activeStrategy = "devtools";
|
|
2788
|
-
console.log(
|
|
2789
|
-
"[FloTrace] Fiber tree walker installed (DevTools hook strategy)"
|
|
2790
|
-
);
|
|
3202
|
+
console.log("[FloTrace] Fiber tree walker installed (DevTools hook strategy)");
|
|
2791
3203
|
setTimeout(() => {
|
|
2792
3204
|
try {
|
|
2793
3205
|
const root = findFiberRootFromDOM();
|
|
@@ -2800,9 +3212,7 @@ function installFiberTreeWalker(options = {}) {
|
|
|
2800
3212
|
}, 100);
|
|
2801
3213
|
} else {
|
|
2802
3214
|
activeStrategy = "dom";
|
|
2803
|
-
console.log(
|
|
2804
|
-
"[FloTrace] Fiber tree walker installed (DOM fallback strategy)"
|
|
2805
|
-
);
|
|
3215
|
+
console.log("[FloTrace] Fiber tree walker installed (DOM fallback strategy)");
|
|
2806
3216
|
setTimeout(() => {
|
|
2807
3217
|
try {
|
|
2808
3218
|
const root = findFiberRootFromDOM();
|
|
@@ -3080,12 +3490,18 @@ function installZustandTracker(stores, client2) {
|
|
|
3080
3490
|
try {
|
|
3081
3491
|
scheduleStoreUpdate(storeName, prevState, newState, client2);
|
|
3082
3492
|
} catch (error) {
|
|
3083
|
-
console.error(
|
|
3493
|
+
console.error(
|
|
3494
|
+
`[FloTrace] Error in Zustand subscribe callback for "${storeName}":`,
|
|
3495
|
+
error
|
|
3496
|
+
);
|
|
3084
3497
|
}
|
|
3085
3498
|
});
|
|
3086
3499
|
activeUnsubscribers.push(unsubscribe);
|
|
3087
3500
|
} catch (error) {
|
|
3088
|
-
console.error(
|
|
3501
|
+
console.error(
|
|
3502
|
+
`[FloTrace] Failed to install tracker for Zustand store "${storeName}":`,
|
|
3503
|
+
error
|
|
3504
|
+
);
|
|
3089
3505
|
}
|
|
3090
3506
|
}
|
|
3091
3507
|
}
|
|
@@ -3128,10 +3544,13 @@ function scheduleStoreUpdate(storeName, prevState, newState, client2) {
|
|
|
3128
3544
|
if (changedKeys.length === 0) return;
|
|
3129
3545
|
const existing = debounceTimers.get(storeName);
|
|
3130
3546
|
if (existing) clearTimeout(existing);
|
|
3131
|
-
debounceTimers.set(
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3547
|
+
debounceTimers.set(
|
|
3548
|
+
storeName,
|
|
3549
|
+
setTimeout(() => {
|
|
3550
|
+
debounceTimers.delete(storeName);
|
|
3551
|
+
sendStoreUpdate(storeName, newState, changedKeys, client2);
|
|
3552
|
+
}, DEBOUNCE_MS)
|
|
3553
|
+
);
|
|
3135
3554
|
}
|
|
3136
3555
|
function sendStoreUpdate(storeName, state, changedKeys, client2) {
|
|
3137
3556
|
try {
|
|
@@ -3704,7 +4123,14 @@ function findMatchingPathInObject(target, targetFp, container, currentPath, dept
|
|
|
3704
4123
|
const child = container[i];
|
|
3705
4124
|
const directMatch = valuesMatch(target, targetFp, child, cache);
|
|
3706
4125
|
if (directMatch) return { path: [...currentPath, String(i)], confidence: directMatch };
|
|
3707
|
-
const nested = findMatchingPathInObject(
|
|
4126
|
+
const nested = findMatchingPathInObject(
|
|
4127
|
+
target,
|
|
4128
|
+
targetFp,
|
|
4129
|
+
child,
|
|
4130
|
+
[...currentPath, String(i)],
|
|
4131
|
+
depth + 1,
|
|
4132
|
+
cache
|
|
4133
|
+
);
|
|
3708
4134
|
if (nested) return nested;
|
|
3709
4135
|
}
|
|
3710
4136
|
} else {
|
|
@@ -3712,7 +4138,14 @@ function findMatchingPathInObject(target, targetFp, container, currentPath, dept
|
|
|
3712
4138
|
const child = container[key];
|
|
3713
4139
|
const directMatch = valuesMatch(target, targetFp, child, cache);
|
|
3714
4140
|
if (directMatch) return { path: [...currentPath, key], confidence: directMatch };
|
|
3715
|
-
const nested = findMatchingPathInObject(
|
|
4141
|
+
const nested = findMatchingPathInObject(
|
|
4142
|
+
target,
|
|
4143
|
+
targetFp,
|
|
4144
|
+
child,
|
|
4145
|
+
[...currentPath, key],
|
|
4146
|
+
depth + 1,
|
|
4147
|
+
cache
|
|
4148
|
+
);
|
|
3716
4149
|
if (nested) return nested;
|
|
3717
4150
|
}
|
|
3718
4151
|
}
|
|
@@ -3817,7 +4250,12 @@ function resolveValueTrace(input) {
|
|
|
3817
4250
|
nodeId: input.nodeId,
|
|
3818
4251
|
componentName: rootComponentName,
|
|
3819
4252
|
propPath: input.propPath,
|
|
3820
|
-
confidence: "exact"
|
|
4253
|
+
confidence: "exact",
|
|
4254
|
+
// P6: `fiber.memoizedProps[FLOTRACE_SOURCE]` is the JSX call site where
|
|
4255
|
+
// this fiber was created — i.e., the `<Consumer .../>` JSX in the
|
|
4256
|
+
// PARENT's source file. That's exactly the "drilled from `Parent.tsx:42`"
|
|
4257
|
+
// attribution the user wants on the leaf prop step.
|
|
4258
|
+
callSiteOfParentJsx: readJsxSourceFromFiber(fiber)
|
|
3821
4259
|
});
|
|
3822
4260
|
} else if (input.hookPath) {
|
|
3823
4261
|
steps.push({
|
|
@@ -3857,7 +4295,12 @@ function resolveValueTrace(input) {
|
|
|
3857
4295
|
nodeId: ancestorNodeId,
|
|
3858
4296
|
componentName: ancestorName,
|
|
3859
4297
|
propPath: trailingSubPath.length > 0 ? [...matchPath, ...trailingSubPath] : matchPath,
|
|
3860
|
-
confidence: matchConfidence
|
|
4298
|
+
confidence: matchConfidence,
|
|
4299
|
+
// P6: attribute the parent JSX call site that drilled this
|
|
4300
|
+
// prop. When the user opted into the JSX runtime AND this
|
|
4301
|
+
// ancestor's fiber went through jsxDEV, the consumer sees
|
|
4302
|
+
// "drilled from `Parent.tsx:42:8`" with a click-to-IDE link.
|
|
4303
|
+
callSiteOfParentJsx: readJsxSourceFromFiber(current)
|
|
3861
4304
|
});
|
|
3862
4305
|
}
|
|
3863
4306
|
} else {
|
|
@@ -3906,7 +4349,11 @@ function resolveValueTrace(input) {
|
|
|
3906
4349
|
const contextMatch = findContextMatch(fiber, rootValue, rootFp, fiberToNodeId, fpCache);
|
|
3907
4350
|
if (contextMatch) {
|
|
3908
4351
|
steps.push(contextMatch.step);
|
|
3909
|
-
const providerStoreMatch = findStoreMatch(
|
|
4352
|
+
const providerStoreMatch = findStoreMatch(
|
|
4353
|
+
contextMatch.providerValue,
|
|
4354
|
+
cachedFp(contextMatch.providerValue, fpCache),
|
|
4355
|
+
fpCache
|
|
4356
|
+
);
|
|
3910
4357
|
if (providerStoreMatch) {
|
|
3911
4358
|
steps.push({
|
|
3912
4359
|
kind: "store",
|
|
@@ -4099,13 +4546,22 @@ function detectWebFramework() {
|
|
|
4099
4546
|
}
|
|
4100
4547
|
export {
|
|
4101
4548
|
DEFAULT_CONFIG,
|
|
4549
|
+
FLOTRACE_SOURCE,
|
|
4102
4550
|
FloTraceWebSocketClient,
|
|
4551
|
+
JSX_RUNTIME_ACTIVE_KEY,
|
|
4103
4552
|
buildAncestorChain,
|
|
4553
|
+
clearCallSiteRenders,
|
|
4104
4554
|
clearFetchOriginTags,
|
|
4555
|
+
computeCallSiteId,
|
|
4556
|
+
computeCallSiteMetricsPayload,
|
|
4557
|
+
describeFiberType,
|
|
4558
|
+
detectInlineLiterals,
|
|
4105
4559
|
detectServerComponent,
|
|
4106
4560
|
detectWebFramework,
|
|
4107
4561
|
disposeWebSocketClient,
|
|
4108
4562
|
findFetchOrigin,
|
|
4563
|
+
getCallSiteRenderRate,
|
|
4564
|
+
getCallSiteRenders,
|
|
4109
4565
|
getChangedKeys,
|
|
4110
4566
|
getComponentNameFromFiber,
|
|
4111
4567
|
getCurrentRenderingFiber,
|
|
@@ -4128,9 +4584,16 @@ export {
|
|
|
4128
4584
|
installTanStackQueryTracker,
|
|
4129
4585
|
installTimelineTracker,
|
|
4130
4586
|
installZustandTracker,
|
|
4587
|
+
isJsxRuntimeActive,
|
|
4131
4588
|
isReduxStore,
|
|
4132
4589
|
isTanStackQueryClient,
|
|
4590
|
+
isUserComponent,
|
|
4591
|
+
logTreeSnapshot,
|
|
4592
|
+
logTreeSummary,
|
|
4593
|
+
markJsxRuntimeActive,
|
|
4133
4594
|
maybeEmitNextjsContext,
|
|
4595
|
+
normalizeJsxSourcePath,
|
|
4596
|
+
recordCallSiteRender,
|
|
4134
4597
|
recordTimelineEvent,
|
|
4135
4598
|
requestFullSnapshot,
|
|
4136
4599
|
requestTreeSnapshot,
|
|
@@ -4138,6 +4601,8 @@ export {
|
|
|
4138
4601
|
resolveValueTrace,
|
|
4139
4602
|
serializeProps,
|
|
4140
4603
|
serializeValue,
|
|
4604
|
+
setDuplicateKeyEmitter,
|
|
4605
|
+
setFiberDebug,
|
|
4141
4606
|
tagFetchData,
|
|
4142
4607
|
uninstallFiberTreeWalker,
|
|
4143
4608
|
uninstallReduxTracker,
|