웹 애플리케이션 페이지를 패키지로 개발해 본 경험 공유

May.09.2024 조호정

Web Frontend

웹 애플리케이션의 일부 페이지를 다른 레포지토리에서 개발하고 적용해 본 경험이 있으신가요? Module Federation이나 iframe 활용 등 여러 방법이 생각나실 텐데요. 이 글에서는 배민 사장님들께 제공되는 서비스인 배민 셀프서비스 내의 상품관리 페이지를 패키지 형태로 배포해 본 경험기를 공유합니다.

개발 배경

보통, 웹 애플리케이션에 새로운 페이지를 추가해야 하면 하나의 레포지토리에서 개발을 진행합니다. 하지만 새로운 페이지를 추가해야 하는데 다른 팀의 레포지토리에서 개발해야 한다면 어떨까요? 이 과제가 그랬습니다.
개발을 진행하는 과정 중에는 단순히 기능 구현뿐만 아니라 각 팀마다 크고 작은 여러 컨벤션들을 지켜야 하고, 구현한 기능의 코드 리뷰까지 완료되어야 비로소 내가 구현한 코드를 반영할 수 있는데요.
이런 과정을 다른 팀에서 관리하는 레포지토리에서 진행하는 것은 효율적이지 않기 때문에, 외부에서 개발을 진행해 완성한 페이지를 셀프서비스에서 불러오는 방법을 채택했습니다.

개발 방식 알아보기

다른 레포지토리에서 개발하기로 결정했다면 이제는 개발 방식을 정해야 할 시간입니다.
외부에서 개발한 코드를 웹 애플리케이션에 통합하는 방법으로는 Module Federation(이하 MF)과, 패키지로 배포하는 방법이 있는데요. 각 방식에 대한 장단점을 간단하게 알아보겠습니다.

Module Federation

Module Federation logo

webpack 5부터 지원하는 기능으로 별도로 컴파일되고 배포된 코드를 호스트에서 로드할 수 있는 방식입니다. 애플리케이션에서 제공하는 기능이 복잡해지고 다양해질수록 번들의 크기가 커지고 청크가 비대해져 빌드 속도가 느려지고 의존성 관리, 코드의 재구조화가 어려워집니다. 이를 해결하기 위해 원격지에서 개별적으로 개발하고 빌드한 페이지와 컴포넌트를 애플리케이션이 가져와 사용하는 방식입니다. 간단히 도식화하면 아래와 같습니다.

Module Federation structure

호스트(Host)는 원격에서 개발된 여러 리모트(Remote) 애플리케이션을 소비하지만, 사용자에게는 마치 하나의 애플리케이션처럼 보입니다. MF에 대해서 자세한 정보가 궁금하시다면 우아콘에서 이 주제로 발표한 "프론트엔드 개발의 미래, Module Federation의 적용" 영상을 참고해 주세요.

장점

  • npm 레지스트리에 패키지를 게시하거나 업데이트할 필요 없이 모듈 방식으로 유연하게 구성 가능.
  • 사용처에서 버전 관리에 대한 부담이 없고, 리모트의 변경 사항을 즉시 사용할 수 있음.

단점

  • 프로젝트에 추가적인 설정이 필요하여 복잡성이 증가함.
  • 런타임 로드 방식이기 때문에 약간의 네트워크 대기 시간이 발생함.

패키지 형태로 관리

npm logo

패키지 형태로 npm 레지스트리에 게시하여 버전 관리를 통해 재사용 가능한 코드를 공유할 수 있습니다.

npm structure

원격지에서 개발한 패키지를 빌드하여 레지스트리에 발행하고, 사용처에서는 install 명령을 통해 가져와서 사용하는 방식입니다.

장점

  • npm 패키지는 버전 관리를 지원하므로 게시된 산출물의 다양한 버전을 유지할 수 있고, 사용처에서 종속성을 관리하므로 버전 관리에 용이.
  • Node.js를 사용하는 모든 프로젝트에서 추가 설정 없이 폭넓은 호환성을 가지고 있음.

단점

  • 업데이트가 필요할 때마다 패키지의 새 버전을 게시하고, 사용처에서도 올바른 버전으로 업데이트 필요.
  • MF 대비 모놀리식한 방식으로, 마이크로 프론트엔드 아키텍처 관점이나 빠른 업데이트가 필요한 경우에는 부적합할 수 있음.

무엇을 선택했을까요?

외부 레포지토리에서 개발하고 웹 애플리케이션에 통합하는 두 가지 방식을 검토해 보았습니다. 두 방식 모두 요구사항을 만족할 수 있지만 아래와 같은 이유가 있어 패키지로 배포된 기능을 불러와서 사용하는 것으로 결정하였습니다.

  • MF 사용 시 호스트 애플리케이션의 추가적인 설정이 필요하며 산출물에 대한 안정성 검증이 필요한 점.
  • npm 레지스트리 내에 이전 버전들도 유지되기 때문에 호스트 애플리케이션에서 중앙화된 버전 관리가 가능하다는 점. (필요시 빠른 롤백 가능)
  • 당시 각 팀마다 큰 프로젝트를 진행하고 있어 일정이 굉장히 촉박했다는 점.

지금 돌이켜보면 기술적인 관점에서는 최고의 선택이었다고 하기에 어려울 수 있을 것 같아요. 하지만 MF를 적용하게 되면 호스트 애플리케이션도 수정이 필요하고, QA 과정도 필요해집니다.
최소 비용으로 목표를 달성했으니 패키지 배포가 최고는 아닐 수 있어도 효율적인 수단이라는 관점에서는 최선의 선택이었습니다.

개발 방식을 선택하였으니 이제 번들러를 선택할 차례입니다.

어떤 모듈 번들러를 사용할까?

모듈 번들러란 말 그대로 여러 모듈(코드 조각)을 하나로 번들링(묶어주는 것)하는 도구로, 시장에는 다양한 모듈 번들러들이 출시되어 있습니다. 그러나 번들러마다 특징이 다르므로 애플리케이션의 성격이나 목적에 맞는 번들러를 선택해 사용하는 것이 효율적이고 편리한 개발을 할 수 있습니다.

Vite vs Webpack

당시 팀 내에서는 패키지 개발의 모듈 번들러로 Vite를 채택해서 사용 중이었습니다. Vite 는 webpack과 더불어 모듈 번들러의 양대산맥처럼 여겨지는데요.


2024년 4월 기준 Vite와 webpack의 다운로드 수. 출처: npmtrends

아직 격차가 좀 있지만, Vite가 빠른 속도로 추격하고 있습니다.

인기의 척도를 나타내는 Stars는 거의 같지만, Vite는 webpack에 비해 비교적 짧은 역사를 가지고 있어 빠른 시간 안에 Star를 획득했음을 알 수 있습니다.
관리 측면에서는 최근까지 업데이트가 된 것으로 보아 둘 다 잘 되고 있음을 말해주고 있습니다.

webpack은 단순한 형태의 라이브러리부터 SPA(Single Page Application), MPA(Multiple Page Application)까지 다양한 프로젝트에 사용이 가능합니다.
그 오랜 역사에서 알 수 있듯이 번들러 중 가장 거대한 커뮤니티와 생태계를 갖추고 있습니다. 또한 그만큼 많은 기능을 지원하는데, 최신 버전에서는 많이 간소화 및 자동화되었다고는 하지만 초기 설정 및 구성이 어려울 수 있습니다.

Vite는 프랑스 말로 ‘빠르다’라는 의미의 단어입니다. 그 이름처럼 매우 빠른 성능을 가지고 있는데요, 특히 개발 서버를 구동하는 시간이 매우 짧은 특징을 가지고 있습니다.
Vite는 개발 서버를 구동할 때에는 esbuild를 사용하여 개발자에게 매우 빠른 콜드 스타트와 코드 변경에 대한 갱신을 가능하게 하고, 실제 production 빌드 시에는 rollup을 번들러로 사용하여 빠른 성능과 함께 유연한 설정과 확장성을 제공합니다.

이 프로젝트에서는 일정이 넉넉하지 않아 개발 속도가 굉장히 중요했는데요. 그런 점에서 Vite는 아주 좋은 선택이 되었습니다.


개발 서버 구동 시 매우 빠른 실행 속도를 보여줍니다

개발 서버 구동 자체도 매우 빠르지만 HMR(Hot Module Replacement) 역시 매우 빨라 코드 변경사항을 브라우저에서 수시로 확인해야 할 때 많은 시간을 절약할 수 있었습니다.


HMR(Hot Module Replacement) 적용 속도 예시입니다

기본적으로 Vite는 esbuild만을 사용하지만, emotion 등의 사용으로 트랜스파일(transpile)이 필요한 경우 그에 맞는 플러그인을 설정해 주어야 합니다.

이때 @vitejs/plugin-react를 사용하면 Babel을 통해 트랜스파일을 수행하게 되고, @vitejs/plugin-react-swc를 사용하면 SWC(Speedy Web Compiler)를 통해 작업을 수행하게 됩니다.
이 프로젝트에서는 SWC를 통한 트랜스파일링을 수행하도록 설정하였습니다.
같은 역할을 하는 Babel이나 tsc보다 작게는 몇 배, 상황에 따라 크게는 몇십 배 빠른 속도를 가지고 있기 때문입니다.

최종적으로 제가 구현할 프로젝트에서 설정한 vite.config.ts파일은 아래와 같습니다.

필수적이지 않은 몇몇 설정값을 제외하면, 상당히 간단한 설정만으로 프로젝트를 구성할 수 있습니다.

추가적으로 package.json 내에 아래와 같이 설정을 추가하여 외부에서 프로젝트에 대한 올바른 값을 가져갈 수 있도록 설정하였습니다.

이렇게 해서 제가 구현하고자 하는 프로젝트의 배포 방식과 번들러에 대한 결정을 완료하였습니다.

개발하면서 겪은 트러블슈팅

계획을 수립하는 단계에서 여러 시나리오들을 많이 고려하더라도 개발 중 많은 문제를 마주치게 되는데요, 저 역시도 계획 수립 당시에 생각하지 못했던 부분에 대한 아쉬움을 겪게 되었습니다.

구현한 기능을 바로 확인하는 방법

웹 페이지를 패키지로 구성하여 컴포넌트를 제공하기 때문에 패키지만으로는 애플리케이션으로 실행해 볼 수 없습니다. 때문에 기능의 구현 및 수정사항을 확인하기 위해서는 페이지를 구동할 기본적인 애플리케이션을 마련해야 했습니다. 그러나 beta 태그를 설정하여 배포하거나 로컬 패키지로 빌드하여 매번 별도의 애플리케이션에서 이 패키지를 설치하고 실행하는 것은 번거롭습니다. 그래서 명령어의 인수를 변경하면 패키지의 페이지를 포함한 간단한 SPA를 빌드하여 빠르게 확인할 수 있도록 구성하였습니다.

다음과 같이 페이지 컴포넌트를 사용하는 아주 간단한 파일을 생성합니다.

각 기능마다 추가로 전달해 주어야 하는 프롭이나 설정들을 위 코드에 추가해야 합니다.
또한 빌드 시 패키지 형태가 아니라 온전한 앱으로 동작이 가능하도록 설정해 주어야 합니다.
이를 위해서 vite.config.ts 파일 내에 아래와 같이 빌드 관련 설정을 추가해 주었고 이 스크립트는 Vite 설정에서 기본으로 제공하는 모드를 전달인자로 받아 처리하도록 구성하였습니다.

이제 다음과 같이 package.json 내에서 scripts 영역에 SPA 빌드를 위한 스크립트를 추가하고 실행할 수 있습니다.

SPA 빌드 시 index.html 이 생성됩니다.

이렇게 빌드된 산출물을 정적 웹 배포 파이프라인에 제공하거나 로컬 서버에서 구동하면 구현된 내용을 실제 환경에서 빠르게 테스트할 수 있습니다.

패키지 내에서의 실행 환경에 대한 처리

패키지로 구성하여 배포한 기능의 경우 사용처의 실행 환경에 대하여 런타임에 알 수 있는 방법이 없습니다.
패키지는 빌드되어 저장소에 올라가는 순간 이미 운영 배포가 완료된 것이 됩니다. 하지만 이 프로젝트로 구현되는 기능은 하나의 온전한 지면으로 그 안에서 서버 API 호출을 통한 통신을 하고 있었습니다. 사용처가 되는 호스트 애플리케이션에서 베타 환경 실행을 하여 베타 환경의 API 엔드 포인트를 호출하게 되더라도 패키지로 구성된 본 기능에서는 이미 운영 환경 배포가 완료되었기 때문에 현재 호스트 애플리케이션이 베타 환경에서 실행되었다는 것을 알 수 없어 운영 환경의 API 엔드 포인트를 호출하게 되는 문제가 있었습니다.
그래서 실행 환경을 prop으로 전달받아 그에 맞는 API 엔드 포인트를 호출할 수 있도록 구성하였습니다.

당시 이 기능을 사용할 사용처에서는 개발(dev), 베타(beta), 운영(prod), 카나리(canary) 환경을 구성하여 활용하고 있었고, 아래와 같이 엔드 포인트를 나누어서 구성하였습니다.

실제 API 호출 시에는 사용처에서 앱을 구동하는 때에 prop으로 전달해 주는 값을 이용하여 런타임에 엔드 포인트를 각기 다르게 호출하게 됩니다.

위와 같이 구현된 컴포넌트는 패키지 빌드 시 하나의 엔트리 포인트만을 제공하기 때문에 사용처에서 일반 컴포넌트를 가져와 사용하듯이 구현할 수 있습니다.

마무리하며

서두에 말씀드린 것처럼 개발 업무를 진행하다 보면 여러 환경적인 요인이나 조직의 내외부 상황에 의해 구현하는 방식을 달리해야 하는 경우들이 있습니다.
재사용을 위한 컴포넌트나 공통 로직을 패키지 형태로 배포한 경험은 있었지만 이처럼 하나의 웹 지면 전체를 패키지 형태로 배포하여 다른 애플리케이션에서 구동해 본 것은 처음이라, 개발하던 중 생각하지 못했던 상황들을 맞이하기도 했습니다.

이 경험이 원격지에서 개발된 코드를 불러와 사용하는 방법 중 가장 스마트하고 진보적인 방법이라고 말씀드릴 수는 없을 것 같습니다. 하지만 업무 진행에 있어서 중요한 사실은, 주어진 일정과 리소스는 언제나 유한하며 우리는 그 안에서 선택과 집중을 해야 한다는 것입니다. 그 측면에서 패키지 배포는 두 팀이 특별한 비용 없이도 적용할 수 있는 좋은 방법이었습니다.
또한, 추후 MF로 노선을 변경하더라도 기능 실행 시 이미 엔트리 포인트가 하나이기 때문에 적은 비용으로 목적을 달성할 수 있을 것이라 생각합니다.

익숙하지 않은 환경에서의 개발이었지만, 위와 같은 과정들을 거치며 큰 문제 없이 프로젝트를 마무리할 수 있었습니다.
물론 그 배경에는 저희 팀원 분들이 먼저 마련해두셨던 기술 리서치와 다양한 레퍼런스가 있었고, 이 페이지를 사용하게 될 조직에서도 저의 여러 문의에 빠르게 응대와 지원을 해주셔서 가능했습니다.

앞으로도 더욱 재밌는 경험을 하고 싶고, 또 그것을 여러분과 공유하고 싶습니다. 보다 좋은 글로 찾아오도록 하겠습니다.