react-jitter 0.3.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/README.md +241 -0
- package/index.d.ts +28 -0
- package/package.json +103 -0
- package/plugin-swc/swc_plugin_react_jitter.wasm +0 -0
- package/runtime/dist/index.d.mts +85 -0
- package/runtime/dist/index.d.ts +85 -0
- package/runtime/dist/index.js +225 -0
- package/runtime/dist/index.mjs +189 -0
package/README.md
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# React Jitter
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/js/react-jitter)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
React Jitter is a developer tool to help you understand why your React components re-render. It pinpoints the exact hooks and values that have changed, so you can optimize your application's performance with precision.
|
|
7
|
+
|
|
8
|
+
## Table of Contents
|
|
9
|
+
|
|
10
|
+
- [Motivation](#motivation)
|
|
11
|
+
- [Getting Started](#getting-started)
|
|
12
|
+
- [Installation](#installation)
|
|
13
|
+
- [Usage](#usage)
|
|
14
|
+
- [API and Configuration](#api-and-configuration)
|
|
15
|
+
- [How It Works](#how-it-works)
|
|
16
|
+
- [Limitations](#limitations)
|
|
17
|
+
- [Contributing](#contributing)
|
|
18
|
+
- [License](#license)
|
|
19
|
+
|
|
20
|
+
## Motivation
|
|
21
|
+
|
|
22
|
+
Unnecessary re-renders are a common source of performance issues in React applications. When a component re-renders, it can be difficult to trace the root cause. React Profiler reports "Hook 7 changed" but not the name of hook or what exactly changed. It also does not provide anything useful for context changes and it just says "Context changed".
|
|
23
|
+
|
|
24
|
+
Existing tools like `why-did-you-render` require you to manually whitelist the hooks and components you want to track. This is impractical in large codebases where you want to monitor everything without tedious configuration. Others, like `react-scan`, are excellent for a high-level overview but does not report hook changes.
|
|
25
|
+
|
|
26
|
+
React Jitter solves these problems by instrumenting your code at build time with an SWC plugin. It tells you the exact hook that caused a re-render and shows you the previous and current values so you can see exactly what changed.
|
|
27
|
+
|
|
28
|
+
## Getting Started
|
|
29
|
+
|
|
30
|
+
### Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install --save-dev react-jitter
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Usage
|
|
37
|
+
|
|
38
|
+
React Jitter is designed to be used **during development only**. You must ensure that both the SWC plugin and the runtime are disabled in your production builds to prevent instrumenting your production code.
|
|
39
|
+
|
|
40
|
+
#### Enabling the SWC Plugin (Development Only)
|
|
41
|
+
|
|
42
|
+
##### Next.js
|
|
43
|
+
|
|
44
|
+
In `next.config.js`, conditionally add the plugin and its configuration based on the environment:
|
|
45
|
+
|
|
46
|
+
```js
|
|
47
|
+
const isDevelopment = process.env.NODE_ENV === "development";
|
|
48
|
+
|
|
49
|
+
const nextConfig = {
|
|
50
|
+
swcMinify: true,
|
|
51
|
+
experimental: {
|
|
52
|
+
swcPlugins: isDevelopment
|
|
53
|
+
? [
|
|
54
|
+
[
|
|
55
|
+
"react-jitter/plugin-swc",
|
|
56
|
+
{
|
|
57
|
+
// An array of hooks to ignore. For example, `["useSelector"]`.
|
|
58
|
+
ignoreHooks: [],
|
|
59
|
+
// An array of glob patterns for files to exclude from instrumentation.
|
|
60
|
+
// By default, `**/node_modules/**` is excluded.
|
|
61
|
+
exclude: [],
|
|
62
|
+
// When true, the arguments of a hook will be captured as strings
|
|
63
|
+
// and included in the `onHookChange` callback. This is disabled
|
|
64
|
+
// by default because it can significantly increase the bundle size,
|
|
65
|
+
// especially if hooks receive large objects or functions as arguments.
|
|
66
|
+
includeArguments: false,
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
]
|
|
70
|
+
: [],
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
module.exports = nextConfig;
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
##### `.swcrc`
|
|
78
|
+
|
|
79
|
+
Since `.swcrc` is a static JSON file, you cannot use logic within it. You should use separate build scripts or a JavaScript-based configuration file (e.g., `swc.config.js`) to apply different SWC configurations for your development and production environments.
|
|
80
|
+
|
|
81
|
+
#### Initializing the Runtime (Development Only)
|
|
82
|
+
|
|
83
|
+
Once the plugin is configured, you need to initialize the runtime in your application. In your main application file (e.g., `_app.tsx` in Next.js), ensure the call is only made in development:
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
// In your app's entry file (e.g., _app.tsx)
|
|
87
|
+
|
|
88
|
+
import { reactJitter } from "react-jitter/runtime";
|
|
89
|
+
|
|
90
|
+
if (process.env.NODE_ENV === "development") {
|
|
91
|
+
reactJitter({
|
|
92
|
+
enabled: true,
|
|
93
|
+
// This function is called whenever a hook's value changes.
|
|
94
|
+
onHookChange: (change) => {
|
|
95
|
+
console.log("Hook changed:", change);
|
|
96
|
+
},
|
|
97
|
+
// This function is called after a component finishes rendering.
|
|
98
|
+
onRender: (render) => {
|
|
99
|
+
console.log("Component rendered:", render);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
You can also change `enabled`, `onHookChange`, and `onRender` at runtime from your browser's developer console. This is useful for temporarily disabling logging or changing the callback behavior without a full page reload.
|
|
106
|
+
|
|
107
|
+
```js
|
|
108
|
+
// Disable React Jitter
|
|
109
|
+
window.reactJitter.enabled = false;
|
|
110
|
+
|
|
111
|
+
// Re-enable React Jitter
|
|
112
|
+
window.reactJitter.enabled = true;
|
|
113
|
+
|
|
114
|
+
// Change the onHookChange callback
|
|
115
|
+
window.reactJitter.onHookChange = (change) => {
|
|
116
|
+
if (change.unstable) {
|
|
117
|
+
console.warn("Unstable hook value:", change);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
// Change the onRender callback
|
|
122
|
+
window.reactJitter.onRender = (render) => {
|
|
123
|
+
if (render.renderCount > 10) {
|
|
124
|
+
console.warn("High render count:", render);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Modern bundlers will tree-shake the `import` and the function call from your production build, so it will have zero performance impact.
|
|
130
|
+
|
|
131
|
+
## API and Configuration
|
|
132
|
+
|
|
133
|
+
The `reactJitter` function accepts a configuration object with two callbacks: `onHookChange` and `onRender`.
|
|
134
|
+
|
|
135
|
+
- `onHookChange`: Called whenever a hook's value changes. It receives a `change` object with details about the hook, its location, and the value that changed.
|
|
136
|
+
- `onRender`: Called after a component finishes rendering. It receives a `render` object with metadata about the component's render cycle.
|
|
137
|
+
|
|
138
|
+
Here is an example of the `change` object from `onHookChange` when an unstable object is detected:
|
|
139
|
+
|
|
140
|
+
```json
|
|
141
|
+
{
|
|
142
|
+
"hook": "useUser",
|
|
143
|
+
"file": "/src/components/UserProfile.tsx",
|
|
144
|
+
"line": 12,
|
|
145
|
+
"offset": 18,
|
|
146
|
+
"scope": {
|
|
147
|
+
"name": "UserProfile",
|
|
148
|
+
"file": "/src/components/UserProfile.tsx",
|
|
149
|
+
"line": 8,
|
|
150
|
+
"offset": 1
|
|
151
|
+
},
|
|
152
|
+
"unstable": true,
|
|
153
|
+
"unstableKeys": ["address"],
|
|
154
|
+
"changedKeys": [],
|
|
155
|
+
"previousResult": {
|
|
156
|
+
"id": "user-123",
|
|
157
|
+
"address": { "street": "123 Main St" }
|
|
158
|
+
},
|
|
159
|
+
"currentResult": {
|
|
160
|
+
"id": "user-123",
|
|
161
|
+
"address": { "street": "123 Main St" }
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
In this case, the `address` object was re-created, causing an unstable reference even though its contents are the same.
|
|
167
|
+
|
|
168
|
+
Here is an example of the `render` object from `onRender`:
|
|
169
|
+
|
|
170
|
+
```json
|
|
171
|
+
{
|
|
172
|
+
"scopeId": "UserProfile-0",
|
|
173
|
+
"renderCount": 5,
|
|
174
|
+
"name": "UserProfile",
|
|
175
|
+
"id": "UserProfile",
|
|
176
|
+
"file": "/src/components/UserProfile.tsx",
|
|
177
|
+
"line": 8,
|
|
178
|
+
"offset": 1,
|
|
179
|
+
"hookResults": {
|
|
180
|
+
"4f23ef0": {
|
|
181
|
+
"id": "user-123",
|
|
182
|
+
"address": { "street": "123 Main St" }
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
This object provides the component's unique instance ID, its render count, location metadata, and a map of all hook results for the current render.
|
|
189
|
+
|
|
190
|
+
You can use the `includeArguments` option to identify which context has changed. When `includeArguments` is set to `true` in the SWC plugin configuration, the `onHookChange` callback will include the arguments passed to the hook. This is especially useful for `useContext`, as it allows you to see which context was used.
|
|
191
|
+
|
|
192
|
+
Here is an example of the `change` object when `includeArguments` is enabled:
|
|
193
|
+
|
|
194
|
+
```json
|
|
195
|
+
{
|
|
196
|
+
"hook": "useContext",
|
|
197
|
+
"arguments": ["UserContext"],
|
|
198
|
+
"file": "/src/components/UserProfile.tsx",
|
|
199
|
+
"line": 12,
|
|
200
|
+
"offset": 18,
|
|
201
|
+
"scope": {
|
|
202
|
+
"name": "UserProfile",
|
|
203
|
+
"file": "/src/components/UserProfile.tsx",
|
|
204
|
+
"line": 8,
|
|
205
|
+
"offset": 1
|
|
206
|
+
},
|
|
207
|
+
"unstable": false,
|
|
208
|
+
"unstableKeys": [],
|
|
209
|
+
"changedKeys": ["user"],
|
|
210
|
+
"previousResult": {
|
|
211
|
+
"user": { "name": "John" }
|
|
212
|
+
},
|
|
213
|
+
"currentResult": {
|
|
214
|
+
"user": { "name": "Jane" }
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
In this example, the `arguments` field shows that the `UserContext` was used, and the `changedKeys` field shows that the `user` property has changed.
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
## How It Works
|
|
223
|
+
|
|
224
|
+
React Jitter is composed of two parts:
|
|
225
|
+
|
|
226
|
+
1. **SWC Plugin**: A plugin for the [SWC compiler](httpss://swc.rs) that transforms your code to add instrumentation. It finds your React components and wraps your hook calls with a light monitoring function.
|
|
227
|
+
2. **Runtime**: A small runtime library that is injected into your components. It keeps track of the values returned by your hooks and, when they change, it reports the differences.
|
|
228
|
+
|
|
229
|
+
One of the most common causes of unnecessary re-renders is "unstable" objects and functions that are re-created on every render. `react-jitter` helps you identify these issues by tracking when a value's reference changes and reporting it to you.
|
|
230
|
+
|
|
231
|
+
## Limitations
|
|
232
|
+
|
|
233
|
+
`react-jitter` detects React components using a set of heuristics. It looks for functions that either use hooks or return JSX, and whose names start with a capital letter. While this covers most common cases, it may not detect all components reliably, especially if you use less common patterns for defining components.
|
|
234
|
+
|
|
235
|
+
## Contributing
|
|
236
|
+
|
|
237
|
+
Contributions are welcome! Please open an issue or submit a pull request if you have any ideas for improvement.
|
|
238
|
+
|
|
239
|
+
## License
|
|
240
|
+
|
|
241
|
+
This project is licensed under the MIT License.
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
type Prettify<T> = {
|
|
2
|
+
[K in keyof T]: T[K];
|
|
3
|
+
} & {};
|
|
4
|
+
|
|
5
|
+
declare module 'react-jitter' {
|
|
6
|
+
/**
|
|
7
|
+
* Configuration options for react-jitter
|
|
8
|
+
*/
|
|
9
|
+
export interface ReactJitterOptions {
|
|
10
|
+
/**
|
|
11
|
+
* List of hook names to ignore (not transform)
|
|
12
|
+
* By default, all default react hooks are ignored except useContext and useReducer.
|
|
13
|
+
*/
|
|
14
|
+
ignoreHooks?: string[];
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* List of glob patterns to exclude from transformation
|
|
18
|
+
* By default, all node_modules are excluded.
|
|
19
|
+
*/
|
|
20
|
+
exclude?: string[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Configuration for react-jitter plugin
|
|
25
|
+
* Can be either a boolean to enable/disable or an options object
|
|
26
|
+
*/
|
|
27
|
+
export type ReactJitterConfig = Prettify<ReactJitterOptions>;
|
|
28
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "react-jitter",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "A developer tool for debugging React performance issues caused by hook changes and component re-renders.",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": {
|
|
7
|
+
"types": "./index.d.ts"
|
|
8
|
+
},
|
|
9
|
+
"./runtime": {
|
|
10
|
+
"types": "./runtime/dist/index.d.ts",
|
|
11
|
+
"import": "./runtime/dist/index.mjs",
|
|
12
|
+
"require": "./runtime/dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./plugin-swc": {
|
|
15
|
+
"default": "./plugin-swc/swc_plugin_react_jitter.wasm"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"main": "plugin-swc/swc_plugin_react_jitter.wasm",
|
|
19
|
+
"types": "./index.d.ts",
|
|
20
|
+
"typesVersions": {
|
|
21
|
+
"*": {
|
|
22
|
+
"runtime": [
|
|
23
|
+
"runtime/dist/index.d.ts"
|
|
24
|
+
],
|
|
25
|
+
"runtime/*": [
|
|
26
|
+
"runtime/dist/*"
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"index.d.ts",
|
|
32
|
+
"plugin-swc/swc_plugin_react_jitter.wasm",
|
|
33
|
+
"runtime/dist"
|
|
34
|
+
],
|
|
35
|
+
"scripts": {
|
|
36
|
+
"prepack": "npm run build",
|
|
37
|
+
"build": "npm run build:runtime && cd plugin-swc && rustup target add wasm32-wasip1 && cargo build --release --target wasm32-wasip1 && cp ./target/wasm32-wasip1/release/swc_plugin_react_jitter.wasm .",
|
|
38
|
+
"build:debug": "npm run build:runtime && cd plugin-swc && rustup target add wasm32-wasip1 && cargo build --target wasm32-wasip1 && cp ./target/wasm32-wasip1/debug/swc_plugin_react_jitter.wasm .",
|
|
39
|
+
"build:runtime": "npm run build --prefix runtime",
|
|
40
|
+
"test": "npm run build && vitest run --testTimeout=0",
|
|
41
|
+
"test:debug": "TEST_DEBUG=true npm run build:debug && vitest run --testTimeout=0",
|
|
42
|
+
"cargo:fix": "cd plugin-swc && cargo clippy --fix --all-targets --all-features --allow-dirty -- -D warnings",
|
|
43
|
+
"release": "npm run build && changeset publish",
|
|
44
|
+
"lint": "biome lint .",
|
|
45
|
+
"format": "biome format --write .",
|
|
46
|
+
"prepare": "husky"
|
|
47
|
+
},
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public"
|
|
50
|
+
},
|
|
51
|
+
"homepage": "https://github.com/sidx1024/react-jitter",
|
|
52
|
+
"repository": {
|
|
53
|
+
"type": "git",
|
|
54
|
+
"url": "git+https://github.com/sidx1024/react-jitter.git",
|
|
55
|
+
"directory": ""
|
|
56
|
+
},
|
|
57
|
+
"bugs": {
|
|
58
|
+
"url": "https://github.com/sidx1024/react-jitter/issues"
|
|
59
|
+
},
|
|
60
|
+
"author": "sidx1024",
|
|
61
|
+
"keywords": [
|
|
62
|
+
"react",
|
|
63
|
+
"react-hooks",
|
|
64
|
+
"performance",
|
|
65
|
+
"re-render",
|
|
66
|
+
"debug",
|
|
67
|
+
"developer-tools",
|
|
68
|
+
"swc",
|
|
69
|
+
"swc-plugin",
|
|
70
|
+
"instrumentation"
|
|
71
|
+
],
|
|
72
|
+
"license": "MIT",
|
|
73
|
+
"workspaces": [
|
|
74
|
+
".",
|
|
75
|
+
"runtime",
|
|
76
|
+
"plugin-swc"
|
|
77
|
+
],
|
|
78
|
+
"preferUnplugged": true,
|
|
79
|
+
"dependencies": {
|
|
80
|
+
"@swc/counter": "^0.1.3"
|
|
81
|
+
},
|
|
82
|
+
"devDependencies": {
|
|
83
|
+
"@biomejs/biome": "^2.1.3",
|
|
84
|
+
"@changesets/cli": "^2.29.5",
|
|
85
|
+
"@swc/core": "^1.12.0",
|
|
86
|
+
"@types/node": "^22.13.9",
|
|
87
|
+
"husky": "^9.1.7",
|
|
88
|
+
"lint-staged": "^16.1.2",
|
|
89
|
+
"vitest": "^3.0.7"
|
|
90
|
+
},
|
|
91
|
+
"lint-staged": {
|
|
92
|
+
"ignore": [
|
|
93
|
+
"runtime/dist/**/*"
|
|
94
|
+
],
|
|
95
|
+
"*.{js,jsx,ts,tsx}": [
|
|
96
|
+
"biome format --write --no-errors-on-unmatched",
|
|
97
|
+
"biome lint --no-errors-on-unmatched"
|
|
98
|
+
],
|
|
99
|
+
"*.rs": [
|
|
100
|
+
"bash -c 'npm run cargo:fix'"
|
|
101
|
+
]
|
|
102
|
+
}
|
|
103
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
declare const ScopeSchema: z.ZodObject<{
|
|
4
|
+
name: z.ZodString;
|
|
5
|
+
id: z.ZodString;
|
|
6
|
+
file: z.ZodString;
|
|
7
|
+
line: z.ZodNumber;
|
|
8
|
+
offset: z.ZodNumber;
|
|
9
|
+
}, "strip", z.ZodTypeAny, {
|
|
10
|
+
name: string;
|
|
11
|
+
id: string;
|
|
12
|
+
file: string;
|
|
13
|
+
line: number;
|
|
14
|
+
offset: number;
|
|
15
|
+
}, {
|
|
16
|
+
name: string;
|
|
17
|
+
id: string;
|
|
18
|
+
file: string;
|
|
19
|
+
line: number;
|
|
20
|
+
offset: number;
|
|
21
|
+
}>;
|
|
22
|
+
declare const HookChangeSchema: z.ZodObject<{
|
|
23
|
+
unstable: z.ZodBoolean;
|
|
24
|
+
unstableKeys: z.ZodArray<z.ZodString, "many">;
|
|
25
|
+
changedKeys: z.ZodArray<z.ZodString, "many">;
|
|
26
|
+
}, "strip", z.ZodTypeAny, {
|
|
27
|
+
unstable: boolean;
|
|
28
|
+
unstableKeys: string[];
|
|
29
|
+
changedKeys: string[];
|
|
30
|
+
}, {
|
|
31
|
+
unstable: boolean;
|
|
32
|
+
unstableKeys: string[];
|
|
33
|
+
changedKeys: string[];
|
|
34
|
+
}>;
|
|
35
|
+
type HookChange = z.infer<typeof HookChangeSchema>;
|
|
36
|
+
type HookEndEvent = {
|
|
37
|
+
id: string;
|
|
38
|
+
hook: string;
|
|
39
|
+
file: string;
|
|
40
|
+
line: number;
|
|
41
|
+
offset: number;
|
|
42
|
+
arguments?: string[];
|
|
43
|
+
};
|
|
44
|
+
type ReactJitterOptions = {
|
|
45
|
+
enabled?: boolean;
|
|
46
|
+
onHookChange?: (change: HookChange) => void;
|
|
47
|
+
onRender?: (scope: Scope & {
|
|
48
|
+
scopeId: string;
|
|
49
|
+
hookResults: Record<string, unknown>;
|
|
50
|
+
renderCount: number;
|
|
51
|
+
}) => void;
|
|
52
|
+
};
|
|
53
|
+
type Scope = z.infer<typeof ScopeSchema>;
|
|
54
|
+
|
|
55
|
+
type HookCall = HookChange & HookEndEvent & {
|
|
56
|
+
scope: Scope;
|
|
57
|
+
previousResult: unknown;
|
|
58
|
+
currentResult: unknown;
|
|
59
|
+
};
|
|
60
|
+
declare global {
|
|
61
|
+
interface Window {
|
|
62
|
+
reactJitter?: {
|
|
63
|
+
enabled?: boolean;
|
|
64
|
+
onHookChange?: (change: HookCall) => void;
|
|
65
|
+
onRender?: (scope: Scope & {
|
|
66
|
+
hookResults: Record<string, unknown>;
|
|
67
|
+
renderCount: number;
|
|
68
|
+
}) => void;
|
|
69
|
+
clear: () => void;
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* A React hook that creates a jitter scope for measuring component performance.
|
|
75
|
+
* @param options Configuration options for the jitter scope
|
|
76
|
+
* @returns void
|
|
77
|
+
*/
|
|
78
|
+
declare function useJitterScope(scope: Scope): {
|
|
79
|
+
s: (id: string) => void;
|
|
80
|
+
e: (hookResult: unknown, hookEndEvent: HookEndEvent) => unknown;
|
|
81
|
+
re: <T>(renderResult: T) => T;
|
|
82
|
+
};
|
|
83
|
+
declare function reactJitter(options: ReactJitterOptions): void;
|
|
84
|
+
|
|
85
|
+
export { reactJitter, useJitterScope };
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
declare const ScopeSchema: z.ZodObject<{
|
|
4
|
+
name: z.ZodString;
|
|
5
|
+
id: z.ZodString;
|
|
6
|
+
file: z.ZodString;
|
|
7
|
+
line: z.ZodNumber;
|
|
8
|
+
offset: z.ZodNumber;
|
|
9
|
+
}, "strip", z.ZodTypeAny, {
|
|
10
|
+
name: string;
|
|
11
|
+
id: string;
|
|
12
|
+
file: string;
|
|
13
|
+
line: number;
|
|
14
|
+
offset: number;
|
|
15
|
+
}, {
|
|
16
|
+
name: string;
|
|
17
|
+
id: string;
|
|
18
|
+
file: string;
|
|
19
|
+
line: number;
|
|
20
|
+
offset: number;
|
|
21
|
+
}>;
|
|
22
|
+
declare const HookChangeSchema: z.ZodObject<{
|
|
23
|
+
unstable: z.ZodBoolean;
|
|
24
|
+
unstableKeys: z.ZodArray<z.ZodString, "many">;
|
|
25
|
+
changedKeys: z.ZodArray<z.ZodString, "many">;
|
|
26
|
+
}, "strip", z.ZodTypeAny, {
|
|
27
|
+
unstable: boolean;
|
|
28
|
+
unstableKeys: string[];
|
|
29
|
+
changedKeys: string[];
|
|
30
|
+
}, {
|
|
31
|
+
unstable: boolean;
|
|
32
|
+
unstableKeys: string[];
|
|
33
|
+
changedKeys: string[];
|
|
34
|
+
}>;
|
|
35
|
+
type HookChange = z.infer<typeof HookChangeSchema>;
|
|
36
|
+
type HookEndEvent = {
|
|
37
|
+
id: string;
|
|
38
|
+
hook: string;
|
|
39
|
+
file: string;
|
|
40
|
+
line: number;
|
|
41
|
+
offset: number;
|
|
42
|
+
arguments?: string[];
|
|
43
|
+
};
|
|
44
|
+
type ReactJitterOptions = {
|
|
45
|
+
enabled?: boolean;
|
|
46
|
+
onHookChange?: (change: HookChange) => void;
|
|
47
|
+
onRender?: (scope: Scope & {
|
|
48
|
+
scopeId: string;
|
|
49
|
+
hookResults: Record<string, unknown>;
|
|
50
|
+
renderCount: number;
|
|
51
|
+
}) => void;
|
|
52
|
+
};
|
|
53
|
+
type Scope = z.infer<typeof ScopeSchema>;
|
|
54
|
+
|
|
55
|
+
type HookCall = HookChange & HookEndEvent & {
|
|
56
|
+
scope: Scope;
|
|
57
|
+
previousResult: unknown;
|
|
58
|
+
currentResult: unknown;
|
|
59
|
+
};
|
|
60
|
+
declare global {
|
|
61
|
+
interface Window {
|
|
62
|
+
reactJitter?: {
|
|
63
|
+
enabled?: boolean;
|
|
64
|
+
onHookChange?: (change: HookCall) => void;
|
|
65
|
+
onRender?: (scope: Scope & {
|
|
66
|
+
hookResults: Record<string, unknown>;
|
|
67
|
+
renderCount: number;
|
|
68
|
+
}) => void;
|
|
69
|
+
clear: () => void;
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* A React hook that creates a jitter scope for measuring component performance.
|
|
75
|
+
* @param options Configuration options for the jitter scope
|
|
76
|
+
* @returns void
|
|
77
|
+
*/
|
|
78
|
+
declare function useJitterScope(scope: Scope): {
|
|
79
|
+
s: (id: string) => void;
|
|
80
|
+
e: (hookResult: unknown, hookEndEvent: HookEndEvent) => unknown;
|
|
81
|
+
re: <T>(renderResult: T) => T;
|
|
82
|
+
};
|
|
83
|
+
declare function reactJitter(options: ReactJitterOptions): void;
|
|
84
|
+
|
|
85
|
+
export { reactJitter, useJitterScope };
|
|
@@ -0,0 +1,225 @@
|
|
|
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/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
reactJitter: () => reactJitter,
|
|
34
|
+
useJitterScope: () => useJitterScope
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(index_exports);
|
|
37
|
+
var import_react = __toESM(require("react"));
|
|
38
|
+
|
|
39
|
+
// src/utils/getChanges.ts
|
|
40
|
+
var import_fast_equals = require("fast-equals");
|
|
41
|
+
function getChanges(prev, next) {
|
|
42
|
+
const changedKeys = [];
|
|
43
|
+
const unstableKeys = [];
|
|
44
|
+
const isObject = (v) => v !== null && typeof v === "object";
|
|
45
|
+
const prevIsArr = Array.isArray(prev);
|
|
46
|
+
const nextIsArr = Array.isArray(next);
|
|
47
|
+
if (prevIsArr !== nextIsArr) {
|
|
48
|
+
return {
|
|
49
|
+
unstable: false,
|
|
50
|
+
unstableKeys: [],
|
|
51
|
+
changedKeys: ["*"]
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
if (prevIsArr && nextIsArr) {
|
|
55
|
+
if (prev.length !== next.length) {
|
|
56
|
+
changedKeys.push("length");
|
|
57
|
+
}
|
|
58
|
+
const max = Math.max(prev.length, next.length);
|
|
59
|
+
for (let i = 0; i < max; i++) {
|
|
60
|
+
const deepEqItem = (0, import_fast_equals.deepEqual)(prev[i], next[i]);
|
|
61
|
+
const refDiffItem = isObject(prev[i]) && isObject(next[i]) && prev[i] !== next[i];
|
|
62
|
+
if (!deepEqItem || refDiffItem) {
|
|
63
|
+
const key = String(i);
|
|
64
|
+
changedKeys.push(key);
|
|
65
|
+
if (refDiffItem && deepEqItem) {
|
|
66
|
+
unstableKeys.push(key);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
} else if (isObject(prev) && isObject(next)) {
|
|
71
|
+
const allKeys = /* @__PURE__ */ new Set([...Object.keys(prev), ...Object.keys(next)]);
|
|
72
|
+
for (const key of allKeys) {
|
|
73
|
+
const deepEqProp = (0, import_fast_equals.deepEqual)(prev[key], next[key]);
|
|
74
|
+
const refDiffProp = isObject(prev[key]) && isObject(next[key]) && prev[key] !== next[key];
|
|
75
|
+
if (!deepEqProp || refDiffProp) {
|
|
76
|
+
changedKeys.push(key);
|
|
77
|
+
if (refDiffProp && deepEqProp) {
|
|
78
|
+
unstableKeys.push(key);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
} else {
|
|
83
|
+
const deepEqRoot = (0, import_fast_equals.deepEqual)(prev, next);
|
|
84
|
+
const refDiffRoot = isObject(prev) && isObject(next) && prev !== next;
|
|
85
|
+
const unstable = refDiffRoot && deepEqRoot;
|
|
86
|
+
const changed = !deepEqRoot || refDiffRoot;
|
|
87
|
+
return {
|
|
88
|
+
unstable,
|
|
89
|
+
unstableKeys: [],
|
|
90
|
+
changedKeys: changed ? [""] : []
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
const isPlainObject = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
94
|
+
const unstableRoot = isPlainObject(prev) && isPlainObject(next) && prev !== next && (0, import_fast_equals.deepEqual)(prev, next);
|
|
95
|
+
if (unstableRoot && changedKeys.length === 0) {
|
|
96
|
+
changedKeys.push("");
|
|
97
|
+
unstableKeys.push("");
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
unstable: unstableKeys.length > 0,
|
|
101
|
+
unstableKeys,
|
|
102
|
+
changedKeys
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// src/index.ts
|
|
107
|
+
var scopes = {};
|
|
108
|
+
var hookStack = /* @__PURE__ */ new Map();
|
|
109
|
+
var scopeCounter = {};
|
|
110
|
+
function useJitterScope(scope) {
|
|
111
|
+
const scopeCount = import_react.default.useRef(getScopeCount(scope)).current;
|
|
112
|
+
const scopeId = `${scope.id}-${scopeCount}`;
|
|
113
|
+
if (!scopes[scopeId]) {
|
|
114
|
+
scopes[scopeId] = {
|
|
115
|
+
scopeId,
|
|
116
|
+
renderCount: 0,
|
|
117
|
+
...scope,
|
|
118
|
+
hookResults: {},
|
|
119
|
+
hookChanges: []
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
scopes[scopeId].renderCount++;
|
|
123
|
+
const hooks = import_react.default.useRef(null);
|
|
124
|
+
if (!hooks.current) {
|
|
125
|
+
hooks.current = {
|
|
126
|
+
s: (id) => {
|
|
127
|
+
const hookId = `${scopeId}-${id}`;
|
|
128
|
+
hookStack.set(hookId, null);
|
|
129
|
+
},
|
|
130
|
+
e: (hookResult, hookEndEvent) => {
|
|
131
|
+
const currentScope = scopes[scopeId];
|
|
132
|
+
if (!currentScope) {
|
|
133
|
+
return hookResult;
|
|
134
|
+
}
|
|
135
|
+
const hookId = `${scopeId}-${hookEndEvent.id}`;
|
|
136
|
+
if (shouldReportChanges()) {
|
|
137
|
+
const prevResult = currentScope.hookResults[hookId];
|
|
138
|
+
const changes = compareChanges(prevResult, hookResult);
|
|
139
|
+
if (changes) {
|
|
140
|
+
const hookCall = {
|
|
141
|
+
hook: hookEndEvent.hook,
|
|
142
|
+
file: hookEndEvent.file,
|
|
143
|
+
line: hookEndEvent.line,
|
|
144
|
+
offset: hookEndEvent.offset,
|
|
145
|
+
id: hookEndEvent.id,
|
|
146
|
+
scope,
|
|
147
|
+
...changes,
|
|
148
|
+
previousResult: prevResult,
|
|
149
|
+
currentResult: hookResult
|
|
150
|
+
};
|
|
151
|
+
if (hookEndEvent.arguments) {
|
|
152
|
+
hookCall.arguments = hookEndEvent.arguments;
|
|
153
|
+
}
|
|
154
|
+
scopes[scopeId].hookChanges.push(hookCall);
|
|
155
|
+
callOnHookChange(hookCall);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
currentScope.hookResults[hookId] = hookResult;
|
|
159
|
+
hookStack.delete(hookId);
|
|
160
|
+
return hookResult;
|
|
161
|
+
},
|
|
162
|
+
re: (renderResult) => {
|
|
163
|
+
callOnRender(scopes[scopeId]);
|
|
164
|
+
return renderResult;
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
return hooks.current;
|
|
169
|
+
}
|
|
170
|
+
function reactJitter(options) {
|
|
171
|
+
var _a, _b, _c, _d, _e, _f;
|
|
172
|
+
if (typeof window === "undefined") {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const windowGlobal = window;
|
|
176
|
+
windowGlobal.reactJitter = {
|
|
177
|
+
enabled: (_b = (_a = windowGlobal.reactJitter) == null ? void 0 : _a.enabled) != null ? _b : options.enabled,
|
|
178
|
+
onHookChange: (_d = (_c = windowGlobal.reactJitter) == null ? void 0 : _c.onHookChange) != null ? _d : options.onHookChange,
|
|
179
|
+
onRender: (_f = (_e = windowGlobal.reactJitter) == null ? void 0 : _e.onRender) != null ? _f : options.onRender,
|
|
180
|
+
clear: () => {
|
|
181
|
+
Object.keys(scopes).forEach((key) => {
|
|
182
|
+
scopes[key].renderCount = 0;
|
|
183
|
+
scopes[key].hookChanges = [];
|
|
184
|
+
scopes[key].hookResults = {};
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
function shouldReportChanges() {
|
|
190
|
+
var _a;
|
|
191
|
+
return typeof ((_a = window == null ? void 0 : window.reactJitter) == null ? void 0 : _a.onHookChange) === "function" && window.reactJitter.enabled;
|
|
192
|
+
}
|
|
193
|
+
function callOnHookChange(hookResult) {
|
|
194
|
+
var _a;
|
|
195
|
+
if (shouldReportChanges() && ((_a = window.reactJitter) == null ? void 0 : _a.onHookChange)) {
|
|
196
|
+
window.reactJitter.onHookChange(hookResult);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
function shouldReportRender() {
|
|
200
|
+
var _a;
|
|
201
|
+
return typeof ((_a = window == null ? void 0 : window.reactJitter) == null ? void 0 : _a.onRender) === "function" && window.reactJitter.enabled;
|
|
202
|
+
}
|
|
203
|
+
function callOnRender(scope) {
|
|
204
|
+
var _a;
|
|
205
|
+
if (shouldReportRender() && ((_a = window.reactJitter) == null ? void 0 : _a.onRender)) {
|
|
206
|
+
window.reactJitter.onRender(scope);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
function getScopeCount(scope) {
|
|
210
|
+
if (!scopeCounter[scope.id]) {
|
|
211
|
+
scopeCounter[scope.id] = 0;
|
|
212
|
+
}
|
|
213
|
+
return scopeCounter[scope.id]++;
|
|
214
|
+
}
|
|
215
|
+
function compareChanges(prev, current) {
|
|
216
|
+
if (prev !== "undefined" && prev !== current) {
|
|
217
|
+
return getChanges(prev, current);
|
|
218
|
+
}
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
222
|
+
0 && (module.exports = {
|
|
223
|
+
reactJitter,
|
|
224
|
+
useJitterScope
|
|
225
|
+
});
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import React from "react";
|
|
3
|
+
|
|
4
|
+
// src/utils/getChanges.ts
|
|
5
|
+
import { deepEqual } from "fast-equals";
|
|
6
|
+
function getChanges(prev, next) {
|
|
7
|
+
const changedKeys = [];
|
|
8
|
+
const unstableKeys = [];
|
|
9
|
+
const isObject = (v) => v !== null && typeof v === "object";
|
|
10
|
+
const prevIsArr = Array.isArray(prev);
|
|
11
|
+
const nextIsArr = Array.isArray(next);
|
|
12
|
+
if (prevIsArr !== nextIsArr) {
|
|
13
|
+
return {
|
|
14
|
+
unstable: false,
|
|
15
|
+
unstableKeys: [],
|
|
16
|
+
changedKeys: ["*"]
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
if (prevIsArr && nextIsArr) {
|
|
20
|
+
if (prev.length !== next.length) {
|
|
21
|
+
changedKeys.push("length");
|
|
22
|
+
}
|
|
23
|
+
const max = Math.max(prev.length, next.length);
|
|
24
|
+
for (let i = 0; i < max; i++) {
|
|
25
|
+
const deepEqItem = deepEqual(prev[i], next[i]);
|
|
26
|
+
const refDiffItem = isObject(prev[i]) && isObject(next[i]) && prev[i] !== next[i];
|
|
27
|
+
if (!deepEqItem || refDiffItem) {
|
|
28
|
+
const key = String(i);
|
|
29
|
+
changedKeys.push(key);
|
|
30
|
+
if (refDiffItem && deepEqItem) {
|
|
31
|
+
unstableKeys.push(key);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
} else if (isObject(prev) && isObject(next)) {
|
|
36
|
+
const allKeys = /* @__PURE__ */ new Set([...Object.keys(prev), ...Object.keys(next)]);
|
|
37
|
+
for (const key of allKeys) {
|
|
38
|
+
const deepEqProp = deepEqual(prev[key], next[key]);
|
|
39
|
+
const refDiffProp = isObject(prev[key]) && isObject(next[key]) && prev[key] !== next[key];
|
|
40
|
+
if (!deepEqProp || refDiffProp) {
|
|
41
|
+
changedKeys.push(key);
|
|
42
|
+
if (refDiffProp && deepEqProp) {
|
|
43
|
+
unstableKeys.push(key);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
} else {
|
|
48
|
+
const deepEqRoot = deepEqual(prev, next);
|
|
49
|
+
const refDiffRoot = isObject(prev) && isObject(next) && prev !== next;
|
|
50
|
+
const unstable = refDiffRoot && deepEqRoot;
|
|
51
|
+
const changed = !deepEqRoot || refDiffRoot;
|
|
52
|
+
return {
|
|
53
|
+
unstable,
|
|
54
|
+
unstableKeys: [],
|
|
55
|
+
changedKeys: changed ? [""] : []
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
const isPlainObject = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
59
|
+
const unstableRoot = isPlainObject(prev) && isPlainObject(next) && prev !== next && deepEqual(prev, next);
|
|
60
|
+
if (unstableRoot && changedKeys.length === 0) {
|
|
61
|
+
changedKeys.push("");
|
|
62
|
+
unstableKeys.push("");
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
unstable: unstableKeys.length > 0,
|
|
66
|
+
unstableKeys,
|
|
67
|
+
changedKeys
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// src/index.ts
|
|
72
|
+
var scopes = {};
|
|
73
|
+
var hookStack = /* @__PURE__ */ new Map();
|
|
74
|
+
var scopeCounter = {};
|
|
75
|
+
function useJitterScope(scope) {
|
|
76
|
+
const scopeCount = React.useRef(getScopeCount(scope)).current;
|
|
77
|
+
const scopeId = `${scope.id}-${scopeCount}`;
|
|
78
|
+
if (!scopes[scopeId]) {
|
|
79
|
+
scopes[scopeId] = {
|
|
80
|
+
scopeId,
|
|
81
|
+
renderCount: 0,
|
|
82
|
+
...scope,
|
|
83
|
+
hookResults: {},
|
|
84
|
+
hookChanges: []
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
scopes[scopeId].renderCount++;
|
|
88
|
+
const hooks = React.useRef(null);
|
|
89
|
+
if (!hooks.current) {
|
|
90
|
+
hooks.current = {
|
|
91
|
+
s: (id) => {
|
|
92
|
+
const hookId = `${scopeId}-${id}`;
|
|
93
|
+
hookStack.set(hookId, null);
|
|
94
|
+
},
|
|
95
|
+
e: (hookResult, hookEndEvent) => {
|
|
96
|
+
const currentScope = scopes[scopeId];
|
|
97
|
+
if (!currentScope) {
|
|
98
|
+
return hookResult;
|
|
99
|
+
}
|
|
100
|
+
const hookId = `${scopeId}-${hookEndEvent.id}`;
|
|
101
|
+
if (shouldReportChanges()) {
|
|
102
|
+
const prevResult = currentScope.hookResults[hookId];
|
|
103
|
+
const changes = compareChanges(prevResult, hookResult);
|
|
104
|
+
if (changes) {
|
|
105
|
+
const hookCall = {
|
|
106
|
+
hook: hookEndEvent.hook,
|
|
107
|
+
file: hookEndEvent.file,
|
|
108
|
+
line: hookEndEvent.line,
|
|
109
|
+
offset: hookEndEvent.offset,
|
|
110
|
+
id: hookEndEvent.id,
|
|
111
|
+
scope,
|
|
112
|
+
...changes,
|
|
113
|
+
previousResult: prevResult,
|
|
114
|
+
currentResult: hookResult
|
|
115
|
+
};
|
|
116
|
+
if (hookEndEvent.arguments) {
|
|
117
|
+
hookCall.arguments = hookEndEvent.arguments;
|
|
118
|
+
}
|
|
119
|
+
scopes[scopeId].hookChanges.push(hookCall);
|
|
120
|
+
callOnHookChange(hookCall);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
currentScope.hookResults[hookId] = hookResult;
|
|
124
|
+
hookStack.delete(hookId);
|
|
125
|
+
return hookResult;
|
|
126
|
+
},
|
|
127
|
+
re: (renderResult) => {
|
|
128
|
+
callOnRender(scopes[scopeId]);
|
|
129
|
+
return renderResult;
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
return hooks.current;
|
|
134
|
+
}
|
|
135
|
+
function reactJitter(options) {
|
|
136
|
+
var _a, _b, _c, _d, _e, _f;
|
|
137
|
+
if (typeof window === "undefined") {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const windowGlobal = window;
|
|
141
|
+
windowGlobal.reactJitter = {
|
|
142
|
+
enabled: (_b = (_a = windowGlobal.reactJitter) == null ? void 0 : _a.enabled) != null ? _b : options.enabled,
|
|
143
|
+
onHookChange: (_d = (_c = windowGlobal.reactJitter) == null ? void 0 : _c.onHookChange) != null ? _d : options.onHookChange,
|
|
144
|
+
onRender: (_f = (_e = windowGlobal.reactJitter) == null ? void 0 : _e.onRender) != null ? _f : options.onRender,
|
|
145
|
+
clear: () => {
|
|
146
|
+
Object.keys(scopes).forEach((key) => {
|
|
147
|
+
scopes[key].renderCount = 0;
|
|
148
|
+
scopes[key].hookChanges = [];
|
|
149
|
+
scopes[key].hookResults = {};
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function shouldReportChanges() {
|
|
155
|
+
var _a;
|
|
156
|
+
return typeof ((_a = window == null ? void 0 : window.reactJitter) == null ? void 0 : _a.onHookChange) === "function" && window.reactJitter.enabled;
|
|
157
|
+
}
|
|
158
|
+
function callOnHookChange(hookResult) {
|
|
159
|
+
var _a;
|
|
160
|
+
if (shouldReportChanges() && ((_a = window.reactJitter) == null ? void 0 : _a.onHookChange)) {
|
|
161
|
+
window.reactJitter.onHookChange(hookResult);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function shouldReportRender() {
|
|
165
|
+
var _a;
|
|
166
|
+
return typeof ((_a = window == null ? void 0 : window.reactJitter) == null ? void 0 : _a.onRender) === "function" && window.reactJitter.enabled;
|
|
167
|
+
}
|
|
168
|
+
function callOnRender(scope) {
|
|
169
|
+
var _a;
|
|
170
|
+
if (shouldReportRender() && ((_a = window.reactJitter) == null ? void 0 : _a.onRender)) {
|
|
171
|
+
window.reactJitter.onRender(scope);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function getScopeCount(scope) {
|
|
175
|
+
if (!scopeCounter[scope.id]) {
|
|
176
|
+
scopeCounter[scope.id] = 0;
|
|
177
|
+
}
|
|
178
|
+
return scopeCounter[scope.id]++;
|
|
179
|
+
}
|
|
180
|
+
function compareChanges(prev, current) {
|
|
181
|
+
if (prev !== "undefined" && prev !== current) {
|
|
182
|
+
return getChanges(prev, current);
|
|
183
|
+
}
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
export {
|
|
187
|
+
reactJitter,
|
|
188
|
+
useJitterScope
|
|
189
|
+
};
|