Skip to main content

performance

Performance profiling and optimization - bundle analysis, Lighthouse CI, React Profiler, memory leaks, service worker cache efficiency. Use for performance investigations, optimization, and budget enforcement.

ソース情報

リポジトリ
greenpill-dev-guild/coop
ソースの最終更新活動
2026年5月17日 03:22
検出された SKILL.md の言語
英語
スター
3
フォーク
1

インストール方法

デフォルトでは、最初にソースを確認する Prompt が選択されています。直接コマンドに切り替えるか、ローカルコピーをダウンロードすることもできます。

ソースファイルを確認

インストールを決める前に、SKILL.md と SkillsMP に表示されている付属ファイルをお読みください。

SKILL.md を表示中

SKILL.md
ソースの指示 · 読み取り専用プレビュー
name
performance
user-invocable
false
description
Performance profiling and optimization - bundle analysis, Lighthouse CI, React Profiler, memory leaks, service worker cache efficiency. Use for performance investigations, optimization, and budget enforcement.
version
1.0.0
status
active
packages
["shared","app","extension"]
dependencies
[]
last_updated
2026-03-12
last_verified
2026-03-12
# Performance Skill Performance profiling and optimization guide: bundle analysis, runtime profiling, memory management, and monitoring. --- ## Activation When invoked: - Identify the performance layer (bundle size, runtime, memory, network). - Measure before optimizing — never optimize without data. - Check bundle budgets before adding dependencies. ## Part 1: Bundle Budgets ### Coop Limits | Target | Budget | Check | |--------|--------|-------| | Main bundle (app) | < 150KB gzipped | `bun --filter app build` | | Extension popup | < 100KB gzipped | `bun --filter extension build` | | Per-route chunk | < 50KB gzipped | Vite code splitting | | Total JS (app) | < 400KB gzipped | `bun run build` output | ### Bundle Analysis ```bash # Analyze bundle composition cd packages/app ANALYZE=true bun run build # Or use vite-bundle-visualizer npx vite-bundle-visualizer ``` ### Code Splitting Strategy ```typescript // Route-level splitting (automatic with React Router lazy) const CoopDetails = lazy(() => import("./views/CoopDetails")); const RoostView = lazy(() => import("./views/RoostView")); // Component-level splitting (for heavy components) const ArchiveExplorer = lazy(() => import("./components/ArchiveExplorer")); // Always wrap lazy components in Suspense <Suspense fallback={<Skeleton />}> <CoopDetails /> </Suspense> ``` ### Dependency Impact Check Before adding any dependency: ```bash # Check package size npx bundlephobia <package-name> # Or check locally after adding bun run build 2>&1 | grep "chunk size" ``` | Category | Max Size | Examples | |----------|----------|---------| | UI library | < 20KB | Radix primitives (~3KB each) | | Utility | < 10KB | date-fns (tree-shakeable) | | Web3 | < 50KB | viem + permissionless (tree-shakeable) | | State | < 5KB | Zustand (~1KB) | | CRDT | < 30KB | Yjs (~15KB) | ### Tree Shaking ```typescript // GOOD: Named imports allow tree shaking import { formatEther } from "viem"; import { createSmartAccountClient } from "permissionless"; // BAD: Barrel re-export may prevent tree shaking for large libs import * as viem from "viem"; // GOOD: Direct path imports for large libraries import format from "date-fns/format"; // BAD: Top-level import pulls entire library import { format } from "date-fns"; ``` ## Part 2: Runtime Profiling ### React Profiler ```typescript import { Profiler } from "react"; function onRenderCallback( id: string, phase: "mount" | "update", actualDuration: number, baseDuration: number, ) { if (actualDuration > 16) { // Longer than one frame (60fps) logger.warn("Slow render detected", { component: id, phase, actualDuration: `${actualDuration.toFixed(1)}ms`, baseDuration: `${baseDuration.toFixed(1)}ms`, }); } } // Wrap suspect components <Profiler id="TabList" onRender={onRenderCallback}> <TabList tabs={tabs} /> </Profiler> ``` ### Identifying Re-render Causes ```typescript // Debug hook: log what caused a re-render function useWhyDidYouRender(componentName: string, props: Record<string, unknown>) { const previousProps = useRef(props); useEffect(() => { const changedProps = Object.entries(props).reduce<Record<string, { from: unknown; to: unknown }>>( (acc, [key, value]) => { if (previousProps.current[key] !== value) { acc[key] = { from: previousProps.current[key], to: value }; } return acc; }, {} ); if (Object.keys(changedProps).length > 0) { logger.debug(`${componentName} re-rendered`, { changedProps }); } previousProps.current = props; }); } ``` ### Performance Marks ```typescript // Mark critical operations performance.mark("coop-load-start"); const coops = await loadCoops(); performance.mark("coop-load-end"); performance.measure("coop-load", "coop-load-start", "coop-load-end"); // Read measurements const entries = performance.getEntriesByName("coop-load"); logger.info("Coop load time", { duration: entries[0]?.duration }); ``` ## Part 3: Web Vitals ### Core Metrics | Metric | Good | Needs Work | Poor | |--------|------|------------|------| | **LCP** (Largest Contentful Paint) | < 2.5s | 2.5-4s | > 4s | | **FID** (First Input Delay) | < 100ms | 100-300ms | > 300ms | | **CLS** (Cumulative Layout Shift) | < 0.1 | 0.1-0.25 | > 0.25 | | **INP** (Interaction to Next Paint) | < 200ms | 200-500ms | > 500ms | | **TTFB** (Time to First Byte) | < 800ms | 800ms-1.8s | > 1.8s | ### Collecting Web Vitals ```typescript // Use web-vitals library import { onLCP, onFID, onCLS, onINP, onTTFB } from "web-vitals"; function reportWebVital(metric: { name: string; value: number; rating: string }) { logger.info("Web Vital", { name: metric.name, value: metric.value.toFixed(1), rating: metric.rating, }); } onLCP(reportWebVital); onFID(reportWebVital); onCLS(reportWebVital); onINP(reportWebVital); onTTFB(reportWebVital); ``` ### LCP Optimization ```typescript // Preload critical images <link rel="preload" href="/hero-image.webp" as="image" fetchPriority="high" /> // Explicit dimensions prevent layout shift <img src={coop.coverImage} width={800} height={400} loading="eager" // Above-fold: eager fetchPriority="high" // LCP candidate alt={coop.name} /> // Below-fold images: lazy load <img src={tab.screenshot} loading="lazy" // Below-fold: lazy width={400} height={300} alt={tab.title} /> ``` ## Part 4: Memory Management ### Detecting Memory Leaks Common leak sources in Coop: | Source | Symptom | Fix | |--------|---------|-----| | Blob URLs not revoked | Memory grows with each screenshot | `URL.revokeObjectURL()` | | Event listeners not removed | Memory grows on navigation | Cleanup in useEffect or `{ once: true }` | | Timers not cleared | Callbacks fire after unmount | Cleanup in useEffect | | Yjs docs not destroyed | Memory grows per coop opened | `yDoc.destroy()` on unmount | | y-webrtc providers not disconnected | WebRTC connections leak | `provider.disconnect()` + `provider.destroy()` | | Large IndexedDB cursors | Memory spike during iteration | Use pagination/limits | ### AbortController Cleanup ```typescript function useCoopSearch(query: string) { const [results, setResults] = useState<Coop[]>([]); useEffect(() => { const controller = new AbortController(); searchCoops(query, { signal: controller.signal }) .then(setResults) .catch((error) => { if (error.name !== "AbortError") { logger.error("Search failed", { error }); } }); return () => controller.abort(); }, [query]); return results; } ``` ## Part 5: List Performance ### Virtualization (50+ Items) ```typescript import { useVirtualizer } from "@tanstack/react-virtual"; function TabList({ tabs }: { tabs: Tab[] }) { const parentRef = useRef<HTMLDivElement>(null); const virtualizer = useVirtualizer({ count: tabs.length, getScrollElement: () => parentRef.current, estimateSize: () => 120, // Estimated row height overscan: 5, // Render 5 extra items above/below }); return ( <div ref={parentRef} className="h-[600px] overflow-auto"> <div style={{ height: `${virtualizer.getTotalSize()}px`, position: "relative" }} > {virtualizer.getVirtualItems().map((virtualItem) => ( <div key={virtualItem.key} style={{ position: "absolute", top: 0, transform: `translateY(${virtualItem.start}px)`, width: "100%", }} > <TabCard tab={tabs[virtualItem.index]} /> </div> ))} </div> </div> ); } ``` ### Memoization Guidelines ```typescript // USE useMemo for: // - Expensive computations (filtering, sorting large arrays) const filteredTabs = useMemo( () => tabs.filter(t => t.status === "active").sort(byDate), [tabs] ); // USE useCallback for: // - Callbacks passed to memoized children const handlePublish = useCallback( (draftId: string) => publishMutation.mutate(draftId), [publishMutation] ); // USE React.memo for: // - List item components rendered many times const TabCard = React.memo(function TabCard({ tab }: Props) { return <Card>{/* ... */}</Card>; }); // DON'T memoize: // - Primitives (strings, numbers, booleans) // - Components that always re-render anyway // - Cheap computations ``` ## Part 6: Network Performance ### Dexie Query Optimization ```typescript // Dexie live queries are reactive and efficient import { useLiveQuery } from "dexie-react-hooks"; // Good: indexed field query const tabs = useLiveQuery( () => db.tabs.where("coopId").equals(coopId).toArray(), [coopId] ); // Bad: full table scan with JS filter const tabs = useLiveQuery( () => db.tabs.filter(t => t.coopId === coopId).toArray(), [coopId] ); ``` ## Anti-Patterns - **Never optimize without measuring first** — profile before changing code - **Never add large dependencies without checking size** — use bundlephobia - **Never forget to clean up Yjs docs and providers** — destroy on unmount - **Never render 50+ items without virtualization** — use @tanstack/react-virtual - **Never memoize primitives** — only expensive computations - **Never use `import *`** — prevents tree shaking - **Never set images without explicit dimensions** — causes CLS ## Quick Reference Checklist ### Performance Investigation - [ ] Bundle size within budgets? (`bun run build` output) - [ ] Web Vitals in "good" range? (LCP < 2.5s, CLS < 0.1) - [ ] No memory leaks? (Yjs docs, blob URLs, listeners cleaned up) - [ ] Lists virtualized if 50+ items? - [ ] Images have explicit width/height? - [ ] Heavy components code-split with `lazy()`? - [ ] Dexie queries use indexed fields? - [ ] Memoization only where justified? ## Related Skills - `react` — React-specific optimization (useMemo, useCallback, React.memo) - `vite` — Bundle splitting and build optimization - `data-layer` — Dexie query efficiency and Yjs sync performance - `monitoring` — Runtime performance tracking
GitHubで見る