rerender-lens 0.4.0 → 0.7.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 +127 -0
- package/README.md +208 -359
- package/dist/{budget-LkNjGtRc.d.ts → budget-CA1MS7lB.d.cts} +20 -10
- package/dist/{budget-COBu7jBU.d.cts → budget-ok1VDJ4t.d.ts} +20 -10
- package/dist/cli.cjs +322 -53
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +322 -53
- package/dist/cli.js.map +1 -1
- package/dist/{devtools-BkCct3cJ.d.cts → devtools-Cl5n_52v.d.ts} +17 -2
- package/dist/{devtools-CZebzpF6.d.ts → devtools-UwQx2VYS.d.cts} +17 -2
- package/dist/index.cjs +537 -49
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +137 -8
- package/dist/index.d.ts +137 -8
- package/dist/index.js +521 -50
- package/dist/index.js.map +1 -1
- package/dist/jest-setup.cjs +1690 -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 +1688 -0
- package/dist/jest-setup.js.map +1 -0
- package/dist/jest.cjs +1777 -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 +1768 -0
- package/dist/jest.js.map +1 -0
- package/dist/{notifiers-BjjGSHpp.d.ts → notifiers-B5poAEvE.d.cts} +8 -4
- package/dist/{notifiers-BGQtWKfX.d.cts → notifiers-CqQW0Eum.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 +71 -19
- package/dist/relay.cjs.map +1 -1
- package/dist/relay.d.cts +25 -3
- package/dist/relay.d.ts +25 -3
- package/dist/relay.js +71 -19
- package/dist/relay.js.map +1 -1
- package/dist/rerender-lens.iife.js +457 -62
- package/dist/runner-report-Bvqoe89M.d.ts +46 -0
- package/dist/runner-report-CvpDg2zt.d.cts +46 -0
- package/dist/setup.cjs +209 -35
- package/dist/setup.cjs.map +1 -1
- package/dist/setup.js +209 -35
- package/dist/setup.js.map +1 -1
- package/dist/{types-BzUEVkxJ.d.cts → types-DwNN5cWe.d.cts} +82 -2
- package/dist/{types-BzUEVkxJ.d.ts → types-DwNN5cWe.d.ts} +82 -2
- package/dist/vite.d.cts +1 -1
- package/dist/vite.d.ts +1 -1
- package/dist/vitest-setup.cjs +357 -44
- 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 +357 -44
- package/dist/vitest-setup.js.map +1 -1
- package/dist/vitest.cjs +420 -80
- 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 +421 -81
- package/dist/vitest.js.map +1 -1
- package/package.json +41 -4
- package/panel/panel.css +49 -0
- package/panel/panel.js +871 -353
package/dist/setup.js
CHANGED
|
@@ -159,30 +159,30 @@ function diffRecords(prev, next, basePath = "") {
|
|
|
159
159
|
if (Object.is(a, b)) continue;
|
|
160
160
|
const kind = classify(a, b);
|
|
161
161
|
if (kind === "different") {
|
|
162
|
-
changes.push({ path: diffBudgetExhausted() ? path : firstDifferentPath(a, b, path), kind, prev: a, next: b });
|
|
162
|
+
changes.push({ path: diffBudgetExhausted() ? path : firstDifferentPath(a, b, path) ?? path, kind, prev: a, next: b });
|
|
163
163
|
} else {
|
|
164
164
|
changes.push({ path, kind, prev: a, next: b });
|
|
165
165
|
}
|
|
166
166
|
}
|
|
167
167
|
return changes;
|
|
168
168
|
}
|
|
169
|
-
function firstDifferentPath(a, b, path, depth = 0) {
|
|
170
|
-
if (depth > 8) return path;
|
|
169
|
+
function firstDifferentPath(a, b, path = "", depth = 0) {
|
|
170
|
+
if (depth > 8) return path || "(value)";
|
|
171
171
|
if (Array.isArray(a) && Array.isArray(b)) {
|
|
172
|
-
if (a.length !== b.length) return `${path}.length
|
|
172
|
+
if (a.length !== b.length) return path ? `${path}.length` : "length";
|
|
173
173
|
for (let i = 0; i < a.length; i++) {
|
|
174
174
|
if (!deepEqual(a[i], b[i])) return firstDifferentPath(a[i], b[i], joinPath(path, i), depth + 1);
|
|
175
175
|
}
|
|
176
|
-
return
|
|
176
|
+
return null;
|
|
177
177
|
}
|
|
178
178
|
if (isPlainObject(a) && isPlainObject(b)) {
|
|
179
179
|
const keys = /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)]);
|
|
180
180
|
for (const k of keys) {
|
|
181
181
|
if (!deepEqual(a[k], b[k])) return firstDifferentPath(a[k], b[k], joinPath(path, k), depth + 1);
|
|
182
182
|
}
|
|
183
|
-
return
|
|
183
|
+
return null;
|
|
184
184
|
}
|
|
185
|
-
return path;
|
|
185
|
+
return deepEqual(a, b) ? null : path || "(value)";
|
|
186
186
|
}
|
|
187
187
|
|
|
188
188
|
// src/report.ts
|
|
@@ -200,10 +200,12 @@ function storeAdvice(custom) {
|
|
|
200
200
|
}
|
|
201
201
|
var isGenuine = (c) => c.kind === "different" || c.kind === "added" || c.kind === "removed";
|
|
202
202
|
var isChildren = (change) => change.path === "children";
|
|
203
|
+
var isRefObject = (change) => change.path === "ref" && change.kind === "deep-equal";
|
|
203
204
|
function fixFor(change) {
|
|
204
205
|
if (isChildren(change) && (change.kind === "element" || change.kind === "deep-equal")) {
|
|
205
206
|
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`;
|
|
206
207
|
}
|
|
208
|
+
if (isRefObject(change)) return `create the ref once with useRef (or createRef outside the render) and pass the same object every time`;
|
|
207
209
|
switch (change.kind) {
|
|
208
210
|
case "function":
|
|
209
211
|
return `wrap it in useCallback (or hoist it out of the parent's render)`;
|
|
@@ -219,6 +221,7 @@ function describe(change) {
|
|
|
219
221
|
if (isChildren(change) && (change.kind === "element" || change.kind === "deep-equal")) {
|
|
220
222
|
return `children are new React elements with the same types and props on every render of the parent`;
|
|
221
223
|
}
|
|
224
|
+
if (isRefObject(change)) return `prop "ref" is a new ref object on every render (React re-attaches it each time)`;
|
|
222
225
|
switch (change.kind) {
|
|
223
226
|
case "deep-equal":
|
|
224
227
|
return `prop "${change.path}" is a new reference but deep-equal to the previous value`;
|
|
@@ -246,11 +249,21 @@ function buildReport(input) {
|
|
|
246
249
|
if (causes.length === 0) trigger = "parent";
|
|
247
250
|
else if (causes.length === 1) trigger = causes[0];
|
|
248
251
|
else trigger = "mixed";
|
|
249
|
-
const
|
|
252
|
+
const nothingChanged = input.propChanges.length === 0 && stateChanges.length === 0 && hookChanges.length === 0;
|
|
253
|
+
const cacheHit = input.compiled === true && trigger === "parent" && input.propChanges.length === 0;
|
|
254
|
+
const revealed = input.revealed === true && trigger === "parent";
|
|
255
|
+
const avoidable = trigger === "parent" && !cacheHit && !revealed;
|
|
250
256
|
const reasons = [];
|
|
251
|
-
|
|
257
|
+
const memoFix = input.classComponent ? `Make "${input.component}" extend PureComponent (or implement shouldComponentUpdate)` : `Wrap "${input.component}" in React.memo (or extend PureComponent)`;
|
|
258
|
+
if (cacheHit) {
|
|
259
|
+
reasons.push(`compiled by React Compiler: its output is memoized and the inputs did not change, so this render is cheap; nothing to fix.`);
|
|
260
|
+
} else if (revealed) {
|
|
261
|
+
reasons.push(
|
|
262
|
+
`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.`
|
|
263
|
+
);
|
|
264
|
+
} else if (avoidable && nothingChanged) {
|
|
252
265
|
const who = input.parent ? `<${input.parent.name}> re-rendered (${describeTrigger(input.parent.trigger)})` : "its parent re-rendered";
|
|
253
|
-
reasons.push(`re-rendered with identical props because ${who}.
|
|
266
|
+
reasons.push(`re-rendered with identical props because ${who}. ${memoFix}.`);
|
|
254
267
|
} else if (avoidable && input.parent) {
|
|
255
268
|
reasons.push(`caused by <${input.parent.name}> re-rendering (${describeTrigger(input.parent.trigger)}).`);
|
|
256
269
|
}
|
|
@@ -259,9 +272,9 @@ function buildReport(input) {
|
|
|
259
272
|
reasons.push(fix ? `${describe(c)}: ${fix}.` : `${describe(c)}.`);
|
|
260
273
|
}
|
|
261
274
|
const memoized = input.memoized !== false;
|
|
262
|
-
if (avoidable && !memoized && input.propChanges.length > 0) {
|
|
275
|
+
if (avoidable && !memoized && !input.compiled && input.propChanges.length > 0) {
|
|
263
276
|
reasons.push(
|
|
264
|
-
`"${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).`
|
|
277
|
+
`"${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)"}.`
|
|
265
278
|
);
|
|
266
279
|
}
|
|
267
280
|
for (const c of stateChanges) {
|
|
@@ -273,6 +286,8 @@ function buildReport(input) {
|
|
|
273
286
|
const where = c.provider && c.provider.component ? ` (provided by <${c.provider.component}>)` : "";
|
|
274
287
|
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` : "";
|
|
275
288
|
reasons.push(`${c.path} changed${where}${keys}.`);
|
|
289
|
+
} else if (c.hook === "useDeferredValue" && isGenuine(c)) {
|
|
290
|
+
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.`);
|
|
276
291
|
} else if (isGenuine(c)) reasons.push(`${hookLabel(c)} changed.`);
|
|
277
292
|
else if (isStateHook(c))
|
|
278
293
|
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.`);
|
|
@@ -303,6 +318,7 @@ function buildReport(input) {
|
|
|
303
318
|
reasons,
|
|
304
319
|
time: now()
|
|
305
320
|
};
|
|
321
|
+
if (input.compiled) report.compiled = true;
|
|
306
322
|
if (input.selfDuration !== void 0) report.selfDuration = input.selfDuration;
|
|
307
323
|
if (input.treeDuration !== void 0) report.treeDuration = input.treeDuration;
|
|
308
324
|
if (input.commitPriority) report.commitPriority = input.commitPriority;
|
|
@@ -340,10 +356,10 @@ var KIND_LABEL = {
|
|
|
340
356
|
};
|
|
341
357
|
function summarize(report) {
|
|
342
358
|
const counts2 = /* @__PURE__ */ new Map();
|
|
343
|
-
for (const c of [...report.propChanges, ...report.stateChanges, ...report.hookChanges]) {
|
|
359
|
+
for (const c of [...report.propChanges || [], ...report.stateChanges || [], ...report.hookChanges || []]) {
|
|
344
360
|
counts2.set(c.kind, (counts2.get(c.kind) ?? 0) + 1);
|
|
345
361
|
}
|
|
346
|
-
const parts = [...counts2].map(([kind, n]) => `${n} ${KIND_LABEL[kind]}`);
|
|
362
|
+
const parts = [...counts2].map(([kind, n]) => `${n} ${KIND_LABEL[kind] || kind}`);
|
|
347
363
|
if (parts.length === 0) parts.push("no changes");
|
|
348
364
|
return parts.join(", ");
|
|
349
365
|
}
|
|
@@ -365,16 +381,27 @@ function printReport(report, options) {
|
|
|
365
381
|
}
|
|
366
382
|
|
|
367
383
|
// src/state.ts
|
|
384
|
+
var MAX_SEEN_COMPONENTS = 500;
|
|
368
385
|
var KEY = /* @__PURE__ */ Symbol.for("rerender-lens.state");
|
|
369
386
|
function getState() {
|
|
370
387
|
const g = globalThis;
|
|
371
388
|
let s = g[KEY];
|
|
372
389
|
if (!s) {
|
|
373
|
-
s = { options: {}, enabled: false, printed: /* @__PURE__ */ new Map(), detach: null, nextInstanceId: 1, nextCommitId: 1, warnedOnce: /* @__PURE__ */ new Set(), scheduled: 0, commits: 0, overheadMs: 0, maxCommitMs: 0, truncated: 0 };
|
|
390
|
+
s = { options: {}, enabled: false, printed: /* @__PURE__ */ new Map(), detach: null, nextInstanceId: 1, nextCommitId: 1, warnedOnce: /* @__PURE__ */ new Set(), scheduled: 0, commits: 0, overheadMs: 0, maxCommitMs: 0, truncated: 0, seen: /* @__PURE__ */ new Set(), seenTracked: /* @__PURE__ */ new Set(), seenOverflow: false };
|
|
374
391
|
g[KEY] = s;
|
|
375
392
|
}
|
|
376
393
|
return s;
|
|
377
394
|
}
|
|
395
|
+
function noteRendered(s, name, tracked) {
|
|
396
|
+
if (!s.seen.has(name)) {
|
|
397
|
+
if (s.seen.size >= MAX_SEEN_COMPONENTS) {
|
|
398
|
+
s.seenOverflow = true;
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
s.seen.add(name);
|
|
402
|
+
}
|
|
403
|
+
if (tracked) s.seenTracked.add(name);
|
|
404
|
+
}
|
|
378
405
|
function warnOnce(key, message) {
|
|
379
406
|
const s = getState();
|
|
380
407
|
if (s.warnedOnce.has(key)) return;
|
|
@@ -406,6 +433,27 @@ function dispatch(report, override) {
|
|
|
406
433
|
}
|
|
407
434
|
|
|
408
435
|
// src/hookNames.ts
|
|
436
|
+
var NODE_COUNT = {
|
|
437
|
+
useState: 1,
|
|
438
|
+
useReducer: 1,
|
|
439
|
+
useRef: 1,
|
|
440
|
+
useMemo: 1,
|
|
441
|
+
useCallback: 1,
|
|
442
|
+
useEffect: 1,
|
|
443
|
+
useLayoutEffect: 1,
|
|
444
|
+
useInsertionEffect: 1,
|
|
445
|
+
useImperativeHandle: 1,
|
|
446
|
+
useSyncExternalStore: 2,
|
|
447
|
+
useTransition: 2,
|
|
448
|
+
useDeferredValue: 1,
|
|
449
|
+
useId: 1,
|
|
450
|
+
useOptimistic: 1,
|
|
451
|
+
useActionState: 3,
|
|
452
|
+
useFormState: 3,
|
|
453
|
+
useEffectEvent: 1,
|
|
454
|
+
useCacheRefresh: 1
|
|
455
|
+
};
|
|
456
|
+
var BUILTIN_HOOKS = /* @__PURE__ */ new Set([...Object.keys(NODE_COUNT), "useContext", "useDebugValue", "use"]);
|
|
409
457
|
var noop = () => {
|
|
410
458
|
};
|
|
411
459
|
function customHooksFromStack(stack, componentName, marker) {
|
|
@@ -420,7 +468,7 @@ function customHooksFromStack(stack, componentName, marker) {
|
|
|
420
468
|
continue;
|
|
421
469
|
}
|
|
422
470
|
if (name === componentName || name.endsWith("." + componentName)) break;
|
|
423
|
-
if (/^use[A-Z0-9_]/.test(name)) names.push(name);
|
|
471
|
+
if (/^use[A-Z0-9_]/.test(name) && !BUILTIN_HOOKS.has(name)) names.push(name);
|
|
424
472
|
if (names.length > 8) break;
|
|
425
473
|
}
|
|
426
474
|
return names;
|
|
@@ -720,6 +768,11 @@ function isMemoizedFiber(fiber) {
|
|
|
720
768
|
}
|
|
721
769
|
return false;
|
|
722
770
|
}
|
|
771
|
+
function isCompiledFiber(fiber) {
|
|
772
|
+
if (fiber.tag === ClassComponent) return false;
|
|
773
|
+
const cache2 = fiber.updateQueue?.memoCache;
|
|
774
|
+
return !!cache2 && Array.isArray(cache2.data);
|
|
775
|
+
}
|
|
723
776
|
function durationsOf(fiber) {
|
|
724
777
|
if (typeof fiber.actualDuration !== "number") return null;
|
|
725
778
|
const tree = fiber.actualDuration;
|
|
@@ -898,7 +951,8 @@ function diffHooks(fiber, alt) {
|
|
|
898
951
|
const labels = hookLabelsFor(fiber, b);
|
|
899
952
|
let i = 0;
|
|
900
953
|
while (a && b) {
|
|
901
|
-
|
|
954
|
+
const deferred = labels?.[i] === "useDeferredValue";
|
|
955
|
+
if ((deferred || isStateNode(b) || isStoreNode(b)) && !Object.is(a.memoizedState, b.memoizedState)) {
|
|
902
956
|
const hook = labels?.[i] ?? hookLabel2(b);
|
|
903
957
|
out.push({
|
|
904
958
|
path: `${hook}#${i}`,
|
|
@@ -932,6 +986,10 @@ function diffContexts(fiber, alt) {
|
|
|
932
986
|
let a = alt.dependencies?.firstContext ?? null;
|
|
933
987
|
let b = fiber.dependencies?.firstContext ?? null;
|
|
934
988
|
let i = 0;
|
|
989
|
+
if (b && !("memoizedValue" in b)) {
|
|
990
|
+
warnOnce("contextValues", "this React does not record context values on the fiber (React < 18): context changes are reported as plain parent re-renders.");
|
|
991
|
+
return out;
|
|
992
|
+
}
|
|
935
993
|
while (a && b) {
|
|
936
994
|
if (a.context === b.context && !Object.is(a.memoizedValue, b.memoizedValue)) {
|
|
937
995
|
const name = b.context.displayName ?? "Context";
|
|
@@ -964,10 +1022,26 @@ function diffContexts(fiber, alt) {
|
|
|
964
1022
|
return out;
|
|
965
1023
|
}
|
|
966
1024
|
var isGenuine2 = (c) => c.kind === "different" || c.kind === "added" || c.kind === "removed";
|
|
1025
|
+
var isRefObject2 = (v) => {
|
|
1026
|
+
if (typeof v !== "object" || v === null) return false;
|
|
1027
|
+
const keys = Object.keys(v);
|
|
1028
|
+
return keys.length === 1 && keys[0] === "current";
|
|
1029
|
+
};
|
|
1030
|
+
function diffProps(prev, next) {
|
|
1031
|
+
if (prev === next) return [];
|
|
1032
|
+
const pr = prev.ref;
|
|
1033
|
+
const nr = next.ref;
|
|
1034
|
+
if (pr !== nr && isRefObject2(pr) && isRefObject2(nr)) {
|
|
1035
|
+
const { ref: _p, ...restPrev } = prev;
|
|
1036
|
+
const { ref: _n, ...restNext } = next;
|
|
1037
|
+
return [{ path: "ref", kind: "deep-equal", prev: pr, next: nr }, ...diffRecords(restPrev, restNext)];
|
|
1038
|
+
}
|
|
1039
|
+
return diffRecords(prev, next);
|
|
1040
|
+
}
|
|
967
1041
|
function analyze(fiber, alt, trackHooks) {
|
|
968
1042
|
const prevProps = alt.memoizedProps ?? {};
|
|
969
1043
|
const nextProps = fiber.memoizedProps ?? {};
|
|
970
|
-
const propChanges =
|
|
1044
|
+
const propChanges = diffProps(prevProps, nextProps);
|
|
971
1045
|
let stateChanges = [];
|
|
972
1046
|
let hookChanges = [];
|
|
973
1047
|
if (fiber.tag === ClassComponent) {
|
|
@@ -980,7 +1054,7 @@ function analyze(fiber, alt, trackHooks) {
|
|
|
980
1054
|
} else if (trackHooks) {
|
|
981
1055
|
hookChanges = [...diffHooks(fiber, alt), ...diffContexts(fiber, alt)];
|
|
982
1056
|
}
|
|
983
|
-
const isStateHook = (h) => h.hook !== "useContext" && h.hook !== "useSyncExternalStore";
|
|
1057
|
+
const isStateHook = (h) => h.hook !== "useContext" && h.hook !== "useSyncExternalStore" && h.hook !== "useDeferredValue";
|
|
984
1058
|
const causes = [];
|
|
985
1059
|
if (propChanges.some(isGenuine2)) causes.push("props");
|
|
986
1060
|
if (stateChanges.some(isGenuine2) || hookChanges.some((h) => isStateHook(h) && isGenuine2(h))) causes.push("state");
|
|
@@ -1020,6 +1094,24 @@ var lastCommit = null;
|
|
|
1020
1094
|
var EFFECT_LOOP_WINDOW_MS = 50;
|
|
1021
1095
|
var MAX_REPORTS_PER_COMMIT = 200;
|
|
1022
1096
|
var COMMIT_TIME_BUDGET_MS = 25;
|
|
1097
|
+
function isDeferredCatchUp(fiber) {
|
|
1098
|
+
const types = fiber._debugHookTypes;
|
|
1099
|
+
if (!types || !types.includes("useDeferredValue") || !fiber.alternate) return false;
|
|
1100
|
+
const first = fiber.memoizedState;
|
|
1101
|
+
if (!isHookList(first)) return false;
|
|
1102
|
+
const labels = hookLabelsFor(fiber, first);
|
|
1103
|
+
if (!labels) return false;
|
|
1104
|
+
let a = fiber.alternate.memoizedState;
|
|
1105
|
+
let b = first;
|
|
1106
|
+
for (let i = 0; a && b; a = a.next, b = b.next, i++) {
|
|
1107
|
+
if (labels[i] === "useDeferredValue" && !Object.is(a.memoizedState, b.memoizedState)) return true;
|
|
1108
|
+
}
|
|
1109
|
+
return false;
|
|
1110
|
+
}
|
|
1111
|
+
function isUnder(fiber, boundaries) {
|
|
1112
|
+
for (let f = fiber.return; f && f.tag !== HostRoot; f = f.return) if (f.tag === SuspenseComponent && boundaries.includes(f)) return true;
|
|
1113
|
+
return false;
|
|
1114
|
+
}
|
|
1023
1115
|
function onCommit(root, commitPriority) {
|
|
1024
1116
|
const s = getState();
|
|
1025
1117
|
if (!s.enabled) return;
|
|
@@ -1029,17 +1121,20 @@ function onCommit(root, commitPriority) {
|
|
|
1029
1121
|
const rendered = [];
|
|
1030
1122
|
const renderedNames = /* @__PURE__ */ new Set();
|
|
1031
1123
|
let hot = false;
|
|
1032
|
-
let
|
|
1124
|
+
let resolvedBoundaries = null;
|
|
1033
1125
|
const stack = [root.current];
|
|
1034
1126
|
while (stack.length) {
|
|
1035
1127
|
const fiber = stack.pop();
|
|
1036
1128
|
const alt = fiber.alternate;
|
|
1037
1129
|
if (alt && isComponentTag(fiber.tag) && didRender(fiber)) {
|
|
1038
1130
|
if (isHotSwapped(fiber, alt)) hot = true;
|
|
1039
|
-
|
|
1040
|
-
|
|
1131
|
+
const name = fiberName(fiber);
|
|
1132
|
+
renderedNames.add(name);
|
|
1133
|
+
const tracked = shouldTrack(fiberType(fiber), o);
|
|
1134
|
+
noteRendered(s, name, tracked);
|
|
1135
|
+
if (tracked) rendered.push(fiber);
|
|
1041
1136
|
}
|
|
1042
|
-
if (fiber.tag === SuspenseComponent && alt && alt.memoizedState !== null && fiber.memoizedState === null)
|
|
1137
|
+
if (fiber.tag === SuspenseComponent && alt && alt.memoizedState !== null && fiber.memoizedState === null) (resolvedBoundaries ?? (resolvedBoundaries = [])).push(fiber);
|
|
1043
1138
|
if (!alt || fiber.child !== alt.child) {
|
|
1044
1139
|
for (let c = fiber.child; c; c = c.sibling) stack.push(c);
|
|
1045
1140
|
}
|
|
@@ -1051,10 +1146,10 @@ function onCommit(root, commitPriority) {
|
|
|
1051
1146
|
let commitCause;
|
|
1052
1147
|
let afterCommit;
|
|
1053
1148
|
const inputDriven = commitPriority === "immediate" || commitPriority === "user-blocking";
|
|
1054
|
-
if (!inputDriven && lastCommit && at - lastCommit.at < EFFECT_LOOP_WINDOW_MS && updaters.some((u) => lastCommit.rendered.has(u))) {
|
|
1149
|
+
if (!inputDriven && lastCommit && at - lastCommit.at < EFFECT_LOOP_WINDOW_MS && updaters.some((u) => lastCommit.rendered.has(u)) && !rendered.some(isDeferredCatchUp)) {
|
|
1055
1150
|
commitCause = "effect-after-commit";
|
|
1056
1151
|
afterCommit = lastCommit.id;
|
|
1057
|
-
} else if (
|
|
1152
|
+
} else if (resolvedBoundaries) commitCause = "suspense-resolved";
|
|
1058
1153
|
if (rendered.length === 0) {
|
|
1059
1154
|
lastCommit = { id: s.nextCommitId, at, rendered: renderedNames };
|
|
1060
1155
|
return;
|
|
@@ -1106,6 +1201,9 @@ function onCommit(root, commitPriority) {
|
|
|
1106
1201
|
owner: ownerName(fiber),
|
|
1107
1202
|
path: componentPath(fiber),
|
|
1108
1203
|
memoized: isMemoizedFiber(fiber),
|
|
1204
|
+
compiled: isCompiledFiber(fiber),
|
|
1205
|
+
classComponent: fiber.tag === ClassComponent,
|
|
1206
|
+
revealed: resolvedBoundaries !== null && a.trigger === "parent" && isUnder(fiber, resolvedBoundaries),
|
|
1109
1207
|
selfDuration: durations ? durations.self : void 0,
|
|
1110
1208
|
treeDuration: durations ? durations.tree : void 0,
|
|
1111
1209
|
commitId,
|
|
@@ -1159,10 +1257,53 @@ function shouldTrack(type, o) {
|
|
|
1159
1257
|
if (o.trackAllMemoized) return isMemo(type) || isPureClass(type);
|
|
1160
1258
|
return false;
|
|
1161
1259
|
}
|
|
1260
|
+
var ANONYMOUS = "Anonymous";
|
|
1261
|
+
function explainTracking(type, o) {
|
|
1262
|
+
if (typeof type === "string")
|
|
1263
|
+
return { tracked: false, name: type, memoized: false, reason: `<${type}> is a DOM element, not a React component; only components are tracked.` };
|
|
1264
|
+
if (!isComponentLike(type))
|
|
1265
|
+
return { tracked: false, name: ANONYMOUS, memoized: false, reason: "This is not a React component type, so it is never tracked." };
|
|
1266
|
+
const name = getDisplayName(type);
|
|
1267
|
+
const memo = isMemo(type);
|
|
1268
|
+
const memoized = memo || isPureClass(type);
|
|
1269
|
+
if (o.exclude?.some((m) => matches(m, name)))
|
|
1270
|
+
return { tracked: false, name, memoized, reason: `<${name}> matches "exclude", which wins over every other rule.`, fix: `Remove "${name}" from the exclude list.` };
|
|
1271
|
+
if (hasMarker(type)) return { tracked: true, name, memoized, reason: `<${name}> is marked with track() (the ${MARKER} static).` };
|
|
1272
|
+
if (o.include?.some((m) => matches(m, name))) return { tracked: true, name, memoized, reason: `<${name}> matches "include".` };
|
|
1273
|
+
if (o.trackAllComponents) return { tracked: true, name, memoized, reason: '"Track every component" is on, so every component is tracked.' };
|
|
1274
|
+
if (o.trackAllMemoized && memoized)
|
|
1275
|
+
return { tracked: true, name, memoized, reason: `<${name}> is ${memo ? "a React.memo component" : "a PureComponent"}, and "Track every React.memo and PureComponent" is on.` };
|
|
1276
|
+
if (name === ANONYMOUS)
|
|
1277
|
+
return {
|
|
1278
|
+
tracked: false,
|
|
1279
|
+
name,
|
|
1280
|
+
memoized,
|
|
1281
|
+
reason: 'This component has no display name, so "include" and "exclude" cannot address it.',
|
|
1282
|
+
fix: "Give the function a name, set displayName, or mark it with track(fn, 'Name')."
|
|
1283
|
+
};
|
|
1284
|
+
if (o.trackAllMemoized)
|
|
1285
|
+
return {
|
|
1286
|
+
tracked: false,
|
|
1287
|
+
name,
|
|
1288
|
+
memoized,
|
|
1289
|
+
reason: `"Track every React.memo and PureComponent" only covers React.memo components and PureComponent classes, and <${name}> is neither.`,
|
|
1290
|
+
fix: `Add "${name}" to include, wrap it in React.memo, or turn on "Track every component".`
|
|
1291
|
+
};
|
|
1292
|
+
return {
|
|
1293
|
+
tracked: false,
|
|
1294
|
+
name,
|
|
1295
|
+
memoized,
|
|
1296
|
+
reason: `Nothing selects <${name}>: it is not marked with track(), no "include" pattern matches it, and neither "track every" option is on.`,
|
|
1297
|
+
fix: `Add "${name}" to include, or turn on "Track every React.memo and PureComponent" / "Track every component".`
|
|
1298
|
+
};
|
|
1299
|
+
}
|
|
1162
1300
|
function init(options = {}) {
|
|
1163
1301
|
const s = getState();
|
|
1164
1302
|
s.options = { ...options };
|
|
1165
1303
|
s.printed.clear();
|
|
1304
|
+
s.seen.clear();
|
|
1305
|
+
s.seenTracked.clear();
|
|
1306
|
+
s.seenOverflow = false;
|
|
1166
1307
|
if (!s.enabled) {
|
|
1167
1308
|
s.detach = attach();
|
|
1168
1309
|
s.enabled = true;
|
|
@@ -1288,7 +1429,7 @@ function flash(target, duration = 500) {
|
|
|
1288
1429
|
}
|
|
1289
1430
|
|
|
1290
1431
|
// src/version.ts
|
|
1291
|
-
var VERSION = "0.
|
|
1432
|
+
var VERSION = "0.7.0" ;
|
|
1292
1433
|
|
|
1293
1434
|
// src/devtools.ts
|
|
1294
1435
|
var DEVTOOLS_MARKER = "__rerenderLens";
|
|
@@ -1414,6 +1555,18 @@ function createDevtoolsNotifier(options = {}) {
|
|
|
1414
1555
|
target.postMessage(msg, "*");
|
|
1415
1556
|
};
|
|
1416
1557
|
const source = options.source ?? "page";
|
|
1558
|
+
const trackingSummary = () => {
|
|
1559
|
+
const s = getState();
|
|
1560
|
+
const o = s.options;
|
|
1561
|
+
return {
|
|
1562
|
+
mode: o.trackAllComponents ? "all" : o.trackAllMemoized ? "memoized" : "marked",
|
|
1563
|
+
include: (o.include ?? []).map(matcherToString).filter((x) => x !== null),
|
|
1564
|
+
exclude: (o.exclude ?? []).map(matcherToString).filter((x) => x !== null),
|
|
1565
|
+
renderedCount: s.seen.size,
|
|
1566
|
+
trackedCount: s.seenTracked.size,
|
|
1567
|
+
overflow: s.seenOverflow
|
|
1568
|
+
};
|
|
1569
|
+
};
|
|
1417
1570
|
const info = () => ({
|
|
1418
1571
|
count: buffer.length,
|
|
1419
1572
|
library: VERSION,
|
|
@@ -1427,7 +1580,8 @@ function createDevtoolsNotifier(options = {}) {
|
|
|
1427
1580
|
scheduled: getState().scheduled,
|
|
1428
1581
|
commits: getState().commits,
|
|
1429
1582
|
overhead: { totalMs: getState().overheadMs, maxCommitMs: getState().maxCommitMs },
|
|
1430
|
-
truncated: getState().truncated
|
|
1583
|
+
truncated: getState().truncated,
|
|
1584
|
+
tracking: trackingSummary()
|
|
1431
1585
|
});
|
|
1432
1586
|
const bridge = {
|
|
1433
1587
|
replay: () => {
|
|
@@ -1473,6 +1627,10 @@ function createDevtoolsNotifier(options = {}) {
|
|
|
1473
1627
|
flashAvoidable: (on) => {
|
|
1474
1628
|
flashOn = !!on;
|
|
1475
1629
|
},
|
|
1630
|
+
explain: (target2) => {
|
|
1631
|
+
const comp = typeof target2 === "number" ? fiberById(target2) : nearestComponent(fiberForNode(target2));
|
|
1632
|
+
return comp ? explainTracking(fiberType(comp), getState().options) : null;
|
|
1633
|
+
},
|
|
1476
1634
|
inspect: (node) => {
|
|
1477
1635
|
const comp = nearestComponent(fiberForNode(node));
|
|
1478
1636
|
if (!comp) return null;
|
|
@@ -1487,6 +1645,7 @@ function createDevtoolsNotifier(options = {}) {
|
|
|
1487
1645
|
component: fiberName(comp),
|
|
1488
1646
|
instanceId: id,
|
|
1489
1647
|
tracked: shouldTrack(fiberType(comp), getState().options),
|
|
1648
|
+
tracking: explainTracking(fiberType(comp), getState().options),
|
|
1490
1649
|
path,
|
|
1491
1650
|
reports: id === null ? [] : buffer.filter((e) => e.instanceId === id).slice(-20).map((e) => e.payload)
|
|
1492
1651
|
};
|
|
@@ -1521,6 +1680,9 @@ function createDevtoolsNotifier(options = {}) {
|
|
|
1521
1680
|
bridge.flashAvoidable(!!data.arg);
|
|
1522
1681
|
reply.result = true;
|
|
1523
1682
|
break;
|
|
1683
|
+
case "explain":
|
|
1684
|
+
reply.result = bridge.explain(data.arg);
|
|
1685
|
+
break;
|
|
1524
1686
|
default:
|
|
1525
1687
|
reply.error = `unknown command ${String(data.cmd)}`;
|
|
1526
1688
|
}
|
|
@@ -1529,6 +1691,7 @@ function createDevtoolsNotifier(options = {}) {
|
|
|
1529
1691
|
}
|
|
1530
1692
|
return reply;
|
|
1531
1693
|
};
|
|
1694
|
+
const isRelayGreeting = (data) => !!data && typeof data === "object" && data.__rerenderLensRelay === true && typeof data.app === "string";
|
|
1532
1695
|
const isCommand = (data) => !!data && typeof data === "object" && data.__rerenderLensCmd === true && typeof data.id === "string";
|
|
1533
1696
|
const envelope = (type, payload) => ({ [DEVTOOLS_MARKER]: true, version: PROTOCOL_VERSION, type, payload });
|
|
1534
1697
|
const sinks = [];
|
|
@@ -1551,14 +1714,20 @@ function createDevtoolsNotifier(options = {}) {
|
|
|
1551
1714
|
const relayUrl = options.relay ?? (typeof window !== "undefined" ? window.__RERENDER_LENS_RELAY__ : void 0);
|
|
1552
1715
|
const ES = options.eventSource ?? (typeof EventSource === "function" ? EventSource : null);
|
|
1553
1716
|
if (relayUrl && ES && typeof fetch === "function") {
|
|
1554
|
-
const
|
|
1717
|
+
const [rawBase, relayQuery = ""] = relayUrl.split("?");
|
|
1718
|
+
const base = (rawBase ?? "").replace(/\/$/, "");
|
|
1719
|
+
const relayToken = new URLSearchParams(relayQuery).get("token");
|
|
1720
|
+
const auth = relayToken ? `token=${encodeURIComponent(relayToken)}` : "";
|
|
1555
1721
|
let queue = [];
|
|
1556
1722
|
let scheduled = false;
|
|
1723
|
+
let appId = null;
|
|
1557
1724
|
const flush = () => {
|
|
1558
1725
|
scheduled = false;
|
|
1559
1726
|
const batch = queue;
|
|
1560
1727
|
queue = [];
|
|
1561
|
-
|
|
1728
|
+
const params = [appId ? `app=${encodeURIComponent(appId)}` : "", auth].filter(Boolean).join("&");
|
|
1729
|
+
const to = `${base}/message${params ? `?${params}` : ""}`;
|
|
1730
|
+
fetch(to, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(batch), keepalive: true }).catch(() => {
|
|
1562
1731
|
});
|
|
1563
1732
|
};
|
|
1564
1733
|
const relaySend = (message) => {
|
|
@@ -1569,7 +1738,8 @@ function createDevtoolsNotifier(options = {}) {
|
|
|
1569
1738
|
}
|
|
1570
1739
|
};
|
|
1571
1740
|
try {
|
|
1572
|
-
const
|
|
1741
|
+
const label = typeof location !== "undefined" ? `${location.host}${location.pathname}`.slice(0, 120) : "";
|
|
1742
|
+
const stream = new ES(`${base}/events?role=app${label ? `&label=${encodeURIComponent(label)}` : ""}${auth ? `&${auth}` : ""}`);
|
|
1573
1743
|
stream.onmessage = (event) => {
|
|
1574
1744
|
let parsed;
|
|
1575
1745
|
try {
|
|
@@ -1577,7 +1747,10 @@ function createDevtoolsNotifier(options = {}) {
|
|
|
1577
1747
|
} catch {
|
|
1578
1748
|
return;
|
|
1579
1749
|
}
|
|
1580
|
-
for (const m of Array.isArray(parsed) ? parsed : [parsed])
|
|
1750
|
+
for (const m of Array.isArray(parsed) ? parsed : [parsed]) {
|
|
1751
|
+
if (isRelayGreeting(m)) appId = m.app;
|
|
1752
|
+
else if (isCommand(m)) relaySend(runCommand(m));
|
|
1753
|
+
}
|
|
1581
1754
|
};
|
|
1582
1755
|
stream.onerror = () => {
|
|
1583
1756
|
};
|
|
@@ -1621,15 +1794,16 @@ function createDevtoolsNotifier(options = {}) {
|
|
|
1621
1794
|
}
|
|
1622
1795
|
|
|
1623
1796
|
// src/setup.ts
|
|
1624
|
-
var
|
|
1797
|
+
var read = (get) => {
|
|
1625
1798
|
try {
|
|
1626
|
-
return
|
|
1799
|
+
return get();
|
|
1627
1800
|
} catch {
|
|
1628
1801
|
return void 0;
|
|
1629
1802
|
}
|
|
1630
1803
|
};
|
|
1631
|
-
|
|
1632
|
-
|
|
1804
|
+
var production = read(() => process.env.NODE_ENV) === "production";
|
|
1805
|
+
if (!production && typeof window !== "undefined" && !window.__RERENDER_LENS_DEVTOOLS__) {
|
|
1806
|
+
const relay = read(() => process.env.RERENDER_LENS_RELAY) || read(() => process.env.NEXT_PUBLIC_RERENDER_LENS_RELAY);
|
|
1633
1807
|
init({ trackAllMemoized: true, notifier: createDevtoolsNotifier(relay ? { relay } : {}) });
|
|
1634
1808
|
}
|
|
1635
1809
|
//# sourceMappingURL=setup.js.map
|