Gulp или Grunt, да всё равно

Недавно появившаяся система сборки gulp внушает большие надежды. Этот инструмент дает возможность всего за 10 минут написать действительно лаконичный код. Часто, одного этого факта вполне достаточно, чтобы противопоставить его более «многословному» Grunt.

Вы ведь помните Grunt? Я достаточно много писал о нем в прошлом, и даже написал книгу, в которой я предложил использовать его как инструмент для сборки. Grunt — потрясающий таск-менеджер, и вы должны как-нибудь обязательно его попробовать.

Некоторые люди считают, что Grunt будет вытеснен gulp, и предрекают последнему множество поклонников. Я же считаю, что это возмутительно и вопиюще нелепо. Хотя это и не так очевидно, но у Grunt есть множество плюсов по отношению к gulp. С другой стороны, у gulp тоже есть свои преимущества по отношению к Grunt.

В этой статье я собираюсь объяснить, что из себя представляет gulp, так как это достаточно новый инструмент, который появился около полугода назад. Затем, я хочу сравнить его с Grunt, чтобы показать, какой из таск-менеджеров что лучше делает и объяснить, почему он это делает лучше.

gulp.png

Введение в gulp

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

Как вы могли заметить в ходе изучения документации, установка gulp очень похожа на установку Grunt:

В этом месте пока что главное различие с Grunt заключается в том, что gulp не выносит свой CLI из основного модуля. Это может немного запутать вас, если вы не понимаете, как работает установка глобальных модулей в npm.

Перед тем, как мы перейдем к примеру gulpfile.js, давайте разберемся, что из себя представляет API gulp. Оно содержит всего 5 методов. В общем-то, это все, что вам понадобится для работы. Это потрясающе лаконичное API, которое действительно помогает gulp фокусироваться на главном — решении задач, связанных со сборкой проектов.

Должен признаться, на мой взгляд это API просто великолепно! Посмотрим на простую задачу из документации gulp — компилирование Jade-шаблонов. Здесь jade() и minify() — это плагины gulp, я объясню вам принцип работы чуть ниже.

js gulp.src('./client/templates/*.jade')
  .pipe(jade()) .pipe(minify())
  .pipe(gulp.dest('./build/minified_templates'));

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

В Grunt же вам, напротив, понадобится именно шаблонный код для той же самой операции: загрузка npm модулей с помощью grunt.loadNpmTasks, создание ссылки на задачу, которая содержит список требуемых тасков, и конфигурация каждой задачи так, как вам нужно. Одна из проблем Grunt, которую решает gulp — это единый монолитный конфигурационный объект, заставляющий вас танцевать с бубном, чтобы достичь желаемых результатов.

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

Помимо всего прочего, лаконичность кода в gulp помогает предотвратить ситуацию, когда конфигурация сборки начинает сбивать вас с толку.

Каков gulp на вкус

Взгляните на этот короткий пример gulpfile.js, взятый из документации.

var gulp = require('gulp'); 
var uglify = require('gulp-uglify'); 

gulp.task('scripts', function() { 
  // Минифицируем и копируем все JavaScript файлы,
  // кроме сторонних скриптов
  gulp.src(['client/js/**/*.js', '!client/js/vendor/**']) 
    .pipe(uglify()) 
    .pipe(gulp.dest('build/js'));

    // Копируем сторонние скрипты
    gulp.src('client/js/vendor/**') 
      .pipe(gulp.dest('build/js/vendor')); 

}); 

// Задача по умолчанию, вызывается запуском `gulp`
gulp.task('default', function() { 
  gulp.run('scripts'); 

  // Отслеживаем файлы, и по их изменению, запускаем задачу 
  gulp.watch('client/js/**', function(event) { 
    gulp.run('scripts'); 
  }); 
});

Согласитесь, это довольно просто прочесть, даже если вы не знакомы с потоковой системой передачи данных в Node.js. Я уверен, что этот файл гораздо легче для восприятия, чем Gruntfile.js, который делал бы аналогичные вещи. В этом примере мне достаточно пробежаться глазами по коду и понять, что происходит. А если вы избавитесь от очевидных комментариев, то вы получите очень краткий gulpfile.js.

Тот факт, что Gulp обеспечивает реализацию метода .watch как часть своего основного API, также не может не радовать, ведь наблюдение за изменениями файлов является ключевой частью функционала систем сборки, оно просто необходима в процессе разработки. Да и поддержка разработки асинхронных задач, кажется, гораздо лучше интегрирована в gulp, чем в Grunt, где цели («targets») действительно усложняют работу при передаче данных в задачи.

В целом, API, которое есть у gulp, обеспечивает более понятное и простое использование, чем API Grunt. Это более-менее весомый аргумент в пользу gulp, имеющего столь простое, лаконичное, потрясающее API. И простые плагины, которые выполняют одну простую задачу, а не делают все, что им может понадобиться, как это принято в большинстве плагинов Grunt. Но, у gulp есть свои недостатки.

Рассекая симбиоз /Gr?u(nt|lp)/

Давайте рассмотрим ситуацию, когда сравнение этих двух таск-менеджеров заходит в тупик. В gulp все основано на потоковой системе передачи данных, как если бы вы писали shell-скрипты. Все в порядке, если вы понимаете, как устроены потоки в Node.js, но если это не так — похоже, что у меня для вас плохие новости.

streams.jpg

Итак, если вы хорошо знакомы с Node.js, то вам будет трудно игнорировать ту смелость, с которой gulp строит поток сборки напрямую вокруг кода, вместо предварительной конфигурации, как это делает Grunt. Однако, с другой стороны, это неоспоримый недостаток — некоторые люди просто никогда не поймут механизм потоков в Node. Это могут быть PHP-разработчики, или другие серверные вуду, типа Ruby или Python. Они не обязаны ничего знать о потоках и буферах в Node. Может быть, они немного разобрались в CommonJS, но едва ли они продвинулись в ее изучении дальше своей зоны комфорта. Такие люди вряд ли откажутся от Grunt в пользу gulp.

В то время, как gulp легче для чтения и восприятия, Grunt легче в написании, что порой гораздо важнее.

В целом, gulp ориентирован на сборку, и, в особенности, на работу с файлами. Ключевой принцип API Gulp можно сформулировать как «In, Out, Watch» («Туда, Обратно, Наблюдаем», прим. ред.). Это здорово, потому что позволяет сконцентрироваться на конкретной задаче по проекту, а именно, его сборке. Именно в этом-то и заключается основная идея — вы передаете файл на вход в систему, затем получаете его обратно, и где-то в это же время с файлом происходят нужные вам преобразования. Но у этой краеугольной идеи, заложенной в API, есть очевидный недостаток. Манипуляции, которые не относятся непосредственно к процессу сборки, становятся выполнить гораздо сложнее. Например, отправка уведомления об окончании сборки или запуск Amazon EC2-серверов противоречат тому, для чего был разработан gulp.

Конечно же, gulp очень молод, и мы можем наблюдать, как развивается его экосистема, но я не стал бы ждать, что разработка задач под эту систему будет более популярна, чем под Grunt. Я просто не верю, что gulp может «побить» Grunt в непрерывной интеграции и в области развертки приложений. Но, с другой стороны, gulp может полностью взять на себя более простые процессы, такие как, например, сборка клиентской части и публикация ее на Heroku.

Gulp не переплюнет Grunt

Хотя gulp и может привлечь столь необходимое внимание к таск-менеджерам и расширить границы того, что они сейчас могут, я приведу ниже несколько причин, почему я считаю, что gulp не вытеснит окончательно Grunt. Во-первых, gulp и не собирается вытеснять Grunt. По крайней мере, никто не преследует подобную цель, и уж точно, ее не преследуют лидеры индустрии.

Grunt против gulp: оба служат разным потребностям и могут существовать сообща. Смотрите: http://t.co/fQ5G8HDNwl и https://t.co/joUVeRqj8T. Мы поддерживаем обоих.

— Addy Osmani (@addyosmani) 8 Января, 2014

Возможно, для вас это и новость, но вряд ли это такой уж сюрприз. В текущую эко-систему Grunt было вложено много усилий, и едва ли хотя бы один из её разработчиков окончательно всё бросит только ради того, чтобы портировать свои наработки и плагины под gulp — пусть и горячего, но все же новичка. Выбор, на самом деле, очень прост — придерживаться того, что у вас уже есть, или бросится в омут по новой, в погоне за популярным и неизведанным.

Во-вторых, как я уже писал ранее, в gulp есть определенный порог входа, которого нет у Grunt. Без опыта в Node.js у вас будут большие трудности с пониманием механизма работы потоков, передачей данных между этими потоками, с буферами, с асинхронным JavaScript в целом, с промисами, коллбеками, да с чем угодно вообще. И, учитывая все эти факторы, я просто не представляю, как gulp может просочиться в среду далеких от Node.js людей и применяться там как система для сборки фронтенда.

Кроме всего вышеперечисленного, Gulp не решает никаких новых проблем. Серьезно. Его API великолепно и целенаправленно, но оно вызывает затруднения в задачах, не касающихся сборки проекта — в этой сфере, безусловно, побеждает Grunt. В npm зарегистрировано больше 2000 плагинов Grunt, в то время как для gulp их написано всего около 200. Интересно наблюдать за экспериментами, в которых gulp запускает задачи Grunt, но я сомневаюсь, что эта практика приживется.

Я не думаю, что симбиоз /Gr?u(nt|lp)/ сделает вашу жизнь легче, вряд ли. Если вам кажется, что вам нужны обе системы сборки, то, вероятно, вам стоит остановиться только на Grunt.

regex.png

Можно также рассмотреть фактор скорости работы этих систем. Но я предлагаю вам подумать над этим самостоятельно. Более важно, на мой взгляд, понять, что здесь нет единственно верного ответа. Может быть gulp и быстрее, но возможности Grunt могут быть куда более обширными, и, в конце концов, вы отдадите свои предпочтения в пользу одного из таск-менеджеров. Но, ради бога, не используйте оба в одном проекте, не будьте как этот парень.

Только что перешел на проекте с #gruntjs на #gulpjs - код проще и время подготовки билда сократилось с 3-5 сек до 10-20 мс. Я не шучу.

— Andy Joslin (@andytjoslin) 27 Декабря, 2013

Не стоит упускать из виду еще один важный фактор — за все дни Grunt не подал и виду, будто бы это противостояние его хоть как-то волнует. И это спокойствие заставляет по-настоящему волноваться.

Кабанчик как будто уже несвежий

Grunt может потухнуть сам по себе. Он целую вечность находился в версии 0.4.1, перед тем как сдвинуться к скромной 0.4.2, а сейчас, кажется, что он вообще никуда не двигается. За все эти дни, практически не было активности в твиттере @gruntjs. Вряд ли это хороший знак.

Я действительно надеюсь, что это такой переходный период, и за кулисами планируется 0.5.0, но меня не покидает ощущение, что команда разработчиков переключилась на другой проект. Я бы не стал однозначно говорить, что Grunt заброшен, но у меня есть такие опасения, потому что я не вижу, никаких признаков активной работы над ним. Мне действительно очень сильно хочется увидеть релиз версии 1.0.0, с переосмысленной структурой конфигурации, с учетом тех проблем, о которых мы говорили выше. Хочется увидеть простую загрузку плагинов, встроенный в ядро watch, подобно тому, как это сделал gulp, простую семантическую структуру файлов и более легкую настройку задач в целом.

Конечно, просто хотеть такие штуки замечательно, но вот реализовать их, не сломав при этом большинство из 2000 работающих плагинов, крайне тяжело. Учитывая, что эко-система плагинов в Grunt является одной из его главных ценностей — сформировать разумный план развития приложения становится чрезвычайно сложно. Остается только сидеть и ждать, либо вы можете попробовать выйти вперед и предложить что-нибудь, что вы бы лично хотели видеть в версии 0.5.0.

Аргументы за то, что бы ничего не делать

Правые Unix-экстремисты снова и снова предлагают перестать плодить сущности, и выкинуть gulp и Grunt из головы. Вот так вот, просто ничего не делать. Я, по правде говоря, не согласен с таким типом экстремизма, но вам может быть более комфортно, если вы напишете все на чистом JavaScript. Я даже думаю, что в этом может быть определенный смысл. Но в данном случае, я считаю, что уже само наличие npm run — достаточный аргументом к существованию gulp.

Да, gulp достаточно близка философия отказа от создания лишних сущностей, но в каких-то моментах он все-таки ближе к Grunt, который, наоборот, весьма далек от этой идеи. Я думаю, разработчики gulp придают значение обеспечению поддержки Windows, хоть это и приносит определенную сложность, так что это действительно компромисс. Вам просто нужно честно спросить себя, что вы ищете. И, если вам нужно что-то действительно очень и очень простое, то вам лучше использовать npm run.

Поддержка Windows вряд ли имеет много смысла, потому что большинство из нас работают на *nix, но это важно для других сообществ, куда Grunt похоже постепенно приходит. Думаю, вы можете использовать кое-что от bash в Windows, но делать там что-то в командной строке — все еще боль. Да и вообще, вряд ли это серьезный аргумент, чтобы не использовать Grunt на Windows.

nothing.png

Что ж, используйте Gulp или Grunt. Да все равно, по большему счету.

Все равно, конечно, но помните

В простоте обучения людей сборке Grunt выигрывает, да и то, его вряд ли легко [понять любому][21], но он проигрывает в лаконичности. В лаконичности выигрывает gulp, у него просто великолепный API. И в то же время, его минус в высоком пороге вхождения, в первую очередь потому, что потоковую передачу данных достаточно трудно понять. В области малых усилий и рисков находится npm run. Он хорош тем, что не плодит лишних сущностей, там вообще нет ничего лишнего. Его очевидный минус в кросс-платформенности, если это для вас важно.

Выбирайте сами. Не стоит принимать решение, основываясь на чьих-то словах. Выбирайте инструмент, который работает для вас. Тот, который вы понимаете, и с которым вам удобно работать. А важнее всего, тот, который делает именно то, что вам нужно. Не делайте выбор вслепую, выбирая наугад, или потому, что кто-то вам что-то рассказал об этом инструменте. Так же, как, например, не стоит застревать, используя монолитные jQuery приложения (это просто аналогия), пробуйте что-то новое! Будьте открыты к инновациям.

Мы сами должны стать теми переменами, которые хотим увидеть в мире. Махатма Ганди

А сейчас мне надо напиться.

[21]: http://24ways.org/2013/grunt-is-not-weird-and-hard/ “Grunt for People Who Think Things Like Grunt are Weird and Hard”"