2025-09-04
Mobil Micro Frontend'ler: Production'da Çok Kanallı Deneyimler
React Native ve WebView'lar ile mobil micro frontend'ler oluşturma. Çok kanallı uygulamalar için production stratejileri ve performans optimizasyonu.
Mobil öncelikli micro frontend mimarileri, ikinci bir kanalla (web, desktop) karşılaştıklarında değişmeden nadiren ayakta kalır. Mimariyi mobilde çalıştıran varsayımlar (WebView yaşam döngüsü, native köprü, native gesture’lar, kanal başına asset bundle’ları) ya web ve desktop’a hiç eşlenmez ya da kodu paylaşmanın amacını ortadan kaldıran bir performans maliyetine eşlenir. Gerçek çok kanallı micro frontend’ler (mobil, web, desktop Electron) sadece “bir kez yaz, her yere deploy et” değil, bir runtime soyutlama katmanı ve kanala özel kod yollarında bir disiplin gerektirir.
Bu yazı, gerçek anlamda çok kanallı micro frontend’ler için mimariyi ve üretim optimizasyonlarını ele alır. Runtime soyutlamasını (ne paylaşılır, ne ayrılır), tek bir kod tabanını kanallar arasında uygulanabilir kılan performans desenlerini, bundle bölme stratejilerini, native köprü disiplinini ve üç ayrı sürüm trenini koordine etmeden web, mobil ve Electron’a güncelleme yayınlamak için operasyonel modeli kapsar.
Mobil Micro Frontend Serisi
Bu, mobil micro frontend serimizin 3. Kısmı (son):
- Kısım 1: Mimari temelleri ve WebView entegrasyon kalıpları
- Kısım 2: WebView iletişim kalıpları ve servis entegrasyonu
- Kısım 3 (Burada bulunuyorsunuz): Çok kanallı mimari ve production optimizasyonu
Yeni başlıyor musunuz? Temeller için Kısım 1 ile başlayın.
İletişim kalıplarına mı ihtiyacınız var? Önce Kısım 2’ye bakın.
Alternatif Çok Kanallı Yaklaşımlar
Çok kanallı çözümümüze dalmadan önce, değerlendirdiğimiz alternatif yaklaşımları ve neden mevcut mimarimizi seçtiğimizi paylaşayım.
Seçenek 1: Re.Pack Çok Kanallı Mimari
Re.Pack, Module Federation aracılığıyla React Native, web ve masaüstü için birleşik bir yaklaşım sunar:
Denediğimiz şey:
// Re.Pack çok kanallı kurulum
// webpack.config.js (paylaşılan konfigürasyon)
const { ModuleFederationPlugin } = require('@module-federation/nextjs-mf');
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'host',
remotes: {
payment: 'payment@http://localhost:3001/remoteEntry.js',
booking: 'booking@http://localhost:3002/remoteEntry.js',
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true },
'@shared/platform': { singleton: true }
}
})
]
};
// Platform abstraction katmanı
// @shared/platform/index.ts
export interface PlatformAPI {
// Navigasyon
navigate(screen: string, params?: any): void;
goBack(): void;
// Native özellikler
camera: {
takePhoto(): Promise<string>;
selectFromGallery(): Promise<string>;
};
// Depolama
storage: {
get(key: string): Promise<any>;
set(key: string, value: any): Promise<void>;
};
// Ağ
network: {
isOnline(): boolean;
onNetworkChange(callback: (online: boolean) => void): void;
};
}
// Platform-specific implementasyonlar
export class ReactNativePlatform implements PlatformAPI {
// React Native implementasyonu
}
export class WebPlatform implements PlatformAPI {
// Web implementasyonu
}
export class ElectronPlatform implements PlatformAPI {
// Electron implementasyonu
}
Neden seçmedik:
- Karmaşıklık: Mevcut uygulamaların önemli refactoring’ini gerektiriyordu
- Takım koordinasyonu: Tüm takımların aynı anda benimser gerekiyordu
- Performans: Mobile’da Module Federation overhead’ı
- Hata ayıklama: Platformlar arası karmaşık stack trace’ler
Ne zaman Re.Pack çok kanallı kullanılır:
- Sıfırdan yeni bir super app inşa ediyorsanız
- Tüm takımlar paylaşılan mimaride koordinasyon kurabiliyorsa
- Platformlar arası gerçek kod paylaşımına ihtiyacınız varsa
- Performans overhead’ı kabul edilebilirse
Seçenek 2: Rspack Çok Kanallı Build’ler
Çok kanallı build’ler için Rspack kullanmayı denedik:
// rspack.config.mjs - Çok kanallı konfigürasyon
export default {
entry: './src/index.tsx',
target: ['web', 'electron-renderer'], // Birden fazla target
module: {
rules: [
{
test: /\.tsx$/,
use: {
loader: 'builtin:swc-loader',
options: {
jsc: {
parser: {
syntax: 'typescript',
tsx: true
},
transform: {
react: {
runtime: 'automatic'
}
}
}
}
}
}
]
},
plugins: [
new ModuleFederationPlugin({
name: 'micro-frontend',
filename: 'remoteEntry.js',
exposes: {
'./App': './src/App.tsx'
}
})
]
};
// Platform tespiti
const platform = process.env.PLATFORM || 'web';
// Platform'a göre koşullu import'lar
const platformAPI = platform === 'react-native'
? require('./platforms/react-native')
: platform === 'electron'
? require('./platforms/electron')
: require('./platforms/web');
Neden seçmedik:
- React Native uyumluluğu: Re.Pack 5.x artık Rspack’i destekliyor, ancak bizim değerlendirmemiz React Native desteği sınırlı olan önceki sürümler sırasında yapıldı
- Build karmaşıklığı: Birden fazla target build’i karmaşıktı
- Ekosistem olgunluğu: Daha az örnek ve dokümantasyon
- Takım benimsenemesi: Önemli yeniden eğitim gerektirecekti
Ne zaman Rspack çok kanallı kullanılır:
- Sadece web ve masaüstü inşa ediyorsanız
- Build performansı kritikse
- Erken benimseyen olmayı göze alabiliyorsanız
- Takımlarınız Rust tabanlı araçlarla rahatta
Seçenek 3: Vite Çok Kanallı Mimari
Çok kanallı build’ler için Vite’ı da değerlendirdik:
// vite.config.ts - Çok kanallı kurulum
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig(({ mode }) => {
const platform = process.env.PLATFORM || 'web';
return {
plugins: [react()],
build: {
rollupOptions: {
external: platform === 'electron' ? ['electron'] : [],
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
platform: [`./src/platforms/${platform}`]
}
}
}
},
define: {
__PLATFORM__: JSON.stringify(platform)
}
};
});
// Platform-specific giriş noktaları
// src/platforms/web/index.ts
export class WebPlatform {
// Web-specific implementasyon
}
// src/platforms/electron/index.ts
export class ElectronPlatform {
// Electron-specific implementasyon
}
Neden seçmedik:
- Module Federation: Module Federation v2 artık stabil, ancak bizim değerlendirmemiz sırasında Vite’ın Module Federation’ı hala deneyseldi
- Production build’ler: Bizim kullanım durumumuz için webpack’ten daha yavaştı
- Plugin ekosistemi: Özel ihtiyaçlarımız için daha az plugin
- Takım aşinalığı: Takımlar webpack ile daha rahattı
Ne zaman Vite çok kanallı kullanılır:
- Modern web uygulamaları inşa ediyorsanız
- Geliştirme hızı production optimizasyonundan daha önemliyse
- Karmaşık Module Federation’a ihtiyacınız yoksa
- Takımlarınız modern araçları tercih ederse
Seçenek 4: Hibrit Yaklaşım (Gerçekte Yaptığımız)
Tüm seçenekleri değerlendirdikten sonra, bize tüm dünyaların en iyisini veren hibrit bir yaklaşım seçtik:
Neden bu işe yaradı:
- Takım özerkliği: Her takım tercih ettiği bundler’ı kullanabiliyordu
- Kademeli migrasyon: Takımlar zaman içinde daha iyi araçlara migrate olabiliyordu
- Risk azaltması: Bir yaklaşım başarısız olsa, diğerleri çalışmaya devam ediyordu
- Performans: Her takım kendi build’lerini optimize edebiliyordu
Çok Kanallı Meydan Okuma
Orijinal mobil mimarimiz React Native WebView’ları için harika çalışıyordu, ancak web ve masaüstüne genişletmek birkaç sorunu ortaya çıkardı:
- Performans: Masaüstü kullanıcıları native düzeyinde performans bekliyordu
- Navigasyon: Tarayıcı geri butonu vs native navigasyon
- Kimlik doğrulama: Platformlar arası farklı güvenlik modelleri
- Offline: Mobile’ın offline çalışması gerekiyor, web geleneksel olarak yapmıyor
- Platform API’ları: Kamera her yerde farklı çalışıyor
Çok Kanallı Mimari Genel Bakış
İşte tüm platformları ele almak için geliştirdiğimiz mimari:
Platform Tespiti ve Adaptasyon
İlk zorluk, runtime ortamını güvenilir bir şekilde tespit etmekti:
// Gerçekten çalışan platform tespiti
export enum Platform {
REACT_NATIVE_IOS = 'react-native-ios',
REACT_NATIVE_ANDROID = 'react-native-android',
WEB_BROWSER = 'web-browser',
ELECTRON_DESKTOP = 'electron-desktop',
UNKNOWN = 'unknown'
}
class PlatformDetector {
private static _platform?: Platform;
static get platform(): Platform {
if (this._platform) return this._platform;
// Önce React Native'i kontrol et
if (typeof navigator !== 'undefined' &&
navigator.product === 'ReactNative') {
// React Native WebView'dayız
if (window.ReactNativeWebView) {
// User agent aracılığıyla iOS vs Android tespit et
const userAgent = navigator.userAgent;
this._platform = userAgent.includes('iPhone') || userAgent.includes('iPad')
? Platform.REACT_NATIVE_IOS
: Platform.REACT_NATIVE_ANDROID;
} else {
this._platform = Platform.UNKNOWN;
}
}
// Electron'u kontrol et
else if (typeof window !== 'undefined' &&
window.process?.type === 'renderer') {
this._platform = Platform.ELECTRON_DESKTOP;
}
// Web tarayıcısı olmalı
else if (typeof window !== 'undefined') {
this._platform = Platform.WEB_BROWSER;
}
else {
this._platform = Platform.UNKNOWN;
}
return this._platform;
}
static isMobile(): boolean {
return this.platform === Platform.REACT_NATIVE_IOS ||
this.platform === Platform.REACT_NATIVE_ANDROID;
}
static isWeb(): boolean {
return this.platform === Platform.WEB_BROWSER;
}
static isDesktop(): boolean {
return this.platform === Platform.ELECTRON_DESKTOP;
}
static hasNativeBridge(): boolean {
return this.isMobile() || this.isDesktop();
}
}
Evrensel Bridge Mimarisi
Anahtar içgörü, tüm platformlarda çalışabilen birleşik bir bridge arayüzü oluşturmaktı:
// Evrensel bridge arayüzü
export interface UniversalBridge {
// Çekirdek mesajlaşma
request<T>(action: string, payload?: any): Promise<T>;
emit(action: string, payload?: any): void;
on(action: string, handler: (payload: any) => void): () => void;
// Platform yetenekleri
getCapabilities(): PlatformCapabilities;
isSupported(feature: string): boolean;
}
export interface PlatformCapabilities {
hasCamera: boolean;
hasBiometrics: boolean;
hasFileSystem: boolean;
hasNotifications: boolean;
canGoBack: boolean;
hasClipboard: boolean;
canDownload: boolean;
hasNativeNavigation: boolean;
}
// Platform-specific implementasyonlar
class ReactNativeBridge implements UniversalBridge {
private bridge: WebViewBridge;
constructor() {
this.bridge = window.bridge; // Kısım 2'den
}
async request<T>(action: string, payload?: any): Promise<T> {
return this.bridge.request(action, payload);
}
emit(action: string, payload?: any): void {
this.bridge.emit(action, payload);
}
on(action: string, handler: (payload: any) => void): () => void {
return this.bridge.on(action, handler);
}
getCapabilities(): PlatformCapabilities {
return {
hasCamera: true,
hasBiometrics: true,
hasFileSystem: true,
hasNotifications: true,
canGoBack: true,
hasClipboard: true,
canDownload: true,
hasNativeNavigation: true
};
}
isSupported(feature: string): boolean {
return this.getCapabilities()[feature as keyof PlatformCapabilities] || false;
}
}
class WebBridge implements UniversalBridge {
private eventEmitter = new EventTarget();
async request<T>(action: string, payload?: any): Promise<T> {
// Web API'ları kullanarak native özellikleri simüle et
switch (action) {
case 'camera.takePhoto':
return this.handleWebCamera(payload) as Promise<T>;
case 'auth.getToken':
return this.handleWebAuth() as Promise<T>;
case 'navigation.goBack':
return this.handleWebNavigation() as Promise<T>;
default:
throw new Error(`Web'de desteklenmeyen eylem: ${action}`);
}
}
emit(action: string, payload?: any): void {
this.eventEmitter.dispatchEvent(
new CustomEvent(action, { detail: payload })
);
}
on(action: string, handler: (payload: any) => void): () => void {
const listener = (event: any) => handler(event.detail);
this.eventEmitter.addEventListener(action, listener);
return () => {
this.eventEmitter.removeEventListener(action, listener);
};
}
private async handleWebCamera(options: any): Promise<any> {
// HTML5 kamera API'sını kullan
const stream = await navigator.mediaDevices.getUserMedia({
video: {
width: { ideal: 1920 },
height: { ideal: 1080 }
}
});
return new Promise((resolve, reject) => {
const video = document.createElement('video');
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d')!;
video.srcObject = stream;
video.play();
video.onloadedmetadata = () => {
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
// Kamera yakalama UI'sını simüle et
const captureButton = document.createElement('button');
captureButton.textContent = 'Yakala';
captureButton.onclick = () => {
ctx.drawImage(video, 0, 0);
const dataUri = canvas.toDataURL('image/jpeg', options?.quality || 0.8);
stream.getTracks().forEach(track => track.stop());
document.body.removeChild(video);
document.body.removeChild(captureButton);
resolve({
uri: dataUri,
width: canvas.width,
height: canvas.height
});
};
document.body.appendChild(video);
document.body.appendChild(captureButton);
};
});
}
private async handleWebAuth(): Promise<any> {
// Web kimlik doğrulaması için localStorage/sessionStorage kullan
const token = localStorage.getItem('auth_token');
if (!token) {
throw new Error('Kimlik doğrulama token'ı yok');
}
return {
token,
expiresAt: Date.now() + 3600000 // 1 saat
};
}
private async handleWebNavigation(): Promise<boolean> {
if (window.history.length > 1) {
window.history.back();
return true;
}
return false;
}
getCapabilities(): PlatformCapabilities {
return {
hasCamera: !!navigator.mediaDevices?.getUserMedia,
hasBiometrics: false, // Tarayıcılarda mevcut değil
hasFileSystem: false, // Tarayıcılarda sınırlı
hasNotifications: 'Notification' in window,
canGoBack: true,
hasClipboard: !!navigator.clipboard,
canDownload: true,
hasNativeNavigation: false
};
}
isSupported(feature: string): boolean {
return this.getCapabilities()[feature as keyof PlatformCapabilities] || false;
}
}
class ElectronBridge implements UniversalBridge {
async request<T>(action: string, payload?: any): Promise<T> {
// Ana process ile iletişim için Electron'un IPC'sini kullan
return window.electronAPI.invoke(action, payload);
}
emit(action: string, payload?: any): void {
window.electronAPI.send(action, payload);
}
on(action: string, handler: (payload: any) => void): () => void {
const listener = (_: any, ...args: any[]) => handler(...args);
window.electronAPI.on(action, listener);
return () => {
window.electronAPI.removeListener(action, listener);
};
}
getCapabilities(): PlatformCapabilities {
return {
hasCamera: true,
hasBiometrics: false, // Native modül gerekir
hasFileSystem: true,
hasNotifications: true,
canGoBack: true,
hasClipboard: true,
canDownload: true,
hasNativeNavigation: true
};
}
isSupported(feature: string): boolean {
return this.getCapabilities()[feature as keyof PlatformCapabilities] || false;
}
}
// Bridge factory
export class BridgeFactory {
static create(): UniversalBridge {
const platform = PlatformDetector.platform;
switch (platform) {
case Platform.REACT_NATIVE_IOS:
case Platform.REACT_NATIVE_ANDROID:
return new ReactNativeBridge();
case Platform.ELECTRON_DESKTOP:
return new ElectronBridge();
case Platform.WEB_BROWSER:
return new WebBridge();
default:
throw new Error(`Desteklenmeyen platform: ${platform}`);
}
}
}
Adaptif UI Bileşenleri
Farklı platformlar, farklı UX kalıplarına ihtiyaç duyuyordu. Adaptif bileşenler kurduk:
// Adaptif buton bileşeni
interface AdaptiveButtonProps {
title: string;
onPress: () => void;
variant?: 'primary' | 'secondary';
disabled?: boolean;
}
export function AdaptiveButton({
title,
onPress,
variant = 'primary',
disabled = false
}: AdaptiveButtonProps) {
const platform = PlatformDetector.platform;
// Platform-specific stiller
const getButtonStyles = () => {
const baseStyles = 'px-4 py-2 rounded font-medium transition-colors';
if (disabled) {
return `${baseStyles} bg-gray-300 text-gray-500 cursor-not-allowed`;
}
const variantStyles = variant === 'primary'
? 'bg-blue-600 text-white hover:bg-blue-700'
: 'bg-gray-200 text-gray-900 hover:bg-gray-300';
// Platform-specific modifikasyonlar
switch (platform) {
case Platform.REACT_NATIVE_IOS:
// iOS tarzı buton
return `${baseStyles} ${variantStyles} shadow-sm`;
case Platform.REACT_NATIVE_ANDROID:
// Material Design stili
return `${baseStyles} ${variantStyles} shadow-md elevation-2`;
case Platform.WEB_BROWSER:
// Web'e optimize edilmiş
return `${baseStyles} ${variantStyles} focus:outline-none focus:ring-2 focus:ring-blue-500`;
case Platform.ELECTRON_DESKTOP:
// Masaüstü uygulama stili
return `${baseStyles} ${variantStyles} text-sm`;
default:
return `${baseStyles} ${variantStyles}`;
}
};
const handleClick = () => {
if (disabled) return;
// Mobile'da haptik geri bildirim ekle
if (PlatformDetector.isMobile()) {
bridge.emit('haptic.impact', { style: 'medium' });
}
onPress();
};
return (
<button
className={getButtonStyles()}
onClick={handleClick}
disabled={disabled}
// Erişilebilirlik
role="button"
aria-label={title}
>
{title}
</button>
);
}
// Adaptif layout bileşenleri
export function AdaptiveLayout({ children }: { children: React.ReactNode }) {
const platform = PlatformDetector.platform;
const getLayoutClass = () => {
switch (platform) {
case Platform.REACT_NATIVE_IOS:
case Platform.REACT_NATIVE_ANDROID:
// Mobile: tam yükseklik, güvenli alanlar
return 'min-h-screen pt-safe pb-safe px-4';
case Platform.WEB_BROWSER:
// Web: responsive, maksimum genişlik
return 'min-h-screen max-w-4xl mx-auto px-4 py-8';
case Platform.ELECTRON_DESKTOP:
// Masaüstü: kompakt, pencere-farkında
return 'h-screen p-6 overflow-auto';
default:
return 'min-h-screen p-4';
}
};
return (
<div className={getLayoutClass()}>
{children}
</div>
);
}
Performans Optimizasyon Stratejileri
Aynı code base’i tüm platformlarda çalıştırmak, tek platform geliştirmede belirgin olmayan performans darboğazları ortaya çıkardı:
Platform’a göre Kod Bölme
// Platform-farkın dinamik import'lar
class PlatformModuleLoader {
private moduleCache = new Map<string, any>();
async loadPlatformModule<T>(moduleName: string): Promise<T> {
const cacheKey = `${moduleName}-${PlatformDetector.platform}`;
if (this.moduleCache.has(cacheKey)) {
return this.moduleCache.get(cacheKey);
}
let module: T;
try {
// Önce platform-specific modülü dene
const platformModule = await this.loadPlatformSpecific<T>(moduleName);
module = platformModule;
} catch (error) {
// Generic modüle geri dön
console.warn(`Platform modülü ${moduleName} bulunamadı, generic kullanılıyor`);
module = await this.loadGeneric<T>(moduleName);
}
this.moduleCache.set(cacheKey, module);
return module;
}
private async loadPlatformSpecific<T>(moduleName: string): Promise<T> {
const platform = PlatformDetector.platform;
switch (platform) {
case Platform.REACT_NATIVE_IOS:
return import(`./modules/${moduleName}/ios`);
case Platform.REACT_NATIVE_ANDROID:
return import(`./modules/${moduleName}/android`);
case Platform.WEB_BROWSER:
return import(`./modules/${moduleName}/web`);
case Platform.ELECTRON_DESKTOP:
return import(`./modules/${moduleName}/electron`);
default:
throw new Error(`${platform} için platform-specific modül yok`);
}
}
private async loadGeneric<T>(moduleName: string): Promise<T> {
return import(`./modules/${moduleName}/index`);
}
}
// Kullanım
const moduleLoader = new PlatformModuleLoader();
async function initializeApp() {
// Platform-optimize edilmiş modülleri yükle
const analytics = await moduleLoader.loadPlatformModule('analytics');
const storage = await moduleLoader.loadPlatformModule('storage');
const networking = await moduleLoader.loadPlatformModule('networking');
// Platform-specific implementasyonlarla başlat
await analytics.initialize();
await storage.initialize();
await networking.initialize();
}
Platformlar Arası Bellek Yönetimi
Farklı platformlar çok farklı bellek kısıtlarına sahipti:
// Adaptif bellek yönetimi
class MemoryManager {
private memoryLimit: number;
private memoryWarningThreshold: number;
private cache = new Map<string, { data: any; timestamp: number; size: number }>();
private totalCacheSize = 0;
constructor() {
// Platform'a göre limitleri ayarla
const platform = PlatformDetector.platform;
switch (platform) {
case Platform.REACT_NATIVE_IOS:
case Platform.REACT_NATIVE_ANDROID:
// Mobile: muhafazakar limitler
this.memoryLimit = 50 * 1024 * 1024; // 50MB
this.memoryWarningThreshold = 40 * 1024 * 1024; // 40MB
break;
case Platform.WEB_BROWSER:
// Web: orta limitler
this.memoryLimit = 100 * 1024 * 1024; // 100MB
this.memoryWarningThreshold = 80 * 1024 * 1024; // 80MB
break;
case Platform.ELECTRON_DESKTOP:
// Masaüstü: cömert limitler
this.memoryLimit = 200 * 1024 * 1024; // 200MB
this.memoryWarningThreshold = 150 * 1024 * 1024; // 150MB
break;
default:
this.memoryLimit = 50 * 1024 * 1024;
this.memoryWarningThreshold = 40 * 1024 * 1024;
}
// Bellek baskısı izlemeyi kur
this.setupMemoryMonitoring();
}
set(key: string, data: any): void {
const serialized = JSON.stringify(data);
const size = new Blob([serialized]).size;
// Önce bellek boşaltmamız gerekip gerekmediğini kontrol et
if (this.totalCacheSize + size > this.memoryLimit) {
this.freeMemory(size);
}
this.cache.set(key, {
data,
timestamp: Date.now(),
size
});
this.totalCacheSize += size;
}
get(key: string): any {
const item = this.cache.get(key);
if (!item) return null;
// LRU için timestamp'ı güncelle
item.timestamp = Date.now();
return item.data;
}
private freeMemory(requiredSize: number): void {
// Timestamp'a göre sırala (LRU)
const entries = Array.from(this.cache.entries())
.sort(([, a], [, b]) => a.timestamp - b.timestamp);
let freed = 0;
for (const [key, item] of entries) {
this.cache.delete(key);
this.totalCacheSize -= item.size;
freed += item.size;
if (freed >= requiredSize || this.totalCacheSize < this.memoryWarningThreshold) {
break;
}
}
console.log(`[MemoryManager] ${freed} byte boşaltıldı`);
}
private setupMemoryMonitoring(): void {
// Her 30 saniyede bellek kullanımını izle
setInterval(() => {
if (this.totalCacheSize > this.memoryWarningThreshold) {
console.warn(`[MemoryManager] Bellek kullanımı yüksek: ${this.totalCacheSize} byte`);
// Cache'in 25%'ini boşalt
this.freeMemory(this.totalCacheSize * 0.25);
}
}, 30000);
// Platform-specific bellek uyarılarına tepki ver
if (PlatformDetector.isMobile()) {
// Mobil uygulamalar bellek uyarıları alabilir
bridge.on('memory.warning', () => {
console.warn('[MemoryManager] Bellek uyarısı alındı, cache temizleniyor');
this.clearAll();
});
}
}
clearAll(): void {
this.cache.clear();
this.totalCacheSize = 0;
}
getStats(): {
totalSize: number;
itemCount: number;
limit: number;
utilizationPercent: number;
} {
return {
totalSize: this.totalCacheSize,
itemCount: this.cache.size,
limit: this.memoryLimit,
utilizationPercent: (this.totalCacheSize / this.memoryLimit) * 100
};
}
}
Ağ Optimizasyonu
Ağ davranışı platformlar arasında önemli ölçüde değişiyordu:
// Adaptif ağ stratejisi
class NetworkManager {
private retryDelays: number[];
private timeoutMs: number;
private maxConcurrentRequests: number;
private activeRequests = new Set<string>();
private requestQueue: Array<() => Promise<any>> = [];
constructor() {
const platform = PlatformDetector.platform;
switch (platform) {
case Platform.REACT_NATIVE_IOS:
case Platform.REACT_NATIVE_ANDROID:
// Mobile: muhafazakar ayarlar
this.retryDelays = [1000, 2000, 5000];
this.timeoutMs = 15000;
this.maxConcurrentRequests = 3;
break;
case Platform.WEB_BROWSER:
// Web: orta ayarlar
this.retryDelays = [500, 1000, 2000];
this.timeoutMs = 10000;
this.maxConcurrentRequests = 6;
break;
case Platform.ELECTRON_DESKTOP:
// Masaüstü: agresif ayarlar
this.retryDelays = [200, 500, 1000];
this.timeoutMs = 5000;
this.maxConcurrentRequests = 10;
break;
default:
this.retryDelays = [1000, 2000, 5000];
this.timeoutMs = 10000;
this.maxConcurrentRequests = 3;
}
}
async request(url: string, options: RequestInit = {}): Promise<Response> {
// Mobile için request kuyruğu uygula
if (PlatformDetector.isMobile() &&
this.activeRequests.size >= this.maxConcurrentRequests) {
return this.queueRequest(() => this.executeRequest(url, options));
}
return this.executeRequest(url, options);
}
private async executeRequest(url: string, options: RequestInit): Promise<Response> {
const requestId = `${url}-${Date.now()}`;
this.activeRequests.add(requestId);
try {
return await this.retryRequest(url, options);
} finally {
this.activeRequests.delete(requestId);
this.processQueue();
}
}
private async retryRequest(url: string, options: RequestInit): Promise<Response> {
let lastError: Error;
for (let attempt = 0; attempt <= this.retryDelays.length; attempt++) {
try {
// Request'e timeout ekle
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);
const response = await fetch(url, {
...options,
signal: controller.signal
});
clearTimeout(timeoutId);
// 4xx hatalarında tekrar deneme (429 hariç)
if (response.status >= 400 && response.status < 500 && response.status !== 429) {
return response;
}
if (response.ok || response.status === 429) {
return response;
}
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
} catch (error) {
lastError = error as Error;
// Abort'ta tekrar deneme
if (error.name === 'AbortError') {
throw error;
}
// Tekrar denemeden önce bekle
if (attempt < this.retryDelays.length) {
console.warn(`Request denemesi ${attempt + 1} başarısız, ${this.retryDelays[attempt]}ms içinde tekrar deneniyor`);
await this.delay(this.retryDelays[attempt]);
}
}
}
throw lastError!;
}
private async queueRequest<T>(requestFn: () => Promise<T>): Promise<T> {
return new Promise((resolve, reject) => {
this.requestQueue.push(async () => {
try {
const result = await requestFn();
resolve(result);
} catch (error) {
reject(error);
}
});
});
}
private processQueue(): void {
while (this.requestQueue.length > 0 &&
this.activeRequests.size < this.maxConcurrentRequests) {
const nextRequest = this.requestQueue.shift();
nextRequest?.();
}
}
private delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
Offline Destek ve Senkronizasyon
En büyük zorluklardan biri, tüm platformlarda çalışan offline desteği uygulamaktı:
// Evrensel offline destek
class OfflineManager {
private isOnline = navigator.onLine;
private syncQueue: Array<{
id: string;
action: string;
payload: any;
timestamp: number;
attempts: number;
}> = [];
private storage: Storage;
private maxRetries = 5;
constructor() {
// Platform-uygun storage kullan
this.storage = PlatformDetector.isMobile()
? this.createMobileStorage()
: localStorage;
this.setupNetworkMonitoring();
this.loadQueueFromStorage();
}
private createMobileStorage(): Storage {
// React Native WebView'da, kalıcı storage için bridge kullanmamız gerekiyor
return {
getItem: async (key: string): Promise<string | null> => {
try {
return await bridge.request('storage.getItem', { key });
} catch {
return null;
}
},
setItem: async (key: string, value: string): Promise<void> => {
await bridge.request('storage.setItem', { key, value });
},
removeItem: async (key: string): Promise<void> => {
await bridge.request('storage.removeItem', { key });
},
clear: async (): Promise<void> => {
await bridge.request('storage.clear');
}
} as any;
}
private setupNetworkMonitoring(): void {
// Web/Masaüstü ağ izleme
if (typeof window !== 'undefined') {
window.addEventListener('online', () => {
this.isOnline = true;
this.processSyncQueue();
});
window.addEventListener('offline', () => {
this.isOnline = false;
});
}
// Bridge aracılığıyla mobil ağ izleme
if (PlatformDetector.isMobile()) {
bridge.on('network.statusChanged', ({ isOnline }: { isOnline: boolean }) => {
this.isOnline = isOnline;
if (isOnline) {
this.processSyncQueue();
}
});
}
}
async queueForSync(action: string, payload: any): Promise<void> {
const queueItem = {
id: this.generateId(),
action,
payload,
timestamp: Date.now(),
attempts: 0
};
this.syncQueue.push(queueItem);
await this.saveQueueToStorage();
// Online'sa hemen senkronize etmeye çalış
if (this.isOnline) {
this.processSyncQueue();
}
}
private async processSyncQueue(): Promise<void> {
if (!this.isOnline || this.syncQueue.length === 0) {
return;
}
const itemsToProcess = [...this.syncQueue];
for (const item of itemsToProcess) {
try {
await this.syncItem(item);
// Başarıda kuyruktan çıkar
this.syncQueue = this.syncQueue.filter(q => q.id !== item.id);
} catch (error) {
console.error(`${item.action} için sync başarısız:`, error);
item.attempts++;
// Maksimum deneme aşılırsa çıkar
if (item.attempts >= this.maxRetries) {
console.error(`${item.action} için maksimum deneme aşıldı, kuyruktan çıkarılıyor`);
this.syncQueue = this.syncQueue.filter(q => q.id !== item.id);
}
}
}
await this.saveQueueToStorage();
}
private async syncItem(item: any): Promise<void> {
// Kuyruktaki eylemi sunucuya gönder
const response = await fetch('/api/sync', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${await this.getAuthToken()}`
},
body: JSON.stringify({
action: item.action,
payload: item.payload,
timestamp: item.timestamp
})
});
if (!response.ok) {
throw new Error(`Sync başarısız: ${response.status}`);
}
}
private async getAuthToken(): Promise<string> {
if (PlatformDetector.hasNativeBridge()) {
const { token } = await bridge.request('auth.getToken');
return token;
} else {
return localStorage.getItem('auth_token') || '';
}
}
private async saveQueueToStorage(): Promise<void> {
const serialized = JSON.stringify(this.syncQueue);
await this.storage.setItem('sync_queue', serialized);
}
private async loadQueueFromStorage(): Promise<void> {
try {
const serialized = await this.storage.getItem('sync_queue');
if (serialized) {
this.syncQueue = JSON.parse(serialized);
}
} catch (error) {
console.error('Sync kuyruğu yüklenemedi:', error);
this.syncQueue = [];
}
}
private generateId(): string {
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}
// Public API
isOnlineState(): boolean {
return this.isOnline;
}
getPendingActions(): number {
return this.syncQueue.length;
}
async forcSync(): Promise<void> {
await this.processSyncQueue();
}
}
Production İzleme ve Analitik
Platformlar arası performansı izlemek sofistike bir yaklaşım gerektiriyordu:
// Evrensel analitik sistemi
class UniversalAnalytics {
private sessionId: string;
private userId?: string;
private deviceInfo: DeviceInfo;
private performanceBuffer: PerformanceEntry[] = [];
constructor() {
this.sessionId = this.generateSessionId();
this.deviceInfo = this.collectDeviceInfo();
this.setupPerformanceMonitoring();
}
private collectDeviceInfo(): DeviceInfo {
const platform = PlatformDetector.platform;
return {
platform,
userAgent: navigator.userAgent,
screenWidth: window.screen?.width || 0,
screenHeight: window.screen?.height || 0,
devicePixelRatio: window.devicePixelRatio || 1,
// Platform-specific bilgi
...this.getPlatformSpecificInfo()
};
}
private getPlatformSpecificInfo(): Partial<DeviceInfo> {
const platform = PlatformDetector.platform;
if (PlatformDetector.isMobile()) {
// Native bridge'den cihaz bilgisi al
return {
deviceModel: 'mobile-device', // Bridge'den gelecek
osVersion: 'unknown', // Bridge'den gelecek
appVersion: '1.0.0' // Bridge'den gelecek
};
}
return {
browserName: this.getBrowserName(),
browserVersion: this.getBrowserVersion()
};
}
private setupPerformanceMonitoring(): void {
// Web Vitals'ı izle
this.observeWebVitals();
// Özel performans metriklerini izle
this.observeCustomMetrics();
// Platform-specific izleme
if (PlatformDetector.isMobile()) {
this.setupMobileMonitoring();
}
}
private observeWebVitals(): void {
// Core Web Vitals
if ('PerformanceObserver' in window) {
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach((entry) => {
this.trackPerformance(entry.name, entry.duration, {
type: entry.entryType,
startTime: entry.startTime
});
});
});
try {
observer.observe({ entryTypes: ['measure', 'navigation', 'paint'] });
} catch (error) {
console.warn('Performance observer desteklenmiyor:', error);
}
}
}
private observeCustomMetrics(): void {
// Micro frontend yükleme zamanlarını takip et
this.trackMicroFrontendLoads();
// Bridge mesaj performansını takip et
this.trackBridgePerformance();
// Bellek kullanımını takip et
this.trackMemoryUsage();
}
private trackMicroFrontendLoads(): void {
// Micro frontend'ler ne zaman görünür hale geldiğini izle
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const loadTime = performance.now();
this.track('micro_frontend_visible', {
microFrontendId: entry.target.id,
loadTime,
visibilityRatio: entry.intersectionRatio
});
}
});
});
// Tüm micro frontend container'larını gözlemle
document.querySelectorAll('[data-micro-frontend]').forEach((element) => {
observer.observe(element);
});
}
private trackBridgePerformance(): void {
// Performansı takip etmek için bridge request'lerini sarla
const originalRequest = bridge.request;
bridge.request = async (action: string, payload: any) => {
const startTime = performance.now();
try {
const result = await originalRequest.call(bridge, action, payload);
const duration = performance.now() - startTime;
this.trackPerformance('bridge_request', duration, {
action,
success: true,
payloadSize: JSON.stringify(payload).length
});
return result;
} catch (error) {
const duration = performance.now() - startTime;
this.trackPerformance('bridge_request', duration, {
action,
success: false,
error: error.message
});
throw error;
}
};
}
private trackMemoryUsage(): void {
if ('memory' in performance) {
setInterval(() => {
const memInfo = (performance as any).memory;
this.track('memory_usage', {
usedJSHeapSize: memInfo.usedJSHeapSize,
totalJSHeapSize: memInfo.totalJSHeapSize,
jsHeapSizeLimit: memInfo.jsHeapSizeLimit,
utilizationPercent: (memInfo.usedJSHeapSize / memInfo.jsHeapSizeLimit) * 100
});
}, 30000); // Her 30 saniye
}
}
private setupMobileMonitoring(): void {
// Native bridge hatalarını takip et
bridge.on('error', (error: any) => {
this.track('bridge_error', {
error: error.message,
code: error.code,
platform: PlatformDetector.platform
});
});
// Uygulama durum değişikliklerini takip et
bridge.on('app_state_change', (state: string) => {
this.track('app_state_change', {
state,
sessionDuration: Date.now() - this.getSessionStartTime()
});
});
}
// Public API
track(event: string, properties: Record<string, any> = {}): void {
const eventData = {
event,
properties: {
...properties,
sessionId: this.sessionId,
userId: this.userId,
platform: this.deviceInfo.platform,
timestamp: Date.now()
},
deviceInfo: this.deviceInfo
};
// Analitik servisine gönder
this.sendAnalytics(eventData);
}
trackPerformance(metric: string, value: number, properties: Record<string, any> = {}): void {
this.track('performance_metric', {
metric,
value,
...properties
});
}
setUserId(userId: string): void {
this.userId = userId;
}
private async sendAnalytics(data: any): Promise<void> {
try {
// Güvenilir teslimat için offline manager kullan
await offlineManager.queueForSync('analytics', data);
} catch (error) {
console.error('Analitik gönderme başarısız:', error);
}
}
private generateSessionId(): string {
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}
private getSessionStartTime(): number {
return parseInt(this.sessionId.split('-')[0]);
}
private getBrowserName(): string {
const userAgent = navigator.userAgent;
if (userAgent.includes('Chrome')) return 'Chrome';
if (userAgent.includes('Firefox')) return 'Firefox';
if (userAgent.includes('Safari')) return 'Safari';
if (userAgent.includes('Edge')) return 'Edge';
return 'Unknown';
}
private getBrowserVersion(): string {
// Basitleştirilmiş versiyon tespiti
const userAgent = navigator.userAgent;
const match = userAgent.match(/(?:Chrome|Firefox|Safari|Edge)\/([0-9.]+)/);
return match ? match[1] : 'Unknown';
}
}
interface DeviceInfo {
platform: Platform;
userAgent: string;
screenWidth: number;
screenHeight: number;
devicePixelRatio: number;
deviceModel?: string;
osVersion?: string;
appVersion?: string;
browserName?: string;
browserVersion?: string;
}
interface PerformanceEntry {
name: string;
duration: number;
startTime: number;
type: string;
}
Production Sonuçları ve Metrikler
Bu çok kanallı mimariyi 6 ay çalıştırdıktan sonra, işte production metriklerimiz:
Platform’a göre Performans Metrikleri
Mobile (React Native WebView)
- İlk contentful paint: Ortalama 1.2s
- Time to interactive: Ortalama 2.1s
- Bellek kullanımı: Micro frontend başına ortalama 85MB
- Batarya etkisi: Native ekranlara göre 18% artış
Web Tarayıcısı
- İlk contentful paint: Ortalama 800ms
- Time to interactive: Ortalama 1.4s
- Bellek kullanımı: Micro frontend başına ortalama 120MB
- Lighthouse puanı: Ortalama 92/100
Masaüstü (Electron)
- İlk contentful paint: Ortalama 600ms
- Time to interactive: Ortalama 1.1s
- Bellek kullanımı: Micro frontend başına ortalama 150MB
- CPU kullanımı: Ortalama 15%
Güvenilirlik Metrikleri
- Bridge mesaj başarı oranı: 99.97%
- Offline sync başarı oranı: 99.2%
- Platform tespit doğruluğu: 100%
- Cross-platform özellik paritesi: 94%
Geliştirme Metrikleri
- Platformlar arası kod yeniden kullanımı: 89%
- Platform-specific kod: 11%
- Takım deployment bağımsızlığı: 100%
- Düzeltme deployment süresi: 2 dakika (web) - 24 saat (mobil app store)
Anahtar Öğretiler
-
Alternatif Yaklaşımların Trade-off’ları Vardır: Her bundler ve iletişim yönteminin güçlü yanları vardır. Özel ihtiyaçlarınız ve takım kısıtlarınıza göre seçin.
-
Hibrit Yaklaşımlar En İyi Çalışır: Takımların tercih ettikleri araçları kullanmalarına izin verirken tutarlı bir arayüz sürdürün.
-
Rspack Umut Verici: Yeni projeler için hızı ve webpack uyumluluğu nedeniyle Rspack’i düşünün.
-
Re.Pack Güçlü ama Karmaşık: Super app’ler için harika ama önemli koordinasyon gerektirir.
-
Performans Kritik: Kullanıcılar yavaş WebView’ları hemen fark eder. Agresif optimize edin.
-
Platform Tespiti Temel: Güvenilir platform tespiti adaptif davranışı mümkün kılar.
-
Offline Destek Her Şeyi Değiştirir: Offline-first inşa etmek uygulamanızı daha sağlam yapar.
-
İzleme Pazarlık Konusu Değil: Kapsamlı izleme eklenmiş değil, inşa edilmiş olmalı.
-
Bellek Yönetimi Önemli: Mobile’da agresif bellek yönetimi kritik.
-
Kullanıcı Deneyimi Teknolojiyi Geçer: Kullanıcılar tutarlılık umursar, implementasyon detaylarını değil.
Öğrenilen Dersler
Olağanüstü İyi Çalışanlar
-
Evrensel Bridge Pattern: Tüm platformlarda çalışan tek bir arayüze sahip olmak geliştirici verimliliği için oyun değiştiriciydi.
-
Adaptif Bileşenler: Platform-farkın UI bileşenleri bakım yükünü önemli ölçüde azalttı.
-
Progressive Enhancement: Web yetenekleriyle başlayıp native özellikler eklemek tersinin yapılmasından daha iyi çalıştı.
-
Offline-First Mimari: Baştan offline desteği inşa etmek mimarimizi genel olarak daha sağlam yaptı.
Farklı Yapacaklarımız
-
Performans Bütçesi: Birinci günden performans bütçeleri ayarlayın ve uygulayın. Daha sonra optimize etmek daha zor.
-
Test Stratejisi: Platformlar arası otomatik testlere daha erken yatırım yapmalıydık.
-
İzleme: Kapsamlı izleme eklenmiş değil, inşa edilmiş olmalıydı.
-
Bellek Yönetimi: Baştan mobile’da daha agresif bellek yönetimi.
Beklenmedik Keşifler
-
Masaüstü Performansı: Electron bizim kullanım durumumuz için şaşırtıcı derecede en iyi performans gösteren platformdı.
-
Batarya Ömrü: Doğru optimizasyon aslında mobile’da orijinal native ekranlarımıza kıyasla batarya ömrünü iyileştirdi.
-
Kullanıcı Memnuniyeti: Kullanıcılar hibrit mimarimizi fark etmediler - tutarlılık “pure native” olmaktan daha önemliydi.
-
Takım Dinamikleri: Deployment bağımsızlığına sahip olmak takımların işbirliği yapma şeklini iyiye doğru değiştirdi.
Ne Zaman Çok Kanallı Micro Frontend’ler Kullanılır
Bu mimari şu durumlarda mantıklıdır:
- Sınırlı kaynaklarla birden fazla platformu desteklemeniz gerekiyorsa
- Takımların deployment bağımsızlığına ihtiyacı varsa
- Yeniden kullanılacak önemli mevcut web uygulamalarınız varsa
- Platformlar arası tutarlılık maksimum performanstan daha önemliyse
- Platform-specific optimizasyona yatırım yapabilirsiniz
Şu durumlarda ideal değildir:
- Performans kesinlikle kritikse (oyunlar, video düzenleme, vb.)
- Deneyimin merkezinde platform-specific özellikleriniz varsa
- Takım boyutu koordinasyonun darboğaz olmayacağı kadar küçükse
- Basit bir uygulama inşa ediyorsanız
Sonuç
Gerçekten çok kanallı bir micro frontend mimarisi inşa etmek önemli zorluklar sundu, ancak önemli değer sağladı. Aynı takım boyutuyla bir platformdan dört platforma geçtik, aslında deployment hızımızı iyileştirdik.
Anahtar içgörüler şunlardı:
- Platform farklılıklarını gizlemeye çalışmak yerine kucaklayın
- Altta yatan platformları gizleyen değil, geliştiren soyutlamalar inşa edin
- Baştan ağır izleme ve hata ayıklama araçlarına yatırım yapın
- Performans optimizasyonu tek seferlik görev değil, devam eden bir süreçtir
Bugün mimarimiz mobil uygulamalar, web tarayıcıları ve masaüstü uygulamaları arasında milyonlarca kullanıcıya hizmet veriyor. Takımlar bağımsız olarak deploy edebiliyor, kullanıcılar tutarlı deneyimler elde ediyor ve micro frontend’lerin tüm ana platformlarda ölçekte çalışabileceğini kanıtladık.
Frontend geliştirmenin geleceği çok kanallıdır. Soru birden fazla platformu desteklemeniz gerekip gerekmediği değil, o gereksinim geldiğinde hazır olup olmayacağınızdır.
React Native ile Mobil Micro Frontend'ler
React Native, Expo ve WebView'lar kullanarak mobil micro frontend'ler oluşturma üzerine 3 bölümlük kapsamlı seri. Mimari, iletişim pattern'leri ve production optimizasyonu dahil.
Serideki tüm yazılar
İlgili yazılar
React Native ve Expo WebView'lar ile mobil micro frontend mimarisi oluşturma. Gerçek dünya örnekleri ve en iyi uygulamalar.
React Native ve WebView'lar arasında güvenli ve verimli iletişim kurma. Postmessage, bridge pattern'leri ve production deneyimleri.
React Native Expo uygulamasına Sentry hata izleme entegrasyonu için adım adım rehber. SDK başlatma, Expo Router enstrümantasyonu, session replay, EAS Build ve EAS Update için source map yükleme ve sık karşılaşılan sorunları kapsar.
esbuild, SWC ve Vite gibi native tool'ların webpack'in performans problemlerini nasıl çözdüğü. 10 saniyelik build'lerden 100ms'ye: developer'ların build zamanları hakkında düşünmeyi bıraktıran geçiş.
Single Table Design uygulamalarında DynamoDB throttling'i önleme ve yönetme stratejileri. Partition key tasarımı, write sharding, kapasite modları, DAX caching, retry pattern'leri ve yüksek throughput sistemler için CloudWatch monitoring konularını kapsar.