Dipublikasikan: 31 Maret 2014
Untuk mengidentifikasi dan mengatasi bottleneck kinerja jalur rendering penting, diperlukan pengetahuan yang baik mengenai kesalahan 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 laman yang ditampilkan, dan peningkatan konversi. Untuk meminimalkan lama pengunjung menampilkan layar kosong, kita perlu mengoptimalkan sumber daya yang akan dimuat dan urutannya.
Untuk membantu mengilustrasikan proses ini, mulailah dengan kasus yang sesederhana mungkin dan secara bertahap membangun halaman untuk menyertakan resource, gaya, dan logika aplikasi tambahan. Dalam prosesnya, kita akan mengoptimalkan setiap kasus; kita juga akan melihat di mana saja kesalahan bisa terjadi.
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 hello world
<!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 setengah dari waktunya dihabiskan untuk menunggu di jaringan, dan separuh lagi untuk 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 tampilan pertama yang cepat. Sebenarnya, bila membicarakan tentang 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 ada banyak hal 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 menghilangkan satu permintaan, tetapi waktu onload
dan domContentLoaded
secara efektif sama. Mengapa? Seperti yang kita ketahui, tidak masalah apakah JavaScript inline atau eksternal, karena begitu browser mencapai tag skrip, browser akan memblokir dan menunggu CSSOM dibangun. Lebih jauh, dalam contoh pertama, browser mengunduh CSS maupun JavaScript secara bersamaan 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, ingatlah bahwa semua skrip inline adalah pemblokir parser, tetapi untuk skrip eksternal, kita dapat menambahkan atribut async
untuk membuka kunci parser. Urungkan penyisipan dan coba yang itu:
<!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 pemblokiran parser (eksternal):
JavaScript Async (eksternal):
Jauh lebih baik! Peristiwa domContentLoaded
dipicu tidak lama setelah HTML diuraikan; browser mengetahui untuk tidak memblokir JavaScript dan karena tidak ada skrip pemblokiran parser lainnya, konstruksi CSSOM juga dapat berlangsung secara pararel.
Atau, kita bisa membuat CSS dan JavaScript secara 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
secara efektif sama seperti dalam contoh sebelumnya; sebagai ganti menandai JavaScript kita sebagai asinkron, kita telah menyisipkan baik CSS dan JS ke dalam halaman itu sendiri. Hal ini membuat halaman HTML kita menjadi jauh lebih besar, tetapi sisi terbaliknya adalah browser tidak harus menunggu mengambil sumber daya eksternal mana pun - semuanya ada di laman itu.
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 mencari tahu sendiri strategi yang optimal.
Dengan demikian, mari kita lihat apakah kita bisa mundur selangkah dan mengidentifikasi beberapa pola performa umum.
Pola performa
Laman paling sederhana mungkin hanya terdiri dari markup HTML: tanpa CSS, tanpa JavaScript, atau tipe sumber daya lainnya. Untuk merender halaman ini, browser harus memulai permintaan, menunggu dokumen HTML tiba, mengurainya, membuat DOM, lalu akhirnya 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 perlu satu kali bolak-balik di jaringan untuk mengambil dokumen lengkap. Mengingat cara kerja protokol transpor TCP, file yang lebih besar mungkin perlu bolak-balik lebih banyak. Akibatnya, dalam kasus terbaik, halaman di atas memiliki satu (minimum) jalur rendering penting 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 menghabiskan satu kali bolak-balik jaringan untuk mengambil dokumen HTML, kemudian markup yang diambil akan memberi tahu bahwa kita juga memerlukan file CSS; ini berarti browser harus kembali ke server dan mengambil CSS agar bisa merender laman pada layar. Akibatnya, halaman ini memerlukan minimal dua perjalanan bolak-balik agar dapat ditampilkan. Sekali lagi, file CSS mungkin perlu beberapa kali bolak-balik, sehingga penekanannya adalah "minimum".
Berikut adalah beberapa istilah yang kami gunakan untuk menjelaskan jalur rendering penting:
- Critical Resource: Resource yang dapat memblokir rendering awal halaman.
- Critical Path Length: Jumlah perjalanan bolak-balik, atau total waktu yang diperlukan untuk mengambil semua resource penting.
- Byte Penting: Jumlah total byte yang diperlukan untuk mencapai rendering pertama halaman, yang merupakan jumlah ukuran file transfer dari semua resource penting. Contoh pertama kita, dengan satu halaman HTML, berisi satu resource penting (dokumen HTML); panjang jalur penting juga sama dengan satu perjalanan bolak-balik jaringan (dengan asumsi file berukuran kecil), dan total byte penting hanyalah ukuran transfer dokumen HTML itu sendiri.
Sekarang bandingkan dengan karakteristik jalur penting 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>
Kita telah menambahkan app.js
, yang merupakan aset JavaScript eksternal pada halaman dan sumber daya 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 melihat "jenjang jaringan" halaman ini, Anda akan melihat bahwa baik permintaan CSS maupun JavaScript akan dimulai pada waktu yang hampir bersamaan; browser mendapatkan HTML, menemukan kedua resource, dan memulai kedua permintaan. Hasilnya, halaman yang ditampilkan pada gambar sebelumnya memiliki karakteristik jalur penting berikut:
- 3 resource penting
- 2 atau lebih perjalanan bolak balik 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 memahami cara browser menjadwalkan pengambilannya.
Setelah chatting dengan developer situs, kita menyadari bahwa JavaScript yang disertakan pada laman tidak perlu diblokir; kita memiliki beberapa analytics dan kode lain di dalamnya yang tidak perlu memblokir rendering laman kita. Dengan mengetahui hal itu, kita dapat menambahkan atribut async
ke elemen <script>
untuk membuka blokir 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 juga tidak perlu memblokir peristiwa
domContentLoaded
. - Semakin cepat peristiwa
domContentLoaded
dipicu, semakin cepat logika aplikasi lainnya dapat mulai dieksekusi.
Hasilnya, laman kita yang telah dioptimalkan kini kembali dengan dua sumber daya penting (HTML dan CSS), dengan panjang jalur penting minimum dua perjalanan bolak balik, dan total 9 KB byte penting.
Terakhir, jika lembar gaya CSS hanya perlu dicetak, seperti apa penampilannya?
<!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 pencetakan, browser tidak perlu diblokir di situ 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.