@presto1314w/vite-devtools-browser 0.3.0 → 0.3.2
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 -323
- package/dist/browser-collector.js +64 -29
- package/dist/browser-session.d.ts +10 -0
- package/dist/browser-session.js +4 -0
- package/dist/browser.d.ts +2 -1
- package/dist/browser.js +60 -3
- package/dist/cli.js +104 -64
- package/dist/daemon.js +68 -16
- package/dist/event-analysis.d.ts +2 -0
- package/dist/event-analysis.js +6 -0
- package/dist/trace.js +18 -4
- package/package.json +17 -17
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
export function initBrowserEventCollector() {
|
|
2
|
-
if (window.__vb_events)
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
window.__vb_push
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
q.
|
|
10
|
-
|
|
2
|
+
if (!window.__vb_events) {
|
|
3
|
+
window.__vb_events = [];
|
|
4
|
+
}
|
|
5
|
+
if (!window.__vb_push) {
|
|
6
|
+
window.__vb_push = (event) => {
|
|
7
|
+
const q = window.__vb_events;
|
|
8
|
+
q.push(event);
|
|
9
|
+
if (q.length > 1000)
|
|
10
|
+
q.shift();
|
|
11
|
+
};
|
|
12
|
+
}
|
|
11
13
|
const inferFramework = () => {
|
|
12
14
|
if (window.__VUE__ || window.__VUE_DEVTOOLS_GLOBAL_HOOK__)
|
|
13
15
|
return "vue";
|
|
@@ -117,15 +119,16 @@ export function initBrowserEventCollector() {
|
|
|
117
119
|
}, 60);
|
|
118
120
|
};
|
|
119
121
|
const attachPiniaSubscriptions = () => {
|
|
120
|
-
if (window.__vb_pinia_attached)
|
|
121
|
-
return;
|
|
122
122
|
const hook = window.__VUE_DEVTOOLS_GLOBAL_HOOK__;
|
|
123
123
|
const app = Array.isArray(hook?.apps) ? hook.apps[0] : null;
|
|
124
124
|
const pinia = window.__PINIA__ || window.pinia || app?.config?.globalProperties?.$pinia;
|
|
125
125
|
const registry = pinia?._s;
|
|
126
126
|
if (!(registry instanceof Map) || registry.size === 0)
|
|
127
127
|
return;
|
|
128
|
-
const attached =
|
|
128
|
+
const attached = window.__vb_pinia_attached ||
|
|
129
|
+
(window.__vb_pinia_attached = new Set());
|
|
130
|
+
const actionAttached = window.__vb_pinia_action_attached ||
|
|
131
|
+
(window.__vb_pinia_action_attached = new Set());
|
|
129
132
|
registry.forEach((store, storeId) => {
|
|
130
133
|
if (!store || typeof store.$subscribe !== "function" || attached.has(String(storeId)))
|
|
131
134
|
return;
|
|
@@ -161,10 +164,33 @@ export function initBrowserEventCollector() {
|
|
|
161
164
|
},
|
|
162
165
|
});
|
|
163
166
|
scheduleRender("store-update", { changedKeys });
|
|
164
|
-
},
|
|
167
|
+
},
|
|
168
|
+
// Flush synchronously so the store event is recorded before a render-time failure
|
|
169
|
+
// can short-circuit the rest of Vue's update cycle.
|
|
170
|
+
{ detached: true, flush: "sync" });
|
|
171
|
+
if (typeof store.$onAction === "function" && !actionAttached.has(String(storeId))) {
|
|
172
|
+
actionAttached.add(String(storeId));
|
|
173
|
+
store.$onAction(({ name, after }) => {
|
|
174
|
+
after(() => {
|
|
175
|
+
window.__vb_push({
|
|
176
|
+
timestamp: Date.now(),
|
|
177
|
+
type: "store-update",
|
|
178
|
+
payload: {
|
|
179
|
+
store: String(storeId),
|
|
180
|
+
mutationType: typeof name === "string" && name.length > 0 ? `action:${name}` : "action",
|
|
181
|
+
events: 0,
|
|
182
|
+
changedKeys: [],
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
scheduleRender("store-update");
|
|
186
|
+
});
|
|
187
|
+
}, true);
|
|
188
|
+
}
|
|
165
189
|
});
|
|
166
190
|
};
|
|
167
191
|
function attachViteListener() {
|
|
192
|
+
if (window.__vb_vite_listener_attached)
|
|
193
|
+
return true;
|
|
168
194
|
const hot = window.__vite_hot;
|
|
169
195
|
if (hot?.ws) {
|
|
170
196
|
hot.ws.addEventListener("message", (e) => {
|
|
@@ -181,6 +207,7 @@ export function initBrowserEventCollector() {
|
|
|
181
207
|
}
|
|
182
208
|
catch { }
|
|
183
209
|
});
|
|
210
|
+
window.__vb_vite_listener_attached = true;
|
|
184
211
|
return true;
|
|
185
212
|
}
|
|
186
213
|
return false;
|
|
@@ -194,22 +221,28 @@ export function initBrowserEventCollector() {
|
|
|
194
221
|
}
|
|
195
222
|
}, 100);
|
|
196
223
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
window.
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
window.
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
224
|
+
if (!window.__vb_onerror_attached) {
|
|
225
|
+
const origOnError = window.onerror;
|
|
226
|
+
window.onerror = (msg, src, line, col, err) => {
|
|
227
|
+
window.__vb_push({
|
|
228
|
+
timestamp: Date.now(),
|
|
229
|
+
type: "error",
|
|
230
|
+
payload: { message: String(msg), source: src, line, col, stack: err?.stack },
|
|
231
|
+
});
|
|
232
|
+
return origOnError ? origOnError(msg, src, line, col, err) : false;
|
|
233
|
+
};
|
|
234
|
+
window.__vb_onerror_attached = true;
|
|
235
|
+
}
|
|
236
|
+
if (!window.__vb_unhandledrejection_attached) {
|
|
237
|
+
window.addEventListener("unhandledrejection", (e) => {
|
|
238
|
+
window.__vb_push({
|
|
239
|
+
timestamp: Date.now(),
|
|
240
|
+
type: "error",
|
|
241
|
+
payload: { message: e.reason?.message, stack: e.reason?.stack },
|
|
242
|
+
});
|
|
211
243
|
});
|
|
212
|
-
|
|
244
|
+
window.__vb_unhandledrejection_attached = true;
|
|
245
|
+
}
|
|
213
246
|
const observeDom = () => {
|
|
214
247
|
const root = document.body || document.documentElement;
|
|
215
248
|
if (!root || window.__vb_render_observer)
|
|
@@ -246,7 +279,9 @@ export function initBrowserEventCollector() {
|
|
|
246
279
|
observeDom();
|
|
247
280
|
patchHistory();
|
|
248
281
|
attachPiniaSubscriptions();
|
|
249
|
-
window.
|
|
282
|
+
if (!window.__vb_pinia_retry_timer) {
|
|
283
|
+
window.__vb_pinia_retry_timer = window.setInterval(attachPiniaSubscriptions, 250);
|
|
284
|
+
}
|
|
250
285
|
scheduleRender("initial-load");
|
|
251
286
|
}
|
|
252
287
|
export function readBrowserEvents() {
|
|
@@ -8,13 +8,23 @@ export type HmrEvent = {
|
|
|
8
8
|
message: string;
|
|
9
9
|
path?: string;
|
|
10
10
|
};
|
|
11
|
+
export type RuntimeError = {
|
|
12
|
+
timestamp: number;
|
|
13
|
+
message: string;
|
|
14
|
+
stack?: string;
|
|
15
|
+
source?: string | null;
|
|
16
|
+
line?: number | null;
|
|
17
|
+
col?: number | null;
|
|
18
|
+
};
|
|
11
19
|
export type BrowserSessionState = {
|
|
12
20
|
context: BrowserContext | null;
|
|
13
21
|
page: Page | null;
|
|
14
22
|
framework: BrowserFramework;
|
|
15
23
|
extensionModeDisabled: boolean;
|
|
24
|
+
collectorInstalled: boolean;
|
|
16
25
|
consoleLogs: string[];
|
|
17
26
|
hmrEvents: HmrEvent[];
|
|
27
|
+
runtimeErrors: RuntimeError[];
|
|
18
28
|
lastReactSnapshot: ReactNode[];
|
|
19
29
|
lastModuleGraphUrls: string[] | null;
|
|
20
30
|
};
|
package/dist/browser-session.js
CHANGED
|
@@ -13,8 +13,10 @@ export function createBrowserSessionState() {
|
|
|
13
13
|
page: null,
|
|
14
14
|
framework: "unknown",
|
|
15
15
|
extensionModeDisabled: false,
|
|
16
|
+
collectorInstalled: false,
|
|
16
17
|
consoleLogs: [],
|
|
17
18
|
hmrEvents: [],
|
|
19
|
+
runtimeErrors: [],
|
|
18
20
|
lastReactSnapshot: [],
|
|
19
21
|
lastModuleGraphUrls: null,
|
|
20
22
|
};
|
|
@@ -30,8 +32,10 @@ export function resetBrowserSessionState(state) {
|
|
|
30
32
|
state.context = null;
|
|
31
33
|
state.page = null;
|
|
32
34
|
state.framework = "unknown";
|
|
35
|
+
state.collectorInstalled = false;
|
|
33
36
|
state.consoleLogs.length = 0;
|
|
34
37
|
state.hmrEvents.length = 0;
|
|
38
|
+
state.runtimeErrors.length = 0;
|
|
35
39
|
state.lastModuleGraphUrls = null;
|
|
36
40
|
state.lastReactSnapshot = [];
|
|
37
41
|
}
|
package/dist/browser.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Page } from "playwright";
|
|
2
|
-
import { type HmrEvent } from "./browser-session.js";
|
|
2
|
+
import { type HmrEvent, type RuntimeError } from "./browser-session.js";
|
|
3
3
|
import { resolveViaSourceMap } from "./sourcemap.js";
|
|
4
4
|
import { EventQueue } from "./event-queue.js";
|
|
5
5
|
export { contextUsable, isClosedTargetError, type HmrEvent } from "./browser-session.js";
|
|
@@ -19,6 +19,7 @@ export declare function cookies(cookies: {
|
|
|
19
19
|
}[], domain: string): Promise<number>;
|
|
20
20
|
export declare function close(): Promise<void>;
|
|
21
21
|
export declare function recordConsoleMessage(logs: string[], events: HmrEvent[], type: string, message: string, maxLogs?: number, maxEvents?: number): void;
|
|
22
|
+
export declare function recordRuntimeError(runtimeErrors: RuntimeError[], message: string, stack?: string, source?: string | null, line?: number | null, col?: number | null, logType?: string, maxErrors?: number): void;
|
|
22
23
|
export declare function parseViteLog(message: string): HmrEvent;
|
|
23
24
|
export declare function goto(url: string): Promise<string>;
|
|
24
25
|
export declare function back(): Promise<void>;
|
package/dist/browser.js
CHANGED
|
@@ -12,6 +12,7 @@ const session = createBrowserSessionState();
|
|
|
12
12
|
let eventQueue = null;
|
|
13
13
|
const MAX_LOGS = 200;
|
|
14
14
|
const MAX_HMR_EVENTS = 500;
|
|
15
|
+
const MAX_RUNTIME_ERRORS = 50;
|
|
15
16
|
export function setEventQueue(queue) {
|
|
16
17
|
eventQueue = queue;
|
|
17
18
|
}
|
|
@@ -25,12 +26,17 @@ export function getCurrentPage() {
|
|
|
25
26
|
* Inject browser-side event collector into the page
|
|
26
27
|
*/
|
|
27
28
|
async function injectEventCollector(currentPage) {
|
|
29
|
+
if (session.context && !session.collectorInstalled) {
|
|
30
|
+
await session.context.addInitScript(initBrowserEventCollector);
|
|
31
|
+
session.collectorInstalled = true;
|
|
32
|
+
}
|
|
28
33
|
await currentPage.evaluate(initBrowserEventCollector);
|
|
29
34
|
}
|
|
30
35
|
/**
|
|
31
36
|
* Flush browser events into daemon event queue
|
|
32
37
|
*/
|
|
33
38
|
export async function flushBrowserEvents(currentPage, queue) {
|
|
39
|
+
await currentPage.evaluate(initBrowserEventCollector);
|
|
34
40
|
const raw = await currentPage.evaluate(readBrowserEvents);
|
|
35
41
|
for (const e of raw) {
|
|
36
42
|
queue.push(e);
|
|
@@ -60,7 +66,15 @@ async function ensurePage() {
|
|
|
60
66
|
}
|
|
61
67
|
function attachListeners(currentPage) {
|
|
62
68
|
currentPage.on("console", (msg) => {
|
|
63
|
-
|
|
69
|
+
const type = msg.type();
|
|
70
|
+
const message = msg.text();
|
|
71
|
+
recordConsoleMessage(session.consoleLogs, session.hmrEvents, type, message);
|
|
72
|
+
if (type === "error" || isVueUnhandledWarning(type, message)) {
|
|
73
|
+
recordRuntimeError(session.runtimeErrors, message, undefined, undefined, undefined, undefined);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
currentPage.on("pageerror", (error) => {
|
|
77
|
+
recordRuntimeError(session.runtimeErrors, error.message, error.stack, undefined, undefined, undefined, "pageerror");
|
|
64
78
|
});
|
|
65
79
|
}
|
|
66
80
|
export function recordConsoleMessage(logs, events, type, message, maxLogs = MAX_LOGS, maxEvents = MAX_HMR_EVENTS) {
|
|
@@ -75,6 +89,34 @@ export function recordConsoleMessage(logs, events, type, message, maxLogs = MAX_
|
|
|
75
89
|
if (events.length > maxEvents)
|
|
76
90
|
events.shift();
|
|
77
91
|
}
|
|
92
|
+
export function recordRuntimeError(runtimeErrors, message, stack, source, line, col, logType = "runtime-error", maxErrors = MAX_RUNTIME_ERRORS) {
|
|
93
|
+
const error = {
|
|
94
|
+
timestamp: Date.now(),
|
|
95
|
+
message,
|
|
96
|
+
stack,
|
|
97
|
+
source,
|
|
98
|
+
line,
|
|
99
|
+
col,
|
|
100
|
+
};
|
|
101
|
+
runtimeErrors.push(error);
|
|
102
|
+
if (runtimeErrors.length > maxErrors)
|
|
103
|
+
runtimeErrors.shift();
|
|
104
|
+
eventQueue?.push({
|
|
105
|
+
timestamp: error.timestamp,
|
|
106
|
+
type: "error",
|
|
107
|
+
payload: {
|
|
108
|
+
message,
|
|
109
|
+
stack,
|
|
110
|
+
source,
|
|
111
|
+
line,
|
|
112
|
+
col,
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
const details = stack ? `${message}\n${stack}` : message;
|
|
116
|
+
session.consoleLogs.push(`[${logType}] ${details}`);
|
|
117
|
+
if (session.consoleLogs.length > MAX_LOGS)
|
|
118
|
+
session.consoleLogs.shift();
|
|
119
|
+
}
|
|
78
120
|
export function parseViteLog(message) {
|
|
79
121
|
const lower = message.toLowerCase();
|
|
80
122
|
const event = {
|
|
@@ -198,15 +240,29 @@ export async function viteModuleGraph(filter, limit = 200, mode = "snapshot") {
|
|
|
198
240
|
export async function errors(mapped = false, inlineSource = false) {
|
|
199
241
|
const currentPage = requireCurrentPage();
|
|
200
242
|
const errorInfo = await readOverlayError(currentPage);
|
|
201
|
-
|
|
243
|
+
const runtimeError = session.runtimeErrors[session.runtimeErrors.length - 1];
|
|
244
|
+
if (!errorInfo && !runtimeError)
|
|
202
245
|
return "no errors";
|
|
203
|
-
const raw =
|
|
246
|
+
const raw = errorInfo
|
|
247
|
+
? `${errorInfo.message ?? "Vite error"}\n\n${errorInfo.stack ?? ""}`.trim()
|
|
248
|
+
: formatRuntimeError(runtimeError);
|
|
204
249
|
if (!mapped)
|
|
205
250
|
return raw;
|
|
206
251
|
const origin = new URL(currentPage.url()).origin;
|
|
207
252
|
const mappedStack = await mapStackTrace(raw, origin, inlineSource);
|
|
208
253
|
return mappedStack;
|
|
209
254
|
}
|
|
255
|
+
function formatRuntimeError(error) {
|
|
256
|
+
const location = error.source && error.line != null && error.col != null
|
|
257
|
+
? `\n\nat ${error.source}:${error.line}:${error.col}`
|
|
258
|
+
: "";
|
|
259
|
+
return `${error.message}${location}${error.stack ? `\n\n${error.stack}` : ""}`.trim();
|
|
260
|
+
}
|
|
261
|
+
function isVueUnhandledWarning(type, message) {
|
|
262
|
+
if (type !== "warning")
|
|
263
|
+
return false;
|
|
264
|
+
return /\[Vue warn\]: Unhandled error during execution/i.test(message);
|
|
265
|
+
}
|
|
210
266
|
export async function logs() {
|
|
211
267
|
if (session.consoleLogs.length === 0)
|
|
212
268
|
return "no logs";
|
|
@@ -220,6 +276,7 @@ export async function screenshot() {
|
|
|
220
276
|
}
|
|
221
277
|
export async function evaluate(script) {
|
|
222
278
|
const currentPage = requireCurrentPage();
|
|
279
|
+
await currentPage.evaluate(initBrowserEventCollector);
|
|
223
280
|
const result = await currentPage.evaluate(script);
|
|
224
281
|
return JSON.stringify(result, null, 2);
|
|
225
282
|
}
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
|
+
import { realpathSync } from "node:fs";
|
|
4
|
+
import { resolve } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
3
6
|
import { send } from "./client.js";
|
|
7
|
+
class CliExit extends Error {
|
|
8
|
+
code;
|
|
9
|
+
constructor(code) {
|
|
10
|
+
super(`CLI_EXIT:${code}`);
|
|
11
|
+
this.code = code;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
4
14
|
export function normalizeUrl(value) {
|
|
5
15
|
if (value.includes("://"))
|
|
6
16
|
return value;
|
|
@@ -195,71 +205,101 @@ export function exit(io, res, msg) {
|
|
|
195
205
|
io.exit(0);
|
|
196
206
|
}
|
|
197
207
|
export function printUsage() {
|
|
198
|
-
return `
|
|
199
|
-
vite-browser - Programmatic access to Vue/React/Svelte DevTools and Vite dev server
|
|
200
|
-
|
|
201
|
-
USAGE
|
|
202
|
-
vite-browser <command> [options]
|
|
203
|
-
|
|
204
|
-
BROWSER CONTROL
|
|
205
|
-
open <url> [--cookies-json <file>] Launch browser and navigate
|
|
206
|
-
close Close browser and daemon
|
|
207
|
-
goto <url> Full-page navigation
|
|
208
|
-
back Go back in history
|
|
209
|
-
reload Reload current page
|
|
210
|
-
|
|
211
|
-
FRAMEWORK DETECTION
|
|
212
|
-
detect Detect framework (vue/react/svelte)
|
|
213
|
-
|
|
214
|
-
VUE COMMANDS
|
|
215
|
-
vue tree [id] Show Vue component tree or inspect component
|
|
216
|
-
vue pinia [store] Show Pinia stores or inspect specific store
|
|
217
|
-
vue router Show Vue Router information
|
|
218
|
-
|
|
219
|
-
REACT COMMANDS
|
|
220
|
-
react tree [id] Show React component tree or inspect component
|
|
221
|
-
|
|
222
|
-
SVELTE COMMANDS
|
|
223
|
-
svelte tree [id] Show Svelte component tree or inspect component
|
|
224
|
-
|
|
225
|
-
VITE COMMANDS
|
|
226
|
-
vite restart Restart Vite dev server
|
|
227
|
-
vite hmr Show HMR summary
|
|
228
|
-
vite hmr trace [--limit <n>] Show HMR timeline
|
|
229
|
-
vite hmr clear Clear tracked HMR timeline
|
|
230
|
-
vite runtime Show Vite runtime status
|
|
231
|
-
vite module-graph [--filter <txt>] [--limit <n>]
|
|
232
|
-
Show loaded Vite module resources
|
|
233
|
-
vite module-graph trace [--filter <txt>] [--limit <n>]
|
|
234
|
-
Show module additions/removals since baseline
|
|
235
|
-
vite module-graph clear Clear module-graph baseline
|
|
236
|
-
errors Show build/runtime errors
|
|
237
|
-
errors --mapped Show errors with source-map mapping
|
|
238
|
-
errors --mapped --inline-source Include mapped source snippets
|
|
239
|
-
correlate errors [--window <ms>] Correlate current errors with recent HMR events
|
|
240
|
-
correlate renders [--window <ms>] Summarize recent render/update propagation evidence
|
|
241
|
-
correlate errors --mapped Correlate mapped errors with recent HMR events
|
|
242
|
-
diagnose hmr [--window <ms>] Diagnose HMR failures from runtime, errors, and trace data
|
|
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
|
|
246
|
-
logs Show dev server logs
|
|
247
|
-
|
|
248
|
-
UTILITIES
|
|
249
|
-
screenshot Save screenshot to temp file
|
|
250
|
-
eval <script> Evaluate JavaScript in page context
|
|
251
|
-
network [idx] List network requests or inspect one
|
|
252
|
-
|
|
253
|
-
OPTIONS
|
|
254
|
-
-h, --help Show this help message
|
|
208
|
+
return `
|
|
209
|
+
vite-browser - Programmatic access to Vue/React/Svelte DevTools and Vite dev server
|
|
210
|
+
|
|
211
|
+
USAGE
|
|
212
|
+
vite-browser <command> [options]
|
|
213
|
+
|
|
214
|
+
BROWSER CONTROL
|
|
215
|
+
open <url> [--cookies-json <file>] Launch browser and navigate
|
|
216
|
+
close Close browser and daemon
|
|
217
|
+
goto <url> Full-page navigation
|
|
218
|
+
back Go back in history
|
|
219
|
+
reload Reload current page
|
|
220
|
+
|
|
221
|
+
FRAMEWORK DETECTION
|
|
222
|
+
detect Detect framework (vue/react/svelte)
|
|
223
|
+
|
|
224
|
+
VUE COMMANDS
|
|
225
|
+
vue tree [id] Show Vue component tree or inspect component
|
|
226
|
+
vue pinia [store] Show Pinia stores or inspect specific store
|
|
227
|
+
vue router Show Vue Router information
|
|
228
|
+
|
|
229
|
+
REACT COMMANDS
|
|
230
|
+
react tree [id] Show React component tree or inspect component
|
|
231
|
+
|
|
232
|
+
SVELTE COMMANDS
|
|
233
|
+
svelte tree [id] Show Svelte component tree or inspect component
|
|
234
|
+
|
|
235
|
+
VITE COMMANDS
|
|
236
|
+
vite restart Restart Vite dev server
|
|
237
|
+
vite hmr Show HMR summary
|
|
238
|
+
vite hmr trace [--limit <n>] Show HMR timeline
|
|
239
|
+
vite hmr clear Clear tracked HMR timeline
|
|
240
|
+
vite runtime Show Vite runtime status
|
|
241
|
+
vite module-graph [--filter <txt>] [--limit <n>]
|
|
242
|
+
Show loaded Vite module resources
|
|
243
|
+
vite module-graph trace [--filter <txt>] [--limit <n>]
|
|
244
|
+
Show module additions/removals since baseline
|
|
245
|
+
vite module-graph clear Clear module-graph baseline
|
|
246
|
+
errors Show build/runtime errors
|
|
247
|
+
errors --mapped Show errors with source-map mapping
|
|
248
|
+
errors --mapped --inline-source Include mapped source snippets
|
|
249
|
+
correlate errors [--window <ms>] Correlate current errors with recent HMR events
|
|
250
|
+
correlate renders [--window <ms>] Summarize recent render/update propagation evidence
|
|
251
|
+
correlate errors --mapped Correlate mapped errors with recent HMR events
|
|
252
|
+
diagnose hmr [--window <ms>] Diagnose HMR failures from runtime, errors, and trace data
|
|
253
|
+
diagnose hmr [--limit <n>] Control how many recent HMR trace entries are inspected
|
|
254
|
+
diagnose propagation [--window <ms>]
|
|
255
|
+
Diagnose likely update -> render -> error propagation
|
|
256
|
+
logs Show dev server logs
|
|
257
|
+
|
|
258
|
+
UTILITIES
|
|
259
|
+
screenshot Save screenshot to temp file
|
|
260
|
+
eval <script> Evaluate JavaScript in page context
|
|
261
|
+
network [idx] List network requests or inspect one
|
|
262
|
+
|
|
263
|
+
OPTIONS
|
|
264
|
+
-h, --help Show this help message
|
|
255
265
|
`;
|
|
256
266
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
267
|
+
function isEntrypoint(argv1) {
|
|
268
|
+
if (!argv1)
|
|
269
|
+
return false;
|
|
270
|
+
try {
|
|
271
|
+
const current = realpathSync(fileURLToPath(import.meta.url));
|
|
272
|
+
const target = realpathSync(resolve(argv1));
|
|
273
|
+
return current === target;
|
|
274
|
+
}
|
|
275
|
+
catch {
|
|
276
|
+
return Boolean(argv1 && import.meta.url.endsWith(argv1.replaceAll("\\", "/")));
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
async function main() {
|
|
280
|
+
try {
|
|
281
|
+
await runCli(process.argv, {
|
|
282
|
+
send,
|
|
283
|
+
readFile: readFileSync,
|
|
284
|
+
stdout: (text) => process.stdout.write(`${text}\n`),
|
|
285
|
+
stderr: (text) => process.stderr.write(`${text}\n`),
|
|
286
|
+
exit: ((code) => {
|
|
287
|
+
throw new CliExit(code);
|
|
288
|
+
}),
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
catch (error) {
|
|
292
|
+
if (error instanceof CliExit) {
|
|
293
|
+
process.exitCode = error.code;
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
throw error;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (isEntrypoint(process.argv[1])) {
|
|
300
|
+
void main().catch((error) => {
|
|
301
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
302
|
+
console.error(message);
|
|
303
|
+
process.exit(1);
|
|
264
304
|
});
|
|
265
305
|
}
|
package/dist/daemon.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createServer } from "node:net";
|
|
2
2
|
import { mkdirSync, writeFileSync, rmSync } from "node:fs";
|
|
3
|
+
import { setTimeout as sleep } from "node:timers/promises";
|
|
3
4
|
import { fileURLToPath } from "node:url";
|
|
4
5
|
import * as browser from "./browser.js";
|
|
5
6
|
import { correlateErrorWithHMR, formatErrorCorrelationReport } from "./correlate.js";
|
|
@@ -18,19 +19,8 @@ export function cleanError(err) {
|
|
|
18
19
|
}
|
|
19
20
|
export function createRunner(api = browser) {
|
|
20
21
|
return async function run(cmd) {
|
|
21
|
-
// Flush browser events to daemon queue before processing command
|
|
22
22
|
const queue = api.getEventQueue();
|
|
23
|
-
|
|
24
|
-
try {
|
|
25
|
-
const currentPage = api.getCurrentPage();
|
|
26
|
-
if (currentPage) {
|
|
27
|
-
await api.flushBrowserEvents(currentPage, queue);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
catch {
|
|
31
|
-
// Ignore flush errors (page might not be open yet)
|
|
32
|
-
}
|
|
33
|
-
}
|
|
23
|
+
await flushCurrentPageEvents(api, queue);
|
|
34
24
|
// Browser control
|
|
35
25
|
if (cmd.action === "open") {
|
|
36
26
|
await api.open(cmd.url);
|
|
@@ -114,8 +104,8 @@ export function createRunner(api = browser) {
|
|
|
114
104
|
return { ok: true, data };
|
|
115
105
|
}
|
|
116
106
|
if (cmd.action === "correlate-renders") {
|
|
117
|
-
const events =
|
|
118
|
-
const data = formatPropagationTraceReport(
|
|
107
|
+
const events = await getSettledEventWindow(api, queue, cmd.windowMs ?? 5000);
|
|
108
|
+
const data = formatPropagationTraceReport(await buildPropagationTrace(api, events));
|
|
119
109
|
return { ok: true, data };
|
|
120
110
|
}
|
|
121
111
|
if (cmd.action === "diagnose-hmr") {
|
|
@@ -128,8 +118,8 @@ export function createRunner(api = browser) {
|
|
|
128
118
|
return { ok: true, data };
|
|
129
119
|
}
|
|
130
120
|
if (cmd.action === "diagnose-propagation") {
|
|
131
|
-
const events =
|
|
132
|
-
const data = formatPropagationDiagnosisReport(diagnosePropagation(
|
|
121
|
+
const events = await getSettledEventWindow(api, queue, cmd.windowMs ?? 5000);
|
|
122
|
+
const data = formatPropagationDiagnosisReport(diagnosePropagation(await buildPropagationTrace(api, events)));
|
|
133
123
|
return { ok: true, data };
|
|
134
124
|
}
|
|
135
125
|
if (cmd.action === "logs") {
|
|
@@ -143,6 +133,10 @@ export function createRunner(api = browser) {
|
|
|
143
133
|
}
|
|
144
134
|
if (cmd.action === "eval") {
|
|
145
135
|
const data = await api.evaluate(cmd.script);
|
|
136
|
+
await settleCurrentPage(api, 120);
|
|
137
|
+
await flushCurrentPageEvents(api, queue);
|
|
138
|
+
await settleCurrentPage(api, 180);
|
|
139
|
+
await flushCurrentPageEvents(api, queue);
|
|
146
140
|
return { ok: true, data };
|
|
147
141
|
}
|
|
148
142
|
if (cmd.action === "network") {
|
|
@@ -152,6 +146,64 @@ export function createRunner(api = browser) {
|
|
|
152
146
|
return { ok: false, error: `unknown action: ${cmd.action}` };
|
|
153
147
|
};
|
|
154
148
|
}
|
|
149
|
+
async function buildPropagationTrace(api, events) {
|
|
150
|
+
const trace = correlateRenderPropagation(events);
|
|
151
|
+
if (!trace)
|
|
152
|
+
return null;
|
|
153
|
+
if (trace.errorMessages.length > 0)
|
|
154
|
+
return trace;
|
|
155
|
+
const currentError = String(await api.errors(false, false));
|
|
156
|
+
if (!currentError || currentError === "no errors")
|
|
157
|
+
return trace;
|
|
158
|
+
return {
|
|
159
|
+
...trace,
|
|
160
|
+
errorMessages: [currentError, ...trace.errorMessages],
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
async function flushCurrentPageEvents(api, queue) {
|
|
164
|
+
if (!queue)
|
|
165
|
+
return;
|
|
166
|
+
try {
|
|
167
|
+
const currentPage = api.getCurrentPage();
|
|
168
|
+
if (currentPage) {
|
|
169
|
+
await api.flushBrowserEvents(currentPage, queue);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
// Ignore flush errors (page might not be open yet)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
async function getSettledEventWindow(api, queue, windowMs) {
|
|
177
|
+
if (!queue)
|
|
178
|
+
return [];
|
|
179
|
+
let events = queue.window(windowMs);
|
|
180
|
+
if (hasPropagationSignals(events))
|
|
181
|
+
return events;
|
|
182
|
+
for (const delayMs of [120, 300]) {
|
|
183
|
+
await settleCurrentPage(api, delayMs);
|
|
184
|
+
await flushCurrentPageEvents(api, queue);
|
|
185
|
+
events = queue.window(windowMs);
|
|
186
|
+
if (hasPropagationSignals(events))
|
|
187
|
+
return events;
|
|
188
|
+
}
|
|
189
|
+
return events;
|
|
190
|
+
}
|
|
191
|
+
function hasPropagationSignals(events) {
|
|
192
|
+
return events.some((event) => event.type === "render" || event.type === "store-update");
|
|
193
|
+
}
|
|
194
|
+
async function settleCurrentPage(api, delayMs) {
|
|
195
|
+
const currentPage = api.getCurrentPage();
|
|
196
|
+
if (!currentPage) {
|
|
197
|
+
await sleep(delayMs);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
try {
|
|
201
|
+
await currentPage.waitForTimeout(delayMs);
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
await sleep(delayMs);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
155
207
|
export async function dispatchLine(line, socket, run = createRunner(), onClose) {
|
|
156
208
|
let cmd;
|
|
157
209
|
try {
|
package/dist/event-analysis.d.ts
CHANGED
|
@@ -27,6 +27,8 @@ export declare function getRenderLabel(payload: RenderEventPayload): string;
|
|
|
27
27
|
export declare function getStoreName(payload: StoreUpdatePayload): string | null;
|
|
28
28
|
export declare function getChangedKeys(payload: StoreUpdatePayload): string[];
|
|
29
29
|
export declare function getStoreHints(payload: RenderEventPayload): string[];
|
|
30
|
+
export declare function getRenderReason(payload: RenderEventPayload): string | null;
|
|
31
|
+
export declare function getRenderChangedKeys(payload: RenderEventPayload): string[];
|
|
30
32
|
export declare function getNetworkUrl(payload: NetworkEventPayload): string | null;
|
|
31
33
|
export declare function getErrorMessage(payload: ErrorEventPayload): string | null;
|
|
32
34
|
export declare function uniqueStrings(values: string[]): string[];
|
package/dist/event-analysis.js
CHANGED
|
@@ -59,6 +59,12 @@ export function getChangedKeys(payload) {
|
|
|
59
59
|
export function getStoreHints(payload) {
|
|
60
60
|
return payload.storeHints.filter((value) => value.length > 0);
|
|
61
61
|
}
|
|
62
|
+
export function getRenderReason(payload) {
|
|
63
|
+
return typeof payload.reason === "string" && payload.reason.length > 0 ? payload.reason : null;
|
|
64
|
+
}
|
|
65
|
+
export function getRenderChangedKeys(payload) {
|
|
66
|
+
return payload.changedKeys.filter((value) => value.length > 0);
|
|
67
|
+
}
|
|
62
68
|
export function getNetworkUrl(payload) {
|
|
63
69
|
return payload.url.length > 0 ? payload.url : null;
|
|
64
70
|
}
|