Cara webpack membantu penyimpanan aset dalam cache
Hal berikutnya (setelah mengoptimalkan ukuran aplikasi yang meningkatkan waktu pemuatan aplikasi dalam cache. Gunakan untuk menyimpan bagian-bagian aplikasi di klien dan menghindari mengunduhnya ulang setiap saat.
Menggunakan pembuatan versi paket dan header cache
Pendekatan umum dalam cache adalah:
beri tahu browser untuk meng-cache file dalam waktu yang sangat lama (misalnya, setahun):
# Server header Cache-Control: max-age=31536000
Jika Anda tidak tahu apa yang dilakukan
Cache-Control
, lihat postingan yang sangat baik tentang penyimpanan cache yang terbaik praktik.dan ganti nama file saat diubah untuk memaksa download ulang:
<!-- Before the change --> <script src="./index-v15.js"></script> <!-- After the change --> <script src="./index-v16.js"></script>
Pendekatan ini memberitahu browser untuk mengunduh file JS, menyimpannya dalam cache, dan menggunakan salinan yang di-cache. Browser hanya akan masuk ke jaringan hanya jika nama file berubah (atau jika satu tahun berlalu).
Dengan webpack, Anda melakukan hal yang sama, tetapi alih-alih menggunakan nomor versi, Anda menentukan
{i>hash <i}file. Untuk menyertakan {i>hash<i} ke dalam nama file, gunakan
[chunkhash]
:
// webpack.config.js
module.exports = {
entry: './index.js',
output: {
filename: 'bundle.[chunkhash].js' // → bundle.8e0d62a03.js
}
};
Jika Anda memerlukan
untuk mengirimkannya ke klien, gunakan HtmlWebpackPlugin
atau
WebpackManifestPlugin
.
HtmlWebpackPlugin
adalah
pendekatan yang sederhana, tetapi kurang fleksibel. Selama kompilasi, plugin ini menghasilkan
file HTML yang mencakup semua sumber daya yang dikompilasi. Jika logika server Anda tidak
rumit, maka cukup bagi Anda:
<!-- index.html -->
<!DOCTYPE html>
<!-- ... -->
<script src="bundle.8e0d62a03.js"></script>
Tujuan
WebpackManifestPlugin
adalah pendekatan yang lebih fleksibel yang berguna
jika Anda memiliki bagian server yang kompleks.
Selama proses build, build ini menghasilkan file JSON dengan pemetaan antarnama file
tanpa {i>hash<i} dan nama
file dengan {i>hash<i}. Gunakan JSON ini pada server untuk mengetahui
file yang akan dikerjakan:
// manifest.json
{
"bundle.js": "bundle.8e0d62a03.js"
}
Bacaan lebih lanjut
- Jake Archibald tentang penyimpanan cache yang terbaik praktik
Mengekstrak dependensi dan runtime ke file terpisah
Dependensi
Dependensi aplikasi cenderung lebih jarang berubah daripada kode aplikasi yang sebenarnya. Jika Anda pindah file tersebut menjadi file terpisah, browser akan dapat menyimpannya di cache secara terpisah – dan tidak akan mengunduhnya ulang setiap kali kode aplikasi berubah.
Untuk mengekstrak dependensi ke dalam potongan terpisah, lakukan tiga langkah:
Ganti nama file output dengan
[name].[chunkname].js
:// webpack.config.js module.exports = { output: { // Before filename: 'bundle.[chunkhash].js', // After filename: '[name].[chunkhash].js' } };
Saat membangun aplikasi, webpack akan menggantikan
[name]
dengan nama suatu potongan. Jika kita tidak menambahkan bagian[name]
, kita akan memiliki membedakan antara potongan berdasarkan {i>hash<i} - yang cukup sulit!Konversi kolom
entry
menjadi objek:// webpack.config.js module.exports = { // Before entry: './index.js', // After entry: { main: './index.js' } };
Dalam cuplikan ini, "main" merupakan nama dari sebuah potongan data. Nama ini akan diganti dengan posisi
[name]
dari langkah 1.Sekarang, jika Anda membangun aplikasi, potongan ini akan mencakup seluruh kode aplikasi – sepertinya kami belum melakukan langkah-langkah ini. Namun, perubahan ini akan segera terjadi.
Di webpack 4, tambahkan opsi
optimization.splitChunks.chunks: 'all'
ke dalam konfigurasi webpack Anda:// webpack.config.js (for webpack 4) module.exports = { optimization: { splitChunks: { chunks: 'all' } } };
Opsi ini mengaktifkan pemisahan kode cerdas. Dengan {i>webpack<i} itu, webpack akan mengekstrak kode vendor jika file menjadi lebih besar dari 30 kB (sebelum minifikasi dan gzip). Tindakan ini juga akan mengekstrak kode umum – berguna jika build Anda menghasilkan beberapa paket (mis. jika Anda memisahkan aplikasi ke dalam beberapa rute).
Di webpack 3, tambahkan
CommonsChunkPlugin
:// 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'), }) ] };
Plugin ini mengambil semua modul yang jalurnya menyertakan
node_modules
dan memindahkannya ke file terpisah yang disebutvendor.[chunkhash].js
.
Setelah perubahan ini, setiap build akan menghasilkan dua file, bukan satu: main.[chunkhash].js
dan
vendor.[chunkhash].js
(vendors~main.[chunkhash].js
untuk webpack 4). Dalam kasus webpack 4,
paket vendor mungkin tidak dibuat jika dependensinya kecil – dan itu tidak apa-apa:
$ 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
Browser akan meng-cache file ini secara terpisah – dan hanya mendownload ulang kode yang berubah.
Kode runtime webpack
Sayangnya, mengekstrak kode vendor saja tidak cukup. Jika Anda mencoba ubah sesuatu di kode aplikasi:
// index.js
…
…
// E.g. add this:
console.log('Wat');
Anda akan melihat bahwa hash vendor
juga berubah:
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
Hal ini terjadi karena paket webpack, terlepas dari kode modul, memiliki sebuah runtime – kode pendek yang mengelola eksekusi modul. Ketika Anda membagi kode menjadi beberapa file, potongan kode ini mulai termasuk pemetaan antara beberapa ID dan file yang sesuai:
// vendor.e6ea4504d61a1cc1c60b.js
script.src = __webpack_require__.p + chunkId + "." + {
"0": "2f2269c7f0a55a5c1871"
}[chunkId] + ".js";
Webpack menyertakan runtime ini ke dalam potongan terakhir yang dihasilkan, yaitu vendor
dalam kasus kita. Dan setiap kali potongan apa pun berubah, potongan kode ini juga berubah,
yang menyebabkan seluruh potongan vendor
berubah.
Untuk mengatasi hal ini, mari pindahkan runtime ke file terpisah. Di webpack 4, ini adalah
dicapai dengan mengaktifkan opsi optimization.runtimeChunk
:
// webpack.config.js (for webpack 4)
module.exports = {
optimization: {
runtimeChunk: true
}
};
Di webpack 3, lakukan hal ini dengan membuat potongan ekstra kosong dengan CommonsChunkPlugin
:
// 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
})
]
};
Setelah perubahan ini, setiap build akan menghasilkan tiga file:
$ 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
Sertakan semuanya ke dalam index.html
dalam urutan terbalik – dan selesai:
<!-- index.html -->
<script src="./runtime.79f17c27b335abc7aaf4.js"></script>
<script src="./vendor.26886caf15818fa82dfa.js"></script>
<script src="./main.00bab6fd3100008a42b0.js"></script>
Bacaan lebih lanjut
- Panduan Webpack mengenai cache jangka panjang
- Dokumen Webpack tentang runtime webpack dan manifes
- "Mengoptimalkan CommonsChunkPlugin"
- Cara kerja
optimization.splitChunks
danoptimization.runtimeChunk
Runtime webpack inline untuk menyimpan permintaan HTTP tambahan
Agar lebih baik lagi, coba sisipkan runtime webpack ke dalam HTML yang dihasilkan. Yaitu, alih-alih yang berikut:
<!-- index.html -->
<script src="./runtime.79f17c27b335abc7aaf4.js"></script>
lakukan ini:
<!-- index.html -->
<script>
!function(e){function n(r){if(t[r])return t[r].exports;…}} ([]);
</script>
Waktu proses{i> <i}ini kecil, dan menjadi {i>inline<i} akan membantu Anda menyimpan permintaan HTTP (cukup penting untuk HTTP/1; kurang penting dengan HTTP/2 tetapi mungkin masih memainkan ).
Berikut cara melakukannya.
Jika Anda membuat HTML dengan HTMLWebpackPlugin
Jika Anda menggunakan HtmlWebpackPlugin yang akan dibuat sebuah {i>file<i} HTML, InlineSourcePlugin adalah hal yang Anda butuhkan:
const HtmlWebpackPlugin = require('html-webpack-plugin');
const InlineSourcePlugin = require('html-webpack-inline-source-plugin');
module.exports = {
plugins: [
new HtmlWebpackPlugin({
inlineSource: 'runtime~.+\\.js',
}),
new InlineSourcePlugin()
]
};
Jika Anda membuat HTML menggunakan logika server kustom
Dengan webpack 4:
Tambahkan
WebpackManifestPlugin
untuk mengetahui nama yang dihasilkan dari potongan {i>runtime<i}:// webpack.config.js (for webpack 4) const ManifestPlugin = require('webpack-manifest-plugin'); module.exports = { plugins: [ new ManifestPlugin() ] };
Build dengan plugin ini akan membuat file yang terlihat seperti ini:
// manifest.json { "runtime~main.js": "runtime~main.8e0d62a03.js" }
Buat konten potongan runtime secara inline dengan cara yang mudah. Mis. dengan Node.js dan Express:
// 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> … `); });
Atau dengan webpack 3:
Buat nama runtime statis dengan menentukan
filename
:module.exports = { plugins: [ new webpack.optimize.CommonsChunkPlugin({ name: 'runtime', minChunks: Infinity, filename: 'runtime.js' }) ] };
Buat konten
runtime.js
menjadi inline dengan cara yang mudah. Mis. dengan Node.js dan Express:// server.js const fs = require('fs'); const runtimeContent = fs.readFileSync('./runtime.js', 'utf-8'); app.get('/', (req, res) => { res.send(` … <script>${runtimeContent}</script> … `); });
Pemuatan lambat kode yang tidak diperlukan saat ini
Terkadang, halaman memiliki bagian yang lebih penting dan kurang penting:
- Jika Anda memuat laman video di YouTube, Anda lebih peduli terhadap video itu daripada tentang komentar. Di sini, video lebih penting daripada komentar.
- Jika Anda membuka artikel di situs berita, Anda akan lebih memperhatikan teks artikel, bukan tentang iklan. Di sini, teks lebih penting daripada iklan.
Jika demikian, tingkatkan performa pemuatan awal dengan hanya mendownload
hal terpenting terlebih dahulu, lalu pemuatan lambat untuk bagian lainnya. Gunakan
fungsi import()
dan
code-splitting untuk hal ini:
// 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()
menentukan bahwa Anda ingin memuat modul tertentu secara dinamis. Kapan
webpack melihat import('./module.js')
, yang akan memindahkan modul ini ke dalam
chunk:
$ 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
dan mendownloadnya hanya saat eksekusi mencapai fungsi import()
.
Tindakan ini akan memperkecil paket main
, sehingga mempercepat waktu pemuatan awal.
Terlebih lagi, ini akan meningkatkan kualitas {i>caching<i} - jika Anda mengubah kode di potongan utama,
potongan komentar tidak akan terpengaruh.
Bacaan lebih lanjut
- Dokumen Webpack untuk
import()
fungsi - Proposal JavaScript untuk menerapkan
import()
sintaksis
Bagi kode menjadi rute dan halaman
Jika aplikasi Anda memiliki beberapa rute atau halaman, tetapi hanya ada satu file JS dengan
kode (satu potongan main
), kemungkinan Anda menyajikan byte tambahan
setiap permintaan. Misalnya, saat pengguna mengunjungi halaman beranda situs Anda:
mereka tidak perlu memuat kode untuk merender artikel yang berada di tetapi mereka akan memuatnya. Selain itu, jika pengguna selalu hanya mengunjungi rumah dan Anda membuat perubahan pada kode artikel, webpack akan membatalkan seluruh paket – dan pengguna harus mendownload ulang seluruh aplikasi.
Jika kita membagi aplikasi menjadi beberapa halaman (atau rute, jika berupa aplikasi satu halaman), pengguna hanya akan mendownload kode yang relevan. Ditambah lagi, browser akan meng-cache kode aplikasi lebih baik: jika Anda mengubah kode halaman beranda, webpack hanya akan membatalkan potongan yang sesuai.
Untuk aplikasi web satu halaman
Untuk memisahkan aplikasi satu halaman berdasarkan rute, gunakan import()
(lihat “Kode pemuatan lambat
yang tidak Anda perlukan saat ini”). Jika Anda menggunakan kerangka kerja,
mungkin solusi yang sudah ada untuk ini:
- "Kode
Pemisahan"
di dokumen
react-router
(untuk React) - "Pemuatan Lambat
Rute" di
Dokumen
vue-router
(untuk Vue.js)
Untuk aplikasi multi-halaman tradisional
Untuk memisahkan aplikasi tradisional berdasarkan halaman, gunakan entri webpack poin. Jika aplikasi Anda memiliki tiga jenis halaman: halaman beranda, halaman artikel, dan halaman akun pengguna, harus memiliki tiga entri:
// webpack.config.js
module.exports = {
entry: {
home: './src/Home/index.js',
article: './src/Article/index.js',
profile: './src/Profile/index.js'
}
};
Untuk setiap file entri, webpack akan membangun hierarki dependensi terpisah dan menghasilkan paket yang hanya mencakup modul yang digunakan oleh entri tersebut:
$ 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
Jadi, jika hanya halaman artikel yang menggunakan Lodash, paket home
dan profile
tidak akan menyertakannya - dan pengguna tidak perlu mengunduh pustaka ini jika
mengunjungi laman beranda.
Namun, hierarki dependensi yang terpisah memiliki kelemahan. Jika dua titik entri
menggunakan
Lodash, dan Anda belum memindahkan
dependensi Anda ke dalam paket vendor, keduanya
akan menyertakan
salinan Lodash. Untuk mengatasinya, di webpack 4, tambahkan
optimization.splitChunks.chunks: 'all'
ke konfigurasi webpack Anda:
// webpack.config.js (for webpack 4)
module.exports = {
optimization: {
splitChunks: {
chunks: 'all'
}
}
};
Opsi ini mengaktifkan pemisahan kode cerdas. Dengan opsi ini, webpack akan secara otomatis mencari kode yang umum dan mengekstraknya ke dalam file terpisah.
Atau, di webpack 3, gunakan CommonsChunkPlugin
– perintah ini akan memindahkan dependensi umum ke file baru yang ditentukan:
module.exports = {
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'common',
minChunks: 2 // 2 is the default value
})
]
};
Jangan ragu untuk bermain-main dengan nilai minChunks
untuk menemukan yang terbaik. Umumnya,
Anda ingin membuatnya kecil, tetapi
tambah jika jumlah potongannya bertambah. Sebagai
misalnya, untuk 3 potongan, minChunks
mungkin 2, tetapi untuk 30 potongan, mungkin 8
- karena jika Anda menyimpannya di angka 2, terlalu banyak modul yang akan masuk ke file umum,
menggelembungkannya terlalu banyak.
Bacaan lebih lanjut
- Dokumen Webpack tentang konsep entri poin
- dokumen Webpack tentang CommonsChunkPlugin
- "Mengoptimalkan CommonsChunkPlugin"
- Cara kerja
optimization.splitChunks
danoptimization.runtimeChunk
Membuat ID modul lebih stabil
Saat membuat kode, webpack menetapkan ID untuk setiap modul. Nantinya, ID ini
digunakan dalam require()
di dalam paket. Anda biasanya melihat ID dalam output build
tepat sebelum jalur modul:
$ 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
↓ Di sini
[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
Secara default, ID dihitung menggunakan penghitung (yaitu modul pertama memiliki ID 0, yang kedua memiliki ID 1, dan seterusnya). Masalahnya adalah ketika Anda menambahkan modul baru, mungkin akan muncul di tengah daftar modul, yang mengubah semua modul berikutnya' ID:
$ 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]
↓ Kami telah menambahkan modul...
[4] ./webPlayer.js 24 kB {1} [built]
↓ Dan lihat apa yang telah dilakukannya! comments.js
kini memiliki ID 5, bukan 4
[5] ./comments.js 58 kB {0} [built]
↓ ads.js
kini memiliki ID 6, bukan 5
[6] ./ads.js 74 kB {1} [built]
+ 1 hidden module
Ini membatalkan semua bagian yang menyertakan atau bergantung pada modul dengan ID yang diubah –
bahkan jika kode mereka yang
sebenarnya tidak berubah. Dalam kasus kita, potongan 0
(potongan
dengan comments.js
) dan potongan main
(potongan dengan kode aplikasi lainnya) mendapatkan
tidak valid – sedangkan hanya main
yang seharusnya.
Untuk mengatasi ini, ubah cara ID modul dihitung menggunakan
HashedModuleIdsPlugin
Fungsi ini menggantikan ID berbasis penghitung dengan hash jalur modul:
$ 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
↓ Di sini
[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
Dengan pendekatan ini, ID modul hanya berubah jika Anda mengganti nama atau memindahkan ruang lingkup modul ini. Modul baru tidak akan memengaruhi modul lain pelanggan.
Untuk mengaktifkan plugin, tambahkan ke bagian plugins
pada konfigurasi:
// webpack.config.js
module.exports = {
plugins: [
new webpack.HashedModuleIdsPlugin()
]
};
Bacaan lebih lanjut
- dokumen Webpack tentang HashedModuleIdsPlugin
Mengambil kesimpulan
- Simpan paket dalam cache dan bedakan antar-versi dengan mengubah nama paket
- Bagi paket menjadi kode aplikasi, kode vendor, dan runtime
- Membuat runtime inline untuk menyimpan permintaan HTTP
- Pemuatan lambat kode tidak penting dengan
import
- Pisahkan kode berdasarkan rute/halaman untuk menghindari pemuatan hal-hal yang tidak perlu