Dipublikasikan: 31 Maret 2014
Untuk mengidentifikasi dan mengatasi bottleneck performa jalur rendering penting, diperlukan pengetahuan yang baik tentang kendala umum. Tur terpandu untuk mengidentifikasi pola performa umum akan membantu Anda mengoptimalkan halaman.
Pengoptimalan jalur rendering penting memungkinkan browser menggambar halaman secepat mungkin: semakin cepat mengubah laman menjadi interaksi yang lebih tinggi, semakin banyak halaman yang ditampilkan, dan peningkatan konversi. Untuk meminimalkan waktu yang dihabiskan pengunjung untuk melihat layar kosong, kita perlu mengoptimalkan sumber daya mana yang dimuat dan bagaimana urutannya.
Untuk membantu menggambarkan proses ini, mulailah dengan kasus yang sesederhana mungkin dan secara bertahap bangun laman kita untuk menyertakan sumber daya, gaya, dan logika aplikasi tambahan. Dalam prosesnya, kita akan mengoptimalkan setiap kasus; kita juga akan melihat di mana terjadi kesalahan.
Sejauh ini kita telah memfokuskan secara eksklusif pada apa yang terjadi di browser setelah resource (file CSS, JS, atau HTML) tersedia untuk diproses. Kita telah mengabaikan waktu yang diperlukan untuk mengambil resource, baik dari cache maupun dari jaringan. Kita akan mengasumsikan hal berikut:
- Perjalanan bolak-balik jaringan (latensi propagasi) ke server menghabiskan waktu 100 md.
- Waktu respons server adalah 100 md bagi dokumen HTML dan 10 md bagi semua file lainnya.
Pengalaman halo dunia
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Critical Path: No Style</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
</body>
</html>
Mulai dengan markup HTML dasar dan gambar tunggal; tanpa CSS atau JavaScript. Kemudian, buka panel Jaringan di Chrome DevTools dan periksa waterfall resource yang dihasilkan:
Seperti yang diharapkan, file HTML memerlukan waktu sekitar 200 md untuk didownload. Perhatikan bahwa bagian transparan pada garis biru menyatakan waktu tunggu browser di jaringan tanpa menerima byte respons sementara bagian yang padat menampilkan waktu untuk menyelesaikan pengunduhan setelah byte respons pertama diterima. Ukuran unduhan HTML kecil (<4 K), jadi kita hanya membutuhkan satu perjalanan bolak balik untuk mengambil file lengkap. Hasilnya, dokumen HTML memerlukan waktu sekitar 200 md untuk diambil, dengan separuh waktu dihabiskan untuk jaringan dan setengah lagi menunggu respons server.
Saat konten HTML tersedia, browser akan mengurai byte, mengonversinya menjadi token, dan membuat hierarki DOM. Perhatikan bahwa DevTools dengan mudah melaporkan waktu untuk peristiwa DOMContentLoaded di bagian bawah (216 md), yang juga dinyatakan dengan garis vertikal biru. Selisih antara akhir download HTML dan garis vertikal biru (DOMContentLoaded) adalah waktu yang dihabiskan browser untuk membuat hierarki DOM—dalam hal ini, hanya beberapa milidetik.
Perhatikan bahwa "awesome photo" kita tidak memblokir peristiwa domContentLoaded
. Ternyata, kita dapat membuat hierarki render dan bahkan menggambar halaman tanpa menunggu setiap aset di halaman: tidak semua resource penting untuk menghasilkan cat pertama yang cepat. Sebenarnya, bila membicarakan jalur rendering penting, biasanya kita membicarakan markup HTML, CSS, dan JavaScript. Gambar tidak memblokir render awal halaman—meskipun kita juga harus mencoba menggambar gambar sesegera mungkin.
Dengan demikian, peristiwa load
(juga dikenal sebagai onload
), diblokir pada gambar: DevTools melaporkan peristiwa onload
pada 335 md. Ingatlah bahwa peristiwa onload
menandai titik ketika semua resource yang diperlukan halaman telah didownload dan diproses; pada titik ini, indikator lingkaran berputar pemuatan dapat berhenti berputar di browser (garis vertikal merah dalam jenjang).
Menambahkan JavaScript dan CSS ke dalam campuran
Laman "pengalaman Hello World" kita tampaknya sederhana, tetapi banyak yang terjadi di balik layar. Pada praktiknya, kita juga akan memerlukan lebih dari sekadar HTML: kemungkinannya adalah, kita akan memiliki stylesheet CSS dan satu atau beberapa skrip untuk menambahkan beberapa interaktivitas ke halaman. Tambahkan keduanya ke campuran untuk melihat apa yang terjadi:
<!DOCTYPE html>
<html>
<head>
<title>Critical Path: Measure Script</title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" />
</head>
<body onload="measureCRP()">
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
<script src="timing.js"></script>
</body>
</html>
Sebelum menambahkan JavaScript dan CSS:
Dengan JavaScript dan CSS:
Penambahan file JavaScript dan CSS eksternal akan menambahkan dua permintaan ekstra ke jenjang kita, yang semuanya yang dikirim pada waktu yang hampir bersamaan oleh browser. Namun, perhatikan bahwa sekarang ada perbedaan waktu yang jauh lebih kecil antara peristiwa domContentLoaded
dan onload
.
Apa yang terjadi?
- Tidak seperti contoh HTML biasa, kita juga perlu mengambil dan mem-parse file CSS untuk membangun CSSOM, dan kita membutuhkan DOM maupun CSSOM untuk membangun pohon render.
- Karena halaman juga berisi file JavaScript pemblokir parser, peristiwa
domContentLoaded
akan diblokir hingga file CSS didownload dan diuraikan: karena JavaScript mungkin akan membuat kueri CSSOM, maka kita harus memblokir file CSS hingga selesai didownload agar kita bisa mengeksekusi JavaScript.
Bagaimana jika kita mengganti skrip eksternal dengan skrip inline? Meskipun skrip dibuat inline secara langsung ke dalam halaman, browser tidak dapat mengeksekusinya sebelum CSSOM dibangun. Singkatnya, JavaScript yang disisipkan juga merupakan pemblokir parser.
Dengan demikian, meski memblokir CSS, apakah penyisipan skrip secara inline akan membuat render laman menjadi lebih cepat? Coba dan lihat apa yang terjadi.
JavaScript Eksternal:
JavaScript Disisipkan:
Kita mengurangi satu permintaan, tetapi waktu onload
dan domContentLoaded
pada dasarnya sama. Mengapa? Seperti yang kita ketahui, tidak masalah jika JavaScript inline atau eksternal, karena segera setelah browser mencapai tag skrip, browser akan memblokir dan menunggu hingga GCLID dibangun. Selanjutnya, pada contoh pertama, browser mengunduh CSS dan JavaScript secara paralel dan selesai mengunduhnya pada waktu yang hampir bersamaan. Dalam hal ini, menyisipkan kode JavaScript secara inline tidak terlalu membantu. Namun, ada beberapa strategi yang dapat membuat halaman kita dirender lebih cepat.
Pertama-tama, ingat bahwa semua skrip inline merupakan pemblokiran parser, tetapi untuk skrip eksternal, kita dapat menambahkan atribut async
untuk membuka pemblokiran parser. Urungkan penyisipan dan cobalah:
<!DOCTYPE html>
<html>
<head>
<title>Critical Path: Measure Async</title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" />
</head>
<body onload="measureCRP()">
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
<script async src="timing.js"></script>
</body>
</html>
JavaScript yang memblokir parser (eksternal):
JavaScript asinkron (eksternal):
Jauh lebih baik! Peristiwa domContentLoaded
diaktifkan tepat setelah HTML diuraikan; browser tahu untuk tidak memblokir JavaScript dan karena tidak ada skrip pemblokir parser lainnya, konstruksi dimulai juga dapat berlangsung secara paralel.
Atau, kita bisa membuat CSS dan JavaScript menjadi inline:
<!DOCTYPE html>
<html>
<head>
<title>Critical Path: Measure Inlined</title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<style>
p {
font-weight: bold;
}
span {
color: red;
}
p span {
display: none;
}
img {
float: right;
}
</style>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
<script>
var span = document.getElementsByTagName('span')[0];
span.textContent = 'interactive'; // change DOM text content
span.style.display = 'inline'; // change CSSOM property
// create a new element, style it, and append it to the DOM
var loadTime = document.createElement('div');
loadTime.textContent = 'You loaded this page on: ' + new Date();
loadTime.style.color = 'blue';
document.body.appendChild(loadTime);
</script>
</body>
</html>
Perhatikan bahwa waktu domContentLoaded
pada dasarnya sama seperti pada contoh sebelumnya; alih-alih menandai JavaScript sebagai asinkron, kita telah menyisipkan baik CSS maupun JS ke dalam halaman itu sendiri. Cara ini membuat laman HTML kita jauh lebih besar, tetapi kelebihannya adalah browser tidak perlu menunggu untuk mengambil sumber daya eksternal apa pun; semuanya ada di
pada halaman tersebut.
Seperti yang dapat Anda lihat, bahkan dengan halaman yang sangat mendasar, mengoptimalkan jalur rendering penting adalah latihan yang tidak mudah: kita perlu memahami grafik dependensi antara berbagai resource, kita perlu mengidentifikasi resource mana yang "penting", dan kita harus memilih di antara berbagai strategi untuk cara menyertakan resource tersebut di halaman. Tidak ada satu solusi untuk masalah ini; setiap halaman berbeda. Anda perlu mengikuti proses serupa untuk mengetahui strategi yang optimal.
Oleh karena itu, lihat apakah kita dapat mundur sejenak dan mengidentifikasi beberapa pola performa umum.
Pola performa
Laman paling sederhana mungkin hanya terdiri dari markup HTML: tanpa CSS, tanpa JavaScript, atau tipe resource lainnya. Untuk merender halaman ini, browser harus memulai permintaan, menunggu dokumen HTML tiba, mengurainya, membangun DOM, kemudian merendernya di layar:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Critical Path: No Style</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
</body>
</html>
Waktu antara T0 dan T1 merekam waktu pemrosesan jaringan dan server. Dalam kasus terbaik (jika file HTML-nya kecil), hanya satu jaringan bolak-balik yang akan mengambil seluruh dokumen. Mengingat cara kerja protokol transpor TCP, file yang lebih besar mungkin perlu bolak-balik lebih banyak. Dengan demikian, dalam kasus terbaik, halaman di atas memiliki jalur rendering penting (minimal) satu perjalanan bolak balik.
Sekarang pertimbangkan halaman yang sama, tetapi dengan file CSS eksternal:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" />
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
</body>
</html>
Sekali lagi, kita dikenai perjalanan bolak-balik jaringan untuk mengambil dokumen HTML, lalu markup yang diambil akan memberi tahu bahwa kita juga memerlukan file CSS; ini berarti bahwa browser harus kembali ke server dan mendapatkan CSS sebelum dapat merender halaman di layar. Akibatnya, halaman ini memerlukan minimal dua perjalanan bolak-balik sebelum dapat ditampilkan. Sekali lagi, file CSS mungkin perlu beberapa kali bolak-balik, sehingga penekanannya adalah "minimum".
Berikut ini beberapa istilah yang kami gunakan untuk menjelaskan jalur rendering penting:
- Sumber Daya Penting: Resource yang dapat memblokir rendering awal halaman.
- Panjang Jalur Kritis: Jumlah perjalanan bolak-balik, atau total waktu yang diperlukan untuk mengambil semua resource penting.
- Byte Penting: Jumlah total byte yang diperlukan untuk sampai ke render pertama halaman, yang merupakan jumlah ukuran file transfer dari semua resource penting. Contoh pertama kami, dengan satu laman HTML, berisi satu sumber daya penting (dokumen HTML); panjang jalur kritis juga sama dengan satu perjalanan bolak-balik jaringan (dengan asumsi filenya kecil), dan total byte penting hanyalah ukuran transfer dari dokumen HTML itu sendiri.
Sekarang bandingkan dengan karakteristik jalur kritis dari contoh HTML dan CSS sebelumnya:
- 2 resource penting
- 2 atau lebih perjalanan bolak balik untuk panjang jalur penting minimum
- 9 KB byte penting
Kita memerlukan HTML maupun CSS untuk membangun pohon render. Akibatnya, HTML dan CSS sama-sama menjadi sumber daya penting: CSS hanya diambil setelah browser mendapatkan dokumen HTML, sehingga panjang jalur pentingnya adalah minimum dua kali bolak-balik. Total jumlah kedua resource menjadi 9 KB byte penting.
Sekarang tambahkan file JavaScript ekstra ke dalam campuran.
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" />
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
<script src="app.js"></script>
</body>
</html>
Kami telah menambahkan app.js
, yang merupakan aset JavaScript eksternal di halaman dan resource pemblokiran parser (yang penting). Yang lebih buruk, untuk mengeksekusi file JavaScript, kita harus memblokir dan menunggu CSSOM; ingatlah bahwa JavaScript dapat mengkueri CSSOM dan karena itu browser akan dijeda hingga style.css
didownload dan CSSOM dikonstruksikan.
Dengan demikian, pada praktiknya jika kita melihat "Waterfall jaringan" halaman ini, Anda akan melihat bahwa permintaan CSS dan JavaScript dimulai pada waktu yang hampir bersamaan; browser mendapatkan HTML, menemukan kedua sumber daya, dan memulai kedua permintaan. Akibatnya, halaman yang ditampilkan pada gambar sebelumnya memiliki karakteristik jalur kritis berikut:
- 3 resource penting
- 2 perjalanan bolak-balik atau lebih untuk panjang jalur penting minimum
- 11 KB byte penting
Sekarang kita memiliki tiga resource penting yang jumlahnya hingga 11 KB byte penting, namun panjang jalur penting kita masih dua kali bolak-balik karena kita bisa mentransfer CSS dan JavaScript secara bersamaan. Dengan mengetahui karakteristik jalur rendering penting, Anda dapat mengidentifikasi resource penting dan juga memahami cara browser menjadwalkan pengambilannya.
Setelah mengobrol dengan developer situs kami, kami menyadari bahwa JavaScript yang kami sertakan di halaman kami tidak perlu diblokir; terdapat beberapa analisis dan kode lain di dalamnya yang tidak perlu memblokir rendering halaman. Dengan pengetahuan tersebut, kita dapat menambahkan atribut async
ke elemen <script>
untuk berhenti memblokir parser:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" />
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
<script src="app.js" async></script>
</body>
</html>
Skrip asinkron memiliki beberapa kelebihan:
- Skrip tidak lagi merupakan pemblokiran parser dan bukan bagian dari jalur rendering penting.
- Karena tidak ada skrip penting lainnya, CSS tidak perlu memblokir peristiwa
domContentLoaded
. - Semakin cepat peristiwa
domContentLoaded
diaktifkan, semakin cepat logika aplikasi lain dapat mulai dieksekusi.
Hasilnya, halaman yang dioptimalkan kini kembali ke dua sumber daya penting (HTML dan CSS), dengan panjang jalur kritis minimum dua perjalanan bolak-balik, dan total 9 KB byte penting.
Akhirnya, jika {i>style sheet<i} CSS hanya diperlukan untuk publikasi, seperti apa tampilannya?
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" media="print" />
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
<script src="app.js" async></script>
</body>
</html>
Karena resource style.css hanya digunakan untuk publikasi cetak, browser tidak perlu memblokirnya untuk merender halaman. Oleh karena itu, begitu konstruksi DOM selesai, browser memiliki cukup informasi untuk merender halaman. Hasilnya, halaman ini hanya memiliki satu resource penting (dokumen HTML), dan panjang jalur rendering penting minimum adalah satu perjalanan bolak balik.