Sayfaların daha hızlı yüklenmesi için modern tarayıcılara modern kod sunma

Bu codelab'de, kullanıcıların rastgele kedileri derecelendirmesine olanak tanıyan basit uygulamanın performansını iyileştirin. Aktarılan kod miktarını en aza indirerek JavaScript paketini nasıl optimize edeceğinizi öğrenin.

Uygulama ekran görüntüsü

Örnek uygulamada, her bir kediyi ne kadar sevdiğinizi belirtmek için bir kelime veya emoji seçebilirsiniz. Bir düğmeyi tıkladığınızda uygulama, düğmenin değerini mevcut kedi resminin altında gösterir.

Ölçüm

Optimizasyon eklemeden önce bir web sitesini incelemek her zaman iyi bir fikirdir:

  1. Siteyi önizlemek için Uygulamayı Görüntüle'ye basın. Ardından, Tam ekran tam ekran düğmesine basın.
  2. Geliştirici Araçları'nı açmak için "Kontrol+Üst Karakter+J" (veya Mac'te "Komut+Option+J") tuşlarına basın.
  3. sekmesini tıklayın.
  4. Önbelleği devre dışı bırak onay kutusunu seçin.
  5. Uygulamayı yeniden yükleyin.

Orijinal paket boyutu isteği

Bu uygulama için 80 KB'dan fazla alan kullanılıyor. Paketin bazı bölümlerinin kullanılıp kullanılmadığını öğrenme zamanı geldi:

  1. Komut menüsünü açmak için Control+Shift+P (veya Mac'te Command+Shift+P) tuşlarına basın. Komut Menüsü

  2. Kapsam sekmesini görüntülemek için Show Coverage tuşuna basın ve Enter tuşuna basın.

  3. Kapsam yakalarken uygulamayı yeniden yüklemek için Kapsam sekmesinde Yeniden yükle'yi tıklayın.

    Uygulamayı kod kapsamıyla yeniden yükleme

  4. Ana paket için kullanılan kod miktarına ve yüklenen kod miktarına göz atın:

    Paketin kod kapsamı

Paketin yarısından fazlası (44 KB) bile kullanılmıyor. Bunun nedeni, uygulamanın eski tarayıcılarda çalışmasını sağlamak için içindeki kodun büyük bir kısmının polyfill'lerden oluşmasıdır.

@babel/preset-env kullanma

JavaScript dilinin söz dizimi, ECMAScript veya ECMA-262 olarak bilinen bir standarda uygundur. Spesifikasyonun her yıl yayınlanan yeni sürümleri, teklif sürecini geçen yeni özellikleri içerir. Her büyük tarayıcı, bu özellikleri destekleme konusunda her zaman farklı bir aşamadadır.

Uygulamada aşağıdaki ES2015 özellikleri kullanılmaktadır:

Aşağıdaki ES2017 özelliği de kullanılır:

Tüm bunların nasıl kullanıldığını görmek için src/index.js adresindeki kaynak kodu inceleyebilirsiniz.

Bu özelliklerin tümü Chrome'un en son sürümünde desteklenir. Peki bu özellikleri desteklemeyen diğer tarayıcılar ne olacak? Uygulamaya dahil edilen Babel, daha yeni söz dizimi içeren kodu eski tarayıcıların ve ortamların anlayabileceği koda derlemek için kullanılan en popüler kitaplıktır. Bunu iki şekilde yapar:

  • Çoklu doldurma, daha yeni ES2015+ işlevlerini taklit etmek için eklenir. Böylece, API'leri tarayıcı tarafından desteklenmese bile kullanılabilir. Array.includes yönteminin bir çoklu dolgu örneğini burada bulabilirsiniz.
  • Eklentiler, ES2015 kodunu (veya sonraki sürümleri) eski ES5 söz dizimine dönüştürmek için kullanılır. Bunlar söz dizimiyle ilgili değişiklikler (ok işlevleri gibi) olduğundan çoklu dolgularla emüle edilemez.

Hangi Babel kitaplıklarının dahil edildiğini görmek için package.json dosyasına bakın:

"dependencies": {
  "@babel/polyfill": "^7.0.0"
},
"devDependencies": {
  //...
  "babel-loader": "^8.0.2",
  "@babel/core": "^7.1.0",
  "@babel/preset-env": "^7.1.0",
  //...
}
  • @babel/core, temel Babel derleyicisidir. Böylece, tüm Babel yapılandırmaları projenin kökündeki bir .babelrc içinde tanımlanır.
  • babel-loader, Babel'i webpack derleme sürecine dahil eder.

Şimdi babel-loader kuralının nasıl dahil edildiğini görmek için webpack.config.js öğesine bakın:

module: {
  rules: [
    //...
    {
      test: /\.js$/,
      exclude: /node_modules/,
      loader: "babel-loader"
    }
  ]
},
  • @babel/polyfill, yeni ECMAScript özellikleri için gerekli tüm çoklu dolguları sağlar. Böylece bu özellikler, yeni ECMAScript özelliklerinin desteklenmediği ortamlarda çalışabilir. Zaten src/index.js.'ün en üstüne içe aktarılmış
import "./style.css";
import "@babel/polyfill";
  • @babel/preset-env, hedef olarak seçilen tarayıcılar veya ortamlar için hangi dönüşümlerin ve çoklu dolguların gerekli olduğunu tanımlar.

Nasıl eklendiğini görmek için Babel yapılandırmaları dosyasına (.babelrc) göz atın:

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": "last 2 versions"
      }
    ]
  ]
}

Bu, Babel ve webpack kurulumudur. Webpack'ten farklı bir modül paketleyici kullanıyorsanız Babel'i uygulamanıza nasıl ekleyeceğinizi öğrenin.

.babelrc içindeki targets özelliği, hangi tarayıcıların hedeflendiğini belirtir. @babel/preset-env, browserslist ile entegre olduğundan bu alanda kullanılabilecek uyumlu sorguların tam listesini browserlist belgelerinde bulabilirsiniz.

"last 2 versions" değeri, uygulamadaki kodu her tarayıcının son iki sürümü için derleyip dönüştürür.

Hata ayıklama

Tarayıcının tüm Babel hedeflerinin yanı sıra dahil edilen tüm dönüşümleri ve çoklu dolguları tam olarak görmek için .babelrc: dosyasına bir debug alanı ekleyin

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": "last 2 versions",
        "debug": true
      }
    ]
  ]
}
  • Araçlar'ı tıklayın.
  • Logs'u (Günlükler) tıklayın.

Uygulamayı yeniden yükleyin ve düzenleyicinin alt kısmındaki Glitch durum günlüklerine göz atın.

Hedeflenen tarayıcılar

Babel, kodun derlendiği tüm hedef ortamlar da dahil olmak üzere derleme süreciyle ilgili çeşitli ayrıntıları konsola kaydeder.

Hedeflenen tarayıcılar

Internet Explorer gibi kullanımdan kaldırılan tarayıcıların bu listeye nasıl dahil edildiğine dikkat edin. Desteklenmeyen tarayıcılara yeni özellikler eklenmediği ve Babel bunlar için belirli söz dizimlerini derlemeye devam ettiği için bu bir sorundur. Kullanıcılar sitenize erişmek için bu tarayıcıyı kullanmıyorsa bu durum, paketinizin boyutunu gereksiz yere artırır.

Babel, kullanılan dönüşüm eklentilerinin listesini de günlüğe kaydeder:

Kullanılan eklentilerin listesi

Oldukça uzun bir liste. Bunlar, Babel'in hedeflenen tüm tarayıcılarda ES2015 ve sonraki sürümlerin söz dizimlerini eski söz dizimlerine dönüştürmek için kullanması gereken tüm eklentilerdir.

Ancak Babel, kullanılan belirli polyfill'leri göstermez:

Eklenti eklenmedi

Bunun nedeni, @babel/polyfill dosyasının tamamının doğrudan içe aktarılmasıdır.

Polifill'leri tek tek yükleme

Varsayılan olarak Babel, @babel/polyfill bir dosyaya aktarıldığında eksiksiz bir ES2015+ ortamı için gereken her polyfill'i içerir. Hedef tarayıcılar için gereken belirli çoklu dolguları içe aktarmak üzere yapılandırmaya useBuiltIns: 'entry' ekleyin.

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": "last 2 versions",
        "debug": true
        "useBuiltIns": "entry"
      }
    ]
  ]
}

Uygulamayı yeniden yükleyin. Artık dahil edilen tüm polyfill'leri görebilirsiniz:

İçe aktarılan polyfill'lerin listesi

Artık yalnızca "last 2 versions" için gerekli olan polyfill'ler dahil edilmiş olsa da bu liste hâlâ çok uzun. Bunun nedeni, her yeni özellik için hedef tarayıcılarda gereken polyfill'lerin hâlâ dahil edilmesidir. Yalnızca kodda kullanılan özellikler için gerekenleri içerecek şekilde özelliğin değerini usage olarak değiştirin.

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": "last 2 versions",
        "debug": true,
        "useBuiltIns": "entry"
        "useBuiltIns": "usage"
      }
    ]
  ]
}

Bu sayede, gerektiğinde çoklu dolgular otomatik olarak dahil edilir. Bu durumda, src/index.js.'teki @babel/polyfill içe aktarma işlemini kaldırabilirsiniz.

import "./style.css";
import "@babel/polyfill";

Artık yalnızca uygulama için gereken polyfill'ler dahil edilmiştir.

Polyfill'lerin listesi otomatik olarak dahil edilir

Uygulama paketi boyutu önemli ölçüde azaltılır.

Paket boyutu 30,1 KB'ya düşürüldü

Desteklenen tarayıcıların listesini daraltma

Dahil edilen tarayıcı hedeflerinin sayısı hâlâ oldukça fazla ve kullanıcıların çoğu Internet Explorer gibi kullanımdan kaldırılmış tarayıcıları kullanmıyor. Yapılandırmaları aşağıdaki şekilde güncelleyin:

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": "last 2 versions",
        "targets": [">0.25%", "not ie 11"],
        "debug": true,
        "useBuiltIns": "usage",
      }
    ]
  ]
}

Getirilen paketin ayrıntılarına göz atın.

30,0 KB'lık paket boyutu

Uygulama çok küçük olduğundan bu değişikliklerin çok fazla etkisi olmayacaktır. Bununla birlikte, kullanıcılarınızın kullanmadığından emin olduğunuz belirli tarayıcıları hariç tutmanın yanı sıra bir tarayıcı pazar payı yüzdesi (">0.25%" gibi) kullanmanız önerilir. Bu konu hakkında daha fazla bilgi edinmek için James Kyle tarafından yazılan "Zararlı olduğu kabul edilen son 2 sürüm" makalesine göz atın.

<script type="module">

Daha iyiye gitmek için daha fazla çalışma yapılabilir. Kullanılmayan polyfill'lerin bir kısmı kaldırılmış olsa da bazı tarayıcılar için gerekli olmayan ve sevk edilmeye devam edenler var. Modüller kullanılarak gereksiz polyfill'ler kullanılmadan daha yeni söz dizimi yazılabilir ve doğrudan tarayıcılara gönderilebilir.

JavaScript modülleri, tüm büyük tarayıcılarda desteklenen nispeten yeni bir özelliktir. Diğer modüllerden içe ve dışa aktarma yapan komut dosyalarını tanımlamak için type="module" özelliği kullanılarak modüller oluşturulabilir. Örneğin:

// math.mjs
export const add = (x, y) => x + y;

<!-- index.html -->
<script type="module">
  import { add } from './math.mjs';

  add(5, 2); // 7
</script>

Daha yeni ECMAScript özelliklerinin çoğu, JavaScript modüllerini destekleyen ortamlarda zaten desteklenmektedir (Babel'e ihtiyaç duymaz.) Bu, Babel yapılandırmasının, uygulamanızın tarayıcıya iki farklı sürümünü gönderecek şekilde değiştirilebileceği anlamına gelir:

  • Modülleri destekleyen daha yeni tarayıcılarda çalışacak ve büyük ölçüde dönüştürülmemiş ancak daha küçük dosya boyutuna sahip bir modül içeren bir sürüm
  • Herhangi bir eski tarayıcıda çalışacak daha büyük, derlenmiş bir komut dosyası içeren bir sürüm

Babel ile ES modüllerini kullanma

Uygulamanın iki sürümü için ayrı @babel/preset-env ayarlarına sahip olmak istiyorsanız .babelrc dosyasını kaldırın. Babel ayarları, uygulamanın her sürümü için iki farklı derleme biçimi belirterek webpack yapılandırmasına eklenebilir.

Eski komut dosyası için webpack.config.js alanına bir yapılandırma ekleyerek başlayın:

const legacyConfig = {
  entry,
  output: {
    path: path.resolve(__dirname, "public"),
    filename: "[name].bundle.js"
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: "babel-loader",
        options: {
          presets: [
            ["@babel/preset-env", {
              useBuiltIns: "usage",
              targets: {
                esmodules: false
              }
            }]
          ]
        }
      },
      cssRule
    ]
  },
  plugins
}

"@babel/preset-env" için targets değerini kullanmak yerine, false değerine sahip esmodules değerinin kullanıldığına dikkat edin. Yani Babel, henüz ES modüllerini desteklemeyen tüm tarayıcıları hedeflemek için gerekli tüm dönüştürme işlemlerini ve polyfill'leri içerir.

webpack.config.js dosyasının başına entry, cssRule ve corePlugins nesneleri ekleyin. Bunların tümü hem modül hem de tarayıcıya sunulan eski komut dosyaları arasında paylaşılır.

const entry = {
  main: "./src"
};

const cssRule = {
  test: /\.css$/,
  use: ExtractTextPlugin.extract({
    fallback: "style-loader",
    use: "css-loader"
  })
};

const plugins = [
  new ExtractTextPlugin({filename: "[name].css", allChunks: true}),
  new HtmlWebpackPlugin({template: "./src/index.html"})
];

Şimdi de benzer şekilde, aşağıdaki modül komut dosyası için legacyConfig değerinin tanımlandığı bir yapılandırma nesnesi oluşturun:

const moduleConfig = {
  entry,
  output: {
    path: path.resolve(__dirname, "public"),
    filename: "[name].mjs"
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: "babel-loader",
        options: {
          presets: [
            ["@babel/preset-env", {
              useBuiltIns: "usage",
              targets: {
                esmodules: true
              }
            }]
          ]
        }
      },
      cssRule
    ]
  },
  plugins
}

Buradaki temel fark, çıkış dosya adı için .mjs dosya uzantısının kullanılmasıdır. esmodules değeri burada true olarak ayarlanmıştır. Bu, bu modüle gönderilen kodun daha küçük ve daha az derlenmiş bir komut dosyası olduğu anlamına gelir. Kullanılan tüm özellikler modülleri destekleyen tarayıcılarda zaten desteklendiğinden bu örnekte herhangi bir dönüşümden geçmez.

Dosyanın en sonunda, her iki yapılandırmayı da tek bir diziye aktarın.

module.exports = [
  legacyConfig, moduleConfig
];

Artık bu işlem hem destekleyen tarayıcılar için daha küçük bir modül hem de eski tarayıcılar için daha büyük bir derlenmiş komut dosyası oluşturuyor.

Modülleri destekleyen tarayıcılar, nomodule özelliğine sahip komut dosyalarını yoksayar. Buna karşılık, modülleri desteklemeyen tarayıcılar type="module" içeren komut dosyası öğelerini yoksayar. Yani derlenmiş bir yedeğin yanı sıra bir modül de ekleyebilirsiniz. İdeal olarak, uygulamanın iki sürümü de index.html biçiminde olmalıdır:

<script type="module" src="main.mjs"></script>
<script nomodule src="main.bundle.js"></script>

Modülleri destekleyen tarayıcılar main.mjs öğesini getirip yürütür ve main.bundle.js. öğesini yoksayar. Modülleri desteklemeyen tarayıcılar ise bunun tam tersini yapar.

Normal komut dosyalarının aksine, modül komut dosyalarının varsayılan olarak her zaman ertelendiği unutulmamalıdır. Eşdeğer nomodule komut dosyasının da ertelenmesini ve yalnızca ayrıştırma işleminden sonra yürütülmesini istiyorsanız defer özelliğini eklemeniz gerekir:

<script type="module" src="main.mjs"></script>
<script nomodule src="main.bundle.js" defer></script>

Burada yapılması gereken son şey, module ve nomodule özelliklerini sırasıyla modüle ve eski komut dosyasına eklemektir. webpack.config.js'in en üstündeki ScriptExtHtmlWebpackPlugin dosyasını içe aktarın:

const path = require("path");

const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ScriptExtHtmlWebpackPlugin = require("script-ext-html-webpack-plugin");

Ardından, yapılandırmalardaki plugins dizisini bu eklentiyi içerecek şekilde güncelleyin:

const plugins = [
  new ExtractTextPlugin({filename: "[name].css", allChunks: true}),
  new HtmlWebpackPlugin({template: "./src/index.html"}),
  new ScriptExtHtmlWebpackPlugin({
    module: /\.mjs$/,
    custom: [
      {
        test: /\.js$/,
        attribute: 'nomodule',
        value: ''
    },
    ]
  })
];

Bu eklenti ayarları, .mjs komut dosyası öğelerinin tamamı için type="module" özelliğinin yanı sıra tüm .js komut dosyası modülleri için de nomodule özelliği ekler.

HTML dokümanında modül yayınlama

Yapılması gereken son işlem, hem eski hem de modern komut dosyası öğelerinin çıktısını HTML dosyasına kaydetmektir. Maalesef nihai HTML dosyasını oluşturan HTMLWebpackPlugin eklentisi, hem module hem de nomodule komut dosyalarının çıktısını şu anda desteklemiyor. Bu sorunu çözmek için oluşturulmuş geçici çözümler ve ayrı eklentiler (ör. BabelMultiTargetPlugin ve HTMLWebpackMultiBuildPlugin) mevcut olsa da, bu eğiticide modül komut dosyası öğesini manuel olarak eklemeye yönelik daha basit bir yaklaşım kullanılmıştır.

Dosyanın sonundaki src/index.js dosyasına aşağıdakileri ekleyin:

    ...
    </form>
    <script type="module" src="main.mjs"></script>
  </body>
</html>

Ardından, uygulamayı Chrome'un en son sürümü gibi modülleri destekleyen bir tarayıcıda yükleyin.

Yeni tarayıcılar için ağ üzerinden getirilen 5,2 KB&#39;lık modül

Yalnızca modül getirilir ve modülün büyük oranda aktarılamaması nedeniyle paket boyutu çok daha küçüktür. Diğer komut dosyası öğesi tarayıcı tarafından tamamen yoksayılır.

Uygulamayı eski bir tarayıcıya yüklerseniz yalnızca gerekli tüm polyfill'leri ve dönüştürme işlemlerini içeren daha büyük, derlenmiş komut dosyası getirilir. Chrome'un eski bir sürümünde (38 sürümü) yapılan tüm isteklerin ekran görüntüsünü aşağıda bulabilirsiniz.

Eski tarayıcılar için getirilen 30 KB&#39;lık komut dosyası

Sonuç

Artık hedeflenen tarayıcılar için yalnızca gerekli polyfill'leri sağlamak üzere @babel/preset-env'ü nasıl kullanacağınızı biliyorsunuz. Ayrıca JavaScript modüllerinin, bir uygulamanın iki farklı derlenmiş sürümünü yayınlayarak performansı nasıl daha da artırabileceğini de biliyorsunuz. Bu iki tekniğin de paket boyutunuzu önemli ölçüde nasıl küçültebileceğini anladıktan sonra optimizasyona başlayabilirsiniz.