Skip to content

patch-package 的使用

开发中会遇到所用的三方库和主工程不兼容的情况,可能只需要做一定的小改动就可以运行,但库作者没有积极更新适配或者合入pr。这时候我们可以使用 patch-package 对依赖打补丁。

它的使用逻辑很简单,主要是三步:添加依赖->修正 node_modules 中的依赖代码->运行 patch-package 生成 patch 文件并加入 git。

笔者遇到的是 react-native-navigation 8.3.0 版本尚未对最新的 React Native 0.81.0 适配,又不想降级 RN 版本,但是看到了针对新版本的 pr #8078,将其应用后发现能够正常编译运行。

1. 安装

安装 patch-packagepostinstall-postinstall 这两个包。

bash
yarn add patch-package postinstall-postinstall

2. 配置 package.json

package.json 文件的 scripts 部分,添加 postinstall 脚本,实现每次 yarn 安装或更新依赖后自动运行,应用补丁。

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

3. 创建补丁

  1. 进入 node_modules 文件夹,找到要修改的包,直接在其中修改代码。

  2. 修改完成后,运行命令创建补丁文件。

    bash
    yarn patch-package [package-name]

    react-native-navigation 为例:

    yarn patch-package react-native-navigation
  3. 项目根目录下会多出一个 patches 文件夹,里面是生成的 .patch 文件,这里是 react-native-navigation+8.3.0.patch,也就是包名+版本号。

笔者实践时尝试直接从 GitHub pr 导出 patch 文件(例如 https://github.com/wix/react-native-navigation/pull/8078 链接后加上 '.patch' 得到 https://github.com/wix/react-native-navigation/pull/8078.patch 并下载),但应用过程中报错,最后还是手动修改代码再生成 patch 文件。后经对比,可能是因为两个 patch 文件中 diff 命令所执行的相对路径不同,理论上做一下替换就能使用。

两份 patch 文件对比

来自 GitHub

diff
From 43cb4634796174fc437aca8c872cb01bc22670b3 Mon Sep 17 00:00:00 2001
From: Manoj Mehra <[email protected]>
Date: Sat, 2 Aug 2025 00:32:44 +0530
Subject: [PATCH] RN-0.80.1-fixes android

---
 .../options/params/ReactPlatformColor.kt      | 14 ++++----
 .../options/parsers/ColorParser.kt            | 16 +++------
 .../react/modal/ModalContentLayout.kt         | 34 ++++++++++---------
 3 files changed, 30 insertions(+), 34 deletions(-)

diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/options/params/ReactPlatformColor.kt b/lib/android/app/src/main/java/com/reactnativenavigation/options/params/ReactPlatformColor.kt
index 5850dc0ca21..ea12b64a07f 100644
--- a/lib/android/app/src/main/java/com/reactnativenavigation/options/params/ReactPlatformColor.kt
+++ b/lib/android/app/src/main/java/com/reactnativenavigation/options/params/ReactPlatformColor.kt
@@ -4,20 +4,22 @@ import com.facebook.react.bridge.ColorPropConverter
 import com.facebook.react.bridge.ReadableMap
 import com.reactnativenavigation.NavigationApplication

-private fun parsePlatformColor(paths: ReadableMap) =
-    ColorPropConverter.getColor(paths, NavigationApplication.instance)
+private fun parsePlatformColor(paths: ReadableMap): Int {
+    return ColorPropConverter.getColor(paths, NavigationApplication.instance) ?: 0 // fallback to black
+}

 class ReactPlatformColor(private val paths: ReadableMap) :
     Colour(parsePlatformColor(paths)) {
+
     override fun get(): Int {
         return parsePlatformColor(paths)
     }

-    override fun get(defaultValue: Int?): Int? {
+    override fun get(defaultValue: Int?): Int {
         return try {
-            parsePlatformColor(paths)
-        }catch (e:Exception){
-            defaultValue
+            ColorPropConverter.getColor(paths, NavigationApplication.instance) ?: defaultValue ?: 0
+        } catch (e: Exception) {
+            defaultValue ?: 0
         }
     }
      }
     \ No newline at end of file
     diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/options/parsers/ColorParser.kt b/lib/android/app/src/main/java/com/reactnativenavigation/options/parsers/ColorParser.kt
     index 857af348911..1e3d58d0f3c 100644
     --- a/lib/android/app/src/main/java/com/reactnativenavigation/options/parsers/ColorParser.kt
     +++ b/lib/android/app/src/main/java/com/reactnativenavigation/options/parsers/ColorParser.kt
     @@ -1,5 +1,4 @@
      package com.reactnativenavigation.options.parsers
     -
      import android.content.Context
      import com.facebook.react.bridge.ColorPropConverter
      import com.reactnativenavigation.options.params.Colour
     @@ -18,21 +17,14 @@ object ColorParser {
             return ReactPlatformColor(JSONParser.convert(json))
         }
         return when (val color = json.opt(colorName)) {
-            null, VAL_NO_COLOR -> {
-                DontApplyColour()
-            }
-            is Int -> {
-                Colour(json.optInt(colorName))
-            }
+            null, VAL_NO_COLOR -> DontApplyColour()
+            is Int -> Colour(json.optInt(colorName))
             is JSONObject -> {
-                ColorPropConverter.getColor(color, context)?.let {
+                ColorPropConverter.getColor(color, context ?: throw IllegalArgumentException("Context must not be null"))?.let {
                     Colour(it)
                 } ?: NullColor()
             }
-            else -> {
-                NullColor()
-            }
+            else -> NullColor()
         }
         -
     }
          }
         \ No newline at end of file
         diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/react/modal/ModalContentLayout.kt b/lib/android/app/src/main/java/com/reactnativenavigation/react/modal/ModalContentLayout.kt
         index 1d877a9b69a..0af4b312ab9 100644
         --- a/lib/android/app/src/main/java/com/reactnativenavigation/react/modal/ModalContentLayout.kt
         +++ b/lib/android/app/src/main/java/com/reactnativenavigation/react/modal/ModalContentLayout.kt
         @@ -12,27 +12,30 @@ import com.facebook.react.uimanager.events.EventDispatcher
          import com.facebook.react.views.view.ReactViewGroup


-class ModalContentLayout(context: Context?) : ReactViewGroup(context), RootView{
+class ModalContentLayout(context: Context) : ReactViewGroup(context), RootView {

     private val mJSTouchDispatcher = JSTouchDispatcher(this)

-    override fun onChildStartedNativeGesture(child: View, androidEvent: MotionEvent) {
-        mJSTouchDispatcher.onChildStartedNativeGesture(androidEvent, this.getEventDispatcher())
+    override fun onChildStartedNativeGesture(childView: View?, ev: MotionEvent) {
+        mJSTouchDispatcher.onChildStartedNativeGesture(ev, getEventDispatcher())
     }
-    override fun onChildStartedNativeGesture(androidEvent: MotionEvent) {
-        mJSTouchDispatcher.onChildStartedNativeGesture(androidEvent, this.getEventDispatcher())
-    }
-    override fun onChildEndedNativeGesture(child: View, androidEvent: MotionEvent) {
-        mJSTouchDispatcher.onChildEndedNativeGesture(androidEvent, this.getEventDispatcher())
+
+    override fun onChildEndedNativeGesture(childView: View, ev: MotionEvent) {
+        mJSTouchDispatcher.onChildEndedNativeGesture(ev, getEventDispatcher())
     }
     +
+    override fun handleException(t: Throwable) {
+    getReactContext().handleException(
+        if (t is Exception) t else RuntimeException(t)
+    )
     +}
     +
     override fun requestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {}
-    private fun getEventDispatcher(): EventDispatcher {
-        val reactContext: ReactContext = this.getReactContext()
-        return UIManagerHelper.getEventDispatcher(reactContext, UIManagerType.FABRIC) ?: throw IllegalStateException("EventDispatcher for Fabric UI Manager is not found")
-    }

-    override fun handleException(t: Throwable?) {
-        getReactContext().handleException(RuntimeException(t))
+    private fun getEventDispatcher(): EventDispatcher {
+        val reactContext: ReactContext = getReactContext()
+        return UIManagerHelper.getEventDispatcher(reactContext, UIManagerType.FABRIC)
+            ?: throw IllegalStateException("EventDispatcher for Fabric UI Manager is not found")
     }

     private fun getReactContext(): ReactContext {
     @@ -49,5 +52,4 @@ class ModalContentLayout(context: Context?) : ReactViewGroup(context), RootView{
         super.onTouchEvent(event)
         return true
     }
     -
     -}
     +}
     \ No newline at end of file

本地导出

diff
diff --git a/node_modules/react-native-navigation/lib/android/app/src/main/java/com/reactnativenavigation/options/params/ReactPlatformColor.kt b/node_modules/react-native-navigation/lib/android/app/src/main/java/com/reactnativenavigation/options/params/ReactPlatformColor.kt
index 5850dc0..ea12b64 100644
--- a/node_modules/react-native-navigation/lib/android/app/src/main/java/com/reactnativenavigation/options/params/ReactPlatformColor.kt
+++ b/node_modules/react-native-navigation/lib/android/app/src/main/java/com/reactnativenavigation/options/params/ReactPlatformColor.kt
@@ -4,20 +4,22 @@ import com.facebook.react.bridge.ColorPropConverter
 import com.facebook.react.bridge.ReadableMap
 import com.reactnativenavigation.NavigationApplication

-private fun parsePlatformColor(paths: ReadableMap) =
-    ColorPropConverter.getColor(paths, NavigationApplication.instance)
+private fun parsePlatformColor(paths: ReadableMap): Int {
+    return ColorPropConverter.getColor(paths, NavigationApplication.instance) ?: 0 // fallback to black
+}

 class ReactPlatformColor(private val paths: ReadableMap) :
     Colour(parsePlatformColor(paths)) {
+
     override fun get(): Int {
         return parsePlatformColor(paths)
     }

-    override fun get(defaultValue: Int?): Int? {
+    override fun get(defaultValue: Int?): Int {
         return try {
-            parsePlatformColor(paths)
-        }catch (e:Exception){
-            defaultValue
+            ColorPropConverter.getColor(paths, NavigationApplication.instance) ?: defaultValue ?: 0
+        } catch (e: Exception) {
+            defaultValue ?: 0
         }
     }
      }
     \ No newline at end of file
     diff --git a/node_modules/react-native-navigation/lib/android/app/src/main/java/com/reactnativenavigation/options/parsers/ColorParser.kt b/node_modules/react-native-navigation/lib/android/app/src/main/java/com/reactnativenavigation/options/parsers/ColorParser.kt
     index 857af34..1e3d58d 100644
     --- a/node_modules/react-native-navigation/lib/android/app/src/main/java/com/reactnativenavigation/options/parsers/ColorParser.kt
     +++ b/node_modules/react-native-navigation/lib/android/app/src/main/java/com/reactnativenavigation/options/parsers/ColorParser.kt
     @@ -1,5 +1,4 @@
      package com.reactnativenavigation.options.parsers
     -
      import android.content.Context
      import com.facebook.react.bridge.ColorPropConverter
      import com.reactnativenavigation.options.params.Colour
     @@ -18,21 +17,14 @@ object ColorParser {
             return ReactPlatformColor(JSONParser.convert(json))
         }
         return when (val color = json.opt(colorName)) {
-            null, VAL_NO_COLOR -> {
-                DontApplyColour()
-            }
-            is Int -> {
-                Colour(json.optInt(colorName))
-            }
+            null, VAL_NO_COLOR -> DontApplyColour()
+            is Int -> Colour(json.optInt(colorName))
             is JSONObject -> {
-                ColorPropConverter.getColor(color, context)?.let {
+                ColorPropConverter.getColor(color, context ?: throw IllegalArgumentException("Context must not be null"))?.let {
                     Colour(it)
                 } ?: NullColor()
             }
-            else -> {
-                NullColor()
-            }
+            else -> NullColor()
         }
         -
     }
          }
         \ No newline at end of file
         diff --git a/node_modules/react-native-navigation/lib/android/app/src/main/java/com/reactnativenavigation/react/modal/ModalContentLayout.kt b/node_modules/react-native-navigation/lib/android/app/src/main/java/com/reactnativenavigation/react/modal/ModalContentLayout.kt
         index 1d877a9..0af4b31 100644
         --- a/node_modules/react-native-navigation/lib/android/app/src/main/java/com/reactnativenavigation/react/modal/ModalContentLayout.kt
         +++ b/node_modules/react-native-navigation/lib/android/app/src/main/java/com/reactnativenavigation/react/modal/ModalContentLayout.kt
         @@ -12,27 +12,30 @@ import com.facebook.react.uimanager.events.EventDispatcher
          import com.facebook.react.views.view.ReactViewGroup


-class ModalContentLayout(context: Context?) : ReactViewGroup(context), RootView{
+class ModalContentLayout(context: Context) : ReactViewGroup(context), RootView {

     private val mJSTouchDispatcher = JSTouchDispatcher(this)

-    override fun onChildStartedNativeGesture(child: View, androidEvent: MotionEvent) {
-        mJSTouchDispatcher.onChildStartedNativeGesture(androidEvent, this.getEventDispatcher())
+    override fun onChildStartedNativeGesture(childView: View?, ev: MotionEvent) {
+        mJSTouchDispatcher.onChildStartedNativeGesture(ev, getEventDispatcher())
     }
-    override fun onChildStartedNativeGesture(androidEvent: MotionEvent) {
-        mJSTouchDispatcher.onChildStartedNativeGesture(androidEvent, this.getEventDispatcher())
-    }
-    override fun onChildEndedNativeGesture(child: View, androidEvent: MotionEvent) {
-        mJSTouchDispatcher.onChildEndedNativeGesture(androidEvent, this.getEventDispatcher())
+
+    override fun onChildEndedNativeGesture(childView: View, ev: MotionEvent) {
+        mJSTouchDispatcher.onChildEndedNativeGesture(ev, getEventDispatcher())
     }
     +
+    override fun handleException(t: Throwable) {
+    getReactContext().handleException(
+        if (t is Exception) t else RuntimeException(t)
+    )
     +}
     +
     override fun requestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {}
-    private fun getEventDispatcher(): EventDispatcher {
-        val reactContext: ReactContext = this.getReactContext()
-        return UIManagerHelper.getEventDispatcher(reactContext, UIManagerType.FABRIC) ?: throw IllegalStateException("EventDispatcher for Fabric UI Manager is not found")
-    }

-    override fun handleException(t: Throwable?) {
-        getReactContext().handleException(RuntimeException(t))
+    private fun getEventDispatcher(): EventDispatcher {
+        val reactContext: ReactContext = getReactContext()
+        return UIManagerHelper.getEventDispatcher(reactContext, UIManagerType.FABRIC)
+            ?: throw IllegalStateException("EventDispatcher for Fabric UI Manager is not found")
     }

     private fun getReactContext(): ReactContext {
     @@ -49,5 +52,4 @@ class ModalContentLayout(context: Context?) : ReactViewGroup(context), RootView{
         super.onTouchEvent(event)
         return true
     }
     -
      }
     \ No newline at end of file

4. 提交补丁

将生成的补丁文件加入 Git 中。

5. 自动应用补丁

之后每次对项目运行 yarn 时,postinstall 脚本会自动执行,patch-package 会查找 patches 文件夹中的所有补丁文件,并将它们应用到 node_modules 中对应的包上。

上次更新于: