Javascript.RU

Использование внутренних опций и собственные флаги

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

Warning: статья частично устарела.

Следующая диаграмма иллюстрирует механизм инициализации и запуска компилятора.

Точкой входа является CompilerRunner (наследует AbstractCompilerRunner).

Именно в этом классе (преимущественно, в коде родителя) происходит создание основных объектов, чтение флагов, указанных в командной строке и задание опций.

Класс Compiler осуществляет процесс компиляции и сжатия javascript. По умолчанию он использует GoogleCodingConvention - вспомогательный класс для чтения информации о коде из имен.

Для того, чтобы произвести компиляцию и оптимизацию, Google Closure Compiler получает информацию из трех основных источников:

  1. Собственно код javascript, на основе которого строится граф взаимодействий и синтаксическое дерево разбора.
  2. Аннотации к коду, например /** @const */. Парсер читает их и добавляет информацию в синтаксическое дерево к аннотированному объекту в виде специальной структуры JSDoc.
  3. Соглашения о коде. Например, константы пишутся большими буквами, экспортируемые (доступные извне) свойства начинаются с подчеркивания "_", приватные свойства заканчиваются подчеркиванием и
    т.п. (это в Google Coding Convention).

Разные части компилятора по-своему используют эту информацию. Важно то, что она сопровождает синтаксическое дерево кода и поэтому всегда доступна.

При создании компилятора AbstractCompilerRunner ставит ему ставится уровень логирования по флагу --logging_level. Для логирования используется стандартный java logger. При указании уровня
FINEST, будут выведены все стадии компиляции.

Однако, этот уровень логирования относится только к компилятору. Информация, которую выводят проходы компилятора, например какая переменная была на какую заменена, какая функция заинлайнена - не будет выводится.

Чтобы установить глобальный уровень логирования, в код можно добавить соответствующую инициализацию LogManager.

Новые флаги можно добавлять в CompilerRunner аналогично уже описанным (см. FLAG_...).

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

В классе-наследнике можно изменять опции компилятора, устанавливать недокументированные опции, а также указать собственный класс со стандартами кодирования вместо GoogleCodingConvention.

В объекте опций компилятора CompilerOptions содержатся флаги (булевы переменные) - какие оптимизации делать, а какие нет.

Эти флаги ставятся непосредственно после создания объекта опций объектом класса CompilationLevel. Его метод setOptionsForCompilationLevel назначает набор опций, исходя из значения --compilation_level.

Вы можете поменять набор или адаптировать его к вашим требованиям.

Например, можно добавить свой проход комплятора для определенного значения опции.

Впрочем, для более удобной поддержки лучше будет модифицировать опции в наследнике CompilerRunner.

Добавим флаг debug_level, который будет устанавливать переопределяемую javascript-переменную DEBUG_LEVEL при компиляции в нужное значение.

Для этого нужно совершить следующие шаги.

Взять код компилятора можно из SVN:

svn co http://closure-compiler.googlecode.com/svn/trunk gc

Создадим рядом с CompilerRunner свой класс-наследник 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();

        /* переопределение javascript-переменной в значение флага */
        options.setDefineToNumberLiteral("DEBUG_LEVEL", FLAG_debug_level.get());

        return options;
    }

    /* точка входа в компилятор */
    public static void main(String[] args) {
        (new MyCompilerRunner(args)).run();
    }

}

Положить этот файл необходимо в ту же директорию, где лежит CompilerRunner.java, то есть в src/com/google/javascript/jscomp.

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

Для сборки необходим установленный JDK 1.6 и ANT. И то и другое достаточно просто в установке.

На верхнем уровне исходных кодов компилятора, рядом с директорией src есть файл build.xml. Это файл сборки ANT.

Для того, чтобы точкой входа служил класс MyCompilerRunner, достаточно заменить в ant-файле build.xml слово CompilerRunner на MyCompilerRunner, и затем запустить ant в директории с build.xml.

После этого в директории build появится новый jar-файл компилятора, с новыми флагами и поциями.

Компиляция в IDE

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

Оказалось, в build.xml прописан процесс перегенерации файла флагов flags.xml. Поэтому при изменении флагов лучше пересобрать компилятор через ant, используя ant plugin или командную строку.

Процессор флагов описан в jar-файле, который доступен в SVN вместе с кодом компилятора. Однако, исходника для этого файла нет.

К счастью, примеры всевозможных флагов есть в самом компиляторе. Можно даже объявить флаг, допускающий многократное указание опций. Такой флаг используется, например, в статье Автоудаление отладочных свойств и объектов.

Расширение компилятора производится аналогично. Добавляется класс MyCompiler, наследующий от Compiler.

Например, включим в унаследованном компиляторе логирование на всю катушку без использования флага java.util.logging.config.file.

Для этого добавим чтение конфига в начало конструктора MyCompiler:

package com.google.javascript.jscomp;

import java.io.*;
import java.util.logging.LogManager;

public class MyCompiler extends Compiler {

    // есть и другие конструкторы, но при запуске в консоли используется этот
    public MyCompiler(PrintStream stream) {
        super(stream);
                      
        try {
            File f = new File("/tmp/logging.properties");
            InputStream in = new FileInputStream(f);
            BufferedInputStream bin = new BufferedInputStream(in);
            LogManager.getLogManager().readConfiguration(bin);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }  
    }
}

Файл logging.properties может быть, например, таким:

handlers= java.util.logging.ConsoleHandler
.level= ALL

java.util.logging.ConsoleHandler.level = ALL
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter

some.class.level = SEVERE

Чтобы задействовать новый класс компилятора, перекроем в MyCompilerRunner соответствующий фабричный метод:

/* фабричный метод для компилятора */
    @Override
    protected Compiler createCompiler() {
        return new MyCompiler(getErrorPrintStream());
    }

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

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

Два рассмотренных класса: Compiler и CompilerRunner проще всего унаследовать и расширить, не нарушая общей структуры и без патчей компилятора.

Как правило, расширения используются для добавления дополнительных проходов и установки своих и недокументированных опций.

Примеры практического применения своих флагов и опций вы можете увидеть в статьях:

Многие опции пока недокументированы и даже недоступны в стандартной сборке. Почему - остается только гадать: работают они вполне эффективно. Видимо, это побочный эффект организации процесса разработки и того, что над Google Closure Compiler идет активная работа.

Следующий класс добавляет ряд опций в виде флагов компилятора.

package com.google.javascript.jscomp;

import com.google.common.flags.Flag;
import com.google.common.flags.FlagSpec;

import java.io.PrintStream;
import java.util.HashSet;
import java.util.List;

public class MyCompilerRunner extends CompilerRunner {

    @FlagSpec(help = "Specify stripTypes. You can include multiple.")
    public static final Flag<List<String>> FLAG_strip_types
            = Flag.stringCollector();

    @FlagSpec(help = "Specify stripTypePrefixes. You can include multiple.")
    public static final Flag<List<String>> FLAG_strip_type_prefixes = Flag.stringCollector();

    @FlagSpec(help = "Specify stripTypeSuffixes. You can include multiple.")
    public static final Flag<List<String>> FLAG_strip_name_suffixes
            = Flag.stringCollector();

    @FlagSpec(help = "Specify stripNamePrefixes. You can include multiple.")
    public static final Flag<List<String>> FLAG_strip_name_prefixes
            = Flag.stringCollector();

    @FlagSpec(help = "@export support.")
    public static final Flag<Boolean> FLAG_export_annotation
            = Flag.value(true);

    @FlagSpec(help = "Enables property renaming: HEURISTIC/AGGRESSIVE_HEURISTIC")
    public static final Flag<PropertyRenamingPolicy> FLAG_property_renaming_policy
            = Flag.value(PropertyRenamingPolicy.OFF);


    /* всевозможные конструкторы */
    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.stripTypes = new HashSet<String>(FLAG_strip_types.get());
        options.stripTypePrefixes = new HashSet<String>(FLAG_strip_type_prefixes.get());
        options.stripNameSuffixes = new HashSet<String>(FLAG_strip_name_suffixes.get());
        options.stripNamePrefixes = new HashSet<String>(FLAG_strip_name_prefixes.get());
        options.generateExports = FLAG_export_annotation.get();
        options.propertyRenaming = FLAG_property_renaming_policy.get();

        return options;
    }

    /* точка входа в компилятор */
    public static void main(String[] args) {
        (new MyCompilerRunner(args)).run();
    }

}

К этому классу, при необходимости, можно добавить свои флаги и опции: externExportsPath, goog.LOCALE, переопределения @define и т.п.


Автор: Гость (не зарегистрирован), дата: 6 мая, 2010 - 10:01
#permalink

Илья, пожалейте простых людей. Выложите готовый jar.


Автор: Гость (не зарегистрирован), дата: 7 мая, 2010 - 10:49
#permalink

В последних версиях класса CompilerRunner уже нет, статья теряет актуальность. Увы, соответственно, как и другие статьи про эвристическое переименование, ...


Автор: dmitryx (не зарегистрирован), дата: 17 февраля, 2011 - 16:19
#permalink

он есть, просто переименован.
(ver 119, 2/27/10):
Rename CompilerRunner -> CommandLineRunner.
I'll also do this for JSCompilerRunner, but it will be a much
bigger change. (Nick)


Автор: VAL (не зарегистрирован), дата: 24 февраля, 2011 - 14:43
#permalink

да, но не работает
import com.google.common.flags.Flag;


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

Учебник javascript

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

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

Интерфейсы

Все об AJAX

Оптимизация

Разное

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

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