Repository navigation
fix(app,database): resolve platform native module when bundlers apply package exports - #9361
Conversation
cefb1cb to
973fa83
Compare
… package exports Packages imported getReactNativeModule and setReactNativeModule from the @react-native-firebase/app/dist/module/internal/nativeModule subpath. The app package exports map resolves that subpath to the exact file nativeModule.js, so bundlers that apply package exports (Re.Pack) load the web resolver on Android and iOS. DatabaseSyncTree then gets the empty web fallback and every Realtime Database off() throws 'undefined is not a function'. Re-export both functions from the app internal index, where the relative ./nativeModule import resolves per platform, import them from there, and add a dependency-cruiser rule that rejects the subpath import outside app.
Keep the getReactNativeModule e2e import on the platform-resolving internal entry instead of the package-exports nativeModule subpath.
Keep the e2e harness resolver import on the platform-resolving internal entry for consistency with the package-exports fix.
973fa83 to
40ec7b8
Compare
mikehardy
left a comment
There was a problem hiding this comment.
Thanks @AndriiSerheniuk — clear root-cause write-up and the right fix: re-export getReactNativeModule / setReactNativeModule from the app internal barrel so bundlers that honor package exports (Re.Pack) resolve the platform files instead of the web stub. The dependency-cruiser guard and PNV contract-test update are good follow-through.
Maintainer follow-ups on the branch (rebased onto current main):
- switch the leftover App e2e resolver import off the
…/internal/nativeModulesubpath - same consistency change for the e2e harness
tests/globals.jsimport
Local verification: Android/iOS full e2e green on the rebased tip; app-area e2e after the globals change also green. Approving.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #9361 +/- ##
============================================
+ Coverage 70.86% 74.77% +3.91%
- Complexity 2243 2286 +43
============================================
Files 446 624 +178
Lines 25532 41119 +15587
Branches 4343 5228 +885
============================================
+ Hits 18091 30741 +12650
- Misses 6136 8789 +2653
- Partials 1305 1589 +284
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
Description
With a bundler that applies package
exports— Re.Pack (Rspack/webpack) withenablePackageExports: true— every Realtime Database unsubscribe throws on Android and iOS:Calling the unsubscribe returned by
onValue(...), callingref.off(), or unmounting a component whose effect cleanup unsubscribes all throw, and the native listener is never detached.Cause.
DatabaseSyncTreeimportsgetReactNativeModulefrom the subpath@react-native-firebase/app/dist/module/internal/nativeModule.packages/app/package.jsonmapsexports["./dist/module/internal/*"]to the literal file./dist/module/internal/*.js. Anexportstarget is an exact file, so the bundler does not trynativeModule.android.js/nativeModule.ios.js: it loadsnativeModule.js, which re-exportsnativeModuleWeb. The webgetReactNativeModulereturns the JS fallback thatdatabase/lib/index.tsregisters, and on Android and iOS that fallback (web/RNFBDatabaseModule.android.ts/.ios.ts) is{}, so.offis undefined.Subscribing works because
DatabaseQuery.ongoes through the module'snativecomposite, built inapp/lib/internal/registry/nativeModule.ts, which imports../nativeModulerelatively; a relative import gets the platform file.Who is affected.
enablePackageExports: true. On React Native 0.87 that is every Re.Pack app:@react-native/asset-utilsshipsexportswithoutmain, so a stock Re.Pack app does not build with package exports off.dist/module/package.json({"type":"module"}) is the nearestpackage.jsonfor these files and has noexports, so Metro resolves them as files and picks the platform variant.getReactNativeModulecallers that use the same subpath fail the same way under these bundlers:firestore(setLogLevelviainternal/staticNativeModule.ts),messaging,phone-number-verification. ThesetReactNativeModuleimporters (analytics,app-check,auth,database,firestore,functions,in-app-messaging,remote-config,storage) only write to the web registry, which is harmless on native, and are switched for consistency.Fix.
app/lib/internal/index.tsre-exportsgetReactNativeModuleandsetReactNativeModulefrom./nativeModule. The build keeps that relative import extensionless, so every bundler resolves it per platform:nativeModule.android/.ioson native,nativeModule.js→nativeModuleWebon web and macOS, as before.packages/*/libfiles that imported@react-native-firebase/app/dist/module/internal/nativeModuleimport from@react-native-firebase/app/dist/module/internalinstead.satellites-no-platform-split-subpath, failslint:depswhen a package outsideappimportsapp/lib/internal/nativeModuledirectly.phone-number-verification/__tests__/nativeModuleContract.test.tsinjects its fixture throughTurboModuleRegistry.get, like the othernativeModuleContracttests. It previously registered the fixture in the web registry, which only reached the library because Jest resolved the same subpath to the web file too.Related issues
None found.
Release Summary
Fix Realtime Database unsubscribe (
off()) and other native-module lookups throwingundefined is not a functionon Android and iOS when the bundler applies package exports (Re.Pack).Checklist
AndroidiOSOther(macOS, web)e2etests added or updated inpackages/\*\*/e2ejesttests added or updated inpackages/\*\*/__tests__Test Plan
Device repro. Minimal app: React Native 0.87.1 default template,
@react-native-firebase/app+database26.4.0. One button:onValue(ref(getDatabase(), path), …), then call the returned unsubscribe after the first value. The fix was applied to the installeddistfiles exactly as this PR builds them.enablePackageExports: true, 26.4.0TypeError: undefined is not a functionTypeError: undefined is not a functionModule that
DatabaseSyncTreereaches in each bundle:internal/nativeModule.android.js/internal/nativeModule.ios.jsinternal/nativeModule.js(→nativeModuleWeb.js)internal/index.js→internal/nativeModule.android.js/internal/nativeModule.ios.js;nativeModuleWeb.jsis no longer in the native bundleRepo checks.
yarn lerna:prepare,yarn lint:js,yarn lint:deps,yarn tsc:compile,yarn tsc:compile:consumer: pass.yarn lint:depswith the old import restored indatabase/lib/DatabaseSyncTree.tsfails withsatellites-no-platform-split-subpath.yarn tests:jest: all suites pass exceptapp/__tests__/iosSimulatorHelpers.test.js, which needs the iOS 27 e2e simulator and fails identically onmainon this machine.🔥