Saltar al contenido principal

Cómo usar SPM Package Traits en Capacitor 8

Usa los traits de paquetes SPM en Capacitor 8: habilita funcionalidades opcionales de plugins con packageTraits, configura swiftToolsVersion 6.1 y añade traits a tu propio plugin.

Ilustración de la configuración de Swift Package Manager en proyectos iOS de Capacitor

Para usar package traits de SPM en Capacitor 8, establece experimental.ios.spm.swiftToolsVersion a "6.1" y enumera los traits por plugin en experimental.ios.spm.packageTraits en tu configuración de Capacitor, luego ejecuta bunx cap sync ios. El CLI escribe esos traits en el CapApp-SPM/Package.swiftgenerado, y SwiftPM compila el plugin con las dependencias opcionales y rutas code correspondientes. Esto requiere Capacitor CLI 8.3.0 o superior y solo aplica a proyectos iOS que usen Swift Package Manager.

Los traits cierran la última gran brecha de funciones entre SPM y CocoaPods para plugins: dependencias nativas opcionales que antes eran subspecs de podspec. Esta guía cubre el lado de la app, el lado del autor del plugin y los límites del soporte experimental actual.

Qué son los package traits

Los package traits provienen de la propuesta de Swift Evolution SE-0450, implementada en Swift 6.1 (Xcode 16.3 y posteriores). Un paquete declara traits con nombre en su Package.swift. Los consumidores eligen cuáles habilitar al depender del paquete. Un trait puede:

  • hacer que una dependencia sea opcional (condition: .when(traits: [...]) en una dependencia de target),
  • activa las rutas de code, ya que cada trait habilitado está disponible como una condición de compilación (#if TraitName),
  • cambia ajustes de build como defines o linker flags,
  • habilita otros traits.

Un paquete también puede marcar traits como predeterminados. Se activan a menos que el consumidor pase una lista explícita.

Si conoces los subspecs de CocoaPods o las features de Cargo en Rust, es la misma idea.

Por qué importa en Capacitor

Capacitor genera el archivo de la app Package.swift por ti, así que no puedes editar manualmente las líneas de dependencias para pasar traits. Antes de CLI 8.3, los autores de plugins con un SDK nativo opcional tenían dos malas opciones en SPM: enlazar siempre el SDK, aumentando el tamaño y a veces añadiendo trabajo de privacidad o cumplimiento de exportación, o publicar un segundo plugin. Casos comunes:

  • SQLite cifrado vs sin cifrar (SQLCipher),
  • SDKs de analytics con y sin recogida del identificador publicitario,
  • un SDK de proveedor opcional para un proveedor de pagos o login,
  • herramientas solo para debug.

Habilita los traits en tu app

1. Verifica las versiones

bunx cap --version      # 8.3.0 or later
xcodebuild -version     # Xcode 26 (required by Capacitor 8 anyway)

El proyecto debe usar SPM: ios/App/CapApp-SPM existe. Si usas CocoaPods, mira cómo migrar tu app Capacitor a SPM.

2. Busca los nombres de los traits

Abre el plugin Package.swift en node_modules y busca el/la traits: array, o revisa su README. Los nombres de los traits distinguen mayúsculas.

3. Configura 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;

La clave es el nombre del paquete npm del plugin. El valor es la lista de traits a habilitar.

4. Sincroniza y revisa el output

bunx cap sync ios

El generado ios/App/CapApp-SPM/Package.swift ahora empieza con // swift-tools-version: 6.1 y la línea de dependencia del plugin incluye los traits:

.package(name: "AcmeCapacitorDb", path: "../../../node_modules/@acme/capacitor-db", traits: [.defaults, "SQLCipher"])

El CLI escribe .defaults (o defaults, .default, default en cualquier caso) como el SwiftPM .defaults valor, y el resto de nombres como strings entre comillas.

Build en Xcode. Si Xcode sigue mostrando el grafo antiguo, usa Archivo > Paquetes > Restablecer caché de paquetes.

Reglas que CLI aplica

El Capacitor CLI valida la configuración durante la sincronización:

  • Si algún plugin tiene una lista de traits no vacía y swiftToolsVersion falta, la sincronización se detiene con: “Package traits require an explicit Swift tools version of 6.1 or higher.”
  • Si swiftToolsVersion es inferior a 6.1, la sincronización se detiene y te avisa que la versión es demasiado baja.
  • swiftToolsVersion debe verse así 6.1 o 6.1.0.
  • Los plugins con un array vacío se tratan como si no estuvieran listados.

Mantener o descartar traits por defecto

Listar traits reemplaza los traits por defecto. Compara:

Valor de configuración Resultado
no listado traits por defecto del plugin
['SQLCipher'] solo SQLCipher, por defecto desactivado
['.defaults', 'SQLCipher'] por defecto y adicionales SQLCipher

Lee la documentación del plugin antes de quitar los defaults. Un plugin podría tener su backend estándar detrás de un trait por defecto.

Añade traits a tu propio plugin

Si mantienes un plugin con un SDK opcional, aquí tienes uno completo Package.swift. El nombre del paquete debe coincidir con el nombre del cual deriva el Capacitor CLI de tu npm ("@acme/capacitor-db pasa a ser AcmeCapacitorDb). Consulta migrar un plugin de Capacitor a SPM para la regla.

// 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]
)

Consulta el repositorio propio de SDK para verificar su URL de paquete, nombre del producto y versión antes de copiar esto.

Detalles que es fácil pasar por alto:

  • La versión 6.x de Tools cambia el modo de lenguaje predeterminado a Swift 6 para tus targets. Capacitor aún no soporta Swift 6 oficialmente, así que mantén swiftLanguageModes: [.v5] a menos que tu code esté listo para strict concurrency.
  • Subir la versión de las herramientas de tu plugin a 6.1 implica que los consumidores necesiten Xcode 16.3+. Las apps de Capacitor 8 ya requieren Xcode 26, así que no es una restricción nueva.

Usa el trait en code

Los traits habilitados son condiciones de compilación en los targets de tu package:

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

Devuelve un error claro que nombre el trait cuando una función esté desactivada. De lo contrario, los desarrolladores de apps verán un fallo silencioso y culparán al plugin.

También puedes definir tu propio flag con swiftSettings: [.define("ACME_SQLCIPHER", .when(traits: ["SQLCipher"]))] si prefieres un nombre con prefijo.

Diseña traits que sean aditivos

SwiftPM unifica los traits en todo el grafo de dependencias. Si dos paquetes dependen de tu plugin con diferentes traits, se habilita la unión. Así:

  • Habilitar un trait debe añadir capacidades, no eliminar API ni cambiar los valores por defecto.
  • Evita traits mutuamente excluyentes. Si no puedes, haz que el build falle con un error claro:
#if TraitA && TraitB
#error("TraitA and TraitB cannot be enabled together")
#endif

Prueba cada combinación

swift build por sí solo compila para Mac, y un plugin que depende de Capacitor solo compila para iOS, así que prueba los traits con una app de prueba Capacitor compilada para un simulador de iOS. Para cada combinación (sin entrada para los traits por defecto, ['SQLCipher'], ['.defaults', 'SQLCipher'] y así sucesivamente), cambia packageTraits en la configuración de la app de prueba, luego sincroniza y compila:

bunx cap sync ios
xcodebuild build \
  -project ios/App/App.xcodeproj \
  -scheme App \
  -destination 'generic/platform=iOS Simulator' \
  CODE_SIGNING_ALLOWED=NO

Añade cada combinación al CI como un job independiente.

Mantén cubiertos a los usuarios de CocoaPods

Los Traits solo existen en SPM. Si tu plugin también incluye un podspec, ofrece la misma opción mediante 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

Usar el mismo nombre de condición de compilación en el subspec te permite compartir uno #if SQLCipher La ruta code. Las apps con CocoaPods seleccionan luego el subspec en su Podfile. El Capacitor CLI escribe una pod una línea por plugin dentro de capacitor_pods, así que documenta la línea extra del Podfile en tu README. Cómo usar CocoaPods con Capacitor 8 muestra dónde van las líneas de pods personalizadas.

Limitaciones

  • Experimental. Las opciones se encuentran en experimental y podrían moverse a ios.spm.* in a future major. Expect a config rename at some point.
  • Swift 6 no tiene soporte oficial. La propia documentación de CLI advierte que configurar swiftToolsVersion a la 6.0 o superior podría causar problemas. Prueba la app completa, sobre todo los plugins que incluyen binarios xcframework.
  • Solo iOS. Android no tiene equivalente en Capacitor. Los plugins gestionan los SDK de Android opcionales con variables de Gradle en variables.gradle o artefactos separados.
  • Los plugins deben activarlo explícitamente. La mayoría de los plugins aún no definen traits. Verifícalo antes de planear uno.

Solución de problemas

El sync falla con “Package traits require an explicit Swift tools version of 6.1 or higher”. Añadir swiftToolsVersion: '6.1' junto a packageTraits.

SwiftPM reporta un trait desconocido. Los nombres de los traits distinguen mayúsculas y deben existir en el Package.swift para la versión instalada.

La funcionalidad sigue sin aparecer tras activar el trait. Reinicia el caché de paquetes en Xcode, limpia la carpeta de build y confirma que el CapApp-SPM/Package.swift tiene el traits: sufijo. Editar ese archivo a mano no sirve, ya que CLI lo sobrescribe en cada sync.

Errores en Swift code tras subir la versión de las herramientas. Es el modo de lenguaje Swift 6 en un paquete que controlas. Añade swiftLanguageModes: [.v5].

Conflictos de nombres entre plugins. Desde CLI 8.4, experimental.ios.spm.packageOptions te permite configurar symlink o moduleAliases por plugin. Más detalles en el Guía de troubleshooting de iOS de Capacitor.

Para entender mejor el panorama de SPM, lee SPM vs CocoaPods para Capacitor. Si buscas cloud builds que ya ejecuten la versión actual de Xcode para proyectos SPM, consulta Build de Capgo.

Live updates para apps Capacitor

Cuando un bug de la capa web está en producción, publica el fix a través de Capgo en lugar de esperar días a la aprobación de la app store. Los usuarios reciben la actualización en segundo plano mientras los cambios nativos siguen el flujo de revisión habitual.

Lo último de nuestro blog

Capgo te da la información clave que necesitas para crear una app móvil realmente profesional.