💻💀/개발

[Monorepo] 여러 패키지에서 React Context Instance 공유하기(feat. Module Federation)

db2 2022. 7. 31. 22:23

들어가기에 앞서

먼저 내가 테스트한 Monorepo의 구조이다.

host/
    src/index.tsx     # Context.Provider 적용
remotes/
    remote1/
    remote2/
    ...
shared/
    context/          # Context 로직  
  1. React Context와 관련된 로직은 shared 패키지에 있다.
  2. host 패키지에서 App 컴포넌트를 Context의 Provider로 감싸 전체 App이 Context를 구독하도록 한다.
  3. Context가 필요한 remotes 패키지에서 useContext()를 사용한다.

React Context의 Instance가 생성되는 시점

테마를 적용하는 Context가 있다. 1️⃣ 에서 createContext()를 호출했고 그 결과 새로운 타입의 Context인 ThemeContext가 만들어졌다. 인스턴스가 생성된 것이 아니다. (참고)

// shared/context/ThemeProvider.tsx

type Theme = typeof THEME_DARK | typeof THEME_LIGHT

export const ThemeContext = createContext<Theme>('' as Theme) // 1️⃣
export const ThemeUpdateContext = createContext<
    React.Dispatch<React.SetStateAction<Theme>>
>(() => {})

ThemeContext.displayName = 'ThemeContext'
ThemeUpdateContext.displayName = 'ThemeUpdateContext'

export const ThemeProvider = ({ children }) => {
    ...
    return (
        <ThemeContext.Provider value={...}>
            <ThemeUpdateContext.Provider value={...}>
                {children}
            </ThemeUpdateContext.Provider>
        </ThemeContext.Provider>
    )
}

ThemeContext의 인스턴스가 생성되는 시점은 바로 App 컴포넌트에 ThemeContext.Provider를 적용하는 2️⃣ 부분이다. 즉, React Context의 인스턴스가 생성되는 곳은 host 패키지라는 뜻이다.

// host/src/index.tsx

import { createRoot } from 'react-dom/client'
import { ThemeProvider } from '@db/shared'
import App from './App'

const container = document.getElementById('app')
const root = createRoot(container!)

root.render(
    <ThemeProvider> // 2️⃣
          <App />
    </ThemeProvider>                      
)

하지만 host 패키지는 흩어져있는 remotes 패키지 내의 컴포넌트와 페이지를 모아 전체 어플리케이션의 라우팅을 처리해주는 역할을 할 뿐이다. 실제로 ThemeContext의 theme 상태를 사용하는 것은 remotes 패키지들이다. 그러기 위해선 host 패키지에서 생성된 인스턴스를 다른 remotes 패키지로 공유하는 것이 필요하다.

Module Federation의 shared 옵션

Webpack의 Module Federation을 사용하면 쉽게 공유가 가능하다.

// host/webpack.config.js

const webpack = require('webpack')
const { ModuleFederationPlugin } = webpack.container
const deps = require('./package.json').dependencies // 디펜던시 안에 shared 패키지 존재

module.exports = {
    plugins: [
        new ModuleFederationPlugin({
          shared: {
              ...deps, 
              react: { singleton: true, requiredVersion: deps['react'] },  
          },
        }),
    ]
}