Micro-Frontends: Single-SPA ve Module Federation

## Micro-Frontends'e Ne Zaman Geçmeli? Micro-frontend mimarisi, tek bir uygulamayı bağımsız olarak geliştirilip dağıtılabilen parçalara ayırır. Ancak...

Micro-Frontends: Single-SPA ve Module Federation

Micro-Frontends'e Ne Zaman Geçmeli?

Micro-frontend mimarisi, tek bir uygulamayı bağımsız olarak geliştirilip dağıtılabilen parçalara ayırır. Ancak bu yaklaşım bedava değildir: her ek katman, beraberinde karmaşıklık, bağımlılık yönetimi ve hata ayıklama maliyeti getirir. Aşağıdaki koşulların en az birkaçı geçerli değilse, monolit veya modüler monolit genellikle daha doğru tercihtir:

  • Birden fazla ekip aynı kod tabanında sık sık çakışıyor ve dağıtım sırasında birbirini bekliyor.
  • Farklı bölümlerin farklı teknoloji yığınlarına (framework, sürüm) ihtiyacı var.
  • Bağımsız dağıtım gerçek bir gereksinim; sadece "modern görünsün" diye istenmiyor.

Bu rehber, iki farklı entegrasyon yaklaşımını karşılaştırır: Single-SPA (uygulama yaşam döngüsü orkestrasyonu) ve Webpack Module Federation (çalışma zamanında modül paylaşımı). İkisi rakip değil, farklı katmanlarda çözüm sunar.

Önkoşullar

  • JavaScript/TypeScript ve modern tarayıcı modül sistemleri hakkında çalışma bilgisi.
  • Bir paket yöneticisi (npm, yarn veya pnpm) ve Node.js kurulu bir geliştirme ortamı.
  • Webpack yapılandırmasının temelleri (entry, output, resolve, plugins).
  • React, Vue veya Angular'dan en az biriyle bileşen geliştirme deneyimi.

Sürüm kapsamı: Single-SPA'nın kendi sürüm numarasını burada sabitlemiyorum; kurulumda npm view single-spa version ile mevcut sürümü kontrol et. Module Federation, Webpack 5 ile birlikte gelir; Webpack 4'te yerleşik olarak bulunmaz.

Single-SPA: Yaşam Döngüsü Orkestrasyonu

Single-SPA, bir "root config" (kök yapılandırma) uygulaması çalıştırır ve kayıtlı micro-frontend'leri URL'ye göre yükler. Her micro-frontend, standart bir yaşam döngüsü sözleşmesini uygular.

1. Kök yapılandırma

// root-config.js
import { registerApplication, start } from "single-spa";

registerApplication({
  name: "@org/navbar",
  app: () => import("@org/navbar"),
  activeWhen: ["/"],
});

registerApplication({
  name: "@org/products",
  app: () => import("@org/products"),
  activeWhen: (location) => location.pathname.startsWith("/products"),
});

start({
  urlRerouteOnly: true,
});

activeWhen koşulu sağlandığında Single-SPA ilgili uygulamayı yükler ve etkinleştirir. urlRerouteOnly: true ayarı, yalnızca URL değiştiğinde yeniden yönlendirme yapılmasını sağlar; bu, gereksiz yeniden yüklemeleri azaltır.

2. Micro-frontend yaşam döngüsü

Her uygulama dört fonksiyondan birini veya birkaçını dışa aktarır: bootstrap, mount, unmount, unload. Framework'e özgü sarmalayıcılar (single-spa-react, single-spa-vue vb.) bu sözleşmeyi üretir.

// products.js — React örneği (single-spa-react kullanıldığında)
import React from "react";
import ReactDOM from "react-dom";
import singleSpaReact from "single-spa-react";
import Root from "./root.component";

const lifecycles = singleSpaReact({
  React,
  ReactDOM,
  rootComponent: Root,
  errorBoundary(err) {
    return <div>Ürünler yüklenemedi.</div>;
  },
});

export const { bootstrap, mount, unmount } = lifecycles;

Beklenen davranış: /products yoluna gidildiğinde mount çağrılır ve bileşen DOM'a eklenir; başka bir rotaya geçildiğinde unmount çağrılır ve bileşen temizlenir. unmount içinde aboneliklerin, zamanlayıcıların ve global olay dinleyicilerinin temizlenmesi gerekir; aksi halde bellek sızıntıları oluşur.

Webpack Module Federation: Çalışma Zamanında Modül Paylaşımı

Module Federation, bir uygulamanın (host) başka bir uygulamanın (remote) modüllerini çalışma zamanında yüklemesini sağlar. Single-SPA'nın aksine, rota tabanlı orkestrasyon yerine modül düzeyinde bir entegrasyon sunar.

Remote uygulama

// remote/webpack.config.js
const { ModuleFederationPlugin } = require("webpack").container;

module.exports = {
  output: {
    publicPath: "http://localhost:3001/",
    uniqueName: "products",
  },
  plugins: [
    new ModuleFederationPlugin({
      name: "products",
      filename: "remoteEntry.js",
      exposes: {
        "./ProductList": "./src/ProductList",
      },
      shared: {
        react: { singleton: true, requiredVersion: "^18.0.0" },
        "react-dom": { singleton: true, requiredVersion: "^18.0.0" },
      },
    }),
  ],
};

exposes alanı, dışarıya açılan modülleri tanımlar. shared ile React gibi bağımlılıkların tek bir kopyasının yüklenmesi hedeflenir; singleton: true bu davranışı zorlar.

Host uygulama

// host/webpack.config.js
const { ModuleFederationPlugin } = require("webpack").container;

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: "shell",
      remotes: {
        products: "products@http://localhost:3001/remoteEntry.js",
      },
      shared: {
        react: { singleton: true, requiredVersion: "^18.0.0" },
        "react-dom": { singleton: true, requiredVersion: "^18.0.0" },
      },
    }),
  ],
};
// host/src/App.jsx
import React, { Suspense, lazy } from "react";

const ProductList = lazy(() => import("products/ProductList"));

export default function App() {
  return (
    <Suspense fallback={<div>Yükleniyor...</div>}>
      <ProductList />
    </Suspense>
  );
}

Beklenen davranış: Host başlatıldığında remoteEntry.js yüklenir ve ProductList modülü çalışma zamanında çözülür. Remote erişilemezse Suspense fallback'i gösterilir; ancak üretim ortamında ağ hatalarını ve sürüm uyumsuzluklarını ayrıca ele almak gerekir.

İki Yaklaşımı Birlikte Kullanmak

Single-SPA rotayı ve yaşam döngüsünü yönetir; Module Federation ise paylaşılan modülleri sağlar. Gerçek projelerde sık görülen desen: Single-SPA kök yapılandırması, her micro-frontend'i import() ile yükler ve bu micro-frontend'ler kendi içlerinde Module Federation remote'larını tüketir. Bu durumda iki katmanlı bir bağımlılık grafiği oluşur; hangi modülün hangi katmanda yüklendiğini net biçimde belgelemek gerekir.

Yaygın Hatalar

  • Paylaşılan bağımlılıkların çift yüklenmesi. shared yapılandırması eksik veya singleton ayarlanmamışsa React iki kez yüklenir; hook hataları ve beklenmeyen davranışlar ortaya çıkar.
  • unmount içinde temizlik yapılmaması. Single-SPA yaşam döngüsünde olay dinleyicileri ve abonelikler temizlenmezse bellek sızıntısı oluşur.
  • publicPath yanlış yapılandırması. Remote uygulamanın publicPath değeri yanlışsa remoteEntry.js ve chunk'lar yüklenemez.
  • Sürüm uyumsuzluğu. Host ve remote farklı React sürümleri bekliyorsa requiredVersion uyuşmazlığı çalışma zamanı hatasına yol açar.
  • Aşırı parçalama. Her küçük bileşeni ayrı micro-frontend yapmak, dağıtım ve hata ayıklama maliyetini gereksiz artırır.

Güvenlik Etkileri

  • Çalışma zamanı kod yükleme. Module Federation, remote uygulamadan gelen JavaScript'i çalıştırır. Remote kaynağı güvenilir değilse bu, keyfi kod yürütmeye eşdeğerdir. Remote URL'lerini sabit ve denetimli tut.
  • Bağımlılık zinciri genişlemesi. Her remote, kendi bağımlılıklarını getirir; tedarik zinciri saldırı yüzeyi büyür. Sürümleri kilitle ve düzenli denetle.
  • CSP ve remoteEntry.js. Katı bir Content Security Policy, dinamik script yüklemeyi engelleyebilir. script-src politikasını remote kaynaklarına göre ayarlaman gerekir.
  • Sır paylaşımı. Micro-frontend'ler arasında paylaşılan bir global durum üzerinden API anahtarı veya token taşımaktan kaçın.

Sonuç

Single-SPA ve Module Federation farklı sorunları çözer: biri uygulama yaşam döngüsünü ve rotayı, diğeri modül paylaşımını yönetir. Doğru tercih, ekibin dağıtım bağımsızlığı ihtiyacına ve mevcut teknoloji yığınına bağlıdır. Her iki yaklaşımda da paylaşılan bağımlılıkların yönetimi, yaşam döngüsü temizliği ve güvenilir remote kaynakları, üretimde karşılaşılan sorunların büyük kısmını oluşturur.

İLGİLİ İÇERİKLER

Bunları da Okumak İsteyebilirsiniz