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:
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.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
- Jake Archibald önbelleğe almayla ilgili en iyi uygulamalar hakkında
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:
Çı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.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.
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ıpvendor.[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
- Uzun süreli önbelleğe alma ile ilgili Webpack kılavuzu
- Webpack çalışma zamanı ve manifest hakkında Webpack dokümanları
- "CommonsChunkPlugin'den en iyi şekilde yararlanma"
optimization.splitChunks
veoptimization.runtimeChunk
'nin işleyiş şekli
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:
Ç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" }
Ç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:
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' }) ] };
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
import()
işlevi için Webpack belgeleriimport()
söz dizimini uygulamak için JavaScript önerisi
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:
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:
react-router
belgelerindeki "Kod Bölme" (React için)vue-router
'nin belgelerindeki "Geç Yükleme Rotaları" (Vue.js için)
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
- Giriş noktası kavramı hakkında Webpack dokümanları
- CommonsChunkPlugin hakkındaki Webpack dokümanları
- "CommonsChunkPlugin'den en iyi şekilde yararlanma"
optimization.splitChunks
veoptimization.runtimeChunk
'nin işleyiş şekli
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
- HashedModuleIdsPlugin hakkındaki Webpack belgeleri
Ö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.