
아직 Mono-Repo나 이를 도와주는 lerna가 널리 알려지지 않을 것 같다.
근래에 Mono-Repo 구축과 lerna를 사용할 기회가 있어서 실제로 반영하고 정보를 수집해 보았다. 모든 lerna 관련된 포스팅을 보았지만 lerna에 관련된 주요 명령어라든가 Mono-Repo에 대한 간략한 설명뿐 예제를 통한 하나의 workflow를 설명하는 곳이 없어서 아쉬움이 있었다.
lerna의 명령어와 설정은 단순하지만, 생각보다 잘 안 되었다. 여러 번의 초기화와 Package 생성을 하면서 어느 정도 감을 잡을 수 있었다.
이렇게 쌓은 노하우 나와 같이 하나의 완성도 있는 절차가 필요하고 개념을 파악하는 사람들에게 도움이 되기를 바라며 정리를 해보았다.
lerna를 알기 전에 Mono-Repo에 대해서 알아야 하고 Mono-Repo를 알기 위해서는 비교 대상인 Multi-Repo를 알아야 한다. 그러므로 해당 포스팅에서는 Multi-Repo와 Mono-Repo를 먼저 정리하겠다.
Multi-Repo는 여러 Repository에 Package를 분산해 두는 것을 의미한다.
말 그대로 하나 또는 두 개 이상의 Package 관리를 하나 또는 두 개 이상의 Repository로 구성하여 관리하는 것이다. 예를 들어 A라는 Package를 구성하는 B, C라는 Package가 있을 경우 B-Repository, C-Repository로 나누어 두는 것을 의미한다.
여기서 꼭 A라는 Package가 없어도 상관없다. 의미 자체를 보면 Package 별로 Repository를 분리한다고 보면 된다. 보통 Package와 Repository는 1:1 관계를 맺으면 통상 사용하는 방식으로 생각하면 된다.
Repository 별 Owner를 지정
빠른 CI Build
패키지의 명확한 분리로 인한 유연성 향상
중복된 설정 및 반복된 설치
eslint와 babel을 설정한다고 하면 모든 Repository에 eslint와 babel을 설치하고 설정 파일을 동일하게 구성해야 한다.이슈의 분산
CHANGELOG 역시 분리됨으로써 관리 포인트가 증가한다.Dependency Hell
중복 코드의 가능성
Multi-Repo와 반대로 Mono-Repo는 여러 Package를 Repository에서 관리하는 것을 의미한다.
Multi-Repo의 단점을 보완하면서 분리된 Package를 하나의 Repository로 합쳐 관리한다. 쉽사리 실 업무에 적용해 보기 쉽지 않기 때문에 Multi-Repo에 비해 활성화가 많이 되지는 않았다.
하지만 점차적으로 오픈 소스에서 지원하는 기능(CLI, UI, Core)이 많아지면서 오픈 소스에서 Mono-Repo를 많이 채택하고 있다.
이러한 Mono-Repo에 대해서 어떤 장단점이 있는지 알아보자.
공통 항목 단일화
쉬운 Pacakge 공유
Multi-Repo의 경우 Pacakge가 분리되어 있기 때문에 Package 간 공유가 쉽지 않다. 그렇기 때문에 중복 코드가 발생할 가능성이 있는데 Mono-Repo의 경우엔 하나의 Repository이기 때문에 패키지 간 공유가 수월하며 중복 코드 역시 발생할 염려가 없다.단일 이슈 트래킹
Mono-Repo로 구성되었다면 그 패키지의 모든 종속된 패키지는 서로 연관 관계를 가질 수 있다. 이로 인해서 이슈 트래킹은 분산 없이 하나로 처리가 가능하며 Mono-Repo는 이를 지원한다.효율적인 의존성 관리
Repository의 거대화
느린 CI Build
Multi-Repo와 반대로 Repository가 하나로 되어 있기 때문에 CI가 하나로 구성된다는 장점이 있지만, Package가 규모가 커짐으로 인해 분산된 CI Build보다 속도가 느릴 수밖에 없다.무분별한 의존성
Dev Tools의 인덱싱 저하
Mono-Repo의 경우는 하나의 개발 도구로 열 경우 해당 Package의 인덱싱 처리 속도가 길어진다.위 내용을 얼추 보면 Mono-Repo의 장점은 Multi-Repo의 단점이고 Multi-Repo의 장점은 Mono-Repo의 단점이라는 것을 알 수 있다. 이렇게 장단점이 교차하기 때문에 무조건 Mono-Repo가 좋은 것은 아니다. 어떤 상황에서는 Multi-Repo가 빛을 발하는 경우가 있을 것이다.
Desktop, Mobile을 지원하며 Vue 기반의 Framework인 Quasar의 구조를 보자. Quasar의 경우 최초에는 Mono-Repo로 구성되어 있진 않았지만, CLI, Extras 등 기능이 확장됨에 따라 Mono-Repo로 구성이 변경되었다.
lerna를 사용하진 않았지만 구조는 Mono-Repo라는 것을 알 수 있다.
사실 Mono-Repo가 활성화되는 시점은 이 사례부터일지도 모른다. 이 사례를 통해서 Mono-Repo를 보장한다고 볼 수 있다.
Mono-Repo로 구성되어있다. 대표적인 예로 Google은 Bazel을 빌드 시스템으로 사용하며, Mono-Repo로 구성되어있다.Mono-Repo Build System인 Pants를 제공하고 있다.Mono-Repo를 구성하는 적절한 시기는 정확하지 않다. 그러므로 구글링 자체로도 정보를 얻기엔 힘든 부분이 있다.
이러한 이유는 모든 프로젝트와 Package의 구조가 규모, 형태, 구조가 모두 다르기 때문이다. 하지만 아래와 같은 상황이라면 Mono-Repo를 구성해도 좋다.
- 서로 다른 패키지가 연관 관계를 가질 경우
- 첫 번째 항목이 고려된 상황에서 N개의 패키지의 형태와 목적이 유사한 경우
- 두 번째 항목이 고려된 상황에서 N개의 패키지 중 배포되어야 할 패키지의 비중이 큰 경우
이 외에 애매한 경우는 Package의 규모이다. Package의 클수록 개발 및 Build 등 Mono-Repo로 투자되는 비용이 많이 들기에 적당한 규모를 찾아야 하는데 이는 설계자 또는 개발자 역량에 따라 다르기 때문에 참고하기 바란다.
지금까지 Mono-Repo에 대해서 알아보았다. 단순하게 Mono-Repo를 알고 구성하는 것은 어렵지 않지만 빠른 Scaffolding과 빠른 배포, 그리고 좀 더 효율적인 종속성 관리가 필요하다.
이를 위해 필요한 기능을 가진 모듈을 찾아 설치하고 설정하고 우리가 원하는 대로 처리 할 수 있고 이런 과정은 lerna가 처리해준다.
즉, Lerna는 Mono-Repo를 위한 CLI 도구이다.
lerna는 Git과 NPM을 사용하여 MOno-Repo 관리와 Workflow를 최적화는 도구이고, 발음상 러나 또는 레르나라고 읽을 수 있다.
lerna의 BI는 Lernaean Hydra 또는 Lerna의 Hydra로 알려진 그리스 신화 생물이고, 많은 머리를 가지고 있었는데 그 정확한 수는 출처에 따라 다르다. 가장 최근의 Hydra 신화에서는 괴물에 재생 능력이 추가되어 머리가 잘릴 때마다 머리가 두 개씩 다시 자라난다고 한다.
lerna의 특징은 Mono-Repo를 구성하고 배포하는 데 중점에 둔 기능으로 볼 수 있다.
다중 패키지의 종속성 관리 및 모듈의 중복성 제거
다중 패키지의 단일 버전 및 독립적 버전 관리
Mono-Repo의 구성을 따르면 여러 개의 Package로 구성된다. 이런 Package는 어떤 상황에서 하나의 버전 정책을 가져갈 수 있지만, 또 어떤 경우에는 서로 독립적인 버전 정책을 가져가야 하는 경우가 있는데 lerna는 이러한 기능을 지원하여 버전 정책을 정할 수 있다.변경된 패키지를 일괄적으로 GIt Remote Repository에 Push
lerna를 사용하면 단 한 번의 commit과 push로 Remote Repository에 반영할 수 있다.변경된 패키지를 일괄적으로 NPM Repository에 Publish
publish 명령을 사용한다. Mono-Repo에서 각 Package를 NPM Repository에 배포하기 위해서 하나하나 publish를 입력할 필요가 없다. 단 한 번의 publish로 변경 사항이 있는 Package만 배포가 될 것이다.앞서 설명했듯이 이미 많은 오픈 소스들이 Mono-Repo를 채택하였고 이를 위해 lerna를 사용하였다. 그렇다면 Lerna를 실제로 사용한 오픈소스에는 어떤 것들이 있을까?
- Babel
- vue-cli
- jest
- nuxt.js
- nuxt.js
- webdriverio
- create-react-app
- create-nuxt-app
- webpack-cli
- graphql-server
- typescript-eslint
- react-router
Bable은 lerna 도입의 선두주자이고 bable의 파생 모듈은 매우 많기 때문에 Mono-Repo로써 적합하다고 볼 수 있다.
위 오픈 소스 중 대표적으로 CLI 구성을 가진 vue-cli와 CLI 및 Template 구성을 가진 create-nuxt-app의 Mono-Repo의 구조를 파악해보자.
OpenBase-lerna에서 lerna의 활동 상태를 확인해보자.
현재 3.22.x 버전까지 릴리즈가 된 상태이고 다운로드 수와 start가 점차 증가하는 것을 볼 수 있다. 하지만 개인적으로 버전이 3.x에 비해 안정적이진 않다고 생각된다. 아래에서 설명을 할 테지만 현재로서는 npm보다 yarn에 적합함을 보이고 이슈 생성과 PR의 요청이 많을 것으로 보아 앞으로 개발이 필요한 사항이 많아 보인다.

lerna의 기본 구조는 Root 경로 아래 packages 폴더가 있고 그 하위에 각 package 별 폴더가 생성된다. pacakges 폴더는 기본값이며 설정에 따라 유동적으로 변경할 수 있다. 각 pacakge 폴더는 package 별 이름을 지정할 수 있으며 각각의 packages에는 package.json이 명시되는 것처럼 하나의 모듈로 간주한다.
Root 경로에 있는 pacakge.json에는 모든 package가 공통으로 사용되는 dependencies가 명시되는 등 공통 항목이 나열된다.

단일 버전 라인에서 작동하며 관리독립적으로 패키지 버전을 관리
그림에서 보듯이
hoist를 통해서 node_module을 최적화하여 중복되는 모듈을(B (1.0)))을 최상위로 재구축한다.
Yarn Workspace의 목적은 Mono-Repo의 Workflow를 용이하게 하는 것이다.lerna의 핵심 명령어는 bootstrap과 publish이다. bootstrap을 통해서 모든 package에 node module을 설치하며 최적화를 통해 중복된 모듈을 정리해준다. publish는 npm publish와 동일한 기능을 하지만 lerna에서는 모든 pacakge를 대상으로 한 번의 명령어로 배포할 수 있다.
이 외의 핵심 명령어는 아래 내용을 참고하고 자세한 옵션은 Lerna 공식 사이드를 참고하자.
lerna clean
$ lerna clean
lerna bootstrap
$ lerna bootstrap
lerna run
$ lerna run
lerna publish
$ lerna publish
lerna exec
$ lerna exec
해당 글의 원본은 Dev.DY에서 확인할 수 있습니다.