Apakah Anda melihat properti CSS di tab Gaya Chrome DevTools terlihat sedikit lebih rapi akhir-akhir ini? Update ini, yang diluncurkan antara Chrome 121 dan 128, adalah hasil dari peningkatan yang signifikan dalam cara kami mengurai dan menyajikan nilai CSS. Dalam artikel ini, kami akan memandu Anda memahami detail teknis transformasi ini—beralih dari sistem pencocokan ekspresi reguler ke parser yang lebih andal.
Mari kita bandingkan DevTools saat ini dengan versi sebelumnya:
Cukup berbeda, bukan? Berikut ini perincian penyempurnaan utama:
color-mix
. Pratinjau praktis yang secara visual mewakili dua argumen warna dalam fungsicolor-mix
.pink
. Pratinjau warna yang dapat diklik untuk warna bernamapink
. Klik untuk membuka pemilih warna agar mudah disesuaikan.var(--undefined, [fallback value])
. Peningkatan penanganan variabel yang tidak ditentukan, dengan variabel yang tidak ditentukan berwarna abu-abu dan nilai penggantian aktif (dalam hal ini, warna HSL) ditampilkan dengan pratinjau warna yang dapat diklik.hsl(…)
: Pratinjau warna lain yang dapat diklik untuk fungsi warnahsl
, yang memberikan akses cepat ke pemilih warna.177deg
: Jam sudut yang dapat diklik yang memungkinkan Anda menarik dan mengubah nilai sudut secara interaktif.var(--saturation, …)
: Link yang dapat diklik ke definisi properti kustom, sehingga memudahkan Anda untuk membuka deklarasi yang relevan.
Perbedaannya sangat mencolok. Untuk mencapai hal ini, kita harus mengajari DevTools untuk memahami nilai properti CSS jauh lebih baik daripada sebelumnya.
Bukankah pratinjau ini sudah tersedia?
Meskipun ikon pratinjau ini mungkin tampak familier, ikon ini tidak selalu ditampilkan secara konsisten, terutama dalam sintaksis CSS yang kompleks seperti contoh di atas. Bahkan jika berhasil, upaya yang signifikan sering kali diperlukan agar alat tersebut berfungsi dengan benar.
Alasannya adalah karena sistem untuk menganalisis nilai telah berkembang secara organik sejak hari-hari pertama DevTools. Namun, CSS belum dapat mengikuti fitur baru yang luar biasa yang baru-baru ini kita dapatkan dari CSS, dan peningkatan kompleksitas bahasa yang sesuai. Sistem ini memerlukan desain ulang total agar dapat mengikuti perkembangan dan itulah yang kami lakukan.
Cara nilai properti CSS diproses
Di DevTools, proses rendering dan dekorasi deklarasi properti di tab Gaya dibagi menjadi dua fase berbeda:
- Analisis struktural. Fase awal ini menguraikan deklarasi properti untuk mengidentifikasi komponen dasarnya dan hubungannya. Misalnya, dalam deklarasi
border: 1px solid red
, deklarasi ini akan mengenali1px
sebagai panjang,solid
sebagai string, danred
sebagai warna. - Merender. Berdasarkan analisis struktural, fase rendering mengubah komponen ini menjadi representasi HTML. Hal ini memperkaya teks properti yang ditampilkan dengan elemen interaktif dan isyarat visual. Misalnya, nilai warna
red
dirender dengan ikon warna yang dapat diklik yang, saat diklik, akan menampilkan pemilih warna untuk modifikasi yang mudah.
Ekspresi reguler
Sebelumnya, kita mengandalkan ekspresi reguler (regex) untuk menguraikan nilai properti untuk analisis struktural. Kami mempertahankan daftar ekspresi reguler untuk mencocokkan bit nilai properti yang kami anggap sebagai dekorasi. Misalnya, ada ekspresi yang cocok dengan warna, panjang, sudut, sub-ekspresi yang lebih rumit, seperti panggilan fungsi var
, dan sebagainya. Kita memindai teks dari kiri ke kanan untuk melakukan analisis nilai, terus mencari ekspresi pertama dari daftar yang cocok dengan bagian teks berikutnya.
Meskipun sering kali bekerja dengan baik, jumlah kasus yang tidak terus bertambah. Selama bertahun-tahun, kami telah menerima banyak laporan bug yang pencocokannya tidak tepat. Ketika kami memperbaikinya – beberapa perbaikan sederhana, sebagian lainnya cukup rumit – kami harus memikirkan kembali pendekatan kami untuk mencegah utang teknis kami. Mari kita lihat beberapa masalahnya.
Pencocokan color-mix()
Regex yang kita gunakan untuk fungsi color-mix()
adalah berikut:
/color-mix\(.*,\s*(?<firstColor>.+)\s*,\s*(?<secondColor>.+)\s*\)/g
Yang cocok dengan sintaksisnya:
color-mix(<color-interpolation-method>, [<color> && <percentage [0,100]>?]#{2})
Coba jalankan contoh berikut untuk memvisualisasikan kecocokan.
const re = /color-mix\(.*,\s*(?<firstColor>.+)\s*,\s*(?<secondColor>.+)\s*\)/g;
// it works - simpler example
const simpler = re.exec('color-mix(in srgb, pink, hsl(127deg 100% 50%))');
console.table(simpler.groups);
re.exec('');
// it doesn't work - complex example
const complex = re.exec('color-mix(in srgb, pink, var(--undefined, hsl(127deg var(--saturation, 100%) 50%)))');
console.table(complex.groups);
Contoh yang lebih sederhana berfungsi dengan baik. Namun, dalam contoh yang lebih kompleks, pencocokan <firstColor>
adalah hsl(177deg var(--saturation
dan pencocokan <secondColor>
adalah 100%) 50%))
, yang sama sekali tidak berarti.
Kami tahu ini adalah masalah. Lagi pula, CSS sebagai bahasa formal tidak reguler, jadi kami sudah menyertakan penanganan khusus untuk menangani argumen fungsi yang lebih rumit, seperti fungsi var
. Namun, seperti yang dapat Anda lihat di screenshot pertama, hal itu masih tidak berfungsi dalam semua kasus.
Pencocokan tan()
Salah satu bug yang dilaporkan lebih lucu adalah tentang fungsi trigonometri tan()
. Regex yang kita gunakan untuk mencocokkan warna menyertakan sub-ekspresi \b[a-zA-Z]+\b(?!-)
untuk mencocokkan warna yang diberi nama seperti kata kunci red
. Kemudian, kita memeriksa apakah bagian yang cocok sebenarnya adalah warna yang dinamai, dan tebak, tan
juga merupakan warna yang dinamai. Jadi, kita salah menafsirkan ekspresi tan()
sebagai warna.
Pencocokan var()
Mari kita lihat contoh lain, fungsi var()
dengan penggantian yang berisi referensi var()
lainnya: var(--non-existent, var(--margin-vertical))
.
Regex kami untuk var()
akan cocok dengan nilai ini. Kecuali, ekspresi akan berhenti mencocokkan pada tanda kurung tutup pertama. Jadi, teks di atas dicocokkan sebagai var(--non-existent, var(--margin-vertical)
. Ini adalah batasan buku teks pencocokan ekspresi reguler. Bahasa yang memerlukan tanda kurung yang cocok pada dasarnya tidak reguler.
Bertransisi ke parser CSS
Jika analisis teks menggunakan ekspresi reguler berhenti berfungsi (karena bahasa yang dianalisis tidak reguler), ada langkah berikutnya yang kanonis: gunakan parser untuk tata bahasa jenis yang lebih tinggi. Untuk CSS, artinya parser untuk bahasa bebas konteks. Bahkan, sistem parser tersebut sudah ada di codebase DevTools: Lezer CodeMirror, yang merupakan fondasi, misalnya, penyorotan sintaksis di CodeMirror, editor yang Anda temukan di panel Sources. Parser CSS Lezer memungkinkan kita menghasilkan hierarki sintaksis (non-abstrak) untuk aturan CSS dan siap digunakan. Kemenangan.
Namun, secara default, kami mendapati bahwa tidak mungkin untuk bermigrasi dari pencocokan berbasis ekspresi reguler ke pencocokan berbasis parser secara langsung: kedua pendekatan tersebut bekerja dari arah yang berlawanan. Saat mencocokkan potongan nilai dengan ekspresi reguler, DevTools akan memindai input dari kiri ke kanan, berulang kali mencoba menemukan kecocokan paling awal dari daftar pola yang diurutkan. Dengan hierarki sintaksis, pencocokan akan dimulai dari bawah ke atas, misalnya, menganalisis argumen panggilan terlebih dahulu, sebelum mencoba mencocokkan panggilan fungsi. Anggaplah ini sebagai mengevaluasi ekspresi aritmetika, yang pertama-tama Anda akan mempertimbangkan ekspresi dalam tanda kurung, lalu operator perkalian, lalu operator penambahan. Dalam framing ini, pencocokan berbasis ekspresi reguler sesuai dengan mengevaluasi ekspresi aritmetika dari kiri ke kanan. Kami benar-benar tidak ingin menulis ulang seluruh sistem pencocokan dari awal: Ada 15 pasangan pencocok dan perender yang berbeda, dengan ribuan baris kode untuknya, yang membuat kami tidak mungkin dapat mengirimkannya dalam satu tonggak pencapaian.
Jadi, kami menemukan solusi yang memungkinkan kami melakukan perubahan inkremental, yang akan kami jelaskan di bawah secara lebih mendetail. Singkatnya, kami mempertahankan pendekatan dua fase, tetapi pada fase pertama kami mencoba mencocokkan sub-ekspresi dari bawah ke atas (sehingga dipisahkan dengan alur ekspresi reguler), dan pada fase kedua kami merender dari atas ke bawah. Dalam kedua fase tersebut, kita dapat menggunakan matcher dan render berbasis ekspresi reguler yang sudah ada, praktis tanpa perubahan, dan dengan demikian dapat memigrasikannya satu per satu.
Tahap 1: Pencocokan dari bawah ke atas
Fase pertama kurang lebih secara akurat dan eksklusif melakukan apa yang tertulis di sampul. Kami melintasi pohon secara berurutan dari bawah ke atas dan mencoba mencocokkan sub-ekspresi pada setiap node hierarki sintaksis yang kami kunjungi. Untuk mencocokkan sub-ekspresi tertentu, matcher dapat menggunakan ekspresi reguler seperti yang dilakukan dalam sistem yang ada. Mulai versi 128, kami sebenarnya masih melakukannya dalam beberapa kasus, misalnya, untuk panjang yang cocok. Atau, matcher dapat menganalisis struktur subhierarki yang di-root pada node saat ini. Hal ini memungkinkannya menangkap error sintaksis dan mencatat informasi struktural secara bersamaan.
Pertimbangkan contoh hierarki sintaksis dari atas:
Untuk hierarki ini, pencocok kami akan diterapkan dalam urutan berikut:
hsl(
177deg
var(--saturation, 100%) 50%)
: Pertama, kita menemukan argumen pertama panggilan fungsihsl
, yaitu sudut hue. Kita mencocokkannya dengan pencocok sudut, sehingga kita dapat mendekorasi nilai sudut dengan ikon sudut.hsl(177deg
var(--saturation, 100%)
50%)
: Kedua, kita menemukan panggilan fungsivar
dengan pencocok var. Untuk panggilan semacam itu, sebaiknya kita melakukan dua hal:- Cari deklarasi variabel dan hitung nilainya, serta tambahkan link dan pop-up ke nama variabel untuk menghubungkannya.
- Hiasi panggilan dengan ikon warna jika nilai yang dihitung adalah warna. Sebenarnya ada hal ketiga, tetapi kita akan membahasnya nanti.
hsl(177deg var(--saturation, 100%) 50%)
: Terakhir, kita mencocokkan ekspresi panggilan untuk fungsihsl
, sehingga kita dapat mendekorasinya dengan ikon warna.
Selain menelusuri sub-ekspresi yang ingin kita hias, sebenarnya ada fitur kedua yang kita jalankan sebagai bagian dari proses pencocokan. Perhatikan bahwa pada langkah #2, kita mencari nilai komputasi untuk sebuah nama variabel. Bahkan, kita mengambil satu langkah lebih jauh dan menyebarkan hasilnya ke atas hierarki. Dan bukan hanya untuk variabel, tetapi juga untuk nilai penggantian. Dijamin bahwa saat mengunjungi node fungsi var
, turunannya telah dikunjungi sebelumnya, sehingga kita sudah mengetahui hasil fungsi var
yang mungkin muncul dalam nilai penggantian. Oleh karena itu, kita dapat mengganti fungsi var
dengan hasilnya dengan mudah dan murah, yang memungkinkan kita menjawab pertanyaan seperti "Apakah hasil panggilan var
ini adalah warna?", seperti yang kita lakukan di langkah #2.
Fase 2: Rendering top-down
Untuk fase kedua, kita membalikkan arah. Dengan mengambil hasil pencocokan dari fase 1, kita merender hierarki menjadi HTML dengan menelusurinya secara berurutan dari atas ke bawah. Untuk setiap node yang dikunjungi, kita memeriksa apakah node tersebut cocok dan jika ya, panggil perender yang sesuai dengan matcher. Kita tidak memerlukan penanganan khusus untuk node yang hanya berisi teks (seperti NumberLiteral
"50%") dengan menyertakan matcher dan perender default untuk node teks. Perender hanya menghasilkan node HTML, yang jika digabungkan, akan menghasilkan representasi nilai properti termasuk dekorasinya.
Untuk contoh hierarki, berikut adalah urutan rendering nilai properti:
- Buka panggilan fungsi
hsl
. Warnanya cocok, jadi panggil perender fungsi warna. Fungsi ini melakukan dua hal:- Menghitung nilai warna sebenarnya menggunakan mekanisme substitusi sambil berjalan untuk argumen
var
apa pun, lalu menggambar ikon warna. - Merender turunan
CallExpression
secara rekursif. Tindakan ini akan otomatis menangani rendering nama fungsi, tanda kurung, dan koma, yang hanya berupa teks.
- Menghitung nilai warna sebenarnya menggunakan mekanisme substitusi sambil berjalan untuk argumen
- Buka argumen pertama panggilan
hsl
. Keduanya cocok, jadi panggil perender sudut, yang menggambar ikon sudut dan teks sudut. - Buka argumen kedua, yang merupakan panggilan
var
. Cocok, jadi panggil var renderer, yang menghasilkan output berikut:- Teks
var(
di awal. - Nama variabel dan menghiasinya dengan link ke definisi variabel atau dengan warna teks abu-abu untuk menunjukkan bahwa variabel tidak ditentukan. Ini juga menambahkan popover ke variabel untuk menampilkan informasi tentang nilainya.
- Koma, lalu secara rekursif merender nilai penggantian.
- Kurung tutup.
- Teks
- Buka argumen terakhir panggilan
hsl
. Tidak cocok, jadi cukup output konten teksnya.
Apakah Anda memperhatikan bahwa dalam algoritma ini, render sepenuhnya mengontrol cara render turunan node yang cocok? Merender turunan secara rekursif bersifat proaktif. Trik ini memungkinkan migrasi bertahap dari rendering berbasis ekspresi reguler ke rendering berbasis hierarki sintaksis. Untuk node yang cocok dengan pencocok ekspresi reguler lama, perender yang sesuai dapat digunakan dalam bentuk aslinya. Dalam istilah hierarki sintaksis, node ini akan bertanggung jawab untuk merender seluruh subhierarki, dan hasilnya (node HTML) dapat dicolokkan dengan rapi ke dalam proses rendering di sekitarnya. Ini memberi kita opsi untuk melakukan port matcher dan perender berpasangan, lalu menukarnya satu per satu.
Fitur keren lainnya dari perender yang mengontrol rendering turunan node yang cocok adalah kemampuannya untuk memahami dependensi antar-ikon yang kita tambahkan. Pada contoh di atas, warna yang dihasilkan oleh fungsi hsl
jelas bergantung pada nilai hue-nya. Artinya, warna yang ditampilkan oleh ikon warna bergantung pada sudut yang ditampilkan oleh ikon sudut. Jika pengguna membuka editor sudut melalui ikon tersebut dan mengubah sudut, kita kini dapat memperbarui warna ikon warna secara real time:
Seperti yang dapat Anda lihat pada contoh di atas, kami juga menggunakan mekanisme ini untuk pasangan ikon lainnya, seperti untuk color-mix()
dan dua saluran warnanya, atau fungsi var
yang menampilkan warna dari penggantiannya.
Dampak performa
Saat mempelajari masalah ini untuk meningkatkan keandalan dan memperbaiki masalah yang sudah lama ada, kami memperkirakan beberapa regresi performa mengingat kami mulai menjalankan parser yang sepenuhnya matang. Untuk mengujinya, kami telah membuat benchmark yang merender sekitar 3,5 ribu deklarasi properti dan membuat profil versi berbasis ekspresi reguler dan berbasis parser dengan throttling 6x pada mesin M1.
Seperti yang kita perkirakan, pendekatan berbasis penguraian ternyata 27% lebih lambat daripada pendekatan berbasis ekspresi reguler untuk kasus tersebut. Pendekatan berbasis ekspresi reguler membutuhkan waktu 11 detik untuk dirender dan pendekatan berbasis parser memerlukan waktu 15 detik untuk dirender.
Mengingat manfaat yang kami peroleh dari pendekatan baru ini, kami memutuskan untuk melanjutkannya.
Ucapan terima kasih
Terima kasih banyak kepada Sofia Emelianova dan Jecelyn Yeen atas bantuan mereka yang sangat berharga dalam mengedit postingan ini.
Mendownload saluran pratinjau
Pertimbangkan untuk menggunakan Chrome Canary, Dev, atau Beta sebagai browser pengembangan default Anda. Saluran pratinjau ini memberi Anda akses ke fitur DevTools terbaru, memungkinkan Anda menguji API platform web canggih, dan membantu Anda menemukan masalah di situs sebelum pengguna melakukannya.
Hubungi tim Chrome DevTools
Gunakan opsi berikut untuk membahas fitur baru, update, atau apa pun yang berkaitan dengan DevTools.
- Kirim masukan dan permintaan fitur kepada kami di crbug.com.
- Laporkan masalah DevTools menggunakan Opsi lainnya > Bantuan > Laporkan masalah DevTools di DevTools.
- Tweet di @ChromeDevTools.
- Berikan komentar di video YouTube Yang baru di DevTools atau video YouTube Tips DevTools.