- PVSM.RU - https://www.pvsm.ru -
Во всем мире объем используемого JS кода в приложениях растет очень сильно, что уже неоднократно подчеркивалось, посмотреть картинки на эту тему можно например тут [1] или тут [2]. Соответственно с ростом количества кода возникает необходимость структурирования данных, управления зависимостями и проч., которые на данный момент решает целый букет фрэймворков, например RequireJS [3] в композиции с Backbone [4]. С другой стороны в мире Java для управления зависимостями и контроля процесса сборки проекта используется Maven, который отлично справляется с задачей разделения больших проектов на модули, запуска тестов в нужное время и т.д. У некоторых разработчиков, уже давно использующих Maven для сборки проекта, может возникнуть желание вынести свой отлично структурированый JS код в отдельный модуль, тестировать его во время сборки и совершать с ним все операции, которые позволяют делать плагины, о чем и пойдет речь.
Задача: создать Maven проект с управлением зависимостями, содержащий структурированый JS код и статичную разметку, шаблонизатором, возможностью тестировать части кода по отдельности. Основная идея создания такого модуля заключается в том, что для предоставления статичных html и JS файлов пользователю предпочтительно использовать nginx или apache http server, которые работают быстрее практически любого java веб контейнера или сервера приложений. Сделать автоматическое копирование ресурсов в нужные папки после сборки не составит труда, но нужно исключить из модуля java класс файлы, что в случае с «одностраничными» сайтами, использующими REST сервисы, не составит труда.
Изучив многообразие доступных решений, был выбран следующий набор, который удовлетворяет нашим требованиям:
Подробное описание каждого из этих фрэймворков вам придется прочитать самим, а мы начнем с создания Maven проекта.
Maven диктует нам правила описания проекта и структуру директорий, в которых располагаются исходники нашего приложения. Для создания проекта нам нужно создать pom.xml файл и добавить в него название проекта, версию и прочую стандартную информацию. Пакетирование выбираем war, потому что это веб часть нашего приложения.
Помимо базовой информации о проекте в build секцию нужно добавить объявление ряда плагинов:
<plugin>
<groupId>com.github.searls</groupId>
<artifactId>jasmine-maven-plugin</artifactId>
<version>1.2.0.0</version>
<extensions>true</extensions>
<executions>
<execution>
<goals>
<goal>test</goal>
</goals>
</execution>
</executions>
<configuration>
<jsSrcDir>${project.basedir}/src/main/webapp/js</jsSrcDir>
<jsTestSrcDir>${project.basedir}/src/test/js</jsTestSrcDir>
<browserVersion>FIREFOX_3</browserVersion>
<!--use require js in specs-->
<specRunnerTemplate>REQUIRE_JS</specRunnerTemplate>
<preloadSources>
<source>libs/jasmine/jasmine-jquery-1.3.1.js</source>
</preloadSources>
<!--customize path to require.js-->
<scriptLoaderPath>libs/require/require.js</scriptLoaderPath>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.1</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>copy-js-files</id>
<phase>generate-test-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/jasmine</outputDirectory>
<resources>
<resource>
<directory>src/main/webapp</directory>
<filtering>false</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
После описания проекта нужно создать желаемую структуру директорий. Получившаяся у меня заготовка проекта в результате обладает следующей структурой:
.
├── README.md
├── pom.xml //Maven описание проекта
└── src //Корневой каталог (структура Maven)
├── main //Исходники приложения (структура Maven)
│ └── webapp //Исходники веб составляющей (структура Maven)
│ ├── css
│ │ ├── bootstrap.css
│ │ ├── style.css
│ │ └── styles.css
│ ├── imgs
│ │ └── 334.gif
│ ├── index.html //Индексная страница (одностраничный сайтик)
│ ├── js
│ │ ├── app.js //инициализация Backbone роутера
│ │ ├── libs //библиотеки
│ │ │ ├── backbone
│ │ │ │ └── backbone-min.js
│ │ │ ├── handlebars
│ │ │ │ └── handlebars.js
│ │ │ ├── jasmine
│ │ │ │ └── jasmine-jquery-1.3.1.js
│ │ │ ├── jquery
│ │ │ │ ├── jquery-min.js
│ │ │ │ └── jquery-serialize.js
│ │ │ ├── require
│ │ │ │ ├── require.js
│ │ │ │ └── text.js
│ │ │ └── underscore
│ │ │ └── underscore-min.js
│ │ ├── main.js //Входной файл JS - настройка RequireJS и вызов app.js
│ │ ├── router.js //глобальный роутер
│ │ └── views //Backbone View сабклассы
│ │ └── layout
│ │ ├── EmptyContent.js
│ │ ├── EmptyFooter.js
│ │ ├── NavigationHeader.js
│ │ └── PageLayoutView.js
│ └── templates //статичные html шаблоны
│ └── layout
│ ├── emptyContentTemplate.html
│ ├── footerTemplate.html
│ ├── navigationTemplate.html
│ └── simpleTemplate.html
└── test // исходники тестов (структура Maven)
└── js //Jasmine тесты
└── layout
└── AboutLayout.js
В приведенной структуре первоначально загружается файл main.js, который объявлен в качестве единственного загружемого скрипта в index.html. Данный скрипт осуществляет инициализацию приложения, которая начинается с Backbone роутера, определяющего компоненты, загружаемые приложением при переходе по различным ссылкам приложения.
Итак, основные компоненты, которыми манипулирует Backbone это объекты, расширяющие View, Model и Collection. Предполагается, что каждый такой набор мы можем сложить в отдельную папку и разбить по подпапкам на основе определенной логики, например, по страницам, в которых они используются. После этого останется только правильно подключать зависимости между компонентами, что в нашем случае будет выглядеть так:
//RequireJS объявление зависимостей
define([
'jquery',
'underscore',
'backbone',
//статичный html темплэйт для handlebars
'text!templates/layout/emptyContentTemplate.html',
//хак для корректной загрузки Handlebars
'handlebars'
], function($, _, Backbone,emptyContentTemplate){
var EmptyContent = Backbone.View.extend({
});
return EmptyContent;
Как было видно в предыдущем снипете статичная .html разметка, используемая компонентом в качестве основы для Handlebars шаблона, передается как одна из зависимостей при помощи RequireJS. Разметка содержит вкрапления синтаксиса, специфичного для шаблонов, и выглядит примерно так:
<div class="item">
<a href="#/description?id={{id}}">{{title}}</a>
</div>
Данный шаблон будет преобразован в полноценную разметку в процессе рендеринга, для чего ему необходимо передать объект содаржащий, значения параметров id и title.
Так как понятия Layout ни один из присутствующих фрэймворков не предоставляет, мы введем свое и назовем его страницей, что в сущности своей будет объектом, расширяющим класс View и содержащим композицию нескольких других View. В методе инициализации данного компонента нужно будет проверить входные параметры, и, если какое-либо из аггрегируемых представлений оверрайдится, использовать экземпляр передаваемого в качестве параметра класса, а не дефолтного.
define([
'jquery',
'underscore',
'backbone',
'views/layout/NavigationHeader',
'views/layout/EmptyContent',
'views/layout/EmptyFooter',
'text!templates/layout/simpleTemplate.html' ,
'handlebars'
], function($, _, Backbone,NavigationHeader,EmptyContent,EmptyFooter,simpleTemplate){
var PageLayoutView = Backbone.View.extend({
template : Handlebars.compile(simpleTemplate),
//defaults to NavigationHeader view function
headerContent : NavigationHeader,
//defaults to EmptyContent view function
mainContent : EmptyContent,
//defaults to EmptyFooter view function
footerContent : EmptyFooter,
initialize : function(options) {
//instantiate appropriate views based on component functions
if (options.mainContent != undefined && options.mainContent != null) {
this.mainContent = options.mainContent;
}
if (options.headerContent != undefined && options.headerContent != null) {
this.headerContent = options.headerContent;
}
if (options.footerContent != undefined && options.footerContent != null) {
this.footerContent = options.footerContent;
}
},
render: function(){
//compile handlebars template with appropriate markup of components
var html = this.template();
//append appropriate content to root element right away after compilation
$(this.el).html(html);
this.headerView = new this.headerContent({el : '#header'});
this.mainView = new this.mainContent({el : '#mian'});
this.footerView = new this.footerContent({el : '#footer'});
this.headerView.render();
this.mainView.render();
this.footerView.render();
return this;
}
});
return PageLayoutView;
});
Тестирование прдлагается осуществлять при помощи Jasmine и соотвтетсвующего плагина. Данный фрэймворк позволяет писать тесты, которые выполняются во время каждой сборки проекта, также есть возможность выполнить цель плагина bdd, что запустит Jetty и позволит вам открыть в браузере страничку с отчетом и прогонять тесты каждый раз при обновлении страницы без полной пересборки проекта. Данный способ очень удобен во время разработки, особенно если вы пишете тесты до кода.
Единственное, что мне пришлось изменить в стандартном описании сценария — это добавление заглушки на консоль, ибо HtmlUnit, в котором будет запущен тестируемый код, не поддерживает ее.
Исходники и заготовку проекта можно взять тут [9].
В процессе работы были использованы следующие материалы:
Автор: aakhmerov
Источник [12]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/maven/24290
Ссылки в тексте:
[1] тут: http://umumble.com/blogs/javascript/590/
[2] тут: http://news.softpedia.com/news/Average-Web-Page-Size-Grew-25-Percent-in-a-Year-236789.shtml
[3] RequireJS: http://requirejs.org/
[4] Backbone: http://backbonejs.org/
[5] Maven: http://maven.apache.org/
[6] Handlebars : http://handlebarsjs.com/
[7] Jasmine : http://pivotal.github.com/jasmine/
[8] Jasmine Maven plugin : http://searls.github.com/jasmine-maven-plugin/
[9] тут: https://github.com/aakhmerov/mrbhj
[10] pseudobry.com/jasminemavenrequirejscoverage/: http://pseudobry.com/jasminemavenrequirejscoverage/
[11] backbonetutorials.com/organizing-backbone-using-modules/: http://backbonetutorials.com/organizing-backbone-using-modules/
[12] Источник: http://habrahabr.ru/post/165085/
Нажмите здесь для печати.