확장 및 내보내기

인턴의 목적

extern은 고급 컴파일 중에 이름이 변경되지 않아야 하는 심볼의 이름을 Closure Compiler에 알려주는 선언입니다. 이러한 심볼은 컴파일 외부의 코드(예: 네이티브 코드 또는 서드 파티 라이브러리)에 의해 정의되는 경우가 많으므로 externs라고 합니다. 이러한 이유로 Closure Compiler가 이러한 심볼의 사용을 유형 검사할 수 있도록 externs에는 유형 주석도 있는 경우가 많습니다.

일반적으로 extern은 컴파일된 코드의 일부 구현자와 소비자 간의 API 계약으로 생각하는 것이 좋습니다. externs는 구현자가 제공하기로 약속한 것과 소비자가 사용하는 데 의존할 수 있는 것을 정의합니다. 양측 모두 계약서 사본이 필요합니다.

extern은 다른 언어의 헤더 파일과 유사합니다.

Externs 구문

extern은 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 명령줄 플래그를 사용하여 일반 소스 파일로 포함할 수 있습니다.

하지만 externs 파일을 지정하는 또 다른 이전 방식이 있습니다. --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;
}
    

내보내기 목적

내보내기는 컴파일 후 기호에 일관된 이름을 부여하는 또 다른 메커니즘입니다. 이러한 변수는 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.exportSymbolgoog.exportProperty 함수를 사용하여 내보내기를 선언할 수도 있습니다.

자세한 내용은 이러한 함수의 Closure Library 문서에서 확인할 수 있습니다. 하지만 특수한 컴파일러 지원이 있으며 컴파일된 출력에서 완전히 변환됩니다.

내보내기 관련 문제

내보내기는 소비자가 참조할 노출된 별칭만 만든다는 점에서 extern과 다릅니다. 컴파일된 코드 내에서 내보낸 심볼은 여전히 이름이 변경됩니다. 따라서 내보낸 기호는 상수여야 합니다. 코드에서 다시 할당하면 노출된 별칭이 잘못된 항목을 가리키기 때문입니다.

이러한 이름 변경의 미묘한 차이는 내보낸 인스턴스 속성과 관련하여 특히 복잡합니다.

이론적으로 내보내기는 긴 이름을 코드 내에서 더 짧은 이름으로 변경할 수 있으므로 externs에 비해 코드 크기가 더 작을 수 있습니다. 실제로 이러한 개선사항은 매우 미미한 경우가 많으며, 내보내기로 인해 발생하는 혼란을 정당화하지 않습니다.

또한 내보내기는 소비자가 따라야 하는 API를 제공하지 않습니다(externs와 달리). 내보내기와 비교할 때 externs는 노출하려는 심볼과 그 유형을 문서화하고 사용 정보를 추가할 위치를 제공합니다. 또한 소비자가 Closure Compiler를 사용하는 경우 컴파일에 사용할 extern이 필요합니다.