Monorepo? Yarn Workspace!

🌸 모노레포. Lerna? Yarn Worksapce!

서론

모노레포라고 들어본적 있다면 거쳐온 단어들이 있을꺼라 생각된다.

Lerna, Git repo, Git submodule…

그럼에도 불구하고 잘 되지 않는다. 모노레포가 되었다 한들 번잡하다. 이에 대한 내가 도착한 해법에 대해 글을 남기고자 한다.

코드 파편화

코드를 짜다보면 매 프로젝트마다 비슷한 코드를 많이 짜게 된다. npm에 배포하긴 뭐하고, 그러다보니 다른 프로젝트를 시작할때 다시 짜서 조금 개선되거나 아님 이전의 코드를 가져와서 파편화되는 경우가 꽤 있었다.

회사는 보통 한가지 도메인을 가지고 있기 때문에 그 도메인에 맞는 모듈이 필요하게 되는데 이게 회사내에서의 여러 프로젝트에 쓰이게된다. 이 모듈은 프로젝트가 진행되어감에 따라 함께 성숙(검증되어)해 가는데 처음부터 이게 공통 모듈이 될 것이라는 걸 알고 있으면 레포 구성에 고민이 생기게된다. 처음부터 레포를 분리해 버리면 번잡하다. 생산성이 희생된다며 주객이 전도된게 아니겠는가. 모노레포 니즈의 발생이다.

모노레포

내가 아는(삽을 떠본) 모노레포를 구성하는 방법에는 3가지 정도가 있었다.

Lerna

러나는 모노레포와 함께 각 패키지를 배포하는 영역까지를 커버한다. 여러개의 레포가 함께 엮여서 개발을 진행하고 하나의 버전으로 갈 것인지, 패키지마다 다른 버저닝을 갖게 할 것인지.

오래되서 기억은 잘 나지 않지만 뭔가 계속적으로 npm install 을 해야하는 등 대단히 짜증나는 일이 많았던 것으로 기억난다.

러나는 Yarn workspace 설명에도 나와있듯이 함께 쓸 수 있으며 배포 레벨에서 콜라보를 할 수 있다. 배포는 또 다른 주제이므로 이 글에서는 제외된다.

Git-repo

구글에서 관리하는 CLI 툴, 지인을 통해 들었는데 써보진 않다. 컨셉 자체는 여러 레포를 한번에 땡기고 푸시 풀하는 등, 하나의 레포처럼 사용하는 컨셉으로 이해했다. 어찌보면 진정한 의미의 모노레포일진 모르겠으나 너무 모노레포다(?).

Git Submodule

깃의 커맨드읜 submodule을 통해 특정 디렉토리의 위치를 특정 레포로 매핑한다. 시작시에 git submodule init 등의 명령어를 통해 submodule 을 클론하게 된다.

IDE 가 그 디렉토리에 대한 파일은 다른 레포라는 것을 인식해줘야한다. 그럼 메인 레포와 submodule 의 레포에 있는 파일이 동시에 수정이 되었다고 가정해보자 우리는 커밋을 할테데 순서는 이렇게 진행 되어야한다.

  • 서브 모듈에 커밋을 한다
  • 서브 모듈을 가리키는 디렉토리의 커밋 ID가 바뀐다.
  • 바뀐 디렉토리의 커밋 ID를 메인 레포에서 변경된 것으로 처리하고 수정된 파일과 함께 커밋한다.

이런 상태가 되어야하는데 이게 잘 안되었던 것 같다. 그때 정리했던 글을 찾아봤다.


2016년 4월 12일 인걸 보니 회사를 그만두고 8개월동안 잠만자다 일어나서 재취업을 했던 그 때인 것 같다. 깃을 처음 실무에서 써보던 때인데 프로젝트 시작하면서 레포 구성에 대해서 고민했었나 보다.


여튼 당시 기억으로는 IDE에서 지원이 뭔가 완벽하지 않았고 터미널을 통해서 작업을 해야했으며 다른 환경에서 작업할때 git submodule init 을 잊어서 왜 안되는지 찾았던 적도 있다.

🌸 Yarn Workspace

아마도 많은 개발자들이 비슷한 고민을 같이 했을테데 이러한 기능이 패키저 매니저 레벨에서 지원되기 시작했다. Yarn Workspace다.

Yarn

Yarn은 노드 패키지 매니저인 npm을 보완(?) 해서 페이스북에서 관리하는 패키지 매니저다. 지난 정리 글을 찾아보니 2016년에 나온 것으로 보인다.

1.0때에 처음 workspace 기능이 추가 되었다(글을 쓰고 있는 시점은 1.6) .

Workspace

모노레포의 각 패키지마다 가지고 있는 node_module 가 루트의 node_modules 를 참조하도록 되있으리라. 내부 구현의 이해가 목적이 아닌만큼 사용법에 초첨을 맞추고 글을 써내려가겠다.

사용법

Init

처음부터 설명을 한다는 가정으로 프로젝트를 생성할 때 반드시 private: true 가 포함되어야한다.

yarn init

그리고 workspace 를 추가해야한다.

Workspace

package.json 을 열고 workspace 프로퍼티를 추가한후에 어레로 패턴을 추가한다. glob 패턴(확인해보진 않음) 을 지원하니 아래와 같이 추가를 하자 패키지들이 들어갈 디렉토리의 이름은 packages 로 가정했다.

define workspaces

workspace 를 사용하는 프로젝트가 되었다.

패키지 생성

packages 디렉토리 밑에 패키지의 이름을 생성하고 프로젝트와 같은 방식으로 package.json 을 생성하자. 이 패키지는 private 이 필요없는 일반적인 패키지다.

패키지 관리

생성을 한 후에는 패키지를 추가하기 위해 그 프로젝트 디렉토리로 들어갈 필요없이 프로젝트 루트에서 관리를 할 수 있다.

workspace 레벨에서 sub package를 관리한다.

이런식으로 yarn의 커맨드 이후에 workspace [패키지명] 을 붙이고 작업을 하면된다. 모든 프로젝트에 공통적으로 적용을 해도 배포와 이슈가 분리된다라고 하면 workspace 자체에 패키지를 추가 할 수도 있다. 예를 들어 typescript 같은 경우가 여기에 해당한다. typescript로 작성된 모듈의 경우에는 컴파일 이후 js로 배포되기 때문이다. 이런 경우에는 -W 옵션을 추가하면 프로젝트 루트의 package.json 을 통해 관리된다.

-W 옵션은 workspace 레벨에서의 종속성 관리 선언을 한다.

이렇게 하면 루트 package.json 에 dependency 가 기록되고 관리된다.

패키지 참조

그럼 이제 상호 패키지는 어떻게 참조하느냐 정도의 문제가 남았다. 생각보다 단순하며 이 작업에 대해 yarn 커맨드가 존재하는지 찾아보지 않아서 모르겠다.

있다면 피드백을 부탁드린다.

예를 들어 패키지 구조가 아래와 같이 있다. readme 패키지에서 speech 패키지를 사용하고 싶다라고 한다면 readme/package.json 파일을 열고 의존성을 추가해주면 된다.

speech는 packages/speech 아래 구현되어 있어야한다.

주의할 점은 의존성 추가시 사용되는 이름이 package.json에 사용된 패키지의 name 과 같아야만 한다.

그리고 루트에서 다시 yarn 을 통해 인스톨을 다시하면 node_modules 를 제대로 참조하게 된다.


글을 위해 샘플 레포를 만들진 않았으니 최근 진행한 펫 프로젝트에서 초기에 구조를 잡았던 커밋 아이디를 참조한다.

디렉토리 구조는 대충 아래와 같다.

https://github.com/deptno/readme

마무리

TypeScript 까지의 연계를 설명하려 하였으나 코드를 제공했고 생각보다 시간이 소모되어 여기서 마무리한다. 같은 고민을 하는 다른 분들도 볼 수 있도록 👏 부탁드린다.