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