모듈 연합
모듈 연합 (Module Federation)
Module Federation은 런타임에 별도로 빌드된 JavaScript 애플리케이션 간에 코드와 의존성을 공유할 수 있게 해주는 기능이에요. Backstage에서 모듈 연합 지원을 사용하면 다음을 할 수 있어요.
출처: 문서
본문
소개 (Introduction)
Module Federation은 런타임에 별도로 빌드된 JavaScript 애플리케이션 간에 코드와 의존성을 공유할 수 있게 해주는 기능이에요. Backstage에서 모듈 연합 지원을 사용하면 다음을 할 수 있어요.
-
프론트엔드 애플리케이션을 런타임에 원격 모듈을 로드할 수 있는 모듈 연합 호스트(host)로 빌드
-
개별 플러그인이나 여러 플러그인 묶음을 동적으로 로드할 수 있는 모듈 연합 리모트(remote)로 패키징
-
호스트와 리모트 간에 의존성을 효율적으로 공유해 코드 중복을 방지
이 가이드는 Backstage에서 모듈 연합 호스트와 리모트를 모두 구성하고 빌드하는 방법, 그리고 표준 Module Federation Runtime API를 사용해 런타임에 모듈 연합을 초기화하는 방법을 설명해요.
개요 (Overview)
모듈 연합 호스트 대 리모트
모듈 연합 용어에서:
-
Host: 원격 모듈을 로드하고 소비하는 주요 프론트엔드 애플리케이션이에요. Backstage에서는 앱 패키지(보통
packages/app)예요. -
Remote: 호스트가 런타임에 로드할 수 있는 별도로 빌드된 모듈이에요. Backstage에서는 모듈 연합 리모트로 빌드된 플러그인 패키지가 일반적이에요.
공유 의존성 (Shared Dependencies)
모듈 연합의 핵심 측면은 공유 의존성이에요. 호스트가 원격 모듈을 로드할 때 둘 다 공통 의존성(React, React Router, Material-UI 등)을 공유해야 singleton 의존성이 인스턴스를 하나만 갖도록 보장해요.
Backstage는 React, React Router, Material-UI 같은 일반적인 패키지에 대한 기본 공유 의존성 목록을 제공해요. 빌드 시점에 version 필드는 package.json 파일에서 자동으로 해석돼요.
모듈 연합 호스트 빌드하기
모듈 연합 호스트는 주요 프론트엔드 애플리케이션이에요. 기본적으로 Backstage 프론트엔드 애플리케이션에는 기본 모듈 연합 공유 의존성 목록이 포함돼요.
프론트엔드 애플리케이션을 빌드하고 번들링할 때 CLI는 자동으로:
-
모노레포 의존성을 기반으로 공유 의존성 버전을 해석
-
해석된 런타임 공유 의존성 목록과 함께 프론트엔드 애플리케이션 번들에 추가 진입점(entrypoint)을 추가
모듈 연합 리모트 빌드하기
플러그인 패키지는 모듈 연합 리모트로 빌드할 수 있어 호스트 애플리케이션이 동적으로 로드할 수 있게 해요.
CLI 사용하기
플러그인을 모듈 연합 리모트로 빌드하려면 package build 명령과 함께 --module-federation 옵션을 사용하세요.
cd plugins/my-pluginyarn build --module-federation
빌드 출력
플러그인을 모듈 연합 리모트로 빌드할 때 CLI는:
-
모노레포 의존성을 기반으로 공유 의존성 버전을 해석(Rspack/Webpack 모듈 연합 플러그인이 자동으로 수행)
-
dist폴더에 번들 자산을 생성하며, 다음을 포함: -
모듈 연합 매니페스트를 담고 있는
mf-manifest.json파일 -
원격 모듈의 주요 진입점인
remoteEntry.js파일
런타임 사용 (Runtime Usage)
Backstage 앱에서 모듈 연합을 사용하려면 공유 의존성 구성으로 Module Federation Runtime을 초기화해야 해요.
기본 사용 (Basic Usage)
앱에서 모듈 연합을 초기화하고 원격 모듈을 로드하는 방법은 다음과 같아요.
packages/app/src/moduleFederation.ts
import { createInstance, ModuleFederation,} from '@module-federation/enhanced/runtime';import { loadModuleFederationHostShared } from '@backstage/module-federation-common';export async function initializeModuleFederation(): Promise<ModuleFederation> { return createInstance({ name: 'app', remotes: [ { name: 'my_plugin', entry: 'http://localhost:3001/mf-manifest.json', }, ], shared: await loadModuleFederationHostShared(), });}export async function loadRemote( instance: ModuleFederation, name: string,): Promise<any> { return await instance.loadRemote<any>(name);}
loadModuleFederationHostShared 함수는 모든 공유 의존성을 병렬로 로드하고 Module Federation Runtime이 기대하는 형식으로 반환해요. 기본적으로 공유 의존성 로드에 실패하면 던져요(throw). 대신 onError 콜백을 전달해 오류를 우아하게 처리할 수 있어요.
const shared = await loadModuleFederationHostShared({ onError: error => console.error(error.message, error.cause),});
기능 로더와의 통합 (Integration with Feature Loaders)
표준 Module Federation 런타임 API는 아래 예시에서 보는 것처럼 프론트엔드 기능 로더와 매우 잘 통합돼요.
packages/app/src/loader.tsx
import { createInstance } from '@module-federation/enhanced/runtime';import { loadModuleFederationHostShared } from '@backstage/module-federation-common';import { createFrontendFeatureLoader } from '@backstage/frontend-plugin-api';export const moduleFederationLoader = createFrontendFeatureLoader({ async loader() { const moduleFederationInstance = createInstance({ name: 'app', remotes: [], shared: await loadModuleFederationHostShared(), }); moduleFederationInstance.registerRemotes([ { name: 'myFirstRemoteWith2ExposedModules', entry: 'https://someCDN.org/myFirstRemoteWith2ExposedModules/mf-manifest.json', }, { name: 'mySecondRemote', entry: 'https://someCDN.org/mySecondRemote/mf-manifest.json', }, ]); const myFirstRemoteModule1 = await moduleFederationInstance.loadRemote<any>( 'myFirstRemoteWith2ExposedModules/module1', ); const myFirstRemoteModule2 = await moduleFederationInstance.loadRemote<any>( 'myFirstRemoteWith2ExposedModules/module2', ); const mySecondRemoteModule = await moduleFederationInstance.loadRemote<any>( 'mySecondRemote', ); return [ myFirstRemoteModule1.default, myFirstRemoteModule2.default, mySecondRemoteModule.default, ]; },});const app = createApp({ features: [moduleFederationLoader],});export default app.createRoot();
표준 API에 더해, 모듈 연합 리모트를 구성하는 더 간단한 방법을 제공할 계획이라는 점에 유의하세요.
추가로 @backstage/frontend-dynamic-feature-loader 패키지에 제공되는 dynamicFrontendFeaturesLoader는 모듈 연합 리모트를 동적 프론트엔드 플러그인으로 로드하는 통합 솔루션을 제공하며, 모듈 연합 지원을 기반으로 하는 기능 로더의 더 완전한 예시예요.
기본 공유 의존성 (Default Shared Dependencies)
기본 공유 의존성은 호스트와 리모트 모두 동일하며, 목록은 @backstage/module-federation-common 패키지에서 찾을 수 있어요.