@flotrace/runtime-core 2.2.3 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +94 -0
- package/dist/chunk-QLOJU5F2.mjs +181 -0
- package/dist/index.d.mts +380 -22
- package/dist/index.d.ts +380 -22
- package/dist/index.js +681 -106
- package/dist/index.mjs +557 -106
- 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 +12 -1
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,20 @@
|
|
|
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
|
+
markJsxRuntimeActive,
|
|
12
|
+
normalizeJsxSourcePath,
|
|
13
|
+
readJsxSourceFromFiber,
|
|
14
|
+
recordCallSiteRender,
|
|
15
|
+
setDuplicateKeyEmitter
|
|
16
|
+
} from "./chunk-QLOJU5F2.mjs";
|
|
17
|
+
|
|
1
18
|
// src/types.ts
|
|
2
19
|
var DEFAULT_CONFIG = {
|
|
3
20
|
port: 3457,
|
|
@@ -129,11 +146,7 @@ function serializeValue(value, depth = 0, seen = /* @__PURE__ */ new WeakSet())
|
|
|
129
146
|
for (let i = 0; i < Math.min(keys.length, MAX_OBJECT_KEYS); i++) {
|
|
130
147
|
const key = keys[i];
|
|
131
148
|
try {
|
|
132
|
-
result[key] = serializeValue(
|
|
133
|
-
value[key],
|
|
134
|
-
depth + 1,
|
|
135
|
-
seen
|
|
136
|
-
);
|
|
149
|
+
result[key] = serializeValue(value[key], depth + 1, seen);
|
|
137
150
|
} catch {
|
|
138
151
|
result[key] = { __type: "truncated", originalType: "error" };
|
|
139
152
|
}
|
|
@@ -234,7 +247,12 @@ var _FloTraceWebSocketClient = class _FloTraceWebSocketClient {
|
|
|
234
247
|
frameworkName: this.config.frameworkName,
|
|
235
248
|
frameworkVersion: this.config.frameworkVersion,
|
|
236
249
|
reactNativeVersion: this.config.reactNativeVersion,
|
|
237
|
-
runtimeVersion: this.config.runtimeVersion
|
|
250
|
+
runtimeVersion: this.config.runtimeVersion,
|
|
251
|
+
// P5: JSX runtime adoption signal — read at WS-open time so
|
|
252
|
+
// multiple fibers have already rendered by the moment we report.
|
|
253
|
+
// `isJsxRuntimeActive` reads `globalThis[Symbol.for('flotrace.jsx-runtime-active')]`,
|
|
254
|
+
// which the dev jsx-runtime sets on first jsxDEV call.
|
|
255
|
+
jsxRuntimeActive: isJsxRuntimeActive()
|
|
238
256
|
});
|
|
239
257
|
this.flush();
|
|
240
258
|
};
|
|
@@ -560,26 +578,31 @@ function classifyFromDebugLabel(state, index, effects, effectIdx, debugLabel) {
|
|
|
560
578
|
const ms = state.memoizedState;
|
|
561
579
|
const normalizedLabel = debugLabel.toLowerCase().replace(/\s/g, "");
|
|
562
580
|
const labelMap = {
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
581
|
+
usestate: "useState",
|
|
582
|
+
usereducer: "useReducer",
|
|
583
|
+
useref: "useRef",
|
|
584
|
+
usememo: "useMemo",
|
|
585
|
+
usecallback: "useCallback",
|
|
586
|
+
useeffect: "useEffect",
|
|
587
|
+
uselayouteffect: "useLayoutEffect",
|
|
588
|
+
useinsertioneffect: "useInsertionEffect",
|
|
589
|
+
usecontext: "useContext",
|
|
590
|
+
useimperativehandle: "useImperativeHandle",
|
|
591
|
+
usedebugvalue: "useDebugValue",
|
|
592
|
+
usetransition: "useTransition",
|
|
593
|
+
usedeferredvalue: "useDeferredValue",
|
|
594
|
+
useid: "useId",
|
|
595
|
+
usesyncexternalstore: "useSyncExternalStore",
|
|
596
|
+
useoptimistic: "useOptimistic",
|
|
597
|
+
useformstatus: "useFormStatus"
|
|
580
598
|
};
|
|
581
599
|
const hookType = labelMap[normalizedLabel] ?? "unknown";
|
|
582
|
-
const base = {
|
|
600
|
+
const base = {
|
|
601
|
+
index,
|
|
602
|
+
type: hookType,
|
|
603
|
+
value: serializeValue(ms, 0, /* @__PURE__ */ new WeakSet()),
|
|
604
|
+
debugLabel
|
|
605
|
+
};
|
|
583
606
|
if (hookType === "useEffect" || hookType === "useLayoutEffect" || hookType === "useInsertionEffect") {
|
|
584
607
|
if (effectIdx < effects.length) {
|
|
585
608
|
const effect = effects[effectIdx];
|
|
@@ -1046,7 +1069,13 @@ var ClassComponent = 1;
|
|
|
1046
1069
|
var ForwardRef = 11;
|
|
1047
1070
|
var MemoComponent = 14;
|
|
1048
1071
|
var SimpleMemoComponent = 15;
|
|
1049
|
-
var USER_TAGS = /* @__PURE__ */ new Set([
|
|
1072
|
+
var USER_TAGS = /* @__PURE__ */ new Set([
|
|
1073
|
+
FunctionComponent,
|
|
1074
|
+
ClassComponent,
|
|
1075
|
+
ForwardRef,
|
|
1076
|
+
MemoComponent,
|
|
1077
|
+
SimpleMemoComponent
|
|
1078
|
+
]);
|
|
1050
1079
|
function isMemoizedFiber(fiber) {
|
|
1051
1080
|
return fiber.tag === MemoComponent || fiber.tag === SimpleMemoComponent;
|
|
1052
1081
|
}
|
|
@@ -1130,13 +1159,15 @@ function buildCascadeTree(rootFiber, triggers) {
|
|
|
1130
1159
|
triggerByName.set(t.componentName, t);
|
|
1131
1160
|
}
|
|
1132
1161
|
}
|
|
1133
|
-
const stack = [
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1162
|
+
const stack = [
|
|
1163
|
+
{
|
|
1164
|
+
fiber: rootFiber,
|
|
1165
|
+
depth: 0,
|
|
1166
|
+
parentRerendered: false,
|
|
1167
|
+
parentNode: null,
|
|
1168
|
+
isRoot: true
|
|
1169
|
+
}
|
|
1170
|
+
];
|
|
1140
1171
|
while (stack.length > 0) {
|
|
1141
1172
|
const entry = stack.pop();
|
|
1142
1173
|
const { fiber, depth, parentRerendered, parentNode, isRoot } = entry;
|
|
@@ -1147,7 +1178,13 @@ function buildCascadeTree(rootFiber, triggers) {
|
|
|
1147
1178
|
if (isNewMount && !didRender) {
|
|
1148
1179
|
let child2 = fiber.child;
|
|
1149
1180
|
while (child2) {
|
|
1150
|
-
stack.push({
|
|
1181
|
+
stack.push({
|
|
1182
|
+
fiber: child2,
|
|
1183
|
+
depth: depth + 1,
|
|
1184
|
+
parentRerendered: false,
|
|
1185
|
+
parentNode,
|
|
1186
|
+
isRoot: false
|
|
1187
|
+
});
|
|
1151
1188
|
child2 = child2.sibling;
|
|
1152
1189
|
}
|
|
1153
1190
|
continue;
|
|
@@ -1155,7 +1192,13 @@ function buildCascadeTree(rootFiber, triggers) {
|
|
|
1155
1192
|
if (!USER_TAGS.has(fiber.tag)) {
|
|
1156
1193
|
let child2 = fiber.child;
|
|
1157
1194
|
while (child2) {
|
|
1158
|
-
stack.push({
|
|
1195
|
+
stack.push({
|
|
1196
|
+
fiber: child2,
|
|
1197
|
+
depth: depth + 1,
|
|
1198
|
+
parentRerendered: didRender || parentRerendered,
|
|
1199
|
+
parentNode,
|
|
1200
|
+
isRoot: false
|
|
1201
|
+
});
|
|
1159
1202
|
child2 = child2.sibling;
|
|
1160
1203
|
}
|
|
1161
1204
|
continue;
|
|
@@ -1164,7 +1207,13 @@ function buildCascadeTree(rootFiber, triggers) {
|
|
|
1164
1207
|
if (reason === null) {
|
|
1165
1208
|
let child2 = fiber.child;
|
|
1166
1209
|
while (child2) {
|
|
1167
|
-
stack.push({
|
|
1210
|
+
stack.push({
|
|
1211
|
+
fiber: child2,
|
|
1212
|
+
depth: depth + 1,
|
|
1213
|
+
parentRerendered: false,
|
|
1214
|
+
parentNode,
|
|
1215
|
+
isRoot: false
|
|
1216
|
+
});
|
|
1168
1217
|
child2 = child2.sibling;
|
|
1169
1218
|
}
|
|
1170
1219
|
continue;
|
|
@@ -1190,7 +1239,11 @@ function buildCascadeTree(rootFiber, triggers) {
|
|
|
1190
1239
|
triggerId,
|
|
1191
1240
|
children: [],
|
|
1192
1241
|
depth,
|
|
1193
|
-
isMemoized: isMemoizedFiber(fiber)
|
|
1242
|
+
isMemoized: isMemoizedFiber(fiber),
|
|
1243
|
+
// P6: JSX-runtime attribution — read from fiber.memoizedProps directly.
|
|
1244
|
+
// Same source the walker uses, so cascade nodes align with LiveTreeNode
|
|
1245
|
+
// attribution for the same user component.
|
|
1246
|
+
jsxSource: readJsxSourceFromFiber(fiber)
|
|
1194
1247
|
};
|
|
1195
1248
|
totalComponents++;
|
|
1196
1249
|
if (reason === "parent-cascade") {
|
|
@@ -1223,7 +1276,10 @@ function analyzeCascade(root, triggers) {
|
|
|
1223
1276
|
try {
|
|
1224
1277
|
const finishedLanes = getFinishedLanes(root);
|
|
1225
1278
|
const lane = classifyLanes(finishedLanes);
|
|
1226
|
-
const { rootCauses, totalComponents, avoidableCount, avoidableDuration } = buildCascadeTree(
|
|
1279
|
+
const { rootCauses, totalComponents, avoidableCount, avoidableDuration } = buildCascadeTree(
|
|
1280
|
+
root.current,
|
|
1281
|
+
triggers
|
|
1282
|
+
);
|
|
1227
1283
|
if (totalComponents === 0) return null;
|
|
1228
1284
|
const totalDuration = rootCauses.reduce((sum, n) => sum + n.subtreeDuration, 0);
|
|
1229
1285
|
const triggerIds = triggers.map((t) => t.triggerId);
|
|
@@ -1305,7 +1361,8 @@ function shouldFlagRename(value) {
|
|
|
1305
1361
|
if (value === null || value === void 0) return false;
|
|
1306
1362
|
if (typeof value !== "object") return false;
|
|
1307
1363
|
if (Array.isArray(value) && value.length === 0) return false;
|
|
1308
|
-
if (!Array.isArray(value) && Object.keys(value).length === 0)
|
|
1364
|
+
if (!Array.isArray(value) && Object.keys(value).length === 0)
|
|
1365
|
+
return false;
|
|
1309
1366
|
return true;
|
|
1310
1367
|
}
|
|
1311
1368
|
function computePropIntersectionRatio(nodeProps, childrenProps) {
|
|
@@ -1514,13 +1571,24 @@ function runAnalysis(tree, fiberRefMap2) {
|
|
|
1514
1571
|
propKey: p.propKey,
|
|
1515
1572
|
role,
|
|
1516
1573
|
hookCount: hookCounts.get(p.nodeId) ?? 0,
|
|
1517
|
-
hasContextHook: contextFlags.get(p.nodeId) ?? false
|
|
1574
|
+
hasContextHook: contextFlags.get(p.nodeId) ?? false,
|
|
1575
|
+
// P6: propagate JSX-runtime attribution from the tree node so the
|
|
1576
|
+
// drill-chain detail can render `(file:line)` per step + click-
|
|
1577
|
+
// to-IDE on each component along the chain. Undefined when the
|
|
1578
|
+
// user hasn't opted into the JSX runtime.
|
|
1579
|
+
jsxSource: n?.jsxSource
|
|
1518
1580
|
};
|
|
1519
1581
|
});
|
|
1520
1582
|
const passthroughCount = chainNodes.filter((n) => n.role === "passthrough").length;
|
|
1521
1583
|
const sourceNode = nodeMap.get(sourceId);
|
|
1522
1584
|
const renames = path.flatMap(
|
|
1523
|
-
(p, idx) => p.isRename ? [
|
|
1585
|
+
(p, idx) => p.isRename ? [
|
|
1586
|
+
{
|
|
1587
|
+
atNodeId: p.nodeId,
|
|
1588
|
+
fromKey: idx > 0 ? path[idx - 1].propKey : sourcePropName,
|
|
1589
|
+
toKey: p.propKey
|
|
1590
|
+
}
|
|
1591
|
+
] : []
|
|
1524
1592
|
);
|
|
1525
1593
|
chains.push({
|
|
1526
1594
|
chainId: makeChainId(sourceId, fp, consumerNodeId),
|
|
@@ -1615,10 +1683,7 @@ var SERVER_COMPONENT_PATTERNS = [
|
|
|
1615
1683
|
/[\\/]app[\\/].+[\\/]error\.[jt]sx?$/
|
|
1616
1684
|
// Next.js error UI
|
|
1617
1685
|
];
|
|
1618
|
-
var SERVER_REFERENCE_PATTERNS = [
|
|
1619
|
-
/_ServerReference$/,
|
|
1620
|
-
/^RSC_/
|
|
1621
|
-
];
|
|
1686
|
+
var SERVER_REFERENCE_PATTERNS = [/_ServerReference$/, /^RSC_/];
|
|
1622
1687
|
var detectionEmitted = false;
|
|
1623
1688
|
function maybeEmitNextjsContext(client2) {
|
|
1624
1689
|
if (detectionEmitted) return;
|
|
@@ -1712,7 +1777,9 @@ function scanActionStateChanges(fiberRefMap2, client2) {
|
|
|
1712
1777
|
for (const [nodeId, fiber] of fiberRefMap2) {
|
|
1713
1778
|
const entries = extractActionEntries(fiber);
|
|
1714
1779
|
if (!entries) continue;
|
|
1715
|
-
const snapshot = JSON.stringify(
|
|
1780
|
+
const snapshot = JSON.stringify(
|
|
1781
|
+
entries.map((e) => ({ i: e.hookIndex, p: e.isPending, s: e.state }))
|
|
1782
|
+
);
|
|
1716
1783
|
if (prevActionStateMap.get(nodeId) === snapshot) continue;
|
|
1717
1784
|
prevActionStateMap.set(nodeId, snapshot);
|
|
1718
1785
|
const componentName = nodeId.split("/").pop()?.replace(/-\d+$/, "") ?? "Unknown";
|
|
@@ -1822,7 +1889,8 @@ function tagFetchData(obj, requestId, depth = 0) {
|
|
|
1822
1889
|
const limit = Math.min(obj.length, FETCH_ORIGIN_TAG_ARRAY_LIMIT);
|
|
1823
1890
|
for (let i = 0; i < limit; i++) tagFetchData(obj[i], requestId, depth + 1);
|
|
1824
1891
|
} else {
|
|
1825
|
-
for (const val of Object.values(obj))
|
|
1892
|
+
for (const val of Object.values(obj))
|
|
1893
|
+
tagFetchData(val, requestId, depth + 1);
|
|
1826
1894
|
}
|
|
1827
1895
|
}
|
|
1828
1896
|
function hasActiveTags() {
|
|
@@ -1858,6 +1926,344 @@ function scanForOrigin(obj, depth, ignoreTTL) {
|
|
|
1858
1926
|
return void 0;
|
|
1859
1927
|
}
|
|
1860
1928
|
|
|
1929
|
+
// src/fiberDebugLogger.ts
|
|
1930
|
+
var MAX_FIBER_RECORDS = 500;
|
|
1931
|
+
var MAX_TREE_RECORDS = 50;
|
|
1932
|
+
var MAX_TOP_NAMES_PER_SNAPSHOT = 10;
|
|
1933
|
+
var MAX_CONTEXTS_PER_FIBER = 8;
|
|
1934
|
+
var totalFiberEvents = 0;
|
|
1935
|
+
var recordingStartedAt = null;
|
|
1936
|
+
var fiberRecords = /* @__PURE__ */ new Map();
|
|
1937
|
+
var treeRecords = [];
|
|
1938
|
+
function isRecording() {
|
|
1939
|
+
return Boolean(globalThis.__FT_DEBUG);
|
|
1940
|
+
}
|
|
1941
|
+
function setFiberDebug(enabled) {
|
|
1942
|
+
globalThis.__FT_DEBUG = enabled;
|
|
1943
|
+
if (enabled) {
|
|
1944
|
+
console.info(
|
|
1945
|
+
"%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",
|
|
1946
|
+
"background:#1e293b;color:#7dd3fc;padding:1px 6px;border-radius:3px;font-weight:600;",
|
|
1947
|
+
"color:#94a3b8;",
|
|
1948
|
+
"color:#a78bfa;font-weight:600;",
|
|
1949
|
+
"color:#94a3b8;",
|
|
1950
|
+
"color:#a78bfa;font-weight:600;",
|
|
1951
|
+
"color:#94a3b8;",
|
|
1952
|
+
"color:#a78bfa;font-weight:600;",
|
|
1953
|
+
"color:#94a3b8;"
|
|
1954
|
+
);
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
function isFiberFunctionType(t) {
|
|
1958
|
+
return typeof t === "function";
|
|
1959
|
+
}
|
|
1960
|
+
function isFiberObjectType(t) {
|
|
1961
|
+
return typeof t === "object" && t !== null;
|
|
1962
|
+
}
|
|
1963
|
+
function isClassComponentType(t) {
|
|
1964
|
+
if (typeof t !== "function") return false;
|
|
1965
|
+
const proto = t.prototype;
|
|
1966
|
+
return typeof proto?.isReactComponent !== "undefined";
|
|
1967
|
+
}
|
|
1968
|
+
function describeFiberType(fiber) {
|
|
1969
|
+
const type = fiber.type;
|
|
1970
|
+
if (typeof type === "string") {
|
|
1971
|
+
return {
|
|
1972
|
+
kind: "host",
|
|
1973
|
+
name: type,
|
|
1974
|
+
displayName: void 0,
|
|
1975
|
+
resolved: type,
|
|
1976
|
+
looksMinified: false
|
|
1977
|
+
};
|
|
1978
|
+
}
|
|
1979
|
+
if (isFiberFunctionType(type)) {
|
|
1980
|
+
const isClass = isClassComponentType(type);
|
|
1981
|
+
const resolved = type.displayName ?? type.name ?? "Anonymous";
|
|
1982
|
+
return {
|
|
1983
|
+
kind: isClass ? "class" : "function",
|
|
1984
|
+
name: type.name,
|
|
1985
|
+
displayName: type.displayName,
|
|
1986
|
+
resolved,
|
|
1987
|
+
looksMinified: looksMinified(resolved)
|
|
1988
|
+
};
|
|
1989
|
+
}
|
|
1990
|
+
if (isFiberObjectType(type)) {
|
|
1991
|
+
if (type.render) {
|
|
1992
|
+
const resolved2 = type.render.displayName ?? type.render.name ?? "ForwardRef";
|
|
1993
|
+
return {
|
|
1994
|
+
kind: "forwardRef",
|
|
1995
|
+
name: type.render.name,
|
|
1996
|
+
displayName: type.render.displayName,
|
|
1997
|
+
resolved: resolved2,
|
|
1998
|
+
looksMinified: looksMinified(resolved2)
|
|
1999
|
+
};
|
|
2000
|
+
}
|
|
2001
|
+
if (type.type) {
|
|
2002
|
+
const resolved2 = type.type.displayName ?? type.type.name ?? "Memo";
|
|
2003
|
+
return {
|
|
2004
|
+
kind: "memo",
|
|
2005
|
+
name: type.type.name,
|
|
2006
|
+
displayName: type.type.displayName,
|
|
2007
|
+
resolved: resolved2,
|
|
2008
|
+
looksMinified: looksMinified(resolved2)
|
|
2009
|
+
};
|
|
2010
|
+
}
|
|
2011
|
+
const resolved = type.displayName ?? type.name ?? "Unknown";
|
|
2012
|
+
return {
|
|
2013
|
+
kind: "unknown",
|
|
2014
|
+
name: type.name,
|
|
2015
|
+
displayName: type.displayName,
|
|
2016
|
+
resolved,
|
|
2017
|
+
looksMinified: looksMinified(resolved)
|
|
2018
|
+
};
|
|
2019
|
+
}
|
|
2020
|
+
return {
|
|
2021
|
+
kind: "unknown",
|
|
2022
|
+
name: void 0,
|
|
2023
|
+
displayName: void 0,
|
|
2024
|
+
resolved: "Unknown",
|
|
2025
|
+
looksMinified: false
|
|
2026
|
+
};
|
|
2027
|
+
}
|
|
2028
|
+
function looksMinified(name) {
|
|
2029
|
+
return /^[a-z_$][a-z0-9_$]?$/i.test(name);
|
|
2030
|
+
}
|
|
2031
|
+
function logFiberType(fiber, context) {
|
|
2032
|
+
if (!isRecording()) return;
|
|
2033
|
+
const info = describeFiberType(fiber);
|
|
2034
|
+
const key = info.resolved;
|
|
2035
|
+
const now2 = Date.now();
|
|
2036
|
+
if (recordingStartedAt === null) recordingStartedAt = now2;
|
|
2037
|
+
totalFiberEvents += 1;
|
|
2038
|
+
const existing = fiberRecords.get(key);
|
|
2039
|
+
if (existing) {
|
|
2040
|
+
existing.count += 1;
|
|
2041
|
+
existing.lastSeen = now2;
|
|
2042
|
+
if (context && existing.contexts.size < MAX_CONTEXTS_PER_FIBER) {
|
|
2043
|
+
existing.contexts.add(context);
|
|
2044
|
+
}
|
|
2045
|
+
if (existing.exampleKey === void 0 && typeof fiber.key === "string") {
|
|
2046
|
+
existing.exampleKey = fiber.key;
|
|
2047
|
+
}
|
|
2048
|
+
return;
|
|
2049
|
+
}
|
|
2050
|
+
if (fiberRecords.size >= MAX_FIBER_RECORDS) {
|
|
2051
|
+
evictOldestFiber();
|
|
2052
|
+
}
|
|
2053
|
+
fiberRecords.set(key, {
|
|
2054
|
+
name: info.resolved,
|
|
2055
|
+
rawName: info.name,
|
|
2056
|
+
rawDisplayName: info.displayName,
|
|
2057
|
+
kind: info.kind,
|
|
2058
|
+
fiberTag: fiber.tag,
|
|
2059
|
+
looksMinified: info.looksMinified,
|
|
2060
|
+
count: 1,
|
|
2061
|
+
contexts: new Set(context ? [context] : []),
|
|
2062
|
+
firstSeen: now2,
|
|
2063
|
+
lastSeen: now2,
|
|
2064
|
+
source: fiber._debugSource ?? void 0,
|
|
2065
|
+
exampleKey: typeof fiber.key === "string" ? fiber.key : void 0
|
|
2066
|
+
});
|
|
2067
|
+
}
|
|
2068
|
+
function evictOldestFiber() {
|
|
2069
|
+
let oldestKey;
|
|
2070
|
+
let oldestTime = Infinity;
|
|
2071
|
+
for (const [key, rec] of fiberRecords) {
|
|
2072
|
+
if (rec.lastSeen < oldestTime) {
|
|
2073
|
+
oldestTime = rec.lastSeen;
|
|
2074
|
+
oldestKey = key;
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
if (oldestKey) fiberRecords.delete(oldestKey);
|
|
2078
|
+
}
|
|
2079
|
+
function walkStats(node, depth, acc) {
|
|
2080
|
+
acc.total += 1;
|
|
2081
|
+
if (depth > acc.maxDepth) acc.maxDepth = depth;
|
|
2082
|
+
if (looksMinified(node.name)) acc.minifiedLike += 1;
|
|
2083
|
+
acc.byName.set(node.name, (acc.byName.get(node.name) ?? 0) + 1);
|
|
2084
|
+
for (const child of node.children) walkStats(child, depth + 1, acc);
|
|
2085
|
+
}
|
|
2086
|
+
function logTreeSnapshot(tree, context) {
|
|
2087
|
+
if (!isRecording() || !tree) return;
|
|
2088
|
+
const stats = { total: 0, minifiedLike: 0, byName: /* @__PURE__ */ new Map(), maxDepth: 0 };
|
|
2089
|
+
walkStats(tree, 0, stats);
|
|
2090
|
+
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(",");
|
|
2091
|
+
treeRecords.push({
|
|
2092
|
+
ts: Date.now(),
|
|
2093
|
+
ctx: context ?? "",
|
|
2094
|
+
rootName: tree.name,
|
|
2095
|
+
totalNodes: stats.total,
|
|
2096
|
+
maxDepth: stats.maxDepth,
|
|
2097
|
+
minifiedLike: stats.minifiedLike,
|
|
2098
|
+
topNames
|
|
2099
|
+
});
|
|
2100
|
+
if (treeRecords.length > MAX_TREE_RECORDS) treeRecords.shift();
|
|
2101
|
+
}
|
|
2102
|
+
var logTreeSummary = logTreeSnapshot;
|
|
2103
|
+
function installConsoleApi() {
|
|
2104
|
+
if (globalThis.__ft) return;
|
|
2105
|
+
const api = {
|
|
2106
|
+
dump() {
|
|
2107
|
+
const fiberRows = serializeFiberRecords();
|
|
2108
|
+
const snapRows = serializeTreeRecords();
|
|
2109
|
+
const minifiedCount = fiberRows.filter((r) => r.looksMinified).length;
|
|
2110
|
+
const elapsedSec = recordingStartedAt === null ? 0 : (Date.now() - recordingStartedAt) / 1e3;
|
|
2111
|
+
const summary = [
|
|
2112
|
+
{
|
|
2113
|
+
metric: "uniqueComponents",
|
|
2114
|
+
value: fiberRecords.size
|
|
2115
|
+
},
|
|
2116
|
+
{
|
|
2117
|
+
metric: "totalFiberEvents",
|
|
2118
|
+
value: totalFiberEvents
|
|
2119
|
+
},
|
|
2120
|
+
{
|
|
2121
|
+
metric: "minifiedLike",
|
|
2122
|
+
value: `${minifiedCount} / ${fiberRecords.size}`
|
|
2123
|
+
},
|
|
2124
|
+
{
|
|
2125
|
+
metric: "snapshots",
|
|
2126
|
+
value: treeRecords.length
|
|
2127
|
+
},
|
|
2128
|
+
{
|
|
2129
|
+
metric: "recordingSec",
|
|
2130
|
+
value: elapsedSec.toFixed(1)
|
|
2131
|
+
}
|
|
2132
|
+
];
|
|
2133
|
+
console.groupCollapsed(
|
|
2134
|
+
`%c[FT debug] dump%c \u2014 ${fiberRecords.size} components, ${totalFiberEvents} events, ${treeRecords.length} snapshots`,
|
|
2135
|
+
"background:#1e293b;color:#7dd3fc;padding:1px 6px;border-radius:3px;font-weight:600;",
|
|
2136
|
+
"color:#94a3b8;"
|
|
2137
|
+
);
|
|
2138
|
+
console.log("Summary:");
|
|
2139
|
+
console.table(summary, ["metric", "value"]);
|
|
2140
|
+
console.log("Fibers \u2014 every observed component (sorted by call count):");
|
|
2141
|
+
console.table(fiberRows, [
|
|
2142
|
+
"name",
|
|
2143
|
+
"rawName",
|
|
2144
|
+
"rawDisplayName",
|
|
2145
|
+
"kind",
|
|
2146
|
+
"fiberTag",
|
|
2147
|
+
"count",
|
|
2148
|
+
"looksMinified",
|
|
2149
|
+
"exampleKey",
|
|
2150
|
+
"contexts",
|
|
2151
|
+
"file",
|
|
2152
|
+
"line",
|
|
2153
|
+
"firstSeenAt",
|
|
2154
|
+
"lastSeenAt",
|
|
2155
|
+
"lastAgoSec"
|
|
2156
|
+
]);
|
|
2157
|
+
console.log("Tree snapshots \u2014 newest last:");
|
|
2158
|
+
console.table(snapRows, [
|
|
2159
|
+
"ts",
|
|
2160
|
+
"ctx",
|
|
2161
|
+
"rootName",
|
|
2162
|
+
"totalNodes",
|
|
2163
|
+
"maxDepth",
|
|
2164
|
+
"minifiedLike",
|
|
2165
|
+
"topNames"
|
|
2166
|
+
]);
|
|
2167
|
+
console.groupEnd();
|
|
2168
|
+
},
|
|
2169
|
+
fibers() {
|
|
2170
|
+
console.table(serializeFiberRecords(), [
|
|
2171
|
+
"name",
|
|
2172
|
+
"rawName",
|
|
2173
|
+
"rawDisplayName",
|
|
2174
|
+
"kind",
|
|
2175
|
+
"fiberTag",
|
|
2176
|
+
"count",
|
|
2177
|
+
"looksMinified",
|
|
2178
|
+
"exampleKey",
|
|
2179
|
+
"contexts",
|
|
2180
|
+
"file",
|
|
2181
|
+
"line",
|
|
2182
|
+
"firstSeenAt",
|
|
2183
|
+
"lastSeenAt",
|
|
2184
|
+
"lastAgoSec"
|
|
2185
|
+
]);
|
|
2186
|
+
},
|
|
2187
|
+
snapshots() {
|
|
2188
|
+
console.table(serializeTreeRecords());
|
|
2189
|
+
},
|
|
2190
|
+
tail(n = 20) {
|
|
2191
|
+
console.table(treeRecords.slice(-n));
|
|
2192
|
+
},
|
|
2193
|
+
clear() {
|
|
2194
|
+
fiberRecords.clear();
|
|
2195
|
+
treeRecords.length = 0;
|
|
2196
|
+
console.info("[FT debug] cleared");
|
|
2197
|
+
},
|
|
2198
|
+
size() {
|
|
2199
|
+
return { fibers: fiberRecords.size, snapshots: treeRecords.length };
|
|
2200
|
+
},
|
|
2201
|
+
export() {
|
|
2202
|
+
return { fibers: serializeFiberRecords(), snapshots: treeRecords.slice() };
|
|
2203
|
+
},
|
|
2204
|
+
download(filename) {
|
|
2205
|
+
const data = api.export();
|
|
2206
|
+
const json = JSON.stringify(data, null, 2);
|
|
2207
|
+
const docRef = globalThis.document;
|
|
2208
|
+
const URLRef = globalThis.URL;
|
|
2209
|
+
if (!docRef || !URLRef || typeof URLRef.createObjectURL !== "function") {
|
|
2210
|
+
console.warn(
|
|
2211
|
+
"[FT debug] download() requires a browser environment \u2014 printing JSON instead"
|
|
2212
|
+
);
|
|
2213
|
+
console.log(json);
|
|
2214
|
+
return;
|
|
2215
|
+
}
|
|
2216
|
+
const blob = new Blob([json], { type: "application/json" });
|
|
2217
|
+
const url = URLRef.createObjectURL(blob);
|
|
2218
|
+
const a = docRef.createElement("a");
|
|
2219
|
+
a.href = url;
|
|
2220
|
+
a.download = filename ?? `flotrace-debug-${Date.now()}.json`;
|
|
2221
|
+
a.click();
|
|
2222
|
+
URLRef.revokeObjectURL(url);
|
|
2223
|
+
}
|
|
2224
|
+
};
|
|
2225
|
+
globalThis.__ft = api;
|
|
2226
|
+
}
|
|
2227
|
+
installConsoleApi();
|
|
2228
|
+
function formatTime(ms) {
|
|
2229
|
+
const d = new Date(ms);
|
|
2230
|
+
const hh = String(d.getHours()).padStart(2, "0");
|
|
2231
|
+
const mm = String(d.getMinutes()).padStart(2, "0");
|
|
2232
|
+
const ss = String(d.getSeconds()).padStart(2, "0");
|
|
2233
|
+
const mss = String(d.getMilliseconds()).padStart(3, "0");
|
|
2234
|
+
return `${hh}:${mm}:${ss}.${mss}`;
|
|
2235
|
+
}
|
|
2236
|
+
function serializeFiberRecords() {
|
|
2237
|
+
const now2 = Date.now();
|
|
2238
|
+
return [...fiberRecords.values()].sort((a, b) => b.count - a.count).map((r) => ({
|
|
2239
|
+
name: r.name,
|
|
2240
|
+
rawName: r.rawName ?? "",
|
|
2241
|
+
rawDisplayName: r.rawDisplayName ?? "",
|
|
2242
|
+
kind: r.kind,
|
|
2243
|
+
fiberTag: r.fiberTag ?? "",
|
|
2244
|
+
count: r.count,
|
|
2245
|
+
looksMinified: r.looksMinified,
|
|
2246
|
+
exampleKey: r.exampleKey ?? "",
|
|
2247
|
+
contexts: [...r.contexts].join(","),
|
|
2248
|
+
file: r.source?.fileName ?? "",
|
|
2249
|
+
line: r.source?.lineNumber ?? "",
|
|
2250
|
+
firstSeenAt: formatTime(r.firstSeen),
|
|
2251
|
+
lastSeenAt: formatTime(r.lastSeen),
|
|
2252
|
+
lastAgoSec: ((now2 - r.lastSeen) / 1e3).toFixed(1)
|
|
2253
|
+
}));
|
|
2254
|
+
}
|
|
2255
|
+
function serializeTreeRecords() {
|
|
2256
|
+
return treeRecords.map((t) => ({
|
|
2257
|
+
ts: formatTime(t.ts),
|
|
2258
|
+
ctx: t.ctx,
|
|
2259
|
+
rootName: t.rootName,
|
|
2260
|
+
totalNodes: t.totalNodes,
|
|
2261
|
+
maxDepth: t.maxDepth,
|
|
2262
|
+
minifiedLike: t.minifiedLike,
|
|
2263
|
+
topNames: t.topNames
|
|
2264
|
+
}));
|
|
2265
|
+
}
|
|
2266
|
+
|
|
1861
2267
|
// src/fiberTreeWalker.ts
|
|
1862
2268
|
var FIBER_TAGS = {
|
|
1863
2269
|
FunctionComponent: 0,
|
|
@@ -2000,6 +2406,7 @@ function debugLog(...args) {
|
|
|
2000
2406
|
}
|
|
2001
2407
|
var fiberRefMap = /* @__PURE__ */ new Map();
|
|
2002
2408
|
function getComponentName2(fiber) {
|
|
2409
|
+
logFiberType(fiber, "getName");
|
|
2003
2410
|
const type = fiber.type;
|
|
2004
2411
|
if (!type) return "Unknown";
|
|
2005
2412
|
if (typeof type === "function") {
|
|
@@ -2125,6 +2532,8 @@ var FRAMEWORK_PATH_PATTERNS = [
|
|
|
2125
2532
|
/formik/
|
|
2126
2533
|
];
|
|
2127
2534
|
function resolveEffectiveSourcePath(fiber) {
|
|
2535
|
+
const jsxSrc = readJsxSourceFromFiber(fiber);
|
|
2536
|
+
if (jsxSrc) return jsxSrc.fileName;
|
|
2128
2537
|
if (fiber._debugSource?.fileName) return fiber._debugSource.fileName;
|
|
2129
2538
|
const ownerHit = walkAncestors(
|
|
2130
2539
|
fiber._debugOwner ?? null,
|
|
@@ -2140,6 +2549,14 @@ function resolveEffectiveSourcePath(fiber) {
|
|
|
2140
2549
|
}
|
|
2141
2550
|
return null;
|
|
2142
2551
|
}
|
|
2552
|
+
function resolveSourceConfidence(fiber, isFramework, isLibrary, precomputedJsxSource) {
|
|
2553
|
+
if (isFramework || isLibrary) return "package";
|
|
2554
|
+
const jsxSrc = precomputedJsxSource ?? readJsxSourceFromFiber(fiber);
|
|
2555
|
+
if (jsxSrc) return "exact";
|
|
2556
|
+
if (fiber._debugSource?.fileName) return "exact";
|
|
2557
|
+
if (resolveEffectiveSourcePath(fiber)) return "inferred";
|
|
2558
|
+
return "unknown";
|
|
2559
|
+
}
|
|
2143
2560
|
var STOP_WALK = /* @__PURE__ */ Symbol("stop-walk");
|
|
2144
2561
|
function walkAncestors(start, maxHops, next, visit) {
|
|
2145
2562
|
let cur = start;
|
|
@@ -2306,13 +2723,7 @@ function walkFiber(fiber, parentId, sharedNameCountMap, depth = 0, inSuspenseFal
|
|
|
2306
2723
|
{ reason: renderReason },
|
|
2307
2724
|
current.actualDuration
|
|
2308
2725
|
);
|
|
2309
|
-
const children = walkFiber(
|
|
2310
|
-
current.child,
|
|
2311
|
-
nodeId,
|
|
2312
|
-
void 0,
|
|
2313
|
-
depth + 1,
|
|
2314
|
-
inSuspenseFallback
|
|
2315
|
-
);
|
|
2726
|
+
const children = walkFiber(current.child, nodeId, void 0, depth + 1, inSuspenseFallback);
|
|
2316
2727
|
const truncatedChildren = children.length > MAX_CHILDREN_PER_NODE ? children.slice(0, MAX_CHILDREN_PER_NODE) : children;
|
|
2317
2728
|
const framework = isFrameworkComponent(current, name) || void 0;
|
|
2318
2729
|
const queryHashes = detectQueryObserverHashes(current);
|
|
@@ -2320,6 +2731,13 @@ function walkFiber(fiber, parentId, sharedNameCountMap, depth = 0, inSuspenseFal
|
|
|
2320
2731
|
const compilerStatus = detectCompilerStatus(current);
|
|
2321
2732
|
const isServerComponent = detectServerComponent(current) || void 0;
|
|
2322
2733
|
const libraryName = framework ? void 0 : detectLibraryName(current, name);
|
|
2734
|
+
const jsxSource = readJsxSourceFromFiber(current);
|
|
2735
|
+
const sourceConfidence = resolveSourceConfidence(
|
|
2736
|
+
current,
|
|
2737
|
+
framework === true,
|
|
2738
|
+
libraryName !== void 0,
|
|
2739
|
+
jsxSource
|
|
2740
|
+
);
|
|
2323
2741
|
const node = {
|
|
2324
2742
|
id: nodeId,
|
|
2325
2743
|
name,
|
|
@@ -2328,8 +2746,8 @@ function walkFiber(fiber, parentId, sharedNameCountMap, depth = 0, inSuspenseFal
|
|
|
2328
2746
|
renderPhase,
|
|
2329
2747
|
renderReason,
|
|
2330
2748
|
renderDuration: current.actualDuration,
|
|
2331
|
-
filePath: current._debugSource?.fileName,
|
|
2332
|
-
lineNumber: current._debugSource?.lineNumber,
|
|
2749
|
+
filePath: jsxSource?.fileName ?? current._debugSource?.fileName,
|
|
2750
|
+
lineNumber: jsxSource?.lineNumber ?? current._debugSource?.lineNumber,
|
|
2333
2751
|
isFramework: framework,
|
|
2334
2752
|
reactKey: resolveEffectiveReactKey(current),
|
|
2335
2753
|
queryHashes,
|
|
@@ -2340,7 +2758,9 @@ function walkFiber(fiber, parentId, sharedNameCountMap, depth = 0, inSuspenseFal
|
|
|
2340
2758
|
compilerStatus,
|
|
2341
2759
|
isServerComponent,
|
|
2342
2760
|
isLibrary: libraryName !== void 0 ? true : void 0,
|
|
2343
|
-
libraryName
|
|
2761
|
+
libraryName,
|
|
2762
|
+
jsxSource,
|
|
2763
|
+
sourceConfidence
|
|
2344
2764
|
};
|
|
2345
2765
|
if (!walkerOptions.pruneSubtree?.(node)) {
|
|
2346
2766
|
nodes.push(node);
|
|
@@ -2414,11 +2834,7 @@ function buildTreeFromFiberRoot(root) {
|
|
|
2414
2834
|
}
|
|
2415
2835
|
fiberRefMap.clear();
|
|
2416
2836
|
const topLevelNodes = walkFiber(rootFiber.child, "");
|
|
2417
|
-
debugLog(
|
|
2418
|
-
"[FloTrace] walkFiber found",
|
|
2419
|
-
topLevelNodes.length,
|
|
2420
|
-
"top-level nodes"
|
|
2421
|
-
);
|
|
2837
|
+
debugLog("[FloTrace] walkFiber found", topLevelNodes.length, "top-level nodes");
|
|
2422
2838
|
if (topLevelNodes.length === 1) {
|
|
2423
2839
|
return topLevelNodes[0];
|
|
2424
2840
|
}
|
|
@@ -2485,9 +2901,7 @@ function findFiberRootFromDOM() {
|
|
|
2485
2901
|
}
|
|
2486
2902
|
}
|
|
2487
2903
|
}
|
|
2488
|
-
console.warn(
|
|
2489
|
-
"[FloTrace] Could not find React fiber root from any DOM element"
|
|
2490
|
-
);
|
|
2904
|
+
console.warn("[FloTrace] Could not find React fiber root from any DOM element");
|
|
2491
2905
|
return null;
|
|
2492
2906
|
} catch (error) {
|
|
2493
2907
|
console.error("[FloTrace] Error finding fiber root from DOM:", error);
|
|
@@ -2549,9 +2963,7 @@ function executeSnapshot(root) {
|
|
|
2549
2963
|
adaptSnapshotInterval(nodeCount);
|
|
2550
2964
|
const client2 = getWebSocketClient();
|
|
2551
2965
|
if (!client2.connected) {
|
|
2552
|
-
console.warn(
|
|
2553
|
-
"[FloTrace] WebSocket not connected, cannot send tree snapshot"
|
|
2554
|
-
);
|
|
2966
|
+
console.warn("[FloTrace] WebSocket not connected, cannot send tree snapshot");
|
|
2555
2967
|
return;
|
|
2556
2968
|
}
|
|
2557
2969
|
const currentFlatTree = flattenTree2(tree);
|
|
@@ -2567,6 +2979,7 @@ function executeSnapshot(root) {
|
|
|
2567
2979
|
"nextInterval:",
|
|
2568
2980
|
snapshotIntervalMs + "ms"
|
|
2569
2981
|
);
|
|
2982
|
+
logTreeSnapshot(tree, `send seq=${snapshotCounter}`);
|
|
2570
2983
|
client2.sendImmediate({
|
|
2571
2984
|
type: "runtime:treeSnapshot",
|
|
2572
2985
|
tree,
|
|
@@ -2587,6 +3000,7 @@ function executeSnapshot(root) {
|
|
|
2587
3000
|
"updated:",
|
|
2588
3001
|
diff.updated.length
|
|
2589
3002
|
);
|
|
3003
|
+
logTreeSummary(tree, `diff seq=${diffSeq}`);
|
|
2590
3004
|
client2.sendImmediate({
|
|
2591
3005
|
type: "runtime:treeDiff",
|
|
2592
3006
|
seq: diffSeq,
|
|
@@ -2727,9 +3141,7 @@ function installFiberTreeWalker(options = {}) {
|
|
|
2727
3141
|
return () => uninstallFiberTreeWalker();
|
|
2728
3142
|
}
|
|
2729
3143
|
if (typeof window === "undefined") {
|
|
2730
|
-
console.warn(
|
|
2731
|
-
"[FloTrace] Not in browser environment, cannot install fiber tree walker"
|
|
2732
|
-
);
|
|
3144
|
+
console.warn("[FloTrace] Not in browser environment, cannot install fiber tree walker");
|
|
2733
3145
|
return () => {
|
|
2734
3146
|
};
|
|
2735
3147
|
}
|
|
@@ -2756,10 +3168,7 @@ function installFiberTreeWalker(options = {}) {
|
|
|
2756
3168
|
try {
|
|
2757
3169
|
originalOnCommitFiberRoot(rendererID, root, priority);
|
|
2758
3170
|
} catch (error) {
|
|
2759
|
-
console.error(
|
|
2760
|
-
"[FloTrace] Error in original onCommitFiberRoot:",
|
|
2761
|
-
error
|
|
2762
|
-
);
|
|
3171
|
+
console.error("[FloTrace] Error in original onCommitFiberRoot:", error);
|
|
2763
3172
|
}
|
|
2764
3173
|
}
|
|
2765
3174
|
if (hookedRendererID === null) {
|
|
@@ -2785,9 +3194,7 @@ function installFiberTreeWalker(options = {}) {
|
|
|
2785
3194
|
scheduleSnapshot(root);
|
|
2786
3195
|
};
|
|
2787
3196
|
activeStrategy = "devtools";
|
|
2788
|
-
console.log(
|
|
2789
|
-
"[FloTrace] Fiber tree walker installed (DevTools hook strategy)"
|
|
2790
|
-
);
|
|
3197
|
+
console.log("[FloTrace] Fiber tree walker installed (DevTools hook strategy)");
|
|
2791
3198
|
setTimeout(() => {
|
|
2792
3199
|
try {
|
|
2793
3200
|
const root = findFiberRootFromDOM();
|
|
@@ -2800,9 +3207,7 @@ function installFiberTreeWalker(options = {}) {
|
|
|
2800
3207
|
}, 100);
|
|
2801
3208
|
} else {
|
|
2802
3209
|
activeStrategy = "dom";
|
|
2803
|
-
console.log(
|
|
2804
|
-
"[FloTrace] Fiber tree walker installed (DOM fallback strategy)"
|
|
2805
|
-
);
|
|
3210
|
+
console.log("[FloTrace] Fiber tree walker installed (DOM fallback strategy)");
|
|
2806
3211
|
setTimeout(() => {
|
|
2807
3212
|
try {
|
|
2808
3213
|
const root = findFiberRootFromDOM();
|
|
@@ -3080,12 +3485,18 @@ function installZustandTracker(stores, client2) {
|
|
|
3080
3485
|
try {
|
|
3081
3486
|
scheduleStoreUpdate(storeName, prevState, newState, client2);
|
|
3082
3487
|
} catch (error) {
|
|
3083
|
-
console.error(
|
|
3488
|
+
console.error(
|
|
3489
|
+
`[FloTrace] Error in Zustand subscribe callback for "${storeName}":`,
|
|
3490
|
+
error
|
|
3491
|
+
);
|
|
3084
3492
|
}
|
|
3085
3493
|
});
|
|
3086
3494
|
activeUnsubscribers.push(unsubscribe);
|
|
3087
3495
|
} catch (error) {
|
|
3088
|
-
console.error(
|
|
3496
|
+
console.error(
|
|
3497
|
+
`[FloTrace] Failed to install tracker for Zustand store "${storeName}":`,
|
|
3498
|
+
error
|
|
3499
|
+
);
|
|
3089
3500
|
}
|
|
3090
3501
|
}
|
|
3091
3502
|
}
|
|
@@ -3128,10 +3539,13 @@ function scheduleStoreUpdate(storeName, prevState, newState, client2) {
|
|
|
3128
3539
|
if (changedKeys.length === 0) return;
|
|
3129
3540
|
const existing = debounceTimers.get(storeName);
|
|
3130
3541
|
if (existing) clearTimeout(existing);
|
|
3131
|
-
debounceTimers.set(
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3542
|
+
debounceTimers.set(
|
|
3543
|
+
storeName,
|
|
3544
|
+
setTimeout(() => {
|
|
3545
|
+
debounceTimers.delete(storeName);
|
|
3546
|
+
sendStoreUpdate(storeName, newState, changedKeys, client2);
|
|
3547
|
+
}, DEBOUNCE_MS)
|
|
3548
|
+
);
|
|
3135
3549
|
}
|
|
3136
3550
|
function sendStoreUpdate(storeName, state, changedKeys, client2) {
|
|
3137
3551
|
try {
|
|
@@ -3648,7 +4062,6 @@ function safeCall(fn, fallback) {
|
|
|
3648
4062
|
|
|
3649
4063
|
// src/valueTraceResolver.ts
|
|
3650
4064
|
var FIBER_TAG_CONTEXT_PROVIDER = 10;
|
|
3651
|
-
var BUDGET_MS = 100;
|
|
3652
4065
|
var SCAN_DEPTH = 3;
|
|
3653
4066
|
var MAX_PROP_CHAIN_DEPTH = 30;
|
|
3654
4067
|
function now() {
|
|
@@ -3695,8 +4108,7 @@ function findReferenceMatchAtTopLevel(target, container) {
|
|
|
3695
4108
|
}
|
|
3696
4109
|
return null;
|
|
3697
4110
|
}
|
|
3698
|
-
function findMatchingPathInObject(target, targetFp, container, currentPath, depth,
|
|
3699
|
-
if (now() > deadline) return null;
|
|
4111
|
+
function findMatchingPathInObject(target, targetFp, container, currentPath, depth, cache) {
|
|
3700
4112
|
if (depth > SCAN_DEPTH) return null;
|
|
3701
4113
|
if (container === null || typeof container !== "object") return null;
|
|
3702
4114
|
const selfMatch = valuesMatch(target, targetFp, container, cache);
|
|
@@ -3706,7 +4118,14 @@ function findMatchingPathInObject(target, targetFp, container, currentPath, dept
|
|
|
3706
4118
|
const child = container[i];
|
|
3707
4119
|
const directMatch = valuesMatch(target, targetFp, child, cache);
|
|
3708
4120
|
if (directMatch) return { path: [...currentPath, String(i)], confidence: directMatch };
|
|
3709
|
-
const nested = findMatchingPathInObject(
|
|
4121
|
+
const nested = findMatchingPathInObject(
|
|
4122
|
+
target,
|
|
4123
|
+
targetFp,
|
|
4124
|
+
child,
|
|
4125
|
+
[...currentPath, String(i)],
|
|
4126
|
+
depth + 1,
|
|
4127
|
+
cache
|
|
4128
|
+
);
|
|
3710
4129
|
if (nested) return nested;
|
|
3711
4130
|
}
|
|
3712
4131
|
} else {
|
|
@@ -3714,7 +4133,14 @@ function findMatchingPathInObject(target, targetFp, container, currentPath, dept
|
|
|
3714
4133
|
const child = container[key];
|
|
3715
4134
|
const directMatch = valuesMatch(target, targetFp, child, cache);
|
|
3716
4135
|
if (directMatch) return { path: [...currentPath, key], confidence: directMatch };
|
|
3717
|
-
const nested = findMatchingPathInObject(
|
|
4136
|
+
const nested = findMatchingPathInObject(
|
|
4137
|
+
target,
|
|
4138
|
+
targetFp,
|
|
4139
|
+
child,
|
|
4140
|
+
[...currentPath, key],
|
|
4141
|
+
depth + 1,
|
|
4142
|
+
cache
|
|
4143
|
+
);
|
|
3718
4144
|
if (nested) return nested;
|
|
3719
4145
|
}
|
|
3720
4146
|
}
|
|
@@ -3781,8 +4207,6 @@ function resolveOriginViaTagOrKeyPath(matchedValue, stateRoot, keyPath) {
|
|
|
3781
4207
|
return findFetchOrigin(matchedValue, { ignoreTTL: true }) ?? findFetchOriginUpKeyPath(stateRoot, keyPath);
|
|
3782
4208
|
}
|
|
3783
4209
|
function resolveValueTrace(input) {
|
|
3784
|
-
const startedAt = now();
|
|
3785
|
-
const deadline = startedAt + BUDGET_MS;
|
|
3786
4210
|
const steps = [];
|
|
3787
4211
|
const base = {
|
|
3788
4212
|
rootNodeId: input.nodeId,
|
|
@@ -3821,7 +4245,12 @@ function resolveValueTrace(input) {
|
|
|
3821
4245
|
nodeId: input.nodeId,
|
|
3822
4246
|
componentName: rootComponentName,
|
|
3823
4247
|
propPath: input.propPath,
|
|
3824
|
-
confidence: "exact"
|
|
4248
|
+
confidence: "exact",
|
|
4249
|
+
// P6: `fiber.memoizedProps[FLOTRACE_SOURCE]` is the JSX call site where
|
|
4250
|
+
// this fiber was created — i.e., the `<Consumer .../>` JSX in the
|
|
4251
|
+
// PARENT's source file. That's exactly the "drilled from `Parent.tsx:42`"
|
|
4252
|
+
// attribution the user wants on the leaf prop step.
|
|
4253
|
+
callSiteOfParentJsx: readJsxSourceFromFiber(fiber)
|
|
3825
4254
|
});
|
|
3826
4255
|
} else if (input.hookPath) {
|
|
3827
4256
|
steps.push({
|
|
@@ -3839,7 +4268,6 @@ function resolveValueTrace(input) {
|
|
|
3839
4268
|
let current = fiber.return;
|
|
3840
4269
|
let hops = 0;
|
|
3841
4270
|
while (current && hops < MAX_PROP_CHAIN_DEPTH) {
|
|
3842
|
-
if (now() > deadline) return { ...base, steps, truncated: true, resolvedAtMs: now() };
|
|
3843
4271
|
if (current.tag !== FIBER_TAG_CONTEXT_PROVIDER) {
|
|
3844
4272
|
const props = current.memoizedProps;
|
|
3845
4273
|
if (props) {
|
|
@@ -3847,7 +4275,7 @@ function resolveValueTrace(input) {
|
|
|
3847
4275
|
let matchPath = refKey !== null ? [refKey] : null;
|
|
3848
4276
|
let matchConfidence = "exact";
|
|
3849
4277
|
if (matchPath === null) {
|
|
3850
|
-
const match = findMatchingPathInObject(rootValue, rootFp, props, [], 0,
|
|
4278
|
+
const match = findMatchingPathInObject(rootValue, rootFp, props, [], 0, fpCache);
|
|
3851
4279
|
if (match) {
|
|
3852
4280
|
matchPath = match.path;
|
|
3853
4281
|
matchConfidence = match.confidence;
|
|
@@ -3862,7 +4290,12 @@ function resolveValueTrace(input) {
|
|
|
3862
4290
|
nodeId: ancestorNodeId,
|
|
3863
4291
|
componentName: ancestorName,
|
|
3864
4292
|
propPath: trailingSubPath.length > 0 ? [...matchPath, ...trailingSubPath] : matchPath,
|
|
3865
|
-
confidence: matchConfidence
|
|
4293
|
+
confidence: matchConfidence,
|
|
4294
|
+
// P6: attribute the parent JSX call site that drilled this
|
|
4295
|
+
// prop. When the user opted into the JSX runtime AND this
|
|
4296
|
+
// ancestor's fiber went through jsxDEV, the consumer sees
|
|
4297
|
+
// "drilled from `Parent.tsx:42:8`" with a click-to-IDE link.
|
|
4298
|
+
callSiteOfParentJsx: readJsxSourceFromFiber(current)
|
|
3866
4299
|
});
|
|
3867
4300
|
}
|
|
3868
4301
|
} else {
|
|
@@ -3911,7 +4344,11 @@ function resolveValueTrace(input) {
|
|
|
3911
4344
|
const contextMatch = findContextMatch(fiber, rootValue, rootFp, fiberToNodeId, fpCache);
|
|
3912
4345
|
if (contextMatch) {
|
|
3913
4346
|
steps.push(contextMatch.step);
|
|
3914
|
-
const providerStoreMatch = findStoreMatch(
|
|
4347
|
+
const providerStoreMatch = findStoreMatch(
|
|
4348
|
+
contextMatch.providerValue,
|
|
4349
|
+
cachedFp(contextMatch.providerValue, fpCache),
|
|
4350
|
+
fpCache
|
|
4351
|
+
);
|
|
3915
4352
|
if (providerStoreMatch) {
|
|
3916
4353
|
steps.push({
|
|
3917
4354
|
kind: "store",
|
|
@@ -3936,7 +4373,7 @@ function resolveValueTrace(input) {
|
|
|
3936
4373
|
}
|
|
3937
4374
|
return { ...base, steps, resolvedAtMs: now() };
|
|
3938
4375
|
}
|
|
3939
|
-
const storeMatch = findStoreMatch(rootValue, rootFp,
|
|
4376
|
+
const storeMatch = findStoreMatch(rootValue, rootFp, fpCache);
|
|
3940
4377
|
if (storeMatch) {
|
|
3941
4378
|
steps.push({
|
|
3942
4379
|
kind: "store",
|
|
@@ -4041,10 +4478,9 @@ function findNearestProvider(consumer, contextObj) {
|
|
|
4041
4478
|
}
|
|
4042
4479
|
return null;
|
|
4043
4480
|
}
|
|
4044
|
-
function findStoreMatch(target, targetFp,
|
|
4481
|
+
function findStoreMatch(target, targetFp, cache) {
|
|
4045
4482
|
for (const [storeName, state] of getZustandSnapshot()) {
|
|
4046
|
-
|
|
4047
|
-
const hit = findMatchingPathInObject(target, targetFp, state, [], 0, deadline, cache);
|
|
4483
|
+
const hit = findMatchingPathInObject(target, targetFp, state, [], 0, cache);
|
|
4048
4484
|
if (hit) {
|
|
4049
4485
|
return {
|
|
4050
4486
|
source: "zustand",
|
|
@@ -4058,8 +4494,7 @@ function findStoreMatch(target, targetFp, deadline, cache) {
|
|
|
4058
4494
|
}
|
|
4059
4495
|
const redux = getReduxSnapshot();
|
|
4060
4496
|
if (redux) {
|
|
4061
|
-
|
|
4062
|
-
const hit = findMatchingPathInObject(target, targetFp, redux, [], 0, deadline, cache);
|
|
4497
|
+
const hit = findMatchingPathInObject(target, targetFp, redux, [], 0, cache);
|
|
4063
4498
|
if (hit) {
|
|
4064
4499
|
return {
|
|
4065
4500
|
source: "redux",
|
|
@@ -4072,8 +4507,7 @@ function findStoreMatch(target, targetFp, deadline, cache) {
|
|
|
4072
4507
|
}
|
|
4073
4508
|
}
|
|
4074
4509
|
for (const [queryHash, entry] of getTanstackSnapshot()) {
|
|
4075
|
-
|
|
4076
|
-
const hit = findMatchingPathInObject(target, targetFp, entry.data, [], 0, deadline, cache);
|
|
4510
|
+
const hit = findMatchingPathInObject(target, targetFp, entry.data, [], 0, cache);
|
|
4077
4511
|
if (hit) {
|
|
4078
4512
|
return {
|
|
4079
4513
|
source: "tanstack-query",
|
|
@@ -4107,13 +4541,22 @@ function detectWebFramework() {
|
|
|
4107
4541
|
}
|
|
4108
4542
|
export {
|
|
4109
4543
|
DEFAULT_CONFIG,
|
|
4544
|
+
FLOTRACE_SOURCE,
|
|
4110
4545
|
FloTraceWebSocketClient,
|
|
4546
|
+
JSX_RUNTIME_ACTIVE_KEY,
|
|
4111
4547
|
buildAncestorChain,
|
|
4548
|
+
clearCallSiteRenders,
|
|
4112
4549
|
clearFetchOriginTags,
|
|
4550
|
+
computeCallSiteId,
|
|
4551
|
+
computeCallSiteMetricsPayload,
|
|
4552
|
+
describeFiberType,
|
|
4553
|
+
detectInlineLiterals,
|
|
4113
4554
|
detectServerComponent,
|
|
4114
4555
|
detectWebFramework,
|
|
4115
4556
|
disposeWebSocketClient,
|
|
4116
4557
|
findFetchOrigin,
|
|
4558
|
+
getCallSiteRenderRate,
|
|
4559
|
+
getCallSiteRenders,
|
|
4117
4560
|
getChangedKeys,
|
|
4118
4561
|
getComponentNameFromFiber,
|
|
4119
4562
|
getCurrentRenderingFiber,
|
|
@@ -4136,9 +4579,15 @@ export {
|
|
|
4136
4579
|
installTanStackQueryTracker,
|
|
4137
4580
|
installTimelineTracker,
|
|
4138
4581
|
installZustandTracker,
|
|
4582
|
+
isJsxRuntimeActive,
|
|
4139
4583
|
isReduxStore,
|
|
4140
4584
|
isTanStackQueryClient,
|
|
4585
|
+
logTreeSnapshot,
|
|
4586
|
+
logTreeSummary,
|
|
4587
|
+
markJsxRuntimeActive,
|
|
4141
4588
|
maybeEmitNextjsContext,
|
|
4589
|
+
normalizeJsxSourcePath,
|
|
4590
|
+
recordCallSiteRender,
|
|
4142
4591
|
recordTimelineEvent,
|
|
4143
4592
|
requestFullSnapshot,
|
|
4144
4593
|
requestTreeSnapshot,
|
|
@@ -4146,6 +4595,8 @@ export {
|
|
|
4146
4595
|
resolveValueTrace,
|
|
4147
4596
|
serializeProps,
|
|
4148
4597
|
serializeValue,
|
|
4598
|
+
setDuplicateKeyEmitter,
|
|
4599
|
+
setFiberDebug,
|
|
4149
4600
|
tagFetchData,
|
|
4150
4601
|
uninstallFiberTreeWalker,
|
|
4151
4602
|
uninstallReduxTracker,
|