מודרניזציה של תשתית ה-CSS בכלי הפיתוח

רענון הארכיטקטורה של כלי הפיתוח: מודרניזציה של תשתית CSS בכלי הפיתוח

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

המצב הקודם של CSS בכלי הפיתוח

ה-CSS הוטמע בכלי הפיתוח בשתי דרכים שונות: אחת לקובצי CSS שנעשה בהם שימוש בחלק הקודם של כלי הפיתוח, ואחת לרכיבי האינטרנט המודרניים שנעשה בהם שימוש בכלי הפיתוח.

הטמעת ה-CSS ב-DevTools הוגדרה לפני שנים רבות והיא לא עדכנית. כלי הפיתוח נשאר עם תבנית module.json, ונדרשו מאמצים רבים כדי להסיר את הקבצים האלה. החסימה האחרונה להסרת הקבצים האלה היא הקטע resources, שמשמש לטעינת קובצי CSS.

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

כל קובצי ה-CSS שהיו ב-DevTools נחשבים 'קודמים' כי הם נטענו באמצעות קובץ module.json, שנמצא בתהליך הסרה. כל קובצי ה-CSS היו צריכים להופיע בקטע resources בקובץ module.json באותה ספרייה שבה נמצא קובץ ה-CSS.

דוגמה לקובץ module.json שנותר:

{
  "resources": [
    "serviceWorkersView.css",
    "serviceWorkerUpdateCycleView.css"
  ]
}

לאחר מכן, קובצי ה-CSS האלה יאכלסו מפה גלובלית של אובייקטים שנקראת Root.Runtime.cachedResources כמיפוי מנתיב לתוכן שלהם. כדי להוסיף סגנונות ל-DevTools, צריך להפעיל את registerRequiredCSS עם הנתיב המדויק לקובץ שרוצים לטעון.

דוגמה לקריאה של registerRequiredCSS:

constructor() {
  
  this.registerRequiredCSS('ui/legacy/components/quick_open/filteredListWidget.css');
  
}

הפעולה הזו תאחזר את התוכן של קובץ ה-CSS ותוסיף אותו כרכיב <style> לדף באמצעות הפונקציה appendStyle:

פונקציית appendStyle שמוסיפה CSS באמצעות רכיב סגנון מוטמע:

const content = Root.Runtime.cachedResources.get(cssFile) || '';

if (!content) {
  console.error(cssFile + ' not preloaded. Check module.json');
}

const styleElement = document.createElement('style');
styleElement.textContent = content;
node.appendChild(styleElement);

כשהשקנו רכיבי אינטרנט מודרניים (באמצעות אלמנטים מותאמים אישית), החלטנו בהתחלה להשתמש ב-CSS באמצעות תגי <style> מוטמעים בקובצי הרכיבים עצמם. הדבר הציב בפנינו אתגרים משלו:

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

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

לחקור פתרונות פוטנציאליים

אפשר לחלק את הבעיה לשני חלקים שונים:

  • להבין איך מערכת ה-build מטפלת בקובצי CSS.
  • להבין איך קובצי ה-CSS מיובאים ומשמשים את DevTools.

בדקנו פתרונות אפשריים שונים לכל חלק, והם מפורטים בהמשך.

ייבוא קובצי CSS

היעד של ייבוא CSS ושימוש בו בקובצי TypeScript היה להתקרב ככל האפשר לתקני האינטרנט, לאכוף עקביות בכל DevTools ולהימנע מכפילות של CSS ב-HTML שלנו. בנוסף, רצינו לבחור פתרון שיאפשר לנו להעביר את השינויים שלנו לתקנים חדשים של פלטפורמות אינטרנט, כמו סקריפטים של מודולים של CSS.

לכן, משפטי @import ותגי לא נראו מתאימים ל-DevTools. הם לא יהיו אחידים עם ייבוא בשאר כלי הפיתוח, וכתוצאה מכך תהיה הצגה קצרה של תוכן ללא עיצוב (FOUC). ההעברה ל-CSS Module Scripts תהיה קשה יותר כי יהיה צריך להוסיף את הייבוא באופן מפורש ולטפל בו באופן שונה מאשר בתגים <link>.

const output = LitHtml.html`
<style> @import "css/styles.css"; </style>
<button> Hello world </button>`
const output = LitHtml.html`
<link rel="stylesheet" href="styles.css">
<button> Hello World </button>`

פתרונות אפשריים באמצעות @import או <link>.

במקום זאת, בחרנו למצוא דרך לייבא את קובץ ה-CSS כאובייקט CSSStyleSheet כדי שנוכל להוסיף אותו ל-Shadow DOM (DevTools משתמש ב-Shadow DOM כבר כמה שנים) באמצעות המאפיין adoptedStyleSheets שלו.

אפשרויות של חבילה

היינו צריכים דרך להמיר קובצי CSS לאובייקט CSSStyleSheet כדי שנוכל בקלות לערוך אותם בקובץ TypeScript. בדקנו את Rollup ואת webpack כחבילות פוטנציאליות שיכולות לבצע את הטרנספורמציה הזו בשבילנו. כבר משתמשים ב-Rollup ב-DevTools בגרסה ל-production, אבל הוספת אחת מהחבילות לגרסה ל-production עלולה לגרום לבעיות ביצועים פוטנציאליות כשעובדים עם מערכת ה-build הנוכחית שלנו. השילוב שלנו עם מערכת ה-build של GN ב-Chromium מקשה על היצירה של חבילות, ולכן חבילות נוטים לא להשתלב היטב עם מערכת ה-build הנוכחית של Chromium.

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

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

הפתרון החדש כולל שימוש ב-adoptedStyleSheets כדי להוסיף סגנונות ל-Shadow DOM מסוים, תוך שימוש במערכת ה-build של GN כדי ליצור אובייקטים מסוג CSSStyleSheet שאפשר לאמץ על ידי document או ShadowRoot.

// CustomButton.ts

// Import the CSS style sheet contents from a JS file generated from CSS
import customButtonStyles from './customButton.css.js';
import otherStyles from './otherStyles.css.js';

export class CustomButton extends HTMLElement{
  
  connectedCallback(): void {
    // Add the styles to the shadow root scope
    this.shadow.adoptedStyleSheets = [customButtonStyles, otherStyles];
  }
}

לשימוש ב-adoptedStyleSheets יש כמה יתרונות, כולל:

  • הוא נמצא בתהליך של הפיכתו לתקן אינטרנט מודרני
  • מניעת כפילויות ב-CSS
  • החלת סגנונות רק על DOM בצל, וכך מניעת בעיות שנגרמות כתוצאה משמות כפולים של כיתות או סלקטורים של מזהי קבצים ב-CSS
  • קל לעבור לתקני אינטרנט עתידיים, כמו סקריפטים של מודולים של CSS וטענות נכוֹנוּת (assertions) של ייבוא

החיסרון היחיד של הפתרון היה שצריך לייבא את הקובץ .css.js כדי להשתמש בהצהרות import. כדי לאפשר ל-GN ליצור קובץ CSS במהלך ה-build, כתבנו את הסקריפט generate_css_js_files.js. מערכת ה-build מעבדת עכשיו כל קובץ CSS וממירה אותו לקובץ JavaScript, שמייצא כברירת מחדל אובייקט CSSStyleSheet. זה נהדר כי אנחנו יכולים לייבא את קובץ ה-CSS ולהשתמש בו בקלות. בנוסף, עכשיו אנחנו יכולים גם למזער בקלות את גרסת ה-build של סביבת הייצור, וכך לחסוך בגודל הקובץ:

const styles = new CSSStyleSheet();
styles.replaceSync(
  // In production, we also minify our CSS styles
  /`${isDebug ? output : cleanCSS.minify(output).styles}
  /*# sourceURL=${fileName} */`/
);

export default styles;

דוגמה ל-iconButton.css.js שנוצר מהסקריפט.

העברת קוד מדור קודם באמצעות כללי ESLint

אפשר להעביר את רכיבי האינטרנט בקלות באופן ידני, אבל תהליך ההעברה של שימושים קודמים ב-registerRequiredCSS היה מורכב יותר. שתי הפונקציות העיקריות שרשמו סגנונות מדור קודם היו registerRequiredCSS ו-createShadowRootWithCoreStyles. מכיוון שהשלבים להעברת הקריאות האלה היו מכניים למדי, החלטנו שנוכל להשתמש בכללי ESLint כדי להחיל תיקונים ולהעביר באופן אוטומטי את הקוד הקודם. כבר יש ב-DevTools מספר כללים מותאמים אישית שספציפיים לקוד של DevTools. זה היה שימושי כי ESLint כבר מנתח את הקוד לAbstract Syntax Tree (ראשי תיבות: AST). AST), ויכולנו לשלוח שאילתות לצמתי הקריאה הספציפיים שהיו קריאות לשירותי CSS שרשומים.

הבעיה הגדולה ביותר שהייתה לנו כשכתבנו את כללי ESLint להעלאה הייתה טיפול במקרי קצה. רצינו לוודא שאנחנו מוצאים את האיזון הנכון בין הידיעה אילו מקרים קיצוניים כדאי לתעד ואילו כדאי להעביר באופן ידני. בנוסף, רצינו לוודא שנוכל להודיע למשתמש מתי קובץ .css.js מיובאים לא נוצר באופן אוטומטי על ידי מערכת ה-build, כי כך נוכל למנוע שגיאות מסוג 'הקובץ לא נמצא' בזמן הריצה.

אחד החסרונות של השימוש בכללי ESLint למעבר היה שלא הצלחנו לשנות את קובץ ה-build הנדרש של GN במערכת. המשתמש היה צריך לבצע את השינויים האלה באופן ידני בכל ספרייה. אמנם הדרך הזו דרשה יותר עבודה, אבל היא הייתה דרך טובה לוודא שכל קובץ .css.js שייבאתם נוצר בפועל על ידי מערכת ה-build.

באופן כללי, השימוש בכללי ESLint להעברה הזו היה מאוד שימושי, כי הצלחנו להעביר במהירות את הקוד הקודם לתשתית החדשה. בנוסף, העובדה שה-AST זמין באופן מיידי אפשרה לנו לטפל במספר מקרים קיצוניים בכלל ולתקן אותם באופן אוטומטי ואמין באמצעות Fixer API של ESLint.

מה הדבר הבא?

עד כה, כל רכיבי האינטרנט בכלי הפיתוח של Chromium הועברו לשימוש בתשתית ה-CSS החדשה במקום להשתמש בסגנונות מוטמעים. גם רוב השימושים הקודמים ב-registerRequiredCSS הועברו לשימוש במערכת החדשה. כל מה שנותר הוא להסיר כמה שיותר קבצים מסוג module.json, ולאחר מכן להעביר את התשתית הנוכחית כדי להטמיע סקריפטים של מודולים של CSS בעתיד.

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

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

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

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