@presto1314w/vite-devtools-browser 0.1.3 → 0.2.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
@@ -17,16 +17,107 @@ let context = null;
17
17
  let page = null;
18
18
  let framework = "unknown";
19
19
  let extensionModeDisabled = false;
20
+ let eventQueue = null;
20
21
  const consoleLogs = [];
21
22
  const MAX_LOGS = 200;
22
23
  const MAX_HMR_EVENTS = 500;
23
24
  let lastReactSnapshot = [];
24
25
  const hmrEvents = [];
25
26
  let lastModuleGraphUrls = null;
27
+ export function setEventQueue(queue) {
28
+ eventQueue = queue;
29
+ }
30
+ export function getEventQueue() {
31
+ return eventQueue;
32
+ }
33
+ export function getCurrentPage() {
34
+ if (!contextUsable(context))
35
+ return null;
36
+ if (!page || page.isClosed())
37
+ return null;
38
+ return page;
39
+ }
40
+ /**
41
+ * Inject browser-side event collector into the page
42
+ */
43
+ 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
+ });
102
+ }
103
+ /**
104
+ * Flush browser events into daemon event queue
105
+ */
106
+ 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
+ });
112
+ for (const e of raw) {
113
+ queue.push(e);
114
+ }
115
+ }
26
116
  export async function open(url) {
27
117
  const currentPage = await ensurePage();
28
118
  if (url) {
29
119
  await currentPage.goto(url, { waitUntil: "domcontentloaded" });
120
+ await injectEventCollector(currentPage);
30
121
  await detectFramework();
31
122
  }
32
123
  }
@@ -71,7 +162,7 @@ async function ensurePage() {
71
162
  }
72
163
  return page;
73
164
  }
74
- function contextUsable(current) {
165
+ export function contextUsable(current) {
75
166
  if (!current)
76
167
  return false;
77
168
  try {
@@ -82,7 +173,7 @@ function contextUsable(current) {
82
173
  return false;
83
174
  }
84
175
  }
85
- function isClosedTargetError(error) {
176
+ export function isClosedTargetError(error) {
86
177
  if (!(error instanceof Error))
87
178
  return false;
88
179
  return /Target page, context or browser has been closed/i.test(error.message);
@@ -115,20 +206,22 @@ async function launch() {
115
206
  }
116
207
  function attachListeners(currentPage) {
117
208
  currentPage.on("console", (msg) => {
118
- const text = `[${msg.type()}] ${msg.text()}`;
119
- consoleLogs.push(text);
120
- if (consoleLogs.length > MAX_LOGS)
121
- consoleLogs.shift();
122
- const viteMessage = msg.text();
123
- if (!viteMessage.includes("[vite]"))
124
- return;
125
- const event = parseViteLog(viteMessage);
126
- hmrEvents.push(event);
127
- if (hmrEvents.length > MAX_HMR_EVENTS)
128
- hmrEvents.shift();
209
+ recordConsoleMessage(consoleLogs, hmrEvents, msg.type(), msg.text());
129
210
  });
130
211
  }
131
- function parseViteLog(message) {
212
+ export function recordConsoleMessage(logs, events, type, message, maxLogs = MAX_LOGS, maxEvents = MAX_HMR_EVENTS) {
213
+ const text = `[${type}] ${message}`;
214
+ logs.push(text);
215
+ if (logs.length > maxLogs)
216
+ logs.shift();
217
+ if (!message.includes("[vite]"))
218
+ return;
219
+ const event = parseViteLog(message);
220
+ events.push(event);
221
+ if (events.length > maxEvents)
222
+ events.shift();
223
+ }
224
+ export function parseViteLog(message) {
132
225
  const lower = message.toLowerCase();
133
226
  const event = {
134
227
  timestamp: Date.now(),
@@ -154,9 +247,107 @@ function parseViteLog(message) {
154
247
  event.path = hotUpdateMatch[1].trim();
155
248
  return event;
156
249
  }
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
+ }
157
347
  export async function goto(url) {
158
348
  const currentPage = await ensurePage();
159
349
  await currentPage.goto(url, { waitUntil: "domcontentloaded" });
350
+ await injectEventCollector(currentPage);
160
351
  await detectFramework();
161
352
  return currentPage.url();
162
353
  }
@@ -295,19 +486,7 @@ export async function viteRuntimeStatus() {
295
486
  timestamp: Date.now(),
296
487
  };
297
488
  });
298
- const output = [];
299
- output.push("# Vite Runtime");
300
- output.push(`URL: ${runtime.url}`);
301
- output.push(`Framework: ${framework}`);
302
- output.push(`Vite Client: ${runtime.hasViteClient ? "loaded" : "not detected"}`);
303
- output.push(`HMR Socket: ${runtime.wsState}`);
304
- output.push(`Error Overlay: ${runtime.hasErrorOverlay ? "present" : "none"}`);
305
- output.push(`Tracked HMR Events: ${hmrEvents.length}`);
306
- const last = hmrEvents[hmrEvents.length - 1];
307
- if (last) {
308
- output.push(`Last HMR Event: ${new Date(last.timestamp).toLocaleTimeString()} [${last.type}] ${last.message}`);
309
- }
310
- return output.join("\n");
489
+ return formatRuntimeStatus(runtime, framework, hmrEvents);
311
490
  }
312
491
  export async function viteHMRTrace(mode, limit = 20) {
313
492
  if (!page)
@@ -331,30 +510,7 @@ export async function viteHMRTrace(mode, limit = 20) {
331
510
  }
332
511
  if (hmrEvents.length === 0)
333
512
  return "No HMR updates";
334
- const safeLimit = Number.isFinite(limit) && limit > 0 ? Math.min(limit, 200) : 20;
335
- const recent = hmrEvents.slice(-safeLimit);
336
- if (mode === "summary") {
337
- const counts = recent.reduce((acc, event) => {
338
- acc[event.type] = (acc[event.type] ?? 0) + 1;
339
- return acc;
340
- }, {});
341
- const lines = ["# HMR Summary"];
342
- lines.push(`Events considered: ${recent.length}`);
343
- lines.push(`Counts: ${Object.entries(counts)
344
- .map(([k, v]) => `${k}=${v}`)
345
- .join(", ")}`);
346
- const last = recent[recent.length - 1];
347
- lines.push(`Last: ${new Date(last.timestamp).toLocaleTimeString()} [${last.type}] ${last.path ?? last.message}`);
348
- lines.push("\nUse `vite-browser vite hmr trace --limit <n>` for timeline details.");
349
- return lines.join("\n");
350
- }
351
- return [
352
- "# HMR Trace",
353
- ...recent.map((event) => {
354
- const detail = event.path ? `${event.path}` : event.message;
355
- return `[${new Date(event.timestamp).toLocaleTimeString()}] ${event.type} ${detail}`;
356
- }),
357
- ].join("\n");
513
+ return formatHmrTrace(mode, hmrEvents, limit);
358
514
  }
359
515
  export async function viteModuleGraph(filter, limit = 200, mode = "snapshot") {
360
516
  if (!page)
@@ -366,52 +522,11 @@ export async function viteModuleGraph(filter, limit = 200, mode = "snapshot") {
366
522
  const moduleRows = await collectModuleRows(page);
367
523
  const currentUrls = moduleRows.map((row) => row.url);
368
524
  const previousUrls = lastModuleGraphUrls ? new Set(lastModuleGraphUrls) : null;
369
- const currentSet = new Set(currentUrls);
370
525
  lastModuleGraphUrls = [...currentUrls];
371
- const normalizedFilter = filter?.trim().toLowerCase();
372
- const safeLimit = Number.isFinite(limit) && limit > 0 ? Math.min(limit, 500) : 200;
373
526
  if (mode === "trace") {
374
- if (!previousUrls) {
375
- return "No module-graph baseline. Captured current snapshot; run `vite module-graph trace` again.";
376
- }
377
- const added = currentUrls.filter((url) => !previousUrls.has(url));
378
- const removed = [...previousUrls].filter((url) => !currentSet.has(url));
379
- const addedFiltered = normalizedFilter
380
- ? added.filter((url) => url.toLowerCase().includes(normalizedFilter))
381
- : added;
382
- const removedFiltered = normalizedFilter
383
- ? removed.filter((url) => url.toLowerCase().includes(normalizedFilter))
384
- : removed;
385
- const lines = [];
386
- lines.push("# Vite Module Graph Trace");
387
- lines.push(`Added: ${addedFiltered.length}, Removed: ${removedFiltered.length}`);
388
- lines.push("");
389
- lines.push("## Added");
390
- if (addedFiltered.length === 0)
391
- lines.push("(none)");
392
- else
393
- addedFiltered.slice(0, safeLimit).forEach((url) => lines.push(`+ ${url}`));
394
- lines.push("");
395
- lines.push("## Removed");
396
- if (removedFiltered.length === 0)
397
- lines.push("(none)");
398
- else
399
- removedFiltered.slice(0, safeLimit).forEach((url) => lines.push(`- ${url}`));
400
- return lines.join("\n");
527
+ return formatModuleGraphTrace(currentUrls, previousUrls, filter, limit);
401
528
  }
402
- const filtered = moduleRows.filter((row) => normalizedFilter ? row.url.toLowerCase().includes(normalizedFilter) : true);
403
- const limited = filtered.slice(0, safeLimit);
404
- if (limited.length === 0)
405
- return "No module resources found";
406
- const lines = [];
407
- lines.push("# Vite Module Graph (loaded resources)");
408
- lines.push(`Total: ${filtered.length}${filtered.length > limited.length ? ` (showing ${limited.length})` : ""}`);
409
- lines.push("# idx initiator ms url");
410
- lines.push("");
411
- limited.forEach((row, idx) => {
412
- lines.push(`${idx} ${row.initiator} ${row.durationMs}ms ${row.url}`);
413
- });
414
- return lines.join("\n");
529
+ return formatModuleGraphSnapshot(moduleRows, filter, limit);
415
530
  }
416
531
  async function collectModuleRows(page) {
417
532
  return page.evaluate(() => {
@@ -500,7 +615,7 @@ export async function network(idx) {
500
615
  return networkLog.format();
501
616
  return networkLog.detail(idx);
502
617
  }
503
- async function mapStackTrace(stack, origin, inlineSource = false) {
618
+ export async function mapStackTrace(stack, origin, inlineSource = false, resolver = resolveViaSourceMap) {
504
619
  const locationRegex = /(https?:\/\/[^\s)]+):(\d+):(\d+)/g;
505
620
  const matches = Array.from(stack.matchAll(locationRegex));
506
621
  if (matches.length === 0)
@@ -512,7 +627,7 @@ async function mapStackTrace(stack, origin, inlineSource = false) {
512
627
  const column = Number.parseInt(match[3], 10);
513
628
  if (!Number.isFinite(line) || !Number.isFinite(column))
514
629
  continue;
515
- const mapped = await resolveViaSourceMap(origin, fileUrl, line, column, inlineSource);
630
+ const mapped = await resolver(origin, fileUrl, line, column, inlineSource);
516
631
  if (!mapped)
517
632
  continue;
518
633
  mappedLines.push(`- ${fileUrl}:${line}:${column} -> ${mapped.file}:${mapped.line}:${mapped.column}`);
package/dist/cli.d.ts CHANGED
@@ -1,2 +1,14 @@
1
1
  #!/usr/bin/env node
2
- export {};
2
+ import { send, type Response } from "./client.js";
3
+ export type CliIo = {
4
+ send: typeof send;
5
+ readFile: (path: string, encoding: BufferEncoding) => string;
6
+ stdout: (text: string) => void;
7
+ stderr: (text: string) => void;
8
+ exit: (code: number) => never;
9
+ };
10
+ export declare function normalizeUrl(value: string): string;
11
+ export declare function parseNumberFlag(args: string[], name: string, fallback: number): number;
12
+ export declare function runCli(argv: string[], io: CliIo): Promise<void>;
13
+ export declare function exit(io: Pick<CliIo, "stdout" | "stderr" | "exit">, res: Response, msg: string): never;
14
+ export declare function printUsage(): string;