콘텐츠 대표 이미지 - JavaScript 모노레포 완전정복: Lerna와 Nx, 누가 내 프로젝트의 구원자가 될 것인가? 🚀

JavaScript 모노레포 완전정복: Lerna와 Nx, 누가 내 프로젝트의 구원자가 될 것인가? 🚀

복잡한 프로젝트 구조에 지친 당신을 위한 모노레포 가이드. Lerna와 Nx의 세계로 함께 떠나봐요!

들어가며: "내 코드는 어디에...?" 프로젝트 지옥에서 탈출하기

안녕, 개발자 친구! 👋 오늘도 수많은 코드와 폴더 속에서 길을 잃진 않았어? 상상해봐. 우리가 엄청난 규모의 서비스를 만든다고.
사용자들을 위한 웹 프론트엔드, 관리자용 어드민 페이지, 모바일 앱을 위한 API 서버, 그리고 이 모든 곳에서 공통으로 사용하는 UI 컴포넌트 라이브러리와 유틸리티 함수들까지... 생각만 해도 머리가 지끈거리지? 🤯

이런 상황에서 보통 우리는 각 프로젝트를 별개의 저장소(Repository)에 나눠서 관리해. 이걸 '폴리레포(Polyrepo)' 또는 '멀티레포(Multi-repo)'라고 불러. 각자 독립적으로 움직이니 깔끔해 보일 수 있지. 하지만 곧 현실의 벽에 부딪히게 돼.

예를 들어, 공통 UI 컴포넌트 라이브러리에서 버튼 디자인을 살짝 바꿨다고 해보자. 그럼 어떻게 해야 할까?

1. UI 컴포넌트 라이브러리 코드를 수정하고, 버전을 올리고, npm에 배포한다.
2. 웹 프론트엔드 프로젝트로 가서 방금 배포한 새 버전으로 업데이트한다.
3. 관리자 어드민 프로젝트로 가서 또! 새 버전으로 업데이트한다.
4. 각 프로젝트마다 테스트를 다시 돌리고, 빌드하고, 배포한다.

버튼 하나 바꿨을 뿐인데... 해야 할 일이 산더미야. 만약 여러 프로젝트에 걸친 큰 변경사항이라면? 아, 상상하고 싶지도 않다. 😫 이런 '파편화된 관리'의 고통 속에서 우리를 구원해 줄 개념이 바로 '모노레포(Monorepo)'야.

모노레포는 말 그대로 '하나(Mono)의 저장소(Repo)'에 관련된 여러 프로젝트를 모두 담아 관리하는 방식이야. 구글, 페이스북, 마이크로소프트 같은 거대 테크 기업들이 이미 오래전부터 사용해 온 방식이지. "어? 그거 그냥 한 폴더에 다 때려 넣는 거 아냐? 더 복잡해지는 거 아니야?" 라고 생각했다면, 반은 맞고 반은 틀려. 제대로 된 도구와 함께라면, 모노레포는 폴리레포의 단점을 해결하고 개발 경험을 극적으로 향상시키는 강력한 무기가 될 수 있어.

이 글에서는 바로 그 '제대로 된 도구', JavaScript 생태계의 모노레포 관리 양대산맥인 Lerna와 Nx에 대해 아주 깊고, 자세하고, 또 재밌게 파헤쳐 볼 거야. 둘의 차이점은 뭔지, 내 프로젝트에는 어떤 게 더 어울릴지, 그리고 실제로 어떻게 사용하는지까지! 이 글을 다 읽고 나면, 당신도 모노레포 전문가가 되어 "아, 그거? 이렇게 하면 돼~"라고 자신 있게 말할 수 있게 될 거야. 자, 그럼 시작해볼까? Let's go! 🚀

1장: 모노레포의 원조 맛집, Lerna 파헤치기 📜

모노레포의 세계에 발을 들였다면 가장 먼저 듣게 될 이름, 바로 Lerna야. Lerna는 "다두아(多頭蛇)"라는 뜻의 그리스 신화 속 히드라 이름에서 따왔다고 해. 여러 개의 머리(프로젝트)를 가진 뱀처럼, 여러 패키지를 하나의 저장소에서 관리한다는 의미를 담고 있지. 꽤나 직관적이지? Lerna는 오랫동안 JavaScript 모노레포의 표준처럼 여겨져 왔어.

Lerna의 핵심 철학: "패키지 관리를 쉽게!"

Lerna의 주된 목표는 두 가지로 요약할 수 있어.

1. 부트스트래핑(Bootstrapping): 여러 패키지들의 의존성을 한 번에 설치하고, 로컬 패키지들끼리 서로 연결(symlink)해주는 것.
2. 퍼블리싱(Publishing): 변경된 패키지들의 버전을 관리하고, npm 같은 패키지 저장소에 배포하는 과정을 자동화하는 것.

즉, Lerna는 '여러 패키지를 만들고, 개발하고, 배포하는' 라이프사이클 전체를 편리하게 만들어주는 데 초점을 맞추고 있어. 그럼 Lerna를 직접 사용해보면서 그 매력을 느껴보자.

Lerna 프로젝트 시작하기: `lerna init`

먼저 Lerna를 설치해야겠지? 전역으로 설치해주자.

npm install -g lerna

그리고 새로운 프로젝트 폴더를 만들고, 그 안에서 `lerna init` 명령어를 실행해봐.

mkdir my-lerna-repo
cd my-lerna-repo
lerna init

이 명령어를 실행하면 마법처럼 몇 개의 파일과 폴더가 생겨날 거야.

my-lerna-repo/
├── packages/
├── lerna.json
└── package.json

하나씩 살펴보자.
- `packages/`: 이곳이 바로 우리의 개별 프로젝트(패키지)들이 살아갈 공간이야. 앞으로 만들 모든 패키지는 이 폴더 안에 위치하게 돼.
- `package.json`: 모노레포 전체의 정보를 담는 루트 `package.json` 파일이야. 여기에 Lerna 같은 개발 의존성을 추가하게 되지.
- `lerna.json`: Lerna의 설정 파일이야. Lerna의 동작을 제어하는 모든 옵션이 여기에 담겨. 버전 관리 방식이나, `packages` 폴더의 위치 등을 설정할 수 있어.

초기 `lerna.json` 파일은 이렇게 생겼을 거야.

{
  "packages": [
    "packages/*"
  ],
  "version": "0.0.0"
}

`packages` 속성은 Lerna가 관리할 패키지들이 어디에 있는지 알려주는 경로 패턴이야. `version`은 현재 모노레포의 전체 버전을 나타내지.

새로운 패키지 만들기: `lerna create`

자, 이제 `packages` 폴더 안에 우리의 첫 번째 패키지를 만들어보자. `header` 컴포넌트와 `footer` 컴포넌트를 각각의 패키지로 만들어볼게.

lerna create header -y
lerna create footer -y

`-y` 옵션은 귀찮은 질문들을 모두 'yes'로 자동 응답해주는 고마운 친구야. 😉

이제 `packages` 폴더를 보면 `header`와 `footer`라는 두 개의 폴더가 생겼고, 각각의 폴더 안에는 독립적인 `package.json` 파일이 들어있을 거야. 완벽하게 분리된 두 개의 패키지가 탄생한 거지!

Lerna의 마법, `lerna bootstrap`과 의존성 관리

이제 진짜 Lerna의 강력함이 드러나는 순간이야. 만약 `header` 패키지가 `lodash`라는 라이브러리를 필요로 하고, `footer` 패키지가 `moment`를 필요로 한다고 가정해보자. 그리고 `header`와 `footer`를 사용하는 `website`라는 메인 애플리케이션 패키지도 하나 만들어볼게.

lerna create website -y

이제 각 패키지의 `package.json`에 의존성을 추가해줘야 해. 하지만 일일이 폴더에 들어가서 `npm install`을 할 필요는 없어. Lerna가 더 스마트한 방법을 제공하거든.

`lerna add` 명령어를 사용하면 특정 패키지 또는 모든 패키지에 의존성을 쉽게 추가할 수 있어.

# header 패키지에만 lodash 추가
lerna add lodash --scope=header

# footer 패키지에만 moment 추가
lerna add moment --scope=footer

# website 패키지에 header와 footer를 의존성으로 추가
lerna add header --scope=website
lerna add footer --scope=website

여기서 `--scope` 플래그가 핵심이야. 어떤 패키지에 의존성을 추가할지 지정해주는 거지. 마지막 두 명령어를 주목해봐. `website` 패키지에 우리가 방금 만든 `header`와 `footer`를 추가했어. npm에 배포되지도 않은 로컬 패키지를 어떻게 추가할 수 있었을까?

바로 이 지점에서 `lerna bootstrap`이 등장해.

lerna bootstrap

이 명령어를 실행하면 Lerna는 다음과 같은 놀라운 일들을 처리해줘.

1. 모노레포 내의 모든 패키지를 순회하며 `package.json`의 `dependencies`와 `devDependencies`를 확인해.
2. 필요한 모든 외부 라이브러리(lodash, moment 등)를 설치해. 이때 호이스팅(Hoisting)이라는 기술을 사용해. 중복되는 의존성들을 루트 레벨의 `node_modules` 폴더로 끌어올려서(hoist) 설치해줘. 이렇게 하면 디스크 공간을 절약하고 설치 속도도 빨라져. 👍
3. 가장 중요한 것! `website` 패키지가 의존하는 `header`와 `footer` 패키지를 `website/node_modules` 폴더 안에 심볼릭 링크(Symbolic Link)로 연결해줘. 덕분에 `website` 코드는 마치 npm에서 설치한 것처럼 `import Header from 'header'` 같은 코드를 사용할 수 있게 돼. 하지만 실제로는 로컬에 있는 `packages/header` 폴더를 직접 참조하고 있는 거지. 이게 바로 모노레포 개발 경험의 핵심이야! `header` 코드를 수정하면 별도의 빌드나 배포 과정 없이 `website`에 즉시 반영돼.

💡 잠깐, 호이스팅(Hoisting)이 뭐지?
만약 `header`와 `footer`가 둘 다 `lodash@4.17.21` 버전을 사용한다고 해보자. 호이스팅이 없다면 `packages/header/node_modules`와 `packages/footer/node_modules`에 각각 `lodash`가 설치될 거야. 중복이지. Lerna는 이걸 감지하고, 루트 폴더의 `node_modules`에 `lodash`를 딱 한 번만 설치해. 그리고 각 패키지에서는 이 공유된 `lodash`를 사용하게 되는 거야. 이걸 '의존성을 끌어올린다'고 해서 호이스팅이라고 불러.

버전 관리 전략: Fixed vs. Independent

Lerna의 또 다른 강력한 기능은 버전 관리야. `lerna.json`에서 어떤 전략을 사용할지 선택할 수 있어.

1. Fixed/Locked Mode (기본값)

이 모드에서는 모든 패키지가 단일 버전 번호를 공유해. `lerna.json`의 `version` 필드가 바로 그 버전이야. `lerna publish`를 실행하면, 변경사항이 있는 패키지들만 실제로 배포되지만, 모노레포 내의 모든 패키지의 `package.json` 버전이 동일하게 업데이트돼. Babel, React 같은 대규모 프로젝트들이 이 방식을 사용해.

  • 장점: 모든 패키지가 한 세트처럼 움직여서 버전 관리가 직관적이고 쉬워. "우리 서비스는 2.5.0 버전이야" 라고 말하면 명확하지.
  • 단점: 아주 사소한 변경(예: 오타 수정)이 하나의 패키지에서만 발생해도, 관련 없는 다른 모든 패키지들의 버전까지 덩달아 올라가.

2. Independent Mode

이름 그대로 각 패키지가 독립적인 버전 번호를 가져. `lerna.json`에서 `version` 필드를 `"independent"`로 설정하면 돼.

{
  "packages": [
    "packages/*"
  ],
  "version": "independent"
}

`lerna publish`를 실행하면, Lerna가 변경된 패키지들을 감지하고 각각에 대해 어떤 버전으로 올릴지(major, minor, patch 등) 물어봐. 각자 다른 버전으로 배포할 수 있지.

  • 장점: 패키지별로 버전을 유연하게 관리할 수 있어. 변경되지 않은 패키지는 버전이 그대로 유지되므로 더 의미 있는 버전 관리가 가능해.
  • 단점: 패키지 간의 의존성 관계가 복잡할 때, 어떤 패키지의 어떤 버전이 서로 호환되는지 추적하기가 어려워질 수 있어.
🤔 어떤 전략을 선택해야 할까?
만약 당신의 패키지들이 서로 긴밀하게 연결되어 있고, 함께 배포되어야 하는 하나의 제품군(예: React와 ReactDOM)이라면 Fixed Mode가 좋아. 반면, 각 패키지가 비교적 독립적인 라이브러리 모음(예: lodash의 각 함수들)이라면 Independent Mode가 더 적합할 수 있어. 프로젝트의 성격을 잘 파악하는 게 중요해!

명령어 실행: `lerna run` & `lerna exec`

모노레포에서는 여러 패키지에 걸쳐 동일한 명령어를 실행해야 할 때가 많아. 예를 들어, 모든 패키지를 한 번에 테스트하거나 빌드하는 경우지. Lerna는 이를 위한 편리한 명령어를 제공해.

`lerna run

이 명령어는 각 패키지의 `package.json`에 정의된 npm 스크립트를 실행해줘. 예를 들어, 모든 패키지에 `test` 스크립트가 있다면,

lerna run test

이 한 줄로 모든 패키지의 테스트를 동시에 실행할 수 있어. `--stream` 옵션을 추가하면 각 패키지의 출력 결과를 실시간으로 볼 수 있고, `--parallel` 옵션을 사용하면 병렬로 실행해서 속도를 높일 수도 있어.

`lerna exec -- `

`run`이 npm 스크립트를 실행하는 거라면, `exec`는 어떤 임의의 쉘 명령어도 실행할 수 있게 해줘. 예를 들어, 모든 패키지의 `node_modules` 폴더를 지우고 싶다면?

lerna exec -- rm -rf node_modules

정말 편리하지? `--` 뒤에 실행하고 싶은 명령어를 자유롭게 적으면 돼.

대망의 배포: `lerna publish`

자, 열심히 개발하고 테스트도 마쳤어. 이제 세상에 우리의 패키지를 선보일 시간이야. `lerna publish` 명령어는 이 복잡한 과정을 한 번에 처리해줘.

lerna publish

이 명령어를 실행하면 내부적으로 다음과 같은 일들이 순차적으로 일어나.

1. 현재 브랜치에서 마지막 태그 이후로 변경된 패키지가 있는지 확인해.
2. 변경된 패키지 목록을 보여주고, 어떤 버전으로 올릴지 물어봐. (e.g., `patch`, `minor`, `major`, or a specific version)
3. 선택한 버전에 따라 `lerna.json`과 변경된 패키지들의 `package.json` 파일의 버전을 업데이트해.
4. 업데이트된 파일들을 Git에 커밋하고, 새로운 버전 태그를 생성해.
5. 변경된 패키지들을 npm 저장소에 배포(publish)해.
6. 마지막으로, 방금 만든 커밋과 태그를 원격 저장소(e.g., GitHub)에 푸시해.

이 모든 과정을 수동으로 한다고 생각하면... 끔찍하지? `lerna publish`는 모노레포 관리자의 실수를 줄여주고, 일관된 배포 프로세스를 유지하게 해주는 정말 고마운 기능이야.

Lerna의 현주소와 한계

Lerna는 분명 훌륭한 도구지만, 세월이 흐르면서 몇 가지 한계점이 드러나기 시작했어. 특히 프로젝트 규모가 커질수록 그 단점이 부각되었지.

1. 속도 문제: Lerna는 기본적으로 '어떤 패키지가 변경되었나?' 정도만 파악할 뿐, 패키지 간의 의존성 그래프를 깊게 분석하지는 않아. 그래서 `lerna run build` 같은 명령을 내리면, 변경되지 않은 패키지까지 포함해서 모든 패키지를 전부 다시 빌드하는 경우가 많아. 대규모 프로젝트에서는 이 빌드 시간이 몇십 분씩 걸리기도 해. 🐢
2. 캐싱의 부재: 한 번 빌드하거나 테스트한 결과를 저장해뒀다가, 코드가 바뀌지 않았으면 다음번에는 그냥 저장된 결과를 재사용하는 '캐싱' 기능이 없어. 매번 똑같은 작업을 정직하게(?) 반복하는 거지.
3. 제한적인 작업 스케줄링: 의존성 순서에 따라 작업을 실행하기는 하지만, 병렬 처리 외에는 최적화된 작업 스케줄링 기능이 부족해.

한때 Lerna는 메인테이너의 부재로 "지원이 중단되었다"는 소문이 돌기도 했어. 하지만 다행히도, 바로 다음에 소개할 Nx를 만드는 Nrwl이라는 회사에서 Lerna의 관리를 맡게 되면서 다시 활발하게 개발이 이루어지고 있어. 그리고 Nx의 강력한 기능들을 Lerna에 통합하는 방향으로 발전하고 있지. 이 이야기는 뒤에서 더 자세히 해줄게!

Lerna는 모노레포의 기본기를 다지기에 아주 좋은 도구야. 특히 여러 개의 라이브러리를 npm에 배포하는 것이 주된 목적이라면, Lerna의 직관적인 `publish` 기능은 여전히 매력적이지. 하지만 더 빠르고, 더 스마트하고, 더 거대한 애플리케이션을 위한 모노레포를 꿈꾼다면, 이제 다음 챕터의 주인공인 Nx를 만나볼 시간이야. ✨

2장: 차세대 모노레포의 강자, Nx 등장! 🤖

Lerna가 모노레포 세계의 문을 연 개척자라면, Nx(Nrwl Extensions for Monorepos)는 그 위에 최첨단 인프라를 건설한 혁신가라고 할 수 있어. Nx는 단순히 패키지를 관리하고 배포하는 것을 넘어, '빠르고 확장 가능한 개발 경험' 자체를 목표로 하는 통합 빌드 시스템이야.

Nx의 캐치프레이즈는 "Smart Monorepos. Fast CI." 야. '똑똑한 모노레포, 빠른 CI'. 이 두 단어에 Nx의 모든 철학이 담겨있어. Lerna가 겪었던 속도와 비효율의 문제를 정면으로 돌파하기 위해 태어난 거지. 그럼 Nx는 과연 무엇이 어떻게 다른 걸까?

Nx의 핵심: 의존성 그래프 (Dependency Graph) Nx는 프로젝트 간의 관계를 정확히 이해하고 분석합니다. App: my-app (React) Lib: ui-components Lib: data-access Lib: utils 만약 'utils' 라이브러리만 변경되었다면? Nx는 'utils'와 'utils'를 사용하는 'ui-components', 'data-access', 'my-app'만 테스트/빌드합니다!

Nx의 킬러 기능 1: 코드 분석과 의존성 그래프 🧠

Nx가 '똑똑하다'고 불리는 가장 큰 이유야. Nx는 모노레포 안의 모든 소스 코드를 정적 분석(Static Analysis)해서 어떤 파일이 어떤 파일을 import하는지, 어떤 프로젝트가 어떤 라이브러리에 의존하는지를 정확하게 파악해. 그리고 이 정보를 바탕으로 위 SVG처럼 생긴 의존성 그래프(Dependency Graph)를 만들어내.

이 그래프가 왜 중요할까? Lerna는 `packages` 폴더 안에 뭐가 있는지 정도만 알지만, Nx는 '`my-app`은 `ui-components`와 `data-access`를 사용하고, `ui-components`는 `utils`를 사용한다'는 관계를 속속들이 알고 있다는 뜻이야. 이 정보 덕분에 뒤에 나올 엄청난 기능들이 가능해져.

언제든지 `nx graph` 명령어를 터미널에 입력하면, 이 의존성 그래프를 시각적으로 직접 확인할 수 있는 웹 애플리케이션이 실행돼. 프로젝트 구조를 한눈에 파악하는 데 정말 큰 도움이 되지. 😮

Nx의 킬러 기능 2: 영향 분석 (Affected Graph) 🎯

의존성 그래프가 있으니, 이제 훨씬 더 똑똑한 일을 할 수 있어. 바로 '영향 분석(Affected)'이야. 내가 특정 파일을 하나 수정했을 때, 그 변경으로 인해 '영향을 받는' 프로젝트가 무엇인지 정확히 집어내는 기술이지.

예를 들어, 위 그래프에서 내가 `utils` 라이브러리의 코드 한 줄을 바꿨다고 해보자. 이 변경으로 인해 영향을 받는 프로젝트는 누구일까? 바로 `utils`를 직접 또는 간접적으로 사용하는 `ui-components`, `data-access`, 그리고 `my-app`이야. `utils`와 아무 상관없는 다른 라이브러리 `auth-logic`이 있다면, 그건 영향을 받지 않았겠지.

이때 `nx affected:build` 라는 명령어를 실행하면, Nx는 Lerna처럼 모든 프로젝트를 빌드하는 게 아니라, 정확히 영향을 받은 `utils`, `ui-components`, `data-access`, `my-app` 네 개의 프로젝트만 골라서 빌드해줘. 프로젝트가 수십, 수백 개가 되어도 변경된 부분과 관련된 최소한의 작업만 수행하는 거야. CI/CD 시간을 말도 안 되게 단축시킬 수 있겠지? 이게 바로 "Fast CI"의 비밀이야.

Nx의 킬러 기능 3: 계산 캐싱 (Computation Caching) ⚡

영향 분석만으로도 놀라운데, Nx는 한 걸음 더 나아가. 바로 '계산 캐싱' 기능이야. 한 번 실행했던 빌드나 테스트 결과를 로컬 컴퓨터나 클라우드에 저장해두는 거지.

상황을 가정해보자.
1. `nx build my-app` 명령어를 실행했어. 빌드에 5분이 걸렸고, Nx는 그 결과물과 빌드 과정에 대한 해시(hash) 값을 어딘가에 저장해 둬. (e.g., `.nx/cache` 폴더)
2. 내가 다른 작업을 하다가, 아무런 코드 변경 없이 다시 `nx build my-app`을 실행했어.
3. Nx는 "어? 이 프로젝트 코드는 예전에 빌드했을 때랑 바뀐 게 하나도 없네?"라는 걸 해시 값을 통해 순식간에 파악해.
4. 그리고는 5분 동안 다시 빌드하는 대신, 이전에 저장해뒀던 결과물을 1초 만에 꺼내와서 "빌드 완료!"라고 알려줘.

이게 로컬 캐시야. 심지어 'Nx Cloud'라는 서비스를 이용하면 이 캐시를 팀원들과, 그리고 CI 서버와 공유할 수도 있어. 내가 내 컴퓨터에서 빌드한 결과를 동료가 재사용하고, CI 서버도 그걸 재사용하는 거야. 팀 전체의 생산성이 어마어마하게 올라가겠지? 이 캐싱 기능은 Nx의 가장 강력한 무기 중 하나야.

⚠️ 잠깐! 캐시는 어떻게 동작할까?
Nx는 단순히 파일 내용만 보는 게 아니야. 프로젝트의 모든 소스 코드, 의존하는 라이브러리의 코드, TypeScript 설정, 사용된 Node.js 버전 등 빌드 결과에 영향을 미칠 수 있는 모든 것들을 종합해서 고유한 해시 값을 만들어. 이 중 단 하나라도 바뀌면 캐시를 사용하지 않고 다시 작업을 실행해. 그래서 캐시 때문에 결과가 달라질 걱정은 하지 않아도 돼. 정말 똑똑하지!

Nx 시작하기: `create-nx-workspace`

백문이 불여일견! Nx 프로젝트를 직접 만들어보자. Lerna와는 다르게 전역 설치 없이 `npx`를 사용하는 것이 권장돼.

npx create-nx-workspace@latest my-nx-repo

명령어를 실행하면, 어떤 종류의 워크스페이스를 만들지 친절하게 물어봐. React, Angular, Node 등 다양한 프리셋을 제공하고, 여러 기술을 함께 사용하는 'integrated' 모노레포를 구성할 수도 있어. 여기서는 React 프리셋을 선택했다고 가정해보자.

생성된 폴더 구조는 Lerna보다 조금 더 복잡해.

my-nx-repo/
├── apps/
│   └── my-app/      # 실제 실행 가능한 애플리케이션
├── libs/
│   └── my-lib/      # 재사용 가능한 라이브러리
├── tools/
├── nx.json          # Nx 워크스페이스 설정
├── workspace.jsonc  # (구버전) 또는 각 프로젝트 폴더의 project.json
└── package.json

- `apps/`: 빌드해서 배포할 수 있는 최종 애플리케이션들이 위치하는 곳이야. (e.g., 웹사이트, API 서버)
- `libs/`: `apps`나 다른 `libs`에서 가져다 쓸 재사용 가능한 코드들이 위치해. (e.g., UI 컴포넌트, 유틸리티 함수, 상태 관리 로직)
- `nx.json`: Nx의 핵심 설정 파일. 캐싱 설정, 타겟 기본값, 플러그인 등이 여기에 정의돼.
- `project.json`: (최신 Nx 버전 기준) 각 `app`과 `lib` 폴더 안에 이 파일이 있어. 해당 프로젝트가 실행할 수 있는 작업(target)들, 예를 들어 `build`, `serve`, `test` 등이 정의되어 있어. Lerna의 `package.json` `scripts`와 비슷한 역할이지만 훨씬 더 구조화되어 있지.

플러그인과 코드 생성기: `nx generate`

Nx의 또 다른 매력은 바로 플러그인(Plugin)과 코드 생성기(Generator)야. 특정 프레임워크나 도구에 대한 지원을 플러그인 형태로 추가할 수 있어. 예를 들어 `@nrwl/react` 플러그인을 설치하면 React 관련 기능들을, `@nrwl/nest`를 설치하면 NestJS 관련 기능들을 사용할 수 있게 돼.

그리고 이 플러그인들은 아주 강력한 코드 생성기를 제공해. `nx generate` (단축 명령어 `nx g`)를 사용하면, 단순히 빈 폴더를 만드는 수준이 아니라, 보일러플레이트 코드, 설정 파일, 테스트 코드까지 모노레포 환경에 맞게 완벽하게 구성된 새로운 애플리케이션이나 라이브러리를 뚝딱 만들어줘.

# 'ui'라는 이름의 새로운 React 라이브러리 생성
nx g @nrwl/react:lib ui

# 'login'이라는 이름의 새로운 React 컴포넌트를 'ui' 라이브러리 안에 생성
nx g @nrwl/react:component login --project=ui

이 명령어 한 줄이면, `libs/ui/src/lib/login` 폴더 안에 `login.tsx`, `login.spec.tsx`, `login.module.css` 파일이 자동으로 생성되고, 필요한 export 설정까지 다 알아서 해줘. 개발자는 그냥 비즈니스 로직에만 집중하면 되는 거야. 개발 경험의 질이 달라지겠지? 🤩

Nx의 작업 실행 방식

Lerna에서 `lerna run test`를 썼던 것처럼, Nx에서도 프로젝트의 작업을 실행할 수 있어. Nx는 `run`이라는 명령어를 사용해.

# my-app 프로젝트의 test 타겟 실행
nx test my-app

# ui 라이브러리의 build 타겟 실행
nx build ui

# 영향을 받은 모든 프로젝트의 test 타겟 실행
nx affected:test

문법이 `nx ` 형태로 더 명확하고, 내부적으로는 앞서 설명한 의존성 그래프, 영향 분석, 캐싱이 모두 동작하면서 가장 효율적인 방식으로 작업을 수행해.

Nx는 Lerna가 가졌던 '패키지 관리 및 배포'라는 기본 기능에 '지능적인 작업 실행과 캐싱'이라는 강력한 엔진을 더한, 진정한 의미의 '빌드 시스템'이라고 할 수 있어. 하지만 기능이 많은 만큼 처음에는 배워야 할 개념들이 조금 더 있는 편이야. 그래도 그 학습 곡선을 넘어섰을 때 얻게 되는 생산성 향상은 상상 이상이지.

자, 이제 우리는 모노레포의 두 거인, Lerna와 Nx에 대해 각각 알아봤어. 다음 장에서는 이 둘을 직접 맞붙여보고, 어떤 상황에 누가 더 유리한지, 그리고 둘을 함께 사용하는 최신 트렌드까지 알아보자! 🥊

3장: Lerna vs. Nx 세기의 대결! 그리고 예상치 못한 콜라보? 🤝

이제 Lerna와 Nx, 두 챔피언의 특징을 모두 살펴봤으니, 링 위로 올려서 직접 비교해볼 시간이야. "그래서 둘 중에 뭘 써야 하는데요?"라는 질문에 답을 찾아보자. 그리고 마지막엔 이 둘이 손을 잡는 놀라운 반전까지 준비되어 있으니 끝까지 집중해줘! 😉

Round 1: 핵심 기능 및 철학 🥊

Lerna:
Lerna의 심장은 '패키지 관리와 퍼블리싱'에 있어. 여러 개의 npm 패키지를 하나의 저장소에서 개발하고, 버전을 관리하고, 쉽게 배포하는 것이 주된 목적이야. `lerna bootstrap`과 `lerna publish`가 Lerna의 존재 이유라고 할 수 있지. Lerna는 개발 워크플로우 자체를 바꾸기보다는, 기존의 패키지 기반 개발 방식을 모노레포 환경에서 더 편리하게 만들어주는 '조력자'에 가까워.

Nx:
Nx의 심장은 '통합 빌드 시스템'이야. 패키지 관리는 기본이고, 그 위에 '어떻게 하면 더 빠르고 효율적으로 빌드하고 테스트할 것인가?'에 대한 고민이 녹아있어. 의존성 그래프, 영향 분석, 계산 캐싱은 모두 이 목표를 위한 도구들이지. Nx는 단순한 조력자를 넘어, 프로젝트의 구조와 개발 방식을 더 효율적인 방향으로 이끌어가는 '설계자'에 가까워.

판정: 목적이 달라. 라이브러리 배포가 주목적이라면 Lerna의 단순함이, 대규모 애플리케이션의 개발 속도와 효율이 중요하다면 Nx의 지능적인 기능이 더 매력적이야.

Round 2: 속도와 퍼포먼스 🥊

Lerna:
기본 Lerna는 캐싱 기능이 없어. `lerna run build`를 실행하면, 변경되지 않은 패키지까지 포함해서 모든 대상을 다시 빌드해. 물론 `--since` 같은 옵션으로 변경된 패키지만 골라낼 수는 있지만, Nx의 정교한 영향 분석에 비하면 제한적이야. 프로젝트가 커질수록 CI/CD 시간은 선형적으로 증가하게 돼.

Nx:
속도는 Nx가 압도적으로 우세해. 🚀
1. 영향 분석(Affected): 변경된 부분만 정확히 골라서 작업하므로 불필요한 시간 낭비가 없어.
2. 계산 캐싱(Caching): 한 번 실행한 작업은 다시 실행하지 않고 캐시에서 결과를 가져와. 로컬 캐시만으로도 엄청난 시간 절약이 되고, Nx Cloud를 통한 분산 캐싱은 팀 전체의 생산성을 극대화해.

판정: Nx의 압승! 프로젝트 규모가 조금이라도 커질 가능성이 있다면, Nx의 퍼포먼스 이점은 절대 무시할 수 없어.

Round 3: 개발자 경험(DX) 및 편의 기능 🥊

Lerna:
Lerna는 배우기 쉽고 직관적이야. 몇 가지 핵심 명령어(`init`, `create`, `bootstrap`, `add`, `run`, `publish`)만 알면 금방 적응할 수 있어. 기존에 여러 저장소를 관리하던 개발자에게는 매우 자연스러운 전환이 될 수 있지.

Nx:
Nx는 코드 생성기, 플러그인, 의존성 그래프 시각화, 내장된 린팅/테스팅 설정 등 개발자를 편하게 해주는 다양한 기능들을 제공해. `nx g lib my-feature` 한 줄로 라이브러리의 모든 기본 구조가 잡히는 경험은 정말 쾌적해. 하지만 플러그인, 타겟, 생성기 등 처음 배워야 할 Nx 고유의 개념들이 있어서 Lerna보다는 학습 곡선이 조금 더 가파른 편이야.

판정: 초기 진입 장벽은 Lerna가 낮지만, 장기적인 개발 편의성과 생산성 측면에서는 풍부한 기능을 제공하는 Nx가 우세하다고 볼 수 있어.

최종 판정: 그래서 뭘 써야 할까?

이런 경우 Lerna를 고려해보세요:
  • 주된 목적이 여러 개의 독립적인 라이브러리를 개발해서 npm에 배포하는 것일 때.
  • 프로젝트 규모가 비교적 작고, 빌드/테스트 시간이 크게 부담되지 않을 때.
  • 최대한 빨리 모노레포 환경을 구축하고 싶고, 새로운 개념 학습에 드는 비용을 최소화하고 싶을 때.
이런 경우 Nx를 강력 추천합니다:
  • 프론트엔드, 백엔드, 공유 라이브러리가 공존하는 대규모 풀스택 애플리케이션을 만들 때.
  • CI/CD 시간이 길어져서 개발 생산성이 저하되고 있을 때.
  • 팀원들과 일관된 코드 구조와 개발 방식을 유지하고 싶을 때.
  • 장기적으로 확장 가능하고 유지보수하기 좋은 아키텍처를 구축하고 싶을 때.

반전: Lerna on Nx - 둘의 장점을 모두 취하다!

Lerna vs. Nx 구도로 한참을 이야기했지만, 사실 현대 모노레포의 가장 흥미로운 트렌드는 이 둘의 '결합'이야. 앞서 잠깐 언급했듯이, Nx를 만드는 Nrwl이 Lerna의 공식 메인테이너가 되면서 놀라운 일이 벌어졌어. Lerna의 내부에 Nx의 강력한 엔진을 탑재할 수 있게 된 거야! 🏎️

이걸 "Lerna powered by Nx" 또는 "Lerna on Nx"라고 불러.

기존 Lerna 프로젝트에 아주 간단한 설정만 추가하면 돼. 먼저 필요한 패키지를 설치하고,

npm i nx -D

`lerna.json` 파일 맨 위에 이 한 줄만 추가해주면 끝이야.

{
  "useNx": true,
  ...
}

이게 다야! 이렇게 하면 무슨 일이 일어날까?

이제 `lerna run build` 같은 명령어를 실행하면, Lerna는 예전처럼 순진하게 모든 패키지를 순회하는 대신, 내부적으로 Nx의 엔진에게 "이봐, 빌드 작업 좀 처리해줘!"라고 요청해. 그럼 Nx는 자신의 특기인 의존성 그래프 분석, 영향 분석, 계산 캐싱을 총동원해서 가장 효율적인 방식으로 빌드를 수행하고 그 결과만 Lerna에게 돌려줘.

즉, 개발자는 Lerna의 익숙하고 간단한 명령어(`lerna run`, `lerna publish`)를 그대로 사용하면서, 그 이면에서는 Nx의 엄청난 퍼포먼스 향상을 누릴 수 있게 되는 거야. Lerna의 쉬운 사용성과 Nx의 강력한 성능, 두 마리 토끼를 모두 잡는 거지. 🐰🐰

💡 이게 왜 중요할까?
이미 Lerna로 구축된 수많은 거대 오픈소스 및 기업 프로젝트들이, 큰 마이그레이션 비용 없이도 Nx의 성능적 이점을 누릴 수 있게 되었다는 의미야. 또한, Lerna의 직관적인 퍼블리싱 기능은 여전히 많은 사랑을 받고 있기 때문에, Nx의 빠른 실행 능력과 Lerna의 편리한 배포 기능을 조합한 이 방식은 현재 가장 이상적인 모노레포 구성 중 하나로 평가받고 있어.

결론적으로, "Lerna냐 Nx냐"의 선택은 이제 "순수한 Lerna냐, Nx냐, 아니면 Lerna와 Nx의 조합이냐"의 문제로 바뀌었어. 대부분의 새로운 대규모 프로젝트는 처음부터 Nx로 시작하는 것이 유리하지만, 기존 Lerna 사용자나 퍼블리싱 기능이 중요한 프로젝트라면 'Lerna on Nx' 조합이 최고의 선택이 될 수 있어. 정말 흥미로운 발전이지?

4장: 실전! Nx로 만드는 '재능 공유 플랫폼' 모노레포 🛠️

이론은 충분히 배웠으니, 이제 직접 손을 더럽힐 시간이야! 👨‍💻👩‍💻
우리가 가상의 '재능 공유 플랫폼'을 만든다고 상상해보자. 이 플랫폼은 사용자를 위한 React 프론트엔드, API를 제공하는 Express 백엔드, 그리고 두 곳에서 공통으로 사용하는 UI 컴포넌트와 유틸리티 함수 라이브러리로 구성될 거야. 이런 복잡한 구조야말로 모노레포가 빛을 발하는 순간이지. 여기서는 가장 강력한 도구인 Nx를 사용해서 처음부터 구축해볼게.

이 실전 예제를 따라 하고 나면, 모노레포의 구조와 Nx의 작동 방식을 몸으로 이해하게 될 거야. 자, 터미널을 켜고 따라와 봐!

Step 1: Nx 워크스페이스 생성하기

먼저, `create-nx-workspace` 명령어로 새로운 모노레포 프로젝트를 생성하자. 프로젝트 이름은 `jaenung-net-clone`으로 할게.

npx create-nx-workspace@latest jaenung-net-clone

명령어를 실행하면 Nx가 몇 가지 질문을 할 거야.
1. Where would you like to create your workspace? -> `jaenung-net-clone` (그냥 엔터)
2. What to create in the new workspace? -> `apps` (여러 앱을 만들 것이므로 이걸 선택)
3. Enable distributed caching to make your CI faster? -> `Yes` (Nx Cloud의 분산 캐싱 기능을 활성화할지 묻는 질문. 'Yes'를 선택해서 강력한 기능을 맛보자!)

잠시 기다리면 `jaenung-net-clone` 폴더와 함께 기본적인 모노레포 구조가 생성될 거야.

Step 2: 애플리케이션과 라이브러리 생성하기

이제 우리 플랫폼에 필요한 조각들을 `nx generate` 명령어로 하나씩 만들어보자.

1. React 프론트엔드 앱 생성 (`web`)

먼저 React 플러그인을 설치해야 해.

npm install -D @nrwl/react

그리고 React 애플리케이션을 생성하자.

nx g @nrwl/react:app web --style=css --routing=true

이 명령어는 `apps/web` 경로에 라우팅 기능과 기본 CSS 설정이 포함된 React 앱을 만들어줘.

2. Express 백엔드 앱 생성 (`api`)

이번엔 Express 플러그인을 설치하고,

npm install -D @nrwl/express

Express 애플리케이션을 생성하자.

nx g @nrwl/express:app api

`apps/api` 경로에 기본적인 Express 서버 코드가 생성될 거야.

3. 공통 UI 라이브러리 생성 (`ui-shared`)

프론트엔드에서 사용할 공통 UI 컴포넌트들을 담을 라이브러리를 만들자. 이 라이브러리는 빌드할 필요 없이 소스 코드 그대로 가져다 쓸 것이므로 `--buildable=false` 옵션을 줄 수 있어.

nx g @nrwl/react:lib ui-shared --buildable=false

`libs/ui-shared` 경로에 React 라이브러리 기본 구조가 생성돼.

4. 공통 유틸리티 라이브러리 생성 (`utils-shared`)

프론트엔드와 백엔드 양쪽에서 모두 사용할 수 있는 순수 TypeScript 유틸리티 함수들을 담을 라이브러리야. 이런 라이브러리는 `@nrwl/js` 플러그인을 사용해.

npm install -D @nrwl/js
nx g @nrwl/js:lib utils-shared --buildable=false

`libs/utils-shared` 경로에 TypeScript 라이브러리가 생성돼.

자, 이제 우리의 모노레포는 이런 구조를 갖게 되었어.

jaenung-net-clone/
├── apps/
│   ├── api/      # Express 백엔드
│   └── web/      # React 프론트엔드
└── libs/
    ├── ui-shared/    # 공통 UI 컴포넌트
    └── utils-shared/ # 공통 유틸리티 함수

벌써부터 체계적인 느낌이 들지 않아? 😊

Step 3: 의존성 연결하고 코드 작성하기

이제 생성된 프로젝트들 사이에 의존성을 만들어보자. `web` 앱은 `ui-shared`와 `utils-shared`를 사용하고, `api` 서버는 `utils-shared`를 사용하게 할 거야.

1. `utils-shared`에 공통 함수 추가하기

`libs/utils-shared/src/lib/utils-shared.ts` 파일을 열고 간단한 함수를 추가하자.

export function formatGreeting(name: string): string {
  return `Hello, ${name}! Welcome to our platform!`;
}

2. `ui-shared`에 공통 컴포넌트 추가하고 `utils-shared` 사용하기

`libs/ui-shared/src/lib/header/header.tsx` 라는 파일을 새로 만들고, `utils-shared` 라이브러리를 import 해서 사용해보자.

import { formatGreeting } from '@jaenung-net-clone/utils-shared';

export function Header({ userName }: { userName: string }) {
  return (
    <header style={{ padding: '1rem', backgroundColor: '#eee' }}>
      <h1>Jaenung.net Clone</h1>
      <p>{formatGreeting(userName)}</p>
    </header>
  );
}

export default Header;

여기서 `@jaenung-net-clone/utils-shared` 라는 경로를 주목해. Nx는 `tsconfig.base.json` 파일에 각 라이브러리에 대한 경로 매핑을 자동으로 추가해줘. 덕분에 우리는 복잡한 상대 경로(`../../...`) 대신 이렇게 깔끔한 절대 경로로 다른 라이브러리를 참조할 수 있어. 이것도 개발 경험을 향상시키는 Nx의 중요한 기능 중 하나야.

3. `web` 앱에서 `ui-shared` 컴포넌트 사용하기

이제 `apps/web/src/app/app.tsx` 파일을 수정해서 방금 만든 `Header` 컴포넌트를 사용해보자.

import { Header } from '@jaenung-net-clone/ui-shared';

export function App() {
  return (
    <div>
      <Header userName="Guest" />
      <main style={{ padding: '1rem' }}>
        <h2>Find Your Talent!</h2>
        {/* ... Page content goes here ... */}
      </main>
    </div>
  );
}

export default App;

4. `api` 서버에서 `utils-shared` 사용하기

`apps/api/src/main.ts` 파일에서도 공통 유틸리티 함수를 사용해보자.

import express from 'express';
import { formatGreeting } from '@jaenung-net-clone/utils-shared';

const host = process.env.HOST ?? 'localhost';
const port = process.env.PORT ? Number(process.env.PORT) : 3000;

const app = express();

app.get('/api', (req, res) => {
  res.send({ message: formatGreeting('API User') });
});

app.listen(port, host, () => {
  console.log(`[ ready ] http://${host}:${port}`);
});

이것으로 모든 연결이 끝났어! 이제 `nx graph` 명령어를 실행해서 우리의 작업이 만들어낸 의존성 그래프를 직접 눈으로 확인해봐. `web`과 `api`가 어떻게 `utils-shared`에 의존하고, `web`이 `ui-shared`에 의존하는지 명확하게 보일 거야.

Step 4: Nx의 마법, `affected` 명령어 체험하기

이제 Nx의 진가를 확인해 볼 시간이야. 먼저, 모든 프로젝트를 테스트해보자.

nx run-many --target=test --all

아마 `web`, `api`, `ui-shared`, `utils-shared` 네 개의 프로젝트에 대한 테스트가 모두 실행될 거야.

자, 이제 가장 깊은 곳에 있는 `utils-shared` 라이브러리의 코드를 살짝 수정해보자.
`libs/utils-shared/src/lib/utils-shared.ts` 파일의 인사말을 바꿔볼게.

export function formatGreeting(name: string): string {
  // "Welcome to our platform!" -> "Welcome to the world of talent!"
  return `Hello, ${name}! Welcome to the world of talent!`;
}

이 작은 변경은 어떤 프로젝트들에 영향을 미칠까? 바로 이 함수를 사용하는 `ui-shared`, `web`, `api` 모두에게 영향을 주겠지. Nx가 이걸 정확히 알고 있는지 확인해보자.

`nx affected:test` 명령어를 실행해봐!

nx affected:test

놀랍게도, Nx는 변경되지 않은 프로젝트는 건드리지 않고, 변경의 영향을 받은 `utils-shared`, `ui-shared`, `web`, `api` 네 개의 프로젝트에 대해서만 테스트를 실행할 거야. (실제로는 `ui-shared`에 테스트가 없으면 3개만 실행될 수 있음). 만약 `web` 앱의 CSS만 수정했다면? `nx affected:test`는 `web` 프로젝트의 테스트만 실행하겠지. 이게 바로 Nx가 CI 시간을 획기적으로 줄여주는 비결이야!

이 간단한 실습을 통해 우리는 Nx를 사용해서 어떻게 체계적으로 모노레포를 구성하고, 프로젝트 간의 의존성을 관리하며, `affected` 명령어를 통해 효율적으로 작업을 수행하는지 경험해봤어. 이런 전문적인 프로젝트 구조와 워크플로우는 협업의 효율을 높여줄 뿐만 아니라, **재능넷** 같은 플랫폼에서 당신의 개발 전문성을 어필하는 데도 큰 도움이 될 거야. 😉

5장: 모노레포 운영, 이것만은 알고 가자! (주의사항 및 팁)

모노레포는 강력한 만큼, 아무런 계획 없이 뛰어들면 오히려 혼란을 초래할 수 있어. 마치 잘 닦인 고속도로 같아서, 규칙을 잘 지키면 아주 빠르고 편하지만, 역주행이라도 하는 날엔 큰 사고로 이어질 수 있지. 😅 성공적인 모노레포 운영을 위해 꼭 알아둬야 할 몇 가지 주의사항과 팁을 알려줄게.

1. 툴링의 복잡성을 받아들여라

모노레포는 `npm install`과 `npm start`만으로 모든 게 해결되던 단순한 세계가 아니야. Lerna나 Nx 같은 전문 도구의 사용법을 익혀야 하고, 그 도구들이 어떻게 동작하는지 이해해야 해. 처음에는 이 학습 과정이 부담스러울 수 있지만, 이 복잡성은 수십 개의 저장소를 오가며 버전을 맞추는 고통을 해결하기 위한 '건강한 복잡성'이라는 걸 기억해야 해.

Tip: 팀에 새로운 멤버가 합류했을 때, 모노레포의 구조와 핵심 명령어(e.g., `nx affected:test`) 사용법에 대한 온보딩 문서를 잘 만들어두는 것이 좋아. 모두가 동일한 방식으로 도구를 사용해야 모노레포의 장점을 극대화할 수 있어.

2. CI/CD 파이프라인을 스마트하게 구성하라

모노레포의 가장 큰 함정 중 하나는 CI/CD 설정이야. 아무 생각 없이 설정하면, 코드 한 줄만 바뀌어도 전체 모노레포를 전부 테스트하고 빌드하는 끔찍한 상황이 발생할 수 있어. 이건 모노레포의 장점을 모두 버리는 행위야.

반드시 `affected` 명령어를 활용해야 해.

GitHub Actions를 예로 들면, 워크플로우 스크립트는 대략 이런 모습이 될 거야.

jobs:
  build_and_test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0 # Nx affected가 비교할 base commit을 가져오기 위해 필요

      - uses: actions/setup-node@v3
        with:
          node-version: '18'

      - run: npm ci

      # 영향을 받은 프로젝트만 빌드
      - run: npx nx affected --target=build --base=origin/main --head=HEAD

      # 영향을 받은 프로젝트만 테스트
      - run: npx nx affected --target=test --base=origin/main --head=HEAD

여기서 `--base=origin/main --head=HEAD` 부분은 현재 브랜치(HEAD)가 `main` 브랜치와 비교해서 어떤 변경사항이 있는지 Nx에게 알려주는 역할을 해. 이렇게 설정하면 CI 서버는 변경과 관련된 최소한의 작업만 수행하게 되어 시간과 비용을 엄청나게 절약할 수 있어.

3. 코드 소유권과 경계를 명확히 하라

모든 코드가 한곳에 있다 보니, "누가 이 코드의 책임자인가?"가 모호해질 수 있어. 다른 팀의 코드를 실수로 건드리거나, 의도치 않은 강한 결합(coupling)이 생길 수도 있지.

해결책:
- `CODEOWNERS` 파일 활용: GitHub의 `CODEOWNERS` 파일을 사용해서 특정 폴더나 라이브러리의 변경 사항에 대해 리뷰를 요청할 담당자를 지정할 수 있어. 예를 들어, `libs/ui-shared/` 폴더의 변경은 무조건 프론트엔드팀의 리뷰를 거치도록 강제하는 거지.
- 경계 설정 (Boundary Rules): Nx는 린트 규칙을 통해 프로젝트 간의 의존성을 강제하는 강력한 기능을 제공해. 예를 들어, '`feature-a` 라이브러리는 `feature-b` 라이브러리를 직접 import할 수 없다'거나, '백엔드 코드는 프론트엔드 코드를 참조할 수 없다' 같은 규칙을 설정해서 아키텍처가 깨지는 것을 방지할 수 있어. (`nx.json`의 `enforceModuleBoundaries` 옵션)

4. 공유 라이브러리를 현명하게 설계하라

모노레포의 꽃은 코드 공유야. 하지만 무엇을, 어떻게 공유할지는 신중하게 결정해야 해.

- 작고 집중된 라이브러리: 거대한 `common` 라이브러리 하나를 만들기보다는, 기능별로 잘게 쪼개진 여러 개의 라이브러리를 만드는 것이 좋아. `ui-buttons`, `utils-formatters`, `data-access-users`처럼 말이야. 이렇게 하면 의존성 그래프가 더 명확해지고, `affected` 명령어의 효율도 극대화돼.
- 명확한 API 제공: 공유 라이브러리는 외부로 노출할 API를 명확하게 정의해야 해. 보통 각 라이브러리의 `index.ts` 파일을 통해 외부에 공개할 함수나 컴포넌트만 `export` 하는 방식을 사용해. 라이브러리 내부의 구현 세부사항을 다른 프로젝트에서 직접 import해서 사용하는 것은 피해야 해.

⚠️ 주의: 의존성 버전 관리
모노레포는 기본적으로 루트 `package.json`에서 모든 의존성의 버전을 단일화해서 관리해. (e.g., 모든 프로젝트가 React 18.2.0 버전을 사용) 이건 일관성을 유지하는 데 큰 장점이지만, 특정 프로젝트만 다른 버전의 라이브러리를 사용해야 하는 특수한 경우에는 문제가 될 수 있어. 대부분의 경우 단일 버전을 유지하는 것이 좋지만, 불가피한 상황이라면 해당 프로젝트만 별도의 `package.json`으로 의존성을 관리하는 방법을 고민해봐야 해. (하지만 권장되지는 않아!)

모노레포는 기술적인 선택일 뿐만 아니라, 팀의 문화와 협업 방식에도 영향을 미치는 아키텍처 패턴이야. 위에서 언급한 점들을 잘 고려해서 신중하게 도입하고 꾸준히 가꿔나간다면, 당신의 개발팀에 엄청난 생산성과 안정성을 가져다줄 거야.

마치며: 그래서, 우리 이제 뭘 써야 할까?

정말 긴 여정이었어, 친구! 👏 우리는 프로젝트 지옥에서 시작해서, 모노레포라는 신세계를 만났고, 그곳의 두 거인 Lerna와 Nx를 속속들이 파헤쳐봤어. 심지어 둘이 힘을 합치는 모습까지 목격했지. 그리고 직접 가상의 플랫폼을 만들어보면서 Nx의 강력함을 체험하기도 했어.

이제 마지막 질문에 답을 할 시간이야. "그래서, 내 다음 프로젝트에는 뭘 써야 할까?"

이 글을 통해 당신이 얻었을 결론은 아마 이럴 거야.

- 만약 당신이 새로운 프로젝트를 시작하고, 그 프로젝트가 둘 이상의 애플리케이션이나 라이브러리를 포함할 가능성이 조금이라도 있다면, 주저하지 말고 Nx로 시작하는 것을 강력히 추천해. 처음에는 조금 낯설지 몰라도, Nx가 제공하는 생산성, 속도, 안정성은 그 학습 비용을 몇 배로 보상해주고도 남아.

- 만약 당신이 이미 Lerna로 운영 중인 프로젝트를 가지고 있고, CI 속도에 불만이 생기기 시작했다면, 모든 것을 Nx로 마이그레이션하기 전에 'Lerna on Nx' 방식을 시도해보는 것이 아주 좋은 선택이야. `useNx: true` 설정 하나만으로도 즉각적인 성능 향상을 경험할 수 있을 거야.

- 만약 당신의 목표가 아주 명확하게 '몇 개의 작은 라이브러리를 만들어서 npm에 배포하는 것'이고, 복잡한 빌드 과정이나 애플리케이션 간의 연동이 없다면, 순수한 Lerna만으로도 충분히 만족스러울 수 있어.

결국 정답은 없어. 당신의 프로젝트 상황과 팀의 목표에 가장 잘 맞는 도구를 선택하는 것이 중요해. 하지만 한 가지는 확실해. 모노레포는 더 이상 거대 기업들만의 전유물이 아니며, Lerna와 Nx 덕분에 우리 같은 평범한 개발자들도 그 강력한 이점을 누릴 수 있게 되었다는 거야.

오늘 배운 지식들이 당신이 앞으로 마주할 복잡한 프로젝트들을 더 자신감 있게, 더 효율적으로 관리하는 데 큰 도움이 되기를 바라. 혹시 모노레포를 구축하다가 어려운 문제에 부딪힌다면, **재능넷** 같은 플랫폼에서 경험 많은 전문가의 도움을 받는 것도 좋은 방법이 될 수 있다는 걸 기억해줘.

자, 이제 당신의 터미널을 열고 새로운 세계를 구축할 시간이야. Happy coding! 💻✨

댓글 작성

이 글에 대한 여러분의 생각을 들려주세요

댓글 0
댓글을 불러오는 중입니다.

아직 댓글이 없습니다.