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.

Quellinformationen

Repository
greenpill-dev-guild/coop
Letzte Quellaktivität
17. Mai 2026 um 03:22
Erkannte Sprache von SKILL.md
Englisch
Sterne
3
Forks
1

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
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
Auf GitHub ansehen