@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/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, 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;
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
- if (!contextUsable(context))
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 context?.close();
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
- 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"],
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
- 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;
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
- if (!page)
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
- if (!page)
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
- if (!page)
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
- 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");
134
+ return inspectReactTree(session, requireCurrentPage(), id);
434
135
  }
435
136
  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);
137
+ return inspectSvelteTree(requireCurrentPage(), id);
439
138
  }
440
139
  export async function viteRestart() {
441
- if (!page)
442
- throw new Error("browser not open");
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
- if (!page)
455
- throw new Error("browser not open");
152
+ requireCurrentPage();
456
153
  return viteHMRTrace("summary", 20);
457
154
  }
458
155
  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);
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
- if (!page)
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 page.evaluate(() => {
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
- if (!page)
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(page);
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
- 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
- });
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(page.url()).origin;
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
- if (!page)
602
- throw new Error("browser not open");
216
+ const currentPage = requireCurrentPage();
603
217
  const path = join(tmpdir(), `vite-browser-${Date.now()}.png`);
604
- await page.screenshot({ path, fullPage: true });
218
+ await currentPage.screenshot({ path, fullPage: true });
605
219
  return path;
606
220
  }
607
221
  export async function evaluate(script) {
608
- if (!page)
609
- throw new Error("browser not open");
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