תוספות וייצוא

מטרת ההתמחות

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

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

קובצי extern דומים לקובצי כותרות בשפות אחרות.

תחביר של קובצי externs

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

בהמשך מופיעה דוגמה לקובץ externs לספרייה פשוטה.

// The `@externs` annotation is the best way to indicate a file contains externs.

/**
 * @fileoverview Public API of my_math.js.
 * @externs
 */

// Externs often declare global namespaces.

const myMath = {};

// Externs can declare functions, most importantly their names.

/**
 * @param {number} x
 * @param {number} y
 * @return {!myMath.DivResult}
 */
myMath.div = function(x, y) {};  // Note the empty body.

// Externs can contain type declarations, such as classes and interfaces.

/** The result of an integer division. */
myMath.DivResult = class {

  // Constructors are special; member fields can be declared in their bodies.

  constructor() {
    /** @type {number} */
    this.quotient;
    /** @type {number} */
    this.remainder;
  }

  // Methods can be declared as usual; their bodies are meaningless though.

  /** @return {!Array<number>} */
  toPair() {}

};

// Fields and methods can also be declared using prototype notation.

/**
 * @override
 * @param {number=} radix
 */
myMath.DivResult.prototype.toString = function(radix) {};
    

הדגל --externs

בדרך כלל, ההערה @externs היא הדרך הטובה ביותר להודיע לקומפיילר שקובץ מכיל externs. אפשר לכלול קבצים כאלה כקבצי מקור רגילים באמצעות הדגל --js בשורת הפקודה,

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

שימוש ב-Externs

אפשר להשתמש ב-externs שמופיעים למעלה באופן הבא.

/**
 * @fileoverview Do some math.
 */

/**
 * @param {number} x
 * @param {number} y
 * @return {number}
 */
export function greatestCommonDivisor(x, y) {
  while (y != 0) {
    const temp = y;
    // `myMath` is a global, it and `myMath.div` are never renamed.
    const result = myMath.div(x, y);
    // `remainder` is also never renamed on instances of `DivResult`.
    y = result.remainder;
    x = temp;
  }
  return x;
}
    

מטרת הייצוא

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

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

הנה דוגמה פשוטה.

/**
 * @fileoverview Do some math.
 */

// Note that the concept of module exports is totally unrelated.

/** @return {number} */
export function myFunction() {
  return 5;
}

// This assignment ensures `myFunctionAlias` will be a global alias exposing `myFunction`,
// even after compilation.

window['myFunctionAlias'] = myFunction;
    

אם אתם משתמשים ב-Closure Library, אפשר להצהיר על ייצוא גם באמצעות הפונקציות goog.exportSymbol ו-goog.exportProperty.

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

בעיות בייצוא

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

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

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

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