Skill: Analyze JS Bundle Size
May 25, 2026 · View on GitHub
Use source-map-explorer and Expo Atlas to visualize what's in your JavaScript bundle.
Quick Command
# 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 visual treemap output (source-map-explorer, Expo Atlas). When regression checks include device flows, use
agent-devicefor app evidence; install it through the environment's approved/trusted path or ask the user if verification needs it and it is missing. Treemap analysis itself may still require exported reports, browser screenshots, or human review.
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:
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+):
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
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:

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.jsComponents(125.29 KB) - Touchable, ScrollView, etc.Animated(79.48 KB) - Animation systemvirtualized-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
# 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:
npx expo-atlas

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
rspack build --analyze
bundle-stats / statoscope
# 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
// rspack.config.js
const { RsdoctorRspackPlugin } = require('@rsdoctor/rspack-plugin');
module.exports = {
plugins: [
process.env.RSDOCTOR && new RsdoctorRspackPlugin(),
].filter(Boolean),
};
Run with:
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-esor specific imports - Moment.js: Replace with
date-fnsordayjs - Intl polyfills: Check Hermes API and method coverage before removing them
- AWS SDK: Import specific services only
Code Examples
Identify Barrel Import Impact
// 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
# 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)
npx bundle-stats compare baseline-stats.json current-stats.json
Quick Commands
React Native CLI:
# 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:
# 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 - Fix barrel import issues
- bundle-tree-shaking.md - Enable dead code elimination
- bundle-library-size.md - Check library sizes before adding