Astro에서 모듈 페더레이션을 사용하여 React 및 Vue 애플리케이션을 원활하게 통합하기
Grace Collins
Solutions Engineer · Leapcell

소개
빠르게 진화하는 프론트엔드 개발 환경에서 대규모 애플리케이션을 구축하는 것은 종종 여러 팀이 프로젝트의 다른 부분을 작업하며 잠재적으로 다양한 프레임워크를 사용하는 것을 포함합니다. 이러한 시나리오는 성능이나 유지보수성을 희생하지 않고 이러한 분산된 부분을 통합된 사용자 경험으로 통합하는 데 어려움을 초래합니다. IFrames 또는 서버 측 포함과 같은 기존 접근 방식은 통신, 스타일링 및 애플리케이션 컨텍스트 공유 측면에서 제한이 있습니다. 아일랜드 아키텍처를 통해 최적의 성능을 위해 설계된 Astro와 같은 메타 프레임워크의 등장은 효과적인 통합 전략의 필요성을 더욱 증폭시킵니다. 이 기사는 모듈 페더레이션이 이 문제에 대한 우아한 솔루션을 어떻게 제공하는지 살펴보고 Astro 프로젝트 내에서 독립적인 React 및 Vue 애플리케이션을 원활하게 통합하여 더 큰 유연성을 확보하고 진정한 컴포저블 프론트엔드 아키텍처를 육성할 수 있도록 합니다.
핵심 개념 설명
구현 세부 사항을 자세히 살펴보기 전에 논의의 핵심이 되는 몇 가지 주요 용어를 명확히 해 보겠습니다.
-
모듈 페더레이션: Webpack의 기능으로, JavaScript 애플리케이션이 다른 애플리케이션에서 코드를 동적으로 로드하고 그 반대도 가능하게 합니다. 이를 통해 종종 "원격"(모듈을 제공하는 애플리케이션) 및 "호스트"(이를 소비하는 애플리케이션)라고 하는 코드베이스를 다른 애플리케이션 간에 공유하고 통합할 수 있습니다. 핵심 혁신은 전체 애플리케이션 또는 구성 요소를 런타임에 공유하고 소비할 수 있는 모듈로 취급하는 것입니다.
-
마이크로 프론트엔드: 웹 애플리케이션이 각각 별도의 팀에서 유지 관리하는 독립적인 프론트엔드 애플리케이션으로 구성되는 아키텍처 스타일입니다. 모듈 페더레이션은 마이크로 프론트엔드 아키텍처를 실현하는 강력한 도구입니다.
-
Astro: 속도를 위해 설계된 최신 정적 사이트 빌더/메타 프레임워크입니다. "아일랜드 아키텍처"를 사용하는데, 최종 빌드에서는 대부분의 JavaScript가 제거되고 필요할 때만 대화형 UI 구성 요소(아일랜드)를 JavaScript로 하이드레이션합니다. 이 접근 방식은 일반적으로 React, Vue, Svelte 등과 같은 다양한 UI 프레임워크와 함께 작동합니다.
-
호스트 애플리케이션: 모듈 페더레이션 컨텍스트에서 이는 다른 애플리케이션에서 모듈을 소비하는 애플리케이션입니다. 이 경우 Astro 프로젝트가 호스트 역할을 합니다.
-
원격 애플리케이션: 다른 애플리케이션에서 소비할 수 있도록 모듈을 노출하는 애플리케이션입니다. 여기에서는 독립적인 React 및 Vue 애플리케이션이 원격 애플리케이션 역할을 합니다.
원리 및 구현
이 통합의 핵심 원칙은 간단합니다. 독립적인 React 및 Vue 애플리케이션을 구성하여 모듈 페더레이션 원격 애플리케이션으로 작동시켜 구성 요소를 노출합니다. 호스트 역할을 하는 Astro 프로젝트는 이러한 노출된 구성 요소를 소비합니다. Astro의 다양한 UI 프레임워크를 지원하는 고유한 기능은 로컬 구성 요소를 렌더링하는 것처럼 모듈 페더레이션을 통해 가져온 이러한 React 및 Vue 구성 요소를 렌더링할 수 있음을 의미합니다.
원격 애플리케이션 설정(React 및 Vue)
먼저 React 및 Vue 애플리케이션을 구성하여 구성 요소를 노출하도록 준비합니다. Webpack 5의 모듈 페더레이션 플러그인을 사용합니다.
React 원격 애플리케이션 예시
Counter
구성 요소를 노출하려는 간단한 React 앱이 있다고 가정해 보겠습니다.
react-remote/webpack.config.js
:
const HtmlWebPackPlugin = require('html-webpack-plugin'); const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin'); const deps = require('./package.json').dependencies; module.exports = { output: { publicPath: 'http://localhost:3001/', // Astro가 원격 애플리케이션을 찾기 위해 필수적 }, resolve: { extensions: ['.jsx', '.js', '.json'], }, devServer: { port: 3001, }, module: { rules: [ { test: /\.jsx?$/, loader: 'babel-loader', exclude: /node_modules/, options: { presets: ['@babel/preset-react'], }, }, ], }, plugins: [ new ModuleFederationPlugin({ name: 'react_remote', filename: 'remoteEntry.js', exposes: { './CounterApp': './src/CounterApp', // 기본 구성 요소 노출 }, shared: { ...deps, react: { singleton: true, requiredVersion: deps.react, }, 'react-dom': { singleton: true, requiredVersion: deps['react-dom'], }, }, }), new HtmlWebPackPlugin({ template: './public/index.html', }), ], };
react-remote/src/CounterApp.jsx
:
import React, { useState } from 'react'; const CounterApp = () => { const [count, setCount] = useState(0); return ( <div style={{ padding: '10px', border: '1px solid blue' }}> <h2>React Counter Component</h2> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}>Increment</button> </div> ); }; export default CounterApp;
Vue 원격 애플리케이션 예시
마찬가지로 Vue 앱도 Greeting
구성 요소를 노출할 수 있습니다.
vue-remote/webpack.config.js
:
const { VueLoaderPlugin } = require('vue-loader'); const HtmlWebPackPlugin = require('html-webpack-plugin'); const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin'); const deps = require('./package.json').dependencies; module.exports = { output: { publicPath: 'http://localhost:3002/', // Astro에 필수적 }, resolve: { extensions: ['.vue', '.js', '.json'], }, devServer: { port: 3002, }, module: { rules: [ { test: /\.vue$/, loader: 'vue-loader', }, { test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/, }, { test: /\.css$/, use: ['style-loader', 'css-loader'], }, ], }, plugins: [ new VueLoaderPlugin(), new ModuleFederationPlugin({ name: 'vue_remote', filename: 'remoteEntry.js', exposes: { './GreetingApp': './src/GreetingApp.vue', // 기본 구성 요소 노출 }, shared: { ...deps, vue: { singleton: true, requiredVersion: deps.vue, }, }, }), new HtmlWebPackPlugin({ template: './public/index.html', }), ], };
vue-remote/src/GreetingApp.vue
:
<template> <div style="padding: 10px; border: 1px solid green;"> <h2>Vue Greeting Component</h2> <p>Hello from Vue!</p> <button @click="greet">Click Me</button> </div> </template> <script> export default { methods: { greet() { alert('Vue says hi!'); } } } </script> <style scoped> /* Scoped styles specific to this component */ </style>
두 원격 애플리케이션 모두 실행 중인지 확인합니다(예: 각 디렉터리에서 npm start
).
Astro에서 사용(호스트 애플리케이션)
이제 Astro 프로젝트를 구성하여 이러한 원격을 사용합니다. Astro의 src/pages
또는 src/components
는 원격 구성 요소를 가져와 렌더링할 위치입니다. Astro는 일반적으로 Vite를 사용하므로 Vite가 모듈 페더레이션을 처리하는 방법을 알려야 합니다. vite-plugin-federation
을 사용합니다.
astro-host/vite.config.ts
:
import { defineConfig } from 'vite'; import { svelte } from '@sveltejs/vite-plugin-svelte'; // 예시: 다른 프레임워크용, MF 자체에는 직접적으로 필요하지 않음 import react from '@vitejs/plugin-react'; import vue from '@vitejs/plugin-vue'; import federation from '@module-federation/vite'; // 모듈 페더레이션용 플러그인 export default defineConfig({ plugins: [ react(), vue(), federation({ name: 'astro_host', remotes: { react_remote: 'http://localhost:3001/remoteEntry.js', vue_remote: 'http://localhost:3002/remoteEntry.js', }, shared: ['react', 'react-dom', 'vue'], // 공유 종속성을 보장합니다. Astro가 직접 사용하지 않을 수 있지만 원격 애플리케이션에는 중요합니다. }), ], });
astro-host/astro.config.mjs
:
import { defineConfig } from 'astro/config'; import react from '@astrojs/react'; import vue from '@astrojs/vue'; // https://astro.build/config export default defineConfig({ integrations: [react(), vue()], vite: { plugins: [ // vite.config.ts 페더레이션 플러그인이 올바르게 연결되었는지 확인합니다. // 모든 vite 설정을 vite.config.ts에서 사용하지 않는 경우 여기에 페더레이션 구성을 복제해야 할 수 있습니다. // 단순성을 위해 Astro가 vite.config.ts를 선택할 것으로 가정합니다. // 그렇지 않은 경우 `federation`을 여기에 수동으로 가져와 구성해야 할 수 있습니다. ], }, });
이제 원격으로 페더레이션된 구성 요소를 가져와 렌더링하는 Astro 구성 요소를 만들어 보겠습니다.
astro-host/src/pages/index.astro
:
--- import Layout from '../layouts/Layout.astro'; import ReactCounter from '../components/ReactCounter.astro'; import VueGreeting from '../components/VueGreeting.astro'; --- <Layout title="Astro에서의 모듈 페더레이션"> <main> <h1>Component Integration with Module Federation in Astro!</h1> <p>This page integrates components from independent React and Vue applications.</p> <div style="margin-top: 20px;"> <h2>React Component Hosted in Astro</h2> <ReactCounter client:load /> {/* client:load는 React 구성 요소를 하이드레이트합니다. */} </div> <div style="margin-top: 20px;"> <h2>Vue Component Hosted in Astro</h2> <VueGreeting client:load /> {/* client:load는 Vue 구성 요소를 하이드레이트합니다. */} </div> </main> </Layout> <style> main { margin: auto; padding: 1.5rem; max-width: 60ch; } h1 { font-size: 3rem; font-weight: 800; margin: 0; } </style>
astro-host/src/components/ReactCounter.astro
:
--- import { createRemoteComponent } from '../lib/module-federation'; const RemoteCounter = await createRemoteComponent('react_remote', './CounterApp'); --- <RemoteCounter client:load />
astro-host/src/components/VueGreeting.astro
:
--- import { createRemoteComponent } from '../lib/module-federation'; const RemoteGreeting = await createRemoteComponent('vue_remote', './GreetingApp'); --- <RemoteGreeting client:load />
astro-host/src/lib/module-federation.ts
: (원격 구성 요소를 동적으로 로드하는 유틸리티)
import { createComponent } from '@module-federation/sdk'; import type { AstroComponentFactory } from 'astro/runtime/server/index.js'; export async function createRemoteComponent(remoteName: string, moduleName: string): Promise<AstroComponentFactory> { const Component = await createComponent({ remote: remoteName, module: moduleName, scope: remoteName, // 범위는 일반적으로 원격 이름과 일치합니다. // 중요: Astro의 경우 원격 구성 요소가 클라이언트 측 아일랜드로 취급되도록 해야 합니다. // 프레임워크 어댑터(예: @astrojs/react, @astrojs/vue)가 실제 하이드레이션을 처리합니다. }); // 이 변환은 @module-federation/sdk의 정확한 버전 및 사용법에 따라 달라질 수 있습니다. // 목표는 Astro 호환 구성 요소를 얻는 것입니다. // 대부분의 프레임워크의 경우 가져온 구성 요소를 클라이언트: 지시어와 함께 사용하도록 직접 래핑할 수 있습니다. return Component as AstroComponentFactory; }
참고: createRemoteComponent
구현은 @module-federation/sdk
와 Astro의 내부 구성 요소 처리 방식의 정확한 버전 및 사용법에 따라 조정이 필요할 수 있습니다. 기본 아이디어는 원격 모듈을 동적으로 가져온 다음 Astro 내에서 렌더링하는 것입니다. client:load
지시어는 Astro가 이러한 구성 요소를 클라이언트 측에서 하이드레이션하여 상호 작용을 가능하게 하는 데 중요합니다.
애플리케이션 시나리오
- 마이크로 프론트엔드 아키텍처: 각 독립적인 애플리케이션(React, Vue)은 독립적으로 개발 및 배포될 수 있으며, 별도의 팀에서 관리합니다. Astro는 이를 단일 제품으로 구성하는 오케스트레이션 계층 역할을 합니다.
- 점진적 마이그레이션: 대규모 애플리케이션을 한 프레임워크에서 다른 프레임워크로 마이그레이션할 때, 애플리케이션의 일부를 새로운 마이크로 프론트엔드로 점진적으로 다시 작성하고 기존 셸(이 경우 Astro)에 통합할 수 있습니다.
- 공유 컴포넌트 라이브러리: 팀은 공통 UI 구성 요소 또는 비즈니스 로직 모듈을 페더레이션 모듈로 노출하여 다른 원격 또는 호스트 애플리케이션이 이를 소비할 수 있도록 하여 일관성과 재사용성을 보장할 수 있습니다.
- 최고의 기술 활용: 다른 팀이 특정 기능에 가장 적합한 프레임워크를 선택할 수 있도록 하여 전체 프로젝트에 모놀리식 프레임워크 결정을 강요하지 않습니다.
결론
모듈 페더레이션을 사용하여 Astro 프로젝트 내에서 여러 독립적인 React 및 Vue 애플리케이션을 통합하는 것은 현대 프론트엔드 개발에 대한 강력하고 유연한 접근 방식을 제공합니다. 런타임에 동적 코드 공유를 가능하게 함으로써 모듈 페더레이션은 마이크로 프론트엔드 아키텍처를 강화하고, 점진적 마이그레이션을 용이하게 하며, 다양한 프레임워크 생태계 전반에 걸쳐 코드 재사용성을 촉진합니다. 이 전략은 개발자 경험을 향상시키고 팀의 자율성을 강화할 뿐만 아니라 Astro의 속도 이점을 활용하고 크로스 프레임워크 구성 요소 통합의 다재다능함을 활용하여 강력하고 성능이 뛰어나며 유지보수 가능한 웹 애플리케이션을 제공합니다. 프론트엔드 개발의 미래는 점점 더 컴포저블해지고 있으며, 모듈 페더레이션은 이 패러다임의 초석입니다.