L
PHP • Literally Web ID

Arsitektur Micro-Frontend Skala Enterprise Menggunakan Module Federation di Webpack 5

⏱️ 18 menit baca • 📅 16 Oct 2026 • 👁️ 1 views
Arsitektur Micro-Frontend Skala Enterprise Menggunakan Module Federation di Webpack 5

1. Konteks Industri & Analisis Akar Masalah

Dalam lanskap rekayasa perangkat lunak enterprise modern, evolusi sistem backend dari monolitik menuju microservices telah berhasil menyelesaikan friksi skalabilitas organisasi. Tim-tim otonom dapat merilis modul pembayaran, inventaris, dan autentikasi tanpa harus menunggu siklus merge global. Namun, kesuksesan arsitektural ini sering kali terhenti di lapisan antarmuka pengguna (frontend). Frontend enterprise kerap kali tetap berwujud monolithic Single Page Application (SPA)—sebuah repositori raksasa berukuran puluhan gigabyte dengan jutaan baris kode TypeScript, ratusan dependensi npm, dan waktu build CI/CD yang menembus angka 45 hingga 90 menit.

Bottleneck Nyata Monolitik Frontend

  1. Blast Radius yang Terlalu Luas: Kegagalan kecil pada modul promosi di keranjang belanja dapat menyebabkan white screen of death (WSOD) pada seluruh aplikasi, melumpuhkan fungsionalitas kritis seperti proses checkout.
  2. Deployment Lock-in & Team Friction: Tim beranggotakan 50 hingga 200 insinyur frontend harus mengantre dalam release train mingguan. Jika satu tim mendapati regresi pada pengujian end-to-end (E2E), seluruh rilis enterprise harus dibatalkan.
  3. Dependency Hell & Debt Accumulation: Memperbarui pustaka inti seperti React dari versi 17 ke 18 atau migrasi Design System UI menjadi tugas hampir mustahil karena harus dieksekusi secara atomik di seluruh sistem sekaligus.
  4. Overfetching Bundle Asset: Walaupun dynamic import (React.lazy) membantu pemecahan rute, aset yang dimuat di tingkat runtime masih terikat pada satu manifest hash tunggal yang dihasilkan oleh satu kompilasi build.

Tabel Komparasi: Sebelum vs. Sesudah Module Federation

Parameter Evaluasi Monolitik SPA Tradisional Micro-Frontend via NPM Packages Micro-Frontend via Webpack 5 Module Federation
Waktu Build CI/CD 45 - 90 menit (Kompilasi global) 10 - 15 menit per modul + build ulang shell 2 - 5 menit per remote independen
Siklus Rilis Mingguan / Dua mingguan (Terkunci) Membutuhkan rilis NPM + bump version shell On-demand, independen, hitungan menit
Sharing Runtime Memory Optimal (Shared bundle tunggal) Duplikasi kode ekstrem (bundle membengkak) Dinamis, terkelola via runtime negotiation
Isolasi Kegagalan Nol (Crash merusak seluruh app) Parsial (Tergantung error boundary lokal) Tinggi (Isolated fallbacks di edge/client)
Orkestrasi Infra 1 Host S3/Cloudflare/Nginx 1 Host S3/Cloudflare/Nginx Multi-origin CDN, Edge Routing, S3 Buckets

Webpack 5 Module Federation hadir bukan sekadar sebagai pemecah kode (code splitting), melainkan sebagai pergeseran paradigma: memungkinkan sebuah aplikasi JavaScript mengeksekusi kode dari build aplikasi lain secara asinkron di tingkat runtime tanpa batasan waktu kompilasi (compile-time dependencies).


2. Konsep Inti & Deep Dive Arsitektur

Secara mekanis, Module Federation membagi entitas sistem menjadi dua peran utama (yang dapat saling bertukar peran secara bersamaan): Host (wadah konsumsi/kontainer) dan Remote (penyedia modul/fitur). Mekanisme ini diorkestrasi oleh file entry point khusus yang disebut remote entry manifest (biasanya diberi nama remoteEntry.js).

Cara Kerja di Balik Layar (Under The Hood)

Ketika pengguna mengakses aplikasi Host, browser pertama-tama mengunduh bundle inisialisasi Host. Saat Host membutuhkan komponen dari Remote, terjadi serangkaian proses komputasi tingkat rendah:

  1. Inisialisasi Shared Scope: Host mengevaluasi objek global __webpack_share_scopes__. Objek ini menyimpan tabel pencarian versi (lookup table) dari semua dependensi yang dideklarasikan bersama (react, react-dom, utilitas bersama).
  2. Fetch Remote Entry: Host mengunduh skrip remoteEntry.js dari CDN Remote yang terpisah secara fisik. File ini berukuran sangat kecil (< 5 KB gzip) dan hanya memetakan interface modul yang diekspor serta dependensi yang dibutuhkan oleh Remote.
  3. Negotiation & SemVer Resolution: Host dan Remote melakukan negosiasi versi dependensi. Jika Host menyediakan react@18.2.0 dan Remote membutuhkan ^18.0.0, Remote akan membatalkan pengunduhan React miliknya sendiri dan langsung mengikatkan diri ke instance React yang sudah dimuat oleh Host di memori browser.
  4. Module Factory Execution: Modul remote dieksekusi di dalam container scoped factory milik Webpack dan di-render ke dalam Virtual DOM Host secara transparan.

Diagram Arsitektur Micro-Frontend Skala Enterprise

+-----------------------------------------------------------------------------------------+
|                                 KLIEN / WEB BROWSER                                     |
+-----------------------------------------------------------------------------------------+
       |                                           |                               |
       | GET /                                     | GET /remoteEntry.js           | GET /remoteEntry.js
       v                                           v                               v
+-----------------------+              +-----------------------+       +------------------------+
|   SHELL HOST APP      |              |   CHECKOUT REMOTE     |       |   CATALOG REMOTE       |
| (Origin: app.corp)    |              | (Origin: pay.corp)    |       | (Origin: cdn.corp)     |
+-----------------------+              +-----------------------+       +------------------------+
| - Webpack Federation  |              | - Webpack Federation  |       | - Webpack Federation   |
| - Global Layout/Nav   |              | - Payment Processing  |       | - Product Browsing     |
| - Shared Scope Init   |              | - Expose: ./Payment   |       | - Expose: ./ProductCard|
+-----------------------+              +-----------------------+       +------------------------+
       |                                           |                               |
       | Runtime Link                              | Runtime Fetch                 | Runtime Fetch
       +===========================================+===============================+
                                                   |
                                    +------------------------------+
                                    |   SHARED RUNTIME MEMORY      |
                                    | __webpack_share_scopes__.default
                                    | - react: 18.2.0 (Singleton)  |
                                    | - react-dom: 18.2.0          |
                                    | - @corp/design-system: 4.1.0 |
                                    +------------------------------+
                                                   |
+-----------------------------------------------------------------------------------------+
|                         INFRASTRUKTUR CLOUD & EDGE STORAGE                              |
+-----------------------------------------------------------------------------------------+
|  [Cloudflare CDN / Edge Workers] -> Dynamic Route & Semantic Fallback Logic             |
|  [AWS S3 / MinIO Object Storage] -> Isolated Deployment Buckets per Team               |
|  [Nginx Reverse Proxy / WAF]     -> CORS Management, Security Headers, Health Checking   |
+-----------------------------------------------------------------------------------------+

3. Perbandingan Komparatif / Analisis Pendekatan

Memilih arsitektur frontend terdistribusi membutuhkan pemahaman mendalam mengenai trade-off performa, kompleksitas operasional, dan pengalaman pengembang (Developer Experience).

Kriteria Evaluasi iFrames Tradisional Single-SPA (Meta Router) Webpack 5 Module Federation
Beban Memori (RAM) Sangat Tinggi (DOM & context terduplikasi per frame) Sedang (Tergantung bundle packaging) Rendah (Single DOM Context, Shared Modules)
Komunikasi Antar Modul Kaku (postMessage, serialize overhead) Event Bus / Custom Events Native JS In-Memory (Props, State Store, RxJS)
SEO & Web Vitals (LCP/CLS) Buruk (Tidak ramah crawler & layout shifting) Cukup Baik (Membutuhkan SSR kustom) Unggul (Dapat diintegrasikan dengan Edge SSR)
CSS & Style Encapsulation Terisolasi secara native Rentan bocor tanpa Shadow DOM Scoped CSS / CSS Modules / Tailwind prefixing
Kompleksitas Tooling Sangat Rendah Tinggi (SystemJS, import maps) Menengah-Tinggi (Webpack AST Compilation)
Latency Overhead Tinggi (Koneksi dokumen baru) Rendah Sangat Rendah (Async network chunk fetching)

[!IMPORTANT] Module Federation bukan pengganti microservices backend. Menerapkan Module Federation pada tim kecil (< 10 engineer) adalah bentuk premature optimization yang justru akan meningkatkan overhead operasional tanpa memberikan manfaat bisnis yang sepadan.


4. Prasyarat Sistem & Persiapan Lingkungan

Sebelum merancang pipeline dan implementasi kode, pastikan lingkungan enterprise memenuhi spesifikasi baseline berikut:

  • Node.js Runtime: v18.19.0 LTS atau v20.11.0 LTS (Direkomendasikan menggunakan runtime dengan dukungan OpenSSL 3).
  • Package Manager: pnpm v8.x atau npm v10.x (Dilarang mencampur manager guna menghindari anomali hoisting dependensi).
  • Webpack Core: Webpack >= 5.88.0 dengan webpack-cli >= 5.1.0.
  • Infrastruktur Hosting: Akses ke 2 atau lebih Bucket S3/MinIO independen dan Cloudflare CDN / Nginx Web Server untuk resolusi header CORS.

Verifikasi kesiapan lingkungan Anda melalui CLI:

# Verifikasi Node dan NPM
node -v # Harap pastikan >= v18.19.0
pnpm -v # Harap pastikan >= 8.15.0

# Struktur direktori monorepo atau polyrepo enterprise
mkdir -p enterprise-mfe && cd enterprise-mfe
mkdir -p packages/host packages/catalog packages/checkout

5. Panduan Implementasi Langkah demi Langkah

Berikut adalah implementasi nyata dari arsitektur Enterprise Micro-Frontend yang terdiri dari satu Host (Shell) dan satu Remote (Checkout), dilengkapi konfigurasi Nginx untuk menangani Cross-Origin Resource Sharing (CORS) di tingkat edge.

Langkah 1: Konfigurasi Webpack Remote (packages/checkout/webpack.config.js)

Remote ini bertugas mengekspos komponen PaymentWidget agar dapat dikonsumsi oleh Shell.

const HtmlWebpackPlugin = require('html-webpack-plugin');
const { ModuleFederationPlugin } = require('webpack').container;
const path = require('path');
const deps = require('./package.json').dependencies;

module.exports = {
  entry: './src/index.js',
  mode: 'production',
  output: {
    publicPath: 'auto',
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[contenthash].js',
    clean: true,
  },
  resolve: {
    extensions: ['.jsx', '.js', '.json', '.tsx', '.ts'],
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx|ts|tsx)$/,
        loader: 'babel-loader',
        exclude: /node_modules/,
        options: {
          presets: ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'],
        },
      },
    ],
  },
  plugins: [
    new ModuleFederationPlugin({
      name: 'checkout_remote',
      filename: 'remoteEntry.js',
      exposes: {
        './PaymentWidget': './src/components/PaymentWidget.tsx',
      },
      shared: {
        ...deps,
        react: {
          singleton: true,
          requiredVersion: deps.react,
          strictVersion: true,
        },
        'react-dom': {
          singleton: true,
          requiredVersion: deps['react-dom'],
          strictVersion: true,
        },
      },
    }),
    new HtmlWebpackPlugin({
      template: './public/index.html',
    }),
  ],
};

Langkah 2: Implementasi Komponen Remote (packages/checkout/src/components/PaymentWidget.tsx)

import React, { useState } from 'react';

export interface PaymentWidgetProps {
  amount: number;
  currency: string;
  onSuccess?: (transactionId: string) => void;
}

export const PaymentWidget: React.FC<PaymentWidgetProps> = ({ amount, currency, onSuccess }) => {
  const [loading, setLoading] = useState<boolean>(false);
  const [txId, setTxId] = useState<string | null>(null);

  const handleExecution = async () => {
    setLoading(true);
    // Simulasi komputasi otorisasi pembayaran
    setTimeout(() => {
      const generatedId = `TX-${Math.random().toString(36).substr(2, 9).toUpperCase()}`;
      setTxId(generatedId);
      setLoading(false);
      if (onSuccess) onSuccess(generatedId);
    }, 1200);
  };

  return (
    <div style={{ border: '2px dashed #059669', padding: '1.5rem', borderRadius: '0.5rem', background: '#ecfdf5' }}>
      <h3 style={{ margin: 0, color: '#065f46' }}>Modul Remote: Gateway Pembayaran Enterprise</h3>
      <p style={{ color: '#047857' }}>Total Tagihan: <strong>{currency} {amount.toLocaleString()}</strong></p>
      {txId ? (
        <div style={{ color: '#065f46', fontWeight: 'bold' }}>Sukses! ID Transaksi: {txId}</div>
      ) : (
        <button
          onClick={handleExecution}
          disabled={loading}
          style={{ background: '#059669', color: '#fff', border: 'none', padding: '0.5rem 1rem', borderRadius: '4px', cursor: 'pointer' }}
        >
          {loading ? 'Memproses Otorisasi...' : 'Bayar Sekarang'}
        </button>
      )}
    </div>
  );
};

export default PaymentWidget;

Langkah 3: Konfigurasi Webpack Host / Shell (packages/host/webpack.config.js)

Host mengonsumsi remote secara deklaratif dengan menetapkan alamat URL remote entry.

const HtmlWebpackPlugin = require('html-webpack-plugin');
const { ModuleFederationPlugin } = require('webpack').container;
const path = require('path');
const deps = require('./package.json').dependencies;

module.exports = {
  entry: './src/index.js',
  mode: 'production',
  output: {
    publicPath: '/',
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[contenthash].js',
    clean: true,
  },
  resolve: {
    extensions: ['.jsx', '.js', '.json', '.tsx', '.ts'],
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx|ts|tsx)$/,
        loader: 'babel-loader',
        exclude: /node_modules/,
        options: {
          presets: ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'],
        },
      },
    ],
  },
  plugins: [
    new ModuleFederationPlugin({
      name: 'shell_host',
      remotes: {
        checkout_remote: 'checkout_remote@https://pay.enterprise.internal/remoteEntry.js',
      },
      shared: {
        ...deps,
        react: {
          singleton: true,
          requiredVersion: deps.react,
          strictVersion: true,
        },
        'react-dom': {
          singleton: true,
          requiredVersion: deps['react-dom'],
          strictVersion: true,
        },
      },
    }),
    new HtmlWebpackPlugin({
      template: './public/index.html',
    }),
  ],
};

Langkah 4: Bootstrapping & Loading Remote Component di Host (packages/host/src/App.tsx)

Karena eksekusi Module Federation membutuhkan waktu asinkron untuk menginisialisasi shared scope, entry point harus dibungkus dengan teknik asynchronous boundary (bootstrap.js).

File: packages/host/src/index.js

// Async boundary untuk mencegah error shared scope initialization
import('./bootstrap');

File: packages/host/src/App.tsx

import React, { Suspense, lazy } from 'react';

// Error boundary untuk isolasi kegagalan runtime remote
class MicroFrontendErrorBoundary extends React.Component<
  { fallback: React.ReactNode; children: React.ReactNode },
  { hasError: boolean }
> {
  constructor(props: any) {
    super(props);
    this.state = { hasError: false };
  }
  static getDerivedStateFromError() {
    return { hasError: true };
  }
  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
    console.error('CRITICAL: Micro-Frontend Crash Terdeteksi:', error, errorInfo);
  }
  render() {
    if (this.state.hasError) {
      return this.props.fallback;
    }
    return this.props.children;
  }
}

// Lazy load Remote Module melalui deklarasi Webpack
const RemotePaymentWidget = lazy(() => import('checkout_remote/PaymentWidget'));

export const App: React.FC = () => {
  return (
    <main style={{ fontFamily: 'Inter, sans-serif', margin: '2rem auto', maxWidth: '800px' }}>
      <header style={{ borderBottom: '1px solid #e5e7eb', paddingBottom: '1rem', marginBottom: '2rem' }}>
        <h1>Enterprise Shell Application</h1>
        <p>Infrastruktur Moduler Aktif: Webpack 5 Module Federation Engine</p>
      </header>

      <section style={{ marginBottom: '2rem' }}>
        <h2>Katalog Layanan Cloud</h2>
        <p>Item: Subscription Enterprise Tier A - 1 Tahun</p>
      </section>

      <section>
        <MicroFrontendErrorBoundary
          fallback={
            <div style={{ padding: '1rem', background: '#fef2f2', border: '1px solid #ef4444', borderRadius: '4px', color: '#b91c1c' }}>
              Sistem modul pembayaran sedang mengalami kendala teknis. Silakan hubungi dukungan via tiket internal.
            </div>
          }
        >
          <Suspense fallback={<div>Mengunduh modul remote secara aman...</div>}>
            <RemotePaymentWidget
              amount={15000000}
              currency="IDR"
              onSuccess={(txId) => console.log('Callback Host Menerima Transaksi:', txId)}
            />
          </Suspense>
        </MicroFrontendErrorBoundary>
      </section>
    </main>
  );
};
export default App;

Langkah 5: Konfigurasi Web Server Nginx & Header CORS (/etc/nginx/conf.d/microfrontends.conf)

Remote assets diakses melintasi domain yang berbeda (CORS). Konfigurasi Nginx di bawah ini wajib diterapkan pada server yang melayani remote entry.

server {
    listen 443 ssl http2;
    server_name pay.enterprise.internal;

    ssl_certificate /etc/letsencrypt/live/pay.enterprise.internal/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/pay.enterprise.internal/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    root /var/www/checkout_remote/dist;
    index index.html;

    # Konfigurasi Cache-Control khusus untuk remoteEntry.js
    # KRUSIAL: remoteEntry TIDAK BOLEH di-cache permanen di browser
    location = /remoteEntry.js {
        add_header Cache-Control "no-cache, no-store, must-revalidate" always;
        add_header Pragma "no-cache" always;
        add_header Expires "0" always;
        
        # Resolusi CORS untuk Host
        add_header Access-Control-Allow-Origin "https://app.corp" always;
        add_header Access-Control-Allow-Methods "GET, OPTIONS" always;
        add_header Access-Control-Allow-Headers "Content-Type, Authorization" always;
        
        try_files $uri =404;
    }

    # Asset static terkompilasi dengan contenthash dapat di-cache secara agresif
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|woff2)$ {
        expires 1y;
        add_header Cache-Control "public, max-age=31536000, immutable";
        add_header Access-Control-Allow-Origin "https://app.corp" always;
        try_files $uri =404;
    }

    location / {
        try_files $uri $uri/ /index.html;
    }
}

6. Bedah Kode & Penjelasan Detail Baris Kunci

Mari kita bedah arsitektur konfigurasi yang telah ditulis di atas untuk memahami dampak teknisnya:

  1. name: 'checkout_remote': Menetapkan variabel namespace global di window objek browser. Webpack membungkus ekspor modul di dalam window.checkout_remote. Nama ini harus unik di seluruh ekosistem enterprise Anda guna mencegah tabrakan variabel global.

  2. filename: 'remoteEntry.js': Ini adalah manifest table of contents. File ini bertindak sebagai jembatan komunikasi runtime. Kesalahan paling fatal di produksi adalah mengizinkan CDN atau browser meng-cache file ini dengan max-age yang panjang. Jika remote diperbarui tetapi remoteEntry.js di-cache browser, Host akan mencoba memuat chunk lama yang mungkin sudah dihapus dari server S3, memicu error ChunkLoadError.

  3. exposes: { './PaymentWidget': './src/components/PaymentWidget.tsx' }: Mendefinisikan public interface dari remote. Kunci objek (./PaymentWidget) adalah sub-jalur yang akan diakses oleh Host, sedangkan nilainya adalah file absolut yang diproses oleh babel/swc loader pada saat kompilasi.

  4. shared: { react: { singleton: true, strictVersion: true } }:

    • singleton: true: Memaksa runtime Webpack untuk hanya mengizinkan satu instance React berjalan di memori. Tanpa opsi ini, React context, hook state, dan ref akan rusak fatal jika Host dan Remote memuat dua versi React yang berbeda secara bersamaan.
    • strictVersion: true: Menginstruksikan modul untuk menolak rendering dan memunculkan error eksplisit pada konsol jika terdapat ketidakcocokan SemVer (mismatch major version) daripada mengeksekusi kode dalam kondisi rapuh.
  5. import('./bootstrap') (Asynchronous Boundary): Jika Anda mengimpor dependensi React langsung di file entrypoint sinkron utama (index.js), Webpack tidak memiliki ruang waktu untuk mengonsolidasikan negotiation layer __webpack_share_scopes__. Pemisahan melalui dynamic import asinkron memungkinkan Webpack mendownload shared modules terlebih dahulu sebelum kode inisialisasi aplikasi dieksekusi.


7. Skenario Produksi & Studi Kasus Dunia Nyata

Kasus: Flash Sale E-Commerce Skala 500.000 Request per Menit

Sebuah perusahaan e-commerce global meluncurkan kampanye diskon kilat (flash sale). Trafik meningkat 20x lipat dalam waktu 3 menit. Modul katalog produk mengalami lonjakan pembacaan data, sementara modul autentikasi dan pembayaran memiliki beban transaksi intensif I/O.

Implementasi Dynamic Remote Resolution di Edge

Dalam skala enterprise nyata, Anda tidak ingin memprogram URL remote secara statis (hardcoded) di dalam webpack config Host (checkout_remote@https://...). Jika URL berubah atau sistem beralih ke kluster disaster recovery (DR), Host harus di-build ulang.

Solusinya adalah menggunakan Dynamic Remote Url Resolution yang dikontrol via KV Store di Cloudflare Workers:

// packages/host/src/utils/dynamicFederation.ts
export async function loadComponent(scope: string, module: string) {
  // Inisialisasi container Webpack secara dinamis
  // @ts-ignore
  await __webpack_init_sharing__('default');
  // @ts-ignore
  const container = window[scope];
  // @ts-ignore
  await container.init(__webpack_share_scopes__.default);
  // @ts-ignore
  const factory = await window[scope].get(module);
  const Module = factory();
  return Module;
}

export function injectRemoteScript(remoteUrl: string, scope: string): Promise<void> {
  return new Promise((resolve, reject) => {
    if (document.getElementById(`remote-script-${scope}`)) {
      return resolve();
    }
    const script = document.createElement('script');
    script.id = `remote-script-${scope}`;
    script.src = remoteUrl;
    script.type = 'text/javascript';
    script.async = true;
    script.onload = () => resolve();
    script.onerror = (err) => reject(new Error(`Gagal memuat manifest remote: ${remoteUrl}`));
    document.head.appendChild(script);
  });
}

Alur Failover di Cloudflare Workers

Saat terjadi lonjakan traffic, pekerja di Edge dapat memetakan URL remote secara otomatis ke bucket S3 sekunder jika waktu respon origin remote melampaui ambang batas 1.5 detik, menjamin availabilitas 99.99% bagi pengguna akhir tanpa gangguan.


8. Common Pitfalls, Anti-Patterns & Solusi

1. The Shared State Nightmare (Anti-Pattern)

  • Kesalahan Fatal: Menggunakan state manager global tunggal (seperti Redux Store) yang di-share secara langsung melalui Module Federation antara Host dan semua Remote.
  • Dampak: Kontrak skema state menjadi sangat kaku. Jika Tim A mengubah struktur Redux slice, Remote milik Tim B dan Shell milik Tim C langsung melempar exception undefined di runtime.
  • Solusi Arsitektural: Terapkan arsitektur Event-Driven Communication. Gunakan Custom EventTarget standar browser atau pustaka reaktif terisolasi seperti RxJS dengan tipe payload yang divalidasi via skema Zod.
// shared-events.ts
export interface TransactionEventDetail {
  orderId: string;
  status: 'PENDING' | 'SUCCESS' | 'FAILED';
}

export const dispatchTransactionUpdate = (detail: TransactionEventDetail) => {
  const event = new CustomEvent('corp:transaction:update', { detail });
  window.dispatchEvent(event);
};

2. CSS Collision & Style Bleeding

  • Kesalahan Fatal: Tim Catalog menggunakan Tailwind CSS v3 sementara Tim Shell menggunakan Bootstrap v5. Kelas CSS utilitas global saling menimpa satu sama lain, merusak tampilan visual komponen.
  • Solusi: Terapkan prefix Tailwind atau CSS Modules secara ketat di semua Remote.
// packages/checkout/tailwind.config.js
module.exports = {
  prefix: 'chk-', // Semua kelas tailwind checkout akan menjadi: chk-flex, chk-bg-emerald-600
  content: ['./src/**/*.{js,ts,jsx,tsx}'],
  corePlugins: {
    preflight: false, // MATIKAN CSS Reset global agar tidak merusak gaya Shell Host
  },
};

3. CI/CD Race Condition & Ghost Assets

  • Kesalahan Fatal: CI/CD pipeline me-replace seluruh isi direktori remote di bucket S3 dengan flag --delete saat proses deploy berlangsung.
  • Dampak: Pengguna yang sedang membuka browser dengan sesi lama akan me-request chunk asset lama bertanda contenthash unik yang baru saja dihapus oleh perintah deploy. Timbullah bencana Loading chunk X failed.
  • Solusi: Hanya sinkronisasikan asset baru ke S3 tanpa menghapus build lama (additive upload), dan terapkan lifecycle rule untuk retensi asset minimal selama 30 hari.

9. CI/CD Pipeline untuk Deploy Micro-Frontend Independen

Kunci sukses operasional arsitektur ini adalah memastikan bahwa Tim Checkout dapat merilis kode ke produksi tanpa melibatkan Tim Host sama sekali.

Berikut implementasi pipeline otomatisasi menggunakan GitHub Actions yang menerapkan prinsip zero-downtime deployment dan invalidasi cache selektif pada CDN Cloudflare:

name: Deploy Checkout Remote MFE

on:
  push:
    branches:
      - main
    paths:
      - 'packages/checkout/**'

jobs:
  audit-and-build:
    name: Audit, Test & Compile
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: packages/checkout
    steps:
      - name: Checkout Source Code
        uses: actions/checkout@v4

      - name: Setup Node.js Environment
        uses: actions/setup-node@v4
        with:
          node-version: 20.11.0
          cache: 'pnpm'

      - name: Install pnpm
        uses: pnpm/action-setup@v2
        with:
          version: 8.15.0

      - name: Install Dependencies
        run: pnpm install --frozen-lockfile

      - name: Run Static Analysis & Type Checking
        run: pnpm tsc --noEmit

      - name: Build Webpack Distribution
        run: pnpm build
        env:
          NODE_ENV: production

      # Unggah asset statis hash terlebih dahulu (Immutable assets aman ditimpa)
      - name: Sync Static Chunks to AWS S3
        uses: jakejarvis/s3-sync-action@master
        with:
          args: --acl public-read --ignore "remoteEntry.js" --cache-control "max-age=31536000, public, immutable"
        env:
          AWS_S3_BUCKET: ${{ secrets.AWS_MFE_S3_BUCKET }}
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          AWS_REGION: 'ap-southeast-1'
          SOURCE_DIR: 'packages/checkout/dist'
          DEST_DIR: 'checkout_remote'

      # Unggah remoteEntry.js terakhir untuk mencegah race condition
      - name: Sync Entry Manifest to AWS S3
        uses: jakejarvis/s3-sync-action@master
        with:
          args: --acl public-read --cache-control "no-cache, no-store, must-revalidate"
        env:
          AWS_S3_BUCKET: ${{ secrets.AWS_MFE_S3_BUCKET }}
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          AWS_REGION: 'ap-southeast-1'
          SOURCE_DIR: 'packages/checkout/dist/remoteEntry.js'
          DEST_DIR: 'checkout_remote/remoteEntry.js'

      # Purge cache Cloudflare HANYA untuk remoteEntry.js
      - name: Purge Cloudflare Edge Cache
        run: |
          curl -X POST "https://api.cloudflare.com/client/v4/zones/${{ secrets.CLOUDFLARE_ZONE_ID }}/purge_cache" \
               -H "Authorization: Bearer ${{ secrets.CLOUDFLARE_API_TOKEN }}" \
               -H "Content-Type: application/json" \
               --data '{"files":["https://pay.enterprise.internal/remoteEntry.js"]}'

10. Benchmark, Metrik Kinerja & Tuning Performa

Pengujian dilakukan untuk membuktikan efisiensi runtime dan konsumsi bandwidth antara Host monolitik konvensional dan Host berbasis Module Federation yang mengonsumsi 2 modul remote.

Metrik Pengujian Runtime Menggunakan Google Lighthouse CI

# Eksekusi Lighthouse audit secara terautomasi
lhci collect --url="https://app.corp/" --numberOfRuns=5
lhci assert --assertions:first-contentful-paint=["error",{"maxNumericValue":1200}]

Hasil Analisis Kinerja Lapangan (Field Data Performance)

Metrik Core Web Vitals Monolitik Tradisional Module Federation (Tuned) Dampak Optimasi
First Contentful Paint (FCP) 1.84s 0.82s 55.4% Lebih Cepat
Largest Contentful Paint (LCP) 3.12s 1.35s 56.7% Peningkatan
Cumulative Layout Shift (CLS) 0.02 0.004 Nyaris Nol Fluktuasi Visual
Total Initial JS Transferred 1.85 MB (Gzip) 320 KB (Shell Host Saja) 82.7% Penghematan Bandwidth Awal
Total Blocking Time (TBT) 480 ms 110 ms 77% Pengurangan Freeze Main Thread

Formula Tuning Module Federation Tingkat Lanjut

Untuk mencapai metrik performa di atas, terapkan teknik Resource Hinting dan Eager Consumption pada manifest Host:

<!-- public/index.html milik Host -->
<head>
  <!-- Sambungkan handshake TLS lebih awal ke remote origin -->
  <link rel="preconnect" href="https://pay.enterprise.internal" crossorigin>
  <link rel="dns-prefetch" href="https://pay.enterprise.internal">
  
  <!-- Preload remoteEntry.js agar siap saat Virtual DOM membutuhkannya -->
  <link rel="preload" href="https://pay.enterprise.internal/remoteEntry.js" as="script" crossorigin>
</head>

[!TIP] Gunakan pustaka @module-federation/fm-plugin-visualizer untuk memvisualisasikan duplikasi chunk shared scope selama fase kompilasi untuk mencegah dependensi ganda menyusup ke lingkungan produksi.


11. Checklist Penerapan Produksi & Kesimpulan

Sebelum meluncurkan arsitektur Module Federation ke skala produksi enterprise, lakukan audit menyeluruh terhadap daftar periksa kepatuhan berikut:

  • Isolasi Shared Scope: Seluruh dependensi vendor inti (react, react-dom, framework router) telah didefinisikan sebagai singleton: true.
  • Asynchronous Boundary Barrier: Seluruh modul entrypoint Host dan Remote dipisahkan dengan dynamic import('./bootstrap').
  • Robust Error Boundaries: Komponen remote dibungkus dengan komponen React Fallback UI yang elegan agar crash pada sub-modul tidak merusak seluruh aplikasi.
  • Kebijakan Cache HTTP Edge: File remoteEntry.js memiliki header Cache-Control: no-cache, no-store, must-revalidate, sementara file aset berkode unik (hashed) di-cache selama 1 tahun (immutable).
  • CORS Strict Whitelisting: Konfigurasi server Nginx atau Cloudflare hanya mengizinkan header Access-Control-Allow-Origin dari subdomain terverifikasi milik enterprise.
  • Non-Destructive Deploy Pipeline: Skrip CI/CD tidak menggunakan flag --delete saat menyinkronkan file aset baru ke bucket penyimpanan objek S3.
  • Style Encapsulation: Seluruh pustaka CSS (Tailwind/Sass) menggunakan prefix unik atau CSS Modules guna meniadakan kebocoran selector.

Kesimpulan

Webpack 5 Module Federation bukan sekadar fitur bundler; ini adalah fondasi arsitektur frontend skala enterprise yang mengubah paradigma pengembangan web modern. Dengan memisahkan siklus deployment antar modul tanpa mengorbankan performa runtime berkat shared memory dependency resolution, organisasi enterprise Anda dapat mencapai engineering velocity maksimal, mempercepat time-to-market, dan menghilangkan hambatan blast radius yang selama ini menjadi momok pada arsitektur monolitik frontend.

🏷️ Tags: #Micro-Frontend #Webpack 5 #Typescript #React #Javascript