Skip to content

모노레포 환경 구축: 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 패키지 매니저 비교

기능npmyarnpnpm
의존성 관리평탄화(hoisting)평탄화(hoisting)Content-addressable 저장소
호이스팅 제어불가불가엄격 모드로 차단 가능
디스크 효율프로젝트마다 복사프로젝트마다 복사글로벌 저장소에서 심볼릭 링크
워크스페이스npm workspacesyarn workspacespnpm 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.yaml

4.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. 환경 구축 절차 요약

순서명령어 / 작업설명
1mkdir careerup && cd careerup프로젝트 루트 생성
2pnpm init루트 package.json 초기화
3corepack enable && corepack use pnpmpnpm 버전 고정
4pnpm-workspace.yaml 생성워크스페이스 범위 정의
5mkdir apps && cd apps앱 디렉터리 생성
6pnpm create mf-app (shell)create-mf-app으로 Shell 프로젝트 생성 (Port 3000)
7루트로 이동 후 pnpm install워크스페이스 의존성 설치
8패키지명 수정@careerup/monorepo, @careerup/shell
9pnpm add -Dw turbo루트에 Turborepo 설치
10turbo.json 생성빌드 파이프라인 정의
11루트 scripts 추가dev, build, serve 스크립트
12pnpm dev전체 개발 환경 실행 확인

주의사항: create-mf-app으로 생성한 프로젝트의 @types/react, @types/react-dom 버전이 오래된 경우 React 18.2에 맞게 수동 업데이트가 필요하다.


핵심 정리

  1. 커리어업의 런타임 앱과 빌드타임 패키지는 강한 연관 관계를 가지므로, 모노레포가 적합하다
  2. pnpm은 content-addressable 저장소로 디스크 효율이 높고, 엄격한 호이스팅 제어로 유령 의존성을 방지한다
  3. Turborepo는 의존 관계를 자동 해석하여 빌드 순서를 결정하고, 캐싱과 병렬 실행으로 빌드 속도를 최적화한다
  4. 디렉터리 구조는 apps/(5개 마이크로앱, Webpack)와 packages/(UIKit, Shell Router 등, Vite)로 분리한다
  5. pnpm-workspace.yaml로 워크스페이스를 정의하고, turbo.json으로 빌드 파이프라인을 설정한다
  6. 루트에서 pnpm dev 한 번으로 전체 개발 환경이 병렬 기동된다

다음 단계

다음 문서 04-통합-방식-설계에서는 런타임 통합(Webpack Module Federation)과 빌드타임 공유(UIKit, Shell Router 패키지)의 구체적인 설계를 다루고, 공통 모듈의 인터페이스를 정의한다.