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

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

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

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

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

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

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

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

קובץ GIF מונפש שמציג את כלי הפיתוח לפני ואחרי. שימו לב שאחרי התמונה, כלי הפיתוח מציגים את הקוד Authored בעץ, לא מציעים יותר קובצי 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 יציגו את הקבצים האלה כברירת מחדל בכלי לניפוי באגים:

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

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

{
  ...
  "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],
  ...
}

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

x_google_ignoreList ב-Angular

החל מגרסה 14.1.0 של Anngular, תוכן התיקיות node_modules ו-webpack סומן כ-"to ignore".

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

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

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

כדי לפתור את הבעיה, כלי הפיתוח חושפים על האובייקט console מנגנון שנקרא Async Stack tagging API (ממשק API לתיוג אסינכרוניים ב-Stack API), שמאפשר למפתחים של framework לרמוז גם על המיקומים שבהם הפעולות מתוזמנות וגם על המקומות שבהם הפעולות האלה מתבצעות.

API לתיוג מקבץ אסינכרוני

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

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

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

דוח קריסות של קוד אסינכרוני מסוים שבוצע עם מידע על המועד שבו הוא תוזמן. חשוב לשים לב איך הוא כולל את 'businessLogic' ואת 'Schedule' (לוח זמנים) בדוח הקריסות.

כדי לעשות זאת, צריך להשתמש בשיטה console חדשה בשם console.createTask(), שמספקת את Async Stack Stack 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);

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

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

ה-API לתיוג מקבץ אסינכרוני בזווית

ב-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

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

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

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

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

במבט קדימה

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

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