Dalam codelab ini, tingkatkan performa aplikasi sederhana ini yang memungkinkan pengguna memberi rating pada kucing acak. Pelajari cara mengoptimalkan paket JavaScript dengan meminimalkan jumlah kode yang ditranspilasi.
Di aplikasi contoh, Anda dapat memilih kata atau emoji untuk menyampaikan seberapa besar Anda menyukai setiap kucing. Saat Anda mengklik tombol, aplikasi akan menampilkan nilai tombol di bawah gambar kucing saat ini.
Ukur
Sebaiknya mulailah dengan memeriksa situs sebelum menambahkan pengoptimalan apa pun:
- Untuk melihat pratinjau situs, tekan Lihat Aplikasi. Kemudian tekan Layar Penuh .
- Tekan `Control+Shift+J` (atau `Command+Option+J` di Mac) untuk membuka DevTools.
- Klik tab Jaringan.
- Centang kotak Nonaktifkan cache.
- Muat ulang aplikasi.
Lebih dari 80 KB digunakan untuk aplikasi ini. Saatnya mencari tahu apakah bagian dari paket tidak digunakan:
Tekan
Control+Shift+P
(atauCommand+Shift+P
di Mac) untuk membuka menu Command.Masukkan
Show Coverage
, lalu tekanEnter
untuk menampilkan tab Cakupan.Di tab Cakupan, klik Muat ulang untuk memuat ulang aplikasi saat merekam cakupan.
Lihat berapa banyak kode yang digunakan dibandingkan dengan jumlah kode yang dimuat untuk paket utama:
Lebih dari setengah paket (44 KB) bahkan tidak digunakan. Hal ini karena banyak kode di dalamnya terdiri dari polyfill untuk memastikan bahwa aplikasi berfungsi di browser lama.
Menggunakan @babel/preset-env
Sintaksis bahasa JavaScript sesuai dengan standar yang dikenal sebagai ECMAScript, atau ECMA-262. Versi spesifikasi yang lebih baru dirilis setiap tahun dan menyertakan fitur baru yang telah lulus proses proposal. Setiap browser utama selalu berada pada tahap yang berbeda dalam mendukung fitur ini.
Fitur ES2015 berikut digunakan dalam aplikasi:
Fitur ES2017 berikut juga digunakan:
Jangan ragu untuk mempelajari kode sumber di src/index.js
untuk melihat cara semua kode ini
digunakan.
Semua fitur ini didukung dalam Chrome versi terbaru, tetapi bagaimana dengan browser lain yang tidak mendukungnya? Babel, yang disertakan dalam aplikasi, adalah library paling populer yang digunakan untuk mengompilasi kode yang berisi sintaksis yang lebih baru menjadi kode yang dapat dipahami oleh browser dan lingkungan lama. Hal ini dilakukan dengan dua cara:
- Polyfill disertakan untuk mengemulasi fungsi ES2015+ yang lebih baru sehingga API-nya
dapat digunakan meskipun tidak didukung oleh browser. Berikut adalah contoh polyfill dari metode
Array.includes
. - Plugin digunakan untuk mengubah kode ES2015 (atau yang lebih baru) menjadi sintaksis ES5 yang lebih lama. Karena ini adalah perubahan terkait sintaksis (seperti fungsi panah), perubahan tersebut tidak dapat diemulasi dengan polyfill.
Lihat package.json
untuk melihat library Babel yang disertakan:
"dependencies": {
"@babel/polyfill": "^7.0.0"
},
"devDependencies": {
//...
"babel-loader": "^8.0.2",
"@babel/core": "^7.1.0",
"@babel/preset-env": "^7.1.0",
//...
}
@babel/core
adalah compiler inti Babel. Dengan ini, semua konfigurasi Babel ditentukan dalam.babelrc
di root project.babel-loader
menyertakan Babel dalam proses build webpack.
Sekarang lihat webpack.config.js
untuk melihat cara babel-loader
disertakan sebagai
aturan:
module: { rules: [ //... { test: /\.js$/, exclude: /node_modules/, loader: "babel-loader" } ] },
@babel/polyfill
menyediakan semua polyfill yang diperlukan untuk setiap fitur ECMAScript yang lebih baru sehingga dapat berfungsi di lingkungan yang tidak mendukungnya. Data ini sudah diimpor di bagian paling atassrc/index.js.
import "./style.css";
import "@babel/polyfill";
@babel/preset-env
mengidentifikasi transformasi dan polyfill mana yang diperlukan untuk setiap browser atau lingkungan yang dipilih sebagai target.
Lihat file konfigurasi Babel, .babelrc
, untuk mengetahui cara penyertaannya:
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions"
}
]
]
}
Ini adalah penyiapan Babel dan webpack. Pelajari cara menyertakan Babel dalam aplikasi Anda jika Anda kebetulan menggunakan bundler modul yang berbeda dari webpack.
Atribut targets
di .babelrc
mengidentifikasi browser yang ditargetkan. @babel/preset-env
terintegrasi dengan browserslist, yang berarti Anda dapat menemukan daftar lengkap kueri
kompatibel yang dapat digunakan di kolom ini di
dokumentasi browserslist.
Nilai "last 2 versions"
mentranspile kode dalam aplikasi untuk
dua versi terakhir dari setiap browser.
Proses Debug
Untuk mendapatkan tampilan lengkap semua target Babel browser serta semua
transformasi dan polyfill yang disertakan, tambahkan kolom debug
ke .babelrc:
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions",
"debug": true
}
]
]
}
- Klik Tools.
- Klik Logs.
Muat ulang aplikasi dan lihat log status Glitch di bagian bawah editor.
Browser yang ditargetkan
Babel mencatat sejumlah detail ke konsol tentang proses kompilasi, termasuk semua lingkungan target yang telah dikompilasi kodenya.
Perhatikan bagaimana browser yang tidak digunakan lagi, seperti Internet Explorer, disertakan dalam daftar ini. Hal ini menjadi masalah karena browser yang tidak didukung tidak akan memiliki fitur yang lebih baru, dan Babel terus mentranspile sintaksis tertentu untuk browser tersebut. Hal ini tidak perlu meningkatkan ukuran paket jika pengguna tidak menggunakan browser ini untuk mengakses situs Anda.
Babel juga mencatat daftar plugin transformasi yang digunakan:
Daftarnya cukup panjang. Berikut adalah semua plugin yang perlu digunakan Babel untuk mengubah sintaksis ES2015+ ke sintaksis yang lebih lama untuk semua browser yang ditargetkan.
Namun, Babel tidak menampilkan polyfill tertentu yang digunakan:
Hal ini karena seluruh @babel/polyfill
diimpor secara langsung.
Memuat polyfill satu per satu
Secara default, Babel menyertakan setiap polyfill yang diperlukan untuk lingkungan ES2015+ yang lengkap saat
@babel/polyfill
diimpor ke dalam file. Untuk mengimpor polyfill tertentu yang diperlukan untuk browser target, tambahkan useBuiltIns: 'entry'
ke konfigurasi.
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions",
"debug": true
"useBuiltIns": "entry"
}
]
]
}
Muat ulang aplikasi. Sekarang Anda dapat melihat semua polyfill spesifik yang disertakan:
Meskipun hanya polyfill yang diperlukan untuk "last 2 versions"
yang disertakan sekarang, daftarnya masih sangat panjang. Hal ini karena
polyfill yang diperlukan untuk browser target untuk setiap fitur yang lebih baru masih disertakan. Ubah nilai atribut menjadi usage
agar hanya menyertakan atribut yang diperlukan untuk fitur yang sedang digunakan dalam kode.
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions",
"debug": true,
"useBuiltIns": "entry"
"useBuiltIns": "usage"
}
]
]
}
Dengan demikian, polyfill otomatis disertakan jika diperlukan.
Artinya, Anda dapat menghapus impor @babel/polyfill
di src/index.js.
import "./style.css";
import "@babel/polyfill";
Sekarang hanya polyfill yang diperlukan untuk aplikasi yang disertakan.
Ukuran paket aplikasi berkurang secara signifikan.
Mempersempit daftar browser yang didukung
Jumlah target browser yang disertakan masih cukup besar, dan tidak banyak pengguna yang menggunakan browser yang tidak digunakan lagi seperti Internet Explorer. Update konfigurasi menjadi berikut:
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions",
"targets": [">0.25%", "not ie 11"],
"debug": true,
"useBuiltIns": "usage",
}
]
]
}
Lihat detail untuk paket yang diambil.
Karena aplikasinya sangat kecil, sebenarnya tidak ada banyak perbedaan dengan
perubahan ini. Namun, menggunakan persentase pangsa pasar browser (seperti
">0.25%"
) bersama dengan mengecualikan browser tertentu yang Anda yakini tidak
digunakan pengguna adalah pendekatan yang direkomendasikan. Baca artikel
"2 versi terakhir" yang dianggap berbahaya
oleh James Kyle untuk mempelajari lebih lanjut.
Gunakan <script type="module">
Masih ada banyak hal yang dapat ditingkatkan. Meskipun sejumlah polyfill yang tidak digunakan telah dihapus, ada banyak yang sedang dikirim dan tidak diperlukan untuk beberapa browser. Dengan menggunakan modul, sintaksis yang lebih baru dapat ditulis dan dikirim ke browser secara langsung tanpa menggunakan polyfill yang tidak diperlukan.
Modul JavaScript adalah fitur yang relatif baru dan didukung di semua browser utama.
Modul dapat dibuat menggunakan atribut type="module"
untuk menentukan skrip yang mengimpor dan mengekspor dari modul
lain. Contoh:
// math.mjs
export const add = (x, y) => x + y;
<!-- index.html -->
<script type="module">
import { add } from './math.mjs';
add(5, 2); // 7
</script>
Banyak fitur ECMAScript yang lebih baru sudah didukung di lingkungan yang mendukung modul JavaScript (bukan memerlukan Babel). Artinya, konfigurasi Babel dapat diubah untuk mengirim dua versi aplikasi yang berbeda ke browser:
- Versi yang akan berfungsi di browser yang lebih baru yang mendukung modul dan menyertakan modul yang sebagian besar tidak ditranspil, tetapi memiliki ukuran file yang lebih kecil
- Versi yang menyertakan skrip yang lebih besar dan ditranspil yang akan berfungsi di browser lama
Menggunakan Modul ES dengan Babel
Agar memiliki setelan @babel/preset-env
yang terpisah untuk kedua versi
aplikasi, hapus file .babelrc
. Setelan Babel dapat ditambahkan ke konfigurasi webpack dengan menentukan dua format kompilasi yang berbeda untuk setiap versi aplikasi.
Mulai dengan menambahkan konfigurasi untuk skrip lama ke webpack.config.js
:
const legacyConfig = {
entry,
output: {
path: path.resolve(__dirname, "public"),
filename: "[name].bundle.js"
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
presets: [
["@babel/preset-env", {
useBuiltIns: "usage",
targets: {
esmodules: false
}
}]
]
}
},
cssRule
]
},
plugins
}
Perhatikan bahwa alih-alih menggunakan nilai targets
untuk "@babel/preset-env"
,
esmodules
dengan nilai false
digunakan sebagai gantinya. Artinya, Babel menyertakan semua transformasi dan polyfill yang diperlukan untuk menargetkan setiap browser yang belum mendukung modul ES.
Tambahkan objek entry
, cssRule
, dan corePlugins
ke awal
file webpack.config.js
. Semua ini dibagikan antara modul dan
skrip lama yang ditayangkan ke browser.
const entry = {
main: "./src"
};
const cssRule = {
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: "css-loader"
})
};
const plugins = [
new ExtractTextPlugin({filename: "[name].css", allChunks: true}),
new HtmlWebpackPlugin({template: "./src/index.html"})
];
Sekarang, buat objek konfigurasi untuk skrip modul di bawah ini tempat legacyConfig
ditentukan:
const moduleConfig = {
entry,
output: {
path: path.resolve(__dirname, "public"),
filename: "[name].mjs"
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
presets: [
["@babel/preset-env", {
useBuiltIns: "usage",
targets: {
esmodules: true
}
}]
]
}
},
cssRule
]
},
plugins
}
Perbedaan utamanya di sini adalah ekstensi file .mjs
digunakan untuk nama file
output. Nilai esmodules
ditetapkan ke true di sini yang berarti bahwa kode
yang dihasilkan ke dalam modul ini adalah skrip yang lebih kecil dan kurang dikompilasi yang tidak
mengalami transformasi apa pun dalam contoh ini karena semua fitur yang digunakan
sudah didukung di browser yang mendukung modul.
Di bagian paling akhir file, ekspor kedua konfigurasi dalam satu array.
module.exports = [
legacyConfig, moduleConfig
];
Sekarang, tindakan ini akan mem-build modul yang lebih kecil untuk browser yang mendukungnya dan skrip transpil yang lebih besar untuk browser lama.
Browser yang mendukung modul mengabaikan skrip dengan atribut nomodule
.
Sebaliknya, browser yang tidak mendukung modul akan mengabaikan elemen skrip dengan type="module"
. Artinya, Anda dapat menyertakan modul serta penggantian
yang dikompilasi. Idealnya, kedua versi aplikasi harus berada di index.html
seperti ini:
<script type="module" src="main.mjs"></script>
<script nomodule src="main.bundle.js"></script>
Browser yang mendukung modul mengambil dan menjalankan main.mjs
serta mengabaikan
main.bundle.js.
Browser yang tidak mendukung modul melakukan hal sebaliknya.
Perlu diperhatikan bahwa tidak seperti skrip reguler, skrip modul selalu ditangguhkan secara default.
Jika Anda ingin skrip nomodule
yang setara juga ditangguhkan dan hanya dieksekusi setelah
pemrosesan, Anda harus menambahkan atribut defer
:
<script type="module" src="main.mjs"></script>
<script nomodule src="main.bundle.js" defer></script>
Hal terakhir yang perlu dilakukan di sini adalah menambahkan atribut module
dan nomodule
masing-masing ke modul dan skrip lama. Impor ScriptExtHtmlWebpackPlugin di bagian paling atas webpack.config.js
:
const path = require("path");
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ScriptExtHtmlWebpackPlugin = require("script-ext-html-webpack-plugin");
Sekarang, perbarui array plugins
dalam konfigurasi untuk menyertakan plugin ini:
const plugins = [ new ExtractTextPlugin({filename: "[name].css", allChunks: true}), new HtmlWebpackPlugin({template: "./src/index.html"}), new ScriptExtHtmlWebpackPlugin({ module: /\.mjs$/, custom: [ { test: /\.js$/, attribute: 'nomodule', value: '' }, ] }) ];
Setelan plugin ini menambahkan atribut type="module"
untuk semua elemen skrip .mjs
serta atribut nomodule
untuk semua modul skrip .js
.
Modul penayangan dalam dokumen HTML
Hal terakhir yang perlu dilakukan adalah menampilkan elemen skrip lama dan modern ke file HTML. Sayangnya, plugin yang membuat file HTML akhir, HTMLWebpackPlugin
, saat ini tidak mendukung output skrip modul dan nomodule. Meskipun ada solusi dan plugin terpisah yang dibuat untuk memecahkan masalah ini, seperti BabelMultiTargetPlugin dan HTMLWebpackMultiBuildPlugin, pendekatan yang lebih sederhana untuk menambahkan elemen skrip modul secara manual digunakan untuk tujuan tutorial ini.
Tambahkan kode berikut ke src/index.js
di akhir file:
...
</form>
<script type="module" src="main.mjs"></script>
</body>
</html>
Sekarang, muat aplikasi di browser yang mendukung modul, seperti Chrome versi terbaru.
Hanya modul yang diambil, dengan ukuran paket yang jauh lebih kecil karena sebagian besar tidak ditranspil. Elemen skrip lainnya sepenuhnya diabaikan oleh browser.
Jika Anda memuat aplikasi di browser lama, hanya skrip transpil yang lebih besar dengan semua polyfill dan transformasi yang diperlukan yang akan diambil. Berikut adalah screenshot untuk semua permintaan yang dibuat di Chrome versi lama (versi 38).
Kesimpulan
Sekarang Anda sudah memahami cara menggunakan @babel/preset-env
untuk menyediakan polyfill yang diperlukan bagi browser yang ditargetkan. Anda juga mengetahui cara modul JavaScript
dapat meningkatkan performa lebih lanjut dengan mengirimkan dua versi aplikasi
yang ditranspil secara berbeda. Dengan pemahaman yang baik tentang cara kedua teknik ini dapat mengurangi
ukuran paket Anda secara signifikan, lanjutkan dan optimalkan.