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-device for 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:

Bundle Treemap from source-map-explorer

The treemap shows:

  • Hierarchy: node_modules/react-native/Libraries/ → individual files
  • Size: Box area proportional to file size (KB shown in labels)
  • Major components visible:
    • react-native (724.18 KB, 80.5%)
    • Renderer (208.44 KB) - ReactNativeRenderer-prod.js, ReactFabric-prod.js
    • Components (125.29 KB) - Touchable, ScrollView, etc.
    • Animated (79.48 KB) - Animation system
    • virtualized-lists (57.57 KB) - FlatList internals

Click on any section to drill down into that directory.

Limitation: May lose ~30% info due to mapping issues.

Method 2: Expo Atlas

More accurate for Expo projects (or with workaround for bare RN).

For Expo Projects

# 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 Treemap

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

FindingProblemSolution
Entire library importedBarrel exportsUse direct imports
Duplicate packagesMultiple versionsDedupe in package.json
Dev dependencies in bundleIncorrect importsCheck conditional imports
Large polyfillsUnnecessary for HermesRemove (see native-sdks-over-polyfills.md)
Moment.js with localesBloated date librarySwitch to date-fns or dayjs

Common Offenders

  • Lodash full import: Use lodash-es or specific imports
  • Moment.js: Replace with date-fns or dayjs
  • Intl polyfills: Check Hermes API and method coverage before removing them
  • AWS SDK: Import specific services only

Code Examples

Identify Barrel Import Impact

// 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