Zum Hauptinhalt springen

So patchst du ein Capacitor Plugin (Bun, npm, pnpm)

So patchst du ein Capacitor Plugin mit bun patch, patch-package oder pnpm patch, wendest native iOS- und Android-Fixes an und entscheidest, wann ein Fork oder Upstream sinnvoll ist.

Logo der Capgo-Plugins auf dunkelblauem Hintergrund für den Leitfaden zum Patchen von Capacitor-Plugins

Um ein Capacitor Plugin zu patchen, bearbeite die Plugin-Dateien in node_modules, speichere die Änderung als Diff mit bun patch --commit (oder patch-package mit npm und Yarn, pnpm patch-commit mit pnpm), committe die generierte Datei in patches/, dann führe bunx cap sync aus, damit die nativen Projekte sie übernehmen. Der Package Manager wendet den Patch bei jedem Install erneut an – für dein Team und in der CI.

Patching ist der richtige Weg, wenn ein Plugin einen kleinen Bug hat, eine Version-Constraint deinen Build blockiert oder ein Upstream-Fix bereits gemergt, aber noch nicht released wurde. Dieser Guide zeigt den Workflow für jeden Package Manager, wie native Patches Xcode und Gradle erreichen und wann ein Fork oder ein Upstream-PR die bessere Wahl ist.

Warum Patching für native Plugin-code funktioniert

Ein Capacitor-Plugin ist ein npm-Package mit drei Teilen: JavaScript in dist/, Android-code in android/, und iOS-code in ios/ plus eine podspec und/oder Package.swift. Capacitor kompiliert die nativen Teile direkt aus node_modules:

  • Android: android/capacitor.settings.gradle umfasst jedes Plugin android Ordner von node_modules.
  • iOS mit CocoaPods: Die Podfile referenziert jedes Plugin mit :path => '../../node_modules/...'.
  • iOS mit SPM: CapApp-SPM/Package.swift referenziert jedes Plugin über lokalen Pfad in node_modules.

Eine Änderung an node_modules/@scope/plugin/ios/Sources/... landet in deinem App-Binary nach cap sync und einen Rebuild. Es muss nichts veröffentlicht werden.

Bevor du patchst

  1. Prüfe auf eine Release. Prüfe das Changelog und die offenen PRs des Plugins. Ein Upgrade ist besser als ein Patch.
  2. Lies die Lizenz. MIT und Apache-2.0 erlauben private Änderungen. Copyleft-Lizenzen wie GPL oder MPL-2.0 können dich dazu verpflichten, den modifizierten Source zu veröffentlichen, wenn du die App auslieferst. Prüfe das vor dem Release.
  3. Dokumentiere das Warum. Schreibe die Issue-URL oder einen kurzen Grund in deine Commit-Message. Sechs Monate später erinnert sich niemand mehr daran, warum patches/ eine Datei darin enthält.

Patching mit Bun

Bun hat Patching bereits eingebaut. Kein extra Package, kein postinstall Script.

# 1. Prepare the package for editing
bun patch @capacitor/haptics

# 2. Edit files under node_modules/@capacitor/haptics

# 3. Save the patch
bun patch --commit node_modules/@capacitor/haptics

Schritt 1 sorgt dafür, dass Bun dir eine private Kopie des Packages gibt, node_modulesdamit Edits den globalen Cache von Bun nicht beeinflussen. Schritt 3 schreibt ein Diff und speichert es in package.json:

{
  "patchedDependencies": {
    "@capacitor/haptics@8.0.2": "patches/@capacitor%2Fhaptics@8.0.2.patch"
  }
}

Beides committen package.json und patches/ Ordner. Jeder bun install, einschließlich bun install --frozen-lockfile in der CI wird der Patch angewendet.

Der Key enthält die exakte Version. Wenn du das Plugin aktualisierst, passt Bun nicht mehr zum Patch. Erstelle ihn für die neue Version neu oder lösche ihn, falls der Fix bereits enthalten ist.

Patching mit npm oder Yarn: patch-package

npm install --save-dev patch-package

Füge einen postinstall Script, damit Patches nach jedem Install angewendet werden:

{
  "scripts": {
    "postinstall": "patch-package"
  }
}

Bearbeite dann die Dateien in node_modules und generiere den Patch:

npx patch-package @capacitor/haptics

Das schreibt patches/@capacitor+haptics+8.0.2.patch. Commit es. Falls der Patch nach einem Upgrade nicht mehr angewendet werden kann, patch-package schlägt die Installation mit einer klaren Meldung fehl.

Yarn Berry hat seinen eigenen yarn patch <pkg> / yarn patch-commit -s <path> flow that stores the patch in the resolutions field.

Patchen mit pnpm

pnpm patch @capacitor/haptics
# edit the temporary folder pnpm prints
pnpm patch-commit <path-printed-above>

pnpm speichert den Patch unter patchedDependencies und wendet ihn bei der Installation an.

Beispiel: Native-Build-Fehler in einem Plugin fixen

Ein häufiger Fall: Deine App nutzt AGP 9, und ein älteres Plugin android/build.gradle verweist noch auf proguard-android.txt, was AGP 9 ablehnt. Du kannst nicht auf ein Release warten.

bun patch some-capacitor-plugin

Bearbeiten node_modules/some-capacitor-plugin/android/build.gradle:

 buildTypes {
     release {
         minifyEnabled false
-        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
     }
 }

Speichern und synchronisieren:

bun patch --commit node_modules/some-capacitor-plugin
bunx cap sync android
cd android && ./gradlew assembleDebug

Der vollständige Hintergrund zu diesem Fehler ist in Capacitor Plugin-Buildfehler mit AGP 9 beheben.

Beispiel: iOS-Dependency-Constraint lockern

Zwei Plugins pinnen unterschiedliche Versionen desselben SDK, und CocoaPods kann sie nicht auflösen. Wenn du verifiziert hast, dass das Plugin mit der neueren SDK funktioniert, lockere seine podspec:

-  s.dependency 'FirebaseMessaging', '11.15.0'
+  s.dependency 'FirebaseMessaging', '>= 11.15.0', '< 13.0'

Bei SPM-Apps befindet sich dieselbe Einschränkung in der Package.swift:

-  .package(url: "https://github.com/firebase/firebase-ios-sdk.git", exact: "11.15.0")
+  .package(url: "https://github.com/firebase/firebase-ios-sdk.git", "11.15.0"..<"13.0.0")

Danach:

bunx cap sync ios

Bei CocoaPods kann es sein, dass du cd ios/App && pod update FirebaseMessaging einmal ausführen musst, weil Podfile.lock noch die alte Version enthält. Bei SPM nutzt du Datei &gt; Pakete &gt; Paketversionen auflösen in Xcode.

Den Capacitor-Core selbst patchen

Manchmal liegt der Bug in @capacitor/android, @capacitor/ios oder im CLI, und der Fix steckt in einem upstream Pull Request. Du kannst diese Pakete auf dieselbe Weise patchen, aber jedes Team pflegt am Ende dieselben Diffs.

Capgo veröffentlicht @capgo/capacitor-patch dafür. Es ist ein nur-Hook-Paket mit einem Katalog kleiner, versionsgebundener Patches, die auf ihre upstream PRs verlinken. Es tut nichts, bis du dich entscheidest, es zu nutzen:

bun add @capgo/capacitor-patch
bunx capgo-capacitor-patch list --all
import type { CapacitorConfig } from '@capacitor/cli';

const config: CapacitorConfig = {
  appId: 'com.example.app',
  appName: 'Example',
  webDir: 'dist',
  plugins: {
    CapacitorPatch: {
      patches: ['upstream-pr-8418-android'],
      strict: true,
    },
  },
};

export default config;

Package-Patches werden vorher ausgeführt cap sync und cap update, und native Projekt-Patches werden danach ausgeführt. bunx capgo-capacitor-patch doctor führt einen Dry Run aus. Mit strict: true, schlägt der Sync fehl, wenn ein ausgewählter Patch nicht mehr anwendbar ist. Das zeigt, ob ein Upgrade ihn überflüssig gemacht hat. Siehe die Plugin-Docs für die vollständige Optionsliste.

Patchen, forken oder upstreamen?

Situation Beste Option
Ein paar Zeilen, Fix bereits upstream gemerged Patchen, nach dem nächsten Release entfernen
Versions-Constraint zu strikt Patchen, Issue upstream eröffnen
Bug, noch kein Upstream-Fix Patchen und PR mit demselben Diff senden
Neues Feature oder großes Refactoring Fork, unter eigenem Scope veröffentlichen
Plugin verwaist Forken oder auf ein gepflegtes Plugin wechseln

Richtig forken

Wenn du forkst, veröffentliche es, damit Installationen reproduzierbar bleiben:

# in the fork, rename to your scope in package.json: "@acme/capacitor-foo"
bun run build
bun publish --access restricted

Die Installation über eine Git-URL funktioniert auch, aber der Ordner des Plugins dist/ Der Ordner muss in diesem Branch existieren oder von einem prepare Script gebaut werden. Nicht jeder Paketmanager führt Lifecycle-Scripts für Abhängigkeiten standardmäßig aus. Ein veröffentlichtes Paket vermeidet das.

Denk daran, dass der Name von npm den nativen Modulnamen ändert, den CLI für SPM generiert (@acme/capacitor-foo wird zu AcmeCapacitorFoo), aktualisiere also den Fork Package.swift entsprechend. Details findest du in Migration eines Capacitor Plugins zu SPM.

Bevor du ein Plugin forkst, weil eine Funktion fehlt, prüfe die Capgo Plugin-Verzeichnis. Es gibt eventuell bereits ein gepflegtes Plugin, das das abdeckt.

Upstreamen

Ziel ist es, deinen Patch wieder loszuwerden. Erstelle ein Issue mit einem minimalen Repro, deinen Capacitor- und Plugin-Versionen sowie dem Patch-Diff. Dann öffne einen PR. Maintainer mergen kleine, getestete Fixes viel schneller als reine Issue-Reports.

Patches und Live Updates

Dein Web-Bundle enthält das JavaScript des Plugins von dist/, daher wird ein Patch an der JS-Seite mit dem nächsten Web-Build ausgeliefert. Mit Capgo Live Updates erreicht das die Nutzer ohne Store-Release.

Native Patches sind anders. Änderungen an Swift, Kotlin, Java, Gradle, podspec oder Package.swift werden erst mit einem neuen Native Build wirksam, der in den Stores eingereicht wird. Pushe kein Web-Bundle, das von einem nativen Patch abhängt, an Nutzer, die noch die alte Binary verwenden. Der Capgo Compatibility Check vergleicht die nativen Plugin-Versionen zwischen einem Bundle und einem Channel, um dies zu erkennen.

Fehlerbehebung

Der Patch wird in der CI nicht angewendet. Make sure patches/ committet und nicht ignoriert wird. Bei patch-package prüfe, dass die CI nicht --ignore-scriptsverwendet, was postinstallüberspringt. Für Bun prüfe patchedDependencies ist im committed package.json.

Meine native Änderung hat keine Wirkung. Du hast die Datei bearbeitet, aber nicht neu gebaut. Führe aus bunx cap sync, dann clean: in Xcode Produkt &gt; Build-Ordner leeren, unter Android: ./gradlew clean. Bei SPM auch Paket-Caches zurücksetzen.

bun patch --commit sagt, es gibt keine Änderungen. Du hast eine Datei bearbeitet, die nicht Teil des Pakets ist (z. B. eine gehoistete Kopie in einem anderen Ordner). Bearbeite den Pfad Bun, der in Schritt 1 ausgegeben wurde.

Patch schlägt nach Plugin-Upgrade fehl. Prüfe, ob der Fix in der neuen Release-Version enthalten ist. Falls nicht, wiederhole den Patch gegen die neue Version.

Das Plugin baut, aber die App stürzt bei dieser Funktion ab. Patches überspringen die eigene Test-Suite des Plugins. Teste den gepatchten Pfad auf einem echten Gerät auf beiden Plattformen. Die Capacitor iOS-Troubleshooting-Guide und Android-Fehlerbehebung erklärt, wie man native Logs liest.

Live-Updates für Capacitor-Apps

Wenn ein Bug im Web-Layer live ist, liefere den Fix über Capgo aus, statt Tage auf die App-Store-Freigabe zu warten. Nutzer bekommen das Update im Hintergrund, während native Änderungen im normalen Review-Prozess bleiben.

Neues aus unserem Blog

Capgo gibt dir die besten Insights, um eine wirklich professionelle Mobile App zu erstellen.