grep

Engineering

react-native 에 모듈 페더레이션 더하기 (with. RePack)

직방

2023년 10월 26일

원문에서 보기 ↗

모듈 페더레이션(Module Federation) 은 애플리케이션을 여러 개의 작은 애플리케이션으로 분할하는 것을 말합니다. 마이크로 서비스와 유사하여 Micro-frontends 라고도 불립니다. 직방 프론트엔드 팀에서는 react-native를 사용하여 앱을 개발하면서 다음과 같은 미션을 수행하기 위해 모듈 페더레이션을 고려하고 있습니다.

모듈 페더레이션의 기능

모듈 페더레이션을 통해 다음과 같은 것을 이룰 수 있습니다.

모듈 페더레이션의 용어

모듈 페더레이션을 이해하기 위해서는 몇가지 용어를 익혀두어야 합니다.

[사진-1] 모듈 페더레이션 예시

react-native 에 모듈 페더레이션을 적용하기 위해서는 Re.Pack 라이브러리를 사용해야 합니다.

Re.Pack

Re.Pack은 callstack 에서 만든 React Native 어플리케이션을 위한 bundler 입니다. react-native cli 를 대체하고, 개발 서버로 작동하거나 React Native 앱을 번들링 하는 역할을 합니다. 또한, Re.Pack 은 Webpack 으로 제작된 번들을 react-native 애플리케이션에서 사용할 수 있게 만들어, 코드 스플리팅(Code splitting) 을 가능하게할 수 있습니다.

Re.Pack을 코드레벨에서 살펴보면서 간단하게 모듈 페더레이션을 살펴보도록 하겠습니다. 앱의 구조는 다음 그림과 같이 host앱이 app1, app2의 remote 앱을 가지는 구성을 해보도록 하겠습니다.

프로젝트의 구조는 다음과 같습니다.(monorepo 구조를 사용합니다)

각각의 모듈에서 독립적으로 구성 및 실행이 가능하도록 구성합니다.

/packages
  ㄴ host
    ㄴ android
    ㄴ ios
    ㄴ webpack.config.js
  ㄴ app1
    ㄴ android
    ㄴ ios
    ㄴ webpack.config.js
  ㄴ app2
    ㄴ android
    ㄴ ios
    ㄴ webpack.config.js

host 설정

host 앱은 app1, app2 컨테이너로부터 “./App” 모듈을 import 합니다.

원격으로 모듈을 불러오기때문에 로딩이 발생하게됩니다. 로딩을 처리하기 위하여 Suspense 와 ErrorBoundary를 사용하도록 합니다.

//App.tsx
import { Federated } from "@callstack/repack/client"

const App1 = React.lazy(() => Federated.importModule("app1", "./App"))
const App2 = React.lazy(() => Federated.importModule("app2", "./App"))

return (
  <ErrorBoundary>
   <React.Suspense
    fallback={
      <Loading />
    }>
    <App1 />
   </React.Suspense>
  </ErrorBoundary>
  <ErrorBoundary>
   <React.Suspense
    fallback={
      <Loading />
    }>
    <App2 />
   </React.Suspense>
  </ErrorBoundary>

host는 각 remote 모듈을 요청할 주소를 설정해주어야합니다. Re.Pack의 ScriptManager.shared.addResolver를 사용하여 주소를 설정하도록합니다.

// index.js
import { ScriptManager, Federated } from "@callstack/repack/client"

const resolveURL = Federated.createURLResolver({
 containers: {
  app1: __DEV__
   ? "http://localhost:9001/[name][ext]"
   : `https://CF.zigbang.in/chunks/app1/${Platform.OS}/[name][ext]`,
  app2: __DEV__
   ? "http://localhost:9002/[name][ext]"
   : `https://CF.zigbang.in/chunks/app2/${Platform.OS}/[name][ext]`,
 },
})

ScriptManager.shared.addResolver(async (scriptId, caller) => {
 const url = resolveURL(scriptId, caller)
 return {
  url,
  cache: false,
  query: {
   platform: Platform.OS,
  },
 }
})

개발시에는 localhost:9001, localhost:9002 주소를 사용하도록 하였기 때문에 host의 dev 서버 이외에도 각각의 모듈의 서버를 실행해주어야합니다. (host 앱은 기본적으로 localhost:8081 주소를 사용합니다.)

//package.json
"start": "react-native webpack-start --port 9001",
$app1 % yarn start
>Server listening at http://[::1]:9001
$app2 % yarn start
>Server listening at http://[::1]:9002

모듈 expose 설정

각 컨테이너에서는 host 에서 요청할 모듈을 expose 해주어야합니다.

//app1/webpack.config.js
plugins: [
      new Repack.plugins.ModuleFederationPlugin({
        name: 'app1',
        exposes: {
          './App': './src/App.tsx',
        },

//app2/webpack.config.js
plugins: [
      new Repack.plugins.ModuleFederationPlugin({
        name: 'app2',
        exposes: {
          './App': './src/App.tsx',
        },

보신 코드를 통해 코드 스플리팅과 모듈을 원격으로 불러오도록 구성이 가능하게 됩니다.

캐싱

remote 모듈은 요청 시 네트워크를 통해 다운로드를 하기 때문에 최적화를 위해 캐싱을 활용할 수 있습니다. 한번 다운로드된 모듈은 ScriptManager에 의해 캐싱되어 이후에 다시 요청을 할 때에는 다운로드를 하지 않습니다. 그렇다면 업데이트된 모듈을 다운로드하기 위해서는 어떻게 해야 할까요?

캐싱된 모듈을 사용하지 않고 모듈을 다시 다운로드하기 위해서 다음과 같은 방법을 사용할 수 있습니다.

Re.Pack 앱 배포

Re.Pack 앱을 배포할때에는 host 앱과 remote 모듈을 배포하는 방식이 다릅니다. host 앱을 배포하기 위하여 번들링 된 .ipa, .apk 파일을 만들어야 합니다.

이때 ios에서는 main.jsbundle 파일이 필요로 하는데 webpack-bundle 커맨드를 통해 번들을 생성하여 main.jsbundle 로 지정해주어야 합니다.

//기존 react-native main.jsbundle 생성
"build:ios": "react-native bundle --entry-file='index.js' --bundle-output='./ios/main.jsbundle' --reset-cache --dev=false --platform='ios'",
//repack에서 bundle 생성
"bundle:ios": "react-native webpack-bundle --platform ios --entry-file index.js  --bundle-output ./ios/main.jsbundle --dev=false",

remote 모듈을 배포를 할 때에도 webpack-bundle 커맨드를 통해 청크를 생성하여야 합니다. webpack-bundle을 통해 생성된 build는 다음과 같은 구조를 가지게 됩니다.

/build
  ㄴ ios
    ㄴ assets
    ㄴ app.container.bundle
    ㄴ app.container.bundle.map
    ㄴ index.bundle
    ㄴ index.bundle.map
    ㄴ src_App_tsx.chunk.bundle
    ㄴ src_App_tsx.chunk.bundle.map   

생성된 청크를 서버에 업로드하여 다운로드할 수 있도록 합니다. 저의 경우 S3에 업로드하였습니다.

Re.Pack 사용시 고려해야할 사항

Re.Pack을 적용하고 사용해 보면서 겪은 것들이 많았습니다.

마치며

react-native에 모듈 페더레이션을 도입하기 위해 Re.Pack을 사용한 경험을 소개해 드렸습니다.

모듈 페더레이션은 react-native를 사용하고 계시는 많은 개발자들에게 성숙한 서비스의 개발환경 개선을 위한 좋은 방안이라고 생각합니다. 이 글에서 제시하는 Re.Pack을 통해 모듈 페더레이션을 사용해 보시면 좋을 것 같습니다.

출처:

사진-1: https://rangle.io/blog/module-federation-federated-application-architectures