Untuk memigrasikan plugin Capacitor ke Swift Package Manager, tambahkan Package.swift di root plugin yang nama package dan product-nya sesuai dengan yang diharapkan Capacitor CLI, pindahkan iOS code ke dalam ios/Sources/<Target>, ganti file bridge Objective-C CAP_PLUGIN bridge file dengan CAPBridgedPlugin conformance di Swift, lalu tambahkan Package.swift ke npm files . Tetap simpan podspec agar aplikasi CocoaPods tetap berjalan.
Sejak Capacitor 8, cap add ios secara default membuat proyek SPM. Plugin tanpa Package.swift akan dilewati di aplikasi tersebut dengan peringatan “Some installed Capacitor plugins are not compatible with SPM”, lalu pengguna melihat “plugin is not implemented” saat runtime. Panduan ini membahas migrasi manual, alat konverter, dan bagian yang sering dilewati panduan lain: aturan penamaan, resource, Objective-C code, dan dependensi pihak ketiga.
Tim aplikasi yang memigrasikan proyeknya sendiri sebaiknya membaca Cara migrasi aplikasi Capacitor Anda ke SPM instead.
Cara Capacitor menggunakan package Anda
Di 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 dipakai via local path dari
node_modules, bukan dari URL git. AndaPackage.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 tiap segmen ke camelCase dan kapitalkan huruf pertamanya:
| paket npm | Nama paket dan produk yang wajib |
|---|---|
@capacitor/haptics |
CapacitorHaptics |
@capgo/capacitor-updater |
CapgoCapacitorUpdater |
capacitor-my-plugin |
CapacitorMyPlugin |
@acme/capacitor-scanner |
AcmeCapacitorScanner |
Jika Package.swift menggunakan nama atau nama produk lain, aplikasi gagal me-resolve paket 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 aslinya 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:
- Pertahankan
swift-tools-version: 5.9kecuali jika Anda butuh fitur yang lebih baru. Paket 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 persis yang cocok dengan versi terpasangnya@capacitor/ios, dan rentang memungkinkan SPM menyelesaikan keduanya.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 mengharuskan 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 metode 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 kelas 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 pemetaan:
identifieradalah argumen pertama dariCAP_PLUGIN(nama kelas).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 terkompilasi dengan baik dan hanya gagal saat dipanggil dari JavaScript. Grep untuk @objc func dengan CAPPluginCall parameter dan hitung terhadap array. Lalu hapus Plugin.h dan Plugin.m.
CAPBridgedPlugin juga bekerja dengan CocoaPods, jadi file Swift yang sama bisa dipakai untuk kedua package manager.
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"
}
}
Panduan -scheme adalah nama package Anda. Lupa Package.swift atau ios/Sources di files adalah alasan paling umum mengapa plugin berfungsi dari git checkout tetapi rusak setelah npm publish. Jalankan bun pm pack --dry-run dan periksa daftar filenya.
Tambahkan output build SPM ke .gitignore:
.build/
Package.resolved
/Packages
.swiftpm/
Langkah 5: tangani yang tak bisa dilakukan converter
Dependensi pihak ketiga
Baris podspec seperti s.dependency 'Alamofire', '~> 5.9' menjadi dependensi package 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 cantumkan 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 privacy manifest
Gambar, JSON, storyboard, dan PrivacyInfo.xcprivacy harus dideklarasikan:
.target(
name: "MyPlugin",
dependencies: [/* ... */],
path: "ios/Sources/MyPlugin",
resources: [
.process("Resources"),
.copy("PrivacyInfo.xcprivacy")
])
CocoaPods membutuhkan file yang sama untuk dideklarasikan di podspec. Letakkan file itu 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 meletakkannya di MyPluginResources.bundle, di samping kelas plugin. Ganti dengan SWIFT_PACKAGE flag, yang secara otomatis didefinisikan 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 plugin menggunakan Objective-C (bukan hanya bridge), letakkan di target terpisah dengan public headers di include folder, 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.
Jalur otomatis: cap2spm
dari tim Ionic capacitor-plugin-converter membangun cap2spm binary yang membaca Plugin.m dan Plugin.h, menambahkan CAPBridgedPlugin conformance ke Swift class Anda, menghasilkan Package.swift, memindahkan file ke Sources dan Tests, memperbarui podspec dan package.json, serta menghapus file project 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
Didesain untuk plugin yang hanya menggunakan Swift di luar file bridge. Jalankan di tree git yang bersih, lalu lakukan Langkah 5 secara manual.
Opsi lain:
- Scaffold plugin baru dengan
bun create @capacitor/plugindan salin implementasi Anda ke dalamnya. Template sudah mendukung SPM dan CocoaPods. Ini sering kali lebih cepat untuk plugin kecil. - Gunakan agen. Panduan
capacitor-plugin-spm-supportskill di Skill Capgo yang open menjelaskan Package.swift, pembersihan bridge, resources, danpackage.json. Instal denganbunx skills add Cap-go/capgo-skills.
Tes di aplikasi SPM asli
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.
Pemecahan Masalah
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 registerPlugin, atau class tersebut 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 nama yang unik. Tim aplikasi 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 berbagi folder. Pisahkan menjadi dua target.
Xcode menampilkan error paket yang sudah usang setelah perbaikan. Gunakan File > Packages > Reset Package Caches, lalu build lagi.
Jika Anda juga membutuhkan fitur opsional yang di-toggle per aplikasi, baca cara menggunakan SPM package traits di Capacitor. For apps that must stay on CocoaPods for now, see cara menggunakan CocoaPods dengan Capacitor 8, dan untuk gambaran besarnya, SPM vs CocoaPods untuk Capacitor.