테마
모노레포 환경 구축: pnpm + Turborepo
pnpm 워크스페이스와 Turborepo를 사용하여 커리어업 마이크로프론트엔드 프로젝트의 모노레포 환경을 설계하고 구축한다.
학습 목표
- 커리어업 프로젝트에 모노레포가 필요한 이유를 이해한다
- pnpm 워크스페이스의 선택 이유와 설정 방법을 파악한다
- Turborepo의 캐싱/병렬 실행이 모노레포에 주는 이점을 이해한다
- 전체 프로젝트 디렉터리 구조(apps/ + packages/)를 설계할 수 있다
- pnpm-workspace.yaml, turbo.json, 루트 package.json을 설정할 수 있다
1. 모노레포 도입 결정
1.1 모노레포의 정의
모노레포(Monorepo)는 잘 정의된 관계를 가진 여러 개의 개별 프로젝트가 포함된 단일 레포지토리이다. 단순히 여러 프로젝트를 한 저장소에 넣는 것이 아니라, 프로젝트 간의 의존 관계를 명시적으로 관리하고 함께 발전시키는 구조이다.
1.2 커리어업에 모노레포가 필요한 이유
커리어업 프로젝트는 다음 두 가지 유형의 프로젝트가 서로 관계를 맺으며 동작한다:
| 유형 | 프로젝트 | 결합 시점 | 설명 |
|---|---|---|---|
| 런타임 앱 | Shell, Posting, Network, Education, Jobs | 런타임 (Module Federation) | 각각 독립 서버를 가진 마이크로앱 |
| 빌드타임 패키지 | UIKit, Shell Router, 공통 유틸 | 빌드타임 (npm 패키지) | 여러 앱에서 공유하는 라이브러리 |
런타임 앱들은 빌드타임 패키지에 의존하고, 빌드타임 패키지가 수정되면 이를 사용하는 앱들이 재빌드되어야 한다. 이러한 강한 연관 관계를 효율적으로 관리하려면 모노레포가 적합하다.
2. pnpm 선택 이유
2.1 패키지 매니저 비교
| 기능 | npm | yarn | pnpm |
|---|---|---|---|
| 의존성 관리 | 평탄화(hoisting) | 평탄화(hoisting) | Content-addressable 저장소 |
| 호이스팅 제어 | 불가 | 불가 | 엄격 모드로 차단 가능 |
| 디스크 효율 | 프로젝트마다 복사 | 프로젝트마다 복사 | 글로벌 저장소에서 심볼릭 링크 |
| 워크스페이스 | npm workspaces | yarn workspaces | pnpm workspaces |
| 유령 의존성 | 발생 가능 | 발생 가능 | 차단 |
2.2 pnpm의 핵심 장점
효율적인 의존성 관리: pnpm은 모든 패키지를 글로벌 content-addressable 저장소에 한 번만 저장하고, 각 프로젝트의 node_modules에는 심볼릭 링크를 생성한다. 10개의 프로젝트가 React 18을 사용해도 디스크에는 한 벌만 존재한다.
엄격한 호이스팅 제어: npm과 yarn은 의존성을 상위로 끌어올리는(hoisting) 과정에서 명시적으로 설치하지 않은 패키지도 사용 가능한 "유령 의존성(phantom dependency)" 문제가 발생한다. pnpm은 이를 엄격하게 차단하여 각 패키지가 자신이 선언한 의존성만 접근할 수 있도록 보장한다.
이 두 가지 특성이 여러 독립 프로젝트가 공존하는 모노레포 환경에 매우 적합하다.
3. Turborepo 선택 이유
3.1 Turborepo의 역할
Turborepo는 모노레포의 빌드 오케스트레이션 도구이다. 여러 패키지 간의 빌드 순서를 자동으로 결정하고, 캐싱과 병렬 실행으로 빌드 속도를 최적화한다.
| 기능 | 설명 |
|---|---|
| 의존 관계 자동 해석 | package.json의 dependencies를 분석하여 빌드 순서를 결정 |
| 로컬 캐싱 | 변경이 없는 패키지의 빌드 결과를 캐시에서 재사용 |
| 병렬 실행 | 의존 관계가 없는 태스크를 동시에 실행 |
| 필터링 | 특정 패키지만 선택적으로 빌드/실행 가능 |
3.2 빌드 파이프라인 예시
Turborepo가 자동으로 처리하는 것:
- UIKit, Shell Router, Utils 패키지를 먼저 병렬로 빌드한다
- 패키지 빌드가 완료된 후 5개 앱을 병렬로 빌드한다
- UIKit만 변경되었다면, Shell Router와 Utils 빌드는 캐시에서 재사용한다
4. 프로젝트 디렉터리 구조 설계
4.1 전체 구조
careerup/
├── apps/ # 런타임 마이크로앱 (Webpack)
│ ├── shell/ # @careerup/shell (Host, Port 3000)
│ │ ├── src/
│ │ ├── webpack.config.js
│ │ └── package.json
│ ├── posting/ # @careerup/posting (Remote, Port 3001)
│ │ ├── src/
│ │ ├── webpack.config.js
│ │ └── package.json
│ ├── networking/ # @careerup/networking (Remote, Port 3002)
│ │ ├── src/
│ │ ├── webpack.config.js
│ │ └── package.json
│ ├── education/ # @careerup/education (Remote, Port 3003)
│ │ ├── src/
│ │ ├── webpack.config.js
│ │ └── package.json
│ └── jobs/ # @careerup/jobs (Remote, Port 3004)
│ ├── src/
│ ├── webpack.config.js
│ └── package.json
├── packages/ # 빌드타임 공유 패키지 (Vite)
│ ├── ui-kit/ # @careerup/ui-kit
│ │ ├── src/
│ │ │ ├── components/ # Button, Card, Icon 등
│ │ │ └── styles/ # global.css
│ │ ├── vite.config.ts
│ │ └── package.json
│ ├── shell-router/ # @careerup/shell-router
│ │ ├── src/
│ │ │ ├── hooks/ # useAppEvent, useShellEvent, useUserEvent
│ │ │ ├── components/ # AppRoutingManager
│ │ │ └── factories/ # mount()
│ │ ├── vite.config.ts
│ │ └── package.json
│ └── utils/ # @careerup/utils
│ ├── src/
│ ├── vite.config.ts
│ └── package.json
├── pnpm-workspace.yaml
├── turbo.json
├── package.json # @careerup/monorepo (루트)
└── pnpm-lock.yaml4.2 apps/ vs packages/ 분리 기준
| 구분 | apps/ | packages/ |
|---|---|---|
| 역할 | 독립 실행 가능한 마이크로앱 | 다른 앱에 포함되는 라이브러리 |
| 서버 | 각각 독립 개발 서버 실행 | 서버 없음, 빌드 결과물만 제공 |
| 번들러 | Webpack (Module Federation) | Vite (라이브러리 모드) |
| 배포 | 독립 배포 가능 | 앱 빌드 시 포함됨 |
| 패키지명 | @careerup/shell 등 | @careerup/ui-kit 등 |
5. 핵심 설정 파일
5.1 pnpm-workspace.yaml
yaml
packages:
- 'apps/*'
- 'packages/*'이 파일은 프로젝트 루트에 위치하며, pnpm에게 워크스페이스에 포함할 디렉터리를 알려준다. apps/ 아래의 모든 폴더와 packages/ 아래의 모든 폴더를 개별 워크스페이스(패키지)로 인식한다.
5.2 turbo.json
json
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"start:live": {
"cache": false,
"persistent": true
},
"build:start": {
"cache": false,
"persistent": true
}
}
}| 파이프라인 | 설명 |
|---|---|
build | 의존 패키지를 먼저 빌드(^build)한 후 자신을 빌드. 결과물은 dist/에 캐싱 |
start:live | 개발 서버 실행. HMR(Hot Module Replacement) 포함. 캐시 없음, 영속적 |
build:start | 빌드된 결과물을 서빙. 프리뷰 용도. 캐시 없음, 영속적 |
5.3 루트 package.json
json
{
"name": "@careerup/monorepo",
"private": true,
"scripts": {
"dev": "turbo start:live",
"build": "turbo build",
"serve": "turbo build:start"
},
"devDependencies": {
"turbo": "^1.x"
}
}루트에서 pnpm dev를 실행하면 Turborepo가 모든 워크스페이스의 start:live 스크립트를 병렬로 실행하여, Shell과 모든 마이크로앱의 개발 서버가 동시에 시작된다.
6. 환경 구축 절차 요약
| 순서 | 명령어 / 작업 | 설명 |
|---|---|---|
| 1 | mkdir careerup && cd careerup | 프로젝트 루트 생성 |
| 2 | pnpm init | 루트 package.json 초기화 |
| 3 | corepack enable && corepack use pnpm | pnpm 버전 고정 |
| 4 | pnpm-workspace.yaml 생성 | 워크스페이스 범위 정의 |
| 5 | mkdir apps && cd apps | 앱 디렉터리 생성 |
| 6 | pnpm create mf-app (shell) | create-mf-app으로 Shell 프로젝트 생성 (Port 3000) |
| 7 | 루트로 이동 후 pnpm install | 워크스페이스 의존성 설치 |
| 8 | 패키지명 수정 | @careerup/monorepo, @careerup/shell 등 |
| 9 | pnpm add -Dw turbo | 루트에 Turborepo 설치 |
| 10 | turbo.json 생성 | 빌드 파이프라인 정의 |
| 11 | 루트 scripts 추가 | dev, build, serve 스크립트 |
| 12 | pnpm dev | 전체 개발 환경 실행 확인 |
주의사항: create-mf-app으로 생성한 프로젝트의 @types/react, @types/react-dom 버전이 오래된 경우 React 18.2에 맞게 수동 업데이트가 필요하다.
핵심 정리
- 커리어업의 런타임 앱과 빌드타임 패키지는 강한 연관 관계를 가지므로, 모노레포가 적합하다
- pnpm은 content-addressable 저장소로 디스크 효율이 높고, 엄격한 호이스팅 제어로 유령 의존성을 방지한다
- Turborepo는 의존 관계를 자동 해석하여 빌드 순서를 결정하고, 캐싱과 병렬 실행으로 빌드 속도를 최적화한다
- 디렉터리 구조는
apps/(5개 마이크로앱, Webpack)와packages/(UIKit, Shell Router 등, Vite)로 분리한다 pnpm-workspace.yaml로 워크스페이스를 정의하고,turbo.json으로 빌드 파이프라인을 설정한다- 루트에서
pnpm dev한 번으로 전체 개발 환경이 병렬 기동된다
다음 단계
다음 문서 04-통합-방식-설계에서는 런타임 통합(Webpack Module Federation)과 빌드타임 공유(UIKit, Shell Router 패키지)의 구체적인 설계를 다루고, 공통 모듈의 인터페이스를 정의한다.