Module Federation

강성훈·2022년 5월 26일
3
post-thumbnail

Module Federation

module federation이란

모듈 페더레이션이란 Zack jackson이 개발한 javascript의 아키텍처입니다. 이 아키텍처를 사용하면 두 개의 서로 다른 애플리케이션 코드베이스 간에 코드와 종속성을 공유할 수 있습니다.
다시 말해 모놀리식 아키텍처의 문제점을 해결할 수 있습니다.

모놀리식 아키텍처의 문제점

서로 상당히 의존하고 있기 때문에 feature들 사이에 문제가 발생했을 때, 이를 의존하고 있는 다른 feature 또한 오류를 발생시킵니다.
또 만약 새로운 것을 추가 혹은 수정을 하면 의존성을 가지는 다른 feature에 오류를 불러 일으킬 수 있습니다.
뿐만 아니라 빌드 및 배포에도 어려움이 있습니다. 만약 이미 배포한 프로덕에 조그마한 수정사항이 있어 수정했다면 다시 그것의 전체를 빌드하고 배포시켜야합니다. 이렇게되면 많은 시간을 낭비하게됩니다.
또 서로 다른 피처들을 개발하고 배포하기전 머지시킬 때 또한 충돌로 인한 문제가 발생 할 수도 있습니다.

마이크로 프론트엔드란

마이크로 프론트 엔드란 백엔드에서 사용하고 있는 마이크로 서비스 아키텍처 처럼 프론트엔드에서 관리하는 서비스를 분리해서 개발, 관리하는 패턴을 말합니다.

서술해보자면 하나의 서비스는 한개의 스크럼 팀에서 개발하고 운영됩니다. 그들은 독립적으로 그들의 서비스를 개발합니다. 그리고 최종적으로 각 팀에서 개발된 개별 어플리케이션은 하나의 프로덕트로 합쳐지게 되는 것입니다. 이렇게 되면 개발자 입장에선 각각의 독립적인 개체이지만 사용자는 하나의 프로덕트로 보게 되는 것입니다.

마이크로 프론트엔드의 장점

웹 애플리케이션을 구축하기 위해 마이크로 프론트엔드 접근 방식을 채택하는 것이 좋은 선택입니다. 특히 이는 변화는 부분이 많은 대규모 앱을 구축하는 경우 그렇습니다.

  • 마이크로 프론트엔드 접근 방식을 채택하면 종단 간 기능 아키텍처를 만들 수 있습니다. 이 접근 방식을 통해 대규모 배포 인프라 없이도 기능을 로컬로 개발 및 배포할 수 있습니다.
  • 더 작고 최적화된 번들 크기를 통해 마이크로 프론트엔드는 원할 때마다 지연 로드 될 수 있는 공유 구성 요소 및 종속성의 결과로 전반적으로 더 나은 개발자 및 사용자 경험을 제공합니다.
  • 나에게 가장 큰 이점 중 하나는 특정 제품을 작업하는 개발자 팀이 다른 팀의 코드와 비호환성에 대한 두려움 없이 원하는 기술 스택을 선택할 수 있다는 것입니다.

장점들이 처음 읽었을 때 와닿지 않을 것입니다. 이는 지금부터 설명하는 글을 읽다보면 자연스럽게 이해하실 수 있을 것입니다.

과연 하나의 앱을 어떻게 나누는 것일까

  1. 페이지별 : 프론트엔드 애플리케이션에서 브라우저에서 동시에 다른 페이지를 실행하면 구형 장치에서 충돌이 발생할 수 있습니다. 따라서 가장 안전한 방법은 페이지별로 분할하는 것입니다. 라우팅이 좋은 경우 모든 페이지에 대해 특정 앱을 실행할 수 있습니다. 이는 할당된 한 페이지에서 항상 작업하기 때문에 팀의 개발자에게도 좋습니다.
  2. 기능별 ⁠: 여러 가지 기능이 서로 다른 작업을 수행하는 한 페이지가 있는 경우 이러한 큰 기능을 더 작은 앱으로 분할하고 특정 기능을 실행하는 독립 응용 프로그램으로 만들 수 있습니다.

만들어보기

간단한 예제를 통해 module federation을 이해해봅시다.

mkdir federation

폴어를 만들고 header 프로젝트를 생성하겠습니다.

npx create-react-app header

헤더에 폴더구조는 다음과 같습니다.

bootstrap.js

index.js에 내용을 다음과 같이 boostrap.js 파일로 옮깁니다.

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

index.js

import('./bootstrap');

app.js

자유롭게

Header.js

import React from 'react';

const Header = () => {
    return (
        <div>
            Header입니다.
        </div>
    );
};

export default Header;

webpack.config.js

const { ModuleFederationPlugin } = require('webpack').container;
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExternalTemplateRemotesPlugin = require('external-remotes-plugin');
const path = require('path');
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');

module.exports = {
  entry: './src/index',
  mode: 'development',
  devtool: 'source-map', // 디버깅 과정 향상 빌드 및 리빌드 속도에 큰 영향을 미침, 가장 느린 거
  optimization: {
    minimize: false,
  },
  devServer: {
    hot: true,
    static: path.join(__dirname, 'dist'),
    port: 3001,
    liveReload: false,
  },
  output: {
    publicPath: 'auto',
    clean: true,
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        loader: 'babel-loader',
        exclude: /node_modules/,
        options: {
          presets: ['@babel/preset-react'],
          plugins: [require.resolve('react-refresh/babel')],
        },
      },
    ],
  },
  plugins: [
    new ModuleFederationPlugin({
      name: 'header',
      filename: 'remoteEntry.js',
      exposes: {
          "./Header": "./src/Header.js",
      },
    }),
    new ExternalTemplateRemotesPlugin(),
    new HtmlWebpackPlugin({
      template: './public/index.html',
      chunks: ['main'],
    }),
    new ReactRefreshWebpackPlugin({
      exclude: [/node_modules/, /bootstrap\.js$/],
    }),
  ],
}

webpack.config.js 파일이 이해가 되지 않을 수 있습니다.
이들을 모두 설명하기에는 너무 많은 내용이므로, plugin에 ModuleFederationPllugin만 설명하겠습니다.

  • name: 뜻 그대로 이름입니다. 내보낼 때의 고유한 이름으로 중복 되서는 안됩니다.
  • filename: 기본값이 remoteEntry.js로 내보낼 내용이 압축되어 remoteEntry.js로 추출됩니다.
  • exposes: 내보낼 파일의 이름

사실 이렇게 보면 3개의 차이를 못 느끼실 것입니다.
그렇지만 header를 받아들이는 frame 코드를 보면 3개의 차이를 느낄 수 있을 것입니다.

frame 프로젝트

index.js와 bootstrap.js는 똑같이 작성합니다.

app.js

import React from "react";

const Header = React.lazy(() => import("header/Header"));

const App = () => {
  return (
    <div>
      <Header />
    </div>
  );
};

export default App;

webpack.config.js

const { ModuleFederationPlugin } = require('webpack').container;
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExternalTemplateRemotesPlugin = require('external-remotes-plugin');
const path = require('path');
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');

module.exports = {
  entry: './src/index',
  mode: 'development',
  devtool: 'source-map', // 디버깅 과정 향상 빌드 및 리빌드 속도에 큰 영향을 미침, 가장 느린 거
  optimization: {
    minimize: false,
  },
  devServer: {
    hot: true,
    static: path.join(__dirname, 'dist'),
    port: 3000,
    liveReload: false,
  },
  output: {
    publicPath: 'auto',
    clean: true,
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        loader: 'babel-loader',
        exclude: /node_modules/,
        options: {
          presets: ['@babel/preset-react'],
          plugins: [require.resolve('react-refresh/babel')],
        },
      },
    ],
  },
  plugins: [
    new ModuleFederationPlugin({
        name: 'frame',
        remotes: {
            header: 'header@http://localhost:3001/remoteEntry.js',
        },
    }),
    new ExternalTemplateRemotesPlugin(),
    new HtmlWebpackPlugin({
      template: './public/index.html',
      chunks: ['main'],
    }),
    new ReactRefreshWebpackPlugin({
      exclude: [/node_modules/, /bootstrap\.js$/],
    }),
  ],
}

package.json, content

https://github.com/Self-Study-Programing/module_federation

profile
고등학생 주니어 개발자

0개의 댓글