Eksporty i eksporty

Cel praktyk

Externs to deklaracje, które informują kompilator Closure Compiler o nazwach symboli, które nie powinny być zmieniane podczas kompilacji zaawansowanej. Nazywa się je zewnętrznymi, ponieważ te symbole są najczęściej definiowane przez kod poza kompilacją, np. kod natywny lub biblioteki innych firm. Z tego powodu pliki externs często zawierają też adnotacje o typach, dzięki czemu kompilator Closure Compiler może sprawdzać typy używanych symboli.

Ogólnie rzecz biorąc, pliki externs można traktować jako umowę API między implementatorem a konsumentami fragmentu skompilowanego kodu. Pliki externs określają, co implementujący zobowiązuje się dostarczyć i z czego mogą korzystać użytkownicy. Obie strony muszą mieć kopię umowy.

Pliki extern są podobne do plików nagłówkowych w innych językach.

Składnia plików externs

Pliki externs wyglądają bardzo podobnie do zwykłych plików JavaScript z adnotacjami dla kompilatora Closure. Główna różnica polega na tym, że ich zawartość nigdy nie jest drukowana w ramach skompilowanych danych wyjściowych, więc żadna z wartości nie ma znaczenia. Ważne są tylko nazwy i typy.

Poniżej znajdziesz przykład pliku externs dla prostej biblioteki.

// 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) {};
    

Flaga --externs

Ogólnie adnotacja @externs jest najlepszym sposobem na poinformowanie kompilatora, że plik zawiera externs. Takie pliki można uwzględnić jako zwykłe pliki źródłowe za pomocą flagi wiersza poleceń --js.

Istnieje jednak inny, starszy sposób określania plików externs. Flaga wiersza poleceń --externs umożliwia jawne przekazywanie plików externs. Nie zalecamy tej metody.

Korzystanie z usług osób z zewnątrz

Z zewnętrznych plików definicji można korzystać w ten sposób:

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

Cel eksportu

Eksporty to kolejny mechanizm nadawania symbolom spójnych nazw po kompilacji. Są mniej przydatne niż externy i często wprowadzają w błąd. W większości przypadków lepiej ich unikać.

Eksportowanie opiera się na tym, że kompilator Closure nie modyfikuje literałów ciągów znaków. Jeśli przypiszesz obiekt do właściwości o nazwie utworzonej za pomocą literału, obiekt będzie dostępny pod tą nazwą właściwości nawet po skompilowaniu.

Poniżej znajdziesz prosty przykład.

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

Jeśli używasz biblioteki Closure, eksporty można też deklarować za pomocą funkcji goog.exportSymbolgoog.exportProperty.

Więcej informacji znajdziesz w dokumentacji biblioteki Closure dotyczącej tych funkcji. Pamiętaj jednak, że mają one specjalne wsparcie kompilatora i zostaną całkowicie przekształcone w skompilowanych danych wyjściowych.

Problemy z eksportowaniem

Eksporty różnią się od zmiennych zewnętrznych tym, że tworzą tylko widoczny alias, do którego mogą się odwoływać odbiorcy. W skompilowanym kodzie wyeksportowany symbol nadal będzie miał zmienioną nazwę. Z tego powodu wyeksportowane symbole muszą być stałe, ponieważ ponowne przypisanie ich w kodzie spowoduje, że ujawniony alias będzie wskazywać nieprawidłowy element.

Ta subtelna różnica w nazewnictwie jest szczególnie skomplikowana w przypadku wyeksportowanych właściwości instancji.

Teoretycznie eksporty mogą mieć mniejszy rozmiar kodu niż externs, ponieważ długie nazwy można nadal zmieniać na krótsze w kodzie. W praktyce te ulepszenia są często bardzo niewielkie i nie uzasadniają zamieszania, jakie powodują eksporty.

Eksporty nie udostępniają też interfejsu API, z którego mogliby korzystać konsumenci, tak jak to robią externy. W porównaniu z eksportami externy dokumentują symbole, które chcesz udostępnić, ich typy i umożliwiają dodawanie informacji o użyciu. Dodatkowo, jeśli Twoi klienci używają też kompilatora Closure Compiler, będą potrzebować plików zewnętrznych do kompilacji.