Цель экстернов
Внешние элементы (extern) — это объявления, сообщающие Closure Compiler имена символов, которые не следует переименовывать во время расширенной компиляции. Они называются внешними элементами (extern), поскольку эти символы чаще всего определяются кодом вне компиляции, например, машинным кодом или сторонними библиотеками. По этой причине внешние элементы часто также имеют аннотации типов, чтобы Closure Compiler мог проверять тип вашего использования этих символов.
В целом, внешние объекты лучше всего рассматривать как API-контракт между разработчиком и потребителями скомпилированного кода. Внешние объекты определяют, что разработчик обещает предоставить, и на что могут рассчитывать потребители. Обеим сторонам необходим экземпляр контракта.
Внешние файлы похожи на заголовочные файлы в других языках.
Синтаксис Externs
Внешние файлы (extern) очень похожи на обычный 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
— лучший способ сообщить компилятору о наличии в файле внешних переменных. Такие файлы можно включить как обычные исходные файлы с помощью флага командной строки --js
.
Однако существует другой, более старый способ указать внешние файлы. Для явного указания внешних файлов можно использовать флаг командной строки --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; }
Цель экспорта
Экспорты — ещё один механизм присвоения символам согласованных имён после компиляции. Они менее полезны, чем внешние объекты, и часто вызывают путаницу. За исключением простых случаев их лучше избегать.
Экспорт основан на том, что 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. Однако имейте в виду, что они поддерживаются специальным компилятором и будут полностью преобразованы в скомпилированном выводе.
Проблемы с экспортом
Экспортируемые символы отличаются от внешних переменных тем, что они создают только открытый псевдоним, на который могут ссылаться потребители. В скомпилированном коде экспортируемый символ всё равно будет переименован. По этой причине экспортируемые символы должны быть константными, поскольку их переназначение в коде приведёт к тому, что открытый псевдоним будет указывать на неверный объект.
Эта тонкость переименования особенно сложна в отношении экспортированных свойств экземпляра.
Теоретически экспорт может позволить уменьшить размер кода по сравнению с внешними объектами, поскольку длинные имена по-прежнему можно заменить на более короткие в коде. На практике эти улучшения часто незначительны и не оправдывают путаницы, которую создает экспорт.
Экспортные объекты также не предоставляют потребителям API, к которому они могли бы обращаться, как внешние объекты. В отличие от экспортных объектов, внешние объекты документируют символы, которые вы планируете предоставить, их типы и предоставляют место для добавления информации об использовании. Кроме того, если ваши потребители также используют Closure Compiler, им потребуются внешние объекты для компиляции.