Из инструментов использовались: CoffeeScript, QUnit, PhantomJS, Google Closure compiler, а собирается всё это с помощью старого доброго GNU Make. Статья для всех, кому интересна библиотека и для тех, кто поверхностно знаком с вышеперечисленными технологиями и хотел бы увидеть их в работе.
Что получилось в результате?
elem = kidomi( ['div#main.content', ['span', {style: {color: 'blue'}}, 'Select file'], ['form', { name: 'inputName', action: 'getform.php', method: 'get'}, 'Username: ', ['input', {'type': 'text', 'name': 'user'}], ['input', {'type': 'submit', 'value': 'Submit'}]]])
Где elem
— это объект HTMLElement
, который выглядит как:
<div id="main" class="content"> <span style="color: blue;">Select file</span> <form name="inputName" action="getform.php" method="get"> Username: <input type="text" name="user"></input> <input type="submit" value="Submit"></input> </form> </div>
Ещё один пример, в котором сначала создаётся элемент <a>
, к onclick
которого привязывается функция, после чего элемент добавляется в общую структуру:
button = kidomi(['a.button', {href: '#'}]); button.onclick = function() { alert('Hello world!'); }; elem = kidomi(['div', ['span', 'Click this button:'], button]);
Бывалые люди сразу вспомнят jquery-haml, однако вдохновение для написания kidomi черпалось из ClojureScript-библиотеки dommy.
О чём же сыр-бор?
- kidomi написана на CoffeeScript.
- Она компилируется Google Closure в расширенном (ADVANCED_MODE) режиме.
- Она покрыта юнит-тестами.
- И эти тесты работают в т.ч. с помощью PhantomJS.
- Всё это собирается и запускается с помощью make.
Тонкости CoffeeScript
Исходный код начинается следующим образом:
window['kidomi'] = kidomi = (data) -> ...
Для тех, кто не знаком с особенностями компиляции CoffeeScript: по-умолчанию весь скомпилированный код оборачивается в функцию-обёртку и таким образом не экспортируется глобально. Кстати это поведение можно отключить флагом компилятора --bare
, но разве позволительно засорять глобальное пространство имён?
(function() { /* ... */ window['kidomi'] = kidomi = function(data) { /* ... */ } /* ... */ }).call(this);
Ещё одна особенность записи:
window['kidomi'] = # а не window.kidomi =
Это сделано специально для компилятора Google Closure, который бы «сократил» название, при записи window.kidomi =
Далее, код пишется в похожем ключе:
kidomi.makeElementFromTagData = makeElementFromTagData = (tagData) -> # ... kidomi.addAttributes = addAttributes = (elem, data) -> # ... # и т.д.
Как вы могли заметить, функции объявляются как локально, так и «экспортируются» в функцию-объект kidomi
. В первом случае это сделано для удобства: не нужно писать никаких префиксов (хотя в CoffeeScript достаточно написать @name
, что скомпилируется в this.name
). A чтобы юнит тесты могли до этой функции добраться, её можно записать в виде аттрибута глобального объекта. Что и делается через kidomi.functionName
.
Тестирование
Помните свои первые шаги в TDD? Мне например стоило неимоверных усилий заставить себя сначала писать тесты, а после — код. Зато, как быстро TDD приносит дивиденды!
Как было сказано выше, для написания юнит тестов для kidomi использовалась библиотека QUnit. Один из простейших тестов выглядит следующим образом:
test('isString', -> ok(kidomi.isString('')) ok(not kidomi.isString({})) ok(not kidomi.isString([])) ok(not kidomi.isString(10)))
А вот и сама функция:
kidomi.isString = isString = (s) -> typeof(s) == 'string' or s instanceof(String);
Стоит обратить внимание на то, что необходимо протестировать не только kidomi.js, но и обработанную напильником компилятором Closure kidomi.min.js. В идеале — все тесты покрывающие несжатый файл должны работать и для сжатой версии. Но тут мы натыкаемся на то, что все имена фунцкий кроме kidomi
были изменены до неузнаваемости. Например, вышеприведённый код isString(s)
превратился в
d.e=k=function(a){return"string"===typeof a||a instanceof String};
Чтобы с этим справиться, нужно скомпилировать библиотеку и тесты как единое целое. Также нужно указать компилятору, что qunit.js — это внешняя зависимость и соответственно такие имена фунцкий, как test
, module
, ok
и т.д. должны остаться без изменений.
Тем не менее, тестирование, в котором библиотека и тесты слиплены в один min.js файл, всё-таки отличается от тестирования сжатой библиотеки отдельно. Один из вариантов — запустить тесты лишь для основной функции.
Таким образом полное тестирование kidomi происходит в 3 прохода:
- Все тесты прогоняются на несжатой kidomi.js. Тесты и библиотека в отельных файлах.
- Все тесты сжимаются вместе с kidomi.js. Тесты и библиотека в одном файле.
- Тесты для функции
kidomi()
прогоняются на сжатой kidomi.min.js. Тесты и библиотека в отельных файлах.
PhantomJS
Как уже рассказывалось на Хабре, PhantomJS — это WebKit работающий в консоли и управляющийся собственным JS-API. На просторах интерета был найден скрипт связывающий PhantomJS и QUnit простым и в то же время эффективным способом: он парсит страницу с результатами тестирования и завершает процесс с кодом 0 (успех) или 1 (ошибка) в зависимости от результата тестов. Кстати, все тесты можно запустить в обычном браузере.
Сборка
Для сборки можно было использовать Rake, Maven, Grunt и т.д., но к сожалению со всеми вышеперечисленными системами я на «вы» (камрады, обещаю наверстать упущенное к следующему посту на тему JavaScript). Make же, как мне кажется, справился с задачей на «Ура!».
Makefile состоит всего из трёх основных целей сборки (build targets): ${BUILD_DIR}, $(BUILD_DIR)/kidomi.js
и $(BUILD_DIR)/kidomi.min.js
(также дополнительные плюшки в виде целей all, clean, .PHONY
и т.д.). В конце Makefile’а подключается файл Makefile.testsuite.mk
содержащий цели и правила для сборки и запуска всех ранее упомянутых тестов.
Заключение
Надеюсь, статья была для вас интересной и вы узнали из неё что-то новое. Исходный код kidomi открыт для всех желающих. Архивы содержат собранную версию библиотеки. Все комментарии, советы, отзывы и критика горячо приветствуются!
Благодарю за внимание!
ссылка на оригинал статьи http://habrahabr.ru/post/204506/
Добавить комментарий