[개발] 모노레포에서 react-native의 모듈 해석

Eunho Lee
10 min readJan 31, 2021

--

기존의 멀티 repo로 관리하면서 생기는 관리 소요와 남발되는 중복 코드를 줄이고, 여러 레포를 단일 레포에서 관리하기 때문에 갖게 되는 통일된 워크플로우 장점으로 인해 모노레포로의 전환을 하는 케이스가 늘어나는 것 같다.

javascript 기반의 웹 프로덕트와는 다르게 native 코드가 연관된 react-native에서는 코드베이스에 따라 모듈 해석에 대한 방식이 달라 셋업 과정에서 고초를 겪을 수 있다.

생각보다 더 강력한 놈이었다

쉽게 생각하고 접근했던 이 작업 과정에서 생각보다 많은 것들에 대한 이해가 필요했고, 그 중에 가장 헷갈렸던 개념들에 대한 소개와 해결에 대한 예시, 그리고 내가 생각한 모노레포에서의 한계점에 대해 이야기 해보려고 한다. 아무쪼록 조금이라도 당신의 시간을 아껴줄 수 있는 좋은 글이 되길 바란다.

시작하기에 앞서 react-native에서 javascript 모듈 해석을 이해하기 위해서 몇 가지 짚고 넘어가면 좋을 개념들이 있다.

yarn workspace hoist

yarn workspace를 통해 패키지 관리를 한다면 관리하는 패키지들의 디펜던시들이 hoist 되어 루트 모듈에 모이는 것을 확인할 수 있다. 다만 모든 디펜던시들이 hoist 되는 것이 아니고 중복이 일어나지 않는 패키지들만 올라오게 된다. 해당 패키지에서만 사용된다면 같은 레벨의 path의 node_modules에 설치가 된다.
yarn 에서는 기본적으로 hoist 가 적용되고 원하지 않는다면 nohoist 옵션을 통해 선택적으로 모듈 hoist 를 막을 수 있다.

javascript의 모듈 해석 알고리즘

Loading from node_modules folders 섹션에서 어떤 식으로 이루어지는 지 확인할 수 있다. 여기에 간단히 정리해보자면 코어 모듈이거나 상대 경로를 통해 접근하는 모듈이 아니라면 해석을 시도하는 파일과 같은 레벨의 path의 node_modules 폴더를 탐색하고, 해당 폴더에 타겟 모듈이 없다면 부모 레벨의 path의 node_modules 폴더를 탐색하게 된다. 그렇게 루트 레벨까지 탐색을 시도한 후 모듈을 로드하거나, 에러를 발생시킨다.

native 모듈의 javascript 모듈 해석

react-native에서는 javascript와 native 코드 간의 참조 관계를 통해 모듈이 설치가 되거나 실행이 된다. 즉, native 모듈에 대한 정보들이 javascript 모듈에 의해 해석이 되는 것이다. ios/ 혹은 android/ 에는 각 플랫폼에 대한 네이티브 코드들이 작성되어 있고 그 내부에는 javascript 모듈인 react-native 모듈을 해석하기 위한 코드들이 들어있다.
네이티브 코드에선 javascript의 모듈 해석 알고리즘과는 다르게 정적인 해석을 시도한다. 지정된 path에 대한 접근 후 성공 혹은 실패하게 된다.

native의 node_modules에 대한 해석은 javascript와 다르다

react-native 모듈의 hoisting

이해를 위해 예시를 하나 준비했다. RN-app-0102, 그리고 두 앱 간의 공용 컴포넌트 관리를 위해 Shared-components 라는 패키지를 관리한다고 할 때 다음과 같은 구조를 가정해 볼 수 있다.

root/ 
├─ node_modules/
├─ pacakges/
│ └─ RN-app-01/
├─ └─ pacakge.json
│ └─ RN-app-02/
├─ └─ pacakge.json
│ └─ Shared-components/
├─ ...└─ pacakge.json
└─ package.json

yarn workspace + lerna bootstrap을 통해 모듈 설치를 하게 되면 패키지 간의 중복 모듈들이 모두 root/node_modules에 hoist 될 것이다. 이런 경우 RN-app-0102react-native 모듈은 root/packages/RN-app-**/node_modules가 아닌 root/node_modules에 설치된다.

native 파일에서의 node_modules 폴더 참조

iOS의 Podfile에는 xcode 프로젝트의 native 디펜던시 상세가 담겨 있다. 살펴보면

// root/packages/RN-app-01/ios/Podfile
platform :ios, '9.0'
require_relative '../../../node_modules/@react-native-community/cli-platform-ios/native_modules'target 'RNApp01' do# Pods for RNApp01pod 'FBLazyVector', :path => "../../../node_modules/react-native/Libraries/FBLazyVector"pod 'FBReactNativeSpec', :path => "../../../node_modules/react-native/Libraries/FBReactNativeSpec"
...

이런 식으로 매핑이 이루어지고, pod install 을 하게 되면 javascript 모듈을 참조하여 네이티브 코드가 설치된다.

react-native 프로젝트를 init하게 되면 node_modules의 path 정보는 기본적으로 현재 프로젝트의 루트를 바라보게 된다.(../node_modules) 하지만 모노레포 구조에서 이 path를 사용하게 되면 없는 모듈을 참조하게 되기 때문에 hoisting 된 path 정보를 사용해야한다. (../../../node_modules)

이 글에서는 문제에 대한 간략한 개념만 소개하기 때문에 native의 상세한 설정에 대한 정보는 아래의 레퍼런스 문서에서 확인하길 바란다.

metro bundler 설정 변경

metro bundler 에 대한 설정에도 변화가 생긴다.

// metro.config.js
...
module.exports = {
projectRoot: path.resolve(__dirname, '../../'),
...
}

이렇게 경로값이 root 경로를 향해 업데이트 된다.

모노레포에서의 한계(RN)

전반적으로 모노레포의 컨셉에 대해 긍정적으로 평가한다. 하지만 기존 멀티 레포의 관리 구조에서 모노레포의 구조로의 변경을 할 때는 정말 힘든 과정이었다. 이 과정에서 몇 가지 모노레포 전환에 대해 한계가 있었다.

첫 번째, 정보가 부족하다.

lerna의 공식 문서, 그리고 github를 보면 아쉬운 점이 참 많다. 그리고 lerna — help 를 통해 출력되는 무수한 옵션들에 대한 설명도 문서에서 많이 누락 되었다. 깃허브에 가보면 알겠지만 contributor가 253명이지만 거의 혼자서 작업을 하는 것 같다. 이 참에 문서 보강을 위해 참여를 해볼까 싶다.

또한 대다수의 정보들이 웹에 집중되어있는 부분도 초기 허들이 높은 이유가 될 수 있다고 본다. 이 글을 쓰기까지 상당히 많은 검색을 했어야 했고 누군가 예제를 만들어주지 않았다면 완수하기 힘들었을 것이다.

두 번째, 디펜던시 중복 제거에 대한 부분이다.

이 모든 과정은 yarn install 혹은 lerna bootstrap 커맨드 하나로 완성이 된다. 따분하게 디펜던시 설치를 기다리다가 설치가 되어 앱을 빌드하려 하면 올바르지 않은 경로로 인해 빌드 fail이 생기고 다시 디버깅을 하며 수정해야했다. 이미 프로덕션 레벨의 앱이었기 때문에 상당히 많은 디펜던시를 가지고 있는데 어떤 것들이 루트로 올라오고 어떤 것들이 패키지 루트에 남아있는지 알 수 없다. 알 수 있는 한 가지 방법은 node_modules 폴더를 뒤져서 비교해보는 것이다. 그리고 왜 이 곳에 설치가 되었는가를 package.json 파일을 열어보고 고민하는 것이다.

수 없이 lerna cleanlerna bootstrap을 하다보면 노트북을 타고 날아갈 수 있을 것만 같은 느낌이 들게 될 것이다. 발열은 덤이다. 이 중복에 대한 레포트라도 파일로 하나 떨구어 주면 좋을 듯 싶은데 아직은 없는 것 같다.

lerna link convert 라는 스크립트가 있다. 이 커맨드를 통해서 관리 패키지의 모든 devDependencies를 루트로 모아주는 것인데 같은 모듈의 다른 버전 간의 중복제거가 어떤 식으로 되는지 아직도 확인을 하지 못했다. 이미 패키지의 package.json 파일에는 devDependencies 항목이 삭제되어버리고 루트로 이동했기 때문에 어떤 버전이 설치되었었는지도 git log를 통해 알 수 밖에 없다. 재미있는 사실은 이 스크립트도 공식 문서에는 없고 어디에서 구한 정보인지 떠돌다가 이슈에까지 등장했다는 점이다.

혼자 작업을 한다면 모노레포와 현재 프로젝트가 가지고 있는 한계 혹은 문제에 대해서 파악하고 있기 때문에 문제가 없겠지만 여러 사람이 작업을 한다면 이 부분에 대한 공유가 굉장히 중요할 것으로 보인다.

하지만 이러한 한계점을 통해 다수의 레포로 관리하며 분산되었던 설정이나 코드 스타일, 로직 등을 다시 한번 점검 할 수 있는 계기가 될 수 있을 것이란 점은 확실하다. react-native-web 에 대한 발전이 가속화되고 있는 시점에서 모노 레포를 통해 웹, 모바일, 데스크탑에 이르는 모든 플랫폼에 대한 코드베이스를 하나로 집중하는 패턴도 많이 보였다.

react-native, flutter 와 같은 라이브러리들의 발전과 함께 모노레포가 가지고 있는 단점들도 보완될 것이기 때문에 더 좋은 환경이 되지 않을까 생각해본다.

Reference

--

--