Lebihkan ke konten utama

Migrate a Capacitor Plugin to Swift Package Manager

Menambahkan dukungan Swift Package Manager ke sebuah Capacitor plugin: Aturan nama Package.swift, CAPBridgedPlugin, Tata letak Sources, sumber daya, Obj-C code dan cap2spm.

Kredit Artikel

Martin Donadieu

Pengarang

Valeria

Pengulas

Jordan

Pengedit

Migrasi sebuah Capacitor Plugin ke Swift Package Manager

Untuk migrasi sebuah Capacitor plugin ke Swift Package Manager, tambahkan sebuah Package.swift di root plugin yang nama paket dan produknya sesuai dengan apa yang diharapkan oleh Capacitor CLI , pindahkan iOS code ke ios/Sources/<Target>Gantikan Objective-C CAP_PLUGIN Migrasikan file penghubung dengan CAPBridgedPlugin Menggunakan konformansi di Swift, dan tambahkan Package.swift menuju ke npm files Tetapkan podspec agar aplikasi CocoaPods tetap berjalan.

Sejak Capacitor 8, cap add ios Tim aplikasi yang bergerak ke proyek sendiri sebaiknya membaca Package.swift gets skipped in those apps with the warning “Some installed Capacitor plugins are not compatible with SPM”, and users then see “plugin is not implemented” at runtime. This guide covers the manual migration, the converter tool, and the parts most guides skip: naming rules, resources, Objective-C code and third-party dependencies.

App teams moving their own project should read Bagaimana Capacitor mengonsumsi paket Anda Pada

How Capacitor consumes your package

On bunx cap sync iosmigrasi ke CLI ios/App/CapApp-SPM/Package.swift di aplikasi. Untuk setiap Capacitor plugin yang memiliki Package.swiftmenghasilkan dua baris:

.package(name: "CapgoCapacitorUpdater", path: "../../../node_modules/@capgo/capacitor-updater")
// ...
.product(name: "CapgoCapacitorUpdater", package: "CapgoCapacitorUpdater")

Dua konsekuensi:

  1. Paket Anda dikonsumsi melalui jalur lokal dari node_modulesBukan dari URL Git. Package.swift harus berada di root package npm, dan semua yang diperlukan harus dipublikasikan ke npm.
  2. bukan dari podspec Anda. npm menghilangkanTidak dari podspec Anda. The CLI menghapus @, turns / menjadi - dan _Kemudian, buatlah setiap segment menjadi camel-case dan huruf pertamanya besar:
npm paket Paket dan nama produk yang diperlukan
@capacitor/haptics CapacitorHaptics
@capgo/capacitor-updater CapgoCapacitorUpdater
capacitor-my-plugin CapacitorMyPlugin
@acme/capacitor-scanner AcmeCapacitorScanner

If your Package.swift menggunakan nama lain atau nama produk, aplikasi gagal menyelesaikan paket dengan kesalahan seperti product 'X' required by package 'capapp-spm' target 'CapApp-SPM' not found.

Langkah 1: Tulis Package.swift

Paket ini adalah tata letak yang digunakan oleh plugin resmi. Berikut adalah manifest yang sebenarnya 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:

  • Tetapkan swift-tools-version: 5.9 kecuali Anda memerlukan fitur yang lebih baru. Paket yang dihasilkan aplikasi juga menggunakan 5.9 secara default, dan Capacitor 8 belum secara resmi mendukung Swift 6.
  • Pakai from: "8.0.0" untuk capacitor-swift-pmMigrasi Plugin Capacitor ke Swift Package Manager @capacitor/iosMigrasi Plugin Capacitor ke Swift Package Manager branch: atau exact: Migrasi Plugin Capacitor ke Swift Package Manager
  • Nama target dapat apa saja, tetapi menjadi nama modul Swift. Hindari nama umum seperti "Plugin" Plugin Migrasi Plugin Capacitor ke Swift Package Manager

Migrasi Plugin Capacitor ke Swift Package Manager

SPM mengharapkan satu folder per sasaran. Konverter dan template resmi menggunakan:

my-plugin/
├── Package.swift
├── MyPlugin.podspec
├── ios/
│   ├── Sources/
│   │   └── MyPlugin/
│   │       ├── MyPlugin.swift
│   │       └── MyPluginImplementation.swift
│   └── Tests/
│       └── MyPluginTests/
│           └── MyPluginTests.swift
└── package.json

Migrasi Plugin Capacitor ke Swift Package Manager ios/Plugin.xcodeproj, ios/Plugin.xcworkspace, ios/Podfile, ios/Plugin/Info.plist and ios/PluginTests/Info.plist.

Migrasi Plugin Capacitor ke Swift Package Manager

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'

Migrasi Plugin Capacitor ke Swift Package Manager

Migrasi Plugin Capacitor ke Swift Package Manager Plugin.m:

#import <Capacitor/Capacitor.h>

CAP_PLUGIN(MyPlugin, "MyPlugin",
    CAP_PLUGIN_METHOD(echo, CAPPluginReturnPromise);
    CAP_PLUGIN_METHOD(startScan, CAPPluginReturnCallback);
)

SwiftPM tidak dapat 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 Petaan:

  • identifier is the first argument of CAP_PLUGIN adalah argumen pertama dari
  • jsName argumen kedua, nama yang digunakan dalam registerPlugin('MyPlugin') dalam bahasa TypeScript Anda.
  • Each CAP_PLUGIN_METHOD Masing-masing CAPPluginMethodMigrasikan Plugin Capacitor ke Swift Package ManagerCAPPluginReturnPromise, CAPPluginReturnCallback or CAPPluginReturnNone).

Metode yang hilang dari pluginMethods berjalan lancar dan gagal hanya ketika JavaScript memanggilnya. Cari dengan grep untuk @objc func dengan sebuah CAPPluginCall parameter dan hitung jumlahnya terhadap array. Kemudian hapus Plugin.h and Plugin.m.

CAPBridgedPlugin Bekerja dengan CocoaPods juga, jadi file Swift yang sama dapat digunakan oleh manajer paket mana pun.

Nilainya adalah nama paket Anda. Lupa

{
  "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"
  }
}

atau -scheme Nama paket Anda adalah __CAPGO_KEEP_0__. Jangan lupa Package.swift Run ios/Sources dengan files Plugin bekerja dari Git checkout dan bermasalah setelah npm publishdan bun pm pack --dry-run dan periksa daftar file.

Tambahkan hasil pembangunan SPM ke .gitignore:

.build/
Package.resolved
/Packages
.swiftpm/

Langkah 5: Tangani apa yang tidak dapat dikonversi.

Ketergantungan pihak ketiga

Baris podspec seperti s.dependency 'Alamofire', '~> 5.9' menjadi ketergantungan paket dan produk 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 hanya SDK yang mengirimkan .xcframeworkshipkannya di dalam paket npm dan gunakan target biner:

.binaryTarget(name: "VendorSDK", path: "ios/Frameworks/VendorSDK.xcframework")

If a vendor offers no SPM package and no xcframework, you can’t add SPM support yet. Keep the plugin CocoaPods-only and say so in the README.

Tetapkan rentang versi Package.swift Dua plugin di aplikasi yang sama yang memerlukan versi yang tidak kompatibel dari satu SDK akan gagal dalam proses resolusi di pengelola paket mana pun.

Sumber Daya 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 memerlukan file yang sama dideklarasikan di podspec. Masukkan mereka ke dalam bundle sumber bernama:

s.resource_bundles = {
  'MyPluginResources' => [
    'ios/Sources/MyPlugin/Resources/**/*',
    'ios/Sources/MyPlugin/PrivacyInfo.xcprivacy'
  ]
}

Ini code, SPM sumber daya hidup di Bundle.module, sedangkan CocoaPods memasukkannya di MyPluginResources.bundleBerikutnya, di samping kelas plugin. SWIFT_PACKAGE Real Objective-C __CAPGO_KEEP_0__

#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

Objekif Objective-C code

Jika bagian dari plugin tersebut adalah Objective-C (bukan hanya bridge), masukkan ke dalam target sendiri dengan header publik di dalam include folder, dan buat target Swift bergantung pada folder tersebut:

.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")

The Swift code then does import MyPluginObjC.

Rute otomatis: cap2spm

The Ionic team’s capacitor-konverter plugin membangun cap2spm biner yang membaca Plugin.m dan Plugin.h, menambahkan CAPBridgedPlugin kesesuaian ke kelas Swift Anda, menghasilkan Package.swift, memindahkan file ke Sources dan Tests, memperbarui podspec dan package.json, dan 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

Itu dirancang untuk plugin yang hanya Swift kecuali file bridge. Jalankan di atas cabang git yang bersih dan kemudian lakukan Langkah 5 secara manual.

Pilihan lainnya:

  • Buat plugin segar dengan with bun create @capacitor/plugin dan salin implementasi Anda di dalamnya. Template sudah mendukung SPM dan CocoaPods. Ini seringkali lebih cepat untuk plugin kecil.
  • Keterampilan di The capacitor-plugin-spm-support kemampuan Capgo Skills berjalan melalui Package.swift, pembersihan bridge, sumber daya dan package.jsonPeriksa bahwa bunx skills add Cap-go/capgo-skills.

Sekarang Anda siap untuk memigrasikan plugin Anda ke SPM.

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

Periksa ios/App/CapApp-SPM/Package.swift Daftar paket Anda, dan sinkronisasi yang dicetak “Semua Capacitor plugin memiliki file Package.swift”. Kemudian bangun di perangkat dan panggil setiap metode. Ulangi di aplikasi 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 paket atau produk tidak sesuai dengan nama yang dihasilkan dari npm Anda. Lihat tabel di atas.

"MyPlugin" plugin is not implemented on ios. Atau Package.swift tidak diterbitkan, jsName tidak sesuai, registerPluginatau kelas kurang memiliki CAPBridgedPluginPeriksa folder plugin di node_modules.

Multiple targets named 'Plugin' masalah modul ganda. Dua plugin menggunakan nama target yang sama. Ubah nama Anda menjadi sesuatu yang unik. Tim aplikasi juga dapat mengatasi masalah ini dengan experimental.ios.spm.packageOptions (moduleAliases or symlink) di konfigurasi Capacitor yang tersedia sejak CLI 8.4.

target 'X' contains mixed language source files. File Objective-C dan Swift berbagi folder. Pecahlah mereka menjadi dua target.

Xcode menampilkan kesalahan paket kuno setelah perbaikan. Pilih File > Paket > Reset Cache Paket, kemudian bangun lagi.

Jika Anda juga memerlukan fitur opsional yang dapat diaktifkan per aplikasi, baca bagaimana menggunakan fitur paket SPM di CapacitorUntuk aplikasi yang harus tetap menggunakan CocoaPods untuk saat ini, lihat bagaimana menggunakan CocoaPods dengan Capacitor 8, dan untuk gambaran yang lebih besar, SPM vs CocoaPods untuk Capacitor.

Update langsung untuk aplikasi Capacitor

Ketika bug layer web masih aktif, kirimkan perbaikan melalui Capgo daripada menunggu hari-hari untuk persetujuan toko aplikasi. Pengguna mendapatkan update di latar belakang sementara perubahan native tetap dalam jalur review normal.

Terbaru dari Blog kami

Capgo memberikan Anda wawasan terbaik yang Anda butuhkan untuk membuat aplikasi mobile profesional yang sebenarnya.