@react-perfscope/ui 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -6
- package/dist/index.cjs +46 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +46 -20
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -27,13 +27,12 @@ const unmount = mount({ recorder })
|
|
|
27
27
|
|
|
28
28
|
Click any row to expand its detail. Each signal kind shows different info:
|
|
29
29
|
|
|
30
|
-
- **forced-reflow** — call stack showing where layout was forced
|
|
30
|
+
- **forced-reflow** — call stack showing where layout was forced; reads in one turn are coalesced into a single entry carrying its read count
|
|
31
31
|
- **layout-shift** — CLS value + each source rect
|
|
32
32
|
- **long-task** — start/end/duration, plus call stack if captured
|
|
33
33
|
- **network** — full URL, transfer size, render-blocking flag
|
|
34
|
-
- **paint** — paint name (first-paint / first-contentful-paint)
|
|
35
34
|
- **web-vital** — metric name, value, rating per [Google thresholds](https://web.dev/vitals/)
|
|
36
|
-
- **render** —
|
|
35
|
+
- **render** — one entry per commit, showing the cascade root and every component that re-rendered
|
|
37
36
|
|
|
38
37
|
The header has a **Save** button that downloads the full recording as JSON. The `render` tab offers **Group by component** and the `forced-reflow` tab offers **Group by source**.
|
|
39
38
|
|
|
@@ -79,13 +78,12 @@ const unmount = mount({ recorder })
|
|
|
79
78
|
|
|
80
79
|
신호 row를 클릭하면 디테일이 펼쳐진다. 종류별로 다른 정보를 보여준다:
|
|
81
80
|
|
|
82
|
-
- **forced-reflow** — 레이아웃을 강제한 호출
|
|
81
|
+
- **forced-reflow** — 레이아웃을 강제한 호출 스택. 한 턴에 일어난 읽기는 읽기 횟수를 담은 한 항목으로 합쳐진다
|
|
83
82
|
- **layout-shift** — CLS 값 + 각 source rect
|
|
84
83
|
- **long-task** — 시작/끝/duration, 캡처된 스택이 있으면 같이 표시
|
|
85
84
|
- **network** — 전체 URL, transfer size, render-blocking 여부
|
|
86
|
-
- **paint** — paint 이름 (first-paint / first-contentful-paint)
|
|
87
85
|
- **web-vital** — 메트릭 이름, 값, [Google 기준](https://web.dev/vitals/)에 따른 등급
|
|
88
|
-
- **render** —
|
|
86
|
+
- **render** — 커밋당 한 항목. 캐스케이드 루트와 그 커밋에서 리렌더된 모든 컴포넌트를 보여준다
|
|
89
87
|
|
|
90
88
|
헤더의 **Save** 버튼으로 전체 recording을 JSON으로 받을 수 있다. `render` 탭에는 **Group by component**, `forced-reflow` 탭에는 **Group by source** 옵션이 있다.
|
|
91
89
|
|
package/dist/index.cjs
CHANGED
|
@@ -132,6 +132,8 @@ var en = {
|
|
|
132
132
|
blocking: "blocking",
|
|
133
133
|
sourceCount: (n) => `${n} source(s)`,
|
|
134
134
|
moreItems: (n) => `+ ${n} more`,
|
|
135
|
+
coalescedReads: (n) => `${n} reads`,
|
|
136
|
+
cascadeComponents: (n) => `${n} components`,
|
|
135
137
|
startRecording: "Start recording",
|
|
136
138
|
stopRecording: "Stop recording",
|
|
137
139
|
rec: "rec",
|
|
@@ -220,6 +222,8 @@ var ko = {
|
|
|
220
222
|
blocking: "\uCC28\uB2E8",
|
|
221
223
|
sourceCount: (n) => `\uC18C\uC2A4 ${n}\uAC1C`,
|
|
222
224
|
moreItems: (n) => `\uC678 ${n}\uAC1C \uB354`,
|
|
225
|
+
coalescedReads: (n) => `\uC77D\uAE30 ${n}\uD68C`,
|
|
226
|
+
cascadeComponents: (n) => `\uCEF4\uD3EC\uB10C\uD2B8 ${n}\uAC1C`,
|
|
223
227
|
startRecording: "\uB179\uD654 \uC2DC\uC791",
|
|
224
228
|
stopRecording: "\uB179\uD654 \uC885\uB8CC",
|
|
225
229
|
rec: "\uB179\uD654",
|
|
@@ -828,8 +832,10 @@ function renderBreakdown(members) {
|
|
|
828
832
|
const counts = /* @__PURE__ */ new Map();
|
|
829
833
|
for (const s of members) {
|
|
830
834
|
if (s.kind !== "render") continue;
|
|
831
|
-
|
|
832
|
-
|
|
835
|
+
for (const m of s.members ?? [s]) {
|
|
836
|
+
if (!counts.has(m.component)) order.push(m.component);
|
|
837
|
+
counts.set(m.component, (counts.get(m.component) ?? 0) + 1);
|
|
838
|
+
}
|
|
833
839
|
}
|
|
834
840
|
return order.map((c) => ({ component: c, count: counts.get(c) }));
|
|
835
841
|
}
|
|
@@ -1731,6 +1737,7 @@ function TooltipContent({ s, startedAt }) {
|
|
|
1731
1737
|
s.duration.toFixed(2),
|
|
1732
1738
|
"ms"
|
|
1733
1739
|
] }),
|
|
1740
|
+
(s.count ?? 1) > 1 ? ` \xD7${s.count}` : "",
|
|
1734
1741
|
" ",
|
|
1735
1742
|
at
|
|
1736
1743
|
] });
|
|
@@ -2008,7 +2015,7 @@ function summary(s) {
|
|
|
2008
2015
|
case "web-vital":
|
|
2009
2016
|
return `${s.name}: ${s.value.toFixed(2)}`;
|
|
2010
2017
|
case "render":
|
|
2011
|
-
return `${s.component} \u2022 ${s.reason} \u2022 ${s.duration.toFixed(2)}ms`;
|
|
2018
|
+
return `${s.component} \u2022 ${s.reason} \u2022 ${s.duration.toFixed(2)}ms${s.count && s.count > 1 ? ` \u2022 ${s.count} components` : ""}`;
|
|
2012
2019
|
}
|
|
2013
2020
|
}
|
|
2014
2021
|
var detailLabelStyle = { color: "#888", marginRight: "6px" };
|
|
@@ -2093,6 +2100,25 @@ function WebVitalDetail({ s }) {
|
|
|
2093
2100
|
}
|
|
2094
2101
|
function RenderDetail({ s }) {
|
|
2095
2102
|
const { t } = useI18n();
|
|
2103
|
+
if (s.members && s.members.length > 0) {
|
|
2104
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { paddingLeft: "12px" }, children: [
|
|
2105
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: detailRowStyle, children: [
|
|
2106
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: detailLabelStyle, children: t.duration }),
|
|
2107
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { children: [
|
|
2108
|
+
s.duration.toFixed(3),
|
|
2109
|
+
"ms"
|
|
2110
|
+
] })
|
|
2111
|
+
] }),
|
|
2112
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: detailRowStyle, children: [
|
|
2113
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: detailLabelStyle, children: t.at }),
|
|
2114
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { children: [
|
|
2115
|
+
s.at.toFixed(2),
|
|
2116
|
+
"ms"
|
|
2117
|
+
] })
|
|
2118
|
+
] }),
|
|
2119
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { marginTop: "6px" }, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CascadeMembers, { members: s.members }) })
|
|
2120
|
+
] });
|
|
2121
|
+
}
|
|
2096
2122
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { paddingLeft: "12px" }, children: [
|
|
2097
2123
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: detailRowStyle, children: [
|
|
2098
2124
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: detailLabelStyle, children: t.component }),
|
|
@@ -2383,6 +2409,7 @@ function SummaryLine({ signal }) {
|
|
|
2383
2409
|
] });
|
|
2384
2410
|
}
|
|
2385
2411
|
if (signal.kind === "forced-reflow") {
|
|
2412
|
+
const count = signal.count ?? 1;
|
|
2386
2413
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { children: [
|
|
2387
2414
|
"@ ",
|
|
2388
2415
|
signal.at.toFixed(1),
|
|
@@ -2391,6 +2418,10 @@ function SummaryLine({ signal }) {
|
|
|
2391
2418
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { style: { color }, children: [
|
|
2392
2419
|
signal.duration.toFixed(2),
|
|
2393
2420
|
"ms"
|
|
2421
|
+
] }),
|
|
2422
|
+
count > 1 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { style: { color: "#888" }, children: [
|
|
2423
|
+
" \u2022 ",
|
|
2424
|
+
t.coalescedReads(count)
|
|
2394
2425
|
] })
|
|
2395
2426
|
] });
|
|
2396
2427
|
}
|
|
@@ -2412,7 +2443,8 @@ function SummaryLine({ signal }) {
|
|
|
2412
2443
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { style: { color }, children: [
|
|
2413
2444
|
signal.duration.toFixed(2),
|
|
2414
2445
|
"ms"
|
|
2415
|
-
] })
|
|
2446
|
+
] }),
|
|
2447
|
+
signal.count && signal.count > 1 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: { color: "#888" }, children: t.cascadeComponents(signal.count) })
|
|
2416
2448
|
] });
|
|
2417
2449
|
}
|
|
2418
2450
|
if (signal.kind === "network") {
|
|
@@ -2432,9 +2464,6 @@ function SummaryLine({ signal }) {
|
|
|
2432
2464
|
}
|
|
2433
2465
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: summary(signal) });
|
|
2434
2466
|
}
|
|
2435
|
-
function cascadeRootOf(list) {
|
|
2436
|
-
return list.find((s) => s.reason === "state") ?? list.find((s) => s.reason === "mount") ?? [...list].sort((a, b) => a.depth - b.depth)[0];
|
|
2437
|
-
}
|
|
2438
2467
|
function tabSupportsGrouping(kind) {
|
|
2439
2468
|
return kind === "render" || kind === "forced-reflow";
|
|
2440
2469
|
}
|
|
@@ -2478,22 +2507,17 @@ function groupSignals(signals, mode, kind) {
|
|
|
2478
2507
|
const byName = /* @__PURE__ */ new Map();
|
|
2479
2508
|
for (const s of signals) {
|
|
2480
2509
|
if (s.kind !== "render") continue;
|
|
2481
|
-
const
|
|
2482
|
-
|
|
2483
|
-
|
|
2510
|
+
for (const m of s.members ?? [s]) {
|
|
2511
|
+
if (!byName.has(m.component)) byName.set(m.component, []);
|
|
2512
|
+
byName.get(m.component).push(m);
|
|
2513
|
+
}
|
|
2484
2514
|
}
|
|
2485
2515
|
return Array.from(byName.entries()).sort((a, b) => b[1].length - a[1].length).map(([label, list]) => ({ label, count: list.length, signals: list }));
|
|
2486
2516
|
}
|
|
2487
2517
|
if (mode === "commit" && kind === "render") {
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
if (!byCommit.has(s.commitId)) byCommit.set(s.commitId, []);
|
|
2492
|
-
byCommit.get(s.commitId).push(s);
|
|
2493
|
-
}
|
|
2494
|
-
return Array.from(byCommit.entries()).sort((a, b) => a[0] - b[0]).map(([id, list]) => {
|
|
2495
|
-
const root = cascadeRootOf(list);
|
|
2496
|
-
return { label: root?.component ?? `commit ${id}`, count: list.length, signals: list };
|
|
2518
|
+
return signals.filter((s) => s.kind === "render").sort((a, b) => a.commitId - b.commitId).map((s) => {
|
|
2519
|
+
const list = s.members ?? [s];
|
|
2520
|
+
return { label: s.component, count: list.length, signals: list };
|
|
2497
2521
|
});
|
|
2498
2522
|
}
|
|
2499
2523
|
if (mode === "source" && kind === "forced-reflow") {
|
|
@@ -2855,7 +2879,9 @@ function Panel(props) {
|
|
|
2855
2879
|
activeKind === "render" && activeTab !== "timeline" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2856
2880
|
RenderInsights,
|
|
2857
2881
|
{
|
|
2858
|
-
signals: grouped.render.
|
|
2882
|
+
signals: grouped.render.flatMap(
|
|
2883
|
+
(s) => s.kind === "render" ? s.members ?? [s] : []
|
|
2884
|
+
),
|
|
2859
2885
|
onSelect: () => {
|
|
2860
2886
|
setGroupMode({ ...groupMode, render: "component" });
|
|
2861
2887
|
setExpandedKey(null);
|