要在 Capacitor 8 中使用 SPM 包特性,请设置 experimental.ios.spm.swiftToolsVersion 改为 "6.1" 并在下方按插件列出 traits experimental.ios.spm.packageTraits 在你的 Capacitor 配置中,然后运行 bunx cap sync ios. CLI 会将这些 traits 写入生成的 CapApp-SPM/Package.swift,SwiftPM 会使用相应的可选依赖和 code 路径来构建插件。这需要 Capacitor CLI 8.3.0 或更高版本,且仅适用于使用 Swift Package Manager 的 iOS 项目。
Traits 弥合了 SPM 与 CocoaPods 在插件上的最后一个重大功能缺口:过去作为 podspec subspecs 的可选原生依赖。本指南涵盖应用侧、插件作者侧,以及当前实验性支持的限制。
什么是 package traits
Package traits 源自 Swift Evolution 提案 SE-0450,已在 Swift 6.1 (Xcode 16.3 及更高版本) 中实现。一个 package 会在其中声明具名 traits Package.swift. 使用方在依赖该 package 时可以选择启用哪些 traits。一个 trait 可以:
- 将依赖设为可选 (
condition: .when(traits: [...])在目标依赖上), - 开启 code 路径,因为每个启用的 trait 都可作为编译条件(
#if TraitName), - 切换 defines 或 linker flags 等构建设置,
- 启用其他 trait。
包也可以将 trait 标记为默认。除非使用方显式传入列表,否则它们默认开启。
如果你熟悉 CocoaPods subspecs 或 Rust 中的 Cargo features,思路是一样的。
为什么在 Capacitor 中很重要
Capacitor 会为你生成应用的 Package.swift 因此你无法手动编辑依赖行来传递 trait。在 CLI 8.3 之前,带有可选原生 SDK 的插件作者在 SPM 上只有两个糟糕的选择:要么始终链接 SDK,导致体积增加,有时还会带来隐私或出口合规工作;要么发布第二个插件。常见场景:
- 加密版 vs 明文版 SQLite (SQLCipher),
- 包含或不包含广告标识符收集功能的 analytics SDKs,
- 用于某个支付或登录提供商的可选厂商 SDK,
- 仅限 debug 使用的工具。
在应用中启用 traits
1. 检查版本
bunx cap --version # 8.3.0 or later
xcodebuild -version # Xcode 26 (required by Capacitor 8 anyway)
项目必须使用 SPM: ios/App/CapApp-SPM 存在。如果您使用的是 CocoaPods,请参阅 如何将你的 Capacitor 应用迁移到 SPM.
2. 查找插件的 trait 名称
打开插件的 Package.swift 中的 node_modules 并查找 traits: 数组,或查看其 README。Trait 名称区分大小写。
3. 配置 Capacitor
import type { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = {
appId: 'com.example.app',
appName: 'Example',
webDir: 'dist',
experimental: {
ios: {
spm: {
swiftToolsVersion: '6.1',
packageTraits: {
'@acme/capacitor-db': ['.defaults', 'SQLCipher'],
},
},
},
},
};
export default config;
键是插件的 npm 包名。值是要启用的 trait 列表。
4. 同步并检查输出
bunx cap sync ios
生成的 ios/App/CapApp-SPM/Package.swift 现在以 // swift-tools-version: 6.1 开头,且插件的依赖行包含 traits:
.package(name: "AcmeCapacitorDb", path: "../../../node_modules/@acme/capacitor-db", traits: [.defaults, "SQLCipher"])
CLI 的编写方式 .defaults (或 defaults, .default, default (无论如何)作为 SwiftPM .defaults 值,其他所有名称则作为带引号的字符串。
在 Xcode 中构建。如果 Xcode 仍显示旧的依赖图,请使用 File > Packages > Reset Package Caches.
CLI 强制执行的规则
Capacitor CLI 在同步期间验证配置:
- 如果任何插件具有非空的 trait 列表,且
swiftToolsVersion缺失,同步将停止并提示:“Package traits 需要显式指定 6.1 或更高版本的 Swift tools version。” - 如果
swiftToolsVersion低于 6.1,同步将停止并提示版本过低。 swiftToolsVersion必须类似于6.1或6.1.0.- 空数组的插件将被视为未列出。
保留或移除默认 traits
列出 traits 会替换默认值。对比:
| 配置值 | 结果 |
|---|---|
| 未列出 | 插件默认 traits |
['SQLCipher'] |
仅 SQLCipher,默认关闭 |
['.defaults', 'SQLCipher'] |
默认值 + SQLCipher |
移除默认项前请先阅读插件文档。插件可能把标准后端放在某个默认 trait 之后。
为你的插件添加 traits
如果你维护的插件包含可选的 SDK,这里有一个完整的 Package.swift。包名必须与 Capacitor CLI 根据你的 npm 名称派生出的名称一致 (@acme/capacitor-db 会变为 AcmeCapacitorDb)。参见 将 Capacitor 插件迁移到 SPM 该规则。
// swift-tools-version: 6.1
import PackageDescription
let package = Package(
name: "AcmeCapacitorDb",
platforms: [.iOS(.v15)],
products: [
.library(name: "AcmeCapacitorDb", targets: ["DbPlugin"])
],
traits: [
.trait(name: "SQLCipher", description: "Link SQLCipher and enable encrypted databases."),
.default(enabledTraits: [])
],
dependencies: [
.package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "8.0.0"),
.package(url: "https://github.com/sqlcipher/SQLCipher.swift.git", from: "4.10.0")
],
targets: [
.target(
name: "DbPlugin",
dependencies: [
.product(name: "Capacitor", package: "capacitor-swift-pm"),
.product(name: "Cordova", package: "capacitor-swift-pm"),
.product(
name: "SQLCipher",
package: "SQLCipher.swift",
condition: .when(traits: ["SQLCipher"])
)
],
path: "ios/Sources/DbPlugin"
)
],
swiftLanguageModes: [.v5]
)
复制此内容前,请先检查 SDK 的官方仓库,确认其当前的包 URL、产品名称和版本。
容易忽略的要点:
- Tools 6.x 版本将默认语言模式更改为 Swift 6 针对您的目标。Capacitor 目前尚未正式支持 Swift 6,因此请保持
swiftLanguageModes: [.v5]除非你的 code 已为严格并发做好准备。 - 将插件的工具版本提升至 6.1 意味着使用者需要 Xcode 16.3+。 Capacitor 8 应用已要求 Xcode 26,因此这不是新的限制。
在 code 中使用该 trait
已启用的 trait 是包目标中的编译条件:
#if SQLCipher
import SQLCipher
#endif
@objc func open(_ call: CAPPluginCall) {
let key = call.getString("encryptionKey")
#if SQLCipher
// open with key
#else
if key != nil {
call.unavailable("Encryption requires the SQLCipher trait. Enable it in experimental.ios.spm.packageTraits.")
return
}
#endif
// open without encryption
}
当某个功能关闭时,返回一个明确指明 trait 名称的错误。否则 App 开发者只会看到静默失败,并把问题归咎于插件。
你也可以使用以下内容定义自己的 flag swiftSettings: [.define("ACME_SQLCIPHER", .when(traits: ["SQLCipher"]))] 如果你更喜欢带前缀的名称。
将 trait 设计为可叠加的
SwiftPM 会统一整个依赖图中的 trait。如果两个包以不同的 trait 依赖你的插件,则会启用它们的并集。因此:
- 启用 trait 应该增加能力,而不是移除 API 或更改默认值。
- 避免使用互斥的 trait。如果无法避免,请让构建直接报错:
#if TraitA && TraitB
#error("TraitA and TraitB cannot be enabled together")
#endif
测试每种组合
swift build 其自身构建仅面向 Mac,而依赖 Capacitor 的插件仅面向 iOS 构建,因此请通过为 iOS Simulator 构建的 Capacitor 测试 App 来测试这些 trait。对于每种组合(默认 trait 不填条目, ['SQLCipher'], ['.defaults', 'SQLCipher'] 以此类推),修改 packageTraits 测试 App 的配置,然后 sync 并 build:
bunx cap sync ios
xcodebuild build \
-project ios/App/App.xcodeproj \
-scheme App \
-destination 'generic/platform=iOS Simulator' \
CODE_SIGNING_ALLOWED=NO
将每种组合作为独立的 job 添加到 CI 中。
继续支持 CocoaPods 用户
Traits 仅存在于 SPM 中。如果你的插件同时提供 podspec,请通过 subspecs 提供相同的选择:
s.default_subspec = 'Core'
s.subspec 'Core' do |core|
core.source_files = 'ios/Sources/DbPlugin/**/*.swift'
core.dependency 'Capacitor'
end
s.subspec 'SQLCipher' do |sc|
sc.dependency 'AcmeCapacitorDb/Core'
sc.dependency 'SQLCipher', '~> 4.10'
sc.pod_target_xcconfig = { 'SWIFT_ACTIVE_COMPILATION_CONDITIONS' => '$(inherited) SQLCipher' }
end
在 subspec 中设置相同的编译条件名称,即可共享一个 #if SQLCipher code 路径。使用 CocoaPods 的应用随后会在其 Podfile 中选择 subspec。Capacitor CLI 会写入一行 pod 内部每个插件一行 capacitor_pods,因此请在 README 中记录额外的 Podfile 行。 如何在 Capacitor 8 中使用 CocoaPods 展示了自定义 pod 行应放在哪里。
局限性
- 实验性功能。 选项位于
experimental并可能迁移至ios.spm.*在未来的大版本中。配置迟早会改名。 - 尚未正式支持 Swift 6。 CLI 自己的文档警告称,设置
swiftToolsVersion升级到 6.0 或更高版本可能会引发问题。请完整测试 app,尤其是附带二进制 xcframeworks 的插件。 - 仅限 iOS。 Android 在 Capacitor 中没有对应机制。插件通过 Gradle 变量处理可选的 Android SDK,
variables.gradle或使用独立的 artifacts。 - 插件必须主动启用。 大多数插件尚未定义 traits。打算依赖某个 trait 前请先确认。
故障排除
同步失败,报错:“Package traits require an explicit Swift tools version of 6.1 or higher”。 添加 swiftToolsVersion: '6.1' 旁边 packageTraits.
SwiftPM 报告未知 trait。 Trait 名称区分大小写,且必须存在于插件的 Package.swift 针对已安装的版本。
启用 trait 后功能仍缺失。 在 Xcode 中重置包缓存,清理 build 文件夹,并确认生成的 CapApp-SPM/Package.swift 包含 traits: 后缀。手动编辑该文件不会生效,因为 CLI 会在每次同步时重写它。
提高工具版本后,Swift code 中出现错误。 这是你控制的包中的 Swift 6 语言模式。添加 swiftLanguageModes: [.v5].
插件之间的命名冲突。 从 CLI 8.4 开始, experimental.ios.spm.packageOptions 允许你为 symlink 或 moduleAliases 每个插件进行设置。更多内容见 Capacitor iOS 故障排除指南.
要了解 SPM 的全貌,请阅读 Capacitor 的 SPM 与 CocoaPods 对比。如果你希望使用已针对 SPM 项目运行最新 Xcode 的云构建,请参阅 Capgo 构建.