跳转至正文

如何为 Capacitor 插件打补丁(Bun、npm、pnpm)

如何使用 bun patch、patch-package 或 pnpm patch 修补 Capacitor 插件,应用原生 iOS 和 Android 修复,并决定何时 fork 或提交上游。

深蓝色背景上的 Capgo 插件 logo,用于修补 Capacitor 插件的指南

要修补 Capacitor 插件,请在 node_modules中编辑插件文件,并使用 bun patch --commit (或 patch-package (在 npm 和 Yarn 上, pnpm patch-commit 在 pnpm 上),将生成的文件提交到 patches/,然后运行 bunx cap sync ,这样原生项目就能读取到它。包管理器会在每次安装时重新应用补丁,你的团队和 CI 中都是如此。

当插件有小 bug、有阻碍构建的版本约束,或上游修复已合并但尚未发布时,打补丁就是正确做法。本指南涵盖每种包管理器的工作流程、原生补丁如何进入 Xcode 和 Gradle,以及何时 fork 或向上游提 PR 更合适。

为什么打补丁对原生插件 code 有效

Capacitor 插件是一个 npm 包,包含三个部分:JavaScript 位于 dist/,Android code 位于 android/,以及 iOS code 位于 ios/ ,外加 podspec 和/或 Package.swift。Capacitor 直接从 node_modules:

  • Android: android/capacitor.settings.gradle 包含每个插件的 android 文件夹,来自 node_modules.
  • 使用 CocoaPods 的 iOS:Podfile 通过以下方式引用每个插件: :path => '../../node_modules/...'.
  • 使用 SPM 的 iOS: CapApp-SPM/Package.swift 在以下位置通过本地路径引用每个插件: node_modules.

因此,对 node_modules/@scope/plugin/ios/Sources/... 的修改最终会进入你的应用二进制文件,在 cap sync 以及重新构建。无需发布任何内容。

打补丁前

  1. 检查是否有新版本发布。 查看插件的更新日志和开放 PR。升级优于打补丁。
  2. 阅读许可证。 MIT 和 Apache-2.0 允许私有修改。GPL 或 MPL-2.0 等 Copyleft 许可证可能要求你在分发应用时公开修改后的源代码。发布前请确认。
  3. 记录原因。 在 commit message 中放入 issue URL 或一行原因。六个月后没人记得为什么 patches/ 里会有这个文件。

使用 Bun 打补丁

Bun 内置了补丁功能。无需额外包,也无需 postinstall 脚本。

# 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

步骤 1 让 Bun 在 node_modules中生成包的私有副本,因此编辑不会影响 Bun 的全局缓存。步骤 3 会写入 diff 并记录在 package.json:

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

提交两者 package.json 以及 patches/ 文件夹。每次 bun install,包括 bun install --frozen-lockfile 在 CI 中,应用该补丁。

键名包含确切版本。如果你升级了插件,Bun 将不再匹配该补丁,因此需要为新版本重新创建它,或者如果修复已发布则删除它。

使用 npm 或 Yarn 打补丁:patch-package

npm install --save-dev patch-package

添加一个 postinstall 脚本,让补丁在每次安装后都能应用:

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

然后编辑 node_modules 中的文件并生成补丁:

npx patch-package @capacitor/haptics

这会写入 patches/@capacitor+haptics+8.0.2.patch。提交它。如果升级后补丁无法应用, patch-package 会让安装失败并给出明确提示。

Yarn Berry 有自己的 yarn patch <pkg> / yarn patch-commit -s <path> 流程,会把 patch 存到 resolutions 该字段中。

使用 pnpm 打 patch

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

pnpm 会把 patch 记录在 patchedDependencies 并在安装时应用。

示例:修复插件中的原生构建错误

常见情况:你的应用用了 AGP 9,而某个旧插件的 android/build.gradle 仍然引用了 proguard-android.txt,而 AGP 9 不接受它。你没法等新版本发布。

bun patch some-capacitor-plugin

编辑 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'
     }
 }

保存并同步:

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

有关此错误的完整背景信息,请参阅 修复 Capacitor 插件在 AGP 9 下的构建错误.

示例:放宽 iOS 依赖约束

两个插件固定了同一 SDK 的不同版本,导致 CocoaPods 无法解析。如果您已验证该插件与较新的 SDK 兼容,请放宽其 podspec:

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

对于 SPM 应用,相同的约束位于插件的 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")

然后:

bunx cap sync ios

使用 CocoaPods 时,你可能需要 cd ios/App && pod update FirebaseMessaging 一次,因为 Podfile.lock 仍然保留着旧版本。如果使用 SPM,请使用 File > Packages > Resolve Package Versions 在 Xcode 中。

修补 Capacitor 核心本身

有时 bug 位于 @capacitor/android, @capacitor/ios 或 CLI 中,修复方案位于上游 pull request 里。你可以用同样的方式修补这些包,但每个团队最终都在维护相同的 diff。

Capgo 发布了 @capgo/capacitor-patch 用于此目的。这是一个仅包含 hook 的包,内含一系列小型、版本受限的补丁,并链接回其上游 PR。除非你选择加入,否则它不会执行任何操作:

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;

包补丁在...之前运行 cap sync 以及 cap update,原生项目补丁随后执行。 bunx capgo-capacitor-patch doctor 执行 dry run。使用 strict: true,当所选补丁不再适用时同步会失败,这能提示你升级已使其过时。参见 插件文档 以查看完整选项列表。

打补丁、fork 还是上游?

场景 最佳选项
几行代码,修复已合并到上游 打补丁,下个版本后移除
版本约束过于严格 打补丁,向上游提 issue
存在 Bug 且上游尚未修复 打补丁并提交包含相同 diff 的 PR
新功能或大规模重构 Fork 并在你的 scope 下发布
插件已弃用 Fork,或切换到受维护的插件

Fork 指南

如果你 Fork,请发布它以确保安装可复现:

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

从 git URL 安装也可以,但插件的 dist/ 文件夹必须存在于该分支中,或由 prepare 脚本构建,且并非所有包管理器默认都会运行依赖生命周期脚本。发布包可以避免此问题。

请记住,npm 名称会改变 CLI 为 SPM 生成的原生模块名称(@acme/capacitor-foo 会变为 AcmeCapacitorFoo),因此请更新 fork 的 Package.swift 以保持一致。详情见 将 Capacitor 插件迁移到 SPM.

在 fork 插件以添加缺失功能之前,请先查看 Capgo 插件目录。可能已有仍在维护的插件覆盖了该功能。

向上游提交

目标是删掉你的补丁。开一个 issue,附上最小复现、你的 Capacitor 和插件版本,以及补丁 diff。然后提交 PR。维护者合并小型且经过测试的修复,比仅凭 issue 报告快得多。

补丁与 Live Updates

你的 web bundle 包含来自 dist/的插件 JavaScript,因此 JS 端的补丁会随下一次 web 构建发布。使用 Capgo 实时更新 无需通过应用商店发布即可触达用户。

原生补丁则不同。对 Swift、Kotlin、Java、Gradle、podspec 等 Package.swift 的修改,只有在提交到商店的新原生构建中才会生效。不要向仍运行旧二进制的用户推送依赖原生补丁的 Web bundle。Capgo 的 兼容性检查 会对比 bundle 和 channel 之间的原生插件版本,以捕获此类问题。

故障排除

补丁未在 CI 中应用。 请确保 patches/ 已提交且未被忽略。对于 patch-package,请确认 CI 未使用 --ignore-scripts,该选项会跳过 postinstall。对于 Bun,请检查 patchedDependencies 位于已提交的 package.json.

我的原生修改没有生效。 你编辑了文件但没有重新构建。请运行 bunx cap sync,然后清理:在 Xcode 中 产品 &gt; 清理构建文件夹,在 Android 上 ./gradlew clean。如果使用 SPM,还需重置包缓存。

bun patch --commit 显示没有更改。 你编辑的文件不属于该包(例如位于其他文件夹中的提升副本)。请编辑第 1 步中 Bun 打印的路径。

升级插件后补丁失败。 检查新发布版本中是否已包含该修复。如果没有,请针对新版本重新应用补丁。

插件构建成功,但应用在该功能上崩溃。 补丁会跳过插件自身的测试套件。请在两个平台的真机上测试打过补丁的路径。下文 Capacitor iOS 故障排除指南 以及 Android 故障排除指南 介绍如何读取原生日志。

为 Capacitor 应用提供 Live Updates

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

博客最新动态

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