metro-bundler
SKILL.md
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
const { getDefaultConfig } = require('expo/metro-config');
/** @type {import('expo/metro-config').MetroConfig} */
const config = getDefaultConfig(__dirname);
module.exports = config;
Custom Configuration
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
// 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)
// 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
// 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
# 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;
// Usage in code
import Logo from './assets/logo.svg';
function App() {
return <Logo width={120} height={40} />;
}
Multiple File Extensions
// 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
# 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
// 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
// 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
// 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"
# 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"
# 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"
# 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"
// 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
# 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
# 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
# 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:
# 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
// 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
// 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
// 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
// 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
Weekly Installs
11
Repository
anton-abyzov/specweaveInstalled on
claude-code10
antigravity8
opencode7
cursor7
codex7
gemini-cli7