react-perf-recorder 0.1.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.
Files changed (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +65 -0
  3. package/claude/README.md +10 -0
  4. package/claude/agents/perf-recorder.md +44 -0
  5. package/claude/mcp.json +8 -0
  6. package/claude/skills/react-perf-recorder/SKILL.md +52 -0
  7. package/claude/skills/react-perf-recorder/references/causes-and-actions.md +47 -0
  8. package/claude/skills/react-perf-recorder/references/from-scripts.md +36 -0
  9. package/claude/skills/react-perf-recorder/references/getting-a-recording.md +41 -0
  10. package/claude/skills/react-perf-recorder/references/measuring-a-fix.md +42 -0
  11. package/claude/skills/react-perf-recorder/references/panel.md +21 -0
  12. package/claude/skills/react-perf-recorder/references/reading-a-recording.md +55 -0
  13. package/dist/browser/chunk-7DJUCCWG.js +447 -0
  14. package/dist/browser/chunk-NTY2W4HE.js +182 -0
  15. package/dist/browser/client.d.ts +589 -0
  16. package/dist/browser/client.js +7161 -0
  17. package/dist/browser/index-BlkKhwHe.d.ts +585 -0
  18. package/dist/browser/plugins/proxy-memoize.d.ts +7 -0
  19. package/dist/browser/plugins/proxy-memoize.js +41 -0
  20. package/dist/browser/plugins/react-query.d.ts +5 -0
  21. package/dist/browser/plugins/react-query.js +74 -0
  22. package/dist/browser/plugins/zustand.d.ts +11 -0
  23. package/dist/browser/plugins/zustand.js +171 -0
  24. package/dist/browser/runtime.d.ts +1 -0
  25. package/dist/browser/runtime.js +12 -0
  26. package/dist/cli.js +23119 -0
  27. package/dist/engine.iife.js +3661 -0
  28. package/dist/node/chunk-HS2BJBJX.js +170 -0
  29. package/dist/node/plugin-api-zXFxjYba.d.cts +61 -0
  30. package/dist/node/plugin-api-zXFxjYba.d.ts +61 -0
  31. package/dist/node/plugins/proxy-memoize.cjs +214 -0
  32. package/dist/node/plugins/proxy-memoize.d.cts +16 -0
  33. package/dist/node/plugins/proxy-memoize.d.ts +16 -0
  34. package/dist/node/plugins/proxy-memoize.js +51 -0
  35. package/dist/node/plugins/react-query.cjs +32 -0
  36. package/dist/node/plugins/react-query.d.cts +6 -0
  37. package/dist/node/plugins/react-query.d.ts +6 -0
  38. package/dist/node/plugins/react-query.js +7 -0
  39. package/dist/node/plugins/zustand.cjs +247 -0
  40. package/dist/node/plugins/zustand.d.cts +17 -0
  41. package/dist/node/plugins/zustand.d.ts +17 -0
  42. package/dist/node/plugins/zustand.js +61 -0
  43. package/dist/node/vite.cjs +1262 -0
  44. package/dist/node/vite.d.cts +103 -0
  45. package/dist/node/vite.d.ts +103 -0
  46. package/dist/node/vite.js +1072 -0
  47. package/docs/contributing.md +24 -0
  48. package/docs/how-it-works.md +34 -0
  49. package/docs/mcp.md +62 -0
  50. package/docs/measuring-a-fix.md +65 -0
  51. package/docs/options.md +22 -0
  52. package/docs/panel.md +59 -0
  53. package/docs/plugins.md +57 -0
  54. package/docs/recording.md +44 -0
  55. package/package.json +139 -0
@@ -0,0 +1,1072 @@
1
+ import {
2
+ appendLines,
3
+ combineProxies,
4
+ createFilter,
5
+ findDeclarations,
6
+ parseModule,
7
+ proxyModule,
8
+ scanModule
9
+ } from "./chunk-HS2BJBJX.js";
10
+
11
+ // src/vite/index.ts
12
+ import fs2 from "fs";
13
+ import { createRequire } from "module";
14
+ import path3 from "path";
15
+
16
+ // node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.mjs
17
+ var comma = ",".charCodeAt(0);
18
+ var semicolon = ";".charCodeAt(0);
19
+ var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
20
+ var intToChar = new Uint8Array(64);
21
+ var charToInt = new Uint8Array(128);
22
+ for (let i = 0; i < chars.length; i++) {
23
+ const c = chars.charCodeAt(i);
24
+ intToChar[i] = c;
25
+ charToInt[c] = i;
26
+ }
27
+ function decodeInteger(reader) {
28
+ let value = 0;
29
+ let shift = 0;
30
+ let integer = 0;
31
+ do {
32
+ const c = reader.next();
33
+ integer = charToInt[c];
34
+ value |= (integer & 31) << shift;
35
+ shift += 5;
36
+ } while (integer & 32);
37
+ return value;
38
+ }
39
+ function decodeSign(num) {
40
+ return num & 1 ? -2147483648 | -(num >>> 1) : num >>> 1;
41
+ }
42
+ function hasMoreVlq(reader, max) {
43
+ if (reader.pos >= max) return false;
44
+ return reader.peek() !== comma;
45
+ }
46
+ var bufLength = 1024 * 16;
47
+ var StringReader = class {
48
+ constructor(buffer) {
49
+ this.pos = 0;
50
+ this.buffer = buffer;
51
+ }
52
+ next() {
53
+ return this.buffer.charCodeAt(this.pos++);
54
+ }
55
+ peek() {
56
+ return this.buffer.charCodeAt(this.pos);
57
+ }
58
+ indexOf(char) {
59
+ const { buffer, pos } = this;
60
+ const idx = buffer.indexOf(char, pos);
61
+ return idx === -1 ? buffer.length : idx;
62
+ }
63
+ };
64
+ function decode(mappings) {
65
+ const { length } = mappings;
66
+ const reader = new StringReader(mappings);
67
+ const decoded = [];
68
+ let genColumn = 0;
69
+ let sourcesIndex = 0;
70
+ let sourceLine = 0;
71
+ let sourceColumn = 0;
72
+ let namesIndex = 0;
73
+ do {
74
+ const semi = reader.indexOf(";");
75
+ const line = [];
76
+ let sorted = true;
77
+ let lastCol = 0;
78
+ genColumn = 0;
79
+ while (reader.pos < semi) {
80
+ let seg;
81
+ genColumn += decodeSign(decodeInteger(reader));
82
+ if (genColumn < lastCol) sorted = false;
83
+ lastCol = genColumn;
84
+ if (hasMoreVlq(reader, semi)) {
85
+ sourcesIndex += decodeSign(decodeInteger(reader));
86
+ sourceLine += decodeSign(decodeInteger(reader));
87
+ sourceColumn += decodeSign(decodeInteger(reader));
88
+ if (hasMoreVlq(reader, semi)) {
89
+ namesIndex += decodeSign(decodeInteger(reader));
90
+ seg = [genColumn, sourcesIndex, sourceLine, sourceColumn, namesIndex];
91
+ } else {
92
+ seg = [genColumn, sourcesIndex, sourceLine, sourceColumn];
93
+ }
94
+ } else {
95
+ seg = [genColumn];
96
+ }
97
+ line.push(seg);
98
+ reader.pos++;
99
+ }
100
+ if (!sorted) sort(line);
101
+ decoded.push(line);
102
+ reader.pos = semi + 1;
103
+ } while (reader.pos <= length);
104
+ return decoded;
105
+ }
106
+ function sort(line) {
107
+ line.sort(sortComparator);
108
+ }
109
+ function sortComparator(a, b) {
110
+ return a[0] - b[0];
111
+ }
112
+
113
+ // node_modules/@jridgewell/resolve-uri/dist/resolve-uri.mjs
114
+ var schemeRegex = /^[\w+.-]+:\/\//;
115
+ var urlRegex = /^([\w+.-]+:)\/\/([^@/#?]*@)?([^:/#?]*)(:\d+)?(\/[^#?]*)?(\?[^#]*)?(#.*)?/;
116
+ var fileRegex = /^file:(?:\/\/((?![a-z]:)[^/#?]*)?)?(\/?[^#?]*)(\?[^#]*)?(#.*)?/i;
117
+ function isAbsoluteUrl(input) {
118
+ return schemeRegex.test(input);
119
+ }
120
+ function isSchemeRelativeUrl(input) {
121
+ return input.startsWith("//");
122
+ }
123
+ function isAbsolutePath(input) {
124
+ return input.startsWith("/");
125
+ }
126
+ function isFileUrl(input) {
127
+ return input.startsWith("file:");
128
+ }
129
+ function isRelative(input) {
130
+ return /^[.?#]/.test(input);
131
+ }
132
+ function parseAbsoluteUrl(input) {
133
+ const match = urlRegex.exec(input);
134
+ return makeUrl(match[1], match[2] || "", match[3], match[4] || "", match[5] || "/", match[6] || "", match[7] || "");
135
+ }
136
+ function parseFileUrl(input) {
137
+ const match = fileRegex.exec(input);
138
+ const path4 = match[2];
139
+ return makeUrl("file:", "", match[1] || "", "", isAbsolutePath(path4) ? path4 : "/" + path4, match[3] || "", match[4] || "");
140
+ }
141
+ function makeUrl(scheme, user, host, port, path4, query, hash) {
142
+ return {
143
+ scheme,
144
+ user,
145
+ host,
146
+ port,
147
+ path: path4,
148
+ query,
149
+ hash,
150
+ type: 7
151
+ };
152
+ }
153
+ function parseUrl(input) {
154
+ if (isSchemeRelativeUrl(input)) {
155
+ const url2 = parseAbsoluteUrl("http:" + input);
156
+ url2.scheme = "";
157
+ url2.type = 6;
158
+ return url2;
159
+ }
160
+ if (isAbsolutePath(input)) {
161
+ const url2 = parseAbsoluteUrl("http://foo.com" + input);
162
+ url2.scheme = "";
163
+ url2.host = "";
164
+ url2.type = 5;
165
+ return url2;
166
+ }
167
+ if (isFileUrl(input))
168
+ return parseFileUrl(input);
169
+ if (isAbsoluteUrl(input))
170
+ return parseAbsoluteUrl(input);
171
+ const url = parseAbsoluteUrl("http://foo.com/" + input);
172
+ url.scheme = "";
173
+ url.host = "";
174
+ url.type = input ? input.startsWith("?") ? 3 : input.startsWith("#") ? 2 : 4 : 1;
175
+ return url;
176
+ }
177
+ function stripPathFilename(path4) {
178
+ if (path4.endsWith("/.."))
179
+ return path4;
180
+ const index = path4.lastIndexOf("/");
181
+ return path4.slice(0, index + 1);
182
+ }
183
+ function mergePaths(url, base) {
184
+ normalizePath(base, base.type);
185
+ if (url.path === "/") {
186
+ url.path = base.path;
187
+ } else {
188
+ url.path = stripPathFilename(base.path) + url.path;
189
+ }
190
+ }
191
+ function normalizePath(url, type) {
192
+ const rel = type <= 4;
193
+ const pieces = url.path.split("/");
194
+ let pointer = 1;
195
+ let positive = 0;
196
+ let addTrailingSlash = false;
197
+ for (let i = 1; i < pieces.length; i++) {
198
+ const piece = pieces[i];
199
+ if (!piece) {
200
+ addTrailingSlash = true;
201
+ continue;
202
+ }
203
+ addTrailingSlash = false;
204
+ if (piece === ".")
205
+ continue;
206
+ if (piece === "..") {
207
+ if (positive) {
208
+ addTrailingSlash = true;
209
+ positive--;
210
+ pointer--;
211
+ } else if (rel) {
212
+ pieces[pointer++] = piece;
213
+ }
214
+ continue;
215
+ }
216
+ pieces[pointer++] = piece;
217
+ positive++;
218
+ }
219
+ let path4 = "";
220
+ for (let i = 1; i < pointer; i++) {
221
+ path4 += "/" + pieces[i];
222
+ }
223
+ if (!path4 || addTrailingSlash && !path4.endsWith("/..")) {
224
+ path4 += "/";
225
+ }
226
+ url.path = path4;
227
+ }
228
+ function resolve(input, base) {
229
+ if (!input && !base)
230
+ return "";
231
+ const url = parseUrl(input);
232
+ let inputType = url.type;
233
+ if (base && inputType !== 7) {
234
+ const baseUrl = parseUrl(base);
235
+ const baseType = baseUrl.type;
236
+ switch (inputType) {
237
+ case 1:
238
+ url.hash = baseUrl.hash;
239
+ // fall through
240
+ case 2:
241
+ url.query = baseUrl.query;
242
+ // fall through
243
+ case 3:
244
+ case 4:
245
+ mergePaths(url, baseUrl);
246
+ // fall through
247
+ case 5:
248
+ url.user = baseUrl.user;
249
+ url.host = baseUrl.host;
250
+ url.port = baseUrl.port;
251
+ // fall through
252
+ case 6:
253
+ url.scheme = baseUrl.scheme;
254
+ }
255
+ if (baseType > inputType)
256
+ inputType = baseType;
257
+ }
258
+ normalizePath(url, inputType);
259
+ const queryHash = url.query + url.hash;
260
+ switch (inputType) {
261
+ // This is impossible, because of the empty checks at the start of the function.
262
+ // case UrlType.Empty:
263
+ case 2:
264
+ case 3:
265
+ return queryHash;
266
+ case 4: {
267
+ const path4 = url.path.slice(1);
268
+ if (!path4)
269
+ return queryHash || ".";
270
+ if (isRelative(base || input) && !isRelative(path4)) {
271
+ return "./" + path4 + queryHash;
272
+ }
273
+ return path4 + queryHash;
274
+ }
275
+ case 5:
276
+ return url.path + queryHash;
277
+ default:
278
+ return url.scheme + "//" + url.user + url.host + url.port + url.path + queryHash;
279
+ }
280
+ }
281
+
282
+ // node_modules/@jridgewell/trace-mapping/dist/trace-mapping.mjs
283
+ function stripFilename(path4) {
284
+ if (!path4) return "";
285
+ const index = path4.lastIndexOf("/");
286
+ return path4.slice(0, index + 1);
287
+ }
288
+ function resolver(mapUrl, sourceRoot) {
289
+ const from = stripFilename(mapUrl);
290
+ const prefix = sourceRoot ? sourceRoot + "/" : "";
291
+ return (source) => resolve(prefix + (source || ""), from);
292
+ }
293
+ var COLUMN = 0;
294
+ var SOURCES_INDEX = 1;
295
+ var SOURCE_LINE = 2;
296
+ var SOURCE_COLUMN = 3;
297
+ var NAMES_INDEX = 4;
298
+ function maybeSort(mappings, owned) {
299
+ const unsortedIndex = nextUnsortedSegmentLine(mappings, 0);
300
+ if (unsortedIndex === mappings.length) return mappings;
301
+ if (!owned) mappings = mappings.slice();
302
+ for (let i = unsortedIndex; i < mappings.length; i = nextUnsortedSegmentLine(mappings, i + 1)) {
303
+ mappings[i] = sortSegments(mappings[i], owned);
304
+ }
305
+ return mappings;
306
+ }
307
+ function nextUnsortedSegmentLine(mappings, start) {
308
+ for (let i = start; i < mappings.length; i++) {
309
+ if (!isSorted(mappings[i])) return i;
310
+ }
311
+ return mappings.length;
312
+ }
313
+ function isSorted(line) {
314
+ for (let j = 1; j < line.length; j++) {
315
+ if (line[j][COLUMN] < line[j - 1][COLUMN]) {
316
+ return false;
317
+ }
318
+ }
319
+ return true;
320
+ }
321
+ function sortSegments(line, owned) {
322
+ if (!owned) line = line.slice();
323
+ return line.sort(sortComparator2);
324
+ }
325
+ function sortComparator2(a, b) {
326
+ return a[COLUMN] - b[COLUMN];
327
+ }
328
+ var found = false;
329
+ function binarySearch(haystack, needle, low, high) {
330
+ while (low <= high) {
331
+ const mid = low + (high - low >> 1);
332
+ const cmp = haystack[mid][COLUMN] - needle;
333
+ if (cmp === 0) {
334
+ found = true;
335
+ return mid;
336
+ }
337
+ if (cmp < 0) {
338
+ low = mid + 1;
339
+ } else {
340
+ high = mid - 1;
341
+ }
342
+ }
343
+ found = false;
344
+ return low - 1;
345
+ }
346
+ function upperBound(haystack, needle, index) {
347
+ for (let i = index + 1; i < haystack.length; index = i++) {
348
+ if (haystack[i][COLUMN] !== needle) break;
349
+ }
350
+ return index;
351
+ }
352
+ function lowerBound(haystack, needle, index) {
353
+ for (let i = index - 1; i >= 0; index = i--) {
354
+ if (haystack[i][COLUMN] !== needle) break;
355
+ }
356
+ return index;
357
+ }
358
+ function memoizedState() {
359
+ return {
360
+ lastKey: -1,
361
+ lastNeedle: -1,
362
+ lastIndex: -1
363
+ };
364
+ }
365
+ function memoizedBinarySearch(haystack, needle, state, key) {
366
+ const { lastKey, lastNeedle, lastIndex } = state;
367
+ let low = 0;
368
+ let high = haystack.length - 1;
369
+ if (key === lastKey) {
370
+ if (needle === lastNeedle) {
371
+ found = lastIndex !== -1 && haystack[lastIndex][COLUMN] === needle;
372
+ return lastIndex;
373
+ }
374
+ if (needle >= lastNeedle) {
375
+ low = lastIndex === -1 ? 0 : lastIndex;
376
+ } else {
377
+ high = lastIndex;
378
+ }
379
+ }
380
+ state.lastKey = key;
381
+ state.lastNeedle = needle;
382
+ return state.lastIndex = binarySearch(haystack, needle, low, high);
383
+ }
384
+ function parse(map) {
385
+ return typeof map === "string" ? JSON.parse(map) : map;
386
+ }
387
+ var LINE_GTR_ZERO = "`line` must be greater than 0 (lines start at line 1)";
388
+ var COL_GTR_EQ_ZERO = "`column` must be greater than or equal to 0 (columns start at column 0)";
389
+ var LEAST_UPPER_BOUND = -1;
390
+ var GREATEST_LOWER_BOUND = 1;
391
+ var TraceMap = class {
392
+ constructor(map, mapUrl) {
393
+ const isString = typeof map === "string";
394
+ if (!isString && map._decodedMemo) return map;
395
+ const parsed = parse(map);
396
+ const { version, file, names: names2, sourceRoot, sources, sourcesContent } = parsed;
397
+ this.version = version;
398
+ this.file = file;
399
+ this.names = names2 || [];
400
+ this.sourceRoot = sourceRoot;
401
+ this.sources = sources;
402
+ this.sourcesContent = sourcesContent;
403
+ this.ignoreList = parsed.ignoreList || parsed.x_google_ignoreList || void 0;
404
+ const resolve2 = resolver(mapUrl, sourceRoot);
405
+ this.resolvedSources = sources.map(resolve2);
406
+ const { mappings } = parsed;
407
+ if (typeof mappings === "string") {
408
+ this._encoded = mappings;
409
+ this._decoded = void 0;
410
+ } else if (Array.isArray(mappings)) {
411
+ this._encoded = void 0;
412
+ this._decoded = maybeSort(mappings, isString);
413
+ } else if (parsed.sections) {
414
+ throw new Error(`TraceMap passed sectioned source map, please use FlattenMap export instead`);
415
+ } else {
416
+ throw new Error(`invalid source map: ${JSON.stringify(parsed)}`);
417
+ }
418
+ this._decodedMemo = memoizedState();
419
+ this._bySources = void 0;
420
+ this._bySourceMemos = void 0;
421
+ }
422
+ };
423
+ function cast(map) {
424
+ return map;
425
+ }
426
+ function decodedMappings(map) {
427
+ var _a;
428
+ return (_a = cast(map))._decoded || (_a._decoded = decode(cast(map)._encoded));
429
+ }
430
+ function originalPositionFor(map, needle) {
431
+ let { line, column, bias } = needle;
432
+ line--;
433
+ if (line < 0) throw new Error(LINE_GTR_ZERO);
434
+ if (column < 0) throw new Error(COL_GTR_EQ_ZERO);
435
+ const decoded = decodedMappings(map);
436
+ if (line >= decoded.length) return OMapping(null, null, null, null);
437
+ const segments = decoded[line];
438
+ const index = traceSegmentInternal(
439
+ segments,
440
+ cast(map)._decodedMemo,
441
+ line,
442
+ column,
443
+ bias || GREATEST_LOWER_BOUND
444
+ );
445
+ if (index === -1) return OMapping(null, null, null, null);
446
+ const segment = segments[index];
447
+ if (segment.length === 1) return OMapping(null, null, null, null);
448
+ const { names: names2, resolvedSources } = map;
449
+ return OMapping(
450
+ resolvedSources[segment[SOURCES_INDEX]],
451
+ segment[SOURCE_LINE] + 1,
452
+ segment[SOURCE_COLUMN],
453
+ segment.length === 5 ? names2[segment[NAMES_INDEX]] : null
454
+ );
455
+ }
456
+ function OMapping(source, line, column, name) {
457
+ return { source, line, column, name };
458
+ }
459
+ function traceSegmentInternal(segments, memo, line, column, bias) {
460
+ let index = memoizedBinarySearch(segments, column, memo, line);
461
+ if (found) {
462
+ index = (bias === LEAST_UPPER_BOUND ? upperBound : lowerBound)(segments, column, index);
463
+ } else if (bias === LEAST_UPPER_BOUND) index++;
464
+ if (index === -1 || index === segments.length) return -1;
465
+ return index;
466
+ }
467
+
468
+ // src/shared/schema.ts
469
+ var SESSION_SCHEMA = "react-perf-recorder/session";
470
+ var ENDPOINT = "__react-perf-recorder";
471
+ var CLIENT_HEADER = "x-react-perf-recorder";
472
+
473
+ // src/vite/component-names.ts
474
+ var DEFAULT_WRAPPERS = ["memo", "forwardRef", "createContext"];
475
+ var DEFAULT_LOCAL = "__rprDefault";
476
+ var nameLine = (local, name) => `if ((typeof ${local} === "function" || (typeof ${local} === "object" && ${local} !== null)) && !${local}.displayName) ${local}.displayName = ${JSON.stringify(name)};`;
477
+ function nameOfFile(file) {
478
+ const parts = file.replace(/[?#].*$/, "").split(/[\\/]/);
479
+ const base = parts.pop()?.replace(/\.[^.]+$/, "");
480
+ const name = base === "index" ? parts.pop() : base;
481
+ return name && /^[A-Za-z_$][\w$-]*$/.test(name) ? name : null;
482
+ }
483
+ function addComponentNames(code, wrappers = DEFAULT_WRAPPERS, file) {
484
+ const { names: names2, defaultCall } = scanModule(code, wrappers, { allowReactPrefix: true, file });
485
+ const lines = names2.map((name) => nameLine(name, name));
486
+ const defaultName = defaultCall && file ? nameOfFile(file) : null;
487
+ let out = code;
488
+ if (defaultCall && defaultName) {
489
+ const { start, end } = defaultCall;
490
+ out = `${code.slice(0, start)}(${DEFAULT_LOCAL} = ${code.slice(start, end)})${code.slice(end)}`;
491
+ lines.push(`var ${DEFAULT_LOCAL};`, nameLine(DEFAULT_LOCAL, defaultName));
492
+ }
493
+ return appendLines(out, lines);
494
+ }
495
+
496
+ // src/vite/entry.ts
497
+ import path from "path";
498
+ var ENTRY_ID = "virtual:react-perf-recorder/entry";
499
+ var RESOLVED_ENTRY_ID = `\0${ENTRY_ID}`;
500
+ function runtimeSpecifier(module, root) {
501
+ if (!path.isAbsolute(module)) return module;
502
+ const rel = path.relative(root, module).replace(/\\/g, "/");
503
+ return rel.startsWith("..") ? `/@fs${module.replace(/\\/g, "/")}` : `/${rel}`;
504
+ }
505
+ function entryCode(clientModule, config, runtimes) {
506
+ const imports = runtimes.map((r, i) => `import plugin${i} from ${JSON.stringify(r.module)};`);
507
+ const list = runtimes.map((r, i) => `[plugin${i}, ${JSON.stringify(r.options ?? null)}]`).join(", ");
508
+ return [
509
+ `import { boot } from ${JSON.stringify(clientModule)};`,
510
+ ...imports,
511
+ `boot(${JSON.stringify(config)}, [${list}], import.meta.hot);`,
512
+ // Keep the entry alive across HMR: booting twice would install the commit hook twice.
513
+ `if (import.meta.hot) import.meta.hot.accept(() => {});`
514
+ ].join("\n");
515
+ }
516
+
517
+ // src/vite/helpers/hook-deps.ts
518
+ var MEMO_HOOKS = /^(useMemo|useCallback)$/;
519
+ var hookName = (callee) => {
520
+ if (callee.type === "Identifier") return callee.name;
521
+ if (callee.type === "MemberExpression" && callee.property.type === "Identifier") return callee.property.name;
522
+ return null;
523
+ };
524
+ function memoDepsAt(body, code, line) {
525
+ let found2 = null;
526
+ const visit = (node) => {
527
+ if (found2 || !node || typeof node !== "object") return;
528
+ if (Array.isArray(node)) {
529
+ for (const child of node) visit(child);
530
+ return;
531
+ }
532
+ const n = node;
533
+ if (n.loc && (n.loc.start.line > line || n.loc.end.line < line)) return;
534
+ if (n.type === "CallExpression" && n.callee.loc?.start.line === line) {
535
+ const name = hookName(n.callee);
536
+ const last = n.arguments[n.arguments.length - 1];
537
+ if (name && MEMO_HOOKS.test(name) && n.arguments.length > 1 && last?.type === "ArrayExpression") {
538
+ found2 = last.elements.map(
539
+ (el) => el ? code.slice(el.start ?? 0, el.end ?? 0).replace(/\s+/g, " ").slice(0, 60) : ""
540
+ );
541
+ return;
542
+ }
543
+ }
544
+ for (const key of Object.keys(n)) {
545
+ if (key === "loc" || key === "leadingComments" || key === "trailingComments" || key === "innerComments") continue;
546
+ visit(n[key]);
547
+ }
548
+ };
549
+ visit(body);
550
+ return found2;
551
+ }
552
+ function declared(body, name) {
553
+ for (const statement of body) {
554
+ const node = statement.type === "ExportNamedDeclaration" || statement.type === "ExportDefaultDeclaration" ? statement.declaration : statement;
555
+ if (!node) continue;
556
+ if (node.type === "FunctionDeclaration" && node.id?.name === name) return node;
557
+ if (node.type === "VariableDeclaration") {
558
+ for (const d of node.declarations) if (d.id.type === "Identifier" && d.id.name === name && d.init) return d.init;
559
+ }
560
+ }
561
+ return null;
562
+ }
563
+ function memoCalls(fn) {
564
+ const out = [];
565
+ const visit = (node) => {
566
+ if (!node || typeof node !== "object") return;
567
+ if (Array.isArray(node)) return void node.forEach(visit);
568
+ const n = node;
569
+ if (n.type === "CallExpression") {
570
+ const name = hookName(n.callee);
571
+ if (name && MEMO_HOOKS.test(name)) out.push(n);
572
+ }
573
+ for (const key of Object.keys(n)) if (key !== "loc" && !key.endsWith("Comments")) visit(n[key]);
574
+ };
575
+ visit(fn);
576
+ return out;
577
+ }
578
+ function memoDepsInHook(body, code, name) {
579
+ const fn = declared(body, name);
580
+ if (fn) {
581
+ const calls = memoCalls(fn);
582
+ const last = calls.length === 1 ? calls[0].arguments[calls[0].arguments.length - 1] : void 0;
583
+ if (calls[0]?.arguments.length !== 2 || last?.type !== "ArrayExpression") return null;
584
+ return {
585
+ kind: "deps",
586
+ deps: last.elements.map(
587
+ (el) => el ? code.slice(el.start ?? 0, el.end ?? 0).replace(/\s+/g, " ").slice(0, 60) : ""
588
+ )
589
+ };
590
+ }
591
+ for (const statement of body)
592
+ if (statement.type === "ImportDeclaration" && statement.specifiers.some((s) => s.local.name === name))
593
+ return { kind: "import", source: statement.source.value };
594
+ return null;
595
+ }
596
+
597
+ // src/vite/middleware.ts
598
+ import { randomBytes } from "crypto";
599
+ import fs from "fs";
600
+ import path2 from "path";
601
+
602
+ // src/shared/summary.ts
603
+ var names = (list, max = 5) => (list ?? []).slice(0, max).join(", ");
604
+ function reasonText(reason) {
605
+ const mark = reason.sameContent ? " SAME-CONTENT" : "";
606
+ const props = [names(reason.changed), reason.sameRef?.length ? `same: ${names(reason.sameRef)}` : ""].filter(Boolean).join(" | ");
607
+ switch (reason.kind) {
608
+ case "state":
609
+ return reason.hook === void 0 ? `class state${mark}` : `state #${reason.hook}${mark}`;
610
+ case "store":
611
+ return `external store #${reason.hook}${mark}${reason.store ? ` [${reason.store}]` : ""}${reason.selector ? ` ${reason.selector}` : ""}`;
612
+ case "context":
613
+ return `context ${reason.context || "(unnamed)"}${mark}`;
614
+ case "props":
615
+ return `props: ${props || "(new object)"}`;
616
+ case "parent":
617
+ if (reason.equal) return "parent: props equal";
618
+ if (!props) return "parent: children";
619
+ return `parent: props ${props}${reason.children ? " +children" : ""}`;
620
+ case "bailout":
621
+ return "bailout: state set to the same value";
622
+ default:
623
+ return "unknown";
624
+ }
625
+ }
626
+ var textOf = (reason) => reason.text ?? reasonText(reason);
627
+ var reasonsById = (reasons = []) => new Map(reasons.map((r) => [r.i, r]));
628
+
629
+ // src/shared/listing.ts
630
+ function topReason(rec) {
631
+ const id = rec.roots[0]?.reasons[0]?.[0];
632
+ const reason = id === void 0 ? void 0 : reasonsById(rec.reasons).get(id);
633
+ return reason ? textOf(reason) : "";
634
+ }
635
+ function listingOf(rec) {
636
+ const top = rec.roots?.[0];
637
+ return {
638
+ durationMs: rec.durationMs ?? 0,
639
+ actions: rec.actions?.length ?? 0,
640
+ commits: rec.totals?.commitsInScope ?? 0,
641
+ renders: rec.totals?.renders ?? 0,
642
+ topRoot: top ? `${top.name} \xD7${top.hits} \xB7 ${rec.reasons ? topReason(rec) : ""}` : null
643
+ };
644
+ }
645
+
646
+ // src/vite/middleware.ts
647
+ var ID = /^\d{8}-\d{6}-[\w.-]+$/;
648
+ var slug = (s) => (s ?? "").replace(/[^\w.-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "app";
649
+ var stamp = (d) => {
650
+ const p = (n, w = 2) => String(n).padStart(w, "0");
651
+ return `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}`;
652
+ };
653
+ function writeAtomic(file, data) {
654
+ const tmp = path2.join(path2.dirname(file), `.${path2.basename(file)}.${process.pid}.tmp`);
655
+ fs.writeFileSync(tmp, data);
656
+ fs.renameSync(tmp, file);
657
+ }
658
+ function dirSize(dir) {
659
+ let total = 0;
660
+ for (const name of fs.readdirSync(dir)) {
661
+ try {
662
+ total += fs.statSync(path2.join(dir, name)).size;
663
+ } catch {
664
+ }
665
+ }
666
+ return total;
667
+ }
668
+ var SessionStore = class {
669
+ constructor(options) {
670
+ this.options = options;
671
+ this.tokens = /* @__PURE__ */ new Map();
672
+ }
673
+ get dir() {
674
+ return this.options.dir;
675
+ }
676
+ get maxBytes() {
677
+ return this.options.maxBytes;
678
+ }
679
+ ensureDir() {
680
+ if (fs.existsSync(this.options.dir)) return;
681
+ fs.mkdirSync(this.options.dir, { recursive: true });
682
+ if (this.options.gitignore) fs.writeFileSync(path2.join(this.options.dir, ".gitignore"), "*\n");
683
+ }
684
+ open(meta) {
685
+ this.ensureDir();
686
+ this.prune();
687
+ const now = /* @__PURE__ */ new Date();
688
+ const scope = meta.scope?.name ? slug(meta.scope.name) : "app";
689
+ const source = slug(meta.source ?? "api").replace(/^script-/, "");
690
+ let id = `${stamp(now)}-${scope}-${source}-${randomBytes(2).toString("hex")}`;
691
+ while (fs.existsSync(path2.join(this.options.dir, id))) id = `${id.slice(0, -4)}${randomBytes(2).toString("hex")}`;
692
+ const dir = path2.join(this.options.dir, id);
693
+ fs.mkdirSync(dir);
694
+ const session = {
695
+ schema: SESSION_SCHEMA,
696
+ version: 1,
697
+ id,
698
+ status: "recording",
699
+ createdAt: now.toISOString(),
700
+ updatedAt: now.toISOString(),
701
+ source: String(meta.source ?? "api"),
702
+ ...meta.label ? { label: String(meta.label).slice(0, 200) } : {},
703
+ page: meta.page ?? { url: "", title: "", viewport: "", dpr: 1, userAgent: "" },
704
+ scope: meta.scope ?? null,
705
+ conditions: meta.conditions ?? {},
706
+ plugins: meta.plugins ?? [],
707
+ events: 0,
708
+ reloads: 0
709
+ };
710
+ writeAtomic(path2.join(dir, "session.json"), JSON.stringify(session, null, 2));
711
+ fs.writeFileSync(path2.join(dir, "events.ndjson"), "");
712
+ const token = randomBytes(16).toString("hex");
713
+ this.tokens.set(id, token);
714
+ return { id, token };
715
+ }
716
+ checkToken(id, token) {
717
+ return Boolean(token) && this.tokens.get(id) === token;
718
+ }
719
+ /** `unloaded`: the page went away mid-recording and sent what it had by beacon; a normal stop ends in finish. */
720
+ append(id, events, unloaded = false) {
721
+ const dir = this.sessionDir(id);
722
+ const meta = this.readMeta(dir);
723
+ if (events.length) fs.appendFileSync(path2.join(dir, "events.ndjson"), `${events.map((e) => JSON.stringify(e)).join("\n")}
724
+ `);
725
+ meta.events += events.length;
726
+ meta.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
727
+ if (meta.status === "recording" && unloaded) meta.status = "interrupted";
728
+ writeAtomic(path2.join(dir, "session.json"), JSON.stringify(meta, null, 2));
729
+ }
730
+ async finish(id, recording) {
731
+ const dir = this.sessionDir(id);
732
+ const sites = await this.mapSites(recording);
733
+ writeAtomic(path2.join(dir, "recording.json"), JSON.stringify({ ...recording, id }, null, 1));
734
+ const meta = this.readMeta(dir);
735
+ meta.status = "done";
736
+ meta.listing = listingOf(recording);
737
+ meta.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
738
+ writeAtomic(path2.join(dir, "session.json"), JSON.stringify(meta, null, 2));
739
+ this.tokens.delete(id);
740
+ return { id, dir, sites };
741
+ }
742
+ /** The panel asking outside a recording: React 19 gives a component's site as a built position, and only the dev server can map it. */
743
+ async mapPositions(positions) {
744
+ const mapSite2 = this.options.mapSite;
745
+ const out = {};
746
+ if (!mapSite2) return out;
747
+ for (const p of positions.slice(0, 200)) {
748
+ if (!p || typeof p.url !== "string" || typeof p.line !== "number" || typeof p.column !== "number") continue;
749
+ const mapped = await mapSite2(p.url, p.line, p.column).catch(() => null);
750
+ if (mapped) out[`${p.url}:${p.line}:${p.column}`] = mapped.site;
751
+ }
752
+ return out;
753
+ }
754
+ /** Turns built positions back into source lines: every hook's call site, and on React 19 the component's own. */
755
+ async mapSites(recording) {
756
+ const sites = {};
757
+ const mapSite2 = this.options.mapSite;
758
+ if (!mapSite2) return sites;
759
+ const cache = /* @__PURE__ */ new Map();
760
+ const map = (g, hooks) => {
761
+ const key = `${g.url}:${g.line}:${g.column}`;
762
+ const cacheKey = hooks ? `${key}|memo` : key;
763
+ if (!cache.has(cacheKey))
764
+ cache.set(
765
+ cacheKey,
766
+ mapSite2(g.url, g.line, g.column, hooks).catch(() => null)
767
+ );
768
+ return cache.get(cacheKey).then((mapped) => {
769
+ if (mapped) sites[key] = { ...sites[key], ...mapped };
770
+ return mapped;
771
+ });
772
+ };
773
+ for (const action of recording.actions ?? []) {
774
+ const own = action.target?.generatedSource;
775
+ if (!own) continue;
776
+ const mapped = await map(own);
777
+ if (mapped) action.target.source = mapped.site;
778
+ delete action.target.generatedSource;
779
+ }
780
+ for (const root of [...recording.roots, ...recording.outsideRoots]) {
781
+ const own = root.generatedSource;
782
+ if (own) {
783
+ const mapped = await map(own);
784
+ if (mapped) root.source = mapped.site;
785
+ delete root.generatedSource;
786
+ }
787
+ for (const hook of Object.values(root.hooks ?? {})) {
788
+ const g = hook.generated;
789
+ if (!g) continue;
790
+ const mapped = await map(g);
791
+ if (mapped) {
792
+ hook.site = mapped.site;
793
+ if (mapped.code) hook.code = mapped.code;
794
+ }
795
+ delete hook.generated;
796
+ }
797
+ }
798
+ for (const memo of recording.memos ?? []) {
799
+ const g = memo.info?.generated;
800
+ if (!g) continue;
801
+ const mapped = await map(g, (memo.info.path ?? []).slice(0, -1));
802
+ if (mapped) {
803
+ memo.info.site = mapped.site;
804
+ if (mapped.code) memo.info.code = mapped.code;
805
+ if (mapped.deps) memo.info.deps = mapped.deps;
806
+ }
807
+ delete memo.info.generated;
808
+ }
809
+ return sites;
810
+ }
811
+ sessionDir(id) {
812
+ if (!ID.test(id)) throw Object.assign(new Error("bad session id"), { status: 400 });
813
+ const dir = path2.join(this.options.dir, id);
814
+ if (!fs.existsSync(dir)) throw Object.assign(new Error("unknown session"), { status: 404 });
815
+ return dir;
816
+ }
817
+ readMeta(dir) {
818
+ return JSON.parse(fs.readFileSync(path2.join(dir, "session.json"), "utf8"));
819
+ }
820
+ /** Oldest sessions go first; a session still being written (updated within a minute) is never removed. */
821
+ prune() {
822
+ const { sessions, bytes } = this.options.retain;
823
+ const entries = fs.readdirSync(this.options.dir).filter((name) => ID.test(name)).sort().map((name) => {
824
+ const dir = path2.join(this.options.dir, name);
825
+ return { dir, size: dirSize(dir), mtime: fs.statSync(dir).mtimeMs };
826
+ });
827
+ let total = entries.reduce((sum, e) => sum + e.size, 0);
828
+ let count = entries.length;
829
+ for (const entry of entries) {
830
+ if (count < sessions && total <= bytes) break;
831
+ if (Date.now() - entry.mtime < 6e4) continue;
832
+ fs.rmSync(entry.dir, { recursive: true, force: true });
833
+ count--;
834
+ total -= entry.size;
835
+ }
836
+ }
837
+ };
838
+ async function readBody(req, limit) {
839
+ const chunks = [];
840
+ let size = 0;
841
+ for await (const chunk of req) {
842
+ size += chunk.length;
843
+ if (size > limit) throw Object.assign(new Error("payload too large"), { status: 413 });
844
+ chunks.push(chunk);
845
+ }
846
+ return Buffer.concat(chunks).toString("utf8");
847
+ }
848
+ var send = (res, status, body) => {
849
+ res.statusCode = status;
850
+ res.setHeader("content-type", "application/json");
851
+ res.end(JSON.stringify(body));
852
+ };
853
+ function createMiddleware(store, base, version) {
854
+ const prefix = `${base.replace(/\/$/, "")}/${ENDPOINT}/`;
855
+ return async (req, res, next) => {
856
+ const url = new URL(req.url ?? "/", "http://localhost");
857
+ if (!url.pathname.startsWith(prefix)) return next();
858
+ const route = url.pathname.slice(prefix.length);
859
+ try {
860
+ if (req.method === "GET" && route === "health") return send(res, 200, { ok: true, version, dir: store.dir });
861
+ if (req.method !== "POST") return send(res, 405, { error: "method not allowed" });
862
+ const match = /^(?:map|sessions(?:\/([^/]+)\/(events|finish))?)$/.exec(route);
863
+ if (!match) return send(res, 404, { error: "not found" });
864
+ const [, id, action] = match;
865
+ const trusted = req.headers[CLIENT_HEADER] === "1" && String(req.headers["content-type"] ?? "").startsWith("application/json");
866
+ const beacon = action === "events" && id && store.checkToken(id, url.searchParams.get("token"));
867
+ if (!trusted && !beacon) return send(res, 415, { error: `requests need content-type application/json and ${CLIENT_HEADER}: 1` });
868
+ const raw = await readBody(req, action === "finish" ? store.maxBytes : Math.min(store.maxBytes, 16 * 1024 * 1024));
869
+ const body = raw ? JSON.parse(raw) : {};
870
+ if (route === "map") return send(res, 200, { sites: await store.mapPositions(body.positions ?? []) });
871
+ if (!action) return send(res, 200, store.open(body));
872
+ if (action === "events") {
873
+ if (!Array.isArray(body.events)) return send(res, 400, { error: "events must be an array" });
874
+ store.append(id, body.events, url.searchParams.get("end") === "1");
875
+ return send(res, 200, { ok: true });
876
+ }
877
+ if (body.recording?.schema !== "react-perf-recorder/recording") return send(res, 400, { error: "not a recording" });
878
+ return send(res, 200, await store.finish(id, body.recording));
879
+ } catch (error) {
880
+ const status = error.status ?? (error instanceof SyntaxError ? 400 : 500);
881
+ return send(res, status, { error: String(error.message ?? error) });
882
+ }
883
+ };
884
+ }
885
+
886
+ // src/vite/plugin-api.ts
887
+ var definePerfRecorderPlugin = (plugin) => plugin;
888
+
889
+ // src/vite/index.ts
890
+ var VERSION = true ? "0.1.0" : "dev";
891
+ function resolvable(specifier, root = process.cwd()) {
892
+ try {
893
+ createRequire(path3.join(path3.resolve(root), "package.json")).resolve(specifier);
894
+ return true;
895
+ } catch {
896
+ return false;
897
+ }
898
+ }
899
+ var DEFAULT_WRAPPER_PATTERN = "^(Anonymous|ForwardRef|Memo)$";
900
+ function resolveOutDir(root, outDir) {
901
+ const dir = outDir ?? process.env.REACT_PERF_RECORDER_DIR ?? ".agent-artifacts/perf-recorder";
902
+ return path3.resolve(root, dir);
903
+ }
904
+ var traceMaps = /* @__PURE__ */ new WeakMap();
905
+ var fileLines = /* @__PURE__ */ new Map();
906
+ function linesOf(file) {
907
+ const mtimeMs = fs2.statSync(file).mtimeMs;
908
+ const cached = fileLines.get(file);
909
+ if (cached?.mtimeMs === mtimeMs) return cached.lines;
910
+ if (fileLines.size > 200) fileLines.clear();
911
+ const lines = fs2.readFileSync(file, "utf8").split("\n");
912
+ fileLines.set(file, { mtimeMs, lines });
913
+ return lines;
914
+ }
915
+ var parsedFiles = /* @__PURE__ */ new Map();
916
+ function parsedOf(file) {
917
+ const mtimeMs = fs2.statSync(file).mtimeMs;
918
+ let parsed = parsedFiles.get(file);
919
+ if (parsed?.mtimeMs !== mtimeMs) {
920
+ if (parsedFiles.size > 50) parsedFiles.clear();
921
+ const code = linesOf(file).join("\n");
922
+ parsedFiles.set(file, parsed = { mtimeMs, code, body: parseModule(code, file) });
923
+ }
924
+ return parsed;
925
+ }
926
+ var EXTENSIONS = ["", ".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", "/index.js"];
927
+ function depsOf(file, line, hooks = []) {
928
+ const here = parsedOf(file);
929
+ if (!here.body) return null;
930
+ const direct = memoDepsAt(here.body, here.code, line);
931
+ if (direct || !hooks.length) return direct;
932
+ const innermost = hooks[hooks.length - 1];
933
+ let at = file;
934
+ for (let hop = 0; hop < 3; hop++) {
935
+ const parsed = parsedOf(at);
936
+ if (!parsed.body) return null;
937
+ const found2 = memoDepsInHook(parsed.body, parsed.code, innermost) ?? (hop === 0 && hooks.length > 1 ? memoDepsInHook(parsed.body, parsed.code, hooks[0]) : null);
938
+ if (!found2) return null;
939
+ if (found2.kind === "deps") return found2.deps;
940
+ if (!found2.source.startsWith(".")) return null;
941
+ const base = path3.resolve(path3.dirname(at), found2.source);
942
+ const next = EXTENSIONS.map((ext) => base + ext).find((candidate) => fs2.existsSync(candidate) && fs2.statSync(candidate).isFile());
943
+ if (!next) return null;
944
+ at = next;
945
+ }
946
+ return null;
947
+ }
948
+ async function mapSite(server, root, url, line, column, hooks) {
949
+ const parsed = new URL(url, "http://localhost");
950
+ const mod = await server.moduleGraph.getModuleByUrl(parsed.pathname + parsed.search);
951
+ const map = mod?.transformResult?.map;
952
+ if (!mod?.file || !map) return null;
953
+ let traced = traceMaps.get(map);
954
+ if (!traced) traceMaps.set(map, traced = new TraceMap(map));
955
+ const pos = originalPositionFor(traced, { line, column: Math.max(0, column - 1) });
956
+ if (pos.line == null) return null;
957
+ const file = pos.source ? path3.isAbsolute(pos.source) ? pos.source : path3.resolve(path3.dirname(mod.file), pos.source) : mod.file;
958
+ const real = fs2.existsSync(file) ? file : mod.file;
959
+ const code = linesOf(real)[pos.line - 1]?.trim().slice(0, 140);
960
+ const deps = hooks ? depsOf(real, pos.line, hooks) : null;
961
+ return { site: `${path3.relative(root, real).replace(/\\/g, "/")}:${pos.line}`, ...code ? { code } : {}, ...deps ? { deps } : {} };
962
+ }
963
+ function perfRecorder(options = {}) {
964
+ let root = process.cwd();
965
+ let base = "/";
966
+ let serving = true;
967
+ const plugins = options.plugins ?? [];
968
+ const ctx = { root: () => root };
969
+ plugins.forEach((p) => p.init?.(ctx));
970
+ const apply = (_, env) => options.enabled ?? (env.command === "serve" && !process.env.VITEST && env.mode !== "test");
971
+ const components = options.components === false ? null : options.components ?? {};
972
+ const componentFilter = createFilter(() => root, components?.include ?? ["src/**/*.{tsx,jsx}"], components?.exclude);
973
+ const wrapperPattern = components?.wrapperPattern ?? DEFAULT_WRAPPER_PATTERN;
974
+ const appFilter = createFilter(() => root, ["src/**/*.{ts,tsx,js,jsx}"]);
975
+ const rootProxy = proxyModule("core", {
976
+ source: "react-dom/client",
977
+ importer: appFilter,
978
+ code: () => [
979
+ // Named exports only: react-dom/client is interop'd from CJS, and `export *` would lose them.
980
+ `import * as original from 'react-dom/client';`,
981
+ `import { noteRoot } from 'react-perf-recorder/runtime';`,
982
+ `export const createRoot = (...args) => { const root = original.createRoot(...args); noteRoot(root); return root; };`,
983
+ `export const hydrateRoot = (...args) => { const root = original.hydrateRoot(...args); noteRoot(root); return root; };`,
984
+ `export const version = original.version;`,
985
+ `export default original.default ?? original;`
986
+ ].join("\n")
987
+ });
988
+ const clientConfig = () => ({
989
+ version: VERSION,
990
+ projectRoot: root.replace(/\\/g, "/"),
991
+ wrapperPattern,
992
+ actions: { values: options.actions?.values ?? false, secretSelector: options.actions?.secretSelector ?? "[data-rpr-secret]" },
993
+ maxDurationMs: options.engine?.maxDurationMs ?? 10 * 6e4,
994
+ bigCommit: options.engine?.bigCommit ?? 150,
995
+ timelineLimit: options.engine?.timelineLimit ?? 5e3,
996
+ timers: options.engine?.timers ?? true,
997
+ endpoint: options.save ?? serving ? `${base.replace(/\/$/, "")}/${ENDPOINT}` : null,
998
+ panel: options.panel === false ? false : {
999
+ corner: options.panel?.corner ?? "bottom-left",
1000
+ highlight: options.panel?.highlight ?? true,
1001
+ shortcuts: { record: options.panel?.shortcuts?.record ?? "Alt+Shift+KeyR", pick: options.panel?.shortcuts?.pick ?? "Alt+Shift+KeyS" }
1002
+ }
1003
+ });
1004
+ const core = {
1005
+ name: "react-perf-recorder",
1006
+ enforce: "pre",
1007
+ apply,
1008
+ // The app's import of react-dom/client is rewritten to the proxy, so the optimizer's scan never meets it and
1009
+ // would find it on the first page load, then reload the page with two copies of React for a moment.
1010
+ config: (config) => ({
1011
+ optimizeDeps: { exclude: ["react-perf-recorder"], include: resolvable("react-dom/client", config.root) ? ["react-dom/client"] : [] }
1012
+ }),
1013
+ configResolved(config) {
1014
+ root = config.root;
1015
+ base = config.base;
1016
+ serving = config.command === "serve";
1017
+ },
1018
+ resolveId: (id, importer) => id === ENTRY_ID ? RESOLVED_ENTRY_ID : rootProxy.resolveId(id, importer),
1019
+ load(id) {
1020
+ const proxied = rootProxy.load(id);
1021
+ if (proxied) return proxied;
1022
+ if (id !== RESOLVED_ENTRY_ID) return null;
1023
+ const runtimes = plugins.filter((p) => p.runtime).map((p) => ({ module: runtimeSpecifier(p.runtime.module, root), options: p.runtime.options }));
1024
+ return entryCode("react-perf-recorder/client", clientConfig(), runtimes);
1025
+ },
1026
+ transform(code, id) {
1027
+ const rewritten = appFilter(id) ? rootProxy.rewrite(code) : null;
1028
+ if (!components || !componentFilter(id)) return rewritten ? { code: rewritten, map: null } : null;
1029
+ const named = addComponentNames(rewritten ?? code, components.wrappers ?? DEFAULT_WRAPPERS, id);
1030
+ return named ? { code: named, map: null } : rewritten ? { code: rewritten, map: null } : null;
1031
+ },
1032
+ // The dev server serves a virtual module under /@id/ and puts the base in front itself for a tag added before it
1033
+ // reads the page's scripts; a build bundles the module from its id, and only for such a tag.
1034
+ transformIndexHtml: {
1035
+ order: "pre",
1036
+ handler: () => [{ tag: "script", attrs: { type: "module", src: serving ? `/@id/${ENTRY_ID}` : ENTRY_ID }, injectTo: "head-prepend" }]
1037
+ },
1038
+ configureServer(server) {
1039
+ const dir = resolveOutDir(root, options.outDir);
1040
+ const store = new SessionStore({
1041
+ dir,
1042
+ maxBytes: options.maxBytes ?? 64 * 1024 * 1024,
1043
+ retain: { sessions: options.retain?.sessions ?? 100, bytes: options.retain?.bytes ?? 500 * 1024 * 1024 },
1044
+ gitignore: !path3.relative(root, dir).startsWith(".."),
1045
+ mapSite: (url, line, column, hooks) => mapSite(server, root, url, line, column, hooks)
1046
+ });
1047
+ server.middlewares.use(createMiddleware(store, base, VERSION));
1048
+ server.config.logger.info(` react-perf-recorder: sessions \u2192 ${dir}`);
1049
+ }
1050
+ };
1051
+ const wrapped = plugins.filter((p) => p.vite).map((p) => ({
1052
+ name: `react-perf-recorder:${p.name}`,
1053
+ enforce: "pre",
1054
+ apply,
1055
+ ...p.vite.config ? { config: (config) => p.vite.config(config) ?? void 0 } : {},
1056
+ ...p.vite.resolveId ? { resolveId: (source, importer) => p.vite.resolveId(source, importer) ?? null } : {},
1057
+ ...p.vite.load ? { load: (id) => p.vite.load(id) ?? null } : {},
1058
+ ...p.vite.transform ? { transform: (code, id) => p.vite.transform(code, id) ?? null } : {}
1059
+ }));
1060
+ return [core, ...wrapped];
1061
+ }
1062
+ export {
1063
+ addComponentNames,
1064
+ appendLines,
1065
+ combineProxies,
1066
+ createFilter,
1067
+ definePerfRecorderPlugin,
1068
+ findDeclarations,
1069
+ perfRecorder,
1070
+ proxyModule,
1071
+ resolveOutDir
1072
+ };