patch-package를 활용한 NPM 패키지 패치(patch) 사례 (feat. React Native)
안녕하세요. 네이버 플레이스의 CONOMI 개발팀에서 글로벌 식당 리뷰 서비스를 만들고 있는 김일입니다.
최근 CONOMI 앱 개발 시 React Native를 패치(patch) 하여 사용했던 경험을 공유합니다. 비슷한 상황에 마주했을 때 도움이 되었으면 좋겠습니다.
TL;DR
Node.js 기반 프로젝트 진행 중 라이브러리나 프레임워크 등 NPM 패키지 자체의 수정이 불가피한 경우가 있을 수 있습니다. 이때 patch-package
라는 패키지를 이용하면 NPM 패키지 의존성은 그대로 유지하면서, 변경한 NPM 패키지의 내용을 버전 관리 대상으로 간편하게 만들어 줄 수 있습니다.
배경
React Native 구 버전에서 발생한 iOS 13의 다크 모드 미지원 이슈
CONOMI개발팀에서 만들고 있는 일본 맛집 서비스 CONOMI 앱은 React Native
(이하 RN) 0.59.10
기반으로 개발되었으며, 0.59.10
버전은 2019년 7월 2일 출시되었습니다.
한편 애플이 지난 2019년 9월 2일 정식으로 공개한 iOS 13
부터는 다크 모드가 추가되었습니다. 문제는 iOS 13 SDK
를 공식적으로 지원하는 Xcode 11
에서 앱을 빌드 한 경우, 사용자가 디바이스의 화면 스타일을 다크 모드로 설정하면 앱 또한 강제적으로 다크 모드로 표시됩니다. 이때 앱이 다크 모드를 준수하도록 대응되어 있지 않다면 의도하지 않은 UI가 나올 수 있습니다.
최근 CONOMI 앱 1.5.2 버전의 QA를 진행하던 중 다크 모드와 관련된 이슈가 발생하였습니다. 앱 상단의 Status Bar 영역이 제대로 노출되지 않는 문제였습니다.
CONOMI 앱의 경우 다크 모드를 준수하는 UI를 따로 대응하지는 않고, 앱 전반의 스타일을 지정할 수 있는 UIUserInterfaceStyle
의 값을 Light
로 고정하여 다크 모드를 무시하도록 하고 있었습니다.
<key>UIUserInterfaceStyle</key>
<string>Light</string>
그런데 앱 상단의 Status Bar의 스타일은 RN 내부에서 직접 세팅할 수 있도록 API가 제공되고 있었는데, CONOMI 앱의 흰색 배경에 알맞도록 dark-content
를 지정해도 흰색으로 노출되어 결과적으로 Status Bar가 보이지 않게 되는 것이었습니다.
원인을 분석하기 위해 RN 0.59.10
의 Status Bar와 관련된 코드를 확인해 보았습니다. StatusBar
컴포넌트의 barStyle
속성을 dark-content
로 지정하면 내부적으로 iOS UIStatusBarStyle
의 값을 UIStatusBarStyleDefault
로 지정하고 있었습니다. (관련 Code: react-native/React/Modules/RCTStatusBarManager.m)
RCT_ENUM_CONVERTER(UIStatusBarStyle, (@{ @”default”: @(UIStatusBarStyleDefault), @”light-content”: @(UIStatusBarStyleLightContent), @”dark-content”: @(UIStatusBarStyleDefault), }), UIStatusBarStyleDefault, integerValue);
그런데 iOS 13
에서는 화면 스타일이 다크 모드일 때 UIStatusBarStyleDefault
를 사용하면 의도대로 표시가 되지 않았고, iOS 13 SDK
에서 새로 추가된 값인 UIStatusBarStyleDarkContent
를 사용해야만 했습니다.
하지만 RN 0.59.10
릴리스 당시엔 iOS 13
이 출시되지 않았으니 당연히 이와 관련된 대응이 되어 있지 않은 상태였습니다.
이와 관련한 유사한 이슈는 이미 RN 공식 GitHub에 Issue로 보고가 되었고, 0.61.2
버전에서는 해결이 되었다고 합니다.
RN 버전 업그레이드?
Status Bar 이슈를 해결하기 위해 RN 버전을 0.61
이상으로 업그레이드하기에는 배보다 배꼽이 더 큰 상황이었습니다. 0.60
이후의 Breaking Changes도 있고, 현재 CONOMI 앱 개발을 위해 사용 중인 수많은 의존 라이브러리들의 호환성을 검증하는 작업이 불가피하기 때문입니다.
NPM 패키지 패치(patch)
불가피한 RN 내부 코드 수정
위 이슈는 RN에서 iOS 13 SDK
를 사용할 수 있도록 Status Bar와 관련된 코드를 단 몇 줄만 추가하면 해결이 가능했습니다. (관련 GitHub Issue : https://github.com/facebook/react-native/issues/26619#issuecomment-536191518)
UIStatusBarStyle
을 dark-content
로 지정시 iOS 13.0
을 지원하는 경우에만 UIStatusBarStyleDarkContent
값을 사용하도록 분기하는 아주 간단한 내용입니다.
AS-IS : react-native/React/Modules/RCTStatusBarManager.m (0.59-stable)
RCT_ENUM_CONVERTER(UIStatusBarStyle, (@{
@"default": @(UIStatusBarStyleDefault),
@"light-content": @(UIStatusBarStyleLightContent),
@"dark-content": @(UIStatusBarStyleDefault),
}), UIStatusBarStyleDefault, integerValue);
TO-BE : https://github.com/facebook/react-native/issues/26619#issuecomment-536191518
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability"RCT_ENUM_CONVERTER(UIStatusBarStyle, (@{
@"default": @(UIStatusBarStyleDefault),
@"light-content": @(UIStatusBarStyleLightContent),
@"dark-content": (@available(iOS 13.0, *)) ? @(UIStatusBarStyleDarkContent) : @(UIStatusBarStyleDefault),
}), UIStatusBarStyleDefault, integerValue);#pragma clang diagnostic pop
이를 위해서는 RN의 코드를 수정해야 하는데, 이 몇 줄 안되는 코드를 수정하기 위해 해당 NPM 패키지의 의존성을 제거하고, 소스 코드 레벨로 분리하여 별도로 관리하는 건 상당히 번거롭고 지저분한 일입니다. 그렇다고 로컬의 /node_modules
경로의 NPM 패키지 코드를 직접 수정하는 건 일회성 빌드는 가능할지 몰라도 소스코드 버전 관리가 불가능합니다.
NPM 의존 패키지 코드를 수정하고 유지할 수 있게 해주는 patch-package
그러던 중 한줄기 빛과 같은 코멘트를 발견했습니다. patch-package
라는 패키지를 이용하면 NPM 패키지 의존성은 그대로 유지하면서, 변경한 NPM 패키지의 내용을 버전 관리 대상으로 만들어 줄 수 있었습니다.
로컬의 /node_modules
경로에 존재하는 NPM 의존 패키지의 코드를 수정하고 patch-package
를 이용해 패치 파일(.patch
)을 생성한 뒤, NPM 패키지 설치 시점에 해당 패치 파일의 내용으로 코드를 덮어 씌우는 방식입니다.
패치 파일은 git diff
를 이용하여 생성된 표준 포맷으로서 보다 자세한 내용은 아래 링크들을 참조해주세요
RN NPM 패키지 패치 과정
patch-package
의 공식 가이드에 따라 아래와 같은 과정으로 패치를 진행했으며, CONOMI 앱에서 사용중인 패키지 매니저 yarn
을 기준으로 작성하였습니다.
1. 준비
package.json
설정
"scripts": {
+ "postinstall": "patch-package"
}
patch-package
및 postinstall-postinstall
설치
yarn add patch-package postinstall-postinstall
2. NPM 패키지 코드 수정
RN GitHub Issue에 등록된 코멘트를 참고하여 로컬의 /node_modules
경로의 RN 패키지 코드를 직접 수정하였습니다.
3. 패치 파일 생성
patch-package
를 이용하여 로컬의 변경 사항을 바탕으로 패치 파일 생성
yarn patch-package package-name$ yarn patch-package react-native
yarn run v1.13.0
$ /conomi-app/node_modules/.bin/patch-package react-native
patch-package 6.2.0
• Creating temporary folder
• Installing react-native@0.59.10 with yarn
• Diffing your files with clean files
✔ Created file patches/react-native+0.59.10.patch
✨ Done in 16.73s.
생성된 패치 파일 내용 (/patches/react-native+0.59.10.patch
)
diff --git a/node_modules/react-native/React/Modules/RCTStatusBarManager.m b/node_modules/react-native/React/Modules/RCTStatusBarManager.m
index 5136b36..403a14e 100644
--- a/node_modules/react-native/React/Modules/RCTStatusBarManager.m
+++ b/node_modules/react-native/React/Modules/RCTStatusBarManager.m
@@ -14,12 +14,17 @@
#if !TARGET_OS_TV
@implementation RCTConvert (UIStatusBar)
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunguarded-availability"
+
RCT_ENUM_CONVERTER(UIStatusBarStyle, (@{
@"default": @(UIStatusBarStyleDefault),
@"light-content": @(UIStatusBarStyleLightContent),
- @"dark-content": @(UIStatusBarStyleDefault),
+ @"dark-content": (@available(iOS 13.0, *)) ? @(UIStatusBarStyleDarkContent) : @(UIStatusBarStyleDefault),
}), UIStatusBarStyleDefault, integerValue);
+#pragma clang diagnostic pop
+
RCT_ENUM_CONVERTER(UIStatusBarAnimation, (@{
@"none": @(UIStatusBarAnimationNone),
@"fade": @(UIStatusBarAnimationFade),
이렇게 생성된 패치 파일을 Git 저장소의 버전 관리 대상으로 추가합니다.
4. 패치 파일 적용
앞서 package.json
에 설정한 scripts
의 postinstall
과정을 통해, yarn
인스톨 시점에 NPM 패키지에 적용할 패치 파일(/patches/{package-name}+{version}.patch
)이 존재하면 자동으로 로컬의 /node_modules
경로의 패키지 코드를 덮어씁니다.
$ yarn
yarn install v1.13.0
[1/4] 🔍 Resolving packages...
[2/4] 🚚 Fetching packages...
[3/4] 🔗 Linking dependencies...
[4/4] 🔨 Building fresh packages...
$ patch-package
patch-package 6.2.0
Applying patches...
react-native@0.59.10 ✔
✨ Done in 5.37s.
5. 패치 적용 결과
결론
일반적으로 NPM 의존 패키지에 문제가 있는 경우 해당 문제가 해결될 때까지 차기 버전의 출시를 기다리거나, 해당 오픈 소스 저장소에 직접 Pull Request 하여 적극적으로 Contribution 하는 방법이 있습니다. 하지만 해당 PR이 Approve/Merge 되어 Release 버전에 포함되기 까지는 많은 시간이 소요됩니다.
이러한 상황에서 빠르고 손쉽게 문제 해결을 할 수 있는 대안이 될 수 있을 것 같습니다.