@presto1314w/vite-devtools-browser 0.1.2 → 0.1.4

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/daemon.js CHANGED
@@ -1,134 +1,160 @@
1
1
  import { createServer } from "node:net";
2
2
  import { mkdirSync, writeFileSync, rmSync } from "node:fs";
3
+ import { fileURLToPath } from "node:url";
3
4
  import * as browser from "./browser.js";
4
5
  import { socketDir, socketPath, pidFile } from "./paths.js";
5
- mkdirSync(socketDir, { recursive: true, mode: 0o700 });
6
- removeSocketFile();
7
- rmSync(pidFile, { force: true });
8
- writeFileSync(pidFile, String(process.pid));
9
- const server = createServer((socket) => {
10
- let buffer = "";
11
- socket.on("data", (chunk) => {
12
- buffer += chunk;
13
- let newline;
14
- while ((newline = buffer.indexOf("\n")) >= 0) {
15
- const line = buffer.slice(0, newline);
16
- buffer = buffer.slice(newline + 1);
17
- if (line)
18
- dispatch(line, socket);
19
- }
20
- });
21
- socket.on("error", () => { });
22
- });
23
- server.listen(socketPath);
24
- process.on("SIGINT", shutdown);
25
- process.on("SIGTERM", shutdown);
26
- process.on("exit", cleanup);
27
- async function dispatch(line, socket) {
28
- const cmd = JSON.parse(line);
29
- const result = await run(cmd).catch((err) => ({ ok: false, error: cleanError(err) }));
30
- socket.write(JSON.stringify({ id: cmd.id, ...result }) + "\n");
31
- if (cmd.action === "close")
32
- setImmediate(shutdown);
33
- }
34
- function cleanError(err) {
6
+ export function cleanError(err) {
7
+ if (!(err instanceof Error))
8
+ return String(err);
35
9
  const msg = err.message;
36
10
  const m = msg.match(/^page\.\w+: (?:Error: )?(.+?)(?:\n|$)/);
37
11
  return m ? m[1] : msg;
38
12
  }
39
- async function run(cmd) {
40
- // Browser control
41
- if (cmd.action === "open") {
42
- await browser.open(cmd.url);
43
- return { ok: true };
44
- }
45
- if (cmd.action === "cookies") {
46
- const data = await browser.cookies(cmd.cookies, cmd.domain);
47
- return { ok: true, data };
48
- }
49
- if (cmd.action === "close") {
50
- await browser.close();
51
- return { ok: true };
52
- }
53
- if (cmd.action === "goto") {
54
- const data = await browser.goto(cmd.url);
55
- return { ok: true, data };
56
- }
57
- if (cmd.action === "back") {
58
- await browser.back();
59
- return { ok: true };
60
- }
61
- if (cmd.action === "reload") {
62
- const data = await browser.reload();
63
- return { ok: true, data };
64
- }
65
- // Framework detection
66
- if (cmd.action === "detect") {
67
- const data = await browser.detectFramework();
68
- return { ok: true, data };
69
- }
70
- // Vue commands
71
- if (cmd.action === "vue-tree") {
72
- const data = await browser.vueTree(cmd.id);
73
- return { ok: true, data };
74
- }
75
- if (cmd.action === "vue-pinia") {
76
- const data = await browser.vuePinia(cmd.store);
77
- return { ok: true, data };
78
- }
79
- if (cmd.action === "vue-router") {
80
- const data = await browser.vueRouter();
81
- return { ok: true, data };
82
- }
83
- // React commands
84
- if (cmd.action === "react-tree") {
85
- const data = await browser.reactTree(cmd.id);
86
- return { ok: true, data };
87
- }
88
- // Svelte commands
89
- if (cmd.action === "svelte-tree") {
90
- const data = await browser.svelteTree(cmd.id);
91
- return { ok: true, data };
92
- }
93
- // Vite commands
94
- if (cmd.action === "vite-restart") {
95
- const data = await browser.viteRestart();
96
- return { ok: true, data };
97
- }
98
- if (cmd.action === "vite-hmr") {
99
- const data = await browser.viteHMR();
100
- return { ok: true, data };
101
- }
102
- if (cmd.action === "errors") {
103
- const data = await browser.errors();
104
- return { ok: true, data };
105
- }
106
- if (cmd.action === "logs") {
107
- const data = await browser.logs();
108
- return { ok: true, data };
109
- }
110
- // Utilities
111
- if (cmd.action === "screenshot") {
112
- const data = await browser.screenshot();
113
- return { ok: true, data };
114
- }
115
- if (cmd.action === "eval") {
116
- const data = await browser.evaluate(cmd.script);
117
- return { ok: true, data };
13
+ export function createRunner(api = browser) {
14
+ return async function run(cmd) {
15
+ // Browser control
16
+ if (cmd.action === "open") {
17
+ await api.open(cmd.url);
18
+ return { ok: true };
19
+ }
20
+ if (cmd.action === "cookies") {
21
+ const data = await api.cookies(cmd.cookies, cmd.domain);
22
+ return { ok: true, data };
23
+ }
24
+ if (cmd.action === "close") {
25
+ await api.close();
26
+ return { ok: true };
27
+ }
28
+ if (cmd.action === "goto") {
29
+ const data = await api.goto(cmd.url);
30
+ return { ok: true, data };
31
+ }
32
+ if (cmd.action === "back") {
33
+ await api.back();
34
+ return { ok: true };
35
+ }
36
+ if (cmd.action === "reload") {
37
+ const data = await api.reload();
38
+ return { ok: true, data };
39
+ }
40
+ // Framework detection
41
+ if (cmd.action === "detect") {
42
+ const data = await api.detectFramework();
43
+ return { ok: true, data };
44
+ }
45
+ // Vue commands
46
+ if (cmd.action === "vue-tree") {
47
+ const data = await api.vueTree(cmd.id);
48
+ return { ok: true, data };
49
+ }
50
+ if (cmd.action === "vue-pinia") {
51
+ const data = await api.vuePinia(cmd.store);
52
+ return { ok: true, data };
53
+ }
54
+ if (cmd.action === "vue-router") {
55
+ const data = await api.vueRouter();
56
+ return { ok: true, data };
57
+ }
58
+ // React commands
59
+ if (cmd.action === "react-tree") {
60
+ const data = await api.reactTree(cmd.id);
61
+ return { ok: true, data };
62
+ }
63
+ // Svelte commands
64
+ if (cmd.action === "svelte-tree") {
65
+ const data = await api.svelteTree(cmd.id);
66
+ return { ok: true, data };
67
+ }
68
+ // Vite commands
69
+ if (cmd.action === "vite-restart") {
70
+ const data = await api.viteRestart();
71
+ return { ok: true, data };
72
+ }
73
+ if (cmd.action === "vite-hmr") {
74
+ const hmrMode = cmd.mode === "trace" || cmd.mode === "clear" ? cmd.mode : "summary";
75
+ const data = await api.viteHMRTrace(hmrMode, cmd.limit ?? 20);
76
+ return { ok: true, data };
77
+ }
78
+ if (cmd.action === "vite-runtime") {
79
+ const data = await api.viteRuntimeStatus();
80
+ return { ok: true, data };
81
+ }
82
+ if (cmd.action === "vite-module-graph") {
83
+ const graphMode = cmd.mode === "trace" || cmd.mode === "clear" ? cmd.mode : "snapshot";
84
+ const data = await api.viteModuleGraph(cmd.filter, cmd.limit ?? 200, graphMode);
85
+ return { ok: true, data };
86
+ }
87
+ if (cmd.action === "errors") {
88
+ const data = await api.errors(Boolean(cmd.mapped), Boolean(cmd.inlineSource));
89
+ return { ok: true, data };
90
+ }
91
+ if (cmd.action === "logs") {
92
+ const data = await api.logs();
93
+ return { ok: true, data };
94
+ }
95
+ // Utilities
96
+ if (cmd.action === "screenshot") {
97
+ const data = await api.screenshot();
98
+ return { ok: true, data };
99
+ }
100
+ if (cmd.action === "eval") {
101
+ const data = await api.evaluate(cmd.script);
102
+ return { ok: true, data };
103
+ }
104
+ if (cmd.action === "network") {
105
+ const data = await api.network(cmd.idx);
106
+ return { ok: true, data };
107
+ }
108
+ return { ok: false, error: `unknown action: ${cmd.action}` };
109
+ };
110
+ }
111
+ export async function dispatchLine(line, socket, run = createRunner(), onClose) {
112
+ let cmd;
113
+ try {
114
+ cmd = JSON.parse(line);
118
115
  }
119
- if (cmd.action === "network") {
120
- const data = await browser.network(cmd.idx);
121
- return { ok: true, data };
116
+ catch {
117
+ socket.write(JSON.stringify({ ok: false, error: "invalid command payload" }) + "\n");
118
+ return;
122
119
  }
123
- return { ok: false, error: `unknown action: ${cmd.action}` };
124
- }
125
- function shutdown() {
126
- cleanup();
127
- process.exit(0);
120
+ const result = await run(cmd).catch((err) => ({ ok: false, error: cleanError(err) }));
121
+ socket.write(JSON.stringify({ id: cmd.id, ...result }) + "\n");
122
+ if (cmd.action === "close")
123
+ setImmediate(() => onClose?.());
128
124
  }
129
- function cleanup() {
125
+ export function startDaemon() {
126
+ const run = createRunner();
127
+ mkdirSync(socketDir, { recursive: true, mode: 0o700 });
130
128
  removeSocketFile();
131
129
  rmSync(pidFile, { force: true });
130
+ writeFileSync(pidFile, String(process.pid));
131
+ const server = createServer((socket) => {
132
+ let buffer = "";
133
+ socket.on("data", (chunk) => {
134
+ buffer += chunk;
135
+ let newline;
136
+ while ((newline = buffer.indexOf("\n")) >= 0) {
137
+ const line = buffer.slice(0, newline);
138
+ buffer = buffer.slice(newline + 1);
139
+ if (line) {
140
+ void dispatchLine(line, socket, run, shutdown);
141
+ }
142
+ }
143
+ });
144
+ socket.on("error", () => { });
145
+ });
146
+ server.listen(socketPath);
147
+ process.on("SIGINT", shutdown);
148
+ process.on("SIGTERM", shutdown);
149
+ process.on("exit", cleanup);
150
+ function shutdown() {
151
+ cleanup();
152
+ process.exit(0);
153
+ }
154
+ function cleanup() {
155
+ removeSocketFile();
156
+ rmSync(pidFile, { force: true });
157
+ }
132
158
  }
133
159
  function removeSocketFile() {
134
160
  // Windows named pipes are not filesystem entries, so unlinking them fails with EPERM.
@@ -136,3 +162,6 @@ function removeSocketFile() {
136
162
  return;
137
163
  rmSync(socketPath, { force: true });
138
164
  }
165
+ if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
166
+ startDaemon();
167
+ }
@@ -14,3 +14,16 @@ export declare function snapshot(page: Page): Promise<ReactNode[]>;
14
14
  export declare function inspect(page: Page, id: number): Promise<ReactInspection>;
15
15
  export declare function format(nodes: ReactNode[]): string;
16
16
  export declare function path(nodes: ReactNode[], id: number): string;
17
+ export declare function typeName(type: number): string;
18
+ export declare function decodeOperations(ops: number[]): ReactNode[];
19
+ export declare function skipOperation(op: number, ops: number[], i: number): number;
20
+ export declare function rectCount(n: number): number;
21
+ export declare function suspenseSkip(ops: number[], i: number): number;
22
+ export declare function previewValue(v: unknown): string;
23
+ export declare function formatHookLine(h: {
24
+ id: number | null;
25
+ name: string;
26
+ value: unknown;
27
+ subHooks?: unknown[];
28
+ }): string;
29
+ export declare function formatInspectionResult(name: string, id: number, value: any): ReactInspection;
@@ -37,7 +37,7 @@ export function path(nodes, id) {
37
37
  }
38
38
  return names.reverse().join(" > ");
39
39
  }
40
- function typeName(type) {
40
+ export function typeName(type) {
41
41
  const names = {
42
42
  11: "Root",
43
43
  12: "Suspense",
@@ -45,118 +45,102 @@ function typeName(type) {
45
45
  };
46
46
  return names[type] ?? `(${type})`;
47
47
  }
48
- async function inPageSnapshot() {
49
- const hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
50
- if (!hook)
51
- throw new Error("React DevTools hook not installed");
52
- const ri = hook.rendererInterfaces?.get?.(1);
53
- if (!ri)
54
- throw new Error("no React renderer attached");
55
- const batches = await collect(ri);
56
- return batches.flatMap(decode);
57
- function collect(ri) {
58
- return new Promise((resolve) => {
59
- const out = [];
60
- const listener = (e) => {
61
- const payload = e.data?.payload;
62
- if (e.data?.source === "react-devtools-bridge" && payload?.event === "operations") {
63
- out.push(payload.payload);
64
- }
65
- };
66
- window.addEventListener("message", listener);
67
- ri.flushInitialOperations();
68
- setTimeout(() => {
69
- window.removeEventListener("message", listener);
70
- resolve(out);
71
- }, 80);
72
- });
48
+ export function decodeOperations(ops) {
49
+ let i = 2;
50
+ const strings = [null];
51
+ const tableEnd = ++i + ops[i - 1];
52
+ while (i < tableEnd) {
53
+ const len = ops[i++];
54
+ strings.push(String.fromCodePoint(...ops.slice(i, i + len)));
55
+ i += len;
73
56
  }
74
- function decode(ops) {
75
- let i = 2;
76
- const strings = [null];
77
- const tableEnd = ++i + ops[i - 1];
78
- while (i < tableEnd) {
79
- const len = ops[i++];
80
- strings.push(String.fromCodePoint(...ops.slice(i, i + len)));
81
- i += len;
82
- }
83
- const nodes = [];
84
- while (i < ops.length) {
85
- const op = ops[i];
86
- if (op === 1) {
87
- const id = ops[i + 1];
88
- const type = ops[i + 2];
89
- i += 3;
90
- if (type === 11) {
91
- nodes.push({ id, type, name: null, key: null, parent: 0 });
92
- i += 4;
93
- }
94
- else {
95
- nodes.push({
96
- id,
97
- type,
98
- name: strings[ops[i + 2]] ?? null,
99
- key: strings[ops[i + 3]] ?? null,
100
- parent: ops[i],
101
- });
102
- i += 5;
103
- }
57
+ const nodes = [];
58
+ while (i < ops.length) {
59
+ const op = ops[i];
60
+ if (op === 1) {
61
+ const id = ops[i + 1];
62
+ const type = ops[i + 2];
63
+ i += 3;
64
+ if (type === 11) {
65
+ nodes.push({ id, type, name: null, key: null, parent: 0 });
66
+ i += 4;
104
67
  }
105
68
  else {
106
- i += skip(op, ops, i);
69
+ nodes.push({
70
+ id,
71
+ type,
72
+ name: strings[ops[i + 2]] ?? null,
73
+ key: strings[ops[i + 3]] ?? null,
74
+ parent: ops[i],
75
+ });
76
+ i += 5;
107
77
  }
108
78
  }
109
- return nodes;
79
+ else {
80
+ i += skipOperation(op, ops, i);
81
+ }
110
82
  }
111
- function skip(op, ops, i) {
112
- if (op === 2)
113
- return 2 + ops[i + 1];
114
- if (op === 3)
115
- return 3 + ops[i + 2];
116
- if (op === 4)
117
- return 3;
118
- if (op === 5)
119
- return 4;
120
- if (op === 6)
121
- return 1;
122
- if (op === 7)
123
- return 3;
124
- if (op === 8)
125
- return 6 + rects(ops[i + 5]);
126
- if (op === 9)
127
- return 2 + ops[i + 1];
128
- if (op === 10)
129
- return 3 + ops[i + 2];
130
- if (op === 11)
131
- return 3 + rects(ops[i + 2]);
132
- if (op === 12)
133
- return suspenders(ops, i);
134
- if (op === 13)
135
- return 2;
83
+ return nodes;
84
+ }
85
+ export function skipOperation(op, ops, i) {
86
+ if (op === 2)
87
+ return 2 + ops[i + 1];
88
+ if (op === 3)
89
+ return 3 + ops[i + 2];
90
+ if (op === 4)
91
+ return 3;
92
+ if (op === 5)
93
+ return 4;
94
+ if (op === 6)
136
95
  return 1;
137
- }
138
- function rects(n) {
139
- return n === -1 ? 0 : n * 4;
140
- }
141
- function suspenders(ops, i) {
142
- let j = i + 2;
143
- for (let c = 0; c < ops[i + 1]; c++)
144
- j += 5 + ops[j + 4];
145
- return j - i;
146
- }
96
+ if (op === 7)
97
+ return 3;
98
+ if (op === 8)
99
+ return 6 + rectCount(ops[i + 5]);
100
+ if (op === 9)
101
+ return 2 + ops[i + 1];
102
+ if (op === 10)
103
+ return 3 + ops[i + 2];
104
+ if (op === 11)
105
+ return 3 + rectCount(ops[i + 2]);
106
+ if (op === 12)
107
+ return suspenseSkip(ops, i);
108
+ if (op === 13)
109
+ return 2;
110
+ return 1;
147
111
  }
148
- function inPageInspect(id) {
149
- const hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
150
- const ri = hook?.rendererInterfaces?.get?.(1);
151
- if (!ri)
152
- throw new Error("no React renderer attached");
153
- if (!ri.hasElementWithId(id))
154
- throw new Error(`element ${id} not found (page reloaded?)`);
155
- const result = ri.inspectElement(1, id, null, true);
156
- if (result?.type !== "full-data")
157
- throw new Error(`inspect failed: ${result?.type}`);
158
- const value = result.value;
159
- const name = ri.getDisplayNameForElementID(id);
112
+ export function rectCount(n) {
113
+ return n === -1 ? 0 : n * 4;
114
+ }
115
+ export function suspenseSkip(ops, i) {
116
+ let j = i + 2;
117
+ for (let c = 0; c < ops[i + 1]; c++)
118
+ j += 5 + ops[j + 4];
119
+ return j - i;
120
+ }
121
+ export function previewValue(v) {
122
+ if (v == null)
123
+ return String(v);
124
+ if (typeof v !== "object")
125
+ return JSON.stringify(v);
126
+ const d = v;
127
+ if (d.type === "undefined")
128
+ return "undefined";
129
+ if (d.preview_long)
130
+ return d.preview_long;
131
+ if (d.preview_short)
132
+ return d.preview_short;
133
+ if (Array.isArray(v))
134
+ return `[${v.map(previewValue).join(", ")}]`;
135
+ const entries = Object.entries(v).map(([k, val]) => `${k}: ${previewValue(val)}`);
136
+ return `{${entries.join(", ")}}`;
137
+ }
138
+ export function formatHookLine(h) {
139
+ const idx = h.id != null ? `[${h.id}] ` : "";
140
+ const sub = h.subHooks?.length ? ` (${h.subHooks.length} sub)` : "";
141
+ return `${idx}${h.name}: ${previewValue(h.value)}${sub}`;
142
+ }
143
+ export function formatInspectionResult(name, id, value) {
160
144
  const lines = [`${name} #${id}`];
161
145
  if (value.key != null)
162
146
  lines.push(`key: ${JSON.stringify(value.key)}`);
@@ -181,7 +165,7 @@ function inPageInspect(id) {
181
165
  return;
182
166
  lines.push(`${label}:`);
183
167
  for (const item of data)
184
- lines.push(` ${hookLine(item)}`);
168
+ lines.push(` ${formatHookLine(item)}`);
185
169
  return;
186
170
  }
187
171
  if (typeof data === "object") {
@@ -190,29 +174,47 @@ function inPageInspect(id) {
190
174
  return;
191
175
  lines.push(`${label}:`);
192
176
  for (const [k, v] of entries)
193
- lines.push(` ${k}: ${preview(v)}`);
177
+ lines.push(` ${k}: ${previewValue(v)}`);
194
178
  }
195
179
  }
196
- function hookLine(h) {
197
- const idx = h.id != null ? `[${h.id}] ` : "";
198
- const sub = h.subHooks?.length ? ` (${h.subHooks.length} sub)` : "";
199
- return `${idx}${h.name}: ${preview(h.value)}${sub}`;
200
- }
201
- function preview(v) {
202
- if (v == null)
203
- return String(v);
204
- if (typeof v !== "object")
205
- return JSON.stringify(v);
206
- const d = v;
207
- if (d.type === "undefined")
208
- return "undefined";
209
- if (d.preview_long)
210
- return d.preview_long;
211
- if (d.preview_short)
212
- return d.preview_short;
213
- if (Array.isArray(v))
214
- return `[${v.map(preview).join(", ")}]`;
215
- const entries = Object.entries(v).map(([k, val]) => `${k}: ${preview(val)}`);
216
- return `{${entries.join(", ")}}`;
180
+ }
181
+ async function inPageSnapshot() {
182
+ const hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
183
+ if (!hook)
184
+ throw new Error("React DevTools hook not installed");
185
+ const ri = hook.rendererInterfaces?.get?.(1);
186
+ if (!ri)
187
+ throw new Error("no React renderer attached");
188
+ const batches = await collect(ri);
189
+ return batches.flatMap(decodeOperations);
190
+ function collect(ri) {
191
+ return new Promise((resolve) => {
192
+ const out = [];
193
+ const listener = (e) => {
194
+ const payload = e.data?.payload;
195
+ if (e.data?.source === "react-devtools-bridge" && payload?.event === "operations") {
196
+ out.push(payload.payload);
197
+ }
198
+ };
199
+ window.addEventListener("message", listener);
200
+ ri.flushInitialOperations();
201
+ setTimeout(() => {
202
+ window.removeEventListener("message", listener);
203
+ resolve(out);
204
+ }, 80);
205
+ });
217
206
  }
218
207
  }
208
+ function inPageInspect(id) {
209
+ const hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
210
+ const ri = hook?.rendererInterfaces?.get?.(1);
211
+ if (!ri)
212
+ throw new Error("no React renderer attached");
213
+ if (!ri.hasElementWithId(id))
214
+ throw new Error(`element ${id} not found (page reloaded?)`);
215
+ const result = ri.inspectElement(1, id, null, true);
216
+ if (result?.type !== "full-data")
217
+ throw new Error(`inspect failed: ${result?.type}`);
218
+ const name = ri.getDisplayNameForElementID(id);
219
+ return formatInspectionResult(name, id, result.value);
220
+ }
@@ -0,0 +1,14 @@
1
+ import { SourceMapConsumer } from "source-map-js";
2
+ type MappedLocation = {
3
+ file: string;
4
+ line: number;
5
+ column: number;
6
+ snippet?: string;
7
+ };
8
+ type FetchImpl = typeof fetch;
9
+ export declare function resolveViaSourceMap(origin: string, fileUrl: string, line: number, column: number, includeSnippet?: boolean, fetchImpl?: FetchImpl): Promise<MappedLocation | null>;
10
+ export declare function clearSourceMapCache(): void;
11
+ export declare function buildMapCandidates(origin: string, fileUrl: string): string[];
12
+ export declare function cleanSource(source: string): string;
13
+ export declare function snippetFor(consumer: SourceMapConsumer, source: string, line: number): string | undefined;
14
+ export {};