RevenueCat / RevenueCat/react-native-purchases

@revenuecat/purchases-js-hybrid-mappings (~512 KB web SDK) included in iOS/Android bundles

Open
#1,643 3 comments 3 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
1.2k
Forks
117
Avg merge
14h 40m
Merged PRs (30d)
40

Description

Problem

react-native-purchases statically requires @revenuecat/purchases-js-hybrid-mappings (v17.10.0) via dist/browser/nativeModule.js, which gets bundled into iOS and Android JavaScript bundles even though it is never executed on native platforms.

This adds ~512 KB of dead code (Svelte runtime, DOM manipulation, RevenueCat web billing SDK) to every native app using this library.

Root cause
  1. dist/purchases.js unconditionally requires ./browser/nativeModule at the module level
  2. browser/nativeModule.js does require("@revenuecat/purchases-js-hybrid-mappings")
  3. Metro bundles all static require() calls regardless of runtime conditions
  4. The shouldUseBrowserMode() guard only prevents execution, not bundling

Additionally, @revenuecat/purchases-js-hybrid-mappings/package.json has a browser field but no react-native field, so Metro resolves the UMD browser build instead of main:

{
  "main": "dist/index.js",
  "browser": "dist/index.umd.js"
}
Impact

The vast majority of react-native-purchases users are building native iOS/Android apps and do not use web billing. They are paying a ~512 KB bundle size penalty for a feature they never use.

Current workaround

We are using a Metro resolveRequest override to return { type: "empty" } for this package on non-web platforms:

// metro.config.js
if (
  platform !== "web" &&
  (moduleName === "@revenuecat/purchases-js-hybrid-mappings" ||
    moduleName.startsWith("@revenuecat/purchases-js-hybrid-mappings/"))
) {
  return { type: "empty" };
}

This is safe because shouldUseBrowserMode() guarantees the browser code path is never reached on native, but it should not be necessary.

Suggested solutions

Any of these would fix it for all users without manual workarounds:

  1. Lazy require: Change browser/nativeModule.js to use require() inside the functions instead of at the top level, so Metro can tree-shake it when unused
  2. Conditional require: Only require("@revenuecat/purchases-js-hybrid-mappings") inside the if (shouldUseBrowserMode()) branch in purchases.js
  3. Add a react-native field to @revenuecat/purchases-js-hybrid-mappings/package.json pointing to a minimal stub for native platforms
  4. Add exports with conditions to @revenuecat/purchases-js-hybrid-mappings/package.json:
    "exports": {
      ".": {
        "react-native": "./dist/stub.js",
        "browser": "./dist/index.umd.js",
        "default": "./dist/index.js"
      }
    }
    

Option 1 or 2 would be the least breaking change and would benefit every React Native user immediately.

Environment
  • react-native-purchases: 9.5.4
  • @revenuecat/purchases-js-hybrid-mappings: 17.10.0
  • Metro bundler (via Expo SDK 55)
  • Platform: iOS / Android

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by tracing the static import from dist/purchases.js through browser/nativeModule.js, then inspect the dependency package.json and the Metro workaround described in the issue. Compare the proposed lazy, conditional, and platform-resolution approaches; done means native iOS and Android bundles no longer include the web mappings code while web behavior remains available.

Written by the indexing model from the issue text.

Assessment

Tech stack
android, ios, react-native, typescript
Domain
build-system, mobile, performance
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.