Guides

How to Analyze a React Native Bundle with Source Maps

How source maps turn production React Native bundle bytes into package, file, comparison, and reporting evidence without uploading source code.

12 min read

A production React Native bundle is easy to measure and hard to explain. Its file size gives you one number, but not which dependency, application module, or bundler definition produced those bytes. A matching source map turns that opaque output into evidence you can inspect.

Bundle analysis is therefore not just “draw the bundle as rectangles.” The useful job is to connect byte ranges in the generated JavaScript back to stable source identities, keep unattributed bytes visible, and make the result comparable across builds. That is the problem Bundle Drop Sight is designed to solve locally in the browser.

Why a large JavaScript bundle becomes a product problem

Every byte in a production bundle has to move through part of the application lifecycle. It is generated during the build, packaged into a native binary or delivered as an update, stored on disk, then loaded by the JavaScript engine. Depending on the engine and build mode, it may also need to be parsed, compiled, or evaluated before the application becomes interactive.

A larger bundle does not guarantee a slow application, but it increases the amount of work available on several critical paths:

  • Cold start: more generated code can mean more data to read and more work before the root application runs. Expo’s current bundle-load metric guidance recommends reducing bundle size as one way to improve the time spent loading and evaluating JavaScript.
  • Native build size: a production JavaScript bundle is one part of the .ipa, .apk, or .aab. Reducing it will not shrink native frameworks or assets, but unnecessary JavaScript still becomes unnecessary application weight.
  • OTA delivery: when JavaScript ships outside a new store binary, more bytes can increase download time, local storage, retry cost, and the delay before an update is ready. Patch delivery can reduce transfer size, but the complete resulting bundle still matters; patch delivery and full-bundle fallback solve different parts of that problem.
  • Low-end devices and poor networks: storage throughput, CPU performance, memory pressure, and network quality vary across a fleet. A change that is invisible on a recent development phone can be noticeable in the long tail.
  • Maintenance: bundle growth often reveals code the product no longer needs—an abandoned dependency, two libraries solving the same problem, duplicated package variants, a feature imported at startup, or a large generated data file embedded in JavaScript.

The last category is why knowing the total is not enough. “The iOS bundle is 5.3 MB” does not tell an engineer what to change. “A newly added validation package contributes 246 KB, an old utility package still contributes 138 KB, and one application screen grew by 90 KB” creates concrete questions: Is the package used? Is the old one removable? Is the screen embedding data or importing a broad entry point?

Bundle analysis makes hidden accumulation reviewable. The goal is not to chase the smallest possible number at the expense of maintainability. It is to know which bytes are intentional, which are accidental, and whether the user-facing value justifies the cost.

What a React Native bundle analyzer actually measures

Metro resolves the modules reachable from an entry point, transforms them, and writes generated JavaScript for a target platform. Metro can also produce a standard source map beside that output; its source-map format documentation describes the standard fields and Metro-specific metadata.

A source map does not say that an original TypeScript file is a particular size on disk. It records generated line and column positions and connects them to source paths. A byte-attribution tool can use consecutive generated mappings to answer a narrower question:

How many bytes in this exact generated bundle are associated with each mapped source?

That distinction matters. The resulting numbers describe the production JavaScript artifact, not the size of an .ipa, .apk, or Android App Bundle. They do not include native frameworks, compiled native code, all packaged assets, store compression, or device-specific delivery. Expo’s app-size guide similarly separates JavaScript bundle analysis from inspection of complete native build artifacts.

From generated bytes to source-level evidence

The bundle supplies the bytes. The matching source map supplies generated positions and source identities. Sight joins them locally before grouping the result.

Production JavaScript bundle

The exact generated file whose byte spans will be measured.

Matching Metro source map

Version 3 mappings from generated positions back to source paths.

Analyze together in the browser

Attribute each mapped generated span

Source paths are normalized, dependency packages are identified, and unattributed bytes stay visible instead of disappearing from the total.

Group without changing the measured total

Application

Project source files

Screens, features, services, and other app-owned modules.

Dependencies

Top-level packages

Package totals with their largest contributing files.

Runtime

Metro bootstrap

Bundler runtime and framework startup definitions.

Unmapped

Bytes without ownership

Generated bytes the supplied map does not attribute.

The same normalized result powers the treemap, ranked lists, inspection, and exports.

Sight reads the generated spans, counts their UTF-8 bytes, and groups normalized source paths into four explicit categories:

CategoryWhat it representsWhat to investigate
Application codeSource owned by the app rather than a packageLarge screens, embedded data, generated files, and features loaded at startup
DependenciesSources under node_modules, grouped by top-level packageUnexpected libraries, duplicated capabilities, and expensive imports
Metro / runtimeMetro bootstrap and recognized runtime definitionsChanges in tooling, bundler output, or framework startup code
Unmapped codeBundle bytes not attributed by the supplied mapMap completeness, generated wrappers, line endings, and other unattributed output

The four categories reconcile with the generated bundle total. “Unmapped” is not silently discarded to make the visualization look cleaner.

The bundle and source map must be one pair

Source maps are positional. A map from yesterday’s bundle can still be valid JSON and still produce misleading ownership for today’s bytes. Even a small source change can move generated offsets throughout a minified file.

React Native’s release-debugging guide makes the same requirement for symbolication: use a source map that corresponds to the exact build being inspected. Sight checks the bundle’s source-map comment and the map’s declared bundle filename when those signals exist. A renamed artifact can still be legitimate, so a filename mismatch is reported rather than treated as mathematical proof that the files differ.

For reliable analysis:

  1. Generate the production JavaScript bundle and source map in the same command.
  2. Use the same platform, entry point, Metro configuration, development flag, and minification settings.
  3. Keep Hermes bytecode disabled for this analysis path; Sight V1 analyzes JavaScript bundle text rather than compiled Hermes bytecode.
  4. Treat the pair as one build artifact when storing or comparing it.

The Sight generation guide provides current Expo and bare React Native commands. The shortest path for Bundle Drop users is:

bash
npx @gfean/react-native-bundle-drop sight

The CLI detects the project type, generates one platform’s matching files, and opens that build in Analyze mode. The browser cannot preselect arbitrary local files from a public page, so the CLI performs a local handoff instead. The bundle and source map remain on the machine.

Read the treemap from the outside in

A treemap uses area to encode attributed bytes. It is valuable because one large rectangle can reveal a dependency or application file that is difficult to notice in a flat list. It is also easy to overread.

Start with the category boundary, then inspect the largest package or file inside it:

  1. Check the total composition. Is the change mainly application code, dependencies, runtime, or unmapped output?
  2. Open the largest dependency packages. A package total is more useful than dozens of disconnected files from the same dependency.
  3. Inspect the largest files inside that package. Sight shows the five largest contributors so the package name becomes an investigation path rather than a verdict.
  4. Switch to file view for application code. Search for feature and source-path names, then use file rank to understand whether a selected file is exceptional inside its parent group.
  5. Use the list for exact values. Rectangle area is excellent for pattern recognition; ranked rows are better for precise review and keyboard navigation.

The built-in storefront demo illustrates the process without using customer code. Its application group contains catalog, cart, checkout, search, and order features. The dependency side contains realistic package distributions. Selecting a rectangle explains its byte share and source path; selecting a package reveals its largest files.

The right conclusion is not always “remove the largest rectangle.” A large package may replace substantial application code, provide native behavior, or be essential to the product. Bundle analysis identifies where to ask a question. It does not calculate the value of the dependency for you.

Compare builds to explain a regression

A single analysis answers what is inside one build. It cannot tell you which change caused a bundle to grow. Comparing two screenshots or two package lists is also unreliable because layout and rank can change even when most sources remain stable.

Sight Compare analyzes a baseline pair and a current pair separately, then matches normalized source paths and package names. The output records bytes before and after, a signed delta, percentage change where a baseline exists, and whether each entry was added, removed, increased, decreased, or unchanged.

A comparison is two complete analyses, not two filenames

Each build is attributed first. Sight then matches normalized package and file identities to calculate byte deltas.

Baseline

Known release or reference build

Production bundle plus its matching source map, analyzed independently.

Current

Candidate build under review

Production bundle plus its matching source map, analyzed independently.

Match normalized identities

Calculate before, after, and signed change

Package and file entries are classified as added, removed, increased, decreased, or unchanged, with category totals kept separate.

Review

Largest regressions first

Sort by absolute byte change instead of scanning two unrelated maps.

Explain

Trace package and file deltas

Move from the total change to the source paths responsible for it.

Share

Export the evidence

Produce a local report for a pull request, release review, or audit.

For a useful comparison, keep the build conditions equivalent. Compare iOS with iOS or Android with Android, use the same entry point, and avoid changing minification or Metro configuration unless that configuration change is the subject of the investigation.

A practical pull-request workflow is:

  1. Generate the baseline from the target branch or last accepted release.
  2. Generate the current build from the proposed change.
  3. Load both matching pairs in Compare mode.
  4. Review total and category deltas before individual packages.
  5. Filter to added, removed, or changed entries and search for the feature under review.
  6. Inspect the largest absolute changes, including decreases that may offset growth elsewhere.
  7. Export the comparison when the reasoning belongs in the pull request or release record.

This makes a review statement specific: “the bundle grew” becomes “application code added 98 KB, dependency code added 134 KB, and 246 KB came from one newly introduced package while an older 138 KB package disappeared.” The exact numbers will differ by project, but the reasoning structure remains useful.

Use exports as evidence, not another source of truth

Sight can create local PDF and standalone HTML reports, image exports for a single treemap, Markdown summaries, and normalized JSON. Comparison exports focus on reports and developer-readable data rather than pretending that two treemaps are one image.

An export is useful for:

  • attaching a bundle-size explanation to a pull request;
  • recording a release baseline before a large dependency migration;
  • sharing the largest application files with a feature owner;
  • preserving a normalized result without sharing the original source map.

The report is derived from the selected artifacts and analysis rules at that moment. Keep the original bundle and matching map when the result must be reproducible. For error tracking, source maps have a different operational role: they must be associated with the exact running bundle identity, as described in Bundle Drop observability.

Local analysis changes the privacy boundary

Source maps can contain source paths and may include sourcesContent, which can contain original source text. Treat them as sensitive engineering artifacts even when the generated bundle is already distributed inside an application.

Sight performs analysis in the browser and does not upload the selected bundle or source map to Bundle Drop. The source-map payload is parsed locally, and source contents are not retained in the normalized result or exports. The same local-only rule applies to the CLI handoff and report generation.

Local processing does not remove every responsibility. A downloaded HTML, JSON, PDF, PNG, SVG, or copied Markdown report can still reveal package names, source paths, and size information. Review the report before placing it in a public issue or repository.

Sight belongs to an existing tool ecosystem

Bundle visualization has useful prior art. source-map-explorer established a clear model for attributing generated bytes through source maps and presenting the result as a treemap or structured output. react-native-bundle-visualizer wraps the React Native generation workflow around that approach.

For Expo projects, Expo Atlas provides a dependency-graph and transformed-module view integrated with Expo tooling. Atlas is often the stronger choice when the investigation is about how a module was transformed, what it imports, or why it appears in the graph. A source-map treemap is often the more direct view when the question is generated byte ownership.

Sight does not make those tools obsolete. Its focus is a public, privacy-first workflow for React Native and Expo artifacts, package and file inspection, build-to-build comparison, and local reporting. The Sight acknowledgements and licenses page distinguishes software redistributed by Sight from projects acknowledged as ecosystem and algorithmic context.

Know what the result cannot prove

Bundle bytes are one engineering signal. They are not a complete performance model.

  • A smaller source may execute more work than a larger source.
  • Byte attribution does not measure startup CPU time, memory pressure, render cost, or network behavior.
  • Raw JavaScript size is different from compressed transfer size and Hermes bytecode size.
  • Tree shaking, platform resolution, Babel transforms, and Metro configuration can change what appears in the output.
  • A large dependency is not automatically unused, replaceable, or harmful.
  • Unmapped bytes may reflect normal bundler output, but a sudden change still deserves investigation.

Use React Native DevTools and native profiling tools for runtime behavior. Use Expo Atlas when dependency-graph and transformed-module context is the missing evidence. Use full .ipa, .apk, or .aab inspection when the question is installed application size.

Make bundle analysis part of change review

The most useful bundle analysis is not a one-time cleanup exercise. It is a repeatable way to explain consequential changes before they become release surprises.

Keep a baseline for important release lines, generate comparable production artifacts, inspect category changes before individual rectangles, and record the packages or files responsible for meaningful deltas. Then decide whether the growth is justified by the feature, whether a dependency can be narrowed or replaced, or whether no action is needed.

That is the practical value of a React Native bundle analyzer: not a smaller number by default, but a defensible explanation of where the generated bytes came from and what changed between two builds.

Interested in safer OTA deployments?

Read the implementation guides or connect a React Native project when you are ready to test the release workflow.