Lebih lanjut ke konten utama

Migrasikan Plugin Capacitor ke Pengelola Paket Swift

Tambahkan dukungan Pengelola Paket Swift ke plugin Capacitor: Aturan Penamaan Package.swift, CAPBridgedPlugin, Tata Letak Sumber, sumber daya, Obj-C code dan cap2spm.

Kredit Artikel

Martin Donadieu

Pengarang

Valeria

Reviewer

Jordan

Editor

Migrate a Capacitor Plugin to Swift Package Manager

To migrate a Capacitor plugin to Swift Package Manager, add a Package.swift Pindahkan file iOS code ke direktori root plugin yang nama paket dan produknya sesuai dengan apa yang diharapkan oleh Capacitor CLI. ios/Sources/<Target>, replace the Objective-C CAP_PLUGIN file-file penghubung CAPBridgedPlugin menambahkan kinerja kompatibilitas dalam Swift, dan menambahkan Package.swift kepadatan ke npm files Daftar. Tahan podspec agar aplikasi CocoaPods tetap berfungsi.

Mengingat Capacitor 8, cap add ios membuat proyek SPM secara default. Sebuah plugin tanpa Package.swift terlewatkan dalam aplikasi-aplikan tersebut dengan peringatan “Beberapa Capacitor yang diinstal tidak kompatibel dengan SPM”, dan pengguna kemudian melihat “plugin tidak diimplementasikan” pada saat runtime. Panduan ini membahas migrasi manual, alat konverter, dan bagian-bagian yang paling panduan lain lewatkan: aturan penamaan, sumber daya, code Objective-C dan dependensi pihak ketiga.

Tim aplikasi yang pindah ke proyek mereka sendiri sebaiknya membaca Bagaimana Migrasi Aplikasi Capacitor Anda ke SPM sebaliknya.

Bagaimana Capacitor mengonsumsi paket Anda

Pada bunx cap sync ios, CLI mengubah ios/App/CapApp-SPM/Package.swift dalam aplikasi. Untuk setiap Capacitor plugin yang memiliki Package.swift, ia menambahkan dua baris:

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

Dua konsekuensi:

  1. Package Anda dikonsumsi oleh jalur lokal dari node_modules, bukan dari URL Git. Package Anda Package.swift harus berada di root package npm, dan semua yang merujuk ke harus diterbitkan ke npm.
  2. Nama package dihasilkan dari nama npm, bukan dari podspec Anda. CLI menghilangkan @, mengubah / dan - context _Migrasi Plugin Capacitor ke Swift Package Manager
Paket npm Nama Paket dan Produk yang Diperlukan
@capacitor/haptics CapacitorHaptics
@capgo/capacitor-updater CapgoCapacitorUpdater
capacitor-my-plugin CapacitorMyPlugin
@acme/capacitor-scanner AcmeCapacitorScanner

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

Langkah 1: tulis Package.swift

Berikut 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.
  • Pilih from: "8.0.0" untuk capacitor-swift-pm. Aplikasi memasang versi yang tepat yang sesuai dengan versi yang terinstal @capacitor/iosdan rentang memungkinkan SPM menyelesaikan baik itu. branch: atau exact: Karena menggunakannya akan menyebabkan konflik penyelesaian.
  • Nama target dapat apa saja, tetapi menjadi nama modul Swift. Hindari nama umum seperti Plugin yang bertabrakan dengan plugin lain.

Langkah 2: Pindahkan sumber

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

Kemudian hapus apa yang tidak perlu SPM. ios/Plugin.xcodeproj, ios/Plugin.xcworkspace, ios/Podfile, ios/Plugin/Info.plist and 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'

Update podspec sehingga CocoaPods mengompilasi file yang sama:

Langkah 3: Ganti bridge Objective-C dengan CAPBridgedPlugin 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 (nama kelas).
  • jsName adalah argumen kedua, nama yang digunakan di registerPlugin('MyPlugin') dalam TypeScript Anda.
  • Each CAP_PLUGIN_METHOD menjadi satu CAPPluginMethod, dengan tipe return yang sama (CAPPluginReturnPromise, CAPPluginReturnCallback atau CAPPluginReturnNone).

A method yang hilang dari pluginMethods Kompilasi berhasil dan gagal hanya ketika JavaScript memanggilnya. Cari dengan @objc func dengan sebuah CAPPluginCall bersarang dan gagal hanya ketika JavaScript memanggilnya. Cari dengan Plugin.h and Plugin.m.

CAPBridgedPlugin berfungsi dengan CocoaPods juga, jadi file Swift yang sama dapat digunakan oleh kedua pengelola paket.

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

The -scheme Nama paket Anda adalah __CAPGO_KEEP_0__. Jangan lupa Package.swift or ios/Sources atau files Alasan utama plugin bekerja dari cekout Git dan bermasalah setelah npm publishatau bun pm pack --dry-run nilai ini adalah nama paket Anda. Lupa

Tambahkan hasil kompilasi SPM ke .gitignore:

.build/
Package.resolved
/Packages
.swiftpm/

nilai ini adalah nama paket Anda. Lupa

Ketergantungan Pihak Ketiga

A garis 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 SDK hanya mengirimkan .xcframeworkshipkannya di dalam paket npm dan gunakan target biner:

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

Jika vendor tidak menawarkan paket SPM dan tidak ada xcframework, Anda tidak bisa menambahkan dukungan SPM. Tahan plugin CocoaPods saja dan tuliskan di README.

Tahan rentang versi di Package.swift dan podspec sejajar. Dua plugin di aplikasi yang sama yang memerlukan versi SDK yang tidak kompatibel akan gagal resolusi di manapun manager paket.

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 daya bernama:

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

Dalam code, sumber daya SPM hidup di Bundle.modulesedangkan CocoaPods memasukkannya di dalamnya MyPluginResources.bundleberikutnya di samping kelas plugin. SWIFT_PACKAGE flag, yang SwiftPM mendefinisikan secara otomatis:

#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

Objek-C nyata 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 membuat 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

__CAPGO_KEEP_0__-plugin-converter capacitor-plugin-converter membangun cap2spm binary yang membaca Plugin.m dan Plugin.h, menambahkan CAPBridgedPlugin konformitas 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-nya kecuali file bridge. Jalankan di atas cabang Git yang bersih dan kemudian lakukan Langkah 5 secara manual.

Opsi lainnya:

  • Membuat plugin segar dengan bun create @capacitor/plugin dan salin implementasi Anda di sana. Template sudah mendukung SPM dan CocoaPods. Ini seringkali lebih cepat untuk plugin kecil.
  • Menggunakan agen. The capacitor-plugin-spm-support keahlian dalam Capgo Keahlian menggambarkan Package.swift, pembersihan bridge, sumber daya dan package.json. Pasang dengan bunx 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

Periksa bahwa ios/App/CapApp-SPM/Package.swift menampilkan daftar paket Anda, dan sinkron telah mencetak “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 nama npm Anda. Lihat tabel di atas.

"MyPlugin" plugin is not implemented on ios. Entah Package.swift belum dipublikasikan, jsName tidak sesuai, registerPluginatau kelas kurang, CAPBridgedPlugin. Periksa folder plugin di node_modules.

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

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

Xcode shows stale package errors after fixes. atau File > Paket > Reset Paket CacheKemudian bangun lagi.

Jika Anda juga memerlukan fitur opsional yang dapat ditonjolkan 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 Capacitor Aplikasi

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.

Dukungan Manusia dari Martin

Mulai Sekarang

Terbaru dari Blog Kami

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