14 October 2011

Компиляция JavaScript проекта с помощью Maven и Closure Compiler

JavaScript
Sandbox
Добрый день, коллеги!

Хотел поделиться своими наработками в области автоматизации процесса сборки javascript проекта использующего Google Closure Compiler и Google Closure Library при помощи Apache Maven. Страничка проекта https://github.com/urmuzov/closure-maven, там же лежит документация по каждому из компонентов проекта.

О проекте


Главный компонент проекта — это архетип. Архетип объединяет в себя все остальные компоненты проекта, которые при желании могут использоваться отдельно от него.

Архетип предоставляет средства для решения самых часто встречающихся задач при разработке, а именно:
  • Запаковывает javascript код в maven артефакты для распространения (такие артефакты содержат помеченные пакеты специальной структуры, названные closure-packages);
  • Распаковывает closure-packages из подключенных в <dependencies> артефактов для компиляции;
  • Помогает в объединении и оптимизации js и css файлов при помощи Web Resource Optimizer for Java (wro4j);
  • Предоставляет 5 профилей компиляции для разных целей:
    • compiled — для «боевой» сборки проекта. Уровень компиляции ADVANCED_OPTIMIZATIONS;
    • merged — для склеивания всех исходных js файлов в один, фактически без компиляции. Уровень компиляции WHITESPACE_ONLY, форматирование PRETTY_PRINT;
    • sources — для дебага javascript файлов в бразуере. Компиляция происходит как и в профиле compiled, но в html будут подключены файлы с исходными кодами;
    • sources-no-compile — для дебага html/css файлов. Компиляции не происходит, просто в html подключаются файлы с исходными кодами;
    • jar — для сборки jar-архива для распространения;

  • Генерирует jsdoc и jslint отчеты при выполнении mvn site.


С профилями sources и sources-no-compile связано одно ограничение, они будут работать если в системе установлен python. Это ограничение связано с выполнением скрипта depswriter.py, формирующим файл deps.js, который необходим для работы Closure Library. Подробнее про depswriter здесь.

Cоздание проекта


Создать проект при помощи этого архетипа очень просто, достаточно выполнить команду (вам потребуется maven версии 3):
mvn -DarchetypeRepository=http://urmuzov.github.com/maven-repository/releases/ \
    -DarchetypeGroupId=com.github.urmuzov \
    -DarchetypeArtifactId=closure-package-maven-archetype \
    -DarchetypeVersion=1.0.2 \
    -DgroupId=my.test.group \
    -DartifactId=test-artifact \
    -Dversion=1.0.0-SNAPSHOT \
    -Dpackage=my.test.pkg \
    archetype:generate

Этой командой вы скажете maven сгенерировать проект исходя из архетипа com.github.urmuzov:closure-package-maven-archetype:1.0.2 находящегося в репозитории urmuzov.github.com/maven-repository/releases при этом группа, артефакт и версия вашего проекта будут соответственно my.test.group, test-artifact и 1.0.0-SNAPSHOT, основной пакет вашего проекта будет my.test.pkg.

Maven сгенерирует такую иерархию каталогов и файлов: (//так обозначены мои комментарии)
~$ cd test-artifact/
~/test-artifact$ tree
.
├── pom.xml
└── src
    └── main
        ├── python
        │   └── closure-library //скрипты на python необходимые для генерации deps.js
        │       ...
        ├── resources
        │   ├── jquery-1.4.4.min.js
        │   └── my
        │       └── test
        │           └── pkg //пакет, который вы указали при создании проекта
        │               └── javascript // зарезервированная директория, из нее компилятор будет брать js файлы для компиляции, также есть зарезервированная директория externs из которой компилятор будет брать экстерны
        │                   ├── desktop.entry.js // точка входа в приложение
        │                   ├── mobile.entry.js // точка входа в приложение
        │                   └── sample // пакет js кода
        │                       └── sample.js // файл с исходным кодом
        └── webapp
            ├── index.html // html файл для точки входа desktop.entry.js
            ├── META-INF
            ├── mobile.html // html файл для точки входа mobile.entry.js
            └── WEB-INF
                ├── web.xml
                └── wro.xml // конфигурация wro
14 directories, 19 files

Для того чтобы разобраться что здесь для чего, попробуем собрать проект.

Сборка проекта


Для того чтобы собрать проект необходимо выполнить следующую команду. Вместо compiled можно использовать любой из 5 профилей.
~/test-artifact$ mvn -P compiled clean install
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building test-artifact 1.0.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
...
[INFO] --- closure-package-maven-plugin:1.0.2:copy (default) @ test-artifact ---
[INFO]
[INFO] --- closure-compiler-maven-plugin:1.0.2:compile (compile-advanced) @ test-artifact ---
[INFO] simplePasses: [desktop, mobile]
[INFO] == SimplePass (/home/urmuzov/test-artifact/target/closure/javascript/desktop.entry.js -> /home/urmuzov/test-artifact/target/test-artifact/desktop.js) ==
[INFO] file size: desktop.js -> 1588 bytes
[INFO] == SimplePass (/home/urmuzov/test-artifact/target/closure/javascript/mobile.entry.js -> /home/urmuzov/test-artifact/target/test-artifact/mobile.js) ==
[INFO] file size: mobile.js -> 1587 bytes
...
[INFO] --- gmaven-plugin:1.0-rc-5:execute (property-setup) @ test-artifact ---
[INFO]  SimplePass[desktop]: For simple inclusion use ${desktop.entry.js} in your HTML file
[INFO]  SimplePass[mobile]: For simple inclusion use ${mobile.entry.js} in your HTML file
...
[INFO] --- wro4j-maven-plugin:1.3.8:run (default) @ test-artifact ---
...
[INFO] /home/urmuzov/java/target/test-artifact/target/all.js (78601bytes) has been created!
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------

Большие куски логов, которые не интересны с точки зрения архетипа я убрал.

Скомпилированный проект выглядит так:
~/test-artifact$ cd target/test-artifact/
~/test-artifact/target/test-artifact$ tree
.
├── desktop.js  // скомпилированная точка входа desktop.entry.js
├── index.html  // html файл точки входа desktop.entry.js
├── META-INF    // папка META-INF в контексте javasript приложения нам не интересна
├── mobile.html // html файл точки входа mobile.entry.js
├── mobile.js   // скомпилированная точка входа mobile.entry.js
├── target
│   └── all.js  // сжатая группа all из src/main/webapp/WEB-INF/wro.xml
└── WEB-INF     // папка WEB-INF в контексте javasript приложения нам не интересна


Операции выполненные при сборке


В процессе сборки были выполнены следующие операции:

  1. closure-package-maven-plugin посмотрел все <dependency> проекта в поисках closure-packages, нашел запакованную Closure Library и распаковал ее в специальную директорию для компилятора
    <dependency>
        <groupId>com.github.urmuzov</groupId>
        <artifactId>closure-library-package</artifactId>
        <version>${closureMaven.version}</version>
    </dependency>
    

  2. closure-compiler-maven-plugin исходя из значения свойства <properties>...<passes>desktop mobile</passes>...</properties> выполнил два «простых прохода» компиляции для файлов desktop.entry.js и mobile.entry.js скомпилировав их в desktop.js и mobile.js. Подробнее о проходах, настройках и плагине в целом можно почитать на этой странице.
  3. При помощи gmaven-plugin были сгенерированы свойства для быстрого подключения js файлов из html файлов, в данном случае это ${desktop.entry.js} и ${mobile.entry.js}. Сразу отвечу на вопрос, «а зачем вообще нужны эти свойства?». Все дело в том, что при использовании профилей compiled или merged необходимо подключать только один файл со скомпилированным кодом, например вот так:
    <script type="text/javascript" src="desktop.js"></script>
    

    А при использовании профилей sources или sources-no-compile необходимо подключать три файла, вот так:
    <script type="text/javascript" src="goog/base.js"></script>
    <script type="text/javascript" src="deps.js"></script>
    <script type="text/javascript" src="desktop.entry.js"></script>
    

    Поэтому для того чтобы не переписывать/комментировать эти строки в html файле при смене профиля можно использовать конструкцию ${output.closure.js.prefix}desktop${output.closure.js.suffix}, которая преобразуется в нужный код для разных профилей. Для большего удобства при помощи gmaven-plugin эта конструкция превращается в ${desktop.entry.js}, пример html файла можно посмотреть здесь.
  4. При помощи wro4j файл jquery-1.4.4.min.js преобразуется в all.js. Эта операция в принципе имеет мало смысла, но если файлов с библиотеками будет несколько wro может их объединить, минифицировать, а для css еще и включить в них картинки при помощи data:url.


Интеграция с IDE


В принципе все IDE имеют схожий функционал для работы с maven.

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


В IntelliJ IDEA тоже выбирается из выпадающего списка в тулбаре:

Но добавляются вручную:


Для Eclipse, я думаю, все делается аналогично.

Дополнительная информация


В принципе этого должно быть достаточно для того чтобы разобраться в использовании этого архетипа, но если что обращайтесь ко мне или к документации, которая пока что только на русском языке:
  • О проекте в целом здесь
  • О плагине для компилятора здесь
  • О архетипе здесь
  • Примеры использования компилятора отдельно от архетипа здесь

Также возможно кому-то будет интересен этот проект с точки зрения использования всех перечисленных выше наработок в одном более сложном проекте.

Ну и как говорится, Fork me on GitHub!
Tags:javascriptmavenclosure compilerclosure library
Hubs: JavaScript
+24
7.6k 53
Comments 8
Popular right now
Top of the last 24 hours