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

ריאן סדון

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

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

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

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

העולם האמיתי

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

כיצד להפעיל מפות מקור בכלי WebKit dev.

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

כיצד להפעיל מפות מקור בכלי הפיתוח של Firefox.

למה מפות המקור צריכות להיות חשובות?

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Base64 VLQ והקפדה על גודל מפת המקור

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

אז איך הם הקטינו את הגודל תוך כדי שמירה על המיפויים המורכבים?

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

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

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

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

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

לא לכל פלח יש שם, שם שיטה או ארגומנט, כך שקטעים בכל פלח ישונו בין 4-5 אורך משתנה. הערך 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 dev, שגם הוא כתוב ב-JavaScript.

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

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

הפעולה הזו מחזירה 1 בכל מיקום ביט שבו היא מופיעה. לכן, ערך מפוענח של Base64 של 33 & 32 יחזיר 32 כי הם חולקים רק את המיקום של 32 ביט, כפי שניתן לראות בתרשים שלמעלה. הפעולה הזו מגדילה את ערך ה-Shift של הביטים ב-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 בפעולה: פונקציות הערכה ופונקציות אנונימיות

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

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

הדגמה: ניתן לראות שהקוד של 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.

נכון לרגע כתיבת השם להערכה, ניתן להשתמש רק בדפדפנים Firefox ו-WebKit. המאפיין displayName נמצא רק בלילה ב-WebKit.

בואו נפגשים

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

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

tools יוצרים מפות מקור, כולל מהדר של Coffeescript. אני רואה את זה עכשיו כסעיף.

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

זה לא מושלם

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

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

בעיות

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

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

כלים ומשאבים

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

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

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