跳转至正文

如何在 Capacitor 8 中使用 SPM Package Traits

在 Capacitor 8 中使用 SPM package traits:通过 packageTraits 启用可选插件功能,设置 swiftToolsVersion 6.1,并为你自己的插件添加 traits。

Capacitor iOS 项目中 Swift Package Manager 配置的示意图

要在 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 构建.

为 Capacitor 应用提供 Live Updates

当 Web 层出现线上 Bug 时,通过 Capgo 发布修复,而不用等 App Store 审核好几天。用户会在后台收到更新,原生变更则仍走正常审核流程。

博客最新动态

Capgo 为你提供打造真正专业的移动应用所需的最有价值的洞察。