Директива @define, удаление веток компилятором
Директива @define позволяет переопределить глобальную переменную (как правило, константу) в процессе компиляции. Компилятор заменит значение javascript-переменной на новое непосредственно в коде javascript.
А так как это константа, то сжатие продвинутым режимом позволяет тут же заинлайнить ее и оптимизировать соответствующие ветки if .
Например:
/** @define {number} */
var DEBUG_LEVEL = 5;
document.onclick = function() {
if (DEBUG_LEVEL > 3) console.log("clicked")
alert("click")
}
Станет при переопределении DEBUG_LEVEL=2 и сжатии обычным режимом:
var DEBUG_LEVEL = 2; // (замена!)
document.onclick = function() {
if (DEBUG_LEVEL > 3) console.log("clicked")
alert("click")
}
А при сжатии продвинутым режимом:
document.onclick = function() {
alert("click")
}
Как видно, переопределенная константа инлайнится, и лишние ветки кода удаляются. Это, разумеется, положительно влияет и на размер скрипта и на производительность.
Чтобы включить возможность переопределения, необходимо сделать аннотацию переменной вида:
/** @define {boolean} */
var myvar = 5
Для успешного определения @define должны соблюдаться следующие правила:
- Переменная должна быть глобальной и определена через var
- Тип переменной должен быть простейшим литеральным (строка, число, булево значение)
Кстати, имя переменной может быть любым, не обязательно БОЛЬШИМИ_БУКВАМИ.
Итак, в скрипте задана переопределяемая глобальная переменная . Следующий шаг - указание компилятору, на что ее заменять.
В документации описан флаг --define . Но на момент написания статьи он не работает, и вообще - его нет в исходных кодах.
Поэтому мы добавим свой флаг, как описано в статье про собственные флаги.
Флаг будет целочисленный.
Добавим его к описаниям флагов в (My)CompilerRunner :
@FlagSpec(help = "Debug level.")
public static final Flag<Integer> FLAG_debug_level = Flag.value(5);
Как видите, значение по умолчанию - 5. Это будет максимальный уровень отладки. Целочисленное значение выбрано из соображений удобства, можно было взять классический DEBUG/INFO/WARNING/...
Флаг добавлен. Теперь нужно из флага сделать установку компилятора на замену переменной.
Для этого дописываем соответствующую опцию в конец метода CompilerRunner#createOptions .
Добавление define производится одним из методов вида setDefineTo* , которые находятся в классе CompilerOptions .
Для целочисленного флага это будет setDefineToNumberLiteral :
options.setDefineToNumberLiteral("DEBUG_LEVEL", FLAG_debug_level.get());
Теперь после сборки ant'ом компилятор умеет распознавать новый флаг.
Код:
/** @define {number} */
var DEBUG_LEVEL = 5;
document.onclick = function() {
if (DEBUG_LEVEL > 3) console.log("clicked")
alert("click")
}
Запуск компилятора:
java -jar compiler.jar --js debug.js --debug_level 3
Даст нам следующий код:
var DEBUG_LEVEL = 3;
document.onclick = function() {
DEBUG_LEVEL > 3 && console.log("clicked");
alert("click")
};
Как видите, переопределение произошло успешно. Однако, лишняя ветка кода не была убрана.
Это произошло из-за того, что в обычном режиме компилятор не имеет права заинлайнить глобальную переменную DEBUG_LEVEL .
Для этого его необходимо запустить в продвинутом режиме:
java -jar compiler.jar --js debug.js --compilation_level ADVANCED_OPTIMIZATIONS --debug_level 3
Результат компиляции:
document.onclick = function() {
alert("click")
}
Таким образом, продвинутый режим дает возможность в полной мере воспользоваться преимуществами переопределения переменных.
Эта директива может применяться не только для отладки, но и для других настроек, которые точно известны во время компиляции.
Например, в библиотеке Google Closure Library, в файле dom/dom.js есть настройки:
/**
* @define {boolean} Whether we know at compile time that the browser is in
* quirks mode.
*/
goog.dom.ASSUME_QUIRKS_MODE = false;
/**
* @define {boolean} Whether we know at compile time that the browser is in
* standards compliance mode.
*/
goog.dom.ASSUME_STANDARDS_MODE = false;
То есть, если мы знаем, что наш сайт работает в STANDARDS MODE (есть правильный DOCTYPE ), то мы можем указать это компилятору, и он уберет из библиотеке лишние ветки кода, которые относятся к поддержке QUIRKS MODE .
Мы рассмотрели переопределение переменных при помощи аннотации @define и соответствующую недокументированую опцию компилятора.
Самое полезное применение, на мой взгляд - это существующая переменная COMPILED , значение которой при компиляции становится true и свой собственный LOG_LEVEL , который позволит автоматически вырезать ненужное логирование из production-кода.
Уже только это делает директиву @define весьма и весьма юзабельной.
Кстати, вот класс MyCompilerRunner с флагом и опцией:
package com.google.javascript.jscomp;
import com.google.common.flags.Flag;
import com.google.common.flags.FlagSpec;
import java.io.PrintStream;
public class MyCompilerRunner extends CompilerRunner {
@FlagSpec(help = "Debug level.")
public static final Flag<Integer> FLAG_debug_level = Flag.value(5);
/* всевозможные конструкторы */
public MyCompilerRunner(String[] args) {
super(args);
}
public MyCompilerRunner(String[] args, PrintStream out, PrintStream err) {
super(args, out, err);
}
/* изменить опции, в соответствие с новым флагом */
@Override
protected CompilerOptions createOptions() {
CompilerOptions options = super.createOptions();
options.setDefineToNumberLiteral("DEBUG_LEVEL", FLAG_debug_level.get());
return options;
}
/* точка входа в компилятор */
public static void main(String[] args) {
(new MyCompilerRunner(args)).run();
}
}
|