Module-Federation

camille·2022년 12월 12일
0

서비스의 규모가 커지면 커질수록 앱에 들어가는 피처들이 많아지고 피처들 사이의 의존성이 생기기 마련이다.

이런 의존성을 제거하기 위해 공통적으로 사용할 것들을 (ex. 컴포넌트 단위 혹은 서비스 단위)를 독립적으로 구현해 놓고 앱에서 불러와 사용하곤 하는데

분뤼된 각각의 컴포넌트, 서비스 단위로 따로 개발하여 적용할 수 있지만 수정사항을 반영하려면 앱을 다시 빌드 하고 배포하는 과정이 필요하다. 아주 작은 단위의 수정사항이 있더라도 수십분의 배포 시간을 거쳐야 하는것이다.

이전 마이크로 프론트엔드의 빌드타임 통합 방식은 모노레포를 사용함으로써 가능했지만 결국 여러 패키지를 한번에 같이 빌드해야 했던 것이 한계가 있었다.

하지만 Module Fedetaion이 등장하면서 여러 개의 애플리케이션을 따로 빌드한 다음 런타임에 통합하여 하나의 애플리케이션으로 동작하게 해주는 마법같은 일을 Webpack5Module Fedetaion이 현실화 할 수 있다.

Module Federation이란?

2020년 10월에 공식적으로 릴리즈된 Webpack5는 Webpack4로 부터 성능 개선 뿐만 아니라 다양한 새로운 기능들이 추가되었다.

Module Federation은 여러 분리된 빌드들이 하나의 앱을 구성할 수 있는 Webpack5의 새로기능이다. 하나의 앱이 다른 빌드에 있는 코드를 동적으로 실행 시킬 수 있는 기술이다.

프론트엔드계애서 핫한 Micro-Frontends의 근간이 되는 기술이기도 하는데,

특정 빌드가 Remote앱이 되고 그 앱에서 다른 빌드들을 동적으로 불러와 사용할 수있다. Remote는 꼭 특정한 하나의 빌드만 될 수 있는 것은 아니다. Federated된 모든 빌드들이 Remote가 될 수 있다.

양방향(Bidirectional)으로 Module Federation이 가능하다. 예를 들어 A 빌드에서 B 빌드에 있는 코드를 실행시킬 수 있고, B빌드에서도 A빌드에 있는 코드를 실행시킬 수 있다는 것이다.

이론적으로는 전방향(Omnidirectional)로도 가능하다고 한다.

용어정리

Webpack에서 모듈이라고 하는 것은 Webpack으로 빌드할 때 사용하는 코드를 포함한 모든 리소스를 말한다. 자바스크립트, CSS, HTML, JSON, 각종 이미지 파일등이며 Module Federation의 Module은 특히 자바스크립트의 모듈을 의미한다. 하지만 CSS, JSON 등의 다른 타입의 리소스들도 자바스크립트 모듈로 번들링 할 수 있기 때문에 다른 타입의 리소스도 모듈이라고 말할 수 있다.

모듈(Module)

프로그래밍에서 모듈이란 프로그램을 구성하는 구성요소의 일부이다.

개발하는 어플리케이션의 크기가 커지면 확장성과 유지보수 측면에서 파일을 여러 개로 분리하는 시점이 오는데 이때 분리된 파일 각각을 모듈이라 부르고, 보통 클래스 하나 혹은 특정한 목적을 가진 복수의 함수로 구성된 라이브러리 하나로 구성된다.

이렇게 파일을 모듈화 하여 관리하면 다음과 같은 장점이 있.

  1. 프로그램의 효율적인 관리 및 성능 향상
  2. 전체적인 소프트웨어 이해의 용이성 증대 및 복잡성 감소
  3. 소프트웨어 디버깅, 테스트, 통합, 수정시 용이성 제공
  4. 기능의 분리가 가능하고 인터페이스가 단순
  5. 오류의 파급효과를 최소화
  6. 모듈의 재사용 가능으로 개발과 유지보수가 용이

자바스크립트에서의 모듈

초기 스크립트는 크기도 작고 기능도 단순했기 때문에 자바스크립트는 긴 세월 동안 모듈 관련 표준 문법 없이 성장할 수 있었다. 하지만 스크립트의 크기가 점차 커지고 기능도 복잡해지자 자바스크립트 커뮤니티는 특별한 라이브러리를 만들어 필요한 모듈을 언제든지 불러올 수 있게 해준다거나 코드를 모듈 단위로 구성해 주는 방법을 만드는등 다양한 시도를 하게된다.

로컬모듈

단일 Webpack 빌드에 포함되는 모듈이다. 서로다른 Webpack 빌드의 결과물은 서로 다른 로컬 모듈이다. 로컬모듈은 단일 빌드 안에서만 로딩할 수 있다.

원격모듈

다른 Webpack 빌드에서 만든 모듈을 대상으로 런타임에 로딩할 수 있는 모듈을 의미한다. 즉 A 빌드와 B 빌드의 결과물은 서로 원격 모듈이 될 수 있다. 각 빌드는 개별 서버에 배포될 수 있으며 런타임에 Dynamic Imports
하듯이 원격 모듈을 로딩할 수 있다.

컨테이너

각각의 빌드를 말하며 하나의 빌드가 하나의 웹 어플리케이션을 나타낸다. A컨테이너는 B컨테이너의 원격 모듈을 로딩할 수 있으며 B에서 A로도 로딩할 수 있다.

Expose

컨테이너가 외부에 노출한 원격 모듈의 목록을 나타내는 설정이. 간단하게는 “내보낼 모듈 이름 : 로컬 모듈 경로” 로 표현 할 수 있으며 Webpack설정의 일부를 보면 이해가 빠를 것이다.

	new ModuleFederationPlugin({
		name: "app2",
		exposes:{
		"./Button":"./src/Button",
 },
});

app2라는 컨테이너는 로컬 모듈 “./src/Button을” "./Button"이라는 이름의 원격 모듈로 Expose(노출)한다는 의미이다. 원격 모듈을 사용하는 컨테이너 app1에서는 다음과 같이 설정한다.

	new ModuleFederationPlugin({
		name: "app1",
		remotes:{
		app2:`app2@http://localhost:3002/remoteEntry.js`,
 },
});

// import하여 Button 컴포넌트를 사용할 수 있다.
const RemoteButton = React.lazy(() => import("app2/Button"));

공유모듈은 여러개 컨테이너에서 같이 사용하는 모듈을 말하며 런타임에 한 번만 로딩된다. 예를 들어 여러 컨테이너에서 react를 사용한다면 react모듈을 여러번 로딩할 필요는 없다.

new ModuleFederationPlugin({
		name:"app2",
		shared:{react: {singleton : true}, "react-dom": {singleton: true}},
});

리모트앱은 모듈을 Expose하는 컨테이너고 호스트앱은 원격 모듈을 사용하는 컨테이너이다.

기술적인 정의

앞서 정리한 용어를 사용하여 Module Federation을 정의하면, 리모트 앱이 노출(Expose)한 원격 모듈을 호스트 앱에서 비동기 로딩하여 사용할 수 있는 도구라고 할 수 있다. Webpack에 의하면 상호 순환 참조도 가능하다.

그래서,

왜 필요한가?

서비스를 개발하고 성공해 나가다 보면 규모가 점점 커질 수 있다. 서비스 규모가 커질 수록 작은 변경에도 영향 범위가 커질 수 있으며, 영향 범위 예측도 점점 복잡하고 어려워진다. Module Federation은 컨테이너 빌드를 따로 하므로 빌드시간, 영향 범위, 로딩 시간 측면에서 유리하다.

구분기존 방식Module Federation 적용 시 기대효과
빌드범위와 배포시간작은 변경에도 전체 빌드를 하고 배포한다.변경된 컨테이너만 빌드하고 배포해서 시간이 줄어든다.
영향도전체 서비스를 대상으로 영향도를 검증한다.변경 영향이 해당 컨테이너에 국한되므로 검증 범위도 줄어든다.(단, 원격 모듈의 인터페이스를 변경했다면 호스트 앱도 검증이 필요하다.)
로딩시간전체 빌드가 변경되었으므로 배포 직후 로딩 시간도 오래 걸린다.(브라우저 캐시 적용 안됨)배포한 컨테이너의 원격 모듈만 새로 로딩하므로 배포 직후 로딩 시간도 상대적으로 짧다.

Module Federation의 동작 원리

리모트 앱을 빌드하면 remoteEntry.js라는 파일이 생성되며 Expose한 원격 모듈을 호스트 앱에서 로딩할 수 있도록 인터페이스를 정의한다.

호스트 앱 app1의 Webpack설정

new ModuleFederationPlugin({
		name: "app1",
		remotes:{
		app2:`app2@http://localhost:3002/remoteEntry.js`,
	},
})

호스트 앱에서 원격 모듈 사용

// "app2"라는 이름으로 원격 모듈임을 나타낸다.
const RemoteButton = React.lazy(() => import("app2/Button"));

remoteEntry.js 파일을 기반으로 호스트 앱이 어떻게 리모트 앱의 원격 모듈을 로딩하는지 순서대로 알아보자.

호스트 앱은 리모트 앱의 remoteEntry.js파일을 로딩한다.

호스트 앱의 번들 파일(main.js)를 확인해 보면 리모트 앱의 모듈을 어떻게 로딩하는 지 확인할 수 있다.

  • app1(호스트 앱)은 app2(리모트 앱)인 컨테이너를 찾기 위해 전역변수 app2를 찾는다.
  • 전역변수 app2가 없다면 리모트 앱의 remoteEntry.js를 로딩한다.
  • remoteEntry.js가 로딩되면 전역변수 app2가 설정된다.
  • resolve되면 전역변수 app2를 컨테이너로 최종 전달 된다.
module.exports = new Promise((resolve, reject) => {
	if(typeof app2 !== "undefined") return resolve();
	__webpack_require__.l("//localhost:3002/remoteEntry.js", (event) => {
		if(typeof app2 !== "undefined") return resolve();
		... // 생략
	}, "app2");
}).then(() => (app2));np

0개의 댓글