Capacitor 플러그인을 Swift Package Manager로 마이그레이션하려면 플러그인 루트에 Package.swift 를 추가하고, package 및 product 이름을 Capacitor CLI이 기대하는 값과 일치시키며, iOS code를 ios/Sources/<Target>로 이동하고, Objective-C CAP_PLUGIN 브리지 파일을 Swift의 CAPBridgedPlugin conformance로 대체하고, Package.swift 를 npm files 목록에 추가하세요. CocoaPods 앱이 계속 작동하도록 podspec은 유지하세요.
Capacitor 8부터 cap add ios 는 기본적으로 SPM 프로젝트를 생성합니다. Package.swift 이 없는 플러그인은 해당 앱에서 “Some installed Capacitor plugins are not compatible with SPM” 경고와 함께 건너뛰어지고, 런타임에 “plugin is not implemented”이 표시됩니다. 이 가이드는 수동 마이그레이션, 변환 도구, 그리고 대부분의 가이드에서 다루지 않는 네이밍 규칙, 리소스, Objective-C code 및 서드파티 의존성을 다룹니다.
자체 프로젝트를 마이그레이션하는 앱 팀은 Capacitor 앱을 SPM으로 마이그레이션하는 방법 대신 사용하세요.
Capacitor가 패키지를 사용하는 방식
웹 bunx cap sync ios, CLI가 다시 작성합니다 ios/App/CapApp-SPM/Package.swift 앱 내에서. 각 Capacitor 플러그인에 대해 Package.swift, 두 줄을 추가합니다:
.package(name: "CapgoCapacitorUpdater", path: "../../../node_modules/@capgo/capacitor-updater")
// ...
.product(name: "CapgoCapacitorUpdater", package: "CapgoCapacitorUpdater")
두 가지 결과:
- 패키지는 git URL이 아닌 로컬 경로에서
node_modules에서 소비됩니다. 당신의Package.swift은 npm 패키지 루트에 있어야 하며, 참조하는 모든 것은 npm에 게시되어야 합니다. - 이름은 npm 이름에서 파생됩니다, podspec이 아닌 곳에서. CLI이(가) 제거합니다
@, 변환합니다/및-앱으로 보내도록_, 각 세그먼트를 camelCase로 변환하고 첫 글자를 대문자로 만듭니다:
| npm 패키지 | 필수 패키지 및 제품 이름 |
|---|---|
@capacitor/haptics |
CapacitorHaptics |
@capgo/capacitor-updater |
CapgoCapacitorUpdater |
capacitor-my-plugin |
CapacitorMyPlugin |
@acme/capacitor-scanner |
AcmeCapacitorScanner |
만약 Package.swift 다른 이름이나 제품 이름을 사용하면, 다음과 같은 오류로 패키지를 해석하지 못해 앱이 실패합니다 product 'X' required by package 'capapp-spm' target 'CapApp-SPM' not found.
1단계: Package.swift 작성
공식 플러그인이 사용하는 레이아웃입니다. 실제 매니페스트는 다음과 같습니다: @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")
]
)
참고:
- 유지하세요
swift-tools-version: 5.9더 새로운 기능이 필요하지 않다면 말이죠. 앱이 생성하는 패키지도 기본적으로 5.9를 사용하며, Capacitor 8은 아직 Swift 6를 공식적으로 지원하지 않습니다. - 사용
from: "8.0.0"위해capacitor-swift-pm. 앱은 설치된 버전과 일치하는 정확한 버전을 고정하며@capacitor/ios, 범위를 사용하면 SPM이 두 버전을 모두 해결할 수 있습니다.branch:또는exact:해석 충돌을 일으킵니다. - 타겟 이름은 무엇이든 될 수 있지만, Swift 모듈 이름이 됩니다. 다음과 같이
Plugin다른 플러그인과 충돌하는 일반적인 이름은 피하세요.
2단계: 소스 이동
SPM은 타겟당 하나의 폴더를 요구합니다. 컨버터와 공식 템플릿은 다음을 사용합니다:
my-plugin/
├── Package.swift
├── MyPlugin.podspec
├── ios/
│ ├── Sources/
│ │ └── MyPlugin/
│ │ ├── MyPlugin.swift
│ │ └── MyPluginImplementation.swift
│ └── Tests/
│ └── MyPluginTests/
│ └── MyPluginTests.swift
└── package.json
그 다음 SPM에 필요 없는 항목을 제거하세요: ios/Plugin.xcodeproj, ios/Plugin.xcworkspace, ios/Podfile, ios/Plugin/Info.plist 및 ios/PluginTests/Info.plist.
CocoaPods가 동일한 파일을 컴파일하도록 podspec을 업데이트하세요:
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'
3단계: Objective-C 브리지를 CAPBridgedPlugin으로 교체
기존 플러그인은 메서드를 다음 위치에 등록합니다: Plugin.m:
#import <Capacitor/Capacitor.h>
CAP_PLUGIN(MyPlugin, "MyPlugin",
CAP_PLUGIN_METHOD(echo, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(startScan, CAPPluginReturnCallback);
)
SwiftPM은 하나의 타겟에 Objective-C와 Swift를 혼합할 수 없으므로, 다음을 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
// ...
}
}
매핑 규칙:
identifier는 다음의 첫 번째 인자입니다:CAP_PLUGIN(클래스 이름).jsName는 두 번째 인자이며, 다음에서 사용하는 이름입니다:registerPlugin('MyPlugin')TypeScript 코드에서.- 각
CAP_PLUGIN_METHOD는 하나의CAPPluginMethod로 변환되며, 동일한 반환 타입을 유지합니다 (CAPPluginReturnPromise,CAPPluginReturnCallback또는CAPPluginReturnNone).
누락된 메서드: pluginMethods 컴파일은 잘 되고 JavaScript가 호출할 때만 실패합니다. grep으로 찾아보세요: @objc func 와 함께 CAPPluginCall 매개변수를 배열과 대조해 세세요. 그런 다음 삭제하세요. Plugin.h 및 Plugin.m.
CAPBridgedPlugin CocoaPods와도 호환되므로 동일한 Swift 파일을 두 패키지 매니저 모두에서 사용할 수 있습니다.
4단계: package.json 및 .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"
}
}
이 -scheme 값은 패키지 이름입니다. 이를 잊으면 Package.swift 또는 ios/Sources 에서 files 플러그인이 git checkout에서는 작동하지만 이후에 깨지는 가장 흔한 이유입니다 npm publish. 실행하세요. bun pm pack --dry-run 그런 다음 파일 목록을 확인하세요.
SPM 빌드 결과물을 ...에 추가하세요 .gitignore:
.build/
Package.resolved
/Packages
.swiftpm/
5단계: 컨버터가 처리하지 못하는 부분 처리하기
서드파티 의존성
다음과 같은 podspec 라인은 s.dependency 'Alamofire', '~> 5.9' 패키지 의존성과 타겟의 product가 됩니다:
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")
]
만약 SDK가 오직 ...만 제공한다면 .xcframework, npm 패키지 안에 포함해 배포하고 binary target을 사용하세요:
.binaryTarget(name: "VendorSDK", path: "ios/Frameworks/VendorSDK.xcframework")
벤더가 SPM 패키지도 xcframework도 제공하지 않는다면 아직 SPM을 추가할 수 없습니다. 플러그인은 CocoaPods 전용으로 유지하고 README에 명시하세요.
버전 범위를 Package.swift 과 podspec에 맞춰 일치시키세요. 같은 앱에서 서로 다른 SDK 버전이 필요한 두 플러그인이 있으면 어떤 패키지 매니저를 사용해도 의존성 해결에 실패합니다.
리소스 및 프라이버시 매니페스트
이미지, JSON, 스토리보드 및 PrivacyInfo.xcprivacy 다음과 같이 선언해야 합니다:
.target(
name: "MyPlugin",
dependencies: [/* ... */],
path: "ios/Sources/MyPlugin",
resources: [
.process("Resources"),
.copy("PrivacyInfo.xcprivacy")
])
CocoaPods에서는 podspec에 동일한 파일을 선언해야 합니다. 이 파일들을 이름이 있는 리소스 번들에 넣으세요:
s.resource_bundles = {
'MyPluginResources' => [
'ios/Sources/MyPlugin/Resources/**/*',
'ios/Sources/MyPlugin/PrivacyInfo.xcprivacy'
]
}
code에서 SPM 리소스는 Bundle.module에 위치하는 반면, CocoaPods는 해당 MyPluginResources.bundle에 플러그인 클래스와 함께 둡니다. 전환할 때 사용하는 SWIFT_PACKAGE 플래그는 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
플러그인 일부가 Objective-C라면(브리지만이 아니라), 별도 타겟에 넣고 public 헤더를 include 폴더에 배치한 뒤, Swift 타겟이 이를 의존하도록 설정하세요:
.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은 다음을 수행합니다 import MyPluginObjC.
자동화 방식: cap2spm
Ionic 팀의 capacitor-plugin-converter는 바이너리를 빌드합니다. 이 cap2spm 바이너리는 Plugin.m 및 Plugin.h, 추가합니다 CAPBridgedPlugin 준수를 추가하고, 다음으로 Package.swift를 생성하고, 파일을 Sources 및 Tests로 이동하고, podspec과 package.json를 업데이트하고, 기존 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
브리지 파일을 제외하고 Swift 전용인 플러그인을 위해 설계되었습니다. 깨끗한 git 트리에서 실행한 후 5단계를 수동으로 수행하세요.
다른 옵션:
- 새 플러그인 스캐폴딩 포함
bun create @capacitor/plugin이후 구현 코드를 복사해 넣으세요. 템플릿은 이미 SPM과 CocoaPods를 지원합니다. 작은 플러그인의 경우 이 방식이 더 빠릅니다. - 에이전트 사용. 이
capacitor-plugin-spm-support숙련 Capgo 기술 스택 Package.swift, 브리지 정리, 리소스 및 ...를 살펴봅니다package.json로 설치하세요.bunx skills add Cap-go/capgo-skills.
실제 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
다음 사항을 확인하세요: ios/App/CapApp-SPM/Package.swift 패키지를 나열하고, 해당 sync가 “All Capacitor plugins have a Package.swift file”를 출력했습니다. 그런 다음 기기에서 빌드하고 각 메서드를 호출하세요. …로 만든 앱에서 반복하세요. bunx cap add ios --packagemanager CocoaPods podspec이 여전히 제대로 동작하는지 확인하려면.
문제 해결
product 'X' required by package 'capapp-spm' ... not found. Package 또는 제품 이름이 npm 이름에서 파생된 이름과 일치하지 않습니다. 위 표를 참고하세요.
"MyPlugin" plugin is not implemented on ios. 다음 중 하나입니다: Package.swift 게시되지 않았거나, jsName 일치하지 않거나 registerPlugin또는 클래스에 ...가 없습니다. CAPBridgedPlugin. 다음 위치의 플러그인 폴더를 확인하세요: node_modules.
Multiple targets named 'Plugin' 또는 중복 모듈 오류를 확인하세요. 두 플러그인이 동일한 target 이름을 사용합니다. 플러그인 이름을 고유한 이름으로 변경하세요. 앱 팀은 다음 방법으로도 우회할 수 있습니다: experimental.ios.spm.packageOptions (moduleAliases 또는 symlink) Capacitor 설정에 있으며, CLI 8.4부터 사용할 수 있습니다.
target 'X' contains mixed language source files. Objective-C와 Swift 파일이 같은 폴더에 있습니다. 두 개의 타깃으로 분리하세요.
수정 후에도 Xcode에 이전 패키지 오류가 표시됩니다. 사용 File > Packages > Reset Package Caches, 그리고 다시 빌드하세요.
앱별로 선택적 기능을 전환해야 한다면 Capacitor에서 SPM 패키지 트레잇 사용법을 읽어보세요. 현재 CocoaPods를 유지해야 하는 앱의 경우, 다음을 참고하세요 Capacitor 8에서 CocoaPods를 사용하는 방법, 그리고 더 큰 그림을 보려면, Capacitor를 위한 SPM vs CocoaPods 비교.