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

Webpack, öğeleri önbelleğe almaya nasıl yardımcı olur?

Bir sonraki adım (uygulama boyutunu optimize ettikten sonra, uygulama yükleme süresini iyileştiren) önbelleğe alma işlemidir. Uygulamanın bazı bölümlerini istemcide tutmak ve her seferinde yeniden indirmekten kaçınmak için kullanın.

Önbelleğe alma işlemi için genel yaklaşım şu şekildedir:

  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 işe yaradığını bilmiyorsanız Jake Archibald'ın en iyi önbelleğe alma uygulamaları hakkındaki mükemmel yayınını inceleyin.

  2. ve yeniden indirmeyi zorunlu kılmak için değiştirildiğinde 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. Derleme sırasında bu eklenti tüm derlenmiş kaynakları içeren bir HTML dosyası oluşturur. Sunucu mantığınız karmaşık değilse aşağıdaki yöntem 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] kısmını eklemezsek parçaları karmalarına göre ayırt etmemiz gerekir. Bu oldukça zor bir işlemdir.

  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 bu adla değiştirilecek.

    Bu adımları uygulamadıysanız uygulamayı derlediğinizde 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ölmeyi etkinleştirir. Bu sayede webpack, 30 KB'tan büyükse (küçültme ve gzip'ten önce) tedarikçi kodunu ayıklayabilir. Aynı zamanda ortak kodu da ayıklar.Bu, derlemeniz birden fazla paket oluşturuyorsa (ör. uygulamanızı rotalara ayırırsanız) kullanışlıdır.

    Web paketi 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 (web paketi 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 firma 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 görürsünüz:

                           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, web paketi paketinin modüllerin kodundan ayrı olarak, modülün yürütülmesini yöneten küçük bir kod parçası olan bir çalışma zamanına sahip olması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 (bizim durumumuzda vendor ) dahil eder. 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şturacaktır:

$ 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. Artık hazırsınız:

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

Daha fazla bilgi

Ekstra HTTP isteğini kaydetmek için satır içi web paketi çalışma zamanı

Daha da iyileştirmek için webpack çalışma zamanını HTML yanıtına yerleştirmeyi deneyin. Örneğin, bunun 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'i kullanıyorsanız ihtiyacınız olan tek şey InlineSourcePlugin'tir:

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çine alın. Ö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 videoya yorumlardan daha fazla önem verirsiniz. Burada video, yorumlardan daha önemlidir.
  • Bir haber sitesinde makale açtığınızda reklamlardan çok makale metnine önem verirsiniz. Burada metin, reklamlardan daha önemlidir.

Bu gibi durumlarda, önce yalnızca en önemli öğeleri indirip kalan bölümleri daha sonra geç yükleyerek ilk yükleme performansını iyileştirin. Bunun için import() işlevini ve code-splitting 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 iyisi, önbelleğe almayı iyileştirir. Ana parçadaki kodu değiştirirseniz yorum yığını 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 istekte 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 kodu 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ık bir uygulamaysa rotalara) bölersek kullanıcı yalnızca ilgili 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 geç yükleme kodu" 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 göre bölmek için web paketinin giriş noktalarını kullanın. Uygulamanızda üç tür sayfa varsa: ana sayfa, makale sayfası ve kullanıcı hesabı sayfası. Bu sayfanın üç girişi 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 bu girişin kullandığı modülleri içeren bir paket 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'in 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ı belirtilen 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 firma kodu ve çalışma zamanına böl
  • 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