얼마 전에 모노레포를 갈아 엎었다. 원래는 Nx를 사용해서 모노레포를 구성했는데 몇가지 문제점을 느낀 뒤 Yarn Workspaces를 바탕으로 다시 모노레포를 구성했다. 이번 포스팅에서는 왜 모노레포를 구성하려고 했는지, Nx는 어떤 부분이 아쉬웠는지, 어떻게 모노레포를 구성했는지 정리했다.
왜 모노레포?
나는 모노레포를 좋아한다. 블로그에도 몇번 모노레포에 대한 글을 작성했을 정도로 좋아한다. 모노레포의 접근 방식 자체가 힙하다는 생각도 물론 있지만, 코드를 재사용할 수 있도록 나누고 관리할 때 좋은 방식이라고 생각한다.
다노에서는 모 노레포를 사용하지 않았고, 서비스마다 각각의 저장소를 사용하는 아주 일반적인 방법을 사용했다. 다노의 웹 서비스인 마이다노와 다노샵 역시 다른 저장소로 나뉘어져 있었는데, 공통적으로 사용할 수 있는 로직이나 훅, 컴포넌트를 공유하기가 어려웠다. 코드 공유의 필요성을 점점 느끼기 시작했다.
다노에서의 첫 모노레포 시도는 Nx 였다.
Nx Workspace
Nx는 확장 가능한 모노레포를 위한 도구라고 하지만 프레임워크의 성격이 짙다. Nx는 기존 모노레포의 워크플로우를 개선하거나 도와주는 툴이 아니라, 모든 워크플로우를 탑재한 워크스페이스, 저장소를 생성해서 써야 한다.
기존 프로젝트 코드를 모노레포로 이전할 계획은 없었고, 코드 공유를 위한 여러 패키지를 하나의 저장소에서 관리하고 싶었기 때문에 Nx를 사용해서 모노레포를 구성했다.
좋은 점
Nx는 CLI로 많은 기능을 제공한다. 특정 패키지 또는 여러 패키지의 스크립트를 실행하는 기능은 기본이고, 현재 커밋을 기준으로 영향을 받은 패키지에 대해서만 실행할 수 있는 기능도 제공한다.
제공하는 플러그인의 수도 많다. 플러그인을 사용해서 리액트를 사용하는 패키지를 만들 수도 있고 Next.js, NestJS 등 여러 패키지를 사용할 수 있다. 심지어 하나의 저장소에 Angular 프로젝트를 같이 관리할 수도 있다.
아쉬운 점
많은 것을 제공하는 도구를 사용할 때 공통적으로 느끼는 것이다. 해치를 탈출하려고 하는 순간부터 도구가 제공하는 시스템, 인프라는 순식간에 제약사항으로 바뀐다.
나의 경우 기존 프로젝트에 사용할 수 있도록 패키지를 NPM 저장소에 올릴 수 있도록 구성할 때 까지는 큰 문제가 없었는데, 패키지 버저닝을 changesets으로 하기 위해 outputPath부터 lint 설정까지, hack에 가깝게 수정해야 했다.
그래서일까? A 패키지를 참조하는 B 패키지를 작성했는데, 빌드된 B 패키지의 타입 선언 내에서 A 패키지를 절대 경로가 아닌 상대 경로로 가져오도록 만들어지는 문제가 있었다. CommonJS로 만들어진 코드는 괜찮은데 타입이 전부 any로 추론되었다.
여기서도 탄식을 금치 못했으나, CJS 파일이 ES6로 만들어지는 것을 보고 나서야 Nx를 거둬야겠다는 생각을 했다. 아직 IE11을 지원해야 하기 때문이다.
Yarn Workspaces
Nx가 제공하는 기능을 사용해서 귀찮은 초기 작업을 안해보려고 했는데, 결국 돌고 돌아 Yarn Workspaces로 다시 구성했다. 거기에 Lerna, Changesets을 사용했다.
모든 제어 권한이 내 손에 들렸다. 원하는 대로 구성할 수 있는 것이 가장 좋지만, Nx의 편리함은 솔직히 좀 아쉬울 따름이다.
Wrap Up
그럼 이번 포스팅의 결론은 Nx가 구리다일까?
아니다. 개인적으로 하고 있는 다른 프로젝트에도 Nx를 사용하고 있는데 여기서는 아주 만족스럽게 사용하고 있다. 물론 해치를 탈출하려는 시도를 아예 하지 않았기 때문에 그럴 수 있는데, 서버/클라이언트 애플리케이션부터 타입/상수 패키지, 테스트용 모킹 패키지를 같이 사용하고 있다.
결국 일장일단이 있다는 뻔한 이야기인데, 나처럼 모노레포의 니즈가 있고 현재 고민하고 있는 분들을 위해 간단하게 정리하는 것으로 마치겠다.
Nx Workspace
- 기능이 많다.
- 애플리케이션까지 모노레포로 관리한다면 아주 괜찮은 선택이다.
- 해치를 벗어나는 순간 많이 힘들다.
Yarn Workspaces (또는 pnpm, rush 등)
- 초반에 설정하는 것이 귀찮고 번거롭다.
- 나에게 맞는 워크플로우를 구성할 수 있다.