Расширенная компиляция

Обзор

Использование Closure Compiler с уровнем compilation_level ADVANCED_OPTIMIZATIONS обеспечивает более высокую степень сжатия, чем компиляция с SIMPLE_OPTIMIZATIONS или WHITESPACE_ONLY . Компиляция с ADVANCED_OPTIMIZATIONS обеспечивает дополнительное сжатие за счёт более агрессивного преобразования кода и переименования символов. Однако этот более агрессивный подход требует большей осторожности при использовании ADVANCED_OPTIMIZATIONS , чтобы гарантировать, что выходной код работает так же, как и входной.

В этом руководстве показано, как работает уровень компиляции ADVANCED_OPTIMIZATIONS и что можно сделать, чтобы гарантировать работоспособность кода после компиляции с ADVANCED_OPTIMIZATIONS . Также вводится понятие extern — символа, определённого в коде, внешнем по отношению к коду, обрабатываемому компилятором.

Перед прочтением этого руководства вы должны быть знакомы с процессом компиляции JavaScript с помощью одного из инструментов Closure Compiler, например, приложения-компилятора на основе Java.

Примечание по терминологии: флаг командной строки --compilation_level поддерживает более распространённые сокращения ADVANCED и SIMPLE , а также более точные ADVANCED_OPTIMIZATIONS и SIMPLE_OPTIMIZATIONS . В этом документе используется более длинная форма, но в командной строке эти названия могут использоваться взаимозаменяемо.

  1. Еще лучшее сжатие
  2. Как включить ADVANCED_OPTIMIZATIONS
  3. На что следует обратить внимание при использовании ADVANCED_OPTIMIZATIONS
    1. Удаление кода, который вы хотите сохранить
    2. Непоследовательные имена свойств
    3. Компиляция двух частей кода по отдельности
    4. Неработающие ссылки между скомпилированным и некомпилированным кодом

Еще лучшее сжатие

При уровне компиляции по умолчанию SIMPLE_OPTIMIZATIONS компилятор Closure Compiler уменьшает размер JavaScript, переименовывая локальные переменные. Однако существуют и другие символы, помимо локальных переменных, которые можно сократить, и существуют другие способы сокращения кода, помимо переименования символов. Компиляция с ADVANCED_OPTIMIZATIONS использует весь спектр возможностей сокращения кода.

Сравните результаты для SIMPLE_OPTIMIZATIONS и ADVANCED_OPTIMIZATIONS для следующего кода:

function unusedFunction(note) {
  alert(note['text']);
}

function displayNoteTitle(note) {
  alert(note['title']);
}

var flowerNote = {};
flowerNote['title'] = "Flowers";
displayNoteTitle(flowerNote);

Компиляция с SIMPLE_OPTIMIZATIONS сокращает код до следующего:

function unusedFunction(a){alert(a.text)}function displayNoteTitle(a){alert(a.title)}var flowerNote={};flowerNote.title="Flowers";displayNoteTitle(flowerNote);

Компиляция с ADVANCED_OPTIMIZATIONS полностью сокращает код до следующего:

alert("Flowers");

Оба сценария создают предупреждение с текстом "Flowers" , но второй сценарий гораздо меньше.

Уровень ADVANCED_OPTIMIZATIONS выходит за рамки простого сокращения имен переменных несколькими способами, включая:

  • более агрессивное переименование:

    Компиляция с SIMPLE_OPTIMIZATIONS переименовывает только параметры note функций displayNoteTitle() и unusedFunction() , поскольку это единственные переменные в скрипте, которые являются локальными для функции. ADVANCED_OPTIMIZATIONS также переименовывает глобальную переменную flowerNote .

  • удаление мертвого кода:

    Компиляция с ADVANCED_OPTIMIZATIONS полностью удаляет функцию unusedFunction() , поскольку она никогда не вызывается в коде.

  • встраивание функции:

    Компиляция с ADVANCED_OPTIMIZATIONS заменяет вызов displayNoteTitle() единственным alert() , составляющим тело функции. Такая замена вызова функции телом функции называется «встраиванием». Если бы функция была длиннее или сложнее, её встраивание могло бы изменить поведение кода, но Closure Compiler определяет, что в данном случае встраивание безопасно, и экономит место. Компиляция с ADVANCED_OPTIMIZATIONS также встраивает константы и некоторые переменные, если считает это возможным.

Этот список — всего лишь пример преобразований по уменьшению размера, которые может выполнить компиляция ADVANCED_OPTIMIZATIONS .

Как включить ADVANCED_OPTIMIZATIONS

Чтобы включить ADVANCED_OPTIMIZATIONS для приложения Closure Compiler, включите флаг командной строки --compilation_level ADVANCED_OPTIMIZATIONS , как в следующей команде:

java -jar compiler.jar --compilation_level ADVANCED_OPTIMIZATIONS --js hello.js

На что следует обратить внимание при использовании ADVANCED_OPTIMIZATIONS

Ниже перечислены некоторые распространенные непреднамеренные эффекты ADVANCED_OPTIMIZATIONS и шаги, которые можно предпринять, чтобы их избежать.

Удаление кода, который вы хотите сохранить

Если скомпилировать только функцию ниже с ADVANCED_OPTIMIZATIONS , Closure Compiler выдаст пустой вывод:

function displayNoteTitle(note) {
  alert(note['myTitle']);
}

Поскольку функция никогда не вызывается в JavaScript, который вы передаете компилятору, Closure Compiler предполагает, что этот код не нужен!

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

Однако если вы обнаружили, что Closure Compiler удаляет функции, которые вы хотите сохранить, есть два способа предотвратить это:

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

В следующих разделах каждый вариант обсуждается более подробно.

Решение: переместите вызовы функций в код, обрабатываемый компилятором замыканий.

Вы можете столкнуться с нежелательным удалением кода, если компилируете только часть кода с помощью Closure Compiler. Например, у вас может быть файл библиотеки, содержащий только определения функций, и HTML-файл, включающий библиотеку и содержащий код, вызывающий эти функции. В этом случае, если вы компилируете файл библиотеки с ADVANCED_OPTIMIZATIONS , Closure Compiler удалит все функции библиотеки.

Простейшее решение этой проблемы — скомпилировать функции вместе с частью программы, которая их вызывает. Например, Closure Compiler не удалит displayNoteTitle() при компиляции следующей программы:

function displayNoteTitle(note) {
  alert(note['myTitle']);
}
displayNoteTitle({'myTitle': 'Flowers'});

Функция displayNoteTitle() в этом случае не удаляется, поскольку Closure Compiler видит, что она вызывается.

Другими словами, вы можете предотвратить удаление нежелательного кода, включив точку входа вашей программы в код, передаваемый Closure Compiler. Точка входа программы — это место в коде, где программа начинает выполняться. Например, в программе Flower Note из предыдущего раздела последние три строки выполняются сразу после загрузки JavaScript в браузер. Это точка входа для данной программы. Чтобы определить, какой код нужно сохранить, Closure Compiler начинает с этой точки входа и отслеживает поток управления программой.

Решение: включите внешние элементы для функций, которые вы хотите предоставить

Более подробная информация об этом решении приведена ниже и на странице, посвященной внешним объектам и экспорту .

Непоследовательные имена свойств

Компиляция Closure Compiler никогда не изменяет строковые литералы в вашем коде, независимо от используемого уровня компиляции. Это означает, что компиляция с ADVANCED_OPTIMIZATIONS обрабатывает свойства по-разному в зависимости от того, обращается ли ваш код к ним через строку. Если вы смешиваете строковые ссылки на свойство со ссылками с точечным синтаксисом, Closure Compiler переименует некоторые ссылки на это свойство, но не все. В результате ваш код, вероятно, будет работать некорректно.

Например, возьмем следующий код:

function displayNoteTitle(note) {
  alert(note['myTitle']);
}
var flowerNote = {};
flowerNote.myTitle = 'Flowers';

alert(flowerNote.myTitle);
displayNoteTitle(flowerNote);

Последние два оператора в этом исходном коде делают одно и то же. Однако при сжатии кода с помощью ADVANCED_OPTIMIZATIONS получается следующее:

var a={};a.a="Flowers";alert(a.a);alert(a.myTitle);

Последний оператор в сжатом коде приводит к ошибке. Прямая ссылка на свойство myTitle была переименована a , но кавычки с ссылкой на myTitle в функции displayNoteTitle не были переименованы. В результате последний оператор ссылается на свойство myTitle , которого больше нет.

Решение: будьте последовательны в названиях своей недвижимости

Это решение довольно простое. Для любого типа или объекта используйте исключительно точечный синтаксис или строки в кавычках. Не смешивайте синтаксисы, особенно применительно к одному и тому же свойству.

Кроме того, по возможности предпочитайте использовать точечный синтаксис, так как он обеспечивает более эффективную проверку и оптимизацию. Используйте доступ к свойствам строк в кавычках только тогда, когда не хотите, чтобы Closure Compiler выполнял переименование, например, когда имя получено из внешнего источника, например, декодированного JSON.

Компиляция двух частей кода по отдельности

Если вы разделяете приложение на отдельные фрагменты кода, возможно, стоит скомпилировать их по отдельности. Однако, если два фрагмента кода взаимодействуют, это может вызвать трудности. Даже если это удастся, результаты двух запусков Closure Compiler будут несовместимы.

Например, предположим, что приложение разделено на две части: часть, которая извлекает данные, и часть, которая отображает данные.

Вот код для извлечения данных:

function getData() {
  // In an actual project, this data would be retrieved from the server.
  return {title: 'Flower Care', text: 'Flowers need water.'};
}

Вот код для отображения данных:

var displayElement = document.getElementById('display');
function displayData(parent, data) {
  var textElement = document.createTextNode(data.text);
  parent.appendChild(textElement);
}
displayData(displayElement, getData());

Если вы попытаетесь скомпилировать эти два фрагмента кода по отдельности, вы столкнётесь с несколькими проблемами. Во-первых, Closure Compiler удаляет функцию getData() по причинам, описанным в разделе «Удаление кода, который необходимо сохранить» . Во-вторых, Closure Compiler выдаёт фатальную ошибку при обработке кода, отображающего данные.

input:6: ERROR - variable getData is undefined
displayData(displayElement, getData());

Поскольку компилятор не имеет доступа к функции getData() при компиляции кода, отображающего данные, он рассматривает getData как неопределенное.

Решение: скомпилируйте весь код страницы вместе

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

Примечание: этот подход не сработает, если вам нужно смешивать скомпилированный и некомпилированный код. Советы по решению этой проблемы см. в разделе «Нерабочие ссылки между скомпилированным и некомпилированным кодом» .

Неработающие ссылки между скомпилированным и некомпилированным кодом

Переименование символов в ADVANCED_OPTIMIZATIONS нарушит взаимодействие между кодом, обрабатываемым Closure Compiler, и любым другим кодом. Компиляция переименовывает функции, определённые в исходном коде. Любой внешний код, вызывающий ваши функции, будет переименован после компиляции, поскольку он всё ещё ссылается на старое имя функции. Аналогично, ссылки в скомпилированном коде на внешне определённые символы могут быть изменены Closure Compiler.

Имейте в виду, что «нескомпилированный код» включает в себя любой код, переданный функции eval() в виде строки. Closure Compiler никогда не изменяет строковые литералы в коде, поэтому Closure Compiler не изменяет строки, переданные операторам eval() .

Имейте в виду, что это связанные, но разные проблемы: поддержание связи между компилируемым и внешним кодом и поддержание связи между внешним и компилируемым кодом. Эти отдельные проблемы имеют общее решение, но у каждой из них есть свои нюансы. Чтобы максимально эффективно использовать Closure Compiler, важно понимать, какой случай у вас.

Прежде чем продолжить, вам, возможно, захочется ознакомиться с понятиями «внешние» и «экспорт» .

Решение для вызова внешнего кода из скомпилированного кода: компиляция с помощью Externs

Если вы используете код, добавленный на страницу другим скриптом, убедитесь, что Closure Compiler не переименовывает ссылки на символы, определённые во внешней библиотеке. Для этого включите в компиляцию файл с внешними переменными для внешней библиотеки. Это сообщит Closure Compiler, какие имена вы не контролируете и, следовательно, не можете изменить. Ваш код должен использовать те же имена, что и во внешнем файле.

Распространенными примерами таких API являются API OpenSocial и API Google Maps . Например, если ваш код вызывает функцию OpenSocial opensocial.newDataRequest() без соответствующих внешних переменных, Closure Compiler преобразует этот вызов в ab() .

Решение для вызова скомпилированного кода из внешнего кода: реализация внешних объектов

Если у вас есть код JavaScript, который вы повторно используете в качестве библиотеки, вы можете использовать Closure Compiler, чтобы сжать только библиотеку, но при этом разрешить нескомпилированному коду вызывать функции в библиотеке.

Решение в этой ситуации — реализовать набор внешних объектов, определяющих открытый API вашей библиотеки. Ваш код будет предоставлять определения для символов, объявленных в этих внешних объектах. Это касается любых классов или функций, упомянутых в ваших внешних объектах. Это также может означать, что ваши классы реализуют интерфейсы, объявленные во внешних объектах.

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

Для этого убедитесь, что при компиляции кода вы также включаете внешние переменные. Это может показаться необычным, поскольку мы часто думаем о внешних переменных как о «пришедших откуда-то ещё», но необходимо сообщить Closure Compiler, какие символы вы раскрываете, чтобы они не переименовывались.

Важное предостережение заключается в том, что вы можете получить диагностику «дублирующегося определения» в коде, определяющем внешние символы. Closure Compiler предполагает, что любой символ во внешних символах предоставляется внешней библиотекой, и в настоящее время не может определить, что вы намеренно предоставляете определение. Эти диагностики можно безопасно подавить , и их подавление можно рассматривать как подтверждение того, что вы действительно выполняете требования своего API.

Кроме того, Closure Compiler может проверять типы ваших определений на соответствие типам объявлений extern. Это служит дополнительным подтверждением корректности ваших определений.