why-render-react 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.
@@ -0,0 +1,401 @@
1
+ /**
2
+ * Configuration manager for Why Render.
3
+ *
4
+ * Why Render is disabled by default and must be explicitly enabled by passing
5
+ * the boolean `true` or a string equivalent to `"true"`.
6
+ *
7
+ * Only the following values enable Why Render:
8
+ *
9
+ * - `true`
10
+ * - `"true"` (case-insensitive)
11
+ * - Strings with surrounding whitespace, such as `" true "`
12
+ *
13
+ * All other values disable Why Render, including:
14
+ *
15
+ * - `false`
16
+ * - `undefined`
17
+ * - `"false"`
18
+ * - `"1"`
19
+ * - `"yes"`
20
+ * - An empty string
21
+ *
22
+ * ## Important
23
+ *
24
+ * This configuration receives a resolved value rather than an environment
25
+ * variable name.
26
+ *
27
+ * Therefore, `WhyRenderConfig` cannot determine where the value came from or
28
+ * verify that it originated from the intended environment variable.
29
+ *
30
+ * Any value explicitly equal to `true` or a string equivalent to `"true"`
31
+ * will enable Why Render, regardless of its source.
32
+ *
33
+ * Developers should ensure they pass the intended Why Render configuration
34
+ * value, such as `process.env.USE_WHY_RENDER`.
35
+ *
36
+ * @example Enable with a boolean
37
+ * ```ts
38
+ * WhyRenderConfig.setup(true);
39
+ *
40
+ * if (WhyRenderConfig.isEnabled()) {
41
+ * // Why Render is enabled.
42
+ * }
43
+ * ```
44
+ *
45
+ * @example Node.js, Bun, or Express
46
+ * ```ts
47
+ * WhyRenderConfig.setup(
48
+ * process.env.USE_WHY_RENDER
49
+ * );
50
+ * ```
51
+ *
52
+ * ```env
53
+ * USE_WHY_RENDER=true
54
+ * ```
55
+ *
56
+ * @example Vite
57
+ * ```ts
58
+ * WhyRenderConfig.setup(
59
+ * import.meta.env.VITE_USE_WHY_RENDER
60
+ * );
61
+ * ```
62
+ *
63
+ * ```env
64
+ * VITE_USE_WHY_RENDER=true
65
+ * ```
66
+ *
67
+ * @example Custom environment object
68
+ * ```ts
69
+ * WhyRenderConfig.setup(
70
+ * env.USE_WHY_RENDER
71
+ * );
72
+ * ```
73
+ *
74
+ * @example Disabled by default
75
+ * ```ts
76
+ * WhyRenderConfig.setup(undefined);
77
+ *
78
+ * WhyRenderConfig.isEnabled();
79
+ * // false
80
+ * ```
81
+ *
82
+ * @example Important: unrelated values can enable Why Render
83
+ *
84
+ * The following is valid, but may unintentionally enable Why Render:
85
+ *
86
+ * ```ts
87
+ * WhyRenderConfig.setup(
88
+ * process.env.DEBUG
89
+ * );
90
+ * ```
91
+ *
92
+ * If `DEBUG` contains `"true"`, Why Render will be enabled because this class
93
+ * receives only the resolved value and cannot determine that it came from
94
+ * `DEBUG` instead of `USE_WHY_RENDER`.
95
+ */
96
+ export class WhyRenderConfig {
97
+ /**
98
+ * The current global Why Render configuration instance.
99
+ *
100
+ * This instance is created by {@link WhyRenderConfig.setup}.
101
+ */
102
+ static instance;
103
+ /**
104
+ * Whether Why Render is enabled.
105
+ *
106
+ * @defaultValue false
107
+ */
108
+ enabled;
109
+ /**
110
+ * Creates a Why Render configuration instance.
111
+ *
112
+ * The constructor is private because the global configuration should be
113
+ * created through {@link WhyRenderConfig.setup}.
114
+ *
115
+ * @param value - The resolved value used to determine whether Why Render
116
+ * is enabled.
117
+ */
118
+ constructor(value) {
119
+ this.enabled = WhyRenderConfig.parseBoolean(value);
120
+ }
121
+ /**
122
+ * Configures Why Render.
123
+ *
124
+ * Calling this method creates the global configuration instance.
125
+ *
126
+ * If Why Render has already been configured, calling this method again
127
+ * replaces the existing configuration with a new one.
128
+ *
129
+ * Why Render is enabled only when the provided value is:
130
+ *
131
+ * - The boolean `true`
132
+ * - A string equivalent to `"true"` after trimming whitespace and
133
+ * ignoring letter case
134
+ *
135
+ * All other values disable Why Render.
136
+ *
137
+ * ## Important
138
+ *
139
+ * This method accepts a resolved value, not an environment variable name.
140
+ *
141
+ * As a result, this class cannot verify that the value came from
142
+ * `USE_WHY_RENDER`.
143
+ *
144
+ * For example, the following code will enable Why Render if `DEBUG`
145
+ * contains `"true"`:
146
+ *
147
+ * ```ts
148
+ * WhyRenderConfig.setup(process.env.DEBUG);
149
+ * ```
150
+ *
151
+ * Developers should pass the intended Why Render environment variable:
152
+ *
153
+ * ```ts
154
+ * WhyRenderConfig.setup(process.env.USE_WHY_RENDER);
155
+ * ```
156
+ *
157
+ * @param value - The resolved configuration value used to enable or
158
+ * disable Why Render.
159
+ *
160
+ * @returns The newly created global {@link WhyRenderConfig} instance.
161
+ *
162
+ * @example Node.js
163
+ * ```ts
164
+ * WhyRenderConfig.setup(
165
+ * process.env.USE_WHY_RENDER
166
+ * );
167
+ * ```
168
+ *
169
+ * ```env
170
+ * USE_WHY_RENDER=true
171
+ * ```
172
+ *
173
+ * @example Bun
174
+ * ```ts
175
+ * WhyRenderConfig.setup(
176
+ * process.env.USE_WHY_RENDER
177
+ * );
178
+ * ```
179
+ *
180
+ * @example Express
181
+ * ```ts
182
+ * WhyRenderConfig.setup(
183
+ * process.env.USE_WHY_RENDER
184
+ * );
185
+ * ```
186
+ *
187
+ * @example Vite
188
+ * ```ts
189
+ * WhyRenderConfig.setup(
190
+ * import.meta.env.VITE_USE_WHY_RENDER
191
+ * );
192
+ * ```
193
+ *
194
+ * ```env
195
+ * VITE_USE_WHY_RENDER=true
196
+ * ```
197
+ *
198
+ * @example Enable directly
199
+ * ```ts
200
+ * WhyRenderConfig.setup(true);
201
+ * ```
202
+ *
203
+ * @example Disable directly
204
+ * ```ts
205
+ * WhyRenderConfig.setup(false);
206
+ * ```
207
+ *
208
+ * @example Missing environment variable
209
+ *
210
+ * Missing values safely disable Why Render.
211
+ *
212
+ * ```ts
213
+ * WhyRenderConfig.setup(
214
+ * process.env.USE_WHY_RENDER
215
+ * );
216
+ *
217
+ * // If USE_WHY_RENDER is undefined:
218
+ * WhyRenderConfig.isEnabled();
219
+ * // false
220
+ * ```
221
+ *
222
+ * @example Warning: unrelated environment variable
223
+ *
224
+ * ```ts
225
+ * WhyRenderConfig.setup(
226
+ * process.env.DEBUG
227
+ * );
228
+ * ```
229
+ *
230
+ * If `DEBUG` is `"true"`, Why Render will be enabled.
231
+ */
232
+ static setup(value) {
233
+ WhyRenderConfig.instance = new WhyRenderConfig(value);
234
+ return WhyRenderConfig.instance;
235
+ }
236
+ /**
237
+ * Returns whether Why Render has been configured.
238
+ *
239
+ * This method checks only whether {@link WhyRenderConfig.setup} has been
240
+ * called.
241
+ *
242
+ * It does not indicate whether Why Render is enabled.
243
+ *
244
+ * @returns `true` when Why Render has been configured; otherwise `false`.
245
+ *
246
+ * @example Not configured
247
+ * ```ts
248
+ * WhyRenderConfig.isConfigured();
249
+ * // false
250
+ * ```
251
+ *
252
+ * @example Configured and enabled
253
+ * ```ts
254
+ * WhyRenderConfig.setup(true);
255
+ *
256
+ * WhyRenderConfig.isConfigured();
257
+ * // true
258
+ * ```
259
+ *
260
+ * @example Configured but disabled
261
+ * ```ts
262
+ * WhyRenderConfig.setup(false);
263
+ *
264
+ * WhyRenderConfig.isConfigured();
265
+ * // true
266
+ *
267
+ * WhyRenderConfig.isEnabled();
268
+ * // false
269
+ * ```
270
+ *
271
+ * @example Configure only when necessary
272
+ * ```ts
273
+ * if (!WhyRenderConfig.isConfigured()) {
274
+ * WhyRenderConfig.setup(
275
+ * process.env.USE_WHY_RENDER
276
+ * );
277
+ * }
278
+ * ```
279
+ */
280
+ static isConfigured() {
281
+ return WhyRenderConfig.instance !== undefined;
282
+ }
283
+ /**
284
+ * Returns the current global Why Render configuration.
285
+ *
286
+ * @returns The current {@link WhyRenderConfig} instance.
287
+ *
288
+ * @throws {Error}
289
+ * Thrown when {@link WhyRenderConfig.setup} has not been called.
290
+ *
291
+ * @example
292
+ * ```ts
293
+ * WhyRenderConfig.setup(
294
+ * process.env.USE_WHY_RENDER
295
+ * );
296
+ *
297
+ * const config =
298
+ * WhyRenderConfig.getConfig();
299
+ *
300
+ * console.log(config.enabled);
301
+ * ```
302
+ *
303
+ * @example Error when not configured
304
+ * ```ts
305
+ * WhyRenderConfig.getConfig();
306
+ *
307
+ * // Throws an Error because setup() has not been called.
308
+ * ```
309
+ */
310
+ static getConfig() {
311
+ if (!WhyRenderConfig.instance) {
312
+ throw new Error([
313
+ "WhyRenderConfig has not been configured.",
314
+ "",
315
+ "Call WhyRenderConfig.setup() before accessing the configuration.",
316
+ "",
317
+ "Example:",
318
+ "WhyRenderConfig.setup(process.env.USE_WHY_RENDER);",
319
+ ].join("\n"));
320
+ }
321
+ return WhyRenderConfig.instance;
322
+ }
323
+ /**
324
+ * Returns whether Why Render is currently enabled.
325
+ *
326
+ * @returns `true` when Why Render is enabled; otherwise `false`.
327
+ *
328
+ * @throws {Error}
329
+ * Thrown when {@link WhyRenderConfig.setup} has not been called.
330
+ *
331
+ * @example
332
+ * ```ts
333
+ * WhyRenderConfig.setup(
334
+ * process.env.USE_WHY_RENDER
335
+ * );
336
+ *
337
+ * if (WhyRenderConfig.isEnabled()) {
338
+ * console.log("Why Render is enabled.");
339
+ * }
340
+ * ```
341
+ *
342
+ * @example Disabled
343
+ * ```ts
344
+ * WhyRenderConfig.setup(false);
345
+ *
346
+ * WhyRenderConfig.isEnabled();
347
+ * // false
348
+ * ```
349
+ */
350
+ static isEnabled() {
351
+ return WhyRenderConfig.getConfig().enabled;
352
+ }
353
+ /**
354
+ * Converts a supported configuration value into a boolean.
355
+ *
356
+ * The value must explicitly represent `true` to enable Why Render.
357
+ *
358
+ * Only the following values enable Why Render:
359
+ *
360
+ * - `true`
361
+ * - A string equivalent to `"true"` after trimming whitespace and
362
+ * ignoring letter case
363
+ *
364
+ * All other values return `false`.
365
+ *
366
+ * @param value - The configuration value to parse.
367
+ *
368
+ * @returns `true` only when the value explicitly enables Why Render;
369
+ * otherwise `false`.
370
+ *
371
+ * @example
372
+ * ```ts
373
+ * // All enable Why Render:
374
+ * true
375
+ * "true"
376
+ * "TRUE"
377
+ * " true "
378
+ * ```
379
+ *
380
+ * @example
381
+ * ```ts
382
+ * // All disable Why Render:
383
+ * false
384
+ * "false"
385
+ * "1"
386
+ * "yes"
387
+ * ""
388
+ * undefined
389
+ * ```
390
+ */
391
+ static parseBoolean(value) {
392
+ if (typeof value === "boolean") {
393
+ return value;
394
+ }
395
+ if (typeof value === "string") {
396
+ return value.trim().toLowerCase() === "true";
397
+ }
398
+ return false;
399
+ }
400
+ }
401
+ //# sourceMappingURL=Config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Config.js","sourceRoot":"","sources":["../../src/utils/Config.ts"],"names":[],"mappings":"AAQA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8FG;AACH,MAAM,OAAO,eAAe;IACxB;;;;OAIG;IACK,MAAM,CAAC,QAAQ,CAA8B;IAErD;;;;OAIG;IACa,OAAO,CAAU;IAEjC;;;;;;;;OAQG;IACH,YAAoB,KAA2B;QAC3C,IAAI,CAAC,OAAO,GAAG,eAAe,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;IACvD,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8GG;IACI,MAAM,CAAC,KAAK,CAAC,KAA2B;QAC3C,eAAe,CAAC,QAAQ,GAAG,IAAI,eAAe,CAAC,KAAK,CAAC,CAAC;QACtD,OAAO,eAAe,CAAC,QAAQ,CAAC;IACpC,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2CG;IACI,MAAM,CAAC,YAAY;QACtB,OAAO,eAAe,CAAC,QAAQ,KAAK,SAAS,CAAC;IAClD,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACI,MAAM,CAAC,SAAS;QACnB,IAAI,CAAC,eAAe,CAAC,QAAQ,EAAE,CAAC;YAC5B,MAAM,IAAI,KAAK,CACX;gBACI,0CAA0C;gBAC1C,EAAE;gBACF,kEAAkE;gBAClE,EAAE;gBACF,UAAU;gBACV,oDAAoD;aACvD,CAAC,IAAI,CAAC,IAAI,CAAC,CACf,CAAC;QACN,CAAC;QAED,OAAO,eAAe,CAAC,QAAQ,CAAC;IACpC,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACI,MAAM,CAAC,SAAS;QACnB,OAAO,eAAe,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC;IAC/C,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAqCG;IACK,MAAM,CAAC,YAAY,CAAC,KAA2B;QACnD,IAAI,OAAO,KAAK,KAAK,SAAS,EAAE,CAAC;YAC7B,OAAO,KAAK,CAAC;QACjB,CAAC;QAED,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC5B,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,KAAK,MAAM,CAAC;QACjD,CAAC;QAED,OAAO,KAAK,CAAC;IACjB,CAAC;CAIJ"}
@@ -0,0 +1,28 @@
1
+ import type { PropChangeType, LabelOptions } from "../global/index.t.js";
2
+ /**
3
+ * `console.log` `%c` style string used for the `[why-rerender]` header
4
+ * label.
5
+ */
6
+ export declare const LABEL_STYLE: string;
7
+ /** Number of characters each change-type label (e.g. `CHANGED`) is padded to. */
8
+ export declare const PAD_SIZE: number;
9
+ /**
10
+ * Builds the `%c`-formatted console log header for a render, e.g.
11
+ * `%c[why-rerender] <CarCard> render #3`.
12
+ *
13
+ * Must be logged together with `LABEL_STYLE` as the corresponding
14
+ * `%c` style argument.
15
+ *
16
+ * @param options - The component name and current render count.
17
+ * @returns A format string containing a single `%c` placeholder.
18
+ */
19
+ export declare const setHeader: ({ name, count }: LabelOptions) => string;
20
+ /**
21
+ * Returns the `%c` style string used to color a logged prop line
22
+ * according to its change type (changed / added / removed).
23
+ *
24
+ * @param type - The kind of prop change being logged.
25
+ * @returns A CSS style string for use as a `console.log` `%c` argument.
26
+ */
27
+ export declare const setBodyText: (type: PropChangeType) => string;
28
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/utils/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AASxE;;;EAGE;AACF,eAAO,MAAM,WAAW,EAAE,MAA8D,CAAA;AAGxF,iFAAiF;AACjF,eAAO,MAAM,QAAQ,EAAE,MAAU,CAAA;AAEjC;;;;;;;;;EASE;AACF,eAAO,MAAM,SAAS,GAAI,iBAAc,YAAY,KAAE,MAErD,CAAA;AAID;;;;;;EAME;AACF,eAAO,MAAM,WAAW,GAAI,MAAK,cAAc,WAE9C,CAAA"}
@@ -0,0 +1,38 @@
1
+ /** Console text color (hex) used for each type of prop change. */
2
+ const COLOR = {
3
+ changed: '#854F0B', // amber
4
+ added: '#0F6E56', // teal
5
+ removed: '#993C1D', // coral
6
+ };
7
+ /**
8
+ * `console.log` `%c` style string used for the `[why-rerender]` header
9
+ * label.
10
+ */
11
+ export const LABEL_STYLE = 'color:#534AB7;font-weight:600;font-family:monospace';
12
+ const PREFIX = '[why-rerender]';
13
+ /** Number of characters each change-type label (e.g. `CHANGED`) is padded to. */
14
+ export const PAD_SIZE = 8;
15
+ /**
16
+ * Builds the `%c`-formatted console log header for a render, e.g.
17
+ * `%c[why-rerender] <CarCard> render #3`.
18
+ *
19
+ * Must be logged together with `LABEL_STYLE` as the corresponding
20
+ * `%c` style argument.
21
+ *
22
+ * @param options - The component name and current render count.
23
+ * @returns A format string containing a single `%c` placeholder.
24
+ */
25
+ export const setHeader = ({ name, count }) => {
26
+ return `%c${PREFIX} <${name}> render #${count}`;
27
+ };
28
+ /**
29
+ * Returns the `%c` style string used to color a logged prop line
30
+ * according to its change type (changed / added / removed).
31
+ *
32
+ * @param type - The kind of prop change being logged.
33
+ * @returns A CSS style string for use as a `console.log` `%c` argument.
34
+ */
35
+ export const setBodyText = (type) => {
36
+ return `color:${COLOR[type]};font-weight:600;font-family:monospace`;
37
+ };
38
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/utils/utils.ts"],"names":[],"mappings":"AAEA,kEAAkE;AAClE,MAAM,KAAK,GAAmC;IAC5C,OAAO,EAAE,SAAS,EAAG,QAAQ;IAC7B,KAAK,EAAI,SAAS,EAAG,OAAO;IAC5B,OAAO,EAAE,SAAS,EAAG,QAAQ;CAC9B,CAAA;AAED;;;EAGE;AACF,MAAM,CAAC,MAAM,WAAW,GAAW,qDAAqD,CAAA;AACxF,MAAM,MAAM,GAAW,gBAAgB,CAAA;AAEvC,iFAAiF;AACjF,MAAM,CAAC,MAAM,QAAQ,GAAW,CAAC,CAAA;AAEjC;;;;;;;;;EASE;AACF,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,EAAC,IAAI,EAAE,KAAK,EAAc,EAAS,EAAE;IAC3D,OAAO,KAAK,MAAM,KAAK,IAAI,aAAa,KAAK,EAAE,CAAA;AACnD,CAAC,CAAA;AAID;;;;;;EAME;AACF,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,IAAmB,EAAE,EAAE;IAC/C,OAAO,SAAS,KAAK,CAAC,IAAI,CAAC,wCAAwC,CAAA;AACvE,CAAC,CAAA"}
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "why-render-react",
3
+ "version": "0.1.0",
4
+ "description": "A React hook that helps you understand why a component re-rendered.",
5
+ "types": "module",
6
+ "main": "dist/index.js",
7
+ "license":"MIT",
8
+ "exports":{
9
+ ".":{
10
+ "import": "./dist/index.js",
11
+ "require": "./dist/index.js"
12
+ }
13
+
14
+ },
15
+ "scripts": {
16
+ "build": "tsc",
17
+ "typecheck": "tsc --noEmit",
18
+ "lint": "eslint .",
19
+ "test": "vitest run",
20
+ "test:watch": "vitest",
21
+ "test:file": "vitest run",
22
+ "check": "npm run typecheck && npm run lint && npm run build",
23
+ "coverage": "vitest run --coverage",
24
+ "release": "npm run build && npm run test && npm publish --access public",
25
+ "pre:deploy": "npm run check && npm whoami && npm link",
26
+ "pre:test": "npm run check && npm link",
27
+ "local:test": "npm run reset:old && npm run pre:test"
28
+ },
29
+ "files": [
30
+ "dist",
31
+ "README.md",
32
+ "LICENSE"
33
+ ],
34
+
35
+ "engines": {
36
+ "node":">=20"
37
+ },
38
+
39
+ "keywords": [],
40
+ "type": "module",
41
+ "peerDependencies": {
42
+ "react": "^19.2.8",
43
+ "react-dom": "^19.2.8"
44
+ },
45
+ "devDependencies": {
46
+ "@testing-library/react": "^16.3.3",
47
+ "@types/node": "^26.4.0",
48
+ "@types/react": "^19.2.18",
49
+ "@vitejs/plugin-react": "^6.1.1",
50
+ "@vitest/coverage-v8": "^4.1.11",
51
+ "eslint": "^9.0.0",
52
+ "jsdom": "^30.0.1",
53
+ "typescript": "^6.0.0",
54
+ "typescript-eslint": "^8.69.0",
55
+ "vite": "^8.2.2",
56
+ "vitest": "^4.1.11"
57
+ }
58
+ }