Proposal: improve modulesProvider() failure mode when CFBundleName cannot be used as a Swift module name (SDK 56, iOS 26) #46373
marcospgp
started this conversation in
Proposals and Ideas
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
Proposal: improve
modulesProvider()failure mode whenCFBundleNamecannot be used as a Swift module name (SDK 56, iOS 26)Background
AppContext.modulesProvider()inexpo-modules-corelooks up the autogeneratedExpoModulesProviderclass viaNSClassFromStringin a four-step fallback chain, and returns an emptyModulesProvider()if every step fails. The empty stub means the entire native-module registry is silently empty for the lifetime of the app. EveryrequireNativeModule("X")throwsCannot find native module 'X'. The app dies insideexpo-router/entrybefore any user code runs, with no log or warning fromexpo-modules-coreitself pointing at the actual cause.The lookup chain (from
expo-modules-core/ios/Core/AppContext.swift):How we tripped on this
Our
app.config.tsoverrodeinfoPlist.CFBundleNameto the human-readable display name ("Ink & Quill"). The XcodePRODUCT_NAMEisInkQuill(valid Swift module identifier), so the Swift module name isInkQuill. At runtime:"Ink & Quill.ExpoModulesProvider"InkQuill, notInk & Quill. Spaces and&are invalid in Swift module identifiers."ExpoModulesProvider"Bundle.allFrameworksModulesProvider()We spent roughly four hours bisecting React Native versions, the New Architecture flag, Bun's content-addressed package store, and
expo-modules-autolinkingoutput before readingAppContext.swiftsource. The actual fix is one line inapp.config.ts: stop overridingCFBundleName, let it default to$(PRODUCT_NAME). The user-facing display name lives inCFBundleDisplayName, which Expo's prebuild derives from the top-levelnamefield automatically.Forensics on step [1]
The autogenerated
Pods/Target Support Files/Pods-MyApp/ExpoModulesProvider.swiftdeclares:The intent of
@objc(ExpoModulesProvider)is to register the class with the Objective-C runtime under the bare nameExpoModulesProviderso step [1] succeeds regardless ofCFBundleName. On our setup it does not.nmon the built.appbinary returns only Swift-mangled symbols:There is no
_OBJC_CLASS_$_ExpoModulesProviderentry.NSClassFromString("ExpoModulesProvider")returns nil because the bare-name ObjC class symbol is missing. The same is true for the intermediate.ofile inDerivedData/.../Objects-normal/arm64/ExpoModulesProvider.o.This looks like a Swift 6 / Xcode 26 interaction with
@objc(...)oninternalclasses, but I have not isolated it to a minimal Swift project. Either Swift 6 requirespublicfor@objc(...)to emit the ObjC class symbol, or the autogenerated file needs a different declaration shape. Step [1] is supposed to be the safety net; right now it is not.Environment
56.0.6(also reproduces on56.0.4)0.85.3Proposals
Three independent changes, any subset of which prevents this debugging session from happening again.
1. Validate
CFBundleNameat prebuild timeThe misconfiguration is detectable statically.
expo prebuildcould rejectinfoPlist.CFBundleNamevalues containing characters invalid in a Swift module identifier, and point atCFBundleDisplayNamefor the user-facing name. A clear error at prebuild time instead of an opaque runtime crash.2. Make step [3] fail loud instead of silent
The empty-stub fallback in
modulesProvider()is the failure mode that costs hours. Replacing it with a thrown error that includes the lookup chain converts ten meaningless JS errors into one actionable native error:This is the change that would have unblocked us in seconds. The silent stub also makes step [3] indistinguishable from "this is a brownfield project with no Expo modules at all", which is its only legitimate use case. Distinguishing the two would be valuable on its own.
3. Fix the
@objc(ExpoModulesProvider)registration on Swift 6 / Xcode 26Step [1] is the architectural safety net for any CFBundleName mismatch. If the bare-name ObjC class symbol were emitted, the lookup chain would survive any reasonable misconfiguration. Two avenues to investigate:
Pods-{App}/ExpoModulesProvider.swifttemplate to usepublic classinstead ofinternal class. Test whether that restores the_OBJC_CLASS_$_ExpoModulesProvidersymbol on Swift 6 / Xcode 26.NSClassFromStringof a string derived fromCFBundleName. The autogenerated provider could register itself in a known runtime registry at load time (+loador a static initializer), andmodulesProvider()could read from that registry. This is harder but removes the dependency on the runtime-derived class name entirely.Why a proposal and not a bug
This is two separate issues: a user-side misconfiguration that should have been caught earlier, and a registration failure mode that may or may not be Expo-side. I have not isolated either to a minimal repro and I do not have time to do so right now. The lookup chain itself is the lever Expo controls, and any of the three proposals above neutralizes the trap regardless of which root cause is closer to Expo's domain. Happy to test patches against our setup if it helps.
Beta Was this translation helpful? Give feedback.
All reactions