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

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

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

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

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

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

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

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

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

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

x_google_ignoreList בזווית

החל מגרסה 14.1.0 של Angular, תוכן התיקיות 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)));

דוחות קריסות מקושרים

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

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

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

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

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

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

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

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

כדי לעשות זאת, צריך להשתמש בשיטת 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);

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

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

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

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

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

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

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

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

ב-Agular, לעיתים קרובות ניתן לראות פריימים של שיחות עם שמות כמו AppComponent_Template_app_button_handleClick_1_listener בדוחי קריסות.

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

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

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

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

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

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

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

במבט קדימה

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

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