Modül paketleyicilerin nasıl çalıştığını öğrenelim ve sonra kendimiz bir tane yazalım

Merhaba! Hoş geldiniz, hoş geldiniz, sizi burada görmek çok güzel! Bugün gerçekten basit bir JavaScript modül paketleyicisi oluşturacağız.

Başlamadan önce birkaç teşekkür etmek istiyorum. Bu makale ağırlıklı olarak aşağıdaki kaynaklardan yararlanmaktadır:

  • JavaScript modül paketleyicisinin paketini kaldırma - Luciano Mammino
  • Mini paket - Ronen Amiel

Tamam, bir modül paketleyicinin gerçekte ne olduğu ile başlayalım.

Modül Paketleyici Nedir?

Modül paketleyici, JavaScript parçalarını ve bağımlılıklarını alıp bunları genellikle tarayıcıda kullanılmak üzere tek bir dosyada paketleyen bir araçtır. Browserify, Webpack, Rollup veya diğerleri gibi araçlar kullanmış olabilirsiniz.

Genellikle bir giriş dosyasıyla başlar ve oradan o giriş dosyası için gereken tüm kodu bir araya getirir.

Bir paketleyicinin iki ana aşaması vardır:

  1. Bağımlılık çözümü
  2. Paketleme

Bir giriş noktasından ( app.jsyukarıdaki gibi) başlayarak , bağımlılık çözümlemesinin amacı, kodunuzun tüm bağımlılıklarını (çalışması gereken diğer kod parçalarını) aramak ve bir grafik (bağımlılık grafiği olarak adlandırılır) oluşturmaktır.

Bu yapıldıktan sonra, bağımlılık grafiğinizi uygulamanın kullanabileceği tek bir dosyaya paketleyebilir veya dönüştürebilirsiniz.

Kodumuza bazı ithalatlarla başlayalım (nedenini daha sonra açıklayacağım).

Bağımlılık Çözümü

Yapmamız gereken ilk şey, bağımlılık çözümleme aşamasında bir modülü nasıl temsil etmek istediğimizi düşünmektir.

Modül Gösterimi

Dört şeye ihtiyacımız olacak:

  • Dosyanın adı ve tanımlayıcısı
  • Dosyanın nereden geldiği (dosya sisteminde)
  • Dosyadaki kod
  • Dosyanın ihtiyacı olan bağımlılıklar

Grafik yapısı, her dosyadaki bağımlılıkları özyinelemeli olarak kontrol ederek oluşturulur.

JavaScript'te, böyle bir veri kümesini temsil etmenin en kolay yolu bir nesne olacaktır.

createModuleObjectYukarıdaki işleve bakıldığında , dikkate değer kısım, çağrılan bir işlev çağrısıdır detective.

Dedektif bir kütüphanedir derin iç içe nasıl) (olursa olsun gerektirecek tüm aramaları bulmak ve biz kendi AST geçişi yapıyor önleyebilirsiniz yolu deneyerek!

Dikkat edilmesi gereken bir şey (ve bu neredeyse tüm modül paketleyicilerinde aynıdır) şudur:

const libName = 'lodash'const lib = require(libName)

Onu bulamayacaktır (çünkü bu, kodu çalıştırmak anlamına gelir).

Peki bu işlevi bir modülün yolundan çalıştırmak ne verir?

Sıradaki ne? Bağımlılık çözümü.

Tamam, henüz değil. İlk olarak, modül haritası denen bir şeyden bahsetmek istiyorum.

Modül Haritası

Düğümdeki modülleri içe aktardığınızda, göreli içe aktarmalar yapabilirsiniz require('./utils'). Öyleyse kodunuz bunu çağırdığında, paketleyici ./utils, her şey paketlendiğinde doğru dosyanın ne olduğunu nasıl bilir ?

Modül haritasının çözdüğü sorun budur.

Modül nesnemiz, id'gerçeğin kaynağımız' olacak benzersiz bir anahtara sahiptir. Bu nedenle, bağımlılık çözümümüzü yaparken, her modül için, gerekli olanların isimlerinin yanı sıra kimliklerinin bir listesini tutacağız. Bu şekilde, çalışma zamanında doğru modülü alabiliriz.

Bu aynı zamanda tüm modülleri iç içe olmayan bir nesnede, id'yi anahtar olarak kullanarak saklayabileceğimiz anlamına gelir.

Bağımlılık Çözümü

Tamam, yani getModulesişlevde makul bir miktar devam ediyor . Ana amacı, kök / giriş modülünden başlamak ve bağımlılıkları özyinelemeli olarak aramak ve çözmektir.

'Bağımlılıkları çöz' ile ne demek istiyorum? Node'da the adında bir şey vardır require.resolveve bu, Node'un ihtiyaç duyduğunuz dosyanın nerede olduğunu nasıl çözdüğünü gösterir. Bunun nedeni, nispeten veya bir node_modulesklasörden içe aktarabilmemizdir .

Şansımıza, bizim için resolvebu algoritmayı uygulayan adında bir npm modülü var . Sadece bağımlılık ve temel URL argümanlarını iletmemiz gerekiyor ve bu bizim için tüm zor işi yapacak.

Projedeki her modülün her bağımlılığı için bu çözümü gerçekleştirmemiz gerekiyor.

Daha mapönce bahsettiğim adlı modül haritasını da oluşturuyoruz .

Fonksiyonun sonunda, modulesprojemizdeki her modül / bağımlılık için modül nesnelerini içerecek isimli bir dizi kalıyor .

Artık buna sahip olduğumuza göre, son adıma geçebiliriz: paketleme!

Paketleme

Tarayıcıda, modül (tür) diye bir şey yoktur. Ancak bu, gerekli işlevi olmadığı ve hayır olduğu anlamına gelir module.exports. Dolayısıyla, tüm bağımlılıklarımıza sahip olsak da, şu anda onları modül olarak kullanmanın bir yolu yok.

Modül Fabrika İşlevi

Fabrika işlevine girin.

Bir fabrika işlevi, bir nesne döndüren bir işlevdir (bu bir yapıcı değildir). Nesne yönelimli programlamanın bir modelidir ve kullanımlarından biri kapsülleme ve bağımlılık enjeksiyonu yapmaktır.

Kulağa hoş gelmek?

Bir fabrika işlevini kullanarak, hem kendi requireişlevimizi hem de module.exportspaket kodumuzda kullanılabilecek nesneyi enjekte edebilir ve modüle kendi kapsamını verebiliriz.

Paketleme

Aşağıda, paketleme için kullanılan paketleme işlevi verilmiştir.

Bunların çoğu sadece JavaScript şablon hazır değerleridir, bu yüzden ne yaptığını tartışalım.

İlk önce modulesSource. Burada, modüllerin her birini gözden geçirip onları bir dizi kaynağa dönüştürüyoruz.

Öyleyse, bir modül nesnesi için çıktı nasıldır?

Şimdi okumak biraz zor, ancak kaynağın kapsüllendiğini görebilirsiniz. Daha önce de bahsettiğim gibi fabrika işlevini sağlıyor modulesve requirekullanıyoruz.

Bağımlılık çözümü sırasında oluşturduğumuz modül haritasını da dahil ediyoruz.

Fonksiyonun ardından, tüm bağımlılıkların büyük bir nesnesini oluşturmak için bunların hepsini birleştiriyoruz.

Bir sonraki kod dizisi bir IIFE'dir, yani bu kodu tarayıcıda (veya başka herhangi bir yerde) çalıştırdığınızda, işlev hemen çalışacaktır. IIFE, kapsamı kapsüllemek için başka bir modeldir ve burada kullanılır, bu nedenle global kapsamı bizim requireve modüllerimizle kirletmeyiz .

Gördüğünüz gibi, iki gerekli işlevi tanımlıyoruz requireve localRequire.

Require, bir modül nesnesinin kimliğini kabul eder, ancak elbette kaynak kodu kimlikler kullanılarak yazılmaz. Bunun yerine, localRequiremodüllerin gerektirdiği herhangi bir argümanı almak ve bunları doğru kimliğe dönüştürmek için diğer işlevi kullanıyoruz . Bu, bu modül haritalarını kullanıyor.

Bundan sonra module object, modülün doldurabileceği bir tanımlıyoruz ve her iki işlevi de fabrikaya geçiriyoruz, ardından geri dönüyoruz module.exports.

Son olarak, require(0)girdi dosyamız olan 0 kimliğine sahip modülü gerektirmeye çağırıyoruz .

Ve bu kadar! Modül paketleyicimiz% 100 tamamlandı!

Tebrikler! ?

Şimdi çalışan bir modül toplayıcımız var.

Bu muhtemelen üretimde kullanılmamalıdır, çünkü birçok özelliği eksiktir (döngüsel bağımlılıkları yönetmek, her dosyanın yalnızca bir kez ayrıştırıldığından emin olmak, es-modülleri vb.), Ancak bu size nasıl olacağına dair iyi bir fikir vermiştir. modül paketleyicileri aslında çalışır.

Aslında, kaynak kodun tamamını kaldırırsanız, bu yaklaşık 60 satırda çalışır.

Okuduğunuz için teşekkürler ve umarım basit modül paketleyicimizin işleyişine bir göz atmışsınızdır. Yaptıysanız, alkışladığınızdan emin olun? ve Paylaş.

Bu makale ilk olarak blogumda yayınlandı.

//Github.com/adamisntdead/wbpck-bundler kaynağına göz atın