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

Обзор

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

В этом руководстве показано, что делает уровень компиляции ADVANCED_OPTIMIZATIONS и что вы можете сделать, чтобы убедиться, что ваш код работает после компиляции с помощью ADVANCED_OPTIMIZATIONS . Он также вводит понятие extern : символ, определенный в коде, внешнем по отношению к коду, обрабатываемому компилятором.

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

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

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

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

С уровнем компиляции по умолчанию SIMPLE_OPTIMIZATIONS компилятор Closure уменьшает размер 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

Пользовательский интерфейс службы Closure Compiler, API службы и приложение имеют разные методы установки для параметраcompile_level ADVANCED_OPTIMIZATIONS compilation_level

Как включить ADVANCED_OPTIMIZATIONS в пользовательском интерфейсе службы Closure Compiler

Чтобы включить ADVANCED_OPTIMIZATIONS для пользовательского интерфейса службы Closure Compiler, щелкните переключатель «Дополнительно».

Как включить ADVANCED_OPTIMIZATIONS в API сервиса Closure Compiler

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

#!/usr/bin/python2.4

import httplib, urllib, sys

params = urllib.urlencode([
    ('code_url', sys.argv[1]),
    ('compilation_level', 'ADVANCED_OPTIMIZATIONS'),
    ('output_format', 'text'),
    ('output_info', 'compiled_code'),
  ])

headers = { "Content-type": "application/x-www-form-urlencoded" }
conn = httplib.HTTPSConnection('closure-compiler.appspot.com')
conn.request('POST', '/compile', params, headers)
response = conn.getresponse()
data = response.read()
print data
conn.close()

Как включить ADVANCED_OPTIMIZATIONS в приложении Closure Compiler

Чтобы включить 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 выдаст пустой вывод:

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

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

Во многих случаях такое поведение именно то, что вам нужно. Например, если вы компилируете свой код вместе с большой библиотекой, 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. Точка входа программы — это место в коде, где программа начинает выполняться. Например, в программе для цветочных заметок из предыдущего раздела последние три строки выполняются сразу после загрузки JavaScript в браузере. Это точка входа для этой программы. Чтобы определить, какой код вам нужно сохранить, Closure Compiler запускается с этой точки входа и отслеживает поток управления программы оттуда.

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

Более подробная информация об этом решении приведена ниже и на странице externs and exports .

Несогласованные имена свойств

Компиляция 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 выполнял переименование, например, когда имя исходит из внешнего источника, такого как декодированный 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 удаляет функцию getData() по причинам, описанным в разделе Удаление кода, который вы хотите сохранить . Во-вторых, Closure Compiler выдает фатальную ошибку при обработке кода, отображающего данные.

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

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

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

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

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

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

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

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

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

Прежде чем продолжить, вы можете ознакомиться с externs и exports .

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

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

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

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

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

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

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

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

Одно важное предостережение заключается в том, что вы можете получить диагностику "дубликата определения" кода, определяющего внешние символы. Компилятор Closure предполагает, что любой символ в externs предоставляется внешней библиотекой, и в настоящее время не может понять, что вы намеренно предоставляете определение. Эти диагностики безопасно подавлять , и вы можете рассматривать подавление как подтверждение того, что вы действительно выполняете свой API.

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