Smart Bundling: Cara Melayani Kode Legacy Hanya Untuk Browser Legacy
Diterbitkan: 2022-03-10Sebuah situs web saat ini menerima sebagian besar lalu lintasnya dari browser yang selalu hijau — sebagian besar memiliki dukungan yang baik untuk ES6+, standar JavaScript baru, API platform web baru, dan atribut CSS. Namun, browser lawas masih perlu didukung dalam waktu dekat — pangsa penggunaannya cukup besar untuk tidak diabaikan, bergantung pada basis pengguna Anda.
Sekilas tabel penggunaan caniuse.com mengungkapkan bahwa browser evergreen menempati pangsa terbesar dari pasar browser — lebih dari 75%. Meskipun demikian, normanya adalah memberi awalan CSS, mengubah semua JavaScript kami ke ES5, dan menyertakan polyfill untuk mendukung setiap pengguna yang kami pedulikan.
Meskipun hal ini dapat dipahami dari konteks historis — web selalu tentang peningkatan progresif — pertanyaannya tetap ada: Apakah kami memperlambat web untuk sebagian besar pengguna kami untuk mendukung serangkaian browser lawas yang semakin berkurang?
Biaya Mendukung Browser Lawas
Mari kita coba memahami bagaimana langkah-langkah berbeda dalam pipeline build biasa dapat menambah bobot pada sumber daya front-end kita:
Transpiling Ke ES5
Untuk memperkirakan berapa banyak transpiling berat yang dapat ditambahkan ke bundel JavaScript, saya mengambil beberapa pustaka JavaScript populer yang aslinya ditulis dalam ES6+ dan membandingkan ukuran bundelnya sebelum dan sesudah transpilasi:
Perpustakaan | Ukuran (ES6 diperkecil) | Ukuran (ES5) | Perbedaan |
---|---|---|---|
TodoMVC | 8.4 KB | 11 KB | 24,5% |
Dapat diseret | 53,5 KB | 77,9 KB | 31,3% |
lukson | 75.4 KB | 100,3 KB | 24,8% |
Video.js | 237.2 KB | 335.8 KB | 29,4% |
pixiJS | 370.8 KB | 452 KB | 18% |
Rata-rata, bundel yang tidak ditranspilasikan adalah sekitar 25% lebih kecil daripada yang telah ditranspilasikan ke ES5. Ini tidak mengherankan mengingat ES6+ menyediakan cara yang lebih ringkas dan ekspresif untuk mewakili logika yang setara dan bahwa transpilasi beberapa fitur ini ke ES5 dapat memerlukan banyak kode.
ES6+ Polyfill
Sementara Babel melakukan pekerjaan yang baik dalam menerapkan transformasi sintaksis ke kode ES6+ kami, fitur bawaan yang diperkenalkan di ES6+ — seperti Promise
, Map
and Set
, dan metode array dan string baru — masih perlu polyfill. Menjatuhkan babel-polyfill
apa adanya dapat menambahkan hampir 90 KB ke bundel mini Anda.
Polyfill Platform Web
Pengembangan aplikasi web modern telah disederhanakan karena ketersediaan sejumlah besar API browser baru. Yang umum digunakan adalah fetch
, untuk meminta sumber daya, IntersectionObserver
, untuk mengamati visibilitas elemen secara efisien, dan spesifikasi URL
, yang membuat pembacaan dan manipulasi URL di web menjadi lebih mudah.
Menambahkan polyfill yang sesuai dengan spesifikasi untuk masing-masing fitur ini dapat berdampak nyata pada ukuran bundel.
Awalan CSS
Terakhir, mari kita lihat dampak awalan CSS. Meskipun prefiks tidak akan menambah bobot mati ke bundel seperti halnya transformasi build lainnya — terutama karena prefiks dikompres dengan baik saat di-Gzip — masih ada beberapa penghematan yang harus dicapai di sini.
Perpustakaan | Ukuran (diperkecil, diawali untuk 5 versi browser terakhir) | Ukuran (diperkecil, diawali untuk versi browser terakhir) | Perbedaan |
---|---|---|---|
Bootstrap | 159 KB | 132 KB | 17% |
Bulma | 184 KB | 164 KB | 10,9% |
Dasar | 139 KB | 118 KB | 15.1% |
UI semantik | 622 KB | 569 KB | 8.5% |
Panduan Praktis Untuk Pengiriman Kode Efisien
Mungkin jelas ke mana saya akan pergi dengan ini. Jika kami memanfaatkan pipeline build yang ada untuk mengirimkan lapisan kompatibilitas ini hanya ke browser yang memerlukannya, kami dapat memberikan pengalaman yang lebih ringan kepada pengguna kami lainnya — mereka yang merupakan mayoritas meningkat — sambil mempertahankan kompatibilitas untuk browser lama.
Ide ini tidak sepenuhnya baru. Layanan seperti Polyfill.io adalah upaya untuk melakukan polyfill secara dinamis pada lingkungan browser saat runtime. Tetapi pendekatan seperti ini memiliki beberapa kekurangan:
- Pemilihan polyfill terbatas pada yang terdaftar oleh layanan — kecuali jika Anda menghosting dan memelihara layanan sendiri.
- Karena polyfilling terjadi saat runtime dan merupakan operasi pemblokiran, waktu pemuatan halaman bisa jauh lebih tinggi untuk pengguna di browser lama.
- Melayani file polyfill yang dibuat khusus untuk setiap pengguna memperkenalkan entropi ke sistem, yang membuat pemecahan masalah menjadi lebih sulit ketika terjadi kesalahan.
Juga, ini tidak memecahkan masalah berat yang ditambahkan oleh transpilasi kode aplikasi, yang terkadang bisa lebih besar dari polyfill itu sendiri.
Mari kita lihat bagaimana kita bisa memecahkan semua sumber kembung yang telah kita identifikasi sampai sekarang.
Alat yang Kami Butuhkan
- paket web
Ini akan menjadi alat pembuatan kami, meskipun prosesnya akan tetap serupa dengan alat pembuatan lainnya, seperti Parcel dan Rollup. - daftar peramban
Dengan ini, kami akan mengelola dan menentukan browser yang ingin kami dukung. - Dan kami akan menggunakan beberapa plugin dukungan Daftar Peramban .
1. Mendefinisikan Browser Modern Dan Lama
Pertama, kami ingin memperjelas apa yang kami maksud dengan browser "modern" dan "lawas". Untuk kemudahan pemeliharaan dan pengujian, ada baiknya membagi browser menjadi dua grup terpisah: menambahkan browser yang memerlukan sedikit atau tanpa polyfilling atau transpilasi ke daftar modern kami, dan meletakkan sisanya di daftar lawas kami.
Konfigurasi Daftar Peramban di akar proyek Anda dapat menyimpan informasi ini. Subbagian "Lingkungan" dapat digunakan untuk mendokumentasikan dua grup browser, seperti:
[modern] Firefox >= 53 Edge >= 15 Chrome >= 58 iOS >= 10.1 [legacy] > 1%
Daftar yang diberikan di sini hanyalah sebuah contoh dan dapat disesuaikan dan diperbarui berdasarkan kebutuhan situs web Anda dan waktu yang tersedia. Konfigurasi ini akan bertindak sebagai sumber kebenaran untuk dua set bundel front-end yang akan kita buat selanjutnya: satu untuk browser modern dan satu untuk semua pengguna lainnya.
2. ES6+ Transpiling Dan Polyfilling
Untuk mengubah JavaScript kami dengan cara yang sadar lingkungan, kami akan menggunakan babel-preset-env
.
Mari kita inisialisasi file .babelrc
di root proyek kita dengan ini:
{ "presets": [ ["env", { "useBuiltIns": "entry"}] ] }
Mengaktifkan flag useBuiltIns
memungkinkan Babel untuk secara selektif melakukan polifill fitur bawaan yang diperkenalkan sebagai bagian dari ES6+. Karena memfilter polyfill untuk hanya menyertakan yang dibutuhkan oleh lingkungan, kami mengurangi biaya pengiriman dengan babel-polyfill
secara keseluruhan.
Agar flag ini berfungsi, kita juga perlu mengimpor babel-polyfill
di titik masuk kita.
// In import "babel-polyfill";
Melakukannya akan menggantikan impor babel-polyfill
besar dengan impor granular, yang difilter oleh lingkungan browser yang kami targetkan.
// Transformed output import "core-js/modules/es7.string.pad-start"; import "core-js/modules/es7.string.pad-end"; import "core-js/modules/web.timers"; …
3. Fitur Platform Web Polyfilling
Untuk mengirimkan polyfills untuk fitur platform web kepada pengguna kami, kami perlu membuat dua titik masuk untuk kedua lingkungan:
require('whatwg-fetch'); require('es6-promise').polyfill(); // … other polyfills
Dan ini:
// polyfills for modern browsers (if any) require('intersection-observer');
Ini adalah satu-satunya langkah dalam alur kami yang memerlukan beberapa tingkat perawatan manual. Kita dapat membuat proses ini tidak terlalu rawan kesalahan dengan menambahkan eslint-plugin-compat ke proyek. Plugin ini memperingatkan kita ketika kita menggunakan fitur browser yang belum di-polyfill.
4. Awalan CSS
Terakhir, mari kita lihat bagaimana kita dapat mengurangi awalan CSS untuk browser yang tidak memerlukannya. Karena autoprefixer
adalah salah satu alat pertama dalam ekosistem yang mendukung pembacaan dari file konfigurasi daftar browserslist
, tidak banyak yang bisa kita lakukan di sini.
Membuat file konfigurasi PostCSS sederhana di root proyek sudah cukup:
module.exports = { plugins: [ require('autoprefixer') ], }
Menyatukan Semuanya
Sekarang setelah kita mendefinisikan semua konfigurasi plugin yang diperlukan, kita dapat menyusun konfigurasi webpack yang membaca ini dan menampilkan dua build terpisah di folder dist/modern
dan dist/legacy
.
const MiniCssExtractPlugin = require('mini-css-extract-plugin') const isModern = process.env.BROWSERSLIST_ENV === 'modern' const buildRoot = path.resolve(__dirname, "dist") module.exports = { entry: [ isModern ? './polyfills.modern.js' : './polyfills.legacy.js', "./main.js" ], output: { path: path.join(buildRoot, isModern ? 'modern' : 'legacy'), filename: 'bundle.[hash].js', }, module: { rules: [ { test: /\.jsx?$/, use: "babel-loader" }, { test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader'] } ]}, plugins: { new MiniCssExtractPlugin(), new HtmlWebpackPlugin({ template: 'index.hbs', filename: 'index.html', }), }, };
Untuk menyelesaikannya, kita akan membuat beberapa perintah build di file package.json
kita:
"scripts": { "build": "yarn build:legacy && yarn build:modern", "build:legacy": "BROWSERSLIST_ENV=legacy webpack -p --config webpack.config.js", "build:modern": "BROWSERSLIST_ENV=modern webpack -p --config webpack.config.js" }
Itu dia. Menjalankan yarn build
sekarang seharusnya memberi kita dua build, yang fungsinya setara.
Melayani Bundel yang Tepat Untuk Pengguna
Membuat build terpisah membantu kami mencapai hanya paruh pertama dari tujuan kami. Kami masih perlu mengidentifikasi dan menyajikan paket yang tepat kepada pengguna.
Ingat konfigurasi Daftar Peramban yang kita definisikan sebelumnya? Bukankah lebih baik jika kita dapat menggunakan konfigurasi yang sama untuk menentukan kategori mana yang termasuk dalam kategori pengguna?
Masukkan browserslist-useragent. Seperti namanya, browserslist-useragent
dapat membaca konfigurasi daftar browserslist
kami dan kemudian mencocokkan agen pengguna dengan lingkungan yang relevan. Contoh berikut menunjukkan ini dengan server Koa:
const Koa = require('koa') const app = new Koa() const send = require('koa-send') const { matchesUA } = require('browserslist-useragent') var router = new Router() app.use(router.routes()) router.get('/', async (ctx, next) => { const useragent = ctx.get('User-Agent') const isModernUser = matchesUA(useragent, { env: 'modern', allowHigherVersions: true, }) const index = isModernUser ? 'dist/modern/index.html', 'dist/legacy/index.html' await send(ctx, index); });
Di sini, menyetel flag allowHigherVersions
memastikan bahwa jika versi browser yang lebih baru dirilis — yang belum menjadi bagian dari basis data Can I Use — mereka akan tetap dilaporkan sebagai benar untuk browser modern.
Salah satu fungsi browserslist-useragent
adalah untuk memastikan bahwa quirks platform diperhitungkan saat mencocokkan agen pengguna. Misalnya, semua browser di iOS (termasuk Chrome) menggunakan WebKit sebagai mesin dasar dan akan dicocokkan dengan kueri Daftar Browser khusus Safari masing-masing.
Mungkin tidak bijaksana untuk hanya mengandalkan kebenaran penguraian agen pengguna dalam produksi. Dengan kembali ke bundel lama untuk browser yang tidak ditentukan dalam daftar modern atau yang memiliki string agen pengguna yang tidak diketahui atau tidak dapat diuraikan, kami memastikan bahwa situs web kami masih berfungsi.
Kesimpulan: Apakah Itu Layak?
Kami telah berhasil mencakup aliran ujung ke ujung untuk pengiriman bundel bebas mengasapi ke klien kami. Tetapi masuk akal untuk bertanya-tanya apakah biaya pemeliharaan yang ditambahkan ke proyek ini sepadan dengan manfaatnya. Mari kita evaluasi pro dan kontra dari pendekatan ini:
1. Pemeliharaan Dan Pengujian
Salah satunya diperlukan untuk mempertahankan hanya satu konfigurasi Daftar Peramban yang mendukung semua alat dalam saluran ini. Memperbarui definisi browser modern dan lama dapat dilakukan kapan saja di masa mendatang tanpa harus memfaktorkan ulang konfigurasi atau kode pendukung. Saya berpendapat bahwa ini membuat biaya pemeliharaan hampir dapat diabaikan.
Namun, ada risiko teoretis kecil yang terkait dengan mengandalkan Babel untuk menghasilkan dua bundel kode yang berbeda, yang masing-masing harus berfungsi dengan baik di lingkungannya masing-masing.
Meskipun kesalahan karena perbedaan dalam bundel mungkin jarang terjadi, memantau kesalahan varian ini akan membantu mengidentifikasi dan mengurangi masalah secara efektif.
2. Waktu Pembuatan vs. Waktu Proses
Tidak seperti teknik lain yang lazim saat ini, semua pengoptimalan ini terjadi pada waktu pembuatan dan tidak terlihat oleh klien.
3. Kecepatan yang Ditingkatkan Secara Progresif
Pengalaman pengguna di browser modern menjadi jauh lebih cepat, sementara pengguna di browser lawas terus mendapatkan paket yang sama seperti sebelumnya, tanpa konsekuensi negatif apa pun.
4. Menggunakan Fitur Browser Modern Dengan Mudah
Kami sering menghindari penggunaan fitur browser baru karena ukuran polyfill yang diperlukan untuk menggunakannya. Kadang-kadang, kami bahkan memilih polifill kecil yang tidak memenuhi spesifikasi untuk menghemat ukuran. Pendekatan baru ini memungkinkan kami untuk menggunakan polyfill yang sesuai dengan spesifikasi tanpa terlalu khawatir akan memengaruhi semua pengguna.
Bundel Diferensial Melayani Dalam Produksi
Mengingat keuntungan yang signifikan, kami mengadopsi alur pembangunan ini saat menciptakan pengalaman pembayaran seluler baru untuk pelanggan Urban Ladder, salah satu pengecer furnitur dan dekorasi terbesar di India.
Dalam bundel kami yang sudah dioptimalkan, kami dapat menghemat sekitar 20% pada sumber daya CSS dan JavaScript Gzip yang dikirimkan ke pengguna seluler modern. Karena lebih dari 80% pengunjung harian kami menggunakan browser yang selalu hijau ini, upaya yang dilakukan sepadan dengan dampaknya.
Sumber Daya Lebih Lanjut
- “Memuat Polyfill Hanya Saat Dibutuhkan”, Philip Walton
-
@babel/preset-env
Prasetel Babel yang cerdas - Daftar Browser "Alat"
Ekosistem plugin yang dibuat untuk Daftar Peramban - Dapatkah saya menggunakan?
Tabel pangsa pasar browser saat ini