diff --git a/.opencode/skills/react-native-best-practices/POWER.md b/.opencode/skills/react-native-best-practices/POWER.md new file mode 100644 index 0000000..bedabb4 --- /dev/null +++ b/.opencode/skills/react-native-best-practices/POWER.md @@ -0,0 +1,161 @@ +--- +name: react-native-best-practices +description: Provides React Native performance optimization guidelines for FPS, TTI, bundle size, memory leaks, re-renders, and animations. Applies to tasks involving Hermes optimization, JS thread blocking, bridge overhead, FlashList, native modules, or debugging jank and frame drops. +license: MIT +author: Callstack +keywords: ["react-native", "expo", "performance", "optimization", "profiling"] +--- + +# Onboarding + +## Step 1: Validate React Native Setup + +Before applying performance optimizations, ensure: +- **Expo CLI** or **React Native CLI** is installed + - Verify with: `npx expo --version` and `npx react-native --version` +- Metro bundler is running (**apply only for** bundle analysis) +- React Native DevTools is available (**apply only for** profiling) + - Press 'j' in Metro terminal or shake device → "Open DevTools" + +## Security Guardrails + +- Review shell commands before running them and prefer version-pinned tooling from trusted sources. +- Do not pipe remote install scripts directly into a shell. +- Treat third-party packages as normal supply-chain dependencies that require provenance and version review. +- If using Re.Pack code splitting, only load first-party chunks from trusted HTTPS origins tied to the current release. + +# When to Load Reference Files + +Load specific reference files from `references/` based on the task: + +## JavaScript/React Performance (`js-*`) + +- **Debugging slow/janky UI or animations** → `references/js-measure-fps.md` +- **Investigating re-render issues** → `references/js-profile-react.md` → `references/js-react-compiler.md` +- **Optimizing list scrolling** → `references/js-lists-flatlist-flashlist.md` +- **Reducing re-renders with state management** → `references/js-atomic-state.md` +- **Using Concurrent React features** → `references/js-concurrent-react.md` +- **Enabling automatic memoization** → `references/js-react-compiler.md` +- **Optimizing animations** → `references/js-animations-reanimated.md` +- **Fixing TextInput lag** → `references/js-uncontrolled-components.md` +- **Hunting JavaScript memory leaks** → `references/js-memory-leaks.md` + +## Native Performance (`native-*`) + +- **Measuring startup time (TTI)** → `references/native-measure-tti.md` +- **Building native modules** → `references/native-turbo-modules.md` +- **Understanding native threading** → `references/native-threading-model.md` +- **Profiling native code** → `references/native-profiling.md` +- **Setting up native tooling** → `references/native-platform-setup.md` +- **Debugging view hierarchy** → `references/native-view-flattening.md` +- **Native memory patterns** → `references/native-memory-patterns.md` +- **Hunting native memory leaks** → `references/native-memory-leaks.md` +- **Choosing native SDKs vs polyfills** → `references/native-sdks-over-polyfills.md` +- **Fixing Android 16KB alignment** → `references/native-android-16kb-alignment.md` + +## Bundle & App Size (`bundle-*`) + +- **Analyzing bundle size** → `references/bundle-analyze-js.md` +- **Analyzing app size** → `references/bundle-analyze-app.md` +- **Fixing barrel imports** → `references/bundle-barrel-exports.md` +- **Enabling tree shaking** → `references/bundle-tree-shaking.md` +- **Android code shrinking** → `references/bundle-r8-android.md` +- **Optimizing Hermes bundle loading** → `references/bundle-hermes-mmap.md` +- **Managing native assets** → `references/bundle-native-assets.md` +- **Evaluating library size** → `references/bundle-library-size.md` +- **Code splitting** → `references/bundle-code-splitting.md` + +## Problem → Reference Mapping + +Use this quick lookup when debugging specific issues: + +| Problem | Start With | +|---------|-----------| +| App feels slow/janky | `references/js-measure-fps.md` → `references/js-profile-react.md` | +| Too many re-renders | `references/js-profile-react.md` → `references/js-react-compiler.md` | +| Slow startup (TTI) | `references/native-measure-tti.md` → `references/bundle-analyze-js.md` | +| Large app size | `references/bundle-analyze-app.md` → `references/bundle-r8-android.md` | +| Memory growing | `references/js-memory-leaks.md` or `references/native-memory-leaks.md` | +| Animation drops frames | `references/js-animations-reanimated.md` | +| List scroll jank | `references/js-lists-flatlist-flashlist.md` | +| TextInput lag | `references/js-uncontrolled-components.md` | +| Native module slow | `references/native-turbo-modules.md` → `references/native-threading-model.md` | +| Native library alignment issue | `references/native-android-16kb-alignment.md` | + +## Quick Reference Commands + +### FPS & Re-renders +```bash +# Open React Native DevTools +# Press 'j' in Metro, or shake device → "Open DevTools" +``` + +Baseline runtime metrics should come from the target interaction itself: +- Capture commit timeline, re-render counts, slow components, and heaviest-commit breakdown. +- Treat component tree depth and count as supporting context only. + +**Common fixes:** +- Replace ScrollView with FlatList/FlashList for lists +- Use React Compiler for automatic memoization +- Use atomic state (Jotai/Zustand) to reduce re-renders +- Use `useDeferredValue` for expensive computations + +**Review guardrails:** +- Check library versions before suggesting API-specific fixes. FlashList v2 deprecates `estimatedItemSize`. +- Do not suggest `useMemo` or `useCallback` dependency changes without a reproducible correctness issue or profiling evidence. +- Do not report stale closures unless the stale read path or repro is clear. + +### Analyze Bundle Size +```bash +npx react-native bundle \ + --entry-file index.js \ + --bundle-output output.js \ + --platform ios \ + --sourcemap-output output.js.map \ + --dev false --minify true + +npx source-map-explorer output.js --no-border-checks +``` + +**Common fixes:** +- Avoid barrel imports (import directly from source) +- Remove unnecessary Intl polyfills only after checking Hermes API and method coverage +- Enable tree shaking (Expo SDK 52+ or Re.Pack) +- Enable R8 for Android native code shrinking + +### Measure TTI +- Use `react-native-performance` for markers +- Only measure cold starts (exclude warm/hot/prewarm) + +**Common fixes:** +- Disable JS bundle compression on Android (enables Hermes mmap) +- Use native navigation (react-native-screens) +- Preload commonly-used expensive screens before navigating to them + +### Native Performance + +**Profile native:** +- iOS: Xcode Instruments → Time Profiler +- Android: Android Studio → CPU Profiler + +**Common fixes:** +- Use background threads for heavy native work +- Prefer async over sync Turbo Module methods +- Use C++ for cross-platform performance-critical code + +## Priority Guidelines + +Apply optimizations in this order: + +| Priority | Category | Impact | Prefix | +|----------|----------|--------|--------| +| 1 | FPS & Re-renders | CRITICAL | `js-*` | +| 2 | Bundle Size | CRITICAL | `bundle-*` | +| 3 | TTI Optimization | HIGH | `native-*`, `bundle-*` | +| 4 | Native Performance | HIGH | `native-*` | +| 5 | Memory Management | MEDIUM-HIGH | `js-*`, `native-*` | +| 6 | Animations | MEDIUM | `js-*` | + +## Attribution + +Based on "The Ultimate Guide to React Native Optimization" by Callstack. diff --git a/.opencode/skills/react-native-best-practices/SKILL.md b/.opencode/skills/react-native-best-practices/SKILL.md new file mode 100644 index 0000000..bde6607 --- /dev/null +++ b/.opencode/skills/react-native-best-practices/SKILL.md @@ -0,0 +1,253 @@ +--- +name: react-native-best-practices +description: Provides React Native performance optimization guidelines for FPS, TTI, bundle size, memory leaks, re-renders, and animations. Applies to tasks involving Hermes optimization, JS thread blocking, bridge overhead, FlashList, native modules, or debugging jank and frame drops. +license: MIT +metadata: + author: Callstack + tags: react-native, expo, performance, optimization, profiling +--- + +# React Native Best Practices + +## Overview + +Performance optimization guide for React Native applications, covering JavaScript/React, Native (iOS/Android), and bundling optimizations. Based on Callstack's "Ultimate Guide to React Native Optimization". + +## Skill Format + +Each reference file follows a hybrid format for fast lookup and deep understanding: + +- **Quick Pattern**: Incorrect/Correct code snippets for immediate pattern matching +- **Quick Command**: Shell commands for process/measurement skills +- **Quick Config**: Configuration snippets for setup-focused skills +- **Quick Reference**: Summary tables for conceptual skills +- **Deep Dive**: Full context with When to Use, Prerequisites, Step-by-Step, Common Pitfalls + +**Impact ratings**: CRITICAL (fix immediately), HIGH (significant improvement), MEDIUM (worthwhile optimization) + +## When to Apply + +Reference these guidelines when: +- Debugging slow/janky UI or animations +- Investigating memory leaks (JS or native) +- Optimizing app startup time (TTI) +- Reducing bundle or app size +- Writing native modules (Turbo Modules) +- Profiling React Native performance +- Reviewing React Native code for performance + +## Security Notes + +- Treat shell commands in these references as local developer operations. Review them before running, prefer version-pinned tooling, and avoid piping remote scripts directly to a shell. +- Treat third-party libraries and plugins as dependencies that still require normal supply-chain controls: pin versions, verify provenance, and update through your standard review process. +- Treat Re.Pack code splitting as first-party artifact delivery only. Remote chunks must come from trusted HTTPS origins you control and be pinned to the current app release. + +## Priority-Ordered Guidelines + +| Priority | Category | Impact | Prefix | +|----------|----------|--------|--------| +| 1 | FPS & Re-renders | CRITICAL | `js-*` | +| 2 | Bundle Size | CRITICAL | `bundle-*` | +| 3 | TTI Optimization | HIGH | `native-*`, `bundle-*` | +| 4 | Native Performance | HIGH | `native-*` | +| 5 | Memory Management | MEDIUM-HIGH | `js-*`, `native-*` | +| 6 | Animations | MEDIUM | `js-*` | + +## Quick Reference + +### Optimization Workflow + +Follow this cycle for any performance issue: **Measure → Optimize → Re-measure → Validate** + +1. **Measure**: Capture baseline metrics before changes. For runtime issues, prefer commit timeline, re-render counts, slow components, heaviest-commit breakdown, and startup/TTI when available. Component tree depth or count are optional context, not substitutes. +2. **Optimize**: Apply the targeted fix from the relevant reference +3. **Re-measure**: Run the same measurement to get updated metrics +4. **Validate**: Confirm improvement (e.g., FPS 45→60, TTI 3.2s→1.8s, bundle 2.1MB→1.6MB) + +If metrics did not improve, revert and try the next suggested fix. + +### Review Guardrails + +- Check library versions before suggesting API-specific fixes. Example: FlashList v2 deprecates `estimatedItemSize`, so do not flag it as missing there. +- Do not suggest `useMemo` or `useCallback` dependency changes unless behavior is demonstrably incorrect or profiling shows wasted work tied to that value. +- Do not report stale closures speculatively. Show the stale read path, a repro, or profiler evidence before calling it out. +- When profiling a flow, measure the target interaction itself. Do not treat component tree depth or component count as the main performance evidence. + +### Critical: FPS & Re-renders + +**Profile first:** +```bash +# Open React Native DevTools +# Press 'j' in Metro, or shake device → "Open DevTools" +``` + +**Common fixes:** +- Replace ScrollView with FlatList/FlashList for lists +- Use React Compiler for automatic memoization +- Use atomic state (Jotai/Zustand) to reduce re-renders +- Use `useDeferredValue` for expensive computations + +### Critical: Bundle Size + +**Analyze bundle:** +```bash +npx react-native bundle \ + --entry-file index.js \ + --bundle-output output.js \ + --platform ios \ + --sourcemap-output output.js.map \ + --dev false --minify true + +npx source-map-explorer output.js --no-border-checks +``` + +**Verify improvement after optimization:** +```bash +# Record baseline size before changes +ls -lh output.js # e.g., Before: 2.1 MB + +# After applying fixes, re-bundle and compare +npx react-native bundle --entry-file index.js --bundle-output output.js \ + --platform ios --dev false --minify true +ls -lh output.js # e.g., After: 1.6 MB (24% reduction) +``` + +**Common fixes:** +- Avoid barrel imports (import directly from source) +- Remove unnecessary Intl polyfills only after checking Hermes API and method coverage +- Enable tree shaking (Expo SDK 52+ or Re.Pack) +- Enable R8 for Android native code shrinking + +### High: TTI Optimization + +**Measure TTI:** +- Use `react-native-performance` for markers +- Only measure cold starts (exclude warm/hot/prewarm) + +**Common fixes:** +- Disable JS bundle compression on Android (enables Hermes mmap) +- Use native navigation (react-native-screens) +- Preload commonly-used expensive screens before navigating to them + +### High: Native Performance + +**Profile native:** +- iOS: Xcode Instruments → Time Profiler +- Android: Android Studio → CPU Profiler + +**Common fixes:** +- Use background threads for heavy native work +- Prefer async over sync Turbo Module methods +- Use C++ for cross-platform performance-critical code + +## References + +Full documentation with code examples in [references/][references]: + +### JavaScript/React (`js-*`) + +| File | Impact | Description | +|------|--------|-------------| +| [js-lists-flatlist-flashlist.md][js-lists-flatlist-flashlist] | CRITICAL | Replace ScrollView with virtualized lists | +| [js-profile-react.md][js-profile-react] | MEDIUM | React DevTools profiling | +| [js-measure-fps.md][js-measure-fps] | HIGH | FPS monitoring and measurement | +| [js-memory-leaks.md][js-memory-leaks] | MEDIUM | JS memory leak hunting | +| [js-atomic-state.md][js-atomic-state] | HIGH | Jotai/Zustand patterns | +| [js-concurrent-react.md][js-concurrent-react] | HIGH | useDeferredValue, useTransition | +| [js-react-compiler.md][js-react-compiler] | HIGH | Automatic memoization | +| [js-animations-reanimated.md][js-animations-reanimated] | MEDIUM | Reanimated worklets | +| [js-bottomsheet.md][js-bottomsheet] | HIGH | Bottom sheet optimization | +| [js-uncontrolled-components.md][js-uncontrolled-components] | HIGH | TextInput optimization | + +### Native (`native-*`) + +| File | Impact | Description | +|------|--------|-------------| +| [native-turbo-modules.md][native-turbo-modules] | HIGH | Building fast native modules | +| [native-sdks-over-polyfills.md][native-sdks-over-polyfills] | HIGH | Native vs JS libraries | +| [native-measure-tti.md][native-measure-tti] | HIGH | TTI measurement setup | +| [native-threading-model.md][native-threading-model] | HIGH | Turbo Module threads | +| [native-profiling.md][native-profiling] | MEDIUM | Xcode/Android Studio profiling | +| [native-platform-setup.md][native-platform-setup] | MEDIUM | iOS/Android tooling guide | +| [native-view-flattening.md][native-view-flattening] | MEDIUM | View hierarchy debugging | +| [native-memory-patterns.md][native-memory-patterns] | MEDIUM | C++/Swift/Kotlin memory | +| [native-memory-leaks.md][native-memory-leaks] | MEDIUM | Native memory leak hunting | +| [native-android-16kb-alignment.md][native-android-16kb-alignment] | CRITICAL | Third-party library alignment for Google Play | + +### Bundling (`bundle-*`) + +| File | Impact | Description | +|------|--------|-------------| +| [bundle-barrel-exports.md][bundle-barrel-exports] | CRITICAL | Avoid barrel imports | +| [bundle-analyze-js.md][bundle-analyze-js] | CRITICAL | JS bundle visualization | +| [bundle-tree-shaking.md][bundle-tree-shaking] | HIGH | Dead code elimination | +| [bundle-analyze-app.md][bundle-analyze-app] | HIGH | App size analysis | +| [bundle-r8-android.md][bundle-r8-android] | HIGH | Android code shrinking | +| [bundle-hermes-mmap.md][bundle-hermes-mmap] | HIGH | Disable bundle compression | +| [bundle-native-assets.md][bundle-native-assets] | HIGH | Asset catalog setup | +| [bundle-library-size.md][bundle-library-size] | MEDIUM | Evaluate dependencies | +| [bundle-code-splitting.md][bundle-code-splitting] | MEDIUM | Re.Pack code splitting | + + +## Searching References + +```bash +# Find patterns by keyword +grep -l "reanimated" references/ +grep -l "flatlist" references/ +grep -l "memory" references/ +grep -l "profil" references/ +grep -l "tti" references/ +grep -l "bundle" references/ +``` + +## Problem → Skill Mapping + +| Problem | Start With | +|---------|------------| +| App feels slow/janky | [js-measure-fps.md][js-measure-fps] → [js-profile-react.md][js-profile-react] | +| Too many re-renders | [js-profile-react.md][js-profile-react] → [js-react-compiler.md][js-react-compiler] | +| Slow startup (TTI) | [native-measure-tti.md][native-measure-tti] → [bundle-analyze-js.md][bundle-analyze-js] | +| Large app size | [bundle-analyze-app.md][bundle-analyze-app] → [bundle-r8-android.md][bundle-r8-android] | +| Memory growing | [js-memory-leaks.md][js-memory-leaks] or [native-memory-leaks.md][native-memory-leaks] | +| Animation drops frames | [js-animations-reanimated.md][js-animations-reanimated] | +| Bottom sheet jank/re-renders | [js-bottomsheet.md][js-bottomsheet] → [js-animations-reanimated.md][js-animations-reanimated] | +| List scroll jank | [js-lists-flatlist-flashlist.md][js-lists-flatlist-flashlist] | +| TextInput lag | [js-uncontrolled-components.md][js-uncontrolled-components] | +| Native module slow | [native-turbo-modules.md][native-turbo-modules] → [native-threading-model.md][native-threading-model] | +| Native library alignment issue | [native-android-16kb-alignment.md][native-android-16kb-alignment] | + +[references]: references/ +[js-lists-flatlist-flashlist]: references/js-lists-flatlist-flashlist.md +[js-profile-react]: references/js-profile-react.md +[js-measure-fps]: references/js-measure-fps.md +[js-memory-leaks]: references/js-memory-leaks.md +[js-atomic-state]: references/js-atomic-state.md +[js-concurrent-react]: references/js-concurrent-react.md +[js-react-compiler]: references/js-react-compiler.md +[js-animations-reanimated]: references/js-animations-reanimated.md +[js-bottomsheet]: references/js-bottomsheet.md +[js-uncontrolled-components]: references/js-uncontrolled-components.md +[native-turbo-modules]: references/native-turbo-modules.md +[native-sdks-over-polyfills]: references/native-sdks-over-polyfills.md +[native-measure-tti]: references/native-measure-tti.md +[native-threading-model]: references/native-threading-model.md +[native-profiling]: references/native-profiling.md +[native-platform-setup]: references/native-platform-setup.md +[native-view-flattening]: references/native-view-flattening.md +[native-memory-patterns]: references/native-memory-patterns.md +[native-memory-leaks]: references/native-memory-leaks.md +[native-android-16kb-alignment]: references/native-android-16kb-alignment.md +[bundle-barrel-exports]: references/bundle-barrel-exports.md +[bundle-analyze-js]: references/bundle-analyze-js.md +[bundle-tree-shaking]: references/bundle-tree-shaking.md +[bundle-analyze-app]: references/bundle-analyze-app.md +[bundle-r8-android]: references/bundle-r8-android.md +[bundle-hermes-mmap]: references/bundle-hermes-mmap.md +[bundle-native-assets]: references/bundle-native-assets.md +[bundle-library-size]: references/bundle-library-size.md +[bundle-code-splitting]: references/bundle-code-splitting.md + +## Attribution + +Based on "The Ultimate Guide to React Native Optimization" by Callstack. diff --git a/.opencode/skills/react-native-best-practices/agents/openai.yaml b/.opencode/skills/react-native-best-practices/agents/openai.yaml new file mode 100644 index 0000000..48c283d --- /dev/null +++ b/.opencode/skills/react-native-best-practices/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "React Native Best Practices" + short_description: "React Native performance optimization guide" + default_prompt: "Use $react-native-best-practices to diagnose and improve React Native performance." diff --git a/.opencode/skills/react-native-best-practices/references/bundle-analyze-app.md b/.opencode/skills/react-native-best-practices/references/bundle-analyze-app.md new file mode 100644 index 0000000..7113685 --- /dev/null +++ b/.opencode/skills/react-native-best-practices/references/bundle-analyze-app.md @@ -0,0 +1,211 @@ +--- +title: Analyze App Bundle Size +impact: HIGH +tags: app-size, ruler, emerge-tools, thinning +--- + +# Skill: Analyze App Bundle Size + +Measure iOS and Android app download/install sizes using Ruler, App Store Connect, and Emerge Tools. + +## Quick Command + +```bash +# Android (Ruler) +cd android && ./gradlew analyzeReleaseBundle + +# iOS (Xcode export with thinning) +cd ios && xcodebuild -exportArchive \ + -archivePath MyApp.xcarchive \ + -exportPath ./export \ + -exportOptionsPlist ExportOptions.plist +# Check: App Thinning Size Report.txt +``` + +## When to Use + +- App download size is too large +- Users complain about storage usage +- App approaching store limits +- Comparing releases for size regression + +> **Note**: This skill involves interpreting visual size reports (Ruler, Emerge Tools X-Ray). AI agents cannot yet process screenshots autonomously. Use this as a guide while reviewing the reports manually, or await MCP-based visual feedback integration (see roadmap). + +## Key Metrics + +| Metric | Description | User Impact | +|--------|-------------|-------------| +| Download Size | Compressed, transferred over network | Download time, data usage | +| Install Size | Uncompressed, on device storage | Storage space | + +**Google finding**: Every 6 MB increase reduces installs by 1%. + +## Android: Ruler (Spotify) + +### Setup + +Add to `android/build.gradle`: + +```groovy +buildscript { + dependencies { + classpath("com.spotify.ruler:ruler-gradle-plugin:2.0.0-beta-3") + } +} +``` + +Add to `android/app/build.gradle`: + +```groovy +apply plugin: "com.spotify.ruler" + +ruler { + abi.set("arm64-v8a") // Target architecture + locale.set("en") + screenDensity.set(480) + sdkVersion.set(34) +} +``` + +### Analyze + +```bash +cd android +./gradlew analyzeReleaseBundle +``` + +Opens HTML report with: +- Download size +- Install size +- Component breakdown (biggest → smallest) + +### CI Size Validation + +```groovy +ruler { + verification { + downloadSizeThreshold = 20 * 1024 * 1024 // 20 MB + installSizeThreshold = 50 * 1024 * 1024 // 50 MB + } +} +``` + +Build fails if thresholds exceeded. + +## iOS: Xcode App Thinning + +### Via App Store Connect (Most Accurate) + +After uploading to TestFlight: +1. Open App Store Connect +2. Go to your build +3. View size table by device variant + +**Note**: TestFlight builds include debug data, App Store builds slightly larger due to DRM. + +### Via Xcode Export + +1. Archive app: **Product → Archive** +2. In Organizer, click **Distribute App** +3. Select **Custom** +4. Choose **App Thinning: All compatible device variants** + +Or in `ExportOptions.plist`: + +```xml +thinning +<thin-for-all-variants> +``` + +### Output + +Creates folder with: +- **Universal IPA**: All variants combined +- **Thinned IPAs**: One per device variant +- **App Thinning Size Report.txt**: + +``` +Variant: SampleApp-.ipa +App + On Demand Resources size: 3.5 MB compressed, 10.6 MB uncompressed +App size: 3.5 MB compressed, 10.6 MB uncompressed +``` + +- Compressed = Download size +- Uncompressed = Install size + +## Emerge Tools (Cross-Platform) + +Third-party service with visual analysis. + +### Upload + +Upload IPA, APK, or AAB through their web interface or CI integration. + +### Features + +![Emerge Tools X-Ray for iOS](images/emerge-xray-ios.png) + +- **X-Ray**: Treemap visualization (like source-map-explorer for binaries) + - Shows Frameworks (hermes.framework), Mach-O sections (TEXT, DATA), etc. + - Color-coded: Binaries, Localizations, Fonts, Asset Catalogs, Videos, CoreML Models + - Visible components: `main.jsbundle` (JS code), RCT modules, DYLD sections +- **Breakdown**: Component-by-component size +- **Insights**: Automated suggestions (use with caution) + +**Caution**: Some suggestions may not apply to React Native (e.g., "remove Hermes"). + +## Size Comparison + +| Tool | Platform | Accuracy | CI Integration | +|------|----------|----------|----------------| +| Ruler | Android | High | Yes (Gradle) | +| App Store Connect | iOS | Highest | No | +| Xcode Export | iOS | High | Yes (xcodebuild) | +| Emerge Tools | Both | High | Yes (API) | + +## Typical React Native App Sizes + +| Component | Approximate Size | +|-----------|------------------| +| Hermes engine | ~2-3 MB | +| React Native core | ~3-5 MB | +| JavaScript bundle | 1-10 MB | +| Assets (images, etc.) | Varies | + +**Baseline empty app**: ~6-10 MB download + +## Optimization Impact Example + +| Optimization | Size Reduction | +|--------------|----------------| +| Enable R8 (Android) | ~30% | +| Remove unused polyfills | 400+ KB | +| Asset catalog (iOS) | 10-50% of assets | +| Tree shaking | 10-15% | + +## Quick Commands + +```bash +# Android release bundle size +cd android && ./gradlew bundleRelease +# Check: android/app/build/outputs/bundle/release/ + +# iOS archive +cd ios && xcodebuild -workspace ios/MyApp.xcworkspace \ + -scheme MyApp \ + -configuration Release \ + -archivePath MyApp.xcarchive \ + archive + +# Export with thinning report +cd ios && xcodebuild -exportArchive \ + -archivePath MyApp.xcarchive \ + -exportPath ./export \ + -exportOptionsPlist ExportOptions.plist +``` + +## Related Skills + +- [bundle-r8-android.md](./bundle-r8-android.md) - Reduce Android size +- [bundle-native-assets.md](./bundle-native-assets.md) - Optimize asset delivery +- [bundle-analyze-js.md](./bundle-analyze-js.md) - JS bundle analysis diff --git a/.opencode/skills/react-native-best-practices/references/bundle-analyze-js.md b/.opencode/skills/react-native-best-practices/references/bundle-analyze-js.md new file mode 100644 index 0000000..531a4ea --- /dev/null +++ b/.opencode/skills/react-native-best-practices/references/bundle-analyze-js.md @@ -0,0 +1,262 @@ +--- +title: Analyze JS Bundle Size +impact: CRITICAL +tags: bundle, analysis, source-map-explorer, expo-atlas +--- + +# Skill: Analyze JS Bundle Size + +Use source-map-explorer and Expo Atlas to visualize what's in your JavaScript bundle. + +## Quick Command + +```bash +# React Native CLI +npx react-native bundle \ + --entry-file index.js \ + --bundle-output output.js \ + --platform ios \ + --sourcemap-output output.js.map \ + --dev false --minify true && \ +npx source-map-explorer output.js --no-border-checks + +# Expo +EXPO_UNSTABLE_ATLAS=true npx expo export --platform ios && npx expo-atlas +``` + +## When to Use + +- JS bundle seems too large +- Want to identify heavy dependencies +- Investigating startup time issues +- Before/after optimization comparison + +> **Note**: This skill involves interpreting visual treemap output (source-map-explorer, Expo Atlas). AI agents cannot yet process screenshots autonomously. Use this as a guide while reviewing the visualization manually, or await MCP-based visual feedback integration (see roadmap). + +## Understanding Hermes Bytecode + +Modern React Native (0.70+) uses Hermes bytecode, not raw JavaScript: +- Skips parsing at runtime +- Still benefits from smaller bundles +- Heavy imports still execute on startup + +**Impact of bundle size:** +- Larger bytecode = longer download from store +- More imports on init path = slower TTI + +## Method 1: source-map-explorer + +### Generate Bundle with Source Map + +**React Native CLI:** + +```bash +npx react-native bundle \ + --entry-file index.js \ + --bundle-output output.js \ + --platform ios \ + --sourcemap-output output.js.map \ + --dev false \ + --minify true +``` + +**Expo (SDK 51+):** + +```bash +npx expo export --platform ios --source-maps --output-dir dist +# Bundle at: dist/ios/_expo/static/js/ios/*.js +# Source map at: dist/ios/_expo/static/js/ios/*.map +``` + +### Analyze + +```bash +npx source-map-explorer output.js --no-border-checks +``` + +**Note**: `--no-border-checks` needed due to Metro's non-standard source maps. + +Opens browser with treemap visualization: + +![Bundle Treemap from source-map-explorer](images/bundle-treemap-source-map-explorer.png) + +The treemap shows: +- **Hierarchy**: `node_modules/` → `react-native/` → `Libraries/` → individual files +- **Size**: Box area proportional to file size (KB shown in labels) +- **Major components visible**: + - `react-native` (724.18 KB, 80.5%) + - `Renderer` (208.44 KB) - ReactNativeRenderer-prod.js, ReactFabric-prod.js + - `Components` (125.29 KB) - Touchable, ScrollView, etc. + - `Animated` (79.48 KB) - Animation system + - `virtualized-lists` (57.57 KB) - FlatList internals + +Click on any section to drill down into that directory. + +**Limitation**: May lose ~30% info due to mapping issues. + +## Method 2: Expo Atlas + +More accurate for Expo projects (or with workaround for bare RN). + +### For Expo Projects + +```bash +# Start with Atlas enabled +EXPO_UNSTABLE_ATLAS=true npx expo start --no-dev + +# Or export +EXPO_UNSTABLE_ATLAS=true npx expo export +``` + +Then launch UI: + +```bash +npx expo-atlas +``` + +![Expo Atlas Treemap](images/expo-atlas-treemap.png) + +Expo Atlas provides more accurate visualization for Expo projects, with similar treemap interface showing module sizes and dependencies. + +### For Non-Expo Projects + +Use `expo-atlas-without-expo` package. + +## Method 3: Re.Pack Bundle Analysis (Webpack/Rspack) + +If using Re.Pack: + +### webpack-bundle-analyzer + +```bash +rspack build --analyze +``` + +### bundle-stats / statoscope + +```bash +# Generate stats +npx react-native bundle \ + --platform android \ + --entry-file index.js \ + --dev false \ + --minify true \ + --json stats.json + +# Analyze +npx bundle-stats --html --json stats.json +``` + +### Rsdoctor + +```javascript +// rspack.config.js +const { RsdoctorRspackPlugin } = require('@rsdoctor/rspack-plugin'); + +module.exports = { + plugins: [ + process.env.RSDOCTOR && new RsdoctorRspackPlugin(), + ].filter(Boolean), +}; +``` + +Run with: + +```bash +RSDOCTOR=true npx react-native start +``` + +## What to Look For + +### Red Flags + +| Finding | Problem | Solution | +|---------|---------|----------| +| Entire library imported | Barrel exports | Use direct imports | +| Duplicate packages | Multiple versions | Dedupe in package.json | +| Dev dependencies in bundle | Incorrect imports | Check conditional imports | +| Large polyfills | Unnecessary for Hermes | Remove (see native-sdks-over-polyfills.md) | +| Moment.js with locales | Bloated date library | Switch to date-fns or dayjs | + +### Common Offenders + +- **Lodash full import**: Use `lodash-es` or specific imports +- **Moment.js**: Replace with `date-fns` or `dayjs` +- **Intl polyfills**: Check Hermes API and method coverage before removing them +- **AWS SDK**: Import specific services only + +## Code Examples + +### Identify Barrel Import Impact + +```tsx +// BAD: Imports entire library through barrel +import { format } from 'date-fns'; + +// In bundle: All of date-fns loaded + +// GOOD: Direct import +import format from 'date-fns/format'; + +// In bundle: Only format function +``` + +## Comparing Bundles + +### source-map-explorer + +```bash +# Generate baseline +npx react-native bundle ... --bundle-output baseline.js --sourcemap-output baseline.js.map + +# Make changes, generate new bundle +npx react-native bundle ... --bundle-output current.js --sourcemap-output current.js.map + +# Compare manually in browser +``` + +### Re.Pack (automated) + +```bash +npx bundle-stats compare baseline-stats.json current-stats.json +``` + +## Quick Commands + +**React Native CLI:** + +```bash +# iOS bundle analysis +npx react-native bundle \ + --entry-file index.js \ + --bundle-output ios-bundle.js \ + --platform ios \ + --sourcemap-output ios-bundle.js.map \ + --dev false \ + --minify true && \ +npx source-map-explorer ios-bundle.js --no-border-checks + +# Android bundle analysis +npx react-native bundle \ + --entry-file index.js \ + --bundle-output android-bundle.js \ + --platform android \ + --sourcemap-output android-bundle.js.map \ + --dev false \ + --minify true && \ +npx source-map-explorer android-bundle.js --no-border-checks +``` + +**Expo:** + +```bash +# Use Expo Atlas (recommended for Expo projects) +EXPO_UNSTABLE_ATLAS=true npx expo export --platform ios +npx expo-atlas +``` + +## Related Skills + +- [bundle-barrel-exports.md](./bundle-barrel-exports.md) - Fix barrel import issues +- [bundle-tree-shaking.md](./bundle-tree-shaking.md) - Enable dead code elimination +- [bundle-library-size.md](./bundle-library-size.md) - Check library sizes before adding diff --git a/.opencode/skills/react-native-best-practices/references/bundle-barrel-exports.md b/.opencode/skills/react-native-best-practices/references/bundle-barrel-exports.md new file mode 100644 index 0000000..42b37f2 --- /dev/null +++ b/.opencode/skills/react-native-best-practices/references/bundle-barrel-exports.md @@ -0,0 +1,248 @@ +--- +title: Avoid Barrel Exports +impact: CRITICAL +tags: bundle, imports, barrel, tree-shaking +--- + +# Skill: Avoid Barrel Exports + +Refactor barrel imports (index files) to reduce bundle size and improve startup time. + +## Quick Pattern + +**Incorrect:** + +```tsx +import { Button } from './components'; +// Loads ALL exports from components/index.ts +``` + +**Correct:** + +```tsx +import Button from './components/Button'; +// Loads only Button +``` + +## When to Use + +- Bundle contains unused code from libraries +- Circular dependency warnings in Metro +- Hot Module Replacement (HMR) breaks frequently +- TTI is slow due to module evaluation + +## What Are Barrel Exports? + +```tsx +// components/index.ts (barrel file) +export { Button } from './Button'; +export { Card } from './Card'; +export { Modal } from './Modal'; +export { Sidebar } from './Sidebar'; + +// Usage (barrel import) +import { Button } from './components'; +``` + +## Problems with Barrel Imports + +### 1. Bundle Size Overhead + +Metro includes **all exports** even if you use one: + +```tsx +// Only need Button, but entire barrel is bundled +import { Button } from './components'; +// Card, Modal, Sidebar also included! +``` + +### 2. Runtime Overhead + +All modules evaluate before returning your import: + +```tsx +import { Button } from './components'; +// JavaScript must evaluate: +// - Button.tsx +// - Card.tsx +// - Modal.tsx +// - Sidebar.tsx +// Even though you only use Button +``` + +### 3. Circular Dependencies + +Barrel files make cycles easier to create accidentally: + +``` +Warning: Require cycle: + components/index.ts -> Button.tsx -> utils/index.ts -> components/index.ts +``` + +Breaks HMR, causes unpredictable behavior. + +## Solution 1: Direct Imports + +Replace barrel imports with direct paths: + +```tsx +// BEFORE: Barrel import +import { Button, Card } from './components'; + +// AFTER: Direct imports +import Button from './components/Button'; +import Card from './components/Card'; +``` + +### Enforce with ESLint + +```bash +npm install -D eslint-plugin-no-barrel-files +``` + +```javascript +// eslint.config.js +import noBarrelFiles from 'eslint-plugin-no-barrel-files'; + +export default [ + { + plugins: { 'no-barrel-files': noBarrelFiles }, + rules: { + 'no-barrel-files/no-barrel-files': 'error', + }, + }, +]; +``` + +## Solution 2: Tree Shaking (Automatic) + +Enable tree shaking to automatically remove unused barrel exports. + +### Expo SDK 52+ + +```tsx +// metro.config.js +const { getDefaultConfig } = require('expo/metro-config'); +const config = getDefaultConfig(__dirname); + +config.transformer.getTransformOptions = async () => ({ + transform: { + experimentalImportSupport: true, + }, +}); + +module.exports = config; +``` + +```bash +# .env +EXPO_UNSTABLE_METRO_OPTIMIZE_GRAPH=1 +EXPO_UNSTABLE_TREE_SHAKING=1 +``` + +### metro-serializer-esbuild + +```bash +npm install @rnx-kit/metro-serializer-esbuild +``` + +### Re.Pack (Webpack/Rspack) + +Tree shaking built-in. + +## Real-World Example: date-fns + +```tsx +// BAD: Imports entire library +import { format, addDays, isToday } from 'date-fns'; + +// GOOD: Direct imports +import format from 'date-fns/format'; +import addDays from 'date-fns/addDays'; +import isToday from 'date-fns/isToday'; +``` + +## Library-Specific Solutions + +Some libraries provide Babel plugins: + +### React Native Paper + +```javascript +// babel.config.js +module.exports = { + plugins: [ + 'react-native-paper/babel', // Auto-transforms imports + ], +}; +``` + +Transforms: +```tsx +import { Button } from 'react-native-paper'; +// Into: +import Button from 'react-native-paper/lib/module/components/Button'; +``` + +## Refactoring Strategy + +### Step 1: Identify Barrel Files + +Look for `index.ts` files with multiple exports: + +```bash +grep -r "export \* from" src/ +grep -r "export { .* } from" src/ +``` + +### Step 2: Update Imports + +```tsx +// Find all usages +// VS Code: Cmd+Shift+F for "from './components'" + +// Replace each with direct import +import Button from './components/Button'; +``` + +### Step 3: (Optional) Keep Barrel for External API + +If your package is consumed by others: + +```tsx +// Keep index.ts for package API +// components/index.ts +export { Button } from './Button'; + +// Internal code uses direct imports +// src/screens/Home.tsx +import Button from '../components/Button'; +``` + +## Migration Script Example + +```bash +# Use codemod or search-replace +# Find: import { (\w+) } from '\.\/components'; +# Replace: import $1 from './components/$1'; +``` + +## Verification + +After refactoring: + +1. Run bundle analysis (see [bundle-analyze-js.md](./bundle-analyze-js.md)) +2. Compare sizes before/after +3. Check for circular dependency warnings + +## Common Pitfalls + +- **Breaking external consumers**: If publishing a library, keep barrel for public API +- **IDE auto-imports**: Configure IDE to prefer direct imports +- **Inconsistent patterns**: Enforce with ESLint across team + +## Related Skills + +- [bundle-analyze-js.md](./bundle-analyze-js.md) - Verify impact +- [bundle-tree-shaking.md](./bundle-tree-shaking.md) - Automatic solution +- [bundle-library-size.md](./bundle-library-size.md) - Check library patterns diff --git a/.opencode/skills/react-native-best-practices/references/bundle-code-splitting.md b/.opencode/skills/react-native-best-practices/references/bundle-code-splitting.md new file mode 100644 index 0000000..9eb18f2 --- /dev/null +++ b/.opencode/skills/react-native-best-practices/references/bundle-code-splitting.md @@ -0,0 +1,247 @@ +--- +title: Remote Code Loading +impact: MEDIUM +tags: code-splitting, repack, lazy-loading, chunks +--- + +# Skill: Remote Code Loading + +Set up code splitting with Re.Pack for on-demand bundle loading from trusted, first-party assets. + +## Quick Pattern + +**Before (static import):** + +```jsx +import SettingsScreen from './screens/SettingsScreen'; +``` + +**After (lazy loaded chunk):** + +```jsx +const SettingsScreen = React.lazy(() => + import(/* webpackChunkName: "settings" */ './screens/SettingsScreen') +); + +}> + + +``` + +## When to Use + +Consider code splitting when: +- **Not using Hermes** (JSC/V8 benefits more) +- App size exceeds 200 MB (Play Store limit) +- Building micro-frontend architecture +- Loading features based on user permissions +- Other optimizations exhausted + +**Note**: Hermes already uses memory mapping for efficient bundle reading. Benefits of code splitting are minimal with Hermes or even counterproductive in some cases. + +## Security Model + +Remote chunks are executable application code. Only load chunks that you build and publish yourself. + +Keep these guardrails in place: +- Serve chunks only from a first-party, HTTPS-only origin you control +- Resolve `scriptId` through a fixed allowlist or release manifest +- Fail closed if a chunk is missing or unexpected +- Do not load chunks from user-controlled input, query params, or third-party domains + +## Prerequisites + +- Re.Pack installed (replaces Metro) + +```bash +npx @callstack/repack-init +``` + +## Step-by-Step Instructions + +### 1. Initialize Re.Pack + +```bash +npx @callstack/repack-init +``` + +Follow prompts to migrate from Metro. Check [migration guide](https://re-pack.dev/docs/getting-started/quick-start). + +### 2. Create Split Point with React.lazy + +```tsx +// BEFORE: Static import +import SettingsScreen from './screens/SettingsScreen'; + +// AFTER: Dynamic import (creates split point) +const SettingsScreen = React.lazy(() => + import(/* webpackChunkName: "settings" */ './screens/SettingsScreen') +); +``` + +### 3. Wrap with Suspense + +```tsx +import React, { Suspense } from 'react'; + +const App = () => { + return ( + }> + + + ); +}; +``` + +### 4. Configure Chunk Loading + +```jsx +// index.js (before AppRegistry) +import { ScriptManager, Script } from '@callstack/repack/client'; + +const CHUNK_URLS = { + settings: 'https://assets.example.com/app/v42/settings.chunk.bundle', +}; + +ScriptManager.shared.addResolver((scriptId) => ({ + url: __DEV__ ? Script.getDevServerURL(scriptId) : getChunkUrl(scriptId), +})); + +function getChunkUrl(scriptId) { + const url = CHUNK_URLS[scriptId]; + + if (!url) { + throw new Error(`Unknown chunk: ${scriptId}`); + } + + return url; +} + +AppRegistry.registerComponent(appName, () => App); +``` + +### 5. Build and Deploy Chunks + +Build generates: +- `index.bundle` - Main bundle +- `settings.chunk.bundle` - Lazy-loaded chunk + +Deploy chunks to a first-party CDN with versioned paths, and keep the allowlist or manifest in sync with the app release. + +## Complete Example + +```tsx +// App.tsx +import React, { Suspense, useState } from 'react'; +import { Button, View, ActivityIndicator } from 'react-native'; + +// Lazy load heavy feature +const HeavyFeature = React.lazy(() => + import(/* webpackChunkName: "heavy-feature" */ './HeavyFeature') +); + +const App = () => { + const [showFeature, setShowFeature] = useState(false); + + return ( + +