Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 17:56:56 +08:00
commit 9916280104
14 changed files with 5089 additions and 0 deletions

View File

@@ -0,0 +1,609 @@
---
name: device-testing
description: Expert in React Native testing strategies including unit tests with Jest, integration tests, E2E tests with Detox, component testing with React Native Testing Library, snapshot testing, mocking native modules, testing on simulators and real devices. Activates for testing, jest, detox, e2e, unit test, integration test, component test, test runner, mock, snapshot test, testing library, react native testing library, test automation.
---
# Device Testing Expert
Comprehensive expertise in React Native testing strategies, from unit tests to end-to-end testing on real devices and simulators. Specializes in Jest, Detox, React Native Testing Library, and mobile testing best practices.
## What I Know
### Testing Pyramid for Mobile
**Three Layers**
1. **Unit Tests** (70%): Fast, isolated, test logic
2. **Integration Tests** (20%): Test component integration
3. **E2E Tests** (10%): Test full user flows on devices
**Tools**
- **Jest**: Unit and integration testing
- **React Native Testing Library**: Component testing
- **Detox**: E2E testing on simulators/emulators
- **Maestro**: Alternative E2E testing (newer)
### Unit Testing with Jest
**Basic Component Test**
```javascript
// UserProfile.test.js
import React from 'react';
import { render, fireEvent } from '@testing-library/react-native';
import UserProfile from './UserProfile';
describe('UserProfile', () => {
it('renders user name correctly', () => {
const user = { name: 'John Doe', email: 'john@example.com' };
const { getByText } = render(<UserProfile user={user} />);
expect(getByText('John Doe')).toBeTruthy();
expect(getByText('john@example.com')).toBeTruthy();
});
it('calls onPress when button is pressed', () => {
const onPress = jest.fn();
const { getByText } = render(
<UserProfile user={{ name: 'John' }} onPress={onPress} />
);
fireEvent.press(getByText('Edit Profile'));
expect(onPress).toHaveBeenCalledTimes(1);
});
});
```
**Testing Hooks**
```javascript
// useCounter.test.js
import { renderHook, act } from '@testing-library/react-hooks';
import useCounter from './useCounter';
describe('useCounter', () => {
it('increments counter', () => {
const { result } = renderHook(() => useCounter());
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});
it('decrements counter', () => {
const { result } = renderHook(() => useCounter(5));
act(() => {
result.current.decrement();
});
expect(result.current.count).toBe(4);
});
});
```
**Async Testing**
```javascript
// api.test.js
import { fetchUser } from './api';
describe('fetchUser', () => {
it('fetches user data successfully', async () => {
const user = await fetchUser('123');
expect(user).toEqual({
id: '123',
name: 'John Doe',
email: 'john@example.com'
});
});
it('handles errors gracefully', async () => {
await expect(fetchUser('invalid')).rejects.toThrow('User not found');
});
});
```
**Snapshot Testing**
```javascript
// Button.test.js
import React from 'react';
import { render } from '@testing-library/react-native';
import Button from './Button';
describe('Button', () => {
it('renders correctly', () => {
const { toJSON } = render(<Button title="Press Me" />);
expect(toJSON()).toMatchSnapshot();
});
it('renders with custom color', () => {
const { toJSON } = render(<Button title="Press Me" color="red" />);
expect(toJSON()).toMatchSnapshot();
});
});
```
### Mocking
**Mocking Native Modules**
```javascript
// __mocks__/react-native-camera.js
export const RNCamera = {
Constants: {
Type: {
back: 'back',
front: 'front'
}
}
};
// In test file
jest.mock('react-native-camera', () => require('./__mocks__/react-native-camera'));
// Or inline mock
jest.mock('react-native-camera', () => ({
RNCamera: {
Constants: {
Type: { back: 'back', front: 'front' }
}
}
}));
```
**Mocking AsyncStorage**
```javascript
// Setup file (jest.setup.js)
import mockAsyncStorage from '@react-native-async-storage/async-storage/jest/async-storage-mock';
jest.mock('@react-native-async-storage/async-storage', () => mockAsyncStorage);
// In test
import AsyncStorage from '@react-native-async-storage/async-storage';
describe('Storage', () => {
beforeEach(() => {
AsyncStorage.clear();
});
it('stores and retrieves data', async () => {
await AsyncStorage.setItem('key', 'value');
const value = await AsyncStorage.getItem('key');
expect(value).toBe('value');
});
});
```
**Mocking Navigation**
```javascript
// Mock React Navigation
jest.mock('@react-navigation/native', () => ({
useNavigation: () => ({
navigate: jest.fn(),
goBack: jest.fn()
})
}));
// In test
import { useNavigation } from '@react-navigation/native';
describe('ProfileScreen', () => {
it('navigates to settings on button press', () => {
const navigate = jest.fn();
useNavigation.mockReturnValue({ navigate });
const { getByText } = render(<ProfileScreen />);
fireEvent.press(getByText('Settings'));
expect(navigate).toHaveBeenCalledWith('Settings');
});
});
```
**Mocking API Calls**
```javascript
// Using jest.mock
jest.mock('./api', () => ({
fetchUser: jest.fn(() => Promise.resolve({
id: '123',
name: 'Mock User'
}))
}));
// Using MSW (Mock Service Worker)
import { rest } from 'msw';
import { setupServer } from 'msw/node';
const server = setupServer(
rest.get('/api/user/:id', (req, res, ctx) => {
return res(ctx.json({
id: req.params.id,
name: 'Mock User'
}));
})
);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
```
### Component Testing with React Native Testing Library
**Queries**
```javascript
import { render, screen } from '@testing-library/react-native';
// By text
screen.getByText('Submit');
screen.findByText('Loading...'); // Async
screen.queryByText('Error'); // Returns null if not found
// By testID
<View testID="profile-container" />
screen.getByTestId('profile-container');
// By placeholder
<TextInput placeholder="Enter email" />
screen.getByPlaceholderText('Enter email');
// By display value
screen.getByDisplayValue('john@example.com');
// Multiple queries
screen.getAllByText('Item'); // Returns array
```
**User Interactions**
```javascript
import { render, fireEvent, waitFor } from '@testing-library/react-native';
describe('LoginForm', () => {
it('submits form with valid data', async () => {
const onSubmit = jest.fn();
const { getByPlaceholderText, getByText } = render(
<LoginForm onSubmit={onSubmit} />
);
// Type into inputs
fireEvent.changeText(getByPlaceholderText('Email'), 'test@example.com');
fireEvent.changeText(getByPlaceholderText('Password'), 'password123');
// Press button
fireEvent.press(getByText('Login'));
// Wait for async operation
await waitFor(() => {
expect(onSubmit).toHaveBeenCalledWith({
email: 'test@example.com',
password: 'password123'
});
});
});
});
```
### E2E Testing with Detox
**Installation**
```bash
# Install Detox
npm install --save-dev detox
# iOS: Install dependencies
brew tap wix/brew
brew install applesimutils
# Initialize Detox
detox init
# Build app for testing (iOS)
detox build --configuration ios.sim.debug
# Run tests
detox test --configuration ios.sim.debug
```
**Configuration (.detoxrc.js)**
```javascript
module.exports = {
testRunner: 'jest',
runnerConfig: 'e2e/config.json',
apps: {
'ios.debug': {
type: 'ios.app',
binaryPath: 'ios/build/Build/Products/Debug-iphonesimulator/MyApp.app',
build: 'xcodebuild -workspace ios/MyApp.xcworkspace -scheme MyApp -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build'
},
'android.debug': {
type: 'android.apk',
binaryPath: 'android/app/build/outputs/apk/debug/app-debug.apk',
build: 'cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug'
}
},
devices: {
simulator: {
type: 'ios.simulator',
device: { type: 'iPhone 15 Pro' }
},
emulator: {
type: 'android.emulator',
device: { avdName: 'Pixel_6_API_34' }
}
},
configurations: {
'ios.sim.debug': {
device: 'simulator',
app: 'ios.debug'
},
'android.emu.debug': {
device: 'emulator',
app: 'android.debug'
}
}
};
```
**Writing Detox Tests**
```javascript
// e2e/login.test.js
describe('Login Flow', () => {
beforeAll(async () => {
await device.launchApp();
});
beforeEach(async () => {
await device.reloadReactNative();
});
it('should login successfully with valid credentials', async () => {
// Type email
await element(by.id('email-input')).typeText('test@example.com');
// Type password
await element(by.id('password-input')).typeText('password123');
// Tap login button
await element(by.id('login-button')).tap();
// Verify navigation to home screen
await expect(element(by.id('home-screen'))).toBeVisible();
});
it('should show error with invalid credentials', async () => {
await element(by.id('email-input')).typeText('invalid@example.com');
await element(by.id('password-input')).typeText('wrong');
await element(by.id('login-button')).tap();
await expect(element(by.text('Invalid credentials'))).toBeVisible();
});
it('should scroll to bottom of list', async () => {
await element(by.id('user-list')).scrollTo('bottom');
await expect(element(by.id('load-more-button'))).toBeVisible();
});
});
```
**Advanced Detox Actions**
```javascript
// Swipe
await element(by.id('carousel')).swipe('left', 'fast', 0.75);
// Scroll
await element(by.id('scroll-view')).scroll(200, 'down');
// Long press
await element(by.id('item-1')).longPress();
// Multi-tap
await element(by.id('like-button')).multiTap(2);
// Wait for element
await waitFor(element(by.id('success-message')))
.toBeVisible()
.withTimeout(5000);
// Take screenshot
await device.takeScreenshot('login-success');
```
### Maestro (Alternative E2E Tool)
**Installation**
```bash
# Install Maestro
curl -Ls "https://get.maestro.mobile.dev" | bash
# Verify installation
maestro --version
```
**Maestro Flow (YAML-based)**
```yaml
# flows/login.yaml
appId: com.myapp
---
# Launch app
- launchApp
# Wait for login screen
- assertVisible: "Login"
# Enter credentials
- tapOn: "Email"
- inputText: "test@example.com"
- tapOn: "Password"
- inputText: "password123"
# Submit
- tapOn: "Login"
# Verify success
- assertVisible: "Welcome"
```
**Run Maestro Flow**
```bash
# iOS Simulator
maestro test flows/login.yaml
# Android Emulator
maestro test --platform android flows/login.yaml
# Real device (USB connected)
maestro test --device <device-id> flows/login.yaml
```
## When to Use This Skill
Ask me when you need help with:
- Setting up Jest for React Native
- Writing unit tests for components and hooks
- Mocking native modules and dependencies
- Writing integration tests
- Setting up Detox or Maestro for E2E testing
- Testing asynchronous operations
- Snapshot testing strategies
- Testing navigation flows
- Debugging test failures
- Running tests in CI/CD pipelines
- Testing on real devices
- Performance testing strategies
## Test Configuration
**Jest Configuration (jest.config.js)**
```javascript
module.exports = {
preset: 'react-native',
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
transformIgnorePatterns: [
'node_modules/(?!(react-native|@react-native|@react-navigation|expo|@expo)/)'
],
collectCoverageFrom: [
'src/**/*.{js,jsx,ts,tsx}',
'!src/**/*.test.{js,jsx,ts,tsx}',
'!src/**/__tests__/**'
],
coverageThreshold: {
global: {
statements: 80,
branches: 75,
functions: 80,
lines: 80
}
}
};
```
**Jest Setup (jest.setup.js)**
```javascript
import 'react-native-gesture-handler/jestSetup';
// Mock native modules
jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper');
// Mock AsyncStorage
import mockAsyncStorage from '@react-native-async-storage/async-storage/jest/async-storage-mock';
jest.mock('@react-native-async-storage/async-storage', () => mockAsyncStorage);
// Global test utilities
global.fetch = jest.fn();
// Silence console warnings in tests
global.console = {
...console,
warn: jest.fn(),
error: jest.fn()
};
```
## Pro Tips & Tricks
### 1. Test IDs for E2E Testing
Add testID to components for reliable selectors:
```javascript
// In component
<TouchableOpacity testID="submit-button" onPress={handleSubmit}>
<Text>Submit</Text>
</TouchableOpacity>
// In Detox test
await element(by.id('submit-button')).tap();
// Avoid using text or accessibility labels (can change with i18n)
```
### 2. Test Factories for Mock Data
```javascript
// testUtils/factories.js
export const createMockUser = (overrides = {}) => ({
id: '123',
name: 'John Doe',
email: 'john@example.com',
...overrides
});
// In test
const user = createMockUser({ name: 'Jane Doe' });
```
### 3. Custom Render with Providers
```javascript
// testUtils/render.js
import { render } from '@testing-library/react-native';
import { NavigationContainer } from '@react-navigation/native';
import { Provider } from 'react-redux';
import { store } from '../store';
export function renderWithProviders(ui, options = {}) {
return render(
<Provider store={store}>
<NavigationContainer>
{ui}
</NavigationContainer>
</Provider>,
options
);
}
// In test
import { renderWithProviders } from './testUtils/render';
renderWithProviders(<MyScreen />);
```
### 4. Parallel Test Execution
```json
// package.json
{
"scripts": {
"test": "jest --maxWorkers=4",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage"
}
}
```
## Integration with SpecWeave
**Test Planning**
- Document test strategy in `spec.md`
- Include test coverage targets in `tasks.md`
- Embed test cases in tasks (BDD format)
**Coverage Tracking**
- Set coverage thresholds (80%+ for critical paths)
- Track coverage trends across increments
- Document testing gaps in increment reports
**CI/CD Integration**
- Run tests on every commit
- Block merges if tests fail
- Generate coverage reports
- Run E2E tests on staging builds

View File

@@ -0,0 +1,440 @@
---
name: expo-workflow
description: Expert in Expo development workflows, EAS Build, EAS Update, Expo Go, dev clients, expo-cli commands, app configuration, and deployment strategies. Activates for expo, expo go, eas build, eas update, expo config, app.json, eas.json, expo dev client, expo prebuild, expo eject, over-the-air updates, expo doctor, expo install, managed workflow, bare workflow.
---
# Expo Workflow Expert
Comprehensive expertise in Expo development workflows, EAS (Expo Application Services), and optimization strategies for rapid mobile development. Specializes in modern Expo SDK features, development builds, and deployment pipelines.
## What I Know
### Expo Fundamentals
**Managed vs Bare Workflow**
- Managed workflow: Full Expo SDK, minimal native code
- Bare workflow: Full native code access with Expo modules
- When to use each approach
- Migration strategies between workflows
**Expo Go vs Development Builds**
- Expo Go: Quick testing, limited native modules
- Dev Client: Full native module support, custom builds
- When to switch from Expo Go to dev builds
- Creating custom dev clients with EAS Build
**Expo SDK & Modules**
- Core Expo modules (expo-camera, expo-location, etc.)
- Third-party native module compatibility
- Module installation best practices
- Autolinking and manual linking
### EAS Build (Cloud Builds)
**Build Profiles**
- Development builds: Fast iteration, dev client
- Preview builds: Internal testing, TestFlight/Internal Testing
- Production builds: App Store/Play Store submission
- Custom build profiles in eas.json
**Platform-Specific Configuration**
- iOS credentials management
- Android keystore handling
- Build caching strategies
- Environment variable injection
**Build Optimization**
- Caching node_modules and gradle dependencies
- Incremental builds
- Build machine types (M1, Ubuntu)
- Build time reduction strategies
### EAS Update (OTA Updates)
**Over-The-Air Updates**
- JavaScript bundle updates without app store submission
- Update channels and branches
- Rollout strategies (gradual rollout, instant rollout)
- Rollback capabilities
**Update Workflows**
- Development channel: Continuous updates
- Preview channel: QA testing
- Production channel: Staged rollouts
- Emergency hotfix workflows
**Update Best Practices**
- Version compatibility management
- Update frequency optimization
- Monitoring update adoption
- Handling update failures gracefully
### App Configuration
**app.json / app.config.js**
- App metadata (name, slug, version)
- Platform-specific configurations
- Asset and icon configuration
- Splash screen customization
- Deep linking setup (scheme, associated domains)
- Permissions configuration
- Build-time environment variables
**eas.json**
- Build profile configuration
- Submit profile setup
- Environment secrets management
- Platform-specific build settings
**Dynamic Configuration**
- Environment-specific configs (dev, staging, prod)
- Feature flags integration
- App variants (white-label apps)
### Development Workflow
**Fast Refresh & Hot Reloading**
- Understanding fast refresh behavior
- Troubleshooting fast refresh issues
- When to use full reload vs fast refresh
**Debugging Tools**
- React DevTools integration
- Remote debugging with Chrome DevTools
- Flipper for advanced debugging
- Network request inspection
- Performance profiling
**Local Development**
- Running on physical devices (QR code scanning)
- Running on simulators/emulators
- Offline development strategies
- Tunnel mode vs LAN mode
### Deployment & Distribution
**App Store Submission**
- iOS: TestFlight, App Store Connect integration
- Android: Internal testing, Play Store submission
- EAS Submit command automation
- Store metadata management
**Internal Distribution**
- Ad-hoc iOS builds
- Android APK distribution
- Enterprise distribution
- TestFlight external testing
**CI/CD Integration**
- GitHub Actions with EAS Build
- GitLab CI integration
- Automated build triggers
- Automated OTA updates on merge
## When to Use This Skill
Ask me when you need help with:
- Setting up Expo development workflow
- Creating development builds with EAS Build
- Configuring app.json or eas.json
- Setting up over-the-air updates with EAS Update
- Troubleshooting Expo Go limitations
- Optimizing build times
- Managing app credentials and secrets
- Configuring deep linking and URL schemes
- Setting up CI/CD pipelines for Expo apps
- Deploying to App Store or Play Store
- Understanding Expo SDK capabilities
- Migrating from Expo Go to dev client
- Handling native modules in Expo projects
## Essential Expo Commands
### Project Setup
```bash
# Create new Expo project
npx create-expo-app@latest MyApp
# Navigate to project
cd MyApp
# Start development server
npx expo start
# Install Expo module
npx expo install expo-camera
# Check project health
npx expo-doctor
```
### Development
```bash
# Start with cache cleared
npx expo start -c
# Start with specific mode
npx expo start --dev-client # Development build
npx expo start --go # Expo Go
# Run on specific platform
npx expo run:ios
npx expo run:android
# Prebuild native projects (bare workflow)
npx expo prebuild
```
### EAS Build
```bash
# Login to EAS
eas login
# Configure EAS
eas build:configure
# Build for all platforms
eas build --platform all
# Build development version
eas build --profile development --platform ios
# Build for production
eas build --profile production --platform all
# Check build status
eas build:list
```
### EAS Update
```bash
# Configure EAS Update
eas update:configure
# Publish update to default channel
eas update --branch production --message "Fix critical bug"
# Publish to specific channel
eas update --channel preview --message "QA testing"
# List published updates
eas update:list
# Rollback update
eas update:rollback
```
### EAS Submit
```bash
# Submit to App Store
eas submit --platform ios
# Submit to Play Store
eas submit --platform android
# Submit specific build
eas submit --platform ios --id <build-id>
```
## Pro Tips & Tricks
### 1. Development Build Optimization
Create a reusable development build once, then use EAS Update for daily changes:
```json
// eas.json
{
"build": {
"development": {
"developmentClient": true,
"distribution": "internal",
"ios": {
"simulator": true
}
}
}
}
```
Build once:
```bash
eas build --profile development --platform all
```
Update JavaScript daily:
```bash
eas update --branch development --message "Daily changes"
```
### 2. Environment-Based Configuration
Use app.config.js for dynamic configuration:
```javascript
// app.config.js
export default ({ config }) => {
const isProduction = process.env.APP_ENV === 'production';
return {
...config,
name: isProduction ? 'MyApp' : 'MyApp Dev',
slug: 'myapp',
extra: {
apiUrl: isProduction
? 'https://api.myapp.com'
: 'https://dev-api.myapp.com',
analyticsKey: process.env.ANALYTICS_KEY,
},
updates: {
url: 'https://u.expo.dev/your-project-id'
}
};
};
```
### 3. Fast Credential Setup
Let EAS manage credentials automatically:
```json
// eas.json
{
"build": {
"production": {
"ios": {
"credentialsSource": "remote"
},
"android": {
"credentialsSource": "remote"
}
}
}
}
```
### 4. Efficient Build Caching
Speed up builds by caching dependencies:
```json
// eas.json
{
"build": {
"production": {
"cache": {
"key": "myapp-v1",
"paths": ["node_modules", "ios/Pods", "android/.gradle"]
}
}
}
}
```
### 5. Gradual OTA Rollout
Safely deploy updates to production:
```bash
# Start with 10% rollout
eas update --branch production --message "New feature" --rollout-percentage 10
# Monitor metrics, then increase
eas update:configure-rollout --branch production --percentage 50
# Full rollout
eas update:configure-rollout --branch production --percentage 100
```
### 6. Quick Testing on Physical Devices
For Expo Go (quick testing):
```bash
# Start dev server
npx expo start
# Scan QR code with:
# - iOS: Camera app
# - Android: Expo Go app
```
For dev client (full features):
```bash
# Install dev client once
eas build --profile development --platform ios
# Daily JavaScript updates via EAS Update
eas update --branch development
```
### 7. Troubleshooting Common Issues
**"Unable to resolve module"**
```bash
# Clear Metro cache
npx expo start -c
# Reinstall dependencies
rm -rf node_modules && npm install
```
**"Build failed on EAS"**
```bash
# Check build logs
eas build:list
eas build:view <build-id>
# Run prebuild locally to catch issues early
npx expo prebuild
```
**"Update not appearing in app"**
```bash
# Check update channel matches app's channel
eas channel:list
# Verify update was published successfully
eas update:list --branch production
# Force reload in app (shake device → reload)
```
### 8. Native Module Integration
When you need a native module not in Expo SDK:
```bash
# Install the module
npm install react-native-awesome-module
# Prebuild to generate native projects
npx expo prebuild
# Rebuild dev client with new module
eas build --profile development --platform all
# Continue using EAS Update for JS changes
eas update --branch development
```
## Integration with SpecWeave
**Increment Planning**
- Document Expo setup steps in `spec.md`
- Include EAS Build/Update configuration in `plan.md`
- Track build and deployment tasks in `tasks.md`
**Testing Strategy**
- Use dev builds for feature development
- Preview builds for QA testing
- Production builds for stakeholder demos
**Living Documentation**
- Document build profiles in `.specweave/docs/internal/operations/`
- Track deployment procedures in runbooks
- Maintain credential management procedures
**Cost Optimization**
- Use EAS Update instead of rebuilding for JS-only changes
- Cache dependencies to reduce build times
- Monitor EAS usage in increment reports

View File

@@ -0,0 +1,614 @@
---
name: metro-bundler
description: Expert in Metro bundler configuration, optimization, troubleshooting, caching strategies, custom transformers, asset management, source maps, bundling performance. Activates for metro, metro bundler, metro.config.js, bundler, bundle, cache, transformer, asset resolver, metro cache, bundling error, unable to resolve module, port 8081.
---
# Metro Bundler Expert
Comprehensive expertise in React Native's Metro bundler, including configuration, optimization, custom transformers, caching strategies, and troubleshooting common bundling issues.
## What I Know
### Metro Fundamentals
**What is Metro?**
- JavaScript bundler for React Native
- Transforms and bundles JavaScript modules
- Handles assets (images, fonts, etc.)
- Provides fast refresh for development
- Generates source maps for debugging
**Key Concepts**
- **Transformer**: Converts source code (TypeScript, JSX) to JavaScript
- **Resolver**: Locates modules in the file system
- **Serializer**: Combines modules into bundles
- **Cache**: Speeds up subsequent builds
### Metro Configuration
**Basic metro.config.js**
```javascript
const { getDefaultConfig } = require('expo/metro-config');
/** @type {import('expo/metro-config').MetroConfig} */
const config = getDefaultConfig(__dirname);
module.exports = config;
```
**Custom Configuration**
```javascript
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
const defaultConfig = getDefaultConfig(__dirname);
const config = {
transformer: {
// Enable Babel transformer
babelTransformerPath: require.resolve('react-native-svg-transformer'),
// Source map options
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: true,
},
}),
},
resolver: {
// Custom asset extensions
assetExts: defaultConfig.resolver.assetExts.filter(ext => ext !== 'svg'),
// Custom source extensions
sourceExts: [...defaultConfig.resolver.sourceExts, 'svg', 'cjs'],
// Node module resolution
nodeModulesPaths: [
'./node_modules',
'../../node_modules', // For monorepos
],
// Custom platform-specific extensions
platforms: ['ios', 'android', 'native'],
},
server: {
// Custom port
port: 8081,
// Enhanced logging
enhanceMiddleware: (middleware) => {
return (req, res, next) => {
console.log(`Metro request: ${req.url}`);
return middleware(req, res, next);
};
},
},
watchFolders: [
// Watch external folders (monorepos)
path.resolve(__dirname, '..', 'shared-library'),
],
resetCache: true, // Reset cache on start (dev only)
};
module.exports = mergeConfig(defaultConfig, config);
```
### Optimization Strategies
**Inline Requires**
```javascript
// metro.config.js
module.exports = {
transformer: {
getTransformOptions: async () => ({
transform: {
inlineRequires: true, // Lazy load modules (faster startup)
},
}),
},
};
// Before (eager loading)
import UserProfile from './UserProfile';
import Settings from './Settings';
function App() {
return (
<View>
{showProfile ? <UserProfile /> : <Settings />}
</View>
);
}
// After inline requires (lazy loading)
function App() {
return (
<View>
{showProfile ?
<require('./UserProfile').default /> :
<require('./Settings').default />
}
</View>
);
}
```
**Bundle Splitting (Experimental)**
```javascript
// metro.config.js
module.exports = {
serializer: {
createModuleIdFactory: () => {
// Generate stable module IDs for better caching
return (path) => {
return require('crypto')
.createHash('sha1')
.update(path)
.digest('hex')
.substring(0, 8);
};
},
},
};
```
**Asset Optimization**
```javascript
// metro.config.js
module.exports = {
transformer: {
// Minify assets
minifierPath: require.resolve('metro-minify-terser'),
minifierConfig: {
compress: {
drop_console: true, // Remove console.log in production
drop_debugger: true,
},
output: {
comments: false,
},
},
},
resolver: {
// Optimize asset resolution
assetExts: [
'png', 'jpg', 'jpeg', 'gif', 'webp', // Images
'mp3', 'wav', 'm4a', 'aac', // Audio
'mp4', 'mov', // Video
'ttf', 'otf', 'woff', 'woff2', // Fonts
],
},
};
```
### Custom Transformers
**SVG Transformer**
```bash
# Install
npm install react-native-svg react-native-svg-transformer
# metro.config.js
const { getDefaultConfig } = require('expo/metro-config');
const config = getDefaultConfig(__dirname);
config.transformer = {
...config.transformer,
babelTransformerPath: require.resolve('react-native-svg-transformer'),
};
config.resolver = {
...config.resolver,
assetExts: config.resolver.assetExts.filter(ext => ext !== 'svg'),
sourceExts: [...config.resolver.sourceExts, 'svg'],
};
module.exports = config;
```
```javascript
// Usage in code
import Logo from './assets/logo.svg';
function App() {
return <Logo width={120} height={40} />;
}
```
**Multiple File Extensions**
```javascript
// metro.config.js
module.exports = {
resolver: {
// Add .web.js, .native.js for platform-specific code
sourceExts: ['js', 'json', 'ts', 'tsx', 'jsx', 'web.js', 'native.js'],
// Custom resolution logic
resolveRequest: (context, moduleName, platform) => {
if (moduleName === 'my-module') {
// Custom module resolution
return {
filePath: '/custom/path/to/module.js',
type: 'sourceFile',
};
}
return context.resolveRequest(context, moduleName, platform);
},
},
};
```
### Caching Strategies
**Cache Management**
```bash
# Clear Metro cache
npx react-native start --reset-cache
npm start -- --reset-cache # Expo
# Or manually
rm -rf $TMPDIR/react-*
rm -rf $TMPDIR/metro-*
# Clear watchman cache
watchman watch-del-all
# Clear all caches (nuclear option)
npm run clear # If configured in package.json
```
**Cache Configuration**
```javascript
// metro.config.js
const path = require('path');
module.exports = {
cacheStores: [
// Custom cache directory
{
get: (key) => {
const cachePath = path.join(__dirname, '.metro-cache', key);
// Implement custom cache retrieval
},
set: (key, value) => {
const cachePath = path.join(__dirname, '.metro-cache', key);
// Implement custom cache storage
},
},
],
// Reset cache on config changes
resetCache: process.env.RESET_CACHE === 'true',
};
```
### Monorepo Setup
**Workspaces Configuration**
```javascript
// metro.config.js (in app directory)
const path = require('path');
const { getDefaultConfig } = require('@react-native/metro-config');
const projectRoot = __dirname;
const workspaceRoot = path.resolve(projectRoot, '../..');
const config = getDefaultConfig(projectRoot);
// Watch workspace directories
config.watchFolders = [workspaceRoot];
// Resolve modules from workspace
config.resolver.nodeModulesPaths = [
path.resolve(projectRoot, 'node_modules'),
path.resolve(workspaceRoot, 'node_modules'),
];
// Avoid hoisting issues
config.resolver.disableHierarchicalLookup = false;
module.exports = config;
```
**Symlink Handling**
```javascript
// metro.config.js
module.exports = {
resolver: {
// Enable symlink support
unstable_enableSymlinks: true,
// Resolve symlinked packages
resolveRequest: (context, moduleName, platform) => {
const resolution = context.resolveRequest(context, moduleName, platform);
if (resolution && resolution.type === 'sourceFile') {
// Resolve real path for symlinks
const realPath = require('fs').realpathSync(resolution.filePath);
return {
...resolution,
filePath: realPath,
};
}
return resolution;
},
},
};
```
### Common Issues & Solutions
**"Unable to resolve module"**
```bash
# Solution 1: Clear cache
npx react-native start --reset-cache
# Solution 2: Reinstall dependencies
rm -rf node_modules
npm install
# Solution 3: Check import paths
# Ensure case-sensitive imports match file names
import UserProfile from './userProfile'; # ❌ Wrong case
import UserProfile from './UserProfile'; # ✅ Correct
# Solution 4: Add to metro.config.js
module.exports = {
resolver: {
extraNodeModules: {
'my-module': path.resolve(__dirname, 'node_modules/my-module'),
},
},
};
```
**"Port 8081 already in use"**
```bash
# Find and kill process
lsof -ti:8081 | xargs kill -9
# Or start on different port
npx react-native start --port 8082
# Update code to use new port
adb reverse tcp:8082 tcp:8082 # Android
```
**"Invariant Violation: Module AppRegistry is not a registered callable module"**
```bash
# Clear all caches
rm -rf $TMPDIR/react-*
rm -rf $TMPDIR/metro-*
watchman watch-del-all
rm -rf node_modules
npm install
npx react-native start --reset-cache
```
**"TransformError: ... SyntaxError"**
```javascript
// Add Babel plugin to metro.config.js
module.exports = {
transformer: {
babelTransformerPath: require.resolve('./customBabelTransformer.js'),
},
};
// customBabelTransformer.js
module.exports = require('metro-react-native-babel-preset');
```
## When to Use This Skill
Ask me when you need help with:
- Configuring Metro bundler
- Custom transformers (SVG, images, etc.)
- Optimizing bundle size and startup time
- Setting up monorepo with Metro
- Troubleshooting "Unable to resolve module" errors
- Clearing Metro cache effectively
- Configuring source maps
- Platform-specific file resolution
- Debugging bundling performance
- Custom asset handling
- Port conflicts (8081)
- Symlink resolution in monorepos
## Essential Commands
### Development
```bash
# Start Metro bundler
npx react-native start
# Start with cache cleared
npx react-native start --reset-cache
# Start with custom port
npx react-native start --port 8082
# Start with verbose logging
npx react-native start --verbose
# Expo dev server
npx expo start
# Expo with cache cleared
npx expo start -c
```
### Debugging
```bash
# Check Metro status
curl http://localhost:8081/status
# Get bundle (for debugging)
curl http://localhost:8081/index.bundle?platform=ios > bundle.js
# Check source map
curl http://localhost:8081/index.map?platform=ios > bundle.map
# List all modules in bundle
curl http://localhost:8081/index.bundle?platform=ios&dev=false&minify=false
```
### Cache Management
```bash
# Clear Metro cache
rm -rf $TMPDIR/react-*
rm -rf $TMPDIR/metro-*
# Clear watchman
watchman watch-del-all
# Clear all (comprehensive)
npm run clear # Custom script
# Or manually:
rm -rf $TMPDIR/react-*
rm -rf $TMPDIR/metro-*
watchman watch-del-all
rm -rf node_modules
npm install
```
## Pro Tips & Tricks
### 1. Bundle Analysis
Analyze bundle size to find optimization opportunities:
```bash
# Generate bundle with source map
npx react-native bundle \
--platform ios \
--dev false \
--entry-file index.js \
--bundle-output ./bundle.js \
--sourcemap-output ./bundle.map
# Analyze with source-map-explorer
npm install -g source-map-explorer
source-map-explorer bundle.js bundle.map
```
### 2. Environment-Specific Configuration
```javascript
// metro.config.js
const isDev = process.env.NODE_ENV !== 'production';
module.exports = {
transformer: {
minifierConfig: {
compress: {
drop_console: !isDev, // Remove console.log in production
},
},
},
serializer: {
getModulesRunBeforeMainModule: () => [
// Polyfills for production
...(!isDev ? [require.resolve('./polyfills.js')] : []),
],
},
};
```
### 3. Custom Asset Pipeline
```javascript
// metro.config.js
module.exports = {
transformer: {
// Optimize images during bundling
assetPlugins: ['expo-asset/tools/hashAssetFiles'],
},
resolver: {
assetExts: ['png', 'jpg', 'jpeg', 'gif', 'webp', 'svg'],
// Custom asset resolution
resolveAsset: (dirPath, assetName, extension) => {
const basePath = `${dirPath}/${assetName}`;
// Try @2x, @3x variants
const variants = ['@3x', '@2x', ''];
for (const variant of variants) {
const path = `${basePath}${variant}.${extension}`;
if (require('fs').existsSync(path)) {
return path;
}
}
return null;
},
},
};
```
### 4. Preloading Heavy Modules
```javascript
// index.js
import { AppRegistry } from 'react-native';
import App from './App';
// Preload heavy modules
import('./src/heavyModule').then(() => {
console.log('Heavy module preloaded');
});
AppRegistry.registerComponent('MyApp', () => App);
```
### 5. Development Performance Boost
```javascript
// metro.config.js
const isDev = process.env.NODE_ENV !== 'production';
module.exports = {
transformer: {
// Skip minification in dev
minifierPath: isDev ? undefined : require.resolve('metro-minify-terser'),
// Faster source maps in dev
getTransformOptions: async () => ({
transform: {
inlineRequires: !isDev, // Only in production
},
}),
},
server: {
// Increase file watching performance
watchFolders: isDev ? [] : undefined,
},
};
```
## Integration with SpecWeave
**Configuration Management**
- Document Metro configuration in `docs/internal/architecture/`
- Track bundle size across increments
- Include bundling optimization in `tasks.md`
**Performance Monitoring**
- Set bundle size thresholds
- Track startup time improvements
- Document optimization strategies
**Troubleshooting**
- Maintain runbook for common Metro issues
- Document cache clearing procedures
- Track bundling errors in increment reports

View File

@@ -0,0 +1,536 @@
---
name: mobile-debugging
description: Expert in debugging React Native and Expo mobile applications. Covers React DevTools, Flipper, Chrome DevTools, network debugging, crash analysis, error boundaries, debugging native modules, remote debugging, breakpoints, console logging strategies. Activates for debugging mobile app, react native debugging, flipper, devtools, breakpoints, crash, error, remote debugging, network request debugging, console.log, debugger, react native debugger.
---
# Mobile Debugging Expert
Specialized in debugging React Native and Expo applications across iOS and Android platforms. Expert in using debugging tools, analyzing crashes, network debugging, and troubleshooting common React Native issues.
## What I Know
### Debugging Tools
**React DevTools**
- Component tree inspection
- Props and state inspection
- Profiler for performance analysis
- Component re-render tracking
- Installation: `npm install -g react-devtools`
- Usage: `react-devtools` before starting app
**Chrome DevTools (Remote Debugging)**
- JavaScript debugger access
- Breakpoints and step-through debugging
- Console for logging and evaluation
- Network tab for API inspection
- Source maps for original code navigation
**Flipper (Meta's Debugging Platform)**
- Layout inspector for UI debugging
- Network inspector with request/response details
- Logs viewer with filtering
- React DevTools plugin integration
- Database inspector
- Crash reporter integration
- Performance metrics monitoring
**React Native Debugger (Standalone)**
- All-in-one debugging solution
- Redux DevTools integration
- React DevTools integration
- Network inspection
- AsyncStorage inspection
### Debugging Techniques
**Console Logging Strategies**
```javascript
// Basic logging
console.log('Debug:', value);
// Structured logging
console.log({
component: 'UserProfile',
action: 'loadData',
userId: user.id,
timestamp: new Date().toISOString()
});
// Conditional logging
if (__DEV__) {
console.log('Development only:', debugData);
}
// Performance logging
console.time('DataLoad');
await fetchData();
console.timeEnd('DataLoad');
// Table logging for arrays
console.table(users);
```
**Breakpoint Debugging**
```javascript
// Debugger statement
function processData(data) {
debugger; // Execution pauses here when debugger attached
return data.map(item => transform(item));
}
// Conditional breakpoints in DevTools
// Right-click on line number → Add conditional breakpoint
// Condition: userId === '12345'
```
**Error Boundaries**
```javascript
import React from 'react';
import { View, Text } from 'react-native';
class ErrorBoundary extends React.Component {
state = { hasError: false, error: null };
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
// Log to error tracking service
console.error('Error caught:', error, errorInfo);
logErrorToService(error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<View>
<Text>Something went wrong.</Text>
<Text>{this.state.error?.message}</Text>
</View>
);
}
return this.props.children;
}
}
// Usage
<ErrorBoundary>
<App />
</ErrorBoundary>
```
### Network Debugging
**Intercepting Network Requests**
```javascript
// Using Flipper (recommended)
// Automatically intercepts fetch() and XMLHttpRequest
// Manual interception for custom debugging
const originalFetch = global.fetch;
global.fetch = async (...args) => {
console.log('Fetch Request:', args[0], args[1]);
const response = await originalFetch(...args);
console.log('Fetch Response:', response.status);
return response;
};
// Using React Native Debugger Network tab
// Automatically works with fetch() and axios
```
**API Response Debugging**
```javascript
// Wrapper for API calls with detailed logging
async function apiCall(endpoint, options = {}) {
const startTime = Date.now();
try {
const response = await fetch(endpoint, options);
const duration = Date.now() - startTime;
console.log({
endpoint,
method: options.method || 'GET',
status: response.status,
duration: `${duration}ms`,
success: response.ok
});
if (!response.ok) {
const error = await response.text();
console.error('API Error Response:', error);
throw new Error(`API Error: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('API Call Failed:', {
endpoint,
error: error.message,
duration: `${Date.now() - startTime}ms`
});
throw error;
}
}
```
### Platform-Specific Debugging
**iOS Debugging**
- Safari Web Inspector for JSContext debugging
- Xcode Console for native logs
- Instruments for performance profiling
- Crash logs: `~/Library/Logs/DiagnosticReports/`
- System logs: `log stream --predicate 'processImagePath contains "MyApp"'`
**Android Debugging**
- Chrome DevTools for JavaScript debugging
- Android Studio Logcat for system logs
- ADB logcat filtering: `adb logcat *:E` (errors only)
- Native crash logs: `adb logcat AndroidRuntime:E`
- Monitoring device: `adb shell top`
### Common Debugging Scenarios
**App Crashes on Startup**
```bash
# iOS: Check Xcode console
# Open Xcode → Window → Devices and Simulators → Select device → View logs
# Android: Check logcat
adb logcat *:E
# Look for:
# - Missing native modules
# - JavaScript bundle errors
# - Permission issues
# - Initialization errors
```
**White Screen / Blank Screen**
```javascript
// Add error boundary to root
import { ErrorBoundary } from 'react-error-boundary';
function ErrorFallback({ error }) {
return (
<View>
<Text>App crashed: {error.message}</Text>
</View>
);
}
<ErrorBoundary FallbackComponent={ErrorFallback}>
<App />
</ErrorBoundary>
```
**Red Screen Errors**
```javascript
// Globally catch errors in development
if (__DEV__) {
ErrorUtils.setGlobalHandler((error, isFatal) => {
console.log('Global Error:', { error, isFatal });
// Log to crash reporting service in production
});
}
```
**Network Request Failures**
```bash
# Check if Metro bundler is accessible
curl http://localhost:8081/status
# Check if API is accessible from device
# iOS Simulator: localhost works
# Android Emulator: use 10.0.2.2 instead of localhost
# Real device: use computer's IP address
# Test network connectivity
adb shell ping 8.8.8.8 # Android
```
**Performance Issues**
```javascript
// Use React DevTools Profiler
import { Profiler } from 'react';
function onRenderCallback(
id,
phase,
actualDuration,
baseDuration,
startTime,
commitTime
) {
console.log({
component: id,
phase,
actualDuration,
baseDuration
});
}
<Profiler id="App" onRender={onRenderCallback}>
<App />
</Profiler>
```
## When to Use This Skill
Ask me when you need help with:
- Setting up debugging tools (Flipper, React DevTools)
- Debugging crashes or error screens
- Inspecting network requests and responses
- Finding performance bottlenecks
- Analyzing component re-renders
- Debugging native module issues
- Reading crash logs and stack traces
- Setting up error boundaries
- Remote debugging on physical devices
- Debugging platform-specific issues
- Troubleshooting "white screen" errors
- Inspecting AsyncStorage or databases
## Essential Debugging Commands
### Start Debugging
```bash
# Open React DevTools
react-devtools
# Start app with remote debugging
npm start
# In app: Shake device → Debug Remote JS
# Or: Press "d" in Metro bundler terminal
```
### Platform Logs
```bash
# iOS System Logs (real device)
idevicesyslog
# iOS Simulator Logs
xcrun simctl spawn booted log stream --level=debug
# Android Logs (all)
adb logcat
# Android Logs (app only, errors)
adb logcat *:E | grep com.myapp
# Android Logs (React Native only)
adb logcat ReactNative:V ReactNativeJS:V *:S
# Clear Android logs
adb logcat -c
```
### Performance Analysis
```bash
# iOS: Use Instruments
# Xcode → Open Developer Tool → Instruments → Time Profiler
# Android: Use Systrace
react-native log-android
# React Native performance monitor
# Shake device → Show Perf Monitor
```
### Flipper Setup
```bash
# Install Flipper Desktop
brew install --cask flipper
# For Expo dev clients, add to app.json:
{
"expo": {
"plugins": ["react-native-flipper"]
}
}
# Rebuild dev client
eas build --profile development --platform all
```
## Pro Tips & Tricks
### 1. Custom Dev Menu
Add custom debugging tools to dev menu:
```javascript
import { DevSettings } from 'react-native';
if (__DEV__) {
DevSettings.addMenuItem('Clear AsyncStorage', async () => {
await AsyncStorage.clear();
console.log('AsyncStorage cleared');
});
DevSettings.addMenuItem('Log Redux State', () => {
console.log('Redux State:', store.getState());
});
DevSettings.addMenuItem('Toggle Debug Mode', () => {
global.DEBUG = !global.DEBUG;
console.log('Debug mode:', global.DEBUG);
});
}
```
### 2. Network Request Logger
Comprehensive network debugging:
```javascript
// Create a network logger file
import axios from 'axios';
if (__DEV__) {
axios.interceptors.request.use(
(config) => {
console.log('→ API Request', {
method: config.method?.toUpperCase(),
url: config.url,
data: config.data,
headers: config.headers
});
return config;
},
(error) => {
console.error('→ Request Error', error);
return Promise.reject(error);
}
);
axios.interceptors.response.use(
(response) => {
console.log('← API Response', {
status: response.status,
url: response.config.url,
data: response.data
});
return response;
},
(error) => {
console.error('← Response Error', {
status: error.response?.status,
url: error.config?.url,
data: error.response?.data
});
return Promise.reject(error);
}
);
}
```
### 3. React Query DevTools (for data fetching)
```javascript
import { useReactQueryDevTools } from '@tanstack/react-query-devtools';
function App() {
// Development only
if (__DEV__) {
useReactQueryDevTools();
}
return <YourApp />;
}
```
### 4. Debugging State Updates
Track state changes with custom hook:
```javascript
import { useEffect, useRef } from 'react';
function useTraceUpdate(props, componentName) {
const prev = useRef(props);
useEffect(() => {
const changedProps = Object.entries(props).reduce((acc, [key, value]) => {
if (prev.current[key] !== value) {
acc[key] = {
from: prev.current[key],
to: value
};
}
return acc;
}, {});
if (Object.keys(changedProps).length > 0) {
console.log(`[${componentName}] Changed props:`, changedProps);
}
prev.current = props;
});
}
// Usage
function MyComponent(props) {
useTraceUpdate(props, 'MyComponent');
return <View>...</View>;
}
```
### 5. Debugging Offline/Online State
```javascript
import NetInfo from '@react-native-community/netinfo';
// Monitor network state
NetInfo.addEventListener(state => {
console.log('Network State:', {
isConnected: state.isConnected,
type: state.type,
isInternetReachable: state.isInternetReachable
});
});
```
### 6. Production Error Tracking
Integrate with error tracking services:
```javascript
// Using Sentry (example)
import * as Sentry from '@sentry/react-native';
Sentry.init({
dsn: 'YOUR_SENTRY_DSN',
enableInExpoDevelopment: true,
debug: __DEV__
});
// Capture custom errors
try {
await riskyOperation();
} catch (error) {
Sentry.captureException(error, {
tags: { feature: 'user-profile' },
extra: { userId: user.id }
});
}
```
## Integration with SpecWeave
**During Development**
- Document debugging approaches in increment `reports/`
- Track known issues and workarounds in `spec.md`
- Include debugging steps in `tasks.md` test plans
**Production Monitoring**
- Set up error boundaries for all features
- Integrate crash reporting (Sentry, Bugsnag)
- Document debugging procedures in runbooks
- Track common errors in living documentation

View File

@@ -0,0 +1,573 @@
---
name: native-modules
description: Expert in React Native native modules, bridging JavaScript and native code, writing custom native modules, using Turbo Modules, Fabric, JSI, autolinking, module configuration, iOS Swift/Objective-C modules, Android Kotlin/Java modules. Activates for native module, native code, bridge, turbo module, JSI, fabric, autolinking, custom native module, ios module, android module, swift, kotlin, objective-c, java native code.
---
# Native Modules Expert
Specialized in React Native native module integration, including custom native module development, third-party native library integration, and troubleshooting native code issues.
## What I Know
### Native Module Fundamentals
**What Are Native Modules?**
- Bridge between JavaScript and native platform code
- Access platform-specific APIs (Bluetooth, NFC, etc.)
- Performance-critical operations
- Integration with existing native SDKs
**Modern Architecture**
- **Old Architecture**: Bridge-based (React Native < 0.68)
- **New Architecture** (React Native 0.68+):
- **JSI** (JavaScript Interface): Direct JS ↔ Native communication
- **Turbo Modules**: Lazy-loaded native modules
- **Fabric**: New rendering engine
### Using Third-Party Native Modules
**Installation with Autolinking**
```bash
# Install module
npm install react-native-camera
# iOS: Install pods (autolinking handles most configuration)
cd ios && pod install && cd ..
# Rebuild the app
npm run ios
npm run android
```
**Manual Linking (Legacy)**
```bash
# React Native < 0.60 (rarely needed now)
react-native link react-native-camera
```
**Expo Integration**
```bash
# For Expo managed workflow, use config plugins
npx expo install react-native-camera
# Add plugin to app.json
{
"expo": {
"plugins": [
[
"react-native-camera",
{
"cameraPermission": "Allow $(PRODUCT_NAME) to access your camera"
}
]
]
}
}
# Rebuild dev client
eas build --profile development --platform all
```
### Creating Custom Native Modules
**iOS Native Module (Swift)**
```swift
// RCTCalendarModule.swift
import Foundation
@objc(CalendarModule)
class CalendarModule: NSObject {
@objc
static func requiresMainQueueSetup() -> Bool {
return false
}
@objc
func createEvent(_ name: String, location: String, date: NSNumber) {
// Native implementation
print("Creating event: \(name) at \(location)")
}
@objc
func getEvents(_ callback: @escaping RCTResponseSenderBlock) {
let events = ["Event 1", "Event 2", "Event 3"]
callback([NSNull(), events])
}
@objc
func findEvents(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
// Async with Promise
DispatchQueue.global().async {
let events = self.fetchEventsFromNativeAPI()
resolve(events)
}
}
}
```
```objectivec
// RCTCalendarModule.m (Bridge file)
#import <React/RCTBridgeModule.h>
@interface RCT_EXTERN_MODULE(CalendarModule, NSObject)
RCT_EXTERN_METHOD(createEvent:(NSString *)name location:(NSString *)location date:(nonnull NSNumber *)date)
RCT_EXTERN_METHOD(getEvents:(RCTResponseSenderBlock)callback)
RCT_EXTERN_METHOD(findEvents:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
@end
```
**Android Native Module (Kotlin)**
```kotlin
// CalendarModule.kt
package com.myapp
import com.facebook.react.bridge.*
class CalendarModule(reactContext: ReactApplicationContext) :
ReactContextBaseJavaModule(reactContext) {
override fun getName(): String {
return "CalendarModule"
}
@ReactMethod
fun createEvent(name: String, location: String, date: Double) {
// Native implementation
println("Creating event: $name at $location")
}
@ReactMethod
fun getEvents(callback: Callback) {
val events = WritableNativeArray().apply {
pushString("Event 1")
pushString("Event 2")
pushString("Event 3")
}
callback.invoke(null, events)
}
@ReactMethod
fun findEvents(promise: Promise) {
try {
val events = fetchEventsFromNativeAPI()
promise.resolve(events)
} catch (e: Exception) {
promise.reject("ERROR", e.message, e)
}
}
}
```
```kotlin
// CalendarPackage.kt
package com.myapp
import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ViewManager
class CalendarPackage : ReactPackage {
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
return listOf(CalendarModule(reactContext))
}
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
return emptyList()
}
}
```
**JavaScript Usage**
```javascript
// CalendarModule.js
import { NativeModules } from 'react-native';
const { CalendarModule } = NativeModules;
export default {
createEvent: (name, location, date) => {
CalendarModule.createEvent(name, location, date);
},
getEvents: (callback) => {
CalendarModule.getEvents((error, events) => {
if (error) {
console.error(error);
} else {
callback(events);
}
});
},
findEvents: async () => {
try {
const events = await CalendarModule.findEvents();
return events;
} catch (error) {
console.error(error);
throw error;
}
}
};
// Usage in components
import CalendarModule from './CalendarModule';
function MyComponent() {
const handleCreateEvent = () => {
CalendarModule.createEvent('Meeting', 'Office', Date.now());
};
const handleGetEvents = async () => {
const events = await CalendarModule.findEvents();
console.log('Events:', events);
};
return (
<View>
<Button title="Create Event" onPress={handleCreateEvent} />
<Button title="Get Events" onPress={handleGetEvents} />
</View>
);
}
```
### Turbo Modules (New Architecture)
**Creating a Turbo Module**
```typescript
// NativeCalendarModule.ts (Codegen spec)
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';
export interface Spec extends TurboModule {
createEvent(name: string, location: string, date: number): void;
findEvents(): Promise<string[]>;
}
export default TurboModuleRegistry.getEnforcing<Spec>('CalendarModule');
```
**Benefits of Turbo Modules**
- Lazy loading: Loaded only when used
- Type safety with TypeScript
- Faster initialization
- Better performance via JSI
### Native UI Components
**Custom Native View (iOS - Swift)**
```swift
// RCTCustomViewManager.swift
import UIKit
@objc(CustomViewManager)
class CustomViewManager: RCTViewManager {
override static func requiresMainQueueSetup() -> Bool {
return true
}
override func view() -> UIView! {
return CustomView()
}
@objc func setColor(_ view: CustomView, color: NSNumber) {
view.backgroundColor = RCTConvert.uiColor(color)
}
}
class CustomView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = .blue
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
```
**Custom Native View (Android - Kotlin)**
```kotlin
// CustomViewManager.kt
class CustomViewManager : SimpleViewManager<View>() {
override fun getName(): String {
return "CustomView"
}
override fun createViewInstance(reactContext: ThemedReactContext): View {
return View(reactContext).apply {
setBackgroundColor(Color.BLUE)
}
}
@ReactProp(name = "color")
fun setColor(view: View, color: Int) {
view.setBackgroundColor(color)
}
}
```
**JavaScript Usage**
```javascript
import { requireNativeComponent } from 'react-native';
const CustomView = requireNativeComponent('CustomView');
function MyComponent() {
return (
<CustomView
style={{ width: 200, height: 200 }}
color="red"
/>
);
}
```
### Common Native Module Issues
**Module Not Found**
```bash
# iOS: Clear build and reinstall pods
cd ios && rm -rf build Pods && pod install && cd ..
npm run ios
# Android: Clean and rebuild
cd android && ./gradlew clean && cd ..
npm run android
# Clear Metro cache
npx react-native start --reset-cache
```
**Autolinking Not Working**
```bash
# Verify module in package.json
npm list react-native-camera
# Re-run pod install
cd ios && pod install && cd ..
# Check react-native.config.js for custom linking config
```
**Native Crashes**
```bash
# iOS: Check Xcode console for crash logs
# Look for:
# - Unrecognized selector sent to instance
# - Null pointer exceptions
# - Memory issues
# Android: Check logcat
adb logcat *:E
# Look for:
# - Java exceptions
# - JNI errors
# - Null pointer exceptions
```
## When to Use This Skill
Ask me when you need help with:
- Integrating third-party native modules
- Creating custom native modules
- Troubleshooting native module installation
- Writing iOS native code (Swift/Objective-C)
- Writing Android native code (Kotlin/Java)
- Debugging native crashes
- Understanding Turbo Modules and JSI
- Migrating to New Architecture
- Creating custom native UI components
- Handling platform-specific APIs
- Resolving autolinking issues
## Essential Commands
### Module Development
```bash
# Create module template
npx create-react-native-module my-module
# Build iOS module
cd ios && xcodebuild
# Build Android module
cd android && ./gradlew assembleRelease
# Test module locally
npm link
cd ../MyApp && npm link my-module
```
### Debugging Native Code
```bash
# iOS: Run with Xcode debugger
open ios/MyApp.xcworkspace
# Android: Run with Android Studio debugger
# Open android/ folder in Android Studio
# Print native logs
# iOS
tail -f ~/Library/Logs/DiagnosticReports/*.crash
# Android
adb logcat | grep "CalendarModule"
```
## Pro Tips & Tricks
### 1. Type-Safe Native Modules with Codegen
Use Codegen (New Architecture) for type safety:
```typescript
// NativeMyModule.ts
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';
export interface Spec extends TurboModule {
getString(key: string): Promise<string>;
setString(key: string, value: string): void;
}
export default TurboModuleRegistry.getEnforcing<Spec>('MyModule');
```
### 2. Event Emitters for Native → JS Communication
```swift
// iOS - Emit events to JavaScript
import Foundation
@objc(DeviceOrientationModule)
class DeviceOrientationModule: RCTEventEmitter {
override func supportedEvents() -> [String]! {
return ["OrientationChanged"]
}
@objc
override static func requiresMainQueueSetup() -> Bool {
return true
}
@objc
func startObserving() {
NotificationCenter.default.addObserver(
self,
selector: #selector(orientationChanged),
name: UIDevice.orientationDidChangeNotification,
object: nil
)
}
@objc
func stopObserving() {
NotificationCenter.default.removeObserver(self)
}
@objc
func orientationChanged() {
let orientation = UIDevice.current.orientation
sendEvent(withName: "OrientationChanged", body: ["orientation": orientation.rawValue])
}
}
```
```javascript
// JavaScript - Listen to native events
import { NativeEventEmitter, NativeModules } from 'react-native';
const { DeviceOrientationModule } = NativeModules;
const eventEmitter = new NativeEventEmitter(DeviceOrientationModule);
function MyComponent() {
useEffect(() => {
const subscription = eventEmitter.addListener('OrientationChanged', (data) => {
console.log('Orientation:', data.orientation);
});
return () => subscription.remove();
}, []);
return <View />;
}
```
### 3. Native Module with Callbacks
```kotlin
// Android - Pass callbacks
@ReactMethod
fun processData(data: String, successCallback: Callback, errorCallback: Callback) {
try {
val result = heavyProcessing(data)
successCallback.invoke(result)
} catch (e: Exception) {
errorCallback.invoke(e.message)
}
}
```
```javascript
// JavaScript
CalendarModule.processData(
'input data',
(result) => console.log('Success:', result),
(error) => console.error('Error:', error)
);
```
### 4. Synchronous Native Methods (Use Sparingly)
```swift
// iOS - Synchronous method (blocks JS thread!)
@objc
func getDeviceId() -> String {
return UIDevice.current.identifierForVendor?.uuidString ?? "unknown"
}
```
```javascript
// JavaScript - Synchronous call
const deviceId = CalendarModule.getDeviceId();
console.log(deviceId); // Returns immediately
```
**Warning**: Synchronous methods block the JS thread. Use only for very fast operations (<5ms).
## Integration with SpecWeave
**Native Module Planning**
- Document native dependencies in `spec.md`
- Include native module setup in `plan.md`
- Add native code compilation to `tasks.md`
**Testing Strategy**
- Unit test native code separately
- Integration test JS ↔ Native bridge
- Test on both iOS and Android
- Document platform-specific behaviors
**Documentation**
- Maintain native module API documentation
- Document platform-specific quirks
- Keep runbooks for common native issues

View File

@@ -0,0 +1,550 @@
---
name: performance-optimization
description: Expert in React Native performance optimization including bundle size reduction, memory management, rendering optimization, image optimization, list performance, navigation optimization, startup time, FlatList, memoization, React.memo, useMemo, useCallback, lazy loading, code splitting. Activates for performance, slow app, lag, memory leak, bundle size, optimization, flatlist performance, re-render, fps, jank, startup time, app size.
---
# Performance Optimization Expert
Specialized in optimizing React Native and Expo applications for production. Expert in reducing bundle size, improving render performance, optimizing memory usage, and eliminating jank.
## What I Know
### Bundle Size Optimization
**Analyzing Bundle Size**
```bash
# Generate bundle stats (Expo)
npx expo export --dump-sourcemap
# Analyze with source-map-explorer
npx source-map-explorer bundles/**/*.map
# Check production bundle size
npx expo export --platform ios
du -sh dist/
# Metro bundle visualizer
npx react-native-bundle-visualizer
```
**Reducing Bundle Size**
- Remove unused dependencies
- Use Hermes engine (Android)
- Enable code minification and obfuscation
- Tree shaking for unused code elimination
- Lazy load heavy screens and components
- Optimize asset sizes (images, fonts)
**Hermes Configuration**
```javascript
// app.json (Expo)
{
"expo": {
"jsEngine": "hermes", // Faster startup, smaller bundle
"ios": {
"jsEngine": "hermes"
},
"android": {
"jsEngine": "hermes"
}
}
}
```
### Rendering Performance
**React.memo for Component Optimization**
```javascript
import React, { memo } from 'react';
// Without memo: Re-renders on every parent render
const UserCard = ({ user }) => (
<View>
<Text>{user.name}</Text>
</View>
);
// With memo: Only re-renders when user prop changes
const UserCard = memo(({ user }) => (
<View>
<Text>{user.name}</Text>
</View>
));
// Custom comparison function
const UserCard = memo(
({ user }) => <View><Text>{user.name}</Text></View>,
(prevProps, nextProps) => prevProps.user.id === nextProps.user.id
);
```
**useMemo and useCallback**
```javascript
import { useMemo, useCallback } from 'react';
function UserList({ users, onUserPress }) {
// Expensive calculation - only recalculates when users changes
const sortedUsers = useMemo(() => {
console.log('Sorting users...');
return users.sort((a, b) => a.name.localeCompare(b.name));
}, [users]);
// Stable callback reference - prevents child re-renders
const handlePress = useCallback((userId) => {
console.log('User pressed:', userId);
onUserPress(userId);
}, [onUserPress]);
return (
<FlatList
data={sortedUsers}
renderItem={({ item }) => (
<UserItem user={item} onPress={handlePress} />
)}
keyExtractor={item => item.id}
/>
);
}
```
**Avoiding Inline Functions and Objects**
```javascript
// ❌ BAD: Creates new function on every render
<TouchableOpacity onPress={() => handlePress(item.id)}>
<Text style={{ color: 'blue' }}>Press</Text>
</TouchableOpacity>
// ✅ GOOD: Stable references
const styles = StyleSheet.create({
buttonText: { color: 'blue' }
});
const handleItemPress = useCallback(() => {
handlePress(item.id);
}, [item.id]);
<TouchableOpacity onPress={handleItemPress}>
<Text style={styles.buttonText}>Press</Text>
</TouchableOpacity>
```
### List Performance (FlatList/SectionList)
**Optimized FlatList Configuration**
```javascript
import { FlatList } from 'react-native';
function OptimizedList({ data }) {
const renderItem = useCallback(({ item }) => (
<UserCard user={item} />
), []);
const keyExtractor = useCallback((item) => item.id, []);
return (
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={keyExtractor}
// Performance optimizations
initialNumToRender={10} // Render 10 items initially
maxToRenderPerBatch={10} // Render 10 items per batch
windowSize={5} // Keep 5 screens worth of items
removeClippedSubviews={true} // Unmount off-screen items
updateCellsBatchingPeriod={50} // Batch updates every 50ms
// Memoization
getItemLayout={getItemLayout} // For fixed-height items
// Optional: Performance monitor
onEndReachedThreshold={0.5} // Load more at 50% scroll
onEndReached={loadMoreData}
/>
);
}
// For fixed-height items (huge performance boost)
const ITEM_HEIGHT = 80;
const getItemLayout = (data, index) => ({
length: ITEM_HEIGHT,
offset: ITEM_HEIGHT * index,
index,
});
```
**FlashList (Better than FlatList)**
```javascript
// Install: npm install @shopify/flash-list
import { FlashList } from "@shopify/flash-list";
function SuperFastList({ data }) {
return (
<FlashList
data={data}
renderItem={({ item }) => <UserCard user={item} />}
estimatedItemSize={80} // Required: approximate item height
/>
);
}
```
### Image Optimization
**Fast Image for Better Performance**
```javascript
// Install: npm install react-native-fast-image
import FastImage from 'react-native-fast-image';
function ProfilePicture({ uri }) {
return (
<FastImage
style={{ width: 100, height: 100 }}
source={{
uri: uri,
priority: FastImage.priority.normal, // high, normal, low
cache: FastImage.cacheControl.immutable // Aggressive caching
}}
resizeMode={FastImage.resizeMode.cover}
/>
);
}
```
**Image Optimization Best Practices**
```javascript
// Use appropriate sizes (not 4K images for thumbnails)
<Image
source={{ uri: 'https://example.com/image.jpg?w=200&h=200' }}
style={{ width: 100, height: 100 }}
/>
// Use local images when possible (bundled)
<Image source={require('./assets/logo.png')} />
// Progressive loading
import { Image } from 'react-native';
<Image
source={{ uri: imageUrl }}
defaultSource={require('./placeholder.png')} // iOS only
style={{ width: 200, height: 200 }}
/>
```
### Memory Management
**Preventing Memory Leaks**
```javascript
import { useEffect } from 'react';
function Component() {
useEffect(() => {
// Set up subscription
const subscription = api.subscribe(data => {
console.log(data);
});
// Clean up on unmount (CRITICAL!)
return () => {
subscription.unsubscribe();
};
}, []);
// Timers
useEffect(() => {
const timer = setInterval(() => {
console.log('Tick');
}, 1000);
return () => clearInterval(timer); // Clean up timer
}, []);
}
```
**Image Memory Management**
```javascript
// Clear image cache when memory warning
import { Platform, Image } from 'react-native';
import FastImage from 'react-native-fast-image';
if (Platform.OS === 'ios') {
// iOS: Clear cache on memory warning
DeviceEventEmitter.addListener('RCTMemoryWarning', () => {
FastImage.clearMemoryCache();
});
}
// Manual cache clearing
FastImage.clearMemoryCache();
FastImage.clearDiskCache();
```
### Navigation Performance
**Lazy Loading Screens**
```javascript
import { lazy, Suspense } from 'react';
import { ActivityIndicator } from 'react-native';
// Lazy load heavy screens
const ProfileScreen = lazy(() => import('./screens/ProfileScreen'));
const SettingsScreen = lazy(() => import('./screens/SettingsScreen'));
function App() {
return (
<Suspense fallback={<ActivityIndicator />}>
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Profile" component={ProfileScreen} />
<Stack.Screen name="Settings" component={SettingsScreen} />
</Stack.Navigator>
</NavigationContainer>
</Suspense>
);
}
```
**React Navigation Optimization**
```javascript
// Freeze inactive screens (React Navigation v6+)
import { enableScreens } from 'react-native-screens';
enableScreens();
// Detach inactive screens
<Stack.Navigator
screenOptions={{
detachPreviousScreen: true, // Unmount inactive screens
}}
>
<Stack.Screen name="Home" component={HomeScreen} />
</Stack.Navigator>
```
### Startup Time Optimization
**Reducing Initial Load Time**
```javascript
// app.json - Optimize splash screen
{
"expo": {
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
}
}
}
// Use Hermes for faster startup
{
"expo": {
"jsEngine": "hermes"
}
}
```
**Defer Non-Critical Initialization**
```javascript
import { InteractionManager } from 'react-native';
function App() {
useEffect(() => {
// Critical initialization
initializeAuth();
// Defer non-critical tasks until after animations
InteractionManager.runAfterInteractions(() => {
initializeAnalytics();
initializeCrashReporting();
preloadImages();
});
}, []);
return <AppContent />;
}
```
### Animation Performance
**Use Native Driver**
```javascript
import { Animated } from 'react-native';
function FadeInView({ children }) {
const opacity = useRef(new Animated.Value(0)).current;
useEffect(() => {
Animated.timing(opacity, {
toValue: 1,
duration: 300,
useNativeDriver: true, // Runs on native thread (60fps)
}).start();
}, []);
return (
<Animated.View style={{ opacity }}>
{children}
</Animated.View>
);
}
```
**Reanimated for Complex Animations**
```javascript
// Install: npm install react-native-reanimated
import Animated, { useSharedValue, useAnimatedStyle, withSpring } from 'react-native-reanimated';
function DraggableBox() {
const offset = useSharedValue(0);
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ translateX: offset.value }],
}));
const handlePress = () => {
offset.value = withSpring(offset.value + 50);
};
return (
<Animated.View style={[styles.box, animatedStyle]}>
<Text>Drag me</Text>
</Animated.View>
);
}
```
## When to Use This Skill
Ask me when you need help with:
- Reducing app bundle size
- Optimizing FlatList/SectionList performance
- Fixing memory leaks
- Improving app startup time
- Eliminating jank and frame drops
- Optimizing image loading and caching
- Reducing component re-renders
- Implementing lazy loading
- Optimizing navigation performance
- Analyzing performance bottlenecks
- Using React.memo, useMemo, useCallback effectively
- Implementing 60fps animations
## Performance Monitoring
### React Native Performance Monitor
```javascript
// In app, shake device → Show Perf Monitor
// Shows:
// - JS frame rate
// - UI frame rate
// - RAM usage
```
### Production Performance Monitoring
```javascript
// Install: npm install @react-native-firebase/perf
import perf from '@react-native-firebase/perf';
// Custom trace
const trace = await perf().startTrace('user_profile_load');
await loadUserProfile();
await trace.stop();
// HTTP monitoring (automatic with Firebase)
import '@react-native-firebase/perf/lib/modular/index';
```
## Pro Tips & Tricks
### 1. Profile with React DevTools Profiler
```javascript
import { Profiler } from 'react';
function onRender(id, phase, actualDuration) {
if (actualDuration > 16) { // Slower than 60fps
console.warn(`Slow render in ${id}: ${actualDuration}ms`);
}
}
<Profiler id="UserList" onRender={onRender}>
<UserList users={users} />
</Profiler>
```
### 2. Debounce Expensive Operations
```javascript
import { debounce } from 'lodash';
import { useCallback } from 'react';
function SearchScreen() {
const debouncedSearch = useCallback(
debounce((query) => {
performSearch(query);
}, 300),
[]
);
return (
<TextInput
onChangeText={debouncedSearch}
placeholder="Search..."
/>
);
}
```
### 3. Virtualize Long Lists
Use FlashList or RecyclerListView instead of ScrollView with many items:
```javascript
// ❌ BAD: Renders all 1000 items
<ScrollView>
{items.map(item => <ItemCard key={item.id} item={item} />)}
</ScrollView>
// ✅ GOOD: Only renders visible items
<FlashList
data={items}
renderItem={({ item }) => <ItemCard item={item} />}
estimatedItemSize={100}
/>
```
### 4. Optimize StyleSheets
```javascript
// ❌ BAD: Creates new style object on every render
<View style={{ backgroundColor: 'red', padding: 10 }} />
// ✅ GOOD: Reuses style object
const styles = StyleSheet.create({
container: {
backgroundColor: 'red',
padding: 10
}
});
<View style={styles.container} />
```
## Integration with SpecWeave
**Performance Requirements**
- Document performance targets in `spec.md` (e.g., <2s startup)
- Include performance testing in `tasks.md` test plans
- Measure before/after optimization in increment reports
**Performance Metrics**
- Bundle size: Track in increment completion reports
- Startup time: Measure and document improvements
- FPS: Target 60fps for critical UI interactions
- Memory usage: Set thresholds and monitor
**Living Documentation**
- Document performance optimization strategies
- Track bundle size trends across increments
- Maintain performance runbooks for common issues

View File

@@ -0,0 +1,216 @@
---
name: react-native-setup
description: Expert in React Native environment setup and configuration. Helps with Node.js, Xcode, Android Studio, watchman installation, CocoaPods, simulators, emulators, and troubleshooting setup issues. Activates for environment setup, installation issues, xcode setup, android studio, simulators, emulators, react-native init, expo init, development environment, SDK configuration.
---
# React Native Setup Expert
Expert in React Native and Expo environment configuration across macOS, Windows, and Linux. Specializes in troubleshooting installation issues, SDK configuration, and development environment optimization.
## What I Know
### Prerequisites & Installation
**Node.js & npm**
- Node.js 18.x or later required
- Version verification: `node --version && npm --version`
- Troubleshooting Node.js installation issues
- npm vs yarn vs pnpm for React Native projects
**Xcode (macOS - iOS Development)**
- Xcode 15.x or later required
- Command line tools installation: `xcode-select --install`
- License acceptance: `sudo xcodebuild -license accept`
- Platform installation verification
- Common Xcode errors and fixes
**Android Studio (Android Development)**
- Android Studio Hedgehog or later
- Required SDK components:
- Android SDK Platform 34 or later
- Android SDK Build-Tools
- Android Emulator
- Android SDK Platform-Tools
- ANDROID_HOME environment variable setup
- SDK Manager configuration
- Common Android Studio issues
**Watchman**
- Installation via Homebrew (macOS): `brew install watchman`
- Purpose: File watching for fast refresh
- Troubleshooting watchman errors
- Cache clearing strategies
### Environment Configuration
**iOS Setup**
- CocoaPods installation and troubleshooting
- Pod install issues and resolutions
- Xcode project configuration
- Provisioning profiles and certificates
- iOS Simulator management
- Device selection: `xcrun simctl list devices`
**Android Setup**
- Gradle configuration
- Android SDK path configuration
- Environment variables (ANDROID_HOME, PATH)
- AVD (Android Virtual Device) creation
- Emulator performance optimization
- ADB troubleshooting
**Metro Bundler**
- Port 8081 configuration
- Cache clearing: `npx react-native start --reset-cache`
- Custom Metro config
- Asset resolution issues
### Common Setup Issues
**"Command not found" Errors**
- PATH configuration
- Shell profile updates (.zshrc, .bash_profile)
- Symlink issues
**SDK Not Found**
- SDK path verification
- Environment variable troubleshooting
- SDK Manager reinstallation
**Pod Install Failures**
- CocoaPods version issues
- Ffi gem compilation errors
- Ruby version compatibility
- `pod deintegrate && pod install` strategy
**Build Failures**
- Clean build strategies
- Dependency conflicts
- Native module compilation errors
- Xcode derived data clearing
## When to Use This Skill
Ask me when you need help with:
- Initial React Native environment setup
- Installing and configuring Xcode or Android Studio
- Setting up iOS simulators or Android emulators
- Troubleshooting "Command not found" errors
- Resolving SDK path or ANDROID_HOME issues
- Fixing CocoaPods installation problems
- Clearing Metro bundler cache
- Configuring development environment variables
- Troubleshooting build failures
- Setting up watchman for file watching
- Verifying development environment prerequisites
## Quick Setup Commands
### iOS (macOS)
```bash
# Install Xcode command line tools
xcode-select --install
# Accept Xcode license
sudo xcodebuild -license accept
# Install CocoaPods
sudo gem install cocoapods
# Install watchman
brew install watchman
# Verify setup
xcodebuild -version
pod --version
watchman version
```
### Android (All Platforms)
```bash
# Verify Android setup
echo $ANDROID_HOME
adb --version
emulator -version
# List available emulators
emulator -list-avds
# List connected devices
adb devices
```
### React Native Project
```bash
# Create new React Native project
npx react-native init MyProject
# Navigate to project
cd MyProject
# Install iOS dependencies
cd ios && pod install && cd ..
# Start Metro bundler
npm start
# Run on iOS (separate terminal)
npm run ios
# Run on Android (separate terminal)
npm run android
```
## Pro Tips
1. **Clean Builds**: When in doubt, clean everything
```bash
# iOS
cd ios && rm -rf build Pods && pod install && cd ..
# Android
cd android && ./gradlew clean && cd ..
# Metro
npx react-native start --reset-cache
```
2. **Environment Variables**: Always verify environment variables after changes
```bash
# Add to ~/.zshrc or ~/.bash_profile
export ANDROID_HOME=$HOME/Library/Android/sdk
export PATH=$PATH:$ANDROID_HOME/emulator
export PATH=$PATH:$ANDROID_HOME/platform-tools
# Reload shell
source ~/.zshrc
```
3. **Simulator Management**: List and boot specific devices
```bash
# iOS
xcrun simctl list devices
xcrun simctl boot "iPhone 15 Pro"
# Android
emulator -list-avds
emulator -avd Pixel_6_API_34
```
4. **Quick Health Check**: Verify entire environment
```bash
node --version # Node.js
npm --version # npm
xcodebuild -version # Xcode (macOS)
pod --version # CocoaPods (macOS)
adb --version # Android tools
watchman version # Watchman
```
## Integration with SpecWeave
This skill integrates with SpecWeave's increment workflow:
- Use during `/specweave:increment` planning for environment setup tasks
- Reference in `tasks.md` for setup-related acceptance criteria
- Include in `spec.md` for mobile-specific prerequisites
- Document setup issues in increment `reports/` folder