שמתם לב שבזמן האחרון מאפייני ה-CSS בכרטיסייה Styles (סגנונות) בכלי הפיתוח ל-Chrome נראים קצת יותר מלוטשים? העדכונים האלה, שהושקו בין הגרסאות 121 ל-128 של Chrome, הם תוצאה של שיפור משמעותי בדרך שבה אנחנו מנתחים ומציגים ערכים של CSS. במאמר הזה נסביר את הפרטים הטכניים של הטרנספורמציה הזו – מעבר ממערכת התאמה של ביטויים רגולריים למנתח חזק יותר.
נבדוק את DevTools הנוכחי לעומת הגרסה הקודמת:
הבדל משמעותי, נכון? ריכזנו כאן פירוט של השיפורים העיקריים:
color-mix
. תצוגה מקדימה שימושית שמייצגת באופן חזותי את שני ארגומנטים הצבע בפונקציהcolor-mix
.pink
. תצוגה מקדימה של הצבע שאפשר ללחוץ עליה עבור הצבע בעל השםpink
. לוחצים עליו כדי לפתוח בוחר צבעים ולבצע התאמות בקלות.var(--undefined, [fallback value])
. טיפול משופר במשתנים לא מוגדרים, כאשר המשתנה הלא מוגדר מופיע באפור וערך החלופות הפעיל (במקרה הזה, צבע HSL) מוצג עם תצוגה מקדימה של הצבע שניתן ללחוץ עליה.hsl(…)
: תצוגה מקדימה נוספת של צבע שניתן ללחוץ עליה עבור פונקציית הצבעhsl
, שמספקת גישה מהירה לבורר הצבעים.177deg
: שעון זווית שניתן ללחוץ עליו, שמאפשר לכם לגרור ולשנות את ערך הזווית באופן אינטראקטיבי.var(--saturation, …)
: קישור שניתן ללחוץ עליו להגדרת הנכס בהתאמה אישית, שמאפשר לעבור בקלות להצהרה הרלוונטית.
ההבדל בולט. כדי לעשות זאת, נאלצנו ללמד את DevTools להבין ערכים של מאפייני CSS בצורה טובה יותר מבעבר.
האם התצוגות המקדימות האלה לא היו זמינות כבר?
סמלי התצוגה המקדימה האלה אולי נראים מוכרים, אבל הם לא תמיד הוצגו באופן עקבי, במיוחד בסינטקס CSS מורכב כמו בדוגמה שלמעלה. גם במקרים שבהם הם עבדו, בדרך כלל נדרשו מאמצים משמעותיים כדי לגרום להם לפעול כמו שצריך.
הסיבה לכך היא שהמערכת לניתוח ערכים התפתחה באופן אורגני מאז הימים הראשונים של DevTools. עם זאת, הוא לא הצליח לעמוד בקצב של התכונות החדשות והמדהימות שאנחנו מקבלים מ-CSS, וגם לא בהתאם לעלייה ברמת המורכבות של השפה. כדי להתקדם עם הזמן, נדרשה מערכת עם עיצוב מחדש מלא, וזה בדיוק מה שעשינו.
איך מתבצע העיבוד של ערכי נכסי CSS
בכלי הפיתוח, תהליך היצירה וההוספה של עיטורים להצהרות על נכסים בכרטיסייה Styles מחולק לשני שלבים נפרדים:
- ניתוח מבני. בשלב הראשוני הזה מתבצעת ניתוח של הצהרת הנכס כדי לזהות את הרכיבים הבסיסיים שלו ואת היחסים ביניהם. לדוגמה, בהצהרה
border: 1px solid red
, המערכת תזהה את1px
כאורך, אתsolid
כמחרוזת ואתred
כצבע. - עיבוד. בשלב העיבוד, על סמך הניתוח המבני, הרכיבים האלה הופכים לייצוג HTML. כך אפשר להעשיר את הטקסט של הנכס המוצג ברכיבים אינטראקטיביים וברמזים חזותיים. לדוגמה, ערך הצבע
red
מוצג באמצעות סמל צבע שניתן ללחוץ עליו. כשלוחצים עליו, מוצג בוחר צבעים שמאפשר לשנות את הצבע בקלות.
ביטויים רגולריים
בעבר הסתמכנו על ביטויים רגולריים (regex) כדי לנתח את ערכי המאפיינים לצורך ניתוח מבני. שמרתנו רשימה של ביטויים רגולריים (regex) שתואמים לחלקים של ערכי המאפיינים שרצינו לקשט. לדוגמה, היו ביטויים שתואמים לצבעים, לאורך, לזוויות ולביטויים משנה מורכבים יותר של CSS, כמו קריאות לפונקציות var
וכו'. סרקנו את הטקסט משמאל לימין כדי לבצע ניתוח ערכים, תוך חיפוש מתמיד של הביטוי הראשון ברשימה שתואם לחלק הבא בטקסט.
הפתרון הזה עבד מצוין ברוב הזמן, אבל מספר המקרים שבהם הוא לא עבד הלך וגדל. במשך השנים קיבלנו מספר לא מבוטל של דיווחים על באגים שבהם ההתאמה לא הייתה מדויקת. כשטיפלנו בהן – חלק מהתיקונים היו פשוטים, וחלקם היו מורכבים למדי – נאלצנו לחשוב מחדש על הגישה שלנו כדי לצמצם את החוב הטכני. נבחן כמה מהבעיות.
התאמה ל-color-mix()
ביטוי ה-regex שבו השתמשנו בפונקציה color-mix()
היה:
/color-mix\(.*,\s*(?<firstColor>.+)\s*,\s*(?<secondColor>.+)\s*\)/g
התאמה לתחביר שלו:
color-mix(<color-interpolation-method>, [<color> && <percentage [0,100]>?]#{2})
אפשר לנסות להריץ את הדוגמה הבאה כדי להציג חזותית את ההתאמות.
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);
הדוגמה הפשוטה פועלת בצורה תקינה. עם זאת, בדוגמה המורכבת יותר, ההתאמה של <firstColor>
היא hsl(177deg var(--saturation
וההתאמה של <secondColor>
היא 100%) 50%))
, שפירושן לא ברור בכלל.
ידענו שזו בעיה. אחרי הכל, CSS כשפה פורמלית היא לא רגילה, ולכן כבר הוספנו טיפול מיוחד כדי לטפל בארגומנטים מורכבים יותר של פונקציות, כמו פונקציות var
. עם זאת, כפי שאפשר לראות בצילום המסך הראשון, הפתרון הזה עדיין לא עבד בכל המקרים.
התאמה ל-tan()
אחד הבאגים שדווחו והיה מצחיק במיוחד היה לגבי הפונקציה הטריגונומטרית tan()
. ביטוי ה-regex שבו השתמשנו להתאמת צבעים כלל ביטוי משנה \b[a-zA-Z]+\b(?!-)
להתאמה לצבעים עם שם, כמו מילת המפתח red
. לאחר מכן בדקנו אם החלק התואם הוא למעשה צבע בעל שם, ותאמינו או לא, גם tan
הוא צבע בעל שם! לכן, פירשנו בטעות ביטויים של tan()
כצבעים.
התאמה ל-var()
נבחן דוגמה נוספת: פונקציות var()
עם חלופה שמכילה הפניות אחרות ל-var()
: var(--non-existent, var(--margin-vertical))
.
ביטוי ה-regex שלנו עבור var()
יתאים לערך הזה. אלא שהיא תפסיק להתאים את הביטוי בסוגריה הסגורה הראשונה. לכן הטקסט שלמעלה תואם כ-var(--non-existent, var(--margin-vertical)
. זוהי הגבלה ידועה של התאמה לביטוי רגולרי. שפות שדורשות סוגריים תואמים הן לא רגולריות באופן מהותי.
מעבר למנתח CSS
כשניתוח הטקסט באמצעות ביטויים רגולריים מפסיק לפעול (כי השפה שנותחת היא לא רגולרית), יש שלב קנוני הבא: שימוש בניתוח תחביר של סוג גבוה יותר. ב-CSS, המשמעות היא שימוש בניתוח תחביר של שפות ללא הקשר. למעשה, מערכת מנתח כזו כבר הייתה בקוד של DevTools: Lezer של CodeMirror, שמהווה את הבסיס, למשל, להדגשת תחביר ב-CodeMirror, העורך שמופיע בחלונית מקורות. מנתח ה-CSS של Lezer אפשר לנו ליצור עצי תחביר (לא מופשטים) של כללי CSS, והוא היה מוכן לשימוש. ניצחון.
עם זאת, גילינו שלא ניתן לעבור ישירות מהתאמה שמבוססת על ביטוי רגולרי להתאמה שמבוססת על מנתח: שתי הגישות פועלות בכיוונים מנוגדים. כשמתבצעת התאמה של קטעי ערכים לביטויים רגולריים, DevTools סורק את הקלט משמאל לימין ומנסה שוב ושוב למצוא את ההתאמה המוקדמת ביותר מרשימת דפוסים מסודרת. בעץ תחביר, ההתאמה תתחיל מלמטה למעלה. לדוגמה, תחילה יתבצע ניתוח של הארגומנטים של קריאה, לפני שתתבצע ניסיון להתאים את קריאת הפונקציה. אפשר לחשוב על זה כעל הערכה של ביטוי אריתמטי, שבו קודם מתייחסים לביטויים בסוגריים, אחר כך למפעילים מכפילים ואז למפעילים נוספים. במסגרת הזו, ההתאמה שמבוססת על ביטוי רגולרי תואמת להערכת הביטוי האריתמטי מימין לשמאל. לא רצינו לכתוב מחדש את כל מערכת ההתאמה מאפס: היו 15 צמדי מתאמים ומעבדים שונים, עם אלפי שורות קוד, ולכן לא סביר שנוכל להשיק את המערכת באירוע ציון דרך אחד.
לכן פיתחנו פתרון שאפשר לנו לבצע שינויים מצטברים, כפי שנפרט בהמשך. בקיצור, שמרתם על הגישה של שני שלבים, אבל בשלב הראשון אנחנו מנסים להתאים ביטויים משניים מלמטה למעלה (כך שאנחנו מפירים את הזרימה של ביטוי ה-regex), ובשלב השני אנחנו מבצעים רינדור מלמעלה למטה. בשני השלבים, הצלחנו להשתמש ב-matchers וב-renders הקיימים שמבוססים על ביטויים רגולריים, כמעט ללא שינוי, וכך הצלחנו להעביר אותם אחד אחרי השני.
שלב 1: התאמה מלמטה למעלה
בשלב הראשון, כמעט כל מה שכתוב על הכריכה מתבצע. אנחנו עוברים על העץ לפי הסדר מלמטה למעלה ומנסים להתאים ביטויים משניים בכל צומת של עץ תחביר שאנחנו מבקרים בו. כדי להתאים לביטוי משנה ספציפי, מתאמת יכול להשתמש בביטוי רגולרי (regex) בדיוק כמו שהוא עשה במערכת הקיימת. החל מגרסה 128, אנחנו עדיין עושים זאת בכמה מקרים, למשל, לאורכים תואמים. לחלופין, מתאמת יכול לנתח את המבנה של עץ המשנה שמתחיל בצומת הנוכחי. כך הוא יכול לזהות שגיאות תחביר ולתעד מידע מבני בו-זמנית.
נבחן את הדוגמה לעץ תחביר שלמעלה:
בעץ הזה, האלגוריתמים שלנו ייכנסו לתוקף בסדר הבא:
hsl(
177deg
var(--saturation, 100%) 50%)
: קודם כול, אנחנו מגלים את הארגומנט הראשון של קריאת הפונקציהhsl
, זווית הגוון. אנחנו מתאימים אותו למתאמת זוויות, כדי שנוכל לקשט את ערך הזווית בסמל הזווית.hsl(177deg
var(--saturation, 100%)
50%)
: בשלב השני, אנחנו מגלים את קריאת הפונקציהvar
באמצעות מתאם var. בקריאות כאלה אנחנו רוצים בעיקר לעשות שני דברים:- מחפשים את ההצהרה על המשתנה ומחשבים את הערך שלו, ומוסיפים קישור וחלון קופץ לשם המשתנה כדי לקשר אליהם, בהתאמה.
- אם הערך המחושב הוא צבע, אפשר לקשט את הקריאה באמצעות סמל צבע. למעשה, יש עוד דבר שלישי, אבל נדבר עליו בהמשך.
hsl(177deg var(--saturation, 100%) 50%)
: לבסוף, אנחנו מתאימים את ביטוי הקריאה לפונקציהhsl
כדי שנוכל לקשט אותו בסמל הצבע.
בנוסף לחיפוש ביטויים משנה שאנחנו רוצים לקשט, יש למעשה תכונה שנייה שאנחנו מפעילים כחלק מתהליך ההתאמה. שימו לב שבשלב 2 אמרנו שאנחנו מחפשים את הערך המחושב של שם משתנה. למעשה, אנחנו עוברים צעד אחד קדימה ומפיצים את התוצאות למעלה בעץ. ולא רק עבור המשתנה, אלא גם עבור ערך החלופי! מובטח שבביקור בצומת של פונקציית var
, הצמתים הצאצאים שלו כבר נצפו, כך שכבר ידועות לנו התוצאות של כל הפונקציות מסוג var
שעשויות להופיע בערך החלופי. לכן אנחנו יכולים להחליף פונקציות var
בתוצאות שלהן בזמן אמת בקלות ובזול, וכך לענות בקלות על שאלות כמו "האם התוצאה של קריאת var
הזו היא צבע?", כפי שעשינו בשלב 2.
שלב 2: רינדור מלמעלה למטה
בשלב השני, אנחנו משנים את הכיוון. על סמך תוצאות ההתאמה משלב 1, אנחנו מייצרים את העץ ב-HTML על ידי סריקה שלו לפי הסדר מלמעלה למטה. לכל צומת שנבקר, אנחנו בודקים אם הוא תואם, ואם כן, קוראים למעבד התצוגה התואם של המתאמים. כדי להימנע מהצורך בטיפול מיוחד בצמתים שמכילים רק טקסט (כמו NumberLiteral
'50%'), אנחנו כוללים מתאים ומעבד ברירת מחדל לצמתי טקסט. הרסטוררים פשוט מניבים צמתים של HTML, שכשמשלבים אותם יוצרים את הייצוג של ערך הנכס, כולל הקישוט שלו.
בעץ לדוגמה, זהו הסדר שבו ערך המאפיין מוצג:
- פונקציית הקריאה
hsl
. הייתה התאמה, לכן צריך לקרוא למעבד של פונקציית הצבע. הוא עושה שתי דברים:- הפונקציה מחשבת את ערך הצבע בפועל באמצעות מנגנון ההחלפה בזמן אמת לכל ארגומנטים של
var
, ולאחר מכן מצייר סמל צבע. - עיבוד חזותי רפטורסיבי של הצאצאים של
CallExpression
. כך יתבצע באופן אוטומטי עיבוד של שם הפונקציה, הסוגריים והפסיקים, שהם רק טקסט.
- הפונקציה מחשבת את ערך הצבע בפועל באמצעות מנגנון ההחלפה בזמן אמת לכל ארגומנטים של
- נכנסים לארגומנט הראשון של קריאת
hsl
. הוא התאים, לכן צריך לקרוא למעבד התצוגה של הזווית, שמצייר את סמל הזווית ואת הטקסט של הזווית. - נכנסים לארגומנט השני, שהוא הקריאה ל-
var
. התוצאה תואמת, לכן צריך להפעיל את המשתנה renderer, שמציג את הפלט הבא:- הטקסט
var(
בהתחלה. - שם המשתנה, ומוסיף לו קישור להגדרת המשתנה או צבע טקסט אפור כדי לציין שהוא לא הוגדר. בנוסף, הוא מוסיף למשתנה חלון קופץ כדי להציג מידע על הערך שלו.
- הפסיק ואז ערך החזרה האוטומטית מוצגים באופן רפליקטיבי.
- סוגר סוגריים.
- הטקסט
- אפשר לעיין בארגומנט האחרון של קריאת
hsl
. לא הייתה התאמה, ולכן צריך להפיק רק את תוכן הטקסט שלו.
שמתם לב שבאלגוריתם הזה, ה-render קובע באופן מלא איך הילדים של צומת תואם יוצגו? עיבוד חזותי רפלוקטיבי של הצאצאים הוא יזום. הטריק הזה אפשר לבצע העברה מדורגת מעיבוד שמבוסס על ביטוי רגולרי לעיבוד שמבוסס על עץ תחביר. בצמתים שתואמים למתאמי ביטוי רגולריים מדור קודם, אפשר להשתמש במעבד התצוגה התואם בצורתו המקורית. במונחים של עץ תחביר, הוא יישא באחריות לעיבוד הגרף המשני כולו, והתוצאה שלו (צומת HTML) תוכל להשתלב בצורה חלקה בתהליך העיבוד שמקיף אותו. כך הייתה לנו אפשרות להעביר מתאמים ומעבד תמונות בזוגות, ולהחליף אותם אחד אחרי השני.
תכונה מגניבה נוספת של מנועי עיבוד תמונה ששולטים בעיבוד התמונות של הצאצאים של הצומת התואם היא שהם מאפשרים לנו להסיק מסקנות לגבי יחסי התלות בין הסמלים שאנחנו מוסיפים. בדוגמה שלמעלה, ברור שהצבע שנוצר על ידי הפונקציה hsl
תלוי בערך הגוון שלו. כלומר, הצבע שמוצג בסמל הצבע תלוי בזווית שמוצגת בסמל הזווית. אם המשתמש פותח את עורך הזווית דרך הסמל הזה ומשנה את הזווית, אנחנו יכולים עכשיו לעדכן את הצבע של סמל הצבע בזמן אמת:
כפי שאפשר לראות בדוגמה שלמעלה, אנחנו משתמשים במנגנון הזה גם בשילובים אחרים של סמלים, כמו color-mix()
ושני ערוצי הצבעים שלו, או פונקציות var
שמחזירות צבע מהחלופה שלה.
ההשפעה על הביצועים
כשהתחלנו לטפל בבעיה הזו כדי לשפר את האמינות ולתקן בעיות קיימות, ציפינו לירידה מסוימת בביצועים, כי התחלנו להריץ מנתח מלא. כדי לבדוק את זה, יצרנו מדד ביצועים שמרינדר כ-3,500 הצהרות על נכסים, וביצענו פרופיל גם לגרסה שמבוססת על ביטוי רגולרי וגם לגרסה שמבוססת על מנתח, עם 6x ניתוב תעבורה (throttling) במכונה עם מעבד M1.
כצפוי, הגישה שמבוססת על ניתוח הייתה איטית ב-27% מהגישה שמבוססת על ביטוי רגולרי במקרה הזה. הזמן שדרש העיבוד של הגישה שמבוססת על ביטוי רגולרי היה 11 שניות, והזמן שדרש העיבוד של הגישה שמבוססת על מנתח היה 15 שניות.
לאור היתרונות שאנחנו מקבלים מהגישה החדשה, החלטנו להמשיך בה.
תודות
אנחנו רוצים להודות מקרב לב ל-Sofia Emelianova ול-Jecelyn Yeen על העזרה החשובה בעריכת הפוסט הזה.
הורדת הערוצים לתצוגה מקדימה
מומלץ להשתמש ב-Chrome Canary, ב-Dev או ב-Beta כדפדפן הפיתוח שמוגדר כברירת מחדל. ערוצי התצוגה המקדימה האלה מעניקים לכם גישה לתכונות העדכניות ביותר של DevTools, מאפשרים לכם לבדוק ממשקי API מתקדמים לפלטפורמות אינטרנט ולמצוא בעיות באתר לפני שהמשתמשים שלכם יעשו זאת.
יצירת קשר עם צוות כלי הפיתוח ל-Chrome
אתם יכולים להשתמש באפשרויות הבאות כדי לדון בתכונות החדשות, בעדכונים או בכל דבר אחר שקשור ל-DevTools.
- אתם יכולים לשלוח לנו משוב ובקשות להוספת תכונות בכתובת crbug.com.
- מדווחים על בעיה בכלי הפיתוח באמצעות הסמל אפשרויות נוספות > עזרה > דיווח על בעיה בכלי הפיתוח ב-DevTools.
- שולחים ציוץ אל @ChromeDevTools.
- אפשר להשאיר תגובות בסרטונים של מה חדש בכלי הפיתוח ב-YouTube או בסרטונים של טיפים לכלי הפיתוח ב-YouTube.