GCC: интеграция с Google Closure Library

Google Closure Compiler содержит ряд специальных возможностей для интеграции с Google Closure Library.

Здесь важны две вещи.

  1. Для их использования возможно использовать минимум от Google Closure Library. Например, взять одну или несколько функций из библиотеки.
  2. GCC – расширяемый компилятор, можно добавить к нему свои «фазы оптимизации» для интеграции с другими инструментами и фреймворками.

Интеграция с Google Closure Library подключается флагом –process_closure_primitives, который по умолчанию установлен в true. То есть, она включена по умолчанию.

Этот флаг запускает специальный проход компилятора, описанный классом ProcessClosurePrimitives и подключает дополнительную проверку типов ClosureReverseAbstractInterpreter.

Мы рассмотрим все действия, которые при этом происходят, а также некоторые опции, которые безопасным образом используют символы Google Closure Library без объявления флага.

Преобразование основных символов

Следующие действия описаны в классе ProcessClosurePrimitives.

Замена константы COMPILED

В Google Closure Library есть переменная:

/**
 * @define {boolean} ...
 */
var COMPILED = false;

Проход ProcessClosurePrimitives переопределяет ее в true и использует это при оптимизациях, удаляя ветки кода, не предназначены для запуска на production.

Такие функции существуют, например, в ядре Google Closure Library. К ним в первую очередь относятся вызовы, предназначенные для сборки и проверки зависимостей. Они содержат код, обрамленный проверкой COMPILED, например:

goog.require = function(rule) {
  // ...
  if (!COMPILED) {
    // основное тело функции
  }
}

Аналогично может поступить и любой скрипт, даже без использования Google Closure Library:

/** @define {boolean} */
var COMPILED = false

Framework = {}

Framework.sayCompiled = function() {
  if (!COMPILED) {
    alert("Not compressed")
  } else {
    alert("Compressed")
  }
}

Для того, чтобы сработало, нужно сжать в продвинутом режиме:

Framework = {};
Framework.sayCompiled = Framework.a = function() {
  alert( "Compressed" );
};

Компилятор переопределил COMPILED в true и произвел соответствующие оптимизации.

Автоподстановка локали

В Google Closure Compiler есть внутренняя опция locale

Эта опция переопределяет переменную goog.LOCALE на установленную при компиляции.

Для использования опции locale, на момент написания статьи, ее нужно задать в Java коде компилятора, т.к. соответствующего флага нет.

Как и COMPILED, константу goog.LOCALE можно использовать в своем коде без библиотеки Google Closure Library.

Проверка зависимостей

Директивы goog.provide, goog.require, goog.addDependency обрабатываются особым образом.

Все зависимости проверяются, а сами директивы проверки – удаляются из сжатого файла.

Экспорт символов

Вызов goog.exportSymbol задаёт экспорт символа.

Если подробнее, то код goog.exportSymbol(„a“,myVar) эквивалентен window['a'] = myVar.

Автозамена классов CSS

Google Closure Library умеет преобразовывать классы CSS на более короткие по списку, который задаётся при помощи goog.setCssNameMapping.

Например, следующая функция задает такой список.

goog.setCssNameMapping({
   "goog-menu": "a",
   "goog-menu-disabled": "a-b",
   "CSS_LOGO": "b",
   "hidden": "c"
 });

Тогда следующий вызов преобразуется в «a a-b»:

goog.getCssName('goog-menu') + ' ' + goog.getCssName('goog-menu', 'disabled')

Google Closure Compiler производит соответствующие преобразования в сжатом файле и удаляет вызов setCssNameMapping из кода.

Чтобы это сжатие работало, в HTML/CSS классы тоже должны сжиматься. По всей видимости, в приложениях Google это и происходит, но соответствующие инструменты закрыты от публики.

Генерация списка экстернов

При объявлении внутренней опции externExportsPath, содержащей путь к файлу, в этот файл будут записаны все экспорты, описанные через goog.exportSymbol/goog.exportProperty.

В дальнейшем этот файл может быть использован как список экстернов для компиляции.

Эта опция может быть полезна для создания внешних библиотек, распространяемых со списком экстернов.

Для её использования нужна своя обёртка вокруг компилятора на Java. Соответствующий проход компилятора описан в классе ExternExportsPass.

Проверка типов

В Google Closure Library есть ряд функций для проверки типов. Например: goog.isArray, goog.isString, goog.isNumber, goog.isDef и т.п.

Компилятор использует их для проверки типов, более подробно см. GCC: статическая проверка типов

Эта логика описана в классе ClosureReverseAbstractInterpreter. Названия функций, определяющих типы, жестко прописаны в Java-коде, поменять их на свои без модификации исходников нельзя.

Автогенерация экспортов из аннотаций

Для этого в Google Closure Compiler есть внутренняя опция generateExports.

Эта недокументированная опция добавляет проход компилятора, описанный классом GenerateExports.

Он читает аннотации @export и создает из них экспортирующие вызовы goog.exportSymbol/exportProperty. Название экспортирующих функций находится в классе соглашений кодирования, каким по умолчанию является GoogleCodingConvention.

Например:

/** @export */
function Widget() {}
  /** @export */
Widget.prototype.hide = function() {
  this.elem.style.display = 'none'
}

После компиляции в продвинутом режиме:

function a() {}
goog.d("Widget", a);
a.prototype.a = function() {
  this.b.style.display = "none"
};
goog.c(a.prototype, "hide", a.prototype.a);

Свойства благополучно экспортированы. Удобно.

Резюме

Google Closure Compiler содержит дополнительные фичи, облегчающие интеграцию с Google Closure Library. Некоторые из них весьма полезны, но требуют создания своего Java-файла, который ставит внутренние опции.

При обработке символов компилятор не смотрит, подключена ли библиотека, он находит обрабатывает их просто по именам. Поэтому вы можете использовать свою реализацию соответствующих функций.

Google Closure Compiler можно легко расширить, добавив свои опции и проходы оптимизатора, для интеграции с вашими инструментами.

Карта учебника

Комментарии

перед тем как писать…
  • Приветствуются комментарии, содержащие дополнения и вопросы по статье, и ответы на них.
  • Для одной строки кода используйте тег <code>, для нескольких строк кода — тег <pre>, если больше 10 строк — ссылку на песочницу (plnkr, JSBin, codepen…)
  • Если что-то непонятно в статье — пишите, что именно и с какого места.