Skip to content

Commit 6fedaed

Browse files
chrfalchclaude
andcommitted
refactor(ios): remove clang VFS overlay, resolve headers via frameworks
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>
1 parent 27549b8 commit 6fedaed

11 files changed

Lines changed: 236 additions & 800 deletions

File tree

packages/react-native/React-Core-prebuilt.podspec

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,36 +17,51 @@ Pod::Spec.new do |s|
1717
s.author = "Meta Platforms, Inc. and its affiliates"
1818
s.platforms = min_supported_versions
1919
s.source = source
20+
21+
# We vend two xcframeworks that ship together in the prebuilt tarball:
22+
# - React.xcframework: the compiled core. Its per-slice React.framework carries
23+
# every <React/...> header + the framework module map, so `#import <React/...>`
24+
# and `@import React;` resolve through FRAMEWORK_SEARCH_PATHS automatically.
25+
# - ReactNativeHeaders.xcframework: headers-only. Carries every other namespace
26+
# (<react/...>, <yoga/...>, folly, glog, ...). Its headers are flattened into a
27+
# top-level Headers/ (see prepare_command) and exposed via the standard pod
28+
# header search path. (<hermes/...> is supplied by the hermes-engine pod here;
29+
# it is folded into ReactNativeHeaders only on the SwiftPM consumer side.)
30+
# There is no clang VFS overlay.
2031
s.vendored_frameworks = "React.xcframework"
2132

2233
s.preserve_paths = '**/*.*'
23-
s.header_mappings_dir = 'React.xcframework/Headers'
24-
s.source_files = 'React.xcframework/Headers/**/*.{h,hpp}'
25-
26-
s.module_name = 'React'
27-
s.module_map = 'React.xcframework/Modules/module.modulemap'
28-
s.public_header_files = 'React.xcframework/Headers/**/*.h'
34+
s.header_mappings_dir = 'Headers'
35+
s.source_files = 'Headers/**/*.{h,hpp}'
36+
s.public_header_files = 'Headers/**/*.h'
2937

3038
add_rn_third_party_dependencies(s)
3139

32-
# We need to make sure that the React.xcframework is copied correctly - in the downloaded tarball
33-
# the root directory is the framework, but when using it we need to have it in a subdirectory
34-
# called React.xcframework, so we need to move the contents of the tarball into that directory.
35-
# This is done in the prepare_command.
36-
# We need to make sure that the headers are copied to the right place - local tar.gz has a different structure
37-
# than the one from the maven repo
40+
# The downloaded tarball ships React.xcframework and ReactNativeHeaders.xcframework
41+
# at its root. We make sure React.xcframework is in its own subdirectory (the Maven
42+
# tarball lays the framework contents at the root; the local tar.gz has a different
43+
# structure) and flatten ReactNativeHeaders' headers into a top-level Headers/ dir
44+
# so CocoaPods exposes them on the header search path.
3845
s.prepare_command = <<~'CMD'
3946
CURRENT_PATH=$(pwd)
4047
XCFRAMEWORK_PATH="${CURRENT_PATH}/React.xcframework"
4148
42-
# Check if XCFRAMEWORK_PATH is empty
43-
if [ -z "$XCFRAMEWORK_PATH" ]; then
44-
echo "ERROR: XCFRAMEWORK_PATH is empty."
45-
exit 0
49+
# Flatten ReactNativeHeaders' headers (identical across slices) into Headers/
50+
# BEFORE we sweep stray root entries into React.xcframework.
51+
mkdir -p Headers
52+
RNH_XCFRAMEWORK_PATH=$(find "$CURRENT_PATH" -type d -name "ReactNativeHeaders.xcframework" | head -n 1)
53+
if [ -n "$RNH_XCFRAMEWORK_PATH" ]; then
54+
RNH_HEADERS_PATH=$(find "$RNH_XCFRAMEWORK_PATH" -type d -name "Headers" | head -n 1)
55+
if [ -n "$RNH_HEADERS_PATH" ]; then
56+
cp -R "$RNH_HEADERS_PATH/." Headers
57+
fi
58+
rm -rf "$RNH_XCFRAMEWORK_PATH"
4659
fi
4760
4861
mkdir -p "${XCFRAMEWORK_PATH}"
49-
find "$CURRENT_PATH" -mindepth 1 -maxdepth 1 ! -name "$(basename "$XCFRAMEWORK_PATH")" -exec mv {} "$XCFRAMEWORK_PATH" \;
62+
find "$CURRENT_PATH" -mindepth 1 -maxdepth 1 \
63+
! -name "$(basename "$XCFRAMEWORK_PATH")" ! -name "Headers" \
64+
-exec mv {} "$XCFRAMEWORK_PATH" \;
5065
CMD
5166

5267
# If we are passing a local tarball, we don't want to switch between Debug and Release

packages/react-native/scripts/cocoapods/rncore.rb

Lines changed: 27 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -13,26 +13,21 @@
1313
### building ReactNativeCore from source (then this function does nothing).
1414
def add_rncore_dependency(s)
1515
if !ReactNativeCoreUtils.build_rncore_from_source()
16-
# Add the dependency
16+
# `<React/...>` resolves through the vendored React.framework; every other
17+
# namespace (`<react/...>`, `<yoga/...>`, `<hermes/...>`, ...) resolves
18+
# through the flattened ReactNativeHeaders headers that React-Core-prebuilt
19+
# exposes on its header search path. No clang VFS overlay.
1720
s.dependency "React-Core-prebuilt"
1821

1922
current_pod_target_xcconfig = s.to_hash["pod_target_xcconfig"] || {}
2023
current_pod_target_xcconfig = current_pod_target_xcconfig.to_h unless current_pod_target_xcconfig.is_a?(Hash)
2124

22-
# Add VFS overlay flags for both Objective-C and Swift
23-
# The VFS overlay file is pre-resolved at pod install time for each platform slice.
24-
# We reference it directly in the xcframework using the React-VFS.yaml file that
25-
# is written to the React-Core-prebuilt folder during setup_vfs_overlay.
26-
# See scripts/ios-prebuild/__docs__/README.md for more details on VFS overlays.
27-
vfs_overlay_flag = "-ivfsoverlay $(PODS_ROOT)/React-Core-prebuilt/React-VFS.yaml"
28-
current_pod_target_xcconfig["OTHER_CFLAGS"] ||= "$(inherited)"
29-
current_pod_target_xcconfig["OTHER_CFLAGS"] += " #{vfs_overlay_flag}"
30-
current_pod_target_xcconfig["OTHER_CPLUSPLUSFLAGS"] ||= "$(inherited)"
31-
current_pod_target_xcconfig["OTHER_CPLUSPLUSFLAGS"] += " #{vfs_overlay_flag}"
32-
# For Swift, we need to use -Xcc to pass flags to the underlying Clang compiler
33-
# Both the flag and its argument need separate -Xcc prefixes
34-
current_pod_target_xcconfig["OTHER_SWIFT_FLAGS"] ||= "$(inherited)"
35-
current_pod_target_xcconfig["OTHER_SWIFT_FLAGS"] += " -Xcc -ivfsoverlay -Xcc $(PODS_ROOT)/React-Core-prebuilt/React-VFS.yaml"
25+
# HEADER_SEARCH_PATHS may already be a String or an Array (e.g. after
26+
# add_rn_third_party_dependencies); normalize to an Array before appending.
27+
header_search_paths = current_pod_target_xcconfig["HEADER_SEARCH_PATHS"] || []
28+
header_search_paths = header_search_paths.split(" ") if header_search_paths.is_a?(String)
29+
header_search_paths << "\"$(PODS_ROOT)/React-Core-prebuilt/Headers\""
30+
current_pod_target_xcconfig["HEADER_SEARCH_PATHS"] = header_search_paths
3631

3732
s.pod_target_xcconfig = current_pod_target_xcconfig
3833
end
@@ -521,95 +516,55 @@ def self.get_nightly_npm_version()
521516
return latest_nightly
522517
end
523518

524-
# Processes the VFS overlay file from the React.xcframework to resolve the ${ROOT_PATH} placeholder.
525-
# This method should be called from react_native_post_install after pod install completes.
519+
# Configures the xcconfig files for aggregate (main app) targets and third-party pod
520+
# targets so the prebuilt ReactNativeHeaders are resolvable. These targets do not go
521+
# through add_rncore_dependency, so they won't otherwise get the header search path.
526522
#
527-
# The VFS overlay file maps header import paths to their actual locations within the xcframework.
528-
# Since the xcframework contains platform-specific slices, we generate a resolved VFS file for each
529-
# slice and also create a default VFS file that can be used immediately (before script phases run).
530-
def self.process_vfs_overlay()
531-
return if @@build_from_source
532-
533-
prebuilt_path = File.join(Pod::Config.instance.project_pods_root, "React-Core-prebuilt")
534-
xcframework_path = File.join(prebuilt_path, "React.xcframework")
535-
vfs_template_path = File.join(xcframework_path, "React-VFS-template.yaml")
536-
537-
unless File.exist?(vfs_template_path)
538-
rncore_log("VFS overlay template not found at #{vfs_template_path}", :error)
539-
exit 1
540-
end
541-
542-
rncore_log("Processing VFS overlay file...")
543-
544-
# Read the template content
545-
vfs_template_content = File.read(vfs_template_path)
546-
547-
# Write the VFS file - use the top-level xcframework path
548-
# so that ${ROOT_PATH}/Headers points to the xcframework's Headers folder
549-
resolved_vfs_content = vfs_template_content.gsub('${ROOT_PATH}', xcframework_path)
550-
resolved_vfs_path = File.join(prebuilt_path, "React-VFS.yaml")
551-
File.write(resolved_vfs_path, resolved_vfs_content)
552-
rncore_log(" Created VFS overlay at #{resolved_vfs_path}")
553-
554-
rncore_log("VFS overlay setup complete")
555-
end
556-
557-
# Configures the xcconfig files for aggregate (main app) targets to enable VFS overlay for React Native Core.
558-
# This is needed because the main app target does not go through podspec processing,
559-
# so it won't get the VFS overlay flags from add_rncore_dependency.
523+
# `<React/...>` resolves through the vendored React.framework; this adds the search
524+
# path to the flattened ReactNativeHeaders headers (every other namespace). There is
525+
# no clang VFS overlay.
560526
#
561527
# Parameters:
562528
# - installer: The CocoaPods installer object
563529
def self.configure_aggregate_xcconfig(installer)
564530
return if @@build_from_source
565531

566-
prebuilt_path = File.join(Pod::Config.instance.project_pods_root, "React-Core-prebuilt")
567-
vfs_overlay_path = File.join(prebuilt_path, "React-VFS.yaml")
568-
569-
unless File.exist?(vfs_overlay_path)
570-
rncore_log("VFS overlay not found at #{vfs_overlay_path}, skipping prebuilt xcconfig configuration", :error)
571-
exit 1
572-
end
573-
574532
rncore_log("Configuring xcconfig for prebuilt React Native Core...")
575533

576-
vfs_overlay_flag = " -ivfsoverlay \"#{vfs_overlay_path}\""
577-
swift_vfs_overlay_flag = " -Xcc -ivfsoverlay -Xcc \"#{vfs_overlay_path}\""
534+
headers_search_path = " \"$(PODS_ROOT)/React-Core-prebuilt/Headers\""
578535

579-
# Add flags to aggregate target xcconfigs (these are used by the main app target)
536+
# Add the header search path to aggregate target xcconfigs (used by the main app target)
580537
installer.aggregate_targets.each do |aggregate_target|
581538
aggregate_target.xcconfigs.each do |config_name, config_file|
582-
add_vfs_overlay_flags(config_file.attributes, vfs_overlay_flag, swift_vfs_overlay_flag)
539+
add_prebuilt_header_search_paths(config_file.attributes, headers_search_path)
583540
xcconfig_path = aggregate_target.xcconfig_path(config_name)
584541
config_file.save_as(xcconfig_path)
585542
end
586543
end
587544

588-
# Add flags to ALL pod targets (for third-party pods that don't call add_rncore_dependency)
545+
# Add the header search path to ALL pod targets (for third-party pods that don't call add_rncore_dependency)
589546
installer.pod_targets.each do |pod_target|
590547
pod_target.build_settings.each do |config_name, build_settings|
591548
xcconfig_path = pod_target.xcconfig_path(config_name)
592549
next unless File.exist?(xcconfig_path)
593550

594551
xcconfig = Xcodeproj::Config.new(xcconfig_path)
595552

596-
# Check if VFS overlay is already present
597-
other_cflags = xcconfig.attributes["OTHER_CFLAGS"] || ""
598-
next if other_cflags.include?("ivfsoverlay")
553+
# Skip if the prebuilt header search path is already present
554+
header_search_paths = xcconfig.attributes["HEADER_SEARCH_PATHS"] || ""
555+
next if header_search_paths.include?("React-Core-prebuilt/Headers")
599556

600-
add_vfs_overlay_flags(xcconfig.attributes, vfs_overlay_flag, swift_vfs_overlay_flag)
557+
add_prebuilt_header_search_paths(xcconfig.attributes, headers_search_path)
601558
xcconfig.save_as(xcconfig_path)
602559
end
603560
end
604561

605562
rncore_log("Prebuilt xcconfig configuration complete")
606563
end
607564

608-
# Helper method to add VFS overlay flags to an xcconfig attributes map
609-
def self.add_vfs_overlay_flags(attributes, vfs_overlay_flag, swift_vfs_overlay_flag)
610-
ReactNativePodsUtils.add_flag_to_map_with_inheritance(attributes, "OTHER_CFLAGS", vfs_overlay_flag)
611-
ReactNativePodsUtils.add_flag_to_map_with_inheritance(attributes, "OTHER_CPLUSPLUSFLAGS", vfs_overlay_flag)
612-
ReactNativePodsUtils.add_flag_to_map_with_inheritance(attributes, "OTHER_SWIFT_FLAGS", swift_vfs_overlay_flag)
565+
# Helper method to add the prebuilt ReactNativeHeaders header search path to an xcconfig attributes map
566+
def self.add_prebuilt_header_search_paths(attributes, headers_search_path)
567+
ReactNativePodsUtils.add_flag_to_map_with_inheritance(attributes, "HEADER_SEARCH_PATHS", headers_search_path)
613568
# Suppress incomplete umbrella warnings for the prebuilt frameworks (it is expected, as our umbrella headers do not include all headers)
614569
ReactNativePodsUtils.add_flag_to_map_with_inheritance(attributes, "OTHER_SWIFT_FLAGS", " -Xcc -Wno-incomplete-umbrella")
615570
end

packages/react-native/scripts/ios-prebuild/__docs__/README.md

Lines changed: 40 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -111,123 +111,46 @@ The build process uses specific `xcodebuild` flags:
111111
- Build times vary depending on the target platform and configuration
112112
- XCFrameworks support multiple architectures in a single bundle
113113

114-
## Known Issues
115-
116-
The generated XCFrameworks currently use CocoaPods-style header structures
117-
rather than standard framework header conventions. This may cause modularity
118-
issues when:
119-
120-
- Consuming the XCFrameworks in projects that expect standard framework headers
121-
- Building dependent frameworks that rely on proper module boundaries
122-
- Integrating with Swift Package Manager projects expecting modular headers
123-
124-
## VFS Overlay System
125-
126-
The prebuilt XCFrameworks use Clang's Virtual File System (VFS) overlay
127-
mechanism to enable header imports without modifying the actual header file
128-
structure. This is necessary because React Native's headers are organized
129-
differently than standard framework conventions.
130-
131-
### Overview
132-
133-
The VFS overlay creates a virtual mapping between the import paths used in code
134-
(e.g., `#import <react/renderer/graphics/Size.h>`) and the actual physical
135-
locations of headers within the XCFramework. This allows the prebuilt frameworks
136-
to work seamlessly while maintaining the original import syntax.
137-
138-
### Build-Time VFS Generation (`vfs.js`)
139-
140-
The `vfs.js` script creates a VFS overlay template during the prebuild process:
141-
142-
1. **Header Collection** (`headers.js`): Scans all podspec files in the React
143-
Native package to discover header files and their target import paths.
144-
145-
2. **VFS Structure Building**: The `buildVFSStructure()` function creates a
146-
hierarchical directory tree representation from the header mappings. Clang's
147-
VFS overlay requires directories to contain their children in a tree
148-
structure.
149-
150-
3. **YAML Generation**: The `generateVFSOverlayYAML()` function converts the VFS
151-
structure into Clang's expected YAML format.
152-
153-
4. **Template Creation**: The generated overlay uses `${ROOT_PATH}` as a
154-
placeholder for the actual installation path. This template is included in
155-
the XCFramework as `React-VFS-template.yaml`.
156-
157-
#### Key Functions
158-
159-
- `createVFSOverlay(rootFolder)`: Main entry point that generates the complete
160-
VFS overlay YAML string
161-
- `createVFSOverlayContents(rootFolder)`: Creates the VFS overlay object
162-
structure
163-
- `buildVFSStructure(mappings)`: Builds the hierarchical directory tree from
164-
flat mappings
165-
- `resolveVFSOverlay(vfsTemplate, rootPath)`: Replaces `${ROOT_PATH}` with the
166-
actual path
167-
168-
### Runtime VFS Processing (CocoaPods)
169-
170-
When consuming prebuilt frameworks via CocoaPods, the VFS overlay is processed
171-
at pod install time by `rncore.rb`:
172-
173-
#### `process_vfs_overlay()`
174-
175-
Called during `react_native_post_install`, this method:
176-
177-
1. Reads the `React-VFS-template.yaml` from the XCFramework
178-
2. Resolves the `${ROOT_PATH}` placeholder with the actual XCFramework path
179-
3. Writes the resolved overlay to
180-
`$(PODS_ROOT)/React-Core-prebuilt/React-VFS.yaml`
181-
182-
#### `add_rncore_dependency(s)`
183-
184-
Adds VFS overlay compiler flags to podspecs that depend on React Native:
185-
186-
```ruby
187-
# For C/C++ compilation
188-
OTHER_CFLAGS += "-ivfsoverlay $(PODS_ROOT)/React-Core-prebuilt/React-VFS.yaml"
189-
OTHER_CPLUSPLUSFLAGS += "-ivfsoverlay $(PODS_ROOT)/React-Core-prebuilt/React-VFS.yaml"
190-
191-
# For Swift compilation (flags passed to underlying Clang)
192-
OTHER_SWIFT_FLAGS += "-Xcc -ivfsoverlay -Xcc $(PODS_ROOT)/React-Core-prebuilt/React-VFS.yaml"
193-
```
194-
195-
#### `configure_aggregate_xcconfig(installer)`
196-
197-
Configures VFS overlay flags for:
198-
199-
- **Aggregate targets**: Main app targets that don't go through podspec
200-
processing
201-
- **All pod targets**: Third-party pods that don't explicitly call
202-
`add_rncore_dependency`
203-
204-
This ensures all compilation units in the project can resolve React Native
205-
headers through the VFS overlay.
206-
207-
### VFS Overlay Format
208-
209-
The VFS overlay uses Clang's hierarchical YAML format:
210-
211-
```yaml
212-
version: 0
213-
case-sensitive: false
214-
roots:
215-
- name: '${ROOT_PATH}/Headers'
216-
type: 'directory'
217-
contents:
218-
- name: 'react'
219-
type: 'directory'
220-
contents:
221-
- name: 'renderer'
222-
type: 'directory'
223-
contents:
224-
- name: 'Size.h'
225-
type: 'file'
226-
external-contents: '${ROOT_PATH}/Headers/React/react/renderer/Size.h'
227-
```
228-
229-
The structure maps virtual paths (what the compiler sees) to physical paths
230-
(where the files actually exist in the XCFramework).
114+
## Header Resolution (headers-spec layout)
115+
116+
The prebuilt XCFrameworks ship a **headers-spec layout** so that header imports
117+
resolve through plain header/framework search paths — there is **no clang VFS
118+
overlay**. The layout contract is defined and validated in code:
119+
120+
- `headers-spec.js`: the executable layout contract (rules R1–R8) — which
121+
namespaces are hoisted, which carry module maps, and how collisions are
122+
rejected.
123+
- `headers-inventory.js`: scans the source tree to build the live header
124+
inventory that feeds the spec.
125+
- `headers-compose.js`: emits the layout. `emitReactFrameworkHeaders()` writes the
126+
`React/` and bare-aliased headers into every slice's `React.framework/Headers`,
127+
and `buildReactNativeHeadersXcframework()` assembles the headers-only
128+
`ReactNativeHeaders.xcframework` carrying every other namespace (incl. `react/`)
129+
plus the third-party dependency namespaces (`folly`, `glog`, `boost`, `fmt`,
130+
`double-conversion`, `fast_float`). The Hermes public headers (`<hermes/...>`)
131+
are folded in only on the SwiftPM consumer side (`ensureHeadersLayout`); the
132+
published prebuild artifact does not yet carry them (TODO in `xcframework.js`).
133+
134+
### Artifacts
135+
136+
The prebuild (`xcframework.js`) always produces:
137+
138+
- `React.xcframework` — the compiled React core. Each slice's `React.framework`
139+
carries the headers-spec layout (every `<React/...>` header + the framework
140+
module map), which is what both CocoaPods and SwiftPM consume.
141+
- `ReactNativeHeaders.xcframework` — headers-only; carries every other namespace.
142+
Consumed by SwiftPM as a `binaryTarget` and by CocoaPods via the
143+
`React-Core-prebuilt` pod (headers flattened onto the header search path).
144+
145+
### CocoaPods consumption
146+
147+
The `React-Core-prebuilt` pod vends `React.xcframework` (so `<React/...>` and
148+
`@import React;` resolve through the framework module via `FRAMEWORK_SEARCH_PATHS`)
149+
and flattens `ReactNativeHeaders.xcframework`'s headers into a top-level `Headers/`
150+
exposed on the pod header search path (so `<react/...>`, `<yoga/...>`, `<folly/...>`
151+
resolve). `rncore.rb` adds the `HEADER_SEARCH_PATHS` entry to
152+
`React-Core-prebuilt/Headers` for podspec, aggregate (main app), and third-party
153+
pod targets. No `-ivfsoverlay` flags are added.
231154

232155
## Integrating in your project with Cocoapods
233156

0 commit comments

Comments
 (0)