@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/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, resolve } from "node:path";
4
- import { chromium } from "playwright";
5
- import * as vueDevtools from "./vue/devtools.js";
6
- import * as reactDevtools from "./react/devtools.js";
7
- import * as svelteDevtools from "./svelte/devtools.js";
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
- const extensionPath = process.env.REACT_DEVTOOLS_EXTENSION ??
11
- resolve(import.meta.dirname, "../../next-browser/extensions/react-devtools-chrome");
12
- const hasReactExtension = existsSync(join(extensionPath, "manifest.json"));
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;
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
- if (!contextUsable(context))
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
- 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
- });
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 context?.close();
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
- if (!contextUsable(context)) {
143
- await close();
144
- context = await launch();
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
- recordConsoleMessage(consoleLogs, hmrEvents, msg.type(), msg.text());
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
- if (!page)
365
- throw new Error("browser not open");
366
- const detected = await page.evaluate(() => {
367
- if (window.__VUE__ || window.__VUE_DEVTOOLS_GLOBAL_HOOK__) {
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
- if (!page)
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
- if (!page)
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
- if (!page)
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
- if (!page)
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
- if (!page)
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
- if (!page)
442
- throw new Error("browser not open");
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
- if (!page)
455
- throw new Error("browser not open");
193
+ requireCurrentPage();
456
194
  return viteHMRTrace("summary", 20);
457
195
  }
458
196
  export async function viteRuntimeStatus() {
459
- if (!page)
460
- throw new Error("browser not open");
461
- const runtime = await page.evaluate(() => {
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
- if (!page)
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 page.evaluate(() => {
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
- if (!page)
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(page);
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
- if (!page)
577
- throw new Error("browser not open");
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
- });
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 = `${errorInfo.message ?? "Vite error"}\n\n${errorInfo.stack ?? ""}`.trim();
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(page.url()).origin;
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
- if (!page)
602
- throw new Error("browser not open");
271
+ const currentPage = requireCurrentPage();
603
272
  const path = join(tmpdir(), `vite-browser-${Date.now()}.png`);
604
- await page.screenshot({ path, fullPage: true });
273
+ await currentPage.screenshot({ path, fullPage: true });
605
274
  return path;
606
275
  }
607
276
  export async function evaluate(script) {
608
- if (!page)
609
- throw new Error("browser not open");
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
+ }