@depup/react-native 0.87.0-depup.0 → 0.87.1-depup.0

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 (37) hide show
  1. package/Libraries/Animated/AnimatedEvent.js +1 -1
  2. package/Libraries/Animated/AnimatedImplementation.js +6 -2
  3. package/Libraries/Animated/nodes/AnimatedNode.js +1 -1
  4. package/Libraries/Animated/nodes/AnimatedValue.js +2 -2
  5. package/Libraries/Core/ReactNativeVersion.js +1 -1
  6. package/README.md +3 -3
  7. package/React/Base/RCTVersion.m +1 -1
  8. package/ReactAndroid/gradle.properties +1 -1
  9. package/ReactAndroid/src/main/java/com/facebook/react/modules/intent/IntentModule.kt +7 -3
  10. package/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/ReactNativeVersion.kt +1 -1
  11. package/ReactCommon/cxxreact/ReactNativeVersion.h +2 -2
  12. package/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTFontUtils.mm +1 -1
  13. package/changes.json +2 -2
  14. package/package.json +12 -12
  15. package/scripts/cocoapods/rndependencies.rb +31 -25
  16. package/scripts/replace-rncore-version.js +18 -2
  17. package/scripts/setup-apple-spm.js +28 -12
  18. package/scripts/spm/autolinking-plugins.js +80 -0
  19. package/scripts/spm/download-spm-artifacts.js +64 -6
  20. package/scripts/spm/expand-spm-dependencies.js +249 -30
  21. package/scripts/spm/generate-spm-autolinking.js +157 -40
  22. package/scripts/spm/generate-spm-package.js +30 -18
  23. package/scripts/spm/generate-spm-xcodeproj.js +595 -71
  24. package/scripts/spm/scaffold-package-swift.js +65 -24
  25. package/scripts/spm/spm-pbxproj.js +158 -28
  26. package/scripts/spm/spm-types.js +29 -3
  27. package/scripts/spm/spm-utils.js +117 -2
  28. package/sdks/.hermesv1version +1 -1
  29. package/sdks/hermes-engine/version.properties +1 -1
  30. package/types_generated/Libraries/Animated/AnimatedImplementation.d.ts +2 -2
  31. package/types_generated/Libraries/Animated/nodes/AnimatedNode.d.ts +4 -1
  32. package/types_generated/Libraries/Animated/nodes/AnimatedValue.d.ts +3 -3
  33. package/scripts/spm/__doc__/rfc-spm-xcframework.md +0 -707
  34. package/scripts/spm/__doc__/spm-autolinking-plugins.md +0 -244
  35. package/scripts/spm/__doc__/spm-header-paths-contract.md +0 -97
  36. package/scripts/spm/__doc__/spm-plugins-assessment.md +0 -128
  37. package/scripts/spm/__doc__/spm-scripts.md +0 -486
@@ -1,707 +0,0 @@
1
- ---
2
- title: Swift Package Manager Support for React Native iOS
3
- author:
4
- - Christian Falch
5
- date: 2026-03-17
6
- ---
7
-
8
- # RFC: Swift Package Manager Support for React Native iOS
9
-
10
- ## Summary
11
-
12
- Add Swift Package Manager (SPM) as an officially supported build system for
13
- React Native iOS apps, alongside CocoaPods. The approach uses **prebuilt
14
- XCFrameworks** published to Maven, eliminating the need for source compilation
15
- of React Native internals and enabling fast, reproducible builds.
16
-
17
- ## Basic example
18
-
19
- ### New project
20
-
21
- ```bash
22
- npx react-native init MyApp
23
- cd MyApp
24
- npx react-native spm # auto-detects first-run → init; prompts to rename legacy CocoaPods xcodeproj
25
- npm run ios
26
- ```
27
-
28
- A future CLI integration (e.g., an `--ios-build-system spm` flag on
29
- `react-native init`) could run `react-native spm init` automatically as part
30
- of project creation, eliminating the manual step.
31
-
32
- ### Existing project
33
-
34
- ```bash
35
- cd MyApp
36
- npx react-native spm
37
- # Prompted: rename CocoaPods MyApp.xcodeproj → MyApp.xcodeproj.legacy?
38
- # Accept (Y) — the SPM xcodeproj writes to the now-free MyApp.xcodeproj slot,
39
- # `npm run ios` resolves to it unambiguously. The legacy stays on disk
40
- # (git mv tracks the rename cleanly) for rollback via `spm clean --project`.
41
- ```
42
-
43
- After initial setup, day-to-day development requires no extra commands. Adding
44
- or removing JS dependencies that include native code is handled automatically
45
- by a build-phase sync step (see [Auto-sync build phase](#auto-sync-build-phase)).
46
-
47
- ## Motivation
48
-
49
- ### Apple is moving away from CocoaPods
50
-
51
- SPM is Apple's endorsed dependency manager. Xcode's SPM integration improves
52
- with every release — package resolution, build caching, and IDE features all
53
- assume SPM as the primary workflow. CocoaPods is community-maintained and has
54
- been officially sunsetted — the CocoaPods trunk will become permanently
55
- read-only on **December 2, 2026**, after which no new pods or updates can be
56
- published ([announcement](https://blog.cocoapods.org/CocoaPods-Specs-Repo/)).
57
- Existing builds will continue to work, but the ecosystem is moving on.
58
-
59
- ### Build speed
60
-
61
- Prebuilt XCFrameworks skip compilation of ~2000 C++/Objective-C files. A clean
62
- SPM build of rn-tester compiles only app sources and codegen output. This is a
63
- significant improvement for CI pipelines and developer iteration speed.
64
-
65
- ### Reduced onboarding friction
66
-
67
- CocoaPods requires Ruby, Bundler, and a working gem environment — a frequent
68
- source of setup issues, especially on new machines or in CI. SPM requires only
69
- Xcode. Removing the Ruby toolchain dependency simplifies onboarding and reduces
70
- the surface area for environment-related build failures.
71
-
72
- ### Adoption barrier
73
-
74
- Many organizations mandate SPM for iOS dependencies. Teams in these
75
- environments are currently blocked from adopting React Native, or must maintain
76
- custom workarounds. First-class SPM support might help overcoming this barrier.
77
-
78
- ### Compatibility
79
-
80
- The SPM workflow generates an `AppName.xcodeproj` that takes the same
81
- filename slot as the legacy CocoaPods xcodeproj. On `init`, the script
82
- prompts to rename the existing CocoaPods project to `AppName.xcodeproj.legacy`
83
- — preserving it for rollback while letting the community CLI's
84
- `findXcodeProject` resolve `npm run ios` to the SPM project unambiguously.
85
- Teams can migrate at their own pace before CocoaPods trunk goes read-only
86
- in December 2026, and `spm clean --project` reverses the migration when
87
- needed.
88
-
89
- ## Detailed design
90
-
91
- ### Architecture
92
-
93
- ```
94
- ┌─────────────────────────────────────────────────┐
95
- │ Maven (artifacts) │
96
- │ ├── React.xcframework (~200 MB, debug) │
97
- │ ├── ReactNativeDependencies.xcframework │
98
- │ └── hermes-engine.xcframework │
99
- └──────────────────┬──────────────────────────────┘
100
- │ download + cache
101
-
102
- ┌─────────────────────────────────────────────────┐
103
- │ ~/Library/Caches/com.facebook.ReactNative/ │
104
- │ └── spm-artifacts/{version}/{flavor}/ │
105
- └──────────────────┬──────────────────────────────┘
106
- │ symlink
107
-
108
- ┌──────────────────────────────────────────────────┐
109
- │ App ios/ │
110
- │ ├── AppName.xcodeproj/ (committed) │
111
- │ │ └── .spm-managed (marker file) │
112
- │ ├── AppName.xcodeproj.legacy/ (committed if │
113
- │ │ rename was │
114
- │ │ accepted) │
115
- │ └── build/ │
116
- │ ├── generated/ │
117
- │ │ ├── autolinking/ (generated) │
118
- │ │ │ ├── Package.swift │
119
- │ │ │ ├── autolinking.json │
120
- │ │ │ ├── packages/ (synth wrappers) │
121
- │ │ │ └── libs/ (alias symlinks │
122
- │ │ │ for self-managed│
123
- │ │ │ deps; basename │
124
- │ │ │ = SwiftName) │
125
- │ │ └── ios/ (codegen) │
126
- │ └── xcframeworks/ (symlinks) │
127
- │ ├── Package.swift │
128
- │ ├── React.xcframework -> cache │
129
- │ ├── ReactNativeDependencies.xcframework │
130
- │ └── hermes-engine.xcframework │
131
- └──────────────────────────────────────────────────┘
132
- ```
133
-
134
- ### Pipeline
135
-
136
- `react-native spm` orchestrates six steps (the underlying script is
137
- `scripts/setup-apple-spm.js`):
138
-
139
- | # | Step | Script | Output |
140
- |---|------|--------|--------|
141
- | 1 | CLI config | `spm/generate-spm-autolinking-config.js` | `build/generated/autolinking/autolinking.json` |
142
- | 2 | Codegen | `generate-codegen-artifacts.js` | `build/generated/ios/` |
143
- | 3 | Autolinking | `spm/generate-spm-autolinking.js` | `build/generated/autolinking/Package.swift` + source symlinks |
144
- | 4 | Download | `spm/download-spm-artifacts.js` | Cached xcframeworks |
145
- | 5 | Package | `spm/generate-spm-package.js` | `build/xcframeworks/Package.swift` + symlinks |
146
- | 6 | Xcodeproj | `spm/generate-spm-xcodeproj.js` | `AppName.xcodeproj` + `.spm-managed` marker (`init` only; create-if-missing on subsequent runs) |
147
- | — | Sync (build-time) | `spm/sync-spm-autolinking.js` | Re-runs steps 1–5 when inputs change (downloads artifacts if missing) |
148
-
149
- The `init` action additionally (a) prompts to rename any existing
150
- CocoaPods `<App>.xcodeproj` to `<App>.xcodeproj.legacy` before step 6, and
151
- (b) appends SPM-specific entries to `.gitignore`
152
- (`build/generated/`, `build/xcframeworks/`, `.build/`, `Package.resolved`).
153
- Existing entries are not duplicated.
154
-
155
- ### Auto-sync build phase
156
-
157
- After initial setup, developers shouldn't need to re-run `react-native spm`
158
- manually when dependencies change. The generated `.xcodeproj` includes a
159
- **Sync SPM Autolinking** pre-build phase (ordered first, before VFS overlay)
160
- that:
161
-
162
- 1. Checks whether xcframework artifacts are missing (`artifacts.json` or
163
- `React.xcframework` absent). This covers fresh clones where no setup
164
- script has been run yet.
165
- 2. Compares timestamps of `package.json`, `react-native.config.js`, and the
166
- `node_modules` directory against `autolinked/.spm-sync-stamp`. In
167
- monorepos where `node_modules` is hoisted, the parent directory is also
168
- checked.
169
- 3. If any check triggers (or the stamp is missing): sources `with-environment.sh`
170
- for node PATH, then runs `spm/sync-spm-autolinking.js` which re-executes
171
- codegen, artifact download (if needed), autolinking, and package generation.
172
- 4. If all inputs are fresh: exits immediately (~1ms shell check).
173
-
174
- Failures emit `warning:` and exit 0 — the existing autolinking may still be
175
- valid. The stamp file is written on successful sync.
176
-
177
- The sync step handles React Native version changes automatically: after
178
- `npm install` pulls a new version, the `node_modules` mtime changes, the sync
179
- step regenerates autolinking and recreates xcframework symlinks pointing to the
180
- new version's cache directory.
181
-
182
- The sync step is **self-healing**: if xcframework artifacts are missing (e.g.,
183
- the local cache at `~/Library/Caches/com.facebook.ReactNative/` was deleted,
184
- or the project was freshly cloned), it automatically downloads them before
185
- proceeding with autolinking and package generation. This means `react-native spm`
186
- is only strictly required for initial project scaffolding (`init`); subsequent
187
- builds recover automatically.
188
-
189
- ### Cleaning generated SPM state
190
-
191
- Xcode's "Clean Build Folder" (Cmd+Shift+K) only removes DerivedData — it does
192
- not touch the project's `build/` or `.build/` directories. Xcode provides no
193
- hook to run custom scripts during GUI clean actions.
194
-
195
- `react-native spm clean` is scoped by opt-in flags. The default removes only
196
- generated dirs under `appRoot`:
197
-
198
- ```bash
199
- react-native spm clean # build/xcframeworks/, build/generated/, .build/
200
- react-native spm clean --project # also: delete SPM xcodeproj, restore .legacy backup
201
- react-native spm clean --derived-data # also: this app's Xcode DerivedData entries
202
- react-native spm clean --cache # also: cached xcframework slot for current version
203
- react-native spm clean --all # = --project --derived-data --cache
204
- ```
205
-
206
- Destructive scopes (`--project`, `--derived-data`, `--cache`, `--all`) prompt
207
- for confirmation (bypass with `--yes`). `--project` is the reverse of the
208
- init-time rename migration — deleting the SPM xcodeproj and restoring
209
- `<App>.xcodeproj.legacy` to its original filename if a backup exists.
210
-
211
- After a plain `clean`, run `react-native spm update` (or open the checked-in
212
- `.xcodeproj` and build) to regenerate state. SPM package resolution is locked
213
- for the duration of a build — if only stubs were left in place, Xcode would
214
- resolve stubs and never pick up the real packages generated by the sync build
215
- phase.
216
-
217
- ### Stub packages for fresh clones
218
-
219
- Xcode resolves SPM packages **before** any build phase runs. On a fresh clone,
220
- the referenced package directories (`build/xcframeworks`, `autolinked`,
221
- `build/generated/ios`) may not exist yet, causing package resolution to fail.
222
-
223
- To solve this, `generate-spm-xcodeproj.js` writes **stub `Package.swift`
224
- files** into each referenced sub-package directory that doesn't already have
225
- one. Each stub defines the expected library products backed by a minimal
226
- placeholder target (`.stub/Stub.swift`). This lets Xcode resolve packages
227
- successfully even before the first build. On the first build, the auto-sync
228
- build phase overwrites the stubs with real Package.swift files generated from
229
- downloaded artifacts and autolinking output.
230
-
231
- ### Caching and CI
232
-
233
- Xcframeworks are cached at
234
- `~/Library/Caches/com.facebook.ReactNative/spm-artifacts/{version}/{flavor}/`
235
- by default. The download step accepts a `--output` flag to write xcframeworks
236
- to an explicit directory.
237
-
238
- For CI pipelines (GitHub Actions, CircleCI, etc.), cache the default path
239
- keyed by the React Native version and flavor to avoid re-downloading
240
- xcframeworks on every build.
241
-
242
- The Maven base URL can be overridden via the `ENTERPRISE_REPOSITORY`
243
- environment variable for teams that mirror artifacts to an internal registry.
244
-
245
- **Planned:** A `RN_SPM_CACHE_DIR` environment variable to override the default
246
- cache directory. This is not yet implemented in the current POC but is needed
247
- for CI environments where a specific path must be persisted across builds.
248
-
249
- ### Package graph
250
-
251
- The generated `<App>.xcodeproj` references three local packages directly
252
- via `XCLocalSwiftPackageReference` — no app-level `Package.swift` is required:
253
-
254
- ```
255
- AppName.xcodeproj
256
- ├── XCLocalSwiftPackageReference → build/xcframeworks/Package.swift
257
- │ ├── ReactNative (product, wraps React binaryTarget)
258
- │ ├── ReactNativeDependencies (binaryTarget)
259
- │ └── hermes-engine (binaryTarget)
260
- ├── XCLocalSwiftPackageReference → build/generated/ios/Package.swift
261
- │ ├── ReactCodegen (target — codegen output)
262
- │ └── ReactAppDependencyProvider (target)
263
- └── XCLocalSwiftPackageReference → build/generated/autolinking/Package.swift
264
- └── <AutolinkedModule>... (targets — symlinked sources)
265
- ```
266
-
267
- These three sub-package paths are **stable**: adding or removing community
268
- deps changes the contents of `build/generated/autolinking/Package.swift`
269
- (gitignored) but never the xcodeproj's references. That's why the
270
- `.xcodeproj` is committed once and not regenerated on subsequent runs.
271
-
272
- The xcodeproj generation is **create-if-missing** on `update` (use
273
- `--force-xcodeproj` for an explicit overwrite). This protects user-side
274
- Xcode edits — signing, capabilities, Build Phases, scheme settings — from
275
- being clobbered. Teammates can clone the repo and open Xcode immediately:
276
- stub `Package.swift` files in each sub-package directory let SPM resolution
277
- succeed before the first build, and the auto-sync build phase downloads
278
- artifacts and writes the real sub-packages on first compile.
279
-
280
- ### Header resolution
281
-
282
- React Native uses CocoaPods-style imports (`#import <React/RCTBridge.h>`) that
283
- SPM does not natively support. Two mechanisms solve this:
284
-
285
- 1. **XCFramework `Headers/` layout.** The prebuild step organizes headers by
286
- `header_dir` (e.g., `Headers/React/`, `Headers/react/renderer/core/`).
287
- Adding `-I Headers` to search paths resolves most imports directly.
288
-
289
- 2. **VFS overlay.** A Clang virtual filesystem overlay (`React-VFS.yaml`)
290
- remaps remaining edge cases — headers that appear in multiple pods or have
291
- platform variants. The overlay is generated as a template at prebuild time
292
- and resolved with local paths at setup time.
293
-
294
- ### Local native modules
295
-
296
- Modules not discovered via autolinking (e.g., app-specific native modules) are
297
- declared in `react-native.config.js`:
298
-
299
- ```js
300
- // react-native.config.js
301
- module.exports = {
302
- spmModules: [
303
- {
304
- name: 'MyNativeModule', // SPM target name
305
- path: 'ios/MyNativeModule', // path to source files
306
- exclude: ['*.podspec'], // files to exclude from the target
307
- publicHeadersPath: '.', // header search path for consumers
308
- },
309
- ],
310
- };
311
- ```
312
-
313
- Each entry becomes a target in `autolinked/Package.swift`. Sources outside the
314
- autolinked directory are mirrored with **file-level symlinks** (SPM rejects
315
- directory symlinks that resolve outside the package root).
316
-
317
- ### Self-managed deps and package identity
318
-
319
- A community library that ships its own `Package.swift` (instead of being
320
- wrapped by the autolinker) is referenced directly. SPM derives the package
321
- identity for a `.package(path:)` dependency from the path's basename — and
322
- a common convention is to ship the manifest inside an `ios/` subdir
323
- (`<dep>/ios/Package.swift`). Two libs following that convention would both
324
- have identity `"ios"`, and SPM rejects with `Conflicting identity for ios`.
325
-
326
- To make every reference globally unique by construction, the autolinker
327
- materializes each self-managed dep as a symlink at
328
- `build/generated/autolinking/libs/<SwiftName>/` pointing at the dep's real
329
- manifest dir. The aggregator `Package.swift` then references the symlink
330
- (`path: "libs/<SwiftName>"`), and SPM uses the symlink basename — the
331
- library's Swift module name — as the package identity. Swift module names
332
- are already unique per dep (deriving from the npm package name), so this
333
- sidesteps the collision in all cases, including against the codegen
334
- package at `build/generated/ios/`.
335
-
336
- The `libs/` directory is wiped and recreated on every autolinker run, so
337
- stale aliases for uninstalled deps disappear automatically.
338
-
339
- ### Third-party library support
340
-
341
- The current implementation handles React Native's own frameworks and app-local
342
- native modules. The primary goal for third-party libraries is to **build using
343
- SPM**. Shipping prebuilt xcframeworks is the recommended approach for faster
344
- builds, but it is not a requirement — libraries can also be compiled from
345
- source via SPM targets. This ensures that library authors with limited
346
- resources can support SPM without needing to set up a prebuild CI pipeline.
347
-
348
- #### Library metadata in `react-native.config.js`
349
-
350
- `react-native.config.js` is the canonical place for library SPM metadata. The
351
- autolinking pipeline already scans `node_modules` for this file to discover
352
- iOS and Android native modules. Adding SPM config alongside the existing
353
- `dependency.platforms.ios` keeps a single source of truth, requires no new
354
- discovery mechanism, and can express things `Package.swift` cannot — such as
355
- Maven URL templates with version and flavor placeholders for downloading
356
- prebuilt xcframeworks. Libraries may still ship a `Package.swift` for direct
357
- SPM consumers outside the React Native ecosystem, but React Native autolinking
358
- reads `react-native.config.js`.
359
-
360
- #### Prebuilt xcframeworks (primary path)
361
-
362
- React Native already prebuilds its core into xcframeworks and publishes them to
363
- Maven. This is the model we want every library to follow. Libraries declare SPM
364
- metadata in `react-native.config.js`:
365
-
366
- ```js
367
- // react-native-maps/react-native.config.js
368
- module.exports = {
369
- dependency: {
370
- platforms: {
371
- ios: { /* existing autolinking config */ },
372
- },
373
- },
374
- spm: {
375
- // Primary: prebuilt xcframework (downloaded at setup time)
376
- xcframework: {
377
- name: 'ReactNativeMaps',
378
- // URL template — {version}, {rn-version}, {flavor} resolved at download time
379
- url: 'https://maven.example.com/.../react-native-maps-{version}-xcframework-{flavor}.tar.gz',
380
- },
381
- // Fallback: source compilation (used during local development or when
382
- // xcframework is unavailable)
383
- source: {
384
- name: 'ReactNativeMaps',
385
- path: 'ios',
386
- publicHeadersPath: '.',
387
- exclude: ['*.podspec', 'Tests/**'],
388
- dependencies: ['MapKit'],
389
- resources: ['ios/Resources/**'],
390
- },
391
- },
392
- };
393
- ```
394
-
395
- **Planned (Phase 2):** When `react-native spm` gains third-party library
396
- support, it will:
397
- 1. If `spm.xcframework` is declared, download the prebuilt binary (fast path).
398
- 2. If the download fails or the `--source` flag is passed, fall back to
399
- `spm.source` and compile from symlinked sources.
400
- 3. If neither is declared, the library requires a manual `spmModules` entry.
401
-
402
- Currently, only `spmModules` entries (see [Local native modules](#local-native-modules))
403
- are supported. The `spm.xcframework` and `spm.source` config fields — including
404
- the `dependencies` field shown above — are not yet implemented.
405
-
406
- #### Source compilation (fallback)
407
-
408
- Source-level autolinking (`spmModules` / `spm.source`) remains available for:
409
- - **Local development** — library authors iterating on native code
410
- - **Libraries without prebuilt xcframeworks** — transitional state
411
- - **App-specific native modules** — code that lives in the app repo
412
-
413
- This reuses the existing `spmModules` mechanism: sources are mirrored with
414
- file-level symlinks into `autolinked/`, compiled as SPM targets with
415
- appropriate header search paths.
416
-
417
- #### `react-native-prebuild` CLI
418
-
419
- React Native already has a mature prebuild pipeline (`scripts/ios-prebuild/`)
420
- that produces signed, packaged xcframeworks published to Maven. Rather than
421
- asking library authors to reinvent this, we can expose the same tooling as a
422
- reusable CLI:
423
-
424
- ```bash
425
- npx react-native-prebuild \
426
- --podspec ios/MyLibrary.podspec \
427
- --react-native-version 0.80.0 \
428
- --platforms ios,ios-simulator \
429
- --flavor release \
430
- --output dist/
431
-
432
- # Output:
433
- # dist/MyLibrary.xcframework.tar.gz
434
- # dist/MyLibrary.framework.dSYM.tar.gz
435
- ```
436
-
437
- The tool would:
438
-
439
- 1. **Download React Native xcframeworks** for the specified version.
440
- 2. **Parse the library's podspec** to discover source files, headers,
441
- `header_dir`, dependencies, and compiler flags.
442
- 3. **Generate a temporary Package.swift** declaring the library as a target
443
- with dependencies on the RN xcframeworks.
444
- 4. **Build** using `xcodebuild` for each platform slice.
445
- 5. **Compose** the xcframework with organized headers, module map, and
446
- optional VFS overlay.
447
- 6. **Sign** the xcframework with the developer's code signing identity.
448
- 7. **Package** as `.tar.gz` with dSYM symbols.
449
-
450
- The tool includes code signing as a built-in step. Library authors provide
451
- their own signing identity (Apple Developer certificate); the tool handles
452
- the `codesign` invocation. Unsigned xcframeworks trigger macOS Gatekeeper
453
- warnings, so signing is strongly recommended for distributed artifacts.
454
- Documentation will cover how to create and manage a signing identity for
455
- this purpose.
456
-
457
- Library authors can integrate this into CI to publish prebuilt artifacts on
458
- every release, targeting a matrix of React Native versions and build flavors.
459
-
460
- #### Version compatibility
461
-
462
- A library's xcframework must be built against a compatible React Native
463
- version. The prebuild tool embeds metadata (React Native version, library
464
- version, build flavor, minimum iOS version) inside the xcframework. The
465
- download step verifies compatibility at setup time, warning if a library was
466
- built against a different React Native version than the app is using.
467
-
468
- ## Drawbacks
469
-
470
- ### Transition period: supporting both CocoaPods and SPM
471
-
472
- With CocoaPods trunk going read-only in December 2026, the migration to SPM is
473
- necessary rather than optional. During the transition period, both build
474
- systems must be supported in parallel. Bug fixes, new features, and build-phase
475
- changes need to be tested against both CocoaPods and SPM until CocoaPods
476
- support is eventually removed.
477
-
478
- ### Download size
479
-
480
- Prebuilt xcframeworks for React Native core are compressed as tar.gz archives.
481
- Individual library xcframeworks are typically 1–15 MB in debug mode including
482
- dSYM bundles. Both debug and release flavors are needed, which doubles the
483
- total. While artifacts are cached locally after the first download, CI
484
- environments without persistent caches will re-download on every build.
485
-
486
- ### Ecosystem adoption takes time
487
-
488
- Third-party libraries must opt in to the prebuild workflow. During the
489
- transition period, many libraries will only support CocoaPods. Apps that depend
490
- on these libraries cannot fully migrate to SPM until the libraries catch up.
491
- This creates a chicken-and-egg problem that may slow adoption.
492
-
493
- ### SPM limitations require `.xcodeproj` generation
494
-
495
- SPM does not support build script phases, `post_install` hooks, or the kind of
496
- build-time customization that CocoaPods provides via its Podfile DSL. The
497
- current design works around this by generating an `.xcodeproj` with explicit
498
- build phases for JS bundling, Hermes engine copying, VFS overlay setup, and
499
- autolinking sync. This is a known limitation of the current approach. If Apple
500
- expands SPM's plugin API to support arbitrary script execution with file I/O
501
- and network access, the `.xcodeproj` could be eliminated in favor of a purely
502
- SPM-native workflow — but this is a future direction that depends on Apple's
503
- roadmap, not something this proposal can resolve.
504
-
505
- ### Committed xcodeproj edits
506
-
507
- The generated `<App>.xcodeproj` is committed and may carry user edits —
508
- signing, capabilities, Build Phases, custom schemes. The `update` action is
509
- **create-if-missing** to protect those edits, which means the script does
510
- not propagate generator improvements into existing projects automatically.
511
- Bug fixes that change the emitted pbxproj need an explicit
512
- `--force-xcodeproj` run to take effect. A future improvement could
513
- preserve user-side edits through a merge step rather than full overwrite —
514
- see "Hardening `update --force-xcodeproj`" in unresolved questions.
515
-
516
- ## Alternatives
517
-
518
- ### Compile React Native from source as SPM targets
519
-
520
- Compiling React Native's C++/Objective-C sources from source as SPM targets is
521
- not the default path due to the ~2000 source files and complex header layout,
522
- which makes clean build times significantly longer. However, source
523
- compilation support is a goal for specific use cases:
524
-
525
- - **Debugging React Native internals** — developers investigating bugs or
526
- contributing fixes to React Native itself need to build from source with
527
- debug symbols.
528
- - **Apps requiring source patches** — projects like Expo Go that need to modify
529
- React Native source code to build successfully, or apps that apply patches
530
- via tools like `patch-package`.
531
-
532
- The source compilation path would reuse the same SPM package structure but
533
- replace binary xcframework targets with source targets. This is planned as a
534
- `--source` flag to `react-native spm`.
535
-
536
- ### SPM build tool plugins
537
-
538
- SPM plugins were evaluated as a way to eliminate the `.xcodeproj` (see
539
- [SPM Plugins Assessment](spm-plugins-assessment.md) for details). The key
540
- findings:
541
-
542
- - **Post-build phases are impossible.** JS bundling and Hermes engine copying
543
- run after linking to place artifacts in the `.app` bundle. SPM has no
544
- post-build plugin capability — this is a deliberate design choice for build
545
- reproducibility.
546
- - **Sandbox restrictions.** Build tool plugins cannot write to the source tree,
547
- run `node`, or access `node_modules`. Pre-build phases like autolinking sync
548
- require all of these.
549
- - **No Xcode build settings.** SPM plugins do not receive `CONFIGURATION`,
550
- `BUILT_PRODUCTS_DIR`, or other settings that the JS bundling script relies on.
551
-
552
- A hybrid approach (some SPM plugins + some Xcode build phases) would be harder
553
- to reason about than the current uniform approach of all Xcode build phases.
554
- SPM plugins are not a viable alternative today.
555
-
556
- ## Adoption strategy
557
-
558
- This proposal introduces SPM as an **additional** build system. It is not a
559
- breaking change. CocoaPods continues to work exactly as before. The two
560
- workflows coexist — an app can have both `Podfile` and `Package.swift` in the
561
- same directory.
562
-
563
- ### Phase 1: React Native core (current)
564
-
565
- SPM works for React Native core frameworks and app-local native modules
566
- declared as `spmModules` in `react-native.config.js`. No third-party library
567
- support. This phase validates the architecture and developer experience with
568
- rn-tester and the helloworld template.
569
-
570
- ### Phase 2: Library ecosystem tooling
571
-
572
- Ship the `react-native-prebuild` CLI. Library authors can prebuild and publish
573
- xcframeworks for their libraries. The autolinking step reads `spm.xcframework`
574
- from installed libraries and downloads artifacts automatically. Libraries
575
- without xcframeworks fall back to `spm.source` (source compilation) or manual
576
- `spmModules` entries.
577
-
578
- ### Phase 3: Ecosystem-wide adoption
579
-
580
- Popular libraries ship prebuilt xcframeworks from CI. App developers get
581
- near-zero-compilation iOS builds — only app code and codegen output are
582
- compiled. React Native provides clear documentation and tooling
583
- (`react-native-prebuild`) to help library authors build and publish
584
- xcframeworks — for example, CI workflow templates and guidance on publishing to
585
- Maven or GitHub Releases. Prebuilt xcframeworks are recommended but not
586
- required; libraries that don't provide them fall back to source compilation.
587
-
588
- ### Migration path for existing apps
589
-
590
- 1. Run `npx react-native spm` from the project root (auto-redirects into
591
- `ios/`).
592
- 2. Accept the rename prompt — your existing `AppName.xcodeproj` becomes
593
- `AppName.xcodeproj.legacy` (preserved for rollback).
594
- 3. Commit the new `AppName.xcodeproj/` (SPM-managed) and the renamed
595
- `AppName.xcodeproj.legacy/`. `git mv` tracks the rename cleanly.
596
- 4. Run `npm run ios` and verify the SPM build.
597
- 5. Once validated, optionally delete the `.legacy` backup, `Podfile`,
598
- `Pods/`, and `.xcworkspace`.
599
-
600
- To roll back: `npx react-native spm clean --project` deletes the SPM
601
- xcodeproj and renames `.legacy` back to the canonical filename.
602
-
603
- No changes to JavaScript code, Metro configuration, or Android setup are
604
- required.
605
-
606
- ### Upgrading React Native
607
-
608
- After upgrading `react-native` in `package.json` and running `npm install`,
609
- the auto-sync build phase detects the `node_modules` mtime change on the next
610
- Xcode build and re-runs the sync step automatically. This downloads the new
611
- version's xcframeworks, regenerates the sub-packages, and updates autolinking.
612
- No manual edits are needed — the xcodeproj's sub-package references are
613
- stable, and those sub-packages are fully regenerated each run. Developers
614
- can also run `react-native spm` manually to trigger the update before
615
- building.
616
-
617
- ## How we teach this
618
-
619
- ### Documentation
620
-
621
- - Add a **"Building with SPM"** guide to the React Native docs, parallel to the
622
- existing CocoaPods setup guide.
623
- - Update the **"Getting Started"** guide to present SPM as an option alongside
624
- CocoaPods, with SPM as the recommended path for new projects once Phase 2 is
625
- stable.
626
- - Add a **library author guide** explaining how to use `react-native-prebuild`
627
- and publish xcframeworks.
628
-
629
- ### CLI discoverability
630
-
631
- - `react-native spm --help` should provide clear usage instructions and
632
- explain each step.
633
- - Error messages should include actionable suggestions (e.g., "Run
634
- `react-native spm init` for first-time setup").
635
- - The auto-sync build phase should surface warnings in Xcode's issue navigator
636
- when autolinking state is stale.
637
-
638
- ### Community template
639
-
640
- - The `react-native init` template should include SPM as an option (e.g.,
641
- `--pm spm` flag or interactive prompt).
642
- - The template should generate the initial `Package.swift` and `.xcodeproj` so
643
- that new projects work with SPM out of the box.
644
-
645
- ### Naming and terminology
646
-
647
- - **"SPM build"** or **"Swift Package Manager build"** to distinguish from the
648
- CocoaPods-based workflow.
649
- - **"xcframeworks"** when referring to the prebuilt binary artifacts.
650
- - Avoid the term "pods" when discussing the SPM workflow to prevent confusion.
651
-
652
- ## Unresolved questions
653
-
654
- 1. **How should version compatibility be enforced?** A library's xcframework
655
- must be built against a compatible React Native version. Should the download
656
- step enforce strict version matching, accept semver-compatible ranges, or
657
- simply warn on mismatch?
658
-
659
- 2. **Where should library xcframeworks be hosted?** Maven Central (consistent
660
- with React Native core), GitHub Releases (simpler for library authors), or a
661
- dedicated registry (better discovery and compatibility metadata). Each has
662
- different trade-offs for discoverability, reliability, and maintenance
663
- burden.
664
-
665
- 3. **Debug symbol (dSYM) distribution.** The prebuild pipeline produces dSYM
666
- bundles alongside xcframeworks, but the best way to distribute and consume
667
- them is not yet defined. Open questions include: should dSYMs be downloaded
668
- alongside xcframeworks automatically or on demand? How should they integrate
669
- with crash reporting services (Sentry, Crashlytics) that need dSYM UUIDs
670
- for symbolication? Should the download step place dSYMs in a location that
671
- Xcode's archive workflow picks up automatically?
672
-
673
- 4. **How should library authors validate SPM compatibility?** A validation
674
- command (`react-native-prebuild --validate`) could verify that a library's
675
- sources compile as an SPM target without producing a full release artifact.
676
- This would be useful for CI checks on pull requests.
677
-
678
- 5. **Hardening `update --force-xcodeproj`.** The default `update` is
679
- create-if-missing, which preserves user edits but means generator
680
- improvements don't propagate to existing projects automatically. Passing
681
- `--force-xcodeproj` clobbers everything. A future improvement could read
682
- the existing pbxproj, merge changes (signing, capabilities, custom Build
683
- Phases), and write back — likely via a proper Xcode project
684
- parser/generator (e.g., `@bacons/xcode`) rather than the current
685
- template-based approach. Planned work for production readiness.
686
-
687
- 6. **Auto-sync failure visibility.** The sync build phase currently emits
688
- `warning:` and exits 0 on failure, which means a broken autolinking state
689
- can persist silently across builds. Planned improvements include a strict
690
- mode (e.g., `RN_SPM_STRICT_SYNC=1` that exits non-zero on failure) and
691
- generating a `#warning` directive in a source file when sync fails, so
692
- Xcode surfaces the issue in the issue navigator even when build log
693
- warnings are missed.
694
-
695
- 7. **Monorepo and package manager compatibility.** The auto-sync build phase
696
- uses `node_modules` mtime to detect dependency changes. This has been
697
- tested with npm in the React Native monorepo but not yet with Yarn
698
- workspaces (hoisted or PnP), pnpm (symlinked `node_modules`), or Bun.
699
- These package managers structure `node_modules` differently and may require
700
- adjustments to the mtime detection logic. Validating and fixing
701
- compatibility across package managers is planned work.
702
-
703
- ## References
704
-
705
- - [RFC0508: Out-of-NPM Artifacts](https://github.com/react-native-community/discussions-and-proposals/blob/main/proposals/0508-out-of-npm-artifacts.md) — established the Maven-based artifact distribution pattern this proposal builds on
706
- - [Apple: Creating Swift Packages](https://developer.apple.com/documentation/xcode/creating-a-standalone-swift-package-with-xcode)
707
- - [SE-0272: Package Manager Binary Dependencies](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0272-swiftpm-binary-dependencies.md)