rerender-lens 0.4.0 → 0.5.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/CHANGELOG.md +78 -0
- package/README.md +192 -362
- package/dist/{budget-COBu7jBU.d.cts → budget-BnJlYwrV.d.ts} +20 -10
- package/dist/{budget-LkNjGtRc.d.ts → budget-CRswn-nC.d.cts} +20 -10
- package/dist/cli.cjs +248 -31
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +248 -31
- package/dist/cli.js.map +1 -1
- package/dist/{devtools-CZebzpF6.d.ts → devtools-BYWat7nQ.d.ts} +7 -1
- package/dist/{devtools-BkCct3cJ.d.cts → devtools-DJi_6AfJ.d.cts} +7 -1
- package/dist/index.cjs +439 -41
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +131 -8
- package/dist/index.d.ts +131 -8
- package/dist/index.js +424 -42
- package/dist/index.js.map +1 -1
- package/dist/jest-setup.cjs +1673 -0
- package/dist/jest-setup.cjs.map +1 -0
- package/dist/jest-setup.d.cts +8 -0
- package/dist/jest-setup.d.ts +8 -0
- package/dist/jest-setup.js +1671 -0
- package/dist/jest-setup.js.map +1 -0
- package/dist/jest.cjs +1760 -0
- package/dist/jest.cjs.map +1 -0
- package/dist/jest.d.cts +23 -0
- package/dist/jest.d.ts +23 -0
- package/dist/jest.js +1751 -0
- package/dist/jest.js.map +1 -0
- package/dist/{notifiers-BjjGSHpp.d.ts → notifiers-DwKlvv4H.d.cts} +8 -4
- package/dist/{notifiers-BGQtWKfX.d.cts → notifiers-FNzKHs0a.d.ts} +8 -4
- package/dist/playwright.cjs +230 -28
- package/dist/playwright.cjs.map +1 -1
- package/dist/playwright.d.cts +4 -4
- package/dist/playwright.d.ts +4 -4
- package/dist/playwright.js +230 -28
- package/dist/playwright.js.map +1 -1
- package/dist/relay.cjs +10 -2
- package/dist/relay.cjs.map +1 -1
- package/dist/relay.js +10 -2
- package/dist/relay.js.map +1 -1
- package/dist/rerender-lens.iife.js +356 -54
- package/dist/runner-report-B8Sc3FZO.d.cts +46 -0
- package/dist/runner-report-CW8X5RwK.d.ts +46 -0
- package/dist/setup.cjs +112 -27
- package/dist/setup.cjs.map +1 -1
- package/dist/setup.js +112 -27
- package/dist/setup.js.map +1 -1
- package/dist/{types-BzUEVkxJ.d.cts → types-DW5-N2XH.d.cts} +55 -2
- package/dist/{types-BzUEVkxJ.d.ts → types-DW5-N2XH.d.ts} +55 -2
- package/dist/vite.d.cts +1 -1
- package/dist/vite.d.ts +1 -1
- package/dist/vitest-setup.cjs +337 -41
- package/dist/vitest-setup.cjs.map +1 -1
- package/dist/vitest-setup.d.cts +4 -4
- package/dist/vitest-setup.d.ts +4 -4
- package/dist/vitest-setup.js +337 -41
- package/dist/vitest-setup.js.map +1 -1
- package/dist/vitest.cjs +400 -77
- package/dist/vitest.cjs.map +1 -1
- package/dist/vitest.d.cts +21 -39
- package/dist/vitest.d.ts +21 -39
- package/dist/vitest.js +401 -78
- package/dist/vitest.js.map +1 -1
- package/package.json +40 -4
- package/panel/panel.css +1 -0
- package/panel/panel.js +499 -309
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { R as RenderReport, O as Options } from './types-DW5-N2XH.cjs';
|
|
2
|
+
import { C as Collector } from './notifiers-DwKlvv4H.cjs';
|
|
3
|
+
|
|
4
|
+
/** Environment variable carrying the path of the run's JSONL file from the reporter to the workers. */
|
|
5
|
+
declare const OUT_ENV = "RERENDER_LENS_OUT";
|
|
6
|
+
interface SetupOptions extends Omit<Options, 'notifier'> {
|
|
7
|
+
/** Also fail each test file that recorded avoidable re-renders (default false: the reporter decides). */
|
|
8
|
+
failFast?: boolean;
|
|
9
|
+
}
|
|
10
|
+
/** How the setup ties into the test runner (`{ afterAll }` from the runner's globals). */
|
|
11
|
+
interface SetupHooks {
|
|
12
|
+
afterAll?: (fn: () => void) => void;
|
|
13
|
+
/** Path of the current test file, for the reporter's output. */
|
|
14
|
+
testPath?: () => string | undefined;
|
|
15
|
+
}
|
|
16
|
+
interface LensSetup {
|
|
17
|
+
collector: Collector;
|
|
18
|
+
/** Append this file's reports to the shared output and clear the collector; registered with `afterAll` when given. */
|
|
19
|
+
flush(): void;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Call from a setup file. Creates the DevTools hook before react-dom loads, starts the library with
|
|
23
|
+
* a collector, and appends this file's reports to the shared output when it ends. The collector is
|
|
24
|
+
* returned for per-test assertions (`assertNoAvoidable`, `assertWithinBudget`).
|
|
25
|
+
*/
|
|
26
|
+
declare function setupRerenderLens(options?: SetupOptions, hooks?: SetupHooks): LensSetup;
|
|
27
|
+
interface ReporterOptions {
|
|
28
|
+
/** Path to a budget JSON (`{ "*": 0, "Row": 2 }`). When set, the run fails on violations. */
|
|
29
|
+
budget?: string;
|
|
30
|
+
/** Print at most this many fixes. Default 10. */
|
|
31
|
+
limit?: number;
|
|
32
|
+
/** Also write the collected reports here as a panel-compatible export (`Sessions > Import`). */
|
|
33
|
+
exportTo?: string;
|
|
34
|
+
/** Where the lines go. Default `process.stdout`. */
|
|
35
|
+
write?: (line: string) => void;
|
|
36
|
+
}
|
|
37
|
+
interface RunResult {
|
|
38
|
+
/** Every report of the run, from every worker. */
|
|
39
|
+
reports: RenderReport[];
|
|
40
|
+
/** Set when a budget file was given and exceeded; the run must fail. */
|
|
41
|
+
error: Error | null;
|
|
42
|
+
}
|
|
43
|
+
/** Reports from every worker of a run. */
|
|
44
|
+
declare function readRun(file: string): RenderReport[];
|
|
45
|
+
|
|
46
|
+
export { type LensSetup as L, OUT_ENV as O, type ReporterOptions as R, type SetupHooks as S, type RunResult as a, type SetupOptions as b, readRun as r, setupRerenderLens as s };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { R as RenderReport, O as Options } from './types-DW5-N2XH.js';
|
|
2
|
+
import { C as Collector } from './notifiers-FNzKHs0a.js';
|
|
3
|
+
|
|
4
|
+
/** Environment variable carrying the path of the run's JSONL file from the reporter to the workers. */
|
|
5
|
+
declare const OUT_ENV = "RERENDER_LENS_OUT";
|
|
6
|
+
interface SetupOptions extends Omit<Options, 'notifier'> {
|
|
7
|
+
/** Also fail each test file that recorded avoidable re-renders (default false: the reporter decides). */
|
|
8
|
+
failFast?: boolean;
|
|
9
|
+
}
|
|
10
|
+
/** How the setup ties into the test runner (`{ afterAll }` from the runner's globals). */
|
|
11
|
+
interface SetupHooks {
|
|
12
|
+
afterAll?: (fn: () => void) => void;
|
|
13
|
+
/** Path of the current test file, for the reporter's output. */
|
|
14
|
+
testPath?: () => string | undefined;
|
|
15
|
+
}
|
|
16
|
+
interface LensSetup {
|
|
17
|
+
collector: Collector;
|
|
18
|
+
/** Append this file's reports to the shared output and clear the collector; registered with `afterAll` when given. */
|
|
19
|
+
flush(): void;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Call from a setup file. Creates the DevTools hook before react-dom loads, starts the library with
|
|
23
|
+
* a collector, and appends this file's reports to the shared output when it ends. The collector is
|
|
24
|
+
* returned for per-test assertions (`assertNoAvoidable`, `assertWithinBudget`).
|
|
25
|
+
*/
|
|
26
|
+
declare function setupRerenderLens(options?: SetupOptions, hooks?: SetupHooks): LensSetup;
|
|
27
|
+
interface ReporterOptions {
|
|
28
|
+
/** Path to a budget JSON (`{ "*": 0, "Row": 2 }`). When set, the run fails on violations. */
|
|
29
|
+
budget?: string;
|
|
30
|
+
/** Print at most this many fixes. Default 10. */
|
|
31
|
+
limit?: number;
|
|
32
|
+
/** Also write the collected reports here as a panel-compatible export (`Sessions > Import`). */
|
|
33
|
+
exportTo?: string;
|
|
34
|
+
/** Where the lines go. Default `process.stdout`. */
|
|
35
|
+
write?: (line: string) => void;
|
|
36
|
+
}
|
|
37
|
+
interface RunResult {
|
|
38
|
+
/** Every report of the run, from every worker. */
|
|
39
|
+
reports: RenderReport[];
|
|
40
|
+
/** Set when a budget file was given and exceeded; the run must fail. */
|
|
41
|
+
error: Error | null;
|
|
42
|
+
}
|
|
43
|
+
/** Reports from every worker of a run. */
|
|
44
|
+
declare function readRun(file: string): RenderReport[];
|
|
45
|
+
|
|
46
|
+
export { type LensSetup as L, OUT_ENV as O, type ReporterOptions as R, type SetupHooks as S, type RunResult as a, type SetupOptions as b, readRun as r, setupRerenderLens as s };
|
package/dist/setup.cjs
CHANGED
|
@@ -161,30 +161,30 @@ function diffRecords(prev, next, basePath = "") {
|
|
|
161
161
|
if (Object.is(a, b)) continue;
|
|
162
162
|
const kind = classify(a, b);
|
|
163
163
|
if (kind === "different") {
|
|
164
|
-
changes.push({ path: diffBudgetExhausted() ? path : firstDifferentPath(a, b, path), kind, prev: a, next: b });
|
|
164
|
+
changes.push({ path: diffBudgetExhausted() ? path : firstDifferentPath(a, b, path) ?? path, kind, prev: a, next: b });
|
|
165
165
|
} else {
|
|
166
166
|
changes.push({ path, kind, prev: a, next: b });
|
|
167
167
|
}
|
|
168
168
|
}
|
|
169
169
|
return changes;
|
|
170
170
|
}
|
|
171
|
-
function firstDifferentPath(a, b, path, depth = 0) {
|
|
172
|
-
if (depth > 8) return path;
|
|
171
|
+
function firstDifferentPath(a, b, path = "", depth = 0) {
|
|
172
|
+
if (depth > 8) return path || "(value)";
|
|
173
173
|
if (Array.isArray(a) && Array.isArray(b)) {
|
|
174
|
-
if (a.length !== b.length) return `${path}.length
|
|
174
|
+
if (a.length !== b.length) return path ? `${path}.length` : "length";
|
|
175
175
|
for (let i = 0; i < a.length; i++) {
|
|
176
176
|
if (!deepEqual(a[i], b[i])) return firstDifferentPath(a[i], b[i], joinPath(path, i), depth + 1);
|
|
177
177
|
}
|
|
178
|
-
return
|
|
178
|
+
return null;
|
|
179
179
|
}
|
|
180
180
|
if (isPlainObject(a) && isPlainObject(b)) {
|
|
181
181
|
const keys = /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)]);
|
|
182
182
|
for (const k of keys) {
|
|
183
183
|
if (!deepEqual(a[k], b[k])) return firstDifferentPath(a[k], b[k], joinPath(path, k), depth + 1);
|
|
184
184
|
}
|
|
185
|
-
return
|
|
185
|
+
return null;
|
|
186
186
|
}
|
|
187
|
-
return path;
|
|
187
|
+
return deepEqual(a, b) ? null : path || "(value)";
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
// src/report.ts
|
|
@@ -202,10 +202,12 @@ function storeAdvice(custom) {
|
|
|
202
202
|
}
|
|
203
203
|
var isGenuine = (c) => c.kind === "different" || c.kind === "added" || c.kind === "removed";
|
|
204
204
|
var isChildren = (change) => change.path === "children";
|
|
205
|
+
var isRefObject = (change) => change.path === "ref" && change.kind === "deep-equal";
|
|
205
206
|
function fixFor(change) {
|
|
206
207
|
if (isChildren(change) && (change.kind === "element" || change.kind === "deep-equal")) {
|
|
207
208
|
return `lift the children out of the parent's render: memoize them with useMemo, hoist static elements to module scope, or render them from a component that does not re-render`;
|
|
208
209
|
}
|
|
210
|
+
if (isRefObject(change)) return `create the ref once with useRef (or createRef outside the render) and pass the same object every time`;
|
|
209
211
|
switch (change.kind) {
|
|
210
212
|
case "function":
|
|
211
213
|
return `wrap it in useCallback (or hoist it out of the parent's render)`;
|
|
@@ -221,6 +223,7 @@ function describe(change) {
|
|
|
221
223
|
if (isChildren(change) && (change.kind === "element" || change.kind === "deep-equal")) {
|
|
222
224
|
return `children are new React elements with the same types and props on every render of the parent`;
|
|
223
225
|
}
|
|
226
|
+
if (isRefObject(change)) return `prop "ref" is a new ref object on every render (React re-attaches it each time)`;
|
|
224
227
|
switch (change.kind) {
|
|
225
228
|
case "deep-equal":
|
|
226
229
|
return `prop "${change.path}" is a new reference but deep-equal to the previous value`;
|
|
@@ -248,11 +251,21 @@ function buildReport(input) {
|
|
|
248
251
|
if (causes.length === 0) trigger = "parent";
|
|
249
252
|
else if (causes.length === 1) trigger = causes[0];
|
|
250
253
|
else trigger = "mixed";
|
|
251
|
-
const
|
|
254
|
+
const nothingChanged = input.propChanges.length === 0 && stateChanges.length === 0 && hookChanges.length === 0;
|
|
255
|
+
const cacheHit = input.compiled === true && trigger === "parent" && input.propChanges.length === 0;
|
|
256
|
+
const revealed = input.revealed === true && trigger === "parent";
|
|
257
|
+
const avoidable = trigger === "parent" && !cacheHit && !revealed;
|
|
252
258
|
const reasons = [];
|
|
253
|
-
|
|
259
|
+
const memoFix = input.classComponent ? `Make "${input.component}" extend PureComponent (or implement shouldComponentUpdate)` : `Wrap "${input.component}" in React.memo (or extend PureComponent)`;
|
|
260
|
+
if (cacheHit) {
|
|
261
|
+
reasons.push(`compiled by React Compiler: its output is memoized and the inputs did not change, so this render is cheap; nothing to fix.`);
|
|
262
|
+
} else if (revealed) {
|
|
263
|
+
reasons.push(
|
|
264
|
+
`re-rendered because the Suspense boundary above it switched from its fallback to content: React re-renders the content it kept hidden while the fallback was shown. A one-off; nothing to fix.`
|
|
265
|
+
);
|
|
266
|
+
} else if (avoidable && nothingChanged) {
|
|
254
267
|
const who = input.parent ? `<${input.parent.name}> re-rendered (${describeTrigger(input.parent.trigger)})` : "its parent re-rendered";
|
|
255
|
-
reasons.push(`re-rendered with identical props because ${who}.
|
|
268
|
+
reasons.push(`re-rendered with identical props because ${who}. ${memoFix}.`);
|
|
256
269
|
} else if (avoidable && input.parent) {
|
|
257
270
|
reasons.push(`caused by <${input.parent.name}> re-rendering (${describeTrigger(input.parent.trigger)}).`);
|
|
258
271
|
}
|
|
@@ -261,9 +274,9 @@ function buildReport(input) {
|
|
|
261
274
|
reasons.push(fix ? `${describe(c)}: ${fix}.` : `${describe(c)}.`);
|
|
262
275
|
}
|
|
263
276
|
const memoized = input.memoized !== false;
|
|
264
|
-
if (avoidable && !memoized && input.propChanges.length > 0) {
|
|
277
|
+
if (avoidable && !memoized && !input.compiled && input.propChanges.length > 0) {
|
|
265
278
|
reasons.push(
|
|
266
|
-
`"${input.component}" is not memoized, so fixing the props alone will not stop this re-render: also wrap it in React.memo (or extend PureComponent).`
|
|
279
|
+
`"${input.component}" is not memoized, so fixing the props alone will not stop this re-render: also ${input.classComponent ? "extend PureComponent (or implement shouldComponentUpdate)" : "wrap it in React.memo (or extend PureComponent)"}.`
|
|
267
280
|
);
|
|
268
281
|
}
|
|
269
282
|
for (const c of stateChanges) {
|
|
@@ -275,6 +288,8 @@ function buildReport(input) {
|
|
|
275
288
|
const where = c.provider && c.provider.component ? ` (provided by <${c.provider.component}>)` : "";
|
|
276
289
|
const keys = c.changedKeys && typeof c.totalKeys === "number" && c.changedKeys.length > 0 && c.changedKeys.length < c.totalKeys ? `: only ${c.changedKeys.map((k) => `"${k}"`).join(", ")} of ${c.totalKeys} keys changed, yet every consumer re-renders. Split the context or memoize the slices consumers read` : "";
|
|
277
290
|
reasons.push(`${c.path} changed${where}${keys}.`);
|
|
291
|
+
} else if (c.hook === "useDeferredValue" && isGenuine(c)) {
|
|
292
|
+
reasons.push(`${hookLabel(c)} caught up with its latest value: this is the deferred second render React schedules after the urgent one; nothing to fix.`);
|
|
278
293
|
} else if (isGenuine(c)) reasons.push(`${hookLabel(c)} changed.`);
|
|
279
294
|
else if (isStateHook(c))
|
|
280
295
|
reasons.push(`${hookLabel(c)} was set to a value deep-equal to the current one (new reference, same contents): reuse the existing object or bail out before calling the setter.`);
|
|
@@ -305,6 +320,7 @@ function buildReport(input) {
|
|
|
305
320
|
reasons,
|
|
306
321
|
time: now()
|
|
307
322
|
};
|
|
323
|
+
if (input.compiled) report.compiled = true;
|
|
308
324
|
if (input.selfDuration !== void 0) report.selfDuration = input.selfDuration;
|
|
309
325
|
if (input.treeDuration !== void 0) report.treeDuration = input.treeDuration;
|
|
310
326
|
if (input.commitPriority) report.commitPriority = input.commitPriority;
|
|
@@ -342,10 +358,10 @@ var KIND_LABEL = {
|
|
|
342
358
|
};
|
|
343
359
|
function summarize(report) {
|
|
344
360
|
const counts2 = /* @__PURE__ */ new Map();
|
|
345
|
-
for (const c of [...report.propChanges, ...report.stateChanges, ...report.hookChanges]) {
|
|
361
|
+
for (const c of [...report.propChanges || [], ...report.stateChanges || [], ...report.hookChanges || []]) {
|
|
346
362
|
counts2.set(c.kind, (counts2.get(c.kind) ?? 0) + 1);
|
|
347
363
|
}
|
|
348
|
-
const parts = [...counts2].map(([kind, n]) => `${n} ${KIND_LABEL[kind]}`);
|
|
364
|
+
const parts = [...counts2].map(([kind, n]) => `${n} ${KIND_LABEL[kind] || kind}`);
|
|
349
365
|
if (parts.length === 0) parts.push("no changes");
|
|
350
366
|
return parts.join(", ");
|
|
351
367
|
}
|
|
@@ -408,6 +424,27 @@ function dispatch(report, override) {
|
|
|
408
424
|
}
|
|
409
425
|
|
|
410
426
|
// src/hookNames.ts
|
|
427
|
+
var NODE_COUNT = {
|
|
428
|
+
useState: 1,
|
|
429
|
+
useReducer: 1,
|
|
430
|
+
useRef: 1,
|
|
431
|
+
useMemo: 1,
|
|
432
|
+
useCallback: 1,
|
|
433
|
+
useEffect: 1,
|
|
434
|
+
useLayoutEffect: 1,
|
|
435
|
+
useInsertionEffect: 1,
|
|
436
|
+
useImperativeHandle: 1,
|
|
437
|
+
useSyncExternalStore: 2,
|
|
438
|
+
useTransition: 2,
|
|
439
|
+
useDeferredValue: 1,
|
|
440
|
+
useId: 1,
|
|
441
|
+
useOptimistic: 1,
|
|
442
|
+
useActionState: 3,
|
|
443
|
+
useFormState: 3,
|
|
444
|
+
useEffectEvent: 1,
|
|
445
|
+
useCacheRefresh: 1
|
|
446
|
+
};
|
|
447
|
+
var BUILTIN_HOOKS = /* @__PURE__ */ new Set([...Object.keys(NODE_COUNT), "useContext", "useDebugValue", "use"]);
|
|
411
448
|
var noop = () => {
|
|
412
449
|
};
|
|
413
450
|
function customHooksFromStack(stack, componentName, marker) {
|
|
@@ -422,7 +459,7 @@ function customHooksFromStack(stack, componentName, marker) {
|
|
|
422
459
|
continue;
|
|
423
460
|
}
|
|
424
461
|
if (name === componentName || name.endsWith("." + componentName)) break;
|
|
425
|
-
if (/^use[A-Z0-9_]/.test(name)) names.push(name);
|
|
462
|
+
if (/^use[A-Z0-9_]/.test(name) && !BUILTIN_HOOKS.has(name)) names.push(name);
|
|
426
463
|
if (names.length > 8) break;
|
|
427
464
|
}
|
|
428
465
|
return names;
|
|
@@ -722,6 +759,11 @@ function isMemoizedFiber(fiber) {
|
|
|
722
759
|
}
|
|
723
760
|
return false;
|
|
724
761
|
}
|
|
762
|
+
function isCompiledFiber(fiber) {
|
|
763
|
+
if (fiber.tag === ClassComponent) return false;
|
|
764
|
+
const cache2 = fiber.updateQueue?.memoCache;
|
|
765
|
+
return !!cache2 && Array.isArray(cache2.data);
|
|
766
|
+
}
|
|
725
767
|
function durationsOf(fiber) {
|
|
726
768
|
if (typeof fiber.actualDuration !== "number") return null;
|
|
727
769
|
const tree = fiber.actualDuration;
|
|
@@ -900,7 +942,8 @@ function diffHooks(fiber, alt) {
|
|
|
900
942
|
const labels = hookLabelsFor(fiber, b);
|
|
901
943
|
let i = 0;
|
|
902
944
|
while (a && b) {
|
|
903
|
-
|
|
945
|
+
const deferred = labels?.[i] === "useDeferredValue";
|
|
946
|
+
if ((deferred || isStateNode(b) || isStoreNode(b)) && !Object.is(a.memoizedState, b.memoizedState)) {
|
|
904
947
|
const hook = labels?.[i] ?? hookLabel2(b);
|
|
905
948
|
out.push({
|
|
906
949
|
path: `${hook}#${i}`,
|
|
@@ -934,6 +977,10 @@ function diffContexts(fiber, alt) {
|
|
|
934
977
|
let a = alt.dependencies?.firstContext ?? null;
|
|
935
978
|
let b = fiber.dependencies?.firstContext ?? null;
|
|
936
979
|
let i = 0;
|
|
980
|
+
if (b && !("memoizedValue" in b)) {
|
|
981
|
+
warnOnce("contextValues", "this React does not record context values on the fiber (React < 18): context changes are reported as plain parent re-renders.");
|
|
982
|
+
return out;
|
|
983
|
+
}
|
|
937
984
|
while (a && b) {
|
|
938
985
|
if (a.context === b.context && !Object.is(a.memoizedValue, b.memoizedValue)) {
|
|
939
986
|
const name = b.context.displayName ?? "Context";
|
|
@@ -966,10 +1013,26 @@ function diffContexts(fiber, alt) {
|
|
|
966
1013
|
return out;
|
|
967
1014
|
}
|
|
968
1015
|
var isGenuine2 = (c) => c.kind === "different" || c.kind === "added" || c.kind === "removed";
|
|
1016
|
+
var isRefObject2 = (v) => {
|
|
1017
|
+
if (typeof v !== "object" || v === null) return false;
|
|
1018
|
+
const keys = Object.keys(v);
|
|
1019
|
+
return keys.length === 1 && keys[0] === "current";
|
|
1020
|
+
};
|
|
1021
|
+
function diffProps(prev, next) {
|
|
1022
|
+
if (prev === next) return [];
|
|
1023
|
+
const pr = prev.ref;
|
|
1024
|
+
const nr = next.ref;
|
|
1025
|
+
if (pr !== nr && isRefObject2(pr) && isRefObject2(nr)) {
|
|
1026
|
+
const { ref: _p, ...restPrev } = prev;
|
|
1027
|
+
const { ref: _n, ...restNext } = next;
|
|
1028
|
+
return [{ path: "ref", kind: "deep-equal", prev: pr, next: nr }, ...diffRecords(restPrev, restNext)];
|
|
1029
|
+
}
|
|
1030
|
+
return diffRecords(prev, next);
|
|
1031
|
+
}
|
|
969
1032
|
function analyze(fiber, alt, trackHooks) {
|
|
970
1033
|
const prevProps = alt.memoizedProps ?? {};
|
|
971
1034
|
const nextProps = fiber.memoizedProps ?? {};
|
|
972
|
-
const propChanges =
|
|
1035
|
+
const propChanges = diffProps(prevProps, nextProps);
|
|
973
1036
|
let stateChanges = [];
|
|
974
1037
|
let hookChanges = [];
|
|
975
1038
|
if (fiber.tag === ClassComponent) {
|
|
@@ -982,7 +1045,7 @@ function analyze(fiber, alt, trackHooks) {
|
|
|
982
1045
|
} else if (trackHooks) {
|
|
983
1046
|
hookChanges = [...diffHooks(fiber, alt), ...diffContexts(fiber, alt)];
|
|
984
1047
|
}
|
|
985
|
-
const isStateHook = (h) => h.hook !== "useContext" && h.hook !== "useSyncExternalStore";
|
|
1048
|
+
const isStateHook = (h) => h.hook !== "useContext" && h.hook !== "useSyncExternalStore" && h.hook !== "useDeferredValue";
|
|
986
1049
|
const causes = [];
|
|
987
1050
|
if (propChanges.some(isGenuine2)) causes.push("props");
|
|
988
1051
|
if (stateChanges.some(isGenuine2) || hookChanges.some((h) => isStateHook(h) && isGenuine2(h))) causes.push("state");
|
|
@@ -1022,6 +1085,24 @@ var lastCommit = null;
|
|
|
1022
1085
|
var EFFECT_LOOP_WINDOW_MS = 50;
|
|
1023
1086
|
var MAX_REPORTS_PER_COMMIT = 200;
|
|
1024
1087
|
var COMMIT_TIME_BUDGET_MS = 25;
|
|
1088
|
+
function isDeferredCatchUp(fiber) {
|
|
1089
|
+
const types = fiber._debugHookTypes;
|
|
1090
|
+
if (!types || !types.includes("useDeferredValue") || !fiber.alternate) return false;
|
|
1091
|
+
const first = fiber.memoizedState;
|
|
1092
|
+
if (!isHookList(first)) return false;
|
|
1093
|
+
const labels = hookLabelsFor(fiber, first);
|
|
1094
|
+
if (!labels) return false;
|
|
1095
|
+
let a = fiber.alternate.memoizedState;
|
|
1096
|
+
let b = first;
|
|
1097
|
+
for (let i = 0; a && b; a = a.next, b = b.next, i++) {
|
|
1098
|
+
if (labels[i] === "useDeferredValue" && !Object.is(a.memoizedState, b.memoizedState)) return true;
|
|
1099
|
+
}
|
|
1100
|
+
return false;
|
|
1101
|
+
}
|
|
1102
|
+
function isUnder(fiber, boundaries) {
|
|
1103
|
+
for (let f = fiber.return; f && f.tag !== HostRoot; f = f.return) if (f.tag === SuspenseComponent && boundaries.includes(f)) return true;
|
|
1104
|
+
return false;
|
|
1105
|
+
}
|
|
1025
1106
|
function onCommit(root, commitPriority) {
|
|
1026
1107
|
const s = getState();
|
|
1027
1108
|
if (!s.enabled) return;
|
|
@@ -1031,7 +1112,7 @@ function onCommit(root, commitPriority) {
|
|
|
1031
1112
|
const rendered = [];
|
|
1032
1113
|
const renderedNames = /* @__PURE__ */ new Set();
|
|
1033
1114
|
let hot = false;
|
|
1034
|
-
let
|
|
1115
|
+
let resolvedBoundaries = null;
|
|
1035
1116
|
const stack = [root.current];
|
|
1036
1117
|
while (stack.length) {
|
|
1037
1118
|
const fiber = stack.pop();
|
|
@@ -1041,7 +1122,7 @@ function onCommit(root, commitPriority) {
|
|
|
1041
1122
|
renderedNames.add(fiberName(fiber));
|
|
1042
1123
|
if (shouldTrack(fiberType(fiber), o)) rendered.push(fiber);
|
|
1043
1124
|
}
|
|
1044
|
-
if (fiber.tag === SuspenseComponent && alt && alt.memoizedState !== null && fiber.memoizedState === null)
|
|
1125
|
+
if (fiber.tag === SuspenseComponent && alt && alt.memoizedState !== null && fiber.memoizedState === null) (resolvedBoundaries ?? (resolvedBoundaries = [])).push(fiber);
|
|
1045
1126
|
if (!alt || fiber.child !== alt.child) {
|
|
1046
1127
|
for (let c = fiber.child; c; c = c.sibling) stack.push(c);
|
|
1047
1128
|
}
|
|
@@ -1053,10 +1134,10 @@ function onCommit(root, commitPriority) {
|
|
|
1053
1134
|
let commitCause;
|
|
1054
1135
|
let afterCommit;
|
|
1055
1136
|
const inputDriven = commitPriority === "immediate" || commitPriority === "user-blocking";
|
|
1056
|
-
if (!inputDriven && lastCommit && at - lastCommit.at < EFFECT_LOOP_WINDOW_MS && updaters.some((u) => lastCommit.rendered.has(u))) {
|
|
1137
|
+
if (!inputDriven && lastCommit && at - lastCommit.at < EFFECT_LOOP_WINDOW_MS && updaters.some((u) => lastCommit.rendered.has(u)) && !rendered.some(isDeferredCatchUp)) {
|
|
1057
1138
|
commitCause = "effect-after-commit";
|
|
1058
1139
|
afterCommit = lastCommit.id;
|
|
1059
|
-
} else if (
|
|
1140
|
+
} else if (resolvedBoundaries) commitCause = "suspense-resolved";
|
|
1060
1141
|
if (rendered.length === 0) {
|
|
1061
1142
|
lastCommit = { id: s.nextCommitId, at, rendered: renderedNames };
|
|
1062
1143
|
return;
|
|
@@ -1108,6 +1189,9 @@ function onCommit(root, commitPriority) {
|
|
|
1108
1189
|
owner: ownerName(fiber),
|
|
1109
1190
|
path: componentPath(fiber),
|
|
1110
1191
|
memoized: isMemoizedFiber(fiber),
|
|
1192
|
+
compiled: isCompiledFiber(fiber),
|
|
1193
|
+
classComponent: fiber.tag === ClassComponent,
|
|
1194
|
+
revealed: resolvedBoundaries !== null && a.trigger === "parent" && isUnder(fiber, resolvedBoundaries),
|
|
1111
1195
|
selfDuration: durations ? durations.self : void 0,
|
|
1112
1196
|
treeDuration: durations ? durations.tree : void 0,
|
|
1113
1197
|
commitId,
|
|
@@ -1290,7 +1374,7 @@ function flash(target, duration = 500) {
|
|
|
1290
1374
|
}
|
|
1291
1375
|
|
|
1292
1376
|
// src/version.ts
|
|
1293
|
-
var VERSION = "0.
|
|
1377
|
+
var VERSION = "0.5.0" ;
|
|
1294
1378
|
|
|
1295
1379
|
// src/devtools.ts
|
|
1296
1380
|
var DEVTOOLS_MARKER = "__rerenderLens";
|
|
@@ -1623,15 +1707,16 @@ function createDevtoolsNotifier(options = {}) {
|
|
|
1623
1707
|
}
|
|
1624
1708
|
|
|
1625
1709
|
// src/setup.ts
|
|
1626
|
-
var
|
|
1710
|
+
var read = (get) => {
|
|
1627
1711
|
try {
|
|
1628
|
-
return
|
|
1712
|
+
return get();
|
|
1629
1713
|
} catch {
|
|
1630
1714
|
return void 0;
|
|
1631
1715
|
}
|
|
1632
1716
|
};
|
|
1633
|
-
|
|
1634
|
-
|
|
1717
|
+
var production = read(() => process.env.NODE_ENV) === "production";
|
|
1718
|
+
if (!production && typeof window !== "undefined" && !window.__RERENDER_LENS_DEVTOOLS__) {
|
|
1719
|
+
const relay = read(() => process.env.RERENDER_LENS_RELAY) || read(() => process.env.NEXT_PUBLIC_RERENDER_LENS_RELAY);
|
|
1635
1720
|
init({ trackAllMemoized: true, notifier: createDevtoolsNotifier(relay ? { relay } : {}) });
|
|
1636
1721
|
}
|
|
1637
1722
|
//# sourceMappingURL=setup.cjs.map
|