Skip to content

fix(metro): Dual ESM/CJS bundling of @sentry/core on RN 0.79+ and unnecessary server integrations in native bundles #6262

@antonis

Description

@antonis

Description

Since React Native 0.79, Metro enables unstable_enablePackageExports by default (Metro 0.82+). @sentry/core's exports field only has import and require conditions — no react-native condition. Metro misclassifies some ESM imports as isESMImport=false, causing both the ESM and CJS module trees of @sentry/core to be bundled into native apps.

Impact

  • ~1 MB of duplicate CJS code ships in every RN 0.79+ app (Hermes compiles everything — no dead code elimination)
  • Server-only integrations from @sentry/core (Express, PostgresJS, Consola, requestdata, spanStreaming) are also bundled unnecessarily (~100 KB)
  • Affects the final APK/IPA shipped to stores

Root cause

Metro with enablePackageExports resolves @sentry/core using conditional exports. For most imports, the import condition is used → ESM. But three specific origins get isESMImport=false due to a Metro detection issue:

  • @sentry/browser/.../contextlines.js
  • @sentry-internal/feedback/.../index.js
  • @sentry-internal/replay/.../index.js

These files use ESM import syntax, but Metro classifies them as CJS require. This triggers the require export condition → CJS resolution → all 169 internal CJS files get pulled in alongside the 169 ESM files.

Verified bundle analysis (sample app, Android)

@sentry/core files Bundle size
Before fix 338 CJS + 338 ESM = 676 5.09 MB
After fix 0 CJS + 314 ESM = 314 4.52 MB

Fix

Two complementary fixes:

  1. This repo (Metro config): Add withSentryEsmResolver to redirect CJS → ESM resolutions for @sentry/core on native platforms. Expand SERVER_ONLY_MODULE_RE to also exclude Express, PostgresJS, Consola, requestdata, and spanStreaming integrations.

  2. Upstream (sentry-javascript): Add "react-native" export condition to @sentry/core, @sentry/browser, and @sentry/react package.json, pointing to ESM builds. This is the long-term fix that works even without withSentryConfig(). See: getsentry/sentry-javascript#XXXX

Timeline

  • @sentry/core v8.0 (May 2024): added conditional exports with import/require
  • RN 0.79 (April 2025): Metro enabled unstable_enablePackageExports by default → dual bundling started
  • RN < 0.79: not affected (Metro ignores exports field)

Metadata

Metadata

Assignees

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions