Skip to content

refactor(ios): remove clang VFS overlay, resolve headers via new ReactNativeHeaders framework#57285

Draft
chrfalch wants to merge 6 commits into
mainfrom
chrfalch/remove-vfs-overlay
Draft

refactor(ios): remove clang VFS overlay, resolve headers via new ReactNativeHeaders framework#57285
chrfalch wants to merge 6 commits into
mainfrom
chrfalch/remove-vfs-overlay

Conversation

@chrfalch

@chrfalch chrfalch commented Jun 19, 2026

Copy link
Copy Markdown
Collaborator

Summary

The prebuilt React.xcframework previously relied on a Clang VFS overlay (React-VFS.yaml) to make headers importable, because the headers were laid out in CocoaPods-style namespaced folders rather than standard framework conventions. The overlay had to be generated at build time, re-resolved at pod-install time per slice, and injected as -ivfsoverlay flags into every Obj-C, C++, and Swift compile (including aggregate and third-party pod targets).

This is fragile, hard to reason about, and incompatible with SwiftPM consumption.

This PR removes the VFS overlay entirely and resolves headers through standard framework/header-search-path mechanics instead.

Headers are now emitted into the artifact according to an explicit, executable spec:

  • React.xcframework — each slice's React.framework carries every <React/...> header plus a framework module map, so #import <React/...> and @import React resolve through FRAMEWORK_SEARCH_PATHS automatically.
  • ReactNativeHeaders.xcframework (new, headers-only) — carries every other namespace (<react/...>, <yoga/...>, folly, glog, …), shipped alongside in the prebuilt tarball and exposed via a single header search path.
  • This makes ReactNativeDependencies binary-only.

No clang VFS overlay, no per-target -ivfsoverlay flags. The layout is driven by a single source of truth (headers-spec.js, rules R1–R8) that both the prebuild compose step and downstream SwiftPM tooling derive from, so the shipped header set cannot drift from the spec.

Source headers are byte-identical to the repo — the only consumer-facing changes needed is bare-form angle includes (#import <RCTAppDelegate.h>#import <React/RCTAppDelegate.h>).

Key changes

  • New: headers-spec.js (the executable layout contract), headers-compose.js (emitter for both xcframeworks), headers-inventory.js (podspec-driven header classifier feeding the spec + a diagnostic manifest).
  • Removed: vfs.js, VFS types in types.js, and the VFS processing/flag-injection paths in rncore.rb and xcframework.js.
  • Updated: React-Core-prebuilt.podspec (vends both xcframeworks, flattens ReactNativeHeaders headers into Headers/ via prepare_command), rncore.rb / react_native_pods.rb (header search path instead of overlay flags), README (VFS docs replaced with the new model).

Changelog:

[IOS] [CHANGED] - Remove the Clang VFS overlay from prebuilt React Native Core; resolve headers via React.xcframework + a new headers-only ReactNativeHeaders.xcframework

Test Plan

  • rn-tester builds against the prebuilt React-Core-prebuilt pod (Debug + Release) with no -ivfsoverlay flags present in the generated xcconfigs.
  • rn-tester builds against React native source code (without any prebuilt artifacts)
  • #import <React/...>, @import React;, and the relocated namespaces (<react/...>, <yoga/...>, folly/glog) all resolve.
  • Prebuilt tarball contains both React.xcframework and ReactNativeHeaders.xcframework pod install flattens headers into React-Core-prebuilt/Headers.
  • Switch RN-tester between Debug/Release and verify that both React.xcframework and ReactNativeHeaders.xcframework are changed between debug and release correctly.

@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 Jun 19, 2026
Comment thread packages/react-native/scripts/ios-prebuild/xcframework.js Dismissed
Comment thread packages/react-native/scripts/ios-prebuild/xcframework.js Dismissed
@facebook-github-tools facebook-github-tools Bot added the Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. label Jun 19, 2026
@cortinico

Copy link
Copy Markdown
Contributor

CI is really red @chrfalch

@chrfalch

chrfalch commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator Author

CI is really red @chrfalch

I know - working on it! Forgot to put it in draft, @cortinico :)

@chrfalch chrfalch marked this pull request as draft June 22, 2026 08:19
chrfalch added a commit that referenced this pull request Jun 22, 2026
`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>
chrfalch and others added 5 commits June 22, 2026 18:40
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>
@chrfalch chrfalch force-pushed the chrfalch/remove-vfs-overlay branch from 920e88b to 684fcf7 Compare June 22, 2026 16:45
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. p: Expo Partner: Expo Partner Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants