6 min read

브라우저에서 dev-cli를 제어하다 — MFA 개발환경 DX 개선기

모노레포 + Module Federation 기반 MFA 구조에서 로컬 개발을 하다 보면, 모듈 전환이나 환경 변경을 위해 터미널로 돌아가는 일이 잦습니다.

dev-cli를 종료하고 모듈을 다시 선택해서 재시작하거나, .env를 수정하고 다시 띄우거나, 언어팩 업데이트를 위해 터미널에서 별도 명령을 실행하거나. 동료들 사이에서도 이거 브라우저에서 바로 되면 좋겠다는 이야기가 종종 나왔습니다.

그래서 만들어봤습니다.


아이디어

먼저 배경을 좀 설명하면, MFA 구조에서 각 모듈은 두 가지 방식으로 로드됩니다.

  • LOCAL: 로컬에서 dev server를 띄워서 HMR로 개발하는 모듈. 코드를 고치면 바로 반영됩니다.
  • CDN: 이미 배포된 버전을 그대로 가져다 쓰는 모듈. 빌드 없이 즉시 로드됩니다.

보통 작업 중인 모듈 1~2개만 LOCAL로 띄우고, 나머지는 CDN에서 가져옵니다. 전체를 다 로컬로 돌리면 리소스도 많이 먹고 시작도 느리니까요. 문제는 "어떤 모듈을 LOCAL로 할지"를 바꿀 때마다 dev-cli를 재시작해야 한다는 점이었습니다.

기존에 콘솔 앱 안에 자체 DevTools 패널이 있었습니다. Ctrl+Shift+D로 여는 건데, API Base URL 전환 정도만 가능한 상태였습니다.

여기에 기능을 더 붙이되, 접근 방식은 단순하게 가기로 했습니다. dev-cli에 HTTP API를 추가해서 브라우저와 통신하게 만드는 겁니다.

언어팩도 비슷한 맥락입니다. 번역 파일이 S3에 올라가 있고, 갱신하려면 Lambda를 호출해야 합니다. 원래는 CLI나 Slack 봇으로 처리하던 건데, 개발 중에 방금 추가한 번역 키가 반영됐는지 확인하려면 터미널로 나가야 했습니다. 비개발 직군(기획, QA)도 로컬 환경에서 언어팩을 갱신할 일이 있어서, 브라우저에서 버튼 하나로 되면 좋겠다는 생각이었습니다.

WebSocket도 고려했지만, 단방향 요청-응답이면 충분한 상황이라 HTTP로 결정했습니다. 실시간 상태 동기화가 필요한 게 아니라 버튼 누르면 동작한다 수준이니까요.


구현

Federation 정보 읽기

Module Federation Enhanced 플러그인은 런타임에 window.__FEDERATION__.__INSTANCES__를 노출합니다. 여기서 현재 로드된 모든 remote의 이름과 entry URL을 가져올 수 있습니다.

const instances = window.__FEDERATION__.__INSTANCES__;
const remotes = instances[0]?.options?.remotes ?? [];
// entry URL에 'local.'이 포함 → LOCAL, 아니면 CDN

이걸 테이블로 표시하면 지금 어떤 모듈이 어디서 오고 있는지가 한눈에 보입니다.

dev-cli에 HTTP 서버 추가

dev-cli가 serve 모드일 때 별도 포트(19999)에 간단한 HTTP 서버를 띄웁니다.

const server = http.createServer(async (req, res) => {
  res.setHeader('Access-Control-Allow-Origin', '*');
  
  if (req.url === '/api/health') { /* 연결 확인 */ }
  if (req.url === '/api/langpack') { /* Lambda 호출 */ }
  if (req.url === '/api/modules') { /* 모듈 재구성 */ }
});
server.listen(19999);

브라우저 DevTools 패널에서 fetch('http://localhost:19999/api/...')로 호출. 패널이 마운트될 때 /api/health를 먼저 쏴서 dev-cli 연결 여부를 확인하고, 안 되면 기능을 비활성화합니다.

언어팩 업데이트

기존에 dev-cli 내부에 있던 languageUpdate 함수를 API로 감쌌습니다.

버튼 클릭 → dev-cli가 Lambda 호출 → 성공 시 브라우저 자동 리로드. SSO 세션이 만료됐으면 안내 메시지를 띄우고 끝.

모듈 LOCAL/CDN 전환 (핵심)

이게 가장 까다로웠습니다. Module Federation의 remote URL은 빌드 시점에 결정되기 때문에 런타임에는 바꿀 수 없습니다. 결국 rsbuild 프로세스를 재시작해야 합니다.

흐름은 이렇습니다:

  1. 브라우저에서 모듈 토글 + CDN 환경 선택
  2. "적용" 클릭 → fullscreen overlay 표시
  3. POST /api/modules로 변경 내역 전송
  4. dev-cli가 내부적으로:
    • 현재 rsbuild 프로세스 전부 kill
    • 설정 파일 갱신
    • 2초 대기 (포트 해제)
    • 새 설정으로 프로세스 spawn
  5. 브라우저가 2초마다 /api/health 폴링
  6. 응답 오면 자동 리로드

dev-cli 프로세스 자체는 죽이지 않는다는 점입니다. HTTP 서버는 계속 살아있고, 내부의 child process만 교체합니다. serve 함수의 프로세스 관리 로직을 클로저로 캡처해서 API 콜백에 노출한 구조입니다.


삽질

포트 해제 타이밍

rsbuild 프로세스를 kill한 직후에 새 프로세스를 spawn하면 포트 충돌이 납니다. OS가 포트를 해제하는 데 시간이 걸리거든요. 처음에는 500ms로 했다가 간헐적으로 실패해서, 2초로 늘렸습니다. 우아하진 않지만 확실합니다.

health 폴링 vs 재시작 완료 감지

dev-cli가 준비됐다는 걸 어떻게 알 수 있을까. rsbuild의 stdout에 "compiled successfully"가 찍히는 시점을 API로 노출하는 방법도 있었는데, 모듈마다 빌드 완료 시점이 다릅니다.

결국 단순하게 갔습니다. health 엔드포인트가 응답하면 최소한 서버는 떴다고 판단하고 리로드. 모듈이 아직 빌드 중이면 브라우저에서 로딩 fallback이 뜰 뿐이니까, 실사용에서 문제는 없었습니다.

CORS

dev-cli가 19999 포트, 콘솔 앱이 3000번대 포트. Origin이 다르니까 당연히 CORS. Access-Control-Allow-Origin: *로 열었습니다. 어차피 localhost끼리의 통신이고, 개발 전용이니까 보안 고려 대상이 아닙니다.


결과

작업 Before After
모듈 전환 터미널 → Ctrl+C → 재선택 → 대기 브라우저 토글 → 적용 → 자동 리로드
CDN 환경 변경 .env 수정 → 재시작 드롭다운 선택 → 적용
언어팩 업데이트 터미널 전환 → 명령 실행 → F5 버튼 클릭 → 자동 리로드
현재 모듈 상태 확인 터미널 로그 확인 DevTools 패널 테이블

체감상 가장 큰 변화는 컨텍스트 스위칭이 줄었다는 점입니다. 브라우저에서 문제를 발견하고, 환경을 바꾸고, 결과를 확인하는 흐름이 끊기지 않습니다.


참고