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:
-
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.
-
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)
Description
Since React Native 0.79, Metro enables
unstable_enablePackageExportsby default (Metro 0.82+).@sentry/core'sexportsfield only hasimportandrequireconditions — noreact-nativecondition. Metro misclassifies some ESM imports asisESMImport=false, causing both the ESM and CJS module trees of@sentry/coreto be bundled into native apps.Impact
@sentry/core(Express, PostgresJS, Consola, requestdata, spanStreaming) are also bundled unnecessarily (~100 KB)Root cause
Metro with
enablePackageExportsresolves@sentry/coreusing conditional exports. For most imports, theimportcondition is used → ESM. But three specific origins getisESMImport=falsedue to a Metro detection issue:@sentry/browser/.../contextlines.js@sentry-internal/feedback/.../index.js@sentry-internal/replay/.../index.jsThese files use ESM
importsyntax, but Metro classifies them as CJSrequire. This triggers therequireexport condition → CJS resolution → all 169 internal CJS files get pulled in alongside the 169 ESM files.Verified bundle analysis (sample app, Android)
Fix
Two complementary fixes:
This repo (Metro config): Add
withSentryEsmResolverto redirect CJS → ESM resolutions for@sentry/coreon native platforms. ExpandSERVER_ONLY_MODULE_REto also exclude Express, PostgresJS, Consola, requestdata, and spanStreaming integrations.Upstream (sentry-javascript): Add
"react-native"export condition to@sentry/core,@sentry/browser, and@sentry/reactpackage.json, pointing to ESM builds. This is the long-term fix that works even withoutwithSentryConfig(). See: getsentry/sentry-javascript#XXXXTimeline
@sentry/corev8.0 (May 2024): added conditionalexportswithimport/requireunstable_enablePackageExportsby default → dual bundling startedexportsfield)