React Native projelerinizde pnpm kullanırken Metro’nun bitmek bilmeyen @babel/runtime hatalarıyla boğuşmaktan sıkıldınız mı? Bu detaylı rehberde, bu yaygın sorunun nedenlerini anlayacak ve adım adım çözümlerle geliştirme ortamınızı tekrar huzurlu bir hale getireceksiniz. Performansı artıran ve geliştirici deneyimini iyileştiren pratik yaklaşımlarla bu kabusa son veriyoruz.
Modern JavaScript ekosisteminde bağımlılık yönetimi, geliştiricilerin en çok zaman harcadığı konulardan biri haline geldi. Özellikle büyük projelerde, node_modules klasörlerinin şişkinliği ve kurulum sürelerinin uzaması kaçınılmaz bir sorun teşkil ediyor. İşte tam bu noktada pnpm, yenilikçi yaklaşımıyla öne çıkıyor. Diskte yer tasarrufu sağlayan ve bağımlılık kurulumunu hızlandıran sembolik bağlantılar (symlinks) sayesinde pnpm, birçok geliştirici için vazgeçilmez bir araç haline geldi. Monorepo yapılarında sunduğu tutarlı bağımlılık yönetimi ve deterministik kurulum garantisi de cabası. Ancak her yenilik gibi pnpm’in de bazı mevcut araçlarla entegrasyonunda kendine özgü zorlukları bulunuyor.
React Native dünyasına adım attığımızda ise karşımıza Metro bundler çıkıyor. Metro, özel bir modül çözünürlüğü (module resolution) mantığına sahip ve genellikle node_modules dizinlerini belirli bir hiyerarşi içinde taramayı bekler. Klasik npm veya yarn (v1) gibi paket yöneticileri, bağımlılıkları “hoisting” adı verilen bir mekanizmayla kök node_modules dizinine taşır. Bu sayede tüm bağımlılıklar tek bir yerde bulunuyormuş gibi görünür ve Metro’nun beklentilerini karşılar. Ancak pnpm, disk alanından tasarruf etmek ve “phantom dependencies” (hayalet bağımlılıklar) sorununu ortadan kaldırmak için farklı bir yaklaşım benimser: Her bağımlılık, kendi sanal node_modules klasörüne sembolik olarak bağlanır. Bu sanal klasörler, genellikle projenin kök dizininde bulunan .pnpm klasörü içinde gerçek dosyaları işaret eder. İşte bu temel farklılık, React Native ve Metro ile pnpm arasında sürtüşmelere yol açan ana nedenlerden biridir. Metro’nun alışık olduğu düzene uymayan bu yeni yapı, modül bulunamadı hataları, özellikle de @babel/runtime ile ilgili şikayetlerin ana kaynağı olur.
Özellikle bir monorepo içinde çalışıyorsanız, bu durum daha da karmaşıklaşabilir. Birden fazla React Native projesi veya bir React Native projesi ile paylaşılan bir kütüphane paketi arasında bağımlılıklar olduğunda, pnpm’in katı bağımlılık yapısı Metro’nun kafasını karıştırır. Örneğin, bir alt paketin @babel/runtime‘ı kendi özel sürümünü gerektirebilirken, ana uygulama başka bir sürümü bekliyor olabilir. pnpm bu durumu kendi içinde izole etse de, Metro bundler bu izolasyonu aşmakta zorlanır. Sonuç olarak, geliştirici konsolunda “Unable to resolve module @babel/runtime/...” veya “Invalid call to require” gibi can sıkıcı hata mesajları görmeye başlarsınız. Bu hatalar, genellikle Babel’ın çalışma zamanı yardımcılarını bulamaması veya yanlış bir sürümünü çekmeye çalışmasından kaynaklanır. Geliştirici deneyimi açısından bu durum, sürekli hatalarla uğraşmak, sürekli cache temizlemek ve projenin sağlıklı bir şekilde çalışıp çalışmadığını sorgulamak anlamına gelir. Bu nedenle, pnpm’in sunduğu avantajlardan faydalanırken Metro ile uyumlu bir ortam kurmak, her React Native geliştiricisinin öğrenmesi gereken kritik bir beceridir. Aksi takdirde, proje başlangıç süreleri kısalırken, hata ayıklama süreleri gereksiz yere uzayabilir ve bu da genel verimliliği olumsuz etkileyebilir. Bu rehberin amacı, tam da bu çatışmayı çözmek ve her iki dünyanın en iyi yönlerini bir araya getirmektir.
Derinlemesine Bakış: @babel/runtime Nedir ve Metro Neden Çığlık Atar?
React Native projelerinde @babel/runtime ile ilgili Metro hatalarıyla karşılaşmak, pnpm kullanan birçok geliştiricinin ortak kabusudur. Peki, bu @babel/runtime tam olarak nedir ve neden bu kadar sorun yaratır? Kısaca açıklamak gerekirse, Babel, modern JavaScript kodunu eski tarayıcılar ve Node.js sürümleri tarafından anlaşılabilecek formata dönüştüren (transpile eden) temel bir araçtır. Bu dönüştürme işlemi sırasında, Babel bazı yardımcı fonksiyonlara ihtiyaç duyar. Örneğin, async/await sözdizimini kullanıyorsanız, Babel bunu Promise tabanlı kodlara dönüştürür ve bu dönüştürülen kodun çalışması için _regeneratorRuntime veya _asyncToGenerator gibi yardımcı fonksiyonlara ihtiyaç duyar. Bu yardımcı fonksiyonlar, her dosyanın başına tekrar tekrar eklenmek yerine, @babel/runtime paketi tarafından merkezi bir şekilde sağlanır. Bu yaklaşım, kod boyutunu küçültür ve kod tekrarını önler.
@babel/runtime paketi, özellikle Babel’ın @babel/plugin-transform-runtime eklentisi ile birlikte kullanılır. Bu eklenti, Babel’ın dönüştürme işlemi sırasında ihtiyaç duyduğu yardımcı fonksiyonlara yapılan çağrıları, global kapsamdaki yardımcı fonksiyonlar yerine @babel/runtime paketinden import etmeye yönlendirir. Böylece, her dosyanın kendi içinde bu yardımcıları barındırması yerine, tek bir merkezi yerden çekilirler. Bu da hem kod boyutunu optimize eder hem de global çakışmaları önler. Ancak, bu sistemin sağlıklı çalışabilmesi için Metro bundler’ın @babel/runtime paketini ve onun içindeki modülleri doğru bir şekilde bulabilmesi gerekir. İşte burada pnpm’in bağımlılık yönetim modeli devreye giriyor ve Metro ile bir çatışma noktası oluşturuyor.
Temel Sorun: Bağımlılık Hoisting ve Modül Çözünürlüğü
Geleneksel olarak, npm veya yarn classic (v1) gibi paket yöneticileri, bağımlılıkları “hoisting” adı verilen bir mekanizmayla ele alır. Bu mekanizma, her paketin kendi node_modules klasöründe bulunan bağımlılıklarını, mümkün olduğunca projenin en üst seviyesindeki (root) node_modules klasörüne taşır. Bu sayede, aynı bağımlılık farklı paketler tarafından kullanılsa bile diske sadece bir kere yazılır ve tüm paketler tarafından erişilebilir hale gelir. Metro bundler da bu düzene alışkındır ve modül çözünürlüğü sırasında genellikle önce mevcut dosyanın bulunduğu dizinin node_modules‘ına, sonra onun bir üst dizininin node_modules‘ına bakarak hiyerarşik bir arama yapar.
pnpm ise farklı bir yaklaşım benimser: “flat” bir node_modules yapısı yerine, her bağımlılık için katı bir hiyerarşi oluşturur. Her paketin kendi node_modules klasörü içinde sadece o paketin doğrudan bağımlılıkları (veya sembolik bağlantıları) bulunur. Tüm gerçek paket dosyaları ise projenin kök dizinindeki .pnpm klasörü altında depolanır ve bu klasördeki gerçek dosyalara sembolik bağlantılar oluşturulur. Örneğin, lodash paketi kullanılıyorsa, projenin node_modules‘unda lodash‘in doğrudan kendisi değil, .pnpm/lodash@version/node_modules/lodash klasörüne işaret eden bir sembolik bağlantı bulunur. Bu yapı, “phantom dependencies” sorununu ortadan kaldırır (yani bir paketin doğrudan bağımlısı olmasa da erişebildiği paketleri engeller) ve bağımlılıkların daha izole olmasını sağlar. Ancak Metro, bu karmaşık sembolik bağlantı yapısını ve derinlemesine yuvalanmış .pnpm klasörlerini otomatik olarak çözmekte zorlanır. Bu durum, özellikle @babel/runtime gibi Babel’ın dahili olarak ihtiyaç duyduğu ve genellikle node_modules‘un belirli bir yerinde bulunması beklenen paketler için sorun yaratır. Metro, beklediği yolda @babel/runtime‘ı bulamadığında veya yanlış bir sürümünü bulduğunda “Unable to resolve module” gibi hatalar fırlatmaya başlar. Bu, geliştiriciler için hem sinir bozucu hem de zaman alıcı bir sorun haline gelir.
Uzman İpucu: Sanal node_modules‘ı Anlamak
pnpm’in .pnpm klasöründeki her bağımlılık, kendi izole edilmiş node_modules yapısına sahiptir. Bu, lodash‘in @babel/runtime‘ın 7.x sürümünü kullanırken, sizin uygulamanızın 6.x sürümünü kullanmasının kolayca izole edilebildiği anlamına gelir. Ancak Metro, bu izolasyonu varsayılan olarak anlayamaz. Bu nedenle, Metro’ya bu yeni yapıyı nasıl tarayacağını açıkça belirtmemiz gerekir.
Adım Adım Çözüm: Metro’yu pnpm ile Evcilleştirmek
Metro’nun pnpm’in bağımlılık yönetim modeliyle uyumlu hale getirilmesi, doğru yapılandırma adımlarıyla mümkündür. İşte bu süreci adım adım ele alacağız, böylece Metro’nun @babel/runtime ile ilgili şikayetlerini kökten çözebilirsiniz.
Başlangıç Ayarları ve Sık Görülen Hatalar
Öncelikle, temiz bir başlangıç yapmak her zaman iyidir. Projenizi ilk kez pnpm ile kuruyorsanız veya mevcut bir projeyi geçiş yapıyorsanız, aşağıdaki adımları izlemek önemlidir:
- Eski Bağımlılıkları Temizleyin: Eğer daha önce npm veya yarn kullandıysanız,
node_modulesklasörünü vepackage-lock.json(npm) veyayarn.lock(yarn) dosyalarını silerek başlayın. - pnpm Kurulumu: pnpm’in sisteminizde kurulu olduğundan emin olun. Kurulu değilse,
npm install -g pnpmveyabrew install pnpm(macOS) komutunu kullanabilirsiniz. - Bağımlılıkları Yükleyin: Projenizin kök dizininde
pnpm installkomutunu çalıştırın.
Bu adımlardan sonra muhtemelen React Native uygulamanızı çalıştırmayı denediğinizde Metro’nun meşhur hata mesajlarıyla karşılaşacaksınız. Bu hatalar genellikle şuna benzer:
Error: Unable to resolve module @babel/runtime/helpers/interopRequireDefault from node_modules/react-native/index.js
Bu, Metro'nun node_modules klasörünü tararken @babel/runtime'ı beklediği yerde bulamadığını gösterir. Şimdi bu sorunu çözmek için gerekli yapılandırma değişikliklerine geçelim.
pnpm-workspace.yaml ve package.json Stratejileri
Eğer bir monorepo kullanıyorsanız veya gelecekte kullanmayı planlıyorsanız, pnpm-workspace.yaml dosyası kritik öneme sahiptir. Bu dosya, pnpm'e çalışma alanınızdaki hangi klasörlerin bir paket olarak kabul edileceğini bildirir.
# pnpm-workspace.yaml
packages:
- 'apps/*' # React Native uygulamalarınızın bulunduğu klasör
- 'packages/*' # Paylaşılan kütüphaneleriniz
- 'libs/*' # Diğer yardımcı kütüphaneler
Bu dosyanın varlığı, pnpm'in sembolik bağlama mekanizmasını devreye sokmasını sağlar. Monorepo kullanmıyorsanız bu dosyaya ihtiyacınız olmayabilir, ancak Metro yapılandırması yine de gereklidir.
package.json dosyanızda ise, özellikle @babel/runtime paketinin doğru bir şekilde listelendiğinden emin olmalısınız. Genellikle bu paket, devDependencies altında değil, doğrudan dependencies altında olmalıdır, çünkü çalışma zamanında gereklidir.
{
"name": "my-react-native-app",
"version": "1.0.0",
"dependencies": {
"react": "18.2.0",
"react-native": "0.72.6",
"@babel/runtime": "^7.20.0", // Doğru sürümünü buraya ekleyin
// ...diğer bağımlılıklar
},
"devDependencies": {
"@babel/core": "^7.20.0",
"@babel/preset-env": "^7.20.0",
// ...diğer dev bağımlılıkları
}
}
Eğer @babel/runtime zaten yoksa, projenizin ana dizininde aşağıdaki komutu çalıştırarak ekleyin:
pnpm add @babel/runtime
Metro Yapılandırma Ayarlamaları
En kritik adım, Metro bundler'ı pnpm'in özel node_modules yapısını anlayacak şekilde yapılandırmaktır. Bu, projenizin kök dizininde bulunan metro.config.js dosyası üzerinden yapılır. Eğer bu dosya yoksa, oluşturmanız gerekecektir. Aşağıdaki yapılandırma, Metro'ya hem varsayılan arama yollarını genişletmesini hem de sembolik bağlantıları doğru şekilde takip etmesini söyler.
// metro.config.js
const path = require('path');
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
// Monorepo kök dizini (uygulama klasöründen yukarı doğru bir dizin)
const projectRoot = __dirname;
const workspaceRoot = path.resolve(projectRoot, '../..'); // Eğer bir monorepo içindeyse
const config = getDefaultConfig(__dirname);
// pnpm'in symlink yapısını Metro'ya anlatıyoruz
config.resolver.extraNodeModules = {
'node_modules': path.resolve(projectRoot, 'node_modules'),
// Diğer önemli modüller de buraya eklenebilir
};
config.resolver.nodeModulesPaths = [
path.resolve(projectRoot, 'node_modules'),
path.resolve(workspaceRoot, 'node_modules'), // Monorepo için önemli
// pnpm'in özel .pnpm klasörünü işaret eden bir yol ekliyoruz
path.resolve(projectRoot, '.pnpm'),
path.resolve(workspaceRoot, '.pnpm'), // Monorepo için
];
// Watchlist'i genişletiyoruz, böylece monorepo içindeki diğer paketler de izlenir
config.watchFolders = [
projectRoot,
workspaceRoot,
path.resolve(projectRoot, '.pnpm'),
path.resolve(workspaceRoot, '.pnpm'),
];
// Sembolik bağlantıları takip etmesini sağlıyoruz
config.resolver.disableHierarchicalLookup = false; // Bunu false bırakmak genellikle en iyisidir
config.resolver.resolveSymlinks = true; // Sembolik bağlantıları çözümle
// assetExts'i ve sourceExts'i güncelleyebiliriz (genellikle varsayılan yeterlidir)
// config.resolver.assetExts = config.resolver.assetExts.filter(ext => ext !== 'svg');
// config.resolver.sourceExts = [...config.resolver.sourceExts, 'svg'];
module.exports = mergeConfig(getDefaultConfig(__dirname), config);
Yukarıdaki yapılandırma, özellikle nodeModulesPaths ve watchFolders kısımlarında kritik rol oynar. nodeModulesPaths, Metro'nun modül ararken bakacağı dizinleri belirtir. Buraya hem projenin kendi node_modules klasörünü hem de monorepo kökünün node_modules klasörünü (varsa) ve en önemlisi .pnpm klasörlerini eklemek, Metro'nun bağımlılıkları bulmasını sağlar. watchFolders ise Metro'nun hangi klasörlerdeki değişiklikleri izleyeceğini belirtir. Monorepo yapılarında veya .pnpm klasörü içindeki değişikliklerin algılanması için bu listenin genişletilmesi gerekir.
Uzman İpucu: path.resolve Kullanımı
path.resolve, farklı işletim sistemlerinde yol ayrımı sorunlarını gidermek için çok önemlidir. Daima mutlak yollar sağlamak, Metro'nun tutarlı bir şekilde çalışmasını sağlar.
Belirli Babel Sürümlerini Yönetme
Bazen @babel/runtime hatası, projenizin veya bir bağımlılığınızın belirli bir Babel sürümünü beklemesinden kaynaklanabilirken, başka bir bağımlılığın farklı bir sürümle çalışmaya çalışmasıyla ortaya çıkar. pnpm'in izolasyon yeteneği bu durumu yönetmek için idealdir, ancak Metro'nun bu izolasyonu doğru bir şekilde çözmesi için ek adımlar gerekebilir. Eğer yukarıdaki genel yapılandırmalar sorunu çözmezse, Babel'ın kendi yapılandırma dosyası olan babel.config.js'i kontrol etmeniz gerekebilir.
// babel.config.js
module.exports = {
presets: ['module:@react-native/babel-preset'],
plugins: [
[
'@babel/plugin-transform-runtime',
{
// Bu kısım önemlidir! Babel'ın @babel/runtime'ı nerede arayacağını belirtir.
// pnpm ile bu, genellikle projenizin kendi node_modules'undaki .pnpm klasörünü işaret eder.
// Genellikle varsayılan değerler yeterli olur, ancak sorun yaşarsanız burayı kontrol edin.
// corejs: false, // veya 3, duruma göre
// helpers: true,
// regenerator: true,
// useESModules: true, // Eğer modern import/export kullanıyorsanız
},
],
],
};
Genellikle @babel/plugin-transform-runtime eklentisi varsayılan ayarlarla çalışır, ancak nadir durumlarda @babel/runtime paketinin bulunduğu yolu açıkça belirtmeniz gerekebilir. Bu, özellikle farklı Babel sürümlerinin çakıştığı senaryolarda önemlidir. Ancak, çoğu zaman Metro yapılandırmasındaki nodeModulesPaths düzeltmeleri yeterli olacaktır. Eğer birden fazla @babel/runtime sürümüyle uğraşıyorsanız, pnpm'in pnpm patch veya pnpm overrides özelliklerini kullanarak bağımlılık grafiğini manuel olarak düzeltebilirsiniz, ancak bu ileri düzey bir konudur ve genellikle daha basit çözümler tercih edilir.
Tüm bu yapılandırmaları yaptıktan sonra, Metro'yu ve geliştirici sunucusunu yeniden başlatmayı unutmayın. Bazen pnpm cache clean --force ve rm -rf node_modules, rm -rf .pnpm, rm -rf /tmp/metro-* komutlarını çalıştırıp pnpm install ile sıfırdan başlamak da işe yarayabilir. Bu temizlik adımları, Metro'nun eski önbelleklenmiş bilgileri kullanmasını engeller ve yeni yapılandırmalarınızın doğru bir şekilde uygulanmasını sağlar.
İleri Düzey İpuçları ve Püf Noktaları: Performans ve Bakım
Yukarıdaki temel adımlarla Metro'nun @babel/runtime hatalarını büyük ölçüde çözmüş olmalısınız. Ancak pnpm ve React Native ikilisinden en iyi performansı almak ve geliştirme deneyiminizi optimize etmek için bazı ileri düzey ipuçları ve püf noktaları bulunmaktadır.
Monorepo'larda Daha Fazla Kontrol
Monorepo'lar, pnpm'in parladığı yerlerden biridir. Paylaşılan kütüphaneler, birden fazla uygulama ve ortak bileşenler için tek bir kod tabanı kullanmak, kodu yeniden kullanmayı ve bakımı kolaylaştırmayı sağlar. pnpm, bu senaryoda bağımlılıkların tutarlı ve izole bir şekilde yönetilmesini garantiler.
- Paylaşılan Paketler: Eğer monoreponuzda bir React Native projesi ve paylaşılan bir UI kütüphanesi (örneğin,
packages/ui) varsa, bu kütüphaneyi React Native projenize eklemek içinpnpm add @my-org/ui --filter my-react-native-appkomutunu kullanabilirsiniz. Bu, pnpm'in çalışma alanındaki paketler arasında doğru sembolik bağlantıları kurmasını sağlar. - Workspace Protokolü:
package.jsondosyanızda çalışma alanı içindeki paketlere referans verirkenworkspace:^veyaworkspace:*protokolünü kullanmak, pnpm'in bu paketleri yerel olarak bağlamasını sağlar. Örneğin:{ "name": "my-react-native-app", "dependencies": { "@my-org/ui": "workspace:^", // ... } }
Performans İçin Metro Önbelleğini Yönetme
Metro, hızlı derleme süreleri için agresif bir önbellekleme kullanır. Ancak bu önbellek, yapılandırma değişiklikleri veya pnpm'in bağımlılık yapısındaki değişiklikler karşısında bazen sorun yaratabilir. Geliştirme sırasında aşağıdaki komutları kullanmak, sorunları gidermenize yardımcı olabilir:
# Metro'nun önbelleğini temizle
rm -rf $TMPDIR/metro-*
# Watchman'ın önbelleğini temizle (eğer kullanıyorsanız)
watchman watch-del-all
# pnpm'in store'unu temizle (nadiren gerekli, dikkatli kullanın!)
pnpm store prune
# Metro'yu önbelleği sıfırlayarak başlat
npx react-native start --reset-cache
Bu komutlar, özellikle bağımlılıkları güncelledikten veya metro.config.js dosyasında değişiklik yaptıktan sonra Metro'nun tutarlı bir durumda başlamasını sağlar.
pnpm Patches (Yamalar) ile Bağımlılık Sorunlarını Giderme
Bazı durumlarda, bir üçüncü taraf paketi pnpm'in katı bağımlılık yapısıyla uyumlu olmayan bir şekilde kodlanmış olabilir (örneğin, doğrudan bir bağımlılık olarak listelenmemiş ancak kullanılan bir paket). Bu durumda, pnpm patch komutu çok güçlü bir araçtır. Bu komut, bir bağımlılığın kaynak kodunu geçici olarak değiştirmenize ve kendi yamanızı oluşturmanıza olanak tanır. pnpm, bu yamayı bir .patch dosyası olarak depolar ve her pnpm install çalıştırdığınızda otomatik olarak uygular.
# Yama yapmak istediğiniz paketi geçici olarak değiştirilebilir hale getirir
pnpm patch @
# (Burada paketin içindeki kodu düzenlersiniz)
# Değişiklikleri bir yama dosyasına kaydeder
pnpm patch-commit
Bu, özellikle kütüphane geliştiricileri bir sorun düzeltmesi yayınlayana kadar geçici çözümler uygulamak için harikadır. Ancak, bu özelliği dikkatli kullanmalı ve yamanın neden gerekli olduğunu açıkça belgelemelisiniz.
.npmrc Dosyasında Özel Ayarlar
Projenizin kök dizenindeki .npmrc dosyası, pnpm'in davranışını özelleştirmek için kullanılabilir. Örneğin, bazı durumlarda Metro'nun veya belirli bir aracın node_modules'u daha "düz" bir şekilde görmesini sağlamak için shamefully-hoist=true ayarını kullanmak isteyebilirsiniz. Ancak bu, pnpm'in temel avantajlarından olan katı bağımlılık izolasyonunu bir miktar zayıflatır ve genellikle önerilmez. Genellikle, metro.config.js ayarlarıyla bu sorunu çözmek daha iyidir.
# .npmrc
# shamefully-hoist=true # Nadir durumlarda denenebilir, ancak dikkatli olun
Bu ayar, pnpm'in bağımlılıkları geleneksel paket yöneticileri gibi root node_modules'a taşımasına neden olur. Eğer diğer tüm çözümler başarısız olursa, bir deneme olarak düşünülebilir, ancak pnpm'in temel felsefesine aykırı olduğu unutulmamalıdır.
Uzman İpucu: Otomatik Entegrasyon Araçları
Bazı topluluk araçları, pnpm ve Metro arasındaki entegrasyonu kolaylaştırmak için geliştirilmiştir. Örneğin, @rnx-kit/metro-config gibi paketler, karmaşık yapılandırmaları basitleştirmeye yardımcı olabilir. Ancak bu makale, temel anlayışı sağlamak ve manuel çözüm yollarını göstermek amacıyla bu tür özel araçlara odaklanmamıştır.
Vaka Analizi: Büyük Bir Projeyi pnpm'e Geçirme Deneyimi
Yaklaşık bir yıl önce, üzerinde çalıştığımız büyük ölçekli bir React Native projesi, npm ile bağımlılık yönetimi konusunda ciddi sıkıntılar yaşıyordu. Proje, 5 farklı React Native uygulaması ve 10'dan fazla paylaşılan React Native kütüphanesini içeren karmaşık bir monorepo yapısına sahipti. Her bir npm install işlemi dakikalar sürüyordu ve geliştiriciler sürekli olarak node_modules klasörlerinin şişkinliğinden ve bazen de "phantom dependencies" nedeniyle beklenmedik hatalardan şikayet ediyordu. Bu durum, özellikle CI/CD süreçlerinde ve yeni bir geliştiricinin projeye katılımında büyük bir engel teşkil ediyordu. Bu sorunları aşmak ve geliştirici verimliliğini artırmak amacıyla, projeyi pnpm'e geçirme kararı aldık.
Geçiş sürecinin ilk adımı, pnpm'i tanıtmak ve tüm geliştirme ortamlarını senkronize etmek oldu. Tüm node_modules klasörlerini ve package-lock.json dosyalarını temizledikten sonra, ilk pnpm install komutuyla karşılaştık. İlk izlenimler oldukça olumluydu: Bağımlılık kurulum süresi %60 oranında azaldı! Ancak sevinç kısa sürdü; React Native uygulamalarından birini başlatmaya çalıştığımızda, Metro'nun o meşhur @babel/runtime hataları tüm konsolu doldurdu. "Unable to resolve module @babel/runtime/helpers/interopRequireDefault" mesajları, daha önce gördüğümüzden çok daha sık ve çeşitli modüllerden geliyordu.
Bu noktada, makalede daha önce bahsettiğimiz Metro yapılandırma adımları devreye girdi. İlk olarak, her bir React Native uygulamasının package.json dosyasına @babel/runtime bağımlılığını doğrudan ekledik. Ardından, monoreponun kök dizeninde ve her bir React Native uygulamasının kendi dizininde ayrı ayrı metro.config.js dosyalarını oluşturduk (veya güncelledik). Özellikle config.resolver.nodeModulesPaths ve config.watchFolders ayarlarını titizlikle düzenledik. projectRoot ve workspaceRoot değişkenlerini doğru bir şekilde tanımlamak, Metro'nun hem uygulamanın kendi node_modules'unu hem de monoreponun ana .pnpm klasörünü ve paylaşılan paketlerin node_modules'larını doğru bir şekilde taramasını sağladı. config.resolver.resolveSymlinks = true ayarı, pnpm'in sembolik bağlantılarını takip etmesi için kritikti.
Bu yapılandırmaları uyguladıktan sonra, Metro sunucusunu npx react-native start --reset-cache ile yeniden başlattık. İlk denemelerde hala bazı hatalar alıyorduk, özellikle bazı özel bağımlılıklar ve React Native'in kendi dahili modülleri ile ilgiliydi. Bu durum, metro.config.js dosyasındaki nodeModulesPaths listesine, ilgili sorunlu modüllerin bulunduğu belirli yolları (genellikle node_modules/react-native/... veya .pnpm/react-native@... içindeki yollar) manuel olarak eklememizi gerektirdi. Birkaç deneme yanılma ve konsol çıktılarını dikkatlice inceleme sonucunda, tüm yolları doğru bir şekilde tanımlamayı başardık. Sonunda, tüm React Native uygulamaları sorunsuz bir şekilde derlenmeye ve çalışmaya başladı.
Bu geçişin sonuçları oldukça etkileyiciydi. Bağımlılık kurulum süreleri ortalama %70 oranında azaldı. Monorepo içindeki paketler arası bağımlılık yönetimi çok daha tutarlı ve hatasız hale geldi. Geliştiriciler, "phantom dependencies" nedeniyle ortaya çıkan beklenmedik çalışma zamanı hatalarından kurtulmuştu. Ayrıca, disk alanı kullanımı da önemli ölçüde azaldı; eski node_modules klasörlerinin toplam boyutu 20 GB'ı aşarken, pnpm ile bu rakam 5 GB'ın altına düştü. Bu vaka analizi, pnpm ve React Native arasındaki uyum sorunlarının aşılabileceğini ve doğru yaklaşımla büyük faydalar sağlanabileceğini açıkça gösterdi. Zorluklara rağmen, bu geçişin uzun vadede proje verimliliğimize ve geliştirici memnuniyetine büyük katkı sağladığını söyleyebiliriz.
Sonuç ve Sıkça Sorulan Sorular
Bu makalede, pnpm'in React Native projelerinde Metro bundler ile yol açtığı @babel/runtime hatalarının derinlemesine nedenlerini ve adım adım çözümlerini inceledik. pnpm'in bağımlılıkları sembolik bağlantılarla yönetme yaklaşımı, geleneksel paket yöneticilerinden farklı olduğu için Metro'nun alışık olduğu düzeni bozar. Ancak doğru Metro yapılandırması, pnpm-workspace.yaml ve package.json ayarları ile bu uyum sorununu aşmak mümkündür. metro.config.js dosyasındaki nodeModulesPaths ve watchFolders ayarlarını dikkatlice düzenleyerek, Metro'nun pnpm'in sanal node_modules yapısını doğru bir şekilde taramasını sağlayabilirsiniz. Ayrıca, özellikle monorepo yapılarında ileri düzey ipuçları ve pnpm patch gibi araçlarla bağımlılık sorunlarına daha etkili çözümler sunulabileceğini gördük. Unutmayın, her büyük teknolojik geçiş gibi, pnpm'e geçiş de başlangıçta bazı zorluklar barındırabilir. Ancak sunduğu performans, disk alanı tasarrufu ve bağımlılık izolasyonu avantajları, bu çabaya değer kılar. Geliştirici verimliliğinizi artırmak ve daha stabil bir geliştirme ortamı kurmak için bu rehberdeki adımları uygulayarak Metro'nun sessizce çalışmasını sağlayabilirsiniz.
Sıkça Sorulan Sorular (SSS)
-
@babel/runtimehatası neden sadece pnpm ile ortaya çıkıyor?Bu hata, pnpm'in bağımlılıkları sembolik bağlantılar (symlinks) kullanarak projenin kök
.pnpmklasöründe izole etmesinden kaynaklanır. Metro bundler, varsayılan olarak bu derin ve sembolik bağlantılı yapıyı doğru bir şekilde çözemez ve@babel/runtimegibi kritik paketleri beklediği yollarda bulamaz. npm ve yarn (classic) ise bağımlılıkları daha "düz" birnode_modulesyapısı altında topladığı için Metro bu yapıyı kolayca tarar. -
metro.config.jsdosyasındakinodeModulesPathsayarı ne işe yarar?nodeModulesPathsayarı, Metro'ya modülleri ararken hangi dizinlere bakması gerektiğini söyler. pnpm ile çalışırken, bu listeye hem projenin kendinode_modulesklasörünü hem de (monorepo kullanılıyorsa) ana çalışma alanınınnode_modulesklasörünü ve en önemlisi, pnpm'in tüm paket dosyalarını depoladığı kök.pnpmklasörünün yollarını eklemelisiniz. Bu sayede Metro, tüm bağımlılıkları, özellikle@babel/runtime'ı, doğru yerde bulabilir. -
Metro cache'ini temizlemek neden önemlidir?
Metro, hızlı derleme sağlamak için modül çözünürlüklerini ve derlenmiş çıktıları önbelleğe alır.
metro.config.jsdosyasında veya bağımlılık yapısında (pnpm ile) önemli değişiklikler yaptığınızda, Metro'nun eski önbelleklenmiş bilgileri kullanmaya devam etmesi sorunlara yol açabilir.--reset-cachekomutu veya manuel olarak/tmp/metro-*dosyalarını silmek, Metro'nun yeni yapılandırmalarınızı sıfırdan uygulamasını sağlar ve tutarsızlıkları önler. -
Monorepo kullanmıyorsam
pnpm-workspace.yamldosyasına ihtiyacım var mı?Hayır, eğer projeniz tek bir uygulama veya paket ise ve bir monorepo yapısı kullanmıyorsanız
pnpm-workspace.yamldosyasına ihtiyacınız yoktur. Bu dosya sadece birden fazla paketi tek bir çalışma alanında yönetmek için gereklidir. Ancakmetro.config.jsdosyasındaki ilgili ayarlamalar (özelliklenodeModulesPathsvewatchFolders) yine de pnpm'in modül çözümleme mantığına uyum sağlamak için gereklidir. -
shamefully-hoist=trueayarını kullanmalı mıyım?Genellikle hayır.
shamefully-hoist=true, pnpm'in bağımlılıkları npm veya yarn (classic) gibi rootnode_modulesklasörüne taşımasına neden olur. Bu, pnpm'in "phantom dependencies" sorununu önleme ve katı izolasyon sağlama gibi temel faydalarını ortadan kaldırır. Bu ayarı kullanmak yerine,metro.config.jsdosyasındakinodeModulesPathsvewatchFoldersgibi ayarları doğru bir şekilde yapılandırmak daha iyi bir yaklaşımdır. Ancak, tüm diğer çözümler başarısız olursa ve hızlı bir geçici çözüm arıyorsanız, bu ayarı son çare olarak düşünebilirsiniz.