Normal ifadelerin ötesinde: Chrome Geliştirici Araçları'nda CSS değeri ayrıştırmasını geliştirme

Philip Pfaffe
Ergün Erdogmus
Ergün Erdogmus

Chrome Geliştirici Araçları'nın Stiller sekmesindeki CSS özelliklerinin son zamanlarda daha şık göründüğünü fark ettiniz mi? Chrome 121 ile 128 arasında kullanıma sunulan bu güncellemeler, CSS değerlerini ayrıştırma ve sunma şeklimizde yaptığımız önemli bir iyileştirmenin sonucudur. Bu makalede, normal ifade eşleştirme sisteminden daha güçlü bir ayrıştırıcıya geçişin teknik ayrıntıları ele alınmaktadır.

Mevcut DevTools'u önceki sürümle karşılaştıralım:

Üstte: En son Chrome sürümü, Altta: Chrome 121.

Oldukça farklı, değil mi? Temel iyileştirmelerin dökümü aşağıda verilmiştir:

  • color-mix: color-mix işlevindeki iki renk bağımsız değişkenini görsel olarak temsil eden kullanışlı bir önizleme.
  • pink. pink adlı renk için tıklanabilir bir renk önizlemesi. Kolay ayarlamalar için renk seçiciyi açmak üzere tıklayın.
  • var(--undefined, [fallback value]). Tanımlanmamış değişkenler için iyileştirme yapıldı. Tanımlanmamış değişken gri renkte gösterilir ve etkin yedek değer (bu durumda HSL rengi) tıklanabilir bir renk önizlemesiyle gösterilir.
  • hsl(…): hsl renk işlevi için tıklanabilir başka bir renk önizlemesi. Renk seçiciye hızlı erişim sağlar.
  • 177deg: Açı değerini etkileşimli olarak sürükleyip değiştirmenize olanak tanıyan tıklanabilir bir açı saati.
  • var(--saturation, …): Özel mülk tanımının tıklanabilir bağlantısıdır. İlgili beyana atlamayı kolaylaştırır.

Aradaki fark dikkat çekici. Bunu başarmak için DevTools'a CSS mülk değerlerini daha önce olduğundan çok daha iyi anlamasını öğretmemiz gerekiyordu.

Bu önizlemeler zaten mevcut değil miydi?

Bu önizleme simgeleri tanıdık görünse de özellikle yukarıdaki örnekteki gibi karmaşık CSS söz diziminde her zaman tutarlı bir şekilde gösterilmemiştir. Çalıştığı durumlarda bile, doğru şekilde çalışması için genellikle önemli miktarda çaba gerekiyordu.

Bunun nedeni, değerleri analiz etme sisteminin DevTools'un ilk günlerinden beri organik olarak büyümesidir. Ancak CSS'den aldığımız son muhteşem yeni özelliklere ve dil karmaşıklığındaki artışa ayak uyduramadı. Sistemin, değişime ayak uydurabilmesi için tamamen yeniden tasarlanması gerekiyordu ve tam da bunu yaptık.

CSS mülk değerlerinin işlenme şekli

DevTools'ta, Stiller sekmesindeki mülk beyanlarının oluşturulması ve süslenmesi işlemi iki farklı aşamaya ayrılır:

  1. Yapısal analiz. Bu ilk aşamada, temel bileşenleri ve bunların ilişkilerini belirlemek için mülk beyanı incelenir. Örneğin, border: 1px solid red beyanında 1px uzunluğu, solid dize, red ise renk olarak tanınır.
  2. Oluşturma. Oluşturma aşaması, yapısal analizden yararlanarak bu bileşenleri HTML temsiline dönüştürür. Bu sayede, gösterilen mülk metni etkileşimli öğelerle ve görsel ipuçlarıyla zenginleştirilir. Örneğin, red renk değeri, tıklandığında kolayca değişiklik yapmanızı sağlayan bir renk seçiciyi gösteren tıklanabilir bir renk simgesiyle oluşturulur.

Normal ifadeler

Daha önce, yapısal analiz için mülk değerlerini incelemek üzere normal ifadelerden (regex) yararlanıyorduk. Süslemeyi düşündüğümüz mülk değeri parçalarıyla eşleşecek normal ifade listesi oluşturduk. Örneğin, CSS renkleri, uzunlukları, açıları, var işlev çağrıları gibi daha karmaşık alt ifadelerle eşleşen ifadeler vardı. Değer analizi yapmak için metni soldan sağa taradık ve listedeki ilk ifadenin metnin bir sonraki parçasıyla eşleşip eşleşmediğini sürekli olarak kontrol ettik.

Bu yöntem çoğu zaman işe yaramasına rağmen, işe yaramadığı durumlar da giderek artıyordu. Yıllar içinde, eşleştirmenin tam olarak doğru olmadığı çok sayıda hata raporu aldık. Bu sorunları düzeltirken (bazı düzeltmeler basit, bazıları oldukça ayrıntılıydı) teknik borcumuzu kontrol altında tutmak için yaklaşımımızı yeniden düşünmemiz gerekti. Bazı sorunlara göz atalım.

Eşleşen color-mix()

color-mix() işlevi için kullandığımız normal ifade şundu:

/color-mix\(.*,\s*(?<firstColor>.+)\s*,\s*(?<secondColor>.+)\s*\)/g

Söz dizimi şu şekildedir:

color-mix(<color-interpolation-method>, [<color> && <percentage [0,100]>?]#{2})

Eşleşmeleri görselleştirmek için aşağıdaki örneği çalıştırmayı deneyin.

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);

Renk karışımı işlevi için eşleşme sonucu.

Basit örnek iyi çalışır. Ancak daha karmaşık örnekte <firstColor> eşleşmesi hsl(177deg var(--saturation, <secondColor> eşleşmesi ise 100%) 50%)) şeklindedir. Bu da tamamen anlamsızdır.

Bunun bir sorun olduğunun farkındayız. Resmi bir dil olarak CSS normal değildir. Bu nedenle, var işlevleri gibi daha karmaşık işlev bağımsız değişkenleriyle başa çıkmak için özel işleme zaten dahil ettik. Ancak ilk ekran görüntüsünde göreceğiniz gibi bu yöntem her durumda işe yaramadı.

Eşleşen tan()

Bildirilen en komik hatalardan biri trigonometrik tan() işleviyle ilgiliydi . Renkleri eşleştirmek için kullandığımız normal ifade, red anahtar kelimesi gibi adlandırılmış renkleri eşleştirmek için bir alt ifade \b[a-zA-Z]+\b(?!-) içeriyordu. Ardından, eşleşen kısmın gerçekten adlandırılmış bir renk olup olmadığını kontrol ettik ve tahmin edin ne oldu? tan de adlandırılmış bir renk çıktı. Bu nedenle, tan() ifadelerini yanlışlıkla renk olarak yorumladık.

Eşleşen var()

Başka bir var() örneğine bakalım. Bu örnekte, diğer var() referanslarını içeren bir yedek işlevi kullanılmaktadır: var(--non-existent, var(--margin-vertical)).

var() için normal ifademiz bu değeri kolayca eşleştirir. Ancak ilk kapanan parantezde eşleştirme durur. Bu nedenle, yukarıdaki metin var(--non-existent, var(--margin-vertical) olarak eşleştirilir. Bu, normal ifade eşleştirmeyle ilgili bir sınırlamadır. Eşleşen parantez gerektiren diller temel olarak normal değildir.

CSS ayrıştırıcıya geçiş

Normal ifadeler kullanılarak yapılan metin analizi çalışmayı bıraktığında (analiz edilen dil normal olmadığı için) bir sonraki adıma geçilir: Daha yüksek türde bir dil bilgisi için bir ayrıştırıcı kullanılır. CSS için bu, bağlamdan bağımsız diller için bir ayrıştırıcı anlamına gelir. Aslında DevTools kod tabanında zaten böyle bir ayrıştırıcı sistemi mevcuttu: CodeMirror'ın Lezer'i. Bu ayrıştırıcı, Kaynaklar panelinde bulunan düzenleyici olan CodeMirror'da söz dizimi vurgulamanın temelini oluşturur. Lezer'in CSS ayrıştırıcısı, CSS kuralları için (soyut olmayan) söz dizimi ağaçları oluşturmamıza olanak tanıdı ve kullanmaya hazırdı. Zafer.

&quot;hsl(177deg var(--saturation, 100%) 50%)&quot; özellik değeri için söz dizimi ağacı. Lezer ayrıştırıcısı tarafından üretilen sonucun, virgül ve parantez için tamamen söz dizimi düğümlerini çıkararak basitleştirilmiş bir sürümüdür.

Ancak, normal ifade tabanlı eşleştirmeden ayrıştırıcı tabanlı eşleştirmeye doğrudan geçişin kullanıma hazır olarak mümkün olmadığını tespit ettik: İki yaklaşım da zıt yönlerden çalışır. DevTools, değer parçalarını normal ifadelerle eşleştirirken girişi soldan sağa tarar ve sıralı bir kalıp listesinden en erken eşleşmeyi bulmaya çalışır. Söz dizimi ağacında eşleşme aşağıdan yukarıya doğru başlar. Örneğin, işlev çağrısını eşleştirmeye çalışmadan önce önce çağrının bağımsız değişkenleri analiz edilir. Bunu, önce parantez içine alınmış ifadeleri, ardından çarpma operatörlerini ve son olarak toplama operatörlerini dikkate aldığınız bir aritmetik ifadeyi değerlendirmek gibi düşünebilirsiniz. Bu çerçevede, normal ifade tabanlı eşleme, aritmetik ifadenin soldan sağa doğru değerlendirilmesine karşılık gelir. Eşleştirme sisteminin tamamını sıfırdan yeniden yazmak istemedik. Binlerce satır kod içeren 15 farklı eşleştirici ve oluşturma çifti vardı. Bu da tek bir aşamada kullanıma sunma olasılığımızı azaltıyordu.

Bu nedenle, kademeli değişiklikler yapmamıza olanak tanıyan bir çözüm geliştirdik. Bu çözümü aşağıda daha ayrıntılı olarak açıklayacağız. Özetlemek gerekirse, iki aşamalı yaklaşımı koruduk ancak ilk aşamada alt ifadeleri aşağıdan yukarıya doğru eşlemeye çalışıyoruz (böylece normal ifade akışından ayrılıyoruz) ve ikinci aşamada yukarıdan aşağıya doğru oluşturuyoruz. Her iki aşamada da, mevcut normal ifade tabanlı eşleyicileri ve oluşturma araçlarını neredeyse hiç değiştirmeden kullanabildik ve böylece bunları tek tek taşıyabildik.

1. Aşama: Aşağıdan yukarıya eşleme

İlk aşama, kapakta yazılanları az çok tam olarak ve yalnızca gerçekleştirir. Ağacı aşağıdan yukarıya doğru sırayla tarar ve ziyaret ettiğimiz her söz dizimi ağacı düğümündeki alt ifadeleri eşleştirmeye çalışırız. Eşleyici, belirli bir alt ifadeyi eşlemek için mevcut sistemde olduğu gibi normal ifadeyi kullanabilir. 128 sürümü itibarıyla, örneğin eşleşen uzunluklar için hâlâ birkaç durumda bunu yapıyoruz. Alternatif olarak, eşleştirici, mevcut düğümde köklenen alt ağacın yapısını analiz edebilir. Bu sayede söz dizimi hatalarını yakalayabilir ve aynı zamanda yapısal bilgileri kaydedebilir.

Yukarıdaki söz dizimi ağacı örneğini inceleyin:

1. Aşama: Söz dizimi ağacında aşağıdan yukarıya doğru eşleme.

Bu ağaç için eşleştiricilerimiz aşağıdaki sırayla uygulanır:

  1. hsl(177degvar(--saturation, 100%) 50%): İlk olarak, hsl işlev çağrısının ilk bağımsız değişkenini (ton açısı) keşfederiz. Açı değerini açı simgesiyle süsleyebilmek için bir açı eşleyiciyle eşleştiririz.
  2. hsl(177degvar(--saturation, 100%)50%): İkinci olarak, var eşleştiricisiyle var işlev çağrısını keşfederiz. Bu tür aramalarda başlıca iki şey yapmak isteriz:
    • Değişkenin tanımını bulun ve değerini hesaplayın. Ardından, değişken adına sırasıyla bağlantı ve pop-up ekleyin.
    • Hesaplanan değer bir renkse çağrıyı renk simgesiyle süsleyin. Üçüncü bir konu da var ama bunu daha sonra konuşacağız.
  3. hsl(177deg var(--saturation, 100%) 50%): Son olarak, hsl işlevinin çağrı ifadesini eşleştirerek renk simgesiyle süsleyebiliriz.

Süslemek istediğimiz alt ifadeleri aramanın yanı sıra eşleme işleminin bir parçası olarak çalıştırdığımız ikinci bir özellik de var. 2. adımda, bir değişken adı için hesaplanan değeri aradığımızı belirtmiştik. Hatta bunu bir adım daha ileri götürüp sonuçları ağaca yayıyoruz. Yalnızca değişken için değil, yedek değer için de geçerlidir. Bir var işlev düğümü ziyaret edildiğinde, düğümün alt öğelerinin önceden ziyaret edildiği garanti edilir. Bu nedenle, yedek değerde görünebilecek tüm var işlevlerinin sonuçlarını zaten biliriz. Bu nedenle, var işlevlerini kolay ve ucuz bir şekilde anında sonuçlarıyla değiştirebiliriz. Bu da 2. adımda yaptığımız gibi "Bu var çağrısının sonucu bir renk mi?" gibi soruları kolayca yanıtlamamızı sağlar.

2. aşama: Yukarıdan aşağıya oluşturma

İkinci aşamada yönü tersine çeviririz. 1. aşamadaki eşleşme sonuçlarını kullanarak ağacı yukarıdan aşağıya doğru sırayla dolaşarak HTML olarak oluşturuyoruz. Ziyaret edilen her düğüm için eşleşip eşleşmediğini kontrol ederiz. Eşleşirse eşleştiricinin ilgili oluşturucusunu çağırırız. Metin düğümleri için varsayılan bir eşleyici ve oluşturucu ekleyerek yalnızca metin içeren düğümler (NumberLiteral "%50" gibi) için özel işleme gerek kalmamasını sağlarız. Oluşturucular, birlikte kullanıldığında süslemeleri de dahil olmak üzere mülk değerinin temsilini oluşturan HTML düğümleri oluşturur.

2. Aşama: Söz dizimi ağacında yukarıdan aşağıya oluşturma.

Örnek ağaçta, mülk değerinin oluşturulma sırası şu şekildedir:

  1. hsl işlev çağrısını ziyaret edin. Eşleşme olduğu için renk işlevi oluşturucusunu çağırın. İki işlevi vardır:
    • Herhangi bir var bağımsız değişkeni için anında değiştirme mekanizmasını kullanarak gerçek renk değerini hesaplar, ardından bir renk simgesi çizer.
    • CallExpression öğesinin alt öğelerini yinelemeli olarak oluşturur. Bu işlem, işlev adını, parantezleri ve virgülleri (yalnızca metin olan) otomatik olarak oluşturur.
  2. hsl çağrısının ilk bağımsız değişkenini ziyaret edin. Eşleşme olduğu için açı simgesini ve açı metnini çizen açı oluşturucuyu çağırın.
  3. var çağrısı olan ikinci bağımsız değişkeni ziyaret edin. Eşleşme olduğu için renderer değişkenini çağırın. Bu değişken aşağıdakileri döndürür:
    • Başında var( metni.
    • Değişken adını, değişkenin tanımını içeren bir bağlantıyla veya tanımlanmadığını belirtmek için gri metin rengiyle süsler. Ayrıca, değişkene değeri hakkında bilgi göstermek için bir pop-up ekler.
    • Virgülle ayrılan yedek değer, yinelemeli olarak oluşturulur.
    • Kapanış parantezi.
  4. hsl çağrısının son bağımsız değişkenini ziyaret edin. Eşleşme olmadığı için yalnızca metin içeriğini döndürür.

Bu algoritmada, eşleşen bir düğümün alt öğelerinin nasıl oluşturulacağının tamamen bir oluşturma işlemi tarafından kontrol edildiğini fark ettiniz mi? Alt öğeleri yinelemeli olarak oluşturmak proaktiftir. Normal ifade tabanlı oluşturmadan söz dizimi ağacı tabanlı oluşturmaya adım adım geçişi sağlayan şey bu hiledir. Eski bir normal ifade eşleştiriciyle eşleşen düğümler için ilgili oluşturma aracı, orijinal biçiminde kullanılabilir. Söz dizimi ağacı terimleriyle, alt ağacın tamamının oluşturulmasından sorumlu olur ve sonucu (HTML düğümü) çevredeki oluşturma işlemine sorunsuz bir şekilde bağlanabilir. Bu sayede eşleştiricileri ve oluşturma araçlarını çiftler halinde taşıma ve tek tek değiştirme seçeneği elde ettik.

Eşleşen düğümlerinin çocuklarının oluşturulmasını kontrol eden oluşturucuların bir diğer harika özelliği, eklediğimiz simgeler arasındaki bağımlılıklarla ilgili çıkarım yapmamızı sağlamasıdır. Yukarıdaki örnekte, hsl işlevi tarafından üretilen renk, ton değerine bağlıdır. Yani renk simgesinin gösterdiği renk, açı simgesinin gösterdiği açıya bağlıdır. Kullanıcı bu simge aracılığıyla açı düzenleyiciyi açıp açıyı değiştirirse artık renk simgesinin rengini gerçek zamanlı olarak güncelleyebiliriz:

Yukarıdaki örnekte de görebileceğiniz gibi, bu mekanizmayı diğer simge eşlemelerinde de kullanırız. Örneğin, color-mix() ve iki renk kanalı veya yedek renkten renk döndüren var işlevleri.

Performansa olan etkisi

Güvenilirliği artırmak ve uzun süredir devam eden sorunları düzeltmek için bu soruna derinlemesine baktığımızda, tam teşekküllü bir ayrıştırıcıyı kullanmaya başladığımız için performansta bir miktar gerileme bekledik. Bunu test etmek için yaklaşık 3.500 mülk beyanını oluşturan ve M1 makinede 6 kat azaltma oranıyla hem normal ifade tabanlı hem de ayrıştırıcı tabanlı sürümleri profilleyen bir karşılaştırma testi oluşturduk.

Beklediğimiz gibi, bu durumda ayrıştırmaya dayalı yaklaşımın normal ifadeye dayalı yaklaşımdan% 27 daha yavaş olduğu ortaya çıktı. Normal ifade tabanlı yaklaşımın oluşturma süresi 11 saniye, ayrıştırıcı tabanlı yaklaşımın oluşturma süresi ise 15 saniyedir.

Yeni yaklaşımdan elde ettiğimiz kazanımları göz önünde bulundurarak bu yaklaşımı kullanmaya devam etmeye karar verdik.

Teşekkür ederiz

Bu yayını düzenleme konusundaki değerli yardımları için Sofia Emelianova ve Jecelyn Yeen'e çok teşekkür ederiz.

Önizleme kanallarını indirme

Varsayılan geliştirme tarayıcınız olarak Chrome Canary, Yeni Geliştirilenler veya Beta sürümünü kullanabilirsiniz. Bu önizleme kanalları, en son DevTools özelliklerine erişmenize, en yeni web platformu API'lerini test etmenize ve sitenizdeki sorunları kullanıcılarınızdan önce bulmanıza yardımcı olur.

Chrome Geliştirici Araçları Ekibi ile iletişime geçme

Yeni özellikler, güncellemeler veya Geliştirici Araçları ile ilgili başka herhangi bir konu hakkında konuşmak için aşağıdaki seçenekleri kullanın.