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

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

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

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

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

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

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

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

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

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

לאחר מכן, קובצי ה-CSS האלה יאכלסו מפת אובייקטים גלובלית שנקראת Root.Runtime.cachedResources כמיפוי מנתיב לתוכן שלהם. כדי להוסיף סגנונות לכלי הפיתוח, צריך להפעיל את השיטה 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 של כלי הפיתוח הוגדל על ידי ה-CSS הכפול שנוסף על ידי מספר מופעים של אותו רכיב אינטרנט.

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

בדיקת פתרונות אפשריים

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

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

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

ייבוא קובצי CSS

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

מהסיבות האלה, נראה שהצהרות @import ותגי לא מתאימים לכלי הפיתוח. הם לא יהיו אחידים בייבוא של שאר כלי הפיתוח, ויגרמו לFlash של תוכן לא מעוצב (FOUC). יהיה קשה יותר לעבור לסקריפטים של מודולים של CSS, מפני שיהיה צורך להוסיף פעולות ייבוא באופן מפורש ולטפל בהן באופן שונה מאשר תגי <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 (בכלי הפיתוח נעשה שימוש ב-Shadow DOM כבר כמה שנים) באמצעות המאפיין ל-adoptedStyleSheets.

אפשרויות של Bundler

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

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

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

הפתרון החדש כולל שימוש ב-adoptedStyleSheets כדי להוסיף סגנונות ל-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)

האזהרה היחידה לפתרון הייתה שהצהרת import דרשה לייבא את הקובץ .css.js. כדי לאפשר ל-GN ליצור קובץ CSS במהלך הבנייה, כתבנו את הסקריפט 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 כדי להחיל תיקונים ולהעביר באופן אוטומטי קוד מדור קודם. כלי הפיתוח כבר משתמשים במספר כללים מותאמים אישית שספציפיים ל-codebase של כלי הפיתוח. האפשרות הזו עזרה כי ESLint כבר מנתחת את הקוד לעץ תחביר מופשט(abbr. AST) ונוכל להריץ שאילתות על צומתי הקריאה הספציפיים שהיו קריאות לרישום CSS.

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

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

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

מה השלבים הבאים?

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

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

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

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

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

  • שלחו לנו הצעה או משוב דרך crbug.com.
  • כדי לדווח על בעיה בכלי הפיתוח, לוחצים על אפשרויות נוספות   עוד   > עזרה > דיווח על בעיות בכלי הפיתוח בכלי הפיתוח.
  • אפשר לשלוח ציוץ אל @ChromeDevTools.
  • אפשר לכתוב תגובות לגבי 'מה חדש' בסרטוני YouTube בכלי הפיתוח או בסרטונים ב-YouTube בקטע 'טיפים לשימוש בכלי הפיתוח'.