外部人材とエクスポート

インターンの目的

extern は、高度なコンパイル中に名前を変更しない記号の名前を Closure Compiler に伝える宣言です。これらのシンボルは、コンパイル外のコード(ネイティブ コードやサードパーティ ライブラリなど)で定義されることが多いため、extern と呼ばれます。そのため、Closure Compiler がこれらのシンボルの使用を型チェックできるように、externs には型アノテーションも含まれていることがよくあります。

一般に、extern は、コンパイル済みコードの一部を実装する側と使用する側との間の API 契約と考えるのが最適です。extern は、実装者が提供することを約束する内容と、コンシューマーが使用を依存できる内容を定義します。両者に契約書のコピーが必要です。

extern は、他の言語のヘッダー ファイルに似ています。

Externs の構文

externs は、Closure Compiler 用にアノテーションが付けられた通常の JavaScript とよく似たファイルです。主な違いは、コンパイルされた出力の一部として内容が出力されないため、値は意味がなく、名前と型のみが意味を持つことです。

次に、簡単なライブラリの 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 アノテーションは、ファイルに extern が含まれていることをコンパイラに通知する最良の方法です。このようなファイルは、--js コマンドライン フラグを使用して通常のソースファイルとして含めることができます。

ただし、extern ファイルを指定する別の古い方法もあります。--externs コマンドライン フラグを使用して、externs ファイルを明示的に渡すことができます。この方法はおすすめしません。

Extern の使用

上記の extern は次のように使用できます。

/**
 * @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;
}
    

エクスポートの目的

エクスポートは、コンパイル後にシンボルに一貫した名前を付けるためのもう 1 つのメカニズムです。extern ほど有用ではなく、混乱を招くことがよくあります。単純なケースを除き、使用は避けるのが最善です。

エクスポートは、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 のドキュメントをご覧ください。ただし、これらは特別なコンパイラ サポートがあり、コンパイルされた出力で完全に変換されることに注意してください。

エクスポートに関する問題

エクスポートは、コンシューマーが参照する公開されたエイリアスのみを作成するという点で、extern とは異なります。コンパイルされたコード内では、エクスポートされたシンボルは引き続き名前変更されます。このため、エクスポートされたシンボルは定数である必要があります。コードで再割り当てすると、公開されたエイリアスが間違ったものを指すことになります。

この名前変更の微妙な違いは、エクスポートされたインスタンス プロパティに関して特に複雑です。

理論上、エクスポートでは、長い名前をコード内で短い名前に変更できるため、externs よりもコードサイズを小さくできます。実際には、これらの改善は非常に小さいことが多く、エクスポートによって生じる混乱を正当化するものではありません。

また、エクスポートでは、externs のようにコンシューマーが従う API は提供されません。エクスポートと比較して、externs では、公開するシンボルとその型がドキュメント化され、使用情報を追加する場所が提供されます。また、コンシューマーが Closure Compiler を使用している場合は、コンパイル対象の externs が必要になります。