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
dist/purchases.js unconditionally requires ./browser/nativeModule at the module level
browser/nativeModule.js does require("@revenuecat/purchases-js-hybrid-mappings")
- Metro bundles all static
require() calls regardless of runtime conditions
- 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:
- 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
- Conditional require: Only
require("@revenuecat/purchases-js-hybrid-mappings") inside the if (shouldUseBrowserMode()) branch in purchases.js
- Add a
react-native field to @revenuecat/purchases-js-hybrid-mappings/package.json pointing to a minimal stub for native platforms
- 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
Problem
react-native-purchasesstatically requires@revenuecat/purchases-js-hybrid-mappings(v17.10.0) viadist/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
dist/purchases.jsunconditionally requires./browser/nativeModuleat the module levelbrowser/nativeModule.jsdoesrequire("@revenuecat/purchases-js-hybrid-mappings")require()calls regardless of runtime conditionsshouldUseBrowserMode()guard only prevents execution, not bundlingAdditionally,
@revenuecat/purchases-js-hybrid-mappings/package.jsonhas abrowserfield but noreact-nativefield, so Metro resolves the UMD browser build instead ofmain:{ "main": "dist/index.js", "browser": "dist/index.umd.js" }Impact
The vast majority of
react-native-purchasesusers 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
resolveRequestoverride to return{ type: "empty" }for this package on non-web platforms: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:
browser/nativeModule.jsto userequire()inside the functions instead of at the top level, so Metro can tree-shake it when unusedrequire("@revenuecat/purchases-js-hybrid-mappings")inside theif (shouldUseBrowserMode())branch inpurchases.jsreact-nativefield to@revenuecat/purchases-js-hybrid-mappings/package.jsonpointing to a minimal stub for native platformsexportswith conditions to@revenuecat/purchases-js-hybrid-mappings/package.json: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