Skip to content

RFC: (iOS) Swift Package Manager in React Native (Cocoapods deprecation)#994

Open
chrfalch wants to merge 3 commits into
react-native-community:mainfrom
chrfalch:chrfalch/add-rfc-draft-for-spm
Open

RFC: (iOS) Swift Package Manager in React Native (Cocoapods deprecation)#994
chrfalch wants to merge 3 commits into
react-native-community:mainfrom
chrfalch:chrfalch/add-rfc-draft-for-spm

Conversation

@chrfalch
Copy link
Copy Markdown

@chrfalch chrfalch commented Apr 21, 2026

This RFC contains a plan and technical description for how to deprecate Cocoapods and replace it with Swift Package Manager on iOS.

RFC Document

Copy link
Copy Markdown
Member

@mrousavy mrousavy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall this is a really exciting!
I added a section about Mixing C++ and Swift with the direct interop, used by Nitro and all Nitro libraries as this is quite relevant to the ecosystem imo.
I'm sure I can find a solution for this soon.

Thanks for the great RFC Christian!

### Build from source

Build React Native from source will be supported in the final version of the SPM package system, but the current POC does not support it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
### Mixing C++ and Swift
As per [the "Setting Up Mixed-Language Swift and C++ Projects" Swift documentation](https://www.swift.org/documentation/cxx-interop/project-build-setup/#mixing-swift-and-c-using-swift-package-manager), Swift Package Manager (SPM) **does not yet support Swift and C++ in the same target.**
This causes issues for Swift + C++ libraries or frameworks, including but not limited to the entire [Nitro Modules](https://nitro.margelo.com) ecosystem, as Nitro is built ontop of the direct Swift + C++ interop feature, and uses calls from Swift -> C++, as well as calls from C++ -> Swift within the same target/library - aka bi-directional interop.
This should not impose a problem for react-native directly as react-native uses Objective-C(++) for interoping between the languages (even Swift - C++ interop uses Objective-C(++) as a bridge, not **direct** Swift C++ interop).
The [Margelo](https://margelo.com) team is investigating whether this can be solved easily in Nitro - e.g. by using separate targets (one C++, one Swift, and possibly a third one to somehow break the bi-directional dependency).
As of today, this will affect popular libraries like react-native-vision-camera, react-native-mmkv, react-native-unistyles, react-native-video, and more.

I'll make sure to find a solution for this soon.

Image

Copy link
Copy Markdown

@Saadnajmi Saadnajmi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you so much for this! I look forward to the new future. I left some minor comments and nits, but this looks very well thought out 👍

- `ReactNativeDependencies`, which packages the native third-party dependencies used by React Native
- `hermes-engine`, which provides the Hermes runtime

The prototype uses a single script, `setup-ios-spm.js`, to prepare an app for this layout. That script:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A slight nit: Since this will be used for iOS and Mac Catalyst in React Native Core, tvOS in react-native-tvos, and macOS + visionOS in React Native macOS, could we name this something like setup-apple-spm.js? There's a lot of places where ios is hardcoded (build/generated/ios the biggest one that comes to mind) and I think it's more representative to say this is for the apple ecosystem of platforms.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree!


This keeps the developer-facing model close to today’s React Native workflow: dependencies are still discovered from the JavaScript project, but the integration backend is SPM instead of CocoaPods.
Local modules
The prototype also supports additional local modules through `spmModules` in `react-native.config.js`. This makes it possible to include native code that is not otherwise discovered through standard autolinking.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One edge case that used to not work but I think does now is a monorepo with several library packages, and one app. Old autolinking used to not work and I would hardcode extra pods in my podfile that pointed to the "local" podspecs, but nowadays autolinking works with local packages too. It would be great if that kept working with SPM too.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You will have a separate Package.swift files that you can commit and change as much as you like - it will reference another Package.swift file that contains the auto linked targets.

Comment on lines +127 to +129
After initialization, autolinking is normally kept up to date automatically by the generated Xcode project. The prototype adds an auto-sync build phase that detects dependency changes and regenerates the autolinked package before compilation when needed.

When a manual refresh is needed, for example after changing native dependencies or local module configuration, developers can re-run:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the idea that if someone adds a node module with native code, there's a script phase somewhere that detects that the user might be missing an autolinked pakage?
I.E:

yarn ios --init
yarn add @shopify/react-native-skia
# User runs and builds the app without running `yarn ios`

Would there be an error, or does the "auto-sync build phase" regenerate autolinked/Package.Swift?? I suppose this is similar to how RN devs know they need to pod install constantly, and much better than adding a post install.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As for now the flow will basically be the same, when you update your dependencies you will need to update. This can of course be improved - the problem today is that running pod install is very slow - otherwise we'd be doing it automatically I guess.


That mechanism is enough to prove that source-based native modules can be integrated into the SPM flow today.

For third-party libraries published to npm, the longer-term direction should be to add SPM metadata alongside the existing React Native autolinking metadata in `react-native.config.js`. That keeps React Native’s dependency discovery in one place and lets the CLI/autolinking pipeline decide how to integrate each library.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To confirm, autolinking would not do what the CLI does today with cocoapods and look for Package.Swift files at the root of every node module? That seems fine, just wanted to confirm.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Saadnajmi - Not sure what you're asking for here? We will basically try to use the same logic for auto linking since it is agnostic when it comes to wether we're using Cocoapods or SPM - it is all about collecting the modules that needs to be linked.

Copy link
Copy Markdown

@hurali97 hurali97 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, thanks for the very well put RFC 🚀

From Brownfield perspective, I have two points in addition to a review:


## Brownfield

Brownfield projects will also be able to use Package.swift files and integrate easily with existing Swift or Objective-C based apps, either as precompiled XCFrameworks or as source - both defined in a Package.swift file. There will be an option in the cli for generating separate Package.swift files for distributing the app as a brownfield target.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There will be an option in the cli for generating separate Package.swift files for distributing the app as a brownfield target.

Is this intended to support a similar use-case we previously did with Podfile using the following code?

target 'BrownfieldLib' do
  inherit! :complete
end

Context: In brownfield apps, when we create a new framework target inside a RNApp, we add the above block in RNApp/ios/Podfile to inherit the package linking and all to the framework target.

Since this behavior may not directly be available with SPM - I am wondering whether the suggested approach of creating a separate Package.swift for the framework target will have duplications containing the same parts as App Target's Package.swift file.

@hurali97
Copy link
Copy Markdown

  • static and dynamic frameworks

How are we planning to support this with SPM? With cocoapods, we would usually change this line to toggle between static and dynamic framework linkage:

linkage = ENV['USE_FRAMEWORKS']

Since with the final shape of SPM, we may not have this Podfile - so I wonder have you thought of any ideas on how we support it?

This in general should be for both greenfield and brownfield.

@hurali97
Copy link
Copy Markdown

  • Brownfield Packaging

This is rather third party library scoped https://github.com/callstack/react-native-brownfield. There we have a few brownfield packages and 3 of them are shipped as separate xcframeworks.

The user then have the flexibility to either:

  • Drag and Drop them to their xcode projects
  • Distribute them via SPM

We do not provide first class support for SPM but that is doable by the user. Now that this RFC is actually going to make that support in the core, we will also comply and extend it to our brownfield packaging. This does NOT in anyway means that the generated SPM from this package needs to be linked by the Brownfield Target - rather by the native host App consuming it.

One point on that:
If with this RFC, we ship a separate Package.swift for Brownfield Target, then we are thinking to opt for the following approach for packaging brownfield:

  • Create a Package.swift with ReactBrownfield.xcframework, brownfield-navigation.xcramework and brownie.xcframework - as binaryTargets
  • Reference the generated Package.swift for Brownfield Target inside this new Package.swift
  • Add the binaryTargets to the Brownfield Target
  • Ship this Package.swift to the native host App

The above is the layout of what I think we can do once we have this RFC delivered as a RC. Before that, we can also experiment with SPM and once RFC is released, we can change the local link for Brownfield Target to the Package.swift link and keep the rest Package.swift the same with binaryTargets.

Copy link
Copy Markdown

@tjzel tjzel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the well-thought RFC, I like this direction.

I haven't had the opportunity to use SPM but I wonder if libraries can comfortably hook some scripts prior to compilation using it.

Libraries like Reanimated and Worklets currently execute some custom Ruby scripts inside .podspec to assert proper configuration and obtain compilation flags.

It would be perfect if library authors could extract their common logic from build.gradle and .podspec and put it into react-native.config.js file but I'm aware it's out of scope of this RFC.


### Build from source

Build React Native from source will be supported in the final version of the SPM package system, but the current POC does not support it.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it mean that stepping into RN code while debugging will be only available when using Cocoapods, during the transition period?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Until we enable building from source with SPM, yes, you'll have to go back and use Cocoapods, unfortunately.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't mean that we will never support it - we just have to set a few limits to our 1 phase scope.

@cipolleschi
Copy link
Copy Markdown

@tjzel I think you should be able to run Swift PM Plugins to run prebuild code and ensure that everything is properly configured. That's a good point to add to the RFC, that libraries should be able to ship and configure Swift PM plugins. @chrfalch

I believe it is ok if the first implementation doesn't have it, and we work with you folks from Reanimated to add the feature! 😉

@chrfalch
Copy link
Copy Markdown
Author

@tjzel and @cipolleschi: Build plugins have limited access to what they can do - but package resolving runs the Package.swift code and can contain almost anything you need, including updating script files or running separate pipelines. This is the equivalent of running pod install and will probably be the most popular hook for doing things like you mention.

@chrfalch
Copy link
Copy Markdown
Author

  • static and dynamic frameworks

How are we planning to support this with SPM? With cocoapods, we would usually change this line to toggle between static and dynamic framework linkage:

linkage = ENV['USE_FRAMEWORKS']

Since with the final shape of SPM, we may not have this Podfile - so I wonder have you thought of any ideas on how we support it?

This in general should be for both greenfield and brownfield.

A package.swift file can include references to both prebuilt XCFrameworks and source files - so as long as the package you want to consume (like Firebase packages) is packaged as a framework you should be able to consume it from your package - without having to change anything in your project.

- generates `autolinked/Package.swift`,
- downloads the required prebuilt XCFramework artifacts,
- generates the local SPM packages consumed by the app, and
- creates an Xcode project configured for the SPM flow.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is a new project created every time this script is called?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, only first time. You should be able to check in both the main Xcode project and Package.swift file.

After initialization, manual refresh is done with:

```bash
yarn ios # it calls node node_modules/react-native/scripts/setup-ios-spm.js under the hoods
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use the ios "verb" to build the iOS project. Does this script also build the app or do we only call this script when we need to manually refresh the project?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First version will most likely need you to run a script/command when updating dependencies like with Cocoapods.

Comment on lines +40 to +44
- runs React Native Codegen,
- generates `autolinked/Package.swift`,
- downloads the required prebuilt XCFramework artifacts,
- generates the local SPM packages consumed by the app, and
- creates an Xcode project configured for the SPM flow.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will there be any hooks for customizations that require running scripts?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great idea, we haven't made any decisions about this yet - can you come up with some use cases for us to look into? (Remember that you will be able to run scripts from the resolve phase of your own package.swift code)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In RNTA, we also generate the Xcode project files when pod install is run. Additionally, we also:

  • Disable warnings in third party libraries to reduce noise
  • Define macros to enable/disable certain parts in third party libraries
  • Add assets/resources declared in an external file
  • Configure code signing (also declared in an external file)
  • Apply config plugins (if applicable)
  • Various scripts to validate configuration files
  • Generate code that must be included in the project

Copy link
Copy Markdown

@tibbe tibbe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for putting this together!

What's the story for migrating existing (non-framework) setups that already have a generated XCode project, etc to the new setup?

4. The app’s generated Xcode project depends on that package, so native modules are available through normal SPM package resolution.

This keeps the developer-facing model close to today’s React Native workflow: dependencies are still discovered from the JavaScript project, but the integration backend is SPM instead of CocoaPods.
Local modules
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this intended as a heading (it's currently not rendered as such)?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants