Pour migrer un plugin Capacitor vers le gestionnaire de packages Swift, ajoutez un fichier Package.swift à la racine du plugin dont le nom de package et de produit correspond à ce que Capacitor CLI attend, déplacez le code iOS dans ios/Sources/<Target>Remplacez l'Objective-C CAP_PLUGIN conformément à Swift, et ajoutez CAPBridgedPlugin à la liste des __CAPGO_KEEP_0__ Package.swift à la npm files list. Keep the podspec so CocoaPods apps keep working.
Depuis Capacitor 8, cap add ios crée des projets SPM par défaut. Un plugin sans Package.swift est ignoré dans ces applications avec l'avertissement « Certaines Capacitor installées ne sont pas compatibles avec SPM », et les utilisateurs voient ensuite « plugin non implémenté » en temps de exécution. Cette guide couvre la migration manuelle, l'outil de conversion, et les parties que la plupart des guides ignorent : les règles de nommage, les ressources, les code Objective-C et les dépendances tierces.
Les équipes de développement de l'application devraient lire How to Migrate Your Capacitor App to SPM instead.
Comment Capacitor consomme votre package
On bunx cap sync iosLes rewrites du CLI ios/App/CapApp-SPM/Package.swift dans l'application. Pour chaque plugin Capacitor qui a un Package.swiftDeux conséquences :
.package(name: "CapgoCapacitorUpdater", path: "../../../node_modules/@capgo/capacitor-updater")
// ...
.product(name: "CapgoCapacitorUpdater", package: "CapgoCapacitorUpdater")
Deux conséquences :
- Votre package est consommé par chemin local depuis
node_modules, et non depuis une URL Git. VotrePackage.swiftdoit se trouver à la racine du package npm, et tout ce qu'il référence doit être publié sur npm. - Le nom est dérivé du nom du package npm,, et non de votre podspec. Le CLI supprime
@, tourne/et-en_Alors, camel-casez chaque segment et majuscole le premier caractère.
| package npm | Nom de package et de produit requis |
|---|---|
@capacitor/haptics |
CapacitorHaptics |
@capgo/capacitor-updater |
CapgoCapacitorUpdater |
capacitor-my-plugin |
CapacitorMyPlugin |
@acme/capacitor-scanner |
AcmeCapacitorScanner |
Si votre Package.swift utilise un autre nom ou un nom de produit, l'application échoue à résoudre les packages avec une erreur comme product 'X' required by package 'capapp-spm' target 'CapApp-SPM' not found.
Étape 1 : écrire Package.swift
Ceci est la disposition que les plugins officiels utilisent. Voici le manifeste réel de @capacitor/haptics 8:
// swift-tools-version: 5.9
import PackageDescription
let package = Package(
name: "CapacitorHaptics",
platforms: [.iOS(.v15)],
products: [
.library(
name: "CapacitorHaptics",
targets: ["HapticsPlugin"])
],
dependencies: [
.package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "8.0.0")
],
targets: [
.target(
name: "HapticsPlugin",
dependencies: [
.product(name: "Capacitor", package: "capacitor-swift-pm"),
.product(name: "Cordova", package: "capacitor-swift-pm")
],
path: "ios/Sources/HapticsPlugin"),
.testTarget(
name: "HapticsPluginTests",
dependencies: ["HapticsPlugin"],
path: "ios/Tests/HapticsPluginTests")
]
)
Remarques :
- Conservez
swift-tools-version: 5.9sauf si vous avez besoin d'une fonctionnalité plus récente. L'application génère également un package qui utilise par défaut 5.9, et Capacitor 8 n'approuve pas officiellement Swift 6 encore. - Utilisez
from: "8.0.0"pourcapacitor-swift-pm. L'application fixe une version exacte qui correspond à celle installée@capacitor/ios, et une plage permet à SPM de résoudre les deux.branch:ouexact:causera des conflits de résolution. - The nom cible peut être n'importe quoi, mais il devient le nom de module Swift. Évitez les noms génériques comme
Pluginqui se chevauchent avec d'autres plugins.
Étape 2 : déplacez les sources
SPM attend un dossier par cible. Le convertisseur et le modèl’officiel utilisent :
my-plugin/
├── Package.swift
├── MyPlugin.podspec
├── ios/
│ ├── Sources/
│ │ └── MyPlugin/
│ │ ├── MyPlugin.swift
│ │ └── MyPluginImplementation.swift
│ └── Tests/
│ └── MyPluginTests/
│ └── MyPluginTests.swift
└── package.json
Ensuite, supprimez ce que SPM n'a pas besoin. ios/Plugin.xcodeproj, ios/Plugin.xcworkspace, ios/Podfile, ios/Plugin/Info.plist and ios/PluginTests/Info.plist.
Ensuite, supprimez ce que SPM n'a pas besoin :
s.source_files = 'ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}'
s.ios.deployment_target = '15.0'
s.dependency 'Capacitor'
s.swift_version = '5.1'
Étape 3 : remplacez le pont Objective-C par CAPBridgedPlugin
Old plugins enregistrent des méthodes dans Plugin.m:
#import <Capacitor/Capacitor.h>
CAP_PLUGIN(MyPlugin, "MyPlugin",
CAP_PLUGIN_METHOD(echo, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(startScan, CAPPluginReturnCallback);
)
SwiftPM ne peut pas mélanger Objective-C et Swift dans un même cible, donc cela se déplace dans la classe Swift.
import Foundation
import Capacitor
@objc(MyPlugin)
public class MyPlugin: CAPPlugin, CAPBridgedPlugin {
public let identifier = "MyPlugin"
public let jsName = "MyPlugin"
public let pluginMethods: [CAPPluginMethod] = [
CAPPluginMethod(name: "echo", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "startScan", returnType: CAPPluginReturnCallback)
]
@objc func echo(_ call: CAPPluginCall) {
call.resolve(["value": call.getString("value") ?? ""])
}
@objc func startScan(_ call: CAPPluginCall) {
call.keepAlive = true
// ...
}
}
Règles de cartographie :
identifierÉtape 3 : remplacez le pont Objective-C par CAPBridgedPluginCAP_PLUGIN(le nom de la classe).jsNameest le deuxième argument, le nom utilisé dansregisterPlugin('MyPlugin')en TypeScript.- Chaque
CAP_PLUGIN_METHODdevient unCAPPluginMethod, gardant le même type de retour (CAPPluginReturnPromise,CAPPluginReturnCallbackouCAPPluginReturnNone).
Une méthode manquante de pluginMethods compile correctement et ne réussit qu'en cas d'appel JavaScript. Recherchez avec Grep @objc func avec CAPPluginCall compile correctement et ne réussit qu'en appelant JavaScript. Cherchez avec Plugin.h avec un Plugin.m.
CAPBridgedPlugin fonctionne également avec CocoaPods, donc le même fichier Swift sert à la fois les gestionnaires de packages.
Étape 4 : mettre à jour package.json et .gitignore
{
"files": [
"android/src/main/",
"android/build.gradle",
"dist/",
"ios/Sources",
"ios/Tests",
"Package.swift",
"MyPlugin.podspec"
],
"scripts": {
"verify:ios": "xcodebuild -scheme CapacitorMyPlugin -destination generic/platform=iOS"
}
}
La -scheme valeur est votre nom de package. En oubliant Package.swift ou ios/Sources Le files le principal motif pour lequel un plugin fonctionne à partir d'un checkout Git et se brise après npm publishou bun pm pack --dry-run vérifiez la liste des fichiers.
Ajoutez les résultats de compilation SPM à .gitignore:
.build/
Package.resolved
/Packages
.swiftpm/
et vérifiez la liste des fichiers.
Dépendances tierces
A une ligne de podspec comme s.dependency 'Alamofire', '~> 5.9' devient une dépendance de package et un produit sur votre cible :
dependencies: [
.package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "8.0.0"),
.package(url: "https://github.com/Alamofire/Alamofire.git", from: "5.9.0")
],
targets: [
.target(
name: "MyPlugin",
dependencies: [
.product(name: "Capacitor", package: "capacitor-swift-pm"),
.product(name: "Cordova", package: "capacitor-swift-pm"),
.product(name: "Alamofire", package: "Alamofire")
],
path: "ios/Sources/MyPlugin")
]
Si le SDK n'expédie que des .xcframeworkexpédiez-l’à l'intérieur du package npm et utilisez une cible binaire :
.binaryTarget(name: "VendorSDK", path: "ios/Frameworks/VendorSDK.xcframework")
Si un fournisseur n'offre pas de package SPM et pas de xcframework, vous ne pouvez pas ajouter le support SPM encore. Gardez le plugin CocoaPods uniquement et indiquez-le dans le README.
Conservez les plages de versions dans Package.swift et le podspec aligné. Deux plugins dans la même application qui nécessitent des versions incompatibles d'un SDK échoueront à la résolution dans l'un ou l'autre gestionnaire de package.
Les ressources et les manifestes de confidentialité
Images, JSON, storyboards et PrivacyInfo.xcprivacy doivent être déclarés :
.target(
name: "MyPlugin",
dependencies: [/* ... */],
path: "ios/Sources/MyPlugin",
resources: [
.process("Resources"),
.copy("PrivacyInfo.xcprivacy")
])
CocoaPods a besoin des mêmes fichiers déclarés dans le podspec. Mettez-les dans un ensemble de ressources nommé :
s.resource_bundles = {
'MyPluginResources' => [
'ios/Sources/MyPlugin/Resources/**/*',
'ios/Sources/MyPlugin/PrivacyInfo.xcprivacy'
]
}
En code, les ressources SPM vivent dans Bundle.module, tandis que CocoaPods les place dans celui-ci MyPluginResources.bundle, à côté de la classe du plugin. Passer à la SWIFT_PACKAGE flag, qui SwiftPM définit automatiquement :
#if SWIFT_PACKAGE
let resourceBundle = Bundle.module
#else
let resourceBundle: Bundle = {
let classBundle = Bundle(for: MyPlugin.self)
guard let url = classBundle.url(forResource: "MyPluginResources", withExtension: "bundle"),
let bundle = Bundle(url: url) else {
return classBundle
}
return bundle
}()
#endif
Réel Objective-C code
Si une partie du plugin est en Objective-C (et non seulement le pont), placez-la dans son propre cible avec des en-têtes publics dans un include dossier, et faites en sorte que la cible Swift dépende de celle-ci :
.target(
name: "MyPluginObjC",
path: "ios/Sources/MyPluginObjC",
publicHeadersPath: "include"),
.target(
name: "MyPlugin",
dependencies: [
"MyPluginObjC",
.product(name: "Capacitor", package: "capacitor-swift-pm")
],
path: "ios/Sources/MyPlugin")
La cible Swift code alors fait import MyPluginObjC.
La voie automatique : cap2spm
L'équipe d'Ionic’s capacitor-plugin-converter construit un cap2spm binaire qui lit Plugin.m et Plugin.h, ajoute CAPBridgedPlugin la conformité à votre classe Swift, génère Package.swift, déplace les fichiers vers Sources et Tests, met à jour le podspec et package.json, et supprime les anciens fichiers de projet Xcode.
curl -OL https://github.com/ionic-team/capacitor-plugin-converter/releases/latest/download/cap2spm.zip
unzip cap2spm.zip
xattr -d com.apple.quarantine ./cap2spm # binary is unsigned
./cap2spm --backup /path/to/my-plugin
Elle est conçue pour les plugins qui sont uniquement Swift, à l'exception des fichiers de pont. Exécutez-le sur un arbre Git propre et effectuez ensuite l'étape 5 à la main.
Autres options :
- Créez un plugin frais avec
bun create @capacitor/plugincopiez votre implémentation à l'intérieur. Le modèle prend déjà en charge SPM et CocoaPods. C'est souvent plus rapide pour les petits plugins. - Utilisez un agent. Le
capacitor-plugin-spm-supportla compétence en Compétences Capgo expliquez Package.swift, nettoyage de pont, ressources etpackage.jsonInstallez avecbunx skills add Cap-go/capgo-skills.
Testez dans une application SPM réelle
bun create @capacitor/app spm-test
cd spm-test
bun add @capacitor/ios
bun add ../my-plugin
bun run build
bunx cap add ios # SPM is the default in Capacitor 8
bunx cap sync ios
Vérifiez que ios/App/CapApp-SPM/Package.swift affiche votre package, et que sync a imprimé « Tous les Capacitor plugins ont un fichier Package.swift ». Ensuite, construisez sur un appareil et appelez chaque méthode. Répétez dans une application créée avec bunx cap add ios --packagemanager CocoaPods pour confirmer que le podspec fonctionne toujours.
Troubleshooting
product 'X' required by package 'capapp-spm' ... not found. Le nom du package ou du produit ne correspond pas au nom dérivé de votre npm.
"MyPlugin" plugin is not implemented on ios. Ou Package.swift n'a pas été publié, jsName ne correspond pas registerPlugin, ou la classe manque de CAPBridgedPlugin. Vérifiez le dossier du plugin dans node_modules.
Multiple targets named 'Plugin' ou des erreurs de module dupliqué. Deux plugins utilisent le même nom de cible. Renommez le vôtre pour quelque chose d'unique. Les équipes d'applications peuvent également s'en sortir avec experimental.ios.spm.packageOptions (moduleAliases ou symlink) dans la Capacitor config, disponible depuis CLI 8.4.
target 'X' contains mixed language source files. Fichiers Objective-C et Swift partagent un dossier. Séparez-les en deux cibles.
Xcode shows stale package errors after fixes. Use Ouvrir > Packages > Réinitialiser les caches de packagesEnsuite, reconstruire.
If you also need optional features toggled per app, read comment utiliser les traits de package SPM dans Capacitor. For apps that must stay on CocoaPods for now, see comment utiliser CocoaPods avec Capacitor 8Et pour la perspective plus large, SPM vs CocoaPods pour Capacitor.