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,247 @@
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/plugins/zustand/index.ts
31
+ var zustand_exports = {};
32
+ __export(zustand_exports, {
33
+ zustand: () => zustand
34
+ });
35
+ module.exports = __toCommonJS(zustand_exports);
36
+
37
+ // src/vite/helpers/filter.ts
38
+ var import_node_path = __toESM(require("path"), 1);
39
+ var cleanId = (id) => id.split("?")[0].replace(/\\/g, "/");
40
+ var globToRegExp = (glob) => {
41
+ let re = "";
42
+ for (let i = 0; i < glob.length; i++) {
43
+ const c = glob[i];
44
+ if (c === "*" && glob[i + 1] === "*") {
45
+ re += ".*";
46
+ i++;
47
+ if (glob[i + 1] === "/") i++;
48
+ } else if (c === "*") re += "[^/]*";
49
+ else if (c === "?") re += "[^/]";
50
+ else if (c === "{") {
51
+ const end = glob.indexOf("}", i);
52
+ re += `(?:${glob.slice(i + 1, end).split(",").map((p) => p.replace(/[.+^$()|[\]\\]/g, "\\$&")).join("|")})`;
53
+ i = end;
54
+ } else re += c.replace(/[.+^$()|[\]\\]/g, "\\$&");
55
+ }
56
+ return new RegExp(`^${re}$`);
57
+ };
58
+ function createFilter(root, include, exclude = []) {
59
+ const inc = include.map(globToRegExp);
60
+ const exc = exclude.map(globToRegExp);
61
+ return (id) => {
62
+ if (!id || id.startsWith("\0") || id.includes("/node_modules/")) return false;
63
+ const file = cleanId(id);
64
+ const rel = import_node_path.default.isAbsolute(file) ? import_node_path.default.relative(root(), file).replace(/\\/g, "/") : file.replace(/^\//, "");
65
+ if (rel.startsWith("..")) return false;
66
+ return inc.some((re) => re.test(rel)) && !exc.some((re) => re.test(rel));
67
+ };
68
+ }
69
+
70
+ // src/vite/helpers/name-declarations.ts
71
+ var import_parser = require("@babel/parser");
72
+ var EMPTY = { names: [], defaultCall: null };
73
+ function pluginSets(file) {
74
+ const ext = file?.replace(/[?#].*$/, "").match(/\.([cm]?[jt]sx?)$/)?.[1];
75
+ if (ext === "tsx") return [["typescript", "jsx"]];
76
+ if (ext === "ts" || ext === "mts" || ext === "cts") return [["typescript"]];
77
+ if (ext === "js" || ext === "jsx" || ext === "mjs" || ext === "cjs") return [["jsx"]];
78
+ return [["typescript", "jsx"], ["typescript"]];
79
+ }
80
+ function parseModule(code, file) {
81
+ for (const plugins of pluginSets(file)) {
82
+ try {
83
+ return (0, import_parser.parse)(code, { sourceType: "module", plugins: [...plugins, "decorators-legacy"], errorRecovery: true }).program.body;
84
+ } catch {
85
+ }
86
+ }
87
+ return null;
88
+ }
89
+ function unwrap(node) {
90
+ let e = node;
91
+ while (e.type === "TSAsExpression" || e.type === "TSSatisfiesExpression" || e.type === "TSNonNullExpression" || e.type === "TSTypeAssertion" || e.type === "ParenthesizedExpression")
92
+ e = e.expression;
93
+ return e;
94
+ }
95
+ function calleeName(callee, allowReactPrefix) {
96
+ if (callee.type === "Identifier") return callee.name;
97
+ if (callee.type === "CallExpression") return calleeName(callee.callee, allowReactPrefix);
98
+ if (callee.type === "TSInstantiationExpression") return calleeName(callee.expression, allowReactPrefix);
99
+ if (allowReactPrefix && callee.type === "MemberExpression" && !callee.computed && callee.object.type === "Identifier" && callee.object.name === "React" && callee.property.type === "Identifier")
100
+ return callee.property.name;
101
+ return null;
102
+ }
103
+ function scanModule(code, callees, { allowReactPrefix = false, file } = {}) {
104
+ if (!callees.length || !callees.some((c) => code.includes(c))) return EMPTY;
105
+ const body = parseModule(code, file);
106
+ if (!body) return EMPTY;
107
+ const isCall = (node) => {
108
+ if (!node) return false;
109
+ const e = unwrap(node);
110
+ if (e.type !== "CallExpression") return false;
111
+ const name = calleeName(e.callee, allowReactPrefix);
112
+ return name !== null && callees.includes(name);
113
+ };
114
+ const names = [];
115
+ let defaultCall = null;
116
+ for (const statement of body) {
117
+ const declaration = statement.type === "ExportNamedDeclaration" ? statement.declaration : statement;
118
+ if (declaration?.type === "VariableDeclaration" && declaration.kind !== "var") {
119
+ for (const d of declaration.declarations) if (d.id.type === "Identifier" && isCall(d.init) && !names.includes(d.id.name)) names.push(d.id.name);
120
+ }
121
+ if (statement.type === "ExportDefaultDeclaration") {
122
+ const e = statement.declaration;
123
+ if (e.start != null && e.end != null && isCall(e)) defaultCall = { start: e.start, end: e.end };
124
+ }
125
+ }
126
+ return { names, defaultCall };
127
+ }
128
+ function findDeclarations(code, callees, options = {}) {
129
+ return scanModule(code, callees, options).names;
130
+ }
131
+ function appendLines(code, lines) {
132
+ if (!lines.length) return null;
133
+ return `${code}
134
+ ${lines.join("\n")}
135
+ `;
136
+ }
137
+ var ifDeclared = (name, call) => `if (typeof ${name} !== "undefined") ${call}`;
138
+
139
+ // src/vite/helpers/proxy-module.ts
140
+ function packageOf(id) {
141
+ const path2 = id.replace(/\?.*$/, "");
142
+ const optimized = /\/deps\/([^/]+)\.js$/.exec(path2);
143
+ if (optimized) return optimized[1].startsWith("chunk-") ? "" : optimized[1].replace(/_/g, "/");
144
+ const at = path2.lastIndexOf("/node_modules/");
145
+ if (at < 0) return "";
146
+ return path2.slice(at + "/node_modules/".length).replace(/\.[cm]?js$/, "").replace(/\/index$/, "");
147
+ }
148
+ function proxyModule(pluginName, options) {
149
+ const id = `\0react-perf-recorder:${pluginName}:${options.source}`;
150
+ return {
151
+ id,
152
+ resolveId(source, importer) {
153
+ if (source === id) return id;
154
+ if (!importer || importer === id || importer.startsWith("\0")) return null;
155
+ if (source !== options.source && packageOf(source) !== options.source) return null;
156
+ return options.importer(importer) ? id : null;
157
+ },
158
+ load(loadId) {
159
+ return loadId === id ? options.code() : null;
160
+ },
161
+ rewrite(code) {
162
+ const quoted = options.source.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
163
+ const imports = new RegExp(`((?:^|[\\s{(,;])(?:from|import)\\s*)(['"])${quoted}\\2`, "g");
164
+ if (!imports.test(code)) return null;
165
+ imports.lastIndex = 0;
166
+ return code.replace(imports, (_all, head, quote) => `${head}${quote}${id}${quote}`);
167
+ }
168
+ };
169
+ }
170
+ function combineProxies(proxies) {
171
+ return {
172
+ id: proxies[0]?.id ?? "",
173
+ resolveId: (source, importer) => {
174
+ for (const p of proxies) {
175
+ const id = p.resolveId(source, importer);
176
+ if (id) return id;
177
+ }
178
+ return null;
179
+ },
180
+ load: (id) => {
181
+ for (const p of proxies) {
182
+ const code = p.load(id);
183
+ if (code != null) return code;
184
+ }
185
+ return null;
186
+ },
187
+ rewrite: (code) => {
188
+ let out = null;
189
+ for (const p of proxies) out = p.rewrite(out ?? code) ?? out;
190
+ return out;
191
+ }
192
+ };
193
+ }
194
+
195
+ // src/plugins/zustand/index.ts
196
+ var RUNTIME = "react-perf-recorder/plugins/zustand/runtime";
197
+ function zustand(options = {}) {
198
+ const functions = options.functions ?? ["create", "createStore", "createWithEqualityFn"];
199
+ let context = null;
200
+ const root = () => context?.root() ?? process.cwd();
201
+ const filter = createFilter(root, options.include ?? ["src/**/*.{ts,tsx,js,jsx}"], options.exclude);
202
+ const wrap = (source, lines) => proxyModule("zustand", {
203
+ source,
204
+ importer: filter,
205
+ code: () => [
206
+ `import * as original from ${JSON.stringify(source)};`,
207
+ `import * as rpr from ${JSON.stringify(RUNTIME)};`,
208
+ `export * from ${JSON.stringify(source)};`,
209
+ ...lines
210
+ ].join("\n")
211
+ });
212
+ const proxies = combineProxies([
213
+ wrap("zustand", [
214
+ "export const create = rpr.wrapCreate(original.create);",
215
+ "export const createStore = rpr.wrapCreate(original.createStore);",
216
+ "export default original.default;"
217
+ ]),
218
+ wrap("zustand/vanilla", ["export const createStore = rpr.wrapCreate(original.createStore);", "export default original.default;"]),
219
+ wrap("zustand/react/shallow", ["export const useShallow = rpr.wrapUseShallow(original.useShallow);"]),
220
+ // v5 also exports `useShallow` from here, and keeps the store with an equality function in `traditional`.
221
+ wrap("zustand/shallow", ["export const useShallow = rpr.wrapUseShallow(original.useShallow);", "export default original.default;"]),
222
+ wrap("zustand/traditional", ["export const createWithEqualityFn = rpr.wrapCreate(original.createWithEqualityFn);"])
223
+ ]);
224
+ return {
225
+ name: "zustand",
226
+ init: (ctx) => void (context = ctx),
227
+ vite: {
228
+ config: () => ({ optimizeDeps: { include: ["zustand", "zustand/vanilla", "zustand/react/shallow"] } }),
229
+ resolveId: (id, importer) => proxies.resolveId(id, importer),
230
+ load: (id) => proxies.load(id),
231
+ transform(code, id) {
232
+ if (!filter(id) || !functions.some((fn) => code.includes(fn))) return null;
233
+ const names = findDeclarations(code, functions, { file: id });
234
+ const out = appendLines(code, [
235
+ `import { nameStore as __rprNameStore } from ${JSON.stringify(RUNTIME)};`,
236
+ ...names.map((name) => ifDeclared(name, `__rprNameStore(${name}, ${JSON.stringify(name)});`))
237
+ ]);
238
+ return names.length && out ? { code: out, map: null } : null;
239
+ }
240
+ },
241
+ runtime: { module: RUNTIME, options: { devtools: options.devtools ?? true } }
242
+ };
243
+ }
244
+ // Annotate the CommonJS export names for ESM import in node:
245
+ 0 && (module.exports = {
246
+ zustand
247
+ });
@@ -0,0 +1,17 @@
1
+ import { P as PerfRecorderPlugin } from '../plugin-api-zXFxjYba.cjs';
2
+
3
+ interface ZustandOptions {
4
+ include?: string[];
5
+ exclude?: string[];
6
+ /** Store factories whose results get a name: `export const useStore = create(...)`, `createWithEqualityFn(...)`. */
7
+ functions?: string[];
8
+ /** Stand in for the Redux DevTools extension to read action names; a real extension still gets everything. */
9
+ devtools?: boolean;
10
+ }
11
+ /**
12
+ * Store causes for commits: which action wrote to which store and which top-level keys it changed (with the same
13
+ * content or not); labels `useShallow(selector)` and the store in `external store` reasons.
14
+ */
15
+ declare function zustand(options?: ZustandOptions): PerfRecorderPlugin;
16
+
17
+ export { type ZustandOptions, zustand };
@@ -0,0 +1,17 @@
1
+ import { P as PerfRecorderPlugin } from '../plugin-api-zXFxjYba.js';
2
+
3
+ interface ZustandOptions {
4
+ include?: string[];
5
+ exclude?: string[];
6
+ /** Store factories whose results get a name: `export const useStore = create(...)`, `createWithEqualityFn(...)`. */
7
+ functions?: string[];
8
+ /** Stand in for the Redux DevTools extension to read action names; a real extension still gets everything. */
9
+ devtools?: boolean;
10
+ }
11
+ /**
12
+ * Store causes for commits: which action wrote to which store and which top-level keys it changed (with the same
13
+ * content or not); labels `useShallow(selector)` and the store in `external store` reasons.
14
+ */
15
+ declare function zustand(options?: ZustandOptions): PerfRecorderPlugin;
16
+
17
+ export { type ZustandOptions, zustand };
@@ -0,0 +1,61 @@
1
+ import {
2
+ appendLines,
3
+ combineProxies,
4
+ createFilter,
5
+ findDeclarations,
6
+ ifDeclared,
7
+ proxyModule
8
+ } from "../chunk-HS2BJBJX.js";
9
+
10
+ // src/plugins/zustand/index.ts
11
+ var RUNTIME = "react-perf-recorder/plugins/zustand/runtime";
12
+ function zustand(options = {}) {
13
+ const functions = options.functions ?? ["create", "createStore", "createWithEqualityFn"];
14
+ let context = null;
15
+ const root = () => context?.root() ?? process.cwd();
16
+ const filter = createFilter(root, options.include ?? ["src/**/*.{ts,tsx,js,jsx}"], options.exclude);
17
+ const wrap = (source, lines) => proxyModule("zustand", {
18
+ source,
19
+ importer: filter,
20
+ code: () => [
21
+ `import * as original from ${JSON.stringify(source)};`,
22
+ `import * as rpr from ${JSON.stringify(RUNTIME)};`,
23
+ `export * from ${JSON.stringify(source)};`,
24
+ ...lines
25
+ ].join("\n")
26
+ });
27
+ const proxies = combineProxies([
28
+ wrap("zustand", [
29
+ "export const create = rpr.wrapCreate(original.create);",
30
+ "export const createStore = rpr.wrapCreate(original.createStore);",
31
+ "export default original.default;"
32
+ ]),
33
+ wrap("zustand/vanilla", ["export const createStore = rpr.wrapCreate(original.createStore);", "export default original.default;"]),
34
+ wrap("zustand/react/shallow", ["export const useShallow = rpr.wrapUseShallow(original.useShallow);"]),
35
+ // v5 also exports `useShallow` from here, and keeps the store with an equality function in `traditional`.
36
+ wrap("zustand/shallow", ["export const useShallow = rpr.wrapUseShallow(original.useShallow);", "export default original.default;"]),
37
+ wrap("zustand/traditional", ["export const createWithEqualityFn = rpr.wrapCreate(original.createWithEqualityFn);"])
38
+ ]);
39
+ return {
40
+ name: "zustand",
41
+ init: (ctx) => void (context = ctx),
42
+ vite: {
43
+ config: () => ({ optimizeDeps: { include: ["zustand", "zustand/vanilla", "zustand/react/shallow"] } }),
44
+ resolveId: (id, importer) => proxies.resolveId(id, importer),
45
+ load: (id) => proxies.load(id),
46
+ transform(code, id) {
47
+ if (!filter(id) || !functions.some((fn) => code.includes(fn))) return null;
48
+ const names = findDeclarations(code, functions, { file: id });
49
+ const out = appendLines(code, [
50
+ `import { nameStore as __rprNameStore } from ${JSON.stringify(RUNTIME)};`,
51
+ ...names.map((name) => ifDeclared(name, `__rprNameStore(${name}, ${JSON.stringify(name)});`))
52
+ ]);
53
+ return names.length && out ? { code: out, map: null } : null;
54
+ }
55
+ },
56
+ runtime: { module: RUNTIME, options: { devtools: options.devtools ?? true } }
57
+ };
58
+ }
59
+ export {
60
+ zustand
61
+ };