כידוע, כלי הפיתוח ל-Chrome היא אפליקציית אינטרנט שנכתבה באמצעות HTML, CSS ו-JavaScript. לאורך השנים, כלי הפיתוח הפכו לעשירים יותר בתכונות, חכמים יותר ומקושרים יותר לפלטפורמת האינטרנט הרחבה. DevTools התרחב עם השנים, אבל הארכיטקטורה שלו דומה במידה רבה לארכיטקטורה המקורית, כשהכלי עדיין היה חלק מ-WebKit.
הפוסט הזה הוא חלק מסדרה של פוסטים בבלוג שמתארים את השינויים שאנחנו מבצעים בארכיטקטורה של DevTools ואת אופן הבנייה שלו. נסביר איך DevTools פעל בעבר, מה היו היתרונות והמגבלות שלו ומה עשינו כדי לצמצם את המגבלות האלה. לכן, נתעמק בנושא מערכות מודולים, נלמד איך לטעון קוד ואיך הגענו לשימוש במודולים של JavaScript.
בהתחלה לא היה כלום
בממשק הקצה הנוכחי יש מגוון מערכות של מודולים עם כלים שנבנו סביבם, וגם את הפורמט הסטנדרטי של מודולי JavaScript, אבל אף אחד מהם לא היה קיים כש-DevTools נוצר לראשונה. כלי הפיתוח מבוססים על קוד ששוחרר לראשונה ב-WebKit לפני יותר מ-12 שנים.
ההתייחסות הראשונה למערכת מודולים ב-DevTools מתייחסת לשנת 2012: השקת רשימת מודולים עם רשימת מקורות משויכת.
זה היה חלק מתשתית Python ששימשה אז כדי לקמפל ולבנות את DevTools.
שינוי המשך חילוץ את כל המודולים לקובץ frontend_modules.json
נפרד (commit) בשנת 2013, ולאחר מכן לקובצי module.json
נפרדים (commit) בשנת 2014.
קובץ module.json
לדוגמה:
{
"dependencies": [
"common"
],
"scripts": [
"StylePane.js",
"ElementsPanel.js"
]
}
מאז 2014, התבנית module.json
משמשת ב-DevTools כדי לציין את המודולים ואת קובצי המקור שלו.
בינתיים, הסביבה העסקית של האינטרנט התפתחה במהירות ונוצרו כמה פורמטים של מודולים, כולל UMD, CommonJS ובסופו של דבר מודולים סטנדרטיים של JavaScript.
עם זאת, כלי הפיתוח נשאר עם הפורמט module.json
.
כלי הפיתוח המשיכו לפעול, אבל היו כמה חסרונות לשימוש במערכת מודולים ייחודית ולא סטנדרטית:
- הפורמט
module.json
דרש כלי build מותאמים אישית, בדומה ל-bundlers מודרניים. - לא הייתה שילוב עם סביבת פיתוח משולבת (IDE), ולכן נדרשו כלים מותאמים אישית כדי ליצור קבצים שסביבות פיתוח משולבות מודרניות יכולות להבין (הסקריפט המקורי ליצירת קבצים מסוג jsconfig.json עבור VS Code).
- הפונקציות, הכיתות והאובייקטים הועברו להיקף הגלובלי כדי לאפשר שיתוף בין המודולים.
- הקבצים היו תלויים בסדר, כלומר הסדר שבו
sources
מופיעים ברשימה היה חשוב. לא הייתה ערובה שהקוד שבו אתם מסתמכים ייטען, מלבד אם אדם אימת אותו.
בסך הכול, כשהערכנו את המצב הנוכחי של מערכת המודולים ב-DevTools ואת הפורמטים האחרים של המודולים (שנעשה בהם שימוש נרחב יותר), הגענו למסקנה שתבנית module.json
יוצרת יותר בעיות ממה שהיא פותרת, והגיע הזמן לתכנן את המעבר ממנה.
היתרונות של תקנים
מבין מערכות המודולים הקיימות, בחרנו במודולים של JavaScript כמערכת להעברה אליה. בזמן קבלת ההחלטה הזו, מודולים של JavaScript עדיין נשלחו מאחורי דגל ב-Node.js, ומספר גדול של חבילות שזמינות ב-NPM לא הכילו חבילה של מודולים של JavaScript שאפשר להשתמש בה. למרות זאת, הגענו למסקנה שמודול JavaScript הוא האפשרות הטובה ביותר.
היתרון העיקרי של מודולים של JavaScript הוא שזהו פורמט המודול הסטנדרטי של JavaScript.
כשציינו את החסרונות של module.json
(ראו למעלה), הבנו שרובם קשורים לשימוש בפורמט מודול ייחודי ולא סטנדרטי.
בחירה בפורמט של מודול שאינו סטנדרטי מחייבת אותנו להשקיע זמן בבניית שילובים עם כלי ה-build וכלים שהתחזקים שלנו השתמשו בהם.
השילובים האלה היו לרוב לא עמידים ולא היו תומכים בתכונות מסוימות, כך שנדרש זמן תחזוקה נוסף, ולפעמים הם הובילו לבאגים עדינים שהועברו בסופו של דבר למשתמשים.
מאחר שמודולים של JavaScript היו סטנדרטיים, המשמעות היא שסביבות פיתוח משולבות (IDE) כמו VS Code, בודקי סוגים כמו Closure Compiler/TypeScript וכלי build כמו Rollup/minifiers יוכלו להבין את קוד המקור שכתבנו.
בנוסף, כשמפתח חדש יצטרף לצוות DevTools, הוא לא יצטרך להשקיע זמן בלמידת פורמט module.json
קנייני, אבל סביר להניח שכבר יהיה לו ניסיון עם מודולים של JavaScript.
כמובן, כשכלי הפיתוח נוצר לראשונה, אף אחת מההטבות שלמעלה לא הייתה קיימת. עברו שנים של עבודה בקבוצות תקנים, הטמעות בסביבת זמן ריצה ומפתחים שמשתמשים במודולים של JavaScript ומספקים משוב, עד שהגענו לנקודה שבה אנחנו נמצאים היום. אבל כשמודולים של JavaScript הפכו לזמינים, נאלצנו לבחור: להמשיך לתחזק את הפורמט שלנו או להשקיע בהעברה לפורמט החדש.
העלות של הכלי החדש והמבריק
למרות שלמודולים של JavaScript יש הרבה יתרונות שרצינו להשתמש בהם, נשארנו בעולם הלא סטנדרטי של module.json
.
כדי ליהנות מהיתרונות של מודולים של JavaScript, היינו צריכים להשקיע סכום משמעותי בניקוי החוב הטכני, ולבצע העברה שעלולה לגרום לשיבושים בתכונות ולהכניס באגים של רגרסיה.
בשלב הזה, השאלה לא הייתה 'אנחנו רוצים להשתמש במודולים של JavaScript?', אלא 'כמה יקר להשתמש במודולים של JavaScript?'. במקרה הזה, נאלצנו לאזן בין הסיכון לגרום למשתמשים שלנו לבעיות חזרה לאחור, העלות של הזמן שהמהנדסים יידרשו להשקיע בהעברה והמצב הגרוע יותר שבו נעבוד באופן זמני.
הנקודה האחרונה הזו התבררה כחשובה מאוד. באופן תיאורטי, אפשר להגיע למודולים של JavaScript, אבל במהלך ההעברה נגיע לקוד שצריך להביא בחשבון גם module.json
וגם מודולים של JavaScript.
לא רק שהיה קשה להשיג זאת מבחינה טכנית, אלא שגם כל המהנדסים שעובדים על DevTools היו צריכים לדעת איך לעבוד בסביבה הזו.
הם יצטרכו לשאול את עצמם כל הזמן "בחלק הזה של קוד הבסיס, האם מדובר ב-module.json
או במודולים של JavaScript, ואיך מבצעים שינויים?".
הצצה מקדימה: העלות המוסתרת של הדרכת המטפלים האחרים בתהליך ההעברה הייתה גבוהה מהצפוי.
אחרי ניתוח העלויות, הגענו למסקנה שעדיין כדאי לעבור למודול JavaScript. לכן, המטרות העיקריות שלנו היו:
- חשוב לוודא שאתם משתמשים במודולים של JavaScript בצורה שתאפשר לכם ליהנות מהיתרונות שלהם במלואם.
- מוודאים שהשילוב עם המערכת הקיימת שמבוססת על
module.json
בטוח ולא משפיע לרעה על המשתמשים (באגים של רגרסיה, תסכול של משתמשים). - להנחות את כל המטפלים ב-DevTools בתהליך ההעברה, בעיקר באמצעות אמצעי בקרה ומאזן מובנים כדי למנוע טעויות לא מכוונות.
גיליונות אלקטרוניים, טרנספורמציות וחובות טכניים
המטרה הייתה ברורה, אבל קשה היה למצוא פתרון עקיף למגבלות שהוטלו על ידי הפורמט module.json
.
עברנו כמה גרסאות, אב טיפוס ושינויים בארכיטקטורה עד שפיתחנו פתרון שאנחנו מרוצים ממנו.
כתבנו מסמך עיצוב עם אסטרטגיית ההעברה שבחרנו.
במסמך העיצוב צוין גם אומדן הזמן הראשוני שלנו: שבועיים עד 4 שבועות.
ספוילר: החלק האינטנסיבי ביותר של ההעברה נמשך 4 חודשים, ומתחילתו ועד סופו נמשך 7 חודשים!
עם זאת, התוכנית המקורית עמדה במבחן הזמן: לימדנו את סביבת זמן הריצה של DevTools לטעון את כל הקבצים שמפורטים במערך scripts
בקובץ module.json
בדרך הישנה, ואת כל הקבצים שמפורטים במערך modules
באמצעות ייבוא דינמי של מודולים של JavaScript.
כל קובץ שנמצא במערך modules
יוכל להשתמש בייבוא/ייצוא של ES.
בנוסף, נבצע את ההעברה בשני שלבים (בסופו של דבר, הפכנו את השלב האחרון לשני שלבי משנה, כפי שמתואר בהמשך): השלבים export
ו-import
.
הסטטוס של כל מודול בכל שלב נסקר בגיליון אלקטרוני גדול:
קטע מגיליון ההתקדמות זמין לכולם כאן.
export
-phase
השלב הראשון הוא להוסיף הצהרות export
לכל הסמלים שהיו אמורים להיות משותפים בין מודולים או קבצים.
הטרנספורמציה תתבצע באופן אוטומטי על ידי הרצת סקריפט לכל תיקייה.
בהנחה שהסמל הבא קיים בעולם של module.json
:
Module.File1.exported = function() {
console.log('exported');
Module.File1.localFunctionInFile();
};
Module.File1.localFunctionInFile = function() {
console.log('Local');
};
(כאן, Module
הוא שם המודול ו-File1
הוא שם הקובץ. ב-sourcetree שלנו, זה יהיה front_end/module/file1.js
).
הקוד יתבצע כך:
export function exported() {
console.log('exported');
Module.File1.localFunctionInFile();
}
export function localFunctionInFile() {
console.log('Local');
}
/** Legacy export object */
Module.File1 = {
exported,
localFunctionInFile,
};
בהתחלה, התכנון שלנו היה לכתוב מחדש גם את הייבוא של קובץ זהה בשלב הזה.
לדוגמה, בדוגמה שלמעלה היינו כותבים מחדש את Module.File1.localFunctionInFile
כ-localFunctionInFile
.
עם זאת, הבנו שיהיה קל יותר להפוך את התהליך לאוטומטי ושההחלה תהיה בטוחה יותר אם נפריד בין שתי הטרנספורמציות האלה.
לכן, השלב 'העברת כל הסמלים באותו קובץ' יהפוך לשלב המשנה השני של השלב import
.
מאחר שהוספת מילת המפתח export
לקובץ הופכת את הקובץ מ'סקריפט' ל'מודול', הרבה מתשתית DevTools נאלצה להתעדכן בהתאם.
זה כלל את סביבת זמן הריצה (עם ייבוא דינמי), אבל גם כלים כמו ESLint
להרצה במצב מודול.
אחת התגליות שגילינו במהלך הטיפול בבעיות האלה היא שהבדיקות שלנו פועלות במצב 'לא מדויק'.
מאחר שמשוואות JavaScript מניחות שהקבצים פועלים במצב "use strict"
, השינוי הזה ישפיע גם על הבדיקות שלנו.
מסתבר שכמות לא מבוטלת של בדיקות הסתמכו על הבעיה הזו, כולל בדיקה שבה נעשה שימוש בהצהרה with
😱.
בסופו של דבר, עדכון התיקייה הראשונה כך שתכלול הצהרות export
נמשך כמעט שבוע ודרשו כמה ניסיונות עם הורדות מחדש.
import
-phase
אחרי שכל הסמלים יוצאו באמצעות הצהרות export
ונשארו בהיקף הגלובלי (מדור קודם), נאלצנו לעדכן את כל ההפניות לסמלים בקובצי נתונים שונים כך שישתמשו בייבוא של ES.
היעד הסופי הוא להסיר את כל 'אובייקטי הייצוא מהדור הקודם', ולנקות את ההיקף הגלובלי.
הטרנספורמציה תתבצע באופן אוטומטי על ידי הרצת סקריפט לכל תיקייה.
לדוגמה, לסמלים הבאים שקיימים בעולם module.json
:
Module.File1.exported();
AnotherModule.AnotherFile.alsoExported();
SameModule.AnotherFile.moduleScoped();
הם יהיו בצורה הבאה:
import * as Module from '../module/Module.js';
import * as AnotherModule from '../another_module/AnotherModule.js';
import {moduleScoped} from './AnotherFile.js';
Module.File1.exported();
AnotherModule.AnotherFile.alsoExported();
moduleScoped();
עם זאת, לגישה הזו היו כמה מגבלות:
- לא כל הסמלים נקראו
Module.File.symbolName
. חלק מהסמלים נקראו רקModule.File
או אפילוModule.CompletelyDifferentName
. עקב חוסר העקביות הזה, נאלצנו ליצור מיפוי פנימי מהאובייקט הגלובלי הישן לאובייקט החדש שיובא. - לפעמים יהיו התנגשויות בין שמות ברמת המודול.
הדוגמה הבולטת ביותר היא השימוש בדפוס של הצהרה על סוגים מסוימים של
Events
, שבהם כל סמל נקרא פשוטEvents
. כלומר, אם רצית להאזין למספר סוגים של אירועים שהוגדרו בקבצים שונים, היה מתרחש התנגש שם בהצהרהimport
עבור ה-Events
האלה. - כפי שהתברר, היו יחסי תלות מעגליים בין הקבצים.
זה היה בסדר בהקשר ברמת האתר, כי השימוש בסמל היה אחרי שכל הקוד נטען.
עם זאת, אם אתם צריכים
import
, התלות המעגלית תהיה מפורשת. זו לא בעיה מיידית, אלא אם יש קריאות לפונקציות עם תוצאות לוואי בקוד ברמת ההיקף הגלובלי, כמו שהיה ב-DevTools. בסך הכול, נדרשו כמה שינויים ותכנון מחדש כדי להפוך את הטרנספורמציה לבטוחה.
עולם חדש לגמרי עם מודולים של JavaScript
בפברואר 2020, 6 חודשים אחרי ההתחלה בספטמבר 2019, בוצעו הניקויים האחרונים בתיקייה ui/
.
זה סימן את הסיום הלא רשמי של ההעברה.
אחרי שהסערה שקטה, סימנו באופן רשמי שהמיגרציה הסתיימה ב-5 במרץ 2020. 🎉
עכשיו, כל המודולים בכלי הפיתוח משתמשים במודולים של JavaScript כדי לשתף קוד.
אנחנו עדיין מציבים כמה סמלים ברמת ההיקף הגלובלי (בקבצים module-legacy.js
) לבדיקות הקודמות שלנו או לשילוב עם חלקים אחרים בארכיטקטורה של DevTools.
הן יוסרו עם הזמן, אבל אנחנו לא מתייחסים אליהן כאל חסימה לפיתוח עתידי.
יש לנו גם מדריך סגנונות לשימוש שלנו במודולים של JavaScript.
נתונים סטטיסטיים
ההערכות השמרניות לגבי מספר רשימות השינויים (CL) (ראשי תיבות של changelist – המונח ב-Gerrit שמייצג שינוי, בדומה לבקשת משיכה ב-GitHub) שמעורבות בהעברה הזו הן כ-250 רשימות שינויים, שבוצעו בעיקר על ידי שני מהנדסים. אין לנו נתונים סטטיסטיים מדויקים לגבי היקף השינויים שבוצעו, אבל אומדן שמרני של השורות שהשתנו (החישוב מבוסס על הסכום של ההבדל המוחלט בין ההוספות למחיקות בכל בקשת שינוי) הוא כ-30,000 שורות (כ-20% מכל הקוד של חזית DevTools).
הקובץ הראשון עם export
שוחרר ב-Chrome 79, והגרסה היציבה שלו שוחררה בדצמבר 2019.
השינוי האחרון במעבר ל-import
נשלח ב-Chrome 83, ששוחרר לגרסה היציבה במאי 2020.
אנחנו מודעים לנסיגה אחת שנוספה לגרסת Chrome היציבה במסגרת ההעברה הזו.
ההשלמה האוטומטית של קטעי קוד בתפריט הפקודות התקלקלה בגלל ייצוא default
זר.
היו לנו כמה נסיגות אחרות, אבל חבילות הבדיקה האוטומטיות שלנו ומשתמשי Chrome Canary דיווחו עליהן ותיקנו אותן לפני שהן יכלו להגיע למשתמשי Chrome בגרסה היציבה.
אפשר לראות את התהליך המלא (לא כל בקשות התמיכה (CL) מצורפות לבאג הזה, אבל רובן כן) ביומן בכתובת crbug.com/1006759.
מה למדנו
- החלטות שהתקבלו בעבר יכולות להשפיע על הפרויקט לאורך זמן. למרות שמודול JavaScript (ופורמטים אחרים של מודולים) היו זמינים במשך זמן רב, כלי הפיתוח לא היו מוכנים להצדיק את ההעברה. קשה להחליט מתי כדאי לבצע את ההעברה ומתי לא, וההחלטה מבוססת על ניחושים מבוססי נתונים.
- האומדנים הראשוניים שלנו היו בשבועות ולא בחודשים. הסיבה העיקרית לכך היא שמצאנו יותר בעיות בלתי צפויות ממה שציפינו למצוא בניתוח העלויות הראשוני. למרות שתוכנית ההעברה הייתה טובה, הבעיה הייתה בחוב הטכני (לעיתים קרובות יותר ממה שרצינו).
- ההעברה של מודולי JavaScript כללה כמות גדולה של פעולות ניקוי של חובות טכניים (שנראה שהן לא קשורות). המעבר לפורמט מודול מודרני וסטנדרטי אפשר לנו להתאים את שיטות הקוד המומלצות שלנו לפיתוח אתרים מודרני. לדוגמה, הצלחנו להחליף את ה-bundler המותאם אישית של Python בהגדרה מינימלית של Rollup.
- למרות ההשפעה הגדולה על קוד הבסיס שלנו (כ-20% מהקוד השתנה), דווחו מעט מאוד נסיגות. אמנם היו לנו בעיות רבות בהעברת הקבצים הראשונים, אבל אחרי זמן מה פיתחנו תהליך עבודה יציב ואוטומטי באופן חלקי. המשמעות היא שההשפעה השלילית על המשתמשים היציבים שלנו במהלך ההעברה הייתה מינימלית.
- קשה, ולפעמים בלתי אפשרי, ללמד את המורכבות של העברה מסוימת למנהלי תוכנה אחרים. קשה לעקוב אחרי העברות בקנה מידה כזה, ונדרש ידע רב בתחום. העברת הידע הזה לאנשים אחרים שעובדים באותו קוד לא רצויה בפני עצמה לצורך העבודה שהם מבצעים. חשוב לדעת מה לשתף ואילו פרטים לא לשתף, וזוהי אומנות שצריך לשלוט בה. לכן חשוב לצמצם את מספר ההעברות הגדולות, או לפחות לא לבצע אותן בו-זמנית.
הורדת הערוצים לתצוגה מקדימה
מומלץ להשתמש ב-Chrome Canary, ב-Dev או ב-Beta כדפדפן הפיתוח שמוגדר כברירת מחדל. ערוצי התצוגה המקדימה האלה מעניקים לכם גישה לתכונות העדכניות ביותר של DevTools, מאפשרים לכם לבדוק ממשקי API מתקדמים לפלטפורמות אינטרנט ולמצוא בעיות באתר לפני שהמשתמשים שלכם יעשו זאת.
יצירת קשר עם צוות כלי הפיתוח ל-Chrome
אתם יכולים להשתמש באפשרויות הבאות כדי לדון בתכונות החדשות, בעדכונים או בכל דבר אחר שקשור ל-DevTools.
- אתם יכולים לשלוח לנו משוב ובקשות להוספת תכונות בכתובת crbug.com.
- מדווחים על בעיה בכלי הפיתוח באמצעות הסמל אפשרויות נוספות > עזרה > דיווח על בעיה בכלי הפיתוח ב-DevTools.
- שולחים ציוץ אל @ChromeDevTools.
- אפשר להשאיר תגובות בסרטונים של מה חדש בכלי הפיתוח ב-YouTube או בסרטונים של טיפים לכלי הפיתוח ב-YouTube.