react-native 0.87.0-rc.0 → 0.87.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/Libraries/Core/InitializeCore.js +9 -1
  2. package/Libraries/Core/ReactNativeVersion.js +1 -1
  3. package/Libraries/ReactNative/AppRegistry.flow.js +1 -0
  4. package/Libraries/ReactNative/AppRegistryImpl.js +2 -3
  5. package/Libraries/ReactPrivate/ReactNativePrivateInterface.js +5 -116
  6. package/Libraries/ReactPrivate/ReactNativePrivateInterface.js.flow +5 -31
  7. package/React/Base/RCTVersion.m +1 -1
  8. package/React/I18n/RCTLocalizedString.mm +38 -2
  9. package/React-Core-prebuilt.podspec +45 -17
  10. package/React-Core.podspec +9 -2
  11. package/ReactAndroid/external-artifacts/build.gradle.kts +49 -0
  12. package/ReactAndroid/gradle.properties +1 -1
  13. package/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/SynchronousMountItem.kt +8 -2
  14. package/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/ReactNativeVersion.kt +1 -1
  15. package/ReactCommon/cxxreact/ReactNativeVersion.h +1 -1
  16. package/index.js +15 -3
  17. package/index.js.flow +0 -2
  18. package/package.json +27 -29
  19. package/react-native.config.js +81 -0
  20. package/scripts/cocoapods/fabric.rb +1 -1
  21. package/scripts/cocoapods/rncore.rb +35 -78
  22. package/scripts/cocoapods/rncore_facades.rb +232 -0
  23. package/scripts/cocoapods/rndependencies.rb +64 -3
  24. package/scripts/cocoapods/rndeps_facades.rb +193 -0
  25. package/scripts/cocoapods/spm.rb +78 -11
  26. package/scripts/codegen/templates/Package.swift.spm-template +97 -0
  27. package/scripts/react-native-xcode.sh +20 -0
  28. package/scripts/react_native_pods.rb +65 -16
  29. package/scripts/replace-rncore-version.js +53 -6
  30. package/scripts/setup-apple-spm.js +1165 -0
  31. package/scripts/spm/__doc__/rfc-spm-xcframework.md +707 -0
  32. package/scripts/spm/__doc__/spm-autolinking-plugins.md +244 -0
  33. package/scripts/spm/__doc__/spm-header-paths-contract.md +97 -0
  34. package/scripts/spm/__doc__/spm-plugins-assessment.md +128 -0
  35. package/scripts/spm/__doc__/spm-scripts.md +451 -0
  36. package/scripts/spm/autolinking-plugins.js +331 -0
  37. package/scripts/spm/download-spm-artifacts.js +1409 -0
  38. package/scripts/spm/expand-spm-dependencies.js +216 -0
  39. package/scripts/spm/flavored-frameworks.js +1008 -0
  40. package/scripts/spm/generate-spm-autolinking-config.js +161 -0
  41. package/scripts/spm/generate-spm-autolinking.js +1888 -0
  42. package/scripts/spm/generate-spm-package.js +302 -0
  43. package/scripts/spm/generate-spm-xcodeproj.js +2224 -0
  44. package/scripts/spm/read-podspec.js +695 -0
  45. package/scripts/spm/scaffold-package-swift.js +1206 -0
  46. package/scripts/spm/spm-pbxproj.js +654 -0
  47. package/scripts/spm/spm-types.js +517 -0
  48. package/scripts/spm/spm-utils.js +645 -0
  49. package/scripts/spm/sync-spm-autolinking.js +160 -0
  50. package/sdks/.hermesv1version +1 -0
  51. package/sdks/hermes-engine/utils/replace_hermes_version.js +18 -4
  52. package/sdks/hermes-engine/version.properties +1 -1
  53. package/src/asset-registry.js +1 -1
  54. package/src/react-private-interface.js +145 -0
  55. package/src/react-private-interface.js.flow +48 -0
  56. package/src/setup-env.js +22 -0
  57. package/src/unstable-internals-do-not-use.d.ts +214 -0
  58. package/src/unstable-internals-do-not-use.js +76 -0
  59. package/third-party-podspecs/ReactNativeDependencies.podspec +2 -2
  60. package/types_generated/Libraries/ReactNative/AppRegistry.flow.d.ts +2 -2
  61. package/types_generated/Libraries/ReactPrivate/ReactNativePrivateInterface.d.ts +6 -21
  62. package/types_generated/index.d.ts +1 -2
  63. package/types_generated/src/private/renderer/events/dispatchNativeEvent.d.ts +26 -0
  64. package/types_generated/src/react-private-interface.d.ts +33 -0
  65. package/Libraries/Utilities/SceneTracker.js +0 -42
  66. package/jest-preset.js +0 -26
  67. package/rn-get-polyfills.js +0 -13
  68. package/types/tsconfig.json +0 -17
  69. package/types_generated/Libraries/Components/Touchable/Touchable.d.ts +0 -261
  70. package/types_generated/tsconfig.test.json +0 -17
@@ -0,0 +1,451 @@
1
+ # SwiftPM Scripts – React Native iOS via Swift Package Manager (Preview)
2
+
3
+ > **Preview.** SwiftPM support is an early preview: the commands, flags,
4
+ > generated layout, and distribution model may change in future releases, and
5
+ > it is not yet recommended for production. Feedback is welcome. CocoaPods
6
+ > remains the supported default.
7
+
8
+ Build React Native iOS apps using **Swift Package Manager** with prebuilt
9
+ XCFrameworks, as an alternative to CocoaPods. It is **opt-in and additive** —
10
+ CocoaPods remains the default; `spm` injects into your existing `.xcodeproj`
11
+ in place and is fully reversible.
12
+
13
+ ## Quick Start
14
+
15
+ ```bash
16
+ cd ios
17
+
18
+ # First-time setup: injects SwiftPM packages into your existing MyApp.xcodeproj,
19
+ # in place. `npx react-native spm` with no action auto-resolves to `add` (or
20
+ # `update` once injected); on a fresh CocoaPods app it converts in one command
21
+ # (implies --deintegrate). To do it explicitly:
22
+ npx react-native spm add --deintegrate
23
+
24
+ # Open in Xcode (or `npm run ios`). Incremental dep changes auto-sync on build.
25
+ open MyApp.xcodeproj
26
+ ```
27
+
28
+ After the initial run, the `.xcodeproj` includes an **auto-sync build phase**
29
+ that detects dependency changes and re-runs autolinking before compilation
30
+ (see [Auto-Sync](#auto-sync-build-phase)) — you don't re-invoke
31
+ `react-native spm` manually for day-to-day dependency changes. **On a fresh
32
+ clone or CI checkout, run `npx react-native spm` once before building** (see
33
+ [Fresh clones & CI](#fresh-clones--ci)).
34
+
35
+ > **Note:** `react-native spm` is a thin wrapper over
36
+ > `node node_modules/react-native/scripts/setup-apple-spm.js`. If the CLI
37
+ > alias is unavailable in your environment, invoke the script directly with
38
+ > the same actions and the kebab-case flag equivalents (e.g.
39
+ > `--skip-codegen`).
40
+
41
+ ## CocoaPods → SwiftPM migration
42
+
43
+ `spm add` injects into a project that is **not** CocoaPods-integrated. On a
44
+ CocoaPods app it fails loud and points you at `--deintegrate`, which:
45
+
46
+ 1. runs `pod deintegrate` — removes CocoaPods integration from the
47
+ `.xcodeproj` (Pods references, `[CP]` build phases, xcconfig links). Your
48
+ `Podfile` is left on disk.
49
+ 2. strips **only** the React Native directives (`use_react_native!`,
50
+ `use_native_modules!`, `prepare_react_native_project!`) from the Podfile —
51
+ every other line, **including your own `pod '…'` entries, is preserved**.
52
+ 3. injects SwiftPM into the `.xcodeproj`.
53
+
54
+ React Native now comes from SwiftPM; no pods are linked yet (deintegrate
55
+ removed the integration).
56
+
57
+ ### Keeping non-RN pods
58
+
59
+ Non-RN pods can stay side-by-side. After `spm add --deintegrate` your Podfile
60
+ still lists them (only the RN directives were removed) — re-integrate them
61
+ with a normal install:
62
+
63
+ ```bash
64
+ pod install # re-integrates the remaining (non-RN) pods; (re)creates the .xcworkspace
65
+ ```
66
+
67
+ Then **open the `.xcworkspace`** (not the `.xcodeproj`): the workspace includes
68
+ the SwiftPM-injected project, so React Native resolves through SwiftPM and your
69
+ other pods through CocoaPods, together.
70
+
71
+ > **Do not re-add `use_react_native!`.** React Native must be provided by
72
+ > _either_ SwiftPM _or_ CocoaPods, never both — they share `build/generated/`,
73
+ > so a dual-managed RN does not build. `spm add` refuses to run while the
74
+ > Podfile still declares `use_react_native!`.
75
+
76
+ The migration is fully reversible — see
77
+ [Removing / resetting](#removing--resetting).
78
+
79
+ ## Brownfield apps
80
+
81
+ `spm add` injects into your existing `.xcodeproj` in place, so an app that
82
+ embeds React Native works the same way — point it at the right project and
83
+ target:
84
+
85
+ ```bash
86
+ npx react-native spm add --xcodeproj MyApp.xcodeproj --productName MyApp
87
+ ```
88
+
89
+ **Requirement:** the `.xcodeproj` must live **inside the React Native JS tree**
90
+ — i.e. the app's `package.json` is a parent directory of the project. Both
91
+ setup and the build-time sync locate React Native by walking up from the
92
+ project to the nearest `package.json`. The common "native project at the repo
93
+ root with the RN JS in a sibling/child subfolder" layout is **not supported
94
+ yet** — there is no way to point at a JS root outside the project's ancestors.
95
+
96
+ Brownfield apps that keep CocoaPods for their other native dependencies follow
97
+ the [coexistence rules above](#keeping-non-rn-pods): React Native from SwiftPM,
98
+ everything else from CocoaPods, and no `use_react_native!` in the Podfile.
99
+
100
+ ## CLI Actions
101
+
102
+ ```bash
103
+ react-native spm [action] [options]
104
+ ```
105
+
106
+ With no action, the command **auto-resolves**: if SwiftPM has been injected
107
+ (`.spm-injected.json` marker present) it routes to `update`; otherwise `add`.
108
+ On a freshly-scaffolded CocoaPods project (clean git tree, stock Podfile) the
109
+ zero-arg path additionally implies `--deintegrate` (the safe-gate), so
110
+ `npx react-native spm` converts a brand-new app to SwiftPM in one command.
111
+
112
+ When invoked from the JS root of a standard RN app (sibling `ios/` subdir),
113
+ the command auto-redirects into `ios/` with a banner.
114
+
115
+ | Action | Description |
116
+ |---|---|
117
+ | `add` | Inject SwiftPM packages (package refs, build settings, the Sync build phase) into the existing `.xcodeproj`, in place. Idempotent. Default on first run. `--deintegrate` first runs `pod deintegrate` + strips React Native from the Podfile. |
118
+ | `update` | Re-run the pipeline and refresh the existing injection. Default once a project is injected. |
119
+ | `deinit` | The exact inverse of `add`: surgically remove only what `add` injected (recorded in `.spm-injected.json`) and drop the marker. Git-recoverable; no prompt. |
120
+ | `scaffold` | Generate `Package.swift` into `node_modules/<dep>/` for community RN libraries that ship only a podspec. |
121
+ | `sync` (advanced) | Lightweight resync invoked by the Xcode auto-sync build phase. Regenerates invariant codegen and autolinking output only. Not for humans. |
122
+ | `codegen` (advanced) | Run codegen and install the SwiftPM codegen template only. |
123
+ | `download` (advanced) | Download/check xcframework artifacts only. |
124
+
125
+ ## CLI Options
126
+
127
+ Flags below use the `react-native spm` (camelCase) form. The raw script
128
+ accepts kebab-case equivalents (e.g. `--skip-codegen`).
129
+
130
+ | Option | Description |
131
+ |---|---|
132
+ | `--version <ver>` | RN version (default: from package.json) |
133
+ | `--yes` | Skip the dirty-pbxproj confirmation prompt |
134
+ | `--xcodeproj <path>` | [add] Which `.xcodeproj` to inject into (when several exist) |
135
+ | `--productName <name>` | [add] Which app target to inject into (when several exist) |
136
+ | `--deintegrate` | [add] Run `pod deintegrate` + strip React Native from the Podfile before injecting |
137
+ | `--artifacts <path>` | [advanced] Local artifact root containing complete `debug/` and `release/` cache slots |
138
+ | `--download <auto\|skip\|force>` | [advanced] Artifact download policy (default: auto) |
139
+ | `--skipCodegen` | [advanced] Skip the codegen step |
140
+
141
+ ### Debug/Release flavor is automatic
142
+
143
+ React Native ships **flavored** prebuilt binaries: the *debug* `React.framework`
144
+ (and `hermesvm` / `ReactNativeDependencies`) carry the dev experience — dev menu,
145
+ assertions, `RN_DEBUG_STRING_CONVERTIBLE` — while *release* strips them for
146
+ production. A Debug build must embed the debug binaries and a Release/archive the
147
+ release ones.
148
+
149
+ SwiftPM `binaryTarget`s can't branch on the build configuration, so runtime
150
+ frameworks are deliberately kept out of the package graph. `spm add` downloads
151
+ and validates **both** flavors into immutable app-local slots. It injects
152
+ SDK/architecture-qualified Xcode settings that link the exact selected binaries,
153
+ plus one phase that copies and signs the selected frameworks into the app.
154
+ Configurations containing `debug` or `development` select Debug; every other
155
+ configuration selects Release. Selection uses only generated build settings and
156
+ standard macOS tools: builds do not run Node, mutate symlinks, regenerate the
157
+ package graph, or require a second build.
158
+
159
+ ## What to commit
160
+
161
+ | Path | Commit? | Why |
162
+ |------|---------|-----|
163
+ | `MyApp.xcodeproj/` | Yes | Your project, with SwiftPM injected in place. Holds your signing, capabilities, Build Phases — `add` only adds SwiftPM refs/settings, additively. |
164
+ | `MyApp.xcodeproj/.spm-injected.json` | Yes | Marker recording every edit `add` made, so `deinit` can surgically reverse it and re-runs stay idempotent. |
165
+ | `build/generated/` | No | Codegen/autolinking output; regenerated |
166
+ | `build/xcframeworks/` | No | Symlinks to the machine-local artifact cache |
167
+ | `Package.resolved` | No | SwiftPM resolution file; machine-specific |
168
+
169
+ Injection is **purely additive** and **idempotent**: `add`/`update` insert only
170
+ SwiftPM package refs, the React build settings, the Sync build phase, and a scheme
171
+ pre-action — every other byte (your signing / capabilities / Build Phases)
172
+ stays untouched, and a re-run is a no-op. The injected refs point at three
173
+ stable sub-package paths under `build/`; adding or removing community deps
174
+ changes the sub-package contents (gitignored) and never re-injects. `deinit`
175
+ removes exactly what was injected (using the marker), leaving the project
176
+ byte-identical to its pre-`add` state.
177
+
178
+ Because everything under `build/` is gitignored, a clean checkout has no
179
+ resolvable Swift packages until they are regenerated — see the next section.
180
+
181
+ ## Fresh clones & CI
182
+
183
+ Xcode resolves the Swift package graph **before any build phase runs**, so on a
184
+ clean checkout (where the gitignored `build/` packages don't exist yet) the
185
+ auto-sync build phase can't regenerate them in time — a bare `xcodebuild`
186
+ fails at *"Resolve Package Graph … build/generated/autolinking doesn't exist"*.
187
+
188
+ Run the setup command once after cloning, before building — the SwiftPM analog
189
+ of `pod install`:
190
+
191
+ ```bash
192
+ npx react-native spm # downloads artifacts (if missing) + regenerates build/
193
+ ```
194
+
195
+ On an already-injected project this routes to `update`: it fetches the
196
+ xcframework artifacts into the shared cache if they aren't present and
197
+ regenerates `build/xcframeworks` + `build/generated`. After this first run,
198
+ incremental dependency changes are picked up automatically by the auto-sync
199
+ build phase.
200
+
201
+ **Automate it** so nobody has to remember — add a `postinstall` hook, which
202
+ runs as part of the `npm install` / `yarn install` your CI already does before
203
+ `xcodebuild`:
204
+
205
+ ```json
206
+ {
207
+ "scripts": {
208
+ "postinstall": "react-native spm"
209
+ }
210
+ }
211
+ ```
212
+
213
+ `npx react-native spm` auto-redirects from the JS root into `ios/`, so the hook
214
+ works from the app root; in CI (non-interactive) it proceeds without prompting.
215
+ It re-runs the full pipeline (codegen + an idempotent re-inject that is a no-op
216
+ when nothing changed), so it is slightly heavier than the internal `sync` the
217
+ build phase calls — a fine trade for not having to remember a command.
218
+
219
+ > A future remote-package distribution (a tagged `Package.swift` repo +
220
+ > `binaryTarget(url:checksum:)`) removes this step entirely: SwiftPM resolves and
221
+ > fetches the artifacts itself during normal package resolution. Until then,
222
+ > the one-time setup run is required on clean machines.
223
+
224
+ ## Local Native Modules
225
+
226
+ Modules not discovered via autolinking can be declared in `react-native.config.js`:
227
+
228
+ ```js
229
+ module.exports = {
230
+ spm: {
231
+ modules: [
232
+ {
233
+ name: 'MyNativeModule',
234
+ path: 'ios/MyNativeModule', // relative to app root
235
+ exclude: ['*.podspec'], // optional
236
+ publicHeadersPath: '.', // optional
237
+ },
238
+ ],
239
+ },
240
+ };
241
+ ```
242
+
243
+ Each entry becomes a target in `build/generated/autolinking/Package.swift`.
244
+ Sources outside `build/generated/autolinking/` are automatically mirrored with
245
+ file-level symlinks.
246
+
247
+ ## Self-managed community packages
248
+
249
+ A community library that ships its own `Package.swift` is referenced
250
+ directly by the autolinker instead of being wrapped. To keep SwiftPM's
251
+ package identity (which it derives from the path basename) unique across
252
+ deps — even when several libs put their manifest inside an `ios/` subdir
253
+ — each self-managed dep is exposed through a uniquely-named symlink at
254
+ `build/generated/autolinking/libs/<SwiftName>/`. The aggregator
255
+ `Package.swift` references that path, so two libs both shipping
256
+ `<dep>/ios/Package.swift` never collide on identity `"ios"`.
257
+
258
+ The `libs/` directory is wiped and recreated on every autolinker run,
259
+ so deleting a dep via `npm uninstall` cleans up the alias automatically
260
+ on the next build.
261
+
262
+ ## Community packages without a Package.swift
263
+
264
+ If an autolinked library ships **no `Package.swift`**, the build fails with a
265
+ clear per-dep error (`Package.swift is missing for library "<name>"`). Generate
266
+ one from the library's podspec:
267
+
268
+ ```bash
269
+ npx react-native spm scaffold # writes Package.swift into node_modules/<dep>/
270
+ ```
271
+
272
+ Because `node_modules/` isn't committed, persist it so it survives the next
273
+ install:
274
+
275
+ ```bash
276
+ npx patch-package <dep> # then commit the generated patch
277
+ ```
278
+
279
+ **Better: contribute the manifest upstream.** The generated `Package.swift` is
280
+ a normal, committable manifest — the ideal fix is for the library to ship it
281
+ itself, so every consumer gets SwiftPM support without a local patch. Please
282
+ **file an issue or open a PR on the library** with the scaffolded
283
+ `Package.swift` (mention it was generated by `react-native spm scaffold` for
284
+ React Native SwiftPM support). Until it lands upstream, the `patch-package`
285
+ workaround keeps your app building.
286
+
287
+ > A library whose sources mix Swift **and** Objective-C/C++ in one target, or
288
+ > that ships neither a `Package.swift` nor a podspec, can't be scaffolded
289
+ > automatically — the error says so. Opt it out via `react-native.config.js`
290
+ > (`platforms.ios = null`) or ask the maintainer for a prebuilt xcframework.
291
+
292
+ ## Framework plugins (Preview)
293
+
294
+ Frameworks with their own module system (e.g. Expo) contribute to the
295
+ autolinking graph through a **plugin** — a function invoked on every
296
+ regeneration (including the build-time sync) that adds SwiftPM package refs,
297
+ product dependencies, and generated sources. Discovery is transitive
298
+ (installing the framework is enough), and the plugin returns data that RN
299
+ merges idempotently.
300
+
301
+ See **[spm-autolinking-plugins.md](./spm-autolinking-plugins.md)** for the
302
+ discovery mechanism, the full context/return contract, lifecycle, and failure
303
+ behavior.
304
+
305
+ ## Removing / resetting
306
+
307
+ To remove SwiftPM entirely, use `deinit` (the inverse of `add`):
308
+
309
+ ```bash
310
+ react-native spm deinit # surgically removes everything `add` injected
311
+ pod install # then, to restore CocoaPods
312
+ ```
313
+
314
+ To reset the regenerable build state (without un-injecting), just delete the
315
+ gitignored dirs and re-run:
316
+
317
+ ```bash
318
+ rm -rf build/xcframeworks build/generated .build
319
+ react-native spm update
320
+ ```
321
+
322
+ Xcode's "Clean Build Folder" (Cmd+Shift+K) only removes DerivedData — it does
323
+ not touch SwiftPM-generated directories. The cached xcframework slot is shared
324
+ across apps; refresh it with `react-native spm update --download force`.
325
+
326
+ ## Troubleshooting
327
+
328
+ | Problem | Fix |
329
+ |---------|-----|
330
+ | `xcodebuild` fails: "Could not resolve package dependencies … `build/generated/autolinking` doesn't exist" | Fresh clone — run `npx react-native spm` once before building (see [Fresh clones & CI](#fresh-clones--ci)) |
331
+ | `spm add` fails: "CocoaPods-integrated project" | Re-run `spm add --deintegrate` (runs `pod deintegrate` + strips RN from the Podfile), or `pod deintegrate` yourself first. |
332
+ | `spm add` fails: "no .xcodeproj found" | Create an app first (`npx @react-native-community/cli init`) or make a project in Xcode, then `spm add`. |
333
+ | `spm add` fails: "multiple .xcodeproj found" | Pass `--xcodeproj <path>` (and `--product-name <target>` if multiple app targets). |
334
+ | Missing headers | Re-run `react-native spm` |
335
+ | "not contained in target" | Re-run setup (regenerates file-level symlinks) |
336
+ | Codegen fails | Use `--skipCodegen` to iterate on other parts |
337
+ | "SPM sync failed" warning | Check Xcode build log for details; node may not be in PATH — ensure `with-environment.sh` is present |
338
+ | Autolinking not updating on build | Touch `package.json` to force a sync, or delete `build/generated/autolinking/.spm-sync-stamp` |
339
+ | Stale SwiftPM state or corrupted build | `rm -rf build/ .build/`, then `react-native spm update`, then reopen Xcode |
340
+ | Want to revert to CocoaPods | `react-native spm deinit`, then `pod install` |
341
+
342
+ ---
343
+
344
+ # Reference / internals
345
+
346
+ ## Pipeline
347
+
348
+ `react-native spm add` and `react-native spm update` orchestrate these steps:
349
+
350
+ | Step | Script | Output |
351
+ |------|--------|--------|
352
+ | 1. CLI config | `spm/generate-spm-autolinking-config.js` | `build/generated/autolinking/autolinking.json` |
353
+ | 2. Codegen | `generate-codegen-artifacts.js` | `build/generated/ios/` |
354
+ | 3. Autolinking | `spm/generate-spm-autolinking.js` | `build/generated/autolinking/Package.swift` |
355
+ | 4. Download | `spm/download-spm-artifacts.js` | Complete Debug and Release cache slots |
356
+ | 5. Package | `spm/generate-spm-package.js` | Immutable flavor slots, central manifest, canonical `ReactHeaders`, and invariant `Package.swift` |
357
+ | 6. Inject | `spm/generate-spm-xcodeproj.js` | Invariant SwiftPM products plus configuration-qualified linker settings and the embed/sign phase |
358
+ | Auto-sync | `spm/sync-spm-autolinking.js` | Re-runs invariant codegen/autolinking output only at Xcode build time |
359
+
360
+ ## Directory Layout
361
+
362
+ ```
363
+ my-app/ios/
364
+ MyApp.xcodeproj/ <-- committed (your project; SwiftPM injected in place, carries .spm-injected.json)
365
+ Podfile <-- present until `pod deintegrate` (CocoaPods coexistence is best-effort)
366
+ build/
367
+ generated/
368
+ autolinking/ <-- gitignored (regenerated at build time)
369
+ Package.swift
370
+ autolinking.json
371
+ packages/ <-- synth wrappers for autolinker-managed deps
372
+ libs/ <-- symlinks to self-managed deps' Package.swift
373
+ dirs, named by Swift module so SwiftPM
374
+ package identity stays unique
375
+ headers/ <-- generated header symlinks
376
+ ios/ <-- gitignored, codegen output
377
+ xcframeworks/ <-- gitignored, immutable runtime flavor slots + invariant package
378
+ debug/
379
+ React.xcframework -> ~/Library/Caches/.../debug/React.xcframework
380
+ ReactNativeDependencies.xcframework -> ...
381
+ hermes-engine.xcframework -> ...
382
+ release/
383
+ React.xcframework -> ~/Library/Caches/.../release/React.xcframework
384
+ ReactNativeDependencies.xcframework -> ...
385
+ hermes-engine.xcframework -> ...
386
+ ReactHeadersTarget/ <-- canonical Objective-C React headers + module map
387
+ ReactNativeHeaders.xcframework -> ...
388
+ ReactNativeDependenciesHeaders.xcframework -> ...
389
+ flavored-frameworks.json
390
+ .artifact-stamp
391
+ ```
392
+
393
+ ## Header Resolution
394
+
395
+ React Native uses CocoaPods-style imports (`#import <React/RCTBridge.h>`) that
396
+ SwiftPM doesn't natively support. The prebuilt artifacts serve them through SwiftPM
397
+ package products — no `-I` search-path flags, and no clang VFS overlay:
398
+
399
+ 1. **`<React/…>` and `import React`** resolve through the invariant
400
+ **`ReactHeaders` Clang target**. It stages one canonical header copy after
401
+ proving Debug and Release expose identical public headers, and uses a plain
402
+ `module React` module map with `React/`-prefixed paths.
403
+ 2. **Lowercase C++ `react/` and every other RN namespace** (`yoga/`, `jsi/`,
404
+ `jsinspector-modern`, …) comes from **`ReactNativeHeaders.xcframework`**, a
405
+ headers-only (LIBRARY-type) binaryTarget whose per-slice `Headers/` SwiftPM
406
+ auto-serves to dependents.
407
+ 3. **Third-party dependency namespaces** (`folly/`, `glog/`, `boost/`, `fmt/`,
408
+ `double-conversion/`, `fast_float/`, `SocketRocket/`) come from
409
+ **`ReactNativeDependenciesHeaders.xcframework`**, the deps headers-only
410
+ sidecar (same mechanism — the binary `ReactNativeDependencies.xcframework`
411
+ is framework-type and can't expose those headers to SwiftPM).
412
+
413
+ Targets that compile against React take these as product dependencies
414
+ (`ReactHeaders`, `ReactNativeHeaders`, `ReactNativeDependenciesHeaders`, plus the
415
+ app's `ReactAppHeaders`), so all of the above resolve with zero search-path
416
+ flags.
417
+
418
+ ## Auto-Sync Build Phase
419
+
420
+ The generated `.xcodeproj` includes a **Sync SPM Autolinking** shell script
421
+ build phase. It keeps `build/generated/autolinking/Package.swift` up to date
422
+ without requiring manual re-runs of `react-native spm` for incremental
423
+ dependency changes. (It cannot bootstrap a fresh clone — Xcode resolves the
424
+ package graph before any phase runs; see [Fresh clones & CI](#fresh-clones--ci).)
425
+
426
+ **How it works:**
427
+
428
+ 1. Compares timestamps of staleness inputs against `build/generated/autolinking/.spm-sync-stamp`:
429
+ - `package.json` — dependency declarations
430
+ - `react-native.config.js` — `spm.modules` config
431
+ - `node_modules/` directory mtime — updated by any package manager (npm, yarn, pnpm, bun); also checks parent `node_modules` for monorepo setups
432
+ - a missing `build/xcframeworks/` (e.g. after a manual clean) also marks stale
433
+ 2. If any input is newer (or the stamp is missing): runs `npx react-native spm sync`,
434
+ which re-executes autolinking + package generation (downloading artifacts if
435
+ the cache slot is incomplete) and writes the stamp file.
436
+ 3. If all inputs are fresh: exits immediately (~1ms).
437
+
438
+ **Build phase ordering:**
439
+
440
+ | # | Phase |
441
+ |---|-------|
442
+ | 0 | Resolve Package Graph (Xcode — runs before all build phases) |
443
+ | 1 | Sync SPM Autolinking |
444
+ | 2 | Sources (compile) |
445
+ | 3 | Frameworks (link) |
446
+ | 4 | Embed React Native Flavored Frameworks |
447
+ | 5 | Resources (copy) |
448
+ | 6 | Build JS Bundle |
449
+
450
+ Failures in the sync phase are non-fatal — it emits a `warning:` and exits 0,
451
+ so an already-generated package graph can still produce a successful build.