Webpack Module Federation ile Micro Frontend Mimarisi (1. Bölüm)

Sercan Eraslan
7 min readNov 10, 2021

--

English version: Micro Frontends Architecture with Webpack Module Federation (Part 1)

Trendyol GOda (Hızlı Market ve Trendyol Yemek) siparişlerinin tüm aşamalarını takip ettiğimiz ve yönettiğimiz React ile yazılmış bir Admin Panelimiz var. İlk başta tek ekip olduğumuz için ve çalışan sayımız az olduğu için Monolithic bir yapı ihtiyaçlarımızı çok iyi karşıladı.

Monolithic yapılar her zaman kötü değildir eğer ihtiyaçlarınızı karşılıyorsa ve size negatif bir etkisi yoksa her yöntem tercih edilebilir. Önemli olan; amaca en verimli şekilde ulaşmanın yolunu bulmaktır.

Yaklaşık 1,5 yıl sonra ekip arkadaşlarımızın sayısı yeterli seviyeye geldiği için Domain Driven Design (DDD) felsefesiyle ekiplerimizi anlamlı domainlere böldük ve bu noktada her ekibin özgürce hareket etmesi ve olabildiğince bağımsız hissedebilmesi için Micro Frontend yapısını tasarlama ihtiyacımız ortaya çıktı.

Daha öncesinde bir kaç farklı projede Micro Frontend deneyimim olmuştu. Bir Micro Frontend mimarisi de kendim tasarlamıştım ama diğer tüm alternatifleri de araştırıp içlerinden ihtiyacımızı en iyi şekilde karşılayacak olanı seçmek daha mantıklıydı. Tüm alternatifleri araştırıp hepsinin artılarını eksilerini değerlendirdik (Bu yazıda tüm alternatiflerden bahsetmeyeceğim çünkü o farklı bir konu) ve yaptığımız değerlendirmenin sonunda Webpack Module Federation’ın bizim ihtiyaçlarımızı çok daha iyi karşılayacağına karar verdik.

Neden Webpack Module Federation?

Tüm alternatifleri incelediğimizde aşağıdaki sebeplerden dolayı Webpack Module Federation’ı tercih etmek çok daha mantıklı geldi.

  • Bakım maliyeti yok (Kendin bir mimari inşa edersen bakım maliyeti olacak)
  • Mimari kırılgan değil (Kendin bir mimari inşa edersen kırılgan olabilir)
  • Ekibe özel öğrenme maliyeti yok (Kendin bir mimari inşa edersen öğrenme maliyeti olacak)
  • Module Federation’a geçiş çok hızlı
  • Her projede yeniden bir mimari inşa etmene gerek yok
  • Tüm gereksinimler build time’da hallediliyor
  • Runtime’da ekstra bir iş yapılmasına gerek yok
  • Dependency’leri çok rahat paylaşabiliyorsun
  • Library/Framework bağımsız
  • Tüm sıkıştırma ve cache sorunlarıyla uğraşmıyorsun
  • Routing sorunlarıyla uğraşmıyorsun
  • Shell ve Micro App’ler birbiriyle ilişkileri tightly coupled değil loosely coupled

Webpack Module Federation Nasıl Kullanılır?

3 farklı şekilde kullanabilirsiniz.

1. Domain

Bu yöntemde istediğiniz kadar Micro Frontend (App) oluşturup birbirinden tamamen bağımsız domainleri Shell App ile yönetebilirsiniz. Mesela Shell App’te bir Menü olsun ve linklere tıklandığında sağ tarafta ilgili App’leri getirsin isterseniz bu yöntemi kullanabilirsiniz.

2. Widget

Bu yöntemde istediğiniz App’ten istediğiniz widget’ı/component’i yani küçük bir parçayı istediğiniz App’e ekleyebilirsiniz. Mesela User App’inde bulunan UserDetail component’ini Product App’i içinde gösterebilirsiniz.

3. Hybrid

Bu yöntemde ise 1. ve 2. yöntemleri beraber kullanabilirsiniz.

Talk is cheap, show me the code!

İsterseniz hemen bir PoC yapalım ve neden diğer yöntemlerden çok daha iyi olduğunu beraber görelim.

Bu PoC’yi https://github.com/module-federation-examples isimli Github organizasyonuna ekledim, direkt olarak fork’layıp istediğiniz kadar deneme yapabilirsiniz :)

İlk olarak Shell App’imizi oluşturalım. Bu bizim yönetici App’imiz olacak. Daha kolay olduğu için tüm App’leri create-react-app script’i ile oluşturacağım, siz isterseniz normal kurulum yapabilirsiniz. Her ne kadar create-react-app ile birlikte hazır bir Webpack config dosyası repoya eklenecek olsa da biz zaten bu dosyayı ileri aşamalarda değiştireceğiz.

Not: create-react-app henüz Webpack 5'i desteklemiyor o yüzden manuel olarak ekleyeceğiz.

npx create-react-app shellcd shellyarn add webpack webpack-cli webpack-server html-webpack-plugin css-loader style-loader babel-loader webpack-dev-server

Product ve User App’lerimizi de aynı yukarıdaki şekilde sadece isimleri değiştirerek oluşturuyoruz.

1. Adım — Bootstraping

src klasörü altına bootstrap.js isimli bir dosya ekleyelim ve içeriği şöyle olsun;

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
<App />,
document.getElementById('root')
);

src klasörü altındaki index.js dosyasının içeriğine de şöyle değiştirelim.

import('./bootstrap');

Bu işlemleri asenkron yükleme yapılsın diye yani App’lerin tamamen hazır olduğuna emin olabilmemiz için yapıyoruz. 3 repoda da bu işlemleri tekrarlayalım.

2. Adım — Webpack Config

Shell App’inin root’una webpack.config.js adında bir dosya ekliyoruz ve içeriğini aşağıdaki şekilde değiştiriyoruz.

const HtmlWebpackPlugin = require('html-webpack-plugin');
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
const deps = require('./package.json').dependencies;
module.exports = {
mode: 'development',
devServer: {
port: 3001,
},
module: {
rules: [
{
test: /\.js?$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env',
'@babel/preset-react',
],
},
},
{
test: /\.css$/i,
use: ["style-loader", "css-loader"],
},
],
},
plugins: [
new ModuleFederationPlugin(
{
name: 'SHELL',
filename: 'remoteEntry.js',
shared: [
{
...deps,
react: { requiredVersion: deps.react, singleton: true },
'react-dom': {
requiredVersion: deps['react-dom'],
singleton: true,
},
},
],
}
),
new HtmlWebpackPlugin({
template:
'./public/index.html',
}),
],
};

Bu dosyada Micro Frontend ile ilgili olan tek kısım ModuleFederationPlugin fonksiyonu ve içine gönderdiğimiz obje. Diğer satırlar normal Webpack config’i ile ilgili.

  • name: Uygulamanın ismini belirlemek için kullanıyoruz. Diğer App’lerle bu isim üzerinden haberleşeceğiz.
  • filename: Giriş dosyası olarak kullanıyoruz. Bu örnekte diğer App’ler SHELL uygulamasına SHELL@http://localhost:3001/remoteEntry.js yazarak erişebilecekler.
  • shared: Bu uygulama hangi dependency’leri diğer uygulamalarla paylaşacak onu belirtmek için kullanıyoruz. Burada dikkat edilmesi gereken nokta singleton: true, eğer singleton: true demezseniz her uygulama ayrı bir React Instance’ı üzerinde çalışır.

Aynı dosyayı Product ve User App’ine de kopyalayın ama port’u artırmayı ve name alanını değiştirmeyi unutmayın.

3. Adım — Tasarım

src klasörü altındaki App.js’i aşağıdaki şekilde değiştirelim.

import React from 'react';
import './App.css';
const App = () => (
<div className="shell-app">
<h2>Hi from Shell App</h2>
</div>
);
export default App;

src klasörü altındaki App.css’i aşağıdaki şekilde değiştirelim.

.shell-app {
margin: 5px;
text-align: center;
background: #FFF3E0;
border: 1px dashed #FFB74D;
border-radius: 5px;
color: #FFB74D;
}

Aynı değişikliği Product ve User App’lerinde de yapalım ve shell yazan yerleri değiştirelim. Renkleri de değiştirmeyi unutmayın.

Şimdi her bir repoya tek tek girerek aşağıdaki komutu çalıştırın.

yarn webpack server

Artık tüm uygulamalarımız Micro Frontend mimarisi için hazır ve birbirinden bağımsız olarak çalışabilir durumda 🎉

4. Adım — Son Dokunuşlar

Module Federation’daki harika 2 özelliğe değinmenin sırası geldi :)

  • exposes: Herhangi bir uygulamadan diğerine bir component’i, bir sayfayı ya da uygulamanın tamamını paylaşmanızı sağlar. expose ettiğiniz her şey ayrı bir build olarak oluşturulur ve böylece doğal bir tree shaking yapılmış olur. Her bir build’in ismi dosyanın MD5 hash’i ile verilir ve böylece cache’lenme sorununu düşünmek zorunda kalmazsınız.
  • remotes: Hangi uygulamalardan bir component’i, bir sayfayı ya da uygulamanın kendisini mi alacağınızı belirler.

Her bir uygulama hem expose edebilir hem de remote alabilir ve bunları birden fazla kez yapabilir.

Şimdi Product App’ini Shell App’ine expose edelim ve böylece ilk Micro Frontend bağlantımızı yapmış olalım.

product reposunda bulunan webpack.config.js dosyasını açalım ve içindeki ModuleFederationPlugin metoduna gönderilen objeyi aşağıdaki gibi değiştirelim. exposes objesindeki value repodaki hangi component’i paylaştığını belirliyor ve objedeki key ise diğer uygulamaların bu component’e hangi isimle erişebileceğini belirliyor.

new ModuleFederationPlugin(
{
name: 'PRODUCT',
filename: 'remoteEntry.js',
exposes: {
'./App': './src/App',
},
shared: [
{
...deps,
react: { requiredVersion: deps.react, singleton: true },
'react-dom': {
requiredVersion: deps['react-dom'],
singleton: true,
},
},
],
}
),

shell reposunda bulunan webpack.config.js dosyasını açalım ve içindeki ModuleFederationPlugin metoduna gönderilen objeyi aşağıdaki gibi değiştirelim. remotes objesindeki value product reposuna nasıl erişeceğini belirliyor (@ işaretinden önceki isim product reposundaki Webpack config içerisindeki isim ile aynı olmalı), objedeki key ise sadece isimle product reposuna erişmemize imkan sağlıyor.

new ModuleFederationPlugin(
{
name: 'SHELL',
filename: 'remoteEntry.js',
remotes: {
PRODUCT: 'PRODUCT@http://localhost:3002/remoteEntry.js'
},
shared: [
{
...deps,
react: { requiredVersion: deps.react, singleton: true },
'react-dom': {
requiredVersion: deps['react-dom'],
singleton: true,
},
},
],
}
),

2 App’i birbirlerine bağladık. Şimdi de Shell App’inde Product App’ini nasıl kullanacağımızı görelim. shell reposundaki App.js’i açıp aşağıdaki değişiklikleri yapalım.

import React from 'react';
import './App.css';
const ProductApp = React.lazy(
() => import('PRODUCT/App')
);
const App = () => (
<div className="App">
<h2>Hi from Shell App</h2>
<React.Suspense fallback='Loading...'>
<ProductApp />
</React.Suspense>
</div>
);
export default App;

React’ın lazy metoduyla PRODUCT App’inden expose edilen “App” isimli component’i ProductApp isimli değişkenimize tanımladık. Farklı bir Micro Frontend’ten alacağımız component’ler için lazy ve template kısmında kullanabilmek için Suspense kullanmamız gerekiyor ki sayfada her şeyin yüklendiğine emin olalım.

Evet, işte bu kadar 🎉

İsterseniz şimdi User App’ine bir component ekleyin ve bu component’i Product App’i içinde kullanmaya çalışın :) expose ve remote’ları kullanarak istediğiniz kadar component’i birbirleri ile paylaşabilirsiniz. Module Federation’ı yaratan Zack Jackson’ın module-federation-examples adında bir Github reposu var. Bu repoda React, Vue, Angular, Server Side Rendering, Shared Routing gibi bir çok konuda örnek uygulamaları var isterseniz inceleyebilirsiniz.

Kapanış

Webpack Module Federation henüz tam olarak stable değil ancak hala en iyi çözüm. Şu anda bu yöntemi Canlı’da Trendyol GO için kullanıyoruz, Trendyol’da 1 farklı ekip daha canlıda kullanıyor ve 2 farklı ekip de Webpack Module Federation’a geçme kararı aldı. Module Federation çok hızlı ve işleri çok kolaylaştırıyor, tekerleği yeniden icat etmeye hiç gerek yok :) Webpack’in ilerleyen versiyonlarında çok daha iyi olacağına eminim :)

Webpack Module Federation ile Micro Frontend Mimarisi (2. Bölüm) yazımızda ise PoC aşamasından Canlı’ya çıkana kadar ki edindiğimiz deneyimlerimizi anlatıyoruz, bekleriz :)

Yeni yazılarımızda görüşmek üzere.

--

--