-
Notifications
You must be signed in to change notification settings - Fork 25.2k
Make +moduleName optional so New Arch modules can drop RCT_EXPORT_MODULE WIP - #58417
Open
christophpurrer wants to merge 1 commit into
Open
Make +moduleName optional so New Arch modules can drop RCT_EXPORT_MODULE WIP #58417christophpurrer wants to merge 1 commit into
+moduleName optional so New Arch modules can drop RCT_EXPORT_MODULE WIP #58417christophpurrer wants to merge 1 commit into
Conversation
...MODULE` WIP Summary: Changelog: [iOS][Breaking] - `+moduleName` is now optional on `RCTBridgeModule` instead of required, so a TurboModule resolved via a module provider need not declare it Investigating whether ObjC Turbo Modules still need `RCT_EXPORT_MODULE` when only the New Architecture is supported. The macro does two things, and they have diverged. **Registration is already dead code in a new-arch-only build.** When both `RCT_REMOVE_LEGACY_MODULE_INTEROP` and `RCT_REMOVE_LEGACY_COMPONENT_INTEROP` are set, `RCT_EXPORT_MODULE` already reduces to a bare name stub (`RCTBridgeModule.h:72-80`), and `RCTRegisterModule` / `RCTGetModuleClasses` are compiled out of `RCTBridge.mm` entirely. Every reader of that registry sits behind the same guards. Discovery instead runs through `RCTTurboModuleManagerDelegate`: `RCTTurboModulePluginClassProvider` over `RctTurboModuleProviderSocket` (populated by `react_module_plugin_providers` in BUCK) internally, and generated `RCTModuleProviders.mm` plus the `NSClassFromString` fallback in OSS. Neither path consults `+moduleName`. **But `+moduleName` was still load-bearing**, which is what actually blocked removing the macro. It was `required` on `RCTBridgeModule`, and `RCTBridgeModuleNameForClass` called it unguarded from `RCTTurboModuleManager.mm:778` — the hot path for every ObjC Turbo Module instantiation. Deleting `RCT_EXPORT_MODULE` therefore produced an unrecognized-selector crash, not graceful degradation. This diff makes absence safe, without touching any macro: - `RCTBridgeModule.h` — move `+ (NSString *)moduleName` below `optional`. It was already effectively optional; `RCTLogBox`, `RCTRedBox` and `RCTDevLoadingView` return `nil` today. - `RCTBridge.mm` and `RCTComponentData.mm` — guard the call with `respondsToSelector:`, letting the pre-existing `name.length == 0 -> NSStringFromClass` fallback handle absence. No behavior change for any module that declares `+moduleName`. This only unblocks removal; it does not remove the macro anywhere. **Why this is tagged Breaking.** `RCTBridgeModule` is public API, and the `required` -> `optional` move changes its published contract. Existing *implementors* are unaffected — an implementation that satisfies a required member also satisfies an optional one — but any code that *consumed* the old guarantee, i.e. called `[cls moduleName]` on an arbitrary `id<RCTBridgeModule>` without a `respondsToSelector:` check, is no longer guaranteed a responder once modules start dropping the macro. Third-party callers doing that need the same guard this diff adds to the two in-tree call sites. **Cxx / ObjC API snapshot: verified unchanged, no regeneration needed.** The `scripts/cxx-api` snapshot format does not encode ObjC `required` / `optional`, and members are sorted alphabetically rather than by declaration order, so neither half of this edit is observable in the `.api` output. Verified empirically rather than assumed — see the test plan. Two caveats worth recording for whoever does the removal: - It is **not** safe to drop the macro everywhere yet. In apps without `react-remove-legacy-module-interop[enabled]` (per-app, rollout incomplete) the `RCTGetModuleClasses()` scan at `RCTTurboModuleManager.mm:761` is live and is the only resolver for a custom-JS-named module absent from the plugin socket; removal there silently yields `nil` for `NativeModules.Foo`. Same for OSS third-party libs where ObjC class name != JS name — both removal flags are opt-in and off by default. Do not drop it from view managers. - Pre-existing latent break, unrelated to this diff: `RCT_EXPORT_MODULE_NO_LOAD` sits at `RCTBridgeModule.h:112-121`, inside the `#else` branch, so `RCT_EXTERN_MODULE` (Swift) is undefined under interop-removal. facebook https://www.internalfb.com/agent-home?session_id=dmh-b3fdfe1a-fc40-4b19-9f59-aff69a033625 Differential Revision: D116325661
@meta-cla
meta-cla
Bot
added
the
CLA Signed
This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.
label
Sep 9, 2026
@facebook-github-tools
facebook-github-tools
Bot
added
p: Facebook
Partner: Facebook
Partner
labels
Sep 9, 2026
@christophpurrer has exported this pull request. If you are a Meta employee, you can view the originating Diff in D116325661.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary:
Changelog:
[iOS][Breaking] -
+moduleNameis now optional onRCTBridgeModuleinstead of required, so a TurboModule resolved via a module provider need not declare itInvestigating whether ObjC Turbo Modules still need
RCT_EXPORT_MODULEwhen onlythe New Architecture is supported. The macro does two things, and they have
diverged.
Registration is already dead code in a new-arch-only build. When both
RCT_REMOVE_LEGACY_MODULE_INTEROPandRCT_REMOVE_LEGACY_COMPONENT_INTEROPareset,
RCT_EXPORT_MODULEalready reduces to a bare name stub(
RCTBridgeModule.h:72-80), andRCTRegisterModule/RCTGetModuleClassesarecompiled out of
RCTBridge.mmentirely. Every reader of that registry sits behindthe same guards. Discovery instead runs through
RCTTurboModuleManagerDelegate:RCTTurboModulePluginClassProvideroverRctTurboModuleProviderSocket(populatedby
react_module_plugin_providersin BUCK) internally, and generatedRCTModuleProviders.mmplus theNSClassFromStringfallback in OSS. Neither pathconsults
+moduleName.But
+moduleNamewas still load-bearing, which is what actually blockedremoving the macro. It was
requiredonRCTBridgeModule, andRCTBridgeModuleNameForClasscalled it unguarded fromRCTTurboModuleManager.mm:778— the hot path for every ObjC Turbo Moduleinstantiation. Deleting
RCT_EXPORT_MODULEtherefore produced anunrecognized-selector crash, not graceful degradation.
This diff makes absence safe, without touching any macro:
RCTBridgeModule.h— move+ (NSString *)moduleNamebelowoptional. It wasalready effectively optional;
RCTLogBox,RCTRedBoxandRCTDevLoadingViewreturn
niltoday.RCTBridge.mmandRCTComponentData.mm— guard the call withrespondsToSelector:, letting the pre-existingname.length == 0 -> NSStringFromClassfallback handle absence.No behavior change for any module that declares
+moduleName. This only unblocksremoval; it does not remove the macro anywhere.
Why this is tagged Breaking.
RCTBridgeModuleis public API, and therequired->optionalmove changes its published contract. Existingimplementors are unaffected — an implementation that satisfies a required member
also satisfies an optional one — but any code that consumed the old guarantee,
i.e. called
[cls moduleName]on an arbitraryid<RCTBridgeModule>without arespondsToSelector:check, is no longer guaranteed a responder once modulesstart dropping the macro. Third-party callers doing that need the same guard this
diff adds to the two in-tree call sites.
Cxx / ObjC API snapshot: verified unchanged, no regeneration needed. The
scripts/cxx-apisnapshot format does not encode ObjCrequired/optional,and members are sorted alphabetically rather than by declaration order, so neither
half of this edit is observable in the
.apioutput. Verified empirically ratherthan assumed — see the test plan.
Two caveats worth recording for whoever does the removal:
react-remove-legacy-module-interop[enabled](per-app, rollout incomplete) theRCTGetModuleClasses()scan atRCTTurboModuleManager.mm:761is live and is theonly resolver for a custom-JS-named module absent from the plugin socket;
removal there silently yields
nilforNativeModules.Foo. Same for OSSthird-party libs where ObjC class name != JS name — both removal flags are
opt-in and off by default. Do not drop it from view managers.
RCT_EXPORT_MODULE_NO_LOADsits atRCTBridgeModule.h:112-121, inside the#elsebranch, soRCT_EXTERN_MODULE(Swift) is undefined underinterop-removal.
facebook
https://www.internalfb.com/agent-home?session_id=dmh-b3fdfe1a-fc40-4b19-9f59-aff69a033625
Differential Revision: D116325661