header_logo

COLDSURF

Browse events

About

Blog

#react native

#microfrontend

#repack

#esbuild

Written by Paul
정확히 Micro Frontend라는 개념이 언제 도입되었는지는 모르겠습니다만, 근래 1~3년 사이에 웹 기반의 Micro Frontend라는 개념이 실무에 많이 자리잡힌 것 같습니다.
다만, 아직도 정확하게 “이것이 정답이다!”라고 하는 Micro Frontend 기법은 자신감 있게 말할 순 없을 것 같기도 합니다.

Multiple Port

마이크로프론트엔드의 개념은 다중 포트에서부터 시작하는 것 같습니다. 예시는 다음과 같습니다.
  1. 나는 쇼핑몰을 운영하는 회사에서 프런트엔드 개발자로 일한다.
  1. 해당 쇼핑몰의 여러 페이지 및 기능들 중 나는 “마이페이지”에 대한 기능만 개발하고 있다.
  1. 나의 옆자리 동료는 “구매페이지”에 대한 기능만 개발하고 있다.
이러한 상황에서, 갑자기 쇼핑몰이 잘 되기 시작하고, 사업적으로 여러 기능 및 페이지를 다루게 되었다고 가정합니다. 따라서 “마이페이지 팀”, “구매페이지 팀” 등 다양한 목적을 가진 팀들이 생겨납니다.
하지만 프런트엔드라는 전체 팀에서 관리하고 있는 서비스는 단 하나의 웹 서비스 입니다. 이러한 상황에서 어떻게 팀별로 잘 분할하여 서로에게 영향을 끼치지 않는 배포까지 이루어지게 할 수 있을까요?
바로 하나의 서비스에서(하나의 단일 host 앱) 포트를 여러개로 분할하여 팀별로 깃등을 관리하여 일을 하는 방식이 생겨나게 됩니다.
제 개인적인 견해로는 “하나의 호스트 앱 안의 다중 포트” 마이크로프론트엔드의 핵심 개념인 것 같습니다.

Monorepo

그렇다면, 실무단에서 이러한 형상들을 코드로 어떻게 구성할 수 있을까하는 고민을 하게 되었습니다. 가장 단순하게는 아키텍쳐적으로 모노레포 형상을 구성하는 것 입니다. 하지만 단순히 모노레포를 구성했다고 하여 마이크로프론트엔드 기법을 적극 도입했다고는 할 수 없습니다.
모노레포의 아키텍쳐는 다음과 같습니다.
📦 root (레포 루트) ├── 📂 apps (애플리케이션 모음) │ ├── 📂 web-app (프론트엔드 애플리케이션) │ │ ├── 📂 src │ │ ├── 📂 public │ │ ├── 📄 package.json │ │ ├── 📄 tsconfig.json │ │ ├── 📄 next.config.js (Next.js 사용 시) │ │ ├── 📄 vite.config.ts (Vite 사용 시) │ │ └── ... │ ├── 📂 mobile-app (모바일 애플리케이션) │ │ ├── 📂 src │ │ ├── 📄 package.json │ │ ├── 📄 tsconfig.json │ │ ├── 📄 metro.config.js (React Native 사용 시) │ │ └── ... │ ├── 📂 backend (백엔드 서비스) │ │ ├── 📂 src │ │ ├── 📂 prisma (DB 스키마, 마이그레이션) │ │ ├── 📄 package.json │ │ ├── 📄 tsconfig.json │ │ ├── 📄 server.js (Express 사용 시) │ │ └── ... │ ├── 📂 admin-dashboard (관리자 대시보드) │ │ ├── 📂 src │ │ ├── 📄 package.json │ │ ├── 📄 tsconfig.json │ │ ├── 📄 vite.config.ts │ │ └── ... ├── 📂 packages (혹은 apps, modules 등) │ ├── 📂 package-a │ │ ├── 📂 src │ │ ├── 📄 package.json │ │ ├── 📄 tsconfig.json │ │ └── ... │ ├── 📂 package-b │ │ ├── 📂 src │ │ ├── 📄 package.json │ │ └── ... │ ├── 📂 shared (공통 모듈) │ │ ├── 📂 src │ │ ├── 📄 package.json │ │ └── ... │ └── ... ├── 📂 tools (빌드 및 개발 도구) │ ├── 📄 eslint-config.js │ ├── 📄 prettier.config.js │ └── ... ├── 📂 config (전역 설정 파일) │ ├── 📄 tsconfig.base.json │ ├── 📄 webpack.config.js │ └── ... ├── 📂 scripts (유틸리티 스크립트) │ ├── 📄 build.js │ ├── 📄 deploy.sh │ └── ... ├── 📂 .github (GitHub Actions, PR 템플릿 등) ├── 📄 package.json (루트 패키지 관리) ├── 📄 lerna.json / nx.json (모노레포 툴 설정) ├── 📄 pnpm-workspace.yaml (pnpm 사용 시) ├── 📄 yarn.lock / pnpm-lock.yaml / package-lock.json └── 📄 README.md
익히 잘 알려진 패턴이며, 위의 예제는 여러개의 서비스를 포함합니다. 만약 하나의 서비스를 모노레포로 관리한다면 다음과 같은 트리구조가 나올 것 같습니다.
📦 root (레포 루트) ├── 📂 apps (애플리케이션 모음) │ ├── 📂 host-app (프론트엔드 애플리케이션) │ │ ├── 📂 src │ │ ├── 📂 public │ │ ├── 📄 package.json │ │ ├── 📄 tsconfig.json │ │ ├── 📄 next.config.js (Next.js 사용 시) │ │ ├── 📄 vite.config.ts (Vite 사용 시) │ │ └── ... │ ├── 📂 pages (웹 페이지 및 모바일 스크린을 구성하는 컴포넌트) │ │ ├── 📂 src │ │ ├── 📄 package.json │ │ ├── 📄 tsconfig.json │ │ └── ... │ ├── 📂 states (상태관리) │ │ ├── 📂 src │ │ ├── 📄 package.json │ │ ├── 📄 state.js │ │ └── ... │ │ └── ... ├── 📂 packages (혹은 apps, modules 등) │ ├── 📂 ui-library │ │ ├── 📂 src │ │ ├── 📄 package.json │ │ ├── 📄 tsconfig.json │ │ └── ... │ ├── 📂 package-b │ │ ├── 📂 src │ │ ├── 📄 package.json │ │ └── ... │ ├── 📂 shared (공통 모듈) │ │ ├── 📂 src │ │ ├── 📄 package.json │ │ └── ... │ └── ... ├── 📂 tools (빌드 및 개발 도구) │ ├── 📄 eslint-config.js │ ├── 📄 prettier.config.js │ └── ... ├── 📂 config (전역 설정 파일) │ ├── 📄 tsconfig.base.json │ ├── 📄 webpack.config.js │ └── ... ├── 📂 scripts (유틸리티 스크립트) │ ├── 📄 build.js │ ├── 📄 deploy.sh │ └── ... ├── 📂 .github (GitHub Actions, PR 템플릿 등) ├── 📄 package.json (루트 패키지 관리) ├── 📄 lerna.json / nx.json (모노레포 툴 설정) ├── 📄 pnpm-workspace.yaml (pnpm 사용 시) ├── 📄 yarn.lock / pnpm-lock.yaml / package-lock.json └── 📄 README.md
위와같은 구조로써 하나의 서비스안에 들어가는 여러 요소들을 모노레포로 관리 할 수도 있을 것 같습니다. 하지만 위의 구조만 이루었다고 해서, 실제 팀별 배포에 들어가는 주기를 단축시킬수는 없을 것 같은 느낌이 듭니다. 만약 “하나의 서비스에서 페이지별로 배포도 다르게 하고 싶다면 어떻게 해야 할까?”라는 고민이 들기 시작합니다.

Module Federation

이때 나오는 해결책이 Module Federation이라는 개념입니다. 모듈 페더레이션은 단순하게 표현하면 여러가지의 모듈들을 각각 관리하여 배포까지 이룰 수 있는 아키텍쳐적인 개념이라고 생각합니다. 예시는 다음과 같습니다.
  • http://localhost:3000/my-page
  • http://localhost:3001/purchase-page
위와 같이 하나의 서비스에서도 호스트앱을 달리하여 포트를 여러개 만들어서 개발 할 수 있습니다. 실제 배포를 하여 프로덕션단에서 합쳐질 때에는 Module Federation을 사용하여 리모트 저장소에 JS chunk 파일들을 배포하여 하나의 연결된 서비스를 구성하곤 합니다.
예를들어 다음과 같습니다.
  • https://cdn.example.com/my-page.chunk.js
  • https://cdn.example.com/purchase-page.chunk.js
와 같은 구조로 s3 등 리모트 저장소에 번들링 된 청크파일들을 올려놓습니다.
실제 유저가 https://example.com 으로 진입하게 됩니다. 호스트앱에서 관리하는 청크파일들을 가져오고 메인 페이지에 도달합니다. 만약 유저가 마이페이지로 진입한다면, <script /> 태그dynamic import를 통해서 federated 된 페이지를 동적 로드합니다.
모듈 페더레이션은 대표적으로 webpack에서 제공하는 webpack module federation이라는 방식이 있습니다. 각각의 모듈에서 expose할 컴포넌트 등을 정의하여 manifest.json 등에 약속된 규칙대로 다이나믹하게 모듈들을 불러옵니다.

RE.Pack 그리고 react native

만약 이러한 마이크로프론트엔드 기법을 react native 기반의 모바일 앱에 적용한다면 어떨까요?
기본적으로 react native는 번들러로써 metro bundler를 탑재하고 있습니다. 해당 metro bundler는 직접적으로 webpack과 같은 Module Federation을 제공하진 않습니다. 따라서 대체 번들러로써 RE.Pack을 쓸 수 있습니다.
repack은 stable v4 기준, metro bundler를 webpack으로 바꾸는 기능을 제공합니다. webpack이 메트로 번들러보다 속도는 느린감이 있다고 합니다만, 더 다양한 bundler의 역할을 커스터마이징 할 수 때문에 webpack을 react native의 번들러로 택한 것 같습니다.
experimental v5 기준으로는 rspack을 제공합니다. (물론 webpack도 같이 제공합니다)
react-native.config.js의 command를 바꾸는 것으로 rspack 혹은 webpack을 번들러로 사용할 지 정할 수 있습니다.
re.pack에서는 Module Federation 기능도 제공합니다.
host app이 있고, 그를 구성하는 다양한 sub app들로 구성되는 개념은 웹과 같습니다. 다만 중요한 개념은 ScriptManager라는 모듈인 것 같습니다.
iOS 코드 기준 ScriptManager는 다음과 같습니다. https://github.com/callstack/repack/blob/main/packages/repack/ios/ScriptManager.mm
이 부분이 웹 브라우저의 <script /> 태그를 대체하는 역할을 하는 것 같습니다. 또한 worklet 등의 특이한 문법을 사용하는 Reanimated도 번들링 플러그인으로 제공하여 호환이 가능합니다.
모듈 페더레이션의 예제도 제공하고 있으니, 참고하시면 좋겠습니다.
 
 
OUTSPIRED: “IN”spired를 받고 “OUT”된 생각을 뜻합니다.
마이크로 서비스에 이어서, 마이크로 프론트엔드! JS 생태계는 여전히 진화중이다.
 

© 2024 COLDSURF, Inc.

Privacy Policy

Terms of Service