refactor(iOS): include resources like bundles into the precompiled XCFrameworks#57305
Draft
chrfalch wants to merge 7 commits into
Draft
refactor(iOS): include resources like bundles into the precompiled XCFrameworks#57305chrfalch wants to merge 7 commits into
chrfalch wants to merge 7 commits into
Conversation
The minimal machinery to build the packaged header structures: - headers-spec.js: the executable layout contract (rules R1-R8) — which namespaces are hoisted into the React framework, which carry module maps, and how collisions are rejected. - headers-inventory.js: scans the source tree and classifies every shipped header (language surface + modularizability bucket) — the input to the spec. computeInventory() feeds the build in-memory; the CLI writes a JSON manifest. - headers-compose.js: emits the layout — writes the <React/...> headers + umbrella + module map into each React.framework slice (detected by the framework's presence), and assembles the headers-only ReactNativeHeaders.xcframework (every other namespace + deps + Hermes). Called by xcframework.js during compose. This is the alternative header source that lets consumers resolve React Native headers without a clang VFS overlay. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Emit the headers-spec layout unconditionally and delete the VFS overlay across
JS, CI publish, and Ruby. Consumers resolve headers the way the SwiftPM branch
does: <React/...> from the vendored React.framework, every other namespace from
ReactNativeHeaders. No root Headers/ on the xcframework, no VFS.
JS:
- xcframework.js: always emit the React.framework spec layout and build
ReactNativeHeaders.xcframework (was gated behind RN_ZERO_I_LAYOUT=1). Remove
the legacy header path entirely — the podspec->root-Headers enumeration,
createModuleMapFile, and copyHeaderFilesToSlices — so the published
React.xcframework is a standard framework (Info.plist + per-slice
React.framework/{Headers,Modules}), no root Headers/ or Modules/. Ship
ReactNativeHeaders.xcframework inside the reactnative-core tarball (sibling of
React.xcframework) so the prebuilt pod can vend both; keep the standalone
ReactNativeHeaders.xcframework.tar.gz for the SPM path. Drop the
React-VFS-template.yaml emit and the ./vfs import.
- vfs.js: deleted (its only consumer was xcframework.js).
- types.js: drop the now-unused VFSEntry/VFSOverlay/HeaderMapping types.
- replace-rncore-version.js: drop the React-VFS.yaml preservation rationale.
Ruby/CocoaPods:
- React-Core-prebuilt.podspec: vend React.xcframework (its per-slice
React.framework + module map serves <React/...> and @import React via
FRAMEWORK_SEARCH_PATHS); flatten ReactNativeHeaders' headers into a top-level
Headers/ in prepare_command and expose them via the pod header search path.
Drop the VFS-era root header_mappings_dir/module_map.
- rncore.rb: remove the -ivfsoverlay injection and process_vfs_overlay;
add_rncore_dependency and configure_aggregate_xcconfig now add a
HEADER_SEARCH_PATHS to React-Core-prebuilt/Headers for podspec, aggregate, and
third-party targets.
- react_native_pods.rb: drop the process_vfs_overlay post-install call.
Docs: replace the "VFS Overlay System" section with the headers-spec layout;
drop the obsolete "Known Issues" (pre-headers-spec) section.
Verified end-to-end: prebuild compose produces a VFS-free, root-Headers-free
React.xcframework; rn-tester pod install + xcodebuild (prebuilt path) BUILD
SUCCEEDED with zero -ivfsoverlay.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
React.framework is a clang module; when an SPM consumer precompiles it, a
modular React/ header that #imports <react/...> hit
-Wnon-modular-include-in-framework-module because the lowercase react/
namespace (served from ReactNativeHeaders, per R1's Linux/Windows-safe layout)
was deliberately kept out of any module.
Give react/ a module where it already lives instead of relocating it (relocation
would require case-folding react.framework -> React.framework, which only works
on case-insensitive filesystems):
- headers-spec.js: drop the react/ namespace-module exemption so its
objc-modular-candidates get a module; emit that module as
ReactNativeHeaders_react (a module literally named 'react' would alias the
React framework module on a case-insensitive filesystem). Module names are
internal; <react/...> still resolves by header path and is now modular.
- headers-inventory.js: classify C++ default member initializers in aggregates
(e.g. struct { NSString *family = nil; } in RCTFontProperties.h) as ObjC++ so
these are not misclassified objc-modular-candidate and pulled into a plain
ObjC module they cannot compile in.
The unguarded ObjC/C react/ headers (e.g. JSRuntimeFactoryCAPI.h) now resolve
modularly; the C++ react/renderer/* includes are #ifdef __cplusplus-guarded and
skipped during the ObjC module emit, so they need no module.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
In prebuilt mode the React core pods' code + headers live entirely in React.xcframework / React-Core-prebuilt. Re-installing their SOURCE podspecs made them ship duplicate headers that shadow the prebuilt artifact and break the React framework's clang explicit-module precompile (-Wnon-modular-include-in-framework-module) under Xcode 26. Install those core pods as dependency-only FACADES instead: generated podspecs with no sources/headers, installed via :path (so nothing is fetched), each depending on React-Core-prebuilt. Version, subspecs, default_subspec and resources (e.g. the privacy manifest) are DERIVED from the real podspec so the facade stays graph- and resource-equivalent to the source pod. With the shadowing gone the React module precompiles cleanly with SWIFT_ENABLE_EXPLICIT_MODULES on, so the Xcode-26 workaround (#53457) is removed. The prebuilt header search path + ReactNativeHeaders module-map activation are consolidated into a single post-install injection site (configure_aggregate_xcconfig); add_rncore_dependency now only declares the React-Core-prebuilt dependency. rn-tester's NativeComponentExample uses the canonical <React/...> include for RCTFabricComponentsPlugins.h (resolved from the framework) so it builds against the facaded React-RCTFabric in prebuilt mode. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`yarn format-check` (prettier) was failing CI on PR #57285. Run prettier on the ios-prebuild headers scripts (headers-compose.js, headers-inventory.js), replace-rncore-version.js, and __docs__/README.md so format-check passes. No logic changes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
9b0b73d to
2c1bf1d
Compare
…tarball The prebuilt React core now ships two xcframeworks — React.xcframework and the headers-only ReactNativeHeaders.xcframework. React-Core-prebuilt's prepare_command flattens the latter's Headers (including module.modulemap) into the pod. The compose step only tar'd React.xcframework, so consumers got no React-Core-prebuilt/Headers/module.modulemap and failed the build with "module map file ... not found". Tar both xcframeworks. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2c1bf1d to
28e4489
Compare
…prebuilt/SwiftPM
Source builds get React-Core's non-header resources from the podspec
resource_bundles, but in the prebuilt path the source pods aren't installed
(CocoaPods facades) / not present (SwiftPM), so they were lost. Reproduce them in
the artifact at compose time via scripts/ios-prebuild/framework-resources.js:
- Privacy manifest: merge the PrivacyInfo.xcprivacy of the pods baked into
React.framework into one manifest at the framework root, where Xcode's
privacy-report aggregation picks it up (React.framework is dynamic; no runtime).
- RCTI18nStrings: rebuild RCTI18nStrings.bundle from React/I18n/strings/*.lproj
inside React.framework, resolved at runtime by the framework-aware loader.
RCTLocalizedString.mm now resolves the strings bundle from the code's own bundle
first (React.framework when prebuilt/SwiftPM) with a main-bundle fallback (static
source builds), keeping the graceful nil -> default behaviour.
React-Core.podspec declares RCTI18nStrings and React-Core_privacy in one
resource_bundles map (a later resource_bundles= had silently overwritten the
first), so source builds ship both again. The RNCore facade no longer carries
React-Core_privacy: the prebuilt artifact owns these resources now.
Red/green unit + integration tests in __tests__/framework-resources-test.js.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
28e4489 to
a9e8b06
Compare
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
Depends on #57285
In source builds,
React-Coreships non-header resources — its privacy manifest (PrivacyInfo.xcprivacy) and its localized strings (RCTI18nStrings) — via the podspec's resource_bundles. In the prebuilt path those source pods aren't installed (CocoaPods facades) or aren't present at all (SwiftPM), so these resources were silently dropped: prebuilt/SwiftPM apps shipped no React Native privacy manifest, and localized strings were unavailable.This embeds them in React.framework at prebuild time so they ship uniformly across CocoaPods-prebuilt and SwiftPM, with source builds unchanged:
It also fixes a latent bug in
React-Core.podspec: a later resource_bundles = was overwriting the earlier resource_bundle =, so source builds had stopped shipping RCTI18nStrings. Both bundles are now declared together. With the artifact owning these resources, the prebuilt RNCore facade no longer carries them.Changelog:
[IOS][FIXED] - Ship React-Core's privacy manifest and localized strings (RCTI18nStrings) inside the prebuilt React.xcframework, so CocoaPods-prebuilt and SwiftPM apps include them
Test Plan