@react-perfscope/ui 0.2.0 → 0.4.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/dist/index.js CHANGED
@@ -72,6 +72,9 @@ var en = {
72
72
  sort: "Sort",
73
73
  sortChronological: "chronological",
74
74
  sortSeverity: "severity (worst first)",
75
+ filterPlaceholder: "Filter\u2026",
76
+ filterAria: "Filter signals",
77
+ filterNoMatches: "No signals match the filter.",
75
78
  groupBy: "Group by",
76
79
  groupChronological: "chronological",
77
80
  groupComponent: "component",
@@ -99,6 +102,8 @@ var en = {
99
102
  blocking: "blocking",
100
103
  sourceCount: (n) => `${n} source(s)`,
101
104
  moreItems: (n) => `+ ${n} more`,
105
+ coalescedReads: (n) => `${n} reads`,
106
+ cascadeComponents: (n) => `${n} components`,
102
107
  startRecording: "Start recording",
103
108
  stopRecording: "Stop recording",
104
109
  rec: "rec",
@@ -160,6 +165,9 @@ var ko = {
160
165
  sort: "\uC815\uB82C",
161
166
  sortChronological: "\uC2DC\uAC04\uC21C",
162
167
  sortSeverity: "\uC2EC\uAC01\uB3C4\uC21C (\uB192\uC740 \uAC83\uBD80\uD130)",
168
+ filterPlaceholder: "\uD544\uD130\u2026",
169
+ filterAria: "\uC2DC\uADF8\uB110 \uD544\uD130",
170
+ filterNoMatches: "\uD544\uD130\uC640 \uC77C\uCE58\uD558\uB294 \uC2DC\uADF8\uB110\uC774 \uC5C6\uC5B4\uC694.",
163
171
  groupBy: "\uADF8\uB8F9\uD654",
164
172
  groupChronological: "\uC2DC\uAC04\uC21C",
165
173
  groupComponent: "\uCEF4\uD3EC\uB10C\uD2B8",
@@ -187,6 +195,8 @@ var ko = {
187
195
  blocking: "\uCC28\uB2E8",
188
196
  sourceCount: (n) => `\uC18C\uC2A4 ${n}\uAC1C`,
189
197
  moreItems: (n) => `\uC678 ${n}\uAC1C \uB354`,
198
+ coalescedReads: (n) => `\uC77D\uAE30 ${n}\uD68C`,
199
+ cascadeComponents: (n) => `\uCEF4\uD3EC\uB10C\uD2B8 ${n}\uAC1C`,
190
200
  startRecording: "\uB179\uD654 \uC2DC\uC791",
191
201
  stopRecording: "\uB179\uD654 \uC885\uB8CC",
192
202
  rec: "\uB179\uD654",
@@ -795,8 +805,10 @@ function renderBreakdown(members) {
795
805
  const counts = /* @__PURE__ */ new Map();
796
806
  for (const s of members) {
797
807
  if (s.kind !== "render") continue;
798
- if (!counts.has(s.component)) order.push(s.component);
799
- counts.set(s.component, (counts.get(s.component) ?? 0) + 1);
808
+ for (const m of s.members ?? [s]) {
809
+ if (!counts.has(m.component)) order.push(m.component);
810
+ counts.set(m.component, (counts.get(m.component) ?? 0) + 1);
811
+ }
800
812
  }
801
813
  return order.map((c) => ({ component: c, count: counts.get(c) }));
802
814
  }
@@ -1698,6 +1710,7 @@ function TooltipContent({ s, startedAt }) {
1698
1710
  s.duration.toFixed(2),
1699
1711
  "ms"
1700
1712
  ] }),
1713
+ (s.count ?? 1) > 1 ? ` \xD7${s.count}` : "",
1701
1714
  " ",
1702
1715
  at
1703
1716
  ] });
@@ -1847,6 +1860,45 @@ function RenderInsights({ signals, onSelect }) {
1847
1860
  );
1848
1861
  }
1849
1862
 
1863
+ // src/filter.ts
1864
+ function signalSearchText(signal) {
1865
+ switch (signal.kind) {
1866
+ case "render": {
1867
+ const parts = [signal.component];
1868
+ if (signal.changedProps) parts.push(...signal.changedProps);
1869
+ if (signal.members) for (const m of signal.members) parts.push(m.component);
1870
+ return parts.join(" ").toLowerCase();
1871
+ }
1872
+ case "network":
1873
+ return signal.url.toLowerCase();
1874
+ case "web-vital":
1875
+ return signal.name.toLowerCase();
1876
+ case "interaction":
1877
+ return [signal.eventType, signal.target ?? ""].join(" ").toLowerCase();
1878
+ case "forced-reflow": {
1879
+ const f = signal.stack[0];
1880
+ return f ? [f.fnName ?? "", f.file].join(" ").toLowerCase() : "";
1881
+ }
1882
+ case "long-task": {
1883
+ const parts = [];
1884
+ if (signal.scripts)
1885
+ for (const s of signal.scripts) parts.push(s.sourceFunctionName, s.invoker, s.sourceURL);
1886
+ if (signal.attribution)
1887
+ for (const a of signal.attribution) parts.push(a.frame.fnName ?? "", a.frame.file);
1888
+ return parts.join(" ").toLowerCase();
1889
+ }
1890
+ case "layout-shift":
1891
+ return "layout-shift";
1892
+ default:
1893
+ return "";
1894
+ }
1895
+ }
1896
+ function signalMatchesFilter(signal, query) {
1897
+ const q = query.trim().toLowerCase();
1898
+ if (!q) return true;
1899
+ return signalSearchText(signal).includes(q);
1900
+ }
1901
+
1850
1902
  // src/panel.tsx
1851
1903
  import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs5 } from "preact/jsx-runtime";
1852
1904
  var RENDER_REASON_COLOR = {
@@ -1975,7 +2027,7 @@ function summary(s) {
1975
2027
  case "web-vital":
1976
2028
  return `${s.name}: ${s.value.toFixed(2)}`;
1977
2029
  case "render":
1978
- return `${s.component} \u2022 ${s.reason} \u2022 ${s.duration.toFixed(2)}ms`;
2030
+ return `${s.component} \u2022 ${s.reason} \u2022 ${s.duration.toFixed(2)}ms${s.count && s.count > 1 ? ` \u2022 ${s.count} components` : ""}`;
1979
2031
  }
1980
2032
  }
1981
2033
  var detailLabelStyle = { color: "#888", marginRight: "6px" };
@@ -2060,6 +2112,25 @@ function WebVitalDetail({ s }) {
2060
2112
  }
2061
2113
  function RenderDetail({ s }) {
2062
2114
  const { t } = useI18n();
2115
+ if (s.members && s.members.length > 0) {
2116
+ return /* @__PURE__ */ jsxs5("div", { style: { paddingLeft: "12px" }, children: [
2117
+ /* @__PURE__ */ jsxs5("div", { style: detailRowStyle, children: [
2118
+ /* @__PURE__ */ jsx5("span", { style: detailLabelStyle, children: t.duration }),
2119
+ /* @__PURE__ */ jsxs5("span", { children: [
2120
+ s.duration.toFixed(3),
2121
+ "ms"
2122
+ ] })
2123
+ ] }),
2124
+ /* @__PURE__ */ jsxs5("div", { style: detailRowStyle, children: [
2125
+ /* @__PURE__ */ jsx5("span", { style: detailLabelStyle, children: t.at }),
2126
+ /* @__PURE__ */ jsxs5("span", { children: [
2127
+ s.at.toFixed(2),
2128
+ "ms"
2129
+ ] })
2130
+ ] }),
2131
+ /* @__PURE__ */ jsx5("div", { style: { marginTop: "6px" }, children: /* @__PURE__ */ jsx5(CascadeMembers, { members: s.members }) })
2132
+ ] });
2133
+ }
2063
2134
  return /* @__PURE__ */ jsxs5("div", { style: { paddingLeft: "12px" }, children: [
2064
2135
  /* @__PURE__ */ jsxs5("div", { style: detailRowStyle, children: [
2065
2136
  /* @__PURE__ */ jsx5("span", { style: detailLabelStyle, children: t.component }),
@@ -2350,6 +2421,7 @@ function SummaryLine({ signal }) {
2350
2421
  ] });
2351
2422
  }
2352
2423
  if (signal.kind === "forced-reflow") {
2424
+ const count = signal.count ?? 1;
2353
2425
  return /* @__PURE__ */ jsxs5("span", { children: [
2354
2426
  "@ ",
2355
2427
  signal.at.toFixed(1),
@@ -2358,6 +2430,10 @@ function SummaryLine({ signal }) {
2358
2430
  /* @__PURE__ */ jsxs5("span", { style: { color }, children: [
2359
2431
  signal.duration.toFixed(2),
2360
2432
  "ms"
2433
+ ] }),
2434
+ count > 1 && /* @__PURE__ */ jsxs5("span", { style: { color: "#888" }, children: [
2435
+ " \u2022 ",
2436
+ t.coalescedReads(count)
2361
2437
  ] })
2362
2438
  ] });
2363
2439
  }
@@ -2379,7 +2455,8 @@ function SummaryLine({ signal }) {
2379
2455
  /* @__PURE__ */ jsxs5("span", { style: { color }, children: [
2380
2456
  signal.duration.toFixed(2),
2381
2457
  "ms"
2382
- ] })
2458
+ ] }),
2459
+ signal.count && signal.count > 1 && /* @__PURE__ */ jsx5("span", { style: { color: "#888" }, children: t.cascadeComponents(signal.count) })
2383
2460
  ] });
2384
2461
  }
2385
2462
  if (signal.kind === "network") {
@@ -2399,9 +2476,6 @@ function SummaryLine({ signal }) {
2399
2476
  }
2400
2477
  return /* @__PURE__ */ jsx5("span", { children: summary(signal) });
2401
2478
  }
2402
- function cascadeRootOf(list) {
2403
- return list.find((s) => s.reason === "state") ?? list.find((s) => s.reason === "mount") ?? [...list].sort((a, b) => a.depth - b.depth)[0];
2404
- }
2405
2479
  function tabSupportsGrouping(kind) {
2406
2480
  return kind === "render" || kind === "forced-reflow";
2407
2481
  }
@@ -2445,22 +2519,17 @@ function groupSignals(signals, mode, kind) {
2445
2519
  const byName = /* @__PURE__ */ new Map();
2446
2520
  for (const s of signals) {
2447
2521
  if (s.kind !== "render") continue;
2448
- const k = s.component;
2449
- if (!byName.has(k)) byName.set(k, []);
2450
- byName.get(k).push(s);
2522
+ for (const m of s.members ?? [s]) {
2523
+ if (!byName.has(m.component)) byName.set(m.component, []);
2524
+ byName.get(m.component).push(m);
2525
+ }
2451
2526
  }
2452
2527
  return Array.from(byName.entries()).sort((a, b) => b[1].length - a[1].length).map(([label, list]) => ({ label, count: list.length, signals: list }));
2453
2528
  }
2454
2529
  if (mode === "commit" && kind === "render") {
2455
- const byCommit = /* @__PURE__ */ new Map();
2456
- for (const s of signals) {
2457
- if (s.kind !== "render") continue;
2458
- if (!byCommit.has(s.commitId)) byCommit.set(s.commitId, []);
2459
- byCommit.get(s.commitId).push(s);
2460
- }
2461
- return Array.from(byCommit.entries()).sort((a, b) => a[0] - b[0]).map(([id, list]) => {
2462
- const root = cascadeRootOf(list);
2463
- return { label: root?.component ?? `commit ${id}`, count: list.length, signals: list };
2530
+ return signals.filter((s) => s.kind === "render").sort((a, b) => a.commitId - b.commitId).map((s) => {
2531
+ const list = s.members ?? [s];
2532
+ return { label: s.component, count: list.length, signals: list };
2464
2533
  });
2465
2534
  }
2466
2535
  if (mode === "source" && kind === "forced-reflow") {
@@ -2595,6 +2664,7 @@ function Panel(props) {
2595
2664
  const [expandedKey, setExpandedKey] = useState3(null);
2596
2665
  const [groupMode, setGroupMode] = useState3({});
2597
2666
  const [sortMode, setSortMode] = useState3({});
2667
+ const [filterText, setFilterText] = useState3({});
2598
2668
  const activeOverlayCount = useRef2(0);
2599
2669
  useEffect(() => () => hideAllOverlays(), []);
2600
2670
  function handleHover(signal) {
@@ -2801,7 +2871,21 @@ function Panel(props) {
2801
2871
  ]
2802
2872
  }
2803
2873
  )
2804
- ] })
2874
+ ] }),
2875
+ /* @__PURE__ */ jsx5(
2876
+ "input",
2877
+ {
2878
+ type: "text",
2879
+ "aria-label": t.filterAria,
2880
+ placeholder: t.filterPlaceholder,
2881
+ value: filterText[activeKind] ?? "",
2882
+ onInput: (e) => {
2883
+ setFilterText({ ...filterText, [activeKind]: e.target.value });
2884
+ setExpandedKey(null);
2885
+ },
2886
+ style: { marginLeft: "auto", background: "#1a1a1a", color: "#e6e6e6", border: "1px solid #2a2a2a", borderRadius: "4px", padding: "2px 6px", fontSize: "11px", width: "120px" }
2887
+ }
2888
+ )
2805
2889
  ] }),
2806
2890
  activeTab === "timeline" && /* @__PURE__ */ jsx5("div", { style: { flexGrow: 1, overflowY: "auto", paddingTop: "4px", paddingBottom: "24px" }, children: /* @__PURE__ */ jsx5(
2807
2891
  Timeline,
@@ -2822,7 +2906,7 @@ function Panel(props) {
2822
2906
  activeKind === "render" && activeTab !== "timeline" && /* @__PURE__ */ jsx5(
2823
2907
  RenderInsights,
2824
2908
  {
2825
- signals: grouped.render.filter((s) => s.kind === "render"),
2909
+ signals: grouped.render.flatMap((s) => s.kind === "render" ? s.members ?? [s] : []).filter((s) => signalMatchesFilter(s, filterText.render ?? "")),
2826
2910
  onSelect: () => {
2827
2911
  setGroupMode({ ...groupMode, render: "component" });
2828
2912
  setExpandedKey(null);
@@ -2845,7 +2929,10 @@ function Panel(props) {
2845
2929
  }
2846
2930
  ),
2847
2931
  /* @__PURE__ */ jsx5("ul", { style: { listStyle: "none", margin: 0, padding: 0, overflowY: "auto", flexGrow: 1, display: activeTab === "timeline" ? "none" : void 0 }, children: activeKind && (() => {
2848
- const baseSignals = (sortMode[activeKind] ?? "chronological") === "severity" ? sortSignalsBySeverity(grouped[activeKind]) : grouped[activeKind];
2932
+ const filtered = grouped[activeKind].filter(
2933
+ (s) => signalMatchesFilter(s, filterText[activeKind] ?? "")
2934
+ );
2935
+ const baseSignals = (sortMode[activeKind] ?? "chronological") === "severity" ? sortSignalsBySeverity(filtered) : filtered;
2849
2936
  return groupSignals(baseSignals, groupMode[activeKind] ?? "chronological", activeKind);
2850
2937
  })().map((g, gi) => {
2851
2938
  const currentMode = groupMode[activeKind] ?? "chronological";
@@ -2903,7 +2990,8 @@ function Panel(props) {
2903
2990
  },
2904
2991
  key
2905
2992
  );
2906
- }) })
2993
+ }) }),
2994
+ activeKind && activeTab !== "timeline" && (filterText[activeKind] ?? "").trim() !== "" && grouped[activeKind].length > 0 && grouped[activeKind].every((s) => !signalMatchesFilter(s, filterText[activeKind] ?? "")) && /* @__PURE__ */ jsx5("div", { style: { padding: "12px 8px", fontSize: "11px", color: "#888" }, children: t.filterNoMatches })
2907
2995
  ] })
2908
2996
  ] });
2909
2997
  }