@presto1314w/vite-devtools-browser 0.3.3 → 0.3.6
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/LICENSE +21 -21
- package/README.md +135 -322
- package/dist/browser-collector.js +7 -6
- package/dist/browser-frameworks.d.ts +10 -0
- package/dist/browser-frameworks.js +63 -18
- package/dist/browser-session.d.ts +13 -1
- package/dist/browser-session.js +58 -41
- package/dist/browser.d.ts +6 -0
- package/dist/browser.js +39 -0
- package/dist/cli.js +104 -57
- package/dist/client.js +2 -2
- package/dist/daemon.js +27 -8
- package/dist/event-queue.d.ts +10 -1
- package/dist/event-queue.js +52 -2
- package/dist/paths.d.ts +25 -0
- package/dist/paths.js +46 -4
- package/dist/react/devtools.d.ts +10 -0
- package/dist/react/devtools.js +18 -2
- package/dist/react/hook-manager.d.ts +29 -0
- package/dist/react/hook-manager.js +75 -0
- package/dist/react/hook.d.ts +1 -0
- package/dist/react/hook.js +255 -0
- package/dist/react/profiler.d.ts +38 -0
- package/dist/react/profiler.js +195 -0
- package/dist/react/zustand.d.ts +31 -0
- package/dist/react/zustand.js +113 -0
- package/dist/vue/devtools.d.ts +1 -1
- package/dist/vue/devtools.js +41 -3
- package/package.json +21 -15
package/dist/paths.js
CHANGED
|
@@ -1,8 +1,50 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { accessSync, constants } from "node:fs";
|
|
2
|
+
import { homedir, tmpdir } from "node:os";
|
|
2
3
|
import { join } from "node:path";
|
|
3
|
-
const isWindows = process.platform === "win32";
|
|
4
|
-
const
|
|
5
|
-
|
|
4
|
+
export const isWindows = process.platform === "win32";
|
|
5
|
+
export const isLinux = process.platform === "linux";
|
|
6
|
+
/**
|
|
7
|
+
* Sanitize a session name for safe use in file paths and pipe names.
|
|
8
|
+
*/
|
|
9
|
+
export function sanitizeSession(name) {
|
|
10
|
+
return name.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
11
|
+
}
|
|
12
|
+
const session = sanitizeSession(process.env.VITE_BROWSER_SESSION || "default");
|
|
13
|
+
/**
|
|
14
|
+
* Resolve the base directory for vite-browser runtime files.
|
|
15
|
+
*
|
|
16
|
+
* Uses `~/.vite-browser` on all platforms.
|
|
17
|
+
* Falls back to `$TMPDIR/vite-browser-<uid>` when the home directory
|
|
18
|
+
* is not writable (e.g. some CI/container environments).
|
|
19
|
+
*/
|
|
20
|
+
export function resolveSocketDir() {
|
|
21
|
+
try {
|
|
22
|
+
const home = homedir();
|
|
23
|
+
if (home) {
|
|
24
|
+
accessSync(home, constants.W_OK);
|
|
25
|
+
return join(home, ".vite-browser");
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// homedir() can throw, and sandboxed / CI environments may expose a
|
|
30
|
+
// home directory that is present but not writable.
|
|
31
|
+
}
|
|
32
|
+
// Fallback: use tmpdir scoped by uid to avoid collisions
|
|
33
|
+
const uid = process.getuid?.() ?? process.pid;
|
|
34
|
+
return join(tmpdir(), `vite-browser-${uid}`);
|
|
35
|
+
}
|
|
36
|
+
export const socketDir = resolveSocketDir();
|
|
37
|
+
/**
|
|
38
|
+
* Socket path for the daemon.
|
|
39
|
+
*
|
|
40
|
+
* - Windows: uses a named pipe `\\.\pipe\vite-browser-<session>`
|
|
41
|
+
* - Unix: uses a Unix domain socket file in socketDir
|
|
42
|
+
*
|
|
43
|
+
* Note: Unix socket paths have a ~104-char limit on macOS and ~108 on
|
|
44
|
+
* Linux. The `~/.vite-browser/<session>.sock` path is well within
|
|
45
|
+
* that range. The tmpdir fallback may produce longer paths; we keep
|
|
46
|
+
* them short by using numeric uid.
|
|
47
|
+
*/
|
|
6
48
|
export const socketPath = isWindows
|
|
7
49
|
? `\\\\.\\pipe\\vite-browser-${session}`
|
|
8
50
|
: join(socketDir, `${session}.sock`);
|
package/dist/react/devtools.d.ts
CHANGED
|
@@ -12,6 +12,16 @@ export type ReactInspection = {
|
|
|
12
12
|
};
|
|
13
13
|
export declare function snapshot(page: Page): Promise<ReactNode[]>;
|
|
14
14
|
export declare function inspect(page: Page, id: number): Promise<ReactInspection>;
|
|
15
|
+
export declare function getPrimaryRendererInterface(hook: {
|
|
16
|
+
rendererInterfaces?: Map<unknown, unknown> | {
|
|
17
|
+
values?: () => IterableIterator<unknown>;
|
|
18
|
+
};
|
|
19
|
+
} | null | undefined): unknown;
|
|
20
|
+
export declare function findRendererInterfaceForElement(hook: {
|
|
21
|
+
rendererInterfaces?: Map<unknown, unknown> | {
|
|
22
|
+
values?: () => IterableIterator<unknown>;
|
|
23
|
+
};
|
|
24
|
+
} | null | undefined, id: number): unknown;
|
|
15
25
|
export declare function format(nodes: ReactNode[]): string;
|
|
16
26
|
export declare function path(nodes: ReactNode[], id: number): string;
|
|
17
27
|
export declare function typeName(type: number): string;
|
package/dist/react/devtools.js
CHANGED
|
@@ -4,6 +4,21 @@ export async function snapshot(page) {
|
|
|
4
4
|
export async function inspect(page, id) {
|
|
5
5
|
return page.evaluate(inPageInspect, id);
|
|
6
6
|
}
|
|
7
|
+
export function getPrimaryRendererInterface(hook) {
|
|
8
|
+
const values = hook?.rendererInterfaces?.values?.();
|
|
9
|
+
return values?.next().value ?? null;
|
|
10
|
+
}
|
|
11
|
+
export function findRendererInterfaceForElement(hook, id) {
|
|
12
|
+
const values = hook?.rendererInterfaces?.values?.();
|
|
13
|
+
if (!values)
|
|
14
|
+
return null;
|
|
15
|
+
for (const rendererInterface of values) {
|
|
16
|
+
if (rendererInterface?.hasElementWithId?.(id)) {
|
|
17
|
+
return rendererInterface;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
7
22
|
export function format(nodes) {
|
|
8
23
|
const children = new Map();
|
|
9
24
|
for (const n of nodes) {
|
|
@@ -182,7 +197,7 @@ async function inPageSnapshot() {
|
|
|
182
197
|
const hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
|
183
198
|
if (!hook)
|
|
184
199
|
throw new Error("React DevTools hook not installed");
|
|
185
|
-
const ri = hook
|
|
200
|
+
const ri = getPrimaryRendererInterface(hook);
|
|
186
201
|
if (!ri)
|
|
187
202
|
throw new Error("no React renderer attached");
|
|
188
203
|
const batches = await collect(ri);
|
|
@@ -207,7 +222,8 @@ async function inPageSnapshot() {
|
|
|
207
222
|
}
|
|
208
223
|
function inPageInspect(id) {
|
|
209
224
|
const hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
|
210
|
-
const ri = hook
|
|
225
|
+
const ri = findRendererInterfaceForElement(hook, id) ??
|
|
226
|
+
getPrimaryRendererInterface(hook);
|
|
211
227
|
if (!ri)
|
|
212
228
|
throw new Error("no React renderer attached");
|
|
213
229
|
if (!ri.hasElementWithId(id))
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React DevTools Hook Management
|
|
3
|
+
*
|
|
4
|
+
* Provides health checks and injection helpers for the bundled React DevTools hook.
|
|
5
|
+
* This removes the dependency on external browser extensions for React inspection.
|
|
6
|
+
*/
|
|
7
|
+
import type { Page } from "playwright";
|
|
8
|
+
/**
|
|
9
|
+
* Get the hook source code, lazily loaded and cached
|
|
10
|
+
*/
|
|
11
|
+
export declare function getHookSource(): string;
|
|
12
|
+
export interface HookHealthStatus {
|
|
13
|
+
installed: boolean;
|
|
14
|
+
hasRenderers: boolean;
|
|
15
|
+
rendererCount: number;
|
|
16
|
+
hasFiberSupport: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Check the health of the React DevTools hook in the page
|
|
20
|
+
*/
|
|
21
|
+
export declare function checkHookHealth(page: Page): Promise<HookHealthStatus>;
|
|
22
|
+
/**
|
|
23
|
+
* Inject the React DevTools hook into a page if not already present.
|
|
24
|
+
*/
|
|
25
|
+
export declare function injectHook(page: Page): Promise<boolean>;
|
|
26
|
+
/**
|
|
27
|
+
* Format hook health status for CLI output
|
|
28
|
+
*/
|
|
29
|
+
export declare function formatHookHealth(status: HookHealthStatus): string;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React DevTools Hook Management
|
|
3
|
+
*
|
|
4
|
+
* Provides health checks and injection helpers for the bundled React DevTools hook.
|
|
5
|
+
* This removes the dependency on external browser extensions for React inspection.
|
|
6
|
+
*/
|
|
7
|
+
import { readFileSync } from "node:fs";
|
|
8
|
+
import { resolve } from "node:path";
|
|
9
|
+
/** Path to the bundled React DevTools hook */
|
|
10
|
+
const hookPath = resolve(import.meta.dirname, "./hook.js");
|
|
11
|
+
/** Cached hook source code */
|
|
12
|
+
let hookSource = null;
|
|
13
|
+
/**
|
|
14
|
+
* Get the hook source code, lazily loaded and cached
|
|
15
|
+
*/
|
|
16
|
+
export function getHookSource() {
|
|
17
|
+
if (!hookSource) {
|
|
18
|
+
hookSource = readFileSync(hookPath, "utf-8");
|
|
19
|
+
}
|
|
20
|
+
return hookSource;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Check the health of the React DevTools hook in the page
|
|
24
|
+
*/
|
|
25
|
+
export async function checkHookHealth(page) {
|
|
26
|
+
return page.evaluate(inPageCheckHookHealth);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Inject the React DevTools hook into a page if not already present.
|
|
30
|
+
*/
|
|
31
|
+
export async function injectHook(page) {
|
|
32
|
+
const alreadyInstalled = await page.evaluate(() => !!window.__REACT_DEVTOOLS_GLOBAL_HOOK__);
|
|
33
|
+
if (alreadyInstalled)
|
|
34
|
+
return false;
|
|
35
|
+
await page.evaluate(getHookSource());
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Format hook health status for CLI output
|
|
40
|
+
*/
|
|
41
|
+
export function formatHookHealth(status) {
|
|
42
|
+
const lines = ["# React DevTools Hook Status\n"];
|
|
43
|
+
lines.push(`Installed: ${status.installed ? "✅ Yes" : "❌ No"}`);
|
|
44
|
+
lines.push(`Fiber support: ${status.hasFiberSupport ? "✅ Yes" : "❌ No"}`);
|
|
45
|
+
lines.push(`Renderers: ${status.rendererCount}`);
|
|
46
|
+
lines.push(`Has renderers: ${status.hasRenderers ? "✅ Yes" : "❌ No"}`);
|
|
47
|
+
if (!status.installed) {
|
|
48
|
+
lines.push("\n⚠️ Hook not installed. React DevTools features will not work.");
|
|
49
|
+
lines.push("The hook should be injected before React loads.");
|
|
50
|
+
}
|
|
51
|
+
else if (!status.hasRenderers) {
|
|
52
|
+
lines.push("\n⚠️ No React renderers detected.");
|
|
53
|
+
lines.push("This page may not be using React, or React hasn't mounted yet.");
|
|
54
|
+
}
|
|
55
|
+
return lines.join("\n");
|
|
56
|
+
}
|
|
57
|
+
// In-page functions
|
|
58
|
+
function inPageCheckHookHealth() {
|
|
59
|
+
const hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
|
60
|
+
if (!hook) {
|
|
61
|
+
return {
|
|
62
|
+
installed: false,
|
|
63
|
+
hasRenderers: false,
|
|
64
|
+
rendererCount: 0,
|
|
65
|
+
hasFiberSupport: false,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const rendererCount = hook.renderers?.size ?? 0;
|
|
69
|
+
return {
|
|
70
|
+
installed: true,
|
|
71
|
+
hasRenderers: rendererCount > 0,
|
|
72
|
+
rendererCount,
|
|
73
|
+
hasFiberSupport: !!hook.supportsFiber,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal React DevTools Global Hook
|
|
3
|
+
*
|
|
4
|
+
* This is a minimal implementation of the React DevTools global hook interface
|
|
5
|
+
* that allows vite-browser to inspect React component trees without requiring
|
|
6
|
+
* the full React DevTools browser extension.
|
|
7
|
+
*
|
|
8
|
+
* Based on the React DevTools architecture (MIT License)
|
|
9
|
+
* https://github.com/facebook/react/tree/main/packages/react-devtools
|
|
10
|
+
*
|
|
11
|
+
* This implementation provides just enough functionality for:
|
|
12
|
+
* - Component tree inspection
|
|
13
|
+
* - Fiber root tracking
|
|
14
|
+
* - Bridge communication for operations
|
|
15
|
+
*/
|
|
16
|
+
(function installReactDevToolsHook() {
|
|
17
|
+
if (typeof window === "undefined")
|
|
18
|
+
return;
|
|
19
|
+
if (window.__REACT_DEVTOOLS_GLOBAL_HOOK__)
|
|
20
|
+
return;
|
|
21
|
+
let nextRendererId = 1;
|
|
22
|
+
const hook = {
|
|
23
|
+
renderers: new Map(),
|
|
24
|
+
rendererInterfaces: new Map(),
|
|
25
|
+
supportsFiber: true,
|
|
26
|
+
// Called by React when a renderer is injected
|
|
27
|
+
inject(renderer) {
|
|
28
|
+
const id = nextRendererId++;
|
|
29
|
+
hook.renderers.set(id, renderer);
|
|
30
|
+
// Create renderer interface for component inspection
|
|
31
|
+
const rendererInterface = createRendererInterface(renderer);
|
|
32
|
+
hook.rendererInterfaces.set(id, rendererInterface);
|
|
33
|
+
return id;
|
|
34
|
+
},
|
|
35
|
+
// Called by React on fiber root commits
|
|
36
|
+
onCommitFiberRoot(id, root, priorityLevel) {
|
|
37
|
+
const rendererInterface = hook.rendererInterfaces.get(id);
|
|
38
|
+
rendererInterface?.trackFiberRoot(root);
|
|
39
|
+
},
|
|
40
|
+
// Called by React on fiber unmount
|
|
41
|
+
onCommitFiberUnmount(id, fiber) {
|
|
42
|
+
const rendererInterface = hook.rendererInterfaces.get(id);
|
|
43
|
+
rendererInterface?.handleCommitFiberUnmount(fiber);
|
|
44
|
+
},
|
|
45
|
+
// Sub-hooks for advanced features
|
|
46
|
+
sub(event, handler) {
|
|
47
|
+
// Event subscription (not implemented in minimal version)
|
|
48
|
+
},
|
|
49
|
+
// Check if DevTools is installed
|
|
50
|
+
checkDCE(fn) {
|
|
51
|
+
// Dead code elimination check - always pass
|
|
52
|
+
try {
|
|
53
|
+
fn();
|
|
54
|
+
}
|
|
55
|
+
catch (e) {
|
|
56
|
+
// Ignore
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
function createRendererInterface(renderer) {
|
|
61
|
+
const fiberRoots = new Set();
|
|
62
|
+
const fiberToId = new Map();
|
|
63
|
+
const idToFiber = new Map();
|
|
64
|
+
const fiberParents = new Map();
|
|
65
|
+
let nextId = 2;
|
|
66
|
+
return {
|
|
67
|
+
trackFiberRoot(root) {
|
|
68
|
+
if (root && root.current) {
|
|
69
|
+
fiberRoots.add(root);
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
handleCommitFiberUnmount(fiber) {
|
|
73
|
+
pruneFiber(fiber);
|
|
74
|
+
},
|
|
75
|
+
// Flush initial operations to populate component tree
|
|
76
|
+
flushInitialOperations() {
|
|
77
|
+
fiberToId.clear();
|
|
78
|
+
idToFiber.clear();
|
|
79
|
+
fiberParents.clear();
|
|
80
|
+
nextId = 2;
|
|
81
|
+
for (const root of fiberRoots) {
|
|
82
|
+
walkTree(root.current, 1);
|
|
83
|
+
}
|
|
84
|
+
// Send operations via message event
|
|
85
|
+
const operations = buildOperations();
|
|
86
|
+
window.postMessage({
|
|
87
|
+
source: "react-devtools-bridge",
|
|
88
|
+
payload: {
|
|
89
|
+
event: "operations",
|
|
90
|
+
payload: operations,
|
|
91
|
+
},
|
|
92
|
+
}, "*");
|
|
93
|
+
},
|
|
94
|
+
// Check if element exists
|
|
95
|
+
hasElementWithId(id) {
|
|
96
|
+
return idToFiber.has(id);
|
|
97
|
+
},
|
|
98
|
+
// Get display name for element
|
|
99
|
+
getDisplayNameForElementID(id) {
|
|
100
|
+
const fiber = idToFiber.get(id);
|
|
101
|
+
if (!fiber)
|
|
102
|
+
return "Unknown";
|
|
103
|
+
return getDisplayName(fiber);
|
|
104
|
+
},
|
|
105
|
+
// Inspect element details
|
|
106
|
+
inspectElement(requestID, id, path, forceFullData) {
|
|
107
|
+
const fiber = idToFiber.get(id);
|
|
108
|
+
if (!fiber)
|
|
109
|
+
return { type: "not-found", id };
|
|
110
|
+
const data = {
|
|
111
|
+
id,
|
|
112
|
+
type: "full-data",
|
|
113
|
+
value: {
|
|
114
|
+
key: fiber.key,
|
|
115
|
+
props: extractProps(fiber),
|
|
116
|
+
state: extractState(fiber),
|
|
117
|
+
hooks: extractHooks(fiber),
|
|
118
|
+
context: null,
|
|
119
|
+
owners: extractOwners(fiber),
|
|
120
|
+
source: extractSource(fiber),
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
return data;
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
function walkTree(fiber, parentId) {
|
|
127
|
+
if (!fiber)
|
|
128
|
+
return;
|
|
129
|
+
const id = nextId++;
|
|
130
|
+
fiberToId.set(fiber, id);
|
|
131
|
+
idToFiber.set(id, fiber);
|
|
132
|
+
fiberParents.set(fiber, parentId);
|
|
133
|
+
// Walk children
|
|
134
|
+
let child = fiber.child;
|
|
135
|
+
while (child) {
|
|
136
|
+
walkTree(child, id);
|
|
137
|
+
child = child.sibling;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function buildOperations() {
|
|
141
|
+
const strings = [];
|
|
142
|
+
const nodeOps = [];
|
|
143
|
+
// Add fiber nodes
|
|
144
|
+
for (const [id, fiber] of idToFiber) {
|
|
145
|
+
const name = getDisplayName(fiber);
|
|
146
|
+
const nameIdx = addString(name);
|
|
147
|
+
const keyIdx = fiber.key ? addString(String(fiber.key)) : 0;
|
|
148
|
+
const parentId = fiberParents.get(fiber) || 0;
|
|
149
|
+
nodeOps.push(1, id, 0, parentId, 0, nameIdx, keyIdx, 0); // ADD_NODE
|
|
150
|
+
}
|
|
151
|
+
const stringOps = [];
|
|
152
|
+
for (const str of strings) {
|
|
153
|
+
const codePoints = Array.from(str).map(c => c.codePointAt(0));
|
|
154
|
+
stringOps.push(codePoints.length, ...codePoints);
|
|
155
|
+
}
|
|
156
|
+
const ops = [0, 0, stringOps.length, ...stringOps];
|
|
157
|
+
// Add root operation
|
|
158
|
+
ops.push(1, 1, 11, 0, 0, 0, 0); // ADD_ROOT
|
|
159
|
+
ops.push(...nodeOps);
|
|
160
|
+
return ops;
|
|
161
|
+
function addString(str) {
|
|
162
|
+
let idx = strings.indexOf(str);
|
|
163
|
+
if (idx === -1) {
|
|
164
|
+
idx = strings.length;
|
|
165
|
+
strings.push(str);
|
|
166
|
+
}
|
|
167
|
+
return idx + 1; // 1-indexed
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function getDisplayName(fiber) {
|
|
171
|
+
if (!fiber)
|
|
172
|
+
return "Unknown";
|
|
173
|
+
if (fiber.type && fiber.type.displayName)
|
|
174
|
+
return fiber.type.displayName;
|
|
175
|
+
if (fiber.type && fiber.type.name)
|
|
176
|
+
return fiber.type.name;
|
|
177
|
+
if (typeof fiber.type === "string")
|
|
178
|
+
return fiber.type;
|
|
179
|
+
if (fiber.tag === 11)
|
|
180
|
+
return "Root";
|
|
181
|
+
return "Anonymous";
|
|
182
|
+
}
|
|
183
|
+
function extractProps(fiber) {
|
|
184
|
+
if (!fiber.memoizedProps)
|
|
185
|
+
return null;
|
|
186
|
+
const props = { ...fiber.memoizedProps };
|
|
187
|
+
delete props.children; // Don't include children in props
|
|
188
|
+
return { data: props };
|
|
189
|
+
}
|
|
190
|
+
function extractState(fiber) {
|
|
191
|
+
if (!fiber.memoizedState)
|
|
192
|
+
return null;
|
|
193
|
+
return { data: fiber.memoizedState };
|
|
194
|
+
}
|
|
195
|
+
function extractHooks(fiber) {
|
|
196
|
+
if (!fiber.memoizedState)
|
|
197
|
+
return null;
|
|
198
|
+
const hooks = [];
|
|
199
|
+
let hook = fiber.memoizedState;
|
|
200
|
+
let index = 0;
|
|
201
|
+
while (hook) {
|
|
202
|
+
hooks.push({
|
|
203
|
+
id: index++,
|
|
204
|
+
name: "Hook",
|
|
205
|
+
value: hook.memoizedState,
|
|
206
|
+
subHooks: [],
|
|
207
|
+
});
|
|
208
|
+
hook = hook.next;
|
|
209
|
+
}
|
|
210
|
+
return hooks.length > 0 ? { data: hooks } : null;
|
|
211
|
+
}
|
|
212
|
+
function extractOwners(fiber) {
|
|
213
|
+
const owners = [];
|
|
214
|
+
let current = fiber.return;
|
|
215
|
+
while (current) {
|
|
216
|
+
if (current.type) {
|
|
217
|
+
owners.push({
|
|
218
|
+
displayName: getDisplayName(current),
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
current = current.return;
|
|
222
|
+
}
|
|
223
|
+
return owners;
|
|
224
|
+
}
|
|
225
|
+
function extractSource(fiber) {
|
|
226
|
+
if (!fiber._debugSource)
|
|
227
|
+
return null;
|
|
228
|
+
const { fileName, lineNumber, columnNumber } = fiber._debugSource;
|
|
229
|
+
return [null, fileName, lineNumber, columnNumber];
|
|
230
|
+
}
|
|
231
|
+
function pruneFiber(fiber) {
|
|
232
|
+
if (!fiber)
|
|
233
|
+
return;
|
|
234
|
+
const mappedId = fiberToId.get(fiber);
|
|
235
|
+
if (mappedId != null) {
|
|
236
|
+
fiberToId.delete(fiber);
|
|
237
|
+
idToFiber.delete(mappedId);
|
|
238
|
+
fiberParents.delete(fiber);
|
|
239
|
+
}
|
|
240
|
+
let child = fiber.child;
|
|
241
|
+
while (child) {
|
|
242
|
+
pruneFiber(child);
|
|
243
|
+
child = child.sibling;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
// Install the hook
|
|
248
|
+
Object.defineProperty(window, "__REACT_DEVTOOLS_GLOBAL_HOOK__", {
|
|
249
|
+
value: hook,
|
|
250
|
+
writable: false,
|
|
251
|
+
enumerable: false,
|
|
252
|
+
configurable: false,
|
|
253
|
+
});
|
|
254
|
+
})();
|
|
255
|
+
export {};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React commit tracking groundwork.
|
|
3
|
+
*
|
|
4
|
+
* This module records real commit metadata that can be observed from the
|
|
5
|
+
* React DevTools hook without pretending to expose a full Profiler surface.
|
|
6
|
+
*/
|
|
7
|
+
import type { Page } from "playwright";
|
|
8
|
+
export interface RenderInteraction {
|
|
9
|
+
id: number;
|
|
10
|
+
name: string;
|
|
11
|
+
timestamp: number;
|
|
12
|
+
}
|
|
13
|
+
export interface RenderInfo {
|
|
14
|
+
rendererId: number;
|
|
15
|
+
rootName: string;
|
|
16
|
+
phase: "mount" | "update" | "nested-update";
|
|
17
|
+
actualDuration: number | null;
|
|
18
|
+
baseDuration: number | null;
|
|
19
|
+
startTime: number | null;
|
|
20
|
+
commitTime: number;
|
|
21
|
+
fiberCount: number;
|
|
22
|
+
interactions: RenderInteraction[];
|
|
23
|
+
}
|
|
24
|
+
export interface RenderTrigger {
|
|
25
|
+
rendererId: number;
|
|
26
|
+
rootName: string;
|
|
27
|
+
reason: "props" | "state" | "hooks" | "parent" | "context" | "unknown";
|
|
28
|
+
timestamp: number;
|
|
29
|
+
details?: string;
|
|
30
|
+
}
|
|
31
|
+
export declare function installRenderTracking(page: Page): Promise<void>;
|
|
32
|
+
export declare function getRecentRenders(page: Page, limit?: number): Promise<RenderInfo[]>;
|
|
33
|
+
export declare function getRenderTriggers(page: Page, limit?: number): Promise<RenderTrigger[]>;
|
|
34
|
+
export declare function clearRenderHistory(page: Page): Promise<void>;
|
|
35
|
+
export declare function formatDuration(duration: number | null): string;
|
|
36
|
+
export declare function formatRenderInfo(renders: RenderInfo[]): string;
|
|
37
|
+
export declare function formatRenderTriggers(triggers: RenderTrigger[]): string;
|
|
38
|
+
export declare function analyzeSlowRenders(renders: RenderInfo[]): string;
|