четверг, 5 февраля 2015 г.

Информатор–оповещатель

Когда я только–только заинтересовался системой сборки, оптимизации, проверки и упаковки разрабатываемого сайта gulp, установил её у себя и начал с ней «играться», мне постоянно попадались примеры, в которых использовался плагин gulp–notify и демонстрировались его возможности по выводу на экран компьютера сообщений о ходе выполнения задач — оповещения об удачном или неудачном завершении, произошедшей ошибке и т.п.

Когда же я принял судьбоносное решение об использовании в качестве базового прототипа сборник задач gulpfile.js из пакета WebStarterKit, там ничего похожего на процедуры дополнительного оповещения не обнаружилось. По здравому размышлению, я пришёл к мысли, что особой острой необходимости в в таких процедурах (например, gulp–notify), вроде бы, нет: «глоток» самостоятельно и интенсивно выводит на экран консоли достаточное количество сведений о процессах, которые происходят при выполнении инициированных задач. Однако, совершенно очевидно, что такое положение дел не в полной мере соответствует представлениям о комфорте работы: разработчик вынужден постоянно наблюдать за экраном, не побоюсь этого слова, таращиться в монитор, при том, что в некоторых случаях работа задачи может растянуться на довольно продолжительное время. Кроме того, если уж взялся автоматизировать процесс разработки, то нужно идти до конца, то есть, упрощать себе, любимому, работу по максимуму, создавая всяческие удобства по тому же самому максимуму.

В общем, я бы тоже охотно и радостно попользовал бы плагин gulp–notify, чтобы просто и незатейливо передать в операционную систему команду, которая выведет на экран нужное мне сообщение. Но в моём случае решение оказалась невозможнім: чтобы использовать функционал gulp–notify с Windows, операционка должна иметь версию не ниже восьмой, то есть, моя Windows XP, а также некоторые другие версии помоложе моей (Vista, Win7) оказываются «лишними на этом празднике жизни».

Надежда забрезжила, когда оказалось, что этот плагин можно «прикрутить» к системе оповещения Growl. Я быстренько установил себе «рычалку» (вольный перевод английского growl), но что–то там не заладилось и скрипты никак не хотели выдавать долгожданные сообщения: то ли я что–то не так настроил, то ли просто они (плагин или Growl) меня невзлюбили (я, действительно, бываю порой неприятным). Недолго думая, решил сменить и плагин, и «рычалку».

Оказалось, что gulp–notify при работе «в тандеме» с Growl использует плагин node-notifier, и я решил «припасть к истокам», то есть, обратиться к первоисточнику. Поскольку мой предыдущий негативный опыт взаимодействия с Growl ещё был свеж в памяти, и горючие слёзы разочарования не высохли на упитанных ланитах, я обратил внимание на возможность вызова с помощью этого плагина всплывающей подсказки из системного лотка, широко известной среди пользователей винды как WindowsBalloon. Для этого, правда, требовалась дополнительная программа notifu — бесплатная утилита из двух не требующих инсталляции файлов (notifu.exe и notifu.pdb для 32–битных систем, notifu64.exe и notifu64.pdb для 64–битных; каждая пара размером около 4,5М).Но, оказалось, что эта утилита поставляется вместе с пакетом node-notifier (потому–то он и «весит» в общей сложности 10М).

Итак, взял я рекомендованный пример и попробовал «проиграть» его на примере задачи JShint (проверка корректности сценария Javascript):

var WindowsBalloon = require('node-notifier').WindowsBalloon;
 
var notifier = new WindowsBalloon();

gulp.task('jshint', function () {
    var proc = gulp.src('js/*.js')
        .pipe($.jshint())
        .pipe($.jshint.reporter('jshint-stylish'));

         notifier.notify({
             title: 'GULP JShint',
             message: 'Выполнение задачи завершено',
             sound: true, // При выводе сообщения подаётся звуковой сигнал
             time: 5000, // Сообщение демонстрируется в течение 5 секунд
             wait: false});

    return proc;
});

В правом нижнем углу экрана всплыло жёлтое облачко с текстом в две строки (см. скриншот внизу). И всё бы хорошо, но выполнение задачи при этом остановилось на заказанные пять секунд (или до тех пор, пока я не клацну мышкой по облаку). Вроде бы, директива wait: false (см. строку 16 в приведенном выше фрагменте кода) должна обеспечить завершение работы задачи, независимо от поведения сообщения, но на практике она ждёт и «задерживает очередь». Замена в этой строке false на true не привело к заметному изменению в поведении плагина. В общем, получилось не совсем то, что ожидалось, и комфорта сложившаяся ситуация совершенно не прибавила.

Вместе с тем, мне когда–то попадался на глаза пример вызова внешней команды (а notifu является именно такой внешней командой), поражающий своей простотой и изяществом: используется вызов стандартного метода дочернего процесса (child_process), изначально встроенного в пакет node.js, то есть, никакие плагины не нужны:

var exec_f = require('child_process').execFile;
exec_f('C:\\Program Files\\Accessories\\notifu', 
  ['/p', 'GULP JShint', '/m', 'Работа сценария завершена', '/i', 'info'], 
  {timeout: 250}
  );

что аналогично вызову команды с аргументами:

> notifu /p 'GULP JShint' /m 'Работа сценария завершена' /i 'info'

У этого способа использования системы оповещения есть несколько особенностей. Во–первых, необходимо обзавестись утилитой notifu (я её загрузил с сайта разработчика и поместил в каталоге C:\Program Files\Accessories). Во–вторых, директива timeout: 250 позволяет прервать этот child_process через 250 мс (продолжительность таймаута должна быть кратной 250): в результате, сообщение в жёлтом «облачке» продолжает «висеть» над системным лотком, а выполнение задачи продолжается практически сразу (ну, что такое 250 миллисекунд!). Похоже, удалось обойти досадный баг с wait в node-notifier.

Итак, приемлемое решение было найдено, осталось уничтожить следы неудачных экспериментов, то есть «снести» node-notifier и Growl, после чего твёрдой поступью зашагать в светлое будущее под знаменем notifu.

«Не введи в искушение, но избавь от лукавого» — гласит основная христианская молитва (кстати, единственная рекомендованная Спасителем). Не получилось: и в искушение ввёл, и от лукавого не избавил. А всё потому, что решил я напоследок попробовать, работает ли node-notifier с growl′ом — просто так попробовать, на всякий случай. Попробовал. Работает.

Получилось гораздо выразительнее, красивее и нагляднее — никакого сравнения с notifu

var Growl = require('node-notifier').Growl;

var notifier = new Growl({
  name: 'Growl @ zaliv.info', 
  host: 'localhost',
  port: 23053
});

gulp.task('jshint', function () {
    var proc = gulp.src('js/*.js')
        .pipe($.jshint())
        .pipe($.jshint.reporter('jshint-stylish'));

         notifier.notify({
             title: 'GULP JShint',
             message: 'Выполнение задачи завершено',
             wait: false});

    return proc;
});

Здесь отказ от ожидания реакции пользователя wait: false (см. строку 17) работает исправно, а продолжительность показа сообщения на экране и куча других свойств регулируются уже в настройках Growl. В общем, моё сердце дрогнуло…

А потом я «встретил» growly и понял, что это — судьба. Я таки удалил node-notifier со всеми его десятью мегабайтами разного мотлоха, включая пять подвязанных к нему плагинов, входящих в его зависимости. Потому, что node-notifier для «общения» с Growl′ом использовал именно growly, а я, как уже заметил мой читатель, стараюсь добраться до первоисточников.

Итак, знакомьтесь: конечная цель моих изысканий — плагин growly.

    var growly = require('growly');
    growly.register('GULP @ zaliv.info'); 

    gulp.task('jshint', function () {  
        var proc = gulp.src('js/*.js')
            .pipe($.jshint())
            .pipe($.jshint.reporter('jshint-stylish'));

        growly.notify('Скрипт выполнен', { title: 'JShint'});
 
        return proc;
});

Можно было бы на этом и остановиться, но я ударился в усовершенствования и украшательства — соорудил три иконки для основных типов сообщений и наваял универсальную функцию для вызова «рычалки»:

var growly = require('growly');

growly.register('GULP @ zaliv.info', 'zaliv.png', [
 {label: 'success', dispname: 'Успешно'},
 {label: 'warning', dispname: 'Предупреждение'},
 {label: 'error', dispname: 'Ошибка'}
]); 

function depesha (header, message, mode) {
 var labels = {
   'i': 'success',
   'w':'warning',
   'e':'error'},
  t = new Date();
  
 growly.notify(message, { 
  title: header + " [" + t.toLocaleTimeString() + "]",
  label: labels[mode],
  icon: 'zaliv-' + mode + '.png'  
  })
};

gulp.task('jshint', function () {  
 gulp.src('js/*.js')
  .pipe($.jshint())
  .pipe($.jshint.reporter('jshint-stylish'));
  
  depesha("JShint", "Задача выполнена", "i");
  depesha("JShint", "Есть незначительные погрешности", "w");    
  depesha("JShint", "Ошибка выполнения", "e");
});

В настройках Growl установил «липкий» (sticky) режим отображения сообщений, то есть, они будут болтаться на экране, пока я их сам не закрою — лучше так, чем пропустить что–нибудь, пока на кухне чайком с зефиркой баловался. Получилась такая картина:

Если будет время, можно ещё «пошаманить» с темами в настройках Growl (настроить свою для каждого типа сообщений в зависимости от метки), но это не к спеху.

воскресенье, 25 января 2015 г.

Настройка на gulpfile.js

Удивительную и несравненную индийскую кухню невозможно представить без экзотических специй: есть у южан масала для овощей, есть для риса, для рыбы, птицы и мяса, есть и масала–чай. Но это не полный перечень многообразий и разновидностей. Дело в том, что каждая хозяйка в каждом доме имеет свой собственный рецепт масалы для овощей, риса, мяса и всего прочего — нередко этот рецепт трепетно и бережно передаётся из поколения в поколение, как в европейских аристократических фамилиях передаётся столовое серебро.

Эти самые масалы прижились и на Ближнем Востоке: в бакалейных отделах супермаркетов можно увидеть ряды, уставленные картонными пачками с готовыми смесями. Впрочем, в арабском мире эти наборы могут иметь и другие имена: например, я охотно пользовался ливанской смесью, называемой «за′атар» (или «загатар», если читать это слово по правилам чтения украинского языка — в арабском тоже есть фрикативный звук «г»). Как бы там ни было, но восточные базары, включая «супербазары» (то есть, супермаркеты) предоставляют достаточный ассортимент специй (например, см. фото в начале статьи), чтобы составить композицию приправ — масалу — на любой, даже самый взыскательный, вкус.

Я, вот, тоже решил замесить свою gulp—«масалу» для автоматизации работы над сайтом, но приступил к работе не с чистого листа, а разбирая и отлаживая уже имеющийся файл сценариев gulpfile.js из весьма неплохого набора WebStarterKit 0.5.2 от Google.

Первым делом, пришлось откорректировать набор плагинов, которые я с энтузиазмом нахватал в период первого знакомства с gulp′ом. На перенастройку ранее сложившейся конфигурации пакета меня побудил доселе неизвестный загрузчик плагинов, который в наборе сценариев gulpfile.js выглядел так:

var gulp = require('gulp');
var $ = require('gulp-load-plugins')();

$.useref();

Оказалось, что этот инструмент загружает (в базовом gulpfile) 17 плагинов, имена которых начинаются с «gulp» — вроде бы, ничего особенного, но при изменении набора этих самых плагинов (удалении ненужных и добавлении необходимых), они неизменно будут «подтягиваться» в скрипт по списку установленных пакетов, имеющихся в из package.json. А вызывается загруженный с помощью этого загрузчика плагин как некий метод: в приведенном выше примере вызывается плагин gulp-useref.

По умолчанию, gulp-load-plugins загружает плагины, соответствующие маске «gulp-*», но эту маску можно изменить. Разработчики рассматриваемого gulpfile.js, по какой–то причине, не стали этого делать и часть плагинов загружают «руками», то есть, путём явного указания имён. Не вдаваясь в причины подобной избирательности, я решил следовать той же тактике (по крайней мере, пока).

В дальнейшем, после вдумчивого и неторопливого сравнения моего первичного перечня плагинов и предлагаемого гугловскими экспертами, мой файл gulp–настроек (package.json) обогатился такими приобретениями:

  • gulp–cache — создание промежуточного временного файла;
  • gulp–changed — отслеживание изменённых файлов;
  • gulp–csso — оптимизация и минимизация CSS–файлов;
  • gulp-if — управление выполнением задачи по условию;
  • gulp-jshint — проверка файлов сценариев Javascript на наличие ошибок;
  • gulp-replace — замена строки;
  • gulp-size — вывод сведений о размере проекта или составляющих его файлов;
  • gulp-useref — редактирование ссылок на используемые ресурсы (css, javascript) с учётом изменений конечных имён файлов;
  • jshint-stylish — дополнение к плагину gulp-jshint, позволяющее выводить разработчику более содержательные описания выявленных ошибок;
  • psi — тестирование компонентов проекта и вывод подробной информации о проекте;
  • run-sequence — запуск нескольких задач gulp последовательно или параллельно.

Оказались невостребованными шесть плагинов из упоминавшихся в гугловском файле сценариев:

  • apache-server-config, gulp–flatten и opn — плагины установлены (имеются в файле конфигурации package.json), но не упоминаются в базовом gulpfile.js, то есть, нет практической надобности в их использовании;
  • browser-sync — дублирует функциональность gulp-livereload;
  • require-dir — подключает файлы из указанной в аргументах папки, но пока подключать нечего;
  • gulp-ruby-sass — дублирует функциональность Scout.

Кроме того, из первоначального набора, составленного при установке gulp, удалены пакеты:

  • gulp-autoprefixer и gulp-sass — дублируют функциональность Scout;
  • gulp-browserify, gulp-notify и gulp-util — не нашлось применения.

А ещё угроза удаления нависла над плагинами из первоначального набора, которые, похоже, дублируют утилиты из нового набора (WebStarterKit):

  • gulp-concat и gulp-rename, похоже, дублирует gulp-useref;
  • gulp-filesize — gulp-size;
  • glob вроде бы, нигде не используется, но вдруг пригодится….

воскресенье, 18 января 2015 г.

Интеграция и кооперация

Несомненно, интегрированная среда разработчика (IDE — Intregrated Development Environment) штука хорошая, можно сказать, замечательная. Как правило, под такой средой понимается единая программа, объединяющая в себе и редактор для написания кода, и компилятор, и систему сборки, и отладчик — потому она и называется интегрированной. Вообще–то, особых проблем с её освоением и использованием быть не должно: можно даже подстроить отдельные компоненты под свои личные префернции с помощью плагинов. Но как быть, если уже привык к некоторому набору инструментов, не только идеально подстроив–настроив, а и отшлифовав–отполировав каждый из них? Перспектива перехода на любую «классическую» IDE воспринимается как замена мягких и уютных бурок на стариковских ногах стильными кожаными туфлями на модном, но некомфортном каблуке.

Вот и возникает противоречие: с одной стороны, чтобы всё срабатывало само собой («механизация и автоматизация в действии»), а с другой стороны, сердце кровью обливается от грядущего расставания с отдельными приложениями, столь милыми сердцу, глазам и прочим органам.

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

Свою «кооперативно–интегрированную» среду для работы с сайтом я начал строить на Notepad++ и «джентльменском наборе» Денвер (веб–сервер Apache, СУБД MySQL, интерпретатор PHP и много других утилит, локально имитирующих стандартную службу хостинга). Строго говоря, текстовый редактор и пакет веб–процессинга никак между собой не связаны, то есть, не интегрированы, но в ближайшее время и с неизбежностью мировой революции будут скооперированы: все усилия по интегрированию или псевдоинтегрированию направлены на решение светлой задачи — добиться незамедлительного и автоматического отображения в браузере любых изменений в компонентах веб–страницы (html, css, php, javascript, изображения jpeg, png, gif), большая часть которых производится именно в текстовом редакторе.

Крайние элементы «пазла» обозначены, теперь нужно выстроить цепочку из правильно подобранных промежуточных компонентов.

Если начинать движение от Блокнота, то к нему уже «прикручен» интерпретатор–преобразователь SASS: и сама система установлена (вернее, клон этой системы, без Ruby, — Scout), и плагин, обеспечивающий интерфейс редактора с интерпретатором.

Следующий элемент цепочки — gulp, система сборки проекта, который «подхватывает» изменённые файлы (в том числе и созданный Скаутом), обрабатывает их в соответствии с моим набором инструкций и посылает сигнал Огнелису о необходимости перегрузить страницу. Браузер распознаёт этот сигнал благодаря предусмотрительно установленному расширению и запрашивает обновлённую версию страницы у веб–сервера. Апач, входящий в состав Денвера (вот мы до него и добрались) выполняет запрос, и перед глазами изумлённого разработчика — ай, шайтан! — возникает то, чего он добивался (или же нечто совершенно неожиданное — в случае ошибки).

Ключевым элементом в этой цепочке на текущий момент оказался Гульп: всё остальное уже настроено и работает на благо любимого сайта. Поскольку автор статьи не располагает кривой козой (нормальной козы, кстати, тоже нет), то возможности объехать проблему на этом сомнительном транспортном средстве не предвидится, по крайней мере, в обозримом будущем. Таким образом, задачу приходится решать.

В качестве отправной точки я выбрал гугловский «стартовый пакет разработчика», который тоже использует Gulp. Итак, начинаю читать документацию и разбираться в его устройстве и функциональности по старому детскому принципу: откручивать кукле голову, чтобы увидеть внутренности.

Собственно говоря, начальный пакет разработчика содержит набор шаблонов и инструментов, который способен полностью заменить HTML5Boilerplate и Bootstrap, хотя команда Google тактично и дипломатично оставляет решение об отказе от этих двух пакетов (и переходе на их продукт) на усмотрение разработчика, то есть, вполне допускает некий симбиоз. Для меня же объектом первостепенного интереса в гугловском наборе является предлагаемый файл–сценарий gulpfile.js: именно в него я и полез с зубилом, молотком, сверлом и плоскогубцами.

воскресенье, 11 января 2015 г.

Шаблоны–трафареты

Детские воспоминания о летнем отдыхе на море, воскрешют перед мысленным взором живописное полотно, изображающее тщедушную лошадёнку размером со здоровенную псину, на которую взгромоздился лихой горец в папахе и чёрном цее с газырями — какой–нибудь весёлый дядька суёт свою розовую от южного солнца и опухшую от разливного вина морду в дырку между папахой и воротником цея, суетливый фотограф клацает затвором и получается фотография: будто бы скачет штымп верхом, почему–то развернувшись лицом и корпусом к объективу, унося в неведомую даль толстомясую тётку с густыми сросшимися бровями и равнодушным взглядом, сидящую на той же конесобаке. Тётка должна бы олицетворять украденную невесту, но даже неискушённые отроческине представления о взаимоотношениях полов вызывали смутные сомнения: неужели этот мужик не мог найти себе чувиху получше?

Позже, изобретательные уличные фотографы перешли на «дурилки картонные», и можно было в центре любого мегаполиса сфотографироваться с фанерными М.Горбачёвым, Р.Рейганом или Усамой бен–Ладеном (не к ночи будь помянут!)

Цифровая эра внесла новые подходы и техники в арсенал фотографов–приколистов: теперь даже не требуется, высунув от старания кончик языка, выпиливать из фанерки какую–нибудь знаменитость или изощряться, малюя на холстине вокруг дырки для лица клиента заведомо малохудожественную композицию. Теперь достаточно запустить графический редактор, вырезать из цифрового изображения физиономию заказчика и вставить его в цифровой же трафарет (например, такой, как изображён на фото вверху).

Вообще говоря, ирония по поводу шаблонов уместна только в контексте неудачных реализаций — в большинстве остальных случаях трафареты весьма и весьма полезны. Гордо выпятив грудь от переполняющего чувства собственной прозорливости и догадливости, должен ответственно заявить, что много лет тому назад самостоятельно пришёл к идее разделять данные (программно создаваемый контент сайта) и представление (оформление сайта, его дизайн). Тогда появились первые версии систем управления контентом, но мне совершенно не хотелось тратить время на вникание в Wordpress или Joomla! — душа просила чего—нибудь попроще.

Я нашёл невероятно простое и столь же невероятно эффективное средство — php–класс XTemplate: нет ничего лишнего, но есть всё, что необходимо.

Вдохновлённый классами шаблонов FastTemplate и QuickTemplate, венгерский программер Варнава Дебрецени (Barnabas Debreczeni) разработал в 2000 году свой движок, который оказался существенно быстрее своих предшественников, хотя и основывался на идентичных синтаксисе и идеологии (заметьте, не развил существовавшие, а наваял «с нуля»). Энтузиазма у него хватило только на год с небольшим, и он забросил этот проект. «Знамя подхватил» британец Джереми Коутс (Jeremy Coates), развивал этот движок с 2002 по 2007 год (добавил некоторые удобные возможности, «перековал» под PHP5) и довёл его до версии 0.4.0, которая уже почти восемь лет радует своей незатейливостью и эффективностью.

Вот как выглядит использование класса со стороны php–скрипта:

 require "xtpl.class.php"; 
 $xtpl=new XTemplate ("page.xtpl");
  
 $xtpl->assign("VARIABLE_1", "Тест");

 $varstr = "Привет, мир!";
 $xtpl->assign("VARIABLE_2", $varstr);
 
 $row=array(
  ID=>"38",
  NAME=>"cranx",
  AGE=>"20"
  ); 
 
 $xtpl->assign("VARIABLE_3", $row);
 $xtpl->parse("main.block");

 $xtpl->parse("main"); 
 $xtpl->out("main");

А так — со стороны шаблона (файл page.xtpl):

<!-- BEGIN: main -->
<!DOCTYPE html>
<head>
<title>Заголовок страницы</title>
</head>
<body>
<p>Это пример простейшей подстановки: жирным шрифтом будет выведено "Привет, мир":
 <b>{VARIABLE_1}</b></p>
<p>Или так: <b>{VARIABLE_2}</b></p>

 <!-- BEGIN: block#Так можно добавлять в  блоки комментарии -->
 <table border="1">
  <tr>
   <td>id</td>
   <td>{VARIABLE_3.ID}</td>
  </tr>
  <tr>
   <td>name</td>
   <td>{VARIABLE_3.NAME#Можно комментировать и тэги}</td>
  </tr>
  <tr>
   <td>age</td>
   <td>{VARIABLE_3.AGE}</td>
  </tr>
 </table>
 <!-- END: block -->

<p>Глобальные переменные можно выводить без предварительного присвоения:<br />
$_SERVER['HTTP_HOST']={PHP._SERVER.HTTP_HOST}<br />
$_SERVER['PHP_SELF']={PHP._SERVER.PHP_SELF}<br />
$_SERVER['HTTP_USER_AGENT']={PHP._SERVER.HTTP_USER_AGENT}<br />
и т.п.<br />
(но эти переменные должны быть инициализированы в скрипте до создания объекта $xtpl)</p>
</body>
</html>
<!-- END: main -->

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

Список функциональности пакета содержит также возможность включения других файлов–шаблонов, например:

{FILE "another.template.xtpl"}
{FILE {VAR_FILENAME}}

Имеется также набор приёмов и функций для обработки пустых значений переменных. Впрочем, это довольно экзотическая тема (мне не приходилось на практике использовать эти возможности, хотя они не отличается сложностью).