Webpack, öğeleri önbelleğe almaya nasıl yardımcı olur?
Uygulama boyutunu optimize ettikten sonra uygulamanın yüklenme süresini kısaltan bir sonraki işlem, önbelleğe almadır. Uygulamanın bazı bölümlerini istemcide tutmak ve her defasında tekrar indirmekten kaçınmak için bunu kullanın.
Paket sürümü oluşturmayı ve önbellek üstbilgilerini kullanma
Önbelleğe alma işleminde genel yaklaşım şu şekildedir:
tarayıcıya dosyayı çok uzun bir süre (ör. bir yıl) önbelleğe almasını bildirin:
# Server header Cache-Control: max-age=31536000
Cache-Control
ürününün ne işe yaradığını bilmiyorsanız Jake Archibald'ın en iyi önbelleğe alma uygulamalarını ele alan mükemmel yayınını inceleyin.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ınan kopyayı kullanmasını söyler. Tarayıcı yalnızca dosya adı değişirse (veya bir yıl geçerse) ağa çalışır.
Webpack ile de aynısını yaparsınız ancak sürüm numarası yerine dosya karmasını belirtirsiniz. Dosya adına karma değerini eklemek için [chunkhash]
işlevini kullanın:
// webpack.config.js
module.exports = {
entry: './index.js',
output: {
filename: 'bundle.[chunkhash].js' // → bundle.8e0d62a03.js
}
};
İstemciye göndermek için dosya adına ihtiyacınız varsa HtmlWebpackPlugin
veya WebpackManifestPlugin
özelliğini kullanın.
HtmlWebpackPlugin
basit ancak daha az esnek bir yaklaşımdır. Bu eklenti, derleme sırasında derlenen tüm kaynakları içeren bir HTML dosyası oluşturur. Sunucu mantığınız karmaşık değilse
sizin için yeterli olacaktır:
<!-- index.html -->
<!DOCTYPE html>
<!-- ... -->
<script src="bundle.8e0d62a03.js"></script>
WebpackManifestPlugin
, karmaşık bir sunucu parçanız varsa kullanışlı olan daha esnek bir yaklaşımdır.
Derleme sırasında, karma olmayan dosya adları ile karma içeren dosya adları arasında eşleme içeren bir JSON dosyası oluşturur. Hangi dosyayla çalışacağınızı bulmak için sunucuda bu JSON'u kullanın:
// manifest.json
{
"bundle.js": "bundle.8e0d62a03.js"
}
Daha fazla bilgi
- Jake Archibald en iyi önbelleğe alma 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ı genellikle gerçek uygulama koduna göre daha az değişme eğilimindedir. 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ış dosya 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ı derlerken
[name]
öğesini bir parçanın adıyla değiştirir.[name]
bölümünü eklemezsek parçaları karmalarına göre ayırt etmemiz gerekecek. Bu oldukça zor bir iş.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 yığının adıdır. Bu ad, 1. adımdaki
[name]
yerine değiştirilecektir.Uygulamayı derlerseniz bu parça şu ana kadar uygulama kodunun tamamını içerir (tıpkı bu adımları uygulamadığımız gibi). Ancak bu durum bir saniye içinde değişecek.
webpack 4'te web paketi yapılandırmanıza
optimization.splitChunks.chunks: 'all'
seçeneğini ekleyin:// webpack.config.js (for webpack 4) module.exports = { optimization: { splitChunks: { chunks: 'all' } } };
Bu seçenek, akıllı kod bölmeyi etkinleştirir. Bununla birlikte webpack, 30 kB'tan büyük olursa (küçültme ve gzip'ten önce) tedarikçi kodunu ayıklar. Ortak kodu da ayıklar.Bu, derlemeniz birkaç paket oluşturuyorsa (ör. uygulamanızı rotalara bölerseniz) 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ır vevendor.[chunkhash].js
adlı ayrı bir dosyaya taşır.
Bu değişikliklerden sonra, her derleme bir yerine iki dosya oluşturacak: main.[chunkhash].js
ve vendor.[chunkhash].js
(webpack 4 için vendors~main.[chunkhash].js
). Webpack 4'te, bağımlılıklar küçükse tedarikçi paketi oluşturulmayabilir. Sorun değil:
$ 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.
Web paketi çalışma zamanı kodu
Maalesef yalnızca satıcının kodunu almak 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, modül kodundan ayrı olarak web paketi paketinde, modülün yürütme işlemini yöneten küçük bir kod parçası olan bir çalışma zamanının bulunmasıdır. Kodu birden fazla dosyaya böldüğünüzde, bu kod parçası parça kimlikleri ve karşılık gelen dosyalar arasında bir eşleme oluşturmaya başlar:
// vendor.e6ea4504d61a1cc1c60b.js
script.src = __webpack_require__.p + chunkId + "." + {
"0": "2f2269c7f0a55a5c1871"
}[chunkId] + ".js";
Webpack, bu çalışma zamanını bizim örneğimizde vendor
olan son oluşturulan yığına ekler. Herhangi bir parça her değiştiğinde bu kod parçası da değişir ve tüm vendor
parçasının değişmesine neden olur.
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 gerçekleştirilir:
// webpack.config.js (for webpack 4)
module.exports = {
optimization: {
runtimeChunk: true
}
};
webpack 3'te bunu,CommonsChunkPlugin
ile fazladan boş bir yığın oluşturarak yapın:
// 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ırada index.html
içine ekleyin. Hepsi bu kadar.
<!-- 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 hakkında web paketi kılavuzu
- webpack çalışma zamanı ve manifest hakkında web paketi belgeleri
- "CommonsChunkPlugin'den en iyi şekilde yararlanma"
optimization.splitChunks
veoptimization.runtimeChunk
nasıl çalışır?
Ekstra HTTP isteği kaydetmek için satır içi web paketi çalışma zamanı
İşleri daha da iyileştirmek için web paketi çalışma zamanını HTML yanıtına satır içine almayı deneyin. Örneğin, şunun yerine:
<!-- index.html -->
<script src="./runtime.79f17c27b335abc7aaf4.js"></script>
şunu yap:
<!-- index.html -->
<script>
!function(e){function n(r){if(t[r])return t[r].exports;…}} ([]);
</script>
Çalışma zamanı küçüktür ve bunu satır içine almak, bir HTTP isteğini kaydetmenize yardımcı olur (HTTP/1 için oldukça önemlidir; HTTP/2'de daha az önemlidir ancak yine de bir efekt oynayabilir).
Bunu şu şekilde yapabilirsiniz:
HTMLWebpackEklentileri ile oluşturuyorsanız
HTML dosyası oluşturmak için HtmlWebpackPlugin kullanırsanız ihtiyacınız olan tek şey InlineSourcePlugin'dir:
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 bir sunucu mantığı kullanarak HTML oluşturursanız
webpack 4 ile:
Çalışma zamanı parçasının oluşturulan adını öğrenmek için
WebpackManifestPlugin
kodunu ekleyin:// webpack.config.js (for webpack 4) const ManifestPlugin = require('webpack-manifest-plugin'); module.exports = { plugins: [ new ManifestPlugin() ] };
Bu eklentiyle yapılan derleme, aşağıdaki gibi bir dosya oluşturur:
// manifest.json { "runtime~main.js": "runtime~main.8e0d62a03.js" }
Çalışma zamanı yığınının içeriğini uygun bir şekilde satır içine alın. Ör. 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
belirterek çalışma zamanı adını statik yapın: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. Ör. 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 ihtiyaç duymadığınız geç yükleme kodu
Bazen bir sayfada daha fazla veya daha az önemli bölümler olabilir:
- YouTube'da bir video sayfası yüklerseniz yorumlardan çok videoya önem verirsiniz. Bu açıdan video yorumlardan daha önemli.
- Bir haber sitesinde bir makaleyi açtığınızda reklamlardan çok makalenin 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 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. Web paketi import('./module.js')
ifadesini 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 bunu 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 almayı iyileştirir. Ana parçadaki kodu değiştirirseniz yorum parçası etkilenmez.
Daha fazla bilgi
import()
işlevi için web paketi belgeleriimport()
söz dizimini uygulamaya yönelik JavaScript teklifi
Kodu rotalara ve sayfalara böl
Uygulamanızda birden fazla rota veya sayfa olmasına rağmen kodu içeren yalnızca bir JS dosyası (tek bir main
parçası) varsa muhtemelen her istekte ekstra bayt sunuyor olabilirsiniz. Örneğin, bir kullanıcı sitenizin ana sayfasını ziyaret ettiğinde:
farklı bir sayfadaki makaleyi oluşturmak için kodu yüklemeleri gerekmez, ancak dosyayı yüklerler. Dahası, kullanıcı her zaman yalnızca ana sayfayı ziyaret ederse ve makale kodunda bir 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) ayırırsak 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 karşılık gelen yığını 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 ihtiyaç duymadığınız geç yüklenen kod" bölümüne bakın). Bir çerçeve kullanırsanız
bunun için bir çözüm mevcut olabilir:
react-router
dokümanlarında "Kod Bölme" (React için)vue-router
belgelerinde "Geç Yükleme Rotaları" (Vue.js için)
Geleneksel çok sayfalı uygulamalar için
Geleneksel uygulamaları sayfalara göre bölmek için webpack'in giriş noktalarını kullanın. Uygulamanızın üç tür sayfası varsa: ana sayfa, makale sayfası ve kullanıcı hesabı sayfası. Bu sayfada üç 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'
}
};
Her giriş dosyası için webpack ayrı bir bağımlılık ağacı oluşturur ve yalnızca bu giriş tarafından kullanılan 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, Lodash yalnızca makale sayfasında kullanılıyorsa home
ve profile
paketlerinde bu özellik bulunmaz ve kullanıcının ana sayfayı ziyaret ederken bu kitaplığı indirmesi gerekmez.
Ancak, ayrı bağımlılık ağaçlarının dezavantajları vardır. İki giriş noktası Lodash'i kullanıyorsa ve bağımlılıklarınızı bir tedarikçi firma paketine taşımadıysanız her iki giriş noktasında da Lodash'in bir kopyası bulunur. Bu sorunu çözmek için webpack 4'te web paketi yapılandırmanıza
optimization.splitChunks.chunks: 'all'
seçeneğini ekleyin:
// webpack.config.js (for webpack 4)
module.exports = {
optimization: {
splitChunks: {
chunks: 'all'
}
}
};
Bu seçenek, akıllı kod bölmeyi etkinleştirir. Bu seçenek kullanıldığında, webpack otomatik olarak ortak kodu arar ve ayrı dosyalara çıkarır.
Alternatif olarak webpack 3'te CommonsChunkPlugin
öğesini kullanabilirsiniz. Bu işlem, genel 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ğerini kullanabilirsiniz. Genel olarak, küçük tutmak istersiniz ama parça sayısı büyüdükçe artar. Örneğin, 3 parça için minChunks
2 olabilir ancak 30 parça için 8 olabilir. Çünkü parçayı 2'de tutarsanız çok fazla sayıda modül ortak dosyaya girer ve bu değeri çok fazla şişirir.
Daha fazla bilgi
- Giriş noktaları kavramı hakkında web paketi belgeleri
- CommonsChunkEklentileri hakkında web paketi belgeleri
- "CommonsChunkPlugin'den en iyi şekilde yararlanma"
optimization.splitChunks
veoptimization.runtimeChunk
nasıl çalışır?
Modül kimliklerini daha kararlı hale getirme
Kodu oluştururken, webpack her modüle bir kimlik atar. Daha sonra bu kimlikler, paketin içindeki require()
içinde kullanılır. Kimlikleri genellikle derleme çıktısında modül yollarının hemen öncesinde 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 (yani ilk modülün kimliği 0, ikincisinin kimliği 1 ve bu şekilde devam eder). Buradaki sorun, yeni bir modül eklediğinizde modül listesinin ortasında görüntülenerek sonraki tüm modüllerin kimliklerini değiştirebilmesidir:
$ 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]
↓ Ne yaptığına bakın! comments.js
artık 4 yerine 5 kimliğine sahip
[5] ./comments.js 58 kB {0} [built]
↓ ads.js
artık 5 yerine 6'ya sahip
[6] ./ads.js 74 kB {1} [built]
+ 1 hidden module
Bu işlem, değişen kimliklere sahip modüller içeren veya bunlara bağlı olan tüm parçaları, gerçek kodları değişmemiş olsa bile geçersiz kılar. Örneğimizde, 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ınır. Öte yandan, yalnızca main
parçası olması gerekirdi.
Bu sorunu çözmek için HashedModuleIdsPlugin
aracılığıyla modül kimliklerinin hesaplanma şeklini değiştirin.
Sayaca dayalı 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, modülün kimliği yalnızca modülü yeniden adlandırdığınızda veya taşırsanız değişir. Yeni modüller diğer modüllerin kimliklerini etkilemeyecek.
Etkinleştirmek için eklentiyi yapılandırmanın plugins
bölümüne ekleyin:
// webpack.config.js
module.exports = {
plugins: [
new webpack.HashedModuleIdsPlugin()
]
};
Daha fazla bilgi
- HashedModuleIdsPlugin ile ilgili web paketi belgeleri
Özet
- Paketi önbelleğe alın ve paket adını değiştirerek sürümleri birbirinden ayırt edin
- Paketi uygulama kodu, tedarikçi kodu ve çalışma zamanına ayırın
- HTTP isteğini kaydetmek için çalışma zamanını satır içine alma
import
ile kritik olmayan kodları geç yükleme- Gereksiz öğelerin yüklenmesini önlemek için kodu rotalara/sayfalara göre böl