Memuat skrip modern

Melewati kode yang tepat untuk setiap browser bukanlah tugas mudah.

Pada artikel ini, kami akan mempertimbangkan beberapa opsi untuk mengatasi masalah ini.



Melewati kode modern oleh browser modern dapat sangat meningkatkan kinerja. Paket JavaScript Anda akan dapat berisi sintaksis modern yang lebih ringkas atau dioptimalkan dan mendukung browser lama.

Di antara alat untuk pengembang, pola modul / nomodule memuat deklaratif modern atau kode lama mendominasi, yang menyediakan browser dengan sumber dan memungkinkan Anda untuk memutuskan mana yang akan digunakan:

<script type="module" src="/modern.js"></script> <script nomodule src="/legacy.js"></script> 

Sayangnya, tidak semuanya begitu sederhana. Pendekatan HTML yang ditunjukkan di atas memicu reload skrip di Edge dan Safari .

Apa yang bisa dilakukan?


Bergantung pada peramban, kami perlu memberikan salah satu opsi untuk skrip yang dikompilasi, tetapi beberapa peramban lama tidak mendukung semua sintaks yang diperlukan untuk ini.

Pertama, ada Perbaikan Safari . Safari 10.1 mendukung modul JS, bukan atribut nomodule dalam skrip, yang memungkinkannya untuk mengeksekusi kode modern dan lama. Namun, acara sebelum- beforeload non-standar yang didukung oleh Safari 10 & 11 dapat digunakan untuk polyfill nomodule .

Metode Satu: Unduhan Dinamis


Anda dapat mengatasi masalah ini dengan menerapkan pemuat skrip kecil. Mirip dengan cara kerja LoadCSS . Alih-alih berharap untuk implementasi modul-ES dan atribut nomodule di nomodule , Anda dapat mencoba untuk mengeksekusi skrip modul sebagai "tes dengan tes lakmus", dan berdasarkan hasilnya, pilih untuk mengunduh kode modern atau lawas.

 <!-- use a module script to detect modern browsers: --> <script type="module"> self.modern = true </script> <!-- now use that flag to load modern VS legacy code: --> <script> addEventListener('load', function() { var s = document.createElement('script') if ('noModule' in s) { // notice the casing s.type = 'module' s.src = '/modern.js' } else { s.src = '/legacy.js' } document.head.appendChild(s) }) </script> 

Tetapi dengan pendekatan ini, Anda harus menunggu skrip modul "lakmus" selesai sebelum Anda menerapkan skrip yang benar. Ini terjadi karena <sript type="module"> selalu bekerja secara tidak sinkron. Tapi ada cara yang lebih baik!

Anda dapat menerapkan opsi independen dengan memeriksa apakah nomodule di browser. Ini berarti bahwa kami akan mempertimbangkan browser seperti Safari 10.1 sebagai usang, bahkan jika mereka mendukung modul. Tapi itu bisa menjadi yang terbaik . Ini kode yang relevan:

 var s = document.createElement('script') if ('noModule' in s) { // notice the casing s.type = 'module' s.src = '/modern.js' } else s.src = '/legacy.js' } document.head.appendChild(s) 

Ini dapat dengan cepat diubah menjadi fungsi yang memuat kode modern atau lawas, dan juga menyediakan pemuatan asinkron:

 <script> $loadjs("/modern.js","/legacy.js") function $loadjs(src,fallback,s) { s = document.createElement('script') if ('noModule' in s) s.type = 'module', s.src = src else s.async = true, s.src = fallback document.head.appendChild(s) } </script> 

Apa kompromi di sini?

Preload

Karena solusinya benar-benar dinamis, browser tidak akan dapat mendeteksi sumber daya JavaScript kami sampai ia memulai kode bootstrap yang kami tulis untuk memasukkan skrip modern atau lawas. Biasanya, browser memindai HTML untuk mencari sumber yang dapat diunduh sebelumnya. Masalah ini terpecahkan, tetapi tidak secara ideal: Anda dapat memuat versi modern paket di browser modern menggunakan <link rl=modulpreload> .

Sayangnya, sejauh ini hanya Chrome yang mendukung modulepreload .

 <link rel="modulepreload" href="/modern.js"> <script type="module">self.modern=1</script> <!-- etc --> 

Jika teknik ini cocok untuk Anda, Anda bisa mengurangi ukuran dokumen HTML yang menjadi tempat Anda menyematkan skrip ini. Jika payload Anda kecil, seperti layar splash atau kode unduhan aplikasi klien, maka menjatuhkan preload scanner tidak akan mempengaruhi kinerja. Dan jika Anda menggambar banyak HTML penting di server untuk dikirim ke browser, maka pemindai preload akan berguna bagi Anda dan pendekatan yang dijelaskan tidak akan menjadi pilihan terbaik untuk Anda.

Inilah yang terlihat seperti solusi ini digunakan:

 <link rel="modulepreload" href="/modern.js"> <script type="module">self.modern=1</script> <script> $loadjs("/modern.js","/legacy.js") function $loadjs(e,d,c){c=document.createElement("script"),self.modern?(c.src=e,c.type="module"):c.src=d,document.head.appendChild(c)} </script> 

Perlu juga dicatat bahwa daftar browser yang mendukung modul JS hampir sama dengan yang mendukung <link rl=preload> . Untuk beberapa situs, mungkin pantas menggunakan <link rl=preload as=script crossorigin> sebagai ganti modulepreload . Performa dapat menurun karena preloading skrip klasik tidak menyiratkan penguraian yang seragam dari waktu ke waktu, seperti halnya dengan modulepreload .

Metode Dua: Lacak Agen Pengguna


Saya tidak memiliki contoh kode yang cocok karena melacak Agen Pengguna adalah tugas yang tidak sepele. Tetapi kemudian Anda dapat membaca artikel yang sangat bagus di Smashing Magazine.

Faktanya, semuanya dimulai dengan <scrit src=bundle.js> di HTML untuk semua browser. Ketika bundle.js diminta, server mem-parsing string Agen Pengguna dari browser yang meminta dan memilih JavaScript yang akan dikembalikan - modern atau lawas, tergantung pada bagaimana browser dikenali.

Pendekatan ini bersifat universal, tetapi menimbulkan konsekuensi serius:

  • Karena server pintar diperlukan, pendekatan ini tidak akan berfungsi dalam kondisi penggunaan statis (generator situs statis, Netlify, dll.).
  • Caching untuk URL JavaScript ini sekarang tergantung pada Agen Pengguna, yang sangat tidak stabil.
  • Definisi UA sulit dan dapat menyebabkan klasifikasi yang salah.
  • Baris User Agent mudah dipalsukan, dan UA baru muncul setiap hari.

Salah satu cara mengatasi keterbatasan ini adalah menggabungkan pola modul / nomodule dengan diferensiasi Agen Pengguna untuk menghindari pengiriman beberapa versi paket ke alamat yang sama. Pendekatan ini mengurangi cache halaman, tetapi menyediakan preloading yang efisien: server penghasil HTML tahu kapan harus menggunakan modulepreload dan kapan harus preload .

 function renderPage(request, response) { let html = `<html><head>...`; const agent = request.headers.userAgent; const isModern = userAgent.isModern(agent); if (isModern) { html += ` <link rel="modulepreload" href="modern.mjs"> <script type="module" src="modern.mjs"></script> `; } else { html += ` <link rel="preload" as="script" href="legacy.js"> <script src="legacy.js"></script> `; } response.end(html); } 

Untuk situs yang sudah menghasilkan HTML di server sebagai tanggapan atas setiap permintaan, ini bisa menjadi transisi yang efektif untuk mengunduh skrip modern.

Metode tiga: browser lama yang bagus


Efek negatif dari pola modul / nomodule terlihat di versi Chrome, Firefox, dan Safari yang lebih lama - jumlahnya sangat kecil, karena peramban diperbarui secara otomatis. Dengan Edge 16-18, situasinya berbeda, tetapi ada harapan: Edge versi baru akan menggunakan mesin rendering berbasis Chromium, yang tidak memiliki masalah seperti itu.

Untuk beberapa aplikasi, ini akan menjadi kompromi yang ideal: unduh kode versi modern di 90% browser, dan berikan kode lama kepada yang lama. Muatan di browser yang lebih lama akan meningkat.

Ngomong-ngomong, tidak ada Agen Pengguna yang rebootnya merupakan masalah tidak menempati pangsa pasar ponsel yang signifikan. Jadi sumber dari semua byte tambahan ini tidak mungkin berupa perangkat seluler atau perangkat dengan prosesor yang lemah.

Jika Anda membuat situs yang terutama diakses oleh peramban seluler atau peramban baru, maka bagi sebagian besar pengguna ini, jenis modul / pola nomodule yang paling sederhana cocok. Pastikan Anda menambahkan perbaikan Safari 10.1 jika perangkat iOS yang lebih lama mendatangi Anda.

    iOS-. <!-- polyfill `nomodule` in Safari 10.1: --> <script type="module"> !function(e,t,n){!("noModule"in(t=e.createElement("script")))&&"onbeforeload"in t&&(n=!1,e.addEventListener("beforeload",function(e){if(e.target===t)n=!0;else if(!e.target.hasAttribute("nomodule")||!n)return;e.preventDefault()},!0),t.type="module",t.src=".",e.head.appendChild(t),t.remove())}(document) </script> <!-- 90+% of browsers: --> <script src="modern.js" type="module'></script> <!-- IE, Edge <16, Safari <10.1, old desktop: --> <script src="legacy.js" nomodule async defer></script> 

Metode Empat: Terapkan Ketentuan Paket


Solusi yang baik adalah menggunakan nomodule untuk mengunduh paket dengan kode yang tidak diperlukan di browser modern, seperti polyfill. Dengan pendekatan ini, dalam kasus terburuk, polyfill akan dimuat atau bahkan dieksekusi (di Safari 10.1), tetapi efeknya akan terbatas pada “re-polyfilling”. Mengingat hari ini pendekatan dengan mengunduh dan mengeksekusi polyfill di semua browser berlaku, ini bisa menjadi peningkatan yang layak.

 <!-- newer browsers will not load this bundle: --> <script nomodule src="polyfills.js"></script> <!-- all browsers load this one: --> <script src="/bundle.js"></script> 

Anda dapat mengkonfigurasi CLI Angular untuk menggunakan pendekatan ini dengan polyfill, seperti yang diperlihatkan Minko Gachev. Setelah mempelajari tentang pendekatan ini, saya menyadari bahwa Anda dapat mengaktifkan injeksi polyfill otomatis di preact-cli - PR ini menunjukkan betapa mudahnya menerapkan teknik ini.

Dan jika Anda menggunakan WebPack, maka ada plugin yang nyaman untuk html-webpack-plugin , yang membuatnya mudah untuk menambahkan nomodule ke paket dengan polyfill.

Jadi apa yang harus dipilih?


Jawabannya tergantung pada situasi Anda. Jika Anda membuat aplikasi klien, dan HTML Anda mengandung lebih dari <sript> , maka Anda mungkin perlu metode pertama .

Jika Anda membuat situs yang dirender di server, dan Anda dapat melakukan caching, maka metode kedua mungkin cocok untuk Anda .

Jika Anda menggunakan render universal , perolehan kinerja yang ditawarkan oleh pemindaian pra-muat bisa sangat penting. Karena itu, perhatikan metode ketiga atau keempat . Pilih yang cocok dengan arsitektur Anda.

Secara pribadi, saya memilih, dengan fokus pada durasi penguraian pada perangkat seluler, dan bukan pada biaya pengunduhan dalam versi desktop. Pengguna seluler menganggap biaya penguraian dan transfer data sebagai biaya aktual (konsumsi baterai dan biaya transfer data), sedangkan pengguna desktop tidak memiliki batasan seperti itu. Saya juga melanjutkan dari pengoptimalan untuk 90% pengguna - pemirsa utama proyek saya menggunakan browser modern dan / atau mobile.

Apa yang harus dibaca


Ingin mempelajari lebih lanjut tentang topik ini? Anda bisa mulai dari sini:

Source: https://habr.com/ru/post/id473994/


All Articles