@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.
- package/Libraries/Animated/AnimatedEvent.js +1 -1
- package/Libraries/Animated/AnimatedImplementation.js +6 -2
- package/Libraries/Animated/nodes/AnimatedNode.js +1 -1
- package/Libraries/Animated/nodes/AnimatedValue.js +2 -2
- package/Libraries/Core/ReactNativeVersion.js +1 -1
- package/README.md +3 -3
- package/React/Base/RCTVersion.m +1 -1
- package/ReactAndroid/gradle.properties +1 -1
- package/ReactAndroid/src/main/java/com/facebook/react/modules/intent/IntentModule.kt +7 -3
- package/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/ReactNativeVersion.kt +1 -1
- package/ReactCommon/cxxreact/ReactNativeVersion.h +2 -2
- package/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTFontUtils.mm +1 -1
- package/changes.json +2 -2
- package/package.json +12 -12
- package/scripts/cocoapods/rndependencies.rb +31 -25
- package/scripts/replace-rncore-version.js +18 -2
- package/scripts/setup-apple-spm.js +28 -12
- package/scripts/spm/autolinking-plugins.js +80 -0
- package/scripts/spm/download-spm-artifacts.js +64 -6
- package/scripts/spm/expand-spm-dependencies.js +249 -30
- package/scripts/spm/generate-spm-autolinking.js +157 -40
- package/scripts/spm/generate-spm-package.js +30 -18
- package/scripts/spm/generate-spm-xcodeproj.js +595 -71
- package/scripts/spm/scaffold-package-swift.js +65 -24
- package/scripts/spm/spm-pbxproj.js +158 -28
- package/scripts/spm/spm-types.js +29 -3
- package/scripts/spm/spm-utils.js +117 -2
- package/sdks/.hermesv1version +1 -1
- package/sdks/hermes-engine/version.properties +1 -1
- package/types_generated/Libraries/Animated/AnimatedImplementation.d.ts +2 -2
- package/types_generated/Libraries/Animated/nodes/AnimatedNode.d.ts +4 -1
- package/types_generated/Libraries/Animated/nodes/AnimatedValue.d.ts +3 -3
- package/scripts/spm/__doc__/rfc-spm-xcframework.md +0 -707
- package/scripts/spm/__doc__/spm-autolinking-plugins.md +0 -244
- package/scripts/spm/__doc__/spm-header-paths-contract.md +0 -97
- package/scripts/spm/__doc__/spm-plugins-assessment.md +0 -128
- 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)
|