patch-package를 활용한 NPM 패키지 패치(patch) 사례 (feat. React Native)

Il Kim
네이버 플레이스 개발 블로그
13 min readFeb 17, 2020
https://github.com/ds300/patch-package

안녕하세요. 네이버 플레이스의 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 영역이 제대로 노출되지 않는 문제였습니다.

앱 상단의 Status Bar가 제대로 노출되지 않는 문제
앱 상단의 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)

UIStatusBarStyledark-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-packagepostinstall-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에 설정한 scriptspostinstall 과정을 통해, 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. 패치 적용 결과

패치 적용 후 상단 Status Bar 정상 노출

결론

일반적으로 NPM 의존 패키지에 문제가 있는 경우 해당 문제가 해결될 때까지 차기 버전의 출시를 기다리거나, 해당 오픈 소스 저장소에 직접 Pull Request 하여 적극적으로 Contribution 하는 방법이 있습니다. 하지만 해당 PR이 Approve/Merge 되어 Release 버전에 포함되기 까지는 많은 시간이 소요됩니다.

이러한 상황에서 빠르고 손쉽게 문제 해결을 할 수 있는 대안이 될 수 있을 것 같습니다.

레퍼런스

--

--