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

חוויית ניפוי באגים משופרת

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

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

קוד להוספה לרשימת ההתעלמות

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

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

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

קובץ GIF מונפש שמציג את DevTools לפני ואחרי. שימו לב איך בתמונה שאחריה, כלי הפיתוח מציגים את הקוד בעל ההרשאה בעץ, כבר לא מציעים אף אחד מקובצי ה-framework בתפריט 'פתיחה מהירה', ובצד שמאל רואים דוח קריסות הרבה יותר נקי.

התוסף של מפת המקור x_google_ignoreList

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

בהמשך מופיעה מפת מקור של קובץ שנוצר, out.js. יש שני קבצים מקוריים מסוג sources שתרמו ליצירת קובץ הפלט: foo.js ו-lib.js. הקוד הראשון הוא קוד שכתב מפתח אתר, והקוד השני הוא מסגרת שבה הוא השתמש.

{
  "version" : 3,
  "file": "out.js",
  "sourceRoot": "",
  "sources": ["foo.js", "lib.js"],
  "sourcesContent": ["...", "..."],
  "names": ["src", "maps", "are", "fun"],
  "mappings": "A,AAAB;;ABCDE;"
}

ה-sourcesContent נכלל בשני המקורות המקוריים האלה, וקבצים אלה יוצגו כברירת מחדל בכלי הפיתוח ל-Chrome בכלי לניפוי הבאגים:

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

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

{
  ...
  "sources": ["foo.js", "lib.js"],
  "x_google_ignoreList": [1],
  ...
}

השדה x_google_ignoreList החדש מכיל אינדקס יחיד שמפנה למערך sources: 1. המשמעות היא שהאזורים שממופים אל lib.js הם למעשה קוד של צד שלישי שצריך להוסיף באופן אוטומטי לרשימת הפריטים להתעלמות.

בדוגמה מורכבת יותר, שמוצגת בהמשך, המדדים 2, 4 ו-5 מציינים שהאזורים שממופים אל lib1.ts,‏ lib2.coffee ו-hmr.js הם כולם קוד של צד שלישי שצריך להוסיף באופן אוטומטי לרשימת ההתעלמות.

{
  ...
  "sources": ["foo.html", "bar.css", "lib1.ts", "baz.js", "lib2.coffee", "hmr.js"],
  "x_google_ignoreList": [2, 4, 5],
  ...
}

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

x_google_ignoreList בזווית

החל מ-Angular v14.1.0, התוכן של התיקיות node_modules ו-webpack סומן כ'לצורך התעלמות'.

השגנו זאת באמצעות שינוי ב-angular-cli על ידי יצירת פלאגין שמתחבר למודול Compiler של webpack.

הפלאגין של webpack שהמהנדסים שלנו יצרו מתחבר לשלב PROCESS_ASSETS_STAGE_DEV_TOOLING ומאכלס את השדה x_google_ignoreList במפות המקור של הנכסים הסופיים ש-webpack יוצר והדפדפן טוען.

const map = JSON.parse(mapContent) as SourceMap;
const ignoreList = [];

for (const [index, path] of map.sources.entries()) {
  if (path.includes('/node_modules/') || path.startsWith('webpack/')) {
    ignoreList.push(index);
  }
}

map[`x_google_ignoreList`] = ignoreList;
compilation.updateAsset(name, new RawSource(JSON.stringify(map)));

מעקבי ערימה מקושרים

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

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

כדי לטפל בבעיה הזו, DevTools חושף מנגנון שנקרא 'Async Stack Tagging API' באובייקט console, שמאפשר למפתחי המסגרת להציע רמזים לגבי המיקומים שבהם הפעולות מתוזמנות וגם לגבי המיקומים שבהם הפעולות האלה מתבצעות.

Async Stack Tagging API

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

דוח קריסות של קוד ביצוע אסינכרוני בלי מידע על המועד שבו הוא תוזמן. הוא מציג רק את דוח הקריסות החל מ-'requestAnimationFrame', אבל לא מכיל מידע ממועד התזמון.

בעזרת Async Stack Tagging אפשר לספק את ההקשר הזה, ודוח הקריסות האסינכרוני נראה כך:

מעקב סטאק של קוד אסינכרוני מסוים שבוצע, עם מידע על מועד התזמון שלו. שימו לב שבניגוד לקודם, הפעם 'businessLogic' ו-'schedule' נכללים ב-stack trace.

כדי לעשות זאת, משתמשים בשיטה חדשה של console בשם console.createTask() ש-Async Stack Tagging API מספק. החתימה שלו היא:

interface Console {
  createTask(name: string): Task;
}

interface Task {
  run<T>(f: () => T): T;
}

קריאה ל-console.createTask() מחזירה מופע של Task, שאפשר להשתמש בו מאוחר יותר כדי להריץ את הקוד האסינכרוני.

// Task Creation
const task = console.createTask(name);

// Task Execution
task.run(f);

אפשר גם למקם את הפעולות האסינכרוניות, ו'סיבות הבסיס' יוצגו בדוח הקריסות ברצף.

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

ה-API של תיוג אסינכרוני ב-Angular

ב-Angular בוצעו שינויים ב-NgZone – הקשר הביצוע של Angular שנשמר במשימות אסינכררוניות.

כשמתזמנים משימה, היא משתמשת ב-console.createTask() כשהיא זמינה. המכונה Task שנוצרת מאוחסנת לשימוש נוסף. כשמפעילים את המשימה, NgZone ישתמש במכונה של Task ששמורה כדי להריץ אותה.

השינויים האלה הובילו ל-NgZone 0.11.8 של Angular, באמצעות בקשות משיכה #46693 ו-#46958.

מסגרות שיחה ידידותיות

כשמפתחים פרויקט, רוב המסגרות יוצרות קוד מכל סוגי שפות התבניות. למשל, תבניות של Angular או JSX שממירות קוד שנראה כמו HTML ל-JavaScript רגיל שפועל בסופו של דבר בדפדפן. לפעמים, לפונקציות כאלה שנוצרות ניתנים שמות לא ידידותיים במיוחד – שמות של אות אחת אחרי שהן עוברות אופטימיזציה למינימום או שמות מעורפלים או לא מוכרים, גם אם הם לא כאלה.

ב-Angular, לא נדיר לראות מסגרות קריאה עם שמות כמו AppComponent_Template_app_button_handleClick_1_listener בנתוני מעקב הסטאק.

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

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

מסגרות קריאה ידידותיות ב-Angular

שינוי השם של פריימים של שיחות ב-Agular הוא מאמץ מתמשך. אנחנו צופים שהשיפורים האלה יתווספו בהדרגה עם הזמן.

במהלך ניתוח תבניות ה-HTML שכתבו הכותבים, המהדר של Angular יוצר קוד TypeScript, שבסופו של דבר עובר לקוד JavaScript שהדפדפן טוען ומפעיל.

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

לדוגמה, אם נוצרת פונקציה למעקב אחרי אירועים והשם שלה לא ידידותי או הוסר במהלך המינימיזציה, מפות המקור יכולות עכשיו לכלול את השם הידידותי יותר של הפונקציה הזו בשדה 'names', והמיפוי של תחילת היקף הפונקציה יכול עכשיו להפנות לשם הזה (כלומר, סוגר הסוגריים הימני של רשימת הפרמטרים). כלי הפיתוח ל-Chrome ישתמשו בשמות האלה כדי לשנות את השם של פריימים של שיחות בדוחות הקריסות.

במבט קדימה

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

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