Javascript.RU

Директива @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 должны соблюдаться следующие правила:

  1. Переменная должна быть глобальной и определена через var
  2. Тип переменной должен быть простейшим литеральным (строка, число, булево значение)

Кстати, имя переменной может быть любым, не обязательно БОЛЬШИМИ_БУКВАМИ.

Итак, в скрипте задана переопределяемая глобальная переменная . Следующий шаг - указание компилятору, на что ее заменять.

В документации описан флаг --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();
    }

}

 
Текущий раздел
Поиск по сайту
Содержание

Учебник javascript

Основные элементы языка

Сундучок с инструментами

Интерфейсы

Все об AJAX

Оптимизация

Разное

Дерево всех статей

Последние темы на форуме
Forum