תמיכה ב-CSS ב-JS בכלי הפיתוח

Alex Rudenko
Alex Rudenko

במאמר הזה נסביר על התמיכה ב-CSS-in-JS בכלי הפיתוח (DevTools) שנוספה ב-Chrome מגרסה 85. באופן כללי, נסביר מה אנחנו מתכוונים ל-CSS-in-JS ואיך הוא שונה מ-CSS רגיל שכלי הפיתוח תומכים בו כבר זמן רב.

מהו CSS-in-JS?

ההגדרה של CSS-in-JS היא קצת מעורפלת. במובן רחב, זוהי גישה לניהול קוד CSS באמצעות JavaScript. לדוגמה, יכול להיות שתוכן ה-CSS מוגדר באמצעות JavaScript ופלט ה-CSS הסופי נוצר על ידי האפליקציה בזמן אמת.

בהקשר של DevTools, CSS-in-JS פירושו שתוכן ה-CSS מוחדר לדף באמצעות ממשקי CSSOM API. הזרקת CSS רגיל מתבצעת באמצעות אלמנטים מסוג <style> או <link>, ויש לו מקור סטטי (למשל צומת DOM או משאב רשת). לעומת זאת, ל-CSS-in-JS לעיתים קרובות אין מקור סטטי. מקרה מיוחד הוא שאפשר לעדכן את התוכן של רכיב <style> באמצעות CSSOM API, וכתוצאה מכך המקור לא מסתנכרן עם גיליון הסגנונות בפועל של ה-CSS.

אם אתם משתמשים בספריית CSS-in-JS כלשהי (למשל styled-component,‏ Emotion,‏ JSS), יכול להיות שהספרייה תזריק סגנונות באמצעות ממשקי API של CSSOM ברקע, בהתאם למצב הפיתוח ולדפדפן.

נבחן כמה דוגמאות לאופן שבו אפשר להחדיר גיליון סגנונות באמצעות CSSOM API, בדומה לאופן שבו פועלות ספריות CSS-in-JS.

// Insert new rule to an existing CSS stylesheet
const element = document.querySelector('style');
const stylesheet = element.sheet;
stylesheet.replaceSync('.some { color: blue; }');
stylesheet.insertRule('.some { color: green; }');

אפשר גם ליצור גיליון סגנונות חדש לגמרי:

// Create a completely new stylesheet
const stylesheet = new CSSStyleSheet();
stylesheet.replaceSync('.some { color: blue; }');
stylesheet.insertRule('.some { color: green; }');

// Apply constructed stylesheet to the document
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];

תמיכה ב-CSS בכלי הפיתוח

התכונה הנפוצה ביותר ב-DevTools לעבודה עם CSS היא החלונית Styles. בחלונית Styles אפשר לראות אילו כללים חלים על רכיב מסוים, לערוך את הכללים ולראות את השינויים בדף בזמן אמת.

לפני השנה שעברה, התמיכה בכללי CSS ששונו באמצעות ממשקי API של CSSOM הייתה מוגבלת למדי: אפשר היה לראות רק את הכללים שהוחלו, אבל לא לערוך אותם. המטרה העיקרית שלנו בשנה שעברה הייתה לאפשר עריכה של כללי CSS-in-JS באמצעות חלונית הסגנונות. לפעמים אנחנו גם קוראים לסגנונות של CSS-in-JS "בנויים" כדי לציין שהם נוצרו באמצעות ממשקי Web API.

נבחן לעומק את אופן העריכה של סגנונות ב-DevTools.

מנגנון עריכת סגנונות ב-DevTools

מנגנון לעריכת סגנונות ב-DevTools

כשבוחרים רכיב ב-DevTools, מוצגת חלונית Styles. בחלונית Styles מונפקת פקודת CDP שנקראת CSS.getMatchedStylesForNode כדי לקבל כללי CSS שחלים על האלמנט. ראשי התיבות CDP הן של Chrome DevTools Protocol. זהו ממשק API שמאפשר לחזית של DevTools לקבל מידע נוסף על הדף שנבדק.

כשהפונקציה CSS.getMatchedStylesForNode מופעלת, היא מזהה את כל גיליונות הסגנונות במסמך ומנתחת אותם באמצעות מנתח ה-CSS של הדפדפן. לאחר מכן, המערכת יוצרת אינדקס שמשויך לכל כלל CSS עם מיקום במקור של גיליון הסגנונות.

יכול להיות שתתהו למה צריך לנתח את ה-CSS שוב. הבעיה היא שבשל סיבות שקשורות לביצועים, הדפדפן עצמו לא מודאג ממיקומי המקור של כללי ה-CSS, ולכן הוא לא מאחסן אותם. אבל ל-DevTools נדרשות המיקומים של מקורות הקוד כדי לתמוך בעריכת CSS. אנחנו לא רוצים שמשתמשי Chrome רגילים ישלמו את מחיר הפגיעה בביצועים, אבל אנחנו רוצים שמשתמשי DevTools יקבלו גישה למיקומי המקור. הגישה הזו לניתוח מחדש מטפלת בשני תרחישי השימוש עם חסרונות מינימליים.

בשלב הבא, ההטמעה של CSS.getMatchedStylesForNode מבקשת ממנוע הסגנונות של הדפדפן לספק כללי CSS שתואמים לרכיב הנתון. לבסוף, השיטה משייכת את הכללים שמוחזרים על ידי מנוע הסגנונות לקוד המקור ומספקת תשובה מובנית לגבי כללי ה-CSS, כדי ש-DevTools תדע איזה חלק מהכלל הוא הסלקטורים או המאפיינים. הוא מאפשר לערוך את הבורר ואת המאפיינים בנפרד ב-DevTools.

עכשיו נעבור לעריכה. זכור לך ש-CSS.getMatchedStylesForNode מחזיר את מיקומי המקור של כל כלל? זה חיוני לעריכה. כשמשנים כלל, כלי הפיתוח מנפיק פקודה אחרת של CDP שמעדכנת את הדף בפועל. הפקודה כוללת את המיקום המקורי של קטע הקוד של הכלל שרוצים לעדכן ואת הטקסט החדש שצריך לעדכן את הקטע באמצעותו.

בקצה העורפי, כשמתנהלים עם קריאת העריכה, כלי הפיתוח מעדכנים את גיליון סגנונות היעד. הוא גם מעדכן את העותק של מקור גיליון הסגנונות שהוא שומר ומעדכן את מיקומי המקור של הכלל המעודכן. בתגובה לקריאה לעריכה, ממשק הקצה של DevTools מקבל בחזרה את המיקומים המעודכנים של קטע הטקסט שעודכן זה עתה.

זה מסביר למה עריכת CSS-in-JS בכלי הפיתוח לא עבדה באופן מובנה: ל-CSS-in-JS אין מקור בפועל שמאוחסן במקום כלשהו וכללי ה-CSS נמצאים בזיכרון הדפדפן במבני הנתונים של CSSOM.

איך הוספנו תמיכה ב-CSS-in-JS

לכן, כדי לתמוך בעריכה של כללי CSS-in-JS, החלטנו שהפתרון הטוב ביותר הוא ליצור מקור לגיליונות סגנונות שנוצרו, שניתן לערוך באמצעות המנגנון הקיים שמתואר למעלה.

השלב הראשון הוא ליצור את טקסט המקור. מנוע הסגנונות של הדפדפן שומר את כללי ה-CSS בכיתה CSSStyleSheet. זו הכיתה שאפשר ליצור ממנה את המופעים מ-JavaScript, כפי שצוין קודם. הקוד ליצירת טקסט המקור הוא:

String InspectorStyleSheet::CollectStyleSheetRules() {
  StringBuilder builder;
  for (unsigned i = 0; i < page_style_sheet_->length(); i++) {
    builder.Append(page_style_sheet_->item(i)->cssText());
    builder.Append('\n');
  }
  return builder.ToString();
}

הפונקציה גורמת לחזרה על הכללים שנמצאים במופע של CSSStyleSheet ומרכיבת מהם מחרוזת אחת. ה-method הזה מופעל כשיוצרים מופע של הכיתה InspectorStyleSheet. הכיתה InspectorStyleSheet עוטפת מופע של CSSStyleSheet ומחלצת מטא-נתונים נוספים שנדרשים ל-DevTools:

void InspectorStyleSheet::UpdateText() {
  String text;
  bool success = InspectorStyleSheetText(&text);
  if (!success)
    success = InlineStyleSheetText(&text);
  if (!success)
    success = ResourceStyleSheetText(&text);
  if (!success)
    success = CSSOMStyleSheetText(&text);
  if (success)
    InnerSetText(text, false);
}

בקטע הקוד הזה, CSSOMStyleSheetText קורא ל-CollectStyleSheetRules באופן פנימי. CSSOMStyleSheetText מופעלת אם גיליון הסגנונות לא נמצא בקוד או שהוא גיליון סגנונות של משאב. בעיקרון, שני קטעי הקוד האלה כבר מאפשרים עריכה בסיסית של גיליונות הסגנון שנוצרים באמצעות המבנה new CSSStyleSheet().

מקרה מיוחד הוא גיליונות הסגנון שמשויכים לתג <style> שעברו מוטציה באמצעות CSSOM API. במקרה כזה, גיליון הסגנונות מכיל את טקסט המקור וכללים נוספים שלא קיימים במקור. כדי לטפל במקרה הזה, אנחנו משיקים שיטה למיזוג הכללים הנוספים האלה בטקסט המקור. הסדר חשוב כאן כי אפשר להוסיף כללי CSS באמצע טקסט המקור המקורי. לדוגמה, נניח שרכיב <style> המקורי הכיל את הטקסט הבא:

/* comment */
.rule1 {}
.rule3 {}

לאחר מכן, בדף הוכנסו כמה כללים חדשים באמצעות JS API, וכך נוצר הסדר הבא של הכללים: ‎.rule0, ‎.rule1, ‎.rule2, ‎.rule3, ‎.rule4. טקסט המקור שנוצר לאחר פעולת המיזוג אמור להיראות כך:

.rule0 {}
/* comment */
.rule1 {}
.rule2 {}
.rule3 {}
.rule4 {}

חשוב לשמור על התגובות וההכנסה המקוריות בתהליך העריכה, כי המיקומים של טקסט המקור של הכללים חייבים להיות מדויקים.

היבט נוסף שמיוחד לגיליונות סגנונות של CSS-in-JS הוא שאפשר לשנות אותם בדף בכל שלב. אם הכללים בפועל של CSSOM לא יהיו מסונכרנים עם גרסת הטקסט, העריכה לא תפעל. לשם כך, הוספנו גלאי שמאפשר לדפדפן להודיע לחלק העורפי של DevTools כשמתבצע שינוי בגיליון סגנונות. לאחר מכן, גיליונות סגנונות שעברו מוטציה מסונכרנים במהלך הקריאה הבאה ל-CSS.getMatchedStylesForNode.

אחרי שכל החלקים האלה מותקנים, אפשר כבר לערוך CSS ב-JS, אבל רצינו לשפר את ממשק המשתמש כדי לציין אם נוצרה גיליון סגנונות. הוספנו מאפיין חדש בשם isConstructed ל-CSS.CSSStyleSheetHeader ב-CDP, שמאפשר לקצה הקדמי להציג בצורה נכונה את המקור של כלל CSS:

גיליון סגנונות שניתן ליצור

מסקנות

לסיכום, עברנו על תרחישי השימוש הרלוונטיים שקשורים ל-CSS-in-JS שלא נתמכים בכלי הפיתוח, וסברנו על הפתרון לתמיכה בתרחישי השימוש האלה. החלק המעניין בהטמעה הזו הוא שהצלחנו לנצל את הפונקציונליות הקיימת על ידי הוספת טקסט מקור רגיל לכללי ה-CSSOM, וכך למנוע צורך בתכנון מחדש מלא של עריכת הסגנונות ב-DevTools.

מידע נוסף זמין בהצעת העיצוב שלנו או בבאג המעקב ב-Chromium, שמפנה לכל התיקונים הקשורים.

הורדת הערוצים לתצוגה מקדימה

מומלץ להשתמש ב-Chrome Canary, ב-Dev או ב-Beta כדפדפן הפיתוח שמוגדר כברירת מחדל. ערוצי התצוגה המקדימה האלה מעניקים לכם גישה לתכונות העדכניות ביותר של DevTools, מאפשרים לכם לבדוק ממשקי API מתקדמים לפלטפורמות אינטרנט ולמצוא בעיות באתר לפני שהמשתמשים שלכם יעשו זאת.

יצירת קשר עם צוות כלי הפיתוח ל-Chrome

אתם יכולים להשתמש באפשרויות הבאות כדי לדון בתכונות החדשות, בעדכונים או בכל דבר אחר שקשור ל-DevTools.