Monorepo와 Lerna란 무엇인가?

JungHoon Park
6 min readJun 9, 2021

공통 컴포넌트 패키지를 만드는 과정에서 알아본 개념

https://github.com/lerna/lerna

여러 FE 프로젝트가 진행됐고 중복되는 컴포넌트가 생겼다.

예를 들어 제품 A, B 에서 컴포넌트 C가 쓰이고 C에 어떤 기능 추가 혹은 버그가 발생한다면 A, B에 가서 C를 수정해야 한다. On-premise 제품이라면 버전 A, B, … 가 있으면 각각의 버전에 가서 컴포넌트 C를 수정해야 한다.

그러므로 공통 컴포넌트만을 따로 모아놓은 패키지를 만들고 그곳에서 관리하며 이것들이 필요한 다른 프로젝트에서는 여기서 가져다 쓰는 형식으로 만들어야 한다.

본 시리즈에서는 컴포넌트 패키지를 구현하면서 자연스럽게 주워듣는 Monorepo, Multirepo, Lerna 등 여러 개념을 알아본 후 Lerna를 더 잘 쓰는 방법을 알아본다. 그 후 Monorepo+Lerna를 사용해 컴포넌트 패키지를 구축한다.

목차

  1. 일반적인 폴더 구조
  2. Multirepo의 필요성
  3. Monorepo의 필요성
  4. Lerna의 필요성
  5. Lerna 더 잘 사용하기

1. 일반적인 폴더 구조

Creact-React-App으로 생성하면 아래와 같은 폴더 구조이다.

src/
componenets/
utils/
webpack.config.json
babel.config.json
.prettierrc
package.json
.eslintrc.json
tsconfig.json

이 구조에서는 공통 컴포넌트를 components/ 폴더 하위에 넣어서 사용한다.

// ant-design이 아래와 같은 구조로 되어있다.
src/
componenets/
form/
mega-size-chart/
...
utils/
webpack.config.json
babel.config.json

위 구조에서 webpack 등을 사용해 패키징을 하면 모든 컴포넌트가 하나의 패키지로 나온다. 만약 form 컴포넌트만 쓰고 싶어도 불필요한 나머지 컴포넌트(mega-size-chart) 또한 내려받아 사용해야 한다.

이렇게 되면 tree shaking을 하지 않는 한 script를 불러오는 시간이 증가하게 되고 성능에 악영향을 끼칠 수 있다.

2. MultiRepo의 필요성

위와 같은 이유로 컴포넌트별로 나눈다. 아래와 같이 구조를 설계했다.

MyMegaSizecChart/
src/
index.tsx
utils/
webpack.config.json
babel.config.json
...
MyForm/
src/
index.tsx
utils/
webpack.config.json
babel.config.json
...

이제 form만 내려받아 사용가능하다.🎉

하지만 패키지를 새로 만들 때마다 lint, babel, webpack 등 설정 파일과 utils와 같은 몇몇 중복이 발생한다.

3. Monorepo의 필요성

위의 중복을 처리하기 위해 다시 처음의 구조로 돌아간다.

여기서 빌드와 배포만 각 컴포넌트별로 진행할 수 있으면 되지 않을까?

src/
components/
mega-size-chart/
form/
utils/
webpack.config.json
babel.config.json
.prettierrc
package.json
.eslintrc.json
tsconfig.json

위의 구조에서 webpack, tsconfig와 같은 파일을 빌드 수행 시 컴포넌트별로 진행되도록 값을 수정하면 아래와 같다.

src/
packages/
mega-size-chart/
webpack.config.json
tsconfig.json
form/
webpack.config.json
tsconfig.json
utils/
webpack.config.json
babel.config.json
.prettierrc
package.json
.eslintrc.json
tsconfig.json

여기서 각 패키지에서 공통으로 사용하는 라이브러리는 루트 폴더에 내려받고 패키지별로 필요한 것은 그 아래에 두면 아래와 같은 구조가 된다.

src/
packages/
mega-size-chart/
webpack.config.json
tsconfig.json
package.json
__node_modules__
form/
webpack.config.json
tsconfig.json
package.json
__node_modules__
utils/
__node_modules__
webpack.config.json
babel.config.json
.prettierrc
package.json
.eslintrc.json
tsconfig.json

각 패키지의 __node_modules__에 위치한 중복된 외부 라이브러리는 npm link에서 제공하는 symlinks 기능을 사용하면 된다. symlinks는 바탕화면의 바로가기 아이콘과 같이 설치 폴더가 실제로 그곳에 위치하지 않지만 어디에 있다고 알려주는 링크를 제공한다.

이제 대부분의 중복을 해결했다.

하지만 몇몇 불편한 점이 있다.

  1. 패키지 하나 또는 모두를 배포하기 위해서는 해당 패키지의 경로로 접근해서 npm publish를 입력해야 한다.
  2. 공통 라이브러리를 설치하기 위해 추가로 각 패키지 경로를 이동해 npm link와 같은 명령어를 입력해야 한다.
  3. 각 패키지의 모든 외부 패키지를 설치하고 싶다면 먼저 루트 폴더에서 npm install을 입력후 각 패키지 경로로 이동해 npm install과 패키지 중복 해결을 위해 npm link를 입력해야 한다.

참고로 yarn workspace 기능을 사용하면 2, 3번이 상당히 간단해 질 수 있지만 버그가 있는 것으로 보인다.

4. Lerna의 필요성

Lerna는 Monorepo에서 git, npm과 같은 패키지 매니저를 사용하기 편리하도록 도와준다.

원리는 간단하다. 만약에 패키지를 배포하고 싶다면 npx lerna publish를 루트 폴더 경로의 cmd에 입력한다. 그러면 아래와 같은 일이 벌어진다.

(요약)

  1. 각 패키지를 순회한다.
  2. 마지막 릴리스 이후 업데이트 된 패키지를 배포한다.

공통 라이브러리를 설치하고 싶다면 npx lerna add <package>

모든 외부 패키지를 설치하고 싶다면 npx lerna bootstrap을 입력하면 된다.

5. Lerna 더 잘 사용하기

lerna create <name>

package를 생성한다.

lerna version

package.json의 버전을 올려주고 커밋 메시지에 따라 change log를 만들어준다.

lerna import <path-to-external-repository>

Lerna 이전에 이미 존재했던 패키지의 깃 히스토리를 그대로 Lerna 프로젝트에 가져온다.

lerna run <script>

각 package의 package.json에 지정된 script를 실행시킨다.

--

--