Smart Bundling: Cara Melayani Kode Legacy Hanya Untuk Browser Legacy

Diterbitkan: 2022-03-10
Ringkasan cepat Meskipun pengelompokan sumber daya yang efektif di web telah menerima banyak mindshare belakangan ini, cara kami mengirimkan sumber daya front-end kepada pengguna kami tetap sama. Bobot rata-rata JavaScript dan sumber daya gaya yang dikirimkan oleh situs web meningkat — meskipun membangun alat untuk mengoptimalkan situs web tidak pernah sebaik ini. Dengan pangsa pasar browser evergreen yang meningkat pesat dan browser meluncurkan dukungan untuk fitur baru secara bersamaan, apakah sudah saatnya kita memikirkan kembali pengiriman aset untuk web modern?

Sebuah 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?

Transpilasi ke ES5, polyfill platform web, polyfill ES6+, awalan CSS
Lapisan kompatibilitas yang berbeda dari aplikasi web. (Lihat versi besar)

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.

Lebih banyak setelah melompat! Lanjutkan membaca di bawah ini

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.

Bundel modern lebih kecil dari bundel lama karena mengabaikan beberapa lapisan kompatibilitas.
Forking bundel kami. (Lihat versi besar)

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.

Firefox >= 53; Tepi >= 15; Chrome >= 58; iOS >= 10.1
Browser yang mendukung ES6+, atribut CSS baru, dan API browser seperti Promises and Fetch. (Lihat versi besar)

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