Uzun süreli önbelleğe alma özelliğinden yararlanın

Webpack, öğe önbelleğe alma işlemine nasıl yardımcı olur?

Uygulama yükleme süresini iyileştiren uygulama boyutunu optimize etmenin ardından yapılması gereken bir sonraki işlem önbelleğe alma işlemidir. Uygulamanın bazı bölümlerini istemcide tutmak ve her seferinde yeniden indirmekten kaçınmak için kullanın.

Paket sürüm oluşturma ve önbellek başlıklarını kullanma

Önbelleğe alma işleminde yaygın olarak kullanılan yaklaşım şudur:

  1. Tarayıcıyı bir dosyayı çok uzun süre (ör. bir yıl) önbelleğe almasını söyleyebilirsiniz:

    # Server header
    Cache-Control: max-age=31536000
    

    Cache-Control işlevinin ne olduğunu bilmiyorsanız Jake Archibald'ın önbelleğe almayla ilgili en iyi uygulamalar hakkındaki mükemmel makalesine göz atın.

  2. ve dosya değiştiğinde yeniden indirme işlemini zorlamak için dosyayı yeniden adlandırın:

    <!-- Before the change -->
    <script src="./index-v15.js"></script>
    
    <!-- After the change -->
    <script src="./index-v16.js"></script>
    

Bu yaklaşım, tarayıcıya JS dosyasını indirmesini, önbelleğe almasını ve önbelleğe alınmış kopyayı kullanmasını söyler. Tarayıcı yalnızca dosya adı değişirse (veya bir yıl geçerse) ağa bağlanır.

Webpack'te de aynı işlemi yaparsınız ancak sürüm numarası yerine dosya karmasını belirtirsiniz. Karma oluşturma işlemini dosya adına dahil etmek için [chunkhash] simgesini kullanın:

// webpack.config.js
module.exports = {
  entry: './index.js',
  output: {
    filename: 'bundle.[chunkhash].js' // → bundle.8e0d62a03.js
  }
};

Dosya adını istemciye göndermeniz gerekiyorsa HtmlWebpackPlugin veya WebpackManifestPlugin değerini kullanın.

HtmlWebpackPlugin basit ancak daha az esnek bir yaklaşımdır. Bu eklenti, derleme sırasında tüm derlenmiş kaynakları içeren bir HTML dosyası oluşturur. Sunucu mantığınız karmaşık değilse aşağıdakiler yeterli olacaktır:

<!-- index.html -->
<!DOCTYPE html>
<!-- ... -->
<script src="bundle.8e0d62a03.js"></script>

WebpackManifestPlugin, karmaşık bir sunucu bölümünüz varsa faydalı olan daha esnek bir yaklaşımdır. Derleme sırasında, karma oluşturma içermeyen dosya adları ile karma oluşturma içeren dosya adları arasında eşleme içeren bir JSON dosyası oluşturur. Hangi dosyayla çalışacağınızı öğrenmek için sunucuda bu JSON'u kullanın:

// manifest.json
{
  "bundle.js": "bundle.8e0d62a03.js"
}

Daha fazla bilgi

Bağımlılıkları ve çalışma zamanını ayrı bir dosyaya ayıklayın

Bağımlılıklar

Uygulama bağımlılıkları, gerçek uygulama kodundan daha seyrek olarak değişir. Bunları ayrı bir dosyaya taşırsanız tarayıcı bunları ayrı olarak önbelleğe alabilir ve uygulama kodu her değiştiğinde bunları yeniden indirmez.

Bağımlılıkları ayrı bir parçaya ayıklamak için üç adım uygulayın:

  1. Çıkış dosyası adını [name].[chunkname].js ile değiştirin:

    // webpack.config.js
    module.exports = {
      output: {
        // Before
        filename: 'bundle.[chunkhash].js',
        // After
        filename: '[name].[chunkhash].js'
      }
    };
    

    Webpack, uygulamayı derlediğinde [name] yerine bir parçanın adını kullanır. [name] bölümünü eklemezsek parçaları karmalarına göre ayırt etmemiz gerekir. Bu da oldukça zordur.

  2. entry alanını bir nesneye dönüştürün:

    // webpack.config.js
    module.exports = {
      // Before
      entry: './index.js',
      // After
      entry: {
        main: './index.js'
      }
    };
    

    Bu snippet'te "main" bir parçanın adıdır. Bu ad, 1. adımdaki [name] yerine kullanılır.

    Bu adımları uygulamamış gibi, uygulamayı derlerseniz bu parça uygulama kodunun tamamını içerir. Ancak bu durum bir saniye içinde değişecek.

  3. Webpack 4'te optimization.splitChunks.chunks: 'all' seçeneğini webpack yapılandırmanıza ekleyin:

    // webpack.config.js (for webpack 4)
    module.exports = {
      optimization: {
        splitChunks: {
          chunks: 'all'
        }
      }
    };
    

    Bu seçenek, akıllı kod bölme özelliğini etkinleştirir. Bu sayede webpack, 30 KB'tan büyükse (küçültme ve gzip'ten önce) tedarikçi kodunu ayıklayabilir. Ayrıca ortak kodu da ayıklayabilir.Bu, derlemeniz birden fazla paket oluşturuyorsa (ör. uygulamanızı rotalara ayırdıysanız) kullanışlıdır.

    Webpack 3'te CommonsChunkPlugin öğesini ekleyin:

    // webpack.config.js (for webpack 3)
    module.exports = {
      plugins: [
        new webpack.optimize.CommonsChunkPlugin({
        // A name of the chunk that will include the dependencies.
        // This name is substituted in place of [name] from step 1
        name: 'vendor',
    
        // A function that determines which modules to include into this chunk
        minChunks: module => module.context && module.context.includes('node_modules'),
        })
      ]
    };
    

    Bu eklenti, yolları node_modules içeren tüm modülleri alıp vendor.[chunkhash].js adlı ayrı bir dosyaya taşır.

Bu değişikliklerden sonra her derleme bir yerine iki dosya oluşturur: main.[chunkhash].js ve vendor.[chunkhash].js (webpack 4 için vendors~main.[chunkhash].js). Webpack 4'te, bağımlılık sayısı az olduğunda tedarikçi paketi oluşturulmayabilir. Bu durum normaldir:

$ webpack
Hash: ac01483e8fec1fa70676
Version: webpack 3.8.1
Time: 3816ms
                        Asset      Size  Chunks             Chunk Names
 ./main.00bab6fd3100008a42b0.js   82 kB       0  [emitted]  main
./vendor.d9e134771799ecdf9483.js  47 kB       1  [emitted]  vendor

Tarayıcı bu dosyaları ayrı olarak önbelleğe alır ve yalnızca değişen kodu yeniden indirir.

Webpack çalışma zamanı kodu

Maalesef yalnızca tedarikçi kodunu ayıklamak yeterli değildir. Uygulama kodunda bir şeyi değiştirmeye çalışırsanız:

// index.js
…
…

// E.g. add this:
console.log('Wat');

vendor karmasının da değiştiğini fark edeceksiniz:

                           Asset   Size  Chunks             Chunk Names
./vendor.d9e134771799ecdf9483.js  47 kB       1  [emitted]  vendor

                            Asset   Size  Chunks             Chunk Names
./vendor.e6ea4504d61a1cc1c60b.js  47 kB       1  [emitted]  vendor

Bunun nedeni, webpack paketinde modüllerin kodunun yanı sıra çalışma zamanı (modül yürütmeyi yöneten küçük bir kod parçası) bulunmasıdır. Kodu birden fazla dosyaya böldüğünüzde bu kod parçası, blok kimlikleri ile ilgili dosyalar arasında bir eşleme içermeye başlar:

// vendor.e6ea4504d61a1cc1c60b.js
script.src = __webpack_require__.p + chunkId + "." + {
    "0": "2f2269c7f0a55a5c1871"
}[chunkId] + ".js";

Webpack, bu çalışma zamanını son oluşturulan parçaya ekler. Bizim durumumuzda bu parça vendor olarak adlandırılır. Her bir parça değiştiğinde bu kod parçası da değişir ve vendor parçasının tamamı değişir.

Bu sorunu çözmek için çalışma zamanını ayrı bir dosyaya taşıyalım. Webpack 4'te bu,optimization.runtimeChunk seçeneği etkinleştirilerek yapılır:

// webpack.config.js (for webpack 4)
module.exports = {
  optimization: {
    runtimeChunk: true
  }
};

Webpack 3'te bunu yapmak için CommonsChunkPlugin ile ek bir boş parça oluşturun:

// webpack.config.js (for webpack 3)
module.exports = {
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      minChunks: module => module.context && module.context.includes('node_modules')
    }),
    // This plugin must come after the vendor one (because webpack
    // includes runtime into the last chunk)
    new webpack.optimize.CommonsChunkPlugin({
      name: 'runtime',
      // minChunks: Infinity means that no app modules
      // will be included into this chunk
      minChunks: Infinity
    })
  ]
};

Bu değişikliklerden sonra her derleme üç dosya oluşturur:

$ webpack
Hash: ac01483e8fec1fa70676
Version: webpack 3.8.1
Time: 3816ms
                            Asset     Size  Chunks             Chunk Names
   ./main.00bab6fd3100008a42b0.js    82 kB       0  [emitted]  main
 ./vendor.26886caf15818fa82dfa.js    46 kB       1  [emitted]  vendor
./runtime.79f17c27b335abc7aaf4.js  1.45 kB       3  [emitted]  runtime

Bunları ters sırayla index.html içine ekleyin ve işlemi tamamlayın:

<!-- index.html -->
<script src="./runtime.79f17c27b335abc7aaf4.js"></script>
<script src="./vendor.26886caf15818fa82dfa.js"></script>
<script src="./main.00bab6fd3100008a42b0.js"></script>

Daha fazla bilgi

Ek bir HTTP isteği tasarrufu için satır içi webpack çalışma zamanı

Daha da iyileştirmek için webpack çalışma zamanını HTML yanıtına yerleştirmeyi deneyin. Yani şunun yerine:

<!-- index.html -->
<script src="./runtime.79f17c27b335abc7aaf4.js"></script>

şunu yapın:

<!-- index.html -->
<script>
!function(e){function n(r){if(t[r])return t[r].exports;…}} ([]);
</script>

Çalışma zamanı küçüktür ve satır içi olarak eklemek bir HTTP isteği tasarruf etmenize yardımcı olur (HTTP/1 için oldukça önemlidir; HTTP/2 için daha az önemlidir ancak yine de bir etkisi olabilir).

Bunu nasıl yapacağınız aşağıda açıklanmıştır.

HtmlWebpackPlugin ile HTML oluşturuyorsanız

HTML dosyası oluşturmak için HtmlWebpackPlugin'ı kullanıyorsanız ihtiyacınız olan tek şey InlineSourcePlugin'dır:

const HtmlWebpackPlugin = require('html-webpack-plugin');
const InlineSourcePlugin = require('html-webpack-inline-source-plugin');

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      inlineSource: 'runtime~.+\\.js',
    }),
    new InlineSourcePlugin()
  ]
};

Özel sunucu mantığı kullanarak HTML oluşturuyorsanız

Webpack 4 ile:

  1. Çalışma zamanı parçasının oluşturulan adını öğrenmek için WebpackManifestPlugin simgesini ekleyin:

    // webpack.config.js (for webpack 4)
    const ManifestPlugin = require('webpack-manifest-plugin');
    
    module.exports = {
      plugins: [
        new ManifestPlugin()
      ]
    };
    

    Bu eklentinin kullanıldığı bir derleme, aşağıdaki gibi bir dosya oluşturur:

    // manifest.json
    {
      "runtime~main.js": "runtime~main.8e0d62a03.js"
    }
    
  2. Çalışma zamanı parçasının içeriğini uygun bir şekilde satır içi olarak yerleştirin. Örneğin, Node.js ve Express ile:

    // server.js
    const fs = require('fs');
    const manifest = require('./manifest.json');
    const runtimeContent = fs.readFileSync(manifest['runtime~main.js'], 'utf-8');
    
    app.get('/', (req, res) => {
      res.send(`
        …
        <script>${runtimeContent}</script>
        …
      `);
    });
    

Veya webpack 3 ile:

  1. filename değerini belirterek çalışma zamanı adını statik hale getirin:

    module.exports = {
      plugins: [
        new webpack.optimize.CommonsChunkPlugin({
          name: 'runtime',
          minChunks: Infinity,
          filename: 'runtime.js'
        })
      ]
    };
    
  2. runtime.js içeriğini uygun bir şekilde satır içine alın. Örneğin, Node.js ve Express ile:

    // server.js
    const fs = require('fs');
    const runtimeContent = fs.readFileSync('./runtime.js', 'utf-8');
    
    app.get('/', (req, res) => {
      res.send(`
        …
        <script>${runtimeContent}</script>
        …
      `);
    });
    

Şu anda ihtiyacınız olmayan kodu yavaş yükleme

Bazen bir sayfanın daha önemli ve daha az önemli bölümleri vardır:

  • YouTube'da bir video sayfasını yüklediğinizde yorumlardan çok videoya önem verirsiniz. Burada video, yorumlardan daha önemlidir.
  • Bir haber sitesinde bir makaleyi açtığınızda reklamlardan çok makalenin metnini önemsersiniz. Burada metin, reklamlardan daha önemlidir.

Bu gibi durumlarda, önce yalnızca en önemli öğeleri indirip kalan bölümleri daha sonra yavaşça yükleyerek ilk yükleme performansını iyileştirin. Bunun için import() işlevini ve kod bölme özelliğini kullanın:

// videoPlayer.js
export function renderVideoPlayer() { … }

// comments.js
export function renderComments() { … }

// index.js
import {renderVideoPlayer} from './videoPlayer';
renderVideoPlayer();

// …Custom event listener
onShowCommentsClick(() => {
  import('./comments').then((comments) => {
    comments.renderComments();
  });
});

import(), belirli bir modülü dinamik olarak yüklemek istediğinizi belirtir. Webpack, import('./module.js') değerini gördüğünde bu modülü ayrı bir parçaya taşır:

$ webpack
Hash: 39b2a53cb4e73f0dc5b2
Version: webpack 3.8.1
Time: 4273ms
                            Asset     Size  Chunks             Chunk Names
      ./0.8ecaf182f5c85b7a8199.js  22.5 kB       0  [emitted]
   ./main.f7e53d8e13e9a2745d6d.js    60 kB       1  [emitted]  main
 ./vendor.4f14b6326a80f4752a98.js    46 kB       2  [emitted]  vendor
./runtime.79f17c27b335abc7aaf4.js  1.45 kB       3  [emitted]  runtime

ve yalnızca yürütme import() işlevine ulaştığında indirir.

Bu, main paketini küçülterek ilk yükleme süresini iyileştirir. Daha da önemlisi, önbelleğe alma işlemini iyileştirir. Ana parçadaki kodu değiştirirseniz yorumlar parçası etkilenmez.

Daha fazla bilgi

Kodu rotalara ve sayfalara bölme

Uygulamanızda birden fazla rota veya sayfa varsa ancak kod içeren tek bir JS dosyası (tek bir main parçası) varsa her istek için fazladan bayt yayınlamanız muhtemeldir. Örneğin, bir kullanıcı sitenizin ana sayfasını ziyaret ettiğinde:

Web&#39;in Temellerini ana sayfası

farklı bir sayfada bulunan bir makaleyi oluşturmak için kodu yüklemeleri gerekmez ancak yüklerler. Ayrıca, kullanıcı her zaman yalnızca ana sayfayı ziyaret ediyorsa ve makale kodunda değişiklik yaparsanız webpack paketin tamamını geçersiz kılar ve kullanıcının uygulamanın tamamını yeniden indirmesi gerekir.

Uygulamayı sayfalara (veya tek sayfalı bir uygulamaysa rotalara) bölersek kullanıcı yalnızca alakalı kodu indirir. Ayrıca tarayıcı, uygulama kodunu daha iyi önbelleğe alır: Ana sayfa kodunu değiştirirseniz webpack yalnızca ilgili parçayı geçersiz kılar.

Tek sayfalık uygulamalar için

Tek sayfalık uygulamaları rotalara göre bölmek için import() kullanın ("Şu anda ihtiyacınız olmayan kodu yavaş yükleme" bölümüne bakın). Kullandığınız çerçevede bu soruna yönelik mevcut bir çözüm olabilir:

Geleneksel çok sayfalı uygulamalar için

Geleneksel uygulamaları sayfalara bölmek için webpack'in giriş noktalarını kullanın. Uygulamanızda üç tür sayfa varsa (ana sayfa, makale sayfası ve kullanıcı hesabı sayfası) üç giriş olmalıdır:

// webpack.config.js
module.exports = {
  entry: {
    home: './src/Home/index.js',
    article: './src/Article/index.js',
    profile: './src/Profile/index.js'
  }
};

Webpack, her giriş dosyası için ayrı bir bağımlılık ağacı oluşturur ve yalnızca söz konusu giriş tarafından kullanılan modülleri içeren bir grup oluşturur:

$ webpack
Hash: 318d7b8490a7382bf23b
Version: webpack 3.8.1
Time: 4273ms
                            Asset     Size  Chunks             Chunk Names
      ./0.8ecaf182f5c85b7a8199.js  22.5 kB       0  [emitted]
   ./home.91b9ed27366fe7e33d6a.js    18 kB       1  [emitted]  home
./article.87a128755b16ac3294fd.js    32 kB       2  [emitted]  article
./profile.de945dc02685f6166781.js    24 kB       3  [emitted]  profile
 ./vendor.4f14b6326a80f4752a98.js    46 kB       4  [emitted]  vendor
./runtime.318d7b8490a7382bf23b.js  1.45 kB       5  [emitted]  runtime

Dolayısıyla, yalnızca makale sayfasında Lodash kullanılıyorsa home ve profile paketleri bu kitaplığı içermez. Bu durumda, kullanıcının ana sayfayı ziyaret ederken bu kitaplığı indirmesi gerekmez.

Ayrı bağımlılık ağaçları da bazı dezavantajlara sahiptir. İki giriş noktası Lodash kullanıyorsa ve bağımlılıklarınızı bir tedarikçi paketine taşımadıysanız her iki giriş noktası da Lodash'ın bir kopyasını içerir. Bu sorunu çözmek için webpack 4'te optimization.splitChunks.chunks: 'all' seçeneğini webpack yapılandırmanıza ekleyin:

// webpack.config.js (for webpack 4)
module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all'
    }
  }
};

Bu seçenek, akıllı kod bölme özelliğini etkinleştirir. Bu seçenekle webpack, ortak kodu otomatik olarak arar ve ayrı dosyalara ayırır.

Alternatif olarak, webpack 3'te CommonsChunkPlugin simgesini kullanın. Bu simge,ortak bağımlılıkları yeni bir dosyaya taşır:

module.exports = {
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: 'common',
      minChunks: 2    // 2 is the default value
    })
  ]
};

En iyi değeri bulmak için minChunks değeriyle oynayabilirsiniz. Genellikle bu değeri küçük tutmak istersiniz ancak parça sayısı artarsa değeri artırabilirsiniz. Örneğin, 3 parça için minChunks 2 olabilir ancak 30 parça için 8 olabilir. Çünkü minChunks değerini 2 olarak tutarsanız ortak dosyaya çok fazla modül eklenir ve dosya çok fazla şişirilir.

Daha fazla bilgi

Modül kimliklerini daha kararlı hale getirme

Webpack, kodu oluştururken her modüle bir kimlik atar. Daha sonra bu kimlikler, paket içindeki require()'lerde kullanılır. Kimlikleri genellikle derleme çıkışında modül yollarından hemen önce görürsünüz:

$ webpack
Hash: df3474e4f76528e3bbc9
Version: webpack 3.8.1
Time: 2150ms
                           Asset      Size  Chunks             Chunk Names
      ./0.8ecaf182f5c85b7a8199.js  22.5 kB       0  [emitted]
   ./main.4e50a16675574df6a9e9.js    60 kB       1  [emitted]  main
 ./vendor.26886caf15818fa82dfa.js    46 kB       2  [emitted]  vendor
./runtime.79f17c27b335abc7aaf4.js  1.45 kB       3  [emitted]  runtime

↓ Burada

[0] ./index.js 29 kB {1} [built]
[2] (webpack)/buildin/global.js 488 bytes {2} [built]
[3] (webpack)/buildin/module.js 495 bytes {2} [built]
[4] ./comments.js 58 kB {0} [built]
[5] ./ads.js 74 kB {1} [built]
+ 1 hidden module

Varsayılan olarak kimlikler bir sayaç kullanılarak hesaplanır (ör.ilk modülün kimliği 0, ikinci modülün kimliği 1 vb.). Bununla ilgili sorun, yeni bir modül eklediğinizde modül listesinin ortasında görünmesi ve sonraki tüm modüllerin kimliklerini değiştirmesidir:

$ webpack
Hash: df3474e4f76528e3bbc9
Version: webpack 3.8.1
Time: 2150ms
                           Asset      Size  Chunks             Chunk Names
      ./0.5c82c0f337fcb22672b5.js    22 kB       0  [emitted]
   ./main.0c8b617dfc40c2827ae3.js    82 kB       1  [emitted]  main
 ./vendor.26886caf15818fa82dfa.js    46 kB       2  [emitted]  vendor
./runtime.79f17c27b335abc7aaf4.js  1.45 kB       3  [emitted]  runtime
   [0] ./index.js 29 kB {1} [built]
   [2] (webpack)/buildin/global.js 488 bytes {2} [built]
   [3] (webpack)/buildin/module.js 495 bytes {2} [built]

↓ Yeni bir modül ekledik…

[4] ./webPlayer.js 24 kB {1} [built]

↓ Sonuç olarak ne oldu? comments.js artık 4 yerine 5 kimliğine sahip

[5] ./comments.js 58 kB {0} [built]

ads.js artık 5 yerine 6 kimliğine sahip

[6] ./ads.js 74 kB {1} [built]
       + 1 hidden module

Bu işlem, gerçek kodları değişmemiş olsa bile değiştirilmiş kimliklere sahip modülleri içeren veya bu modüllere bağlı olan tüm parçaları geçersiz kılar. Bizim durumumuzda, 0 parçası (comments.js içeren parça) ve main parçası (diğer uygulama kodunun bulunduğu parça) geçersiz kılındı. Oysa yalnızca main parçasının geçersiz kılınması gerekirdi.

Bu sorunu çözmek için HashedModuleIdsPlugin kullanarak modül kimliklerinin hesaplanma şeklini değiştirin. Sayaç tabanlı kimlikleri modül yollarının karmalarıyla değiştirir:

$ webpack
Hash: df3474e4f76528e3bbc9
Version: webpack 3.8.1
Time: 2150ms
                           Asset      Size  Chunks             Chunk Names
      ./0.6168aaac8461862eab7a.js  22.5 kB       0  [emitted]
   ./main.a2e49a279552980e3b91.js    60 kB       1  [emitted]  main
 ./vendor.ff9f7ea865884e6a84c8.js    46 kB       2  [emitted]  vendor
./runtime.25f5d0204e4f77fa57a1.js  1.45 kB       3  [emitted]  runtime

↓ Burada

[3IRH] ./index.js 29 kB {1} [built]
[DuR2] (webpack)/buildin/global.js 488 bytes {2} [built]
[JkW7] (webpack)/buildin/module.js 495 bytes {2} [built]
[LbCc] ./webPlayer.js 24 kB {1} [built]
[lebJ] ./comments.js 58 kB {0} [built]
[02Tr] ./ads.js 74 kB {1} [built]
    + 1 hidden module

Bu yaklaşımda, bir modülün kimliği yalnızca modülü yeniden adlandırırsanız veya taşırsanız değişir. Yeni modüller diğer modüllerin kimliklerini etkilemez.

Eklentiyi etkinleştirmek için yapılandırma dosyasının plugins bölümüne ekleyin:

// webpack.config.js
module.exports = {
  plugins: [
    new webpack.HashedModuleIdsPlugin()
  ]
};

Daha fazla bilgi

Özet

  • Paketi önbelleğe alma ve paket adını değiştirerek sürümleri ayırt etme
  • Paketi uygulama kodu, tedarikçi kodu ve çalışma zamanına bölme
  • HTTP isteğini kaydetmek için çalışma zamanını satır içi olarak yerleştirme
  • import ile kritik olmayan kodu yavaş yükleme
  • Gereksiz öğelerin yüklenmesini önlemek için kodu rotalara/sayfalara bölün.