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
package/panel/panel.js
CHANGED
|
@@ -1,190 +1,288 @@
|
|
|
1
1
|
/* Built from extension/src/panel.ts by `npm run build`; do not edit by hand. */
|
|
2
2
|
"use strict";
|
|
3
3
|
(() => {
|
|
4
|
-
//
|
|
5
|
-
var
|
|
6
|
-
var
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
added: "added",
|
|
12
|
-
removed: "removed"
|
|
13
|
-
};
|
|
14
|
-
var AVOIDABLE_KINDS = /* @__PURE__ */ new Set(["deep-equal", "function", "element"]);
|
|
15
|
-
var FN_PREFIX = "\u0192 ";
|
|
16
|
-
var MAX_REPORTS = 2e3;
|
|
17
|
-
var MAX_PER_NODE = 200;
|
|
18
|
-
var MAX_COMMITS = 500;
|
|
19
|
-
var MAX_PER_COMMIT = 500;
|
|
20
|
-
var THROTTLE_MIN_REPORTS = 200;
|
|
21
|
-
var BEST_FIX_INTERVAL = 500;
|
|
22
|
-
var LEFT_RENDER_INTERVAL = 250;
|
|
23
|
-
var SYNC_RETRY_MIN = 500;
|
|
24
|
-
var SYNC_RETRY_MAX = 5e3;
|
|
25
|
-
var SYNC_RETRIES = 20;
|
|
26
|
-
var NAVIGATION_SETTLE = 1200;
|
|
27
|
-
var ROW_H = 22;
|
|
28
|
-
var ITEM_H = 20;
|
|
29
|
-
var OVERSCAN = 8;
|
|
30
|
-
var FALLBACK_VIEWPORT = 800;
|
|
31
|
-
function el(tag, attrs, children) {
|
|
32
|
-
const node = document.createElement(tag);
|
|
33
|
-
if (attrs) {
|
|
34
|
-
for (const k of Object.keys(attrs)) {
|
|
35
|
-
const v = attrs[k];
|
|
36
|
-
if (k === "class") node.className = String(v);
|
|
37
|
-
else if (k === "text") node.textContent = String(v);
|
|
38
|
-
else if (k.startsWith("on")) {
|
|
39
|
-
if (typeof v === "function") node.addEventListener(k.slice(2), v);
|
|
40
|
-
} else if (v === true) node.setAttribute(k, "");
|
|
41
|
-
else if (v !== void 0 && v !== null && v !== false) node.setAttribute(k, String(v));
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
if (children) {
|
|
45
|
-
for (const c of [].concat(children)) if (c != null) node.append(c);
|
|
46
|
-
}
|
|
47
|
-
return node;
|
|
4
|
+
// src/diff.ts
|
|
5
|
+
var REACT_ELEMENT = /* @__PURE__ */ Symbol.for("react.element");
|
|
6
|
+
var REACT_TRANSITIONAL_ELEMENT = /* @__PURE__ */ Symbol.for("react.transitional.element");
|
|
7
|
+
function isReactElement(v) {
|
|
8
|
+
if (typeof v !== "object" || v === null) return false;
|
|
9
|
+
const t = v.$$typeof;
|
|
10
|
+
return t === REACT_ELEMENT || t === REACT_TRANSITIONAL_ELEMENT;
|
|
48
11
|
}
|
|
49
|
-
function
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
return
|
|
12
|
+
function isPlainObject(v) {
|
|
13
|
+
if (typeof v !== "object" || v === null) return false;
|
|
14
|
+
const proto = Object.getPrototypeOf(v);
|
|
15
|
+
return proto === Object.prototype || proto === null;
|
|
53
16
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (counts.size === 0) return "no changes";
|
|
64
|
-
return [...counts].map(([k, n]) => `${n} ${KIND_LABEL[k] || k}`).join(", ");
|
|
17
|
+
function remember(seen, a, b) {
|
|
18
|
+
let set = seen.get(a);
|
|
19
|
+
if (!set) {
|
|
20
|
+
set = /* @__PURE__ */ new Set();
|
|
21
|
+
seen.set(a, set);
|
|
22
|
+
}
|
|
23
|
+
if (set.has(b)) return true;
|
|
24
|
+
set.add(b);
|
|
25
|
+
return false;
|
|
65
26
|
}
|
|
66
|
-
var
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
component: p.component,
|
|
74
|
-
instanceId: typeof p.instanceId === "number" ? p.instanceId : 0,
|
|
75
|
-
commitId: typeof p.commitId === "number" ? p.commitId : 0,
|
|
76
|
-
renderCount: typeof p.renderCount === "number" ? p.renderCount : 0,
|
|
77
|
-
trigger: typeof p.trigger === "string" ? p.trigger : "parent",
|
|
78
|
-
avoidable: !!p.avoidable,
|
|
79
|
-
props: { prev: isRecord(props.prev) ? props.prev : {}, next: isRecord(props.next) ? props.next : {} },
|
|
80
|
-
propChanges: arr(p.propChanges),
|
|
81
|
-
stateChanges: arr(p.stateChanges),
|
|
82
|
-
hookChanges: arr(p.hookChanges),
|
|
83
|
-
parent,
|
|
84
|
-
owner: typeof p.owner === "string" ? p.owner : null,
|
|
85
|
-
path: Array.isArray(p.path) ? p.path.filter((x) => typeof x === "string") : [],
|
|
86
|
-
reasons: Array.isArray(p.reasons) ? p.reasons.filter((x) => typeof x === "string") : [],
|
|
87
|
-
time: typeof p.time === "number" ? p.time : 0,
|
|
88
|
-
receivedAt: typeof p.receivedAt === "number" ? p.receivedAt : 0
|
|
27
|
+
var scope = null;
|
|
28
|
+
var DEFAULT_DIFF_BUDGET = 5e4;
|
|
29
|
+
function beginDiffScope(budget = 2e5) {
|
|
30
|
+
const previous = scope;
|
|
31
|
+
scope = { cache: /* @__PURE__ */ new WeakMap(), remaining: budget, exhausted: false };
|
|
32
|
+
return () => {
|
|
33
|
+
scope = previous;
|
|
89
34
|
};
|
|
90
|
-
if (typeof p.selfDuration === "number") r.selfDuration = p.selfDuration;
|
|
91
|
-
if (typeof p.treeDuration === "number") r.treeDuration = p.treeDuration;
|
|
92
|
-
if (typeof p.memoized === "boolean") r.memoized = p.memoized;
|
|
93
|
-
if (typeof p.commitPriority === "string") r.commitPriority = p.commitPriority;
|
|
94
|
-
if (Array.isArray(p.hookState)) r.hookState = p.hookState.filter((h) => isRecord(h) && typeof h.path === "string");
|
|
95
|
-
if (Array.isArray(p.contexts)) r.contexts = p.contexts.filter((c) => isRecord(c) && typeof c.name === "string");
|
|
96
|
-
if (isRecord(p.state)) r.state = p.state;
|
|
97
|
-
if (Array.isArray(p.updaters)) r.updaters = p.updaters.filter((u) => typeof u === "string");
|
|
98
|
-
if (p.commitCause === "effect-after-commit" || p.commitCause === "suspense-resolved") r.commitCause = p.commitCause;
|
|
99
|
-
if (typeof p.afterCommit === "number") r.afterCommit = p.afterCommit;
|
|
100
|
-
if (typeof p.key === "string") r.key = p.key;
|
|
101
|
-
if (isRecord(p.source) && typeof p.source.fileName === "string") r.source = p.source;
|
|
102
|
-
return r;
|
|
103
35
|
}
|
|
104
|
-
function
|
|
105
|
-
if (
|
|
106
|
-
|
|
107
|
-
if (typeof
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
36
|
+
function deepEqual(a, b, seen = /* @__PURE__ */ new Map()) {
|
|
37
|
+
if (Object.is(a, b)) return true;
|
|
38
|
+
if (typeof a !== typeof b) return false;
|
|
39
|
+
if (typeof a !== "object" || a === null || b === null) return false;
|
|
40
|
+
if (!scope) {
|
|
41
|
+
const end = beginDiffScope(DEFAULT_DIFF_BUDGET);
|
|
42
|
+
try {
|
|
43
|
+
return deepEqual(a, b, seen);
|
|
44
|
+
} finally {
|
|
45
|
+
end();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const sc = scope;
|
|
49
|
+
const objA = a;
|
|
50
|
+
const objB = b;
|
|
51
|
+
const hit = sc.cache.get(objA)?.get(objB);
|
|
52
|
+
if (hit !== void 0) return hit;
|
|
53
|
+
if (--sc.remaining < 0) {
|
|
54
|
+
sc.exhausted = true;
|
|
55
|
+
return false;
|
|
111
56
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
if (
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
57
|
+
const result = deepEqualObjects(objA, objB, seen);
|
|
58
|
+
let m = sc.cache.get(objA);
|
|
59
|
+
if (!m) {
|
|
60
|
+
m = /* @__PURE__ */ new WeakMap();
|
|
61
|
+
sc.cache.set(objA, m);
|
|
62
|
+
}
|
|
63
|
+
m.set(objB, result);
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
function deepEqualObjects(a, b, seen) {
|
|
67
|
+
const objA = a;
|
|
68
|
+
const objB = b;
|
|
69
|
+
if (remember(seen, objA, objB)) return true;
|
|
70
|
+
if (a instanceof Date) return b instanceof Date && a.getTime() === b.getTime();
|
|
71
|
+
if (a instanceof RegExp) return b instanceof RegExp && a.source === b.source && a.flags === b.flags;
|
|
72
|
+
if (Array.isArray(a)) {
|
|
73
|
+
if (!Array.isArray(b) || a.length !== b.length) return false;
|
|
74
|
+
for (let i = 0; i < a.length; i++) if (!deepEqual(a[i], b[i], seen)) return false;
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
if (Array.isArray(b)) return false;
|
|
78
|
+
if (ArrayBuffer.isView(a)) {
|
|
79
|
+
if (!ArrayBuffer.isView(b) || a.constructor !== b.constructor) return false;
|
|
80
|
+
const ta = a;
|
|
81
|
+
const tb = b;
|
|
82
|
+
if (ta.length !== tb.length) return false;
|
|
83
|
+
for (let i = 0; i < ta.length; i++) if (ta[i] !== tb[i]) return false;
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
if (a instanceof Map) {
|
|
87
|
+
if (!(b instanceof Map) || a.size !== b.size) return false;
|
|
88
|
+
for (const [k, v] of a) {
|
|
89
|
+
if (!b.has(k) || !deepEqual(v, b.get(k), seen)) return false;
|
|
124
90
|
}
|
|
125
|
-
return
|
|
91
|
+
return true;
|
|
126
92
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
93
|
+
if (a instanceof Set) {
|
|
94
|
+
if (!(b instanceof Set) || a.size !== b.size) return false;
|
|
95
|
+
if (a.size > 50) {
|
|
96
|
+
for (const v of a) if (!b.has(v)) return false;
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
outer: for (const v of a) {
|
|
100
|
+
if (b.has(v)) continue;
|
|
101
|
+
for (const w of b) if (deepEqual(v, w, seen)) continue outer;
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
return true;
|
|
132
105
|
}
|
|
133
|
-
if (
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
106
|
+
if (isReactElement(a)) {
|
|
107
|
+
if (!isReactElement(b)) return false;
|
|
108
|
+
return a.type === b.type && a.key === b.key && deepEqual(a.props, b.props, seen);
|
|
109
|
+
}
|
|
110
|
+
if (isPlainObject(a) && isPlainObject(b)) {
|
|
111
|
+
const ka = Object.keys(a);
|
|
112
|
+
const kb = Object.keys(b);
|
|
113
|
+
if (ka.length !== kb.length) return false;
|
|
114
|
+
const rb = b;
|
|
115
|
+
for (const k of ka) {
|
|
116
|
+
if (!Object.prototype.hasOwnProperty.call(b, k)) return false;
|
|
117
|
+
if (!deepEqual(a[k], rb[k], seen)) return false;
|
|
118
|
+
}
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
return false;
|
|
140
122
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if (d.open && !d.querySelector("pre")) d.append(el("pre", { text: JSON.stringify(obj, null, 2) }));
|
|
147
|
-
},
|
|
148
|
-
{ once: true }
|
|
149
|
-
);
|
|
150
|
-
return d;
|
|
123
|
+
var AVOIDABLE_KINDS = /* @__PURE__ */ new Set(["deep-equal", "function", "element"]);
|
|
124
|
+
function joinPath(base, key) {
|
|
125
|
+
if (typeof key === "number") return `${base}[${key}]`;
|
|
126
|
+
if (base === "") return key;
|
|
127
|
+
return /^[A-Za-z_$][\w$]*$/.test(key) ? `${base}.${key}` : `${base}[${JSON.stringify(key)}]`;
|
|
151
128
|
}
|
|
152
|
-
function firstDifferentPath(a, b,
|
|
153
|
-
if (
|
|
154
|
-
if (!isRecord(a) || !isRecord(b)) return base || "(value)";
|
|
155
|
-
if (Array.isArray(a) !== Array.isArray(b)) return base || "(value)";
|
|
129
|
+
function firstDifferentPath(a, b, path = "", depth = 0) {
|
|
130
|
+
if (depth > 8) return path || "(value)";
|
|
156
131
|
if (Array.isArray(a) && Array.isArray(b)) {
|
|
157
|
-
if (a.length !== b.length) return
|
|
132
|
+
if (a.length !== b.length) return path ? `${path}.length` : "length";
|
|
158
133
|
for (let i = 0; i < a.length; i++) {
|
|
159
|
-
|
|
160
|
-
if (p) return p;
|
|
134
|
+
if (!deepEqual(a[i], b[i])) return firstDifferentPath(a[i], b[i], joinPath(path, i), depth + 1);
|
|
161
135
|
}
|
|
162
136
|
return null;
|
|
163
137
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
138
|
+
if (isPlainObject(a) && isPlainObject(b)) {
|
|
139
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)]);
|
|
140
|
+
for (const k of keys) {
|
|
141
|
+
if (!deepEqual(a[k], b[k])) return firstDifferentPath(a[k], b[k], joinPath(path, k), depth + 1);
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
168
144
|
}
|
|
169
|
-
return null;
|
|
145
|
+
return deepEqual(a, b) ? null : path || "(value)";
|
|
170
146
|
}
|
|
171
|
-
function diffLeaves(a, b, limit = 20,
|
|
172
|
-
if (out.length >= limit || a
|
|
173
|
-
if (!isRecord(a) || !isRecord(b) || Array.isArray(a) !== Array.isArray(b)) {
|
|
174
|
-
out.push({ path: base || "(value)", prev: a, next: b });
|
|
175
|
-
return out;
|
|
176
|
-
}
|
|
147
|
+
function diffLeaves(a, b, limit = 20, path = "", out = []) {
|
|
148
|
+
if (out.length >= limit || Object.is(a, b)) return out;
|
|
177
149
|
if (Array.isArray(a) && Array.isArray(b)) {
|
|
178
150
|
const n = Math.max(a.length, b.length);
|
|
179
|
-
for (let i = 0; i < n && out.length < limit; i++) diffLeaves(a[i], b[i], limit,
|
|
151
|
+
for (let i = 0; i < n && out.length < limit; i++) diffLeaves(a[i], b[i], limit, joinPath(path, i), out);
|
|
180
152
|
return out;
|
|
181
153
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
154
|
+
if (isPlainObject(a) && isPlainObject(b)) {
|
|
155
|
+
for (const k of /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)])) {
|
|
156
|
+
if (out.length >= limit) break;
|
|
157
|
+
diffLeaves(a[k], b[k], limit, joinPath(path, k), out);
|
|
158
|
+
}
|
|
159
|
+
return out;
|
|
185
160
|
}
|
|
161
|
+
if (!deepEqual(a, b)) out.push({ path: path || "(value)", prev: a, next: b });
|
|
186
162
|
return out;
|
|
187
163
|
}
|
|
164
|
+
|
|
165
|
+
// src/causes.ts
|
|
166
|
+
var isAncestorReport = (anc, r) => anc.path.length < r.path.length && r.path[anc.path.length] === anc.component && anc.path.every((p, i) => r.path[i] === p);
|
|
167
|
+
var bump = (m, key, n = 1) => void m.set(key, (m.get(key) || 0) + n);
|
|
168
|
+
function indexByComponent(reports) {
|
|
169
|
+
const index = /* @__PURE__ */ new Map();
|
|
170
|
+
for (const r of reports) {
|
|
171
|
+
const list = index.get(r.component);
|
|
172
|
+
if (list) list.push(r);
|
|
173
|
+
else index.set(r.component, [r]);
|
|
174
|
+
}
|
|
175
|
+
return index;
|
|
176
|
+
}
|
|
177
|
+
function rootCauseOf(r, commitReports, index = indexByComponent(commitReports)) {
|
|
178
|
+
let cur = r;
|
|
179
|
+
const seen = /* @__PURE__ */ new Set([r]);
|
|
180
|
+
while (cur.trigger === "parent" && cur.parent) {
|
|
181
|
+
const parent = cur.parent;
|
|
182
|
+
const candidates = index.get(parent.name);
|
|
183
|
+
const p = candidates && candidates.find((x) => isAncestorReport(x, cur));
|
|
184
|
+
if (!p || seen.has(p)) return { name: parent.name, trigger: parent.trigger, report: null };
|
|
185
|
+
seen.add(p);
|
|
186
|
+
cur = p;
|
|
187
|
+
}
|
|
188
|
+
return cur === r ? null : { name: cur.component, trigger: cur.trigger, report: cur };
|
|
189
|
+
}
|
|
190
|
+
function rootCausesOf(reports) {
|
|
191
|
+
const roots = /* @__PURE__ */ new Map();
|
|
192
|
+
const rootByReport = /* @__PURE__ */ new Map();
|
|
193
|
+
const index = indexByComponent(reports);
|
|
194
|
+
let avoidable = 0;
|
|
195
|
+
let wasted = 0;
|
|
196
|
+
for (const r of reports) {
|
|
197
|
+
if (!r.avoidable) continue;
|
|
198
|
+
avoidable++;
|
|
199
|
+
if (typeof r.selfDuration === "number") wasted += r.selfDuration;
|
|
200
|
+
const root = rootCauseOf(r, reports, index);
|
|
201
|
+
const name = root ? root.name : r.parent && r.parent.name || "(unknown)";
|
|
202
|
+
const trigger = root ? root.trigger : r.parent && r.parent.trigger || "parent";
|
|
203
|
+
rootByReport.set(r, name);
|
|
204
|
+
let agg = roots.get(name);
|
|
205
|
+
if (!agg) {
|
|
206
|
+
agg = { name, trigger, count: 0, components: /* @__PURE__ */ new Map() };
|
|
207
|
+
roots.set(name, agg);
|
|
208
|
+
}
|
|
209
|
+
agg.count++;
|
|
210
|
+
bump(agg.components, r.component);
|
|
211
|
+
}
|
|
212
|
+
return { roots: [...roots.values()].sort((a, b) => b.count - a.count), rootByReport, avoidable, wasted };
|
|
213
|
+
}
|
|
214
|
+
function analyzeCommit(reports) {
|
|
215
|
+
const first = reports[0];
|
|
216
|
+
return {
|
|
217
|
+
id: first && typeof first.commitId === "number" ? first.commitId : 0,
|
|
218
|
+
total: reports.length,
|
|
219
|
+
...rootCausesOf(reports),
|
|
220
|
+
contexts: contextAttribution(reports),
|
|
221
|
+
reports
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
function contextAttribution(reports) {
|
|
225
|
+
const byCtx = /* @__PURE__ */ new Map();
|
|
226
|
+
for (const r of reports) {
|
|
227
|
+
for (const c of r.hookChanges || []) {
|
|
228
|
+
if (c.hook !== "useContext" && !/^useContext/.test(c.path)) continue;
|
|
229
|
+
const m = /useContext\((.*)\)/.exec(c.path);
|
|
230
|
+
const name = m && m[1] ? m[1] : c.path;
|
|
231
|
+
let agg = byCtx.get(name);
|
|
232
|
+
if (!agg) {
|
|
233
|
+
agg = { name, consumers: 0, avoidable: 0, components: /* @__PURE__ */ new Map(), commits: /* @__PURE__ */ new Set(), providers: /* @__PURE__ */ new Map(), changedKeys: /* @__PURE__ */ new Set(), totalKeys: 0 };
|
|
234
|
+
byCtx.set(name, agg);
|
|
235
|
+
}
|
|
236
|
+
agg.consumers++;
|
|
237
|
+
if (AVOIDABLE_KINDS.has(c.kind)) agg.avoidable++;
|
|
238
|
+
bump(agg.components, r.component);
|
|
239
|
+
agg.commits.add(typeof r.commitId === "number" ? r.commitId : 0);
|
|
240
|
+
if (c.provider && c.provider.component) bump(agg.providers, c.provider.component);
|
|
241
|
+
if (c.changedKeys) for (const k of c.changedKeys) agg.changedKeys.add(k);
|
|
242
|
+
if (typeof c.totalKeys === "number") agg.totalKeys = Math.max(agg.totalKeys, c.totalKeys);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return [...byCtx.values()].sort((a, b) => b.consumers - a.consumers);
|
|
246
|
+
}
|
|
247
|
+
function cascadeTree(reports) {
|
|
248
|
+
const root = { name: "", children: /* @__PURE__ */ new Map(), report: null };
|
|
249
|
+
for (const r of reports) {
|
|
250
|
+
let node = root;
|
|
251
|
+
for (const seg of r.path.concat([r.component])) {
|
|
252
|
+
let next = node.children.get(seg);
|
|
253
|
+
if (!next) {
|
|
254
|
+
next = { name: seg, children: /* @__PURE__ */ new Map(), report: null };
|
|
255
|
+
node.children.set(seg, next);
|
|
256
|
+
}
|
|
257
|
+
node = next;
|
|
258
|
+
}
|
|
259
|
+
if (!node.report || r.avoidable) node.report = r;
|
|
260
|
+
node.count = (node.count || 0) + 1;
|
|
261
|
+
if (r.avoidable) node.avoidable = (node.avoidable || 0) + 1;
|
|
262
|
+
}
|
|
263
|
+
return root;
|
|
264
|
+
}
|
|
265
|
+
function rootCauseSummary(name, commits, analyze) {
|
|
266
|
+
const analyzeFn = analyze || ((_, reports) => analyzeCommit(reports));
|
|
267
|
+
const out = { name, trigger: "parent", commits: [], total: 0, components: /* @__PURE__ */ new Map(), affected: [] };
|
|
268
|
+
for (const [key, reports] of commits) {
|
|
269
|
+
const analysis = analyzeFn(key, reports);
|
|
270
|
+
const root = analysis.roots.find((x) => x.name === name);
|
|
271
|
+
if (!root) continue;
|
|
272
|
+
out.trigger = root.trigger;
|
|
273
|
+
out.commits.push({ key, analysis, count: root.count, components: root.components });
|
|
274
|
+
out.total += root.count;
|
|
275
|
+
for (const [c, n] of root.components) bump(out.components, c, n);
|
|
276
|
+
for (const r of reports) if (r.avoidable && analysis.rootByReport.get(r) === name) out.affected.push(r);
|
|
277
|
+
}
|
|
278
|
+
out.commits.reverse();
|
|
279
|
+
return out;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// src/fixes.ts
|
|
283
|
+
var MAX_FIX_REPORTS = 50;
|
|
284
|
+
var rootOf = (path) => path.split(/[.[]/)[0] || path;
|
|
285
|
+
var identifier = (name) => /^[A-Za-z_$][\w$]*$/.test(name) ? name : "value";
|
|
188
286
|
function shortValue(v, max = 60) {
|
|
189
287
|
let s;
|
|
190
288
|
try {
|
|
@@ -195,12 +293,11 @@
|
|
|
195
293
|
if (s === void 0) s = String(v);
|
|
196
294
|
return s.length > max ? s.slice(0, max - 3) + "..." : s;
|
|
197
295
|
}
|
|
198
|
-
var identifier = (name) => /^[A-Za-z_$][\w$]*$/.test(name) ? name : "value";
|
|
199
296
|
function fixesFor(r) {
|
|
200
297
|
const out = [];
|
|
201
298
|
const ownerName = r.owner || r.parent && r.parent.name || null;
|
|
202
|
-
const
|
|
203
|
-
const
|
|
299
|
+
const propChanges = r.propChanges || [];
|
|
300
|
+
const changes = [...propChanges, ...r.stateChanges || [], ...r.hookChanges || []];
|
|
204
301
|
if (r.avoidable && (changes.length === 0 || r.memoized === false)) {
|
|
205
302
|
const identical = changes.length === 0;
|
|
206
303
|
out.push({
|
|
@@ -219,9 +316,10 @@ export const ${r.component} = memo(function ${r.component}(props) {
|
|
|
219
316
|
// class components: extend PureComponent instead`
|
|
220
317
|
});
|
|
221
318
|
}
|
|
222
|
-
for (const c of
|
|
319
|
+
for (const c of propChanges) {
|
|
320
|
+
if (!AVOIDABLE_KINDS.has(c.kind)) continue;
|
|
223
321
|
const owner = ownerName || "?";
|
|
224
|
-
const root = c.path
|
|
322
|
+
const root = rootOf(c.path);
|
|
225
323
|
const id = identifier(root);
|
|
226
324
|
if (root === "children" && (c.kind === "element" || c.kind === "deep-equal")) {
|
|
227
325
|
out.push({
|
|
@@ -243,9 +341,7 @@ const children = useMemo(() => (
|
|
|
243
341
|
// static children: hoist them to module scope
|
|
244
342
|
const STATIC = <em>hi</em>;`
|
|
245
343
|
});
|
|
246
|
-
|
|
247
|
-
}
|
|
248
|
-
if (c.kind === "function") {
|
|
344
|
+
} else if (c.kind === "function") {
|
|
249
345
|
out.push({
|
|
250
346
|
kind: "useCallback",
|
|
251
347
|
owner,
|
|
@@ -295,7 +391,7 @@ const ${id.toUpperCase()} = ${shortValue(c.next, 80)};`
|
|
|
295
391
|
});
|
|
296
392
|
}
|
|
297
393
|
}
|
|
298
|
-
for (const c of [
|
|
394
|
+
for (const c of [...r.stateChanges || [], ...r.hookChanges || []]) {
|
|
299
395
|
const isContext = c.hook === "useContext" || /^useContext/.test(c.path);
|
|
300
396
|
const ctxName = isContext ? (/useContext\((.*)\)/.exec(c.path) || [])[1] || "Context" : "";
|
|
301
397
|
const providerOwner = isContext && c.provider && c.provider.component ? c.provider.component : null;
|
|
@@ -319,17 +415,16 @@ const ${c.changedKeys[0]} = useContextSelector(${ctxName}, (v) => v.${c.changedK
|
|
|
319
415
|
}
|
|
320
416
|
if (!AVOIDABLE_KINDS.has(c.kind)) continue;
|
|
321
417
|
if (isContext) {
|
|
322
|
-
const name = ctxName;
|
|
323
418
|
out.push({
|
|
324
419
|
kind: "contextValue",
|
|
325
|
-
owner: providerOwner || `${
|
|
420
|
+
owner: providerOwner || `${ctxName}.Provider`,
|
|
326
421
|
target: r.component,
|
|
327
|
-
prop:
|
|
328
|
-
label: `memoize the ${
|
|
329
|
-
detail: `<${r.component}> re-rendered because ${
|
|
330
|
-
snippet: `// ${providerOwner || `where <${
|
|
422
|
+
prop: ctxName,
|
|
423
|
+
label: `memoize the ${ctxName} provider value${providerOwner ? ` in <${providerOwner}>` : ""}`,
|
|
424
|
+
detail: `<${r.component}> re-rendered because ${ctxName} produced a new value that is deep-equal to the previous one${providerOwner ? ` (Provider rendered by <${providerOwner}>)` : ""}.`,
|
|
425
|
+
snippet: `// ${providerOwner || `where <${ctxName}.Provider> is rendered`}
|
|
331
426
|
const value = useMemo(() => ({ /* ... */ }), [/* deps */]);
|
|
332
|
-
<${
|
|
427
|
+
<${ctxName}.Provider value={value}>`
|
|
333
428
|
});
|
|
334
429
|
} else if (c.hook === "useSyncExternalStore") {
|
|
335
430
|
const chain = c.custom || [];
|
|
@@ -374,137 +469,41 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
374
469
|
for (const r of reports) {
|
|
375
470
|
if (!r.avoidable) continue;
|
|
376
471
|
for (const f of fixesFor(r)) {
|
|
377
|
-
const
|
|
378
|
-
let agg = byKey.get(
|
|
472
|
+
const key = fixKey(f);
|
|
473
|
+
let agg = byKey.get(key);
|
|
379
474
|
if (!agg) {
|
|
380
|
-
agg = { ...f, key
|
|
381
|
-
byKey.set(
|
|
475
|
+
agg = { ...f, key, count: 0, components: {}, reports: [] };
|
|
476
|
+
byKey.set(key, agg);
|
|
382
477
|
}
|
|
383
478
|
agg.count++;
|
|
384
|
-
agg.components
|
|
385
|
-
if (agg.reports.length <
|
|
479
|
+
agg.components[r.component] = (agg.components[r.component] || 0) + 1;
|
|
480
|
+
if (agg.reports.length < MAX_FIX_REPORTS) agg.reports.push(r);
|
|
386
481
|
}
|
|
387
482
|
}
|
|
388
483
|
return [...byKey.values()].sort((a, b) => b.count - a.count || a.label.localeCompare(b.label));
|
|
389
484
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
}
|
|
400
|
-
function
|
|
401
|
-
|
|
402
|
-
const
|
|
403
|
-
|
|
404
|
-
const parent = cur.parent;
|
|
405
|
-
const candidates = index.get(parent.name);
|
|
406
|
-
const p = candidates && candidates.find((x) => isAncestorReport(x, cur));
|
|
407
|
-
if (!p || seen.has(p)) return { name: parent.name, trigger: parent.trigger, report: null };
|
|
408
|
-
seen.add(p);
|
|
409
|
-
cur = p;
|
|
410
|
-
}
|
|
411
|
-
return cur === r ? null : { name: cur.component, trigger: cur.trigger, report: cur };
|
|
412
|
-
}
|
|
413
|
-
function analyzeCommit(reports) {
|
|
414
|
-
const roots = /* @__PURE__ */ new Map();
|
|
415
|
-
const rootByReport = /* @__PURE__ */ new Map();
|
|
416
|
-
const index = indexByComponent(reports);
|
|
417
|
-
let avoidable = 0;
|
|
418
|
-
let wasted = 0;
|
|
419
|
-
for (const r of reports) {
|
|
420
|
-
if (!r.avoidable) continue;
|
|
421
|
-
avoidable++;
|
|
422
|
-
if (typeof r.selfDuration === "number") wasted += r.selfDuration;
|
|
423
|
-
const root = rootCauseOf(r, reports, index);
|
|
424
|
-
const name = root ? root.name : r.parent && r.parent.name || "(unknown)";
|
|
425
|
-
const trigger = root ? root.trigger : r.parent && r.parent.trigger || "parent";
|
|
426
|
-
rootByReport.set(r, name);
|
|
427
|
-
let agg = roots.get(name);
|
|
428
|
-
if (!agg) {
|
|
429
|
-
agg = { name, trigger, count: 0, components: /* @__PURE__ */ new Map() };
|
|
430
|
-
roots.set(name, agg);
|
|
431
|
-
}
|
|
432
|
-
agg.count++;
|
|
433
|
-
agg.components.set(r.component, (agg.components.get(r.component) || 0) + 1);
|
|
434
|
-
}
|
|
435
|
-
const first = reports[0];
|
|
436
|
-
return {
|
|
437
|
-
id: first ? first.commitId : 0,
|
|
438
|
-
receivedAt: first ? first.receivedAt : 0,
|
|
439
|
-
total: reports.length,
|
|
440
|
-
avoidable,
|
|
441
|
-
wasted,
|
|
442
|
-
roots: [...roots.values()].sort((a, b) => b.count - a.count),
|
|
443
|
-
rootByReport,
|
|
444
|
-
contexts: contextAttribution(reports),
|
|
445
|
-
fixes: rankFixes(reports),
|
|
446
|
-
reports
|
|
447
|
-
};
|
|
448
|
-
}
|
|
449
|
-
function contextAttribution(reports) {
|
|
450
|
-
const byCtx = /* @__PURE__ */ new Map();
|
|
451
|
-
for (const r of reports) {
|
|
452
|
-
for (const c of r.hookChanges || []) {
|
|
453
|
-
if (c.hook !== "useContext" && !/^useContext/.test(c.path)) continue;
|
|
454
|
-
const m = /useContext\((.*)\)/.exec(c.path);
|
|
455
|
-
const name = m && m[1] ? m[1] : c.path;
|
|
456
|
-
let agg = byCtx.get(name);
|
|
457
|
-
if (!agg) {
|
|
458
|
-
agg = { name, consumers: 0, avoidable: 0, components: /* @__PURE__ */ new Map(), commits: /* @__PURE__ */ new Set(), providers: /* @__PURE__ */ new Map(), changedKeys: /* @__PURE__ */ new Set(), totalKeys: 0 };
|
|
459
|
-
byCtx.set(name, agg);
|
|
460
|
-
}
|
|
461
|
-
agg.consumers++;
|
|
462
|
-
if (AVOIDABLE_KINDS.has(c.kind)) agg.avoidable++;
|
|
463
|
-
agg.components.set(r.component, (agg.components.get(r.component) || 0) + 1);
|
|
464
|
-
agg.commits.add(r.commitId);
|
|
465
|
-
if (c.provider && c.provider.component) agg.providers.set(c.provider.component, (agg.providers.get(c.provider.component) || 0) + 1);
|
|
466
|
-
if (c.changedKeys) for (const k of c.changedKeys) agg.changedKeys.add(k);
|
|
467
|
-
if (typeof c.totalKeys === "number") agg.totalKeys = Math.max(agg.totalKeys, c.totalKeys);
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
return [...byCtx.values()].sort((a, b) => b.consumers - a.consumers);
|
|
471
|
-
}
|
|
472
|
-
function cascadeTree(reports) {
|
|
473
|
-
const root = { name: "", children: /* @__PURE__ */ new Map(), report: null };
|
|
474
|
-
for (const r of reports) {
|
|
475
|
-
let node = root;
|
|
476
|
-
for (const seg of r.path.concat([r.component])) {
|
|
477
|
-
let next = node.children.get(seg);
|
|
478
|
-
if (!next) {
|
|
479
|
-
next = { name: seg, children: /* @__PURE__ */ new Map(), report: null };
|
|
480
|
-
node.children.set(seg, next);
|
|
481
|
-
}
|
|
482
|
-
node = next;
|
|
483
|
-
}
|
|
484
|
-
if (!node.report || r.avoidable) node.report = r;
|
|
485
|
-
node.count = (node.count || 0) + 1;
|
|
486
|
-
if (r.avoidable) node.avoidable = (node.avoidable || 0) + 1;
|
|
487
|
-
}
|
|
488
|
-
return root;
|
|
489
|
-
}
|
|
490
|
-
function rootCauseSummary(name, commits, analyze = (_, reports) => analyzeCommit(reports)) {
|
|
491
|
-
const out = { name, trigger: "parent", commits: [], total: 0, components: /* @__PURE__ */ new Map(), fixes: [] };
|
|
492
|
-
const affected = [];
|
|
493
|
-
for (const [key, reports] of commits) {
|
|
494
|
-
const analysis = analyze(key, reports);
|
|
495
|
-
const root = analysis.roots.find((x) => x.name === name);
|
|
496
|
-
if (!root) continue;
|
|
497
|
-
out.trigger = root.trigger;
|
|
498
|
-
out.commits.push({ key, analysis, count: root.count, components: root.components });
|
|
499
|
-
out.total += root.count;
|
|
500
|
-
for (const [c, n] of root.components) out.components.set(c, (out.components.get(c) || 0) + n);
|
|
501
|
-
for (const r of reports) if (r.avoidable && analysis.rootByReport.get(r) === name) affected.push(r);
|
|
485
|
+
|
|
486
|
+
// src/report.ts
|
|
487
|
+
var KIND_LABEL = {
|
|
488
|
+
"deep-equal": "equal by value",
|
|
489
|
+
function: "new function",
|
|
490
|
+
element: "equal element",
|
|
491
|
+
different: "changed",
|
|
492
|
+
added: "added",
|
|
493
|
+
removed: "removed"
|
|
494
|
+
};
|
|
495
|
+
function summarize(report) {
|
|
496
|
+
const counts = /* @__PURE__ */ new Map();
|
|
497
|
+
for (const c of [...report.propChanges || [], ...report.stateChanges || [], ...report.hookChanges || []]) {
|
|
498
|
+
counts.set(c.kind, (counts.get(c.kind) ?? 0) + 1);
|
|
502
499
|
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
return
|
|
500
|
+
const parts = [...counts].map(([kind, n]) => `${n} ${KIND_LABEL[kind] || kind}`);
|
|
501
|
+
if (parts.length === 0) parts.push("no changes");
|
|
502
|
+
return parts.join(", ");
|
|
506
503
|
}
|
|
507
|
-
|
|
504
|
+
|
|
505
|
+
// src/sessions.ts
|
|
506
|
+
function summarizeReports(reports, meta = {}) {
|
|
508
507
|
var _a;
|
|
509
508
|
const byComponent = {};
|
|
510
509
|
let avoidable = 0;
|
|
@@ -522,10 +521,10 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
522
521
|
}
|
|
523
522
|
}
|
|
524
523
|
return {
|
|
525
|
-
id:
|
|
526
|
-
name:
|
|
527
|
-
startedAt:
|
|
528
|
-
endedAt:
|
|
524
|
+
id: meta.id ?? `s${Date.now().toString(36)}`,
|
|
525
|
+
name: meta.name ?? "session",
|
|
526
|
+
startedAt: meta.startedAt ?? (reports[0]?.time ?? 0),
|
|
527
|
+
endedAt: meta.endedAt ?? (reports[reports.length - 1]?.time ?? null),
|
|
529
528
|
total: reports.length,
|
|
530
529
|
avoidable,
|
|
531
530
|
wasted,
|
|
@@ -533,7 +532,7 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
533
532
|
fixes: rankFixes(reports).map((f) => ({ key: f.key, label: f.label, count: f.count }))
|
|
534
533
|
};
|
|
535
534
|
}
|
|
536
|
-
function
|
|
535
|
+
function compareSummaries(before, after) {
|
|
537
536
|
const names = /* @__PURE__ */ new Set([...Object.keys(before.byComponent), ...Object.keys(after.byComponent)]);
|
|
538
537
|
const rows = [];
|
|
539
538
|
for (const component of names) {
|
|
@@ -552,8 +551,155 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
552
551
|
avoidable: { before: before.avoidable, after: after.avoidable, delta: after.avoidable - before.avoidable },
|
|
553
552
|
wasted: { before: before.wasted, after: after.wasted, delta: after.wasted - before.wasted },
|
|
554
553
|
resolvedFixes: before.fixes.filter((f) => !afterKeys.has(f.key)),
|
|
555
|
-
newFixes: after.fixes.filter((f) => !beforeKeys.has(f.key))
|
|
554
|
+
newFixes: after.fixes.filter((f) => !beforeKeys.has(f.key)),
|
|
555
|
+
regressions: rows.filter((r) => r.delta > 0)
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
var summarizeSession = (session, reports) => summarizeReports(reports, session);
|
|
559
|
+
var compareSessions = compareSummaries;
|
|
560
|
+
|
|
561
|
+
// extension/src/panel.ts
|
|
562
|
+
var PROTOCOL = 2;
|
|
563
|
+
var FN_PREFIX = "\u0192 ";
|
|
564
|
+
var MAX_REPORTS = 2e3;
|
|
565
|
+
var MAX_PER_NODE = 200;
|
|
566
|
+
var MAX_COMMITS = 500;
|
|
567
|
+
var MAX_PER_COMMIT = 500;
|
|
568
|
+
var THROTTLE_MIN_REPORTS = 200;
|
|
569
|
+
var BEST_FIX_INTERVAL = 500;
|
|
570
|
+
var LEFT_RENDER_INTERVAL = 250;
|
|
571
|
+
var SYNC_RETRY_MIN = 500;
|
|
572
|
+
var SYNC_RETRY_MAX = 5e3;
|
|
573
|
+
var SYNC_RETRIES = 20;
|
|
574
|
+
var NAVIGATION_SETTLE = 1200;
|
|
575
|
+
var INFO_REFRESH_MS = 3e3;
|
|
576
|
+
var ROW_H = 22;
|
|
577
|
+
var ITEM_H = 20;
|
|
578
|
+
var OVERSCAN = 8;
|
|
579
|
+
var FALLBACK_VIEWPORT = 800;
|
|
580
|
+
function el(tag, attrs, children) {
|
|
581
|
+
const node = document.createElement(tag);
|
|
582
|
+
if (attrs) {
|
|
583
|
+
for (const k of Object.keys(attrs)) {
|
|
584
|
+
const v = attrs[k];
|
|
585
|
+
if (k === "class") node.className = String(v);
|
|
586
|
+
else if (k === "text") node.textContent = String(v);
|
|
587
|
+
else if (k.startsWith("on")) {
|
|
588
|
+
if (typeof v === "function") node.addEventListener(k.slice(2), v);
|
|
589
|
+
} else if (v === true) node.setAttribute(k, "");
|
|
590
|
+
else if (v !== void 0 && v !== null && v !== false) node.setAttribute(k, String(v));
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
if (children) {
|
|
594
|
+
for (const c of [].concat(children)) if (c != null) node.append(c);
|
|
595
|
+
}
|
|
596
|
+
return node;
|
|
597
|
+
}
|
|
598
|
+
function fmtTime(ms) {
|
|
599
|
+
const d = new Date(ms);
|
|
600
|
+
const p = (n, w) => String(n).padStart(w, "0");
|
|
601
|
+
return `${p(d.getHours(), 2)}:${p(d.getMinutes(), 2)}:${p(d.getSeconds(), 2)}.${p(d.getMilliseconds(), 3)}`;
|
|
602
|
+
}
|
|
603
|
+
var fmtMs = (n) => typeof n === "number" && Number.isFinite(n) ? `${n.toFixed(1)} ms` : "";
|
|
604
|
+
var plural = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`;
|
|
605
|
+
var componentList = (m) => (m instanceof Map ? [...m] : Object.entries(m)).map(([c, n]) => `<${c}>${n > 1 ? " \xD7" + n : ""}`).join(", ");
|
|
606
|
+
function changesOf(report) {
|
|
607
|
+
return [].concat(report.propChanges || [], report.stateChanges || [], report.hookChanges || []);
|
|
608
|
+
}
|
|
609
|
+
var isRecord = (v) => typeof v === "object" && v !== null;
|
|
610
|
+
function normalizeReport(p) {
|
|
611
|
+
if (!isRecord(p) || typeof p.component !== "string") return null;
|
|
612
|
+
const arr = (x) => Array.isArray(x) ? x.filter((c) => isRecord(c) && typeof c.path === "string") : [];
|
|
613
|
+
const props = isRecord(p.props) ? p.props : {};
|
|
614
|
+
const parent = isRecord(p.parent) && typeof p.parent.name === "string" ? { name: p.parent.name, trigger: typeof p.parent.trigger === "string" ? p.parent.trigger : "parent" } : null;
|
|
615
|
+
const r = {
|
|
616
|
+
component: p.component,
|
|
617
|
+
instanceId: typeof p.instanceId === "number" ? p.instanceId : 0,
|
|
618
|
+
commitId: typeof p.commitId === "number" ? p.commitId : 0,
|
|
619
|
+
renderCount: typeof p.renderCount === "number" ? p.renderCount : 0,
|
|
620
|
+
trigger: typeof p.trigger === "string" ? p.trigger : "parent",
|
|
621
|
+
avoidable: !!p.avoidable,
|
|
622
|
+
props: { prev: isRecord(props.prev) ? props.prev : {}, next: isRecord(props.next) ? props.next : {} },
|
|
623
|
+
propChanges: arr(p.propChanges),
|
|
624
|
+
stateChanges: arr(p.stateChanges),
|
|
625
|
+
hookChanges: arr(p.hookChanges),
|
|
626
|
+
parent,
|
|
627
|
+
owner: typeof p.owner === "string" ? p.owner : null,
|
|
628
|
+
path: Array.isArray(p.path) ? p.path.filter((x) => typeof x === "string") : [],
|
|
629
|
+
reasons: Array.isArray(p.reasons) ? p.reasons.filter((x) => typeof x === "string") : [],
|
|
630
|
+
time: typeof p.time === "number" ? p.time : 0,
|
|
631
|
+
receivedAt: typeof p.receivedAt === "number" ? p.receivedAt : 0
|
|
556
632
|
};
|
|
633
|
+
if (typeof p.selfDuration === "number") r.selfDuration = p.selfDuration;
|
|
634
|
+
if (typeof p.treeDuration === "number") r.treeDuration = p.treeDuration;
|
|
635
|
+
if (typeof p.memoized === "boolean") r.memoized = p.memoized;
|
|
636
|
+
if (p.compiled === true) r.compiled = true;
|
|
637
|
+
if (typeof p.commitPriority === "string") r.commitPriority = p.commitPriority;
|
|
638
|
+
if (Array.isArray(p.hookState)) r.hookState = p.hookState.filter((h) => isRecord(h) && typeof h.path === "string");
|
|
639
|
+
if (Array.isArray(p.contexts)) r.contexts = p.contexts.filter((c) => isRecord(c) && typeof c.name === "string");
|
|
640
|
+
if (isRecord(p.state)) r.state = p.state;
|
|
641
|
+
if (Array.isArray(p.updaters)) r.updaters = p.updaters.filter((u) => typeof u === "string");
|
|
642
|
+
if (p.commitCause === "effect-after-commit" || p.commitCause === "suspense-resolved") r.commitCause = p.commitCause;
|
|
643
|
+
if (typeof p.afterCommit === "number") r.afterCommit = p.afterCommit;
|
|
644
|
+
if (typeof p.key === "string") r.key = p.key;
|
|
645
|
+
if (isRecord(p.source) && typeof p.source.fileName === "string") r.source = p.source;
|
|
646
|
+
return r;
|
|
647
|
+
}
|
|
648
|
+
function valueNode(v, depth = 0) {
|
|
649
|
+
if (v === null || v === void 0) return el("span", { class: "v nil", text: String(v) });
|
|
650
|
+
const t = typeof v;
|
|
651
|
+
if (typeof v === "string") {
|
|
652
|
+
if (v.startsWith(FN_PREFIX)) return el("span", { class: "v fn", text: v });
|
|
653
|
+
if (/^<[^>]+>$/.test(v)) return el("span", { class: "v", text: v });
|
|
654
|
+
return el("span", { class: "v str", text: JSON.stringify(v) });
|
|
655
|
+
}
|
|
656
|
+
if (t === "number" || t === "bigint") return el("span", { class: "v num", text: String(v) });
|
|
657
|
+
if (t === "boolean") return el("span", { class: "v bool", text: String(v) });
|
|
658
|
+
if (Array.isArray(v)) {
|
|
659
|
+
const short = v.length <= 4 && v.every((x) => typeof x !== "object" || x === null);
|
|
660
|
+
if (short) {
|
|
661
|
+
const s = el("span", { class: "v" }, "[");
|
|
662
|
+
v.forEach((x, i) => {
|
|
663
|
+
if (i) s.append(", ");
|
|
664
|
+
s.append(valueNode(x, depth + 1));
|
|
665
|
+
});
|
|
666
|
+
s.append("]");
|
|
667
|
+
return s;
|
|
668
|
+
}
|
|
669
|
+
return objectDetails(`Array(${v.length})`, v);
|
|
670
|
+
}
|
|
671
|
+
const o = v;
|
|
672
|
+
if (o.$type === "element") {
|
|
673
|
+
const props = isRecord(o.props) ? o.props : {};
|
|
674
|
+
const label2 = `<${String(o.name)}${o.key !== void 0 ? ` key=${JSON.stringify(o.key)}` : ""}>`;
|
|
675
|
+
return Object.keys(props).length ? objectDetails(label2, props) : el("span", { class: "v", text: label2 });
|
|
676
|
+
}
|
|
677
|
+
if (o.$type === "Date") return el("span", { class: "v", text: `Date(${String(o.value)})` });
|
|
678
|
+
if (o.$type === "RegExp") return el("span", { class: "v", text: String(o.value) });
|
|
679
|
+
if (o.$type === "Map" && Array.isArray(o.entries)) return objectDetails(`Map(${o.entries.length})`, o.entries);
|
|
680
|
+
if (o.$type === "Set" && Array.isArray(o.values)) return objectDetails(`Set(${o.values.length})`, o.values);
|
|
681
|
+
const keys = Object.keys(o).filter((k) => k !== "$type");
|
|
682
|
+
const label = `${o.$type ? String(o.$type) + " " : ""}{${keys.slice(0, 3).join(", ")}${keys.length > 3 ? ", ..." : ""}}`;
|
|
683
|
+
return objectDetails(label, o);
|
|
684
|
+
}
|
|
685
|
+
function objectDetails(label, obj) {
|
|
686
|
+
const d = el("details", { class: "obj" }, [el("summary", { text: label })]);
|
|
687
|
+
d.addEventListener(
|
|
688
|
+
"toggle",
|
|
689
|
+
() => {
|
|
690
|
+
if (d.open && !d.querySelector("pre")) d.append(el("pre", { text: JSON.stringify(obj, null, 2) }));
|
|
691
|
+
},
|
|
692
|
+
{ once: true }
|
|
693
|
+
);
|
|
694
|
+
return d;
|
|
695
|
+
}
|
|
696
|
+
function analyzeCommit2(reports) {
|
|
697
|
+
const first = reports[0];
|
|
698
|
+
return { ...analyzeCommit(reports), receivedAt: first ? first.receivedAt : 0, fixes: rankFixes(reports) };
|
|
699
|
+
}
|
|
700
|
+
function rootCauseSummary2(name, commits, analyze = (_, reports) => analyzeCommit2(reports)) {
|
|
701
|
+
const s = rootCauseSummary(name, commits, analyze);
|
|
702
|
+
return { ...s, fixes: rankFixes(s.affected) };
|
|
557
703
|
}
|
|
558
704
|
var PRIORITY_LABEL = { immediate: "discrete input", "user-blocking": "continuous input", normal: "transition / async", low: "low", idle: "idle" };
|
|
559
705
|
function sourceContext(text, line, around = 3) {
|
|
@@ -710,7 +856,8 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
710
856
|
if (r.parent) by.append(el("div", { text: `Triggered by <${r.parent.name}> (${r.parent.trigger})` }));
|
|
711
857
|
else by.append(el("div", { text: "Update started in this component" }));
|
|
712
858
|
if (r.owner) by.append(el("div", { class: "meta", text: `Created by <${r.owner}>` }));
|
|
713
|
-
if (r.
|
|
859
|
+
if (r.compiled) by.append(el("div", { class: "meta", text: "Compiled by React Compiler (output memoized per input; identical inputs make the render cheap)" }));
|
|
860
|
+
else if (r.memoized === false) by.append(el("div", { class: "meta", text: "Not memoized (re-renders whenever its parent does)" }));
|
|
714
861
|
else if (r.memoized === true) by.append(el("div", { class: "meta", text: "Memoized (React.memo / PureComponent)" }));
|
|
715
862
|
if (r.updaters && r.updaters.length) by.append(el("div", { text: `Update scheduled by ${r.updaters.map((u) => `<${u}>`).join(", ")}` }));
|
|
716
863
|
if (r.commitCause === "effect-after-commit") by.append(el("div", { class: "cause effect", text: `Effect loop: state set right after commit #${r.afterCommit ?? "?"}` }));
|
|
@@ -1250,7 +1397,7 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
1250
1397
|
function analysisFor(key, reports) {
|
|
1251
1398
|
const cached = commitAnalyses.get(key);
|
|
1252
1399
|
if (cached && cached.len === reports.length) return cached.analysis;
|
|
1253
|
-
const analysis =
|
|
1400
|
+
const analysis = analyzeCommit2(reports);
|
|
1254
1401
|
commitAnalyses.set(key, { len: reports.length, analysis });
|
|
1255
1402
|
return analysis;
|
|
1256
1403
|
}
|
|
@@ -1981,7 +2128,7 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
1981
2128
|
}
|
|
1982
2129
|
function renderRootDetails() {
|
|
1983
2130
|
const name = state.selectedRoot;
|
|
1984
|
-
const s =
|
|
2131
|
+
const s = rootCauseSummary2(name, state.commits, analysisFor);
|
|
1985
2132
|
details.append(
|
|
1986
2133
|
el("div", { class: "details-header" }, [
|
|
1987
2134
|
el("span", { class: "title" }, ["Root cause ", el("span", { class: "name", text: `<${name}>` })]),
|
|
@@ -2434,6 +2581,33 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
2434
2581
|
const max = el("input", { type: "number", min: "0", value: String(current.maxReportsPerComponent || 0) });
|
|
2435
2582
|
max.addEventListener("change", () => void applyOptions({ maxReportsPerComponent: Math.max(0, Number(max.value) || 0) }));
|
|
2436
2583
|
sec.append(el("label", { class: "opt col" }, ["Stop printing a component after N reports (0 = never)", max]));
|
|
2584
|
+
const help = el("div", { class: "section" }, [el("h3", { text: "Help" })]);
|
|
2585
|
+
help.append(
|
|
2586
|
+
el("div", { class: "meta", text: "Opens a GitHub issue prefilled with versions, options and counts. No report contents are included; attach an Export if it helps." }),
|
|
2587
|
+
el("a", { class: "report-link", href: issueUrl(), target: "_blank", rel: "noreferrer", text: "Report a problem \u2197" })
|
|
2588
|
+
);
|
|
2589
|
+
body.append(help);
|
|
2590
|
+
}
|
|
2591
|
+
function issueUrl() {
|
|
2592
|
+
const lib = state.library;
|
|
2593
|
+
const ext = typeof chrome !== "undefined" && chrome.runtime && typeof chrome.runtime.getManifest === "function" ? chrome.runtime.getManifest().version : "n/a";
|
|
2594
|
+
const react = lib?.react?.map((r) => `${r.version || "?"}${r.bundleType === 0 ? " (prod)" : ""}`).join(", ") || "not detected";
|
|
2595
|
+
const transportLabel = state.tabLabel || (state.relay ? "content script" : state.polling ? "polling" : "none");
|
|
2596
|
+
const lines = [
|
|
2597
|
+
"### What happened",
|
|
2598
|
+
"",
|
|
2599
|
+
"(what you did, what you expected, what the panel showed)",
|
|
2600
|
+
"",
|
|
2601
|
+
"### Environment",
|
|
2602
|
+
`- extension ${ext}, panel protocol ${PROTOCOL}, transport: ${transportLabel}`,
|
|
2603
|
+
`- library ${lib?.library || "not detected"} (protocol ${lib?.protocol ?? "?"}), React ${react}${lib?.production ? ", production build" : ""}`,
|
|
2604
|
+
`- options: ${JSON.stringify(lib?.options || {})}`,
|
|
2605
|
+
`- overhead: ${lib?.overhead ? `${lib.overhead.totalMs.toFixed(1)} ms over ${lib.commits ?? 0} commits, worst ${lib.overhead.maxCommitMs.toFixed(1)} ms` : "n/a"}; reports skipped by the cap: ${lib?.truncated ?? 0}`,
|
|
2606
|
+
`- buffered: ${state.reports.length} reports, ${totals.avoidable} avoidable; top fix: ${bestFix ? bestFix.label : "none"}`,
|
|
2607
|
+
`- browser: ${typeof navigator !== "undefined" ? navigator.userAgent : "n/a"}`
|
|
2608
|
+
];
|
|
2609
|
+
const params2 = new URLSearchParams({ title: "Panel: ", body: lines.join("\n"), labels: "bug" });
|
|
2610
|
+
return `https://github.com/NexaLeaf/rerender-lens/issues/new?${params2.toString()}`;
|
|
2437
2611
|
}
|
|
2438
2612
|
function handle(message) {
|
|
2439
2613
|
if (!isRecord(message)) return;
|
|
@@ -2590,12 +2764,28 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
2590
2764
|
const info = await io.bridge("info");
|
|
2591
2765
|
if (info) {
|
|
2592
2766
|
emit({ type: "hello", version: info.protocol, payload: info });
|
|
2767
|
+
startInfoRefresh();
|
|
2593
2768
|
return true;
|
|
2594
2769
|
}
|
|
2595
2770
|
} catch {
|
|
2596
2771
|
}
|
|
2597
2772
|
return false;
|
|
2598
2773
|
}
|
|
2774
|
+
let infoTimer = null;
|
|
2775
|
+
function startInfoRefresh() {
|
|
2776
|
+
if (infoTimer) return;
|
|
2777
|
+
infoTimer = setInterval(() => {
|
|
2778
|
+
if (disposed) return stopInfoRefresh();
|
|
2779
|
+
io.bridge("info").then((info) => {
|
|
2780
|
+
if (info) emit({ type: "hello", version: info.protocol, payload: info });
|
|
2781
|
+
else stopInfoRefresh();
|
|
2782
|
+
}).catch(() => stopInfoRefresh());
|
|
2783
|
+
}, INFO_REFRESH_MS);
|
|
2784
|
+
}
|
|
2785
|
+
function stopInfoRefresh() {
|
|
2786
|
+
if (infoTimer) clearInterval(infoTimer);
|
|
2787
|
+
infoTimer = null;
|
|
2788
|
+
}
|
|
2599
2789
|
let syncGen = 0;
|
|
2600
2790
|
let syncTimer = null;
|
|
2601
2791
|
let disposed = false;
|
|
@@ -2603,6 +2793,7 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
2603
2793
|
syncGen++;
|
|
2604
2794
|
if (syncTimer) clearTimeout(syncTimer);
|
|
2605
2795
|
syncTimer = null;
|
|
2796
|
+
stopInfoRefresh();
|
|
2606
2797
|
}
|
|
2607
2798
|
function syncWithRetry(onReady, initialDelay = 0) {
|
|
2608
2799
|
cancelSync();
|
|
@@ -3158,7 +3349,7 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
3158
3349
|
const apps = isRecord(m.payload) && typeof m.payload.apps === "number" ? m.payload.apps : 0;
|
|
3159
3350
|
const wasOnline = appsOnline;
|
|
3160
3351
|
appsOnline = apps;
|
|
3161
|
-
if (apps > 0 && wasOnline
|
|
3352
|
+
if (apps > 0 && (wasOnline === null || wasOnline === 0)) void attach();
|
|
3162
3353
|
else if (apps === 0) emit({ type: "disconnected" });
|
|
3163
3354
|
} else if (m.__rerenderLens === true && typeof m.type === "string") emit({ type: m.type, version: typeof m.version === "number" ? m.version : void 0, payload: m.payload });
|
|
3164
3355
|
}
|
|
@@ -3187,7 +3378,6 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
3187
3378
|
};
|
|
3188
3379
|
};
|
|
3189
3380
|
open();
|
|
3190
|
-
void attach();
|
|
3191
3381
|
},
|
|
3192
3382
|
replay: () => void bridge("replay"),
|
|
3193
3383
|
clear: () => void bridge("clear"),
|
|
@@ -3272,7 +3462,7 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
3272
3462
|
createBroadcastTransport,
|
|
3273
3463
|
createRelayClientTransport,
|
|
3274
3464
|
sourceContext,
|
|
3275
|
-
analysis: { firstDifferentPath, diffLeaves, fixesFor, rankFixes, rootCauseOf, analyzeCommit, contextAttribution, cascadeTree, rootCauseSummary, summarizeSession, compareSessions }
|
|
3465
|
+
analysis: { firstDifferentPath, diffLeaves, fixesFor, rankFixes, rootCauseOf, analyzeCommit: analyzeCommit2, contextAttribution, cascadeTree, rootCauseSummary: rootCauseSummary2, summarizeSession, compareSessions }
|
|
3276
3466
|
};
|
|
3277
3467
|
window.RerenderLensPanel = api;
|
|
3278
3468
|
var hasChrome = typeof chrome !== "undefined" && !!chrome && !!chrome.runtime && !!chrome.runtime.id;
|