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-package
和 postinstall-postinstall
这两个包。
yarn add patch-package postinstall-postinstall
2. 配置 package.json
在 package.json
文件的 scripts
部分,添加 postinstall
脚本,实现每次 yarn
安装或更新依赖后自动运行,应用补丁。
{
"scripts": {
"postinstall": "patch-package"
}
}
3. 创建补丁
进入
node_modules
文件夹,找到要修改的包,直接在其中修改代码。修改完成后,运行命令创建补丁文件。
bashyarn patch-package [package-name]
以
react-native-navigation
为例:yarn patch-package react-native-navigation
项目根目录下会多出一个
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
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 --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
中对应的包上。