Untuk migrasi plugin Capacitor ke Swift Package Manager, tambahkan Package.swift di root plugin yang nama package dan product-nya sesuai dengan ekspektasi Capacitor CLI, pindahkan iOS code ke ios/Sources/<Target>, ganti file Objective-C CAP_PLUGIN bridge dengan CAPBridgedPlugin konformitas di Swift, lalu tambahkan Package.swift ke npm files . Tetap sertakan podspec agar aplikasi CocoaPods tetap berjalan.
Sejak Capacitor 8, cap add ios membuat proyek SPM secara default. Plugin tanpa Package.swift akan dilewati di aplikasi tersebut dengan peringatan “Some installed Capacitor plugins are not compatible with SPM”, dan pengguna akan melihat “plugin is not implemented” saat runtime. Panduan ini membahas migrasi manual, alat konverter, dan bagian yang sering dilewatkan panduan lain: aturan penamaan, resource, code Objective-C, dan dependensi pihak ketiga.
App teams moving their own project should read Cara Migrasi Aplikasi Capacitor Anda ke SPM instead.
Cara Capacitor menggunakan package Anda
Pada bunx cap sync ios, CLI menulis ulang ios/App/CapApp-SPM/Package.swift di dalam aplikasi. Untuk setiap plugin Capacitor yang memiliki Package.swift, ia menambahkan dua baris:
.package(name: "CapgoCapacitorUpdater", path: "../../../node_modules/@capgo/capacitor-updater")
// ...
.product(name: "CapgoCapacitorUpdater", package: "CapgoCapacitorUpdater")
Dua konsekuensinya:
- Package Anda dikonsumsi via local path dari
node_modules, not from a git URL. YourPackage.swiftharus berada di root package npm, dan semua referensinya harus dipublikasikan ke npm. - Namanya diambil dari nama npm, bukan dari podspec Anda. CLI menghapus
@, mengubah/dan-ke dalam_, lalu ubah setiap segmen menjadi camel-case dan kapitalisasi huruf pertamanya:
| package npm | Nama package dan product yang diperlukan |
|---|---|
@capacitor/haptics |
CapacitorHaptics |
@capgo/capacitor-updater |
CapgoCapacitorUpdater |
capacitor-my-plugin |
CapacitorMyPlugin |
@acme/capacitor-scanner |
AcmeCapacitorScanner |
Jika Package.swift menggunakan nama atau nama product lain, aplikasi akan gagal resolve package dengan error seperti product 'X' required by package 'capapp-spm' target 'CapApp-SPM' not found.
Langkah 1: tulis Package.swift
Ini adalah layout yang digunakan plugin resmi. Berikut adalah manifest asli dari @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")
]
)
Catatan:
- Keep
swift-tools-version: 5.9kecuali Anda butuh fitur yang lebih baru. Package yang di-generate aplikasi juga menggunakan 5.9 secara default, dan Capacitor 8 belum mendukung Swift 6 secara resmi. - Gunakan
from: "8.0.0"untukcapacitor-swift-pm. Aplikasi mengunci versi eksak yang sesuai dengan yang terinstal@capacitor/ios, and a range lets SPM resolve both.branch:atauexact:akan menyebabkan konflik resolusi. - Nama target bisa apa saja, tetapi akan menjadi nama modul Swift. Hindari nama generik seperti
Pluginyang bentrok dengan plugin lain.
Langkah 2: pindahkan source
SPM mengharapkan satu folder per target. Converter dan template resmi menggunakan:
my-plugin/
├── Package.swift
├── MyPlugin.podspec
├── ios/
│ ├── Sources/
│ │ └── MyPlugin/
│ │ ├── MyPlugin.swift
│ │ └── MyPluginImplementation.swift
│ └── Tests/
│ └── MyPluginTests/
│ └── MyPluginTests.swift
└── package.json
Kemudian hapus apa yang tidak dibutuhkan SPM: ios/Plugin.xcodeproj, ios/Plugin.xcworkspace, ios/Podfile, ios/Plugin/Info.plist dan ios/PluginTests/Info.plist.
Perbarui podspec agar CocoaPods mengompilasi file yang sama:
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'
Langkah 3: ganti bridge Objective-C dengan CAPBridgedPlugin
Plugin lama mendaftarkan method di Plugin.m:
#import <Capacitor/Capacitor.h>
CAP_PLUGIN(MyPlugin, "MyPlugin",
CAP_PLUGIN_METHOD(echo, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(startScan, CAPPluginReturnCallback);
)
SwiftPM tidak bisa mencampur Objective-C dan Swift dalam satu target, jadi ini dipindahkan ke class 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
// ...
}
}
Aturan mapping:
identifieradalah argumen pertama dariCAP_PLUGIN(nama class).jsNameadalah argumen kedua, nama yang digunakan diregisterPlugin('MyPlugin')di TypeScript Anda.- Setiap
CAP_PLUGIN_METHODmenjadi satuCAPPluginMethod, dengan tipe return yang sama (CAPPluginReturnPromise,CAPPluginReturnCallbackatauCAPPluginReturnNone).
Metode yang hilang dari pluginMethods berhasil dikompilasi dan hanya gagal saat dipanggil JavaScript. Cari dengan @objc func dengan sebuah CAPPluginCall dan hitung jumlahnya terhadap array. Lalu hapus Plugin.h dan Plugin.m.
CAPBridgedPlugin juga berfungsi dengan CocoaPods, jadi file Swift yang sama bisa digunakan untuk kedua package manager tersebut.
Langkah 4: perbarui package.json dan .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"
}
}
Nilai -scheme adalah nama package Anda. Lupa menyertakan Package.swift atau ios/Sources di dalam files adalah alasan paling umum mengapa plugin berjalan lancar via git checkout tapi error setelah npm publish. Jalankan bun pm pack --dry-run dan cek daftar filenya.
Tambahkan output build SPM ke .gitignore:
.build/
Package.resolved
/Packages
.swiftpm/
Langkah 5: tangani hal yang tidak bisa dilakukan konverter
Dependensi pihak ketiga
Baris podspec seperti s.dependency 'Alamofire', '~> 5.9' menjadi package dependency dan product pada target Anda:
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")
]
Jika SDK hanya menyediakan .xcframework, masukkan ke dalam package npm dan gunakan binary target:
.binaryTarget(name: "VendorSDK", path: "ios/Frameworks/VendorSDK.xcframework")
Jika vendor tidak menyediakan package SPM maupun xcframework, Anda belum bisa menambahkan dukungan SPM. Tetap gunakan CocoaPods untuk plugin tersebut dan jelaskan di README.
Pastikan rentang versi di Package.swift dan podspec tetap selaras. Dua plugin dalam satu app yang membutuhkan versi SDK yang tidak kompatibel akan menyebabkan error resolusi di package manager mana pun.
Resource dan manifest privasi
Gambar, JSON, storyboard, dan PrivacyInfo.xcprivacy harus dideklarasikan:
.target(
name: "MyPlugin",
dependencies: [/* ... */],
path: "ios/Sources/MyPlugin",
resources: [
.process("Resources"),
.copy("PrivacyInfo.xcprivacy")
])
CocoaPods butuh file yang sama dideklarasikan di podspec. Masukkan ke dalam resource bundle bernama:
s.resource_bundles = {
'MyPluginResources' => [
'ios/Sources/MyPlugin/Resources/**/*',
'ios/Sources/MyPlugin/PrivacyInfo.xcprivacy'
]
}
Di code, resource SPM berada di Bundle.module, sedangkan CocoaPods menaruhnya di MyPluginResources.bundle, di samping class plugin. Beralihlah dengan SWIFT_PACKAGE flag, yang didefinisikan otomatis oleh SwiftPM:
#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
Objective-C code Asli
Jika sebagian dari plugin menggunakan Objective-C (bukan sekadar bridge), masukkan ke dalam target tersendiri dengan public header di dalam sebuah include , dan buat target Swift bergantung padanya:
.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")
Swift code kemudian melakukan import MyPluginObjC.
Cara otomatis: cap2spm
Dari tim Ionic, capacitor-plugin-converter membangun sebuah cap2spm binary yang membaca Plugin.m dan Plugin.h, menambahkan CAPBridgedPlugin conformance ke Swift class Anda, membuat Package.swift, memindahkan file ke Sources dan Tests, memperbarui podspec dan package.json, serta menghapus file proyek Xcode lama.
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
Ini dirancang untuk plugin yang hanya berisi Swift selain file bridge. Jalankan di git tree yang bersih lalu lakukan Langkah 5 secara manual.
Opsi lainnya:
- Scaffold plugin baru dengan
bun create @capacitor/plugindan salin implementasi Anda ke dalamnya. Template ini sudah mendukung SPM dan CocoaPods. Cara ini sering kali lebih cepat untuk plugin kecil. - Gunakan agent. Skill
capacitor-plugin-spm-supportdi Keahlian Capgo menjelaskan Package.swift, pembersihan bridge, resource, danpackage.json. Instal denganbunx skills add Cap-go/capgo-skills.
Tes di aplikasi SPM nyata
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
Pastikan bahwa ios/App/CapApp-SPM/Package.swift mencantumkan package Anda, dan bahwa sync mencetak “All Capacitor plugins have a Package.swift file”. Lalu build di perangkat dan panggil setiap method. Ulangi di app yang dibuat dengan bunx cap add ios --packagemanager CocoaPods untuk memastikan podspec masih berfungsi.
Troubleshooting
product 'X' required by package 'capapp-spm' ... not found. Nama package atau produk tidak cocok dengan nama yang diturunkan dari nama npm Anda. Lihat tabel di atas.
"MyPlugin" plugin is not implemented on ios. Entah Package.swift tidak dipublikasikan, jsName tidak cocok dengan registerPlugin, atau class tidak memiliki CAPBridgedPlugin. Periksa folder plugin di node_modules.
Multiple targets named 'Plugin' atau error module duplikat. Dua plugin menggunakan nama target yang sama. Ubah nama Anda menjadi sesuatu yang unik. Tim app juga bisa mengatasinya dengan experimental.ios.spm.packageOptions (moduleAliases atau symlink) di konfigurasi Capacitor, tersedia sejak CLI 8.4.
target 'X' contains mixed language source files. File Objective-C dan Swift berada dalam satu folder. Pisahkan menjadi dua target.
Xcode menampilkan error package yang usang setelah diperbaiki. Gunakan File > Packages > Reset Package Caches, lalu build kembali.
Jika Anda juga perlu mengaktifkan fitur opsional per aplikasi, baca cara menggunakan SPM package traits di Capacitor. Untuk aplikasi yang harus tetap menggunakan CocoaPods saat ini, lihat cara menggunakan CocoaPods dengan Capacitor 8, dan untuk gambaran besarnya, SPM vs CocoaPods untuk Capacitor.