@presto1314w/vite-devtools-browser 0.2.0 → 0.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 +43 -9
- package/dist/browser-collector.d.ts +3 -0
- package/dist/browser-collector.js +256 -0
- package/dist/browser-frameworks.d.ts +11 -0
- package/dist/browser-frameworks.js +72 -0
- package/dist/browser-session.d.ts +27 -0
- package/dist/browser-session.js +113 -0
- package/dist/browser-vite.d.ts +25 -0
- package/dist/browser-vite.js +181 -0
- package/dist/browser.d.ts +4 -28
- package/dist/browser.js +70 -444
- package/dist/cli.js +13 -0
- package/dist/correlate.d.ts +2 -1
- package/dist/correlate.js +11 -42
- package/dist/daemon.js +12 -0
- package/dist/diagnose-propagation.d.ts +10 -0
- package/dist/diagnose-propagation.js +58 -0
- package/dist/diagnose.js +28 -5
- package/dist/event-analysis.d.ts +32 -0
- package/dist/event-analysis.js +75 -0
- package/dist/event-queue.d.ts +70 -5
- package/dist/trace.d.ts +15 -0
- package/dist/trace.js +75 -0
- package/package.json +1 -1
package/dist/browser.js
CHANGED
|
@@ -1,29 +1,17 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
2
1
|
import { tmpdir } from "node:os";
|
|
3
|
-
import { join
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { initBrowserEventCollector, readBrowserEvents } from "./browser-collector.js";
|
|
4
|
+
import { detectBrowserFramework, inspectReactTree, inspectSvelteTree, inspectVuePinia, inspectVueRouter, inspectVueTree, } from "./browser-frameworks.js";
|
|
5
|
+
import { closeBrowserSession, createBrowserSessionState, ensureBrowserPage, getCurrentPage as getSessionPage, } from "./browser-session.js";
|
|
6
|
+
import { collectModuleRows, formatHmrTrace, formatModuleGraphSnapshot, formatModuleGraphTrace, formatRuntimeStatus, readOverlayError, readRuntimeSnapshot, } from "./browser-vite.js";
|
|
8
7
|
import * as networkLog from "./network.js";
|
|
9
8
|
import { resolveViaSourceMap } from "./sourcemap.js";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
const installHook = hasReactExtension
|
|
14
|
-
? readFileSync(join(extensionPath, "build", "installHook.js"), "utf-8")
|
|
15
|
-
: null;
|
|
16
|
-
let context = null;
|
|
17
|
-
let page = null;
|
|
18
|
-
let framework = "unknown";
|
|
19
|
-
let extensionModeDisabled = false;
|
|
9
|
+
export { contextUsable, isClosedTargetError } from "./browser-session.js";
|
|
10
|
+
export { formatHmrTrace, formatModuleGraphSnapshot, formatModuleGraphTrace, formatRuntimeStatus, normalizeLimit, } from "./browser-vite.js";
|
|
11
|
+
const session = createBrowserSessionState();
|
|
20
12
|
let eventQueue = null;
|
|
21
|
-
const consoleLogs = [];
|
|
22
13
|
const MAX_LOGS = 200;
|
|
23
14
|
const MAX_HMR_EVENTS = 500;
|
|
24
|
-
let lastReactSnapshot = [];
|
|
25
|
-
const hmrEvents = [];
|
|
26
|
-
let lastModuleGraphUrls = null;
|
|
27
15
|
export function setEventQueue(queue) {
|
|
28
16
|
eventQueue = queue;
|
|
29
17
|
}
|
|
@@ -31,84 +19,19 @@ export function getEventQueue() {
|
|
|
31
19
|
return eventQueue;
|
|
32
20
|
}
|
|
33
21
|
export function getCurrentPage() {
|
|
34
|
-
|
|
35
|
-
return null;
|
|
36
|
-
if (!page || page.isClosed())
|
|
37
|
-
return null;
|
|
38
|
-
return page;
|
|
22
|
+
return getSessionPage(session);
|
|
39
23
|
}
|
|
40
24
|
/**
|
|
41
25
|
* Inject browser-side event collector into the page
|
|
42
26
|
*/
|
|
43
27
|
async function injectEventCollector(currentPage) {
|
|
44
|
-
await currentPage.evaluate(
|
|
45
|
-
if (window.__vb_events)
|
|
46
|
-
return; // already injected
|
|
47
|
-
window.__vb_events = [];
|
|
48
|
-
window.__vb_push = (event) => {
|
|
49
|
-
const q = window.__vb_events;
|
|
50
|
-
q.push(event);
|
|
51
|
-
if (q.length > 1000)
|
|
52
|
-
q.shift();
|
|
53
|
-
};
|
|
54
|
-
// Subscribe to Vite HMR WebSocket
|
|
55
|
-
function attachViteListener() {
|
|
56
|
-
const hot = window.__vite_hot;
|
|
57
|
-
if (hot?.ws) {
|
|
58
|
-
hot.ws.addEventListener('message', (e) => {
|
|
59
|
-
try {
|
|
60
|
-
const data = JSON.parse(e.data);
|
|
61
|
-
window.__vb_push({
|
|
62
|
-
timestamp: Date.now(),
|
|
63
|
-
type: data.type === 'error' ? 'hmr-error' : 'hmr-update',
|
|
64
|
-
payload: data
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
catch { }
|
|
68
|
-
});
|
|
69
|
-
return true;
|
|
70
|
-
}
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
|
-
// Retry if __vite_hot not ready yet
|
|
74
|
-
if (!attachViteListener()) {
|
|
75
|
-
let attempts = 0;
|
|
76
|
-
const timer = setInterval(() => {
|
|
77
|
-
attempts++;
|
|
78
|
-
if (attachViteListener() || attempts >= 50) {
|
|
79
|
-
clearInterval(timer);
|
|
80
|
-
}
|
|
81
|
-
}, 100);
|
|
82
|
-
}
|
|
83
|
-
// Hook window.onerror for runtime errors
|
|
84
|
-
const origOnError = window.onerror;
|
|
85
|
-
window.onerror = (msg, src, line, col, err) => {
|
|
86
|
-
window.__vb_push({
|
|
87
|
-
timestamp: Date.now(),
|
|
88
|
-
type: 'error',
|
|
89
|
-
payload: { message: String(msg), source: src, line, col, stack: err?.stack }
|
|
90
|
-
});
|
|
91
|
-
return origOnError ? origOnError(msg, src, line, col, err) : false;
|
|
92
|
-
};
|
|
93
|
-
// Hook unhandledrejection
|
|
94
|
-
window.addEventListener('unhandledrejection', (e) => {
|
|
95
|
-
window.__vb_push({
|
|
96
|
-
timestamp: Date.now(),
|
|
97
|
-
type: 'error',
|
|
98
|
-
payload: { message: e.reason?.message, stack: e.reason?.stack }
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
});
|
|
28
|
+
await currentPage.evaluate(initBrowserEventCollector);
|
|
102
29
|
}
|
|
103
30
|
/**
|
|
104
31
|
* Flush browser events into daemon event queue
|
|
105
32
|
*/
|
|
106
33
|
export async function flushBrowserEvents(currentPage, queue) {
|
|
107
|
-
const raw = await currentPage.evaluate(
|
|
108
|
-
const events = window.__vb_events ?? [];
|
|
109
|
-
window.__vb_events = []; // clear after flush
|
|
110
|
-
return events;
|
|
111
|
-
});
|
|
34
|
+
const raw = await currentPage.evaluate(readBrowserEvents);
|
|
112
35
|
for (const e of raw) {
|
|
113
36
|
queue.push(e);
|
|
114
37
|
}
|
|
@@ -116,97 +39,28 @@ export async function flushBrowserEvents(currentPage, queue) {
|
|
|
116
39
|
export async function open(url) {
|
|
117
40
|
const currentPage = await ensurePage();
|
|
118
41
|
if (url) {
|
|
119
|
-
await currentPage.goto(url, { waitUntil: "domcontentloaded" });
|
|
120
|
-
await injectEventCollector(currentPage);
|
|
121
|
-
await detectFramework();
|
|
42
|
+
await navigateAndRefreshContext(currentPage, () => currentPage.goto(url, { waitUntil: "domcontentloaded" }), true);
|
|
122
43
|
}
|
|
123
44
|
}
|
|
124
45
|
export async function cookies(cookies, domain) {
|
|
125
|
-
if (!context)
|
|
46
|
+
if (!session.context)
|
|
126
47
|
throw new Error("browser not open");
|
|
127
|
-
await context.addCookies(cookies.map((c) => ({ name: c.name, value: c.value, domain, path: "/" })));
|
|
48
|
+
await session.context.addCookies(cookies.map((c) => ({ name: c.name, value: c.value, domain, path: "/" })));
|
|
128
49
|
return cookies.length;
|
|
129
50
|
}
|
|
130
51
|
export async function close() {
|
|
131
|
-
await
|
|
132
|
-
context = null;
|
|
133
|
-
page = null;
|
|
134
|
-
framework = "unknown";
|
|
135
|
-
consoleLogs.length = 0;
|
|
136
|
-
hmrEvents.length = 0;
|
|
137
|
-
lastModuleGraphUrls = null;
|
|
52
|
+
await closeBrowserSession(session);
|
|
138
53
|
networkLog.clear();
|
|
139
|
-
lastReactSnapshot = [];
|
|
140
54
|
}
|
|
141
55
|
async function ensurePage() {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
}
|
|
146
|
-
if (!context)
|
|
147
|
-
throw new Error("browser not open");
|
|
148
|
-
if (!page || page.isClosed()) {
|
|
149
|
-
try {
|
|
150
|
-
page = context.pages()[0] ?? (await context.newPage());
|
|
151
|
-
}
|
|
152
|
-
catch (error) {
|
|
153
|
-
if (!isClosedTargetError(error))
|
|
154
|
-
throw error;
|
|
155
|
-
await close();
|
|
156
|
-
extensionModeDisabled = true;
|
|
157
|
-
context = await launch();
|
|
158
|
-
page = context.pages()[0] ?? (await context.newPage());
|
|
159
|
-
}
|
|
160
|
-
attachListeners(page);
|
|
161
|
-
networkLog.attach(page);
|
|
162
|
-
}
|
|
163
|
-
return page;
|
|
164
|
-
}
|
|
165
|
-
export function contextUsable(current) {
|
|
166
|
-
if (!current)
|
|
167
|
-
return false;
|
|
168
|
-
try {
|
|
169
|
-
current.pages();
|
|
170
|
-
return true;
|
|
171
|
-
}
|
|
172
|
-
catch {
|
|
173
|
-
return false;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
export function isClosedTargetError(error) {
|
|
177
|
-
if (!(error instanceof Error))
|
|
178
|
-
return false;
|
|
179
|
-
return /Target page, context or browser has been closed/i.test(error.message);
|
|
180
|
-
}
|
|
181
|
-
async function launch() {
|
|
182
|
-
if (hasReactExtension && installHook && !extensionModeDisabled) {
|
|
183
|
-
try {
|
|
184
|
-
const ctx = await chromium.launchPersistentContext("", {
|
|
185
|
-
headless: false,
|
|
186
|
-
viewport: { width: 1280, height: 720 },
|
|
187
|
-
args: [
|
|
188
|
-
`--disable-extensions-except=${extensionPath}`,
|
|
189
|
-
`--load-extension=${extensionPath}`,
|
|
190
|
-
"--auto-open-devtools-for-tabs",
|
|
191
|
-
],
|
|
192
|
-
});
|
|
193
|
-
await ctx.waitForEvent("serviceworker").catch(() => { });
|
|
194
|
-
await ctx.addInitScript(installHook);
|
|
195
|
-
return ctx;
|
|
196
|
-
}
|
|
197
|
-
catch {
|
|
198
|
-
extensionModeDisabled = true;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
const browser = await chromium.launch({
|
|
202
|
-
headless: false,
|
|
203
|
-
args: ["--auto-open-devtools-for-tabs"],
|
|
56
|
+
return ensureBrowserPage(session, (currentPage) => {
|
|
57
|
+
attachListeners(currentPage);
|
|
58
|
+
networkLog.attach(currentPage);
|
|
204
59
|
});
|
|
205
|
-
return browser.newContext({ viewport: { width: 1280, height: 720 } });
|
|
206
60
|
}
|
|
207
61
|
function attachListeners(currentPage) {
|
|
208
62
|
currentPage.on("console", (msg) => {
|
|
209
|
-
recordConsoleMessage(consoleLogs, hmrEvents, msg.type(), msg.text());
|
|
63
|
+
recordConsoleMessage(session.consoleLogs, session.hmrEvents, msg.type(), msg.text());
|
|
210
64
|
});
|
|
211
65
|
}
|
|
212
66
|
export function recordConsoleMessage(logs, events, type, message, maxLogs = MAX_LOGS, maxEvents = MAX_HMR_EVENTS) {
|
|
@@ -247,108 +101,9 @@ export function parseViteLog(message) {
|
|
|
247
101
|
event.path = hotUpdateMatch[1].trim();
|
|
248
102
|
return event;
|
|
249
103
|
}
|
|
250
|
-
export function normalizeLimit(limit, fallback, max) {
|
|
251
|
-
if (!Number.isFinite(limit) || limit <= 0)
|
|
252
|
-
return fallback;
|
|
253
|
-
return Math.min(limit, max);
|
|
254
|
-
}
|
|
255
|
-
export function formatRuntimeStatus(runtime, currentFramework, events) {
|
|
256
|
-
const output = [];
|
|
257
|
-
output.push("# Vite Runtime");
|
|
258
|
-
output.push(`URL: ${runtime.url}`);
|
|
259
|
-
output.push(`Framework: ${currentFramework}`);
|
|
260
|
-
output.push(`Vite Client: ${runtime.hasViteClient ? "loaded" : "not detected"}`);
|
|
261
|
-
output.push(`HMR Socket: ${runtime.wsState}`);
|
|
262
|
-
output.push(`Error Overlay: ${runtime.hasErrorOverlay ? "present" : "none"}`);
|
|
263
|
-
output.push(`Tracked HMR Events: ${events.length}`);
|
|
264
|
-
const last = events[events.length - 1];
|
|
265
|
-
if (last) {
|
|
266
|
-
output.push(`Last HMR Event: ${new Date(last.timestamp).toLocaleTimeString()} [${last.type}] ${last.message}`);
|
|
267
|
-
}
|
|
268
|
-
return output.join("\n");
|
|
269
|
-
}
|
|
270
|
-
export function formatHmrTrace(mode, events, limit) {
|
|
271
|
-
if (events.length === 0)
|
|
272
|
-
return "No HMR updates";
|
|
273
|
-
const safeLimit = normalizeLimit(limit, 20, 200);
|
|
274
|
-
const recent = events.slice(-safeLimit);
|
|
275
|
-
if (mode === "summary") {
|
|
276
|
-
const counts = recent.reduce((acc, event) => {
|
|
277
|
-
acc[event.type] = (acc[event.type] ?? 0) + 1;
|
|
278
|
-
return acc;
|
|
279
|
-
}, {});
|
|
280
|
-
const lines = ["# HMR Summary"];
|
|
281
|
-
lines.push(`Events considered: ${recent.length}`);
|
|
282
|
-
lines.push(`Counts: ${Object.entries(counts)
|
|
283
|
-
.map(([k, v]) => `${k}=${v}`)
|
|
284
|
-
.join(", ")}`);
|
|
285
|
-
const last = recent[recent.length - 1];
|
|
286
|
-
lines.push(`Last: ${new Date(last.timestamp).toLocaleTimeString()} [${last.type}] ${last.path ?? last.message}`);
|
|
287
|
-
lines.push("\nUse `vite-browser vite hmr trace --limit <n>` for timeline details.");
|
|
288
|
-
return lines.join("\n");
|
|
289
|
-
}
|
|
290
|
-
return [
|
|
291
|
-
"# HMR Trace",
|
|
292
|
-
...recent.map((event) => {
|
|
293
|
-
const detail = event.path ? `${event.path}` : event.message;
|
|
294
|
-
return `[${new Date(event.timestamp).toLocaleTimeString()}] ${event.type} ${detail}`;
|
|
295
|
-
}),
|
|
296
|
-
].join("\n");
|
|
297
|
-
}
|
|
298
|
-
export function formatModuleGraphSnapshot(rows, filter, limit = 200) {
|
|
299
|
-
const normalizedFilter = filter?.trim().toLowerCase();
|
|
300
|
-
const safeLimit = normalizeLimit(limit, 200, 500);
|
|
301
|
-
const filtered = rows.filter((row) => normalizedFilter ? row.url.toLowerCase().includes(normalizedFilter) : true);
|
|
302
|
-
const limited = filtered.slice(0, safeLimit);
|
|
303
|
-
if (limited.length === 0)
|
|
304
|
-
return "No module resources found";
|
|
305
|
-
const lines = [];
|
|
306
|
-
lines.push("# Vite Module Graph (loaded resources)");
|
|
307
|
-
lines.push(`Total: ${filtered.length}${filtered.length > limited.length ? ` (showing ${limited.length})` : ""}`);
|
|
308
|
-
lines.push("# idx initiator ms url");
|
|
309
|
-
lines.push("");
|
|
310
|
-
limited.forEach((row, idx) => {
|
|
311
|
-
lines.push(`${idx} ${row.initiator} ${row.durationMs}ms ${row.url}`);
|
|
312
|
-
});
|
|
313
|
-
return lines.join("\n");
|
|
314
|
-
}
|
|
315
|
-
export function formatModuleGraphTrace(currentUrls, previousUrls, filter, limit = 200) {
|
|
316
|
-
if (!previousUrls) {
|
|
317
|
-
return "No module-graph baseline. Captured current snapshot; run `vite module-graph trace` again.";
|
|
318
|
-
}
|
|
319
|
-
const currentSet = new Set(currentUrls);
|
|
320
|
-
const added = currentUrls.filter((url) => !previousUrls.has(url));
|
|
321
|
-
const removed = [...previousUrls].filter((url) => !currentSet.has(url));
|
|
322
|
-
const normalizedFilter = filter?.trim().toLowerCase();
|
|
323
|
-
const safeLimit = normalizeLimit(limit, 200, 500);
|
|
324
|
-
const addedFiltered = normalizedFilter
|
|
325
|
-
? added.filter((url) => url.toLowerCase().includes(normalizedFilter))
|
|
326
|
-
: added;
|
|
327
|
-
const removedFiltered = normalizedFilter
|
|
328
|
-
? removed.filter((url) => url.toLowerCase().includes(normalizedFilter))
|
|
329
|
-
: removed;
|
|
330
|
-
const lines = [];
|
|
331
|
-
lines.push("# Vite Module Graph Trace");
|
|
332
|
-
lines.push(`Added: ${addedFiltered.length}, Removed: ${removedFiltered.length}`);
|
|
333
|
-
lines.push("");
|
|
334
|
-
lines.push("## Added");
|
|
335
|
-
if (addedFiltered.length === 0)
|
|
336
|
-
lines.push("(none)");
|
|
337
|
-
else
|
|
338
|
-
addedFiltered.slice(0, safeLimit).forEach((url) => lines.push(`+ ${url}`));
|
|
339
|
-
lines.push("");
|
|
340
|
-
lines.push("## Removed");
|
|
341
|
-
if (removedFiltered.length === 0)
|
|
342
|
-
lines.push("(none)");
|
|
343
|
-
else
|
|
344
|
-
removedFiltered.slice(0, safeLimit).forEach((url) => lines.push(`- ${url}`));
|
|
345
|
-
return lines.join("\n");
|
|
346
|
-
}
|
|
347
104
|
export async function goto(url) {
|
|
348
105
|
const currentPage = await ensurePage();
|
|
349
|
-
await currentPage.goto(url, { waitUntil: "domcontentloaded" });
|
|
350
|
-
await injectEventCollector(currentPage);
|
|
351
|
-
await detectFramework();
|
|
106
|
+
await navigateAndRefreshContext(currentPage, () => currentPage.goto(url, { waitUntil: "domcontentloaded" }), true);
|
|
352
107
|
return currentPage.url();
|
|
353
108
|
}
|
|
354
109
|
export async function back() {
|
|
@@ -357,90 +112,33 @@ export async function back() {
|
|
|
357
112
|
}
|
|
358
113
|
export async function reload() {
|
|
359
114
|
const currentPage = await ensurePage();
|
|
360
|
-
await currentPage.reload({ waitUntil: "domcontentloaded" });
|
|
115
|
+
await navigateAndRefreshContext(currentPage, () => currentPage.reload({ waitUntil: "domcontentloaded" }));
|
|
361
116
|
return currentPage.url();
|
|
362
117
|
}
|
|
363
118
|
export async function detectFramework() {
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
const version = window.__VUE__?.version || "unknown";
|
|
369
|
-
return `vue@${version}`;
|
|
370
|
-
}
|
|
371
|
-
const reactHook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
|
372
|
-
if (reactHook || window.React || document.querySelector("[data-reactroot]")) {
|
|
373
|
-
const renderers = reactHook?.renderers;
|
|
374
|
-
const firstRenderer = renderers ? renderers.values().next().value : null;
|
|
375
|
-
const version = firstRenderer?.version || window.React?.version || "unknown";
|
|
376
|
-
return `react@${version}`;
|
|
377
|
-
}
|
|
378
|
-
if (window.__SVELTE__ ||
|
|
379
|
-
window.__svelte ||
|
|
380
|
-
window.__SVELTE_DEVTOOLS_GLOBAL_HOOK__) {
|
|
381
|
-
const version = window.__SVELTE__?.VERSION ||
|
|
382
|
-
window.__svelte?.version ||
|
|
383
|
-
"unknown";
|
|
384
|
-
return `svelte@${version}`;
|
|
385
|
-
}
|
|
386
|
-
return "unknown";
|
|
387
|
-
});
|
|
388
|
-
if (detected.startsWith("vue"))
|
|
389
|
-
framework = "vue";
|
|
390
|
-
else if (detected.startsWith("react"))
|
|
391
|
-
framework = "react";
|
|
392
|
-
else if (detected.startsWith("svelte"))
|
|
393
|
-
framework = "svelte";
|
|
394
|
-
else
|
|
395
|
-
framework = "unknown";
|
|
396
|
-
return detected;
|
|
119
|
+
const currentPage = requireCurrentPage();
|
|
120
|
+
const result = await detectBrowserFramework(currentPage);
|
|
121
|
+
session.framework = result.framework;
|
|
122
|
+
return result.detected;
|
|
397
123
|
}
|
|
398
124
|
export async function vueTree(id) {
|
|
399
|
-
|
|
400
|
-
throw new Error("browser not open");
|
|
401
|
-
return id ? vueDevtools.getComponentDetails(page, id) : vueDevtools.getComponentTree(page);
|
|
125
|
+
return inspectVueTree(requireCurrentPage(), id);
|
|
402
126
|
}
|
|
403
127
|
export async function vuePinia(store) {
|
|
404
|
-
|
|
405
|
-
throw new Error("browser not open");
|
|
406
|
-
return vueDevtools.getPiniaStores(page, store);
|
|
128
|
+
return inspectVuePinia(requireCurrentPage(), store);
|
|
407
129
|
}
|
|
408
130
|
export async function vueRouter() {
|
|
409
|
-
|
|
410
|
-
throw new Error("browser not open");
|
|
411
|
-
return vueDevtools.getRouterInfo(page);
|
|
131
|
+
return inspectVueRouter(requireCurrentPage());
|
|
412
132
|
}
|
|
413
133
|
export async function reactTree(id) {
|
|
414
|
-
|
|
415
|
-
throw new Error("browser not open");
|
|
416
|
-
if (!id) {
|
|
417
|
-
lastReactSnapshot = await reactDevtools.snapshot(page);
|
|
418
|
-
return reactDevtools.format(lastReactSnapshot);
|
|
419
|
-
}
|
|
420
|
-
const parsed = Number.parseInt(id, 10);
|
|
421
|
-
if (!Number.isFinite(parsed))
|
|
422
|
-
throw new Error("react component id must be a number");
|
|
423
|
-
const inspected = await reactDevtools.inspect(page, parsed);
|
|
424
|
-
const lines = [];
|
|
425
|
-
const componentPath = reactDevtools.path(lastReactSnapshot, parsed);
|
|
426
|
-
if (componentPath)
|
|
427
|
-
lines.push(`path: ${componentPath}`);
|
|
428
|
-
lines.push(inspected.text);
|
|
429
|
-
if (inspected.source) {
|
|
430
|
-
const [file, line, column] = inspected.source;
|
|
431
|
-
lines.push(`source: ${file}:${line}:${column}`);
|
|
432
|
-
}
|
|
433
|
-
return lines.join("\n");
|
|
134
|
+
return inspectReactTree(session, requireCurrentPage(), id);
|
|
434
135
|
}
|
|
435
136
|
export async function svelteTree(id) {
|
|
436
|
-
|
|
437
|
-
throw new Error("browser not open");
|
|
438
|
-
return id ? svelteDevtools.getComponentDetails(page, id) : svelteDevtools.getComponentTree(page);
|
|
137
|
+
return inspectSvelteTree(requireCurrentPage(), id);
|
|
439
138
|
}
|
|
440
139
|
export async function viteRestart() {
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
return page.evaluate(async () => {
|
|
140
|
+
const currentPage = requireCurrentPage();
|
|
141
|
+
return currentPage.evaluate(async () => {
|
|
444
142
|
try {
|
|
445
143
|
const response = await fetch("/__vite_restart", { method: "POST" });
|
|
446
144
|
return response.ok ? "restarted" : "restart endpoint not available";
|
|
@@ -451,52 +149,22 @@ export async function viteRestart() {
|
|
|
451
149
|
});
|
|
452
150
|
}
|
|
453
151
|
export async function viteHMR() {
|
|
454
|
-
|
|
455
|
-
throw new Error("browser not open");
|
|
152
|
+
requireCurrentPage();
|
|
456
153
|
return viteHMRTrace("summary", 20);
|
|
457
154
|
}
|
|
458
155
|
export async function viteRuntimeStatus() {
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
const findViteClient = () => {
|
|
463
|
-
const scripts = Array.from(document.querySelectorAll("script[src]"));
|
|
464
|
-
return scripts.some((script) => script.getAttribute("src")?.includes("/@vite/client"));
|
|
465
|
-
};
|
|
466
|
-
const wsStateName = (wsState) => {
|
|
467
|
-
if (wsState == null)
|
|
468
|
-
return "unknown";
|
|
469
|
-
if (wsState === 0)
|
|
470
|
-
return "connecting";
|
|
471
|
-
if (wsState === 1)
|
|
472
|
-
return "open";
|
|
473
|
-
if (wsState === 2)
|
|
474
|
-
return "closing";
|
|
475
|
-
if (wsState === 3)
|
|
476
|
-
return "closed";
|
|
477
|
-
return "unknown";
|
|
478
|
-
};
|
|
479
|
-
const hot = window.__vite_hot;
|
|
480
|
-
const ws = hot?.ws || hot?.socket;
|
|
481
|
-
return {
|
|
482
|
-
url: location.href,
|
|
483
|
-
hasViteClient: findViteClient(),
|
|
484
|
-
wsState: wsStateName(ws?.readyState),
|
|
485
|
-
hasErrorOverlay: Boolean(document.querySelector("vite-error-overlay")),
|
|
486
|
-
timestamp: Date.now(),
|
|
487
|
-
};
|
|
488
|
-
});
|
|
489
|
-
return formatRuntimeStatus(runtime, framework, hmrEvents);
|
|
156
|
+
const currentPage = requireCurrentPage();
|
|
157
|
+
const runtime = await readRuntimeSnapshot(currentPage);
|
|
158
|
+
return formatRuntimeStatus(runtime, session.framework, session.hmrEvents);
|
|
490
159
|
}
|
|
491
160
|
export async function viteHMRTrace(mode, limit = 20) {
|
|
492
|
-
|
|
493
|
-
throw new Error("browser not open");
|
|
161
|
+
const currentPage = requireCurrentPage();
|
|
494
162
|
if (mode === "clear") {
|
|
495
|
-
hmrEvents.length = 0;
|
|
163
|
+
session.hmrEvents.length = 0;
|
|
496
164
|
return "cleared HMR trace";
|
|
497
165
|
}
|
|
498
|
-
if (hmrEvents.length === 0) {
|
|
499
|
-
const fallback = await
|
|
166
|
+
if (session.hmrEvents.length === 0) {
|
|
167
|
+
const fallback = await currentPage.evaluate(() => {
|
|
500
168
|
const updates = window.__vite_hmr_updates || [];
|
|
501
169
|
return updates.slice(-20).map((u) => ({
|
|
502
170
|
timestamp: u.timestamp ?? Date.now(),
|
|
@@ -506,108 +174,53 @@ export async function viteHMRTrace(mode, limit = 20) {
|
|
|
506
174
|
}));
|
|
507
175
|
});
|
|
508
176
|
if (fallback.length > 0)
|
|
509
|
-
hmrEvents.push(...fallback);
|
|
177
|
+
session.hmrEvents.push(...fallback);
|
|
510
178
|
}
|
|
511
|
-
if (hmrEvents.length === 0)
|
|
179
|
+
if (session.hmrEvents.length === 0)
|
|
512
180
|
return "No HMR updates";
|
|
513
|
-
return formatHmrTrace(mode, hmrEvents, limit);
|
|
181
|
+
return formatHmrTrace(mode, session.hmrEvents, limit);
|
|
514
182
|
}
|
|
515
183
|
export async function viteModuleGraph(filter, limit = 200, mode = "snapshot") {
|
|
516
|
-
|
|
517
|
-
throw new Error("browser not open");
|
|
184
|
+
const currentPage = requireCurrentPage();
|
|
518
185
|
if (mode === "clear") {
|
|
519
|
-
lastModuleGraphUrls = null;
|
|
186
|
+
session.lastModuleGraphUrls = null;
|
|
520
187
|
return "cleared module-graph baseline";
|
|
521
188
|
}
|
|
522
|
-
const moduleRows = await collectModuleRows(
|
|
189
|
+
const moduleRows = await collectModuleRows(currentPage);
|
|
523
190
|
const currentUrls = moduleRows.map((row) => row.url);
|
|
524
|
-
const previousUrls = lastModuleGraphUrls ? new Set(lastModuleGraphUrls) : null;
|
|
525
|
-
lastModuleGraphUrls = [...currentUrls];
|
|
191
|
+
const previousUrls = session.lastModuleGraphUrls ? new Set(session.lastModuleGraphUrls) : null;
|
|
192
|
+
session.lastModuleGraphUrls = [...currentUrls];
|
|
526
193
|
if (mode === "trace") {
|
|
527
194
|
return formatModuleGraphTrace(currentUrls, previousUrls, filter, limit);
|
|
528
195
|
}
|
|
529
196
|
return formatModuleGraphSnapshot(moduleRows, filter, limit);
|
|
530
197
|
}
|
|
531
|
-
async function collectModuleRows(page) {
|
|
532
|
-
return page.evaluate(() => {
|
|
533
|
-
const isLikelyModuleUrl = (url) => {
|
|
534
|
-
if (!url)
|
|
535
|
-
return false;
|
|
536
|
-
if (url.includes("/@vite/"))
|
|
537
|
-
return true;
|
|
538
|
-
if (url.includes("/@id/"))
|
|
539
|
-
return true;
|
|
540
|
-
if (url.includes("/src/"))
|
|
541
|
-
return true;
|
|
542
|
-
if (url.includes("/node_modules/"))
|
|
543
|
-
return true;
|
|
544
|
-
return /\.(mjs|cjs|js|jsx|ts|tsx|vue|css)(\?|$)/.test(url);
|
|
545
|
-
};
|
|
546
|
-
const scripts = Array.from(document.querySelectorAll("script[src]")).map((node) => node.src);
|
|
547
|
-
const resources = performance
|
|
548
|
-
.getEntriesByType("resource")
|
|
549
|
-
.map((entry) => {
|
|
550
|
-
const item = entry;
|
|
551
|
-
return {
|
|
552
|
-
url: item.name,
|
|
553
|
-
initiator: item.initiatorType || "unknown",
|
|
554
|
-
durationMs: Number(item.duration.toFixed(1)),
|
|
555
|
-
};
|
|
556
|
-
})
|
|
557
|
-
.filter((entry) => isLikelyModuleUrl(entry.url));
|
|
558
|
-
const all = [
|
|
559
|
-
...scripts
|
|
560
|
-
.filter((url) => isLikelyModuleUrl(url))
|
|
561
|
-
.map((url) => ({ url, initiator: "script-tag", durationMs: 0 })),
|
|
562
|
-
...resources,
|
|
563
|
-
];
|
|
564
|
-
const seen = new Set();
|
|
565
|
-
const unique = [];
|
|
566
|
-
for (const row of all) {
|
|
567
|
-
if (seen.has(row.url))
|
|
568
|
-
continue;
|
|
569
|
-
seen.add(row.url);
|
|
570
|
-
unique.push(row);
|
|
571
|
-
}
|
|
572
|
-
return unique;
|
|
573
|
-
});
|
|
574
|
-
}
|
|
575
198
|
export async function errors(mapped = false, inlineSource = false) {
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
const errorInfo = await page.evaluate(() => {
|
|
579
|
-
const overlay = document.querySelector("vite-error-overlay");
|
|
580
|
-
if (!overlay || !overlay.shadowRoot)
|
|
581
|
-
return null;
|
|
582
|
-
const message = overlay.shadowRoot.querySelector(".message")?.textContent?.trim();
|
|
583
|
-
const stack = overlay.shadowRoot.querySelector(".stack")?.textContent?.trim();
|
|
584
|
-
return { message, stack };
|
|
585
|
-
});
|
|
199
|
+
const currentPage = requireCurrentPage();
|
|
200
|
+
const errorInfo = await readOverlayError(currentPage);
|
|
586
201
|
if (!errorInfo)
|
|
587
202
|
return "no errors";
|
|
588
203
|
const raw = `${errorInfo.message ?? "Vite error"}\n\n${errorInfo.stack ?? ""}`.trim();
|
|
589
204
|
if (!mapped)
|
|
590
205
|
return raw;
|
|
591
|
-
const origin = new URL(
|
|
206
|
+
const origin = new URL(currentPage.url()).origin;
|
|
592
207
|
const mappedStack = await mapStackTrace(raw, origin, inlineSource);
|
|
593
208
|
return mappedStack;
|
|
594
209
|
}
|
|
595
210
|
export async function logs() {
|
|
596
|
-
if (consoleLogs.length === 0)
|
|
211
|
+
if (session.consoleLogs.length === 0)
|
|
597
212
|
return "no logs";
|
|
598
|
-
return consoleLogs.slice(-50).join("\n");
|
|
213
|
+
return session.consoleLogs.slice(-50).join("\n");
|
|
599
214
|
}
|
|
600
215
|
export async function screenshot() {
|
|
601
|
-
|
|
602
|
-
throw new Error("browser not open");
|
|
216
|
+
const currentPage = requireCurrentPage();
|
|
603
217
|
const path = join(tmpdir(), `vite-browser-${Date.now()}.png`);
|
|
604
|
-
await
|
|
218
|
+
await currentPage.screenshot({ path, fullPage: true });
|
|
605
219
|
return path;
|
|
606
220
|
}
|
|
607
221
|
export async function evaluate(script) {
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
const result = await page.evaluate(script);
|
|
222
|
+
const currentPage = requireCurrentPage();
|
|
223
|
+
const result = await currentPage.evaluate(script);
|
|
611
224
|
return JSON.stringify(result, null, 2);
|
|
612
225
|
}
|
|
613
226
|
export async function network(idx) {
|
|
@@ -639,3 +252,16 @@ export async function mapStackTrace(stack, origin, inlineSource = false, resolve
|
|
|
639
252
|
return stack;
|
|
640
253
|
return `${stack}\n\n# Mapped Stack\n${mappedLines.join("\n")}`;
|
|
641
254
|
}
|
|
255
|
+
function requireCurrentPage() {
|
|
256
|
+
const currentPage = getCurrentPage();
|
|
257
|
+
if (!currentPage)
|
|
258
|
+
throw new Error("browser not open");
|
|
259
|
+
return currentPage;
|
|
260
|
+
}
|
|
261
|
+
async function navigateAndRefreshContext(currentPage, navigate, refreshFramework = false) {
|
|
262
|
+
await navigate();
|
|
263
|
+
await injectEventCollector(currentPage);
|
|
264
|
+
if (refreshFramework) {
|
|
265
|
+
await detectFramework();
|
|
266
|
+
}
|
|
267
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -143,6 +143,11 @@ export async function runCli(argv, io) {
|
|
|
143
143
|
const res = await io.send("correlate-errors", { mapped, inlineSource, windowMs });
|
|
144
144
|
exit(io, res, res.ok && res.data ? String(res.data) : "");
|
|
145
145
|
}
|
|
146
|
+
if (cmd === "correlate" && arg === "renders") {
|
|
147
|
+
const windowMs = parseNumberFlag(args, "--window", 5000);
|
|
148
|
+
const res = await io.send("correlate-renders", { windowMs });
|
|
149
|
+
exit(io, res, res.ok && res.data ? String(res.data) : "");
|
|
150
|
+
}
|
|
146
151
|
if (cmd === "diagnose" && arg === "hmr") {
|
|
147
152
|
const mapped = args.includes("--mapped");
|
|
148
153
|
const inlineSource = args.includes("--inline-source");
|
|
@@ -151,6 +156,11 @@ export async function runCli(argv, io) {
|
|
|
151
156
|
const res = await io.send("diagnose-hmr", { mapped, inlineSource, windowMs, limit });
|
|
152
157
|
exit(io, res, res.ok && res.data ? String(res.data) : "");
|
|
153
158
|
}
|
|
159
|
+
if (cmd === "diagnose" && arg === "propagation") {
|
|
160
|
+
const windowMs = parseNumberFlag(args, "--window", 5000);
|
|
161
|
+
const res = await io.send("diagnose-propagation", { windowMs });
|
|
162
|
+
exit(io, res, res.ok && res.data ? String(res.data) : "");
|
|
163
|
+
}
|
|
154
164
|
if (cmd === "logs") {
|
|
155
165
|
const res = await io.send("logs");
|
|
156
166
|
exit(io, res, res.ok && res.data ? String(res.data) : "");
|
|
@@ -227,9 +237,12 @@ VITE COMMANDS
|
|
|
227
237
|
errors --mapped Show errors with source-map mapping
|
|
228
238
|
errors --mapped --inline-source Include mapped source snippets
|
|
229
239
|
correlate errors [--window <ms>] Correlate current errors with recent HMR events
|
|
240
|
+
correlate renders [--window <ms>] Summarize recent render/update propagation evidence
|
|
230
241
|
correlate errors --mapped Correlate mapped errors with recent HMR events
|
|
231
242
|
diagnose hmr [--window <ms>] Diagnose HMR failures from runtime, errors, and trace data
|
|
232
243
|
diagnose hmr [--limit <n>] Control how many recent HMR trace entries are inspected
|
|
244
|
+
diagnose propagation [--window <ms>]
|
|
245
|
+
Diagnose likely update -> render -> error propagation
|
|
233
246
|
logs Show dev server logs
|
|
234
247
|
|
|
235
248
|
UTILITIES
|