הפוסט הזה הוא חלק מסדרת פוסטים בבלוג שמתארים את השינויים שאנחנו מבצעים בארכיטקטורה של DevTools ואת אופן הבנייה שלו.
בהמשך למעבר למודול JavaScript ולמעבר לרכיבי Web, אנחנו ממשיכים היום בסדרת הפוסטים בבלוג שלנו על השינויים שאנחנו מבצעים בארכיטקטורה של Devtools ועל האופן שבו היא נוצרת. (אם עדיין לא צפיתם בו, פרסמנו סרטון על העבודה שלנו בנושא שדרוג הארכיטקטורה של DevTools לאינטרנט המודרני, עם 14 טיפים לשיפור פרויקטים באינטרנט).
בפוסט הזה נסביר על התהליך של 13 החודשים שבהם עזבנו את בודק הסוגים של Closure Compiler לטובת TypeScript.
מבוא
בגלל הגודל של קוד הבסיס של DevTools והצורך לספק ביטחון למהנדסים שעובדים עליו, השימוש בבודק הטיפוסים הוא חובה. לשם כך, כלי הפיתוח אימצו את Closure Compiler כבר בשנת 2013. השימוש ב-Closure אפשר למהנדסי DevTools לבצע שינויים בביטחון, כי המהדר של Closure מבצע בדיקות טיפוס כדי לוודא שכל השילובים של המערכות מוגדרים כראוי.
עם זאת, עם הזמן, בודקי סוגים חלופיים הפכו לפופולריים בפיתוח אינטרנט מודרני. שתי דוגמאות בולטות הן TypeScript ו-Flow. בנוסף, TypeScript הפכה לשפת תכנות רשמית ב-Google. עם הזמן, בודקי הטיפוסים החדשים האלה נהיו פופולריים יותר, אבל שמנו לב גם שאנחנו שולחים חזרה לקוד שינויים שבודק הטיפוסים היה אמור לזהות. לכן החלטנו לבדוק מחדש את הבחירה שלנו בכלי לבדיקת סוגים ולבחון את השלבים הבאים לפיתוח ב-DevTools.
בדיקת בודקי הטיפוסים
מאחר שכבר היה ב-DevTools בודק סוגים, השאלה שעליה היינו צריכים לענות הייתה:
האם נמשיך להשתמש ב-Closure Compiler או נעבור למכשיר בדיקת סוגים חדש?
כדי לענות על השאלה הזו, היינו צריכים להעריך את בודקי הסוגים לפי כמה מאפיינים. השימוש שלנו בבודק הטיפוסים מתמקד בביטחון של המהנדסים, ולכן ההיבט החשוב ביותר עבורנו הוא תקינות הטיפוסים. במילים אחרות: מה מידת האמינות של בודק הטיפוסים בזיהוי בעיות אמיתיות?
ההערכה שלנו התמקדה בנסיגות (regressions) ששלחנו, ובקביעת הגורמים העיקריים שלהן. ההנחה היא שכבר השתמשנו ב-Closure Compiler, ולכן הבעיות האלה לא זוהו על ידו. לכן, נצטרך לקבוע אם בודק סוגים אחר היה יכול לזהות את הבעיה.
בדיקת תקינות הטיפוסים ב-TypeScript
TypeScript היא שפת תכנות נתמכת באופן רשמי ב-Google, והפופולריות שלה הולכת וגדלה במהירות, לכן החלטנו לבדוק אותה קודם. TypeScript הייתה בחירה מעניינת, כי צוות TypeScript עצמו משתמש ב-DevTools כאחד מפרויקטי הבדיקה שלו כדי לעקוב אחרי התאימות שלו לבדיקה של סוגים ב-JavaScript. פלט הבדיקה של נקודת התייחסות הראה ש-TypeScript זיהה כמות גדולה של בעיות שקשורות לסוגים – בעיות שהמחשב של Closure לא תמיד מזהה. סביר להניח שרבות מהבעיות האלה היו הגורם העיקרי לבעיות הרגרסיה ששלחנו. כתוצאה מכך, הגענו למסקנה ש-TypeScript יכולה להיות אפשרות קיימת ל-DevTools.
במהלך המעבר למודול JavaScript, כבר גילינו ש-Closure Compiler מגלה יותר בעיות מאשר בעבר. המעבר לפורמט מודולים סטנדרטי הגדיל את היכולת של Closure להבין את קוד הבסיס שלנו, וכתוצאה מכך הגדיל את היעילות של בודקי הטיפוסים. עם זאת, צוות TypeScript השתמש בגרסה בסיסית של DevTools שקדמה להעברת המודולים של JavaScript. לכן, נדרשנו לבדוק אם המעבר למודול JavaScript צמצם גם את כמות השגיאות שהמהדר של TypeScript יזהה.
הערכה של TypeScript
כלי הפיתוח קיים כבר יותר מעשור, והוא התפתח לאפליקציית אינטרנט גדולה ועשירה בתכונות. בזמן כתיבת הפוסט הזה בבלוג, DevTools מכיל כ-150,000 שורות של קוד JavaScript מצד ראשון. כשהרצנו את המהדר של TypeScript בקוד המקור שלנו, נפח השגיאות היה עצום. הצלחנו להבין שגם אם המהדר של TypeScript הנפיק פחות שגיאות שקשורות לפתרון הקוד (כ-2,000 שגיאות), עדיין היו 6,000 שגיאות נוספות בקוד הבסיסי שלנו שקשורות לתאימות בין טיפוסים.
התוצאות הראו ש-TypeScript הצליח להבין איך לפתור את הסוגים, אבל מצא כמות משמעותית של אי-תאימות בין סוגים בקוד הבסיסי שלנו.
ניתוח ידני של השגיאות האלה הראה ש-TypeScript היה (ברוב המקרים) נכון.
הסיבה לכך היא שבדרך כלל המהדר של Closure מסיק שסוג הוא Any
, ואילו TypeScript מבצע הסקת סוגים על סמך הקצאות ומסיק סוג מדויק יותר.
לכן, TypeScript אכן הצליחה להבין טוב יותר את המבנה של האובייקטים שלנו וזיהתה שימושים בעייתיים.
חשוב לזכור ששימוש במהדר Closure ב-DevTools כלל שימוש תדיר ב-@unrestricted
.
הוספת הערה לכיתה באמצעות @unrestricted
משביתה למעשה את בדיקות המאפיינים המחמירות של המהדר של Closure לגבי הכיתה הספציפית הזו. המשמעות היא שמפתח יכול להרחיב את הגדרת הכיתה כרצונו בלי אבטחת סוגים.
לא הצלחנו למצוא הקשר היסטורי לכך שהשימוש ב-@unrestricted
היה נפוץ בקוד של DevTools, אבל הוא הוביל להפעלת Closure compiler במצב הפעלה פחות בטוח בחלקים גדולים מקוד הבסיס.
ניתוח חוצה של הרגרסיות שלנו עם שגיאות הסוג ש-TypeScript זיהתה הראה גם חפיפה, מה שהוביל אותנו להאמין ש-TypeScript הייתה יכולה למנוע את הבעיות האלה (בתנאי שהסוגים עצמם היו נכונים).
ביצוע שיחה ל-any
בשלב הזה, נאלצנו להחליט אם לשפר את השימוש שלנו ב-Closure Compiler או לעבור ל-TypeScript. (מכיוון ש-Flow לא נתמך ב-Google או ב-Chromium, נאלצנו לוותר על האפשרות הזו). על סמך דיונים עם מהנדסי Google שעוסקים בכלים של JavaScript/TypeScript והמלצות שלהם, בחרנו במהדר TypeScript. (פרסמנו לאחרונה גם פוסט בבלוג בנושא העברת Puppeteer ל-TypeScript).
הסיבות העיקריות לשימוש במהדר של TypeScript היו שיפור של תקינות הסוגים, ויתרון נוסף היה התמיכה מצוותים פנימיים של TypeScript ב-Google והתכונות של שפת TypeScript, כמו interfaces
(לעומת typedefs
ב-JSDoc).
הבחירה במהדר TypeScript חייבה אותנו להשקיע משמעותית בקוד הבסיסי של DevTools ובארכיטקטורה הפנימית שלו. לכן, הערכנו שנצטרך לפחות שנה כדי לעבור ל-TypeScript (היעדים שלנו הם רבעון שלישי 2020).
ביצוע ההעברה
השאלה הגדולה ביותר שנותרנו איתה היא: איך נעבור ל-TypeScript? יש לנו 150,000 שורות קוד, ולא ניתן להעביר את כולן בבת אחת. ידענו גם שהרצת TypeScript בקוד הבסיסי שלנו תחשוף אלפי שגיאות.
בדקנו כמה אפשרויות:
- קבלת כל השגיאות ב-TypeScript והשוואתן לפלט 'זהב'. הגישה הזו תהיה דומה לגישה של צוות TypeScript. החיסרון הגדול ביותר של הגישה הזו הוא שכיחות גבוהה של התנגשויות במיזוג, כי עשרות מהנדסים עובדים באותו קוד בסיס.
- מגדירים את כל הסוגים הבעייתיים כ-
any
. כך, TypeScript תדכא שגיאות. לא בחרנו באפשרות הזו כי המטרה של ההעברה הייתה נכונות הסוג, וההשתקה עלולה לפגוע בכך. - מתקנים את כל השגיאות ב-TypeScript באופן ידני. תצטרכו לתקן אלפי שגיאות, וזה תהליך שאורך זמן רב.
למרות המאמץ הצפוי הרב, בחרנו באפשרות 3. היו סיבות נוספות לבחירה באפשרות הזו: לדוגמה, היא אפשרה לנו לבדוק את כל הקוד ולבצע בדיקה של כל הפונקציונליות, כולל ההטמעה שלה, פעם בעשור. מנקודת מבט עסקית, לא סיפקנו ערך חדש, אלא רק שמרו על הסטטוס קוו. לכן, היה קשה יותר להצדיק את הבחירה באפשרות 3.
עם זאת, השימוש ב-TypeScript נראה לנו כדרך למנוע בעיות עתידיות, במיוחד בנושא נסיגה לאחור (regression). לכן, הטיעון היה פחות "אנחנו מוסיפים ערך עסקי חדש" ויותר "אנחנו מוודאים שלא נפסיד את הערך העסקי שהרווחנו".
תמיכה ב-JavaScript של מהדר TypeScript
אחרי שקיבלנו אישור והתחלנו לפתח תוכנית להפעלת המהדר של Closure ושל TypeScript באותו קוד JavaScript, התחלנו עם כמה קובצי JavaScript קטנים. הגישה שלנו הייתה בעיקר מלמטה למעלה: התחלנו בקוד הליבה והמשכנו לחלקים העליונים בארכיטקטורה עד שהגענו ללוחות ברמה הגבוהה.
הצלחנו לבצע את העבודה במקביל על ידי הוספה מראש של @ts-nocheck
לכל קובץ ב-DevTools. התהליך של 'תיקון TypeScript' יהיה הסרת ההערה @ts-nocheck
ופתרון כל השגיאות ש-TypeScript תמצא. כך יכולנו להיות בטוחים שכל הקובצים נבדקו ושהרבה מבעיות הסוג טופלו.
באופן כללי, הגישה הזו עבדה עם מעט בעיות. נתקלנו בכמה באגים במהדר TypeScript, אבל רובם היו עמוקים:
- פרמטר אופציונלי עם סוג פונקציה שמחזיר את הערך
any
מטופל כפרמטר נדרש: #38551 - הקצאת מאפיין לשיטה סטטית של כיתה מפריעה להצהרה: #38553
- בהצהרה על תת-מחלקה עם קונסטרוקטור ללא ארגומנטים ועל סופר-מחלקה עם קונסטרוקטור עם ארגומנטים, קונסטרוקטור הצאצא מושמט: #41397
הבאגים האלה מדגישים שב-99% מהמקרים, המהדר של TypeScript הוא בסיס יציב שאפשר לבנות עליו. כן, לפעמים הבאגים הלא ברורים האלה גרמו לבעיות ב-DevTools, אבל ברוב המקרים הם היו לא ברורים מספיק כדי שנוכל לעקוף אותם בקלות.
הבעיה היחידה שגרמה לבלבול מסוים הייתה הפלט הלא-דטרמיניסטי של קבצים מסוג .tsbuildinfo
: #37156.
ב-Chromium אנחנו דורשים ששתי גרסאות build של אותו commit ב-Chromium יניבו את אותו פלט בדיוק.
לצערנו, מהנדסי ה-build של Chromium גילו שהפלט של .tsbuildinfo
לא היה דטרמיניסטי: crbug.com/1054494.
כדי לעקוף את הבעיה הזו, נאלצנו לבצע תיקון 'קוף' בקובץ .tsbuildinfo
(שמכיל בעיקר JSON) ולעבד אותו לאחר מכן כדי להחזיר פלט דטרמיניסטי: https://crrev.com/c/2091448
למרבה המזל, צוות TypeScript פתר את הבעיה במקור, ותוך זמן קצר הצלחנו להסיר את הפתרון החלופי. תודה לצוות TypeScript על הנכונות לקבל דוחות על באגים ועל תיקון הבעיות האלה במהירות!
באופן כללי, אנחנו מרוצים מהנכונות (הטיפוס) של המהדר של TypeScript. אנחנו מקווים ש-Devtools, כפרויקט גדול בקוד פתוח של JavaScript, עזר לחזק את התמיכה ב-JavaScript ב-TypeScript.
ניתוח של מה שקרה לאחר מכן
הצלחנו להתקדם משמעותית בפתרון שגיאות הסוג האלה ולהגדיל בהדרגה את כמות הקוד שנבדק על ידי TypeScript. עם זאת, באוגוסט 2020 (9 חודשים אחרי תחילת ההעברה) בדקנו את ההתקדמות וגילינו שלא נגיע למועד היעד בקצב הנוכחי. אחד מהמהנדסים שלנו יצר תרשים ניתוח כדי להציג את ההתקדמות של 'הפיכת הקוד ל-TypeScript' (השם שנתינו להעברה הזו).
TypeScript Migration progress - Tracking lines of code remaining that need migrating
ההערכות לגבי המועד שבו לא יהיו לנו יותר שורות זמינות נעו בין יולי 2021 לדצמבר 2021, כמעט שנה אחרי מועד היעד שלנו. אחרי דיונים עם ההנהלה ומהנדסים אחרים, החלטנו להגדיל את מספר המהנדסים שעובדים על המעבר לתמיכה במהדר TypeScript. הצלחנו לעשות זאת כי תכננו את ההעברה כך שתהיה אפשרות לבצע אותה במקביל, כך שמהנדסים רבים שעובדים על קבצים שונים לא יוצרים ביניהם התנגשויות.
בשלב הזה, תהליך ההמרה ל-TypeScript הפך למאמץ צוותי. בעזרה הנוספת הצלחנו לסיים את המיגרציה בסוף נובמבר 2020, 13 חודשים אחרי שהיא התחילה, ושנה וחצי לפני התחזית הראשונית שלנו.
בסך הכול, 18 מהנדסים שלחו 771 רשימות שינויים (שדומות לבקשות משיכה). בבאג למעקב שלנו (https://crbug.com/1011811) יש יותר מ-1,200 תגובות (כמעט כולן פרסומים אוטומטיים מרשימות שינויים). בגיליון המעקב שלנו היו יותר מ-500 שורות של כל הקבצים שצריך להמיר ל-TypeScript, הגורם שהוקצה להם ובאילו רשימות שינויים הם הומרו ל-TypeScript.
צמצום ההשפעה של ביצועי המהדר של TypeScript
הבעיה הגדולה ביותר שאנחנו מתמודדים איתה כרגע היא הביצועים האיטיים של מהדר TypeScript. בהתחשב במספר המהנדסים שמפתחים את Chromium ו-DevTools, צוואר הבקבוק הזה יקר. לצערנו, לא הצלחנו לזהות את הסיכון הזה לפני ההעברה, ורק אחרי שהעברנו את רוב הקבצים ל-TypeScript גילינו עלייה ניכרת בזמן הנדרש ליצירת גרסאות build של Chromium: https://crbug.com/1139220
דיווחנו על הבעיה הזו לצוות המהדר של Microsoft TypeScript, אבל לצערנו הם קבעו שההתנהגות הזו מכוונת. אנחנו מקווים שהם יבחנו מחדש את הבעיה הזו, אבל בינתיים אנחנו פועלים כדי לצמצם את ההשפעה על הביצועים האיטיים בצד Chromium ככל האפשר.
לצערנו, הפתרונות שזמינים לנו היום לא תמיד מתאימים לאנשים שאינם גוגלרים. התרומות של קוד פתוח ל-Chromium חשובות מאוד (במיוחד אלה של צוות Microsoft Edge), ולכן אנחנו מחפשים באופן פעיל חלופות שיתאימו לכל התורמים. עם זאת, בשלב הזה לא מצאנו פתרון חלופי מתאים.
המצב הנוכחי של TypeScript בכלי הפיתוח
בשלב הזה, הסרנו את בודק הסוגים של קובץ הידור Closure מקוד המקור שלנו, ועכשיו אנחנו מסתמכים רק על קובץ הידור TypeScript. אנחנו יכולים לכתוב קובצי TypeScript ולהשתמש בתכונות ספציפיות ל-TypeScript (כמו ממשקים, גנריקה וכו'), שעוזרות לנו בעבודה היומיומית. אנחנו בטוחים יותר שמהדר TypeScript יזהה שגיאות סוג ונסיגות (regressions), וזה מה שקיווינו שיקרה כשהתחלנו לעבוד על ההעברה הזו. ההעברה הזו, כמו הרבה העברות אחרות, הייתה איטית, מורכבת ולפעמים מאתגרת, אבל אנחנו רואים את היתרונות שלה ועכשיו אנחנו מאמינים שהיא הייתה שווה את המאמץ.
מורידים את הערוצים של התצוגה המקדימה.
מומלץ להשתמש ב-Chrome Canary, ב-Dev או ב-Beta כדפדפן הפיתוח שמוגדר כברירת מחדל. ערוצי התצוגה המקדימה האלה מעניקים לכם גישה לתכונות העדכניות ביותר של DevTools, מאפשרים לכם לבדוק ממשקי API מתקדמים לפלטפורמות אינטרנט ולמצוא בעיות באתר לפני שהמשתמשים שלכם יעשו זאת.
יצירת קשר עם צוות כלי הפיתוח ל-Chrome
אתם יכולים להשתמש באפשרויות הבאות כדי לדון בתכונות החדשות, בעדכונים או בכל דבר אחר שקשור ל-DevTools.
- אתם יכולים לשלוח לנו משוב ובקשות להוספת תכונות בכתובת crbug.com.
- מדווחים על בעיה בכלי הפיתוח באמצעות הסמל אפשרויות נוספות > עזרה > דיווח על בעיה בכלי הפיתוח ב-DevTools.
- שולחים ציוץ אל @ChromeDevTools.
- אפשר להשאיר תגובות בסרטונים של מה חדש בכלי הפיתוח ב-YouTube או בסרטונים של טיפים לכלי הפיתוח ב-YouTube.