모듈 연합을 사용하여 다양한 프론트엔드 애플리케이션을 원활하게 통합하기
Ethan Miller
Product Engineer · Leapcell

소개
급변하는 현대 웹 개발 환경, 특히 대규모 조직이나 복잡한 제품군 내에서 모놀리식 프론트엔드 아키텍처는 종종 상당한 문제를 야기합니다. 팀은 느린 빌드 시간, 독립적인 기능 배포의 어려움, 긴밀하게 결합된 배포의 지속적인 위험이라는 어려움에 직면합니다. 애플리케이션이 복잡성 및 기능 세트로 성장함에 따라 보다 모듈화되고 확장 가능하며 독립적으로 배포 가능한 접근 방식에 대한 필요성이 점점 더 중요해지고 있습니다. 이것이 바로 여러 독립적인 프론트엔드 애플리케이션을 단일의 통합된 사용자 경험으로 구성하는 개념이 등장하는 지점입니다. 이 글에서는 이러한 문제를 정확히 해결하는 강력한 기능인 모듈 연합에 대해 자세히 알아보고, 개발자가 별개의 프론트엔드 애플리케이션을 원활하게 통합하여 정교한 시스템을 구축할 수 있도록 지원합니다.
모듈 연합의 핵심 개념 이해
모듈 연합의 메커니즘과 이점을 자세히 살펴보기 전에, 그 작동의 기반이 되는 몇 가지 핵심 개념을 이해하는 것이 필수적입니다.
- 호스트(또는 컨테이너) 애플리케이션: 이는 다른 원격 애플리케이션의 모듈을 소비하고 오케스트레이션하는 기본 애플리케이션입니다. 셸 또는 래퍼 역할을 합니다.
- 원격 애플리케이션: 이는 다른 호스트 또는 원격 애플리케이션에서 소비할 특정 모듈 또는 구성 요소를 노출하는 독립적인 애플리케이션입니다.
- 노출된 모듈: 이는 원격 애플리케이션이 다른 애플리케이션에서 사용할 수 있도록 공개하기로 선택한 코드(구성 요소, 함수, 유틸리티 등)의 특정 부분입니다.
- 공유 모듈: 모듈 연합은 애플리케이션 간에 종속성을 공유하는 것을 본질적으로 지원합니다. 이는 번들 크기를 최적화하고 런타임에 공유 라이브러리(React 또는 Vue와 같은)의 단일 버전만 로드되도록 하여 잠재적인 충돌 및 블로트를 방지하는 데 중요합니다.
모듈 연합 작동 방식
Webpack 5와 함께 도입된 모듈 연합은 JavaScript 애플리케이션이 강력하고 효율적인 방식으로 다른 애플리케이션에서 코드를 동적으로 로드하고 종속성을 공유할 수 있도록 합니다. 핵심적으로 모듈 연합은 "호스트" 애플리케이션이 런타임에 "원격" 모듈을 로드할 수 있도록 합니다.
전자 상거래 플랫폼이 있다고 가정해 보겠습니다. 전체 플랫폼을 하나의 거대한 프론트엔드로 구축하는 대신, "제품 카탈로그" 애플리케이션, "쇼핑 카트" 애플리케이션, "사용자 프로필" 애플리케이션과 같이 우려 사항을 독립적인 마이크로 프론트엔드로 분리할 수 있습니다. 모듈 연합을 사용하면 "제품 카탈로그"는 FeaturedProduct
구성 요소를 노출하고, "쇼핑 카트"는 AddToCartButton
을 노출하며, "사용자 프로필"은 AuthWidget
을 노출할 수 있습니다. 그런 다음 기본 "셸" 애플리케이션은 호스트 역할을 하며 필요에 따라 이러한 구성 요소를 가져와 렌더링합니다.
간단한 예제를 통해 설명해 보겠습니다.
원격 애플리케이션 설정 (구성 요소 노출)
먼저, product-catalog
라고 하는 원격 애플리케이션을 정의합니다. 이 애플리케이션의 목표는 ProductCard
구성 요소를 노출하는 것입니다.
// product-catalog/src/components/ProductCard.jsx import React from 'react';
const ProductCard = ({ name, price }) => { return ( <div style={{ border: '1px solid #ccc', padding: '10px', margin: '10px' }}> <h3>{name}</h3> <p>Price: ${price}</p> <button>View Details</button> </div> ); };
export default ProductCard;
이제 product-catalog
에 대해 이 구성 요소를 노출하도록 webpack.config.js
를 구성합니다.
// product-catalog/webpack.config.js const HtmlWebpackPlugin = require('html-webpack-plugin'); const { ModuleFederationPlugin } = require('webpack').container;
module.exports = { mode: 'development', entry: './src/index.js', output: { publicPath: 'http://localhost:3001/', // 노출된 모듈의 공개 경로 }, devServer: { port: 3001, }, module: { rules: [ { test: /.jsx?$/, loader: 'babel-loader', exclude: /node_modules/, options: { presets: ['@babel/preset-react'], }, }, ], }, plugins: [ new ModuleFederationPlugin({ name: 'product_catalog', // 이 원격 애플리케이션의 고유 이름 filename: 'remoteEntry.js', // 노출된 모듈 메타데이터를 포함하는 파일 exposes: { './ProductCard': './src/components/ProductCard', // ProductCard 구성 요소 노출 }, shared: { react: { singleton: true, requiredVersion: '^18.0.0' }, 'react-dom': { singleton: true, requiredVersion: '^18.0.0' } }, }), new HtmlWebpackPlugin({ template: './public/index.html', }), ], };
여기서 name
은 이 원격의 고유 식별자입니다. filename
은 호스트가 가져올 매니페스트 파일입니다. exposes
는 사용 가능한 모듈을 정의합니다. shared
는 종속성 중복 제거에 중요합니다.
호스트 애플리케이션 설정 (구성 요소 소비)
다음으로, product-catalog
에서 ProductCard
를 소비할 호스트 애플리케이션 main-shell
을 만듭니다.
// main-shell/src/App.jsx import React, { Suspense } from 'react';
// 원격 구성 요소를 동적으로 가져옵니다. const ProductCard = React.lazy(() => import('product_catalog/ProductCard'));
const App = () => { return ( <div> <h1>Welcome to the Main Shell!</h1> <h2>Featured Products:</h2> <Suspense fallback={<div>Loading Product Card...</div>}> <ProductCard name="Stylish Headphones" price="199.99" /> <ProductCard name="Ergonomic Keyboard" price="120.00" /> </Suspense> </div> ); };
export default App;
그리고 해당 webpack.config.js
:
// main-shell/webpack.config.js const HtmlWebpackPlugin = require('html-webpack-plugin'); const { ModuleFederationPlugin } = require('webpack').container;
module.exports = { mode: 'development', entry: './src/index.js', output: { publicPath: 'http://localhost:3000/', }, devServer: { port: 3000, }, module: { rules: [ { test: /.jsx?$/, loader: 'babel-loader', exclude: /node_modules/, options: { presets: ['@babel/preset-react'], }, }, ], }, plugins: [ new ModuleFederationPlugin({ name: 'main_shell', remotes: { product_catalog: 'product_catalog@http://localhost:3001/remoteEntry.js', // 원격 애플리케이션 정의 }, shared: { react: { singleton: true, requiredVersion: '^18.0.0' }, 'react-dom': { singleton: true, requiredVersion: '^18.0.0' } }, }), new HtmlWebpackPlugin({ template: './public/index.html', }), ], };
호스트 구성의 remotes
필드는 Webpack에 product_catalog
의 remoteEntry.js
파일을 어디서 찾을지 알려줍니다. 여기의 shared
구성은 원격의 것과 동일하며 react
및 react-dom
이 고유 인스턴스가 되도록 보장합니다.
main-shell
애플리케이션이 로드되면 동적으로 product_catalog
의 remoteEntry.js
(작은 매니페스트 파일)를 가져옵니다. 이 파일은 ProductCard
구성 요소를 가져오는 방법을 알려줍니다. 이를 통해 물리적으로 분리된 애플리케이션의 런타임 통합이 가능합니다.
애플리케이션 시나리오
모듈 연합은 다음과 같은 시나리오에서 탁월합니다.
- 모놀리식 프론트엔드를 마이크로 프론트엔드로 분할: 각 마이크로 프론트엔드는 서로 다른 팀에서 독립적으로 개발, 배포 및 확장할 수 있습니다.
- 공통 구성 요소 또는 라이브러리 공유: 여러 애플리케이션에서 UI 구성 요소 라이브러리, 유틸리티 함수 또는 전체 비즈니스 로직 모듈을 공유하여 코드를 복제하지 않습니다.
- A/B 테스트 또는 기능 플래그 구현: 전체 애플리케이션을 다시 배포하지 않고 사용자 그룹 또는 실험 조건에 따라 구성 요소 또는 기능의 다른 버전을 동적으로 로드합니다.
- 확장 가능한 플랫폼 구축: 자체 모듈을 제공하여 타사 개발자 또는 다른 내부 팀이 핵심 플랫폼을 확장할 수 있도록 합니다.
결론
모듈 연합은 대규모 프론트엔드 개발에 접근하는 방식에 있어 패러다임 전환을 나타냅니다. 개발자가 여러 독립적인 애플리케이션을 단일의 일관된 경험으로 구성할 수 있도록 함으로써 확장성을 크게 향상시키고, 배포를 단순화하며, 다양한 팀 및 프로젝트 전반에 걸쳐 코드 재사용을 촉진합니다. 이 강력한 Webpack 기능은 진정한 독립적이고 강력한 마이크로 프론트엔드 아키텍처의 약속에 더 가까이 다가가 웹 애플리케이션을 위한 모듈화된 미래를 실현합니다.