מבוא למפות מקור ב-JavaScript

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

מפות מקור מאפשרות למפות קובץ משולב/מוקטן בחזרה למצב לא מובנה. במהלך הפיתוח לסביבת הייצור, יחד עם הקטנה ושילוב של קובצי ה-JavaScript, יוצרים מפת מקור שמכילה מידע על הקבצים המקוריים. כשמריצים שאילתה על שורה ועמודה מסוימים ב-JavaScript שנוצר, אפשר לחפש במפת המקור שיחזיר את המיקום המקורי. כלים למפתחים (כרגע WebKit גרסאות build ליליות, Google Chrome או Firefox 23 ואילך) יכולים לנתח את מפת המקור באופן אוטומטי ולהציג אותה כאילו אתם מפעילים קבצים לא משולבים ולא משולבים.

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

דוגמה לספריית המקור של JavaScript של Mozilla בפעולה.

בעולם האמיתי

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

איך מפעילים מפות מקור בכלי WebKit למפתחים.

ב-Firefox 23 ואילך, מפות המקור מופעלות כברירת מחדל בכלי הפיתוח המובנים.

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

למה מפות מקור צריכות לעניין אותי?

בשלב זה מיפוי המקור פועל רק בין JavaScript לא דחוס או משולב ל-JavaScript דחוס/לא משולב. עם זאת, העתיד נראה זוהר עם שיחות על שפות מה הידור ל-JavaScript, כמו קפהScript, ואפילו אפשרות להוסיף תמיכה במעבדים מקדימים של CSS כמו SASS או LESS.

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

  • CoffeeScript
  • ECMAScript 6 ואילך
  • SASS/LESS ואחרים
  • כמעט כל שפה שעברה הידור ל-JavaScript

כדאי לצפות בהקלטת המסך הזו של ניפוי באגים ב-CookieScript ב-build ניסיוני של קונסולת Firefox:

ב-Google Web Toolkit (GWT) נוספה לאחרונה תמיכה במפות מקור. ריי קרומוול מצוות GWT ביצע הקלטת מסך מדהימה שהציגה את התמיכה במפת המקור בפעולה.

דוגמה נוספת שהרכבתי משתמשת בספריית Traceur של Google, שמאפשרת לכתוב ES6 (ECMAScript 6 או Next) ולהרכיב אותו לקוד תואם ES3. המהדר של Traceur גם יוצר מפת מקור. אפשר לראות את ההדגמה הזו של תכונות ומחלקות של ES6, שבהן נעשה שימוש כאילו הם נתמכים בדפדפן באופן טבעי, הודות למפת המקור.

אזור textarea בהדגמה מאפשר גם לכתוב ES6, שתעובד בזמן אמת ותיצור מפת מקור יחד עם קוד ES3 המקביל.

ניפוי באגים ב-Traceur ES6 באמצעות מפות מקור.

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

איך פועלת מפת המקור?

המהדר/מזעור היחיד של JavaScript שתומך כרגע ביצירת מפות מקור הוא מהדר החסימות. (אסביר בהמשך איך להשתמש בו). לאחר שילוב והקטנת ה-JavaScript, לצדו יהיה קובץ מפת מקור.

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

//# sourceMappingURL=/path/to/file.js.map

כך, הכלים למפתחים יכולים למפות שיחות בחזרה למיקום שלהן בקובצי המקור המקוריים. בעבר פרגמה של התגובות הייתה //@, אבל עקב בעיות מסוימות עם התגובות המותנות של IE, התקבלה החלטה לשנות אותה ל-//#. נכון לעכשיו, Chrome Canary, WebKit Nightly ו-Firefox 24+ תומכים בפרגמת התגובות החדשה. השינוי הזה בתחביר משפיע גם על sourceURL.

אם התגובה המוזרה לא מוצאת חן בעיניכם, תוכלו להגדיר כותרת מיוחדת בקובץ ה-JavaScript המקושט:

X-SourceMap: /path/to/file.js.map

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

דוגמה ל-WebKit Devtools של מפות מקור מופעלות ושל מפות מקור מושבתות.

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

איך יוצרים מפת מקור?

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

java -jar compiler.jar \
--js script.js \
--create_source_map ./script-min.js.map \
--source_map_format=V3 \
--js_output_file script-min.js

שני דגלי הפקודות החשובים הם --create_source_map ו---source_map_format. האפשרות הזו נדרשת כי גרסת ברירת המחדל היא V2 ואנחנו רוצים לעבוד רק עם V3.

המבנה של מפת מקור

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

{
    version : 3,
    file: "out.js",
    sourceRoot : "",
    sources: ["foo.js", "bar.js"],
    names: ["src", "maps", "are", "fun"],
    mappings: "AAgBC,SAAQ,CAAEA"
}

למעלה אפשר לראות שמפת מקור היא ייצוג מילולי של אובייקט שמכיל הרבה מידע עסיסי:

  • מספר הגרסה שעליה מבוססת מפת המקור
  • שם הקובץ של הקוד שנוצר (קובץ הייצור המשולב/הממוזער)
  • sourceRoot מאפשרת להוסיף לתחילת המקורות מבנה של תיקיות - זוהי גם שיטה לחיסכון במקום
  • המקורות מכילים את כל שמות הקבצים שולבו
  • מכילים את כל שמות המשתנים/השיטות שמופיעים לאורך הקוד.
  • לבסוף, מאפיין המיפויים הוא המקום שבו מתרחש הקסם של שימוש בערכי VLQ מסוג Base64. החיסכון האמיתי במקום מתבצע כאן.

Base64 VLQ והקטנת מפת המקור

במקור, למפרט של מפת המקור היה פלט מפורט מאוד של כל המיפויים, וכתוצאה מכך מפת המקור הייתה גדולה פי 10 בערך מהגודל של הקוד שנוצר. בגרסה השנייה הופחתו בכ-50% ובגרסה שלוש הופחתו שוב ב-50%, כך שעבור קובץ בגודל 133kB מקבלים מפת מקור בגודל של כ-300kB.

אז איך הם הפחיתו את הגודל ועדיין שמרו על המיפויים המורכבים?

נעשה שימוש ב-VLQ (כמות באורך משתנה) יחד עם קידוד הערך לערך Base64. מאפיין המיפויים הוא מחרוזת גדולה במיוחד. בתוך המחרוזת הזו יש נקודה ופסיק (;) שמייצגת מספר שורה בתוך הקובץ שנוצר. בכל שורה יש פסיקים (,) שמייצגים כל פלח באותה שורה. כל אחד מהפלחים האלה הוא 1, 4 או 5 בשדות באורך משתנה. חלקם עשויים להופיע ארוכים יותר, אבל הם מכילים קטעי המשך. כל מקטע מסתמך על הקטע הקודם, וכך עוזר להקטין את גודל הקובץ מכיוון שכל ביט הוא ביחס לקטעים הקודמים שלו.

פירוט של קטע בקובץ JSON של מפת המקור.

כפי שצוין למעלה, לכל מקטע יכול להיות 1, 4 או 5 באורך משתנה. הדיאגרמה הזו נחשבת לאורך משתנה של ארבע עם ביט המשך אחד (g). נציג פירוט של הפלח הזה והצגה של מפת המקור במיקום המקורי.

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

  • עמודה שנוצרה
  • הקובץ המקורי שבו הופיעה
  • מספר השורה המקורית
  • העמודה המקורית
  • ואם אפשר, שם מקורי

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

אחרי עיבוד הנתונים בתרשים שלמעלה, הערך AAgBC יחזיר את הערכים 0, 0, 32, 16, 1. 32 הוא ביט ההמשך שעוזר לבנות את הערך הבא של 16. B רק המפוענח ב-Base64 הוא 1. לכן, הערכים החשובים שנמצאים בשימוש הם 0, 0, 16, 1. לאחר מכן נדע ששורה 1 (השורות נספרות לפי נקודה-פסיק) עמודה 0 במפות הקבצים שנוצרות לקובץ 0 (מערך הקבצים 0 הוא foo.js), שורה 16 בעמודה 1.

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

כדי להבין כראוי איך אנחנו מקבלים את ערך 16 מ-B, נדרשת הבנה בסיסית של האופרטורים של הביטים השונים ואיך המפרט פועל למיפוי מקורות. הספרה הקודמת, g, מסומנת כביט המשך על ידי השוואה בין הספרה (32) לבין VLQ_CONTINUATION_BIT (בינארי 100000 או 32) באמצעות אופרטור AND (&) ברמת הסיביות.

32 & 32 = 32
// or
100000
|
|
V
100000

הפעולה הזו תחזיר 1 בכל מיקום של ביט שבו הוא מופיע. לכן, ערך 33 & 32 מפוענח של Base64 יחזיר 32, כי הוא משתף רק את המיקום של 32 ביט, כפי שניתן לראות בתרשים שלמעלה. לאחר מכן, הפעולה הזו מגדילה ב-5 את ערך השינוי של הביט לכל ביט ההמשך הקודם. במקרה שלמעלה, היא השתנתה רק ב-5 פעם אחת, לכן שמאלה מחליף 1 (B) ב-5.

1 <<../ 5 // 32

// Shift the bit by 5 spots
______
|    |
V    V
100001 = 100000 = 32

לאחר מכן, הערך הזה מומר מערך חתום של VLQ על ידי הזזת המספר (32) נקודה אחת ימינה.

32 >> 1 // 16
//or
100000
|
 |
 V
010000 = 16

אז סיימנו: כך הופכים מ-1 ל-16. התהליך הזה אולי נראה מסובך מדי, אבל כשהמספרים גדלים זה הגיוני יותר.

בעיות XSSI אפשריות

במפרט מפורטות בעיות שקשורות להכללה של סקריפטים באתרים שונים, שעלולות להיגרם כתוצאה מהצריכה של מפת מקור. כדי לצמצם את הסיכון, מומלץ להוסיף את השורה הראשונה במפת המקור לערך ')]}' כדי לבטל בכוונה את התוקף של JavaScript, ואז תתקבל שגיאת תחביר. כלי הפיתוח של WebKit כבר יכולים לפתור את הבעיה.

if (response.slice(0, 3) === ")]}") {
    response = response.substring(response.indexOf('\n'));
}

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

sourceURL ו-displayName בפעולה: הערכה ופונקציות אנונימיות

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

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

הדגמה: הצגת הקוד של eval() כסקריפט בכתובת sourceURL

//# sourceURL=sqrt.coffee
איך נראית תגובה מיוחדת של sourceURL בכלים של פיתוח

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

btns[0].addEventListener("click", function(e) {
    var fn = function() {
        console.log("You clicked button number: 1");
    };

    fn.displayName = "Anonymous function of button 1";

    return fn();
}, false);
הצגת המאפיין displayName בפעולה.

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

נכון למועד הכתיבה, שמות ה-eval זמינים רק בדפדפנים Firefox ו-WebKit. הנכס displayName זמין רק ללילה ב-WebKit.

ביחד

כרגע יש דיון ארוך מאוד לגבי הוספת תמיכה במפת מקור ל-CafeScript. כדאי לבדוק את הבעיה ולהוסיף תמיכה כדי להוסיף יצירת מפות מקור למהדר (compiler) ב-CafeScript. זה יהיה זכייה ענקית של CookieScript והעוקבים אחריה.

ב-UglifyJS יש גם בעיה במפת מקור שכדאי גם לבדוק.

tools רבים יוצרים מפות מקור, כולל מהדר קפה (Cafescript). אני רואה את זה כנקודת דיון עכשיו.

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

זה לא מושלם

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

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

בעיות

לאחרונה הוסיפה jQuery 1.9 תמיכה במפות מקור כאשר הן מוצגות מחוץ לרשתות CDN לא מקוונות. הוא גם הצביע על באג מסוים כשנעשה שימוש בתגובות מותנות של IE (//@cc_on) לפני טעינת ה-jQuery. מאז קיימת מחויבות לצמצם את התופעה הזו על ידי גלישת ה-sourceMappingURL בתגובה מרובת שורות. השיעור שנלמד לא להשתמש בתגובות מותנות.

הבעיה הזו טופלה מאז ושינוי התחביר ל-//#.

כלים ומקורות מידע

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

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

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