Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 17 additions & 14 deletions packages/react-native/scripts/ios-prebuild/__docs__/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,11 +126,14 @@ overlay**. The layout contract is defined and validated in code:
the `React/` and bare-aliased headers into every slice's
`React.framework/Headers`, and `buildReactNativeHeadersXcframework()`
assembles the headers-only `ReactNativeHeaders.xcframework` carrying every
other namespace (incl. `react/`) plus the third-party dependency namespaces
(`folly`, `glog`, `boost`, `fmt`, `double-conversion`, `fast_float`). The
Hermes public headers (`<hermes/...>`) are folded in only on the SwiftPM
consumer side (`ensureHeadersLayout`); the published prebuild artifact does
not yet carry them (TODO in `xcframework.js`).
other React Native namespace (incl. `react/`). The third-party dependency
namespaces (`folly`, `glog`, `boost`, `fmt`, `double-conversion`,
`fast_float`, `SocketRocket`) ship in the separate headers-only
`ReactNativeDependenciesHeaders.xcframework` sidecar, built by the
dependencies prebuild. The Hermes public headers (`<hermes/...>`) are folded
into the published `ReactNativeHeaders` when the hermes-ios headers are
staged; without them the compose ships no `hermes/`, unless `--require-hermes`
is set, which makes it fail closed.

### Artifacts

Expand All @@ -139,15 +142,15 @@ The prebuild (`xcframework.js`) always produces:
- `React.xcframework` — the compiled React core. Each slice's `React.framework`
carries the headers-spec layout (every `<React/...>` header + the framework
module map). CocoaPods consumes that layout directly, through
`FRAMEWORK_SEARCH_PATHS`. SwiftPM consumes the same headers indirectly: the
XCFramework is not a member of the Swift package graph, so the consumer side
stages a copy of `React.framework/Headers` into
`ReactHeadersTarget/include/React` and rewrites `framework module React` to a
plain `module React`, vended as the `ReactHeaders` target (see
`spm-header-paths-contract.md` in the SwiftPM docs).
- `ReactNativeHeaders.xcframework` — headers-only; carries every other
namespace. Consumed by SwiftPM as a `binaryTarget` and by CocoaPods via the
`React-Core-prebuilt` pod (headers flattened onto the header search path).
`FRAMEWORK_SEARCH_PATHS`. SwiftPM consumes the same headers indirectly,
because the XCFramework is not a member of the Swift package graph. The
consumer side stages a copy of `React.framework/Headers` into
`ReactHeadersTarget/include/React`, rewrites `framework module React` to a
plain `module React`, and vends it as the `ReactHeaders` target (see
[spm-header-paths-contract.md](../../spm/__docs__/spm-header-paths-contract.md)).
- `ReactNativeHeaders.xcframework` — headers-only; carries every other React
Native namespace. Consumed by SwiftPM as a `binaryTarget` and by CocoaPods via
the `React-Core-prebuilt` pod (headers flattened onto the header search path).

### CocoaPods consumption

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,13 +120,14 @@ resolving `<react/...>` through `React.framework` requires case-folding
`react.framework` → `React.framework`, which only works on case-insensitive
filesystems. The header-search-path route (R2) is exact everywhere.

**R2 — every other namespace ships in ONE headers-only xcframework,
**R2 — every other React Native namespace ships in ONE headers-only xcframework,
`ReactNativeHeaders`.** Namespace dirs at its `Headers/` root: `react/`,
`yoga/`, `jsi/`, `cxxreact/`, `React_RCTAppDelegate/`, … **including the
third-party deps namespaces** (folly/glog/boost/fmt/double-conversion/
fast_float, copied out of the ReactNativeDependencies artifact — which thereby
becomes binary-only). SPM/Xcode auto-serve a binaryTarget's `Headers/` on the
consumer's search path, so everything resolves with zero flags.
`yoga/`, `jsi/`, `cxxreact/`, `React_RCTAppDelegate/`, … The third-party deps
namespaces (folly, glog, boost, fmt, double-conversion, fast_float,
SocketRocket) are **not** here: they ship in the headers-only
`ReactNativeDependenciesHeaders` sidecar built by the deps prebuild. SPM/Xcode
auto-serve a binaryTarget's `Headers/` on the consumer's search path, so
everything resolves with zero flags.

**R3 — NO include rewriting, anywhere.** Shipped headers are byte-identical to
the repo. If a header's includes don't work in the packaged layout, the fix is
Expand Down Expand Up @@ -271,11 +272,11 @@ module.

### `buildReactNativeHeadersXcframework`

1. Stage all R2 entries, then copy the six deps namespaces from
`third-party/ReactNativeDependencies.xcframework/Headers`. A declared deps
namespace that is missing is a **hard error** — previously a warn-and-ship,
which once produced a silently deps-less artifact (1.6 MB instead of 11 MB).
2. Optionally fold in the `hermes/` public headers (consumer-side compose path).
1. Stage all R2 entries. The deps namespaces are not copied here; they ship in
the `ReactNativeDependenciesHeaders` sidecar, whose namespace guards live in
`buildDepsHeadersXcframework` (`headers-xcframework.js`).
2. Fold in the `hermes/` public headers when they are staged (both the prebuild
compose and the consumer-side compose path).
3. Write the R10 umbrella files, then the R5 module map.
4. Compile a stub static archive per slice (headers-only artifacts still need a
library for `xcodebuild -create-xcframework`) and compose the xcframework.
Expand All @@ -296,13 +297,17 @@ mtime + hermes presence).
| `#import <React/RCTMountingManager.h>` | same, **textual** via the R9 entry | R9 |
| `#import <react/renderer/...>` (C++) | `ReactNativeHeaders/Headers` search path, textual | R2 |
| `#import <yoga/Yoga.h>` | same, **modular** via the yoga R5 module | R2, R5 |
| `#import <folly/dynamic.h>` | same (deps namespaces relocated here) | R2 |
| `#import <folly/dynamic.h>` | SwiftPM: the `ReactNativeDependenciesHeaders` sidecar's `Headers/` (deps namespaces are not in `ReactNativeHeaders`) | R2 |
| `<React_RCTAppDelegate/React_RCTAppDelegate-umbrella.h>` | same, modular | R10 |
| `#import "RCTFabricComponentsPlugins.h"` (quoted, community Fabric pods) | CocoaPods only: re-vended by the `React-RCTFabric` facade into the pod header map (`rncore_facades.rb`, `FACADE_REEXPOSED_HEADERS`) | — |

- **SwiftPM**: both xcframeworks are plain `.binaryTarget`s; Xcode auto-serves
`React.framework`'s `Headers/`+`Modules/` and `ReactNativeHeaders`' `Headers/`
(incl. its `module.modulemap`) to dependents. Zero flags.
- **SwiftPM**: `React.xcframework` is not in the package graph. Its headers are
copied into `ReactHeadersTarget/include/React` with the module map rewritten
to a plain `module React`, vended as the `ReactHeaders` target. The only
`.binaryTarget`s are `ReactNativeHeaders` and
`ReactNativeDependenciesHeaders`; Xcode auto-serves their `Headers/` (incl.
`module.modulemap`) to dependents. Zero flags. See
[spm-header-paths-contract.md](../../spm/__docs__/spm-header-paths-contract.md).
- **CocoaPods**: `React-Core-prebuilt`'s `prepare_command` flattens
`ReactNativeHeaders`' Headers (incl. the module map) into the pod, and the
React-core pods are installed as dependency-only **facades**
Expand All @@ -316,8 +321,8 @@ mtime + hermes presence).
| R9 allowlist validation | private header removed/renamed, or bucket drifted | `validatePrivateReactHeaders` |
| R10 umbrella check | a probed namespace loses all modular headers | `planFromInventory` |
| R5 exemption assert | an invalid-module-identifier namespace gains a modular-candidate header | `planFromInventory` |
| Deps namespace guard (missing) | folly/glog/… not staged at compose time | `buildReactNativeHeadersXcframework` |
| Deps namespace guard (undeclared) | the deps artifact ships a namespace not in `DEPS_NAMESPACES` (new third-party dep) | `buildReactNativeHeadersXcframework` |
| Deps namespace guard (missing) | folly/glog/… not staged at compose time | `buildDepsHeadersXcframework` |
| Deps namespace guard (undeclared) | the deps artifact ships a namespace not in `DEPS_NAMESPACES` (new third-party dep) | `buildDepsHeadersXcframework` |
| Include-health ratchet | a shipped header gains a `notShipped`/`unresolved`/quoted-unresolvable include not in the committed baseline | `headers-verify.js` |
| Structural gate | composed module maps/umbrellas differ from the spec render; R9 headers or deps dirs absent | `headers-verify.js` |
| Compile gates | the React module, any R5 namespace module, the R10 umbrella, the R9 textual (Expo-shape) surface, or Swift `RCTBridge.moduleRegistry` fails to compile | `headers-verify.js` (CI: prebuild compose job) |
Expand Down
50 changes: 20 additions & 30 deletions packages/react-native/scripts/spm/__docs__/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,19 @@

[🏠 Home](../../../../../__docs__/README.md)

> **Preview.** SwiftPM support is an early preview: the commands, flags,
> generated layout, and distribution model may change in future releases, and it
> is not yet recommended for production. CocoaPods remains the supported
> default.
> **Preview.** SwiftPM support is an early preview. The commands, flags,
> generated layout, and distribution model may change in future releases. It is
> not yet recommended for production. CocoaPods remains the supported default.

The scripts in `scripts/spm/` let a React Native iOS app consume React Native
through **Swift Package Manager** instead of CocoaPods, using prebuilt
XCFrameworks. Support is opt-in and additive: `npx react-native spm` injects
package references into the app's existing `.xcodeproj` in place, and `deinit`
reverses exactly what it injected.

The motivation, staged migration plan, and open questions live in
[RFC0994](https://github.com/react-native-community/discussions-and-proposals/blob/main/proposals/0994-swift-package-manager-support-for-react-native-ios-projects.md).
The documents here describe how the implementation actually works.
[RFC0994](https://github.com/react-native-community/discussions-and-proposals/blob/main/proposals/0994-swift-package-manager-support-for-react-native-ios-projects.md)
holds the motivation, the staged migration plan, and the open questions. The
documents here describe how the implementation works.

## 🚀 Usage

Expand All @@ -24,21 +23,10 @@ cd ios
npx react-native spm # add on first run, update thereafter
```

**If any autolinked dependency ships no `Package.swift`, this stops with
`error: Package.swift is missing for library "<name>"` and exit code 2.** That
is deliberate — `add` and `update` never scaffold silently, so a missing
manifest is visible and fixed on purpose. Generate the manifests first, then
re-run setup:

```bash
npx react-native spm scaffold # writes Package.swift into node_modules/<dep>/
npx react-native spm # then inject as usual
```

Because `node_modules` isn't committed, persist each scaffolded manifest with
`npx patch-package <dep>` and commit the patch — otherwise the same error
returns on every fresh install and in CI. Better still, contribute the manifest
upstream. See
**If an autolinked dependency ships no `Package.swift`, setup stops with
`error: Package.swift is missing for library "<name>"` and exit code 2.** Run
`npx react-native spm scaffold`, persist the manifest with `patch-package`, and
re-run setup. See
[Community packages without a Package.swift](./spm-scripts.md#community-packages-without-a-packageswift).

See **[spm-scripts.md](./spm-scripts.md)** for the CLI actions and flags,
Expand All @@ -52,20 +40,20 @@ Three documents cover the design, each owning one area:
| Document | Covers |
| -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [spm-scripts.md](./spm-scripts.md) | The tool itself: CLI surface, the six-step pipeline, [every file it creates or modifies](./spm-scripts.md#files-the-tool-touches), the two auto-sync hooks, and how Debug/Release flavor selection works. |
| [spm-header-paths-contract.md](./spm-header-paths-contract.md) | How headers and package references resolve. The contract is **zero-`-I`**: no header search paths and no `unsafeFlags` in any generated manifest. Also covers remote mode. |
| [spm-header-paths-contract.md](./spm-header-paths-contract.md) | How headers and package references resolve. The contract is **zero-`-I`**: no generated manifest carries a search path into React Native's own headers. Also covers remote mode. |
| [spm-autolinking-plugins.md](./spm-autolinking-plugins.md) | The extension seam for frameworks with their own module system (Expo is the first consumer): discovery, the full context/return contract including `flavoredFrameworks`, `watchPaths` and `scriptPhases`, and failure behavior. |

Two ideas explain most of the architecture:

- **Headers go through SwiftPM; runtime binaries do not.** A `binaryTarget`
cannot vary by build configuration, but React Native ships flavored binaries
(a debug `React.framework` carries the dev menu and assertions; release strips
them). So the package graph vends headers only, and the flavored frameworks
are linked and embedded through generated Xcode build settings instead.
cannot vary by build configuration, but React Native ships flavored binaries.
So the package graph vends headers only, and generated Xcode build settings
link and embed the flavored frameworks. See
[Debug/Release flavor is automatic](./spm-scripts.md#debugrelease-flavor-is-automatic).
- **Generated state is regenerable, and the injection is reversible.**
Everything under `build/` is gitignored and rebuilt from the app's
`package.json`; everything written into the `.xcodeproj` is recorded in a
`.spm-injected.json` marker so `deinit` can undo precisely that.
`package.json`. Every edit to the `.xcodeproj` is recorded in a
`.spm-injected.json` marker, so `deinit` can undo exactly that.

## 🔗 Relationship with other systems

Expand All @@ -83,7 +71,9 @@ Two ideas explain most of the architecture:
local `React-GeneratedCode` package rather than a Pod.
- **`@react-native-community/cli config`** — supplies the autolinking metadata
(`autolinking.json`) that the SwiftPM autolinker turns into a `Package.swift`.
Overridable via `--configCommand`.
Override it with the `RCT_SPM_AUTOLINKING_CONFIG_COMMAND` environment
variable, or with `--config-command` when you run `setup-apple-spm.js`
directly.

### Uses this

Expand Down
Loading
Loading