Skip to content
leeyc blog Search
← Back to blog

[Micro Frontend] Module Federation을 이용한 Runtime integration

Module Federation 이란?

JavaScript 아키텍처의 한 유형으로, “단일 Webpack 빌드에 포함된 모듈뿐만 아니라 여러 서버에 배포되어 있는 원격 모듈을 하나의 애플리케이션에서 로딩할 수 있는 기능”입니다. Webpack 5부터 코어로 추가되었습니다.

웹 애플리케이션을 여러 독립적인 모듈로 분할하고, 동적으로 로드/언로드할 수 있는 아키텍처입니다. 각 모듈은 독립적으로 개발되고 배포되며, 필요에 따라 동적으로 통합됩니다.

핵심 이점

용어

기대효과

항목기존 방식Module Federation
빌드 범위작은 변경도 전체 빌드 필요변경된 컨테이너만 빌드
영향도전체 서비스 검증 필요해당 컨테이너 범위만 검증
로딩 시간전체 빌드 변경으로 오래 소요변경 모듈만 새로 로드

구현

Remote (React v18.2.0, Webpack 5, TypeScript)

// Component
import '../../tailwind.css';

export interface NavbarProps {}

const Navbar = () => {
  return (
    <ul className="flex gap-1">
      <li className="flex justify-center items-center border-solid border-b-2">
        <a href="https://www.naver.com">네이버</a>
      </li>
      <li className="flex justify-center items-center border-solid border-b-2">
        <a href="https://google.com">구글</a>
      </li>
      <li className="flex justify-center items-center border-solid border-b-2">
        <a href="https://www.daum.net">다음</a>
      </li>
    </ul>
  );
};

export default Navbar;
// webpack.config.js
const path = require('path');
const { EnvironmentPlugin } = require('webpack');
const HtmlWebPackPlugin = require('html-webpack-plugin');
const { ModuleFederationPlugin } = require('webpack').container;
const deps = require('./package.json').dependencies;

module.exports = (_, argv) => ({
  devServer: {
    static: { directory: path.resolve(__dirname) },
    port: 3000,
    hot: true,
  },
  module: {
    rules: [
      {
        test: /\.m?js/,
        type: 'javascript/auto',
        resolve: {
          fullySpecified: false,
        },
      },
      {
        test: /\.(css|s[ac]ss)$/i,
        use: ['style-loader', 'css-loader', 'postcss-loader'],
      },
      {
        test: /\.(ts|tsx|js|jsx)$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: [
              '@babel/preset-typescript',
              ['@babel/preset-react', { runtime: 'automatic' }],
              '@babel/preset-env',
            ],
            plugins: ['@babel/transform-runtime'],
          },
        },
      },
    ],
  },
  plugins: [
    new ModuleFederationPlugin({
      name: 'remote',
      filename: 'remoteEntry.a350ed3e.js',
      exposes: {
        './Navbar': './src/components/Navbar/index.tsx',
      },
      shared: {
        ...deps,
      },
    }),
    new HtmlWebPackPlugin({
      template: './public/index.html',
    }),
  ],
  output: {
    chunkFilename: '[id].a350ed3e.bundle.js',
    publicPath: 'auto',
    clean: true,
  },
  resolve: {
    extensions: ['.ts', '.tsx', '.js'],
    modules: [path.resolve(__dirname, 'src'), 'node_modules'],
    alias: {
      '@components': path.resolve(__dirname, 'src/components/index.ts'),
    },
  },
});

Host (Next.js, SCSS, TypeScript)

pnpm add -D @module-federation/nextjs-mf
// next.config.js
const NextFederationPlugin = require('@module-federation/nextjs-mf');

/** @type {import('next').NextConfig} */
const nextConfig = {
  webpack(config, options) {
    const { isServer } = options;
    if(!isServer) {
      config.plugins.push(
        new NextFederationPlugin({
          name: 'host',
          remotes: {
            remote: `remote@http://localhost:4000/remoteEntry.a350ed3e.js`,
          },
          filename: 'static/chunks/remoteEntry.a350ed3e.js',
        }),
      );
    }
    return config;
  },
};

module.exports = nextConfig;
// Component
import dynamic from "next/dynamic";

const Navbar = dynamic(() => import('remote/Navbar'), {
  ssr: false,
  loading: () => <>...loading</>
})

export default function Home() {
  return (
    <div>
      hello world
      <Navbar />
    </div>
  );
}

동작 방식

Runtime Integration 흐름:

참고자료


← Previous Design System 2. eslint, tsconfig 설정 Next → [React] Polymorphic component