Takip et

pnpm ve React Native ile @babel/runtime Kabusu: Metro Hatalarını Nasıl Susturdum?

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:

  1. Eski Bağımlılıkları Temizleyin: Eğer daha önce npm veya yarn kullandıysanız, node_modules klasörünü ve package-lock.json (npm) veya yarn.lock (yarn) dosyalarını silerek başlayın.
  2. pnpm Kurulumu: pnpm’in sisteminizde kurulu olduğundan emin olun. Kurulu değilse, npm install -g pnpm veya brew install pnpm (macOS) komutunu kullanabilirsiniz.
  3. Bağımlılıkları Yükleyin: Projenizin kök dizininde pnpm install komutunu ç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çin pnpm add @my-org/ui --filter my-react-native-app komutunu kullanabilirsiniz. Bu, pnpm'in çalışma alanındaki paketler arasında doğru sembolik bağlantıları kurmasını sağlar.
  • Workspace Protokolü: package.json dosyanızda çalışma alanı içindeki paketlere referans verirken workspace:^ veya workspace:* 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)

  1. @babel/runtime hatası neden sadece pnpm ile ortaya çıkıyor?

    Bu hata, pnpm'in bağımlılıkları sembolik bağlantılar (symlinks) kullanarak projenin kök .pnpm klasö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/runtime gibi kritik paketleri beklediği yollarda bulamaz. npm ve yarn (classic) ise bağımlılıkları daha "düz" bir node_modules yapısı altında topladığı için Metro bu yapıyı kolayca tarar.

  2. metro.config.js dosyasındaki nodeModulesPaths ayarı ne işe yarar?

    nodeModulesPaths ayarı, Metro'ya modülleri ararken hangi dizinlere bakması gerektiğini söyler. pnpm ile çalışırken, bu listeye hem projenin kendi node_modules klasörünü hem de (monorepo kullanılıyorsa) ana çalışma alanının node_modules klasörünü ve en önemlisi, pnpm'in tüm paket dosyalarını depoladığı kök .pnpm klasörünün yollarını eklemelisiniz. Bu sayede Metro, tüm bağımlılıkları, özellikle @babel/runtime'ı, doğru yerde bulabilir.

  3. 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.js dosyası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-cache komutu 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.

  4. Monorepo kullanmıyorsam pnpm-workspace.yaml dosyası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.yaml dosyasına ihtiyacınız yoktur. Bu dosya sadece birden fazla paketi tek bir çalışma alanında yönetmek için gereklidir. Ancak metro.config.js dosyasındaki ilgili ayarlamalar (özellikle nodeModulesPaths ve watchFolders) yine de pnpm'in modül çözümleme mantığına uyum sağlamak için gereklidir.

  5. shamefully-hoist=true ayarını kullanmalı mıyım?

    Genellikle hayır. shamefully-hoist=true, pnpm'in bağımlılıkları npm veya yarn (classic) gibi root node_modules klasö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.js dosyasındaki nodeModulesPaths ve watchFolders gibi 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.

Yorumlar
İçeriği beğendiniz mi? Bir tartışma başlatın veya görüşlerinizi paylaşın.
Yorum Yaz

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

E-posta Bülteni
Yazılım Topluluğuna Katılın
En son güncellemeleri, yaratıcı ipuçlarını ve özel kaynakları doğrudan e-posta kutunuza alın. Tasarım ve inovasyonun geleceğini birlikte keşfedelim.