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,74 @@
1
+ import {
2
+ definePlugin
3
+ } from "../chunk-NTY2W4HE.js";
4
+ import "../chunk-7DJUCCWG.js";
5
+
6
+ // src/plugins/react-query/runtime.ts
7
+ var ACTIONS = /* @__PURE__ */ new Set(["fetch", "success", "error", "invalidate"]);
8
+ var SEARCH_EVERY_MS = 1e3;
9
+ var runtime_default = definePlugin(() => {
10
+ let unsubscribe = null;
11
+ let found = false;
12
+ let searchedAt = -Infinity;
13
+ const counts = /* @__PURE__ */ new Map();
14
+ const kinds = /* @__PURE__ */ new WeakMap();
15
+ function search(session) {
16
+ searchedAt = session.now();
17
+ const [provider] = session.findFibers((f) => typeof f.memoizedProps?.client?.getQueryCache === "function", 1);
18
+ if (!provider) return;
19
+ found = true;
20
+ const cache = provider.memoizedProps.client.getQueryCache();
21
+ unsubscribe = cache.subscribe((event) => {
22
+ const action = event.action?.type;
23
+ const updated = event.type === "updated";
24
+ if (!(event.type === "added" || event.type === "removed" || updated && action && ACTIONS.has(action))) return;
25
+ const key = JSON.stringify(event.query.queryKey).slice(0, 70);
26
+ const kind = updated ? action : event.type;
27
+ counts.set(`${kind} ${key}`, (counts.get(`${kind} ${key}`) ?? 0) + 1);
28
+ if (!updated) {
29
+ session.emitCause({ type: `${kind} ${key}` });
30
+ return;
31
+ }
32
+ const cause = session.emitCause({ type: `${kind} ${key}`, waitForTimer: true, merge: key });
33
+ if (!cause) return;
34
+ const seen = kinds.get(cause);
35
+ if (seen) {
36
+ if (!seen.includes(kind)) seen.push(kind);
37
+ cause.type = `${seen.join(" \u2192 ")} ${key}`;
38
+ } else kinds.set(cause, [kind]);
39
+ });
40
+ }
41
+ return {
42
+ name: "react-query",
43
+ packages: ["@tanstack/react-query", "@tanstack/query-core"],
44
+ start(session) {
45
+ counts.clear();
46
+ found = false;
47
+ search(session);
48
+ },
49
+ commit(session) {
50
+ if (!found && session.now() - searchedAt >= SEARCH_EVERY_MS) search(session);
51
+ },
52
+ stop(session) {
53
+ if (!found) search(session);
54
+ unsubscribe?.();
55
+ unsubscribe = null;
56
+ const list = [...counts].sort((a, b) => b[1] - a[1]);
57
+ const byKind = /* @__PURE__ */ new Map();
58
+ for (const [type, n] of list) {
59
+ const kind = type.split(" ")[0];
60
+ byKind.set(kind, (byKind.get(kind) ?? 0) + n);
61
+ }
62
+ return {
63
+ version: 1,
64
+ active: found,
65
+ highlights: list.length ? list.slice(0, 3).map(([type, n]) => `${n}\xD7 ${type}`) : ["no query events during the recording"],
66
+ metrics: Object.fromEntries([...byKind].map(([kind, n]) => [`events.${kind}`, { value: n, kind: "count" }])),
67
+ data: { clientFound: found, events: list.slice(0, 50) }
68
+ };
69
+ }
70
+ };
71
+ });
72
+ export {
73
+ runtime_default as default
74
+ };
@@ -0,0 +1,11 @@
1
+ import { a as RuntimePluginFactory } from '../index-BlkKhwHe.js';
2
+
3
+ /** `create(fn)` and the curried `create<T>()(fn)` of zustand, `createStore` of zustand/vanilla. */
4
+ declare function wrapCreate<F extends (...args: any[]) => any>(factory: F): F;
5
+ declare function wrapUseShallow<F extends (...args: any[]) => any>(useShallow: F): F;
6
+ declare function nameStore(store: unknown, name: string): void;
7
+ declare const _default: RuntimePluginFactory<{
8
+ devtools?: boolean;
9
+ } | null>;
10
+
11
+ export { _default as default, nameStore, wrapCreate, wrapUseShallow };
@@ -0,0 +1,171 @@
1
+ import {
2
+ definePlugin
3
+ } from "../chunk-NTY2W4HE.js";
4
+ import "../chunk-7DJUCCWG.js";
5
+
6
+ // src/plugins/zustand/devtools-shim.ts
7
+ var KEY = "__REDUX_DEVTOOLS_EXTENSION__";
8
+ function installDevtoolsShim(onSend) {
9
+ const target = window;
10
+ const existing = Object.getOwnPropertyDescriptor(target, KEY);
11
+ if (existing?.get && existing.get.rpr) return;
12
+ let real = target[KEY] ?? void 0;
13
+ const shim = Object.assign((...args) => real ? real(...args) : (next) => next, {
14
+ connect(options) {
15
+ const connection = real?.connect?.(options);
16
+ return {
17
+ init: (...args) => connection?.init?.(...args),
18
+ subscribe: (listener) => connection?.subscribe?.(listener) ?? (() => {
19
+ }),
20
+ unsubscribe: () => connection?.unsubscribe?.(),
21
+ error: (message) => connection?.error?.(message),
22
+ send(action, state) {
23
+ try {
24
+ onSend(action, state);
25
+ } catch {
26
+ }
27
+ return connection?.send?.(action, state);
28
+ }
29
+ };
30
+ }
31
+ });
32
+ const get = Object.assign(() => shim, { rpr: true });
33
+ Object.defineProperty(target, KEY, {
34
+ configurable: true,
35
+ enumerable: false,
36
+ get,
37
+ set(value) {
38
+ if (value !== shim) real = value;
39
+ }
40
+ });
41
+ }
42
+
43
+ // src/plugins/zustand/runtime.ts
44
+ var stores = /* @__PURE__ */ new Set();
45
+ var apiByGetState = /* @__PURE__ */ new WeakMap();
46
+ var names = /* @__PURE__ */ new WeakMap();
47
+ var shallowInner = /* @__PURE__ */ new WeakMap();
48
+ var eventByState = /* @__PURE__ */ new WeakMap();
49
+ var anonymous = 0;
50
+ var follow = null;
51
+ var apiOf = (value) => {
52
+ const candidate = value;
53
+ return candidate && typeof candidate.getState === "function" && typeof candidate.subscribe === "function" ? candidate : null;
54
+ };
55
+ function register(result) {
56
+ const api = apiOf(result);
57
+ if (api && !apiByGetState.has(api.getState)) {
58
+ apiByGetState.set(api.getState, api);
59
+ stores.add(new WeakRef(api));
60
+ follow?.(api);
61
+ }
62
+ return result;
63
+ }
64
+ function wrapCreate(factory) {
65
+ if (typeof factory !== "function") return factory;
66
+ return function(...args) {
67
+ if (args[0] === void 0) {
68
+ const curried = factory.apply(this, args);
69
+ return (...inner) => register(curried(...inner));
70
+ }
71
+ return register(factory.apply(this, args));
72
+ };
73
+ }
74
+ function wrapUseShallow(useShallow) {
75
+ if (typeof useShallow !== "function") return useShallow;
76
+ return ((selector) => {
77
+ const fn = useShallow(selector);
78
+ shallowInner.set(fn, selector);
79
+ return fn;
80
+ });
81
+ }
82
+ function nameStore(store, name) {
83
+ const api = apiOf(store);
84
+ if (api) names.set(api, name);
85
+ }
86
+ var storeName = (api) => {
87
+ let name = names.get(api);
88
+ if (!name) names.set(api, name = `store${++anonymous}`);
89
+ return name;
90
+ };
91
+ var MAX_KEYS = 40;
92
+ function changedKeys(prev, next) {
93
+ if (!prev || !next || typeof prev !== "object" || typeof next !== "object") return [{ key: "(state)", prev, next }];
94
+ const a = prev;
95
+ const b = next;
96
+ const out = [];
97
+ for (const key of Object.keys(b)) if (a[key] !== b[key] && out.length < MAX_KEYS) out.push({ key, prev: a[key], next: b[key] });
98
+ return out;
99
+ }
100
+ var runtime_default = definePlugin((options) => {
101
+ const unsubscribes = [];
102
+ const counts = /* @__PURE__ */ new Map();
103
+ let ctx = null;
104
+ let actions = 0;
105
+ return {
106
+ name: "zustand",
107
+ setup(context) {
108
+ ctx = context;
109
+ if (options?.devtools === false) return;
110
+ installDevtoolsShim((action, state) => {
111
+ if (!ctx?.recording || !state || typeof state !== "object") return;
112
+ const event = eventByState.get(state);
113
+ const type = typeof action === "string" ? action : action?.type;
114
+ if (event && type && type !== "anonymous") {
115
+ event.type = type;
116
+ actions++;
117
+ }
118
+ });
119
+ },
120
+ describe(fn, kind, next) {
121
+ if (kind === "selector") {
122
+ const inner = shallowInner.get(fn);
123
+ return inner ? `useShallow(${next(inner)})` : null;
124
+ }
125
+ const api = apiByGetState.get(fn);
126
+ return api ? storeName(api) : null;
127
+ },
128
+ start(session) {
129
+ counts.clear();
130
+ actions = 0;
131
+ follow = (api) => {
132
+ unsubscribes.push(
133
+ api.subscribe((state, prev) => {
134
+ const name = storeName(api);
135
+ counts.set(name, (counts.get(name) ?? 0) + 1);
136
+ const event = session.emitCause({ type: `${name}.setState`, changes: changedKeys(prev, state), data: { store: name }, aim: true });
137
+ if (event && state && typeof state === "object") eventByState.set(state, event);
138
+ })
139
+ );
140
+ };
141
+ for (const ref of stores) {
142
+ const api = ref.deref();
143
+ if (api) follow(api);
144
+ else stores.delete(ref);
145
+ }
146
+ },
147
+ stop() {
148
+ follow = null;
149
+ unsubscribes.splice(0).forEach((unsubscribe) => unsubscribe());
150
+ const list = [...counts].sort((a, b) => b[1] - a[1]);
151
+ let active = false;
152
+ for (const ref of stores) if (ref.deref()) active = true;
153
+ return {
154
+ version: 1,
155
+ active,
156
+ highlights: list.length ? list.slice(0, 3).map(([name, n]) => `${name}: ${n} updates`) : ["no store updates during the recording"],
157
+ metrics: {
158
+ "actions.named": { value: actions, kind: "count" },
159
+ ...Object.fromEntries(list.map(([name, n]) => [`${name}.updates`, { value: n, kind: "count" }]))
160
+ },
161
+ data: { stores: list.map(([name, updates]) => ({ name, updates })) }
162
+ };
163
+ }
164
+ };
165
+ });
166
+ export {
167
+ runtime_default as default,
168
+ nameStore,
169
+ wrapCreate,
170
+ wrapUseShallow
171
+ };
@@ -0,0 +1 @@
1
+ export { C as CauseInput, e as Conditions, D as DescribeKind, F as Fiber, J as JsonValue, M as MemoInstrumentation, i as MemoStat, b as PluginContext, J as PluginOptions, d as PluginSection, c as Primitive, R as RuntimePlugin, a as RuntimePluginFactory, S as SessionContext, j as createMemoInstrumentation, k as definePlugin, n as noteRoot } from './index-BlkKhwHe.js';
@@ -0,0 +1,12 @@
1
+ import {
2
+ createMemoInstrumentation,
3
+ definePlugin
4
+ } from "./chunk-NTY2W4HE.js";
5
+ import {
6
+ noteRoot
7
+ } from "./chunk-7DJUCCWG.js";
8
+ export {
9
+ createMemoInstrumentation,
10
+ definePlugin,
11
+ noteRoot
12
+ };