Finalidade dos externs
Externs são declarações que informam ao Closure Compiler os nomes dos símbolos que não devem ser renomeados durante a compilação avançada. Eles são chamados de externs porque esses símbolos são definidos com mais frequência por código fora da compilação, como código nativo ou bibliotecas de terceiros. Por isso, as externs geralmente também têm anotações de tipo, para que o Closure Compiler possa verificar o uso desses símbolos.
Em geral, é melhor pensar em externs como um contrato de API entre o implementador e os consumidores de algum trecho de código compilado. Os externs definem o que o implementador promete fornecer e o que os consumidores podem usar. Ambos os lados precisam de uma cópia do contrato.
Os externs são semelhantes aos arquivos de cabeçalho em outras linguagens.
Sintaxe de externs
Externs são arquivos muito parecidos com JavaScript normal anotado para o Closure Compiler. A principal diferença é que o conteúdo deles nunca é impresso como parte da saída compilada. Portanto, nenhum dos valores é significativo, apenas os nomes e tipos.
Confira abaixo um exemplo de arquivo de externs para uma biblioteca simples.
// 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) {};
A flag --externs
Em geral, a anotação @externs
é a melhor maneira de informar
ao compilador que um arquivo contém externs. Esses arquivos podem ser incluídos
como arquivos de origem normais usando a flag de linha de comando --js
.
No entanto, há outra maneira, mais antiga, de especificar arquivos externs. A flag de linha de comando --externs
pode ser usada para transmitir arquivos de externs explicitamente. Esse método não é recomendado.
Como usar externs
Os externs acima podem ser consumidos da seguinte forma.
/** * @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; }
Finalidade das exportações
As exportações são outro mecanismo para dar aos símbolos nomes consistentes após a compilação. Eles são menos úteis do que os externs e geralmente confusos. Para todos os casos, exceto os simples, é melhor evitar.
As exportações dependem do fato de que o Closure Compiler não modifica literais de string. Ao atribuir um objeto a uma propriedade nomeada usando um literal, o objeto vai ficar disponível pelo nome da propriedade mesmo após a compilação.
Confira um exemplo simples abaixo.
/** * @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;
Se você estiver usando a Closure Library, as exportações também poderão ser declaradas usando as funções
goog.exportSymbol
e goog.exportProperty
.
Mais informações estão disponíveis na documentação da biblioteca Closure sobre essas funções. No entanto, elas têm suporte especial do compilador e serão totalmente transformadas na saída compilada.
Problemas com exportações
As exportações são diferentes das externs porque criam apenas um alias exposto para referência dos consumidores. No código compilado, o símbolo exportado ainda será renomeado. Por isso, os símbolos exportados precisam ser constantes, já que reatribuí-los no código faria com que o alias exposto apontasse para algo errado.
Essa sutileza na renomeação é especialmente complicada em relação às propriedades de instância exportadas.
Em teoria, as exportações podem permitir um tamanho de código menor em comparação com as externs, já que nomes longos ainda podem ser alterados para mais curtos no seu código. Na prática, essas melhorias costumam ser muito pequenas e não justificam a confusão criada pelas exportações.
As exportações também não fornecem uma API para os consumidores seguirem da mesma forma que os externs. Em comparação com as exportações, os externs documentam os símbolos que você pretende expor, os tipos deles e oferecem um lugar para adicionar informações de uso. Além disso, se os consumidores também usarem o Closure Compiler, eles vão precisar de externs para compilar.