Skip to content

빌드타임 공유에서 런타임 공유로 (Phase 2~3)

패키지 매니저 변경과 NX 도입을 거쳐, 최종적으로 Module Federation 런타임 통합과 독립 배포 환경을 구축한다.

학습 목표

  1. 빌드타임 코드 공유에서 패키지 빌드 후 공유로 전환하는 이유를 이해한다.
  2. Phantom Dependency 제거를 위한 패키지 매니저 변경 과정을 파악한다.
  3. NX를 활용한 빌드 오케스트레이션의 필요성을 설명할 수 있다.
  4. Webpack Module Federation을 통한 런타임 통합 메커니즘을 이해한다.
  5. 마이크로 앱의 독립 개발 환경 구축과 배포 파이프라인을 설명할 수 있다.

본문

1. Phase 2: 빌드타임 공유에서 패키지 빌드 후 공유로

Phase 1에서 코드를 분리하고 의존성을 정리했지만, 여전히 모든 패키지는 소스코드 수준에서 직접 참조되고 있었다. 이제 인프라성 패키지를 실제로 빌드하여 결과물(dist/)을 통해 공유하는 단계로 넘어간다.

패키지가 늘어난 시점의 구조

코드 분리와 의존성 정리를 거치면서 패키지가 크게 늘어났다.

패키지역할레이어
UIKit디자인 시스템 컴포넌트 라이브러리 (Emotion 기반)Core
APIAPI 호출 유틸리티, HTTP ClientCore
Shared Core전체 서비스 공통 유틸리티Core
Main Service (Shell)Application Shell, 라우팅 허브App
Drive, Mail, Task, ...각 도메인 서비스 앱App

이 인프라 패키지들은 적절한 인터페이스만 열고 내부적으로 유지보수할 수 있도록 패키지 빌드 및 설정이 필요했다.

패키지 빌드 전환을 위해 필요한 세 가지 작업


2. 패키지 매니저 변경: Yarn Classic -> pnpm

변경 여정

Phantom Dependency를 해결하기 위해 패키지 매니저를 변경해야 했다.

단계패키지 매니저경험
초기Yarn ClassicPhantom Dependency 문제 존재
1차 변경Yarn Berry (PnP)저장소 무거움, 클론 느림, 비표준 모듈 해석
최종 변경pnpm표준 node_modules 흐름 + Phantom Dependency 해결

Yarn Berry의 Plug'n'Play(PnP)는 node_modules를 사용하지 않고 자체 모듈 리졸버를 사용하기 때문에, 많은 도구에서 비표준적인 설정을 추가해야 했다. 또한 워크스페이스와 패키지가 많아지면서 저장소가 무거워져 클론 시간이 크게 늘었다.

pnpm으로 최종 변경한 뒤의 이점:

  • node_modules의 자연스러운 흐름을 유지
  • 심볼릭 링크 기반으로 Phantom Dependency를 원천 차단
  • 각 패키지의 package.json에 실제 사용하는 의존성만 정확하게 명시 가능

전환 후에는 각 패키지가 실제로 사용하는 의존성만 package.json에 남기고, 불필요한 의존성을 제거하는 대규모 정리 작업을 수행했다.


3. NX 도입: 빌드 오케스트레이션

패키지가 늘어나면서 빌드 순서를 사람이 직접 관리하기 어려워졌다.

예를 들어:

  • Shared Core를 먼저 빌드해야 API 패키지가 빌드 가능
  • API 패키지가 빌드되어야 Drive, Mail 등이 빌드 가능
  • UIKit도 먼저 빌드되어야 App 레이어가 사용 가능

NX를 도입하여 다음 기능을 활용했다:

  • 태스크 그래프: 패키지 간 의존 관계를 분석하여 빌드 순서를 자동 결정
  • 병렬 실행: 독립적인 패키지는 동시에 빌드
  • 캐싱: 변경되지 않은 패키지는 빌드를 건너뛰고 캐시된 결과물 사용

빌드 도구 분리

대상빌드 도구이유
Core 패키지 (UIKit, API, Shared Core)Vite라이브러리 빌드에 적합
App 패키지 (Shell, Drive, Mail 등)WebpackModule Federation 플러그인 지원

기존에는 모든 것이 동일한 빌드 도구를 사용했지만, Module Federation은 Webpack 기반이므로 App 패키지는 Webpack으로 전환했다. Core 패키지는 Vite로 빌드하고, 그 결과물을 Webpack이 소비하는 구조를 만들었다.

이 과정에서 Vite 빌드 결과물이 Webpack에서 정상 동작하는지 지속적으로 테스트하며 점진적으로 적용해야 했다.


4. Phase 3: 런타임 모듈 페더레이션 전환

Phase 2까지는 빌드타임에 패키지를 공유했다. Phase 3에서는 런타임에 코드를 공유하여 각 서비스를 독립적으로 빌드하고 배포할 수 있도록 전환한다.

Module Federation 설정

기존에 TypeScript path alias로 참조하던 코드를 Module Federation의 remote/expose로 교체한다.

javascript
// Shell(Host) - webpack.config.js
new ModuleFederationPlugin({
  name: 'shell',
  remotes: {
    drive: 'drive@/drive/remoteEntry.js',
    mail: 'mail@/mail/remoteEntry.js',
  },
  shared: {
    react: { singleton: true },
    'react-dom': { singleton: true },
  },
});

// Drive(Remote) - webpack.config.js
new ModuleFederationPlugin({
  name: 'drive',
  filename: 'remoteEntry.js',
  exposes: {
    './DriveRouter': './src/shared/DriveRouter',
    './DriveAttachButton': './src/shared/DriveAttachButton',
  },
  shared: {
    react: { singleton: true },
    'react-dom': { singleton: true },
  },
});

핵심은 소스코드 자체에는 변경이 거의 없다는 점이다. Phase 1에서 TS path alias로 분리하고 lazy import를 적용해 두었기 때문에, Webpack 설정만 변경하면 런타임 통합으로 자연스럽게 전환된다.


5. 독립 개발 환경 구축

각 마이크로 앱이 독립적으로 개발할 수 있는 환경을 구축한다.

로컬 개발 환경 전략

시나리오방법
Drive 서비스 개발Shell + Drive 두 개의 dev server 실행
다른 서비스의 프래그먼트가 필요 없는 경우에러 바운더리로 대체, 해당 서비스 띄우지 않음
다른 서비스의 프래그먼트가 필요한 경우배포된 개발/프로덕션 서버의 remoteEntry.js를 참조

Drive 서비스를 개발할 때 Shell과 Drive만 로컬에서 실행하면 된다. 다른 서비스(Mail, Task 등)의 프래그먼트는 에러 바운더리로 감싸져 있으므로, 해당 서비스를 띄우지 않아도 Drive 개발에는 문제가 없다.

배포 전략

독립된 도메인이 아니라 동일한 Nginx 서버 내 하위 경로로 배포한다.

dooray.com/              -> Shell 정적 파일
dooray.com/drive/         -> Drive 정적 파일 (별도 디렉토리)
dooray.com/mail/          -> Mail 정적 파일 (별도 디렉토리)

이 방식의 장점:

  • 인증/세션 공유에 추가 작업이 필요 없음
  • CORS 등 보안 이슈가 발생하지 않음
  • 기존 인프라를 그대로 활용 가능

6. 전환 과정 요약: 각 Phase별 빌드/공유 방식 비교

구분Phase 1Phase 2Phase 3
코드 공유 방식TS path alias (소스 직접 참조)패키지 빌드 후 dist/ 참조Module Federation (런타임 로드)
빌드 주체Main Service만 빌드Core 패키지 빌드 + App 빌드각 서비스 독립 빌드
개발 서버Main Service 하나Main Service 하나Shell + 개별 서비스 서버
배포 단위전체 SPA 하나전체 SPA 하나서비스별 독립 배포
패키지 매니저Yarn Classicpnpmpnpm
빌드 도구Vite (전체)Vite (Core) + Webpack (App)Vite (Core) + Webpack (App)
오케스트레이션없음NXNX

7. 점진적 전환의 원칙

각 Phase를 넘어갈 때 다음 원칙을 지켰다:

  1. 소스코드 변경 최소화: 도구와 설정 변경이 주이며, 비즈니스 로직은 건드리지 않음
  2. 점진적 리스크 관리: 한 서비스(Drive)부터 전환하고, 안정화 후 다음 서비스(Mail) 진행
  3. 롤백 가능성 확보: 문제가 생기면 이전 단계로 돌아갈 수 있는 상태 유지
  4. 지속적 검증: 전환 후 반드시 전체 서비스가 정상 동작하는지 확인

핵심 정리

항목내용
Phase 2 목표소스코드 직접 참조에서 패키지 빌드 결과물 공유로 전환
패키지 매니저Yarn Classic -> Yarn Berry -> pnpm (최종)
오케스트레이션NX 도입 (빌드 순서 자동 결정, 캐싱)
빌드 도구 분리Core(Vite) + App(Webpack)
Phase 3 목표Module Federation으로 런타임 통합, 독립 배포
배포 전략동일 도메인 내 하위 경로 분리 (Nginx)
전환 순서Drive 먼저 전환 -> 안정화 -> Mail 전환 -> 순차 확대
핵심 원칙소스코드 변경 최소화, 점진적 리스크 관리

기억할 포인트:

  • Phase 1에서 TS path alias + lazy import로 미리 준비해 두었기 때문에, Module Federation 전환 시 소스코드 변경이 최소화되었다.
  • 패키지 매니저는 한 번에 정답을 찾지 못할 수 있다 (Yarn Berry -> pnpm 재전환).
  • 독립 배포가 가능해지면서 각 서비스 팀이 자체 QA/배포 파이프라인을 갖게 되었다.

다음 단계

다음 문서에서는 이 프로젝트를 돌아보며 교훈과 회고를 정리한다. Redux를 필수 기술로 지정한 것에 대한 후회, 프래그먼트와 라우팅 분리의 필요성, CSS-in-JS 선택에 대한 고찰 등 실전 경험에서 얻은 통찰을 다룬다.

다음: 04-교훈과-회고 ->