Ağaç sallama ile JavaScript yüklerini azaltın

Günümüzde web uygulamaları, özellikle de JavaScript kısmı oldukça büyük olabiliyor. 2018'in ortaları itibarıyla HTTP Arşivi, mobil cihazlardaki JavaScript'in ortanca aktarım boyutunu yaklaşık 350 KB olarak belirlemiştir. Bu yalnızca aktarım boyutudur. JavaScript genellikle ağ üzerinden gönderildiğinde sıkıştırılır. Yani JavaScript'in gerçek miktarı, tarayıcı tarafından açıldıktan sonra oldukça fazla olur. Kaynak işlemesi söz konusu olduğunda, sıkıştırma işlemi yararlı olmadığından bunu vurgulamak önemlidir. Sıkıştırılmış 900 KB JavaScript boyutu, sıkıştırıldığında yaklaşık 300 KB olabilse de ayrıştırıcı ve derleyici için 900 KB'tır.

JavaScript'i indirme, sıkıştırmayı açma, ayrıştırma, derleme ve çalıştırma sürecini gösteren bir şema.
JavaScript'i indirme ve çalıştırma işlemi. Komut dosyasının aktarım boyutu 300 KB sıkıştırılmış olsa da ayrıştırılması, derlenmesi ve yürütülmesi gereken JavaScript kodunun 900 KB olduğunu unutmayın.

JavaScript, işlenmesi pahalı bir kaynaktır. Kod çözme işlemi, indirildikten sonra oldukça önemsiz hale gelen görüntülerin aksine, JavaScript'in ayrıştırılması, derlenmesi ve son olarak yürütülmesi gerekir. Bayt için bayt, bu JavaScript'i diğer kaynak türlerinden daha pahalı hale getirir.

170 KB JavaScript'in işleme süresini benzer büyüklükteki bir JPEG resmiyle karşılaştıran bir diyagram. JavaScript kaynağı, bayt için JPEG'den çok daha fazla kaynak yoğun bir bayttır.
Eşdeğer boyutlu bir JPEG'nin kod çözme zamanına karşılık 170 KB JavaScript'i ayrıştırma/derlemenin işleme maliyeti. (kaynak).

JavaScript motorlarının verimliliğini artırmak için sürekli olarak iyileştirmeler yapılmaktadır ancak JavaScript performansını iyileştirmek, her zaman olduğu gibi geliştiricilerin görevidir.

Bu amaçla, JavaScript performansını iyileştirmeye yönelik teknikler vardır. Kod bölme, uygulama JavaScript'ini parçalara ayırarak ve bu parçaları yalnızca ihtiyaç duyan bir uygulamanın yollarına sunarak performansı iyileştiren bir tekniktir.

Bu teknik işe yaraysa da, JavaScript yüklü uygulamalarda sık karşılaşılan bir sorun (hiçbir zaman kullanılmayan kodların eklenmesi) çözmez. Ağaç sallama bu sorunu çözme girişiminde bulunur.

Ağaç sallama nedir?

Ağaç sallama bir tür kod yok etme yöntemidir. Terim, Toplayıcı tarafından popüler hale getirildi ancak geçersiz kod eleme kavramı bir süredir var. Kavram ayrıca bu makalede örnek bir uygulama yoluyla gösterilen webpack'te de satın alma işlemi bulmuştur.

"Ağaç sallama" terimi uygulamanızın zihinsel modelinden ve ağaç benzeri bir yapı olarak bağımlılıklarından kaynaklanır. Ağaçtaki her düğüm, uygulamanız için farklı işlevler sağlayan bir bağımlılığı temsil eder. Modern uygulamalarda bu bağımlılıklar aşağıdaki gibi statik import ifadeleri üzerinden getirilir:

// Import all the array utilities!
import arrayUtils from "array-utils";
.

Bir uygulama gençken (isterseniz fidan) az sayıda bağımlılığı olabilir. Ayrıca, eklediğiniz bağımlılıkların tümünü olmasa da çoğunu kullanıyor. Ancak uygulamanız olgunlaştıkça daha fazla bağımlılık eklenebilir. Bileşik hale getirmek için, eski bağımlılıklar artık kullanılmaz ancak kod tabanınızdan ayıklanamaz. Sonuç olarak bir uygulama, çok sayıda kullanılmayan JavaScript ile gönderilir. Ağaç sallama yöntemi, statik import ifadelerinin ES6 modüllerinin belirli bölümlerini çekme biçiminden yararlanarak bu sorunu giderir:

// Import only some of the utilities!
import { unique, implode, explode } from "array-utils";

Bu import örneği ile önceki örnek arasındaki fark, "array-utils" modülündeki (çok fazla kod olabilir) her şeyi içe aktarmak yerine bu örnekte öğenin yalnızca belirli bölümlerini içe aktarmasıdır. Modülün tamamı içe aktarıldığından geliştirici derlemelerinde bu durum hiçbir şey değiştirmez. Üretim derlemelerinde webpack "shake" yapacak şekilde yapılandırılabilir açıkça içe aktarılmayan ES6 modüllerinden yapılan dışa aktarma işlemlerini azaltır. Bu da üretim derlemelerini küçültür. Bu kılavuzda, bunu nasıl yapacağınızı öğreneceksiniz.

Ağaç sallama fırsatlarını bulmak

Örnek olarak, ağaç sallamanın nasıl çalıştığını gösteren örnek tek sayfalık bir uygulama sunulmaktadır. İsterseniz onu klonlayıp takip edebilirsiniz. Ancak bu kılavuzda sürecin her adımını birlikte ele alacağız. Dolayısıyla, klonlamaya gerek yoktur (uygulamalı eğitimle ilgilenmeniz gerekmiyorsa).

Örnek uygulama, gitar efekt pedallarının arama yapılabilir bir veritabanıdır. Bir sorgu girdiğinizde efekt pedallarının listesi görünür.

Gitar efekt pedalları veritabanında arama yapmak için kullanılan tek sayfalık örnek uygulamanın ekran görüntüsü.
Örnek uygulamanın ekran görüntüsü.

Bu uygulamaya yön veren davranış, tedarikçi firmaya (ör. Preact ve Emotion) ve uygulamaya özel kod paketleri (veya webpack'in gerektirdiği "parçalar")

Chrome'un Geliştirici Araçları ağ panelinde gösterilen iki uygulama kod paketinin (veya parçalarının) ekran görüntüsü.
Uygulamanın iki JavaScript paketi. Bunlar sıkıştırılmamış boyutlardır.

Yukarıdaki şekilde gösterilen JavaScript paketleri üretim derlemeleridir, yani hata ayıklama ile optimize edilirler. Uygulamaya özel bir paket için 21, 1 KB kötü bir sayı olmasa da hiçbir ağaç sallamanın olmadığı unutulmamalıdır. Şimdi uygulama koduna bakalım ve bunu düzeltmek için neler yapılabileceğine bakalım.

Her uygulamada, ağaç sallama fırsatları bulmak için statik import ifadeleri aramak gerekecektir. Ana bileşen dosyasının üst kısmına yakın şuna benzer bir satır görürsünüz:

import * as utils from "../../utils/utils";

ES6 modüllerini çeşitli yollarla içe aktarabilirsiniz ancak buna benzer modüller ilginizi çeker. Bu özel satır, "utils modülünden import her şey yazar ve onu utils adlı bir ad alanına yerleştirin." Burada sormamız gereken en önemli soru şudur: "Bu modülde ne kadar içerik var?"

utils modülü kaynak koduna bakarsanız yaklaşık 1.300 satır kod olduğunu görürsünüz.

Tüm bu öğelere ihtiyacınız var mı? utils modülünü içe aktaran ana bileşen dosyasında arama yaparak bu ad alanının kaç örneğinin göründüğünü tekrar kontrol edelim.

Metin düzenleyicide yapılan ve yalnızca 3 sonuç döndüren "utils." aramasının ekran görüntüsü.
Çok sayıda modülü içe aktardığımız utils ad alanı, ana bileşen dosyasında yalnızca üç kez çağrılır.

utils ad alanı, uygulamamızda yalnızca üç noktada görünür. Peki hangi işlevler için kullanılır? Ana bileşen dosyasına tekrar baktığınızda, bunun yalnızca tek bir işlev olduğu görülüyor. utils.simpleSort bu işlev, sıralama açılır listeleri değiştirildiğinde arama sonuçları listesini bir dizi ölçüte göre sıralamak için kullanılır:

if (this.state.sortBy === "model") {
  // `simpleSort` gets used here...
  json = utils.simpleSort(json, "model", this.state.sortOrder);
} else if (this.state.sortBy === "type") {
  // ..and here...
  json = utils.simpleSort(json, "type", this.state.sortOrder);
} else {
  // ..and here.
  json = utils.simpleSort(json, "manufacturer", this.state.sortOrder);
}

Çok sayıda dışa aktarma işlemi içeren 1.300 satırlık bir dosyadan yalnızca biri kullanılır. Bu, çok sayıda kullanılmayan JavaScript'in gönderilmesine neden olur.

Bu örnek uygulamanın biraz tartışmalı olduğu kabul edilse de, bu sentetik senaryonun üretim amaçlı web uygulamasında karşılaşabileceğiniz gerçek optimizasyon fırsatlarına benzediği gerçeğini değiştirmez. Ağaç sallamanın faydalı olabileceğini düşündüğünüze göre, bu nasıl yapılır?

Babel'in ES6 modüllerini CommonJS modüllerine dönüştürmesini engelleme

Babel vazgeçilmez bir araçtır, ancak ağaç sarsıntılarının etkilerini gözlemlemeyi biraz zorlaştırabilir. @babel/preset-env kullanıyorsanız Babel, ES6 modüllerini daha geniş çapta uyumlu CommonJS modüllerine dönüştürebilir.requireimport

CommonJS modüllerinde ağaç sallamayı yapmak daha zor olduğundan, web paketi kullanmaya karar verdiğinizde paketlerden ne budanacağını bilemez. Çözüm, @babel/preset-env ürününün, ES6 modüllerini açıkça olduğu gibi bırakacak şekilde yapılandırılmasıdır. Babel'i nerede yapılandırdığınız fark etmeksizin (babel.config.js veya package.json'da) fazladan bir şeyler eklemeniz gerekir:

// babel.config.js
export default {
  presets: [
    [
      "@babel/preset-env", {
        modules: false
      }
    ]
  ]
}

@babel/preset-env yapılandırmanızda modules: false belirtilmesi, Babel'in istendiği gibi davranmasını sağlar. Bu da webpack'in bağımlılık ağacınızı analiz etmesine ve kullanılmayan bağımlılıkları ortadan kaldırmasına olanak tanır.

Yan etkileri göz önünde bulundurma

Uygulamanızdan bağımlılıkları sallarken göz önünde bulundurmanız gereken bir diğer unsur, bazı yan etkilere yol açabilir. Yan etkiye örnek olarak, işlevi, kendi kapsamı dışında bir şeyi değiştirir. Bu durum, yan etki olarak kabul edilir. en önemli husustur:

let fruits = ["apple", "orange", "pear"];

console.log(fruits); // (3) ["apple", "orange", "pear"]

const addFruit = function(fruit) {
  fruits.push(fruit);
};

addFruit("kiwi");

console.log(fruits); // (4) ["apple", "orange", "pear", "kiwi"]

Bu örnekte addFruit, kapsamı dışında olan fruits dizisini değiştirdiğinde bir yan etki oluşturuyor.

Yan etkiler, ES6 modülleri için de geçerlidir ve ağaç sallama bağlamında önemlidir. Tahmin edilebilir girişler alan ve kendi kapsamlarının dışında herhangi bir şeyi değiştirmeden eşit derecede tahmin edilebilir çıkışlar üreten modüller, kullanılmadığı sürece güvenle çıkarılabilecek bağımlılıklardır. Bunlar bağımsız modüler kodlardır. Dolayısıyla "modüller"

Web paketi söz konusu olduğunda, projenin package.json dosyasında "sideEffects": false öğesini belirterek paketin ve bağımlılıklarının yan etki içermediğini belirtmek için bir ipucu kullanılabilir:

{
  "name": "webpack-tree-shaking-example",
  "version": "1.0.0",
  "sideEffects": false
}

Alternatif olarak, hangi belirli dosyaların yan efekt içermeyen olduğunu webpack'e bildirebilirsiniz:

{
  "name": "webpack-tree-shaking-example",
  "version": "1.0.0",
  "sideEffects": [
    "./src/utils/utils.js"
  ]
}

İkinci örnekte, belirtilmeyen herhangi bir dosyanın yan etkisi olmadığı varsayılır. Bunu package.json dosyanıza eklemek istemiyorsanız bu işareti web paketi yapılandırmanızda module.rules aracılığıyla da belirtebilirsiniz.

Yalnızca gerekli öğeler içe aktarılıyor

Babel'e ES6 modüllerini olduğu gibi bırakması için talimat verildikten sonra, yalnızca utils modülünden gereken işlevleri getirmek için import söz dizimimizde küçük bir düzenleme yapılması gerekiyor. Bu kılavuzdaki örnekte, yalnızca simpleSort işlevi olması yeterlidir:

import { simpleSort } from "../../utils/utils";

utils modülünün tamamı yerine yalnızca simpleSort içe aktarıldığından her utils.simpleSort örneğinin simpleSort olarak değiştirilmesi gerekir:

if (this.state.sortBy === "model") {
  json = simpleSort(json, "model", this.state.sortOrder);
} else if (this.state.sortBy === "type") {
  json = simpleSort(json, "type", this.state.sortOrder);
} else {
  json = simpleSort(json, "manufacturer", this.state.sortOrder);
}

Bu örnekte ağaç sallamanın işe yaraması için gereken tek şey budur. Bağımlılık ağacını sallamadan önceki web paketi çıkışı şudur:

                 Asset      Size  Chunks             Chunk Names
js/vendors.16262743.js  37.1 KiB       0  [emitted]  vendors
   js/main.797ebb8b.js  20.8 KiB       1  [emitted]  main

Bu, ağaç sallama başarılı olduktan sonra çıktı:

                 Asset      Size  Chunks             Chunk Names
js/vendors.45ce9b64.js  36.9 KiB       0  [emitted]  vendors
   js/main.559652be.js  8.46 KiB       1  [emitted]  main

Her iki paket de küçülse de en çok avantaj sağlayan main paketidir. utils modülünün kullanılmayan kısımlarını sallayarak main paketi yaklaşık %60 küçülür. Bu, hem komut dosyasının indirilmesine kadar geçen süreyi hem de işlem süresini kısaltır.

Gidip biraz ağaç sallayın!

Ağaç sallamasından elde edeceğiniz mesafe, uygulamanıza, bağımlılıklarına ve mimarisine bağlıdır. Deneyin! Bu optimizasyonu gerçekleştirmek için modül paketleyicinizi ayarlamadığınızı biliyorsanız, denemenin uygulamanıza fayda sağlayacağını görmenin bir sakıncası yoktur.

Ağaç sallamasından önemli bir performans elde edebilir veya hiç performans görebilirsiniz. Ancak derleme sisteminizi, üretim derlemelerinde bu optimizasyondan yararlanacak şekilde yapılandırarak ve yalnızca uygulamanızın ihtiyaçlarını seçerek içe aktararak uygulama paketlerinizi proaktif olarak mümkün olduğunca küçük tutarsınız.

Bu makalenin kalitesini önemli ölçüde iyileştiren değerli geri bildirimleri için Kristofer Baxter, Jason Miller, Addy Osmani, Jeff Posnick, Sam Saccone ve Philip Walton'a özel teşekkürlerimizi sunuyoruz.