אוסף מתקדם

סקירה כללית

שימוש ב-Closure Compiler עם compilation_level של ADVANCED_OPTIMIZATIONS מניב שיעורי דחיסה טובים יותר מאשר קומפילציה עם SIMPLE_OPTIMIZATIONS או WHITESPACE_ONLY. הקומפילציה עם ADVANCED_OPTIMIZATIONS מאפשרת דחיסה נוספת כי היא אגרסיבית יותר בדרכים שבהן היא משנה קוד ומשנה את השם של הסמלים. עם זאת, הגישה האגרסיבית יותר הזו מחייבת הקפדה רבה יותר כשמשתמשים ב-ADVANCED_OPTIMIZATIONS כדי לוודא שקוד הפלט פועל באותו אופן כמו קוד הקלט.

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

לפני שקוראים את המדריך הזה, כדאי להכיר את תהליך ההידור של JavaScript באמצעות אחד מהכלים של Closure Compiler, כמו אפליקציית ההידור שמבוססת על Java.

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

  1. דחיסה טובה עוד יותר
  2. איך מפעילים את ADVANCED_OPTIMIZATIONS
  3. מה צריך לשים לב כשמשתמשים ב-ADVANCED_OPTIMIZATIONS
    1. הסרת קוד שרוצים לשמור
    2. שמות נכסים לא עקביים
    3. קומפילציה של שני חלקי קוד בנפרד
    4. הפניות שבורות בין קוד שעבר קומפילציה לבין קוד שלא עבר קומפילציה

דחיסה משופרת

ברמת הקומפילציה שמוגדרת כברירת מחדל, SIMPLE_OPTIMIZATIONS, הכלי Closure Compiler מקטין את קובץ ה-JavaScript על ידי שינוי השם של משתנים מקומיים. יש סמלים אחרים מלבד משתנים מקומיים שאפשר לקצר, ויש דרכים לצמצם את הקוד מלבד שינוי השם של הסמלים. קומפילציה עם ADVANCED_OPTIMIZATIONS מאפשרת לנצל את כל האפשרויות לצמצום הקוד.

משווים את הפלט של SIMPLE_OPTIMIZATIONS ושל ADVANCED_OPTIMIZATIONS לקוד הבא:

function unusedFunction(note) {   alert(note['text']); }  function displayNoteTitle(note) {   alert(note['title']); }  var flowerNote = {}; flowerNote['title'] = "Flowers"; displayNoteTitle(flowerNote);

קומפילציה עם SIMPLE_OPTIMIZATIONS מקצרת את הקוד כך:

function unusedFunction(a){alert(a.text)}function displayNoteTitle(a){alert(a.title)}var flowerNote={};flowerNote.title="Flowers";displayNoteTitle(flowerNote);

הקומפילציה עם ADVANCED_OPTIMIZATIONS מקצרת את הקוד באופן מלא כך:

alert("Flowers");

שני הסקריפטים האלה יוצרים התראה עם הטקסט "Flowers", אבל הסקריפט השני קצר בהרבה.

הרמה ADVANCED_OPTIMIZATIONS חורגת מקיצור פשוט של שמות משתנים בכמה דרכים, כולל:

  • שינוי שם אגרסיבי יותר:

    קומפילציה עם SIMPLE_OPTIMIZATIONS בלבד משנה את השם של הפרמטרים note של הפונקציות displayNoteTitle() ו-unusedFunction(), כי אלה המשתנים היחידים בסקריפט שהם מקומיים לפונקציה. ‫ADVANCED_OPTIMIZATIONS משנה גם את השם של המשתנה הגלובלי flowerNote.

  • הסרת קוד לא פעיל:

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

  • function inlining:

    קומפילציה עם ADVANCED_OPTIMIZATIONS מחליפה את הקריאה ל-displayNoteTitle() ב-alert() יחיד שמרכיב את גוף הפונקציה. החלפה כזו של קריאה לפונקציה בגוף הפונקציה נקראת 'הצבה'. אם הפונקציה הייתה ארוכה יותר או מורכבת יותר, יכול להיות שהטמעה שלה הייתה משנה את ההתנהגות של הקוד, אבל Closure Compiler קובע שבמקרה הזה ההטמעה בטוחה וחוסכת מקום. הידור עם ADVANCED_OPTIMIZATIONS גם משבץ קבועים וחלק מהמשתנים כשהוא קובע שאפשר לעשות זאת בצורה בטוחה.

הרשימה הזו היא רק דוגמה לשינויים שמקטינים את הגודל שאפשר לבצע באמצעות קומפילציה של ADVANCED_OPTIMIZATIONS.

איך מפעילים את האפשרות ADVANCED_OPTIMIZATIONS

כדי להפעיל את ADVANCED_OPTIMIZATIONS באפליקציית Closure Compiler, צריך לכלול את דגל שורת הפקודה --compilation_level ADVANCED_OPTIMIZATIONS, כמו בפקודה הבאה:

 java -jar compiler.jar --compilation_level ADVANCED_OPTIMIZATIONS --js hello.js 

דברים שכדאי לשים לב אליהם כשמשתמשים ב-ADVANCED_OPTIMIZATIONS

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

הסרה של קוד שרוצים לשמור

אם קומפיילים רק את הפונקציה שלמטה באמצעות ADVANCED_OPTIMIZATIONS, Closure Compiler יוצר פלט ריק:

function displayNoteTitle(note) {   alert(note['myTitle']); }

הפונקציה אף פעם לא נקראת ב-JavaScript שמועבר לקומפיילר, ולכן Closure Compiler מניח שהקוד הזה לא נחוץ.

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

עם זאת, אם אתם מגלים ש-Closure Compiler מסיר פונקציות שאתם רוצים לשמור, יש שתי דרכים למנוע את זה:

  • מעבירים את הקריאות לפונקציות לקוד שעובר עיבוד על ידי Closure Compiler.
  • כוללים externs לפונקציות שרוצים לחשוף.

בקטעים הבאים מוסבר על כל אפשרות בפירוט.

פתרון: מעבירים את הקריאות לפונקציות לקוד שעובר עיבוד על ידי Closure Compiler

אם קומפיילים רק חלק מהקוד באמצעות Closure Compiler, יכול להיות שקוד לא רצוי יוסר. לדוגמה, יכול להיות שיש לכם קובץ ספריה שמכיל רק הגדרות של פונקציות, וקובץ HTML שכולל את הספריה ומכיל את הקוד שמפעיל את הפונקציות האלה. במקרה כזה, אם קומפיילים את קובץ הספרייה באמצעות ADVANCED_OPTIMIZATIONS, ‏ Closure Compiler מסיר את כל הפונקציות של הספרייה.

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

function displayNoteTitle(note) {   alert(note['myTitle']); } displayNoteTitle({'myTitle': 'Flowers'});

הפונקציה displayNoteTitle() לא מוסרת במקרה הזה כי Closure Compiler רואה שהיא נקראת.

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

פתרון: כוללים Externs לפונקציות שרוצים לחשוף

מידע נוסף על הפתרון הזה מופיע בהמשך ובדף בנושא externs וייצוא.

שמות נכסים לא עקביים

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

לדוגמה, ניקח את הקוד הבא:

function displayNoteTitle(note) {   alert(note['myTitle']); } var flowerNote = {}; flowerNote.myTitle = 'Flowers';  alert(flowerNote.myTitle); displayNoteTitle(flowerNote);

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

var a={};a.a="Flowers";alert(a.a);alert(a.myTitle);

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

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

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

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

קומפילציה של שני חלקי קוד בנפרד

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

לדוגמה, נניח שאפליקציה מחולקת לשני חלקים: חלק שמחלץ נתונים וחלק שמציג נתונים.

הנה הקוד לאחזור הנתונים:

function getData() {   // In an actual project, this data would be retrieved from the server.   return {title: 'Flower Care', text: 'Flowers need water.'}; }

קוד להצגת הנתונים:

var displayElement = document.getElementById('display'); function displayData(parent, data) {   var textElement = document.createTextNode(data.text);   parent.appendChild(textElement); } displayData(displayElement, getData());

אם תנסו לקמפל את שני חלקי הקוד האלה בנפרד, תיתקלו בכמה בעיות. קודם כל, Closure Compiler מסיר את הפונקציה getData(), מהסיבות שמתוארות במאמר הסרה של קוד שרוצים לשמור. שנית, ה-Closure Compiler יוצר שגיאה חמורה כשמעבדים את הקוד שמציג את הנתונים.

 input:6: ERROR - variable getData is undefined displayData(displayElement, getData()); 

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

פתרון: קומפילציה של כל הקוד בדף

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

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

הפניות שבורות בין קוד שעבר קומפילציה לבין קוד שלא עבר קומפילציה

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

חשוב לזכור ש'קוד לא מהודר' כולל כל קוד שמועבר לפונקציה eval() כמחרוזת. ‫Closure Compiler אף פעם לא משנה מחרוזות מילוליות בקוד, ולכן הוא לא משנה מחרוזות שמועברות להצהרות eval().

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

לפני שממשיכים, כדאי לקרוא על externs וייצוא.

פתרון להתקשרות לקוד חיצוני מקוד שעבר קומפילציה: קומפילציה עם קובצי externs

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

דוגמאות נפוצות לכך הן ממשקי API כמו OpenSocial API ו-Google Maps API. לדוגמה, אם הקוד שלכם קורא לפונקציית OpenSocial‏ opensocial.newDataRequest(), בלי קובצי ה-externs המתאימים, Closure Compiler יהפוך את הקריאה הזו ל-a.b().

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

אם יש לכם קוד JavaScript שאתם משתמשים בו שוב ושוב כספרייה, יכול להיות שתרצו להשתמש ב-Closure Compiler כדי לצמצם רק את הספרייה, ועדיין לאפשר לקוד שלא עבר קומפילציה לקרוא לפונקציות בספרייה.

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

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

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

חשוב לציין כאן שאולי תקבלו אבחון של "הגדרה כפולה" לגבי הקוד שמגדיר את הסמלים החיצוניים. הכלי Closure Compiler מניח שכל סמל בקובצי ה-externs מסופק על ידי ספרייה חיצונית, ולא יכול להבין כרגע שאתם מספקים הגדרה בכוונה. אפשר להשבית את האבחונים האלה, וההשבתה היא אישור לכך שאתם באמת ממלאים את דרישות ה-API.

בנוסף, Closure Compiler יכול לבצע בדיקת סוגים כדי לוודא שההגדרות שלכם תואמות לסוגים של הצהרות extern. כך תוכלו לקבל אישור נוסף שההגדרות שלכם נכונות.