안녕하세요 저는 말랑이 온라인이라는 서비스를 만들고 있는 Whoyaho에서 풀스택 개발자로 일하는 최봉수 입니다!
최근 package 버전업을 실패해봤고, 왜 시도하려 했는지, 왜 실패했는지, 주의할 점은 무엇인지 공유드리려고 합니다!
긴글 읽기 힘드신 분들을 위해 핵심만 짧게 요약하면
- https://react-native-community.github.io/upgrade-helper/
이 곳의 도움을 받아서 업그레이드를 하자- React-Native에 종속적인 Third Party 라이브러리를 주의 깊게 살펴보자
- 라이브러리가 newArchtecture(fabric, hermes...)를 도입해도 되는지 확인해보자
- 라이브러리가 내가 올리려는 React-Native 버전을 지원하는지 확인해보자
요 내용에 대해 말하고 싶었습니다! 조금 기네요.. 이제 긴 글을 시작해보겠습니다!
여러분들의 프론트엔드 프로젝트에는 상태관리 라이브러리가 사용되나요?
저희 서비스에서는 특별한 상태관리 라이브러리를 사용하지 않고 context로 상태를 관리하고 있었습니다!
기존 서비스는 Context에 최소한의 데이터만 저장하고, 최대한 최신의 서버 데이터를 가져와서 최신화 시켜주는 것을 중요하게 생각했습니다.
(React-query를 사용하진 않았지만 최신의 React-query가 원하는 방향성이 었던 듯 합니다! 아 물론 캐싱은 어려웠네요..!)
그러다 최근에는 "상태 관리의 필요성"과 "관심사 분리의 필요성"을 느끼면서 상태관리 라이브러리를 추가하기로 했고, 그 중 Recoil을 사용하기로 했습니다.
(상태관리에 대한 더 자세한 이야기는 다른 글에서 해보도록 하겠습니다!
어쨌든 Recoil을 사용하기로 다른 상태관리 라이브러리들에 비해 패키지 용량이 큰데 괜찮나? 라는 이야기를 들었습니다.
그러면서 살펴본 결과 recoil이 가장 큰 것을 확인할 수 있었습니다!
사실 다른 상태관리 라이브러리들에 비해 용량이 큰 것은 맞지만 용량 때문에 사용안할 정도는 아니라고 생각했고, 최적화를 진행했습니다.
그리고 Recoil의 비동기 처리로는 Recoil에서 권장하는 Suspense로 처리하려 했습니다!
여기서 버전업을 하려는 목적이 나옵니다!
저희 기존 버전은 아래와 같이 운영되고 있었습니다!
"react": "17.0.1",
"react-native": "0.64.2",
아시는 분도 있으시겠지만, Suspense는 react 16.6에서 실험 버전으로 생겼고 18버전부터 정식 지원됐습니다!
따라서 기존 버전에서 사용할 수도 있지만 정식 지원을 하는 버전을 사용하는 것이 좋다고 생각했습니다.
그리고 저희 서비스는 codePush로 잦은 배포를 하다보니 이번에 recoil도 추가하고, 최적화도 진행하고, react 버전을 올려주기로 했습니다!
미션!
Recoil 추가 (이건 그냥 하면 되니까..)- 번들 사이즈 최적화
- React 버전 올리기
일단 번들 사이즈 최적화를 너무 깊게 하진 않을 생각이었습니다!
번들 최적화에 너무 많은 시간을 사용하는 것 보단, 눈에 보이는 가장 빠르게 할 수 있는 것들만 최적화 하고, 기능 개발을 더 하는 것이 필요했습니다.
그래서 다음과 같은 작업들만 진행했습니다.
- 라이브러리 정리
- 사용하지 않는데 설치돼있던 라이브러리
- 사용하지만 한 곳에서만 사용돼서 직접 구현할 수 있는 라이브러리 (ex: BottomSheet)
- 유용하게 사용하지만 너무 용량이 큰 라이브러리 (ex: moment => dayjs)
지금 당장 불필요하거나 대체할 수 있는 라이브러리들은 제거했습니다.
특히 BottomSheet 같은 라이브러리 경우 react-native 코드로도 충분히 구현할 수 있어서 삭제했습니다.
또한 저희 서비스가 글로벌에서도 운영되기 때문에 moment를 사용해줬었는데 dayjs로도 충분히 구현 가능하여 삭제했습니다.
그리고 예전에 설치됐지만 삭제되지 못한 라이브러리들도 한번에 삭제했습니다.
- 불필요한 파일 정리
- 몇몇 이미지 파일이나 음악 파일들을 서버에서 받아오는 것으로 변경
- 지금은 사용하지 않는 native 모듈 제거
위와 마찬가지로 지금은 사용되지 않거나 굳이 프론트엔드에 종속될 필요가 없는 데이터들을 삭제했습니다!
- 최적화
- 번들 최적화
yarn berry (실패)
토스 2021 슬레쉬보고 번들 최적화를 진행했습니다!
그 중 번들 자체의 용량을 줄이기 위해 deduplicate와 라이브러리 최적화를 시도했습니다.
그리고 yarn berry를 시도했지만 실패했습니다.
React-Native에서는 node_modules에 의존적이라 node_modules를 배제하는 yarn berry의 경우 사용이 불가능했습니다.
링크를 들어가 보시면 pnp지원 논의를 볼 수 있습니다!
https://github.com/react-native-community/cli/issues/27#issuecomment-1126791348
이 글을 쓰는 가장 큰 이유는 이 부분인 것 같습니다.
결과적으로 버전업에 실패했고(버전업을 하지 않기로 했고), 그동안 제가 했던 삽질들입니다!
그렇게 무작정 React를 18.2.0으로 업그레이드 했을 때 당연히 에러 덩어리였습니다!
(글을 쓰겠다는 생각을 작업을 마무리하고 해버려서 error가 따로 없습니다... 다음부턴 다 캡쳐해야지..)
그리고 알아보니 React-Native의 버전까지 올려줘야 했습니다!
굉장히 당연한건데 이 부분을 생각을 못했더라구요..
그래서 React-Native의 버전 역시 함께 올려줬습니다.
역시 버전만 올린다고 해결되지 않았습니다!
버그 투성이여서 생각해보니 버전에 따른 node_modules만 수정할 것이 아니라 native 모듈들도 수정해줘야 했고,
달라진 React버전에 맞는 셋팅들도 진행해야 했습니다.
그래서 react-native init을 해서 비교하는 미친짓을 해보려 했는데, 역시 제가 눈으로 보고 달라진 점을 찾는다고 해결되진 않았습니다.
그 떄 열심히 구글링하다 찾아낸 친구가 React Native Upgrade Helper입니다.
아래로 스크롤 해보시면 전체적으로 변경된 파일들이 전체적으로 나옵니다.
해당 부분을 하나하나 바꿔주니 에러가 생기지 않았습니다!
그리고 생각했던 것은 React의 버전입니다.
지금 가장 최신 React버전은 18.2.0이고 React-Native에서는 18.0.0을 설치하는 것을 이야기 해줍니다.
물론 마이너 버전 업데이트에서는 크리티컬한 버그가 없을 것이라 생각했지만,
2번이 업데이트 된 상황에 혹시 뭔가 다른게 있나..? 하고 릴리즈 노트를 확인해봤습니다.
다행이도 React에 변경점은 없고 React-Dom에만 있어서 React-Native를 사용하는데는 React 18.0.0버전을 사용해도 문제가 없을 거라고 생각했습니다.
그런데 업데이트를 하다 보니 비둘기 마음은 콩밭에 있고, 개발자 마음은 신기술에 있다고 새로운 아키텍쳐가 사용된 것을 확인해버렸습니다.
바로 Fabric 이라는 아키텍쳐입니다
React => Javascript(Jsc) => birdge(shadow tree, async, Native module) => native 단계에서
React => Javascript(Jsi) => Fabric => Native 으로 변화 됐습니다!
쉽게 말하면 기존에는 React로 코드를 작성하면 Javascript로 해석하고 그걸 바탕으로 Native 코드를 호출하기 위해 bridge에서 작업을 진행해줘야 했습니다. 이는 Javascript와 Native가 서로를 직접 인식하지 못하기 때문에 JSON을 통해 중간 과정을 거쳐야 하는 것을 의미하는데, 이 과정에서 수 많은 전달 과정이 생기고 비 효율이 생기게 됩니다!
반면에 Fabric모델은 JSI를 이용해서 Javascript가 C++ 호스트 객체들을 참조하거나 그들을 대상으로 함수를 실행시킬 수 있습니다! 그리고 C++는 Ios, Android로 작성되거나 해석될 수 있습니다. 즉 그렇기 때문에 Javascipt와 Native를 C++를 기반으로 bridge라는 중간과정 없이 한번에 연결될 수 있었습니다. 또한 이 사이에서 Codegen이 안정된 타입을 바탕으로 더 빠르게 Native코드들을 작성해낼 수 있게 됩니다.
즉 중간 과정에서의 번거로움이 사라져서 최적화가 더 잘 이뤄진다는 것입니다.
(이 부분에 잘못 알고 있는 부분이 있다면 언제든 지적 부탁드립니다!)
모델은 React-Native 0.68버전부터 도입 됐습니다.
그리고 이미 최적화를 해야한다는 개념이 머리에 들어온 터라 새로운 이러한 새로운 모델은 못 참았습니다.
중간정리
- React 버전을 올려야한다.
- 그러다 보니 React-Native 버전도 함께 올려야 했다.
- 버전을 올릴 때 React Native Upgrade Helper를 사용해주면 조금 더 편하게 올릴 수 있다.
- 그러다가 Fabric이라는 새로운 아키텍쳐를 발견해서 이 것도 추가했다.
Android를 빌드 할 경우 kotlinVersion을 제대로 못 받아오는 에러가 있었습니다.
정확한 원인은 찾지 못했지만 아래의 pr을 보면 해당 이슈가 다른 유저들에게도 발생했었고 kotlinVersion을 못 받아올 경우 React-Native 패키지 자체에서 kotlinVersion을 강제로 입력해주는 식으로 수정했습니다.
https://github.com/facebook/react-native/pull/34255
그러나 해당 이슈는 당시 가장 최신버전 0.69.4 에서는 포함되지 않았습니다.
(지금 최신인 0.70.0-rc.3에는 추가 됐네요!)
그래서 해결책으로
1. install된 node_module 패키지에 직접 수정된 패키지를 입력해준다.
2. React-Native 0.69.4(stabe-0.69 branch)를 fork해와서 해당 부분만 수정해주고 포크 해온 라이브러리를 yarn add 해준다.
1번의 해결책의 경우 개발자가 매번 패키지를 수정해줘야하는 너무나 큰 불편함이 있었기 때문에 2번을 시도하려 고민하고 있을 때 새로운 문제를 만났습니다.
https://www.npmjs.com/package/react-native-fast-image
해당 라이브러리는 지금도 꾸준히 사용되고 있고, 눈에 띄는 이미지 캐싱 성능을 보여줘서 저희 프로젝트에서도 사용하고 있었습니다.
보이시는 화면은 제가 직접 구동해서 캡쳐해둔 화면인데 (이건 왜 있지..?)
보이시는 것 처럼 FastImage를 사용했던 이미지들은 모두 에러가 났습니다.
그러고 react-native-fast-image package를 다시 봤는데
가장 최근 업데이트가 1년 전인것을 확인해버렸습니다...
그래서 소스 코드를 분석해서 적용해볼까 고민했지만
네이티브 모듈을 건들기엔 처음의 목표와 너무 동떨어졌고, 해당 문제를 해결하기 위해 시간을 더 많이 쓸 수 없었습니다.
그래서 Android에서는 Fabric사용을 포기했습니다.
그 후 다시 빌드해보니 위에서 kotilnVersion을 못받아오는 문제가 해결됐고, Ios만 Fabric을 지원하게끔 구현하려 했습니다.
네 역시 Ios도 실패했습니다!
이번엔 react-native-Reanimated 라이브러리 문제였는데
https://github.com/software-mansion/react-native-reanimated/issues/2991
해당 이슈를 보면 3.0.0-rc0 버전부터 지원한다고 제작자님(?)께서 말씀해주셨고 그 외에 react-native-gesture-handler react-native-screens 라이브러리도 버전을 올려야한다는 것을 알아서 버전을 올려봤습니다.
그래도 오류는 고쳐지지 않았고(rc버전을 사용하는 것에 조금 부담을 느끼기도 했고), ios에서도 Fabric을 포기하게 됐습니다.
원래 목표는 Fabric이 아니라 React 버전업이었기 때문에 콩밭에 가 있던 마음을 다잡고 다시 버전업에 집중하기로 했습니다.
이 외에도 React-Native에 종속적인 라이브러리들은 버전을 함께 올려주려 했습니다.
그런데 라이브러리를 올리니 기존에 사용하던 메서드들을 사용하지 못하는 일들이 몇번 있었습니다.
예를들어 react-native-iap라이브러리가 문제가 있었습니다.
purchaseErrorListener 메서드를 사용하는 곳이 있었는데 8.6.3으로 버전을 올리면 해당 메서드가 사라지고,
9.0.0으로 업데이트 하니 purchaseErrorListener메서드는 있지만 IAP default의사용 방법이 바뀌게 됩니다.
물론 상황에 맞게 코드를 다시 만들어주면 되지만 지금 전체적으로 모든 패키지를 업데이트 하면서 생기는 사이드 이팩트를 모두 감당하기 어렵다고 생각했습니다.
그리고 로직을 담당하는 라이브러리 뿐만 아니라 UI를 그려주는 라이브러리 예를 들면 react-navigation같은 라이브러리도 버전을 올리면 뷰 자체가 달라지는 문제가 있었습니다.
그래서 뷰와 로직이 변하지 않는 최선의 최신 라이브러리를 찾는 과정도 많은 시행착오를 겪었습니다.
그래도 버전을 올려서 배포하려 했으나 최종적으로 codePush가 지원이 안됐습니다.
위의 사진을 보시면 0.65버전 부터는 7.2 이후의 버전부터 된다고 나와있습니다. 그렇지만 지금 최신 버전은 7.0.5입니다. 그런데 정확히 몇 버전부터 안되는지는 아직 정의되지 않은 듯 합니다. 찾아보니 0.66 도, 0.68 도 된다는 의견도 있고, 0.65 이후로는 안된다는 의견도 있습니다.제가 확인했던 깃허브에서도 논의가 있고 아직 결론은 아무도 모르는 듯 합니다.
https://github.com/microsoft/react-native-code-push/issues/2287
긴글 읽어주셔서 감사하고, 저는 버전업을 포기했습니다!
원래 목표인 Recoil을 붙이고, 최소한의 번들 최적화만 진행하고 배포 대기중입니다.
그런데 배운 것은 많은 것 같습니다.
이 경험이 없었다면 Fabric이라는 아키텍쳐도 더 늦게 알았을 것이고 React 18에 대해서도 더 알 수 있었고, 라이브러리를 보는 법, 그리고 코드를 하나하나 수정하면서 우리 회사에서 만드는 코드의 전반적인 모습도 알 수 있었습니다.
React-Native를 주력으로 사용하고 있지만 아직 메이저 버전이 나오지 않은 이유를 알 것 같기도 합니다!
아직 불안정한 부분이 많고, 더 좋게 바뀌는 부분도 많은 것 같습니다.
그래서 여러분들은 저와 같은 고통 겪지 않으셨으면 좋겠습니다!! flutter로 도망가지 않으셨으면 좋겠습니다!
글에 피드백 주시거나 궁금하신 점 있으시면 댓글 혹은 메일 부탁드립니다
귀중한 경험 공유해주셔서 감사합니다!