📌

5 Steps to Migrate Capacitor Plugin to SPM Support

2024/11/28に公開

Let's migrate your Capacitor iOS plugin to support Swift Package Manager (SPM) in addition to CocoaPods. Read more about the benefits of SPM here: https://zenn.dev/rdlabo/articles/4456315c9ca829(This is Japanese Article)

1. Rename Plugin Files

The "plugin files" here refer to the header and m files located under ios/Plugin, and Swift files prefixed with @objc that are called first as plugins. Older plugin structures look like this:

  • Plugin.swift
  • Plugin.m
  • Plugin.h

This is confusing. Rename your files to the more modern structure below. Replace ** with your plugin name; for example, for an AdMob plugin, **Plugin.swift would become AdMobPlugin.swift.

  • **Plugin.swift
  • **Plugin.m
  • **Plugin.h

Note: You'll eventually delete files like Plugin.xcodeproj, so you don't need to rename them in Xcode. Simply rename the files themselves. Now open **Plugin.swift. If the class name isn't **Plugin, change it:

- @objc(Stripe)
- public class Plugin: CAPPlugin {
+ @objc(StripePlugin)
+ public class StripePlugin: CAPPlugin {

2. Use capacitor-plugin-converter

Use the capacitor-plugin-converter tool to automate parts of the conversion. This is straightforward: download the zip file using the following command:

% curl -OL https://github.com/ionic-team/capacitor-plugin-converter/releases/latest/download/cap2spm.zip

Double-click to extract the Unix executable. Let's say you downloaded it to the same directory as your payment plugin, which is located in the ./payment folder. (In this example, ** in **.swift is PaymentPlugin.) Run this command:

% ./cap2spm payment --no-backup

The --no-backup flag prevents renaming existing files with a .old extension. This is usually unnecessary if you're using Git. This command deletes **Plugin.h and **Plugin.m, moving their contents into the pluginMethods property of **Plugin.swift. Expect additions similar to this:

https://github.com/capacitor-community/admob/blob/master/ios/Sources/AdMobPlugin/AdMobPlugin.swift#L12-L29

SPM doesn't read header and m files, so this migration is necessary. A Package.swift file will also be automatically generated in the top directory.

3. Create a New Plugin within the Plugin

After considering various migration methods, creating a new plugin and copying the ios folder is the least problematic. Here's how to create a new plugin:

% cd payment # Navigate to your plugin folder (e.g., 'payment')
% npm init @capacitor/plugin@latest -- --package-id com.hoge.huga --repo https://example.com --author "hoge" --license MIT --description "hoge" ## Create a new plugin to copy into

For simplicity, I've adjusted the command to allow for placeholder values for unnecessary fields. You'll be prompted to fill in the following:

✔ What should be the npm package of your plugin?
 … Enter your current plugin's npm name.
✔ What directory should be used for your plugin?
… Plugin folder name. Let's use `new-template`.
✔ What should be the class name for your plugin?
… Enter the "**" part of `**Plugin.swift`.

npm install will run after file generation, but you can safely interrupt it.

4. Refresh the iOS Folder

Now, let's refresh the structure. We'll use parts of the new-template directory and keep what's needed from the existing structure. Remember, we're not changing the web and Android code, so avoid accidentally deleting or overwriting it.

4.1. Overwrite new-template with Existing Plugin Code

Move the existing plugin code (ios/plugins contents) to the new-template/ios/Sources/**Plugin directory.

4.2. Overwrite the Existing ios Folder with the new-template/ios Folder

The existing ios folder is now redundant. Because the structure has significantly changed, instead of deleting unnecessary files and readjusting paths, let's create a cleaner structure for the future.

Delete the existing ios folder and place the new-template/ios folder in its place.

4.4. Overwrite the Top-Level Package.swift with new-template/Package.swift

Overwrite the automatically generated Package.swift from step 2 with the one from the newly created plugin. In most cases, the newly generated Package.swift provides a cleaner structure than the one created by the migration tool.

4.5. Delete new-template

Delete the new-template directory; it's no longer needed.

4.6. Update the **.podspec File

Update the podspec file to reflect the change in the iOS directory path:

- s.source_files = 'ios/Plugin/**/*.{swift,h,m,c,cc,mm,cpp}'
+ s.source_files = 'ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}'

5. Update Package.swift

If you have dependencies, you need to update Package.swift. For example, let's say your podspec has these dependencies:

  s.dependency 'StripePaymentSheet', '~> 23.32.0'
  s.dependency 'StripeApplePay', '~> 23.32.0'

You need to add them to Package.swift as well:

    dependencies: [
        .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", branch: "main"),
+       .package(url: "https://github.com/stripe/stripe-ios-spm.git", branch: "main")
    ],
    targets: [
        .target(
            name: "StripePlugin",
            dependencies: [
                .product(name: "Capacitor", package: "capacitor-swift-pm"),
                .product(name: "Cordova", package: "capacitor-swift-pm"),
+               .product(name: "StripePaymentSheet", package: "stripe-ios-spm"),
+               .product(name: "StripeApplePay", package: "stripe-ios-spm")
            ],
            path: "ios/Sources/StripePlugin"),

Remember that you'll need to update both the podspec and Package.swift files to manage packages as you'll be using two package managers simultaneously.

That completes the migration!

Summary

While it would be ideal if capacitor-plugin-converter handled more of this automatically, the evolution of Capacitor plugin structures over time necessitates these steps. Copying and moving folders might seem complicated, but with a clear plan of the migration steps, it becomes a straightforward process. Give it a try!

Discussion