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 bu basit uygulamanın performansını iyileştirin. Kodun ne kadarının aktarıldığını inceleyerek 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, mevcut kedi resminin altında düğmenin değerini gösterir.

Ölçüm

Herhangi bir 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 "Control+Üst Karakter+J" (veya Mac'te "Command+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'tan fazla dosya kullanılır. Paketin bazı bölümlerinin kullanılıp kullanılmadığını öğrenme zamanı geldi:

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

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

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

    Uygulamayı kod kapsamıyla yeniden yükle

  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) kullanılmaz. 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 hesabını kullanın

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

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

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

Bunların nasıl kullanıldığını görmek için src/index.js kaynak kodunu inceleyebilirsiniz.

Bu özelliklerin tümü Chrome'un son sürümünde desteklenmektedir, peki bunları desteklemeyen diğer tarayıcılar için ne olacak? Uygulamanın içinde yer alan Babel, eski tarayıcıların ve ortamların anlayabileceği şekilde yeni söz dizimi içeren kodları derlemek için kullanılan en popüler kitaplıktır. Bunu iki şekilde yapar:

  • Polyfill'ler, tarayıcı tarafından desteklenmese bile API'lerinin kullanılabilmesi için yeni ES2015+ işlevlerini emüle etmek amacıyla eklenmiştir. Array.includes yönteminin bir polyfill örneğini burada bulabilirsiniz.
  • Eklentiler, ES2015 kodunu (veya üzerini) 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 öğesine 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. Bu şekilde, tüm Babel yapılandırmaları projenin kökünde bir .babelrc içinde tanımlanır.
  • babel-loader, web paketi oluşturma işlemine Babel'ı ekler.

Ş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. Bu öğe zaten src/index.js. sayfasını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 dahil edildiğ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 bir Babel ve web paketi kurulumudur. Webpack'ten farklı bir modül paketleyici kullanıyorsanız uygulamanıza Babel'i nasıl dahil edeceğinizi öğrenin.

.babelrc öğesindeki targets özelliği, hangi tarayıcıların hedeflendiğini tanımlar. @babel/preset-env, tarayıcı listesi ile entegre olur. Böylece, bu alanda kullanılabilecek uyumlu sorguların tam listesini tarayıcı listesi dokümanlarında bulabilirsiniz.

"last 2 versions" değeri, her tarayıcının son iki sürümü için kodu uygulamada aktarı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, derleme işlemi hakkında, kodun derlendiği tüm hedef ortamlar da dahil olmak üzere ç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. Bu bir sorundur çünkü desteklenmeyen tarayıcılara yeni özellikler eklenmez ve Babel bunlar için belirli söz dizimini aktarmaya devam eder. 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

Bu liste oldukça uzun. Bunlar, Babel'in herhangi bir ES2015+ söz dizimini hedeflenen tüm tarayıcılarda eski söz dizimine dönüştürmek için kullanması gereken tüm eklentilerdir.

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

Çoklu dolgu eklenmedi

Bunun nedeni, tüm @babel/polyfill öğesinin doğrudan içe aktarılmasıdır.

Polyfill'leri tek tek yükleme

@babel/polyfill bir dosyaya aktarıldığında Babel, varsayılan olarak eksiksiz bir ES2015+ ortamı için gereken her çoklu dolguyu 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 belirli polyfill'lerin tamamını görebilirsiniz:

İçe aktarılan polyfill'lerin listesi

"last 2 versions" için yalnızca gerekli olan çoklu dolgular artık dahil edilmiş olsa da bu liste hâlâ çok uzun bir liste. Bunun nedeni, her yeni özellik için hedef tarayıcılara yönelik gerekli olan çoklu dolguların yine de dahil edilmesidir. Yalnızca kodda kullanılan özellikler için gerekenleri eklemek üzere ö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, @babel/polyfill içe aktarma işlemini src/index.js. ürününden kaldırabileceğiniz anlamına gelir

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

Artık yalnızca uygulama için gereken gerekli çoklu dolgular dahil edilmiştir.

Çoklu dolgu listesi otomatik olarak dahil edilir

Uygulama paketi boyutu önemli ölçüde küçüldü.

Paket boyutu 30,1 KB'ye 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 paket boyutu

Uygulama çok küçük olduğundan bu değişiklikler arasında pek fark yoktur. Ancak önerilen yaklaşım, bir tarayıcı pazar payı yüzdesi (">0.25%" gibi) kullanmak ve kullanıcılarınızın kullanmadığından emin olduğunuz belirli tarayıcıları hariç tutmaktır. Bu konuda daha fazla bilgi edinmek için James Kyle'ın "Zararlı olarak değerlendirilen son 2 sürüm" makalesine göz atın.

<script type="module">

Daha fazla iyileştirme 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, daha yeni söz dizimi yazılabilir ve gereksiz çoklu dolgular kullanılmadan doğrudan tarayıcılara gönderilebilir.

JavaScript modülleri, başlıca tüm tarayıcılarda desteklenen nispeten yeni bir özelliktir. Modüller, diğer modüllerden içe ve dışa aktarma yapan komut dosyalarını tanımlamak için type="module" özelliği kullanılarak 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>

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

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

ES Modüllerini Babel ile Kullanma

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

webpack.config.js öğesine eski komut dosyası için 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. Bu, Babel'in henüz ES modüllerini desteklemeyen tüm tarayıcıları hedeflemek için gerekli tüm dönüşümleri ve çoklu dolguları içerdiği anlamına gelir.

webpack.config.js dosyasının başına entry, cssRule ve corePlugins nesnelerini 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, legacyConfig öğesinin tanımlandığı, aşağıda modül komut dosyası için 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 doğru olarak ayarlanır. Bu, kullanılan tüm özellikler modülleri destekleyen tarayıcılarda zaten desteklendiğinden, bu örnekte herhangi bir dönüşümden geçmeyen daha küçük ve daha az derlenmiş bir komut dosyası olduğu anlamına gelir.

Dosyanın en sonunda, her iki yapılandırmayı tek bir dizide dışa aktarın.

module.exports = [
  legacyConfig, moduleConfig
];

Böylece 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 aktarılmış komut dosyası oluşturur.

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 modül de ekleyebilirsiniz. İdeal olarak uygulamanın iki sürümü index.html içinde şu şekilde olmalıdır:

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

Modüllerin getirme ve çalıştırma işlemlerini destekleyen tarayıcılar main.mjs öğesini yoksayar main.bundle.js. Modülleri desteklemeyen tarayıcılar tam tersini yapar.

Normal komut dosyalarının aksine, modül komut dosyalarının her zaman varsayılan olarak ertelendiğini belirtmek isteriz. Eşdeğer nomodule komut dosyasının da ertelenmesini ve yalnızca ayrıştırmadan 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 işlem, modüle ve eski komut dosyasına sırasıyla module ve nomodule özelliklerini eklemektir. webpack.config.js öğesinin en üst kısmındaki ScriptExtHtmlWebpackPlugin'i 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");

Şimdi 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 sunum modülleri

Yapılması gereken son işlem, hem eski hem de modern komut dosyası öğelerinin çıktısını HTML dosyasına kaydetmektir. Maalesef HTML dosyasının son halini (HTMLWebpackPlugin) oluşturan eklenti hem modül hem de nomodule komut dosyalarının çıkışını şu anda desteklememektedir. 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 bölümüne aşağıdakini ekleyin:

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

Şimdi 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 5,2 KB modül getirildi

Yalnızca modül getirilir ve modülün büyük ölçüde dönüştürülmemesi nedeniyle paket boyutu çok daha küçüktür. Diğer komut dosyası öğesi, tarayıcı tarafından tamamen yoksayılır.

Uygulamayı daha eski bir tarayıcıya yüklerseniz yalnızca gerekli tüm çoklu dolguların ve dönüşümlerin bulunduğu daha büyük, dönüştürülmüş komut dosyası getirilir. Chrome'un eski bir sürümünde (38 sürümü) gönderilen tüm isteklerin ekran görüntüsünü burada bulabilirsiniz.

Eski tarayıcılar için 30 KB komut dosyası getirildi

Sonuç

Artık yalnızca hedeflenen tarayıcılar için gerekli olan çoklu dolguları sağlamak üzere @babel/preset-env özelliğinin nasıl kullanılacağını biliyorsunuz. Ayrıca, JavaScript modüllerinin bir uygulamanın kopyalanmış iki farklı sürümünü göndererek performansı nasıl daha fazla iyileştirebileceğini de biliyorsunuz. Bu iki tekniğin de paketinizin boyutunu nasıl önemli ölçüde küçültebileceğini iyice anladığınızda, yola devam edip optimizasyon yapabilirsiniz.