Skip to content

fix(misc): add react-native export condition for Hermes-safe resolution#2393

Merged
mandarini merged 1 commit into
supabase:masterfrom
BLOCKMATERIAL:fix/hermes-rn-esm-resolution
May 22, 2026
Merged

fix(misc): add react-native export condition for Hermes-safe resolution#2393
mandarini merged 1 commit into
supabase:masterfrom
BLOCKMATERIAL:fix/hermes-rn-esm-resolution

Conversation

@BLOCKMATERIAL
Copy link
Copy Markdown
Contributor

Problem

v2.106.1 (#2381) fixed the CJS bundle so it no longer contains import('@opentelemetry/api') — hermesc-safe. However, Metro on Expo SDK 55+ never resolves the CJS bundle: with unstable_enablePackageExports enabled by default (Metro 0.82+), the "import" condition wins over "require", so Metro picks dist/index.mjs, which still contains the dynamic import(). Hermes rejects it at parse time:

error: Invalid expression encountered
...if(otelModulePromise===null)otelModulePromise=import(...)
                                                 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~

Every React Native release build on 2.106.0/2.106.1 fails on Expo SDK 55. Reproducible with expo export:embed --platform android && hermesc <bundle> — both expo and hermesc are pre-bundled with Expo SDK 55.

This is a different code path from the one #2381 addressed (Metro consuming the CJS bundle). Reports in #2380 from users still on 2.106.1 confirm Expo SDK 55 still fails.

Refs #2380.

Fix

Add a "react-native" condition to exports["."] and exports["./cors"], placed before "import" and "require", pointing to the existing Hermes-safe dist/*.cjs bundles. Metro recognizes the react-native condition by default (Metro's resolverMainFields and unstable_conditionNames).

 "exports": {
   ".": {
+    "react-native": {
+      "types": "./dist/index.d.cts",
+      "default": "./dist/index.cjs"
+    },
     "import": {
       "types": "./dist/index.d.mts",
       "default": "./dist/index.mjs"
     },
     "require": {
       "types": "./dist/index.d.cts",
       "default": "./dist/index.cjs"
     }
   },

Effect by consumer:

Consumer Before After
Vite / webpack / Turbopack / Next.js importdist/index.mjs unchanged
Node ESM importdist/index.mjs unchanged
Node CJS / Bun / Deno requiredist/index.cjs unchanged
Metro (React Native) importdist/index.mjs (broken) react-nativedist/index.cjs

The fix does not change the build pipeline, doesn't touch @supabase/tracing, and doesn't introduce new code paths. It's a pure exports-map change: Metro picks the bundle that PR #2381 already made Hermes-safe.

Verification

Reproduced and verified locally on the original failing build (expo export:embed + bundled hermesc from React Native 0.83.6, Expo SDK 55.0.26):

  1. Baseline (2.106.1, unchanged): hermesc exits 2, Invalid expression encountered at the import(OTEL_PKG) call in dist/index.mjs (Metro resolved the ESM bundle).
  2. With this patch applied to 2.106.1's package.json only (no rebuild):
    • Metro now resolves dist/index.cjs.
    • Bundle contains zero import( occurrences.
    • hermesc exits 0; output .hbc (11M) is valid.

Test artifacts available on request — happy to attach the bundle/log.

Regression test

Extends test/bundle-hermes-compat.test.cjs with a 4th check on package.json itself:

  • Both exports["."] and exports["./cors"] define a "react-native" condition.
  • "react-native" appears before "import" and "require" (Node-style conditional exports resolve in declaration order — placement matters when bundlers also match later conditions).
  • "react-native" resolves to a .cjs target (Hermes-safe by construction).

This catches future regressions where someone reorders the conditions or accidentally points react-native at the ESM bundle. Wired into the existing module-resolution CI job through npm run test:hermes-compat.

Why this approach over alternatives

  • Alternative A: separate /tracing entrypoint. Cleanest architecturally but requires an opt-in API change for OTel users — breaking change for 2.106.x consumers who already wired up tracing.
  • Alternative B: rewrite loadOtel() to eval('import(...)') or similar. Sidesteps hermesc but trips browser strict CSP (unsafe-eval).
  • Alternative C (this PR): react-native export condition. Zero source code changes, zero consumer-facing changes, leverages the Hermes-safe CJS bundle that fix(misc): hide dynamic import from hermesc #2381 already produces. Metro unstable_enablePackageExports was added specifically to support this kind of platform-specific resolution.

Metro on Expo SDK 55+ (with `unstable_enablePackageExports` enabled by
default) resolves the `import` export condition before `require`, so it
picks `dist/index.mjs` — which still contains the dynamic `import()` for
`@opentelemetry/api`. Hermes rejects `import()` at parse time, breaking
every React Native release build with `Invalid expression encountered`.

PR supabase#2381 fixed the CJS bundle (Hermes-safe), but Metro never sees it
because the `import` condition wins. Adding a `react-native` condition
that points to `dist/index.cjs` makes Metro pick the Hermes-safe bundle
without affecting any other consumer:

- Vite / webpack / Turbopack / Next.js: unchanged (still resolve `import`).
- Node ESM: unchanged (still resolves `import`).
- Node CJS / Bun / Deno: unchanged (still resolve `require`).
- Metro (React Native): now resolves `react-native` -> `dist/index.cjs`.

Verified locally on the original reproducer (`expo export:embed` +
`hermesc`): on `@supabase/supabase-js@2.106.1` with only this exports
change applied, hermesc exits 0 and the resulting `.hbc` contains no
`import()` expressions.

Adds a 4th assertion to `test/bundle-hermes-compat.test.cjs`:
- `exports["."]` and `exports["./cors"]` both define `"react-native"`.
- `"react-native"` appears before `"import"` and `"require"` in key order
  (Node-style conditional exports resolve in declaration order).
- `"react-native"` resolves to a `.cjs` target (Hermes-safe).

Refs supabase#2380.
@BLOCKMATERIAL BLOCKMATERIAL requested a review from a team as a code owner May 21, 2026 20:29
@mandarini mandarini self-assigned this May 22, 2026
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 22, 2026

Open in StackBlitz

@supabase/auth-js

npm i https://pkg.pr.new/@supabase/auth-js@2393

@supabase/functions-js

npm i https://pkg.pr.new/@supabase/functions-js@2393

@supabase/postgrest-js

npm i https://pkg.pr.new/@supabase/postgrest-js@2393

@supabase/realtime-js

npm i https://pkg.pr.new/@supabase/realtime-js@2393

@supabase/storage-js

npm i https://pkg.pr.new/@supabase/storage-js@2393

@supabase/supabase-js

npm i https://pkg.pr.new/@supabase/supabase-js@2393

commit: 3d1e51c

@mandarini mandarini merged commit c72cc56 into supabase:master May 22, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants