@presto1314w/vite-devtools-browser 0.2.2 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +318 -296
- 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 +37 -0
- package/dist/browser-session.js +117 -0
- package/dist/browser-vite.d.ts +25 -0
- package/dist/browser-vite.js +181 -0
- package/dist/browser.d.ts +5 -28
- package/dist/browser.js +127 -446
- package/dist/cli.js +78 -55
- 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/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 +17 -17
package/dist/browser.js
CHANGED
|
@@ -1,29 +1,18 @@
|
|
|
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
|
-
|
|
25
|
-
const hmrEvents = [];
|
|
26
|
-
let lastModuleGraphUrls = null;
|
|
15
|
+
const MAX_RUNTIME_ERRORS = 50;
|
|
27
16
|
export function setEventQueue(queue) {
|
|
28
17
|
eventQueue = queue;
|
|
29
18
|
}
|
|
@@ -31,84 +20,23 @@ export function getEventQueue() {
|
|
|
31
20
|
return eventQueue;
|
|
32
21
|
}
|
|
33
22
|
export function getCurrentPage() {
|
|
34
|
-
|
|
35
|
-
return null;
|
|
36
|
-
if (!page || page.isClosed())
|
|
37
|
-
return null;
|
|
38
|
-
return page;
|
|
23
|
+
return getSessionPage(session);
|
|
39
24
|
}
|
|
40
25
|
/**
|
|
41
26
|
* Inject browser-side event collector into the page
|
|
42
27
|
*/
|
|
43
28
|
async function injectEventCollector(currentPage) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
});
|
|
29
|
+
if (session.context && !session.collectorInstalled) {
|
|
30
|
+
await session.context.addInitScript(initBrowserEventCollector);
|
|
31
|
+
session.collectorInstalled = true;
|
|
32
|
+
}
|
|
33
|
+
await currentPage.evaluate(initBrowserEventCollector);
|
|
102
34
|
}
|
|
103
35
|
/**
|
|
104
36
|
* Flush browser events into daemon event queue
|
|
105
37
|
*/
|
|
106
38
|
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
|
-
});
|
|
39
|
+
const raw = await currentPage.evaluate(readBrowserEvents);
|
|
112
40
|
for (const e of raw) {
|
|
113
41
|
queue.push(e);
|
|
114
42
|
}
|
|
@@ -116,97 +44,36 @@ export async function flushBrowserEvents(currentPage, queue) {
|
|
|
116
44
|
export async function open(url) {
|
|
117
45
|
const currentPage = await ensurePage();
|
|
118
46
|
if (url) {
|
|
119
|
-
await currentPage.goto(url, { waitUntil: "domcontentloaded" });
|
|
120
|
-
await injectEventCollector(currentPage);
|
|
121
|
-
await detectFramework();
|
|
47
|
+
await navigateAndRefreshContext(currentPage, () => currentPage.goto(url, { waitUntil: "domcontentloaded" }), true);
|
|
122
48
|
}
|
|
123
49
|
}
|
|
124
50
|
export async function cookies(cookies, domain) {
|
|
125
|
-
if (!context)
|
|
51
|
+
if (!session.context)
|
|
126
52
|
throw new Error("browser not open");
|
|
127
|
-
await context.addCookies(cookies.map((c) => ({ name: c.name, value: c.value, domain, path: "/" })));
|
|
53
|
+
await session.context.addCookies(cookies.map((c) => ({ name: c.name, value: c.value, domain, path: "/" })));
|
|
128
54
|
return cookies.length;
|
|
129
55
|
}
|
|
130
56
|
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;
|
|
57
|
+
await closeBrowserSession(session);
|
|
138
58
|
networkLog.clear();
|
|
139
|
-
lastReactSnapshot = [];
|
|
140
59
|
}
|
|
141
60
|
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"],
|
|
61
|
+
return ensureBrowserPage(session, (currentPage) => {
|
|
62
|
+
attachListeners(currentPage);
|
|
63
|
+
networkLog.attach(currentPage);
|
|
204
64
|
});
|
|
205
|
-
return browser.newContext({ viewport: { width: 1280, height: 720 } });
|
|
206
65
|
}
|
|
207
66
|
function attachListeners(currentPage) {
|
|
208
67
|
currentPage.on("console", (msg) => {
|
|
209
|
-
|
|
68
|
+
const type = msg.type();
|
|
69
|
+
const message = msg.text();
|
|
70
|
+
recordConsoleMessage(session.consoleLogs, session.hmrEvents, type, message);
|
|
71
|
+
if (type === "error" || isVueUnhandledWarning(type, message)) {
|
|
72
|
+
recordRuntimeError(session.runtimeErrors, message, undefined, undefined, undefined, undefined);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
currentPage.on("pageerror", (error) => {
|
|
76
|
+
recordRuntimeError(session.runtimeErrors, error.message, error.stack, undefined, undefined, undefined, "pageerror");
|
|
210
77
|
});
|
|
211
78
|
}
|
|
212
79
|
export function recordConsoleMessage(logs, events, type, message, maxLogs = MAX_LOGS, maxEvents = MAX_HMR_EVENTS) {
|
|
@@ -221,6 +88,34 @@ export function recordConsoleMessage(logs, events, type, message, maxLogs = MAX_
|
|
|
221
88
|
if (events.length > maxEvents)
|
|
222
89
|
events.shift();
|
|
223
90
|
}
|
|
91
|
+
export function recordRuntimeError(runtimeErrors, message, stack, source, line, col, logType = "runtime-error", maxErrors = MAX_RUNTIME_ERRORS) {
|
|
92
|
+
const error = {
|
|
93
|
+
timestamp: Date.now(),
|
|
94
|
+
message,
|
|
95
|
+
stack,
|
|
96
|
+
source,
|
|
97
|
+
line,
|
|
98
|
+
col,
|
|
99
|
+
};
|
|
100
|
+
runtimeErrors.push(error);
|
|
101
|
+
if (runtimeErrors.length > maxErrors)
|
|
102
|
+
runtimeErrors.shift();
|
|
103
|
+
eventQueue?.push({
|
|
104
|
+
timestamp: error.timestamp,
|
|
105
|
+
type: "error",
|
|
106
|
+
payload: {
|
|
107
|
+
message,
|
|
108
|
+
stack,
|
|
109
|
+
source,
|
|
110
|
+
line,
|
|
111
|
+
col,
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
const details = stack ? `${message}\n${stack}` : message;
|
|
115
|
+
session.consoleLogs.push(`[${logType}] ${details}`);
|
|
116
|
+
if (session.consoleLogs.length > MAX_LOGS)
|
|
117
|
+
session.consoleLogs.shift();
|
|
118
|
+
}
|
|
224
119
|
export function parseViteLog(message) {
|
|
225
120
|
const lower = message.toLowerCase();
|
|
226
121
|
const event = {
|
|
@@ -247,108 +142,9 @@ export function parseViteLog(message) {
|
|
|
247
142
|
event.path = hotUpdateMatch[1].trim();
|
|
248
143
|
return event;
|
|
249
144
|
}
|
|
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
145
|
export async function goto(url) {
|
|
348
146
|
const currentPage = await ensurePage();
|
|
349
|
-
await currentPage.goto(url, { waitUntil: "domcontentloaded" });
|
|
350
|
-
await injectEventCollector(currentPage);
|
|
351
|
-
await detectFramework();
|
|
147
|
+
await navigateAndRefreshContext(currentPage, () => currentPage.goto(url, { waitUntil: "domcontentloaded" }), true);
|
|
352
148
|
return currentPage.url();
|
|
353
149
|
}
|
|
354
150
|
export async function back() {
|
|
@@ -357,90 +153,33 @@ export async function back() {
|
|
|
357
153
|
}
|
|
358
154
|
export async function reload() {
|
|
359
155
|
const currentPage = await ensurePage();
|
|
360
|
-
await currentPage.reload({ waitUntil: "domcontentloaded" });
|
|
156
|
+
await navigateAndRefreshContext(currentPage, () => currentPage.reload({ waitUntil: "domcontentloaded" }));
|
|
361
157
|
return currentPage.url();
|
|
362
158
|
}
|
|
363
159
|
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;
|
|
160
|
+
const currentPage = requireCurrentPage();
|
|
161
|
+
const result = await detectBrowserFramework(currentPage);
|
|
162
|
+
session.framework = result.framework;
|
|
163
|
+
return result.detected;
|
|
397
164
|
}
|
|
398
165
|
export async function vueTree(id) {
|
|
399
|
-
|
|
400
|
-
throw new Error("browser not open");
|
|
401
|
-
return id ? vueDevtools.getComponentDetails(page, id) : vueDevtools.getComponentTree(page);
|
|
166
|
+
return inspectVueTree(requireCurrentPage(), id);
|
|
402
167
|
}
|
|
403
168
|
export async function vuePinia(store) {
|
|
404
|
-
|
|
405
|
-
throw new Error("browser not open");
|
|
406
|
-
return vueDevtools.getPiniaStores(page, store);
|
|
169
|
+
return inspectVuePinia(requireCurrentPage(), store);
|
|
407
170
|
}
|
|
408
171
|
export async function vueRouter() {
|
|
409
|
-
|
|
410
|
-
throw new Error("browser not open");
|
|
411
|
-
return vueDevtools.getRouterInfo(page);
|
|
172
|
+
return inspectVueRouter(requireCurrentPage());
|
|
412
173
|
}
|
|
413
174
|
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");
|
|
175
|
+
return inspectReactTree(session, requireCurrentPage(), id);
|
|
434
176
|
}
|
|
435
177
|
export async function svelteTree(id) {
|
|
436
|
-
|
|
437
|
-
throw new Error("browser not open");
|
|
438
|
-
return id ? svelteDevtools.getComponentDetails(page, id) : svelteDevtools.getComponentTree(page);
|
|
178
|
+
return inspectSvelteTree(requireCurrentPage(), id);
|
|
439
179
|
}
|
|
440
180
|
export async function viteRestart() {
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
return page.evaluate(async () => {
|
|
181
|
+
const currentPage = requireCurrentPage();
|
|
182
|
+
return currentPage.evaluate(async () => {
|
|
444
183
|
try {
|
|
445
184
|
const response = await fetch("/__vite_restart", { method: "POST" });
|
|
446
185
|
return response.ok ? "restarted" : "restart endpoint not available";
|
|
@@ -451,52 +190,22 @@ export async function viteRestart() {
|
|
|
451
190
|
});
|
|
452
191
|
}
|
|
453
192
|
export async function viteHMR() {
|
|
454
|
-
|
|
455
|
-
throw new Error("browser not open");
|
|
193
|
+
requireCurrentPage();
|
|
456
194
|
return viteHMRTrace("summary", 20);
|
|
457
195
|
}
|
|
458
196
|
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);
|
|
197
|
+
const currentPage = requireCurrentPage();
|
|
198
|
+
const runtime = await readRuntimeSnapshot(currentPage);
|
|
199
|
+
return formatRuntimeStatus(runtime, session.framework, session.hmrEvents);
|
|
490
200
|
}
|
|
491
201
|
export async function viteHMRTrace(mode, limit = 20) {
|
|
492
|
-
|
|
493
|
-
throw new Error("browser not open");
|
|
202
|
+
const currentPage = requireCurrentPage();
|
|
494
203
|
if (mode === "clear") {
|
|
495
|
-
hmrEvents.length = 0;
|
|
204
|
+
session.hmrEvents.length = 0;
|
|
496
205
|
return "cleared HMR trace";
|
|
497
206
|
}
|
|
498
|
-
if (hmrEvents.length === 0) {
|
|
499
|
-
const fallback = await
|
|
207
|
+
if (session.hmrEvents.length === 0) {
|
|
208
|
+
const fallback = await currentPage.evaluate(() => {
|
|
500
209
|
const updates = window.__vite_hmr_updates || [];
|
|
501
210
|
return updates.slice(-20).map((u) => ({
|
|
502
211
|
timestamp: u.timestamp ?? Date.now(),
|
|
@@ -506,108 +215,67 @@ export async function viteHMRTrace(mode, limit = 20) {
|
|
|
506
215
|
}));
|
|
507
216
|
});
|
|
508
217
|
if (fallback.length > 0)
|
|
509
|
-
hmrEvents.push(...fallback);
|
|
218
|
+
session.hmrEvents.push(...fallback);
|
|
510
219
|
}
|
|
511
|
-
if (hmrEvents.length === 0)
|
|
220
|
+
if (session.hmrEvents.length === 0)
|
|
512
221
|
return "No HMR updates";
|
|
513
|
-
return formatHmrTrace(mode, hmrEvents, limit);
|
|
222
|
+
return formatHmrTrace(mode, session.hmrEvents, limit);
|
|
514
223
|
}
|
|
515
224
|
export async function viteModuleGraph(filter, limit = 200, mode = "snapshot") {
|
|
516
|
-
|
|
517
|
-
throw new Error("browser not open");
|
|
225
|
+
const currentPage = requireCurrentPage();
|
|
518
226
|
if (mode === "clear") {
|
|
519
|
-
lastModuleGraphUrls = null;
|
|
227
|
+
session.lastModuleGraphUrls = null;
|
|
520
228
|
return "cleared module-graph baseline";
|
|
521
229
|
}
|
|
522
|
-
const moduleRows = await collectModuleRows(
|
|
230
|
+
const moduleRows = await collectModuleRows(currentPage);
|
|
523
231
|
const currentUrls = moduleRows.map((row) => row.url);
|
|
524
|
-
const previousUrls = lastModuleGraphUrls ? new Set(lastModuleGraphUrls) : null;
|
|
525
|
-
lastModuleGraphUrls = [...currentUrls];
|
|
232
|
+
const previousUrls = session.lastModuleGraphUrls ? new Set(session.lastModuleGraphUrls) : null;
|
|
233
|
+
session.lastModuleGraphUrls = [...currentUrls];
|
|
526
234
|
if (mode === "trace") {
|
|
527
235
|
return formatModuleGraphTrace(currentUrls, previousUrls, filter, limit);
|
|
528
236
|
}
|
|
529
237
|
return formatModuleGraphSnapshot(moduleRows, filter, limit);
|
|
530
238
|
}
|
|
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
239
|
export async function errors(mapped = false, inlineSource = false) {
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
const
|
|
579
|
-
|
|
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
|
-
});
|
|
586
|
-
if (!errorInfo)
|
|
240
|
+
const currentPage = requireCurrentPage();
|
|
241
|
+
const errorInfo = await readOverlayError(currentPage);
|
|
242
|
+
const runtimeError = session.runtimeErrors[session.runtimeErrors.length - 1];
|
|
243
|
+
if (!errorInfo && !runtimeError)
|
|
587
244
|
return "no errors";
|
|
588
|
-
const raw =
|
|
245
|
+
const raw = errorInfo
|
|
246
|
+
? `${errorInfo.message ?? "Vite error"}\n\n${errorInfo.stack ?? ""}`.trim()
|
|
247
|
+
: formatRuntimeError(runtimeError);
|
|
589
248
|
if (!mapped)
|
|
590
249
|
return raw;
|
|
591
|
-
const origin = new URL(
|
|
250
|
+
const origin = new URL(currentPage.url()).origin;
|
|
592
251
|
const mappedStack = await mapStackTrace(raw, origin, inlineSource);
|
|
593
252
|
return mappedStack;
|
|
594
253
|
}
|
|
254
|
+
function formatRuntimeError(error) {
|
|
255
|
+
const location = error.source && error.line != null && error.col != null
|
|
256
|
+
? `\n\nat ${error.source}:${error.line}:${error.col}`
|
|
257
|
+
: "";
|
|
258
|
+
return `${error.message}${location}${error.stack ? `\n\n${error.stack}` : ""}`.trim();
|
|
259
|
+
}
|
|
260
|
+
function isVueUnhandledWarning(type, message) {
|
|
261
|
+
if (type !== "warning")
|
|
262
|
+
return false;
|
|
263
|
+
return /\[Vue warn\]: Unhandled error during execution/i.test(message);
|
|
264
|
+
}
|
|
595
265
|
export async function logs() {
|
|
596
|
-
if (consoleLogs.length === 0)
|
|
266
|
+
if (session.consoleLogs.length === 0)
|
|
597
267
|
return "no logs";
|
|
598
|
-
return consoleLogs.slice(-50).join("\n");
|
|
268
|
+
return session.consoleLogs.slice(-50).join("\n");
|
|
599
269
|
}
|
|
600
270
|
export async function screenshot() {
|
|
601
|
-
|
|
602
|
-
throw new Error("browser not open");
|
|
271
|
+
const currentPage = requireCurrentPage();
|
|
603
272
|
const path = join(tmpdir(), `vite-browser-${Date.now()}.png`);
|
|
604
|
-
await
|
|
273
|
+
await currentPage.screenshot({ path, fullPage: true });
|
|
605
274
|
return path;
|
|
606
275
|
}
|
|
607
276
|
export async function evaluate(script) {
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
const result = await page.evaluate(script);
|
|
277
|
+
const currentPage = requireCurrentPage();
|
|
278
|
+
const result = await currentPage.evaluate(script);
|
|
611
279
|
return JSON.stringify(result, null, 2);
|
|
612
280
|
}
|
|
613
281
|
export async function network(idx) {
|
|
@@ -639,3 +307,16 @@ export async function mapStackTrace(stack, origin, inlineSource = false, resolve
|
|
|
639
307
|
return stack;
|
|
640
308
|
return `${stack}\n\n# Mapped Stack\n${mappedLines.join("\n")}`;
|
|
641
309
|
}
|
|
310
|
+
function requireCurrentPage() {
|
|
311
|
+
const currentPage = getCurrentPage();
|
|
312
|
+
if (!currentPage)
|
|
313
|
+
throw new Error("browser not open");
|
|
314
|
+
return currentPage;
|
|
315
|
+
}
|
|
316
|
+
async function navigateAndRefreshContext(currentPage, navigate, refreshFramework = false) {
|
|
317
|
+
await navigate();
|
|
318
|
+
await injectEventCollector(currentPage);
|
|
319
|
+
if (refreshFramework) {
|
|
320
|
+
await detectFramework();
|
|
321
|
+
}
|
|
322
|
+
}
|